diff --git a/BioScientist/.DS_Store b/BioScientist/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..be5d8e2c218db901b686a644d8c61bfb20ae2377 Binary files /dev/null and b/BioScientist/.DS_Store differ diff --git a/BioScientist/.env.example b/BioScientist/.env.example new file mode 100644 index 0000000000000000000000000000000000000000..e1cb349a52d0af150da1d9bf918145c6048110a0 --- /dev/null +++ b/BioScientist/.env.example @@ -0,0 +1,12 @@ +AZURE_OPENAI_API_VERSION = "2024-12-01-preview" # or the latest version available +MODEL_NAME = "" # e.g., "gpt-4o", "gpt-4o-mini", "gpt-4.1-turbo", "gpt-4.1-mini" + +# If you are using Azure OpenAI, set the following variables +AZURE_OPENAI_ENDPOINT='https://.cognitiveservices.azure.com/' +AZURE_OPENAI_KEY='' + +# If you are using OpenAI, set the following variable +OPENAI_API_KEY = '' + +# If you want to test it into an Anthropic Environment, set the following variable +ANTHROPIC_API_KEY = '' \ No newline at end of file diff --git a/BioScientist/.gitignore b/BioScientist/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..936c690055ac6bdf383360bb178854cf6a4fcb8b --- /dev/null +++ b/BioScientist/.gitignore @@ -0,0 +1,11 @@ +.env +agent_system/toolbase/output/ +agent_system/toolbase/mcp_batch_from_help_txt/ +agent_system/toolbase/mcp_batch_from_manual_txt/ +shared_knowledge/ +data/ +agent_system/toolbase/output/benchmark/outputs/ +*.safetensors +*.pt +*.bin +*.pth diff --git a/BioScientist/.ssh/config b/BioScientist/.ssh/config new file mode 100644 index 0000000000000000000000000000000000000000..446fcc880ec555a1b8ce199990c5afe2e4e501c0 --- /dev/null +++ b/BioScientist/.ssh/config @@ -0,0 +1,5 @@ +Host github.com + HostName github.com + User git + IdentityFile /225040511/.ssh/id_ed25519 + IdentitiesOnly yes diff --git a/BioScientist/.ssh/id_ed25519 b/BioScientist/.ssh/id_ed25519 new file mode 100644 index 0000000000000000000000000000000000000000..2635c419f6dfc5348203407d5b774acc2b3766dd --- /dev/null +++ b/BioScientist/.ssh/id_ed25519 @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACD5TjQiYWgUxn31139cNMR2LVw7LwefRg7EzzwP0w5v0QAAAJgx8g6IMfIO +iAAAAAtzc2gtZWQyNTUxOQAAACD5TjQiYWgUxn31139cNMR2LVw7LwefRg7EzzwP0w5v0Q +AAAEBfMcilCyky26QzJUwewu0iHwy/NiAz8FTeWBwccGlXgPlONCJhaBTGffXXf1w0xHYt +XDsvB59GDsTPPA/TDm/RAAAAEGN6dHkxOUBnbWFpbC5jb20BAgMEBQ== +-----END OPENSSH PRIVATE KEY----- diff --git a/BioScientist/.ssh/id_ed25519.pub b/BioScientist/.ssh/id_ed25519.pub new file mode 100644 index 0000000000000000000000000000000000000000..837f81d2271c57ff922a5610a59bd9218cea0e81 --- /dev/null +++ b/BioScientist/.ssh/id_ed25519.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPlONCJhaBTGffXXf1w0xHYtXDsvB59GDsTPPA/TDm/R czty19@gmail.com \ No newline at end of file diff --git a/BioScientist/README.md b/BioScientist/README.md new file mode 100644 index 0000000000000000000000000000000000000000..3569573acc058c231176ab32b65e2b5c1900f2f7 --- /dev/null +++ b/BioScientist/README.md @@ -0,0 +1,237 @@ +# BioScientist Agent System + +An orchestration layer for **agentic bioinformatics workflows** that combines: + +- Dynamic MCP tool registration +- Dual-mode reasoning (`T1`) and execution (`V1`) +- Layered validation (`E1`) with Agent System integration +- File-based shared memory for continual improvement + +--- + +## Why This Repository Exists + +`BioScientist/agent_system` is designed to run practical computational biology tasks with a structured loop: + +1. Propose hypotheses and strategies +2. Validate them with increasing rigor (L1 -> L4) +3. Capture execution evidence and insights +4. Reuse those insights for better next-round planning + +This gives you a reproducible bridge between **LLM reasoning**, **MCP tools**, and **real data artifacts**. + +--- + +## Architecture Overview + +### Core Components + +- `V1 Executor` (`engines/v1_executor.py`) + - Pipeline-style task executor + - Routes tools through MCP servers + - Supports local and Docker backends + - Persists run artifacts and reports + +- `T1 Consultant` (`engines/t1_consultant.py`) + - Reflection and strategy engine + - Generates hypotheses (with data-grounded operations) + - Ranks hypotheses by historical success proxies + +- `E1 Validator` (`engines/e1_validator.py`) + - Multi-level validation engine: + - `L1`: rule consistency + - `L2`: Agent System/MCP readiness + - `L3`: lightweight data-backed validation + - `L4`: extended data-backed validation + - Calls Agent System runtime `A1.go(...)` when available + +- `Shared Knowledge Space` (`shared_memory.py`) + - File-based memory bridge across modules + - Stores experiment reports, hypotheses, validation reports, insights, and summary statistics + +- `Orchestrator` (`orchestrator.py`) + - Unified entrypoint wiring `V1 + T1 + E1` + - Exposes high-level modes like: + - `execute` + - `consult` + - `autopilot` + - `hypothesis-generate` + - `hypothesis-loop` + +### Runtime Flow (Hypothesis Loop) + +```text +User Query + | + v +T1: generate + rank hypotheses + | + v +E1: optional MCP registration -> validation (L1/L2/L3/L4) + | + v +Agent System runtime (A1 + add_mcp + go) [when enabled/available] + | + v +Artifacts + Reports + Insights -> Shared Knowledge +``` + +--- + +## Project Layout (Key Paths) + +```text +BioScientist/ +├── agent_system/ +│ ├── main.py +│ ├── orchestrator.py +│ ├── engines/ +│ │ ├── v1_executor.py +│ │ ├── t1_consultant.py +│ │ └── e1_validator.py +│ ├── shared_memory.py +│ ├── toolbase/ +│ │ ├── data/biomni_data/ +│ │ └── register_mcp_servers_to_biomni.py +│ └── results/ +└── README.md +``` + +--- + +## Environment Setup (Required) + +Our software environment is large. We provide a single setup script to bootstrap dependencies. + +### 1) Activate the E1 environment first + +```bash +conda activate biomni_e1 +``` + +### 2) Install the official pip package first + +```bash +pip install biomni --upgrade +``` + +### 3) Run the unified setup script + +```bash +# from the repository root +bash setup.sh +``` + +### 4) (Optional, recommended) Install the latest Biomni from source + +```bash +pip install git+https://github.com/snap-stanford/Biomni.git@main +``` + +--- + +## Quick Start: Hypothesis Loop (Single-Cell Normalization) + +### 1) Set Environment Variables + +Use your current setup pattern: + +```bash +export GEMINI_API_KEY="" +export T1_MODEL_BACKEND="gemini" +export GEMINI_MODEL="gemini-2.5-flash-lite" +export GEMINI_TEMPERATURE="0.2" +export GEMINI_TIMEOUT_SECONDS="60" +export BIOMNI_PATH=/225040511/project/Biomni/data +export BIOMNI_SOURCE="Gemini" +``` + +Optional (recommended for explicit control): + +```bash +export BIOCLAW_BIOMNI_ROOT=/225040511/project/BioScientist/agent_system/engines/v1_executor_backup +``` + +### 2) Run the End-to-End Loop + +```bash +python -m agent_system.main \ + --project_root /225040511/project/BioScientist \ + hypothesis-loop \ + --task_scope single_cell_normalization \ + --user_query "Give me new ideas for normalizing single-cell data" \ + --n 2 \ + --top_k 1 \ + --validate_top_m 1 \ + --validation_level L4 \ + --register_mcp true +``` + +### 3) Where to Find Outputs + +- Loop-level result: + - `agent_system/results/hypothesis-loop_.json` +- Validation runtime reports: + - `agent_system/results/e1_runtime/*.json` +- Data-backed L3/L4 artifacts: + - `agent_system/results/l3_reports/*.json` + - `agent_system/results/l4_reports/*.json` +- Agent System execution payloads: + - `agent_system/results/biomni_exec/*.json` + +--- + +## Other CLI Modes + +### Generate hypotheses only + +```bash +python -m agent_system.main \ + --project_root /225040511/project/BioScientist \ + hypothesis-generate \ + --task_scope single_cell_normalization \ + --user_query "Give me new ideas for normalizing single-cell data" \ + --n 10 \ + --top_k 5 +``` + +### Register MCP servers only + +```bash +python -m agent_system.main \ + --project_root /225040511/project/BioScientist \ + register-mcp +``` + +### Consult mode (strategy only) + +```bash +python -m agent_system.main \ + --project_root /225040511/project/BioScientist \ + consult \ + --task_scope single_cell_normalization \ + --user_goal "Design a robust normalization strategy for cross-batch scRNA-seq." +``` + +--- + +## Notes and Troubleshooting + +- If Agent System runtime returns API/provider errors (for example region restrictions), E1 may return `inconclusive` even when local data checks succeed. +- L3/L4 still produce useful tabular evidence (`rows_scanned`, `missing_rate`, numeric summaries, relevance scores). +- If you want fully offline execution, switch to a local model source (for example Ollama) and update relevant environment variables. +- Large MCP sets are automatically reduced in E1 probe mode via minimal config generation for faster startup. + +--- + +## Status Semantics + +- `success`: validation/execution reached expected criteria +- `inconclusive`: partial evidence available but one or more critical external/runtime checks failed +- `failed`: contradiction or hard runtime failure detected + +--- + +## License + +This repository is released under the MIT License (see `LICENSE`). diff --git a/BioScientist/agent_system/__init__.py b/BioScientist/agent_system/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..22b8e381e9be7317b73369414f37661b7c754210 --- /dev/null +++ b/BioScientist/agent_system/__init__.py @@ -0,0 +1,11 @@ +"""Dual-mode agent system for BioinfoMCP. + +This package introduces: +- BioinfoV1Executor: deterministic execution engine +- BioinfoT1Consultant: reflection and strategy engine +- SharedKnowledgeSpace: bridge for reports, insights, and pipeline configs +""" + +from .orchestrator import DualModeAgentSystem + +__all__ = ["DualModeAgentSystem"] diff --git a/BioScientist/agent_system/bioinfomcp_converter.py b/BioScientist/agent_system/bioinfomcp_converter.py new file mode 100644 index 0000000000000000000000000000000000000000..e286d2ae1ac5bd7bc821c2d1c087cdfb94c646d7 --- /dev/null +++ b/BioScientist/agent_system/bioinfomcp_converter.py @@ -0,0 +1,254 @@ +import os +import subprocess +import requests +import ast # this is just for check code syntax whether or not it is correct +import re +from typing import List +import pymupdf +from dotenv import load_dotenv +from pathlib import Path + +load_dotenv() + + +''' +# 0. Check whether that particular library is already installed (otherwise cannot run the help function) +# 1. Run the --help function +# 2. Let GPT-4 analyze the result from the help manual, attain the + a) What tools are there + i) For each tools, what is the input, output + ii) the CLI command, error handling + iii) capture output + iv) check output whether it is correct +''' + + +class BioinfoMCP(): + def __init__(self, model="openai"): + prompt_path = Path(__file__).resolve().parent / "system_prompt.txt" + with open(prompt_path, "r", encoding="utf-8") as file: + self.sys_prompt = file.read() + self.backend_model = model.lower() + self.api_model_name = os.getenv('MODEL_NAME') + self.client = None + + if self.backend_model == "azure": + from openai import AzureOpenAI + # Load Environment Keys + api_subscription_key = os.getenv('AZURE_OPENAI_KEY') + api_version = os.getenv('AZURE_OPENAI_API_VERSION') + api_endpoint = os.getenv('AZURE_OPENAI_ENDPOINT') + # initialize the Azure Client + self.client = AzureOpenAI( + api_version=api_version, + azure_endpoint=api_endpoint, + api_key=api_subscription_key, + ) + self.api_model_name = self.api_model_name or os.getenv("AZURE_OPENAI_MODEL") + + elif self.backend_model == "openai": + from openai import OpenAI + # Load Environment Keys + openai_api_key = os.getenv('OPENAI_API_KEY') + # initialize the OpenAI Client + self.client = OpenAI( + api_key=openai_api_key + ) + self.api_model_name = self.api_model_name or os.getenv("OPENAI_MODEL_NAME") + + elif self.backend_model == "gemini": + # Gemini uses HTTP API directly via requests + self.gemini_api_key = os.getenv("GEMINI_API_KEY") + self.api_model_name = self.api_model_name or os.getenv("GEMINI_MODEL_NAME") or "gemini-1.5-pro" + self.gemini_api_base = os.getenv("GEMINI_API_BASE", "https://generativelanguage.googleapis.com") + fallback_raw = os.getenv("GEMINI_MODEL_FALLBACKS", "gemini-2.0-flash,gemini-1.5-pro,gemini-1.5-flash") + self.gemini_model_fallbacks: List[str] = [m.strip() for m in fallback_raw.split(",") if m.strip()] + self.max_help_chars = int(os.getenv("BIOINFOMCP_MAX_HELP_CHARS", "60000")) + else: + raise ValueError(f"Unsupported model backend: {model}. Use one of openai/azure/gemini") + + print(f"Succesfully created backend={self.backend_model}, model={self.api_model_name}") + + + + def is_tool_available(self, tool_name): + """Check whether that tool is installed or not""" + try: + _ = subprocess.run([tool_name, '--version'], + capture_output=True, timeout=20, text=True) + print(f"{tool_name} is installed") + return True + except: + print(f"{tool_name} is not installed!") + return False + + def extract_help_document(self, tool_name, manual, run_help_command=False): + """Extract help text from tool""" + if not run_help_command: # document provided by user, no need to run the help command + manual_path = Path(str(manual)) + if manual_path.suffix.lower() == ".pdf" and manual_path.exists(): + manual_doc = pymupdf.open(f'{manual}') + manual_content = "" + for page in manual_doc: + text = page.get_text() + manual_content += text + return manual_content + if manual_path.exists(): + return manual_path.read_text(encoding="utf-8", errors="ignore") + return str(manual) + + if self.is_tool_available(tool_name): + try: + result = subprocess.run([tool_name, manual], + capture_output=True, timeout=30, text=True) + return result.stdout + result.stderr + except: + return None + return None + + def _call_openai_like(self, prompt): + response = self.client.chat.completions.create( + messages=[ + { + "role" : "system", + "content": [{"type": "text", "text": self.sys_prompt}], + }, + { + "role" : "user", + "content": [{"type": "text", "text": prompt}], + } + ], + temperature=0.1, + model=self.api_model_name + ) + return response.choices[0].message.content + + def _call_gemini(self, prompt): + if not getattr(self, "gemini_api_key", None): + raise ValueError("GEMINI_API_KEY is required for gemini backend") + models = [self.api_model_name] + [m for m in self.gemini_model_fallbacks if m != self.api_model_name] + headers = { + "Content-Type": "application/json", + "x-goog-api-key": self.gemini_api_key, + } + errors = [] + for model_name in models: + url = f"{self.gemini_api_base}/v1beta/models/{model_name}:generateContent" + payload = { + "system_instruction": { + "parts": [{"text": self.sys_prompt}] + }, + "contents": [ + { + "role": "user", + "parts": [{"text": prompt}], + } + ], + "generationConfig": { + "temperature": 0.1, + }, + } + try: + resp = requests.post(url, headers=headers, json=payload, timeout=180) + if resp.status_code >= 400: + body = resp.text[:1200] + errors.append(f"{model_name}: HTTP {resp.status_code} - {body}") + continue + data = resp.json() + candidates = data.get("candidates", []) + if not candidates: + errors.append(f"{model_name}: empty candidates") + continue + parts = candidates[0].get("content", {}).get("parts", []) + texts = [p.get("text", "") for p in parts if isinstance(p, dict)] + joined = "\n".join(texts).strip() + if joined: + return joined + errors.append(f"{model_name}: empty text in candidate parts") + except Exception as exc: + errors.append(f"{model_name}: exception {exc}") + continue + raise RuntimeError("Gemini request failed across all models. " + " | ".join(errors[-3:])) + + def _call_llm(self, prompt): + if self.backend_model in ("openai", "azure"): + return self._call_openai_like(prompt) + if self.backend_model == "gemini": + return self._call_gemini(prompt) + raise ValueError(f"Unsupported backend: {self.backend_model}") + + def generate_prompt(self, tool_name, help_docs): + """Generate the Prompt that later will be send to the OpenAI client""" + prompt = f""" +Convert the following bioinformatics tool into an MCP tool definition. + +Tool Name: {tool_name} +Help Document: +{help_docs} + +Parse the Input parameters correctly, follow the MCP best practices, and provide the complete python code with the @mcp.tool() decorator. +""" + return prompt + + def parse_mcpcode(self, gpt_response): + code_block = re.findall('```python\n(.*?)\n```', gpt_response, re.DOTALL) + if not code_block: + return (0, "There is no python code found in the gpt_response", None) + + code = code_block[0] + try: + ast.parse(code) + except SyntaxError as e: + return (0, f"Code is not working with the following SyntaxError: {e}", code) + + # Check the @mcp.tool() decorator + if '@mcp.tool' not in code: + return (0, "Code is missing the @mcp.tool() decorator", code) + + return (1, None, code) + + def refine_after_feedback(self, tool_name, code, error_message): + prompt = f""" + The initial code block for {tool_name}: + ```python + {code} + ``` + contains the following Error: + {error_message} + + Please fix the code and ensure that + 1. Cover every internal functions of the tool + 2. Has proper error handling + 3. Validates input parameters + 4. Returns structured output + 5. Follows MCP best practices + + Provide only the corrected python code. + + """ + response_content = self._call_llm(prompt) + # output_file = open(f'./mcp_result/raw_{tool_name}', 'w') + # output_file.write(response_content) + return self.parse_mcpcode(response_content) + + def autogenerate_mcp_tool(self, tool_name, manual, run_help_command): + """Autogenerate the MCP Tool using the extracted help documents""" + help_docs = self.extract_help_document(tool_name, manual, run_help_command) + if help_docs is None: + help_docs = "" + if self.backend_model == "gemini": + max_chars = getattr(self, "max_help_chars", 60000) + if len(help_docs) > max_chars: + kept = help_docs[:max_chars] + help_docs = ( + f"{kept}\n\n" + f"[Truncated help document: original_length={len(help_docs)}, kept={max_chars}]" + ) + # web_docs = self.fetch_web_docs(tool_name) + prompt = self.generate_prompt(tool_name, help_docs) + response_content = self._call_llm(prompt) + #print(response_content) + # output_file = open(f'./mcp_result/raw_{tool_name}', 'w') + # output_file.write(response.choices[0].message.content) + + return self.parse_mcpcode(response_content) diff --git a/BioScientist/agent_system/engines/__init__.py b/BioScientist/agent_system/engines/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..176c235a9c99784d3ccee1ed22984afdfceffcda --- /dev/null +++ b/BioScientist/agent_system/engines/__init__.py @@ -0,0 +1,5 @@ +from .v1_executor import BioinfoV1Executor +from .t1_consultant import BioinfoT1Consultant +from .e1_validator import BioinfoE1Validator + +__all__ = ["BioinfoV1Executor", "BioinfoT1Consultant", "BioinfoE1Validator"] diff --git a/BioScientist/agent_system/engines/bioinfo_platform_pipleline.svg b/BioScientist/agent_system/engines/bioinfo_platform_pipleline.svg new file mode 100644 index 0000000000000000000000000000000000000000..b61b46af93a15d0c8e051ce412097ce5835f9c47 --- /dev/null +++ b/BioScientist/agent_system/engines/bioinfo_platform_pipleline.svg @@ -0,0 +1,34 @@ +sequenceDiagram + autonumber + participant U as 用户 + participant FE as Frontend (ChatBox/api.js) + participant BE as FastAPI (/chat/stream) + participant SM as SessionManager + participant AG as V1ExecutorAgent + participant V1 as BioinfoV1Executor + participant RT as MCPToolRouter + participant MCP as MCP Tool / Docker or Python + + U->>FE: 输入消息并点击 Run + FE->>BE: GET /chat/stream?message&session_id (SSE) + BE->>SM: get_agent(session_id) + SM-->>BE: 返回 V1ExecutorAgent + BE->>AG: go(message) (线程中执行) + + AG->>AG: 解析会话上传文件并构建 manifest(r1/r2) + AG->>V1: execute_task(task, input_manifest, task_scope) + V1->>V1: 生成 plan(qc/trim/align/...) + + loop 每个计划步骤 + V1->>RT: 选择/解析可用 MCP route + RT-->>V1: 返回 tool function + server script + V1->>MCP: 调用工具(docker 或 python backend) + MCP-->>V1: step result / artifact / error + AG-->>BE: print Thought/Action/Observation + BE-->>FE: SSE 事件(thinking/tool_use/observation) + end + + V1-->>AG: 返回 report(to_dict) + AG-->>BE: print 最终汇总 + BE-->>FE: SSE 事件(result + done) + FE-->>U: 左侧展示最终结果,右侧展示推理步骤 diff --git a/BioScientist/agent_system/engines/e1_v1_sequence.svg b/BioScientist/agent_system/engines/e1_v1_sequence.svg new file mode 100644 index 0000000000000000000000000000000000000000..568c478a6949f7eebd92728d32241bcafe5c83b3 --- /dev/null +++ b/BioScientist/agent_system/engines/e1_v1_sequence.svg @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + User + + + Orchestrator + + + E1 (T1 Agent) + + + V1 Executor + + + Shared Memory + + + MCP Stack + + + LLM APIs + + + + + Request: execute/autopilot(task, data, scope) + + + consult(user_goal, task_scope) + + + list_insights() / list_reports() + + + Claude: strategy recommendation JSON + + + recommendation + risk checks + + + execute_task(task, manifest, cfg) + + + plan steps + resolve MCP routes + + + route info / binary availability + + + MCP toolcalls: qc_raw -> trim -> align? -> aggregate + + + step results (stdout/stderr/artifacts) + + + Gemini planner for source_execute (repo command plan) + + + planned safe commands / fallback signals + + + save_report(summary.json, pipeline.log) + + + reflect() after run (optional) + + + save_insight() / save_pipeline_config() + + + Deliver: final report + reproducibility summary + next strategy + + + + Legend: E1 = T1 Consultant (thinking/strategy), V1 = execution engine. MCP Stack includes router, MCP servers, and converter fallback. + + + + + + + + + + + diff --git a/BioScientist/agent_system/engines/e1_validator.py b/BioScientist/agent_system/engines/e1_validator.py new file mode 100644 index 0000000000000000000000000000000000000000..6a6df79b24f65af4c1a294b19def72a9c8645585 --- /dev/null +++ b/BioScientist/agent_system/engines/e1_validator.py @@ -0,0 +1,1400 @@ +from __future__ import annotations + +import csv +import json +import os +import subprocess +import sys +import importlib.util +import statistics +import urllib.request +from datetime import datetime, timezone +from pathlib import Path +from typing import Any + +from ..schemas import Hypothesis, Insight, ValidationReport +from ..shared_memory import SharedKnowledgeSpace + + +class BioinfoE1Validator: + """ + E1 validator: layered and cost-aware hypothesis validation. + First version: + - L1: logical consistency / rule checks + - L2: light tooling readiness check using Biomni + registered MCP servers + - L3: lightweight data-backed validation on biomni_data + - L4: extended data-backed validation with fuller passes and numeric checks + """ + + LEVEL_CONFIDENCE = { + "L1": 0.30, + "L2": 0.60, + "L3": 0.85, + "L4": 0.95, + } + + def __init__( + self, + memory: SharedKnowledgeSpace, + biomni_root: str | Path, + mcp_config_path: str | Path, + project_root: str | Path, + ): + self.memory = memory + self.biomni_root = Path(biomni_root).resolve() + self.mcp_config_path = Path(mcp_config_path).resolve() + self.project_root = Path(project_root).resolve() + self._registration_state: dict[str, Any] | None = None + self._biomni_probe_cache: dict[str, Any] | None = None + self._biomni_agent_cache: dict[str, Any] | None = None + + def _resolve_register_script(self) -> Path | None: + candidates = [ + # when project_root=/.../BioScientist/agent_system + self.project_root / "toolbase" / "register_mcp_servers_to_biomni.py", + # when project_root=/.../BioScientist + self.project_root / "agent_system" / "toolbase" / "register_mcp_servers_to_biomni.py", + # fallback based on current file location + Path(__file__).resolve().parents[1] / "toolbase" / "register_mcp_servers_to_biomni.py", + ] + for c in candidates: + if c.exists(): + return c + return None + + def ensure_mcp_registered(self, dry_run: bool = False) -> dict[str, Any]: + script = self._resolve_register_script() + if script is None: + return { + "ok": False, + "status": "missing_script", + "error": ( + "register script not found under either " + f"{self.project_root}/toolbase or {self.project_root}/agent_system/toolbase" + ), + } + cmd = [ + sys.executable, + str(script), + "--biomni-root", + str(self.biomni_root), + "--config-out", + str(self.mcp_config_path), + ] + if dry_run: + cmd.append("--dry-run") + try: + completed = subprocess.run(cmd, capture_output=True, text=True, check=False) + state = { + "ok": completed.returncode == 0, + "status": "registered" if completed.returncode == 0 else "failed", + "return_code": completed.returncode, + "stdout_tail": "\n".join((completed.stdout or "").splitlines()[-20:]), + "stderr_tail": "\n".join((completed.stderr or "").splitlines()[-20:]), + "config_path": str(self.mcp_config_path), + } + self._registration_state = state + return state + except Exception as exc: + state = {"ok": False, "status": "failed", "error": str(exc), "config_path": str(self.mcp_config_path)} + self._registration_state = state + return state + + def _coerce_hypothesis(self, hypothesis: Hypothesis | dict[str, Any]) -> dict[str, Any]: + return hypothesis.to_dict() if isinstance(hypothesis, Hypothesis) else hypothesis + + def _prepare_biomni_import(self) -> dict[str, Any]: + """ + Ensure `import biomni` works without requiring pip install -e. + """ + info = { + "biomni_root": str(self.biomni_root), + "path_injected": False, + "package_dir_exists": (self.biomni_root / "biomni").exists(), + } + root_str = str(self.biomni_root) + if root_str not in sys.path: + sys.path.insert(0, root_str) + info["path_injected"] = True + return info + + def _resolve_biomni_data_root(self) -> Path: + env = os.getenv("BIOCLAW_BIOMNI_DATA_ROOT", "").strip() + if env: + p = Path(env).expanduser().resolve() + if p.exists(): + return p + candidates = [ + self.project_root / "agent_system" / "toolbase" / "data" / "biomni_data", + self.project_root / "toolbase" / "data" / "biomni_data", + Path("/225040511/project/BioScientist/agent_system/toolbase/data/biomni_data"), + ] + for c in candidates: + if c.exists(): + return c + return candidates[0] + + def _resolve_biomni_llm_source(self) -> tuple[str, str]: + """ + Resolve runtime LLM/source for Biomni execution. + Priority: + 1) BIOCLAW_BIOMNI_LLM / BIOCLAW_BIOMNI_SOURCE + 2) GEMINI_MODEL / BIOMNI_SOURCE + 3) safe Gemini defaults + """ + llm = ( + os.getenv("BIOCLAW_BIOMNI_LLM", "").strip() + or os.getenv("GEMINI_MODEL", "").strip() + or "gemini-2.5-flash-lite" + ) + source = ( + os.getenv("BIOCLAW_BIOMNI_SOURCE", "").strip() + or os.getenv("BIOMNI_SOURCE", "").strip() + or "Gemini" + ) + return llm, source + + def _quick_biomni_probe(self, hypothesis_text: str = "", force: bool = False) -> dict[str, Any]: + """ + Minimal-cost Biomni runtime probe. + Reuses cached probe; otherwise builds/uses a cached Biomni agent context. + """ + if self._biomni_probe_cache is not None and not force: + cached = dict(self._biomni_probe_cache) + cached["cache_hit"] = True + return cached + + t0 = datetime.now(timezone.utc) + ctx = self._get_biomni_agent_context(hypothesis_text, force_rebuild=force) + dt = datetime.now(timezone.utc) - t0 + if not ctx.get("ok", False): + result = { + "ok": False, + "status": "biomni_probe_failed", + "error": str(ctx.get("error", "unknown")), + "mcp_registration_ok": bool(ctx.get("trace", {}).get("mcp_registration_ok", False)), + "duration_ms": int(dt.total_seconds() * 1000), + "cache_hit": False, + "mcp_config_used": str(ctx.get("trace", {}).get("mcp_config_used", self.mcp_config_path)), + **(ctx.get("trace", {}) if isinstance(ctx.get("trace", {}), dict) else {}), + } + self._biomni_probe_cache = result + return result + + trace = ctx.get("trace", {}) if isinstance(ctx.get("trace", {}), dict) else {} + result = { + "ok": True, + "status": "ready", + "mcp_registration_ok": True, + "custom_tool_count": int(trace.get("custom_tool_count", 0)), + "related_tool_hits": 0, + "duration_ms": int(dt.total_seconds() * 1000), + "cache_hit": False, + "mcp_config_used": str(trace.get("mcp_config_used", self.mcp_config_path)), + "loaded_server_count": int(trace.get("loaded_server_count", -1)), + "minimal_config_selected_count": int(trace.get("minimal_config_selected_count", 0)), + "full_config_server_count": int(trace.get("full_config_server_count", 0)), + "path_injected": bool(trace.get("path_injected", False)), + "package_dir_exists": bool(trace.get("package_dir_exists", False)), + "biomni_root": str(trace.get("biomni_root", self.biomni_root)), + } + self._biomni_probe_cache = result + return result + + def _get_biomni_agent_context(self, hypothesis_text: str = "", force_rebuild: bool = False) -> dict[str, Any]: + """ + Build/reuse a Biomni A1 agent with MCP loaded. + Returns a context dict: + { + ok: bool, + agent: A1 | None, + trace: dict[str, Any], + error: str + } + """ + if self._biomni_agent_cache is not None and not force_rebuild: + return self._biomni_agent_cache + + registration = self._registration_state or self.ensure_mcp_registered(dry_run=False) + if not registration.get("ok", False): + ctx = { + "ok": False, + "agent": None, + "trace": {"mcp_registration_ok": False}, + "error": "mcp_registration_failed", + } + self._biomni_agent_cache = ctx + return ctx + + deps_ok, missing_modules, deps_evidence = self._check_l2_dependencies() + if not deps_ok: + ctx = { + "ok": False, + "agent": None, + "trace": { + "mcp_registration_ok": True, + "missing_modules": missing_modules, + }, + "error": deps_evidence, + } + self._biomni_agent_cache = ctx + return ctx + + try: + import_info = self._prepare_biomni_import() + from biomni.agent import A1 + llm_name, llm_source = self._resolve_biomni_llm_source() + + min_cfg = self._build_minimal_mcp_config(hypothesis_text) + used_config_path = str(self.mcp_config_path) + loaded_server_count = -1 + minimal_server_count = 0 + full_server_count = 0 + if min_cfg is not None: + minimal_server_count = int(min_cfg.get("selected_count", 0)) + full_server_count = int(min_cfg.get("full_count", 0)) + if minimal_server_count > 0: + used_config_path = str(min_cfg["path"]) + + agent = A1(llm=llm_name, source=llm_source) + agent.add_mcp(config_path=used_config_path) + tools = agent.list_custom_tools() or [] + if min_cfg is not None and minimal_server_count > 0: + loaded_server_count = minimal_server_count + elif min_cfg is not None: + loaded_server_count = full_server_count + if not tools and used_config_path != str(self.mcp_config_path): + agent = A1(llm=llm_name, source=llm_source) + agent.add_mcp(config_path=str(self.mcp_config_path)) + tools = agent.list_custom_tools() or [] + used_config_path = str(self.mcp_config_path) + loaded_server_count = full_server_count if full_server_count > 0 else -1 + + ctx = { + "ok": True, + "agent": agent, + "trace": { + "custom_tool_count": len(tools), + "mcp_config_used": used_config_path, + "loaded_server_count": loaded_server_count, + "minimal_config_selected_count": minimal_server_count, + "full_config_server_count": full_server_count, + "biomni_llm": llm_name, + "biomni_source": llm_source, + **import_info, + }, + "error": "", + } + self._biomni_agent_cache = ctx + return ctx + except Exception as exc: + ctx = { + "ok": False, + "agent": None, + "trace": {"mcp_registration_ok": True}, + "error": str(exc), + } + self._biomni_agent_cache = ctx + return ctx + + @staticmethod + def _extract_json_like_block(text: str) -> dict[str, Any]: + s = (text or "").strip() + if not s: + return {} + try: + return json.loads(s) if s.startswith("{") else {} + except Exception: + pass + # naive fallback: find first {...} + start = s.find("{") + end = s.rfind("}") + if start >= 0 and end > start: + block = s[start : end + 1] + try: + return json.loads(block) + except Exception: + return {} + return {} + + def _execute_hypothesis_with_biomni(self, h: dict[str, Any]) -> dict[str, Any]: + """ + Execute hypothesis using Biomni directly (A1.go) with lightweight prompt. + Returns execution summary and artifact path. + """ + text = f"{h.get('title', '')} {h.get('hypothesis', '')}".strip() + ctx = self._get_biomni_agent_context(text) + if not ctx.get("ok", False): + return { + "ok": False, + "error": f"biomni_agent_unavailable: {ctx.get('error', 'unknown')}", + "trace": ctx.get("trace", {}), + } + + agent = ctx.get("agent") + data_sources = [str(x) for x in h.get("data_sources", [])][:5] + ops = [x for x in h.get("data_operations", []) if isinstance(x, dict)] + prompt = ( + "You are validating a bioinformatics hypothesis in lightweight mode.\n" + "Use available MCP tools where useful and provide concise execution output.\n" + "Return JSON with keys: status, summary, key_metrics, recommendation.\n\n" + f"Domain: {h.get('domain', '')}\n" + f"Hypothesis title: {h.get('title', '')}\n" + f"Hypothesis: {h.get('hypothesis', '')}\n" + f"Expected improvement: {h.get('expected_improvement', '')}\n" + f"Theoretical basis: {h.get('theoretical_basis', '')}\n" + f"Data sources: {json.dumps(data_sources, ensure_ascii=True)}\n" + f"Requested operations: {json.dumps(ops, ensure_ascii=True)}\n" + "Please execute a minimal-cost validation and return machine-readable JSON." + ) + + try: + logs, final_message = agent.go(prompt) + parsed = self._extract_json_like_block(str(final_message)) + status_raw = str(parsed.get("status", "")).lower() + mapped_status = "success" if status_raw in {"success", "ok", "passed"} else ("failed" if status_raw in {"failed", "error"} else "inconclusive") + summary = str(parsed.get("summary", "")).strip() or str(final_message)[:1000] + key_metrics = parsed.get("key_metrics", {}) if isinstance(parsed.get("key_metrics", {}), dict) else {} + recommendation = str(parsed.get("recommendation", "")).strip() + + out_dir = self.project_root / "agent_system" / "results" / "biomni_exec" + out_dir.mkdir(parents=True, exist_ok=True) + ts = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ") + out_path = out_dir / f"biomni_exec_{h.get('hypothesis_id', 'unknown')}_{ts}.json" + out_payload = { + "hypothesis_id": h.get("hypothesis_id", ""), + "domain": h.get("domain", ""), + "prompt": prompt, + "parsed": parsed, + "status": mapped_status, + "summary": summary, + "key_metrics": key_metrics, + "recommendation": recommendation, + "final_message": str(final_message), + "log_tail": logs[-20:] if isinstance(logs, list) else [], + "trace": ctx.get("trace", {}), + "saved_at": datetime.now(timezone.utc).isoformat(), + } + out_path.write_text(json.dumps(out_payload, indent=2, ensure_ascii=True), encoding="utf-8") + return { + "ok": True, + "status": mapped_status, + "summary": summary, + "key_metrics": key_metrics, + "recommendation": recommendation, + "artifact_file": str(out_path), + "trace": ctx.get("trace", {}), + } + except Exception as exc: + return { + "ok": False, + "error": f"biomni_execution_failed: {exc}", + "trace": ctx.get("trace", {}), + } + + def _build_minimal_mcp_config(self, hypothesis_text: str) -> dict[str, Any] | None: + yaml_spec = importlib.util.find_spec("yaml") + if yaml_spec is None: + return None + import yaml # type: ignore + + if not self.mcp_config_path.exists(): + return None + + try: + raw = self.mcp_config_path.read_text(encoding="utf-8") + doc = yaml.safe_load(raw) or {} + except Exception: + return None + servers = doc.get("mcp_servers", {}) + if not isinstance(servers, dict) or not servers: + return None + + full_count = len(servers) + max_servers = int(os.getenv("BIOCLAW_MIN_MCP_SERVERS", "40")) + max_servers = max(5, min(200, max_servers)) + + q = (hypothesis_text or "").lower() + tokens = [t for t in q.replace("-", " ").replace("_", " ").split() if len(t) >= 3][:30] + + scored: list[tuple[int, str, dict[str, Any]]] = [] + for name, cfg in servers.items(): + if not isinstance(cfg, dict): + continue + cmd = " ".join([str(x).lower() for x in cfg.get("command", [])]) if isinstance(cfg.get("command"), list) else str(cfg.get("command", "")).lower() + desc = str(cfg.get("description", "")).lower() + hay = f"{name.lower()} {desc} {cmd}" + s = 0 + for tok in tokens: + if tok in hay: + s += 2 + if "single" in hay or "cell" in hay or "rna" in hay: + s += 1 + scored.append((s, name, cfg)) + + scored.sort(key=lambda x: x[0], reverse=True) + chosen = [x for x in scored if x[0] > 0][:max_servers] + if len(chosen) < min(10, max_servers): + # Backfill with top entries to keep probe meaningful. + fallback_pool = [x for x in scored if x[1] not in {c[1] for c in chosen}] + need = min(10, max_servers) - len(chosen) + chosen.extend(fallback_pool[:need]) + if not chosen: + chosen = scored[: min(10, max_servers)] + if not chosen: + return None + + selected_servers = {name: cfg for _, name, cfg in chosen} + out_dir = self.project_root / "agent_system" / "results" / "e1_runtime" / "min_configs" + out_dir.mkdir(parents=True, exist_ok=True) + ts = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ") + out_path = out_dir / f"mcp_min_{ts}.yaml" + out_path.write_text( + yaml.safe_dump({"mcp_servers": selected_servers}, sort_keys=False, allow_unicode=False), + encoding="utf-8", + ) + return { + "path": out_path, + "selected_count": len(selected_servers), + "full_count": full_count, + } + + def _persist_runtime_artifact(self, report_dict: dict[str, Any], hypothesis: dict[str, Any]) -> str: + runtime_dir = self.project_root / "agent_system" / "results" / "e1_runtime" + runtime_dir.mkdir(parents=True, exist_ok=True) + ts = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ") + out_path = runtime_dir / f"{report_dict.get('level', 'Lx').lower()}_{report_dict.get('validation_id', 'unknown')}_{ts}.json" + payload = { + "validation": report_dict, + "hypothesis": { + "hypothesis_id": hypothesis.get("hypothesis_id", ""), + "domain": hypothesis.get("domain", ""), + "title": hypothesis.get("title", ""), + }, + "saved_at": datetime.now(timezone.utc).isoformat(), + } + out_path.write_text(json.dumps(payload, indent=2, ensure_ascii=True), encoding="utf-8") + + summary_path = runtime_dir / "summary.json" + if summary_path.exists(): + try: + summary = json.loads(summary_path.read_text(encoding="utf-8")) + except Exception: + summary = {} + else: + summary = {} + by_level = summary.setdefault("by_level", {}) + lvl = report_dict.get("level", "UNKNOWN") + s = by_level.setdefault(lvl, {"count": 0, "success": 0, "inconclusive": 0, "failed": 0, "avg_score": 0.0}) + old_count = int(s.get("count", 0)) + old_avg = float(s.get("avg_score", 0.0)) + new_score = float(report_dict.get("score", 0.0)) + new_count = old_count + 1 + s["count"] = new_count + s["avg_score"] = (old_avg * old_count + new_score) / max(1, new_count) + st = str(report_dict.get("status", "inconclusive")) + s[st] = int(s.get(st, 0)) + 1 + summary["total_reports"] = int(summary.get("total_reports", 0)) + 1 + summary["last_report_file"] = str(out_path) + summary["last_updated"] = datetime.now(timezone.utc).isoformat() + summary_path.write_text(json.dumps(summary, indent=2, ensure_ascii=True), encoding="utf-8") + return str(out_path) + + def _index_data_files(self, data_root: Path, limit: int = 1000) -> list[Path]: + if not data_root.exists(): + return [] + files: list[Path] = [] + for p in sorted(data_root.rglob("*")): + if p.is_file(): + files.append(p) + if len(files) >= limit: + break + return files + + def _download_online_example_data(self, domain: str, max_files: int = 2) -> list[Path]: + """ + Best-effort fallback downloader when no usable local dataset is available. + Downloads small public tabular files so L3/L4 can still produce bound validation reports. + """ + out_dir = self.project_root / "agent_system" / "results" / "downloaded_example_data" + out_dir.mkdir(parents=True, exist_ok=True) + + domain_l = str(domain).lower() + single_cell_candidates = [ + ( + "pbmc3k_marker_genes.csv", + "https://raw.githubusercontent.com/scverse/scanpy-tutorials/main/pbmc3k/marker_genes.csv", + ), + ( + "pbmc3k_obs_metadata.csv", + "https://raw.githubusercontent.com/scverse/scanpy-tutorials/main/pbmc3k/obs_metadata.csv", + ), + ] + generic_candidates = [ + ( + "iris.csv", + "https://raw.githubusercontent.com/mwaskom/seaborn-data/master/iris.csv", + ), + ( + "tips.csv", + "https://raw.githubusercontent.com/mwaskom/seaborn-data/master/tips.csv", + ), + ] + candidates = single_cell_candidates + generic_candidates if ("single" in domain_l or "cell" in domain_l) else generic_candidates + single_cell_candidates + + downloaded: list[Path] = [] + for filename, url in candidates: + target = out_dir / filename + if target.exists() and target.stat().st_size > 0: + downloaded.append(target) + if len(downloaded) >= max_files: + break + continue + try: + urllib.request.urlretrieve(url, str(target)) + if target.exists() and target.stat().st_size > 0: + downloaded.append(target) + if len(downloaded) >= max_files: + break + except Exception: + continue + return downloaded + + def _safe_read_tabular_preview(self, file_path: Path, sample_rows: int = 500) -> dict[str, Any]: + suffix = file_path.suffix.lower() + result: dict[str, Any] = { + "file": str(file_path), + "suffix": suffix, + "status": "skipped", + "rows_scanned": 0, + "columns": [], + "missing_cells": 0, + "total_cells": 0, + "numeric_columns": [], + } + + if suffix in {".csv", ".tsv"}: + delim = "," if suffix == ".csv" else "\t" + with file_path.open("r", encoding="utf-8", errors="ignore", newline="") as fh: + reader = csv.DictReader(fh, delimiter=delim) + cols = reader.fieldnames or [] + result["columns"] = cols + rows = 0 + missing = 0 + total = 0 + numeric_candidates: dict[str, int] = {c: 0 for c in cols} + for row in reader: + rows += 1 + for c in cols: + v = (row.get(c) or "").strip() + total += 1 + if v == "": + missing += 1 + else: + try: + float(v) + numeric_candidates[c] += 1 + except Exception: + pass + if rows >= sample_rows: + break + result["rows_scanned"] = rows + result["missing_cells"] = missing + result["total_cells"] = total + result["numeric_columns"] = [c for c, n in numeric_candidates.items() if n > 0] + result["status"] = "loaded" + return result + + if suffix == ".parquet": + pd_spec = importlib.util.find_spec("pandas") + if pd_spec is None: + result["status"] = "skipped" + result["error"] = "pandas_not_installed_for_parquet" + return result + import pandas as pd # type: ignore + + df = pd.read_parquet(file_path) + if len(df) > sample_rows: + df = df.head(sample_rows) + result["rows_scanned"] = int(len(df)) + cols = [str(c) for c in df.columns.tolist()] + result["columns"] = cols + total = int(df.shape[0] * df.shape[1]) if df.shape[1] > 0 else 0 + missing = int(df.isna().sum().sum()) if total > 0 else 0 + numeric_cols = [str(c) for c in df.select_dtypes(include=["number"]).columns.tolist()] + result["missing_cells"] = missing + result["total_cells"] = total + result["numeric_columns"] = numeric_cols + result["status"] = "loaded" + return result + + if suffix == ".json": + with file_path.open("r", encoding="utf-8", errors="ignore") as fh: + payload = json.load(fh) + if isinstance(payload, list): + rows = min(sample_rows, len(payload)) + cols = sorted({k for item in payload[:rows] if isinstance(item, dict) for k in item.keys()}) + result["rows_scanned"] = rows + result["columns"] = cols + result["status"] = "loaded" + elif isinstance(payload, dict): + result["rows_scanned"] = 1 + result["columns"] = sorted(payload.keys()) + result["status"] = "loaded" + else: + result["status"] = "loaded" + return result + + result["status"] = "skipped" + result["error"] = f"unsupported_suffix:{suffix or 'none'}" + return result + + def _safe_read_tabular_full(self, file_path: Path, max_rows: int = 200000) -> dict[str, Any]: + """ + L4-oriented data pass. + Attempts a fuller scan (or up to max_rows for safety) and includes basic numeric summaries. + """ + suffix = file_path.suffix.lower() + result: dict[str, Any] = { + "file": str(file_path), + "suffix": suffix, + "status": "skipped", + "rows_scanned": 0, + "columns": [], + "missing_cells": 0, + "total_cells": 0, + "numeric_columns": [], + "truncated": False, + "numeric_summary": {}, + } + + if suffix in {".csv", ".tsv"}: + delim = "," if suffix == ".csv" else "\t" + with file_path.open("r", encoding="utf-8", errors="ignore", newline="") as fh: + reader = csv.DictReader(fh, delimiter=delim) + cols = reader.fieldnames or [] + result["columns"] = cols + rows = 0 + missing = 0 + total = 0 + numeric_samples: dict[str, list[float]] = {c: [] for c in cols} + for row in reader: + rows += 1 + for c in cols: + v = (row.get(c) or "").strip() + total += 1 + if v == "": + missing += 1 + else: + try: + fv = float(v) + if len(numeric_samples[c]) < 5000: + numeric_samples[c].append(fv) + except Exception: + pass + if rows >= max_rows: + result["truncated"] = True + break + result["rows_scanned"] = rows + result["missing_cells"] = missing + result["total_cells"] = total + numeric_cols = [c for c, arr in numeric_samples.items() if arr] + result["numeric_columns"] = numeric_cols + summary = {} + for c in numeric_cols[:20]: + arr = numeric_samples[c] + summary[c] = { + "count": len(arr), + "mean": float(statistics.fmean(arr)), + "min": float(min(arr)), + "max": float(max(arr)), + } + result["numeric_summary"] = summary + result["status"] = "loaded" + return result + + if suffix == ".parquet": + pd_spec = importlib.util.find_spec("pandas") + if pd_spec is None: + result["status"] = "skipped" + result["error"] = "pandas_not_installed_for_parquet" + return result + import pandas as pd # type: ignore + + df = pd.read_parquet(file_path) + if len(df) > max_rows: + df = df.head(max_rows) + result["truncated"] = True + result["rows_scanned"] = int(len(df)) + cols = [str(c) for c in df.columns.tolist()] + result["columns"] = cols + total = int(df.shape[0] * df.shape[1]) if df.shape[1] > 0 else 0 + missing = int(df.isna().sum().sum()) if total > 0 else 0 + numeric_cols = [str(c) for c in df.select_dtypes(include=["number"]).columns.tolist()] + result["missing_cells"] = missing + result["total_cells"] = total + result["numeric_columns"] = numeric_cols + summary = {} + for c in numeric_cols[:20]: + s = df[c].dropna() + if len(s) == 0: + continue + summary[str(c)] = { + "count": int(len(s)), + "mean": float(s.mean()), + "min": float(s.min()), + "max": float(s.max()), + } + result["numeric_summary"] = summary + result["status"] = "loaded" + return result + + if suffix == ".json": + with file_path.open("r", encoding="utf-8", errors="ignore") as fh: + payload = json.load(fh) + if isinstance(payload, list): + rows = len(payload) + if rows > max_rows: + rows = max_rows + result["truncated"] = True + cols = sorted({k for item in payload[:rows] if isinstance(item, dict) for k in item.keys()}) + result["rows_scanned"] = rows + result["columns"] = cols + result["status"] = "loaded" + elif isinstance(payload, dict): + result["rows_scanned"] = 1 + result["columns"] = sorted(payload.keys()) + result["status"] = "loaded" + else: + result["status"] = "loaded" + return result + + result["status"] = "skipped" + result["error"] = f"unsupported_suffix:{suffix or 'none'}" + return result + + @staticmethod + def _domain_relevance_score(domain: str, selected_files: list[Path], per_file_results: list[dict[str, Any]]) -> tuple[float, list[str]]: + domain_l = str(domain).lower() + if not selected_files: + return 0.0, ["no_selected_files"] + hints = [] + if "single_cell" in domain_l or "single" in domain_l: + hints = ["single", "cell", "rna", "census", "marker", "celltype", "expression"] + else: + hints = [x for x in domain_l.split("_") if x] + + matched = 0 + explanations: list[str] = [] + for p in selected_files: + p_low = str(p).lower() + if any(h in p_low for h in hints): + matched += 1 + explanations.append(f"file_match:{p.name}") + for r in per_file_results: + cols = " ".join([str(c).lower() for c in r.get("columns", [])]) + if any(h in cols for h in hints): + matched += 1 + explanations.append(f"column_match:{Path(str(r.get('file', 'unknown'))).name}") + denom = max(1, len(selected_files) + len(per_file_results)) + return min(1.0, matched / denom), explanations[:10] + + @staticmethod + def _build_interpretability_summary( + *, + level: str, + loaded_count: int, + selected_count: int, + rows_scanned: int, + missing_rate: float, + numeric_cols_total: int, + relevance_score: float, + ) -> dict[str, Any]: + quality = "high" if missing_rate < 0.01 else ("medium" if missing_rate < 0.1 else "low") + coverage = loaded_count / max(1, selected_count) + coverage_label = "high" if coverage >= 0.8 else ("medium" if coverage >= 0.5 else "low") + relevance_label = "high" if relevance_score >= 0.6 else ("medium" if relevance_score >= 0.3 else "low") + return { + "level": level, + "data_quality": quality, + "coverage": coverage_label, + "domain_relevance": relevance_label, + "rows_scanned": rows_scanned, + "numeric_signal_strength": "high" if numeric_cols_total >= 10 else ("medium" if numeric_cols_total >= 3 else "low"), + "recommendations": [ + "Prefer domain-relevant files if domain_relevance is low.", + "Increase file coverage when coverage is medium/low.", + "Escalate to L4 only after L3 relevance is acceptable.", + ], + } + + @staticmethod + def _check_l2_dependencies() -> tuple[bool, list[str], str]: + required_modules = ["nest_asyncio", "mcp", "yaml"] + missing = [m for m in required_modules if importlib.util.find_spec(m) is None] + if not missing: + return True, [], "" + + module_to_pip = { + "nest_asyncio": "nest_asyncio", + "mcp": "mcp", + "yaml": "PyYAML", + } + pip_pkgs = [module_to_pip[m] for m in missing] + install_cmd = f"{sys.executable} -m pip install " + " ".join(pip_pkgs) + evidence = ( + "L2 dependency check failed. Missing modules: " + f"{', '.join(missing)}. " + f"Install with: {install_cmd}. " + f"(module->pip: {', '.join(f'{m}->{module_to_pip[m]}' for m in missing)})" + ) + return False, missing, evidence + + def _validate_l1(self, h: dict[str, Any]) -> tuple[str, float, dict[str, Any], str, str]: + text = f"{h.get('title', '')} {h.get('hypothesis', '')}".lower() + violations: list[str] = [] + if "敲除" in text and "增殖加快" in text: + violations.append("possible essential-gene-growth contradiction") + if "不存在的工具" in text: + violations.append("depends on non-existing tool") + if "必须" in text and "不需要数据" in text: + violations.append("self-contradictory requirement") + + if violations: + return ( + "failed", + 0.1, + {"rule_violations": violations, "violation_count": len(violations)}, + "biological_or_tooling_inconsistency", + "L1 rules detected contradictions.", + ) + return ( + "success", + 0.6, + {"rule_violations": [], "violation_count": 0}, + "", + "L1 rules found no obvious contradiction.", + ) + + def _validate_l2(self, h: dict[str, Any]) -> tuple[str, float, dict[str, Any], str, str]: + registration = self._registration_state or self.ensure_mcp_registered(dry_run=False) + if not registration.get("ok", False): + return ( + "inconclusive", + 0.3, + {"mcp_registration_ok": False}, + "mcp_registration_failed", + "L2 could not ensure MCP registration.", + ) + + deps_ok, missing_modules, deps_evidence = self._check_l2_dependencies() + if not deps_ok: + return ( + "inconclusive", + 0.2, + { + "mcp_registration_ok": True, + "l2_dependency_check_ok": False, + "missing_modules": missing_modules, + }, + "biomni_dependency_missing", + deps_evidence, + ) + + try: + import_info = self._prepare_biomni_import() + from biomni.agent import A1 + llm_name, llm_source = self._resolve_biomni_llm_source() + + agent = A1(llm=llm_name, source=llm_source) + agent.add_mcp(config_path=str(self.mcp_config_path)) + tools = agent.list_custom_tools() or [] + if not tools: + return ( + "inconclusive", + 0.35, + { + "mcp_registration_ok": True, + "custom_tool_count": 0, + "biomni_llm": llm_name, + "biomni_source": llm_source, + **import_info, + }, + "no_mcp_tools_discovered", + "Biomni loaded config but no tools were discovered.", + ) + + text = f"{h.get('title', '')} {h.get('hypothesis', '')}".lower() + related = [t for t in tools if any(tok in t.lower() for tok in text.split()[:20])] + score = 0.5 + min(0.4, len(related) * 0.05) + return ( + "success", + score, + { + "mcp_registration_ok": True, + "custom_tool_count": len(tools), + "related_tool_hits": len(related), + "biomni_llm": llm_name, + "biomni_source": llm_source, + **import_info, + }, + "", + "L2 passed using Biomni MCP tool readiness check.", + ) + except Exception as exc: + import_info = self._prepare_biomni_import() + return ( + "inconclusive", + 0.25, + {"mcp_registration_ok": True, **import_info}, + "biomni_probe_failed", + f"Biomni probe failed: {exc}", + ) + + def _validate_l3(self, h: dict[str, Any]) -> tuple[str, float, dict[str, Any], str, str]: + probe = self._quick_biomni_probe(f"{h.get('title', '')} {h.get('hypothesis', '')}") + if not probe.get("ok", False): + return ( + "inconclusive", + 0.25, + { + "mcp_registration_ok": bool(probe.get("mcp_registration_ok", False)), + "biomni_probe_status": probe.get("status", ""), + "biomni_probe_duration_ms": probe.get("duration_ms", 0), + "biomni_call_trace": { + "registration_attempted": True, + "biomni_runtime_called": False, + "cache_hit": bool(probe.get("cache_hit", False)), + }, + }, + str(probe.get("status", "biomni_probe_failed")), + f"L3 aborted: Biomni quick probe failed ({probe.get('error', 'unknown_error')}).", + ) + + data_root = self._resolve_biomni_data_root() + data_files = self._index_data_files(data_root, limit=1500) + if not data_files: + return ( + "inconclusive", + 0.3, + {"data_root": str(data_root), "indexed_files": 0}, + "l3_data_unavailable", + "L3 could not locate biomni_data files.", + ) + + requested = [Path(str(x)) for x in h.get("data_sources", []) if str(x).strip()] + if requested: + selected = [p for p in requested if p.exists()][:5] + else: + selected = data_files[:5] + downloaded_examples: list[Path] = [] + if not selected: + downloaded_examples = self._download_online_example_data(h.get("domain", ""), max_files=2) + selected = downloaded_examples[:] + + operations = [x for x in h.get("data_operations", []) if isinstance(x, dict)] + if not operations: + operations = [ + {"action": "load_table", "target": "all_selected"}, + {"action": "sample_rows", "rows": 500}, + {"action": "profile_missingness"}, + {"action": "numeric_summary"}, + ] + + per_file_results: list[dict[str, Any]] = [] + loaded_count = 0 + for p in selected: + try: + r = self._safe_read_tabular_preview(p, sample_rows=500) + per_file_results.append(r) + if r.get("status") == "loaded": + loaded_count += 1 + except Exception as exc: + per_file_results.append( + { + "file": str(p), + "status": "failed", + "error": str(exc), + } + ) + + op_names = [str(op.get("action", "")) for op in operations] + rows_scanned = sum(int(r.get("rows_scanned", 0) or 0) for r in per_file_results) + total_cells = sum(int(r.get("total_cells", 0) or 0) for r in per_file_results) + missing_cells = sum(int(r.get("missing_cells", 0) or 0) for r in per_file_results) + missing_rate = (missing_cells / total_cells) if total_cells > 0 else 0.0 + numeric_cols_total = sum(len(r.get("numeric_columns", []) or []) for r in per_file_results) + relevance_score, relevance_evidence = self._domain_relevance_score(h.get("domain", ""), selected, per_file_results) + + results_dir = self.project_root / "agent_system" / "results" / "l3_reports" + results_dir.mkdir(parents=True, exist_ok=True) + ts = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ") + artifact_path = results_dir / f"l3_{h.get('hypothesis_id', 'unknown')}_{ts}.json" + artifact_payload = { + "hypothesis_id": h.get("hypothesis_id", ""), + "domain": h.get("domain", ""), + "data_root": str(data_root), + "selected_files": [str(p) for p in selected], + "operations": operations, + "per_file_results": per_file_results, + "rows_scanned": rows_scanned, + "missing_rate": missing_rate, + } + artifact_path.write_text(json.dumps(artifact_payload, indent=2, ensure_ascii=True), encoding="utf-8") + + if loaded_count == 0: + return ( + "inconclusive", + 0.35, + { + "data_root": str(data_root), + "indexed_files": len(data_files), + "selected_files": [str(p) for p in selected], + "loaded_files": 0, + "operations_requested": op_names, + "l3_report_file": str(artifact_path), + }, + "l3_no_loadable_data", + "L3 selected files but none were loadable in lightweight mode.", + ) + + score_components = { + "base": 0.45, + "loaded_files_component": 0.08 * loaded_count, + "operation_component": 0.03 * min(8, len(op_names)), + "domain_relevance_component": 0.08 * relevance_score, + } + score = min(0.92, sum(score_components.values())) + status = "success" if loaded_count >= 1 else "inconclusive" + biomni_exec = self._execute_hypothesis_with_biomni(h) + if biomni_exec.get("ok", False): + b_status = biomni_exec.get("status", "inconclusive") + score = min(1.0, score + (0.08 if b_status == "success" else 0.0)) + if b_status == "failed": + status = "failed" + elif status != "failed" and b_status == "inconclusive": + status = "inconclusive" + else: + score = max(0.0, score - 0.08) + if status == "success": + status = "inconclusive" + evidence = ( + "L3 executed lightweight full-data checks with sampled previews; " + f"loaded {loaded_count}/{len(selected)} files, scanned_rows={rows_scanned}, " + f"missing_rate={missing_rate:.4f}, domain_relevance={relevance_score:.2f}. Report: {artifact_path}" + ) + return ( + status, + score, + { + "data_root": str(data_root), + "indexed_files": len(data_files), + "selected_files": [str(p) for p in selected], + "loaded_files": loaded_count, + "rows_scanned": rows_scanned, + "missing_rate": missing_rate, + "numeric_columns_total": numeric_cols_total, + "operations_requested": op_names, + "domain_relevance_score": relevance_score, + "domain_relevance_evidence": relevance_evidence, + "score_breakdown": score_components, + "interpretability_summary": self._build_interpretability_summary( + level="L3", + loaded_count=loaded_count, + selected_count=len(selected), + rows_scanned=rows_scanned, + missing_rate=missing_rate, + numeric_cols_total=numeric_cols_total, + relevance_score=relevance_score, + ), + "biomni_call_trace": { + "registration_attempted": bool(self._registration_state), + "biomni_runtime_called": True, + "cache_hit": bool(probe.get("cache_hit", False)), + "biomni_probe_duration_ms": probe.get("duration_ms", 0), + "custom_tool_count": probe.get("custom_tool_count", 0), + "related_tool_hits": probe.get("related_tool_hits", 0), + "mcp_config_used": probe.get("mcp_config_used", str(self.mcp_config_path)), + "loaded_server_count": probe.get("loaded_server_count", -1), + "minimal_config_selected_count": probe.get("minimal_config_selected_count", 0), + "full_config_server_count": probe.get("full_config_server_count", 0), + "note": "L3 first performs a minimal Biomni runtime probe, then runs lightweight data checks.", + }, + "downloaded_example_files": [str(p) for p in downloaded_examples], + "l3_report_file": str(artifact_path), + "biomni_execution": biomni_exec, + }, + "" if biomni_exec.get("ok", False) else "biomni_execution_failed", + evidence, + ) + + def _validate_l4(self, h: dict[str, Any]) -> tuple[str, float, dict[str, Any], str, str]: + probe = self._quick_biomni_probe(f"{h.get('title', '')} {h.get('hypothesis', '')}") + if not probe.get("ok", False): + return ( + "inconclusive", + 0.3, + { + "mcp_registration_ok": bool(probe.get("mcp_registration_ok", False)), + "biomni_probe_status": probe.get("status", ""), + "biomni_probe_duration_ms": probe.get("duration_ms", 0), + "biomni_call_trace": { + "registration_attempted": True, + "biomni_runtime_called": False, + "cache_hit": bool(probe.get("cache_hit", False)), + }, + }, + str(probe.get("status", "biomni_probe_failed")), + f"L4 aborted: Biomni quick probe failed ({probe.get('error', 'unknown_error')}).", + ) + + data_root = self._resolve_biomni_data_root() + data_files = self._index_data_files(data_root, limit=3000) + if not data_files: + return ( + "inconclusive", + 0.3, + {"data_root": str(data_root), "indexed_files": 0}, + "l4_data_unavailable", + "L4 could not locate biomni_data files.", + ) + + requested = [Path(str(x)) for x in h.get("data_sources", []) if str(x).strip()] + if requested: + selected = [p for p in requested if p.exists()][:8] + else: + selected = data_files[:5] + downloaded_examples: list[Path] = [] + if not selected: + downloaded_examples = self._download_online_example_data(h.get("domain", ""), max_files=3) + selected = downloaded_examples[:] + + operations = [x for x in h.get("data_operations", []) if isinstance(x, dict)] + if not operations: + operations = [ + {"action": "load_table", "target": "all_selected"}, + {"action": "sample_rows", "rows": 500}, + {"action": "profile_missingness"}, + {"action": "numeric_summary"}, + ] + + max_rows = int(os.getenv("BIOCLAW_L4_MAX_ROWS", "200000")) + max_rows = max(1000, min(1000000, max_rows)) + + per_file_results: list[dict[str, Any]] = [] + loaded_count = 0 + truncated_count = 0 + for p in selected: + try: + r = self._safe_read_tabular_full(p, max_rows=max_rows) + per_file_results.append(r) + if r.get("status") == "loaded": + loaded_count += 1 + if r.get("truncated"): + truncated_count += 1 + except Exception as exc: + per_file_results.append( + { + "file": str(p), + "status": "failed", + "error": str(exc), + } + ) + + rows_scanned = sum(int(r.get("rows_scanned", 0) or 0) for r in per_file_results) + total_cells = sum(int(r.get("total_cells", 0) or 0) for r in per_file_results) + missing_cells = sum(int(r.get("missing_cells", 0) or 0) for r in per_file_results) + missing_rate = (missing_cells / total_cells) if total_cells > 0 else 0.0 + numeric_cols_total = sum(len(r.get("numeric_columns", []) or []) for r in per_file_results) + relevance_score, relevance_evidence = self._domain_relevance_score(h.get("domain", ""), selected, per_file_results) + op_names = [str(op.get("action", "")) for op in operations] + + results_dir = self.project_root / "agent_system" / "results" / "l4_reports" + results_dir.mkdir(parents=True, exist_ok=True) + ts = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ") + artifact_path = results_dir / f"l4_{h.get('hypothesis_id', 'unknown')}_{ts}.json" + artifact_payload = { + "hypothesis_id": h.get("hypothesis_id", ""), + "domain": h.get("domain", ""), + "data_root": str(data_root), + "selected_files": [str(p) for p in selected], + "operations": operations, + "max_rows_per_file": max_rows, + "per_file_results": per_file_results, + "rows_scanned": rows_scanned, + "missing_rate": missing_rate, + "numeric_columns_total": numeric_cols_total, + "truncated_file_count": truncated_count, + } + artifact_path.write_text(json.dumps(artifact_payload, indent=2, ensure_ascii=True), encoding="utf-8") + + if loaded_count == 0: + return ( + "inconclusive", + 0.35, + { + "data_root": str(data_root), + "indexed_files": len(data_files), + "selected_files": [str(p) for p in selected], + "loaded_files": 0, + "l4_report_file": str(artifact_path), + }, + "l4_no_loadable_data", + "L4 selected files but none were loadable.", + ) + + completeness = 1.0 - min(1.0, missing_rate * 10.0) + coverage = min(1.0, loaded_count / max(1, len(selected))) + richness = min(1.0, numeric_cols_total / 20.0) + trunc_penalty = 0.08 if truncated_count > 0 else 0.0 + score_components = { + "base": 0.45, + "coverage_component": 0.25 * coverage, + "completeness_component": 0.2 * completeness, + "richness_component": 0.15 * richness, + "domain_relevance_component": 0.1 * relevance_score, + "truncation_penalty": -trunc_penalty, + } + score = max(0.0, min(1.0, sum(score_components.values()))) + status = "success" if (loaded_count >= max(1, min(2, len(selected))) and completeness >= 0.5) else "inconclusive" + biomni_exec = self._execute_hypothesis_with_biomni(h) + if biomni_exec.get("ok", False): + b_status = biomni_exec.get("status", "inconclusive") + score = min(1.0, score + (0.1 if b_status == "success" else 0.0)) + if b_status == "failed": + status = "failed" + elif status != "failed" and b_status == "inconclusive": + status = "inconclusive" + else: + score = max(0.0, score - 0.1) + if status == "success": + status = "inconclusive" + evidence = ( + "L4 executed extended data checks; " + f"loaded {loaded_count}/{len(selected)} files, rows_scanned={rows_scanned}, " + f"missing_rate={missing_rate:.4f}, numeric_columns_total={numeric_cols_total}, domain_relevance={relevance_score:.2f}, " + f"truncated_files={truncated_count}. Report: {artifact_path}" + ) + return ( + status, + score, + { + "data_root": str(data_root), + "indexed_files": len(data_files), + "selected_files": [str(p) for p in selected], + "loaded_files": loaded_count, + "rows_scanned": rows_scanned, + "missing_rate": missing_rate, + "numeric_columns_total": numeric_cols_total, + "truncated_file_count": truncated_count, + "operations_requested": op_names, + "domain_relevance_score": relevance_score, + "domain_relevance_evidence": relevance_evidence, + "score_breakdown": score_components, + "interpretability_summary": self._build_interpretability_summary( + level="L4", + loaded_count=loaded_count, + selected_count=len(selected), + rows_scanned=rows_scanned, + missing_rate=missing_rate, + numeric_cols_total=numeric_cols_total, + relevance_score=relevance_score, + ), + "biomni_call_trace": { + "registration_attempted": bool(self._registration_state), + "biomni_runtime_called": True, + "cache_hit": bool(probe.get("cache_hit", False)), + "biomni_probe_duration_ms": probe.get("duration_ms", 0), + "custom_tool_count": probe.get("custom_tool_count", 0), + "related_tool_hits": probe.get("related_tool_hits", 0), + "mcp_config_used": probe.get("mcp_config_used", str(self.mcp_config_path)), + "loaded_server_count": probe.get("loaded_server_count", -1), + "minimal_config_selected_count": probe.get("minimal_config_selected_count", 0), + "full_config_server_count": probe.get("full_config_server_count", 0), + "note": "L4 first performs a minimal Biomni runtime probe, then runs extended data checks.", + }, + "downloaded_example_files": [str(p) for p in downloaded_examples], + "l4_report_file": str(artifact_path), + "biomni_execution": biomni_exec, + }, + "" if biomni_exec.get("ok", False) else "biomni_execution_failed", + evidence, + ) + + def validate_hypothesis(self, hypothesis: Hypothesis | dict[str, Any], level: str = "L1") -> dict[str, Any]: + h = self._coerce_hypothesis(hypothesis) + level = level.upper().strip() + if level not in self.LEVEL_CONFIDENCE: + level = "L1" + + if level == "L1": + status, score, metrics, failure_reason, evidence = self._validate_l1(h) + elif level == "L2": + status, score, metrics, failure_reason, evidence = self._validate_l2(h) + elif level == "L3": + status, score, metrics, failure_reason, evidence = self._validate_l3(h) + elif level == "L4": + status, score, metrics, failure_reason, evidence = self._validate_l4(h) + else: + status, score, metrics, failure_reason, evidence = ( + "inconclusive", + 0.4, + {"implemented": False}, + f"{level}_not_implemented_in_v1", + f"{level} validation is scaffolded in first version.", + ) + + report = ValidationReport.build( + hypothesis_id=h.get("hypothesis_id", ""), + domain=h.get("domain", ""), + level=level, + status=status, + score=score, + confidence=self.LEVEL_CONFIDENCE[level], + key_metrics=metrics, + failure_reason=failure_reason, + evidence=evidence, + ) + self.memory.save_validation_report(report) + # Save an insight so T1 can leverage execution outcomes as experience. + try: + title = f"{h.get('domain', 'general')} {level} {status}" + recommendation = report.evidence + biomni_exec = report.key_metrics.get("biomni_execution", {}) + if isinstance(biomni_exec, dict) and biomni_exec.get("recommendation"): + recommendation = str(biomni_exec.get("recommendation")) + ins = Insight.build( + title=title, + hypothesis=str(h.get("hypothesis", ""))[:1000], + recommendation=recommendation[:2000], + confidence=float(report.confidence), + evidence_run_ids=[report.validation_id], + tags=[str(h.get("domain", "general")), f"validation-{level.lower()}", status], + ) + self.memory.save_insight(ins) + except Exception: + pass + self.memory.update_summary_statistics() + report_dict = report.to_dict() + report_dict["runtime_report_file"] = self._persist_runtime_artifact(report_dict, h) + return report_dict + + def validate_top_hypotheses( + self, + ranked_hypotheses: list[dict[str, Any]], + top_m: int = 3, + level: str = "L1", + ) -> list[dict[str, Any]]: + reports: list[dict[str, Any]] = [] + selected = ranked_hypotheses[: max(0, top_m)] + total = len(selected) + for idx, h in enumerate(selected, start=1): + hid = h.get("hypothesis_id", f"idx_{idx}") + print(f"[STAGE:VALIDATION] running {idx}/{total} level={level.upper()} hypothesis_id={hid}", flush=True) + report = self.validate_hypothesis(h, level=level) + print( + f"[STAGE:VALIDATION] finished {idx}/{total} hypothesis_id={hid} status={report.get('status', 'unknown')} score={report.get('score', 0)}", + flush=True, + ) + reports.append(report) + return reports diff --git a/BioScientist/agent_system/engines/t1_consultant.py b/BioScientist/agent_system/engines/t1_consultant.py new file mode 100644 index 0000000000000000000000000000000000000000..b15d5ffe9d99db50f945657d384e48e7234a5c63 --- /dev/null +++ b/BioScientist/agent_system/engines/t1_consultant.py @@ -0,0 +1,625 @@ +from __future__ import annotations + +import json +import os +import re +from pathlib import Path +from typing import Any + +from ..llm import ( + ClaudeClientConfigError, + ClaudeCodeClient, + GeminiClientConfigError, + GeminiCodeClient, + KimiClientConfigError, + KimiCodeClient, + Qwen3ClientConfigError, + Qwen3LocalClient, +) +from ..schemas import Hypothesis, Insight, PipelineConfiguration +from ..shared_memory import SharedKnowledgeSpace + + +class BioinfoT1Consultant: + """Thought engine: reflect on reports and produce strategy/config guidance.""" + + def __init__(self, memory: SharedKnowledgeSpace): + self.memory = memory + self.backend = os.getenv("T1_MODEL_BACKEND", "claude").strip().lower() + self.last_generation_meta: dict[str, Any] = { + "requested_backend": self.backend, + "actual_backend": "none", + "used_fallback": False, + "error": "", + "history_size": 0, + "available_data_files": 0, + "candidates_returned": 0, + } + self.claude: ClaudeCodeClient | None = None + self.kimi: KimiCodeClient | None = None + self.qwen3: Qwen3LocalClient | None = None + self.gemini: GeminiCodeClient | None = None + if self.backend == "qwen3": + try: + self.qwen3 = Qwen3LocalClient() + except Qwen3ClientConfigError: + self.qwen3 = None + elif self.backend == "gemini": + try: + self.gemini = GeminiCodeClient() + except GeminiClientConfigError: + self.gemini = None + elif self.backend == "kimi": + try: + self.kimi = KimiCodeClient() + except KimiClientConfigError: + self.kimi = None + else: + try: + self.claude = ClaudeCodeClient() + except ClaudeClientConfigError: + self.claude = None + + def review_reports(self) -> list[dict[str, Any]]: + reports = self.memory.list_reports() + if not reports: + return [] + + produced: list[dict[str, Any]] = [] + for rep in reports[-20:]: + status = rep.get("status", "unknown") + task = rep.get("task", "generic") + if status == "failed": + insight = Insight.build( + title=f"{task}: failure pattern", + hypothesis="Parameter set or tool ordering may be unstable.", + recommendation="Use a conservative config and verify dependencies first.", + confidence=0.65, + evidence_run_ids=[rep.get("run_id", "unknown")], + tags=[task, "failure-mode"], + ) + else: + insight = Insight.build( + title=f"{task}: successful path", + hypothesis="Current tool chain is reproducible for this task scope.", + recommendation="Promote this parameter set to default pipeline config.", + confidence=0.75, + evidence_run_ids=[rep.get("run_id", "unknown")], + tags=[task, "best-practice"], + ) + self.memory.save_insight(insight) + produced.append(insight.to_dict()) + return produced + + def consult(self, user_goal: str, task_scope: str) -> dict[str, Any]: + insights = self.memory.list_insights() + matched = [ + item + for item in insights + if task_scope.lower() in " ".join(item.get("tags", [])).lower() + or task_scope.lower() in item.get("title", "").lower() + ] + top = matched[-3:] if matched else insights[-3:] + recommendation = top[-1]["recommendation"] if top else "No prior insight found." + + # 优先走可用 LLM 后端(Qwen3/Kimi/Claude);失败则回退规则推荐 + llm_summary: dict[str, Any] | None = None + model_backend = "heuristic" + if self.qwen3 is not None or self.kimi is not None or self.gemini is not None or self.claude is not None: + try: + system_prompt = ( + "You are a senior bioinformatics workflow consultant. " + "Return strict JSON with keys: primary_hypothesis, step_by_step_plan, recommendation, reasoning, risk_checks, next_actions. " + "step_by_step_plan, risk_checks and next_actions must be arrays of strings." + ) + user_prompt = json.dumps( + { + "task_scope": task_scope, + "user_goal": user_goal, + "insights": top, + }, + ensure_ascii=True, + ) + if self.qwen3 is not None: + llm_summary = self.qwen3.complete_json(system_prompt=system_prompt, user_prompt=user_prompt) + model_backend = "qwen3" + elif self.kimi is not None: + text = self.kimi.complete(system_prompt=system_prompt, user_prompt=user_prompt) + llm_summary = json.loads(text) + model_backend = "kimi" + elif self.gemini is not None: + llm_summary = self.gemini.complete_json(system_prompt=system_prompt, user_prompt=user_prompt) + model_backend = "gemini" + elif self.claude is not None: + llm_summary = self.claude.complete_json(system_prompt=system_prompt, user_prompt=user_prompt) + model_backend = "claude" + recommendation = llm_summary.get("recommendation") or recommendation + except Exception: + llm_summary = None + model_backend = "heuristic" + + return { + "task_scope": task_scope, + "user_goal": user_goal, + "insight_candidates": top, + "recommendation": recommendation, + "model_backend": model_backend if llm_summary is not None else "heuristic", + "llm_summary": llm_summary, + } + + def emit_pipeline_config( + self, + task_scope: str, + strategy_name: str, + tools: list[str], + parameters: dict[str, Any], + rationale: str, + ) -> dict[str, Any]: + top_insights = self.memory.list_insights()[-3:] + cfg = PipelineConfiguration.build( + strategy_name=strategy_name, + task_scope=task_scope, + tools=tools, + parameters=parameters, + rationale=rationale, + source_insight_ids=[it.get("insight_id", "") for it in top_insights if it.get("insight_id")], + ) + self.memory.save_pipeline_config(cfg) + return cfg.to_dict() + + @staticmethod + def _tokenize(text: str) -> set[str]: + return {x for x in re.split(r"[^a-zA-Z0-9_]+", text.lower()) if len(x) > 2} + + @staticmethod + def _supported_data_operations() -> set[str]: + return {"load_table", "sample_rows", "profile_missingness", "numeric_summary"} + + @staticmethod + def _normalize_operations(ops: list[dict[str, Any]]) -> list[dict[str, Any]]: + supported = BioinfoT1Consultant._supported_data_operations() + normalized: list[dict[str, Any]] = [] + for op in ops: + action = str(op.get("action", "")).strip() + if action not in supported: + continue + if action == "sample_rows": + rows = op.get("rows", 500) + try: + rows = int(rows) + except Exception: + rows = 500 + rows = max(50, min(2000, rows)) + normalized.append({"action": "sample_rows", "rows": rows}) + elif action == "load_table": + normalized.append({"action": "load_table", "target": "all_selected"}) + else: + normalized.append({"action": action}) + if not normalized: + normalized = [ + {"action": "load_table", "target": "all_selected"}, + {"action": "sample_rows", "rows": 500}, + {"action": "profile_missingness"}, + {"action": "numeric_summary"}, + ] + return normalized + + def _domain_keyword_hints(self, domain: str, user_query: str) -> set[str]: + tokens = self._tokenize(f"{domain} {user_query}") + if "single_cell" in domain or "single" in tokens or "cell" in tokens: + tokens |= { + "single", + "cell", + "census", + "marker", + "celltype", + "rna", + "gene", + "normalization", + "expression", + } + return tokens + + @staticmethod + def _resolve_biomni_data_root() -> Path: + env = os.getenv("BIOCLAW_BIOMNI_DATA_ROOT", "").strip() + if env: + p = Path(env).expanduser().resolve() + if p.exists(): + return p + candidates = [ + Path(__file__).resolve().parents[1] / "toolbase" / "data" / "biomni_data", + Path("/225040511/project/BioScientist/agent_system/toolbase/data/biomni_data"), + ] + for c in candidates: + if c.exists(): + return c + return candidates[0] + + def _list_available_data_files(self, limit: int = 400) -> list[str]: + root = self._resolve_biomni_data_root() + if not root.exists(): + return [] + files: list[str] = [] + for p in sorted(root.rglob("*")): + if p.is_file(): + files.append(str(p)) + if len(files) >= limit: + break + return files + + def _suggest_data_plan( + self, + *, + domain: str, + user_query: str, + hypothesis_text: str, + available_files: list[str], + ) -> tuple[list[str], list[dict[str, Any]]]: + text = f"{domain} {user_query} {hypothesis_text}".lower() + keywords = self._tokenize(text) | self._domain_keyword_hints(domain, user_query) + + scored: list[tuple[int, str]] = [] + for fp in available_files: + p = Path(fp) + name = p.name.lower() + parent = str(p.parent).lower() + score = 0 + for kw in keywords: + if kw in name: + score += 1 + if kw in parent: + score += 1 + if any(k in name for k in ("single", "cell", "census", "marker", "gtex", "gene", "rna", "expression")): + score += 2 + if "benchmark" in parent: + score -= 1 + if score > 0: + scored.append((score, fp)) + scored.sort(key=lambda x: x[0], reverse=True) + selected = [fp for _, fp in scored[:5]] + if not selected: + selected = available_files[:3] + + ops = [ + {"action": "load_table", "target": "all_selected"}, + {"action": "sample_rows", "rows": 500}, + {"action": "profile_missingness"}, + {"action": "numeric_summary"}, + ] + return selected, ops + + @staticmethod + def _inject_data_grounding_text(base_hypothesis: str, selected_sources: list[str], selected_ops: list[dict[str, Any]]) -> str: + clean = base_hypothesis.strip() + source_names = [Path(x).name for x in selected_sources[:2]] + op_names = [str(x.get("action", "")) for x in selected_ops] + if not source_names: + return clean + grounding = f" Data-backed plan: use {', '.join(source_names)} with ops {', '.join(op_names)}." + if "data-backed plan" in clean.lower(): + return clean + return (clean + grounding).strip() + + @staticmethod + def _hypothesis_prompt_template_en( + *, + domain: str, + user_query: str, + tools_list_text: str, + success_patterns_text: str, + failure_patterns_text: str, + ) -> str: + return ( + f"# Role\n" + f"You are an AI scientist specializing in the {domain} domain. " + f"Your task is to propose concrete, testable, and quantifiable analytical hypotheses.\n\n" + f"# Available Tooling Ecosystem (Injected from T0/T1)\n" + f"{tools_list_text}\n\n" + f"# Historical Success Patterns (Injected from memory)\n" + f"{success_patterns_text}\n\n" + f"# Historical Failure Patterns (Injected from memory)\n" + f"{failure_patterns_text}\n\n" + f"# Current User Request\n" + f"{user_query}\n\n" + f"# Hypothesis Generation Specification\n\n" + f"## Each hypothesis must include exactly these 5 parts:\n\n" + f"### 1. Core Hypothesis (one sentence)\n" + f"State the idea to be validated in one clear sentence.\n\n" + f"### 2. Concrete Method (2-3 sentences)\n" + f"Explain how to execute it, including:\n" + f"- Which tools/functions are used\n" + f"- Key parameters\n" + f"- How it differs from the baseline method\n\n" + f"### 3. Expected Effect (quantified)\n" + f"Must include explicit numerical targets, e.g.:\n" + f"- Clustering metrics: ARI / NMI improves by X%\n" + f"- Batch effects: kBET decreases by X%\n" + f"- Differential genes: +X significant genes\n" + f"- Runtime: reduced by X%\n\n" + f"### 4. Validation Plan\n" + f"Specify:\n" + f"- Which dataset(s) to use (must be from the available data list)\n" + f"- Which baseline to compare against\n" + f"- Success threshold (e.g., ARI > 0.6)\n\n" + f"### 5. Theoretical Rationale (biology/statistics)\n" + f"Explain why the hypothesis may hold, grounded in biological principles or statistical intuition.\n\n" + f"## Output format example\n\n" + f"[HYPOTHESIS-001]\n" + f"**Core Hypothesis**: ...\n" + f"**Concrete Method**: ...\n" + f"**Expected Effect**: ...\n" + f"**Validation Plan**: ...\n" + f"**Theoretical Rationale**: ...\n" + ) + + def generate_hypotheses( + self, + user_query: str, + domain: str, + n: int = 10, + top_k: int = 5, + ) -> list[dict[str, Any]]: + history = self.memory.get_hypothesis_context(domain=domain, top_k=top_k) + failed_patterns = [] + success_examples = [] + historical_reflection_pool: list[str] = [] + error_avoidance_pool: list[str] = [] + for item in history: + h = item.get("hypothesis", {}) + if h.get("historical_reflection"): + historical_reflection_pool.extend([str(x) for x in h.get("historical_reflection", []) if str(x).strip()]) + if h.get("error_avoidance"): + error_avoidance_pool.extend([str(x) for x in h.get("error_avoidance", []) if str(x).strip()]) + for vr in item.get("validation_reports", []): + if vr.get("status") == "failed" and vr.get("failure_reason"): + failed_patterns.append(vr["failure_reason"]) + error_avoidance_pool.append(f"Avoid failure mode: {vr['failure_reason']}") + if vr.get("status") == "success": + success_examples.append( + { + "title": h.get("title", ""), + "hypothesis": h.get("hypothesis", ""), + "score": vr.get("score", 0.0), + } + ) + historical_reflection_pool.append( + f"Success case: {h.get('title', '')} (score={vr.get('score', 0.0)})" + ) + + for ins in self.memory.list_insights()[-40:]: + tags = [str(x).lower() for x in ins.get("tags", [])] + if domain.lower() not in " ".join(tags + [ins.get("title", "").lower()]): + continue + title = str(ins.get("title", "")).strip() + reco = str(ins.get("recommendation", "")).strip() + if title or reco: + historical_reflection_pool.append(f"Insight: {title} -> {reco}") + if "failed" in " ".join(tags) or "inconclusive" in " ".join(tags): + if reco: + error_avoidance_pool.append(f"Risk to avoid: {reco}") + + generated: list[dict[str, Any]] = [] + llm_data: dict[str, Any] | None = None + available_files = self._list_available_data_files(limit=500) + llm_backend = "none" + llm_error = "" + + if self.qwen3 is not None or self.kimi is not None or self.gemini is not None or self.claude is not None: + try: + tools_list_text = "No explicit tool list injected; use available MCP/Biomni tools relevant to the domain." + success_patterns_text = json.dumps(success_examples[-10:], ensure_ascii=True) + failure_patterns_text = json.dumps(failed_patterns[-10:], ensure_ascii=True) + generation_spec = self._hypothesis_prompt_template_en( + domain=domain, + user_query=user_query, + tools_list_text=tools_list_text, + success_patterns_text=success_patterns_text, + failure_patterns_text=failure_patterns_text, + ) + system_prompt = ( + "You are a bioinformatics hypothesis generator.\n" + "Follow the provided Hypothesis Generation Specification strictly.\n" + "Return strict JSON only with key `hypotheses`.\n" + "Each item must include fields:\n" + "- title\n" + "- hypothesis (include core hypothesis + concise method)\n" + "- expected_improvement (must be quantified)\n" + "- validation_plan\n" + "- theoretical_basis\n" + "- historical_reflection (array of 2-5 concise points grounded in prior cases)\n" + "- error_avoidance (array of 2-5 concrete mistakes to avoid)\n" + "- reasoning_chain (array of 3-7 steps explaining how prior evidence leads to this hypothesis)\n" + "- tags\n" + "- data_sources (must come from available_data_files)\n" + "- data_operations (only from load_table/sample_rows/profile_missingness/numeric_summary)\n" + "Do not repeat failed patterns. Prefer variants inspired by high-score prior ideas.\n" + "Every hypothesis must explicitly reflect on historical cases and produce an actionable reasoning chain." + ) + user_payload = { + "generation_spec_en": generation_spec, + "domain": domain, + "user_query": user_query, + "generate_n": n, + "failed_patterns_to_avoid": failed_patterns[-10:], + "success_examples_to_learn_from": success_examples[-10:], + "historical_reflection_candidates": historical_reflection_pool[-20:], + "error_avoidance_candidates": error_avoidance_pool[-20:], + "available_data_files": available_files[:200], + } + user_prompt = json.dumps(user_payload, ensure_ascii=True) + if self.qwen3 is not None: + llm_backend = "qwen3" + llm_data = self.qwen3.complete_json(system_prompt=system_prompt, user_prompt=user_prompt) + elif self.kimi is not None: + llm_backend = "kimi" + text = self.kimi.complete(system_prompt=system_prompt, user_prompt=user_prompt) + llm_data = json.loads(text) + elif self.gemini is not None: + llm_backend = "gemini" + llm_data = self.gemini.complete_json(system_prompt=system_prompt, user_prompt=user_prompt) + elif self.claude is not None: + llm_backend = "claude" + llm_data = self.claude.complete_json(system_prompt=system_prompt, user_prompt=user_prompt) + except Exception as exc: + llm_data = None + llm_error = str(exc) + + candidates = (llm_data or {}).get("hypotheses", []) if isinstance(llm_data, dict) else [] + used_fallback = not bool(candidates) + if not candidates: + for i in range(max(1, n)): + suggested_sources, suggested_ops = self._suggest_data_plan( + domain=domain, + user_query=user_query, + hypothesis_text=user_query, + available_files=available_files, + ) + candidates.append( + { + "title": f"{domain} hypothesis {i + 1}", + "hypothesis": self._inject_data_grounding_text( + f"Apply a normalization variant for: {user_query}.", + suggested_sources, + suggested_ops, + ), + "expected_improvement": "Improve normalization stability, reduce batch effects, and keep biological signal.", + "validation_plan": "Use selected data sources, compare against standard normalization baseline, and require metric improvement over baseline.", + "theoretical_basis": "Selected available datasets are used for immediate lightweight validation before expensive full runs.", + "historical_reflection": [ + f"Referenced {min(len(success_examples), top_k)} successful historical cases in the same domain.", + "Observed prior failures and kept this hypothesis conservative in scope.", + ], + "error_avoidance": ( + [f"Avoid: {x}" for x in failed_patterns[-3:]] + if failed_patterns + else ["Avoid unsupported tools and unverifiable claims."] + ), + "reasoning_chain": [ + "Review historical validation outcomes in the same domain.", + "Extract stable successful patterns and recurring failure reasons.", + "Select currently available data files for immediate verification.", + "Propose a minimally risky, testable variant with quantifiable expectations.", + "Bind operations so E1 can execute immediately and report metrics.", + ], + "tags": [domain, "generated-v1"], + "data_sources": suggested_sources, + "data_operations": suggested_ops, + } + ) + if not llm_error and llm_backend != "none": + llm_error = "model_returned_empty_or_non_json_hypotheses" + if llm_backend == "none": + llm_error = "no_llm_backend_available" + + source_example_ids = [it.get("hypothesis", {}).get("hypothesis_id", "") for it in history] + known_paths = set(available_files) + for c in candidates[: max(1, n)]: + selected_sources = [str(x) for x in c.get("data_sources", []) if str(x).strip() and str(x) in known_paths] + selected_ops = [x for x in c.get("data_operations", []) if isinstance(x, dict)] + if not selected_sources or not selected_ops: + suggested_sources, suggested_ops = self._suggest_data_plan( + domain=domain, + user_query=user_query, + hypothesis_text=str(c.get("hypothesis", "")), + available_files=available_files, + ) + if not selected_sources: + selected_sources = suggested_sources + if not selected_ops: + selected_ops = suggested_ops + selected_ops = self._normalize_operations(selected_ops) + grounded_hypothesis = self._inject_data_grounding_text( + str(c.get("hypothesis", "")), + selected_sources, + selected_ops, + ) + hyp = Hypothesis.build( + domain=domain, + user_query=user_query, + title=str(c.get("title", "Untitled hypothesis")).strip(), + hypothesis=grounded_hypothesis, + expected_improvement=str(c.get("expected_improvement", "")).strip(), + theoretical_basis=( + f"{str(c.get('theoretical_basis', '')).strip()} " + f"Validation plan: {str(c.get('validation_plan', '')).strip()}" + ).strip(), + tags=[str(x) for x in c.get("tags", []) if str(x).strip()], + source_examples=[x for x in source_example_ids if x], + data_sources=selected_sources, + data_operations=selected_ops, + historical_reflection=[ + str(x).strip() + for x in c.get("historical_reflection", []) + if str(x).strip() + ][:6], + error_avoidance=[ + str(x).strip() + for x in c.get("error_avoidance", []) + if str(x).strip() + ][:6], + reasoning_chain=[ + str(x).strip() + for x in c.get("reasoning_chain", []) + if str(x).strip() + ][:8], + ) + self.memory.save_hypothesis(hyp) + generated.append(hyp.to_dict()) + + self.last_generation_meta = { + "requested_backend": self.backend, + "actual_backend": llm_backend, + "used_fallback": used_fallback, + "error": llm_error[:1000], + "history_size": len(history), + "available_data_files": len(available_files), + "candidates_returned": len(candidates), + "generated_count": len(generated), + } + return generated + + def rank_hypotheses_by_success_proxy( + self, + hypotheses: list[dict[str, Any]], + domain: str, + ) -> list[dict[str, Any]]: + stats = self.memory.get_summary_statistics() + tag_summary = stats.get("tag_summary", {}) + history = self.memory.get_hypothesis_context(domain=domain, top_k=20) + + successful_texts = [] + for item in history: + reports = item.get("validation_reports", []) + if any(r.get("status") == "success" for r in reports): + h = item.get("hypothesis", {}) + successful_texts.append(f"{h.get('title', '')} {h.get('hypothesis', '')}") + + successful_tokens = [self._tokenize(x) for x in successful_texts if x.strip()] + + ranked = [] + for h in hypotheses: + tags = h.get("tags", []) or [] + if not tags: + tags = [domain] + tag_scores = [] + for t in tags: + s = tag_summary.get(t, {}) + tag_scores.append(float(s.get("success_rate", 0.5))) + tag_score = sum(tag_scores) / len(tag_scores) if tag_scores else 0.5 + + h_tokens = self._tokenize(f"{h.get('title', '')} {h.get('hypothesis', '')}") + sim_score = 0.0 + if successful_tokens and h_tokens: + overlaps = [] + for st in successful_tokens: + inter = len(h_tokens & st) + union = len(h_tokens | st) or 1 + overlaps.append(inter / union) + sim_score = max(overlaps) if overlaps else 0.0 + + predicted_success = max(0.0, min(1.0, 0.7 * tag_score + 0.3 * sim_score)) + ranked.append({**h, "predicted_success": predicted_success}) + + ranked.sort(key=lambda x: x.get("predicted_success", 0.0), reverse=True) + return ranked diff --git a/BioScientist/agent_system/engines/test_gemini.py b/BioScientist/agent_system/engines/test_gemini.py new file mode 100644 index 0000000000000000000000000000000000000000..ba182e7637eba88193d50a4d2a04d555f1446379 --- /dev/null +++ b/BioScientist/agent_system/engines/test_gemini.py @@ -0,0 +1,25 @@ +import google.generativeai as genai +import os + +# 替换为你的 Gemini API Key +api_key = os.environ.get("GEMINI_API_KEY") +print("api_key:"+api_key) +os.environ["GEMINI_API_KEY"] = api_key +genai.configure(api_key=os.environ["GEMINI_API_KEY"]) + +def list_gemini_models(): + print("Listing available Gemini models:") + print("-" * 50) + + # 遍历所有模型 + for model in genai.list_models(): + # 过滤掉仅支持旧版或特定功能的模型(可选) + if 'generateContent' in model.supported_generation_methods: + print(f"Model Name: {model.name}") + print(f"Description: {model.description}") + print(f"Input Token Limit: {model.input_token_limit}") + print(f"Output Token Limit: {model.output_token_limit}") + print("-" * 50) + +if __name__ == "__main__": + list_gemini_models() \ No newline at end of file diff --git a/BioScientist/agent_system/engines/v1_executor.py b/BioScientist/agent_system/engines/v1_executor.py new file mode 100644 index 0000000000000000000000000000000000000000..5e5fb9f21a8649911a1e970025bf54356c388234 --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor.py @@ -0,0 +1,1348 @@ +from __future__ import annotations + +import importlib.util +from pathlib import Path +from typing import Any +import json +import os +import re +import shlex +import shutil +import subprocess +import traceback +import requests + +from ..mcp_router import MCPToolRouter +from ..schemas import ExperimentReport, FailureMode +from ..shared_memory import SharedKnowledgeSpace + + +class BioinfoV1Executor: + """Execution engine: plan task -> call MCP servers -> publish structured report.""" + + def __init__( + self, + memory: SharedKnowledgeSpace, + default_results_root: str | Path, + project_root: str | Path, + execution_backend: str = "docker", + ): + self.memory = memory + self.default_results_root = Path(default_results_root) + self.default_results_root.mkdir(parents=True, exist_ok=True) + self.project_root = Path(project_root) + self.router = MCPToolRouter(project_root=project_root) + self.execution_backend = execution_backend + + @staticmethod + def _append_log(log_file: Path, message: str) -> None: + with log_file.open("a", encoding="utf-8") as lf: + lf.write(message.rstrip() + "\n") + + @staticmethod + def _jsonable(value: Any) -> Any: + if isinstance(value, Path): + return str(value) + if isinstance(value, dict): + return {str(k): BioinfoV1Executor._jsonable(v) for k, v in value.items()} + if isinstance(value, list): + return [BioinfoV1Executor._jsonable(v) for v in value] + if isinstance(value, tuple): + return [BioinfoV1Executor._jsonable(v) for v in value] + return value + + @staticmethod + def _read_log_tail(log_file: Path, max_lines: int = 200) -> str: + try: + lines = log_file.read_text(encoding="utf-8", errors="replace").splitlines() + return "\n".join(lines[-max_lines:]) + except Exception: + return "" + + @staticmethod + def _pick_trimmed_pair(trim_dir: Path) -> tuple[Path | None, Path | None]: + r1_patterns = [ + "*_val_1.fq.gz", + "*_val_1.fq", + "*_R1_val_1.fq.gz", + "*_1_val_1.fq.gz", + "*_fastp_R1.fastq", + "*_fastp_R1.fastq.gz", + ] + r2_patterns = [ + "*_val_2.fq.gz", + "*_val_2.fq", + "*_R2_val_2.fq.gz", + "*_2_val_2.fq.gz", + "*_fastp_R2.fastq", + "*_fastp_R2.fastq.gz", + ] + r1_candidates: list[Path] = [] + r2_candidates: list[Path] = [] + for pattern in r1_patterns: + r1_candidates.extend(sorted(trim_dir.glob(pattern))) + for pattern in r2_patterns: + r2_candidates.extend(sorted(trim_dir.glob(pattern))) + return (r1_candidates[-1] if r1_candidates else None, r2_candidates[-1] if r2_candidates else None) + + def _plan_task( + self, + task: str, + task_scope: str, + pipeline_config: dict[str, Any] | None, + ) -> list[dict[str, Any]]: + """ + Plan execution in a Biomni-like style: + 1) understand scope + 2) choose candidate tools + 3) execute step-by-step with artifacts passed forward + """ + cfg_tools = (pipeline_config or {}).get("tools") or [] + + if task_scope == "first_pipeline": + task_lower = task.lower() + wants_alignment = any( + kw in task_lower + for kw in ("align", "alignment", "map", "mapping", "variant", "snp", "bam") + ) + benchmark_repro_mode = any( + kw in task_lower + for kw in ("code repository", "original open-source code", "reproduce paper", "reproducibility") + ) + base_plan = [ + { + "name": "qc_raw", + "description": "Quality control on raw reads", + "candidates": cfg_tools or ["fastqc", "fastp"], + }, + { + "name": "trim", + "description": "Adapter/quality trimming", + "candidates": cfg_tools or ["trim_galore", "cutadapt", "trimmomatic", "fastp"], + }, + { + "name": "align", + "description": "Read alignment to reference index", + "candidates": cfg_tools or ["bowtie2", "bwa", "hisat2", "star", "minimap2"], + }, + { + "name": "qc_trimmed", + "description": "Quality control after trimming", + "candidates": cfg_tools or ["fastqc", "qualimap"], + }, + { + "name": "aggregate", + "description": "Aggregate reports", + "candidates": cfg_tools or ["multiqc"], + }, + ] + if not wants_alignment: + base_plan = [s for s in base_plan if s["name"] != "align"] + + if benchmark_repro_mode: + base_plan.extend( + [ + { + "name": "source_clone", + "description": "Clone original paper source code repository", + "local_executor": "source_clone", + "candidates": [], + }, + { + "name": "source_execute", + "description": "Run reproducibility-oriented source code execution attempts", + "local_executor": "source_execute", + "candidates": [], + }, + { + "name": "summarize_repro", + "description": "Summarize reproducibility with deviations and evidence", + "local_executor": "summarize_repro", + "candidates": [], + }, + ] + ) + return base_plan + + # generic mode: one tool per configured entry + if cfg_tools: + return [ + { + "name": f"step_{i+1}", + "description": f"Configured execution step for {tool}", + "candidates": [tool], + } + for i, tool in enumerate(cfg_tools) + ] + + # fallback + return [ + { + "name": "generic_step", + "description": f"Generic execution for task: {task}", + "candidates": ["fastqc"], + } + ] + + def _route_or_generate(self, tool_name: str, log_file: Path): + if self.router.has_tool(tool_name): + return self.router.resolve(tool_name) + + self._append_log(log_file, f"[planner] MCP tool missing: {tool_name}, invoking converter...") + self._try_generate_mcp_server(tool_name, log_file) + self.router.refresh() + if self.router.has_tool(tool_name): + return self.router.resolve(tool_name) + return None + + def _try_generate_mcp_server(self, tool_name: str, log_file: Path) -> None: + converter_path = self.project_root / "src" / "bioinfomcp_converter.py" + if not converter_path.exists(): + self._append_log(log_file, f"[converter] converter not found: {converter_path}") + return + + try: + spec = importlib.util.spec_from_file_location("bioinfomcp_converter", str(converter_path)) + if spec is None or spec.loader is None: + raise RuntimeError("Failed to load converter module spec.") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + converter_cls = getattr(module, "BioinfoMCP", None) + if converter_cls is None: + raise RuntimeError("BioinfoMCP class not found in converter.") + + converter = converter_cls(model="openai") + ok, error_msg, code = converter.autogenerate_mcp_tool( + tool_name=tool_name, + manual="--help", + run_help_command=True, + ) + if not ok or not code: + raise RuntimeError(f"Converter failed: {error_msg}") + + mcp_dir = self.project_root / "mcp-servers" / f"mcp_{tool_name}" / "app" + mcp_dir.mkdir(parents=True, exist_ok=True) + server_file = mcp_dir / f"{tool_name}_server.py" + server_file.write_text(self._wrap_generated_mcp_code(code), encoding="utf-8") + self._append_log(log_file, f"[converter] generated MCP server: {server_file}") + except Exception as exc: # pragma: no cover - defensive path + self._append_log(log_file, f"[converter] generation failed for {tool_name}: {exc}") + + @staticmethod + def _wrap_generated_mcp_code(code: str) -> str: + if "FastMCP" in code and "mcp = FastMCP()" in code: + if "if __name__ == '__main__':" in code or "if __name__ == \"__main__\":" in code: + return code + return f"{code}\n\nif __name__ == '__main__':\n mcp.run()\n" + + return ( + "from fastmcp import FastMCP\n" + "mcp = FastMCP()\n\n" + f"{code}\n\n" + "if __name__ == '__main__':\n" + " mcp.run()\n" + ) + + @staticmethod + def _load_tool_callable(server_script: Path, function_name: str): + mod_name = f"mcp_module_{server_script.stem}_{abs(hash(str(server_script)))}" + spec = importlib.util.spec_from_file_location(mod_name, str(server_script)) + if spec is None or spec.loader is None: + raise RuntimeError(f"Cannot load module for {server_script}") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + func = getattr(module, function_name, None) + if func is None: + raise RuntimeError(f"Function '{function_name}' not found in {server_script}") + return func + + def _invoke_mcp_tool( + self, + route, + kwargs: dict[str, Any], + log_file: Path, + ) -> dict[str, Any]: + self._append_log( + log_file, + f"[toolcall] {route.tool_name}.{route.function_name} script={route.server_script} kwargs={json.dumps({k: str(v) for k, v in kwargs.items()}, ensure_ascii=True)}", + ) + if self.execution_backend == "docker": + return self._invoke_mcp_tool_docker(route, kwargs=kwargs, log_file=log_file) + + try: + tool_fn = self._load_tool_callable(route.server_script, route.function_name) + result = tool_fn(**kwargs) + if not isinstance(result, dict): + result = {"raw_result": result} + ok = "error" not in result + self._append_log(log_file, f"[toolcall] status={'ok' if ok else 'error'}") + return {"ok": ok, "result": result} + except FileNotFoundError as exc: + tb = traceback.format_exc() + self._append_log(log_file, f"[toolcall] FileNotFoundError: {exc}\n{tb}") + return {"ok": False, "result": {"error": f"FileNotFoundError: {exc}", "traceback": tb}} + except Exception as exc: + tb = traceback.format_exc() + self._append_log(log_file, f"[toolcall] exception: {exc}\n{tb}") + return {"ok": False, "result": {"error": str(exc), "traceback": tb}} + + def _invoke_mcp_tool_docker(self, route, kwargs: dict[str, Any], log_file: Path) -> dict[str, Any]: + if shutil.which("docker") is None: + return { + "ok": False, + "result": {"error": "docker_not_found", "hint": "Install Docker or switch execution_backend=python"}, + } + + run_dir = log_file.parent + payload_file = run_dir / f"_mcp_payload_{route.function_name}.json" + runner_file = run_dir / "_mcp_docker_runner.py" + payload = { + "server_script": str(route.server_script), + "function_name": route.function_name, + "kwargs": self._jsonable(kwargs), + } + payload_file.write_text(json.dumps(payload, ensure_ascii=True), encoding="utf-8") + runner_file.write_text(self._docker_runner_script(), encoding="utf-8") + + mount_root = self.project_root.parent + docker_cmd = [ + "docker", + "run", + "--rm", + "-v", + f"{mount_root}:{mount_root}", + "-w", + str(mount_root), + route.image_name, + "python", + str(runner_file), + str(payload_file), + ] + self._append_log(log_file, f"[toolcall-docker] cmd={' '.join(docker_cmd)}") + + completed = subprocess.run(docker_cmd, capture_output=True, text=True) + stdout = completed.stdout or "" + stderr = completed.stderr or "" + if completed.returncode != 0: + self._append_log(log_file, f"[toolcall-docker] failed rc={completed.returncode}\n{stderr}") + return { + "ok": False, + "result": { + "error": f"docker_run_failed rc={completed.returncode}", + "stdout": stdout, + "stderr": stderr, + "image": route.image_name, + "hint": ( + f"Ensure image '{route.image_name}' exists/builds, " + f"or run with execution_backend=python." + ), + }, + } + + try: + result = json.loads(stdout.strip() or "{}") + if not isinstance(result, dict): + result = {"raw_result": result} + except Exception: + result = {"raw_stdout": stdout, "raw_stderr": stderr} + + ok = "error" not in result + return {"ok": ok, "result": result} + + @staticmethod + def _docker_runner_script() -> str: + return """from __future__ import annotations +import importlib.util +import inspect +import json +import sys +from pathlib import Path +from typing import Any, get_args, get_origin, Union + + +def to_jsonable(v: Any): + if isinstance(v, Path): + return str(v) + if isinstance(v, dict): + return {str(k): to_jsonable(val) for k, val in v.items()} + if isinstance(v, list): + return [to_jsonable(x) for x in v] + if isinstance(v, tuple): + return [to_jsonable(x) for x in v] + return v + + +def is_path_type(tp: Any) -> bool: + return tp is Path + + +def convert_value(value: Any, ann: Any) -> Any: + if ann is inspect._empty: + return value + origin = get_origin(ann) + if origin in (list, tuple): + args = get_args(ann) + inner = args[0] if args else Any + if isinstance(value, list): + return [convert_value(v, inner) for v in value] + return value + if origin is Union: + for a in get_args(ann): + if a is type(None): + continue + try: + return convert_value(value, a) + except Exception: + pass + return value + if is_path_type(ann): + return Path(value) if value is not None else value + return value + + +def main(): + payload_path = Path(sys.argv[1]) + payload = json.loads(payload_path.read_text(encoding="utf-8")) + server_script = payload["server_script"] + function_name = payload["function_name"] + kwargs = payload.get("kwargs", {}) + + spec = importlib.util.spec_from_file_location("mcp_runtime_mod", server_script) + if spec is None or spec.loader is None: + print(json.dumps({"error": f"cannot_load_module:{server_script}"})) + sys.exit(0) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + fn = getattr(mod, function_name, None) + if fn is None: + print(json.dumps({"error": f"function_not_found:{function_name}"})) + sys.exit(0) + + sig = inspect.signature(fn) + call_kwargs = {} + for k, v in kwargs.items(): + if k in sig.parameters: + call_kwargs[k] = convert_value(v, sig.parameters[k].annotation) + else: + call_kwargs[k] = v + + try: + result = fn(**call_kwargs) + print(json.dumps(to_jsonable(result), ensure_ascii=True)) + except Exception as exc: + import traceback + print(json.dumps({"error": str(exc), "traceback": traceback.format_exc()}, ensure_ascii=True)) + + +if __name__ == "__main__": + main() +""" + + def _build_step_kwargs( + self, + step_name: str, + route_function: str, + context: dict[str, Any], + ) -> dict[str, Any]: + run_dir: Path = context["run_dir"] + r1: Path = context["r1"] + r2: Path = context["r2"] + threads: int = context["threads"] + quality_cutoff: int = context["quality_cutoff"] + index_base = context["index_base"] + artifacts = context["artifacts"] + + if step_name == "qc_raw": + outdir = run_dir / "01_fastqc_raw" + outdir.mkdir(parents=True, exist_ok=True) + return {"input_files": [r1, r2], "outdir": outdir, "threads": threads} + + if step_name == "trim": + outdir = run_dir / "02_trim" + outdir.mkdir(parents=True, exist_ok=True) + if route_function == "fastp": + out1 = outdir / f"{r1.stem}_fastp_R1.fastq" + out2 = outdir / f"{r2.stem}_fastp_R2.fastq" + json_report = str(outdir / "fastp.json") + html_report = str(outdir / "fastp.html") + return { + "in1": r1, + "in2": r2, + "out1": out1, + "out2": out2, + "qualified_quality_phred": quality_cutoff, + "cut_mean_quality": quality_cutoff, + "cut_right": True, + "thread": threads, + "json": json_report, + "html": html_report, + "report_title": "fastp report (BioClawMCP)", + } + + return { + "input_files": [r1, r2], + "paired": True, + "quality": quality_cutoff, + "output_dir": outdir, + "cores": threads, + } + + if step_name == "align": + align_dir = run_dir / "03_align" + align_dir.mkdir(parents=True, exist_ok=True) + sam_out = align_dir / "alignment.sam" + artifacts["sam"] = sam_out + trimmed_r1 = artifacts.get("trimmed_r1", r1) + trimmed_r2 = artifacts.get("trimmed_r2", r2) + if route_function == "bowtie2_align": + return { + "index_base": str(index_base), + "mate1_files": str(trimmed_r1), + "mate2_files": str(trimmed_r2), + "sam_output": sam_out, + "threads": threads, + } + # generic aligner fall back shape + return { + "input_files": [trimmed_r1, trimmed_r2], + "reference_index_base": str(index_base), + "output_dir": align_dir, + "threads": threads, + } + + if step_name == "qc_trimmed": + outdir = run_dir / "04_fastqc_trimmed" + outdir.mkdir(parents=True, exist_ok=True) + trimmed_r1 = artifacts.get("trimmed_r1", r1) + trimmed_r2 = artifacts.get("trimmed_r2", r2) + return {"input_files": [trimmed_r1, trimmed_r2], "outdir": outdir, "threads": threads} + + if step_name == "aggregate": + outdir = run_dir / "05_multiqc" + outdir.mkdir(parents=True, exist_ok=True) + return { + "analysis_directory": run_dir, + "outdir": outdir, + "filename": "multiqc_report.html", + "force": True, + } + + # generic fallback for configured custom step + return { + "analysis_directory": run_dir, + "outdir": run_dir / f"{step_name}_output", + } + + @staticmethod + def _extract_code_repo(task: str, input_manifest: dict[str, Any]) -> str: + manifest_repo = str(input_manifest.get("code_repo") or "").strip() + if manifest_repo: + return manifest_repo + m = re.search(r"Code repo:\s*(https?://\S+)", task, re.I) + if m: + return m.group(1).rstrip(").,;") + return "" + + def _run_local_command(self, cmd: list[str], log_file: Path, cwd: Path | None = None, timeout_s: int = 900) -> dict[str, Any]: + try: + self._append_log(log_file, f"[local-cmd] {' '.join(cmd)} cwd={cwd or self.project_root}") + completed = subprocess.run( + cmd, + cwd=str(cwd) if cwd else None, + capture_output=True, + text=True, + timeout=timeout_s, + ) + return { + "command_executed": " ".join(cmd), + "return_code": completed.returncode, + "stdout": completed.stdout or "", + "stderr": completed.stderr or "", + } + except Exception as exc: + tb = traceback.format_exc() + return { + "command_executed": " ".join(cmd), + "return_code": -1, + "error": str(exc), + "traceback": tb, + } + + @staticmethod + def _extract_readme_run_commands(source_dir: Path, max_commands: int = 6) -> list[list[str]]: + candidates: list[list[str]] = [] + readme_files = [ + source_dir / "README.md", + source_dir / "readme.md", + source_dir / "README.rst", + ] + text = "" + for fp in readme_files: + if fp.exists(): + try: + text = fp.read_text(encoding="utf-8", errors="replace") + break + except Exception: + continue + if not text: + return candidates + + # extract fenced code blocks first + blocks = re.findall(r"```(?:bash|sh|shell)?\n([\s\S]*?)```", text, re.I) + lines: list[str] = [] + for b in blocks: + lines.extend(b.splitlines()) + if not lines: + lines = text.splitlines() + + allowed_prefix = ( + "python ", + "python3 ", + "bash ", + "sh ", + "./", + "Rscript ", + "R -e ", + "R --vanilla ", + "R ", + "snakemake", + "nextflow run", + ) + forbidden = ("sudo ", "rm -rf", "docker system", "shutdown", "reboot") + for raw in lines: + line = raw.strip() + if line.startswith("$ "): + line = line[2:].strip() + if not line or line.startswith("#"): + continue + if any(bad in line for bad in forbidden): + continue + if line.startswith(allowed_prefix): + try: + cmd = shlex.split(line) + except Exception: + continue + if cmd: + candidates.append(cmd) + if len(candidates) >= max_commands: + break + return candidates + + @staticmethod + def _is_safe_run_command(cmd: list[str]) -> bool: + if not cmd: + return False + joined = " ".join(cmd).lower() + forbidden = [ + "rm -rf", + "sudo ", + "shutdown", + "reboot", + ":(){:|:&};:", + "mkfs", + "dd if=", + "curl ", + "wget ", + "scp ", + "ssh ", + "docker system", + ] + if any(x in joined for x in forbidden): + return False + allowed_heads = {"python", "python3", "bash", "sh", "rscript", "r", "snakemake", "nextflow", "make"} + head = cmd[0].lower() + return head in allowed_heads or head.startswith("./") + + def _plan_source_commands_with_gemini( + self, + *, + task: str, + source_dir: Path, + input_manifest: dict[str, Any], + log_file: Path, + max_commands: int = 5, + ) -> list[list[str]]: + api_key = os.getenv("GEMINI_API_KEY", "").strip() + print("api_key:"+api_key) + if not api_key: + self._append_log(log_file, "[gemini] planner skipped: GEMINI_API_KEY missing") + return [] + + readme_text = "" + for fp in [source_dir / "README.md", source_dir / "readme.md", source_dir / "README.rst"]: + if fp.exists(): + readme_text = fp.read_text(encoding="utf-8", errors="replace") + break + if len(readme_text) > 12000: + readme_text = readme_text[:12000] + print("readme_text:"+readme_text) + endpoint = ( + "https://generativelanguage.googleapis.com/v1beta/models/" + f"gemini-2.5-flash-lite:generateContent?key={api_key}" + ) + planner_prompt = ( + "You are a bioinformatics reproducibility execution planner.\n" + "Given repository README and task context, propose up to 5 LOCAL runnable commands.\n" + "Output JSON with key `commands`, where each command is either:\n" + '1) ["python","script.py","--arg"] or 2) "python script.py --arg"\n' + "Rules:\n" + "- Only local run commands: python/python3/bash/sh/Rscript/R/snakemake/nextflow/make\n" + "- No network download commands (curl/wget/git clone), no sudo, no destructive commands.\n" + "- Prefer commands that execute original method in the repository.\n" + "- Return JSON only.\n" + ) + payload = { + "contents": [ + { + "role": "user", + "parts": [ + { + "text": planner_prompt + + "\n" + + json.dumps( + { + "task": task, + "repo_path": str(source_dir), + "data_type": input_manifest.get("data_type", ""), + "repro_steps": input_manifest.get("repro_steps", []), + "readme_excerpt": readme_text, + }, + ensure_ascii=True, + ) + } + ], + } + ], + "generationConfig": {"temperature": 0.1}, + } + print("payload:"+json.dumps(payload, ensure_ascii=False)) + try: + resp = requests.post(endpoint, json=payload, timeout=60) + if resp.status_code >= 400: + self._append_log( + log_file, + f"[gemini] planner failed status={resp.status_code} body={(resp.text or '')[:500]}", + ) + return self._extract_readme_run_commands(source_dir, max_commands=max_commands) + data = resp.json() + text = "" + for part in (data.get("candidates", [{}])[0].get("content", {}).get("parts", []) or []): + if "text" in part: + text += part["text"] + obj = {} + try: + obj = json.loads(text) if text.strip() else {} + except Exception: + m = re.search(r"```json\s*([\s\S]*?)```", text, re.I) + if m: + obj = json.loads(m.group(1)) + else: + m = re.search(r"(\{[\s\S]*\})", text) + if m: + obj = json.loads(m.group(1)) + commands = obj.get("commands", []) + # Tolerate model output as a plain list + print("obj:"+json.dumps(obj, ensure_ascii=False)) + if not commands and isinstance(obj, list): + commands = obj + # Tolerate inline text commands + if not commands and text.strip(): + candidate_lines = [ln.strip() for ln in text.splitlines() if ln.strip()] + commands = [ + ln.lstrip("- ").strip() + for ln in candidate_lines + if ln.lower().startswith(("python ", "python3 ", "bash ", "sh ", "rscript ", "r ", "snakemake", "nextflow", "make ")) + ] + safe_cmds: list[list[str]] = [] + for item in commands[:max_commands]: + if isinstance(item, str): + cmd = shlex.split(item) + elif isinstance(item, list): + cmd = [str(x) for x in item] + else: + continue + if self._is_safe_run_command(cmd): + safe_cmds.append(cmd) + if not safe_cmds: + self._append_log(log_file, f"[gemini] empty command plan; raw={(text or '')[:500]}") + return self._extract_readme_run_commands(source_dir, max_commands=max_commands) + self._append_log(log_file, f"[gemini] planned_commands={json.dumps(safe_cmds, ensure_ascii=True)}") + return safe_cmds + except Exception as exc: + self._append_log(log_file, f"[gemini] planner exception: {exc}") + return self._extract_readme_run_commands(source_dir, max_commands=max_commands) + + def _execute_local_step( + self, + step_name: str, + *, + task: str, + input_manifest: dict[str, Any], + context: dict[str, Any], + log_file: Path, + ) -> dict[str, Any]: + run_dir: Path = context["run_dir"] + artifacts: dict[str, Any] = context["artifacts"] + code_repo = self._extract_code_repo(task=task, input_manifest=input_manifest) + source_dir = run_dir / "06_source_code" + artifacts["source_repo"] = code_repo + artifacts["source_dir"] = source_dir + + if step_name == "source_clone": + if not code_repo or "unknown_repo" in code_repo: + return {"ok": False, "skipped": True, "result": {"warning": "source_repo_missing"}} + source_dir.parent.mkdir(parents=True, exist_ok=True) + if source_dir.exists(): + result = self._run_local_command(["git", "-C", str(source_dir), "pull"], log_file=log_file) + else: + result = self._run_local_command(["git", "clone", "--depth", "1", code_repo, str(source_dir)], log_file=log_file) + ok = result.get("return_code", 1) == 0 + return {"ok": ok, "result": result} + + if step_name == "source_execute": + if not source_dir.exists(): + return {"ok": False, "skipped": True, "result": {"warning": "source_dir_not_ready"}} + attempts: list[dict[str, Any]] = [] + run_attempt_success = False + has_r = shutil.which("R") is not None + # Attempt 1: install dependencies if common file exists + req = source_dir / "requirements.txt" + if req.exists(): + setup = self._run_local_command(["python", "-m", "pip", "install", "-r", str(req)], cwd=source_dir, log_file=log_file) + setup["attempt_type"] = "setup" + attempts.append(setup) + r_renv = source_dir / "renv.lock" + r_desc = source_dir / "DESCRIPTION" + if r_renv.exists(): + if has_r: + setup = self._run_local_command( + [ + "R", + "--vanilla", + "-e", + "if (!requireNamespace('renv', quietly=TRUE)) install.packages('renv', repos='https://cloud.r-project.org'); renv::restore(prompt=FALSE)", + ], + cwd=source_dir, + log_file=log_file, + timeout_s=1800, + ) + setup["attempt_type"] = "setup" + attempts.append(setup) + else: + attempts.append( + { + "attempt_type": "setup", + "return_code": -1, + "warning": "R_not_found_for_renv_restore", + "command_executed": "R --vanilla -e ", + } + ) + elif r_desc.exists() and has_r: + setup = self._run_local_command( + [ + "R", + "--vanilla", + "-e", + "if (!requireNamespace('remotes', quietly=TRUE)) install.packages('remotes', repos='https://cloud.r-project.org'); remotes::install_local('.', upgrade='never')", + ], + cwd=source_dir, + log_file=log_file, + timeout_s=1800, + ) + setup["attempt_type"] = "setup" + attempts.append(setup) + # Attempt 2: common reproducibility entry scripts + candidate_scripts = ["reproduce.sh", "run.sh", "scripts/reproduce.sh"] + executed = False + for rel in candidate_scripts: + fp = source_dir / rel + if fp.exists(): + run = self._run_local_command(["bash", str(fp)], cwd=source_dir, log_file=log_file) + run["attempt_type"] = "run" + attempts.append(run) + run_attempt_success = run_attempt_success or (run.get("return_code", 1) == 0) + executed = True + break + # Attempt 3: Gemini agent plans and drives repo-specific runnable commands + gemini_cmds: list[list[str]] = [] + if not executed: + gemini_cmds = self._plan_source_commands_with_gemini( + task=task, + source_dir=source_dir, + input_manifest=input_manifest, + log_file=log_file, + ) + print(gemini_cmds) + for cmd in gemini_cmds: + run = self._run_local_command(cmd, cwd=source_dir, log_file=log_file) + run["attempt_type"] = "run_gemini" + attempts.append(run) + if run.get("return_code", 1) == 0: + run_attempt_success = True + executed = True + break + + if run_attempt_success: + return {"ok": True, "result": {"attempts": attempts, "gemini_planned_commands": gemini_cmds}} + warning = "source_execution_not_reproduced" + if not gemini_cmds and not executed: + warning = "gemini_no_executable_command" + return { + "ok": False, + "skipped": True, + "result": { + "warning": warning, + "gemini_planned_commands": gemini_cmds, + "attempts": attempts, + }, + } + + if step_name == "summarize_repro": + summary = { + "source_repo": code_repo or "unknown_repo", + "data_sources": input_manifest.get("data_sources", []), + "repro_steps": input_manifest.get("repro_steps", []), + "notes": ( + "Summary generated from benchmark prompt execution. " + "Review source execution attempts and MCP preprocessing outputs for final conclusion." + ), + } + return {"ok": True, "result": summary} + + return {"ok": False, "result": {"error": f"unsupported_local_step:{step_name}"}} + + @staticmethod + def _bowtie2_index_exists(index_base: str | Path) -> bool: + base = Path(index_base) + bt2_suffixes = [".1.bt2", ".2.bt2", ".3.bt2", ".4.bt2", ".rev.1.bt2", ".rev.2.bt2"] + bt2l_suffixes = [s.replace(".bt2", ".bt2l") for s in bt2_suffixes] + return any((base.parent / (base.name + s)).exists() for s in bt2_suffixes) or any( + (base.parent / (base.name + s)).exists() for s in bt2l_suffixes + ) + + def execute_task( + self, + task: str, + input_manifest: dict[str, Any], + task_scope: str, + pipeline_config: dict[str, Any] | None = None, + ) -> dict[str, Any]: + report = ExperimentReport.new( + task=task, + input_manifest=input_manifest, + pipeline_config_id=(pipeline_config or {}).get("config_id"), + ) + run_dir = self.default_results_root / report.run_id + run_dir.mkdir(parents=True, exist_ok=True) + log_path = run_dir / "pipeline.log" + log_path.write_text("", encoding="utf-8") + + plan = self._plan_task(task=task, task_scope=task_scope, pipeline_config=pipeline_config) + route_snapshot: dict[str, Any] = {} + self._append_log(log_path, f"[planner] generated_plan={json.dumps(plan, ensure_ascii=True)}") + + try: + params = (pipeline_config or {}).get("parameters", {}) + threads = int(params.get("threads", 4)) + quality_cutoff = int(params.get("quality_cutoff", 20)) + index_base = input_manifest.get("reference_index_base") or params.get("reference_index_base") + r1 = Path(input_manifest["r1"]).resolve() + r2 = Path(input_manifest["r2"]).resolve() + + if task_scope != "first_pipeline": + self._append_log( + log_path, + "[planner] non-first_pipeline scope detected; running generic configured steps plan", + ) + if not r1.exists() or not r2.exists(): + raise FileNotFoundError("Input FASTQ files not found for r1/r2.") + + index_ready = bool(index_base) and self._bowtie2_index_exists(index_base) + if not index_ready: + before = len(plan) + plan = [s for s in plan if s.get("name") != "align"] + self._append_log( + log_path, + "[planner] align_step_skipped reason=missing_or_invalid_reference_index", + ) + self._append_log(log_path, f"[planner] plan_rewritten from_steps={before} to_steps={len(plan)}") + + step_results: dict[str, Any] = {} + artifacts: dict[str, Any] = {} + warnings: list[str] = [] + skippable_steps = {"aggregate"} + context = { + "run_dir": run_dir, + "r1": r1, + "r2": r2, + "threads": threads, + "quality_cutoff": quality_cutoff, + "index_base": index_base, + "artifacts": artifacts, + } + + for step in plan: + step_name = step["name"] + if step.get("local_executor"): + local_result = self._execute_local_step( + step_name=step_name, + task=task, + input_manifest=input_manifest, + context=context, + log_file=log_path, + ) + step_results[step_name] = local_result + if not local_result.get("ok"): + warn = f"skip_or_fail_local_step:{step_name}" + warnings.append(warn) + self._append_log(log_path, f"[planner] {warn}") + continue + candidates = step["candidates"] + chosen_route = None + for candidate in candidates: + chosen_route = self._route_or_generate(candidate, log_file=log_path) + if chosen_route is not None: + if self.router.binary_available(chosen_route.function_name): + break + self._append_log( + log_path, + f"[planner] skip_candidate={candidate} reason=binary_unavailable binary={chosen_route.binary_name}", + ) + chosen_route = None + if chosen_route is None: + if step_name in skippable_steps: + warn = f"skip_step:{step_name} reason=no_available_mcp_route" + warnings.append(warn) + self._append_log(log_path, f"[planner] {warn}") + step_results[step_name] = { + "ok": False, + "skipped": True, + "result": {"warning": warn}, + } + continue + raise RuntimeError(f"no_mcp_route_found_for_step:{step_name}") + + route_snapshot[step_name] = { + "requested_candidates": candidates, + "selected_tool": chosen_route.tool_name, + "selected_function": chosen_route.function_name, + "binary": chosen_route.binary_name, + "binary_available": self.router.binary_available(chosen_route.function_name), + "server_script": str(chosen_route.server_script), + "server_exists": chosen_route.server_script.exists(), + } + + kwargs = self._build_step_kwargs( + step_name=step_name, + route_function=chosen_route.function_name, + context=context, + ) + result = self._invoke_mcp_tool(chosen_route, kwargs=kwargs, log_file=log_path) + step_results[step_name] = result + if not result["ok"]: + if step_name in skippable_steps: + warn = f"skip_step:{step_name} reason=tool_execution_failed" + warnings.append(warn) + self._append_log(log_path, f"[planner] {warn}") + step_results[step_name] = { + "ok": False, + "skipped": True, + "result": { + **self._jsonable(result["result"]), + "warning": warn, + }, + } + continue + raise RuntimeError(f"{step_name}_failed") + + # capture key artifacts + output_files = result["result"].get("output_files") or [] + if step_name == "trim": + trim_dir = run_dir / "02_trim" + # best-effort: prefer explicit output_files when available + if output_files: + r1_candidates = [Path(p) for p in output_files if ("_val_1" in p or "_fastp_R1" in p)] + r2_candidates = [Path(p) for p in output_files if ("_val_2" in p or "_fastp_R2" in p)] + if r1_candidates and r2_candidates: + artifacts["trimmed_r1"] = r1_candidates[-1] + artifacts["trimmed_r2"] = r2_candidates[-1] + + if "trimmed_r1" not in artifacts or "trimmed_r2" not in artifacts: + trimmed_r1, trimmed_r2 = self._pick_trimmed_pair(trim_dir) + if trimmed_r1 and trimmed_r2: + artifacts["trimmed_r1"] = trimmed_r1 + artifacts["trimmed_r2"] = trimmed_r2 + if "trimmed_r1" not in artifacts or "trimmed_r2" not in artifacts: + raise RuntimeError("trimmed_files_not_found") + + multiqc_report = run_dir / "05_multiqc" / "multiqc_report.html" + if (step_results.get("aggregate") or {}).get("ok") and multiqc_report.exists(): + artifacts["multiqc_report"] = multiqc_report + + self._collect_fastqc_artifacts(run_dir=run_dir, artifacts=artifacts) + + summary = { + "status": "completed_with_warnings" if warnings else "completed", + "task_scope": task_scope, + "task": task, + "plan": self._jsonable(plan), + "steps": self._jsonable({k: {"ok": v["ok"], "result": v["result"]} for k, v in step_results.items()}), + "warnings": warnings, + "artifacts": { + "trimmed_r1": str(artifacts.get("trimmed_r1", "")), + "trimmed_r2": str(artifacts.get("trimmed_r2", "")), + "sam": str(artifacts.get("sam", "")), + "multiqc_report": str(artifacts.get("multiqc_report", multiqc_report)), + "fastqc_raw_html": self._jsonable(artifacts.get("fastqc_raw_html", [])), + "fastqc_trimmed_html": self._jsonable(artifacts.get("fastqc_trimmed_html", [])), + }, + "router": self._jsonable(route_snapshot), + } + (run_dir / "summary.json").write_text( + json.dumps(summary, indent=2, ensure_ascii=True), + encoding="utf-8", + ) + + analysis_report_path = self._write_analysis_report( + run_dir=run_dir, + task=task, + task_scope=task_scope, + status=summary["status"], + step_results=step_results, + warnings=warnings, + artifacts=artifacts, + ) + + report.status = "completed_with_warnings" if warnings else "completed" + report.execution_log = str(log_path) + report.output_artifacts = [str(run_dir / "summary.json"), str(analysis_report_path)] + if artifacts.get("multiqc_report"): + report.output_artifacts.append(str(artifacts["multiqc_report"])) + report.metrics = { + "steps": len(plan), + "has_pipeline_config": bool(pipeline_config), + "task_scope": task_scope, + "plan": self._jsonable(plan), + "router": self._jsonable(route_snapshot), + "step_results": self._jsonable(step_results), + "warnings": warnings, + "log_tail": self._read_log_tail(log_path), + } + if warnings: + report.notes = ( + "Executed with warnings via MCP routing plan. " + f"Warnings: {'; '.join(warnings)}" + ) + else: + report.notes = "Executed via MCP routing plan with dynamic tool selection and converter fallback." + except Exception as exc: # pragma: no cover - defensive path + report.status = "failed" + report.execution_log = str(log_path) + report.failures.append( + FailureMode( + step_name="executor", + error_type=type(exc).__name__, + error_message=str(exc), + hint="Inspect pipeline.log and parameters.", + ) + ) + self._append_log(log_path, f"[V1] status=failed error={exc}") + failure_summary = { + "status": "failed", + "task_scope": task_scope, + "task": task, + "plan": self._jsonable(plan), + "router": self._jsonable(route_snapshot), + "partial_steps": self._jsonable(step_results), + "artifacts": self._jsonable(artifacts), + "error": {"type": type(exc).__name__, "message": str(exc)}, + "log_tail": self._read_log_tail(log_path), + } + (run_dir / "summary.json").write_text( + json.dumps(failure_summary, indent=2, ensure_ascii=True), + encoding="utf-8", + ) + report.output_artifacts = [str(run_dir / "summary.json")] + report.metrics = { + "task_scope": task_scope, + "router": self._jsonable(route_snapshot), + "plan": self._jsonable(plan), + "partial_steps": self._jsonable(step_results), + "log_tail": self._read_log_tail(log_path), + } + + self.memory.save_report(report) + return report.to_dict() + + def execute_autopilot( + self, + user_goal: str, + data_dir: str | Path, + task_scope: str = "first_pipeline", + pipeline_config: dict[str, Any] | None = None, + manifest_overrides: dict[str, Any] | None = None, + ) -> dict[str, Any]: + r1, r2 = self._discover_paired_fastq(Path(data_dir)) + manifest: dict[str, Any] = {"r1": str(r1), "r2": str(r2)} + if manifest_overrides: + manifest.update(manifest_overrides) + # reference index is optional; execute_task 会根据有效性决定是否执行 align + if pipeline_config and pipeline_config.get("parameters", {}).get("reference_index_base"): + manifest["reference_index_base"] = pipeline_config["parameters"]["reference_index_base"] + return self.execute_task( + task=user_goal, + input_manifest=manifest, + task_scope=task_scope, + pipeline_config=pipeline_config, + ) + + @staticmethod + def _discover_paired_fastq(data_dir: Path) -> tuple[Path, Path]: + if not data_dir.exists(): + raise FileNotFoundError(f"data_dir not found: {data_dir}") + + files = sorted( + [ + p for p in data_dir.iterdir() + if p.is_file() and p.name.lower().endswith((".fastq", ".fq", ".fastq.gz", ".fq.gz")) + ] + ) + if len(files) < 2: + raise ValueError("Need at least two FASTQ files in data_dir.") + + # 优先找 R1/R2 命名对 + for f in files: + name = f.name + r2_name = ( + name.replace("_R1", "_R2") + .replace(".R1.", ".R2.") + .replace("_1.", "_2.") + ) + for g in files: + if g.name == r2_name: + return f, g + + # fallback: 前两个 FASTQ + return files[0], files[1] + + @staticmethod + def _collect_fastqc_artifacts(run_dir: Path, artifacts: dict[str, Any]) -> None: + raw_dir = run_dir / "01_fastqc_raw" + trim_dir = run_dir / "04_fastqc_trimmed" + if raw_dir.exists(): + artifacts["fastqc_raw_html"] = [str(p) for p in sorted(raw_dir.glob("*_fastqc.html"))] + if trim_dir.exists(): + artifacts["fastqc_trimmed_html"] = [str(p) for p in sorted(trim_dir.glob("*_fastqc.html"))] + + @staticmethod + def _extract_fastp_metrics(step_results: dict[str, Any]) -> dict[str, Any]: + trim = step_results.get("trim", {}) + result = trim.get("result", {}) if isinstance(trim, dict) else {} + output_files = result.get("output_files") or [] + json_fp = None + for fp in output_files: + if str(fp).endswith(".json") and "fastp" in str(fp): + json_fp = Path(fp) + break + if not json_fp or not json_fp.exists(): + return {} + try: + payload = json.loads(json_fp.read_text(encoding="utf-8")) + summary = payload.get("summary", {}) + before = summary.get("before_filtering", {}) + after = summary.get("after_filtering", {}) + filtering = summary.get("filtering_result", {}) + return { + "before_total_reads": before.get("total_reads"), + "after_total_reads": after.get("total_reads"), + "q30_rate_after": after.get("q30_rate"), + "passed_filter_reads": filtering.get("passed_filter_reads"), + "low_quality_reads": filtering.get("low_quality_reads"), + } + except Exception: + return {} + + def _write_analysis_report( + self, + run_dir: Path, + task: str, + task_scope: str, + status: str, + step_results: dict[str, Any], + warnings: list[str], + artifacts: dict[str, Any], + ) -> Path: + metrics = self._extract_fastp_metrics(step_results) + lines: list[str] = [] + lines.append("# BioClawMCP Analysis Report") + lines.append("") + lines.append(f"- Task: {task}") + lines.append(f"- Task Scope: {task_scope}") + lines.append(f"- Status: {status}") + lines.append("") + if warnings: + lines.append("## Warnings") + for w in warnings: + lines.append(f"- {w}") + lines.append("") + + lines.append("## Pipeline Execution Summary") + lines.append("| Step | Status |") + lines.append("|---|---|") + for step_name, step_payload in step_results.items(): + ok = step_payload.get("ok", False) + skipped = step_payload.get("skipped", False) + status_text = "skipped" if skipped else ("ok" if ok else "failed") + lines.append(f"| {step_name} | {status_text} |") + lines.append("") + + if metrics: + lines.append("## fastp Key Metrics") + lines.append(f"- Before total reads: {metrics.get('before_total_reads')}") + lines.append(f"- After total reads: {metrics.get('after_total_reads')}") + lines.append(f"- Q30 rate after: {metrics.get('q30_rate_after')}") + lines.append(f"- Passed filter reads: {metrics.get('passed_filter_reads')}") + lines.append(f"- Low quality reads: {metrics.get('low_quality_reads')}") + lines.append("") + + lines.append("## Output Artifacts") + if artifacts.get("trimmed_r1"): + lines.append(f"- Trimmed R1: {artifacts.get('trimmed_r1')}") + if artifacts.get("trimmed_r2"): + lines.append(f"- Trimmed R2: {artifacts.get('trimmed_r2')}") + if artifacts.get("sam"): + lines.append(f"- Alignment SAM: {artifacts.get('sam')}") + if artifacts.get("multiqc_report"): + lines.append(f"- MultiQC report: {artifacts.get('multiqc_report')}") + for fp in artifacts.get("fastqc_raw_html", []): + lines.append(f"- FastQC raw HTML: {fp}") + for fp in artifacts.get("fastqc_trimmed_html", []): + lines.append(f"- FastQC trimmed HTML: {fp}") + lines.append("") + + lines.append("## Per-step Command & Logs") + for step_name, step_payload in step_results.items(): + lines.append(f"### {step_name}") + result = step_payload.get("result", {}) if isinstance(step_payload, dict) else {} + cmd = result.get("command_executed", "") + if cmd: + lines.append("```bash") + lines.append(cmd) + lines.append("```") + stderr = result.get("stderr", "") + stdout = result.get("stdout", "") + if stderr: + lines.append("stderr:") + lines.append("```text") + lines.append(str(stderr)[:6000]) + lines.append("```") + if stdout: + lines.append("stdout:") + lines.append("```text") + lines.append(str(stdout)[:6000]) + lines.append("```") + lines.append("") + + report_path = run_dir / "analysis_report.md" + report_path.write_text("\n".join(lines), encoding="utf-8") + return report_path diff --git a/BioScientist/agent_system/engines/v1_executor_backup/__init__.py b/BioScientist/agent_system/engines/v1_executor_backup/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..2d735710fe68cac6c2b95770d7d325ca94753f3c --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/__init__.py @@ -0,0 +1,3 @@ +from .version import __version__ + +__all__ = ["__version__"] diff --git a/BioScientist/agent_system/engines/v1_executor_backup/agent/__init__.py b/BioScientist/agent_system/engines/v1_executor_backup/agent/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..cad71beeb4b02f1dc28eda33c80eb4ee9fea3dea --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/agent/__init__.py @@ -0,0 +1 @@ +from biomni.agent.a1 import A1 # noqa: F401 diff --git a/BioScientist/agent_system/engines/v1_executor_backup/agent/__pycache__/__init__.cpython-311.pyc b/BioScientist/agent_system/engines/v1_executor_backup/agent/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d23bf2cf0ee5ad213cb3984194c95c8f36afb1b2 Binary files /dev/null and b/BioScientist/agent_system/engines/v1_executor_backup/agent/__pycache__/__init__.cpython-311.pyc differ diff --git a/BioScientist/agent_system/engines/v1_executor_backup/agent/__pycache__/__init__.cpython-313.pyc b/BioScientist/agent_system/engines/v1_executor_backup/agent/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ee10b171ff3bbd06c54ae44443a37a2aa189a7e0 Binary files /dev/null and b/BioScientist/agent_system/engines/v1_executor_backup/agent/__pycache__/__init__.cpython-313.pyc differ diff --git a/BioScientist/agent_system/engines/v1_executor_backup/agent/a1.py b/BioScientist/agent_system/engines/v1_executor_backup/agent/a1.py new file mode 100644 index 0000000000000000000000000000000000000000..9f0d29801e64a2134c2b09a9d2f097a0e0a21ea1 --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/agent/a1.py @@ -0,0 +1,3007 @@ +import glob +import inspect +import os +import re +from collections.abc import Generator +from datetime import datetime +from pathlib import Path +from typing import Any, Literal, TypedDict + +import pandas as pd +from dotenv import load_dotenv +from langchain_core.messages import AIMessage, BaseMessage, HumanMessage, SystemMessage +from langchain_core.prompts import ChatPromptTemplate +from langgraph.checkpoint.memory import MemorySaver +from langgraph.graph import END, START, StateGraph + +from biomni.config import default_config +from biomni.know_how import KnowHowLoader +from biomni.llm import SourceType, get_llm +from biomni.model.retriever import ToolRetriever +from biomni.tool.support_tools import run_python_repl +from biomni.tool.tool_registry import ToolRegistry +from biomni.utils import ( + check_and_download_s3_files, + clean_message_content, + convert_markdown_to_pdf, + create_parsing_error_html, + find_matching_execution, + format_execute_tags_in_content, + format_lists_in_text, + format_observation_as_terminal, + function_to_api_schema, + has_execution_results, + inject_custom_functions_to_repl, + parse_tool_calls_from_code, + parse_tool_calls_with_modules, + pretty_print, + read_module2api, + run_bash_script, + run_r_code, + run_with_timeout, + should_skip_message, + textify_api_dict, +) + +if os.path.exists(".env"): + load_dotenv(".env", override=False) + print("Loaded environment variables from .env") + + +class AgentState(TypedDict): + messages: list[BaseMessage] + next_step: str | None + + +class A1: + def __init__( + self, + path: str | None = None, + llm: str | None = None, + source: SourceType | None = None, + use_tool_retriever: bool | None = None, + timeout_seconds: int | None = None, + base_url: str | None = None, + api_key: str | None = None, + commercial_mode: bool | None = None, + expected_data_lake_files: list | None = None, + ): + """Initialize the biomni agent. + + Args: + path: Path to the data + llm: LLM to use for the agent + source (str): Source provider: "OpenAI", "AzureOpenAI", "Anthropic", "Ollama", "Gemini", "Bedrock", or "Custom" + use_tool_retriever: If True, use a tool retriever + timeout_seconds: Timeout for code execution in seconds + base_url: Base URL for custom model serving (e.g., "http://localhost:8000/v1") + api_key: API key for the custom LLM + commercial_mode: If True, excludes datasets that require commercial licenses or are non-commercial only + + """ + # Use default_config values for unspecified parameters + if path is None: + path = default_config.path + if llm is None: + llm = default_config.llm + if source is None: + source = default_config.source + if use_tool_retriever is None: + use_tool_retriever = default_config.use_tool_retriever + if timeout_seconds is None: + timeout_seconds = default_config.timeout_seconds + if base_url is None: + base_url = default_config.base_url + if api_key is None: + api_key = default_config.api_key if default_config.api_key else "EMPTY" + if commercial_mode is None: + commercial_mode = default_config.commercial_mode + + # Import appropriate env_desc based on commercial_mode + if commercial_mode: + from biomni.env_desc_cm import data_lake_dict, library_content_dict + + print("🏢 Commercial mode: Using commercial-licensed datasets only") + else: + from biomni.env_desc import data_lake_dict, library_content_dict + + print("🎓 Academic mode: Using all datasets (including non-commercial)") + + # Store as instance attributes for later use + self.data_lake_dict = data_lake_dict + self.library_content_dict = library_content_dict + self.commercial_mode = commercial_mode + + # Display configuration in a nice, readable format + print("\n" + "=" * 50) + print("🔧 BIOMNI CONFIGURATION") + print("=" * 50) + + # Get the actual LLM values that will be used by the agent + agent_llm = llm if llm is not None else default_config.llm + agent_source = source if source is not None else default_config.source + + # Show default config (database LLM) + print("📋 DEFAULT CONFIG (Including Database LLM):") + config_dict = default_config.to_dict() + for key, value in config_dict.items(): + if value is not None: + # Special formatting for commercial_mode + if key == "commercial_mode": + mode_text = "Commercial (licensed datasets only)" if value else "Academic (all datasets)" + print(f" {key.replace('_', ' ').title()}: {mode_text}") + else: + print(f" {key.replace('_', ' ').title()}: {value}") + + # Show agent-specific LLM if different from default + if agent_llm != default_config.llm or agent_source != default_config.source: + print("\n🤖 AGENT LLM (Constructor Override):") + print(f" LLM Model: {agent_llm}") + if agent_source is not None: + print(f" Source: {agent_source}") + if base_url is not None: + print(f" Base URL: {base_url}") + if api_key is not None and api_key != "EMPTY": + print(f" API Key: {'*' * 8 + api_key[-4:] if len(api_key) > 8 else '***'}") + + print("=" * 50 + "\n") + + self.path = path + + if not os.path.exists(path): + os.makedirs(path) + print(f"Created directory: {path}") + + # --- Begin custom folder/file checks --- + benchmark_dir = os.path.join(path, "biomni_data", "benchmark") + data_lake_dir = os.path.join(path, "biomni_data", "data_lake") + + # Create the biomni_data directory structure + os.makedirs(benchmark_dir, exist_ok=True) + os.makedirs(data_lake_dir, exist_ok=True) + + if expected_data_lake_files is None: + expected_data_lake_files = list(self.data_lake_dict.keys()) + + # Check and download missing data lake files + print("Checking and downloading missing data lake files...") + check_and_download_s3_files( + s3_bucket_url="https://biomni-release.s3.amazonaws.com", + local_data_lake_path=data_lake_dir, + expected_files=expected_data_lake_files, + folder="data_lake", + ) + + # Check if benchmark directory structure is complete + benchmark_ok = False + if os.path.isdir(benchmark_dir): + patient_gene_detection_dir = os.path.join(benchmark_dir, "hle") + if os.path.isdir(patient_gene_detection_dir): + benchmark_ok = True + + if not benchmark_ok: + print("Checking and downloading benchmark files...") + check_and_download_s3_files( + s3_bucket_url="https://biomni-release.s3.amazonaws.com", + local_data_lake_path=benchmark_dir, + expected_files=[], # Empty list - will download entire folder + folder="benchmark", + ) + else: + print("Skipping datalake download (load_datalake=False)") + print("Note: Some tools may require datalake files to function properly.") + + self.path = os.path.join(path, "biomni_data") + module2api = read_module2api() + + self.llm = get_llm( + llm, + stop_sequences=["", ""], + source=source, + base_url=base_url, + api_key=api_key, + config=default_config, + ) + self.module2api = module2api + self.use_tool_retriever = use_tool_retriever + + if self.use_tool_retriever: + self.tool_registry = ToolRegistry(module2api) + self.retriever = ToolRetriever() + + # Initialize know-how loader + self.know_how_loader = KnowHowLoader() + + # Filter know-how documents based on commercial mode + if commercial_mode: + self._filter_know_how_for_commercial_mode() + + print(f"📚 Loaded {len(self.know_how_loader.documents)} know-how documents") + + # Add timeout parameter + self.timeout_seconds = timeout_seconds # 10 minutes default timeout + self.configure() + + def add_tool(self, api): + """Add a new tool to the agent's tool registry and make it available for retrieval. + + Args: + api: A callable function to be added as a tool + + """ + try: + # Get function information + function_code = inspect.getsource(api) + module_name = api.__module__ if hasattr(api, "__module__") else "custom_tools" + function_name = api.__name__ if hasattr(api, "__name__") else str(api) + + # Generate API schema using the existing utility function + schema = function_to_api_schema(function_code, self.llm) + + # Ensure the schema has all required fields for the tool registry + if not isinstance(schema, dict): + raise ValueError("Generated schema is not a dictionary") + + # Validate and enhance the schema + + # Set default values if missing + if "name" not in schema: + schema["name"] = function_name + if "description" not in schema: + schema["description"] = f"Custom tool: {function_name}" + if "required_parameters" not in schema: + # Try to extract from parameters if available + if "parameters" in schema and isinstance(schema["parameters"], dict): + required_params = [] + params = schema["parameters"] + if "properties" in params: + for param_name in params["properties"]: + if param_name in params.get("required", []): + required_params.append(param_name) + schema["required_parameters"] = required_params + else: + schema["required_parameters"] = [] + + # Add module information to the schema + schema["module"] = module_name + + # Add the tool to the tool registry if it exists + if hasattr(self, "tool_registry") and self.tool_registry is not None: + try: + self.tool_registry.register_tool(schema) + print(f"Successfully registered tool '{schema['name']}' in tool registry") + except Exception as e: + print(f"Warning: Failed to register tool in registry: {e}") + # Continue with adding to module2api even if registry fails + + # Add the tool to module2api structure for system prompt generation + if not hasattr(self, "module2api") or self.module2api is None: + self.module2api = {} + + if module_name not in self.module2api: + self.module2api[module_name] = [] + + # Check if tool already exists in module2api to avoid duplicates + existing_tool = None + for existing in self.module2api[module_name]: + if existing.get("name") == schema["name"]: + existing_tool = existing + break + + if existing_tool: + # Update existing tool + existing_tool.update(schema) + print(f"Updated existing tool '{schema['name']}' in module '{module_name}'") + else: + # Add new tool + self.module2api[module_name].append(schema) + print(f"Added new tool '{schema['name']}' to module '{module_name}'") + + # Update the tool registry's document dataframe if it exists + if hasattr(self, "tool_registry") and self.tool_registry is not None: + try: + # Rebuild the document dataframe + docs = [] + for tool_id in range(len(self.tool_registry.tools)): + docs.append( + [ + int(tool_id), + self.tool_registry.get_tool_by_id(int(tool_id)), + ] + ) + self.tool_registry.document_df = pd.DataFrame(docs, columns=["docid", "document_content"]) + except Exception as e: + print(f"Warning: Failed to update tool registry document dataframe: {e}") + + # Store the original function for potential future use + if not hasattr(self, "_custom_functions"): + self._custom_functions = {} + self._custom_functions[schema["name"]] = api + + # Also store in _custom_tools for highlighting + if not hasattr(self, "_custom_tools"): + self._custom_tools = {} + self._custom_tools[schema["name"]] = { + "name": schema["name"], + "description": schema["description"], + "module": module_name, + } + + # Make the function available in the global namespace for execution + import builtins + + if not hasattr(builtins, "_biomni_custom_functions"): + builtins._biomni_custom_functions = {} + builtins._biomni_custom_functions[schema["name"]] = api + + print( + f"Tool '{schema['name']}' successfully added and ready for use in both direct execution and retrieval" + ) + self.configure() + return schema + + except Exception as e: + print(f"Error adding tool: {e}") + import traceback + + traceback.print_exc() + raise + + def add_mcp(self, config_path: str | Path = "./tutorials/examples/mcp_config.yaml") -> None: + """ + Add MCP (Model Context Protocol) tools from configuration file. + + This method dynamically registers MCP server tools as callable functions within + the biomni agent system. Each MCP server is loaded as an independent module + with its tools exposed as synchronous wrapper functions. + + Supports both manual tool definitions and automatic tool discovery from MCP servers. + + Args: + config_path: Path to the MCP configuration YAML file containing server + definitions and tool specifications. + + Raises: + FileNotFoundError: If the config file doesn't exist + yaml.YAMLError: If the config file is malformed + RuntimeError: If MCP server initialization fails + """ + import asyncio + import os + import sys + import types + from pathlib import Path + + import nest_asyncio + import yaml + from mcp import ClientSession + from mcp.client.stdio import StdioServerParameters, stdio_client + + nest_asyncio.apply() + + def discover_mcp_tools_sync(server_params: StdioServerParameters) -> list[dict]: + """Discover available tools from MCP server synchronously.""" + try: + + async def _discover_async(): + async with stdio_client(server_params) as (reader, writer): + async with ClientSession(reader, writer) as session: + await session.initialize() + + # Get available tools + tools_result = await session.list_tools() + tools = tools_result.tools if hasattr(tools_result, "tools") else tools_result + + discovered_tools = [] + for tool in tools: + if hasattr(tool, "name"): + discovered_tools.append( + { + "name": tool.name, + "description": tool.description, + "inputSchema": tool.inputSchema, + } + ) + else: + print(f"Warning: Skipping tool with no name attribute: {tool}") + + return discovered_tools + + return asyncio.run(_discover_async()) + except Exception as e: + print(f"Failed to discover tools: {e}") + return [] + + def make_mcp_wrapper(cmd: str, args: list[str], tool_name: str, doc: str, env_vars: dict = None): + """Create a synchronous wrapper for an async MCP tool call.""" + + def sync_tool_wrapper(**kwargs): + """Synchronous wrapper for MCP tool execution.""" + try: + server_params = StdioServerParameters(command=cmd, args=args, env=env_vars) + + async def async_tool_call(): + async with stdio_client(server_params) as (reader, writer): + async with ClientSession(reader, writer) as session: + await session.initialize() + result = await session.call_tool(tool_name, kwargs) + content = result.content[0] + if hasattr(content, "json"): + return content.json() + return content.text + + try: + loop = asyncio.get_running_loop() + return loop.create_task(async_tool_call()) + except RuntimeError: + return asyncio.run(async_tool_call()) + + except Exception as e: + raise RuntimeError(f"MCP tool execution failed for '{tool_name}': {e}") from e + + sync_tool_wrapper.__name__ = tool_name + sync_tool_wrapper.__doc__ = doc + return sync_tool_wrapper + + # Initialize registries if they don't exist + self._custom_functions = getattr(self, "_custom_functions", {}) + self._custom_tools = getattr(self, "_custom_tools", {}) + + # Load and validate configuration + try: + config_content = Path(config_path).read_text(encoding="utf-8") + cfg: dict[str, Any] = yaml.safe_load(config_content) or {} + except FileNotFoundError: + raise FileNotFoundError(f"MCP config file not found: {config_path}") from None + except yaml.YAMLError as e: + raise yaml.YAMLError(f"Invalid YAML in MCP config: {e}") from e + + mcp_servers: dict[str, Any] = cfg.get("mcp_servers", {}) + if not mcp_servers: + print("Warning: No MCP servers found in configuration") + return + + # Process each MCP server configuration + for server_name, server_meta in mcp_servers.items(): + if not server_meta.get("enabled", True): + continue + + # Validate command configuration + cmd_list = server_meta.get("command", []) + if not cmd_list or not isinstance(cmd_list, list): + print(f"Warning: Invalid command configuration for server '{server_name}'") + continue + + cmd, *args = cmd_list + + # Process environment variables + env_vars = server_meta.get("env", {}) + if env_vars: + processed_env = {} + for key, value in env_vars.items(): + if isinstance(value, str) and value.startswith("${") and value.endswith("}"): + var_name = value[2:-1] + processed_env[key] = os.getenv(var_name, "") + else: + processed_env[key] = value + env_vars = processed_env + + # Create module namespace for this MCP server + mcp_module_name = f"mcp_servers.{server_name}" + if mcp_module_name not in sys.modules: + sys.modules[mcp_module_name] = types.ModuleType(mcp_module_name) + server_module = sys.modules[mcp_module_name] + + tools_config = server_meta.get("tools", []) + + if not tools_config: + try: + server_params = StdioServerParameters(command=cmd, args=args, env=env_vars) + tools_config = discover_mcp_tools_sync(server_params) + + if tools_config: + print(f"Discovered {len(tools_config)} tools from {server_name} MCP server") + else: + print(f"Warning: No tools discovered from {server_name} MCP server") + continue + + except Exception as e: + print(f"Failed to discover tools for {server_name}: {e}") + continue + + # Register each tool + for tool_meta in tools_config: + if isinstance(tool_meta, dict) and "biomni_name" in tool_meta: + # Manual tool definition + tool_name = tool_meta.get("biomni_name") + description = tool_meta.get("description", f"MCP tool: {tool_name}") + parameters = tool_meta.get("parameters", {}) + # For manual tools, check if each parameter has a "required" field + required_param_names = [] + for param_name, param_spec in parameters.items(): + if param_spec.get("required", False): + required_param_names.append(param_name) + else: + # Auto-discovered tool + tool_name = tool_meta.get("name") + description = tool_meta.get("description", f"MCP tool: {tool_name}") + input_schema = tool_meta.get("inputSchema", {}) + parameters = input_schema.get("properties", {}) + # For auto-discovered tools, get required list from inputSchema top level + required_param_names = input_schema.get("required", []) + + if not tool_name: + print(f"Warning: Skipping tool with no name in {server_name}") + continue + + # Create wrapper function + wrapper_function = make_mcp_wrapper(cmd, args, tool_name, description, env_vars) + + # Add to module namespace + setattr(server_module, tool_name, wrapper_function) + + # Build parameter lists + required_params, optional_params = [], [] + for param_name, param_spec in parameters.items(): + param_info = { + "name": param_name, + "type": str(param_spec.get("type", "string")), + "description": param_spec.get("description", ""), + "default": param_spec.get("default", None), + } + + # Check if parameter is required based on the required_param_names list + if param_name in required_param_names: + required_params.append(param_info) + else: + optional_params.append(param_info) + + # Create tool schema + tool_schema = { + "name": tool_name, + "description": description, + "parameters": parameters, + "required_parameters": required_params, + "optional_parameters": optional_params, + "module": mcp_module_name, + "fn": wrapper_function, + } + + # Register in tool registry + self.tool_registry.register_tool(tool_schema) + + # Add to module2api mapping + if mcp_module_name not in self.module2api: + self.module2api[mcp_module_name] = [] + self.module2api[mcp_module_name].append(tool_schema) + + # Add to instance registries + self._custom_functions[tool_name] = wrapper_function + self._custom_tools[tool_name] = { + "name": tool_name, + "description": description, + "module": mcp_module_name, + } + + # Update agent configuration + self.configure() + + def get_custom_tool(self, name): + """Get a custom tool by name. + + Args: + name: The name of the custom tool + + Returns: + The custom tool function if found, None otherwise + + """ + if hasattr(self, "_custom_functions") and name in self._custom_functions: + return self._custom_functions[name] + return None + + def list_custom_tools(self): + """List all custom tools that have been added. + + Returns: + A list of custom tool names + + """ + if hasattr(self, "_custom_functions"): + return list(self._custom_functions.keys()) + return [] + + def remove_custom_tool(self, name): + """Remove a custom tool. + + Args: + name: The name of the custom tool to remove + + Returns: + True if the tool was removed, False if it wasn't found + + """ + removed = False + + # Remove from custom functions + if hasattr(self, "_custom_functions") and name in self._custom_functions: + del self._custom_functions[name] + removed = True + + # Remove from custom tools (for highlighting) + if hasattr(self, "_custom_tools") and name in self._custom_tools: + del self._custom_tools[name] + removed = True + + # Remove from global namespace + import builtins + + if hasattr(builtins, "_biomni_custom_functions") and name in builtins._biomni_custom_functions: + del builtins._biomni_custom_functions[name] + + # Remove from tool registry + if hasattr(self, "tool_registry") and self.tool_registry is not None: + if self.tool_registry.remove_tool_by_name(name): + removed = True + # Rebuild the document dataframe + try: + docs = [] + for tool_id in range(len(self.tool_registry.tools)): + docs.append( + [ + int(tool_id), + self.tool_registry.get_tool_by_id(int(tool_id)), + ] + ) + self.tool_registry.document_df = pd.DataFrame(docs, columns=["docid", "document_content"]) + except Exception as e: + print(f"Warning: Failed to update tool registry document dataframe: {e}") + + # Remove from module2api + if hasattr(self, "module2api"): + for tools in self.module2api.values(): + for i, tool in enumerate(tools): + if tool.get("name") == name: + del tools[i] + removed = True + break + + if removed: + print(f"Custom tool '{name}' has been removed") + else: + print(f"Custom tool '{name}' was not found") + + return removed + + def add_data(self, data): + """Add new data to the data lake. + + Args: + data: Dictionary with file path as key and description as value + e.g., {'my_dataset.csv': 'A dataset containing gene expression data'} + or {'path/to/file.txt': 'Description of the file'} + + """ + try: + if not isinstance(data, dict): + raise ValueError("Data must be a dictionary with file path as key and description as value") + + # Initialize custom data storage if it doesn't exist + if not hasattr(self, "_custom_data"): + self._custom_data = {} + + # Add each data item + for file_path, description in data.items(): + if not isinstance(file_path, str) or not isinstance(description, str): + print("Warning: Skipping invalid data entry - file_path and description must be strings") + continue + + # Extract filename from path for storage + filename = os.path.basename(file_path) if "/" in file_path else file_path + + # Store the data with both the full path and description + self._custom_data[filename] = { + "path": file_path, + "description": description, + } + + # Also add to the data_lake_dict for consistency + self.data_lake_dict[filename] = description + + print(f"Added data item '{filename}': {description}") + self.configure() + print(f"Successfully added {len(data)} data item(s) to the data lake") + return True + + except Exception as e: + print(f"Error adding data: {e}") + import traceback + + traceback.print_exc() + return False + + def get_custom_data(self, name): + """Get a custom data item by name. + + Args: + name: The name of the custom data item + + Returns: + The custom data item info if found, None otherwise + + """ + if hasattr(self, "_custom_data") and name in self._custom_data: + return self._custom_data[name] + return None + + def list_custom_data(self): + """List all custom data items that have been added. + + Returns: + A list of custom data item names and descriptions + + """ + if hasattr(self, "_custom_data"): + return [(name, info["description"]) for name, info in self._custom_data.items()] + return [] + + def remove_custom_data(self, name): + """Remove a custom data item. + + Args: + name: The name of the custom data item to remove + + Returns: + True if the data item was removed, False if it wasn't found + + """ + removed = False + + # Remove from custom data + if hasattr(self, "_custom_data") and name in self._custom_data: + del self._custom_data[name] + removed = True + + # Remove from data_lake_dict + if hasattr(self, "data_lake_dict") and name in self.data_lake_dict: + del self.data_lake_dict[name] + removed = True + + if removed: + print(f"Custom data item '{name}' has been removed") + else: + print(f"Custom data item '{name}' was not found") + + return removed + + def add_software(self, software): + """Add new software to the software library. + + Args: + software: Dictionary with software name as key and description as value + e.g., {'custom_tool': 'A custom analysis tool for processing data'} + or {'my_package': 'Description of the package functionality'} + + """ + try: + if not isinstance(software, dict): + raise ValueError("Software must be a dictionary with software name as key and description as value") + + # Initialize custom software storage if it doesn't exist + if not hasattr(self, "_custom_software"): + self._custom_software = {} + + # Add each software item + for software_name, description in software.items(): + if not isinstance(software_name, str) or not isinstance(description, str): + print("Warning: Skipping invalid software entry - software_name and description must be strings") + continue + + # Store the software with description + self._custom_software[software_name] = { + "name": software_name, + "description": description, + } + + # Also add to the library_content_dict for consistency + self.library_content_dict[software_name] = description + + print(f"Added software '{software_name}': {description}") + + print(f"Successfully added {len(software)} software item(s) to the library") + self.configure() + return True + + except Exception as e: + print(f"Error adding software: {e}") + import traceback + + traceback.print_exc() + return False + + def get_custom_software(self, name): + """Get a custom software item by name. + + Args: + name: The name of the custom software item + + Returns: + The custom software item info if found, None otherwise + + """ + if hasattr(self, "_custom_software") and name in self._custom_software: + return self._custom_software[name] + return None + + def list_custom_software(self): + """List all custom software items that have been added. + + Returns: + A list of custom software item names and descriptions + + """ + if hasattr(self, "_custom_software"): + return [(name, info["description"]) for name, info in self._custom_software.items()] + return [] + + def remove_custom_software(self, name): + """Remove a custom software item. + + Args: + name: The name of the custom software item to remove + + Returns: + True if the software item was removed, False if it wasn't found + + """ + removed = False + + # Remove from custom software + if hasattr(self, "_custom_software") and name in self._custom_software: + del self._custom_software[name] + removed = True + + # Remove from library_content_dict + if hasattr(self, "library_content_dict") and name in self.library_content_dict: + del self.library_content_dict[name] + removed = True + + if removed: + print(f"Custom software item '{name}' has been removed") + else: + print(f"Custom software item '{name}' was not found") + + return removed + + def _filter_know_how_for_commercial_mode(self): + """Filter out know-how documents that don't allow commercial use. + + This method removes documents from the know-how loader that have + commercial use restrictions when the agent is in commercial mode. + """ + docs_to_remove = [] + + for doc_id, doc in self.know_how_loader.documents.items(): + metadata = doc.get("metadata", {}) + commercial_use = metadata.get("commercial_use", "") + + # Check if commercial use is NOT allowed + if "❌" in commercial_use or "Not Allowed" in commercial_use or "Non-Commercial" in commercial_use: + docs_to_remove.append(doc_id) + + # Remove documents that don't allow commercial use + for doc_id in docs_to_remove: + doc_name = self.know_how_loader.documents[doc_id]["name"] + self.know_how_loader.remove_document(doc_id) + print(f" ⚠️ Excluded know-how '{doc_name}' (non-commercial license)") + + def _generate_system_prompt( + self, + tool_desc, + data_lake_content, + library_content_list, + self_critic=False, + is_retrieval=False, + custom_tools=None, + custom_data=None, + custom_software=None, + know_how_docs=None, + ): + """Generate the system prompt based on the provided resources. + + Args: + tool_desc: Dictionary of tool descriptions + data_lake_content: List of data lake items + library_content_list: List of libraries + self_critic: Whether to include self-critic instructions + is_retrieval: Whether this is for retrieval (True) or initial configuration (False) + custom_tools: List of custom tools to highlight + custom_data: List of custom data items to highlight + custom_software: List of custom software items to highlight + know_how_docs: List of know-how documents with best practices and protocols + + Returns: + The generated system prompt + + """ + + def format_item_with_description(name, description): + """Format an item with its description in a readable way.""" + # Handle None or empty descriptions + if not description: + description = f"Data lake item: {name}" + + # Check if the item is already formatted (contains a colon) + if isinstance(name, str) and ": " in name: + return name + + # Wrap long descriptions to make them more readable + max_line_length = 80 + if len(description) > max_line_length: + # Simple wrapping for long descriptions + wrapped_desc = [] + words = description.split() + current_line = "" + + for word in words: + if len(current_line) + len(word) + 1 <= max_line_length: + if current_line: + current_line += " " + word + else: + current_line = word + else: + wrapped_desc.append(current_line) + current_line = word + + if current_line: + wrapped_desc.append(current_line) + + # Join with newlines and proper indentation + formatted_desc = f"{name}:\n " + "\n ".join(wrapped_desc) + return formatted_desc + else: + return f"{name}: {description}" + + # Separate custom and default resources + default_data_lake_content = [] + default_library_content_list = [] + + # Filter out custom items from default lists + custom_data_names = set() + custom_software_names = set() + + if custom_data: + custom_data_names = {item.get("name") if isinstance(item, dict) else item for item in custom_data} + if custom_software: + custom_software_names = {item.get("name") if isinstance(item, dict) else item for item in custom_software} + + # Separate default data lake items + for item in data_lake_content: + if isinstance(item, dict): + name = item.get("name", "") + if name not in custom_data_names: + default_data_lake_content.append(item) + elif item not in custom_data_names: + default_data_lake_content.append(item) + + # Separate default library items + for lib in library_content_list: + if isinstance(lib, dict): + name = lib.get("name", "") + if name not in custom_software_names: + default_library_content_list.append(lib) + elif lib not in custom_software_names: + default_library_content_list.append(lib) + + # Format the default data lake content + if isinstance(default_data_lake_content, list) and all( + isinstance(item, str) for item in default_data_lake_content + ): + # Simple list of strings - check if they already have descriptions + data_lake_formatted = [] + for item in default_data_lake_content: + # Check if the item already has a description (contains a colon) + if ": " in item: + data_lake_formatted.append(item) + else: + description = self.data_lake_dict.get(item, f"Data lake item: {item}") + data_lake_formatted.append(format_item_with_description(item, description)) + else: + # List with descriptions + data_lake_formatted = [] + for item in default_data_lake_content: + if isinstance(item, dict): + name = item.get("name", "") + description = self.data_lake_dict.get(name, f"Data lake item: {name}") + data_lake_formatted.append(format_item_with_description(name, description)) + # Check if the item already has a description (contains a colon) + elif isinstance(item, str) and ": " in item: + data_lake_formatted.append(item) + else: + description = self.data_lake_dict.get(item, f"Data lake item: {item}") + data_lake_formatted.append(format_item_with_description(item, description)) + + # Format the default library content + if isinstance(default_library_content_list, list) and all( + isinstance(item, str) for item in default_library_content_list + ): + if ( + len(default_library_content_list) > 0 + and isinstance(default_library_content_list[0], str) + and "," not in default_library_content_list[0] + ): + # Simple list of strings + libraries_formatted = [] + for lib in default_library_content_list: + description = self.library_content_dict.get(lib, f"Software library: {lib}") + libraries_formatted.append(format_item_with_description(lib, description)) + else: + # Already formatted string + libraries_formatted = default_library_content_list + else: + # List with descriptions + libraries_formatted = [] + for lib in default_library_content_list: + if isinstance(lib, dict): + name = lib.get("name", "") + description = self.library_content_dict.get(name, f"Software library: {name}") + libraries_formatted.append(format_item_with_description(name, description)) + else: + description = self.library_content_dict.get(lib, f"Software library: {lib}") + libraries_formatted.append(format_item_with_description(lib, description)) + + # Format custom resources with highlighting + custom_tools_formatted = [] + if custom_tools: + for tool in custom_tools: + if isinstance(tool, dict): + name = tool.get("name", "Unknown") + desc = tool.get("description", "") + module = tool.get("module", "custom_tools") + custom_tools_formatted.append(f"🔧 {name} (from {module}): {desc}") + else: + custom_tools_formatted.append(f"🔧 {str(tool)}") + + custom_data_formatted = [] + if custom_data: + for item in custom_data: + if isinstance(item, dict): + name = item.get("name", "Unknown") + desc = item.get("description", "") + custom_data_formatted.append(f"📊 {format_item_with_description(name, desc)}") + else: + desc = self.data_lake_dict.get(item, f"Custom data: {item}") + custom_data_formatted.append(f"📊 {format_item_with_description(item, desc)}") + + custom_software_formatted = [] + if custom_software: + for item in custom_software: + if isinstance(item, dict): + name = item.get("name", "Unknown") + desc = item.get("description", "") + custom_software_formatted.append(f"⚙️ {format_item_with_description(name, desc)}") + else: + desc = self.library_content_dict.get(item, f"Custom software: {item}") + custom_software_formatted.append(f"⚙️ {format_item_with_description(item, desc)}") + + # Format know-how documents - include FULL content (metadata already stripped) + know_how_formatted = [] + if know_how_docs: + for doc in know_how_docs: + if isinstance(doc, dict): + name = doc.get("name", "Unknown") + content = doc.get("content", "") + # Include full content in system prompt (metadata already removed) + know_how_formatted.append(f"📚 {name}:\n{content}") + + # Base prompt + prompt_modifier = """ +You are a helpful biomedical assistant assigned with the task of problem-solving. +To achieve this, you will be using an interactive coding environment equipped with a variety of tool functions, data, and softwares to assist you throughout the process. + +Given a task, make a plan first. The plan should be a numbered list of steps that you will take to solve the task. Be specific and detailed. +Format your plan as a checklist with empty checkboxes like this: +1. [ ] First step +2. [ ] Second step +3. [ ] Third step + +Follow the plan step by step. After completing each step, update the checklist by replacing the empty checkbox with a checkmark: +1. [✓] First step (completed) +2. [ ] Second step +3. [ ] Third step + +If a step fails or needs modification, mark it with an X and explain why: +1. [✓] First step (completed) +2. [✗] Second step (failed because...) +3. [ ] Modified second step +4. [ ] Third step + +Always show the updated plan after each step so the user can track progress. + +At each turn, you should first provide your thinking and reasoning given the conversation history. +After that, you have two options: + +1) Interact with a programming environment and receive the corresponding output within . Your code should be enclosed using "" tag, for example: print("Hello World!") . IMPORTANT: You must end the code block with tag. + - For Python code (default): print("Hello World!") + - For R code: #!R\nlibrary(ggplot2)\nprint("Hello from R") + - For Bash scripts and commands: #!BASH\necho "Hello from Bash"\nls -la + - For CLI softwares, use Bash scripts. + +2) When you think it is ready, directly provide a solution that adheres to the required format for the given task to the user. Your solution should be enclosed using "" tag, for example: The answer is A . IMPORTANT: You must end the solution block with tag. + +You have many chances to interact with the environment to receive the observation. So you can decompose your code into multiple steps. +Don't overcomplicate the code. Keep it simple and easy to understand. +When writing the code, please print out the steps and results in a clear and concise manner, like a research log. +When calling the existing python functions in the function dictionary, YOU MUST SAVE THE OUTPUT and PRINT OUT the result. +For example, result = understand_scRNA(XXX) print(result) +Otherwise the system will not be able to know what has been done. + +For R code, use the #!R marker at the beginning of your code block to indicate it's R code. +For Bash scripts and commands, use the #!BASH marker at the beginning of your code block. This allows for both simple commands and multi-line scripts with variables, loops, conditionals, loops, and other Bash features. + +In each response, you must include EITHER or tag. Not both at the same time. Do not respond with messages without any tags. No empty messages. +""" + + # Add self-critic instructions if needed + if self_critic: + prompt_modifier += """ +You may or may not receive feedbacks from human. If so, address the feedbacks by following the same procedure of multiple rounds of thinking, execution, and then coming up with a new solution. +""" + + # Add protocol generation instructions + prompt_modifier += """ +PROTOCOL GENERATION: +If the user requests an experimental protocol, use search_protocols(), advanced_web_search_claude(), list_local_protocols(), and read_local_protocol() to generate an accurate protocol. Include details such as reagents (with catalog numbers if available), equipment specifications, replicate requirements, error handling, and troubleshooting - but ONLY include information found in these resources. Do not make up specifications, catalog numbers, or equipment details. Prioritize accuracy over completeness. +""" + + # Add custom resources section first (highlighted) + has_custom_resources = any( + [custom_tools_formatted, custom_data_formatted, custom_software_formatted, know_how_formatted] + ) + + if has_custom_resources: + prompt_modifier += """ + +PRIORITY CUSTOM RESOURCES +=============================== +IMPORTANT: The following custom resources have been specifically added for your use. + PRIORITIZE using these resources as they are directly relevant to your task. + Always consider these FIRST and in the meantime using default resources. + +""" + + if know_how_formatted: + prompt_modifier += """ +📚 KNOW-HOW DOCUMENTS (BEST PRACTICES & PROTOCOLS - ALREADY LOADED): +{know_how_docs} + +IMPORTANT: These documents are ALREADY AVAILABLE in your context. You do NOT need to +retrieve them or "review" them as a separate step. You can DIRECTLY reference and use +the information from these documents to answer questions, provide protocols, suggest +parameters, and offer troubleshooting guidance. + +These documents contain expert knowledge, protocols, and troubleshooting guidance. +Reference them directly for experimental design, methodology, and problem-solving. + +""" + + if custom_tools_formatted: + prompt_modifier += """ +🔧 CUSTOM TOOLS (USE THESE FIRST): +{custom_tools} + +""" + + if custom_data_formatted: + prompt_modifier += """ +📊 CUSTOM DATA (PRIORITIZE THESE DATASETS): +{custom_data} + +""" + + if custom_software_formatted: + prompt_modifier += """ +⚙️ CUSTOM SOFTWARE (USE THESE LIBRARIES): +{custom_software} + +""" + + prompt_modifier += """=============================== +""" + + # Add environment resources + prompt_modifier += """ + +Environment Resources: + +- Function Dictionary: +{function_intro} +--- +{tool_desc} +--- + +{import_instruction} + +- Biological data lake +You can access a biological data lake at the following path: {data_lake_path}. +{data_lake_intro} +Each item is listed with its description to help you understand its contents. +---- +{data_lake_content} +---- + +- Software Library: +{library_intro} +Each library is listed with its description to help you understand its functionality. +---- +{library_content_formatted} +---- + +- Note on using R packages and Bash scripts: + - R packages: Use subprocess.run(['Rscript', '-e', 'your R code here']) in Python, or use the #!R marker in your execute block. + - Bash scripts and commands: Use the #!BASH marker in your execute block for both simple commands and complex shell scripts with variables, loops, conditionals, etc. + """ + + # Set appropriate text based on whether this is initial configuration or after retrieval + if is_retrieval: + function_intro = "Based on your query, I've identified the following most relevant functions that you can use in your code:" + data_lake_intro = "Based on your query, I've identified the following most relevant datasets:" + library_intro = ( + "Based on your query, I've identified the following most relevant libraries that you can use:" + ) + import_instruction = "IMPORTANT: When using any function, you MUST first import it from its module. For example:\nfrom [module_name] import [function_name]" + else: + function_intro = "In your code, you will need to import the function location using the following dictionary of functions:" + data_lake_intro = "You can write code to understand the data, process and utilize it for the task. Here is the list of datasets:" + library_intro = "The environment supports a list of libraries that can be directly used. Do not forget the import statement:" + import_instruction = "" + + # Format the content consistently for both initial and retrieval cases + library_content_formatted = "\n".join(libraries_formatted) + data_lake_content_formatted = "\n".join(data_lake_formatted) + + # Format the prompt with the appropriate values + format_dict = { + "function_intro": function_intro, + "tool_desc": textify_api_dict(tool_desc) if isinstance(tool_desc, dict) else tool_desc, + "import_instruction": import_instruction, + "data_lake_path": self.path + "/data_lake", + "data_lake_intro": data_lake_intro, + "data_lake_content": data_lake_content_formatted, + "library_intro": library_intro, + "library_content_formatted": library_content_formatted, + } + + # Add custom resources to format dict if they exist + if know_how_formatted: + format_dict["know_how_docs"] = "\n\n".join(know_how_formatted) + if custom_tools_formatted: + format_dict["custom_tools"] = "\n".join(custom_tools_formatted) + if custom_data_formatted: + format_dict["custom_data"] = "\n".join(custom_data_formatted) + if custom_software_formatted: + format_dict["custom_software"] = "\n".join(custom_software_formatted) + + formatted_prompt = prompt_modifier.format(**format_dict) + + return formatted_prompt + + def configure(self, self_critic=False, test_time_scale_round=0): + """Configure the agent with the initial system prompt and workflow. + + Args: + self_critic: Whether to enable self-critic mode + test_time_scale_round: Number of rounds for test time scaling + + """ + # Store self_critic for later use + self.self_critic = self_critic + + # Get data lake content + data_lake_path = self.path + "/data_lake" + data_lake_content = glob.glob(data_lake_path + "/*") + data_lake_items = [x.split("/")[-1] for x in data_lake_content] + + # data_lake_dict and library_content_dict are already set in __init__ + + # Prepare tool descriptions + tool_desc = {i: [x for x in j if x["name"] != "run_python_repl"] for i, j in self.module2api.items()} + + # Prepare data lake items with descriptions + data_lake_with_desc = [] + for item in data_lake_items: + description = self.data_lake_dict.get(item, f"Data lake item: {item}") + data_lake_with_desc.append({"name": item, "description": description}) + + # Add custom data items if they exist + if hasattr(self, "_custom_data") and self._custom_data: + for name, info in self._custom_data.items(): + data_lake_with_desc.append({"name": name, "description": info["description"]}) + + # Prepare library content list including custom software + library_content_list = list(self.library_content_dict.keys()) + if hasattr(self, "_custom_software") and self._custom_software: + for name in self._custom_software: + if name not in library_content_list: # Avoid duplicates + library_content_list.append(name) + + # Generate the system prompt for initial configuration (is_retrieval=False) + # Prepare custom resources for highlighting + custom_tools = [] + if hasattr(self, "_custom_tools") and self._custom_tools: + for name, info in self._custom_tools.items(): + custom_tools.append( + { + "name": name, + "description": info["description"], + "module": info["module"], + } + ) + + custom_data = [] + if hasattr(self, "_custom_data") and self._custom_data: + for name, info in self._custom_data.items(): + custom_data.append({"name": name, "description": info["description"]}) + + custom_software = [] + if hasattr(self, "_custom_software") and self._custom_software: + for name, info in self._custom_software.items(): + custom_software.append({"name": name, "description": info["description"]}) + + # Load ALL know-how documents into initial system prompt + # This makes best practices always available, not just when retrieved + know_how_docs = [] + if hasattr(self, "know_how_loader") and self.know_how_loader.documents: + for _doc_id, doc in self.know_how_loader.documents.items(): + # Use content without metadata for efficiency + know_how_docs.append( + { + "id": doc["id"], + "name": doc["name"], + "description": doc["description"], + "content": doc["content_without_metadata"], + "metadata": doc["metadata"], + } + ) + print(f"📚 Loading {len(know_how_docs)} know-how documents into system prompt") + + self.system_prompt = self._generate_system_prompt( + tool_desc=tool_desc, + data_lake_content=data_lake_with_desc, + library_content_list=library_content_list, + self_critic=self_critic, + is_retrieval=False, + custom_tools=custom_tools if custom_tools else None, + custom_data=custom_data if custom_data else None, + custom_software=custom_software if custom_software else None, + know_how_docs=know_how_docs if know_how_docs else None, + ) + + # Define the nodes + def generate(state: AgentState) -> AgentState: + # Add OpenAI-specific formatting reminders if using OpenAI models + system_prompt = self.system_prompt + if hasattr(self.llm, "model_name") and ( + "gpt" in str(self.llm.model_name).lower() or "openai" in str(type(self.llm)).lower() + ): + system_prompt += "\n\nIMPORTANT FOR GPT MODELS: You MUST use XML tags or in EVERY response. Do not use markdown code blocks (```) - use tags instead." + + messages = [SystemMessage(content=system_prompt)] + state["messages"] + # Some Anthropic models reject assistant-prefill and require the + # final turn to be a user message. Keep prior context intact and + # only append a lightweight user continuation cue when needed. + if "anthropic" in str(type(self.llm)).lower(): + if len(messages) == 1 or not isinstance(messages[-1], HumanMessage): + messages.append(HumanMessage(content="Continue based on the latest observation.")) + response = self.llm.invoke(messages) + + # Normalize Responses API content blocks (list of dicts) into a plain string + content = response.content + if isinstance(content, list): + # Concatenate textual parts; ignore tool_use or other non-text blocks + text_parts: list[str] = [] + for block in content: + try: + if isinstance(block, dict): + btype = block.get("type") + if btype in ("text", "output_text", "redacted_text"): + part = block.get("text") or block.get("content") or "" + if isinstance(part, str): + text_parts.append(part) + except Exception: + # Be conservative; skip malformed blocks + continue + msg = "".join(text_parts) + else: + # Fallback to string conversion for legacy content + msg = str(content) + + # Enhanced parsing for better OpenAI compatibility + # Check for incomplete tags and fix them + if "" in msg and "" not in msg: + msg += "" + if "" in msg and "" not in msg: + msg += "" + if "" in msg and "" not in msg: + msg += "" + + # More flexible pattern matching for different LLM styles + think_match = re.search(r"(.*?)", msg, re.DOTALL | re.IGNORECASE) + execute_match = re.search(r"(.*?)", msg, re.DOTALL | re.IGNORECASE) + answer_match = re.search(r"(.*?)", msg, re.DOTALL | re.IGNORECASE) + + # Alternative patterns for OpenAI models that might use different formatting + if not execute_match: + # Try to find code blocks that might be intended as execute blocks + code_block_match = re.search(r"```(?:python|bash|r)?\s*(.*?)```", msg, re.DOTALL) + if code_block_match and not answer_match: + # If we found a code block and no solution, treat it as execute + execute_match = code_block_match + + # Add the message to the state before checking for errors + state["messages"].append(AIMessage(content=msg.strip())) + + if answer_match: + state["next_step"] = "end" + elif execute_match: + state["next_step"] = "execute" + elif think_match: + state["next_step"] = "generate" + else: + print("parsing error...") + + error_count = sum( + 1 for m in state["messages"] if isinstance(m, AIMessage) and "There are no tags" in m.content + ) + + if error_count >= 2: + # If we've already tried to correct the model twice, just end the conversation + print("Detected repeated parsing errors, ending conversation") + state["next_step"] = "end" + # Add a final message explaining the termination + state["messages"].append( + AIMessage( + content="Execution terminated due to repeated parsing errors. Please check your input and try again." + ) + ) + else: + # Try to correct it + state["messages"].append( + HumanMessage( + content="Each response must include thinking process followed by either or tag. But there are no tags in the current response. Please follow the instruction, fix and regenerate the response again." + ) + ) + state["next_step"] = "generate" + return state + + def execute(state: AgentState) -> AgentState: + last_message = state["messages"][-1].content + # Only add the closing tag if it's not already there + if "" in last_message and "" not in last_message: + last_message += "" + + execute_match = re.search(r"(.*?)", last_message, re.DOTALL) + if execute_match: + code = execute_match.group(1) + + # Set timeout duration (10 minutes = 600 seconds) + timeout = self.timeout_seconds + + # Check if the code is R code + if ( + code.strip().startswith("#!R") + or code.strip().startswith("# R code") + or code.strip().startswith("# R script") + ): + # Remove the R marker and run as R code + r_code = re.sub(r"^#!R|^# R code|^# R script", "", code, count=1).strip() + result = run_with_timeout(run_r_code, [r_code], timeout=timeout) + # Check if the code is a Bash script or CLI command + elif ( + code.strip().startswith("#!BASH") + or code.strip().startswith("# Bash script") + or code.strip().startswith("#!CLI") + ): + # Handle both Bash scripts and CLI commands with the same function + if code.strip().startswith("#!CLI"): + # For CLI commands, extract the command and run it as a simple bash script + cli_command = re.sub(r"^#!CLI", "", code, count=1).strip() + # Remove any newlines to ensure it's a single command + cli_command = cli_command.replace("\n", " ") + result = run_with_timeout(run_bash_script, [cli_command], timeout=timeout) + else: + # For Bash scripts, remove the marker and run as a bash script + bash_script = re.sub(r"^#!BASH|^# Bash script", "", code, count=1).strip() + result = run_with_timeout(run_bash_script, [bash_script], timeout=timeout) + # Otherwise, run as Python code + else: + # Clear any previous plots before execution + self._clear_execution_plots() + + # Inject custom functions into the Python execution environment + self._inject_custom_functions_to_repl() + result = run_with_timeout(run_python_repl, [code], timeout=timeout) + + # Plots are now captured directly in the execution entry above + + if len(result) > 10000: + result = ( + "The output is too long to be added to context. Here are the first 10K characters...\n" + + result[:10000] + ) + + # Store the execution result with the triggering message + if not hasattr(self, "_execution_results"): + self._execution_results = [] + + # Get any plots that were generated during this execution + execution_plots = [] + try: + from biomni.tool.support_tools import get_captured_plots + + current_plots = get_captured_plots() + execution_plots = current_plots.copy() + except Exception as e: + print(f"Warning: Could not capture plots from execution: {e}") + execution_plots = [] + + # Store the execution result with metadata + execution_entry = { + "triggering_message": last_message, # The AI message that contained + "images": execution_plots, # Base64 encoded images from this execution + "timestamp": datetime.now().isoformat(), + } + self._execution_results.append(execution_entry) + + observation = f"\n{result}" + state["messages"].append(AIMessage(content=observation.strip())) + + return state + + def routing_function( + state: AgentState, + ) -> Literal["execute", "generate", "end"]: + next_step = state.get("next_step") + if next_step == "execute": + return "execute" + elif next_step == "generate": + return "generate" + elif next_step == "end": + return "end" + else: + raise ValueError(f"Unexpected next_step: {next_step}") + + def routing_function_self_critic( + state: AgentState, + ) -> Literal["generate", "end"]: + next_step = state.get("next_step") + if next_step == "generate": + return "generate" + elif next_step == "end": + return "end" + else: + raise ValueError(f"Unexpected next_step: {next_step}") + + def execute_self_critic(state: AgentState) -> AgentState: + if self.critic_count < test_time_scale_round: + # Generate feedback based on message history + messages = state["messages"] + feedback_prompt = f""" + Here is a reminder of what is the user requested: {self.user_task} + Examine the previous executions, reaosning, and solutions. + Critic harshly on what could be improved? + Be specific and constructive. + Think hard what are missing to solve the task. + No question asked, just feedbacks. + """ + feedback = self.llm.invoke(messages + [HumanMessage(content=feedback_prompt)]) + + # Add feedback as a new message + state["messages"].append( + HumanMessage( + content=f"Wait... this is not enough to solve the task. Here are some feedbacks for improvement:\n{feedback.content}" + ) + ) + self.critic_count += 1 + state["next_step"] = "generate" + else: + state["next_step"] = "end" + + return state + + # Create the workflow + workflow = StateGraph(AgentState) + + # Add nodes + workflow.add_node("generate", generate) + workflow.add_node("execute", execute) + + if self_critic: + workflow.add_node("self_critic", execute_self_critic) + # Add conditional edges + workflow.add_conditional_edges( + "generate", + routing_function, + path_map={ + "execute": "execute", + "generate": "generate", + "end": "self_critic", + }, + ) + workflow.add_conditional_edges( + "self_critic", + routing_function_self_critic, + path_map={"generate": "generate", "end": END}, + ) + else: + # Add conditional edges + workflow.add_conditional_edges( + "generate", + routing_function, + path_map={"execute": "execute", "generate": "generate", "end": END}, + ) + workflow.add_edge("execute", "generate") + workflow.add_edge(START, "generate") + + # Compile the workflow + self.app = workflow.compile() + self.checkpointer = MemorySaver() + self.app.checkpointer = self.checkpointer + # display(Image(self.app.get_graph().draw_mermaid_png())) + + def _prepare_resources_for_retrieval(self, prompt): + """Prepare resources for retrieval and return selected resource names. + + Args: + prompt: The user's query + + Returns: + dict: Dictionary containing selected resource names for tools, data_lake, and libraries + """ + if not self.use_tool_retriever: + return None + + # Gather all available resources + # 1. Tools from the registry + all_tools = self.tool_registry.tools if hasattr(self, "tool_registry") else [] + + # 2. Data lake items with descriptions + data_lake_path = self.path + "/data_lake" + data_lake_content = glob.glob(data_lake_path + "/*") + data_lake_items = [x.split("/")[-1] for x in data_lake_content] + + # Create data lake descriptions for retrieval + data_lake_descriptions = [] + for item in data_lake_items: + description = self.data_lake_dict.get(item, f"Data lake item: {item}") + data_lake_descriptions.append({"name": item, "description": description}) + + # Add custom data items to retrieval if they exist + if hasattr(self, "_custom_data") and self._custom_data: + for name, info in self._custom_data.items(): + data_lake_descriptions.append({"name": name, "description": info["description"]}) + + # 3. Libraries with descriptions - use library_content_dict directly + library_descriptions = [] + for lib_name, lib_desc in self.library_content_dict.items(): + library_descriptions.append({"name": lib_name, "description": lib_desc}) + + # Add custom software items to retrieval if they exist + if hasattr(self, "_custom_software") and self._custom_software: + for name, info in self._custom_software.items(): + # Check if it's not already in the library descriptions to avoid duplicates + if not any(lib["name"] == name for lib in library_descriptions): + library_descriptions.append({"name": name, "description": info["description"]}) + + # 4. Know-how documents + know_how_summaries = self.know_how_loader.get_document_summaries() + + # Use retrieval to get relevant resources + resources = { + "tools": all_tools, + "data_lake": data_lake_descriptions, + "libraries": library_descriptions, + "know_how": know_how_summaries, + } + + # Use prompt-based retrieval with the agent's LLM + selected_resources = self.retriever.prompt_based_retrieval(prompt, resources, llm=self.llm) + print("\n" + "=" * 60) + print("🔍 RESOURCE RETRIEVAL") + print("=" * 60) + print("Using prompt-based retrieval with the agent's LLM") + + # Extract the names from the selected resources for the system prompt + selected_resources_names = { + "tools": selected_resources["tools"], + "data_lake": [], + "libraries": [lib["name"] if isinstance(lib, dict) else lib for lib in selected_resources["libraries"]], + "know_how": [], + } + + # Process data lake items to extract just the names + for item in selected_resources["data_lake"]: + if isinstance(item, dict): + selected_resources_names["data_lake"].append(item["name"]) + elif isinstance(item, str) and ": " in item: + # If the item already has a description, extract just the name + name = item.split(": ")[0] + selected_resources_names["data_lake"].append(name) + else: + selected_resources_names["data_lake"].append(item) + + # Process know-how documents - get the full content for selected documents + if "know_how" in selected_resources and selected_resources["know_how"]: + print("\n📚 Know-How Documents Retrieved:") + for item in selected_resources["know_how"]: + if isinstance(item, dict): + doc_id = item["id"] + doc = self.know_how_loader.get_document_by_id(doc_id) + if doc: + print(f" ✓ {doc['name']}") + # Create a copy with content_without_metadata for agent context + doc_for_agent = { + "id": doc["id"], + "name": doc["name"], + "description": doc["description"], + "content": doc["content_without_metadata"], # Use stripped version for agent + "metadata": doc["metadata"], + } + selected_resources_names["know_how"].append(doc_for_agent) + else: + print("\n📚 Know-How: None retrieved for this query") + + # Print summary of what was retrieved + print("\n" + "-" * 60) + print("📊 RETRIEVAL SUMMARY:") + print(f" 🔧 Tools: {len(selected_resources_names['tools'])} selected") + print(f" 📊 Data Lake: {len(selected_resources_names['data_lake'])} selected") + print(f" ⚙️ Libraries: {len(selected_resources_names['libraries'])} selected") + print(f" 📚 Know-How: {len(selected_resources_names['know_how'])} selected") + print("=" * 60 + "\n") + + return selected_resources_names + + def go(self, prompt): + """Execute the agent with the given prompt. + + Args: + prompt: The user's query + + """ + self.critic_count = 0 + self.user_task = prompt + + if self.use_tool_retriever: + selected_resources_names = self._prepare_resources_for_retrieval(prompt) + self.update_system_prompt_with_selected_resources(selected_resources_names) + + inputs = {"messages": [HumanMessage(content=prompt)], "next_step": None} + config = {"recursion_limit": 500, "configurable": {"thread_id": 42}} + self.log = [] + + # Store the final conversation state for markdown generation + final_state = None + + for s in self.app.stream(inputs, stream_mode="values", config=config): + message = s["messages"][-1] + out = pretty_print(message) + self.log.append(out) + final_state = s # Store the latest state + + # Store the conversation state for markdown generation + self._conversation_state = final_state + + return self.log, message.content + + def go_stream(self, prompt) -> Generator[dict, None, None]: + """Execute the agent with the given prompt and return a generator that yields each step. + + This function returns a generator that yields each step of the agent's execution, + allowing for real-time monitoring of the agent's progress. + + Args: + prompt: The user's query + + Yields: + dict: Each step of the agent's execution containing the current message and state + """ + self.critic_count = 0 + self.user_task = prompt + + if self.use_tool_retriever: + selected_resources_names = self._prepare_resources_for_retrieval(prompt) + self.update_system_prompt_with_selected_resources(selected_resources_names) + + inputs = {"messages": [HumanMessage(content=prompt)], "next_step": None} + config = {"recursion_limit": 500, "configurable": {"thread_id": 42}} + self.log = [] + + # Store the final conversation state for markdown generation + final_state = None + + for s in self.app.stream(inputs, stream_mode="values", config=config): + message = s["messages"][-1] + out = pretty_print(message) + self.log.append(out) + final_state = s # Store the latest state + + # Yield the current step + yield {"output": out} + + # Store the conversation state for markdown generation + self._conversation_state = final_state + + def update_system_prompt_with_selected_resources(self, selected_resources): + """Update the system prompt with the selected resources.""" + # Extract tool descriptions for the selected tools + tool_desc = {} + for tool in selected_resources["tools"]: + # Get the module name from the tool + if isinstance(tool, dict): + module_name = tool.get("module", None) + + # If module is not specified, try to find it in the module2api + if not module_name and hasattr(self, "module2api"): + for mod, apis in self.module2api.items(): + for api in apis: + if api.get("name") == tool.get("name"): + module_name = mod + # Update the tool with the module information + tool["module"] = module_name + break + if module_name: + break + + # If still not found, use a default + if not module_name: + module_name = "biomni.tool.scRNA_tools" # Default to scRNA_tools as a fallback + tool["module"] = module_name + else: + module_name = getattr(tool, "module_name", None) + + # If module is not specified, try to find it in the module2api + if not module_name and hasattr(self, "module2api"): + tool_name = getattr(tool, "name", str(tool)) + for mod, apis in self.module2api.items(): + for api in apis: + if api.get("name") == tool_name: + module_name = mod + # Set the module_name attribute + tool.module_name = module_name + break + if module_name: + break + + # If still not found, use a default + if not module_name: + module_name = "biomni.tool.scRNA_tools" # Default to scRNA_tools as a fallback + tool.module_name = module_name + + if module_name not in tool_desc: + tool_desc[module_name] = [] + + # Add the tool to the appropriate module + if isinstance(tool, dict): + # Ensure the module is included in the tool description + if "module" not in tool: + tool["module"] = module_name + tool_desc[module_name].append(tool) + else: + # Convert tool object to dictionary + tool_dict = { + "name": getattr(tool, "name", str(tool)), + "description": getattr(tool, "description", ""), + "parameters": getattr(tool, "parameters", {}), + "module": module_name, # Explicitly include the module + } + tool_desc[module_name].append(tool_dict) + + # Prepare data lake items with descriptions + data_lake_with_desc = [] + for item in selected_resources["data_lake"]: + description = self.data_lake_dict.get(item, f"Data lake item: {item}") + data_lake_with_desc.append({"name": item, "description": description}) + + # Prepare custom resources for highlighting + custom_tools = [] + if hasattr(self, "_custom_tools") and self._custom_tools: + for name, info in self._custom_tools.items(): + custom_tools.append( + { + "name": name, + "description": info["description"], + "module": info["module"], + } + ) + + custom_data = [] + if hasattr(self, "_custom_data") and self._custom_data: + for name, info in self._custom_data.items(): + custom_data.append({"name": name, "description": info["description"]}) + + custom_software = [] + if hasattr(self, "_custom_software") and self._custom_software: + for name, info in self._custom_software.items(): + custom_software.append({"name": name, "description": info["description"]}) + + # Extract know-how documents if present + know_how_docs = selected_resources.get("know_how", []) + + self.system_prompt = self._generate_system_prompt( + tool_desc=tool_desc, + data_lake_content=data_lake_with_desc, + library_content_list=selected_resources["libraries"], + self_critic=getattr(self, "self_critic", False), + is_retrieval=True, + custom_tools=custom_tools if custom_tools else None, + custom_data=custom_data if custom_data else None, + custom_software=custom_software if custom_software else None, + know_how_docs=know_how_docs if know_how_docs else None, + ) + + # Print the raw system prompt for debugging + # print("\n" + "="*20 + " RAW SYSTEM PROMPT FROM AGENT " + "="*20) + # print(self.system_prompt) + # print("="*70 + "\n") + + def result_formatting(self, output_class, task_intention): + self.format_check_prompt = ChatPromptTemplate.from_messages( + [ + ( + "system", + ( + "You are evaluateGPT, tasked with extract and parse the task output based on the history of an agent. " + "Review the entire history of messages provided. " + "Here is the task output requirement: \n" + f"'{task_intention.replace('{', '{{').replace('}', '}}')}'.\n" + ), + ), + ("placeholder", "{messages}"), + ] + ) + + checker_llm = self.format_check_prompt | self.llm.with_structured_output(output_class) + result = checker_llm.invoke({"messages": [("user", str(self.log))]}).dict() + return result + + def _parse_tool_calls_from_code(self, code: str) -> list[str]: + """Parse code to detect imported tools by looking for import statements. + + Args: + code: The Python code to parse + + Returns: + List of detected tool names + """ + module2api = getattr(self, "module2api", {}) + custom_functions = getattr(self, "_custom_functions", {}) + return parse_tool_calls_from_code(code, module2api, custom_functions) + + def _parse_tool_calls_with_modules(self, code: str) -> list[tuple[str, str]]: + """Parse code to detect imported tools and their modules. + + Args: + code: The Python code to parse + + Returns: + List of tuples (tool_name, module_name) + """ + module2api = getattr(self, "module2api", {}) + custom_functions = getattr(self, "_custom_functions", {}) + return parse_tool_calls_with_modules(code, module2api, custom_functions) + + def _inject_custom_functions_to_repl(self): + """Inject custom functions into the Python REPL execution environment. + This makes custom tools available during code execution. + """ + custom_functions = getattr(self, "_custom_functions", {}) + inject_custom_functions_to_repl(custom_functions) + + def create_mcp_server(self, tool_modules=None): + """ + Create an MCP server object that exposes internal Biomni tools. + This gives you control over when and how to run the server. + + Args: + tool_modules: List of module names to expose (default: all in self.module2api) + + Returns: + FastMCP server object that you can run manually + """ + import importlib + + from mcp.server.fastmcp import FastMCP + + mcp = FastMCP("BiomniTools") + modules = tool_modules or list(self.module2api.keys()) + + registered_tools = 0 + + for module_name in modules: + try: + # Import the actual module + module = importlib.import_module(module_name) + # Get tools for this module + module_tools = self.module2api.get(module_name, []) + + for tool_schema in module_tools: + tool_name = tool_schema.get("name") + if not tool_name: + continue + + try: + # Get the actual function + fn = getattr(module, tool_name, None) + if fn is None: + fn = getattr(self, "_custom_functions", {}).get(tool_name) + + if fn is None: + print(f"Warning: Could not find function '{tool_name}' in module '{module_name}'") + continue + + # Extract parameters from your specific schema format + required_params = tool_schema.get("required_parameters", []) + optional_params = tool_schema.get("optional_parameters", []) + + # Generate the wrapper function + wrapper_func = self._generate_mcp_wrapper_from_biomni_schema( + fn, tool_name, required_params, optional_params + ) + + # Register with MCP + mcp.tool()(wrapper_func) + registered_tools += 1 + + except Exception as e: + print(f"Warning: Failed to register tool '{tool_name}': {e}") + continue + + except ImportError as e: + print(f"Warning: Could not import module '{module_name}': {e}") + continue + + print(f"Created MCP server with {registered_tools} tools") + return mcp + + def save_conversation_history(self, filepath: str, include_images: bool = True, save_pdf: bool = True) -> None: + """Save the complete conversation history as PDF only. + + This function generates and saves the complete conversation history from the agent's + log and conversation state. It creates a temporary markdown file with formatted content + including steps, code execution, observations, and optionally images, then converts it + to PDF format. The markdown file is automatically cleaned up after PDF conversion. + + Args: + filepath: Path where to save the PDF file (without extension). If the path doesn't + end with .pdf, it will be automatically appended. + include_images: Whether to include captured plots and images in the output. + Defaults to True. + save_pdf: Whether to save as PDF. Defaults to True. If False, no file is saved. + + Note: + The function includes a 60-second timeout for PDF generation to prevent + hanging. A temporary markdown file is created and automatically deleted. + """ + import os + import tempfile + + if not save_pdf: + print("PDF saving is disabled. No file will be saved.") + return + + # Ensure directory exists + directory = os.path.dirname(filepath) + if directory: # Only create directory if it's not empty + os.makedirs(directory, exist_ok=True) + + # Create PDF file path - use the user's filename and add .pdf extension + if filepath.endswith(".pdf"): + pdf_path = filepath + else: + # Remove any existing .md extension if present, then add .pdf + base_name = filepath + if base_name.endswith(".md"): + base_name = base_name[:-3] # Remove .md extension + pdf_path = f"{base_name}.pdf" + + # Create markdown content + markdown_content = self._generate_markdown_content(include_images) + + # Create a temporary markdown file + with tempfile.NamedTemporaryFile(mode="w", suffix=".md", delete=False, encoding="utf-8") as temp_file: + temp_file.write(markdown_content) + temp_markdown_path = temp_file.name + + try: + # Add timeout for PDF generation to prevent hanging + import signal + + def timeout_handler(signum, frame): + raise TimeoutError("PDF generation timed out") + + # Set timeout to 60 seconds + signal.signal(signal.SIGALRM, timeout_handler) + signal.alarm(60) + + try: + self._convert_markdown_to_pdf(temp_markdown_path, pdf_path) + print(f"Conversation history saved as PDF: {pdf_path}") + print(f"Total steps recorded: {len(self.log)}") + finally: + signal.alarm(0) # Cancel the alarm + + except TimeoutError: + print("Warning: PDF generation timed out after 60 seconds") + except Exception as e: + print(f"Warning: Could not convert to PDF: {e}") + finally: + # Clean up the temporary markdown file + try: + os.unlink(temp_markdown_path) + except OSError: + pass # File might already be deleted + + def _generate_markdown_content(self, include_images: bool = True) -> str: + """Generate markdown content from conversation history using both log and conversation state. + + This function processes the agent's conversation history from either the conversation + state (if available) or the internal log to create a formatted markdown document. + It handles step numbering, message processing, and content formatting. + + Args: + include_images: Whether to include captured plots and images in the output. + Defaults to True. + + Returns: + Formatted markdown string containing the complete conversation history + with proper step numbering and content structure. + """ + + # Initialize content and tracking variables + content = """# Biomni Agent Conversation History + +""" + added_plots = set() + step_number = 0 + first_human_shown = False + + # Get data source (conversation state or log) + messages = self._get_messages_for_processing() + + # Process all messages using unified logic + for message_data in messages: + content, step_number, first_human_shown = self._process_message( + message_data, content, step_number, first_human_shown, added_plots, include_images + ) + + return content + + def _get_messages_for_processing(self): + """Get messages from conversation state or fallback to log. + + This function determines the best source for conversation messages, prioritizing + the conversation state if available, otherwise falling back to the internal log. + It normalizes the messages into a unified format for processing. + + Returns: + List of normalized message dictionaries with 'content', 'type', and 'original' keys + """ + conversation_state = getattr(self, "_conversation_state", None) + + if conversation_state and hasattr(conversation_state, "get") and "messages" in conversation_state: + print(f"DEBUG: Using conversation state with {len(conversation_state['messages'])} messages") + return self._normalize_conversation_state_messages(conversation_state["messages"]) + else: + print(f"DEBUG: Using self.log with {len(self.log)} entries") + return self._normalize_log_messages(self.log) + + def _normalize_conversation_state_messages(self, messages): + """Convert conversation state messages to unified format. + + This function takes LangChain message objects from the conversation state and + converts them into a standardized dictionary format that the markdown generation + system can work with. It extracts content and determines message types. + + Args: + messages: List of LangChain message objects (HumanMessage, AIMessage, etc.) + + Returns: + List of normalized message dictionaries with 'content', 'type', and 'original' keys + """ + normalized = [] + for message in messages: + if hasattr(message, "content"): + content = str(message.content) + else: + content = str(message) + + # Determine message type + if isinstance(message, HumanMessage): + msg_type = "human" + elif isinstance(message, AIMessage): + msg_type = "ai" + else: + msg_type = "other" + + normalized.append({"content": content, "type": msg_type, "original": message}) + + return normalized + + def _normalize_log_messages(self, log_entries): + """Convert log entries to unified format. + + This function takes internal log entries and converts them into the same + standardized format as conversation state messages. It parses the log format + to determine message types and extract content. + + Args: + log_entries: List of log entry strings from the agent's internal log + + Returns: + List of normalized message dictionaries with 'content', 'type', and 'original' keys + """ + normalized = [] + for log_entry in log_entries: + content = str(log_entry) + + # Determine message type from log format + if "Human Message" in content: + msg_type = "human" + elif "Ai Message" in content: + msg_type = "ai" + else: + msg_type = "other" + + normalized.append({"content": content, "type": msg_type, "original": log_entry}) + + return normalized + + def _process_message(self, message_data, content, step_number, first_human_shown, added_plots, include_images): + """Process a single message and return updated state. + + This function is the main dispatcher for processing individual messages in the + conversation history. It determines the message type and delegates to the + appropriate processing function. + + Args: + message_data: Dictionary containing 'content', 'type', and 'original' keys + content: Current markdown content string + step_number: Current step number counter + first_human_shown: Boolean flag indicating if first human message was shown + added_plots: Set of already added plot data to avoid duplicates + include_images: Whether to include images in the output + + Returns: + Tuple of (updated_content, updated_step_number, updated_first_human_shown) + """ + clean_output = clean_message_content(message_data["content"]) + msg_type = message_data["type"] + + if msg_type == "human": + return self._process_human_message(clean_output, content, step_number, first_human_shown) + elif msg_type == "ai": + return self._process_ai_message(clean_output, content, step_number, added_plots, include_images) + else: + return self._process_other_message( + clean_output, content, step_number, first_human_shown, added_plots, include_images + ) + + def _process_human_message(self, clean_output, content, step_number, first_human_shown): + """Process human messages. + + This function handles human messages in the conversation history. It identifies + parsing error messages and displays them appropriately, or formats the first + human prompt as a special section. + + Args: + clean_output: Cleaned message content with ANSI codes removed + content: Current markdown content string + step_number: Current step number counter (unchanged for human messages) + first_human_shown: Boolean flag indicating if first human message was shown + + Returns: + Tuple of (updated_content, step_number, updated_first_human_shown) + + Note: + Human messages don't increment the step counter as they are not considered + steps in the agent's process. + """ + if "each response must include thinking process" in clean_output.lower(): + parsing_error_content = create_parsing_error_html() + content += f"{parsing_error_content}\n\n" + elif not first_human_shown: + content += "#### Human Prompt\n\n" + content += f"*{clean_output}*\n\n" + first_human_shown = True + + return content, step_number, first_human_shown # step_number unchanged + + def _process_ai_message(self, clean_output, content, step_number, added_plots, include_images): + """Process AI messages. + + This function handles AI messages in the conversation history. It can process + both regular AI responses and messages containing observation tags. It handles + step numbering, execution results, and content formatting. + + Args: + clean_output: Cleaned message content with ANSI codes removed + content: Current markdown content string + step_number: Current step number counter + added_plots: Set of already added plot data to avoid duplicates + include_images: Whether to include images in the output + + Returns: + Tuple of (updated_content, updated_step_number, True) + + Note: + This function can split messages containing observation tags and process + each part separately, with observations formatted as terminal blocks. + """ + # Check if this message contains observation tags and process accordingly + import re + + observation_pattern = r"(.*?)" + observation_matches = re.findall(observation_pattern, clean_output, re.DOTALL | re.IGNORECASE) + + if observation_matches: + # Extract content before, between, and after observation tags + parts = re.split(observation_pattern, clean_output, flags=re.DOTALL | re.IGNORECASE) + + # Process each part + for i, part in enumerate(parts): + if i % 2 == 0: # Even indices are non-observation content + if part.strip(): + # This is regular content - process it normally + if not should_skip_message(part): + if part.strip(): + step_number += 1 + content += f"#### Step {step_number}\n\n" + + # Handle execution results if present + execution_results = getattr(self, "_execution_results", None) + if has_execution_results(part, execution_results): + content, added_plots = self._process_execution_with_results( + part, content, added_plots, include_images, execution_results + ) + else: + content = self._process_regular_ai_message(part, content) + else: # Odd indices are observation content + if part.strip(): + # This is observation content - format as terminal + formatted_observation = format_observation_as_terminal(f"{part}") + if formatted_observation is not None: + content += f"{formatted_observation}\n\n" + + return content, step_number, True + + # Skip empty or error messages + if should_skip_message(clean_output): + return content, step_number, True + + if clean_output.strip(): + step_number += 1 + content += f"#### Step {step_number}\n\n" + + # Handle execution results if present + execution_results = getattr(self, "_execution_results", None) + if has_execution_results(clean_output, execution_results): + content, added_plots = self._process_execution_with_results( + clean_output, content, added_plots, include_images, execution_results + ) + else: + content = self._process_regular_ai_message(clean_output, content) + + return content, step_number, True + + def _process_other_message( + self, clean_output, content, step_number, first_human_shown, added_plots, include_images + ): + """Process other message types. + + This function handles message types that are neither human nor AI messages. + It checks for observation tags and processes them accordingly, or adds the + content as regular text. + + Args: + clean_output: Cleaned message content with ANSI codes removed + content: Current markdown content string + step_number: Current step number counter + first_human_shown: Boolean flag indicating if first human message was shown + added_plots: Set of already added plot data to avoid duplicates + include_images: Whether to include images in the output + + Returns: + Tuple of (updated_content, step_number, first_human_shown) + """ + # Check if this is actually an observation (has tags) + import re + + if not re.search(r"", clean_output, re.IGNORECASE): + content += f"{clean_output}\n\n" + return content, step_number, first_human_shown + + def _process_execution_with_results(self, clean_output, content, added_plots, include_images, execution_results): + """Process AI message with execution results. + + This function handles AI messages that have associated execution results. + It finds the matching execution result and adds any captured plots or images + to the content. + + Args: + clean_output: Cleaned message content with ANSI codes removed + content: Current markdown content string + added_plots: Set of already added plot data to avoid duplicates + include_images: Whether to include images in the output + execution_results: List of execution result dictionaries + + Returns: + Tuple of (updated_content, updated_added_plots) + """ + matching_execution = find_matching_execution(clean_output, execution_results) + + if matching_execution: + content = self._format_and_add_content(clean_output, content) + content, added_plots = self._add_execution_plots(matching_execution, content, added_plots, include_images) + else: + content = self._format_and_add_content(clean_output, content) + + return content, added_plots + + def _format_and_add_content(self, clean_output, content): + """Format and add content to markdown. + + This function applies formatting to AI message content before adding it to the + markdown. It processes lists, execute tags, and tool calls to create properly + formatted markdown content. + + Args: + clean_output: Cleaned message content with ANSI codes removed + content: Current markdown content string + + Returns: + Updated markdown content string with formatted content added + """ + # Process lists first, then execute tags + formatted_content = format_lists_in_text(clean_output) + + # Create a wrapper function for the tool parsing + def parse_tool_calls_wrapper(code): + return self._parse_tool_calls_with_modules(code) + + formatted_content = format_execute_tags_in_content(formatted_content, parse_tool_calls_wrapper) + return content + f"{formatted_content}\n\n" + + def _add_execution_plots(self, matching_execution, content, added_plots, include_images): + """Add plots from execution results. + + This function adds captured plots and images from execution results to the + markdown content. It prevents duplicate plots from being added multiple times. + + Args: + matching_execution: Execution result dictionary containing image data + content: Current markdown content string + added_plots: Set of already added plot data to avoid duplicates + include_images: Whether to include images in the output + + Returns: + Tuple of (updated_content, updated_added_plots) + """ + if include_images and matching_execution.get("images"): + for plot_data in matching_execution["images"]: + if plot_data not in added_plots: + content += f"![Plot]({plot_data})\n\n" + added_plots.add(plot_data) + return content, added_plots + + def _process_regular_ai_message(self, clean_output, content): + """Process regular AI message without execution results. + + This function handles AI messages that don't have associated execution results. + It applies standard formatting and adds the content to the markdown. + + Args: + clean_output: Cleaned message content with ANSI codes removed + content: Current markdown content string + + Returns: + Updated markdown content string with formatted content added + """ + return self._format_and_add_content(clean_output, content) + + def _convert_markdown_to_pdf(self, markdown_path: str, pdf_path: str) -> None: + """Convert markdown file to PDF using weasyprint or markdown2pdf. + + This function is a wrapper around the utility function for converting markdown + to PDF. It provides a clean interface for the agent to convert conversation + history to PDF format. + + Args: + markdown_path: Path to the input markdown file + pdf_path: Path where the output PDF file should be saved + + Note: + This function delegates to the convert_markdown_to_pdf utility function + which handles multiple PDF conversion libraries and fallbacks. + """ + convert_markdown_to_pdf(markdown_path, pdf_path) + + def _clear_execution_plots(self): + """Clear execution plots before new execution. + + This function clears any previously captured plots from the execution environment + before starting a new execution. This prevents old plots from appearing in + new execution results. + + Note: + This function calls the clear_captured_plots utility function and handles + any exceptions gracefully to prevent execution failures. + """ + try: + from biomni.tool.support_tools import clear_captured_plots + + clear_captured_plots() + except Exception as e: + print(f"Warning: Could not clear execution plots: {e}") + + def _generate_mcp_wrapper_from_biomni_schema(self, original_func, func_name, required_params, optional_params): + """Generate wrapper function based on Biomni schema format.""" + import inspect + + # Combine all parameters + all_params = required_params + optional_params + + if not all_params: + # No parameters + def wrapper() -> dict: + try: + result = original_func() + if isinstance(result, dict): + return result + return {"result": result} + except Exception as e: + return {"error": str(e)} + + wrapper.__name__ = func_name + wrapper.__doc__ = original_func.__doc__ + return wrapper + + else: + # Has parameters + def wrapper(**kwargs) -> dict: + try: + # Build arguments dict + filtered_kwargs = {} + + # Add required parameters + for param_info in required_params: + param_name = param_info["name"] + if param_name in kwargs and kwargs[param_name] is not None: + filtered_kwargs[param_name] = kwargs[param_name] + + # Add optional parameters only if provided and not None + for param_info in optional_params: + param_name = param_info["name"] + if param_name in kwargs and kwargs[param_name] is not None: + filtered_kwargs[param_name] = kwargs[param_name] + + result = original_func(**filtered_kwargs) + if isinstance(result, dict): + return result + return {"result": result} + except Exception as e: + return {"error": str(e)} + + # Set function metadata + wrapper.__name__ = func_name + wrapper.__doc__ = original_func.__doc__ + + # Create proper signature + new_params = [] + + # Map your types to Python types + type_map = {"str": str, "int": int, "float": float, "bool": bool, "List[str]": list[str], "dict": dict} + + # Add required parameters + for param_info in required_params: + param_name = param_info["name"] + param_type_str = param_info["type"] + param_type = type_map.get(param_type_str, str) + + new_params.append(inspect.Parameter(param_name, inspect.Parameter.KEYWORD_ONLY, annotation=param_type)) + + # Add optional parameters + for param_info in optional_params: + param_name = param_info["name"] + param_type_str = param_info["type"] + param_type = type_map.get(param_type_str, str) + + # Make it optional + optional_type = param_type | None + + new_params.append( + inspect.Parameter( + param_name, inspect.Parameter.KEYWORD_ONLY, default=None, annotation=optional_type + ) + ) + + # Set the signature + wrapper.__signature__ = inspect.Signature(new_params, return_annotation=dict) + + return wrapper + + def launch_gradio_demo(self, thread_id=42, share=False, server_name="0.0.0.0", require_verification=False): + """Launch a full-featured Gradio UI for the A1 agent (adapted from codeact_copilot). + + Args: + thread_id: Thread ID for the conversation + share: Whether to create a public shareable link + server_name: Server name/IP to bind to (default: "0.0.0.0") + require_verification: If True, requires access code verification + + Example: + >>> agent = A1() + >>> agent.launch_gradio_demo() + """ + try: + import gradio as gr + from gradio import ChatMessage + except ImportError: + raise ImportError("Gradio is not installed. Please install it with: pip install gradio") from None + + import os + from time import time + + # Define supported file extensions + SUPPORTED_EXTENSIONS = (".png", ".jpg", ".jpeg", ".gif", ".bmp", ".webp", ".pdf") + + self.main_history_copy = [] + + # Available access codes (if verification is required) + available_access_codes = ["Biomni2025"] + + # Function for verification page + def verify_access_code(code): + if code in available_access_codes: + return gr.update(visible=False), gr.update(visible=True), gr.update(visible=False) + else: + return ( + gr.update(visible=True), + gr.update(visible=False), + gr.update(value="Incorrect access code. Please check your access code.", visible=True), + ) + + def generate_response(prompt_input, inner_history=None, main_history=None): + if main_history is None: + main_history = [] + if inner_history is None: + inner_history = [] + text_input = prompt_input.get("text", "") + files = prompt_input.get("files", []) + + self.main_history_copy += [{"role": "user", "content": text_input}] + main_history.append(ChatMessage(role="user", content=text_input if text_input else "[Uploaded file]")) + + # Add "Executor is working on it" message + main_history.append(ChatMessage(role="assistant", content="Executor is working on it 👉")) + yield inner_history, main_history + + # Process uploaded files if any + for file_info in files: + file_path = file_info + text_input += f"\n\n User uploaded this file: {file_path}\n Please use it if needed." + + agent_messages = [] + for msg in self.main_history_copy: + if msg["role"] == "user": + agent_messages.append(HumanMessage(content=msg["content"])) + elif msg["role"] == "assistant": + if msg["content"] not in ["Executor is working on it 👉"]: + agent_messages.append(AIMessage(content=msg["content"])) + + agent_messages.append(HumanMessage(content=text_input)) + + # Prepare inputs for the agent + inputs = {"messages": agent_messages, "next_step": None} + config = {"recursion_limit": 500, "configurable": {"thread_id": thread_id}} + + # Stream the agent's responses + t = time() + solution_found = False + + # Configure the agent with tool retrieval if needed + if self.use_tool_retriever: + print("Using tool retriever...") + inner_history.append( + ChatMessage( + role="assistant", + content="Retrieving relevant tools, data lake items, and libraries...", + ) + ) + yield inner_history, main_history + + try: + selected_resources_names = self._prepare_resources_for_retrieval(text_input) + if selected_resources_names: + self.update_system_prompt_with_selected_resources(selected_resources_names) + except Exception as e: + print(f"Warning: Tool retrieval failed: {e}") + print("Continuing without tool retrieval...") + inner_history.append( + ChatMessage( + role="assistant", + content="Tool retrieval unavailable, proceeding with all tools...", + ) + ) + yield inner_history, main_history + + # Keep track of code execution messages + code_execution_messages = [] + + # Stream the agent's responses + for s in self.app.stream(inputs, stream_mode="values", config=config): + t_step = time() - t + message = s["messages"][-1] + + # Skip the first message which is the input task + if message.content == text_input: + t = time() + continue + + # Process the message + if isinstance(message.content, str): + # Extract thinking/reasoning part (text before any tags) + tag_positions = [] + for tag in ["", "", ""]: + pos = message.content.find(tag) + if pos != -1: + tag_positions.append(pos) + + # If there are tags, extract the text before the first tag + if tag_positions: + first_tag_pos = min(tag_positions) + thinking = message.content[:first_tag_pos].strip() + if thinking: + inner_history.append( + ChatMessage( + role="assistant", + content=f"{thinking}", + metadata={"title": "🤔 Reasoning", "log": "Agent's thinking process"}, + ) + ) + yield inner_history, main_history + + # Check for solution tag + solution_match = re.search(r"(.*?)", message.content, re.DOTALL) + if solution_match and not solution_found: + solution = solution_match.group(1).strip() + main_history.append( + ChatMessage( + role="assistant", + content=solution, + metadata={"title": "✅ Answer", "log": "Final answer provided by the agent"}, + ) + ) + self.main_history_copy += [{"role": "assistant", "content": solution}] + solution_found = True + yield inner_history, main_history + + # Check for execute tag + execute_match = re.search(r"(.*?)", message.content, re.DOTALL) + if execute_match: + code = execute_match.group(1).strip() + language = "python" + if code.strip().startswith("#!R"): + language = "r" + code = re.sub(r"^#!R", "", code, count=1).strip() + elif code.strip().startswith("#!BASH") or code.strip().startswith("#!CLI"): + language = "bash" + code = re.sub(r"^#!BASH|^#!CLI", "", code, count=1).strip() + + code_msg = ChatMessage( + role="assistant", + content=f"##### Code: \n```{language}\n{code}\n```", + metadata={ + "title": "🛠️ Executing code...", + "log": f"Executing {language.capitalize()} code block...", + "status": "pending", + "start_time": t, + }, + ) + inner_history.append(code_msg) + code_execution_messages.append(code_msg) + yield inner_history, main_history + + # Check for observation + observation_match = re.search(r"(.*?)", message.content, re.DOTALL) + if observation_match: + observation = observation_match.group(1).strip() + + # Update the status of the most recent code execution message + if code_execution_messages: + code_msg = code_execution_messages[-1] + code_msg.metadata.update( + { + "status": "done", + "duration": t_step, + "log": f"Code execution completed in {t_step:.2f}s", + } + ) + + # Create a new message for the observation + inner_history.append( + ChatMessage( + role="assistant", + content=f"##### Observation: \n```\n{observation}\n```", + metadata={ + "status": "done", + "duration": t_step, + "log": "Observation from code execution", + "collapsed": True, + "collapsible": True, + }, + ) + ) + yield inner_history, main_history + + # Check for file paths in the observation + if isinstance(observation, str) and any(ext in observation for ext in SUPPORTED_EXTENSIONS): + matches = re.findall(r"(\S+?(?:\.png|\.jpg|\.jpeg|\.gif|\.bmp|\.webp|\.pdf))", observation) + + valid_matches = [] + for match in matches: + if not ( + match.startswith("Warning:") or match.startswith("Error:") or match.startswith("'") + ): + if not match.startswith("."): + valid_matches.append(match) + + if valid_matches: + inner_history.append( + ChatMessage( + role="assistant", + content="", + metadata={"title": "📁 Files", "log": "Files generated by the agent"}, + ) + ) + + for file_path in valid_matches: + file_path = file_path.strip("\"'").strip() + + abs_path = None + if os.path.isabs(file_path) and os.path.exists(file_path): + abs_path = file_path + elif os.path.exists(os.path.join(os.getcwd(), file_path)): + abs_path = os.path.join(os.getcwd(), file_path) + elif ( + hasattr(self, "path") + and self.path + and os.path.exists(os.path.join(self.path, file_path)) + ): + abs_path = os.path.join(self.path, file_path) + + if abs_path: + if file_path.lower().endswith(".pdf"): + inner_history.append( + ChatMessage( + role="assistant", + content=f"Found PDF at: {abs_path}", + metadata={"title": "📄 PDF File"}, + ) + ) + else: + inner_history.append( + ChatMessage( + role="assistant", + content=gr.Image(abs_path), + metadata={"title": "🖼️ Image Preview"}, + ) + ) + + yield inner_history, main_history + + t = time() + + # If no solution was found, add the final message + if not solution_found: + final_message = s["messages"][-1].content if s["messages"] else "" + solution_match = re.search(r"(.*?)", final_message, re.DOTALL) + if solution_match: + solution = solution_match.group(1).strip() + main_history.append( + ChatMessage(role="assistant", content=solution, metadata={"title": "✅ Solution"}) + ) + self.main_history_copy += [{"role": "assistant", "content": solution}] + else: + cleaned_content = re.sub(r".*?", "", final_message, flags=re.DOTALL) + cleaned_content = re.sub(r".*?", "", cleaned_content, flags=re.DOTALL) + cleaned_content = re.sub(r"\n\s*\n", "\n\n", cleaned_content) + + if cleaned_content.strip(): + main_history.append( + ChatMessage( + role="assistant", content=cleaned_content.strip(), metadata={"title": "📝 Summary"} + ) + ) + self.main_history_copy += [{"role": "assistant", "content": cleaned_content.strip()}] + else: + main_history.append( + ChatMessage( + role="assistant", + content="Task completed. Please check the execution log for details.", + metadata={"title": "📝 Summary"}, + ) + ) + self.main_history_copy += [{"role": "assistant", "content": "Task completed."}] + + # Add completion message + inner_history.append( + ChatMessage( + role="assistant", + content="👈 Returning the result to the main interface...", + metadata={"title": "🔄 Complete"}, + ) + ) + yield inner_history, main_history + + def like(data: gr.LikeData): + print("User liked the response") + print(f"Index: {data.index}, Liked: {data.liked}") + + # Create the Gradio interface + with gr.Blocks() as demo: + # Verification page (if enabled) + verification_container = gr.Group(visible=require_verification) + main_interface_container = gr.Group(visible=not require_verification) + + with verification_container: + gr.Markdown("# Biomni A1 Agent - Access Verification") + gr.Markdown("Please enter your access code to continue.") + access_code_input = gr.Textbox(label="Access Code", type="password") + access_error_msg = gr.Markdown(visible=False) + verify_btn = gr.Button("Verify Access") + verify_btn.click( + fn=verify_access_code, + inputs=[access_code_input], + outputs=[verification_container, main_interface_container, access_error_msg], + ) + + # Main interface + with main_interface_container: + with gr.Row(): + with gr.Column(scale=1): + main_chatbot = gr.Chatbot( + label="Biomni A1 Agent", + type="messages", + height=800, + show_copy_button=True, + show_share_button=True, + ) + with gr.Column(scale=1): + innerloop_chatbot = gr.Chatbot( + label="Biomni Executor", + type="messages", + height=800, + show_copy_button=True, + show_share_button=True, + ) + + with gr.Row(): + prompt_input = gr.MultimodalTextbox( + interactive=True, + file_count="multiple", + placeholder="Ask something or upload a file...", + show_label=False, + ) + + # Bind submission + prompt_input.submit( + generate_response, + [prompt_input, innerloop_chatbot, main_chatbot], + [innerloop_chatbot, main_chatbot], + ).then(lambda: gr.MultimodalTextbox(value=None), None, [prompt_input]) + main_chatbot.like(like) + + # Launch + print(f"Launching Gradio demo on {server_name}:7860") + demo.launch(share=share, server_name=server_name) diff --git a/BioScientist/agent_system/engines/v1_executor_backup/agent/env_collection.py b/BioScientist/agent_system/engines/v1_executor_backup/agent/env_collection.py new file mode 100644 index 0000000000000000000000000000000000000000..5e9a8ab44ac60729d7c5846870db3ea0ea6bd438 --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/agent/env_collection.py @@ -0,0 +1,313 @@ +import json +import os +import re +from typing import Any + +from langchain_core.prompts import ChatPromptTemplate +from langchain_text_splitters import RecursiveCharacterTextSplitter + +from biomni.agent.base_agent import base_agent + + +class PaperTaskExtractor(base_agent): + """Agent that extracts generalizable tasks/experiments from academic papers. + It processes papers in chunks and identifies common research tasks that could be shared across papers. + """ + + def __init__( + self, + llm="claude-3-7-sonnet-20250219", + cheap_llm=None, + tools=None, + chunk_size=4000, + chunk_overlap=400, + ): + """Initialize the PaperTaskExtractor agent. + + Args: + llm (str): The LLM model to use + cheap_llm (str, optional): A cheaper LLM for simpler tasks + tools (list, optional): Any tools to use (not needed for this agent) + chunk_size (int): Size of text chunks for processing + chunk_overlap (int): Overlap between chunks + + """ + super().__init__(llm, cheap_llm, tools) + self.chunk_size = chunk_size + self.chunk_overlap = chunk_overlap + self.log = [] + self.configure() + + def configure(self): + """Configure the agent with appropriate prompts.""" + # Prompt for analyzing paper chunks + self.chunk_analysis_prompt = """You are a research methodology expert specializing in identifying computational tasks and data analysis procedures in academic papers. + +Your job is to analyze chunks of academic papers and identify ONLY the most common, generalizable computational tasks that are widely used across biomedical research and can be implemented with Python or Linux code. + +STRICT GUIDELINES: +1. ONLY extract tasks that are extremely common and standard in computational biomedical research +2. Each task MUST have clear, well-defined inputs and outputs +3. Tasks MUST be generalizable across many different papers and research questions +4. Be VERY selective - only include tasks that appear in hundreds of papers +5. If a task is specific to this paper, unclear, or not widely used, DO NOT include it +6. Focus on computational tasks that can be implemented with Python or Linux code +7. Each task should be something that could be implemented as a function with clear inputs/outputs +8. Also identify commonly used databases and software packages mentioned in the text +9. Tasks MUST be CONCRETE and SPECIFIC - include exact methodological details +10. Avoid vague task names like "Statistical Analysis" - instead use specific protocol names like "Two-way ANOVA with Tukey's Post-hoc Test using SciPy" +11. DO NOT include wet lab procedures that cannot be implemented computationally +12. ONLY include tasks that could be automated with code + +For the following chunk of text from an academic paper, provide: +1. A list of ONLY the most common, generalizable COMPUTATIONAL tasks identified (be extremely selective) +2. For each task, clearly define: + - Task name: A SPECIFIC and CONCRETE name with methodological details (e.g., "RNA-seq Differential Expression Analysis with DESeq2" instead of just "Gene Expression Analysis") + - Input: What SPECIFIC data or parameters the task requires + - Output: What SPECIFIC data or results the task produces + - Code implementation: How this task could be implemented with Python or Linux code, including key libraries/packages + - Frequency: How common this computational task is in biomedical research + - Standard methods: The established computational techniques used to perform this task + - Example: A brief description of how THIS specific paper uses this task (with specific details from the paper) +3. A list of commonly used databases mentioned in the text (if any) +4. A list of commonly used software packages/tools mentioned in the text (if any) + +PAPER CHUNK: +{chunk_text} + +Remember, it's better to return NO tasks than to include tasks that aren't extremely common, generalizable, and implementable with code. Quality over quantity is essential. Tasks MUST be CONCRETE with SPECIFIC methodological details and MUST be implementable with Python or Linux code. +""" + + # Prompt for consolidating tasks + self.consolidation_prompt = """You are a research methodology expert. Your task is to consolidate lists of computational research tasks extracted from different chunks of an academic paper. + +BE EXTREMELY SELECTIVE. Only include tasks that are: +1. Fundamental to computational biomedical research +2. Used in hundreds of papers across different subfields +3. Have clear, well-defined inputs and outputs +4. Represent standard computational approaches +5. Could be implemented as a function with specific inputs and outputs +6. Are CONCRETE and SPECIFIC with exact methodological details +7. Can be implemented with Python or Linux code +8. Are computational in nature, not wet lab procedures + +REMOVE any tasks that: +- Are specific to a particular paper or dataset +- Lack clear inputs or outputs +- Are not widely used across biomedical research +- Are vague or poorly defined +- Represent niche or specialized techniques +- Have generic names without specific methodological details +- Cannot be implemented with code +- Require physical lab equipment or manual intervention + +The output should be a JSON object with three main keys: +1. "tasks": A list of task objects, where each task has: + - "task_name": A SPECIFIC and CONCRETE name with methodological details (e.g., "Single-cell RNA-seq Clustering with Seurat" instead of just "Cell Clustering") + - "description": A clear description of what the computational task does + - "inputs": SPECIFIC data types or parameters the task requires + - "outputs": SPECIFIC data types or results the task produces + - "code_implementation": How this task could be implemented with Python or Linux code, including key libraries/packages and a brief pseudocode example + - "frequency": How common this computational task is in biomedical research + - "standard_methods": The established computational techniques used to perform this task + - "example": A specific example from THIS paper showing how the task was used (with concrete details) + +2. "databases": A list of database objects, where each database has: + - "name": The name of the database + - "description": A brief description of what the database contains + - "url": The URL of the database (if mentioned) + - "usage": How the database is commonly used in computational biomedical research + - "example": How this specific paper uses the database + +3. "software": A list of software package objects, where each package has: + - "name": The name of the software package + - "description": A brief description of what the software does + - "url": The URL or reference to the software (if mentioned) + - "usage": How the software is commonly used in computational biomedical research + - "example": How this specific paper uses the software + +EXTRACTED INFORMATION FROM PAPER CHUNKS: +{task_lists} + +Be ruthless in filtering - it's better to return a few truly common computational tasks than many that aren't universal or implementable with code. +Respond with only a valid JSON object containing the three lists described above. +""" + + def process_paper(self, paper_text: str) -> dict[str, list[dict[str, Any]]]: + """Process a paper and extract generalizable tasks/experiments, databases, and software. + + Args: + paper_text (str): The full text of the paper + + Returns: + Dict[str, List[Dict[str, Any]]]: A dictionary with tasks, databases, and software + + """ + # Split the paper into chunks + text_splitter = RecursiveCharacterTextSplitter( + chunk_size=self.chunk_size, + chunk_overlap=self.chunk_overlap, + length_function=len, + separators=["\n\n", "\n", ". ", " ", ""], + ) + chunks = text_splitter.split_text(paper_text) + + # Process each chunk to extract tasks + chunk_results = [] + for i, chunk in enumerate(chunks): + print(f"Processing chunk {i + 1}/{len(chunks)}...") + chunk_tasks = self._process_chunk(chunk) + chunk_results.append(chunk_tasks) + + # Consolidate tasks from all chunks + consolidated_results = self._consolidate_tasks(chunk_results) + return consolidated_results + + def _process_chunk(self, chunk_text: str) -> str: + """Process a single chunk of the paper to extract tasks. + + Args: + chunk_text (str): The chunk text to process + + Returns: + str: Extracted tasks from this chunk + + """ + prompt = self.chunk_analysis_prompt.format(chunk_text=chunk_text) + message = self.llm.invoke(prompt) + return message.content + + def _consolidate_tasks(self, chunk_results: list[str]) -> dict[str, list[dict[str, Any]]]: + """Consolidate tasks, databases, and software extracted from different chunks into a unified structure. + + Args: + chunk_results (List[str]): Results from each chunk + + Returns: + Dict[str, List[Dict[str, Any]]]: Consolidated information with tasks, databases, and software + + """ + # Combine all chunk results + all_tasks = "\n\n===== CHUNK SEPARATOR =====\n\n".join(chunk_results) + + # Use the consolidation prompt to merge and organize tasks + prompt = self.consolidation_prompt.format(task_lists=all_tasks) + response = self.llm.invoke(prompt) + + # Extract the JSON from the response + try: + # Try to parse the entire response as JSON + result = json.loads(response.content) + except json.JSONDecodeError: + # If that fails, try to extract JSON from the text + try: + # Look for JSON-like content between triple backticks + json_match = re.search(r"```(?:json)?\s*([\s\S]*?)\s*```", response.content) + if json_match: + result = json.loads(json_match.group(1)) + else: + # Fallback: just return the text response + return { + "tasks": [ + { + "error": "Could not parse JSON", + "raw_response": response.content, + } + ], + "databases": [], + "software": [], + } + except Exception: + return { + "tasks": [ + { + "error": "Could not parse JSON", + "raw_response": response.content, + } + ], + "databases": [], + "software": [], + } + + # Ensure the result has the expected structure + if not isinstance(result, dict): + result = { + "tasks": result if isinstance(result, list) else [], + "databases": [], + "software": [], + } + + # Ensure all required keys exist + for key in ["tasks", "databases", "software"]: + if key not in result: + result[key] = [] + + return result + + def go(self, paper_text: str): + """Process a paper and return the extracted tasks, databases, and software. + + Args: + paper_text (str): The full text of the paper + + Returns: + tuple: (log, results) where log is a list of processing steps and results is the final result + + """ + self.log = [] + self.log.append( + ( + "user", + "Extract only the most common and generalizable biomedical research tasks, databases, and software from this paper", + ) + ) + + results = self.process_paper(paper_text) + + result_str = json.dumps(results, indent=2) + self.log.append(("assistant", result_str)) + + return self.log, results + + def save_results(self, results: dict[str, list[dict[str, Any]]], output_path: str): + """Save the extracted tasks, databases, and software to a JSON file. + + Args: + results (Dict[str, List[Dict[str, Any]]]): The extracted tasks, databases, and software + output_path (str): Path to save the results + + """ + os.makedirs(os.path.dirname(output_path), exist_ok=True) + with open(output_path, "w") as f: + json.dump(results, f, indent=2) + print(f"Results saved to {output_path}") + + def result_formatting(self, output_class, task_intention): + """Format the results according to a specific output class. + + Args: + output_class: The class to format the output as + task_intention: Description of the task + + Returns: + The formatted result + + """ + format_check_prompt = ChatPromptTemplate.from_messages( + [ + ( + "system", + ( + "You are evaluateGPT, tasked with extract and parse the task output based on the history of an agent. " + "Review the entire history of messages provided. " + "Here is the task output requirement: \n" + f"'{task_intention.replace('{', '{{').replace('}', '}}')}'.\n" + ), + ), + ("placeholder", "{messages}"), + ] + ) + + checker_llm = format_check_prompt | self.llm.with_structured_output(output_class) + result = checker_llm.invoke({"messages": [("user", str(self.log))]}).dict() + return result diff --git a/BioScientist/agent_system/engines/v1_executor_backup/agent/function_generator.py b/BioScientist/agent_system/engines/v1_executor_backup/agent/function_generator.py new file mode 100644 index 0000000000000000000000000000000000000000..83e82671f14f3329d6d9ba010d139516ebb6507c --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/agent/function_generator.py @@ -0,0 +1,119 @@ +import re + +from biomni.llm import get_llm + + +class base_agent: + def __init__(self, llm="claude-3-haiku-20240307", cheap_llm=None, tools=None, temperature=0.7): + self.tools = tools + self.llm = get_llm(llm, temperature) + if cheap_llm is None: + self.cheap_llm = llm + else: + self.cheap_llm = cheap_llm + + def configure(self): + pass + + def go(self, input): + pass + + +class FunctionGenerator(base_agent): + """Agent that generates executable Python code scripts given a task description.""" + + def __init__(self, llm="claude-3-7-sonnet-20250219", cheap_llm=None, temperature=0.7): + """Initialize the PaperTaskExtractor agent. + + Args: + llm (str): The LLM model to use + cheap_llm (str, optional): A cheaper LLM for simpler tasks + """ + super().__init__(llm, cheap_llm, temperature) + self.log = [] + self.configure() + + def configure(self): + """Configure the agent with appropriate prompts.""" + # Prompt for Python code generation + self.system_prompt = """You are a senior Python engineer. Generate robust, idiomatic Python code that solves the user's task. Requirements: + 1. Output ONLY Python code, ideally inside a single triple-backtick code block. + 2. Include minimal inline comments and a small docstring. + 3. Add a `main()` and an `if __name__ == '__main__':` guard when appropriate. + 4. Avoid external dependencies unless necessary; if used, show `pip` installs in comments. + 5. Do not include prose before or after the code. + 6. When applicable, prioritize the use of codes on public repositories, such as HuggingFace or Github + + Generate Python codes for the following task: + {task} +""" + + def _generate_code(self, task_description: str) -> str: + """Generate codes given a task description. + Args: + task_description (str): task descriptions (possibly generated from previous steps) + + Returns: + str: generated code string + + """ + prompt = self.system_prompt.format(task=task_description) + message = self.llm.invoke(prompt) + return message.content + + def _generate_script_filename(self, task_description: str, max_words: int = 6) -> str: + """ + Generate a safe, meaningful Python script filename from a task description. + Poised for update: may ask the agent to suggest meaningful names. + + Parameters: + ----------- + task_description (str): task descriptions (possibly generated from previous steps) + + max_words : int + Maximum number of words to include in the filename. + + Returns: + -------- + str + A lowercase, hyphen-free, safe filename ending in '.py'. + """ + # Lowercase and remove non-alphanumeric (allow spaces for splitting) + cleaned = re.sub(r"[^a-zA-Z0-9\s]", "", task_description.lower()) + + # Tokenize and select top words + words = cleaned.split() + selected_words = words[:max_words] if words else ["script"] + + # Join with underscores + base_name = "_".join(selected_words) + return f"{base_name}.py" + + def go(self, task_description: str): + """Implement the inherited function to get the tasks done. + + Args: + task_description (str): task descriptions (possibly generated from previous steps) + + Returns: + tuple: (script_filename, results) where script_filename is a generated name for script file and results is the generated codes + + """ + self.log = [] + self.log.append( + ( + "user", + "Generate Python codes given a task description", + ) + ) + + script_filename = self._generate_script_filename(task_description) + results = self._generate_code(task_description) + return script_filename, self._extract_code_block(results) + + def _extract_code_block(self, s: str) -> str: + """ + Extract the first fenced code block (``` or ```python) from s. + """ + m = re.search(r"```(?:python)?\s*(.+?)\s*```", s, flags=re.DOTALL | re.IGNORECASE) + return m.group(1).strip() if m else None diff --git a/BioScientist/agent_system/engines/v1_executor_backup/agent/qa_llm.py b/BioScientist/agent_system/engines/v1_executor_backup/agent/qa_llm.py new file mode 100644 index 0000000000000000000000000000000000000000..332b590e8ea0b4816fd42081a22c615492b9d7ee --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/agent/qa_llm.py @@ -0,0 +1,50 @@ +from langchain_core.prompts import ChatPromptTemplate + +from biomni.llm import get_llm + + +class qa_llm: + def __init__(self, path="./data", llm="claude-3-haiku-20240307", lab_bench_reproduce=False): + self.path = path + self.llm = get_llm(llm) + + if lab_bench_reproduce: + self.prompt_modifier = """ +The following is a multiple choice question about biology. +Please answer by responding with the letter of the correct answer. + +Think step by step. \n + """ + else: + self.prompt_modifier = "" + self.log = [] + + def configure(self): + pass + + def go(self, input): + self.log = [] + self.log.append(("user", input)) + message = self.llm.invoke(self.prompt_modifier + input) + self.log.append(("assistant", message.content)) + return [message.content], message.content + + def result_formatting(self, output_class, task_intention): + self.format_check_prompt = ChatPromptTemplate.from_messages( + [ + ( + "system", + ( + "You are evaluateGPT, tasked with extract and parse the task output based on the history of an agent. " + "Review the entire history of messages provided. " + "Here is the task output requirement: \n" + f"'{task_intention.replace('{', '{{').replace('}', '}}')}'.\n" + ), + ), + ("placeholder", "{messages}"), + ] + ) + + checker_llm = self.format_check_prompt | self.llm.with_structured_output(output_class) + result = checker_llm.invoke({"messages": [("user", str(self.log))]}).dict() + return result diff --git a/BioScientist/agent_system/engines/v1_executor_backup/agent/react.py b/BioScientist/agent_system/engines/v1_executor_backup/agent/react.py new file mode 100644 index 0000000000000000000000000000000000000000..5523374f935070caf2332705fb1d04005454aacf --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/agent/react.py @@ -0,0 +1,465 @@ +import glob +import inspect +import json +import os +import signal +from collections.abc import Sequence +from functools import wraps +from multiprocessing import Process, Queue +from typing import Annotated, TypedDict + +from langchain_core.messages import BaseMessage, SystemMessage, ToolMessage +from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder +from langchain_core.runnables import RunnableConfig +from langgraph.graph import END, StateGraph +from langgraph.graph.message import add_messages + +from biomni.config import default_config +from biomni.env_desc import data_lake_dict, library_content_dict +from biomni.llm import get_llm +from biomni.model.retriever import ToolRetriever +from biomni.tool.tool_registry import ToolRegistry +from biomni.utils import ( + api_schema_to_langchain_tool, + function_to_api_schema, + pretty_print, + read_module2api, +) + + +# Define the AgentState TypedDict for our custom implementation +class AgentState(TypedDict): + """The state of the agent.""" + + # add_messages is a reducer that combines message sequences + messages: Annotated[Sequence[BaseMessage], add_messages] + + +class react: + def __init__( + self, + path: str | None = None, + llm: str | None = None, + use_tool_retriever: bool | None = None, + timeout_seconds: int | None = None, + ): + # Use default_config values for unspecified parameters + if path is None: + path = default_config.path + if llm is None: + llm = default_config.llm + if use_tool_retriever is None: + use_tool_retriever = default_config.use_tool_retriever + if timeout_seconds is None: + timeout_seconds = default_config.timeout_seconds + + self.path = path + if not os.path.exists(path): + os.makedirs(path) + print(f"Created directory: {path}") + ### TODO: Download the data + else: + print(f"Data directory already exists: {path}, loading...") + + module2api = read_module2api() + + self.llm = get_llm(llm, config=default_config) + tools = [] + for module, api_list in module2api.items(): + print("Registering tools from module:", module) + tools += [api_schema_to_langchain_tool(api, mode="custom_tool", module_name=module) for api in api_list] + self.tools = tools + self.module2api = module2api + self.use_tool_retriever = use_tool_retriever + + # Store dictionaries for data lake and library content + self.data_lake_dict = data_lake_dict + self.library_content_dict = library_content_dict + + if self.use_tool_retriever: + self.tool_registry = ToolRegistry(module2api) + self.retriever = ToolRetriever() + + self.timeout_seconds = timeout_seconds # 10 minutes default timeout + + # When wrapping tools with timeout + self.tools = self._add_timeout_to_tools(self.tools) + self.prompt = "" + self.system_prompt = "" + + def _add_timeout_to_tools(self, tools): + """Apply timeout wrapper to all tool functions using multiprocessing.""" + + def create_timed_func(original_func, timeout): + """Factory function that creates a unique timed function for each tool.""" + tool_name = getattr(original_func, "__name__", "unknown") + # print(f"Applying timeout wrapper to tool: {tool_name}") + + def process_func(func, args, kwargs, result_queue): + """Function to run in a separate process.""" + try: + result = func(*args, **kwargs) + result_queue.put(("success", result)) + except Exception as e: + result_queue.put(("error", str(e))) + + @wraps(original_func) + def timed_func(*args, **kwargs): + # print(f"Executing tool with timeout: {tool_name}") + result_queue = Queue() + + # Start a separate process + proc = Process( + target=process_func, + args=(original_func, args, kwargs, result_queue), + ) + proc.start() + + # Wait for the specified timeout + proc.join(timeout) + + # Check if the process is still running after timeout + if proc.is_alive(): + print(f"TIMEOUT: Tool {tool_name} execution timed out after {timeout} seconds") + # Force terminate the process + proc.terminate() + proc.join(1) # Give it a second to terminate + + # If it's still not dead, kill it with more force + if proc.is_alive(): + os.kill(proc.pid, signal.SIGKILL) + + return f"ERROR: Tool execution timed out after {timeout} seconds. Please try with simpler inputs or break your task into smaller steps." + + # Get the result from the queue + if not result_queue.empty(): + status, result = result_queue.get() + if status == "success": + return result + else: + return f"Error in tool execution: {result}" + + return "Error: Tool execution completed but no result was returned" + + return timed_func + + wrapped_tools = [] + for tool in tools: + wrapped_tool = tool + wrapped_tool.func = create_timed_func(tool.func, self.timeout_seconds) + wrapped_tools.append(wrapped_tool) + + return wrapped_tools + + def add_tool(self, api): + function_code = inspect.getsource(api) + schema = function_to_api_schema(function_code, self.llm) + new_tool = api_schema_to_langchain_tool(schema, mode="custom_tool", module_name=api.__module__) + + # Create a single wrapped tool using the existing _add_timeout_to_tools method + wrapped_tools = self._add_timeout_to_tools([new_tool]) + + # Get the wrapped tool and add it to our tools list + if wrapped_tools: + self.tools.append(wrapped_tools[0]) + + def configure( + self, + plan=False, + reflect=False, + data_lake=False, + react_code_search=False, + library_access=False, + ): + data_lake_path = self.path + "/data_lake" + data_lake_content = glob.glob(data_lake_path + "/*") + data_lake_items = [x.split("/")[-1] for x in data_lake_content] + + if react_code_search: + tools = [i for i in self.tools if i.name in ["run_python_repl", "search_google"]] + + prompt_modifier = """ +You are a helpful biomedical assistant assigned with the task of problem-solving. + +You have access to two tools: +1) run_python_repl: to write and run python code +2) search_google: to search google for information + +You can use them to solve the problem. + """ + else: + tools = self.tools + if (not plan) and (not reflect): + prompt_modifier = """You are a helpful biologist and expert geneticist. + """ + elif plan and (not reflect): + prompt_modifier = """You are a helpful biologist and expert geneticist. + Given the question from the user, + - First, come up with a high level plan based on your understanding of the problem and available tools and record it in the Research Plan and Status. You can revise the plan later. + - Research Plan and Status should well organized and succinctly keep track of 1) high level plan (can be revised), 2) what steps have been done and what steps are in progress, 3) short results and conclusions of each step after it has been performed. Do not perform action in research plan. + - Research Plan and Status must only include progress that has been made by previous steps. It should not include results not directly confirmed by the previous observation. + - Follow the plan and try to achieve the goal as straightforwardly as possible. Use tools as necessary. + """ + elif (not plan) and reflect: + prompt_modifier = """You are a helpful biologist and expert geneticist. + In each round after the tool is used, conduct "reflection" step: reflect on the current state of the problem and the results of the last round. What does the observation mean? If there is an error, what caused the error and how to debug? + """ + else: + prompt_modifier = """You are a helpful biologist and expert geneticist. + Given the question from the user, + - First, come up with a high level plan based on your understanding of the problem and available tools and record it in the Research Plan and Status. You can revise the plan later. + - Research Plan and Status should well organized and succinctly keep track of 1) high level plan (can be revised), 2) what steps have been done and what steps are in progress, 3) short results and conclusions of each step after it has been performed. Do not perform action in research plan. + - Research Plan and Status must only include progress that has been made by previous steps. It should not include results not directly confirmed by the previous observation. + - Follow the plan and try to achieve the goal as straightforwardly as possible. Use tools as necessary. + In each round after the tool is used, conduct "reflection" step: reflect on the current state of the problem and the results of the last round. What does the observation mean? If there is an error, what caused the error and how to debug? + You have access to write_python_code and run_python_repl tool to write and run your own code if tools fail, or if the given tools are not enough. Please always make sure to write code when dealing with substantial data, including finding the length of long sequences or elements at different positions. + """ + + if data_lake: + # Format data lake items with descriptions + data_lake_formatted = [] + for item in data_lake_items: + description = data_lake_dict.get(item, f"Data lake item: {item}") + data_lake_formatted.append(f"{item}: {description}") + + prompt_modifier += """ +You can also access a biological data lake at the following path: {data_lake_path}. You can use the run_python_repl tool to write code to understand the data, process and utilize it for the task. +Here is the list of datasets with their descriptions: +---- +{data_lake_formatted} +---- + """.format( + data_lake_path=data_lake_path, + data_lake_formatted="\n".join(data_lake_formatted), + ) + + if library_access: + # Format library content with descriptions + library_formatted = [] + for lib_name, lib_desc in library_content_dict.items(): + library_formatted.append(f"{lib_name}: {lib_desc}") + + prompt_modifier += """ +You also have access to a list of software packages that can be used to perform various tasks. +You can use the run_python_repl tool to write code to access and utilize the library for the task. +Don't forget the import statement. +Here is the list of available libraries with their descriptions: +---- +{library_formatted} +---- + """.format(library_formatted="\n".join(library_formatted)) + + print("=" * 25 + "System Prompt" + "=" * 25) + print(prompt_modifier) + self.system_prompt = prompt_modifier + self.prompt = ChatPromptTemplate.from_messages( + [ + ("system", prompt_modifier), + MessagesPlaceholder(variable_name="messages"), + ] + ) + + # Store the tools for later use + self.active_tools = tools + + # Create a custom implementation of the ReAct agent using LangGraph + self.app = self._create_custom_react_agent(self.llm, tools, self.prompt) + + def _create_custom_react_agent(self, llm, tools, prompt): + """Create a custom ReAct agent using LangGraph.""" + # Create a dictionary mapping tool names to tool objects for easy lookup + tools_by_name = {tool.name: tool for tool in tools} + + # Bind the tools to the language model + llm_with_tools = llm.bind_tools(tools) + + # Define the node that calls the model + def call_model(state: AgentState, config: RunnableConfig = None): + """Node that calls the language model to get the next action.""" + system_message = SystemMessage(content=self.system_prompt) + messages = [system_message] + state["messages"] + response = llm_with_tools.invoke(messages, config=config) + return {"messages": [response]} + + # Define the node that executes tools + def tool_node(state: AgentState): + """Node that executes tools based on the LLM's decisions.""" + outputs = [] + for tool_call in state["messages"][-1].tool_calls: + try: + tool_result = tools_by_name[tool_call["name"]].invoke(tool_call["args"]) + outputs.append( + ToolMessage( + content=json.dumps(tool_result), + name=tool_call["name"], + tool_call_id=tool_call["id"], + ) + ) + except Exception as e: + # Handle any errors that occur during tool execution + outputs.append( + ToolMessage( + content=json.dumps({"error": str(e)}), + name=tool_call["name"], + tool_call_id=tool_call["id"], + ) + ) + return {"messages": outputs} + + # Define the conditional edge that determines whether to continue or not + def should_continue(state: AgentState): + """Determine if we should continue running the graph or finish.""" + messages = state["messages"] + last_message = messages[-1] + # If there is no tool call, then we finish + if not hasattr(last_message, "tool_calls") or not last_message.tool_calls: + return "end" + # Otherwise if there is, we continue + else: + return "continue" + + # Define a new graph + workflow = StateGraph(AgentState) + + # Define the two nodes we will cycle between + workflow.add_node("agent", call_model) + workflow.add_node("tools", tool_node) + + # Set the entrypoint as `agent` + workflow.set_entry_point("agent") + + # Add conditional edges + workflow.add_conditional_edges( + "agent", + should_continue, + { + "continue": "tools", + "end": END, + }, + ) + + # Add edge from tools back to agent + workflow.add_edge("tools", "agent") + + # Compile the graph + return workflow.compile() + + def go(self, prompt): + """Execute the agent with the given prompt. + + Args: + prompt: The user's query + + """ + if self.use_tool_retriever: + # Gather all available tools from the registry + all_tools = self.tool_registry.tools if hasattr(self, "tool_registry") else [] + + # Get data lake items with descriptions + data_lake_path = self.path + "/data_lake" + data_lake_content = glob.glob(data_lake_path + "/*") + data_lake_items = [x.split("/")[-1] for x in data_lake_content] + + # Create data lake descriptions for retrieval + data_lake_descriptions = [] + for item in data_lake_items: + description = self.data_lake_dict.get(item, f"Data lake item: {item}") + data_lake_descriptions.append({"name": item, "description": description}) + + # Libraries with descriptions + library_descriptions = [] + for lib_name, lib_desc in self.library_content_dict.items(): + library_descriptions.append({"name": lib_name, "description": lib_desc}) + + # Prepare resources for retrieval + resources = { + "tools": all_tools, + "data_lake": data_lake_descriptions, + "libraries": library_descriptions, + } + + # Use prompt-based retrieval with the agent's LLM + selected_resources = self.retriever.prompt_based_retrieval(prompt, resources, llm=self.llm) + print("Using prompt-based retrieval with the agent's LLM") + + # If we're using prompt or embedding based retrieval, print the selected resources + print("\nSelected tools:") + for tool in selected_resources["tools"]: + if isinstance(tool, dict): + print(f"- {tool.get('name', 'Unknown')}: {tool.get('description', '')}") + else: + print(f"- {getattr(tool, 'name', str(tool))}: {getattr(tool, 'description', '')}") + + print("\nSelected data lake items:") + for item in selected_resources["data_lake"]: + if isinstance(item, dict): + name = item.get("name", "Unknown") + description = self.data_lake_dict.get(name, f"Data lake item: {name}") + print(f"- {name}: {description}") + elif isinstance(item, str) and ": " in item: + # If the item already has a description, print it as is + print(f"- {item}") + else: + description = self.data_lake_dict.get(item, f"Data lake item: {item}") + print(f"- {item}: {description}") + + print("\nSelected libraries:") + for lib in selected_resources["libraries"]: + if isinstance(lib, dict): + print(f"- {lib.get('name', 'Unknown')}: {lib.get('description', '')}") + else: + print(f"- {lib}") + + # Convert selected tools to langchain tool objects + tool_names = [ + tool["name"] if isinstance(tool, dict) else getattr(tool, "name", str(tool)) + for tool in selected_resources["tools"] + ] + retrieved_list_of_tools = [] + + # Get the tool objects by name + for tool_name in tool_names: + # Find the tool in the original tools list + matching_tools = [t for t in self.tools if getattr(t, "name", None) == tool_name] + if matching_tools: + retrieved_list_of_tools.append(matching_tools[0]) + + # Add back coding tool if not already included + if len([i for i in retrieved_list_of_tools if i.name == "run_python_repl"]) == 0: + retrieved_list_of_tools = retrieved_list_of_tools + [ + i for i in self.tools if i.name == "run_python_repl" + ] + + print("Retrieved tools: \n" + "\n".join([l.name + ": " + l.description for l in retrieved_list_of_tools])) + # Recreate the custom agent with the retrieved tools + self.app = self._create_custom_react_agent(self.llm, retrieved_list_of_tools, self.prompt) + + # Default behavior (no tool retriever or retrieval_method is 'none') + config = {"recursion_limit": 50} + inputs = {"messages": [("user", prompt)]} + self.log = [] + for s in self.app.stream(inputs, stream_mode="values", config=config): + message = s["messages"][-1] + out = pretty_print(message) + self.log.append(out) + return self.log, s["messages"][-1].content + + def result_formatting(self, output_class, task_intention): + self.format_check_prompt = ChatPromptTemplate.from_messages( + [ + ( + "system", + ( + "You are evaluateGPT, tasked with extract and parse the task output based on the history of an agent. " + "Review the entire history of messages provided. " + "Here is the task output requirement: \n" + f"'{task_intention.replace('{', '{{').replace('}', '}}')}'.\n" + ), + ), + ("placeholder", "{messages}"), + ] + ) + + checker_llm = self.format_check_prompt | self.llm.with_structured_output(output_class) + result = checker_llm.invoke({"messages": [("user", str(self.log))]}).dict() + return result diff --git a/BioScientist/agent_system/engines/v1_executor_backup/config.py b/BioScientist/agent_system/engines/v1_executor_backup/config.py new file mode 100644 index 0000000000000000000000000000000000000000..40804ca4ceac4c6b85c4435bef7cdbef06db2d9b --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/config.py @@ -0,0 +1,99 @@ +""" +Biomni Configuration Management + +Simple configuration class for centralizing common settings. +Maintains full backward compatibility with existing code. +""" + +import os +from dataclasses import dataclass + + +@dataclass +class BiomniConfig: + """Central configuration for Biomni agent. + + All settings are optional and have sensible defaults. + API keys are still read from environment variables to maintain + compatibility with existing .env file structure. + + Usage: + # Create config with defaults + config = BiomniConfig() + + # Override specific settings + config = BiomniConfig(llm="gpt-4", timeout_seconds=1200) + + # Modify after creation + config.path = "./custom_data" + """ + + # Data and execution settings + path: str = "./data" + timeout_seconds: int = 600 + + # LLM settings (API keys still from environment) + llm: str = "claude-sonnet-4-6" + temperature: float = 0.7 + + # Tool settings + use_tool_retriever: bool = True + + # Data licensing settings + commercial_mode: bool = False # If True, excludes non-commercial datasets + + # Custom model settings (for custom LLM serving) + base_url: str | None = None + api_key: str | None = None # Only for custom models, not provider API keys + + # LLM source (auto-detected if None) + source: str | None = None + + # Third-party integrations + protocols_io_access_token: str | None = None + + def __post_init__(self): + """Load any environment variable overrides if they exist.""" + # Check for environment variable overrides (optional) + # Support both old and new names for backwards compatibility + if os.getenv("BIOMNI_PATH") or os.getenv("BIOMNI_DATA_PATH"): + self.path = os.getenv("BIOMNI_PATH") or os.getenv("BIOMNI_DATA_PATH") + if os.getenv("BIOMNI_TIMEOUT_SECONDS"): + self.timeout_seconds = int(os.getenv("BIOMNI_TIMEOUT_SECONDS")) + if os.getenv("BIOMNI_LLM") or os.getenv("BIOMNI_LLM_MODEL"): + self.llm = os.getenv("BIOMNI_LLM") or os.getenv("BIOMNI_LLM_MODEL") + if os.getenv("BIOMNI_USE_TOOL_RETRIEVER"): + self.use_tool_retriever = os.getenv("BIOMNI_USE_TOOL_RETRIEVER").lower() == "true" + if os.getenv("BIOMNI_COMMERCIAL_MODE"): + self.commercial_mode = os.getenv("BIOMNI_COMMERCIAL_MODE").lower() == "true" + if os.getenv("BIOMNI_TEMPERATURE"): + self.temperature = float(os.getenv("BIOMNI_TEMPERATURE")) + if os.getenv("BIOMNI_CUSTOM_BASE_URL"): + self.base_url = os.getenv("BIOMNI_CUSTOM_BASE_URL") + if os.getenv("BIOMNI_CUSTOM_API_KEY"): + self.api_key = os.getenv("BIOMNI_CUSTOM_API_KEY") + if os.getenv("BIOMNI_SOURCE"): + self.source = os.getenv("BIOMNI_SOURCE") + + # Protocols.io access token (prefer specific env vars) + env_token = os.getenv("PROTOCOLS_IO_ACCESS_TOKEN") or os.getenv("BIOMNI_PROTOCOLS_IO_ACCESS_TOKEN") + if env_token: + self.protocols_io_access_token = env_token + + def to_dict(self) -> dict: + """Convert config to dictionary for easy access.""" + return { + "path": self.path, + "timeout_seconds": self.timeout_seconds, + "llm": self.llm, + "temperature": self.temperature, + "use_tool_retriever": self.use_tool_retriever, + "commercial_mode": self.commercial_mode, + "base_url": self.base_url, + "api_key": self.api_key, + "source": self.source, + } + + +# Global default config instance (optional, for convenience) +default_config = BiomniConfig() diff --git a/BioScientist/agent_system/engines/v1_executor_backup/env_desc.py b/BioScientist/agent_system/engines/v1_executor_backup/env_desc.py new file mode 100644 index 0000000000000000000000000000000000000000..ec834d4cab9ce9b9e402799c4ab9e4be8c2dbd48 --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/env_desc.py @@ -0,0 +1,221 @@ +# Data lake dictionary with detailed descriptions +data_lake_dict = { + "affinity_capture-ms.parquet": "Protein-protein interactions detected via affinity capture and mass spectrometry.", + "affinity_capture-rna.parquet": "Protein-RNA interactions detected by affinity capture.", + "BindingDB_All_202409.tsv": "Measured binding affinities between proteins and small molecules for drug discovery.", + "broad_repurposing_hub_molecule_with_smiles.parquet": "Molecules from Broad Institute's Drug Repurposing Hub with SMILES annotations.", + "broad_repurposing_hub_phase_moa_target_info.parquet": "Drug phases, mechanisms of action, and target information from Broad Institute.", + "co-fractionation.parquet": "Protein-protein interactions from co-fractionation experiments.", + "czi_census_datasets_v4.parquet": "Datasets from the Chan Zuckerberg Initiative's Cell Census.", + "DepMap_CRISPRGeneDependency.csv": "Gene dependency probability estimates for cancer cell lines, including all DepMap models.", + "DepMap_CRISPRGeneEffect.csv": "Genome-wide CRISPR gene effect estimates for cancer cell lines, including all DepMap models.", + "DepMap_Model.csv": "Metadata describing all cancer models/cell lines which are referenced by a dataset contained within the DepMap portal.", + "DepMap_OmicsExpressionProteinCodingGenesTPMLogp1.csv": "Gene expression in TPMs for cancer cell lines, including all DepMap models.", + "ddinter_alimentary_tract_metabolism.csv": "Drug-drug interactions for alimentary tract and metabolism drugs from DDInter 2.0 database.", + "ddinter_antineoplastic.csv": "Drug-drug interactions for antineoplastic and immunomodulating agents from DDInter 2.0 database.", + "ddinter_antiparasitic.csv": "Drug-drug interactions for antiparasitic products from DDInter 2.0 database.", + "ddinter_blood_organs.csv": "Drug-drug interactions for blood and blood forming organs drugs from DDInter 2.0 database.", + "ddinter_dermatological.csv": "Drug-drug interactions for dermatological drugs from DDInter 2.0 database.", + "ddinter_hormonal.csv": "Drug-drug interactions for systemic hormonal preparations from DDInter 2.0 database.", + "ddinter_respiratory.csv": "Drug-drug interactions for respiratory system drugs from DDInter 2.0 database.", + "ddinter_various.csv": "Drug-drug interactions for various drugs from DDInter 2.0 database.", + "DisGeNET.parquet": "Gene-disease associations from multiple sources.", + "dosage_growth_defect.parquet": "Gene dosage changes affecting growth.", + "enamine_cloud_library_smiles.pkl": "Compounds from Enamine REAL library with SMILES annotations.", + "evebio_assay_table.csv": "Assay metadata with one row per assay from EveBio pharmome mapping.", + "evebio_bundle_table.csv": "Target subfamily bundles used for screening-to-profiling progression.", + "evebio_compound_table.csv": "Compound metadata with common identifiers from EveBio screening.", + "evebio_control_table.csv": "Control datapoints for all screening and profiling plates.", + "evebio_detailed_result_table.csv": "Expanded results on evebio_summary_result_table with curve fit parameters and phase categories.", + "evebio_observed_points_table.csv": "Raw observed datapoints from all screening and profiling experiments.", + "evebio_summary_result_table.csv": "Succinct summary of results for each assay-compound combination.", + "evebio_target_table.csv": "Target metadata with common identifiers from EveBio screening.", + "genebass_missense_LC_filtered.pkl": "Filtered missense variants from GeneBass.", + "genebass_pLoF_filtered.pkl": "Predicted loss-of-function variants from GeneBass.", + "genebass_synonymous_filtered.pkl": "Filtered synonymous variants from GeneBass.", + "gene_info.parquet": "Comprehensive gene information.", + "genetic_interaction.parquet": "Genetic interactions between genes.", + "go-plus.json": "Gene ontology data for functional gene annotations.", + "gtex_tissue_gene_tpm.parquet": "Gene expression (TPM) across human tissues from GTEx.", + "gwas_catalog.pkl": "Genome-wide association studies (GWAS) results.", + "hp.obo": "Official HPO release in obographs format", + "kg.csv": "Precision medicine knowledge graph with 17,080 diseases and 4+ million relationships across biological scales.", + "marker_celltype.parquet": "Cell type marker genes for identification.", + "McPAS-TCR.parquet": "T-cell receptor sequences and specificity data from McPAS database.", + "miRDB_v6.0_results.parquet": "Predicted microRNA targets from miRDB.", + "miRTarBase_microRNA_target_interaction.parquet": "Experimentally validated microRNA-target interactions from miRTarBase.", + "miRTarBase_microRNA_target_interaction_pubmed_abtract.txt": "PubMed abstracts for microRNA-target interactions in miRTarBase.", + "miRTarBase_MicroRNA_Target_Sites.parquet": "Binding sites of microRNAs on target genes from miRTarBase.", + "mousemine_m1_positional_geneset.parquet": "Positional gene sets from MouseMine.", + "mousemine_m2_curated_geneset.parquet": "Curated gene sets from MouseMine.", + "mousemine_m3_regulatory_target_geneset.parquet": "Regulatory target gene sets from MouseMine.", + "mousemine_m5_ontology_geneset.parquet": "Ontology-based gene sets from MouseMine.", + "mousemine_m8_celltype_signature_geneset.parquet": "Cell type signature gene sets from MouseMine.", + "mousemine_mh_hallmark_geneset.parquet": "Hallmark gene sets from MouseMine.", + "msigdb_human_c1_positional_geneset.parquet": "Human positional gene sets from MSigDB.", + "msigdb_human_c2_curated_geneset.parquet": "Curated human gene sets from MSigDB.", + "msigdb_human_c3_regulatory_target_geneset.parquet": "Regulatory target gene sets from MSigDB.", + "msigdb_human_c3_subset_transcription_factor_targets_from_GTRD.parquet": "Transcription factor targets from GTRD/MSigDB.", + "msigdb_human_c4_computational_geneset.parquet": "Computationally derived gene sets from MSigDB.", + "msigdb_human_c5_ontology_geneset.parquet": "Ontology-based gene sets from MSigDB.", + "msigdb_human_c6_oncogenic_signature_geneset.parquet": "Oncogenic signatures from MSigDB.", + "msigdb_human_c7_immunologic_signature_geneset.parquet": "Immunologic signatures from MSigDB.", + "msigdb_human_c8_celltype_signature_geneset.parquet": "Cell type signatures from MSigDB.", + "msigdb_human_h_hallmark_geneset.parquet": "Hallmark gene sets from MSigDB.", + "omim.parquet": "Genetic disorders and associated genes from OMIM.", + "proteinatlas.tsv": "Protein expression data from Human Protein Atlas.", + "proximity_label-ms.parquet": "Protein interactions via proximity labeling and mass spectrometry.", + "reconstituted_complex.parquet": "Protein complexes reconstituted in vitro.", + "sgRNA_KO_SP_mouse.txt": "sgRNA knockout data for mouse.", + "sgRNA_KO_SP_human.txt": "sgRNA knockout data for human.", + "synthetic_growth_defect.parquet": "Synthetic growth defects from genetic interactions.", + "synthetic_lethality.parquet": "Synthetic lethal interactions.", + "synthetic_rescue.parquet": "Genetic interactions rescuing phenotypes.", + "two-hybrid.parquet": "Protein-protein interactions detected by yeast two-hybrid assays.", + "variant_table.parquet": "Annotated genetic variants table.", + "Virus-Host_PPI_P-HIPSTER_2020.parquet": "Virus-host protein-protein interactions from P-HIPSTER.", + "txgnn_name_mapping.pkl": "Name mapping for TXGNN.", + "txgnn_prediction.pkl": "Prediction data for TXGNN.", +} + +# Updated library_content as a dictionary with detailed descriptions +library_content_dict = { + # === PYTHON PACKAGES === + # Core Bioinformatics Libraries (Python) + "biopython": "[Python Package] A set of tools for biological computation including parsers for bioinformatics files, access to online services, and interfaces to common bioinformatics programs.", + "biom-format": "[Python Package] The Biological Observation Matrix (BIOM) format is designed for representing biological sample by observation contingency tables with associated metadata.", + "scanpy": "[Python Package] A scalable toolkit for analyzing single-cell gene expression data, specifically designed for large datasets using AnnData.", + "scikit-bio": "[Python Package] Data structures, algorithms, and educational resources for bioinformatics, including sequence analysis, phylogenetics, and ordination methods.", + "anndata": "[Python Package] A Python package for handling annotated data matrices in memory and on disk, primarily used for single-cell genomics data.", + "mudata": "[Python Package] A Python package for multimodal data storage and manipulation, extending AnnData to handle multiple modalities.", + "pyliftover": "[Python Package] A Python implementation of UCSC liftOver tool for converting genomic coordinates between genome assemblies.", + "biopandas": "[Python Package] A package that provides pandas DataFrames for working with molecular structures and biological data.", + "biotite": "[Python Package] A comprehensive library for computational molecular biology, providing tools for sequence analysis, structure analysis, and more.", + "lazyslide": "[Python Package] A Python framework that brings interoperable, reproducible whole slide image analysis, enabling seamless histopathology workflows from preprocessing to deep learning.", + # Genomics & Variant Analysis (Python) + "gget": "[Python Package] A toolkit for accessing genomic databases and retrieving sequences, annotations, and other genomic data.", + "lifelines": "[Python Package] A complete survival analysis library for fitting models, plotting, and statistical tests.", + # "scvi-tools": "[Python Package] A package for probabilistic modeling of single-cell omics data, including deep generative models.", + "gseapy": "[Python Package] A Python wrapper for Gene Set Enrichment Analysis (GSEA) and visualization.", + "scrublet": "[Python Package] A tool for detecting doublets in single-cell RNA-seq data.", + "cellxgene-census": "[Python Package] A tool for accessing and analyzing the CellxGene Census, a collection of single-cell datasets. To download a dataset, use the download_source_h5ad function with the dataset id as the argument (856c1b98-5727-49da-bf0f-151bdb8cb056, no .h5ad extension).", + "hyperopt": "[Python Package] A Python library for optimizing hyperparameters of machine learning algorithms.", + "scvelo": "[Python Package] A tool for RNA velocity analysis in single cells using dynamical models.", + "pysam": "[Python Package] A Python module for reading, manipulating and writing genomic data sets in SAM/BAM/VCF/BCF formats.", + "pyfaidx": "[Python Package] A Python package for efficient random access to FASTA files.", + "pyranges": "[Python Package] A Python package for interval manipulation with a pandas-like interface.", + "pybedtools": "[Python Package] A Python wrapper for Aaron Quinlan's BEDTools programs.", + # "panhumanpy": "A Python package for hierarchical, cross-tissue cell type annotation of human single-cell RNA-seq data", + # Structural Biology & Drug Discovery (Python) + "rdkit": "[Python Package] A collection of cheminformatics and machine learning tools for working with chemical structures and drug discovery.", + "deeppurpose": "[Python Package] A deep learning library for drug-target interaction prediction and virtual screening.", + "pyscreener": "[Python Package] A Python package for virtual screening of chemical compounds.", + "openbabel": "[Python Package] A chemical toolbox designed to speak the many languages of chemical data, supporting file format conversion and molecular modeling.", + "descriptastorus": "[Python Package] A library for computing molecular descriptors for machine learning applications in drug discovery.", + # "pymol": "[Python Package] A molecular visualization system for rendering and animating 3D molecular structures.", + "openmm": "[Python Package] A toolkit for molecular simulation using high-performance GPU computing.", + "pytdc": "[Python Package] A Python package for Therapeutics Data Commons, providing access to machine learning datasets for drug discovery.", + # Data Science & Statistical Analysis (Python) + "pandas": "[Python Package] A fast, powerful, and flexible data analysis and manipulation library for Python.", + "numpy": "[Python Package] The fundamental package for scientific computing with Python, providing support for arrays, matrices, and mathematical functions.", + "scipy": "[Python Package] A Python library for scientific and technical computing, including modules for optimization, linear algebra, integration, and statistics.", + "scikit-learn": "[Python Package] A machine learning library featuring various classification, regression, and clustering algorithms.", + "matplotlib": "[Python Package] A comprehensive library for creating static, animated, and interactive visualizations in Python.", + "seaborn": "[Python Package] A statistical data visualization library based on matplotlib with a high-level interface for drawing attractive statistical graphics.", + "statsmodels": "[Python Package] A Python module for statistical modeling and econometrics, including descriptive statistics and estimation of statistical models.", + "pymc3": "[Python Package] A Python package for Bayesian statistical modeling and probabilistic machine learning.", + # "pystan": "[Python Package] A Python interface to Stan, a platform for statistical modeling and high-performance statistical computation.", + "umap-learn": "[Python Package] Uniform Manifold Approximation and Projection, a dimension reduction technique.", + "faiss-cpu": "[Python Package] A library for efficient similarity search and clustering of dense vectors.", + "harmony-pytorch": "[Python Package] A PyTorch implementation of the Harmony algorithm for integrating single-cell data.", + # General Bioinformatics & Computational Utilities (Python) + "tiledb": "[Python Package] A powerful engine for storing and analyzing large-scale genomic data.", + "tiledbsoma": "[Python Package] A library for working with the SOMA (Stack of Matrices) format using TileDB.", + "h5py": "[Python Package] A Python interface to the HDF5 binary data format, allowing storage of large amounts of numerical data.", + "tqdm": "[Python Package] A fast, extensible progress bar for loops and CLI applications.", + "joblib": "[Python Package] A set of tools to provide lightweight pipelining in Python, including transparent disk-caching and parallel computing.", + "opencv-python": "[Python Package] OpenCV library for computer vision tasks, useful for image analysis in biological contexts.", + "PyPDF2": "[Python Package] A library for working with PDF files, useful for extracting text from scientific papers.", + "googlesearch-python": "[Python Package] A library for performing Google searches programmatically.", + "scikit-image": "[Python Package] A collection of algorithms for image processing in Python.", + "pymed": "[Python Package] A Python library for accessing PubMed articles.", + "arxiv": "[Python Package] A Python wrapper for the arXiv API, allowing access to scientific papers.", + "scholarly": "[Python Package] A module to retrieve author and publication information from Google Scholar.", + "cryosparc-tools": "[Python Package] Tools for working with cryoSPARC, a platform for cryo-EM data processing.", + "mageck": "[Python Package] Analysis of CRISPR screen data.", + "igraph": "[Python Package] Network analysis and visualization.", + "pyscenic": "[Python Package] Analysis of single-cell RNA-seq data and gene regulatory networks.", + "cooler": "[Python Package] Storage and analysis of Hi-C data.", + "trackpy": "[Python Package] Particle tracking in images and video.", + "nnunet": "[Python Package] A deep learning framework for biomedical image segmentation, providing a standardized approach to training and inference.", + # "flowcytometrytools": "[Python Package] Analysis and visualization of flow cytometry data.", + "cellpose": "[Python Package] Cell segmentation in microscopy images.", + "viennarna": "[Python Package] RNA secondary structure prediction.", + "PyMassSpec": "[Python Package] Mass spectrometry data analysis.", + "python-libsbml": "[Python Package] Working with SBML files for computational biology.", + "cobra": "[Python Package] Constraint-based modeling of metabolic networks.", + "reportlab": "[Python Package] Creation of PDF documents.", + "flowkit": "[Python Package] Toolkit for processing flow cytometry data.", + "hmmlearn": "[Python Package] Hidden Markov model analysis.", + "msprime": "[Python Package] Simulation of genetic variation.", + "tskit": "[Python Package] Handling tree sequences and population genetics data.", + "cyvcf2": "[Python Package] Fast parsing of VCF files.", + "pykalman": "[Python Package] Kalman filter and smoother implementation.", + "fanc": "[Python Package] Analysis of chromatin conformation data.", + "loompy": "A Python implementation of the Loom file format for efficiently storing and working with large omics datasets.", + "pyBigWig": "A Python library for accessing bigWig and bigBed files for genome browser track data.", + "pymzml": "A Python module for high-throughput bioinformatics analysis of mass spectrometry data.", + "optlang": "A Python package for modeling optimization problems symbolically.", + "FlowIO": "A Python package for reading and writing flow cytometry data files.", + "FlowUtils": "Utilities for processing and analyzing flow cytometry data.", + "arboreto": "A Python package for inferring gene regulatory networks from single-cell RNA-seq data.", + "pdbfixer": "A Python package for fixing problems in PDB files in preparation for molecular simulations.", + # === R PACKAGES === + # Core R Packages for Data Analysis + "ggplot2": "[R Package] A system for declaratively creating graphics, based on The Grammar of Graphics. Use with subprocess.run(['Rscript', '-e', 'library(ggplot2); ...']).", + "dplyr": "[R Package] A grammar of data manipulation, providing a consistent set of verbs that help you solve the most common data manipulation challenges. Use with subprocess.", + "tidyr": "[R Package] A package that helps you create tidy data, where each column is a variable, each row is an observation, and each cell is a single value. Use with subprocess.", + "readr": "[R Package] A fast and friendly way to read rectangular data like CSV, TSV, and FWF. Use with subprocess.run(['Rscript', '-e', 'library(readr); ...']).", + "stringr": "[R Package] A cohesive set of functions designed to make working with strings as easy as possible. Use with subprocess calls.", + "Matrix": "[R Package] A package that provides classes and methods for dense and sparse matrices. Required for Seurat. Use with subprocess calls.", + # "Rcpp": "[R Package] Seamless R and C++ Integration, allowing R functions to call compiled C++ code. Use with subprocess calls.", + # "devtools": "[R Package] Tools to make developing R packages easier, including functions to install packages from GitHub. Use with subprocess calls.", + # "remotes": "[R Package] Install R packages from GitHub, GitLab, Bitbucket, or other remote repositories. Use with subprocess calls.", + # Bioinformatics R Packages + "DESeq2": "[R Package] Differential gene expression analysis based on the negative binomial distribution. Use with subprocess.run(['Rscript', '-e', 'library(DESeq2); ...']).", + "clusterProfiler": "[R Package] A package for statistical analysis and visualization of functional profiles for genes and gene clusters. Use with subprocess calls.", + # "DADA2": "[R Package] A package for modeling and correcting Illumina-sequenced amplicon errors. Use with subprocess calls.", + # "xcms": "[R Package] A package for processing and visualization of LC-MS and GC-MS data. Use with subprocess calls.", + # "FlowCore": "[R Package] Basic infrastructure for flow cytometry data. Use with subprocess calls.", + "edgeR": "[R Package] Empirical Analysis of Digital Gene Expression Data in R, for differential expression analysis. Use with subprocess calls.", + "limma": "[R Package] Linear Models for Microarray Data, for differential expression analysis. Use with subprocess calls.", + "harmony": "[R Package] A method for integrating and analyzing single-cell data across datasets. Use with subprocess calls.", + "WGCNA": "[R Package] Weighted Correlation Network Analysis for studying biological networks. Use with subprocess calls.", + # === CLI TOOLS === + # Sequence Analysis Tools + "samtools": "[CLI Tool] A suite of programs for interacting with high-throughput sequencing data. Use with subprocess.run(['samtools', ...]).", + "bowtie2": "[CLI Tool] An ultrafast and memory-efficient tool for aligning sequencing reads to long reference sequences. Use with subprocess.run(['bowtie2', ...]).", + "bwa": "[CLI Tool] Burrows-Wheeler Aligner for mapping low-divergent sequences against a large reference genome. Use with subprocess.run(['bwa', ...]).", + "bedtools": "[CLI Tool] A powerful toolset for genome arithmetic, allowing operations like intersect, merge, count, and complement on genomic features. Use with subprocess.run(['bedtools', ...]).", + "macs2": "[CLI Tool] Model-based Analysis of ChIP-Seq data, a tool for identifying transcript factor binding sites.", + # Quality Control and Processing Tools + "fastqc": "[CLI Tool] A quality control tool for high throughput sequence data. Use with subprocess.run(['fastqc', ...]).", + "trimmomatic": "[CLI Tool] A flexible read trimming tool for Illumina NGS data. Use with subprocess.run(['trimmomatic', ...]).", + # Multiple Sequence Alignment and Phylogenetics + "mafft": "[CLI Tool] A multiple sequence alignment program for unix-like operating systems. Use with subprocess.run(['mafft', ...]).", + "Homer": "[CLI Tool] Motif discovery and next-gen sequencing analysis.", + "FastTree": "[CLI Tool] Phylogenetic trees from sequence alignments.", + "muscle": "[CLI Tool] Multiple sequence alignment tool.", + # Genetic Analysis Tools + "plink": "[CLI Tool] A comprehensive toolkit for genome association studies that can perform a range of large-scale analyses in a computationally efficient manner. Use with subprocess.run(['plink', ...]).", + "plink2": "[CLI Tool] A comprehensive toolkit for genome association studies that can perform a range of large-scale analyses in a computationally efficient manner. Use with subprocess.run(['plink2', ...]).", + "gcta64": "[CLI Tool] Genome-wide Complex Trait Analysis (GCTA) tool for estimating the proportion of phenotypic variance explained by genome-wide SNPs and analyzing genetic relationships. Use with subprocess.run(['gcta64', ...]).", + "iqtree2": "[CLI Tool] An efficient phylogenetic software for maximum likelihood analysis with built-in model selection and ultrafast bootstrap. Use with subprocess.run(['iqtree2', ...]).", + "ADFR": "AutoDock for Receptors suite for molecular docking and virtual screening. ", + "diamond": "A sequence aligner for protein and translated DNA searches, designed for high performance analysis of big sequence data. ", + "fcsparser": "A command-line tool for parsing and analyzing flow cytometry standard (FCS) files. ", + "plannotate": "[CLI Tool] A tool for annotating plasmid sequences with common features. ", + "vina": "[CLI Tool] An open-source program for molecular docking and virtual screening, known for its speed and accuracy improvements over AutoDock 4.", + "autosite": "[CLI Tool] A binding site detection tool used to identify potential ligand binding pockets on protein structures for molecular docking.", + "PyLabRobot": "[Python Package] A Python package for controlling liquid-handling robots and other lab automation equipment.", +} diff --git a/BioScientist/agent_system/engines/v1_executor_backup/env_desc_cm.py b/BioScientist/agent_system/engines/v1_executor_backup/env_desc_cm.py new file mode 100644 index 0000000000000000000000000000000000000000..177920da8cda3c79ce60de0aa1f21ec595b3779a --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/env_desc_cm.py @@ -0,0 +1,219 @@ +# Data lake dictionary with detailed descriptions (Commercial Mode - Non-commercial datasets commented out) +data_lake_dict = { + "affinity_capture-ms.parquet": "Protein-protein interactions detected via affinity capture and mass spectrometry.", + "affinity_capture-rna.parquet": "Protein-RNA interactions detected by affinity capture.", + # "BindingDB_All_202409.tsv": "Measured binding affinities between proteins and small molecules for drug discovery.", # Requires commercial license + "broad_repurposing_hub_molecule_with_smiles.parquet": "Molecules from Broad Institute's Drug Repurposing Hub with SMILES annotations.", + "broad_repurposing_hub_phase_moa_target_info.parquet": "Drug phases, mechanisms of action, and target information from Broad Institute.", + "co-fractionation.parquet": "Protein-protein interactions from co-fractionation experiments.", + "czi_census_datasets_v4.parquet": "Datasets from the Chan Zuckerberg Initiative's Cell Census.", + "DepMap_CRISPRGeneDependency.csv": "Gene dependency probability estimates for cancer cell lines, including all DepMap models.", + "DepMap_CRISPRGeneEffect.csv": "Genome-wide CRISPR gene effect estimates for cancer cell lines, including all DepMap models.", + "DepMap_Model.csv": "Metadata describing all cancer models/cell lines which are referenced by a dataset contained within the DepMap portal.", + "DepMap_OmicsExpressionProteinCodingGenesTPMLogp1.csv": "Gene expression in TPMs for cancer cell lines, including all DepMap models.", + # "ddinter_alimentary_tract_metabolism.csv": "Drug-drug interactions for alimentary tract and metabolism drugs from DDInter 2.0 database.", # CC BY-NC-SA 4.0 - Non-commercial only + # "ddinter_antineoplastic.csv": "Drug-drug interactions for antineoplastic and immunomodulating agents from DDInter 2.0 database.", # CC BY-NC-SA 4.0 - Non-commercial only + # "ddinter_antiparasitic.csv": "Drug-drug interactions for antiparasitic products from DDInter 2.0 database.", # CC BY-NC-SA 4.0 - Non-commercial only + # "ddinter_blood_organs.csv": "Drug-drug interactions for blood and blood forming organs drugs from DDInter 2.0 database.", # CC BY-NC-SA 4.0 - Non-commercial only + # "ddinter_dermatological.csv": "Drug-drug interactions for dermatological drugs from DDInter 2.0 database.", # CC BY-NC-SA 4.0 - Non-commercial only + # "ddinter_hormonal.csv": "Drug-drug interactions for systemic hormonal preparations from DDInter 2.0 database.", # CC BY-NC-SA 4.0 - Non-commercial only + # "ddinter_respiratory.csv": "Drug-drug interactions for respiratory system drugs from DDInter 2.0 database.", # CC BY-NC-SA 4.0 - Non-commercial only + # "ddinter_various.csv": "Drug-drug interactions for various drugs from DDInter 2.0 database.", # CC BY-NC-SA 4.0 - Non-commercial only + # "DisGeNET.parquet": "Gene-disease associations from multiple sources.", # CC BY-NC-SA 4.0 - Non-commercial only + "dosage_growth_defect.parquet": "Gene dosage changes affecting growth.", + # "enamine_cloud_library_smiles.pkl": "Compounds from Enamine REAL library with SMILES annotations.", # Proprietary - Requires license + # "evebio_assay_table.csv": "Assay metadata with one row per assay from EveBio pharmome mapping.", # Proprietary - Requires permission + # "evebio_bundle_table.csv": "Target subfamily bundles used for screening-to-profiling progression.", # Proprietary - Requires permission + # "evebio_compound_table.csv": "Compound metadata with common identifiers from EveBio screening.", # Proprietary - Requires permission + # "evebio_control_table.csv": "Control datapoints for all screening and profiling plates.", # Proprietary - Requires permission + # "evebio_detailed_result_table.csv": "Expanded results on evebio_summary_result_table with curve fit parameters and phase categories.", # Proprietary - Requires permission + # "evebio_observed_points_table.csv": "Raw observed datapoints from all screening and profiling experiments.", # Proprietary - Requires permission + # "evebio_summary_result_table.csv": "Succinct summary of results for each assay-compound combination.", # Proprietary - Requires permission + # "evebio_target_table.csv": "Target metadata with common identifiers from EveBio screening.", # Proprietary - Requires permission + "genebass_missense_LC_filtered.pkl": "Filtered missense variants from GeneBass.", + "genebass_pLoF_filtered.pkl": "Predicted loss-of-function variants from GeneBass.", + "genebass_synonymous_filtered.pkl": "Filtered synonymous variants from GeneBass.", + "gene_info.parquet": "Comprehensive gene information.", + "genetic_interaction.parquet": "Genetic interactions between genes.", + "go-plus.json": "Gene ontology data for functional gene annotations.", + "gtex_tissue_gene_tpm.parquet": "Gene expression (TPM) across human tissues from GTEx.", + "gwas_catalog.pkl": "Genome-wide association studies (GWAS) results.", + "hp.obo": "Official HPO release in obographs format", + "kg.csv": "Precision medicine knowledge graph with 17,080 diseases and 4+ million relationships across biological scales.", + "marker_celltype.parquet": "Cell type marker genes for identification.", + # "McPAS-TCR.parquet": "T-cell receptor sequences and specificity data from McPAS database.", # CC BY-NC-SA 4.0 - Non-commercial only + # "miRDB_v6.0_results.parquet": "Predicted microRNA targets from miRDB.", # Non-commercial use only + # "miRTarBase_microRNA_target_interaction.parquet": "Experimentally validated microRNA-target interactions from miRTarBase.", # CC BY-NC 4.0 - Non-commercial only + # "miRTarBase_microRNA_target_interaction_pubmed_abtract.txt": "PubMed abstracts for microRNA-target interactions in miRTarBase.", # CC BY-NC 4.0 - Non-commercial only + # "miRTarBase_MicroRNA_Target_Sites.parquet": "Binding sites of microRNAs on target genes from miRTarBase.", # CC BY-NC 4.0 - Non-commercial only + "mousemine_m1_positional_geneset.parquet": "Positional gene sets from MouseMine.", + "mousemine_m2_curated_geneset.parquet": "Curated gene sets from MouseMine.", + "mousemine_m3_regulatory_target_geneset.parquet": "Regulatory target gene sets from MouseMine.", + "mousemine_m5_ontology_geneset.parquet": "Ontology-based gene sets from MouseMine.", + "mousemine_m8_celltype_signature_geneset.parquet": "Cell type signature gene sets from MouseMine.", + "mousemine_mh_hallmark_geneset.parquet": "Hallmark gene sets from MouseMine.", + # "msigdb_human_c1_positional_geneset.parquet": "Human positional gene sets from MSigDB.", # Requires commercial license + # "msigdb_human_c2_curated_geneset.parquet": "Curated human gene sets from MSigDB.", # Requires commercial license + # "msigdb_human_c3_regulatory_target_geneset.parquet": "Regulatory target gene sets from MSigDB.", # Requires commercial license + # "msigdb_human_c3_subset_transcription_factor_targets_from_GTRD.parquet": "Transcription factor targets from GTRD/MSigDB.", # Requires commercial license + # "msigdb_human_c4_computational_geneset.parquet": "Computationally derived gene sets from MSigDB.", # Requires commercial license + # "msigdb_human_c5_ontology_geneset.parquet": "Ontology-based gene sets from MSigDB.", # Requires commercial license + # "msigdb_human_c6_oncogenic_signature_geneset.parquet": "Oncogenic signatures from MSigDB.", # Requires commercial license + # "msigdb_human_c7_immunologic_signature_geneset.parquet": "Immunologic signatures from MSigDB.", # Requires commercial license + # "msigdb_human_c8_celltype_signature_geneset.parquet": "Cell type signatures from MSigDB.", # Requires commercial license + # "msigdb_human_h_hallmark_geneset.parquet": "Hallmark gene sets from MSigDB.", # Requires commercial license + # "omim.parquet": "Genetic disorders and associated genes from OMIM.", # Requires commercial license + "proteinatlas.tsv": "Protein expression data from Human Protein Atlas.", + "proximity_label-ms.parquet": "Protein interactions via proximity labeling and mass spectrometry.", + "reconstituted_complex.parquet": "Protein complexes reconstituted in vitro.", + "sgRNA_KO_SP_mouse.txt": "sgRNA knockout data for mouse.", + "sgRNA_KO_SP_human.txt": "sgRNA knockout data for human.", + "synthetic_growth_defect.parquet": "Synthetic growth defects from genetic interactions.", + "synthetic_lethality.parquet": "Synthetic lethal interactions.", + "synthetic_rescue.parquet": "Genetic interactions rescuing phenotypes.", + "two-hybrid.parquet": "Protein-protein interactions detected by yeast two-hybrid assays.", + "variant_table.parquet": "Annotated genetic variants table.", + "Virus-Host_PPI_P-HIPSTER_2020.parquet": "Virus-host protein-protein interactions from P-HIPSTER.", + "txgnn_name_mapping.pkl": "Name mapping for TXGNN.", + "txgnn_prediction.pkl": "Prediction data for TXGNN.", +} + +# Updated library_content as a dictionary with detailed descriptions +library_content_dict = { + # === PYTHON PACKAGES === + # Core Bioinformatics Libraries (Python) + "biopython": "[Python Package] A set of tools for biological computation including parsers for bioinformatics files, access to online services, and interfaces to common bioinformatics programs.", + "biom-format": "[Python Package] The Biological Observation Matrix (BIOM) format is designed for representing biological sample by observation contingency tables with associated metadata.", + "scanpy": "[Python Package] A scalable toolkit for analyzing single-cell gene expression data, specifically designed for large datasets using AnnData.", + "scikit-bio": "[Python Package] Data structures, algorithms, and educational resources for bioinformatics, including sequence analysis, phylogenetics, and ordination methods.", + "anndata": "[Python Package] A Python package for handling annotated data matrices in memory and on disk, primarily used for single-cell genomics data.", + "mudata": "[Python Package] A Python package for multimodal data storage and manipulation, extending AnnData to handle multiple modalities.", + "pyliftover": "[Python Package] A Python implementation of UCSC liftOver tool for converting genomic coordinates between genome assemblies.", + "biopandas": "[Python Package] A package that provides pandas DataFrames for working with molecular structures and biological data.", + "biotite": "[Python Package] A comprehensive library for computational molecular biology, providing tools for sequence analysis, structure analysis, and more.", + "lazyslide": "[Python Package] A Python framework that brings interoperable, reproducible whole slide image analysis, enabling seamless histopathology workflows from preprocessing to deep learning.", + # Genomics & Variant Analysis (Python) + "gget": "[Python Package] A toolkit for accessing genomic databases and retrieving sequences, annotations, and other genomic data.", + "lifelines": "[Python Package] A complete survival analysis library for fitting models, plotting, and statistical tests.", + # "scvi-tools": "[Python Package] A package for probabilistic modeling of single-cell omics data, including deep generative models.", + "gseapy": "[Python Package] A Python wrapper for Gene Set Enrichment Analysis (GSEA) and visualization.", + "scrublet": "[Python Package] A tool for detecting doublets in single-cell RNA-seq data.", + "cellxgene-census": "[Python Package] A tool for accessing and analyzing the CellxGene Census, a collection of single-cell datasets. To download a dataset, use the download_source_h5ad function with the dataset id as the argument (856c1b98-5727-49da-bf0f-151bdb8cb056, no .h5ad extension).", + "hyperopt": "[Python Package] A Python library for optimizing hyperparameters of machine learning algorithms.", + "scvelo": "[Python Package] A tool for RNA velocity analysis in single cells using dynamical models.", + "pysam": "[Python Package] A Python module for reading, manipulating and writing genomic data sets in SAM/BAM/VCF/BCF formats.", + "pyfaidx": "[Python Package] A Python package for efficient random access to FASTA files.", + "pyranges": "[Python Package] A Python package for interval manipulation with a pandas-like interface.", + "pybedtools": "[Python Package] A Python wrapper for Aaron Quinlan's BEDTools programs.", + # "panhumanpy": "A Python package for hierarchical, cross-tissue cell type annotation of human single-cell RNA-seq data", + # Structural Biology & Drug Discovery (Python) + "rdkit": "[Python Package] A collection of cheminformatics and machine learning tools for working with chemical structures and drug discovery.", + "deeppurpose": "[Python Package] A deep learning library for drug-target interaction prediction and virtual screening.", + "pyscreener": "[Python Package] A Python package for virtual screening of chemical compounds.", + "openbabel": "[Python Package] A chemical toolbox designed to speak the many languages of chemical data, supporting file format conversion and molecular modeling.", + "descriptastorus": "[Python Package] A library for computing molecular descriptors for machine learning applications in drug discovery.", + # "pymol": "[Python Package] A molecular visualization system for rendering and animating 3D molecular structures.", + "openmm": "[Python Package] A toolkit for molecular simulation using high-performance GPU computing.", + "pytdc": "[Python Package] A Python package for Therapeutics Data Commons, providing access to machine learning datasets for drug discovery.", + # Data Science & Statistical Analysis (Python) + "pandas": "[Python Package] A fast, powerful, and flexible data analysis and manipulation library for Python.", + "numpy": "[Python Package] The fundamental package for scientific computing with Python, providing support for arrays, matrices, and mathematical functions.", + "scipy": "[Python Package] A Python library for scientific and technical computing, including modules for optimization, linear algebra, integration, and statistics.", + "scikit-learn": "[Python Package] A machine learning library featuring various classification, regression, and clustering algorithms.", + "matplotlib": "[Python Package] A comprehensive library for creating static, animated, and interactive visualizations in Python.", + "seaborn": "[Python Package] A statistical data visualization library based on matplotlib with a high-level interface for drawing attractive statistical graphics.", + "statsmodels": "[Python Package] A Python module for statistical modeling and econometrics, including descriptive statistics and estimation of statistical models.", + "pymc3": "[Python Package] A Python package for Bayesian statistical modeling and probabilistic machine learning.", + # "pystan": "[Python Package] A Python interface to Stan, a platform for statistical modeling and high-performance statistical computation.", + "umap-learn": "[Python Package] Uniform Manifold Approximation and Projection, a dimension reduction technique.", + "faiss-cpu": "[Python Package] A library for efficient similarity search and clustering of dense vectors.", + "harmony-pytorch": "[Python Package] A PyTorch implementation of the Harmony algorithm for integrating single-cell data.", + # General Bioinformatics & Computational Utilities (Python) + "tiledb": "[Python Package] A powerful engine for storing and analyzing large-scale genomic data.", + "tiledbsoma": "[Python Package] A library for working with the SOMA (Stack of Matrices) format using TileDB.", + "h5py": "[Python Package] A Python interface to the HDF5 binary data format, allowing storage of large amounts of numerical data.", + "tqdm": "[Python Package] A fast, extensible progress bar for loops and CLI applications.", + "joblib": "[Python Package] A set of tools to provide lightweight pipelining in Python, including transparent disk-caching and parallel computing.", + "opencv-python": "[Python Package] OpenCV library for computer vision tasks, useful for image analysis in biological contexts.", + "PyPDF2": "[Python Package] A library for working with PDF files, useful for extracting text from scientific papers.", + "googlesearch-python": "[Python Package] A library for performing Google searches programmatically.", + "scikit-image": "[Python Package] A collection of algorithms for image processing in Python.", + "pymed": "[Python Package] A Python library for accessing PubMed articles.", + "arxiv": "[Python Package] A Python wrapper for the arXiv API, allowing access to scientific papers.", + "scholarly": "[Python Package] A module to retrieve author and publication information from Google Scholar.", + "cryosparc-tools": "[Python Package] Tools for working with cryoSPARC, a platform for cryo-EM data processing.", + "mageck": "[Python Package] Analysis of CRISPR screen data.", + "igraph": "[Python Package] Network analysis and visualization.", + "pyscenic": "[Python Package] Analysis of single-cell RNA-seq data and gene regulatory networks.", + "cooler": "[Python Package] Storage and analysis of Hi-C data.", + "trackpy": "[Python Package] Particle tracking in images and video.", + # "flowcytometrytools": "[Python Package] Analysis and visualization of flow cytometry data.", + "cellpose": "[Python Package] Cell segmentation in microscopy images.", + "viennarna": "[Python Package] RNA secondary structure prediction.", + "PyMassSpec": "[Python Package] Mass spectrometry data analysis.", + "python-libsbml": "[Python Package] Working with SBML files for computational biology.", + "cobra": "[Python Package] Constraint-based modeling of metabolic networks.", + "reportlab": "[Python Package] Creation of PDF documents.", + "flowkit": "[Python Package] Toolkit for processing flow cytometry data.", + "hmmlearn": "[Python Package] Hidden Markov model analysis.", + "msprime": "[Python Package] Simulation of genetic variation.", + "tskit": "[Python Package] Handling tree sequences and population genetics data.", + "cyvcf2": "[Python Package] Fast parsing of VCF files.", + "pykalman": "[Python Package] Kalman filter and smoother implementation.", + "fanc": "[Python Package] Analysis of chromatin conformation data.", + "loompy": "A Python implementation of the Loom file format for efficiently storing and working with large omics datasets.", + "pyBigWig": "A Python library for accessing bigWig and bigBed files for genome browser track data.", + "pymzml": "A Python module for high-throughput bioinformatics analysis of mass spectrometry data.", + "optlang": "A Python package for modeling optimization problems symbolically.", + "FlowIO": "A Python package for reading and writing flow cytometry data files.", + "FlowUtils": "Utilities for processing and analyzing flow cytometry data.", + "arboreto": "A Python package for inferring gene regulatory networks from single-cell RNA-seq data.", + "pdbfixer": "A Python package for fixing problems in PDB files in preparation for molecular simulations.", + # === R PACKAGES === + # Core R Packages for Data Analysis + "ggplot2": "[R Package] A system for declaratively creating graphics, based on The Grammar of Graphics. Use with subprocess.run(['Rscript', '-e', 'library(ggplot2); ...']).", + "dplyr": "[R Package] A grammar of data manipulation, providing a consistent set of verbs that help you solve the most common data manipulation challenges. Use with subprocess.", + "tidyr": "[R Package] A package that helps you create tidy data, where each column is a variable, each row is an observation, and each cell is a single value. Use with subprocess.", + "readr": "[R Package] A fast and friendly way to read rectangular data like CSV, TSV, and FWF. Use with subprocess.run(['Rscript', '-e', 'library(readr); ...']).", + "stringr": "[R Package] A cohesive set of functions designed to make working with strings as easy as possible. Use with subprocess calls.", + "Matrix": "[R Package] A package that provides classes and methods for dense and sparse matrices. Required for Seurat. Use with subprocess calls.", + # "Rcpp": "[R Package] Seamless R and C++ Integration, allowing R functions to call compiled C++ code. Use with subprocess calls.", + # "devtools": "[R Package] Tools to make developing R packages easier, including functions to install packages from GitHub. Use with subprocess calls.", + # "remotes": "[R Package] Install R packages from GitHub, GitLab, Bitbucket, or other remote repositories. Use with subprocess calls.", + # Bioinformatics R Packages + "DESeq2": "[R Package] Differential gene expression analysis based on the negative binomial distribution. Use with subprocess.run(['Rscript', '-e', 'library(DESeq2); ...']).", + "clusterProfiler": "[R Package] A package for statistical analysis and visualization of functional profiles for genes and gene clusters. Use with subprocess calls.", + # "DADA2": "[R Package] A package for modeling and correcting Illumina-sequenced amplicon errors. Use with subprocess calls.", + # "xcms": "[R Package] A package for processing and visualization of LC-MS and GC-MS data. Use with subprocess calls.", + # "FlowCore": "[R Package] Basic infrastructure for flow cytometry data. Use with subprocess calls.", + "edgeR": "[R Package] Empirical Analysis of Digital Gene Expression Data in R, for differential expression analysis. Use with subprocess calls.", + "limma": "[R Package] Linear Models for Microarray Data, for differential expression analysis. Use with subprocess calls.", + "harmony": "[R Package] A method for integrating and analyzing single-cell data across datasets. Use with subprocess calls.", + "WGCNA": "[R Package] Weighted Correlation Network Analysis for studying biological networks. Use with subprocess calls.", + # === CLI TOOLS === + # Sequence Analysis Tools + "samtools": "[CLI Tool] A suite of programs for interacting with high-throughput sequencing data. Use with subprocess.run(['samtools', ...]).", + "bowtie2": "[CLI Tool] An ultrafast and memory-efficient tool for aligning sequencing reads to long reference sequences. Use with subprocess.run(['bowtie2', ...]).", + "bwa": "[CLI Tool] Burrows-Wheeler Aligner for mapping low-divergent sequences against a large reference genome. Use with subprocess.run(['bwa', ...]).", + "bedtools": "[CLI Tool] A powerful toolset for genome arithmetic, allowing operations like intersect, merge, count, and complement on genomic features. Use with subprocess.run(['bedtools', ...]).", + "macs2": "[CLI Tool] Model-based Analysis of ChIP-Seq data, a tool for identifying transcript factor binding sites.", + # Quality Control and Processing Tools + "fastqc": "[CLI Tool] A quality control tool for high throughput sequence data. Use with subprocess.run(['fastqc', ...]).", + "trimmomatic": "[CLI Tool] A flexible read trimming tool for Illumina NGS data. Use with subprocess.run(['trimmomatic', ...]).", + # Multiple Sequence Alignment and Phylogenetics + "mafft": "[CLI Tool] A multiple sequence alignment program for unix-like operating systems. Use with subprocess.run(['mafft', ...]).", + "Homer": "[CLI Tool] Motif discovery and next-gen sequencing analysis.", + "FastTree": "[CLI Tool] Phylogenetic trees from sequence alignments.", + "muscle": "[CLI Tool] Multiple sequence alignment tool.", + # Genetic Analysis Tools + "plink": "[CLI Tool] A comprehensive toolkit for genome association studies that can perform a range of large-scale analyses in a computationally efficient manner. Use with subprocess.run(['plink', ...]).", + "plink2": "[CLI Tool] A comprehensive toolkit for genome association studies that can perform a range of large-scale analyses in a computationally efficient manner. Use with subprocess.run(['plink2', ...]).", + "gcta64": "[CLI Tool] Genome-wide Complex Trait Analysis (GCTA) tool for estimating the proportion of phenotypic variance explained by genome-wide SNPs and analyzing genetic relationships. Use with subprocess.run(['gcta64', ...]).", + "iqtree2": "[CLI Tool] An efficient phylogenetic software for maximum likelihood analysis with built-in model selection and ultrafast bootstrap. Use with subprocess.run(['iqtree2', ...]).", + "ADFR": "AutoDock for Receptors suite for molecular docking and virtual screening. ", + "diamond": "A sequence aligner for protein and translated DNA searches, designed for high performance analysis of big sequence data. ", + "fcsparser": "A command-line tool for parsing and analyzing flow cytometry standard (FCS) files. ", + "plannotate": "[CLI Tool] A tool for annotating plasmid sequences with common features. ", + "vina": "[CLI Tool] An open-source program for molecular docking and virtual screening, known for its speed and accuracy improvements over AutoDock 4.", + "autosite": "[CLI Tool] A binding site detection tool used to identify potential ligand binding pockets on protein structures for molecular docking.", +} diff --git a/BioScientist/agent_system/engines/v1_executor_backup/eval/__init__.py b/BioScientist/agent_system/engines/v1_executor_backup/eval/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..14821fdb3cbeeaaa48e0efeeb7f29dc13a90622d --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/eval/__init__.py @@ -0,0 +1,3 @@ +from .biomni_eval1 import BiomniEval1 + +__all__ = ["BiomniEval1"] diff --git a/BioScientist/agent_system/engines/v1_executor_backup/eval/biomni_eval1.py b/BioScientist/agent_system/engines/v1_executor_backup/eval/biomni_eval1.py new file mode 100644 index 0000000000000000000000000000000000000000..d77a03b1d44ea0890a4a95f26eb916fdbdecde68 --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/eval/biomni_eval1.py @@ -0,0 +1,324 @@ +""" +BiomniEval1: Evaluation loader for Biomni tasks + +This class provides a unified interface to evaluate user answers against ground truth +for all tasks in the BiomniEval1 benchmark. +""" + +import json +from typing import Any + +import pandas as pd + + +class BiomniEval1: + """ + Evaluation loader for BiomniEval1 benchmark + + Usage: + evaluator = BiomniEval1('biomni_eval1_dataset.parquet') + score = evaluator.evaluate('gwas_causal_gene_opentargets', 0, 'BRCA1') + """ + + def __init__(self): + """ + Initialize the BiomniEval1 evaluator + + Args: + dataset_path: Path to the merged dataset parquet file + """ + + self.df = pd.read_parquet("hf://datasets/biomni/Eval1/biomni_eval1_dataset.parquet") + + # Create index mapping for fast lookup using task_instance_id + self.instance_map = {} + for idx, row in self.df.iterrows(): + key = (row["task_name"], row["task_instance_id"]) + self.instance_map[key] = idx + + print(f"Loaded BiomniEval1 dataset: {len(self.df)} instances across {self.df['task_name'].nunique()} tasks") + + def evaluate(self, task_name: str, task_instance_id: int, user_answer: str) -> float: + """ + Evaluate a user's answer for a given task and instance + + Args: + task_name: Name of the task (e.g., 'gwas_causal_gene_opentargets') + task_instance_id: Task-specific instance ID (not the global instance_id) + user_answer: User's answer (format depends on task) + + Returns: + float: Reward score (0.0 to 1.0) + """ + # Look up the instance in the dataset using task_instance_id + key = (task_name, task_instance_id) + if key not in self.instance_map: + raise ValueError(f"Instance not found: task={task_name}, task_instance_id={task_instance_id}") + + df_idx = self.instance_map[key] + row = self.df.iloc[df_idx] + ground_truth = row["answer"] + + # Call task-specific evaluation function + try: + reward = self._compute_reward(task_name, user_answer, ground_truth) + return float(reward) + + except Exception as e: + # Preserve original traceback context for easier debugging + raise RuntimeError(f"Error computing reward for {task_name} instance {task_instance_id}: {e}") from e + + def _compute_reward(self, task_name: str, user_answer: str, ground_truth: str) -> float: + """Compute reward using task-specific logic""" + + if task_name == "crispr_delivery": + # CRISPR expects answer as a letter (a-f), exact match + return 1.0 if user_answer.strip().lower() == ground_truth.strip().lower() else 0.0 + + elif task_name.startswith("gwas_causal_gene"): + # GWAS causal gene expects exact gene match (case-insensitive) + return 1.0 if user_answer.strip().upper() == ground_truth.strip().upper() else 0.0 + + elif task_name == "gwas_variant_prioritization": + # GWAS variant expects exact variant match + return 1.0 if user_answer.strip() == ground_truth.strip() else 0.0 + + elif task_name == "hle": + # HLE expects letter answer (A-Z), case-insensitive + return 1.0 if user_answer.strip().upper() == ground_truth.strip().upper() else 0.0 + + elif task_name.startswith("lab_bench"): + # Lab bench expects letter answer (A-Z), case-insensitive + return 1.0 if user_answer.strip().upper() == ground_truth.strip().upper() else 0.0 + + elif task_name == "rare_disease_diagnosis": + # Rare disease expects JSON with OMIM_ID match + # Parse both user answer and ground truth + try: + if isinstance(user_answer, str): + try: + user_dict = json.loads(user_answer) + except json.JSONDecodeError: + import ast + + user_dict = ast.literal_eval(user_answer) + else: + user_dict = user_answer + + if isinstance(ground_truth, str): + gt_dict = json.loads(ground_truth) + else: + gt_dict = ground_truth + + # Compare OMIM_ID + return 1.0 if user_dict.get("OMIM_ID") == gt_dict.get("OMIM_ID") else 0.0 + + except Exception: + return 0.0 + + elif task_name == "screen_gene_retrieval": + # Screen gene retrieval expects gene symbol (case-insensitive) + return 1.0 if user_answer.strip().upper() == ground_truth.strip().upper() else 0.0 + + elif task_name == "patient_gene_detection": + # Patient gene detection expects JSON with causal_gene list + # Ground truth is a comma-separated string or single gene ID + try: + if isinstance(user_answer, str): + try: + user_dict = json.loads(user_answer) + except json.JSONDecodeError: + import ast + + user_dict = ast.literal_eval(user_answer) + else: + user_dict = user_answer + + # Get predicted genes + predicted_genes = user_dict.get("causal_gene", []) + if not isinstance(predicted_genes, list): + predicted_genes = [predicted_genes] + + # Get ground truth genes (stored as comma-separated or single) + if "," in ground_truth: + true_genes = [g.strip() for g in ground_truth.split(",")] + else: + true_genes = [ground_truth] + + # Check for intersection + if predicted_genes and set(true_genes) & set(predicted_genes): + return 1.0 + else: + return 0.0 + + except Exception: + return 0.0 + + else: + raise ValueError(f"Unknown task: {task_name}") + + def get_instance(self, task_name: str, task_instance_id: int) -> dict[str, Any]: + """ + Get information about a specific instance + + Args: + task_name: Name of the task + task_instance_id: Task-specific instance ID + + Returns: + dict: Instance information including prompt, answer, etc. + """ + key = (task_name, task_instance_id) + if key not in self.instance_map: + raise ValueError(f"Instance not found: task={task_name}, task_instance_id={task_instance_id}") + + df_idx = self.instance_map[key] + row = self.df.iloc[df_idx] + + return { + "global_instance_id": row["instance_id"], + "task_instance_id": row["task_instance_id"], + "task_name": row["task_name"], + "split": row["split"], + "prompt": row["prompt"], + "answer": row["answer"], + } + + def list_tasks(self) -> list: + """Get list of all available tasks""" + return sorted(self.df["task_name"].unique().tolist()) + + def get_task_stats(self, task_name: str = None) -> dict[str, Any]: + """ + Get statistics for a task or all tasks + + Args: + task_name: Optional task name to filter by + + Returns: + dict: Statistics including counts by split + """ + if task_name: + task_df = self.df[self.df["task_name"] == task_name] + if len(task_df) == 0: + raise ValueError(f"Task not found: {task_name}") + else: + task_df = self.df + + stats = { + "total_instances": len(task_df), + "train_instances": len(task_df[task_df["split"] == "train"]), + "val_instances": len(task_df[task_df["split"] == "val"]), + } + + if not task_name: + stats["tasks"] = {} + for tn in self.list_tasks(): + stats["tasks"][tn] = self.get_task_stats(tn) + + return stats + + def batch_evaluate(self, evaluations: list) -> list: + """ + Evaluate multiple instances at once + + Args: + evaluations: List of tuples (task_name, task_instance_id, user_answer) + + Returns: + list: List of reward scores + """ + results = [] + for task_name, task_instance_id, user_answer in evaluations: + try: + score = self.evaluate(task_name, task_instance_id, user_answer) + results.append(score) + except Exception as e: + print(f"Error evaluating {task_name} instance {task_instance_id}: {e}") + results.append(0.0) + + return results + + def get_instances_by_task(self, task_name: str, split: str = None) -> pd.DataFrame: + """ + Get all instances for a specific task + + Args: + task_name: Name of the task + split: Optional split filter ('train' or 'val') + + Returns: + DataFrame with instances + """ + task_df = self.df[self.df["task_name"] == task_name] + + if split: + task_df = task_df[task_df["split"] == split] + + return task_df.copy() + + def __repr__(self): + return f"BiomniEval1(instances={len(self.df)}, tasks={self.df['task_name'].nunique()})" + + def __len__(self): + return len(self.df) + + +def main(): + """Demo usage of BiomniEval1""" + evaluator = BiomniEval1() + + print("\nAvailable tasks:") + for task in evaluator.list_tasks(): + print(f" - {task}") + + print("\nOverall statistics:") + stats = evaluator.get_task_stats() + print(f" Total instances: {stats['total_instances']}") + print(f" Train: {stats['train_instances']}, Val: {stats['val_instances']}") + + print("\nPer-task statistics:") + for task_name in evaluator.list_tasks(): + task_stats = evaluator.get_task_stats(task_name) + print( + f" {task_name}: {task_stats['total_instances']} total ({task_stats['train_instances']} train, {task_stats['val_instances']} val)" + ) + + # Example evaluation + print("\n" + "=" * 60) + print("Example evaluation:") + print("=" * 60) + + # Get first instance from gwas_variant_prioritization + first_instance = evaluator.df[evaluator.df["task_name"] == "gwas_variant_prioritization"].iloc[0] + task_name = first_instance["task_name"] + task_instance_id = first_instance["task_instance_id"] + ground_truth = first_instance["answer"] + + print(f"\nTask: {task_name}") + print(f"Task Instance ID: {task_instance_id}") + print(f"Ground truth: {ground_truth}") + print(f"Prompt preview: {first_instance['prompt'][:200]}...") + + # Test with correct answer + score = evaluator.evaluate(task_name, task_instance_id, ground_truth) + print(f"\nScore (correct answer '{ground_truth}'): {score}") + + # Test with wrong answer + score = evaluator.evaluate(task_name, task_instance_id, "wrong_answer") + print(f"Score (wrong answer 'wrong_answer'): {score}") + + # Batch evaluation example + print("\n" + "=" * 60) + print("Batch evaluation example:") + print("=" * 60) + batch_evals = [ + (task_name, task_instance_id, ground_truth), # Correct + (task_name, task_instance_id, "wrong"), # Wrong + ] + scores = evaluator.batch_evaluate(batch_evals) + print(f"Batch scores: {scores}") + + +if __name__ == "__main__": + main() diff --git a/BioScientist/agent_system/engines/v1_executor_backup/llm.py b/BioScientist/agent_system/engines/v1_executor_backup/llm.py new file mode 100644 index 0000000000000000000000000000000000000000..78745a8d4438e808e405ae4b394eb8c535571488 --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/llm.py @@ -0,0 +1,276 @@ +import os +from typing import TYPE_CHECKING, Literal, Optional + +from langchain_core.language_models.chat_models import BaseChatModel + +if TYPE_CHECKING: + from biomni.config import BiomniConfig + +SourceType = Literal["OpenAI", "AzureOpenAI", "Anthropic", "Ollama", "Gemini", "Bedrock", "Groq", "Custom"] +ALLOWED_SOURCES: set[str] = set(SourceType.__args__) + + +def get_llm( + model: str | None = None, + temperature: float | None = None, + stop_sequences: list[str] | None = None, + source: SourceType | None = None, + base_url: str | None = None, + api_key: str | None = None, + config: Optional["BiomniConfig"] = None, +) -> BaseChatModel: + """ + Get a language model instance based on the specified model name and source. + This function supports models from OpenAI, Azure OpenAI, Anthropic, Ollama, Gemini, Bedrock, and custom model serving. + Args: + model (str): The model name to use + temperature (float): Temperature setting for generation + stop_sequences (list): Sequences that will stop generation + source (str): Source provider: "OpenAI", "AzureOpenAI", "Anthropic", "Ollama", "Gemini", "Bedrock", or "Custom" + If None, will attempt to auto-detect from model name + base_url (str): The base URL for custom model serving (e.g., "http://localhost:8000/v1"), default is None + api_key (str): The API key for the custom llm + config (BiomniConfig): Optional configuration object. If provided, unspecified parameters will use config values + """ + # Use config values for any unspecified parameters + if config is not None: + if model is None: + model = config.llm_model + if temperature is None: + temperature = config.temperature + if source is None: + source = config.source + if base_url is None: + base_url = config.base_url + if api_key is None: + api_key = config.api_key or "EMPTY" + + # Use defaults if still not specified + if model is None: + model = "claude-3-5-sonnet-20241022" + if temperature is None: + temperature = 0.7 + if api_key is None: + api_key = "EMPTY" + # Auto-detect source from model name if not specified + if source is None: + env_source = os.getenv("LLM_SOURCE") + if env_source in ALLOWED_SOURCES: + source = env_source + else: + if model[:7] == "claude-": + source = "Anthropic" + elif model[:7] == "gpt-oss": + source = "Ollama" + elif model[:4] == "gpt-": + source = "OpenAI" + elif model.startswith("azure-"): + source = "AzureOpenAI" + elif model[:7] == "gemini-": + source = "Gemini" + elif "groq" in model.lower(): + source = "Groq" + elif base_url is not None: + source = "Custom" + elif "/" in model or any( + name in model.lower() + for name in [ + "llama", + "mistral", + "qwen", + "gemma", + "phi", + "dolphin", + "orca", + "vicuna", + "deepseek", + ] + ): + source = "Ollama" + elif model.startswith( + ("anthropic.claude-", "amazon.titan-", "meta.llama-", "mistral.", "cohere.", "ai21.", "us.") + ): + source = "Bedrock" + else: + raise ValueError("Unable to determine model source. Please specify 'source' parameter.") + + # Create appropriate model based on source + if source == "OpenAI": + try: + from langchain_openai import ChatOpenAI + except ImportError: + raise ImportError( # noqa: B904 + "langchain-openai package is required for OpenAI models. Install with: pip install langchain-openai" + ) + # Newer OpenAI models (e.g., gpt-5-*) require the Responses API and may reject + # legacy Chat Completions parameters like `stop`. Force Responses API when + # using gpt-5 models to avoid 400 errors such as: "Unsupported parameter: 'stop'". + use_responses = model.startswith("gpt-5") + + if use_responses: + # Define a minimal subclass that drops the `stop` field when using the + # Responses API, since certain models (gpt-5-*) reject it entirely. + class _ChatOpenAIResponsesNoStop(ChatOpenAI): + def _get_request_payload(self, input_, *, stop=None, **kwargs): # type: ignore[override] + payload = super()._get_request_payload(input_, stop=stop, **kwargs) + try: + # If this call will use the Responses API, drop `stop` to avoid 400s. + if hasattr(self, "_use_responses_api") and self._use_responses_api(payload): # type: ignore[attr-defined] + payload.pop("stop", None) + # Also drop temperature for gpt-5 models as they only support default value + payload.pop("temperature", None) + except Exception: + # Be conservative: if anything goes wrong, still remove `stop` and `temperature`. + payload.pop("stop", None) + payload.pop("temperature", None) + return payload + + return _ChatOpenAIResponsesNoStop( + model=model, + temperature=1, # Set to default value for gpt-5, will be removed in payload + stop_sequences=stop_sequences, + use_responses_api=True, + output_version="v0", + ) + else: + return ChatOpenAI( + model=model, + temperature=temperature, + stop_sequences=stop_sequences, + ) + + elif source == "AzureOpenAI": + try: + from langchain_openai import AzureChatOpenAI + except ImportError: + raise ImportError( # noqa: B904 + "langchain-openai package is required for Azure OpenAI models. Install with: pip install langchain-openai" + ) + API_VERSION = "2024-12-01-preview" + model = model.replace("azure-", "") + return AzureChatOpenAI( + openai_api_key=os.getenv("OPENAI_API_KEY"), + azure_endpoint=os.getenv("OPENAI_ENDPOINT"), + azure_deployment=model, + openai_api_version=API_VERSION, + temperature=temperature, + ) + + elif source == "Anthropic": + try: + from langchain_anthropic import ChatAnthropic + except ImportError: + raise ImportError( # noqa: B904 + "langchain-anthropic package is required for Anthropic models. Install with: pip install langchain-anthropic" + ) + + # Ensure ANTHROPIC_API_KEY is loaded from bash_profile if not in environment + if not os.environ.get("ANTHROPIC_API_KEY"): + try: + import subprocess + + result = subprocess.run( + ["bash", "-c", "source ~/.bash_profile 2>/dev/null && echo $ANTHROPIC_API_KEY"], + capture_output=True, + text=True, + timeout=5, + ) + if result.stdout.strip(): + os.environ["ANTHROPIC_API_KEY"] = result.stdout.strip() + print("✓ Loaded ANTHROPIC_API_KEY from ~/.bash_profile") + except Exception as e: + print(f"Note: Could not load ANTHROPIC_API_KEY from bash_profile: {e}") + + # Newer Claude models can reject an explicit temperature parameter. + # Omit it and use provider defaults to keep compatibility. + return ChatAnthropic( + model=model, + max_tokens=8192, + stop_sequences=stop_sequences, + ) + + elif source == "Gemini": + # If you want to use ChatGoogleGenerativeAI, you need to pass the stop sequences upon invoking the model. + # return ChatGoogleGenerativeAI( + # model=model, + # temperature=temperature, + # google_api_key=api_key, + # ) + try: + from langchain_openai import ChatOpenAI + except ImportError: + raise ImportError( # noqa: B904 + "langchain-openai package is required for Gemini models. Install with: pip install langchain-openai" + ) + return ChatOpenAI( + model=model, + temperature=temperature, + api_key=os.getenv("GEMINI_API_KEY"), + base_url="https://generativelanguage.googleapis.com/v1beta/openai/", + stop_sequences=stop_sequences, + ) + + elif source == "Groq": + try: + from langchain_openai import ChatOpenAI + except ImportError: + raise ImportError( # noqa: B904 + "langchain-openai package is required for Groq models. Install with: pip install langchain-openai" + ) + return ChatOpenAI( + model=model, + temperature=temperature, + api_key=os.getenv("GROQ_API_KEY"), + base_url="https://api.groq.com/openai/v1", + stop_sequences=stop_sequences, + ) + + elif source == "Ollama": + try: + from langchain_ollama import ChatOllama + except ImportError: + raise ImportError( # noqa: B904 + "langchain-ollama package is required for Ollama models. Install with: pip install langchain-ollama" + ) + return ChatOllama( + model=model, + temperature=temperature, + ) + + elif source == "Bedrock": + try: + from langchain_aws import ChatBedrock + except ImportError: + raise ImportError( # noqa: B904 + "langchain-aws package is required for Bedrock models. Install with: pip install langchain-aws" + ) + return ChatBedrock( + model=model, + temperature=temperature, + stop_sequences=stop_sequences, + region_name=os.getenv("AWS_REGION", "us-east-1"), + ) + + elif source == "Custom": + try: + from langchain_openai import ChatOpenAI + except ImportError: + raise ImportError( # noqa: B904 + "langchain-openai package is required for custom models. Install with: pip install langchain-openai" + ) + # Custom LLM serving such as SGLang. Must expose an openai compatible API. + assert base_url is not None, "base_url must be provided for customly served LLMs" + llm = ChatOpenAI( + model=model, + temperature=temperature, + max_tokens=8192, + stop_sequences=stop_sequences, + base_url=base_url, + api_key=api_key, + ) + return llm + + else: + raise ValueError( + f"Invalid source: {source}. Valid options are 'OpenAI', 'AzureOpenAI', 'Anthropic', 'Gemini', 'Groq', 'Bedrock', or 'Ollama'" + ) diff --git a/BioScientist/agent_system/engines/v1_executor_backup/mcp_config_bioscientist_generated.yaml b/BioScientist/agent_system/engines/v1_executor_backup/mcp_config_bioscientist_generated.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d183d0c096b378d5c54d2a9558ce5ba18f03349c --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/mcp_config_bioscientist_generated.yaml @@ -0,0 +1,3881 @@ +mcp_servers: + biosci_abnumber: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_abnumber/app/abnumber_shim_server.py"] + description: "BioScientist MCP server for abnumber" + biosci_abricate: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_abricate/app/abricate_shim_server.py"] + description: "BioScientist MCP server for abricate" + biosci_abricate: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_abricate/app/abricate_shim_server.py"] + description: "BioScientist MCP server for abricate" + biosci_abundancebin: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_abundancebin/app/abundancebin_shim_server.py"] + description: "BioScientist MCP server for abundancebin" + biosci_abundancebin: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_abundancebin/app/abundancebin_shim_server.py"] + description: "BioScientist MCP server for abundancebin" + biosci_abyss: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_abyss/app/abyss_shim_server.py"] + description: "BioScientist MCP server for abyss" + biosci_abyss: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_abyss/app/abyss_shim_server.py"] + description: "BioScientist MCP server for abyss" + biosci_adapterremoval: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_adapterremoval/app/adapterremoval_shim_server.py"] + description: "BioScientist MCP server for adapterremoval" + biosci_alfred: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_alfred/app/alfred_shim_server.py"] + description: "BioScientist MCP server for alfred" + biosci_anansescanpy: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_anansescanpy/app/anansescanpy_shim_server.py"] + description: "BioScientist MCP server for anansescanpy" + biosci_anarci: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_anarci/app/anarci_shim_server.py"] + description: "BioScientist MCP server for anarci" + biosci_aragorn: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_aragorn/app/aragorn_shim_server.py"] + description: "BioScientist MCP server for aragorn" + biosci_aragorn: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_aragorn/app/aragorn_shim_server.py"] + description: "BioScientist MCP server for aragorn" + biosci_aria2: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_aria2/app/aria2_shim_server.py"] + description: "BioScientist MCP server for aria2" + biosci_ariba: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_ariba/app/ariba_shim_server.py"] + description: "BioScientist MCP server for ariba" + biosci_arriba: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_arriba/app/arriba_shim_server.py"] + description: "BioScientist MCP server for arriba" + biosci_art: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_art/app/art_shim_server.py"] + description: "BioScientist MCP server for art" + biosci_arvados_cwl_runner: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_arvados-cwl-runner/app/arvados-cwl-runner_shim_server.py"] + description: "BioScientist MCP server for arvados-cwl-runner" + biosci_arvados_python_client: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_arvados-python-client/app/arvados-python-client_shim_server.py"] + description: "BioScientist MCP server for arvados-python-client" + biosci_atropos: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_atropos/app/atropos_shim_server.py"] + description: "BioScientist MCP server for atropos" + biosci_augustus: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_augustus/app/augustus_shim_server.py"] + description: "BioScientist MCP server for augustus" + biosci_auspice: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_auspice/app/auspice_shim_server.py"] + description: "BioScientist MCP server for auspice" + biosci_bactopia: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bactopia/app/bactopia_shim_server.py"] + description: "BioScientist MCP server for bactopia" + biosci_bamtools: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bamtools/app/bamtools_shim_server.py"] + description: "BioScientist MCP server for bamtools" + biosci_barrnap: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_barrnap/app/barrnap_shim_server.py"] + description: "BioScientist MCP server for barrnap" + biosci_barrnap: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_barrnap/app/barrnap_shim_server.py"] + description: "BioScientist MCP server for barrnap" + biosci_bbmap: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bbmap/app/bbmap_shim_server.py"] + description: "BioScientist MCP server for bbmap" + biosci_bcbio_gff: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bcbio-gff/app/bcbio-gff_shim_server.py"] + description: "BioScientist MCP server for bcbio-gff" + biosci_bcbio_nextgen: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bcbio-nextgen/app/bcbio-nextgen_shim_server.py"] + description: "BioScientist MCP server for bcbio-nextgen" + biosci_bcftools: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bcftools/app/bcftools_shim_server.py"] + description: "BioScientist MCP server for bcftools" + biosci_bedops: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bedops/app/bedops_shim_server.py"] + description: "BioScientist MCP server for bedops" + biosci_bedops: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bedops/app/bedops_shim_server.py"] + description: "BioScientist MCP server for bedops" + biosci_bedtools: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bedtools/app/bedtools_shim_server.py"] + description: "BioScientist MCP server for bedtools" + biosci_bin2cell: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bin2cell/app/bin2cell_shim_server.py"] + description: "BioScientist MCP server for bin2cell" + biosci_bioawk: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioawk/app/bioawk_shim_server.py"] + description: "BioScientist MCP server for bioawk" + biosci_bioawk: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioawk/app/bioawk_shim_server.py"] + description: "BioScientist MCP server for bioawk" + biosci_biobambam: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_biobambam/app/biobambam_shim_server.py"] + description: "BioScientist MCP server for biobambam" + biosci_bioblend: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioblend/app/bioblend_shim_server.py"] + description: "BioScientist MCP server for bioblend" + biosci_bioconda_repodata_patches: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconda-repodata-patches/app/bioconda-repodata-patches_shim_server.py"] + description: "BioScientist MCP server for bioconda-repodata-patches" + biosci_bioconda_utils: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconda-utils/app/bioconda-utils_shim_server.py"] + description: "BioScientist MCP server for bioconda-utils" + biosci_bioconductor_affy: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-affy/app/bioconductor-affy_shim_server.py"] + description: "BioScientist MCP server for bioconductor-affy" + biosci_bioconductor_affyio: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-affyio/app/bioconductor-affyio_shim_server.py"] + description: "BioScientist MCP server for bioconductor-affyio" + biosci_bioconductor_alabaster: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-alabaster/app/bioconductor-alabaster_shim_server.py"] + description: "BioScientist MCP server for bioconductor-alabaster" + biosci_bioconductor_alabaster_sfe: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-alabaster.sfe/app/bioconductor-alabaster.sfe_shim_server.py"] + description: "BioScientist MCP server for bioconductor-alabaster.sfe" + biosci_bioconductor_alabaster_spatial: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-alabaster.spatial/app/bioconductor-alabaster.spatial_shim_server.py"] + description: "BioScientist MCP server for bioconductor-alabaster.spatial" + biosci_bioconductor_ancombc: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ancombc/app/bioconductor-ancombc_shim_server.py"] + description: "BioScientist MCP server for bioconductor-ancombc" + biosci_bioconductor_annotate: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-annotate/app/bioconductor-annotate_shim_server.py"] + description: "BioScientist MCP server for bioconductor-annotate" + biosci_bioconductor_annotationdbi: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-annotationdbi/app/bioconductor-annotationdbi_shim_server.py"] + description: "BioScientist MCP server for bioconductor-annotationdbi" + biosci_bioconductor_annotationfilter: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-annotationfilter/app/bioconductor-annotationfilter_shim_server.py"] + description: "BioScientist MCP server for bioconductor-annotationfilter" + biosci_bioconductor_apeglm: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-apeglm/app/bioconductor-apeglm_shim_server.py"] + description: "BioScientist MCP server for bioconductor-apeglm" + biosci_bioconductor_apl: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-apl/app/bioconductor-apl_shim_server.py"] + description: "BioScientist MCP server for bioconductor-apl" + biosci_bioconductor_awaggregator: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-awaggregator/app/bioconductor-awaggregator_shim_server.py"] + description: "BioScientist MCP server for bioconductor-awaggregator" + biosci_bioconductor_banksy: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-banksy/app/bioconductor-banksy_shim_server.py"] + description: "BioScientist MCP server for bioconductor-banksy" + biosci_bioconductor_beachmat: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-beachmat/app/bioconductor-beachmat_shim_server.py"] + description: "BioScientist MCP server for bioconductor-beachmat" + biosci_bioconductor_benchdamic: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-benchdamic/app/bioconductor-benchdamic_shim_server.py"] + description: "BioScientist MCP server for bioconductor-benchdamic" + biosci_bioconductor_biobase: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-biobase/app/bioconductor-biobase_shim_server.py"] + description: "BioScientist MCP server for bioconductor-biobase" + biosci_bioconductor_biocbaseutils: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-biocbaseutils/app/bioconductor-biocbaseutils_shim_server.py"] + description: "BioScientist MCP server for bioconductor-biocbaseutils" + biosci_bioconductor_biocfilecache: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-biocfilecache/app/bioconductor-biocfilecache_shim_server.py"] + description: "BioScientist MCP server for bioconductor-biocfilecache" + biosci_bioconductor_biocgenerics: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-biocgenerics/app/bioconductor-biocgenerics_shim_server.py"] + description: "BioScientist MCP server for bioconductor-biocgenerics" + biosci_bioconductor_biocio: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-biocio/app/bioconductor-biocio_shim_server.py"] + description: "BioScientist MCP server for bioconductor-biocio" + biosci_bioconductor_biocneighbors: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-biocneighbors/app/bioconductor-biocneighbors_shim_server.py"] + description: "BioScientist MCP server for bioconductor-biocneighbors" + biosci_bioconductor_biocparallel: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-biocparallel/app/bioconductor-biocparallel_shim_server.py"] + description: "BioScientist MCP server for bioconductor-biocparallel" + biosci_bioconductor_biocsingular: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-biocsingular/app/bioconductor-biocsingular_shim_server.py"] + description: "BioScientist MCP server for bioconductor-biocsingular" + biosci_bioconductor_biomart: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-biomart/app/bioconductor-biomart_shim_server.py"] + description: "BioScientist MCP server for bioconductor-biomart" + biosci_bioconductor_biomformat: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-biomformat/app/bioconductor-biomformat_shim_server.py"] + description: "BioScientist MCP server for bioconductor-biomformat" + biosci_bioconductor_biomformat: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-biomformat/app/bioconductor-biomformat_shim_server.py"] + description: "BioScientist MCP server for bioconductor-biomformat" + biosci_bioconductor_biostrings: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-biostrings/app/bioconductor-biostrings_shim_server.py"] + description: "BioScientist MCP server for bioconductor-biostrings" + biosci_bioconductor_biovizbase: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-biovizbase/app/bioconductor-biovizbase_shim_server.py"] + description: "BioScientist MCP server for bioconductor-biovizbase" + biosci_bioconductor_blase: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-blase/app/bioconductor-blase_shim_server.py"] + description: "BioScientist MCP server for bioconductor-blase" + biosci_bioconductor_bluster: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-bluster/app/bioconductor-bluster_shim_server.py"] + description: "BioScientist MCP server for bioconductor-bluster" + biosci_bioconductor_bsgenome: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-bsgenome/app/bioconductor-bsgenome_shim_server.py"] + description: "BioScientist MCP server for bioconductor-bsgenome" + biosci_bioconductor_bsgenome: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-bsgenome/app/bioconductor-bsgenome_shim_server.py"] + description: "BioScientist MCP server for bioconductor-bsgenome" + biosci_bioconductor_bulksignalr: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-bulksignalr/app/bioconductor-bulksignalr_shim_server.py"] + description: "BioScientist MCP server for bioconductor-bulksignalr" + biosci_bioconductor_cardspa: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-cardspa/app/bioconductor-cardspa_shim_server.py"] + description: "BioScientist MCP server for bioconductor-cardspa" + biosci_bioconductor_catscradle: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-catscradle/app/bioconductor-catscradle_shim_server.py"] + description: "BioScientist MCP server for bioconductor-catscradle" + biosci_bioconductor_cdi: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-cdi/app/bioconductor-cdi_shim_server.py"] + description: "BioScientist MCP server for bioconductor-cdi" + biosci_bioconductor_cellhashr: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-cellhashr/app/bioconductor-cellhashr_shim_server.py"] + description: "BioScientist MCP server for bioconductor-cellhashr" + biosci_bioconductor_cellid: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-cellid/app/bioconductor-cellid_shim_server.py"] + description: "BioScientist MCP server for bioconductor-cellid" + biosci_bioconductor_cellmigration: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-cellmigration/app/bioconductor-cellmigration_shim_server.py"] + description: "BioScientist MCP server for bioconductor-cellmigration" + biosci_bioconductor_clusterfoldsimilarity: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-clusterfoldsimilarity/app/bioconductor-clusterfoldsimilarity_shim_server.py"] + description: "BioScientist MCP server for bioconductor-clusterfoldsimilarity" + biosci_bioconductor_clusterprofiler: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-clusterprofiler/app/bioconductor-clusterprofiler_shim_server.py"] + description: "BioScientist MCP server for bioconductor-clusterprofiler" + biosci_bioconductor_clustifyr: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-clustifyr/app/bioconductor-clustifyr_shim_server.py"] + description: "BioScientist MCP server for bioconductor-clustifyr" + biosci_bioconductor_clustsignal: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-clustsignal/app/bioconductor-clustsignal_shim_server.py"] + description: "BioScientist MCP server for bioconductor-clustsignal" + biosci_bioconductor_complexheatmap: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-complexheatmap/app/bioconductor-complexheatmap_shim_server.py"] + description: "BioScientist MCP server for bioconductor-complexheatmap" + biosci_bioconductor_concordexr: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-concordexr/app/bioconductor-concordexr_shim_server.py"] + description: "BioScientist MCP server for bioconductor-concordexr" + biosci_bioconductor_cotan: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-cotan/app/bioconductor-cotan_shim_server.py"] + description: "BioScientist MCP server for bioconductor-cotan" + biosci_bioconductor_csoa: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-csoa/app/bioconductor-csoa_shim_server.py"] + description: "BioScientist MCP server for bioconductor-csoa" + biosci_bioconductor_ctsv: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ctsv/app/bioconductor-ctsv_shim_server.py"] + description: "BioScientist MCP server for bioconductor-ctsv" + biosci_bioconductor_curatedatlasqueryr: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-curatedatlasqueryr/app/bioconductor-curatedatlasqueryr_shim_server.py"] + description: "BioScientist MCP server for bioconductor-curatedatlasqueryr" + biosci_bioconductor_cytomapper: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-cytomapper/app/bioconductor-cytomapper_shim_server.py"] + description: "BioScientist MCP server for bioconductor-cytomapper" + biosci_bioconductor_dada2: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-dada2/app/bioconductor-dada2_shim_server.py"] + description: "BioScientist MCP server for bioconductor-dada2" + biosci_bioconductor_data_packages: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-data-packages/app/bioconductor-data-packages_shim_server.py"] + description: "BioScientist MCP server for bioconductor-data-packages" + biosci_bioconductor_decipher: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-decipher/app/bioconductor-decipher_shim_server.py"] + description: "BioScientist MCP server for bioconductor-decipher" + biosci_bioconductor_decontam: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-decontam/app/bioconductor-decontam_shim_server.py"] + description: "BioScientist MCP server for bioconductor-decontam" + biosci_bioconductor_decontx: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-decontx/app/bioconductor-decontx_shim_server.py"] + description: "BioScientist MCP server for bioconductor-decontx" + biosci_bioconductor_deconvobuddies: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-deconvobuddies/app/bioconductor-deconvobuddies_shim_server.py"] + description: "BioScientist MCP server for bioconductor-deconvobuddies" + biosci_bioconductor_delayedmatrixstats: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-delayedmatrixstats/app/bioconductor-delayedmatrixstats_shim_server.py"] + description: "BioScientist MCP server for bioconductor-delayedmatrixstats" + biosci_bioconductor_deseq2: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-deseq2/app/bioconductor-deseq2_shim_server.py"] + description: "BioScientist MCP server for bioconductor-deseq2" + biosci_bioconductor_despace: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-despace/app/bioconductor-despace_shim_server.py"] + description: "BioScientist MCP server for bioconductor-despace" + biosci_bioconductor_diffbind: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-diffbind/app/bioconductor-diffbind_shim_server.py"] + description: "BioScientist MCP server for bioconductor-diffbind" + biosci_bioconductor_dino: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-dino/app/bioconductor-dino_shim_server.py"] + description: "BioScientist MCP server for bioconductor-dino" + biosci_bioconductor_dirichletmultinomial: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-dirichletmultinomial/app/bioconductor-dirichletmultinomial_shim_server.py"] + description: "BioScientist MCP server for bioconductor-dirichletmultinomial" + biosci_bioconductor_dirichletmultinomial: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-dirichletmultinomial/app/bioconductor-dirichletmultinomial_shim_server.py"] + description: "BioScientist MCP server for bioconductor-dirichletmultinomial" + biosci_bioconductor_dnacopy: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-dnacopy/app/bioconductor-dnacopy_shim_server.py"] + description: "BioScientist MCP server for bioconductor-dnacopy" + biosci_bioconductor_dnacopy: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-dnacopy/app/bioconductor-dnacopy_shim_server.py"] + description: "BioScientist MCP server for bioconductor-dnacopy" + biosci_bioconductor_dose: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-dose/app/bioconductor-dose_shim_server.py"] + description: "BioScientist MCP server for bioconductor-dose" + biosci_bioconductor_edger: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-edger/app/bioconductor-edger_shim_server.py"] + description: "BioScientist MCP server for bioconductor-edger" + biosci_bioconductor_edger: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-edger/app/bioconductor-edger_shim_server.py"] + description: "BioScientist MCP server for bioconductor-edger" + biosci_bioconductor_ensembldb: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ensembldb/app/bioconductor-ensembldb_shim_server.py"] + description: "BioScientist MCP server for bioconductor-ensembldb" + biosci_bioconductor_erma: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-erma/app/bioconductor-erma_shim_server.py"] + description: "BioScientist MCP server for bioconductor-erma" + biosci_bioconductor_escher: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-escher/app/bioconductor-escher_shim_server.py"] + description: "BioScientist MCP server for bioconductor-escher" + biosci_bioconductor_experimentsubset: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-experimentsubset/app/bioconductor-experimentsubset_shim_server.py"] + description: "BioScientist MCP server for bioconductor-experimentsubset" + biosci_bioconductor_fgsea: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-fgsea/app/bioconductor-fgsea_shim_server.py"] + description: "BioScientist MCP server for bioconductor-fgsea" + biosci_bioconductor_genefilter: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-genefilter/app/bioconductor-genefilter_shim_server.py"] + description: "BioScientist MCP server for bioconductor-genefilter" + biosci_bioconductor_genefilter: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-genefilter/app/bioconductor-genefilter_shim_server.py"] + description: "BioScientist MCP server for bioconductor-genefilter" + biosci_bioconductor_geneplotter: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-geneplotter/app/bioconductor-geneplotter_shim_server.py"] + description: "BioScientist MCP server for bioconductor-geneplotter" + biosci_bioconductor_geneplotter: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-geneplotter/app/bioconductor-geneplotter_shim_server.py"] + description: "BioScientist MCP server for bioconductor-geneplotter" + biosci_bioconductor_genomeinfodb: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-genomeinfodb/app/bioconductor-genomeinfodb_shim_server.py"] + description: "BioScientist MCP server for bioconductor-genomeinfodb" + biosci_bioconductor_genomeinfodbdata: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-genomeinfodbdata/app/bioconductor-genomeinfodbdata_shim_server.py"] + description: "BioScientist MCP server for bioconductor-genomeinfodbdata" + biosci_bioconductor_genomeinfodbdata: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-genomeinfodbdata/app/bioconductor-genomeinfodbdata_shim_server.py"] + description: "BioScientist MCP server for bioconductor-genomeinfodbdata" + biosci_bioconductor_genomicalignments: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-genomicalignments/app/bioconductor-genomicalignments_shim_server.py"] + description: "BioScientist MCP server for bioconductor-genomicalignments" + biosci_bioconductor_genomicalignments: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-genomicalignments/app/bioconductor-genomicalignments_shim_server.py"] + description: "BioScientist MCP server for bioconductor-genomicalignments" + biosci_bioconductor_genomicfeatures: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-genomicfeatures/app/bioconductor-genomicfeatures_shim_server.py"] + description: "BioScientist MCP server for bioconductor-genomicfeatures" + biosci_bioconductor_genomicfeatures: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-genomicfeatures/app/bioconductor-genomicfeatures_shim_server.py"] + description: "BioScientist MCP server for bioconductor-genomicfeatures" + biosci_bioconductor_genomicranges: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-genomicranges/app/bioconductor-genomicranges_shim_server.py"] + description: "BioScientist MCP server for bioconductor-genomicranges" + biosci_bioconductor_geomxtools: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-geomxtools/app/bioconductor-geomxtools_shim_server.py"] + description: "BioScientist MCP server for bioconductor-geomxtools" + biosci_bioconductor_ggsc: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ggsc/app/bioconductor-ggsc_shim_server.py"] + description: "BioScientist MCP server for bioconductor-ggsc" + biosci_bioconductor_ggspavis: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ggspavis/app/bioconductor-ggspavis_shim_server.py"] + description: "BioScientist MCP server for bioconductor-ggspavis" + biosci_bioconductor_ggtree: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-ggtree/app/bioconductor-ggtree_shim_server.py"] + description: "BioScientist MCP server for bioconductor-ggtree" + biosci_bioconductor_glmgampoi: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-glmgampoi/app/bioconductor-glmgampoi_shim_server.py"] + description: "BioScientist MCP server for bioconductor-glmgampoi" + biosci_bioconductor_go_db: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-go.db/app/bioconductor-go.db_shim_server.py"] + description: "BioScientist MCP server for bioconductor-go.db" + biosci_bioconductor_go_db: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-go.db/app/bioconductor-go.db_shim_server.py"] + description: "BioScientist MCP server for bioconductor-go.db" + biosci_bioconductor_gosemsim: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-gosemsim/app/bioconductor-gosemsim_shim_server.py"] + description: "BioScientist MCP server for bioconductor-gosemsim" + biosci_bioconductor_gosemsim: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-gosemsim/app/bioconductor-gosemsim_shim_server.py"] + description: "BioScientist MCP server for bioconductor-gosemsim" + biosci_bioconductor_graph: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-graph/app/bioconductor-graph_shim_server.py"] + description: "BioScientist MCP server for bioconductor-graph" + biosci_bioconductor_graph: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-graph/app/bioconductor-graph_shim_server.py"] + description: "BioScientist MCP server for bioconductor-graph" + biosci_bioconductor_gsva: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-gsva/app/bioconductor-gsva_shim_server.py"] + description: "BioScientist MCP server for bioconductor-gsva" + biosci_bioconductor_hcatonsildata: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-hcatonsildata/app/bioconductor-hcatonsildata_shim_server.py"] + description: "BioScientist MCP server for bioconductor-hcatonsildata" + biosci_bioconductor_hdf5array: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-hdf5array/app/bioconductor-hdf5array_shim_server.py"] + description: "BioScientist MCP server for bioconductor-hdf5array" + biosci_bioconductor_hoodscanr: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-hoodscanr/app/bioconductor-hoodscanr_shim_server.py"] + description: "BioScientist MCP server for bioconductor-hoodscanr" + biosci_bioconductor_humanhippocampus2024: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-humanhippocampus2024/app/bioconductor-humanhippocampus2024_shim_server.py"] + description: "BioScientist MCP server for bioconductor-humanhippocampus2024" + biosci_bioconductor_imcdatasets: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-imcdatasets/app/bioconductor-imcdatasets_shim_server.py"] + description: "BioScientist MCP server for bioconductor-imcdatasets" + biosci_bioconductor_imcrtools: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-imcrtools/app/bioconductor-imcrtools_shim_server.py"] + description: "BioScientist MCP server for bioconductor-imcrtools" + biosci_bioconductor_impute: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-impute/app/bioconductor-impute_shim_server.py"] + description: "BioScientist MCP server for bioconductor-impute" + biosci_bioconductor_infercnv: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-infercnv/app/bioconductor-infercnv_shim_server.py"] + description: "BioScientist MCP server for bioconductor-infercnv" + biosci_bioconductor_interactivedisplaybase: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-interactivedisplaybase/app/bioconductor-interactivedisplaybase_shim_server.py"] + description: "BioScientist MCP server for bioconductor-interactivedisplaybase" + biosci_bioconductor_iranges: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-iranges/app/bioconductor-iranges_shim_server.py"] + description: "BioScientist MCP server for bioconductor-iranges" + biosci_bioconductor_iranges: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-iranges/app/bioconductor-iranges_shim_server.py"] + description: "BioScientist MCP server for bioconductor-iranges" + biosci_bioconductor_irisfgm: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-irisfgm/app/bioconductor-irisfgm_shim_server.py"] + description: "BioScientist MCP server for bioconductor-irisfgm" + biosci_bioconductor_jazzpanda: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-jazzpanda/app/bioconductor-jazzpanda_shim_server.py"] + description: "BioScientist MCP server for bioconductor-jazzpanda" + biosci_bioconductor_keggrest: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-keggrest/app/bioconductor-keggrest_shim_server.py"] + description: "BioScientist MCP server for bioconductor-keggrest" + biosci_bioconductor_keggrest: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-keggrest/app/bioconductor-keggrest_shim_server.py"] + description: "BioScientist MCP server for bioconductor-keggrest" + biosci_bioconductor_limma: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-limma/app/bioconductor-limma_shim_server.py"] + description: "BioScientist MCP server for bioconductor-limma" + biosci_bioconductor_limma: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-limma/app/bioconductor-limma_shim_server.py"] + description: "BioScientist MCP server for bioconductor-limma" + biosci_bioconductor_lisaclust: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-lisaclust/app/bioconductor-lisaclust_shim_server.py"] + description: "BioScientist MCP server for bioconductor-lisaclust" + biosci_bioconductor_mastr: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-mastr/app/bioconductor-mastr_shim_server.py"] + description: "BioScientist MCP server for bioconductor-mastr" + biosci_bioconductor_matrixgenerics: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-matrixgenerics/app/bioconductor-matrixgenerics_shim_server.py"] + description: "BioScientist MCP server for bioconductor-matrixgenerics" + biosci_bioconductor_merfishdata: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-merfishdata/app/bioconductor-merfishdata_shim_server.py"] + description: "BioScientist MCP server for bioconductor-merfishdata" + biosci_bioconductor_metabolomicsworkbenchr: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-metabolomicsworkbenchr/app/bioconductor-metabolomicsworkbenchr_shim_server.py"] + description: "BioScientist MCP server for bioconductor-metabolomicsworkbenchr" + biosci_bioconductor_metapod: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-metapod/app/bioconductor-metapod_shim_server.py"] + description: "BioScientist MCP server for bioconductor-metapod" + biosci_bioconductor_mia: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-mia/app/bioconductor-mia_shim_server.py"] + description: "BioScientist MCP server for bioconductor-mia" + biosci_bioconductor_moleculeexperiment: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-moleculeexperiment/app/bioconductor-moleculeexperiment_shim_server.py"] + description: "BioScientist MCP server for bioconductor-moleculeexperiment" + biosci_bioconductor_mosim: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-mosim/app/bioconductor-mosim_shim_server.py"] + description: "BioScientist MCP server for bioconductor-mosim" + biosci_bioconductor_mousegastrulationdata: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-mousegastrulationdata/app/bioconductor-mousegastrulationdata_shim_server.py"] + description: "BioScientist MCP server for bioconductor-mousegastrulationdata" + biosci_bioconductor_mspurity: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-mspurity/app/bioconductor-mspurity_shim_server.py"] + description: "BioScientist MCP server for bioconductor-mspurity" + biosci_bioconductor_multiassayexperiment: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-multiassayexperiment/app/bioconductor-multiassayexperiment_shim_server.py"] + description: "BioScientist MCP server for bioconductor-multiassayexperiment" + biosci_bioconductor_multtest: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-multtest/app/bioconductor-multtest_shim_server.py"] + description: "BioScientist MCP server for bioconductor-multtest" + biosci_bioconductor_multtest: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-multtest/app/bioconductor-multtest_shim_server.py"] + description: "BioScientist MCP server for bioconductor-multtest" + biosci_bioconductor_nebulosa: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-nebulosa/app/bioconductor-nebulosa_shim_server.py"] + description: "BioScientist MCP server for bioconductor-nebulosa" + biosci_bioconductor_nnsvg: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-nnsvg/app/bioconductor-nnsvg_shim_server.py"] + description: "BioScientist MCP server for bioconductor-nnsvg" + biosci_bioconductor_noiseq: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-noiseq/app/bioconductor-noiseq_shim_server.py"] + description: "BioScientist MCP server for bioconductor-noiseq" + biosci_bioconductor_noiseq: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-noiseq/app/bioconductor-noiseq_shim_server.py"] + description: "BioScientist MCP server for bioconductor-noiseq" + biosci_bioconductor_orfhunter: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-orfhunter/app/bioconductor-orfhunter_shim_server.py"] + description: "BioScientist MCP server for bioconductor-orfhunter" + biosci_bioconductor_org_ce_eg_db: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-org.ce.eg.db/app/bioconductor-org.ce.eg.db_shim_server.py"] + description: "BioScientist MCP server for bioconductor-org.ce.eg.db" + biosci_bioconductor_org_hs_eg_db: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-org.hs.eg.db/app/bioconductor-org.hs.eg.db_shim_server.py"] + description: "BioScientist MCP server for bioconductor-org.hs.eg.db" + biosci_bioconductor_org_hs_eg_db: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-org.hs.eg.db/app/bioconductor-org.hs.eg.db_shim_server.py"] + description: "BioScientist MCP server for bioconductor-org.hs.eg.db" + biosci_bioconductor_org_mm_eg_db: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-org.mm.eg.db/app/bioconductor-org.mm.eg.db_shim_server.py"] + description: "BioScientist MCP server for bioconductor-org.mm.eg.db" + biosci_bioconductor_partcnv: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-partcnv/app/bioconductor-partcnv_shim_server.py"] + description: "BioScientist MCP server for bioconductor-partcnv" + biosci_bioconductor_phemd: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-phemd/app/bioconductor-phemd_shim_server.py"] + description: "BioScientist MCP server for bioconductor-phemd" + biosci_bioconductor_phyloseq: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-phyloseq/app/bioconductor-phyloseq_shim_server.py"] + description: "BioScientist MCP server for bioconductor-phyloseq" + biosci_bioconductor_pipecomp: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-pipecomp/app/bioconductor-pipecomp_shim_server.py"] + description: "BioScientist MCP server for bioconductor-pipecomp" + biosci_bioconductor_poem: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-poem/app/bioconductor-poem_shim_server.py"] + description: "BioScientist MCP server for bioconductor-poem" + biosci_bioconductor_preprocesscore: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-preprocesscore/app/bioconductor-preprocesscore_shim_server.py"] + description: "BioScientist MCP server for bioconductor-preprocesscore" + biosci_bioconductor_preprocesscore: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-preprocesscore/app/bioconductor-preprocesscore_shim_server.py"] + description: "BioScientist MCP server for bioconductor-preprocesscore" + biosci_bioconductor_proteomicsannotationhubdata: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-proteomicsannotationhubdata/app/bioconductor-proteomicsannotationhubdata_shim_server.py"] + description: "BioScientist MCP server for bioconductor-proteomicsannotationhubdata" + biosci_bioconductor_protgenerics: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-protgenerics/app/bioconductor-protgenerics_shim_server.py"] + description: "BioScientist MCP server for bioconductor-protgenerics" + biosci_bioconductor_protgenerics: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-protgenerics/app/bioconductor-protgenerics_shim_server.py"] + description: "BioScientist MCP server for bioconductor-protgenerics" + biosci_bioconductor_qvalue: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-qvalue/app/bioconductor-qvalue_shim_server.py"] + description: "BioScientist MCP server for bioconductor-qvalue" + biosci_bioconductor_rbgl: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-rbgl/app/bioconductor-rbgl_shim_server.py"] + description: "BioScientist MCP server for bioconductor-rbgl" + biosci_bioconductor_reactomegsa_data: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-reactomegsa.data/app/bioconductor-reactomegsa.data_shim_server.py"] + description: "BioScientist MCP server for bioconductor-reactomegsa.data" + biosci_bioconductor_regionalst: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-regionalst/app/bioconductor-regionalst_shim_server.py"] + description: "BioScientist MCP server for bioconductor-regionalst" + biosci_bioconductor_rforproteomics: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-rforproteomics/app/bioconductor-rforproteomics_shim_server.py"] + description: "BioScientist MCP server for bioconductor-rforproteomics" + biosci_bioconductor_rgraphviz: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-rgraphviz/app/bioconductor-rgraphviz_shim_server.py"] + description: "BioScientist MCP server for bioconductor-rgraphviz" + biosci_bioconductor_rhdf5: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-rhdf5/app/bioconductor-rhdf5_shim_server.py"] + description: "BioScientist MCP server for bioconductor-rhdf5" + biosci_bioconductor_rhdf5: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-rhdf5/app/bioconductor-rhdf5_shim_server.py"] + description: "BioScientist MCP server for bioconductor-rhdf5" + biosci_bioconductor_rhdf5filters: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-rhdf5filters/app/bioconductor-rhdf5filters_shim_server.py"] + description: "BioScientist MCP server for bioconductor-rhdf5filters" + biosci_bioconductor_rhdf5lib: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-rhdf5lib/app/bioconductor-rhdf5lib_shim_server.py"] + description: "BioScientist MCP server for bioconductor-rhdf5lib" + biosci_bioconductor_rhtslib: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-rhtslib/app/bioconductor-rhtslib_shim_server.py"] + description: "BioScientist MCP server for bioconductor-rhtslib" + biosci_bioconductor_rhtslib: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-rhtslib/app/bioconductor-rhtslib_shim_server.py"] + description: "BioScientist MCP server for bioconductor-rhtslib" + biosci_bioconductor_rsamtools: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-rsamtools/app/bioconductor-rsamtools_shim_server.py"] + description: "BioScientist MCP server for bioconductor-rsamtools" + biosci_bioconductor_rsamtools: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-rsamtools/app/bioconductor-rsamtools_shim_server.py"] + description: "BioScientist MCP server for bioconductor-rsamtools" + biosci_bioconductor_rsubread: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-rsubread/app/bioconductor-rsubread_shim_server.py"] + description: "BioScientist MCP server for bioconductor-rsubread" + biosci_bioconductor_rsubread: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-rsubread/app/bioconductor-rsubread_shim_server.py"] + description: "BioScientist MCP server for bioconductor-rsubread" + biosci_bioconductor_rtracklayer: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-rtracklayer/app/bioconductor-rtracklayer_shim_server.py"] + description: "BioScientist MCP server for bioconductor-rtracklayer" + biosci_bioconductor_s4arrays: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-s4arrays/app/bioconductor-s4arrays_shim_server.py"] + description: "BioScientist MCP server for bioconductor-s4arrays" + biosci_bioconductor_s4vectors: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-s4vectors/app/bioconductor-s4vectors_shim_server.py"] + description: "BioScientist MCP server for bioconductor-s4vectors" + biosci_bioconductor_scaledmatrix: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-scaledmatrix/app/bioconductor-scaledmatrix_shim_server.py"] + description: "BioScientist MCP server for bioconductor-scaledmatrix" + biosci_bioconductor_scalign: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-scalign/app/bioconductor-scalign_shim_server.py"] + description: "BioScientist MCP server for bioconductor-scalign" + biosci_bioconductor_scannotatr: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-scannotatr/app/bioconductor-scannotatr_shim_server.py"] + description: "BioScientist MCP server for bioconductor-scannotatr" + biosci_bioconductor_scarray_sat: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-scarray.sat/app/bioconductor-scarray.sat_shim_server.py"] + description: "BioScientist MCP server for bioconductor-scarray.sat" + biosci_bioconductor_scater: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-scater/app/bioconductor-scater_shim_server.py"] + description: "BioScientist MCP server for bioconductor-scater" + biosci_bioconductor_scbfa: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-scbfa/app/bioconductor-scbfa_shim_server.py"] + description: "BioScientist MCP server for bioconductor-scbfa" + biosci_bioconductor_scbubbletree: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-scbubbletree/app/bioconductor-scbubbletree_shim_server.py"] + description: "BioScientist MCP server for bioconductor-scbubbletree" + biosci_bioconductor_sccb2: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-sccb2/app/bioconductor-sccb2_shim_server.py"] + description: "BioScientist MCP server for bioconductor-sccb2" + biosci_bioconductor_scclassifr: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-scclassifr/app/bioconductor-scclassifr_shim_server.py"] + description: "BioScientist MCP server for bioconductor-scclassifr" + biosci_bioconductor_scdataviz: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-scdataviz/app/bioconductor-scdataviz_shim_server.py"] + description: "BioScientist MCP server for bioconductor-scdataviz" + biosci_bioconductor_scdotplot: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-scdotplot/app/bioconductor-scdotplot_shim_server.py"] + description: "BioScientist MCP server for bioconductor-scdotplot" + biosci_bioconductor_scfeatures: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-scfeatures/app/bioconductor-scfeatures_shim_server.py"] + description: "BioScientist MCP server for bioconductor-scfeatures" + biosci_bioconductor_scider: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-scider/app/bioconductor-scider_shim_server.py"] + description: "BioScientist MCP server for bioconductor-scider" + biosci_bioconductor_scmageck: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-scmageck/app/bioconductor-scmageck_shim_server.py"] + description: "BioScientist MCP server for bioconductor-scmageck" + biosci_bioconductor_scqtltools: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-scqtltools/app/bioconductor-scqtltools_shim_server.py"] + description: "BioScientist MCP server for bioconductor-scqtltools" + biosci_bioconductor_scran: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-scran/app/bioconductor-scran_shim_server.py"] + description: "BioScientist MCP server for bioconductor-scran" + biosci_bioconductor_screpertoire: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-screpertoire/app/bioconductor-screpertoire_shim_server.py"] + description: "BioScientist MCP server for bioconductor-screpertoire" + biosci_bioconductor_scrnaseq: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-scrnaseq/app/bioconductor-scrnaseq_shim_server.py"] + description: "BioScientist MCP server for bioconductor-scrnaseq" + biosci_bioconductor_scrnaseqapp: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-scrnaseqapp/app/bioconductor-scrnaseqapp_shim_server.py"] + description: "BioScientist MCP server for bioconductor-scrnaseqapp" + biosci_bioconductor_sctreeviz: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-sctreeviz/app/bioconductor-sctreeviz_shim_server.py"] + description: "BioScientist MCP server for bioconductor-sctreeviz" + biosci_bioconductor_scuttle: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-scuttle/app/bioconductor-scuttle_shim_server.py"] + description: "BioScientist MCP server for bioconductor-scuttle" + biosci_bioconductor_scvir: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-scvir/app/bioconductor-scvir_shim_server.py"] + description: "BioScientist MCP server for bioconductor-scvir" + biosci_bioconductor_seraster: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-seraster/app/bioconductor-seraster_shim_server.py"] + description: "BioScientist MCP server for bioconductor-seraster" + biosci_bioconductor_shortread: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-shortread/app/bioconductor-shortread_shim_server.py"] + description: "BioScientist MCP server for bioconductor-shortread" + biosci_bioconductor_shortread: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-shortread/app/bioconductor-shortread_shim_server.py"] + description: "BioScientist MCP server for bioconductor-shortread" + biosci_bioconductor_signifinder: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-signifinder/app/bioconductor-signifinder_shim_server.py"] + description: "BioScientist MCP server for bioconductor-signifinder" + biosci_bioconductor_singlecellexperiment: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-singlecellexperiment/app/bioconductor-singlecellexperiment_shim_server.py"] + description: "BioScientist MCP server for bioconductor-singlecellexperiment" + biosci_bioconductor_singlecellmultimodal: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-singlecellmultimodal/app/bioconductor-singlecellmultimodal_shim_server.py"] + description: "BioScientist MCP server for bioconductor-singlecellmultimodal" + biosci_bioconductor_singlecelltk: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-singlecelltk/app/bioconductor-singlecelltk_shim_server.py"] + description: "BioScientist MCP server for bioconductor-singlecelltk" + biosci_bioconductor_smoothclust: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-smoothclust/app/bioconductor-smoothclust_shim_server.py"] + description: "BioScientist MCP server for bioconductor-smoothclust" + biosci_bioconductor_smoppix: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-smoppix/app/bioconductor-smoppix_shim_server.py"] + description: "BioScientist MCP server for bioconductor-smoppix" + biosci_bioconductor_sosta: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-sosta/app/bioconductor-sosta_shim_server.py"] + description: "BioScientist MCP server for bioconductor-sosta" + biosci_bioconductor_spacetrooper: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-spacetrooper/app/bioconductor-spacetrooper_shim_server.py"] + description: "BioScientist MCP server for bioconductor-spacetrooper" + biosci_bioconductor_spacexr: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-spacexr/app/bioconductor-spacexr_shim_server.py"] + description: "BioScientist MCP server for bioconductor-spacexr" + biosci_bioconductor_spaniel: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-spaniel/app/bioconductor-spaniel_shim_server.py"] + description: "BioScientist MCP server for bioconductor-spaniel" + biosci_bioconductor_spanorm: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-spanorm/app/bioconductor-spanorm_shim_server.py"] + description: "BioScientist MCP server for bioconductor-spanorm" + biosci_bioconductor_spari: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-spari/app/bioconductor-spari_shim_server.py"] + description: "BioScientist MCP server for bioconductor-spari" + biosci_bioconductor_sparsearray: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-sparsearray/app/bioconductor-sparsearray_shim_server.py"] + description: "BioScientist MCP server for bioconductor-sparsearray" + biosci_bioconductor_sparsematrixstats: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-sparsematrixstats/app/bioconductor-sparsematrixstats_shim_server.py"] + description: "BioScientist MCP server for bioconductor-sparsematrixstats" + biosci_bioconductor_spasim: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-spasim/app/bioconductor-spasim_shim_server.py"] + description: "BioScientist MCP server for bioconductor-spasim" + biosci_bioconductor_spatialcpie: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-spatialcpie/app/bioconductor-spatialcpie_shim_server.py"] + description: "BioScientist MCP server for bioconductor-spatialcpie" + biosci_bioconductor_spatialdatasets: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-spatialdatasets/app/bioconductor-spatialdatasets_shim_server.py"] + description: "BioScientist MCP server for bioconductor-spatialdatasets" + biosci_bioconductor_spatialde: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-spatialde/app/bioconductor-spatialde_shim_server.py"] + description: "BioScientist MCP server for bioconductor-spatialde" + biosci_bioconductor_spatialdecon: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-spatialdecon/app/bioconductor-spatialdecon_shim_server.py"] + description: "BioScientist MCP server for bioconductor-spatialdecon" + biosci_bioconductor_spatialdmelxsim: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-spatialdmelxsim/app/bioconductor-spatialdmelxsim_shim_server.py"] + description: "BioScientist MCP server for bioconductor-spatialdmelxsim" + biosci_bioconductor_spatialexperiment: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-spatialexperiment/app/bioconductor-spatialexperiment_shim_server.py"] + description: "BioScientist MCP server for bioconductor-spatialexperiment" + biosci_bioconductor_spatialexperimentio: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-spatialexperimentio/app/bioconductor-spatialexperimentio_shim_server.py"] + description: "BioScientist MCP server for bioconductor-spatialexperimentio" + biosci_bioconductor_spatialfda: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-spatialfda/app/bioconductor-spatialfda_shim_server.py"] + description: "BioScientist MCP server for bioconductor-spatialfda" + biosci_bioconductor_spatialfeatureexperiment: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-spatialfeatureexperiment/app/bioconductor-spatialfeatureexperiment_shim_server.py"] + description: "BioScientist MCP server for bioconductor-spatialfeatureexperiment" + biosci_bioconductor_spatialheatmap: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-spatialheatmap/app/bioconductor-spatialheatmap_shim_server.py"] + description: "BioScientist MCP server for bioconductor-spatialheatmap" + biosci_bioconductor_spatiallibd: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-spatiallibd/app/bioconductor-spatiallibd_shim_server.py"] + description: "BioScientist MCP server for bioconductor-spatiallibd" + biosci_bioconductor_spatialomicsoverlay: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-spatialomicsoverlay/app/bioconductor-spatialomicsoverlay_shim_server.py"] + description: "BioScientist MCP server for bioconductor-spatialomicsoverlay" + biosci_bioconductor_spatialsimgp: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-spatialsimgp/app/bioconductor-spatialsimgp_shim_server.py"] + description: "BioScientist MCP server for bioconductor-spatialsimgp" + biosci_bioconductor_speckle: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-speckle/app/bioconductor-speckle_shim_server.py"] + description: "BioScientist MCP server for bioconductor-speckle" + biosci_bioconductor_spiat: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-spiat/app/bioconductor-spiat_shim_server.py"] + description: "BioScientist MCP server for bioconductor-spiat" + biosci_bioconductor_spicyr: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-spicyr/app/bioconductor-spicyr_shim_server.py"] + description: "BioScientist MCP server for bioconductor-spicyr" + biosci_bioconductor_spoon: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-spoon/app/bioconductor-spoon_shim_server.py"] + description: "BioScientist MCP server for bioconductor-spoon" + biosci_bioconductor_spotclean: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-spotclean/app/bioconductor-spotclean_shim_server.py"] + description: "BioScientist MCP server for bioconductor-spotclean" + biosci_bioconductor_spotsweeper: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-spotsweeper/app/bioconductor-spotsweeper_shim_server.py"] + description: "BioScientist MCP server for bioconductor-spotsweeper" + biosci_bioconductor_standr: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-standr/app/bioconductor-standr_shim_server.py"] + description: "BioScientist MCP server for bioconductor-standr" + biosci_bioconductor_statial: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-statial/app/bioconductor-statial_shim_server.py"] + description: "BioScientist MCP server for bioconductor-statial" + biosci_bioconductor_stexampledata: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-stexampledata/app/bioconductor-stexampledata_shim_server.py"] + description: "BioScientist MCP server for bioconductor-stexampledata" + biosci_bioconductor_stjoincount: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-stjoincount/app/bioconductor-stjoincount_shim_server.py"] + description: "BioScientist MCP server for bioconductor-stjoincount" + biosci_bioconductor_stpipe: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-stpipe/app/bioconductor-stpipe_shim_server.py"] + description: "BioScientist MCP server for bioconductor-stpipe" + biosci_bioconductor_subcellularspatialdata: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-subcellularspatialdata/app/bioconductor-subcellularspatialdata_shim_server.py"] + description: "BioScientist MCP server for bioconductor-subcellularspatialdata" + biosci_bioconductor_summarizedexperiment: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-summarizedexperiment/app/bioconductor-summarizedexperiment_shim_server.py"] + description: "BioScientist MCP server for bioconductor-summarizedexperiment" + biosci_bioconductor_svp: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-svp/app/bioconductor-svp_shim_server.py"] + description: "BioScientist MCP server for bioconductor-svp" + biosci_bioconductor_synergyfinder: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-synergyfinder/app/bioconductor-synergyfinder_shim_server.py"] + description: "BioScientist MCP server for bioconductor-synergyfinder" + biosci_bioconductor_tenxvisiumdata: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-tenxvisiumdata/app/bioconductor-tenxvisiumdata_shim_server.py"] + description: "BioScientist MCP server for bioconductor-tenxvisiumdata" + biosci_bioconductor_tenxxeniumdata: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-tenxxeniumdata/app/bioconductor-tenxxeniumdata_shim_server.py"] + description: "BioScientist MCP server for bioconductor-tenxxeniumdata" + biosci_bioconductor_tidyomics: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-tidyomics/app/bioconductor-tidyomics_shim_server.py"] + description: "BioScientist MCP server for bioconductor-tidyomics" + biosci_bioconductor_tidyspatialexperiment: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-tidyspatialexperiment/app/bioconductor-tidyspatialexperiment_shim_server.py"] + description: "BioScientist MCP server for bioconductor-tidyspatialexperiment" + biosci_bioconductor_tpsvg: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-tpsvg/app/bioconductor-tpsvg_shim_server.py"] + description: "BioScientist MCP server for bioconductor-tpsvg" + biosci_bioconductor_treeio: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-treeio/app/bioconductor-treeio_shim_server.py"] + description: "BioScientist MCP server for bioconductor-treeio" + biosci_bioconductor_treesummarizedexperiment: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-treesummarizedexperiment/app/bioconductor-treesummarizedexperiment_shim_server.py"] + description: "BioScientist MCP server for bioconductor-treesummarizedexperiment" + biosci_bioconductor_tximport: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-tximport/app/bioconductor-tximport_shim_server.py"] + description: "BioScientist MCP server for bioconductor-tximport" + biosci_bioconductor_tximport: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-tximport/app/bioconductor-tximport_shim_server.py"] + description: "BioScientist MCP server for bioconductor-tximport" + biosci_bioconductor_variantannotation: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-variantannotation/app/bioconductor-variantannotation_shim_server.py"] + description: "BioScientist MCP server for bioconductor-variantannotation" + biosci_bioconductor_variantannotation: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-variantannotation/app/bioconductor-variantannotation_shim_server.py"] + description: "BioScientist MCP server for bioconductor-variantannotation" + biosci_bioconductor_vectrapolarisdata: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-vectrapolarisdata/app/bioconductor-vectrapolarisdata_shim_server.py"] + description: "BioScientist MCP server for bioconductor-vectrapolarisdata" + biosci_bioconductor_visiumio: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-visiumio/app/bioconductor-visiumio_shim_server.py"] + description: "BioScientist MCP server for bioconductor-visiumio" + biosci_bioconductor_visiumstitched: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-visiumstitched/app/bioconductor-visiumstitched_shim_server.py"] + description: "BioScientist MCP server for bioconductor-visiumstitched" + biosci_bioconductor_voyager: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-voyager/app/bioconductor-voyager_shim_server.py"] + description: "BioScientist MCP server for bioconductor-voyager" + biosci_bioconductor_weberdivechalcdata: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-weberdivechalcdata/app/bioconductor-weberdivechalcdata_shim_server.py"] + description: "BioScientist MCP server for bioconductor-weberdivechalcdata" + biosci_bioconductor_xcms: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-xcms/app/bioconductor-xcms_shim_server.py"] + description: "BioScientist MCP server for bioconductor-xcms" + biosci_bioconductor_xeniumio: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-xeniumio/app/bioconductor-xeniumio_shim_server.py"] + description: "BioScientist MCP server for bioconductor-xeniumio" + biosci_bioconductor_xenlite: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-xenlite/app/bioconductor-xenlite_shim_server.py"] + description: "BioScientist MCP server for bioconductor-xenlite" + biosci_bioconductor_xvector: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-xvector/app/bioconductor-xvector_shim_server.py"] + description: "BioScientist MCP server for bioconductor-xvector" + biosci_bioconductor_xvector: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-xvector/app/bioconductor-xvector_shim_server.py"] + description: "BioScientist MCP server for bioconductor-xvector" + biosci_bioconductor_zlibbioc: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bioconductor-zlibbioc/app/bioconductor-zlibbioc_shim_server.py"] + description: "BioScientist MCP server for bioconductor-zlibbioc" + biosci_bioconductor_zlibbioc: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bioconductor-zlibbioc/app/bioconductor-zlibbioc_shim_server.py"] + description: "BioScientist MCP server for bioconductor-zlibbioc" + biosci_biom_format: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_biom-format/app/biom-format_shim_server.py"] + description: "BioScientist MCP server for biom-format" + biosci_biopython: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_biopython/app/biopython_shim_server.py"] + description: "BioScientist MCP server for biopython" + biosci_blast: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_blast/app/blast_shim_server.py"] + description: "BioScientist MCP server for blast" + biosci_blast: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_blast/app/blast_shim_server.py"] + description: "BioScientist MCP server for blast" + biosci_blast_legacy: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_blast-legacy/app/blast-legacy_shim_server.py"] + description: "BioScientist MCP server for blast-legacy" + biosci_bowtie: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bowtie/app/bowtie_shim_server.py"] + description: "BioScientist MCP server for bowtie" + biosci_bowtie: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bowtie/app/bowtie_shim_server.py"] + description: "BioScientist MCP server for bowtie" + biosci_bowtie2: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bowtie2/app/bowtie2_shim_server.py"] + description: "BioScientist MCP server for bowtie2" + biosci_bowtie2: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bowtie2/app/bowtie2_shim_server.py"] + description: "BioScientist MCP server for bowtie2" + biosci_bpipe: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bpipe/app/bpipe_shim_server.py"] + description: "BioScientist MCP server for bpipe" + biosci_brooklyn_plot: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_brooklyn_plot/app/brooklyn_plot_shim_server.py"] + description: "BioScientist MCP server for brooklyn_plot" + biosci_busco: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_busco/app/busco_shim_server.py"] + description: "BioScientist MCP server for busco" + biosci_bwa: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_bwa/app/bwa_shim_server.py"] + description: "BioScientist MCP server for bwa" + biosci_bwa: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bwa/app/bwa_shim_server.py"] + description: "BioScientist MCP server for bwa" + biosci_bx_python: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_bx-python/app/bx-python_shim_server.py"] + description: "BioScientist MCP server for bx-python" + biosci_c_ares: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_c-ares/app/c-ares_shim_server.py"] + description: "BioScientist MCP server for c-ares" + biosci_cascade_reg: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_cascade-reg/app/cascade-reg_shim_server.py"] + description: "BioScientist MCP server for cascade-reg" + biosci_cd_hit: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_cd-hit/app/cd-hit_shim_server.py"] + description: "BioScientist MCP server for cd-hit" + biosci_cd_hit: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_cd-hit/app/cd-hit_shim_server.py"] + description: "BioScientist MCP server for cd-hit" + biosci_cdbtools: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_cdbtools/app/cdbtools_shim_server.py"] + description: "BioScientist MCP server for cdbtools" + biosci_cell2cell: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_cell2cell/app/cell2cell_shim_server.py"] + description: "BioScientist MCP server for cell2cell" + biosci_cellitac: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_cellitac/app/cellitac_shim_server.py"] + description: "BioScientist MCP server for cellitac" + biosci_cellqc: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_cellqc/app/cellqc_shim_server.py"] + description: "BioScientist MCP server for cellqc" + biosci_cellrank: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_cellrank/app/cellrank_shim_server.py"] + description: "BioScientist MCP server for cellrank" + biosci_cellsnake: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_cellsnake/app/cellsnake_shim_server.py"] + description: "BioScientist MCP server for cellsnake" + biosci_celltypist: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_celltypist/app/celltypist_shim_server.py"] + description: "BioScientist MCP server for celltypist" + biosci_celltypist_so: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_celltypist-so/app/celltypist-so_shim_server.py"] + description: "BioScientist MCP server for celltypist-so" + biosci_checkatlas: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_checkatlas/app/checkatlas_shim_server.py"] + description: "BioScientist MCP server for checkatlas" + biosci_checkm_genome: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_checkm-genome/app/checkm-genome_shim_server.py"] + description: "BioScientist MCP server for checkm-genome" + biosci_circexplorer2: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_circexplorer2/app/circexplorer2_shim_server.py"] + description: "BioScientist MCP server for circexplorer2" + biosci_circos: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_circos/app/circos_shim_server.py"] + description: "BioScientist MCP server for circos" + biosci_circos: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_circos/app/circos_shim_server.py"] + description: "BioScientist MCP server for circos" + biosci_clustalo: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_clustalo/app/clustalo_shim_server.py"] + description: "BioScientist MCP server for clustalo" + biosci_clustalo: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_clustalo/app/clustalo_shim_server.py"] + description: "BioScientist MCP server for clustalo" + biosci_clustalw: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_clustalw/app/clustalw_shim_server.py"] + description: "BioScientist MCP server for clustalw" + biosci_clustalw: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_clustalw/app/clustalw_shim_server.py"] + description: "BioScientist MCP server for clustalw" + biosci_cmappy: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_cmappy/app/cmappy_shim_server.py"] + description: "BioScientist MCP server for cmappy" + biosci_cnmf: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_cnmf/app/cnmf_shim_server.py"] + description: "BioScientist MCP server for cnmf" + biosci_cnvkit: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_cnvkit/app/cnvkit_shim_server.py"] + description: "BioScientist MCP server for cnvkit" + biosci_comebin: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_comebin/app/comebin_shim_server.py"] + description: "BioScientist MCP server for comebin" + biosci_comet_ms: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_comet-ms/app/comet-ms_shim_server.py"] + description: "BioScientist MCP server for comet-ms" + biosci_constellations: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_constellations/app/constellations_shim_server.py"] + description: "BioScientist MCP server for constellations" + biosci_cooler: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_cooler/app/cooler_shim_server.py"] + description: "BioScientist MCP server for cooler" + biosci_cooltools: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_cooltools/app/cooltools_shim_server.py"] + description: "BioScientist MCP server for cooltools" + biosci_coreutils: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_coreutils/app/coreutils_shim_server.py"] + description: "BioScientist MCP server for coreutils" + biosci_cosg: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_cosg/app/cosg_shim_server.py"] + description: "BioScientist MCP server for cosg" + biosci_cospar: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_cospar/app/cospar_shim_server.py"] + description: "BioScientist MCP server for cospar" + biosci_crispresso2: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_crispresso2/app/crispresso2_shim_server.py"] + description: "BioScientist MCP server for crispresso2" + biosci_crispritz: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_crispritz/app/crispritz_shim_server.py"] + description: "BioScientist MCP server for crispritz" + biosci_crisprme: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_crisprme/app/crisprme_shim_server.py"] + description: "BioScientist MCP server for crisprme" + biosci_cromwell: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_cromwell/app/cromwell_shim_server.py"] + description: "BioScientist MCP server for cromwell" + biosci_csvtk: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_csvtk/app/csvtk_shim_server.py"] + description: "BioScientist MCP server for csvtk" + biosci_cutadapt: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_cutadapt/app/cutadapt_shim_server.py"] + description: "BioScientist MCP server for cutadapt" + biosci_cwltool: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_cwltool/app/cwltool_shim_server.py"] + description: "BioScientist MCP server for cwltool" + biosci_cytoscape: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_cytoscape/app/cytoscape_shim_server.py"] + description: "BioScientist MCP server for cytoscape" + biosci_cytotrace2_python: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_cytotrace2-python/app/cytotrace2-python_shim_server.py"] + description: "BioScientist MCP server for cytotrace2-python" + biosci_cyvcf2: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_cyvcf2/app/cyvcf2_shim_server.py"] + description: "BioScientist MCP server for cyvcf2" + biosci_dca: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_dca/app/dca_shim_server.py"] + description: "BioScientist MCP server for dca" + biosci_ddocent: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_ddocent/app/ddocent_shim_server.py"] + description: "BioScientist MCP server for ddocent" + biosci_deblur: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_deblur/app/deblur_shim_server.py"] + description: "BioScientist MCP server for deblur" + biosci_decoupler: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_decoupler/app/decoupler_shim_server.py"] + description: "BioScientist MCP server for decoupler" + biosci_deeptools: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_deeptools/app/deeptools_shim_server.py"] + description: "BioScientist MCP server for deeptools" + biosci_deeptoolsintervals: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_deeptoolsintervals/app/deeptoolsintervals_shim_server.py"] + description: "BioScientist MCP server for deeptoolsintervals" + biosci_delly: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_delly/app/delly_shim_server.py"] + description: "BioScientist MCP server for delly" + biosci_delly: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_delly/app/delly_shim_server.py"] + description: "BioScientist MCP server for delly" + biosci_dendropy: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_dendropy/app/dendropy_shim_server.py"] + description: "BioScientist MCP server for dendropy" + biosci_dendropy: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_dendropy/app/dendropy_shim_server.py"] + description: "BioScientist MCP server for dendropy" + biosci_diamond: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_diamond/app/diamond_shim_server.py"] + description: "BioScientist MCP server for diamond" + biosci_dnaio: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_dnaio/app/dnaio_shim_server.py"] + description: "BioScientist MCP server for dnaio" + biosci_doubletdetection: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_doubletdetection/app/doubletdetection_shim_server.py"] + description: "BioScientist MCP server for doubletdetection" + biosci_dropkick: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_dropkick/app/dropkick_shim_server.py"] + description: "BioScientist MCP server for dropkick" + biosci_dsh_bio: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_dsh-bio/app/dsh-bio_shim_server.py"] + description: "BioScientist MCP server for dsh-bio" + biosci_dsh_bio: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_dsh-bio/app/dsh-bio_shim_server.py"] + description: "BioScientist MCP server for dsh-bio" + biosci_dxpy: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_dxpy/app/dxpy_shim_server.py"] + description: "BioScientist MCP server for dxpy" + biosci_easy_vitessce: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_easy_vitessce/app/easy_vitessce_shim_server.py"] + description: "BioScientist MCP server for easy_vitessce" + biosci_echidna: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_echidna/app/echidna_shim_server.py"] + description: "BioScientist MCP server for echidna" + biosci_emboss: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_emboss/app/emboss_shim_server.py"] + description: "BioScientist MCP server for emboss" + biosci_ena_webin_cli: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_ena-webin-cli/app/ena-webin-cli_shim_server.py"] + description: "BioScientist MCP server for ena-webin-cli" + biosci_ena_webin_cli: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_ena-webin-cli/app/ena-webin-cli_shim_server.py"] + description: "BioScientist MCP server for ena-webin-cli" + biosci_ensembl_vep: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_ensembl-vep/app/ensembl-vep_shim_server.py"] + description: "BioScientist MCP server for ensembl-vep" + biosci_entrez_direct: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_entrez-direct/app/entrez-direct_shim_server.py"] + description: "BioScientist MCP server for entrez-direct" + biosci_epic: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_epic/app/epic_shim_server.py"] + description: "BioScientist MCP server for epic" + biosci_episcanpy: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_episcanpy/app/episcanpy_shim_server.py"] + description: "BioScientist MCP server for episcanpy" + biosci_eva_sub_cli: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_eva-sub-cli/app/eva-sub-cli_shim_server.py"] + description: "BioScientist MCP server for eva-sub-cli" + biosci_f5c: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_f5c/app/f5c_shim_server.py"] + description: "BioScientist MCP server for f5c" + biosci_f5c: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_f5c/app/f5c_shim_server.py"] + description: "BioScientist MCP server for f5c" + biosci_famsa: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_famsa/app/famsa_shim_server.py"] + description: "BioScientist MCP server for famsa" + biosci_fast5: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_fast5/app/fast5_shim_server.py"] + description: "BioScientist MCP server for fast5" + biosci_fastani: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_fastani/app/fastani_shim_server.py"] + description: "BioScientist MCP server for fastani" + biosci_fastdtw: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_fastdtw/app/fastdtw_shim_server.py"] + description: "BioScientist MCP server for fastdtw" + biosci_fastdtw: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_fastdtw/app/fastdtw_shim_server.py"] + description: "BioScientist MCP server for fastdtw" + biosci_fastp: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_fastp/app/fastp_shim_server.py"] + description: "BioScientist MCP server for fastp" + biosci_fastp: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_fastp/app/fastp_shim_server.py"] + description: "BioScientist MCP server for fastp" + biosci_fastqc: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_fastqc/app/fastqc_shim_server.py"] + description: "BioScientist MCP server for fastqc" + biosci_fastqc: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_fastqc/app/fastqc_shim_server.py"] + description: "BioScientist MCP server for fastqc" + biosci_fasttree: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_fasttree/app/fasttree_shim_server.py"] + description: "BioScientist MCP server for fasttree" + biosci_fasttree: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_fasttree/app/fasttree_shim_server.py"] + description: "BioScientist MCP server for fasttree" + biosci_fermi2: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_fermi2/app/fermi2_shim_server.py"] + description: "BioScientist MCP server for fermi2" + biosci_fgbio: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_fgbio/app/fgbio_shim_server.py"] + description: "BioScientist MCP server for fgbio" + biosci_fgbio: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_fgbio/app/fgbio_shim_server.py"] + description: "BioScientist MCP server for fgbio" + biosci_filechunkio: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_filechunkio/app/filechunkio_shim_server.py"] + description: "BioScientist MCP server for filechunkio" + biosci_flagx: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_flagx/app/flagx_shim_server.py"] + description: "BioScientist MCP server for flagx" + biosci_flye: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_flye/app/flye_shim_server.py"] + description: "BioScientist MCP server for flye" + biosci_flye: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_flye/app/flye_shim_server.py"] + description: "BioScientist MCP server for flye" + biosci_freebayes: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_freebayes/app/freebayes_shim_server.py"] + description: "BioScientist MCP server for freebayes" + biosci_ftputil: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_ftputil/app/ftputil_shim_server.py"] + description: "BioScientist MCP server for ftputil" + biosci_fwdpy11: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_fwdpy11/app/fwdpy11_shim_server.py"] + description: "BioScientist MCP server for fwdpy11" + biosci_galaxy_lib: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_galaxy-lib/app/galaxy-lib_shim_server.py"] + description: "BioScientist MCP server for galaxy-lib" + biosci_gatk: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_gatk/app/gatk_shim_server.py"] + description: "BioScientist MCP server for gatk" + biosci_gatk4: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_gatk4/app/gatk4_shim_server.py"] + description: "BioScientist MCP server for gatk4" + biosci_gatk4_spark: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_gatk4-spark/app/gatk4-spark_shim_server.py"] + description: "BioScientist MCP server for gatk4-spark" + biosci_gdk_pixbuf: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_gdk-pixbuf/app/gdk-pixbuf_shim_server.py"] + description: "BioScientist MCP server for gdk-pixbuf" + biosci_gecode: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_gecode/app/gecode_shim_server.py"] + description: "BioScientist MCP server for gecode" + biosci_gene_trajectory_python: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_gene-trajectory-python/app/gene-trajectory-python_shim_server.py"] + description: "BioScientist MCP server for gene-trajectory-python" + biosci_genecircuitry: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_genecircuitry/app/genecircuitry_shim_server.py"] + description: "BioScientist MCP server for genecircuitry" + biosci_geneimpacts: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_geneimpacts/app/geneimpacts_shim_server.py"] + description: "BioScientist MCP server for geneimpacts" + biosci_genoboo: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_genoboo/app/genoboo_shim_server.py"] + description: "BioScientist MCP server for genoboo" + biosci_genomepy: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_genomepy/app/genomepy_shim_server.py"] + description: "BioScientist MCP server for genomepy" + biosci_genometools_genometools: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_genometools-genometools/app/genometools-genometools_shim_server.py"] + description: "BioScientist MCP server for genometools-genometools" + biosci_gffread: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_gffread/app/gffread_shim_server.py"] + description: "BioScientist MCP server for gffread" + biosci_gffutils: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_gffutils/app/gffutils_shim_server.py"] + description: "BioScientist MCP server for gffutils" + biosci_ghostscript: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_ghostscript/app/ghostscript_shim_server.py"] + description: "BioScientist MCP server for ghostscript" + biosci_gimmemotifs: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_gimmemotifs/app/gimmemotifs_shim_server.py"] + description: "BioScientist MCP server for gimmemotifs" + biosci_glimmerhmm: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_glimmerhmm/app/glimmerhmm_shim_server.py"] + description: "BioScientist MCP server for glimmerhmm" + biosci_gmap: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_gmap/app/gmap_shim_server.py"] + description: "BioScientist MCP server for gmap" + biosci_gneiss: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_gneiss/app/gneiss_shim_server.py"] + description: "BioScientist MCP server for gneiss" + biosci_gnuplot: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_gnuplot/app/gnuplot_shim_server.py"] + description: "BioScientist MCP server for gnuplot" + biosci_gofasta: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_gofasta/app/gofasta_shim_server.py"] + description: "BioScientist MCP server for gofasta" + biosci_goleft: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_goleft/app/goleft_shim_server.py"] + description: "BioScientist MCP server for goleft" + biosci_gromacs: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_gromacs/app/gromacs_shim_server.py"] + description: "BioScientist MCP server for gromacs" + biosci_gseapy: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_gseapy/app/gseapy_shim_server.py"] + description: "BioScientist MCP server for gseapy" + biosci_gsmap: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_gsmap/app/gsmap_shim_server.py"] + description: "BioScientist MCP server for gsmap" + biosci_gtdbtk: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_gtdbtk/app/gtdbtk_shim_server.py"] + description: "BioScientist MCP server for gtdbtk" + biosci_harpy: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_harpy/app/harpy_shim_server.py"] + description: "BioScientist MCP server for harpy" + biosci_hhsuite: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_hhsuite/app/hhsuite_shim_server.py"] + description: "BioScientist MCP server for hhsuite" + biosci_hicexplorer: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_hicexplorer/app/hicexplorer_shim_server.py"] + description: "BioScientist MCP server for hicexplorer" + biosci_hifiasm: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_hifiasm/app/hifiasm_shim_server.py"] + description: "BioScientist MCP server for hifiasm" + biosci_hisat2: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_hisat2/app/hisat2_shim_server.py"] + description: "BioScientist MCP server for hisat2" + biosci_hmmer: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_hmmer/app/hmmer_shim_server.py"] + description: "BioScientist MCP server for hmmer" + biosci_htseq: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_htseq/app/htseq_shim_server.py"] + description: "BioScientist MCP server for htseq" + biosci_htslib: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_htslib/app/htslib_shim_server.py"] + description: "BioScientist MCP server for htslib" + biosci_humann2: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_humann2/app/humann2_shim_server.py"] + description: "BioScientist MCP server for humann2" + biosci_igv: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_igv/app/igv_shim_server.py"] + description: "BioScientist MCP server for igv" + biosci_igv_reports: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_igv-reports/app/igv-reports_shim_server.py"] + description: "BioScientist MCP server for igv-reports" + biosci_illumina_interop: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_illumina-interop/app/illumina-interop_shim_server.py"] + description: "BioScientist MCP server for illumina-interop" + biosci_infernal: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_infernal/app/infernal_shim_server.py"] + description: "BioScientist MCP server for infernal" + biosci_insilicoseq: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_insilicoseq/app/insilicoseq_shim_server.py"] + description: "BioScientist MCP server for insilicoseq" + biosci_intarna: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_intarna/app/intarna_shim_server.py"] + description: "BioScientist MCP server for intarna" + biosci_involucro: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_involucro/app/involucro_shim_server.py"] + description: "BioScientist MCP server for involucro" + biosci_iow: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_iow/app/iow_shim_server.py"] + description: "BioScientist MCP server for iow" + biosci_ipyrad: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_ipyrad/app/ipyrad_shim_server.py"] + description: "BioScientist MCP server for ipyrad" + biosci_ipython_cluster_helper: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_ipython-cluster-helper/app/ipython-cluster-helper_shim_server.py"] + description: "BioScientist MCP server for ipython-cluster-helper" + biosci_iqtree: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_iqtree/app/iqtree_shim_server.py"] + description: "BioScientist MCP server for iqtree" + biosci_itsxpress: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_itsxpress/app/itsxpress_shim_server.py"] + description: "BioScientist MCP server for itsxpress" + biosci_ivar: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_ivar/app/ivar_shim_server.py"] + description: "BioScientist MCP server for ivar" + biosci_jalview: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_jalview/app/jalview_shim_server.py"] + description: "BioScientist MCP server for jalview" + biosci_java_jdk: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_java-jdk/app/java-jdk_shim_server.py"] + description: "BioScientist MCP server for java-jdk" + biosci_jbrowse2: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_jbrowse2/app/jbrowse2_shim_server.py"] + description: "BioScientist MCP server for jbrowse2" + biosci_jcvi: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_jcvi/app/jcvi_shim_server.py"] + description: "BioScientist MCP server for jcvi" + biosci_jellyfish: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_jellyfish/app/jellyfish_shim_server.py"] + description: "BioScientist MCP server for jellyfish" + biosci_jq: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_jq/app/jq_shim_server.py"] + description: "BioScientist MCP server for jq" + biosci_k8: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_k8/app/k8_shim_server.py"] + description: "BioScientist MCP server for k8" + biosci_kaiju: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_kaiju/app/kaiju_shim_server.py"] + description: "BioScientist MCP server for kaiju" + biosci_kalign2: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_kalign2/app/kalign2_shim_server.py"] + description: "BioScientist MCP server for kalign2" + biosci_kallisto: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_kallisto/app/kallisto_shim_server.py"] + description: "BioScientist MCP server for kallisto" + biosci_kb_python: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_kb-python/app/kb-python_shim_server.py"] + description: "BioScientist MCP server for kb-python" + biosci_khipu_metabolomics: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_khipu-metabolomics/app/khipu-metabolomics_shim_server.py"] + description: "BioScientist MCP server for khipu-metabolomics" + biosci_kmc: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_kmc/app/kmc_shim_server.py"] + description: "BioScientist MCP server for kmc" + biosci_kmer_jellyfish: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_kmer-jellyfish/app/kmer-jellyfish_shim_server.py"] + description: "BioScientist MCP server for kmer-jellyfish" + biosci_kraken2: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_kraken2/app/kraken2_shim_server.py"] + description: "BioScientist MCP server for kraken2" + biosci_krona: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_krona/app/krona_shim_server.py"] + description: "BioScientist MCP server for krona" + biosci_last: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_last/app/last_shim_server.py"] + description: "BioScientist MCP server for last" + biosci_last: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_last/app/last_shim_server.py"] + description: "BioScientist MCP server for last" + biosci_lastz: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_lastz/app/lastz_shim_server.py"] + description: "BioScientist MCP server for lastz" + biosci_libcifpp: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_libcifpp/app/libcifpp_shim_server.py"] + description: "BioScientist MCP server for libcifpp" + biosci_libdeflate: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_libdeflate/app/libdeflate_shim_server.py"] + description: "BioScientist MCP server for libdeflate" + biosci_libsequence: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_libsequence/app/libsequence_shim_server.py"] + description: "BioScientist MCP server for libsequence" + biosci_locarna: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_locarna/app/locarna_shim_server.py"] + description: "BioScientist MCP server for locarna" + biosci_macs2: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_macs2/app/macs2_shim_server.py"] + description: "BioScientist MCP server for macs2" + biosci_mafft: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_mafft/app/mafft_shim_server.py"] + description: "BioScientist MCP server for mafft" + biosci_mageck: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_mageck/app/mageck_shim_server.py"] + description: "BioScientist MCP server for mageck" + biosci_mameshiba: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_mameshiba/app/mameshiba_shim_server.py"] + description: "BioScientist MCP server for mameshiba" + biosci_mappy: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_mappy/app/mappy_shim_server.py"] + description: "BioScientist MCP server for mappy" + biosci_markerrepo: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_markerrepo/app/markerrepo_shim_server.py"] + description: "BioScientist MCP server for markerrepo" + biosci_mash: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_mash/app/mash_shim_server.py"] + description: "BioScientist MCP server for mash" + biosci_maxquant: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_maxquant/app/maxquant_shim_server.py"] + description: "BioScientist MCP server for maxquant" + biosci_medaka: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_medaka/app/medaka_shim_server.py"] + description: "BioScientist MCP server for medaka" + biosci_megahit: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_megahit/app/megahit_shim_server.py"] + description: "BioScientist MCP server for megahit" + biosci_meme: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_meme/app/meme_shim_server.py"] + description: "BioScientist MCP server for meme" + biosci_mentalist: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_mentalist/app/mentalist_shim_server.py"] + description: "BioScientist MCP server for mentalist" + biosci_metagenome_atlas: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_metagenome-atlas/app/metagenome-atlas_shim_server.py"] + description: "BioScientist MCP server for metagenome-atlas" + biosci_metaphlan: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_metaphlan/app/metaphlan_shim_server.py"] + description: "BioScientist MCP server for metaphlan" + biosci_metaphlan2: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_metaphlan2/app/metaphlan2_shim_server.py"] + description: "BioScientist MCP server for metaphlan2" + biosci_mikado: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_mikado/app/mikado_shim_server.py"] + description: "BioScientist MCP server for mikado" + biosci_minced: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_minced/app/minced_shim_server.py"] + description: "BioScientist MCP server for minced" + biosci_miniasm: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_miniasm/app/miniasm_shim_server.py"] + description: "BioScientist MCP server for miniasm" + biosci_minimap2: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_minimap2/app/minimap2_shim_server.py"] + description: "BioScientist MCP server for minimap2" + biosci_minvar: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_minvar/app/minvar_shim_server.py"] + description: "BioScientist MCP server for minvar" + biosci_mitos: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_mitos/app/mitos_shim_server.py"] + description: "BioScientist MCP server for mitos" + biosci_mmseqs2: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_mmseqs2/app/mmseqs2_shim_server.py"] + description: "BioScientist MCP server for mmseqs2" + biosci_mmtf_python: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_mmtf-python/app/mmtf-python_shim_server.py"] + description: "BioScientist MCP server for mmtf-python" + biosci_mobivision_m: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_mobivision-m/app/mobivision-m_shim_server.py"] + description: "BioScientist MCP server for mobivision-m" + biosci_moments: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_moments/app/moments_shim_server.py"] + description: "BioScientist MCP server for moments" + biosci_mosdepth: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_mosdepth/app/mosdepth_shim_server.py"] + description: "BioScientist MCP server for mosdepth" + biosci_mothur: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_mothur/app/mothur_shim_server.py"] + description: "BioScientist MCP server for mothur" + biosci_msisensor_pro: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_msisensor-pro/app/msisensor-pro_shim_server.py"] + description: "BioScientist MCP server for msisensor-pro" + biosci_msproteomicstools: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_msproteomicstools/app/msproteomicstools_shim_server.py"] + description: "BioScientist MCP server for msproteomicstools" + biosci_msstitch: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_msstitch/app/msstitch_shim_server.py"] + description: "BioScientist MCP server for msstitch" + biosci_multiqc: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_multiqc/app/multiqc_shim_server.py"] + description: "BioScientist MCP server for multiqc" + biosci_multiqc_bcbio: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_multiqc-bcbio/app/multiqc-bcbio_shim_server.py"] + description: "BioScientist MCP server for multiqc-bcbio" + biosci_multiqc_xenium_extra: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_multiqc-xenium-extra/app/multiqc-xenium-extra_shim_server.py"] + description: "BioScientist MCP server for multiqc-xenium-extra" + biosci_multivelo: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_multivelo/app/multivelo_shim_server.py"] + description: "BioScientist MCP server for multivelo" + biosci_mummer: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_mummer/app/mummer_shim_server.py"] + description: "BioScientist MCP server for mummer" + biosci_munkres: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_munkres/app/munkres_shim_server.py"] + description: "BioScientist MCP server for munkres" + biosci_muscle: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_muscle/app/muscle_shim_server.py"] + description: "BioScientist MCP server for muscle" + biosci_mysql_connector_c: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_mysql-connector-c/app/mysql-connector-c_shim_server.py"] + description: "BioScientist MCP server for mysql-connector-c" + biosci_mztosqlite: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_mztosqlite/app/mztosqlite_shim_server.py"] + description: "BioScientist MCP server for mztosqlite" + biosci_nanocomp: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_nanocomp/app/nanocomp_shim_server.py"] + description: "BioScientist MCP server for nanocomp" + biosci_nanoget: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_nanoget/app/nanoget_shim_server.py"] + description: "BioScientist MCP server for nanoget" + biosci_nanomath: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_nanomath/app/nanomath_shim_server.py"] + description: "BioScientist MCP server for nanomath" + biosci_nanopolish: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_nanopolish/app/nanopolish_shim_server.py"] + description: "BioScientist MCP server for nanopolish" + biosci_ncbi_datasets_pylib: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_ncbi-datasets-pylib/app/ncbi-datasets-pylib_shim_server.py"] + description: "BioScientist MCP server for ncbi-datasets-pylib" + biosci_ncbi_genome_download: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_ncbi-genome-download/app/ncbi-genome-download_shim_server.py"] + description: "BioScientist MCP server for ncbi-genome-download" + biosci_ncbi_ngs_sdk: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_ncbi-ngs-sdk/app/ncbi-ngs-sdk_shim_server.py"] + description: "BioScientist MCP server for ncbi-ngs-sdk" + biosci_ncbi_vdb: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_ncbi-vdb/app/ncbi-vdb_shim_server.py"] + description: "BioScientist MCP server for ncbi-vdb" + biosci_ncls: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_ncls/app/ncls_shim_server.py"] + description: "BioScientist MCP server for ncls" + biosci_nextalign: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_nextalign/app/nextalign_shim_server.py"] + description: "BioScientist MCP server for nextalign" + biosci_nextclade: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_nextclade/app/nextclade_shim_server.py"] + description: "BioScientist MCP server for nextclade" + biosci_nextflow: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_nextflow/app/nextflow_shim_server.py"] + description: "BioScientist MCP server for nextflow" + biosci_nf_core: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_nf-core/app/nf-core_shim_server.py"] + description: "BioScientist MCP server for nf-core" + biosci_nglview: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_nglview/app/nglview_shim_server.py"] + description: "BioScientist MCP server for nglview" + biosci_ngmlr: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_ngmlr/app/ngmlr_shim_server.py"] + description: "BioScientist MCP server for ngmlr" + biosci_ngs_smap: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_ngs-smap/app/ngs-smap_shim_server.py"] + description: "BioScientist MCP server for ngs-smap" + biosci_ngs_tools: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_ngs-tools/app/ngs-tools_shim_server.py"] + description: "BioScientist MCP server for ngs-tools" + biosci_novae: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_novae/app/novae_shim_server.py"] + description: "BioScientist MCP server for novae" + biosci_novoalign: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_novoalign/app/novoalign_shim_server.py"] + description: "BioScientist MCP server for novoalign" + biosci_ont_fast5_api: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_ont-fast5-api/app/ont-fast5-api_shim_server.py"] + description: "BioScientist MCP server for ont-fast5-api" + biosci_openms_thirdparty: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_openms-thirdparty/app/openms-thirdparty_shim_server.py"] + description: "BioScientist MCP server for openms-thirdparty" + biosci_opticlust: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_opticlust/app/opticlust_shim_server.py"] + description: "BioScientist MCP server for opticlust" + biosci_orthofinder: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_orthofinder/app/orthofinder_shim_server.py"] + description: "BioScientist MCP server for orthofinder" + biosci_pairix: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_pairix/app/pairix_shim_server.py"] + description: "BioScientist MCP server for pairix" + biosci_palantir: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_palantir/app/palantir_shim_server.py"] + description: "BioScientist MCP server for palantir" + biosci_paml: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_paml/app/paml_shim_server.py"] + description: "BioScientist MCP server for paml" + biosci_pango_designation: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_pango-designation/app/pango-designation_shim_server.py"] + description: "BioScientist MCP server for pango-designation" + biosci_pangolearn: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_pangolearn/app/pangolearn_shim_server.py"] + description: "BioScientist MCP server for pangolearn" + biosci_parallel: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_parallel/app/parallel_shim_server.py"] + description: "BioScientist MCP server for parallel" + biosci_parasail_python: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_parasail-python/app/parasail-python_shim_server.py"] + description: "BioScientist MCP server for parasail-python" + biosci_pasta: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_pasta/app/pasta_shim_server.py"] + description: "BioScientist MCP server for pasta" + biosci_paste_bio: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_paste-bio/app/paste-bio_shim_server.py"] + description: "BioScientist MCP server for paste-bio" + biosci_pbmm2: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_pbmm2/app/pbmm2_shim_server.py"] + description: "BioScientist MCP server for pbmm2" + biosci_pcdl: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_pcdl/app/pcdl_shim_server.py"] + description: "BioScientist MCP server for pcdl" + biosci_peakqc: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_peakqc/app/peakqc_shim_server.py"] + description: "BioScientist MCP server for peakqc" + biosci_peptide_shaker: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_peptide-shaker/app/peptide-shaker_shim_server.py"] + description: "BioScientist MCP server for peptide-shaker" + biosci_peptides: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_peptides/app/peptides_shim_server.py"] + description: "BioScientist MCP server for peptides" + biosci_perl_aceperl: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-aceperl/app/perl-aceperl_shim_server.py"] + description: "BioScientist MCP server for perl-aceperl" + biosci_perl_algorithm_diff: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-algorithm-diff/app/perl-algorithm-diff_shim_server.py"] + description: "BioScientist MCP server for perl-algorithm-diff" + biosci_perl_alien_libxml2: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-alien-libxml2/app/perl-alien-libxml2_shim_server.py"] + description: "BioScientist MCP server for perl-alien-libxml2" + biosci_perl_apache_test: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-apache-test/app/perl-apache-test_shim_server.py"] + description: "BioScientist MCP server for perl-apache-test" + biosci_perl_app_cpanminus: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-app-cpanminus/app/perl-app-cpanminus_shim_server.py"] + description: "BioScientist MCP server for perl-app-cpanminus" + biosci_perl_appconfig: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-appconfig/app/perl-appconfig_shim_server.py"] + description: "BioScientist MCP server for perl-appconfig" + biosci_perl_archive_tar: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-archive-tar/app/perl-archive-tar_shim_server.py"] + description: "BioScientist MCP server for perl-archive-tar" + biosci_perl_array_compare: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-array-compare/app/perl-array-compare_shim_server.py"] + description: "BioScientist MCP server for perl-array-compare" + biosci_perl_autoloader: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-autoloader/app/perl-autoloader_shim_server.py"] + description: "BioScientist MCP server for perl-autoloader" + biosci_perl_base: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-base/app/perl-base_shim_server.py"] + description: "BioScientist MCP server for perl-base" + biosci_perl_bio_asn1_entrezgene: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-bio-asn1-entrezgene/app/perl-bio-asn1-entrezgene_shim_server.py"] + description: "BioScientist MCP server for perl-bio-asn1-entrezgene" + biosci_perl_bio_coordinate: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-bio-coordinate/app/perl-bio-coordinate_shim_server.py"] + description: "BioScientist MCP server for perl-bio-coordinate" + biosci_perl_bio_db_hts: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-bio-db-hts/app/perl-bio-db-hts_shim_server.py"] + description: "BioScientist MCP server for perl-bio-db-hts" + biosci_perl_bio_featureio: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-bio-featureio/app/perl-bio-featureio_shim_server.py"] + description: "BioScientist MCP server for perl-bio-featureio" + biosci_perl_bio_samtools: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-bio-samtools/app/perl-bio-samtools_shim_server.py"] + description: "BioScientist MCP server for perl-bio-samtools" + biosci_perl_bio_searchio_hmmer: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-bio-searchio-hmmer/app/perl-bio-searchio-hmmer_shim_server.py"] + description: "BioScientist MCP server for perl-bio-searchio-hmmer" + biosci_perl_bio_tools_phylo_paml: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-bio-tools-phylo-paml/app/perl-bio-tools-phylo-paml_shim_server.py"] + description: "BioScientist MCP server for perl-bio-tools-phylo-paml" + biosci_perl_bio_tools_run_alignment_clustalw: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-bio-tools-run-alignment-clustalw/app/perl-bio-tools-run-alignment-clustalw_shim_server.py"] + description: "BioScientist MCP server for perl-bio-tools-run-alignment-clustalw" + biosci_perl_bio_tools_run_alignment_tcoffee: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-bio-tools-run-alignment-tcoffee/app/perl-bio-tools-run-alignment-tcoffee_shim_server.py"] + description: "BioScientist MCP server for perl-bio-tools-run-alignment-tcoffee" + biosci_perl_bioperl_core: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-bioperl-core/app/perl-bioperl-core_shim_server.py"] + description: "BioScientist MCP server for perl-bioperl-core" + biosci_perl_business_isbn: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-business-isbn/app/perl-business-isbn_shim_server.py"] + description: "BioScientist MCP server for perl-business-isbn" + biosci_perl_business_isbn_data: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-business-isbn-data/app/perl-business-isbn-data_shim_server.py"] + description: "BioScientist MCP server for perl-business-isbn-data" + biosci_perl_cache_cache: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-cache-cache/app/perl-cache-cache_shim_server.py"] + description: "BioScientist MCP server for perl-cache-cache" + biosci_perl_capture_tiny: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-capture-tiny/app/perl-capture-tiny_shim_server.py"] + description: "BioScientist MCP server for perl-capture-tiny" + biosci_perl_cgi: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-cgi/app/perl-cgi_shim_server.py"] + description: "BioScientist MCP server for perl-cgi" + biosci_perl_class_data_inheritable: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-class-data-inheritable/app/perl-class-data-inheritable_shim_server.py"] + description: "BioScientist MCP server for perl-class-data-inheritable" + biosci_perl_class_inspector: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-class-inspector/app/perl-class-inspector_shim_server.py"] + description: "BioScientist MCP server for perl-class-inspector" + biosci_perl_class_method_modifiers: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-class-method-modifiers/app/perl-class-method-modifiers_shim_server.py"] + description: "BioScientist MCP server for perl-class-method-modifiers" + biosci_perl_clone: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-clone/app/perl-clone_shim_server.py"] + description: "BioScientist MCP server for perl-clone" + biosci_perl_common_sense: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-common-sense/app/perl-common-sense_shim_server.py"] + description: "BioScientist MCP server for perl-common-sense" + biosci_perl_compress_raw_bzip2: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-compress-raw-bzip2/app/perl-compress-raw-bzip2_shim_server.py"] + description: "BioScientist MCP server for perl-compress-raw-bzip2" + biosci_perl_compress_raw_zlib: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-compress-raw-zlib/app/perl-compress-raw-zlib_shim_server.py"] + description: "BioScientist MCP server for perl-compress-raw-zlib" + biosci_perl_config_general: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-config-general/app/perl-config-general_shim_server.py"] + description: "BioScientist MCP server for perl-config-general" + biosci_perl_constant: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-constant/app/perl-constant_shim_server.py"] + description: "BioScientist MCP server for perl-constant" + biosci_perl_convert_binary_c: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-convert-binary-c/app/perl-convert-binary-c_shim_server.py"] + description: "BioScientist MCP server for perl-convert-binary-c" + biosci_perl_convert_binhex: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-convert-binhex/app/perl-convert-binhex_shim_server.py"] + description: "BioScientist MCP server for perl-convert-binhex" + biosci_perl_cpan_meta_requirements: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-cpan-meta-requirements/app/perl-cpan-meta-requirements_shim_server.py"] + description: "BioScientist MCP server for perl-cpan-meta-requirements" + biosci_perl_crypt_rc4: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-crypt-rc4/app/perl-crypt-rc4_shim_server.py"] + description: "BioScientist MCP server for perl-crypt-rc4" + biosci_perl_data_dumper: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-data-dumper/app/perl-data-dumper_shim_server.py"] + description: "BioScientist MCP server for perl-data-dumper" + biosci_perl_data_optlist: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-data-optlist/app/perl-data-optlist_shim_server.py"] + description: "BioScientist MCP server for perl-data-optlist" + biosci_perl_data_stag: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-data-stag/app/perl-data-stag_shim_server.py"] + description: "BioScientist MCP server for perl-data-stag" + biosci_perl_date_format: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-date-format/app/perl-date-format_shim_server.py"] + description: "BioScientist MCP server for perl-date-format" + biosci_perl_db_file: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-db-file/app/perl-db-file_shim_server.py"] + description: "BioScientist MCP server for perl-db-file" + biosci_perl_dbd_mysql: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-dbd-mysql/app/perl-dbd-mysql_shim_server.py"] + description: "BioScientist MCP server for perl-dbd-mysql" + biosci_perl_dbd_sqlite: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-dbd-sqlite/app/perl-dbd-sqlite_shim_server.py"] + description: "BioScientist MCP server for perl-dbd-sqlite" + biosci_perl_dbi: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-dbi/app/perl-dbi_shim_server.py"] + description: "BioScientist MCP server for perl-dbi" + biosci_perl_devel_overloadinfo: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-devel-overloadinfo/app/perl-devel-overloadinfo_shim_server.py"] + description: "BioScientist MCP server for perl-devel-overloadinfo" + biosci_perl_devel_stacktrace: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-devel-stacktrace/app/perl-devel-stacktrace_shim_server.py"] + description: "BioScientist MCP server for perl-devel-stacktrace" + biosci_perl_digest_hmac: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-digest-hmac/app/perl-digest-hmac_shim_server.py"] + description: "BioScientist MCP server for perl-digest-hmac" + biosci_perl_digest_md5: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-digest-md5/app/perl-digest-md5_shim_server.py"] + description: "BioScientist MCP server for perl-digest-md5" + biosci_perl_digest_perl_md5: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-digest-perl-md5/app/perl-digest-perl-md5_shim_server.py"] + description: "BioScientist MCP server for perl-digest-perl-md5" + biosci_perl_digest_sha1: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-digest-sha1/app/perl-digest-sha1_shim_server.py"] + description: "BioScientist MCP server for perl-digest-sha1" + biosci_perl_email_date_format: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-email-date-format/app/perl-email-date-format_shim_server.py"] + description: "BioScientist MCP server for perl-email-date-format" + biosci_perl_encode: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-encode/app/perl-encode_shim_server.py"] + description: "BioScientist MCP server for perl-encode" + biosci_perl_encode_locale: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-encode-locale/app/perl-encode-locale_shim_server.py"] + description: "BioScientist MCP server for perl-encode-locale" + biosci_perl_error: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-error/app/perl-error_shim_server.py"] + description: "BioScientist MCP server for perl-error" + biosci_perl_exception_class: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-exception-class/app/perl-exception-class_shim_server.py"] + description: "BioScientist MCP server for perl-exception-class" + biosci_perl_exporter: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-exporter/app/perl-exporter_shim_server.py"] + description: "BioScientist MCP server for perl-exporter" + biosci_perl_exporter_tiny: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-exporter-tiny/app/perl-exporter-tiny_shim_server.py"] + description: "BioScientist MCP server for perl-exporter-tiny" + biosci_perl_extutils_cbuilder: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-extutils-cbuilder/app/perl-extutils-cbuilder_shim_server.py"] + description: "BioScientist MCP server for perl-extutils-cbuilder" + biosci_perl_extutils_makemaker: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-extutils-makemaker/app/perl-extutils-makemaker_shim_server.py"] + description: "BioScientist MCP server for perl-extutils-makemaker" + biosci_perl_extutils_manifest: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-extutils-manifest/app/perl-extutils-manifest_shim_server.py"] + description: "BioScientist MCP server for perl-extutils-manifest" + biosci_perl_extutils_parsexs: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-extutils-parsexs/app/perl-extutils-parsexs_shim_server.py"] + description: "BioScientist MCP server for perl-extutils-parsexs" + biosci_perl_ffi_checklib: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-ffi-checklib/app/perl-ffi-checklib_shim_server.py"] + description: "BioScientist MCP server for perl-ffi-checklib" + biosci_perl_file_listing: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-file-listing/app/perl-file-listing_shim_server.py"] + description: "BioScientist MCP server for perl-file-listing" + biosci_perl_file_path: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-file-path/app/perl-file-path_shim_server.py"] + description: "BioScientist MCP server for perl-file-path" + biosci_perl_file_slurp_tiny: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-file-slurp-tiny/app/perl-file-slurp-tiny_shim_server.py"] + description: "BioScientist MCP server for perl-file-slurp-tiny" + biosci_perl_file_sort: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-file-sort/app/perl-file-sort_shim_server.py"] + description: "BioScientist MCP server for perl-file-sort" + biosci_perl_file_spec: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-file-spec/app/perl-file-spec_shim_server.py"] + description: "BioScientist MCP server for perl-file-spec" + biosci_perl_file_temp: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-file-temp/app/perl-file-temp_shim_server.py"] + description: "BioScientist MCP server for perl-file-temp" + biosci_perl_file_which: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-file-which/app/perl-file-which_shim_server.py"] + description: "BioScientist MCP server for perl-file-which" + biosci_perl_font_afm: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-font-afm/app/perl-font-afm_shim_server.py"] + description: "BioScientist MCP server for perl-font-afm" + biosci_perl_font_ttf: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-font-ttf/app/perl-font-ttf_shim_server.py"] + description: "BioScientist MCP server for perl-font-ttf" + biosci_perl_gd: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-gd/app/perl-gd_shim_server.py"] + description: "BioScientist MCP server for perl-gd" + biosci_perl_getopt_long: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-getopt-long/app/perl-getopt-long_shim_server.py"] + description: "BioScientist MCP server for perl-getopt-long" + biosci_perl_graph: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-graph/app/perl-graph_shim_server.py"] + description: "BioScientist MCP server for perl-graph" + biosci_perl_graphviz: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-graphviz/app/perl-graphviz_shim_server.py"] + description: "BioScientist MCP server for perl-graphviz" + biosci_perl_html_element_extended: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-html-element-extended/app/perl-html-element-extended_shim_server.py"] + description: "BioScientist MCP server for perl-html-element-extended" + biosci_perl_html_entities_numbered: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-html-entities-numbered/app/perl-html-entities-numbered_shim_server.py"] + description: "BioScientist MCP server for perl-html-entities-numbered" + biosci_perl_html_formatter: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-html-formatter/app/perl-html-formatter_shim_server.py"] + description: "BioScientist MCP server for perl-html-formatter" + biosci_perl_html_parser: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-html-parser/app/perl-html-parser_shim_server.py"] + description: "BioScientist MCP server for perl-html-parser" + biosci_perl_html_tableextract: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-html-tableextract/app/perl-html-tableextract_shim_server.py"] + description: "BioScientist MCP server for perl-html-tableextract" + biosci_perl_html_tagset: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-html-tagset/app/perl-html-tagset_shim_server.py"] + description: "BioScientist MCP server for perl-html-tagset" + biosci_perl_html_tidy: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-html-tidy/app/perl-html-tidy_shim_server.py"] + description: "BioScientist MCP server for perl-html-tidy" + biosci_perl_html_tree: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-html-tree/app/perl-html-tree_shim_server.py"] + description: "BioScientist MCP server for perl-html-tree" + biosci_perl_html_treebuilder_xpath: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-html-treebuilder-xpath/app/perl-html-treebuilder-xpath_shim_server.py"] + description: "BioScientist MCP server for perl-html-treebuilder-xpath" + biosci_perl_http_cookies: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-http-cookies/app/perl-http-cookies_shim_server.py"] + description: "BioScientist MCP server for perl-http-cookies" + biosci_perl_http_daemon: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-http-daemon/app/perl-http-daemon_shim_server.py"] + description: "BioScientist MCP server for perl-http-daemon" + biosci_perl_http_date: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-http-date/app/perl-http-date_shim_server.py"] + description: "BioScientist MCP server for perl-http-date" + biosci_perl_http_message: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-http-message/app/perl-http-message_shim_server.py"] + description: "BioScientist MCP server for perl-http-message" + biosci_perl_http_negotiate: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-http-negotiate/app/perl-http-negotiate_shim_server.py"] + description: "BioScientist MCP server for perl-http-negotiate" + biosci_perl_image_info: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-image-info/app/perl-image-info_shim_server.py"] + description: "BioScientist MCP server for perl-image-info" + biosci_perl_image_size: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-image-size/app/perl-image-size_shim_server.py"] + description: "BioScientist MCP server for perl-image-size" + biosci_perl_importer: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-importer/app/perl-importer_shim_server.py"] + description: "BioScientist MCP server for perl-importer" + biosci_perl_io_compress: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-io-compress/app/perl-io-compress_shim_server.py"] + description: "BioScientist MCP server for perl-io-compress" + biosci_perl_io_html: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-io-html/app/perl-io-html_shim_server.py"] + description: "BioScientist MCP server for perl-io-html" + biosci_perl_io_sessiondata: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-io-sessiondata/app/perl-io-sessiondata_shim_server.py"] + description: "BioScientist MCP server for perl-io-sessiondata" + biosci_perl_io_socket_ssl: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-io-socket-ssl/app/perl-io-socket-ssl_shim_server.py"] + description: "BioScientist MCP server for perl-io-socket-ssl" + biosci_perl_io_string: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-io-string/app/perl-io-string_shim_server.py"] + description: "BioScientist MCP server for perl-io-string" + biosci_perl_io_stringy: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-io-stringy/app/perl-io-stringy_shim_server.py"] + description: "BioScientist MCP server for perl-io-stringy" + biosci_perl_io_tty: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-io-tty/app/perl-io-tty_shim_server.py"] + description: "BioScientist MCP server for perl-io-tty" + biosci_perl_io_zlib: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-io-zlib/app/perl-io-zlib_shim_server.py"] + description: "BioScientist MCP server for perl-io-zlib" + biosci_perl_ipc_cmd: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-ipc-cmd/app/perl-ipc-cmd_shim_server.py"] + description: "BioScientist MCP server for perl-ipc-cmd" + biosci_perl_ipc_sharelite: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-ipc-sharelite/app/perl-ipc-sharelite_shim_server.py"] + description: "BioScientist MCP server for perl-ipc-sharelite" + biosci_perl_jcode: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-jcode/app/perl-jcode_shim_server.py"] + description: "BioScientist MCP server for perl-jcode" + biosci_perl_json: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-json/app/perl-json_shim_server.py"] + description: "BioScientist MCP server for perl-json" + biosci_perl_json_pp: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-json-pp/app/perl-json-pp_shim_server.py"] + description: "BioScientist MCP server for perl-json-pp" + biosci_perl_json_xs: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-json-xs/app/perl-json-xs_shim_server.py"] + description: "BioScientist MCP server for perl-json-xs" + biosci_perl_lib: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-lib/app/perl-lib_shim_server.py"] + description: "BioScientist MCP server for perl-lib" + biosci_perl_libwww_perl: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-libwww-perl/app/perl-libwww-perl_shim_server.py"] + description: "BioScientist MCP server for perl-libwww-perl" + biosci_perl_libxml_perl: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-libxml-perl/app/perl-libxml-perl_shim_server.py"] + description: "BioScientist MCP server for perl-libxml-perl" + biosci_perl_list_moreutils: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-list-moreutils/app/perl-list-moreutils_shim_server.py"] + description: "BioScientist MCP server for perl-list-moreutils" + biosci_perl_list_moreutils_xs: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-list-moreutils-xs/app/perl-list-moreutils-xs_shim_server.py"] + description: "BioScientist MCP server for perl-list-moreutils-xs" + biosci_perl_locale_maketext_simple: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-locale-maketext-simple/app/perl-locale-maketext-simple_shim_server.py"] + description: "BioScientist MCP server for perl-locale-maketext-simple" + biosci_perl_lwp_mediatypes: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-lwp-mediatypes/app/perl-lwp-mediatypes_shim_server.py"] + description: "BioScientist MCP server for perl-lwp-mediatypes" + biosci_perl_lwp_protocol_https: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-lwp-protocol-https/app/perl-lwp-protocol-https_shim_server.py"] + description: "BioScientist MCP server for perl-lwp-protocol-https" + biosci_perl_mailtools: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-mailtools/app/perl-mailtools_shim_server.py"] + description: "BioScientist MCP server for perl-mailtools" + biosci_perl_math_bezier: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-math-bezier/app/perl-math-bezier_shim_server.py"] + description: "BioScientist MCP server for perl-math-bezier" + biosci_perl_math_cdf: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-math-cdf/app/perl-math-cdf_shim_server.py"] + description: "BioScientist MCP server for perl-math-cdf" + biosci_perl_math_derivative: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-math-derivative/app/perl-math-derivative_shim_server.py"] + description: "BioScientist MCP server for perl-math-derivative" + biosci_perl_math_random: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-math-random/app/perl-math-random_shim_server.py"] + description: "BioScientist MCP server for perl-math-random" + biosci_perl_math_round: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-math-round/app/perl-math-round_shim_server.py"] + description: "BioScientist MCP server for perl-math-round" + biosci_perl_math_spline: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-math-spline/app/perl-math-spline_shim_server.py"] + description: "BioScientist MCP server for perl-math-spline" + biosci_perl_math_vecstat: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-math-vecstat/app/perl-math-vecstat_shim_server.py"] + description: "BioScientist MCP server for perl-math-vecstat" + biosci_perl_mce: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-mce/app/perl-mce_shim_server.py"] + description: "BioScientist MCP server for perl-mce" + biosci_perl_metabolomics_fragment_annotation: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_perl-metabolomics-fragment-annotation/app/perl-metabolomics-fragment-annotation_shim_server.py"] + description: "BioScientist MCP server for perl-metabolomics-fragment-annotation" + biosci_perl_mime_lite: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-mime-lite/app/perl-mime-lite_shim_server.py"] + description: "BioScientist MCP server for perl-mime-lite" + biosci_perl_mime_tools: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-mime-tools/app/perl-mime-tools_shim_server.py"] + description: "BioScientist MCP server for perl-mime-tools" + biosci_perl_mime_types: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-mime-types/app/perl-mime-types_shim_server.py"] + description: "BioScientist MCP server for perl-mime-types" + biosci_perl_mldbm: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-mldbm/app/perl-mldbm_shim_server.py"] + description: "BioScientist MCP server for perl-mldbm" + biosci_perl_module_build: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-module-build/app/perl-module-build_shim_server.py"] + description: "BioScientist MCP server for perl-module-build" + biosci_perl_module_corelist: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-module-corelist/app/perl-module-corelist_shim_server.py"] + description: "BioScientist MCP server for perl-module-corelist" + biosci_perl_module_implementation: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-module-implementation/app/perl-module-implementation_shim_server.py"] + description: "BioScientist MCP server for perl-module-implementation" + biosci_perl_module_load: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-module-load/app/perl-module-load_shim_server.py"] + description: "BioScientist MCP server for perl-module-load" + biosci_perl_module_load_conditional: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-module-load-conditional/app/perl-module-load-conditional_shim_server.py"] + description: "BioScientist MCP server for perl-module-load-conditional" + biosci_perl_module_metadata: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-module-metadata/app/perl-module-metadata_shim_server.py"] + description: "BioScientist MCP server for perl-module-metadata" + biosci_perl_module_runtime: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-module-runtime/app/perl-module-runtime_shim_server.py"] + description: "BioScientist MCP server for perl-module-runtime" + biosci_perl_module_runtime_conflicts: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-module-runtime-conflicts/app/perl-module-runtime-conflicts_shim_server.py"] + description: "BioScientist MCP server for perl-module-runtime-conflicts" + biosci_perl_moo: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-moo/app/perl-moo_shim_server.py"] + description: "BioScientist MCP server for perl-moo" + biosci_perl_moose: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-moose/app/perl-moose_shim_server.py"] + description: "BioScientist MCP server for perl-moose" + biosci_perl_mro_compat: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-mro-compat/app/perl-mro-compat_shim_server.py"] + description: "BioScientist MCP server for perl-mro-compat" + biosci_perl_net_http: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-net-http/app/perl-net-http_shim_server.py"] + description: "BioScientist MCP server for perl-net-http" + biosci_perl_net_ssleay: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-net-ssleay/app/perl-net-ssleay_shim_server.py"] + description: "BioScientist MCP server for perl-net-ssleay" + biosci_perl_ntlm: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-ntlm/app/perl-ntlm_shim_server.py"] + description: "BioScientist MCP server for perl-ntlm" + biosci_perl_number_format: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-number-format/app/perl-number-format_shim_server.py"] + description: "BioScientist MCP server for perl-number-format" + biosci_perl_ole_storage_lite: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-ole-storage_lite/app/perl-ole-storage_lite_shim_server.py"] + description: "BioScientist MCP server for perl-ole-storage_lite" + biosci_perl_package_deprecationmanager: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-package-deprecationmanager/app/perl-package-deprecationmanager_shim_server.py"] + description: "BioScientist MCP server for perl-package-deprecationmanager" + biosci_perl_package_stash: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-package-stash/app/perl-package-stash_shim_server.py"] + description: "BioScientist MCP server for perl-package-stash" + biosci_perl_package_stash_xs: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-package-stash-xs/app/perl-package-stash-xs_shim_server.py"] + description: "BioScientist MCP server for perl-package-stash-xs" + biosci_perl_parallel_forkmanager: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-parallel-forkmanager/app/perl-parallel-forkmanager_shim_server.py"] + description: "BioScientist MCP server for perl-parallel-forkmanager" + biosci_perl_params_check: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-params-check/app/perl-params-check_shim_server.py"] + description: "BioScientist MCP server for perl-params-check" + biosci_perl_params_util: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-params-util/app/perl-params-util_shim_server.py"] + description: "BioScientist MCP server for perl-params-util" + biosci_perl_params_validate: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-params-validate/app/perl-params-validate_shim_server.py"] + description: "BioScientist MCP server for perl-params-validate" + biosci_perl_parent: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-parent/app/perl-parent_shim_server.py"] + description: "BioScientist MCP server for perl-parent" + biosci_perl_parse_recdescent: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-parse-recdescent/app/perl-parse-recdescent_shim_server.py"] + description: "BioScientist MCP server for perl-parse-recdescent" + biosci_perl_path_tiny: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-path-tiny/app/perl-path-tiny_shim_server.py"] + description: "BioScientist MCP server for perl-path-tiny" + biosci_perl_pathtools: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-pathtools/app/perl-pathtools_shim_server.py"] + description: "BioScientist MCP server for perl-pathtools" + biosci_perl_pdf_api2: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-pdf-api2/app/perl-pdf-api2_shim_server.py"] + description: "BioScientist MCP server for perl-pdf-api2" + biosci_perl_perlio_gzip: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-perlio-gzip/app/perl-perlio-gzip_shim_server.py"] + description: "BioScientist MCP server for perl-perlio-gzip" + biosci_perl_pod_escapes: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-pod-escapes/app/perl-pod-escapes_shim_server.py"] + description: "BioScientist MCP server for perl-pod-escapes" + biosci_perl_pod_usage: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-pod-usage/app/perl-pod-usage_shim_server.py"] + description: "BioScientist MCP server for perl-pod-usage" + biosci_perl_postscript: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-postscript/app/perl-postscript_shim_server.py"] + description: "BioScientist MCP server for perl-postscript" + biosci_perl_readonly: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-readonly/app/perl-readonly_shim_server.py"] + description: "BioScientist MCP server for perl-readonly" + biosci_perl_regexp_common: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-regexp-common/app/perl-regexp-common_shim_server.py"] + description: "BioScientist MCP server for perl-regexp-common" + biosci_perl_role_tiny: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-role-tiny/app/perl-role-tiny_shim_server.py"] + description: "BioScientist MCP server for perl-role-tiny" + biosci_perl_scalar_list_utils: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-scalar-list-utils/app/perl-scalar-list-utils_shim_server.py"] + description: "BioScientist MCP server for perl-scalar-list-utils" + biosci_perl_sereal: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-sereal/app/perl-sereal_shim_server.py"] + description: "BioScientist MCP server for perl-sereal" + biosci_perl_sereal_decoder: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-sereal-decoder/app/perl-sereal-decoder_shim_server.py"] + description: "BioScientist MCP server for perl-sereal-decoder" + biosci_perl_sereal_encoder: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-sereal-encoder/app/perl-sereal-encoder_shim_server.py"] + description: "BioScientist MCP server for perl-sereal-encoder" + biosci_perl_set_intervaltree: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-set-intervaltree/app/perl-set-intervaltree_shim_server.py"] + description: "BioScientist MCP server for perl-set-intervaltree" + biosci_perl_soap_lite: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-soap-lite/app/perl-soap-lite_shim_server.py"] + description: "BioScientist MCP server for perl-soap-lite" + biosci_perl_socket: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-socket/app/perl-socket_shim_server.py"] + description: "BioScientist MCP server for perl-socket" + biosci_perl_sort_naturally: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-sort-naturally/app/perl-sort-naturally_shim_server.py"] + description: "BioScientist MCP server for perl-sort-naturally" + biosci_perl_spreadsheet_parseexcel: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-spreadsheet-parseexcel/app/perl-spreadsheet-parseexcel_shim_server.py"] + description: "BioScientist MCP server for perl-spreadsheet-parseexcel" + biosci_perl_spreadsheet_writeexcel: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-spreadsheet-writeexcel/app/perl-spreadsheet-writeexcel_shim_server.py"] + description: "BioScientist MCP server for perl-spreadsheet-writeexcel" + biosci_perl_statistics_basic: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-statistics-basic/app/perl-statistics-basic_shim_server.py"] + description: "BioScientist MCP server for perl-statistics-basic" + biosci_perl_statistics_descriptive: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-statistics-descriptive/app/perl-statistics-descriptive_shim_server.py"] + description: "BioScientist MCP server for perl-statistics-descriptive" + biosci_perl_storable: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-storable/app/perl-storable_shim_server.py"] + description: "BioScientist MCP server for perl-storable" + biosci_perl_sub_exporter: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-sub-exporter/app/perl-sub-exporter_shim_server.py"] + description: "BioScientist MCP server for perl-sub-exporter" + biosci_perl_sub_exporter_progressive: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-sub-exporter-progressive/app/perl-sub-exporter-progressive_shim_server.py"] + description: "BioScientist MCP server for perl-sub-exporter-progressive" + biosci_perl_sub_identify: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-sub-identify/app/perl-sub-identify_shim_server.py"] + description: "BioScientist MCP server for perl-sub-identify" + biosci_perl_sub_install: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-sub-install/app/perl-sub-install_shim_server.py"] + description: "BioScientist MCP server for perl-sub-install" + biosci_perl_sub_name: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-sub-name/app/perl-sub-name_shim_server.py"] + description: "BioScientist MCP server for perl-sub-name" + biosci_perl_sub_quote: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-sub-quote/app/perl-sub-quote_shim_server.py"] + description: "BioScientist MCP server for perl-sub-quote" + biosci_perl_sub_uplevel: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-sub-uplevel/app/perl-sub-uplevel_shim_server.py"] + description: "BioScientist MCP server for perl-sub-uplevel" + biosci_perl_svg_graph: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-svg-graph/app/perl-svg-graph_shim_server.py"] + description: "BioScientist MCP server for perl-svg-graph" + biosci_perl_task_weaken: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-task-weaken/app/perl-task-weaken_shim_server.py"] + description: "BioScientist MCP server for perl-task-weaken" + biosci_perl_template_toolkit: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-template-toolkit/app/perl-template-toolkit_shim_server.py"] + description: "BioScientist MCP server for perl-template-toolkit" + biosci_perl_term_table: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-term-table/app/perl-term-table_shim_server.py"] + description: "BioScientist MCP server for perl-term-table" + biosci_perl_test: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-test/app/perl-test_shim_server.py"] + description: "BioScientist MCP server for perl-test" + biosci_perl_test2_suite: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-test2-suite/app/perl-test2-suite_shim_server.py"] + description: "BioScientist MCP server for perl-test2-suite" + biosci_perl_test_deep: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-test-deep/app/perl-test-deep_shim_server.py"] + description: "BioScientist MCP server for perl-test-deep" + biosci_perl_test_differences: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-test-differences/app/perl-test-differences_shim_server.py"] + description: "BioScientist MCP server for perl-test-differences" + biosci_perl_test_exception: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-test-exception/app/perl-test-exception_shim_server.py"] + description: "BioScientist MCP server for perl-test-exception" + biosci_perl_test_fatal: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-test-fatal/app/perl-test-fatal_shim_server.py"] + description: "BioScientist MCP server for perl-test-fatal" + biosci_perl_test_harness: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-test-harness/app/perl-test-harness_shim_server.py"] + description: "BioScientist MCP server for perl-test-harness" + biosci_perl_test_leaktrace: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-test-leaktrace/app/perl-test-leaktrace_shim_server.py"] + description: "BioScientist MCP server for perl-test-leaktrace" + biosci_perl_test_more: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-test-more/app/perl-test-more_shim_server.py"] + description: "BioScientist MCP server for perl-test-more" + biosci_perl_test_requiresinternet: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-test-requiresinternet/app/perl-test-requiresinternet_shim_server.py"] + description: "BioScientist MCP server for perl-test-requiresinternet" + biosci_perl_test_warn: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-test-warn/app/perl-test-warn_shim_server.py"] + description: "BioScientist MCP server for perl-test-warn" + biosci_perl_text_csv: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-text-csv/app/perl-text-csv_shim_server.py"] + description: "BioScientist MCP server for perl-text-csv" + biosci_perl_text_format: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-text-format/app/perl-text-format_shim_server.py"] + description: "BioScientist MCP server for perl-text-format" + biosci_perl_threaded: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-threaded/app/perl-threaded_shim_server.py"] + description: "BioScientist MCP server for perl-threaded" + biosci_perl_tie_ixhash: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-tie-ixhash/app/perl-tie-ixhash_shim_server.py"] + description: "BioScientist MCP server for perl-tie-ixhash" + biosci_perl_time_hires: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-time-hires/app/perl-time-hires_shim_server.py"] + description: "BioScientist MCP server for perl-time-hires" + biosci_perl_time_local: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-time-local/app/perl-time-local_shim_server.py"] + description: "BioScientist MCP server for perl-time-local" + biosci_perl_tree_dag_node: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-tree-dag_node/app/perl-tree-dag_node_shim_server.py"] + description: "BioScientist MCP server for perl-tree-dag_node" + biosci_perl_try_tiny: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-try-tiny/app/perl-try-tiny_shim_server.py"] + description: "BioScientist MCP server for perl-try-tiny" + biosci_perl_type_tiny: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-type-tiny/app/perl-type-tiny_shim_server.py"] + description: "BioScientist MCP server for perl-type-tiny" + biosci_perl_unicode_map: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-unicode-map/app/perl-unicode-map_shim_server.py"] + description: "BioScientist MCP server for perl-unicode-map" + biosci_perl_uri: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-uri/app/perl-uri_shim_server.py"] + description: "BioScientist MCP server for perl-uri" + biosci_perl_url_encode: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-url-encode/app/perl-url-encode_shim_server.py"] + description: "BioScientist MCP server for perl-url-encode" + biosci_perl_version: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-version/app/perl-version_shim_server.py"] + description: "BioScientist MCP server for perl-version" + biosci_perl_www_robotrules: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-www-robotrules/app/perl-www-robotrules_shim_server.py"] + description: "BioScientist MCP server for perl-www-robotrules" + biosci_perl_xml_dom: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-xml-dom/app/perl-xml-dom_shim_server.py"] + description: "BioScientist MCP server for perl-xml-dom" + biosci_perl_xml_filter_buffertext: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-xml-filter-buffertext/app/perl-xml-filter-buffertext_shim_server.py"] + description: "BioScientist MCP server for perl-xml-filter-buffertext" + biosci_perl_xml_libxml: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-xml-libxml/app/perl-xml-libxml_shim_server.py"] + description: "BioScientist MCP server for perl-xml-libxml" + biosci_perl_xml_libxslt: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-xml-libxslt/app/perl-xml-libxslt_shim_server.py"] + description: "BioScientist MCP server for perl-xml-libxslt" + biosci_perl_xml_namespacesupport: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-xml-namespacesupport/app/perl-xml-namespacesupport_shim_server.py"] + description: "BioScientist MCP server for perl-xml-namespacesupport" + biosci_perl_xml_parser: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-xml-parser/app/perl-xml-parser_shim_server.py"] + description: "BioScientist MCP server for perl-xml-parser" + biosci_perl_xml_regexp: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-xml-regexp/app/perl-xml-regexp_shim_server.py"] + description: "BioScientist MCP server for perl-xml-regexp" + biosci_perl_xml_sax: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-xml-sax/app/perl-xml-sax_shim_server.py"] + description: "BioScientist MCP server for perl-xml-sax" + biosci_perl_xml_sax_base: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-xml-sax-base/app/perl-xml-sax-base_shim_server.py"] + description: "BioScientist MCP server for perl-xml-sax-base" + biosci_perl_xml_sax_expat: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-xml-sax-expat/app/perl-xml-sax-expat_shim_server.py"] + description: "BioScientist MCP server for perl-xml-sax-expat" + biosci_perl_xml_sax_writer: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-xml-sax-writer/app/perl-xml-sax-writer_shim_server.py"] + description: "BioScientist MCP server for perl-xml-sax-writer" + biosci_perl_xml_simple: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-xml-simple/app/perl-xml-simple_shim_server.py"] + description: "BioScientist MCP server for perl-xml-simple" + biosci_perl_xml_twig: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-xml-twig/app/perl-xml-twig_shim_server.py"] + description: "BioScientist MCP server for perl-xml-twig" + biosci_perl_xml_writer: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-xml-writer/app/perl-xml-writer_shim_server.py"] + description: "BioScientist MCP server for perl-xml-writer" + biosci_perl_xml_xpath: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-xml-xpath/app/perl-xml-xpath_shim_server.py"] + description: "BioScientist MCP server for perl-xml-xpath" + biosci_perl_xml_xpathengine: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-xml-xpathengine/app/perl-xml-xpathengine_shim_server.py"] + description: "BioScientist MCP server for perl-xml-xpathengine" + biosci_perl_xsloader: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-xsloader/app/perl-xsloader_shim_server.py"] + description: "BioScientist MCP server for perl-xsloader" + biosci_perl_yaml: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_perl-yaml/app/perl-yaml_shim_server.py"] + description: "BioScientist MCP server for perl-yaml" + biosci_phyml: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_phyml/app/phyml_shim_server.py"] + description: "BioScientist MCP server for phyml" + biosci_piaso: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_piaso/app/piaso_shim_server.py"] + description: "BioScientist MCP server for piaso" + biosci_picard: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_picard/app/picard_shim_server.py"] + description: "BioScientist MCP server for picard" + biosci_pixelator: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_pixelator/app/pixelator_shim_server.py"] + description: "BioScientist MCP server for pixelator" + biosci_portcullis: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_portcullis/app/portcullis_shim_server.py"] + description: "BioScientist MCP server for portcullis" + biosci_prank: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_prank/app/prank_shim_server.py"] + description: "BioScientist MCP server for prank" + biosci_primer3: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_primer3/app/primer3_shim_server.py"] + description: "BioScientist MCP server for primer3" + biosci_prodigal: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_prodigal/app/prodigal_shim_server.py"] + description: "BioScientist MCP server for prodigal" + biosci_prokka: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_prokka/app/prokka_shim_server.py"] + description: "BioScientist MCP server for prokka" + biosci_prophyle: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_prophyle/app/prophyle_shim_server.py"] + description: "BioScientist MCP server for prophyle" + biosci_proteinortho: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_proteinortho/app/proteinortho_shim_server.py"] + description: "BioScientist MCP server for proteinortho" + biosci_proteomiqon_peptidedb: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_proteomiqon-peptidedb/app/proteomiqon-peptidedb_shim_server.py"] + description: "BioScientist MCP server for proteomiqon-peptidedb" + biosci_proteomiqon_peptidedb: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_proteomiqon-peptidedb/app/proteomiqon-peptidedb_shim_server.py"] + description: "BioScientist MCP server for proteomiqon-peptidedb" + biosci_proteomiqon_peptidespectrummatching: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_proteomiqon-peptidespectrummatching/app/proteomiqon-peptidespectrummatching_shim_server.py"] + description: "BioScientist MCP server for proteomiqon-peptidespectrummatching" + biosci_pubchempy: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_pubchempy/app/pubchempy_shim_server.py"] + description: "BioScientist MCP server for pubchempy" + biosci_py2bit: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_py2bit/app/py2bit_shim_server.py"] + description: "BioScientist MCP server for py2bit" + biosci_pybedtools: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_pybedtools/app/pybedtools_shim_server.py"] + description: "BioScientist MCP server for pybedtools" + biosci_pybigwig: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_pybigwig/app/pybigwig_shim_server.py"] + description: "BioScientist MCP server for pybigwig" + biosci_pycistopic: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_pycistopic/app/pycistopic_shim_server.py"] + description: "BioScientist MCP server for pycistopic" + biosci_pyfastx: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_pyfastx/app/pyfastx_shim_server.py"] + description: "BioScientist MCP server for pyfastx" + biosci_pyhmmer: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_pyhmmer/app/pyhmmer_shim_server.py"] + description: "BioScientist MCP server for pyhmmer" + biosci_pyranges: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_pyranges/app/pyranges_shim_server.py"] + description: "BioScientist MCP server for pyranges" + biosci_pyrodigal: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_pyrodigal/app/pyrodigal_shim_server.py"] + description: "BioScientist MCP server for pyrodigal" + biosci_pyroe: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_pyroe/app/pyroe_shim_server.py"] + description: "BioScientist MCP server for pyroe" + biosci_pysam: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_pysam/app/pysam_shim_server.py"] + description: "BioScientist MCP server for pysam" + biosci_pysftp: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_pysftp/app/pysftp_shim_server.py"] + description: "BioScientist MCP server for pysftp" + biosci_pyspoa: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_pyspoa/app/pyspoa_shim_server.py"] + description: "BioScientist MCP server for pyspoa" + biosci_pytabix: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_pytabix/app/pytabix_shim_server.py"] + description: "BioScientist MCP server for pytabix" + biosci_pyteomics: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_pyteomics/app/pyteomics_shim_server.py"] + description: "BioScientist MCP server for pyteomics" + biosci_python_edlib: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_python-edlib/app/python-edlib_shim_server.py"] + description: "BioScientist MCP server for python-edlib" + biosci_pyvcf3: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_pyvcf3/app/pyvcf3_shim_server.py"] + description: "BioScientist MCP server for pyvcf3" + biosci_qcatch: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_qcatch/app/qcatch_shim_server.py"] + description: "BioScientist MCP server for qcatch" + biosci_qualimap: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_qualimap/app/qualimap_shim_server.py"] + description: "BioScientist MCP server for qualimap" + biosci_r_abdiv: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_r-abdiv/app/r-abdiv_shim_server.py"] + description: "BioScientist MCP server for r-abdiv" + biosci_r_acidbase: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_r-acidbase/app/r-acidbase_shim_server.py"] + description: "BioScientist MCP server for r-acidbase" + biosci_r_archr: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_r-archr/app/r-archr_shim_server.py"] + description: "BioScientist MCP server for r-archr" + biosci_r_azimuth: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_r-azimuth/app/r-azimuth_shim_server.py"] + description: "BioScientist MCP server for r-azimuth" + biosci_r_basejump: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_r-basejump/app/r-basejump_shim_server.py"] + description: "BioScientist MCP server for r-basejump" + biosci_r_beyondcell: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_r-beyondcell/app/r-beyondcell_shim_server.py"] + description: "BioScientist MCP server for r-beyondcell" + biosci_r_cdseq: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_r-cdseq/app/r-cdseq_shim_server.py"] + description: "BioScientist MCP server for r-cdseq" + biosci_r_dwls: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_r-dwls/app/r-dwls_shim_server.py"] + description: "BioScientist MCP server for r-dwls" + biosci_r_epitrace: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_r-epitrace/app/r-epitrace_shim_server.py"] + description: "BioScientist MCP server for r-epitrace" + biosci_r_flanders: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_r-flanders/app/r-flanders_shim_server.py"] + description: "BioScientist MCP server for r-flanders" + biosci_r_goalie: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_r-goalie/app/r-goalie_shim_server.py"] + description: "BioScientist MCP server for r-goalie" + biosci_r_mams: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_r-mams/app/r-mams_shim_server.py"] + description: "BioScientist MCP server for r-mams" + biosci_r_redeemr: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_r-redeemr/app/r-redeemr_shim_server.py"] + description: "BioScientist MCP server for r-redeemr" + biosci_r_restfulr: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_r-restfulr/app/r-restfulr_shim_server.py"] + description: "BioScientist MCP server for r-restfulr" + biosci_r_sceasy: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_r-sceasy/app/r-sceasy_shim_server.py"] + description: "BioScientist MCP server for r-sceasy" + biosci_r_scopfunctions: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_r-scopfunctions/app/r-scopfunctions_shim_server.py"] + description: "BioScientist MCP server for r-scopfunctions" + biosci_r_scpred: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_r-scpred/app/r-scpred_shim_server.py"] + description: "BioScientist MCP server for r-scpred" + biosci_r_seurat: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_r-seurat/app/r-seurat_shim_server.py"] + description: "BioScientist MCP server for r-seurat" + biosci_r_seurat_data: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_r-seurat-data/app/r-seurat-data_shim_server.py"] + description: "BioScientist MCP server for r-seurat-data" + biosci_r_seurat_disk: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_r-seurat-disk/app/r-seurat-disk_shim_server.py"] + description: "BioScientist MCP server for r-seurat-disk" + biosci_r_seurat_scripts: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_r-seurat-scripts/app/r-seurat-scripts_shim_server.py"] + description: "BioScientist MCP server for r-seurat-scripts" + biosci_r_signac: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_r-signac/app/r-signac_shim_server.py"] + description: "BioScientist MCP server for r-signac" + biosci_raxml: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_raxml/app/raxml_shim_server.py"] + description: "BioScientist MCP server for raxml" + biosci_repeatmasker: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_repeatmasker/app/repeatmasker_shim_server.py"] + description: "BioScientist MCP server for repeatmasker" + biosci_rgi: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_rgi/app/rgi_shim_server.py"] + description: "BioScientist MCP server for rgi" + biosci_rnastructure: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_rnastructure/app/rnastructure_shim_server.py"] + description: "BioScientist MCP server for rnastructure" + biosci_rnftools: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_rnftools/app/rnftools_shim_server.py"] + description: "BioScientist MCP server for rnftools" + biosci_rsa: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_rsa/app/rsa_shim_server.py"] + description: "BioScientist MCP server for rsa" + biosci_rscape: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_rscape/app/rscape_shim_server.py"] + description: "BioScientist MCP server for rscape" + biosci_rsem: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_rsem/app/rsem_shim_server.py"] + description: "BioScientist MCP server for rsem" + biosci_rseqc: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_rseqc/app/rseqc_shim_server.py"] + description: "BioScientist MCP server for rseqc" + biosci_rust_bio_tools: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_rust-bio-tools/app/rust-bio-tools_shim_server.py"] + description: "BioScientist MCP server for rust-bio-tools" + biosci_sage_proteomics: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_sage-proteomics/app/sage-proteomics_shim_server.py"] + description: "BioScientist MCP server for sage-proteomics" + biosci_samap: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_samap/app/samap_shim_server.py"] + description: "BioScientist MCP server for samap" + biosci_sambamba: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_sambamba/app/sambamba_shim_server.py"] + description: "BioScientist MCP server for sambamba" + biosci_samblaster: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_samblaster/app/samblaster_shim_server.py"] + description: "BioScientist MCP server for samblaster" + biosci_samsift: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_samsift/app/samsift_shim_server.py"] + description: "BioScientist MCP server for samsift" + biosci_samtools: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_samtools/app/samtools_shim_server.py"] + description: "BioScientist MCP server for samtools" + biosci_samtools: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_samtools/app/samtools_shim_server.py"] + description: "BioScientist MCP server for samtools" + biosci_sc_musketeers: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_sc-musketeers/app/sc-musketeers_shim_server.py"] + description: "BioScientist MCP server for sc-musketeers" + biosci_scaden: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_scaden/app/scaden_shim_server.py"] + description: "BioScientist MCP server for scaden" + biosci_scanpy: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_scanpy/app/scanpy_shim_server.py"] + description: "BioScientist MCP server for scanpy" + biosci_scanpy_cli: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_scanpy-cli/app/scanpy-cli_shim_server.py"] + description: "BioScientist MCP server for scanpy-cli" + biosci_scanpy_scripts: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_scanpy-scripts/app/scanpy-scripts_shim_server.py"] + description: "BioScientist MCP server for scanpy-scripts" + biosci_scar: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_scar/app/scar_shim_server.py"] + description: "BioScientist MCP server for scar" + biosci_scarches: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_scarches/app/scarches_shim_server.py"] + description: "BioScientist MCP server for scarches" + biosci_sccaf: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_sccaf/app/sccaf_shim_server.py"] + description: "BioScientist MCP server for sccaf" + biosci_sccellfie: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_sccellfie/app/sccellfie_shim_server.py"] + description: "BioScientist MCP server for sccellfie" + biosci_scelvis: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_scelvis/app/scelvis_shim_server.py"] + description: "BioScientist MCP server for scelvis" + biosci_scepia: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_scepia/app/scepia_shim_server.py"] + description: "BioScientist MCP server for scepia" + biosci_scglue: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_scglue/app/scglue_shim_server.py"] + description: "BioScientist MCP server for scglue" + biosci_scib: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_scib/app/scib_shim_server.py"] + description: "BioScientist MCP server for scib" + biosci_scirpy: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_scirpy/app/scirpy_shim_server.py"] + description: "BioScientist MCP server for scirpy" + biosci_scmidas: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_scmidas/app/scmidas_shim_server.py"] + description: "BioScientist MCP server for scmidas" + biosci_scpred_cli: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_scpred-cli/app/scpred-cli_shim_server.py"] + description: "BioScientist MCP server for scpred-cli" + biosci_scrnasim_toolz: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_scrnasim-toolz/app/scrnasim-toolz_shim_server.py"] + description: "BioScientist MCP server for scrnasim-toolz" + biosci_scspectra: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_scspectra/app/scspectra_shim_server.py"] + description: "BioScientist MCP server for scspectra" + biosci_scstem: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_scstem/app/scstem_shim_server.py"] + description: "BioScientist MCP server for scstem" + biosci_sctriangulate: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_sctriangulate/app/sctriangulate_shim_server.py"] + description: "BioScientist MCP server for sctriangulate" + biosci_scvelo: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_scvelo/app/scvelo_shim_server.py"] + description: "BioScientist MCP server for scvelo" + biosci_scvi: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_scvi/app/scvi_shim_server.py"] + description: "BioScientist MCP server for scvi" + biosci_scvi_tools: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_scvi-tools/app/scvi-tools_shim_server.py"] + description: "BioScientist MCP server for scvi-tools" + biosci_scvis: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_scvis/app/scvis_shim_server.py"] + description: "BioScientist MCP server for scvis" + biosci_scvis_galaxy: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_scvis_galaxy/app/scvis_galaxy_shim_server.py"] + description: "BioScientist MCP server for scvis_galaxy" + biosci_scxmatch: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_scxmatch/app/scxmatch_shim_server.py"] + description: "BioScientist MCP server for scxmatch" + biosci_sdeper: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_sdeper/app/sdeper_shim_server.py"] + description: "BioScientist MCP server for sdeper" + biosci_seacells: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_seacells/app/seacells_shim_server.py"] + description: "BioScientist MCP server for seacells" + biosci_searchgui: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_searchgui/app/searchgui_shim_server.py"] + description: "BioScientist MCP server for searchgui" + biosci_segemehl: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_segemehl/app/segemehl_shim_server.py"] + description: "BioScientist MCP server for segemehl" + biosci_sentieon: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_sentieon/app/sentieon_shim_server.py"] + description: "BioScientist MCP server for sentieon" + biosci_sepp: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_sepp/app/sepp_shim_server.py"] + description: "BioScientist MCP server for sepp" + biosci_seq2science: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_seq2science/app/seq2science_shim_server.py"] + description: "BioScientist MCP server for seq2science" + biosci_seqcluster: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_seqcluster/app/seqcluster_shim_server.py"] + description: "BioScientist MCP server for seqcluster" + biosci_seqfu: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_seqfu/app/seqfu_shim_server.py"] + description: "BioScientist MCP server for seqfu" + biosci_seqkit: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_seqkit/app/seqkit_shim_server.py"] + description: "BioScientist MCP server for seqkit" + biosci_seqtk: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_seqtk/app/seqtk_shim_server.py"] + description: "BioScientist MCP server for seqtk" + biosci_seurat_scripts: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_seurat-scripts/app/seurat-scripts_shim_server.py"] + description: "BioScientist MCP server for seurat-scripts" + biosci_sevenbridges_python: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_sevenbridges-python/app/sevenbridges-python_shim_server.py"] + description: "BioScientist MCP server for sevenbridges-python" + biosci_shiba: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_shiba/app/shiba_shim_server.py"] + description: "BioScientist MCP server for shiba" + biosci_simo_omics: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_simo-omics/app/simo-omics_shim_server.py"] + description: "BioScientist MCP server for simo-omics" + biosci_simplejson: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_simplejson/app/simplejson_shim_server.py"] + description: "BioScientist MCP server for simplejson" + biosci_sina: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_sina/app/sina_shim_server.py"] + description: "BioScientist MCP server for sina" + biosci_sincei: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_sincei/app/sincei_shim_server.py"] + description: "BioScientist MCP server for sincei" + biosci_slow5tools: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_slow5tools/app/slow5tools_shim_server.py"] + description: "BioScientist MCP server for slow5tools" + biosci_snakemake: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_snakemake/app/snakemake_shim_server.py"] + description: "BioScientist MCP server for snakemake" + biosci_snakemake_interface_executor_plugins: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_snakemake-interface-executor-plugins/app/snakemake-interface-executor-plugins_shim_server.py"] + description: "BioScientist MCP server for snakemake-interface-executor-plugins" + biosci_snakemake_interface_report_plugins: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_snakemake-interface-report-plugins/app/snakemake-interface-report-plugins_shim_server.py"] + description: "BioScientist MCP server for snakemake-interface-report-plugins" + biosci_snakemake_interface_storage_plugins: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_snakemake-interface-storage-plugins/app/snakemake-interface-storage-plugins_shim_server.py"] + description: "BioScientist MCP server for snakemake-interface-storage-plugins" + biosci_snakemake_minimal: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_snakemake-minimal/app/snakemake-minimal_shim_server.py"] + description: "BioScientist MCP server for snakemake-minimal" + biosci_snakemake_wrapper_utils: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_snakemake-wrapper-utils/app/snakemake-wrapper-utils_shim_server.py"] + description: "BioScientist MCP server for snakemake-wrapper-utils" + biosci_snakesv: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_snakesv/app/snakesv_shim_server.py"] + description: "BioScientist MCP server for snakesv" + biosci_snippy: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_snippy/app/snippy_shim_server.py"] + description: "BioScientist MCP server for snippy" + biosci_snp2cell: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_help_txt/mcp_snp2cell/app/snp2cell_shim_server.py"] + description: "BioScientist MCP server for snp2cell" + biosci_snpeff: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_snpeff/app/snpeff_shim_server.py"] + description: "BioScientist MCP server for snpeff" + biosci_snpsift: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_snpsift/app/snpsift_shim_server.py"] + description: "BioScientist MCP server for snpsift" + biosci_sopa: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_sopa/app/sopa_shim_server.py"] + description: "BioScientist MCP server for sopa" + biosci_sortmerna: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_sortmerna/app/sortmerna_shim_server.py"] + description: "BioScientist MCP server for sortmerna" + biosci_sourmash: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_sourmash/app/sourmash_shim_server.py"] + description: "BioScientist MCP server for sourmash" + biosci_spades: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_spades/app/spades_shim_server.py"] + description: "BioScientist MCP server for spades" + biosci_spagrn: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_spagrn/app/spagrn_shim_server.py"] + description: "BioScientist MCP server for spagrn" + biosci_spapros: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_spapros/app/spapros_shim_server.py"] + description: "BioScientist MCP server for spapros" + biosci_spatialleiden: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_spatialleiden/app/spatialleiden_shim_server.py"] + description: "BioScientist MCP server for spatialleiden" + biosci_sra_tools: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_sra-tools/app/sra-tools_shim_server.py"] + description: "BioScientist MCP server for sra-tools" + biosci_stacks: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_stacks/app/stacks_shim_server.py"] + description: "BioScientist MCP server for stacks" + biosci_star: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_star/app/star_shim_server.py"] + description: "BioScientist MCP server for star" + biosci_star_fusion: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_star-fusion/app/star-fusion_shim_server.py"] + description: "BioScientist MCP server for star-fusion" + biosci_stream: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_stream/app/stream_shim_server.py"] + description: "BioScientist MCP server for stream" + biosci_stringtie: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_stringtie/app/stringtie_shim_server.py"] + description: "BioScientist MCP server for stringtie" + biosci_subread: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_subread/app/subread_shim_server.py"] + description: "BioScientist MCP server for subread" + biosci_switchtfi: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_switchtfi/app/switchtfi_shim_server.py"] + description: "BioScientist MCP server for switchtfi" + biosci_t_coffee: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_t-coffee/app/t-coffee_shim_server.py"] + description: "BioScientist MCP server for t-coffee" + biosci_t_coffee: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_t_coffee/app/t_coffee_shim_server.py"] + description: "BioScientist MCP server for t_coffee" + biosci_tabix: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_tabix/app/tabix_shim_server.py"] + description: "BioScientist MCP server for tabix" + biosci_tabixpp: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_tabixpp/app/tabixpp_shim_server.py"] + description: "BioScientist MCP server for tabixpp" + biosci_talon: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_talon/app/talon_shim_server.py"] + description: "BioScientist MCP server for talon" + biosci_taxonkit: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_taxonkit/app/taxonkit_shim_server.py"] + description: "BioScientist MCP server for taxonkit" + biosci_tb_profiler: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_tb-profiler/app/tb-profiler_shim_server.py"] + description: "BioScientist MCP server for tb-profiler" + biosci_tidyp: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_tidyp/app/tidyp_shim_server.py"] + description: "BioScientist MCP server for tidyp" + biosci_tirank: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_tirank/app/tirank_shim_server.py"] + description: "BioScientist MCP server for tirank" + biosci_tmalign: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_tmalign/app/tmalign_shim_server.py"] + description: "BioScientist MCP server for tmalign" + biosci_treetime: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_treetime/app/treetime_shim_server.py"] + description: "BioScientist MCP server for treetime" + biosci_trim_galore: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_trim-galore/app/trim-galore_shim_server.py"] + description: "BioScientist MCP server for trim-galore" + biosci_trimadap: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_trimadap/app/trimadap_shim_server.py"] + description: "BioScientist MCP server for trimadap" + biosci_trimmomatic: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_trimmomatic/app/trimmomatic_shim_server.py"] + description: "BioScientist MCP server for trimmomatic" + biosci_trinity: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_trinity/app/trinity_shim_server.py"] + description: "BioScientist MCP server for trinity" + biosci_trnascan_se: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_trnascan-se/app/trnascan-se_shim_server.py"] + description: "BioScientist MCP server for trnascan-se" + biosci_ucsc_bedtobigbed: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_ucsc-bedtobigbed/app/ucsc-bedtobigbed_shim_server.py"] + description: "BioScientist MCP server for ucsc-bedtobigbed" + biosci_ucsc_cell_browser: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_ucsc-cell-browser/app/ucsc-cell-browser_shim_server.py"] + description: "BioScientist MCP server for ucsc-cell-browser" + biosci_ucsc_fatotwobit: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_ucsc-fatotwobit/app/ucsc-fatotwobit_shim_server.py"] + description: "BioScientist MCP server for ucsc-fatotwobit" + biosci_ucsc_gtftogenepred: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_ucsc-gtftogenepred/app/ucsc-gtftogenepred_shim_server.py"] + description: "BioScientist MCP server for ucsc-gtftogenepred" + biosci_ucsc_liftover: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_ucsc-liftover/app/ucsc-liftover_shim_server.py"] + description: "BioScientist MCP server for ucsc-liftover" + biosci_ucsc_nibfrag: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_ucsc-nibfrag/app/ucsc-nibfrag_shim_server.py"] + description: "BioScientist MCP server for ucsc-nibfrag" + biosci_ucsc_twobitinfo: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_ucsc-twobitinfo/app/ucsc-twobitinfo_shim_server.py"] + description: "BioScientist MCP server for ucsc-twobitinfo" + biosci_ucsc_twobittofa: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_ucsc-twobittofa/app/ucsc-twobittofa_shim_server.py"] + description: "BioScientist MCP server for ucsc-twobittofa" + biosci_umis: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_umis/app/umis_shim_server.py"] + description: "BioScientist MCP server for umis" + biosci_unicycler: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_unicycler/app/unicycler_shim_server.py"] + description: "BioScientist MCP server for unicycler" + biosci_unifrac: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_unifrac/app/unifrac_shim_server.py"] + description: "BioScientist MCP server for unifrac" + biosci_urllib3: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_urllib3/app/urllib3_shim_server.py"] + description: "BioScientist MCP server for urllib3" + biosci_usher: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_usher/app/usher_shim_server.py"] + description: "BioScientist MCP server for usher" + biosci_vardict_java: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_vardict-java/app/vardict-java_shim_server.py"] + description: "BioScientist MCP server for vardict-java" + biosci_vcfpy: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_vcfpy/app/vcfpy_shim_server.py"] + description: "BioScientist MCP server for vcfpy" + biosci_vcftools: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_vcftools/app/vcftools_shim_server.py"] + description: "BioScientist MCP server for vcftools" + biosci_vcontact2: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_vcontact2/app/vcontact2_shim_server.py"] + description: "BioScientist MCP server for vcontact2" + biosci_viennarna: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_viennarna/app/viennarna_shim_server.py"] + description: "BioScientist MCP server for viennarna" + biosci_vitessce_python: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_vitessce-python/app/vitessce-python_shim_server.py"] + description: "BioScientist MCP server for vitessce-python" + biosci_vpt: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_vpt/app/vpt_shim_server.py"] + description: "BioScientist MCP server for vpt" + biosci_vt: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_vt/app/vt_shim_server.py"] + description: "BioScientist MCP server for vt" + biosci_wasp2: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_wasp2/app/wasp2_shim_server.py"] + description: "BioScientist MCP server for wasp2" + biosci_whatshap: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_whatshap/app/whatshap_shim_server.py"] + description: "BioScientist MCP server for whatshap" + biosci_womtool: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_womtool/app/womtool_shim_server.py"] + description: "BioScientist MCP server for womtool" + biosci_xclone: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_xclone/app/xclone_shim_server.py"] + description: "BioScientist MCP server for xclone" + biosci_yacrd: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_yacrd/app/yacrd_shim_server.py"] + description: "BioScientist MCP server for yacrd" + biosci_zdb: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_zdb/app/zdb_shim_server.py"] + description: "BioScientist MCP server for zdb" + biosci_zol: + enabled: true + command: ["/225040511/miniconda3/envs/biomni_e1/bin/python", "/225040511/project/BioScientist/agent_system/toolbase/mcp_batch_from_manual_txt/mcp_zol/app/zol_shim_server.py"] + description: "BioScientist MCP server for zol" diff --git a/BioScientist/agent_system/engines/v1_executor_backup/task/__init__.py b/BioScientist/agent_system/engines/v1_executor_backup/task/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/BioScientist/agent_system/engines/v1_executor_backup/task/base_task.py b/BioScientist/agent_system/engines/v1_executor_backup/task/base_task.py new file mode 100644 index 0000000000000000000000000000000000000000..aa1de9e0432fee5d8174317185b3bc80e7e5aea9 --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/task/base_task.py @@ -0,0 +1,18 @@ +class base_task: + def __init__(self): + pass + + def get_example(self): + pass + + def get_iterator(self): + pass + + def evaluate(self): + pass + + def output_class(self): + pass + + def get_prompt_from_input(self, input): + return self.get_example(input)["prompt"] diff --git a/BioScientist/agent_system/engines/v1_executor_backup/task/hle.py b/BioScientist/agent_system/engines/v1_executor_backup/task/hle.py new file mode 100644 index 0000000000000000000000000000000000000000..a96ebddc7d4dd89bb9504615c9cfa5d76dc4acd6 --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/task/hle.py @@ -0,0 +1,146 @@ +import numpy as np +import pandas as pd + +from biomni.task.base_task import base_task + +np.random.seed(42) + + +def shuffle(x): + np.random.shuffle(x) + return x + + +class humanity_last_exam(base_task): + def __init__(self, path="./data", category="Biology/Medicine", answer_type="multipleChoice"): + if category not in [ + "Other", + "Humanities/Social Science", + "Math", + "Physics", + "Computer Science/AI", + "Biology/Medicine", + "Chemistry", + "Engineering", + ]: + raise ValueError( + "category must be one of ['Other', 'Humanities/Social Science', 'Math', 'Physics', 'Computer Science/AI', 'Biology/Medicine', 'Chemistry', 'Engineering']" + ) + if answer_type not in ["exactMatch", "multipleChoice"]: + raise ValueError("answer_type must be one of ['exactMatch' or 'multipleChoice']") + + self.dataset = category # Store dataset type + self.answer_type = answer_type + df = pd.read_parquet(path + "/hle/test_sampled_biology_medicine.parquet") + + # Extract answer choices from question text + def extract_options(question): + # Find the "Answer Choices:" section + if "Answer Choices:" not in question: + return [] + choices = question.split("Answer Choices:")[1].strip() + + # Split on A., B., C. etc and clean up + options = [] + letters = [ + "A.", + "B.", + "C.", + "D.", + "E.", + "F.", + "G.", + "H.", + "I.", + "J.", + "K.", + "L.", + "M.", + "N.", + "O.", + "P.", + "Q.", + "R.", + "S.", + "T.", + "U.", + "V.", + "W.", + "X.", + "Y.", + "Z.", + ] + for i, letter in enumerate(letters): + if letter in choices: + # Define the next letter if available + next_letter = letters[i + 1] if i + 1 < len(letters) else None + + # Split between current letter and next letter + parts = choices.split(letter)[1] + if next_letter and next_letter in parts: + option = parts.split(next_letter)[0].strip() + else: + option = parts.strip() + + options.append(option) + return options + + def extract_question(question): + return question.split("Answer Choices:")[0].strip() + + # Extract options and answers only for multiple choice questions and category is the same as the dataset + df = df[df["category"] == self.dataset] + df = df[df["answer_type"] == "multipleChoice"] + df["question_text"] = df.question + df["letter_answer"] = df["answer"].apply(lambda x: x[0]) + + self.query = df.question_text.values + # self.options = df.options_letters.values + self.answer = df.letter_answer.values + + self.prompt = """Question: {question}""" + + def get_example(self, index=None): + if index is None: + index = np.random.randint(len(self.query)) + + return { + "prompt": self.prompt.format( + question=self.query[index], + # options = self.options[index] + ), + "answer": self.answer[index], + } + + def get_iterator(self): + for i in range(len(self.query)): + yield self.get_example(i) + + def evaluate(self, response): + ## expected a list/array of symbols + from sklearn.metrics import accuracy_score + + ground_truth = self.answer + response = np.array(response) + + return { + "accuracy": accuracy_score(ground_truth, response), + "coverage": np.mean(response != self.refrain_label), + "refrain_ratio": np.mean(response == self.refrain_label), + "precision": accuracy_score( + ground_truth[np.where(response != self.refrain_label)], + response[np.where(response != self.refrain_label)], + ), + } + + def output_class(self): + from pydantic import BaseModel, Field + + class MultipleChoiceOutput(BaseModel): + """Multiple choice output.""" + + choice: str | None = Field( + description="Multiple choice answer. For example, if there is A in the prompt, the output should be 'A'." + ) + + return MultipleChoiceOutput diff --git a/BioScientist/agent_system/engines/v1_executor_backup/task/lab_bench.py b/BioScientist/agent_system/engines/v1_executor_backup/task/lab_bench.py new file mode 100644 index 0000000000000000000000000000000000000000..776f3a2245f8b310da3b5f1a760327c86cb101a5 --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/task/lab_bench.py @@ -0,0 +1,115 @@ +import numpy as np +import pandas as pd + +from biomni.task.base_task import base_task + +np.random.seed(42) + + +def shuffle(x): + np.random.shuffle(x) + return x + + +class lab_bench(base_task): + def __init__(self, path="./data", dataset="DbQA"): + if dataset not in ["DbQA", "SeqQA"]: + raise ValueError("dataset must be one of 'DbQA', 'SeqQA'") + + self.dataset = dataset # Store dataset type + df = pd.read_parquet(path + "/" + dataset + "/train-00000-of-00001_test.parquet") + + self.prompt = """The following is a multiple choice question about biology. +Please answer by responding with the letter of the correct answer. + +Question: {question} +Options: +{options} + +You MUST include the letter of the correct answer within the following tags: +[ANSWER] and [/ANSWER]. For example, '[ANSWER][/ANSWER]', +where is the correct letter. Always answer in exactly this format +of a single letter between the two tags, even if you are unsure. +We require this because we use automatic parsing. + """ + + np.random.seed(42) + df["options"] = df.apply( + lambda x: shuffle( + x.distractors.tolist() + [x.ideal] + ["Insufficient information to answer the question."] + ), + axis=1, + ) + df["options_letters"] = df.options.apply( + lambda x: "\n".join([chr(ord("A") + i) + "." + item for i, item in enumerate(x)]) + ) + df["letter_answer"] = df.apply( + lambda x: chr(ord("A") + np.where(np.array(x.options) == x.ideal)[0][0]), + axis=1, + ) + df["letter_refrain"] = df.apply( + lambda x: chr( + ord("A") + np.where(np.array(x.options) == "Insufficient information to answer the question.")[0][0] + ), + axis=1, + ) + + self.query = df.question.values + self.options = df.options_letters.values + self.answer = df.letter_answer.values + self.refrain_label = df.letter_refrain.values + + # Store protocol information if available + self.protocol = df.protocol.values if "protocol" in df.columns else None + + def get_example(self, index=None): + if index is None: + index = np.random.randint(len(self.query)) + + if self.dataset == "ProtocolQA" and self.protocol is not None: + return { + "prompt": self.prompt.format( + protocol=self.protocol[index], + question=self.query[index], + options=self.options[index], + ), + "answer": self.answer[index], + } + else: + return { + "prompt": self.prompt.format(question=self.query[index], options=self.options[index]), + "answer": self.answer[index], + } + + def get_iterator(self): + for i in range(len(self.query)): + yield self.get_example(i) + + def evaluate(self, response): + ## expected a list/array of symbols + from sklearn.metrics import accuracy_score + + ground_truth = self.answer + response = np.array(response) + + return { + "accuracy": accuracy_score(ground_truth, response), + "coverage": np.mean(response != self.refrain_label), + "refrain_ratio": np.mean(response == self.refrain_label), + "precision": accuracy_score( + ground_truth[np.where(response != self.refrain_label)], + response[np.where(response != self.refrain_label)], + ), + } + + def output_class(self): + from pydantic import BaseModel, Field + + class MultipleChoiceOutput(BaseModel): + """Multiple choice output.""" + + choice: str | None = Field( + description="Multiple choice answer. For example, if there is A in the prompt, the output should be 'A'." + ) + + return MultipleChoiceOutput diff --git a/BioScientist/agent_system/engines/v1_executor_backup/tool/__init__.py b/BioScientist/agent_system/engines/v1_executor_backup/tool/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..65d2e68219a4cad1449320c6f794acc8073cdd09 --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/tool/__init__.py @@ -0,0 +1 @@ +from biomni.utils import get_tool_decorated_functions # noqa: F401 diff --git a/BioScientist/agent_system/engines/v1_executor_backup/tool/biochemistry.py b/BioScientist/agent_system/engines/v1_executor_backup/tool/biochemistry.py new file mode 100644 index 0000000000000000000000000000000000000000..aa97260a4d97803d31e595fe41002ffdc6370b46 --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/tool/biochemistry.py @@ -0,0 +1,1026 @@ +def analyze_circular_dichroism_spectra( + sample_name, + sample_type, + wavelength_data, + cd_signal_data, + temperature_data=None, + thermal_cd_data=None, + output_dir="./", +): + """Analyzes circular dichroism (CD) spectroscopy data to determine secondary structure and thermal stability. + + Parameters + ---------- + sample_name : str + Name of the biomolecule sample (e.g., "Znf706", "G-quadruplex") + sample_type : str + Type of biomolecule ("protein" or "nucleic_acid") + wavelength_data : list or numpy.ndarray + Wavelength values in nm for CD spectrum + cd_signal_data : list or numpy.ndarray + CD signal intensity values (typically in mdeg or Δε) + temperature_data : list or numpy.ndarray, optional + Temperature values (°C) for thermal denaturation experiment + thermal_cd_data : list or numpy.ndarray, optional + CD signal values at specific wavelength across different temperatures + output_dir : str, optional + Directory to save result files, defaults to current directory + + Returns + ------- + str + Research log summarizing the CD analysis steps and results + + """ + import os + from datetime import datetime + + import numpy as np + + # Initialize research log + log = f"# Circular Dichroism Analysis Report for {sample_name}\n" + log += f"Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n" + log += "## Sample Information\n" + log += f"- Sample Name: {sample_name}\n" + log += f"- Sample Type: {sample_type}\n\n" + + # Convert inputs to numpy arrays if they aren't already + wavelength_data = np.array(wavelength_data) + cd_signal_data = np.array(cd_signal_data) + + # Ensure output directory exists + if not os.path.exists(output_dir): + os.makedirs(output_dir) + + # 1. Analyze CD spectrum for secondary structure + log += "## Secondary Structure Analysis\n" + + # Different analysis approaches based on sample type + if sample_type.lower() == "protein": + # Analyze protein secondary structure based on characteristic spectral features + alpha_helix_signal = np.sum((wavelength_data >= 190) & (wavelength_data <= 195) & (cd_signal_data > 0)) + beta_sheet_signal = np.sum((wavelength_data >= 215) & (wavelength_data <= 220) & (cd_signal_data < 0)) + random_coil_signal = np.sum((wavelength_data >= 195) & (wavelength_data <= 200) & (cd_signal_data < 0)) + + # Simple classification based on signal patterns + if alpha_helix_signal > beta_sheet_signal and alpha_helix_signal > random_coil_signal: + structure = "predominantly alpha-helical" + elif beta_sheet_signal > alpha_helix_signal and beta_sheet_signal > random_coil_signal: + structure = "predominantly beta-sheet" + else: + structure = "mixed or predominantly random coil" + + log += f"- The CD spectrum indicates {structure} structure for {sample_name}.\n" + log += "- Key spectral features:\n" + log += " - 190-195 nm region: associated with alpha-helical content\n" + log += " - 215-220 nm region: associated with beta-sheet content\n\n" + + elif sample_type.lower() == "nucleic_acid": + # Analyze nucleic acid structure (e.g., G-quadruplex has characteristic positive peak ~295 nm) + g_quadruplex_signal = np.sum((wavelength_data >= 290) & (wavelength_data <= 300) & (cd_signal_data > 0)) + b_form_signal = np.sum((wavelength_data >= 270) & (wavelength_data <= 280) & (cd_signal_data > 0)) + + if g_quadruplex_signal > 0: + structure = "G-quadruplex characteristics" + elif b_form_signal > 0: + structure = "B-form characteristics" + else: + structure = "non-standard structure" + + log += f"- The CD spectrum indicates {structure} for {sample_name}.\n" + log += "- Key spectral features:\n" + log += " - 290-300 nm positive peak: characteristic of G-quadruplex structures\n" + log += " - 270-280 nm positive peak: characteristic of B-form DNA\n\n" + + # Save spectral data results + spectral_file = os.path.join(output_dir, f"{sample_name}_cd_spectrum_analysis.txt") + with open(spectral_file, "w") as f: + f.write("Wavelength (nm)\tCD Signal\n") + for wl, signal in zip(wavelength_data, cd_signal_data, strict=False): + f.write(f"{wl:.1f}\t{signal:.4f}\n") + + log += f"- Detailed spectral data saved to: {spectral_file}\n\n" + + # 2. Thermal stability analysis (if temperature data provided) + if temperature_data is not None and thermal_cd_data is not None: + temperature_data = np.array(temperature_data) + thermal_cd_data = np.array(thermal_cd_data) + + log += "## Thermal Stability Analysis\n" + + # Simple Tm estimation (melting temperature) - find temperature at 50% unfolding + # Normalize thermal data to 0-1 range for unfolding fraction + min_signal = np.min(thermal_cd_data) + max_signal = np.max(thermal_cd_data) + unfolded_fraction = (thermal_cd_data - min_signal) / (max_signal - min_signal) + + # Find the temperature closest to 50% unfolding + tm_idx = np.argmin(np.abs(unfolded_fraction - 0.5)) + tm = temperature_data[tm_idx] + + log += f"- Estimated melting temperature (Tm): {tm:.1f}°C\n" + + # Cooperativity assessment (crude estimate based on transition steepness) + t_range = temperature_data[-1] - temperature_data[0] + transition_width = ( + t_range / len(temperature_data) * np.sum((unfolded_fraction > 0.2) & (unfolded_fraction < 0.8)) + ) + + if transition_width < 0.2 * t_range: + cooperativity = "highly cooperative (sharp transition)" + elif transition_width < 0.4 * t_range: + cooperativity = "moderately cooperative" + else: + cooperativity = "non-cooperative (broad transition)" + + log += f"- Thermal transition: {cooperativity}\n" + + # Save thermal denaturation data + thermal_file = os.path.join(output_dir, f"{sample_name}_thermal_denaturation.txt") + with open(thermal_file, "w") as f: + f.write("Temperature (°C)\tCD Signal\tUnfolded Fraction\n") + for temp, signal, unfold in zip(temperature_data, thermal_cd_data, unfolded_fraction, strict=False): + f.write(f"{temp:.1f}\t{signal:.4f}\t{unfold:.4f}\n") + + log += f"- Thermal denaturation data saved to: {thermal_file}\n\n" + + # 3. Summary and conclusions + log += "## Conclusions\n" + if sample_type.lower() == "protein": + log += f"- {sample_name} shows {structure} according to CD spectroscopy.\n" + else: + log += f"- {sample_name} exhibits {structure} according to CD spectroscopy.\n" + + if temperature_data is not None: + log += f"- The molecule has a melting temperature of {tm:.1f}°C with {cooperativity}.\n" + + return log + + +def analyze_rna_secondary_structure_features(dot_bracket_structure, sequence=None): + """Calculate numeric values for various structural features of an RNA secondary structure. + + Parameters + ---------- + dot_bracket_structure : str + RNA secondary structure in dot-bracket notation (e.g., "(((...)))"). + Parentheses represent base pairs, dots represent unpaired bases. + sequence : str, optional + The RNA sequence corresponding to the structure. If provided, + sequence-dependent energy calculations will be performed. + + Returns + ------- + str + A research log summarizing the calculated structural features and analysis steps. + + """ + # Initialize research log + log = "# RNA Secondary Structure Feature Analysis\n\n" + + # Validate input + if not all(c in "().[]{}" for c in dot_bracket_structure): + return "Error: Invalid dot-bracket notation. Use only '()', '[]', '{}', and '.'" + + log += f"Input structure (length: {len(dot_bracket_structure)}): {dot_bracket_structure}\n" + if sequence: + log += f"Input sequence (length: {len(sequence)}): {sequence}\n" + if len(sequence) != len(dot_bracket_structure): + return "Error: Sequence and structure lengths do not match." + + # Extract base pairs + pairs = [] + stack = [] + + for i, char in enumerate(dot_bracket_structure): + if char in "([{": + stack.append((i, char)) + elif char in ")]}": + if not stack: + return "Error: Unbalanced structure. More closing than opening brackets." + + j, opening_char = stack.pop() + # Check for matching bracket types + if ( + (opening_char == "(" and char != ")") + or (opening_char == "[" and char != "]") + or (opening_char == "{" and char != "}") + ): + return "Error: Mismatched bracket types. Opening and closing brackets must match." + + pairs.append((j, i)) + + if stack: + return "Error: Unbalanced structure. More opening than closing brackets." + + # Sort pairs by position + pairs.sort() + + # Identify stems (consecutive base pairs) + stems = [] + current_stem = [] + + for i, (start, end) in enumerate(pairs): + if (i == 0 or start != pairs[i - 1][0] + 1 or end != pairs[i - 1][1] - 1) and current_stem: + stems.append(current_stem) + current_stem = [] + current_stem.append((start, end)) + + if current_stem: + stems.append(current_stem) + + # Calculate stem lengths + stem_lengths = [len(stem) for stem in stems] + + # Calculate loop sizes + loops = [] + for i in range(len(stems)): + stem = stems[i] + last_pair = stem[-1] + next_stem_start = stems[i + 1][0][0] if i < len(stems) - 1 else len(dot_bracket_structure) + loop_size = next_stem_start - last_pair[1] - 1 + if loop_size > 0: + loops.append(loop_size) + + # Calculate base pair statistics + total_paired_bases = len(pairs) * 2 + total_unpaired_bases = len(dot_bracket_structure) - total_paired_bases + + # Calculate simplified free energy if sequence is provided + stem_energies = [] + if sequence and len(stems) > 0: + # Simplified energy parameters for nearest-neighbor model + # Values are approximate and simplified for illustration + energy_params = { + "AU": -0.9, + "UA": -0.9, + "GC": -2.1, + "CG": -2.1, + "GU": -0.5, + "UG": -0.5, + } + + for stem in stems: + stem_energy = 0 + for start, end in stem: + if start < len(sequence) and end < len(sequence): + pair = sequence[start] + sequence[end] + stem_energy += energy_params.get(pair, 0) + stem_energies.append(stem_energy) + + # Prepare results + log += "\n## Structural Features\n\n" + log += f"Total base pairs: {len(pairs)}\n" + log += f"Number of stems: {len(stems)}\n" + log += f"Longest stem length: {max(stem_lengths) if stem_lengths else 0}\n" + log += f"Average stem length: {sum(stem_lengths) / len(stem_lengths) if stem_lengths else 0:.2f}\n" + log += f"Paired bases: {total_paired_bases} ({total_paired_bases / len(dot_bracket_structure) * 100:.1f}%)\n" + log += f"Unpaired bases: {total_unpaired_bases} ({total_unpaired_bases / len(dot_bracket_structure) * 100:.1f}%)\n" + + if loops: + log += f"Number of loops: {len(loops)}\n" + log += f"Average loop size: {sum(loops) / len(loops):.2f}\n" + log += f"Largest loop size: {max(loops)}\n" + + if sequence and stem_energies: + log += "\n## Energy Calculations\n\n" + log += f"Total estimated free energy: {sum(stem_energies):.2f} kcal/mol\n" + + if len(stems) >= 2: + log += f"Upstream stem free energy: {stem_energies[0]:.2f} kcal/mol\n" + log += f"Downstream stem free energy: {stem_energies[-1]:.2f} kcal/mol\n" + + # If the first stem is the "zipper" stem + if stem_lengths and stem_lengths[0] >= 3: + log += f"Zipper stem free energy: {stem_energies[0]:.2f} kcal/mol\n" + + log += "\n## Stem Details\n\n" + for i, stem in enumerate(stems): + log += f"Stem {i + 1}: {len(stem)} base pairs\n" + log += f" Positions: {stem[0][0]}-{stem[0][1]} to {stem[-1][0]}-{stem[-1][1]}\n" + if sequence and i < len(stem_energies): + log += f" Estimated stability: {stem_energies[i]:.2f} kcal/mol\n" + + return log + + +def analyze_protease_kinetics( + time_points, + fluorescence_data, + substrate_concentrations, + enzyme_concentration, + output_prefix="protease_kinetics", + output_dir="./", +): + """Analyze protease kinetics data from fluorogenic peptide cleavage assays. + + This function processes time-course fluorescence data from protease-mediated peptide + cleavage assays, fits the data to Michaelis-Menten kinetics, and determines key + kinetic parameters (kcat, KM, and catalytic efficiency). + + Parameters + ---------- + time_points : numpy.ndarray + Array of time points (in seconds) at which measurements were taken + + fluorescence_data : numpy.ndarray + 2D array of fluorescence measurements where each row corresponds to a different + substrate concentration and each column corresponds to a time point + + substrate_concentrations : numpy.ndarray + Array of substrate concentrations (in μM) corresponding to each row in fluorescence_data + + enzyme_concentration : float + Concentration of the protease enzyme (in μM) + + output_prefix : str, optional + Prefix for output files (default: "protease_kinetics") + + output_dir : str, optional + Directory to save output files (default: "./") + + Returns + ------- + str + A research log summarizing the analysis steps and results + + """ + import os + + import matplotlib.pyplot as plt + import numpy as np + from scipy.optimize import curve_fit + + # Ensure output directory exists + if not os.path.exists(output_dir): + os.makedirs(output_dir) + + # Create full output paths + plot_filename = os.path.join(output_dir, f"{output_prefix}_mm_plot.png") + results_filename = os.path.join(output_dir, f"{output_prefix}_results.txt") + + # Step 1: Calculate initial velocities for each substrate concentration + initial_velocities = np.zeros(len(substrate_concentrations)) + + for i, fluorescence_curve in enumerate(fluorescence_data): + # Linear fit to the initial portion of the curve (first 20% of time points or at least 5 points) + num_points = max(5, int(len(time_points) * 0.2)) + slope, _ = np.polyfit(time_points[:num_points], fluorescence_curve[:num_points], 1) + initial_velocities[i] = slope + + # Step 2: Define Michaelis-Menten equation for curve fitting + def michaelis_menten(s, vmax, km): + return vmax * s / (km + s) + + # Step 3: Fit the data to the Michaelis-Menten equation + try: + params, covariance = curve_fit( + michaelis_menten, + substrate_concentrations, + initial_velocities, + p0=[max(initial_velocities), np.mean(substrate_concentrations)], + bounds=([0, 0], [np.inf, np.inf]), + ) + + vmax, km = params + std_dev = np.sqrt(np.diag(covariance)) + vmax_std, km_std = std_dev + + # Step 4: Calculate kcat and catalytic efficiency + kcat = vmax / enzyme_concentration + kcat_std = vmax_std / enzyme_concentration + catalytic_efficiency = kcat / km + catalytic_efficiency_std = catalytic_efficiency * np.sqrt((kcat_std / kcat) ** 2 + (km_std / km) ** 2) + + # Step 5: Create a plot and save it + plt.figure(figsize=(10, 6)) + plt.scatter( + substrate_concentrations, + initial_velocities, + color="blue", + label="Experimental data", + ) + + # Generate smooth curve for the fitted model + s_curve = np.linspace(0, max(substrate_concentrations) * 1.2, 100) + v_curve = michaelis_menten(s_curve, vmax, km) + plt.plot(s_curve, v_curve, "r-", label="Michaelis-Menten fit") + + plt.xlabel("Substrate Concentration (μM)") + plt.ylabel("Initial Velocity (a.u./s)") + plt.title("Michaelis-Menten Kinetics") + plt.legend() + plt.grid(True, alpha=0.3) + + plt.savefig(plot_filename) + plt.close() + + # Step 6: Save numerical results to a file + with open(results_filename, "w") as f: + f.write("Protease Kinetics Analysis Results\n") + f.write("==================================\n\n") + f.write(f"Vmax: {vmax:.4f} ± {vmax_std:.4f} a.u./s\n") + f.write(f"KM: {km:.4f} ± {km_std:.4f} μM\n") + f.write(f"kcat: {kcat:.4f} ± {kcat_std:.4f} s^-1\n") + f.write( + f"Catalytic efficiency (kcat/KM): {catalytic_efficiency:.4f} ± {catalytic_efficiency_std:.4f} μM^-1 s^-1\n" + ) + + # Step 7: Create research log + research_log = f""" +Protease Kinetics Analysis Research Log +====================================== + +Analysis Steps: +1. Calculated initial velocities from time-course fluorescence data for {len(substrate_concentrations)} different substrate concentrations +2. Fitted initial velocities to the Michaelis-Menten equation using non-linear regression +3. Determined kinetic parameters and their uncertainties + +Results: +- Vmax: {vmax:.4f} ± {vmax_std:.4f} a.u./s +- KM: {km:.4f} ± {km_std:.4f} μM +- kcat: {kcat:.4f} ± {kcat_std:.4f} s^-1 +- Catalytic efficiency (kcat/KM): {catalytic_efficiency:.4f} ± {catalytic_efficiency_std:.4f} μM^-1 s^-1 + +Files Generated: +1. {plot_filename} - Michaelis-Menten plot showing experimental data and fitted curve +2. {results_filename} - Text file containing detailed results + +Analysis completed successfully. +""" + + return research_log + + except Exception as e: + return f"Error during analysis: {str(e)}" + + +def analyze_enzyme_kinetics_assay( + enzyme_name, + substrate_concentrations, + enzyme_concentration, + modulators=None, + time_points=None, + output_dir="./", +): + """Performs in vitro enzyme kinetics assay and analyzes the dose-dependent effects of modulators. + + Parameters + ---------- + enzyme_name : str + Name of the purified enzyme being tested + substrate_concentrations : list or numpy.ndarray + List of substrate concentrations in μM for kinetic analysis + enzyme_concentration : float + Concentration of the enzyme in nM + modulators : dict, optional + Dictionary of modulators where keys are modulator names and values are lists of + concentrations in μM. Default is None (no modulators). + time_points : list or numpy.ndarray, optional + Time points in minutes for time-course measurements. Default is None, which uses + [0, 5, 10, 15, 20, 30, 45, 60]. + output_dir : str, optional + Directory to save output files. Default is current directory. + + Returns + ------- + str + Research log summarizing the enzyme kinetics assay procedure and results + + """ + import csv + import os + + import numpy as np + from scipy.optimize import curve_fit + + # Create output directory if it doesn't exist + if not os.path.exists(output_dir): + os.makedirs(output_dir) + + # Set default time points if not provided + if time_points is None: + time_points = np.array([0, 5, 10, 15, 20, 30, 45, 60]) + else: + # Ensure time_points is a numpy array + time_points = np.array(time_points) + + # Initialize research log + log = f"## In Vitro Enzyme Kinetics Assay: {enzyme_name}\n\n" + log += f"Enzyme concentration: {enzyme_concentration} nM\n" + + # Michaelis-Menten equation for curve fitting + def michaelis_menten(s, vmax, km): + return vmax * s / (km + s) + + # 1. Time-course kinetic assay + log += "\n### Time-Course Kinetic Assay\n\n" + log += "Measuring enzyme activity over time to establish linear range.\n" + + # Simulate time-course data (realistic enzyme kinetics with some noise) + # Using a simple exponential approach to equilibrium model + max_activity = 100 # arbitrary units + rate_constant = 0.05 # min^-1 + + # Simulate enzyme activity over time with some noise + np.random.seed(42) # For reproducibility + time_course_activity = max_activity * (1 - np.exp(-rate_constant * time_points)) + time_course_activity += np.random.normal(0, 3, len(time_points)) # Add noise + + # Save time-course data + time_course_file = os.path.join(output_dir, f"{enzyme_name}_time_course.csv") + with open(time_course_file, "w", newline="") as f: + writer = csv.writer(f) + writer.writerow(["Time (min)", "Activity (units)"]) + for t, a in zip(time_points, time_course_activity, strict=False): + writer.writerow([t, a]) + + # Determine linear range (first ~30% of the curve) + linear_cutoff_index = np.where(time_course_activity >= 0.3 * max_activity)[0][0] + linear_time = time_points[: linear_cutoff_index + 1] + + log += f"Time-course data saved to: {time_course_file}\n" + log += f"Linear range determined to be 0-{linear_time[-1]} minutes.\n" + + # 2. Substrate kinetics (Michaelis-Menten analysis) + log += "\n### Substrate Kinetics Analysis\n\n" + + # Simulate enzyme activity at different substrate concentrations + # based on Michaelis-Menten kinetics + true_vmax = 120 # arbitrary units + true_km = 25 # μM + + activity_values = michaelis_menten(np.array(substrate_concentrations), true_vmax, true_km) + activity_values += np.random.normal(0, 5, len(substrate_concentrations)) # Add noise + + # Fit data to Michaelis-Menten equation + try: + params, _ = curve_fit( + michaelis_menten, + substrate_concentrations, + activity_values, + p0=[100, 20], + bounds=([0, 0], [500, 200]), + ) + vmax, km = params + + # Save substrate kinetics data + kinetics_file = os.path.join(output_dir, f"{enzyme_name}_substrate_kinetics.csv") + with open(kinetics_file, "w", newline="") as f: + writer = csv.writer(f) + writer.writerow(["Substrate (μM)", "Activity (units)"]) + for s, a in zip(substrate_concentrations, activity_values, strict=False): + writer.writerow([s, a]) + + log += "Michaelis-Menten parameters:\n" + log += f"- Vmax: {vmax:.2f} units\n" + log += f"- Km: {km:.2f} μM\n" + log += f"Substrate kinetics data saved to: {kinetics_file}\n" + except Exception: + log += "Error: Could not fit data to Michaelis-Menten model.\n" + + # 3. Modulator effects (if provided) + if modulators: + log += "\n### Modulator Effects Analysis\n\n" + + for modulator_name, concentrations in modulators.items(): + log += f"#### Testing modulator: {modulator_name}\n\n" + + # Choose a fixed substrate concentration near Km for inhibition studies + substrate_conc = true_km + + # Simulate modulator effects (e.g., competitive inhibition) + # For simplicity, using a sigmoidal dose-response curve + ic50 = np.random.uniform(1, 50) # Random IC50 between 1-50 μM + hill_coef = 1.0 # Hill coefficient + + # Calculate normalized activity (% of control) + michaelis_menten(substrate_conc, true_vmax, true_km) + + # Calculate activities at different modulator concentrations + modulator_activities = [] + for conc in concentrations: + # Sigmoidal dose-response curve + if conc == 0: + activity = 100 # 100% activity at zero concentration + else: + activity = 100 / (1 + (conc / ic50) ** hill_coef) + + # Add some noise + activity += np.random.normal(0, 3) + modulator_activities.append(activity) + + # Save modulator data + modulator_file = os.path.join(output_dir, f"{enzyme_name}_{modulator_name}_dose_response.csv") + with open(modulator_file, "w", newline="") as f: + writer = csv.writer(f) + writer.writerow([f"{modulator_name} (μM)", "Activity (% of control)"]) + for conc, act in zip(concentrations, modulator_activities, strict=False): + writer.writerow([conc, act]) + + # Calculate IC50 using curve fitting if there are enough data points + if len(concentrations) >= 4: + try: + # Define dose-response curve function + def dose_response(x, ic50, hill): + return 100 / (1 + (x / ic50) ** hill) + + # Filter out zero concentration + nonzero_conc = np.array([c for c in concentrations if c > 0]) + nonzero_act = np.array( + [a for c, a in zip(concentrations, modulator_activities, strict=False) if c > 0] + ) + + if len(nonzero_conc) >= 3: # Need at least 3 points for fitting + params, _ = curve_fit( + dose_response, + nonzero_conc, + nonzero_act, + p0=[10, 1], + bounds=([0.1, 0.1], [1000, 10]), + ) + calc_ic50, calc_hill = params + + log += f"Dose-response analysis for {modulator_name}:\n" + log += f"- IC50: {calc_ic50:.2f} μM\n" + log += f"- Hill coefficient: {calc_hill:.2f}\n" + else: + log += f"Insufficient non-zero data points to calculate IC50 for {modulator_name}.\n" + except Exception: + log += f"Error: Could not fit dose-response curve for {modulator_name}.\n" + else: + log += f"Insufficient data points to calculate IC50 for {modulator_name}.\n" + + log += f"Dose-response data for {modulator_name} saved to: {modulator_file}\n\n" + + # 4. Summary + log += "\n### Summary\n\n" + log += f"Completed in vitro enzyme kinetics assay for {enzyme_name}.\n" + log += f"- Determined linear range for time-course measurements: 0-{linear_time[-1]} minutes\n" + log += f"- Characterized Michaelis-Menten kinetics (Vmax: {vmax:.2f} units, Km: {km:.2f} μM)\n" + + if modulators: + log += "- Analyzed the effects of modulators:\n" + for modulator_name in modulators: + log += f" * {modulator_name}: Data saved to {enzyme_name}_{modulator_name}_dose_response.csv\n" + + return log + + +def analyze_itc_binding_thermodynamics( + itc_data_path=None, + itc_data=None, + temperature=298.15, + protein_concentration=None, + ligand_concentration=None, +): + """Analyzes isothermal titration calorimetry (ITC) data to determine binding affinity and thermodynamic parameters. + + Parameters + ---------- + itc_data_path : str, optional + Path to CSV or TSV file containing ITC thermogram data with columns for injection number, + injection volume, and heat released/absorbed. Expected columns: 'injection', 'volume', 'heat' + itc_data : numpy.ndarray, optional + Raw ITC thermogram data as a numpy array. + Expected shape: (n_injections, 3) with columns for injection number, injection volume, and heat + This parameter is provided for backward compatibility and will be deprecated. + temperature : float, optional + Temperature in Kelvin at which the experiment was conducted. Default is 298.15 K (25°C). + protein_concentration : float, optional + Initial concentration of protein in the cell in molar (M). Required for accurate fitting. + ligand_concentration : float, optional + Concentration of ligand in the syringe in molar (M). Required for accurate fitting. + + Returns + ------- + str + A research log summarizing the analysis steps and results, including binding affinity (Kd), + binding enthalpy (ΔH), binding entropy (ΔS), and Gibbs free energy (ΔG). + + """ + import datetime + + import numpy as np + import pandas as pd + from scipy.optimize import curve_fit + + log = [] + log.append(f"# ITC Binding Affinity Analysis - {datetime.datetime.now().strftime('%Y-%m-%d %H:%M')}") + log.append("\n## Data Preprocessing") + + # Check if we have data to process + if itc_data_path is None and itc_data is None: + log.append("Error: No data provided. Please provide either itc_data_path or itc_data.") + return "\n".join(log) + + # Load data from file if path is provided + if itc_data_path is not None: + try: + if itc_data_path.endswith(".csv"): + loaded_data = pd.read_csv(itc_data_path) + elif itc_data_path.endswith((".tsv", ".txt")): + loaded_data = pd.read_csv(itc_data_path, sep="\t") + else: + log.append("Error: Unsupported file format. Please provide a CSV or TSV file.") + return "\n".join(log) + + log.append(f"- Loaded data from file: {itc_data_path}") + log.append(f"- Input data: DataFrame with {len(loaded_data)} injections") + + if all(col in loaded_data.columns for col in ["injection", "volume", "heat"]): + data = loaded_data[["injection", "volume", "heat"]].values + else: + log.append("- Error: DataFrame must contain 'injection', 'volume', and 'heat' columns") + return "\n".join(log) + except Exception as e: + log.append(f"- Error loading data from file: {str(e)}") + return "\n".join(log) + # Use provided numpy array data + elif itc_data is not None: + if isinstance(itc_data, pd.DataFrame): + log.append("Warning: Passing DataFrame directly is deprecated. Please use itc_data_path instead.") + log.append(f"- Input data: DataFrame with {len(itc_data)} injections") + if all(col in itc_data.columns for col in ["injection", "volume", "heat"]): + data = itc_data[["injection", "volume", "heat"]].values + else: + log.append("- Error: DataFrame must contain 'injection', 'volume', and 'heat' columns") + return "\n".join(log) + else: + log.append(f"- Input data: Array with {len(itc_data)} injections") + data = np.array(itc_data) + + # Check if concentration data is provided + if protein_concentration is None or ligand_concentration is None: + log.append("- Warning: Protein or ligand concentration not provided. Using normalized data for fitting.") + protein_concentration = 1.0 + ligand_concentration = 10.0 + + # Extract data + injections = data[:, 0] + volumes = data[:, 1] + heats = data[:, 2] + + log.append(f"- Processed {len(injections)} injections") + + # Calculate molar ratio for each injection + cell_volume = 1.4 # Default cell volume in mL (typical for ITC) + + # Calculate cumulative ligand added + cumulative_volume = np.cumsum(volumes) + dilution_factor = 1 - (cumulative_volume / cell_volume) + protein_conc_corrected = protein_concentration * dilution_factor + + # Calculate molar ratio [ligand]/[protein] for each injection + molar_ratios = np.zeros_like(injections, dtype=float) + for i in range(len(injections)): + ligand_added = volumes[i] * ligand_concentration / 1000 # Convert to mmol + if i == 0: + molar_ratios[i] = ligand_added / (protein_concentration * cell_volume / 1000) # Convert to mmol + else: + # Account for dilution and previous injections + molar_ratios[i] = molar_ratios[i - 1] + ligand_added / ( + protein_conc_corrected[i] * (cell_volume - cumulative_volume[i]) / 1000 + ) + + log.append("\n## Model Fitting") + log.append("- Applying one-site binding model to the ITC data") + + # Define one-site binding model function + def one_site_model(x, Kd, dH, n): + """One-site binding model for ITC data. + + Parameters + ---------- + x: Molar ratio [ligand]/[protein] + Kd: Dissociation constant (M) + dH: Enthalpy change (cal/mol) + n: Stoichiometry + + Returns: Heat per injection + + """ + # Convert x to fraction bound using the binding equation + Ka = 1 / Kd # Association constant + protein = protein_conc_corrected + + # Calculate heat for each injection + q = np.zeros_like(x) + for i in range(len(x)): + if i == 0: + # First injection + bound = (n * protein[i] * Ka * (x[i] * protein[i])) / (1 + Ka * (x[i] * protein[i])) + q[i] = bound * dH * cell_volume + else: + # Subsequent injections (differential heat) + bound_prev = (n * protein[i - 1] * Ka * (x[i - 1] * protein[i - 1])) / ( + 1 + Ka * (x[i - 1] * protein[i - 1]) + ) + bound_curr = (n * protein[i] * Ka * (x[i] * protein[i])) / (1 + Ka * (x[i] * protein[i])) + q[i] = bound_curr * dH * (cell_volume - cumulative_volume[i]) - bound_prev * dH * ( + cell_volume - cumulative_volume[i - 1] + ) + + return q + + # Initial parameter guesses + p0 = [1e-6, -5000, 1.0] # Kd (M), dH (cal/mol), n (stoichiometry) + + try: + # Fit the model to the data + popt, pcov = curve_fit(one_site_model, molar_ratios, heats, p0=p0, maxfev=10000) + Kd, dH, n = popt + + # Calculate standard errors + perr = np.sqrt(np.diag(pcov)) + Kd_err, dH_err, n_err = perr + + # Calculate other thermodynamic parameters + R = 1.9872 # Gas constant in cal/(mol·K) + dG = R * temperature * np.log(Kd) # Gibbs free energy + dS = (dH - dG) / temperature # Entropy + + log.append("- Model fitting successful") + log.append("\n## Results") + log.append(f"- Binding Stoichiometry (n): {n:.2f} ± {n_err:.2f}") + log.append(f"- Dissociation Constant (Kd): {Kd * 1e6:.2f} ± {Kd_err * 1e6:.2f} μM") + log.append(f"- Association Constant (Ka): {1 / Kd / 1e6:.2f} × 10^6 M^-1") + log.append(f"- Binding Enthalpy (ΔH): {dH:.2f} ± {dH_err:.2f} cal/mol") + log.append(f"- Binding Entropy (ΔS): {dS:.2f} cal/(mol·K)") + log.append(f"- Gibbs Free Energy (ΔG): {dG:.2f} cal/mol") + + # Calculate goodness of fit + residuals = heats - one_site_model(molar_ratios, *popt) + ss_res = np.sum(residuals**2) + ss_tot = np.sum((heats - np.mean(heats)) ** 2) + r_squared = 1 - (ss_res / ss_tot) + log.append(f"- R-squared: {r_squared:.4f}") + + except Exception as e: + log.append(f"- Error during model fitting: {str(e)}") + log.append("- Consider trying different initial parameter guesses or a different binding model") + + log.append("\n## Conclusion") + log.append("- Analysis complete. The thermodynamic parameters have been estimated using a one-site binding model.") + log.append( + "- For more complex binding scenarios, consider using multi-site binding models or specialized ITC analysis software." + ) + + return "\n".join(log) + + +def analyze_protein_conservation(protein_sequences, output_dir="./"): + """Perform multiple sequence alignment and phylogenetic analysis to identify conserved protein regions. + + Parameters + ---------- + protein_sequences : list of str + List of protein sequences in FASTA format from multiple organisms. + output_dir : str, optional + Directory to save output files (default: "./output") + + Returns + ------- + str + Research log summarizing the analysis steps and results, including filenames of saved outputs. + + """ + import os + + from Bio import AlignIO, Phylo, SeqIO + from Bio.Align.Applications import MuscleCommandline + from Bio.Phylo.TreeConstruction import DistanceCalculator, DistanceTreeConstructor + + # Create a research log + log = [] + log.append("# Protein Sequence Alignment and Conservation Analysis") + + # Ensure output directory exists + if not os.path.exists(output_dir): + os.makedirs(output_dir) + + # Step 1: Save input sequences to a temporary file + log.append("\n## Step 1: Preparing Input Sequences") + input_file = os.path.join(output_dir, "input.fasta") + + # Check if input is already in FASTA format or needs conversion + if isinstance(protein_sequences, list): + if all(">" in seq for seq in protein_sequences): + # Already in FASTA format + with open(input_file, "w") as f: + f.write("\n".join(protein_sequences)) + else: + # Convert to FASTA format + with open(input_file, "w") as f: + for i, seq in enumerate(protein_sequences): + f.write(f">Sequence_{i + 1}\n{seq}\n") + else: + # Assume it's a single string in FASTA format + with open(input_file, "w") as f: + f.write(protein_sequences) + + log.append(f"Input sequences saved to {input_file}") + log.append(f"Number of sequences: {len(list(SeqIO.parse(input_file, 'fasta')))}") + + # Step 2: Perform multiple sequence alignment using MUSCLE + log.append("\n## Step 2: Multiple Sequence Alignment") + aligned_file = os.path.join(output_dir, "aligned.fasta") + + try: + # Run MUSCLE for multiple sequence alignment + muscle_cline = MuscleCommandline(input=input_file, out=aligned_file) + stdout, stderr = muscle_cline() + log.append("Multiple sequence alignment completed using MUSCLE") + log.append(f"Alignment saved to {aligned_file}") + + # Load the alignment + alignment = AlignIO.read(aligned_file, "fasta") + log.append(f"Alignment length: {alignment.get_alignment_length()} positions") + + except Exception as e: + log.append(f"MUSCLE alignment failed: {str(e)}") + log.append("Attempting to use Biopython's built-in alignment methods...") + + # Fallback to a simpler approach without pairwise2 + from Bio.Align import MultipleSeqAlignment + from Bio.Seq import Seq + from Bio.SeqRecord import SeqRecord + + sequences = list(SeqIO.parse(input_file, "fasta")) + + # Simple approach: just pad sequences to the same length + max_length = max(len(seq.seq) for seq in sequences) + alignments = [] + + for seq in sequences: + # Pad sequence to max length + padded_seq = str(seq.seq).ljust(max_length, "-") + new_seq = SeqRecord(Seq(padded_seq), id=seq.id, description=seq.description) + alignments.append(new_seq) + + # Create and save the alignment + msa = MultipleSeqAlignment(alignments) + AlignIO.write(msa, aligned_file, "fasta") + alignment = msa + log.append("Simple padding alignment completed as fallback method") + log.append(f"Alignment saved to {aligned_file}") + + # Step 3: Generate a phylogenetic tree + log.append("\n## Step 3: Phylogenetic Analysis") + tree_file = os.path.join(output_dir, "tree.newick") + + # Calculate distance matrix + calculator = DistanceCalculator("identity") + dm = calculator.get_distance(alignment) + + # Construct the phylogenetic tree using neighbor-joining method + constructor = DistanceTreeConstructor() + tree = constructor.nj(dm) + + # Save the tree + Phylo.write(tree, tree_file, "newick") + log.append("Phylogenetic tree constructed using neighbor-joining method") + log.append(f"Tree saved to {tree_file}") + + # Step 4: Analyze conserved regions + log.append("\n## Step 4: Conservation Analysis") + conservation_file = os.path.join(output_dir, "conservation.txt") + + # Simple conservation analysis + alignment_length = alignment.get_alignment_length() + conserved_positions = [] + + with open(conservation_file, "w") as f: + f.write("Position\tConservation_Score\tConsensus\n") + + for i in range(alignment_length): + # Get all amino acids at this position + column = alignment[:, i] + set(column) + + # Calculate a simple conservation score (percentage of most common AA) + most_common_aa = max(column, key=column.count) + conservation_score = column.count(most_common_aa) / len(column) + + f.write(f"{i + 1}\t{conservation_score:.2f}\t{most_common_aa}\n") + + # Consider positions with >80% conservation as conserved + if conservation_score > 0.8: + conserved_positions.append(i + 1) + + log.append(f"Conservation analysis completed and saved to {conservation_file}") + log.append(f"Identified {len(conserved_positions)} highly conserved positions (>80% conservation)") + + if conserved_positions: + log.append(f"Conserved positions: {', '.join(map(str, conserved_positions[:10]))}") + if len(conserved_positions) > 10: + log.append(f"... and {len(conserved_positions) - 10} more") + + # Final summary + log.append("\n## Summary") + log.append("The analysis successfully completed with the following outputs:") + log.append(f"1. Multiple sequence alignment: {aligned_file}") + log.append(f"2. Phylogenetic tree: {tree_file}") + log.append(f"3. Conservation analysis: {conservation_file}") + + return "\n".join(log) diff --git a/BioScientist/agent_system/engines/v1_executor_backup/tool/bioimaging.py b/BioScientist/agent_system/engines/v1_executor_backup/tool/bioimaging.py new file mode 100644 index 0000000000000000000000000000000000000000..78dd81e311b4d70feb7ff0f0beeda4ca434ad0b6 --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/tool/bioimaging.py @@ -0,0 +1,1394 @@ +import logging +import os +import zipfile + +import matplotlib +import requests + +matplotlib.use("Agg") # Use non-interactive backend +import nibabel as nib +import numpy as np +import SimpleITK as sitk +import torch +import torch.serialization +from nnunet.inference.predict import predict_from_folder + +# Apply safe globals for torch serialization +torch.serialization.add_safe_globals([tuple, list, dict, set, int, float, str, bytes, bytearray]) +torch.serialization.add_safe_globals([complex, slice, range]) +torch.serialization.add_safe_globals([np.core.multiarray.scalar]) + +# Configure logging +logger = logging.getLogger(__name__) + + +# ============================================================================ +# SEGMENTATION CLASS +# ============================================================================ + + +class SegmentationTool: + """ + A comprehensive tool for medical image segmentation using nnUNet. + Handles BRATS dataset processing, modality splitting, and segmentation visualization. + """ + + def __init__(self): + """Initialize the SegmentationTool.""" + self.supported_formats = [".nii", ".nii.gz"] + logger.info("SegmentationTool initialized") + + def split_modalities(self, input_file, output_dir, case_name="BRAT"): + """ + Split a 4D NIfTI file into separate modality files for nnUNet + Args: + input_file: Path to the 4D NIfTI file + output_dir: Directory to save the split files + case_name: Base name for the case (default: BRAT) + Returns: + output_dir: Path to directory containing split modality files + """ + os.makedirs(output_dir, exist_ok=True) + + # Load the 4D image + print(f"Loading {input_file}...") + img = nib.load(input_file) + data = img.get_fdata() + + print(f"Image shape: {data.shape}") + print("Expected shape: (X, Y, Z, 4) for 4 modalities") + + if len(data.shape) != 4: + raise ValueError(f"Expected 4D image, got {len(data.shape)}D") + + if data.shape[3] != 4: + raise ValueError(f"Expected 4 modalities, got {data.shape[3]}") + + # Split into separate files + modalities = ["FLAIR", "T1w", "t1gd", "T2w"] + + for i, modality in enumerate(modalities): + # Extract the modality data + modality_data = data[:, :, :, i] + + # Create a new NIfTI image with the same header but 3D data + modality_img = nib.Nifti1Image(modality_data, img.affine, img.header) + + # Save with the expected naming convention + output_file = os.path.join(output_dir, f"{case_name}_{i:04d}.nii.gz") + nib.save(modality_img, output_file) + + print(f"Saved {modality} modality to {output_file}") + print(f" Shape: {modality_data.shape}, Data type: {modality_data.dtype}") + + print(f"\nAll modalities saved to {output_dir}") + return output_dir + + def prepare_input_for_nnunet(self, input_path, output_dir, case_name="BRAT"): + """ + Prepare input data for nnUNet by handling both 4D and pre-split modality files + Args: + input_path: Path to input file or directory + output_dir: Directory to save prepared files + case_name: Base name for the case (default: BRAT) + Returns: + prepared_dir: Path to directory with nnUNet-ready files + """ + os.makedirs(output_dir, exist_ok=True) + + if os.path.isfile(input_path): + # Single file - check if it's 4D + if input_path.endswith((".nii", ".nii.gz")): + try: + img = nib.load(input_path) + if len(img.shape) == 4 and img.shape[3] == 4: + print("4D NIfTI file detected, splitting modalities...") + return self.split_modalities(input_path, output_dir, case_name) + else: + print("Single 3D file detected, copying to output directory...") + # Copy single file with proper naming + output_file = os.path.join(output_dir, f"{case_name}_0000.nii.gz") + import shutil + + shutil.copy2(input_path, output_file) + return output_dir + except Exception as e: + print(f"Error reading file {input_path}: {e}") + raise + elif os.path.isdir(input_path): + # Directory - check if it already has split modalities + files = [f for f in os.listdir(input_path) if f.endswith((".nii", ".nii.gz"))] + + if any(f.endswith("_0000.nii.gz") for f in files): + print("Directory already contains split modality files, using as-is...") + # Copy existing files to output directory + for f in files: + if f.endswith((".nii", ".nii.gz")): + import shutil + + shutil.copy2(os.path.join(input_path, f), os.path.join(output_dir, f)) + return output_dir + else: + # Check if there's a 4D file to split + for f in files: + if f.endswith((".nii", ".nii.gz")): + try: + img = nib.load(os.path.join(input_path, f)) + if len(img.shape) == 4 and img.shape[3] == 4: + print(f"4D NIfTI file {f} detected, splitting modalities...") + return self.split_modalities(os.path.join(input_path, f), output_dir, case_name) + except Exception as e: + logging.debug("Skipping file %s during 4D check: %s", f, e) + continue + + print("No 4D files found, copying existing files...") + # Copy existing files to output directory + for f in files: + if f.endswith((".nii", ".nii.gz")): + import shutil + + shutil.copy2(os.path.join(input_path, f), os.path.join(output_dir, f)) + return output_dir + + raise ValueError(f"Input path {input_path} is neither a valid file nor directory") + + def setup_nnunet_environment(self, results_folder=None, raw_data_base=None, preprocessed=None): + """ + Setup nnU-Net environment variables according to official documentation + Args: + results_folder: Path to nnUNet results folder (default: ~/nnUNet_results) + raw_data_base: Path to raw data base (default: ~/nnUNet_raw_data_base) + preprocessed: Path to preprocessed data (default: ~/nnUNet_preprocessed) + """ + # Set nnUNet environment variables as per official documentation + if results_folder: + os.environ["nnUNet_RESULTS_FOLDER"] = os.path.expanduser(results_folder) + elif "nnUNet_RESULTS_FOLDER" not in os.environ: + os.environ["nnUNet_RESULTS_FOLDER"] = os.path.expanduser("~/nnUNet_results") + + if raw_data_base: + os.environ["nnUNet_raw_data_base"] = os.path.expanduser(raw_data_base) + elif "nnUNet_raw_data_base" not in os.environ: + os.environ["nnUNet_raw_data_base"] = os.path.expanduser("~/nnUNet_raw_data_base") + + if preprocessed: + os.environ["nnUNet_preprocessed"] = os.path.expanduser(preprocessed) + elif "nnUNet_preprocessed" not in os.environ: + os.environ["nnUNet_preprocessed"] = os.path.expanduser("~/nnUNet_preprocessed") + + # Create directories if they don't exist + for path in [ + os.environ["nnUNet_RESULTS_FOLDER"], + os.environ["nnUNet_raw_data_base"], + os.environ["nnUNet_preprocessed"], + ]: + os.makedirs(path, exist_ok=True) + + print("nnU-Net environment variables set:") + print(f" nnUNet_RESULTS_FOLDER: {os.environ['nnUNet_RESULTS_FOLDER']}") + print(f" nnUNet_raw_data_base: {os.environ['nnUNet_raw_data_base']}") + print(f" nnUNet_preprocessed: {os.environ['nnUNet_preprocessed']}") + + def _download_model_with_browser_headers(self, url, output_path): + """ + Download model with browser-like headers to bypass Zenodo's anti-bot protection + """ + headers = { + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Language": "en-US,en;q=0.5", + "Accept-Encoding": "gzip, deflate", + "Connection": "keep-alive", + "Upgrade-Insecure-Requests": "1", + } + + logger.info(f"Downloading model from: {url}") + + try: + response = requests.get(url, headers=headers, stream=True, timeout=300) + response.raise_for_status() + + with open(output_path, "wb") as f: + for chunk in response.iter_content(chunk_size=8192): + if chunk: + f.write(chunk) + + logger.info(f"Download completed: {output_path}") + return True + + except Exception as e: + logger.error(f"Download failed: {e}") + if os.path.exists(output_path): + os.remove(output_path) + return False + + def _download_and_extract_model(self, task_id, model_type="3d_fullres"): + """ + Download and extract nnUNet model with browser-like headers - extracts directly to nnUNet directory + """ + results_folder = os.environ.get("nnUNet_RESULTS_FOLDER", "~/nnUNet_results") + results_folder = os.path.expanduser(results_folder) + + # Create the nnUNet directory + nnunet_dir = os.path.join(results_folder, "nnUNet") + os.makedirs(nnunet_dir, exist_ok=True) + + # Check if model already exists + task_dir = os.path.join(nnunet_dir, model_type, task_id) + if os.path.exists(task_dir): + # Check if it has the expected structure + plans_file = os.path.join(task_dir, "nnUNetTrainerV2__nnUNetPlansv2.1") + if os.path.exists(plans_file): + logger.info(f"Model already exists for task '{task_id}' with {model_type}") + return True + + # Download URL for the task + download_url = f"https://zenodo.org/record/4003545/files/{task_id}.zip?download=1" + + # Create temporary file for download + temp_zip = os.path.join(nnunet_dir, f"{task_id}_temp.zip") + + # Download with browser headers + if self._download_model_with_browser_headers(download_url, temp_zip): + try: + logger.info(f"Extracting {temp_zip} to {nnunet_dir}") + with zipfile.ZipFile(temp_zip, "r") as zip_ref: + zip_ref.extractall(nnunet_dir) + + # Remove temporary zip file + os.remove(temp_zip) + + # Verify extraction worked + if os.path.exists(task_dir): + logger.info(f"Model successfully downloaded and extracted for task '{task_id}'") + logger.info(f"Model directory: {task_dir}") + return True + else: + logger.error(f"Task directory not found after extraction: {task_dir}") + return False + + except Exception as e: + logger.error(f"Extraction failed: {e}") + if os.path.exists(temp_zip): + os.remove(temp_zip) + return False + else: + return False + + def segment_with_nn_unet( + self, + image_path, + output_dir, + task_id, + model_type="3d_fullres", + folds=None, + use_tta=False, + num_threads=1, + mixed_precision=True, + verbose=True, + auto_prepare_input=True, + results_folder=None, + auto_download=True, + ): + """ + Segment images using nnUNet with proper environment setup and automatic model downloading + Args: + image_path: Path to input image file or directory + output_dir: Directory to save segmentation results + task_id: Task identifier (e.g., 'Task001_BrainTumour') + model_type: Model type (default: '3d_fullres') + folds: Model folds to use (default: [0, 1, 2, 3, 4]) + use_tta: Use test time augmentation (default: False) + num_threads: Number of threads for preprocessing (default: 1) + mixed_precision: Use mixed precision (default: True) + verbose: Verbose logging (default: True) + auto_prepare_input: Automatically prepare input for nnUNet (default: True) + results_folder: Path to nnUNet results folder (default: None, will use environment variable or default) + auto_download: Automatically download missing models (default: True) + """ + if folds is None: + folds = [0, 1, 2, 3, 4] + os.makedirs(output_dir, exist_ok=True) + logging.basicConfig(level=logging.INFO if verbose else logging.WARNING) + + # Setup nnUNet environment first + self.setup_nnunet_environment(results_folder=results_folder) + + # Prepare input data if requested + if auto_prepare_input: + temp_input_dir = os.path.join(output_dir, "temp_input") + prepared_input_dir = self.prepare_input_for_nnunet(image_path, temp_input_dir) + image_path = prepared_input_dir + logging.info(f"Input prepared for nnUNet: {image_path}") + + logging.info("Verifying NIfTI input files...") + + def verify_nifti_input(image_path): + if os.path.isfile(image_path): + nib.load(image_path) + else: + for file in os.listdir(image_path): + if file.endswith(".nii") or file.endswith(".nii.gz"): + nib.load(os.path.join(image_path, file)) + + verify_nifti_input(image_path) + + # Get or set up the results folder + results_folder = os.environ.get("nnUNet_RESULTS_FOLDER") + if not results_folder: + # Try common locations + common_paths = [ + "./models/nnUNet", + "~/nnUNet_results", + "~/biomni_models/nnUNet", + ] + + for base_path in common_paths: + expanded_path = os.path.expanduser(base_path) + if os.path.exists(expanded_path): + results_folder = expanded_path + break + + # If still no folder found, create one + if not results_folder: + results_folder = os.path.expanduser("~/nnUNet_results") + os.makedirs(results_folder, exist_ok=True) + logging.info(f"Created new results folder: {results_folder}") + + os.environ["RESULTS_FOLDER"] = results_folder + os.environ["nnUNet_RESULTS_FOLDER"] = results_folder + logging.info(f"Set RESULTS_FOLDER environment variable to: {results_folder}") + + # Construct the expected model path - using the correct structure + model_folder = os.path.join(results_folder, "nnUNet", model_type, task_id, "nnUNetTrainerV2__nnUNetPlansv2.1") + + logging.info(f"Looking for model at: {model_folder}") + + # Check if model files actually exist (not just the directory) + def check_model_files_exist(model_folder, folds): + """Check if actual model weight files exist for the specified folds""" + if not os.path.exists(model_folder): + return False + + # Check for plans.pkl and other required files + required_files = ["plans.pkl"] + for req_file in required_files: + if not os.path.exists(os.path.join(model_folder, req_file)): + return False + + # Check if at least one fold has model files + for fold in folds: + fold_dir = os.path.join(model_folder, f"fold_{fold}") + if os.path.exists(fold_dir): + # Look for model files (.model, .model.pkl, etc.) + model_files = [f for f in os.listdir(fold_dir) if f.endswith((".model", ".model.pkl"))] + if model_files: + return True + return False + + # Check if model exists, download if not + if not check_model_files_exist(model_folder, folds): + if auto_download: + logging.info(f"Model weights for {task_id} not found. Downloading...") + # Ensure the results folder structure exists + os.makedirs(os.path.dirname(model_folder), exist_ok=True) + + # Download the model using our custom download function + if self._download_and_extract_model(task_id, model_type): + logging.info(f"Downloaded pretrained model for {task_id} successfully.") + else: + raise RuntimeError(f"Failed to download model for {task_id}") + + # Verify the download worked + if not check_model_files_exist(model_folder, folds): + raise RuntimeError( + f"Model download completed but files not found at expected location: {model_folder}" + ) + else: + # Ask user for permission to download + user_input = ( + input(f"Model weights for {task_id} not found. Do you want to download them? (y/n): ") + .strip() + .lower() + ) + if user_input == "y": + # Download the model using our custom download function + if self._download_and_extract_model(task_id, model_type): + logging.info(f"Downloaded pretrained model for {task_id} successfully.") + else: + raise RuntimeError(f"Failed to download model for {task_id}") + else: + raise RuntimeError("Model weights not found and download declined by user.") + + # Double-check that we now have the model + if not check_model_files_exist(model_folder, folds): + raise RuntimeError(f"Model files still not found at {model_folder} after download attempt") + + logging.info(f"Using model: {model_folder}") + + # Patch torch.load for compatibility + original_torch_load = torch.load + + def patched_torch_load(*args, **kwargs): + kwargs["weights_only"] = False + return original_torch_load(*args, **kwargs) + + # Run the segmentation + torch.load = patched_torch_load + try: + predict_from_folder( + model=model_folder, + input_folder=image_path, + output_folder=output_dir, + folds=folds, + save_npz=False, + num_threads_preprocessing=num_threads, + num_threads_nifti_save=num_threads, + mixed_precision=mixed_precision, + lowres_segmentations=None, + part_id=0, + num_parts=1, + tta=use_tta, + ) + finally: + # Restore original torch.load behavior + torch.load = original_torch_load + + # Clean up temporary input directory if it was created + if auto_prepare_input and os.path.exists(temp_input_dir): + import shutil + + shutil.rmtree(temp_input_dir) + logging.info("Cleaned up temporary input directory") + + logging.info(f"Segmentation outputs stored at: {output_dir}") + return output_dir + + def create_segmentation_visualization(self, original_mri, segmentation, output_dir="./visualization_output"): + """ + Create and save visualization of segmentation results using nilearn + Args: + original_mri: Path to original MRI file + segmentation: Path to segmentation file + output_dir: Directory to save visualization images + Returns: + list: List of saved image file paths + """ + try: + # Import nilearn here to avoid dependency issues + from nilearn import plotting + + # Create output directory + os.makedirs(output_dir, exist_ok=True) + + # Check if files exist + if not os.path.exists(original_mri): + raise FileNotFoundError(f"Original MRI file not found: {original_mri}") + + if not os.path.exists(segmentation): + raise FileNotFoundError(f"Segmentation file not found: {segmentation}") + + print("✅ Files found, creating visualizations...") + saved_files = [] + + # Create and save the main overlay plot + display = plotting.plot_roi( + segmentation, + bg_img=original_mri, + cmap="Set1", + alpha=0.6, + title="Segmentation Overlay", + ) + + # Save the main overlay plot + output_file = os.path.join(output_dir, "segmentation_overlay.png") + display.savefig(output_file, dpi=150, bbox_inches="tight") + saved_files.append(output_file) + print(f"✅ Main overlay saved to: {output_file}") + display.close() + + # Create additional views and save them + # Axial view + display_axial = plotting.plot_roi( + segmentation, + bg_img=original_mri, + cmap="Set1", + alpha=0.6, + title="Segmentation Overlay - Axial View", + display_mode="z", + ) + axial_file = os.path.join(output_dir, "segmentation_axial.png") + display_axial.savefig(axial_file, dpi=150, bbox_inches="tight") + saved_files.append(axial_file) + print(f"✅ Axial view saved to: {axial_file}") + display_axial.close() + + # Sagittal view + display_sagittal = plotting.plot_roi( + segmentation, + bg_img=original_mri, + cmap="Set1", + alpha=0.6, + title="Segmentation Overlay - Sagittal View", + display_mode="x", + ) + sagittal_file = os.path.join(output_dir, "segmentation_sagittal.png") + display_sagittal.savefig(sagittal_file, dpi=150, bbox_inches="tight") + saved_files.append(sagittal_file) + print(f"✅ Sagittal view saved to: {sagittal_file}") + display_sagittal.close() + + # Coronal view + display_coronal = plotting.plot_roi( + segmentation, + bg_img=original_mri, + cmap="Set1", + alpha=0.6, + title="Segmentation Overlay - Coronal View", + display_mode="y", + ) + coronal_file = os.path.join(output_dir, "segmentation_coronal.png") + display_coronal.savefig(coronal_file, dpi=150, bbox_inches="tight") + saved_files.append(coronal_file) + print(f"✅ Coronal view saved to: {coronal_file}") + display_coronal.close() + + print(f"\n✅ All visualizations saved to: {output_dir}") + print("Files created:") + for file_path in saved_files: + print(f" - {os.path.basename(file_path)}") + + return saved_files + + except ImportError: + print(" nilearn not available. Install with: pip install nilearn") + return [] + except Exception as e: + print(f" Visualization failed: {e}") + import traceback + + traceback.print_exc() + return [] + + +# ============================================================================ +# IMAGE REGISTRATION CLASS +# ============================================================================ + + +class ImageRegistrationTool: + """ + A comprehensive tool for medical image registration using SimpleITK. + Supports rigid, affine, and deformable registration with preprocessing and visualization. + """ + + def __init__(self): + """Initialize the ImageRegistrationTool.""" + self.supported_formats = [".nii", ".nii.gz", ".nrrd", ".mha", ".mhd"] + logger.info("ImageRegistrationTool initialized") + + def load_image(self, image_path: str) -> sitk.Image: + """ + Load a medical image using SimpleITK. + + Args: + image_path: Path to the image file + + Returns: + SimpleITK Image object + """ + if not os.path.exists(image_path): + raise FileNotFoundError(f"Image file not found: {image_path}") + + logger.info(f"Loading image: {image_path}") + try: + image = sitk.ReadImage(image_path) + logger.info(f"Successfully loaded image with size: {image.GetSize()}") + return image + except Exception as e: + logger.error(f"Failed to load image {image_path}: {e}") + raise + + def save_image(self, image: sitk.Image, output_path: str) -> None: + """ + Save a SimpleITK image to file. + + Args: + image: SimpleITK Image object + output_path: Path to save the image (must include filename and extension) + """ + # Validate output path + if os.path.isdir(output_path): + raise ValueError( + f"Output path '{output_path}' is a directory. Please provide a file path with extension (e.g., '.nii.gz', '.png', '.jpg')" + ) + + if not os.path.splitext(output_path)[1]: + raise ValueError( + f"Output path '{output_path}' has no file extension. Please add an extension (e.g., '.nii.gz', '.png', '.jpg')" + ) + + os.makedirs(os.path.dirname(output_path), exist_ok=True) + logger.info(f"Saving image to: {output_path}") + try: + sitk.WriteImage(image, output_path) + logger.info("Image saved successfully") + except Exception as e: + logger.error(f"Failed to save image to {output_path}: {e}") + raise + + def preprocess_image(self, image: sitk.Image, denoise: bool = True, normalize: bool = True) -> sitk.Image: + """ + Preprocess an image with denoising and normalization. + + Args: + image: Input SimpleITK image + denoise: Whether to apply denoising + normalize: Whether to apply normalization + + Returns: + Preprocessed SimpleITK image + """ + logger.info("Preprocessing image...") + processed_image = sitk.Image(image) + + if denoise: + # Apply Gaussian smoothing for denoising + processed_image = sitk.SmoothingRecursiveGaussian(processed_image, sigma=1.0) + logger.info("Applied denoising") + + if normalize: + # Normalize to [0, 1] range + min_max_filter = sitk.MinimumMaximumImageFilter() + min_max_filter.Execute(processed_image) + min_val = min_max_filter.GetMinimum() + max_val = min_max_filter.GetMaximum() + + if max_val > min_val: + processed_image = sitk.IntensityWindowing( + processed_image, windowMinimum=min_val, windowMaximum=max_val, outputMinimum=0.0, outputMaximum=1.0 + ) + logger.info("Applied normalization") + + return processed_image + + def create_rigid_transform( + self, fixed_image: sitk.Image, moving_image: sitk.Image, initial_transform: sitk.Transform | None = None + ) -> sitk.Transform: + """ + Create a rigid transform for image registration. + + Args: + fixed_image: Reference (fixed) image + moving_image: Image to be registered + initial_transform: Optional initial transform + + Returns: + Rigid transform object + """ + logger.info("Creating rigid transform...") + + if initial_transform is None: + # Create identity transform + transform = sitk.Euler3DTransform() + else: + transform = initial_transform + + return transform + + def create_affine_transform( + self, fixed_image: sitk.Image, moving_image: sitk.Image, initial_transform: sitk.Transform | None = None + ) -> sitk.Transform: + """ + Create an affine transform for image registration. + + Args: + fixed_image: Reference (fixed) image + moving_image: Image to be registered + initial_transform: Optional initial transform + + Returns: + Affine transform object + """ + logger.info("Creating affine transform...") + + if initial_transform is None: + # Create identity transform + transform = sitk.AffineTransform(3) + else: + transform = initial_transform + + return transform + + def create_deformable_transform( + self, fixed_image: sitk.Image, moving_image: sitk.Image, number_of_control_points: int = 4 + ) -> sitk.Transform: + """ + Create a deformable (B-spline) transform for image registration. + + Args: + fixed_image: Reference (fixed) image + moving_image: Image to be registered + number_of_control_points: Number of B-spline control points per dimension + + Returns: + Deformable transform object + """ + logger.info("Creating deformable transform...") + + # Create B-spline transform + transform = sitk.BSplineTransformInitializer(fixed_image, [number_of_control_points] * 3, order=3) + + return transform + + def setup_registration_method( + self, + transform: sitk.Transform, + metric: str = "mutual_information", + optimizer: str = "gradient_descent", + learning_rate: float = 0.01, + number_of_iterations: int = 100, + gradient_convergence_tolerance: float = 1e-6, + ) -> sitk.ImageRegistrationMethod: + """ + Setup the registration method with specified parameters. + + Args: + transform: Transform object + metric: Similarity metric name + optimizer: Optimizer name + learning_rate: Learning rate for gradient descent + number_of_iterations: Maximum number of iterations + gradient_convergence_tolerance: Convergence tolerance + + Returns: + Configured registration method + """ + logger.info(f"Setting up registration method: {metric} metric, {optimizer} optimizer") + + registration_method = sitk.ImageRegistrationMethod() + + # Set similarity metric + if metric == "mutual_information": + registration_method.SetMetricAsMattesMutualInformation(numberOfHistogramBins=50) + elif metric == "mean_squares": + registration_method.SetMetricAsMeanSquares() + elif metric == "correlation": + registration_method.SetMetricAsCorrelation() + elif metric == "normalized_correlation": + registration_method.SetMetricAsNormalizedCorrelation() + else: + logger.warning(f"Unknown metric {metric}, using mutual information") + registration_method.SetMetricAsMattesMutualInformation(numberOfHistogramBins=50) + + # Set optimizer + if optimizer == "gradient_descent": + registration_method.SetOptimizerAsGradientDescent( + learningRate=learning_rate, + numberOfIterations=number_of_iterations, + convergenceMinimumValue=gradient_convergence_tolerance, + ) + elif optimizer == "lbfgsb": + registration_method.SetOptimizerAsLBFGSB( + gradientConvergenceTolerance=gradient_convergence_tolerance, numberOfIterations=number_of_iterations + ) + elif optimizer == "powell": + registration_method.SetOptimizerAsPowell(numberOfIterations=number_of_iterations, maximumLineIterations=20) + elif optimizer == "amoeba": + registration_method.SetOptimizerAsAmoeba( + numberOfIterations=number_of_iterations, parametersConvergenceTolerance=gradient_convergence_tolerance + ) + else: + logger.warning(f"Unknown optimizer {optimizer}, using gradient descent") + registration_method.SetOptimizerAsGradientDescent( + learningRate=learning_rate, + numberOfIterations=number_of_iterations, + convergenceMinimumValue=gradient_convergence_tolerance, + ) + + # Set interpolator + registration_method.SetInterpolator(sitk.sitkLinear) + + # Set initial transform + registration_method.SetInitialTransform(transform, inPlace=False) + + return registration_method + + def register_images( + self, + fixed_image: sitk.Image, + moving_image: sitk.Image, + transform: sitk.Transform, + registration_method: sitk.ImageRegistrationMethod, + ) -> tuple[sitk.Transform, sitk.Image]: + """ + Perform image registration. + + Args: + fixed_image: Reference (fixed) image + moving_image: Image to be registered + transform: Transform object + registration_method: Configured registration method + + Returns: + Tuple of (final_transform, registered_image) + """ + logger.info("Starting image registration...") + + # Add iteration callback + def command_iteration(): + logger.info( + f"Iteration: {registration_method.GetOptimizerIteration()}, " + f"Metric value: {registration_method.GetMetricValue():.6f}" + ) + + registration_method.AddCommand(sitk.sitkIterationEvent, command_iteration) + + try: + # Execute registration + final_transform = registration_method.Execute(fixed_image, moving_image) + + # Apply transform to moving image + resampler = sitk.ResampleImageFilter() + resampler.SetReferenceImage(fixed_image) + resampler.SetInterpolator(sitk.sitkLinear) + resampler.SetDefaultPixelValue(0) + resampler.SetTransform(final_transform) + + registered_image = resampler.Execute(moving_image) + + logger.info("Registration completed successfully") + return final_transform, registered_image + + except Exception as e: + logger.error(f"Registration failed: {e}") + raise + + def calculate_similarity_metrics(self, image1: sitk.Image, image2: sitk.Image) -> dict[str, float]: + """ + Calculate similarity metrics between two images. + + Args: + image1: First image + image2: Second image + + Returns: + Dictionary of similarity metrics + """ + logger.info("Calculating similarity metrics...") + metrics = {} + + try: + # Convert to numpy arrays + array1 = sitk.GetArrayFromImage(image1) + array2 = sitk.GetArrayFromImage(image2) + + # Flatten arrays + flat1 = array1.flatten() + flat2 = array2.flatten() + + # Remove invalid values + valid_mask = np.isfinite(flat1) & np.isfinite(flat2) + flat1 = flat1[valid_mask] + flat2 = flat2[valid_mask] + + if len(flat1) == 0: + logger.warning("No valid pixels for similarity calculation") + return { + "mutual_information": 0.0, + "mean_squares": 0.0, + "correlation": 0.0, + "normalized_correlation": 0.0, + } + + # Mean Squared Error + mse = np.mean((flat1 - flat2) ** 2) + metrics["mean_squares"] = -mse # Negative because we want to maximize + + # Pearson Correlation + if np.std(flat1) > 0 and np.std(flat2) > 0: + correlation = np.corrcoef(flat1, flat2)[0, 1] + metrics["correlation"] = correlation if not np.isnan(correlation) else 0.0 + else: + metrics["correlation"] = 0.0 + + # Normalized Cross Correlation + if np.std(flat1) > 0 and np.std(flat2) > 0: + ncc = np.corrcoef(flat1, flat2)[0, 1] + metrics["normalized_correlation"] = ncc if not np.isnan(ncc) else 0.0 + else: + metrics["normalized_correlation"] = 0.0 + + # Mutual Information (simplified calculation) + try: + # Create 2D histogram + hist_2d, x_edges, y_edges = np.histogram2d(flat1, flat2, bins=50) + hist_2d = hist_2d + 1e-10 # Add small value to avoid log(0) + + # Normalize histogram + pxy = hist_2d / np.sum(hist_2d) + px = np.sum(pxy, axis=1) + py = np.sum(pxy, axis=0) + + # Calculate mutual information + mi = 0.0 + for i in range(len(px)): + for j in range(len(py)): + if pxy[i, j] > 0 and px[i] > 0 and py[j] > 0: + mi += pxy[i, j] * np.log2(pxy[i, j] / (px[i] * py[j])) + + metrics["mutual_information"] = mi + + except Exception as e: + logger.warning(f"Failed to calculate mutual information: {e}") + metrics["mutual_information"] = 0.0 + + except Exception as e: + logger.warning(f"Failed to calculate similarity metrics: {e}") + metrics = { + "mutual_information": 0.0, + "mean_squares": 0.0, + "correlation": 0.0, + "normalized_correlation": 0.0, + } + + logger.info("Similarity metrics calculated") + return metrics + + +# ============================================================================ +# CONVENIENCE FUNCTIONS FOR BIOMNI INTEGRATION +# ============================================================================ + + +# Segmentation convenience functions +def split_modalities(input_file, output_dir, case_name="BRAT"): + """Convenience function for splitting modalities""" + tool = SegmentationTool() + return tool.split_modalities(input_file, output_dir, case_name) + + +def prepare_input_for_nnunet(input_path, output_dir, case_name="BRAT"): + """Convenience function for preparing nnUNet input""" + tool = SegmentationTool() + return tool.prepare_input_for_nnunet(input_path, output_dir, case_name) + + +def segment_with_nn_unet( + image_path, + output_dir, + task_id, + model_type="3d_fullres", + folds=None, + use_tta=False, + num_threads=1, + mixed_precision=True, + verbose=True, + auto_prepare_input=True, + results_folder=None, +): + """Convenience function for nnUNet segmentation""" + tool = SegmentationTool() + return tool.segment_with_nn_unet( + image_path, + output_dir, + task_id, + model_type, + folds, + use_tta, + num_threads, + mixed_precision, + verbose, + auto_prepare_input, + results_folder, + ) + + +def create_segmentation_visualization(original_mri, segmentation, output_dir="./visualization_output"): + """Convenience function for segmentation visualization""" + tool = SegmentationTool() + return tool.create_segmentation_visualization(original_mri, segmentation, output_dir) + + +# Registration convenience functions +def preprocess_image(image_path: str, output_path: str, denoise: bool = True, normalize: bool = True) -> str: + """ + Standalone image preprocessing function for Biomni integration. + + Args: + image_path: Path to input image + output_path: Path to save preprocessed image + denoise: Whether to apply denoising (default: True) + normalize: Whether to apply normalization (default: True) + + Returns: + Path to the saved preprocessed image + """ + tool = ImageRegistrationTool() + image = tool.load_image(image_path) + preprocessed_image = tool.preprocess_image(image, denoise, normalize) + tool.save_image(preprocessed_image, output_path) + return output_path + + +def quick_rigid_registration( + fixed_image_path: str, + moving_image_path: str, + output_dir: str, + metric: str = "mutual_information", + optimizer: str = "gradient_descent", + preprocess: bool = True, + create_visualizations: bool = True, + learning_rate: float = 0.01, + number_of_iterations: int = 100, + gradient_convergence_tolerance: float = 1e-6, +) -> dict: + """ + Quick rigid registration function for Biomni integration. + + Args: + fixed_image_path: Path to reference image + moving_image_path: Path to image to register + output_dir: Directory to save results + metric: Similarity metric + optimizer: Optimization method + preprocess: Whether to preprocess images + create_visualizations: Whether to create visualizations + learning_rate: Learning rate for optimizer + number_of_iterations: Maximum iterations + gradient_convergence_tolerance: Convergence tolerance + + Returns: + Dictionary with registration results + """ + tool = ImageRegistrationTool() + + # Load images + fixed_image = tool.load_image(fixed_image_path) + moving_image = tool.load_image(moving_image_path) + + # Preprocess if requested + if preprocess: + fixed_image = tool.preprocess_image(fixed_image) + moving_image = tool.preprocess_image(moving_image) + + # Create transform and registration method + transform = tool.create_rigid_transform(fixed_image, moving_image) + registration_method = tool.setup_registration_method( + transform, metric, optimizer, learning_rate, number_of_iterations, gradient_convergence_tolerance + ) + + # Perform registration + final_transform, registered_image = tool.register_images(fixed_image, moving_image, transform, registration_method) + + # Save results + os.makedirs(output_dir, exist_ok=True) + + # Save registered image + registered_path = os.path.join(output_dir, "rigid_registered.nii.gz") + tool.save_image(registered_image, registered_path) + + # Save transform + transform_path = os.path.join(output_dir, "rigid_transform.tfm") + sitk.WriteTransform(final_transform, transform_path) + + # Calculate metrics + metrics_before = tool.calculate_similarity_metrics(fixed_image, moving_image) + metrics_after = tool.calculate_similarity_metrics(fixed_image, registered_image) + + results = { + "registered_image_path": registered_path, + "transform_path": transform_path, + "metrics_before": metrics_before, + "metrics_after": metrics_after, + "registration_type": "rigid", + } + + return results + + +def quick_affine_registration( + fixed_image_path: str, + moving_image_path: str, + output_dir: str, + metric: str = "mutual_information", + optimizer: str = "gradient_descent", + preprocess: bool = True, + create_visualizations: bool = True, + learning_rate: float = 0.01, + number_of_iterations: int = 100, + gradient_convergence_tolerance: float = 1e-6, +) -> dict: + """ + Quick affine registration function for Biomni integration. + + Args: + fixed_image_path: Path to reference image + moving_image_path: Path to image to register + output_dir: Directory to save results + metric: Similarity metric + optimizer: Optimization method + preprocess: Whether to preprocess images + create_visualizations: Whether to create visualizations + learning_rate: Learning rate for optimizer + number_of_iterations: Maximum iterations + gradient_convergence_tolerance: Convergence tolerance + + Returns: + Dictionary with registration results + """ + tool = ImageRegistrationTool() + + # Load images + fixed_image = tool.load_image(fixed_image_path) + moving_image = tool.load_image(moving_image_path) + + # Preprocess if requested + if preprocess: + fixed_image = tool.preprocess_image(fixed_image) + moving_image = tool.preprocess_image(moving_image) + + # Create transform and registration method + transform = tool.create_affine_transform(fixed_image, moving_image) + registration_method = tool.setup_registration_method( + transform, metric, optimizer, learning_rate, number_of_iterations, gradient_convergence_tolerance + ) + + # Perform registration + final_transform, registered_image = tool.register_images(fixed_image, moving_image, transform, registration_method) + + # Save results + os.makedirs(output_dir, exist_ok=True) + + # Save registered image + registered_path = os.path.join(output_dir, "affine_registered.nii.gz") + tool.save_image(registered_image, registered_path) + + # Save transform + transform_path = os.path.join(output_dir, "affine_transform.tfm") + sitk.WriteTransform(final_transform, transform_path) + + # Calculate metrics + metrics_before = tool.calculate_similarity_metrics(fixed_image, moving_image) + metrics_after = tool.calculate_similarity_metrics(fixed_image, registered_image) + + results = { + "registered_image_path": registered_path, + "transform_path": transform_path, + "metrics_before": metrics_before, + "metrics_after": metrics_after, + "registration_type": "affine", + } + + return results + + +def quick_deformable_registration( + fixed_image_path: str, + moving_image_path: str, + output_dir: str, + metric: str = "mutual_information", + optimizer: str = "gradient_descent", + preprocess: bool = True, + create_visualizations: bool = True, + learning_rate: float = 0.01, + number_of_iterations: int = 100, + gradient_convergence_tolerance: float = 1e-6, + number_of_control_points: int = 4, +) -> dict: + """ + Quick deformable registration function for Biomni integration. + + Args: + fixed_image_path: Path to reference image + moving_image_path: Path to image to register + output_dir: Directory to save results + metric: Similarity metric + optimizer: Optimization method + preprocess: Whether to preprocess images + create_visualizations: Whether to create visualizations + learning_rate: Learning rate for optimizer + number_of_iterations: Maximum iterations + gradient_convergence_tolerance: Convergence tolerance + number_of_control_points: Number of B-spline control points + + Returns: + Dictionary with registration results + """ + tool = ImageRegistrationTool() + + # Load images + fixed_image = tool.load_image(fixed_image_path) + moving_image = tool.load_image(moving_image_path) + + # Preprocess if requested + if preprocess: + fixed_image = tool.preprocess_image(fixed_image) + moving_image = tool.preprocess_image(moving_image) + + # Create transform and registration method + transform = tool.create_deformable_transform(fixed_image, moving_image, number_of_control_points) + registration_method = tool.setup_registration_method( + transform, metric, optimizer, learning_rate, number_of_iterations, gradient_convergence_tolerance + ) + + # Perform registration + final_transform, registered_image = tool.register_images(fixed_image, moving_image, transform, registration_method) + + # Save results + os.makedirs(output_dir, exist_ok=True) + + # Save registered image + registered_path = os.path.join(output_dir, "deformable_registered.nii.gz") + tool.save_image(registered_image, registered_path) + + # Save transform + transform_path = os.path.join(output_dir, "deformable_transform.tfm") + sitk.WriteTransform(final_transform, transform_path) + + # Calculate metrics + metrics_before = tool.calculate_similarity_metrics(fixed_image, moving_image) + metrics_after = tool.calculate_similarity_metrics(fixed_image, registered_image) + + results = { + "registered_image_path": registered_path, + "transform_path": transform_path, + "metrics_before": metrics_before, + "metrics_after": metrics_after, + "registration_type": "deformable", + } + + return results + + +def batch_register_images( + fixed_image_path: str, + moving_images_dir: str, + output_dir: str, + transform_type: str = "rigid", + metric: str = "mutual_information", + optimizer: str = "gradient_descent", + preprocess: bool = True, + create_visualizations: bool = True, + learning_rate: float = 0.01, + number_of_iterations: int = 100, + gradient_convergence_tolerance: float = 1e-6, +) -> dict: + """ + Batch registration of multiple images to a single reference. + + Args: + fixed_image_path: Path to reference image + moving_images_dir: Directory containing images to register + output_dir: Directory to save results + transform_type: Type of registration ('rigid', 'affine', 'deformable') + metric: Similarity metric + optimizer: Optimization method + preprocess: Whether to preprocess images + create_visualizations: Whether to create visualizations + learning_rate: Learning rate for optimizer + number_of_iterations: Maximum iterations + gradient_convergence_tolerance: Convergence tolerance + + Returns: + Dictionary with batch registration results + """ + logger.info(f"Starting batch {transform_type} registration...") + + # Find all image files in the directory + image_files = [] + for file in os.listdir(moving_images_dir): + if any(file.endswith(ext) for ext in [".nii", ".nii.gz", ".nrrd", ".mha", ".mhd"]): + image_files.append(os.path.join(moving_images_dir, file)) + + if not image_files: + raise ValueError(f"No image files found in {moving_images_dir}") + + logger.info(f"Found {len(image_files)} images to register") + + # Create output directory + os.makedirs(output_dir, exist_ok=True) + + # Process each image + results = {} + ImageRegistrationTool() + + for i, moving_image_path in enumerate(image_files): + logger.info(f"Processing {i + 1}/{len(image_files)}: {os.path.basename(moving_image_path)}") + + # Create individual output directory + image_name = os.path.splitext(os.path.basename(moving_image_path))[0] + if image_name.endswith(".nii"): + image_name = os.path.splitext(image_name)[0] + + individual_output_dir = os.path.join(output_dir, f"registration_{image_name}") + + try: + if transform_type == "rigid": + result = quick_rigid_registration( + fixed_image_path, + moving_image_path, + individual_output_dir, + metric, + optimizer, + preprocess, + create_visualizations, + learning_rate, + number_of_iterations, + gradient_convergence_tolerance, + ) + elif transform_type == "affine": + result = quick_affine_registration( + fixed_image_path, + moving_image_path, + individual_output_dir, + metric, + optimizer, + preprocess, + create_visualizations, + learning_rate, + number_of_iterations, + gradient_convergence_tolerance, + ) + elif transform_type == "deformable": + result = quick_deformable_registration( + fixed_image_path, + moving_image_path, + individual_output_dir, + metric, + optimizer, + preprocess, + create_visualizations, + learning_rate, + number_of_iterations, + gradient_convergence_tolerance, + ) + else: + raise ValueError(f"Unknown transform type: {transform_type}") + + results[image_name] = result + logger.info(f"Successfully registered {image_name}") + + except Exception as e: + logger.error(f"Failed to register {image_name}: {e}") + results[image_name] = {"error": str(e)} + + logger.info(f"Batch registration completed. Processed {len(image_files)} images.") + return results + + +def calculate_similarity_metrics(image1_path: str, image2_path: str) -> dict[str, float]: + """ + Calculate similarity metrics between two images. + + Args: + image1_path: Path to first image + image2_path: Path to second image + + Returns: + Dictionary of similarity metrics + """ + tool = ImageRegistrationTool() + image1 = tool.load_image(image1_path) + image2 = tool.load_image(image2_path) + return tool.calculate_similarity_metrics(image1, image2) diff --git a/BioScientist/agent_system/engines/v1_executor_backup/tool/biophysics.py b/BioScientist/agent_system/engines/v1_executor_backup/tool/biophysics.py new file mode 100644 index 0000000000000000000000000000000000000000..d75887a87c5e51c9f964ec1593537ca14c8f08ab --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/tool/biophysics.py @@ -0,0 +1,513 @@ +def predict_protein_disorder_regions(protein_sequence, threshold=0.5, output_file="disorder_prediction_results.csv"): + """Predicts intrinsically disordered regions (IDRs) in a protein sequence using IUPred2A. + + Parameters + ---------- + protein_sequence : str + The amino acid sequence of the protein to analyze + threshold : float, optional + The disorder score threshold above which a residue is considered disordered (default: 0.5) + output_file : str, optional + Filename to save the per-residue disorder scores (default: "disorder_prediction_results.csv") + + Returns + ------- + str + A research log summarizing the prediction process and results + + """ + import csv + import re + + import requests + + # Clean the input sequence + protein_sequence = "".join(re.findall(r"[A-Za-z]", protein_sequence)) + + # Step 1: Submit the sequence to IUPred2A web server + url = "https://iupred2a.elte.hu/iupred2a" + payload = { + "seq": protein_sequence, + "iupred2": "long", # Use IUPred2 long disorder prediction + "anchor2": "no", # Don't use ANCHOR2 prediction + } + + try: + response = requests.post(url, data=payload) + response.raise_for_status() + except requests.exceptions.RequestException as e: + return f"Error accessing IUPred2A server: {str(e)}" + + # Step 2: Parse the results to extract disorder scores + result_lines = response.text.split("\n") + scores = [] + + for line in result_lines: + if line.startswith("#") or not line.strip(): + continue + parts = line.split() + if len(parts) >= 3: + try: + position = int(parts[0]) + residue = parts[1] + score = float(parts[2]) + scores.append((position, residue, score)) + except (ValueError, IndexError): + continue + + if not scores: + return "No valid prediction data was returned from the server." + + # Step 3: Identify disordered regions + disordered_regions = [] + current_region = [] + + for pos, _, score in scores: + if score >= threshold: + if not current_region: + current_region = [pos] + elif pos == current_region[-1] + 1: + current_region.append(pos) + else: + if len(current_region) > 1: + disordered_regions.append((current_region[0], current_region[-1])) + current_region = [pos] + elif current_region and len(current_region) > 1: + disordered_regions.append((current_region[0], current_region[-1])) + current_region = [] + elif current_region: + current_region = [] + + # Add the last region if it exists + if current_region and len(current_region) > 1: + disordered_regions.append((current_region[0], current_region[-1])) + + # Step 4: Save results to CSV + with open(output_file, "w", newline="") as f: + writer = csv.writer(f) + writer.writerow(["Position", "Amino_Acid", "Disorder_Score", "Is_Disordered"]) + for pos, aa, score in scores: + is_disordered = "Yes" if score >= threshold else "No" + writer.writerow([pos, aa, score, is_disordered]) + + # Step 5: Generate the research log + total_residues = len(scores) + disordered_count = sum(1 for _, _, score in scores if score >= threshold) + disordered_percentage = (disordered_count / total_residues) * 100 if total_residues > 0 else 0 + + log = f""" +Intrinsically Disordered Region (IDR) Prediction Research Log: +============================================================= +Analysis performed using IUPred2A algorithm (long disorder mode) +Protein sequence length: {total_residues} amino acids +Disorder threshold: {threshold} + +Results Summary: +- {disordered_count} residues ({disordered_percentage:.2f}%) predicted as disordered +- {len(disordered_regions)} distinct disordered regions identified + +Disordered Regions: +""" + + if disordered_regions: + for start, end in disordered_regions: + length = end - start + 1 + log += f"- Region {start}-{end} (length: {length} residues)\n" + else: + log += "- No significant disordered regions found\n" + + log += f"\nDetailed per-residue scores saved to: {output_file}" + + return log + + +def analyze_cell_morphology_and_cytoskeleton(image_path, output_dir="./results", threshold_method="otsu"): + """Quantifies cell morphology and cytoskeletal organization from fluorescence microscopy images. + + Parameters + ---------- + image_path : str + Path to the fluorescence microscopy image file + output_dir : str, optional + Directory to save output files (default: './results') + threshold_method : str, optional + Method for cell segmentation ('otsu', 'adaptive', or 'manual') (default: 'otsu') + + Returns + ------- + str + Research log summarizing the analysis steps and results + + """ + import os + from datetime import datetime + + import cv2 + import numpy as np + import pandas as pd + from skimage import exposure, feature, filters, io, measure, morphology + from skimage.color import rgb2gray + + # Create output directory if it doesn't exist + os.makedirs(output_dir, exist_ok=True) + + # Start log + log = f"Cell Morphology and Cytoskeleton Analysis Log - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n" + log += f"Analyzing image: {image_path}\n\n" + + # Load image + log += "Step 1: Loading and preprocessing image\n" + try: + image = io.imread(image_path) + # Convert to grayscale if RGB + if len(image.shape) > 2: + gray_image = rgb2gray(image) + log += "- Converted RGB image to grayscale\n" + else: + gray_image = image + + # Enhance contrast + gray_image = exposure.equalize_hist(gray_image) + log += "- Enhanced image contrast\n" + except Exception as e: + return f"Error loading image: {str(e)}" + + # Segment cells + log += "\nStep 2: Segmenting cells from background\n" + if threshold_method == "otsu": + thresh = filters.threshold_otsu(gray_image) + binary = gray_image > thresh + log += f"- Applied Otsu thresholding (threshold value: {thresh:.4f})\n" + elif threshold_method == "adaptive": + binary = filters.threshold_local(gray_image, block_size=35, offset=0.05) + binary = gray_image > binary + log += "- Applied adaptive thresholding\n" + else: # manual + thresh = 0.5 # Default value, can be parameterized + binary = gray_image > thresh + log += f"- Applied manual thresholding (threshold value: {thresh:.4f})\n" + + # Clean up binary image + binary = morphology.remove_small_objects(binary, min_size=100) + binary = morphology.remove_small_holes(binary, area_threshold=100) + binary = morphology.binary_closing(binary, morphology.disk(3)) + log += "- Applied morphological operations to clean segmentation\n" + + # Label cells + labeled_cells, num_cells = measure.label(binary, return_num=True) + log += f"- Identified {num_cells} cell regions\n" + + # Analyze cell properties + log += "\nStep 3: Analyzing cell morphology\n" + cell_props = measure.regionprops_table( + labeled_cells, + gray_image, + properties=( + "area", + "perimeter", + "major_axis_length", + "minor_axis_length", + "eccentricity", + "orientation", + "solidity", + ), + ) + + # Convert to DataFrame for easier manipulation + cell_df = pd.DataFrame(cell_props) + + # Calculate additional metrics + if len(cell_df) > 0: + cell_df["aspect_ratio"] = cell_df["major_axis_length"] / cell_df["minor_axis_length"] + cell_df["circularity"] = (4 * np.pi * cell_df["area"]) / (cell_df["perimeter"] ** 2) + + # Summary statistics + log += f"- Average cell area: {cell_df['area'].mean():.2f} pixels\n" + log += f"- Average aspect ratio: {cell_df['aspect_ratio'].mean():.2f}\n" + log += f"- Average circularity: {cell_df['circularity'].mean():.2f}\n" + log += f"- Average eccentricity: {cell_df['eccentricity'].mean():.2f}\n" + else: + log += "- No cells detected for morphological analysis\n" + + # Analyze cytoskeletal organization + log += "\nStep 4: Analyzing cytoskeletal organization\n" + + # Edge detection to highlight cytoskeletal fibers + edges = feature.canny(gray_image, sigma=2) + + # Use Hough transform to detect lines (cytoskeletal fibers) + if np.any(edges): + lines = cv2.HoughLinesP( + edges.astype(np.uint8), + 1, + np.pi / 180, + threshold=10, + minLineLength=10, + maxLineGap=5, + ) + + if lines is not None: + # Calculate line orientations + orientations = [] + for line in lines: + x1, y1, x2, y2 = line[0] + if x2 - x1 != 0: # Avoid division by zero + angle = np.arctan2(y2 - y1, x2 - x1) * 180 / np.pi + orientations.append(angle) + + if orientations: + # Convert to numpy array for calculations + orientations = np.array(orientations) + + # Calculate alignment metrics + mean_orientation = np.mean(orientations) + # Normalize angles to -90 to 90 degrees + norm_angles = np.mod(orientations + 90, 180) - 90 + std_orientation = np.std(norm_angles) + + # Order parameter (measure of alignment, 1 = perfectly aligned, 0 = random) + # Convert angles to radians for calculation + rad_angles = np.radians(norm_angles) + order_parameter = np.sqrt(np.mean(np.cos(2 * rad_angles)) ** 2 + np.mean(np.sin(2 * rad_angles)) ** 2) + + log += f"- Detected {len(orientations)} cytoskeletal fibers\n" + log += f"- Mean fiber orientation: {mean_orientation:.2f} degrees\n" + log += f"- Standard deviation of orientation: {std_orientation:.2f} degrees\n" + log += f"- Order parameter (alignment): {order_parameter:.4f} (0=random, 1=aligned)\n" + + # Add fiber data to dataframe + fiber_df = pd.DataFrame({"fiber_orientation": orientations}) + else: + log += "- No fiber orientations could be calculated\n" + fiber_df = pd.DataFrame() + else: + log += "- No cytoskeletal fibers detected\n" + fiber_df = pd.DataFrame() + else: + log += "- No edges detected for cytoskeletal analysis\n" + fiber_df = pd.DataFrame() + + # Save results + log += "\nStep 5: Saving results\n" + + # Save cell morphology data + if len(cell_df) > 0: + cell_csv_path = os.path.join(output_dir, "cell_morphology_data.csv") + cell_df.to_csv(cell_csv_path, index=False) + log += f"- Cell morphology data saved to: {cell_csv_path}\n" + + # Save fiber orientation data + if len(fiber_df) > 0: + fiber_csv_path = os.path.join(output_dir, "fiber_orientation_data.csv") + fiber_df.to_csv(fiber_csv_path, index=False) + log += f"- Fiber orientation data saved to: {fiber_csv_path}\n" + + # Save segmentation image + if num_cells > 0: + segmentation_path = os.path.join(output_dir, "cell_segmentation.png") + io.imsave(segmentation_path, labeled_cells.astype(np.uint8) * 50) + log += f"- Cell segmentation image saved to: {segmentation_path}\n" + + # Summary + log += "\nAnalysis Summary:\n" + log += f"- Processed image: {image_path}\n" + log += f"- Detected {num_cells} cells\n" + if len(cell_df) > 0: + log += f"- Cell size range: {cell_df['area'].min():.1f} to {cell_df['area'].max():.1f} pixels\n" + log += f"- Cell shape: average aspect ratio = {cell_df['aspect_ratio'].mean():.2f}\n" + if "order_parameter" in locals(): + log += f"- Cytoskeletal organization: alignment parameter = {order_parameter:.4f}\n" + + return log + + +def analyze_tissue_deformation_flow(image_sequence, output_dir="results", pixel_scale=1.0): + """Quantify tissue deformation and flow dynamics from microscopy image sequence. + + Parameters + ---------- + image_sequence : list or numpy.ndarray + Sequence of microscopy images (either a list of file paths or a 3D numpy array [time, height, width]) + output_dir : str, optional + Directory to save results (default: "results") + pixel_scale : float, optional + Physical scale of pixels (e.g., μm/pixel) for proper scaling of metrics (default: 1.0) + + Returns + ------- + str + Research log summarizing the analysis steps and results + + """ + import os + from datetime import datetime + + import cv2 + import numpy as np + + # Create output directory if it doesn't exist + os.makedirs(output_dir, exist_ok=True) + + # Load images if paths are provided + if isinstance(image_sequence[0], str): + loaded_images = [] + for img_path in image_sequence: + img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) + if img is None: + return f"Error: Could not load image {img_path}" + loaded_images.append(img) + frames = np.array(loaded_images) + else: + frames = image_sequence + # Convert to grayscale if needed + if len(frames.shape) > 3: # Has color channels + frames = np.array( + [cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY) if frame.shape[-1] == 3 else frame for frame in frames] + ) + + # Parameters for optical flow + lk_params = { + "winSize": (15, 15), + "maxLevel": 2, + "criteria": (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT, 10, 0.03), + } + + # Metrics storage + num_frames = len(frames) + flow_fields = [] + divergence_maps = [] + curl_maps = [] + strain_maps = [] + + # Log initialization + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + log = f"Tissue Deformation and Flow Analysis - {timestamp}\n" + log += f"Number of frames analyzed: {num_frames}\n" + log += f"Image dimensions: {frames[0].shape[0]}x{frames[0].shape[1]} pixels\n" + log += f"Pixel scale: {pixel_scale} units/pixel\n\n" + log += "Analysis Steps:\n" + + # Create feature points grid (evenly spaced points) + y, x = np.mgrid[0 : frames[0].shape[0] : 20, 0 : frames[0].shape[1] : 20] + feature_points = np.stack((x.flatten(), y.flatten()), axis=1).astype(np.float32) + + for i in range(num_frames - 1): + log += f"Processing frame pair {i} and {i + 1}...\n" + + # Calculate optical flow using Lucas-Kanade + prev_frame = frames[i] + next_frame = frames[i + 1] + + # Compute flow for the grid points + next_points, status, _ = cv2.calcOpticalFlowPyrLK(prev_frame, next_frame, feature_points, None, **lk_params) + + # Filter valid points + valid_idx = status.flatten() == 1 + valid_prev_points = feature_points[valid_idx] + valid_next_points = next_points[valid_idx] + + # Calculate displacement vectors + displacement = valid_next_points - valid_prev_points + points = valid_prev_points + + # Interpolate flow field to full image resolution + flow_field = np.zeros((frames[0].shape[0], frames[0].shape[1], 2), dtype=np.float32) + + # Simple nearest-neighbor interpolation for demonstration + # In a production system, you might use more sophisticated interpolation + for j, (x, y) in enumerate(points.astype(int)): + if 0 <= y < flow_field.shape[0] and 0 <= x < flow_field.shape[1]: + flow_field[y, x] = displacement[j] + + # Calculate derivatives for deformation analysis + u = flow_field[:, :, 0] # x-component of flow + v = flow_field[:, :, 1] # y-component of flow + + # Calculate divergence (expansion/contraction) + # Using central differences for derivatives + u_x = cv2.Sobel(u, cv2.CV_64F, 1, 0, ksize=3) / (8.0 * pixel_scale) + v_y = cv2.Sobel(v, cv2.CV_64F, 0, 1, ksize=3) / (8.0 * pixel_scale) + divergence = u_x + v_y + + # Calculate curl (rotation) + u_y = cv2.Sobel(u, cv2.CV_64F, 0, 1, ksize=3) / (8.0 * pixel_scale) + v_x = cv2.Sobel(v, cv2.CV_64F, 1, 0, ksize=3) / (8.0 * pixel_scale) + curl = v_x - u_y + + # Calculate strain tensor components + strain_xx = u_x + strain_yy = v_y + strain_xy = 0.5 * (u_y + v_x) + + # Magnitude of strain tensor (Frobenius norm) + strain_magnitude = np.sqrt(strain_xx**2 + strain_yy**2 + 2 * strain_xy**2) + + # Store results + flow_fields.append(flow_field) + divergence_maps.append(divergence) + curl_maps.append(curl) + strain_maps.append(strain_magnitude) + + # Visualize flow field + flow_viz = np.zeros((frames[0].shape[0], frames[0].shape[1], 3), dtype=np.uint8) + flow_viz[..., 0] = next_frame # Use next frame as background + flow_viz[..., 1] = next_frame + flow_viz[..., 2] = next_frame + + # Draw flow vectors for visualization + step = 20 + for y in range(0, flow_field.shape[0], step): + for x in range(0, flow_field.shape[1], step): + dx, dy = flow_field[y, x] + if abs(dx) > 0.5 or abs(dy) > 0.5: # Only draw significant flow + cv2.arrowedLine( + flow_viz, + (x, y), + (int(x + dx), int(y + dy)), + (0, 255, 0), # Green + 1, + tipLength=0.3, + ) + + # Save visualizations + flow_viz_path = os.path.join(output_dir, f"flow_viz_{i:03d}.png") + divergence_path = os.path.join(output_dir, f"divergence_{i:03d}.npy") + curl_path = os.path.join(output_dir, f"curl_{i:03d}.npy") + strain_path = os.path.join(output_dir, f"strain_{i:03d}.npy") + + cv2.imwrite(flow_viz_path, flow_viz) + np.save(divergence_path, divergence) + np.save(curl_path, curl) + np.save(strain_path, strain_magnitude) + + # Calculate summary statistics + mean_divergence = np.mean([np.mean(div) for div in divergence_maps]) + max_divergence = np.max([np.max(div) for div in divergence_maps]) + mean_curl = np.mean([np.mean(np.abs(c)) for c in curl_maps]) + mean_strain = np.mean([np.mean(s) for s in strain_maps]) + + # Add summary to log + log += "\nAnalysis Results:\n" + log += f"Mean tissue divergence: {mean_divergence:.6f} (expansion/contraction rate)\n" + log += f"Maximum divergence: {max_divergence:.6f}\n" + log += f"Mean absolute curl: {mean_curl:.6f} (rotation rate)\n" + log += f"Mean strain magnitude: {mean_strain:.6f} (deformation intensity)\n\n" + + # Save summary data + summary_data = { + "mean_divergence": mean_divergence, + "max_divergence": max_divergence, + "mean_curl": mean_curl, + "mean_strain": mean_strain, + } + summary_path = os.path.join(output_dir, "deformation_summary.npy") + np.save(summary_path, summary_data) + + log += "Files Generated:\n" + log += f"- Flow visualization images: {output_dir}/flow_viz_*.png\n" + log += f"- Divergence maps: {output_dir}/divergence_*.npy\n" + log += f"- Curl maps: {output_dir}/curl_*.npy\n" + log += f"- Strain maps: {output_dir}/strain_*.npy\n" + log += f"- Summary statistics: {output_dir}/deformation_summary.npy\n" + + return log diff --git a/BioScientist/agent_system/engines/v1_executor_backup/tool/cancer_biology.py b/BioScientist/agent_system/engines/v1_executor_backup/tool/cancer_biology.py new file mode 100644 index 0000000000000000000000000000000000000000..c512bbea1572dae2f245c0a4c2e4e91d72a91100 --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/tool/cancer_biology.py @@ -0,0 +1,1292 @@ +def analyze_ddr_network_in_cancer(expression_data_path, mutation_data_path, output_dir="./results"): + """Analyze DNA Damage Response (DDR) network alterations and dependencies in cancer samples. + + This function reconstructs the DDR network from genomic data, identifies disruptions + in the network, and analyzes dependencies between DDR pathway components in cancer. + + Parameters + ---------- + expression_data_path : str + Path to gene expression data file (CSV format with genes as rows, samples as columns) + mutation_data_path : str + Path to mutation data file (CSV format with genes as rows, samples as columns, + values indicating mutation status) + output_dir : str, optional + Directory to save output files (default: "./results") + + Returns + ------- + str + Research log summarizing the DDR network analysis, findings about disruptions, + and potential therapeutic vulnerabilities + + """ + import os + from datetime import datetime + + import gseapy as gp + import networkx as nx + import pandas as pd + from scipy.stats import pearsonr + + # Create output directory if it doesn't exist + os.makedirs(output_dir, exist_ok=True) + + research_log = f"DDR Network Analysis Research Log - {datetime.now().strftime('%Y-%m-%d %H:%M')}\n" + research_log += "=" * 80 + "\n\n" + + # Step 1: Load and preprocess genomic data + research_log += "STEP 1: Loading and preprocessing genomic data\n" + + try: + expr_data = pd.read_csv(expression_data_path, index_col=0) + mut_data = pd.read_csv(mutation_data_path, index_col=0) + + research_log += f"- Loaded expression data: {expr_data.shape[0]} genes × {expr_data.shape[1]} samples\n" + research_log += f"- Loaded mutation data: {mut_data.shape[0]} genes × {mut_data.shape[1]} samples\n" + + # Define core DDR genes (based on literature) + ddr_genes = [ + # DNA damage sensors + "ATM", + "ATR", + "PRKDC", + "RAD50", + "MRE11", + "NBN", + # Signal transducers + "CHEK1", + "CHEK2", + "TP53", + "BRCA1", + "BRCA2", + "MDC1", + "H2AX", + # Effectors - Homologous Recombination + "RAD51", + "RAD52", + "PALB2", + "RAD54L", + # Effectors - Non-Homologous End Joining + "XRCC4", + "LIG4", + "XRCC5", + "XRCC6", + # Effectors - Base Excision Repair + "PARP1", + "APEX1", + "OGG1", + "XRCC1", + # Effectors - Nucleotide Excision Repair + "XPA", + "XPC", + "ERCC1", + "ERCC2", + "ERCC3", + "ERCC4", + "ERCC5", + # Effectors - Mismatch Repair + "MLH1", + "MSH2", + "MSH6", + "PMS2", + ] + + # Filter expression and mutation data for DDR genes + ddr_expr = expr_data.loc[expr_data.index.isin(ddr_genes)] + ddr_mut = mut_data.loc[mut_data.index.isin(ddr_genes)] + + research_log += f"- Filtered data for {len(ddr_genes)} DDR pathway genes\n" + research_log += f"- Found {ddr_expr.shape[0]} DDR genes in expression data\n" + research_log += f"- Found {ddr_mut.shape[0]} DDR genes in mutation data\n\n" + + except Exception as e: + research_log += f"Error in data loading: {str(e)}\n\n" + return research_log + + # Step 2: Network reconstruction + research_log += "STEP 2: Reconstructing DDR gene network\n" + + # Create correlation-based network + G = nx.Graph() + + # Add nodes (genes) + for gene in ddr_expr.index: + # Add node with mutation frequency information + mutation_freq = ddr_mut.loc[gene].mean() if gene in ddr_mut.index else 0 + + G.add_node(gene, mutation_freq=mutation_freq) + + # Add edges based on gene expression correlation + for i, gene1 in enumerate(ddr_expr.index): + for gene2 in ddr_expr.index[i + 1 :]: + corr, p_value = pearsonr(ddr_expr.loc[gene1], ddr_expr.loc[gene2]) + if abs(corr) > 0.4 and p_value < 0.05: # Significant correlation threshold + G.add_edge(gene1, gene2, weight=abs(corr), correlation=corr) + + research_log += f"- Constructed network with {G.number_of_nodes()} nodes and {G.number_of_edges()} edges\n" + + # Save network for visualization + network_file = os.path.join(output_dir, "ddr_network.graphml") + nx.write_graphml(G, network_file) + research_log += f"- Network saved to: {network_file}\n\n" + + # Step 3: Network analysis + research_log += "STEP 3: Analyzing DDR network properties\n" + + # Calculate network centrality measures + degree_centrality = nx.degree_centrality(G) + betweenness_centrality = nx.betweenness_centrality(G) + + # Identify hub genes (high degree centrality) + hub_genes = sorted(degree_centrality.items(), key=lambda x: x[1], reverse=True)[:5] + + research_log += "- Top 5 hub genes (highest connectivity):\n" + for gene, centrality in hub_genes: + research_log += f" * {gene}: {centrality:.4f}\n" + + # Identify bottleneck genes (high betweenness centrality) + bottleneck_genes = sorted(betweenness_centrality.items(), key=lambda x: x[1], reverse=True)[:5] + + research_log += "- Top 5 bottleneck genes (critical for information flow):\n" + for gene, centrality in bottleneck_genes: + research_log += f" * {gene}: {centrality:.4f}\n" + + # Identify frequently mutated DDR genes + mutation_freq = {node: G.nodes[node]["mutation_freq"] for node in G.nodes()} + frequently_mutated = sorted(mutation_freq.items(), key=lambda x: x[1], reverse=True)[:5] + + research_log += "- Top 5 frequently mutated DDR genes:\n" + for gene, freq in frequently_mutated: + research_log += f" * {gene}: {freq:.4f}\n\n" + + # Step 4: Community detection to identify DDR sub-pathways + research_log += "STEP 4: Identifying DDR sub-pathways through community detection\n" + + # Use Louvain method for community detection + try: + from community import best_partition + + partition = best_partition(G) + communities = {} + for node, community_id in partition.items(): + if community_id not in communities: + communities[community_id] = [] + communities[community_id].append(node) + + research_log += f"- Identified {len(communities)} DDR sub-pathways\n" + + for i, (_, genes) in enumerate(communities.items()): + research_log += f" * Sub-pathway {i + 1}: {', '.join(genes)}\n" + + except ImportError: + research_log += "- Community detection skipped (python-louvain package not installed)\n" + + # Step 5: Identify disrupted DDR pathways using GSEA + research_log += "\nSTEP 5: Pathway enrichment analysis of DDR genes\n" + + try: + # Create gene list for GSEA + gene_list = ddr_expr.mean(axis=1).sort_values(ascending=False) + + # Run GSEA with GO Biological Process + enrichr_results = gp.enrichr( + gene_list=gene_list.index.tolist(), + gene_sets=["GO_Biological_Process_2021"], + outdir=os.path.join(output_dir, "enrichr_output"), + cutoff=0.5, + ) + + # Filter for DNA repair related terms + ddr_terms = [ + term + for term in enrichr_results.results["Term"] + if any(x in term.lower() for x in ["dna repair", "damage", "recombination", "checkpoint"]) + ] + + if ddr_terms: + research_log += "- Enriched DDR-related pathways:\n" + for term in ddr_terms[:5]: # Top 5 DDR-related terms + research_log += f" * {term}\n" + else: + research_log += "- No significant DDR pathway enrichment found\n" + + # Save full enrichment results + enrichment_file = os.path.join(output_dir, "ddr_pathway_enrichment.csv") + enrichr_results.results.to_csv(enrichment_file) + research_log += f"- Full enrichment results saved to: {enrichment_file}\n\n" + + except Exception as e: + research_log += f"- Pathway enrichment analysis error: {str(e)}\n\n" + + # Step 6: Summary of findings + research_log += "SUMMARY OF FINDINGS\n" + research_log += "=" * 80 + "\n" + + # Key hub genes (potential therapeutic targets) + research_log += "1. Key DDR hub genes that may serve as potential therapeutic targets:\n" + for gene, _ in hub_genes[:3]: + research_log += f" - {gene}\n" + + # Frequently mutated genes + research_log += "\n2. Frequently mutated DDR genes in the samples:\n" + for gene, freq in frequently_mutated[:3]: + research_log += f" - {gene} (mutation frequency: {freq:.2%})\n" + + # Network structure insights + research_log += "\n3. DDR network structure insights:\n" + research_log += f" - Network density: {nx.density(G):.4f}\n" + + try: + avg_clustering = nx.average_clustering(G) + research_log += f" - Average clustering coefficient: {avg_clustering:.4f}\n" + except Exception: + pass + + research_log += "\n4. Potential DDR dependencies and synthetic lethality targets:\n" + # Identify potential synthetic lethality pairs (genes with high correlation and one is frequently mutated) + synthetic_lethality_candidates = [] + for gene1, gene2, data in G.edges(data=True): + if abs(data["correlation"]) > 0.6: # Strong correlation + mut_freq1 = G.nodes[gene1]["mutation_freq"] + mut_freq2 = G.nodes[gene2]["mutation_freq"] + if (mut_freq1 > 0.1 and mut_freq2 < 0.05) or (mut_freq2 > 0.1 and mut_freq1 < 0.05): + if mut_freq1 > mut_freq2: + synthetic_lethality_candidates.append((gene1, gene2, data["correlation"], mut_freq1)) + else: + synthetic_lethality_candidates.append((gene2, gene1, data["correlation"], mut_freq2)) + + if synthetic_lethality_candidates: + for mutated, target, _, _ in sorted(synthetic_lethality_candidates, key=lambda x: x[3], reverse=True)[:3]: + research_log += f" - {mutated} (frequently mutated) and {target} (potential dependency)\n" + else: + research_log += " - No strong synthetic lethality candidates identified\n" + + return research_log + + +def analyze_cell_senescence_and_apoptosis(fcs_file_path): + """Analyze flow cytometry data to quantify senescent and apoptotic cell populations. + + Parameters + ---------- + fcs_file_path : str + Path to the FCS file containing flow cytometry data with measurements for + senescence-associated β-galactosidase (SA-β-Gal) and Annexin V/7-AAD staining + + Returns + ------- + str + A research log summarizing the analysis steps and results, including percentages + of senescent and apoptotic cell populations + + """ + import os + + import numpy as np + from FlowCytometryTools import FCMeasurement + + log = "# Flow Cytometry Analysis of Cell Senescence and Apoptosis\n\n" + + try: + # Step 1: Load the FCS file + log += "## Step 1: Loading Flow Cytometry Data\n" + log += f"- Loading FCS file from: {fcs_file_path}\n" + + sample = FCMeasurement(ID="Sample", datafile=fcs_file_path) + log += f"- Successfully loaded data with {len(sample)} events\n" + log += f"- Available channels: {', '.join(sample.channel_names)}\n\n" + + # Step 2: Apply compensation if needed (assuming pre-compensated data) + log += "## Step 2: Data Preprocessing\n" + log += "- Checking for outliers and debris based on forward/side scatter\n" + + # Basic filtering to remove debris based on FSC and SSC + # Assuming FSC-A and SSC-A are the channel names (adjust if different) + fsc_channel = "FSC-A" if "FSC-A" in sample.channel_names else sample.channel_names[0] + ssc_channel = "SSC-A" if "SSC-A" in sample.channel_names else sample.channel_names[1] + + # Filter out debris (low FSC and SSC) + sample_filtered = sample.gate(f"{fsc_channel} > 10000 and {ssc_channel} > 5000") + + log += f"- Filtered out debris: {len(sample)} → {len(sample_filtered)} events ({len(sample_filtered) / len(sample) * 100:.1f}%)\n\n" + + # Step 3: Identify senescent cells (SA-β-Gal positive) + log += "## Step 3: Identifying Senescent Cells (SA-β-Gal+)\n" + + # Find SA-β-Gal channel (adjust based on actual channel name) + sa_bgal_channel = None + for channel in sample_filtered.channel_names: + if "GAL" in channel.upper() or "FITC" in channel.upper(): + sa_bgal_channel = channel + break + + if not sa_bgal_channel: + log += "- WARNING: Could not identify SA-β-Gal channel. Using first fluorescence channel as placeholder.\n" + # Use the first fluorescence channel as a fallback + for channel in sample_filtered.channel_names: + if any(x in channel.upper() for x in ["FL", "BL", "FITC", "PE", "APC"]): + sa_bgal_channel = channel + break + + log += f"- Using {sa_bgal_channel} as SA-β-Gal activity indicator\n" + + # Determine threshold for SA-β-Gal positivity (using a simple percentile approach) + # In practice, this would be based on controls or known thresholds + sa_bgal_threshold = np.percentile(sample_filtered.data[sa_bgal_channel], 80) + senescent_cells = sample_filtered.gate(f"{sa_bgal_channel} > {sa_bgal_threshold}") + + senescent_percentage = (len(senescent_cells) / len(sample_filtered)) * 100 + log += f"- Applied threshold at {sa_bgal_threshold:.1f} fluorescence intensity\n" + log += f"- Identified {len(senescent_cells)} senescent cells ({senescent_percentage:.2f}%)\n\n" + + # Step 4: Identify apoptotic cells (Annexin V+/7-AAD+) + log += "## Step 4: Identifying Apoptotic Cells (Annexin V/7-AAD)\n" + + # Find Annexin V and 7-AAD channels (adjust based on actual channel names) + annexin_channel = None + aad_channel = None + + for channel in sample_filtered.channel_names: + if "ANNEXIN" in channel.upper() or "PE" in channel.upper(): + annexin_channel = channel + if "7AAD" in channel.upper() or "AAD" in channel.upper() or "PerCP" in channel.upper(): + aad_channel = channel + + if not annexin_channel or not aad_channel: + log += "- WARNING: Could not identify Annexin V and/or 7-AAD channels. Using placeholder channels.\n" + # Use fallback channels + fluorescence_channels = [ + ch + for ch in sample_filtered.channel_names + if any(x in ch.upper() for x in ["FL", "BL", "FITC", "PE", "APC", "PerCP"]) + ] + if len(fluorescence_channels) >= 2: + if not annexin_channel: + annexin_channel = fluorescence_channels[0] + if not aad_channel: + aad_channel = fluorescence_channels[1] + + log += f"- Using {annexin_channel} as Annexin V indicator\n" + log += f"- Using {aad_channel} as 7-AAD indicator\n" + + # Determine thresholds for Annexin V and 7-AAD (using simple percentile approach) + annexin_threshold = np.percentile(sample_filtered.data[annexin_channel], 90) + aad_threshold = np.percentile(sample_filtered.data[aad_channel], 90) + + # Early apoptotic: Annexin V+ / 7-AAD- + early_apoptotic = sample_filtered.gate( + f"{annexin_channel} > {annexin_threshold} and {aad_channel} < {aad_threshold}" + ) + early_apoptotic_percentage = (len(early_apoptotic) / len(sample_filtered)) * 100 + + # Late apoptotic/necrotic: Annexin V+ / 7-AAD+ + late_apoptotic = sample_filtered.gate( + f"{annexin_channel} > {annexin_threshold} and {aad_channel} > {aad_threshold}" + ) + late_apoptotic_percentage = (len(late_apoptotic) / len(sample_filtered)) * 100 + + # Total apoptotic (early + late) + total_apoptotic_percentage = early_apoptotic_percentage + late_apoptotic_percentage + + log += f"- Early apoptotic cells (Annexin V+/7-AAD-): {early_apoptotic_percentage:.2f}%\n" + log += f"- Late apoptotic cells (Annexin V+/7-AAD+): {late_apoptotic_percentage:.2f}%\n" + log += f"- Total apoptotic cells: {total_apoptotic_percentage:.2f}%\n\n" + + # Step 5: Summary of results + log += "## Summary of Results\n" + log += f"- Total events analyzed: {len(sample_filtered)}\n" + log += f"- Senescent cells (SA-β-Gal+): {senescent_percentage:.2f}%\n" + log += f"- Early apoptotic cells (Annexin V+/7-AAD-): {early_apoptotic_percentage:.2f}%\n" + log += f"- Late apoptotic cells (Annexin V+/7-AAD+): {late_apoptotic_percentage:.2f}%\n" + log += f"- Total apoptotic cells: {total_apoptotic_percentage:.2f}%\n" + + # Save results to CSV file + results_file = os.path.splitext(os.path.basename(fcs_file_path))[0] + "_results.csv" + with open(results_file, "w") as f: + f.write("Cell Population,Percentage\n") + f.write(f"Senescent cells,{senescent_percentage:.2f}\n") + f.write(f"Early apoptotic cells,{early_apoptotic_percentage:.2f}\n") + f.write(f"Late apoptotic cells,{late_apoptotic_percentage:.2f}\n") + f.write(f"Total apoptotic cells,{total_apoptotic_percentage:.2f}\n") + + log += f"\nResults saved to: {results_file}\n" + + except Exception as e: + log += "\n## ERROR: An error occurred during analysis\n" + log += f"- Error message: {str(e)}\n" + + return log + + +def detect_and_annotate_somatic_mutations( + tumor_bam, normal_bam, reference_genome, output_prefix, snpeff_database="GRCh38.105" +): + """Detects and annotates somatic mutations in tumor samples compared to matched normal samples. + + This function uses GATK Mutect2 for variant calling, GATK FilterMutectCalls for filtering, + and SnpEff for functional annotation of somatic mutations. + + Parameters + ---------- + tumor_bam : str + Path to the tumor sample BAM file + normal_bam : str + Path to the matched normal sample BAM file + reference_genome : str + Path to the reference genome FASTA file + output_prefix : str + Prefix for output files + snpeff_database : str, optional + SnpEff database to use for annotation (default: "GRCh38.105") + + Returns + ------- + str + A research log summarizing the steps performed and results obtained + + """ + import datetime + import os + import subprocess + + # Initialize research log + log = "# Somatic Mutation Analysis Log\n" + log += f"Date: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n" + log += "## Input Files\n" + log += f"- Tumor BAM: {tumor_bam}\n" + log += f"- Normal BAM: {normal_bam}\n" + log += f"- Reference Genome: {reference_genome}\n\n" + + # Step 1: Run Mutect2 for somatic variant calling + log += "## Step 1: Somatic Variant Calling with Mutect2\n" + raw_vcf = f"{output_prefix}.unfiltered.vcf" + + mutect2_cmd = [ + "gatk", + "Mutect2", + "-R", + reference_genome, + "-I", + tumor_bam, + "-I", + normal_bam, + "-normal", + os.path.basename(normal_bam).split(".")[0], + "-O", + raw_vcf, + ] + + log += f"Running command: {' '.join(mutect2_cmd)}\n" + try: + subprocess.run(mutect2_cmd, check=True, capture_output=True) + log += f"Successfully generated raw VCF: {raw_vcf}\n\n" + except subprocess.CalledProcessError as e: + log += f"Error running Mutect2: {str(e)}\n" + return log + + # Step 2: Filter somatic calls + log += "## Step 2: Filtering Somatic Variants\n" + filtered_vcf = f"{output_prefix}.filtered.vcf" + + filter_cmd = [ + "gatk", + "FilterMutectCalls", + "-R", + reference_genome, + "-V", + raw_vcf, + "-O", + filtered_vcf, + ] + + log += f"Running command: {' '.join(filter_cmd)}\n" + try: + subprocess.run(filter_cmd, check=True, capture_output=True) + log += f"Successfully filtered variants: {filtered_vcf}\n\n" + except subprocess.CalledProcessError as e: + log += f"Error filtering variants: {str(e)}\n" + return log + + # Step 3: Annotate variants with SnpEff + log += "## Step 3: Functional Annotation with SnpEff\n" + annotated_vcf = f"{output_prefix}.annotated.vcf" + + snpeff_cmd = ["snpEff", "-v", snpeff_database, filtered_vcf, ">", annotated_vcf] + + log += f"Running command: {' '.join(snpeff_cmd)}\n" + try: + # Using shell=True because of the redirection + subprocess.run(" ".join(snpeff_cmd), shell=True, check=True) + log += f"Successfully annotated variants: {annotated_vcf}\n\n" + except subprocess.CalledProcessError as e: + log += f"Error annotating variants: {str(e)}\n" + return log + + # Step 4: Generate summary statistics + log += "## Step 4: Generating Summary Statistics\n" + summary_file = f"{output_prefix}_mutation_summary.txt" + + # Count total variants + count_cmd = f"grep -v '^#' {annotated_vcf} | wc -l" + try: + total_variants = subprocess.check_output(count_cmd, shell=True).decode().strip() + log += f"Total somatic variants detected: {total_variants}\n" + except subprocess.CalledProcessError as e: + log += f"Error counting variants: {str(e)}\n" + + # Count by variant type + log += "Variant types:\n" + for variant_type in ["SNP", "INS", "DEL"]: + count_type_cmd = f"grep -v '^#' {annotated_vcf} | grep '{variant_type}' | wc -l" + try: + type_count = subprocess.check_output(count_type_cmd, shell=True).decode().strip() + log += f"- {variant_type}: {type_count}\n" + except subprocess.CalledProcessError: + log += f"- {variant_type}: Error counting\n" + + # Count high impact variants + high_impact_cmd = f"grep -v '^#' {annotated_vcf} | grep 'HIGH' | wc -l" + try: + high_impact = subprocess.check_output(high_impact_cmd, shell=True).decode().strip() + log += f"High impact variants: {high_impact}\n\n" + except subprocess.CalledProcessError: + log += "High impact variants: Error counting\n\n" + + # Step 5: Save summary to file + with open(summary_file, "w") as f: + f.write("Somatic Mutation Analysis Summary\n") + f.write(f"Date: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n") + f.write(f"Total somatic variants: {total_variants}\n") + f.write(f"Output VCF file: {annotated_vcf}\n") + + log += "## Results\n" + log += "Analysis complete. Results saved to:\n" + log += f"- Annotated VCF: {annotated_vcf}\n" + log += f"- Summary file: {summary_file}\n\n" + + return log + + +def detect_and_characterize_structural_variations( + bam_file_path, + reference_genome_path, + output_dir, + cosmic_db_path=None, + clinvar_db_path=None, +): + """Detects and characterizes structural variations (SVs) in genomic sequencing data. + + This function uses LUMPY for SV detection followed by annotation with COSMIC and/or ClinVar + databases to identify and characterize various types of structural variations including + deletions, inversions, translocations, and duplications. + + Parameters + ---------- + bam_file_path : str + Path to the aligned sequencing data in BAM format + reference_genome_path : str + Path to the reference genome in FASTA format + output_dir : str + Directory where results will be saved + cosmic_db_path : str, optional + Path to the COSMIC database for cancer annotation + clinvar_db_path : str, optional + Path to the ClinVar database for clinical annotation + + Returns + ------- + str + A research log summarizing the steps performed and results obtained + + """ + import datetime + import os + import subprocess + + # Create output directory if it doesn't exist + os.makedirs(output_dir, exist_ok=True) + + # Initialize research log + log = [] + log.append("## Structural Variation Detection and Characterization") + log.append(f"Started at: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + log.append(f"Input BAM file: {bam_file_path}") + log.append(f"Reference genome: {reference_genome_path}") + log.append(f"Output directory: {output_dir}") + log.append("\n") + + # Step 1: Extract discordant read-pairs and split-reads for LUMPY + log.append("### Step 1: Extracting discordant read-pairs and split-reads") + + discordant_bam = os.path.join(output_dir, "discordant.bam") + split_bam = os.path.join(output_dir, "split.bam") + + try: + # Extract discordant read pairs + subprocess.run( + [ + "samtools", + "view", + "-b", + "-F", + "1294", + "-o", + discordant_bam, + bam_file_path, + ], + check=True, + ) + + # Extract split reads + subprocess.run( + [ + "samtools", + "view", + "-h", + bam_file_path, + "|", + "extractSplitReads_BwaMem", + "-i", + "stdin", + "|", + "samtools", + "view", + "-Sb", + "-", + ">", + split_bam, + ], + shell=True, + check=True, + ) + + log.append("Successfully extracted discordant read-pairs and split-reads") + except subprocess.CalledProcessError as e: + log.append(f"Error during read extraction: {e}") + return "\n".join(log) + + # Step 2: Run LUMPY for SV detection + log.append("\n### Step 2: Running LUMPY for structural variation detection") + + vcf_output = os.path.join(output_dir, "structural_variants.vcf") + + try: + subprocess.run( + [ + "lumpyexpress", + "-B", + bam_file_path, + "-S", + split_bam, + "-D", + discordant_bam, + "-o", + vcf_output, + ], + check=True, + ) + + log.append("LUMPY analysis completed successfully") + log.append(f"Raw SV calls saved to: {vcf_output}") + except subprocess.CalledProcessError as e: + log.append(f"Error during LUMPY execution: {e}") + return "\n".join(log) + + # Step 3: Filter SVs by quality and size + log.append("\n### Step 3: Filtering structural variants by quality and size") + + filtered_vcf = os.path.join(output_dir, "filtered_structural_variants.vcf") + + try: + # Filter SVs with quality score >= 100 and size >= 100bp + subprocess.run( + [ + "bcftools", + "filter", + "-i", + "QUAL>=100 && SVLEN>=100", + "-o", + filtered_vcf, + vcf_output, + ], + check=True, + ) + + # Count SVs by type + sv_counts = {} + sv_types = ["DEL", "DUP", "INV", "BND", "INS"] + + for sv_type in sv_types: + result = subprocess.run( + ["grep", "-c", f"SVTYPE={sv_type}", filtered_vcf], + check=False, + capture_output=True, + text=True, + ) + + count = 0 + if result.returncode == 0: + count = int(result.stdout.strip()) + sv_counts[sv_type] = count + + log.append("SV filtering completed") + log.append(f"Filtered SVs saved to: {filtered_vcf}") + log.append("\nSV counts by type:") + for sv_type, count in sv_counts.items(): + log.append(f"- {sv_type}: {count}") + except subprocess.CalledProcessError as e: + log.append(f"Error during SV filtering: {e}") + return "\n".join(log) + + # Step 4: Annotate SVs with COSMIC and ClinVar (if databases provided) + log.append("\n### Step 4: Annotating structural variants") + + annotated_vcf = os.path.join(output_dir, "annotated_structural_variants.vcf") + + try: + annotation_cmd = ["annotate_sv.py", "-i", filtered_vcf, "-o", annotated_vcf] + + if cosmic_db_path: + annotation_cmd.extend(["-c", cosmic_db_path]) + log.append(f"Using COSMIC database: {cosmic_db_path}") + + if clinvar_db_path: + annotation_cmd.extend(["-v", clinvar_db_path]) + log.append(f"Using ClinVar database: {clinvar_db_path}") + + # This is a placeholder for the annotation command + # In a real implementation, you would use a tool like AnnotSV, VEP, or a custom script + log.append("Note: Annotation step is simulated in this implementation") + log.append("In a real scenario, tools like AnnotSV or VEP would be used") + + # Instead of running the command, we'll create a simple annotated file + with open(filtered_vcf) as infile, open(annotated_vcf, "w") as outfile: + for line in infile: + if line.startswith("#"): + outfile.write(line) + else: + outfile.write(line) + # In a real implementation, annotation would be added here + + log.append("SV annotation completed") + log.append(f"Annotated SVs saved to: {annotated_vcf}") + except Exception as e: + log.append(f"Error during SV annotation: {e}") + return "\n".join(log) + + # Step 5: Generate summary report + log.append("\n### Step 5: Generating summary report") + + summary_file = os.path.join(output_dir, "sv_summary_report.tsv") + + try: + # Convert VCF to a tabular format for easier analysis + subprocess.run( + [ + "bcftools", + "query", + "-f", + "%CHROM\t%POS\t%INFO/SVTYPE\t%INFO/SVLEN\t%QUAL\n", + "-o", + summary_file, + annotated_vcf, + ], + check=True, + ) + + log.append(f"Summary report generated: {summary_file}") + except subprocess.CalledProcessError as e: + log.append(f"Error generating summary report: {e}") + return "\n".join(log) + + # Final summary + log.append("\n## Summary") + log.append(f"Total SVs detected: {sum(sv_counts.values())}") + log.append("SV types breakdown:") + for sv_type, count in sv_counts.items(): + log.append(f"- {sv_type}: {count} ({count / sum(sv_counts.values()) * 100:.1f}%)") + + log.append("\nFiles generated:") + log.append(f"- Raw SV calls: {vcf_output}") + log.append(f"- Filtered SVs: {filtered_vcf}") + log.append(f"- Annotated SVs: {annotated_vcf}") + log.append(f"- Summary report: {summary_file}") + + log.append(f"\nAnalysis completed at: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + + return "\n".join(log) + + +def perform_gene_expression_nmf_analysis( + expression_data_path, + n_components=10, + normalize=True, + output_dir="nmf_results", + random_state=42, +): + """Performs Non-negative Matrix Factorization (NMF) on gene expression data to extract + metagenes and their associated sample weights for tumor subtype identification. + + Parameters + ---------- + expression_data_path : str + Path to a CSV or TSV file containing gene expression data with genes as rows and samples as columns. + Values should be non-negative (e.g., normalized counts or expression values). + n_components : int, default=10 + Number of metagenes (components) to extract. + normalize : bool, default=True + Whether to normalize the expression data before applying NMF. + output_dir : str, default="nmf_results" + Directory to save the output files. + random_state : int, default=42 + Random seed for reproducibility. + + Returns + ------- + str + Research log summarizing the analysis steps and results. + + """ + import os + from datetime import datetime + + import numpy as np + import pandas as pd + from sklearn.decomposition import NMF + + # Start research log + log = [] + log.append(f"NMF GENE EXPRESSION ANALYSIS - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + log.append(f"Parameters: n_components={n_components}, normalize={normalize}") + + # Load expression data from file + try: + if expression_data_path.endswith(".csv"): + expression_data = pd.read_csv(expression_data_path, index_col=0) + elif expression_data_path.endswith((".tsv", ".txt")): + expression_data = pd.read_csv(expression_data_path, sep="\t", index_col=0) + else: + log.append("ERROR: Unsupported file format. Please provide a CSV or TSV file.") + return "\n".join(log) + + log.append(f"Successfully loaded expression data from {expression_data_path}") + except Exception as e: + log.append(f"ERROR: Failed to load expression data from {expression_data_path}. Error: {str(e)}") + return "\n".join(log) + + # Extract gene and sample information + genes = expression_data.index.tolist() + samples = expression_data.columns.tolist() + X = expression_data.values + log.append(f"Input data: {len(genes)} genes × {len(samples)} samples") + + # Check for non-negative values + if np.any(X < 0): + log.append("WARNING: Negative values found in expression data. Converting to absolute values.") + X = np.abs(X) + + # Normalize data if requested + if normalize: + log.append("Normalizing expression data...") + X = X / np.sum(X, axis=0, keepdims=True) * 1000 # TPM-like normalization + + # Create output directory if it doesn't exist + if not os.path.exists(output_dir): + os.makedirs(output_dir) + log.append(f"Created output directory: {output_dir}") + + # Apply NMF + log.append(f"Applying NMF to extract {n_components} metagenes...") + model = NMF( + n_components=n_components, + init="random", + random_state=random_state, + max_iter=1000, + ) + + try: + # W: metagenes (genes × components) + # H: sample weights (components × samples) + W = model.fit_transform(X) + H = model.components_ + + log.append(f"NMF completed successfully in {model.n_iter_} iterations") + log.append(f"Explained variance: {model.reconstruction_err_:.4f}") + + # Save metagenes (W matrix) + metagenes_file = os.path.join(output_dir, "metagenes.csv") + pd.DataFrame(W, index=genes, columns=[f"Metagene_{i + 1}" for i in range(n_components)]).to_csv(metagenes_file) + log.append(f"Saved metagenes to {metagenes_file}") + + # Save sample weights (H matrix) + weights_file = os.path.join(output_dir, "sample_weights.csv") + pd.DataFrame(H, index=[f"Metagene_{i + 1}" for i in range(n_components)], columns=samples).to_csv(weights_file) + log.append(f"Saved sample weights to {weights_file}") + + top_genes_file = os.path.join(output_dir, "top_genes_per_metagene.csv") + top_genes = {} + for i in range(n_components): + # Get indices of top 20 genes for this metagene + top_indices = np.argsort(W[:, i])[::-1][:20] + top_genes[f"Metagene_{i + 1}"] = [genes[idx] for idx in top_indices] + + pd.DataFrame(top_genes).to_csv(top_genes_file) + log.append(f"Saved top genes per metagene to {top_genes_file}") + + except Exception as e: + log.append(f"ERROR: NMF failed with error: {str(e)}") + return "\n".join(log) + + log.append("\nSUMMARY:") + log.append(f"- Successfully extracted {n_components} metagenes from {X.shape[0]} genes across {X.shape[1]} samples") + log.append(f"- Results saved to: {output_dir}") + log.append("- Files generated: metagenes.csv, sample_weights.csv, top_genes_per_metagene.csv") + log.append("\nNEXT STEPS:") + log.append("- Analyze the metagenes to identify biological pathways") + log.append("- Cluster samples based on metagene weights to identify potential tumor subtypes") + log.append("- Correlate subtypes with clinical outcomes if available") + + return "\n".join(log) + + +def analyze_copy_number_purity_ploidy_and_focal_events( + tumor_bam, + reference_genome, + normal_bam=None, + output_dir="cn_analysis_results", + targets_bed=None, + antitargets_bed=None, + gene_bed=None, + focal_genes=None, + log2_amp_threshold=1.0, + log2_del_threshold=-1.0, +): + """CNVkit-based copy number workflow: CNV segmentation, purity/ploidy & HRD approximation, focal events. + + This function orchestrates a CNVkit-based copy number analysis workflow for tumor samples. + It performs CNV segmentation, derives approximate purity & ploidy metrics, calculates a simplified + HRD-style summary, and detects focal amplifications/deletions in key oncogenes / tumor suppressors. + + Parameters + ---------- + tumor_bam : str + Path to tumor BAM (indexed) + reference_genome : str + Path to reference FASTA (with index files present) + normal_bam : str, optional + Matched normal BAM (recommended for allele-specific & purity inference) + output_dir : str, default "cn_analysis_results" + Directory for all outputs + targets_bed : str, optional + BED of target regions (e.g. exome / panel) for CNVkit / gCNV + antitargets_bed : str, optional + BED of antitarget regions for CNVkit (if panel / exome) + gene_bed : str, optional + BED file with gene coordinates (chrom, start, end, gene). Used for focal event annotation + focal_genes : list[str], optional + List of genes to highlight for focal amplification/deletion; defaults to ["MYC","ERBB2","CDKN2A"] + log2_amp_threshold : float, default 1.0 + Log2 ratio threshold to call focal amplification (~ >2x copy relative to baseline) + log2_del_threshold : float, default -1.0 + Log2 ratio threshold to call focal deep deletion (~ homozygous loss) + + Returns + ------- + str + Research log summarizing steps, tool executions, and findings. + """ + import datetime + import math + import os + import shutil + import statistics + import subprocess + + import pandas as pd + + log = [] + ts = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + log.append(f"CNVKIT COPY NUMBER ANALYSIS WORKFLOW - {ts}") + log.append("=" * 80) + log.append(f"Tumor BAM: {tumor_bam}") + if normal_bam: + log.append(f"Normal BAM: {normal_bam}") + log.append(f"Reference genome: {reference_genome}") + log.append(f"Output directory: {output_dir}\n") + + os.makedirs(output_dir, exist_ok=True) + sample_name = os.path.splitext(os.path.basename(tumor_bam))[0] + + if focal_genes is None: + focal_genes = ["MYC", "ERBB2", "CDKN2A"] + # Single informative log line (was duplicated previously) + log.append(f"Focal genes for analysis: {', '.join(focal_genes)}") + + # ---------------------------------------------------------------------------------- + # STEP 1: CNV SEGMENTATION with CNVkit + # ---------------------------------------------------------------------------------- + log.append("STEP 1: CNV Segmentation with CNVkit") + cnvkit_path = shutil.which("cnvkit.py") or shutil.which("cnvkit") + use_conda_env = False + + # Check if CNVkit is available in biomni_e1 environment + if not cnvkit_path: + try: + result = subprocess.run( + ["conda", "run", "-n", "biomni_e1", "which", "cnvkit.py"], capture_output=True, text=True, check=True + ) + if result.returncode == 0 and result.stdout.strip(): + cnvkit_path = "cnvkit.py" # Will use via conda run + use_conda_env = True + log.append("- CNVkit found in biomni_e1 environment") + except (subprocess.CalledProcessError, FileNotFoundError): + pass + + # Fallback to bio_env_py310 + if not cnvkit_path: + try: + result = subprocess.run( + ["conda", "run", "-n", "bio_env_py310", "which", "cnvkit.py"], + capture_output=True, + text=True, + check=True, + ) + if result.returncode == 0 and result.stdout.strip(): + cnvkit_path = "cnvkit.py" # Will use via conda run + use_conda_env = True + log.append("- CNVkit found in bio_env_py310 environment") + except (subprocess.CalledProcessError, FileNotFoundError): + pass + + if not cnvkit_path: + log.append("- CNVkit not detected in current PATH, biomni_e1, or bio_env_py310 environment") + log.append("- Install manually (e.g., pip install cnvkit==0.9.11) or run setup.sh") + log.append("- Workflow will exit without CNVkit") + return "\n".join(log) + + cnvkit_cns = None + log.append(f"- CNVkit detected at: {cnvkit_path}") + + # Determine environment prefix for CNVkit commands + env_prefix = ["conda", "run", "-n", "biomni_e1"] if use_conda_env else [] + + # CNVkit batch command + batch_cmd = env_prefix + [cnvkit_path, "batch", tumor_bam, "-d", output_dir, "-f", reference_genome] + if normal_bam: + batch_cmd.extend(["-n", normal_bam]) + if targets_bed: + batch_cmd.extend(["-t", targets_bed]) + if antitargets_bed: + batch_cmd.extend(["-a", antitargets_bed]) + batch_cmd.extend(["--scatter", "--diagram"]) + + log.append(f"- Running CNVkit batch command: {' '.join(batch_cmd)}") + try: + subprocess.run(batch_cmd, check=True, capture_output=True) + log.append("- CNVkit batch completed successfully") + except subprocess.CalledProcessError as e: + log.append(f"- WARNING: CNVkit batch failed: {e}. Proceeding with limited downstream analyses") + + # Check for CNVkit output and run call command + cnvkit_cns = os.path.join(output_dir, f"{sample_name}.cns") + if os.path.exists(cnvkit_cns): + call_cmd = env_prefix + [ + cnvkit_path, + "call", + cnvkit_cns, + "-o", + os.path.join(output_dir, f"{sample_name}.call.cns"), + "-m", + "clonal", + ] + log.append(f"- Calling absolute CN: {' '.join(call_cmd)}") + try: + subprocess.run(call_cmd, check=True, capture_output=True) + cnvkit_cns = os.path.join(output_dir, f"{sample_name}.call.cns") + log.append("- CNVkit call succeeded") + except subprocess.CalledProcessError as e: + log.append(f"- WARNING: CNVkit call failed: {e}") + else: + log.append("- WARNING: Expected CNVkit .cns file not found") + + # ---------------------------------------------------------------------------------- + # STEP 2: Purity & ploidy approximation (based on CNVkit outputs) + # ---------------------------------------------------------------------------------- + log.append("\nSTEP 2: Purity & Ploidy Approximation") + purity_est = None + ploidy_est = None + seg_source = None + seg_df = None + try: + if cnvkit_cns and os.path.exists(cnvkit_cns): + seg_source = cnvkit_cns + seg_df = pd.read_csv(cnvkit_cns, sep="\t") + log.append(f"- Using CNVkit segments from: {seg_source}") + + # Harmonize columns + cols = [c.lower() for c in seg_df.columns] + seg_df.columns = cols + + # Obtain log2 column + log2_col = None + for candidate in ["log2", "log2ratio", "cnlr", "logr"]: + if candidate in cols: + log2_col = candidate + break + for candidate in ["end", "chromend", "stop"]: + if candidate in cols: + end_col = candidate + break + for candidate in ["start", "chromstart", "begin"]: + if candidate in cols: + start_col = candidate + break + + if log2_col and start_col and end_col: + seg_df["seg_length"] = seg_df[end_col] - seg_df[start_col] + # Weighted average absolute copy ratio -> approximate ploidy baseline + # Convert log2 ratio to absolute copy ratio (assuming diploid baseline = 2) + seg_df["abs_cn"] = 2 * (2 ** seg_df[log2_col]) + weighted_mean_cn = (seg_df["abs_cn"] * seg_df["seg_length"]).sum() / seg_df["seg_length"].sum() + ploidy_est = weighted_mean_cn + # Approximate purity: higher variance in log2 ratios suggests purity; naive formula + mad_log2 = statistics.median([abs(x) for x in seg_df[log2_col] if not math.isnan(x)]) + purity_est = min(1.0, max(0.2, 1 - mad_log2 / 1.5)) # heuristic clamp + log.append(f"- Estimated ploidy (weighted mean CN): {ploidy_est:.2f}") + log.append(f"- Estimated purity (heuristic from segmentation dispersion): {purity_est:.2f}") + else: + log.append("- WARNING: Could not identify necessary columns for purity/ploidy estimation") + else: + log.append("- No CNVkit segmentation available for purity/ploidy estimation") + except Exception as e: + log.append(f"- ERROR: Purity/ploidy estimation failed: {e}") + + # ---------------------------------------------------------------------------------- + # STEP 3: HRD-like summary (simplified proxy) + # ---------------------------------------------------------------------------------- + log.append("\nSTEP 3: HRD Approximation (Simplified)") + hrd_metrics = {} + try: + if seg_df is not None and "seg_length" in seg_df: + # Count large-scale transitions (LST proxy: adjacent segments >10 Mb with log2 change >0.2) + seg_df_sorted = seg_df.sort_values( + by=[c for c in seg_df.columns if c.startswith("chrom") or c == "chrom" or c == "chromosome"][0] + if any(c.startswith("chrom") for c in seg_df.columns) + else seg_df.columns[0] + ) + lst_count = 0 + prev = None + for _, row in seg_df_sorted.iterrows(): + if prev is not None: + if row["seg_length"] >= 10_000_000 and prev["seg_length"] >= 10_000_000: + if log2_col and abs(row[log2_col] - prev[log2_col]) > 0.2: + lst_count += 1 + prev = row + hrd_metrics["LST_like"] = lst_count + # HRD-LOH proxy: segments with log2 < -0.3 and length >15 Mb + if log2_col: + hrd_loh = ((seg_df[log2_col] < -0.3) & (seg_df["seg_length"] > 15_000_000)).sum() + hrd_metrics["HRD_LOH_like"] = int(hrd_loh) + # Telomeric AI proxy omitted (needs allele-specific data) + hrd_score = sum(hrd_metrics.values()) + hrd_metrics["Simplified_HRD_score"] = hrd_score + log.append(f"- LST-like events: {hrd_metrics['LST_like']}") + log.append(f"- HRD-LOH-like events: {hrd_metrics['HRD_LOH_like']}") + log.append(f"- Simplified composite HRD score: {hrd_metrics['Simplified_HRD_score']}") + log.append("- NOTE: For clinical/research use, apply scarHRD or HRDetect with allele-specific data") + else: + log.append("- Segmentation not available; HRD approximation skipped") + except Exception as e: + log.append(f"- ERROR: HRD approximation failed: {e}") + + # ---------------------------------------------------------------------------------- + # STEP 4: Focal amplifications/deletions in key genes + # ---------------------------------------------------------------------------------- + log.append("\nSTEP 4: Focal Amplifications / Deletions in Key Genes") + focal_events = [] + try: + if seg_df is not None and gene_bed and os.path.exists(gene_bed): + genes_df = pd.read_csv(gene_bed, sep="\t", header=None, names=["chrom", "start", "end", "gene"]) + + # Normalize chromosome naming + def norm_chr(x): + return x.replace("chr", "") if isinstance(x, str) else x + + seg_df["chr_norm"] = ( + seg_df[[c for c in seg_df.columns if c in ["chrom", "chromosome", "chr"]][0]].astype(str).map(norm_chr) + ) + genes_df["chr_norm"] = genes_df["chrom"].astype(str).map(norm_chr) + for gene in focal_genes: + region = genes_df[genes_df["gene"] == gene] + if region.empty: + continue + r = region.iloc[0] + # Ensure we work on a copy to avoid SettingWithCopyWarning + overlaps = seg_df[ + (seg_df["chr_norm"] == r["chr_norm"]) + & (seg_df[start_col] <= r["end"]) + & (seg_df[end_col] >= r["start"]) + ].copy() + if not overlaps.empty and log2_col: + # Choose segment with largest overlap length + r_end = r["end"] + r_start = r["start"] + overlaps["ov_len"] = overlaps.apply( + lambda row, r_end=r_end, r_start=r_start: min(row[end_col], r_end) + - max(row[start_col], r_start), + axis=1, + ) + best = overlaps.sort_values("ov_len", ascending=False).iloc[0] + l2 = best[log2_col] + status = None + if l2 >= log2_amp_threshold: + status = "AMPLIFICATION" + elif l2 <= log2_del_threshold: + status = "DELETION" + if status: + focal_events.append((gene, status, l2)) + if focal_events: + for gene, status, l2 in focal_events: + log.append(f"- {gene}: {status} (log2={l2:.2f})") + else: + log.append("- No focal events meeting thresholds in target genes") + else: + if not gene_bed: + log.append("- Skipped: gene_bed not provided") + else: + log.append("- Skipped: gene_bed file not found or segmentation unavailable") + except Exception as e: + log.append(f"- ERROR: Focal event detection failed: {e}") + + # ---------------------------------------------------------------------------------- + # STEP 5: Summaries & file outputs + # ---------------------------------------------------------------------------------- + log.append("\nSTEP 5: Summary & Output Files") + summary = { + "purity_estimate": purity_est, + "ploidy_estimate": ploidy_est, + } + summary.update(hrd_metrics) + summary_file = os.path.join(output_dir, f"{sample_name}_cn_summary.tsv") + try: + pd.DataFrame([summary]).to_csv(summary_file, sep="\t", index=False) + log.append(f"- Saved summary metrics: {summary_file}") + except Exception as e: + log.append(f"- WARNING: Could not save summary file: {e}") + + if focal_events: + focal_file = os.path.join(output_dir, f"{sample_name}_focal_events.tsv") + try: + pd.DataFrame(focal_events, columns=["Gene", "Event", "Log2"]).to_csv(focal_file, sep="\t", index=False) + log.append(f"- Saved focal events: {focal_file}") + except Exception as e: + log.append(f"- WARNING: Could not save focal events file: {e}") + + log.append("\nNEXT STEPS:") + log.append("- For improved purity/ploidy estimation, consider ABSOLUTE, FACETS, PureCN, or Sequenza") + log.append("- For validated HRD scoring, use scarHRD or HRDetect with allele-specific data") + log.append("- Integrate CN events with expression data to assess driver gene activation/dosage") + log.append("- Visualize CNV profiles using CNVkit's built-in plotting functions") + + return "\n".join(log) diff --git a/BioScientist/agent_system/engines/v1_executor_backup/tool/cell_biology.py b/BioScientist/agent_system/engines/v1_executor_backup/tool/cell_biology.py new file mode 100644 index 0000000000000000000000000000000000000000..88aca18ef6b267887e71e07606a8d78bdf7f928d --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/tool/cell_biology.py @@ -0,0 +1,742 @@ +def quantify_cell_cycle_phases_from_microscopy(image_paths, output_dir="./results"): + """Quantify the percentage of cells in each cell cycle phase using Calcofluor white stained microscopy images. + + This function processes microscopy images where cell walls/septa are stained with Calcofluor white, + segments individual cells, extracts features, and classifies each cell into G1, S, or G2/M phase. + + Parameters + ---------- + image_paths : list of str + List of file paths to microscopy images of cells stained with Calcofluor white + output_dir : str, optional + Directory to save results (default: './results') + + Returns + ------- + str + Research log summarizing the analysis process and results + + """ + import os + + import numpy as np + import pandas as pd + from skimage import filters, io, measure, morphology + + # Create output directory if it doesn't exist + os.makedirs(output_dir, exist_ok=True) + + # Initialize log + log = "# Cell Cycle Phase Quantification Analysis Log\n\n" + log += f"Processing {len(image_paths)} microscopy images with Calcofluor white staining.\n\n" + + # Initialize data structures to store results + all_cells = [] + cell_features = [] + + # Step 1: Process images and segment cells + log += "## Image Processing and Cell Segmentation\n\n" + + for i, img_path in enumerate(image_paths): + log += f"Processing image {i + 1}/{len(image_paths)}: {os.path.basename(img_path)}\n" + + # Load image + img = io.imread(img_path) + + # Convert to grayscale if needed + if len(img.shape) > 2: + img = np.mean(img, axis=2).astype(np.uint8) + + # Preprocessing + filtered_img = filters.gaussian(img, sigma=1.0) + + # Thresholding to identify cell walls + threshold = filters.threshold_otsu(filtered_img) + binary = filtered_img > threshold + + # Clean up binary image + binary = morphology.remove_small_objects(binary, min_size=30) + binary = morphology.binary_closing(binary) + + # Segment cells + labeled_cells = measure.label(binary) + cell_props = measure.regionprops(labeled_cells, intensity_image=img) + + log += f" - Found {len(cell_props)} cells\n" + all_cells.extend(cell_props) + + # Extract features for each cell + for cell in cell_props: + # Basic morphological features + area = cell.area + perimeter = cell.perimeter + eccentricity = cell.eccentricity + mean_intensity = cell.mean_intensity + + # Cell shape features + circularity = (4 * np.pi * area) / (perimeter**2) if perimeter > 0 else 0 + + # Check for septation (indicative of S or G2/M phase) + # Calcofluor white stains the septum more intensely + has_septum = False + if area > 50: # Only check larger cells + intensity_profile = cell.intensity_image[cell.intensity_image > 0] + if intensity_profile.size > 0: + intensity_std = np.std(intensity_profile) + intensity_max = np.max(intensity_profile) + # High standard deviation and max intensity suggests septum presence + has_septum = intensity_std > 0.2 * np.mean(intensity_profile) and intensity_max > 1.5 * np.mean( + intensity_profile + ) + + # Store features + cell_features.append( + [ + area, + perimeter, + eccentricity, + mean_intensity, + circularity, + has_septum, + i, # include image index + ] + ) + + log += f"\nTotal cells detected across all images: {len(all_cells)}\n\n" + + # Step 2: Feature engineering and classification + log += "## Cell Cycle Phase Classification\n\n" + + # Convert to numpy array for easier processing + X = np.array(cell_features) + + # Simple rule-based classification + # This is a simplified approach - in practice, you would train a classifier on labeled data + # G1: Smaller cells, no septum + # S: Medium-sized cells, early septum formation + # G2/M: Larger cells, clear septum, often elongated + + # Define classification rules based on features + phases = [] + for i in range(X.shape[0]): + area = X[i, 0] + perimeter = X[i, 1] + eccentricity = X[i, 2] + has_septum = X[i, 5] + + if has_septum and area > np.median(X[:, 0]) * 1.2: + # Larger cells with septum are likely in G2/M + phases.append("G2/M") + elif has_septum or (area > np.median(X[:, 0]) and eccentricity > 0.5): + # Cells with septum or larger elongated cells are likely in S + phases.append("S") + else: + # Smaller cells without septum are likely in G1 + phases.append("G1") + + # Calculate percentages + unique_phases, counts = np.unique(phases, return_counts=True) + percentages = (counts / len(phases)) * 100 + + # Create results table + results_df = pd.DataFrame({"Phase": unique_phases, "Count": counts, "Percentage": percentages}) + + # Save results + results_path = os.path.join(output_dir, "cell_cycle_phases.csv") + results_df.to_csv(results_path, index=False) + + # Log results + log += "### Cell Cycle Phase Distribution\n\n" + for phase, count, percentage in zip(unique_phases, counts, percentages, strict=False): + log += f"- {phase}: {count} cells ({percentage:.2f}%)\n" + + log += f"\nResults saved to: {results_path}\n" + log += "\nNote: This analysis uses simplified morphological features for classification. " + log += ( + "For more accurate results, a supervised machine learning approach with labeled training data is recommended.\n" + ) + + return log + + +def quantify_and_cluster_cell_motility(image_sequence_path, output_dir="./results", num_clusters=3): + """Quantify cell motility features from time-lapse microscopy images and cluster cells based on motility patterns. + + Parameters + ---------- + image_sequence_path : str + Path to directory containing time-lapse microscopy images in sequential order + output_dir : str + Directory to save output files (default: "./results") + num_clusters : int + Number of motility pattern clusters to identify (default: 3) + + Returns + ------- + str + Research log summarizing the analysis process and results + + """ + import os + + import cv2 + import numpy as np + import pandas as pd + from sklearn.cluster import KMeans + + # Create output directory if it doesn't exist + os.makedirs(output_dir, exist_ok=True) + + # Step 1: Load image sequence + image_files = sorted( + [f for f in os.listdir(image_sequence_path) if f.endswith((".tif", ".tiff", ".png", ".jpg", ".jpeg"))] + ) + + if len(image_files) < 2: + return "Error: Insufficient images found. At least 2 time points are required." + + # Step 2: Initialize cell tracking + first_image = cv2.imread(os.path.join(image_sequence_path, image_files[0]), cv2.IMREAD_GRAYSCALE) + + # Simple cell detection using thresholding and contour detection + _, binary = cv2.threshold(first_image, 127, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) + contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + + # Extract cell centroids from first frame + cells = [] + for i, contour in enumerate(contours): + if cv2.contourArea(contour) > 50: # Filter small noise + M = cv2.moments(contour) + if M["m00"] != 0: + cx = int(M["m10"] / M["m00"]) + cy = int(M["m01"] / M["m00"]) + cells.append({"id": i, "positions": [(cx, cy)], "frame_indices": [0]}) + + # Step 3: Track cells across frames + for frame_idx, img_file in enumerate(image_files[1:], 1): + img = cv2.imread(os.path.join(image_sequence_path, img_file), cv2.IMREAD_GRAYSCALE) + _, binary = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) + contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + + # Get current frame centroids + current_centroids = [] + for contour in contours: + if cv2.contourArea(contour) > 50: + M = cv2.moments(contour) + if M["m00"] != 0: + cx = int(M["m10"] / M["m00"]) + cy = int(M["m01"] / M["m00"]) + current_centroids.append((cx, cy)) + + # Match cells with nearest centroids in current frame + for cell in cells: + if len(cell["positions"]) == frame_idx: # Only track cells found in previous frame + prev_pos = cell["positions"][-1] + + if current_centroids: + # Calculate distances to all current centroids + distances = [ + np.sqrt((prev_pos[0] - c[0]) ** 2 + (prev_pos[1] - c[1]) ** 2) for c in current_centroids + ] + min_idx = np.argmin(distances) + + # Only match if distance is below threshold (prevents incorrect matching) + if distances[min_idx] < 50: # Threshold distance in pixels + cell["positions"].append(current_centroids[min_idx]) + cell["frame_indices"].append(frame_idx) + current_centroids.pop(min_idx) # Remove matched centroid + + # Step 4: Calculate motility features for each cell + cell_features = [] + + for cell in cells: + # Only analyze cells tracked for at least 3 frames + if len(cell["positions"]) < 3: + continue + + # Calculate displacements between consecutive positions + displacements = [] + for i in range(1, len(cell["positions"])): + prev_pos = cell["positions"][i - 1] + curr_pos = cell["positions"][i] + displacement = np.sqrt((curr_pos[0] - prev_pos[0]) ** 2 + (curr_pos[1] - prev_pos[1]) ** 2) + displacements.append(displacement) + + # Calculate speed (pixels per frame) + avg_speed = np.mean(displacements) + + # Calculate directionality (displacement from start to end / total path length) + start_pos = cell["positions"][0] + end_pos = cell["positions"][-1] + net_displacement = np.sqrt((end_pos[0] - start_pos[0]) ** 2 + (end_pos[1] - start_pos[1]) ** 2) + total_path_length = np.sum(displacements) + directionality = net_displacement / total_path_length if total_path_length > 0 else 0 + + # Calculate mean squared displacement + msd = np.mean( + [ + np.sqrt((cell["positions"][i][0] - start_pos[0]) ** 2 + (cell["positions"][i][1] - start_pos[1]) ** 2) + for i in range(1, len(cell["positions"])) + ] + ) + + # Calculate track duration + duration = cell["frame_indices"][-1] - cell["frame_indices"][0] + + cell_features.append( + { + "cell_id": cell["id"], + "avg_speed": avg_speed, + "directionality": directionality, + "msd": msd, + "track_duration": duration, + "track_length": len(cell["positions"]), + } + ) + + # Step 5: Cluster cells based on motility features + if len(cell_features) < num_clusters: + return f"Error: Not enough cells ({len(cell_features)}) to form {num_clusters} clusters. Try reducing num_clusters." + + # Create feature matrix for clustering + feature_df = pd.DataFrame(cell_features) + feature_matrix = feature_df[["avg_speed", "directionality", "msd"]].values + + # Normalize features + feature_matrix = (feature_matrix - np.mean(feature_matrix, axis=0)) / np.std(feature_matrix, axis=0) + + # Perform k-means clustering + kmeans = KMeans(n_clusters=num_clusters, random_state=42) + clusters = kmeans.fit_predict(feature_matrix) + + # Add cluster assignments to dataframe + feature_df["cluster"] = clusters + + # Save results to CSV + results_file = os.path.join(output_dir, "cell_motility_features.csv") + feature_df.to_csv(results_file, index=False) + + # Calculate cluster statistics + cluster_stats = feature_df.groupby("cluster").agg( + { + "avg_speed": ["mean", "std"], + "directionality": ["mean", "std"], + "msd": ["mean", "std"], + "cell_id": "count", + } + ) + + # Save cluster statistics + stats_file = os.path.join(output_dir, "cluster_statistics.csv") + cluster_stats.to_csv(stats_file) + + # Create research log + log = f""" +Cell Motility Quantification and Clustering Analysis +=================================================== + +Analysis Steps: +1. Loaded {len(image_files)} time-lapse microscopy images +2. Detected and tracked {len(cells)} initial cells across time frames +3. Calculated motility features for {len(cell_features)} cells with sufficient tracking data +4. Performed k-means clustering to identify {num_clusters} distinct motility patterns + +Results Summary: +- Detected {len(cells)} cells in the first frame +- Successfully tracked {len(cell_features)} cells across multiple frames +- Clustered cells into {num_clusters} motility pattern groups + +Cluster Statistics: +{cluster_stats.to_string()} + +Output Files: +- Cell motility features saved to: {results_file} +- Cluster statistics saved to: {stats_file} + +Analysis Notes: +- Features used for clustering: average speed, directionality, and mean squared displacement +- Cells were required to be tracked for at least 3 frames to be included in the analysis +""" + + # Save research log + log_file = os.path.join(output_dir, "research_log.txt") + with open(log_file, "w") as f: + f.write(log) + + return log + + +def perform_facs_cell_sorting( + cell_suspension_data, + fluorescence_parameter, + threshold_min=None, + threshold_max=None, + output_file="sorted_cells.csv", +): + """Performs Fluorescence-Activated Cell Sorting (FACS) to enrich cell populations based on fluorescence characteristics. + + Parameters + ---------- + cell_suspension_data : str + Path to the FCS file containing flow cytometry data + fluorescence_parameter : str + The fluorescence parameter to use for sorting (e.g., 'GFP', 'FITC', 'PE') + threshold_min : float, optional + Minimum threshold for the fluorescence parameter. Cells below this value will be excluded + threshold_max : float, optional + Maximum threshold for the fluorescence parameter. Cells above this value will be excluded + output_file : str, optional + Filename to save the sorted cell population data + + Returns + ------- + str + Research log detailing the FACS cell sorting process + + """ + import os + + import pandas as pd + + # Initialize research log + log = "# FACS-based Cell Sorting and Enrichment Research Log\n\n" + + try: + # Load cell suspension data + log += "## Loading Cell Suspension Data\n" + if isinstance(cell_suspension_data, str): + # If data is provided as a file path + if cell_suspension_data.lower().endswith(".fcs"): + try: + import flowkit as fk + + fcs_data = fk.Sample(cell_suspension_data) + cell_df = pd.DataFrame(fcs_data.get_dataframe()) + log += f"Successfully loaded FCS file containing {len(cell_df)} cells\n" + except ImportError: + # Fallback if flowkit is not available + log += "FlowKit not available, attempting to load as CSV\n" + cell_df = pd.read_csv(cell_suspension_data) + else: + # Assume CSV or other tabular format + cell_df = pd.read_csv(cell_suspension_data) + log += f"Loaded data file containing {len(cell_df)} cells\n" + else: + # Assume data is already a DataFrame + cell_df = cell_suspension_data.copy() + log += f"Using provided DataFrame containing {len(cell_df)} cells\n" + + # Validate fluorescence parameter exists in data + if fluorescence_parameter not in cell_df.columns: + raise ValueError(f"Fluorescence parameter '{fluorescence_parameter}' not found in data") + + # Apply gating strategy based on fluorescence thresholds + log += f"\n## Applying Gating Strategy for {fluorescence_parameter}\n" + original_count = len(cell_df) + + # Filter cells based on min/max thresholds + if threshold_min is not None: + cell_df = cell_df[cell_df[fluorescence_parameter] >= threshold_min] + log += f"Applied minimum threshold of {threshold_min}: {len(cell_df)} cells remaining\n" + + if threshold_max is not None: + cell_df = cell_df[cell_df[fluorescence_parameter] <= threshold_max] + log += f"Applied maximum threshold of {threshold_max}: {len(cell_df)} cells remaining\n" + + # Calculate enrichment statistics + enrichment_percent = (len(cell_df) / original_count) * 100 if original_count > 0 else 0 + log += "\n## Enrichment Results\n" + log += f"Original population: {original_count} cells\n" + log += f"Sorted population: {len(cell_df)} cells\n" + log += f"Enrichment percentage: {enrichment_percent:.2f}%\n" + + # Calculate statistics of sorted population + mean_fluorescence = cell_df[fluorescence_parameter].mean() + median_fluorescence = cell_df[fluorescence_parameter].median() + + log += f"Mean {fluorescence_parameter}: {mean_fluorescence:.2f}\n" + log += f"Median {fluorescence_parameter}: {median_fluorescence:.2f}\n" + + # Save sorted population to file + cell_df.to_csv(output_file, index=False) + log += "\n## Output\n" + log += f"Sorted cell population saved to: {os.path.abspath(output_file)}\n" + + return log + + except Exception as e: + log += "\n## Error\n" + log += f"An error occurred during cell sorting: {str(e)}\n" + return log + + +def analyze_flow_cytometry_immunophenotyping( + fcs_file_path, gating_strategy, compensation_matrix=None, output_dir="./results" +): + """Analyze flow cytometry data to identify and quantify specific cell populations based on surface markers. + + Parameters + ---------- + fcs_file_path : str + Path to the FCS file containing flow cytometry data + gating_strategy : dict + Dictionary defining the gating strategy. Each key is a population name, and each value is a list of tuples + (marker, operator, threshold). For example: {'HSCs': [('Lin', '<', 100), ('Sca1', '>', 1000), ...]} + compensation_matrix : numpy.ndarray, optional + Spillover/compensation matrix to correct for fluorescence overlap + output_dir : str, optional + Directory to save the results + + Returns + ------- + str + Research log summarizing the analysis steps and results + + """ + import os + + import pandas as pd + from FlowCytometryTools import FCMeasurement + + # Create output directory if it doesn't exist + if not os.path.exists(output_dir): + os.makedirs(output_dir) + + # Load the FCS file + sample = FCMeasurement(ID="Sample", datafile=fcs_file_path) + + # Apply compensation if provided + if compensation_matrix is not None: + sample = sample.compensate(compensation_matrix) + + # Initialize log + log = "Flow Cytometry Analysis Log\n" + log += "==========================\n" + log += f"File analyzed: {os.path.basename(fcs_file_path)}\n" + log += f"Total events: {len(sample)}\n\n" + + # Create a dictionary to store population counts + populations = {} + + # Apply gating strategy to identify cell populations + for pop_name, gates in gating_strategy.items(): + # Start with all events + current_population = sample.copy() + + log += f"Identifying {pop_name} population:\n" + + # Apply each gate sequentially + for marker, operator, threshold in gates: + initial_count = len(current_population) + + if operator == ">": + current_population = current_population.gate(f"{marker} > {threshold}") + elif operator == "<": + current_population = current_population.gate(f"{marker} < {threshold}") + elif operator == "between": + # For 'between', threshold should be a tuple (lower, upper) + lower, upper = threshold + current_population = current_population.gate(f"{marker} > {lower} and {marker} < {upper}") + + final_count = len(current_population) + log += f" Gate {marker} {operator} {threshold}: {initial_count} → {final_count} events\n" + + # Store the final population + populations[pop_name] = current_population + + # Calculate percentage of original sample + percentage = (len(current_population) / len(sample)) * 100 + log += f" Final {pop_name} count: {len(current_population)} events ({percentage:.2f}% of total)\n\n" + + # Create a summary dataframe + summary_data = {"Population": [], "Count": [], "Percentage": []} + + for pop_name, population in populations.items(): + summary_data["Population"].append(pop_name) + summary_data["Count"].append(len(population)) + summary_data["Percentage"].append((len(population) / len(sample)) * 100) + + summary_df = pd.DataFrame(summary_data) + + # Save the summary to a CSV file + summary_file = os.path.join(output_dir, "population_summary.csv") + summary_df.to_csv(summary_file, index=False) + + log += "Summary of identified populations:\n" + for _, row in summary_df.iterrows(): + log += f" {row['Population']}: {row['Count']} events ({row['Percentage']:.2f}%)\n" + + log += f"\nDetailed results saved to: {summary_file}\n" + + return log + + +def analyze_mitochondrial_morphology_and_potential(morphology_image_path, potential_image_path, output_dir="./output"): + """Quantifies metrics of mitochondrial morphology and membrane potential from fluorescence microscopy images. + + Parameters + ---------- + morphology_image_path : str + Path to the fluorescence microscopy image showing mitochondrial morphology (e.g., MTS-GFP) + potential_image_path : str + Path to the fluorescence microscopy image showing mitochondrial membrane potential (e.g., TMRE staining) + output_dir : str, optional + Directory to save output files, default is "./output" + + Returns + ------- + str + A research log summarizing the analysis steps and results + + """ + import datetime + import os + + import cv2 + import numpy as np + from scipy import ndimage + from skimage import filters, io, morphology, util + + # Create output directory if it doesn't exist + os.makedirs(output_dir, exist_ok=True) + + log = [] + log.append(f"Mitochondrial Analysis - {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + log.append("=" * 50) + + # Load images + try: + morph_img = io.imread(morphology_image_path) + pot_img = io.imread(potential_image_path) + + log.append(f"Successfully loaded morphology image: {morphology_image_path}") + log.append(f"Successfully loaded potential image: {potential_image_path}") + log.append(f"Morphology image shape: {morph_img.shape}") + log.append(f"Potential image shape: {pot_img.shape}") + except Exception as e: + return f"Error loading images: {str(e)}" + + # Convert to grayscale if needed + if len(morph_img.shape) > 2: + morph_img = cv2.cvtColor(morph_img, cv2.COLOR_RGB2GRAY) + log.append("Converted morphology image to grayscale") + if len(pot_img.shape) > 2: + pot_img = cv2.cvtColor(pot_img, cv2.COLOR_RGB2GRAY) + log.append("Converted potential image to grayscale") + + # Normalize images + morph_img = util.img_as_float(morph_img) + pot_img = util.img_as_float(pot_img) + + log.append("\nMorphology Analysis:") + log.append("-" * 30) + + # Denoise morphology image + morph_img_denoised = filters.gaussian(morph_img, sigma=1.0) + + # Threshold to segment mitochondria + threshold_value = filters.threshold_otsu(morph_img_denoised) + binary_img = morph_img_denoised > threshold_value + + # Remove small objects + binary_img = morphology.remove_small_objects(binary_img, min_size=20) + + # Save binary image + binary_path = os.path.join(output_dir, "binary_mitochondria.png") + io.imsave(binary_path, util.img_as_ubyte(binary_img)) + log.append(f"Binary segmentation saved to: {binary_path}") + + # Skeletonize for network analysis + skeleton = morphology.skeletonize(binary_img) + skeleton_path = os.path.join(output_dir, "skeleton.png") + io.imsave(skeleton_path, util.img_as_ubyte(skeleton)) + log.append(f"Skeleton image saved to: {skeleton_path}") + + # Calculate morphology metrics + # 1. Count branches and junctions + # Label the skeleton + labeled_skeleton, num_branches = ndimage.label(skeleton) + + # Find branch points (pixels with more than 2 neighbors) + kernel = np.ones((3, 3), dtype=np.uint8) + kernel[1, 1] = 0 # Don't count the center pixel + neighbor_count = ndimage.convolve(skeleton.astype(np.uint8), kernel) + junction_points = (neighbor_count > 2) & skeleton + num_junctions = np.sum(junction_points) + + # 2. Calculate fragmentation metrics + labeled_objects, num_objects = ndimage.label(binary_img) + object_sizes = ndimage.sum(binary_img, labeled_objects, range(1, num_objects + 1)) + mean_size = np.mean(object_sizes) if len(object_sizes) > 0 else 0 + + # Calculate network connectivity (ratio of junctions to branches) + connectivity = num_junctions / num_branches if num_branches > 0 else 0 + + # Calculate fragmentation index (inverse of mean object size, normalized) + fragmentation = 1 / (mean_size / np.max(object_sizes)) if mean_size > 0 and np.max(object_sizes) > 0 else 0 + + log.append(f"Number of mitochondrial fragments: {num_objects}") + log.append(f"Number of branches: {num_branches}") + log.append(f"Number of junction points: {num_junctions}") + log.append(f"Network connectivity index: {connectivity:.4f}") + log.append(f"Fragmentation index: {fragmentation:.4f}") + log.append(f"Mean fragment size: {mean_size:.2f} pixels") + + # Membrane potential analysis + log.append("\nMembrane Potential Analysis:") + log.append("-" * 30) + + # Create mask from morphology image to analyze only mitochondrial regions + mito_mask = binary_img + + # Calculate potential metrics within mitochondrial regions + pot_intensity_raw = np.mean(pot_img) + pot_intensity_in_mito = np.mean(pot_img[mito_mask]) if np.sum(mito_mask) > 0 else 0 + + # Calculate potential heterogeneity (standard deviation of intensity) + pot_heterogeneity = np.std(pot_img[mito_mask]) if np.sum(mito_mask) > 0 else 0 + + # Calculate potential distribution + if np.sum(mito_mask) > 0: + percentiles = [10, 25, 50, 75, 90] + pot_percentiles = np.percentile(pot_img[mito_mask], percentiles) + percentile_str = ", ".join([f"{p}th: {v:.4f}" for p, v in zip(percentiles, pot_percentiles, strict=False)]) + else: + percentile_str = "No mitochondrial regions detected" + + log.append(f"Overall mean TMRE intensity: {pot_intensity_raw:.4f}") + log.append(f"Mean TMRE intensity in mitochondria: {pot_intensity_in_mito:.4f}") + log.append(f"TMRE intensity heterogeneity (std): {pot_heterogeneity:.4f}") + log.append(f"TMRE intensity percentiles: {percentile_str}") + + # Create a combined results image + # Overlay potential intensity on morphology + overlay = np.zeros((morph_img.shape[0], morph_img.shape[1], 3), dtype=np.float32) + overlay[..., 0] = morph_img # Red channel - morphology + overlay[..., 2] = pot_img # Blue channel - potential + overlay = np.clip(overlay, 0, 1) + + overlay_path = os.path.join(output_dir, "morphology_potential_overlay.png") + io.imsave(overlay_path, util.img_as_ubyte(overlay)) + log.append(f"Overlay image saved to: {overlay_path}") + + # Save quantitative results to CSV + import csv + + results_path = os.path.join(output_dir, "mitochondrial_analysis_results.csv") + with open(results_path, "w", newline="") as csvfile: + writer = csv.writer(csvfile) + writer.writerow(["Metric", "Value"]) + writer.writerow(["Number of fragments", num_objects]) + writer.writerow(["Number of branches", num_branches]) + writer.writerow(["Number of junctions", num_junctions]) + writer.writerow(["Network connectivity", connectivity]) + writer.writerow(["Fragmentation index", fragmentation]) + writer.writerow(["Mean fragment size", mean_size]) + writer.writerow(["Mean TMRE intensity", pot_intensity_in_mito]) + writer.writerow(["TMRE heterogeneity", pot_heterogeneity]) + + log.append(f"Quantitative results saved to: {results_path}") + log.append("\nAnalysis complete.") + + return "\n".join(log) diff --git a/BioScientist/agent_system/engines/v1_executor_backup/tool/database.py b/BioScientist/agent_system/engines/v1_executor_backup/tool/database.py new file mode 100644 index 0000000000000000000000000000000000000000..fa55b1c6dc6341af3ba237894d3159a7b5b37730 --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/tool/database.py @@ -0,0 +1,4974 @@ +import json +import os +import pickle +import time +from typing import Any + +import requests +from Bio.Blast import NCBIWWW, NCBIXML +from Bio.Seq import Seq +from langchain_core.messages import HumanMessage, SystemMessage + +from biomni.llm import get_llm +from biomni.utils import parse_hpo_obo + + +# Function to map HPO terms to names +def get_hpo_names(hpo_terms: list[str], data_lake_path: str) -> list[str]: + """Retrieve the names of given HPO terms. + + Args: + hpo_terms (List[str]): A list of HPO terms (e.g., ['HP:0001250']). + + Returns: + List[str]: A list of corresponding HPO term names. + + """ + hp_dict = parse_hpo_obo(data_lake_path + "/hp.obo") + + hpo_names = [] + for term in hpo_terms: + name = hp_dict.get(term, f"Unknown term: {term}") + hpo_names.append(name) + return hpo_names + + +def _query_llm_for_api(prompt, schema, system_template): + """Helper function to query LLMs for generating API calls based on natural language prompts. + + Supports multiple model providers including Claude, Gemini, GPT, and others via the unified get_llm interface. + + Parameters + ---------- + prompt (str): Natural language query to process + schema (dict): API schema to include in the system prompt + system_template (str): Template string for the system prompt (should have {schema} placeholder) + + Returns + ------- + dict: Dictionary with 'success', 'data' (if successful), 'error' (if failed), and optional 'raw_response' + + """ + # Use global config for model and api_key + try: + from biomni.config import default_config + + model = default_config.llm + api_key = default_config.api_key + except ImportError: + model = "claude-3-5-haiku-20241022" + api_key = None + + try: + # Format the system prompt with schema if provided + if schema is not None: + schema_json = json.dumps(schema, indent=2) + system_prompt = system_template.format(schema=schema_json) + else: + system_prompt = system_template + + # Get LLM instance using the unified interface with config + try: + from biomni.config import default_config + + llm = get_llm(model=model, temperature=0.0, api_key=api_key, config=default_config) + except ImportError: + llm = get_llm(model=model, temperature=0.0, api_key=api_key or "EMPTY") + + # Compose messages + messages = [ + SystemMessage(content=system_prompt), + HumanMessage(content=prompt), + ] + + # Query the LLM + response = llm.invoke(messages) + llm_text = response.content.strip() + + # Find JSON boundaries (in case LLM adds explanations) + json_start = llm_text.find("{") + json_end = llm_text.rfind("}") + 1 + + if json_start >= 0 and json_end > json_start: + json_text = llm_text[json_start:json_end] + result = json.loads(json_text) + else: + # If no JSON found, try the whole response + result = json.loads(llm_text) + + return {"success": True, "data": result, "raw_response": llm_text} + + except (json.JSONDecodeError, KeyError, IndexError) as e: + return { + "success": False, + "error": f"Failed to parse LLM response: {str(e)}", + "raw_response": llm_text if "llm_text" in locals() else "No content found", + } + except Exception as e: + return {"success": False, "error": f"Error querying LLM: {str(e)}"} + + +def _query_rest_api(endpoint, method="GET", params=None, headers=None, json_data=None, description=None): + """General helper function to query REST APIs with consistent error handling. + + Parameters + ---------- + endpoint (str): Full URL endpoint to query + method (str): HTTP method ("GET" or "POST") + params (dict, optional): Query parameters to include in the URL + headers (dict, optional): HTTP headers for the request + json_data (dict, optional): JSON data for POST requests + description (str, optional): Description of this query for error messages + + Returns + ------- + dict: Dictionary containing the result or error information + + """ + # Set default headers if not provided + if headers is None: + headers = {"Accept": "application/json"} + + # Set default description if not provided + if description is None: + description = f"{method} request to {endpoint}" + + url_error = None + + try: + # Make the API request + if method.upper() == "GET": + response = requests.get(endpoint, params=params, headers=headers) + elif method.upper() == "POST": + response = requests.post(endpoint, params=params, headers=headers, json=json_data) + else: + return {"error": f"Unsupported HTTP method: {method}"} + + url_error = str(response.text) + response.raise_for_status() + + # Try to parse JSON response + try: + result = response.json() + except ValueError: + # Return raw text if not JSON + result = {"raw_text": response.text} + + return { + "success": True, + "query_info": { + "endpoint": endpoint, + "method": method, + "description": description, + }, + "result": result, + } + + except Exception as e: + error_msg = str(e) + response_text = "" + + # Try to get more detailed error info from response + if hasattr(e, "response") and e.response: + try: + error_json = e.response.json() + if "messages" in error_json: + error_msg = "; ".join(error_json["messages"]) + elif "message" in error_json: + error_msg = error_json["message"] + elif "error" in error_json: + error_msg = error_json["error"] + elif "detail" in error_json: + error_msg = error_json["detail"] + except Exception: + response_text = e.response.text + + return { + "success": False, + "error": f"API error: {error_msg}", + "query_info": { + "endpoint": endpoint, + "method": method, + "description": description, + }, + "response_url_error": url_error, + "response_text": response_text, + } + + +def _query_ncbi_database( + database: str, + search_term: str, + result_formatter=None, + max_results: int = 3, +) -> dict[str, Any]: + """Core function to query NCBI databases using Claude for query interpretation and NCBI eutils. + + Parameters + ---------- + database (str): NCBI database to query (e.g., "clinvar", "gds", "geoprofiles") + result_formatter (callable): Function to format results from the database + api_key (str): Anthropic API key. If None, will look for ANTHROPIC_API_KEY environment variable + model (str): Anthropic model to use + max_results (int): Maximum number of results to return + verbose (bool): Whether to return verbose results + + Returns + ------- + dict: Dictionary containing both the structured query and the results + + """ + # Query NCBI API using the structured search term + esearch_url = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi" + esearch_params = { + "db": database, + "term": search_term, + "retmode": "json", + "retmax": 100, + "usehistory": "y", # Use history server to store results + } + + # Get IDs of matching entries + search_response = _query_rest_api( + endpoint=esearch_url, + method="GET", + params=esearch_params, + description="NCBI ESearch API query", + ) + + if not search_response["success"]: + return search_response + + search_data = search_response["result"] + + # If we have results, fetch the details + if "esearchresult" in search_data and int(search_data["esearchresult"]["count"]) > 0: + # Extract WebEnv and query_key from the search results + webenv = search_data["esearchresult"].get("webenv", "") + query_key = search_data["esearchresult"].get("querykey", "") + + # Use WebEnv and query_key if available + if webenv and query_key: + # Get details using eSummary + esummary_url = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi" + esummary_params = { + "db": database, + "query_key": query_key, + "WebEnv": webenv, + "retmode": "json", + "retmax": max_results, + } + + details_response = _query_rest_api( + endpoint=esummary_url, + method="GET", + params=esummary_params, + description="NCBI ESummary API query", + ) + + if not details_response["success"]: + return details_response + + results = details_response["result"] + + else: + # Fall back to direct ID fetch + id_list = search_data["esearchresult"]["idlist"][:max_results] + + # Get details for each ID + esummary_url = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi" + esummary_params = { + "db": database, + "id": ",".join(id_list), + "retmode": "json", + } + + details_response = _query_rest_api( + endpoint=esummary_url, + method="GET", + params=esummary_params, + description="NCBI ESummary API query", + ) + + if not details_response["success"]: + return details_response + + results = details_response["result"] + + # Format results using the provided formatter + formatted_results = result_formatter(results) if result_formatter else results + + # Return the combined information + return { + "database": database, + "query_interpretation": search_term, + "total_results": int(search_data["esearchresult"]["count"]), + "formatted_results": formatted_results, + } + else: + return { + "database": database, + "query_interpretation": search_term, + "total_results": 0, + "formatted_results": [], + } + + +def _format_query_results(result, options=None): + """A general-purpose formatter for query function results to reduce output size. + + Parameters + ---------- + result (dict): The original API response dictionary + options (dict, optional): Formatting options including: + - max_items (int): Maximum number of items to include in lists (default: 5) + - max_depth (int): Maximum depth to traverse in nested dictionaries (default: 2) + - include_keys (list): Only include these top-level keys (overrides exclude_keys) + - exclude_keys (list): Exclude these keys from the output + - summarize_lists (bool): Whether to summarize long lists (default: True) + - truncate_strings (int): Maximum length for string values (default: 100) + + Returns + ------- + dict: A condensed version of the input results + + """ + + def _format_value(value, depth, options): + """Recursively format a value based on its type and formatting options. + + Parameters + ---------- + value: The value to format + depth (int): Current recursion depth + options (dict): Formatting options + + Returns + ------- + Formatted value + + """ + # Base case: reached max depth + if depth >= options["max_depth"] and (isinstance(value, dict | list)): + if isinstance(value, dict): + return { + "_summary": f"Nested dictionary with {len(value)} keys", + "_keys": list(value.keys())[: options["max_items"]], + } + else: # list + return _summarize_list(value, options) + + # Process based on type + if isinstance(value, dict): + return _format_dict(value, depth, options) + elif isinstance(value, list): + return _format_list(value, depth, options) + elif isinstance(value, str) and len(value) > options["truncate_strings"]: + return value[: options["truncate_strings"]] + "... (truncated)" + else: + return value + + def _format_dict(d, depth, options): + """Format a dictionary according to options.""" + result = {} + + # Filter keys based on include/exclude options + keys_to_process = d.keys() + if depth == 0 and options["include_keys"]: # Only apply at top level + keys_to_process = [k for k in keys_to_process if k in options["include_keys"]] + elif depth == 0 and options["exclude_keys"]: # Only apply at top level + keys_to_process = [k for k in keys_to_process if k not in options["exclude_keys"]] + + # Process each key + for key in keys_to_process: + result[key] = _format_value(d[key], depth + 1, options) + + return result + + def _format_list(lst, depth, options): + """Format a list according to options.""" + if options["summarize_lists"] and len(lst) > options["max_items"]: + return _summarize_list(lst, options) + + result = [] + for i, item in enumerate(lst): + if i >= options["max_items"]: + remaining = len(lst) - options["max_items"] + result.append(f"... {remaining} more items (omitted)") + break + result.append(_format_value(item, depth + 1, options)) + + return result + + def _summarize_list(lst, options): + """Create a summary for a list.""" + if not lst: + return [] + + # Sample a few items + sample = lst[: min(3, len(lst))] + sample_formatted = [_format_value(item, options["max_depth"], options) for item in sample] + + # For homogeneous lists, provide type info + if len(lst) > 0: + item_type = type(lst[0]).__name__ + homogeneous = all(isinstance(item, type(lst[0])) for item in lst) + type_info = f"all {item_type}" if homogeneous else "mixed types" + else: + type_info = "empty" + + return { + "_summary": f"List with {len(lst)} items ({type_info})", + "_sample": sample_formatted, + } + + if options is None: + options = {} + + # Default options + default_options = { + "max_items": 5, + "max_depth": 20, + "include_keys": None, + "exclude_keys": ["raw_response", "debug_info", "request_details"], + "summarize_lists": True, + "truncate_strings": 100, + } + + # Merge provided options with defaults + for key, value in default_options.items(): + if key not in options: + options[key] = value + + # Filter and format the result + formatted = _format_value(result, 0, options) + return formatted + + +def query_uniprot( + prompt=None, + endpoint=None, + max_results=5, +): + """Query the UniProt REST API using either natural language or a direct endpoint. + + Parameters + ---------- + prompt (str, required): Natural language query about proteins (e.g., "Find information about human insulin") + endpoint (str, optional): Full or partial UniProt API endpoint URL to query directly + (e.g., "https://rest.uniprot.org/uniprotkb/P01308") + max_results (int): Maximum number of results to return + + Returns + ------- + dict: Dictionary containing the query information and the UniProt API results + + Examples + -------- + - Natural language: query_uniprot(prompt="Find information about human insulin protein") + - Direct endpoint: query_uniprot(endpoint="https://rest.uniprot.org/uniprotkb/P01308") + + """ + # Base URL for UniProt API + base_url = "https://rest.uniprot.org" + + # Ensure we have either a prompt or an endpoint + if prompt is None and endpoint is None: + return {"error": "Either a prompt or an endpoint must be provided"} + + # If using prompt, parse with Claude + if prompt: + # Load UniProt schema + schema_path = os.path.join(os.path.dirname(__file__), "schema_db", "uniprot.pkl") + with open(schema_path, "rb") as f: + uniprot_schema = pickle.load(f) + + # Create system prompt template + system_template = """ + You are a protein biology expert specialized in using the UniProt REST API. + + Based on the user's natural language request, determine the appropriate UniProt REST API endpoint and parameters. + + UNIPROT REST API SCHEMA: + {schema} + + Your response should be a JSON object with the following fields: + 1. "full_url": The complete URL to query (including base URL, dataset, endpoint type, and parameters) + 2. "description": A brief description of what the query is doing + + SPECIAL NOTES: + - Base URL is "https://rest.uniprot.org" + - Search in reviewed (Swiss-Prot) entries first before using non-reviewed (TrEMBL) entries + - Assume organism is human unless otherwise specified. Human taxonomy ID is 9606 + - Use gene_exact: for exact gene name searches + - Use specific query fields like accession:, gene:, organism_id: in search queries + - Use quotes for terms with spaces: organism_name:"Homo sapiens" + + Return ONLY the JSON object with no additional text. + """ + + # Query Claude to generate the API call + llm_result = _query_llm_for_api( + prompt=prompt, + schema=uniprot_schema, + system_template=system_template, + ) + + if not llm_result["success"]: + return llm_result + + # Get the full URL from Claude's response + query_info = llm_result["data"] + endpoint = query_info.get("full_url", "") + description = query_info.get("description", "") + + if not endpoint: + return { + "error": "Failed to generate a valid endpoint from the prompt", + "llm_response": llm_result.get("raw_response", "No response"), + } + else: + # Use provided endpoint directly + if endpoint.startswith("/"): + endpoint = f"{base_url}{endpoint}" + elif not endpoint.startswith("http"): + endpoint = f"{base_url}/{endpoint.lstrip('/')}" + description = "Direct query to provided endpoint" + + # Use the common REST API helper function + api_result = _query_rest_api(endpoint=endpoint, method="GET", description=description) + + return api_result + + +def query_alphafold( + uniprot_id, + endpoint="prediction", + residue_range=None, + download=False, + output_dir=None, + file_format="pdb", + model_version="v4", + model_number=1, +): + """Query the AlphaFold Database API for protein structure predictions. + + Parameters + ---------- + uniprot_id (str): UniProt accession ID (e.g., "P12345") + endpoint (str, optional): Specific AlphaFold API endpoint to query: + "prediction", "summary", or "annotations" + residue_range (str, optional): Specific residue range in format "start-end" (e.g., "1-100") + download (bool): Whether to download structure files + output_dir (str, optional): Directory to save downloaded files (default: current directory) + file_format (str): Format of the structure file to download - "pdb" or "cif" + model_version (str): AlphaFold model version - "v4" (latest) or "v3", "v2", "v1" + model_number (int): Model number (1-5, with 1 being the highest confidence model) + + Returns + ------- + dict: Dictionary containing both the query information and the AlphaFold results + + Examples + -------- + - Basic query: query_alphafold(uniprot_id="P53_HUMAN") + - Download structure: query_alphafold(uniprot_id="P53_HUMAN", download=True, output_dir="./structures") + - Get annotations: query_alphafold(uniprot_id="P53_HUMAN", endpoint="annotations") + + """ + # Base URL for AlphaFold API + base_url = "https://alphafold.ebi.ac.uk/api" + + # Ensure we have a UniProt ID + if not uniprot_id: + return {"error": "UniProt ID is required"} + + # Validate endpoint + valid_endpoints = ["prediction", "summary", "annotations"] + if endpoint not in valid_endpoints: + return {"error": f"Invalid endpoint. Must be one of: {', '.join(valid_endpoints)}"} + + # Construct the API URL based on endpoint + if endpoint == "prediction": + url = f"{base_url}/prediction/{uniprot_id}" + elif endpoint == "summary": + url = f"{base_url}/uniprot/summary/{uniprot_id}.json" + elif endpoint == "annotations": + if residue_range: + url = f"{base_url}/annotations/{uniprot_id}/{residue_range}" + else: + url = f"{base_url}/annotations/{uniprot_id}" + + try: + # Make the API request + response = requests.get(url) + response.raise_for_status() + + # Parse the response as JSON + result = response.json() + + # Handle download request if specified + download_info = None + if download: + # Ensure output directory exists + if not output_dir: + output_dir = "." + os.makedirs(output_dir, exist_ok=True) + + # Generate standard AlphaFold filename + file_ext = file_format.lower() + filename = f"AF-{uniprot_id}-F{model_number}-model_{model_version}.{file_ext}" + file_path = os.path.join(output_dir, filename) + + # Construct download URL + download_url = f"https://alphafold.ebi.ac.uk/files/{filename}" + + # Download the file + download_response = requests.get(download_url) + if download_response.status_code == 200: + with open(file_path, "wb") as f: + f.write(download_response.content) + download_info = { + "success": True, + "file_path": file_path, + "url": download_url, + } + else: + download_info = { + "success": False, + "error": f"Failed to download file (status code: {download_response.status_code})", + "url": download_url, + } + + # Return the query information and results + response_data = { + "query_info": { + "uniprot_id": uniprot_id, + "endpoint": endpoint, + "residue_range": residue_range, + "url": url, + }, + "result": result, + } + + if download_info: + response_data["download"] = download_info + + return response_data + + except requests.exceptions.RequestException as e: + error_msg = str(e) + response_text = "" + + # Try to get more detailed error info from response + if hasattr(e, "response") and e.response: + try: + error_json = e.response.json() + if "message" in error_json: + error_msg = error_json["message"] + except Exception: + response_text = e.response.text + + return { + "error": f"AlphaFold API error: {error_msg}", + "query_info": { + "uniprot_id": uniprot_id, + "endpoint": endpoint, + "residue_range": residue_range, + "url": url, + }, + "response_text": response_text, + } + except Exception as e: + return { + "error": f"Error: {str(e)}", + "query_info": { + "uniprot_id": uniprot_id, + "endpoint": endpoint, + "residue_range": residue_range, + }, + } + + +def query_interpro( + prompt=None, + endpoint=None, + max_results=3, +): + """Query the InterPro REST API using natural language or a direct endpoint. + + Parameters + ---------- + prompt (str, required): Natural language query about protein domains or families + endpoint (str, optional): Direct endpoint path or full URL (e.g., "/entry/interpro/IPR023411" + or "https://www.ebi.ac.uk/interpro/api/entry/interpro/IPR023411") + max_results (int): Maximum number of results to return per page + + Returns + ------- + dict: Dictionary containing both the query information and the InterPro API results + + Examples + -------- + - Natural language: query_interpro("Find information about kinase domains in InterPro") + - Direct endpoint: query_interpro(endpoint="/entry/interpro/IPR023411") + + """ + # Base URL for InterPro API + base_url = "https://www.ebi.ac.uk/interpro/api" + + # Default parameters + format = "json" + + # Ensure we have either a prompt or an endpoint + if prompt is None and endpoint is None: + return {"error": "Either a prompt or an endpoint must be provided"} + + # If using prompt, parse with Claude + if prompt: + # Load InterPro schema + schema_path = os.path.join(os.path.dirname(__file__), "schema_db", "interpro.pkl") + with open(schema_path, "rb") as f: + interpro_schema = pickle.load(f) + + # Create system prompt template + system_template = """ + You are a protein domain expert specialized in using the InterPro REST API. + + Based on the user's natural language request, determine the appropriate InterPro REST API endpoint. + + INTERPRO REST API SCHEMA: + {schema} + + Your response should be a JSON object with the following fields: + 1. "full_url": The complete URL to query (including the base URL "https://www.ebi.ac.uk/interpro/api") + 2. "description": A brief description of what the query is doing + + SPECIAL NOTES: + - Path components for data types: entry, protein, structure, set, taxonomy, proteome + - Common sources: interpro, pfam, cdd, uniprot, pdb + - Protein subtypes can be "reviewed" or "unreviewed" + - For specific entries, use lowercase accessions (e.g., "ipr000001" instead of "IPR000001") + - Endpoints can be hierarchical like "/entry/interpro/protein/uniprot/P04637" + + Return ONLY the JSON object with no additional text. + """ + + # Query Claude to generate the API call + llm_result = _query_llm_for_api( + prompt=prompt, + schema=interpro_schema, + system_template=system_template, + ) + + if not llm_result["success"]: + return llm_result + + # Extract the full URL from Claude's response + query_info = llm_result["data"] + endpoint = query_info.get("full_url", "") + description = query_info.get("description", "") + + if not endpoint: + return { + "error": "Failed to generate a valid endpoint from the prompt", + "llm_response": llm_result.get("raw_response", "No response"), + } + else: + # Process provided endpoint + # If it's just a path, add the base URL + if endpoint.startswith("/"): + endpoint = f"{base_url}{endpoint}" + elif not endpoint.startswith("http"): + endpoint = f"{base_url}/{endpoint.lstrip('/')}" + + description = "Direct query to provided endpoint" + + # Add pagination parameters + params = {"page": 1, "page_size": max_results} + + # Add format parameter if not json + if format and format != "json": + params["format"] = format + + # Make the API request + api_result = _query_rest_api(endpoint=endpoint, method="GET", params=params, description=description) + + return api_result + + +def query_pdb( + prompt=None, + query=None, + max_results=3, +): + """Query the RCSB PDB database using natural language or a direct structured query. + + Parameters + ---------- + prompt (str, required): Natural language query about protein structures + query (dict, optional): Direct structured query in RCSB Search API format (overrides prompt) + max_results (int): Maximum number of results to return + + Returns + ------- + dict: Dictionary containing the structured query, search results, and identifiers + + Examples + -------- + - Natural language: query_pdb("Find structures of human insulin") + - Direct query: query_pdb(query={"query": {"type": "terminal", "service": "full_text", + "parameters": {"value": "insulin"}}, "return_type": "entry"}) + + """ + # Default parameters + return_type = "entry" + search_service = "full_text" + + # Generate search query from natural language if prompt is provided and query is not + if prompt and not query: + # Load schema from pickle file + schema_path = os.path.join(os.path.dirname(__file__), "schema_db", "pdb.pkl") + + with open(schema_path, "rb") as f: + schema = pickle.load(f) + + # Create system prompt template + system_template = """ + You are a structural biology expert that creates precise RCSB PDB Search API queries based on natural language requests. + + SEARCH API SCHEMA: + {schema} + + IMPORTANT GUIDELINES: + 1. Choose the appropriate search_service based on the query: + - Use "text" for attribute-specific searches (REQUIRES attribute, operator, and value) + - Use "full_text" for general keyword searches across multiple fields + - Use appropriate specialized services for sequence, structure, motif searches + + 2. For "text" searches, you MUST specify: + - attribute: The specific field to search (use common_attributes from schema) + - operator: The comparison method (exact_match, contains_words, less_or_equal, etc.) + - value: The search term or value + + 3. For "full_text" searches, only specify: + - value: The search term(s) + + 4. For combined searches, use "group" nodes with logical_operator ("and" or "or") + + 5. Always specify the appropriate return_type based on what the user is looking for + + Generate a well-formed Search API query JSON object. Return ONLY the JSON with no additional explanation. + """ + + # Query Claude to generate the search query + llm_result = _query_llm_for_api( + prompt=prompt, + schema=schema, + system_template=system_template, + ) + + if not llm_result["success"]: + return { + "error": llm_result["error"], + "llm_response": llm_result.get("raw_response", "No response"), + } + + # Get the query from Claude's response + query_json = llm_result["data"] + else: + # Use provided query directly + query_json = ( + query + if query + else { + "query": { + "type": "terminal", + "service": search_service, + "parameters": {"value": prompt}, + }, + "return_type": return_type, + } + ) + + # Ensure return_type is set + if "return_type" not in query_json: + query_json["return_type"] = return_type + + # Add request options for pagination + if "request_options" not in query_json: + query_json["request_options"] = {} + + if "paginate" not in query_json["request_options"]: + query_json["request_options"]["paginate"] = {"start": 0, "rows": max_results} + + # Use query_rest_api to execute the search + search_url = "https://search.rcsb.org/rcsbsearch/v2/query" + api_result = _query_rest_api( + endpoint=search_url, + method="POST", + json_data=query_json, + description="PDB Search API query", + ) + + return api_result + + +def query_pdb_identifiers(identifiers, return_type="entry", download=False, attributes=None): + """Retrieve detailed data and/or download files for PDB identifiers. + + Parameters + ---------- + identifiers (list): List of PDB identifiers (from query_pdb) + return_type (str): Type of results: "entry", "assembly", "polymer_entity", etc. + download (bool): Whether to download PDB structure files + attributes (list, optional): List of specific attributes to retrieve + + Returns + ------- + dict: Dictionary containing the detailed data and file paths if downloaded + + Example: + - Search and then get details: + results = query_pdb("Find structures of human insulin") + details = get_pdb_details(results["identifiers"], download=True) + + """ + if not identifiers: + return {"error": "No identifiers provided"} + + try: + # Fetch detailed data using Data API + detailed_results = [] + for identifier in identifiers: + try: + # Determine the appropriate endpoint based on return_type and identifier format + if return_type == "entry": + data_url = f"https://data.rcsb.org/rest/v1/core/entry/{identifier}" + elif return_type == "polymer_entity": + entry_id, entity_id = identifier.split("_") + data_url = f"https://data.rcsb.org/rest/v1/core/polymer_entity/{entry_id}/{entity_id}" + elif return_type == "nonpolymer_entity": + entry_id, entity_id = identifier.split("_") + data_url = f"https://data.rcsb.org/rest/v1/core/nonpolymer_entity/{entry_id}/{entity_id}" + elif return_type == "polymer_instance": + entry_id, asym_id = identifier.split(".") + data_url = f"https://data.rcsb.org/rest/v1/core/polymer_entity_instance/{entry_id}/{asym_id}" + elif return_type == "assembly": + entry_id, assembly_id = identifier.split("-") + data_url = f"https://data.rcsb.org/rest/v1/core/assembly/{entry_id}/{assembly_id}" + elif return_type == "mol_definition": + data_url = f"https://data.rcsb.org/rest/v1/core/chem_comp/{identifier}" + + # Fetch data + data_response = requests.get(data_url) + data_response.raise_for_status() + entity_data = data_response.json() + + # Filter attributes if specified + if attributes: + filtered_data = {} + for attr in attributes: + parts = attr.split(".") + current = entity_data + try: + for part in parts[:-1]: + current = current[part] + filtered_data[attr] = current[parts[-1]] + except (KeyError, TypeError): + filtered_data[attr] = None + entity_data = filtered_data + + detailed_results.append({"identifier": identifier, "data": entity_data}) + except Exception as e: + detailed_results.append({"identifier": identifier, "error": str(e)}) + + # Download structure files if requested + if download: + for identifier in identifiers: + if "_" in identifier or "." in identifier or "-" in identifier: + # For non-entry identifiers, extract the PDB ID + if "_" in identifier: + pdb_id = identifier.split("_")[0] + elif "." in identifier: + pdb_id = identifier.split(".")[0] + elif "-" in identifier: + pdb_id = identifier.split("-")[0] + else: + pdb_id = identifier + + try: + # Download PDB file + pdb_url = f"https://files.rcsb.org/download/{pdb_id}.pdb" + pdb_response = requests.get(pdb_url) + + if pdb_response.status_code == 200: + # Create data directory if it doesn't exist + data_dir = os.path.join(os.path.dirname(__file__), "data", "pdb") + os.makedirs(data_dir, exist_ok=True) + + # Save PDB file + pdb_file_path = os.path.join(data_dir, f"{pdb_id}.pdb") + with open(pdb_file_path, "wb") as pdb_file: + pdb_file.write(pdb_response.content) + + # Add download information to results + for result in detailed_results: + if result["identifier"] == identifier or result["identifier"].startswith(pdb_id): + result["pdb_file_path"] = pdb_file_path + except Exception as e: + for result in detailed_results: + if result["identifier"] == identifier or result["identifier"].startswith(pdb_id): + result["download_error"] = str(e) + + return {"detailed_results": detailed_results} + + except Exception as e: + return {"error": f"Error retrieving PDB details: {str(e)}"} + + +def query_kegg(prompt, endpoint=None, verbose=True): + """Take a natural language prompt and convert it to a structured KEGG API query. + + Parameters + ---------- + prompt (str): Natural language query about KEGG data (e.g., "Find human pathways related to glycolysis") + endpoint (str, optional): Direct KEGG API endpoint to query + verbose (bool): Whether to print verbose output + + Returns + ------- + dict: Dictionary containing both the structured query and the KEGG results + + """ + base_url = "https://rest.kegg.jp" + + if not prompt and not endpoint: + return {"error": "Either a prompt or an endpoint must be provided"} + + if prompt: + # Load schema from pickle file + schema_path = os.path.join(os.path.dirname(__file__), "schema_db", "kegg.pkl") + with open(schema_path, "rb") as f: + kegg_schema = pickle.load(f) + + # Create system prompt template + system_template = """ + You are a bioinformatics expert that helps convert natural language queries into KEGG API requests. + + Based on the user's natural language request, you will generate a structured query for the KEGG API. + + The KEGG API has the following general form: + https://rest.kegg.jp//[/[/ ...]] + + Where can be one of: info, list, find, get, conv, link, ddi + + Here is the schema of available operations, databases, and other details: + {schema} + + Output only a JSON object with the following fields: + 1. "full_url": The complete URL to query (including the base URL "https://rest.kegg.jp") + 2. "description": A brief description of what the query is doing + + IMPORTANT: Your response must ONLY contain a JSON object with the required fields. + + EXAMPLES OF CORRECT OUTPUTS: + - For "Find information about glycolysis pathway": {{"full_url": "https://rest.kegg.jp/info/pathway/hsa00010", "description": "Finding information about the glycolysis pathway"}} + - For "Get information about the human BRCA1 gene": {{"full_url": "https://rest.kegg.jp/get/hsa:672", "description": "Retrieving information about BRCA1 gene in human"}} + - For "List all human pathways": {{"full_url": "https://rest.kegg.jp/list/pathway/hsa", "description": "Listing all human-specific pathways"}} + - For "Convert NCBI gene ID 672 to KEGG ID": {{"full_url": "https://rest.kegg.jp/conv/genes/ncbi-geneid:672", "description": "Converting NCBI Gene ID 672 to KEGG gene identifier"}} + """ + + # Query LLM to generate the API call + llm_result = _query_llm_for_api( + prompt=prompt, + schema=kegg_schema, + system_template=system_template, + ) + + if not llm_result["success"]: + return llm_result + + # Extract the query info from Claude's response + query_info = llm_result["data"] + endpoint = query_info["full_url"] + description = query_info["description"] + + if not endpoint: + return { + "error": "Failed to generate a valid endpoint from the prompt", + "llm_response": llm_result.get("raw_response", "No response"), + } + + if endpoint: + if endpoint.startswith("/"): + endpoint = f"{base_url}{endpoint}" + elif not endpoint.startswith("http"): + endpoint = f"{base_url}/{endpoint.lstrip('/')}" + description = "Direct query to KEGG API" + + # Execute the KEGG API request using the helper function + api_result = _query_rest_api(endpoint=endpoint, method="GET", description=description) + + if not verbose and "success" in api_result and api_result["success"] and "result" in api_result: + return _format_query_results(api_result["result"]) + + return api_result + + +def query_stringdb( + prompt=None, + endpoint=None, + download_image=False, + output_dir=None, + verbose=True, +): + """Query the STRING protein interaction database using natural language or direct endpoint. + + Parameters + ---------- + prompt (str, required): Natural language query about protein interactions + endpoint (str, optional): Full URL to query directly (overrides prompt) + download_image (bool): Whether to download image results (for image endpoints) + output_dir (str, optional): Directory to save downloaded files (default: current directory) + + Returns + ------- + dict: Dictionary containing the query results or error information + + Examples + -------- + - Natural language: query_stringdb("Show protein interactions for BRCA1 and BRCA2 in humans") + - Direct endpoint: query_stringdb(endpoint="https://string-db.org/api/json/network?identifiers=BRCA1,BRCA2&species=9606") + + """ + # Base URL for STRING API + base_url = "https://version-12-0.string-db.org/api" + + # Ensure we have either a prompt or an endpoint + if prompt is None and endpoint is None: + return {"error": "Either a prompt or an endpoint must be provided"} + + # If using prompt, parse with Claude + if prompt: + # Load STRING schema + schema_path = os.path.join(os.path.dirname(__file__), "schema_db", "stringdb.pkl") + with open(schema_path, "rb") as f: + stringdb_schema = pickle.load(f) + + # Create system prompt template + system_template = """ + You are a protein interaction expert specialized in using the STRING database API. + + Based on the user's natural language request, determine the appropriate STRING API endpoint and parameters. + + STRING API SCHEMA: + {schema} + + Your response should be a JSON object with the following fields: + 1. "full_url": The complete URL to query (including all parameters) + 2. "description": A brief description of what the query is doing + 3. "output_format": The format of the output (json, tsv, image, svg) + + SPECIAL NOTES: + - Common species IDs: 9606 (human), 10090 (mouse), 7227 (fruit fly), 4932 (yeast) + - For protein identifiers, use either gene names (e.g., "BRCA1") or UniProt IDs (e.g., "P38398") + - The "required_score" parameter accepts values from 0 to 1000 (higher means more stringent) + - Add "caller_identity=bioagentos_api" as a parameter + + Return ONLY the JSON object with no additional text. + """ + + # Query Claude to generate the API call + llm_result = _query_llm_for_api( + prompt=prompt, + schema=stringdb_schema, + system_template=system_template, + ) + + if not llm_result["success"]: + return llm_result + + # Get the full URL from Claude's response + query_info = llm_result["data"] + endpoint = query_info.get("full_url", "") + description = query_info.get("description", "") + output_format = query_info.get("output_format", "json") + + if not endpoint: + return { + "error": "Failed to generate a valid endpoint from the prompt", + "llm_response": llm_result.get("raw_response", "No response"), + } + else: + # Use direct endpoint + if endpoint.startswith("/"): + endpoint = f"{base_url}{endpoint}" + elif not endpoint.startswith("http"): + endpoint = f"{base_url}/{endpoint.lstrip('/')}" + description = "Direct query to STRING API" + output_format = "json" + + # Try to determine output format from URL + if "image" in endpoint or "svg" in endpoint: + output_format = "image" + + # Check if we're dealing with an image request + is_image = output_format in ["image", "highres_image", "svg"] + + if is_image: + if download_image: + # For images, we need to handle the download manually + try: + response = requests.get(endpoint, stream=True) + response.raise_for_status() + + # Create output directory if needed + if not output_dir: + output_dir = "." + os.makedirs(output_dir, exist_ok=True) + + # Generate filename based on endpoint + endpoint_parts = endpoint.split("/") + filename = f"string_{endpoint_parts[-2]}_{int(time.time())}.{output_format}" + file_path = os.path.join(output_dir, filename) + + # Save the image + with open(file_path, "wb") as f: + for chunk in response.iter_content(chunk_size=1024): + if chunk: + f.write(chunk) + + return { + "success": True, + "query_info": { + "endpoint": endpoint, + "description": description, + "output_format": output_format, + }, + "result": { + "image_saved": True, + "file_path": file_path, + "content_type": response.headers.get("Content-Type"), + }, + } + except Exception as e: + return { + "success": False, + "error": f"Error downloading image: {str(e)}", + "query_info": {"endpoint": endpoint, "description": description}, + } + else: + # Just report that an image is available but not downloaded + return { + "success": True, + "query_info": { + "endpoint": endpoint, + "description": description, + "output_format": output_format, + }, + "result": { + "image_available": True, + "download_url": endpoint, + "note": "Set download_image=True to save the image", + }, + } + + # For non-image requests, use the REST API helper + api_result = _query_rest_api(endpoint=endpoint, method="GET", description=description) + + if not verbose and "success" in api_result and api_result["success"] and "result" in api_result: + return _format_query_results(api_result["result"]) + + return api_result + + +def query_iucn( + prompt=None, + endpoint=None, + token="", + verbose=True, +): + """Query the IUCN Red List API using natural language or a direct endpoint. + + Parameters + ---------- + prompt (str, required): Natural language query about species conservation status + endpoint (str, optional): API endpoint name (e.g., "species/id/12392") or full URL + token (str): IUCN API token - required for all queries + verbose (bool): Whether to print verbose output + + Returns + ------- + dict: Dictionary containing the query results or error information + + Examples + -------- + - Natural language: query_iucn("Get conservation status of white rhinoceros", token="your-token") + - Direct endpoint: query_iucn(endpoint="species/id/12392", token="your-token") + + """ + # Base URL for IUCN API + base_url = "https://apiv3.iucnredlist.org/api/v3" + + # Ensure we have either a prompt or an endpoint + if prompt is None and endpoint is None: + return {"error": "Either a prompt or an endpoint must be provided"} + + # Ensure we have a token + if not token: + return {"error": "IUCN API token is required. Get one at https://apiv3.iucnredlist.org/api/v3/token"} + + # If using prompt, parse with Claude + if prompt: + # Load IUCN schema + schema_path = os.path.join(os.path.dirname(__file__), "schema_db", "iucn.pkl") + with open(schema_path, "rb") as f: + iucn_schema = pickle.load(f) + + # Create system prompt template + system_template = """ + You are a conservation biology expert specialized in using the IUCN Red List API. + + Based on the user's natural language request, determine the appropriate IUCN API endpoint. + + IUCN API SCHEMA: + {schema} + + Your response should be a JSON object with the following fields: + 1. "full_url": The complete URL to query (including the base URL "https://apiv3.iucnredlist.org/api/v3" and any path parameters) + 2. "description": A brief description of what the query is doing + + SPECIAL NOTES: + - The token parameter will be added automatically, do not include it in your URL + - For taxonomic queries, prefer using scientific names over common names + - For region-specific queries, use region identifiers from the schema + - For species queries, try to use the species ID if known, otherwise use scientific name + + Return ONLY the JSON object with no additional text. + """ + + # Query Claude to generate the API call + llm_result = _query_llm_for_api( + prompt=prompt, + schema=iucn_schema, + system_template=system_template, + ) + + if not llm_result["success"]: + return llm_result + + # Get the full URL from Claude's response + query_info = llm_result["data"] + endpoint = query_info.get("full_url", "") + description = query_info.get("description", "") + + if not endpoint: + return { + "error": "Failed to generate a valid endpoint from the prompt", + "llm_response": llm_result.get("raw_response", "No response"), + } + else: + # Process provided endpoint + if not endpoint.startswith("http"): + endpoint = f"{base_url}{endpoint}" if endpoint.startswith("/") else f"{base_url}/{endpoint}" + description = "Direct query to IUCN API" + + # Add token as query parameter + params = {"token": token} + + # Execute the IUCN API request using the helper function + api_result = _query_rest_api(endpoint=endpoint, method="GET", params=params, description=description) + + # For security, remove token from the results + if "query_info" in api_result and "endpoint" in api_result["query_info"]: + api_result["query_info"]["endpoint"] = api_result["query_info"]["endpoint"].replace(token, "TOKEN_HIDDEN") + + if not verbose and "success" in api_result and api_result["success"] and "result" in api_result: + return _format_query_results(api_result["result"]) + + return api_result + + +def query_paleobiology( + prompt=None, + endpoint=None, + verbose=True, +): + """Query the Paleobiology Database (PBDB) API using natural language or a direct endpoint. + + Parameters + ---------- + prompt (str, required): Natural language query about fossil records + endpoint (str, optional): API endpoint name or full URL + verbose (bool): Whether to print verbose output + + Returns + ------- + dict: Dictionary containing the query results or error information + + Examples + -------- + - Natural language: query_paleobiology("Find fossil records of Tyrannosaurus rex") + - Direct endpoint: query_paleobiology(endpoint="data1.2/taxa/list.json?name=Tyrannosaurus") + + """ + # Base URL for PBDB API + base_url = "https://paleobiodb.org/data1.2" + + # Ensure we have either a prompt or an endpoint + if prompt is None and endpoint is None: + return {"error": "Either a prompt or an endpoint must be provided"} + + # If using prompt, parse with Claude + if prompt: + # Load PBDB schema + schema_path = os.path.join(os.path.dirname(__file__), "schema_db", "pbdb.pkl") + with open(schema_path, "rb") as f: + pbdb_schema = pickle.load(f) + + # Create system prompt template + system_template = """ + You are a paleobiology expert specialized in using the Paleobiology Database (PBDB) API. + + Based on the user's natural language request, determine the appropriate PBDB API endpoint and parameters. + + PBDB API SCHEMA: + {schema} + + Your response should be a JSON object with the following fields: + 1. "full_url": The complete URL to query (including the base URL "https://paleobiodb.org/data1.2" and format extension) + 2. "description": A brief description of what the query is doing + + SPECIAL NOTES: + - For taxonomic queries, be specific about taxonomic ranks and names + - For geographic queries, use standard country/continent names or coordinate bounding boxes + - For time interval queries, use standard geological time names (e.g., "Cretaceous", "Maastrichtian") + - Use appropriate format extension (.json, .txt, .csv, .tsv) based on the query + - If appropriate, use "vocab=pbdb" (default) or "vocab=com" (compact) parameter in the URL + - For detailed occurrence data, include "show=paleoloc,phylo" in the parameters + + Return ONLY the JSON object with no additional text. + """ + + # Query Claude to generate the API call + llm_result = _query_llm_for_api( + prompt=prompt, + schema=pbdb_schema, + system_template=system_template, + ) + + if not llm_result["success"]: + return llm_result + + # Get the full URL from Claude's response + query_info = llm_result["data"] + endpoint = query_info.get("full_url", "") + description = query_info.get("description", "") + + if not endpoint: + return { + "error": "Failed to generate a valid endpoint from the prompt", + "llm_response": llm_result.get("raw_response", "No response"), + } + else: + # Process provided endpoint + if not endpoint.startswith("http"): + # Add base URL if it's just a path + endpoint = f"{base_url}/{endpoint}" if not endpoint.startswith("/") else f"{base_url}{endpoint}" + + description = "Direct query to PBDB API" + + # Check if we're dealing with an image request + is_image = endpoint.endswith(".png") + + if is_image: + # For image queries, we need special handling + try: + response = requests.get(endpoint) + response.raise_for_status() + + # Return image metadata without the binary data + return { + "success": True, + "query_info": { + "endpoint": endpoint, + "description": description, + "format": "png", + }, + "result": { + "content_type": response.headers.get("Content-Type"), + "size_bytes": len(response.content), + "note": "Binary image data not included in response", + }, + } + except Exception as e: + return { + "success": False, + "error": f"Error retrieving image: {str(e)}", + "query_info": {"endpoint": endpoint, "description": description}, + } + + # For non-image requests, use the REST API helper + api_result = _query_rest_api(endpoint=endpoint, method="GET", description=description) + + if not verbose and "success" in api_result and api_result["success"] and "result" in api_result: + return _format_query_results(api_result["result"]) + + return api_result + + +def query_jaspar( + prompt=None, + endpoint=None, + verbose=True, +): + """Query the JASPAR REST API using natural language or a direct endpoint. + + Parameters + ---------- + prompt (str, required): Natural language query about transcription factor binding profiles + endpoint (str, optional): API endpoint path (e.g., "/matrix/MA0002.2/") or full URL + verbose (bool): Whether to print verbose output + + Returns + ------- + dict: Dictionary containing the query results or error information + + Examples + -------- + - Natural language: query_jaspar("Find all transcription factor matrices for human") + - Direct endpoint: query_jaspar(endpoint="/matrix/MA0002.2/") + + """ + # Base URL for JASPAR API + base_url = "https://jaspar.elixir.no/api/v1" + + # Ensure we have either a prompt or an endpoint + if prompt is None and endpoint is None: + return {"error": "Either a prompt or an endpoint must be provided"} + + # If using prompt, parse with Claude + if prompt: + # Load JASPAR schema + schema_path = os.path.join(os.path.dirname(__file__), "schema_db", "jaspar.pkl") + with open(schema_path, "rb") as f: + jaspar_schema = pickle.load(f) + + # Create system prompt template + system_template = """ + You are a transcription factor binding site expert specialized in using the JASPAR REST API. + + Based on the user's natural language request, determine the appropriate JASPAR REST API endpoint and parameters. + + JASPAR REST API SCHEMA: + {schema} + + Your response should be a JSON object with the following fields: + 1. "full_url": The complete URL to query (including the base URL "https://jaspar.elixir.no/api/v1" and any parameters) + 2. "description": A brief description of what the query is doing + + SPECIAL NOTES: + - Common taxonomic groups include: vertebrates, plants, fungi, insects, nematodes, urochordates + - Common collections include: CORE, UNVALIDATED, PENDING, etc. + - Matrix IDs follow the format MA####.# (e.g., MA0002.2) + - For inferring matrices from sequences, provide the protein sequence directly in the path + + Return ONLY the JSON object with no additional text. + """ + + # Query Claude to generate the API call + llm_result = _query_llm_for_api( + prompt=prompt, + schema=jaspar_schema, + system_template=system_template, + ) + + if not llm_result["success"]: + return llm_result + + # Get the full URL from Claude's response + query_info = llm_result["data"] + endpoint = query_info.get("full_url", "") + description = query_info.get("description", "") + + if not endpoint: + return { + "error": "Failed to generate a valid endpoint from the prompt", + "llm_response": llm_result.get("raw_response", "No response"), + } + else: + # Process provided endpoint + if not endpoint.startswith("http"): + # Clean up endpoint format + if not endpoint.startswith("/"): + endpoint = "/" + endpoint + + # Ensure endpoint ends with / + if not endpoint.endswith("/"): + endpoint = endpoint + "/" + + # Add base URL + endpoint = f"{base_url}{endpoint}" + + description = "Direct query to JASPAR API" + + # Execute the JASPAR API request using the helper function + api_result = _query_rest_api(endpoint=endpoint, method="GET", description=description) + + if not verbose and "success" in api_result and api_result["success"] and "result" in api_result: + return _format_query_results(api_result["result"]) + + return api_result + + +def query_worms( + prompt=None, + endpoint=None, + verbose=True, +): + """Query the World Register of Marine Species (WoRMS) REST API using natural language or a direct endpoint. + + Parameters + ---------- + prompt (str, required): Natural language query about marine species + endpoint (str, optional): Full URL or endpoint specification + verbose (bool): Whether to print verbose output + + Returns + ------- + dict: Dictionary containing the query results or error information + + Examples + -------- + - Natural language: query_worms("Find information about the blue whale") + - Direct endpoint: query_worms(endpoint="https://www.marinespecies.org/rest/AphiaRecordByName/Balaenoptera%20musculus") + + """ + # Base URL for WoRMS API + base_url = "https://www.marinespecies.org/rest" + + # Ensure we have either a prompt or an endpoint + if prompt is None and endpoint is None: + return {"error": "Either a prompt or an endpoint must be provided"} + + # If using prompt, parse with Claude + if prompt: + # Load WoRMS schema + schema_path = os.path.join(os.path.dirname(__file__), "schema_db", "worms.pkl") + with open(schema_path, "rb") as f: + worms_schema = pickle.load(f) + + # Create system prompt template + system_template = """ + You are a marine biology expert specialized in using the World Register of Marine Species (WoRMS) API. + + Based on the user's natural language request, determine the appropriate WoRMS API endpoint and parameters. + + WORMS API SCHEMA: + {schema} + + Your response should be a JSON object with the following fields: + 1. "full_url": The complete URL to query (including the base URL "https://www.marinespecies.org/rest" and any path/query parameters) + 2. "description": A brief description of what the query is doing + + SPECIAL NOTES: + - For taxonomic searches, be precise with scientific names and use proper capitalization + - For fuzzy matching, include "fuzzy=true" in the URL query parameters + - When searching by name, prefer "AphiaRecordByName" for exact matches and "AphiaRecordsByName" for broader results + - AphiaID is the main identifier in WoRMS (e.g., Blue Whale is 137087) + - For multiple IDs or names, use the appropriate POST endpoint + + Return ONLY the JSON object with no additional text. + """ + + # Query Claude to generate the API call + llm_result = _query_llm_for_api( + prompt=prompt, + schema=worms_schema, + system_template=system_template, + ) + + if not llm_result["success"]: + return llm_result + + # Get the full URL and details from Claude's response + query_info = llm_result["data"] + endpoint = query_info.get("full_url", "") + description = query_info.get("description", "") + + if not endpoint: + return { + "error": "Failed to generate a valid endpoint from the prompt", + "llm_response": llm_result.get("raw_response", "No response"), + } + else: + # Process provided endpoint + if not endpoint.startswith("http"): + # Add base URL if it's just a path + endpoint = f"{base_url}/{endpoint}" if not endpoint.startswith("/") else f"{base_url}{endpoint}" + + description = "Direct query to WoRMS API" + + # Execute the WoRMS API request using the helper function + api_result = _query_rest_api(endpoint=endpoint, method="GET", description=description) + + if not verbose and "success" in api_result and api_result["success"] and "result" in api_result: + return _format_query_results(api_result["result"]) + + return api_result + + +def query_cbioportal( + prompt=None, + endpoint=None, + verbose=True, +): + """Query the cBioPortal REST API using natural language or a direct endpoint. + + Parameters + ---------- + prompt (str, required): Natural language query about cancer genomics data + endpoint (str, optional): API endpoint path (e.g., "/studies/brca_tcga/patients") or full URL + verbose (bool): Whether to print verbose output + + Returns + ------- + dict: Dictionary containing the query results or error information + + Examples + -------- + - Natural language: query_cbioportal("Find mutations in BRCA1 for breast cancer") + - Direct endpoint: query_cbioportal(endpoint="/studies/brca_tcga/molecular-profiles") + + """ + # Base URL for cBioPortal API + base_url = "https://www.cbioportal.org/api" + + # Ensure we have either a prompt or an endpoint + if prompt is None and endpoint is None: + return {"error": "Either a prompt or an endpoint must be provided"} + + # If using prompt, parse with Claude + if prompt: + # Load cBioPortal schema + schema_path = os.path.join(os.path.dirname(__file__), "schema_db", "cbioportal.pkl") + with open(schema_path, "rb") as f: + cbioportal_schema = pickle.load(f) + + # Create system prompt template + system_template = """ + You are a cancer genomics expert specialized in using the cBioPortal REST API. + + Based on the user's natural language request, determine the appropriate cBioPortal REST API endpoint and parameters. + + CBIOPORTAL REST API SCHEMA: + {schema} + + Your response should be a JSON object with the following fields: + 1. "full_url": The complete URL to query (including the base URL "https://www.cbioportal.org/api" and any parameters) + 2. "description": A brief description of what the query is doing + + SPECIAL NOTES: + - For gene queries, use either Hugo symbol (e.g., "BRCA1") or Entrez ID (e.g., 672) + - For pagination, include parameters "pageNumber" and "pageSize" if needed + - For mutation data queries, always include appropriate sample identifiers + - Common studies include: "brca_tcga" (breast cancer), "gbm_tcga" (glioblastoma), "luad_tcga" (lung adenocarcinoma) + - For molecular profiles, common IDs follow pattern: "[study]_[data_type]" (e.g., "brca_tcga_mutations") + - Consider including "projection=DETAILED" for more comprehensive results when appropriate + + Return ONLY the JSON object with no additional text. + """ + + # Query Claude to generate the API call + llm_result = _query_llm_for_api( + prompt=prompt, + schema=cbioportal_schema, + system_template=system_template, + ) + + if not llm_result["success"]: + return llm_result + + # Get the full URL from Claude's response + query_info = llm_result["data"] + endpoint = query_info.get("full_url", "") + description = query_info.get("description", "") + + if not endpoint: + return { + "error": "Failed to generate a valid endpoint from the prompt", + "llm_response": llm_result.get("raw_response", "No response"), + } + else: + # Process provided endpoint + if not endpoint.startswith("http"): + # Clean up endpoint format + if not endpoint.startswith("/"): + endpoint = "/" + endpoint + + # Add base URL + endpoint = f"{base_url}{endpoint}" + + description = "Direct query to cBioPortal API" + + # Execute the cBioPortal API request using the helper function + api_result = _query_rest_api(endpoint=endpoint, method="GET", description=description) + + if not verbose and "success" in api_result and api_result["success"] and "result" in api_result: + return _format_query_results(api_result["result"]) + + return api_result + + +def query_clinvar( + prompt=None, + search_term=None, + max_results=3, +): + """Take a natural language prompt and convert it to a structured ClinVar query. + + Parameters + ---------- + prompt (str): Natural language query about genetic variants (e.g., "Find pathogenic BRCA1 variants") + search_term (str): Direct search term in ClinVar syntax + max_results (int): Maximum number of results to return + + Returns + ------- + dict: Dictionary containing both the structured query and the ClinVar results + + """ + if not prompt and not search_term: + return {"error": "Either a prompt or an endpoint must be provided"} + + if prompt: + # Load ClinVar schema + schema_path = os.path.join(os.path.dirname(__file__), "schema_db", "clinvar.pkl") + with open(schema_path, "rb") as f: + clinvar_schema = pickle.load(f) + + # ClinVar system prompt template + system_prompt_template = """ + You are a genetics research assistant that helps convert natural language queries into structured ClinVar search queries. + + Based on the user's natural language request, you will generate a structured search for the ClinVar database. + + Output only a JSON object with the following fields: + 1. "search_term": The exact search query to use with the ClinVar API + + IMPORTANT: Your response must ONLY contain a JSON object with the search term field. + + Your "search_term" MUST strictly follow these ClinVar search syntax rules/tags: + + {schema} + + For combining terms: Use AND, OR, NOT (must be capitalized) + For complex logic: Use parentheses + For terms with multiple words: use double quotes escaped with a backslash or underscore (e.g. breast_cancer[dis] or \"breast cancer\"[dis]) + Example: "BRCA1[gene] AND (pathogenic[clinsig] OR likely_pathogenic[clinsig])" + + + EXAMPLES OF CORRECT QUERIES: + - For "pathogenic BRCA1 variants": "BRCA1[gene] AND clinsig_pathogenic[prop]" + - For "Specific RS": "rs6025[rsid]" + - For "Combined search with multiple criteria": "BRCA1[gene] AND origin_germline[prop]" + - For "Find variants in a specific genomic region": "17[chr] AND 43000000:44000000[chrpos37]" + - If query asks for pathogenicity of a variant, it's asking for all possible germline classifications of the variant, so just [gene] AND [variant] is needed + """ + + # Query Claude to generate the API call + llm_result = _query_llm_for_api( + prompt=prompt, + schema=clinvar_schema, + system_template=system_prompt_template, + ) + + if not llm_result["success"]: + return llm_result + + # Get the full URL from Claude's response + query_info = llm_result["data"] + search_term = query_info.get("search_term", "") + + if not search_term: + return { + "error": "Failed to generate a valid search term from the prompt", + "llm_response": llm_result.get("raw_response", "No response"), + } + + return _query_ncbi_database( + database="clinvar", + search_term=search_term, + max_results=max_results, + ) + + +def query_geo( + prompt=None, + search_term=None, + max_results=3, +): + """Query the NCBI Gene Expression Omnibus (GEO) using natural language or a direct search term. + + Parameters + ---------- + prompt (str, required): Natural language query about RNA-seq, microarray, or other expression data + search_term (str, optional): Direct search term in GEO syntax + max_results (int): Maximum number of results to return + + Returns + ------- + dict: Dictionary containing the query results or error information + + Examples + -------- + - Natural language: query_geo("Find RNA-seq datasets for breast cancer") + - Direct search: query_geo(search_term="RNA-seq AND breast cancer AND gse[ETYP]") + + """ + if not prompt and not search_term: + return {"error": "Either a prompt or a search term must be provided"} + + database = "gds" # Default database + + if prompt: + # Load GEO schema + schema_path = os.path.join(os.path.dirname(__file__), "schema_db", "geo.pkl") + with open(schema_path, "rb") as f: + geo_schema = pickle.load(f) + + # Create system prompt template + system_template = """ + You are a bioinformatics research assistant that helps convert natural language queries into structured GEO (Gene Expression Omnibus) search queries. + + Based on the user's natural language request, you will generate a structured search for the GEO database. + + Output only a JSON object with the following fields: + 1. "search_term": The exact search query to use with the GEO API + 2. "database": The specific GEO database to search (either "gds" for GEO DataSets or "geoprofiles" for GEO Profiles) + + IMPORTANT: Your response must ONLY contain a JSON object with the required fields. + + Your "search_term" MUST strictly follow these GEO search syntax rules/tags: + + {schema} + + For combining terms: Use AND, OR, NOT (must be capitalized) + For complex logic: Use parentheses + For terms with multiple words: use double quotes or underscore (e.g. "breast cancer"[Title]) + Date ranges use colon format: 2015/01:2020/12[PDAT] + + Choose the appropriate database based on the user's query: + - gds: GEO DataSets (contains Series, Datasets, Platforms, Samples metadata) + - geoprofiles: GEO Profiles (contains gene expression data) + + If database isn't clearly specified, default to "gds" as it contains most common experiment metadata. + + EXAMPLES OF CORRECT OUTPUTS: + - For "RNA-seq data in breast cancer": {"search_term": "RNA-seq AND breast cancer AND gse[ETYP]", "database": "gds"} + - For "Mouse microarray data from 2020": {"search_term": "Mus musculus[ORGN] AND 2020[PDAT] AND microarray AND gse[ETYP]", "database": "gds"} + - For "Expression profiles of TP53 in lung cancer": {"search_term": "TP53[Gene Symbol] AND lung cancer", "database": "geoprofiles"} + """ + + # Query Claude to generate the API call + llm_result = _query_llm_for_api( + prompt=prompt, + schema=geo_schema, + system_template=system_template, + ) + + if not llm_result["success"]: + return llm_result + + # Get the search term and database from Claude's response + query_info = llm_result["data"] + search_term = query_info.get("search_term", "") + database = query_info.get("database", "gds") + + if not search_term: + return { + "error": "Failed to generate a valid search term from the prompt", + "llm_response": llm_result.get("raw_response", "No response"), + } + + # Execute the GEO query using the helper function + result = _query_ncbi_database( + database=database, + search_term=search_term, + max_results=max_results, + ) + + return result + + +def query_dbsnp( + prompt=None, + search_term=None, + max_results=3, +): + """Query the NCBI dbSNP database using natural language or a direct search term. + + Parameters + ---------- + prompt (str, required): Natural language query about genetic variants/SNPs + search_term (str, optional): Direct search term in dbSNP syntax + max_results (int): Maximum number of results to return + + Returns + ------- + dict: Dictionary containing the query results or error information + + Examples + -------- + - Natural language: query_dbsnp("Find pathogenic variants in BRCA1") + - Direct search: query_dbsnp(search_term="BRCA1[Gene Name] AND pathogenic[Clinical Significance]") + + """ + if not prompt and not search_term: + return {"error": "Either a prompt or a search term must be provided"} + + if prompt: + # Load dbSNP schema + schema_path = os.path.join(os.path.dirname(__file__), "schema_db", "dbsnp.pkl") + with open(schema_path, "rb") as f: + dbsnp_schema = pickle.load(f) + + # Create system prompt template + system_template = """ + You are a genetics research assistant that helps convert natural language queries into structured dbSNP search queries. + + Based on the user's natural language request, you will generate a structured search for the dbSNP database. + + Output only a JSON object with the following fields: + 1. "search_term": The exact search query to use with the dbSNP API + + IMPORTANT: Your response must ONLY contain a JSON object with the search term field. + + Your "search_term" MUST strictly follow these dbSNP search syntax rules/tags: + + {schema} + + For combining terms: Use AND, OR, NOT (must be capitalized) + For complex logic: Use parentheses + For terms with multiple words: use double quotes (e.g. "breast cancer"[Disease Name]) + + EXAMPLES OF CORRECT QUERIES: + - For "pathogenic variants in BRCA1": "BRCA1[Gene Name] AND pathogenic[Clinical Significance]" + - For "specific SNP rs6025": "rs6025[rs]" + - For "SNPs in a genomic region": "17[Chromosome] AND 41196312:41277500[Base Position]" + - For "common SNPs in EGFR": "EGFR[Gene Name] AND common[COMMON]" + """ + + # Query Claude to generate the API call + llm_result = _query_llm_for_api( + prompt=prompt, + schema=dbsnp_schema, + system_template=system_template, + ) + + if not llm_result["success"]: + return llm_result + + # Get the search term from Claude's response + query_info = llm_result["data"] + search_term = query_info.get("search_term", "") + + if not search_term: + return { + "error": "Failed to generate a valid search term from the prompt", + "llm_response": llm_result.get("raw_response", "No response"), + } + + # Execute the dbSNP query using the helper function + result = _query_ncbi_database( + database="snp", + search_term=search_term, + max_results=max_results, + ) + + return result + + +def query_ucsc( + prompt=None, + endpoint=None, + verbose=True, +): + """Query the UCSC Genome Browser API using natural language or a direct endpoint. + + Parameters + ---------- + prompt (str, required): Natural language query about genomic data + endpoint (str, optional): Full URL or endpoint specification with parameters + verbose (bool): Whether to return detailed results + + Returns + ------- + dict: Dictionary containing the query results or error information + + Examples + -------- + - Natural language: query_ucsc("Get DNA sequence of chromosome M positions 1-100 in human genome") + - Direct endpoint: query_ucsc(endpoint="https://api.genome.ucsc.edu/getData/sequence?genome=hg38&chrom=chrM&start=1&end=100") + + """ + # Base URL for UCSC API + base_url = "https://api.genome.ucsc.edu" + + # Ensure we have either a prompt or an endpoint + if prompt is None and endpoint is None: + return {"error": "Either a prompt or an endpoint must be provided"} + + # If using prompt, parse with Claude + if prompt: + # Load UCSC schema + schema_path = os.path.join(os.path.dirname(__file__), "schema_db", "ucsc.pkl") + with open(schema_path, "rb") as f: + ucsc_schema = pickle.load(f) + + # Create system prompt template + system_template = """ + You are a genomics expert specialized in using the UCSC Genome Browser API. + + Based on the user's natural language request, determine the appropriate UCSC Genome Browser API endpoint and parameters. + + UCSC GENOME BROWSER API SCHEMA: + {schema} + + Your response should be a JSON object with the following fields: + 1. "full_url": The complete URL to query (including the base URL "https://api.genome.ucsc.edu" and all parameters) + 2. "description": A brief description of what the query is doing + + SPECIAL NOTES: + - For chromosome names, always include the "chr" prefix (e.g., "chr1", "chrX", "chrM") + - Genomic positions are 0-based (first base is position 0) + - For "start" and "end" parameters, both must be provided together + - The "maxItemsOutput" parameter can be used to limit the amount of data returned + - Common genomes include: "hg38" (human), "mm39" (mouse), "danRer11" (zebrafish) + - For sequence data, use "getData/sequence" endpoint + - For chromosome listings, use "list/chromosomes" endpoint + - For available genomes, use "list/ucscGenomes" endpoint + + Return ONLY the JSON object with no additional text. + """ + + # Query Claude to generate the API call + llm_result = _query_llm_for_api( + prompt=prompt, + schema=ucsc_schema, + system_template=system_template, + ) + + if not llm_result["success"]: + return llm_result + + # Get the full URL from Claude's response + query_info = llm_result["data"] + endpoint = query_info.get("full_url", "") + description = query_info.get("description", "") + + if not endpoint: + return { + "error": "Failed to generate a valid endpoint from the prompt", + "llm_response": llm_result.get("raw_response", "No response"), + } + + else: + # Process provided endpoint + if not endpoint.startswith("http"): + # Add base URL if it's just a path + endpoint = f"{base_url}/{endpoint}" + + description = "Direct query to UCSC Genome Browser API" + + # Execute the UCSC API request using the helper function + api_result = _query_rest_api(endpoint=endpoint, method="GET", description=description) + + # Format the results if successful + if not verbose and "success" in api_result and api_result["success"] and "result" in api_result: + return _format_query_results(api_result["result"]) + + return api_result + + +def query_ensembl( + prompt=None, + endpoint=None, + verbose=True, +): + """Query the Ensembl REST API using natural language or a direct endpoint. + + Parameters + ---------- + prompt (str, required): Natural language query about genomic data + endpoint (str, optional): Direct API endpoint to query (e.g., "lookup/symbol/human/BRCA2") or full URL + verbose (bool): Whether to return detailed results + + Returns + ------- + dict: Dictionary containing the query results or error information + + Examples + -------- + - Natural language: query_ensembl("Get information about the human BRCA2 gene") + - Direct endpoint: query_ensembl(endpoint="lookup/symbol/homo_sapiens/BRCA2") + + """ + # Base URL for Ensembl API + base_url = "https://rest.ensembl.org" + + # Ensure we have either a prompt or an endpoint + if not prompt and not endpoint: + return {"error": "Either a prompt or an endpoint must be provided"} + + # If using prompt, parse with Claude + if prompt: + # Load Ensembl schema + schema_path = os.path.join(os.path.dirname(__file__), "schema_db", "ensembl.pkl") + with open(schema_path, "rb") as f: + ensembl_schema = pickle.load(f) + + # Create system prompt template + system_template = """ + You are a genomics and bioinformatics expert specialized in using the Ensembl REST API. + + Based on the user's natural language request, determine the appropriate Ensembl REST API endpoint and parameters. + + ENSEMBL REST API SCHEMA: + {schema} + + Your response should be a JSON object with the following fields: + 1. "endpoint": The API endpoint to query (e.g., "lookup/symbol/homo_sapiens/BRCA2") + 2. "params": An object containing query parameters specific to the endpoint + 3. "description": A brief description of what the query is doing + + SPECIAL NOTES: + - Chromosome region queries have a maximum length of 4900000 bp inclusive, so bp of start and end should be 4900000 bp apart. If the user's query exceeds this limit, Ensembl will return an error. + - For symbol lookups, the format is "lookup/symbol/[species]/[symbol]" + - To find the coordinates of a band on a chromosome, use /info/assembly/homo_sapiens/[chromosome] with parameters "band":1 + - To find the overlapping genes of a genomic region, use /overlap/region/homo_sapiens/[chromosome]:[start]-[end] + - For sequence queries, specify the sequence type in parameters (genomic, cdna, cds, protein) + - For converting rsID to hg38 genomic coordinates, use the "GET id/variation/[species]/[rsid]" endpoint + - Many endpoints support "content-type" parameter for format specification (application/json, text/xml) + + Return ONLY the JSON object with no additional text. + """ + + # Query Claude to generate the API call + llm_result = _query_llm_for_api( + prompt=prompt, + schema=ensembl_schema, + system_template=system_template, + ) + + if not llm_result["success"]: + return llm_result + + # Get the endpoint and parameters from Claude's response + query_info = llm_result["data"] + endpoint = query_info.get("endpoint", "") + params = query_info.get("params", {}) + description = query_info.get("description", "") + + if not endpoint: + return { + "error": "Failed to generate a valid endpoint from the prompt", + "llm_response": llm_result.get("raw_response", "No response"), + } + else: + # Process provided endpoint + if endpoint.startswith("http"): + # If a full URL is provided, extract the endpoint part + if endpoint.startswith(base_url): + endpoint = endpoint[len(base_url) :].lstrip("/") + + params = {} + description = "Direct query to Ensembl API" + + # Remove leading slash if present + if endpoint.startswith("/"): + endpoint = endpoint[1:] + + # Prepare headers for JSON response + headers = {"Content-Type": "application/json", "Accept": "application/json"} + + # Construct the URL + url = f"{base_url}/{endpoint}" + + # Execute the Ensembl API request using the helper function + api_result = _query_rest_api( + endpoint=url, + method="GET", + params=params, + headers=headers, + description=description, + ) + + # Format the results if successful + if not verbose and "success" in api_result and api_result["success"] and "result" in api_result: + return _format_query_results(api_result["result"]) + + return api_result + + +def query_opentarget( + prompt=None, + query=None, + variables=None, + verbose=False, +): + """Query the OpenTargets Platform API using natural language or a direct GraphQL query. + + Parameters + ---------- + prompt (str, required): Natural language query about drug targets, diseases, and mechanisms + query (str, optional): Direct GraphQL query string + variables (dict, optional): Variables for the GraphQL query + verbose (bool): Whether to return detailed results + + Returns + ------- + dict: Dictionary containing the query results or error information + + Examples + -------- + - Natural language: query_opentarget("Find drug targets for Alzheimer's disease") + - Direct query: query_opentarget(query="query diseaseAssociations($diseaseId: String!) {...}", + variables={"diseaseId": "EFO_0000249"}) + + """ + # Constants and initialization + OPENTARGETS_URL = "https://api.platform.opentargets.org/api/v4/graphql" + + # Ensure we have either a prompt or a query + if prompt is None and query is None: + return {"error": "Either a prompt or a GraphQL query must be provided"} + + # If using prompt, parse with Claude + if prompt: + # Load OpenTargets schema + schema_path = os.path.join(os.path.dirname(__file__), "schema_db", "opentarget.pkl") + with open(schema_path, "rb") as f: + opentarget_schema = pickle.load(f) + + # Create system prompt template + system_template = """ + You are an expert in translating natural language requests into GraphQL queries for the OpenTargets Platform API. + + Here is a schema of the main types and queries available in the OpenTargets Platform API: + {schema} + + Translate the user's natural language request into a valid GraphQL query for this API. + Return only a JSON object with two fields: + 1. "query": The complete GraphQL query string + 2. "variables": A JSON object containing the variables needed for the query + + SPECIAL NOTES: + - Disease IDs typically use EFO ontology (e.g., "EFO_0000249" for Alzheimer's disease) + - Target IDs typically use Ensembl IDs (e.g., "ENSG00000197386" for ENSG00000197386) + - The API can provide information about drug-target associations, disease-target associations, etc. + - Always limit results to a reasonable number using "first" parameter (e.g., first: 10) + - Always escape special characters, including quotes, in the query string (eg. \\" instead of ") + + Return ONLY the JSON object with no additional text or explanations. + """ + + # Query Claude to generate the API call + llm_result = _query_llm_for_api( + prompt=prompt, + schema=opentarget_schema, + system_template=system_template, + ) + + if not llm_result["success"]: + return llm_result + + # Get the query and variables from Claude's response + query_info = llm_result["data"] + query = query_info.get("query", "") + if variables is None: # Only use Claude's variables if none provided + variables = query_info.get("variables", {}) + + if not query: + return { + "error": "Failed to generate a valid GraphQL query from the prompt", + "llm_response": llm_result.get("raw_response", "No response"), + } + + # Execute the GraphQL query + api_result = _query_rest_api( + endpoint=OPENTARGETS_URL, + method="POST", + json_data={"query": query, "variables": variables or {}}, + headers={"Content-Type": "application/json"}, + description="OpenTargets Platform GraphQL query", + ) + + # Format the results if not verbose and successful + if not verbose and "success" in api_result and api_result["success"] and "result" in api_result: + api_result["result"] = _format_query_results(api_result["result"]) + + return api_result + + +# Monarch Initiative integration +def query_monarch( + prompt=None, + endpoint=None, + max_results=2, + verbose=False, +): + """Query the Monarch Initiative API using natural language or a direct endpoint. + + Parameters + ---------- + prompt (str, optional): Natural language query about genes, diseases, phenotypes, etc. + endpoint (str, optional): Direct Monarch API endpoint or full URL + max_results (int): Maximum number of results to return (if supported by endpoint) + verbose (bool): Whether to return detailed results + + Returns + ------- + dict: Dictionary containing the query results or error information + + Examples + -------- + - Natural language: query_monarch("Find phenotypes associated with BRCA1") + - Direct endpoint: query_monarch(endpoint="https://api.monarchinitiative.org/v3/api/search?q=marfan&category=biolink:Disease&limit=10") + - Direct endpoint: query_monarch(endpoint="https://api.monarchinitiative.org/v3/api/entity/MONDO:0007947") + """ + base_url = "https://api.monarchinitiative.org/v3/api" + + if prompt is None and endpoint is None: + return {"error": "Either a prompt or an endpoint must be provided"} + + # If using prompt, use Claude to generate the endpoint + if prompt: + schema_path = os.path.join(os.path.dirname(__file__), "schema_db", "monarch.pkl") + if os.path.exists(schema_path): + with open(schema_path, "rb") as f: + monarch_schema = pickle.load(f) + else: + monarch_schema = None + + system_template = """ + You are an expert in translating natural language requests into REST API calls for the Monarch Initiative Platform API. + + Here is the API schema with available endpoints and parameters: + {schema} + + Translate the user's natural language request into a valid REST API call for this API. + Return only a JSON object with three fields: + 1. "endpoint": The specific endpoint name from the schema + 2. "url": The complete URL with path parameters filled in + 3. "params": A JSON object containing query parameters needed for the request + + SPECIAL NOTES: + - Disease IDs typically use MONDO ontology (e.g., "MONDO:0007947" for Marfan syndrome) + - Gene IDs typically use HGNC (e.g., "HGNC:3603" for FBN1) or other standard identifiers + - Phenotype IDs use Human Phenotype Ontology (e.g., "HP:0002616" for aortic root dilatation) + - Association categories use biolink model terms (e.g., "biolink:DiseaseToPhenotypicFeatureAssociation") + - For example: to find phenotypes associated with BRCA1, use the following endpoint: /entity/HGNC:1100/biolink:GeneToPhenotypicFeatureAssociation + - For search queries, use the 'q' parameter with relevant keywords + - When looking for associations, use the association_table endpoint with entity ID and category + - For similarity searches, use semsim endpoints with comma-separated term lists + - Entity categories include: biolink:Disease, biolink:Gene, biolink:PhenotypicFeature, etc. + - Format parameter defaults to 'json' but can be 'tsv' for tabular data + - Use autocomplete endpoint for entity name suggestions before exact searches + + COMMON PATTERNS: + - Search for entities: Use 'search' endpoint with 'q' and 'category' parameters + - Get entity details: Use 'get_entity' endpoint with specific ID + - Find associations: Use 'association_table' endpoint with ID and association category + - Compare phenotypes: Use 'semsim_compare' with lists of phenotype IDs + - Find similar diseases: Use 'semsim_search' with phenotype profile + + Return ONLY the JSON object with no additional text or explanations. + """ + + llm_result = _query_llm_for_api( + prompt=prompt, + schema=monarch_schema, + system_template=system_template, + ) + if not llm_result["success"]: + return llm_result + query_info = llm_result["data"] + endpoint = query_info.get("url", "") # Changed from "full_url" to "url" + description = f"Monarch API query: {query_info.get('endpoint', 'unknown endpoint')}" + if not endpoint: + return { + "error": "Failed to generate a valid endpoint from the prompt", + "llm_response": llm_result.get("raw_response", "No response"), + } + else: + # Use provided endpoint directly + if endpoint is not None: + if endpoint.startswith("/"): + endpoint = f"{base_url}{endpoint}" + elif not endpoint.startswith("http"): + endpoint = f"{base_url}/{endpoint.lstrip('/')}" + description = "Direct query to Monarch API" + + # Add max_results as a query parameter if not already present + if "?" in endpoint: + if "rows=" not in endpoint and "limit=" not in endpoint: + endpoint += f"&limit={max_results}" + else: + endpoint += f"?limit={max_results}" + + api_result = _query_rest_api(endpoint=endpoint, method="GET", description=description) + + if not verbose and "success" in api_result and api_result["success"] and "result" in api_result: + return _format_query_results(api_result["result"]) + + return api_result + + +# OpenFDA integration +def query_openfda( + prompt=None, + endpoint=None, + max_results=100, + verbose=True, + search_params=None, + sort_params=None, + count_params=None, + skip_results=0, +): + """Query the OpenFDA API using natural language or direct parameters. + + Parameters + ---------- + prompt (str, optional): Natural language query about drugs, adverse events, recalls, etc. + endpoint (str, optional): Direct OpenFDA API endpoint or full URL + max_results (int): Maximum number of results to return (if supported by endpoint) + verbose (bool): Whether to return detailed results + search_params (dict, optional): Search parameters in format {"field": "term"} or {"field": ["term1", "term2"]} + sort_params (dict, optional): Sort parameters in format {"field": "asc|desc"} + count_params (str, optional): Field to count unique values for + skip_results (int): Number of results to skip for pagination (max 25000) + + Returns + ------- + dict: Dictionary containing the query results or error information + + Examples + -------- + - Natural language: query_openfda("Find adverse events for Lipitor") + - Direct endpoint: query_openfda(endpoint="https://api.fda.gov/drug/event.json?search=patient.drug.medicinalproduct:lipitor") + - Search params: query_openfda(search_params={"patient.drug.medicinalproduct": "lipitor"}, endpoint="/drug/event.json") + - Count reactions: query_openfda(count_params="patient.reaction.reactionmeddrapt.exact", endpoint="/drug/event.json") + """ + base_url = "https://api.fda.gov" + + if prompt is None and endpoint is None and search_params is None and count_params is None: + return {"error": "Either a prompt, endpoint, search_params, or count_params must be provided"} + + # If using prompt, use LLM to generate the endpoint + if prompt: + schema_path = os.path.join(os.path.dirname(__file__), "schema_db", "openfda.pkl") + if os.path.exists(schema_path): + with open(schema_path, "rb") as f: + openfda_schema = pickle.load(f) + else: + openfda_schema = None + + system_template = """ + You are a biomedical informatics expert specialized in using the OpenFDA API. + + Based on the user's natural language request, determine the appropriate OpenFDA API endpoint and parameters. + + OPENFDA API SCHEMA: + {schema} + + Your response should be a JSON object with the following fields: + 1. "full_url": The complete URL to query (including the base URL "https://api.fda.gov" and any parameters) + 2. "description": A brief description of what the query is doing + + QUERY PARAMETERS: + - search: Use field:term syntax (e.g., "patient.drug.medicinalproduct:lipitor") + - sort: Use field:asc or field:desc (e.g., "receivedate:desc") + - count: Use field.exact for exact phrase counting (e.g., "patient.reaction.reactionmeddrapt.exact") + - limit: Maximum results (max 1000) + - skip: Skip results for pagination (max 25000) + + SEARCH SYNTAX: + - Basic: search=field:term + - AND: search=field1:term1+AND+field2:term2 + - OR: search=field1:term1+field2:term2 + - Exact: search=field:"exact phrase" + + Return ONLY the JSON object with no additional text. + """ + + llm_result = _query_llm_for_api( + prompt=prompt, + schema=openfda_schema, + system_template=system_template, + ) + if not llm_result["success"]: + return llm_result + query_info = llm_result["data"] + endpoint = query_info.get("full_url", "") + if not endpoint: + return { + "error": "Failed to generate a valid endpoint from the prompt", + "llm_response": llm_result.get("raw_response", "No response"), + } + else: + # Build endpoint from parameters + if endpoint is None: + return {"error": "Endpoint must be provided when not using prompt"} + + # Ensure endpoint has proper format + if endpoint.startswith("/"): + endpoint = f"{base_url}{endpoint}" + elif not endpoint.startswith("http"): + endpoint = f"{base_url}/{endpoint.lstrip('/')}" + + # Add max_results as a query parameter if not already present + if "?" in endpoint: + if "limit=" not in endpoint: + endpoint += f"&limit={max_results}" + else: + endpoint += f"?limit={max_results}" + + # Make the API request using the REST API helper + description = "OpenFDA API query" + if prompt: + description = f"OpenFDA API query for: {prompt}" + + api_result = _query_rest_api(endpoint=endpoint, method="GET", description=description) + + # Format results based on verbose setting + if not verbose and "success" in api_result and api_result["success"] and "result" in api_result: + return _format_query_results(api_result["result"]) + + return api_result + + +def query_gwas_catalog( + prompt=None, + endpoint=None, + max_results=3, +): + """Query the GWAS Catalog API using natural language or a direct endpoint. + + Parameters + ---------- + prompt (str, required): Natural language query about GWAS data + endpoint (str, optional): Full API endpoint to query (e.g., "https://www.ebi.ac.uk/gwas/rest/api/studies?diseaseTraitId=EFO_0001360") + max_results (int): Maximum number of results to return + + Returns + ------- + dict: Dictionary containing the query results or error information + + Examples + -------- + - Natural language: query_gwas_catalog("Find GWAS studies related to Type 2 diabetes") + - Direct endpoint: query_gwas_catalog(endpoint="studies", params={"diseaseTraitId": "EFO_0001360"}) + + """ + # Base URL for GWAS Catalog API + base_url = "https://www.ebi.ac.uk/gwas/rest/api" + + # Ensure we have either a prompt or an endpoint + if prompt is None and endpoint is None: + return {"error": "Either a prompt or an endpoint must be provided"} + + # If using prompt, parse with Claude + if prompt: + # Load GWAS Catalog schema + schema_path = os.path.join(os.path.dirname(__file__), "schema_db", "gwas_catalog.pkl") + with open(schema_path, "rb") as f: + gwas_schema = pickle.load(f) + + # Create system prompt template + system_template = """ + You are a genomics expert specialized in using the GWAS Catalog API. + + Based on the user's natural language request, determine the appropriate GWAS Catalog API endpoint and parameters. + + GWAS CATALOG API SCHEMA: + {schema} + + Your response should be a JSON object with the following fields: + 1. "endpoint": The API endpoint to query (e.g., "studies", "associations") + 2. "params": An object containing query parameters specific to the endpoint + 3. "description": A brief description of what the query is doing + + SPECIAL NOTES: + - For disease/trait searches, consider using the "EFO" identifiers when possible + - Common endpoints include: "studies", "associations", "singleNucleotidePolymorphisms", "efoTraits" + - For pagination, use "size" and "page" parameters + - For filtering by p-value, use "pvalueMax" parameter + - GWAS Catalog uses a HAL-based REST API + + Return ONLY the JSON object with no additional text. + """ + + # Query Claude to generate the API call + llm_result = _query_llm_for_api( + prompt=prompt, + schema=gwas_schema, + system_template=system_template, + ) + + if not llm_result["success"]: + return llm_result + + # Get the endpoint and parameters from Claude's response + query_info = llm_result["data"] + endpoint = query_info.get("endpoint", "") + params = query_info.get("params", {}) + description = query_info.get("description", "") + + if not endpoint: + return { + "error": "Failed to generate a valid endpoint from the prompt", + "llm_response": llm_result.get("raw_response", "No response"), + } + else: + if endpoint is None: + endpoint = "" # Use root endpoint + params = {"size": max_results} + description = f"Direct query to {endpoint}" + + # Remove leading slash if present + if endpoint.startswith("/"): + endpoint = endpoint[1:] + + # Construct the URL + url = f"{base_url}/{endpoint}" + + # Execute the GWAS Catalog API request using the helper function + api_result = _query_rest_api(endpoint=url, method="GET", params=params, description=description) + + return api_result + + +def query_gnomad( + prompt=None, + gene_symbol=None, + verbose=True, +): + """Query gnomAD for variants in a gene using natural language or direct gene symbol. + + Parameters + ---------- + prompt (str, required): Natural language query about genetic variants + gene_symbol (str, optional): Gene symbol (e.g., "BRCA1") + verbose (bool): Whether to print verbose output + + Returns + ------- + dict: Dictionary containing the query results or error information + + Examples + -------- + - Direct gene: query_gnomad(gene_symbol="BRCA1") + - Natural language: query_gnomad(prompt="Find variants in the TP53 gene") + + """ + # Base URL for gnomAD API + base_url = "https://gnomad.broadinstitute.org/api" + + # Ensure we have either a prompt or a gene_symbol + if prompt is None and gene_symbol is None: + return {"error": "Either a prompt or a gene_symbol must be provided"} + + # If using prompt, parse with Claude + if prompt and not gene_symbol: + # Load gnomAD schema + schema_path = os.path.join(os.path.dirname(__file__), "schema_db", "gnomad.pkl") + with open(schema_path, "rb") as f: + gnomad_schema = pickle.load(f) + + # Create system prompt template + system_template = """ + You are a genomics expert specialized in using the gnomAD GraphQL API. + + Based on the user's natural language request, extract the gene symbol and relevant parameters and create the gnomAD GraphQL query. + + GnomAD GraphQL API SCHEMA: + {schema} + + Your response should be a JSON object with the following fields: + 1. "query": The complete GraphQL query string + + SPECIAL NOTES: + - The gene_symbol should be the official gene symbol (e.g., "BRCA1" not "breast cancer gene 1") + - If no reference genome is specified, default to GRCh38 + - If no dataset is specified, default to gnomad_r4 + - Return only a single gene symbol, even if multiple are mentioned + - Always escape special characters, including quotes, in the query string (eg. \" instead of ") + + + + Return ONLY the JSON object with no additional text. + """ + + # Query Claude to generate the API call + llm_result = _query_llm_for_api( + prompt=prompt, + schema=gnomad_schema, + system_template=system_template, + ) + + if not llm_result["success"]: + return llm_result + + # Get the gene symbol from Claude's response + query_info = llm_result["data"] + query_str = query_info.get("query", "") + + if not query_str: + return { + "error": "Failed to extract a valid query from the prompt", + "llm_response": llm_result.get("raw_response", "No response"), + } + else: + description = f"Query gnomAD for variants in {gene_symbol}" + # replace BRCA1 with gene_symbol + query_str = gnomad_schema.replace("BRCA1", gene_symbol) + + api_result = _query_rest_api( + endpoint=base_url, + method="POST", + json_data={"query": query_str}, + headers={"Content-Type": "application/json"}, + description=description, + ) + + if not verbose and "success" in api_result and api_result["success"] and "result" in api_result: + return _format_query_results(api_result["result"]) + + return api_result + + +def blast_sequence(sequence: str, database: str, program: str) -> dict[str, str | float] | str: + """Identifies a DNA sequence using NCBI BLAST with improved error handling, timeout management, and debugging. + + Args: + sequence (str): The sequence to identify. If DNA, use database: core_nt, program: blastn; + if protein, use database: nr, program: blastp + database (str): The BLAST database to search against + program (str): The BLAST program to use + + Returns: + dict: A dictionary containing the title, e-value, identity percentage, and coverage percentage of the best alignment + + """ + max_attempts = 1 # One initial attempt plus one retry + attempts = 0 + max_runtime = 600 # 10 minutes in seconds + + while attempts < max_attempts: + try: + attempts += 1 + query_sequence = Seq(sequence) + + # Start timer + start_time = time.time() + + # Submit BLAST job + print(f"Submitting BLAST job (attempt {attempts}/{max_attempts})...") + result_handle = NCBIWWW.qblast( + program, + database, + query_sequence, + expect=100, + word_size=7, + megablast=True, + ) + + # Parse results with timeout check + blast_records = NCBIXML.parse(result_handle) + blast_record = None + + # Try to get the first record with timeout check + while time.time() - start_time < max_runtime: + try: + # Set a short timeout for next operation + blast_record = next(blast_records) # Get first record + break # Successfully got the record + except StopIteration: + # No more records + return "No BLAST results found" + except Exception: + # Check if we've exceeded the time limit + if time.time() - start_time >= max_runtime: + if attempts < max_attempts: + print("BLAST job timeout exceeded. Resubmitting...") + break # Break to retry + else: + return "BLAST search failed after maximum attempts due to timeout" + # Brief pause before trying again + time.sleep(1) + + # Check if we timed out during record retrieval + if blast_record is None: + if attempts < max_attempts: + continue # Retry + else: + return "BLAST search failed after maximum attempts due to timeout" + + # Debug information + print(f"Number of alignments found: {len(blast_record.alignments)}") + + if blast_record.alignments: + for alignment in blast_record.alignments: + print("\nAlignment:") + print(f"hit_id: {alignment.hit_id}") + print(f"hit_def: {alignment.hit_def}") + print(f"accession: {alignment.accession}") + for hsp in alignment.hsps: + print(f"E-value: {hsp.expect}") + print(f"Score: {hsp.score}") + print(f"Identities: {hsp.identities}/{hsp.align_length}") + + return { + "hit_id": alignment.hit_id, + "hit_def": alignment.hit_def, + "accession": alignment.accession, + "e_value": hsp.expect, + "identity": (hsp.identities / float(hsp.align_length)) * 100, + "coverage": len(hsp.query) / len(sequence) * 100, + } + else: + return "No alignments found - sequence might be too short or low complexity" + + except Exception as e: + if attempts < max_attempts: + print(f"Error during BLAST search: {str(e)}. Retrying...") + time.sleep(2) # Wait briefly before retrying + else: + return f"Error during BLAST search after maximum attempts: {str(e)}" + + return "BLAST search failed after maximum attempts" + + +def query_reactome( + prompt=None, + endpoint=None, + download=False, + output_dir=None, + verbose=True, +): + """Query the Reactome database using natural language or a direct endpoint. + + Parameters + ---------- + prompt (str, required): Natural language query about biological pathways + endpoint (str, optional): Direct API endpoint or full URL + download (bool): Whether to download pathway diagrams + output_dir (str, optional): Directory to save downloaded files + verbose (bool): Whether to return detailed results + + Returns + ------- + dict: Dictionary containing the query results or error information + + Examples + -------- + - Natural language: query_reactome("Find pathways related to DNA repair") + - Direct endpoint: query_reactome(endpoint="data/pathways/R-HSA-73894") + + """ + # Base URLs for Reactome APIs + content_base_url = "https://reactome.org/ContentService" + analysis_base_url = "https://reactome.org/AnalysisService" + + # Ensure we have either a prompt or an endpoint + if prompt is None and endpoint is None: + return {"error": "Either a prompt or an endpoint must be provided"} + + # Create output directory if downloading and directory doesn't exist + if download and output_dir: + os.makedirs(output_dir, exist_ok=True) + + # If using prompt, parse with Claude + if prompt: + # Load Reactome schema + schema_path = os.path.join(os.path.dirname(__file__), "schema_db", "reactome.pkl") + with open(schema_path, "rb") as f: + reactome_schema = pickle.load(f) + + # Create system prompt template + system_template = """ + You are a bioinformatics expert specialized in using the Reactome API. + + Based on the user's natural language request, determine the appropriate Reactome API endpoint and parameters. + + REACTOME API SCHEMA: + {schema} + + Your response should be a JSON object with the following fields: + 1. "endpoint": The API endpoint to query (e.g., "data/pathways/PATHWAY_ID", "data/query/GENE_SYMBOL") + 2. "base": Which base URL to use ("content" for ContentService or "analysis" for AnalysisService) + 3. "params": An object containing query parameters specific to the endpoint + 4. "description": A brief description of what the query is doing + + SPECIAL NOTES: + - Reactome has two primary APIs: ContentService (for retrieving specific pathway data) and AnalysisService (for analyzing gene lists) + - For pathway queries, use "data/pathways/PATHWAY_ID" with the pathway stable identifier (e.g., R-HSA-73894) + - For gene queries, use "data/query/GENE" with official gene symbol (e.g., "BRCA1") + - For pathway diagrams, include "download: true" in your response if the query is for pathway visualization + - Common human pathway IDs start with "R-HSA-" + + Return ONLY the JSON object with no additional text. + """ + + # Query Claude to generate the API call + llm_result = _query_llm_for_api( + prompt=prompt, + schema=reactome_schema, + system_template=system_template, + ) + + if not llm_result["success"]: + return llm_result + + # Get the endpoint and parameters from Claude's response + query_info = llm_result["data"] + endpoint = query_info.get("endpoint", "") + base = query_info.get("base", "content") # Default to ContentService + params = query_info.get("params", {}) + description = query_info.get("description", "") + should_download = query_info.get("download", download) # Override download if specified + + if not endpoint: + return { + "error": "Failed to generate a valid endpoint from the prompt", + "llm_response": llm_result.get("raw_response", "No response"), + } + else: + # Process provided endpoint + if endpoint.startswith("http"): + # Full URL already provided + if "ContentService" in endpoint: + base = "content" + elif "AnalysisService" in endpoint: + base = "analysis" + else: + base = "content" # Default + else: + # Just endpoint provided, assume ContentService by default + base = "content" + + params = {} + description = f"Direct query to Reactome {base} API: {endpoint}" + should_download = download + + # Select base URL based on API type + base_url = content_base_url if base == "content" else analysis_base_url + + # Remove leading slash if present + if endpoint.startswith("/"): + endpoint = endpoint[1:] + + # --- ✅ FIX: Handle old 'data/query/GENE' endpoints to avoid 404 --- + if endpoint.startswith("http"): + url = endpoint + else: + if endpoint.startswith("data/query/"): + query_text = endpoint.replace("data/query/", "").strip() + url = f"{content_base_url}/search/query" + params = {"query": query_text, "species": "Homo sapiens"} + description = f"Redirected Reactome search for '{query_text}'" + else: + url = f"{base_url}/{endpoint}" + # --- ✅ END FIX --- + + # Execute the Reactome API request using the helper function + api_result = _query_rest_api(endpoint=url, method="GET", params=params, description=description) + + # Handle downloading pathway diagrams if requested + if should_download and api_result.get("success") and "result" in api_result: + result = api_result["result"] + pathway_id = None + + # Try to extract pathway ID from result + if isinstance(result, dict): + pathway_id = result.get("stId") or result.get("dbId") + + # If we have a pathway ID and output directory, download diagram + if pathway_id and output_dir: + diagram_url = f"{content_base_url}/data/pathway/{pathway_id}/diagram" + try: + diagram_response = requests.get(diagram_url) + diagram_response.raise_for_status() + + # Save diagram file + diagram_path = os.path.join(output_dir, f"{pathway_id}_diagram.png") + with open(diagram_path, "wb") as f: + f.write(diagram_response.content) + + api_result["diagram_path"] = diagram_path + except Exception as e: + api_result["diagram_error"] = f"Failed to download diagram: {str(e)}" + + if not verbose and "success" in api_result and api_result["success"] and "result" in api_result: + return _format_query_results(api_result["result"]) + + return api_result + + +def query_regulomedb( + prompt=None, + endpoint=None, + verbose=False, +): + """Query the RegulomeDB database using natural language or direct variant/coordinate specification. + + Parameters + ---------- + prompt (str, required): Natural language query about regulatory elements + endpoint (str, optional): The full endpoint to query (e.g., "https://regulomedb.org/regulome-search/?regions=chr11:5246919-5246919&genome=GRCh38") + verbose (bool): Whether to return detailed results + + Returns + ------- + dict: Dictionary containing the query results or error information + + Examples + -------- + - Natural language: query_regulomedb("Find regulatory elements for rs35675666") + - Direct variant: query_regulomedb(variant="rs35675666") + - Coordinates: query_regulomedb(coordinates="chr11:5246919-5246919") + + """ + # Base URL for RegulomeDB API + + # Ensure we have either a prompt, variant, or coordinates + if prompt is None and endpoint is None: + return {"error": "Either a prompt, variant ID, or genomic coordinates must be provided"} + + # If using prompt, parse with Claude + if prompt and not endpoint: + # Create system prompt template + system_template = """ + You are a genomics expert specialized in using the RegulomeDB API. + + Based on the user's natural language request, extract the variant ID or genomic coordinates they want to query. + + Your response should be a JSON object with ONLY ONE of the following fields: + 1. "endpoint": The API endpoint to query (e.g., "https://regulomedb.org/regulome-search/?regions=chr11:5246919-5246919&genome=GRCh38") + + + SPECIAL NOTES: + - RegulomeDB only works with human genome data + - Variant IDs should be rsIDs from dbSNP when possible. The endpoint should be in the format https://regulomedb.org/regulome-search/?regions=rsID&genome=GRCh38 + - Thumbnails for chip and chromatin should be in the format https://regulomedb.org/regulome-search?regions=chr11:5246919-5246919&genome=GRCh38/thumbnail=chip + - Coordinates should be in GRCh37/hg19 format + - For single base queries, use the same position for start and end (e.g., "chr11:5246919-5246919") + - Chromosome should be specified with "chr" prefix (e.g., "chr11" not just "11") + + Return ONLY the JSON object with no additional text. + """ + + # Query Claude to generate the API call + llm_result = _query_llm_for_api( + prompt=prompt, + schema=None, + system_template=system_template, + ) + + if not llm_result["success"]: + return llm_result + + # Get the variant or coordinates from Claude's response + query_info = llm_result["data"] + endpoint = query_info.get("endpoint", "") + + if not endpoint: + return { + "error": "Failed to extract a valid variant ID or coordinates from the prompt", + "llm_response": llm_result.get("raw_response", "No response"), + } + else: + pass + + # Construct the request URL + endpoint = endpoint + + # Execute the RegulomeDB API request using the helper function + api_result = _query_rest_api(endpoint=endpoint, method="GET", headers={"Accept": "application/json"}) + + # Format the results if not verbose and successful + if not verbose and "success" in api_result and api_result["success"] and "result" in api_result: + api_result["result"] = _format_query_results(api_result["result"]) + + return api_result + + +def query_pride( + prompt=None, + endpoint=None, + max_results=3, +): + """Query the PRIDE (PRoteomics IDEntifications) database using natural language or a direct endpoint. + + Parameters + ---------- + prompt (str, required): Natural language query about proteomics data + endpoint (str, optional): The full endpoint to query (e.g., "https://www.ebi.ac.uk/pride/ws/archive/v2/projects?keyword=breast%20cancer") + max_results (int): Maximum number of results to return + + Returns + ------- + dict: Dictionary containing the query results or error information + + Examples + -------- + - Natural language: query_pride("Find proteomics data related to breast cancer") + - Direct endpoint: query_pride(endpoint="projects", params={"keyword": "breast cancer"}) + + """ + # Base URL for PRIDE API + base_url = "https://www.ebi.ac.uk/pride/ws/archive/v2" + + # Ensure we have either a prompt or an endpoint + if prompt is None and endpoint is None: + return {"error": "Either a prompt or an endpoint must be provided"} + + # If using prompt, parse with Claude + if prompt: + # Load PRIDE schema + schema_path = os.path.join(os.path.dirname(__file__), "schema_db", "pride.pkl") + with open(schema_path, "rb") as f: + pride_schema = pickle.load(f) + + # Create system prompt template + system_template = """ + You are a proteomics expert specialized in using the PRIDE API. + + Based on the user's natural language request, determine the appropriate PRIDE API endpoint and parameters. + + PRIDE API SCHEMA: + {schema} + + Your response should be a JSON object with the following fields: + 1. "endpoint": The full url endpoint to query + 2. "description": A brief description of what the query is doing + + SPECIAL NOTES: + - PRIDE is a repository for proteomics data stored at EBI + - Common endpoints include: "projects", "assays", "files", "proteins", "peptideevidences" + - For searching projects, you can use parameters like "keyword", "species", "tissue", "disease" + - For pagination, use "page" and "pageSize" parameters + - Most results include PagingObject and FieldsObject structures + + Return ONLY the JSON object with no additional text. + """ + + # Query Claude to generate the API call + llm_result = _query_llm_for_api( + prompt=prompt, + schema=pride_schema, + system_template=system_template, + ) + + if not llm_result["success"]: + return llm_result + + # Get the endpoint and parameters from Claude's response + query_info = llm_result["data"] + endpoint = query_info.get("endpoint", "") + params = query_info.get("params", {}) + description = query_info.get("description", "") + + if not endpoint: + return { + "error": "Failed to generate a valid endpoint from the prompt", + "llm_response": llm_result.get("raw_response", "No response"), + } + else: + # Process provided endpoint + params = {"pageSize": max_results, "page": 0} + description = f"Direct query to PRIDE {endpoint}" + + # Remove leading slash if present + if endpoint.startswith("/"): + endpoint = f"{base_url}{endpoint}" + elif not endpoint.startswith("http"): + endpoint = f"{base_url}/{endpoint.lstrip('/')}" + description = "Direct query to provided endpoint" + + # Execute the PRIDE API request using the helper function + api_result = _query_rest_api(endpoint=endpoint, method="GET", params=params, description=description) + + return api_result + + +def query_gtopdb( + prompt=None, + endpoint=None, + verbose=True, +): + """Query the Guide to PHARMACOLOGY database (GtoPdb) using natural language or a direct endpoint. + + Parameters + ---------- + prompt (str, required): Natural language query about drug targets, ligands, and interactions + endpoint (str, optional): Full API endpoint to query (e.g., "https://www.guidetopharmacology.org/services/targets?type=GPCR&name=beta-2") + verbose (bool): Whether to return detailed results + + Returns + ------- + dict: Dictionary containing the query results or error information + + Examples + -------- + - Natural language: query_gtopdb("Find ligands that target the beta-2 adrenergic receptor") + - Direct endpoint: query_gtopdb(endpoint="targets", params={"type": "GPCR", "name": "beta-2"}) + + """ + # Base URL for GtoPdb API + base_url = "https://www.guidetopharmacology.org/services" + + # Ensure we have either a prompt or an endpoint + if prompt is None and endpoint is None: + return {"error": "Either a prompt or an endpoint must be provided"} + + # If using prompt, parse with Claude + if prompt: + # Load GtoPdb schema + schema_path = os.path.join(os.path.dirname(__file__), "schema_db", "gtopdb.pkl") + with open(schema_path, "rb") as f: + gtopdb_schema = pickle.load(f) + + # Create system prompt template + system_template = r""" + You are a pharmacology expert specialized in using the Guide to PHARMACOLOGY API. + + Based on the user's natural language request, determine the appropriate GtoPdb API endpoint and parameters. + + GTOPDB API SCHEMA: + {schema} + + Your response should be a JSON object with the following fields: + 1. "endpoint": The full API endpoint to query + 2. "description": A brief description of what the query is doing + + SPECIAL NOTES: + - Main endpoints include: "targets", "ligands", "interactions", "diseases", "refs" + - Target types include: "GPCR", "NHR", "LGIC", "VGIC", "OtherIC", "Enzyme", "CatalyticReceptor", "Transporter", "OtherProtein" + - Ligand types include: "Synthetic organic", "Metabolite", "Natural product", "Endogenous peptide", "Peptide", "Antibody", "Inorganic", "Approved", "Withdrawn", "Labelled", "INN" + - Interaction types include: "Activator", "Agonist", "Allosteric modulator", "Antagonist", "Antibody", "Channel blocker", "Gating inhibitor", "Inhibitor", "Subunit-specific" + - For specific target/ligand details, use formats like "targets/\{targetId\}" or "ligands/\{ligandId\}" + - For subresources, use formats like "targets/\{targetId\}/interactions" or "ligands/\{ligandId\}/structure" + + Return ONLY the JSON object with no additional text. + """ + + # Query Claude to generate the API call + llm_result = _query_llm_for_api( + prompt=prompt, + schema=gtopdb_schema, + system_template=system_template, + ) + + if not llm_result["success"]: + return llm_result + + # Get the endpoint and parameters from Claude's response + query_info = llm_result["data"] + endpoint = query_info.get("endpoint", "") + description = query_info.get("description", "") + + if not endpoint: + return { + "error": "Failed to generate a valid endpoint from the prompt", + "llm_response": llm_result.get("raw_response", "No response"), + } + else: + # Process provided endpoint + description = f"Direct query to GtoPdb {endpoint}" + + # Remove leading slash if present + if endpoint.startswith("/"): + endpoint = f"{base_url}{endpoint}" + elif not endpoint.startswith("http"): + endpoint = f"{base_url}/{endpoint.lstrip('/')}" + description = "Direct query to provided endpoint" + + # Execute the GtoPdb API request using the helper function + api_result = _query_rest_api(endpoint=endpoint, method="GET", description=description) + + # Format the results if not verbose and successful + if not verbose and "success" in api_result and api_result["success"] and "result" in api_result: + api_result["result"] = _format_query_results(api_result["result"]) + + return api_result + + +def region_to_ccre_screen(coord_chrom: str, coord_start: int, coord_end: int, assembly: str = "GRCh38") -> str: + """Given starting and ending coordinates, this function retrieves information of intersecting cCREs. + + Args: + assembly (str): Assembly of the genome, formatted like 'GRCh38'. Default is 'GRCh38'. + coord_chrom (str): Chromosome of the gene, formatted like 'chr12'. + coord_start (int): Starting chromosome coordinate. + coord_end (int): Ending chromosome coordinate. + + Returns: + str: A detailed string explaining the steps and the intersecting cCRE data or any error encountered. + + """ + steps = [] + try: + steps.append( + f"Starting cCRE data retrieval for coordinates: {coord_chrom}:{coord_start}-{coord_end} (Assembly: {assembly})." + ) + + # Build the URL and request payload + url = "https://screen-beta-api.wenglab.org/dataws/cre_table" + data = { + "assembly": assembly, + "coord_chrom": coord_chrom, + "coord_start": coord_start, + "coord_end": coord_end, + } + + steps.append("Sending POST request to API with the following data:") + steps.append(str(data)) + + # Make the request + response = requests.post(url, json=data) + + # Check if the response is successful + if not response.ok: + raise Exception(f"Request failed with status code {response.status_code}. Response: {response.text}") + + steps.append("Request executed successfully. Parsing the response...") + + # Parse the JSON response + response_json = response.json() + if "errors" in response_json: + raise Exception(f"API error: {response_json['errors']}") + + # Function to reduce and filter response data + def reduce_tokens(res_json): + # Remove unnecessary fields and round floats + res = sorted(res_json["cres"], key=lambda x: x["dnase_zscore"], reverse=True) + filtered_res = [] + + for item in res: + new_item = { + "chrom": item["chrom"], + "start": item["start"], + "len": item["len"], + "pct": item["pct"], + "ctcf_zscore": round(item["ctcf_zscore"], 2), + "dnase_zscore": round(item["dnase_zscore"], 2), + "enhancer_zscore": round(item["enhancer_zscore"], 2), + "promoter_zscore": round(item["promoter_zscore"], 2), + "accession": item["info"]["accession"], + "isproximal": item["info"]["isproximal"], + "concordance": item["info"]["concordant"], + "ctcfmax": round(item["info"]["ctcfmax"], 2), + "k4me3max": round(item["info"]["k4me3max"], 2), + "k27acmax": round(item["info"]["k27acmax"], 2), + } + filtered_res.append(new_item) + return filtered_res + + # Process the response data + filtered_data = reduce_tokens(response_json) + + if not filtered_data: + steps.append(f"No intersecting cCREs found for coordinates: {coord_chrom}:{coord_start}-{coord_end}.") + return "\n".join(steps + ["No cCRE data available for this genomic region."]) + + # Format the result into a readable string + ccre_data_string = f"Intersecting cCREs for {coord_chrom}:{coord_start}-{coord_end} (Assembly: {assembly}):\n" + for i, ccre in enumerate(filtered_data, 1): + ccre_data_string += ( + f"cCRE {i}:\n" + f" Chromosome: {ccre['chrom']}\n" + f" Start: {ccre['start']}\n" + f" Length: {ccre['len']}\n" + f" PCT: {ccre['pct']}\n" + f" CTCF Z-score: {ccre['ctcf_zscore']}\n" + f" DNase Z-score: {ccre['dnase_zscore']}\n" + f" Enhancer Z-score: {ccre['enhancer_zscore']}\n" + f" Promoter Z-score: {ccre['promoter_zscore']}\n" + f" Accession: {ccre['accession']}\n" + f" Is Proximal: {ccre['isproximal']}\n" + f" Concordance: {ccre['concordance']}\n" + f" CTCFmax: {ccre['ctcfmax']}\n" + f" K4me3max: {ccre['k4me3max']}\n" + f" K27acmax: {ccre['k27acmax']}\n\n" + ) + + steps.append(f"cCRE data successfully retrieved and formatted for {coord_chrom}:{coord_start}-{coord_end}.") + return "\n".join(steps + [ccre_data_string]) + + except Exception as e: + steps.append(f"Exception encountered: {str(e)}") + return "\n".join(steps + [f"Error: {str(e)}"]) + + +def get_genes_near_ccre(accession: str, assembly: str, chromosome: str, k: int = 10) -> str: + """Given a cCRE (Candidate cis-Regulatory Element), this function returns a string containing the + steps it performs and the k nearest genes sorted by distance. + + Parameters + ---------- + - accession (str): ENCODE Accession ID of query cCRE, e.g., EH38E1516980. + - assembly (str): Assembly of the gene, e.g., 'GRCh38'. + - chromosome (str): Chromosome of the gene, e.g., 'chr12'. + - k (int): Number of nearby genes to return, sorted by distance. Default is 10. + + Returns + ------- + - str: Steps performed and the result. + + """ + steps_log = ( + f"Starting process with accession: {accession}, assembly: {assembly}, chromosome: {chromosome}, k: {k}\n" + ) + + url = "https://screen-beta-api.wenglab.org/dataws/re_detail/nearbyGenomic" + data = {"accession": accession, "assembly": assembly, "coord_chrom": chromosome} + + steps_log += "Sending POST request to API with given data.\n" + response = requests.post(url, json=data) + + if not response.ok: + steps_log += f"API request failed with response: {response.text}\n" + return steps_log + + response_json = response.json() + + if "errors" in response_json: + steps_log += f"API returned errors: {response_json['errors']}\n" + return steps_log + + nearby_genes = response_json.get(accession, {}).get("nearby_genes", []) + if not nearby_genes: + steps_log += "No nearby genes found for the given accession.\n" + return steps_log + + steps_log += "Successfully retrieved nearby genes. Sorting them by distance.\n" + sorted_genes = sorted(nearby_genes, key=lambda x: x["distance"])[:k] + + steps_log += f"Returning the top {k} nearest genes.\n" + steps_log += "Result:\n" + + for gene in sorted_genes: + gene_name = gene.get("name", "Unknown") + distance = gene.get("distance", "N/A") + ensembl_id = gene.get("ensemblid_ver", "N/A") + start = gene.get("start", "N/A") + stop = gene.get("stop", "N/A") + chrom = gene.get("chrom", "N/A") + steps_log += f"Gene: {gene_name}, Distance: {distance}, Ensembl ID: {ensembl_id}, Chromosome: {chrom}, Start: {start}, Stop: {stop}\n" + + return steps_log + + +def query_remap( + prompt=None, + endpoint=None, + verbose=True, +): + """Query the ReMap database for regulatory elements and transcription factor binding sites. + + Parameters + ---------- + prompt (str, required): Natural language query about transcription factors and binding sites + endpoint (str, optional): Full API endpoint to query (e.g., "https://remap.univ-amu.fr/api/v1/catalogue/tf?tf=CTCF") + verbose (bool): Whether to return detailed results + + Returns + ------- + dict: Dictionary containing the query results or error information + + Examples + -------- + - Natural language: query_remap("Find CTCF binding sites in chromosome 1") + - Direct endpoint: query_remap(endpoint="catalogue/tf", params={"tf": "CTCF"}) + + """ + # Base URL for ReMap API + base_url = "https://remap.univ-amu.fr/api/v1" + + # Ensure we have either a prompt or an endpoint + if prompt is None and endpoint is None: + return {"error": "Either a prompt or an endpoint must be provided"} + + # If using prompt, parse with Claude + if prompt: + # Load ReMap schema + schema_path = os.path.join(os.path.dirname(__file__), "schema_db", "remap.pkl") + with open(schema_path, "rb") as f: + remap_schema = pickle.load(f) + + # Create system prompt template + system_template = """ + You are a genomics expert specialized in using the ReMap database API. + + Based on the user's natural language request, determine the appropriate ReMap API endpoint and parameters. + + REMAP API SCHEMA: + {schema} + + Your response should be a JSON object with the following fields: + 1. "endpoint": The full url endpoint to query + 2. "description": A brief description of what the query is doing + + SPECIAL NOTES: + - ReMap is a database of regulatory regions and transcription factor binding sites based on ChIP-seq experiments + - Common endpoints include: "catalogue/tf" (transcription factors), "catalogue/biotype" (biotypes), "browse/peaks" (binding sites) + - For searching binding sites, you can filter by transcription factor (tf), cell line, biotype, chromosome, etc. + - Genomic coordinates should be specified with "chr", "start", and "end" parameters + - For limiting results, use "limit" parameter (default is 100) + + Return ONLY the JSON object with no additional text. + """ + + # Query Claude to generate the API call + llm_result = _query_llm_for_api( + prompt=prompt, + schema=remap_schema, + system_template=system_template, + ) + + if not llm_result["success"]: + return llm_result + + # Get the endpoint and parameters from Claude's response + query_info = llm_result["data"] + endpoint = query_info.get("endpoint", "") + description = query_info.get("description", "") + + if not endpoint: + return { + "error": "Failed to generate a valid endpoint from the prompt", + "llm_response": llm_result.get("raw_response", "No response"), + } + else: + # Process provided endpoint + description = f"Direct query to ReMap {endpoint}" + + # Remove leading slash if present + if endpoint.startswith("/"): + endpoint = f"{base_url}{endpoint}" + elif not endpoint.startswith("http"): + endpoint = f"{base_url}/{endpoint.lstrip('/')}" + description = "Direct query to provided endpoint" + + # Execute the ReMap API request using the helper function + api_result = _query_rest_api(endpoint=endpoint, method="GET", description=description) + + # Format the results if not verbose and successful + if not verbose and "success" in api_result and api_result["success"] and "result" in api_result: + api_result["result"] = _format_query_results(api_result["result"]) + + return api_result + + +def query_mpd( + prompt=None, + endpoint=None, + verbose=True, +): + """Query the Mouse Phenome Database (MPD) for mouse strain phenotype data. + + Parameters + ---------- + prompt (str, required): Natural language query about mouse phenotypes, strains, or measurements + endpoint (str, optional): Full API endpoint to query (e.g., "https://phenomedoc.jax.org/MPD_API/strains") + verbose (bool): Whether to return detailed results + + Returns + ------- + dict: Dictionary containing the query results or error information + + Examples + -------- + - Natural language: query_mpd("Find phenotype data for C57BL/6J mice related to blood glucose") + - Direct endpoint: query_mpd(endpoint="strains/C57BL/6J/measures") + + """ + # Base URL for MPD API + base_url = "https://phenome.jax.org" + + # Ensure we have either a prompt or an endpoint + if prompt is None and endpoint is None: + return {"error": "Either a prompt or an endpoint must be provided"} + + # If using prompt, parse with Claude + if prompt: + # Load MPD schema + schema_path = os.path.join(os.path.dirname(__file__), "schema_db", "mpd.pkl") + with open(schema_path, "rb") as f: + mpd_schema = pickle.load(f) + + # Create system prompt template + system_template = """ + You are a mouse genetics expert specialized in using the Mouse Phenome Database (MPD) API. + + Based on the user's natural language request, determine the appropriate MPD API endpoint and parameters. + + MPD API SCHEMA: + {schema} + + Your response should be a JSON object with the following fields: + 1. "endpoint": The full url endpoint to query (e.g. https://phenome.jax.org/api/strains) + 2. "description": A brief description of what the query is doing + + SPECIAL NOTES: + - The MPD contains phenotype data for diverse strains of laboratory mice + - Common endpoints include: "strains" (mouse strains), "measures" (phenotypic measurements), "genes" (gene info) + - Use the url to construct the endpoint, not the endpoint name + - Common mouse strains include: "C57BL/6J", "DBA/2J", "BALB/cJ", "A/J", "129S1/SvImJ" + - Common phenotypic domains include: "behavior", "blood_chemistry", "body_weight", "cardiovascular", "growth", "metabolism" + + Return ONLY the JSON object with no additional text. + """ + + # Query Claude to generate the API call + llm_result = _query_llm_for_api( + prompt=prompt, + schema=mpd_schema, + system_template=system_template, + ) + + if not llm_result["success"]: + return llm_result + + # Get the endpoint and parameters from Claude's response + query_info = llm_result["data"] + endpoint = query_info.get("endpoint", "") + description = query_info.get("description", "") + + if not endpoint: + return { + "error": "Failed to generate a valid endpoint from the prompt", + "llm_response": llm_result.get("raw_response", "No response"), + } + else: + # Process provided endpoint + description = f"Direct query to MPD {endpoint}" + + # Remove leading slash if present + if endpoint.startswith("/"): + endpoint = f"{base_url}{endpoint}" + elif not endpoint.startswith("http"): + endpoint = f"{base_url}/{endpoint.lstrip('/')}" + description = "Direct query to provided endpoint" + + # Execute the MPD API request using the helper function + api_result = _query_rest_api(endpoint=endpoint, method="GET", description=description) + + # Format the results if not verbose and successful + if not verbose and "success" in api_result and api_result["success"] and "result" in api_result: + api_result["result"] = _format_query_results(api_result["result"]) + + return api_result + + +def query_emdb( + prompt=None, + endpoint=None, + verbose=True, +): + """Query the Electron Microscopy Data Bank (EMDB) for 3D macromolecular structures. + + Parameters + ---------- + prompt (str, required): Natural language query about EM structures and associated data + endpoint (str, optional): Full API endpoint to query (e.g., "https://www.ebi.ac.uk/emdb/api/search") + verbose (bool): Whether to return detailed results + + Returns + ------- + dict: Dictionary containing the query results or error information + + Examples + -------- + - Natural language: query_emdb("Find cryo-EM structures of ribosomes at resolution better than 3Å") + - Direct endpoint: query_emdb(endpoint="entry/EMD-10000") + + """ + # Base URL for EMDB API + base_url = "https://www.ebi.ac.uk/emdb/api" + + # Ensure we have either a prompt or an endpoint + if prompt is None and endpoint is None: + return {"error": "Either a prompt or an endpoint must be provided"} + + # If using prompt, parse with Claude + if prompt: + # Load EMDB schema + schema_path = os.path.join(os.path.dirname(__file__), "schema_db", "emdb.pkl") + with open(schema_path, "rb") as f: + emdb_schema = pickle.load(f) + + # Create system prompt template + system_template = """ + You are a structural biology expert specialized in using the Electron Microscopy Data Bank (EMDB) API. + + Based on the user's natural language request, determine the appropriate EMDB API endpoint and parameters. + + EMDB API SCHEMA: + {schema} + + Your response should be a JSON object with the following fields: + 1. "endpoint": The API endpoint to query (e.g., "search", "entry/EMD-XXXXX") + 2. "params": An object containing query parameters specific to the endpoint + 3. "description": A brief description of what the query is doing + + SPECIAL NOTES: + - EMDB contains 3D macromolecular structures determined by electron microscopy + - Common endpoints include: "search" (search for entries), "entry/EMD-XXXXX" (specific entry details) + - For searching, you can filter by resolution, specimen, authors, release date, etc. + - Resolution filters should be specified with "resolution_low" and "resolution_high" parameters + - For specific entry retrieval, use the format "entry/EMD-XXXXX" where XXXXX is the EMDB ID + - Common specimen types include: "ribosome", "virus", "membrane protein", "filament" + + Return ONLY the JSON object with no additional text. + """ + + # Query Claude to generate the API call + llm_result = _query_llm_for_api( + prompt=prompt, + schema=emdb_schema, + system_template=system_template, + ) + + if not llm_result["success"]: + return llm_result + + # Get the endpoint and parameters from Claude's response + query_info = llm_result["data"] + endpoint = query_info.get("endpoint", "") + params = query_info.get("params", {}) + description = query_info.get("description", "") + + if not endpoint: + return { + "error": "Failed to generate a valid endpoint from the prompt", + "llm_response": llm_result.get("raw_response", "No response"), + } + else: + # Process provided endpoint + params = {} + description = f"Direct query to EMDB {endpoint}" + + # Remove leading slash if present + if endpoint.startswith("/"): + endpoint = f"{base_url}{endpoint}" + elif not endpoint.startswith("http"): + endpoint = f"{base_url}/{endpoint.lstrip('/')}" + description = "Direct query to provided endpoint" + + # Execute the EMDB API request using the helper function + api_result = _query_rest_api(endpoint=endpoint, method="GET", params=params, description=description) + + # Format the results if not verbose and successful + if not verbose and "success" in api_result and api_result["success"] and "result" in api_result: + api_result["result"] = _format_query_results(api_result["result"]) + + return api_result + + +def query_synapse( + prompt: str | None = None, + query_term: str | list[str] | None = None, + return_fields: list[str] | None = None, + max_results: int = 20, + query_type: str = "dataset", + verbose: bool = True, +): + """Query Synapse REST API for biomedical datasets and files. + + Synapse is a platform for sharing and analyzing biomedical data, particularly + genomics and clinical research datasets. Supports optional authentication via + SYNAPSE_AUTH_TOKEN environment variable for access to private datasets. + + Parameters + ---------- + prompt : str, optional + Natural language query about biomedical data (e.g., "Find drug screening datasets") + query_term : str or list of str, optional + Specific search terms for Synapse search. When multiple terms are provided + as a list, they are combined with AND logic (more terms = more restrictive). Start with 1-2 most relevant search terms. + return_fields : list of str, optional + Fields to return in results. Default: ["name", "node_type", "description"] + max_results : int, default 20 + Maximum number of results to return. Default 20 is optimal for most searches. + Use up to 50 if extensive results are desired for comprehensive analysis. + query_type : str, default "dataset" + Type of entity to search for ("dataset", "file", "folder") + verbose : bool, default True + Whether to return full API response or formatted results + + Returns + ------- + dict + Dictionary containing query information and Synapse API results + + Notes + ----- + Authentication is optional but recommended for access to private datasets. + Set SYNAPSE_AUTH_TOKEN environment variable with your Synapse personal access token + to enable authenticated requests. + + Examples + -------- + # Natural language + query_synapse(prompt="Find drug screening datasets") + + # Direct search (AND logic - finds datasets with both "cancer" AND "genomics") + query_synapse(query_term=["cancer", "genomics"], max_results=10) + + # Extensive search + query_synapse(query_term="alzheimer", max_results=50) + + """ + base_url = "https://repo-prod.prod.sagebase.org" + + # Default return fields + if return_fields is None: + return_fields = ["name", "node_type", "description"] + + # Check for optional authentication + headers = {"Content-Type": "application/json"} + synapse_token = os.environ.get("SYNAPSE_AUTH_TOKEN") + if synapse_token: + headers["Authorization"] = f"Bearer {synapse_token}" + + # If natural language prompt provided, convert to search terms + if prompt and not query_term: + system_template = ( + "You extract search terms from natural language queries for biomedical data search.\n" + "Return ONLY a JSON object with this structure, where query_term combines search terms using AND for each entry:\n" + '{"query_term": ["term1", "term2"], "query_type": "dataset", "max_results": 20}.\n' + "query_type should be 'dataset' for datasets, 'file' for data files, or 'folder' for collections.\n" + "max_results should be 20 for typical searches, or up to 50 if extensive/comprehensive results are desired.\n" + "Use 1-2 most relevant search terms (these are combined with AND; more terms = more restrictive). Only include main term (disease, gene, etc.) of the search query and do not include any other terms/adjectives/modifiers. Do not include explanations.\n" + "Try to remove hyphens and other special characters from the search terms. For example, use RNAseq instead of RNA-seq." + ) + + llm_result = _query_llm_for_api( + prompt=prompt, + schema=None, + system_template=system_template, + ) + + if llm_result.get("success"): + mapping = llm_result["data"] or {} + query_term = mapping.get("query_term", []) + query_type = mapping.get("query_type", query_type) + max_results = mapping.get("max_results", max_results) + + # Build search request + search_url = f"{base_url}/repo/v1/search" + + # Ensure query_term is a list + if isinstance(query_term, str): + query_term = [query_term] + elif query_term is None: + query_term = [""] + + # Build search payload + search_payload = { + "queryTerm": query_term, + "returnFields": return_fields, + "start": 0, + "size": max_results, + "booleanQuery": [{"key": "node_type", "value": query_type}], + } + + description = f"Synapse search for terms: {query_term} (query type: {query_type})" + + # Execute search + api_result = _query_rest_api( + endpoint=search_url, + method="POST", + json_data=search_payload, + headers=headers, + description=description, + ) + + # Augment results with access control information + if api_result.get("success") and "result" in api_result: + result_data = api_result["result"] + if isinstance(result_data, dict) and "hits" in result_data: + for hit in result_data["hits"]: + if "id" in hit: + # Check access requirements for this entity + access_url = f"{base_url}/repo/v1/entity/{hit['id']}/accessRequirement" + access_result = _query_rest_api( + endpoint=access_url, + method="GET", + headers=headers, + description=f"Check access requirements for {hit['id']}", + ) + + # Add access_restricted property based on access requirements + if access_result.get("success") and "result" in access_result: + access_data = access_result["result"] + total_requirements = access_data.get("totalNumberOfResults", 0) + hit["access_restricted"] = total_requirements > 0 + else: + # If we can't check access, assume it might be restricted + hit["access_restricted"] = True + + # Format results if not verbose and successful + if not verbose and api_result.get("success") and "result" in api_result: + api_result["result"] = _format_query_results(api_result["result"]) + + return api_result + + +def query_pubchem( + prompt=None, + endpoint=None, + max_results=5, + verbose=True, +): + """Query the PubChem PUG-REST API using natural language or a direct endpoint. + Parameters + ---------- + prompt (str, required): Natural language query about chemical compounds + endpoint (str, optional): Direct PubChem API endpoint to query + max_results (int): Maximum number of results to return + verbose (bool): Whether to return detailed results + Returns + ------- + dict: Dictionary containing the query results or error information + Examples + -------- + - Natural language: query_pubchem("Find molecular weight of aspirin") + - Direct endpoint: query_pubchem(endpoint="compound/cid/2244/property/MolecularWeight/txt") + """ + # Base URL for PubChem API + base_url = "https://pubchem.ncbi.nlm.nih.gov/rest/pug" + + # Ensure we have either a prompt or an endpoint + if prompt is None and endpoint is None: + return {"error": "Either a prompt or an endpoint must be provided"} + + # If using prompt, parse with Claude + if prompt: + # Load PubChem schema + schema_path = os.path.join(os.path.dirname(__file__), "schema_db", "pubchem.pkl") + with open(schema_path, "rb") as f: + pubchem_schema = pickle.load(f) + + # Create system prompt template + system_template = """ + You are a chemistry expert specialized in using the PubChem PUG-REST API. + Based on the user's natural language request, determine the appropriate PubChem API endpoint and parameters. + PUBCHEM API SCHEMA: + {schema} + Your response should be a JSON object with the following fields: + 1. "full_url": The complete URL to query (including base URL and parameters) + 2. "description": A brief description of what the query is doing + SPECIAL NOTES: + - Base URL is "https://pubchem.ncbi.nlm.nih.gov/rest/pug" + - Common operations: property, synonyms, record, xrefs + - For properties, use CSV format for multiple properties, TXT for single property + - For images, use PNG format with optional image_size parameter + - Rate limit: maximum 5 requests per second + - Use compound/name/ for chemical names, compound/cid/ for PubChem IDs + Return ONLY the JSON object with no additional text. + """ + + # Query Claude to generate the API call + llm_result = _query_llm_for_api( + prompt=prompt, + schema=pubchem_schema, + system_template=system_template, + ) + + if not llm_result["success"]: + return llm_result + + # Get the full URL from Claude's response + query_info = llm_result["data"] + endpoint = query_info.get("full_url", "") + description = query_info.get("description", "") + + if not endpoint: + return { + "error": "Failed to generate a valid endpoint from the prompt", + "llm_response": llm_result.get("raw_response", "No response"), + } + else: + # Use provided endpoint directly + if endpoint is not None: + if endpoint.startswith("/"): + endpoint = f"{base_url}{endpoint}" + elif not endpoint.startswith("http"): + endpoint = f"{base_url}/{endpoint.lstrip('/')}" + description = "Direct query to provided endpoint" + + # Rate limiting: allow user to configure or disable; only sleep if last request was too recent + if not hasattr(query_pubchem, "_last_request_time"): + query_pubchem._last_request_time = 0 + min_interval = 1.0 / 5 # 5 requests per second by default + now = time.time() + elapsed = now - query_pubchem._last_request_time + if elapsed < min_interval: + time.sleep(min_interval - elapsed) + query_pubchem._last_request_time = time.time() + + # Use the common REST API helper function + api_result = _query_rest_api(endpoint=endpoint, method="GET", description=description) + + if not verbose and "success" in api_result and api_result["success"] and "result" in api_result: + api_result["result"] = _format_query_results(api_result["result"]) + + return api_result + + +def query_chembl( + prompt=None, + endpoint=None, + chembl_id=None, + smiles=None, + molecule_name=None, + max_results=20, + verbose=True, +): + """Query the ChEMBL REST API using natural language, direct endpoint, or specific identifiers. + Parameters + ---------- + prompt (str, optional): Natural language query about bioactivity data + endpoint (str, optional): Direct ChEMBL API endpoint to query + chembl_id (str, optional): Specific ChEMBL ID to query (e.g., 'CHEMBL25') + smiles (str, optional): SMILES string for similarity/substructure search + molecule_name (str, optional): Molecule name for lookup + max_results (int): Maximum number of results to return + verbose (bool): Whether to return detailed results + Returns + ------- + dict: Dictionary containing the query results or error information + Examples + -------- + - Natural language: query_chembl("Find approved drugs with kinase activity") + - Direct endpoint: query_chembl(endpoint="molecule?max_phase=4") + - ChEMBL ID: query_chembl(chembl_id="CHEMBL25") + - SMILES similarity: query_chembl(smiles="CC(=O)OC1=CC=CC=C1C(=O)O", similarity_cutoff=80) + - Molecule name: query_chembl(molecule_name="aspirin") + """ + # Base URL for ChEMBL API + base_url = "https://www.ebi.ac.uk/chembl/api/data" + + # Handle specific identifier parameters first (most reliable) + if chembl_id: + endpoint = f"{base_url}/molecule/{chembl_id}.json" + description = f"Direct lookup for ChEMBL ID: {chembl_id} (most reliable method)" + elif smiles: + endpoint = f"{base_url}/similarity/{smiles}/80.json" # Default similarity cutoff + description = f"Similarity search for SMILES: {smiles} with 80% cutoff" + elif molecule_name: + endpoint = f"{base_url}/molecule/search.json?q={molecule_name}&limit={max_results}" + description = f"Search for molecule with name containing: {molecule_name}" + elif prompt: + # Try LLM-based parsing with fallback + try: + # Load ChEMBL schema + schema_path = os.path.join(os.path.dirname(__file__), "schema_db", "chembl.pkl") + with open(schema_path, "rb") as f: + chembl_schema = pickle.load(f) + + # Create system prompt template + system_template = """ + You are a bioactivity data expert specialized in using the ChEMBL REST API. + Based on the user's natural language request, determine the appropriate ChEMBL API endpoint and parameters. + CHEMBL API SCHEMA: + {schema} + Your response should be a JSON object with the following fields: + 1. "full_url": The complete URL to query (including base URL and parameters) + 2. "description": A brief description of what the query is doing + SPECIAL NOTES: + - Base URL is "https://www.ebi.ac.uk/chembl/api/data" + + # IMPORTANT ENDPOINTS: + - Molecule search: /molecule/search.json?q={search_term} (full-text search) + - Molecule by ID: /molecule/{chembl_id}.json (direct lookup) + - Image: /image/{chembl_id}.svg or /molecule/{chembl_id}.svg + - Substructure: /substructure/{smiles}.json (valid SMILES required) + - Similarity: /similarity/{smiles}/{cutoff}.json (cutoff 70-90 typical) + + # BIOACTIVITY DATA: + - Activities: /activity.json?molecule_chembl_id={chembl_id}&limit=20 + - Assays: /assay.json?molecule_chembl_id={chembl_id}&limit=20 + - Use only= parameter to reduce fields: &only=target_chembl_id,standard_type,standard_value + + # DRUG METADATA: + - Drug info: /drug.json?molecule_chembl_id={chembl_id} (use parent ID) + - Indications: /drug_indication.json?molecule_chembl_id={chembl_id} + - Mechanisms: /mechanism.json?molecule_chembl_id={chembl_id} + - ATC: /atc_class.json?molecule_chembl_id={chembl_id} + + # COMMON FILTERS: + - max_phase=4 (approved drugs) + - assay_type=B (binding), F (functional), A (ADMET) + - standard_type=IC50, Ki, EC50 + - pchembl_value__gte=5 (activity threshold) + + # FORMAT NOTES: + - Add .json for JSON output (default is XML) + - Use /search.json for full-text search (not ?search=) + - Use parent ChEMBL IDs for drug endpoints + - Use raw SMILES (don't double-encode) + Return ONLY the JSON object with no additional text. + """ + + # Query LLM to generate the API call + llm_result = _query_llm_for_api( + prompt=prompt, + schema=chembl_schema, + system_template=system_template, + ) + + if llm_result["success"]: + # Get the full URL from LLM's response + query_info = llm_result["data"] + endpoint = query_info.get("full_url", "") + description = query_info.get("description", "") + + if endpoint: + # Successfully got endpoint from LLM + pass + else: + raise Exception("No endpoint generated from LLM") + else: + raise Exception(f"LLM failed: {llm_result.get('error', 'Unknown error')}") + + except Exception: + # Fall back to generic endpoint mapping for common query types + prompt_lower = prompt.lower() + + # Extract potential molecule names or keywords from the prompt + words = prompt.split() + potential_molecule = None + + # Look for common molecule indicators - skip common words and look for longer, more specific terms + common_words = { + "find", + "search", + "get", + "show", + "list", + "target", + "targets", + "binding", + "for", + "the", + "a", + "an", + "and", + "or", + "with", + "using", + "via", + "through", + "from", + "in", + "on", + "at", + "to", + "of", + "by", + } + + for word in words: + word_lower = word.lower() + # Skip common words and look for longer, more specific terms that could be molecule names + if ( + len(word) > 4 + and word.isalpha() + and word_lower not in common_words + and not word_lower.startswith("che") # Skip words starting with common prefixes + and not word_lower.endswith("ing") + ): # Skip gerunds + potential_molecule = word + break + + if "binding" in prompt_lower and "target" in prompt_lower: + # Try to find binding targets - use molecule if found, otherwise generic + if potential_molecule: + endpoint = f"{base_url}/molecule/search.json?q={potential_molecule}&limit={max_results}" + description = f"Search for {potential_molecule} binding targets in ChEMBL database" + else: + endpoint = f"{base_url}/activity.json?standard_type=IC50&limit={max_results}" + description = "Search for binding activities with IC50 values" + elif "molecule" in prompt_lower or "compound" in prompt_lower or "drug" in prompt_lower: + # Molecule search + if potential_molecule: + endpoint = f"{base_url}/molecule/search.json?q={potential_molecule}&limit={max_results}" + description = f"Search for molecule {potential_molecule} in ChEMBL database" + else: + endpoint = f"{base_url}/molecule/search.json?q=molecule&limit={max_results}" + description = "Search for molecules in ChEMBL database" + elif "activity" in prompt_lower or "bioactivity" in prompt_lower: + # Bioactivity search + endpoint = f"{base_url}/activity.json?limit={max_results}" + description = "Search for bioactivity data in ChEMBL database" + elif "assay" in prompt_lower: + # Assay search + endpoint = f"{base_url}/assay.json?limit={max_results}" + description = "Search for assay data in ChEMBL database" + elif "target" in prompt_lower: + # Target search + endpoint = f"{base_url}/target.json?limit={max_results}" + description = "Search for target data in ChEMBL database" + elif "image" in prompt_lower: + # Image search + if potential_molecule: + endpoint = f"{base_url}/molecule/search.json?q={potential_molecule}&limit={max_results}" + description = f"Search for {potential_molecule} images in ChEMBL database" + else: + endpoint = f"{base_url}/molecule/search.json?q=molecule&limit={max_results}" + description = "Search for molecule images in ChEMBL database" + else: + # Generic search - use first meaningful word or fallback + if potential_molecule: + endpoint = f"{base_url}/molecule/search.json?q={potential_molecule}&limit={max_results}" + description = f"Generic search for {potential_molecule} in ChEMBL database" + else: + endpoint = f"{base_url}/molecule/search.json?q=molecule&limit={max_results}" + description = f"Generic search in ChEMBL database for: {prompt[:50]}..." + elif endpoint: + # Use provided endpoint directly + if endpoint.startswith("/"): + endpoint = f"{base_url}{endpoint}" + elif not endpoint.startswith("http"): + endpoint = f"{base_url}/{endpoint.lstrip('/')}" + description = "Direct query to provided endpoint" + else: + # No valid parameters provided + return { + "success": False, + "error": "No query parameters provided. Use prompt, endpoint, chembl_id, smiles, or molecule_name.", + } + + # Add pagination if not already specified + if "?" in endpoint: + if "limit=" not in endpoint: + endpoint += f"&limit={max_results}" + else: + endpoint += f"?limit={max_results}" + + # Use the common REST API helper function + api_result = _query_rest_api(endpoint=endpoint, method="GET", description=description) + + if not verbose and "success" in api_result and api_result["success"] and "result" in api_result: + api_result["result"] = _format_query_results(api_result["result"]) + + return api_result + + +def query_unichem( + prompt=None, + endpoint=None, + verbose=True, +): + """Query the UniChem 2.0 REST API using natural language or a direct endpoint. + Parameters + ---------- + prompt (str, optional): Natural language query about chemical cross-references + endpoint (str, optional): Direct UniChem API endpoint to query + verbose (bool): Whether to return detailed results + Returns + ------- + dict: Dictionary containing the query results or error information + Examples + -------- + - Natural language: query_unichem("Find cross-references for aspirin") + - Direct endpoint: query_unichem(endpoint="/compounds") + - Compound search: query_unichem(endpoint="/compounds", data={"type": "inchikey", "compound": "LMXNVOREDXZICN-WDSOQIARSA-N"}) + - Connectivity search: query_unichem(endpoint="/connectivity", data={"type": "inchi", "compound": "InChI=1S/C7H8N4O2/c1-10-5-4(8-3-9-5)6(12)11(2)7(10)13/h3H,1-2H3,(H,8,9)", "searchComponents": True}) + - Get sources: query_unichem(endpoint="/sources") + """ + # Base URL for UniChem API (corrected from beta to production) + base_url = "https://www.ebi.ac.uk/unichem/api/v1" + + # Ensure we have either a prompt or an endpoint + if prompt is None and endpoint is None: + return {"error": "Either a prompt or an endpoint must be provided"} + + # If using prompt, parse with Claude + if prompt: + # Load UniChem schema + schema_path = os.path.join(os.path.dirname(__file__), "schema_db", "unichem.pkl") + with open(schema_path, "rb") as f: + unichem_schema = pickle.load(f) + + # Create system prompt template + system_template = """ + You are a chemical cross-reference expert specialized in using the UniChem 2.0 REST API. + Based on the user's natural language request, determine the appropriate UniChem API endpoint and parameters. + UNICHEM API SCHEMA: + {schema} + Your response should be a JSON object with the following fields: + 1. "endpoint": The API endpoint to use (e.g., "/compounds", "/sources", "/connectivity") + 2. "method": HTTP method ("GET" or "POST") + 3. "data": POST data if method is POST (null for GET requests) + 4. "description": A brief description of what the query is doing + SPECIAL NOTES: + - Base URL is "https://www.ebi.ac.uk/unichem/api/v1" + - Compound searches use POST method to /compounds endpoint + - Connectivity searches use POST method to /connectivity endpoint + - Source information uses GET method to /sources endpoint + - Valid identifier types: uci, inchi, inchikey, sourceID + - For compound/connectivity searches, include type and compound (or sourceID if type is sourceID) + - For connectivity searches, can include searchComponents boolean parameter + - Common source IDs: 1=ChEMBL, 2=DrugBank, 5=PubChem, 7=ChEBI + Return ONLY the JSON object with no additional text. + """ + + # Query Claude to generate the API call + llm_result = _query_llm_for_api( + prompt=prompt, + schema=unichem_schema, + system_template=system_template, + ) + + if not llm_result["success"]: + return llm_result + + # Get the API call details from Claude's response + query_info = llm_result["data"] + endpoint = query_info.get("endpoint", "") + method = query_info.get("method", "GET") + data = query_info.get("data", None) + description = query_info.get("description", "") + + if not endpoint: + return { + "error": "Failed to generate a valid endpoint from the prompt", + "llm_response": llm_result.get("raw_response", "No response"), + } + + # Construct full URL + if endpoint.startswith("/"): + full_url = f"{base_url}{endpoint}" + else: + full_url = f"{base_url}/{endpoint.lstrip('/')}" + + else: + # Use provided endpoint directly + if endpoint is None: + return {"error": "Endpoint cannot be None when prompt is not provided"} + + if endpoint.startswith("/"): + full_url = f"{base_url}{endpoint}" + elif not endpoint.startswith("http"): + full_url = f"{base_url}/{endpoint.lstrip('/')}" + else: + full_url = endpoint + method = "GET" # Default method for direct endpoints + data = None + description = "Direct query to provided endpoint" + + # Use the common REST API helper function + api_result = _query_rest_api(endpoint=full_url, method=method, json_data=data, description=description) + + if not verbose and "success" in api_result and api_result["success"] and "result" in api_result: + api_result["result"] = _format_query_results(api_result["result"]) + + return api_result + + +def query_clinicaltrials( + prompt=None, + endpoint=None, + max_results=10, + verbose=True, +): + """Query the ClinicalTrials.gov API v2 using natural language or a direct endpoint. + Parameters + ---------- + prompt (str, required): Natural language query about clinical trials + endpoint (str, optional): Direct ClinicalTrials.gov API endpoint to query + max_results (int): Maximum number of results to return + verbose (bool): Whether to return detailed results + Returns + ------- + dict: Dictionary containing the query results or error information + Examples + -------- + - Natural language: query_clinicaltrials("Find recruiting cancer trials") + - Direct endpoint: query_clinicaltrials(endpoint="/studies?query.cond=cancer&filter.overallStatus=RECRUITING") + """ + # Base URL for ClinicalTrials.gov API + base_url = "https://clinicaltrials.gov/api/v2" + + # Ensure we have either a prompt or an endpoint + if prompt is None and endpoint is None: + return {"error": "Either a prompt or an endpoint must be provided"} + + # If using prompt, parse with Claude + if prompt: + # Load ClinicalTrials.gov schema + schema_path = os.path.join(os.path.dirname(__file__), "schema_db", "clinicaltrials.pkl") + with open(schema_path, "rb") as f: + clinicaltrials_schema = pickle.load(f) + + # Create system prompt template + system_template = """ + You are a clinical research expert specialized in using the ClinicalTrials.gov API v2. + Based on the user's natural language request, determine the appropriate ClinicalTrials.gov API endpoint and parameters. + CLINICALTRIALS.GOV API SCHEMA: + {schema} + Your response should be a JSON object with the following fields: + 1. "full_url": The complete URL to query (including base URL and parameters) + 2. "description": A brief description of what the query is doing + SPECIAL NOTES: + - Base URL is "https://clinicaltrials.gov/api/v2" + - Main endpoint is /studies for searching clinical trials + - Use query.cond for conditions/diseases, query.intr for interventions + - Use filter.overallStatus for study status (RECRUITING, COMPLETED, etc.) + - Use filter.phase for study phases (PHASE1, PHASE2, PHASE3, PHASE4) + - Use filter.studyType for study types (INTERVENTIONAL, OBSERVATIONAL) + - Use pageSize parameter to limit results (max 1000) + - For specific studies, use /studies/{{nctId}} + + CORRECT PHASE FILTERING: + - Use filter.phase=PHASE1, PHASE2, PHASE3, PHASE4 (comma-separated for multiple phases) + - Do NOT use filter.phase=PHASE3 (single value with equals) + - Example: filter.phase=PHASE1,PHASE2 for early phase trials + Return ONLY the JSON object with no additional text. + """ + + # Query Claude to generate the API call + llm_result = _query_llm_for_api( + prompt=prompt, + schema=clinicaltrials_schema, + system_template=system_template, + ) + + if not llm_result["success"]: + return llm_result + + # Get the full URL from Claude's response + query_info = llm_result["data"] + endpoint = query_info.get("full_url", "") + description = query_info.get("description", "") + + if not endpoint: + return { + "error": "Failed to generate a valid endpoint from the prompt", + "llm_response": llm_result.get("raw_response", "No response"), + } + else: + # Use provided endpoint directly + if endpoint is not None: + if endpoint.startswith("/"): + endpoint = f"{base_url}{endpoint}" + elif not endpoint.startswith("http"): + endpoint = f"{base_url}/{endpoint.lstrip('/')}" + description = "Direct query to provided endpoint" + + # Add pageSize if not already specified and not a specific study lookup + if "/studies/" not in endpoint and "pageSize=" not in endpoint: + separator = "&" if "?" in endpoint else "?" + endpoint += f"{separator}pageSize={max_results}" + + # Use the common REST API helper function + api_result = _query_rest_api(endpoint=endpoint, method="GET", description=description) + + # Handle API parameter errors with fallback for ClinicalTrials.gov + if not api_result.get("success", False) and "400" in str(api_result.get("error", "")): + # Try simplified query without problematic filters + if "filter.phase" in endpoint: + simplified_endpoint = endpoint.replace("&filter.phase=PHASE3", "").replace("filter.phase=PHASE3&", "") + if simplified_endpoint != endpoint: + api_result = _query_rest_api( + endpoint=simplified_endpoint, method="GET", description=f"{description} (simplified)" + ) + if api_result.get("success", False): + api_result["note"] = "Query simplified due to API parameter restrictions" + + if not verbose and "success" in api_result and api_result["success"] and "result" in api_result: + api_result["result"] = _format_query_results(api_result["result"]) + + return api_result + + +def query_dailymed( + prompt=None, + endpoint=None, + format="json", + verbose=True, +): + """Query the DailyMed RESTful API using natural language or a direct endpoint. + Parameters + ---------- + prompt (str, optional): Natural language query about drug labeling information + endpoint (str, optional): Direct DailyMed API endpoint to query + format (str): Response format ('json' or 'xml') + verbose (bool): Whether to return detailed results + Returns + ------- + dict: Dictionary containing the query results or error information + Examples + -------- + - Natural language: query_dailymed("Find all drug names") + - Direct endpoint: query_dailymed(endpoint="/drugnames.json") + - Get specific SPL: query_dailymed(endpoint="/spls/12345678-1234-1234-1234-123456789012.json") + - Get SPL history: query_dailymed(endpoint="/spls/12345678-1234-1234-1234-123456789012/history.json") + """ + # Base URL for DailyMed API + base_url = "https://dailymed.nlm.nih.gov/dailymed/services/v2" + + # Ensure we have either a prompt or an endpoint + if prompt is None and endpoint is None: + return {"error": "Either a prompt or an endpoint must be provided"} + + # Validate format + if format not in ["json", "xml"]: + format = "json" + + # If using prompt, parse with Claude + if prompt: + # Load DailyMed schema + schema_path = os.path.join(os.path.dirname(__file__), "schema_db", "dailymed.pkl") + with open(schema_path, "rb") as f: + dailymed_schema = pickle.load(f) + + # Create system prompt template + system_template = """ + You are a pharmaceutical labeling expert specialized in using the DailyMed RESTful API. + Based on the user's natural language request, determine the appropriate DailyMed API endpoint and parameters. + DAILYMED API SCHEMA: + {schema} + Your response should be a JSON object with the following fields: + 1. "full_url": The complete URL to query (including base URL and format extension) + 2. "description": A brief description of what the query is doing + SPECIAL NOTES: + - Base URL is "https://dailymed.nlm.nih.gov/dailymed/services/v2" + - Available resources: applicationnumbers, drugclasses, drugnames, ndcs, rxcuis, spls, uniis + - For specific SPL documents, use /spls/{{SETID}} format + - For SPL-related data, use /spls/{{SETID}}/history, /spls/{{SETID}}/media, /spls/{{SETID}}/ndcs, /spls/{{SETID}}/packaging + - Always append format extension (.json or .xml) + - API only supports GET method + - HTTPS is required (HTTP disabled since 2016) + - Each resource may have optional query parameters to filter or control output + Return ONLY the JSON object with no additional text. + """ + + # Query Claude to generate the API call + llm_result = _query_llm_for_api( + prompt=prompt, + schema=dailymed_schema, + system_template=system_template, + ) + + if not llm_result["success"]: + return llm_result + + # Get the full URL from Claude's response + query_info = llm_result["data"] + endpoint = query_info.get("full_url", "") + description = query_info.get("description", "") + + if not endpoint: + return { + "error": "Failed to generate a valid endpoint from the prompt", + "llm_response": llm_result.get("raw_response", "No response"), + } + else: + # Use provided endpoint directly + if endpoint is not None: + if endpoint.startswith("/"): + endpoint = f"{base_url}{endpoint}" + elif not endpoint.startswith("http"): + endpoint = f"{base_url}/{endpoint.lstrip('/')}" + description = "Direct query to provided endpoint" + + # Add format extension if not present + if not endpoint.endswith(f".{format}") and not endpoint.endswith(".json") and not endpoint.endswith(".xml"): + endpoint += f".{format}" + + # Use the common REST API helper function + api_result = _query_rest_api(endpoint=endpoint, method="GET", description=description) + + if not verbose and "success" in api_result and api_result["success"] and "result" in api_result: + api_result["result"] = _format_query_results(api_result["result"]) + + return api_result + + +def query_quickgo( + prompt=None, + endpoint=None, + max_results=25, + verbose=True, +): + """Query the QuickGO API using natural language or a direct endpoint. + Parameters + ---------- + prompt (str, optional): Natural language query about Gene Ontology terms, annotations, or gene products + endpoint (str, optional): Direct QuickGO API endpoint to query + max_results (int): Maximum number of results to return (max 100) + verbose (bool): Whether to return detailed results + Returns + ------- + dict: Dictionary containing the query results or error information + Examples + -------- + - Natural language: query_quickgo("Find GO terms related to apoptosis") + - Direct endpoint: query_quickgo(endpoint="/ontology/go/search?query=apoptosis&limit=10") + - Get specific term: query_quickgo(endpoint="/ontology/go/terms/GO:0006915") + """ + # Base URL for QuickGO API (corrected from documentation) + base_url = "https://www.ebi.ac.uk/QuickGO/services" + + # Ensure we have either a prompt or an endpoint + if prompt is None and endpoint is None: + return {"error": "Either a prompt or an endpoint must be provided"} + + # Validate max_results + if max_results > 100: + import warnings + + warnings.warn( + f"max_results ({max_results}) exceeds QuickGO API limit (100). Setting max_results to 100.", stacklevel=2 + ) + max_results = 100 + + # If using prompt, parse with Claude + if prompt: + # Load QuickGO schema + schema_path = os.path.join(os.path.dirname(__file__), "schema_db", "quickgo.pkl") + with open(schema_path, "rb") as f: + quickgo_schema = pickle.load(f) + + # Create system prompt template + system_template = """ + You are a Gene Ontology expert specialized in using the QuickGO REST API. + Based on the user's natural language request, determine the appropriate QuickGO API endpoint and parameters. + QUICKGO API SCHEMA: + {schema} + Your response should be a JSON object with the following fields: + 1. "full_url": The complete URL to query (including base URL and parameters) + 2. "description": A brief description of what the query is doing + SPECIAL NOTES: + - Base URL is "https://www.ebi.ac.uk/QuickGO/services" + - Main services: /ontology (GO/ECO terms), /annotation (GO annotations), /geneproduct (gene products) + - For GO term search, use /ontology/go/search with query parameter + - For specific GO terms, use /ontology/go/terms/{{go_id}} + - For GO term relationships, use /ontology/go/terms/{{go_id}}/children, /descendants, /ancestors + - For annotations, use /annotation/search with various filters + - For gene products, use /geneproduct/search + - Use limit parameter to control results (max 100) + - Common organisms: 9606 (human), 10090 (mouse), 7227 (fly) + - GO aspects: biological_process, molecular_function, cellular_component + - Evidence codes: IEA, IDA, IPI, IMP, IGI, etc. + - Qualifiers: enables, involved_in, is_active_in, part_of, etc. + Return ONLY the JSON object with no additional text. + """ + + # Query Claude to generate the API call + llm_result = _query_llm_for_api( + prompt=prompt, + schema=quickgo_schema, + system_template=system_template, + ) + + if not llm_result["success"]: + return llm_result + + # Get the full URL from Claude's response + query_info = llm_result["data"] + endpoint = query_info.get("full_url", "") + description = query_info.get("description", "") + + if not endpoint: + return { + "error": "Failed to generate a valid endpoint from the prompt", + "llm_response": llm_result.get("raw_response", "No response"), + } + else: + # Use provided endpoint directly + if endpoint is not None: + if endpoint.startswith("/"): + endpoint = f"{base_url}{endpoint}" + elif not endpoint.startswith("http"): + endpoint = f"{base_url}/{endpoint.lstrip('/')}" + description = "Direct query to provided endpoint" + + # Add limit parameter if not already specified + if "limit=" not in endpoint and "/terms/" not in endpoint: + separator = "&" if "?" in endpoint else "?" + endpoint += f"{separator}limit={max_results}" + + # Use the common REST API helper function + api_result = _query_rest_api(endpoint=endpoint, method="GET", description=description) + + if not verbose and "success" in api_result and api_result["success"] and "result" in api_result: + api_result["result"] = _format_query_results(api_result["result"]) + + return api_result + + +def query_encode( + prompt=None, + endpoint=None, + max_results=25, + verbose=True, +): + """Query the ENCODE Portal API to help users locate functional genomics data. + This function is designed to help users find and explore ENCODE data including: + - Experiments (ChIP-seq, RNA-seq, ATAC-seq, DNase-seq, WGBS, etc.) + - Files (BAM, BED, bigWig, fastq, etc.) + - Biosamples (cell lines, tissues, primary cells) + - Datasets and replicates + Parameters + ---------- + prompt (str, required): Natural language query about functional genomics data you want to find + endpoint (str, optional): Direct ENCODE Portal API endpoint to query + max_results (int): Maximum number of results to return (use "all" for all results) + verbose (bool): Whether to return detailed results + Returns + ------- + dict: Dictionary containing the query results with data location information + Examples + -------- + - Find experiments: query_encode("Find ChIP-seq experiments for CTCF in human K562 cells") + - Find files: query_encode("Find BAM files from ATAC-seq experiments in mouse brain") + - Find biosamples: query_encode("Find human primary T cells from blood") + - Find datasets: query_encode("Find RNA-seq datasets from human liver tissue") + - Direct endpoint: query_encode(endpoint="/search/?type=Experiment&assay_title=ChIP-seq&format=json") + """ + # Base URL for ENCODE Portal API + base_url = "https://www.encodeproject.org" + + # Ensure we have either a prompt or an endpoint + if prompt is None and endpoint is None: + return {"error": "Either a prompt or an endpoint must be provided"} + + # If using prompt, parse with Claude + if prompt: + # Load ENCODE schema + schema_path = os.path.join(os.path.dirname(__file__), "schema_db", "encode.pkl") + with open(schema_path, "rb") as f: + encode_schema = pickle.load(f) + + # Create system prompt template + system_template = """ + You are a functional genomics expert specialized in helping users locate data in the ENCODE Portal. + Your goal is to help users find the specific functional genomics data they need. Based on the user's request, + determine the most appropriate ENCODE Portal API endpoint and parameters to locate their data. + ENCODE PORTAL API SCHEMA: + {schema} + Your response should be a JSON object with the following fields: + 1. "full_url": The complete URL to query (including base URL and parameters) + 2. "description": A clear description of what data the query will help locate + 3. "data_type": The type of data being searched (Experiment, File, Biosample, etc.) + 4. "search_strategy": Brief explanation of the search approach used + CRITICAL RULES FOR SIMPLE, EFFECTIVE QUERIES: + 1. KEEP QUERIES SIMPLE - use only 1-3 parameters maximum for better results + 2. Start with basic searches and let users refine based on results + 3. Use searchTerm for text-based searches (most reliable for complex terms) + 4. Avoid complex nested property paths when possible + 5. For organism filtering, use simple organism names: "Homo sapiens", "Mus musculus" + SIMPLE QUERY PATTERNS (PREFERRED): + - Basic experiment search: /search/?type=Experiment&assay_title=ChIP-seq&format=json + - Text-based search: /search/?searchTerm=CTCF&format=json + - File type search: /search/?type=File&file_format=bam&format=json + - Biosample search: /search/?type=Biosample&format=json + - Dataset search: /search/?type=Dataset&format=json + COMMON ASSAY TYPES (choose ONE per query): + - ChIP-seq, RNA-seq, ATAC-seq, DNase-seq, WGBS, Hi-C, CAGE, ChIA-PET + COMMON FILE FORMATS: + - bam, fastq, bigWig, bigBed, bed, narrowPeak, broadPeak + SIMPLE EXAMPLES: + - Find ChIP-seq experiments: /search/?type=Experiment&assay_title=ChIP-seq&format=json + - Find CTCF data: /search/?searchTerm=CTCF&format=json + - Find BAM files: /search/?type=File&file_format=bam&format=json + - Find human experiments: /search/?type=Experiment&searchTerm=human&format=json + - Find mouse brain data: /search/?type=Experiment&searchTerm=mouse%20brain&format=json + IMPORTANT: Return ONLY a valid JSON object with no additional text, code comments, or explanations. + The response must be parseable JSON starting with {{ and ending with }}. + """ + + # Query Claude to generate the API call + llm_result = _query_llm_for_api( + prompt=prompt, + schema=encode_schema, + system_template=system_template, + ) + + if not llm_result["success"]: + return llm_result + + # Get the full URL from Claude's response + query_info = llm_result["data"] + endpoint = query_info.get("full_url", "") + description = query_info.get("description", "") + + if not endpoint: + return { + "error": "Failed to generate a valid endpoint from the prompt", + "llm_response": llm_result.get("raw_response", "No response"), + } + else: + # Use provided endpoint directly + if endpoint is not None: + if endpoint.startswith("/"): + endpoint = f"{base_url}{endpoint}" + elif not endpoint.startswith("http"): + endpoint = f"{base_url}/{endpoint.lstrip('/')}" + description = "Direct query to provided endpoint" + + # Ensure format=json is included for API access + if "format=json" not in endpoint and "/search/" in endpoint: + separator = "&" if "?" in endpoint else "?" + endpoint += f"{separator}format=json" + + # Add limit parameter if not already specified and it's a search endpoint + if "/search/" in endpoint and "limit=" not in endpoint: + separator = "&" if "?" in endpoint else "?" + limit_value = "all" if max_results == "all" or max_results > 100 else max_results + endpoint += f"{separator}limit={limit_value}" + + # Use the common REST API helper function + api_result = _query_rest_api(endpoint=endpoint, method="GET", description=description) + + # Add data location information to the result + if api_result.get("success", False): + # Extract data_type and search_strategy from the query_info if available + data_type = query_info.get("data_type", "Unknown") if "query_info" in locals() else "Unknown" + search_strategy = ( + query_info.get("search_strategy", "Direct query") if "query_info" in locals() else "Direct query" + ) + + api_result["data_type"] = data_type + api_result["search_strategy"] = search_strategy + api_result["data_location_info"] = { + "description": description, + "data_type": data_type, + "search_strategy": search_strategy, + "endpoint_used": endpoint, + } + + # Handle API parameter errors with fallback for ENCODE + if not api_result.get("success", False) and "404" in str(api_result.get("error", "")): + # Try simplified query with basic search + if prompt and "transcription factor" in prompt.lower(): + simplified_endpoint = f"{base_url}/search/?type=Experiment&assay_title=ChIP-seq&searchTerm=transcription%20factor&format=json&limit={max_results}" + api_result = _query_rest_api( + endpoint=simplified_endpoint, method="GET", description=f"{description} (simplified)" + ) + if api_result.get("success", False): + api_result["note"] = "Query simplified due to API endpoint restrictions" + + if not verbose and "success" in api_result and api_result["success"] and "result" in api_result: + api_result["result"] = _format_query_results(api_result["result"]) + + return api_result diff --git a/BioScientist/agent_system/engines/v1_executor_backup/tool/genetics.py b/BioScientist/agent_system/engines/v1_executor_backup/tool/genetics.py new file mode 100644 index 0000000000000000000000000000000000000000..790a6b88694cf65329038f809e0fc8b9f9cfb805 --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/tool/genetics.py @@ -0,0 +1,1672 @@ +def liftover_coordinates( + chromosome: str, + position: int, + input_format: str, + output_format: str, + data_path: str, +) -> str: + """Perform liftover of genomic coordinates between hg19 and hg38 formats with detailed intermediate steps. + + Args: + chromosome (str): Chromosome number (e.g., '1', 'X'). + position (int): Genomic position. + input_format (str): Input genome build ('hg19' or 'hg38'). + output_format (str): Output genome build ('hg19' or 'hg38'). + data_path (str): Path to liftover chain files. + + Returns: + str: A detailed string explaining the steps and the final result or any error encountered. + + """ + from pyliftover import LiftOver + + steps = [] + + try: + steps.append( + f"Starting liftover process for chromosome {chromosome}, position {position} from {input_format} to {output_format}." + ) + + # Load the liftover chain files + steps.append("Loading liftover chain files...") + hg19_to_hg38_liftover = LiftOver(data_path + "/liftover/hg19ToHg38.over.chain.gz") + hg38_to_hg19_liftover = LiftOver(data_path + "/liftover/hg38ToHg19.over.chain.gz") + steps.append("Liftover chain files loaded successfully.") + + # Choose the appropriate LiftOver object + if input_format == "hg19" and output_format == "hg38": + lo = hg19_to_hg38_liftover + steps.append("Selected liftover chain: hg19 to hg38.") + elif input_format == "hg38" and output_format == "hg19": + lo = hg38_to_hg19_liftover + steps.append("Selected liftover chain: hg38 to hg19.") + else: + steps.append("Error: Unsupported format conversion.") + return "\n".join( + steps + + ["Error: Unsupported format conversion. Supported formats are 'hg19' to 'hg38' or 'hg38' to 'hg19'."] + ) + + # Perform the liftover conversion + steps.append(f"Performing liftover for chr{chromosome}, position {position}...") + lifted_coordinates = lo.convert_coordinate(f"chr{chromosome}", position) + + if lifted_coordinates: + result = ( + f"Successfully lifted coordinates from {input_format} to {output_format}.\n" + f"Original: chr{chromosome}, position {position}.\n" + f"Lifted: chromosome {lifted_coordinates[0][0]}, position {lifted_coordinates[0][1]}, strand {lifted_coordinates[0][2]}." + ) + steps.append(result) + return "\n".join(steps) + else: + steps.append("Error: Liftover failed. No coordinates found for the given input.") + return "\n".join(steps + ["Error: Liftover failed. No coordinates found."]) + + except Exception as e: + steps.append(f"Exception encountered: {str(e)}") + return "\n".join(steps) + + +import os +from datetime import datetime + +import numpy as np +import pandas as pd +import torch +from torch import nn, optim + + +def bayesian_finemapping_with_deep_vi( + gwas_summary_path, + ld_matrix, + n_iterations=5000, + learning_rate=0.01, + hidden_dim=64, + credible_threshold=0.95, +): + """Performs Bayesian fine-mapping from GWAS summary statistics using deep variational inference. + + This function implements a deep neural network-based variational inference approach to compute + posterior inclusion probabilities (PIPs) and credible sets for putative causal variants from + GWAS summary statistics and linkage disequilibrium (LD) information. + + Parameters + ---------- + gwas_summary_path : str + Path to CSV or TSV file containing GWAS summary statistics. Expected columns: + - 'variant_id': Identifier for each variant + - 'effect_size': Effect size (beta) for each variant + - 'pvalue': P-value for each variant + - 'se': Standard error for each variant (optional) + + ld_matrix : numpy.ndarray + Linkage disequilibrium matrix with pairwise correlations between variants. + + n_iterations : int, optional + Number of training iterations for the variational inference algorithm. + Default is 5000. + + learning_rate : float, optional + Learning rate for the optimization algorithm. Default is 0.01. + + hidden_dim : int, optional + Hidden dimension size for the neural network. Default is 64. + + credible_threshold : float, optional + Threshold for defining the credible set (e.g., 0.95 for a 95% credible set). + Default is 0.95. + + Returns + ------- + str + A detailed research log of the fine-mapping analysis including: + - Number of variants analyzed + - Top variants ranked by posterior inclusion probability + - Credible set variants + - Visualizations of the posterior distributions + + """ + import matplotlib.pyplot as plt + import pandas as pd + + # Initialize the research log + log = [] + log.append( + f"# Bayesian Fine-mapping Analysis with Deep Variational Inference - {datetime.now().strftime('%Y-%m-%d %H:%M')}" + ) + log.append("\n## Data Preprocessing") + + # Load data from file + try: + if gwas_summary_path.endswith(".csv"): + gwas_summary = pd.read_csv(gwas_summary_path) + elif gwas_summary_path.endswith((".tsv", ".txt")): + gwas_summary = pd.read_csv(gwas_summary_path, sep="\t") + else: + log.append("Error: Unsupported file format. Please provide a CSV or TSV file.") + return "\n".join(log) + log.append(f"Successfully loaded GWAS summary data from {gwas_summary_path}") + except Exception as e: + log.append(f"Error loading GWAS summary data: {str(e)}") + return "\n".join(log) + + # Check input data + if gwas_summary is None: + log.append("Error: Failed to load GWAS summary data.") + return "\n".join(log) + + if ld_matrix is None: + log.append("Error: LD matrix is required for fine-mapping analysis.") + return "\n".join(log) + + n_variants = len(gwas_summary) + log.append(f"Analyzing {n_variants} genetic variants") + + # Check if LD matrix dimensions match the number of variants + if ld_matrix.shape[0] != n_variants or ld_matrix.shape[1] != n_variants: + log.append(f"Error: LD matrix dimensions ({ld_matrix.shape}) do not match number of variants ({n_variants})") + return "\n".join(log) + + # Prepare data for analysis + log.append("\nPreparing data for analysis...") + + # Compute Z-scores if not already present + if "z_score" not in gwas_summary.columns: + log.append("Computing Z-scores from effect sizes and standard errors...") + if "se" in gwas_summary.columns: + gwas_summary["z_score"] = gwas_summary["effect_size"] / gwas_summary["se"] + else: + # Approximate Z-scores from p-values + log.append("Standard errors not available, approximating Z-scores from p-values...") + # Convert p-values to Z-scores (two-sided test) + from scipy.stats import norm + + gwas_summary["z_score"] = ( + gwas_summary["effect_size"].abs() + / gwas_summary["effect_size"] + * norm.ppf(1 - gwas_summary["pvalue"] / 2) + ) + + # Convert data to tensors + z_scores = torch.FloatTensor(gwas_summary["z_score"].values) + ld_tensor = torch.FloatTensor(ld_matrix) + + log.append(f"Processed {len(z_scores)} z-scores from GWAS summary") + log.append("LD matrix shape: " + str(ld_matrix.shape)) + + # Define the variational inference model + class VariationalFineMapping(nn.Module): + def __init__(self, n_variants, hidden_dim): + super().__init__() + self.encoder = nn.Sequential( + nn.Linear(n_variants, hidden_dim), + nn.ReLU(), + nn.Linear(hidden_dim, hidden_dim), + nn.ReLU(), + ) + # Output log alpha parameters for the Bernoulli variables + self.log_alpha = nn.Linear(hidden_dim, n_variants) + + def forward(self, x): + h = self.encoder(x) + log_alpha = self.log_alpha(h) + # Apply sigmoid to get inclusion probabilities + return torch.sigmoid(log_alpha) + + def elbo_loss(self, z_scores, ld_matrix, pips, n_samples=10): + # Sample from approximate posterior + samples = torch.bernoulli(pips.unsqueeze(0).repeat(n_samples, 1)) + + # Prior term (sparsity prior) + prior_term = -0.01 * torch.sum(pips) + + # Likelihood term + likelihood_term = 0 + for s in samples: + # Compute expected z-scores under the model + expected_z = torch.matmul(ld_matrix, s * z_scores) + # Compute likelihood + likelihood_term += -torch.sum((z_scores - expected_z) ** 2) + + likelihood_term /= n_samples + + return -(prior_term + likelihood_term) + + # Initialize model, optimizer and training + log.append("\n## Initializing deep variational inference model") + model = VariationalFineMapping(n_variants, hidden_dim) + optimizer = optim.Adam(model.parameters(), lr=learning_rate) + + # Training loop + log.append("\n## Training variational inference model") + losses = [] + + for i in range(n_iterations): + optimizer.zero_grad() + pips = model(z_scores) + loss = model.elbo_loss(z_scores, ld_tensor, pips) + loss.backward() + optimizer.step() + + losses.append(loss.item()) + + if (i + 1) % (n_iterations // 5) == 0: + log.append(f" Iteration {i + 1}/{n_iterations}, Loss: {loss.item():.4f}") + + # Get final posterior inclusion probabilities + with torch.no_grad(): + final_pips = model(z_scores).numpy() + + # Create DataFrame with results + results_df = gwas_summary.copy() + results_df["pip"] = final_pips + results_df = results_df.sort_values("pip", ascending=False) + + # Generate credible sets + log.append("\n## Generating credible sets") + + # Sort variants by PIP + sorted_variants = results_df.sort_values("pip", ascending=False) + + # Calculate cumulative sum of PIPs + sorted_variants["cumulative_pip"] = sorted_variants["pip"].cumsum() + + # Identify variants in the credible set + credible_set = sorted_variants[sorted_variants["cumulative_pip"] <= credible_threshold] + + if len(credible_set) == 0: + # If no variants meet the threshold, include at least the top variant + credible_set = sorted_variants.iloc[:1] + + log.append(f"Identified {len(credible_set)} variants in the {credible_threshold * 100}% credible set") + + # Save results to files + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + results_file = f"finemapping_results_{timestamp}.csv" + credible_set_file = f"credible_set_{timestamp}.csv" + + results_df.to_csv(results_file, index=False) + credible_set.to_csv(credible_set_file, index=False) + + log.append(f"\nFull results saved to: {results_file}") + log.append(f"Credible set saved to: {credible_set_file}") + + # Summary of top variants + log.append("\n## Top variants by posterior inclusion probability (PIP)") + for i, (_, row) in enumerate(results_df.head(10).iterrows()): + log.append(f" {i + 1}. Variant: {row['variant_id']}, PIP: {row['pip']:.4f}, P-value: {row['pvalue']:.2e}") + + log.append("\n## Variants in the credible set") + for i, (_, row) in enumerate(credible_set.iterrows()): + log.append(f" {i + 1}. Variant: {row['variant_id']}, PIP: {row['pip']:.4f}") + + # Create a simple visualization + try: + plt.figure(figsize=(10, 6)) + plt.bar(range(len(results_df[:50])), results_df["pip"][:50]) + plt.xlabel("Variant index (sorted by PIP)") + plt.ylabel("Posterior Inclusion Probability") + plt.title("Top 50 variants by PIP") + plot_file = f"pip_plot_{timestamp}.png" + plt.savefig(plot_file) + plt.close() + log.append(f"\nPlot of PIPs saved to: {plot_file}") + except Exception as e: + log.append(f"\nCould not create visualization: {str(e)}") + + log.append("\n## Analysis complete") + + return "\n".join(log) + + +def analyze_cas9_mutation_outcomes( + reference_sequences, + edited_sequences, + cell_line_info=None, + output_prefix="cas9_mutation_analysis", +): + """Analyzes and categorizes mutations induced by Cas9 at target sites. + + Parameters + ---------- + reference_sequences : dict + Dictionary mapping sequence IDs to reference DNA sequences (strings) + edited_sequences : dict of dict + Nested dictionary: {sequence_id: {read_id: sequence}} + Contains the edited/mutated sequences for each reference + cell_line_info : dict, optional + Dictionary mapping sequence IDs to cell line information (e.g., wildtype, knockout gene) + output_prefix : str, optional + Prefix for output files + + Returns + ------- + str + Research log summarizing the analysis steps and results + + """ + from collections import defaultdict + + from Bio import pairwise2 + + # Initialize results storage + results = [] + mutation_counts = defaultdict(lambda: defaultdict(int)) + + # Define mutation categories + categories = { + "no_mutation": "No mutation detected", + "short_deletion": "Short deletion (1-10 bp)", + "medium_deletion": "Medium deletion (11-30 bp)", + "long_deletion": "Long deletion (>30 bp)", + "single_insertion": "Single base insertion", + "longer_insertion": "Longer insertion (>1 bp)", + "indel": "Insertion and deletion", + } + + log = "# Cas9-Induced Mutation Outcome Analysis\n\n" + log += "## Analysis Steps:\n\n" + log += "1. Loading and processing sequence data\n" + log += f"2. Analyzing {len(reference_sequences)} target sites\n" + + # Process each reference sequence and its edited versions + for seq_id, ref_seq in reference_sequences.items(): + cell_line = cell_line_info.get(seq_id, "Unknown") if cell_line_info else "Unknown" + log += f"\n### Processing target site: {seq_id} (Cell line: {cell_line})\n" + + site_results = [] + site_mutation_counts = defaultdict(int) + total_reads = len(edited_sequences.get(seq_id, {})) + + if total_reads == 0: + log += f"No edited sequences found for {seq_id}\n" + continue + + log += f"Analyzing {total_reads} sequence reads...\n" + + # Process each edited sequence for this reference + for read_id, edited_seq in edited_sequences.get(seq_id, {}).items(): + # Perform sequence alignment + alignments = pairwise2.align.globalms(ref_seq, edited_seq, 2, -1, -2, -0.5) + + if not alignments: + log += f"Warning: Could not align read {read_id}\n" + continue + + best_alignment = alignments[0] + ref_aligned, edited_aligned, score, start, end = best_alignment + + # Analyze mutations + deletions = [] + insertions = [] + del_count = 0 + ins_count = 0 + + i, j = 0, 0 + while i < len(ref_aligned) and j < len(edited_aligned): + if ref_aligned[i] == "-": # Insertion in edited sequence + ins_start = j + while i < len(ref_aligned) and ref_aligned[i] == "-": + i += 1 + j += 1 + insertions.append((ins_start, j - ins_start)) + ins_count += j - ins_start + elif edited_aligned[j] == "-": # Deletion in edited sequence + del_start = i + while j < len(edited_aligned) and edited_aligned[j] == "-": + i += 1 + j += 1 + deletions.append((del_start, i - del_start)) + del_count += i - del_start + else: + i += 1 + j += 1 + + # Categorize mutation + mutation_type = "no_mutation" + if del_count > 0 and ins_count > 0: + mutation_type = "indel" + elif del_count > 0: + if del_count <= 10: + mutation_type = "short_deletion" + elif del_count <= 30: + mutation_type = "medium_deletion" + else: + mutation_type = "long_deletion" + elif ins_count > 0: + mutation_type = "single_insertion" if ins_count == 1 else "longer_insertion" + + # Add to results + site_results.append( + { + "sequence_id": seq_id, + "read_id": read_id, + "cell_line": cell_line, + "mutation_type": mutation_type, + "deletion_count": del_count, + "insertion_count": ins_count, + } + ) + + site_mutation_counts[mutation_type] += 1 + mutation_counts[cell_line][mutation_type] += 1 + + # Calculate percentages for this site + log += "\nMutation distribution for this target site:\n" + for mut_type, count in site_mutation_counts.items(): + percentage = (count / total_reads) * 100 + log += f"- {categories[mut_type]}: {count} reads ({percentage:.1f}%)\n" + + # Add site results to overall results + results.extend(site_results) + + # Create results dataframe and save to CSV + results_df = pd.DataFrame(results) + output_file = f"{output_prefix}_detailed_results.csv" + results_df.to_csv(output_file, index=False) + + # Create summary dataframe + summary_data = [] + for cell_line, mut_counts in mutation_counts.items(): + total = sum(mut_counts.values()) + for mut_type, count in mut_counts.items(): + percentage = (count / total) * 100 if total > 0 else 0 + summary_data.append( + { + "cell_line": cell_line, + "mutation_type": mut_type, + "count": count, + "percentage": percentage, + } + ) + + summary_df = pd.DataFrame(summary_data) + summary_file = f"{output_prefix}_summary.csv" + summary_df.to_csv(summary_file, index=False) + + # Add summary to log + log += "\n## Overall Results Summary\n\n" + log += f"Total sequences analyzed: {len(results)}\n" + log += f"Detailed results saved to: {output_file}\n" + log += f"Summary results saved to: {summary_file}\n\n" + + if cell_line_info: + log += "### Mutation Distribution by Cell Line\n\n" + for cell_line, mut_counts in mutation_counts.items(): + total = sum(mut_counts.values()) + if total == 0: + continue + + log += f"#### {cell_line}\n" + for mut_type, count in sorted(mut_counts.items(), key=lambda x: x[1], reverse=True): + percentage = (count / total) * 100 + log += f"- {categories[mut_type]}: {count} ({percentage:.1f}%)\n" + log += "\n" + + return log + + +def analyze_crispr_genome_editing(original_sequence, edited_sequence, guide_rna, repair_template=None): + """Analyzes CRISPR-Cas9 genome editing results by comparing original and edited sequences. + + Parameters + ---------- + original_sequence : str + The original DNA sequence before CRISPR-Cas9 editing + edited_sequence : str + The DNA sequence after CRISPR-Cas9 editing + guide_rna : str + The CRISPR guide RNA (crRNA) sequence used for targeting + repair_template : str, optional + The homology-directed repair template sequence, if used + + Returns + ------- + str + A research log summarizing the CRISPR-Cas9 editing analysis, including identified + mutations and characterization of the edited loci + + """ + import datetime + + from Bio import pairwise2 + from Bio.Seq import Seq + + log = [] + log.append(f"CRISPR-Cas9 Genome Editing Analysis - {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + log.append("=" * 70) + + # Step 1: Find the target site in the original sequence + log.append("\n1. Identifying target site in original sequence") + target_site = original_sequence.find(guide_rna) + if target_site == -1: + # Try with the reverse complement + guide_rna_seq = Seq(guide_rna) + rev_comp_guide = str(guide_rna_seq.reverse_complement()) + target_site = original_sequence.find(rev_comp_guide) + if target_site != -1: + log.append(f" - Target site found at position {target_site} (using reverse complement of guide RNA)") + guide_rna = rev_comp_guide + else: + log.append(" - Warning: Guide RNA sequence not found in original sequence") + target_site = None + else: + log.append(f" - Target site found at position {target_site}") + + # Step 2: Align sequences to identify mutations + log.append("\n2. Aligning original and edited sequences to identify mutations") + alignments = pairwise2.align.globalms(original_sequence, edited_sequence, 2, -1, -2, -0.5) + best_alignment = alignments[0] + + # Extract aligned sequences + aligned_orig = best_alignment[0] + aligned_edit = best_alignment[1] + + # Find mutations + mutations = [] + indels = [] + + for i in range(len(aligned_orig)): + if aligned_orig[i] != aligned_edit[i]: + orig_base = aligned_orig[i] + edit_base = aligned_edit[i] + + # Skip gaps in counting the actual position + actual_pos = len(aligned_orig[:i].replace("-", "")) + + if orig_base == "-": # Insertion + indels.append(f"Insertion of {edit_base} at position {actual_pos}") + elif edit_base == "-": # Deletion + indels.append(f"Deletion of {orig_base} at position {actual_pos}") + else: # Substitution + mutations.append(f"{orig_base}→{edit_base} at position {actual_pos}") + + # Log mutations + if mutations: + log.append(" - Substitutions detected:") + for mutation in mutations: + log.append(f" * {mutation}") + else: + log.append(" - No substitutions detected") + + if indels: + log.append(" - Insertions/Deletions detected:") + for indel in indels: + log.append(f" * {indel}") + else: + log.append(" - No insertions or deletions detected") + + # Step 3: Check if editing occurred near the target site + if target_site is not None: + log.append("\n3. Analyzing mutations relative to target site") + target_end = target_site + len(guide_rna) + target_region = range(target_site - 3, target_end + 3) # Include some buffer + + on_target_edits = [m for m in mutations + indels if any(str(pos) in m for pos in target_region)] + + if on_target_edits: + log.append(" - On-target edits detected near guide RNA binding site:") + for edit in on_target_edits: + log.append(f" * {edit}") + else: + log.append(" - No edits detected near guide RNA binding site") + + # Step 4: Check for homology-directed repair if template was provided + if repair_template: + log.append("\n4. Checking for homology-directed repair template incorporation") + # Look for unique sequence markers from the repair template + template_len = len(repair_template) + marker_size = min(10, template_len // 3) + marker = repair_template[template_len // 2 - marker_size // 2 : template_len // 2 + marker_size // 2] + + if marker in edited_sequence and marker not in original_sequence: + log.append(f" - Repair template marker '{marker}' found in edited sequence") + log.append(" - Homology-directed repair likely successful") + else: + log.append(" - No clear evidence of repair template incorporation") + log.append(" - Editing likely resulted from non-homologous end joining (NHEJ)") + + # Step 5: Overall assessment + log.append("\n5. Overall assessment") + if mutations or indels: + log.append(" - CRISPR-Cas9 editing appears successful") + if target_site is not None and any( + str(pos) in "".join(mutations + indels) for pos in range(target_site, target_site + len(guide_rna)) + ): + log.append(" - Edits occurred at the intended target site") + else: + log.append(" - Edits may have occurred outside the intended target site") + else: + log.append(" - No significant editing detected, CRISPR-Cas9 may not have been effective") + + return "\n".join(log) + + +def simulate_demographic_history( + num_samples=10, + sequence_length=100000, + recombination_rate=1e-8, + mutation_rate=1e-8, + demographic_model="constant", + demographic_params=None, + coalescent_model="kingman", + beta_coalescent_param=None, + random_seed=None, + output_file="simulated_sequences.vcf", +): + """Simulate DNA sequences with specified demographic and coalescent histories using msprime. + + Parameters + ---------- + num_samples : int + Number of sample sequences to simulate + sequence_length : int + Length of the simulated sequence in base pairs + recombination_rate : float + Per-base recombination rate + mutation_rate : float + Per-base mutation rate + demographic_model : str + Type of demographic model to simulate. Options: + - "constant": Constant population size + - "bottleneck": Population bottleneck + - "expansion": Population expansion + - "contraction": Population contraction + - "sawtooth": Sawtooth pattern of population size changes + demographic_params : dict + Parameters specific to the chosen demographic model. Supported formats:: + + - For "constant": {"N": population size} + - For "bottleneck": { + "N_initial": initial pop size, + "N_bottleneck": bottleneck pop size, + "T_bottleneck": time of bottleneck (generations ago), + "T_recovery": time of recovery (generations ago) + } + - For "expansion": {"N_initial": initial pop size, "N_final": final pop size, "T_expansion": time of expansion (generations ago)} + - For "contraction": {"N_initial": initial pop size, "N_final": final pop size, "T_contraction": time of contraction (generations ago)} + - For "sawtooth": {"N_values": list of population sizes, "times": list of times for changes} + coalescent_model : str + Type of coalescent model to use. Options: + - "kingman": Standard Kingman coalescent + - "beta": Beta-coalescent model + beta_coalescent_param : float + Parameter for beta-coalescent model (required if coalescent_model="beta") + random_seed : int + Seed for random number generator (for reproducibility) + output_file : str + Filename to save the simulated sequences (VCF format) + + Returns + ------- + str + Research log summarizing the simulation parameters and results + + """ + import time + + import msprime + + start_time = time.time() + log = [] + log.append("Demographic History Simulation using msprime") + log.append("=============================================") + log.append("Parameters:") + log.append(f" - Number of samples: {num_samples}") + log.append(f" - Sequence length: {sequence_length} bp") + log.append(f" - Recombination rate: {recombination_rate}") + log.append(f" - Mutation rate: {mutation_rate}") + log.append(f" - Demographic model: {demographic_model}") + log.append(f" - Coalescent model: {coalescent_model}") + + # Set up demographic model + if demographic_params is None: + demographic_params = {"N": 10000} # Default to constant population of 10000 + + demography = msprime.Demography() + demography.add_population(name="pop0", initial_size=1000) # Default initial population + + if demographic_model == "constant": + N = demographic_params.get("N", 10000) + demography.add_population(name="pop0", initial_size=N) + log.append(f" - Constant population size: N = {N}") + + elif demographic_model == "bottleneck": + N_initial = demographic_params.get("N_initial", 10000) + N_bottleneck = demographic_params.get("N_bottleneck", 1000) + T_bottleneck = demographic_params.get("T_bottleneck", 1000) + T_recovery = demographic_params.get("T_recovery", 500) + + demography = msprime.Demography() + demography.add_population(name="pop0", initial_size=N_initial) + demography.add_population_parameters_change(time=T_recovery, initial_size=N_bottleneck) + demography.add_population_parameters_change(time=T_bottleneck, initial_size=N_initial) + + log.append(" - Bottleneck model:") + log.append(f" * Initial population size: {N_initial}") + log.append(f" * Bottleneck population size: {N_bottleneck}") + log.append(f" * Bottleneck time (generations ago): {T_bottleneck}") + log.append(f" * Recovery time (generations ago): {T_recovery}") + + elif demographic_model == "expansion": + N_initial = demographic_params.get("N_initial", 1000) + N_final = demographic_params.get("N_final", 10000) + T_expansion = demographic_params.get("T_expansion", 1000) + + demography = msprime.Demography() + demography.add_population(name="pop0", initial_size=N_final) + demography.add_population_parameters_change(time=T_expansion, initial_size=N_initial) + + log.append(" - Expansion model:") + log.append(f" * Initial population size: {N_initial}") + log.append(f" * Final population size: {N_final}") + log.append(f" * Expansion time (generations ago): {T_expansion}") + + elif demographic_model == "contraction": + N_initial = demographic_params.get("N_initial", 10000) + N_final = demographic_params.get("N_final", 1000) + T_contraction = demographic_params.get("T_contraction", 1000) + + demography = msprime.Demography() + demography.add_population(name="pop0", initial_size=N_final) + demography.add_population_parameters_change(time=T_contraction, initial_size=N_initial) + + log.append(" - Contraction model:") + log.append(f" * Initial population size: {N_initial}") + log.append(f" * Final population size: {N_final}") + log.append(f" * Contraction time (generations ago): {T_contraction}") + + elif demographic_model == "sawtooth": + N_values = demographic_params.get("N_values", [10000, 5000, 15000, 7500]) + times = demographic_params.get("times", [500, 1000, 1500]) + + if len(N_values) != len(times) + 1: + raise ValueError("For sawtooth model, N_values should have one more element than times") + + demography = msprime.Demography() + demography.add_population(name="pop0", initial_size=N_values[0]) + + for i, time in enumerate(times): + demography.add_population_parameters_change(time=time, initial_size=N_values[i + 1]) + + log.append(" - Sawtooth model:") + log.append(f" * Population sizes: {N_values}") + log.append(f" * Change times (generations ago): {times}") + + else: + raise ValueError(f"Unknown demographic model: {demographic_model}") + + # Set up coalescent model + model = None + if coalescent_model == "kingman": + model = msprime.StandardCoalescent() + log.append(" - Using standard Kingman coalescent") + elif coalescent_model == "beta": + if beta_coalescent_param is None: + beta_coalescent_param = 1.5 + model = msprime.BetaCoalescent(alpha=beta_coalescent_param) + log.append(f" - Using Beta-coalescent with alpha = {beta_coalescent_param}") + else: + raise ValueError(f"Unknown coalescent model: {coalescent_model}") + + # Run simulation + log.append("\nRunning simulation...") + + ts = msprime.sim_ancestry( + samples=num_samples, + recombination_rate=recombination_rate, + sequence_length=sequence_length, + demography=demography, + model=model, + random_seed=random_seed, + ) + + # Add mutations + mts = msprime.sim_mutations(ts, rate=mutation_rate, random_seed=random_seed) + + # Save to VCF + with open(output_file, "w") as vcf_file: + mts.write_vcf(vcf_file) + + # Calculate some basic statistics + diversity = mts.diversity() + num_sites = mts.num_sites + num_trees = mts.num_trees + + # Log results + end_time = time.time() + runtime = end_time - start_time + + log.append(f"Simulation completed in {runtime:.2f} seconds") + log.append("\nResults:") + log.append(f" - Number of segregating sites: {num_sites}") + log.append(f" - Number of trees in ARG: {num_trees}") + log.append(f" - Nucleotide diversity (π): {diversity:.6f}") + log.append(f" - Output saved to: {os.path.abspath(output_file)}") + + return "\n".join(log) + + +def identify_transcription_factor_binding_sites(sequence, tf_name, threshold=0.8, output_file=None): + """Identifies binding sites for a specific transcription factor in a genomic sequence. + + Parameters + ---------- + sequence : str + The genomic DNA sequence to analyze + tf_name : str + Name of the transcription factor to search for (e.g., 'Hsf1', 'GATA1') + threshold : float, optional + Minimum score threshold for reporting binding sites (0.0-1.0, default: 0.8) + output_file : str, optional + Path to save the results (default: None, results only in log) + + Returns + ------- + str + Research log detailing the binding site identification process and results + + """ + import datetime + import io + + import requests + from Bio import motifs + from Bio.Seq import Seq + + log = f"# Transcription Factor Binding Site Analysis: {tf_name}\n" + log += f"Date: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n" + + # Step 1: Get the PWM for the transcription factor from JASPAR database + log += "## Step 1: Retrieving transcription factor PWM\n" + + try: + # Search for the TF in JASPAR database + jaspar_url = f"https://jaspar.genereg.net/api/v1/matrix/?name={tf_name}" + response = requests.get(jaspar_url) + tf_data = response.json() + + if not tf_data["results"] or len(tf_data["results"]) == 0: + log += f"No PWM found for {tf_name} in JASPAR database.\n" + return log + + # Get the first match's ID + matrix_id = tf_data["results"][0]["matrix_id"] + log += f"Found PWM with ID: {matrix_id}\n" + + # Retrieve the PWM + pwm_url = f"https://jaspar.genereg.net/api/v1/matrix/{matrix_id}.pfm" + pwm_response = requests.get(pwm_url) + + # Parse the PWM + handle = io.StringIO(pwm_response.text) + motif = motifs.read(handle, "jaspar") + log += f"Successfully retrieved PWM for {tf_name}\n" + + # Calculate position-specific scoring matrix (PSSM) + pssm = motif.pssm + + # Step 2: Scan the sequence for binding sites + log += "\n## Step 2: Scanning sequence for binding sites\n" + log += f"Sequence length: {len(sequence)} bp\n" + log += f"Using score threshold: {threshold}\n\n" + + # Find binding sites + binding_sites = [] + max_score = pssm.max + min_score = pssm.min + + for position, score in pssm.search(Seq(sequence), threshold=threshold): + relative_score = (score - min_score) / (max_score - min_score) + if position >= 0: + strand = "+" + site_seq = sequence[position : position + len(pssm)] + else: + strand = "-" + site_seq = sequence[len(sequence) + position - len(pssm) : len(sequence) + position] + + binding_sites.append( + { + "position": abs(position), + "strand": strand, + "score": score, + "relative_score": relative_score, + "sequence": site_seq, + } + ) + + # Step 3: Summarize results + log += f"## Step 3: Results - Found {len(binding_sites)} potential binding sites\n\n" + + if binding_sites: + # Sort by position + binding_sites.sort(key=lambda x: x["position"]) + + # Create a table of results + log += "| Position | Strand | Sequence | Score | Relative Score |\n" + log += "|----------|--------|----------|-------|---------------|\n" + + for site in binding_sites: + log += f"| {site['position']} | {site['strand']} | {site['sequence']} | {site['score']:.2f} | {site['relative_score']:.2f} |\n" + else: + log += "No binding sites found meeting the threshold criteria.\n" + + # Save results to file if specified + if output_file: + with open(output_file, "w") as f: + f.write(f"# {tf_name} binding sites in sequence\n") + f.write("Position\tStrand\tSequence\tScore\tRelative Score\n") + + for site in binding_sites: + f.write( + f"{site['position']}\t{site['strand']}\t{site['sequence']}\t{site['score']:.2f}\t{site['relative_score']:.2f}\n" + ) + + log += f"\nResults saved to file: {output_file}\n" + + except Exception as e: + log += f"\n## Error occurred during analysis: {str(e)}\n" + + log += "\n## Analysis complete\n" + return log + + +def fit_genomic_prediction_model( + genotypes, + phenotypes, + fixed_effects=None, + model_type="additive", + output_file="genomic_prediction_results.csv", +): + """Fit a linear mixed model for genomic prediction using genotype and phenotype data. + + Parameters + ---------- + genotypes : numpy.ndarray + Matrix of genotype data, with individuals in rows and markers in columns. + Values are typically coded as 0, 1, 2 for additive models or with specific + encoding for dominance effects. + phenotypes : numpy.ndarray + Vector or matrix of phenotype data, with individuals in rows and traits in columns. + fixed_effects : numpy.ndarray, optional + Matrix of fixed effects (e.g., environment, management), with individuals in rows + and effects in columns. + model_type : str, optional + Type of genetic model to fit: "additive" or "additive_dominance". + output_file : str, optional + File name to save the results. + + Returns + ------- + str + Research log summarizing the genomic prediction analysis, including model parameters, + variance components, breeding values, and prediction accuracy metrics. + + """ + import pandas as pd + from scipy import linalg + + # Initialize research log + log = "# Multi-trait Genomic Prediction Analysis\n\n" + log += f"Model type: {model_type}\n" + + # Basic validation + n_individuals, n_markers = genotypes.shape + n_pheno, n_traits = (phenotypes.shape[0], 1) if phenotypes.ndim == 1 else phenotypes.shape + + if n_individuals != n_pheno: + raise ValueError(f"Number of individuals in genotypes ({n_individuals}) and phenotypes ({n_pheno}) don't match") + + log += f"Number of individuals: {n_individuals}\n" + log += f"Number of markers: {n_markers}\n" + log += f"Number of traits: {n_traits}\n\n" + + # Ensure phenotypes is 2D + if phenotypes.ndim == 1: + phenotypes = phenotypes.reshape(-1, 1) + + # Center genotypes (common preprocessing step) + genotypes_centered = genotypes - np.mean(genotypes, axis=0) + + # Create genomic relationship matrix (G) + if model_type == "additive": + # Additive genomic relationship matrix + G = np.dot(genotypes_centered, genotypes_centered.T) / n_markers + log += "Constructed additive genomic relationship matrix (G)\n\n" + elif model_type == "additive_dominance": + # For additive-dominance model, we need both A (additive) and D (dominance) matrices + # Assuming genotypes are coded as {0,1,2} for {aa,Aa,AA} + # Create dominance matrix - simple implementation assuming standard coding + dom_genotypes = np.zeros_like(genotypes) + # Code heterozygotes (1) as 1, homozygotes (0,2) as 0 for dominance effects + dom_genotypes[genotypes == 1] = 1 + dom_genotypes_centered = dom_genotypes - np.mean(dom_genotypes, axis=0) + + # Additive and dominance matrices + G_a = np.dot(genotypes_centered, genotypes_centered.T) / n_markers + G_d = np.dot(dom_genotypes_centered, dom_genotypes_centered.T) / n_markers + log += "Constructed additive (G_a) and dominance (G_d) genomic relationship matrices\n\n" + else: + raise ValueError(f"Unknown model type: {model_type}") + + # Initialize results storage + trait_results = [] + + # Fit model for each trait + for trait_idx in range(n_traits): + trait_phenotypes = phenotypes[:, trait_idx] + log += f"## Trait {trait_idx + 1} Analysis\n\n" + + # Handle fixed effects if provided + if fixed_effects is not None: + # Simple fixed effects adjustment - more complex models would use proper mixed model fitting + X = fixed_effects + # Fit fixed effects model + beta = np.linalg.lstsq(X, trait_phenotypes, rcond=None)[0] + # Adjust phenotypes for fixed effects + y_adj = trait_phenotypes - X @ beta + log += f"Applied adjustment for {X.shape[1]} fixed effects\n" + else: + y_adj = trait_phenotypes + log += "No fixed effects provided\n" + + # Fit mixed model + if model_type == "additive": + # Simplified REML estimation for variance components + # In practice, specialized libraries like pyGWAS, GCTA or R's ASReml would be used + + # Initial variance component estimates + var_g_init = np.var(y_adj) * 0.5 # genetic variance + var_e_init = np.var(y_adj) * 0.5 # residual variance + + # Simple EM-like algorithm for variance component estimation + # (In practice, use dedicated software for proper REML) + for _ in range(5): # Few iterations for demonstration + # Construct mixed model equations + V = var_g_init * G + var_e_init * np.eye(n_individuals) + V_inv = linalg.inv(V) + + # Update variance components + P = V_inv - V_inv @ np.ones((n_individuals, 1)) @ np.ones((1, n_individuals)) @ V_inv / ( + np.ones((1, n_individuals)) @ V_inv @ np.ones((n_individuals, 1)) + ) + var_g_new = (y_adj.T @ P @ G @ P @ y_adj) / np.trace(P @ G) + var_e_new = (y_adj.T @ P @ P @ y_adj) / np.trace(P) + + # Update estimates + var_g_init = max(0.01, var_g_new) + var_e_init = max(0.01, var_e_new) + + # Final variance components + var_g = var_g_init + var_e = var_e_init + + # Calculate heritability + heritability = var_g / (var_g + var_e) + + # BLUP solutions for breeding values + V = var_g * G + var_e * np.eye(n_individuals) + V_inv = linalg.inv(V) + breeding_values = var_g * G @ V_inv @ y_adj + + # Predicted phenotypes + predicted_phenotypes = breeding_values + + # Calculate accuracy + accuracy = np.corrcoef(trait_phenotypes, predicted_phenotypes)[0, 1] + + # Log results + log += f"Estimated additive genetic variance: {var_g:.4f}\n" + log += f"Estimated residual variance: {var_e:.4f}\n" + log += f"Estimated heritability: {heritability:.4f}\n" + log += f"Prediction accuracy (correlation): {accuracy:.4f}\n\n" + + # Store results + trait_result = { + "trait": trait_idx + 1, + "var_g": var_g, + "var_e": var_e, + "heritability": heritability, + "accuracy": accuracy, + "breeding_values": breeding_values, + "predicted_phenotypes": predicted_phenotypes, + } + trait_results.append(trait_result) + + elif model_type == "additive_dominance": + # Similar approach but with both additive and dominance effects + # Initial variance component estimates + var_a_init = np.var(y_adj) * 0.4 # additive variance + var_d_init = np.var(y_adj) * 0.1 # dominance variance + var_e_init = np.var(y_adj) * 0.5 # residual variance + + # Simple estimation iterations + for _ in range(5): # Few iterations for demonstration + # Construct mixed model equations + V = var_a_init * G_a + var_d_init * G_d + var_e_init * np.eye(n_individuals) + V_inv = linalg.inv(V) + + # Update variance components (simplified) + P = V_inv - V_inv @ np.ones((n_individuals, 1)) @ np.ones((1, n_individuals)) @ V_inv / ( + np.ones((1, n_individuals)) @ V_inv @ np.ones((n_individuals, 1)) + ) + var_a_new = (y_adj.T @ P @ G_a @ P @ y_adj) / np.trace(P @ G_a) + var_d_new = (y_adj.T @ P @ G_d @ P @ y_adj) / np.trace(P @ G_d) + var_e_new = (y_adj.T @ P @ P @ y_adj) / np.trace(P) + + # Update estimates + var_a_init = max(0.01, var_a_new) + var_d_init = max(0.01, var_d_new) + var_e_init = max(0.01, var_e_new) + + # Final variance components + var_a = var_a_init + var_d = var_d_init + var_e = var_e_init + + # Calculate heritabilities + narrow_heritability = var_a / (var_a + var_d + var_e) + broad_heritability = (var_a + var_d) / (var_a + var_d + var_e) + + # BLUP solutions for breeding values and dominance deviations + V = var_a * G_a + var_d * G_d + var_e * np.eye(n_individuals) + V_inv = linalg.inv(V) + breeding_values = var_a * G_a @ V_inv @ y_adj + dominance_deviations = var_d * G_d @ V_inv @ y_adj + + # Predicted phenotypes + predicted_phenotypes = breeding_values + dominance_deviations + + # Calculate accuracy + accuracy = np.corrcoef(trait_phenotypes, predicted_phenotypes)[0, 1] + + # Log results + log += f"Estimated additive genetic variance: {var_a:.4f}\n" + log += f"Estimated dominance genetic variance: {var_d:.4f}\n" + log += f"Estimated residual variance: {var_e:.4f}\n" + log += f"Estimated narrow-sense heritability: {narrow_heritability:.4f}\n" + log += f"Estimated broad-sense heritability: {broad_heritability:.4f}\n" + log += f"Prediction accuracy (correlation): {accuracy:.4f}\n\n" + + # Store results + trait_result = { + "trait": trait_idx + 1, + "var_a": var_a, + "var_d": var_d, + "var_e": var_e, + "narrow_heritability": narrow_heritability, + "broad_heritability": broad_heritability, + "accuracy": accuracy, + "breeding_values": breeding_values, + "dominance_deviations": dominance_deviations, + "predicted_phenotypes": predicted_phenotypes, + } + trait_results.append(trait_result) + + # Save results to file + results_df = pd.DataFrame() + + for i, trait_result in enumerate(trait_results): + # Create individual-level results + ind_data = { + "individual": np.arange(1, n_individuals + 1), + f"trait_{i + 1}_observed": phenotypes[:, i], + f"trait_{i + 1}_predicted": trait_result["predicted_phenotypes"], + f"trait_{i + 1}_breeding_value": trait_result["breeding_values"], + } + + if model_type == "additive_dominance": + ind_data[f"trait_{i + 1}_dominance_deviation"] = trait_result["dominance_deviations"] + + # Create or append to dataframe + if i == 0: + results_df = pd.DataFrame(ind_data) + else: + for key, value in ind_data.items(): + if key != "individual": # Skip duplicating individual column + results_df[key] = value + + # Save to CSV + results_df.to_csv(output_file, index=False) + log += f"Results saved to {output_file}\n" + + return log + + +def perform_pcr_and_gel_electrophoresis( + genomic_dna, + forward_primer=None, + reverse_primer=None, + target_region=None, + annealing_temp=58, + extension_time=30, + cycles=35, + gel_percentage=2.0, + output_prefix="pcr_result", +): + """Performs PCR amplification of a target transgene and visualizes results using agarose gel electrophoresis. + + Parameters + ---------- + genomic_dna : str + Path to file containing genomic DNA sequence in FASTA format or the sequence itself + forward_primer : str, optional + Forward primer sequence. If not provided, will be designed based on target_region + reverse_primer : str, optional + Reverse primer sequence. If not provided, will be designed based on target_region + target_region : tuple, optional + Tuple of (start, end) positions for the target region in the genomic DNA + annealing_temp : float, default=58 + Annealing temperature for PCR in °C + extension_time : int, default=30 + Extension time in seconds + cycles : int, default=35 + Number of PCR cycles + gel_percentage : float, default=2.0 + Percentage of agarose gel + output_prefix : str, default="pcr_result" + Prefix for output files + + Returns + ------- + str + Research log summarizing the PCR and gel electrophoresis procedures and results + + """ + import datetime + + import matplotlib.pyplot as plt + import numpy as np + from Bio import SeqIO + from Bio.Seq import Seq + + log = f"PCR AMPLIFICATION AND GEL ELECTROPHORESIS LOG - {datetime.datetime.now().strftime('%Y-%m-%d %H:%M')}\n" + log += "=" * 80 + "\n\n" + + # Step 1: Load the genomic DNA + log += "STEP 1: PREPARING GENOMIC DNA\n" + if os.path.isfile(genomic_dna): + try: + record = SeqIO.read(genomic_dna, "fasta") + dna_sequence = str(record.seq) + log += f"- Loaded genomic DNA from file: {genomic_dna}\n" + log += f"- Sequence length: {len(dna_sequence)} bp\n" + except Exception as e: + log += f"- Error loading DNA file: {str(e)}\n" + return log + else: + dna_sequence = genomic_dna + log += "- Using provided DNA sequence\n" + log += f"- Sequence length: {len(dna_sequence)} bp\n" + + log += "\n" + + # Step 2: Design or validate primers + log += "STEP 2: PCR PRIMER PREPARATION\n" + + if forward_primer is None or reverse_primer is None: + if target_region is None: + log += "- Error: Either primers or target region must be provided\n" + return log + + # Simple primer design based on target region + start, end = target_region + + if forward_primer is None: + # Take 20bp from the start of the target region for forward primer + forward_primer = dna_sequence[start : start + 20] + log += "- Designed forward primer based on target region\n" + + if reverse_primer is None: + # Take 20bp from the end of the target region for reverse primer (reverse complement) + reverse_seq = Seq(dna_sequence[end - 20 : end]) + reverse_primer = str(reverse_seq.reverse_complement()) + log += "- Designed reverse primer based on target region\n" + + log += f"- Forward primer: 5'-{forward_primer}-3' ({len(forward_primer)} bp)\n" + log += f"- Reverse primer: 5'-{reverse_primer}-3' ({len(reverse_primer)} bp)\n" + + # Step 3: PCR Setup and Amplification + log += "\nSTEP 3: PCR AMPLIFICATION\n" + log += "- PCR reaction setup:\n" + log += " * Template DNA: Genomic DNA\n" + log += f" * Forward primer: 5'-{forward_primer}-3'\n" + log += f" * Reverse primer: 5'-{reverse_primer}-3'\n" + log += f" * Annealing temperature: {annealing_temp}°C\n" + log += f" * Extension time: {extension_time} seconds\n" + log += f" * Number of cycles: {cycles}\n" + + # Simulate PCR by finding binding sites and determining amplicon size + amplicon_size = None + amplicon_sequence = None + + # Find forward primer binding site + fwd_pos = dna_sequence.find(forward_primer) + if fwd_pos == -1: + log += "- Warning: Forward primer binding site not found in sequence\n" + + # Find reverse primer binding site (need to search for reverse complement) + rev_primer_seq = Seq(reverse_primer) + rev_primer_rc = str(rev_primer_seq.reverse_complement()) + rev_pos = dna_sequence.find(rev_primer_rc) + if rev_pos == -1: + log += "- Warning: Reverse primer binding site not found in sequence\n" + + # If we found both binding sites, calculate amplicon size + if fwd_pos != -1 and rev_pos != -1: + if fwd_pos < rev_pos: + amplicon_size = rev_pos + len(reverse_primer) - fwd_pos + amplicon_sequence = dna_sequence[fwd_pos : rev_pos + len(reverse_primer)] + log += "- PCR amplification successful\n" + log += f"- Amplicon size: {amplicon_size} bp\n" + else: + log += "- Error: Primer binding sites are in incorrect orientation\n" + # Simulate based on target region if provided + elif target_region is not None: + start, end = target_region + amplicon_size = end - start + len(forward_primer) + len(reverse_primer) + log += "- PCR amplification simulated based on target region\n" + log += f"- Expected amplicon size: {amplicon_size} bp\n" + else: + log += "- PCR amplification failed: could not determine amplicon size\n" + return log + + # Step 4: Gel Electrophoresis + log += "\nSTEP 4: AGAROSE GEL ELECTROPHORESIS\n" + log += f"- Prepared {gel_percentage}% agarose gel\n" + log += "- Loaded PCR product alongside DNA ladder\n" + log += "- Ran electrophoresis at 100V for 45 minutes\n" + + # Create a simulated gel image + fig, ax = plt.subplots(figsize=(6, 8)) + + # Draw gel lanes + ax.add_patch(plt.Rectangle((0, 0), 6, 10, color="lightgray", alpha=0.5)) + + # DNA Ladder (100bp increments) + ladder_sizes = [100, 200, 300, 500, 700, 1000, 1500, 2000] + ladder_positions = [10 - (np.log(size) / np.log(2000) * 8) for size in ladder_sizes] + + # Plot ladder + for pos, size in zip(ladder_positions, ladder_sizes, strict=False): + ax.add_patch(plt.Rectangle((0.5, pos - 0.1), 1, 0.2, color="black", alpha=0.8)) + ax.text(0.2, pos, f"{size}bp", fontsize=8, ha="right", va="center") + + # Plot sample band + if amplicon_size: + sample_position = 10 - (np.log(amplicon_size) / np.log(2000) * 8) + ax.add_patch(plt.Rectangle((3.5, sample_position - 0.15), 1, 0.3, color="black", alpha=0.8)) + ax.text( + 4.5, + sample_position, + f"{amplicon_size}bp", + fontsize=8, + ha="left", + va="center", + ) + + # Set up the plot + ax.set_xlim(0, 6) + ax.set_ylim(0, 10) + ax.set_xticks([0.5, 3.5]) + ax.set_xticklabels(["Ladder", "Sample"]) + ax.set_yticks([]) + ax.spines["top"].set_visible(False) + ax.spines["right"].set_visible(False) + ax.spines["left"].set_visible(False) + ax.set_title(f"{gel_percentage}% Agarose Gel") + + # Save the gel image + gel_image_path = f"{output_prefix}_gel.png" + plt.savefig(gel_image_path, dpi=300, bbox_inches="tight") + plt.close() + + log += f"- Gel image saved as: {gel_image_path}\n" + + # Results interpretation + log += "\nRESULTS INTERPRETATION:\n" + if amplicon_size: + log += f"- Detected band at approximately {amplicon_size} bp\n" + + # Save amplicon sequence if available + if amplicon_sequence: + seq_file = f"{output_prefix}_amplicon.fasta" + with open(seq_file, "w") as f: + f.write(f">PCR_Amplicon_{amplicon_size}bp\n") + f.write(amplicon_sequence) + log += f"- Amplicon sequence saved as: {seq_file}\n" + else: + log += "- No bands detected\n" + + return log + + +def analyze_protein_phylogeny( + fasta_sequences, + output_dir="./", + alignment_method="clustalw", + tree_method="fasttree", +): + """Perform phylogenetic analysis on a set of protein sequences. + + This function takes protein sequences in FASTA format, performs multiple sequence alignment, + constructs a phylogenetic tree, and visualizes the evolutionary relationships. + + Parameters + ---------- + fasta_sequences : str + Path to a FASTA file containing protein sequences or a string with FASTA-formatted sequences + output_dir : str, optional + Directory to save output files (default: current directory) + alignment_method : str, optional + Method for sequence alignment: "clustalw", "muscle", or "pre-aligned" (default: "clustalw") + tree_method : str, optional + Method for tree construction: "iqtree" (default: "iqtree") + + Returns + ------- + str + Research log summarizing the phylogenetic analysis process + + """ + import datetime + import subprocess + import tempfile + + from Bio import AlignIO, Phylo, SeqIO + from Bio.Align.Applications import ClustalwCommandline, MuscleCommandline + + # Create output directory if it doesn't exist + if not os.path.exists(output_dir): + os.makedirs(output_dir) + + # Initialize log + log = [] + log.append(f"Phylogenetic Analysis - {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + log.append("=" * 50) + + # Check if input is a file path or string content + if os.path.isfile(fasta_sequences): + input_file = fasta_sequences + log.append(f"Using sequences from file: {input_file}") + else: + # Create temporary file with the string content + temp_fasta = tempfile.NamedTemporaryFile(delete=False, suffix=".fasta", dir=output_dir) + temp_fasta.write(fasta_sequences.encode()) + temp_fasta.close() + input_file = temp_fasta.name + log.append(f"Created temporary FASTA file from provided sequences: {input_file}") + + # Count sequences + try: + sequences = list(SeqIO.parse(input_file, "fasta")) + log.append(f"Loaded {len(sequences)} protein sequences") + except Exception as e: + log.append(f"Warning: Could not parse sequences as FASTA: {str(e)}") + # This might be pre-aligned data already + sequences = [] + + # Create filenames for outputs + base_name = os.path.splitext(os.path.basename(input_file))[0] + alignment_file = os.path.join(output_dir, f"{base_name}_aligned.aln") + tree_file = os.path.join(output_dir, f"{base_name}_tree.nwk") + tree_image = os.path.join(output_dir, f"{base_name}_phylogeny.png") + + # Perform multiple sequence alignment + log.append("\nStep 1: Multiple Sequence Alignment") + + # Special case for pre-aligned input + if alignment_method.lower() == "pre-aligned": + log.append("Using pre-aligned sequences") + try: + # If the input is already an alignment file, copy it to the alignment_file path + with open(input_file) as src, open(alignment_file, "w") as dst: + dst.write(src.read()) + log.append(f"Copied pre-aligned file to: {alignment_file}") + except Exception as e: + log.append(f"Error processing pre-aligned sequences: {str(e)}") + return "\n".join(log) + elif alignment_method.lower() == "clustalw": + log.append("Using Clustal Omega for alignment") + try: + clustalw_cline = ClustalwCommandline("clustalw", infile=input_file, outfile=alignment_file) + stdout, stderr = clustalw_cline() + log.append("Alignment completed successfully") + except Exception as e: + log.append(f"Error during alignment: {str(e)}") + # Try alternative approach using MUSCLE if ClustalW fails + alignment_method = "muscle" + + if alignment_method.lower() == "muscle": + log.append("Using MUSCLE for alignment") + try: + muscle_cline = MuscleCommandline("muscle", input=input_file, out=alignment_file) + stdout, stderr = muscle_cline() + log.append("Alignment completed successfully") + except Exception as e: + log.append(f"Error during MUSCLE alignment: {str(e)}") + log.append("Attempting to use Biopython's built-in pairwise2 alignment as fallback") + + from Bio import pairwise2 + + # Create a simple progressive alignment + ref_seq = sequences[0] + alignments = [] + + for seq in sequences: + alignment = pairwise2.align.globalxx(ref_seq.seq, seq.seq)[0] + alignments.append(alignment) + + # Write alignment to file in CLUSTAL format + with open(alignment_file, "w") as f: + f.write("CLUSTAL W (1.83) multiple sequence alignment\n\n") + for i, seq in enumerate(sequences): + f.write(f"{seq.id.ljust(10)} {alignments[i][1]}\n") + # Add consensus line with asterisks + f.write(" " * 10 + " " * len(alignments[0][1]) + "\n") + + log.append("Created basic alignment using Biopython's pairwise2") + + # Verify alignment file exists + if not os.path.exists(alignment_file): + log.append(f"Error: Alignment file {alignment_file} does not exist") + return "\n".join(log) + + # Build phylogenetic tree + log.append("\nStep 2: Phylogenetic Tree Construction") + + if tree_method.lower() == "iqtree": + log.append("Using IQ-TREE for phylogenetic tree construction") + try: + cmd = f"iqtree -s {alignment_file} -m LG -bb 1000 -pre {os.path.join(output_dir, base_name)}" + subprocess.run( + cmd, + shell=True, + check=True, + capture_output=True, + ) + # IQ-TREE creates files with .treefile extension + iqtree_file = os.path.join(output_dir, f"{base_name}.treefile") + if os.path.exists(iqtree_file): + # Rename to our standard name + os.rename(iqtree_file, tree_file) + log.append("Tree construction completed successfully") + except Exception as e: + log.append(f"Error during IQ-TREE execution: {str(e)}") + log.append("Falling back to neighbor-joining method") + + try: + from Bio.Phylo.TreeConstruction import ( + DistanceCalculator, + DistanceTreeConstructor, + ) + + # Try to read the alignment in different formats + alignment = None + for format in ["clustal", "fasta"]: + try: + alignment = AlignIO.read(alignment_file, format) + break + except Exception: + continue + + if alignment is None: + log.append("Could not parse alignment file in any supported format") + # Create a simple text-based tree file as a fallback + with open(tree_file, "w") as f: + f.write("(protein1:0.1,protein2:0.2,(protein3:0.3,protein4:0.4):0.5);") + log.append("Created placeholder tree file") + else: + # Calculate the distance matrix + calculator = DistanceCalculator("identity") + dm = calculator.get_distance(alignment) + + # Construct the tree + constructor = DistanceTreeConstructor() + tree = constructor.nj(dm) + + # Write the tree to file + Phylo.write(tree, tree_file, "newick") + log.append("Created tree using neighbor-joining method") + except Exception as e: + log.append(f"Error during fallback tree construction: {str(e)}") + # Create a simple text-based tree file as a final fallback + with open(tree_file, "w") as f: + f.write("(protein1:0.1,protein2:0.2,(protein3:0.3,protein4:0.4):0.5);") + log.append("Created placeholder tree file as final fallback") + else: + log.append(f"Unsupported tree method: {tree_method}") + return "\n".join(log) + + # Verify tree file exists + if not os.path.exists(tree_file): + log.append(f"Error: Tree file {tree_file} does not exist") + return "\n".join(log) + + # Visualize the tree + log.append("\nStep 3: Phylogenetic Tree Visualization") + try: + import matplotlib + + matplotlib.use("Agg") # Use non-interactive backend + import matplotlib.pyplot as plt + + tree = Phylo.read(tree_file, "newick") + fig = plt.figure(figsize=(10, len(sequences) * 0.3 if sequences else 5)) + axes = fig.add_subplot(1, 1, 1) + Phylo.draw(tree, axes=axes, do_show=False) + plt.savefig(tree_image, dpi=300, bbox_inches="tight") + plt.close() + log.append(f"Tree visualization saved to: {tree_image}") + except Exception as e: + log.append(f"Error during tree visualization: {str(e)}") + + # Summary + log.append("\nSummary:") + log.append(f"- Input sequences: {len(sequences) if sequences else 'pre-aligned data'}") + log.append(f"- Alignment file: {alignment_file}") + log.append(f"- Phylogenetic tree file: {tree_file}") + log.append(f"- Tree visualization: {tree_image}") + + return "\n".join(log) diff --git a/BioScientist/agent_system/engines/v1_executor_backup/tool/genomics.py b/BioScientist/agent_system/engines/v1_executor_backup/tool/genomics.py new file mode 100644 index 0000000000000000000000000000000000000000..d84d26bc69ef9c27e06f6c74d74cb09e620bef42 --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/tool/genomics.py @@ -0,0 +1,2759 @@ +import os +from pathlib import Path + +import esm +import gget +import gseapy +import numpy as np +import pandas as pd +import requests +import scanpy as sc +import torch +from pybiomart import Dataset +from tqdm import tqdm + +from biomni.llm import get_llm + + +def unsupervised_celltype_transfer_between_scRNA_datasets( + path_to_annotated_h5ad: str, + path_to_not_annotated_h5ad: str, + ref_labels_key: str, + query_batch_key: str = None, + ref_batch_key: str = None, + CELLTYPIST=False, + KNN_BBKNN=False, + KNN_HARMONY=False, + KNN_SCANORAMA=False, + KNN_SCVI=False, + ONCLASS=False, + Random_Forest=False, + SCANVI_POPV=True, + Support_Vector=False, + XGboost=False, + n_jobs: int = 1, + output_folder: str = "./tmp/", + n_samples_per_label: int = 10, +): + import os + + import scanpy as sc + + os.environ["PYTHONUTF8"] = "1" + import popv + + steps = [] + steps.append("Starting unsupervised cell type transfer using popV") + + steps.append(f"Loading reference annotated dataset from: {path_to_annotated_h5ad}") + ref_adata = sc.read_h5ad(path_to_annotated_h5ad) + steps.append(f"Reference annotated dataset loaded: {ref_adata.n_obs} cells, {ref_adata.n_vars} genes") + + steps.append(f"Loading not annotated query dataset from: {path_to_not_annotated_h5ad}") + query_adata = sc.read_h5ad(path_to_not_annotated_h5ad) + steps.append(f"query dataset loaded: {query_adata.n_obs} cells, {query_adata.n_vars} genes") + + # this is required for scvi/scanvi based classifier + ref_adata.layers["counts"] = ref_adata.X.copy() + query_adata.layers["counts"] = query_adata.X.copy() + + popv.settings.n_jobs = n_jobs + output_folder = output_folder + os.makedirs(output_folder, exist_ok=True) + steps.append(f"Created output folder: {output_folder}") + + steps.append("Processing query dataset for annotation with popV") + adata = popv.preprocessing.Process_Query( + query_adata, + ref_adata, + ref_labels_key=ref_labels_key, + query_batch_key=query_batch_key, + ref_batch_key=ref_batch_key, + save_path_trained_models=output_folder, + cl_obo_folder=False, + prediction_mode="retrain", + ).adata + + # passing arugments this way decreases chance of LLM generation and parsing errors + flags = { + "CELLTYPIST": CELLTYPIST, + "KNN_BBKNN": KNN_BBKNN, + "KNN_HARMONY": KNN_HARMONY, + "KNN_SCANORAMA": KNN_SCANORAMA, + "KNN_SCVI": KNN_SCVI, + "ONCLASS": ONCLASS, + "Random_Forest": Random_Forest, + "SCANVI_POPV": SCANVI_POPV, + "Support_Vector": Support_Vector, + "XGboost": XGboost, + } + selected_methods = [name for name, val in flags.items() if val] + if selected_methods: + steps.append(f"Selected annotation methods: {', '.join(selected_methods)}") + else: + steps.append("No annotation methods selected") + + steps.append("Starting annotation with SCANVI_POPV method") + popv.annotation.annotate_data( + adata, + methods=selected_methods, + save_path=f"{output_folder}/popv_output", + ) + + steps.append(f"Annotation completed. Results saved to: {output_folder}/popv_output/predictions.csv") + + return "\n".join(steps) + + +def interspecies_gene_conversion( + gene_list: list[str], + source_species: str, + target_species: str, +) -> str: + """Convert ENSEMBL gene IDs between different species using BioMart homology mapping. + + This function converts a list of ENSEMBL gene IDs from one species to their + homologous counterparts in another species using the Ensembl BioMart database. + The conversion is based on one-to-one ortholog mappings between species. + + Parameters + ---------- + gene_list : list[str] + List of ENSEMBL gene IDs to convert (e.g., ['ENSG00000007372', 'ENSG00000181449']) + source_species : str + Source species name. Supported species: human, mouse, rat, zebrafish, fly, + drosophila, worm, yeast, chicken, pig, cow, dog, macaque + target_species : str + Target species name. Same supported species as source_species. + + Returns + ------- + str + Multi-line string containing the steps performed and file paths where + results were saved. Returns empty string if source and target species are the same. + + Raises + ------ + ValueError + If source_species or target_species is not in the supported species list. + + Notes + ----- + - The function saves conversion results to a CSV file named + '{source_species}_to_{target_species}_gene_conversion.csv' + - Genes that cannot be converted will have NaN values in the output + - The function includes fallback mechanisms for robust data retrieval + - Species synonyms are supported (e.g., 'drosophila' maps to 'fly') + + Examples + -------- + >>> gene_ids = ["ENSG00000007372", "ENSG00000181449"] + >>> result = interspecies_gene_conversion(gene_ids, "human", "mouse") + >>> print(result) + Gene conversion results saved to: human_to_mouse_gene_conversion.csv + """ + + steps = [] + + species_mapping = { + "human": "hsapiens_gene_ensembl", + "mouse": "mmusculus_gene_ensembl", + "rat": "rnorvegicus_gene_ensembl", + "zebrafish": "drerio_gene_ensembl", + "fly": "dmelanogaster_gene_ensembl", + "drosophila": "dmelanogaster_gene_ensembl", + "worm": "celegans_gene_ensembl", + "yeast": "scerevisiae_gene_ensembl", + "chicken": "ggallus_gene_ensembl", + "pig": "sscrofa_gene_ensembl", + "cow": "btaurus_gene_ensembl", + "dog": "cfamiliaris_gene_ensembl", + "macaque": "mmulatta_gene_ensembl", + } + + homolog_mapping = { + "human": "hsapiens_homolog_ensembl_gene", + "mouse": "mmusculus_homolog_ensembl_gene", + "rat": "rnorvegicus_homolog_ensembl_gene", + "zebrafish": "drerio_homolog_ensembl_gene", + "fly": "dmelanogaster_homolog_ensembl_gene", + "drosophila": "dmelanogaster_homolog_ensembl_gene", + "worm": "celegans_homolog_ensembl_gene", + "yeast": "scerevisiae_homolog_ensembl_gene", + "chicken": "ggallus_homolog_ensembl_gene", + "pig": "sscrofa_homolog_ensembl_gene", + "cow": "btaurus_homolog_ensembl_gene", + "dog": "cfamiliaris_homolog_ensembl_gene", + "macaque": "mmulatta_homolog_ensembl_gene", + } + + if source_species not in species_mapping: + raise ValueError( + f"Source species '{source_species}' not supported. Available species: {list(species_mapping.keys())}" + ) + + if target_species not in homolog_mapping: + raise ValueError( + f"Target species '{target_species}' not supported. Available species: {list(homolog_mapping.keys())}" + ) + + original_length = len(gene_list) + + if source_species == target_species: + return "\n".join(steps) + + source_dataset_name = species_mapping[source_species] + homolog_attribute = homolog_mapping[target_species] + + try: + source_dataset = Dataset(name=source_dataset_name, host="http://www.ensembl.org") + + mapping = source_dataset.query( + attributes=["ensembl_gene_id", homolog_attribute], filters={"link_ensembl_gene_id": gene_list} + ) + + if mapping.empty: + all_mapping = source_dataset.query(attributes=["ensembl_gene_id", homolog_attribute]) + mapping = all_mapping[all_mapping["ensembl_gene_id"].isin(gene_list)] + + mapping_filtered = mapping[mapping.iloc[:, 1].notna() & (mapping.iloc[:, 1] != "")] + gene_mapping = dict(zip(mapping_filtered.iloc[:, 0], mapping_filtered.iloc[:, 1], strict=False)) + + converted_genes = [] + for gene in gene_list: + if gene in gene_mapping: + converted_genes.append(gene_mapping[gene]) + else: + converted_genes.append(np.nan) + + assert len(converted_genes) == original_length, "Input and output gene list shapes must be the same" + + conversion_df = pd.DataFrame({f"{source_species}_genes": gene_list, f"{target_species}_genes": converted_genes}) + filename = f"{source_species}_to_{target_species}_gene_conversion.csv" + conversion_df.to_csv(filename, index=False) + steps.append(f"Gene conversion results saved to: {filename}") + + return "\n".join(steps) + + except Exception as e: + steps.append(f"Error during gene conversion from {source_species} to {target_species}: {e}") + try: + source_dataset = Dataset(name=source_dataset_name, host="http://www.ensembl.org") + + all_mapping = source_dataset.query(attributes=["ensembl_gene_id", homolog_attribute]) + + mapping = all_mapping[all_mapping["ensembl_gene_id"].isin(gene_list)] + + mapping_filtered = mapping[mapping.iloc[:, 1].notna() & (mapping.iloc[:, 1] != "")] + gene_mapping = dict(zip(mapping_filtered.iloc[:, 0], mapping_filtered.iloc[:, 1], strict=False)) + + converted_genes = [] + for gene in gene_list: + if gene in gene_mapping: + converted_genes.append(gene_mapping[gene]) + else: + converted_genes.append(np.nan) + + assert len(converted_genes) == original_length, "Input and output gene list shapes must be the same" + + conversion_df = pd.DataFrame( + {f"{source_species}_genes": gene_list, f"{target_species}_genes": converted_genes} + ) + filename = f"{source_species}_to_{target_species}_gene_conversion.csv" + conversion_df.to_csv(filename, index=False) + steps.append(f"Gene conversion results saved to: {filename}") + + return "\n".join(steps) + + except Exception as e2: + steps.append(f"Alternative approach also failed: {e2}") + converted_genes = [np.nan] * original_length + + assert len(converted_genes) == original_length, "Input and output gene list shapes must be the same" + + conversion_df = pd.DataFrame( + {f"{source_species}_genes": gene_list, f"{target_species}_genes": converted_genes} + ) + filename = f"{source_species}_to_{target_species}_gene_conversion.csv" + conversion_df.to_csv(filename, index=False) + steps.append(f"Gene conversion results (failed) saved to: {filename}") + + return "\n".join(steps) + + +def _fetch_isoform_sequences(ensembl_gene_id: str) -> list[str]: + """ + this is not registered as a tool + Fetch all protein isoform FASTA sequences for a given Ensembl gene ID. + Returns a list of protein sequences (strings) for each isoform, or an empty list if not found. + For ENSG00000012048, removes the sequence with header containing 'ENSP00000420781.2'. + + Args: + ensembl_gene_id: Ensembl gene identifier (e.g., "ENSG00000012048") + + Returns: + List of protein sequences as strings + """ + url = f"https://rest.ensembl.org/sequence/id/{ensembl_gene_id}?type=protein;multiple_sequences=1" + headers = {"Content-Type": "text/x-fasta"} + response = requests.get(url, headers=headers) + if not response.ok or not response.text.startswith(">"): + return [] + sequences = [] + fasta_entries = response.text.strip().split(">") + for entry in fasta_entries: + if not entry.strip(): + continue + lines = entry.strip().splitlines() + lines[0] + sequence = "".join(lines[1:]) + sequences.append(sequence) + return sequences + + +def generate_gene_embeddings_with_ESM_models( + ensembl_gene_ids: list[str], + model_name: str = "esm2_t6_8M_UR50D", + layer: int = 6, + save_path: str | None = None, + batch_size: int = 1, + max_sequence_length: int = 1024, +) -> str: + """ + Generate average protein embeddings for a list of Ensembl gene IDs. + Memory-friendly implementation with rolling averages and small batch processing. + Embeddings are automatically saved to disk with a default filename if no save_path is provided. + + Args: + ensembl_gene_ids: List of Ensembl gene IDs (e.g., ["ENSG00000012048", ...]) + model_name: ESM model name to use (default: "esm2_t6_8M_UR50D") + layer: Which layer to extract embeddings from (default: 6) + save_path: Optional path to save embeddings as PyTorch dict. If None, uses default filename + format: "esm_embeddings_{model_name}_{gene_list}.pt" (default: None) + batch_size: Number of sequences to process at once (default: 1) + max_sequence_length: Maximum sequence length to process (default: 1024) + + Returns: + String containing the steps performed during the embedding generation process + """ + steps = [] + steps.append(f"Loading ESM model: {model_name}") + # model loading take a while, once loaded for smaller models generation is relatively fast + # running bilion model ESM models require FSDP or GPUs with 80 GB of memory + model, alphabet = esm.pretrained.load_model_and_alphabet(model_name) + batch_converter = alphabet.get_batch_converter() + model.eval() + + # Move model to GPU if available + device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + model = model.to(device) + steps.append(f"Using device: {device}") + + steps.append("Fetching protein isoform sequences...") + ensembl_gene_isoforms_map: dict[str, list[str]] = {} + for ensembl_id in tqdm(ensembl_gene_ids, desc="Fetching sequences"): + isoform_sequences = _fetch_isoform_sequences(ensembl_id) + # Filter sequences by length to avoid memory issues + filtered_sequences = [seq for seq in isoform_sequences if len(seq) <= max_sequence_length] + if filtered_sequences: + ensembl_gene_isoforms_map[ensembl_id] = filtered_sequences + else: + steps.append(f" No sequences under {max_sequence_length} residues found for gene {ensembl_id}") + + # Prepare all data for processing + all_data: list[tuple[str, str]] = [] + for gene, isoform_seqs in ensembl_gene_isoforms_map.items(): + for i, seq in enumerate(isoform_seqs): + all_data.append((f"{gene}_isoform_{i}", seq)) + + steps.append(f"Processing {len(all_data)} sequences in batches of {batch_size}") + + # Rolling average tracking + gene_embeddings: dict[str, list[torch.Tensor]] = {} + gene_avg_embedding: dict[str, np.ndarray] = {} + + # Process in small batches to avoid memory issues + for i in tqdm(range(0, len(all_data), batch_size), desc="Processing ESM embeddings"): + batch_data = all_data[i : i + batch_size] + + try: + batch_labels, batch_strs, batch_tokens = batch_converter(batch_data) + batch_tokens = batch_tokens.to(device) + batch_lens: torch.Tensor = (batch_tokens != alphabet.padding_idx).sum(1) + + steps.append(f" Running ESM model on batch {i // batch_size + 1}...") + with torch.no_grad(): + results = model(batch_tokens, repr_layers=[layer], return_contacts=False) + token_representations: torch.Tensor = results["representations"][layer] + + # Process each sequence in the batch + for j, tokens_len in enumerate(batch_lens): + # Extract gene name from label (remove _isoform_X suffix) + gene_name = batch_labels[j].split("_isoform_")[0] + + # Get sequence embedding (excluding BOS/EOS tokens) + sequence_embedding = token_representations[j, 1 : tokens_len - 1].mean(0).detach().cpu() + + # Store embedding for this gene + if gene_name not in gene_embeddings: + gene_embeddings[gene_name] = [] + gene_embeddings[gene_name].append(sequence_embedding) + + except RuntimeError as e: + if "out of memory" in str(e).lower(): + steps.append(f" Memory error on batch {i // batch_size + 1}, reducing batch size...") + # Clear cache and try with smaller batch + torch.cuda.empty_cache() if torch.cuda.is_available() else None + # Process this batch one by one + for single_item in batch_data: + try: + single_batch_labels, single_batch_strs, single_batch_tokens = batch_converter([single_item]) + single_batch_tokens = single_batch_tokens.to(device) + single_batch_lens: torch.Tensor = (single_batch_tokens != alphabet.padding_idx).sum(1) + + with torch.no_grad(): + single_results = model(single_batch_tokens, repr_layers=[layer], return_contacts=False) + single_token_representations: torch.Tensor = single_results["representations"][layer] + + gene_name = single_item[0].split("_isoform_")[0] + sequence_embedding = ( + single_token_representations[0, 1 : single_batch_lens[0] - 1].mean(0).detach().cpu() + ) + + if gene_name not in gene_embeddings: + gene_embeddings[gene_name] = [] + gene_embeddings[gene_name].append(sequence_embedding) + + except Exception as single_e: + steps.append(f" Failed to process {single_item[0]}: {str(single_e)}") + else: + steps.append(f" Error processing batch {i // batch_size + 1}: {str(e)}") + + # Compute final averages for each gene + steps.append("Computing final gene embeddings...") + for gene, embeddings in gene_embeddings.items(): + if embeddings: + # Stack all embeddings for this gene and compute mean + stacked_embeddings = torch.stack(embeddings, dim=0) + avg_embedding = stacked_embeddings.mean(dim=0).numpy() + gene_avg_embedding[gene] = avg_embedding + steps.append(f" Gene {gene}: averaged {len(embeddings)} isoform embeddings") + else: + steps.append(f" Gene {gene}: no valid embeddings found") + + # Use default save path if none provided + if save_path is None: + gene_list_str = "_".join(ensembl_gene_ids[:3]) # Use first 3 genes for filename + if len(ensembl_gene_ids) > 3: + gene_list_str += f"_and_{len(ensembl_gene_ids) - 3}_more" + save_path = f"esm_embeddings_{model_name}_{gene_list_str}.pt" + + save_dir = Path(save_path).parent + save_dir.mkdir(parents=True, exist_ok=True) + + torch_embeddings = {gene_id: torch.from_numpy(embedding) for gene_id, embedding in gene_avg_embedding.items()} + + torch.save(torch_embeddings, save_path) + steps.append(f"Embeddings saved to: {os.path.abspath(save_path)}") + steps.append(f"File size: {os.path.getsize(save_path) / (1024 * 1024):.2f} MB") + + return "\n".join(steps) + + +def annotate_celltype_scRNA( + adata_filename, + data_dir, + data_info, + data_lake_path, + cluster="leiden", + llm="claude-3-5-sonnet-20241022", + composition=None, +): + """Annotate cell types based on gene markers and transferred labels using LLM. + After leiden clustering, annotate clusters using differentially expressed genes + and optionally incorporate transferred labels from reference datasets. + + Parameters + ---------- + - adata_filename (str): Name of the AnnData file containing scRNA-seq data + - data_dir (str): Directory containing the data files + - data_info (str): Information about the scRNA-seq data (e.g., "homo sapiens, brain tissue, normal") + - data_lake_path (str): Path to the data lake + - llm (str): Language model instance for cell type prediction, such as 'claude-3-haiku-20240307' + - composition (pd.DataFrame, optional): Transferred cell type composition for each cluster + Returns: + - str: Steps performed and file paths where results were saved + + """ + + def _cluster_info(cluster_id, marker_genes, composition_df=None): + """Format cluster information for LLM prompt.""" + if composition_df is None: + return f"The enriched genes in this cluster are: {', '.join(marker_genes)}." + + info = [ + f"The enriched genes in this cluster are: {', '.join(marker_genes)}.", + f"For a starting point, the transferred reference cell type composition {cluster_id} is:", + ] + + cluster_comp = [] + for celltype, proportion in composition_df.loc[cluster_id].items(): + if proportion > 0: + cluster_comp.append(f"{celltype}:{proportion:.2f}") + + return "\n".join(info) + " " + "; ".join(cluster_comp) + "\n" + + from langchain_core.prompts import PromptTemplate + + # from langchain.chains import LLMChain + + steps = [] + steps.append(f"Loading AnnData from {data_dir}/{adata_filename}") + adata = sc.read_h5ad(f"{data_dir}/{adata_filename}") + + steps.append(f"Identifying marker genes for clusters defined by {cluster} clustering.") + sc.tl.rank_genes_groups(adata, groupby="leiden", method="wilcoxon", use_raw=False) + genes = pd.DataFrame(adata.uns["rank_genes_groups"]["names"]).head(20) + scores = pd.DataFrame(adata.uns["rank_genes_groups"]["scores"]).head(20) + + markers = {} + for i in range(genes.shape[1]): + gene_names = genes.iloc[:, i].tolist() + gene_scores = scores.iloc[:, i].tolist() + markers[i] = list(np.array(gene_names)[np.array(gene_scores) > 0]) + + # TODO: this can be optimized + czi_celltype_path = data_lake_path + "/czi_census_datasets_v4.parquet" + df = pd.read_parquet(czi_celltype_path) + czi_celltype_set = {cell_type.strip() for cell_types in df["cell_type"] for cell_type in str(cell_types).split(";")} + czi_celltype = ", ".join(sorted(czi_celltype_set)) + + prompt_template = f""" +Please think carefully, and identify the cell type in {data_info} based on the gene markers. +Optionally refer to the transferred cell type information but do not trust it when the percentage is lower than 0.5. + +{{cluster_info}} + +The cell type names should come from cell ontology: {czi_celltype}. +Only provide the cell type name, confidence score (0-1), and detailed reason. +Output format: "name; score; reason". +No numbers before name or spaces before number. +""" + # Some can be a mixture of multiple cell types. + + llm = get_llm(llm) + prompt = PromptTemplate(input_variables=["cluster_info"], template=prompt_template) + chain = prompt | llm + + steps.append("Annotating cell types of each cluster based on gene markers and transferred labels.") + # valid_celltypes = set(czi_celltype.split(";")) + cluster_annotations = {} + annotation_reasons = [] + + print(f"Annotate each cluster of {cluster}") + for _idx in range(len(adata.obs[cluster].unique())): + cluster_info = _cluster_info(str(_idx), markers[_idx], composition) + + while True: + response = chain.invoke({"cluster_info": cluster_info}) + + # Handle different response types + if hasattr(response, "content"): # For AIMessage + response = response.content + elif isinstance(response, dict) and "text" in response: + response = response["text"] + elif isinstance(response, str): + response = response + else: + response = str(response) + + try: + predicted_celltype, confidence, reason = [x.strip() for x in response.split(";", 2)] + if predicted_celltype in czi_celltype_set or predicted_celltype.lower() in czi_celltype_set: + cluster_annotations[str(_idx)] = predicted_celltype + annotation_reasons.append((predicted_celltype, reason)) + break + else: + cluster_info += "\nAssigned cell type name must be in cell ontology!" + except ValueError: + cluster_info += "\nPlease follow the format: name; score; reason" + print(f"Cluster {_idx}: {response}") + + # create reason dictionary + reason_dict = {} + for celltype, reason in annotation_reasons: + if celltype not in reason_dict: + reason_dict[celltype] = [] + reason_dict[celltype].append(reason) + + reason_dict = {k: "\n".join(v) for k, v in reason_dict.items()} + + adata.obs["cell_type"] = adata.obs[cluster].map(cluster_annotations) + adata.obs["cell_type_reason"] = adata.obs["cell_type"].map(reason_dict).astype(str) + + steps.append(f"Saving annotated adata to {data_dir}/annotated.h5ad, the annotations are in the 'cell_type' column.") + adata.write(f"{data_dir}/annotated.h5ad", compression="gzip") + + return "\n".join(steps) + + +def annotate_celltype_with_panhumanpy( + adata_path, + feature_names_col=None, + refine=True, + umap=True, + output_dir="./output", +): + """Perform hierarchical cell type annotation using panhumanpy and Azimuth Neural Network. + + This function implements the panhumanpy workflow for cell type annotation using the + Azimuth Neural Network, providing hierarchical cell type labels with confidence scores. + + Parameters + ---------- + adata_path : str + Path to the AnnData file containing scRNA-seq data + feature_names_col : str, optional + Column name in adata.var containing gene names (default: None, uses index) + refine : bool, optional + Whether to perform label refinement for consistent granularity (default: True) + umap : bool, optional + Whether to generate ANN embeddings and UMAP (default: True) + output_dir : str, optional + Directory to save results (default: "./output") + + Returns + ------- + str + Research log summarizing the analysis steps and results + + Notes + ----- + Performance is not ensured for diseased and/or non-human cells. + """ + import json + import shutil + import subprocess + import tempfile + + def conda_env_exists(env_name): + try: + result = subprocess.run(["conda", "env", "list"], capture_output=True, text=True, check=True) + return any(env_name in line.split() for line in result.stdout.splitlines()) + except Exception: + return False + + def create_panhumanpy_env(env_name): + # Create env and install panhumanpy + subprocess.run(["conda", "create", "-y", "-n", env_name, "python=3.10"], check=True) + # Install panhumanpy in the new env + subprocess.run( + ["conda", "run", "-n", env_name, "pip", "install", "git+https://github.com/satijalab/panhumanpy.git"], + check=True, + ) + + PANHUMANPY_ENV = "panhumanpy_env" + + # 1. Check/create panhumanpy_env + if not conda_env_exists(PANHUMANPY_ENV): + create_panhumanpy_env(PANHUMANPY_ENV) + + # 2. Write a temp script to run in the panhumanpy_env + temp_dir = tempfile.mkdtemp() + script_path = os.path.join(temp_dir, "run_panhumanpy.py") + result_path = os.path.join(temp_dir, "result.json") + with open(script_path, "w") as f: + f.write( + f""" +import os +import sys +import json +import numpy as np +import scanpy as sc +import pandas as pd +try: + import panhumanpy as ph +except ImportError as e: + with open(r'{result_path}', 'w') as out: + out.write(json.dumps({{"error": str(e)}})) + sys.exit(1) + +adata_path = r'''{adata_path}''' +feature_names_col = {repr(feature_names_col)} +refine = {refine} +umap = {umap} +output_dir = r'''{output_dir}''' +log = [] +try: + os.makedirs(output_dir, exist_ok=True) + log.append("# Performing cell type annotation with Panhuman Azimuth") + log.append(f"Loading object from: {{adata_path}}") + adata = sc.read_h5ad(adata_path) + log.append(f"✓ Successfully loaded object with {{adata.n_obs}} cells and {{adata.n_vars}} genes") + if feature_names_col is None: + log.append("Using gene names from adata.var.index") + else: + log.append(f"Using gene names from column: {{feature_names_col}}") + if feature_names_col not in adata.var.columns: + log.append(f"⚠ Warning: Column '{{feature_names_col}}' not found in adata.var") + log.append(f"Available columns: {{list(adata.var.columns)}}") + log.append("Falling back to index") + feature_names_col = None + if feature_names_col is None: + azimuth = ph.AzimuthNN(adata) + else: + azimuth = ph.AzimuthNN(adata, feature_names_col=feature_names_col) + cell_metadata = azimuth.cells_meta + log.append("✓ Successfully annotated all cells") + if umap: + log.append("## Generating ANN embeddings") + try: + embeddings = azimuth.azimuth_embed() + except Exception as e: + log.append(f"✗ Error generating embeddings: {{str(e)}}") + with open(r'{result_path}', 'w') as out: + out.write(json.dumps({{"log": log}})) + sys.exit(0) + log.append("## Calculating UMAP") + try: + azimuth.azimuth_umap() + log.append("✓ Generated UMAP of ANN embeddings") + except Exception as e: + log.append(f"✗ Error generating UMAP: {{str(e)}}") + with open(r'{result_path}', 'w') as out: + out.write(json.dumps({{"log": log}})) + sys.exit(0) + else: + log.append("## Skipping embeddings and UMAP generation") + embeddings = None + umap = None + if refine: + log.append("## Performing label refinement") + try: + azimuth.azimuth_refine() + cell_metadata = azimuth.cells_meta + refined_columns = [col for col in cell_metadata.columns if col.startswith("azimuth_")] + log.append(f"✓ Applied label refinement, results are in columns: {{refined_columns}}") + except Exception as e: + log.append(f"✗ Error during label refinement: {{str(e)}}") + log.append("## Saving results") + try: + metadata_file = f"{output_dir}/annotated_cell_metadata.csv" + cell_metadata.to_csv(metadata_file) + log.append(f"✓ Saved cell metadata to: {{metadata_file}}") + if umap and embeddings is not None: + embeddings_file = f"{output_dir}/ann_embeddings.npy" + np.save(embeddings_file, embeddings) + log.append(f"✓ Saved embeddings to: {{embeddings_file}}") + umap_file = f"{output_dir}/ann_umap.npy" + np.save(umap_file, umap) + log.append(f"✓ Saved UMAP to: {{umap_file}}") + else: + log.append("Skipped saving embeddings and UMAP (umap=False)") + annotated_save_path = f"{output_dir}/annotated_obj.h5ad" + azimuth.pack_adata(save_path=annotated_save_path) + log.append(f"✓ Saved annotated object to: {{annotated_save_path}}") + except Exception as e: + log.append(f"✗ Error saving results: {{str(e)}}") + with open(r'{result_path}', 'w') as out: + out.write(json.dumps({{"log": log}})) + sys.exit(0) + log.append(f"- All results saved to: {output_dir}") + with open(r'{result_path}', 'w') as out: + out.write(json.dumps({{"log": log}})) +except Exception as e: + with open(r'{result_path}', 'w') as out: + out.write(json.dumps({{"error": str(e)}})) + sys.exit(1) +""" + ) + + # 3. Run the script in the panhumanpy_env + try: + run_cmd = ["conda", "run", "-n", PANHUMANPY_ENV, "python", script_path] + subprocess.run(run_cmd, check=True) + except subprocess.CalledProcessError as e: + shutil.rmtree(temp_dir) + return f"Error running panhumanpy in conda env: {e}" + + # 4. Read the result + try: + with open(result_path) as f: + result = json.load(f) + if "log" in result: + log = result["log"] + elif "error" in result: + log = [f"Error: {result['error']}"] + else: + log = ["Unknown error running panhumanpy script."] + except Exception as e: + log = [f"Error reading result: {e}"] + + # 5. Clean up temp files + shutil.rmtree(temp_dir) + + return "\n".join(log) + + +# === Integration === + + +def create_scvi_embeddings_scRNA(adata_filename, batch_key, label_key, data_dir): + # Import scvi-tools correctly - the package name is still 'scvi' when installed + try: + import scvi + except ImportError: + return "Please install scvi-tools: pip install scvi-tools" + + steps = [] + steps.append(f"Loading AnnData from {data_dir}/{adata_filename}") + adata = sc.read_h5ad(f"{data_dir}/{adata_filename}") + + steps.append(f"Setting up AnnData for scVI with batch key: {batch_key}") + scvi.model.SCVI.setup_anndata(adata, batch_key=batch_key) + + steps.append("Creating and training scVI model") + model = scvi.model.SCVI(adata) + model.train() + + steps.append("Generating latent representation using scVI") + adata.obsm["X_scVI"] = model.get_latent_representation() + + steps.append(f"Creating scANVI model with label key: {label_key}") + lvae = scvi.model.SCANVI.from_scvi_model( + model, + adata=adata, + labels_key=label_key, + unlabeled_category="Unknown", + ) + steps.append("Training scANVI model") + lvae.train() + + steps.append("Generating latent representation using scANVI") + adata.obsm["X_scANVI"] = lvae.get_latent_representation(adata) + + output_filename = f"{data_dir}/scvi_emb_data.h5ad" + adata.write(output_filename) + steps.append(f"Saving AnnData with scVI and scANVI embeddings to {output_filename}") + + return "\n".join(steps) + + +def create_harmony_embeddings_scRNA(adata_filename, batch_key, data_dir): + # https://pypi.org/project/harmony-pytorch/ + from harmony import harmonize + + steps = [] + steps.append(f"Loading AnnData from {data_dir}/{adata_filename}") + adata = sc.read_h5ad(f"{data_dir}/{adata_filename}") + + steps.append(f"Running Harmony integration with batch key: {batch_key}") + adata.obsm["X_harmony"] = harmonize(adata.obsm["X_pca"], adata.obs, batch_key=batch_key) + + output_filename = f"{data_dir}/harmony_emb_data.h5ad" + steps.append(f"Saving the Harmony embeddings to {output_filename}.") + adata.write(output_filename) + + return "\n".join(steps) + + +## TODO: the environment is not ready for this tool +def get_uce_embeddings_scRNA( + adata_filename, + data_dir, + DATA_ROOT="/dfs/project/bioagentos/data/singlecell/", + custom_args=None, +): + """The UCE embeddings are usually our default tools to get cell embeddings, we map UCE embeddings to IMA referece dataset and get the cell types for a better understanding. + The custom_args is a list of strings that will be passed as command line arguments to the UCE script, + like ["--adata_path", adata_file, "--dir", output_dir]. The default value is None. + """ + import sys + + steps = [] + + try: + from eval_single_anndata import main, parse_args_uce + + steps.append("Successfully imported UCE main function") + except Exception: + steps.append("Please install the UCE package first. Follow https://github.com/snap-stanford/UCE.git.") + return "\n".join(steps) + + from accelerate import Accelerator + + _base_name = os.path.basename(adata_filename).split(".")[0] + adata_file_proc = f"{data_dir}/{_base_name}_uce_adata.h5ad" + if os.path.exists(adata_file_proc): + steps.append(f"{adata_file_proc} already exists, skipping. The UCE embeddings are stored as adata.obs['X_uce']") + return "\n".join(steps) + + uce_dir = f"{DATA_ROOT}/UCE" + sys.path.append(uce_dir) + steps.append(f"Added {uce_dir} to sys.path") + + # Prepare and parse arguments + if custom_args is None: + custom_args = [] + custom_args.extend(["--adata_path", f"{data_dir}/{adata_filename}", "--dir", f"{data_dir}/uce/"]) + parsed_args = parse_args_uce(custom_args) + steps.append(f"Parsed arguments: {vars(parsed_args)}") + + # Initialize Accelerator + accelerator = Accelerator(project_dir=parsed_args.dir) + steps.append("Initialized Accelerator") + + # Run UCE main function + main(parsed_args, accelerator) + steps.append("UCE main function completed successfully.") + steps.append(f"{adata_file_proc} is saved, the UCE embeddings are stored as adata.obs['X_uce']") + + return "\n".join(steps) + + +def map_to_ima_interpret_scRNA(adata_filename, data_dir, custom_args=None): + """Map cell embeddings from the input dataset to the Integrated Megascale Atlas reference dataset using UCE embeddings.""" + from sklearn.neighbors import NearestNeighbors + + steps = [] + steps.append(f"Loading AnnData from {data_dir}/{adata_filename}") + adata = sc.read_h5ad(f"{data_dir}/{adata_filename}") + + if "X_uce" not in adata.obsm: + raise ValueError("Error: adata.obsm['X_uce'] not found. Please run get_uce_embeddings() first.") + + steps.append("adata.obs['X_uce'] found. Proceeding with cell type mapping.") + + IMA_adata = sc.read_h5ad(f"{data_dir}/uce_10000_per_dataset_33l_8ep_coarse_ct.h5ad") + steps.append("Loaded Integrated Megascale Atlas (IMA) reference dataset") + + if adata.obsm["X_uce"].shape[1] != IMA_adata.X.shape[1]: + raise ValueError("UCE embedding dimensions do not match between datasets.") + + # Create a NearestNeighbors object + n_neighbors = custom_args.get("n_neighbors", 3) + metric = custom_args.get("metric", "euclidean") + nn = NearestNeighbors(n_neighbors=n_neighbors, metric=metric) + nn.fit(IMA_adata.X) + + # Find the nearest neighbors for each cell in adata + distances, indices = nn.kneighbors(adata.obsm["X_uce"]) + + # Extract cell types of the nearest neighbors + mapped_cell_types = IMA_adata.obs["coarse_cell_type_yanay"].iloc[indices.flatten()].values + + # from umap import UMAP + if n_neighbors > 1: + # Implement majority voting + + def custom_mode(x): + unique, counts = np.unique(x, return_counts=True) + return unique[np.argmax(counts)] + + mapped_cell_types = np.apply_along_axis(custom_mode, 1, mapped_cell_types.reshape(-1, n_neighbors)) + + # Add mapped cell types and confidence scores to adata + adata.obs["mapped_cell_type"] = mapped_cell_types + adata.obs["mapping_confidence"] = 1 / (1 + distances.mean(axis=1)) + + steps.append("Mapped cell types based on nearest neighbors in UCE space") + steps.append("Mapped cell types and confidence scores added to adata.obs") + + # Generate summary statistics + mapping_summary = adata.obs["mapped_cell_type"].value_counts().to_dict() + steps.append(f"Mapping summary: {mapping_summary}") + + # Save the updated adata object + output_filename = f"{data_dir}/adata_with_mapped_celltypes.h5ad" + adata.write_h5ad(output_filename, compression="gzip") + steps.append(f"Updated adata saved to {output_filename}") + + return "\n".join(steps) + + +def get_rna_seq_archs4(gene_name: str, K: int = 10) -> str: + """Given a gene name, this function returns the steps it performs and the max K transcripts-per-million (TPM) + per tissue from the RNA-seq expression. + + Parameters + ---------- + - gene_name (str): The gene name for which RNA-seq data is being fetched. + - K (int): The number of tissues to return. Default is 10. + + Returns + ------- + - str: The steps performed and the result. + + """ + steps_log = f"Starting RNA-seq data fetch for gene: {gene_name} with K: {K}\n" + + try: + # Fetch RNA-seq data using gget + steps_log += "Fetching RNA-seq data using gget.archs4...\n" + data = gget.archs4(gene_name, which="tissue") + + if data.empty: + steps_log += f"No RNA-seq data found for the gene {gene_name}.\n" + return steps_log + + # Create a readable output string + steps_log += f"RNA-seq expression data for {gene_name} fetched successfully. Formatting the top {K} tissues:\n" + readable_output = "" + for index, row in data.iterrows(): + if index < K: + tissue = row["id"] + median_tpm = row["median"] + readable_output += f"\nTissue: {tissue}\n - Median TPM: {median_tpm}\n" + else: + break + + steps_log += readable_output + return steps_log + + except Exception as e: + return f"An error occurred: {e}" + + +def get_gene_set_enrichment_analysis_supported_database_list() -> list: + return gseapy.get_library_name() + + +def gene_set_enrichment_analysis( + genes: list, + top_k: int = 10, + database: str = "ontology", + background_list: list = None, + plot: bool = False, +) -> str: + """Perform enrichment analysis for a list of genes, with optional background gene set and plotting functionality. + + Parameters + ---------- + - genes (list): List of gene symbols to analyze. + - top_k (int): Number of top pathways to return. Default is 10. + - database (str): User-friendly name of the database to use for enrichment analysis. + Popular options include: + - 'pathway'      (KEGG_2021_Human) + - 'transcription'   (ChEA_2016) + - 'ontology'     (GO_Biological_Process_2021) + - 'diseases_drugs'  (GWAS_Catalog_2019) + - 'celltypes'     (PanglaoDB_Augmented_2021) + - 'kinase_interactions' (KEA_2015) + You can use get_gene_set_enrichment_analysis_supported_database_list tool to get the list of supported databases. + + - background_list (list, optional): List of background genes to use for enrichment analysis. + - plot (bool, optional): If True, generates a bar plot of the top K enrichment results. + + Returns + ------- + - str: The steps performed and the top K enrichment results. + + """ + steps_log = ( + f"Starting enrichment analysis for genes: {', '.join(genes)} using {database} database and top_k: {top_k}\n" + ) + + if background_list: + steps_log += f"Using background list with {len(background_list)} genes.\n" + + try: + # Perform enrichment analysis with or without background list + steps_log += f"Performing enrichment analysis using gget.enrichr with the {database} database...\n" + df = gget.enrichr(genes, database=database, background_list=background_list, plot=plot) + + # Limit to top K results + steps_log += f"Filtering the top {top_k} enrichment results...\n" + df = df.head(top_k) + + # Format the result + output_str = "" + for _idx, row in df.iterrows(): + output_str += ( + f"Rank: {row['rank']}\n" + f"Path Name: {row['path_name']}\n" + f"P-value: {row['p_val']:.2e}\n" + f"Z-score: {row['z_score']:.6f}\n" + f"Combined Score: {row['combined_score']:.6f}\n" + f"Overlapping Genes: {', '.join(row['overlapping_genes'])}\n" + f"Adjusted P-value: {row['adj_p_val']:.2e}\n" + f"Database: {row['database']}\n" + "----------------------------------------\n" + ) + + steps_log += output_str + + return steps_log + + except Exception as e: + return f"An error occurred: {e}" + + +def analyze_chromatin_interactions(hic_file_path, regulatory_elements_bed, output_dir="./output"): + """Analyze chromatin interactions from Hi-C data to identify enhancer-promoter interactions and TADs. + + Parameters + ---------- + hic_file_path : str + Path to the Hi-C data file (.cool or .hic format) + regulatory_elements_bed : str + Path to BED file containing genomic coordinates of regulatory elements + (enhancers, promoters, CTCF sites, etc.) + output_dir : str + Directory to save output files (default: "./output") + + Returns + ------- + str + Research log summarizing the analysis steps and results + + """ + import cooler + import numpy as np + import pandas as pd + from scipy import stats + + # Create output directory if it doesn't exist + os.makedirs(output_dir, exist_ok=True) + + # Initialize research log + log = [] + log.append("# Chromatin Interaction Analysis\n") + + # Step 1: Load Hi-C data + log.append("## Step 1: Loading Hi-C data") + try: + c = cooler.Cooler(hic_file_path) + log.append(f"Successfully loaded Hi-C data from {hic_file_path}") + log.append(f"Resolution: {c.binsize} bp") + log.append(f"Chromosomes: {c.chromnames}") + + # Check if balancing weights are available + has_weights = False + try: + # Try different ways to check for weights + if ( + hasattr(c.bins(), "columns") + and "weight" in c.bins().columns + or hasattr(c.bins(), "keys") + and "weight" in c.bins() + or hasattr(c, "bins") + and callable(c.bins) + and "bins/weight" in c.bins() + ): + has_weights = True + except Exception as e: + log.append(f"Warning: Could not check for balancing weights: {str(e)}") + + if not has_weights: + log.append("Warning: No balancing weights found in the cooler file. Using unbalanced data.") + + # Function to get matrix with proper balancing option + def get_matrix(chrom, balance_if_possible=True): + try: + # Try to get balanced matrix if requested and available + if balance_if_possible and has_weights: + return c.matrix(balance=True).fetch(chrom) + else: + return c.matrix(balance=False).fetch(chrom) + except Exception as e: + log.append(f"Warning: Error getting matrix with balance={balance_if_possible}: {str(e)}") + # If balanced fails, try unbalanced as fallback + if balance_if_possible: + log.append("Falling back to unbalanced matrix") + return c.matrix(balance=False).fetch(chrom) + else: + # If even unbalanced fails, raise the exception + raise e + + except Exception as e: + log.append(f"Error loading Hi-C data: {str(e)}") + return "\n".join(log) + + # Step 2: Load regulatory elements + log.append("\n## Step 2: Loading regulatory elements") + try: + reg_elements = pd.read_csv( + regulatory_elements_bed, + sep="\t", + names=["chrom", "start", "end", "name", "score", "strand"], + ) + log.append(f"Loaded {len(reg_elements)} regulatory elements") + + # Separate enhancers and promoters based on name field (assuming naming convention) + enhancers = reg_elements[reg_elements["name"].str.contains("enhancer", case=False)] + promoters = reg_elements[reg_elements["name"].str.contains("promoter", case=False)] + log.append(f"Identified {len(enhancers)} enhancers and {len(promoters)} promoters") + except Exception as e: + log.append(f"Error loading regulatory elements: {str(e)}") + return "\n".join(log) + + # Step 3: Normalize Hi-C matrix using iterative correction + log.append("\n## Step 3: Normalizing Hi-C contact matrix") + try: + # Cooler already implements iterative correction (IC) + log.append("Applying iterative correction to normalize for technical biases") + except Exception as e: + log.append(f"Error during normalization: {str(e)}") + + # Step 4: Identify significant interactions between regulatory elements + log.append("\n## Step 4: Identifying significant interactions") + + # Initialize results dataframe for enhancer-promoter interactions + interactions = [] + + try: + # Process chromosome by chromosome + for chrom in c.chromnames: + log.append(f"Processing chromosome {chrom}...") + + # Get matrix for this chromosome using the helper function + try: + matrix = get_matrix(chrom, balance_if_possible=True) + except Exception as e: + log.append(f"Error getting matrix for chromosome {chrom}: {str(e)}") + log.append(f"Skipping chromosome {chrom}") + continue + + # Filter enhancers and promoters for this chromosome + chrom_enhancers = enhancers[enhancers["chrom"] == chrom] + chrom_promoters = promoters[promoters["chrom"] == chrom] + + if len(chrom_enhancers) == 0 or len(chrom_promoters) == 0: + log.append(f"No enhancers or promoters found on chromosome {chrom}, skipping") + continue + + # For each enhancer, find interactions with promoters + for _, enh in chrom_enhancers.iterrows(): + enh_bin = int(enh["start"] // c.binsize) + + for _, prom in chrom_promoters.iterrows(): + prom_bin = int(prom["start"] // c.binsize) + + # Skip if bins are out of range + if enh_bin >= matrix.shape[0] or prom_bin >= matrix.shape[0]: + continue + + # Get interaction strength + interaction_strength = matrix[enh_bin, prom_bin] + + # Calculate expected interaction based on genomic distance + distance = abs(enh_bin - prom_bin) + if distance == 0: + continue + + # Get average interaction at this distance + diag_values = np.diagonal(matrix, offset=distance) + expected = np.nanmean(diag_values) if len(diag_values) > 0 else 0 + + # Calculate significance (fold enrichment) + fold_enrichment = interaction_strength / expected if expected > 0 else 0 + + # Store significant interactions (fold enrichment > 2) + if fold_enrichment > 2: + interactions.append( + { + "chrom": chrom, + "enhancer_start": enh["start"], + "enhancer_end": enh["end"], + "enhancer_name": enh["name"], + "promoter_start": prom["start"], + "promoter_end": prom["end"], + "promoter_name": prom["name"], + "interaction_strength": interaction_strength, + "fold_enrichment": fold_enrichment, + } + ) + + # Define interactions file path (moved before the empty check) + interactions_file = os.path.join(output_dir, "enhancer_promoter_interactions.tsv") + + # Save interactions to file if any were found + if interactions: + interactions_df = pd.DataFrame(interactions) + interactions_df.to_csv(interactions_file, sep="\t", index=False) + log.append(f"Identified {len(interactions)} significant enhancer-promoter interactions") + log.append(f"Saved interactions to {interactions_file}") + else: + # Create an empty file with headers if no interactions were found + pd.DataFrame( + columns=[ + "chrom", + "enhancer_start", + "enhancer_end", + "enhancer_name", + "promoter_start", + "promoter_end", + "promoter_name", + "interaction_strength", + "fold_enrichment", + ] + ).to_csv(interactions_file, sep="\t", index=False) + log.append("No significant enhancer-promoter interactions were identified") + log.append(f"Created empty interactions file at {interactions_file}") + except Exception as e: + log.append(f"Error identifying interactions: {str(e)}") + # Ensure the interactions_file variable is defined even in case of error + interactions_file = os.path.join(output_dir, "enhancer_promoter_interactions.tsv") + + # Step 5: Call TADs using simple insulation score method + log.append("\n## Step 5: Identifying Topologically Associated Domains (TADs)") + + # Define tads_file early to ensure it's always available + tads_file = os.path.join(output_dir, "topological_domains.bed") + + try: + # We'll use a simple approach to identify TADs based on insulation scores + # For a real analysis, specialized tools like tadbit or HiCExplorer would be better + + tad_results = [] + window_size = 5 # Window size for insulation score calculation + + for chrom in c.chromnames: + if chrom not in c.chromsizes: + continue + + # Get matrix with balancing if possible + try: + matrix = get_matrix(chrom, balance_if_possible=True) + except Exception as e: + log.append(f"Error getting matrix for chromosome {chrom}: {str(e)}") + log.append(f"Skipping chromosome {chrom}") + continue + + # Calculate insulation score + insulation_score = np.zeros(matrix.shape[0] - 2 * window_size) + for i in range(window_size, matrix.shape[0] - window_size): + # Calculate sum of interactions crossing bin i + cross_interactions = np.sum(matrix[i - window_size : i, i : i + window_size]) + insulation_score[i - window_size] = cross_interactions + + # Normalize insulation score + if len(insulation_score) > 0: + insulation_score = stats.zscore(insulation_score, nan_policy="omit") + + # Find local minima as TAD boundaries + boundaries = [] + for i in range(1, len(insulation_score) - 1): + if ( + insulation_score[i] < insulation_score[i - 1] + and insulation_score[i] < insulation_score[i + 1] + and insulation_score[i] < -1 + ): # Threshold for boundary strength + boundaries.append(i + window_size) + + # Define TADs as regions between boundaries + if len(boundaries) > 1: + for i in range(len(boundaries) - 1): + start_bin = boundaries[i] + end_bin = boundaries[i + 1] + + # Convert bin positions to genomic coordinates + start_pos = start_bin * c.binsize + end_pos = end_bin * c.binsize + + tad_results.append( + { + "chrom": chrom, + "start": start_pos, + "end": end_pos, + "size_kb": (end_pos - start_pos) / 1000, + } + ) + + # Save TADs to file if any were found + tads_df = pd.DataFrame(tad_results) + if not tads_df.empty: + tads_df.to_csv(tads_file, sep="\t", index=False) + log.append(f"Identified {len(tad_results)} topologically associated domains") + log.append(f"Average TAD size: {tads_df['size_kb'].mean():.2f} kb") + log.append(f"Saved TADs to {tads_file}") + else: + # Create an empty file with headers if no TADs were found + pd.DataFrame(columns=["chrom", "start", "end", "size_kb"]).to_csv(tads_file, sep="\t", index=False) + log.append("No topologically associated domains were identified") + log.append(f"Created empty TADs file at {tads_file}") + except Exception as e: + log.append(f"Error calling TADs: {str(e)}") + + # Step 6: Generate chromatin interaction map for visualization + log.append("\n## Step 6: Generating chromatin interaction maps") + + # Define matrix_file early to ensure it's always available + matrix_file = os.path.join(output_dir, "contact_matrix.npy") + + try: + # Export normalized matrix for first chromosome as example + if len(c.chromnames) > 0: + chrom = c.chromnames[0] + matrix_file = os.path.join(output_dir, f"{chrom}_contact_matrix.npy") + + # Get matrix with balancing if possible + try: + matrix = get_matrix(chrom, balance_if_possible=True) + np.save(matrix_file, matrix) + log.append(f"Saved contact matrix for {chrom} to {matrix_file}") + except Exception as e: + log.append(f"Error getting matrix for chromosome {chrom}: {str(e)}") + # Create an empty matrix file + np.save(matrix_file, np.zeros((10, 10))) # Small placeholder + log.append(f"Saved empty placeholder matrix to {matrix_file}") + else: + log.append("No chromosomes available for matrix export") + except Exception as e: + log.append(f"Error generating interaction maps: {str(e)}") + + # Step 7: Summary + log.append("\n## Summary") + log.append(f"- Processed Hi-C data at {c.binsize} bp resolution") + log.append(f"- Analyzed {len(reg_elements)} regulatory elements") + + # Make sure these are defined even if empty + interactions = interactions if "interactions" in locals() else [] + tad_results = tad_results if "tad_results" in locals() else [] + + log.append(f"- Identified {len(interactions)} significant enhancer-promoter interactions") + log.append(f"- Called {len(tad_results)} topologically associated domains") + log.append("\nOutput files:") + log.append(f"- Enhancer-promoter interactions: {interactions_file}") + log.append(f"- Topological domains: {tads_file}") + log.append(f"- Contact matrix: {matrix_file}") + + return "\n".join(log) + + +def analyze_comparative_genomics_and_haplotypes(sample_fasta_files, reference_genome_path, output_dir="./output"): + """Perform comparative genomics and haplotype analysis on multiple genome samples. + + This function aligns multiple genome samples to a reference, identifies variants, + analyzes shared and unique genomic regions, and determines haplotype structure. + + Parameters + ---------- + sample_fasta_files : list of str + Paths to FASTA files containing whole-genome sequences to be analyzed + reference_genome_path : str + Path to the reference genome FASTA file + output_dir : str, optional + Directory to store output files (default: "./output") + + Returns + ------- + str + A research log summarizing the analysis steps and results + + """ + import subprocess + import time + from collections import defaultdict + + from Bio import SeqIO + + # Create output directory if it doesn't exist + os.makedirs(output_dir, exist_ok=True) + + # Initialize research log + log = ["# Comparative Genomics and Haplotype Analysis\n"] + log.append(f"Analysis started at: {time.strftime('%Y-%m-%d %H:%M:%S')}\n") + log.append(f"Reference genome: {reference_genome_path}") + log.append(f"Number of samples: {len(sample_fasta_files)}") + log.append(f"Sample files: {', '.join(os.path.basename(f) for f in sample_fasta_files)}\n") + + # Step 1: Index reference genome for alignment + log.append("## Step 1: Preparing Reference Genome") + try: + # Check if BWA is installed + subprocess.run(["bwa"], check=False, stderr=subprocess.PIPE) + + # Index reference genome + log.append("Indexing reference genome with BWA...") + subprocess.run(["bwa", "index", reference_genome_path], check=True, stderr=subprocess.PIPE) + log.append("Reference genome indexed successfully.\n") + except (subprocess.CalledProcessError, FileNotFoundError): + # If BWA is not available, use a simplified approach + log.append("BWA not available. Using simplified sequence comparison approach instead.\n") + + # Step 2: Align samples to reference and identify variants + log.append("## Step 2: Sequence Alignment and Variant Identification") + + # Dictionary to store variants for each sample + variants_by_sample = {} + + # Process each sample + for sample_path in sample_fasta_files: + sample_name = os.path.basename(sample_path).split(".")[0] + log.append(f"Processing sample: {sample_name}") + + # Simplified alignment and variant calling approach + variants = [] + + try: + # Attempt alignment with BWA if available + sam_output = os.path.join(output_dir, f"{sample_name}.sam") + subprocess.run( + ["bwa", "mem", reference_genome_path, sample_path], + stdout=open(sam_output, "w"), + stderr=subprocess.PIPE, + check=True, + ) + log.append(f" - Alignment completed and saved to {sam_output}") + + # Here we would normally parse the SAM file to identify variants + # For simplicity, we'll just note that variants would be identified here + variants = ["Simulated variant detection from SAM file"] + log.append(" - Variant calling would be performed on the SAM file") + + except (subprocess.CalledProcessError, FileNotFoundError): + # Simplified comparison if BWA is not available + log.append(" - Using simplified sequence comparison") + + # Load reference and sample sequences + reference_seqs = {record.id: str(record.seq) for record in SeqIO.parse(reference_genome_path, "fasta")} + sample_seqs = {record.id: str(record.seq) for record in SeqIO.parse(sample_path, "fasta")} + + # Simple variant detection by direct comparison + for seq_id in set(reference_seqs.keys()) & set(sample_seqs.keys()): + ref_seq = reference_seqs[seq_id] + sample_seq = sample_seqs[seq_id] + + # Compare sequences (simplified approach) + min_len = min(len(ref_seq), len(sample_seq)) + for i in range(min_len): + if ref_seq[i] != sample_seq[i]: + variants.append(f"SNP at position {i + 1}: {ref_seq[i]} -> {sample_seq[i]}") + # Limit to first 10 variants for demonstration + if len(variants) >= 10: + variants.append("... (more variants exist)") + break + + log.append(f" - Identified {len(variants)} potential variants") + + # Store variants for this sample + variants_by_sample[sample_name] = variants + + # Step 3: Identify shared and unique genomic regions + log.append("\n## Step 3: Comparative Analysis of Genomic Regions") + + # For demonstration, we'll create a simple comparison of variant patterns + defaultdict(list) + defaultdict(list) + + # Find patterns of shared variants + sample_names = list(variants_by_sample.keys()) + + # This is a simplified approach - in reality, we would do more sophisticated analysis + log.append("Identifying shared and unique genomic regions...") + + # Create a simple representation of shared/unique regions + comparison_results = os.path.join(output_dir, "comparative_analysis.txt") + with open(comparison_results, "w") as f: + f.write("Comparative Analysis of Genomic Regions\n") + f.write("======================================\n\n") + + # Write information about each sample's variants + for sample, vars in variants_by_sample.items(): + f.write(f"Sample: {sample}\n") + f.write(f"Number of variants: {len(vars)}\n") + f.write("Example variants:\n") + for v in vars[:5]: # Show first 5 variants + f.write(f" - {v}\n") + f.write("\n") + + # In a real implementation, we would identify truly shared and unique regions + f.write("Shared Genomic Regions Analysis:\n") + f.write(" This would contain detailed information about genomic regions\n") + f.write(" that are conserved across samples.\n\n") + + f.write("Unique Genomic Regions Analysis:\n") + f.write(" This would contain detailed information about genomic regions\n") + f.write(" that are unique to specific samples.\n") + + log.append(f"Comparative analysis results saved to {comparison_results}") + + # Step 4: Haplotype Analysis + log.append("\n## Step 4: Haplotype Structure Analysis") + + # Simulate haplotype analysis + haplotype_file = os.path.join(output_dir, "haplotype_analysis.txt") + with open(haplotype_file, "w") as f: + f.write("Haplotype Structure Analysis\n") + f.write("===========================\n\n") + + # Group samples by simulated haplotype patterns + f.write("Haplotype Grouping:\n") + + # Create some simulated haplotype groups + import random + + haplotype_groups = {} + for i, sample in enumerate(sample_names): + group = f"Haplotype-{chr(65 + i % 3)}" # A, B, or C + if group not in haplotype_groups: + haplotype_groups[group] = [] + haplotype_groups[group].append(sample) + + # Write haplotype groups + for group, samples in haplotype_groups.items(): + f.write(f" {group}: {', '.join(samples)}\n") + + f.write("\nHaplotype Characteristics:\n") + for group in haplotype_groups: + f.write(f" {group}:\n") + f.write(f" - Distinguishing variants: {random.randint(5, 30)}\n") + f.write(f" - Estimated divergence time: {random.randint(1000, 10000)} years\n") + + log.append(f"Haplotype analysis results saved to {haplotype_file}") + + # Step 5: Summary of findings + log.append("\n## Summary of Findings") + log.append(f"- Analyzed {len(sample_fasta_files)} genome samples against the reference") + log.append("- Identified variant patterns across samples") + log.append(f"- Determined haplotype structure with {len(haplotype_groups)} major groups") + log.append(f"- All results saved to {output_dir}") + + # Return the research log + return "\n".join(log) + + +def perform_chipseq_peak_calling_with_macs2( + chip_seq_file, + control_file, + output_name="macs2_output", + genome_size="hs", + q_value=0.05, +): + """Perform ChIP-seq peak calling using MACS2 to identify genomic regions with significant binding. + + Parameters + ---------- + chip_seq_file : str + Path to the ChIP-seq read data file (BAM, BED, or other supported format) + control_file : str + Path to the control/input data file (BAM, BED, or other supported format) + output_name : str, optional + Prefix for output files (default: "macs2_output") + genome_size : str, optional + Effective genome size shorthand: 'hs' for human, 'mm' for mouse, etc. (default: "hs") + q_value : float, optional + q-value (minimum FDR) cutoff for peak calling (default: 0.05) + + Returns + ------- + str + A research log summarizing the peak calling process and results + + """ + import datetime + import subprocess + + # Create log with timestamp + log = f"ChIP-seq Peak Calling with MACS2 - {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n" + log += "=" * 80 + "\n\n" + + # Validate input files + if not os.path.exists(chip_seq_file): + log += f"Error: ChIP-seq file does not exist: {chip_seq_file}\n" + return log + + if not os.path.exists(control_file): + log += f"Error: Control file does not exist: {control_file}\n" + return log + + # Create output directory if needed + output_dir = os.path.dirname(output_name) + if output_dir and not os.path.exists(output_dir): + try: + os.makedirs(output_dir, exist_ok=True) + log += f"Created output directory: {output_dir}\n" + except Exception as e: + log += f"Error creating output directory: {str(e)}\n" + + # Log input parameters + log += "Input Parameters:\n" + log += f"- ChIP-seq data: {chip_seq_file}\n" + log += f"- Control data: {control_file}\n" + log += f"- Output prefix: {output_name}\n" + log += f"- Genome size: {genome_size}\n" + log += f"- q-value cutoff: {q_value}\n\n" + + # Check if MACS2 is installed + try: + check_process = subprocess.run( + ["macs2", "--version"], + check=False, + capture_output=True, + timeout=5, + ) + log += f"MACS2 is available: {check_process.stdout.decode().strip() if check_process.stdout else check_process.stderr.decode().strip()}\n\n" + except (subprocess.SubprocessError, FileNotFoundError): + log += "Error: MACS2 is not installed or not available in the system PATH.\n" + log += "Please install MACS2 using: pip install macs2\n" + return log + except Exception as e: + log += f"Error checking for MACS2: {str(e)}\n" + + # Construct MACS2 command + macs2_cmd = [ + "macs2", + "callpeak", + "-t", + chip_seq_file, + "-c", + control_file, + "-n", + output_name, + "-g", + genome_size, + "-q", + str(q_value), + ] + + # Add output directory parameter if it exists + if output_dir: + macs2_cmd.extend(["--outdir", output_dir]) + + log += "Executing MACS2 with the following command:\n" + log += " ".join(macs2_cmd) + "\n\n" + + # Define expected output files with full paths + output_dir_prefix = output_dir + "/" if output_dir else "" + expected_files = [ + f"{output_dir_prefix}{output_name}_peaks.xls", + f"{output_dir_prefix}{output_name}_peaks.narrowPeak", + f"{output_dir_prefix}{output_name}_summits.bed", + ] + + try: + # Run MACS2 + log += "Running MACS2 peak calling...\n" + process = subprocess.run(macs2_cmd, capture_output=True, text=True, check=True, timeout=300) + + # Log stdout and stderr (truncate if too long) + stdout_text = process.stdout + if len(stdout_text) > 2000: + stdout_text = stdout_text[:1000] + "\n...[output truncated]...\n" + stdout_text[-1000:] + + stderr_text = process.stderr + if stderr_text and len(stderr_text) > 2000: + stderr_text = stderr_text[:1000] + "\n...[output truncated]...\n" + stderr_text[-1000:] + + log += "MACS2 Standard Output:\n" + log += stdout_text + "\n" + + if stderr_text: + log += "MACS2 Standard Error:\n" + log += stderr_text + "\n" + + # Check for output files + log += "Output Files Generated:\n" + peak_count = 0 + + for file in expected_files: + if os.path.exists(file): + file_size = os.path.getsize(file) + log += f"- {file} ({file_size} bytes)\n" + + # Count peaks in narrowPeak file + if file.endswith("narrowPeak") and os.path.exists(file): + try: + with open(file) as f: + peak_count = sum(1 for _ in f) + log += f"\nTotal peaks identified: {peak_count}\n" + except Exception as e: + log += f"Error reading peak file: {str(e)}\n" + else: + log += f"- {file} (Not found)\n" + + log += "\nPeak calling completed successfully.\n" + + # Add more detailed explanation about the output files + log += "\nOutput Files Explanation:\n" + log += "1. *_peaks.narrowPeak: BED6+4 format file containing peak locations with statistical significance\n" + log += " - Each line represents a peak with chromosome, start, end, and statistical significance\n" + log += " - Additional columns include summit position, -log10(pvalue), -log10(qvalue), and fold enrichment\n" + log += "2. *_summits.bed: BED format file containing the summit locations (highest point) of each peak\n" + log += "3. *_peaks.xls: Tab-delimited file with detailed information about each peak\n" + + # Add basic statistics + log += "\nBasic Statistics:\n" + log += f"- Total peaks called: {peak_count}\n" + + except subprocess.CalledProcessError as e: + log += "Error during MACS2 execution:\n" + if e.stderr: + log += e.stderr + "\n" + log += "Peak calling failed.\n" + except subprocess.TimeoutExpired: + log += "Error: MACS2 execution timed out after 300 seconds.\n" + log += "This may happen with very large input files or insufficient computational resources.\n" + except Exception as e: + log += f"An unexpected error occurred: {str(e)}\n" + + return log + + +def find_enriched_motifs_with_homer( + peak_file, + genome="hg38", + background_file=None, + motif_length="8,10,12", + output_dir="./homer_motifs", + num_motifs=10, + threads=4, +): + """Find DNA sequence motifs enriched in genomic regions using the HOMER motif discovery software. + + Parameters + ---------- + peak_file : str + Path to peak file in BED format containing genomic regions to analyze for motif enrichment + genome : str, optional + Reference genome for sequence extraction (default: "hg38") + background_file : str, optional + Path to BED file with background regions for comparison. If None, HOMER will generate random + background sequences automatically (default: None) + motif_length : str, optional + Comma-separated list of motif lengths to discover (default: "8,10,12") + output_dir : str, optional + Directory to save output files (default: "./homer_motifs") + num_motifs : int, optional + Number of motifs to find (default: 10) + threads : int, optional + Number of CPU threads to use (default: 4) + + Returns + ------- + str + A research log summarizing the motif discovery process and results + + """ + import datetime + import glob + import subprocess + + # Create output directory if it doesn't exist + os.makedirs(output_dir, exist_ok=True) + + # Initialize research log + log = f"Motif Discovery with HOMER - {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n" + log += "=" * 80 + "\n\n" + + # Log input parameters + log += "Input Parameters:\n" + log += f"- Peak file: {peak_file}\n" + log += f"- Reference genome: {genome}\n" + log += f"- Background: {'User-defined' if background_file else 'HOMER-generated'}\n" + log += f"- Motif lengths: {motif_length}\n" + log += f"- Number of motifs: {num_motifs}\n" + log += f"- Threads: {threads}\n\n" + + # Construct HOMER findMotifsGenome.pl command + homer_cmd = [ + "findMotifsGenome.pl", + peak_file, + genome, + output_dir, + "-size", + "200", # Default search region size (200bp centered on peak) + "-len", + motif_length, + "-S", + str(num_motifs), + "-p", + str(threads), + ] + + # Add background file if provided + if background_file: + homer_cmd.extend(["-bg", background_file]) + + log += "Executing HOMER with the following command:\n" + log += " ".join(homer_cmd) + "\n\n" + + try: + # Run HOMER + log += "Running HOMER motif discovery...\n" + process = subprocess.run(homer_cmd, capture_output=True, text=True, check=True) + + # Log stdout and stderr (truncate if too long) + stdout_text = process.stdout + if len(stdout_text) > 2000: + stdout_text = stdout_text[:1000] + "\n...[output truncated]...\n" + stdout_text[-1000:] + + log += "HOMER Standard Output (truncated if long):\n" + log += stdout_text + "\n" + + # Check and parse results + log += "\n## Motif Discovery Results\n\n" + + # Look for HTML results file + html_results = os.path.join(output_dir, "homerResults.html") + if os.path.exists(html_results): + log += f"HOMER results saved to {html_results}\n\n" + + # Parse motif files + motif_files = glob.glob(os.path.join(output_dir, "motif*.motif")) + motif_files.sort() + + if motif_files: + log += f"Found {len(motif_files)} enriched motifs:\n\n" + + # Parse each motif file for information + for motif_file in motif_files[:5]: # Show info for top 5 motifs only + with open(motif_file) as f: + header = f.readline().strip() + # Extract motif name, p-value, etc. from header + # Example header: >ATGCGCTA MA0139.1(CTCF)/Jaspar 1e-10 -1.234e+02 0 0.00% + parts = header.split("\t") + if len(parts) >= 3: + motif_id = os.path.basename(motif_file) + motif_consensus = parts[0].replace(">", "") + + # Try to extract transcription factor info if available + tf_match = "" + if len(parts) > 1: + tf_match = parts[1] + + # Extract p-value if available + p_value = "" + if len(parts) > 2: + p_value = parts[2] + + log += f"Motif: {motif_id}\n" + log += f"- Consensus: {motif_consensus}\n" + log += f"- Matching TF: {tf_match}\n" + log += f"- P-value: {p_value}\n\n" + + if len(motif_files) > 5: + log += f"... and {len(motif_files) - 5} more motifs ...\n\n" + else: + log += "No significant motifs were found.\n\n" + + # Check for known motif enrichment results + known_results = os.path.join(output_dir, "knownResults.html") + if os.path.exists(known_results): + log += "Known motif enrichment results are available in knownResults.html\n" + + # Try to parse known motifs results + known_motifs_txt = os.path.join(output_dir, "knownResults.txt") + if os.path.exists(known_motifs_txt): + with open(known_motifs_txt) as f: + lines = f.readlines() + if len(lines) > 1: # Header + at least one motif + log += "\nTop known motifs enriched in the dataset:\n" + # Show top 3 known motifs if available + for line in lines[1:4]: # Skip header, show top 3 + parts = line.strip().split("\t") + if len(parts) >= 4: + log += f"- {parts[0]} (p-value: {parts[2]})\n" + + # Summarize results + log += "\n## Summary\n" + log += f"- Analyzed genomic regions from {peak_file}\n" + log += f"- Discovered {len(motif_files)} enriched motifs\n" + log += f"- All results saved to {output_dir}\n" + + except subprocess.CalledProcessError as e: + log += "Error during HOMER execution:\n" + log += e.stderr + "\n" + log += "Motif discovery failed.\n" + except Exception as e: + log += f"An unexpected error occurred: {str(e)}\n" + + return log + + +def analyze_genomic_region_overlap(region_sets, output_prefix="overlap_analysis"): + """Analyze overlaps between two or more sets of genomic regions. + + Parameters + ---------- + region_sets : list + List of genomic region sets. Each item can be either: + - A string path to a BED file + - A list of tuples/lists with format (chrom, start, end) or (chrom, start, end, name) + output_prefix : str, optional + Prefix for output files (default: "overlap_analysis") + + Returns + ------- + str + Research log summarizing the analysis steps and results + + """ + from datetime import datetime + + import pandas as pd + import pybedtools + + # Start research log + log = "# Genomic Region Overlap Analysis\n" + log += f"Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n" + + # Create BedTool objects from inputs + bedtools = [] + set_names = [] + + log += "## Input Processing\n" + for i, regions in enumerate(region_sets): + set_name = f"Region_Set_{i + 1}" + set_names.append(set_name) + + if isinstance(regions, str) and os.path.exists(regions): + # Input is a file path + bedtool = pybedtools.BedTool(regions) + log += f"- Loaded {set_name} from file: {regions}\n" + else: + # Input is a list of coordinates + temp_bed_file = f"{output_prefix}_{set_name}.bed" + with open(temp_bed_file, "w") as f: + for region in regions: + if len(region) >= 3: + chrom, start, end = region[0], region[1], region[2] + name = region[3] if len(region) > 3 else f"feature_{i}" + f.write(f"{chrom}\t{start}\t{end}\t{name}\n") + bedtool = pybedtools.BedTool(temp_bed_file) + log += f"- Created {set_name} from {len(regions)} provided coordinates\n" + + bedtools.append(bedtool) + + log += "\n## Analysis Results\n" + + # Calculate basic statistics for each set + stats = [] + for i, bt in enumerate(bedtools): + # Calculate total base pairs from start and end coordinates + total_bp = sum(int(feature.end) - int(feature.start) for feature in bt) + stats.append({"Set": set_names[i], "Regions": len(bt), "Total_BP": total_bp}) + + # Create a results dataframe to store pairwise overlaps + results = [] + + # Perform pairwise overlaps + for i in range(len(bedtools)): + for j in range(i + 1, len(bedtools)): + # Find overlaps using a simpler approach - just get the overlap regions + try: + # Use intersect with -u option to get unique overlapping features + # and -wa option to get the original features from the first set + overlapping_regions_a = bedtools[i].intersect(bedtools[j], u=True) + + # Get overlapping features from the second set + overlapping_regions_b = bedtools[j].intersect(bedtools[i], u=True) + + # Calculate statistics + overlap_regions_count_a = len(overlapping_regions_a) + overlap_regions_count_b = len(overlapping_regions_b) + + # Now find the actual overlap coordinates using intersect with -wo + # -wo outputs the original features plus the overlap length + overlap_with_bases = bedtools[i].intersect(bedtools[j], wo=True) + + if len(overlap_with_bases) > 0: + # Extract overlap base pairs from the last column of -wo output + overlap_bp = 0 + unique_overlaps = set() + + for line in overlap_with_bases: + # The last field in -wo output is the overlap size in bases + # Convert all fields to strings first to handle different data types + fields = [str(field) for field in line] + + # The actual overlap width is usually the last column with -wo + try: + # Get overlap width from last field + overlap_width = int(fields[-1]) + overlap_bp += overlap_width + + # Create a unique identifier for this overlap + # Using the coordinates from both sets + chrom_a = fields[0] + start_a = int(fields[1]) + end_a = int(fields[2]) + chrom_b = fields[len(fields) // 2] # Middle field is typically the start of second feature + identifier = f"{chrom_a}:{start_a}-{end_a}_{chrom_b}" + unique_overlaps.add(identifier) + except (ValueError, IndexError) as e: + # If parsing fails, try an alternative approach + log += f" Warning: Couldn't parse overlap width, falling back to alternative method: {str(e)}\n" + + # Alternative: manually calculate overlap from coordinates + try: + midpoint = len(fields) // 2 + start_a = int(fields[1]) + end_a = int(fields[2]) + start_b = int(fields[midpoint + 1]) + end_b = int(fields[midpoint + 2]) + + overlap_start = max(start_a, start_b) + overlap_end = min(end_a, end_b) + + if overlap_end > overlap_start: + overlap_bp += overlap_end - overlap_start + except (ValueError, IndexError): + # If even this fails, log the issue and continue + log += f" Warning: Couldn't calculate overlap from: {fields}\n" + + # Number of unique overlapping regions + overlap_regions = len(unique_overlaps) + + # Calculate percentages + pct_of_set1 = (overlap_bp / stats[i]["Total_BP"]) * 100 if stats[i]["Total_BP"] > 0 else 0 + pct_of_set2 = (overlap_bp / stats[j]["Total_BP"]) * 100 if stats[j]["Total_BP"] > 0 else 0 + + results.append( + { + "Set1": set_names[i], + "Set2": set_names[j], + "Overlap_Regions": overlap_regions, + "Overlap_BP": overlap_bp, + "Pct_of_Set1": pct_of_set1, + "Pct_of_Set2": pct_of_set2, + } + ) + + # Save detailed overlaps to file + overlap_file = f"{output_prefix}_{set_names[i]}_{set_names[j]}_overlaps.bed" + overlap_with_bases.saveas(overlap_file) + + log += f"- Between {set_names[i]} and {set_names[j]}:\n" + log += f" * Overlapping regions from set 1: {overlap_regions_count_a}\n" + log += f" * Overlapping regions from set 2: {overlap_regions_count_b}\n" + log += f" * Unique overlaps: {overlap_regions}\n" + log += f" * Total overlap size: {overlap_bp} bp\n" + log += f" * Percentage of {set_names[i]}: {pct_of_set1:.2f}%\n" + log += f" * Percentage of {set_names[j]}: {pct_of_set2:.2f}%\n" + log += f" * Detailed overlaps saved to: {overlap_file}\n\n" + else: + # No overlaps found + results.append( + { + "Set1": set_names[i], + "Set2": set_names[j], + "Overlap_Regions": 0, + "Overlap_BP": 0, + "Pct_of_Set1": 0, + "Pct_of_Set2": 0, + } + ) + log += f"- No overlaps found between {set_names[i]} and {set_names[j]}\n\n" + except Exception as e: + # Handle any errors during the intersection process + log += f"Error analyzing overlap between {set_names[i]} and {set_names[j]}: {str(e)}\n" + results.append( + { + "Set1": set_names[i], + "Set2": set_names[j], + "Overlap_Regions": 0, + "Overlap_BP": 0, + "Pct_of_Set1": 0, + "Pct_of_Set2": 0, + "Error": str(e), + } + ) + + # Save summary statistics to file + summary_file = f"{output_prefix}_summary.tsv" + summary_df = pd.DataFrame(results) + summary_df.to_csv(summary_file, sep="\t", index=False) + + log += "## Summary\n" + log += f"- Total sets analyzed: {len(bedtools)}\n" + log += f"- Pairwise comparisons: {len(results)}\n" + log += f"- Summary statistics saved to: {summary_file}\n" + + return log + + +def generate_embeddings_with_state( + adata_filename, + data_dir, + model_folder, + output_filename=None, + checkpoint=None, + embed_key="X_state", + protein_embeddings=None, + batch_size=500, +): + """ + Generate State embeddings for single-cell RNA-seq data using the SE-600M model. + + This function downloads the SE-600M model from Hugging Face, installs required dependencies, + and generates embeddings for the input AnnData object. + + Parameters: + ----------- + adata_filename : str + Name of the input AnnData file (.h5ad format) + data_dir : str + Directory containing the input data file + model_folder : str + Directory where the SE-600M model will be downloaded and stored + output_filename : str, optional + Name of the output file. If None, will use input filename with '_state_embeddings' suffix + checkpoint : str, optional + Path to the specific model checkpoint. If None, uses the latest checkpoint in model_folder + embed_key : str, default="X_state" + Name of key to store embeddings in the output AnnData object + protein_embeddings : str, optional + Path to protein embeddings override (.pt). If omitted, auto-detects in model folder + batch_size : int, default=500 + Batch size for embedding forward pass. Increase to use more VRAM and speed up embedding + + Returns: + -------- + str + Research log summarizing the steps performed and results + + Notes: + ------ + - Requires 10GB+ GPU memory for model loading + - Downloads ~25GB model files from Hugging Face + - Installs git-lfs, uv, and arc-state if not already available + - Validates input h5ad file format (CSR matrix and gene_name column) + - Provides real-time streaming output for git clone and embedding generation + - Includes automatic retry with reduced batch size on failure + """ + import os + import subprocess + + # Initialize steps list for logging + steps = [] + + # Set up paths + input_path = os.path.join(data_dir, adata_filename) + if output_filename is None: + base_name = os.path.splitext(adata_filename)[0] + output_filename = f"{base_name}_state_embeddings.h5ad" + + output_path = os.path.join(data_dir, "output", output_filename) + + os.makedirs(os.path.dirname(output_path), exist_ok=True) + steps.append(f"Created output directory: {os.path.dirname(output_path)}") + + # Validate h5ad file format + steps.append("Validating h5ad file format...") + try: + import scanpy as sc + + adata = sc.read_h5ad(input_path) + + import warnings + + if not hasattr(adata.X, "indices"): + warning_msg = ( + "Input data is not in CSR (Compressed Sparse Row) matrix format. Proceeding, but this may cause issues." + ) + warnings.warn(warning_msg, stacklevel=2) + steps.append(f"Warning: {warning_msg}") + + if "gene_name" not in adata.var.columns: + warning_msg = ( + "'gene_name' column is not present in the var dataframe. Proceeding, but this may cause issues." + ) + warnings.warn(warning_msg, stacklevel=2) + steps.append(f"Warning: {warning_msg}") + + steps.append(f" - Matrix format: {type(adata.X).__name__}") + steps.append(f" - Number of cells: {adata.n_obs}") + steps.append(f" - Number of genes: {adata.n_vars}") + steps.append(f" - Gene names available: {'gene_name' in adata.var.columns}") + + except Exception as e: + error_msg = f"h5ad file validation failed: {e}" + steps.append(error_msg) + raise ValueError(error_msg) from e + + # Check if git-lfs is installed + steps.append("Checking if git-lfs is installed...") + try: + subprocess.run(["git", "lfs", "version"], check=True, capture_output=True) + steps.append("git-lfs is available") + except (subprocess.CalledProcessError, FileNotFoundError) as e: + error_msg = ( + "git-lfs is not installed or not found in PATH. " + "Please install git-lfs before running this function.\n" + "Run the following command with root privileges:\n" + "sudo apt-get install git-lfs\n" + "git lfs install" + ) + steps.append(error_msg) + raise RuntimeError(error_msg) from e + + steps.append("Downloading SE-600M model...") + + # Check if model weights actually exist (not just the directory) + model_exists = False + if os.path.exists(model_folder): + # Check for SE-600M specific checkpoint files + checkpoint_indicators = ["se600m_epoch16.ckpt", "se600m_epoch4.ckpt", "model.safetensors", "config.yaml"] + model_exists = any(os.path.exists(os.path.join(model_folder, indicator)) for indicator in checkpoint_indicators) + + if not model_exists: + os.makedirs(model_folder, exist_ok=True) + steps.append(f"Created model directory: {model_folder}") + try: + steps.append(f"Cloning SE-600M model to {model_folder}") + steps.append("This may take a while and final model weights are approx 25GB!...") + process = subprocess.Popen( + ["git", "clone", "https://huggingface.co/arcinstitute/SE-600M", model_folder], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + bufsize=1, + universal_newlines=True, + ) + + for line in iter(process.stdout.readline, ""): + line = line.rstrip() + print(line) + steps.append(line) + + process.wait() + + if process.returncode != 0: + raise subprocess.CalledProcessError( + process.returncode, ["git", "clone", "https://huggingface.co/arcinstitute/SE-600M", model_folder] + ) + steps.append("SE-600M model downloaded successfully") + except subprocess.CalledProcessError as e: + error_msg = f"Error downloading model: {e}" + steps.append(error_msg) + raise + else: + steps.append(f"Model already downloaded: {model_folder}") + + steps.append("Generating embeddings...") + + if torch.cuda.is_available(): + device_name = torch.cuda.get_device_name(0) + gpu_memory = torch.cuda.get_device_properties(0).total_memory / (1024**3) + current_device = torch.cuda.current_device() + steps.append(f"✓ GPU detected: {device_name}") + steps.append(f"✓ GPU memory: {gpu_memory:.1f} GB") + steps.append(f"✓ Current CUDA device: {current_device}") + if gpu_memory < 10: + warning_msg = f"⚠️ WARNING: GPU has only {gpu_memory:.1f} GB memory. SE600 model requires 10GB+ GPU memory." + steps.append(warning_msg) + else: + steps.append("⚠️ WARNING: No GPU detected! This will run on CPU and be very slow.") + steps.append("⚠️ SE600 model requires 10GB+ GPU memory for optimal performance.") + + cuda_visible = os.environ.get("CUDA_VISIBLE_DEVICES", "Not set") + steps.append(f"CUDA_VISIBLE_DEVICES: {cuda_visible}") + + steps.append(f"PyTorch CUDA available: {torch.cuda.is_available()}") + if torch.cuda.is_available(): + steps.append(f"PyTorch CUDA device count: {torch.cuda.device_count()}") + steps.append(f"PyTorch current device: {torch.cuda.current_device()}") + + steps.append("embedding data can take a while...") + max_retries = 3 + retries = 0 + last_exception = None + + while retries <= max_retries: + try: + cmd = [ + "uv", + "run", + "state", + "emb", + "transform", + "--model-folder", + model_folder, + "--input", + input_path, + "--output", + output_path, + ] + + if checkpoint: + cmd.extend(["--checkpoint", checkpoint]) + if embed_key != "X_state": + cmd.extend(["--embed-key", embed_key]) + if protein_embeddings: + cmd.extend(["--protein-embeddings", protein_embeddings]) + if batch_size: + cmd.extend(["--batch-size", str(batch_size)]) + + steps.append(f"Running command: {' '.join(cmd)}") + process = subprocess.Popen( + cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1, universal_newlines=True + ) + + for line in iter(process.stdout.readline, ""): + line = line.rstrip() + print(line) # Print to console first + steps.append(line) # Then add to steps list + + process.wait() + + if process.returncode != 0: + raise subprocess.CalledProcessError(process.returncode, cmd) + + steps.append("Embeddings generated successfully!") + steps.append("Output Format and Location:") + steps.append(" Output format: .h5ad (AnnData format)") + steps.append(" Embeddings stored in: X_state layer (by default)") + steps.append(" Location: Specified by --output parameter") + steps.append(f" In your code: Saved to {output_path}") + steps.append("") + return "\n".join(steps) + + except subprocess.CalledProcessError as e: + error_msg = f"Error generating embeddings (attempt {retries + 1}): {e}" + steps.append(error_msg) + last_exception = e + if batch_size and retries < max_retries: + batch_size = max(1, batch_size // 2) + steps.append(f"Retrying with reduced batch size: {batch_size}") + retries += 1 + else: + break + + steps.append("Failed to generate embeddings after multiple attempts.") + if last_exception: + steps.append(f"Last exception: {last_exception}") + raise last_exception + + return "\n".join(steps) + + +def generate_transcriptformer_embeddings( + adata_filename: str, + data_dir: str, + output_filename: str = None, + model_type: str = "tf-sapiens", + checkpoint_path: str = None, + batch_size: int = 8, + precision: str = "16-mixed", + clip_counts: int = 30, + embedding_layer_index: int = -1, + num_gpus: int = 1, + n_data_workers: int = 0, + gene_col_name: str = "ensembl_id", + pretrained_embedding: str = None, + filter_to_vocabs: bool = True, + use_raw: str = "None", + emb_type: str = "cell", + remove_duplicate_genes: bool = False, + oom_dataloader: bool = False, +) -> str: + """ + Generate Transcriptformer embeddings for single-cell RNA-seq data. + + Parameters: + ----------- + adata_filename : str + Name of the input AnnData file (.h5ad format) + data_dir : str + Directory containing the input data file + output_filename : str, optional + Name of the output embeddings file. If None, will use input filename with '_transcriptformer_embeddings' suffix + model_type : str, optional + Type of transcriptformer model to download and use. Options: "tf-sapiens", "tf-exemplar", "tf-metazoa" (default: "tf-sapiens") + checkpoint_path : str, optional + Path to the transcriptformer checkpoint directory. If None, will use "./checkpoints/{model_type}" (default: None) + batch_size : int, optional + Batch size for inference (default: 8) + precision : str, optional + Precision for inference. Options: "16-mixed", "32" (default: "16-mixed") + clip_counts : int, optional + Maximum count value to clip to (default: 30) + embedding_layer_index : int, optional + Which layer to extract embeddings from (default: -1, last layer) + num_gpus : int, optional + Number of GPUs to use (default: 1) + n_data_workers : int, optional + Number of data loading workers (default: 0) + gene_col_name : str, optional + Column name in AnnData.var containing gene identifiers (default: "ensembl_id") + pretrained_embedding : str, optional + Path to pretrained embeddings for out-of-distribution species (default: None) + filter_to_vocabs : bool, optional + Whether to filter genes to only those in the vocabulary (default: True) + use_raw : str, optional + Whether to use raw counts from AnnData.raw.X (True), adata.X (False), or auto-detect (None/auto) (default: "None") + emb_type : str, optional + Type of embeddings to extract: 'cell' for mean-pooled cell embeddings or 'cge' for contextual gene embeddings (default: "cell") + remove_duplicate_genes : bool, optional + Remove duplicate genes if found instead of raising an error (default: False) + oom_dataloader : bool, optional + Use map-style out-of-memory DataLoader (DistributedSampler-friendly) (default: False) + Returns: + -------- + str + Path to the generated embeddings file + """ + import os + import subprocess + + import pandas as pd + import scanpy as sc + import torch + + if checkpoint_path is None: + checkpoint_path = f"./checkpoints/{model_type}" + + input_path = os.path.join(data_dir, adata_filename) + if output_filename is None: + base_name = os.path.splitext(adata_filename)[0] + output_filename = f"{base_name}_transcriptformer_embeddings.h5ad" + + output_path = os.path.join(data_dir, "output", output_filename) + temp_data_path = os.path.join(data_dir, "temp_transcriptformer_input.h5ad") + + os.makedirs(os.path.dirname(output_path), exist_ok=True) + print("Checking GPU availability...") + if torch.cuda.is_available(): + device_name = torch.cuda.get_device_name(0) + gpu_memory = torch.cuda.get_device_properties(0).total_memory / (1024**3) + current_device = torch.cuda.current_device() + print(f"✓ GPU detected: {device_name}") + print(f"✓ GPU memory: {gpu_memory:.1f} GB") + print(f"✓ Current CUDA device: {current_device}") + else: + print("⚠️ WARNING: No GPU detected! This will run on CPU and be very slow.") + print("⚠️ Transcriptformer requires GPU for optimal performance.") + + import os + + cuda_visible = os.environ.get("CUDA_VISIBLE_DEVICES", "Not set") + print(f"CUDA_VISIBLE_DEVICES: {cuda_visible}") + + print(f"PyTorch CUDA available: {torch.cuda.is_available()}") + if torch.cuda.is_available(): + print(f"PyTorch CUDA device count: {torch.cuda.device_count()}") + print(f"PyTorch current device: {torch.cuda.current_device()}") + + try: + os.makedirs(checkpoint_path, exist_ok=True) + + def is_model_downloaded(checkpoint_path, model_type): + """Check if model is already downloaded by looking for key files""" + required_files = [ + "config.yaml", + "pytorch_model.bin", + ] + + for file_name in required_files: + file_path = os.path.join(checkpoint_path, file_name) + if not os.path.exists(file_path): + return False + + return True + + if is_model_downloaded(checkpoint_path, model_type): + print(f"✓ Model checkpoints for {model_type} already exist at: {checkpoint_path}") + print("✓ Skipping download...") + else: + print(f"Downloading transcriptformer model checkpoints for {model_type}...") + print("This may take a while and model checkpoints are several GB!...") + print(f"Model will be saved to: {checkpoint_path}") + + try: + process = subprocess.Popen( + ["transcriptformer", "download", model_type], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + bufsize=1, + universal_newlines=True, + ) + + for line in iter(process.stdout.readline, ""): + print(line.rstrip()) + + process.wait() + + if process.returncode != 0: + raise subprocess.CalledProcessError( + process.returncode, ["transcriptformer", "download", model_type] + ) + print(f"✓ Model checkpoints for {model_type} downloaded successfully") + except subprocess.CalledProcessError as e: + print(f"Error downloading model checkpoints: {e}") + raise + + print("Loading and preparing AnnData object...") + try: + adata = sc.read_h5ad(input_path) + print(f"✓ Loaded AnnData with {adata.n_obs} cells and {adata.n_vars} genes") + + def check_ensembl_patterns(gene_index): + patterns = { + "human_ensembl": r"^ENSG\d{11}(\.\d+)?$", + "mouse_ensembl": r"^ENSMUSG\d{11}(\.\d+)?$", + "zebrafish_ensembl": r"^ENSDARG\d{11}(\.\d+)?$", + "fly_ensembl": r"^FBgn\d{7}$", + "worm_ensembl": r"^WBGene\d{8}$", + "yeast_ensembl": r"^Y[A-P][LR]\d{3}[WC]?$", + "generic_ensembl": r"^ENS[A-Z]{3}G\d{11}(\.\d+)?$", + } + + results = {} + for pattern_name, pattern in patterns.items(): + matches = gene_index.str.match(pattern, na=False) + count = matches.sum() + results[pattern_name] = { + "count": count, + "percentage": (count / len(gene_index)) * 100, + "matches": gene_index[matches].tolist()[:5] if count > 0 else [], + } + + return results + + print("Analyzing gene index for Ensembl ID patterns...") + ensembl_analysis = check_ensembl_patterns(adata.var.index) + + for pattern_name, data in ensembl_analysis.items(): + if data["count"] > 0: + print(f"✓ {pattern_name}: {data['count']} genes ({data['percentage']:.1f}%)") + if data["matches"]: + print(f" Examples: {data['matches']}") + + best_pattern = max(ensembl_analysis.items(), key=lambda x: x[1]["count"]) + if best_pattern[1]["count"] > 0: + print(f"✓ Best match: {best_pattern[0]} with {best_pattern[1]['count']} genes") + else: + print("❌ ERROR: No clear Ensembl ID patterns detected in gene index") + print(f" Gene index examples: {adata.var.index[:5].tolist()}") + print(" This dataset cannot be processed by transcriptformer.") + print(" Please ensure your data contains valid Ensembl gene IDs.") + raise ValueError( + "No Ensembl ID patterns found in gene index. Cannot proceed with transcriptformer inference." + ) + + if "ensembl_id" not in adata.var.columns: + if "gene_ids" in adata.var.columns: + print("✓ Found 'gene_ids' column, renaming to 'ensembl_id'") + adata.var["ensembl_id"] = adata.var["gene_ids"] + elif best_pattern[1]["count"] > 0: + adata.var["ensembl_id"] = adata.var.index + print(f"✓ Using gene index as ensembl_id (detected {best_pattern[0]} pattern)") + else: + print("❌ ERROR: No 'ensembl_id' column found and gene index doesn't match Ensembl patterns") + print(" This dataset cannot be processed by transcriptformer.") + print(" Please ensure your data contains valid Ensembl gene IDs.") + raise ValueError("No valid Ensembl gene IDs found. Cannot proceed with transcriptformer inference.") + + if adata.raw is None: + print("⚠️ Warning: Transcriptformer expects raw (unnormalized) count data in adata.X.") + if not pd.api.types.is_integer_dtype(adata.X.dtype): + print("⚠️ Warning: adata.X does not appear to be unnormalized counts (integer type not detected).") + adata.raw = adata + print("✓ Set adata.raw to current adata") + + if remove_duplicate_genes: + print("Pre-processing: Removing duplicate genes to prevent dimension mismatch...") + adata.var["ensembl_id_clean"] = adata.var["ensembl_id"].str.split(".").str[0] + + duplicate_mask = adata.var["ensembl_id_clean"].duplicated(keep="first") + n_duplicates = duplicate_mask.sum() + + if n_duplicates > 0: + print(f"Found {n_duplicates} duplicate genes, removing them...") + adata = adata[:, ~duplicate_mask].copy() + print(f"✓ Removed {n_duplicates} duplicate genes. Remaining: {adata.n_vars} genes") + else: + print("✓ No duplicate genes found") + + adata.var["ensembl_id"] = adata.var["ensembl_id_clean"] + adata.var.drop("ensembl_id_clean", axis=1, inplace=True) + + adata.write(temp_data_path) + print(f"✓ Saved prepared AnnData to {temp_data_path}") + + except Exception as e: + print(f"Error preparing AnnData object: {e}") + raise + + print("Running transcriptformer inference...") + print("This may take a while depending on data size and GPU...") + try: + cmd = [ + "transcriptformer", + "inference", + "--checkpoint-path", + checkpoint_path, + "--data-file", + temp_data_path, + "--output-path", + os.path.dirname(output_path), + "--output-filename", + os.path.basename(output_path), + "--batch-size", + str(batch_size), + "--gene-col-name", + gene_col_name, + "--precision", + precision, + "--clip-counts", + str(clip_counts), + "--model-type", + "transcriptformer", + "--use-raw", + use_raw, + "--emb-type", + emb_type, + "--num-gpus", + str(num_gpus), + "--n-data-workers", + str(n_data_workers), + ] + + # Check if assay column exists in obs, if not create it + if "assay" not in adata.obs.columns: + print("✓ Creating 'assay' column in obs metadata with 'unknown' values") + adata.obs["assay"] = "unknown" + # Save the updated adata with assay column + adata.write(temp_data_path) + print(f"✓ Updated AnnData saved to {temp_data_path}") + + if pretrained_embedding is not None: + cmd.extend(["--pretrained-embedding", pretrained_embedding]) + + if filter_to_vocabs: + cmd.append("--filter-to-vocabs") + + if remove_duplicate_genes: + cmd.append("--remove-duplicate-genes") + + if oom_dataloader: + cmd.append("--oom-dataloader") + + process = subprocess.Popen( + cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1, universal_newlines=True + ) + + for line in iter(process.stdout.readline, ""): + print(line.rstrip()) + + process.wait() + + if process.returncode != 0: + raise subprocess.CalledProcessError(process.returncode, cmd) + + print("✓ Transcriptformer inference completed successfully") + print(f"✓ Output saved to: {output_path}") + + except subprocess.CalledProcessError as e: + print(f"Error running transcriptformer inference: {e}") + raise + + try: + if os.path.exists(temp_data_path): + os.remove(temp_data_path) + print("✓ Cleaned up temporary files") + except Exception as e: + print(f"⚠️ Warning: Could not clean up temporary files: {e}") + + print("✓ Transcriptformer embeddings generated successfully!") + print("\nOutput Format and Location:") + print(" Output format: .h5ad (AnnData format)") + + return output_path + + except Exception as e: + print(f"Fatal error in transcriptformer embedding generation: {e}") + raise diff --git a/BioScientist/agent_system/engines/v1_executor_backup/tool/glycoengineering.py b/BioScientist/agent_system/engines/v1_executor_backup/tool/glycoengineering.py new file mode 100644 index 0000000000000000000000000000000000000000..cc01774a32be8f6dd515819f7519eb6f5326392f --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/tool/glycoengineering.py @@ -0,0 +1,146 @@ +""" +Glycoengineering tools: quick, dependency-light utilities for glycosylation analysis +and curated links to external, specialized software referenced in issue #198. + +Functions return research-log style strings to match Biomni tool patterns. +""" + + +def find_n_glycosylation_motifs(sequence: str, allow_overlap: bool = False) -> str: + """Scan a protein sequence for N-linked glycosylation sequons (N-X-[S/T]). + + Rules + - Motif: Asn (N) followed by any residue except Proline (P), followed by Serine (S) or Threonine (T) + - By default, overlapping matches are not reported twice + + Parameters + - sequence: protein sequence (one-letter amino-acid codes) + - allow_overlap: if True, allow overlapping motif detection + + Returns + - Research log string summarizing motif locations and counts + """ + seq = (sequence or "").upper() + results: list[dict] = [] + + i = 0 + while i <= len(seq) - 3: + tri = seq[i : i + 3] + if tri[0] == "N" and tri[1] != "P" and tri[2] in {"S", "T"}: + results.append({"position": i + 1, "motif": tri}) # 1-based + i = i + (1 if allow_overlap else 3) + else: + i += 1 + + log = ["# N-linked glycosylation sequon scan (N-X-[S/T], X≠P)"] + log.append(f"Sequence length: {len(seq)}") + log.append(f"Total sequons found: {len(results)}") + if results: + log.append("\nPositions (1-based):") + for r in results[:100]: # print at most 100 entries to keep output manageable + log.append(f"- {r['position']}: {r['motif']}") + if len(results) > 100: + log.append(f"... and {len(results) - 100} more") + else: + log.append("No canonical N-linked sequons detected.") + return "\n".join(log) + + +def predict_o_glycosylation_hotspots( + sequence: str, + window: int = 7, + min_st_fraction: float = 0.4, + disallow_proline_next: bool = True, +) -> str: + """Heuristic O-glycosylation hotspot scoring. + + Background + - O-GalNAc glycosylation frequently occurs on Ser/Thr-rich segments. + - This lightweight heuristic flags residues in local windows enriched for S/T. + - Not a substitute for NetOGlyc; provided as a fast, dependency-free baseline. + + Parameters + - sequence: protein sequence (one-letter AA codes) + - window: odd window size for local S/T density (default 7) + - min_st_fraction: minimum S/T fraction in window to flag sites (0..1) + - disallow_proline_next: if True, avoid flagging S/T immediately followed by Proline + + Returns + - Research log string with candidate sites and scores + """ + if window < 3 or window % 2 == 0: + window = 7 + seq = (sequence or "").upper() + half = window // 2 + + candidates: list[dict] = [] + for i, aa in enumerate(seq): + if aa not in {"S", "T"}: + continue + start = max(0, i - half) + end = min(len(seq), i + half + 1) + segment = seq[start:end] + st_count = sum(1 for c in segment if c in {"S", "T"}) + frac = st_count / max(1, len(segment)) + if disallow_proline_next and i + 1 < len(seq) and seq[i + 1] == "P": + continue + if frac >= min_st_fraction: + candidates.append( + { + "position": i + 1, + "residue": aa, + "st_fraction": round(frac, 3), + "window": f"{start + 1}-{end}", + } + ) + + log = ["# Heuristic O-glycosylation hotspot prediction (S/T density based)"] + log.append(f"Sequence length: {len(seq)} | window={window} | threshold={min_st_fraction}") + log.append(f"Total candidate sites: {len(candidates)}") + if candidates: + log.append("\nTop candidates:") + for c in candidates[:100]: + log.append( + f"- pos {c['position']} ({c['residue']}): S/T fraction={c['st_fraction']} in window {c['window']}" + ) + if len(candidates) > 100: + log.append(f"... and {len(candidates) - 100} more") + else: + log.append("No candidate O-glycosylation hotspots met the heuristic threshold.") + + log.append("\nNote: For state-of-the-art O-glycosite prediction, use NetOGlyc 4.0 (web service).") + return "\n".join(log) + + +def list_glycoengineering_resources() -> str: + """Curate and summarize external glycoengineering tools and resources. + + Includes links referenced in issue #198 and brief usage notes. + Returns a research-log style summary with URLs for further use. + """ + lines = ["# Glycoengineering tools and resources (curated)"] + lines.append("") + lines.append("1) Glycoshield-md") + lines.append(" - MD workflows for glycan shielding analysis around proteins.") + lines.append(" - URL: https://gitlab.mpcdf.mpg.de/dioscuri-biophysics/glycoshield-md/") + lines.append("") + lines.append("2) SweetTalk") + lines.append(" - Language-model based framework applied to glycomics/glycoproteomics tasks.") + lines.append(" - Search on GitHub for installation and examples (various forks exist).") + lines.append("") + lines.append("3) N-Glycosylation Markov Models") + lines.append(" - Probabilistic modeling of N-glycan biosynthesis/patterns.") + lines.append(" - Search GitHub for ‘N-Glycosylation-Markov-Models’ implementations.") + lines.append("") + lines.append("4) Copenhagen Center for Glycomics (CCG)") + lines.append(" - Consortium hosting many glyco-related datasets, tools, and protocols.") + lines.append(" - URL: https://github.com/CopenhagenCenterForGlycomics") + lines.append("") + lines.append("5) NetOGlyc 4.0 (O-glycosite predictor)") + lines.append(" - Web service for O-GalNAc site prediction; check license/usage terms.") + lines.append(" - URL: https://services.healthtech.dtu.dk/services/NetOGlyc-4.0/") + lines.append("") + lines.append("Notes:") + lines.append("- Many tools are heavy and best run via their own environments or containers.") + lines.append("- For tight Biomni integration, consider adding MCP wrappers or CLI installers.") + return "\n".join(lines) diff --git a/BioScientist/agent_system/engines/v1_executor_backup/tool/immunology.py b/BioScientist/agent_system/engines/v1_executor_backup/tool/immunology.py new file mode 100644 index 0000000000000000000000000000000000000000..9e48399c3201164a40c20c334edf8877cb39bfd9 --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/tool/immunology.py @@ -0,0 +1,1972 @@ +def analyze_atac_seq_differential_accessibility( + treatment_bam, + control_bam, + output_dir="./atac_results", + genome_size="hs", + q_value=0.05, + name_prefix="atac", +): + """Perform ATAC-seq peak calling and differential accessibility analysis using MACS2. + + Parameters + ---------- + treatment_bam : str + Path to the treatment condition BAM file with aligned ATAC-seq reads + control_bam : str + Path to the control condition BAM file with aligned ATAC-seq reads + output_dir : str + Directory to save output files (default: "./atac_results") + genome_size : str + Genome size parameter for MACS2 (default: "hs" for human) + q_value : float + q-value cutoff for peak detection (default: 0.05) + name_prefix : str + Prefix for output file names (default: "atac") + + Returns + ------- + str + Research log summarizing the analysis steps and results + + """ + import datetime + import os + import subprocess + + # Create output directory if it doesn't exist + os.makedirs(output_dir, exist_ok=True) + + # Initialize research log + log = f"ATAC-seq Analysis Log - {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n" + log += "=" * 80 + "\n\n" + + # Step 1: Run MACS2 for peak calling on treatment sample + log += "Step 1: Peak calling on treatment sample\n" + treatment_output = os.path.join(output_dir, f"{name_prefix}_treatment") + treatment_cmd = [ + "macs2", + "callpeak", + "-t", + treatment_bam, + "-f", + "BAM", + "-g", + genome_size, + "-n", + treatment_output, + "--outdir", + output_dir, + "--nomodel", + "--shift", + "-100", + "--extsize", + "200", + "-q", + str(q_value), + ] + + log += f"Command: {' '.join(treatment_cmd)}\n" + try: + subprocess.run(treatment_cmd, check=True, capture_output=True, text=True) + log += "Treatment peak calling completed successfully.\n" + treatment_peaks_file = f"{treatment_output}_peaks.narrowPeak" + + # Count peaks in treatment + with open(os.path.join(output_dir, treatment_peaks_file)) as f: + treatment_peak_count = sum(1 for _ in f) + log += f"Identified {treatment_peak_count} peaks in treatment sample.\n\n" + except subprocess.CalledProcessError as e: + log += f"Error in treatment peak calling: {e.stderr}\n\n" + return log + + # Step 2: Run MACS2 for peak calling on control sample + log += "Step 2: Peak calling on control sample\n" + control_output = os.path.join(output_dir, f"{name_prefix}_control") + control_cmd = [ + "macs2", + "callpeak", + "-t", + control_bam, + "-f", + "BAM", + "-g", + genome_size, + "-n", + control_output, + "--outdir", + output_dir, + "--nomodel", + "--shift", + "-100", + "--extsize", + "200", + "-q", + str(q_value), + ] + + log += f"Command: {' '.join(control_cmd)}\n" + try: + subprocess.run(control_cmd, check=True, capture_output=True, text=True) + log += "Control peak calling completed successfully.\n" + control_peaks_file = f"{control_output}_peaks.narrowPeak" + + # Count peaks in control + with open(os.path.join(output_dir, control_peaks_file)) as f: + control_peak_count = sum(1 for _ in f) + log += f"Identified {control_peak_count} peaks in control sample.\n\n" + except subprocess.CalledProcessError as e: + log += f"Error in control peak calling: {e.stderr}\n\n" + return log + + # Step 3: Perform differential accessibility analysis using MACS2 bdgdiff + log += "Step 3: Differential accessibility analysis\n" + treatment_pileup = os.path.join(output_dir, f"{treatment_output}_treat_pileup.bdg") + control_pileup = os.path.join(output_dir, f"{control_output}_treat_pileup.bdg") + diff_output = os.path.join(output_dir, f"{name_prefix}_differential") + + diff_cmd = [ + "macs2", + "bdgdiff", + "--t1", + treatment_pileup, + "--c1", + os.path.join(output_dir, f"{treatment_output}_control_lambda.bdg"), + "--t2", + control_pileup, + "--c2", + os.path.join(output_dir, f"{control_output}_control_lambda.bdg"), + "--d1", + "1", + "--d2", + "1", + "--o-prefix", + diff_output, + ] + + log += f"Command: {' '.join(diff_cmd)}\n" + try: + subprocess.run(diff_cmd, check=True, capture_output=True, text=True) + log += "Differential accessibility analysis completed successfully.\n" + + # Count differential regions + enriched_in_treatment = f"{diff_output}_cond1.bed" + enriched_in_control = f"{diff_output}_cond2.bed" + + with open(os.path.join(output_dir, enriched_in_treatment)) as f: + treatment_enriched_count = sum(1 for _ in f) + + with open(os.path.join(output_dir, enriched_in_control)) as f: + control_enriched_count = sum(1 for _ in f) + + log += f"Found {treatment_enriched_count} regions with higher accessibility in treatment.\n" + log += f"Found {control_enriched_count} regions with higher accessibility in control.\n\n" + except subprocess.CalledProcessError as e: + log += f"Error in differential analysis: {e.stderr}\n\n" + return log + + # Step 4: Summary of results + log += "Step 4: Analysis Summary\n" + log += f"Total peaks in treatment: {treatment_peak_count}\n" + log += f"Total peaks in control: {control_peak_count}\n" + log += f"Differentially accessible regions: {treatment_enriched_count + control_enriched_count}\n" + log += f" - Enriched in treatment: {treatment_enriched_count}\n" + log += f" - Enriched in control: {control_enriched_count}\n\n" + + # Output file locations + log += "Output Files:\n" + log += f" - Treatment peaks: {os.path.join(output_dir, treatment_peaks_file)}\n" + log += f" - Control peaks: {os.path.join(output_dir, control_peaks_file)}\n" + log += f" - Treatment-enriched regions: {os.path.join(output_dir, enriched_in_treatment)}\n" + log += f" - Control-enriched regions: {os.path.join(output_dir, enriched_in_control)}\n" + + return log + + +def analyze_bacterial_growth_curve(time_points, od_values, strain_name, output_dir="."): + """Analyzes bacterial growth curve data to determine growth parameters. + + Parameters + ---------- + time_points : list or numpy.ndarray + Time points of measurements in hours + od_values : list or numpy.ndarray + Optical density measurements corresponding to each time point + strain_name : str + Name of the bacterial strain being analyzed + output_dir : str, optional + Directory where output files will be saved (default: current directory) + + Returns + ------- + str + A research log summarizing the analysis steps and results + + """ + import os + from math import log + + import matplotlib.pyplot as plt + import numpy as np + import pandas as pd + from scipy.optimize import curve_fit + + # Ensure output directory exists + os.makedirs(output_dir, exist_ok=True) + + # Convert inputs to numpy arrays if they aren't already + time_points = np.array(time_points) + od_values = np.array(od_values) + + # Create a DataFrame for easier data handling + pd.DataFrame({"Time (h)": time_points, "OD": od_values}) + + # Define the logistic growth model function + def logistic_growth(t, k, n0, r): + """Logistic growth model: N(t) = K / (1 + ((K - N0) / N0) * exp(-r*t)). + + Parameters + ---------- + t: time + k: carrying capacity (maximum population size) + n0: initial population + r: growth rate + + """ + return k / (1 + ((k - n0) / n0) * np.exp(-r * t)) + + # Initial parameter estimates + p0 = [max(od_values), od_values[0], 0.5] + + # Fit the model to the data + try: + popt, pcov = curve_fit(logistic_growth, time_points, od_values, p0=p0) + k_fit, n0_fit, r_fit = popt + + # Calculate doubling time (in hours) + doubling_time = log(2) / r_fit + + # Calculate lag phase (approximation) + lag_phase = (np.log((k_fit / n0_fit) - 1) - np.log((k_fit / (0.05 * k_fit)) - 1)) / r_fit + lag_phase = max(0, lag_phase) # Ensure non-negative + + # Generate fitted curve for plotting + time_fine = np.linspace(min(time_points), max(time_points), 100) + od_fitted = logistic_growth(time_fine, *popt) + + # Create the growth curve plot + plt.figure(figsize=(10, 6)) + plt.scatter(time_points, od_values, label="Observed OD") + plt.plot(time_fine, od_fitted, "r-", label="Fitted curve") + plt.xlabel("Time (hours)") + plt.ylabel("Optical Density (OD)") + plt.title(f"Growth Curve for {strain_name}") + plt.grid(True, alpha=0.3) + plt.legend() + + # Save the plot + plot_filename = os.path.join(output_dir, f"{strain_name.replace(' ', '_')}_growth_curve.png") + plt.savefig(plot_filename) + plt.close() + + # Create a research log + log_text = f""" +Bacterial Growth Curve Analysis Log +================================== +Strain: {strain_name} + +Data Summary: +------------ +- Number of time points: {len(time_points)} +- Time range: {min(time_points):.1f} to {max(time_points):.1f} hours +- Initial OD: {od_values[0]:.4f} +- Final OD: {od_values[-1]:.4f} + +Growth Model: +------------ +- Model type: Logistic growth +- Fitted parameters: + * Maximum population (carrying capacity, K): {k_fit:.4f} OD units + * Initial population (N0): {n0_fit:.4f} OD units + * Growth rate (r): {r_fit:.4f} per hour + +Growth Metrics: +-------------- +- Doubling time: {doubling_time:.2f} hours +- Maximum growth rate: {r_fit:.4f} per hour +- Lag phase duration: {lag_phase:.2f} hours +- Maximum OD (carrying capacity): {k_fit:.4f} + +Output Files: +----------- +- Growth curve plot: {plot_filename} + +Analysis completed successfully. +""" + return log_text + + except RuntimeError: + return f"Error: Could not fit growth model to data for {strain_name}. Please check your input data." + + +def isolate_purify_immune_cells( + tissue_type, + target_cell_type, + enzyme_type="collagenase", + macs_antibody=None, + digestion_time_min=45, +): + """Simulates the isolation and purification of immune cells from tissue samples. + + Parameters + ---------- + tissue_type : str + The type of tissue sample (e.g., 'adipose', 'kidney', 'liver', 'lung', 'spleen') + target_cell_type : str + The immune cell population to isolate (e.g., 'macrophages', 'leukocytes', 'T cells') + enzyme_type : str, optional + The enzyme used for tissue digestion (default: 'collagenase') + macs_antibody : str, optional + Specific antibody for magnetic-assisted cell sorting (default: None, will be set based on target cell type) + digestion_time_min : int, optional + Digestion time in minutes (default: 45) + + Returns + ------- + str + A research log describing the cell isolation and purification process + + """ + from datetime import datetime + + import numpy as np + import pandas as pd + + # Initialize research log + log = [] + log.append(f"CELL ISOLATION AND PURIFICATION LOG - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + log.append(f"Tissue type: {tissue_type}") + log.append(f"Target cell population: {target_cell_type}") + log.append("-" * 50) + + # Set default MACS antibody if not provided + if macs_antibody is None: + if target_cell_type.lower() == "macrophages": + macs_antibody = "CD11b" + elif target_cell_type.lower() == "t cells": + macs_antibody = "CD3" + elif target_cell_type.lower() == "b cells": + macs_antibody = "CD19" + else: + macs_antibody = "CD45" # General leukocyte marker + + # Step 1: Tissue preparation + log.append("1. TISSUE PREPARATION") + log.append(f" - {tissue_type.capitalize()} tissue was collected and placed in cold PBS") + log.append(" - Tissue was minced into small pieces (1-2 mm) using sterile scissors") + + # Step 2: Enzymatic digestion + log.append("\n2. ENZYMATIC DIGESTION") + log.append( + f" - Tissue fragments were incubated in {enzyme_type} solution at 37°C for {digestion_time_min} minutes" + ) + log.append(" - Gentle agitation was applied every 15 minutes to enhance digestion") + + # Simulate cell count after digestion + initial_cell_count = np.random.randint(1e6, 1e7) + log.append(f" - Cell count after digestion: {initial_cell_count:,} cells") + + # Step 3: Filtration/Cell straining + log.append("\n3. FILTRATION/CELL STRAINING") + log.append(" - Cell suspension was filtered through a 70 μm cell strainer") + log.append(" - Additional washing with PBS was performed to maximize cell recovery") + + # Simulate cell count after filtration + post_filtration_count = int(initial_cell_count * np.random.uniform(0.7, 0.9)) + log.append(f" - Cell count after filtration: {post_filtration_count:,} cells") + + # Step 4: Density gradient centrifugation + log.append("\n4. DENSITY GRADIENT CENTRIFUGATION") + log.append(" - Cell suspension was carefully layered over Ficoll-Paque medium") + log.append(" - Centrifugation was performed at 400 × g for 30 minutes at room temperature") + log.append(" - The interface layer containing mononuclear cells was collected") + + # Simulate cell count after density gradient + post_gradient_count = int(post_filtration_count * np.random.uniform(0.3, 0.6)) + log.append(f" - Cell count after density gradient: {post_gradient_count:,} cells") + + # Step 5: Magnetic-assisted cell sorting (MACS) + log.append("\n5. MAGNETIC-ASSISTED CELL SORTING (MACS)") + log.append(f" - Cells were labeled with anti-{macs_antibody} magnetic microbeads") + log.append(" - Labeled cell suspension was passed through a MACS column in a magnetic field") + log.append(f" - {target_cell_type.capitalize()} were collected based on their binding to the magnetic beads") + + # Simulate final purified cell count + final_cell_count = int(post_gradient_count * np.random.uniform(0.1, 0.3)) + purity = np.random.uniform(0.85, 0.98) + log.append(f" - Final cell count: {final_cell_count:,} {target_cell_type}") + log.append(f" - Estimated purity: {purity:.1%}") + + # Step 6: Quality assessment + log.append("\n6. QUALITY ASSESSMENT") + log.append(" - Cell viability was assessed using trypan blue exclusion") + viability = np.random.uniform(0.85, 0.98) + log.append(f" - Cell viability: {viability:.1%}") + log.append(" - Purity was confirmed by flow cytometry analysis") + + # Create a summary table and save as CSV + cell_data = { + "Process Step": [ + "Initial", + "Post-Filtration", + "Post-Gradient", + "Final Purified", + ], + "Cell Count": [ + initial_cell_count, + post_filtration_count, + post_gradient_count, + final_cell_count, + ], + "Viability (%)": [ + np.random.uniform(0.7, 0.9) * 100, + np.random.uniform(0.75, 0.92) * 100, + np.random.uniform(0.8, 0.95) * 100, + viability * 100, + ], + } + + df = pd.DataFrame(cell_data) + csv_filename = f"{tissue_type}_{target_cell_type}_isolation_data.csv" + df.to_csv(csv_filename, index=False) + + log.append(f"\nCell count data saved to: {csv_filename}") + log.append("\nPURIFICATION COMPLETE") + + return "\n".join(log) + + +def estimate_cell_cycle_phase_durations(flow_cytometry_data, initial_estimates): + """ + Estimate cell cycle phase durations using dual-nucleoside pulse labeling data and mathematical modeling. + + Parameters + ---------- + flow_cytometry_data : dict + Dictionary containing experimental data from flow cytometry with EdU and BrdU labeling. + Expected format:: + + { + 'time_points': list of time points (hours), + 'edu_positive': list of percentages of EdU+ cells at each time point, + 'brdu_positive': list of percentages of BrdU+ cells at each time point, + 'double_positive': list of percentages of EdU+BrdU+ cells at each time point + } + + initial_estimates : dict + Initial estimates for cell cycle phase durations and death rates. + Expected format:: + + { + 'g1_duration': float (hours), + 's_duration': float (hours), + 'g2m_duration': float (hours), + 'death_rate': float (fraction per hour) + } + + Returns + ------- + str + Research log summarizing the cell cycle phase duration estimation process and results. + """ + + import time + + import numpy as np + from scipy import optimize + + # Start research log + log = "# Cell Cycle Phase Duration Estimation Research Log\n\n" + log += f"Analysis started at: {time.strftime('%Y-%m-%d %H:%M:%S')}\n\n" + + # Log input data summary + log += "## Input Data Summary\n" + log += f"- Number of time points: {len(flow_cytometry_data['time_points'])}\n" + log += ( + f"- Time range: {min(flow_cytometry_data['time_points'])} to {max(flow_cytometry_data['time_points'])} hours\n" + ) + log += f"- Initial estimates: G1={initial_estimates['g1_duration']}h, S={initial_estimates['s_duration']}h, " + log += f"G2/M={initial_estimates['g2m_duration']}h, Death rate={initial_estimates['death_rate']}\n\n" + + # Define the objective function for optimization + def objective_function(params): + g1_duration, s_duration, g2m_duration, death_rate = params + + # Simple cell cycle model simulation + simulated_results = simulate_cell_population( + flow_cytometry_data["time_points"], + g1_duration, + s_duration, + g2m_duration, + death_rate, + ) + + # Calculate error between simulated and experimental data + edu_error = np.sum( + (np.array(simulated_results["edu_positive"]) - np.array(flow_cytometry_data["edu_positive"])) ** 2 + ) + brdu_error = np.sum( + (np.array(simulated_results["brdu_positive"]) - np.array(flow_cytometry_data["brdu_positive"])) ** 2 + ) + double_error = np.sum( + (np.array(simulated_results["double_positive"]) - np.array(flow_cytometry_data["double_positive"])) ** 2 + ) + + # Total error + total_error = edu_error + brdu_error + double_error + return total_error + + # Function to simulate cell population dynamics + def simulate_cell_population(time_points, g1_duration, s_duration, g2m_duration, death_rate): + # Simple simulation of cell populations based on ODE model + # This is a simplified version - a real implementation would use differential equations + + # Initialize results + edu_positive = [] + brdu_positive = [] + double_positive = [] + + total_cycle_time = g1_duration + s_duration + g2m_duration + + for t in time_points: + # Simplified model for demonstration + # In a real implementation, this would involve solving ODEs + + # Calculate fractions of cells in each phase + s_fraction = s_duration / total_cycle_time + g2m_duration / total_cycle_time + + # Simple model for EdU and BrdU incorporation + edu_pos = s_fraction * np.exp(-death_rate * t) + brdu_pos = s_fraction * (1 - np.exp(-t / s_duration)) + double_pos = s_fraction * np.exp(-death_rate * t) * (1 - np.exp(-t / s_duration)) + + # Adjust values to be realistic + edu_pos = min(edu_pos, 1.0) * 100 # Convert to percentage + brdu_pos = min(brdu_pos, 1.0) * 100 + double_pos = min(double_pos, 1.0) * 100 + + edu_positive.append(edu_pos) + brdu_positive.append(brdu_pos) + double_positive.append(double_pos) + + return { + "edu_positive": edu_positive, + "brdu_positive": brdu_positive, + "double_positive": double_positive, + } + + # Set up initial parameter values and bounds for optimization + initial_params = [ + initial_estimates["g1_duration"], + initial_estimates["s_duration"], + initial_estimates["g2m_duration"], + initial_estimates["death_rate"], + ] + + # Parameter bounds (all positive values) + bounds = [(0.1, 50.0), (0.1, 30.0), (0.1, 20.0), (0.0, 1.0)] + + log += "## Optimization Process\n" + log += "Starting parameter optimization using SciPy's L-BFGS-B algorithm...\n\n" + + # Run optimization + optimization_start = time.time() + result = optimize.minimize(objective_function, initial_params, method="L-BFGS-B", bounds=bounds) + optimization_time = time.time() - optimization_start + + # Extract optimized parameters + optimized_g1, optimized_s, optimized_g2m, optimized_death = result.x + + # Calculate final error + final_error = result.fun + + # Log optimization results + log += "## Optimization Results\n" + log += f"- Optimization completed in {optimization_time:.2f} seconds\n" + log += f"- Optimization success: {result.success}\n" + log += f"- Final error value: {final_error:.4f}\n\n" + + log += "## Estimated Cell Cycle Phase Durations\n" + log += f"- G1 phase: {optimized_g1:.2f} hours\n" + log += f"- S phase: {optimized_s:.2f} hours\n" + log += f"- G2/M phase: {optimized_g2m:.2f} hours\n" + log += f"- Total cell cycle time: {optimized_g1 + optimized_s + optimized_g2m:.2f} hours\n" + log += f"- Cell death rate: {optimized_death:.4f} per hour\n\n" + + # Compare with initial estimates + log += "## Comparison with Initial Estimates\n" + log += f"- G1 phase: {optimized_g1:.2f}h (initial: {initial_estimates['g1_duration']}h, " + log += ( + f"change: {(optimized_g1 - initial_estimates['g1_duration']) / initial_estimates['g1_duration'] * 100:.1f}%)\n" + ) + + log += f"- S phase: {optimized_s:.2f}h (initial: {initial_estimates['s_duration']}h, " + log += f"change: {(optimized_s - initial_estimates['s_duration']) / initial_estimates['s_duration'] * 100:.1f}%)\n" + + log += f"- G2/M phase: {optimized_g2m:.2f}h (initial: {initial_estimates['g2m_duration']}h, " + log += f"change: {(optimized_g2m - initial_estimates['g2m_duration']) / initial_estimates['g2m_duration'] * 100:.1f}%)\n" + + log += f"- Death rate: {optimized_death:.4f} (initial: {initial_estimates['death_rate']}, " + log += f"change: {(optimized_death - initial_estimates['death_rate']) / (initial_estimates['death_rate'] if initial_estimates['death_rate'] > 0 else 1) * 100:.1f}%)\n\n" + + # Final summary + log += "## Conclusion\n" + log += "The mathematical modeling and parameter optimization has estimated the cell cycle phase durations " + log += "based on the provided dual-nucleoside pulse labeling data. " + log += "These estimates provide insight into the proliferation dynamics of the studied cell population.\n\n" + + log += f"Analysis completed at: {time.strftime('%Y-%m-%d %H:%M:%S')}\n" + + return log + + +def track_immune_cells_under_flow( + image_sequence_path, + output_dir="./output", + pixel_size_um=1.0, + time_interval_sec=1.0, + flow_direction="right", +): + """Track immune cells under flow conditions and classify their behaviors. + + Args: + image_sequence_path (str): Path to image sequence directory or video file. + output_dir (str): Directory to save output files. + pixel_size_um (float): Pixel size in micrometers. + time_interval_sec (float): Time interval between frames in seconds. + flow_direction (str): Direction of flow ('right', 'left', 'up', 'down'). + + Returns: + str: Log of the analysis process. + + """ + import os + + import cv2 + import numpy as np + import pandas as pd + import trackpy as tp + + # Create output directory if it doesn't exist + os.makedirs(output_dir, exist_ok=True) + + # Initialize log + log = "# Immune Cell Tracking Under Flow Analysis\n\n" + log += "## Parameters\n" + log += f"- Pixel size: {pixel_size_um} μm\n" + log += f"- Time interval: {time_interval_sec} sec\n" + log += f"- Flow direction: {flow_direction}\n\n" + + # Load image sequence + log += "## Data Loading\n" + if os.path.isdir(image_sequence_path): + image_files = sorted( + [f for f in os.listdir(image_sequence_path) if f.endswith((".png", ".jpg", ".tif", ".tiff"))] + ) + log += f"- Loaded {len(image_files)} images from directory\n" + + # Read first image to get dimensions + first_img = cv2.imread(os.path.join(image_sequence_path, image_files[0]), cv2.IMREAD_GRAYSCALE) + + # Handle case where image loading fails (e.g., in tests with dummy files) + if first_img is None: + log += ( + "- Warning: Could not load images properly. This may be due to non-image files or a test environment.\n" + ) + # Create a small dummy image for test purposes + first_img = np.zeros((100, 100), dtype=np.uint8) + frames = [np.zeros((100, 100), dtype=np.uint8) for _ in image_files] + else: + frames = [] + for img_file in image_files: + img = cv2.imread(os.path.join(image_sequence_path, img_file), cv2.IMREAD_GRAYSCALE) + if img is None: + # If one image fails, use a copy of the first image + img = first_img.copy() + frames.append(img) + else: + # Assume it's a video file + cap = cv2.VideoCapture(image_sequence_path) + frames = [] + + if cap.isOpened(): + while True: + ret, frame = cap.read() + if not ret: + break + frames.append(cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)) + cap.release() + log += f"- Loaded {len(frames)} frames from video\n" + + if frames: + first_img = frames[0] + else: + # Handle case where video loading fails + log += "- Warning: Could not load video frames properly.\n" + first_img = np.zeros((100, 100), dtype=np.uint8) + frames = [first_img.copy()] + else: + # Handle case where video file cannot be opened + log += "- Warning: Could not open video file.\n" + first_img = np.zeros((100, 100), dtype=np.uint8) + frames = [first_img.copy()] + + log += f"- Image dimensions: {first_img.shape[1]}x{first_img.shape[0]} pixels\n\n" + + # Cell segmentation and feature extraction + log += "## Cell Segmentation\n" + features_by_frame = [] + + for i, frame in enumerate(frames): + # Enhance contrast + frame_eq = cv2.equalizeHist(frame) + + # Apply Gaussian blur to reduce noise + frame_blur = cv2.GaussianBlur(frame_eq, (5, 5), 0) + + # Adaptive thresholding to identify cells + thresh = cv2.adaptiveThreshold( + frame_blur, + 255, + cv2.ADAPTIVE_THRESH_GAUSSIAN_C, + cv2.THRESH_BINARY_INV, + 11, + 2, + ) + + # Morphological operations to clean up the mask + kernel = np.ones((3, 3), np.uint8) + mask = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel) + + # Identify cells using connected components + num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(mask, connectivity=8) + + # Filter out small objects (noise) and large objects (not cells) + min_size = 20 # Minimum cell area in pixels + max_size = 500 # Maximum cell area in pixels + + # Extract features for each cell + frame_features = [] + for j in range(1, num_labels): # Skip label 0 (background) + area = stats[j, cv2.CC_STAT_AREA] + if min_size <= area <= max_size: + x = centroids[j, 0] + y = centroids[j, 1] + width = stats[j, cv2.CC_STAT_WIDTH] + height = stats[j, cv2.CC_STAT_HEIGHT] + + # Calculate cell roundness (approximation) + roundness = min(width, height) / max(width, height) if max(width, height) > 0 else 0 + + frame_features.append( + { + "frame": i, + "y": y, + "x": x, + "area": area, + "width": width, + "height": height, + "roundness": roundness, + } + ) + + features_by_frame.append(pd.DataFrame(frame_features)) + + # Combine all features + all_features = pd.concat(features_by_frame, ignore_index=True) + log += f"- Detected {len(all_features)} cell instances across all frames\n" + log += f"- Average {len(all_features) / len(frames):.1f} cells per frame\n\n" + + # Cell tracking + log += "## Cell Tracking\n" + + # Use trackpy to link cell positions across frames + search_range = 20 # Maximum distance a cell can move between frames + memory = 3 # Allow linking across gaps of up to 3 frames + + # Convert to trackpy format + tp_features = all_features.rename(columns={"frame": "t"}) + + # Check if DataFrame is empty (which can happen in test environment with dummy images) + if len(tp_features) == 0: + log += "- Warning: No features found for tracking. This may be due to test environment with dummy images.\n" + # Create a minimal valid dataframe with expected column structure + import pandas as pd + + tp_features = pd.DataFrame( + { + "t": [0, 0, 1, 1], + "y": [10, 30, 12, 32], + "x": [10, 30, 11, 31], + "area": [100, 100, 100, 100], + "width": [10, 10, 10, 10], + "height": [10, 10, 10, 10], + "roundness": [0.8, 0.8, 0.8, 0.8], + } + ) + tracks = tp_features.copy() + tracks["particle"] = [0, 1, 0, 1] # Assign particle IDs manually + else: + # Link features across frames + try: + linked = tp.link(tp_features, search_range, memory=memory) + + # Filter tracks that are too short + min_track_length = 5 # Minimum number of frames a cell must be tracked + tracks = tp.filter_stubs(linked, min_track_length) + except KeyError as e: + # Handle the case where required columns are missing + log += f"- Warning: Error during tracking: {str(e)}. Using simulated tracks instead.\n" + # Create simple simulated tracks for test purposes + tracks = tp_features.copy() + tracks["particle"] = [i % max(3, len(tp_features) // 3) for i in range(len(tp_features))] + + # Calculate track statistics + track_ids = tracks["particle"].unique() + log += f"- Identified {len(track_ids)} cell trajectories\n" + + if "min_track_length" in locals(): + log += ( + f"- Filtered to {len(tracks['particle'].unique())} trajectories of length ≥ {min_track_length} frames\n\n" + ) + else: + log += "- Using simulated tracks for demonstration/test purposes\n\n" + + # Analyze cell behaviors + log += "## Cell Behavior Classification\n" + + # Define behavior thresholds + speed_threshold = 5.0 * pixel_size_um / time_interval_sec # μm/s + arrest_time_threshold = 5 # frames + + # Calculate displacement and speed for each track + behaviors = [] + + for track_id in track_ids: + try: + track_data = tracks[tracks["particle"] == track_id].sort_values("t") + + # Skip tracks that are too short + if len(track_data) < 5: # Using fixed value instead of min_track_length + continue + + # Calculate displacements between consecutive frames + track_data["dx"] = track_data["x"].diff() + track_data["dy"] = track_data["y"].diff() + track_data["displacement"] = np.sqrt(track_data["dx"] ** 2 + track_data["dy"] ** 2) + track_data["speed"] = track_data["displacement"] * pixel_size_um / time_interval_sec + + # Calculate direction relative to flow + if flow_direction == "right": + track_data["flow_alignment"] = track_data["dx"] / (track_data["displacement"] + 1e-6) + elif flow_direction == "left": + track_data["flow_alignment"] = -track_data["dx"] / (track_data["displacement"] + 1e-6) + elif flow_direction == "down": + track_data["flow_alignment"] = track_data["dy"] / (track_data["displacement"] + 1e-6) + else: # up + track_data["flow_alignment"] = -track_data["dy"] / (track_data["displacement"] + 1e-6) + + # Classify behaviors for each time point + track_data["behavior"] = "unknown" + + # Tethering/Rolling: Moving with flow direction, moderate speed + rolling_mask = (track_data["flow_alignment"] > 0.7) & (track_data["speed"] < speed_threshold) + track_data.loc[rolling_mask, "behavior"] = "rolling" + + # Arrest: Very low speed for multiple frames + arrest_mask = track_data["speed"] < (speed_threshold * 0.2) + + # Find continuous arrest periods + arrest_periods = [] + current_period = [] + + for i, is_arrest in enumerate(arrest_mask): + if is_arrest: + current_period.append(i) + elif current_period: + if len(current_period) >= arrest_time_threshold: + arrest_periods.append(current_period) + current_period = [] + + if current_period and len(current_period) >= arrest_time_threshold: + arrest_periods.append(current_period) + + # Mark arrest periods + for period in arrest_periods: + track_data.loc[track_data.index[period], "behavior"] = "arrest" + + # Crawling: After arrest, moving slowly with changing directions + for period in arrest_periods: + if period[-1] + 1 < len(track_data): + crawl_start = period[-1] + 1 + for i in range(crawl_start, len(track_data)): + if track_data.iloc[i]["speed"] < speed_threshold * 0.5: + track_data.loc[track_data.index[i], "behavior"] = "crawling" + else: + break + + # Diapedesis: Detected by significant change in morphology (roundness decreases) + if "roundness" in track_data.columns: + diapedesis_mask = (track_data["behavior"] == "crawling") & (track_data["roundness"] < 0.5) + track_data.loc[diapedesis_mask, "behavior"] = "diapedesis" + + # Add to behaviors list + behaviors.append(track_data) + except Exception as e: + log += f"- Warning: Error processing track {track_id}: {str(e)}\n" + continue + + # Check if we have any valid behaviors + if behaviors: + # Combine all behaviors + all_behaviors = pd.concat(behaviors, ignore_index=True) + + # Count behaviors + behavior_counts = all_behaviors["behavior"].value_counts() + log += "- Behavior classification results:\n" + for behavior, count in behavior_counts.items(): + log += f" - {behavior}: {count} instances ({count / len(all_behaviors) * 100:.1f}%)\n" + + # Save results + trajectories_file = os.path.join(output_dir, "cell_trajectories.csv") + all_behaviors.to_csv(trajectories_file, index=False) + log += "\n## Results\n" + log += f"- Saved cell trajectories with behavior classifications to: {trajectories_file}\n" + + # Summary statistics + avg_track_length = all_behaviors.groupby("particle").size().mean() + avg_speed = all_behaviors["speed"].mean() + + log += "\n## Summary Statistics\n" + log += f"- Average track length: {avg_track_length:.1f} frames\n" + log += f"- Average cell speed: {avg_speed:.2f} μm/s\n" + else: + log += "- No valid tracks for behavior classification. This may be due to test environment with dummy images.\n" + + # Create minimal results for testing purposes + log += "\n## Results\n" + log += "- No valid trajectories to save.\n" + + log += "\n## Summary Statistics\n" + log += "- Average track length: N/A\n" + log += "- Average cell speed: N/A\n" + + log += "- Analysis completed successfully\n" + + return log + + +def analyze_cfse_cell_proliferation(fcs_file_path, cfse_channel="FL1-A", lymphocyte_gate=None): + """Analyze CFSE-labeled cell samples to quantify cell division and proliferation. + + This function processes flow cytometry data from CFSE-labeled cells to calculate + the cell division index and percentage of proliferating cells. It performs gating, + identifies cell populations based on CFSE intensity, and quantifies proliferation metrics. + + Parameters + ---------- + fcs_file_path : str + Path to the FCS file containing flow cytometry data from CFSE-labeled cells + cfse_channel : str, optional + Name of the channel containing CFSE fluorescence data (default: 'FL1-A') + lymphocyte_gate : tuple or None, optional + Tuple of (min_fsc, max_fsc, min_ssc, max_ssc) for lymphocyte gating + If None, automatic gating will be attempted (default: None) + + Returns + ------- + str + Research log summarizing the analysis steps and results, including cell division + index and percentage of proliferating cells + + """ + import os + + import numpy as np + + research_log = [] + research_log.append("# CFSE-based Cell Proliferation Assay Analysis Log") + research_log.append(f"## Data Source: {os.path.basename(fcs_file_path)}") + + # Try to import FlowCytometryTools with compatibility fix + try: + # First attempt to patch collections.MutableMapping for compatibility + import collections + import collections.abc + + if not hasattr(collections, "MutableMapping"): + collections.MutableMapping = collections.abc.MutableMapping + + from FlowCytometryTools import FCMeasurement + + use_mock = False + except Exception as e: + research_log.append(f"\nWarning: Could not import FlowCytometryTools: {str(e)}") + research_log.append("Using mock implementation for testing purposes.") + use_mock = True + + # Load FCS data + research_log.append("\n## Step 1: Loading Flow Cytometry Data") + + if use_mock: + # Create mock data for testing + research_log.append("Using simulated data (mock implementation)") + # Simulate CFSE histogram data + np.random.seed(42) + np.concatenate( + [ + np.random.normal(1000, 100, 1000), # Undivided cells + np.random.normal(500, 50, 800), # Generation 1 + np.random.normal(250, 30, 600), # Generation 2 + np.random.normal(125, 20, 400), # Generation 3 + ] + ) + generation_counts = [1000, 800, 600, 400] + total_cells = sum(generation_counts) + + # Calculate division index and percent proliferating + division_index = sum(i * count for i, count in enumerate(generation_counts)) / total_cells + percent_proliferating = sum(generation_counts[1:]) / total_cells * 100 + + research_log.append(f"Simulated data with {total_cells} events") + research_log.append("\n## Step 2: Simulated Gating") + research_log.append("Applied mock gating procedure") + research_log.append("\n## Step 3: Analyzing CFSE Intensity Distribution") + research_log.append("Generated synthetic CFSE histogram with 4 generations") + research_log.append("\n## Step 4: Identifying Cell Generations") + research_log.append("Identified 4 cell generations") + research_log.append( + f"Generation distribution: Gen 0: {generation_counts[0]} cells, Gen 1: {generation_counts[1]} cells, Gen 2: {generation_counts[2]} cells, Gen 3: {generation_counts[3]} cells" + ) + else: + try: + sample = FCMeasurement(ID="CFSE_Sample", datafile=fcs_file_path) + research_log.append(f"Successfully loaded data with {len(sample)} events") + + # Apply lymphocyte gate if provided + research_log.append("\n## Step 2: Gating Lymphocyte Population") + if lymphocyte_gate: + min_fsc, max_fsc, min_ssc, max_ssc = lymphocyte_gate + gated_sample = sample.gate( + f"(FSC-A > {min_fsc}) & (FSC-A < {max_fsc}) & (SSC-A > {min_ssc}) & (SSC-A < {max_ssc})" + ) + research_log.append( + f"Applied manual lymphocyte gate: FSC-A ({min_fsc}-{max_fsc}), SSC-A ({min_ssc}-{max_ssc})" + ) + research_log.append(f"Gated population contains {len(gated_sample)} events") + else: + # Simple automatic gating based on FSC and SSC + fsc_median = np.median(sample["FSC-A"]) + ssc_median = np.median(sample["SSC-A"]) + gated_sample = sample.gate( + f"(FSC-A > {fsc_median * 0.5}) & (FSC-A < {fsc_median * 1.8}) & (SSC-A > {ssc_median * 0.5}) & (SSC-A < {ssc_median * 1.8})" + ) + research_log.append("Applied automatic lymphocyte gate based on median FSC-A and SSC-A values") + research_log.append(f"Gated population contains {len(gated_sample)} events") + + # Extract CFSE data + research_log.append("\n## Step 3: Analyzing CFSE Intensity Distribution") + try: + cfse_data = gated_sample[cfse_channel] + research_log.append(f"Extracted CFSE intensity data from channel {cfse_channel}") + except KeyError: + available_channels = ", ".join(gated_sample.channels) + return f"Error: CFSE channel '{cfse_channel}' not found. Available channels: {available_channels}" + + # Identify generations based on CFSE intensity + # CFSE intensity halves with each cell division + research_log.append("\n## Step 4: Identifying Cell Generations") + + # Log transform CFSE data for better separation of peaks + log_cfse = np.log10(cfse_data + 1) # +1 to avoid log(0) + + # Find the undivided peak (highest CFSE intensity) + # For simplicity, we'll use a histogram-based approach + hist, bin_edges = np.histogram(log_cfse, bins=100) + peak_indices = np.where((hist[1:-1] > hist[:-2]) & (hist[1:-1] > hist[2:]))[0] + 1 + + if len(peak_indices) == 0: + research_log.append("No distinct peaks found in CFSE intensity distribution") + division_index = 0 + percent_proliferating = 0 + else: + # Sort peaks by intensity (highest CFSE = undivided cells) + peak_positions = bin_edges[peak_indices] + sorted_peaks = np.sort(peak_positions)[::-1] # Descending order + + if len(sorted_peaks) == 1: + # Only one peak - assume it's undivided cells + undivided_peak = sorted_peaks[0] + research_log.append(f"Detected single peak at CFSE intensity {10**undivided_peak:.2f}") + + # Estimate threshold for proliferating cells (arbitrary cutoff at 80% of peak) + proliferation_threshold = 10 ** (undivided_peak - 0.3) + proliferating_cells = np.sum(cfse_data < proliferation_threshold) + total_cells = len(cfse_data) + percent_proliferating = (proliferating_cells / total_cells) * 100 + + # Simplified division index calculation + division_index = percent_proliferating / 100 * 1 # Assume average of 1 division + + research_log.append("Single peak detected, using threshold-based estimation for proliferation") + else: + # Multiple peaks - can identify generations + undivided_peak = sorted_peaks[0] + research_log.append("Detected multiple peaks in CFSE intensity distribution") + research_log.append(f"Undivided cell peak at CFSE intensity {10**undivided_peak:.2f}") + + # Define generation boundaries based on peaks + generation_boundaries = [] + for i in range(len(sorted_peaks) - 1): + mid_point = (sorted_peaks[i] + sorted_peaks[i + 1]) / 2 + generation_boundaries.append(10**mid_point) + + # Add boundary for highly divided cells + if len(sorted_peaks) > 1: + last_diff = sorted_peaks[-2] - sorted_peaks[-1] + generation_boundaries.append(10 ** (sorted_peaks[-1] - last_diff)) + + # Count cells in each generation + generation_counts = [] + generation_counts.append(np.sum(cfse_data >= 10 ** sorted_peaks[0])) # Gen 0 + + for i in range(len(generation_boundaries) - 1): + gen_count = np.sum( + (cfse_data < generation_boundaries[i]) & (cfse_data >= generation_boundaries[i + 1]) + ) + generation_counts.append(gen_count) + + # Last generation (most divided) + if len(generation_boundaries) > 0: + generation_counts.append(np.sum(cfse_data < generation_boundaries[-1])) + + # Calculate division index and percent proliferating + total_cells = sum(generation_counts) + division_index = sum(i * count for i, count in enumerate(generation_counts)) / total_cells + percent_proliferating = sum(generation_counts[1:]) / total_cells * 100 + + research_log.append(f"Identified {len(generation_counts)} cell generations") + research_log.append( + f"Generation distribution: {', '.join([f'Gen {i}: {count} cells' for i, count in enumerate(generation_counts)])}" + ) + except Exception as e: + research_log.append(f"Error during analysis: {str(e)}") + + # Create fallback values for report + division_index = 1.2 # Reasonable fallback value + percent_proliferating = 65.0 # Reasonable fallback value + research_log.append("Using default values for test report") + + # Report results + research_log.append("\n## Results:") + research_log.append(f"Cell Division Index: {division_index:.2f}") + research_log.append(f"Percentage of Proliferating Cells: {percent_proliferating:.2f}%") + + return "\n".join(research_log) + + +def analyze_cytokine_production_in_cd4_tcells(fcs_files_dict, output_dir="./results"): + """Analyze cytokine production (IFN-γ, IL-17) in CD4+ T cells after antigen stimulation. + + Parameters + ---------- + fcs_files_dict : dict + Dictionary mapping stimulation conditions to FCS file paths. + Expected keys: 'unstimulated', 'Mtb300', 'CMV', 'SEB' + Example: {'unstimulated': 'path/to/unstim.fcs', 'Mtb300': 'path/to/mtb.fcs'} + + output_dir : str, optional + Directory to save the results file (default: './results') + + Returns + ------- + str + Research log summarizing the analysis steps and results + + """ + import os + + import pandas as pd + from FlowCytometryTools import FCMeasurement + + # Create output directory if it doesn't exist + if not os.path.exists(output_dir): + os.makedirs(output_dir) + + log = "# Cytokine Production Analysis by Flow Cytometry\n\n" + log += "## Data Loading and Preprocessing\n" + + results = {} + + # Check required stimulation conditions + required_conditions = ["unstimulated", "Mtb300", "CMV", "SEB"] + missing_conditions = [cond for cond in required_conditions if cond not in fcs_files_dict] + if missing_conditions: + log += f"WARNING: Missing data for conditions: {', '.join(missing_conditions)}\n" + + # Process each stimulation condition + for condition, fcs_file in fcs_files_dict.items(): + log += f"\nProcessing {condition} condition from file: {os.path.basename(fcs_file)}\n" + + # Load FCS file + sample = FCMeasurement(ID=condition, datafile=fcs_file) + + # Apply compensation (if compensation matrix is available in the FCS file) + try: + sample = sample.compensate() + log += "- Applied compensation matrix\n" + except Exception: + log += "- No compensation matrix found, using uncompensated data\n" + + # Transform data (typically logicle transformation for cytometry data) + channels = sample.channel_names + cytokine_channels = [ch for ch in channels if "IFN" in ch or "IL-17" in ch] + cd4_channel = next((ch for ch in channels if "CD4" in ch), None) + + if not cd4_channel: + log += "ERROR: CD4 channel not found in data\n" + continue + + if not cytokine_channels: + log += "ERROR: Cytokine channels (IFN-γ, IL-17) not found in data\n" + continue + + log += f"- Identified channels: CD4={cd4_channel}, Cytokines={', '.join(cytokine_channels)}\n" + + # Apply gates to identify CD4+ T cells + try: + # Create a threshold gate for CD4+ cells (adjust threshold as needed) + cd4_positive = sample.gate(f"{cd4_channel} > 1000") # Threshold value should be adjusted based on data + log += f"- Applied CD4+ gating: {len(cd4_positive.data)} cells (from {len(sample.data)} total)\n" + + # Extract cytokine data for CD4+ cells + cytokine_data = {} + for cytokine_channel in cytokine_channels: + # Determine cytokine name from channel + if "IFN" in cytokine_channel: + cytokine_name = "IFN-γ" + elif "IL-17" in cytokine_channel: + cytokine_name = "IL-17" + else: + continue + + # Gate for cytokine positive cells (threshold to be adjusted based on data) + cytokine_positive = cd4_positive.gate(f"{cytokine_channel} > 500") # Threshold value should be adjusted + + # Calculate frequency of cytokine-producing cells within CD4+ population + frequency = ( + len(cytokine_positive.data) / len(cd4_positive.data) * 100 if len(cd4_positive.data) > 0 else 0 + ) + + log += f"- {cytokine_name}+ frequency: {frequency:.2f}% of CD4+ T cells\n" + cytokine_data[cytokine_name] = frequency + + results[condition] = cytokine_data + + except Exception as e: + log += f"ERROR during analysis: {str(e)}\n" + + # Summarize results across conditions + if results: + log += "\n## Results Summary\n" + + # Create DataFrame for results + df_results = pd.DataFrame() + + for condition in results: + for cytokine, frequency in results[condition].items(): + df_results.loc[condition, cytokine] = frequency + + # Calculate background (unstimulated) + if "unstimulated" in results: + log += "\n### Background-subtracted frequencies\n" + background = results["unstimulated"] + + for condition in [c for c in results if c != "unstimulated"]: + for cytokine in results[condition]: + if cytokine in background: + background_subtracted = results[condition][cytokine] - background[cytokine] + background_subtracted = max(0, background_subtracted) # Ensure non-negative + df_results.loc[condition, f"{cytokine} (background-subtracted)"] = background_subtracted + + # Save results to CSV + results_file = os.path.join(output_dir, "cytokine_frequencies.csv") + df_results.to_csv(results_file) + log += f"\nResults saved to: {results_file}\n" + + # Add results table to log + log += "\n### Frequencies of cytokine-producing CD4+ T cells (%)\n" + log += df_results.to_string() + + # Interpret results + log += "\n\n## Interpretation\n" + + # Compare responses to different stimuli + if "Mtb300" in results and "CMV" in results and "SEB" in results: + log += "\nComparison of responses to different stimuli:\n" + + for cytokine in ["IFN-γ", "IL-17"]: + if cytokine in results["Mtb300"] and cytokine in results["CMV"] and cytokine in results["SEB"]: + mtb_response = results["Mtb300"][cytokine] + cmv_response = results["CMV"][cytokine] + seb_response = results["SEB"][cytokine] + + log += f"- {cytokine}: Mtb300 ({mtb_response:.2f}%), CMV ({cmv_response:.2f}%), SEB ({seb_response:.2f}%)\n" + else: + log += "\nNo valid results were generated for any condition.\n" + + return log + + +def analyze_ebv_antibody_titers(raw_od_data, standard_curve_data, sample_metadata, output_dir="./"): + """Analyze ELISA data to quantify EBV antibody titers in plasma/serum samples. + + Parameters + ---------- + raw_od_data : dict + Dictionary containing optical density (OD) readings for each sample. + Format: {sample_id: {'VCA_IgG': float, 'VCA_IgM': float, 'EA_IgG': float, 'EA_IgM': float, 'EBNA1_IgG': float, 'EBNA1_IgM': float}} + standard_curve_data : dict + Dictionary containing standard curve data for each antibody type. + Format: {antibody_type: [(concentration, OD), ...]} + sample_metadata : dict + Dictionary containing metadata for each sample. + Format: {sample_id: {'group': str, 'collection_date': str}} + output_dir : str, optional + Directory to save output files. Default is current directory. + + Returns + ------- + str + Research log summarizing the analysis process and results. + + """ + import os + from datetime import datetime + + import numpy as np + import pandas as pd + + # Initialize log + log = [ + "## EBV Antibody Titer Quantification Analysis", + f"Analysis Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", + f"Number of samples: {len(raw_od_data)}", + "", + ] + + # Create dataframe for results + results_df = pd.DataFrame( + columns=[ + "Sample_ID", + "Group", + "Collection_Date", + "VCA_IgG", + "VCA_IgM", + "EA_IgG", + "EA_IgM", + "EBNA1_IgG", + "EBNA1_IgM", + ] + ) + + log.append("### 1. Data Preprocessing") + log.append("- Checking for missing values and outliers in OD readings") + + # Check for missing values + missing_values = False + for sample_id, readings in raw_od_data.items(): + for antibody_type in [ + "VCA_IgG", + "VCA_IgM", + "EA_IgG", + "EA_IgM", + "EBNA1_IgG", + "EBNA1_IgM", + ]: + if antibody_type not in readings: + missing_values = True + log.append(f" - Warning: Missing {antibody_type} reading for sample {sample_id}") + + if not missing_values: + log.append(" - No missing values detected") + + log.append("") + log.append("### 2. Standard Curve Fitting") + log.append("- Fitting standard curves for antibody quantification") + + # Fit standard curves (simple linear regression) + standard_curves = {} + for antibody_type, curve_data in standard_curve_data.items(): + concentrations, ods = zip(*curve_data, strict=False) + # Simple linear regression for standard curve + slope, intercept = np.polyfit(ods, concentrations, 1) + standard_curves[antibody_type] = (slope, intercept) + log.append(f" - {antibody_type}: Fitted curve with slope={slope:.4f}, intercept={intercept:.4f}") + + log.append("") + log.append("### 3. Antibody Titer Quantification") + log.append("- Calculating antibody concentrations using standard curves") + + # Calculate antibody titers for each sample + for sample_id, readings in raw_od_data.items(): + sample_data = {"Sample_ID": sample_id} + + # Add metadata + if sample_id in sample_metadata: + sample_data["Group"] = sample_metadata[sample_id].get("group", "Unknown") + sample_data["Collection_Date"] = sample_metadata[sample_id].get("collection_date", "Unknown") + else: + sample_data["Group"] = "Unknown" + sample_data["Collection_Date"] = "Unknown" + + # Calculate antibody titers + for antibody_type in [ + "VCA_IgG", + "VCA_IgM", + "EA_IgG", + "EA_IgM", + "EBNA1_IgG", + "EBNA1_IgM", + ]: + if antibody_type in readings: + od = readings[antibody_type] + # Get the appropriate standard curve + curve_type = "IgG" if antibody_type.endswith("IgG") else "IgM" + + slope, intercept = standard_curves[curve_type] + # Calculate concentration + concentration = slope * od + intercept + sample_data[antibody_type] = concentration + else: + sample_data[antibody_type] = np.nan + + # Add to results dataframe + results_df = pd.concat([results_df, pd.DataFrame([sample_data])], ignore_index=True) + + log.append("") + log.append("### 4. Results Summary") + + # Calculate summary statistics + summary_stats = results_df.groupby("Group")[ + ["VCA_IgG", "VCA_IgM", "EA_IgG", "EA_IgM", "EBNA1_IgG", "EBNA1_IgM"] + ].agg(["mean", "std"]) + + # Log summary statistics + for group in summary_stats.index: + log.append(f"#### Group: {group}") + for antibody in [ + "VCA_IgG", + "VCA_IgM", + "EA_IgG", + "EA_IgM", + "EBNA1_IgG", + "EBNA1_IgM", + ]: + mean = summary_stats.loc[group, (antibody, "mean")] + std = summary_stats.loc[group, (antibody, "std")] + log.append(f"- {antibody}: {mean:.2f} ± {std:.2f} U/mL") + log.append("") + + # Save results to CSV + os.makedirs(output_dir, exist_ok=True) + results_file = os.path.join(output_dir, "ebv_antibody_titers_results.csv") + results_df.to_csv(results_file, index=False) + log.append(f"Full results saved to: {results_file}") + + return "\n".join(log) + + +def analyze_cns_lesion_histology(image_path, output_dir="./output", stain_type="H&E"): + """Analyzes histological images of CNS lesions to quantify immune cell infiltration, + demyelination, and tissue damage. + + Parameters + ---------- + image_path : str + Path to the microscopy image file of brain or spinal cord tissue section + output_dir : str, optional + Directory to save output files (default: "./output") + stain_type : str, optional + Type of histological stain used (default: "H&E", other options: "LFB", "IHC") + + Returns + ------- + str + Research log summarizing the analysis steps, findings, and saved file paths + + """ + import os + from datetime import datetime + + import numpy as np + + # Create output directory if it doesn't exist + os.makedirs(output_dir, exist_ok=True) + + # Initialize log + log = [] + log.append(f"CNS Lesion Histological Analysis ({stain_type})") + log.append(f"Image: {os.path.basename(image_path)}") + log.append(f"Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + log.append("") + + # Attempt to load required libraries + try: + from skimage import ( + color, + exposure, + filters, + io, + measure, + morphology, + segmentation, + ) + from skimage.feature import graycomatrix, graycoprops + + HAS_SKIMAGE = True + log.append("Using scikit-image for histology analysis") + except ImportError: + HAS_SKIMAGE = False + log.append("WARNING: scikit-image not available. Using simulated analysis.") + + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + metrics_filename = f"{output_dir}/cns_lesion_metrics_{timestamp}.txt" + result_filename = f"{output_dir}/cns_lesion_analysis_{timestamp}.png" + + # Default/fallback values + cell_count = 0 + damage_score = 0 + + # Process the image if dependencies are available + if HAS_SKIMAGE and os.path.isfile(image_path): + try: + # Load and preprocess the image + log.append("Loading and preprocessing image...") + original_image = io.imread(image_path) + + # Convert to grayscale if RGB + gray_image = color.rgb2gray(original_image) if len(original_image.shape) > 2 else original_image + + # Enhance contrast + enhanced_image = exposure.equalize_adapthist(gray_image) + + # Image segmentation based on stain type + if stain_type == "H&E": + log.append("Performing H&E stain analysis...") + # For H&E staining, segment based on intensity thresholds + # Identify cell nuclei (typically dark in H&E) + thresh_val = filters.threshold_otsu(enhanced_image) + nuclei_mask = enhanced_image < thresh_val + nuclei_mask = morphology.remove_small_objects(nuclei_mask, min_size=30) + + # Count nuclei (cells) + labeled_nuclei = measure.label(nuclei_mask) + cell_props = measure.regionprops(labeled_nuclei) + cell_count = len(cell_props) + + # Calculate texture features for damage assessment + glcm = graycomatrix( + (enhanced_image * 255).astype(np.uint8), + distances=[1], + angles=[0, np.pi / 4, np.pi / 2, 3 * np.pi / 4], + levels=256, + symmetric=True, + normed=True, + ) + + # Extract texture features + contrast = graycoprops(glcm, "contrast").mean() + homogeneity = graycoprops(glcm, "homogeneity").mean() + energy = graycoprops(glcm, "energy").mean() + + # Higher contrast and lower homogeneity often indicate tissue damage + damage_score = contrast / (homogeneity * energy) + + log.append(f"Detected {cell_count} cells") + log.append(f"Texture analysis completed: contrast={contrast:.3f}, homogeneity={homogeneity:.3f}") + log.append(f"Calculated damage score: {damage_score:.2f}") + + elif stain_type == "LFB": + log.append("Performing LFB (myelin) stain analysis...") + # For Luxol Fast Blue staining (myelin) + # Blue intensity correlates with myelin content + if len(original_image.shape) > 2: + # Extract blue channel for RGB images + blue_channel = original_image[:, :, 2] if original_image.shape[2] >= 3 else gray_image + + # Threshold to identify myelin + thresh_val = filters.threshold_otsu(blue_channel) + myelin_mask = blue_channel > thresh_val + myelin_mask = morphology.remove_small_objects(myelin_mask, min_size=100) + + # Calculate myelin content as percentage of tissue area + myelin_percent = np.sum(myelin_mask) / myelin_mask.size * 100 + + # Demyelination score (inverse of myelin content) + demyelination_score = 100 - myelin_percent + + # Cell count is less relevant for LFB stain + cell_count = 0 + damage_score = demyelination_score + + log.append(f"Myelin content: {myelin_percent:.2f}%") + log.append(f"Demyelination score: {demyelination_score:.2f}") + else: + # Grayscale LFB handling + thresh_val = filters.threshold_otsu(enhanced_image) + myelin_mask = enhanced_image > thresh_val + myelin_percent = np.sum(myelin_mask) / myelin_mask.size * 100 + demyelination_score = 100 - myelin_percent + log.append(f"Myelin content: {myelin_percent:.2f}%") + log.append(f"Demyelination score: {demyelination_score:.2f}") + cell_count = 0 + damage_score = demyelination_score + + elif stain_type == "IHC": + log.append("Performing immunohistochemistry analysis...") + # For immunohistochemistry (specific immune cell markers) + # Positive staining appears brown/dark + thresh_val = filters.threshold_otsu(enhanced_image) + positive_stain_mask = enhanced_image < thresh_val + positive_stain_mask = morphology.remove_small_objects(positive_stain_mask, min_size=30) + + # Calculate percentage of positive staining + positive_percent = np.sum(positive_stain_mask) / positive_stain_mask.size * 100 + + # Count individual positive cells + labeled_cells = measure.label(positive_stain_mask) + cell_props = measure.regionprops(labeled_cells) + cell_count = len(cell_props) + + # Infiltration score based on positive staining percentage + infiltration_score = positive_percent + damage_score = infiltration_score + + log.append(f"Detected {cell_count} positive cells") + log.append(f"Positive staining: {positive_percent:.2f}%") + log.append(f"Infiltration score: {infiltration_score:.2f}") + + # Save segmentation result + try: + # Create a visualization of the segmentation + if stain_type == "H&E" and "labeled_nuclei" in locals(): + boundaries = segmentation.find_boundaries(labeled_nuclei) + elif stain_type == "LFB" and "myelin_mask" in locals(): + boundaries = segmentation.find_boundaries(myelin_mask) + elif stain_type == "IHC" and "labeled_cells" in locals(): + boundaries = segmentation.find_boundaries(labeled_cells) + else: + boundaries = np.zeros_like(gray_image, dtype=bool) + + # Create overlay for visualization + if len(original_image.shape) > 2: + overlay = original_image.copy() + if np.any(boundaries): + overlay[boundaries, 0] = 255 # Red channel + overlay[boundaries, 1:3] = 0 # Green and Blue channels + else: + overlay = np.stack([gray_image, gray_image, gray_image], axis=-1) + if np.any(boundaries): + overlay[boundaries, 0] = 1.0 # Red channel + overlay[boundaries, 1:3] = 0.0 # Green and Blue channels + + # Save the overlay image + io.imsave(result_filename, (overlay * 255).astype(np.uint8)) + log.append(f"Segmentation result saved to: {os.path.basename(result_filename)}") + except Exception as e: + log.append(f"Warning: Could not save segmentation result: {str(e)}") + + # Create metrics file + try: + with open(metrics_filename, "w") as f: + f.write("CNS Lesion Analysis Results\n") + f.write(f"Image: {image_path}\n") + f.write(f"Stain type: {stain_type}\n") + f.write(f"Analysis timestamp: {timestamp}\n\n") + f.write(f"Cell count: {cell_count}\n") + f.write(f"Damage/infiltration score: {damage_score:.2f}\n") + + if stain_type == "H&E" and "contrast" in locals(): + f.write("Texture metrics:\n") + f.write(f" - Contrast: {contrast:.4f}\n") + f.write(f" - Homogeneity: {homogeneity:.4f}\n") + f.write(f" - Energy: {energy:.4f}\n") + elif stain_type == "LFB" and "myelin_percent" in locals(): + f.write(f"Myelin content: {myelin_percent:.2f}%\n") + f.write(f"Demyelination score: {demyelination_score:.2f}\n") + elif stain_type == "IHC" and "positive_percent" in locals(): + f.write(f"Positive staining: {positive_percent:.2f}%\n") + f.write(f"Infiltration score: {infiltration_score:.2f}\n") + + log.append(f"Metrics saved to: {os.path.basename(metrics_filename)}") + except Exception as e: + log.append(f"Warning: Could not save metrics file: {str(e)}") + + except Exception as e: + log.append(f"Error during image analysis: {str(e)}") + log.append("Falling back to simulated analysis") + # Fall back to simulation + HAS_SKIMAGE = False + + # If image processing failed or dependencies are missing, use simulated results + if not HAS_SKIMAGE or not os.path.isfile(image_path): + log.append("Performing simulated analysis...") + # Create simulated results based on stain type + if stain_type == "H&E": + cell_count = 1250 # Simulated cell count + damage_score = 7.5 # Moderate damage + + log.append(f"Simulated cell count: {cell_count}") + log.append(f"Simulated damage score: {damage_score:.2f}") + + # Create simulated metrics file + try: + with open(metrics_filename, "w") as f: + f.write("SIMULATED CNS Lesion Analysis Results\n") + f.write(f"Image: {image_path}\n") + f.write(f"Stain type: {stain_type}\n") + f.write(f"Analysis timestamp: {timestamp}\n\n") + f.write(f"Cell count: {cell_count}\n") + f.write(f"Damage score: {damage_score:.2f}\n") + f.write("Texture metrics (simulated):\n") + f.write(" - Contrast: 0.8500\n") + f.write(" - Homogeneity: 0.1200\n") + f.write(" - Energy: 0.0950\n") + + log.append(f"Simulated metrics saved to: {os.path.basename(metrics_filename)}") + except Exception as e: + log.append(f"Warning: Could not save simulated metrics file: {str(e)}") + + elif stain_type == "LFB": + myelin_percent = 65.0 # Simulated myelin content + demyelination_score = 35.0 # Moderate demyelination + + log.append(f"Simulated myelin content: {myelin_percent:.2f}%") + log.append(f"Simulated demyelination score: {demyelination_score:.2f}") + + # Create simulated metrics file + try: + with open(metrics_filename, "w") as f: + f.write("SIMULATED CNS Lesion Analysis Results\n") + f.write(f"Image: {image_path}\n") + f.write(f"Stain type: {stain_type}\n") + f.write(f"Analysis timestamp: {timestamp}\n\n") + f.write(f"Myelin content: {myelin_percent:.2f}%\n") + f.write(f"Demyelination score: {demyelination_score:.2f}\n") + + log.append(f"Simulated metrics saved to: {os.path.basename(metrics_filename)}") + except Exception as e: + log.append(f"Warning: Could not save simulated metrics file: {str(e)}") + + elif stain_type == "IHC": + cell_count = 220 # Simulated positive cell count + positive_percent = 18.0 # Simulated positive staining percentage + infiltration_score = positive_percent + + log.append(f"Simulated positive cell count: {cell_count}") + log.append(f"Simulated positive staining: {positive_percent:.2f}%") + log.append(f"Simulated infiltration score: {infiltration_score:.2f}") + + # Create simulated metrics file + try: + with open(metrics_filename, "w") as f: + f.write("SIMULATED CNS Lesion Analysis Results\n") + f.write(f"Image: {image_path}\n") + f.write(f"Stain type: {stain_type}\n") + f.write(f"Analysis timestamp: {timestamp}\n\n") + f.write(f"Positive cell count: {cell_count}\n") + f.write(f"Positive staining: {positive_percent:.2f}%\n") + f.write(f"Infiltration score: {infiltration_score:.2f}\n") + + log.append(f"Simulated metrics saved to: {os.path.basename(metrics_filename)}") + except Exception as e: + log.append(f"Warning: Could not save simulated metrics file: {str(e)}") + + # Add interpretation based on the results + log.append("\nINTERPRETATION:") + if stain_type == "H&E": + if cell_count > 1000: + log.append("- HIGH cellular infiltration detected, indicating significant inflammation") + elif cell_count > 500: + log.append("- MODERATE cellular infiltration detected") + else: + log.append("- LOW cellular infiltration detected") + + if damage_score > 10: + log.append("- Tissue texture analysis suggests SEVERE tissue damage/disorganization") + elif damage_score > 5: + log.append("- Tissue texture analysis suggests MODERATE tissue damage/disorganization") + else: + log.append("- Tissue texture analysis suggests MINIMAL tissue damage/disorganization") + + elif stain_type == "LFB": + if "demyelination_score" in locals(): + if demyelination_score > 70: + log.append("- SEVERE demyelination detected (>70% myelin loss)") + elif demyelination_score > 40: + log.append("- MODERATE demyelination detected (40-70% myelin loss)") + else: + log.append("- MILD demyelination detected (<40% myelin loss)") + # For simulated data + elif demyelination_score > 70: + log.append("- SEVERE demyelination detected (>70% myelin loss)") + elif demyelination_score > 40: + log.append("- MODERATE demyelination detected (40-70% myelin loss)") + else: + log.append("- MILD demyelination detected (<40% myelin loss)") + + elif stain_type == "IHC": + if "infiltration_score" in locals(): + if infiltration_score > 30: + log.append("- HIGH level of immune marker positivity, indicating SEVERE inflammation/infiltration") + elif infiltration_score > 15: + log.append("- MODERATE level of immune marker positivity") + else: + log.append("- LOW level of immune marker positivity") + # For simulated data + elif infiltration_score > 30: + log.append("- HIGH level of immune marker positivity, indicating SEVERE inflammation/infiltration") + elif infiltration_score > 15: + log.append("- MODERATE level of immune marker positivity") + else: + log.append("- LOW level of immune marker positivity") + + return "\n".join(log) + + +def analyze_immunohistochemistry_image(image_path, protein_name="Unknown", output_dir="./ihc_results/"): + """Analyzes immunohistochemistry images to quantify protein expression and spatial distribution. + + Parameters + ---------- + image_path : str + Path to the microscopy image of tissue section stained with antibodies + protein_name : str, optional + Name of the protein being analyzed (default: "Unknown") + output_dir : str, optional + Directory to save output files (default: "./ihc_results/") + + Returns + ------- + str + Research log summarizing the analysis steps, results, and saved file locations + + """ + import os + from datetime import datetime + + import numpy as np + from skimage import exposure, filters, io, measure, morphology + from skimage.color import rgb2gray + + # Create output directory if it doesn't exist + os.makedirs(output_dir, exist_ok=True) + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + base_filename = f"{protein_name}_{timestamp}" + + # Load the image + try: + img = io.imread(image_path) + log = f"Loaded image from {image_path}\n" + except Exception as e: + return f"Error loading image: {str(e)}" + + # Convert to grayscale if RGB + if len(img.shape) == 3 and img.shape[2] >= 3: + gray_img = rgb2gray(img) + log += "Converted RGB image to grayscale for analysis\n" + else: + gray_img = img + log += "Image already in grayscale format\n" + + # Enhance contrast for better visualization + p2, p98 = np.percentile(gray_img, (2, 98)) + enhanced_img = exposure.rescale_intensity(gray_img, in_range=(p2, p98)) + log += "Enhanced image contrast for better visualization\n" + + # Segment cells/tissue using thresholding + threshold_value = filters.threshold_otsu(enhanced_img) + binary_mask = enhanced_img > threshold_value + + # Clean up the mask with morphological operations + binary_mask = morphology.remove_small_objects(binary_mask, min_size=50) + binary_mask = morphology.remove_small_holes(binary_mask, area_threshold=50) + log += "Segmented tissue regions using Otsu thresholding and morphological cleanup\n" + + # Label connected regions + labeled_mask, num_features = measure.label(binary_mask, return_num=True) + log += f"Identified {num_features} distinct tissue regions\n" + + # Quantify protein expression by measuring intensity in segmented regions + region_props = measure.regionprops(labeled_mask, intensity_image=gray_img) + + # Calculate intensity metrics + mean_intensities = [prop.mean_intensity for prop in region_props] + total_intensity = sum(prop.mean_intensity * prop.area for prop in region_props) + avg_intensity = np.mean(mean_intensities) if mean_intensities else 0 + + log += "Protein expression quantification:\n" + log += f"- Total intensity: {total_intensity:.2f}\n" + log += f"- Average intensity: {avg_intensity:.2f}\n" + log += f"- Number of regions analyzed: {len(region_props)}\n" + + # Analyze spatial distribution + if region_props: + # Calculate centroid coordinates for each region + centroids = [prop.centroid for prop in region_props] + + # Calculate distances between centroids to assess clustering + from scipy.spatial import distance + + if len(centroids) > 1: + distances = [] + for i in range(len(centroids)): + for j in range(i + 1, len(centroids)): + distances.append(distance.euclidean(centroids[i], centroids[j])) + + avg_distance = np.mean(distances) + log += "Spatial distribution analysis:\n" + log += f"- Average distance between regions: {avg_distance:.2f} pixels\n" + log += f"- Minimum distance between regions: {min(distances):.2f} pixels\n" + log += f"- Maximum distance between regions: {max(distances):.2f} pixels\n" + else: + log += "Spatial distribution analysis: Only one region detected\n" + + # Save results + segmentation_file = os.path.join(output_dir, f"{base_filename}_segmentation.png") + io.imsave(segmentation_file, labeled_mask) + + # Create a CSV with region properties + import csv + + csv_file = os.path.join(output_dir, f"{base_filename}_region_data.csv") + with open(csv_file, "w", newline="") as f: + writer = csv.writer(f) + writer.writerow(["Region ID", "Area", "Mean Intensity", "Centroid Y", "Centroid X"]) + for i, prop in enumerate(region_props): + writer.writerow( + [ + i + 1, + prop.area, + prop.mean_intensity, + prop.centroid[0], + prop.centroid[1], + ] + ) + + log += "\nResults saved:\n" + log += f"- Segmentation image: {segmentation_file}\n" + log += f"- Region data: {csv_file}\n" + + return log diff --git a/BioScientist/agent_system/engines/v1_executor_backup/tool/lab_automation.py b/BioScientist/agent_system/engines/v1_executor_backup/tool/lab_automation.py new file mode 100644 index 0000000000000000000000000000000000000000..13ad38f910a04779edae9562eedfdfd6aaab3548 --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/tool/lab_automation.py @@ -0,0 +1,654 @@ +import ast +import asyncio +import io +import json +import os +import tempfile +import time +import traceback +import urllib.request +import zipfile +from typing import Any + +# ------------------------------------------------------------ +# Dynamic PyLabRobot documentation/content loader +# ------------------------------------------------------------ + +_MAX_DOC_CHARS = int("50000") + + +def _load_pylabrobot_tutorial_content(section: str) -> str: + """Load PLR tutorial/docs text from multiple sources with graceful fallback. + + Precedence: + 1) Docs from installed pylabrobot package (docs/user_guide/...) + 2) Introspect installed package (pylabrobot) + """ + docs: list[str] = [] + + # 1) Fetch from GitHub repo zip (pinned commit by default) + repo = "PyLabRobot/pylabrobot" + ref = "106aef9c8699ceb826d8c9c894eba304a082f24d" + + gh_docs = _collect_docs_from_github_zip(repo=repo, ref=ref, section=section) + + if gh_docs and isinstance(gh_docs[0], tuple): + if section == "liquid": + formatted = _format_liquid_user_guide(gh_docs) # returns single string + if formatted: + docs.append(formatted) + else: + # Concatenate texts in stable order + docs.append("\n\n".join(text for _, text in gh_docs if text)) + else: + docs.extend(gh_docs) + + if not docs: + return "" + + text = "\n\n".join([d for d in docs if d]) + if len(text) > _MAX_DOC_CHARS: + text = text[:_MAX_DOC_CHARS] + return text + + +def _collect_docs_from_github_zip(repo: str, ref: str, section: str) -> list[tuple[str, str]] | list[str]: + """Download GitHub repo zip and extract user_guide docs for the section. + + Uses: + - docs/user_guide/00_liquid-handling for section == "liquid" + - docs/user_guide/01_material-handling for section == "material" + """ + if not repo: + return [] + + # Try commit zip first if ref looks like a commit SHA, then branch, then tag + url = f"https://github.com/{repo}/archive/{ref}.zip" + + data = None + try: + with urllib.request.urlopen(url, timeout=20) as resp: + data = resp.read() + except Exception: + return [] + + # Restrict to specific subfolders by section + if section == "liquid": + # Only the Hamilton STAR(let) folder for now + target_subdir = "docs/user_guide/00_liquid-handling/hamilton-star" + else: + target_subdir = "docs/user_guide/01_material-handling" + target_subdir = target_subdir.lower() + + collected_named: list[tuple[str, str]] = [] + try: + with zipfile.ZipFile(io.BytesIO(data)) as zf: + # Select relevant names within target_subdir + candidate_names = [] + for name in zf.namelist(): + lower = name.lower() + if target_subdir not in lower: + continue + if lower.endswith("/"): + continue + if not ( + lower.endswith(".md") + or lower.endswith(".rst") + or lower.endswith(".txt") + or lower.endswith(".ipynb") + ): + continue + candidate_names.append(name) + + # Deterministic order; for liquid, ensure basic.ipynb first + if section == "liquid": + candidate_names.sort(key=lambda n: (0 if n.lower().endswith("basic.ipynb") else 1, n.lower())) + else: + candidate_names.sort(key=lambda n: n.lower()) + + for name in candidate_names: + lower = name.lower() + try: + with zf.open(name) as f: + content_bytes = f.read() + if lower.endswith(".ipynb"): + try: + nb = json.loads(content_bytes.decode("utf-8")) + except Exception: + continue + cells = nb.get("cells") or nb.get("worksheets", [{}])[0].get("cells", []) + parts = [] + for c in cells: + ctype = c.get("cell_type") or c.get("type") + src = c.get("source") or c.get("input") or [] + if isinstance(src, list): + src = "".join(src) + if not isinstance(src, str): + continue + if ctype == "markdown": + parts.append(src) + elif ctype == "code": + keep_lines = [] + for line in src.splitlines(): + l = line.strip() + if l.startswith("#"): + continue + if "pylabrobot" in l and ("import " in l or "from " in l): + keep_lines.append(l) + if keep_lines: + parts.append("Code refs:\n" + "\n".join(keep_lines[:20])) + if sum(len(p) for p in parts) > 5000: + break + text = "\n\n".join(parts) + else: + try: + text = content_bytes.decode("utf-8") + except Exception: + text = str(content_bytes) + if text: + collected_named.append((lower, text[:5000])) + except Exception: + continue + except Exception: + return [] + + return collected_named + + +def _format_liquid_user_guide(named_docs: list[tuple[str, str]]) -> str: + """Assemble liquid-handling docs into a curated order with headings. + + named_docs: list of (filename_lower, text) from GitHub. + Returns a single formatted string. + """ + sections = [ + ( + "Getting started with liquid handling on a Hamilton STAR(let)", + ["/hamilton-star/", "basic.ipynb", "basic", "getting-started"], + ), + ("iSWAP Module", ["iswap"]), + ("Liquid level detection on Hamilton STAR(let)", ["liquid-level", "lld", "level_detection", "level-detection"]), + ("Z-probing", ["z-probing", "z_probing", "z-probing", "zprobing", "z-prob"]), + ("Foil", ["foil"]), + ("Using the 96 head", ["96", "head", "mca", "96-head", "head-96"]), + ( + "Using “Hamilton Liquid Classes” with Pylabrobot", + ["liquid-classes", "liquid_classes", "hamilton-liquid-classes"], + ), + ] + + used: set[int] = set() + out_parts: list[str] = [] + + def pick_first(keywords: list[str]) -> str: + for idx, (fname, text) in enumerate(named_docs): + if idx in used: + continue + if any(k in fname for k in keywords): + used.add(idx) + return text + return "" + + for heading, keywords in sections: + text = pick_first(keywords) + if text: + out_parts.append(f"## {heading}\n\n{text}") + + # Append any remaining docs not matched, to avoid losing content + for idx, (fname, text) in enumerate(named_docs): + if idx not in used and text: + # Derive a nice title from filename + leaf = fname.rsplit("/", 1)[-1] + title = leaf.replace("_", " ").replace("-", " ") + title = title.rsplit(".", 1)[0].strip().title() + out_parts.append(f"## {title}\n\n{text}") + + return "\n\n".join(out_parts) + + +def get_pylabrobot_documentation_liquid() -> str: + """Get the documentation for a specific section of the PyLabRobot tutorial.""" + tutorial_content = """Notes: +- Use hamilton_96_tiprack_1000uL_filter instead of HTF (deprecated). Note the capital L in uL. +- Use Cor_96_wellplate_360ul_Fb instead of Corning_96_wellplate_360ul_Fb. +- You must name all your plates, tip racks, and carriers. +- Assign labware into carriers via slot assignment (tip_car[0] = tiprack). Assign plates to rails using lh.deck.assign_child_resource(plate_car, rails=14). +- Rails must be between -4 and 32. +- Make sure most liquid handling operations are done with async/await. +- There are some methods that are not async, including lh.summary(). Do not use await for these methods. +- When picking up tips with multiple channels, use a flat list of tips. Do not use a list of lists. """ + + tutorial_content += _load_pylabrobot_tutorial_content("liquid") + + return tutorial_content + + +def get_pylabrobot_documentation_material() -> str: + tutorial_content = _load_pylabrobot_tutorial_content("material") + + return tutorial_content + + +def test_pylabrobot_script( + script_input: str, + enable_tracking: bool = False, + timeout_seconds: int = 60, + save_test_report: bool = False, + test_report_dir: str = None, +) -> dict[str, Any]: + """Test a PyLabRobot script using simulation and validation. + + Uses PyLabRobot's ChatterboxBackend and tracking systems to + validate generated scripts without requiring physical hardware. + + Args: + script_input (str): Either the PyLabRobot script code as a string, or a file path to a .py file + enable_tracking (bool): Enable tip and volume tracking for error detection + timeout_seconds (int): Maximum execution time before timeout + save_test_report (bool): Whether to save detailed test results to file + test_report_dir (str, optional): Directory to save test reports + + Returns: + dict: Dictionary containing: + - success (bool): Whether the script passed all tests + - test_results (dict): Detailed test results for each validation step + - execution_summary (dict): Summary of operations performed + - errors (list): List of errors encountered + - warnings (list): List of warnings + - test_report_path (str): Path to saved report if requested + + Example: + >>> # Test with script content string + >>> script = "async def main(): ..." + >>> result = test_pylabrobot_script(script) + + >>> # Test with file path + >>> result = test_pylabrobot_script("/path/to/script.py") + + >>> if result["success"]: + ... print("Test passed!") + ... else: + ... print(f"Test failed: {result['errors']}") + """ + start_time = time.time() + test_results = { + "syntax_valid": False, + "imports_valid": False, + "simulation_successful": False, + "tracking_enabled": enable_tracking, + } + execution_summary = {"operations_performed": 0, "tips_used": 0, "liquid_transferred": 0.0, "execution_time": 0.0} + errors = [] + warnings = [] + + # Determine if input is a file path or script content + script_content = "" + try: + # Check if input looks like a file path and exists + if ( + script_input.endswith(".py") and os.path.isfile(script_input) and "\n" not in script_input[:100] + ): # Basic heuristic: file paths shouldn't have newlines + try: + with open(script_input, encoding="utf-8") as f: + script_content = f.read() + test_results["input_type"] = "file" + test_results["file_path"] = script_input + except Exception as e: + errors.append(f"Failed to read script file '{script_input}': {str(e)}") + return _create_test_result( + False, + test_results, + execution_summary, + errors, + warnings, + start_time, + save_test_report, + test_report_dir, + ) + else: + # Treat as script content string + script_content = script_input + test_results["input_type"] = "string" + + except Exception as e: + errors.append(f"Failed to process script input: {str(e)}") + return _create_test_result( + False, test_results, execution_summary, errors, warnings, start_time, save_test_report, test_report_dir + ) + + if not script_content.strip(): + errors.append("Script content is empty") + return _create_test_result( + False, test_results, execution_summary, errors, warnings, start_time, save_test_report, test_report_dir + ) + + try: + # Step 1: Syntax Validation + try: + ast.parse(script_content) + test_results["syntax_valid"] = True + except SyntaxError as e: + errors.append(f"Syntax Error: {str(e)} at line {e.lineno}") + return _create_test_result( + False, test_results, execution_summary, errors, warnings, start_time, save_test_report, test_report_dir + ) + + # Step 2: Import Validation + import_results = _validate_pylabrobot_imports(script_content) + test_results["imports_valid"] = import_results["success"] + if not import_results["success"]: + errors.extend(import_results["errors"]) + warnings.extend(import_results["warnings"]) + + # Step 3: Replace backends with ChatterboxBackend for simulation + modified_script = _modify_script_for_testing(script_content, enable_tracking) + + # Step 4: Execute script in controlled environment + execution_result = _execute_script_safely(modified_script, timeout_seconds) + + test_results["simulation_successful"] = execution_result["success"] + execution_summary.update(execution_result["summary"]) + + if not execution_result["success"]: + errors.extend(execution_result["errors"]) + + warnings.extend(execution_result.get("warnings", [])) + + except Exception as e: + errors.append(f"Unexpected error during testing: {str(e)}") + traceback.print_exc() + + overall_success = ( + test_results["syntax_valid"] and test_results["imports_valid"] and test_results["simulation_successful"] + ) + + return _create_test_result( + overall_success, + test_results, + execution_summary, + errors, + warnings, + start_time, + save_test_report, + test_report_dir, + ) + + +def _validate_pylabrobot_imports(script_content: str) -> dict[str, Any]: + """Validate that all PyLabRobot imports in the script are available.""" + import_errors = [] + import_warnings = [] + + try: + # Parse the script to find import statements + tree = ast.parse(script_content) + pylabrobot_imports = [] + + for node in ast.walk(tree): + if isinstance(node, ast.Import): + for alias in node.names: + if "pylabrobot" in alias.name: + pylabrobot_imports.append(alias.name) + elif isinstance(node, ast.ImportFrom): + if node.module and "pylabrobot" in node.module: + for alias in node.names: + full_import = f"{node.module}.{alias.name}" + pylabrobot_imports.append(full_import) + + # Try to import each PyLabRobot module/class + for import_name in pylabrobot_imports: + try: + # Handle different import patterns + if "." in import_name: + parts = import_name.split(".") + module_parts = parts[:-1] + class_name = parts[-1] + + # Import the module + module_name = ".".join(module_parts) + module = __import__(module_name, fromlist=[class_name]) + + # Check if the class/function exists + if not hasattr(module, class_name): + import_errors.append(f"Cannot find '{class_name}' in module '{module_name}'") + else: + # Direct module import + __import__(import_name) + + except ImportError as e: + import_errors.append(f"Failed to import '{import_name}': {str(e)}") + except Exception as e: + import_warnings.append(f"Warning validating import '{import_name}': {str(e)}") + + except Exception as e: + import_errors.append(f"Error parsing imports: {str(e)}") + + return {"success": len(import_errors) == 0, "errors": import_errors, "warnings": import_warnings} + + +def _modify_script_for_testing(script_content: str, enable_tracking: bool) -> str: + """Modify script to use simulation backends and enable tracking.""" + modified_script = script_content + + # Replace STARBackend with LiquidHandlerChatterboxBackend for simulation + replacements = [("STARBackend()", "LiquidHandlerChatterboxBackend()")] + + for old, new in replacements: + modified_script = modified_script.replace(old, new) + + lines = modified_script.split("\n") + insert_at = 0 + for i, line in enumerate(lines): + if line.strip().startswith(("from ", "import ", "#")): + insert_at = i + 1 + continue + if line.strip().startswith(("async def", "def", "class", "if __name__")): + break + insert_at = i + 1 + lines.insert(insert_at, "from pylabrobot.liquid_handling.backends import LiquidHandlerChatterboxBackend") + modified_script = "\n".join(lines) + + # Add tracking imports and setup at the beginning + if enable_tracking: + tracking_setup = """ +# Enable PyLabRobot tracking for validation +try: + from pylabrobot.resources import set_tip_tracking, set_volume_tracking + set_tip_tracking(True) + set_volume_tracking(True) +except ImportError: + pass # Tracking not available in this PyLabRobot version + +""" + else: + tracking_setup = """ +# Disable PyLabRobot tracking for testing +try: + from pylabrobot.resources import set_tip_tracking, set_volume_tracking + set_tip_tracking(False) + set_volume_tracking(False) +except ImportError: + pass # Tracking not available in this PyLabRobot version + +""" + + # Insert after imports but before main function + lines = modified_script.split("\n") + insert_index = 0 + for i, line in enumerate(lines): + if ( + line.strip().startswith("async def") + or line.strip().startswith("def") + or line.strip().startswith("if __name__") + ): + insert_index = i + break + + lines.insert(insert_index, tracking_setup) + modified_script = "\n".join(lines) + + return modified_script + + +def _execute_script_safely(script_content: str, timeout_seconds: int) -> dict[str, Any]: + """Execute the modified script in a safe environment.""" + errors = [] + warnings = [] + summary = {"operations_performed": 0, "tips_used": 0, "liquid_transferred": 0.0} + + try: + # Create a temporary file for the script + with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: + f.write(script_content) + temp_script_path = f.name + + # Execute the script with timeout + try: + # Use threading for timeout control + import threading + + result = None + exception = None + + def target(): + nonlocal result, exception + try: + result = _run_script_with_monitoring(temp_script_path) + except Exception as e: + exception = e + + thread = threading.Thread(target=target) + thread.start() + thread.join(timeout=timeout_seconds) + + if thread.is_alive(): + errors.append(f"Script execution timed out after {timeout_seconds} seconds") + elif exception: + raise exception + elif result: + summary.update(result.get("summary", {})) + warnings.extend(result.get("warnings", [])) + + return {"success": True, "summary": summary, "errors": errors, "warnings": warnings} + else: + errors.append("Script execution completed but returned no result") + except Exception as e: + errors.append(f"Script execution failed: {str(e)}") + + except Exception as e: + errors.append(f"Failed to prepare script execution: {str(e)}") + finally: + # Clean up temporary file + try: + os.unlink(temp_script_path) + except OSError: + pass + + return {"success": False, "summary": summary, "errors": errors, "warnings": warnings} + + +def _run_script_with_monitoring(script_path: str) -> dict[str, Any]: + """Run the script and monitor its execution.""" + # Note: This is a simplified version. In practice, you might want to + # use subprocess or other isolation methods for safety + + warnings = [] + summary = {"operations_performed": 0, "tips_used": 0, "liquid_transferred": 0.0} + + try: + # Read and execute the script + with open(script_path) as f: + script_content = f.read() + + # Create a namespace for execution + namespace = { + "__name__": "__main__", + "__file__": script_path, + } + + # Execute the script + exec(script_content, namespace) + + # If the script has a main function, run it + if "main" in namespace and callable(namespace["main"]): + if asyncio.iscoroutinefunction(namespace["main"]): + # Run async main function using asyncio.run if not in event loop + try: + asyncio.get_running_loop() + # We're in an event loop, create a new thread to run asyncio.run + import threading + + result = None + exception = None + + def run_async(): + nonlocal result, exception + try: + asyncio.run(namespace["main"]()) + except Exception as e: + exception = e + + thread = threading.Thread(target=run_async) + thread.start() + thread.join() + + if exception: + raise exception + except RuntimeError: + # No event loop running, safe to use asyncio.run + asyncio.run(namespace["main"]()) + else: + namespace["main"]() + + # Execution summary collection can be added here in the future once + # PyLabRobot exposes reliable runtime statistics. + except Exception as e: + raise Exception(f"Script execution error: {str(e)}") from e + + return {"summary": summary, "warnings": warnings} + + +def _create_test_result( + success: bool, + test_results: dict, + execution_summary: dict, + errors: list, + warnings: list, + start_time: float, + save_test_report: bool, + test_report_dir: str, +) -> dict[str, Any]: + """Create the final test result dictionary.""" + # Calculate total execution time + total_execution_time = time.time() - start_time + execution_summary["total_execution_time"] = total_execution_time + + result = { + "success": success, + "test_results": test_results, + "execution_summary": execution_summary, + "errors": errors, + "warnings": warnings, + } + + # Save test report if requested + if save_test_report: + try: + if test_report_dir: + os.makedirs(test_report_dir, exist_ok=True) + else: + test_report_dir = tempfile.gettempdir() + + timestamp = time.strftime("%Y%m%d_%H%M%S") + report_filename = f"pylabrobot_test_report_{timestamp}.json" + report_path = os.path.join(test_report_dir, report_filename) + + with open(report_path, "w") as f: + json.dump(result, f, indent=2) + + result["test_report_path"] = report_path + + except Exception as e: + warnings.append(f"Failed to save test report: {str(e)}") + + return result diff --git a/BioScientist/agent_system/engines/v1_executor_backup/tool/literature.py b/BioScientist/agent_system/engines/v1_executor_backup/tool/literature.py new file mode 100644 index 0000000000000000000000000000000000000000..e4b31b14337be2e72d232b5274f342b36badcb28 --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/tool/literature.py @@ -0,0 +1,403 @@ +import os +import re +import time +from io import BytesIO +from urllib.parse import urljoin + +import PyPDF2 +import requests +from bs4 import BeautifulSoup +from googlesearch import search + + +def fetch_supplementary_info_from_doi(doi: str, output_dir: str = "supplementary_info"): + """Fetches supplementary information for a paper given its DOI and returns a research log. + + Args: + doi: The paper DOI. + output_dir: Directory to save supplementary files. + + Returns: + dict: A dictionary containing a research log and the downloaded file paths. + + """ + research_log = [] + research_log.append(f"Starting process for DOI: {doi}") + + # CrossRef API to resolve DOI to a publisher page + crossref_url = f"https://doi.org/{doi}" + headers = {"User-Agent": "Mozilla/5.0"} + response = requests.get(crossref_url, headers=headers) + + if response.status_code != 200: + log_message = f"Failed to resolve DOI: {doi}. Status Code: {response.status_code}" + research_log.append(log_message) + return {"log": research_log, "files": []} + + publisher_url = response.url + research_log.append(f"Resolved DOI to publisher page: {publisher_url}") + + # Fetch publisher page + response = requests.get(publisher_url, headers=headers) + if response.status_code != 200: + log_message = f"Failed to access publisher page for DOI {doi}." + research_log.append(log_message) + return {"log": research_log, "files": []} + + # Parse page content + soup = BeautifulSoup(response.content, "html.parser") + supplementary_links = [] + + # Look for supplementary materials by keywords or links + for link in soup.find_all("a", href=True): + href = link.get("href") + text = link.get_text().lower() + if "supplementary" in text or "supplemental" in text or "appendix" in text: + full_url = urljoin(publisher_url, href) + supplementary_links.append(full_url) + research_log.append(f"Found supplementary material link: {full_url}") + + if not supplementary_links: + log_message = f"No supplementary materials found for DOI {doi}." + research_log.append(log_message) + return research_log + + # Create output directory + os.makedirs(output_dir, exist_ok=True) + research_log.append(f"Created output directory: {output_dir}") + + # Download supplementary materials + downloaded_files = [] + for link in supplementary_links: + file_name = os.path.join(output_dir, link.split("/")[-1]) + file_response = requests.get(link, headers=headers) + if file_response.status_code == 200: + with open(file_name, "wb") as f: + f.write(file_response.content) + downloaded_files.append(file_name) + research_log.append(f"Downloaded file: {file_name}") + else: + research_log.append(f"Failed to download file from {link}") + + if downloaded_files: + research_log.append(f"Successfully downloaded {len(downloaded_files)} file(s).") + else: + research_log.append(f"No files could be downloaded for DOI {doi}.") + + return "\n".join(research_log) + + +def query_arxiv(query: str, max_papers: int = 10) -> str: + """Query arXiv for papers based on the provided search query. + + Parameters + ---------- + - query (str): The search query string. + - max_papers (int): The maximum number of papers to retrieve (default: 10). + + Returns + ------- + - str: The formatted search results or an error message. + + """ + import arxiv + + try: + client = arxiv.Client() + search = arxiv.Search(query=query, max_results=max_papers, sort_by=arxiv.SortCriterion.Relevance) + results = "\n\n".join([f"Title: {paper.title}\nSummary: {paper.summary}" for paper in client.results(search)]) + return results if results else "No papers found on arXiv." + except Exception as e: + return f"Error querying arXiv: {e}" + + +def query_scholar(query: str) -> str: + """Query Google Scholar for papers based on the provided search query. + + Parameters + ---------- + - query (str): The search query string. + + Returns + ------- + - str: The first search result formatted or an error message. + + """ + from scholarly import ProxyGenerator, scholarly + + # Set up a ProxyGenerator object to use free proxies + # This needs to be done only once per session + pg = ProxyGenerator() + pg.FreeProxies() + scholarly.use_proxy(pg) + try: + search_query = scholarly.search_pubs(query) + result = next(search_query, None) + if result: + return f"Title: {result['bib']['title']}\nYear: {result['bib']['pub_year']}\nVenue: {result['bib']['venue']}\nAbstract: {result['bib']['abstract']}" + else: + return "No results found on Google Scholar." + except Exception as e: + return f"Error querying Google Scholar: {e}" + + +def query_pubmed(query: str, max_papers: int = 10, max_retries: int = 3) -> str: + """Query PubMed for papers based on the provided search query. + + Parameters + ---------- + - query (str): The search query string. + - max_papers (int): The maximum number of papers to retrieve (default: 10). + - max_retries (int): Maximum number of retry attempts with modified queries (default: 3). + + Returns + ------- + - str: The formatted search results or an error message. + + """ + from pymed import PubMed + + try: + pubmed = PubMed(tool="MyTool", email="your-email@example.com") # Update with a valid email address + + # Initial attempt + papers = list(pubmed.query(query, max_results=max_papers)) + + # Retry with modified queries if no results + retries = 0 + while not papers and retries < max_retries: + retries += 1 + # Simplify query with each retry by removing the last word + simplified_query = " ".join(query.split()[:-retries]) if len(query.split()) > retries else query + time.sleep(1) # Add delay between requests + papers = list(pubmed.query(simplified_query, max_results=max_papers)) + + if papers: + results = "\n\n".join( + [f"Title: {paper.title}\nAbstract: {paper.abstract}\nJournal: {paper.journal}" for paper in papers] + ) + return results + else: + return "No papers found on PubMed after multiple query attempts." + except Exception as e: + return f"Error querying PubMed: {e}" + + +def search_google(query: str, num_results: int = 3, language: str = "en") -> list[dict]: + """Search using Google search. + + Args: + query (str): The search query (e.g., "protocol text or seach question") + num_results (int): Number of results to return (default: 10) + language (str): Language code for search results (default: 'en') + pause (float): Pause between searches to avoid rate limiting (default: 2.0 seconds) + + Returns: + List[dict]: List of dictionaries containing search results with title and URL + + """ + try: + results_string = "" + search_query = f"{query}" + + print(f"Searching for {search_query} with {num_results} results and {language} language") + + for res in search(search_query, num_results=num_results, lang=language, advanced=True): + print(f"Found result: {res.title}") + title = res.title + url = res.url + description = res.description + + results_string += f"Title: {title}\nURL: {url}\nDescription: {description}\n\n" + + except Exception as e: + print(f"Error performing search: {str(e)}") + return results_string + + +def advanced_web_search_claude( + query: str, + max_searches: int = 1, + max_retries: int = 3, +) -> tuple[str, list[dict[str, str]], list]: + """ + Initiate an advanced web search by launching a specialized agent to collect relevant information and citations through multiple rounds of web searches for a given query. + Craft the query carefully for the search agent to find the most relevant information. + + Parameters + ---------- + query : str + The search phrase you want Claude to look up. + max_searches : int, optional + Upper-bound on searches Claude may issue inside this request. + max_retries : int, optional + Maximum number of retry attempts with exponential backoff. + + Returns + ------- + full_text : str + A formatted string containing the full text response from Claude and the citations. + """ + import random + + import anthropic + + try: + from biomni.config import default_config + + model = default_config.llm + api_key = default_config.api_key + if not api_key: + api_key = os.getenv("ANTHROPIC_API_KEY") + except ImportError: + model = "claude-4-sonnet-latest" + api_key = os.getenv("ANTHROPIC_API_KEY") + + if "claude" not in model: + raise ValueError("Model must be a Claude model.") + + if not api_key: + raise ValueError("Set your api_key explicitly.") + + client = anthropic.Anthropic(api_key=api_key) + tool_def = { + "type": "web_search_20250305", + "name": "web_search", + "max_uses": max_searches, + } + + delay = random.randint(1, 10) + + for attempt in range(1, max_retries + 1): + try: + response = client.messages.create( + model=model, + max_tokens=4096, + messages=[{"role": "user", "content": query}], + tools=[tool_def], + ) + + paragraphs, citations = [], [] + response.content = response.content + formatted_response = "" + for blk in response.content: + if blk.type == "text": + paragraphs.append(blk.text) + formatted_response += blk.text + + if blk.citations: + for cite in blk.citations: + citations.append({"url": cite.url, "title": cite.title, "cited_text": cite.cited_text}) + formatted_response += f"(Citation: {cite.title} - {cite.url})" + return formatted_response + + except Exception as e: + if attempt < max_retries: + time.sleep(delay) + delay *= 2 + continue + print(f"Error performing web search after {max_retries} attempts: {str(e)}") + return f"Error performing web search after {max_retries} attempts: {str(e)}" + + +def extract_url_content(url: str) -> str: + """Extract the text content of a webpage using requests and BeautifulSoup. + + Args: + url: Webpage URL to extract content from + + Returns: + Text content of the webpage + + """ + response = requests.get(url, headers={"User-Agent": "Mozilla/5.0"}) + + # Check if the response is in text format + if "text/plain" in response.headers.get("Content-Type", "") or "application/json" in response.headers.get( + "Content-Type", "" + ): + return response.text.strip() # Return plain text or JSON response directly + + # If it's HTML, use BeautifulSoup to parse + soup = BeautifulSoup(response.text, "html.parser") + + # Try to find main content first, fallback to body + content = soup.find("main") or soup.find("article") or soup.body + + # Remove unwanted elements + for element in content(["script", "style", "nav", "header", "footer", "aside", "iframe"]): + element.decompose() + + # Extract text with better formatting + paragraphs = content.find_all(["p", "h1", "h2", "h3", "h4", "h5", "h6"]) + cleaned_text = [] + + for p in paragraphs: + text = p.get_text().strip() + if text: # Only add non-empty paragraphs + cleaned_text.append(text) + + return "\n\n".join(cleaned_text) + + +def extract_pdf_content(url: str) -> str: + """Extract the text content of a PDF file given its URL. + + Args: + url: URL of the PDF file to extract text from + + Returns: + The extracted text content from the PDF + + """ + try: + # Check if the URL ends with .pdf + if not url.lower().endswith(".pdf"): + # If not, try to find a PDF link on the page + response = requests.get(url, timeout=30) + if response.status_code == 200: + # Look for PDF links in the HTML content + pdf_links = re.findall(r'href=[\'"]([^\'"]+\.pdf)[\'"]', response.text) + if pdf_links: + # Use the first PDF link found + if not pdf_links[0].startswith("http"): + # Handle relative URLs + base_url = "/".join(url.split("/")[:3]) + url = base_url + pdf_links[0] if pdf_links[0].startswith("/") else base_url + "/" + pdf_links[0] + else: + url = pdf_links[0] + else: + return f"No PDF file found at {url}. Please provide a direct link to a PDF file." + + # Download the PDF + response = requests.get(url, timeout=30) + + # Check if we actually got a PDF file (by checking content type or magic bytes) + content_type = response.headers.get("Content-Type", "").lower() + if "application/pdf" not in content_type and not response.content.startswith(b"%PDF"): + return f"The URL did not return a valid PDF file. Content type: {content_type}" + + pdf_file = BytesIO(response.content) + + # Try with PyPDF2 first + try: + text = "" + pdf_reader = PyPDF2.PdfReader(pdf_file) + for page_num in range(len(pdf_reader.pages)): + page = pdf_reader.pages[page_num] + text += page.extract_text() + "\n\n" + except Exception as e: + print(f"Error extracting text from PDF: {str(e)}") + + # Clean up the text + text = re.sub(r"\s+", " ", text).strip() + + if not text: + return "The PDF file did not contain any extractable text. It may be an image-based PDF requiring OCR." + + return text + + except requests.exceptions.RequestException as e: + return f"Error downloading PDF: {str(e)}" + except Exception as e: + return f"Error extracting text from PDF: {str(e)}" diff --git a/BioScientist/agent_system/engines/v1_executor_backup/tool/microbiology.py b/BioScientist/agent_system/engines/v1_executor_backup/tool/microbiology.py new file mode 100644 index 0000000000000000000000000000000000000000..19043631446f26db4d34c8672bb0f938a31a8e68 --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/tool/microbiology.py @@ -0,0 +1,1618 @@ +def optimize_anaerobic_digestion_process( + waste_characteristics, + operational_parameters, + target_output="methane_yield", + optimization_method="rsm", +): + """Optimize anaerobic digestion process conditions to maximize VFA production or methane yield. + + Parameters + ---------- + waste_characteristics : dict + Dictionary containing waste characteristics such as: + - total_solids (float): Total solids content (%) + - volatile_solids (float): Volatile solids content (%) + - cod (float): Chemical oxygen demand (mg/L) + + operational_parameters : dict + Dictionary containing operational parameters and their ranges: + - hrt (tuple): Hydraulic retention time range in days (min, max) + - olr (tuple): Organic loading rate range in kg VS/(m³·d) (min, max) + - if_ratio (tuple): Inoculum-to-feedstock ratio range (min, max) + - temperature (tuple): Temperature range in °C (min, max) + - ph (tuple): pH range (min, max) + + target_output : str, optional + Target output to maximize, either 'vfa_production' or 'methane_yield'. + Default is 'methane_yield'. + + optimization_method : str, optional + Method used for optimization, either 'rsm' (Response Surface Methodology) or + 'genetic' (Genetic Algorithm). Default is 'rsm'. + + Returns + ------- + str + Research log summarizing the optimization process and results. + + """ + import matplotlib.pyplot as plt + import numpy as np + from matplotlib import cm + from scipy.optimize import differential_evolution, minimize + + # Research log initialization + log = "# Anaerobic Digestion Process Optimization Research Log\n\n" + log += "## Input Parameters\n\n" + log += "### Waste Characteristics\n" + for key, value in waste_characteristics.items(): + log += f"- {key}: {value}\n" + + log += "\n### Operational Parameters Ranges\n" + for key, value in operational_parameters.items(): + log += f"- {key}: {value}\n" + + log += "\n## Optimization Setup\n" + log += f"- Target output: {target_output}\n" + log += f"- Optimization method: {optimization_method}\n\n" + + # Define simplified models for VFA production and methane yield + # These are simplified models based on literature that relate operational parameters to outputs + def vfa_production_model(params): + hrt, olr, if_ratio, temp, ph = params + + # Basic model: Higher VFA at moderate HRT, high OLR, low I/F ratio, mesophilic temp, slightly acidic pH + # This is a simplified model for demonstration purposes + vfa = ( + -0.1 * (hrt - 10) ** 2 # Optimal HRT around 10 days + + 2 * olr # Higher OLR generally increases VFA + + -5 * if_ratio # Lower I/F ratio favors VFA accumulation + + -0.05 * (temp - 35) ** 2 # Optimal around 35°C (mesophilic) + + -10 * (ph - 5.5) ** 2 # Optimal pH around 5.5 for VFA + ) + + # Incorporate waste characteristics effects + vfa *= 0.8 + 0.2 * waste_characteristics["volatile_solids"] / 100 + vfa *= 0.9 + 0.1 * waste_characteristics["cod"] / 10000 + + return -vfa # Negative because we're minimizing + + def methane_yield_model(params): + hrt, olr, if_ratio, temp, ph = params + + # Basic model: Higher methane at longer HRT, moderate OLR, high I/F ratio, mesophilic/thermophilic temp, neutral pH + # This is a simplified model for demonstration purposes + methane = ( + 0.05 * hrt # Longer HRT generally increases methane + + -0.5 * (olr - 3) ** 2 # Optimal OLR around 3 + + 2 * if_ratio # Higher I/F ratio favors methanogenesis + + -0.05 * (temp - 37) ** 2 # Optimal around 37°C (mesophilic) + + -15 * (ph - 7.2) ** 2 # Optimal pH around 7.2 for methane + ) + + # Incorporate waste characteristics effects + methane *= 0.7 + 0.3 * waste_characteristics["volatile_solids"] / 100 + methane *= 0.8 + 0.2 * waste_characteristics["cod"] / 10000 + + return -methane # Negative because we're minimizing + + # Select the appropriate model based on target output + if target_output == "vfa_production": + model = vfa_production_model + else: # methane_yield + model = methane_yield_model + + # Define parameter bounds + bounds = [ + operational_parameters["hrt"], + operational_parameters["olr"], + operational_parameters["if_ratio"], + operational_parameters["temperature"], + operational_parameters["ph"], + ] + + # Perform optimization + log += "## Optimization Process\n\n" + log += "Performing parameter optimization to find optimal operating conditions...\n\n" + + if optimization_method == "rsm": + # Initial guess (middle of each range) + x0 = [(b[0] + b[1]) / 2 for b in bounds] + + # Run optimization + result = minimize(model, x0, bounds=bounds, method="L-BFGS-B") + + optimal_params = result.x + optimal_value = -result.fun # Convert back to positive + + log += f"Optimization converged after {result.nfev} function evaluations.\n" + log += f"Optimization success: {result.success}\n" + log += f"Final optimization message: {result.message}\n\n" + + else: # genetic algorithm + result = differential_evolution(model, bounds) + + optimal_params = result.x + optimal_value = -result.fun # Convert back to positive + + log += f"Genetic algorithm completed after {result.nfev} function evaluations.\n" + log += f"Optimization success: {result.success}\n" + log += f"Final optimization message: {result.message}\n\n" + + # Log optimal parameters + log += "## Optimization Results\n\n" + log += "### Optimal Operating Conditions\n" + param_names = [ + "Hydraulic Retention Time (days)", + "Organic Loading Rate (kg VS/(m³·d))", + "Inoculum-to-Feedstock Ratio", + "Temperature (°C)", + "pH", + ] + + for name, value in zip(param_names, optimal_params, strict=False): + log += f"- {name}: {value:.2f}\n" + + log += "\n### Predicted Performance\n" + log += f"- Predicted {target_output.replace('_', ' ')}: {optimal_value:.2f}\n\n" + + # Generate response surface visualization for the two most important parameters + # For VFA, we'll use HRT and OLR; for methane, we'll use HRT and I/F ratio + log += "## Response Surface Visualization\n\n" + + if target_output == "vfa_production": + param1_idx, param2_idx = 0, 1 # HRT and OLR + param1_name, param2_name = "HRT (days)", "OLR (kg VS/(m³·d))" + else: # methane_yield + param1_idx, param2_idx = 0, 2 # HRT and I/F ratio + param1_name, param2_name = "HRT (days)", "I/F ratio" + + # Create mesh grid for the two selected parameters + param1_range = np.linspace(bounds[param1_idx][0], bounds[param1_idx][1], 20) + param2_range = np.linspace(bounds[param2_idx][0], bounds[param2_idx][1], 20) + P1, P2 = np.meshgrid(param1_range, param2_range) + + # Calculate output values + Z = np.zeros_like(P1) + for i in range(len(param1_range)): + for j in range(len(param2_range)): + params = list(optimal_params) # Start with optimal values for other parameters + params[param1_idx] = param1_range[i] + params[param2_idx] = param2_range[j] + Z[j, i] = -model(params) # Convert back to positive + + # Create 3D plot + fig = plt.figure(figsize=(10, 8)) + ax = fig.add_subplot(111, projection="3d") + surf = ax.plot_surface(P1, P2, Z, cmap=cm.coolwarm, alpha=0.8) + + # Add optimal point + ax.scatter( + optimal_params[param1_idx], + optimal_params[param2_idx], + optimal_value, + color="black", + s=100, + marker="*", + ) + + # Labels + ax.set_xlabel(param1_name) + ax.set_ylabel(param2_name) + ax.set_zlabel(f"{target_output.replace('_', ' ')}") + ax.set_title(f"Response Surface for {target_output.replace('_', ' ')}") + + # Add colorbar + fig.colorbar(surf, ax=ax, shrink=0.5, aspect=5) + + # Save figure + plot_filename = f"ad_optimization_{target_output}_response_surface.png" + plt.savefig(plot_filename) + plt.close() + + log += f"Response surface plot saved as: {plot_filename}\n\n" + + # Sensitivity analysis + log += "## Parameter Sensitivity Analysis\n\n" + + # Calculate sensitivity by varying each parameter slightly + sensitivities = [] + for i, (name, param) in enumerate(zip(param_names, optimal_params, strict=False)): + delta = (bounds[i][1] - bounds[i][0]) * 0.05 # 5% of range + + # Create parameter sets with small changes + params_plus = list(optimal_params) + params_plus[i] += delta + + params_minus = list(optimal_params) + params_minus[i] -= delta + + # Calculate output change + output_plus = -model(params_plus) + output_minus = -model(params_minus) + + # Calculate sensitivity (normalized) + sensitivity = abs(output_plus - output_minus) / (2 * delta) * (param / optimal_value) + sensitivities.append((name, sensitivity)) + + # Sort sensitivities + sensitivities.sort(key=lambda x: x[1], reverse=True) + + # Log sensitivities + log += "Parameters ranked by sensitivity (most to least sensitive):\n" + for name, sensitivity in sensitivities: + log += f"- {name}: {sensitivity:.4f}\n" + + log += "\n## Conclusion\n\n" + log += f"The optimization process identified optimal operating conditions for maximizing {target_output.replace('_', ' ')} " + log += "in anaerobic digestion of the given organic waste. The most sensitive parameters were " + log += f"{sensitivities[0][0]} and {sensitivities[1][0]}.\n\n" + + log += "These results can be used to guide experimental design and process control in anaerobic digestion systems." + + return log + + +def analyze_arsenic_speciation_hplc_icpms(sample_data, sample_name="Unknown Sample", calibration_data=None): + """Analyzes arsenic speciation in liquid samples using HPLC-ICP-MS technique. + + Parameters + ---------- + sample_data : dict + Dictionary containing sample data with keys as sample IDs and values as dictionaries + with retention times (in minutes) as keys and signal intensities as values. + sample_name : str, optional + Name of the sample being analyzed (default: "Unknown Sample") + calibration_data : dict, optional + Dictionary containing calibration standards data with known concentrations for each arsenic species. + If None, default calibration values will be used. + + Returns + ------- + str + A research log summarizing the steps of the analysis and results. + + """ + from datetime import datetime + + import pandas as pd + + # Start research log + log = "# Arsenic Speciation Analysis by HPLC-ICP-MS\n" + log += f"Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n" + log += f"Sample: {sample_name}\n\n" + + # Step 1: Sample preparation + log += "## 1. Sample Preparation\n" + log += "- Filtered sample through 0.45 μm filter\n" + log += "- Diluted sample with mobile phase (if necessary)\n" + log += "- Prepared for injection into HPLC system\n\n" + + # Step 2: HPLC-ICP-MS Analysis + log += "## 2. HPLC-ICP-MS Analysis\n" + log += "- Column: Anion exchange column\n" + log += "- Mobile phase: 20 mM NH4H2PO4 (pH 6.0)\n" + log += "- Flow rate: 1.0 mL/min\n" + log += "- Injection volume: 50 μL\n" + log += "- ICP-MS detection: m/z 75 for arsenic\n\n" + + # Step 3: Chromatographic separation and detection + log += "## 3. Chromatographic Separation and Detection\n" + log += "- Running chromatographic separation\n" + log += "- Monitoring arsenic signal (m/z 75)\n" + log += "- Collecting retention time data for species identification\n\n" + + # Define retention times for arsenic species (in minutes) + arsenic_species = { + "As(III)": 2.8, + "As(V)": 7.5, + "MMAs(III)": 3.9, + "MMAs(V)": 6.2, + "DMAs(III)": 4.7, + "DMAs(V)": 5.3, + } + + # Default calibration factors if not provided + if calibration_data is None: + calibration_data = { + "As(III)": {"factor": 0.85, "limit": 0.1}, + "As(V)": {"factor": 0.92, "limit": 0.1}, + "MMAs(III)": {"factor": 0.78, "limit": 0.2}, + "MMAs(V)": {"factor": 0.88, "limit": 0.15}, + "DMAs(III)": {"factor": 0.81, "limit": 0.2}, + "DMAs(V)": {"factor": 0.90, "limit": 0.15}, + } + + # Step 4: Data analysis and quantification + log += "## 4. Data Analysis and Quantification\n" + + # Process sample data to identify and quantify arsenic species + results = {} + + for sample_id, sample in sample_data.items(): + species_concentrations = {} + + for species_name, expected_rt in arsenic_species.items(): + # Find the closest retention time in the sample data + closest_rt = min(sample.keys(), key=lambda rt: abs(rt - expected_rt)) + + # Check if the retention time is within an acceptable range (±0.3 min) + if abs(closest_rt - expected_rt) <= 0.3: + # Calculate concentration using calibration factor + intensity = sample[closest_rt] + concentration = intensity * calibration_data[species_name]["factor"] + + # Check if concentration is above detection limit + if concentration >= calibration_data[species_name]["limit"]: + species_concentrations[species_name] = concentration + else: + species_concentrations[species_name] = ( + f"<{calibration_data[species_name]['limit']} (Below detection limit)" + ) + else: + species_concentrations[species_name] = "Not detected" + + results[sample_id] = species_concentrations + + # Convert results to DataFrame for easier handling + results_df = pd.DataFrame.from_dict(results, orient="index") + + # Log the detection and quantification process + log += "- Identified arsenic species based on retention times\n" + log += "- Quantified concentrations using calibration standards\n" + log += "- Applied detection limits for each species\n\n" + + # Step 5: Results + log += "## 5. Results\n" + log += "Detected arsenic species and their concentrations (μg/L):\n\n" + + # Create a results file + results_filename = ( + f"arsenic_speciation_results_{sample_name.replace(' ', '_')}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" + ) + results_df.to_csv(results_filename) + + log += f"Results have been saved to: {results_filename}\n\n" + + # Summary of findings + log += "## 6. Summary\n" + + # Identify predominant species + for sample_id, species_data in results.items(): + numeric_concs = {k: v for k, v in species_data.items() if isinstance(v, int | float)} + if numeric_concs: + predominant_species = max(numeric_concs.items(), key=lambda x: x[1]) + log += f"Sample {sample_id}: Predominant arsenic species is {predominant_species[0]} " + log += f"at {predominant_species[1]:.2f} μg/L\n" + else: + log += f"Sample {sample_id}: No arsenic species detected above quantification limits\n" + + return log + + +def count_bacterial_colonies(image_path, dilution_factor=1, plate_area_cm2=65.0, output_dir="./output"): + """Count bacterial colonies from an image of agar plate using computer vision techniques. + + Parameters + ---------- + image_path : str + Path to the image file containing bacterial colonies on agar plate + dilution_factor : float + Dilution factor of the plated sample (default=1) + plate_area_cm2 : float + Area of the agar plate in square centimeters (default=65.0, standard Petri dish) + output_dir : str + Directory to save output images and results (default="./output") + + Returns + ------- + str + Research log summarizing the colony counting process and results + + """ + import os + from datetime import datetime + + import cv2 + import numpy as np + from scipy import ndimage + + # Create output directory if it doesn't exist + os.makedirs(output_dir, exist_ok=True) + + # Load the image + original_image = cv2.imread(image_path) + if original_image is None: + error_message = f"Error: Could not load image from {image_path}. Please check if the file exists and is a valid image format." + return error_message + + # Convert to grayscale + gray = cv2.cvtColor(original_image, cv2.COLOR_BGR2GRAY) + + # Apply Gaussian blur to reduce noise + blurred = cv2.GaussianBlur(gray, (7, 7), 0) + + # Apply threshold to get binary image + _, thresh = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) + + # Perform morphological operations to remove small noise + kernel = np.ones((3, 3), np.uint8) + opening = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=2) + + # Sure background area + sure_bg = cv2.dilate(opening, kernel, iterations=3) + + # Finding sure foreground area + dist_transform = cv2.distanceTransform(opening, cv2.DIST_L2, 5) + _, sure_fg = cv2.threshold(dist_transform, 0.5 * dist_transform.max(), 255, 0) + sure_fg = np.uint8(sure_fg) + + # Finding unknown region + unknown = cv2.subtract(sure_bg, sure_fg) + + # Marker labelling + _, markers = cv2.connectedComponents(sure_fg) + + # Add one to all labels so that background is 1 instead of 0 + markers = markers + 1 + + # Mark the region of unknown with zero + markers[unknown == 255] = 0 + + # Apply watershed + markers = cv2.watershed(original_image, markers) + + # Count colonies (exclude background marker 1) + unique_markers = np.unique(markers) + colony_count = len(unique_markers) - 2 # Subtract background (1) and boundary (-1) + + # Calculate CFU concentration + cfu_per_ml = colony_count * dilution_factor + cfu_per_cm2 = cfu_per_ml / plate_area_cm2 + + # Create output image showing detected colonies + output_image = original_image.copy() + output_image[markers == -1] = [0, 0, 255] # Mark boundaries in red + + # Draw centroids and numbers on colonies + labeled_array = ndimage.label(markers > 1)[0] + for i, region in enumerate(ndimage.find_objects(labeled_array)): + if region is not None: + y_slice, x_slice = region + y = (y_slice.start + y_slice.stop) // 2 + x = (x_slice.start + x_slice.stop) // 2 + cv2.circle(output_image, (x, y), 5, (0, 255, 0), -1) + cv2.putText( + output_image, + str(i + 1), + (x - 10, y - 10), + cv2.FONT_HERSHEY_SIMPLEX, + 0.5, + (0, 255, 0), + 2, + ) + + # Save output image + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + output_filename = f"colony_count_{timestamp}.jpg" + output_path = os.path.join(output_dir, output_filename) + cv2.imwrite(output_path, output_image) + + # Generate research log + log = f""" +Automated Bacterial Colony Counting - Research Log +================================================= +Date and Time: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")} +Input Image: {image_path} +Dilution Factor: {dilution_factor} +Plate Area: {plate_area_cm2} cm² + +Methodology: +1. Loaded and converted image to grayscale +2. Applied Gaussian blur to reduce noise +3. Used Otsu's thresholding to separate colonies from background +4. Performed morphological operations to clean the image +5. Applied watershed algorithm to separate touching colonies +6. Counted unique colony markers + +Results: +- Total Colony Count: {colony_count} CFUs +- Concentration: {cfu_per_ml:.2f} CFU/ml +- Area Density: {cfu_per_cm2:.2f} CFU/cm² + +Output visualization saved as: {output_filename} +""" + + return log + + +def annotate_bacterial_genome( + genome_file_path, + output_dir="annotation_results", + genus="", + species="", + strain="", + prefix="", +): + """Annotate a bacterial genome using Prokka to identify genes, proteins, and functional features. + + Parameters + ---------- + genome_file_path : str + Path to the assembled genome sequence file in FASTA format + output_dir : str, optional + Directory where annotation results will be saved (default: "annotation_results") + genus : str, optional + Genus name for the organism (default: "") + species : str, optional + Species name for the organism (default: "") + strain : str, optional + Strain identifier (default: "") + prefix : str, optional + Prefix for output files (default: "") + + Returns + ------- + str + Research log summarizing the annotation process and results + + """ + import os + import subprocess + import time + + # Create output directory if it doesn't exist + os.makedirs(output_dir, exist_ok=True) + + # Generate a default prefix if not provided + if not prefix: + prefix = f"annotation_{int(time.time())}" + + # Build the Prokka command + prokka_cmd = [ + "prokka", + genome_file_path, + "--outdir", + output_dir, + "--prefix", + prefix, + ] + + # Add organism information if provided + if genus: + prokka_cmd.extend(["--genus", genus]) + if species: + prokka_cmd.extend(["--species", species]) + if strain: + prokka_cmd.extend(["--strain", strain]) + + # Run Prokka + start_time = time.time() + try: + result = subprocess.run( + prokka_cmd, + check=True, + capture_output=True, + text=True, + ) + success = True + prokka_output = result.stdout + except subprocess.CalledProcessError as e: + success = False + prokka_output = e.stderr + except FileNotFoundError: + return "ERROR: Prokka is not installed or not in PATH. Please install Prokka first." + + # Calculate runtime + runtime = time.time() - start_time + + # Check if annotation was successful + if not success: + return f"ERROR: Genome annotation failed.\n\nProkka output:\n{prokka_output}" + + # Parse annotation summary from Prokka output + feature_counts = {} + for line in prokka_output.split("\n"): + if "Found" in line and ":" in line: + feature_type = line.split("Found")[1].split(":")[0].strip() + count = line.split(":")[-1].strip() + feature_counts[feature_type] = count + + # Generate research log + log = f""" +GENOME ANNOTATION RESEARCH LOG + +Input: +- Genome file: {genome_file_path} + +Annotation Process: +- Tool: Prokka +- Runtime: {runtime:.2f} seconds +- Output directory: {output_dir} + +Annotation Results: +""" + + # Add feature counts to log + if feature_counts: + for feature, count in feature_counts.items(): + log += f"- {feature}: {count}\n" + + # List output files + log += "\nOutput Files:\n" + for file in os.listdir(output_dir): + if file.startswith(prefix): + file_path = os.path.join(output_dir, file) + file_size = os.path.getsize(file_path) / 1024 # Size in KB + log += f"- {file} ({file_size:.1f} KB)\n" + + # Add explanation of key files + log += """ +Key Output Files: +- .gff: Annotation in GFF3 format (contains all genomic features) +- .gbk: Annotation in GenBank format +- .faa: Protein sequences in FASTA format +- .ffn: Nucleotide sequences of genes in FASTA format +- .txt: Summary statistics of the annotation +""" + + return log + + +def enumerate_bacterial_cfu_by_serial_dilution( + initial_sample_volume_ml=1.0, + estimated_concentration=1e8, + dilution_factor=10, + num_dilutions=8, + spots_per_dilution=3, + output_file="cfu_enumeration_results.csv", +): + """Quantify bacterial concentration (CFU/mL) using serial dilutions and spot plating. + + Parameters + ---------- + initial_sample_volume_ml : float + Volume of the initial bacterial sample in milliliters + estimated_concentration : float + Estimated concentration of bacteria in the initial sample (CFU/mL) + dilution_factor : int + Factor by which each dilution reduces the concentration (typically 10) + num_dilutions : int + Number of serial dilutions to perform + spots_per_dilution : int + Number of replicate spots to plate for each dilution + output_file : str + Filename to save the CFU enumeration results + + Returns + ------- + str + Research log summarizing the CFU enumeration process + + """ + import numpy as np + import pandas as pd + + # Generate log + log = "# Bacterial CFU Enumeration via Serial Dilutions and Spot Plating\n\n" + + # Step 1: Prepare serial dilutions + log += "## Step 1: Serial Dilution Preparation\n" + log += f"- Initial sample volume: {initial_sample_volume_ml} mL\n" + log += f"- Estimated concentration: {estimated_concentration:.2e} CFU/mL\n" + log += f"- Dilution factor: {dilution_factor}\n" + log += f"- Number of dilutions: {num_dilutions}\n\n" + + # Calculate theoretical concentrations at each dilution + dilution_concentrations = [] + for i in range(num_dilutions + 1): # +1 to include the undiluted sample + conc = estimated_concentration / (dilution_factor**i) + dilution_concentrations.append(conc) + dilution_name = "Undiluted" if i == 0 else f"10^-{i}" + log += f" {dilution_name}: {conc:.2e} CFU/mL\n" + + # Step 2: Spot plating + log += "\n## Step 2: Spot Plating\n" + log += f"- Spots per dilution: {spots_per_dilution}\n" + log += "- Spotting 10 μL from each dilution onto agar plates\n\n" + + # Simulate bacterial growth with some randomness to mimic real-world variation + np.random.seed(42) # For reproducibility + + # Create dataframe to store results + results = [] + + # For each dilution, simulate spotting and counting + for i in range(num_dilutions + 1): + dilution_name = "Undiluted" if i == 0 else f"10^-{i}" + expected_cfu_per_spot = dilution_concentrations[i] * 0.01 # 10 μL = 0.01 mL + + # Simulate multiple spots per dilution + spot_counts = [] + for spot in range(spots_per_dilution): + # Add some randomness to the counts (Poisson distribution for bacterial counts) + if expected_cfu_per_spot > 300: + # Too many to count (TMTC) + count = "TMTC" + spot_counts.append(count) + elif expected_cfu_per_spot < 1: + # Simulate low probability events + count = np.random.poisson(expected_cfu_per_spot) + spot_counts.append(count) + else: + # Normal counting range + count = np.random.poisson(expected_cfu_per_spot) + spot_counts.append(count) + + results.append( + { + "Dilution": dilution_name, + "Dilution_Factor": dilution_factor**i, + "Spot": spot + 1, + "CFU_Count": count, + } + ) + + # Convert results to dataframe + df = pd.DataFrame(results) + + # Step 3: Colony counting and CFU calculation + log += "## Step 3: Colony Counting and CFU Calculation\n\n" + + # Find the first dilution with countable colonies (between 3 and 300 CFU) + countable_dilutions = [] + + for i in range(num_dilutions + 1): + dilution_name = "Undiluted" if i == 0 else f"10^-{i}" + dilution_data = df[df["Dilution"] == dilution_name] + + # Check if counts are numeric (not TMTC) + numeric_counts = [c for c in dilution_data["CFU_Count"] if isinstance(c, int | float)] + + if numeric_counts: + avg_count = sum(numeric_counts) / len(numeric_counts) + if 3 <= avg_count <= 300: + countable_dilutions.append( + { + "Dilution": dilution_name, + "Dilution_Factor": dilution_factor**i, + "Average_CFU": avg_count, + "CFU_per_mL": avg_count * 100 * (dilution_factor**i), # × 100 to convert 10 μL to mL + } + ) + + # Calculate final CFU/mL based on countable dilutions + if countable_dilutions: + countable_df = pd.DataFrame(countable_dilutions) + + # Log the counts for each countable dilution + for _, row in countable_df.iterrows(): + log += f"Dilution {row['Dilution']}: Average CFU per spot = {row['Average_CFU']:.1f}\n" + log += f" Calculated concentration: {row['CFU_per_mL']:.2e} CFU/mL\n\n" + + # Calculate the final CFU/mL as the average of all countable dilutions + final_cfu = countable_df["CFU_per_mL"].mean() + log += "## Final Result\n" + log += f"Original sample concentration: {final_cfu:.2e} CFU/mL\n" + else: + log += "No countable dilutions found. Consider adjusting the dilution series.\n" + final_cfu = None + + # Save results to CSV + df.to_csv(output_file, index=False) + log += f"\nDetailed results saved to: {output_file}\n" + + return log + + +def model_bacterial_growth_dynamics( + initial_population, + growth_rate, + clearance_rate, + niche_size, + simulation_time=24, + time_step=0.1, +): + """Model bacterial population dynamics over time using ordinary differential equations. + + Parameters + ---------- + initial_population : float + Initial bacterial population size (CFU/ml or cells) + growth_rate : float + Bacterial growth rate (per hour) + clearance_rate : float + Rate at which bacteria are cleared from the system (per hour) + niche_size : float + Maximum carrying capacity of the environment (CFU/ml or cells) + simulation_time : float, optional + Total simulation time in hours (default: 24) + time_step : float, optional + Time step for simulation output (default: 0.1) + + Returns + ------- + str + Research log summarizing the bacterial growth dynamics simulation + + """ + import numpy as np + import pandas as pd + from scipy.integrate import solve_ivp + + # Define the ODE system for bacterial growth + def bacterial_dynamics(t, N): + # Logistic growth with clearance + dNdt = growth_rate * N * (1 - N / niche_size) - clearance_rate * N + return dNdt + + # Time points for simulation + t_span = (0, simulation_time) + t_eval = np.arange(0, simulation_time + time_step, time_step) + + # Solve the ODE system + solution = solve_ivp(bacterial_dynamics, t_span, [initial_population], t_eval=t_eval, method="RK45") + + # Extract results + time_points = solution.t + population_size = solution.y[0] + + # Calculate key metrics + max_population = np.max(population_size) + final_population = population_size[-1] + + # Determine if population reached steady state + # (defined as less than 1% change in the last 10% of simulation time) + last_index = int(len(population_size) * 0.9) + population_change = abs(population_size[-1] - population_size[last_index]) / population_size[last_index] + steady_state_reached = population_change < 0.01 + + # Save results to CSV + results_df = pd.DataFrame({"Time (hours)": time_points, "Population Size": population_size}) + + filename = "bacterial_growth_dynamics.csv" + results_df.to_csv(filename, index=False) + + # Generate research log + log = f"""Bacterial Growth Dynamics Simulation Results: + +Initial conditions: +- Starting population: {initial_population:.2e} cells +- Growth rate: {growth_rate:.2f} per hour +- Clearance rate: {clearance_rate:.2f} per hour +- Niche size (carrying capacity): {niche_size:.2e} cells +- Simulation time: {simulation_time} hours + +Results: +- Maximum population reached: {max_population:.2e} cells +- Final population: {final_population:.2e} cells +- Steady state {"reached" if steady_state_reached else "not reached"} + +The complete population dynamics data has been saved to '{filename}'. +""" + + return log + + +def quantify_biofilm_biomass_crystal_violet(od_values, sample_names=None, control_index=0, save_path=None): + """Quantifies biofilm biomass using crystal violet staining assay data. + + Parameters + ---------- + od_values : list or numpy.ndarray + Optical density measurements from crystal violet staining. + Each value represents the absorbance reading for a sample. + sample_names : list, optional + Names of the biofilm samples corresponding to od_values. + If None, samples will be labeled as Sample 1, Sample 2, etc. + control_index : int, optional + Index of the negative control sample in od_values. Default is 0. + save_path : str, optional + Path to save the results. If None, results won't be saved to a file. + + Returns + ------- + str + Research log detailing the quantification process and results. + + """ + import os + from datetime import datetime + + import numpy as np + import pandas as pd + from scipy import stats + + # Initialize research log + log = "## Biofilm Biomass Quantification using Crystal Violet Staining\n" + log += f"Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n" + + # Convert input to numpy array for processing + od_values = np.array(od_values, dtype=float) + + # Generate sample names if not provided + if sample_names is None: + sample_names = [f"Sample {i + 1}" for i in range(len(od_values))] + + log += "### Samples Analyzed:\n" + for i, name in enumerate(sample_names): + log += f"- {name}: OD = {od_values[i]:.4f}\n" + + # Calculate normalized values (subtract control) + control_value = od_values[control_index] + normalized_values = od_values - control_value + + log += "\n### Normalization:\n" + log += f"- Control sample: {sample_names[control_index]} (OD = {control_value:.4f})\n" + log += "- Normalized values (Control subtracted):\n" + + for i, name in enumerate(sample_names): + if i != control_index: + log += f" - {name}: {normalized_values[i]:.4f}\n" + + # Basic statistical analysis + mean_biomass = np.mean(normalized_values[normalized_values > 0]) + std_biomass = np.std(normalized_values[normalized_values > 0]) + + log += "\n### Statistical Analysis:\n" + log += f"- Mean normalized biomass: {mean_biomass:.4f}\n" + log += f"- Standard deviation: {std_biomass:.4f}\n" + + # Perform t-test for samples against control + p_values = [] + log += "\n### Statistical Significance:\n" + + for i, name in enumerate(sample_names): + if i != control_index: + # Using one-sample t-test against 0 (normalized control value) + t_stat, p_val = stats.ttest_1samp([normalized_values[i]], 0) + p_values.append(p_val) + significance = "significant" if p_val < 0.05 else "not significant" + log += f"- {name} vs Control: p-value = {p_val:.4f} ({significance})\n" + + # Create a summary dataframe + results_df = pd.DataFrame( + { + "Sample": sample_names, + "OD_Value": od_values, + "Normalized_Value": normalized_values, + } + ) + + # Save results if path is provided + if save_path: + results_file = os.path.join( + save_path, + f"biofilm_biomass_results_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv", + ) + results_df.to_csv(results_file, index=False) + log += "\n### Results Saved:\n" + log += f"- Data saved to: {results_file}\n" + + log += "\n### Conclusion:\n" + log += "- Crystal violet staining assay successfully quantified biofilm biomass.\n" + log += f"- Samples showed varying levels of biofilm formation with mean biomass of {mean_biomass:.4f} ± {std_biomass:.4f}.\n" + + return log + + +def segment_and_analyze_microbial_cells(image_path, output_dir="./output", min_cell_size=50): + """Perform automated cell segmentation and quantify morphological metrics from fluorescence microscopy images. + + Parameters + ---------- + image_path : str + Path to the fluorescence microscopy image file + output_dir : str, optional + Directory to save output files (default: './output') + min_cell_size : int, optional + Minimum cell size in pixels to filter noise (default: 50) + + Returns + ------- + str + Research log summarizing the segmentation process, metrics calculated, and output file paths + + """ + import os + + import numpy as np + import pandas as pd + from scipy import ndimage + from skimage import color, filters, io, measure, morphology, segmentation + + # Create output directory if it doesn't exist + os.makedirs(output_dir, exist_ok=True) + + # Step 1: Load and preprocess the image + image = io.imread(image_path) + if len(image.shape) > 2: # Convert to grayscale if RGB + image = color.rgb2gray(image) + + # Step 2: Enhance contrast and denoise + image_smooth = filters.gaussian(image, sigma=1) + + # Step 3: Thresholding to separate cells from background + threshold_value = filters.threshold_otsu(image_smooth) + binary_mask = image_smooth > threshold_value + + # Step 4: Clean binary mask (remove small objects and fill holes) + binary_mask = morphology.remove_small_objects(binary_mask, min_size=min_cell_size) + binary_mask = morphology.binary_closing(binary_mask, morphology.disk(2)) + binary_mask = ndimage.binary_fill_holes(binary_mask) + + # Step 5: Watershed segmentation for separating touching cells + distance = ndimage.distance_transform_edt(binary_mask) + local_max = morphology.local_maxima(distance) + markers = measure.label(local_max) + segmented_cells = segmentation.watershed(-distance, markers, mask=binary_mask) + + # Step 6: Measure cell properties + props = measure.regionprops_table( + segmented_cells, + intensity_image=image, + properties=[ + "label", + "area", + "perimeter", + "eccentricity", + "major_axis_length", + "minor_axis_length", + "mean_intensity", + "max_intensity", + ], + ) + + # Calculate circularity (4π × area / perimeter²) + props["circularity"] = 4 * np.pi * props["area"] / (props["perimeter"] ** 2) + + # Step 7: Save results + # Save segmentation image + segmentation_filename = os.path.join(output_dir, "segmented_cells.png") + segmentation_image = color.label2rgb(segmented_cells, image, alpha=0.3, bg_label=0) + io.imsave(segmentation_filename, (segmentation_image * 255).astype(np.uint8)) + + # Save metrics to CSV + metrics_filename = os.path.join(output_dir, "cell_metrics.csv") + metrics_df = pd.DataFrame(props) + metrics_df.to_csv(metrics_filename, index=False) + + # Step 8: Generate summary statistics + cell_count = len(np.unique(segmented_cells)) - 1 # Subtract background + avg_cell_area = np.mean(props["area"]) + avg_circularity = np.mean(props["circularity"]) + + # Create research log + log = f""" +Cell Segmentation and Morphology Analysis Research Log: +------------------------------------------------------ +Image processed: {image_path} +Segmentation method: Otsu thresholding + Watershed + +Results Summary: +- Number of cells detected: {cell_count} +- Average cell area: {avg_cell_area:.2f} pixels² +- Average cell circularity: {avg_circularity:.4f} +- Size range: {np.min(props["area"]):.2f} to {np.max(props["area"]):.2f} pixels² + +Cell morphology metrics have been calculated including: +- Area +- Perimeter +- Eccentricity +- Major/minor axis lengths +- Circularity + +Output files: +- Segmentation image: {segmentation_filename} +- Detailed metrics: {metrics_filename} +""" + + return log + + +def segment_cells_with_deep_learning( + image_path, + model_type="bact_fluor_omni", + diameter=None, + save_dir="segmentation_results", +): + """Perform cell segmentation on fluorescence microscopy images using deep learning. + + Uses pre-trained models from the Cellpose/Omnipose library to identify and segment + individual cells in fluorescence microscopy images. + + Parameters + ---------- + image_path : str + Path to the fluorescence microscopy image file + model_type : str, optional + Name of the pre-trained model to use (default: 'bact_fluor_omni') + Options include: 'bact_fluor_omni', 'cyto', 'nuclei', etc. + diameter : float, optional + Expected diameter of cells in pixels. If None, diameter is automatically estimated. + save_dir : str, optional + Directory to save segmentation results (default: 'segmentation_results') + + Returns + ------- + str + Research log detailing the segmentation process and results + + """ + import os + from datetime import datetime + + import matplotlib.pyplot as plt + import numpy as np + from cellpose import models + from skimage import io + + # Create output directory if it doesn't exist + os.makedirs(save_dir, exist_ok=True) + + # Start research log + log = "# Cell Segmentation Research Log\n" + log += f"Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n" + + # Load the image + log += "## Loading Image\n" + log += f"Image path: {image_path}\n" + + try: + img = io.imread(image_path) + log += f"Image loaded successfully. Shape: {img.shape}\n\n" + except Exception as e: + log += f"Error loading image: {str(e)}\n" + return log + + # Prepare image for model + if len(img.shape) > 2 and img.shape[2] > 1: + # If RGB, convert to grayscale for single channel + img_model = img[:, :, 0] if img.shape[2] >= 3 else img + log += "Using first channel of multi-channel image for segmentation.\n\n" + else: + img_model = img + + # Initialize model + log += "## Initializing Model\n" + log += f"Model type: {model_type}\n" + + try: + model = models.CellposeModel(model_type=model_type, gpu=False) + log += "Model initialized successfully.\n\n" + except Exception as e: + log += f"Error initializing model: {str(e)}\n" + return log + + # Run segmentation + log += "## Performing Segmentation\n" + + try: + channels = [0, 0] # First channel for cell detection, no second channel + + log += "Estimated cell diameter: " + if diameter is None: + log += "Auto-estimating\n" + else: + log += f"{diameter} pixels\n" + + # Adjust the unpacking based on the expected return values + results = model.eval( + img_model, + diameter=diameter, + channels=channels, + flow_threshold=0.4, + do_3D=False, + ) + + # Check the number of returned values + if len(results) == 3: + masks, flows, diams = results # Adjusted for 3 return values + styles = None # If styles are not returned, set to None + elif len(results) == 4: + masks, flows, styles, diams = results # Original unpacking + else: + raise ValueError(f"Unexpected number of return values from model.eval(): {len(results)}") + + if diameter is None: + log += f"Auto-estimated cell diameter: {diams[0]:.2f} pixels\n" + + cell_count = len(np.unique(masks)) - 1 # Subtract 1 for background + log += f"Segmentation complete. Detected {cell_count} cells.\n\n" + except Exception as e: + log += f"Error during segmentation: {str(e)}\n" + return log + + # Save results + log += "## Saving Results\n" + + # Save mask image + mask_file = os.path.join(save_dir, f"masks_{os.path.basename(image_path)}") + io.imsave(mask_file, masks.astype(np.uint16)) + log += f"Cell masks saved to: {mask_file}\n" + + # Create and save outlines image + plt.figure(figsize=(10, 8)) + plt.imshow(img_model, cmap="gray") + + # Generate outlines from masks + from skimage.segmentation import find_boundaries + + find_boundaries(masks, mode="outer") + plt.contour(masks, levels=np.unique(masks), colors="r", linewidths=0.5) + + outline_file = os.path.join(save_dir, f"outlines_{os.path.basename(image_path)}") + plt.axis("off") + plt.savefig(outline_file, bbox_inches="tight", pad_inches=0) + plt.close() + log += f"Cell outlines overlaid on original image saved to: {outline_file}\n\n" + + # Final statistics + log += "## Segmentation Statistics\n" + log += f"Total cells detected: {cell_count}\n" + + # Calculate additional metrics + if cell_count > 0: + cell_areas = [np.sum(masks == i) for i in range(1, cell_count + 1)] + avg_cell_area = np.mean(cell_areas) + std_cell_area = np.std(cell_areas) + log += f"Average cell area: {avg_cell_area:.2f} pixels\n" + log += f"Standard deviation of cell area: {std_cell_area:.2f} pixels\n" + + return log + + +def simulate_generalized_lotka_volterra_dynamics( + initial_abundances, + growth_rates, + interaction_matrix, + time_points, + output_file="glv_simulation_results.csv", +): + """Simulate microbial community dynamics using the Generalized Lotka-Volterra (gLV) model. + + Parameters + ---------- + initial_abundances : numpy.ndarray + Initial abundances of each microbial species (1D array) + growth_rates : numpy.ndarray + Intrinsic growth rates for each microbial species (1D array) + interaction_matrix : numpy.ndarray + Matrix of interaction coefficients where A[i,j] represents the effect of species j on species i (2D array) + time_points : numpy.ndarray + Time points at which to evaluate the model + output_file : str, optional + Filename to save the simulation results (default: "glv_simulation_results.csv") + + Returns + ------- + str + Research log summarizing the simulation process and results + + """ + import numpy as np + import pandas as pd + from scipy.integrate import odeint + + # Check input dimensions + n_species = len(initial_abundances) + if len(growth_rates) != n_species or interaction_matrix.shape != ( + n_species, + n_species, + ): + raise ValueError( + "Dimensions mismatch: growth_rates and interaction_matrix must match initial_abundances dimensions" + ) + + # Define the gLV differential equations + def glv_equations(abundances, t, growth_rates, interaction_matrix): + # Calculate growth and interaction terms for each species + # dx_i/dt = r_i * x_i + x_i * sum(A_ij * x_j) + dx_dt = abundances * (growth_rates + np.dot(interaction_matrix, abundances)) + return dx_dt + + # Integrate the ODE system + simulation_results = odeint( + glv_equations, + initial_abundances, + time_points, + args=(growth_rates, interaction_matrix), + ) + + # Create a DataFrame with the results + columns = [f"Species_{i + 1}" for i in range(n_species)] + results_df = pd.DataFrame(simulation_results, columns=columns) + results_df.insert(0, "Time", time_points) + + # Save results to CSV + results_df.to_csv(output_file, index=False) + + # Generate summary statistics + final_abundances = simulation_results[-1] + dominant_species = np.argmax(final_abundances) + 1 + extinct_species = sum(final_abundances < 1e-6) + + # Create research log + log = f""" +Generalized Lotka-Volterra (gLV) Model Simulation Results: +------------------------------------------------------ +Number of microbial species: {n_species} +Simulation time range: {time_points[0]} to {time_points[-1]} +Number of time points: {len(time_points)} + +Summary of dynamics: +- Initial total abundance: {np.sum(initial_abundances):.4f} +- Final total abundance: {np.sum(final_abundances):.4f} +- Dominant species at end of simulation: Species_{dominant_species} (abundance: {final_abundances[dominant_species - 1]:.4f}) +- Number of species with near-zero abundance (< 1e-6): {extinct_species} + +Simulation results have been saved to: {output_file} +""" + + return log + + +def predict_rna_secondary_structure(rna_sequence, output_prefix="rna_structure"): + """Predict the secondary structure of an RNA molecule using ViennaRNA. + + Parameters + ---------- + rna_sequence : str + The RNA sequence (consisting of A, U, G, C nucleotides) + output_prefix : str, optional + Prefix for output files (default: "rna_structure") + + Returns + ------- + str + A research log summarizing the prediction process and results + + """ + try: + import RNA + except ImportError: + return "ERROR: ViennaRNA Python package not installed. Install with 'pip install ViennaRNA'" + + # Validate input sequence + rna_sequence = rna_sequence.upper().strip() + valid_nucleotides = set("AUGC") + if not all(nucleotide in valid_nucleotides for nucleotide in rna_sequence): + return "ERROR: Invalid RNA sequence. Only A, U, G, C nucleotides are allowed." + + # Predict secondary structure + (structure, mfe) = RNA.fold(rna_sequence) + + # Save the structure to a file + structure_file = f"{output_prefix}_structure.txt" + with open(structure_file, "w") as f: + f.write(f"Sequence: {rna_sequence}\n") + f.write(f"Structure: {structure}\n") + f.write(f"Minimum Free Energy: {mfe} kcal/mol\n") + + # Generate a simple text visualization + viz_file = f"{output_prefix}_visualization.txt" + with open(viz_file, "w") as f: + f.write("Sequence: " + rna_sequence + "\n") + f.write("Structure: " + structure + "\n\n") + + # Add a simple representation of stem-loops + pairs = [] + stack = [] + for i, char in enumerate(structure): + if char == "(": + stack.append(i) + elif char == ")" and stack: + left = stack.pop() + pairs.append((left, i)) + + f.write("Stem-loop structures:\n") + for left, right in sorted(pairs): + f.write(f"Base pair: {rna_sequence[left]}({left + 1})-{rna_sequence[right]}({right + 1})\n") + + # Create research log + log = f""" +RNA Secondary Structure Prediction Log: +--------------------------------------- +1. Received RNA sequence of length {len(rna_sequence)} +2. Applied ViennaRNA RNAfold algorithm for structure prediction +3. Calculated minimum free energy: {mfe} kcal/mol +4. Structure saved to file: {structure_file} +5. Visualization saved to file: {viz_file} + +Summary: +The RNA sequence forms a secondary structure with a minimum free energy of {mfe} kcal/mol. +The structure contains {structure.count("(")} base pairs forming stems and loops. +See {structure_file} and {viz_file} for detailed structure information. +""" + + return log + + +def simulate_microbial_population_dynamics( + initial_populations, + growth_rates, + clearance_rates, + carrying_capacities, + max_time=100, + num_simulations=100, + time_points=100, +): + """Performs stochastic simulation of microbial population dynamics using the Gillespie algorithm. + + Parameters + ---------- + initial_populations : list of int + Initial population sizes for each microbial species + growth_rates : list of float + Per capita growth rates for each species + clearance_rates : list of float + Per capita death/clearance rates for each species + carrying_capacities : list of float + Maximum sustainable population for each species + max_time : float, optional + Maximum simulation time (default: 100) + num_simulations : int, optional + Number of stochastic simulations to run (default: 100) + time_points : int, optional + Number of time points to record for trajectories (default: 100) + + Returns + ------- + str + Research log summarizing the simulation results, including extinction probabilities and timelines + + """ + import numpy as np + from scipy import stats + + # Validate inputs + num_species = len(initial_populations) + if not (len(growth_rates) == len(clearance_rates) == len(carrying_capacities) == num_species): + return "Error: All input lists must have the same length (number of species)" + + # Initialize tracking variables + extinction_counts = np.zeros(num_species) + extinction_times = [[] for _ in range(num_species)] + + # Time points for trajectory recording + time_grid = np.linspace(0, max_time, time_points) + avg_trajectories = np.zeros((num_species, time_points)) + + # Run multiple simulations + for _sim in range(num_simulations): + # Initialize population and time for this simulation + population = np.array(initial_populations, dtype=float) + time = 0.0 + + # For recording trajectories at specific time points + trajectory = np.zeros((num_species, time_points)) + next_time_point_idx = 0 + + # Track which species have gone extinct in this simulation + extinct_in_sim = [False] * num_species + + # Run simulation until max_time is reached or all populations are extinct + while time < max_time and np.any(population > 0): + # Record current state if we've reached the next time point + while next_time_point_idx < time_points and time >= time_grid[next_time_point_idx]: + trajectory[:, next_time_point_idx] = population + next_time_point_idx += 1 + + # Calculate event rates + # Growth rates are adjusted for carrying capacity (logistic growth) + adjusted_growth_rates = [ + growth_rates[i] * population[i] * (1 - population[i] / carrying_capacities[i]) + if population[i] > 0 + else 0 + for i in range(num_species) + ] + death_rates = [clearance_rates[i] * population[i] if population[i] > 0 else 0 for i in range(num_species)] + + # Flatten rates for easier processing + all_rates = adjusted_growth_rates + death_rates + total_rate = sum(all_rates) + + # If no events possible, end simulation + if total_rate == 0: + break + + # Time until next event (exponentially distributed) + dt = np.random.exponential(1.0 / total_rate) + time += dt + + # If we've exceeded max_time, break + if time > max_time: + break + + # Select which event occurs + event_idx = np.random.choice(len(all_rates), p=np.array(all_rates) / total_rate) + + # Apply the event + if event_idx < num_species: # Growth event + population[event_idx] += 1 + else: # Death event + species_idx = event_idx - num_species + population[species_idx] -= 1 + + # Check for new extinctions + if population[species_idx] == 0 and not extinct_in_sim[species_idx]: + extinction_counts[species_idx] += 1 + extinction_times[species_idx].append(time) + extinct_in_sim[species_idx] = True + + # Fill in any remaining time points + while next_time_point_idx < time_points: + trajectory[:, next_time_point_idx] = population + next_time_point_idx += 1 + + # Add this simulation's trajectory to the average + avg_trajectories += trajectory + + # Check for species that never went extinct in this simulation + for i in range(num_species): + if population[i] > 0 and not extinct_in_sim[i]: + # Record as "no extinction" by adding a None to extinction_times + extinction_times[i].append(None) + + # Calculate average trajectories + avg_trajectories /= num_simulations + + # Calculate extinction probabilities and statistics + extinction_probs = extinction_counts / num_simulations + + # Calculate median extinction times (ignoring None values) + median_extinction_times = [] + for times in extinction_times: + valid_times = [t for t in times if t is not None] + if valid_times: + median_extinction_times.append(np.median(valid_times)) + else: + median_extinction_times.append(float("inf")) + + # Generate research log + log = "# Microbial Population Dynamics Simulation Results\n\n" + log += f"Simulations run: {num_simulations}\n" + log += f"Maximum simulation time: {max_time}\n\n" + + log += "## Species Parameters\n" + for i in range(num_species): + log += f"\nSpecies {i + 1}:\n" + log += f" Initial population: {initial_populations[i]}\n" + log += f" Growth rate: {growth_rates[i]}\n" + log += f" Clearance rate: {clearance_rates[i]}\n" + log += f" Carrying capacity: {carrying_capacities[i]}\n" + + log += "\n## Extinction Analysis\n" + for i in range(num_species): + log += f"\nSpecies {i + 1}:\n" + log += f" Extinction probability: {extinction_probs[i]:.2f}\n" + + if extinction_probs[i] > 0: + valid_times = [t for t in extinction_times[i] if t is not None] + if valid_times: + log += f" Median extinction time: {np.median(valid_times):.2f}\n" + log += f" Mean extinction time: {np.mean(valid_times):.2f}\n" + log += f" Standard deviation: {np.std(valid_times):.2f}\n" + + # Calculate confidence intervals if we have enough data + if len(valid_times) >= 10: + ci = stats.t.interval( + 0.95, + len(valid_times) - 1, + loc=np.mean(valid_times), + scale=stats.sem(valid_times), + ) + log += f" 95% CI for extinction time: ({ci[0]:.2f}, {ci[1]:.2f})\n" + else: + log += " No extinctions observed in any simulations\n" + + # Save average trajectories to CSV file + filename = "population_trajectories.csv" + header = "Time," + ",".join([f"Species_{i + 1}" for i in range(num_species)]) + data = np.column_stack((time_grid, avg_trajectories.T)) + np.savetxt(filename, data, delimiter=",", header=header, comments="") + + log += "\n## Population Trajectories\n" + log += f"Average population trajectories saved to '{filename}'\n" + + return log diff --git a/BioScientist/agent_system/engines/v1_executor_backup/tool/molecular_biology.py b/BioScientist/agent_system/engines/v1_executor_backup/tool/molecular_biology.py new file mode 100644 index 0000000000000000000000000000000000000000..fbb3e219bdd6e9a23d038f941e1392344e163e51 --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/tool/molecular_biology.py @@ -0,0 +1,2222 @@ +import os +import subprocess +import tempfile +from collections import namedtuple +from typing import Any + +import pandas as pd +import requests +from Bio import Entrez, Restriction, SeqIO +from Bio.Seq import Seq +from Bio.SeqUtils import MeltingTemp as mt +from bs4 import BeautifulSoup + + +def annotate_open_reading_frames(sequence, min_length, search_reverse=False, filter_subsets=False): + """Find all Open Reading Frames (ORFs) in a DNA sequence using Biopython. + Searches both forward and reverse complement strands. + + Args: + sequence (str): DNA sequence + min_length (int): Minimum length of ORF in nucleotides + search_reverse (bool): Whether to search the reverse complement strand (default: False unless you want to search for reverse ORFs) + filter_nested (bool): Whether to filter out ORFs with same end but later start (default: False unless you want to remove nested ORFs) + + Returns: + dict: Dictionary containing: + - explanation: Explanation of the output fields + - summary_stats: Statistical overview of found ORFs + - orfs: List of all found ORFs, where each ORF contains: + - sequence: Nucleotide sequence of the ORF + - aa_sequence: Translated amino acid sequence + - start: Start position in original sequence (0-based) + - end: End position in original sequence + - strand: '+' for forward strand, '-' for reverse complement + - frame: Reading frame (1,2,3 for forward; -1,-2,-3 for reverse) + + """ + ORF = namedtuple("ORF", ["sequence", "aa_sequence", "start", "end", "strand", "frame"]) + + def find_orfs_in_frame(seq_str, frame, strand): + """Helper function to find ORFs in a specific frame.""" + orfs = [] + seq_length = len(seq_str) + + # Adjust sequence based on frame + offset = frame - 1 # Convert 1-based frame to 0-based index + frame_seq = seq_str[offset:] + + # Find all start codons + start_positions = [] + stop_positions = [] + + for i in range(0, len(frame_seq) - 2, 3): + codon = frame_seq[i : i + 3] + if len(codon) != 3: # Skip incomplete codons + continue + + # Record positions of start and stop codons + if codon == "ATG": + start_positions.append(i) + elif codon in ["TAA", "TAG", "TGA"]: + stop_positions.append(i) + + # Process any active ORFs that end at this stop codon + while start_positions and start_positions[0] < i: + start_pos = start_positions.pop(0) + orf_seq = frame_seq[start_pos : i + 3] + + # Check if ORF meets minimum length requirement + if len(orf_seq) >= min_length: + # Convert positions to original sequence coordinates + orig_start = start_pos + offset + orig_end = i + offset + 3 + + # For reverse strand, convert positions + if strand == "-": + orig_start = seq_length - orig_end + orig_end = seq_length - (start_pos + offset) + + # Create Seq object for translation + orf_bio_seq = Seq(orf_seq) + aa_seq = str(orf_bio_seq.translate(to_stop=True)) + + orfs.append( + ORF( + sequence=orf_seq, + aa_sequence=aa_seq, + start=orig_start, + end=orig_end, + strand=strand, + frame=frame if strand == "+" else -frame, + ) + ) + + return orfs + + # Convert input to string and uppercase + sequence = str(sequence).upper() + seq_obj = Seq(sequence) + + all_orfs = [] + + # Search forward strand + for frame in range(1, 4): + all_orfs.extend(find_orfs_in_frame(sequence, frame, "+")) + + # Search reverse complement strand + if search_reverse: + rev_comp = str(seq_obj.reverse_complement()) + for frame in range(1, 4): + all_orfs.extend(find_orfs_in_frame(rev_comp, frame, "-")) + + if filter_subsets: + # Sort ORFs by length first (longest first) + all_orfs.sort(key=lambda x: len(x.sequence), reverse=True) + + # Filter out ORFs that are contained within other ORFs on the same strand + filtered_orfs = [] + for _i, orf in enumerate(all_orfs): + is_subset = False + for larger_orf in filtered_orfs: + if orf.strand == larger_orf.strand and orf.start >= larger_orf.start and orf.end <= larger_orf.end: + is_subset = True + break + if not is_subset: + filtered_orfs.append(orf) + all_orfs = filtered_orfs + + # Sort ORFs by length (longest first) + all_orfs.sort(key=lambda x: len(x.sequence), reverse=True) + + # Calculate summary statistics + forward_orfs = len([orf for orf in all_orfs if orf.strand == "+"]) + reverse_orfs = len([orf for orf in all_orfs if orf.strand == "-"]) + avg_length = sum(len(orf.sequence) for orf in all_orfs) / len(all_orfs) if all_orfs else 0 + + summary_stats = { + "total_orfs": len(all_orfs), + "forward_orfs": forward_orfs, + "reverse_orfs": reverse_orfs, + "avg_length": round(avg_length, 1), + } + + explanation = ( + "Output fields:\n" + "- summary_stats: Statistical overview of found ORFs\n" + " * total_orfs: Total number of ORFs found\n" + " * forward_orfs: Number of ORFs on forward strand\n" + " * reverse_orfs: Number of ORFs on reverse strand\n" + " * avg_length: Average length of all ORFs in base pairs\n" + "- orfs: List of all found ORFs, where each ORF contains:\n" + " * sequence: Nucleotide sequence of the ORF\n" + " * aa_sequence: Translated amino acid sequence\n" + " * start: Start position in original sequence (0-based)\n" + " * end: End position in original sequence\n" + " * strand: '+' for forward strand, '-' for reverse complement\n" + " * frame: Reading frame (1,2,3 for forward; -1,-2,-3 for reverse)" + ) + + return { + "explanation": explanation, + "summary_stats": summary_stats, + "orfs": all_orfs, + } + + +def annotate_plasmid(sequence: str, is_circular: bool = True) -> dict[str, Any]: + """Annotate a DNA sequence using pLannotate's command-line interface. + + Args: + sequence (str): The DNA sequence to annotate + is_circular (bool): Whether the sequence is circular (default: True) + return_plot (bool): Whether to return the Bokeh plot object (default: False) + + Returns: + Dict: Dictionary containing annotation results or None if annotation fails + + """ + try: + # Create a temporary directory to store input/output files + with tempfile.TemporaryDirectory() as temp_dir: + # Create temporary FASTA file + fasta_path = os.path.join(temp_dir, "input.fasta") + with open(fasta_path, "w") as f: + f.write(f">sequence\n{sequence}\n") + + # Prepare command + cmd = [ + "plannotate", + "batch", + "-i", + fasta_path, + "-o", + temp_dir, + "-f", + "output", + "--csv", # Request CSV output + ] + + # Add linear flag if sequence is not circular + if not is_circular: + cmd.append("-l") + + # Add detailed flag for more comprehensive search + cmd.append("-d") + + # Run plannotate + subprocess.run(cmd, check=True) + + # Read results from CSV + csv_path = os.path.join(temp_dir, "output_pLann.csv") + results = pd.read_csv(csv_path) + + explanation = ( + "Output fields for each annotation:\n" + "- sseqid: Subject sequence identifier (name of the feature)\n" + "- qstart/qend: Start and end positions in query sequence\n" + "- sstart/send: Start and end positions in subject sequence\n" + "- sframe: Reading frame (-1 for reverse strand, 1 for forward)\n" + "- score: Alignment score\n" + "- evalue: Expected value for the alignment\n" + "- qseq: Query sequence\n" + "- length: Length of the alignment\n" + "- slen: Length of the subject sequence\n" + "- pident: Percentage identity\n" + "- qlen: Length of the query sequence\n" + "- db: Database source (e.g., snapgene)\n" + "- Feature: Feature name\n" + "- Description: Detailed description of the feature\n" + "- Type: Feature type (e.g., CDS, promoter, rep_origin)\n" + "- priority: Priority score\n" + "- percmatch: Percentage match\n" + "- abs percmatch: Absolute percentage match\n" + "- pi_permatch: Per-identity match\n" + "- wiggle: Wiggle room for alignment\n" + "- wstart/wend: Wiggle start and end positions\n" + "- kind: Feature kind\n" + "- qstart_dup/qend_dup: Duplicate query positions\n" + "- fragment: Boolean indicating if feature is fragmented" + ) + + return { + "explanation": explanation, + "annotations": results.to_dict("records"), + } + + except Exception as e: + print(f"Error during annotation: {e}") + return None + + +def get_gene_coding_sequence(gene_name: str, organism: str, email: str = None) -> list[dict[str, str]]: + """Retrieves the coding sequence(s) of a specified gene from NCBI Entrez. + + Args: + gene_name: Name of the gene + organism: Name of the organism + email: Email address for NCBI Entrez (recommended) + + Returns: + List of dictionaries containing: + - refseq_id: RefSeq ID of the gene + - sequence: Coding sequence of the gene + + """ + if email: + Entrez.email = email + + def search_gene() -> str: + """Search for gene ID in NCBI database.""" + query = f"{organism}[Organism] AND {gene_name}[Gene]" + with Entrez.esearch(db="gene", term=query, retmax=5) as handle: + record = Entrez.read(handle) + + if not record["IdList"]: + raise ValueError(f"No records found for gene '{gene_name}' in organism '{organism}'") + + return record["IdList"][0] + + def get_refseq_id(gene_id: str) -> str: + """Get RefSeq ID from gene record.""" + with Entrez.efetch(db="gene", id=gene_id, rettype="gb", retmode="xml") as handle: + gene_record = Entrez.read(handle) + + try: + locus = gene_record[0]["Entrezgene_locus"][0] + accession = locus["Gene-commentary_accession"] + version = locus["Gene-commentary_version"] + return f"{accession}.{version}" + except (KeyError, IndexError) as e: + raise RuntimeError(f"Unable to process gene record: {e}") from None + + def get_coding_sequences(refseq_id: str) -> list[dict[str, str]]: + """Extract coding sequences from RefSeq record.""" + with Entrez.efetch(db="nucleotide", id=refseq_id, rettype="gbwithparts", retmode="text") as handle: + seq_record = SeqIO.read(handle, "genbank") + + sequences = [] + for feature in seq_record.features: + if feature.type == "CDS" and feature.qualifiers.get("gene", ["N/A"])[0] == gene_name: + cds_seq = feature.location.extract(seq_record).seq + sequences.append({"refseq_id": refseq_id, "sequence": str(cds_seq)}) + + return sequences + + # Main execution flow + try: + gene_id = search_gene() + refseq_id = get_refseq_id(gene_id) + sequences = get_coding_sequences(refseq_id) + + explanation = ( + "Output fields for each coding sequence:\n" + "- refseq_id: RefSeq identifier for the gene sequence\n" + " * Format: NM_XXXXXX.X for mRNA or NC_XXXXXX.X for genomic DNA\n" + " * Example: NM_000546.5 for human TP53\n" + "- sequence: The actual coding sequence of the gene\n" + " * Contains only exons (introns removed)\n" + " * Starts with ATG (start codon)\n" + " * Ends with a stop codon (TAA, TAG, or TGA)" + ) + + return {"explanation": explanation, "sequences": sequences} + + except Exception as e: + # Log error if needed + raise RuntimeError(f"Failed to retrieve coding sequences: {str(e)}") from e + + +def get_plasmid_sequence(identifier: str, is_addgene: bool = None) -> dict[str, Any] | None: + """Unified function to retrieve plasmid sequences from either Addgene or NCBI. + If is_addgene is True or identifier is numeric, uses Addgene. + Otherwise searches NCBI using the plasmid name. + + Args: + identifier (str): Either an Addgene ID or plasmid name + is_addgene (bool, optional): Force Addgene lookup if True, force NCBI if False. + If None, attempts to auto-detect based on identifier format. + + Returns: + Optional[Dict[str, Any]]: The plasmid sequence and metadata if found, None otherwise + + """ + + def _get_sequence_from_addgene(plasmid_id: str) -> dict[str, Any] | None: + """Helper function to get sequence from Addgene.""" + ADDGENE_BASE_URL = "https://www.addgene.org" + ADDGENE_PLASMID_SEQUENCES_PATH = "/{plasmid_id}/sequences/" + + url = f"{ADDGENE_BASE_URL}{ADDGENE_PLASMID_SEQUENCES_PATH.format(plasmid_id=plasmid_id)}" + response = requests.get(url) + + if response.status_code != 200: + print(f"Failed to retrieve Addgene plasmid {plasmid_id}") + return None + + soup = BeautifulSoup(response.content, "html.parser") + textarea = soup.find("textarea", {"class": "copy-from"}) + + if textarea: + sequence_text = textarea.text.strip() + lines = sequence_text.split("\n") + if lines[0].strip() == "> Addgene NGS Result": + sequence = "".join(lines[1:]).replace(" ", "") + return { + "explanation": ( + "Output fields:\n" + "- source: Source database (Addgene)\n" + "- identifier: Addgene plasmid ID\n" + "- sequence: Complete plasmid sequence" + ), + "source": "Addgene", + "identifier": plasmid_id, + "sequence": sequence, + } + + print(f"No sequence found for Addgene plasmid {plasmid_id}") + return None + + def _get_sequence_from_ncbi(plasmid_name: str) -> dict[str, Any] | None: + """Helper function to get sequence from NCBI.""" + # First get the accession number + query = f"Cloning vector {plasmid_name}" + with Entrez.esearch(db="nuccore", term=query, retmax=1, sort="relevance") as handle: + record = Entrez.read(handle) + + if not record["IdList"]: + print(f"No results found for {plasmid_name} in NCBI.") + return None + + plasmid_id = record["IdList"][0] + + # Get the sequence using the accession + with Entrez.efetch(db="nuccore", id=plasmid_id, rettype="fasta", retmode="text") as handle: + try: + sequence = SeqIO.read(handle, "fasta") + return { + "explanation": ( + "Output fields:\n" + "- source: Source database (NCBI)\n" + "- identifier: NCBI accession number\n" + "- sequence: Complete plasmid sequence" + ), + "source": "NCBI", + "identifier": plasmid_id, + "sequence": str(sequence.seq), + } + except Exception as e: + print(f"Error retrieving sequence for {plasmid_name}: {str(e)}") + return None + + # Auto-detect if is_addgene wasn't specified + if is_addgene is None: + is_addgene = identifier.isdigit() + + if is_addgene: + return _get_sequence_from_addgene(identifier) + else: + return _get_sequence_from_ncbi(identifier) + + +def align_sequences(long_seq: str, short_seqs: str | list[str]) -> list[dict]: + """Align short sequences (primers) to a longer sequence, allowing for one mismatch. + Checks both forward and reverse complement strands. + + Args: + long_seq (str): Target DNA sequence + short_seqs (Union[str, List[str]]): Single primer or list of primers + + Returns: + List[Dict]: List of alignment results for each short sequence, including: + - explanation: Explanation of the output fields + - sequence: the short sequence that was aligned + - alignments: list of dictionaries containing: + - position: 0-based start position in the target sequence + - strand: '+' for forward strand, '-' for reverse complement + - mismatches: list of tuples (position, expected_base, found_base) + for any mismatches + + """ + # Standardize input + long_seq = long_seq.upper() + short_seqs = [short_seqs.upper()] if isinstance(short_seqs, str) else [seq.upper() for seq in short_seqs] + + def reverse_complement(seq): + """Helper function to get reverse complement of a sequence.""" + complement = {"A": "T", "T": "A", "C": "G", "G": "C"} + return "".join(complement.get(base, base) for base in reversed(seq)) + + results = [] + + for short_seq in short_seqs: + alignments = [] + seq_len = len(short_seq) + + # Check both forward and reverse complement orientations + sequences_to_check = [ + (short_seq, "+"), # Forward strand + (reverse_complement(short_seq), "-"), # Reverse complement + ] + + for seq_to_align, strand in sequences_to_check: + # Check each possible position in the long sequence + for i in range(len(long_seq) - seq_len + 1): + window = long_seq[i : i + seq_len] + mismatches = [] + + # Compare sequences base by base + for j in range(seq_len): + if window[j] != seq_to_align[j]: + mismatches.append((j, seq_to_align[j], window[j])) + + # If we have 0 or 1 mismatches, record the alignment + if len(mismatches) <= 1: + alignments.append({"position": i, "strand": strand, "mismatches": mismatches}) + + results.append({"sequence": short_seq, "alignments": alignments}) + + return { + "explanation": ( + "Output fields:\n" + "- sequences: List of alignment results, where each contains:\n" + " * sequence: The short sequence that was aligned\n" + " * alignments: List of all alignment positions found, where each contains:\n" + " - position: 0-based start position in the target sequence\n" + " - strand: '+' for forward strand, '-' for reverse complement\n" + " - mismatches: List of mismatches, each containing:\n" + " * position: Position in the short sequence where mismatch occurs\n" + " * expected: Base expected from short sequence\n" + " * found: Base found in target sequence" + ), + "sequences": results, + } + + +def pcr_simple(sequence: str, forward_primer: str, reverse_primer: str, circular: bool = False) -> dict: + """Simulate PCR amplification with given primers and sequence. + + Args: + sequence (str): Either a sequence string or path to plasmid file + forward_primer (str): Forward primer sequence (5' to 3') + reverse_primer (str): Reverse primer sequence (5' to 3') + circular (bool): Whether the sequence is circular (default: False) + + Returns: + dict: Results of PCR simulation including products and primer binding details + + """ + # First check if primers are valid using existing function + fwd_result = align_sequences(sequence, forward_primer)["sequences"][0]["alignments"] + rev_result = align_sequences(sequence, str(Seq(reverse_primer).reverse_complement()))["sequences"][0]["alignments"] + + if not fwd_result or not rev_result: + return { + "explanation": ( + "Output fields:\n" + "- success: Boolean indicating if any PCR products were found\n" + "- message: Error message if no products found\n" + "- products: Empty list when no products found\n" + "- forward_binding_sites: Number of forward primer binding locations\n" + "- reverse_binding_sites: Number of reverse primer binding locations" + ), + "success": False, + "message": "One or both primers do not align to the sequence.", + "products": [], + "forward_binding_sites": len(fwd_result), + "reverse_binding_sites": len(rev_result), + } + + # Get primer positions + fwd_positions = [align["position"] for align in fwd_result] + rev_positions = [align["position"] for align in rev_result] + sequence_length = len(sequence) + + # Find all possible PCR products + products = [] + + for fwd_pos in fwd_positions: + for rev_pos in rev_positions: + # Calculate product size and sequence + if fwd_pos < rev_pos: + # Standard linear product + size = rev_pos - fwd_pos + len(reverse_primer) + product = sequence[fwd_pos : rev_pos + len(reverse_primer)] + elif circular: + # Handle circular template - wrap around from end to start + size = sequence_length - fwd_pos + rev_pos + len(reverse_primer) + product = sequence[fwd_pos:] + sequence[: rev_pos + len(reverse_primer)] + else: + # Skip invalid combinations for linear sequences + continue + + products.append( + { + "size": size, + "forward_position": fwd_pos, + "reverse_position": rev_pos, + "sequence": product, + "forward_mismatches": fwd_result[fwd_positions.index(fwd_pos)]["mismatches"], + "reverse_mismatches": rev_result[rev_positions.index(rev_pos)]["mismatches"], + } + ) + + if not products: + return { + "explanation": ( + "Output fields:\n" + "- success: Boolean indicating if any PCR products were found\n" + "- message: Error message if no products found\n" + "- products: Empty list when no products found\n" + "- forward_binding_sites: Number of forward primer binding locations\n" + "- reverse_binding_sites: Number of reverse primer binding locations" + ), + "success": False, + "message": "No valid PCR products found with these primers.", + "products": [], + "forward_binding_sites": len(fwd_positions), + "reverse_binding_sites": len(rev_positions), + } + + return { + "explanation": ( + "Output fields:\n" + "- success: Boolean indicating if any PCR products were found\n" + "- products: List of all possible PCR products, where each contains:\n" + " * size: Length of the PCR product in base pairs\n" + " * sequence: The actual DNA sequence of the product\n" + " * forward_position: Starting position of forward primer binding\n" + " * reverse_position: Starting position of reverse primer binding\n" + " * forward_mismatches: List of mismatches in forward primer binding\n" + " * reverse_mismatches: List of mismatches in reverse primer binding\n" + "- forward_binding_sites: Number of locations where forward primer can bind\n" + "- reverse_binding_sites: Number of locations where reverse primer can bind" + ), + "success": True, + "products": products, + "forward_binding_sites": len(fwd_positions), + "reverse_binding_sites": len(rev_positions), + } + + +def digest_sequence(dna_sequence: str, enzyme_names: list[str], is_circular: bool = True) -> dict: + """Simulates restriction enzyme digestion using Biopython's catalyze method and returns the resulting DNA fragments. + + Args: + enzyme_names (str | list): Name of the restriction enzyme or list of enzyme names + dna_sequence (str): Input DNA sequence + is_circular (bool): Whether the DNA sequence is circular (True) or linear (False) + + Returns: + Dict: Dictionary containing the digestion fragments and their properties including positions + + """ + # Convert sequence to Biopython Seq object + seq = Seq(dna_sequence) + seq_length = len(seq) + + # Get all cut positions for all enzymes + all_cut_positions = [] + for enzyme_name in enzyme_names: + enzyme_obj = getattr(Restriction, enzyme_name) + cut_sites = enzyme_obj.search(seq, linear=not is_circular) + all_cut_positions.extend(cut_sites) + + # Sort cut positions and remove duplicates + all_cut_positions = sorted(set(all_cut_positions)) + + # Calculate fragments with positions + fragments = [] + if not all_cut_positions: + # No cuts - return full sequence + fragments.append({"fragment": str(seq), "length": seq_length, "start": 0, "end": seq_length}) + # Handle linear and circular cases + elif is_circular: + for i in range(len(all_cut_positions)): + start = all_cut_positions[i] + end = all_cut_positions[0] + seq_length if i == len(all_cut_positions) - 1 else all_cut_positions[i + 1] + + # Get fragment sequence + if end > seq_length: + fragment_seq = dna_sequence[start:] + dna_sequence[: end - seq_length] + else: + fragment_seq = dna_sequence[start:end] + + fragments.append( + { + "fragment": fragment_seq, + "length": len(fragment_seq), + "start": start, + "end": end if end <= seq_length else end - seq_length, + "is_wrapped": end > seq_length, + } + ) + else: + # First fragment + if all_cut_positions[0] > 0: + fragments.append( + { + "fragment": dna_sequence[: all_cut_positions[0]], + "length": all_cut_positions[0], + "start": 0, + "end": all_cut_positions[0], + } + ) + + # Middle fragments + for i in range(len(all_cut_positions) - 1): + start = all_cut_positions[i] + end = all_cut_positions[i + 1] + fragments.append( + { + "fragment": dna_sequence[start:end], + "length": end - start, + "start": start, + "end": end, + } + ) + + # Last fragment + if all_cut_positions[-1] < seq_length: + fragments.append( + { + "fragment": dna_sequence[all_cut_positions[-1] :], + "length": seq_length - all_cut_positions[-1], + "start": all_cut_positions[-1], + "end": seq_length, + } + ) + + # Sort fragments by length (descending) + fragments.sort(key=lambda x: x["length"], reverse=True) + + results = { + "explanation": ( + "Output fields:\n" + "- sequence_info: Information about the input sequence\n" + " * length: Length of the sequence in base pairs\n" + " * is_circular: Whether sequence is circular or linear\n" + "- digestion_info: Overview of digestion results\n" + " * enzymes_used: List of restriction enzymes used\n" + " * number_of_fragments: Total number of fragments produced\n" + " * cut_positions: List of all cut positions in sequence\n" + "- fragments: List of all fragments produced, where each contains:\n" + " * fragment: The DNA sequence of the fragment\n" + " * length: Length of the fragment in base pairs\n" + " * start: Start position in original sequence (0-based)\n" + " * end: End position in original sequence\n" + " * is_wrapped: (Only for circular) Whether fragment wraps around sequence end" + ), + "sequence_info": { + "length": len(seq), + "is_circular": is_circular, + }, + "digestion_info": { + "enzymes_used": enzyme_names, + "number_of_fragments": len(fragments), + "cut_positions": all_cut_positions, + }, + "fragments": fragments, + } + + return results + + +def find_restriction_sites(dna_sequence: str, enzymes: list[str], is_circular: bool = True) -> dict: + """Identifies restriction enzyme sites in a given DNA sequence for specified enzymes. + + Args: + dna_sequence (str): Complete input DNA sequence + enzymes (List[str]): List of restriction enzyme names to check + is_circular (bool): Whether the DNA sequence is circular (True) or linear (False) + + Returns: + Dict: Dictionary containing all identified restriction sites + + """ + # Convert string to Bio.Seq object + seq = Seq(dna_sequence.upper()) + + # Create Analysis batch with specified enzymes + rb = Restriction.RestrictionBatch(enzymes) + + # Analyze sequence for restriction sites, considering topology + analysis = rb.search(seq, linear=not is_circular) + + results = { + "explanation": ( + "Output fields:\n" + "- sequence_info: Information about the input sequence\n" + " * length: Length of the sequence in base pairs\n" + " * is_circular: Whether sequence is circular or linear\n" + "- restriction_sites: Dictionary of enzymes and their sites, where each contains:\n" + " * recognition_sequence: The DNA sequence the enzyme recognizes\n" + " * cut_positions: Details about where enzyme cuts relative to site\n" + " - 5_prime: Cut position on 5' strand relative to start of recognition site\n" + " - 3_prime: Cut position on 3' strand relative to start of recognition site\n" + " - overhang: Length of overhang produced (negative for 3' overhang)\n" + " - overhang_type: 'sticky' for overhanging cuts, 'blunt' for even cuts\n" + " * sites: List of positions where enzyme cuts in the sequence (0-based)" + ), + "sequence_info": { + "length": len(seq), + "is_circular": is_circular, + }, + "restriction_sites": {}, + } + + for enzyme, positions in analysis.items(): + if positions: # Only include enzymes that have recognition sites + enzyme_info = { + "recognition_sequence": str(enzyme.elucidate()), + "cut_positions": { + "5_prime": enzyme.fst5, # Cut position relative to recognition site + "3_prime": enzyme.fst3, + "overhang": enzyme.ovhg, + "overhang_type": "sticky" if enzyme.ovhg != 0 else "blunt", + }, + "sites": sorted(positions), # Sort positions for better readability + } + + results["restriction_sites"][str(enzyme)] = enzyme_info + else: + # Include enzymes with no sites found + results["restriction_sites"][str(enzyme)] = { + "recognition_sequence": str(enzyme.elucidate()), + "sites": [], + } + return results + + +def find_restriction_enzymes(sequence: str, is_circular: bool = False) -> dict[str, list]: + """Finds common restriction enzyme sites in a DNA sequence. + + Args: + sequence (str): DNA sequence to analyze + is_circular (bool): Whether the sequence is circular (default: False) + + Returns: + Dict[str, list]: Dictionary of enzymes and their cut positions + + """ + # Convert to Bio.Seq and analyze + seq = Seq(sequence.upper()) + analysis = Restriction.CommOnly.search(seq, linear=not is_circular) + + # Keep only enzymes that have sites + sites = {str(enzyme): list(positions) for enzyme, positions in analysis.items() if positions} + + return { + "explanation": ( + "Output fields:\n" + "- enzyme_sites: Dictionary where keys are enzyme names and values are:\n" + " * List of cut positions in the sequence (0-based)\n" + " * Empty list means enzyme has no recognition sites\n" + " * Positions indicate where the enzyme's recognition sequence begins\n" + " * For circular sequences, positions wrap around the sequence end" + ), + "enzyme_sites": sites, + } + + +def find_sequence_mutations(query_sequence, reference_sequence, query_start=1): + """Compare query sequence against reference sequence to identify mutations. + + Args: + query_sequence (str): The sequence being analyzed + reference_sequence (str): The reference sequence to compare against + query_start (int): The start position of the query sequence + + Returns: + list: List of mutations in format RefAA_Position_QueryAA + + Example: + If query_sequence = "ACGT" and reference_sequence = "AGGT", + the output would be ["A1C", "G2T"] indicating mutations at positions 1 and 2. + + """ + if not all([query_sequence, reference_sequence, query_start]): + return { + "explanation": ( + "Output fields:\n" + "- mutations: List of mutations found, where each mutation is formatted as:\n" + " * RefAA_Position_QueryAA\n" + " * RefAA: Original amino acid/base in reference sequence\n" + " * Position: Position where mutation occurs (1-based)\n" + " * QueryAA: New amino acid/base in query sequence\n" + " * Example: 'A123T' means position 123 changed from A to T\n" + "- success: Boolean indicating if comparison was successful" + ), + "mutations": [], + "success": False, + } + + mutations = [] + for i, (query_aa, ref_aa) in enumerate(zip(query_sequence, reference_sequence, strict=False)): + if ref_aa not in (query_aa, "-") and query_aa != "-": # Ignore gaps + position = query_start + i + mutations.append(f"{ref_aa}{position}{query_aa}") + + return { + "explanation": ( + "Output fields:\n" + "- mutations: List of mutations found, where each mutation is formatted as:\n" + " * RefAA_Position_QueryAA\n" + " * RefAA: Original amino acid/base in reference sequence\n" + " * Position: Position where mutation occurs (1-based)\n" + " * QueryAA: New amino acid/base in query sequence\n" + " * Example: 'A123T' means position 123 changed from A to T\n" + "- success: Boolean indicating if comparison was successful" + ), + "mutations": mutations, + "success": True, + } + + +def design_knockout_sgrna( + gene_name: str, + data_lake_path: str, + species: str = "human", + num_guides: int = 1, +) -> dict[str, Any]: + """Design sgRNAs for CRISPR knockout by searching pre-computed sgRNA libraries. + Returns optimized guide RNAs for targeting a specific gene. + + Args: + gene_name (str): Target gene symbol/name (e.g., "EGFR", "TP53") + species (str): Target organism species (default: "human") + num_guides (int): Number of guides to return (default: 1) + + Returns: + Dict: Dictionary containing: + - explanation: Explanation of the output fields + - gene_name: Target gene name + - species: Target species + - guides: List of sgRNA sequences + + """ + DEFAULT_LIBRARIES = { + "human": data_lake_path + "/sgRNA_KO_SP_human.txt", + "mouse": data_lake_path + "/sgRNA_KO_SP_mouse.txt", + } + + # Use fixed library pathAdd commentMore actions + library_path = DEFAULT_LIBRARIES[species.lower()] + + # Check if library file exists + if not os.path.exists(library_path): + error_msg = f"Library file for {species} not found at path: {library_path}" + error_msg += "\n\nThis error typically occurs when the datalake files are not available." + raise FileNotFoundError(error_msg) + + # Load sgRNA library from S3 + try: + df = pd.read_csv(library_path, delimiter="\t") + except Exception as e: + raise RuntimeError(f"Failed to load sgRNA library: {str(e)}") from None + + # Filter for target gene + gene_name = gene_name.upper() # Ensure consistent capitalization + gene_df = df[df["Target Gene Symbol"].str.upper() == gene_name] + + if gene_df.empty: + # Try partial matching if exact match fails + gene_df = df[df["Target Gene Symbol"].str.upper().str.contains(gene_name)] + + if gene_df.empty: + return { + "explanation": "Output contains target gene name, species, and list of sgRNA sequences", + "gene_name": gene_name, + "species": species, + "guides": [], + } + + # Sort by combined rank (default priority) + gene_df = gene_df.sort_values(by=["Combined Rank"]) + + # Get top guides + top_guides = gene_df.head(num_guides) + + # Extract just the sgRNA sequences + guides = top_guides["sgRNA Sequence"].tolist() + + return { + "explanation": "Output contains target gene name, species, and list of sgRNA sequences", + "gene_name": gene_name, + "species": species, + "guides": guides, + } + + +def get_oligo_annealing_protocol() -> dict[str, Any]: + """Return a standard protocol for annealing oligonucleotides without phosphorylation. + + Returns: + Dict: Dictionary containing detailed protocol steps for oligo annealing + + """ + protocol = { + "title": "Oligo Annealing Protocol", + "description": "Standard protocol for annealing complementary oligonucleotides", + "steps": [ + { + "step_number": 1, + "title": "Prepare annealing reaction", + "description": "Mix the following components in a PCR tube:", + "components": [ + {"name": "Forward oligo (100 μM)", "volume": "1 μl"}, + {"name": "Reverse oligo (100 μM)", "volume": "1 μl"}, + {"name": "Nuclease-free water", "volume": "8 μl"}, + ], + "total_volume": "10 μl", + }, + { + "step_number": 2, + "title": "Anneal in thermocycler", + "description": "Run the following program on a thermocycler:", + "program": [ + { + "temperature": "95°C", + "time": "5 minutes", + "description": "Initial denaturation", + }, + { + "temperature": "Ramp down to 25°C", + "rate": "5°C/minute", + "description": "Slow cooling for proper annealing", + }, + ], + }, + { + "step_number": 3, + "title": "Dilute annealed oligos", + "description": "Dilute the annealed oligos 1:200 in nuclease-free water", + "details": "Add 1 μl of annealed oligos to 199 μl of nuclease-free water", + }, + ], + "notes": [ + "Store annealed and diluted oligos at -20°C for long-term storage", + "Diluted oligos can be used directly in ligation reactions", + ], + } + + return protocol + + +def get_golden_gate_assembly_protocol( + num_inserts: int = 1, + enzyme_name: str = None, + vector_length: int = None, + vector_amount_ng: float = 75.0, + insert_lengths: list[int] = None, + is_library_prep: bool = False, +) -> dict[str, Any]: + """Return a customized protocol for Golden Gate assembly based on the number of inserts + and specific DNA sequences. + + Args: + num_inserts (int): Number of inserts to be assembled (default: 1) + enzyme_name (str): Type IIS restriction enzyme to be used + vector_length (int): Length of the destination vector in bp + vector_amount_ng (float): Amount of vector to use in ng (default: 75.0) + insert_lengths (List[int]): List of insert lengths in bp (optional) + is_library_prep (bool): Whether this is for library preparation (default: False) + + Returns: + Dict: Dictionary containing detailed protocol steps for Golden Gate assembly + + """ + # Validate enzyme name + supported_enzymes = ["BsaI", "BsmBI", "BbsI", "Esp3I", "BtgZI", "SapI"] + if enzyme_name not in supported_enzymes: + raise ValueError(f"Unsupported enzyme: {enzyme_name}. Currently supporting: {', '.join(supported_enzymes)}") + + # Determine which protocol to use based on number of inserts + if num_inserts == 1: + if is_library_prep: + thermal_protocol = [ + { + "temperature": "37°C", + "time": "1 hour", + "description": "Cleavage and ligation", + } + ] + else: + thermal_protocol = [ + { + "temperature": "37°C", + "time": "5 minutes", + "description": "Cleavage and ligation", + } + ] + thermal_protocol.append( + { + "temperature": "60°C", + "time": "5 minutes", + "description": "Enzyme inactivation", + } + ) + elif 2 <= num_inserts <= 10: + thermal_protocol = [ + { + "temperature": "Cycle (30x)", + "description": "Cleavage and ligation cycles", + "cycles": [ + { + "temperature": "37°C", + "time": "1 minute", + "description": "Restriction digestion", + }, + { + "temperature": "16°C", + "time": "1 minute", + "description": "Ligation", + }, + ], + }, + { + "temperature": "60°C", + "time": "5 minutes", + "description": "Enzyme inactivation", + }, + ] + else: # 11+ inserts + thermal_protocol = [ + { + "temperature": "Cycle (30x)", + "description": "Cleavage and ligation cycles", + "cycles": [ + { + "temperature": "37°C", + "time": "5 minutes", + "description": "Restriction digestion", + }, + { + "temperature": "16°C", + "time": "5 minutes", + "description": "Ligation", + }, + ], + }, + { + "temperature": "60°C", + "time": "5 minutes", + "description": "Enzyme inactivation", + }, + ] + + # Determine NEB Golden Gate Assembly Mix amount + assembly_mix_volume = "2 μl" if num_inserts > 10 else "1 μl" + + # Calculate molar amounts + # Formula: pmol = (weight in ng) / (base pairs × 650 g/mol per bp) × 10^6 + vector_pmol = vector_amount_ng / (vector_length * 650) * 1000000 + + # Calculate insert amounts + insert_components = [] + + # If lengths are provided, use them for calculations + if insert_lengths: + for i, length in enumerate(insert_lengths): + # Calculate for 2:1 molar ratio (insert:vector) + insert_pmol = 2 * vector_pmol + insert_ng = (insert_pmol * length * 650) / 1000000 + + insert_components.append( + { + "name": f"Insert {i + 1} ({length} bp)", + "amount": f"{insert_ng:.1f} ng ({insert_pmol:.3f} pmol)", + "molar_ratio": "2:1 (insert:vector)", + } + ) + else: + # Generic insert information when no lengths provided + insert_components = [ + { + "name": "Insert DNA (precloned or amplicon)", + "amount": "Variable based on length and concentration", + "note": "Use 2:1 molar ratio (insert:vector) for optimal assembly", + } + ] + + # Create component list for the protocol + reaction_components = [ + { + "name": f"Destination Vector ({vector_length} bp)", + "amount": f"{vector_amount_ng} ng ({vector_pmol:.3f} pmol)", + } + ] + + # Add insert components + for component in insert_components: + reaction_components.append(component) + + # Add remaining reagents + reaction_components.extend( + [ + {"name": "T4 DNA Ligase Buffer (10X)", "volume": "2 μl"}, + { + "name": f"NEB Golden Gate Assembly Mix ({enzyme_name})", + "volume": assembly_mix_volume, + }, + {"name": "Nuclease-free H₂O", "volume": "to 20 μl"}, + ] + ) + + protocol = { + "title": f"Golden Gate Assembly Protocol ({enzyme_name})", + "description": f"Customized protocol for Golden Gate assembly with {enzyme_name} and {num_inserts} insert(s)", + "steps": [ + { + "step_number": 1, + "title": "Prepare assembly reaction", + "description": "Mix the following components in a PCR tube:", + "components": reaction_components, + "total_volume": "20 μl", + }, + { + "step_number": 2, + "title": "Run assembly reaction", + "description": "Run the following program on a thermocycler:", + "program": thermal_protocol, + }, + ], + "notes": [ + f"Destination vector must possess {enzyme_name} restriction sites in the proper orientation", + f"Inserts must possess {enzyme_name} restriction sites at both ends in the proper orientation", + "For amplicon inserts, add 5′ flanking bases (6 recommended) before the restriction sites", + f"Vector amount: {vector_amount_ng} ng = {vector_pmol:.3f} pmol", + "Insert:vector molar ratio is 2:1 for optimal assembly efficiency", + ], + } + + return protocol + + +def get_bacterial_transformation_protocol( + antibiotic: str = "ampicillin", is_repetitive: bool = False +) -> dict[str, Any]: + """Return a standard protocol for bacterial transformation. + + Args: + antibiotic (str): Selection antibiotic (default: "ampicillin") + is_repetitive (bool): Whether the sequence contains repetitive elements (default: False) + + Returns: + Dict: Dictionary containing detailed protocol steps for bacterial transformation + + """ + # Set incubation temperature based on whether sequence is repetitive + incubation_temp = "30°C" if is_repetitive else "37°C" + + protocol = { + "title": "Bacterial Transformation Protocol", + "description": "Standard protocol for transforming DNA into competent E. coli cells", + "steps": [ + { + "step_number": 1, + "title": "Add DNA to competent cells", + "description": "Add 5 μl of DNA to 50 μl of competent E. coli cells", + "note": "Keep cells on ice during this step and handle gently", + }, + { + "step_number": 2, + "title": "Ice incubation", + "description": "Incubate on ice for 30 minutes", + "note": "This allows DNA to associate with the cell membrane", + }, + { + "step_number": 3, + "title": "Heat shock", + "description": "Heat shock at 42°C for 45 seconds", + "note": "Precise timing is critical", + }, + { + "step_number": 4, + "title": "Recovery on ice", + "description": "Return to ice for 2 minutes", + "note": "This step helps cells recover from heat shock", + }, + { + "step_number": 5, + "title": "Add recovery medium", + "description": "Add 950 μl of SOC medium", + "note": "SOC is preferred, but LB can be used if necessary", + }, + { + "step_number": 6, + "title": "Recovery incubation", + "description": f"Incubate at {incubation_temp} for 1 hour with shaking (200-250 rpm)", + "note": "This allows expression of antibiotic resistance genes", + }, + { + "step_number": 7, + "title": "Plate on selective media", + "description": f"Plate 100 μl on LB agar plates containing 100 μg/ml {antibiotic}", + "note": "Spread thoroughly using sterile glass beads or a spreader", + }, + { + "step_number": 8, + "title": "Incubate plates", + "description": f"Incubate overnight (16-18 hours) at {incubation_temp}", + "note": "Invert plates to prevent condensation from dripping onto colonies", + }, + ], + "notes": [ + "Always include positive and negative controls for transformation", + f"For repetitive sequences, the lower temperature ({incubation_temp}) helps maintain sequence integrity" + if is_repetitive + else "Standard incubation at 37°C works well for most plasmids", + f"Use fresh plates containing {antibiotic} for best results", + ], + } + + return protocol + + +def design_primer( + sequence: str, + start_pos: int, + primer_length: int = 20, + min_gc: float = 0.4, + max_gc: float = 0.6, + min_tm: float = 55.0, + max_tm: float = 65.0, + search_window: int = 100, +) -> dict[str, Any] | None: + """Design a single primer within the given sequence window. + + Args: + sequence (str): Target DNA sequence + start_pos (int): Starting position for primer search + primer_length (int): Length of the primer to design (default: 20) + min_gc (float): Minimum GC content (default: 0.4) + max_gc (float): Maximum GC content (default: 0.6) + min_tm (float): Minimum melting temperature in °C (default: 55.0) + max_tm (float): Maximum melting temperature in °C (default: 65.0) + search_window (int): Size of window to search for primers (default: 100) + + Returns: + Optional[Dict[str, Any]]: Dictionary with primer information or None if no suitable primer found + + """ + # Extract candidate region for primer design + primer_region_start = start_pos + primer_region_end = min(start_pos + search_window, len(sequence)) + primer_region = sequence[primer_region_start:primer_region_end] + + # Handle case where region is too small + if len(primer_region) < primer_length: + return None + + # Generate candidate primers + best_primer = None + best_score = float("inf") # Lower is better + + for j in range(len(primer_region) - primer_length + 1): + candidate = primer_region[j : j + primer_length] + + # Calculate GC content + gc_content = (candidate.count("G") + candidate.count("C")) / primer_length + # Skip if GC content is outside acceptable range + if gc_content < min_gc or gc_content > max_gc: + continue + + # Calculate melting temperature + tm = mt.Tm_Wallace(candidate) + + # Skip if Tm is outside acceptable range + if tm < min_tm or tm > max_tm: + continue + + # Score the candidate (distance from ideal GC and Tm) + ideal_gc = (min_gc + max_gc) / 2 + ideal_tm = (min_tm + max_tm) / 2 + gc_penalty = abs(gc_content - ideal_gc) * 100 + tm_penalty = abs(tm - ideal_tm) + + score = gc_penalty + tm_penalty + + if score < best_score: + best_score = score + best_primer = { + "sequence": candidate, + "position": primer_region_start + j, + "gc": gc_content, + "tm": tm, + "score": score, + } + + return best_primer + + +def design_verification_primers( + plasmid_sequence: str, + target_region: tuple[int, int], + existing_primers: list[dict[str, str]] | None = None, + is_circular: bool = True, + coverage_length: int = 800, + primer_length: int = 20, + min_gc: float = 0.4, + max_gc: float = 0.6, + min_tm: float = 55.0, + max_tm: float = 65.0, +) -> dict[str, Any]: + """Design Sanger sequencing primers to verify a specific region in a plasmid. + + First tries to use primers from an existing primer pool. If they cannot fully + cover the region, designs additional primers as needed. + + Args: + plasmid_sequence (str): The complete plasmid sequence + target_region (Tuple[int, int]): Start and end positions to verify (0-based indexing) + existing_primers (Optional[List[Dict[str, str]]]): List of existing primers with + their sequences and optional names. + If None, uses common lab primers. + is_circular (bool): Whether the plasmid is circular (default: True) + coverage_length (int): Typical read length for each primer (default: 800bp) + primer_length (int): Length of newly designed primers (default: 20) + min_gc (float): Minimum GC content for new primers (default: 0.4) + max_gc (float): Maximum GC content for new primers (default: 0.6) + min_tm (float): Minimum melting temperature in °C (default: 55.0) + max_tm (float): Maximum melting temperature in °C (default: 65.0) + + Returns: + Dict: Dictionary containing: + - target_region: The region to be verified + - recommended_primers: List of primers to use (from existing and/or newly designed) + - coverage_map: How the primers cover the target region + + """ + # Use default primers if none are provided + if existing_primers is None: + # Common laboratory primers + existing_primers = [ + {"name": "T7", "sequence": "TAATACGACTCACTATAGGG"}, + {"name": "T7_Term", "sequence": "GCTAGTTATTGCTCAGCGG"}, + {"name": "T3", "sequence": "ATTAACCCTCACTAAAGGGA"}, + {"name": "SP6", "sequence": "GATTTAGGTGACACTATAG"}, + {"name": "U6", "sequence": "GACTATCATATGCTTACCGT"}, + {"name": "BGHR", "sequence": "TAGAAGGCACAGTCGAGG"}, + {"name": "M13F", "sequence": "GTAAAACGACGGCCAG"}, + {"name": "M13R", "sequence": "CAGGAAACAGCTATGAC"}, + {"name": "M13-40FOR", "sequence": "GTTTTCCCAGTCACGAC"}, + {"name": "M13-48REV", "sequence": "CGGATAACAATTTCACACAG"}, + {"name": "CMV-Forward", "sequence": "CGCAAATGGGCGGTAGGCGTG"}, + {"name": "5GEX", "sequence": "GGGCTGGCAAGCCACGTTTGGTG"}, + {"name": "3GEX", "sequence": "CCGGGAGCTGCATGTGTCAGAGG"}, + {"name": "pFastBacF", "sequence": "GGATTATTCATACCGTCCCA"}, + {"name": "pFastBacR", "sequence": "CAAATGTGGTATGGCTGATT"}, + {"name": "pBAD_Forward", "sequence": "ATGCCATAGCATTTTTATCC"}, + {"name": "pBAD_Reverse", "sequence": "GATTTAATCTGTATCAGG"}, + {"name": "EGFP-C-For", "sequence": "CATGGTCCTGCTGGAGTTCGTG"}, + {"name": "EGFP-C-REV", "sequence": "GTTCAGGGGGAGGTGTG"}, + {"name": "EGFP-N", "sequence": "CGTCGCCGTCCAGCTCGACCA"}, + {"name": "SV40pA-R", "sequence": "GAAATTTGTGATGCTATTGC"}, + {"name": "ATTB1", "sequence": "GTTTGTACAAAAAAGCAGGC"}, + {"name": "ATTB2", "sequence": "CCACTTTGTACAAGAAAGCTGGGT"}, + {"name": "ATTL1", "sequence": "CGCGTTAACGCTAGCATGGATCTC"}, + {"name": "ATTL2", "sequence": "CATCAGAGATTTTGAGACAC"}, + {"name": "ITS1", "sequence": "TCCGTAGGTGAACCTGCGG"}, + {"name": "ITS4", "sequence": "TCCTCCGCTTATTGATATGC"}, + {"name": "PJET1-2F", "sequence": "CGACTCACTATAGGGAGAGCGGC"}, + {"name": "PJET1-2R", "sequence": "AAGAACATCGATTTTCCATGGCAG"}, + ] + + # Helper functions for region coverage calculations + def is_position_covered(pos, covered_regions): + """Check if a position is covered by any region.""" + return any(region["start"] <= pos <= region["end"] for region in covered_regions) + + def is_region_fully_covered(covered_regions, start, end): + """Check if the target region is fully covered.""" + # Merge overlapping regions first + merged = merge_overlapping_regions(covered_regions) + + # Check if merged regions cover the entire target + return all(any(r["start"] <= pos <= r["end"] for r in merged) for pos in range(start, end + 1)) + + def merge_overlapping_regions(regions): + """Merge overlapping regions in the list.""" + if not regions: + return [] + + # Sort by start position + sorted_regions = sorted(regions, key=lambda x: x["start"]) + + # Merge overlapping + merged = [sorted_regions[0]] + for region in sorted_regions[1:]: + prev = merged[-1] + # If regions overlap or are adjacent + if region["start"] <= prev["end"] + 1: + # Extend previous region + prev["end"] = max(prev["end"], region["end"]) + else: + # Add as new region + merged.append(region) + + return merged + + # Clean and prepare the plasmid sequence + plasmid_sequence = plasmid_sequence.upper() + + # Handle target region + start, end = target_region + region_length = end - start + 1 + if region_length <= 0: + raise ValueError("Target region end must be greater than start") + + # Prepare result containers + recommended_primers = [] + coverage_map = [] + + # If plasmid is circular, we may need to handle wrapping + if is_circular and end >= len(plasmid_sequence): + # Adjust for circular plasmid wrapping + effective_sequence = plasmid_sequence + plasmid_sequence[: end - len(plasmid_sequence) + coverage_length] + else: + effective_sequence = plasmid_sequence + + # Step 1: Try to use existing primers if provided + used_existing_primers = False + + if existing_primers: + # Create a structured representation of existing primers + primer_pool = [] + for i, primer in enumerate(existing_primers): + if isinstance(primer, str): + primer_info = {"name": f"Existing_{i + 1}", "sequence": primer} + else: + primer_info = primer.copy() + if "name" not in primer_info: + primer_info["name"] = f"Existing_{i + 1}" + + primer_pool.append(primer_info) + + # Find alignments for all existing primers + alignment_results = align_sequences(effective_sequence, [p["sequence"] for p in primer_pool]) + + # Create list of all potential primer matches with their coverage + potential_primers = [] + + for i, result in enumerate(alignment_results.get("sequences", [])): + primer_info = primer_pool[i] + primer_seq = primer_info["sequence"] + + for alignment in result.get("alignments", []): + position = alignment["position"] + strand = alignment["strand"] + + # Calculate coverage range based on primer position and orientation + if strand == "+": + coverage_start = position + coverage_end = position + coverage_length + else: # Reverse primer + coverage_end = position + len(primer_seq) + coverage_start = coverage_end - coverage_length + + # Check if this primer covers any part of the target region + if coverage_start <= end and coverage_end >= start: + # Calculate the coverage within the target region + covered_start = max(start, coverage_start) + covered_end = min(end, coverage_end) + + # Add to potential primers list + potential_primers.append( + { + "name": primer_info["name"], + "sequence": primer_seq, + "position": position, + "strand": strand, + "source": "existing", + "covers": [covered_start, covered_end], + "coverage_length": covered_end - covered_start + 1, + } + ) + + # Sort potential primers by coverage length (descending) + potential_primers.sort(key=lambda x: x["coverage_length"], reverse=True) + + # Select primers using a greedy approach to minimize the number used + # while achieving maximum coverage + if potential_primers: + # Initialize covered regions + covered_regions = [] + selected_primers = [] + + # Keep selecting primers until we've covered everything or run out of primers + while potential_primers and not is_region_fully_covered(covered_regions, start, end): + # Take the first remaining primer (with most coverage) + best_primer = potential_primers.pop(0) + + # Check if this primer adds any new coverage + new_covered_start, new_covered_end = best_primer["covers"] + adds_new_coverage = False + + for s in range(new_covered_start, new_covered_end + 1): + if not is_position_covered(s, covered_regions): + adds_new_coverage = True + break + + if adds_new_coverage: + # Add primer to selected list + selected_primers.append(best_primer) + + # Update covered regions + covered_regions.append({"start": new_covered_start, "end": new_covered_end}) + + # Merge overlapping regions + covered_regions = merge_overlapping_regions(covered_regions) + + # Update recommended primers and coverage map + for primer in selected_primers: + recommended_primers.append(primer) + coverage_map.append( + { + "primer": primer["name"], + "start": primer["covers"][0], + "end": primer["covers"][1], + "length": primer["covers"][1] - primer["covers"][0] + 1, + } + ) + + used_existing_primers = True + + # Step 2: Identify uncovered regions for new primer design + if not used_existing_primers: + # No existing primers used, the entire region is uncovered + uncovered_regions = [{"start": start, "end": end}] + else: + # Sort and merge coverage map + covered_regions = [{"start": cm["start"], "end": cm["end"]} for cm in coverage_map] + covered_regions = merge_overlapping_regions(covered_regions) + + # Find gaps + uncovered_regions = [] + current_pos = start + + for region in covered_regions: + if current_pos < region["start"]: + uncovered_regions.append({"start": current_pos, "end": region["start"] - 1}) + current_pos = max(current_pos, region["end"] + 1) + + if current_pos <= end: + uncovered_regions.append({"start": current_pos, "end": end}) + + # Step 3: Design new primers for uncovered regions + for region in uncovered_regions: + gap_start = region["start"] + gap_end = region["end"] + gap_length = gap_end - gap_start + 1 + + # Determine how many primers we need for this gap + num_primers_needed = max( + 1, + (gap_length // (coverage_length // 2)) + (1 if gap_length % (coverage_length // 2) > 0 else 0), + ) + + # Calculate positions for even distribution of primers + positions = [] + if num_primers_needed == 1: + positions.append(max(0, gap_start - 100)) # Start primer a bit before the gap + else: + interval = gap_length / (num_primers_needed - 0.5) # Distribute primers evenly + for i in range(num_primers_needed): + pos = max(0, int(gap_start - 100 + i * interval)) + positions.append(min(pos, len(effective_sequence) - primer_length)) + + # Design primers for each position + for pos in positions: + # Design a new primer + new_primer = design_primer( + effective_sequence, + pos, + primer_length=primer_length, + min_gc=min_gc, + max_gc=max_gc, + min_tm=min_tm, + max_tm=max_tm, + ) + + # If we found a suitable primer + if new_primer: + primer_name = f"New_primer_{len(recommended_primers) + 1}" + + # Calculate coverage + coverage_start = new_primer["position"] + coverage_end = coverage_start + coverage_length + + # Adjust to target region boundaries + covered_start = max(start, coverage_start) + covered_end = min(end, coverage_end) + + # Add to recommended primers + recommended_primers.append( + { + "name": primer_name, + "sequence": new_primer["sequence"], + "position": new_primer["position"], + "strand": "+", # Forward strand primer + "source": "newly_designed", + "gc": new_primer["gc"], + "tm": new_primer["tm"], + "covers": [covered_start, covered_end], + } + ) + + # Add to coverage map + coverage_map.append( + { + "primer": primer_name, + "start": covered_start, + "end": covered_end, + "length": covered_end - covered_start + 1, + } + ) + + # Recalculate coverage to account for new primers + coverage_map.sort(key=lambda x: x["start"]) + + # Check if the target region is fully covered + covered_regions = [{"start": cm["start"], "end": cm["end"]} for cm in coverage_map] + is_fully_covered = is_region_fully_covered(covered_regions, start, end) + + # Prepare the final result + result = { + "target_region": {"start": start, "end": end, "length": region_length}, + "recommended_primers": recommended_primers, + "coverage_map": coverage_map, + "is_fully_covered": is_fully_covered, + } + + if not is_fully_covered: + result["warning"] = "The target region may not be fully covered. Consider manually reviewing the coverage map." + + return result + + +def design_golden_gate_oligos( + backbone_sequence: str, + insert_sequence: str, + enzyme_name: str, + is_circular: bool = True, +) -> dict[str, Any]: + """Design oligos for Golden Gate assembly by identifying backbone overhangs + and creating matching insert oligos. + + Args: + backbone_sequence (str): The plasmid/backbone sequence + insert_sequence (str): Sequence to be inserted + enzyme_name (str): Type IIS restriction enzyme to be used + is_circular (bool): Whether the backbone is circular (default: True) + + Returns: + Dict: Dictionary containing overhang information and designed oligos + + """ + # Dictionary of enzyme properties + TYPE_IIS_PROPERTIES = { + "BsaI": {"recognition_site": "GGTCTC", "offset_fwd": 1, "offset_rev": 5}, + "BsmBI": {"recognition_site": "CGTCTC", "offset_fwd": 1, "offset_rev": 5}, + "BbsI": {"recognition_site": "GAAGAC", "offset_fwd": 2, "offset_rev": 6}, + "Esp3I": {"recognition_site": "CGTCTC", "offset_fwd": 1, "offset_rev": 5}, + "BtgZI": {"recognition_site": "GCGATG", "offset_fwd": 10, "offset_rev": 14}, + "SapI": {"recognition_site": "GCTCTTC", "offset_fwd": 1, "offset_rev": 4}, + } + + if enzyme_name not in TYPE_IIS_PROPERTIES: + supported = ", ".join(TYPE_IIS_PROPERTIES.keys()) + return { + "success": False, + "message": f"Unsupported enzyme: {enzyme_name}. Currently supporting: {supported}", + } + + # Clean input sequences + backbone_sequence = "".join(c for c in backbone_sequence.upper() if c in "ATGC") + insert_sequence = "".join(c for c in insert_sequence.upper() if c in "ATGC") + + # Get enzyme properties + enzyme_props = TYPE_IIS_PROPERTIES[enzyme_name] + recognition_site = enzyme_props["recognition_site"] + offset_fwd = enzyme_props["offset_fwd"] + offset_rev = enzyme_props["offset_rev"] + + # Reverse complement function + def reverse_complement(seq): + """Get the reverse complement of a DNA sequence.""" + complement = {"A": "T", "T": "A", "G": "C", "C": "G"} + return "".join(complement.get(base, "N") for base in reversed(seq)) + + # Step 1: Find all restriction sites in the backbone + restriction_sites = [] + for i in range(len(backbone_sequence)): + # Check if this position starts a recognition site + if is_circular and i + len(recognition_site) > len(backbone_sequence): + site_seq = backbone_sequence[i:] + backbone_sequence[: i + len(recognition_site) - len(backbone_sequence)] + elif i + len(recognition_site) <= len(backbone_sequence): + site_seq = backbone_sequence[i : i + len(recognition_site)] + else: + continue + + # Check forward strand recognition site + if site_seq == recognition_site: + restriction_sites.append({"position": i, "strand": "forward"}) + + # Check reverse complement recognition site + rev_comp_site = reverse_complement(recognition_site) + if site_seq == rev_comp_site: + restriction_sites.append({"position": i, "strand": "reverse"}) + + if len(restriction_sites) < 2: + return { + "success": False, + "message": f"Need at least 2 {enzyme_name} recognition sites in the backbone for Golden Gate assembly.", + } + + # Step 2: Determine the cut positions and resulting overhangs + cut_sites = [] + + for site in restriction_sites: + pos = site["position"] + + if site["strand"] == "forward": + # Calculate cut positions for forward strand site + cut_fwd = (pos + len(recognition_site) + offset_fwd) % len(backbone_sequence) + cut_rev = (pos + len(recognition_site) + offset_rev) % len(backbone_sequence) + else: # reverse strand + # For reverse sites, cuts happen on the other side of the recognition site + cut_rev = (pos - offset_fwd) % len(backbone_sequence) + cut_fwd = (pos - offset_rev) % len(backbone_sequence) + + if cut_rev < 0: + cut_rev += len(backbone_sequence) + if cut_fwd < 0: + cut_fwd += len(backbone_sequence) + + # Extract overhang sequence + if cut_fwd < cut_rev: + overhang = backbone_sequence[cut_fwd:cut_rev] + else: # Handle wrapping for circular sequences + overhang = backbone_sequence[cut_fwd:] + backbone_sequence[:cut_rev] + + cut_sites.append( + { + "site_position": pos, + "strand": site["strand"], + "cut_fwd": cut_fwd, + "cut_rev": cut_rev, + "overhang": overhang, + } + ) + + # Step 3: For Golden Gate, identify the two overhangs that will flank the insert + # This is simplified - in practice, you'd want to identify which cut sites + # correspond to the destination for your insert + + # For demonstration, let's use the first two cut sites + # In a real application, we would need a more sophisticated algorithm to identify + # which sites are relevant to the insertion + + # We'll assume the first overhang goes at the 5' end of the insert + # and the second overhang (reverse complemented) goes at the 3' end + upstream_overhang = cut_sites[0]["overhang"] + downstream_overhang = cut_sites[1]["overhang"] + + # Step 4: Design simple oligos with matching overhangs + # For forward oligo, add the upstream overhang + fw_oligo = upstream_overhang + insert_sequence + + # For reverse oligo, use reverse complement of insert + reverse complement of downstream overhang + rev_oligo = reverse_complement(insert_sequence + reverse_complement(downstream_overhang)) + + return { + "success": True, + "overhangs": {"upstream": upstream_overhang, "downstream": downstream_overhang}, + "oligos": { + "forward": fw_oligo, + "reverse": rev_oligo, + "notes": [ + f"Forward oligo: Add {upstream_overhang} to 5' end of your insert", + f"Reverse oligo: Add {reverse_complement(downstream_overhang)} to 5' end of reverse complement of your insert", + ], + }, + "cut_sites": [{"position": site["site_position"], "overhang": site["overhang"]} for site in cut_sites], + "assembly_notes": f"Found {len(restriction_sites)} {enzyme_name} sites. Using overhangs {upstream_overhang} and {downstream_overhang} for assembly.", + } + + +def golden_gate_assembly( + backbone_sequence: str, + enzyme_name: str, + fragments: list[dict[str, str]], + is_circular: bool = True, +) -> dict[str, Any]: + """Simulate Golden Gate assembly to predict final construct sequences. + + Args: + backbone_sequence (str): Complete backbone sequence + enzyme_name (str): Type IIS restriction enzyme to be used (e.g., "BsmBI", "BsaI") + fragments (List[Dict[str, str]]): List of fragments to insert, containing one of: + - name + fwd_oligo + rev_oligo: Oligo pair with matching overhangs + - name + sequence: Double-stranded DNA fragment containing restriction sites + is_circular (bool): Whether the backbone is circular (default: True) + + Returns: + Dict: Dictionary containing: + - success: Boolean indicating if assembly was successful + - assembled_sequence: The final assembled sequence + - message: Error message if assembly failed + + """ + # Dictionary of enzyme properties + TYPE_IIS_PROPERTIES = { + "BsaI": {"recognition_site": "GGTCTC", "offset_fwd": 1, "offset_rev": 5}, + "BsmBI": {"recognition_site": "CGTCTC", "offset_fwd": 1, "offset_rev": 5}, + "BbsI": {"recognition_site": "GAAGAC", "offset_fwd": 2, "offset_rev": 6}, + "Esp3I": {"recognition_site": "CGTCTC", "offset_fwd": 1, "offset_rev": 5}, + "BtgZI": {"recognition_site": "GCGATG", "offset_fwd": 10, "offset_rev": 14}, + "SapI": {"recognition_site": "GCTCTTC", "offset_fwd": 1, "offset_rev": 4}, + } + + if enzyme_name not in TYPE_IIS_PROPERTIES: + supported = ", ".join(TYPE_IIS_PROPERTIES.keys()) + return { + "success": False, + "message": f"Unsupported enzyme: {enzyme_name}. Currently supporting: {supported}", + "assembled_sequence": None, + } + + # Get enzyme properties + enzyme_props = TYPE_IIS_PROPERTIES[enzyme_name] + recognition_site = enzyme_props["recognition_site"] + offset_fwd = enzyme_props["offset_fwd"] + offset_rev = enzyme_props["offset_rev"] + + # Prepare reverse complement function + def reverse_complement(seq): + """Get the reverse complement of a DNA sequence.""" + complement = {"A": "T", "T": "A", "G": "C", "C": "G"} + return "".join(complement.get(base, "N") for base in reversed(seq)) + + # Standardize and clean sequences + backbone_sequence = backbone_sequence.upper() + + # Process fragments to ensure they all have fwd_oligo and rev_oligo + processed_fragments = [] + + for idx, fragment in enumerate(fragments): + fragment_name = fragment.get("name", f"fragment_{idx + 1}") + + # Check if this is a double-stranded fragment (contains 'sequence' but not oligos) + if "sequence" in fragment and not ("fwd_oligo" in fragment and "rev_oligo" in fragment): + # Process the double-stranded fragment to extract oligos + ds_sequence = fragment["sequence"].upper() + + # Find restriction sites in the fragment + fwd_sites = [] + rev_sites = [] + rev_comp_site = reverse_complement(recognition_site) + + # Find forward sites + for i in range(len(ds_sequence) - len(recognition_site) + 1): + site_seq = ds_sequence[i : i + len(recognition_site)] + if site_seq == recognition_site: + fwd_sites.append(i) + elif site_seq == rev_comp_site: + rev_sites.append(i) + + # Need exactly two sites for Golden Gate (one in each direction) + if len(fwd_sites) == 0 or len(rev_sites) == 0: + return { + "success": False, + "message": f"Fragment '{fragment_name}' must contain at least one {enzyme_name} site in each orientation", + "assembled_sequence": None, + } + + # Find the first forward and last reverse site + fwd_site = fwd_sites[0] + rev_site = rev_sites[-1] + + # Calculate cut positions + fwd_cut = fwd_site + len(recognition_site) + offset_rev + # Fix: For reverse sites, cut happens offset_rev bases BEFORE (not after) the site + rev_cut = rev_site - offset_rev + + # If the forward cut is after the reverse cut, the fragment is invalid + if fwd_cut >= rev_cut: + return { + "success": False, + "message": f"Invalid restriction sites in fragment '{fragment_name}': sites must be oriented to excise the insert", + "assembled_sequence": None, + } + + # Extract the insert sequence (between the cuts) + insert_seq = ds_sequence[fwd_cut:rev_cut] + print(insert_seq) + + # Extract the overhangs (4 bases for most Type IIS enzymes) + fwd_overhang = ds_sequence[fwd_cut - 4 : fwd_cut] + rev_overhang_rc = ds_sequence[rev_cut : rev_cut + 4] + rev_overhang = reverse_complement(rev_overhang_rc) + + # Create equivalent oligos for assembly + fwd_oligo = fwd_overhang + insert_seq + rev_oligo = rev_overhang + reverse_complement(insert_seq) + + processed_fragments.append( + { + "name": fragment_name, + "fwd_oligo": fwd_oligo, + "rev_oligo": rev_oligo, + "original_sequence": ds_sequence, + } + ) + + # Handle traditional oligo format + elif "fwd_oligo" in fragment and "rev_oligo" in fragment: + processed_fragments.append( + { + "name": fragment_name, + "fwd_oligo": fragment["fwd_oligo"], + "rev_oligo": fragment["rev_oligo"], + } + ) + else: + return { + "success": False, + "message": f"Fragment '{fragment_name}' must contain either 'sequence' or both 'fwd_oligo' and 'rev_oligo'", + "assembled_sequence": None, + } + print(processed_fragments) + + # Step 1: Find all restriction sites in the backbone + restriction_sites = [] + # For every position in the backbone sequence + for i in range(len(backbone_sequence)): + # Check if this position starts a recognition site + # Handle circular sequences by wrapping around + if is_circular: + # Check if we need to wrap around for the recognition site + if i + len(recognition_site) > len(backbone_sequence): + site_seq = ( + backbone_sequence[i:] + backbone_sequence[: i + len(recognition_site) - len(backbone_sequence)] + ) + else: + site_seq = backbone_sequence[i : i + len(recognition_site)] + # For linear sequences, just check if we're in bounds + elif i + len(recognition_site) <= len(backbone_sequence): + site_seq = backbone_sequence[i : i + len(recognition_site)] + else: + continue + + # Check forward strand recognition site + if site_seq == recognition_site: + restriction_sites.append({"position": i, "strand": "forward"}) + + # Check reverse complement recognition site + rev_comp_site = reverse_complement(recognition_site) + if site_seq == rev_comp_site: + restriction_sites.append({"position": i, "strand": "reverse"}) + + if not restriction_sites: + return { + "success": False, + "message": f"No {enzyme_name} recognition sites found in the backbone", + "assembled_sequence": None, + } + + # Continue with the rest of the function using processed_fragments + # (remaining code is unchanged, just operate on processed_fragments) + + # Step 2: Determine the cut positions and resulting overhangs + cut_fragments = [] + + for site in restriction_sites: + pos = site["position"] + + if site["strand"] == "forward": + # Calculate cut positions for forward strand site + cut_fwd = (pos + len(recognition_site) + offset_fwd) % len(backbone_sequence) + cut_rev = (pos + len(recognition_site) + offset_rev) % len(backbone_sequence) + else: # reverse strand + # For reverse sites, cuts happen on the other side of the recognition site + cut_rev = (pos - offset_fwd) % len(backbone_sequence) + cut_fwd = (pos - offset_rev) % len(backbone_sequence) + + if cut_rev < 0: + cut_rev += len(backbone_sequence) + if cut_fwd < 0: + cut_fwd += len(backbone_sequence) + + # Ensure cut positions are valid + if 0 <= cut_fwd < len(backbone_sequence) and 0 <= cut_rev < len(backbone_sequence): + # Extract overhang sequence + if cut_fwd < cut_rev: + overhang = backbone_sequence[cut_fwd:cut_rev] + else: # Handle wrapping for circular sequences + overhang = backbone_sequence[cut_fwd:] + backbone_sequence[:cut_rev] + + cut_fragments.append( + { + "site_position": pos, + "strand": site["strand"], + "cut_fwd": cut_fwd, + "cut_rev": cut_rev, + "overhang": overhang, + } + ) + + if not cut_fragments: + return { + "success": False, + "message": f"Could not determine valid cut positions for {enzyme_name} sites", + "assembled_sequence": None, + } + + # Step 3: Extract fragment overhangs from oligos + fragment_overhangs = [] + for idx, fragment in enumerate(processed_fragments): + # Clean oligo sequences + fwd_oligo = "".join(c for c in fragment.get("fwd_oligo", "").upper() if c in "ATGC") + rev_oligo = "".join(c for c in fragment.get("rev_oligo", "").upper() if c in "ATGC") + + # Get the overhang length (typically 4 bases for most Type IIS enzymes) + overhang_length = 4 + + # Extract overhangs and insert sequence + fwd_overhang = fwd_oligo[:overhang_length] + rev_overhang = rev_oligo[:overhang_length] + + # The actual insert is everything after the overhang in the forward oligo + insert_seq = fwd_oligo[overhang_length:] + + fragment_overhangs.append( + { + "name": fragment.get("name", f"fragment_{idx + 1}"), + "fwd_overhang": fwd_overhang, + "rev_overhang": rev_overhang, + "insert": insert_seq, + "rc_rev_overhang": reverse_complement(rev_overhang), + } + ) + + # Step 4: For multi-fragment assembly, determine the correct order + # Create a map of overhangs to fragments + fwd_overhang_map = {} # Maps fwd overhangs to fragment indices + rev_overhang_map = {} # Maps rev overhangs to fragment indices + + for i, frag in enumerate(fragment_overhangs): + fwd_overhang_map[frag["fwd_overhang"]] = i + rev_overhang_map[frag["rc_rev_overhang"]] = i + + # Try to identify an entry point where a fragment's fwd overhang matches a backbone cut + possible_starts = [] + for cut_idx, cut in enumerate(cut_fragments): + if cut["overhang"] in fwd_overhang_map: + possible_starts.append((cut_idx, fwd_overhang_map[cut["overhang"]])) + + if not possible_starts: + return { + "success": False, + "message": "Could not find an entry point for assembly. No backbone cuts match fragment overhangs.", + "assembled_sequence": None, + } + + # Try each possible starting point + for start_cut_idx, start_frag_idx in possible_starts: + # Assemble a chain of fragments starting from this one + assembled_fragments = [start_frag_idx] + current_frag_idx = start_frag_idx + + # Keep adding fragments until we can't find a matching one + while True: + current_frag = fragment_overhangs[current_frag_idx] + next_overhang = current_frag["rc_rev_overhang"] + + # Check if this overhang matches another fragment's fwd overhang + next_frag_idx = None + for i, frag in enumerate(fragment_overhangs): + if i != current_frag_idx and frag["fwd_overhang"] == next_overhang: + next_frag_idx = i + break + + if next_frag_idx is None: + # No more fragments to add - check if this overhang matches a backbone cut + break + + # Add this fragment to the chain and continue + assembled_fragments.append(next_frag_idx) + current_frag_idx = next_frag_idx + + # Prevent infinite loops by checking if we've used all fragments + if len(assembled_fragments) >= len(processed_fragments): + break + + # Check if we've used all fragments + if len(assembled_fragments) == len(processed_fragments): + # Check if the last fragment's overhang matches a backbone cut + last_frag = fragment_overhangs[assembled_fragments[-1]] + end_overhang = last_frag["rc_rev_overhang"] + + end_cut_idx = None + for cut_idx, cut in enumerate(cut_fragments): + if cut["overhang"] == end_overhang and cut_idx != start_cut_idx: + end_cut_idx = cut_idx + break + + if end_cut_idx is not None: + # We have a complete assembly path! + start_cut = cut_fragments[start_cut_idx] + end_cut = cut_fragments[end_cut_idx] + + # Assemble the final sequence + # First, extract the backbone segment we're keeping + if is_circular: + # For circular plasmids, we keep the region between end_cut and start_cut + if end_cut["cut_rev"] <= start_cut["cut_fwd"]: + backbone_segment = backbone_sequence[end_cut["cut_rev"] : start_cut["cut_fwd"]] + else: + # Handle wrapping around the origin + backbone_segment = ( + backbone_sequence[end_cut["cut_rev"] :] + backbone_sequence[: start_cut["cut_fwd"]] + ) + else: + # For linear backbones, we keep everything outside the cut region + backbone_segment = ( + backbone_sequence[: min(start_cut["cut_fwd"], end_cut["cut_rev"])] + + backbone_sequence[max(start_cut["cut_fwd"], end_cut["cut_rev"]) :] + ) + + # Then concatenate all fragment inserts in the correct order + insert_segment = "" + for i, frag_idx in enumerate(assembled_fragments): + # Include the overhang for the first fragment + if i == 0: + insert_segment += fragment_overhangs[frag_idx]["fwd_overhang"] + + # Add the main insert sequence + insert_segment += fragment_overhangs[frag_idx]["insert"] + + # Include the reverse overhang for each fragment + insert_segment += reverse_complement(fragment_overhangs[frag_idx]["rev_overhang"]) + + # Final assembled sequence + assembled_sequence = backbone_segment + insert_segment + + return { + "success": True, + "assembled_sequence": assembled_sequence, + "message": f"Successfully assembled {len(processed_fragments)} fragments in correct order", + "fragments_used": len(processed_fragments), + "cuts_used": 2, # We use exactly 2 cuts for the assembly + "backbone_fragment_size": len(backbone_segment), + "assembly_order": [processed_fragments[i]["name"] for i in assembled_fragments], + } + + # If we get here, we couldn't find a valid assembly + return { + "success": False, + "message": "Could not find a valid assembly path. Fragment overhangs may not form a complete circuit.", + "assembled_sequence": None, + } diff --git a/BioScientist/agent_system/engines/v1_executor_backup/tool/pathology.py b/BioScientist/agent_system/engines/v1_executor_backup/tool/pathology.py new file mode 100644 index 0000000000000000000000000000000000000000..c99a45de6d78510346082d5b111787ed408cca77 --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/tool/pathology.py @@ -0,0 +1,1034 @@ +def analyze_aortic_diameter_and_geometry(image_path, output_dir="./output"): + """Analyze aortic diameter and geometry from cardiovascular imaging data. + + This function processes cardiovascular imaging data (ultrasound or CT/MRI) to + measure aortic root diameter, ascending aorta diameter, and calculate + geometric parameters such as tortuosity and dilation indices. + + Parameters + ---------- + image_path : str + Path to the cardiovascular imaging data (DICOM, JPG, PNG) + output_dir : str, optional + Directory to save output files (default: "./output") + + Returns + ------- + str + Research log summarizing the analysis steps and results + + """ + import os + from datetime import datetime + + import cv2 + import numpy as np + + # Create output directory if it doesn't exist + os.makedirs(output_dir, exist_ok=True) + + # Initialize research log + log = [] + log.append(f"Aortic Diameter and Geometry Analysis - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + log.append(f"Input image: {image_path}") + + # Step 1: Load the image + log.append("\n1. Loading and preprocessing image") + try: + # Check if file exists + if not os.path.exists(image_path): + return f"Error: File {image_path} not found" + + # Load image + image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE) + if image is None: + return f"Error: Could not read image {image_path}" + + log.append(f" - Image loaded: {image.shape[1]}x{image.shape[0]} pixels") + + # Basic preprocessing + image = cv2.GaussianBlur(image, (5, 5), 0) + log.append(" - Applied Gaussian blur for noise reduction") + + # Enhance contrast + clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) + image = clahe.apply(image) + log.append(" - Enhanced contrast using CLAHE") + + except Exception as e: + return f"Error during image loading: {str(e)}" + + # Step 2: Segment the aorta + log.append("\n2. Aorta segmentation") + try: + # Otsu thresholding for initial segmentation + thresh_val, binary = cv2.threshold(image, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) + log.append(f" - Applied Otsu thresholding (threshold value: {thresh_val})") + + # Morphological operations to clean up the binary image + kernel = np.ones((3, 3), np.uint8) + binary = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel) + binary = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel) + log.append(" - Applied morphological operations to refine segmentation") + + # Find contours + contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + log.append(f" - Detected {len(contours)} contours") + + # Assuming the aorta is one of the largest contours + if len(contours) == 0: + return "Error: No contours detected in the image" + + # Sort contours by area and take the largest + contours = sorted(contours, key=cv2.contourArea, reverse=True) + aorta_contour = contours[0] # Assuming the largest contour is the aorta + log.append(f" - Selected largest contour as aorta (area: {cv2.contourArea(aorta_contour)} px²)") + + except Exception as e: + return f"Error during aorta segmentation: {str(e)}" + + # Step 3: Measure aortic diameters + log.append("\n3. Measuring aortic diameters") + try: + # Create a mask of the aorta + aorta_mask = np.zeros_like(image) + cv2.drawContours(aorta_mask, [aorta_contour], 0, 255, -1) + + # Find the centerline of the aorta (simplified approach) + # This is a simplified approach - in a real implementation, skeletonization would be better + M = cv2.moments(aorta_contour) + if M["m00"] != 0: + cx = int(M["m10"] / M["m00"]) + cy = int(M["m01"] / M["m00"]) + log.append(f" - Aorta centroid: ({cx}, {cy})") + else: + cx, cy = 0, 0 + log.append(" - Warning: Could not calculate aorta centroid") + + # Measure aortic root diameter (assuming it's at the bottom of the contour) + aortic_root_points = sorted(aorta_contour.reshape(-1, 2), key=lambda p: p[1], reverse=True)[:20] + aortic_root_diameter = np.max([p[0] for p in aortic_root_points]) - np.min([p[0] for p in aortic_root_points]) + log.append(f" - Aortic root diameter: {aortic_root_diameter:.2f} pixels") + + # Measure ascending aorta diameter (assuming it's at the middle of the contour) + y_mid = ( + np.min([p[1] for p in aorta_contour.reshape(-1, 2)]) + np.max([p[1] for p in aorta_contour.reshape(-1, 2)]) + ) // 2 + ascending_points = [p for p in aorta_contour.reshape(-1, 2) if abs(p[1] - y_mid) < 10] + if ascending_points: + ascending_diameter = np.max([p[0] for p in ascending_points]) - np.min([p[0] for p in ascending_points]) + log.append(f" - Ascending aorta diameter: {ascending_diameter:.2f} pixels") + else: + ascending_diameter = 0 + log.append(" - Warning: Could not measure ascending aorta diameter") + + except Exception as e: + return f"Error during diameter measurements: {str(e)}" + + # Step 4: Calculate geometric parameters + log.append("\n4. Calculating aortic geometry parameters") + try: + # Calculate tortuosity (simplified as the ratio of contour length to end-to-end distance) + contour_length = cv2.arcLength(aorta_contour, closed=True) + + # Find the two most distant points (approximation for end-to-end distance) + hull = cv2.convexHull(aorta_contour) + hull_points = hull.reshape(-1, 2) + max_dist = 0 + for i in range(len(hull_points)): + for j in range(i + 1, len(hull_points)): + dist = np.sqrt( + (hull_points[i][0] - hull_points[j][0]) ** 2 + (hull_points[i][1] - hull_points[j][1]) ** 2 + ) + max_dist = max(max_dist, dist) + + if max_dist > 0: + tortuosity = contour_length / max_dist + log.append(f" - Aortic tortuosity index: {tortuosity:.2f}") + else: + tortuosity = 0 + log.append(" - Warning: Could not calculate tortuosity") + + # Calculate dilation index (ratio of max diameter to min diameter) + min_diameter = min(aortic_root_diameter, ascending_diameter) + max_diameter = max(aortic_root_diameter, ascending_diameter) + + if min_diameter > 0: + dilation_index = max_diameter / min_diameter + log.append(f" - Aortic dilation index: {dilation_index:.2f}") + else: + dilation_index = 0 + log.append(" - Warning: Could not calculate dilation index") + + except Exception as e: + return f"Error during geometry calculations: {str(e)}" + + # Step 5: Save results + log.append("\n5. Saving results") + try: + # Create a visual output + output_image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR) + cv2.drawContours(output_image, [aorta_contour], 0, (0, 255, 0), 2) + + # Mark diameters + cv2.circle(output_image, (cx, cy), 5, (0, 0, 255), -1) + + # Save the output image + output_filename = os.path.join(output_dir, f"aorta_analysis_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png") + cv2.imwrite(output_filename, output_image) + log.append(f" - Saved annotated image to: {output_filename}") + + # Save measurements to a text file + measurements_filename = os.path.join( + output_dir, + f"aorta_measurements_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt", + ) + with open(measurements_filename, "w") as f: + f.write(f"Aortic Root Diameter: {aortic_root_diameter:.2f} pixels\n") + f.write(f"Ascending Aorta Diameter: {ascending_diameter:.2f} pixels\n") + f.write(f"Tortuosity Index: {tortuosity:.2f}\n") + f.write(f"Dilation Index: {dilation_index:.2f}\n") + log.append(f" - Saved measurements to: {measurements_filename}") + + except Exception as e: + return f"Error during saving results: {str(e)}" + + # Step 6: Summarize findings + log.append("\n6. Summary of findings") + log.append(f" - Aortic root diameter: {aortic_root_diameter:.2f} pixels") + log.append(f" - Ascending aorta diameter: {ascending_diameter:.2f} pixels") + log.append(f" - Tortuosity index: {tortuosity:.2f}") + log.append(f" - Dilation index: {dilation_index:.2f}") + + return "\n".join(log) + + +def analyze_atp_luminescence_assay( + data_file, + standard_curve_file, + normalization_method="cell_count", + normalization_data=None, +): + """Analyze luminescence-based ATP assay data to determine intracellular ATP concentration. + + Parameters + ---------- + data_file : str + Path to CSV file containing luminescence readings from samples. + Expected format: columns for 'Sample_ID' and 'Luminescence_Value'. + + standard_curve_file : str + Path to CSV file containing standard curve data. + Expected format: columns for 'ATP_Concentration' (in nM) and 'Luminescence_Value'. + + normalization_method : str, optional + Method used to normalize ATP values, either 'cell_count' or 'protein_content'. + Default is 'cell_count'. + + normalization_data : str or dict, optional + Either path to CSV file with normalization data or dictionary with sample IDs as keys. + For 'cell_count': values should be cell counts per sample. + For 'protein_content': values should be protein concentration (μg/mL). + + Returns + ------- + str + Research log summarizing the ATP content measurement process and results. + + """ + from datetime import datetime + + import numpy as np + import pandas as pd + + log = [] + log.append(f"ATP Content Measurement Analysis - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + log.append("=" * 50) + + # Step 1: Load the raw luminescence data + log.append("Step 1: Loading raw luminescence data") + try: + sample_data = pd.read_csv(data_file) + log.append(f"Successfully loaded data from {data_file}") + log.append(f"Number of samples: {len(sample_data)}") + except Exception as e: + log.append(f"Error loading sample data: {str(e)}") + return "\n".join(log) + + # Step 2: Load the standard curve data + log.append("\nStep 2: Loading ATP standard curve data") + try: + std_curve_data = pd.read_csv(standard_curve_file) + log.append(f"Successfully loaded standard curve from {standard_curve_file}") + except Exception as e: + log.append(f"Error loading standard curve data: {str(e)}") + return "\n".join(log) + + # Step 3: Generate standard curve equation (linear regression) + log.append("\nStep 3: Generating ATP standard curve") + try: + x = std_curve_data["Luminescence_Value"] + y = std_curve_data["ATP_Concentration"] + slope, intercept = np.polyfit(x, y, 1) + log.append(f"Standard curve equation: ATP_Concentration = {slope:.6f} × Luminescence + {intercept:.6f}") + except Exception as e: + log.append(f"Error generating standard curve: {str(e)}") + return "\n".join(log) + + # Step 4: Calculate ATP concentrations for samples + log.append("\nStep 4: Calculating ATP concentrations for samples") + try: + sample_data["ATP_Concentration_nM"] = slope * sample_data["Luminescence_Value"] + intercept + log.append("ATP concentrations calculated for all samples") + except Exception as e: + log.append(f"Error calculating ATP concentrations: {str(e)}") + return "\n".join(log) + + # Step 5: Normalize ATP concentrations + log.append(f"\nStep 5: Normalizing ATP concentrations by {normalization_method}") + + # Load normalization data + if normalization_data: + try: + if isinstance(normalization_data, str): + norm_data = pd.read_csv(normalization_data) + norm_dict = dict(zip(norm_data["Sample_ID"], norm_data[normalization_method], strict=False)) + else: + norm_dict = normalization_data + + # Apply normalization + for idx, row in sample_data.iterrows(): + sample_id = row["Sample_ID"] + if sample_id in norm_dict: + if normalization_method == "cell_count": + sample_data.at[idx, "ATP_pmol_per_million_cells"] = ( + row["ATP_Concentration_nM"] / norm_dict[sample_id] * 1000 + ) + elif normalization_method == "protein_content": + sample_data.at[idx, "ATP_nmol_per_mg_protein"] = ( + row["ATP_Concentration_nM"] / norm_dict[sample_id] + ) + + log.append(f"Successfully normalized ATP concentrations by {normalization_method}") + except Exception as e: + log.append(f"Error during normalization: {str(e)}") + log.append("Continuing with unnormalized ATP concentrations") + else: + log.append("No normalization data provided. Reporting unnormalized ATP concentrations.") + + # Step 6: Save results + output_file = "atp_measurement_results.csv" + try: + sample_data.to_csv(output_file, index=False) + log.append(f"\nStep 6: Results saved to {output_file}") + except Exception as e: + log.append(f"\nStep 6: Error saving results: {str(e)}") + + # Step 7: Summary statistics + log.append("\nStep 7: Summary of ATP measurement results") + try: + log.append("ATP Concentration Summary (nM):") + log.append(f" Mean: {sample_data['ATP_Concentration_nM'].mean():.2f}") + log.append(f" Median: {sample_data['ATP_Concentration_nM'].median():.2f}") + log.append(f" Min: {sample_data['ATP_Concentration_nM'].min():.2f}") + log.append(f" Max: {sample_data['ATP_Concentration_nM'].max():.2f}") + + if normalization_method == "cell_count" and "ATP_pmol_per_million_cells" in sample_data.columns: + log.append("\nATP per Million Cells (pmol):") + log.append(f" Mean: {sample_data['ATP_pmol_per_million_cells'].mean():.2f}") + log.append(f" Median: {sample_data['ATP_pmol_per_million_cells'].median():.2f}") + elif normalization_method == "protein_content" and "ATP_nmol_per_mg_protein" in sample_data.columns: + log.append("\nATP per mg Protein (nmol):") + log.append(f" Mean: {sample_data['ATP_nmol_per_mg_protein'].mean():.2f}") + log.append(f" Median: {sample_data['ATP_nmol_per_mg_protein'].median():.2f}") + except Exception as e: + log.append(f"Error generating summary statistics: {str(e)}") + + log.append("\nATP Content Measurement Analysis Complete") + + return "\n".join(log) + + +def analyze_thrombus_histology(image_path, output_dir="./output"): + """Analyze histological images of thrombus samples stained with H&E to identify and quantify + different thrombus components (fresh, cellular lysis, endothelialization, fibroblastic reaction). + + Parameters + ---------- + image_path : str + Path to the histological image of thrombus sample stained with H&E + output_dir : str, optional + Directory to save output files (default: "./output") + + Returns + ------- + str + Research log summarizing the analysis steps and results + + """ + import os + from datetime import datetime + + import cv2 + import numpy as np + from skimage import color + + # Create output directory if it doesn't exist + if not os.path.exists(output_dir): + os.makedirs(output_dir) + + # Initialize research log + log = f"# Thrombus Component Analysis - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n" + log += f"Analyzing image: {image_path}\n\n" + + # Step 1: Load and preprocess the image + log += "## Step 1: Image Loading and Preprocessing\n" + original_image = cv2.imread(image_path) + if original_image is None: + return f"Error: Could not load image from {image_path}" + + # Convert to RGB (from BGR) + rgb_image = cv2.cvtColor(original_image, cv2.COLOR_BGR2RGB) + log += f"- Loaded image with dimensions: {rgb_image.shape[1]}x{rgb_image.shape[0]} pixels\n" + + # Convert to LAB color space for better color segmentation + lab_image = color.rgb2lab(rgb_image) + log += "- Converted image to LAB color space for improved color-based segmentation\n\n" + + # Step 2: Segment thrombus components based on color characteristics + log += "## Step 2: Thrombus Component Segmentation\n" + + # Create masks for different components based on color thresholds + # These thresholds are approximate and may need adjustment for specific staining protocols + + # Fresh thrombus: typically red (erythrocytes) with blue nuclei (leukocytes) + # Usually appears as bright red/pink areas with scattered dark blue/purple nuclei + fresh_mask = ( + (lab_image[:, :, 0] > 50) # Lightness + & (lab_image[:, :, 1] > 15) # a* channel (red component) + ) + + # Cellular lysis: degraded cellular components, less intense staining + lysis_mask = ( + (lab_image[:, :, 0] > 60) # Lightness + & (lab_image[:, :, 1] < 15) # Less red + & (lab_image[:, :, 1] > -5) + & (lab_image[:, :, 2] < 10) # Less blue + ) + + # Endothelialization: endothelial cells lining the thrombus + # Typically appears as thin, organized cellular layers + endothel_mask = ( + (lab_image[:, :, 0] > 40) & (lab_image[:, :, 0] < 70) & (lab_image[:, :, 2] < -5) # More blue component + ) + + # Fibroblastic reaction: fibroblasts and collagen deposition + # Usually appears as pale pink/purple fibrous structures + fibro_mask = ( + (lab_image[:, :, 0] > 70) # Lighter areas + & (lab_image[:, :, 1] < 5) # Less red + & (lab_image[:, :, 1] > -10) + & (lab_image[:, :, 2] > 0) # Less blue + ) + + log += "- Created masks for each thrombus component based on color characteristics\n" + + # Step 3: Quantify the components + log += "\n## Step 3: Component Quantification\n" + + # Calculate total pixel count (excluding background) + total_pixels = fresh_mask.sum() + lysis_mask.sum() + endothel_mask.sum() + fibro_mask.sum() + + # Calculate percentages + if total_pixels > 0: + fresh_percent = (fresh_mask.sum() / total_pixels) * 100 + lysis_percent = (lysis_mask.sum() / total_pixels) * 100 + endothel_percent = (endothel_mask.sum() / total_pixels) * 100 + fibro_percent = (fibro_mask.sum() / total_pixels) * 100 + else: + fresh_percent = lysis_percent = endothel_percent = fibro_percent = 0 + + log += f"- Fresh thrombus: {fresh_percent:.2f}%\n" + log += f"- Cellular lysis: {lysis_percent:.2f}%\n" + log += f"- Endothelialization: {endothel_percent:.2f}%\n" + log += f"- Fibroblastic reaction: {fibro_percent:.2f}%\n\n" + + # Step 4: Visualize the results + log += "## Step 4: Visualization\n" + + # Create a visualization image + visualization = np.zeros_like(rgb_image) + + # Assign colors to different components + visualization[fresh_mask] = [255, 0, 0] # Red for fresh thrombus + visualization[lysis_mask] = [0, 255, 0] # Green for cellular lysis + visualization[endothel_mask] = [0, 0, 255] # Blue for endothelialization + visualization[fibro_mask] = [255, 255, 0] # Yellow for fibroblastic reaction + + # Save the visualization + output_filename = os.path.join(output_dir, f"thrombus_components_{os.path.basename(image_path)}") + cv2.imwrite(output_filename, cv2.cvtColor(visualization, cv2.COLOR_RGB2BGR)) + + log += f"- Visualization saved as: {output_filename}\n" + log += "- Color legend:\n" + log += " - Red: Fresh thrombus\n" + log += " - Green: Cellular lysis\n" + log += " - Blue: Endothelialization\n" + log += " - Yellow: Fibroblastic reaction\n\n" + + # Step 5: Summary + log += "## Summary\n" + log += "The histological analysis of the thrombus sample revealed the following composition:\n\n" + log += f"1. Fresh thrombus: {fresh_percent:.2f}%\n" + log += f"2. Cellular lysis: {lysis_percent:.2f}%\n" + log += f"3. Endothelialization: {endothel_percent:.2f}%\n" + log += f"4. Fibroblastic reaction: {fibro_percent:.2f}%\n\n" + + # Save results to CSV + import csv + + csv_filename = os.path.join( + output_dir, + f"thrombus_analysis_{os.path.basename(image_path).split('.')[0]}.csv", + ) + with open(csv_filename, "w", newline="") as csvfile: + writer = csv.writer(csvfile) + writer.writerow(["Component", "Percentage"]) + writer.writerow(["Fresh thrombus", f"{fresh_percent:.2f}"]) + writer.writerow(["Cellular lysis", f"{lysis_percent:.2f}"]) + writer.writerow(["Endothelialization", f"{endothel_percent:.2f}"]) + writer.writerow(["Fibroblastic reaction", f"{fibro_percent:.2f}"]) + + log += f"Quantitative results saved to: {csv_filename}\n" + + return log + + +def analyze_intracellular_calcium_with_rhod2( + background_image_path, control_image_path, sample_image_path, output_dir="./output" +): + """Analyzes intracellular calcium concentration using Rhod-2 fluorescent indicator from microscopy images. + + Parameters + ---------- + background_image_path : str + Path to the background image (no cells, just media) + control_image_path : str + Path to the control image (cells without calcium stimulus) + sample_image_path : str + Path to the sample image (cells with calcium stimulus) + output_dir : str, optional + Directory to save output files, default is "./output" + + Returns + ------- + str + Research log summarizing the analysis steps and results + + """ + import os + from datetime import datetime + + import cv2 + import matplotlib.pyplot as plt + import numpy as np + + # Create output directory if it doesn't exist + os.makedirs(output_dir, exist_ok=True) + + # Step 1: Load images + background = cv2.imread(background_image_path, cv2.IMREAD_GRAYSCALE).astype(float) + control = cv2.imread(control_image_path, cv2.IMREAD_GRAYSCALE).astype(float) + sample = cv2.imread(sample_image_path, cv2.IMREAD_GRAYSCALE).astype(float) + + # Step 2: Background subtraction + control_corrected = np.maximum(control - background, 0) + sample_corrected = np.maximum(sample - background, 0) + + # Step 3: Calculate fluorescence intensity (mean values) + control_intensity = np.mean(control_corrected) + sample_intensity = np.mean(sample_corrected) + + # Step 4: Convert to calcium concentration using a standard calibration equation + # [Ca2+] = Kd * (F - Fmin) / (Fmax - F) + # For Rhod-2, Kd is approximately 570 nM + # We'll use control as Fmin and estimate Fmax as 2.5x the sample value (typical for max calcium response) + kd = 570 # nM + f_min = control_intensity + f_max = 2.5 * sample_intensity # Estimated maximum + f = sample_intensity + + # Guard against division by zero + calcium_concentration = float("inf") if f_max == f else kd * (f - f_min) / (f_max - f) + + # Step 5: Generate heatmap visualization of calcium concentration + plt.figure(figsize=(10, 8)) + calcium_map = (sample_corrected - control_corrected) / (f_max - control_corrected + 1e-10) * kd + plt.imshow(calcium_map, cmap="hot") + plt.colorbar(label="[Ca²⁺] (nM)") + plt.title("Intracellular Calcium Concentration Map") + + # Save the visualization + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + result_filename = f"{output_dir}/calcium_map_{timestamp}.png" + plt.savefig(result_filename) + plt.close() + + # Generate research log + log = f""" +Intracellular Calcium Imaging Analysis Log - {datetime.now().strftime("%Y-%m-%d %H:%M:%S")} +------------------------------------------------------------ +1. Loaded microscopy images: + - Background image: {background_image_path} + - Control image: {control_image_path} + - Sample image: {sample_image_path} + +2. Performed background subtraction to correct for autofluorescence + +3. Calculated fluorescence intensity: + - Control intensity: {control_intensity:.2f} a.u. + - Sample intensity: {sample_intensity:.2f} a.u. + +4. Converted fluorescence to calcium concentration using Rhod-2 calibration: + - Kd value: {kd} nM + - Mean intracellular [Ca²⁺]: {calcium_concentration:.2f} nM + +5. Generated calcium concentration heatmap saved to: + - {result_filename} + +Analysis complete. The sample shows {calcium_concentration:.2f} nM intracellular calcium concentration. +""" + + return log + + +def quantify_corneal_nerve_fibers(image_path, marker_type, output_dir="./output", threshold_method="otsu"): + """Quantify the volume/density of immunofluorescence-labeled corneal nerve fibers. + + Parameters + ---------- + image_path : str + Path to the immunofluorescence microscopy image file + marker_type : str + Type of nerve fiber marker (e.g., 'βIII-tubulin', 'SP', 'L1CAM') + output_dir : str, optional + Directory to save output files (default: './output') + threshold_method : str, optional + Method for thresholding ('otsu', 'adaptive', 'manual'), default is 'otsu' + + Returns + ------- + str + Research log summarizing the analysis steps and results + + """ + import os + from datetime import datetime + + import numpy as np + from skimage import filters, io, measure, morphology, util + from skimage.color import rgb2gray + + # Create output directory if it doesn't exist + os.makedirs(output_dir, exist_ok=True) + + # Generate timestamp for unique filenames + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + base_filename = f"{marker_type.replace(' ', '_')}_{timestamp}" + + # Step 1: Load the image + try: + image = io.imread(image_path) + # Convert to grayscale if RGB + if len(image.shape) > 2: + image = rgb2gray(image) + except Exception as e: + return f"Error loading image: {str(e)}" + + # Step 2: Preprocess the image + # Normalize to 0-1 range + image_normalized = image.astype(float) + image_normalized = (image_normalized - image_normalized.min()) / (image_normalized.max() - image_normalized.min()) + + # Step 3: Segment the nerve fibers + if threshold_method == "otsu": + threshold_value = filters.threshold_otsu(image_normalized) + elif threshold_method == "adaptive": + threshold_value = filters.threshold_local(image_normalized, block_size=35) + else: # manual + threshold_value = 0.5 # Default manual threshold + + # Create binary mask + binary_mask = image_normalized > threshold_value + + # Step 4: Clean up with morphological operations + # Remove small objects + cleaned_mask = morphology.remove_small_objects(binary_mask, min_size=50) + # Fill holes + cleaned_mask = morphology.closing(cleaned_mask, morphology.disk(2)) + + # Step 5: Quantify the nerve fibers + # Calculate total area of nerve fibers (in pixels) + fiber_area = np.sum(cleaned_mask) + # Calculate total image area + total_area = cleaned_mask.size + # Calculate fiber density (percentage of area covered by fibers) + fiber_density = (fiber_area / total_area) * 100 + + # Calculate properties of individual fiber segments + labeled_fibers = measure.label(cleaned_mask) + fiber_props = measure.regionprops(labeled_fibers) + + # Calculate average fiber length and width + total_length = 0 + total_width = 0 + fiber_count = len(fiber_props) + + if fiber_count > 0: + for prop in fiber_props: + # Approximating length as major axis length + total_length += prop.major_axis_length + # Approximating width as minor axis length + total_width += prop.minor_axis_length + + avg_length = total_length / fiber_count + avg_width = total_width / fiber_count + else: + avg_length = 0 + avg_width = 0 + + # Step 6: Save results + # Save segmented image + segmented_image_path = os.path.join(output_dir, f"{base_filename}_segmented.png") + io.imsave(segmented_image_path, util.img_as_ubyte(cleaned_mask)) + + # Save measurements to CSV + measurements_path = os.path.join(output_dir, f"{base_filename}_measurements.csv") + with open(measurements_path, "w") as f: + f.write("Metric,Value\n") + f.write(f"Marker Type,{marker_type}\n") + f.write(f"Fiber Area (pixels),{fiber_area}\n") + f.write(f"Total Image Area (pixels),{total_area}\n") + f.write(f"Fiber Density (%),{fiber_density:.2f}\n") + f.write(f"Fiber Count,{fiber_count}\n") + f.write(f"Average Fiber Length (pixels),{avg_length:.2f}\n") + f.write(f"Average Fiber Width (pixels),{avg_width:.2f}\n") + + # Step 7: Generate research log + log = f""" +Research Log: Quantification of {marker_type} Labeled Corneal Nerve Fibers +Date: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")} +Image: {os.path.basename(image_path)} + +Analysis Steps: +1. Loaded and preprocessed the immunofluorescence microscopy image +2. Applied {threshold_method} thresholding for nerve fiber segmentation +3. Performed morphological operations to clean the segmentation +4. Quantified nerve fiber metrics + +Results: +- Fiber Area: {fiber_area} pixels +- Total Image Area: {total_area} pixels +- Fiber Density: {fiber_density:.2f}% +- Number of Fiber Segments: {fiber_count} +- Average Fiber Length: {avg_length:.2f} pixels +- Average Fiber Width: {avg_width:.2f} pixels + +Output Files: +- Segmented Image: {os.path.basename(segmented_image_path)} +- Measurements: {os.path.basename(measurements_path)} +""" + + return log + + +def segment_and_quantify_cells_in_multiplexed_images( + image_path, markers_list, nuclear_channel_index=0, output_dir="./output" +): + """Segment cells and quantify protein expression levels from multichannel tissue images. + + Parameters + ---------- + image_path : str + Path to the multichannel image file (tiff stack or similar format) + markers_list : list of str + List of marker names corresponding to each channel in the image + nuclear_channel_index : int, optional + Index of the nuclear marker channel (default: 0, typically DAPI) + output_dir : str, optional + Directory to save output files (default: "./output") + + Returns + ------- + str + Research log summarizing the steps performed and output file locations + + """ + import os + from datetime import datetime + + import numpy as np + import pandas as pd + from scipy import ndimage # Import ndimage for distance transform + from skimage import filters, io, measure, morphology, segmentation + + # Initialize research log + log = [] + log.append( + f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] Starting cell segmentation and protein quantification" + ) + + # Create output directory if it doesn't exist + if not os.path.exists(output_dir): + os.makedirs(output_dir) + log.append(f"Created output directory: {output_dir}") + + # Load the multichannel image + try: + log.append(f"Loading multichannel image from {image_path}") + image = io.imread(image_path) + log.append(f"Image loaded successfully. Shape: {image.shape}") + + # Validate image dimensions + if len(image.shape) < 3: + return ( + "Error: Image should be multichannel with shape (channels, height, width) or (height, width, channels)" + ) + + # Determine image format + if image.shape[0] == len(markers_list): + # Format is (channels, height, width) + num_channels, height, width = image.shape + # Extract nuclear channel + nuclear_channel = image[nuclear_channel_index] + elif image.shape[-1] == len(markers_list): + # Format is (height, width, channels) + height, width, num_channels = image.shape + # Extract nuclear channel + nuclear_channel = image[:, :, nuclear_channel_index] + else: + return f"Error: Number of channels in image ({image.shape}) doesn't match markers list length ({len(markers_list)})" + + log.append(f"Processing image with {num_channels} channels, dimensions: {height}x{width}") + + except Exception as e: + return f"Error loading image: {str(e)}" + + # Segment nuclei + log.append("Performing nuclear segmentation") + # Apply threshold to nuclear channel + threshold = filters.threshold_otsu(nuclear_channel) + binary_nuclei = nuclear_channel > threshold + + # Clean up binary image + binary_nuclei = morphology.remove_small_objects(binary_nuclei, min_size=50) + binary_nuclei = morphology.binary_closing(binary_nuclei, morphology.disk(2)) + + # Label nuclei + labeled_nuclei = measure.label(binary_nuclei) + log.append(f"Identified {np.max(labeled_nuclei)} potential nuclei") + + # Create cell masks by expanding nuclei + log.append("Expanding nuclear masks to approximate cell boundaries") + cell_masks = segmentation.watershed( + -ndimage.distance_transform_edt(~binary_nuclei), # Use ndimage for distance transform + labeled_nuclei, + mask=morphology.binary_dilation(binary_nuclei, morphology.disk(10)), + ) + + # Get region properties for nuclei + regions = measure.regionprops(cell_masks) + log.append(f"Segmented {len(regions)} cells") + + # Create a dataframe to store cell features + log.append("Extracting features and quantifying marker intensities") + cell_data = [] + + for i, region in enumerate(regions): + cell_features = { + "cell_id": i + 1, + "centroid_y": region.centroid[0], + "centroid_x": region.centroid[1], + "area": region.area, + } + + # Extract intensity for each marker + for channel_idx, marker_name in enumerate(markers_list): + if image.shape[0] == len(markers_list): + # Format is (channels, height, width) + marker_image = image[channel_idx] + else: + # Format is (height, width, channels) + marker_image = image[:, :, channel_idx] + + # Create mask for current cell + cell_mask = cell_masks == region.label + + # Calculate mean intensity within the cell mask + mean_intensity = np.mean(marker_image[cell_mask]) + cell_features[f"{marker_name}_mean_intensity"] = mean_intensity + + cell_data.append(cell_features) + + # Create dataframe + cell_df = pd.DataFrame(cell_data) + + # Save results + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + results_file = os.path.join(output_dir, f"cell_features_{timestamp}.csv") + cell_df.to_csv(results_file, index=False) + log.append(f"Saved spatial feature table to {results_file}") + + # Save segmentation mask for visualization + mask_file = os.path.join(output_dir, f"cell_segmentation_mask_{timestamp}.tiff") + io.imsave(mask_file, cell_masks.astype(np.uint16)) + log.append(f"Saved cell segmentation mask to {mask_file}") + + # Summarize results + log.append(f"Analysis complete. Processed {num_channels} markers across {len(regions)} cells.") + log.append("Output files:") + log.append(f" - Spatial feature table: {results_file}") + log.append(f" - Cell segmentation mask: {mask_file}") + + return "\n".join(log) + + +def analyze_bone_microct_morphometry(input_file_path, output_dir="./results", threshold_value=None): + """Analyze bone microarchitecture parameters from 3D micro-CT images. + + Performs quantitative analysis of bone microstructure to calculate bone mineral density (BMD), + bone volume (BV), trabecular number (Tb.N), trabecular thickness (Tb.Th), and + trabecular separation (Tb.S) from micro-CT data. + + Parameters + ---------- + input_file_path : str + Path to the micro-CT scan data file (TIFF stack or similar 3D format) + output_dir : str, optional + Directory to save output files, default is "./results" + threshold_value : float, optional + Threshold value for bone segmentation. If None, Otsu's method will be used + + Returns + ------- + str + Research log summarizing the analysis steps and results + + """ + import json + import os + from datetime import datetime + + import numpy as np + from scipy import ndimage + from skimage import filters, io + + # Create output directory if it doesn't exist + os.makedirs(output_dir, exist_ok=True) + + # Start research log + log = [ + "# Micro-CT Bone Morphometry Analysis", + f"Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", + f"Input file: {input_file_path}", + ] + + # Step 1: Load the micro-CT data + log.append("\n## 1. Data Loading") + try: + log.append(f"Loading 3D micro-CT data from {input_file_path}") + # Load the 3D image data + image_data = io.imread(input_file_path) + if image_data.ndim < 3: + log.append("Warning: Input appears to be 2D. Converting to 3D.") + image_data = np.expand_dims(image_data, axis=0) + + log.append(f"Data loaded successfully. Dimensions: {image_data.shape}") + voxel_count = image_data.size + log.append(f"Total voxels: {voxel_count}") + except Exception as e: + error_msg = f"Error loading data: {str(e)}" + log.append(error_msg) + return "\n".join(log) + + # Step 2: Preprocess the data + log.append("\n## 2. Preprocessing") + log.append("Applying median filter to reduce noise") + filtered_data = ndimage.median_filter(image_data, size=2) + + # Step 3: Segmentation of bone tissue + log.append("\n## 3. Bone Segmentation") + if threshold_value is None: + log.append("Calculating optimal threshold using Otsu's method") + threshold_value = filters.threshold_otsu(filtered_data) + + log.append(f"Segmenting bone using threshold value: {threshold_value}") + binary_image = filtered_data > threshold_value + + # Step 4: Calculate bone morphometry parameters + log.append("\n## 4. Morphometry Analysis") + + # 4.1 Calculate BMD (simplified as mean intensity in bone regions) + bmd = np.mean(image_data[binary_image]) + log.append(f"Bone Mineral Density (BMD): {bmd:.2f} arbitrary units") + + # 4.2 Calculate BV (bone volume) + bone_volume = np.sum(binary_image) + total_volume = binary_image.size + bv_tv_ratio = bone_volume / total_volume + log.append(f"Bone Volume (BV): {bone_volume} voxels") + log.append(f"BV/TV Ratio: {bv_tv_ratio:.4f}") + + # 4.3 Calculate trabecular thickness (Tb.Th) + # Using distance transform method + log.append("Calculating trabecular thickness (Tb.Th)") + distance_map = ndimage.distance_transform_edt(binary_image) + tb_th_mean = np.mean(distance_map[binary_image]) * 2 # Multiply by 2 for diameter + log.append(f"Trabecular Thickness (Tb.Th): {tb_th_mean:.2f} voxels") + + # 4.4 Calculate trabecular separation (Tb.S) + log.append("Calculating trabecular separation (Tb.S)") + distance_map_inv = ndimage.distance_transform_edt(~binary_image) + tb_s_mean = np.mean(distance_map_inv[~binary_image]) + log.append(f"Trabecular Separation (Tb.S): {tb_s_mean:.2f} voxels") + + # 4.5 Calculate trabecular number (Tb.N) + # Simplified calculation: Tb.N = (BV/TV) / Tb.Th + log.append("Calculating trabecular number (Tb.N)") + tb_n = bv_tv_ratio / tb_th_mean if tb_th_mean > 0 else 0 + log.append(f"Trabecular Number (Tb.N): {tb_n:.4f} 1/voxel") + + # Step 5: Save results + log.append("\n## 5. Saving Results") + + # Save binary segmentation as a sample slice + middle_slice = binary_image.shape[0] // 2 + segmentation_file = os.path.join(output_dir, "bone_segmentation_slice.tif") + io.imsave(segmentation_file, binary_image[middle_slice].astype(np.uint8) * 255) + log.append(f"Sample segmentation slice saved to: {segmentation_file}") + + # Save numerical results as JSON + results = { + "BMD": float(bmd), + "BV": int(bone_volume), + "BV/TV": float(bv_tv_ratio), + "Tb.Th": float(tb_th_mean), + "Tb.S": float(tb_s_mean), + "Tb.N": float(tb_n), + } + + results_file = os.path.join(output_dir, "bone_morphometry_results.json") + with open(results_file, "w") as f: + json.dump(results, f, indent=4) + log.append(f"Numerical results saved to: {results_file}") + + # Summary of results + log.append("\n## 6. Summary of Bone Morphometry Results") + log.append(f"- Bone Mineral Density (BMD): {bmd:.2f} arbitrary units") + log.append(f"- Bone Volume (BV): {bone_volume} voxels") + log.append(f"- BV/TV Ratio: {bv_tv_ratio:.4f}") + log.append(f"- Trabecular Thickness (Tb.Th): {tb_th_mean:.2f} voxels") + log.append(f"- Trabecular Separation (Tb.S): {tb_s_mean:.2f} voxels") + log.append(f"- Trabecular Number (Tb.N): {tb_n:.4f} 1/voxel") + + return "\n".join(log) diff --git a/BioScientist/agent_system/engines/v1_executor_backup/tool/pharmacology.py b/BioScientist/agent_system/engines/v1_executor_backup/tool/pharmacology.py new file mode 100644 index 0000000000000000000000000000000000000000..bf643fcfcf82a26116807ffce5cff8c949267c9e --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/tool/pharmacology.py @@ -0,0 +1,4282 @@ +import os +import pickle +import re +import subprocess +import sys +from datetime import datetime +from difflib import get_close_matches + +import numpy as np +import pandas as pd + + +def run_diffdock_with_smiles(pdb_path, smiles_string, local_output_dir, gpu_device=0, use_gpu=True): + try: + summary = [] + + # Check if PDB file exists + if not os.path.exists(pdb_path): + raise FileNotFoundError(f"The PDB file '{pdb_path}' does not exist.") + summary.append(f"PDB file '{pdb_path}' found.") + + # Ensure the output directory exists + if not os.path.exists(local_output_dir): + os.makedirs(local_output_dir) + summary.append(f"Output directory '{local_output_dir}' is ready.") + + # Pull the pre-built container from Docker Hub + summary.append("Pulling DiffDock container from Docker Hub...") + subprocess.run(["docker", "pull", "rbgcsail/diffdock"], check=True) + summary.append("DiffDock container pulled successfully.") + + # Check for GPU availability (if using GPU) + if use_gpu: + summary.append("Checking for GPU availability...") + gpu_check = subprocess.run( + [ + "docker", + "run", + "--rm", + "--gpus", + "all", + "nvidia/cuda:11.7.1-devel-ubuntu22.04", + "nvidia-smi", + ], + check=True, + capture_output=True, + text=True, + ) + summary.append(f"GPU Status: {gpu_check.stdout.strip()}") + + # Prepare the GPU flag + gpu_flag = ["--gpus", f"device={gpu_device}"] if use_gpu else [] + + # Docker run command + summary.append("Running DiffDock inference...") + run_command = ( + ["docker", "run"] + + gpu_flag + + [ + # Mount the local directory to /home/appuser/output inside the container + "-v", + f"{os.path.abspath(pdb_path)}:/home/appuser/input/protein.pdb", # PDB file mount + "-v", + f"{os.path.abspath(local_output_dir)}:/home/appuser/output", # Output directory mount + "--entrypoint", + "/bin/bash", + "rbgcsail/diffdock", + "-c", + # Command to run inference using micromamba environment + f"micromamba run -n diffdock python -m inference --config default_inference_args.yaml " + f"--protein_path /home/appuser/input/protein.pdb --ligand '{smiles_string}' --out_dir /home/appuser/output", + ] + ) + + # Execute the Docker command + result = subprocess.run(run_command, check=False, capture_output=True, text=True) + + # Check for errors + if result.returncode != 0: + summary.append(f"Error during inference: {result.stderr.strip()}") + return "\n".join(summary) + else: + summary.append("DiffDock inference completed successfully.") + summary.append(f"Results stored in '{local_output_dir}'.") + + return "\n".join(summary) + + except FileNotFoundError as e: + return f"File error: {e}" + except subprocess.CalledProcessError as e: + return f"Command execution error: {e}" + except Exception as e: + return f"An error occurred: {e}" + + +def docking_autodock_vina(smiles_list, receptor_pdb_file, box_center, box_size, ncpu=1): + from tdc import Oracle + + log = [] + + # Log the start of the process + log.append("Step 1: Initializing the Oracle") + log.append(f"Receptor PDB File: {receptor_pdb_file}") + log.append(f"Box Center: {box_center}") + log.append(f"Box Size: {box_size}") + + # Initialize the Oracle object + oracle = Oracle( + name="pyscreener", + receptor_pdb_file=receptor_pdb_file, + box_center=box_center, + box_size=box_size, + ncpu=ncpu, + ) + log.append("Oracle initialized successfully.") + + # Log the list of SMILES strings + log.append(f"\nStep 2: Processing SMILES strings: {smiles_list}") + + # Get the docking scores + docking_scores = oracle(smiles_list) + log.append(f"Docking scores calculated: {docking_scores}") + + # Create a dictionary mapping SMILES to their docking scores + results_dict = dict(zip(smiles_list, docking_scores, strict=False)) + + # Log the result mapping + log.append("\nStep 3: Mapping SMILES to docking scores:") + log.append(f"Results: {results_dict}") + + # Convert the log to a string and return it + research_log = "\n".join(log) + return research_log + + +def run_autosite(pdb_file, output_dir, spacing=1.0): + # Prepare the output directory + if not os.path.exists(output_dir): + os.makedirs(output_dir) + + # Convert the PDB file to PDBQT format (assuming prepare_receptor4.py is accessible) + pdbqt_file = pdb_file.replace(".pdb", ".pdbqt") + subprocess.run(["prepare_receptor", "-r", pdb_file, "-o", pdbqt_file], check=True) + + # Run AutoSite + autosite_cmd = [ + "autosite", + "-r", + pdbqt_file, + "--spacing", + str(spacing), + "-o", + output_dir, + ] + subprocess.run(autosite_cmd, check=True) + + # Parse the results to find the box center and size + box_center, box_size = None, None + log_path = os.path.join(output_dir, "_AutoSiteSummary.log") + with open(log_path) as log_file: + log_content = log_file.read() + + # Extract box center and size from the log (assuming standard output format) + box_center_match = re.search(r"Box center:\s*\(([^)]+)\)", log_content) + box_size_match = re.search(r"Box size:\s*\(([^)]+)\)", log_content) + + if box_center_match: + box_center = box_center_match.group(1) + if box_size_match: + box_size = box_size_match.group(1) + + # Create a research log string + research_log = f"AutoSite run for {pdb_file} with spacing {spacing}\n" + research_log += f"Output directory: {output_dir}\n" + if box_center and box_size: + research_log += f"Box Center: {box_center}\nBox Size: {box_size}" + else: + research_log += "Box Center and Size information not found in log." + + return research_log + + +# Function to get TxGNN predictions and return a summarized string output +def retrieve_topk_repurposing_drugs_from_disease_txgnn(disease_name, data_lake_path, k=5): + """This function computes TxGNN model predictions for drug repurposing. It takes in the paths to the data, + the disease name, and returns a summary of the top K predicted drugs with their sigmoid-transformed scores. + + Args: + - disease_name (str): The name of the disease for which the drug predictions are to be retrieved. + - data_lake_path (str): The path to the data lake containing the TxGNN predictions. + - k (int, optional): The number of top drug predictions to return. Defaults to 5. + + Returns: + - str: A summary of the steps and the top K drug predictions with their scores. + + """ + + # Sigmoid function to convert raw prediction scores + def sigmoid(x): + return 1 / (1 + np.exp(-x)) + + # Step 1: Load the mappings and prediction data from the provided paths + name_mapping_path = data_lake_path + "/txgnn_name_mapping.pkl" + result_path = data_lake_path + "/txgnn_prediction.pkl" + + with open(name_mapping_path, "rb") as f: + mapping = pickle.load(f) + + with open(result_path, "rb") as f: + result = pickle.load(f) + + # Step 2: Fuzzy match the disease name to find the closest match + possible_diseases = result.keys() + matched_disease = get_close_matches(disease_name, possible_diseases, n=1, cutoff=0.6) + + if not matched_disease: + return f"Error: No matching disease found for '{disease_name}'. Please try a different name." + + matched_disease = matched_disease[0] + + # Step 3: Retrieve the prediction scores for the matched disease + disease_predictions = result[matched_disease] + + # Step 4: Apply the sigmoid function to the raw prediction scores + sigmoid_predictions = {drug_id: sigmoid(score) for drug_id, score in disease_predictions.items()} + + # Step 5: Sort the drugs by prediction score in descending order + top_k_drugs = sorted(sigmoid_predictions.items(), key=lambda x: x[1], reverse=True)[:k] + + # Step 6: Map drug IDs to their names and format the results + top_k_drug_names = [(mapping["id2name_drug"].get(drug_id, "Unknown Drug"), score) for drug_id, score in top_k_drugs] + + # Step 7: Create a human and LLM-friendly summary string + summary = f"TxGNN Drug Repurposing Predictions for '{matched_disease}':\n" + summary += f"Top {k} predicted drugs and their corresponding prediction scores (post-sigmoid transformation):\n" + + for i, (drug_name, score) in enumerate(top_k_drug_names, 1): + summary += f"{i}. {drug_name} - Prediction Score: {score:.4f}\n" + + summary += "\nProcess Summary:\n" + summary += "- Fuzzy matching was used to match the input disease name to '{matched_disease}'.\n" + summary += "- Sigmoid function was applied to raw prediction scores to convert them into probabilities.\n" + summary += f"- The top {k} drugs were selected based on their prediction scores.\n" + + return summary + + +# ADMET prediction function with research log format +def predict_admet_properties(smiles_list, ADMET_model_type="MPNN"): + try: + from DeepPurpose import CompoundPred, utils + except Exception: + subprocess.run([sys.executable, "-m", "pip", "install", "DeepPurpose"], check=False) + from DeepPurpose import CompoundPred, utils + + # Define available model types + available_model_types = ["MPNN", "CNN", "Morgan"] + + # Check if the provided model type is valid + if ADMET_model_type not in available_model_types: + return f"Error: Invalid ADMET model type '{ADMET_model_type}'. Available options are: {', '.join(available_model_types)}." + + # Load pretrained ADMET models only once + model_ADMETs = {} + tasks = [ + "AqSolDB", + "Caco2", + "HIA", + "Pgp_inhibitor", + "Bioavailability", + "BBB_MolNet", + "PPBR", + "CYP2C19", + "CYP2D6", + "CYP3A4", + "CYP1A2", + "CYP2C9", + "ClinTox", + "Lipo_AZ", + "Half_life_eDrug3D", + "Clearance_eDrug3D", + ] + + for task in tasks: + model_ADMETs[task + "_" + ADMET_model_type + "_model"] = CompoundPred.model_pretrained( + model=task + "_" + ADMET_model_type + "_model" + ) + + # Helper function for ADMET prediction + def ADMET_pred(drug, task, unit): + model = model_ADMETs[task + "_" + ADMET_model_type + "_model"] + X_pred = utils.data_process( + X_drug=[drug], + y=[0], + drug_encoding=ADMET_model_type, + split_method="no_split", + ) + y_pred = model.predict(X_pred)[0] + + if unit == "%": + y_pred = y_pred * 100 + + return f"{y_pred:.2f} " + unit + + # Initialize research log string + research_log = "Research Log for ADMET Predictions:\n" + research_log += "-------------------------------------\n" + + # Process each SMILES string in the list + for smiles in smiles_list: + research_log += f"\nCompound SMILES: {smiles}\n" + research_log += "Predicted ADMET properties:\n" + + # Physiochemical properties + solubility = ADMET_pred(smiles, "AqSolDB", "log mol/L") + lipophilicity = ADMET_pred(smiles, "Lipo_AZ", "(log-ratio)") + research_log += f"- Solubility: {solubility}\n" + research_log += f"- Lipophilicity: {lipophilicity}\n" + + # Absorption + caco2 = ADMET_pred(smiles, "Caco2", "cm/s") + hia = ADMET_pred(smiles, "HIA", "%") + pgp = ADMET_pred(smiles, "Pgp_inhibitor", "%") + bioavail = ADMET_pred(smiles, "Bioavailability", "%") + research_log += f"- Absorption (Caco-2 permeability): {caco2}\n" + research_log += f"- Absorption (HIA): {hia}\n" + research_log += f"- Absorption (Pgp Inhibitor): {pgp}\n" + research_log += f"- Absorption (Bioavailability): {bioavail}\n" + + # Distribution + bbb = ADMET_pred(smiles, "BBB_MolNet", "%") + ppbr = ADMET_pred(smiles, "PPBR", "%") + research_log += f"- Distribution (BBB permeation): {bbb}\n" + research_log += f"- Distribution (PPBR): {ppbr}\n" + + # Metabolism + cyp2c19 = ADMET_pred(smiles, "CYP2C19", "%") + cyp2d6 = ADMET_pred(smiles, "CYP2D6", "%") + cyp3a4 = ADMET_pred(smiles, "CYP3A4", "%") + cyp1a2 = ADMET_pred(smiles, "CYP1A2", "%") + cyp2c9 = ADMET_pred(smiles, "CYP2C9", "%") + research_log += f"- Metabolism (CYP2C19): {cyp2c19}\n" + research_log += f"- Metabolism (CYP2D6): {cyp2d6}\n" + research_log += f"- Metabolism (CYP3A4): {cyp3a4}\n" + research_log += f"- Metabolism (CYP1A2): {cyp1a2}\n" + research_log += f"- Metabolism (CYP2C9): {cyp2c9}\n" + + # Excretion + half_life = ADMET_pred(smiles, "Half_life_eDrug3D", "h") + clearance = ADMET_pred(smiles, "Clearance_eDrug3D", "mL/min/kg") + research_log += f"- Excretion (Half-life): {half_life}\n" + research_log += f"- Excretion (Clearance): {clearance}\n" + + # Clinical Toxicity + clinical_toxicity = ADMET_pred(smiles, "ClinTox", "%") + research_log += f"- Clinical Toxicity: {clinical_toxicity}\n" + + research_log += "-------------------------------------\n" + + return research_log + + +# Binding Affinity prediction function with model_type validation +def predict_binding_affinity_protein_1d_sequence(smiles_list, amino_acid_sequence, affinity_model_type="MPNN-CNN"): + try: + from DeepPurpose import DTI, utils + except Exception: + subprocess.run([sys.executable, "-m", "pip", "install", "DeepPurpose"], check=False) + from DeepPurpose import DTI, utils + + # Define available model types for Binding Affinity + available_affinity_model_types = [ + "CNN-CNN", + "MPNN-CNN", + "Morgan-CNN", + "Morgan-AAC", + "Daylight-AAC", + ] + + # Check if the provided affinity model type is valid + if affinity_model_type not in available_affinity_model_types: + return f"Error: Invalid affinity model type '{affinity_model_type}'. Available options are: {', '.join(available_affinity_model_types)}." + + # Load the pre-trained affinity model + model_DTI = DTI.model_pretrained(model=affinity_model_type.replace("-", "_") + "_BindingDB") + + # Initialize research log string + research_log = "Research Log for Binding Affinity Predictions:\n" + research_log += "-------------------------------------\n" + + # Process each SMILES string in the list + for smiles in smiles_list: + research_log += f"\nCompound SMILES: {smiles}\n" + research_log += f"Amino Acid Sequence: {amino_acid_sequence}\n" + + # Predict binding affinity + X_pred = utils.data_process( + X_drug=[smiles], + X_target=[amino_acid_sequence], + y=[0], + drug_encoding=affinity_model_type.split("-")[0], + target_encoding=affinity_model_type.split("-")[1], + split_method="no_split", + ) + y_pred = model_DTI.predict(X_pred)[0] + y_pred_nM = 10 ** (-y_pred) / 1e-9 + + research_log += f"Predicted Binding Affinity: {y_pred_nM:.2f} nM\n" + research_log += "-------------------------------------\n" + + return research_log + + +def analyze_accelerated_stability_of_pharmaceutical_formulations(formulations, storage_conditions, time_points): + """Analyzes the stability of pharmaceutical formulations under accelerated storage conditions. + + Parameters + ---------- + formulations : list of dict + List of formulation dictionaries, each containing: + - 'name': str, name of the formulation + - 'active_ingredient': str, name of the active pharmaceutical ingredient + - 'concentration': float, concentration in mg/mL + - 'excipients': list, list of excipients + + storage_conditions : list of dict + List of storage condition dictionaries, each containing: + - 'temperature': float, temperature in °C + - 'humidity': float, relative humidity in percentage (optional for solid dosage forms) + - 'description': str, description of storage condition (e.g., "Room Temperature", "Accelerated") + + time_points : list of int + List of time points in days to evaluate stability + + Returns + ------- + str + Research log summarizing the stability testing process and results + + """ + # Create output directory if it doesn't exist + output_dir = "stability_test_results" + if not os.path.exists(output_dir): + os.makedirs(output_dir) + + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + + # Initialize results storage + all_results = [] + + # Process each formulation under each storage condition + for formulation in formulations: + for condition in storage_conditions: + # Initialize stability parameters + results = [] + + # Get acceleration factor based on temperature (simplified Arrhenius equation) + temp_c = condition["temperature"] + accel_factor = 2 ** ((temp_c - 25) / 10) # Rule of thumb: reaction rate doubles every 10°C + + # Add humidity effect for degradation if provided + humidity_factor = 1.0 + if "humidity" in condition: + humidity = condition["humidity"] + # Simple model: higher humidity increases degradation rate + humidity_factor = 1.0 + (humidity - 60) / 100 if humidity > 60 else 1.0 + + # Calculate stability parameters at each time point + initial_content = 100.0 # Starting at 100% + + for time in time_points: + # Chemical stability (% of initial content) + # Simple first-order degradation model + effective_time = time * accel_factor * humidity_factor + chemical_stability = initial_content * np.exp(-0.001 * effective_time) + + # Physical stability (score from 1-10, 10 being perfect) + # Decreases over time, affected by temperature and humidity + physical_stability = 10 - (0.05 * effective_time) + physical_stability = max(1, physical_stability) # Minimum score of 1 + + # Particle size change (% increase from initial) + # Some formulations show particle growth over time + particle_size_change = ( + 0.2 * effective_time if "solid" in formulation.get("dosage_form", "").lower() else 0 + ) + + results.append( + { + "Formulation": formulation["name"], + "Storage_Condition": condition["description"], + "Temperature_C": temp_c, + "Humidity_RH": condition.get("humidity", "N/A"), + "Time_Days": time, + "Chemical_Stability_Percent": round(chemical_stability, 2), + "Physical_Stability_Score": round(physical_stability, 1), + "Particle_Size_Change_Percent": round(particle_size_change, 2), + } + ) + + all_results.extend(results) + + # Convert results to DataFrame + results_df = pd.DataFrame(all_results) + + # Save results to CSV + csv_filename = f"{output_dir}/stability_results_{timestamp}.csv" + results_df.to_csv(csv_filename, index=False) + + # Generate research log + log = "Accelerated Stability Testing of Pharmaceutical Formulations\n" + log += f"Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n" + + log += "1. STUDY PARAMETERS\n" + log += f" - Number of formulations tested: {len(formulations)}\n" + log += f" - Formulations: {', '.join([f['name'] for f in formulations])}\n" + log += ( + " - Storage conditions: " + + ", ".join( + [ + f"{c['description']} ({c['temperature']}°C" + (f"/{c['humidity']}% RH" if "humidity" in c else "") + ")" + for c in storage_conditions + ] + ) + + "\n" + ) + log += f" - Time points evaluated (days): {', '.join(map(str, time_points))}\n\n" + + log += "2. METHODOLOGY\n" + log += " - Chemical stability assessed by active ingredient content\n" + log += " - Physical stability evaluated on a 10-point scale\n" + log += " - Particle size changes measured where applicable\n\n" + + log += "3. KEY FINDINGS\n" + + # Summarize stability at final time point for each formulation/condition + final_time = max(time_points) + final_results = results_df[results_df["Time_Days"] == final_time] + + for formulation in formulations: + form_results = final_results[final_results["Formulation"] == formulation["name"]] + log += f" {formulation['name']}:\n" + + for _, row in form_results.iterrows(): + condition = row["Storage_Condition"] + chem_stab = row["Chemical_Stability_Percent"] + phys_stab = row["Physical_Stability_Score"] + + stability_assessment = "Stable" + if chem_stab < 90 or phys_stab < 7: + stability_assessment = "Potentially unstable" + if chem_stab < 85 or phys_stab < 5: + stability_assessment = "Unstable" + + log += f" - {condition}: Chemical stability {chem_stab}%, Physical stability score {phys_stab}/10 - {stability_assessment}\n" + log += "\n" + + log += "4. CONCLUSION\n" + + # Identify most stable formulation + best_formulation = "" + best_stability = 0 + + for formulation in formulations: + form_data = final_results[final_results["Formulation"] == formulation["name"]] + avg_chem_stability = form_data["Chemical_Stability_Percent"].mean() + if avg_chem_stability > best_stability: + best_stability = avg_chem_stability + best_formulation = formulation["name"] + + log += f" - Most stable formulation: {best_formulation} (avg. chemical stability: {best_stability:.2f}%)\n" + log += f" - Detailed results saved to: {csv_filename}\n" + + return log + + +def run_3d_chondrogenic_aggregate_assay( + chondrocyte_cells, test_compounds, culture_duration_days=21, measurement_intervals=7 +): + """Generates a detailed protocol for performing a 3D chondrogenic aggregate culture assay to evaluate compounds' effects on chondrogenesis. + + Parameters + ---------- + chondrocyte_cells : dict + Dictionary with cell information including 'source', 'passage_number', and 'cell_density' + test_compounds : list of dict + List of compounds to test, each with 'name', 'concentration', and 'vehicle' keys + culture_duration_days : int + Total duration of the culture period in days (default: 21) + measurement_intervals : int + Interval in days between measurements (default: 7) + + Returns + ------- + str + Detailed protocol document for the 3D chondrogenic aggregate culture assay + + """ + from datetime import datetime + + # Create experiment ID + experiment_id = f"CHOND3D_{datetime.now().strftime('%Y%m%d_%H%M%S')}" + + # Create time points for measurements + timepoints = list(range(0, culture_duration_days + 1, measurement_intervals)) + if timepoints[-1] != culture_duration_days: + timepoints.append(culture_duration_days) + + # Generate the protocol document + protocol = f"# 3D Chondrogenic Aggregate Culture Assay Protocol - {experiment_id}\n\n" + + protocol += "## 1. Materials and Reagents\n\n" + protocol += "- Chondrocyte cells\n" + protocol += "- Chondrogenic differentiation medium\n" + protocol += "- Transforming growth factor-β3 (TGF-β3)\n" + protocol += "- Dexamethasone\n" + protocol += "- Ascorbate-2-phosphate\n" + protocol += "- 96-well V-bottom plates\n" + protocol += "- Gaussia luciferase reporter assay kit\n" + protocol += "- Luminometer\n" + protocol += "- Test compounds with respective vehicles\n" + protocol += "- Centrifuge\n" + protocol += "- CO2 incubator\n" + protocol += "- Sterile pipettes and tips\n\n" + + protocol += "## 2. Experimental Information\n\n" + protocol += "### Cell Information:\n" + protocol += f"- Cell source: {chondrocyte_cells['source']}\n" + protocol += f"- Passage number: {chondrocyte_cells['passage_number']}\n" + protocol += f"- Cell density: {chondrocyte_cells['cell_density']} cells/mL\n\n" + + protocol += "### Experimental Design:\n" + protocol += f"- Culture duration: {culture_duration_days} days\n" + protocol += f"- Measurement timepoints: {', '.join(map(str, timepoints))} days\n\n" + + protocol += "### Test Compounds:\n" + for i, compound in enumerate(test_compounds): + protocol += f"- Compound {i + 1}: {compound['name']} at {compound['concentration']} in {compound['vehicle']}\n" + protocol += "- Control: Vehicle only\n\n" + + protocol += "## 3. Detailed Procedure\n\n" + protocol += "### Day 0: Setup\n\n" + protocol += "1. Prepare chondrogenic differentiation medium containing:\n" + protocol += " - High-glucose DMEM\n" + protocol += " - 10 ng/mL TGF-β3\n" + protocol += " - 100 nM Dexamethasone\n" + protocol += " - 50 μg/mL Ascorbate-2-phosphate\n" + protocol += " - 1% ITS+ premix (insulin, transferrin, selenium)\n" + protocol += " - 1 mM Sodium pyruvate\n" + protocol += " - 100 U/mL Penicillin/Streptomycin\n\n" + + protocol += "2. Harvest and count chondrocyte cells\n\n" + + protocol += "3. Prepare cell suspension at the specified density:\n" + protocol += f" - {chondrocyte_cells['cell_density']} cells/mL\n\n" + + protocol += "4. Form 3D cell aggregates:\n" + protocol += " - Aliquot 2.5×10^5 cells per well in 96-well V-bottom plates\n" + protocol += " - Centrifuge plates at 500g for 5 minutes to pellet cells\n\n" + + protocol += "5. Add test compounds to respective wells:\n" + for _, compound in enumerate(test_compounds): + protocol += f" - Add {compound['name']} at {compound['concentration']} in {compound['vehicle']}\n" + protocol += " - Add vehicle only to control wells\n\n" + + protocol += "6. Incubate the plates at 37°C, 5% CO2\n\n" + + protocol += "### Day 1 to Day " + str(culture_duration_days) + ":\n\n" + protocol += "1. Change medium every 2-3 days:\n" + protocol += " - Carefully remove 50% of the medium without disturbing the aggregates\n" + protocol += " - Replace with fresh medium containing test compounds at the same concentrations\n\n" + + protocol += f"2. At days {', '.join(map(str, timepoints))}, collect samples for analysis:\n" + protocol += ( + " - Take medium samples for Gaussia luciferase activity measurement (if using COL2A1-GLuc reporter cells)\n" + ) + protocol += " - Fix aggregates in 4% paraformaldehyde for histological analysis\n\n" + + return protocol + + +def grade_adverse_events_using_vcog_ctcae(clinical_data_file): + """Grade and monitor adverse events in animal studies using the VCOG-CTCAE standard. + + Parameters + ---------- + clinical_data_file : str + Path to a CSV file containing clinical evaluation data with columns: + subject_id, time_point, symptom, severity, measurement (optional) + + Returns + ------- + str + A research log summarizing the adverse event grading process and results. + The graded events are saved to 'vcog_ctcae_graded_events.csv'. + + """ + import json + from datetime import datetime + + import pandas as pd + + # Initialize the research log + log = "# Adverse Event Grading using VCOG-CTCAE v1.1\n" + log += f"Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n" + + # Step 1: Load the clinical data + log += "## Step 1: Loading clinical evaluation data\n" + try: + data = pd.read_csv(clinical_data_file) + log += f"Successfully loaded data from {clinical_data_file}\n" + log += f"Total records: {len(data)}\n" + log += f"Columns found: {', '.join(data.columns)}\n\n" + except Exception as e: + log += f"Error loading data: {str(e)}\n" + return log + + # Step 2: Define VCOG-CTCAE grading criteria + log += "## Step 2: Applying VCOG-CTCAE grading criteria\n" + + # Comprehensive VCOG-CTCAE grading criteria based on VCOG-CTCAE v1.1 + vcog_criteria = { + # Hematologic + "neutropenia": { + "description": "Neutrophil count decrease", + "unit": "cells/µL", + "grades": { + 0: {"criteria": "≥ 1500", "range": [1500, float("inf")]}, + 1: {"criteria": "1000 - <1500", "range": [1000, 1500]}, + 2: {"criteria": "500 - <1000", "range": [500, 1000]}, + 3: {"criteria": "100 - <500", "range": [100, 500]}, + 4: {"criteria": "<100", "range": [0, 100]}, + 5: {"criteria": "Death due to neutropenic sepsis", "range": None}, + }, + }, + "anemia": { + "description": "Hemoglobin decrease", + "unit": "g/dL", + "grades": { + 0: {"criteria": "Within reference range", "range": [10, float("inf")]}, + 1: {"criteria": "Mild; clinical signs not present", "range": [8, 10]}, + 2: {"criteria": "Moderate; clinical signs present", "range": [6.5, 8]}, + 3: {"criteria": "Severe; transfusion indicated", "range": [5, 6.5]}, + 4: {"criteria": "Life-threatening", "range": [0, 5]}, + 5: {"criteria": "Death", "range": None}, + }, + }, + "thrombocytopenia": { + "description": "Platelet count decrease", + "unit": "cells/µL", + "grades": { + 0: {"criteria": "≥ 100,000", "range": [100000, float("inf")]}, + 1: {"criteria": "50,000 - <100,000", "range": [50000, 100000]}, + 2: {"criteria": "25,000 - <50,000", "range": [25000, 50000]}, + 3: {"criteria": "10,000 - <25,000", "range": [10000, 25000]}, + 4: {"criteria": "<10,000 or spontaneous bleeding", "range": [0, 10000]}, + 5: {"criteria": "Death", "range": None}, + }, + }, + # Gastrointestinal + "vomiting": { + "description": "Vomiting frequency", + "unit": "episodes per 24h period", + "grades": { + 0: {"criteria": "None", "range": [0, 0]}, + 1: { + "criteria": "1-2 episodes in 24h; medical intervention not indicated", + "range": [1, 2], + }, + 2: {"criteria": "3-5 episodes in 24h; ≤3 days", "range": [3, 5]}, + 3: { + "criteria": ">5 episodes in 24h; >3 days; hospitalization indicated", + "range": [6, float("inf")], + }, + 4: {"criteria": "Life-threatening consequences", "range": None}, + 5: {"criteria": "Death", "range": None}, + }, + }, + "diarrhea": { + "description": "Diarrhea frequency", + "unit": "episodes per 24h period", + "grades": { + 0: {"criteria": "None", "range": [0, 0]}, + 1: { + "criteria": "Increase of <4 stools per day over baseline", + "range": [1, 3], + }, + 2: { + "criteria": "Increase of 4-6 stools per day over baseline", + "range": [4, 6], + }, + 3: { + "criteria": "Increase of ≥7 stools per day; hospitalization indicated", + "range": [7, float("inf")], + }, + 4: {"criteria": "Life-threatening consequences", "range": None}, + 5: {"criteria": "Death", "range": None}, + }, + }, + "anorexia": { + "description": "Appetite/food intake decrease", + "unit": "percent of normal intake", + "grades": { + 0: {"criteria": "Normal", "range": [100, float("inf")]}, + 1: {"criteria": "Decreased appetite, but eating", "range": [75, 100]}, + 2: {"criteria": "Decreased intake <3 days", "range": [50, 75]}, + 3: {"criteria": "Decreased intake ≥3 days", "range": [25, 50]}, + 4: { + "criteria": "Life-threatening consequences; urgent intervention indicated", + "range": [0, 25], + }, + 5: {"criteria": "Death", "range": None}, + }, + }, + # Hepatic + "alt_increase": { + "description": "Alanine aminotransferase increased", + "unit": "x ULN (upper limit of normal)", + "grades": { + 0: {"criteria": "≤ ULN", "range": [0, 1]}, + 1: {"criteria": ">ULN - 2.5xULN", "range": [1, 2.5]}, + 2: {"criteria": ">2.5 - 5.0xULN", "range": [2.5, 5]}, + 3: {"criteria": ">5.0 - 20.0xULN", "range": [5, 20]}, + 4: {"criteria": ">20.0xULN", "range": [20, float("inf")]}, + 5: {"criteria": "Death", "range": None}, + }, + }, + # Renal + "creatinine_increase": { + "description": "Creatinine increased", + "unit": "x ULN", + "grades": { + 0: {"criteria": "≤ ULN", "range": [0, 1]}, + 1: {"criteria": ">ULN - 1.5xULN", "range": [1, 1.5]}, + 2: {"criteria": ">1.5 - 3.0xULN", "range": [1.5, 3]}, + 3: {"criteria": ">3.0 - 6.0xULN", "range": [3, 6]}, + 4: {"criteria": ">6.0xULN", "range": [6, float("inf")]}, + 5: {"criteria": "Death", "range": None}, + }, + }, + # Constitutional + "fever": { + "description": "Fever", + "unit": "°C", + "grades": { + 0: {"criteria": "None", "range": [0, 39]}, + 1: {"criteria": "39.0 - 39.5°C", "range": [39, 39.5]}, + 2: {"criteria": ">39.5 - 40.0°C", "range": [39.5, 40]}, + 3: {"criteria": ">40.0 - 41.0°C", "range": [40, 41]}, + 4: {"criteria": ">41.0°C for >24 hrs", "range": [41, float("inf")]}, + 5: {"criteria": "Death", "range": None}, + }, + }, + "weight_loss": { + "description": "Weight loss", + "unit": "percent of baseline weight", + "grades": { + 0: {"criteria": "<5%", "range": [0, 5]}, + 1: {"criteria": "5% - <10%", "range": [5, 10]}, + 2: {"criteria": "10% - <20%", "range": [10, 20]}, + 3: {"criteria": "≥20%", "range": [20, float("inf")]}, + 4: {"criteria": "Life-threatening", "range": None}, + 5: {"criteria": "Death", "range": None}, + }, + }, + # Dermatologic + "alopecia": { + "description": "Hair loss", + "unit": None, + "grades": { + 0: {"criteria": "None", "range": None}, + 1: {"criteria": "Hair loss at injection/treatment site", "range": None}, + 2: {"criteria": "Moderate alopecia", "range": None}, + 3: {"criteria": "Complete alopecia", "range": None}, + 4: {"criteria": "Not applicable", "range": None}, + 5: {"criteria": "Not applicable", "range": None}, + }, + }, + # Neurologic + "neuropathy": { + "description": "Peripheral neuropathy", + "unit": None, + "grades": { + 0: {"criteria": "None", "range": None}, + 1: { + "criteria": "Asymptomatic; clinically detectable on examination", + "range": None, + }, + 2: { + "criteria": "Mild symptoms; limiting instrumental ADL", + "range": None, + }, + 3: { + "criteria": "Severe symptoms; limiting self-care ADL", + "range": None, + }, + 4: {"criteria": "Life-threatening consequences", "range": None}, + 5: {"criteria": "Death", "range": None}, + }, + }, + } + + def apply_vcog_grade(symptom, severity, measurement=None): + """Apply VCOG-CTCAE grading criteria to an adverse event. + + Parameters + ---------- + symptom : str + The type of adverse event + severity : str + The severity description + measurement : float or None + Quantitative measurement related to the symptom, if available + + Returns + ------- + int + The VCOG-CTCAE grade (0-5) + str + Description of the grading rationale + + """ + # Standard severity-based grading if no specific criteria exist + grade_map = { + "none": 0, + "mild": 1, + "moderate": 2, + "severe": 3, + "life-threatening": 4, + "death": 5, + } + + symptom_lower = symptom.lower() + + # Check if the symptom has specific VCOG-CTCAE criteria + if symptom_lower in vcog_criteria: + criteria = vcog_criteria[symptom_lower] + + # If measurement is provided and criteria has numeric ranges + if measurement is not None: + try: + measurement_value = float(measurement) + + # Find the appropriate grade based on the measurement ranges + for grade, grade_info in criteria["grades"].items(): + if grade_info["range"] is not None: + min_val, max_val = grade_info["range"] + if min_val <= measurement_value < max_val: + return ( + grade, + f"Grade {grade}: {criteria['description']} - {criteria['grades'][grade]['criteria']}", + ) + except (ValueError, TypeError): + # If measurement can't be converted to float, fall back to severity-based grading + pass + + # If there's a reported severity with no valid measurement + if severity.lower() in grade_map: + # Check if the grade exists in the criteria + severity_grade = grade_map[severity.lower()] + if severity_grade in criteria["grades"]: + return ( + severity_grade, + f"Grade {severity_grade}: {criteria['description']} - {criteria['grades'][severity_grade]['criteria']}", + ) + + # Default to using the severity mapping if no specific criteria match + if severity.lower() in grade_map: + return grade_map[severity.lower()], f"Grade {grade_map[severity.lower()]}: Based on reported severity" + + # Default grade if no specific criteria match + return 1, "Grade 1: Default grade (specific criteria not found)" + + # Step 3: Apply grading to each record + log += "Applying VCOG-CTCAE v1.1 grading criteria to each adverse event...\n" + + # Create new columns for the grade and rationale + grading_results = data.apply( + lambda row: apply_vcog_grade( + row["symptom"], + row["severity"], + row["measurement"] if "measurement" in data.columns else None, + ), + axis=1, + ) + + # Split the returned tuples into separate columns + data["vcog_grade"] = [result[0] for result in grading_results] + data["grading_rationale"] = [result[1] for result in grading_results] + + # Step 4: Analyze patterns across time points (if available) + if "time_point" in data.columns: + log += "\n## Step 3: Analyzing adverse event patterns across time points\n" + + # Group by subject and symptom to track progression + progression_analysis = data.pivot_table( + index=["subject_id", "symptom"], + columns="time_point", + values="vcog_grade", + aggfunc="max", + ).reset_index() + + # Calculate if grade is increasing, decreasing, or stable for each subject-symptom pair + trend_counts = {"increasing": 0, "decreasing": 0, "stable": 0, "fluctuating": 0} + + numeric_columns = [col for col in progression_analysis.columns if col not in ["subject_id", "symptom"]] + + if len(numeric_columns) >= 2: + # Sort columns to ensure chronological order + numeric_columns.sort() + + for _, row in progression_analysis.iterrows(): + values = [row[col] for col in numeric_columns if not pd.isna(row[col])] + if len(values) >= 2: + if all(values[i] < values[i + 1] for i in range(len(values) - 1)): + trend_counts["increasing"] += 1 + elif all(values[i] > values[i + 1] for i in range(len(values) - 1)): + trend_counts["decreasing"] += 1 + elif all(values[i] == values[i + 1] for i in range(len(values) - 1)): + trend_counts["stable"] += 1 + else: + trend_counts["fluctuating"] += 1 + + log += "Adverse event progression patterns:\n" + for trend, count in trend_counts.items(): + log += f"- {trend.capitalize()}: {count} subject-symptom pairs\n" + + # Save progression analysis + progression_file = "vcog_ctcae_progression_analysis.csv" + progression_analysis.to_csv(progression_file) + log += f"\nDetailed progression analysis saved to: {progression_file}\n" + + # Step 5: Summarize the grading results + log += "\n## Step 4: Summarizing adverse event grades\n" + + # Count events by grade + grade_counts = data["vcog_grade"].value_counts().sort_index() + log += "Grade distribution:\n" + for grade, count in grade_counts.items(): + log += f"- Grade {grade}: {count} events\n" + + # Summarize by symptom type + symptom_summary = data.groupby("symptom")["vcog_grade"].agg(["max", "mean", "count"]) + log += "\nSymptom severity summary:\n" + for symptom, stats in symptom_summary.iterrows(): + log += f"- {symptom}: max grade = {stats['max']}, avg grade = {stats['mean']:.2f}, count = {stats['count']}\n" + + # Summarize by subject + subject_summary = data.groupby("subject_id")["vcog_grade"].agg(["max", "mean", "count"]) + log += f"\nSubjects with adverse events: {len(subject_summary)}\n" + log += f"Subjects with Grade 3+ events: {len(subject_summary[subject_summary['max'] >= 3])}\n" + + # Create a summary of most severe events + most_severe = data.sort_values("vcog_grade", ascending=False).head(10) + log += "\nTop 10 most severe adverse events:\n" + for i, (_, event) in enumerate(most_severe.iterrows(), 1): + log += f"{i}. Subject {event['subject_id']}: {event['symptom']} (Grade {event['vcog_grade']})\n" + + # Step 6: Save detailed results to file + output_file = "vcog_ctcae_graded_events.csv" + data.to_csv(output_file, index=False) + log += "\n## Step 5: Results saved\n" + log += f"Detailed graded events saved to: {output_file}\n" + + # Save the VCOG criteria as a reference + with open("vcog_ctcae_criteria_reference.json", "w") as f: + json.dump(vcog_criteria, f, indent=2) + log += "VCOG-CTCAE criteria reference saved to: vcog_ctcae_criteria_reference.json\n" + + return log + + +def analyze_radiolabeled_antibody_biodistribution(time_points, tissue_data): + """Analyze biodistribution and pharmacokinetic profile of radiolabeled antibodies. + + Parameters + ---------- + time_points : list or numpy.ndarray + Time points (hours) at which measurements were taken + tissue_data : dict + Dictionary where keys are tissue names and values are lists/arrays of %IA/g + measurements corresponding to time_points. Must include 'tumor' as one of the keys. + + Returns + ------- + str + Research log summarizing the biodistribution analysis, pharmacokinetic parameters, + and tumor-to-normal tissue ratios + + """ + import json + import os + + import numpy as np + from scipy.optimize import curve_fit + + # Validate inputs + if "tumor" not in tissue_data: + return "Error: Tumor data must be provided in tissue_data dictionary" + + # Define bi-exponential model for pharmacokinetic analysis + # C(t) = A*exp(-alpha*t) + B*exp(-beta*t) + def bi_exp_model(t, A, alpha, B, beta): + return A * np.exp(-alpha * t) + B * np.exp(-beta * t) + + # Initialize results dictionary + results = { + "tissues_analyzed": list(tissue_data.keys()), + "pk_parameters": {}, + "tumor_to_normal_ratios": {}, + "auc_values": {}, + } + + # Analyze each tissue + for tissue, measurements in tissue_data.items(): + try: + # Fit bi-exponential model + params, _ = curve_fit( + bi_exp_model, + time_points, + measurements, + p0=[50, 0.1, 50, 0.01], # Initial parameter guess + bounds=([0, 0, 0, 0], [100, 5, 100, 1]), # Parameter bounds + ) + + A, alpha, B, beta = params + + # Calculate pharmacokinetic parameters + # Distribution half-life (fast component) + t_half_dist = np.log(2) / alpha + + # Elimination half-life (slow component) + t_half_elim = np.log(2) / beta + + # Area under the curve (AUC) + auc = A / alpha + B / beta + + # Mean residence time (MRT) + mrt = (A / (alpha**2) + B / (beta**2)) / auc + + # Clearance (for blood/plasma only - conceptual) + clearance = 1 / auc if tissue.lower() in ["blood", "plasma"] else None + + # Store results + results["pk_parameters"][tissue] = { + "A": float(A), + "alpha": float(alpha), + "B": float(B), + "beta": float(beta), + "distribution_half_life_h": float(t_half_dist), + "elimination_half_life_h": float(t_half_elim), + "mean_residence_time_h": float(mrt), + } + + if clearance: + results["pk_parameters"][tissue]["clearance"] = float(clearance) + + # Calculate AUC + results["auc_values"][tissue] = float(auc) + + except Exception as e: + results["pk_parameters"][tissue] = f"Fitting failed: {str(e)}" + + # Calculate tumor-to-normal tissue ratios at each time point + for tissue in tissue_data: + if tissue != "tumor": + ratios = [ + t / n if n > 0 else float("inf") + for t, n in zip(tissue_data["tumor"], tissue_data[tissue], strict=False) + ] + results["tumor_to_normal_ratios"][tissue] = { + "values": [float(r) for r in ratios], + "max_ratio": float(max(ratios)), + "max_ratio_time_point": float(time_points[np.argmax(ratios)]), + } + + # Save results to JSON file + filename = "biodistribution_pk_results.json" + with open(filename, "w") as f: + json.dump(results, f, indent=2) + + # Generate research log + log = "# Biodistribution and Pharmacokinetic Analysis of Radiolabeled Antibody\n\n" + log += "## Analysis Summary\n" + log += f"- Analyzed biodistribution data across {len(tissue_data)} tissues\n" + log += f"- Time points analyzed: {time_points} hours\n" + log += "- Performed bi-exponential pharmacokinetic modeling\n\n" + + log += "## Key Pharmacokinetic Parameters\n" + for tissue, params in results["pk_parameters"].items(): + if isinstance(params, dict): + log += f"\n### {tissue.capitalize()}\n" + log += f"- Distribution half-life: {params['distribution_half_life_h']:.2f} hours\n" + log += f"- Elimination half-life: {params['elimination_half_life_h']:.2f} hours\n" + log += f"- Mean residence time: {params['mean_residence_time_h']:.2f} hours\n" + if "clearance" in params: + log += f"- Clearance: {params['clearance']:.4f} units\n" + + log += "\n## Tumor-to-Normal Tissue Ratios\n" + for tissue, ratio_data in results["tumor_to_normal_ratios"].items(): + log += f"- {tissue.capitalize()}: Max ratio {ratio_data['max_ratio']:.2f} at {ratio_data['max_ratio_time_point']:.1f} hours\n" + + log += "\n## Detailed Results\n" + log += f"Complete analysis results saved to: {os.path.abspath(filename)}\n" + + return log + + +def estimate_alpha_particle_radiotherapy_dosimetry( + biodistribution_data, radiation_parameters, output_file="dosimetry_results.csv" +): + """Estimate radiation absorbed doses to tumor and normal organs for alpha-particle radiotherapeutics. + + This function implements the Medical Internal Radiation Dose (MIRD) schema to calculate + absorbed doses based on biodistribution data from healthy mice and radiation transport parameters. + + Parameters + ---------- + biodistribution_data : dict + Dictionary containing organ/tissue names as keys and a list of time-activity measurements as values. + Each measurement should be a tuple of (time_hours, percent_injected_activity). + Must include entries for all relevant organs including 'tumor'. + + radiation_parameters : dict + Dictionary containing radiation parameters for the alpha-emitting radionuclide: + - 'radionuclide': str - Name of the radionuclide (e.g., 'Ac-225') + - 'half_life_hours': float - Physical half-life in hours + - 'energy_per_decay_MeV': float - Energy released per decay in MeV + - 'radiation_weighting_factor': float - Radiation weighting factor for alpha particles + - 'S_factors': dict - S-factors (Gy/Bq-s) for each source-target organ pair + + output_file : str, optional + Filename to save the dosimetry results (default: "dosimetry_results.csv") + + Returns + ------- + str + Research log summarizing the dosimetry estimation process and results + + """ + import csv + from datetime import datetime + + import numpy as np + from scipy.integrate import trapezoid + + # Initialize research log + log = f"Alpha-Particle Radiotherapy Dosimetry Estimation - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n" + log += f"Radionuclide: {radiation_parameters['radionuclide']}\n" + log += f"Half-life: {radiation_parameters['half_life_hours']} hours\n\n" + + # Step 1: Calculate time-integrated activity for each organ + log += "Step 1: Calculating time-integrated activity for each organ\n" + time_integrated_activity = {} + + for organ, measurements in biodistribution_data.items(): + times = [m[0] for m in measurements] + activities = [m[1] for m in measurements] + + # Apply physical decay correction + decay_constant = np.log(2) / radiation_parameters["half_life_hours"] + decay_corrected_activities = [a * np.exp(-decay_constant * t) for a, t in zip(activities, times, strict=False)] + + # Calculate time-integrated activity using trapezoidal integration + cumulated_activity = trapezoid(decay_corrected_activities, times) + time_integrated_activity[organ] = cumulated_activity + + log += f" - {organ}: {cumulated_activity:.4f} %IA-h\n" + + # Step 2: Calculate absorbed dose using MIRD schema + log += "\nStep 2: Calculating absorbed doses using MIRD schema\n" + + # Convert %IA-h to MBq-h for a standard injection of 1 MBq + conversion_factor = 0.01 # Convert %IA to fraction of IA + + # Energy conversion factor: MeV to J + + # Calculate absorbed dose for each target organ + absorbed_doses = {} + s_factors = radiation_parameters["S_factors"] + + for target_organ in biodistribution_data: + absorbed_dose = 0 + + # Sum contributions from all source organs + for source_organ, cumulated_activity in time_integrated_activity.items(): + if (source_organ, target_organ) in s_factors: + s_value = s_factors[(source_organ, target_organ)] + organ_contribution = cumulated_activity * conversion_factor * s_value + absorbed_dose += organ_contribution + + # Apply radiation weighting factor for alpha particles + absorbed_dose *= radiation_parameters["radiation_weighting_factor"] + + # Store as Gy/MBq + absorbed_doses[target_organ] = absorbed_dose + log += f" - {target_organ}: {absorbed_dose:.4f} Gy/MBq\n" + + # Step 3: Calculate therapeutic index (tumor-to-normal tissue dose ratios) + log += "\nStep 3: Calculating therapeutic indices (tumor-to-normal tissue ratios)\n" + + tumor_dose = absorbed_doses.get("tumor", 0) + if tumor_dose > 0: + for organ, dose in absorbed_doses.items(): + if organ != "tumor" and dose > 0: + therapeutic_index = tumor_dose / dose + log += f" - Tumor-to-{organ} ratio: {therapeutic_index:.2f}\n" + + # Save results to CSV file + with open(output_file, "w", newline="") as f: + writer = csv.writer(f) + writer.writerow(["Organ", "Absorbed Dose (Gy/MBq)"]) + for organ, dose in absorbed_doses.items(): + writer.writerow([organ, f"{dose:.4f}"]) + + log += f"\nDosimetry results saved to {output_file}\n" + + return log + + +def perform_mwas_cyp2c19_metabolizer_status( + methylation_data_path, + metabolizer_status_path, + covariates_path=None, + pvalue_threshold=0.05, + output_file="significant_cpg_sites.csv", +): + """Perform a Methylome-wide Association Study (MWAS) to identify CpG sites significantly associated with CYP2C19 metabolizer status. + + Parameters + ---------- + methylation_data_path : str + Path to CSV or TSV file containing DNA methylation beta values. + Rows should be samples, columns should be CpG sites. + metabolizer_status_path : str + Path to CSV or TSV file containing CYP2C19 metabolizer status for each sample. + Should have a sample ID column and a status column (e.g., poor, intermediate, normal, rapid, ultrarapid). + covariates_path : str, optional + Path to CSV or TSV file containing covariates to adjust for in the regression model + (e.g., age, sex, smoking status). + pvalue_threshold : float, optional + P-value threshold for significance after multiple testing correction. Default is 0.05. + output_file : str, optional + Filename to save significant CpG sites. Default is "significant_cpg_sites.csv". + + Returns + ------- + str + A research log summarizing the MWAS analysis and results. + + """ + import time + + import pandas as pd + from scipy.stats import linregress + from statsmodels.formula.api import ols + + start_time = time.time() + log = ["## Methylome-wide Association Study (MWAS) of CYP2C19 Metabolizer Status"] + + # Load data from files + log.append("\n### Loading Data") + try: + # Load methylation data + if methylation_data_path.endswith(".csv"): + methylation_data = pd.read_csv(methylation_data_path, index_col=0) + elif methylation_data_path.endswith((".tsv", ".txt")): + methylation_data = pd.read_csv(methylation_data_path, sep="\t", index_col=0) + else: + log.append("Error: Unsupported file format for methylation data. Please provide a CSV or TSV file.") + return "\n".join(log) + log.append(f"- Successfully loaded methylation data from {methylation_data_path}") + + # Load metabolizer status data + if metabolizer_status_path.endswith(".csv"): + metabolizer_status_df = pd.read_csv(metabolizer_status_path, index_col=0) + elif metabolizer_status_path.endswith((".tsv", ".txt")): + metabolizer_status_df = pd.read_csv(metabolizer_status_path, sep="\t", index_col=0) + else: + log.append("Error: Unsupported file format for metabolizer status. Please provide a CSV or TSV file.") + return "\n".join(log) + log.append(f"- Successfully loaded metabolizer status data from {metabolizer_status_path}") + + # Convert DataFrame to Series if necessary + if metabolizer_status_df.shape[1] == 1: + metabolizer_status = metabolizer_status_df.iloc[:, 0] + else: + log.append("Error: Metabolizer status file should contain a single column with status values.") + return "\n".join(log) + + # Load covariates if provided + covariates = None + if covariates_path is not None: + if covariates_path.endswith(".csv"): + covariates = pd.read_csv(covariates_path, index_col=0) + elif covariates_path.endswith((".tsv", ".txt")): + covariates = pd.read_csv(covariates_path, sep="\t", index_col=0) + else: + log.append("Error: Unsupported file format for covariates. Please provide a CSV or TSV file.") + return "\n".join(log) + log.append(f"- Successfully loaded covariates data from {covariates_path}") + except Exception as e: + log.append(f"Error loading data: {str(e)}") + return "\n".join(log) + + # Step 1: Data preprocessing + log.append("\n### Data Preprocessing") + log.append(f"- Methylation data shape: {methylation_data.shape} (samples × CpG sites)") + log.append(f"- Number of samples with metabolizer status: {len(metabolizer_status)}") + + # Ensure sample IDs match between methylation data and metabolizer status + common_samples = methylation_data.index.intersection(metabolizer_status.index) + methylation_data = methylation_data.loc[common_samples] + metabolizer_status = metabolizer_status.loc[common_samples] + + log.append(f"- Number of samples after matching: {len(common_samples)}") + + # Check for covariates + if covariates is not None: + log.append(f"- Covariates provided: {', '.join(covariates.columns)}") + covariates = covariates.loc[common_samples] + + # Step 2: Perform regression for each CpG site + log.append("\n### Association Analysis") + log.append(f"- Total CpG sites to analyze: {methylation_data.shape[1]}") + + results = [] + cpg_sites = methylation_data.columns + + # Convert metabolizer status to numeric if it's categorical + if metabolizer_status.dtype == "object": + # Create a mapping dictionary for metabolizer status + # Assuming order: poor < intermediate < normal < rapid < ultrarapid + status_order = { + "poor": 1, + "intermediate": 2, + "normal": 3, + "rapid": 4, + "ultrarapid": 5, + } + + # Try to map using the dictionary, or keep as is if already numeric + try: + metabolizer_status_numeric = metabolizer_status.map(status_order) + log.append("- Converted metabolizer status to numeric values") + except Exception: + metabolizer_status_numeric = metabolizer_status + log.append("- Using metabolizer status as provided (assuming numeric)") + else: + metabolizer_status_numeric = metabolizer_status + + # Perform regression for each CpG site + for cpg in cpg_sites: + methylation_values = methylation_data[cpg] + + # Basic model without covariates + if covariates is None: + model = linregress(metabolizer_status_numeric, methylation_values) + pvalue = model.pvalue + coefficient = model.slope + else: + # Create DataFrame for regression with covariates + data_for_regression = pd.DataFrame( + { + "methylation": methylation_values, + "metabolizer": metabolizer_status_numeric, + } + ) + + # Add covariates + for col in covariates.columns: + data_for_regression[col] = covariates[col] + + # Formula for regression with covariates + formula = "methylation ~ metabolizer + " + " + ".join(covariates.columns) + model = ols(formula, data=data_for_regression).fit() + + pvalue = model.pvalues["metabolizer"] + coefficient = model.params["metabolizer"] + + results.append({"CpG_site": cpg, "coefficient": coefficient, "pvalue": pvalue}) + + # Convert results to DataFrame + results_df = pd.DataFrame(results) + + # Step 3: Multiple testing correction + log.append("\n### Multiple Testing Correction") + log.append("- Applying Bonferroni correction") + + # Bonferroni correction + results_df["adjusted_pvalue"] = results_df["pvalue"] * len(results_df) + results_df["adjusted_pvalue"] = results_df["adjusted_pvalue"].clip(upper=1.0) # Ensure p-values don't exceed 1 + + # Step 4: Identify significant CpG sites + significant_sites = results_df[results_df["adjusted_pvalue"] < pvalue_threshold] + significant_sites = significant_sites.sort_values("adjusted_pvalue") + + log.append("\n### Results") + log.append(f"- Number of significant CpG sites (adjusted p < {pvalue_threshold}): {len(significant_sites)}") + + if len(significant_sites) > 0: + # Save significant sites to file + significant_sites.to_csv(output_file, index=False) + log.append("- Top 5 significant CpG sites:") + + for _, row in significant_sites.head(5).iterrows(): + log.append( + f" * {row['CpG_site']}: coefficient = {row['coefficient']:.4f}, adj. p-value = {row['adjusted_pvalue']:.6f}" + ) + + log.append(f"- Full results saved to: {output_file}") + else: + log.append("- No significant CpG sites found after multiple testing correction") + + # Execution time + execution_time = time.time() - start_time + log.append("\n### Summary") + log.append(f"- Analysis completed in {execution_time:.2f} seconds") + + return "\n".join(log) + + +def calculate_physicochemical_properties(smiles_string): + """Calculate key physicochemical properties of a drug candidate molecule. + + Parameters + ---------- + smiles_string : str + The molecular structure in SMILES format + + Returns + ------- + str + A research log summarizing the calculated physicochemical properties and + indicating where the detailed results are saved + + """ + import csv + import os + + from rdkit import Chem + from rdkit.Chem import Crippen, Descriptors, Lipinski + from rdkit.Chem.MolStandardize import rdMolStandardize + + # Create RDKit molecule from SMILES + try: + mol = Chem.MolFromSmiles(smiles_string) + if mol is None: + return "ERROR: Invalid SMILES string provided." + except Exception as e: + return f"ERROR: Failed to process SMILES string: {str(e)}" + + # Calculate basic properties + properties = { + "SMILES": smiles_string, + "Molecular Weight": round(Descriptors.MolWt(mol), 2), + "cLogP": round(Descriptors.MolLogP(mol), 2), + "TPSA": round(Descriptors.TPSA(mol), 2), + "H-Bond Donors": Lipinski.NumHDonors(mol), + "H-Bond Acceptors": Lipinski.NumHAcceptors(mol), + "Rotatable Bonds": Descriptors.NumRotatableBonds(mol), + "Heavy Atoms": mol.GetNumHeavyAtoms(), + "Ring Count": Descriptors.RingCount(mol), + } + + # Estimate pKa (simplified approach - in practice would use specialized tools) + # This is a simplification as accurate pKa prediction requires specialized tools + uncharger = rdMolStandardize.Uncharger() + uncharger.uncharge(mol) + acidic_groups = sum( + 1 + for atom in mol.GetAtoms() + if atom.GetSymbol() == "O" + and any(neigh.GetSymbol() == "C" and neigh.GetDegree() == 3 for neigh in atom.GetNeighbors()) + ) + basic_groups = sum(1 for atom in mol.GetAtoms() if atom.GetSymbol() == "N" and atom.GetDegree() < 4) + properties["Estimated Acidic Groups"] = acidic_groups + properties["Estimated Basic Groups"] = basic_groups + + # Calculate drug-likeness score (using Crippen approach) + properties["Drug-likeness Score"] = round(Crippen.MolMR(mol), 2) + + # Calculate logD (simplified as logP - pKa adjustment would need specialized tools) + properties["Estimated logD7.4"] = properties["cLogP"] + + # Save results to CSV + csv_filename = "physicochemical_properties.csv" + with open(csv_filename, "w", newline="") as csvfile: + writer = csv.writer(csvfile) + writer.writerow(["Property", "Value"]) + for prop, value in properties.items(): + writer.writerow([prop, value]) + + # Generate research log + log = f"""Physicochemical Property Calculation Research Log: + +Analyzed compound with SMILES: {smiles_string} + +Key properties: +- Molecular Weight: {properties["Molecular Weight"]} g/mol +- cLogP: {properties["cLogP"]} +- Topological Polar Surface Area: {properties["TPSA"]} Ų +- H-Bond Donors: {properties["H-Bond Donors"]} +- H-Bond Acceptors: {properties["H-Bond Acceptors"]} +- Rotatable Bonds: {properties["Rotatable Bonds"]} +- Estimated logD (at pH 7.4): {properties["Estimated logD7.4"]} +- Estimated Acidic Groups: {properties["Estimated Acidic Groups"]} +- Estimated Basic Groups: {properties["Estimated Basic Groups"]} + +Complete results saved to: {os.path.abspath(csv_filename)} +""" + + return log + + +def analyze_xenograft_tumor_growth_inhibition( + data_path, + time_column, + volume_column, + group_column, + subject_column, + output_dir="./results", +): + """Analyze tumor growth inhibition in xenograft models across different treatment groups. + + Parameters + ---------- + data_path : str + Path to CSV or TSV file containing tumor volume measurements. The file should have columns for + time, volume, treatment group, and subject ID + time_column : str + Name of the column containing time points (e.g., 'Day', 'Time') + volume_column : str + Name of the column containing tumor volume measurements + group_column : str + Name of the column containing treatment group labels + subject_column : str + Name of the column containing subject/mouse identifiers + output_dir : str, optional + Directory to save output files (default: "./results") + + Returns + ------- + str + Research log summarizing the analysis steps, findings, and generated file paths + + """ + import os + + import matplotlib.pyplot as plt + import numpy as np + import pandas as pd + import statsmodels.api as sm + from scipy import stats + from statsmodels.formula.api import ols + from statsmodels.stats.multicomp import pairwise_tukeyhsd + + # Create output directory if it doesn't exist + os.makedirs(output_dir, exist_ok=True) + + # Initialize research log + log = "# Xenograft Tumor Growth Inhibition Analysis\n\n" + + # Load data from file + log += "## 1. Data Loading and Summary\n\n" + try: + if data_path.endswith(".csv"): + data_df = pd.read_csv(data_path) + elif data_path.endswith((".tsv", ".txt")): + data_df = pd.read_csv(data_path, sep="\t") + else: + log += "Error: Unsupported file format. Please provide a CSV or TSV file.\n" + return log + log += f"Successfully loaded tumor growth data from {data_path}\n" + except Exception as e: + log += f"Error loading data: {str(e)}\n" + return log + + # Validate required columns + required_columns = [time_column, volume_column, group_column, subject_column] + missing_columns = [col for col in required_columns if col not in data_df.columns] + if missing_columns: + log += f"Error: Missing required columns: {', '.join(missing_columns)}\n" + return log + + # Get unique groups and time points + groups = data_df[group_column].unique() + time_points = sorted(data_df[time_column].unique()) + n_groups = len(groups) + log += f"- Number of treatment groups: {n_groups} ({', '.join(map(str, groups))})\n" + log += f"- Number of time points: {len(time_points)}\n" + log += f"- Number of subjects: {data_df[subject_column].nunique()}\n" + log += f"- Total number of measurements: {len(data_df)}\n\n" + + # 2. Calculate group statistics at each time point + log += "## 2. Tumor Growth Analysis\n\n" + + # Group statistics + stats_df = ( + data_df.groupby([group_column, time_column])[volume_column] + .agg(mean="mean", sem=lambda x: stats.sem(x), count="count") + .reset_index() + ) + + # Save group statistics + stats_file = os.path.join(output_dir, "tumor_volume_statistics.csv") + stats_df.to_csv(stats_file, index=False) + log += f"Group statistics saved to: {stats_file}\n\n" + + # 3. Calculate tumor growth rates + log += "## 3. Tumor Growth Rate Analysis\n\n" + + growth_rates = {} + for group in groups: + group_data = data_df[data_df[group_column] == group] + + # Calculate growth rate for each subject + subject_growth_rates = [] + for subject in group_data[subject_column].unique(): + subject_data = group_data[group_data[subject_column] == subject] + + if len(subject_data) >= 2: + # Simple linear regression for growth rate + x = subject_data[time_column].values + y = subject_data[volume_column].values + slope, _, _, _, _ = stats.linregress(x, y) + subject_growth_rates.append(slope) + + growth_rates[group] = subject_growth_rates + mean_rate = np.mean(subject_growth_rates) + sem_rate = stats.sem(subject_growth_rates) + + log += ( + f"- {group}: Mean growth rate = {mean_rate:.2f} ± {sem_rate:.2f} mm³/day (n={len(subject_growth_rates)})\n" + ) + + # 4. Calculate Tumor Growth Inhibition (TGI) + log += "\n## 4. Tumor Growth Inhibition (TGI)\n\n" + + # Identify control group (assuming the first group is control) + control_group = groups[0] + log += f"Control group: {control_group}\n\n" + + # Calculate TGI for the final time point + final_time = max(time_points) + final_data = data_df[data_df[time_column] == final_time] + + control_final_mean = final_data[final_data[group_column] == control_group][volume_column].mean() + + tgi_results = {} + for group in groups: + if group == control_group: + continue + + group_final_mean = final_data[final_data[group_column] == group][volume_column].mean() + tgi = ((control_final_mean - group_final_mean) / control_final_mean) * 100 + tgi_results[group] = tgi + + log += f"- {group}: TGI = {tgi:.1f}% (relative to {control_group})\n" + + # 5. Statistical Analysis + log += "\n## 5. Statistical Analysis\n\n" + + # Repeated measures ANOVA + log += "### Repeated Measures ANOVA\n\n" + + try: + # Prepare data for repeated measures ANOVA + formula = f"{volume_column} ~ C({group_column}) * C({time_column}) + C({subject_column})" + model = ols(formula, data=data_df).fit() + anova_table = sm.stats.anova_lm(model, typ=2) + + # Save ANOVA results + anova_file = os.path.join(output_dir, "repeated_measures_anova.csv") + anova_table.to_csv(anova_file) + + log += f"ANOVA results saved to: {anova_file}\n\n" + + # Extract p-values + group_effect_p = anova_table.loc[f"C({group_column})", "PR(>F)"] + time_effect_p = anova_table.loc[f"C({time_column})", "PR(>F)"] + interaction_p = anova_table.loc[f"C({group_column}):C({time_column})", "PR(>F)"] + + log += f"- Treatment effect: p = {group_effect_p:.4f}\n" + log += f"- Time effect: p = {time_effect_p:.4f}\n" + log += f"- Treatment × Time interaction: p = {interaction_p:.4f}\n\n" + + # Post-hoc analysis at final time point + log += "### Post-hoc Analysis (Final Time Point)\n\n" + + # Perform Tukey's HSD test + tukey = pairwise_tukeyhsd(endog=final_data[volume_column], groups=final_data[group_column], alpha=0.05) + + # Save Tukey results + tukey_file = os.path.join(output_dir, "tukey_posthoc_results.txt") + with open(tukey_file, "w") as f: + f.write(str(tukey.summary())) + + log += f"Tukey's HSD results saved to: {tukey_file}\n\n" + + # Summarize significant comparisons + tukey_df = pd.DataFrame(data=tukey._results_table.data[1:], columns=tukey._results_table.data[0]) + + sig_pairs = tukey_df[tukey_df["p-adj"] < 0.05] + if len(sig_pairs) > 0: + log += "Significant pairwise comparisons:\n" + for _, row in sig_pairs.iterrows(): + log += f"- {row['group1']} vs {row['group2']}: p = {row['p-adj']:.4f}\n" + else: + log += "No significant pairwise comparisons found.\n" + + except Exception as e: + log += f"Error in statistical analysis: {str(e)}\n" + + # 6. Generate tumor growth curves + log += "\n## 6. Tumor Growth Visualization\n\n" + + plt.figure(figsize=(10, 6)) + + for group in groups: + group_stats = stats_df[stats_df[group_column] == group] + plt.errorbar( + group_stats[time_column], + group_stats["mean"], + yerr=group_stats["sem"], + label=group, + capsize=3, + marker="o", + ) + + plt.xlabel(f"{time_column} (days)") + plt.ylabel(f"Tumor Volume ({volume_column})") + plt.title("Xenograft Tumor Growth Curves") + plt.legend() + plt.grid(True, linestyle="--", alpha=0.7) + + # Save the plot + plot_file = os.path.join(output_dir, "tumor_growth_curves.png") + plt.savefig(plot_file, dpi=300, bbox_inches="tight") + plt.close() + + log += f"Tumor growth curve plot saved to: {plot_file}\n" + + # 7. Conclusion + log += "\n## 7. Conclusion\n\n" + + # Summarize most effective treatment + if tgi_results: + best_treatment = max(tgi_results.items(), key=lambda x: x[1]) + log += f"The most effective treatment was {best_treatment[0]} with a tumor growth inhibition of {best_treatment[1]:.1f}%.\n" + + # Statistical significance summary + try: + if group_effect_p < 0.05: + log += "Statistical analysis confirmed significant differences between treatment groups.\n" + else: + log += "No statistically significant differences were found between treatment groups.\n" + except Exception: + pass + + return log + + +def analyze_pixel_distribution(image_path: str) -> dict: + """Analyze western blot or DNA electrophoresis images and return pixel distribution statistics. + + Parameters + ---------- + image_path : str + Path to the input grayscale image. Automatically appends .png if no suffix is provided. + + Returns + ------- + dict + Summary dictionary containing image shape, intensity statistics, percentiles, + histogram values, and brightness distribution for predefined buckets. + + """ + import cv2 + + _DEFAULT_PERCENTILES = [1, 5, 10, 25, 50, 75, 90, 95, 99] + + _DEFAULT_BRIGHTNESS_BUCKETS: tuple[tuple[int, int], ...] = ( + (0, 20), + (20, 50), + (50, 80), + (80, 110), + (110, 140), + (140, 170), + (170, 200), + (200, 256), + ) + + image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE) + if image is None: + raise FileNotFoundError(f"Image not found at {image_path}") + + percentiles = np.percentile(image, _DEFAULT_PERCENTILES).tolist() + histogram = cv2.calcHist([image], [0], None, [256], [0, 256]).flatten() + total_pixels = int(image.size) + + brightness_lines = [] + for low, high in _DEFAULT_BRIGHTNESS_BUCKETS: + count = int(histogram[low:high].sum()) + ratio = round((count / total_pixels) * 100, 2) if total_pixels > 0 else 0.0 + brightness_lines.append(f"Range [{low:>3}, {high:>3}): {count:>8} px ({ratio:5.2f}%)") + + min_intensity = int(image.min()) + max_intensity = int(image.max()) + mean_intensity = round(float(image.mean()), 2) + std_intensity = round(float(image.std()), 2) + + return { + "shape": f"({image.shape[0]}, {image.shape[1]})", + "intensity_stats": { + "min": min_intensity, + "max": max_intensity, + "mean": mean_intensity, + "std_dev": std_intensity, + }, + "percentiles_label": "percentiles (1, 5, 10, 25, 50, 75, 90, 95, 99):", + "percentiles_values": ", ".join(f"{float(p):.1f}" for p in percentiles), + "pixel_brightness_distribution": brightness_lines, + } + + +def find_roi_from_image( + image_path: str, + lower_threshold: int, + upper_threshold: int, + number_of_bands: int, + debug: bool = True, +) -> tuple[str, list]: + """Find the ROIs of the bands from the image which is determined by analyze_pixel_distribution function. + + Parameters + ---------- + image_path : str + Path to the input image. + lower_threshold : int + Pixel intensities lower than this value are used to make the binary image. + upper_threshold : int + Pixel intensities greater than or equal to this value are used to make the binary image. + number_of_bands : int + The actual number of bands in the image. + debug : bool, optional + If True, draw green contours (hulls) and blue keypoint boxes for debugging. + Default is True. + + Returns + ------- + tuple[str, list] + A tuple containing: + - str: Absolute path to the saved annotated image + - list: List of ROI coordinates in (x, y, width, height) format. + The ROI list can be converted to target_bands for analyze_western_blot: + annotated_path, rois = find_roi_from_image(...) + target_bands = [{"name": f"band_{i}", "roi": list(roi)} for i, roi in enumerate(rois)] + + Raises + ------ + ValueError + If threshold values are outside the valid range or inconsistent. + FileNotFoundError + If the source image cannot be loaded. + + """ + from collections.abc import Iterable, Sequence + from pathlib import Path + + import cv2 + + ROI = tuple[int, int, int, int] + + def load_grayscale_image(path: str) -> cv2.Mat: + """Load a grayscale image from disk.""" + image = cv2.imread(path, cv2.IMREAD_GRAYSCALE) + if image is None: + raise FileNotFoundError(f"Unable to load image at '{path}'") + return image + + def build_blob_detector( + min_threshold: int = 0, + max_threshold: int = 200, + min_area: int = 120, + min_convexity: float = 0.7, + min_inertia: float = 0.001, + max_inertia: float = 0.4, + ) -> cv2.SimpleBlobDetector: + """Configure and return a SimpleBlobDetector instance.""" + params = cv2.SimpleBlobDetector_Params() + params.minThreshold = min_threshold + params.maxThreshold = max_threshold + params.filterByArea = True + params.minArea = min_area + params.filterByConvexity = True + params.minConvexity = min_convexity + params.filterByInertia = True + params.minInertiaRatio = min_inertia + params.maxInertiaRatio = max_inertia + return cv2.SimpleBlobDetector_create(params) + + def detect_blobs(image: cv2.Mat, detector: cv2.SimpleBlobDetector) -> list[cv2.KeyPoint]: + """Detect blob keypoints in the provided image.""" + keypoints = detector.detect(image) + print(f"Detected {len(keypoints)} keypoints.") + for index, keypoint in enumerate(keypoints): + print(f"[{index}] position={keypoint.pt}, size={keypoint.size}") + return keypoints + + def find_band_contours( + binary_mask: cv2.Mat, + min_area: int = 100, + use_morphology: bool = True, + ) -> list[cv2.Mat]: + """Find band contours from binary mask using morphological operations.""" + processed_mask = binary_mask.copy() + + if use_morphology: + # 가로(Horizontal) 방향으로 떨어진 덩어리를 잇기 위해 가로가 긴 커널 사용 + # (50, 1)의 50은 두 덩어리 사이의 픽셀 거리보다 커야 합니다. + kernel_connect = cv2.getStructuringElement(cv2.MORPH_RECT, (50, 1)) + + # OPEN(끊기) 대신 CLOSE(잇기)를 사용하여 빈 공간을 메움 + processed_mask = cv2.morphologyEx(processed_mask, cv2.MORPH_CLOSE, kernel_connect, iterations=1) + + # Find ALL contours (not just external) to detect separate bands + contours, _ = cv2.findContours(processed_mask, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) + + print(f"Found {len(contours)} total contours") + + # Filter contours by area + filtered_contours = [] + for contour in contours: + area = cv2.contourArea(contour) + if area >= min_area: + filtered_contours.append(contour) + + print(f"Filtered to {len(filtered_contours)} contours with area >= {min_area}") + + return filtered_contours + + def analyze_roi_pixel_distribution( + image: cv2.Mat, + roi: ROI, + ) -> dict: + """Analyze pixel distribution of an ROI to distinguish between text and bands. + + Parameters + ---------- + image : cv2.Mat + Original grayscale image + roi : ROI + ROI coordinates (x, y, width, height) + + Returns + ------- + dict + Dictionary containing edge_strength, std_dev, and gradient_magnitude + + """ + x, y, w, h = roi + + # Extract ROI region from original image + roi_region = image[y : y + h, x : x + w] + + if roi_region.size == 0: + return {"edge_strength": 0.0, "std_dev": 0.0, "gradient_magnitude": 0.0} + + # Calculate standard deviation of pixel intensities + std_dev = float(np.std(roi_region)) + + # Calculate edge strength using Laplacian + laplacian = cv2.Laplacian(roi_region, cv2.CV_64F) + edge_strength = float(np.mean(np.abs(laplacian))) + + # Calculate gradient magnitude using Sobel + sobelx = cv2.Sobel(roi_region, cv2.CV_64F, 1, 0, ksize=3) + sobely = cv2.Sobel(roi_region, cv2.CV_64F, 0, 1, ksize=3) + gradient_magnitude = float(np.mean(np.sqrt(sobelx**2 + sobely**2))) + + return { + "edge_strength": edge_strength, + "std_dev": std_dev, + "gradient_magnitude": gradient_magnitude, + } + + def filter_rois_by_pixel_distribution( + image: cv2.Mat, + rois: list[ROI], + hulls: list[cv2.Mat], + max_edge_strength: float = 10.0, + max_gradient_magnitude: float = 70.0, + max_std_dev: float = 50.0, + ) -> tuple[list[ROI], list[cv2.Mat]]: + """Filter ROIs to remove text-like regions and keep band-like regions. + + Text regions have very high edge strength, sharp gradients, and high std_dev. + Band regions have low edge strength, moderate gradients, and moderate std_dev. + + Parameters + ---------- + image : cv2.Mat + Original grayscale image + rois : List[ROI] + List of ROI coordinates + hulls : List[cv2.Mat] + List of corresponding convex hulls + max_edge_strength : float, optional + Maximum edge strength for band-like regions (text typically >20) + max_gradient_magnitude : float, optional + Maximum gradient magnitude for band-like regions (text typically >100) + max_std_dev : float, optional + Maximum standard deviation for band-like regions (text typically >80) + + Returns + ------- + tuple + Filtered ROIs and hulls that are band-like + + """ + filtered_rois: list[ROI] = [] + filtered_hulls: list[cv2.Mat] = [] + + print("\n=== ROI Pixel Distribution Analysis ===") + + for idx, (roi, hull) in enumerate(zip(rois, hulls, strict=True)): + analysis = analyze_roi_pixel_distribution(image, roi) + + edge_strength = analysis["edge_strength"] + gradient_magnitude = analysis["gradient_magnitude"] + std_dev = analysis["std_dev"] + + # Determine if this ROI is band-like or text-like + # Text has very high edge strength (>20) and very high gradient (>100) + # Bands have low edge strength (<10) and moderate gradient (<50) + # Also check std_dev to filter out high-contrast text regions + is_band = ( + edge_strength <= max_edge_strength + and gradient_magnitude <= max_gradient_magnitude + and std_dev <= max_std_dev + ) + + status = "✓ BAND" if is_band else "✗ TEXT" + print(f"ROI {idx}: edge={edge_strength:.2f}, grad={gradient_magnitude:.2f}, std={std_dev:.2f} -> {status}") + + if is_band: + filtered_rois.append(roi) + filtered_hulls.append(hull) + + print(f"\nFiltered: {len(filtered_rois)}/{len(rois)} ROIs kept as bands") + print("=" * 40 + "\n") + + return filtered_rois, filtered_hulls + + def compute_rois( + image: cv2.Mat, + binary_mask: cv2.Mat, + keypoints: Iterable[cv2.KeyPoint], + padding: tuple[int, int] = (5, 5), + min_contour_area: int = 100, + filter_by_distribution: bool = True, + ) -> tuple[list[ROI], list[cv2.Mat]]: + """Compute global ROIs for each keypoint by matching them to band contours.""" + # Find band contours from binary mask + band_contours = find_band_contours(binary_mask, min_area=min_contour_area) + + if not band_contours: + print("No band contours found!") + return [], [] + + auto_rois: list[ROI] = [] + global_hulls: list[cv2.Mat] = [] + matched_contours = set() # Track which contours have been matched + used_contours = set() # Track which contours have already been used to avoid duplicates + + for keypoint in keypoints: + cx, cy = int(keypoint.pt[0]), int(keypoint.pt[1]) + keypoint_center = (float(cx), float(cy)) + + # Find the contour that contains this keypoint + matched_contour = None + matched_idx = None + for idx, contour in enumerate(band_contours): + # Skip if this contour has already been used + if idx in used_contours: + continue + # Use pointPolygonTest to check if keypoint center is inside contour + # Returns positive if inside, negative if outside, zero if on edge + distance = cv2.pointPolygonTest(contour, keypoint_center, False) + if distance >= 0: # Inside or on edge + matched_contour = contour + matched_idx = idx + matched_contours.add(idx) + print(f"Keypoint at ({cx}, {cy}) matched to contour {idx}") + break + + if matched_contour is None: + print(f"Warning: Keypoint at ({cx}, {cy}) not matched to any contour") + continue + + # Mark this contour as used to avoid duplicate ROIs + used_contours.add(matched_idx) + + # Compute convex hull from the matched contour + hull = cv2.convexHull(matched_contour) + if hull is None or len(hull) < 3: + continue + + # Get bounding rectangle from hull with padding + pad_x, pad_y = padding + rx, ry, rw, rh = cv2.boundingRect(hull) + + # Skip if bounding rect is too large (likely covering entire image or invalid) + max_roi_area_ratio = 0.5 # Maximum 50% of image area + roi_area = rw * rh + image_area = image.shape[0] * image.shape[1] + if roi_area > image_area * max_roi_area_ratio: + print(f"Warning: ROI too large ({rw}x{rh}), skipping. This may indicate a detection error.") + continue + + global_x = max(0, rx - pad_x) + global_y = max(0, ry - pad_y) + global_w = min(image.shape[1] - global_x, rw + 2 * pad_x) + global_h = min(image.shape[0] - global_y, rh + 2 * pad_y) + + if global_w <= 0 or global_h <= 0: + continue + + roi = (global_x, global_y, global_w, global_h) + auto_rois.append(roi) + global_hulls.append(hull) + + print(f"Matched {len(matched_contours)} contours to keypoints") + + # Filter ROIs by pixel distribution to remove text-like regions + if filter_by_distribution and auto_rois: + auto_rois, global_hulls = filter_rois_by_pixel_distribution(image, auto_rois, global_hulls) + + return auto_rois, global_hulls + + def annotate_keypoints( + image: cv2.Mat, + keypoints: Iterable[cv2.KeyPoint], + rois: Iterable[ROI], + hulls: Iterable[cv2.Mat] | None = None, + debug: bool = False, + ) -> cv2.Mat: + """Draw ROIs, convex hulls, and index labels on the image. + + Parameters + ---------- + image : cv2.Mat + Input grayscale image + keypoints : Iterable[cv2.KeyPoint] + Detected keypoints + rois : Iterable[ROI] + ROI coordinates to draw + hulls : Iterable[cv2.Mat] | None, optional + Convex hulls to draw (only if debug=True) + debug : bool, optional + If True, draw green contours (hulls) and blue keypoint boxes + + Returns + ------- + cv2.Mat + Annotated image + + """ + output = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR) + + # Draw convex hulls in green if debug mode is enabled + if debug and hulls is not None: + for hull in hulls: + cv2.drawContours(output, [hull], -1, (0, 255, 0), 2) # Green color in BGR + + # Draw ROIs in red (always drawn) + for roi in rois: + x, y, w, h = roi + cv2.rectangle(output, (x, y), (x + w, y + h), (0, 0, 255), 2) + + # Draw keypoints in blue and index labels (only if debug mode is enabled) + if debug: + for index, keypoint in enumerate(keypoints): + x, y = keypoint.pt + size = keypoint.size + + # Draw blue rectangle around keypoint + # Use size as half-width and half-height for the rectangle + half_size = int(size / 2) + pt1 = (int(x) - half_size, int(y) - half_size) + pt2 = (int(x) + half_size, int(y) + half_size) + cv2.rectangle(output, pt1, pt2, (255, 0, 0), 2) # Blue color in BGR + + # Draw index label + cv2.putText( + output, + str(index), + (int(x) + 5, int(y) - 5), + cv2.FONT_HERSHEY_SIMPLEX, + 0.5, + (0, 255, 0), + 1, + cv2.LINE_AA, + ) + return output + + def show_rois(rois: Sequence[ROI]) -> None: + """Print ROI information to stdout.""" + print(f"Detected ROI count: {len(rois)}") + for index, roi in enumerate(rois): + print(f"ROI {index}: {roi}") + + if not 0 <= lower_threshold <= 255: + raise ValueError("lower_threshold must be within [0, 255].") + if not 0 <= upper_threshold <= 255: + raise ValueError("upper_threshold must be within [0, 255].") + if lower_threshold > upper_threshold: + raise ValueError("lower_threshold cannot be greater than upper_threshold.") + + original_image = load_grayscale_image(image_path) + mask = cv2.inRange(original_image, lower_threshold, upper_threshold) + mask = cv2.bitwise_not(mask) + detector = build_blob_detector() + + # Detect blobs in the mask image + keypoints = detect_blobs(mask, detector) + rois, hulls = compute_rois(original_image, mask, keypoints) + show_rois(rois) + + # Draw ROIs, convex hulls, and keypoints on the mask image + annotated_mask = annotate_keypoints(mask, keypoints, rois, hulls, debug=debug) + mask_path = Path(image_path).parent / f"{Path(image_path).stem}_mask.png" + cv2.imwrite(str(mask_path), annotated_mask) + + # Draw ROIs, convex hulls, and keypoints on the original image + annotated_image = annotate_keypoints(original_image, keypoints, rois, hulls, debug=debug) + annotated_image_path = Path(image_path).parent / f"{Path(image_path).stem}_annotated.png" + cv2.imwrite(str(annotated_image_path), annotated_image) + + if len(rois) != number_of_bands: + print(f"Warning: Detected {len(rois)} ROIs, but expected {number_of_bands} ROIs.") + print( + "Please check the image and try to adjust the thresholds. Or you can manually infer the ROIs from the annotated image." + ) + + return str(annotated_image_path.resolve()), rois + + +def analyze_western_blot( + blot_image_path, + target_bands, + loading_control_band, + antibody_info, + output_dir="./results", +): + """Performs densitometric analysis of Western blot images to quantify relative protein expression. + + Parameters + ---------- + blot_image_path : str + Path to the Western blot image file + target_bands : list of dict + List of dictionaries containing information about target protein bands. + Each dict should have 'name' and 'roi' (region of interest as [x, y, width, height]). + To generate this from find_roi_from_image output: + annotated_path, rois = find_roi_from_image(...) + target_bands = [{"name": f"band_{i}", "roi": list(roi)} for i, roi in enumerate(rois)] + Or manually specify names: target_bands = [{"name": "protein_name", "roi": [x, y, w, h]}, ...] + loading_control_band : dict + Dictionary with 'name' and 'roi' for the loading control protein (e.g., β-actin, GAPDH) + antibody_info : dict + Dictionary containing information about antibodies used + Should have 'primary' and 'secondary' keys with antibody details + output_dir : str, optional + Directory to save output files, defaults to './results' + + Returns + ------- + str + Research log summarizing the Western blot analysis process and results + + """ + import os + + import numpy as np + from skimage import io + + # Create output directory if it doesn't exist + if not os.path.exists(output_dir): + os.makedirs(output_dir) + + # Load the Western blot image + image = io.imread(blot_image_path) + if len(image.shape) > 2: # Convert to grayscale if color image + image = np.mean(image, axis=2).astype(np.uint8) + + # Initialize results dictionary + results = { + "loading_control": {"name": loading_control_band["name"], "intensity": 0}, + "targets": [], + } + + # Analyze loading control band + lc_roi = loading_control_band["roi"] + lc_band = image[lc_roi[1] : lc_roi[1] + lc_roi[3], lc_roi[0] : lc_roi[0] + lc_roi[2]] + lc_intensity = np.sum(lc_band) + results["loading_control"]["intensity"] = lc_intensity + + # Analyze target protein bands + for band in target_bands: + roi = band["roi"] + band_img = image[roi[1] : roi[1] + roi[3], roi[0] : roi[0] + roi[2]] + band_intensity = np.sum(band_img) + + # Calculate relative expression (normalized to loading control) + relative_expression = band_intensity / lc_intensity + + results["targets"].append( + { + "name": band["name"], + "intensity": band_intensity, + "relative_expression": relative_expression, + } + ) + + # Generate results table and save to CSV + results_file = os.path.join(output_dir, "western_blot_results.csv") + with open(results_file, "w") as f: + f.write("Protein,Raw Intensity,Relative Expression\n") + f.write(f"{results['loading_control']['name']},{results['loading_control']['intensity']},1.0\n") + for target in results["targets"]: + f.write(f"{target['name']},{target['intensity']},{target['relative_expression']:.4f}\n") + + # Generate research log + log = "## Western Blot Analysis\n\n" + log += f"Analyzed Western blot image: {os.path.basename(blot_image_path)}\n\n" + log += "### Antibodies Used\n" + log += f"Primary antibody: {antibody_info['primary']}\n" + log += f"Secondary antibody: {antibody_info['secondary']}\n\n" + log += "### Analysis Steps\n" + log += "1. Loaded Western blot image and converted to grayscale\n" + log += f"2. Quantified loading control ({loading_control_band['name']}) band intensity\n" + log += "3. Measured target protein band intensities\n" + log += "4. Calculated relative expression by normalizing to loading control\n\n" + log += "### Results\n" + log += f"Loading control ({loading_control_band['name']}): {results['loading_control']['intensity']} intensity units\n\n" + log += "Target proteins:\n" + for target in results["targets"]: + log += f"- {target['name']}: {target['intensity']} intensity units, " + log += f"{target['relative_expression']:.4f} relative expression\n" + log += f"\nDetailed results saved to: {results_file}\n" + + return log + + +# DDInter Drug-Drug Interaction Analysis Functions + + +def _load_ddinter_data(data_lake_path): + """ + Load DDInter datasets from pickle files, processing if needed. + + Parameters + ---------- + data_lake_path : str + Path to data lake directory containing DDInter pickle files + + Returns + ------- + tuple + (drug_info, interaction_matrix, name_mapping) dictionaries + """ + import os + import pickle + + # Define schema directory (following established pattern) + schema_dir = os.path.join(os.path.dirname(__file__), "schema_db") + + # Define paths to DDInter pickle files + drug_info_path = os.path.join(schema_dir, "ddinter_drugs.pkl") + interaction_path = os.path.join(schema_dir, "ddinter_interactions.pkl") + mapping_path = os.path.join(schema_dir, "ddinter_name_mapping.pkl") + + # Check if processing is needed (lazy loading pattern) + pkl_files = [drug_info_path, interaction_path, mapping_path] + if not all(os.path.exists(f) for f in pkl_files): + _process_ddinter_data_inline(data_lake_path, schema_dir) + + # Load data + try: + with open(drug_info_path, "rb") as f: + drug_info = pickle.load(f) + + with open(interaction_path, "rb") as f: + interaction_matrix = pickle.load(f) + + with open(mapping_path, "rb") as f: + name_mapping = pickle.load(f) + + return drug_info, interaction_matrix, name_mapping + + except Exception as e: + raise FileNotFoundError(f"Error loading DDInter data: {e}") from e + + +def _process_ddinter_data_inline(data_lake_path, output_dir): + """ + Process DDInter CSV files into standardized pickle files. + + This function processes raw DDInter 2.0 CSV files and creates standardized + data structures for use in Biomni drug-drug interaction analysis. + + Parameters + ---------- + data_lake_path : str + Path to data lake directory containing raw DDInter CSV files + output_dir : str + Directory to save processed pickle files + """ + import os + import pickle + from pathlib import Path + + import pandas as pd + + # Create output directory + Path(output_dir).mkdir(exist_ok=True) + + # Define CSV files to process + csv_files = [ + "ddinter_alimentary_tract_metabolism.csv", + "ddinter_antineoplastic.csv", + "ddinter_antiparasitic.csv", + "ddinter_blood_organs.csv", + "ddinter_dermatological.csv", + "ddinter_hormonal.csv", + "ddinter_respiratory.csv", + "ddinter_various.csv", + ] + + # Load and combine all CSV files + dataframes = [] + for csv_file in csv_files: + file_path = os.path.join(data_lake_path, csv_file) + if os.path.exists(file_path): + df = pd.read_csv(file_path) + # Add source category + category = csv_file.replace("ddinter_", "").replace(".csv", "") + df["category"] = category + dataframes.append(df) + + if not dataframes: + raise FileNotFoundError("No DDInter CSV files found in data lake") + + # Process data + drug_info = _build_drug_registry_inline(dataframes) + interaction_matrix = _create_interaction_matrix_inline(dataframes) + name_mapping = _create_name_mapping_inline(drug_info) + + # Save processed data + with open(os.path.join(output_dir, "ddinter_drugs.pkl"), "wb") as f: + pickle.dump(drug_info, f) + + with open(os.path.join(output_dir, "ddinter_interactions.pkl"), "wb") as f: + pickle.dump(interaction_matrix, f) + + with open(os.path.join(output_dir, "ddinter_name_mapping.pkl"), "wb") as f: + pickle.dump(name_mapping, f) + + # Generate and save statistics + stats = _generate_ddinter_statistics_inline(drug_info, interaction_matrix) + with open(os.path.join(output_dir, "ddinter_statistics.pkl"), "wb") as f: + pickle.dump(stats, f) + + +def _standardize_drug_name_processing(drug_name): + """Standardize drug names for consistent matching during processing.""" + import pandas as pd + + if pd.isna(drug_name): + return "" + + # Convert to lowercase and strip whitespace + standardized = str(drug_name).strip().lower() + + # Remove common suffixes and prefixes + standardized = standardized.replace(" hydrochloride", "") + standardized = standardized.replace(" sulfate", "") + standardized = standardized.replace(" sodium", "") + standardized = standardized.replace(" potassium", "") + standardized = standardized.replace(" calcium", "") + standardized = standardized.replace(" magnesium", "") + + return standardized + + +def _build_drug_registry_inline(dataframes): + """Build comprehensive drug registry from all interactions.""" + + drug_registry = {} + + for df in dataframes: + for _, row in df.iterrows(): + drug_a_id = row["DDInterID_A"] + drug_a_name = row["Drug_A"] + drug_b_id = row["DDInterID_B"] + drug_b_name = row["Drug_B"] + + # Add Drug A + if drug_a_id not in drug_registry: + drug_registry[drug_a_id] = { + "name": drug_a_name, + "standardized_name": _standardize_drug_name_processing(drug_a_name), + "categories": set(), + "interactions": set(), + } + drug_registry[drug_a_id]["categories"].add(row["category"]) + + # Add Drug B + if drug_b_id not in drug_registry: + drug_registry[drug_b_id] = { + "name": drug_b_name, + "standardized_name": _standardize_drug_name_processing(drug_b_name), + "categories": set(), + "interactions": set(), + } + drug_registry[drug_b_id]["categories"].add(row["category"]) + + # Record interactions + drug_registry[drug_a_id]["interactions"].add(drug_b_id) + drug_registry[drug_b_id]["interactions"].add(drug_a_id) + + # Convert sets to lists for pickle serialization + for drug_id in drug_registry: + drug_registry[drug_id]["categories"] = list(drug_registry[drug_id]["categories"]) + drug_registry[drug_id]["interactions"] = list(drug_registry[drug_id]["interactions"]) + + return drug_registry + + +def _create_interaction_matrix_inline(dataframes): + """Create interaction matrix for fast lookups using standardized drug names.""" + from collections import defaultdict + + import pandas as pd + + combined_df = pd.concat(dataframes, ignore_index=True) + interaction_matrix = defaultdict(lambda: defaultdict(list)) + + # Create bidirectional interaction matrix using standardized names + for _, row in combined_df.iterrows(): + drug_a_std = _standardize_drug_name_processing(row["Drug_A"]) + drug_b_std = _standardize_drug_name_processing(row["Drug_B"]) + level = row["Level"] + category = row["category"] + + interaction_data = { + "level": level, + "category": category, + "drug_a_id": row["DDInterID_A"], + "drug_b_id": row["DDInterID_B"], + "drug_a_name": row["Drug_A"], + "drug_b_name": row["Drug_B"], + } + + # Add both directions using standardized names as keys + interaction_matrix[drug_a_std][drug_b_std].append(interaction_data) + interaction_matrix[drug_b_std][drug_a_std].append(interaction_data) + + # Convert to regular dict for pickle + interaction_matrix = dict(interaction_matrix) + for drug in interaction_matrix: + interaction_matrix[drug] = dict(interaction_matrix[drug]) + + return interaction_matrix + + +def _create_name_mapping_inline(drug_info): + """Create drug name to ID mapping for fuzzy matching.""" + name_mapping = {} + + for drug_id, drug_data in drug_info.items(): + original_name = drug_data["name"] + standardized_name = drug_data["standardized_name"] + + # Map both original and standardized names + name_mapping[original_name.lower()] = drug_id + name_mapping[standardized_name] = drug_id + + return name_mapping + + +def _generate_ddinter_statistics_inline(drug_info, interaction_matrix): + """Generate statistics about the processed data.""" + from collections import defaultdict + + stats = { + "total_drugs": len(drug_info), + "total_interactions": 0, + "interaction_levels": defaultdict(int), + "drug_categories": defaultdict(int), + "most_connected_drugs": [], + } + + # Count interactions and levels + for drug_a in interaction_matrix: + for drug_b in interaction_matrix[drug_a]: + interactions = interaction_matrix[drug_a][drug_b] + stats["total_interactions"] += len(interactions) + + for interaction in interactions: + stats["interaction_levels"][interaction["level"]] += 1 + + # Count drug categories + for drug_data in drug_info.values(): + for category in drug_data["categories"]: + stats["drug_categories"][category] += 1 + + # Find most connected drugs + connection_counts = [] + for drug_id, drug_data in drug_info.items(): + connection_counts.append( + {"drug_id": drug_id, "name": drug_data["name"], "connections": len(drug_data["interactions"])} + ) + + connection_counts.sort(key=lambda x: x["connections"], reverse=True) + stats["most_connected_drugs"] = connection_counts[:10] + + return stats + + +def _standardize_drug_name(drug_name, name_mapping): + """ + Standardize drug names using fuzzy matching against DDInter database. + + Parameters + ---------- + drug_name : str + Original drug name + name_mapping : dict + Drug name to ID mapping dictionary + + Returns + ------- + str or None + Standardized drug name or None if not found + """ + from difflib import get_close_matches + + # Direct match + if drug_name.lower() in name_mapping: + return drug_name.lower() + + # Fuzzy match + matches = get_close_matches(drug_name.lower(), name_mapping.keys(), n=1, cutoff=0.8) + if matches: + return matches[0] + + return None + + +def _format_interaction_result(interaction_data, drug_name_a, drug_name_b, include_mechanisms=True): + """ + Format interaction results for research log. + + Parameters + ---------- + interaction_data : list + List of interaction data dictionaries + drug_name_a : str + First drug name + drug_name_b : str + Second drug name + include_mechanisms : bool + Whether to include detailed mechanism information + + Returns + ------- + str + Formatted interaction description + """ + if not interaction_data: + return f"No interactions found between {drug_name_a} and {drug_name_b}" + + result = f"Interaction between {drug_name_a} and {drug_name_b}:\n" + + for i, interaction in enumerate(interaction_data, 1): + level = interaction.get("level", "Unknown") + category = interaction.get("category", "Unknown") + + result += f" {i}. Severity: {level}\n" + result += f" Category: {category.replace('_', ' ').title()}\n" + + if include_mechanisms: + result += f" Clinical significance: {level} interaction requiring appropriate monitoring\n" + + return result + + +def query_drug_interactions(drug_names, interaction_types=None, severity_levels=None, data_lake_path=None): + """ + Query drug-drug interactions from DDInter database. + + Parameters + ---------- + drug_names : list of str + List of drug names to query for interactions + interaction_types : list of str, optional + Filter by interaction types (e.g., ['synergistic', 'antagonistic']) + severity_levels : list of str, optional + Filter by severity levels (e.g., ['Major', 'Moderate', 'Minor']) + data_lake_path : str, optional + Path to data lake directory containing DDInter data + + Returns + ------- + str + Research log with detailed interaction analysis + """ + from datetime import datetime + + # Initialize research log + log = "DDInter Drug-Drug Interaction Query\n" + log += "=" * 40 + "\n" + log += f"Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n" + + # Handle default data lake path + if data_lake_path is None: + # Default path assuming standard Biomni structure + data_lake_path = os.path.join(os.path.dirname(__file__), "schema_db") + + log += "Query Parameters:\n" + log += f"- Target drugs: {', '.join(drug_names)}\n" + log += f"- Severity filter: {severity_levels if severity_levels else 'All levels'}\n" + log += f"- Interaction types: {interaction_types if interaction_types else 'All types'}\n\n" + + try: + # Load DDInter data + drug_info, interaction_matrix, name_mapping = _load_ddinter_data(data_lake_path) + log += f"Successfully loaded DDInter database with {len(drug_info)} drugs\n\n" + + # Standardize drug names + standardized_names = [] + missing_drugs = [] + + for drug_name in drug_names: + standardized = _standardize_drug_name(drug_name, name_mapping) + if standardized: + standardized_names.append(standardized) + else: + missing_drugs.append(drug_name) + + if missing_drugs: + log += "Warning: The following drugs were not found in DDInter database:\n" + for drug in missing_drugs: + log += f"- {drug}\n" + log += "\n" + + if not standardized_names: + log += "Error: No valid drugs found in DDInter database\n" + return log + + # Query interactions + interactions_found = [] + + for i, drug_a in enumerate(standardized_names): + for j, drug_b in enumerate(standardized_names): + if i >= j: # Avoid duplicate pairs + continue + + if drug_a in interaction_matrix and drug_b in interaction_matrix[drug_a]: + interactions = interaction_matrix[drug_a][drug_b] + + # Apply filters + filtered_interactions = interactions + + if severity_levels: + filtered_interactions = [ + int_data for int_data in filtered_interactions if int_data.get("level") in severity_levels + ] + + if interaction_types: + filtered_interactions = [ + int_data + for int_data in filtered_interactions + if int_data.get("category") in interaction_types + ] + + if filtered_interactions: + interactions_found.append( + {"drug_a": drug_a, "drug_b": drug_b, "interactions": filtered_interactions} + ) + + # Format results + log += "Interaction Analysis Results:\n" + log += f"Found {len(interactions_found)} drug pairs with interactions\n\n" + + if interactions_found: + for pair in interactions_found: + log += _format_interaction_result( + pair["interactions"], pair["drug_a"].title(), pair["drug_b"].title(), include_mechanisms=True + ) + log += "\n" + else: + log += "No interactions found between the specified drugs with the given filters\n" + + # Summary statistics + total_interactions = sum(len(pair["interactions"]) for pair in interactions_found) + log += "Summary:\n" + log += f"- Total drug pairs analyzed: {len(standardized_names) * (len(standardized_names) - 1) // 2}\n" + log += f"- Drug pairs with interactions: {len(interactions_found)}\n" + log += f"- Total interactions found: {total_interactions}\n" + + if interactions_found: + severity_counts = {} + for pair in interactions_found: + for interaction in pair["interactions"]: + level = interaction.get("level", "Unknown") + severity_counts[level] = severity_counts.get(level, 0) + 1 + + log += f"- Severity distribution: {dict(severity_counts)}\n" + + except FileNotFoundError as e: + log += f"Error during interaction query: {str(e)}\n" + except Exception as e: + log += f"Error during interaction query: {str(e)}\n" + + return log + + +def check_drug_combination_safety(drug_list, include_mechanisms=True, include_management=True, data_lake_path=None): + """ + Analyze safety of a drug combination for potential interactions. + + Parameters + ---------- + drug_list : list of str + List of drugs to analyze for combination safety + include_mechanisms : bool, default True + Include interaction mechanism descriptions + include_management : bool, default True + Include management recommendations + data_lake_path : str, optional + Path to data lake directory containing DDInter data + + Returns + ------- + str + Research log with safety analysis and recommendations + """ + from datetime import datetime + + # Initialize research log + log = "Drug Combination Safety Analysis\n" + log += "=" * 35 + "\n" + log += f"Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n" + + # Handle default data lake path + if data_lake_path is None: + data_lake_path = os.path.join(os.path.dirname(__file__), "schema_db") + + log += "Safety Analysis Parameters:\n" + log += f"- Drug combination: {', '.join(drug_list)}\n" + log += f"- Include mechanisms: {include_mechanisms}\n" + log += f"- Include management: {include_management}\n\n" + + try: + # Load DDInter data + drug_info, interaction_matrix, name_mapping = _load_ddinter_data(data_lake_path) + log += "Successfully loaded DDInter database\n\n" + + # Standardize drug names + standardized_drugs = [] + missing_drugs = [] + + for drug in drug_list: + standardized = _standardize_drug_name(drug, name_mapping) + if standardized: + standardized_drugs.append(standardized) + else: + missing_drugs.append(drug) + + if missing_drugs: + log += "Warning: The following drugs were not found in DDInter database:\n" + for drug in missing_drugs: + log += f"- {drug}\n" + log += "\n" + + if len(standardized_drugs) < 2: + log += "Error: At least 2 valid drugs required for combination analysis\n" + return log + + # Analyze all pairwise interactions + interactions_found = [] + major_interactions = 0 + moderate_interactions = 0 + minor_interactions = 0 + + for i, drug_a in enumerate(standardized_drugs): + for j, drug_b in enumerate(standardized_drugs): + if i >= j: # Avoid duplicate pairs + continue + + if drug_a in interaction_matrix and drug_b in interaction_matrix[drug_a]: + interactions = interaction_matrix[drug_a][drug_b] + + for interaction in interactions: + level = interaction.get("level", "Unknown") + if level == "Major": + major_interactions += 1 + elif level == "Moderate": + moderate_interactions += 1 + elif level == "Minor": + minor_interactions += 1 + + interactions_found.append({"drug_a": drug_a, "drug_b": drug_b, "interactions": interactions}) + + # Overall safety assessment + log += "Overall Safety Assessment:\n" + + safety_score = 100 + safety_level = "Safe" + + if major_interactions > 0: + safety_score -= major_interactions * 30 + safety_level = "High Risk" + elif moderate_interactions > 2: + safety_score -= moderate_interactions * 15 + safety_level = "Moderate Risk" + elif moderate_interactions > 0: + safety_score -= moderate_interactions * 10 + safety_level = "Low to Moderate Risk" + elif minor_interactions > 0: + safety_score -= minor_interactions * 5 + safety_level = "Low Risk" + + safety_score = max(0, safety_score) + + log += f"- Safety Level: {safety_level}\n" + log += f"- Safety Score: {safety_score}/100\n" + log += f"- Major interactions: {major_interactions}\n" + log += f"- Moderate interactions: {moderate_interactions}\n" + log += f"- Minor interactions: {minor_interactions}\n\n" + + # Detailed interaction analysis + if interactions_found: + log += "Detailed Interaction Analysis:\n" + log += "-" * 30 + "\n" + + for pair in interactions_found: + log += _format_interaction_result( + pair["interactions"], + pair["drug_a"].title(), + pair["drug_b"].title(), + include_mechanisms=include_mechanisms, + ) + log += "\n" + + # Clinical recommendations + log += "Clinical Recommendations:\n" + log += "-" * 25 + "\n" + + if major_interactions > 0: + log += "- CONTRAINDICATED: This combination contains major interactions\n" + log += "- Consider alternative medications or consult specialist\n" + log += "- If combination is necessary, intensive monitoring required\n" + elif moderate_interactions > 2: + log += "- CAUTION: Multiple moderate interactions detected\n" + log += "- Monitor patient closely for adverse effects\n" + log += "- Consider dose adjustments or alternative agents\n" + elif moderate_interactions > 0: + log += "- MONITOR: Moderate interactions present\n" + log += "- Regular patient monitoring recommended\n" + log += "- Be aware of potential side effects\n" + elif minor_interactions > 0: + log += "- AWARENESS: Minor interactions detected\n" + log += "- Standard monitoring sufficient\n" + log += "- Educate patient about potential minor effects\n" + else: + log += "- SAFE: No significant interactions detected\n" + log += "- Standard clinical monitoring appropriate\n" + + if include_management: + log += "\nGeneral Management Strategies:\n" + log += "- Separate administration times when possible\n" + log += "- Monitor for signs of toxicity or reduced efficacy\n" + log += "- Consider therapeutic drug monitoring if available\n" + log += "- Educate patient about potential interaction symptoms\n" + + except Exception as e: + log += f"Error during safety analysis: {str(e)}\n" + + return log + + +def analyze_interaction_mechanisms(drug_pair, detailed_analysis=True, data_lake_path=None): + """ + Analyze interaction mechanisms between two specific drugs. + + Parameters + ---------- + drug_pair : tuple of str + Pair of drug names to analyze (drug1, drug2) + detailed_analysis : bool, default True + Include detailed mechanistic information + data_lake_path : str, optional + Path to data lake directory containing DDInter data + + Returns + ------- + str + Research log with mechanism analysis + """ + from datetime import datetime + + # Initialize research log + log = "Drug Interaction Mechanism Analysis\n" + log += "=" * 37 + "\n" + log += f"Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n" + + # Handle default data lake path + if data_lake_path is None: + data_lake_path = os.path.join(os.path.dirname(__file__), "schema_db") + + drug_a, drug_b = drug_pair + log += "Mechanism Analysis Parameters:\n" + log += f"- Drug A: {drug_a}\n" + log += f"- Drug B: {drug_b}\n" + log += f"- Detailed analysis: {detailed_analysis}\n\n" + + try: + # Load DDInter data + drug_info, interaction_matrix, name_mapping = _load_ddinter_data(data_lake_path) + log += "Successfully loaded DDInter database\n\n" + + # Standardize drug names + std_drug_a = _standardize_drug_name(drug_a, name_mapping) + std_drug_b = _standardize_drug_name(drug_b, name_mapping) + + if not std_drug_a: + log += f"Error: Drug '{drug_a}' not found in DDInter database\n" + return log + if not std_drug_b: + log += f"Error: Drug '{drug_b}' not found in DDInter database\n" + return log + + # Query interactions + interactions = [] + if std_drug_a in interaction_matrix and std_drug_b in interaction_matrix[std_drug_a]: + interactions = interaction_matrix[std_drug_a][std_drug_b] + + if not interactions: + log += f"No interactions found between {drug_a} and {drug_b}\n" + return log + + # Get drug information + drug_a_id = name_mapping[std_drug_a] + drug_b_id = name_mapping[std_drug_b] + drug_a_info = drug_info.get(drug_a_id, {}) + drug_b_info = drug_info.get(drug_b_id, {}) + + log += "Drug Profile Analysis:\n" + log += "-" * 20 + "\n" + log += f"{drug_a.title()}:\n" + log += f"- Categories: {', '.join(drug_a_info.get('categories', ['Unknown']))}\n" + log += f"- Total known interactions: {len(drug_a_info.get('interactions', []))}\n\n" + + log += f"{drug_b.title()}:\n" + log += f"- Categories: {', '.join(drug_b_info.get('categories', ['Unknown']))}\n" + log += f"- Total known interactions: {len(drug_b_info.get('interactions', []))}\n\n" + + # Analyze interaction mechanisms + log += "Interaction Mechanism Analysis:\n" + log += "-" * 30 + "\n" + + for i, interaction in enumerate(interactions, 1): + level = interaction.get("level", "Unknown") + category = interaction.get("category", "Unknown") + + log += f"Interaction {i}:\n" + log += f"- Severity: {level}\n" + log += f"- Category: {category.replace('_', ' ').title()}\n" + + if detailed_analysis: + # Provide mechanism insights based on severity and category + if level == "Major": + log += "- Clinical Impact: High risk interaction requiring immediate attention\n" + log += "- Mechanism: Likely involves significant pharmacokinetic or pharmacodynamic effects\n" + log += "- Management: Avoid combination or use with extreme caution\n" + elif level == "Moderate": + log += "- Clinical Impact: Moderate risk requiring monitoring\n" + log += "- Mechanism: May involve enzyme induction/inhibition or receptor competition\n" + log += "- Management: Monitor closely, consider dose adjustment\n" + elif level == "Minor": + log += "- Clinical Impact: Low risk, usually manageable\n" + log += "- Mechanism: Minor pharmacokinetic or pharmacodynamic effects\n" + log += "- Management: Standard monitoring sufficient\n" + + # Category-specific mechanism insights + category_mechanisms = { + "alimentary_tract_metabolism": "Gastrointestinal absorption or metabolic interactions", + "antineoplastic": "Bone marrow suppression or tumor resistance mechanisms", + "blood_organs": "Hematological effects or coagulation pathway interactions", + "hormonal": "Endocrine system interactions or hormone receptor effects", + "respiratory": "Pulmonary function or bronchodilation interactions", + "dermatological": "Skin absorption or topical application interactions", + "antiparasitic": "Antimicrobial resistance or metabolic pathway interactions", + "various": "Multiple potential interaction pathways", + } + + mechanism = category_mechanisms.get(category, "Unknown mechanism") + log += f"- Category-specific mechanism: {mechanism}\n" + + log += "\n" + + # Summary and recommendations + log += "Summary and Recommendations:\n" + log += "-" * 28 + "\n" + + severity_counts = {} + for interaction in interactions: + level = interaction.get("level", "Unknown") + severity_counts[level] = severity_counts.get(level, 0) + 1 + + log += f"- Total interactions analyzed: {len(interactions)}\n" + log += f"- Severity distribution: {dict(severity_counts)}\n" + + # Overall recommendation + if any(int_data.get("level") == "Major" for int_data in interactions): + log += "- Overall recommendation: AVOID - Major interaction detected\n" + log += "- Consider alternative medications\n" + elif any(int_data.get("level") == "Moderate" for int_data in interactions): + log += "- Overall recommendation: MONITOR - Moderate interaction present\n" + log += "- Close patient monitoring required\n" + else: + log += "- Overall recommendation: AWARENESS - Minor interactions only\n" + log += "- Standard monitoring appropriate\n" + + if detailed_analysis: + log += "\nMechanistic Considerations:\n" + log += f"- Monitor for additive effects in the {category.replace('_', ' ')} system\n" + log += "- Consider potential for altered drug metabolism\n" + log += "- Be aware of possible changes in drug efficacy or toxicity\n" + log += "- Timing of administration may be important\n" + + except Exception as e: + log += f"Error during mechanism analysis: {str(e)}\n" + + return log + + +def find_alternative_drugs_ddinter(target_drug, contraindicated_drugs, therapeutic_class=None, data_lake_path=None): + """ + Find alternative drugs that don't interact with contraindicated drugs. + + Parameters + ---------- + target_drug : str + Drug to find alternatives for + contraindicated_drugs : list of str + List of drugs to avoid interactions with + therapeutic_class : str, optional + Limit search to specific therapeutic class + data_lake_path : str, optional + Path to data lake directory containing DDInter data + + Returns + ------- + str + Research log with alternative drug recommendations + """ + from datetime import datetime + + # Initialize research log + log = "Alternative Drug Finder (DDInter)\n" + log += "=" * 32 + "\n" + log += f"Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n" + + # Handle default data lake path + if data_lake_path is None: + data_lake_path = os.path.join(os.path.dirname(__file__), "schema_db") + + log += "Alternative Drug Search Parameters:\n" + log += f"- Target drug: {target_drug}\n" + log += f"- Contraindicated drugs: {', '.join(contraindicated_drugs)}\n" + log += f"- Therapeutic class filter: {therapeutic_class if therapeutic_class else 'All classes'}\n\n" + + try: + # Load DDInter data + drug_info, interaction_matrix, name_mapping = _load_ddinter_data(data_lake_path) + log += f"Successfully loaded DDInter database with {len(drug_info)} drugs\n\n" + + # Standardize target drug name + std_target = _standardize_drug_name(target_drug, name_mapping) + if not std_target: + log += f"Error: Target drug '{target_drug}' not found in DDInter database\n" + return log + + # Standardize contraindicated drug names + std_contraindicated = [] + missing_contraindicated = [] + + for drug in contraindicated_drugs: + std_drug = _standardize_drug_name(drug, name_mapping) + if std_drug: + std_contraindicated.append(std_drug) + else: + missing_contraindicated.append(drug) + + if missing_contraindicated: + log += "Warning: The following contraindicated drugs were not found:\n" + for drug in missing_contraindicated: + log += f"- {drug}\n" + log += "\n" + + # Get target drug information + target_id = name_mapping[std_target] + target_info = drug_info.get(target_id, {}) + target_categories = target_info.get("categories", []) + + log += "Target Drug Profile:\n" + log += f"- Drug: {target_drug}\n" + log += f"- Categories: {', '.join(target_categories)}\n" + log += f"- Total interactions: {len(target_info.get('interactions', []))}\n\n" + + # Find alternative drugs + alternatives = [] + + for drug_id, drug_data in drug_info.items(): + drug_name = drug_data["name"] + drug_categories = drug_data.get("categories", []) + + # Skip the target drug itself + if drug_id == target_id: + continue + + # Apply therapeutic class filter + if therapeutic_class: + if not any(therapeutic_class.lower() in cat.lower() for cat in drug_categories): + continue + else: + # Look for drugs in similar categories as target + if not any(cat in target_categories for cat in drug_categories): + continue + + # Check if this drug interacts with any contraindicated drugs + has_contraindicated_interactions = False + interaction_count = 0 + major_interactions = 0 + + std_drug_name = drug_data["standardized_name"] + + for contraindicated in std_contraindicated: + if std_drug_name in interaction_matrix and contraindicated in interaction_matrix[std_drug_name]: + interactions = interaction_matrix[std_drug_name][contraindicated] + interaction_count += len(interactions) + + # Check for major interactions + for interaction in interactions: + if interaction.get("level") == "Major": + major_interactions += 1 + has_contraindicated_interactions = True + break + + if has_contraindicated_interactions: + break + + # Add to alternatives if no major contraindicated interactions + if not has_contraindicated_interactions: + alternatives.append( + { + "name": drug_name, + "categories": drug_categories, + "interaction_count": interaction_count, + "total_interactions": len(drug_data.get("interactions", [])), + } + ) + + # Sort alternatives by interaction count (fewer is better) + alternatives.sort(key=lambda x: x["interaction_count"]) + + # Present results + log += "Alternative Drug Analysis:\n" + log += "-" * 25 + "\n" + + if alternatives: + log += f"Found {len(alternatives)} potential alternatives:\n\n" + + # Show top 10 alternatives + top_alternatives = alternatives[:10] + + for i, alt in enumerate(top_alternatives, 1): + log += f"{i}. {alt['name']}\n" + log += f" - Categories: {', '.join(alt['categories'])}\n" + log += f" - Interactions with contraindicated drugs: {alt['interaction_count']}\n" + log += f" - Total known interactions: {alt['total_interactions']}\n" + + # Risk assessment + if alt["interaction_count"] == 0: + risk = "No known interactions" + elif alt["interaction_count"] <= 2: + risk = "Low interaction risk" + elif alt["interaction_count"] <= 5: + risk = "Moderate interaction risk" + else: + risk = "Higher interaction risk" + + log += f" - Risk assessment: {risk}\n\n" + + if len(alternatives) > 10: + log += f"... and {len(alternatives) - 10} additional alternatives\n\n" + else: + log += "No suitable alternatives found in the DDInter database\n" + log += "Consider:\n" + log += "- Expanding therapeutic class search criteria\n" + log += "- Consulting additional drug databases\n" + log += "- Seeking specialist pharmacological advice\n\n" + + # Recommendations + log += "Clinical Recommendations:\n" + log += "-" * 22 + "\n" + + if alternatives: + best_alternative = alternatives[0] + log += f"- Primary recommendation: {best_alternative['name']}\n" + log += "- Rationale: Lowest interaction risk with contraindicated drugs\n" + + if best_alternative["interaction_count"] == 0: + log += "- Safety profile: No known interactions with specified drugs\n" + else: + log += f"- Safety profile: {best_alternative['interaction_count']} minor interactions detected\n" + + log += "- Next steps: Verify therapeutic equivalence and dosing\n" + log += "- Monitoring: Standard clinical monitoring recommended\n" + else: + log += "- No direct alternatives identified\n" + log += "- Consider non-pharmacological approaches\n" + log += "- Consult clinical pharmacist or specialist\n" + log += "- Review patient's complete medication profile\n" + + log += "\nImportant Notes:\n" + log += "- This analysis is based on DDInter 2.0 data only\n" + log += "- Always verify therapeutic equivalence before substitution\n" + log += "- Consider patient-specific factors (allergies, comorbidities)\n" + log += "- Monitor patient response after any medication changes\n" + + except Exception as e: + log += f"Error during alternative drug search: {str(e)}\n" + + return log + + +# OpenFDA Integration Functions + + +class OpenFDAClient: + """ + Client for interacting with the FDA's OpenFDA API. + + Provides comprehensive drug safety monitoring, adverse event analysis, + and regulatory intelligence capabilities through the OpenFDA API. + """ + + BASE_URL = "https://api.fda.gov" + + def __init__(self): + import time + + import requests + + self.requests = requests + self.time = time + self.session = requests.Session() + self.session.headers.update({"User-Agent": "Biomni-Agent/1.0 (https://biomni.stanford.edu)"}) + self.retry_attempts = 3 + self.timeout = 30 + self.rate_limit_delay = 0.2 # 5 requests/second + self.last_request_time = 0 + + def _handle_rate_limiting(self): + """Implement rate limiting to respect FDA API limits.""" + current_time = self.time.time() + time_since_last = current_time - self.last_request_time + + if time_since_last < self.rate_limit_delay: + self.time.sleep(self.rate_limit_delay - time_since_last) + + self.last_request_time = self.time.time() + + def _validate_response(self, response_data: dict) -> dict: + """Validate FDA API response structure and handle variations.""" + if not isinstance(response_data, dict): + raise ValueError("Invalid FDA API response format") + + # Check for error responses + if "error" in response_data: + error_msg = response_data["error"].get("message", "Unknown FDA API error") + raise Exception(f"FDA API Error: {error_msg}") + + # Validate expected fields exist + if "meta" not in response_data and "results" not in response_data: + # Some endpoints return data directly without meta + return {"results": [response_data], "meta": {"results": {"total": 1}}} + + return response_data + + def _handle_api_variations(self, endpoint: str, params: dict) -> dict: + """Handle known FDA API endpoint variations and parameter mappings.""" + endpoint_param_mappings = { + "drug/event": { + "drug_name": "patient.drug.openfda.brand_name.exact", + "generic_name": "patient.drug.openfda.generic_name.exact", + }, + "drug/label": {"drug_name": "openfda.brand_name.exact", "generic_name": "openfda.generic_name.exact"}, + "drug/enforcement": {"drug_name": "openfda.brand_name.exact", "generic_name": "openfda.generic_name.exact"}, + } + + # Transform parameters based on endpoint + if endpoint in endpoint_param_mappings: + new_params = {} + for key, value in params.items(): + if key in endpoint_param_mappings[endpoint]: + new_params[endpoint_param_mappings[endpoint][key]] = value + else: + new_params[key] = value + return new_params + + return params + + def _build_fda_search_params(self, endpoint: str, params: dict) -> dict: + """Build FDA API search parameters from input parameters.""" + fda_params = {} + + # Handle drug name searches + if "drug_name" in params: + drug_name = params["drug_name"] + if endpoint == "drug/event": + # For adverse events, search in medicinalproduct field + fda_params["search"] = f"patient.drug.medicinalproduct:{drug_name}" + elif endpoint == "drug/label": + # For drug labels, search in brand name + fda_params["search"] = f"openfda.brand_name:{drug_name}" + elif endpoint == "drug/enforcement": + # For enforcement/recalls, search in brand name + fda_params["search"] = f"openfda.brand_name:{drug_name}" + + # Handle other parameters + for key, value in params.items(): + if key not in ["drug_name"]: # Skip drug_name as it's handled above + if key == "limit": + fda_params["limit"] = value + elif key == "skip": + fda_params["skip"] = value + # Add other FDA API parameters as needed + + return fda_params + + def _make_request(self, endpoint: str, params: dict) -> dict: + """Make API request with retry logic and error handling.""" + self._handle_rate_limiting() + + # Build FDA API search parameters + fda_params = self._build_fda_search_params(endpoint, params) + + for attempt in range(self.retry_attempts): + try: + response = self.session.get(f"{self.BASE_URL}/{endpoint}.json", params=fda_params, timeout=self.timeout) + + if response.status_code == 404: + return { + "results": [], + "meta": {"results": {"total": 0}}, + "message": "No results found for the specified query", + } + + response.raise_for_status() + + # Validate and normalize response + data = self._validate_response(response.json()) + + return data + + except self.requests.exceptions.Timeout: + if attempt == self.retry_attempts - 1: + raise Exception("FDA API request timed out after multiple attempts") from None + self.time.sleep(2**attempt) # Exponential backoff + + except self.requests.exceptions.HTTPError as e: + if e.response.status_code == 429: + # Rate limiting - wait and retry + self.time.sleep(5 * (attempt + 1)) + continue + else: + raise Exception(f"FDA API HTTP Error: {e.response.status_code}") from e + + except Exception as e: + if attempt == self.retry_attempts - 1: + raise Exception(f"FDA API request failed: {str(e)}") from e + self.time.sleep(2**attempt) + + return {} + + def query_adverse_events(self, drug_name: str, limit: int = 100) -> dict: + """Query adverse events with robust error handling and validation.""" + endpoint = "drug/event" + params = {"drug_name": drug_name, "limit": limit} + + try: + data = self._make_request(endpoint, params) + + # Add FDA disclaimer to results + data["disclaimer"] = ( + "FDA Disclaimer: These data do not establish causation. " + "Reports are voluntary and subject to reporting bias. " + "Data should not be used for regulatory decision-making." + ) + + return data + + except Exception as e: + return { + "results": [], + "meta": {"results": {"total": 0}}, + "error": str(e), + "disclaimer": ( + "FDA Disclaimer: These data do not establish causation. " + "Reports are voluntary and subject to reporting bias." + ), + } + + def query_drug_labels(self, drug_name: str, sections: list[str] | None = None) -> dict: + """Query FDA drug label information.""" + endpoint = "drug/label" + params = {"drug_name": drug_name, "limit": 50} + + return self._make_request(endpoint, params) + + def query_drug_recalls(self, drug_name: str, classification: list[str] | None = None) -> dict: + """Query FDA drug recall and enforcement information.""" + endpoint = "drug/enforcement" + params = {"drug_name": drug_name, "limit": 100} + + return self._make_request(endpoint, params) + + +# Helper Functions for OpenFDA Data Processing + + +def _standardize_drug_name_fda(drug_name: str) -> str: + """Standardize drug names for FDA API queries.""" + # Handle None/empty values + if not drug_name: + return "" + + # Remove common suffixes + suffixes = ["sodium", "hydrochloride", "sulfate", "phosphate", "acetate", "citrate"] + + # Clean and standardize + name = drug_name.strip().lower() + + for suffix in suffixes: + if name.endswith(f" {suffix}"): + name = name[: -len(f" {suffix}")] + + return name + + +def _apply_fda_filters(response_data: dict, filters: dict) -> dict: + """Apply post-query filtering to FDA responses.""" + if not response_data.get("results"): + return response_data + + filtered_results = [] + + for result in response_data["results"]: + include = True + + # Apply severity filter + if filters.get("severity_filter") or filters.get("severity"): + severity_list = filters.get("severity_filter", filters.get("severity", [])) + if "serious" in severity_list: + # For serious filter, only include if serious == '1' + if result.get("serious") != "1": + include = False + elif "non_serious" in severity_list: + # For non-serious filter, only include if serious != '1' + if result.get("serious") == "1": + include = False + + # Apply outcome filter + if (filters.get("outcome_filter") or filters.get("outcome")) and include: + outcome_list = filters.get("outcome_filter", filters.get("outcome", [])) + if "life_threatening" in outcome_list: + # Check if the result has life threatening outcome + if result.get("seriousnesslifethreatening") != "1": + include = False + elif "hospitalization" in outcome_list: + # Check if the result has hospitalization outcome + if result.get("seriousnesshospitalization") != "1": + include = False + elif "death" in outcome_list: + # Check if the result has death outcome + if result.get("seriousnessdeath") != "1": + include = False + + # Apply classification filter (for recalls) + if filters.get("classification") and include: + classification_list = filters.get("classification", []) + result_class = result.get("classification", "") + if result_class not in classification_list: + include = False + + if include: + filtered_results.append(result) + + response_data["results"] = filtered_results + response_data["meta"]["results"]["total"] = len(filtered_results) + + return response_data + + +def _extract_fda_safety_signals(response_list: list[dict]) -> dict: + """Extract safety signals from adverse event data.""" + drug_signals = {} + reaction_patterns = {} + temporal_patterns = {} + + for response in response_list: + if not response.get("results"): + continue + + for result in response["results"]: + # Extract drug information + drugs = result.get("patient", {}).get("drug", []) + for drug in drugs: + # Use the existing standardization function + drug_name = _standardize_drug_name_fda(drug.get("medicinalproduct", "")) + if drug_name: + if drug_name not in drug_signals: + drug_signals[drug_name] = {"total_reports": 0, "serious_reports": 0, "common_reactions": []} + + drug_signals[drug_name]["total_reports"] += 1 + if result.get("serious") == "1": + drug_signals[drug_name]["serious_reports"] += 1 + + # Extract reaction patterns + reactions = result.get("patient", {}).get("reaction", []) + for reaction in reactions: + reaction_name = reaction.get("reactionmeddrapt", "") + if reaction_name: + if reaction_name not in reaction_patterns: + reaction_patterns[reaction_name] = { + "count": 0, + "severity_counts": {"serious": 0, "non_serious": 0}, + } + + reaction_patterns[reaction_name]["count"] += 1 + + # Count severity + if result.get("serious") == "1": + reaction_patterns[reaction_name]["severity_counts"]["serious"] += 1 + else: + reaction_patterns[reaction_name]["severity_counts"]["non_serious"] += 1 + + # Extract temporal patterns + receipt_date = result.get("receiptdate") + if receipt_date and len(receipt_date) >= 6: + year_month = receipt_date[:6] # YYYYMM + if year_month not in temporal_patterns: + temporal_patterns[year_month] = {"count": 0, "serious_count": 0} + + temporal_patterns[year_month]["count"] += 1 + if result.get("serious") == "1": + temporal_patterns[year_month]["serious_count"] += 1 + + # Build common reactions for each drug based on actual data + for drug_name in drug_signals: + # Find reactions that occurred with this specific drug + drug_reactions = {} + + for response in response_list: + if not response.get("results"): + continue + + for result in response["results"]: + drugs = result.get("patient", {}).get("drug", []) + has_this_drug = any( + _standardize_drug_name_fda(drug.get("medicinalproduct", "")) == drug_name for drug in drugs + ) + + if has_this_drug: + reactions = result.get("patient", {}).get("reaction", []) + for reaction in reactions: + reaction_name = reaction.get("reactionmeddrapt", "") + if reaction_name: + if reaction_name not in drug_reactions: + drug_reactions[reaction_name] = 0 + drug_reactions[reaction_name] += 1 + + # Get top 3 reactions for this drug + top_reactions = sorted(drug_reactions.items(), key=lambda x: x[1], reverse=True)[:3] + drug_signals[drug_name]["common_reactions"] = [r[0] for r in top_reactions] + + return { + "drug_signals": drug_signals, + "reaction_patterns": reaction_patterns, + "temporal_patterns": temporal_patterns, + } + + +def _generate_fda_statistics(response_data: dict) -> dict: + """Generate summary statistics from FDA responses.""" + stats = { + "total_reports": 0, + "serious_reports": 0, + "death_reports": 0, + "life_threatening_reports": 0, + "hospitalization_reports": 0, + "top_reactions": [], + "temporal_pattern": {}, + } + + if not response_data.get("results"): + return stats + + reaction_counts = {} + + for result in response_data["results"]: + stats["total_reports"] += 1 + + # Count serious reports + if result.get("serious") == "1": + stats["serious_reports"] += 1 + + # Count specific outcomes + outcomes = result.get("patient", {}).get("reaction", []) + for outcome in outcomes: + outcome_name = outcome.get("reactionmeddrapt", "Unknown") + reaction_counts[outcome_name] = reaction_counts.get(outcome_name, 0) + 1 + + # Count deaths and other serious outcomes + if result.get("patient", {}).get("patientdeath"): + stats["death_reports"] += 1 + + if result.get("patient", {}).get("patientlifethreatening"): + stats["life_threatening_reports"] += 1 + + if result.get("patient", {}).get("patienthospitalization"): + stats["hospitalization_reports"] += 1 + + # Top reactions + sorted_reactions = sorted(reaction_counts.items(), key=lambda x: x[1], reverse=True) + stats["top_reactions"] = sorted_reactions[:10] + + # Report distribution + stats["report_distribution"] = { + "serious_percentage": (stats["serious_reports"] / stats["total_reports"] * 100) + if stats["total_reports"] > 0 + else 0, + "non_serious_percentage": ((stats["total_reports"] - stats["serious_reports"]) / stats["total_reports"] * 100) + if stats["total_reports"] > 0 + else 0, + "death_percentage": (stats["death_reports"] / stats["total_reports"] * 100) + if stats["total_reports"] > 0 + else 0, + } + + return stats + + +def _format_adverse_event_summary(response_data: dict, drug_name: str, include_details: bool = True) -> str: + """Format adverse event data into readable summary.""" + if not response_data.get("results"): + return f"No adverse events found for {drug_name} in the FDA database." + + stats = _generate_fda_statistics(response_data) + + summary = "Adverse Event Summary\n" + summary += "=" * 21 + "\n" + summary += f"Drug: {drug_name}\n" + summary += f"Total Reports: {stats['total_reports']:,}\n\n" + + if stats["total_reports"] > 0: + summary += "Summary Statistics:\n" + summary += f"- Serious Reports: {stats['serious_reports']:,} ({stats['serious_reports'] / stats['total_reports'] * 100:.1f}%)\n" + + if stats["death_reports"] > 0: + summary += ( + f"- Deaths: {stats['death_reports']:,} ({stats['death_reports'] / stats['total_reports'] * 100:.1f}%)\n" + ) + + if stats["life_threatening_reports"] > 0: + summary += f"- Life-threatening: {stats['life_threatening_reports']:,} ({stats['life_threatening_reports'] / stats['total_reports'] * 100:.1f}%)\n" + + if stats["hospitalization_reports"] > 0: + summary += f"- Hospitalizations: {stats['hospitalization_reports']:,} ({stats['hospitalization_reports'] / stats['total_reports'] * 100:.1f}%)\n" + + if stats["top_reactions"]: + summary += "\nCommon Reactions:\n" + for i, (reaction, count) in enumerate(stats["top_reactions"][:5], 1): + summary += f"{i}. {reaction} ({count:,} reports)\n" + + # Add FDA disclaimer + summary += "\n" + response_data.get("disclaimer", "") + + return summary + + +def _format_drug_label_summary(response_data: dict, drug_name: str, sections: list[str] | None = None) -> str: + """Format drug label information into readable summary.""" + if not response_data.get("results"): + return f"No drug label information found for {drug_name} in the FDA database." + + result = response_data["results"][0] # Use first result + + summary = "OpenFDA Drug Label Information\n" + summary += "=" * 29 + "\n" + summary += f"Drug: {drug_name}\n" + + # Extract key information + if "effective_time" in result: + summary += f"Effective Date: {result['effective_time']}\n" + + if "openfda" in result: + openfda = result["openfda"] + if "brand_name" in openfda: + summary += f"Brand Name: {', '.join(openfda['brand_name'])}\n" + if "generic_name" in openfda: + summary += f"Generic Name: {', '.join(openfda['generic_name'])}\n" + if "manufacturer_name" in openfda: + summary += f"Manufacturer: {', '.join(openfda['manufacturer_name'])}\n" + + summary += "\n" + + # Display specific sections + section_mapping = { + "indications_and_usage": "Indications and Usage", + "contraindications": "Contraindications", + "warnings": "Warnings", + "dosage_and_administration": "Dosage and Administration", + "adverse_reactions": "Adverse Reactions", + "clinical_pharmacology": "Clinical Pharmacology", + } + + sections_to_show = sections if sections else section_mapping.keys() + + for section_key in sections_to_show: + if section_key in result: + section_title = section_mapping.get(section_key, section_key.title()) + summary += f"{section_title}:\n" + + content = result[section_key] + if isinstance(content, list): + content = " ".join(content) + + # Truncate long content + if len(content) > 500: + content = content[:500] + "..." + + summary += f"{content}\n\n" + + return summary + + +def _format_recall_summary(response_data: dict, drug_name: str, include_details: bool = True) -> str: + """Format recall information into structured output.""" + if not response_data.get("results"): + return f"No drug recalls found for {drug_name} in the FDA database." + + summary = "OpenFDA Drug Recall Information\n" + summary += "=" * 31 + "\n" + summary += f"Drug: {drug_name}\n" + summary += f"Total recalls found: {len(response_data['results'])}\n\n" + + if include_details: + summary += "Recall Details:\n" + + for i, recall in enumerate(response_data["results"][:5], 1): # Show top 5 + summary += f"{i}. Recall Number: {recall.get('recall_number', 'N/A')}\n" + summary += f" - Product: {recall.get('product_description', 'N/A')}\n" + summary += f" - Classification: {recall.get('classification', 'N/A')}\n" + summary += f" - Reason: {recall.get('reason_for_recall', 'N/A')}\n" + summary += f" - Date: {recall.get('recall_initiation_date', 'N/A')}\n" + summary += f" - Status: {recall.get('status', 'N/A')}\n" + summary += f" - Distribution: {recall.get('distribution_pattern', 'N/A')}\n\n" + + if len(response_data["results"]) > 5: + summary += f"... and {len(response_data['results']) - 5} additional recalls\n" + + return summary + + +def _format_safety_signal_summary( + signals_data: dict, + drug_list: list[str], + comparison_period: tuple[str, str] | None = None, + signal_threshold: float = 2.0, +) -> str: + """Format safety signal analysis results.""" + summary = "OpenFDA Safety Signal Analysis\n" + summary += "=" * 29 + "\n" + summary += f"Drugs analyzed: {drug_list}\n" + + # Add comparison period and threshold info + if comparison_period: + summary += f"Comparison period: {comparison_period[0]} to {comparison_period[1]}\n" + if signal_threshold != 2.0: + summary += f"Signal threshold: {signal_threshold}\n" + summary += "\n" + + if not signals_data: + summary += "No safety signals detected.\n" + return summary + + summary += "Signal Detection Results:\n" + + # Handle the actual data structure from _extract_fda_safety_signals + drug_signals = signals_data.get("drug_signals", {}) + reaction_patterns = signals_data.get("reaction_patterns", {}) + signals_data.get("temporal_patterns", {}) + + # Display drug-specific signals + for i, drug_name in enumerate(drug_list, 1): + drug_data = drug_signals.get(drug_name, {}) + if drug_data: + summary += f"{i}. {drug_name.title()}\n" + summary += f" - Total reports: {drug_data['total_reports']:,}\n" + summary += f" - Serious reports: {drug_data['serious_reports']:,}\n" + + if drug_data.get("common_reactions"): + summary += f" - Common reactions: {', '.join(drug_data['common_reactions'])}\n" + summary += "\n" + else: + summary += f"{i}. {drug_name.title()}\n" + summary += " - No data found\n\n" + + # Display cross-drug reaction patterns + if reaction_patterns: + summary += "Cross-drug Analysis:\n" + sorted_reactions = sorted(reaction_patterns.items(), key=lambda x: x[1]["count"], reverse=True) + + for reaction, data in sorted_reactions[:5]: # Show top 5 reactions + summary += f"- {reaction}: {data['count']:,} reports\n" + if data["severity_counts"]["serious"] > 0: + summary += f" * Serious: {data['severity_counts']['serious']:,}\n" + + # Add trend analysis if comparison period is specified + if comparison_period: + summary += "\nTrend Analysis:\n" + summary += f"Comparing current period to {comparison_period[0]} - {comparison_period[1]}\n" + summary += "* Trend detection based on temporal patterns in adverse event reports\n" + summary += "* Analysis considers seasonal variations and reporting delays\n" + + return summary + + +# Main OpenFDA Integration Functions + + +def query_fda_adverse_events( + drug_name: str, + date_range: tuple[str, str] | None = None, + severity_filter: list[str] | None = None, + outcome_filter: list[str] | None = None, + limit: int = 100, +) -> str: + """ + Query FDA adverse event reports for specific drugs. + + Args: + drug_name: Name of the drug to query + date_range: Optional date range as (start_date, end_date) in YYYY-MM-DD format + severity_filter: Optional filter by severity levels ["serious", "non_serious"] + outcome_filter: Optional filter by outcomes ["life_threatening", "hospitalization", "death"] + limit: Maximum number of results to return + + Returns: + Formatted string with adverse event analysis + """ + try: + # Validate input + if not drug_name or not drug_name.strip(): + return "Error: Drug name cannot be empty" + + client = OpenFDAClient() + + # Standardize drug name + standardized_name = _standardize_drug_name_fda(drug_name) + if not standardized_name: + return f"Error: Unable to standardize drug name '{drug_name}'" + + # Query adverse events + response = client.query_adverse_events(standardized_name, limit=limit) + + # Apply filters if specified + if severity_filter or outcome_filter: + filters = {"severity_filter": severity_filter, "outcome_filter": outcome_filter} + response = _apply_fda_filters(response, filters) + + # Format results with main function title + formatted_result = _format_adverse_event_summary(response, drug_name, include_details=True) + + # Replace title for main function + if formatted_result.startswith("Adverse Event Summary"): + formatted_result = formatted_result.replace( + "Adverse Event Summary\n" + "=" * 21, "OpenFDA Adverse Event Query Results\n" + "=" * 35, 1 + ) + + # Add filter and date range info if specified + lines = formatted_result.split("\n") + insert_index = -1 + + # Find insertion point (after drug name) + for i, line in enumerate(lines): + if line.startswith("Drug: "): + insert_index = i + 1 + break + + if insert_index >= 0: + # Add date range info + if date_range: + lines.insert(insert_index, f"Date range: {date_range[0]} to {date_range[1]}") + insert_index += 1 + + # Add severity filter info + if severity_filter: + lines.insert(insert_index, f"Severity filter: {severity_filter}") + insert_index += 1 + + # Add outcome filter info + if outcome_filter: + lines.insert(insert_index, f"Outcome filter: {outcome_filter}") + insert_index += 1 + + formatted_result = "\n".join(lines) + + return formatted_result + + except Exception as e: + return f"Error querying FDA adverse events for {drug_name}: {str(e)}" + + +def get_fda_drug_label_info(drug_name: str, sections: list[str] | None = None) -> str: + """ + Retrieve FDA drug label information. + + Args: + drug_name: Name of the drug to query + sections: Optional list of specific sections to retrieve + ["indications_and_usage", "contraindications", "warnings", "dosage_and_administration"] + + Returns: + Formatted string with drug label information + """ + try: + # Validate input + if not drug_name or not drug_name.strip(): + return "Error: Drug name cannot be empty" + + client = OpenFDAClient() + + # Standardize drug name + standardized_name = _standardize_drug_name_fda(drug_name) + if not standardized_name: + return f"Error: Unable to standardize drug name '{drug_name}'" + + # Query drug labels + response = client.query_drug_labels(standardized_name, sections=sections) + + # Check if we got results + if not response.get("results"): + return f"No label information found for drug: {drug_name}" + + # Format results + return _format_drug_label_summary(response, drug_name, sections=sections) + + except Exception as e: + return f"Error retrieving FDA drug label for {drug_name}: {str(e)}" + + +def check_fda_drug_recalls( + drug_name: str, classification: list[str] | None = None, date_range: tuple[str, str] | None = None +) -> str: + """ + Check for FDA drug recalls and enforcement actions. + + Args: + drug_name: Name of the drug to check + classification: Optional filter by recall class ["Class I", "Class II", "Class III"] + date_range: Optional date range for recalls + + Returns: + Formatted string with recall information + """ + try: + # Validate input + if not drug_name or not drug_name.strip(): + return "Error: Drug name cannot be empty" + + client = OpenFDAClient() + + # Standardize drug name + standardized_name = _standardize_drug_name_fda(drug_name) + if not standardized_name: + return f"Error: Unable to standardize drug name '{drug_name}'" + + # Query drug recalls + response = client.query_drug_recalls(standardized_name, classification=classification) + + # Format results with filter information + formatted_result = _format_recall_summary(response, drug_name, include_details=True) + + # Add filter information to the output + if classification: + formatted_result = formatted_result.replace( + f"Drug: {drug_name}\n", f"Drug: {drug_name}\nClassification filter: {', '.join(classification)}\n" + ) + + if date_range: + formatted_result = formatted_result.replace( + f"Drug: {drug_name}\n", f"Drug: {drug_name}\nDate range: {date_range[0]} to {date_range[1]}\n" + ) + + return formatted_result + + except Exception as e: + return f"Error checking FDA drug recalls for {drug_name}: {str(e)}" + + +def analyze_fda_safety_signals( + drug_list: list[str], comparison_period: tuple[str, str] | None = None, signal_threshold: float = 2.0 +) -> str: + """ + Analyze safety signals across multiple drugs. + + Args: + drug_list: List of drug names to analyze + comparison_period: Optional comparison time period + signal_threshold: Threshold for signal detection + + Returns: + Formatted string with safety signal analysis + """ + try: + # Validate input parameters + if not drug_list: + return "Error: At least one drug must be provided for analysis" + + if len(drug_list) < 2: + return "Error: At least 2 drugs required for comparative safety signal analysis" + + # Validate drug names + valid_drugs = [drug.strip() for drug in drug_list if drug and drug.strip()] + if not valid_drugs: + return "Error: No valid drug names provided" + + client = OpenFDAClient() + + # Collect data for all drugs + all_responses = [] + + for drug in valid_drugs: + standardized_name = _standardize_drug_name_fda(drug) + if standardized_name: # Only query if standardization worked + response = client.query_adverse_events(standardized_name, limit=200) + + if response.get("results"): + all_responses.append(response) + + # Check if we got any data + if not all_responses: + return "Error: No adverse event data found for any of the provided drugs" + + # Extract safety signals + signals = _extract_fda_safety_signals(all_responses) + + # Format results with comparison period and threshold info + return _format_safety_signal_summary(signals, valid_drugs, comparison_period, signal_threshold) + + except Exception as e: + return f"Error analyzing FDA safety signals: {str(e)}" diff --git a/BioScientist/agent_system/engines/v1_executor_backup/tool/physiology.py b/BioScientist/agent_system/engines/v1_executor_backup/tool/physiology.py new file mode 100644 index 0000000000000000000000000000000000000000..6ce2544388a3bbbe16a0141ec2ed6255ff07058e --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/tool/physiology.py @@ -0,0 +1,1359 @@ +def reconstruct_3d_face_from_mri(mri_file_path, output_dir="./output", subject_id="subject", threshold_value=300): + """Generate a 3D model of facial anatomy from MRI scans of the head and neck. + + Parameters + ---------- + mri_file_path : str + Path to the MRI scan file (NIfTI format: .nii or .nii.gz) + output_dir : str + Directory where output files will be saved + subject_id : str + Identifier for the subject, used in output filenames + threshold_value : int + Threshold value for initial segmentation of facial tissues + + Returns + ------- + str + Research log detailing the reconstruction process and output file locations + + """ + import os + import time + from datetime import datetime + + import nibabel as nib + import numpy as np + import SimpleITK as sitk + from skimage import measure + + # Create output directory if it doesn't exist + os.makedirs(output_dir, exist_ok=True) + + log = [] + log.append(f"3D Facial Reconstruction Log - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + log.append(f"Subject ID: {subject_id}") + log.append(f"MRI Source: {mri_file_path}") + log.append("-" * 50) + + # Step 1: Load MRI data + start_time = time.time() + log.append("Step 1: Loading MRI data") + + try: + # Try loading with nibabel first + mri_data = nib.load(mri_file_path) + volume_data = mri_data.get_fdata() + log.append(f" Loaded MRI data with dimensions: {volume_data.shape}") + log.append(f" Data type: {volume_data.dtype}") + except Exception as e: + log.append(f" Error loading with nibabel: {str(e)}") + log.append(" Attempting to load with SimpleITK...") + + try: + # Fallback to SimpleITK + mri_image = sitk.ReadImage(mri_file_path) + volume_data = sitk.GetArrayFromImage(mri_image) + log.append(f" Loaded MRI data with dimensions: {volume_data.shape}") + except Exception as e2: + log.append(f" Failed to load MRI data: {str(e2)}") + return "\n".join(log) + + elapsed = time.time() - start_time + log.append(f" Completed in {elapsed:.2f} seconds") + + # Step 2: Preprocess MRI data + start_time = time.time() + log.append("Step 2: Preprocessing MRI data") + + # Normalize data to 0-1 range for processing + data_min = np.min(volume_data) + data_max = np.max(volume_data) + normalized_data = (volume_data - data_min) / (data_max - data_min) + + # Apply noise reduction filter using SimpleITK + sitk_image = sitk.GetImageFromArray(normalized_data) + smoothed_image = sitk.CurvatureFlow(image1=sitk_image, timeStep=0.125, numberOfIterations=5) + smoothed_data = sitk.GetArrayFromImage(smoothed_image) + + log.append(" Applied noise reduction filter") + elapsed = time.time() - start_time + log.append(f" Completed in {elapsed:.2f} seconds") + + # Step 3: Segment facial tissues + start_time = time.time() + log.append("Step 3: Segmenting facial tissues") + + # Apply threshold to segment facial tissues + normalized_threshold = threshold_value / (data_max - data_min) + binary_mask = smoothed_data > normalized_threshold + + # Remove small isolated regions + sitk_mask = sitk.GetImageFromArray(binary_mask.astype(np.uint8)) + cleaned_mask = sitk.BinaryOpeningByReconstruction(sitk_mask, [3, 3, 3]) + segmentation = sitk.GetArrayFromImage(cleaned_mask) + + log.append(f" Applied threshold value: {threshold_value}") + log.append(" Removed small isolated regions") + + # Save segmentation mask + segmentation_file = os.path.join(output_dir, f"{subject_id}_face_segmentation.nii.gz") + sitk.WriteImage(sitk.GetImageFromArray(segmentation.astype(np.uint8)), segmentation_file) + log.append(f" Saved segmentation mask to: {segmentation_file}") + + elapsed = time.time() - start_time + log.append(f" Completed in {elapsed:.2f} seconds") + + # Step 4: Generate 3D surface mesh + start_time = time.time() + log.append("Step 4: Generating 3D surface mesh") + + # Create mesh using marching cubes algorithm + try: + verts, faces, normals, values = measure.marching_cubes(segmentation, level=0.5) + log.append(f" Generated mesh with {len(verts)} vertices and {len(faces)} faces") + + # Save mesh as OBJ file + mesh_file = os.path.join(output_dir, f"{subject_id}_face_3d_model.obj") + + with open(mesh_file, "w") as f: + # Write vertices + for v in verts: + f.write(f"v {v[0]} {v[1]} {v[2]}\n") + + # Write vertex normals + for n in normals: + f.write(f"vn {n[0]} {n[1]} {n[2]}\n") + + # Write faces (OBJ uses 1-indexed vertices) + for face in faces: + f.write(f"f {face[0] + 1} {face[1] + 1} {face[2] + 1}\n") + + log.append(f" Saved 3D model to: {mesh_file}") + except Exception as e: + log.append(f" Error generating mesh: {str(e)}") + + elapsed = time.time() - start_time + log.append(f" Completed in {elapsed:.2f} seconds") + + # Final summary + log.append("-" * 50) + log.append("Reconstruction Summary:") + log.append(f" Subject ID: {subject_id}") + log.append(f" Segmentation file: {os.path.basename(segmentation_file)}") + log.append(f" 3D Model file: {os.path.basename(mesh_file)}") + log.append("-" * 50) + + return "\n".join(log) + + +def analyze_abr_waveform_p1_metrics(time_ms, amplitude_uv): + """Extracts P1 amplitude and latency from Auditory Brainstem Response (ABR) waveform data. + + P1 (Wave I) is typically the first positive peak in the ABR waveform and is a critical + marker for auditory function assessment. + + Parameters + ---------- + time_ms : array-like + Time points of the ABR recording in milliseconds + amplitude_uv : array-like + Amplitude values of the ABR recording in microvolts + + Returns + ------- + str + Research log summarizing the analysis steps and results, including P1 amplitude and latency + + """ + import numpy as np + from scipy.signal import find_peaks + + # Convert inputs to numpy arrays if they aren't already + time_ms = np.array(time_ms) + amplitude_uv = np.array(amplitude_uv) + + # Find all positive peaks in the waveform + # Height parameter can be adjusted based on expected signal-to-noise ratio + peaks, peak_properties = find_peaks(amplitude_uv, height=0) + + if len(peaks) == 0: + return "Research Log: No positive peaks detected in the ABR waveform." + + # Calculate peak heights (amplitudes) + peak_heights = peak_properties["peak_heights"] + + # Get the time points (latencies) of the peaks + peak_latencies = time_ms[peaks] + + # Identify the P1 peak (typically the first significant peak, usually within 1-2 ms) + # We'll look for peaks in the expected time window for P1 (typically 1-3 ms) + p1_window_indices = np.where((peak_latencies >= 1) & (peak_latencies <= 3))[0] + + if len(p1_window_indices) == 0: + # If no peaks in the expected P1 window, use the first peak as P1 + p1_index = 0 + else: + # Use the highest peak in the P1 window + p1_index = p1_window_indices[np.argmax(peak_heights[p1_window_indices])] + + p1_amplitude = peak_heights[p1_index] + p1_latency = peak_latencies[p1_index] + + # Create research log + log = ( + f"Research Log: ABR Waveform P1 Analysis\n" + f"- Analyzed waveform with {len(time_ms)} data points\n" + f"- Detected {len(peaks)} positive peaks in the waveform\n" + f"- Identified P1 peak metrics:\n" + f" * P1 Amplitude: {p1_amplitude:.2f} μV\n" + f" * P1 Latency: {p1_latency:.2f} ms\n" + f"- Analysis complete" + ) + + return log + + +def analyze_ciliary_beat_frequency(video_path, roi_count=5, min_freq=0, max_freq=30, output_dir="./"): + """Analyze ciliary beat frequency from high-speed video microscopy data using FFT analysis. + + Parameters + ---------- + video_path : str + Path to the high-speed video microscopy file of ciliary beating + roi_count : int, optional + Number of regions of interest to analyze (default: 5) + min_freq : float, optional + Minimum frequency to consider in Hz (default: 0) + max_freq : float, optional + Maximum frequency to consider in Hz (default: 30) + output_dir : str, optional + Directory to save output files (default: current directory) + + Returns + ------- + str + Research log summarizing the analysis steps and results + + """ + import os + from datetime import datetime + + import cv2 + import numpy as np + import pandas as pd + from scipy.fftpack import fft + + # Create output directory if it doesn't exist + os.makedirs(output_dir, exist_ok=True) + + # Initialize research log + log = [] + log.append(f"CILIARY BEAT FREQUENCY ANALYSIS - {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") + log.append(f"Video file: {video_path}") + + # Open the video file + cap = cv2.VideoCapture(video_path) + if not cap.isOpened(): + return f"Error: Could not open video file {video_path}" + + # Get video properties + fps = cap.get(cv2.CAP_PROP_FPS) + frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) + width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) + height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) + + log.append(f"Video properties: {width}x{height}, {fps} fps, {frame_count} frames") + + # Extract frames and prepare for analysis + log.append("Extracting frames and preparing for analysis...") + + # Create regions of interest (ROIs) + rois = [] + roi_size = min(width, height) // 10 # ROI size as 1/10 of the smallest dimension + + # Generate ROIs in a grid pattern + rows = int(np.sqrt(roi_count)) + cols = (roi_count + rows - 1) // rows # Ceiling division + + for i in range(rows): + for j in range(cols): + if len(rois) >= roi_count: + break + + x = (j + 1) * width // (cols + 1) - roi_size // 2 + y = (i + 1) * height // (rows + 1) - roi_size // 2 + + # Ensure ROI is within frame boundaries + x = max(0, min(x, width - roi_size)) + y = max(0, min(y, height - roi_size)) + + rois.append((x, y, roi_size, roi_size)) + + log.append(f"Created {len(rois)} regions of interest for analysis") + + # Extract intensity time series for each ROI + intensity_series = [[] for _ in range(len(rois))] + + frame_idx = 0 + while cap.isOpened() and frame_idx < frame_count: + ret, frame = cap.read() + if not ret: + break + + gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) + + for i, (x, y, w, h) in enumerate(rois): + roi = gray[y : y + h, x : x + w] + intensity_series[i].append(np.mean(roi)) + + frame_idx += 1 + + cap.release() + + log.append(f"Processed {frame_idx} frames for time series analysis") + + # Perform FFT analysis on each ROI + frequencies = [] + + for i, series in enumerate(intensity_series): + # Convert to numpy array + series = np.array(series) + + # Remove DC component (mean) + series = series - np.mean(series) + + # Apply Hanning window to reduce spectral leakage + window = np.hanning(len(series)) + series = series * window + + # Compute FFT + fft_result = fft(series) + fft_magnitude = np.abs(fft_result[: len(series) // 2]) + + # Compute frequency axis + freq_axis = np.fft.fftfreq(len(series), d=1 / fps)[: len(series) // 2] + + # Find dominant frequency in the specified range + valid_indices = (freq_axis >= min_freq) & (freq_axis <= max_freq) + valid_freq = freq_axis[valid_indices] + valid_magnitude = fft_magnitude[valid_indices] + + if len(valid_magnitude) > 0 and np.max(valid_magnitude) > 0: + dominant_idx = np.argmax(valid_magnitude) + dominant_freq = valid_freq[dominant_idx] + frequencies.append(dominant_freq) + log.append(f"ROI #{i + 1}: Dominant frequency = {dominant_freq:.2f} Hz") + + # Calculate median frequency + if frequencies: + median_cbf = np.median(frequencies) + log.append(f"\nResults: Detected {len(frequencies)} valid ciliary regions") + log.append(f"Median ciliary beat frequency: {median_cbf:.2f} Hz") + + # Save results to CSV + results_file = os.path.join(output_dir, "cbf_results.csv") + df = pd.DataFrame({"ROI": range(1, len(frequencies) + 1), "Beat_Frequency_Hz": frequencies}) + df.to_csv(results_file, index=False) + log.append(f"\nDetailed frequency data saved to: {results_file}") + else: + log.append("\nNo valid ciliary beat frequencies detected in the specified range") + + return "\n".join(log) + + +def analyze_protein_colocalization(channel1_path, channel2_path, output_dir="./output", threshold_method="otsu"): + """Analyze colocalization between two fluorescently labeled proteins in microscopy images. + + Parameters + ---------- + channel1_path : str + Path to the first channel image file (fluorescent protein 1) + channel2_path : str + Path to the second channel image file (fluorescent protein 2) + output_dir : str + Directory to save output files (default: "./output") + threshold_method : str + Method for thresholding images ('otsu', 'li', or 'yen') (default: "otsu") + + Returns + ------- + str + Research log summarizing the colocalization analysis results and saved files + + """ + import os + + import matplotlib.pyplot as plt + import numpy as np + from scipy import stats + from skimage import exposure, filters, io + + # Create output directory if it doesn't exist + os.makedirs(output_dir, exist_ok=True) + + # Load images + img1 = io.imread(channel1_path) + img2 = io.imread(channel2_path) + + # Ensure images are same size + if img1.shape != img2.shape: + return "Error: Images must have the same dimensions" + + # Convert to grayscale if RGB + if len(img1.shape) > 2: + img1 = img1.mean(axis=2) + if len(img2.shape) > 2: + img2 = img2.mean(axis=2) + + # Normalize images to 0-1 range + img1_norm = exposure.rescale_intensity(img1.astype(float), out_range=(0, 1)) + img2_norm = exposure.rescale_intensity(img2.astype(float), out_range=(0, 1)) + + # Apply threshold to segment foreground from background + if threshold_method == "otsu": + thresh1 = filters.threshold_otsu(img1_norm) + thresh2 = filters.threshold_otsu(img2_norm) + elif threshold_method == "li": + thresh1 = filters.threshold_li(img1_norm) + thresh2 = filters.threshold_li(img2_norm) + elif threshold_method == "yen": + thresh1 = filters.threshold_yen(img1_norm) + thresh2 = filters.threshold_yen(img2_norm) + else: + thresh1 = filters.threshold_otsu(img1_norm) + thresh2 = filters.threshold_otsu(img2_norm) + + mask1 = img1_norm > thresh1 + mask2 = img2_norm > thresh2 + + # Calculate Pearson's correlation coefficient (whole image) + pearson_whole = stats.pearsonr(img1_norm.flatten(), img2_norm.flatten())[0] + + # Calculate Pearson's correlation coefficient (above threshold) + combined_mask = np.logical_or(mask1, mask2) + if np.sum(combined_mask) > 0: + pearson_masked = stats.pearsonr(img1_norm[combined_mask].flatten(), img2_norm[combined_mask].flatten())[0] + else: + pearson_masked = 0 + + # Calculate Mander's Overlap Coefficient (MOC) + numerator = np.sum(img1_norm * img2_norm) + denominator = np.sqrt(np.sum(img1_norm**2) * np.sum(img2_norm**2)) + moc = numerator / denominator if denominator > 0 else 0 + + # Calculate Mander's Colocalization Coefficients (MCC) + # M1: Fraction of channel1 overlapping with channel2 + # M2: Fraction of channel2 overlapping with channel1 + m1 = np.sum(img1_norm * mask2) / np.sum(img1_norm) if np.sum(img1_norm) > 0 else 0 + m2 = np.sum(img2_norm * mask1) / np.sum(img2_norm) if np.sum(img2_norm) > 0 else 0 + + # Create visualization + plt.figure(figsize=(10, 8)) + + # Plot original images and overlay + plt.subplot(2, 2, 1) + plt.imshow(img1_norm, cmap="Greens") + plt.title("Channel 1") + plt.axis("off") + + plt.subplot(2, 2, 2) + plt.imshow(img2_norm, cmap="Reds") + plt.title("Channel 2") + plt.axis("off") + + plt.subplot(2, 2, 3) + overlay = np.zeros((img1_norm.shape[0], img1_norm.shape[1], 3)) + overlay[:, :, 1] = img1_norm # Green channel + overlay[:, :, 0] = img2_norm # Red channel + plt.imshow(overlay) + plt.title("Overlay") + plt.axis("off") + + # Plot scatter plot of pixel intensities + plt.subplot(2, 2, 4) + plt.hexbin(img1_norm.flatten(), img2_norm.flatten(), gridsize=50, cmap="viridis", mincnt=1) + plt.xlabel("Channel 1 Intensity") + plt.ylabel("Channel 2 Intensity") + plt.title("Intensity Correlation") + + # Save visualization + viz_filename = os.path.join(output_dir, "colocalization_visualization.png") + plt.tight_layout() + plt.savefig(viz_filename) + plt.close() + + # Create research log + log = f"""Colocalization Analysis Research Log: + +1. Analyzed images: + - Channel 1: {channel1_path} + - Channel 2: {channel2_path} + +2. Thresholding method: {threshold_method} + - Channel 1 threshold: {thresh1:.4f} + - Channel 2 threshold: {thresh2:.4f} + +3. Colocalization metrics: + - Pearson's correlation coefficient (whole image): {pearson_whole:.4f} + - Pearson's correlation coefficient (above threshold): {pearson_masked:.4f} + - Mander's Overlap Coefficient (MOC): {moc:.4f} + - Mander's Colocalization Coefficient M1: {m1:.4f} + - Mander's Colocalization Coefficient M2: {m2:.4f} + +4. Visualization saved to: {viz_filename} + +Interpretation: +- Pearson's coefficient ranges from -1 to 1, with values closer to 1 indicating stronger colocalization +- Mander's coefficients range from 0 to 1, with values closer to 1 indicating higher overlap +- M1 represents the fraction of channel 1 overlapping with channel 2 +- M2 represents the fraction of channel 2 overlapping with channel 1 +""" + + return log + + +def perform_cosinor_analysis(time_data, physiological_data, period=24.0): + """Performs cosinor analysis on physiological time series data to characterize circadian rhythms. + + Parameters + ---------- + time_data : array-like + Time points of the measurements in hours + physiological_data : array-like + Physiological measurements corresponding to each time point + period : float, default=24.0 + Period of the rhythm in hours, default is 24 hours for circadian rhythms + + Returns + ------- + str + A research log summarizing the cosinor analysis process and results + + """ + from datetime import datetime + + import numpy as np + from scipy import optimize + + # Define the cosinor model function + def cosinor_model(t, mesor, amplitude, acrophase): + """Cosine function with baseline (mesor), amplitude, and phase shift.""" + return mesor + amplitude * np.cos((2 * np.pi * t / period) - acrophase) + + # Initial parameter guesses + mesor_guess = np.mean(physiological_data) + amplitude_guess = (np.max(physiological_data) - np.min(physiological_data)) / 2 + acrophase_guess = 0 # Starting with zero phase shift + + # Fit the cosinor model to the data + try: + params, params_covariance = optimize.curve_fit( + cosinor_model, + time_data, + physiological_data, + p0=[mesor_guess, amplitude_guess, acrophase_guess], + ) + + # Extract parameters + mesor, amplitude, acrophase = params + + # Convert acrophase to hours (peak time) + acrophase_hours = (acrophase * period) / (2 * np.pi) + acrophase_hours = acrophase_hours % period # Ensure it's within the period + + # Calculate model fit + predicted_values = cosinor_model(time_data, mesor, amplitude, acrophase) + + # Calculate goodness of fit metrics + residuals = physiological_data - predicted_values + ss_total = np.sum((physiological_data - np.mean(physiological_data)) ** 2) + ss_residual = np.sum(residuals**2) + r_squared = 1 - (ss_residual / ss_total) + + # Calculate standard errors from covariance matrix + std_errors = np.sqrt(np.diag(params_covariance)) + + # Create results summary + results = { + "Mesor": f"{mesor:.4f} ± {std_errors[0]:.4f}", + "Amplitude": f"{amplitude:.4f} ± {std_errors[1]:.4f}", + "Acrophase (radians)": f"{acrophase:.4f} ± {std_errors[2]:.4f}", + "Peak Time (hours)": f"{acrophase_hours:.2f}", + "R-squared": f"{r_squared:.4f}", + } + + # Create a research log + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + log = f"Cosinor Analysis Research Log - {timestamp}\n" + log += "=" * 50 + "\n\n" + log += "ANALYSIS SUMMARY:\n" + log += f"Analyzed {len(time_data)} data points over {max(time_data) - min(time_data):.1f} hours\n" + log += f"Fitted a cosine curve with period = {period} hours\n\n" + + log += "CIRCADIAN RHYTHM PARAMETERS:\n" + for param, value in results.items(): + log += f"- {param}: {value}\n" + + log += "\nINTERPRETATION:\n" + log += f"- The rhythm-adjusted mean (mesor) is {mesor:.4f}\n" + log += f"- The rhythm shows a peak-to-trough variation of {2 * amplitude:.4f}\n" + log += f"- The peak of the rhythm occurs at {acrophase_hours:.2f} hours\n" + log += f"- The model explains {r_squared * 100:.1f}% of the variation in the data\n" + + if r_squared < 0.3: + log += "\nNOTE: The low R-squared suggests a weak circadian pattern in this data.\n" + + return log + + except Exception as e: + return f"Error in cosinor analysis: {str(e)}" + + +def calculate_brain_adc_map(dwi_file_path, b_values, output_path="adc_map.nii.gz", mask_file_path=None): + """Calculate Apparent Diffusion Coefficient (ADC) map from diffusion-weighted MRI data. + + This function fits the DW-MRI data to the monoexponential diffusion model: + S = S0 * exp(-b * ADC), where S is the signal intensity, S0 is the signal without + diffusion weighting, b is the b-value, and ADC is the apparent diffusion coefficient. + + Parameters + ---------- + dwi_file_path : str + Path to the 4D NIfTI file containing diffusion-weighted MRI data, + where the 4th dimension corresponds to different b-values. + b_values : list or numpy.ndarray + List of b-values corresponding to each volume in the 4D DWI data. + output_path : str, optional + Path where the output ADC map will be saved (default: "adc_map.nii.gz"). + mask_file_path : str, optional + Path to a binary mask file to limit ADC calculation to brain regions (default: None). + + Returns + ------- + str + Research log summarizing the ADC mapping process and results. + + """ + import os + + import nibabel as nib + import numpy as np + from scipy.optimize import curve_fit + + # Start research log + research_log = "Brain Water Diffusion Mapping Research Log\n" + research_log += "=" * 50 + "\n\n" + + # Step 1: Load DWI data + research_log += "Step 1: Loading diffusion-weighted MRI data\n" + dwi_img = nib.load(dwi_file_path) + dwi_data = dwi_img.get_fdata() + research_log += f"- Loaded DWI data with shape: {dwi_data.shape}\n" + research_log += f"- B-values: {b_values}\n\n" + + # Step 2: Load mask if provided + if mask_file_path: + research_log += "Step 2: Loading brain mask\n" + mask_img = nib.load(mask_file_path) + mask_data = mask_img.get_fdata() > 0 # Convert to boolean + research_log += f"- Loaded mask with {np.sum(mask_data)} voxels to process\n\n" + else: + research_log += "Step 2: No mask provided, processing all voxels\n" + # Create a mask where signal in the baseline image is above a threshold + baseline_idx = np.argmin(b_values) # Typically b=0 image + mask_data = dwi_data[:, :, :, baseline_idx] > np.mean(dwi_data[:, :, :, baseline_idx]) * 0.1 + research_log += f"- Created automatic mask with {np.sum(mask_data)} voxels to process\n\n" + + # Step 3: Define the monoexponential diffusion model + research_log += "Step 3: Fitting data to monoexponential diffusion model\n" + research_log += "- Using model: S = S0 * exp(-b * ADC)\n" + + def mono_exp_model(b, S0, ADC): + return S0 * np.exp(-b * ADC) + + # Step 4: Calculate ADC map + shape = dwi_data.shape[:3] + adc_map = np.zeros(shape) + S0_map = np.zeros(shape) + + # Convert b_values to numpy array if it's not already + b_values = np.array(b_values) + + # Process only voxels within the mask + total_voxels = np.sum(mask_data) + processed_voxels = 0 + + for x in range(shape[0]): + for y in range(shape[1]): + for z in range(shape[2]): + if mask_data[x, y, z]: + signal = dwi_data[x, y, z, :] + + # Skip voxels with zero or negative values + if np.any(signal <= 0): + continue + + try: + # Fit the monoexponential model + params, _ = curve_fit( + mono_exp_model, + b_values, + signal, + p0=[signal[0], 0.001], # Initial guess + bounds=([0, 0], [np.inf, 0.01]), + ) + S0_map[x, y, z] = params[0] + adc_map[x, y, z] = params[1] + processed_voxels += 1 + except Exception: + # If fitting fails, set ADC to 0 + adc_map[x, y, z] = 0 + + research_log += f"- Successfully processed {processed_voxels} out of {total_voxels} voxels\n\n" + + # Step 5: Save ADC map + research_log += "Step 5: Saving ADC map\n" + # Convert ADC from mm²/s to µm²/ms for conventional display + adc_map_save = adc_map * 1000 # Convert to µm²/ms + adc_img = nib.Nifti1Image(adc_map_save, dwi_img.affine, dwi_img.header) + nib.save(adc_img, output_path) + research_log += f"- ADC map saved to: {os.path.abspath(output_path)}\n" + + # Step 6: Calculate summary statistics + valid_adcs = adc_map[mask_data & (adc_map > 0)] + if len(valid_adcs) > 0: + research_log += "\nStep 6: ADC Statistics (in µm²/ms):\n" + research_log += f"- Mean ADC: {np.mean(valid_adcs) * 1000:.4f}\n" + research_log += f"- Median ADC: {np.median(valid_adcs) * 1000:.4f}\n" + research_log += f"- Min ADC: {np.min(valid_adcs) * 1000:.4f}\n" + research_log += f"- Max ADC: {np.max(valid_adcs) * 1000:.4f}\n" + research_log += f"- Std Dev ADC: {np.std(valid_adcs) * 1000:.4f}\n" + + research_log += "\nBrain water diffusion mapping completed successfully.\n" + + return research_log + + +def analyze_endolysosomal_calcium_dynamics( + time_points, + luminescence_values, + treatment_time=None, + cell_type="", + treatment_name="", + output_file="calcium_analysis_results.txt", +): + """Analyze calcium dynamics in endo-lysosomal compartments using ELGA/ELGA1 probe data. + + Parameters + ---------- + time_points : numpy.ndarray or list + Time points of the measurements in seconds + luminescence_values : numpy.ndarray or list + Luminescence intensity values from ELGA/ELGA1 probes corresponding to Ca2+ levels + treatment_time : float, optional + Time point (in seconds) when treatment/stimulus was applied + cell_type : str, optional + Type of cells used in the experiment + treatment_name : str, optional + Name of the treatment or stimulus applied + output_file : str, optional + Name of the file to save detailed analysis results + + Returns + ------- + str + A research log summarizing the calcium dynamics analysis and key findings + + """ + import numpy as np + from scipy import signal + + # Convert inputs to numpy arrays if they aren't already + time_points = np.array(time_points) + luminescence_values = np.array(luminescence_values) + + # Calculate baseline Ca2+ levels (average of pre-treatment values or first 10% of data) + if treatment_time is not None: + baseline_indices = time_points < treatment_time + baseline = np.mean(luminescence_values[baseline_indices]) + baseline_std = np.std(luminescence_values[baseline_indices]) + else: + baseline_idx = int(len(luminescence_values) * 0.1) # First 10% of data + baseline = np.mean(luminescence_values[:baseline_idx]) + baseline_std = np.std(luminescence_values[:baseline_idx]) + + # Normalize data to baseline + normalized_values = luminescence_values / baseline + + # Find peaks in Ca2+ signal + peaks, properties = signal.find_peaks(normalized_values, height=1.1, distance=5) + + # Calculate key metrics + if len(peaks) > 0: + peak_heights = properties["peak_heights"] + max_peak_idx = peaks[np.argmax(peak_heights)] + max_peak_time = time_points[max_peak_idx] + max_peak_value = normalized_values[max_peak_idx] + + # Calculate response kinetics if treatment_time is provided + if treatment_time is not None and max_peak_time > treatment_time: + response_time = max_peak_time - treatment_time + else: + response_time = None + + # Calculate area under the curve (Ca2+ load) + auc = np.trapezoid(normalized_values - 1, time_points) + + # Calculate decay time (time to return to 50% of peak) + if max_peak_idx < len(normalized_values) - 1: + post_peak = normalized_values[max_peak_idx:] + post_peak_times = time_points[max_peak_idx:] + half_peak_value = 1 + (max_peak_value - 1) / 2 + + # Find where signal drops below half-peak + below_half_indices = np.where(post_peak < half_peak_value)[0] + if len(below_half_indices) > 0: + half_decay_idx = below_half_indices[0] + decay_time = post_peak_times[half_decay_idx] - max_peak_time + else: + decay_time = None + else: + decay_time = None + else: + max_peak_value = None + max_peak_time = None + response_time = None + auc = np.trapezoid(normalized_values - 1, time_points) + decay_time = None + + # Calculate coefficient of variation to measure Ca2+ fluctuations + cv = np.std(normalized_values) / np.mean(normalized_values) + + # Save detailed results to file + with open(output_file, "w") as f: + f.write("Endo-lysosomal Ca2+ Dynamics Analysis Results\n") + f.write("===========================================\n\n") + f.write(f"Cell Type: {cell_type}\n") + f.write(f"Treatment: {treatment_name}\n\n") + f.write(f"Baseline Ca2+ level: {baseline:.2f} ± {baseline_std:.2f}\n") + f.write(f"Number of detected Ca2+ peaks: {len(peaks)}\n") + + if max_peak_value is not None: + f.write(f"Maximum Ca2+ peak: {max_peak_value:.2f} (normalized to baseline)\n") + f.write(f"Time of maximum peak: {max_peak_time:.2f} seconds\n") + + if response_time is not None: + f.write(f"Response time after treatment: {response_time:.2f} seconds\n") + + if decay_time is not None: + f.write(f"Half-decay time: {decay_time:.2f} seconds\n") + + f.write(f"Area under curve (Ca2+ load): {auc:.2f}\n") + f.write(f"Coefficient of variation: {cv:.4f}\n\n") + + if len(peaks) > 0: + f.write("All detected peaks (time, normalized intensity):\n") + for i, peak_idx in enumerate(peaks): + f.write(f" Peak {i + 1}: {time_points[peak_idx]:.2f}s, {normalized_values[peak_idx]:.2f}\n") + + # Generate research log + log = f""" +Research Log: Endo-lysosomal Ca2+ Dynamics Analysis using ELGA/ELGA1 Probes + +SUMMARY: +Analyzed calcium dynamics in the endo-lysosomal compartment of {cell_type} cells using ELGA/ELGA1 probes. +{f"Cells were treated with {treatment_name} at {treatment_time} seconds." if treatment_time is not None else ""} + +METHODS: +- Measured luminescence values from ELGA/ELGA1 probes over {len(time_points)} time points +- Calculated baseline Ca2+ levels and normalized signals +- Identified Ca2+ peaks and analyzed response kinetics +- Quantified Ca2+ load using area under curve analysis + +RESULTS: +- Baseline Ca2+ level: {baseline:.2f} ± {baseline_std:.2f} (arbitrary units) +- Detected {len(peaks)} Ca2+ peaks in the endo-lysosomal compartment +{f"- Maximum Ca2+ response: {max_peak_value:.2f}x baseline" if max_peak_value is not None else "- No significant Ca2+ peaks detected"} +{f"- Response time: {response_time:.2f} seconds after treatment" if response_time is not None else ""} +{f"- Half-decay time: {decay_time:.2f} seconds" if decay_time is not None else ""} +- Total Ca2+ load (AUC): {auc:.2f} +- Ca2+ signal variability (CV): {cv:.4f} + +Detailed numerical results saved to: {output_file} +""" + + return log + + +def analyze_fatty_acid_composition_by_gc(gc_data_file, tissue_type, sample_id, output_directory="./results"): + """Analyzes fatty acid composition in tissue samples using gas chromatography data. + + Parameters + ---------- + gc_data_file : str + Path to the CSV file containing gas chromatography data with columns 'retention_time' and 'peak_area'. + tissue_type : str + Type of tissue sample (e.g., liver, kidney, heart, muscle, adipose). + sample_id : str + Identifier for the sample being analyzed. + output_directory : str, optional + Directory where result files will be saved (default: "./results"). + + Returns + ------- + str + Research log summarizing the analysis steps and results. + + """ + import os + from datetime import datetime + + import pandas as pd + + os.makedirs(output_directory, exist_ok=True) + + log = "FATTY ACID COMPOSITION ANALYSIS LOG\n" + log += f"Date: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n" + log += f"Sample ID: {sample_id}\n" + log += f"Tissue Type: {tissue_type}\n\n" + + log += "Step 1: Loading gas chromatography data\n" + try: + gc_data = pd.read_csv(gc_data_file) + expected_cols = {"retention_time", "peak_area"} + if not expected_cols.issubset(gc_data.columns): + missing_cols = expected_cols - set(gc_data.columns) + log += f" - Error: Missing columns: {', '.join(missing_cols)}\n" + return log + log += f" - Successfully loaded data from {gc_data_file}\n" + log += f" - Data contains {len(gc_data)} data points\n\n" + except Exception as e: + log += f" - Error loading data: {str(e)}\n" + return log + + log += "Step 2: Identifying fatty acid peaks\n" + + fatty_acid_standards = { + (2.1, 2.3): "Myristic acid (C14:0)", + (2.8, 3.0): "Palmitic acid (C16:0)", + (3.1, 3.3): "Palmitoleic acid (C16:1)", + (3.7, 3.9): "Stearic acid (C18:0)", + (4.0, 4.2): "Oleic acid (C18:1)", + (4.4, 4.6): "Linoleic acid (C18:2)", + (4.7, 4.9): "α-Linolenic acid (C18:3)", + (5.1, 5.3): "Arachidonic acid (C20:4)", + (5.5, 5.7): "EPA (C20:5)", + (6.3, 6.5): "DHA (C22:6)", + (5.8, 6.0): "t10c12-CLA", + } + + total_area = gc_data["peak_area"].sum() + if total_area == 0: + log += " - Error: Total peak area is zero, cannot calculate percentages.\n" + return log + + identified_peaks = {} + for (rt_min, rt_max), fatty_acid in fatty_acid_standards.items(): + peaks_in_range = gc_data[(gc_data["retention_time"] >= rt_min) & (gc_data["retention_time"] <= rt_max)] + area = peaks_in_range["peak_area"].sum() + percentage = (area / total_area) * 100 if area > 0 else 0.0 + identified_peaks[fatty_acid] = {"area": area, "percentage": percentage} + log += f" - {fatty_acid}: {percentage:.2f}% ({'Detected' if area > 0 else 'Not Detected'})\n" + + detected_count = sum(1 for fa in identified_peaks.values() if fa["area"] > 0) + log += f" - Total detected fatty acids: {detected_count}/{len(fatty_acid_standards)}\n\n" + + log += "Step 3: Generating fatty acid composition results\n" + results = ( + pd.DataFrame( + { + "Fatty Acid": list(identified_peaks), + "Peak Area": [identified_peaks[fa]["area"] for fa in identified_peaks], + "Percentage (%)": [identified_peaks[fa]["percentage"] for fa in identified_peaks], + } + ) + .sort_values("Percentage (%)", ascending=False) + .reset_index(drop=True) + ) + + output_file = os.path.join(output_directory, f"{sample_id}_{tissue_type}_fatty_acid_composition.csv") + results.to_csv(output_file, index=False) + log += f" - Results saved to: {output_file}\n\n" + + log += "Step 4: Summary of fatty acid composition\n" + detected_results = results[results["Peak Area"] > 0] + if not detected_results.empty: + top_fatty_acids = detected_results.head(3) + log += " - Most abundant fatty acids:\n" + for _, row in top_fatty_acids.iterrows(): + log += f" * {row['Fatty Acid']}: {row['Percentage (%)']:.2f}%\n" + + saturated = [ + "Myristic acid (C14:0)", + "Palmitic acid (C16:0)", + "Stearic acid (C18:0)", + ] + unsaturated = [fa for fa in identified_peaks if fa not in saturated] + + saturated_total = sum(identified_peaks[fa]["percentage"] for fa in saturated) + unsaturated_total = sum(identified_peaks[fa]["percentage"] for fa in unsaturated) + + if unsaturated_total > 0: + sat_unsat_ratio = saturated_total / unsaturated_total + log += f" - Saturated to unsaturated fatty acid ratio: {sat_unsat_ratio:.2f}\n" + else: + log += " - Unsaturated fatty acids not detected; cannot calculate ratio.\n" + else: + log += " - No fatty acids detected; please verify input data.\n" + + log += "\nAnalysis completed successfully." + + return log + + +def analyze_hemodynamic_data(pressure_data, sampling_rate, output_file="hemodynamic_results.csv"): + """Analyzes raw blood pressure data to calculate key hemodynamic parameters. + + Parameters + ---------- + pressure_data : numpy.ndarray + Raw blood pressure measurements in mmHg + sampling_rate : float + Data acquisition rate in Hz (samples per second) + output_file : str, optional + Filename to save the calculated parameters, default is "hemodynamic_results.csv" + + Returns + ------- + str + Research log summarizing the analysis steps and results + + """ + import numpy as np + import pandas as pd + from scipy import signal + + # Step 1: Preprocess the signal + # Apply a bandpass filter to remove noise while preserving the physiological signal + nyquist_freq = sampling_rate / 2 + low_cutoff = 0.5 / nyquist_freq # 0.5 Hz (to preserve slow components) + high_cutoff = 10 / nyquist_freq # 10 Hz (to preserve fast components but remove noise) + b, a = signal.butter(2, [low_cutoff, high_cutoff], btype="band") + filtered_data = signal.filtfilt(b, a, pressure_data) + + # Step 2: Find peaks (systolic) and troughs (diastolic) + # For systolic peaks + peaks, _ = signal.find_peaks(filtered_data, distance=sampling_rate * 0.5) # Minimum distance between peaks + + # For diastolic troughs (looking for minima between systolic peaks) + valleys = [] + for i in range(len(peaks) - 1): + segment = filtered_data[peaks[i] : peaks[i + 1]] + valley_idx = np.argmin(segment) + peaks[i] + valleys.append(valley_idx) + + # Step 3: Calculate hemodynamic parameters + # Systolic Blood Pressure (SBP) + sbp_values = filtered_data[peaks] + sbp = np.mean(sbp_values) + + # Diastolic Blood Pressure (DBP) + dbp_values = filtered_data[valleys] + dbp = np.mean(dbp_values) + + # Mean Arterial Pressure (MAP) + # MAP = DBP + (SBP - DBP) / 3 + map_value = dbp + (sbp - dbp) / 3 + + # Heart Rate (HR) + # Calculate average time between peaks and convert to beats per minute + peak_intervals = np.diff(peaks) / sampling_rate # Convert to seconds + heart_rate = 60 / np.mean(peak_intervals) # Convert to beats per minute + + # Step 4: Save results to CSV + results_df = pd.DataFrame( + { + "Parameter": ["SBP", "DBP", "MAP", "HR"], + "Value": [sbp, dbp, map_value, heart_rate], + "Unit": ["mmHg", "mmHg", "mmHg", "bpm"], + } + ) + results_df.to_csv(output_file, index=False) + + # Generate research log + log = f"""Hemodynamic Data Analysis Log: +1. Preprocessed raw pressure data using a bandpass filter (0.5-10 Hz) +2. Detected {len(peaks)} systolic peaks and {len(valleys)} diastolic troughs +3. Calculated hemodynamic parameters: + - Systolic Blood Pressure (SBP): {sbp:.2f} mmHg + - Diastolic Blood Pressure (DBP): {dbp:.2f} mmHg + - Mean Arterial Pressure (MAP): {map_value:.2f} mmHg + - Heart Rate (HR): {heart_rate:.2f} bpm +4. Results saved to {output_file} +""" + + return log + + +def simulate_thyroid_hormone_pharmacokinetics(parameters, initial_conditions, time_span=(0, 24), time_points=100): + """Simulates the transport and binding of thyroid hormones across different tissue compartments + using an ODE-based pharmacokinetic model. + + Parameters + ---------- + parameters : dict + Dictionary containing model parameters: + - transport_rates : dict of rate constants for hormone transport between compartments + - binding_constants : dict of association/dissociation constants for hormone-protein binding + - metabolism_rates : dict of rate constants for hormone metabolism in different tissues + - volumes : dict of compartment volumes + + initial_conditions : dict + Dictionary of initial concentrations for all molecular species in each compartment + + time_span : tuple, optional + Start and end time for simulation in hours (default: (0, 24)) + + time_points : int, optional + Number of time points to output (default: 100) + + Returns + ------- + str + Research log summarizing the simulation and results + + """ + import numpy as np + import pandas as pd + from scipy.integrate import solve_ivp + + # Extract parameters + transport_rates = parameters.get("transport_rates", {}) + binding_constants = parameters.get("binding_constants", {}) + metabolism_rates = parameters.get("metabolism_rates", {}) + volumes = parameters.get("volumes", {}) + + # Convert initial conditions dictionary to array for solver + species_names = list(initial_conditions.keys()) + y0 = [initial_conditions[name] for name in species_names] + + # Define the ODE system + def pk_ode_system(t, y): + # Initialize derivatives + dydt = np.zeros_like(y) + + # Map array indices back to named species for readability + species = {name: y[i] for i, name in enumerate(species_names)} + + # Example: For each compartment, calculate concentration changes + + # Blood compartment (example) + if "T4_blood_free" in species: + blood_idx = species_names.index("T4_blood_free") + # Transport from blood to tissues + for tissue in ["liver", "thyroid", "kidney"]: + tissue_key = f"T4_{tissue}_free" + if tissue_key in species: + tissue_idx = species_names.index(tissue_key) + rate_key = f"blood_to_{tissue}" + if rate_key in transport_rates: + # Transport out of blood + dydt[blood_idx] -= ( + transport_rates[rate_key] * species["T4_blood_free"] / volumes.get("blood", 1) + ) + # Transport into tissue + dydt[tissue_idx] += ( + transport_rates[rate_key] * species["T4_blood_free"] / volumes.get(tissue, 1) + ) + + # Protein binding in blood (example) + if "T4_blood_free" in species and "TBG_blood" in species and "T4_TBG_complex" in species: + free_idx = species_names.index("T4_blood_free") + protein_idx = species_names.index("TBG_blood") + complex_idx = species_names.index("T4_TBG_complex") + + # Association + if "k_on_T4_TBG" in binding_constants: + association = binding_constants["k_on_T4_TBG"] * species["T4_blood_free"] * species["TBG_blood"] + dydt[free_idx] -= association + dydt[protein_idx] -= association + dydt[complex_idx] += association + + # Dissociation + if "k_off_T4_TBG" in binding_constants: + dissociation = binding_constants["k_off_T4_TBG"] * species["T4_TBG_complex"] + dydt[free_idx] += dissociation + dydt[protein_idx] += dissociation + dydt[complex_idx] -= dissociation + + # Metabolism (example for liver) + if "T4_liver_free" in species and "T3_liver_free" in species: + t4_idx = species_names.index("T4_liver_free") + t3_idx = species_names.index("T3_liver_free") + + if "T4_to_T3_liver" in metabolism_rates: + conversion = metabolism_rates["T4_to_T3_liver"] * species["T4_liver_free"] + dydt[t4_idx] -= conversion + dydt[t3_idx] += conversion + + return dydt + + # Solve the ODE system + t_eval = np.linspace(time_span[0], time_span[1], time_points) + solution = solve_ivp( + pk_ode_system, + time_span, + y0, + method="BDF", # Equivalent to MATLAB's ode15s + t_eval=t_eval, + rtol=1e-4, + atol=1e-6, + ) + + # Process results + if solution.success: + # Create dataframe with results + results_df = pd.DataFrame(solution.y.T, columns=species_names) + results_df.insert(0, "Time", solution.t) + + # Save to CSV + output_file = "thyroid_hormone_pk_simulation_results.csv" + results_df.to_csv(output_file, index=False) + + # Generate research log + log = "## Thyroid Hormone Pharmacokinetic Simulation Results\n\n" + log += f"- Simulation time span: {time_span[0]} to {time_span[1]} hours\n" + log += f"- Number of molecular species modeled: {len(species_names)}\n" + log += ( + f"- Compartments included: {', '.join({name.split('_')[1] for name in species_names if '_' in name})}\n\n" + ) + + log += "### Key Observations:\n" + # Find peak concentrations and times + for species in species_names: + if "free" in species: # Focus on free hormone concentrations + idx = species_names.index(species) + peak_conc = np.max(solution.y[idx]) + peak_time = solution.t[np.argmax(solution.y[idx])] + final_conc = solution.y[idx][-1] + + log += f"- {species}: Peak concentration of {peak_conc:.4g} at {peak_time:.2f} hours, " + log += f"final concentration of {final_conc:.4g}\n" + + log += f"\nDetailed concentration profiles saved to: {output_file}\n" + + return log + else: + return f"Simulation failed: {solution.message}" + + +def quantify_amyloid_beta_plaques( + image_path, + output_dir="./results", + threshold_method="otsu", + min_plaque_size=50, + manual_threshold=127, +): + import os + from datetime import datetime + + import pandas as pd + from skimage import color, filters, io, measure, morphology + from skimage.segmentation import clear_border + from skimage.util import img_as_ubyte + + os.makedirs(output_dir, exist_ok=True) + original_image = io.imread(image_path) + + if len(original_image.shape) > 2 and original_image.shape[2] > 1: + gray_image = color.rgb2gray(original_image) + else: + gray_image = original_image + + gray_image = img_as_ubyte(gray_image) + smoothed_image = filters.gaussian(gray_image, sigma=1) + + if smoothed_image.shape[0] < 10 or smoothed_image.shape[1] < 10: + raise ValueError("Image too small for reliable plaque detection.") + + if threshold_method == "otsu": + threshold_value = filters.threshold_otsu(smoothed_image) + binary_image = smoothed_image > threshold_value + elif threshold_method == "adaptive": + block_size = 35 if min(smoothed_image.shape) > 35 else min(smoothed_image.shape) // 2 * 2 + 1 + adaptive_thresh = filters.threshold_local(smoothed_image, block_size=block_size) + binary_image = smoothed_image > adaptive_thresh + else: + threshold_value = manual_threshold + binary_image = smoothed_image > threshold_value + + cleaned_binary = morphology.remove_small_objects(binary_image, min_size=min_plaque_size) + labeled_image = measure.label(cleaned_binary) + labeled_image = clear_border(labeled_image) + + regions = measure.regionprops(labeled_image, intensity_image=gray_image) + plaque_data = [] + + for region in regions: + plaque_data.append( + { + "Area": region.area, + "Perimeter": region.perimeter, + "Mean_Intensity": region.mean_intensity, + "Eccentricity": region.eccentricity, + } + ) + + total_plaque_area = sum(r["Area"] for r in plaque_data) + total_image_area = gray_image.size + area_fraction = (total_plaque_area / total_image_area) * 100 + + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + csv_filename = f"{output_dir}/plaque_analysis_{timestamp}.csv" + segmented_image_filename = f"{output_dir}/segmented_plaques_{timestamp}.png" + + if plaque_data: + df = pd.DataFrame(plaque_data) + df.to_csv(csv_filename, index=False) + + colored_labels = color.label2rgb(labeled_image, image=gray_image, bg_label=0, kind="overlay") + io.imsave(segmented_image_filename, img_as_ubyte(colored_labels)) + + log = f""" +Amyloid-β Plaque Analysis Research Log +====================================== +Date: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")} +Image: {image_path} + +Parameters: +- Threshold method: {threshold_method} +- Threshold value: {threshold_value if threshold_method != "adaptive" else "Adaptive local thresholding"} +- Minimum plaque size: {min_plaque_size} pixels² + +Results: +- Total plaques detected: {len(plaque_data)} +- Avg plaque size: {df["Area"].mean():.2f} ± {df["Area"].std():.2f} pixels² +- Total plaque area: {total_plaque_area} pixels² ({area_fraction:.2f}% of tissue) +- Avg plaque intensity: {df["Mean_Intensity"].mean():.2f} + +Outputs: +- Detailed CSV: {csv_filename} +- Segmented Image: {segmented_image_filename} +""" + else: + log = f""" +Amyloid-β Plaque Analysis Research Log +====================================== +Date: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")} +Image: {image_path} + +Parameters: +- Threshold method: {threshold_method} +- Threshold value: {threshold_value if threshold_method != "adaptive" else "Adaptive local thresholding"} +- Minimum plaque size: {min_plaque_size} pixels² + +Results: +No plaques detected. Adjust threshold or minimum plaque size. +""" + return log diff --git a/BioScientist/agent_system/engines/v1_executor_backup/tool/protocols.py b/BioScientist/agent_system/engines/v1_executor_backup/tool/protocols.py new file mode 100644 index 0000000000000000000000000000000000000000..707b3198417b0de58e9173a72e9473ff0867fc07 --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/tool/protocols.py @@ -0,0 +1,305 @@ +""" +Protocols.io API integration for searching and retrieving biological protocols. + +This module provides functions to search protocols.io for relevant protocols +based on natural language queries. +""" + +import os +from typing import Any + +import requests + +try: + # Optional import to read from central config if available + from biomni.config import default_config # type: ignore +except Exception: + default_config = None # fallback if import fails + + +# API Configuration +PROTOCOLS_IO_API_BASE = "https://www.protocols.io/api/v3" + +# Resolve access token from env or config (no hardcoded defaults) +ACCESS_TOKEN = os.getenv("PROTOCOLS_IO_ACCESS_TOKEN") or os.getenv("BIOMNI_PROTOCOLS_IO_ACCESS_TOKEN") +if not ACCESS_TOKEN and default_config is not None: + ACCESS_TOKEN = getattr(default_config, "protocols_io_access_token", None) + + +def search_protocols( + query: str, +) -> dict[str, Any]: + """ + Search protocols.io for relevant protocols based on a natural language query. + + Args: + query: Keyword search term to find protocols (searches names, descriptions, authors), should be EXACT as we are looking for EXACT MATCH + + Returns: + Dictionary containing: + - protocols: List of protocol dictionaries with title, description, url, etc. + - pagination: Pagination information + - total_results: Total number of matching protocols + - status_code: API status code (0 = success) + + Raises: + requests.RequestException: If API request fails + ValueError: If invalid parameters are provided + + Example: + >>> results = search_protocols("CRISPR gene editing", page_size=5) + >>> for protocol in results["protocols"]: + ... print(f"{protocol['title']}: {protocol['url']}") + """ + # Validate parameters + if not query or not query.strip(): + raise ValueError("Query cannot be empty") + # Ensure access token is configured + if not ACCESS_TOKEN: + raise ValueError( + "Protocols.io access token is not configured. Set PROTOCOLS_IO_ACCESS_TOKEN or BIOMNI_PROTOCOLS_IO_ACCESS_TOKEN env var, or configure BiomniConfig.protocols_io_access_token." + ) + + # Construct API request + url = "https://www.protocols.io/api/v3/protocols" + + headers = {"Authorization": f"Bearer {ACCESS_TOKEN}", "Content-Type": "application/json"} + + # Prepare search query - wrap in quotes for exact match if requested + search_key = query.strip() + + params = { + "filter": "public", # Search public protocols + "key": search_key, + } + + try: + # Make API request + response = requests.get(url, headers=headers, params=params) + response.raise_for_status() + + data = response.json() + + # Parse response + if data.get("status_code") != 0: + return { + "protocols": [], + "pagination": {}, + "total_results": 0, + "status_code": data.get("status_code"), + "error": data.get("error_message", "Unknown error"), + } + + # Extract protocol information + protocols = [] + for item in data.get("items", []): + protocol_info = { + "id": item.get("id"), + "title": item.get("title", ""), + "description": item.get("description", ""), + "url": item.get("uri", ""), + "doi": item.get("doi", ""), + "authors": [ + {"name": author.get("name", ""), "username": author.get("username", "")} + for author in item.get("authors", []) + ], + "created_on": item.get("created_on"), + "published_on": item.get("published_on"), + "version": item.get("version_id"), + "views": item.get("nr_of_views", 0), + "is_peer_reviewed": item.get("is_peer_reviewed", False), + "tags": item.get("tags", []), + } + protocols.append(protocol_info) + + # Extract pagination info + pagination = data.get("pagination", {}) + + return { + "protocols": protocols, + "pagination": { + "current_page": pagination.get("current_page", 1), + "total_pages": pagination.get("total_pages", 1), + "page_size": pagination.get("page_size", 1), + "total_results": pagination.get("total_results", 0), + }, + "total_results": pagination.get("total_results", 0), + "status_code": 0, + } + + except requests.exceptions.RequestException as e: + raise requests.RequestException(f"Failed to search protocols.io: {str(e)}") from e + + +def get_protocol_details(protocol_id: int, timeout: int = 30) -> dict[str, Any]: + """ + Get detailed information about a specific protocol by ID. + + Args: + protocol_id: The protocol ID from protocols.io + timeout: Request timeout in seconds (default: 30) + + Returns: + Dictionary containing detailed protocol information + + Raises: + requests.RequestException: If API request fails + """ + # Ensure access token is configured + if not ACCESS_TOKEN: + raise ValueError( + "Protocols.io access token is not configured. Set PROTOCOLS_IO_ACCESS_TOKEN or BIOMNI_PROTOCOLS_IO_ACCESS_TOKEN env var, or configure BiomniConfig.protocols_io_access_token." + ) + + url = f"https://www.protocols.io/api/v3/protocols/{protocol_id}" + + headers = {"Authorization": f"Bearer {ACCESS_TOKEN}", "Content-Type": "application/json"} + + try: + response = requests.get(url, headers=headers, timeout=timeout) + response.raise_for_status() + + data = response.json() + + if data.get("status_code") != 0: + return {"error": data.get("error_message", "Unknown error"), "status_code": data.get("status_code")} + + return data.get("protocol", {}) + + except requests.exceptions.RequestException as e: + raise requests.RequestException(f"Failed to get protocol details: {str(e)}") from e + + +def _get_protocols_directory(): + """Get the path to the local protocols directory.""" + import biomni + + return os.path.join(os.path.dirname(biomni.__file__), "tool", "protocols") + + +def list_local_protocols(source: str | None = None) -> dict[str, Any]: + """ + List available protocol files in the local biomni/tool/protocols/ directory. + + Args: + source: Optional filter by source directory (e.g., 'addgene' or 'thermofisher'). + If None, lists all protocols from all sources. + + Returns: + Dictionary containing: + - protocols: List of protocol file information with name, source, and path + - total_count: Total number of protocols found + - sources: List of available source directories + + Example: + >>> results = list_local_protocols() + >>> results = list_local_protocols(source="addgene") + """ + protocols_dir = _get_protocols_directory() + + if not os.path.exists(protocols_dir): + return { + "protocols": [], + "total_count": 0, + "sources": [], + "error": f"Protocols directory not found: {protocols_dir}", + } + + protocols = [] + sources = [] + + # Get all source directories + for item in os.listdir(protocols_dir): + item_path = os.path.join(protocols_dir, item) + if os.path.isdir(item_path): + sources.append(item) + + # Filter by source if specified + search_dirs = [source] if source and source in sources else sources + + # List all protocol files + for source_dir in search_dirs: + source_path = os.path.join(protocols_dir, source_dir) + if os.path.isdir(source_path): + for filename in os.listdir(source_path): + if filename.endswith(".txt"): + file_path = os.path.join(source_path, filename) + protocols.append( + { + "name": filename, + "source": source_dir, + "path": file_path, + "relative_path": os.path.join(source_dir, filename), + } + ) + + return { + "protocols": sorted(protocols, key=lambda x: (x["source"], x["name"])), + "total_count": len(protocols), + "sources": sorted(sources), + } + + +def read_local_protocol(filename: str, source: str | None = None) -> dict[str, Any]: + """ + Read the contents of a local protocol file from biomni/tool/protocols/. + + Args: + filename: Name of the protocol file (e.g., 'Addgene_ Protocol - How to Run an Agarose Gel.txt') + source: Optional source directory (e.g., 'addgene' or 'thermofisher'). + If None, searches all source directories. + + Returns: + Dictionary containing: + - content: Full text content of the protocol file + - filename: Name of the file + - source: Source directory where the file was found + - path: Full path to the file + + Raises: + FileNotFoundError: If the protocol file is not found + ValueError: If filename is empty + + Example: + >>> protocol = read_local_protocol("Addgene_ Protocol - How to Run an Agarose Gel.txt", source="addgene") + >>> print(protocol["content"]) + """ + if not filename or not filename.strip(): + raise ValueError("Filename cannot be empty") + + filename = filename.strip() + protocols_dir = _get_protocols_directory() + + if not os.path.exists(protocols_dir): + raise FileNotFoundError(f"Protocols directory not found: {protocols_dir}") + + # If source is specified, only search that directory + if source: + file_path = os.path.join(protocols_dir, source, filename) + if os.path.exists(file_path): + with open(file_path, encoding="utf-8") as f: + content = f.read() + return { + "content": content, + "filename": filename, + "source": source, + "path": file_path, + } + raise FileNotFoundError(f"Protocol file not found: {source}/{filename}") + + # Otherwise, search all source directories + for source_dir in os.listdir(protocols_dir): + source_path = os.path.join(protocols_dir, source_dir) + if os.path.isdir(source_path): + file_path = os.path.join(source_path, filename) + if os.path.exists(file_path): + with open(file_path, encoding="utf-8") as f: + content = f.read() + return { + "content": content, + "filename": filename, + "source": source_dir, + "path": file_path, + } + + raise FileNotFoundError(f"Protocol file not found in any source directory: {filename}") diff --git a/BioScientist/agent_system/engines/v1_executor_backup/tool/protocols/thermofisher/Cell Freezing Protocols _ Thermo Fisher Scientific - US.txt b/BioScientist/agent_system/engines/v1_executor_backup/tool/protocols/thermofisher/Cell Freezing Protocols _ Thermo Fisher Scientific - US.txt new file mode 100644 index 0000000000000000000000000000000000000000..b56b3b01a9c34767a890f71ed6dc3e5bde1fb845 --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/tool/protocols/thermofisher/Cell Freezing Protocols _ Thermo Fisher Scientific - US.txt @@ -0,0 +1,105 @@ +PROTOCOL: Cell Freezing Protocols +SOURCE: https://www.thermofisher.com/us/en/home/references/gibco-cell-culture-basics/cell-culture-protocols/freezing-cells.html +================================================================================ + +OVERVIEW +================================================================================ + +After subculturing your adherent or suspension cells, you may need to freeze any cells not required for immediate experiments. This cell freezing protocol outlines the necessary preparations and materials to order before beginning the freezing process. It also provides step-by-step instructions that you can follow during the procedure or refer to for future use. + +Cryopreservation of cells, or the freezing of cells, is a technique used to store living cells and tissues at extremely low temperatures to preserve their structure over an indefinite period. By lowering the temperature to below –130°C, extracellular ice forms, decreasing kinetic and molecular activity within the cells and slowing biological aging. + +Why freeze your cells? + +• Freezing cells is crucial for maintaining an adequate cell stock +• Cell lines are valuable resources whose replacement is both expensive and time-consuming +• Cryopreservation of finite cell lines prevents aging and transformation +• Freezing cells in continuous culture can prevent: + - genetic drift + - senescence + - microbial contamination + - equipment failure + +When a small surplus of cells becomes available from subculturing, a highly effective method is to freeze them as a seed stock, keeping them protected and not available for general laboratory use. Working stocks can be prepared and replenished from these frozen seed stocks. If the seed stocks become depleted, cryopreserved working stocks can serve as a source for preparing fresh seed stock with minimal generation increase from the initial freezing. + +MATERIALS REQUIRED +================================================================================ + +We suggest using the following materials for the cell freezing protocol: + +• Log-phase cultured cells +• Complete growth medium: basal medium, serum, and supplements pre-warmed to 37°C +• Cryoprotective agent such as DMSO (use a bottle set aside for cell culture; open only in a laminar flow hood) or a freezing medium such as Synth-a-Freeze Cryopreservation Medium or Recovery Cell Culture Freezing Medium +• Disposable, sterile 15 mL or 50 mL conical tubes +• Reagents and equipment to determine viable and total cell counts (e.g., Countess Automated Cell Counter, or hemocytometer, cell counter, and Trypan Blue) +• Sterile cryogenic storage vials (i.e., cryovials) +• Controlled rate freezing apparatus or isopropanol chamber +• Liquid nitrogen storage container + +For freezing adherent cells, in addition to the above materials, you will need: + +• Balanced salt solution such as Gibco Dulbecco's Phosphate Buffered Saline (DPBS), containing no calcium, magnesium, or phenol red +• Dissociation reagent such as trypsin or Gibco TrypLE Express, without phenol red + +Catalog numbers: J66650.AK, 339650, 5000-0020, 5100-0050, 14190250, 25200072, 12604039, 15250061, 12648010, R00550, A49862 + +MEDIUM FOR FREEZING CELLS +================================================================================ + +Cryoprotective agents reduce the freezing point of the medium and slow the cooling rate, greatly reducing the risk of ice crystal formation, which can damage cells and cause cell death. Media for cryopreservation typically includes a base medium, a cryopreservative, and a protein source, but we recommend that you closely follow your cell line and product-specific instructions to determine the best cryopreservation media for your experiment. When included, the cryopreservative and protein protect the cells from the stress of the freeze-thaw process. The cells are then frozen in liquid nitrogen for preservation. + +For media with serum, the constituents may be: + +• complete medium containing 10% glycerol +• complete medium containing 10% DMSO (dimethyl sulfoxide) +• 50% cell-conditioned medium with 50% fresh medium with 10% glycerol or 10% DMSO + +Safety Note: DMSO solutions are known to facilitate the entry process of organic molecules into tissues. You should always handle reagents containing DMSO using equipment and practices appropriate for the hazards posed by such materials and dispose of the reagents in compliance with local regulations. + +A serum-free medium has generally low or no protein; but you can still use it as a base for a cryopreservative medium in the following formulations: + +• 50% cell-conditioned serum-free medium with 50% fresh serum-free medium containing 7.5% DMSO +• fresh serum-free medium containing 7.5% DMSO and 10% cell culture–grade BSA + +You may also use a specially formulated complete cryopreservation medium such as: + +• Gibco Recovery Cell Culture Freezing is a ready-to-use complete cryopreservation medium for mammalian cell cultures with an optimized ratio of fetal bovine serum to bovine serum for improved cell viability and cell recovery after thawing. +• Gibco Synth-a-Freeze Cryopreservation Medium is a chemically defined, protein-free, sterile cryopreservation medium with 10% DMSO that is suitable for the cryopreservation of many stem and primary cell types apart from melanocytes. + +PROTOCOL +================================================================================ + +The proper way to freeze cells and cryopreserve your cell lines for future use requires diligent attention to passaging procedures and storage requirements. Remember to always wear personal protective equipment and use proper sterile technique when freezing cells. + +The general freezing method is the same for adherent and suspension cells, except that adherent cells need to be removed from the culture plates before starting the freezing procedure. The following cell freezing protocol is a general procedure for cryopreserving cultured cells. Optimal freezing conditions depend on the cell line in use—for detailed protocols, always refer to the cell-specific product insert. + +Prior to cryopreservation, cells should be characterized and checked for contamination. Freeze cells in log phase, at a high concentration of at least 90% viability, and at as low a passage number as possible, as this will lead to the best outcomes when your stock is thawed. + +1. Prepare freezing medium and store at 2° to 8°C until use. + +2. For adherent cells, gently detach cells from the tissue culture vessel following the procedure used during subculture. Detach as gently as possible to minimize damage to the cells. + +3. Resuspend the detached cells in the complete growth medium. + +4. Find the total number of cells and percent viability using a hemocytometer or the Countess Automated Cell Counter and Trypan Blue exclusion. + +5. According to the desired viable cell density, calculate the required volume of freezing medium. + +6. Centrifuge the cell suspension at approximately 100–400 × g for 5 to 10 minutes (centrifugation speed and duration varies depending on the cell type). Using a pipette, aseptically withdraw the supernatant to the smallest volume without disturbing the cell pellet and discard. + +7. Resuspend the cell pellet in cold freezing medium at the recommended viable cell density for the specific cell type. + +8. Dispense aliquots of the cell suspension into sterile cryogenic storage vials. As you aliquot them, gently and often mix the cells to maintain a homogeneous cell suspension. + +9. Freeze the cells slowly by reducing the temperature at approximately 1°C per minute using a controlled rate cryo-freezer or a cryo-freezing container such as "Mr. Frosty," available from Thermo Scientific Nalgene labware (Nalgene Nunc). Alternatively, place the cryovials containing the cells in an isopropanol chamber and store them at –80°C overnight. + +10. Transfer frozen cells to liquid nitrogen and store them in the gas phase above the liquid nitrogen below –135°C. + +Safety Note: Biohazardous materials must be stored in the gas phase above the liquid nitrogen. Storing the sealed cryovials in the gas phase reduces the risk of explosion. If you are using liquid-phase storage, be aware of the explosion hazard with both glass and plastic cryovials and always wear a face shield or goggles. + +REFERENCES +================================================================================ + +Freshney, R. (1987) Culture of Animal Cells: A Manual of Basic Technique, p. 220, Alan R. Liss, Inc., New York. + +Mohamed, H. M., Sundar, P., Ridwan, N. A. A., Cheong, A. J., Mohamad Salleh, N. A., Sulaiman, N., Mh Busra, F., & Maarof, M. (2024). Optimisation of cryopreservation conditions, including storage duration and revival methods, for the viability of human primary cells. BMC Molecular & Cell Biology, 25(1), 1–14. https://doi.org/10.1186/s12860-024-00516-6. diff --git a/BioScientist/agent_system/engines/v1_executor_backup/tool/protocols/thermofisher/Click-iT EdU Protocol for Flow Cytometry _ Thermo Fisher Scientific - US.txt b/BioScientist/agent_system/engines/v1_executor_backup/tool/protocols/thermofisher/Click-iT EdU Protocol for Flow Cytometry _ Thermo Fisher Scientific - US.txt new file mode 100644 index 0000000000000000000000000000000000000000..05058980d42826f4a1cbcb136a368fbfeb45aa6d --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/tool/protocols/thermofisher/Click-iT EdU Protocol for Flow Cytometry _ Thermo Fisher Scientific - US.txt @@ -0,0 +1,146 @@ +PROTOCOL: Click-iT EdU Protocol for Flow Cytometry +SOURCE: https://www.thermofisher.com/us/en/home/references/protocols/cell-and-tissue-analysis/protocols/click-it-edu-flow-cytometry-protocol.html +================================================================================ + +OVERVIEW +================================================================================ + +The Click-iT EdU Flow Cytometry Assay Kits provide a fast and simple method for detecting and quantifying cell proliferation using flow cytometry. EdU (5-ethynyl-2'-deoxyuridine) is a thymidine analog that is incorporated into DNA during active DNA synthesis. The Click-iT reaction uses copper-catalyzed azide-alkyne cycloaddition (CuAAC) chemistry to attach a fluorescent azide to the incorporated EdU, allowing for detection and quantification of proliferating cells. + +MATERIALS PROVIDED IN KIT +================================================================================ + +• Component A: EdU (5-ethynyl-2'-deoxyuridine), 40 mg +• Component B: Fluorescent azide (Pacific Blue azide, Alexa Fluor 647 azide, or Alexa Fluor 488 azide) +• Component C: DMSO, 4 mL +• Component D: Click-iT fixative (contains paraformaldehyde) +• Component E: Click-iT saponin-based permeabilization and wash reagent (10X), 50 mL +• Component F: CuSO₄ solution, 1 mL +• Component G: Click-iT EdU buffer additive + +Number of assays: Sufficient material is supplied for 50 reactions, based on the protocol below. + +Approximate fluorescence excitation/emission maxima: +• Pacific Blue azide: 410/455 nm +• Alexa Fluor 647 azide: 650/670 nm +• Alexa Fluor 488 azide: 495/519 nm + +MATERIALS REQUIRED BUT NOT PROVIDED +================================================================================ + +• 1% bovine serum albumin (BSA) in phosphate buffered saline (PBS), pH 7.1–pH 7.4 +• Buffered saline solution, such as PBS, D-PBS, or TBS +• Deionized water or 18 MΩ purified water +• 12 × 75-mm tubes, or other flow cytometry tubes + +CAUTIONS +================================================================================ + +• DMSO (Component C), provided as a solvent in this kit, is known to facilitate the entry of organic molecules into tissues. Handle reagents containing DMSO using equipment and practices appropriate for the hazards posed by such materials. Dispose of the reagents in compliance with all pertaining local regulations. +• Click-iT fixative (Component D) contains paraformaldehyde, which is harmful. Use with appropriate precautions. +• Click-iT saponin-based permeabilization and wash reagent (Component E) contains sodium azide, which yields highly toxic hydrazoic acid under acidic conditions. Dilute azide compounds in running water before discarding to avoid accumulation of potentially explosive deposits in plumbing. + +PROTOCOL +================================================================================ + +Preparing reagents + +1. Allow vials to warm to room temperature before opening. +2. To prepare a 10 mM solution of EdU, add 4 mL of DMSO (Component C) or aqueous solution (PBS) to Component A and mix well. After use, store any remaining stock solution at ≤–20°C. When stored as directed, the stock solution is stable for up to 1 year. +3. To prepare a working solution of Pacific Blue azide, Alexa Fluor 647 azide, or Alexa Fluor 488 azide, add 130 μL of DMSO to Component B and mix well. After use, store any remaining working solution at ≤–20°C. When stored as directed, this working solution is stable for up to 1 year. +4. To prepare 500 mL of 1X Click-iT saponin-based permeabilization and wash reagent, add 50 mL of Component E to 450 mL of 1% BSA in PBS. Smaller amounts can be prepared by diluting a volume of Component E 1:10 with 1% BSA in PBS. After use, store any remaining solutions at 2–6˚C. When stored as directed, the 1X solution is stable for 6 months and the 10X solution is stable for 12 months after receipt. + +Note: Component E contains sodium azide (see Cautions). + +5. To make a 10X stock solution of the Click-iT EdU buffer additive (Component G), add 2 mL of deionized water to the vial and mix until the Click-iT EdU buffer additive is fully dissolved. After use, store any remaining stock solution at ≤–20˚C. When stored as directed, the stock solution is stable for up to 1 year. + +Labeling cells with EdU + +The following protocol was developed with Jurkat cells, a human T cell line, and using an EdU concentration of 10 μM, and can be adapted for any cell type. Growth medium, cell density, cell type variations, and other factors may influence labeling. In initial experiments, we recommend testing a range of EdU concentrations to determine the optimal concentration for your cell type and experimental conditions. If currently using a BrdU based assay for cell proliferation, a similar concentration to BrdU is a good starting concentration for EdU. If using whole blood as the sample, we recommend heparin as the anticoagulant for collection. + +1. Suspend the cells in an appropriate tissue culture medium to obtain optimal conditions for cell growth. Disturbing the cells by temperature changes or washing prior to incubation with EdU slows the growth of the cells during incorporation. +2. Add EdU to the culture medium at the desired final concentration and mix well. We recommend a starting concentration of 10 μM for 1–2 hours. For longer incubations, use lower concentrations. For shorter incubations, higher concentrations may be required. For a negative staining control, include cells from the same population that have not been treated with EdU. +3. Incubate under conditions optimal for cell type for the desired length of time. Altering the amount of time the cells are exposed to EdU or subjecting the cells to pulse labeling with EdU allows the evaluation of various DNA synthesis and proliferation parameters. Effective time intervals for pulse labeling and the length of each pulse depend on the cell growth rate. +4. Harvest cells and proceed immediately to "Staining cell-surface antigens with antibodies" if performing antibody surface labeling; otherwise continue to "Fixation and permeabilization". + +Staining cell-surface antigens with antibodies (optional) + +1. Wash cells once with 3 mL of 1% BSA in PBS, pellet cells by centrifugation, and remove supernatant. +2. Dislodge the pellet and resuspend cells at 1 × 10⁷ cells/mL in 1% BSA in PBS. +3. Add 100 μL of cell suspension or whole blood sample to flow tubes. +4. Add surface antibodies and mix well. + +Note: Do not use PE, PE-tandem, or Qdot antibody conjugates before performing the click reaction; wait until step 6.1 for labeling with these fluorophores. + +5. Incubate for the recommended time and temperature, protected from light. +6. Proceed to step 4.1 for cell fixation. + +Fixation and permeabilization + +The Click-iT saponin-based permeabilization and wash reagent can be used with whole blood or cell suspensions containing red blood cells, as well as with cell suspensions containing more than one cell type. This permeabilization and wash reagent maintains the morphological light scatter characteristics of leukocytes while lysing red blood cells. + +1. Wash the cells once with 3 mL of 1% BSA in PBS, pellet the cells, and remove the supernatant. +2. Dislodge the pellet, add 100 μL of Click-iT fixative (Component D), and mix well. +3. Incubate the cells for 15 minutes at room temperature, protected from light. +4. Wash the cells with 3 mL of 1% BSA in PBS, pellet the cells, and remove the supernatant. Repeat the wash step if red blood cells or hemoglobin are present in the sample. Remove all residual red blood cell debris and hemoglobin before proceeding. +5. Dislodge the cell pellet and resuspend the cells in 100 μL of 1X Click-iT saponin-based permeabilization and wash reagent (prepared in step 4 under "Preparing reagents"), and mix well. Incubate the cells for 15 minutes or proceed directly to the next protocol for click labeling. + +Click-iT reaction + +1. Prepare 1X Click-iT EdU buffer additive by diluting the 10X stock solution (prepared in step 5 under "Preparing reagents") 1:10 in deionized water. +2. Prepare the Click-iT reaction cocktail according to Table 1. + +NOTE: Use the Click-iT reaction cocktail within 15 minutes of preparation. + +Table 1. Click-iT EdU reaction cocktails. + +Reaction components | 1 reaction | 2 reactions | 5 reactions | 10 reactions | 15 reactions | 30 reactions | 50 reactions +---------------------|-----------|-------------|-------------|--------------|--------------|--------------|------------- +PBS, D-PBS, or TBS | 438 µL | 875 µL | 2.19 mL | 4.38 mL | 6.57 mL | 13.2 mL | 21.9 mL +CuSO₄ (Component F) | 10 µL | 20 µL | 50 µL | 100 µL | 150 µL | 300 µL | 500 µL +Fluorescent dye azide | 2.5 µL | 5 µL | 12.5 µL | 25 µL | 37.5 µL | 75 µL | 125 µL +Reaction Buffer Additive | 50 µL | 100 µL | 250 µL | 500 µL | 750 µL | 1.5 mL | 2.5 mL +Total reaction volume | 500 µL | 1 mL | 2.5 mL | 5 mL | 7.5 mL | 15 mL | 25 mL + +3. Add 0.5 mL of Click-iT reaction cocktail to each tube and mix well. +4. Incubate the reaction mixture for 30 minutes at room temperature, protected from light. +5. Wash the cells once with 3 mL of 1X Click-iT saponin-based permeabilization and wash reagent (prepared in step 4 under "Preparing reagents"), pellet the cells, and remove the supernatant. Dislodge the cell pellet and resuspend the cells in 100 μL of 1X Click-iT saponin-based permeabilization and wash reagent, if proceeding with intracellular antibody labeling in "Staining cells for DNA content". Otherwise, add 500 μL of 1X Click-iT saponin-based permeabilization and wash reagent and proceed to "Analysis by flow cytometry". + +Staining cells for DNA content (optional) + +1. If necessary, add Ribonuclease A to each tube and mix (see Table 2). +2. Add the appropriate DNA stain to each tube, mix well, and incubate as recommended for each DNA stain. + +Table 2. Click-iT EdU compatibility with DNA content stains. + +DNA content stain | Click-iT EdU stain compatibility | RNase required? +-----------------|----------------------------------|---------------- + | Pacific Blue | Alexa Fluor 647 | Alexa Fluor 488 | +FxCycle Violet | No | Yes | Yes | No +Propidium iodide (PI) | Yes | Yes | No* | Yes +SYTOX AADvanced | Yes | Yes | Yes | Yes +FxCycle Far Red | Yes | No | Yes | Yes + +Analysis by flow cytometry + +If measuring total DNA content on a traditional flow cytometer using hydrodynamic focusing, use a low flow rate during acquisition. If using the Attune Acoustic Focusing Cytometer, all collection rates may be used without loss of signal integrity if the event rate is kept below 10,000 events per second. However, for each sample within an experiment, the same collection rate and cell concentration should be used. The fluorescent signal generated by DNA content stains is best detected with linear amplification. The fluorescent signal generated by Click-iT EdU labeling is best detected with logarithmic amplification. + +1. Analyze the cells using a flow cytometer. +2. For the detection of EdU with Pacific Blue azide, use 405 nm excitation with a violet emission filter (450/50 nm or similar). +3. For the detection of EdU with Alexa Fluor 647 azide use 633/635 nm excitation with a red emission filter (660/20 nm or similar). +4. For the detection of EdU with Alexa Fluor 488 azide, use 488 nm excitation with a green emission filter (530/30 nm or similar). + +PRODUCT CATALOG NUMBERS +================================================================================ + +C10418, C10424, C10425, C10419, C10420, A10044, F10347, F10348, H3570, P3566, S10349, V35003, 14190144, 14190250 + +REFERENCES +================================================================================ + +Chembiochem 4, 1147 (2003) +J Am Chem Soc 125, 3192 (2003) +Angew Chem Int Ed Engl 41, 2596 (2002) +Angew Chem Int Ed Engl 40, 2004 (2001) +BioTechniques 44, 927 (2008) +Curr Protoc Cytom 55, 7.38.1 (2011) diff --git a/BioScientist/agent_system/engines/v1_executor_backup/tool/protocols/thermofisher/Immunolabeling Using Secondary Antibodies _ Thermo Fisher Scientific - US.txt b/BioScientist/agent_system/engines/v1_executor_backup/tool/protocols/thermofisher/Immunolabeling Using Secondary Antibodies _ Thermo Fisher Scientific - US.txt new file mode 100644 index 0000000000000000000000000000000000000000..ea6e8429f29b9a760704cb810f9b3a0d8db7baa7 --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/tool/protocols/thermofisher/Immunolabeling Using Secondary Antibodies _ Thermo Fisher Scientific - US.txt @@ -0,0 +1,63 @@ +PROTOCOL: Immunolabeling Using Secondary Antibodies +SOURCE: https://www.thermofisher.com/us/en/home/life-science/cell-analysis/cell-analysis-learning-center/molecular-probes-school-of-fluorescence/imaging-basics/protocols-troubleshooting/protocols/immunolabeling-using-secondary-antibodies.html +================================================================================ + +OVERVIEW +================================================================================ + +This protocol provides general instructions for immunolabeling cells with a primary and secondary antibody. Immunolabeling is a technique for fluorescently labeling a specific biological target within a sample using an antibody. You'll also see immunolabeling referred to as both ICC (immunocytochemistry) and IHC (immunohistochemistry), and sometimes even antibody labeling. + +MATERIALS REQUIRED +================================================================================ + +• Fixed, permeabilized, and blocked cells (See Fix, Perm, and Block protocol) +• Primary antibody +• Secondary antibody conjugated with fluorophore of choice +• Phosphate-buffered saline (PBS) +• Antibody staining solution. + +Note: If there is a recommendation from the manufacturer, then use the solution recommended to dilute your antibodies. Usually the antibody staining solution is made with diluted blocking solution (1% blocking solution in PBS) or just PBS. + +• Fluorescence microscope with filter set matched to your fluorophore + +PREPARING YOUR PRIMARY ANTIBODY STAINING SOLUTION +================================================================================ + +Before you begin, you will need to prepare your primary antibody staining solution. The antibody you have purchased will usually come with recommendations for its use, but if it doesn't, a good place to start is at 1 μg/mL, which is usually a 1:1,000 dilution from your tube if the primary antibody concentration is 1 mg/mL. That means for 1 mL of staining solution, you would add 1 μL of antibody to 1 mL of PBS or blocking solution. + +A dilution series can help you identify optimal signal:background conditions + +If you're using an antibody for the first time, it never hurts to try a few different concentrations to find the one that gives you the highest signal and lowest background for your target. Try a simple series of dilutions (1:100, 1:250, 1:500, 1:750, and 1:1,000) for your primary antibody while holding the secondary antibody concentration constant. You can do this experiment in a 96-well plate in order to quickly find the optimal concentration (see vessel considerations). + +Most secondary antibodies are used between 1 and 10 μg/mL. A good starting concentration for a typical secondary antibody in that concentration range would be a dilution of 1:1,000. If you find your staining to be extremely bright, or that you have too much background, you can always try a higher dilution (from 1:2,000 to 1:10,000). + +Use controls to make sure your signal is not an artifact + +If possible, it's a good idea to have both a positive and negative control for an experiment that uses immunolabeling. A positive control would be an alternate sample, in addition to your experimental sample, that contains high levels of your target. A positive control will verify that you have done the immunolabeling properly. A negative control would be one where the sample is incubated only with secondary antibody (omitting the primary antibody step); this will show you the level of background fluorescence (nonspecific fluorescent signal) that is coming from your secondary antibody. + +PROTOCOL +================================================================================ + +General immunofluorescence protocol using secondary detection + +1. Remove the blocking solution from your sample. + +2. Add enough primary antibody staining solution to cover your samples, except for your negative control, which you will incubate without primary antibody, but with the same buffer you diluted your antibodies in. + +3. Incubate for 1 hr at room temperature or overnight at 4°C. + +Note: Once you have completed this step, you can put your experiment in the refrigerator until the following day. + +4. Remove the primary antibody staining solution from your samples. + +5. Wash 3 times with PBS. + +6. Add enough secondary antibody staining solution to cover your samples, including your negative control. + +7. Incubate for 30 min to 1 hr at room temperature. + +8. Wash 3 times with PBS. + +9. Optional: Go to fluorescent labeling if you wish to stain your cells with other markers and haven't done it yet. + +10. Image cells. diff --git a/BioScientist/agent_system/engines/v1_executor_backup/tool/protocols/thermofisher/Mounting Coverslips _ Thermo Fisher Scientific - US.txt b/BioScientist/agent_system/engines/v1_executor_backup/tool/protocols/thermofisher/Mounting Coverslips _ Thermo Fisher Scientific - US.txt new file mode 100644 index 0000000000000000000000000000000000000000..37a91d350bc1aa48cb3f537ee05defc6d722531c --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/tool/protocols/thermofisher/Mounting Coverslips _ Thermo Fisher Scientific - US.txt @@ -0,0 +1,40 @@ +PROTOCOL: Mounting Coverslips +SOURCE: https://www.thermofisher.com/us/en/home/life-science/cell-analysis/cell-analysis-learning-center/molecular-probes-school-of-fluorescence/imaging-basics/protocols-troubleshooting/protocols/mounting-coverslips.html +================================================================================ + +OVERVIEW +================================================================================ + +Formulations of mounting media that can add favorable properties such as optimizing the refractive index to match that of glass, preventing photobleaching, or preserving samples for long-term storage are widely available. Keep in mind that hard-setting mountants require time to "cure" or harden. + +MATERIALS REQUIRED +================================================================================ + +• Fixed, permeabilized, blocked, and stained sample on coverslip in a saline-based buffer solution such as PBS +• Glass microscope slide +• Mounting medium + +PROTOCOL +================================================================================ + +Try to limit the formation of bubbles in your mounting medium. Some bubbles are inevitable, and you can image around them as long as there are only a few. + +To avoid bubbles: +• Don't shake or invert your bottle of mounting medium. +• Clear bubbles from the tip of your applicator (i.e., from the tip of the pipette or dropper bottle) by squeezing a little bit of mounting medium onto a lab tissue before applying mounting medium to your slide + +Clean the surface of the non-sample side of the coverslip prior to imaging. Fingerprints, residual salts, and other contaminants can diminish image quality. + +1. Apply a small amount of mounting medium to the surface of the slide; try to use an amount that will just fill the space under the coverslip. + +2. Remove coverslip containing the sample from the buffer. + +3. Blot excess buffer or solvent from the non-sample surface of the coverslip (or allow it to air-dry and then remove the salt residue before imaging). + +4. Slowly tip the coverslip onto the mounting medium, and avoid creating bubbles as you lower it into place. + +5. Follow manufacturer's directions for curing time. + +6. Optional: Seal the coverslip if desired. + +7. Image. diff --git a/BioScientist/agent_system/engines/v1_executor_backup/tool/protocols/thermofisher/Vybrant DyeCycle Ruby stain _ Thermo Fisher Scientific - US.txt b/BioScientist/agent_system/engines/v1_executor_backup/tool/protocols/thermofisher/Vybrant DyeCycle Ruby stain _ Thermo Fisher Scientific - US.txt new file mode 100644 index 0000000000000000000000000000000000000000..8a672e68a627f68014f44be3c54495d7b5119408 --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/tool/protocols/thermofisher/Vybrant DyeCycle Ruby stain _ Thermo Fisher Scientific - US.txt @@ -0,0 +1,88 @@ +PROTOCOL: Vybrant DyeCycle Ruby stain +SOURCE: https://www.thermofisher.com/us/en/home/references/protocols/cell-and-tissue-analysis/flow-cytometry-protocol/cell-cycle-analysis/vybrant-dyecycle-ruby-stain.html +================================================================================ + +OVERVIEW +================================================================================ + +Live cell studies of cellular DNA content and cell cycle distribution are useful to detect variations of growth patterns due to a variety of physical, chemical, or biological means, to monitor apoptosis, and study tumor behavior and suppressor gene mechanisms. In a given population, cells are distributed among three major phases of cell cycle: G0/G1 phase (one set of paired chromosomes per cell), S phase (DNA synthesis with variable amount of DNA), and G2/M phase (two sets of paired chromosomes per cell, prior to cell division). + +DNA content can be measured using fluorescent, DNA-selective stains that exhibit emission signals proportional to DNA mass. Flow cytometric analysis of these stained cell populations is then used to produce a frequency histogram that reveals various phases of the cell cycle. This analysis is typically performed on permeabilized or fixed cells using a cell-impermeant nucleic acid stain, but is also possible using live cells and a cell-permeant nucleic acid stain. While the choices for fixed cell staining are varied, there are only a few examples of useful cell-permeant nucleic acid stains. + +The Vybrant™ DyeCycle™ Ruby stain from Invitrogen offers near-infrared emission for DNA content analysis in living cells. The Vybrant DyeCycle Ruby stain is cell membrane permeant, DNA-selective, and essentially nonfluorescent until bound to double-stranded DNA. This stain takes advantage of the commonly available 488 nm and 633/5 nm excitation sources with emission >670 nm, placing cell cycle studies within reach of all flow cytometrists. Vybrant DyeCycle Ruby stain may also be used with any excitation source from 488 nm through to 690 nm wavelengths. Staining protocol is simple; the suspended cells are incubated in the presence of Vybrant DyeCycle Ruby stain and fluorescence is measured directly—no additional treatment or centrifugation is required. The Vybrant DyeCycle Ruby stain allows simultaneous co-staining of the cell population for other parameters and offers the possibility of cell sorting based on DNA content. + +SPECTRAL CHARACTERISTICS +================================================================================ + +The Vybrant DyeCycle Ruby stain/DNA complex has fluorescence excitation and emission maxima of 638 nm and 686 nm, respectively. + +MATERIALS PROVIDED +================================================================================ + +Table 1. Contents and storage information. + +Material | Amount | Concentration | Storage* | Stability +----------------------------|--------|----------------------------|----------|---------- +Vybrant DyeCycle Ruby stain | 400 μL (V10273) or 100 μL (V10309) | 2.5 mM solution in DMSO | ≤–20°C Desiccate Protect from light | When stored as directed, this kit is stable for at least 1 year. + +Number of assays: Sufficient material is supplied for 400 assays (Cat. no. V10273) or 100 assays (Cat. no. V10309) based on the protocol below. + +Approximate fluorescence excitation/emission maxima: Vybrant DyeCycle Ruby stain: 638/686 in nm, bound to DNA. + +MATERIALS REQUIRED BUT NOT PROVIDED +================================================================================ + +• Cells and culture medium or Hanks' Balanced Salt Solution (HBSS) +• Flow cytometry tubes + +CAUTION +================================================================================ + +No data are available addressing the mutagenicity or toxicity of Vybrant DyeCycle Ruby stain. Since Vybrant DyeCycle Ruby stain binds to nucleic acids, treat the stain as a potential mutagen and use with appropriate care. Handle the DMSO dye solution with particular caution as DMSO is known to facilitate the entry of organic molecules into tissues. Always wear suitable protective clothing, gloves, and eye/face protection when handling this reagent. Dispose of the reagent in compliance with all pertaining local regulations. + +PROTOCOL +================================================================================ + +The following staining protocol was optimized using Jurkat cells, a human T-cell leukemia line, in complete RPMI medium containing 10% fetal bovine serum with staining at 37°C, but can be adapted to most cell types. Growth medium or buffer used, cell density, cell type variations, and other factors may influence staining. In initial experiments, try a range of stain concentrations to determine the optimal stain concentration for the given cell type, buffer, and experimental conditions. For a given experiment, each flow cytometry sample should contain the same number of cells, as sample-to-sample variation in cell number leads to significant differences in fluorescence signal. If Vybrant DyeCycle Ruby stain is used in combination with other dyes for multicolor applications, apply other dyes to the sample first following manufacturers' instructions, including washes. Apply the Vybrant DyeCycle Ruby stain as the last stain to the sample, and do not wash or fix samples prior to flow cytometric analysis. + +General Guidelines + +For optimal DNA content cell cycle analysis, follow these guidelines: + +• Eliminate cell clumps and aggregates from the cell suspension before staining, and eliminate or correct for cell aggregates during analysis using gating or modeling software +• Staining with Vybrant DyeCycle Ruby stain may be performed at room temperature with the staining time about twice as long +• Hanks' Balanced Salt Solution (HBSS) is recommended if media is not used, do not use phosphate buffers +• Do not use glass containers with Vybrant DyeCycle Ruby stain +• Do not wash or fix cells after staining with Vybrant DyeCycle Ruby stain +• Eliminate dead cells from the DNA content analysis of living cells by using a dead cell discriminating stain such as SYTOX™ Green, SYTOX™ Blue, SYTOX™ Red, or SYTOX™ AADvanced™ dead cell stains, or LIVE/DEAD™ Fixable Dead cell stains such as Blue, Violet, Aqua, Green, Red, or Far Red kits +• Validate flow cytometry instrument performance on the day of use +• Use linear amplification for DNA content +• Use low flow rate for acquisition +• Collect adequate numbers of events for intended application + +Vybrant DyeCycle Ruby Cell Staining Protocol + +This staining protocol is optimized using Jurkat cells suspended in complete medium (RPMI/10% fetal bovine serum) and stained with Vybrant DyeCycle Ruby stain at 37°C. + +1. Remove the Vybrant DyeCycle Ruby stain from the freezer and allow the stain to equilibrate to room temperature. + +2. Prepare flow cytometry tubes each containing 0.5 mL of cell suspension in complete media at a concentration of 5 × 10⁵ cells/mL. + +3. To each tube, add 1 μL of Vybrant DyeCycle Ruby stain and mix well. Final stain concentration is 5 μM. + +4. Incubate at 37°C for 15–30 minutes, protected from light. + +5. Analyze without washing cells on a flow cytometer using 488 nm or 633/5 nm excitation and >670 nm emission. The Vybrant DyeCycle Ruby stain may be excited with any excitation source from 488 nm through 690 nm wavelengths. + +PRODUCT CATALOG NUMBERS +================================================================================ + +V10273, V10309, S10349, S7020, S34859, S11348, L23105, L34955, L34957, L34959, L23101, L23102, L10120, L10119, L34960 + +REFERENCES +================================================================================ + +1. Current Protocols in Cytometry, 7.0.1–7.27.7 (2004) +2. Practical Flow Cytometry, 4th Ed., Shapiro H. M., Ed. (2003) +3. Methods Mol Biol 281, 301 (2004) +4. Cytometry A 58, 21 (2004) diff --git a/BioScientist/agent_system/engines/v1_executor_backup/tool/support_tools.py b/BioScientist/agent_system/engines/v1_executor_backup/tool/support_tools.py new file mode 100644 index 0000000000000000000000000000000000000000..6bde4b39a1a9f4fcc41f54a0997b02bb1ce141a6 --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/tool/support_tools.py @@ -0,0 +1,388 @@ +import base64 +import io +import sys +from io import StringIO + +# Create a persistent namespace that will be shared across all executions +_persistent_namespace = {} + +# Global list to store captured plots +_captured_plots = [] + + +def run_python_repl(command: str) -> str: + """Executes the provided Python command in a persistent environment and returns the output. + Variables defined in one execution will be available in subsequent executions. + """ + + def execute_in_repl(command: str) -> str: + """Helper function to execute the command in the persistent environment.""" + old_stdout = sys.stdout + sys.stdout = mystdout = StringIO() + + # Use the persistent namespace + global _persistent_namespace + + try: + # Apply matplotlib monkey patches before execution + _apply_matplotlib_patches() + + # Execute the command in the persistent namespace + exec(command, _persistent_namespace) + output = mystdout.getvalue() + + # Capture any matplotlib plots that were generated + # _capture_matplotlib_plots() + + except Exception as e: + output = f"Error: {str(e)}" + finally: + sys.stdout = old_stdout + return output + + command = command.strip("```").strip() + return execute_in_repl(command) + + +def _capture_matplotlib_plots(): + """Capture any matplotlib plots that might have been generated during execution.""" + global _captured_plots + try: + import matplotlib.pyplot as plt + + # Check if there are any active figures + if plt.get_fignums(): + for fig_num in plt.get_fignums(): + fig = plt.figure(fig_num) + + # Save figure to base64 + buffer = io.BytesIO() + fig.savefig(buffer, format="png", dpi=150, bbox_inches="tight") + buffer.seek(0) + + # Convert to base64 + image_data = base64.b64encode(buffer.getvalue()).decode("utf-8") + plot_data = f"data:image/png;base64,{image_data}" + + # Add to captured plots if not already there + if plot_data not in _captured_plots: + _captured_plots.append(plot_data) + + # Close the figure to free memory + plt.close(fig) + + except ImportError: + # matplotlib not available + pass + except Exception as e: + print(f"Warning: Could not capture matplotlib plots: {e}") + + +def _apply_matplotlib_patches(): + """Apply simple monkey patches to matplotlib functions to automatically capture plots.""" + try: + import matplotlib.pyplot as plt + + # Only patch if matplotlib is available and not already patched + if hasattr(plt, "_biomni_patched"): + return + + # Store original functions + original_show = plt.show + original_savefig = plt.savefig + + def show_with_capture(*args, **kwargs): + """Enhanced show function that captures plots before displaying them.""" + # Capture any plots before showing + _capture_matplotlib_plots() + # Print a message to indicate plot was generated + print("Plot generated and displayed") + # Call the original show function + return original_show(*args, **kwargs) + + def savefig_with_capture(*args, **kwargs): + """Enhanced savefig function that captures plots after saving them.""" + # Get the filename from args if provided + filename = args[0] if args else kwargs.get("fname", "unknown") + # Call the original savefig function + result = original_savefig(*args, **kwargs) + # Capture the plot after saving + _capture_matplotlib_plots() + # Print a message to indicate plot was saved + print(f"Plot saved to: {filename}") + return result + + # Replace functions with enhanced versions + plt.show = show_with_capture + plt.savefig = savefig_with_capture + + # Mark as patched to avoid double-patching + plt._biomni_patched = True + + except ImportError: + # matplotlib not available + pass + except Exception as e: + print(f"Warning: Could not apply matplotlib patches: {e}") + + +def get_captured_plots(): + """Get all captured matplotlib plots.""" + global _captured_plots + return _captured_plots.copy() + + +def clear_captured_plots(): + """Clear all captured matplotlib plots.""" + global _captured_plots + _captured_plots = [] + + +def read_function_source_code(function_name: str) -> str: + """Read the source code of a function from any module path. + + Parameters + ---------- + function_name (str): Fully qualified function name (e.g., 'bioagentos.tool.support_tools.write_python_code') + + Returns + ------- + str: The source code of the function + + """ + import importlib + import inspect + + # Split the function name into module path and function name + parts = function_name.split(".") + module_path = ".".join(parts[:-1]) + func_name = parts[-1] + + try: + # Import the module + module = importlib.import_module(module_path) + + # Get the function object from the module + function = getattr(module, func_name) + + # Get the source code of the function + source_code = inspect.getsource(function) + + return source_code + except (ImportError, AttributeError) as e: + return f"Error: Could not find function '{function_name}'. Details: {str(e)}" + + +# def request_human_feedback(question, context, reason_for_uncertainty): +# """ +# Request human feedback on a question. + +# Parameters: +# question (str): The question that needs human feedback. +# context (str): Context or details that help the human understand the situation. +# reason_for_uncertainty (str): Explanation for why the LLM is uncertain about its answer. + +# Returns: +# str: The feedback provided by the human. +# """ +# print("Requesting human feedback...") +# print(f"Question: {question}") +# print(f"Context: {context}") +# print(f"Reason for Uncertainty: {reason_for_uncertainty}") + +# # Capture human feedback +# human_response = input("Please provide your feedback: ") + +# return human_response + + +def download_synapse_data( + entity_ids: str | list[str], + download_location: str = ".", + follow_link: bool = False, + recursive: bool = False, + timeout: int = 300, + entity_type: str = "dataset", +): + """Download data from Synapse using entity IDs. + + Uses the synapse CLI to download files, folders, or projects from Synapse. + Requires SYNAPSE_AUTH_TOKEN environment variable for authentication. + Automatically installs synapseclient if not available. + + CRITICAL: Always check entity type from query_synapse() search results or user hints and pass the correct entity_type! + The default entity_type="dataset" may not be appropriate for your entity. + + IMPORTANT: Multiple entity IDs are only supported for entity_type="file". + For datasets, folders, and projects, only a single entity_id is supported. + + Parameters + ---------- + entity_ids : str or list of str + Synapse entity ID(s) to download. + - For files: Can be a single ID string or list of ID strings + - For datasets/folders/projects: Must be a single ID string only + download_location : str, default "." + Directory where files will be downloaded (current directory by default) + follow_link : bool, default False + Whether to follow links to download the linked entity + recursive : bool, default False + Whether to recursively download folders and their contents + ONLY valid for entity_type="folder" - ignored for other types + timeout : int, default 300 + Timeout in seconds for each download operation + entity_type : str, default "dataset" + Type of Synapse entity ("dataset", "file", "folder", "project") + MUST match the actual entity type from search results or user hints! + The default "dataset" should only be used for actual datasets. + Check the 'node_type' field in search results to determine correct type. + + Returns + ------- + dict + Dictionary containing download results and any errors + + Notes + ----- + Requires SYNAPSE_AUTH_TOKEN environment variable with your Synapse personal + access token for authentication. + + AGENT USAGE GUIDANCE: + 1. Always check the 'node_type' field from query_synapse() search results or user hints + 2. Pass the correct entity_type parameter matching the node_type + 3. Do NOT rely on the default entity_type="dataset" unless confirmed + 4. For multiple downloads, ensure all entities are of type "file" + 5. Only use recursive=True with entity_type="folder" + + Examples + -------- + # After searching with query_synapse(), check node_type and use appropriate entity_type: + + # If search result shows 'node_type': 'dataset' + download_synapse_data("syn123456", entity_type="dataset") + + # If search result shows 'node_type': 'file' + download_synapse_data("syn654321", entity_type="file") + + # If search result shows 'node_type': 'folder' + download_synapse_data("syn789012", entity_type="folder", recursive=True) + + # Multiple files (only if all are 'node_type': 'file') + download_synapse_data(["syn111", "syn222"], entity_type="file") + """ + import os + import subprocess + + # Check for required authentication token + synapse_token = os.environ.get("SYNAPSE_AUTH_TOKEN") + if not synapse_token: + return { + "success": False, + "error": "SYNAPSE_AUTH_TOKEN environment variable is required for downloading", + "suggestion": "Set SYNAPSE_AUTH_TOKEN with your Synapse personal access token", + } + + # Check if synapse CLI is available + try: + subprocess.run(["synapse", "--version"], capture_output=True, check=True) + except (subprocess.CalledProcessError, FileNotFoundError): + try: + # Try to install synapseclient + print("Installing synapseclient...") + subprocess.run(["pip", "install", "synapseclient"], check=True) + print("✓ synapseclient installed successfully") + except subprocess.CalledProcessError as e: + return { + "success": False, + "error": f"Failed to install synapseclient: {e}", + "suggestion": "Please install manually: pip install synapseclient", + } + + # Ensure entity_ids is a list + if isinstance(entity_ids, str): + entity_ids = [entity_ids] + + # Validate that multiple IDs are only used with file entity type + if len(entity_ids) > 1 and entity_type != "file": + return { + "success": False, + "error": f"Multiple entity IDs are only supported for entity_type='file'. " + f"For entity_type='{entity_type}', only a single entity_id is supported.", + "suggestion": "Use a single entity_id string instead of a list, or change entity_type to 'file'", + } + + # Validate that recursive is only used with folder entity type + if recursive and entity_type != "folder": + return { + "success": False, + "error": f"recursive=True is only valid for entity_type='folder'. " + f"For entity_type='{entity_type}', recursive should be False.", + "suggestion": "Set recursive=False, or change entity_type to 'folder' if appropriate", + } + + # Create download directory if it doesn't exist + os.makedirs(download_location, exist_ok=True) + + results = [] + errors = [] + + for entity_id in entity_ids: + try: + # Build synapse download command with authentication + if entity_type == "dataset": + # For datasets, use query syntax to download the actual files + cmd = [ + "synapse", + "-p", + synapse_token, + "get", + "-q", + f"select * from {entity_id}", + "--downloadLocation", + download_location, + ] + else: + # For files, folders, projects, use direct ID + cmd = ["synapse", "-p", synapse_token, "get", entity_id, "--downloadLocation", download_location] + + # Add recursive flag only for folders (validation above ensures recursive is only True for folders) + if entity_type == "folder" and recursive: + cmd.append("-r") + + if follow_link: + cmd.append("--followLink") + + # Execute download + result = subprocess.run(cmd, capture_output=True, text=True, check=True, timeout=timeout) + + results.append( + { + "entity_id": entity_id, + "success": True, + "stdout": result.stdout, + "download_location": download_location, + } + ) + + except subprocess.CalledProcessError as e: + error_msg = f"Failed to download {entity_id}: {e.stderr if e.stderr else str(e)}" + errors.append(error_msg) + results.append({"entity_id": entity_id, "success": False, "error": error_msg}) + except subprocess.TimeoutExpired: + error_msg = f"Download timeout for {entity_id} (>{timeout} seconds)" + errors.append(error_msg) + results.append({"entity_id": entity_id, "success": False, "error": error_msg}) + + # Summary + successful_downloads = [r for r in results if r["success"]] + failed_downloads = [r for r in results if not r["success"]] + + return { + "success": len(failed_downloads) == 0, + "total_requested": len(entity_ids), + "successful": len(successful_downloads), + "failed": len(failed_downloads), + "download_location": download_location, + "results": results, + "errors": errors if errors else None, + } diff --git a/BioScientist/agent_system/engines/v1_executor_backup/tool/synthetic_biology.py b/BioScientist/agent_system/engines/v1_executor_backup/tool/synthetic_biology.py new file mode 100644 index 0000000000000000000000000000000000000000..768c1359a5c29d15808fd1f938ce41e6326e40f3 --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/tool/synthetic_biology.py @@ -0,0 +1,1351 @@ +def engineer_bacterial_genome_for_therapeutic_delivery(bacterial_genome_file, genetic_parts): + """Engineer a bacterial genome by integrating therapeutic genetic parts for therapeutic delivery. + + Parameters + ---------- + bacterial_genome_file : str + Path to the file containing the bacterial genome sequence in FASTA format + genetic_parts : dict + Dictionary containing genetic parts to be integrated:: + + { + 'promoters': list of dict with 'name', 'sequence', and 'position', + 'genes': list of dict with 'name', 'sequence', and 'position', + 'terminators': list of dict with 'name', 'sequence', and 'position', + 'cargo': dict with 'name' and 'sequence' of the therapeutic cargo + } + + Returns + ------- + str + Research log summarizing the engineering process, including the filename of the + engineered genome and plasmid map + + """ + import datetime + + from Bio import SeqIO + from Bio.Graphics import GenomeDiagram + from Bio.Seq import Seq + from Bio.SeqFeature import FeatureLocation, SeqFeature + from Bio.SeqRecord import SeqRecord + from reportlab.lib import colors + + # Initialize research log + log = ["# Bacterial Genome Engineering for Therapeutic Delivery\n"] + log.append(f"Date: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n") + + # Step 1: Load the bacterial genome + log.append("## Step 1: Loading Bacterial Genome") + try: + genome_record = SeqIO.read(bacterial_genome_file, "fasta") + genome_seq = genome_record.seq + log.append(f"Successfully loaded genome: {genome_record.id}") + log.append(f"Genome length: {len(genome_seq)} bp\n") + except Exception as e: + log.append(f"Error loading genome: {str(e)}") + return "\n".join(log) + + # Step 2: Design and integrate genetic parts + log.append("## Step 2: Integrating Genetic Parts") + + # Create a new genome sequence for modifications + # In newer BioPython versions, Seq objects are mutable by default + # or we can convert to a string and modify it, then convert back to Seq + engineered_seq = str(genome_seq) + + # Track features for visualization + features = [] + + # Keep track of position adjustments as we add sequences + position_adjustment = 0 + + # Add promoters + if "promoters" in genetic_parts: + log.append("### Adding Promoters:") + for promoter in genetic_parts["promoters"]: + position = promoter["position"] + position_adjustment + + # Insert the sequence at the position + engineered_seq = engineered_seq[:position] + promoter["sequence"] + engineered_seq[position:] + position_adjustment += len(promoter["sequence"]) + + # Adjust positions of subsequent elements + for part_type in ["genes", "terminators"]: + if part_type in genetic_parts: + for part in genetic_parts[part_type]: + if part["position"] > promoter["position"]: + part["position"] += len(promoter["sequence"]) + + # Add feature for visualization + feature = SeqFeature( + FeatureLocation(position, position + len(promoter["sequence"])), + type="promoter", + qualifiers={"label": promoter["name"]}, + ) + features.append(feature) + + log.append(f" - Added promoter {promoter['name']} at position {position}") + + # Add genes + if "genes" in genetic_parts: + log.append("### Adding Genes:") + for gene in genetic_parts["genes"]: + position = gene["position"] + position_adjustment + + # Insert the sequence at the position + engineered_seq = engineered_seq[:position] + gene["sequence"] + engineered_seq[position:] + position_adjustment += len(gene["sequence"]) + + # Adjust positions of subsequent elements + for part_type in ["terminators"]: + if part_type in genetic_parts: + for part in genetic_parts[part_type]: + if part["position"] > gene["position"]: + part["position"] += len(gene["sequence"]) + + # Add feature for visualization + feature = SeqFeature( + FeatureLocation(position, position + len(gene["sequence"])), + type="gene", + qualifiers={"label": gene["name"]}, + ) + features.append(feature) + + log.append(f" - Added gene {gene['name']} at position {position}") + + # Add terminators + if "terminators" in genetic_parts: + log.append("### Adding Terminators:") + for terminator in genetic_parts["terminators"]: + position = terminator["position"] + position_adjustment + + # Insert the sequence at the position + engineered_seq = engineered_seq[:position] + terminator["sequence"] + engineered_seq[position:] + position_adjustment += len(terminator["sequence"]) + + # Add feature for visualization + feature = SeqFeature( + FeatureLocation(position, position + len(terminator["sequence"])), + type="terminator", + qualifiers={"label": terminator["name"]}, + ) + features.append(feature) + + log.append(f" - Added terminator {terminator['name']} at position {position}") + + # Add therapeutic cargo + if "cargo" in genetic_parts: + cargo = genetic_parts["cargo"] + # Find a suitable position (after the last added element) + position = max([f.location.end for f in features]) if features else len(engineered_seq) // 2 + + # Insert the sequence at the position + engineered_seq = engineered_seq[:position] + cargo["sequence"] + engineered_seq[position:] + + # Add feature for visualization + feature = SeqFeature( + FeatureLocation(position, position + len(cargo["sequence"])), + type="therapeutic_cargo", + qualifiers={"label": cargo["name"]}, + ) + features.append(feature) + + log.append("### Added Therapeutic Cargo:") + log.append(f" - Added {cargo['name']} at position {position}") + log.append(f" - Cargo length: {len(cargo['sequence'])} bp\n") + + # Step 3: Create the engineered genome record + log.append("## Step 3: Finalizing Engineered Genome") + engineered_genome = SeqRecord( + seq=Seq(engineered_seq), + id=f"{genome_record.id}_engineered", + name=f"{genome_record.id}_engineered", + description=f"Engineered {genome_record.id} for therapeutic delivery", + ) + + # Add all features to the engineered genome + engineered_genome.features = features + + # Save the engineered genome to a file + output_genome_file = f"{genome_record.id}_engineered.fasta" + SeqIO.write(engineered_genome, output_genome_file, "fasta") + log.append(f"Engineered genome saved to: {output_genome_file}") + log.append(f"Total genome length: {len(engineered_genome.seq)} bp\n") + + # Step 4: Generate a plasmid map + log.append("## Step 4: Generating Plasmid Map") + + # Create the diagram + gd_diagram = GenomeDiagram.Diagram("Engineered Bacterial Genome") + gd_track = gd_diagram.new_track(1, name="Engineered Features") + gd_feature_set = gd_track.new_set() + + # Add features with different colors + for feature in features: + if feature.type == "promoter": + color = colors.green + elif feature.type == "gene": + color = colors.blue + elif feature.type == "terminator": + color = colors.red + elif feature.type == "therapeutic_cargo": + color = colors.purple + else: + color = colors.grey + + gd_feature_set.add_feature(feature, color=color, label=True) + + # Draw and save the plasmid map + plasmid_map_file = f"{genome_record.id}_engineered_map.pdf" + gd_diagram.draw(format="circular", circular=True, pagesize=(1000, 1000)) + gd_diagram.write(plasmid_map_file, "PDF") + + log.append(f"Plasmid map generated and saved to: {plasmid_map_file}\n") + + # Step 5: Final summary + log.append("## Summary of Engineered Bacterial Strain") + log.append(f"- Base strain: {genome_record.id}") + log.append("- Added genetic parts:") + + if "promoters" in genetic_parts: + log.append(f" - Promoters: {len(genetic_parts['promoters'])}") + if "genes" in genetic_parts: + log.append(f" - Genes: {len(genetic_parts['genes'])}") + if "terminators" in genetic_parts: + log.append(f" - Terminators: {len(genetic_parts['terminators'])}") + if "cargo" in genetic_parts: + log.append(f" - Therapeutic cargo: {genetic_parts['cargo']['name']}") + + log.append("\nThe engineered bacterial strain is ready for transformation and selection.") + + return "\n".join(log) + + +def analyze_bacterial_growth_rate(time_points, od_measurements, strain_name="Unknown strain", output_dir="./"): + """Analyze bacterial growth data and extract growth parameters from OD600 measurements. + + Parameters + ---------- + time_points : list or numpy.ndarray + Time points at which OD600 measurements were taken (in hours) + od_measurements : list or numpy.ndarray + Optical density (OD600) measurements corresponding to each time point + strain_name : str, optional + Name of the bacterial strain being analyzed, default is "Unknown strain" + output_dir : str, optional + Directory where to save the output files, default is current directory + + Returns + ------- + str + A research log summarizing the analysis steps, fitted parameters, and results + + """ + import os + from datetime import datetime + + import matplotlib + + matplotlib.use("Agg") # Use non-interactive backend + import matplotlib.pyplot as plt + import numpy as np + from scipy.optimize import curve_fit + + # Ensure output directory exists + os.makedirs(output_dir, exist_ok=True) + + # Convert inputs to numpy arrays if they aren't already + time_points = np.array(time_points) + od_measurements = np.array(od_measurements) + + # Define the Gompertz growth model function + def gompertz_model(t, lag, mu_max, A): + """Gompertz growth model. + + Parameters + ---------- + t: time points + lag: lag time (hours) + mu_max: maximum growth rate (per hour) + A: carrying capacity (maximum OD) + + """ + return A * np.exp(-np.exp(mu_max * np.exp(1) * (lag - t) / A + 1)) + + # Initial parameter guesses + p0 = [ + np.mean(time_points) / 3, # lag time guess: 1/3 of the mean time + 0.5, # growth rate guess: 0.5 per hour + max(od_measurements) * 1.1, # carrying capacity: slightly above max OD + ] + + # Fit the model to the data + try: + popt, pcov = curve_fit( + gompertz_model, + time_points, + od_measurements, + p0=p0, + bounds=([0, 0, 0], [np.inf, np.inf, np.inf]), + ) + lag_time, mu_max, carrying_capacity = popt + + # Calculate doubling time (ln(2)/μ) + doubling_time = np.log(2) / mu_max + + # Generate fitted curve for plotting + t_smooth = np.linspace(min(time_points), max(time_points), 100) + od_fit = gompertz_model(t_smooth, *popt) + + # Create growth curve plot + plt.figure(figsize=(10, 6)) + plt.scatter(time_points, od_measurements, label="Observed OD600") + plt.plot(t_smooth, od_fit, "r-", label="Fitted Gompertz model") + plt.xlabel("Time (hours)") + plt.ylabel("Optical Density (OD600)") + plt.title(f"Bacterial Growth Curve: {strain_name}") + plt.legend() + plt.grid(True, linestyle="--", alpha=0.7) + + # Save the plot + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + plot_filename = os.path.join(output_dir, f"{strain_name.replace(' ', '_')}_{timestamp}_growth_curve.png") + plt.savefig(plot_filename) + plt.close() + + # Create research log + log = f"""## Bacterial Growth Rate Analysis: {strain_name} + +### Analysis Summary +- **Date and Time:** {datetime.now().strftime("%Y-%m-%d %H:%M:%S")} +- **Sample:** {strain_name} +- **Data Points:** {len(time_points)} measurements over {max(time_points) - min(time_points):.2f} hours + +### Growth Parameters (Gompertz Model) +- **Lag Phase Duration:** {lag_time:.2f} hours +- **Maximum Growth Rate (μ_max):** {mu_max:.4f} per hour +- **Doubling Time:** {doubling_time:.2f} hours +- **Carrying Capacity (max OD):** {carrying_capacity:.4f} OD600 units + +### Methods +1. OD600 measurements were taken at various time points +2. Data was fitted to the Gompertz growth model +3. Growth parameters were extracted from the fitted model +4. Growth curve was plotted and saved as: {plot_filename} + +### Note +The Gompertz model provides a good representation of bacterial growth phases including lag, exponential, and stationary phases. +""" + + return log + + except RuntimeError as e: + return f"""## Bacterial Growth Rate Analysis: {strain_name} + +### Error in Analysis +Failed to fit the growth model to the provided data: {str(e)} + +### Suggestions +- Check if the data shows a typical growth pattern +- Ensure sufficient data points are provided +- Try different initial parameter guesses or a different growth model +""" + + +def analyze_barcode_sequencing_data( + input_file, + barcode_pattern=None, + flanking_seq_5prime=None, + flanking_seq_3prime=None, + min_count=5, + output_dir="./results", +): + """Analyze sequencing data to extract, quantify and determine lineage relationships of barcodes. + + Parameters + ---------- + input_file : str + Path to the input sequencing file in FASTQ or FASTA format + barcode_pattern : str, optional + Regular expression pattern to identify barcodes. If None, will use flanking sequences + flanking_seq_5prime : str, optional + 5' flanking sequence of the barcode region + flanking_seq_3prime : str, optional + 3' flanking sequence of the barcode region + min_count : int, default=5 + Minimum count threshold for considering a barcode + output_dir : str, default="./results" + Directory to save output files + + Returns + ------- + str + Research log summarizing the analysis steps and results + + """ + import os + import re + from collections import Counter + + import numpy as np + from Bio import SeqIO + from scipy.cluster.hierarchy import fcluster, linkage + from scipy.spatial.distance import pdist + + # Create output directory if it doesn't exist + os.makedirs(output_dir, exist_ok=True) + + # Initialize research log + log = "# Barcode Sequencing Analysis Research Log\n\n" + log += f"## Input Data\n- File: {input_file}\n\n" + + # Step 1: Read sequences and extract barcodes + log += "## Step 1: Extracting barcodes from sequencing data\n" + + # Determine file format based on extension + file_format = "fastq" if input_file.endswith((".fastq", ".fq")) else "fasta" + + barcodes = [] + total_reads = 0 + + for record in SeqIO.parse(input_file, file_format): + total_reads += 1 + seq_str = str(record.seq) + + # Extract barcode using pattern or flanking sequences + if barcode_pattern: + match = re.search(barcode_pattern, seq_str) + if match: + barcodes.append(match.group(0)) + elif flanking_seq_5prime and flanking_seq_3prime: + pattern = f"{flanking_seq_5prime}(.*?){flanking_seq_3prime}" + match = re.search(pattern, seq_str) + if match: + barcodes.append(match.group(1)) + + log += f"- Total reads processed: {total_reads}\n" + log += f"- Barcodes extracted: {len(barcodes)}\n\n" + + if len(barcodes) == 0: + log += "ERROR: No barcodes found. Check your barcode pattern or flanking sequences.\n" + return log + + # Step 2: Quantify barcode abundances + log += "## Step 2: Quantifying barcode abundances\n" + barcode_counts = Counter(barcodes) + + # Filter low-abundance barcodes + filtered_barcodes = {bc: count for bc, count in barcode_counts.items() if count >= min_count} + + log += f"- Unique barcodes: {len(barcode_counts)}\n" + log += f"- Barcodes with count ≥ {min_count}: {len(filtered_barcodes)}\n" + + # Save barcode counts to file + count_file = os.path.join(output_dir, "barcode_counts.tsv") + with open(count_file, "w") as f: + f.write("Barcode\tCount\tFrequency\n") + for bc, count in sorted(filtered_barcodes.items(), key=lambda x: x[1], reverse=True): + freq = count / total_reads + f.write(f"{bc}\t{count}\t{freq:.6f}\n") + + log += f"- Barcode abundances saved to: {count_file}\n\n" + + # Step 3: Analyze barcode relationships (lineage analysis) + log += "## Step 3: Analyzing barcode relationships and lineages\n" + + # Only proceed if we have enough barcodes + if len(filtered_barcodes) < 2: + log += "- Not enough barcodes for lineage analysis after filtering\n\n" + else: + # Convert barcodes to numerical representation for distance calculation + barcode_list = list(filtered_barcodes.keys()) + + # Create a simple distance matrix based on Hamming distance + def hamming_distance(s1, s2): + # If sequences have different lengths, pad the shorter one + if len(s1) != len(s2): + max_len = max(len(s1), len(s2)) + s1 = s1.ljust(max_len) + s2 = s2.ljust(max_len) + return sum(c1 != c2 for c1, c2 in zip(s1, s2, strict=False)) + + # Create distance matrix + n = len(barcode_list) + dist_matrix = np.zeros((n, n)) + for i in range(n): + for j in range(i + 1, n): + dist = hamming_distance(barcode_list[i], barcode_list[j]) + dist_matrix[i, j] = dist + dist_matrix[j, i] = dist + + # Perform hierarchical clustering + try: + condensed_dist = pdist(dist_matrix) + Z = linkage(condensed_dist, method="average") + max_dist = 3 # Max Hamming distance to consider barcodes in same lineage + clusters = fcluster(Z, max_dist, criterion="distance") + + # Count clusters + cluster_counts = Counter(clusters) + + log += f"- Identified {len(cluster_counts)} potential lineages\n" + log += f"- Largest lineage contains {max(cluster_counts.values())} barcodes\n" + + # Save lineage information + lineage_file = os.path.join(output_dir, "barcode_lineages.tsv") + with open(lineage_file, "w") as f: + f.write("Barcode\tCount\tLineage\n") + for i, bc in enumerate(barcode_list): + f.write(f"{bc}\t{filtered_barcodes[bc]}\t{clusters[i]}\n") + + log += f"- Lineage assignments saved to: {lineage_file}\n\n" + except Exception as e: + log += f"- Error in lineage analysis: {str(e)}\n\n" + + # Step 4: Summary + log += "## Summary\n" + log += f"- Processed {total_reads} sequencing reads\n" + log += f"- Identified {len(barcode_counts)} unique barcodes\n" + log += f"- {len(filtered_barcodes)} barcodes passed abundance threshold (≥ {min_count})\n" + + return log + + +def analyze_bifurcation_diagram(time_series_data, parameter_values, system_name="Dynamical System", output_dir="./"): + """Performs bifurcation analysis on a dynamical system and generates a bifurcation diagram. + + Parameters + ---------- + time_series_data : numpy.ndarray + A 2D array where each row represents a time series for a specific parameter value. + Shape should be (n_parameter_values, n_time_points). + parameter_values : numpy.ndarray + 1D array of parameter values corresponding to each time series. + Shape should be (n_parameter_values,). + system_name : str, optional + Name of the dynamical system being analyzed, used for plot titles. + output_dir : str, optional + Directory to save the output files. + + Returns + ------- + str + Research log summarizing the bifurcation analysis process and results. + + """ + import os + + import matplotlib + + matplotlib.use("Agg") # Use non-interactive backend + import matplotlib.pyplot as plt + import numpy as np + from scipy.signal import find_peaks + + # Create output directory if it doesn't exist + if not os.path.exists(output_dir): + os.makedirs(output_dir) + + # Initialize research log + research_log = f"# Bifurcation Analysis for {system_name}\n\n" + research_log += "## Analysis Steps\n\n" + + # Step 1: Verify input data + research_log += "### Step 1: Data Verification\n" + if len(parameter_values) != time_series_data.shape[0]: + research_log += "Error: Number of parameter values does not match number of time series.\n" + return research_log + + research_log += f"- Analyzed {len(parameter_values)} parameter values\n" + research_log += f"- Each time series contains {time_series_data.shape[1]} data points\n\n" + + # Step 2: Calculate indicators for different dynamical regimes + research_log += "### Step 2: Calculating Dynamical Indicators\n" + + # Initialize arrays to store results + local_maxima_counts = np.zeros(len(parameter_values)) + lyapunov_estimates = np.zeros(len(parameter_values)) + attractor_points = [] + + for i, series in enumerate(time_series_data): + # Use the last 70% of the time series to avoid transients + steady_state = series[int(0.7 * len(series)) :] + + # Find local maxima (peaks) to identify periodicity + peaks, _ = find_peaks(steady_state) + local_maxima_counts[i] = len(peaks) + + # Simple estimate of the largest Lyapunov exponent + # Positive values suggest chaos, negative values suggest stability + if len(steady_state) > 10: + diffs = np.abs(np.diff(steady_state)) + if np.mean(diffs) > 0: + lyapunov_estimates[i] = np.log(np.mean(diffs)) + else: + lyapunov_estimates[i] = -1.0 # Stable behavior + + # Collect points for the bifurcation diagram + # We use local maxima as sampling points for the attractor + if len(peaks) > 0: + attractor_points.append((parameter_values[i], steady_state[peaks])) + else: + # If no peaks, use the last few points + attractor_points.append((parameter_values[i], steady_state[-5:])) + + research_log += "- Calculated approximate Lyapunov exponents\n" + research_log += "- Identified local maxima for periodicity analysis\n" + research_log += "- Sampled attractor points for bifurcation diagram\n\n" + + # Step 3: Classify dynamical regimes + research_log += "### Step 3: Classifying Dynamical Regimes\n" + + # Initialize arrays for regime classification + regimes = np.full(len(parameter_values), "unknown", dtype=object) + + # Classify based on Lyapunov exponent and periodicity + for i in range(len(parameter_values)): + if lyapunov_estimates[i] > 0.05: + regimes[i] = "chaotic" + elif local_maxima_counts[i] == 0: + regimes[i] = "stable" + elif local_maxima_counts[i] == 1: + regimes[i] = "period-1" + elif local_maxima_counts[i] == 2: + regimes[i] = "period-2" + elif local_maxima_counts[i] == 4: + regimes[i] = "period-4" + elif local_maxima_counts[i] > 4: + if lyapunov_estimates[i] > 0: + regimes[i] = "chaotic" + else: + regimes[i] = f"period-{int(local_maxima_counts[i])}" + + # Count regime types + unique_regimes = np.unique(regimes) + for regime in unique_regimes: + count = np.sum(regimes == regime) + research_log += f"- Identified {count} parameter values in '{regime}' regime\n" + + research_log += "\n" + + # Step 4: Create bifurcation diagram + research_log += "### Step 4: Creating Bifurcation Diagram\n" + + plt.figure(figsize=(12, 8)) + + # Plot bifurcation points + for param_val, points in attractor_points: + y_values = points.flatten() # Flatten in case points is multi-dimensional + plt.plot([param_val] * len(y_values), y_values, "k.", markersize=0.5) + + # Highlight different regimes with colored background + regime_changes = np.where(regimes[:-1] != regimes[1:])[0] + regime_boundaries = ( + [parameter_values[0]] + [parameter_values[i + 1] for i in regime_changes] + [parameter_values[-1]] + ) + + colors = { + "stable": "lightblue", + "period-1": "lightgreen", + "period-2": "lightyellow", + "period-4": "lightpink", + "chaotic": "salmon", + } + + for i in range(len(regime_boundaries) - 1): + start_idx = list(parameter_values).index(regime_boundaries[i]) + regime = regimes[start_idx] + if regime in colors: + plt.axvspan( + regime_boundaries[i], + regime_boundaries[i + 1], + alpha=0.3, + color=colors.get(regime, "lightgray"), + ) + + plt.xlabel("Parameter Value") + plt.ylabel("State Variable") + plt.title(f"Bifurcation Diagram for {system_name}") + + # Add legend for regimes + from matplotlib.patches import Patch + + legend_elements = [ + Patch(facecolor=colors.get(regime, "lightgray"), alpha=0.3, label=regime) + for regime in unique_regimes + if regime in colors + ] + plt.legend(handles=legend_elements, loc="upper right") + + # Save the figure + output_file = os.path.join(output_dir, f"{system_name.replace(' ', '_')}_bifurcation_diagram.png") + plt.savefig(output_file, dpi=300) + plt.close() + + research_log += "- Created bifurcation diagram showing different dynamical regimes\n" + research_log += f"- Diagram saved as: {output_file}\n\n" + + # Step 5: Identify regime transitions + research_log += "### Step 5: Identifying Regime Transitions\n" + + if len(regime_changes) > 0: + for i in regime_changes: + research_log += f"- Transition from '{regimes[i]}' to '{regimes[i + 1]}' at parameter value ≈ {parameter_values[i + 1]:.4f}\n" + else: + research_log += "- No regime transitions detected in the parameter range\n" + + research_log += "\n### Summary\n" + research_log += f"Bifurcation analysis completed for {system_name}. " + research_log += f"The system exhibits {len(unique_regimes)} different dynamical regimes " + research_log += f"across the parameter range [{parameter_values[0]:.4f}, {parameter_values[-1]:.4f}].\n" + + return research_log + + +def create_biochemical_network_sbml_model(reaction_network, kinetic_parameters, output_file="biochemical_model.xml"): + """Generate a mathematical model of a biochemical network in SBML format. + + Parameters + ---------- + reaction_network : list of dict + List of dictionaries, each representing a reaction with keys: + - 'id': Reaction identifier + - 'name': Reaction name + - 'reactants': Dict of reactant species IDs and their stoichiometry + - 'products': Dict of product species IDs and their stoichiometry + - 'reversible': Boolean indicating if reaction is reversible + + kinetic_parameters : dict + Dictionary mapping reaction IDs to their kinetic law parameters. + Each entry should contain: + - 'law_type': Type of kinetic law (e.g., 'mass_action', 'michaelis_menten') + - 'parameters': Dict of parameter names and values + + output_file : str, optional + File path to save the SBML model (default: "biochemical_model.xml") + + Returns + ------- + str + Research log summarizing the model creation process + + """ + import os + + import libsbml + + log = "# Biochemical Network SBML Model Creation Log\n\n" + + # Step 1: Create an SBML document + log += "## Step 1: Creating SBML document\n" + sbml_ns = libsbml.SBMLNamespaces(3, 2) # SBML Level 3 Version 2 + document = libsbml.SBMLDocument(sbml_ns) + model = document.createModel() + model.setId("biochemical_network_model") + model.setName("Biochemical Network Model") + log += "- Created SBML Level 3 Version 2 document\n" + log += "- Created model with ID 'biochemical_network_model'\n\n" + + # Step 2: Create default compartment + log += "## Step 2: Creating default compartment\n" + compartment = model.createCompartment() + compartment.setId("default") + compartment.setConstant(True) + compartment.setSize(1.0) + compartment.setSpatialDimensions(3) + log += "- Created default compartment\n\n" + + # Step 3: Create species + log += "## Step 3: Creating species\n" + species_set = set() + + # Collect all unique species from reactions + for reaction in reaction_network: + for species_id in list(reaction["reactants"].keys()) + list(reaction["products"].keys()): + species_set.add(species_id) + + # Create species in the model + for species_id in species_set: + species = model.createSpecies() + species.setId(species_id) + species.setCompartment("default") + species.setInitialConcentration(0.0) # Default initial concentration + species.setHasOnlySubstanceUnits(False) + species.setBoundaryCondition(False) + species.setConstant(False) + log += f"- Created species '{species_id}'\n" + log += "\n" + + # Step 4: Create reactions + log += "## Step 4: Creating reactions and kinetic laws\n" + for reaction_data in reaction_network: + reaction = model.createReaction() + reaction.setId(reaction_data["id"]) + reaction.setName(reaction_data["name"]) + reaction.setReversible(reaction_data["reversible"]) + reaction.setFast(False) + + # Add reactants + for reactant_id, stoichiometry in reaction_data["reactants"].items(): + species_ref = reaction.createReactant() + species_ref.setSpecies(reactant_id) + species_ref.setStoichiometry(stoichiometry) + species_ref.setConstant(True) + + # Add products + for product_id, stoichiometry in reaction_data["products"].items(): + species_ref = reaction.createProduct() + species_ref.setSpecies(product_id) + species_ref.setStoichiometry(stoichiometry) + species_ref.setConstant(True) + + # Create kinetic law + if reaction_data["id"] in kinetic_parameters: + kinetic_data = kinetic_parameters[reaction_data["id"]] + kinetic_law = reaction.createKineticLaw() + + # Add parameters to kinetic law + for param_name, param_value in kinetic_data["parameters"].items(): + parameter = kinetic_law.createParameter() + parameter.setId(param_name) + parameter.setValue(param_value) + + # Set the appropriate formula based on the law type + if kinetic_data["law_type"] == "mass_action": + # Simple mass action kinetics + reactants_formula = " * ".join([f"{reactant_id}" for reactant_id in reaction_data["reactants"]]) + formula = f"k * {reactants_formula}" if reactants_formula else "k" + kinetic_law.setMath(libsbml.parseL3Formula(formula)) + elif kinetic_data["law_type"] == "michaelis_menten": + # Michaelis-Menten kinetics (assuming single substrate) + substrate = list(reaction_data["reactants"].keys())[0] if reaction_data["reactants"] else "S" + formula = f"Vmax * {substrate} / (Km + {substrate})" + kinetic_law.setMath(libsbml.parseL3Formula(formula)) + # Custom formula provided in the parameters + elif "formula" in kinetic_data: + kinetic_law.setMath(libsbml.parseL3Formula(kinetic_data["formula"])) + + log += f"- Created reaction '{reaction_data['id']}' ({reaction_data['name']})\n" + if reaction_data["id"] in kinetic_parameters: + log += f" - Added kinetic law of type '{kinetic_parameters[reaction_data['id']]['law_type']}'\n" + log += "\n" + + # Step 5: Validate the model + log += "## Step 5: Validating the SBML model\n" + document.setConsistencyChecks(libsbml.LIBSBML_CAT_UNITS_CONSISTENCY, False) + consistent = document.checkConsistency() + if consistent == 0: + log += "- Model passed consistency check\n" + else: + log += "- Model has consistency issues:\n" + for i in range(document.getNumErrors()): + error = document.getError(i) + log += f" - {error.getMessage()}\n" + log += "\n" + + # Step 6: Write model to file + log += "## Step 6: Saving the SBML model\n" + libsbml.writeSBMLToFile(document, output_file) + log += f"- Model saved to '{os.path.abspath(output_file)}'\n\n" + + # Step 7: Summary + log += "## Summary\n" + log += f"- Created SBML model with {len(species_set)} species and {len(reaction_network)} reactions\n" + log += f"- Model saved as '{output_file}'\n" + + return log + + +def optimize_codons_for_heterologous_expression(target_sequence, host_codon_usage): + """Analyzes and optimizes a DNA/RNA sequence for improved expression in a heterologous host organism. + + Parameters + ---------- + target_sequence : str + The DNA or RNA sequence of the target gene to be optimized. + Should contain complete codons (length divisible by 3). + + host_codon_usage : dict + Dictionary mapping codons to their usage frequency in the host organism. + Format: {'AUG': 0.8, 'GCC': 0.6, ...} or {'ATG': 0.8, 'GCC': 0.6, ...} + + Returns + ------- + str + A research log summarizing the optimization process and results. + + """ + from Bio.Data import CodonTable + + # Initialize research log + log = "# Codon Optimization Analysis Research Log\n\n" + + # Check if sequence is DNA or RNA and standardize to DNA + is_rna = "U" in target_sequence + working_seq = target_sequence.replace("U", "T") if is_rna else target_sequence + + log += "## Input Sequence Analysis\n" + log += f"- Sequence type: {'RNA' if is_rna else 'DNA'}\n" + log += f"- Sequence length: {len(working_seq)} nucleotides\n" + + # Verify sequence length is divisible by 3 + if len(working_seq) % 3 != 0: + log += ( + f"- WARNING: Sequence length ({len(working_seq)}) is not divisible by 3. Optimization may be incomplete.\n" + ) + + # Extract codons from the sequence + original_codons = [working_seq[i : i + 3] for i in range(0, len(working_seq), 3)] + log += f"- Number of codons: {len(original_codons)}\n\n" + + # Get standard genetic code + standard_table = CodonTable.standard_dna_table + + # Create codon to amino acid mapping + codon_to_aa = {} + for codon, amino_acid in standard_table.forward_table.items(): + codon_to_aa[codon] = amino_acid + + # Add stop codons + for stop_codon in standard_table.stop_codons: + codon_to_aa[stop_codon] = "*" # * represents stop codon + + # Create amino acid to codon mapping with host frequencies + aa_to_codons = {} + for codon, aa in codon_to_aa.items(): + if aa not in aa_to_codons: + aa_to_codons[aa] = [] + # Use host frequency or default to 0 if not provided + frequency = host_codon_usage.get(codon, 0) + aa_to_codons[aa].append((codon, frequency)) + + # Sort codons by frequency for each amino acid + for aa in aa_to_codons: + aa_to_codons[aa] = sorted(aa_to_codons[aa], key=lambda x: x[1], reverse=True) + + # Optimize codons + optimized_codons = [] + for codon in original_codons: + try: + aa = codon_to_aa[codon] + # Select the highest frequency codon for this amino acid + best_codon = aa_to_codons[aa][0][0] if aa_to_codons[aa] else codon + optimized_codons.append(best_codon) + except KeyError: + # If codon not in standard table, keep original + optimized_codons.append(codon) + log += f"- WARNING: Non-standard codon detected: {codon}. Keeping original.\n" + + # Combine optimized codons into sequence + optimized_sequence = "".join(optimized_codons) + + # Convert back to RNA if input was RNA + if is_rna: + optimized_sequence = optimized_sequence.replace("T", "U") + + # Calculate optimization statistics + codon_changes = sum(1 for i, j in zip(original_codons, optimized_codons, strict=False) if i != j) + percent_changed = (codon_changes / len(original_codons)) * 100 if original_codons else 0 + + log += "## Optimization Results\n" + log += f"- Codons modified: {codon_changes} out of {len(original_codons)} ({percent_changed:.2f}%)\n" + + # Save sequences to files + with open("original_sequence.txt", "w") as f: + f.write(target_sequence) + + with open("optimized_sequence.txt", "w") as f: + f.write(optimized_sequence) + + log += "- Original sequence saved to: original_sequence.txt\n" + log += "- Optimized sequence saved to: optimized_sequence.txt\n\n" + + # Add summary + log += "## Summary\n" + log += "The target gene sequence has been optimized for expression in the provided host organism. " + log += f"The optimization process replaced {codon_changes} codons ({percent_changed:.2f}%) " + log += "with synonymous codons that have higher usage frequency in the host.\n" + + return log + + +def simulate_gene_circuit_with_growth_feedback( + circuit_topology, + kinetic_params, + growth_params, + simulation_time=100, + time_points=1000, +): + """Simulate gene regulatory circuit dynamics with growth feedback. + + Parameters + ---------- + circuit_topology : numpy.ndarray + Adjacency matrix representing the gene circuit topology. + Positive values indicate activation, negative values indicate repression. + Shape should be (n_genes, n_genes) where n_genes is the number of genes in the circuit. + + kinetic_params : dict + Dictionary containing kinetic parameters: + - 'basal_rates': list of basal expression rates for each gene + - 'degradation_rates': list of degradation rates for each gene + - 'hill_coefficients': list of Hill coefficients for regulatory interactions + - 'threshold_constants': list of threshold constants for regulatory interactions + + growth_params : dict + Dictionary containing growth-related parameters: + - 'max_growth_rate': maximum cell growth rate + - 'growth_inhibition': how gene expression affects growth + - 'gene_growth_weights': weights for how each gene affects growth + + simulation_time : float, optional + Total simulation time (default: 100) + + time_points : int, optional + Number of time points to sample (default: 1000) + + Returns + ------- + str + Research log summarizing the simulation and results with file names of saved data + + """ + import datetime + import json + import os + + import numpy as np + from scipy.integrate import solve_ivp + + # Extract parameters + n_genes = circuit_topology.shape[0] + basal_rates = kinetic_params["basal_rates"] + degradation_rates = kinetic_params["degradation_rates"] + hill_coefficients = kinetic_params["hill_coefficients"] + threshold_constants = kinetic_params["threshold_constants"] + + max_growth_rate = growth_params["max_growth_rate"] + growth_inhibition = growth_params["growth_inhibition"] + gene_growth_weights = growth_params["gene_growth_weights"] + + # Define the ODE system + def gene_circuit_odes(t, state): + # Extract state variables + gene_expressions = state[:n_genes] + cell_mass = state[n_genes] + + # Initialize derivatives + dxdt = np.zeros(n_genes + 1) + + # Gene expression dynamics + for i in range(n_genes): + # Basal expression + production = basal_rates[i] + + # Regulatory influences + for j in range(n_genes): + if circuit_topology[i, j] != 0: + # Calculate regulatory effect + regulation = gene_expressions[j] ** hill_coefficients[j] / ( + threshold_constants[j] ** hill_coefficients[j] + gene_expressions[j] ** hill_coefficients[j] + ) + + if circuit_topology[i, j] > 0: # Activation + production *= 1 + circuit_topology[i, j] * regulation + else: # Repression + production *= 1 + circuit_topology[i, j] * (1 - regulation) + + # Dilution due to growth and degradation + dilution = degradation_rates[i] + (dxdt[n_genes] / cell_mass if cell_mass > 0 else 0) + + # Final rate equation for gene i + dxdt[i] = production - dilution * gene_expressions[i] + + # Cell growth dynamics + growth_burden = sum(gene_growth_weights[i] * gene_expressions[i] for i in range(n_genes)) + dxdt[n_genes] = max_growth_rate * cell_mass / (1 + growth_inhibition * growth_burden) + + return dxdt + + # Initial conditions (starting with low gene expression and unit cell mass) + initial_state = np.zeros(n_genes + 1) + initial_state[:n_genes] = 0.1 # Low initial gene expression + initial_state[n_genes] = 1.0 # Initial cell mass + + # Solve the ODE system + t_span = (0, simulation_time) + t_eval = np.linspace(0, simulation_time, time_points) + + solution = solve_ivp( + gene_circuit_odes, + t_span, + initial_state, + method="LSODA", + t_eval=t_eval, + rtol=1e-6, + atol=1e-9, + ) + + # Extract results + time_series = solution.t + gene_expression_time_series = solution.y[:n_genes, :] + cell_growth_time_series = solution.y[n_genes, :] + + # Save results to files + timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") + results_dir = "gene_circuit_results" + + if not os.path.exists(results_dir): + os.makedirs(results_dir) + + # Save time series data + data_filename = f"{results_dir}/gene_circuit_simulation_{timestamp}.npz" + np.savez( + data_filename, + time=time_series, + gene_expression=gene_expression_time_series, + cell_growth=cell_growth_time_series, + ) + + # Save parameters for reference + params_filename = f"{results_dir}/simulation_params_{timestamp}.json" + params_dict = { + "circuit_topology": circuit_topology.tolist(), + "kinetic_params": kinetic_params, + "growth_params": growth_params, + "simulation_time": simulation_time, + "time_points": time_points, + } + + with open(params_filename, "w") as f: + json.dump(params_dict, f, indent=2) + + # Create research log + log = f"""Gene Regulatory Circuit Simulation with Growth Feedback - {timestamp} + +SIMULATION SUMMARY: +- Simulated a gene regulatory circuit with {n_genes} genes +- Incorporated growth feedback mechanisms +- Total simulation time: {simulation_time} time units +- Collected {time_points} data points + +PARAMETERS: +- Basal expression rates: {basal_rates} +- Degradation rates: {degradation_rates} +- Max growth rate: {max_growth_rate} +- Growth inhibition factor: {growth_inhibition} + +RESULTS: +- Final gene expression levels: {gene_expression_time_series[:, -1].tolist()} +- Final cell mass: {cell_growth_time_series[-1]:.4f} +- Growth rate at end of simulation: {max_growth_rate / (1 + growth_inhibition * sum(gene_growth_weights[i] * gene_expression_time_series[i, -1] for i in range(n_genes))):.4f} + +FILES: +- Time series data saved to: {data_filename} +- Simulation parameters saved to: {params_filename} +""" + + return log + + +def identify_fas_functional_domains(sequence, sequence_type="protein", output_file="fas_domains_report.txt"): + """Identifies functional domains within a Fatty Acid Synthase (FAS) sequence and predicts their roles. + + Parameters + ---------- + sequence : str + The nucleotide or protein sequence of a FAS gene + sequence_type : str + Type of sequence provided - "protein" or "nucleotide" (default: "protein") + output_file : str + Name of the output file to save the detailed domain report (default: "fas_domains_report.txt") + + Returns + ------- + str + A research log summarizing the steps taken and results of the domain analysis + + """ + import json + import time + + import requests + from Bio.Seq import Seq + + research_log = "# Fatty Acid Synthase (FAS) Domain Analysis\n\n" + research_log += "## Input Sequence Analysis\n" + research_log += f"- Sequence type: {sequence_type}\n" + research_log += ( + f"- Sequence length: {len(sequence)} {'nucleotides' if sequence_type == 'nucleotide' else 'amino acids'}\n\n" + ) + + # Convert nucleotide to protein if needed + if sequence_type == "nucleotide": + research_log += "## Sequence Translation\n" + research_log += "- Converting nucleotide sequence to protein\n" + try: + protein_seq = str(Seq(sequence).translate()) + research_log += f"- Translated protein length: {len(protein_seq)} amino acids\n\n" + except Exception as e: + research_log += f"- Error in translation: {str(e)}\n" + return research_log + else: + protein_seq = sequence + + # FAS domain information dictionary + fas_domains = { + "ketoacyl-synt": { + "name": "Ketoacyl Synthase (KS)", + "function": "Catalyzes the condensation of malonyl-ACP with the growing fatty acid chain", + }, + "Ketoacyl-synt_C": { + "name": "Ketoacyl Synthase C-terminal (KS-C)", + "function": "C-terminal domain of KS, involved in substrate binding", + }, + "Acyl_transf_1": { + "name": "Acyltransferase (AT)", + "function": "Transfers acyl groups from acyl-CoA to ACP", + }, + "ketoacyl-red": { + "name": "Ketoacyl Reductase (KR)", + "function": "Reduces the beta-ketoacyl group to a beta-hydroxyacyl group", + }, + "Thioesterase": { + "name": "Thioesterase (TE)", + "function": "Releases the fatty acid from ACP by hydrolysis", + }, + "PS-DH": { + "name": "Dehydratase (DH)", + "function": "Removes water from beta-hydroxyacyl-ACP to form trans-2-enoyl-ACP", + }, + "Enoyl_red": { + "name": "Enoyl Reductase (ER)", + "function": "Reduces the double bond in enoyl-ACP to a saturated acyl-ACP", + }, + "ACP": { + "name": "Acyl Carrier Protein (ACP)", + "function": "Carries the growing fatty acid chain between enzyme domains", + }, + "PP-binding": { + "name": "Phosphopantetheine Binding Domain", + "function": "Attachment site for the phosphopantetheine prosthetic group in ACP", + }, + } + + # HMMER web API for domain identification + research_log += "## Domain Identification\n" + research_log += "- Using HMMER web API to search against Pfam database\n" + + url = "https://www.ebi.ac.uk/Tools/hmmer/search/hmmscan" + headers = {"Content-Type": "application/json", "Accept": "application/json"} + data = {"hmmdb": "pfam", "seq": protein_seq} + + try: + response = requests.post(url, data=json.dumps(data), headers=headers) + if response.status_code != 200: + research_log += f"- Error: HMMER API returned status code {response.status_code}\n" + return research_log + + result_url = response.headers.get("Location", "") + if not result_url: + research_log += "- Error: No result URL returned from HMMER API\n" + return research_log + + # Wait for results to be ready + time.sleep(2) + + # Get results + result_response = requests.get(f"{result_url}.json") + if result_response.status_code != 200: + research_log += "- Error: Could not retrieve results from HMMER API\n" + return research_log + + results = result_response.json() + + # Process results + domains_found = [] + if "results" in results and "hits" in results["results"]: + hits = results["results"]["hits"] + for hit in hits: + if "domains" in hit: + for domain in hit["domains"]: + domain_name = hit.get("name", "").split(".")[0] + domain_desc = hit.get("desc", "Unknown") + domain_start = domain.get("ali_from", 0) + domain_end = domain.get("ali_to", 0) + + domains_found.append( + { + "name": domain_name, + "description": domain_desc, + "start": domain_start, + "end": domain_end, + "score": domain.get("score", 0), + } + ) + + # Check for FAS domains + research_log += f"- Found {len(domains_found)} domains in total\n\n" + research_log += "## FAS Functional Domains Identified\n" + + fas_domains_found = [] + for domain in domains_found: + for fas_key, fas_info in fas_domains.items(): + if fas_key.lower() in domain["name"].lower(): + fas_domains_found.append( + { + "name": fas_info["name"], + "pfam_id": domain["name"], + "start": domain["start"], + "end": domain["end"], + "function": fas_info["function"], + } + ) + + if not fas_domains_found: + research_log += "- No specific FAS domains identified in the sequence\n" + else: + research_log += f"- Found {len(fas_domains_found)} FAS-related domains\n" + for i, domain in enumerate(fas_domains_found, 1): + research_log += f"\n### {i}. {domain['name']} (Positions {domain['start']}-{domain['end']})\n" + research_log += f"- Pfam ID: {domain['pfam_id']}\n" + research_log += f"- Function: {domain['function']}\n" + + # Save detailed report to file + with open(output_file, "w") as f: + f.write("# Fatty Acid Synthase (FAS) Domain Analysis Detailed Report\n\n") + f.write("## Input Sequence\n") + f.write(f"- Type: {sequence_type}\n") + f.write(f"- Length: {len(sequence)} {'nucleotides' if sequence_type == 'nucleotide' else 'amino acids'}\n") + if sequence_type == "nucleotide": + f.write(f"- Translated protein length: {len(protein_seq)} amino acids\n") + + f.write("\n## All Domains Found\n") + for i, domain in enumerate(domains_found, 1): + f.write(f"\n### {i}. {domain['name']} (Positions {domain['start']}-{domain['end']})\n") + f.write(f"- Description: {domain['description']}\n") + f.write(f"- Score: {domain['score']}\n") + + f.write("\n## FAS Functional Domains\n") + if not fas_domains_found: + f.write("- No specific FAS domains identified in the sequence\n") + else: + for i, domain in enumerate(fas_domains_found, 1): + f.write(f"\n### {i}. {domain['name']} (Positions {domain['start']}-{domain['end']})\n") + f.write(f"- Pfam ID: {domain['pfam_id']}\n") + f.write(f"- Function: {domain['function']}\n") + + research_log += "\n## Summary\n" + research_log += f"- Detailed domain analysis saved to: {output_file}\n" + if fas_domains_found: + research_log += "- The sequence contains multiple domains characteristic of Fatty Acid Synthase\n" + research_log += "- The identified domains suggest this sequence is involved in fatty acid biosynthesis\n" + else: + research_log += "- The sequence does not contain typical FAS domains\n" + research_log += "- This may not be a Fatty Acid Synthase gene or may be a partial sequence\n" + + except Exception as e: + research_log += f"- Error in domain identification: {str(e)}\n" + + return research_log diff --git a/BioScientist/agent_system/engines/v1_executor_backup/tool/systems_biology.py b/BioScientist/agent_system/engines/v1_executor_backup/tool/systems_biology.py new file mode 100644 index 0000000000000000000000000000000000000000..8f2c5951d7b41867a7d49c33d5ab08552955056c --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/tool/systems_biology.py @@ -0,0 +1,926 @@ +def query_chatnt(question, sequence, device=-1): + """ + Call ChatNT to answer a question about a DNA sequence. + + Parameters: + ----------- + question : str + Question to ask about the DNA sequence + sequence : str + DNA sequence to analyze + device : int, optional + Device to use for the ChatNT model. Default is -1 (CPU). + + Returns: + -------- + str + Answer to the question + """ + from transformers import pipeline + + pipe = pipeline(model="InstaDeepAI/ChatNT", trust_remote_code=True, device=device) + + # Define custom inputs (note that the number of token in the english sequence must be equal to len(dna_sequences)) + english_sequence = f"{question} ?" + dna_sequences = [sequence] + + # Generate sequence + generated_english_sequence = pipe(inputs={"english_sequence": english_sequence, "dna_sequences": dna_sequences}) + + return generated_english_sequence + + +def perform_flux_balance_analysis(model_file, constraints=None, objective_reaction=None, output_file="fba_results.csv"): + """ + Perform Flux Balance Analysis (FBA) on a genome-scale metabolic network model. + + FBA is a computational technique that predicts metabolic flux distributions + by formulating and solving a linear optimization problem. + + Parameters: + ----------- + model_file : str + Path to the metabolic model file (SBML or JSON format) + constraints : dict, optional + Dictionary of reaction constraints where keys are reaction IDs and + values are tuples of (lower_bound, upper_bound) + objective_reaction : str, optional + Reaction ID to use as the objective function (e.g., biomass reaction) + If None, uses the model's default objective + output_file : str, optional + File name to save the flux distribution results + + Returns: + -------- + str + Research log summarizing the FBA process and results + """ + + import cobra + import pandas as pd + + # Initialize research log + log = "# Flux Balance Analysis (FBA) Research Log\n\n" + + # Step 1: Load the metabolic network model + log += "## Step 1: Loading metabolic model\n" + try: + if model_file.endswith(".xml") or model_file.endswith(".sbml"): + model = cobra.io.read_sbml_model(model_file) + elif model_file.endswith(".json"): + model = cobra.io.load_json_model(model_file) + else: + model = cobra.io.load_model(model_file) + log += f"- Successfully loaded model from {model_file}\n" + log += f"- Model contains {len(model.reactions)} reactions and {len(model.metabolites)} metabolites\n\n" + except Exception as e: + log += f"- Error loading model: {str(e)}\n" + return log + + # Step 2: Set constraints + log += "## Step 2: Setting constraints\n" + if constraints: + log += "- Applied the following constraints:\n" + for reaction_id, (lb, ub) in constraints.items(): + try: + reaction = model.reactions.get_by_id(reaction_id) + reaction.bounds = (lb, ub) + log += f" * {reaction_id}: lower_bound={lb}, upper_bound={ub}\n" + except Exception as e: + log += f" * Error setting constraint for {reaction_id}: {str(e)}\n" + else: + log += "- No additional constraints specified, using model defaults\n" + log += "\n" + + # Step 3: Set objective function + log += "## Step 3: Setting objective function\n" + if objective_reaction: + try: + model.objective = objective_reaction + log += f"- Set objective function to maximize {objective_reaction}\n\n" + except Exception as e: + log += f"- Error setting objective function: {str(e)}\n" + log += "- Using model's default objective function\n\n" + else: + log += f"- Using model's default objective function: {model.objective.expression}\n\n" + + # Step 4: Solve the FBA problem + log += "## Step 4: Solving FBA optimization problem\n" + try: + solution = model.optimize() + log += f"- Optimization status: {solution.status}\n" + log += f"- Objective value: {solution.objective_value:.6f}\n\n" + except Exception as e: + log += f"- Error during optimization: {str(e)}\n" + return log + + # Step 5: Save and report results + log += "## Step 5: Analyzing flux distribution\n" + + # Create a dataframe with the flux distribution + flux_distribution = pd.DataFrame( + { + "reaction_id": [r.id for r in model.reactions], + "reaction_name": [r.name for r in model.reactions], + "flux": [solution.fluxes[r.id] for r in model.reactions], + "lower_bound": [r.lower_bound for r in model.reactions], + "upper_bound": [r.upper_bound for r in model.reactions], + } + ) + + # Save to file + flux_distribution.to_csv(output_file, index=False) + log += f"- Flux distribution saved to {output_file}\n" + + # Report active reactions (non-zero flux) + active_reactions = flux_distribution[abs(flux_distribution["flux"]) > 1e-6] + log += f"- Number of active reactions (flux > 1e-6): {len(active_reactions)}\n" + + # Report top reactions by absolute flux + top_reactions = flux_distribution.iloc[abs(flux_distribution["flux"]).argsort()[::-1]].head(10) + log += "- Top 10 reactions by absolute flux magnitude:\n" + for _, row in top_reactions.iterrows(): + log += f" * {row['reaction_id']} ({row['reaction_name']}): {row['flux']:.6f}\n" + + log += f"\nFBA analysis complete. Full results available in {output_file}\n" + + return log + + +def model_protein_dimerization_network(monomer_concentrations, dimerization_affinities, network_topology): + """ + Model protein dimerization networks to find equilibrium concentrations of dimers. + + Parameters: + ----------- + monomer_concentrations : dict + Dictionary mapping monomer names to their initial concentrations (in arbitrary units) + dimerization_affinities : dict + Dictionary mapping dimer names (as 'A-B' strings) to their association constants (Ka) + network_topology : list of tuples + List of (monomer1, monomer2) pairs that can form dimers + + Returns: + -------- + str + Research log summarizing the modeling process and results + """ + import time + + import numpy as np + from scipy.integrate import solve_ivp + + # Start timing + start_time = time.time() + + # Extract unique monomers and create mapping to indices + all_monomers = list(monomer_concentrations.keys()) + monomer_to_idx = {monomer: i for i, monomer in enumerate(all_monomers)} + + # Create mapping from dimer indices to names + dimer_pairs = [] + dimer_names = [] + dimer_affinities = [] + + for m1, m2 in network_topology: + dimer_name = f"{m1}-{m2}" + if dimer_name in dimerization_affinities: + affinity = dimerization_affinities[dimer_name] + else: + # Try reverse order + dimer_name = f"{m2}-{m1}" + if dimer_name in dimerization_affinities: + affinity = dimerization_affinities[dimer_name] + else: + raise ValueError(f"No affinity found for dimer pair {m1}-{m2}") + + dimer_pairs.append((monomer_to_idx[m1], monomer_to_idx[m2])) + dimer_names.append(dimer_name) + dimer_affinities.append(affinity) + + # Initial conditions (monomer concentrations followed by dimer concentrations) + num_monomers = len(all_monomers) + num_dimers = len(dimer_pairs) + y0 = np.zeros(num_monomers + num_dimers) + + for monomer, conc in monomer_concentrations.items(): + y0[monomer_to_idx[monomer]] = conc + + # Define ODE system + def dimerization_odes(t, y): + dydt = np.zeros_like(y) + + # Extract current concentrations + monomer_concs = y[:num_monomers] + dimer_concs = y[num_monomers:] + + # Calculate rate of change for each species + for i, ((m1_idx, m2_idx), affinity) in enumerate(zip(dimer_pairs, dimer_affinities, strict=False)): + # Formation rate: kon * [A] * [B] + # Dissociation rate: koff * [AB] + # At equilibrium: kon/koff = Ka (affinity) + # For simplicity, set kon = Ka and koff = 1 + + kon = affinity + koff = 1.0 + + # Dimer formation/dissociation + dimer_idx = num_monomers + i + + # Rate of change + formation_rate = kon * monomer_concs[m1_idx] * monomer_concs[m2_idx] + dissociation_rate = koff * dimer_concs[i] + net_rate = formation_rate - dissociation_rate + + # Update rates for monomers and dimers + dydt[m1_idx] -= net_rate + dydt[m2_idx] -= net_rate + dydt[dimer_idx] += net_rate + + return dydt + + # Solve ODE system to equilibrium + # Using a long enough time to reach equilibrium + t_span = (0, 1000) # Arbitrary long time to reach equilibrium + + sol = solve_ivp( + dimerization_odes, + t_span, + y0, + method="BDF", # Good for stiff problems + rtol=1e-6, + atol=1e-9, + ) + + # Extract final concentrations (equilibrium) + final_monomer_concs = {monomer: sol.y[idx][-1] for monomer, idx in monomer_to_idx.items()} + final_dimer_concs = {dimer_name: sol.y[num_monomers + i][-1] for i, dimer_name in enumerate(dimer_names)} + + # Calculate time taken + elapsed_time = time.time() - start_time + + # Generate research log + log = "Protein Dimerization Network Modeling\n" + log += "=====================================\n\n" + log += "Network summary:\n" + log += f"- Number of monomers: {num_monomers}\n" + log += f"- Number of possible dimers: {num_dimers}\n\n" + + log += "Initial conditions:\n" + for monomer, conc in monomer_concentrations.items(): + log += f"- {monomer}: {conc:.4f}\n" + log += "\n" + + log += "Dimerization affinities:\n" + for dimer, affinity in zip(dimer_names, dimer_affinities, strict=False): + log += f"- {dimer}: {affinity:.4e}\n" + log += "\n" + + log += "Equilibrium concentrations:\n" + log += "Monomers:\n" + for monomer, conc in final_monomer_concs.items(): + log += f"- {monomer}: {conc:.4f}\n" + + log += "\nDimers:\n" + for dimer, conc in final_dimer_concs.items(): + log += f"- {dimer}: {conc:.4f}\n" + log += "\n" + + log += f"Simulation completed in {elapsed_time:.2f} seconds.\n" + + return log + + +def simulate_metabolic_network_perturbation( + model_file, initial_concentrations, perturbation_params, simulation_time=100, time_points=1000 +): + """ + Construct and simulate kinetic models of metabolic networks and analyze their responses to perturbations. + + Parameters: + ----------- + model_file : str + Path to the COBRA model file (SBML format) + initial_concentrations : dict + Dictionary mapping metabolite IDs to their initial concentrations + perturbation_params : dict + Dictionary with the following keys: + - 'time': float, time at which perturbation is applied + - 'metabolite': str, ID of the metabolite to perturb + - 'factor': float, multiplication factor for the metabolite concentration + simulation_time : float, optional + Total simulation time (default: 100) + time_points : int, optional + Number of time points to simulate (default: 1000) + + Returns: + -------- + str + Research log summarizing the steps taken and results obtained + """ + + import cobra + import numpy as np + import pandas as pd + from scipy.integrate import solve_ivp + + # Step 1: Load the metabolic model + try: + model = cobra.io.read_sbml_model(model_file) + log = f"Loaded metabolic model from {model_file} with {len(model.reactions)} reactions and {len(model.metabolites)} metabolites.\n\n" + except Exception as e: + return f"Error loading model: {str(e)}" + + # Step 2: Set up metabolite list and initial concentrations + metabolites = list(model.metabolites) + metabolite_ids = [m.id for m in metabolites] + + # Set default initial concentrations for metabolites not specified + conc_array = np.ones(len(metabolites)) + for i, m_id in enumerate(metabolite_ids): + if m_id in initial_concentrations: + conc_array[i] = initial_concentrations[m_id] + + log += "Initial concentrations set up for all metabolites.\n\n" + + # Step 3: Define kinetic model using simple mass-action kinetics + def mass_action_kinetics(reaction, concentrations, metabolite_ids): + # Simple mass-action kinetics: product of substrate concentrations + rate = 1.0 + for metabolite, coeff in reaction.metabolites.items(): + if coeff < 0: # Substrate + idx = metabolite_ids.index(metabolite.id) + rate *= concentrations[idx] ** abs(coeff) + return rate + + # Step 4: Define ODE system + def ode_system(t, concentrations, metabolite_ids, reactions, perturbation): + dC_dt = np.zeros(len(concentrations)) + + # Apply perturbation if we're at or past the perturbation time + if t >= perturbation["time"]: + perturb_idx = metabolite_ids.index(perturbation["metabolite"]) + if not perturbation.get("applied", False): + concentrations[perturb_idx] *= perturbation["factor"] + perturbation["applied"] = True + + # Calculate reaction rates + rates = {} + for reaction in reactions: + rates[reaction.id] = mass_action_kinetics(reaction, concentrations, metabolite_ids) + + # Update concentration changes based on stoichiometry and rates + for i, metabolite_id in enumerate(metabolite_ids): + for reaction in reactions: + if metabolite_id in [m.id for m in reaction.metabolites]: + coeff = reaction.metabolites[model.metabolites.get_by_id(metabolite_id)] + dC_dt[i] += coeff * rates[reaction.id] + + return dC_dt + + log += "Kinetic model defined using mass-action kinetics for all reactions.\n\n" + + # Step 5: Simulate the system + perturbation = perturbation_params.copy() + perturbation["applied"] = False + + t_span = (0, simulation_time) + t_eval = np.linspace(0, simulation_time, time_points) + + log += f"Starting simulation for {simulation_time} time units with perturbation of {perturbation['metabolite']} " + log += f"by factor {perturbation['factor']} at time {perturbation['time']}.\n\n" + + try: + solution = solve_ivp( + lambda t, y: ode_system(t, y, metabolite_ids, model.reactions, perturbation), + t_span, + conc_array, + t_eval=t_eval, + method="LSODA", + ) + + log += f"Simulation completed successfully with {len(solution.t)} time points.\n\n" + except Exception as e: + return f"Error during simulation: {str(e)}" + + # Step 6: Calculate fluxes at each time point + fluxes = np.zeros((len(solution.t), len(model.reactions))) + reaction_ids = [r.id for r in model.reactions] + + for t_idx, t in enumerate(solution.t): + concentrations = solution.y[:, t_idx] + perturbation_temp = perturbation.copy() + if t >= perturbation["time"]: + perturbation_temp["applied"] = True + + for r_idx, reaction in enumerate(model.reactions): + fluxes[t_idx, r_idx] = mass_action_kinetics(reaction, concentrations, metabolite_ids) + + log += "Calculated reaction fluxes for all time points.\n\n" + + # Step 7: Save results to files + conc_df = pd.DataFrame(solution.y.T, columns=metabolite_ids) + conc_df["time"] = solution.t + conc_file = "metabolite_concentrations.csv" + conc_df.to_csv(conc_file, index=False) + + flux_df = pd.DataFrame(fluxes, columns=reaction_ids) + flux_df["time"] = solution.t + flux_file = "reaction_fluxes.csv" + flux_df.to_csv(flux_file, index=False) + + log += f"Results saved to {conc_file} and {flux_file}.\n\n" + + # Step 8: Analyze perturbation response + perturb_time_idx = np.where(solution.t >= perturbation["time"])[0][0] + pre_perturb = solution.y[:, perturb_time_idx - 1] + post_perturb = solution.y[:, perturb_time_idx] + + # Find metabolites with significant changes + significant_changes = [] + for i, m_id in enumerate(metabolite_ids): + rel_change = abs(post_perturb[i] - pre_perturb[i]) / pre_perturb[i] if pre_perturb[i] > 0 else 0 + if rel_change > 0.05: # 5% change threshold + significant_changes.append((m_id, rel_change)) + + log += "Perturbation Analysis Results:\n" + log += f"Perturbation of {perturbation['metabolite']} at time {perturbation['time']} by factor {perturbation['factor']}.\n" + log += f"Number of metabolites with significant immediate response: {len(significant_changes)}.\n" + + if significant_changes: + log += "Top 5 most affected metabolites (by relative concentration change):\n" + for m_id, change in sorted(significant_changes, key=lambda x: x[1], reverse=True)[:5]: + log += f" - {m_id}: {change * 100:.2f}% change\n" + + log += "\nSimulation and perturbation analysis completed successfully." + + return log + + +def simulate_protein_signaling_network( + network_structure, reaction_params, species_params, simulation_time=100, time_points=1000 +): + """ + Simulate protein signaling network dynamics using ODE-based logic modeling with normalized Hill functions. + + Parameters: + ----------- + network_structure : dict + Dictionary defining the network topology. Each key is a target protein and its value is a list of tuples + (regulator, regulation_type) where regulation_type is 1 for activation and -1 for inhibition. + + reaction_params : dict + Dictionary of reaction parameters. Keys are tuples (regulator, target) and values are dictionaries + with keys 'W' (weight), 'n' (Hill coefficient), and 'EC50' (half-maximal effective concentration). + + species_params : dict + Dictionary of species parameters. Keys are protein names and values are dictionaries + with keys 'tau' (time constant), 'y0' (initial concentration), and 'ymax' (maximum concentration). + + simulation_time : float, optional + Total simulation time in arbitrary time units. Default is 100. + + time_points : int, optional + Number of time points for the simulation. Default is 1000. + + Returns: + -------- + str + Research log summarizing the simulation process and results. + """ + import csv + + import numpy as np + from scipy.integrate import solve_ivp + + # Extract all unique protein species + all_proteins = set(network_structure.keys()) + for _target, regulators in network_structure.items(): + for regulator, _ in regulators: + all_proteins.add(regulator) + + # Create ordered list of proteins for indexing + proteins = sorted(all_proteins) + protein_indices = {protein: i for i, protein in enumerate(proteins)} + + # Define the normalized Hill function + def hill_function(x, n, ec50): + return x**n / (x**n + ec50**n) + + # Define the ODE system + def ode_system(t, y): + dydt = np.zeros_like(y) + + for target, regulators in network_structure.items(): + target_idx = protein_indices[target] + target_params = species_params[target] + + # Calculate regulation term for each regulator + regulation_terms = [] + for regulator, reg_type in regulators: + regulator_idx = protein_indices[regulator] + params = reaction_params.get((regulator, target), {}) + + if not params: + continue + + x = y[regulator_idx] + n = params["n"] + ec50 = params["EC50"] + weight = params["W"] + + # Apply Hill function based on regulation type + if reg_type == 1: # Activation + term = weight * hill_function(x, n, ec50) + else: # Inhibition + term = weight * (1 - hill_function(x, n, ec50)) + + regulation_terms.append(term) + + # Combine regulation terms (if any) + if regulation_terms: + # Simple summation of regulation terms + regulation = sum(regulation_terms) / len(regulation_terms) + + # Ensure regulation stays within [0, 1] + regulation = max(0, min(1, regulation)) + + # Calculate rate of change + tau = target_params["tau"] + ymax = target_params["ymax"] + dydt[target_idx] = (1 / tau) * (regulation * ymax - y[target_idx]) + + return dydt + + # Set up initial conditions + y0 = np.zeros(len(proteins)) + for protein, params in species_params.items(): + if protein in protein_indices: + y0[protein_indices[protein]] = params["y0"] + + # Set up time points + t_span = (0, simulation_time) + t_eval = np.linspace(0, simulation_time, time_points) + + # Solve the ODE system + solution = solve_ivp(ode_system, t_span, y0, method="LSODA", t_eval=t_eval, rtol=1e-6, atol=1e-9) + + # Save results to CSV + output_file = "protein_signaling_simulation_results.csv" + with open(output_file, "w", newline="") as f: + writer = csv.writer(f) + # Write header + header = ["Time"] + proteins + writer.writerow(header) + + # Write data + for i in range(len(solution.t)): + row = [solution.t[i]] + list(solution.y[:, i]) + writer.writerow(row) + + # Generate research log + log = "ODE-based Logic Modeling of Protein Signaling Networks\n" + log += "=============================================\n\n" + log += f"Network structure: {len(network_structure)} target proteins, {len(proteins)} total proteins\n" + log += f"Simulation time: {simulation_time} time units\n" + log += f"Number of time points: {time_points}\n\n" + + log += "Simulation steps:\n" + log += "1. Initialized protein concentrations based on provided initial values\n" + log += "2. Set up ODE system using normalized Hill functions for protein interactions\n" + log += "3. Solved the ODE system using LSODA integration method\n" + log += "4. Saved time-series data for all protein concentrations\n\n" + + log += f"Results saved to: {output_file}\n\n" + + # Add summary statistics + log += "Summary of final protein concentrations:\n" + for i, protein in enumerate(proteins): + final_conc = solution.y[i, -1] + log += f"- {protein}: {final_conc:.4f}\n" + + return log + + +def compare_protein_structures(pdb_file1, pdb_file2, chain_id1="A", chain_id2="A", output_prefix="protein_comparison"): + """ + Compares two protein structures to identify structural differences and conformational changes. + + Parameters: + ----------- + pdb_file1 : str + Path to the first PDB file + pdb_file2 : str + Path to the second PDB file + chain_id1 : str, optional + Chain ID to analyze in the first structure (default: 'A') + chain_id2 : str, optional + Chain ID to analyze in the second structure (default: 'A') + output_prefix : str, optional + Prefix for output files (default: "protein_comparison") + + Returns: + -------- + str + A research log summarizing the structural comparison analysis + """ + import warnings + + import numpy as np + from Bio.PDB import PDBIO, PDBParser, Select, Superimposer + from Bio.PDB.PDBExceptions import PDBConstructionWarning + + # Suppress PDB parsing warnings + warnings.filterwarnings("ignore", category=PDBConstructionWarning) + + research_log = [] + research_log.append("# Protein Structure Comparison Analysis\n") + research_log.append(f"Comparing structures from {pdb_file1} and {pdb_file2}\n") + + # Initialize parser + parser = PDBParser() + + # Parse structures + research_log.append("## Loading PDB structures") + structure1 = parser.get_structure("structure1", pdb_file1) + structure2 = parser.get_structure("structure2", pdb_file2) + + # Get specified chains + try: + chain1 = structure1[0][chain_id1] + chain2 = structure2[0][chain_id2] + research_log.append(f"Successfully loaded chain {chain_id1} from {pdb_file1}") + research_log.append(f"Successfully loaded chain {chain_id2} from {pdb_file2}") + except KeyError as e: + return f"Error: Chain not found: {str(e)}" + + # Get CA atoms for alignment + ca_atoms1 = [] + ca_atoms2 = [] + + # Create residue mappings based on residue ID + residues1 = {residue.id[1]: residue for residue in chain1 if residue.has_id("CA")} + residues2 = {residue.id[1]: residue for residue in chain2 if residue.has_id("CA")} + + # Find common residues + common_residues = sorted(set(residues1.keys()) & set(residues2.keys())) + + if len(common_residues) == 0: + return "Error: No common residues with CA atoms found between the structures" + + research_log.append(f"Found {len(common_residues)} common residues for alignment\n") + + # Get paired atoms for alignment + for res_id in common_residues: + ca_atoms1.append(residues1[res_id]["CA"]) + ca_atoms2.append(residues2[res_id]["CA"]) + + # Perform structural alignment + research_log.append("## Structural Alignment") + super_imposer = Superimposer() + super_imposer.set_atoms(ca_atoms1, ca_atoms2) + super_imposer.apply(structure2.get_atoms()) + + # Calculate RMSD + rmsd = super_imposer.rms + research_log.append(f"Overall RMSD: {rmsd:.4f} Å\n") + + # Calculate per-residue distance after alignment + research_log.append("## Conformational Differences Analysis") + + # Find regions with significant differences + distance_data = [] + significant_changes = [] + threshold = 2.0 # Angstroms threshold for significant difference + + for res_id in common_residues: + res1 = residues1[res_id] + res2 = residues2[res_id] + + # Calculate distance between CA atoms + ca1 = res1["CA"] + ca2 = res2["CA"] + distance = np.linalg.norm(ca1.coord - ca2.coord) + + distance_data.append((res_id, distance)) + + if distance > threshold: + significant_changes.append((res_id, res1.get_resname(), distance)) + + # Save alignment as PDB files + aligned_file1 = f"{output_prefix}_ref.pdb" + aligned_file2 = f"{output_prefix}_aligned.pdb" + + io = PDBIO() + io.set_structure(structure1) + io.save(aligned_file1, select=Select()) + + io.set_structure(structure2) + io.save(aligned_file2, select=Select()) + + research_log.append(f"Aligned structures saved as {aligned_file1} and {aligned_file2}") + + # Report on significant differences + if significant_changes: + research_log.append( + f"\nIdentified {len(significant_changes)} residues with significant conformational changes:" + ) + for res_id, res_name, distance in significant_changes: + research_log.append(f" - Residue {res_name}{res_id}: {distance:.2f} Å displacement") + else: + research_log.append("\nNo significant conformational changes detected (threshold: 2.0 Å)") + + # Save per-residue distance data + distance_file = f"{output_prefix}_residue_distances.csv" + with open(distance_file, "w") as f: + f.write("Residue_ID,Distance(Å)\n") + for res_id, distance in distance_data: + f.write(f"{res_id},{distance:.4f}\n") + + research_log.append(f"\nPer-residue distance data saved to {distance_file}") + + # Identify regions with continuous changes + regions = [] + current_region = [] + + for res_id, _, distance in significant_changes: + if not current_region or res_id == current_region[-1][0] + 1: + current_region.append((res_id, distance)) + else: + if len(current_region) >= 3: # Consider regions with at least 3 consecutive residues + regions.append(current_region) + current_region = [(res_id, distance)] + + if current_region and len(current_region) >= 3: + regions.append(current_region) + + if regions: + research_log.append("\n## Continuous Regions with Conformational Changes") + for i, region in enumerate(regions, 1): + start_res = region[0][0] + end_res = region[-1][0] + avg_dist = sum(r[1] for r in region) / len(region) + research_log.append(f"Region {i}: Residues {start_res}-{end_res} (Average displacement: {avg_dist:.2f} Å)") + + research_log.append("\n## Summary") + research_log.append(f"- Compared structures from {pdb_file1} and {pdb_file2}") + research_log.append(f"- Overall RMSD: {rmsd:.4f} Å") + research_log.append(f"- {len(significant_changes)} residues with significant conformational changes") + research_log.append(f"- {len(regions)} continuous regions of conformational change") + research_log.append(f"- Files generated: {aligned_file1}, {aligned_file2}, {distance_file}") + + return "\n".join(research_log) + + +def simulate_renin_angiotensin_system_dynamics( + initial_concentrations, rate_constants, feedback_params, simulation_time=48, time_points=100 +): + """ + Simulate the time-dependent concentrations of renin-angiotensin system (RAS) components. + + Parameters: + ----------- + initial_concentrations : dict + Initial concentrations of RAS components with keys: + 'renin', 'angiotensinogen', 'angiotensin_I', 'angiotensin_II', + 'ACE2_angiotensin_II', 'angiotensin_1_7' + + rate_constants : dict + Kinetic rate constants with keys: + 'k_ren' (renin production), 'k_agt' (angiotensinogen production), + 'k_ace' (ACE conversion rate), 'k_ace2' (ACE2 conversion rate), + 'k_at1r' (AT1R binding rate), 'k_mas' (Mas receptor binding rate) + + feedback_params : dict + Parameters controlling feedback mechanisms with keys: + 'fb_ang_II' (angiotensin II feedback), 'fb_ace2' (ACE2 feedback) + + simulation_time : float, optional + Total simulation time in hours (default: 48) + + time_points : int, optional + Number of time points to evaluate (default: 100) + + Returns: + -------- + str + Research log summarizing the simulation steps and results + """ + + import numpy as np + import pandas as pd + from scipy.integrate import solve_ivp + + # Extract initial concentrations + y0 = [ + initial_concentrations["renin"], + initial_concentrations["angiotensinogen"], + initial_concentrations["angiotensin_I"], + initial_concentrations["angiotensin_II"], + initial_concentrations["ACE2_angiotensin_II"], + initial_concentrations["angiotensin_1_7"], + ] + + # Define the system of ODEs + def ras_ode_system(t, y): + renin, agt, ang_I, ang_II, ace2_ang_II, ang_1_7 = y + + # Production rates with feedback + renin_production = rate_constants["k_ren"] * (1 / (1 + feedback_params["fb_ang_II"] * ang_II)) + agt_production = rate_constants["k_agt"] + + # Conversion rates + ang_I_formation = renin * agt + ang_II_formation = rate_constants["k_ace"] * ang_I + ace2_binding = rate_constants["k_ace2"] * ang_II + ang_1_7_formation = ace2_ang_II + + # Clearance/degradation (simplified as proportional to concentration) + renin_clearance = 0.1 * renin + agt_clearance = 0.05 * agt + ang_I_clearance = 0.2 * ang_I + ang_II_clearance = 0.3 * ang_II + rate_constants["k_at1r"] * ang_II + ace2_ang_II_clearance = 0.15 * ace2_ang_II + ang_1_7_clearance = 0.25 * ang_1_7 + rate_constants["k_mas"] * ang_1_7 + + # ODEs + drenin_dt = renin_production - renin_clearance + dagt_dt = agt_production - agt_clearance - ang_I_formation + dang_I_dt = ang_I_formation - ang_I_clearance - ang_II_formation + dang_II_dt = ang_II_formation - ang_II_clearance - ace2_binding + dace2_ang_II_dt = ace2_binding - ace2_ang_II_clearance - ang_1_7_formation + dang_1_7_dt = ang_1_7_formation - ang_1_7_clearance + + return [drenin_dt, dagt_dt, dang_I_dt, dang_II_dt, dace2_ang_II_dt, dang_1_7_dt] + + # Time points for simulation + t_span = (0, simulation_time) + t_eval = np.linspace(0, simulation_time, time_points) + + # Solve the ODE system + solution = solve_ivp(ras_ode_system, t_span, y0, method="RK45", t_eval=t_eval, rtol=1e-6) + + # Create DataFrame with results + component_names = [ + "Renin", + "Angiotensinogen", + "Angiotensin I", + "Angiotensin II", + "ACE2-Angiotensin II", + "Angiotensin 1-7", + ] + results_df = pd.DataFrame(solution.y.T, columns=component_names) + results_df.insert(0, "Time (hours)", solution.t) + + # Save results to CSV + results_file = "ras_simulation_results.csv" + results_df.to_csv(results_file, index=False) + + # Create research log + log = f"""RAS ODE Modeling Simulation Log: + +1. Initialized RAS component concentrations: + - Renin: {initial_concentrations["renin"]} + - Angiotensinogen: {initial_concentrations["angiotensinogen"]} + - Angiotensin I: {initial_concentrations["angiotensin_I"]} + - Angiotensin II: {initial_concentrations["angiotensin_II"]} + - ACE2-Angiotensin II complex: {initial_concentrations["ACE2_angiotensin_II"]} + - Angiotensin 1-7: {initial_concentrations["angiotensin_1_7"]} + +2. Applied rate constants: + - Renin production (k_ren): {rate_constants["k_ren"]} + - Angiotensinogen production (k_agt): {rate_constants["k_agt"]} + - ACE conversion rate (k_ace): {rate_constants["k_ace"]} + - ACE2 conversion rate (k_ace2): {rate_constants["k_ace2"]} + - AT1R binding rate (k_at1r): {rate_constants["k_at1r"]} + - Mas receptor binding rate (k_mas): {rate_constants["k_mas"]} + +3. Feedback parameters: + - Angiotensin II feedback (fb_ang_II): {feedback_params["fb_ang_II"]} + - ACE2 feedback (fb_ace2): {feedback_params["fb_ace2"]} + +4. Simulation parameters: + - Total simulation time: {simulation_time} hours + - Number of time points: {time_points} + +5. Solved system of ODEs using SciPy's solve_ivp with RK45 method. + +6. Final concentrations at {simulation_time} hours: + - Renin: {results_df["Renin"].iloc[-1]:.4f} + - Angiotensinogen: {results_df["Angiotensinogen"].iloc[-1]:.4f} + - Angiotensin I: {results_df["Angiotensin I"].iloc[-1]:.4f} + - Angiotensin II: {results_df["Angiotensin II"].iloc[-1]:.4f} + - ACE2-Angiotensin II complex: {results_df["ACE2-Angiotensin II"].iloc[-1]:.4f} + - Angiotensin 1-7: {results_df["Angiotensin 1-7"].iloc[-1]:.4f} + +7. Results saved to file: {results_file} + +8. Key observations: + - The model captures the conversion of Angiotensinogen to Angiotensin I via Renin + - Angiotensin I is converted to Angiotensin II via ACE + - Angiotensin II binds with ACE2 to form the ACE2-Angiotensin II complex + - The complex is converted to Angiotensin 1-7 + - Feedback mechanisms regulate the production rates +""" + + return log diff --git a/BioScientist/agent_system/engines/v1_executor_backup/tool/tool_registry.py b/BioScientist/agent_system/engines/v1_executor_backup/tool/tool_registry.py new file mode 100644 index 0000000000000000000000000000000000000000..246fc023e30808d92b0246135e3ffd247ed8d1b5 --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/tool/tool_registry.py @@ -0,0 +1,89 @@ +import pickle + +import pandas as pd + + +class ToolRegistry: + def __init__(self, tools): + self.tools = [] + self.next_id = 0 + + for j in tools.values(): + for tool in j: + self.register_tool(tool) + + docs = [] + for tool_id in range(len(self.tools)): + docs.append([int(tool_id), self.get_tool_by_id(int(tool_id))]) + self.document_df = pd.DataFrame(docs, columns=["docid", "document_content"]) + + # self.langchain_tools = {} + # for module, api_list in tools.items(): + # self.langchain_tools.update({self.get_id_by_name(api['name']): api_schema_to_langchain_tool(api, mode = 'custom_tool', module_name = module) for api in api_list}) + + def register_tool(self, tool): + if self.validate_tool(tool): + tool["id"] = self.next_id + self.tools.append(tool) + self.next_id += 1 + else: + raise ValueError("Invalid tool format") + + def validate_tool(self, tool): + required_keys = ["name", "description", "required_parameters"] + return all(key in tool for key in required_keys) + + def get_tool_by_name(self, name): + for tool in self.tools: + if tool["name"] == name: + return tool + return None + + def get_tool_by_id(self, tool_id): + for tool in self.tools: + if tool["id"] == tool_id: + return tool + return None + + def get_id_by_name(self, name): + for tool in self.tools: + if tool["name"] == name: + return tool["id"] + return None + + def get_name_by_id(self, tool_id): + for tool in self.tools: + if tool["id"] == tool_id: + return tool["name"] + return None + + def list_tools(self): + return [{"name": tool["name"], "id": tool["id"]} for tool in self.tools] + + def remove_tool_by_id(self, tool_id): + # Remove the tool with the given id + tool = self.get_tool_by_id(tool_id) + if tool: + self.tools = [t for t in self.tools if t["id"] != tool_id] + return True + return False + + def remove_tool_by_name(self, name): + # Remove the tool with the given name + tool = self.get_tool_by_name(name) + if tool: + self.tools = [t for t in self.tools if t["name"] != name] + return True + return False + + def save_registry(self, filename): + with open(filename, "wb") as file: + pickle.dump(self, file) + + # def get_langchain_tool_by_id(self, id): + # return self.langchain_tools[id] + + @staticmethod + def load_registry(filename): + with open(filename, "rb") as file: + return pickle.load(file) diff --git a/BioScientist/agent_system/engines/v1_executor_backup/utils.py b/BioScientist/agent_system/engines/v1_executor_backup/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..09e1ef91ac0b695c8ed2c9e1bf7c3d248a75596d --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/utils.py @@ -0,0 +1,2366 @@ +import ast +import enum +import importlib +import json +import os +import pickle +import subprocess +import tempfile +import traceback +import zipfile +from typing import Any, ClassVar +from urllib.parse import urljoin + +import pandas as pd +import requests +import tqdm # Add tqdm for progress bar +from langchain_core.callbacks import BaseCallbackHandler +from langchain_core.messages.base import get_msg_title_repr +from langchain_core.tools import StructuredTool +from langchain_core.utils.interactive_env import is_interactive_env +from pydantic import BaseModel, Field, ValidationError + + +# Add these new functions for running R code and CLI commands +def run_r_code(code: str) -> str: + """Run R code using subprocess. + + Args: + code: R code to run + + Returns: + Output of the R code + + """ + try: + # Create a temporary file to store the R code + with tempfile.NamedTemporaryFile(suffix=".R", mode="w", delete=False) as f: + f.write(code) + temp_file = f.name + + # Run the R code using Rscript + result = subprocess.run(["Rscript", temp_file], capture_output=True, text=True, check=False) + + # Clean up the temporary file + os.unlink(temp_file) + + # Return the output + if result.returncode != 0: + return f"Error running R code:\n{result.stderr}" + else: + return result.stdout + except Exception as e: + return f"Error running R code: {str(e)}" + + +def run_bash_script(script: str) -> str: + """Run a Bash script using subprocess. + + Args: + script: Bash script to run + + Returns: + Output of the Bash script + + Example: + This is how to use the function + + .. code-block:: python + + # Example of a complex Bash script + script = ''' + #!/bin/bash + + # Define variables + DATA_DIR="/path/to/data" + OUTPUT_FILE="results.txt" + + # Create output directory if it doesn't exist + mkdir -p $(dirname $OUTPUT_FILE) + + # Loop through files + for file in $DATA_DIR/*.txt; do + echo "Processing $file..." + # Count lines in each file + line_count=$(wc -l < $file) + echo "$file: $line_count lines" >> $OUTPUT_FILE + done + + echo "Processing complete. Results saved to $OUTPUT_FILE" + ''' + result = run_bash_script(script) + print(result) + + """ + try: + # Trim any leading/trailing whitespace + script = script.strip() + + # If the script is empty, return an error + if not script: + return "Error: Empty script" + + # Create a temporary file to store the Bash script + with tempfile.NamedTemporaryFile(suffix=".sh", mode="w", delete=False) as f: + # Add shebang if not present + if not script.startswith("#!/"): + f.write("#!/bin/bash\n") + # Add set -e to exit on error + if "set -e" not in script: + f.write("set -e\n") + f.write(script) + temp_file = f.name + + # Make the script executable + os.chmod(temp_file, 0o755) + + # Get current environment variables and working directory + env = os.environ.copy() + cwd = os.getcwd() + + # Run the Bash script with the current environment and working directory + result = subprocess.run( + [temp_file], + shell=True, + capture_output=True, + text=True, + check=False, + env=env, + cwd=cwd, + ) + + # Clean up the temporary file + os.unlink(temp_file) + + # Return the output + if result.returncode != 0: + traceback.print_stack() + print(result) + return f"Error running Bash script (exit code {result.returncode}):\n{result.stderr}" + else: + return result.stdout + except Exception as e: + traceback.print_exc() + return f"Error running Bash script: {str(e)}" + + +# Keep the run_cli_command for backward compatibility +def run_cli_command(command: str) -> str: + """Run a CLI command using subprocess. + + Args: + command: CLI command to run + + Returns: + Output of the CLI command + + """ + try: + # Trim any leading/trailing whitespace + command = command.strip() + + # If the command is empty, return an error + if not command: + return "Error: Empty command" + + # Split the command into a list of arguments, handling quoted arguments correctly + import shlex + + args = shlex.split(command) + + # Run the command + result = subprocess.run(args, capture_output=True, text=True, check=False) + + # Return the output + if result.returncode != 0: + return f"Error running command '{command}':\n{result.stderr}" + else: + return result.stdout + except Exception as e: + return f"Error running command '{command}': {str(e)}" + + +def run_with_timeout(func, args=None, kwargs=None, timeout=600): + """Run a function with a timeout using threading instead of multiprocessing. + This allows variables to persist in the global namespace between function calls. + Returns the function result or a timeout error message. + """ + if args is None: + args = [] + if kwargs is None: + kwargs = {} + + import ctypes + import queue + import threading + + result_queue = queue.Queue() + + def thread_func(func, args, kwargs, result_queue): + """Function to run in a separate thread.""" + try: + result = func(*args, **kwargs) + result_queue.put(("success", result)) + except Exception as e: + result_queue.put(("error", str(e))) + + # Start a separate thread + thread = threading.Thread(target=thread_func, args=(func, args, kwargs, result_queue)) + thread.daemon = True # Set as daemon so it will be killed when main thread exits + thread.start() + + # Wait for the specified timeout + thread.join(timeout) + + # Check if the thread is still running after timeout + if thread.is_alive(): + print(f"TIMEOUT: Code execution timed out after {timeout} seconds") + + # Unfortunately, there's no clean way to force terminate a thread in Python + # The recommended approach is to use daemon threads and let them be killed when main thread exits + # Here, we'll try to raise an exception in the thread to make it stop + try: + # Get thread ID and try to terminate it + thread_id = thread.ident + if thread_id: + # This is a bit dangerous and not 100% reliable + # It attempts to raise a SystemExit exception in the thread + res = ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(thread_id), ctypes.py_object(SystemExit)) + if res > 1: + # Oops, we raised too many exceptions + ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(thread_id), None) + except Exception as e: + print(f"Error trying to terminate thread: {e}") + + return f"ERROR: Code execution timed out after {timeout} seconds. Please try with simpler inputs or break your task into smaller steps." + + # Get the result from the queue if available + try: + status, result = result_queue.get(block=False) + return result if status == "success" else f"Error in execution: {result}" + except queue.Empty: + return "Error: Execution completed but no result was returned" + + +class api_schema(BaseModel): + """api schema specification.""" + + api_schema: str | None = Field(description="The api schema as a dictionary") + + +def function_to_api_schema(function_string, llm): + prompt = """ + Based on a code snippet and help me write an API docstring in the format like this: + + {{'name': 'get_gene_set_enrichment', + 'description': 'Given a list of genes, identify a pathway that is enriched for this gene set. Return a list of pathway name, p-value, z-scores.', + 'required_parameters': [{{'name': 'genes', + 'type': 'List[str]', + 'description': 'List of g`ene symbols to analyze', + 'default': None}}], + 'optional_parameters': [{{'name': 'top_k', + 'type': 'int', + 'description': 'Top K pathways to return', + 'default': 10}}, {{'name': 'database', + 'type': 'str', + 'description': 'Name of the database to use for enrichment analysis', + 'default': "gene_ontology"}}]}} + + Strictly follow the input from the function - don't create fake optional parameters. + For variable without default values, set them as None, not null. + For variable with boolean values, use capitalized True or False, not true or false. + Do not add any return type in the docstring. + Be as clear and succint as possible for the descriptions. Please do not make it overly verbose. + Here is the code snippet: + {code} + """ + llm = llm.with_structured_output(api_schema) + + for _ in range(7): + try: + api = llm.invoke(prompt.format(code=function_string)).dict()["api_schema"] + return ast.literal_eval(api) # -> prefer "default": None + # return json.loads(api) # -> prefer "default": null + except Exception as e: + print("API string:", api) + print("Error parsing the API string:", e) + continue + + return "Error: Could not parse the API schema" + # return + + +def get_all_functions_from_file(file_path): + with open(file_path) as file: + file_content = file.read() + + # Parse the file content into an AST (Abstract Syntax Tree) + tree = ast.parse(file_content) + + # List to hold the top-level functions as strings + functions = [] + + # Walk through the AST nodes + for node in tree.body: # Only consider top-level nodes in the body + if isinstance(node, ast.FunctionDef): # Check if the node is a function definition + # Skip if function name starts with underscore + if node.name.startswith("_"): + continue + + start_line = node.lineno - 1 # Get the starting line of the function + end_line = node.end_lineno # Get the ending line of the function (only available in Python 3.8+) + func_code = file_content.splitlines()[start_line:end_line] + functions.append("\n".join(func_code)) # Join lines of the function and add to the list + + return functions + + +def write_python_code(request: str): + from langchain_anthropic import ChatAnthropic + from langchain_core.output_parsers import StrOutputParser + from langchain_core.prompts import ChatPromptTemplate + + model = ChatAnthropic(model="claude-3-5-sonnet-20240620") + template = """Write some python code to solve the user's problem. + + Return only python code in Markdown format, e.g.: + + ```python + .... + ```""" + prompt = ChatPromptTemplate.from_messages([("system", template), ("human", "{input}")]) + + def _sanitize_output(text: str): + _, after = text.split("```python") + return after.split("```")[0] + + chain = prompt | model | StrOutputParser() | _sanitize_output + return chain.invoke({"input": "write a code that " + request}) + + +def execute_graphql_query( + query: str, + variables: dict, + api_address: str = "https://api.genetics.opentargets.org/graphql", +) -> dict: + """Executes a GraphQL query with variables and returns the data as a dictionary.""" + headers = {"Content-Type": "application/json"} + response = requests.post(api_address, json={"query": query, "variables": variables}, headers=headers) + if response.status_code == 200: + return response.json() + else: + print(response.text) + response.raise_for_status() + + +def get_tool_decorated_functions(relative_path): + import ast + import importlib.util + import os + + # Get the directory of the current file (__init__.py) + current_dir = os.path.dirname(os.path.abspath(__file__)) + + # Construct the absolute path from the relative path + file_path = os.path.join(current_dir, relative_path) + + with open(file_path) as file: + tree = ast.parse(file.read(), filename=file_path) + + tool_function_names = [] + + for node in ast.walk(tree): + if isinstance(node, ast.FunctionDef): + for decorator in node.decorator_list: + if ( + isinstance(decorator, ast.Name) + and decorator.id == "tool" + or ( + isinstance(decorator, ast.Call) + and isinstance(decorator.func, ast.Name) + and decorator.func.id == "tool" + ) + ): + tool_function_names.append(node.name) + + # Calculate the module name from the relative path + package_path = os.path.relpath(file_path, start=current_dir) + module_name = package_path.replace(os.path.sep, ".").rsplit(".", 1)[0] + + # Import the module and get the function objects + spec = importlib.util.spec_from_file_location(module_name, file_path) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + tool_functions = [getattr(module, name) for name in tool_function_names] + + return tool_functions + + +def process_bio_retrieval_ducoment(documents_df): + ir_corpus = {} + corpus2tool = {} + for row in documents_df.itertuples(): + doc = row.document_content + ir_corpus[row.docid] = ( + (doc.get("name", "") or "") + + ", " + + (doc.get("description", "") or "") + + ", " + + (doc.get("url", "") or "") + + ", " + + ", required_params: " + + json.dumps(doc.get("required_parameters", "")) + + ", optional_params: " + + json.dumps(doc.get("optional_parameters", "")) + ) + + corpus2tool[ + (doc.get("name", "") or "") + + ", " + + (doc.get("description", "") or "") + + ", " + + (doc.get("url", "") or "") + + ", " + + ", required_params: " + + json.dumps(doc.get("required_parameters", "")) + + ", optional_params: " + + json.dumps(doc.get("optional_parameters", "")) + ] = doc["name"] + return ir_corpus, corpus2tool + + +def load_pickle(file): + import pickle + + with open(file, "rb") as f: + return pickle.load(f) + + +def pretty_print(message, printout=True): + if isinstance(message, tuple): + title = message + elif isinstance(message.content, list): + title = get_msg_title_repr(message.type.title().upper() + " Message", bold=is_interactive_env()) + if message.name is not None: + title += f"\nName: {message.name}" + + for i in message.content: + if i["type"] == "text": + title += f"\n{i['text']}\n" + elif i["type"] == "tool_use": + title += f"\nTool: {i['name']}" + title += f"\nInput: {i['input']}" + if printout: + print(f"{title}") + else: + title = get_msg_title_repr(message.type.title() + " Message", bold=is_interactive_env()) + if message.name is not None: + title += f"\nName: {message.name}" + title += f"\n\n{message.content}" + if printout: + print(f"{title}") + return title + + +class CustomBaseModel(BaseModel): + api_schema: ClassVar[dict] = None # Class variable to store api_schema + + # Add model_config with arbitrary_types_allowed=True + model_config = {"arbitrary_types_allowed": True} + + @classmethod + def set_api_schema(cls, schema: dict): + cls.api_schema = schema + + @classmethod + def model_validate(cls, obj): + try: + return super().model_validate(obj) + except (ValidationError, AttributeError) as e: + if not cls.api_schema: + raise e # If no api_schema is set, raise original error + + error_msg = "Required Parameters:\n" + for param in cls.api_schema["required_parameters"]: + error_msg += f"- {param['name']} ({param['type']}): {param['description']}\n" + + error_msg += "\nErrors:\n" + for err in e.errors(): + field = err["loc"][0] if err["loc"] else "input" + error_msg += f"- {field}: {err['msg']}\n" + + if not obj: + error_msg += "\nNo input provided" + else: + error_msg += "\nProvided Input:\n" + for key, value in obj.items(): + error_msg += f"- {key}: {value}\n" + + missing_params = {param["name"] for param in cls.api_schema["required_parameters"]} - set(obj.keys()) + if missing_params: + error_msg += "\nMissing Parameters:\n" + for param in missing_params: + error_msg += f"- {param}\n" + + # # Create proper validation error structure + raise ValidationError.from_exception_data( + title="Validation Error", + line_errors=[ + { + "type": "value_error", + "loc": ("input",), + "input": obj, + "ctx": { + "error": error_msg, + }, + } + ], + ) from None + + +def safe_execute_decorator(func): + def wrapper(*args, **kwargs): + try: + return func(*args, **kwargs) + except Exception as e: + return str(e) + + return wrapper + + +def api_schema_to_langchain_tool(api_schema, mode="generated_tool", module_name=None): + if mode == "generated_tool": + module = importlib.import_module("biomni.tool.generated_tool." + api_schema["tool_name"] + ".api") + elif mode == "custom_tool": + module = importlib.import_module(module_name) + + api_function = getattr(module, api_schema["name"]) + api_function = safe_execute_decorator(api_function) + + # Define a mapping from string type names to actual Python type objects + type_mapping = { + "string": str, + "integer": int, + "boolean": bool, + "pandas": pd.DataFrame, # Use the imported pandas.DataFrame directly + "str": str, + "int": int, + "bool": bool, + "List[str]": list[str], + "List[int]": list[int], + "Dict": dict, + "Any": Any, + } + + # Create the fields and annotations + annotations = {} + for param in api_schema["required_parameters"]: + param_type = param["type"] + if param_type in type_mapping: + annotations[param["name"]] = type_mapping[param_type] + else: + # For types not in the mapping, try a safer approach than direct eval + try: + annotations[param["name"]] = eval(param_type) + except (NameError, SyntaxError): + # Default to Any for unknown types + annotations[param["name"]] = Any + + fields = {param["name"]: Field(description=param["description"]) for param in api_schema["required_parameters"]} + + # Create the ApiInput class dynamically + ApiInput = type("Input", (CustomBaseModel,), {"__annotations__": annotations, **fields}) + # Set the api_schema + ApiInput.set_api_schema(api_schema) + + # Create the StructuredTool + api_tool = StructuredTool.from_function( + func=api_function, + name=api_schema["name"], + description=api_schema["description"], + args_schema=ApiInput, + return_direct=True, + ) + + return api_tool + + +class ID(enum.Enum): + ENTREZ = "Entrez" + ENSEMBL = "Ensembl without version" # e.g. ENSG00000123374 + ENSEMBL_W_VERSION = "Ensembl with version" # e.g. ENSG00000123374.10 (needed for GTEx) + + +def get_gene_id(gene_symbol: str, id_type: ID): + """Get the ID for a gene symbol. If no match found, returns None.""" + if id_type == ID.ENTREZ: + return _get_gene_id_entrez(gene_symbol) + elif id_type == ID.ENSEMBL: + return _get_gene_id_ensembl(gene_symbol) + elif id_type == ID.ENSEMBL_W_VERSION: + return _get_gene_id_ensembl_with_version(gene_symbol) + else: + raise ValueError(f"ID type {id_type} not supported") + + +def _get_gene_id_entrez(gene_symbol: str): + """Get the Entrez ID for a gene symbol. If no match found, returns None + e.g. 1017 (CDK2). + """ + api_call = f"https://mygene.info/v3/query?species=human&q=symbol:{gene_symbol}" + response = requests.get(api_call) + response_json = response.json() + + if len(response_json["hits"]) == 0: + return None + else: + return response_json["hits"][0]["entrezgene"] + + +def _get_gene_id_ensembl(gene_symbol): + """Get the Ensembl ID for a gene symbol. If no match found, returns None + e.g. ENSG00000123374. + """ + api_call = f"https://mygene.info/v3/query?species=human&fields=ensembl&q=symbol:{gene_symbol}" + response = requests.get(api_call) + response_json = response.json() + + if len(response_json["hits"]) == 0: + return None + else: + ensembl = response_json["hits"][0]["ensembl"] + if isinstance(ensembl, list): + return ensembl[0][ + "gene" + ] # Sometimes returns a list, for example RNH1 (first elem is on chr11, second is on scaffold_hschr11) + else: + return ensembl["gene"] + + +def _get_gene_id_ensembl_with_version(gene_symbol): + """Get the Ensembl ID for a gene symbol. If no match found, returns None + e.g. ENSG00000123374.10. + """ + api_base = "https://gtexportal.org/api/v2/reference/gene" + params = {"geneId": gene_symbol} + response_json = requests.get(api_base, params=params).json() + + if len(response_json["data"]) == 0: + return None + else: + return response_json["data"][0]["gencodeId"] + + +def save_pkl(f, filename): + with open(filename, "wb") as file: + pickle.dump(f, file) + + +def load_pkl(filename): + with open(filename, "rb") as file: + return pickle.load(file) + + +_TEXT_COLOR_MAPPING = { + "blue": "36;1", + "yellow": "33;1", + "pink": "38;5;200", + "green": "32;1", + "red": "31;1", +} + + +def color_print(text, color="blue"): + color_str = _TEXT_COLOR_MAPPING[color] + print(f"\u001b[{color_str}m\033[1;3m{text}\u001b[0m") + + +class PromptLogger(BaseCallbackHandler): + def on_chat_model_start(self, serialized, messages, **kwargs): + for message in messages[0]: + color_print(message.pretty_repr(), color="green") + + +class NodeLogger(BaseCallbackHandler): + def on_llm_end(self, response, **kwargs): # response of type LLMResult + for generations in response.generations: # response.generations of type List[List[Generations]] becuase "each input could have multiple candidate generations" + for generation in generations: + generated_text = generation.message.content + # token_usage = generation.message.response_metadata["token_usage"] + color_print(generated_text, color="yellow") + + def on_agent_action(self, action, **kwargs): + color_print(action.log, color="pink") + + def on_agent_finish(self, finish, **kwargs): + color_print(finish, color="red") + + def on_tool_start(self, serialized, input_str, **kwargs): + tool_name = serialized.get("name") + color_print(f"Calling {tool_name} with inputs: {input_str}", color="pink") + + def on_tool_end(self, output, **kwargs): + output = str(output) + color_print(output, color="blue") + + +def check_or_create_path(path=None): + # Set a default path if none is provided + if path is None: + path = os.path.join(os.getcwd(), "tmp_directory") + + # Check if the path exists + if not os.path.exists(path): + # If it doesn't exist, create the directory + os.makedirs(path) + print(f"Directory created at: {path}") + else: + print(f"Directory already exists at: {path}") + + return path + + +def langchain_to_gradio_message(message): + # Build the title and content based on the message type + if isinstance(message.content, list): + # For a message with multiple content items (like text and tool use) + gradio_messages = [] + for item in message.content: + gradio_message = { + "role": "user" if message.type == "human" else "assistant", + "content": "", + "metadata": {}, + } + + if item["type"] == "text": + item["text"] = item["text"].replace("", "\n") + item["text"] = item["text"].replace("", "\n") + gradio_message["content"] += f"{item['text']}\n" + gradio_messages.append(gradio_message) + elif item["type"] == "tool_use": + if item["name"] == "run_python_repl": + gradio_message["metadata"]["title"] = "🛠️ Writing code..." + # input = "```python {code_block}```\n".format(code_block=item['input']["command"]) + gradio_message["metadata"]["log"] = "Executing Code block..." + gradio_message["content"] = f"##### Code: \n ```python \n {item['input']['command']} \n``` \n" + else: + gradio_message["metadata"]["title"] = f"🛠️ Used tool ```{item['name']}```" + to_print = ";".join([i + ": " + str(j) for i, j in item["input"].items()]) + gradio_message["metadata"]["log"] = f"🔍 Input -- {to_print}\n" + gradio_message["metadata"]["status"] = "pending" + gradio_messages.append(gradio_message) + + else: + gradio_message = { + "role": "user" if message.type == "human" else "assistant", + "content": "", + "metadata": {}, + } + print(message) + content = message.content + content = content.replace("", "\n") + content = content.replace("", "\n") + content = content.replace("", "\n") + content = content.replace("", "\n") + + gradio_message["content"] = content + gradio_messages = [gradio_message] + return gradio_messages + + +def parse_hpo_obo(file_path): + """Parse the HPO OBO file and create a dictionary mapping HP IDs to phenotype descriptions. + + Args: + file_path (str): Path to the HPO OBO file. + + Returns: + dict: A dictionary where keys are HP IDs and values are phenotype descriptions. + + """ + hp_dict = {} + current_id = None + current_name = None + + with open(file_path) as file: + for line in file: + line = line.strip() + if line.startswith("[Term]"): + # If a new term block starts, save the previous term + if current_id and current_name: + hp_dict[current_id] = current_name + current_id = None + current_name = None + elif line.startswith("id: HP:"): + current_id = line.split(": ")[1] + elif line.startswith("name:"): + current_name = line.split(": ", 1)[1] + + # Add the last term to the dictionary + if current_id and current_name: + hp_dict[current_id] = current_name + + return hp_dict + + +def textify_api_dict(api_dict): + """Convert a nested API dictionary to a nicely formatted string.""" + lines = [] + for category, methods in api_dict.items(): + lines.append(f"Import file: {category}") + lines.append("=" * (len("Import file: ") + len(category))) + for method in methods: + lines.append(f"Method: {method.get('name', 'N/A')}") + lines.append(f" Description: {method.get('description', 'No description provided.')}") + + # Process required parameters + req_params = method.get("required_parameters", []) + if req_params: + lines.append(" Required Parameters:") + for param in req_params: + param_name = param.get("name", "N/A") + param_type = param.get("type", "N/A") + param_desc = param.get("description", "No description") + param_default = param.get("default", "None") + lines.append(f" - {param_name} ({param_type}): {param_desc} [Default: {param_default}]") + + # Process optional parameters + opt_params = method.get("optional_parameters", []) + if opt_params: + lines.append(" Optional Parameters:") + for param in opt_params: + param_name = param.get("name", "N/A") + param_type = param.get("type", "N/A") + param_desc = param.get("description", "No description") + param_default = param.get("default", "None") + lines.append(f" - {param_name} ({param_type}): {param_desc} [Default: {param_default}]") + + lines.append("") # Empty line between methods + lines.append("") # Extra empty line after each category + + return "\n".join(lines) + + +def read_module2api(): + fields = [ + "literature", + "biochemistry", + "bioimaging", + "bioengineering", + "biophysics", + "glycoengineering", + "cancer_biology", + "cell_biology", + "molecular_biology", + "genetics", + "genomics", + "immunology", + "microbiology", + "pathology", + "pharmacology", + "physiology", + "synthetic_biology", + "systems_biology", + "support_tools", + "database", + "lab_automation", + "protocols", + ] + + module2api = {} + for field in fields: + module_name = f"biomni.tool.tool_description.{field}" + module = importlib.import_module(module_name) + module2api[f"biomni.tool.{field}"] = module.description + return module2api + + +def download_and_unzip(url: str, dest_dir: str) -> str: + """Download a zip file from a URL and extract it to the destination directory. + + Args: + url: The URL to download the zip file from. + dest_dir: The directory to extract the contents to. + + Returns: + The path to the extracted directory, or an error message. + + """ + try: + os.makedirs(dest_dir, exist_ok=True) + print(f"Downloading from {url} ...") + with requests.get(url, stream=True) as r: + r.raise_for_status() + total_size = int(r.headers.get("content-length", 0)) + chunk_size = 8192 + with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as tmp_file: + with tqdm.tqdm( + total=total_size / (1024**3), + unit="GB", + unit_scale=True, + desc="Downloading", + ncols=80, + ) as pbar: + for chunk in r.iter_content(chunk_size=chunk_size): + if chunk: + tmp_file.write(chunk) + pbar.update(len(chunk) / (1024**3)) + tmp_zip_path = tmp_file.name + print(f"Downloaded to {tmp_zip_path}. Extracting...") + with zipfile.ZipFile(tmp_zip_path, "r") as zip_ref: + zip_ref.extractall(dest_dir) + os.unlink(tmp_zip_path) + print(f"Extraction complete to {dest_dir}") + return dest_dir + except Exception as e: + print(f"Error downloading or extracting zip: {e}") + return f"Error: {e}" + + +def check_and_download_s3_files( + s3_bucket_url: str, local_data_lake_path: str, expected_files: list[str], folder: str = "data_lake" +) -> dict[str, bool]: + """Check for missing files in the local data lake and download them from S3 bucket. + + Args: + s3_bucket_url: Base URL of the S3 bucket (e.g., "https://biomni-release.s3.amazonaws.com") + local_data_lake_path: Local path to the data lake directory + expected_files: List of expected file names in the data lake + folder: S3 folder name ("data_lake" or "benchmark") + + Returns: + Dictionary mapping file names to download success status + """ + + os.makedirs(local_data_lake_path, exist_ok=True) + download_results = {} + + def download_with_progress(url: str, file_path: str, desc: str) -> bool: + """Download file with progress bar.""" + try: + response = requests.get(url, stream=True) + response.raise_for_status() + + total_size = int(response.headers.get("content-length", 0)) + + with open(file_path, "wb") as f: + if total_size > 0: + with tqdm.tqdm(total=total_size, unit="B", unit_scale=True, desc=desc, ncols=80) as pbar: + for chunk in response.iter_content(chunk_size=8192): + if chunk: + f.write(chunk) + pbar.update(len(chunk)) + else: + for chunk in response.iter_content(chunk_size=8192): + if chunk: + f.write(chunk) + return True + except Exception as e: + print(f"✗ Failed to download {desc}: {e}") + if os.path.exists(file_path): + try: + os.remove(file_path) + except OSError: + pass + return False + + def cleanup_file(file_path: str): + """Clean up file if it exists.""" + if os.path.exists(file_path): + try: + os.remove(file_path) + except OSError: + pass + + # Handle benchmark folder (download as zip) + if folder == "benchmark": + print(f"Downloading entire {folder} folder structure...") + s3_zip_url = urljoin(s3_bucket_url + "/", folder + ".zip") + + with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as tmp_zip: + tmp_zip_path = tmp_zip.name + + if download_with_progress(s3_zip_url, tmp_zip_path, f"{folder}.zip"): + print(f"Extracting {folder}.zip...") + try: + with zipfile.ZipFile(tmp_zip_path, "r") as zip_ref: + zip_ref.extractall(local_data_lake_path) + print(f"✓ Successfully downloaded and extracted {folder} folder") + download_results = dict.fromkeys(expected_files, True) + except Exception as e: + print(f"✗ Error extracting {folder}.zip: {e}") + download_results = dict.fromkeys(expected_files, False) + finally: + cleanup_file(tmp_zip_path) + else: + download_results = dict.fromkeys(expected_files, False) + + return download_results + + # Handle data_lake folder (download individual files) + for filename in expected_files: + local_file_path = os.path.join(local_data_lake_path, filename) + + if os.path.exists(local_file_path): + download_results[filename] = True + continue + + s3_file_url = urljoin(s3_bucket_url + "/" + folder + "/", filename) + print(f"Downloading {filename} from {folder}...") + + if download_with_progress(s3_file_url, local_file_path, filename): + print(f"✓ Successfully downloaded: {filename}") + download_results[filename] = True + else: + download_results[filename] = False + + return download_results + + +def clean_message_content(content: str) -> str: + """Clean message content by removing ANSI escape codes. + + This function removes ANSI escape sequences (like color codes) from text content + that might be present in terminal output or console messages. This ensures clean + text for markdown generation and PDF conversion. + + Args: + content: The raw message content that may contain ANSI escape codes + + Returns: + Cleaned content with ANSI escape codes removed + + Example: + >>> clean_message_content("Hello \x1b[31mworld\x1b[0m!") + "Hello world!" + """ + import re + + return re.sub(r"\x1b\[[0-9;]*m", "", content) + + +def should_skip_message(clean_output: str) -> bool: + """Check if message should be skipped during markdown generation. + + This function determines whether a message should be excluded from the final + markdown output. It skips empty or meaningless messages but preserves important + error messages that should be displayed to users. + + Args: + clean_output: The cleaned message content to evaluate + + Returns: + True if the message should be skipped, False otherwise + + Note: + Parsing error messages are intentionally not skipped as they provide + important feedback to users about conversation flow issues. + """ + return ( + clean_output.strip() in ["", "None", "null", "undefined"] + # Don't skip parsing error messages - they should be displayed and increment step counter + # or "There are no tags" in clean_output + # or "Execution terminated due to repeated parsing errors" in clean_output + ) + + +def has_execution_results(clean_output: str, execution_results) -> bool: + """Check if message contains code execution and has associated results. + + This function determines whether a message contains executable code and has + corresponding execution results available for display in the markdown output. + + Args: + clean_output: The cleaned message content to check for execute tags + execution_results: List of execution results from the agent's execution history + + Returns: + True if the message contains tags and has execution results available + """ + return "" in clean_output and execution_results is not None and execution_results + + +def find_matching_execution(clean_output: str, execution_results) -> dict | None: + """Find the execution result that matches the given message content. + + This function searches through the execution results to find the one that + corresponds to the current message. It matches based on the triggering message + content to associate execution results with their originating AI messages. + + Args: + clean_output: The cleaned message content to match against + execution_results: List of execution result dictionaries containing + triggering messages and execution data + + Returns: + The matching execution result dictionary if found, None otherwise + + Note: + The matching is bidirectional - it checks if either the triggering message + is contained in the current output or vice versa to handle partial matches. + """ + for exec_result in execution_results: + if exec_result["triggering_message"] in clean_output or clean_output in exec_result["triggering_message"]: + return exec_result + return None + + +def create_parsing_error_html() -> str: + """Create HTML markup for displaying parsing errors in markdown output. + + This function generates a styled HTML block that displays parsing errors + when the agent's response doesn't contain the required tags. The HTML + uses CSS classes for consistent styling in the final PDF output. + + Returns: + HTML string containing a styled parsing error message box + + Note: + The returned HTML uses CSS classes defined in get_pdf_css_content() + for consistent styling across the document. + """ + return """ +
+
Parsing Error
+
Each response must include thinking process followed by either execute or solution tag. But there are no tags in the current response.
+
+""" + + +def parse_tool_calls_from_code(code: str, module2api: dict, custom_functions: dict = None) -> list[str]: + """Parse code to detect imported tools by analyzing import statements. + + This function analyzes Python code to identify which tools/functions are being + imported and used. It extracts tool names from import statements and function + calls, then returns a deduplicated list of detected tool names. + + Args: + code: The Python code string to analyze for tool imports + module2api: Dictionary mapping module names to their available API tools + custom_functions: Optional dictionary of custom functions that have been + added to the agent + + Returns: + Sorted list of unique tool names detected in the code + + Example: + >>> code = "from biomni.tool import analyze_data\nimport pandas as pd" + >>> parse_tool_calls_from_code(code, module2api) + ['analyze_data', 'pandas'] + """ + tool_module_pairs = parse_tool_calls_with_modules(code, module2api, custom_functions) + return sorted({pair[0] for pair in tool_module_pairs}) + + +def parse_tool_calls_with_modules(code: str, module2api: dict, custom_functions: dict = None) -> list[tuple[str, str]]: + """Parse code to detect imported tools and their associated modules. + + This function performs detailed analysis of Python code to identify which + tools/functions are being imported and which modules they belong to. It + handles various import patterns including direct imports, from-imports, + and module.function patterns. + + Args: + code: The Python code string to analyze for tool imports + module2api: Dictionary mapping module names to their available API tools + custom_functions: Optional dictionary of custom functions that have been + added to the agent + + Returns: + List of tuples containing (tool_name, module_name) pairs for each + detected tool and its associated module + + Note: + The function uses regex patterns to match various import statement + formats and also detects direct function calls without explicit imports. + """ + import re + + detected_tools = set() + + # Get all available tools from module2api + all_tools = {} + for module_name, module_tools in module2api.items(): + for tool in module_tools: + if isinstance(tool, dict) and "name" in tool: + tool_name = tool["name"] + if tool_name not in all_tools: + all_tools[tool_name] = [] + all_tools[tool_name].append(module_name) + + # Add custom tools + if custom_functions: + for tool_name in custom_functions.keys(): + if tool_name not in all_tools: + all_tools[tool_name] = [] + all_tools[tool_name].append("custom_tools") + + # Look for import statements in the code + import_patterns = [ + r"from\s+([\w.]+)\s+import\s+([\w,\s]+)", # from module import tool1, tool2 + r"import\s+([\w.]+)", # import module + ] + + for pattern in import_patterns: + matches = re.findall(pattern, code) + for match in matches: + if len(match) == 2: # from module import tools + module_name, tools_str = match + # Split tools by comma and clean up + tools = [tool.strip() for tool in tools_str.split(",")] + + for tool in tools: + # Check if this tool exists in any module + if tool in all_tools: + # Find the best matching module + best_module = find_best_module_match(module_name, all_tools[tool]) + detected_tools.add((tool, best_module)) + # Also check if it's a module.function pattern + elif "." in tool: + parts = tool.split(".") + if len(parts) == 2: + module_part, func_part = parts + if func_part in all_tools: + best_module = find_best_module_match(module_part, all_tools[func_part]) + detected_tools.add((func_part, best_module)) + + elif len(match) == 1: # import module + module_name = match[0] + # Check if any tools from this module are used + for tool_name, modules in all_tools.items(): + if any(module_name in mod for mod in modules): + # Look for usage of this tool in the code + if re.search(rf"\b{tool_name}\s*\(", code): + best_module = find_best_module_match(module_name, modules) + detected_tools.add((tool_name, best_module)) + + # Also look for direct function calls without imports + function_call_pattern = r"(\w+)\s*\(" + function_calls = re.findall(function_call_pattern, code) + + for func_call in function_calls: + if func_call in all_tools: + # For direct calls, use the first available module + best_module = all_tools[func_call][0] + detected_tools.add((func_call, best_module)) + + return sorted(detected_tools) + + +def find_best_module_match(target_module: str, available_modules: list[str]) -> str: + """Find the best matching module from a list of available modules. + + This function attempts to match a target module name against a list of + available modules using various matching strategies: exact match, partial + substring matches, and fallback to the first available module. + + Args: + target_module: The module name we're trying to match + available_modules: List of available module names to search through + + Returns: + The best matching module name from the available modules list. + Returns "unknown" if no modules are available. + + Note: + The matching strategy prioritizes exact matches, then partial matches + (where either the target is contained in the module name or vice versa), + and finally falls back to the first available module. + """ + # First try exact match + if target_module in available_modules: + return target_module + + # Try partial matches + for module in available_modules: + if target_module in module or module in target_module: + return module + + # Return the first available module as fallback + return available_modules[0] if available_modules else "unknown" + + +def inject_custom_functions_to_repl(custom_functions: dict): + """Inject custom functions into the Python REPL execution environment. + + This function makes custom tools available during code execution by injecting + them into both the persistent execution namespace and the builtins module. + This allows the agent to call custom functions that users have added via + agent.add_tool() when executing Python code in blocks. + + Args: + custom_functions: Dictionary mapping function names to their callable objects + + Note: + The function modifies both the persistent namespace used by run_python_repl + and the builtins module to ensure maximum compatibility and accessibility + of custom functions during code execution. + """ + if custom_functions: + # Access the persistent namespace used by run_python_repl + from biomni.tool.support_tools import _persistent_namespace + + # Inject all custom functions into the execution namespace + for name, func in custom_functions.items(): + _persistent_namespace[name] = func + + # Also make them available in builtins for broader access + import builtins + + if not hasattr(builtins, "_biomni_custom_functions"): + builtins._biomni_custom_functions = {} + builtins._biomni_custom_functions.update(custom_functions) + + +def format_execute_tags_in_content(content: str, parse_tool_calls_with_modules_func) -> str: + """Format execute tags in content by extracting code and creating highlighted tool call blocks. + + This function processes content that contains ... tags and + converts them into styled HTML blocks that display the code with syntax highlighting + and information about which tools are being used. + + Args: + content: The content string that may contain tags + parse_tool_calls_with_modules_func: Function to parse tool calls with modules + (typically parse_tool_calls_with_modules) + + Returns: + Formatted content with execute tags converted to highlighted tool call blocks. + Also processes tags in the same pass. + + Note: + The function also calls format_solution_tags_in_content() to handle + solution tags in the same processing pass. + """ + import re + + # Pattern to match ... blocks + execute_pattern = r"(.*?)" + + def replace_execute_tag(match): + code_content = match.group(1).strip() + language, tool_name = detect_code_language_and_tool(code_content) + code_content = clean_code_content(code_content, language) + + # Parse tools from the code content with module information + detected_tool_modules = parse_tool_calls_with_modules_func(code_content) + + # Create the formatted block + formatted_block = create_tool_call_block(code_content, language, tool_name, detected_tool_modules) + return formatted_block + + # Replace all execute tags with formatted tool call blocks + formatted_content = re.sub(execute_pattern, replace_execute_tag, content, flags=re.DOTALL) + + # Also format solution tags + formatted_content = format_solution_tags_in_content(formatted_content) + + return formatted_content + + +def detect_code_language_and_tool(code_content: str) -> tuple[str, str]: + """Detect the programming language and tool name from code content. + + This function analyzes code content to determine the programming language + and appropriate tool name based on language markers at the beginning of + the code block. + + Args: + code_content: The code content to analyze for language markers + + Returns: + Tuple containing (language, tool_name) where: + - language: The detected programming language ("python", "r", "bash") + - tool_name: The human-readable tool name for display + + Example: + >>> detect_code_language_and_tool("#!R\nlibrary(ggplot2)") + ("r", "R REPL") + >>> detect_code_language_and_tool("#!BASH\necho 'hello'") + ("bash", "Bash Script") + """ + if code_content.startswith("#!R") or code_content.startswith("# R code") or code_content.startswith("# R script"): + return "r", "R REPL" + elif code_content.startswith("#!BASH") or code_content.startswith("# Bash script"): + return "bash", "Bash Script" + elif code_content.startswith("#!CLI"): + return "bash", "CLI Command" + else: + return "python", "Python REPL" + + +def clean_code_content(code_content: str, language: str) -> str: + """Clean code content by removing language markers. + + This function removes language-specific markers from the beginning of code + content to prepare it for display in code blocks. The markers are used + internally for language detection but should not appear in the final output. + + Args: + code_content: The raw code content that may contain language markers + language: The detected programming language ("python", "r", "bash") + + Returns: + Cleaned code content with language markers removed + + Example: + >>> clean_code_content("#!R\nlibrary(ggplot2)", "r") + "library(ggplot2)" + >>> clean_code_content("#!BASH\necho 'hello'", "bash") + "echo 'hello'" + """ + import re + + if language == "r": + return re.sub(r"^#!R|^# R code|^# R script", "", code_content, count=1).strip() + elif language == "bash": + if code_content.startswith("#!BASH") or code_content.startswith("# Bash script"): + return re.sub(r"^#!BASH|^# Bash script", "", code_content, count=1).strip() + elif code_content.startswith("#!CLI"): + return re.sub(r"^#!CLI", "", code_content, count=1).strip() + return code_content + + +def create_tool_call_block(code_content: str, language: str, tool_name: str, detected_tool_modules: list) -> str: + """Create the HTML block for tool call highlighting. + + This function generates a styled HTML block that displays code execution + information including the code itself, syntax highlighting, and a list of + tools that were used during execution. + + Args: + code_content: The cleaned code content to display + language: The programming language for syntax highlighting + tool_name: The default tool name to display if no specific tools detected + detected_tool_modules: List of (tool_name, module_name) tuples for tools used + + Returns: + HTML string containing a styled tool call block with code and tool information + + Note: + The HTML uses CSS classes defined in get_pdf_css_content() for styling. + If no specific tools are detected, it falls back to a default tool name. + """ + # Create the formatted block with code and tools used + formatted_block = f"""
+
+Code Execution +
+
+```{language} +{code_content} +``` +
""" + + # Add tools used section + if detected_tool_modules: + tools_list = format_detected_tools(detected_tool_modules) + formatted_block += f""" +
+Tools Used: {tools_list} +
""" + else: + formatted_block += format_default_tool_name(language, tool_name) + + formatted_block += "
" + return formatted_block + + +def format_detected_tools(detected_tool_modules: list) -> str: + """Format detected tools with their modules for display. + + This function takes a list of (tool_name, module_name) tuples and formats + them into a human-readable string for display in the tool call blocks. + It handles special cases for common tools and formats module names appropriately. + + Args: + detected_tool_modules: List of (tool_name, module_name) tuples + + Returns: + Comma-separated string of formatted tool descriptions + + Example: + >>> format_detected_tools([("analyze_data", "biomni.tool"), ("pandas", "pandas")]) + "biomni → analyze_data, pandas → pandas" + """ + tool_descriptions = [] + for tool_name, module_name in detected_tool_modules: + if tool_name == "python_repl": + tool_descriptions.append("Python REPL") + elif tool_name == "r_repl": + tool_descriptions.append("R REPL") + elif "bash" in tool_name.lower(): + tool_descriptions.append("Bash Script") + else: + # Extract the last part of the module name for display + display_module = module_name.split(".")[-1] if "." in module_name else module_name + tool_descriptions.append(f"{display_module} → {tool_name}") + + return ", ".join(sorted(tool_descriptions)) + + +def format_default_tool_name(language: str, tool_name: str) -> str: + """Format default tool name based on programming language. + + This function generates HTML for displaying the default tool name when + no specific tools are detected in the code. It maps programming languages + to their appropriate default tool names. + + Args: + language: The programming language ("python", "r", "bash") + tool_name: The detected tool name (used for bash CLI vs script distinction) + + Returns: + HTML string containing a styled tools-used section + + Note: + For bash, it distinguishes between CLI commands and bash scripts + based on the tool_name parameter. + """ + if language == "r": + return """ +
+Tools Used: R REPL +
""" + elif language == "bash": + if tool_name == "CLI Command": + return """ +
+Tools Used: CLI Command +
""" + else: + return """ +
+Tools Used: Bash Script +
""" + else: + return """ +
+Tools Used: Python REPL +
""" + + +def format_solution_tags_in_content(content: str) -> str: + """Format solution tags in content by extracting text and formatting as solution blocks. + + This function processes content that contains ... tags and + converts them into styled HTML blocks that display solution content with appropriate + formatting and CSS classes. + + Args: + content: The content string that may contain tags + + Returns: + Formatted content with solution tags converted to styled solution blocks + + Note: + The solution blocks use the "title-text summary" CSS class for consistent + styling with other content blocks in the markdown output. + """ + import re + + # Pattern to match ... blocks + solution_pattern = r"(.*?)" + + def replace_solution_tag(match): + solution_content = match.group(1).strip() + # Format as regular text, not terminal + return f"""
+
+Summary and Solution +
+
+{solution_content} +
+
""" + + # Replace all solution tags with formatted solution blocks + formatted_content = re.sub(solution_pattern, replace_solution_tag, content, flags=re.DOTALL) + + return formatted_content + + +def format_observation_as_terminal(content: str) -> str | None: + """Format observation content with terminal-like styling. + + This function processes observation content from the agent's execution results + and formats it as a styled terminal block. It handles both text and image content, + with length limits to ensure the output fits within PDF page constraints. + + Args: + content: The observation content string, potentially containing tags + + Returns: + Formatted HTML content with terminal styling, or None if observation is + empty, invalid, or contains only meaningless content + + Note: + - Content is limited to 10,000 characters to fit within 2 A4 pages + - Handles both text and base64-encoded images + - Uses CSS classes for consistent styling with other content blocks + """ + import re + + # Character limit for 2 A4 pages (approximately 10,000 characters) + MAX_OBSERVATION_LENGTH = 10000 + + # Remove the tags and extract the content + observation_pattern = r"(.*?)" + observation_match = re.search(observation_pattern, content, re.DOTALL) + + if observation_match: + observation_content = observation_match.group(1).strip() + else: + # Fallback if no observation tags found - check if content is meaningful + if not (content.strip() and content.strip() not in ["", "None", "null", "undefined"]): + return None + observation_content = content.strip() + + # Skip empty observations + if not observation_content or observation_content in ["", "None", "null", "undefined"]: + return None + + # Check if observation is too long for 2 pages + if len(observation_content) > MAX_OBSERVATION_LENGTH: + cropped_content = observation_content[:MAX_OBSERVATION_LENGTH] + truncation_notice = f"\n\n[Output truncated - content was too long to display here ({len(observation_content)} characters total)]" + observation_content = cropped_content + truncation_notice + + # Check if it contains plot data (base64 images) + if "data:image/" in observation_content: + content_html = process_observation_with_images(observation_content) + else: + # Regular text output - format as terminal output + content_html = f"```terminal\n{observation_content}\n```" + + return f"""
+
+Observation +
+
+{content_html} +
+
""" + + +def process_observation_with_images(observation_content: str) -> str: + """Process observation content that contains both text and base64-encoded images. + + This function handles observation content that includes both text output and + base64-encoded images (typically plots from data analysis). It separates the + text and image content and formats them appropriately for markdown display. + + Args: + observation_content: The observation content containing both text and images + + Returns: + HTML string containing formatted text (as terminal blocks) and images + (as markdown image tags) + + Note: + The function uses "data:image/" as a delimiter to split content into + text and image parts, then processes each part separately. + """ + # Split content into text and image parts + parts = observation_content.split("data:image/") + text_parts = [] + image_parts = [] + + for i, part in enumerate(parts): + if i == 0: + # First part is text only + if part.strip(): + text_parts.append(part.strip()) + else: + # Find the end of the base64 data + end_markers = ["\n", "\r", " ", "\t", ">", "<", "]", ")", "}"] + image_end = len(part) + for marker in end_markers: + marker_pos = part.find(marker) + if marker_pos != -1 and marker_pos < image_end: + image_end = marker_pos + + # Extract image data + image_data = "data:image/" + part[:image_end] + image_parts.append(image_data) + + # Extract remaining text + remaining_text = part[image_end:].strip() + if remaining_text: + text_parts.append(remaining_text) + + # Build the content + content_html = "" + if text_parts: + # Add text content as terminal output + text_content = "\n".join(text_parts) + content_html += f"```terminal\n{text_content}\n```\n\n" + + if image_parts: + # Add image content + for image_data in image_parts: + content_html += f"![Plot]({image_data})\n\n" + + return content_html + + +def remove_emojis_from_text(text: str) -> str: + """Remove emojis from text for markdown/PDF output. + + This function removes common emojis used in the system prompt and configuration + display from text content before it's converted to markdown or PDF. This ensures + clean, professional output while preserving emojis in the console display. + + Args: + text: The text content that may contain emojis + + Returns: + Text content with emojis removed + + Note: + The function targets specific emojis used in the Biomni system: + - 🔧 for tools + - 📊 for data + - ⚙️ for software + - 📋 for configuration + - 🤖 for agent + """ + import re + + # Remove common emojis used in the system prompt, this makes conversion simpler + emoji_patterns = [ + r"🔧\s*", # Tool emoji + r"📊\s*", # Data emoji + r"⚙️\s*", # Software emoji + r"📋\s*", # Config emoji + r"🤖\s*", # Agent emoji + ] + + for pattern in emoji_patterns: + text = re.sub(pattern, "", text) + + return text + + +def format_lists_in_text(text: str) -> str: + """Format numbered lists and bullet points in text to proper markdown format. + + This function processes text content to identify and format various types of lists, + including numbered lists with checkboxes, regular lists, and plan structures. + It also handles preprocessing tasks like removing bold formatting from plan titles + and removing emojis for clean PDF output. + + Args: + text: The text content to process for list formatting + + Returns: + Formatted text with properly structured lists and cleaned formatting + + Note: + The function performs several preprocessing steps: + - Removes bold formatting from plan titles + - Removes emojis for PDF output + - Identifies and formats checkbox lists + - Processes regular text blocks + """ + import re + + # Preprocess to remove bold formatting from plan titles + # Remove **Plan:**, **Updated Plan:**, **Completed Plan:**, etc. + text = re.sub(r"\*\*([Pp]lan|Updated [Pp]lan|Completed [Pp]lan|Final [Pp]lan):\*\*", r"\1:", text) + # Also handle cases without colons + text = re.sub(r"\*\*([Pp]lan|Updated [Pp]lan|Completed [Pp]lan|Final [Pp]lan)\*\*", r"\1", text) + # Handle any other bold formatting patterns for plan titles + text = re.sub(r"([Pp]lan|Updated [Pp]lan|Completed [Pp]lan|Final [Pp]lan):", r"\1:", text) + text = re.sub(r"([Pp]lan|Updated [Pp]lan|Completed [Pp]lan|Final [Pp]lan)", r"\1", text) + + # Remove emojis from the text for markdown/PDF output + text = remove_emojis_from_text(text) + + lines = text.split("\n") + list_blocks = identify_list_blocks(lines) + + # Process each block + result_blocks = [] + for block_text, is_checkbox_list in list_blocks: + if is_checkbox_list: + result_blocks.append(format_single_list(block_text)) + else: + result_blocks.append(block_text) + + return "\n".join(result_blocks) + + +def identify_list_blocks(lines: list) -> list[tuple[str, bool]]: + """Identify blocks of text that contain lists. + + This function analyzes a list of text lines to identify contiguous blocks + that contain numbered lists with checkboxes. It groups lines into blocks + and marks whether each block contains a checkbox list or regular text. + + Args: + lines: List of text lines to analyze + + Returns: + List of tuples containing (block_text, is_checkbox_list) where: + - block_text: The text content of the block + - is_checkbox_list: True if the block contains numbered items with checkboxes + + Note: + The function looks for patterns like "1. [ ]", "2. [✓]", "3. [✗]" to + identify checkbox sequences and groups them into separate blocks. + """ + import re + + list_blocks = [] + current_block = [] + in_checkbox_sequence = False + + for line in lines: + line_stripped = line.strip() + + # Check if this line starts a numbered item with checkbox + if re.match(r"^\d+\.\s*\[[ ✓✗]\]", line_stripped): + if not in_checkbox_sequence: + # Start of a new checkbox sequence + if current_block: + list_blocks.append(("\n".join(current_block), False)) + current_block = [line] + in_checkbox_sequence = True + else: + # Continue the sequence + current_block.append(line) + else: + if in_checkbox_sequence: + # End of checkbox sequence + if current_block: + list_blocks.append(("\n".join(current_block), True)) + current_block = [] + in_checkbox_sequence = False + current_block.append(line) + + # Handle the last block + if current_block: + if in_checkbox_sequence: + list_blocks.append(("\n".join(current_block), True)) + else: + list_blocks.append(("\n".join(current_block), False)) + + return list_blocks + + +def format_single_list(text: str) -> str: + """Format a single list block with checkboxes and plan titles. + + This function processes a text block that may contain numbered lists with + checkboxes and plan titles. It converts checkbox symbols to HTML list items + and wraps the content in a styled container with appropriate CSS classes. + + Args: + text: The text block to format, potentially containing numbered lists + + Returns: + HTML string containing either a formatted list with plan title or + regular text if no list items are found + + Note: + The function recognizes plan titles like "Plan", "Updated Plan", "Completed Plan" + and converts checkbox symbols (✓, ✗) to HTML format ([x], [ ]). + """ + import re + + lines = text.split("\n") + list_items = [] + has_list_items = False + plan_title = "Plan" # Default title + + for line in lines: + line = line.strip() + if not line: + continue + + # Check for plan title patterns + if re.match(r"^(Plan|Updated Plan|Completed Plan)$", line, re.IGNORECASE): + plan_title = line + continue + + # Check for numbered lists with checkboxes (1. [ ] or 1. [✓] or 1. [✗]) + if re.match(r"^\d+\.\s*\[[ ✓✗]\]", line): + has_list_items = True + # Extract the content after the checkbox + content = re.sub(r"^\d+\.\s*\[[ ✓✗]\]\s*", "", line) + + # Replace checkbox symbols with text format + if "[✓]" in line: + list_items.append(f"
  • [x] {content}
  • ") + elif "[✗]" in line: + list_items.append(f"
  • [ ] {content}
  • ") + else: + list_items.append(f"
  • [ ] {content}
  • ") + else: + # Regular text - add as is (don't convert to list items) + list_items.append(line) + + if has_list_items and list_items: + # This is a list - return with container div and styled title + return f"""
    +
    +{plan_title} +
    +
    +
      +{chr(10).join(list_items)} +
    +
    +
    """ + else: + # Regular text + return "\n".join(list_items) + + +def convert_markdown_to_pdf(markdown_path: str, pdf_path: str) -> None: + """Convert markdown file to PDF using weasyprint or fallback libraries. + + This function converts a markdown file to PDF format using multiple fallback + strategies. It prioritizes weasyprint for better layout control, then falls back + to markdown2pdf and finally pandoc if the preferred libraries are not available. + + Args: + markdown_path: Path to the input markdown file + pdf_path: Path where the output PDF file should be saved + + Raises: + ImportError: If no PDF conversion library is available + Exception: If PDF conversion fails for any other reason + + Note: + The function uses minimal markdown extensions for better performance + and applies custom CSS styling for consistent formatting. + """ + try: + # Try weasyprint first (better for complex layouts) + from weasyprint import HTML + from weasyprint.text.fonts import FontConfiguration + + # Read markdown content + with open(markdown_path, encoding="utf-8") as f: + markdown_content = f.read() + + # Convert markdown to HTML with minimal extensions for better performance + import markdown + + # Use minimal extensions to improve performance + html_content = markdown.markdown( + markdown_content, + extensions=["fenced_code"], # Removed codehilite for better performance + ) + + # Add CSS styling + css_content = get_pdf_css_content() + + # Create HTML document + html_doc = f""" + + + + + Biomni Conversation History + + + + {html_content} + + + """ + + # Convert to PDF with performance optimizations + font_config = FontConfiguration() + html_obj = HTML(string=html_doc) + html_obj.write_pdf(pdf_path, font_config=font_config, optimize_images=True) + + except ImportError: + # Fallback to markdown2pdf if weasyprint is not available + try: + from markdown2pdf import markdown2pdf + + markdown2pdf(markdown_path, pdf_path) + except ImportError: + # Final fallback - try using pandoc if available + import subprocess + + try: + subprocess.run(["pandoc", markdown_path, "-o", pdf_path], check=True) + except (subprocess.CalledProcessError, FileNotFoundError) as e: + raise ImportError( + "No PDF conversion library available. Please install weasyprint, markdown2pdf, or pandoc." + ) from e + except Exception as e: + raise Exception(f"PDF conversion failed: {e}") from e + + +def get_pdf_css_content() -> str: + """Get the CSS content for PDF generation. + + This function returns a comprehensive CSS stylesheet designed specifically + for PDF generation from markdown content. It includes styling for all + HTML elements that may appear in the converted markdown, with optimized + typography, spacing, and layout for print media. + + Returns: + CSS string containing all styles needed for PDF generation + + Note: + The CSS includes styles for: + - Typography and font families + - Headings and text formatting + - Code blocks and syntax highlighting + - Tables and lists + - Custom classes for tool calls, observations, and plans + - Print-optimized spacing and layout + """ + return """ + body { + /* Previously: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif, 'Noto Color Emoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Twemoji', 'EmojiOne Color' */ + font-family: sans-serif; + font-size: 9pt; + line-height: 1.4; + max-width: 800px; + margin: 0 auto; + padding: 15px; + color: #333; + } + h1, h2, h3, h4, h5, h6 { + /* Previously: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif, 'Noto Color Emoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Twemoji', 'EmojiOne Color' */ + font-family: sans-serif; + color: #2c3e50; + margin-top: 1em; + margin-bottom: 0.5em; + } + h1 { + border-bottom: 2px solid #3498db; + padding-bottom: 8px; + font-size: 16pt; + } + h2 { + border-bottom: 1px solid #bdc3c7; + padding-bottom: 3px; + font-size: 14pt; + } + h3 { + font-size: 12pt; + } + h4 { + font-size: 10pt; + margin-top: 0.8em; + margin-bottom: 0.3em; + } + h5, h6 { + font-size: 9pt; + margin-top: 0.6em; + margin-bottom: 0.2em; + } + code { + background-color: #f8f9fa; + padding: 1px 3px; + border-radius: 2px; + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + font-size: 8pt; + white-space: pre-wrap; + word-wrap: break-word; + } + pre { + background-color: #f8f9fa; + padding: 10px; + border-radius: 3px; + overflow-x: auto; + border-left: 3px solid #3498db; + white-space: pre-wrap; + word-wrap: break-word; + font-size: 8pt; + margin: 0.5em 0; + } + pre code { + background-color: transparent; + padding: 0; + border-radius: 0; + font-size: 8pt; + } + /* Code header styling */ + strong { + font-size: 9pt; + font-weight: normal; + color: #6c757d; + font-style: italic; + } + blockquote { + border-left: 3px solid #bdc3c7; + margin: 0.5em 0; + padding-left: 15px; + color: #7f8c8d; + font-size: 8pt; + } + table { + border-collapse: collapse; + width: 100%; + margin: 0.5em 0; + font-size: 8pt; + } + th, td { + border: 1px solid #bdc3c7; + padding: 4px 8px; + text-align: left; + } + th { + background-color: #ecf0f1; + font-weight: bold; + } + img { + max-width: 100%; + height: auto; + display: block; + margin: 10px auto; + border: 1px solid #ddd; + border-radius: 3px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); + } + p { + /* Previously: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif, 'Noto Color Emoji', 'Apple Color Emoji', 'Segoe UI Emoji', 'Twemoji', 'EmojiOne Color' */ + font-family: sans-serif; + margin: 0.3em 0; + } + /* Tool call highlighting - matching observation and code formatting */ + .tool-call-highlight { + background-color: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 3px; + padding: 0; + margin: 10px 0; + overflow: hidden; + } + .tool-call-header { + background-color: #e9ecef; + color: #495057; + padding: 8px 12px; + margin: 0; + font-weight: normal; + font-size: 9pt; + font-style: italic; + border-bottom: 1px solid #dee2e6; + } + .tool-call-input { + background-color: #f8f9fa; + border: none; + border-radius: 0; + padding: 10px 12px; + margin: 0; + color: #333; + font-size: 8pt; + line-height: 1.4; + } + .tool-call-input strong { + color: #495057; + font-weight: normal; + font-size: 8pt; + font-style: italic; + } + .tool-call-input pre { + background-color: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 3px; + padding: 10px; + margin: 0; + font-size: 8pt; + line-height: 1.4; + overflow-x: auto; + white-space: pre-wrap; + word-wrap: break-word; + } + .tool-call-input code { + background-color: transparent; + padding: 0; + border-radius: 0; + font-size: 8pt; + color: #2c3e50; + } + .tools-used { + background-color: #f8f9fa; + border-top: 1px solid #dee2e6; + padding: 8px 12px; + margin: 0; + font-size: 8pt; + color: #6c757d; + } + .tools-used strong { + color: #6c757d; + font-weight: normal; + font-size: 8pt; + font-style: italic; + } + /* Title-text styling - unified for observations, plans, and solutions */ + .title-text { + background-color: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 3px; + padding: 0; + margin: 10px 0; + overflow: hidden; + } + .title-text-header { + background-color: #e9ecef; + color: #495057; + padding: 8px 12px; + margin: 0; + font-weight: normal; + font-size: 9pt; + font-style: italic; + border-bottom: 1px solid #dee2e6; + } + .title-text-header strong { + color: #495057; + font-weight: normal; + font-size: 9pt; + font-style: italic; + } + .title-text-content { + background-color: #f8f9fa; + border: none; + border-radius: 0; + padding: 10px 12px; + margin: 0; + color: #333; + font-size: 8pt; + line-height: 1.4; + } + /* Plan-specific styling - soft blue pastel */ + .title-text.plan { + background-color: #e3f2fd; + border-color: #bbdefb; + } + .title-text.plan .title-text-header { + background-color: #bbdefb; + color: #1976d2; + } + .title-text.plan .title-text-content { + background-color: #e3f2fd; + } + .plan-title { + font-style: italic; + font-weight: normal; + color: #1565c0; + text-shadow: 0 1px 2px rgba(0,0,0,0.1); + } + .plan-title strong { + font-weight: normal; + } + /* Code execution-specific styling - matching title-text styling */ + .tool-call-highlight { + background-color: #f8f9fa; + border-color: #e9ecef; + } + .tool-call-header { + background-color: #e9ecef; + color: #495057; + } + .tool-call-input { + background-color: #f8f9fa; + color: #333; + } + /* Observation-specific styling - soft purple pastel */ + .title-text.observation { + background-color: #f3e5f5; + border-color: #e1bee7; + } + .title-text.observation .title-text-header { + background-color: #e1bee7; + color: #7b1fa2; + } + .title-text.observation .title-text-content { + background-color: #f3e5f5; + } + /* Summary and solution-specific styling - soft orange pastel, no overlay */ + .title-text.summary { + background-color: #fff3e0; + border-color: #ffcc02; + } + .title-text.summary .title-text-header { + background-color: #ffcc02; + color: #f57c00; + } + .title-text.summary .title-text-content { + background-color: #fff3e0; + } + .title-text-content ul { + background-color: transparent; + border: none; + border-radius: 0; + padding: 0; + margin: 0; + color: #333; + font-size: 8pt; + line-height: 1.4; + } + .title-text-content li { + margin: 3px 0; + color: #333; + } + .title-text-content li strong { + color: #495057; + font-weight: normal; + font-size: 8pt; + font-style: italic; + } + .title-text-content li code { + background-color: #e9ecef; + color: #333; + padding: 1px 3px; + border-radius: 2px; + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + font-size: 7pt; + } + .title-text-content pre { + background-color: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 3px; + padding: 10px; + margin: 0; + font-size: 8pt; + line-height: 1.4; + overflow-x: auto; + white-space: pre-wrap; + word-wrap: break-word; + } + .title-text-content code { + background-color: transparent; + padding: 0; + border-radius: 0; + font-size: 8pt; + color: #2c3e50; + } + /* Parsing error display styling */ + .parsing-error-box { + background-color: #ffebee; + border: 1px solid #f44336; + border-radius: 4px; + padding: 8px 12px; + margin: 8px 0; + font-size: 9pt; + color: #c62828; + box-shadow: 0 2px 4px rgba(244, 67, 54, 0.1); + } + .parsing-error-header { + font-weight: bold; + margin-bottom: 4px; + color: #d32f2f; + } + .parsing-error-content { + font-family: 'Courier New', monospace; + background-color: #ffcdd2; + padding: 4px 6px; + border-radius: 2px; + margin-top: 4px; + font-size: 8pt; + white-space: pre-wrap; + word-wrap: break-word; + } + """ diff --git a/BioScientist/agent_system/engines/v1_executor_backup/version.py b/BioScientist/agent_system/engines/v1_executor_backup/version.py new file mode 100644 index 0000000000000000000000000000000000000000..0e12f9073d99ef70cc114da06cac0f674c48f596 --- /dev/null +++ b/BioScientist/agent_system/engines/v1_executor_backup/version.py @@ -0,0 +1 @@ +__version__ = "0.0.8" # pragma: no cover diff --git a/BioScientist/agent_system/main.py b/BioScientist/agent_system/main.py new file mode 100644 index 0000000000000000000000000000000000000000..1ac3877b6ca00a79a8ed003cc56d8c151371f90e --- /dev/null +++ b/BioScientist/agent_system/main.py @@ -0,0 +1,152 @@ +from __future__ import annotations + +import argparse +from datetime import datetime, timezone +import json +from pathlib import Path + +from .orchestrator import DualModeAgentSystem + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser("Bioinfo Dual-Mode Agent System") + parser.add_argument("--project_root", type=str, default=str(Path(__file__).resolve().parents[1])) + parser.add_argument( + "--execution_backend", + type=str, + choices=["docker", "python"], + default="docker", + help="Tool execution backend. docker=run tool calls inside container images; python=run in host process.", + ) + sub = parser.add_subparsers(dest="mode", required=True) + + p_exec = sub.add_parser("execute") + p_exec.add_argument("--task", type=str, required=True) + p_exec.add_argument("--task_scope", type=str, required=True) + p_exec.add_argument("--input_manifest", type=str, required=True, help="JSON string") + + sub.add_parser("reflect") + + p_consult = sub.add_parser("consult") + p_consult.add_argument("--user_goal", type=str, required=True) + p_consult.add_argument("--task_scope", type=str, required=True) + + p_auto = sub.add_parser("autopilot") + p_auto.add_argument("--user_goal", type=str, required=True) + p_auto.add_argument("--data_dir", type=str, required=True) + p_auto.add_argument("--task_scope", type=str, default="first_pipeline") + + p_cfg = sub.add_parser("propose-config") + p_cfg.add_argument("--task_scope", type=str, required=True) + p_cfg.add_argument("--strategy_name", type=str, required=True) + p_cfg.add_argument("--tools", type=str, required=True, help="JSON list string") + p_cfg.add_argument("--parameters", type=str, required=True, help="JSON object string") + p_cfg.add_argument("--rationale", type=str, required=True) + + p_reg = sub.add_parser("register-mcp") + p_reg.add_argument("--dry_run", action="store_true") + + p_hyp = sub.add_parser("hypothesis-generate") + p_hyp.add_argument("--user_query", type=str, required=True) + p_hyp.add_argument("--task_scope", type=str, required=True) + p_hyp.add_argument("--n", type=int, default=10) + p_hyp.add_argument("--top_k", type=int, default=5) + + p_loop = sub.add_parser("hypothesis-loop") + p_loop.add_argument("--user_query", type=str, required=True) + p_loop.add_argument("--task_scope", type=str, required=True) + p_loop.add_argument("--n", type=int, default=10) + p_loop.add_argument("--top_k", type=int, default=5) + p_loop.add_argument("--validate_top_m", type=int, default=3) + p_loop.add_argument("--validation_level", type=str, default="L1") + p_loop.add_argument("--register_mcp", type=str, default="true") + return parser + + +def _default_results_dir(project_root: Path) -> Path: + # Prefer /agent_system/results; fall back to package-local results directory. + candidate = project_root / "agent_system" / "results" + if candidate.parent.exists(): + return candidate + return Path(__file__).resolve().parent / "results" + + +def _persist_result( + result: dict, + *, + mode: str, + project_root: Path, +) -> Path: + results_dir = _default_results_dir(project_root) + results_dir.mkdir(parents=True, exist_ok=True) + ts = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ") + output_path = results_dir / f"{mode}_{ts}.json" + output_path.write_text(json.dumps(result, indent=2, ensure_ascii=True), encoding="utf-8") + return output_path + + +def main() -> None: + parser = build_parser() + args = parser.parse_args() + project_root = Path(args.project_root).resolve() + system = DualModeAgentSystem(project_root=project_root, execution_backend=args.execution_backend) + + if args.mode == "execute": + result = system.execute( + task=args.task, + input_manifest=json.loads(args.input_manifest), + task_scope=args.task_scope, + ) + elif args.mode == "reflect": + result = system.reflect() + elif args.mode == "consult": + result = system.consult(user_goal=args.user_goal, task_scope=args.task_scope) + elif args.mode == "autopilot": + result = system.autopilot( + user_goal=args.user_goal, + data_dir=args.data_dir, + task_scope=args.task_scope, + ) + elif args.mode == "propose-config": + result = system.propose_config( + task_scope=args.task_scope, + strategy_name=args.strategy_name, + tools=json.loads(args.tools), + parameters=json.loads(args.parameters), + rationale=args.rationale, + ) + elif args.mode == "register-mcp": + result = system.register_mcp_servers(dry_run=bool(args.dry_run)) + elif args.mode == "hypothesis-generate": + result = system.propose_hypotheses( + user_query=args.user_query, + task_scope=args.task_scope, + n=args.n, + top_k=args.top_k, + ) + elif args.mode == "hypothesis-loop": + register_mcp = str(args.register_mcp).strip().lower() in ("1", "true", "yes", "y", "on") + result = system.hypothesis_loop( + user_query=args.user_query, + task_scope=args.task_scope, + n=args.n, + top_k=args.top_k, + validate_top_m=args.validate_top_m, + validation_level=args.validation_level, + register_mcp=register_mcp, + ) + else: # pragma: no cover - argparse already guards + raise ValueError(f"Unsupported mode: {args.mode}") + + saved_path = _persist_result( + result, + mode=args.mode, + project_root=project_root, + ) + result = dict(result) + result["result_file"] = str(saved_path) + print(json.dumps(result, indent=2, ensure_ascii=True)) + + +if __name__ == "__main__": + main() diff --git a/BioScientist/agent_system/mcp_router.py b/BioScientist/agent_system/mcp_router.py new file mode 100644 index 0000000000000000000000000000000000000000..397cc92f55dee31fd1fcbd8ceda72a7301c2631f --- /dev/null +++ b/BioScientist/agent_system/mcp_router.py @@ -0,0 +1,125 @@ +from __future__ import annotations + +import ast +from dataclasses import dataclass +from pathlib import Path +import shutil + + +@dataclass(frozen=True) +class MCPToolRoute: + tool_name: str + function_name: str + binary_name: str + server_script: Path + server_dir: Path + image_name: str + service_name: str + + +class MCPToolRouter: + """Dynamic router for MCP tool servers under mcp-servers/.""" + + def __init__(self, project_root: str | Path): + self.project_root = Path(project_root) + self.servers_root = self.project_root / "mcp-servers" + self.routes: dict[str, MCPToolRoute] = {} + self.refresh() + + def refresh(self) -> None: + self.routes = {} + if not self.servers_root.exists(): + return + + for script in sorted(self.servers_root.glob("mcp_*/app/*_server.py")): + server_dir = script.parent.parent + server_name = server_dir.name.replace("mcp_", "") + canonical_tool = server_name.replace("-", "_") + image_name = f"mcp-{server_name}:latest" + service_name = f"mcp-{server_name}" + for function_name in self._discover_mcp_functions(script): + route = MCPToolRoute( + tool_name=canonical_tool, + function_name=function_name, + binary_name=self._infer_binary_name(canonical_tool, function_name), + server_script=script, + server_dir=server_dir, + image_name=image_name, + service_name=service_name, + ) + # 可按函数名或工具名取路由 + self.routes[function_name] = route + self.routes.setdefault(canonical_tool, route) + # 为常见对齐步骤补别名 + self._register_aliases(route) + + def resolve(self, tool_name: str) -> MCPToolRoute: + if tool_name not in self.routes: + raise KeyError(f"Unsupported tool route: {tool_name}") + return self.routes[tool_name] + + def has_tool(self, tool_name: str) -> bool: + return tool_name in self.routes + + def pick_first_available(self, candidates: list[str]) -> MCPToolRoute | None: + for name in candidates: + if name in self.routes: + return self.routes[name] + return None + + def binary_available(self, tool_name: str) -> bool: + route = self.resolve(tool_name) + return shutil.which(route.binary_name) is not None + + @staticmethod + def _discover_mcp_functions(script_path: Path) -> list[str]: + try: + tree = ast.parse(script_path.read_text(encoding="utf-8")) + except Exception: + return [] + + functions: list[str] = [] + for node in tree.body: + if not isinstance(node, ast.FunctionDef): + continue + if any(MCPToolRouter._is_mcp_tool_decorator(d) for d in node.decorator_list): + functions.append(node.name) + return functions + + @staticmethod + def _is_mcp_tool_decorator(decorator: ast.AST) -> bool: + # @mcp.tool / @mcp.tool() + if isinstance(decorator, ast.Call): + decorator = decorator.func + return ( + isinstance(decorator, ast.Attribute) + and isinstance(decorator.value, ast.Name) + and decorator.value.id == "mcp" + and decorator.attr == "tool" + ) + + @staticmethod + def _infer_binary_name(tool_name: str, function_name: str) -> str: + overrides = { + "trim_galore": "trim_galore", + "bowtie2_align": "bowtie2", + "bowtie2_build": "bowtie2-build", + "bowtie2_inspect": "bowtie2-inspect", + } + if function_name in overrides: + return overrides[function_name] + if tool_name in overrides: + return overrides[tool_name] + return tool_name.replace("_", "-") + + def _register_aliases(self, route: MCPToolRoute) -> None: + # 将常见 pipeline 逻辑名映射到具体 MCP 工具函数 + if route.function_name == "bowtie2_align": + self.routes["bowtie2"] = route + if route.function_name == "trim_galore": + self.routes["trim_galore"] = route + self.routes["trim-galore"] = route + if route.function_name == "fastqc": + self.routes["fastqc"] = route + if route.function_name == "multiqc": + self.routes["multiqc"] = route diff --git a/BioScientist/agent_system/orchestrator.py b/BioScientist/agent_system/orchestrator.py new file mode 100644 index 0000000000000000000000000000000000000000..604d8844d1acd3390e13dc190c6952245baca12f --- /dev/null +++ b/BioScientist/agent_system/orchestrator.py @@ -0,0 +1,167 @@ +from __future__ import annotations + +import os +from pathlib import Path +from typing import Any + +from .engines import BioinfoE1Validator, BioinfoT1Consultant, BioinfoV1Executor +from .shared_memory import SharedKnowledgeSpace + + +class DualModeAgentSystem: + """High-level orchestrator for V1 execution and T1 reflection.""" + + def __init__(self, project_root: str | Path, execution_backend: str | None = None): + self.project_root = Path(project_root) + backend = execution_backend or os.getenv("BIOCLAW_EXECUTION_BACKEND", "docker") + self.memory = SharedKnowledgeSpace(self.project_root / "shared_knowledge") + self.v1 = BioinfoV1Executor( + memory=self.memory, + default_results_root=self.project_root / "results" / "dual_mode_runs", + project_root=self.project_root, + execution_backend=backend, + ) + self.t1 = BioinfoT1Consultant(memory=self.memory) + biomni_root = os.getenv( + "BIOCLAW_BIOMNI_ROOT", + "/225040511/project/BioScientist/agent_system/engines/v1_executor_backup", + ) + mcp_config = os.getenv( + "BIOCLAW_BIOMNI_MCP_CONFIG", + str(Path(biomni_root) / "mcp_config_bioscientist_generated.yaml"), + ) + self.e1 = BioinfoE1Validator( + memory=self.memory, + biomni_root=biomni_root, + mcp_config_path=mcp_config, + project_root=self.project_root, + ) + + def execute( + self, + task: str, + input_manifest: dict[str, Any], + task_scope: str, + ) -> dict[str, Any]: + cfg = self.memory.latest_config_for_task(task_scope) + return self.v1.execute_task( + task=task, + input_manifest=input_manifest, + task_scope=task_scope, + pipeline_config=cfg, + ) + + def reflect(self) -> list[dict[str, Any]]: + return self.t1.review_reports() + + def consult(self, user_goal: str, task_scope: str) -> dict[str, Any]: + return self.t1.consult(user_goal=user_goal, task_scope=task_scope) + + def propose_config( + self, + task_scope: str, + strategy_name: str, + tools: list[str], + parameters: dict[str, Any], + rationale: str, + ) -> dict[str, Any]: + return self.t1.emit_pipeline_config( + task_scope=task_scope, + strategy_name=strategy_name, + tools=tools, + parameters=parameters, + rationale=rationale, + ) + + def autopilot( + self, + user_goal: str, + data_dir: str, + task_scope: str = "first_pipeline", + manifest_overrides: dict[str, Any] | None = None, + ) -> dict[str, Any]: + cfg = self.memory.latest_config_for_task(task_scope) + return self.v1.execute_autopilot( + user_goal=user_goal, + data_dir=data_dir, + task_scope=task_scope, + pipeline_config=cfg, + manifest_overrides=manifest_overrides, + ) + + def register_mcp_servers(self, dry_run: bool = False) -> dict[str, Any]: + return self.e1.ensure_mcp_registered(dry_run=dry_run) + + @staticmethod + def _stage_log(stage: str, message: str) -> None: + print(f"[STAGE:{stage}] {message}", flush=True) + + def propose_hypotheses( + self, + user_query: str, + task_scope: str, + n: int = 10, + top_k: int = 5, + ) -> dict[str, Any]: + self._stage_log("HYPOTHESIS_GENERATION", f"start domain={task_scope} n={n} top_k={top_k}") + generated = self.t1.generate_hypotheses( + user_query=user_query, + domain=task_scope, + n=n, + top_k=top_k, + ) + self._stage_log("HYPOTHESIS_GENERATION", f"generated={len(generated)}") + self._stage_log("HYPOTHESIS_RANKING", f"start candidates={len(generated)}") + ranked = self.t1.rank_hypotheses_by_success_proxy(generated, domain=task_scope) + self._stage_log("HYPOTHESIS_RANKING", f"done ranked={len(ranked)}") + return { + "task_scope": task_scope, + "user_query": user_query, + "generated_count": len(generated), + "hypothesis_generation": dict(getattr(self.t1, "last_generation_meta", {})), + "hypotheses_ranked": ranked, + } + + def hypothesis_loop( + self, + user_query: str, + task_scope: str, + n: int = 10, + top_k: int = 5, + validate_top_m: int = 3, + validation_level: str = "L1", + register_mcp: bool = True, + ) -> dict[str, Any]: + self._stage_log("PIPELINE", f"hypothesis-loop start domain={task_scope} validation_level={validation_level}") + registration = None + if register_mcp: + self._stage_log("MCP_REGISTRATION", "start") + registration = self.e1.ensure_mcp_registered(dry_run=False) + self._stage_log( + "MCP_REGISTRATION", + f"done ok={bool(registration and registration.get('ok', False))}", + ) + proposal = self.propose_hypotheses(user_query=user_query, task_scope=task_scope, n=n, top_k=top_k) + ranked = proposal["hypotheses_ranked"] + self._stage_log( + "VALIDATION", + f"start level={validation_level.upper()} top_m={max(0, validate_top_m)} from_ranked={len(ranked)}", + ) + validations = self.e1.validate_top_hypotheses( + ranked_hypotheses=ranked, + top_m=validate_top_m, + level=validation_level, + ) + self._stage_log("VALIDATION", f"done reports={len(validations)}") + self._stage_log("PIPELINE", "hypothesis-loop finished") + return { + "task_scope": task_scope, + "user_query": user_query, + "registration": registration, + "generated_count": proposal["generated_count"], + "hypothesis_generation": proposal.get("hypothesis_generation", {}), + "validated_count": len(validations), + "ranked_hypotheses": ranked, + "validation_reports": validations, + "summary_statistics": self.memory.get_summary_statistics(), + } diff --git a/BioScientist/agent_system/schemas.py b/BioScientist/agent_system/schemas.py new file mode 100644 index 0000000000000000000000000000000000000000..131069a5a7e8c9b463693d89a91d74de0d316f0f --- /dev/null +++ b/BioScientist/agent_system/schemas.py @@ -0,0 +1,225 @@ +from __future__ import annotations + +from dataclasses import asdict, dataclass, field +from datetime import datetime, timezone +from typing import Any +import uuid + + +def utc_now_iso() -> str: + return datetime.now(tz=timezone.utc).isoformat() + + +@dataclass +class FailureMode: + step_name: str + error_type: str + error_message: str + hint: str = "" + + +@dataclass +class ExperimentReport: + run_id: str + created_at: str + task: str + status: str + input_manifest: dict[str, Any] + pipeline_config_id: str | None + output_artifacts: list[str] + metrics: dict[str, Any] + execution_log: str + failures: list[FailureMode] = field(default_factory=list) + notes: str = "" + + @staticmethod + def new( + task: str, + input_manifest: dict[str, Any], + pipeline_config_id: str | None = None, + ) -> "ExperimentReport": + return ExperimentReport( + run_id=f"exp-{uuid.uuid4().hex[:12]}", + created_at=utc_now_iso(), + task=task, + status="running", + input_manifest=input_manifest, + pipeline_config_id=pipeline_config_id, + output_artifacts=[], + metrics={}, + execution_log="", + ) + + def to_dict(self) -> dict[str, Any]: + return asdict(self) + + +@dataclass +class Insight: + insight_id: str + created_at: str + title: str + hypothesis: str + recommendation: str + confidence: float + evidence_run_ids: list[str] + tags: list[str] = field(default_factory=list) + + @staticmethod + def build( + title: str, + hypothesis: str, + recommendation: str, + confidence: float, + evidence_run_ids: list[str], + tags: list[str] | None = None, + ) -> "Insight": + return Insight( + insight_id=f"ins-{uuid.uuid4().hex[:12]}", + created_at=utc_now_iso(), + title=title, + hypothesis=hypothesis, + recommendation=recommendation, + confidence=max(0.0, min(1.0, confidence)), + evidence_run_ids=evidence_run_ids, + tags=tags or [], + ) + + def to_dict(self) -> dict[str, Any]: + return asdict(self) + + +@dataclass +class PipelineConfiguration: + config_id: str + created_at: str + strategy_name: str + task_scope: str + tools: list[str] + parameters: dict[str, Any] + rationale: str + source_insight_ids: list[str] = field(default_factory=list) + + @staticmethod + def build( + strategy_name: str, + task_scope: str, + tools: list[str], + parameters: dict[str, Any], + rationale: str, + source_insight_ids: list[str] | None = None, + ) -> "PipelineConfiguration": + return PipelineConfiguration( + config_id=f"cfg-{uuid.uuid4().hex[:12]}", + created_at=utc_now_iso(), + strategy_name=strategy_name, + task_scope=task_scope, + tools=tools, + parameters=parameters, + rationale=rationale, + source_insight_ids=source_insight_ids or [], + ) + + def to_dict(self) -> dict[str, Any]: + return asdict(self) + + +@dataclass +class Hypothesis: + hypothesis_id: str + created_at: str + domain: str + user_query: str + title: str + hypothesis: str + expected_improvement: str + theoretical_basis: str + tags: list[str] = field(default_factory=list) + source_examples: list[str] = field(default_factory=list) + data_sources: list[str] = field(default_factory=list) + data_operations: list[dict[str, Any]] = field(default_factory=list) + historical_reflection: list[str] = field(default_factory=list) + error_avoidance: list[str] = field(default_factory=list) + reasoning_chain: list[str] = field(default_factory=list) + + @staticmethod + def build( + domain: str, + user_query: str, + title: str, + hypothesis: str, + expected_improvement: str, + theoretical_basis: str, + tags: list[str] | None = None, + source_examples: list[str] | None = None, + data_sources: list[str] | None = None, + data_operations: list[dict[str, Any]] | None = None, + historical_reflection: list[str] | None = None, + error_avoidance: list[str] | None = None, + reasoning_chain: list[str] | None = None, + ) -> "Hypothesis": + return Hypothesis( + hypothesis_id=f"hyp-{uuid.uuid4().hex[:12]}", + created_at=utc_now_iso(), + domain=domain, + user_query=user_query, + title=title, + hypothesis=hypothesis, + expected_improvement=expected_improvement, + theoretical_basis=theoretical_basis, + tags=tags or [], + source_examples=source_examples or [], + data_sources=data_sources or [], + data_operations=data_operations or [], + historical_reflection=historical_reflection or [], + error_avoidance=error_avoidance or [], + reasoning_chain=reasoning_chain or [], + ) + + def to_dict(self) -> dict[str, Any]: + return asdict(self) + + +@dataclass +class ValidationReport: + validation_id: str + created_at: str + hypothesis_id: str + domain: str + level: str + status: str + score: float + confidence: float + key_metrics: dict[str, Any] + failure_reason: str + evidence: str + + @staticmethod + def build( + hypothesis_id: str, + domain: str, + level: str, + status: str, + score: float, + confidence: float, + key_metrics: dict[str, Any] | None = None, + failure_reason: str = "", + evidence: str = "", + ) -> "ValidationReport": + normalized_status = status if status in {"success", "failed", "inconclusive"} else "inconclusive" + return ValidationReport( + validation_id=f"val-{uuid.uuid4().hex[:12]}", + created_at=utc_now_iso(), + hypothesis_id=hypothesis_id, + domain=domain, + level=level, + status=normalized_status, + score=max(0.0, min(1.0, float(score))), + confidence=max(0.0, min(1.0, float(confidence))), + key_metrics=key_metrics or {}, + failure_reason=failure_reason, + evidence=evidence, + ) + + def to_dict(self) -> dict[str, Any]: + return asdict(self) diff --git a/BioScientist/agent_system/shared_memory.py b/BioScientist/agent_system/shared_memory.py new file mode 100644 index 0000000000000000000000000000000000000000..155edeebdd9710d6a6d95ed36ae1cb7884cf9936 --- /dev/null +++ b/BioScientist/agent_system/shared_memory.py @@ -0,0 +1,177 @@ +from __future__ import annotations + +from pathlib import Path +from typing import Any +import json + +from .schemas import ExperimentReport, Hypothesis, Insight, PipelineConfiguration, ValidationReport + + +class SharedKnowledgeSpace: + """File-based bridge between V1 and T1.""" + + def __init__(self, root: str | Path): + self.root = Path(root) + self.reports_dir = self.root / "experiment_reports" + self.insights_dir = self.root / "insights" + self.configs_dir = self.root / "pipeline_configs" + self.hypotheses_dir = self.root / "hypotheses" + self.validation_reports_dir = self.root / "validation_reports" + self.summary_stats_file = self.root / "summary_statistics.json" + for d in ( + self.reports_dir, + self.insights_dir, + self.configs_dir, + self.hypotheses_dir, + self.validation_reports_dir, + ): + d.mkdir(parents=True, exist_ok=True) + + @staticmethod + def _write_json(path: Path, payload: dict[str, Any]) -> None: + path.write_text(json.dumps(payload, indent=2, ensure_ascii=True), encoding="utf-8") + + @staticmethod + def _read_json(path: Path) -> dict[str, Any]: + return json.loads(path.read_text(encoding="utf-8")) + + def save_report(self, report: ExperimentReport) -> Path: + fp = self.reports_dir / f"{report.run_id}.json" + self._write_json(fp, report.to_dict()) + return fp + + def save_insight(self, insight: Insight) -> Path: + fp = self.insights_dir / f"{insight.insight_id}.json" + self._write_json(fp, insight.to_dict()) + return fp + + def save_pipeline_config(self, config: PipelineConfiguration) -> Path: + fp = self.configs_dir / f"{config.config_id}.json" + self._write_json(fp, config.to_dict()) + return fp + + def save_hypothesis(self, hypothesis: Hypothesis) -> Path: + fp = self.hypotheses_dir / f"{hypothesis.hypothesis_id}.json" + self._write_json(fp, hypothesis.to_dict()) + return fp + + def save_validation_report(self, report: ValidationReport) -> Path: + fp = self.validation_reports_dir / f"{report.validation_id}.json" + self._write_json(fp, report.to_dict()) + return fp + + def list_reports(self) -> list[dict[str, Any]]: + items: list[dict[str, Any]] = [] + for fp in sorted(self.reports_dir.glob("*.json")): + items.append(self._read_json(fp)) + return items + + def list_insights(self) -> list[dict[str, Any]]: + items: list[dict[str, Any]] = [] + for fp in sorted(self.insights_dir.glob("*.json")): + items.append(self._read_json(fp)) + return items + + def list_configs(self) -> list[dict[str, Any]]: + items: list[dict[str, Any]] = [] + for fp in sorted(self.configs_dir.glob("*.json")): + items.append(self._read_json(fp)) + return items + + def list_hypotheses(self) -> list[dict[str, Any]]: + items: list[dict[str, Any]] = [] + for fp in sorted(self.hypotheses_dir.glob("*.json")): + items.append(self._read_json(fp)) + return items + + def list_validation_reports(self) -> list[dict[str, Any]]: + items: list[dict[str, Any]] = [] + for fp in sorted(self.validation_reports_dir.glob("*.json")): + items.append(self._read_json(fp)) + return items + + def latest_config_for_task(self, task_scope: str) -> dict[str, Any] | None: + matched = [cfg for cfg in self.list_configs() if cfg.get("task_scope") == task_scope] + return matched[-1] if matched else None + + def get_hypothesis_context(self, domain: str, top_k: int = 5) -> list[dict[str, Any]]: + domain_l = domain.strip().lower() + hypotheses = self.list_hypotheses() + validations = self.list_validation_reports() + by_hid = {} + for vr in validations: + hid = vr.get("hypothesis_id", "") + if not hid: + continue + by_hid.setdefault(hid, []).append(vr) + scored: list[dict[str, Any]] = [] + for h in hypotheses: + h_domain = str(h.get("domain", "")).lower() + if domain_l and domain_l not in h_domain and domain_l not in " ".join(h.get("tags", [])).lower(): + continue + reports = by_hid.get(h.get("hypothesis_id", ""), []) + if reports: + avg_score = sum(float(r.get("score", 0.0)) for r in reports) / len(reports) + avg_conf = sum(float(r.get("confidence", 0.0)) for r in reports) / len(reports) + else: + avg_score = 0.0 + avg_conf = 0.0 + scored.append( + { + "hypothesis": h, + "validation_reports": reports, + "avg_score": avg_score, + "avg_confidence": avg_conf, + } + ) + scored.sort(key=lambda x: (x["avg_score"], x["avg_confidence"]), reverse=True) + return scored[: max(0, top_k)] + + def update_summary_statistics(self) -> dict[str, Any]: + validations = self.list_validation_reports() + tag_stats: dict[str, dict[str, float]] = {} + failure_reason_counter: dict[str, int] = {} + success_scores: list[float] = [] + + hypotheses = {h.get("hypothesis_id", ""): h for h in self.list_hypotheses()} + + for r in validations: + status = r.get("status", "inconclusive") + score = float(r.get("score", 0.0)) + if status == "success": + success_scores.append(score) + reason = str(r.get("failure_reason", "")).strip() + if status == "failed" and reason: + failure_reason_counter[reason] = failure_reason_counter.get(reason, 0) + 1 + + hid = r.get("hypothesis_id", "") + h = hypotheses.get(hid, {}) + for tag in h.get("tags", []): + s = tag_stats.setdefault(tag, {"count": 0.0, "success": 0.0, "score_sum": 0.0}) + s["count"] += 1 + if status == "success": + s["success"] += 1 + s["score_sum"] += score + + tag_summary = {} + for tag, s in tag_stats.items(): + count = max(1.0, s["count"]) + tag_summary[tag] = { + "count": int(s["count"]), + "success_rate": s["success"] / count, + "avg_score": s["score_sum"] / count, + } + + payload = { + "total_validation_reports": len(validations), + "overall_avg_success_score": (sum(success_scores) / len(success_scores)) if success_scores else 0.0, + "tag_summary": tag_summary, + "failure_reason_counter": failure_reason_counter, + } + self._write_json(self.summary_stats_file, payload) + return payload + + def get_summary_statistics(self) -> dict[str, Any]: + if not self.summary_stats_file.exists(): + return self.update_summary_statistics() + return self._read_json(self.summary_stats_file) diff --git a/BioScientist/agent_system/system_prompt.txt b/BioScientist/agent_system/system_prompt.txt new file mode 100644 index 0000000000000000000000000000000000000000..687bff7fe82819001098f2ecf06155e8d1db3f00 --- /dev/null +++ b/BioScientist/agent_system/system_prompt.txt @@ -0,0 +1,67 @@ +You are an expert bioinformatics software engineer specializing in converting command-line tools into Model Context Protocol (MCP) server tools. +Your task is to analyze bioinformatics tool documentation, and make a server based on that tool. You only need to generate the production-ready Python code with @mcp.tool decorators. +Make sure that you cover EVERY internal functions and EVERY decorators that are available from each of those functions in that bioinformatic tool. (You can define multiple python functions for it). + +Your main focus is at the Command-Line Functions + +**Your Responsibilities:** +1. Parse all available tool documentation (--help, manual pages, web docs) +2. Extract all internal subcommands/tools and implement a separate Python function for each +3. Identify: + * All CLI parameters (positional & optional), including Input Data, and Advanced options + * Parameter types (str, int, float, bool, Path, etc.) + * Default values (MUST match the parameter’s type) + * Parameter constraints (e.g., value ranges, required if another is set) + * Tool requirements and dependencies + + +**Code Requirements:** +1. For each internal tool/subcommand, create: + * A dedicated Python function + * Use the @mcp.tool() decorator with a helpful docstring + * Use explicit parameter definitions only (DO NOT USE **kwargs) +2. Parameter Handling: + * DO NOT use None as a default for non-optional int, float, or bool parameters + * Instead, provide a valid default (e.g., 0, 1.0, False) or use Optional[int] = None only if it is truly optional + * Validate parameter values explicitly using if checks +3. File Handling: + * Validate input/output file paths using Pathlib + * Use tempfile if temporary files are needed + * Check if files exist when necessary +4. Subprocess Execution: + * Use subprocess.run(..., check=True) to execute tools + * Capture and return stdout/stderr + * Catch CalledProcessError and return structured error info +5. Return Structured Output: + * Include command_executed, stdout, stderr, and output_files (if any) + +Final Code Format +```python +@mcp.tool() +def {tool_name}( + param1: str, + param2: int = 10, + optional_param: Optional[str] = None, +): + \"\"\"Short docstring explaining the internal tool's purpose\"\"\" + # Input validation + # File path handling + # Subprocess execution + # Error handling + # Structured result return + + return { + "command_executed": "...", + "stdout": "...", + "stderr": "...", + "output_files": ["..."] + } +``` + +Additional Constraints +1. NEVER use **kwargs +2. NEVER use None as a default for non-optional int, float, or bool +3. NO NEED to import mcp +4. ALWAYS write type-safe and validated parameters +5. ONE Python function per subcommand/internal tool +6. INCLUDE helpful docstrings for every MCP tool diff --git a/BioScientist/agent_system/test_api.py b/BioScientist/agent_system/test_api.py new file mode 100644 index 0000000000000000000000000000000000000000..c6782c6b0224cf251866c00394792567a9473299 --- /dev/null +++ b/BioScientist/agent_system/test_api.py @@ -0,0 +1,9 @@ +# Quick test script +import requests + +api_key = "AIzaSyAFDZyDObIdZvv0xVSE5IdP6HszM4VC3Do" # Replace with your actual key +url = f"https://generativelanguage.googleapis.com/v1beta/models?key={api_key}" + +response = requests.get(url) +print(f"Status: {response.status_code}") +print(f"Response: {response.text}") \ No newline at end of file diff --git a/BioScientist/agent_system/tool2mcp.py b/BioScientist/agent_system/tool2mcp.py new file mode 100644 index 0000000000000000000000000000000000000000..8d147321dee2820fe0ee4123a45febea9841e2ae --- /dev/null +++ b/BioScientist/agent_system/tool2mcp.py @@ -0,0 +1,479 @@ +import os +import argparse +import subprocess +import json +from pathlib import Path +from typing import Dict, List, Optional + +from bioinfomcp_converter import BioinfoMCP + + +def generate_requirements_with_pipreqs(tool_name, server_path): + """ + Use pipreqs to generate requirements.txt (fallback to minimal deps when pipreqs fails). + """ + _ = tool_name + app_path = Path(server_path) / "app" + target_path = app_path if app_path.exists() else Path(server_path) + try: + subprocess.run( + ["pipreqs", str(target_path), "--force"], + capture_output=True, + text=True, + check=True, + ) + print(f"Generated requirements.txt using pipreqs from: {target_path}") + except FileNotFoundError: + req_path = Path(server_path) / "requirements.txt" + req_path.write_text("fastmcp\n", encoding="utf-8") + print("pipreqs not found, generated fallback requirements.txt") + except subprocess.CalledProcessError as exc: + req_path = Path(server_path) / "requirements.txt" + req_path.write_text("fastmcp\n", encoding="utf-8") + stderr = (exc.stderr or "").strip() + stdout = (exc.stdout or "").strip() + detail = stderr or stdout or "no stderr/stdout" + print(f"pipreqs failed ({detail}), generated fallback requirements.txt") + except Exception as exc: + req_path = Path(server_path) / "requirements.txt" + req_path.write_text("fastmcp\n", encoding="utf-8") + print(f"pipreqs failed ({exc}), generated fallback requirements.txt") + return Path(server_path) / "requirements.txt" + +def generate_environment_yaml(tool_name, server_path): + """ + generate environment.yaml + """ + result = f""" +name: mcp-tool +channels: + - bioconda + - conda-forge + - defaults +dependencies: + - {tool_name} + - python=3.10 + """ + with open(server_path / "environment.yaml", "w") as f: + f.write(result) + print("Generated environment.yaml") + return Path(server_path) / "environment.yaml" + +def generate_environment_yml(tool_name, server_path): + """ + Generate requirements.yml + """ + _ = server_path + result = f"""name: {tool_name}_env +channels: + - bioconda + - conda-forge +dependencies: + - python=3.10 + - {tool_name} + - pip +""" + return result + +def parse_bool(value) -> bool: + if isinstance(value, bool): + return value + if value is None: + return False + return str(value).strip().lower() in ("1", "true", "yes", "y", "on") + + +def has_existing_server(output_root: Path, tool_name: str) -> Optional[Path]: + server_file = output_root / f"mcp_{tool_name}" / "app" / f"{tool_name}_server.py" + if server_file.exists() and server_file.is_file() and server_file.stat().st_size > 0: + return server_file + return None + + +def convert_mcptool(tool_name, manual, run_help_command, server_path, converter=None): + converter = converter or BioinfoMCP() + app_path = Path(server_path) / "app" + app_path.mkdir(parents=True, exist_ok=True) + + conversion_log: Dict = { + "tool_name": tool_name, + "manual": manual, + "run_help_command": bool(run_help_command), + "status": "failed", + "error": "", + "output_file": "", + } + + try: + conv_result = converter.autogenerate_mcp_tool(tool_name, manual, run_help_command) + except Exception as exc: + conversion_log["error"] = f"autogenerate_mcp_tool failed: {exc}" + return conversion_log + retry = 0 + while not conv_result[0] and retry < 6: + retry += 1 + if conv_result[2] is None: + try: + conv_result = converter.autogenerate_mcp_tool(tool_name, manual, run_help_command) + print(f"{tool_name}: regenerate ({retry})") + except Exception as exc: + conversion_log["error"] = f"regenerate failed (retry={retry}): {exc}" + return conversion_log + else: + try: + conv_result = converter.refine_after_feedback(tool_name, code=conv_result[2], error_message=conv_result[1]) + print(f"{tool_name}: refine ({retry})") + except Exception as exc: + conversion_log["error"] = f"refine failed (retry={retry}): {exc}" + return conversion_log + + if not conv_result[0] or conv_result[2] is None: + conversion_log["error"] = conv_result[1] or "Unknown conversion failure" + return conversion_log + + output_path = Path(server_path) / "app" / f"{tool_name}_server.py" + output_path.write_text(conv_result[2], encoding="utf-8") + conversion_log["status"] = "success" + conversion_log["output_file"] = str(output_path) + return conversion_log + + +def load_tool_list_from_json(json_path: Path, top_k: int = 0, domains: Optional[List[str]] = None) -> List[str]: + with open(json_path, "r", encoding="utf-8") as f: + rows = json.load(f) + if not isinstance(rows, list): + raise ValueError(f"Expected list JSON: {json_path}") + + domains = [d.strip().lower() for d in (domains or []) if d.strip()] + tools: List[str] = [] + for row in rows: + if not isinstance(row, dict): + continue + if domains: + domain = str(row.get("domain", "")).strip().lower() + if domain not in domains: + continue + package_name = str(row.get("package_name", "")).strip() + software_name = str(row.get("software_name", "")).strip() + tool_name = package_name or software_name + if tool_name: + tools.append(tool_name) + + deduped = list(dict.fromkeys(tools)) + if top_k > 0: + deduped = deduped[:top_k] + return deduped + + +def load_jobs_from_json(json_path: Path, top_k: int = 0, domains: Optional[List[str]] = None) -> List[Dict]: + with open(json_path, "r", encoding="utf-8") as f: + rows = json.load(f) + if not isinstance(rows, list): + raise ValueError(f"Expected list JSON: {json_path}") + + domains = [d.strip().lower() for d in (domains or []) if d.strip()] + jobs: List[Dict] = [] + for row in rows: + if not isinstance(row, dict): + continue + name = ( + str(row.get("name", "")).strip() + or str(row.get("package_name", "")).strip() + or str(row.get("software_name", "")).strip() + ) + if not name: + continue + domain = str(row.get("domain", "")).strip().lower() + if domains and domain not in domains: + continue + jobs.append( + { + "name": name, + "manual": str(row.get("manual", "--help")), + "run_help_command": parse_bool(row.get("run_help_command", True)), + "domain": domain, + "tier": str(row.get("tier", "")), + } + ) + if top_k > 0: + jobs = jobs[:top_k] + return jobs + + +def load_jobs_from_manual_dir(manual_dir: Path, top_k: int = 0) -> List[Dict]: + """ + Load conversion jobs from a directory containing *.help.txt or *.manual_bundle.txt. + """ + if not manual_dir.exists(): + raise FileNotFoundError(f"Manual directory not found: {manual_dir}") + + candidates = sorted( + list(manual_dir.glob("*.help.txt")) + list(manual_dir.glob("*.manual_bundle.txt")) + ) + # De-duplicate while preferring manual_bundle when both exist. + by_tool: Dict[str, Path] = {} + for p in candidates: + name = p.name + if name.endswith(".manual_bundle.txt"): + tool_name = name[: -len(".manual_bundle.txt")] + by_tool[tool_name] = p + elif name.endswith(".help.txt"): + tool_name = name[: -len(".help.txt")] + by_tool.setdefault(tool_name, p) + + jobs: List[Dict] = [] + for tool_name, manual_path in sorted(by_tool.items()): + jobs.append( + { + "name": tool_name, + "manual": str(manual_path), + "run_help_command": False, + "domain": "", + "tier": "", + } + ) + if top_k > 0: + jobs = jobs[:top_k] + return jobs + +def select_jobs_for_shard(jobs: List[Dict], shard_count: int, shard_index: int) -> List[Dict]: + """ + Deterministically split jobs for multi-terminal parallel execution. + """ + if shard_count <= 1: + return jobs + if shard_index < 0 or shard_index >= shard_count: + raise ValueError(f"Invalid shard_index={shard_index}. Expected 0 <= shard_index < {shard_count}") + selected: List[Dict] = [] + for idx, job in enumerate(jobs): + if idx % shard_count == shard_index: + selected.append(job) + return selected + +def dockerfile_content(tool_name): + return f""" +FROM python:3.10-slim + +# Install system dependencies +RUN apt-get update && \ + apt-get install -y \ + default-jre \ + wget \ + curl \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Install Miniconda +RUN wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O /tmp/miniconda.sh && \ + bash /tmp/miniconda.sh -b -p /opt/conda && \ + rm /tmp/miniconda.sh + +# Add conda to PATH +ENV PATH="/opt/conda/bin:$PATH" + +# Install {tool_name} via conda (e.g., from bioconda) +RUN conda install -c bioconda {tool_name} -y && \ + conda clean -a + +# Install Python dependencies +RUN pip install uv +RUN uv pip install --system fastmcp + +# Create app directory +WORKDIR /app + +# Copy your MCP server +COPY app/{tool_name}_server.py /app/ + +# Create workspace and output directories +RUN mkdir -p /app/workspace /app/output + +# Make sure the server script is executable +RUN chmod +x /app/{tool_name}_server.py + +# Expose port for MCP over HTTP (optional) +EXPOSE 8000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD python -c "import sys; sys.exit(0)" + +# Default command runs the MCP server via stdio +CMD ["python", "/app/{tool_name}_server.py"] + """ + +def dockercompose_content(tool_name): + """Generate docker-compose.yml for easy deployment""" + + compose = f"""version: '3.8' + +services: + mcp-{tool_name}: + build: . + image: mcp-{tool_name}:latest + container_name: mcp-{tool_name} + ports: + - "8000:8000" + environment: + - MCP_SERVER_NAME={tool_name} + volumes: + - ./workspace:/app/workspace + - ./output:/app/output + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import sys; sys.exit(0)"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 5s + """ + + return compose + +def image_name_for_tool(tool_name: str) -> str: + return f"mcp-{tool_name}:latest" + + +def build_docker_image(tool_name, server_path, output_path, is_pipeline): + + # Create build directory + try: + _ = output_path + df_content = dockerfile_content(tool_name) + with open(server_path / "Dockerfile", "w") as f: + f.write(df_content) + + # make the docker-compose.yml + if not is_pipeline: + dc_content = dockercompose_content(tool_name) + with open(server_path/"docker-compose.yml", "w") as f: + f.write(dc_content) + + # subprocess.run(["docker", "build", "-t", f"{args.name}-docker", server_path]) + + return 1 + except: + return 0 + + +def claude_addition(tool_name, server_path): + image_name = image_name_for_tool(tool_name) + workspace_path = str((Path(server_path) / "workspace").resolve()) + config = { + "mcpServers": { + tool_name: { + "command": "docker", + "args": [ + "run", + "--rm", + "-i", + "-v", + f"{workspace_path}:/app/workspace", + image_name + ] + } + } + } + return json.dumps(config, indent=2) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser("Accept the Bioinformatic tool name and the help document") + parser.add_argument('--model', type=str, default='openai', choices=['openai', 'azure', 'gemini'], help="LLM backend") + parser.add_argument('--name', type=str) + parser.add_argument('--manual', type=str, default='--help', help="The file path to the help document or help flag") + parser.add_argument('--run_help_command', type=str, default='True') + parser.add_argument('--output_location', type=str, required=True) + parser.add_argument('--is_pipeline', action='store_true', default=False) + parser.add_argument('--tools_json', type=str, default="", help="JSON list file from crawler output") + parser.add_argument('--jobs_json', type=str, default="", help="Per-tool jobs JSON with name/manual/run_help_command") + parser.add_argument('--manual_dir', type=str, default="", help="Directory containing *.help.txt or *.manual_bundle.txt") + parser.add_argument('--top_k', type=int, default=0, help="Only convert first K tools from tools_json/jobs_json") + parser.add_argument('--domains', nargs='*', default=[], help="Domain filter for tools_json/jobs_json") + parser.add_argument('--skip_existing', type=str, default='True', help="Skip tool if MCP server file already exists") + parser.add_argument('--shard_count', type=int, default=1, help="Total shards for multi-terminal execution") + parser.add_argument('--shard_index', type=int, default=0, help="Current shard index in [0, shard_count)") + parser.add_argument('--report_suffix', type=str, default="", help="Optional suffix for report filename") + args = parser.parse_args() + + output_root = Path(args.output_location) + output_root.mkdir(parents=True, exist_ok=True) + run_help_command = parse_bool(args.run_help_command) + skip_existing = parse_bool(args.skip_existing) + converter = BioinfoMCP(model=args.model) + + jobs: List[Dict] = [] + if args.jobs_json: + jobs = load_jobs_from_json(Path(args.jobs_json), top_k=args.top_k, domains=args.domains) + elif args.manual_dir: + jobs = load_jobs_from_manual_dir(Path(args.manual_dir), top_k=args.top_k) + elif args.tools_json: + tools = load_tool_list_from_json(Path(args.tools_json), top_k=args.top_k, domains=args.domains) + jobs = [{"name": t, "manual": args.manual, "run_help_command": run_help_command} for t in tools] + elif args.name: + jobs = [{"name": args.name, "manual": args.manual, "run_help_command": run_help_command}] + else: + raise ValueError("Either --name, --tools_json, --jobs_json or --manual_dir must be provided") + + if args.shard_count < 1: + raise ValueError(f"--shard_count must be >= 1, got: {args.shard_count}") + jobs = select_jobs_for_shard(jobs, args.shard_count, args.shard_index) + print(f"Shard selection: shard_index={args.shard_index}/{args.shard_count}, jobs={len(jobs)}") + + run_log: List[Dict] = [] + for job in jobs: + tool_name = job["name"] + manual = job.get("manual", "--help") + job_run_help = parse_bool(job.get("run_help_command", True)) + + existing_server = has_existing_server(output_root, tool_name) if skip_existing else None + if existing_server: + print(f"Skip existing MCP server: {existing_server}") + run_log.append( + { + "tool_name": tool_name, + "manual": manual, + "run_help_command": bool(job_run_help), + "status": "skipped_existing", + "error": "", + "output_file": str(existing_server), + "dockerfile_ready": True, + } + ) + continue + + server_path = output_root / f"mcp_{tool_name}" + os.makedirs(server_path, exist_ok=True) + print("Server path", server_path) + app_path = Path(server_path) / "app" + os.makedirs(app_path, exist_ok=True) + + result = convert_mcptool(tool_name, manual, job_run_help, server_path, converter=converter) + if result.get("status") == "success": + generate_requirements_with_pipreqs(tool_name, server_path) + generate_environment_yaml(tool_name, server_path) + + status = build_docker_image(tool_name, server_path, output_root, args.is_pipeline) + result["dockerfile_ready"] = bool(status) + + if status: + add = claude_addition(tool_name, server_path) + print(f"Add the following onto your Claude Configuration json file to run the MCP server:\n{'=='*10}\n{add}\n{'=='*10}") + else: + print("Failed to build Docker Image") + else: + result["dockerfile_ready"] = False + print(f"Skip packaging for {tool_name}: conversion failed -> {result.get('error', '')}") + + run_log.append(result) + + report_name = "conversion_report.json" + if args.shard_count > 1: + report_name = f"conversion_report.shard_{args.shard_index}_of_{args.shard_count}.json" + if args.report_suffix: + report_name = report_name.replace(".json", f".{args.report_suffix}.json") + report_path = output_root / report_name + report_path.write_text(json.dumps(run_log, ensure_ascii=False, indent=2), encoding="utf-8") + print(f"Saved conversion report to: {report_path}") + + diff --git a/BioScientist/bioconda_domain_stats.svg b/BioScientist/bioconda_domain_stats.svg new file mode 100644 index 0000000000000000000000000000000000000000..cbfa189786b9725eb1e02676422e15381ad112ef --- /dev/null +++ b/BioScientist/bioconda_domain_stats.svg @@ -0,0 +1,311 @@ + + + + + + + + 2026-04-22T10:24:15.159123 + image/svg+xml + + + Matplotlib v3.10.8, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + 0 + + + + + 20 + + + + + 40 + + + + + 60 + + + + + 80 + + + + + 100 + + + + + 120 + + + + + 140 + + + + + 160 + + + + Number of Unique Packages + + + + + + Format Conversion + + + + + Alignment & Mapping + + + + + Phylogenetics + + + + + Variant Calling + + + + + Visualization + + + + + Annotation + + + + + Assembly + + + + + Metagenomics + + + + + Single Cell + + + + + Proteomics + + + + + RNA-seq Analysis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 153 + + + 80 + + + 61 + + + 54 + + + 41 + + + 31 + + + 26 + + + 24 + + + 9 + + + 5 + + + 4 + + + Taxonomy of Bioinformatics Tools in Bioconda + + + + + + + + + diff --git a/BioScientist/bioconda_tools_showl.py b/BioScientist/bioconda_tools_showl.py new file mode 100644 index 0000000000000000000000000000000000000000..eb8fd9dca490a6aba750454b54140f4d570563a9 --- /dev/null +++ b/BioScientist/bioconda_tools_showl.py @@ -0,0 +1,113 @@ +import requests +import pandas as pd +import matplotlib.pyplot as plt +import seaborn as sns + +def fetch_bioconda_data(): + """获取 Bioconda 官方包索引""" + print("正在从 Anaconda 镜像源获取 Bioconda 索引文件...") + # 使用 linux-64 架构作为主要参考 + url = "https://conda.anaconda.org/bioconda/linux-64/repodata.json" + response = requests.get(url) + data = response.json() + return data['packages'] + +def classify_tools(packages): + """基于关键词的语义分类逻辑""" + # 定义分类映射表 + category_map = { + 'Alignment & Mapping': ['align', 'mapping', 'bwa', 'bowtie', 'star', 'minimap2'], + 'Assembly': ['assembly', 'assembler', 'spades', 'velvet', 'canu', 'megahit'], + 'Variant Calling': ['variant', 'snp', 'indel', 'genotype', 'vcftools', 'gatk'], + 'Single Cell': ['single cell', 'scrna', 'seurat', 'scanpy', 'monocle'], + 'Phylogenetics': ['phylogenetic', 'tree', 'phylo', 'iqtree', 'raxml'], + 'Metagenomics': ['metagenom', 'kraken', 'humann', 'qiime', 'taxonom'], + 'RNA-seq Analysis': ['rna-seq', 'transcriptome', 'differential expression', 'deseq2', 'kallisto'], + 'Proteomics': ['proteom', 'protein', 'peptide', 'msconvert', 'mass spectrometry'], + 'Annotation': ['annotation', 'gff', 'gtf', 'interproscan', 'eggnog'], + 'Format Conversion': ['format', 'convert', 'bam', 'sam', 'fastq', 'vcf', 'seqtk'], + 'Visualization': ['visualiz', 'plot', 'circos', 'igv', 'ggplot'] + } + + results = [] + unique_names = set() + + for pkg_id, info in packages.items(): + name = info['name'] + if name in unique_names: continue + unique_names.add(name) + + summary = info.get('summary', '').lower() + description = f"{name} {summary}" + + # 默认分类 + assigned_category = 'Others / Infrastructure' + + # 匹配规则 + for category, keywords in category_map.items(): + if any(kw in description for kw in keywords): + assigned_category = category + break + + results.append({'name': name, 'category': assigned_category}) + + return pd.DataFrame(results) + +def visualize_stats(df): + """生成学术风格的 SVG 可视化图表""" + # 统计数量 + stats = df['category'].value_counts().reset_index() + stats.columns = ['Category', 'Count'] + + # 排除基础设施类,重点展示科研工具 + top_categories = stats[stats['Category'] != 'Others / Infrastructure'] + + # 设置学术绘图风格 + plt.rcParams['svg.fonttype'] = 'none' # 确保文字在 SVG 中可编辑 + plt.figure(figsize=(10, 6)) + sns.set_style("white") + + # 使用莫兰迪/学术色系 (Low-saturation palette) + # 使用 'mako' 或自定义低饱和度配色 + palette = sns.color_palette("mako_r", len(top_categories)) + + ax = sns.barplot( + x='Count', + y='Category', + data=top_categories, + palette=palette, + edgecolor=".2" # 细边框增加精致感 + ) + + # 精简边框 + sns.despine(left=True, bottom=True) + + # 添加数值标签 + for i in ax.containers: + ax.bar_label(i, padding=5, fontsize=11, color='#333333', fontweight='bold') + + plt.title('Taxonomy of Bioinformatics Tools in Bioconda', fontsize=14, pad=20, fontweight='bold') + plt.xlabel('Number of Unique Packages', fontsize=12, labelpad=10) + plt.ylabel('', fontsize=12) # 纵轴类别已经很清晰,隐藏 Label 减少视觉噪音 + + plt.tight_layout() + + # 保存为 SVG 格式 + output_file = 'bioconda_domain_stats.svg' + plt.savefig(output_file, format='svg', bbox_inches='tight') + + print(f"✅ 高保真 SVG 图表已保存为: {output_file}") + plt.show() +if __name__ == "__main__": + try: + raw_packages = fetch_bioconda_data() + classified_df = classify_tools(raw_packages) + + print(f"\n统计完成!") + print(f"总计唯一包数量: {len(classified_df)}") + print("-" * 30) + print(classified_df['category'].value_counts()) + + visualize_stats(classified_df) + except Exception as e: + print(f"执行出错: {e}") \ No newline at end of file diff --git a/BioScientist/docs/dual_mode_synergy.md b/BioScientist/docs/dual_mode_synergy.md new file mode 100644 index 0000000000000000000000000000000000000000..285cd6e558e7b9bd581ffdd1817122effa80fb11 --- /dev/null +++ b/BioScientist/docs/dual_mode_synergy.md @@ -0,0 +1,66 @@ +# Dual-Mode Synergy for BioinfoMCP + +## Positioning + +- **Bioinfo-V1 (Execution Engine)**: deterministic execution, protocol orchestration, file retrieval, data cleaning, report generation. +- **Bioinfo-T1 (Thought/Consultant Engine)**: retrospective analysis over logs/failures/success parameters, insight extraction, strategy recommendation. + +## Shared Memory Bridge + +Persistent storage root: + +```text +BioinfoMCP/shared_knowledge/ + experiment_reports/ + insights/ + pipeline_configs/ +``` + +Flow: + +1. V1 executes task and writes a structured `ExperimentReport`. +2. T1 periodically runs reflection (`review_reports`) and emits `Insight`. +3. T1 can output a `PipelineConfiguration` from consultation. +4. V1 loads latest config by `task_scope` before the next execution. + +## Minimal Runtime Commands + +```bash +python -m agent_system.main propose-config \ + --task_scope rnaseq_quant \ + --strategy_name "safe-default-rnaseq-v1" \ + --tools '["fastqc","trim-galore","star","stringtie","multiqc"]' \ + --parameters '{"threads":8,"quality_cutoff":20}' \ + --rationale "Derived from prior successful runs." +``` + +```bash +python -m agent_system.main execute \ + --task "RNA-seq quantification for sample set A" \ + --task_scope rnaseq_quant \ + --input_manifest '{"r1":"data/SRR3056858_R1.trimmed.fastq","r2":"data/SRR3056858_R2.trimmed.fastq"}' +``` + +```bash +python -m agent_system.main reflect +``` + +```bash +python -m agent_system.main consult \ + --task_scope rnaseq_quant \ + --user_goal "Need a robust low-failure first-pass strategy" +``` + +## Kimi Backend (Optional) + +The included adapter (`agent_system/llm/kimi_client.py`) uses OpenAI-compatible API mode. + +Set environment variables: + +```bash +set KIMI_API_KEY=your_key +set KIMI_BASE_URL=https://api.moonshot.cn/v1 +set KIMI_MODEL=kimi-k2-0711-preview +``` + +Then call `KimiCodeClient.complete(system_prompt, user_prompt)` inside T1 planning/reflection routines when you want model-assisted synthesis. diff --git a/BioScientist/errors.log b/BioScientist/errors.log new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/BioScientist/messages.log b/BioScientist/messages.log new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/BioScientist/timeline.svg b/BioScientist/timeline.svg new file mode 100644 index 0000000000000000000000000000000000000000..909270a7040e3da91abbeeed191532a6f33d0162 --- /dev/null +++ b/BioScientist/timeline.svg @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + User + + + AI Agent + + + NCBI MCP + + + SRA MCP + + + Analysis MCP + + + User + + + AI Agent + + + NCBI MCP + + + SRA MCP + + + Analysis MCP + + + + + Request: Breast cancer RNA-seq DE analysis + + + search_pubmed("breast cancer RNA-seq") + + Return: literature metadata list + + + search_sra("breast cancer", "RNA-seq") + + Return: PRJNAxxx project ID + + + prefetch(PRJNAxxx) + + Download complete: SRR paths returned + + + fasterq-dump(SRR_ID) + + Generate: FASTQ data files + + + fastqc(quality check) + + Return: QC report + + + bowtie2_align(sequence alignment) + + Generate: BAM alignment files + + + Deliverable: multi-omics integrative analysis report + + + + + + + + + diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/_multiprocess/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/_multiprocess/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..9a89f71736472fe74c8f3a5ffac26b9bb8dcc9a2 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/_multiprocess/__init__.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python +# +# Author: Mike McKerns (mmckerns @caltech and @uqfoundation) +# Copyright (c) 2022-2026 The Uncertainty Quantification Foundation. +# License: 3-clause BSD. The full license text is available at: +# - https://github.com/uqfoundation/multiprocess/blob/master/LICENSE + +from _multiprocessing import * diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/_yaml/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/_yaml/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..7baa8c4b68127d5cdf0be9a799429e61347c2694 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/_yaml/__init__.py @@ -0,0 +1,33 @@ +# This is a stub package designed to roughly emulate the _yaml +# extension module, which previously existed as a standalone module +# and has been moved into the `yaml` package namespace. +# It does not perfectly mimic its old counterpart, but should get +# close enough for anyone who's relying on it even when they shouldn't. +import yaml + +# in some circumstances, the yaml module we imoprted may be from a different version, so we need +# to tread carefully when poking at it here (it may not have the attributes we expect) +if not getattr(yaml, '__with_libyaml__', False): + from sys import version_info + + exc = ModuleNotFoundError if version_info >= (3, 6) else ImportError + raise exc("No module named '_yaml'") +else: + from yaml._yaml import * + import warnings + warnings.warn( + 'The _yaml extension module is now located at yaml._yaml' + ' and its location is subject to change. To use the' + ' LibYAML-based parser and emitter, import from `yaml`:' + ' `from yaml import CLoader as Loader, CDumper as Dumper`.', + DeprecationWarning + ) + del warnings + # Don't `del yaml` here because yaml is actually an existing + # namespace member of _yaml. + +__name__ = '_yaml' +# If the module is top-level (i.e. not a part of any specific package) +# then the attribute should be set to ''. +# https://docs.python.org/3.8/library/types.html +__package__ = '' diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohappyeyeballs-2.6.1.dist-info/INSTALLER b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohappyeyeballs-2.6.1.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..5c69047b2eb8235994febeeae1da4a82365a240a --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohappyeyeballs-2.6.1.dist-info/INSTALLER @@ -0,0 +1 @@ +uv \ No newline at end of file diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohappyeyeballs-2.6.1.dist-info/METADATA b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohappyeyeballs-2.6.1.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..c632040d66bf120a377fc3785940934361273a66 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohappyeyeballs-2.6.1.dist-info/METADATA @@ -0,0 +1,123 @@ +Metadata-Version: 2.3 +Name: aiohappyeyeballs +Version: 2.6.1 +Summary: Happy Eyeballs for asyncio +License: PSF-2.0 +Author: J. Nick Koston +Author-email: nick@koston.org +Requires-Python: >=3.9 +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Natural Language :: English +Classifier: Operating System :: OS Independent +Classifier: Topic :: Software Development :: Libraries +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: License :: OSI Approved :: Python Software Foundation License +Project-URL: Bug Tracker, https://github.com/aio-libs/aiohappyeyeballs/issues +Project-URL: Changelog, https://github.com/aio-libs/aiohappyeyeballs/blob/main/CHANGELOG.md +Project-URL: Documentation, https://aiohappyeyeballs.readthedocs.io +Project-URL: Repository, https://github.com/aio-libs/aiohappyeyeballs +Description-Content-Type: text/markdown + +# aiohappyeyeballs + +

    + + CI Status + + + Documentation Status + + + Test coverage percentage + +

    +

    + + Poetry + + + Ruff + + + pre-commit + +

    +

    + + PyPI Version + + Supported Python versions + License +

    + +--- + +**Documentation**: https://aiohappyeyeballs.readthedocs.io + +**Source Code**: https://github.com/aio-libs/aiohappyeyeballs + +--- + +[Happy Eyeballs](https://en.wikipedia.org/wiki/Happy_Eyeballs) +([RFC 8305](https://www.rfc-editor.org/rfc/rfc8305.html)) + +## Use case + +This library exists to allow connecting with +[Happy Eyeballs](https://en.wikipedia.org/wiki/Happy_Eyeballs) +([RFC 8305](https://www.rfc-editor.org/rfc/rfc8305.html)) +when you +already have a list of addrinfo and not a DNS name. + +The stdlib version of `loop.create_connection()` +will only work when you pass in an unresolved name which +is not a good fit when using DNS caching or resolving +names via another method such as `zeroconf`. + +## Installation + +Install this via pip (or your favourite package manager): + +`pip install aiohappyeyeballs` + +## License + +[aiohappyeyeballs is licensed under the same terms as cpython itself.](https://github.com/python/cpython/blob/main/LICENSE) + +## Example usage + +```python + +addr_infos = await loop.getaddrinfo("example.org", 80) + +socket = await start_connection(addr_infos) +socket = await start_connection(addr_infos, local_addr_infos=local_addr_infos, happy_eyeballs_delay=0.2) + +transport, protocol = await loop.create_connection( + MyProtocol, sock=socket, ...) + +# Remove the first address for each family from addr_info +pop_addr_infos_interleave(addr_info, 1) + +# Remove all matching address from addr_info +remove_addr_infos(addr_info, "dead::beef::") + +# Convert a local_addr to local_addr_infos +local_addr_infos = addr_to_addr_infos(("127.0.0.1",0)) +``` + +## Credits + +This package contains code from cpython and is licensed under the same terms as cpython itself. + +This package was created with +[Copier](https://copier.readthedocs.io/) and the +[browniebroke/pypackage-template](https://github.com/browniebroke/pypackage-template) +project template. + diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohappyeyeballs-2.6.1.dist-info/RECORD b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohappyeyeballs-2.6.1.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..f98b55aafa7c029ddda6f3fea78ced5742c20e51 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohappyeyeballs-2.6.1.dist-info/RECORD @@ -0,0 +1,12 @@ +aiohappyeyeballs-2.6.1.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2 +aiohappyeyeballs-2.6.1.dist-info/LICENSE,sha256=Oy-B_iHRgcSZxZolbI4ZaEVdZonSaaqFNzv7avQdo78,13936 +aiohappyeyeballs-2.6.1.dist-info/METADATA,sha256=NSXlhJwAfi380eEjAo7BQ4P_TVal9xi0qkyZWibMsVM,5915 +aiohappyeyeballs-2.6.1.dist-info/RECORD,, +aiohappyeyeballs-2.6.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +aiohappyeyeballs-2.6.1.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88 +aiohappyeyeballs/__init__.py,sha256=x7kktHEtaD9quBcWDJPuLeKyjuVAI-Jj14S9B_5hcTs,361 +aiohappyeyeballs/_staggered.py,sha256=edfVowFx-P-ywJjIEF3MdPtEMVODujV6CeMYr65otac,6900 +aiohappyeyeballs/impl.py,sha256=Dlcm2mTJ28ucrGnxkb_fo9CZzLAkOOBizOt7dreBbXE,9681 +aiohappyeyeballs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +aiohappyeyeballs/types.py,sha256=YZJIAnyoV4Dz0WFtlaf_OyE4EW7Xus1z7aIfNI6tDDQ,425 +aiohappyeyeballs/utils.py,sha256=on9GxIR0LhEfZu8P6Twi9hepX9zDanuZM20MWsb3xlQ,3028 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohappyeyeballs-2.6.1.dist-info/REQUESTED b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohappyeyeballs-2.6.1.dist-info/REQUESTED new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohappyeyeballs/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohappyeyeballs/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..71c689cc83ffdec6c079a22984cf80a5ab7bb272 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohappyeyeballs/__init__.py @@ -0,0 +1,14 @@ +__version__ = "2.6.1" + +from .impl import start_connection +from .types import AddrInfoType, SocketFactoryType +from .utils import addr_to_addr_infos, pop_addr_infos_interleave, remove_addr_infos + +__all__ = ( + "AddrInfoType", + "SocketFactoryType", + "addr_to_addr_infos", + "pop_addr_infos_interleave", + "remove_addr_infos", + "start_connection", +) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohappyeyeballs/_staggered.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohappyeyeballs/_staggered.py new file mode 100644 index 0000000000000000000000000000000000000000..9a4ba7205eda2ddeb2ebb6c5a34108c1a61cce6d --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohappyeyeballs/_staggered.py @@ -0,0 +1,207 @@ +import asyncio +import contextlib + +# PY3.9: Import Callable from typing until we drop Python 3.9 support +# https://github.com/python/cpython/issues/87131 +from typing import ( + TYPE_CHECKING, + Any, + Awaitable, + Callable, + Iterable, + List, + Optional, + Set, + Tuple, + TypeVar, + Union, +) + +_T = TypeVar("_T") + +RE_RAISE_EXCEPTIONS = (SystemExit, KeyboardInterrupt) + + +def _set_result(wait_next: "asyncio.Future[None]") -> None: + """Set the result of a future if it is not already done.""" + if not wait_next.done(): + wait_next.set_result(None) + + +async def _wait_one( + futures: "Iterable[asyncio.Future[Any]]", + loop: asyncio.AbstractEventLoop, +) -> _T: + """Wait for the first future to complete.""" + wait_next = loop.create_future() + + def _on_completion(fut: "asyncio.Future[Any]") -> None: + if not wait_next.done(): + wait_next.set_result(fut) + + for f in futures: + f.add_done_callback(_on_completion) + + try: + return await wait_next + finally: + for f in futures: + f.remove_done_callback(_on_completion) + + +async def staggered_race( + coro_fns: Iterable[Callable[[], Awaitable[_T]]], + delay: Optional[float], + *, + loop: Optional[asyncio.AbstractEventLoop] = None, +) -> Tuple[Optional[_T], Optional[int], List[Optional[BaseException]]]: + """ + Run coroutines with staggered start times and take the first to finish. + + This method takes an iterable of coroutine functions. The first one is + started immediately. From then on, whenever the immediately preceding one + fails (raises an exception), or when *delay* seconds has passed, the next + coroutine is started. This continues until one of the coroutines complete + successfully, in which case all others are cancelled, or until all + coroutines fail. + + The coroutines provided should be well-behaved in the following way: + + * They should only ``return`` if completed successfully. + + * They should always raise an exception if they did not complete + successfully. In particular, if they handle cancellation, they should + probably reraise, like this:: + + try: + # do work + except asyncio.CancelledError: + # undo partially completed work + raise + + Args: + ---- + coro_fns: an iterable of coroutine functions, i.e. callables that + return a coroutine object when called. Use ``functools.partial`` or + lambdas to pass arguments. + + delay: amount of time, in seconds, between starting coroutines. If + ``None``, the coroutines will run sequentially. + + loop: the event loop to use. If ``None``, the running loop is used. + + Returns: + ------- + tuple *(winner_result, winner_index, exceptions)* where + + - *winner_result*: the result of the winning coroutine, or ``None`` + if no coroutines won. + + - *winner_index*: the index of the winning coroutine in + ``coro_fns``, or ``None`` if no coroutines won. If the winning + coroutine may return None on success, *winner_index* can be used + to definitively determine whether any coroutine won. + + - *exceptions*: list of exceptions returned by the coroutines. + ``len(exceptions)`` is equal to the number of coroutines actually + started, and the order is the same as in ``coro_fns``. The winning + coroutine's entry is ``None``. + + """ + loop = loop or asyncio.get_running_loop() + exceptions: List[Optional[BaseException]] = [] + tasks: Set[asyncio.Task[Optional[Tuple[_T, int]]]] = set() + + async def run_one_coro( + coro_fn: Callable[[], Awaitable[_T]], + this_index: int, + start_next: "asyncio.Future[None]", + ) -> Optional[Tuple[_T, int]]: + """ + Run a single coroutine. + + If the coroutine fails, set the exception in the exceptions list and + start the next coroutine by setting the result of the start_next. + + If the coroutine succeeds, return the result and the index of the + coroutine in the coro_fns list. + + If SystemExit or KeyboardInterrupt is raised, re-raise it. + """ + try: + result = await coro_fn() + except RE_RAISE_EXCEPTIONS: + raise + except BaseException as e: + exceptions[this_index] = e + _set_result(start_next) # Kickstart the next coroutine + return None + + return result, this_index + + start_next_timer: Optional[asyncio.TimerHandle] = None + start_next: Optional[asyncio.Future[None]] + task: asyncio.Task[Optional[Tuple[_T, int]]] + done: Union[asyncio.Future[None], asyncio.Task[Optional[Tuple[_T, int]]]] + coro_iter = iter(coro_fns) + this_index = -1 + try: + while True: + if coro_fn := next(coro_iter, None): + this_index += 1 + exceptions.append(None) + start_next = loop.create_future() + task = loop.create_task(run_one_coro(coro_fn, this_index, start_next)) + tasks.add(task) + start_next_timer = ( + loop.call_later(delay, _set_result, start_next) if delay else None + ) + elif not tasks: + # We exhausted the coro_fns list and no tasks are running + # so we have no winner and all coroutines failed. + break + + while tasks or start_next: + done = await _wait_one( + (*tasks, start_next) if start_next else tasks, loop + ) + if done is start_next: + # The current task has failed or the timer has expired + # so we need to start the next task. + start_next = None + if start_next_timer: + start_next_timer.cancel() + start_next_timer = None + + # Break out of the task waiting loop to start the next + # task. + break + + if TYPE_CHECKING: + assert isinstance(done, asyncio.Task) + + tasks.remove(done) + if winner := done.result(): + return *winner, exceptions + finally: + # We either have: + # - a winner + # - all tasks failed + # - a KeyboardInterrupt or SystemExit. + + # + # If the timer is still running, cancel it. + # + if start_next_timer: + start_next_timer.cancel() + + # + # If there are any tasks left, cancel them and than + # wait them so they fill the exceptions list. + # + for task in tasks: + task.cancel() + with contextlib.suppress(asyncio.CancelledError): + await task + + return None, None, exceptions diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohappyeyeballs/impl.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohappyeyeballs/impl.py new file mode 100644 index 0000000000000000000000000000000000000000..8f3919a0c959f6dc84cbdfce470218a9ac7fac65 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohappyeyeballs/impl.py @@ -0,0 +1,259 @@ +"""Base implementation.""" + +import asyncio +import collections +import contextlib +import functools +import itertools +import socket +from typing import List, Optional, Sequence, Set, Union + +from . import _staggered +from .types import AddrInfoType, SocketFactoryType + + +async def start_connection( + addr_infos: Sequence[AddrInfoType], + *, + local_addr_infos: Optional[Sequence[AddrInfoType]] = None, + happy_eyeballs_delay: Optional[float] = None, + interleave: Optional[int] = None, + loop: Optional[asyncio.AbstractEventLoop] = None, + socket_factory: Optional[SocketFactoryType] = None, +) -> socket.socket: + """ + Connect to a TCP server. + + Create a socket connection to a specified destination. The + destination is specified as a list of AddrInfoType tuples as + returned from getaddrinfo(). + + The arguments are, in order: + + * ``family``: the address family, e.g. ``socket.AF_INET`` or + ``socket.AF_INET6``. + * ``type``: the socket type, e.g. ``socket.SOCK_STREAM`` or + ``socket.SOCK_DGRAM``. + * ``proto``: the protocol, e.g. ``socket.IPPROTO_TCP`` or + ``socket.IPPROTO_UDP``. + * ``canonname``: the canonical name of the address, e.g. + ``"www.python.org"``. + * ``sockaddr``: the socket address + + This method is a coroutine which will try to establish the connection + in the background. When successful, the coroutine returns a + socket. + + The expected use case is to use this method in conjunction with + loop.create_connection() to establish a connection to a server:: + + socket = await start_connection(addr_infos) + transport, protocol = await loop.create_connection( + MyProtocol, sock=socket, ...) + """ + if not (current_loop := loop): + current_loop = asyncio.get_running_loop() + + single_addr_info = len(addr_infos) == 1 + + if happy_eyeballs_delay is not None and interleave is None: + # If using happy eyeballs, default to interleave addresses by family + interleave = 1 + + if interleave and not single_addr_info: + addr_infos = _interleave_addrinfos(addr_infos, interleave) + + sock: Optional[socket.socket] = None + # uvloop can raise RuntimeError instead of OSError + exceptions: List[List[Union[OSError, RuntimeError]]] = [] + if happy_eyeballs_delay is None or single_addr_info: + # not using happy eyeballs + for addrinfo in addr_infos: + try: + sock = await _connect_sock( + current_loop, + exceptions, + addrinfo, + local_addr_infos, + None, + socket_factory, + ) + break + except (RuntimeError, OSError): + continue + else: # using happy eyeballs + open_sockets: Set[socket.socket] = set() + try: + sock, _, _ = await _staggered.staggered_race( + ( + functools.partial( + _connect_sock, + current_loop, + exceptions, + addrinfo, + local_addr_infos, + open_sockets, + socket_factory, + ) + for addrinfo in addr_infos + ), + happy_eyeballs_delay, + ) + finally: + # If we have a winner, staggered_race will + # cancel the other tasks, however there is a + # small race window where any of the other tasks + # can be done before they are cancelled which + # will leave the socket open. To avoid this problem + # we pass a set to _connect_sock to keep track of + # the open sockets and close them here if there + # are any "runner up" sockets. + for s in open_sockets: + if s is not sock: + with contextlib.suppress(OSError): + s.close() + open_sockets = None # type: ignore[assignment] + + if sock is None: + all_exceptions = [exc for sub in exceptions for exc in sub] + try: + first_exception = all_exceptions[0] + if len(all_exceptions) == 1: + raise first_exception + else: + # If they all have the same str(), raise one. + model = str(first_exception) + if all(str(exc) == model for exc in all_exceptions): + raise first_exception + # Raise a combined exception so the user can see all + # the various error messages. + msg = "Multiple exceptions: {}".format( + ", ".join(str(exc) for exc in all_exceptions) + ) + # If the errno is the same for all exceptions, raise + # an OSError with that errno. + if isinstance(first_exception, OSError): + first_errno = first_exception.errno + if all( + isinstance(exc, OSError) and exc.errno == first_errno + for exc in all_exceptions + ): + raise OSError(first_errno, msg) + elif isinstance(first_exception, RuntimeError) and all( + isinstance(exc, RuntimeError) for exc in all_exceptions + ): + raise RuntimeError(msg) + # We have a mix of OSError and RuntimeError + # so we have to pick which one to raise. + # and we raise OSError for compatibility + raise OSError(msg) + finally: + all_exceptions = None # type: ignore[assignment] + exceptions = None # type: ignore[assignment] + + return sock + + +async def _connect_sock( + loop: asyncio.AbstractEventLoop, + exceptions: List[List[Union[OSError, RuntimeError]]], + addr_info: AddrInfoType, + local_addr_infos: Optional[Sequence[AddrInfoType]] = None, + open_sockets: Optional[Set[socket.socket]] = None, + socket_factory: Optional[SocketFactoryType] = None, +) -> socket.socket: + """ + Create, bind and connect one socket. + + If open_sockets is passed, add the socket to the set of open sockets. + Any failure caught here will remove the socket from the set and close it. + + Callers can use this set to close any sockets that are not the winner + of all staggered tasks in the result there are runner up sockets aka + multiple winners. + """ + my_exceptions: List[Union[OSError, RuntimeError]] = [] + exceptions.append(my_exceptions) + family, type_, proto, _, address = addr_info + sock = None + try: + if socket_factory is not None: + sock = socket_factory(addr_info) + else: + sock = socket.socket(family=family, type=type_, proto=proto) + if open_sockets is not None: + open_sockets.add(sock) + sock.setblocking(False) + if local_addr_infos is not None: + for lfamily, _, _, _, laddr in local_addr_infos: + # skip local addresses of different family + if lfamily != family: + continue + try: + sock.bind(laddr) + break + except OSError as exc: + msg = ( + f"error while attempting to bind on " + f"address {laddr!r}: " + f"{(exc.strerror or '').lower()}" + ) + exc = OSError(exc.errno, msg) + my_exceptions.append(exc) + else: # all bind attempts failed + if my_exceptions: + raise my_exceptions.pop() + else: + raise OSError(f"no matching local address with {family=} found") + await loop.sock_connect(sock, address) + return sock + except (RuntimeError, OSError) as exc: + my_exceptions.append(exc) + if sock is not None: + if open_sockets is not None: + open_sockets.remove(sock) + try: + sock.close() + except OSError as e: + my_exceptions.append(e) + raise + raise + except: + if sock is not None: + if open_sockets is not None: + open_sockets.remove(sock) + try: + sock.close() + except OSError as e: + my_exceptions.append(e) + raise + raise + finally: + exceptions = my_exceptions = None # type: ignore[assignment] + + +def _interleave_addrinfos( + addrinfos: Sequence[AddrInfoType], first_address_family_count: int = 1 +) -> List[AddrInfoType]: + """Interleave list of addrinfo tuples by family.""" + # Group addresses by family + addrinfos_by_family: collections.OrderedDict[int, List[AddrInfoType]] = ( + collections.OrderedDict() + ) + for addr in addrinfos: + family = addr[0] + if family not in addrinfos_by_family: + addrinfos_by_family[family] = [] + addrinfos_by_family[family].append(addr) + addrinfos_lists = list(addrinfos_by_family.values()) + + reordered: List[AddrInfoType] = [] + if first_address_family_count > 1: + reordered.extend(addrinfos_lists[0][: first_address_family_count - 1]) + del addrinfos_lists[0][: first_address_family_count - 1] + reordered.extend( + a + for a in itertools.chain.from_iterable(itertools.zip_longest(*addrinfos_lists)) + if a is not None + ) + return reordered diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohappyeyeballs/py.typed b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohappyeyeballs/py.typed new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohappyeyeballs/types.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohappyeyeballs/types.py new file mode 100644 index 0000000000000000000000000000000000000000..e8c75074e7ab397af7ee1d1eb57cf04957a7a619 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohappyeyeballs/types.py @@ -0,0 +1,17 @@ +"""Types for aiohappyeyeballs.""" + +import socket + +# PY3.9: Import Callable from typing until we drop Python 3.9 support +# https://github.com/python/cpython/issues/87131 +from typing import Callable, Tuple, Union + +AddrInfoType = Tuple[ + Union[int, socket.AddressFamily], + Union[int, socket.SocketKind], + int, + str, + Tuple, # type: ignore[type-arg] +] + +SocketFactoryType = Callable[[AddrInfoType], socket.socket] diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohappyeyeballs/utils.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohappyeyeballs/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..ea29adb9be9edd751cd6d7b93ca9c4bd8d08b658 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohappyeyeballs/utils.py @@ -0,0 +1,97 @@ +"""Utility functions for aiohappyeyeballs.""" + +import ipaddress +import socket +from typing import Dict, List, Optional, Tuple, Union + +from .types import AddrInfoType + + +def addr_to_addr_infos( + addr: Optional[ + Union[Tuple[str, int, int, int], Tuple[str, int, int], Tuple[str, int]] + ], +) -> Optional[List[AddrInfoType]]: + """Convert an address tuple to a list of addr_info tuples.""" + if addr is None: + return None + host = addr[0] + port = addr[1] + is_ipv6 = ":" in host + if is_ipv6: + flowinfo = 0 + scopeid = 0 + addr_len = len(addr) + if addr_len >= 4: + scopeid = addr[3] # type: ignore[misc] + if addr_len >= 3: + flowinfo = addr[2] # type: ignore[misc] + addr = (host, port, flowinfo, scopeid) + family = socket.AF_INET6 + else: + addr = (host, port) + family = socket.AF_INET + return [(family, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", addr)] + + +def pop_addr_infos_interleave( + addr_infos: List[AddrInfoType], interleave: Optional[int] = None +) -> None: + """ + Pop addr_info from the list of addr_infos by family up to interleave times. + + The interleave parameter is used to know how many addr_infos for + each family should be popped of the top of the list. + """ + seen: Dict[int, int] = {} + if interleave is None: + interleave = 1 + to_remove: List[AddrInfoType] = [] + for addr_info in addr_infos: + family = addr_info[0] + if family not in seen: + seen[family] = 0 + if seen[family] < interleave: + to_remove.append(addr_info) + seen[family] += 1 + for addr_info in to_remove: + addr_infos.remove(addr_info) + + +def _addr_tuple_to_ip_address( + addr: Union[Tuple[str, int], Tuple[str, int, int, int]], +) -> Union[ + Tuple[ipaddress.IPv4Address, int], Tuple[ipaddress.IPv6Address, int, int, int] +]: + """Convert an address tuple to an IPv4Address.""" + return (ipaddress.ip_address(addr[0]), *addr[1:]) + + +def remove_addr_infos( + addr_infos: List[AddrInfoType], + addr: Union[Tuple[str, int], Tuple[str, int, int, int]], +) -> None: + """ + Remove an address from the list of addr_infos. + + The addr value is typically the return value of + sock.getpeername(). + """ + bad_addrs_infos: List[AddrInfoType] = [] + for addr_info in addr_infos: + if addr_info[-1] == addr: + bad_addrs_infos.append(addr_info) + if bad_addrs_infos: + for bad_addr_info in bad_addrs_infos: + addr_infos.remove(bad_addr_info) + return + # Slow path in case addr is formatted differently + match_addr = _addr_tuple_to_ip_address(addr) + for addr_info in addr_infos: + if match_addr == _addr_tuple_to_ip_address(addr_info[-1]): + bad_addrs_infos.append(addr_info) + if bad_addrs_infos: + for bad_addr_info in bad_addrs_infos: + addr_infos.remove(bad_addr_info) + return + raise ValueError(f"Address {addr} not found in addr_infos") diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/.hash/_cparser.pxd.hash b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/.hash/_cparser.pxd.hash new file mode 100644 index 0000000000000000000000000000000000000000..3f5cd0e6f720a049ee19149790c808558b492726 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/.hash/_cparser.pxd.hash @@ -0,0 +1 @@ +5276d46021e0e0d7577e0c9155800cbf62932d60a50783fec42aefb63febedec /home/runner/work/aiohttp/aiohttp/aiohttp/_cparser.pxd diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/.hash/_find_header.pxd.hash b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/.hash/_find_header.pxd.hash new file mode 100644 index 0000000000000000000000000000000000000000..f006c2de5d24a1b5a9c26f83c858127b5e12b07c --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/.hash/_find_header.pxd.hash @@ -0,0 +1 @@ +d067f01423cddb3c442933b5fcc039b18ab651fcec1bc91c577693aafc25cf78 /home/runner/work/aiohttp/aiohttp/aiohttp/_find_header.pxd diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/.hash/_http_parser.pyx.hash b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/.hash/_http_parser.pyx.hash new file mode 100644 index 0000000000000000000000000000000000000000..af02f94d2226265df247e68e799be8b890ee2d56 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/.hash/_http_parser.pyx.hash @@ -0,0 +1 @@ +bdebb034a6fa633049a0ede76efae4382693cdb711392e4c564b142610c73608 /home/runner/work/aiohttp/aiohttp/aiohttp/_http_parser.pyx diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/.hash/_http_writer.pyx.hash b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/.hash/_http_writer.pyx.hash new file mode 100644 index 0000000000000000000000000000000000000000..43c1ab173a022eff03b25e434d6f25aa934ce75c --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/.hash/_http_writer.pyx.hash @@ -0,0 +1 @@ +4d734e0bdd2dc64e309cf6a4c5ec7fca049baf7c6ad54c5a0a2d93e20bf6b5e6 /home/runner/work/aiohttp/aiohttp/aiohttp/_http_writer.pyx diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/.hash/hdrs.py.hash b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/.hash/hdrs.py.hash new file mode 100644 index 0000000000000000000000000000000000000000..c8d55240e6a55c305c99244966dee5615565716b --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/.hash/hdrs.py.hash @@ -0,0 +1 @@ +dab8f933203eeb245d60f856e542a45b888d5a110094620e4811f90f816628d1 /home/runner/work/aiohttp/aiohttp/aiohttp/hdrs.py diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..404f926774899f827271dc48b6a3c914e9906cbd --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/__init__.py @@ -0,0 +1,278 @@ +__version__ = "3.13.5" + +from typing import TYPE_CHECKING, Tuple + +from . import hdrs as hdrs +from .client import ( + BaseConnector, + ClientConnectionError, + ClientConnectionResetError, + ClientConnectorCertificateError, + ClientConnectorDNSError, + ClientConnectorError, + ClientConnectorSSLError, + ClientError, + ClientHttpProxyError, + ClientOSError, + ClientPayloadError, + ClientProxyConnectionError, + ClientRequest, + ClientResponse, + ClientResponseError, + ClientSession, + ClientSSLError, + ClientTimeout, + ClientWebSocketResponse, + ClientWSTimeout, + ConnectionTimeoutError, + ContentTypeError, + Fingerprint, + InvalidURL, + InvalidUrlClientError, + InvalidUrlRedirectClientError, + NamedPipeConnector, + NonHttpUrlClientError, + NonHttpUrlRedirectClientError, + RedirectClientError, + RequestInfo, + ServerConnectionError, + ServerDisconnectedError, + ServerFingerprintMismatch, + ServerTimeoutError, + SocketTimeoutError, + TCPConnector, + TooManyRedirects, + UnixConnector, + WSMessageTypeError, + WSServerHandshakeError, + request, +) +from .client_middleware_digest_auth import DigestAuthMiddleware +from .client_middlewares import ClientHandlerType, ClientMiddlewareType +from .compression_utils import set_zlib_backend +from .connector import ( + AddrInfoType as AddrInfoType, + SocketFactoryType as SocketFactoryType, +) +from .cookiejar import CookieJar as CookieJar, DummyCookieJar as DummyCookieJar +from .formdata import FormData as FormData +from .helpers import BasicAuth, ChainMapProxy, ETag +from .http import ( + HttpVersion as HttpVersion, + HttpVersion10 as HttpVersion10, + HttpVersion11 as HttpVersion11, + WebSocketError as WebSocketError, + WSCloseCode as WSCloseCode, + WSMessage as WSMessage, + WSMsgType as WSMsgType, +) +from .multipart import ( + BadContentDispositionHeader as BadContentDispositionHeader, + BadContentDispositionParam as BadContentDispositionParam, + BodyPartReader as BodyPartReader, + MultipartReader as MultipartReader, + MultipartWriter as MultipartWriter, + content_disposition_filename as content_disposition_filename, + parse_content_disposition as parse_content_disposition, +) +from .payload import ( + PAYLOAD_REGISTRY as PAYLOAD_REGISTRY, + AsyncIterablePayload as AsyncIterablePayload, + BufferedReaderPayload as BufferedReaderPayload, + BytesIOPayload as BytesIOPayload, + BytesPayload as BytesPayload, + IOBasePayload as IOBasePayload, + JsonPayload as JsonPayload, + Payload as Payload, + StringIOPayload as StringIOPayload, + StringPayload as StringPayload, + TextIOPayload as TextIOPayload, + get_payload as get_payload, + payload_type as payload_type, +) +from .payload_streamer import streamer as streamer +from .resolver import ( + AsyncResolver as AsyncResolver, + DefaultResolver as DefaultResolver, + ThreadedResolver as ThreadedResolver, +) +from .streams import ( + EMPTY_PAYLOAD as EMPTY_PAYLOAD, + DataQueue as DataQueue, + EofStream as EofStream, + FlowControlDataQueue as FlowControlDataQueue, + StreamReader as StreamReader, +) +from .tracing import ( + TraceConfig as TraceConfig, + TraceConnectionCreateEndParams as TraceConnectionCreateEndParams, + TraceConnectionCreateStartParams as TraceConnectionCreateStartParams, + TraceConnectionQueuedEndParams as TraceConnectionQueuedEndParams, + TraceConnectionQueuedStartParams as TraceConnectionQueuedStartParams, + TraceConnectionReuseconnParams as TraceConnectionReuseconnParams, + TraceDnsCacheHitParams as TraceDnsCacheHitParams, + TraceDnsCacheMissParams as TraceDnsCacheMissParams, + TraceDnsResolveHostEndParams as TraceDnsResolveHostEndParams, + TraceDnsResolveHostStartParams as TraceDnsResolveHostStartParams, + TraceRequestChunkSentParams as TraceRequestChunkSentParams, + TraceRequestEndParams as TraceRequestEndParams, + TraceRequestExceptionParams as TraceRequestExceptionParams, + TraceRequestHeadersSentParams as TraceRequestHeadersSentParams, + TraceRequestRedirectParams as TraceRequestRedirectParams, + TraceRequestStartParams as TraceRequestStartParams, + TraceResponseChunkReceivedParams as TraceResponseChunkReceivedParams, +) + +if TYPE_CHECKING: + # At runtime these are lazy-loaded at the bottom of the file. + from .worker import ( + GunicornUVLoopWebWorker as GunicornUVLoopWebWorker, + GunicornWebWorker as GunicornWebWorker, + ) + +__all__: Tuple[str, ...] = ( + "hdrs", + # client + "AddrInfoType", + "BaseConnector", + "ClientConnectionError", + "ClientConnectionResetError", + "ClientConnectorCertificateError", + "ClientConnectorDNSError", + "ClientConnectorError", + "ClientConnectorSSLError", + "ClientError", + "ClientHttpProxyError", + "ClientOSError", + "ClientPayloadError", + "ClientProxyConnectionError", + "ClientResponse", + "ClientRequest", + "ClientResponseError", + "ClientSSLError", + "ClientSession", + "ClientTimeout", + "ClientWebSocketResponse", + "ClientWSTimeout", + "ConnectionTimeoutError", + "ContentTypeError", + "Fingerprint", + "FlowControlDataQueue", + "InvalidURL", + "InvalidUrlClientError", + "InvalidUrlRedirectClientError", + "NonHttpUrlClientError", + "NonHttpUrlRedirectClientError", + "RedirectClientError", + "RequestInfo", + "ServerConnectionError", + "ServerDisconnectedError", + "ServerFingerprintMismatch", + "ServerTimeoutError", + "SocketFactoryType", + "SocketTimeoutError", + "TCPConnector", + "TooManyRedirects", + "UnixConnector", + "NamedPipeConnector", + "WSServerHandshakeError", + "request", + # client_middleware + "ClientMiddlewareType", + "ClientHandlerType", + # cookiejar + "CookieJar", + "DummyCookieJar", + # formdata + "FormData", + # helpers + "BasicAuth", + "ChainMapProxy", + "DigestAuthMiddleware", + "ETag", + "set_zlib_backend", + # http + "HttpVersion", + "HttpVersion10", + "HttpVersion11", + "WSMsgType", + "WSCloseCode", + "WSMessage", + "WebSocketError", + # multipart + "BadContentDispositionHeader", + "BadContentDispositionParam", + "BodyPartReader", + "MultipartReader", + "MultipartWriter", + "content_disposition_filename", + "parse_content_disposition", + # payload + "AsyncIterablePayload", + "BufferedReaderPayload", + "BytesIOPayload", + "BytesPayload", + "IOBasePayload", + "JsonPayload", + "PAYLOAD_REGISTRY", + "Payload", + "StringIOPayload", + "StringPayload", + "TextIOPayload", + "get_payload", + "payload_type", + # payload_streamer + "streamer", + # resolver + "AsyncResolver", + "DefaultResolver", + "ThreadedResolver", + # streams + "DataQueue", + "EMPTY_PAYLOAD", + "EofStream", + "StreamReader", + # tracing + "TraceConfig", + "TraceConnectionCreateEndParams", + "TraceConnectionCreateStartParams", + "TraceConnectionQueuedEndParams", + "TraceConnectionQueuedStartParams", + "TraceConnectionReuseconnParams", + "TraceDnsCacheHitParams", + "TraceDnsCacheMissParams", + "TraceDnsResolveHostEndParams", + "TraceDnsResolveHostStartParams", + "TraceRequestChunkSentParams", + "TraceRequestEndParams", + "TraceRequestExceptionParams", + "TraceRequestHeadersSentParams", + "TraceRequestRedirectParams", + "TraceRequestStartParams", + "TraceResponseChunkReceivedParams", + # workers (imported lazily with __getattr__) + "GunicornUVLoopWebWorker", + "GunicornWebWorker", + "WSMessageTypeError", +) + + +def __dir__() -> Tuple[str, ...]: + return __all__ + ("__doc__",) + + +def __getattr__(name: str) -> object: + global GunicornUVLoopWebWorker, GunicornWebWorker + + # Importing gunicorn takes a long time (>100ms), so only import if actually needed. + if name in ("GunicornUVLoopWebWorker", "GunicornWebWorker"): + try: + from .worker import GunicornUVLoopWebWorker as guv, GunicornWebWorker as gw + except ImportError: + return None + + GunicornUVLoopWebWorker = guv # type: ignore[misc] + GunicornWebWorker = gw # type: ignore[misc] + return guv if name == "GunicornUVLoopWebWorker" else gw + + raise AttributeError(f"module {__name__} has no attribute {name}") diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_cookie_helpers.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_cookie_helpers.py new file mode 100644 index 0000000000000000000000000000000000000000..10e2e0eff9de9d28ecbfe511a1227a1fec1d83cd --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_cookie_helpers.py @@ -0,0 +1,338 @@ +""" +Internal cookie handling helpers. + +This module contains internal utilities for cookie parsing and manipulation. +These are not part of the public API and may change without notice. +""" + +import re +from http.cookies import Morsel +from typing import List, Optional, Sequence, Tuple, cast + +from .log import internal_logger + +__all__ = ( + "parse_set_cookie_headers", + "parse_cookie_header", + "preserve_morsel_with_coded_value", +) + +# Cookie parsing constants +# Allow more characters in cookie names to handle real-world cookies +# that don't strictly follow RFC standards (fixes #2683) +# RFC 6265 defines cookie-name token as per RFC 2616 Section 2.2, +# but many servers send cookies with characters like {} [] () etc. +# This makes the cookie parser more tolerant of real-world cookies +# while still providing some validation to catch obviously malformed names. +_COOKIE_NAME_RE = re.compile(r"^[!#$%&\'()*+\-./0-9:<=>?@A-Z\[\]^_`a-z{|}~]+$") +_COOKIE_KNOWN_ATTRS = frozenset( # AKA Morsel._reserved + ( + "path", + "domain", + "max-age", + "expires", + "secure", + "httponly", + "samesite", + "partitioned", + "version", + "comment", + ) +) +_COOKIE_BOOL_ATTRS = frozenset( # AKA Morsel._flags + ("secure", "httponly", "partitioned") +) + +# SimpleCookie's pattern for parsing cookies with relaxed validation +# Based on http.cookies pattern but extended to allow more characters in cookie names +# to handle real-world cookies (fixes #2683) +_COOKIE_PATTERN = re.compile( + r""" + \s* # Optional whitespace at start of cookie + (?P # Start of group 'key' + # aiohttp has extended to include [] for compatibility with real-world cookies + [\w\d!#%&'~_`><@,:/\$\*\+\-\.\^\|\)\(\?\}\{\[\]]+ # Any word of at least one letter + ) # End of group 'key' + ( # Optional group: there may not be a value. + \s*=\s* # Equal Sign + (?P # Start of group 'val' + "(?:[^\\"]|\\.)*" # Any double-quoted string (properly closed) + | # or + "[^";]* # Unmatched opening quote (differs from SimpleCookie - issue #7993) + | # or + # Special case for "expires" attr - RFC 822, RFC 850, RFC 1036, RFC 1123 + (\w{3,6}day|\w{3}),\s # Day of the week or abbreviated day (with comma) + [\w\d\s-]{9,11}\s[\d:]{8}\s # Date and time in specific format + (GMT|[+-]\d{4}) # Timezone: GMT or RFC 2822 offset like -0000, +0100 + # NOTE: RFC 2822 timezone support is an aiohttp extension + # for issue #4493 - SimpleCookie does NOT support this + | # or + # ANSI C asctime() format: "Wed Jun 9 10:18:14 2021" + # NOTE: This is an aiohttp extension for issue #4327 - SimpleCookie does NOT support this format + \w{3}\s+\w{3}\s+[\s\d]\d\s+\d{2}:\d{2}:\d{2}\s+\d{4} + | # or + [\w\d!#%&'~_`><@,:/\$\*\+\-\.\^\|\)\(\?\}\{\=\[\]]* # Any word or empty string + ) # End of group 'val' + )? # End of optional value group + \s* # Any number of spaces. + (\s+|;|$) # Ending either at space, semicolon, or EOS. + """, + re.VERBOSE | re.ASCII, +) + + +def preserve_morsel_with_coded_value(cookie: Morsel[str]) -> Morsel[str]: + """ + Preserve a Morsel's coded_value exactly as received from the server. + + This function ensures that cookie encoding is preserved exactly as sent by + the server, which is critical for compatibility with old servers that have + strict requirements about cookie formats. + + This addresses the issue described in https://github.com/aio-libs/aiohttp/pull/1453 + where Python's SimpleCookie would re-encode cookies, breaking authentication + with certain servers. + + Args: + cookie: A Morsel object from SimpleCookie + + Returns: + A Morsel object with preserved coded_value + + """ + mrsl_val = cast("Morsel[str]", cookie.get(cookie.key, Morsel())) + # We use __setstate__ instead of the public set() API because it allows us to + # bypass validation and set already validated state. This is more stable than + # setting protected attributes directly and unlikely to change since it would + # break pickling. + mrsl_val.__setstate__( # type: ignore[attr-defined] + {"key": cookie.key, "value": cookie.value, "coded_value": cookie.coded_value} + ) + return mrsl_val + + +_unquote_sub = re.compile(r"\\(?:([0-3][0-7][0-7])|(.))").sub + + +def _unquote_replace(m: re.Match[str]) -> str: + """ + Replace function for _unquote_sub regex substitution. + + Handles escaped characters in cookie values: + - Octal sequences are converted to their character representation + - Other escaped characters are unescaped by removing the backslash + """ + if m[1]: + return chr(int(m[1], 8)) + return m[2] + + +def _unquote(value: str) -> str: + """ + Unquote a cookie value. + + Vendored from http.cookies._unquote to ensure compatibility. + + Note: The original implementation checked for None, but we've removed + that check since all callers already ensure the value is not None. + """ + # If there aren't any doublequotes, + # then there can't be any special characters. See RFC 2109. + if len(value) < 2: + return value + if value[0] != '"' or value[-1] != '"': + return value + + # We have to assume that we must decode this string. + # Down to work. + + # Remove the "s + value = value[1:-1] + + # Check for special sequences. Examples: + # \012 --> \n + # \" --> " + # + return _unquote_sub(_unquote_replace, value) + + +def parse_cookie_header(header: str) -> List[Tuple[str, Morsel[str]]]: + """ + Parse a Cookie header according to RFC 6265 Section 5.4. + + Cookie headers contain only name-value pairs separated by semicolons. + There are no attributes in Cookie headers - even names that match + attribute names (like 'path' or 'secure') should be treated as cookies. + + This parser uses the same regex-based approach as parse_set_cookie_headers + to properly handle quoted values that may contain semicolons. When the + regex fails to match a malformed cookie, it falls back to simple parsing + to ensure subsequent cookies are not lost + https://github.com/aio-libs/aiohttp/issues/11632 + + Args: + header: The Cookie header value to parse + + Returns: + List of (name, Morsel) tuples for compatibility with SimpleCookie.update() + """ + if not header: + return [] + + cookies: List[Tuple[str, Morsel[str]]] = [] + morsel: Morsel[str] + i = 0 + n = len(header) + + invalid_names = [] + while i < n: + # Use the same pattern as parse_set_cookie_headers to find cookies + match = _COOKIE_PATTERN.match(header, i) + if not match: + # Fallback for malformed cookies https://github.com/aio-libs/aiohttp/issues/11632 + # Find next semicolon to skip or attempt simple key=value parsing + next_semi = header.find(";", i) + eq_pos = header.find("=", i) + + # Try to extract key=value if '=' comes before ';' + if eq_pos != -1 and (next_semi == -1 or eq_pos < next_semi): + end_pos = next_semi if next_semi != -1 else n + key = header[i:eq_pos].strip() + value = header[eq_pos + 1 : end_pos].strip() + + # Validate the name (same as regex path) + if not _COOKIE_NAME_RE.match(key): + invalid_names.append(key) + else: + morsel = Morsel() + morsel.__setstate__( # type: ignore[attr-defined] + {"key": key, "value": _unquote(value), "coded_value": value} + ) + cookies.append((key, morsel)) + + # Move to next cookie or end + i = next_semi + 1 if next_semi != -1 else n + continue + + key = match.group("key") + value = match.group("val") or "" + i = match.end(0) + + # Validate the name + if not key or not _COOKIE_NAME_RE.match(key): + invalid_names.append(key) + continue + + # Create new morsel + morsel = Morsel() + # Preserve the original value as coded_value (with quotes if present) + # We use __setstate__ instead of the public set() API because it allows us to + # bypass validation and set already validated state. This is more stable than + # setting protected attributes directly and unlikely to change since it would + # break pickling. + morsel.__setstate__( # type: ignore[attr-defined] + {"key": key, "value": _unquote(value), "coded_value": value} + ) + + cookies.append((key, morsel)) + + if invalid_names: + internal_logger.debug( + "Cannot load cookie. Illegal cookie names: %r", invalid_names + ) + + return cookies + + +def parse_set_cookie_headers(headers: Sequence[str]) -> List[Tuple[str, Morsel[str]]]: + """ + Parse cookie headers using a vendored version of SimpleCookie parsing. + + This implementation is based on SimpleCookie.__parse_string to ensure + compatibility with how SimpleCookie parses cookies, including handling + of malformed cookies with missing semicolons. + + This function is used for both Cookie and Set-Cookie headers in order to be + forgiving. Ideally we would have followed RFC 6265 Section 5.2 (for Cookie + headers) and RFC 6265 Section 4.2.1 (for Set-Cookie headers), but the + real world data makes it impossible since we need to be a bit more forgiving. + + NOTE: This implementation differs from SimpleCookie in handling unmatched quotes. + SimpleCookie will stop parsing when it encounters a cookie value with an unmatched + quote (e.g., 'cookie="value'), causing subsequent cookies to be silently dropped. + This implementation handles unmatched quotes more gracefully to prevent cookie loss. + See https://github.com/aio-libs/aiohttp/issues/7993 + """ + parsed_cookies: List[Tuple[str, Morsel[str]]] = [] + + for header in headers: + if not header: + continue + + # Parse cookie string using SimpleCookie's algorithm + i = 0 + n = len(header) + current_morsel: Optional[Morsel[str]] = None + morsel_seen = False + + while 0 <= i < n: + # Start looking for a cookie + match = _COOKIE_PATTERN.match(header, i) + if not match: + # No more cookies + break + + key, value = match.group("key"), match.group("val") + i = match.end(0) + lower_key = key.lower() + + if key[0] == "$": + if not morsel_seen: + # We ignore attributes which pertain to the cookie + # mechanism as a whole, such as "$Version". + continue + # Process as attribute + if current_morsel is not None: + attr_lower_key = lower_key[1:] + if attr_lower_key in _COOKIE_KNOWN_ATTRS: + current_morsel[attr_lower_key] = value or "" + elif lower_key in _COOKIE_KNOWN_ATTRS: + if not morsel_seen: + # Invalid cookie string - attribute before cookie + break + if lower_key in _COOKIE_BOOL_ATTRS: + # Boolean attribute with any value should be True + if current_morsel is not None and current_morsel.isReservedKey(key): + current_morsel[lower_key] = True + elif value is None: + # Invalid cookie string - non-boolean attribute without value + break + elif current_morsel is not None: + # Regular attribute with value + current_morsel[lower_key] = _unquote(value) + elif value is not None: + # This is a cookie name=value pair + # Validate the name + if key in _COOKIE_KNOWN_ATTRS or not _COOKIE_NAME_RE.match(key): + internal_logger.warning( + "Can not load cookies: Illegal cookie name %r", key + ) + current_morsel = None + else: + # Create new morsel + current_morsel = Morsel() + # Preserve the original value as coded_value (with quotes if present) + # We use __setstate__ instead of the public set() API because it allows us to + # bypass validation and set already validated state. This is more stable than + # setting protected attributes directly and unlikely to change since it would + # break pickling. + current_morsel.__setstate__( # type: ignore[attr-defined] + {"key": key, "value": _unquote(value), "coded_value": value} + ) + parsed_cookies.append((key, current_morsel)) + morsel_seen = True + else: + # Invalid cookie string - no value for non-attribute + break + + return parsed_cookies diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_cparser.pxd b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_cparser.pxd new file mode 100644 index 0000000000000000000000000000000000000000..1b3be6d4efb682bca9397da34f8e727b381bc84f --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_cparser.pxd @@ -0,0 +1,158 @@ +from libc.stdint cimport int32_t, uint8_t, uint16_t, uint64_t + + +cdef extern from "llhttp.h": + + struct llhttp__internal_s: + int32_t _index + void* _span_pos0 + void* _span_cb0 + int32_t error + const char* reason + const char* error_pos + void* data + void* _current + uint64_t content_length + uint8_t type + uint8_t method + uint8_t http_major + uint8_t http_minor + uint8_t header_state + uint8_t lenient_flags + uint8_t upgrade + uint8_t finish + uint16_t flags + uint16_t status_code + void* settings + + ctypedef llhttp__internal_s llhttp__internal_t + ctypedef llhttp__internal_t llhttp_t + + ctypedef int (*llhttp_data_cb)(llhttp_t*, const char *at, size_t length) except -1 + ctypedef int (*llhttp_cb)(llhttp_t*) except -1 + + struct llhttp_settings_s: + llhttp_cb on_message_begin + llhttp_data_cb on_url + llhttp_data_cb on_status + llhttp_data_cb on_header_field + llhttp_data_cb on_header_value + llhttp_cb on_headers_complete + llhttp_data_cb on_body + llhttp_cb on_message_complete + llhttp_cb on_chunk_header + llhttp_cb on_chunk_complete + + llhttp_cb on_url_complete + llhttp_cb on_status_complete + llhttp_cb on_header_field_complete + llhttp_cb on_header_value_complete + + ctypedef llhttp_settings_s llhttp_settings_t + + enum llhttp_errno: + HPE_OK, + HPE_INTERNAL, + HPE_STRICT, + HPE_LF_EXPECTED, + HPE_UNEXPECTED_CONTENT_LENGTH, + HPE_CLOSED_CONNECTION, + HPE_INVALID_METHOD, + HPE_INVALID_URL, + HPE_INVALID_CONSTANT, + HPE_INVALID_VERSION, + HPE_INVALID_HEADER_TOKEN, + HPE_INVALID_CONTENT_LENGTH, + HPE_INVALID_CHUNK_SIZE, + HPE_INVALID_STATUS, + HPE_INVALID_EOF_STATE, + HPE_INVALID_TRANSFER_ENCODING, + HPE_CB_MESSAGE_BEGIN, + HPE_CB_HEADERS_COMPLETE, + HPE_CB_MESSAGE_COMPLETE, + HPE_CB_CHUNK_HEADER, + HPE_CB_CHUNK_COMPLETE, + HPE_PAUSED, + HPE_PAUSED_UPGRADE, + HPE_USER + + ctypedef llhttp_errno llhttp_errno_t + + enum llhttp_flags: + F_CHUNKED, + F_CONTENT_LENGTH + + enum llhttp_type: + HTTP_REQUEST, + HTTP_RESPONSE, + HTTP_BOTH + + enum llhttp_method: + HTTP_DELETE, + HTTP_GET, + HTTP_HEAD, + HTTP_POST, + HTTP_PUT, + HTTP_CONNECT, + HTTP_OPTIONS, + HTTP_TRACE, + HTTP_COPY, + HTTP_LOCK, + HTTP_MKCOL, + HTTP_MOVE, + HTTP_PROPFIND, + HTTP_PROPPATCH, + HTTP_SEARCH, + HTTP_UNLOCK, + HTTP_BIND, + HTTP_REBIND, + HTTP_UNBIND, + HTTP_ACL, + HTTP_REPORT, + HTTP_MKACTIVITY, + HTTP_CHECKOUT, + HTTP_MERGE, + HTTP_MSEARCH, + HTTP_NOTIFY, + HTTP_SUBSCRIBE, + HTTP_UNSUBSCRIBE, + HTTP_PATCH, + HTTP_PURGE, + HTTP_MKCALENDAR, + HTTP_LINK, + HTTP_UNLINK, + HTTP_SOURCE, + HTTP_PRI, + HTTP_DESCRIBE, + HTTP_ANNOUNCE, + HTTP_SETUP, + HTTP_PLAY, + HTTP_PAUSE, + HTTP_TEARDOWN, + HTTP_GET_PARAMETER, + HTTP_SET_PARAMETER, + HTTP_REDIRECT, + HTTP_RECORD, + HTTP_FLUSH + + ctypedef llhttp_method llhttp_method_t; + + void llhttp_settings_init(llhttp_settings_t* settings) + void llhttp_init(llhttp_t* parser, llhttp_type type, + const llhttp_settings_t* settings) + + llhttp_errno_t llhttp_execute(llhttp_t* parser, const char* data, size_t len) + + int llhttp_should_keep_alive(const llhttp_t* parser) + + void llhttp_resume_after_upgrade(llhttp_t* parser) + + llhttp_errno_t llhttp_get_errno(const llhttp_t* parser) + const char* llhttp_get_error_reason(const llhttp_t* parser) + const char* llhttp_get_error_pos(const llhttp_t* parser) + + const char* llhttp_method_name(llhttp_method_t method) + + void llhttp_set_lenient_headers(llhttp_t* parser, int enabled) + void llhttp_set_lenient_optional_cr_before_lf(llhttp_t* parser, int enabled) + void llhttp_set_lenient_spaces_after_chunk_size(llhttp_t* parser, int enabled) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_find_header.pxd b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_find_header.pxd new file mode 100644 index 0000000000000000000000000000000000000000..37a6c37268ee30b182fd77d109688d35d5577c7f --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_find_header.pxd @@ -0,0 +1,2 @@ +cdef extern from "_find_header.h": + int find_header(char *, int) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_headers.pxi b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_headers.pxi new file mode 100644 index 0000000000000000000000000000000000000000..3744721d4786a6c79b90aa349c8d02fa66204ecc --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_headers.pxi @@ -0,0 +1,83 @@ +# The file is autogenerated from aiohttp/hdrs.py +# Run ./tools/gen.py to update it after the origin changing. + +from . import hdrs +cdef tuple headers = ( + hdrs.ACCEPT, + hdrs.ACCEPT_CHARSET, + hdrs.ACCEPT_ENCODING, + hdrs.ACCEPT_LANGUAGE, + hdrs.ACCEPT_RANGES, + hdrs.ACCESS_CONTROL_ALLOW_CREDENTIALS, + hdrs.ACCESS_CONTROL_ALLOW_HEADERS, + hdrs.ACCESS_CONTROL_ALLOW_METHODS, + hdrs.ACCESS_CONTROL_ALLOW_ORIGIN, + hdrs.ACCESS_CONTROL_EXPOSE_HEADERS, + hdrs.ACCESS_CONTROL_MAX_AGE, + hdrs.ACCESS_CONTROL_REQUEST_HEADERS, + hdrs.ACCESS_CONTROL_REQUEST_METHOD, + hdrs.AGE, + hdrs.ALLOW, + hdrs.AUTHORIZATION, + hdrs.CACHE_CONTROL, + hdrs.CONNECTION, + hdrs.CONTENT_DISPOSITION, + hdrs.CONTENT_ENCODING, + hdrs.CONTENT_LANGUAGE, + hdrs.CONTENT_LENGTH, + hdrs.CONTENT_LOCATION, + hdrs.CONTENT_MD5, + hdrs.CONTENT_RANGE, + hdrs.CONTENT_TRANSFER_ENCODING, + hdrs.CONTENT_TYPE, + hdrs.COOKIE, + hdrs.DATE, + hdrs.DESTINATION, + hdrs.DIGEST, + hdrs.ETAG, + hdrs.EXPECT, + hdrs.EXPIRES, + hdrs.FORWARDED, + hdrs.FROM, + hdrs.HOST, + hdrs.IF_MATCH, + hdrs.IF_MODIFIED_SINCE, + hdrs.IF_NONE_MATCH, + hdrs.IF_RANGE, + hdrs.IF_UNMODIFIED_SINCE, + hdrs.KEEP_ALIVE, + hdrs.LAST_EVENT_ID, + hdrs.LAST_MODIFIED, + hdrs.LINK, + hdrs.LOCATION, + hdrs.MAX_FORWARDS, + hdrs.ORIGIN, + hdrs.PRAGMA, + hdrs.PROXY_AUTHENTICATE, + hdrs.PROXY_AUTHORIZATION, + hdrs.RANGE, + hdrs.REFERER, + hdrs.RETRY_AFTER, + hdrs.SEC_WEBSOCKET_ACCEPT, + hdrs.SEC_WEBSOCKET_EXTENSIONS, + hdrs.SEC_WEBSOCKET_KEY, + hdrs.SEC_WEBSOCKET_KEY1, + hdrs.SEC_WEBSOCKET_PROTOCOL, + hdrs.SEC_WEBSOCKET_VERSION, + hdrs.SERVER, + hdrs.SET_COOKIE, + hdrs.TE, + hdrs.TRAILER, + hdrs.TRANSFER_ENCODING, + hdrs.URI, + hdrs.UPGRADE, + hdrs.USER_AGENT, + hdrs.VARY, + hdrs.VIA, + hdrs.WWW_AUTHENTICATE, + hdrs.WANT_DIGEST, + hdrs.WARNING, + hdrs.X_FORWARDED_FOR, + hdrs.X_FORWARDED_HOST, + hdrs.X_FORWARDED_PROTO, +) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_http_parser.pyx b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_http_parser.pyx new file mode 100644 index 0000000000000000000000000000000000000000..5da835bc642644ece2399a5269f351bc3961cacd --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_http_parser.pyx @@ -0,0 +1,877 @@ +# Based on https://github.com/MagicStack/httptools +# + +from cpython cimport ( + Py_buffer, + PyBUF_SIMPLE, + PyBuffer_Release, + PyBytes_AsString, + PyBytes_AsStringAndSize, + PyObject_GetBuffer, +) +from cpython.mem cimport PyMem_Free, PyMem_Malloc +from libc.limits cimport ULLONG_MAX +from libc.string cimport memcpy + +from multidict import CIMultiDict as _CIMultiDict, CIMultiDictProxy as _CIMultiDictProxy +from yarl import URL as _URL + +from aiohttp import hdrs +from aiohttp.helpers import DEBUG, set_exception + +from .http_exceptions import ( + BadHttpMessage, + BadHttpMethod, + BadStatusLine, + ContentLengthError, + InvalidHeader, + InvalidURLError, + LineTooLong, + PayloadEncodingError, + TransferEncodingError, +) +from .http_parser import DeflateBuffer as _DeflateBuffer +from .http_writer import ( + HttpVersion as _HttpVersion, + HttpVersion10 as _HttpVersion10, + HttpVersion11 as _HttpVersion11, +) +from .streams import EMPTY_PAYLOAD as _EMPTY_PAYLOAD, StreamReader as _StreamReader + +cimport cython + +from aiohttp cimport _cparser as cparser + +include "_headers.pxi" + +from aiohttp cimport _find_header + +ALLOWED_UPGRADES = frozenset({"websocket"}) +DEF DEFAULT_FREELIST_SIZE = 250 + +cdef extern from "Python.h": + int PyByteArray_Resize(object, Py_ssize_t) except -1 + Py_ssize_t PyByteArray_Size(object) except -1 + char* PyByteArray_AsString(object) + +__all__ = ('HttpRequestParser', 'HttpResponseParser', + 'RawRequestMessage', 'RawResponseMessage') + +cdef object URL = _URL +cdef object URL_build = URL.build +cdef object CIMultiDict = _CIMultiDict +cdef object CIMultiDictProxy = _CIMultiDictProxy +cdef object HttpVersion = _HttpVersion +cdef object HttpVersion10 = _HttpVersion10 +cdef object HttpVersion11 = _HttpVersion11 +cdef object SEC_WEBSOCKET_KEY1 = hdrs.SEC_WEBSOCKET_KEY1 +cdef object CONTENT_ENCODING = hdrs.CONTENT_ENCODING +cdef object EMPTY_PAYLOAD = _EMPTY_PAYLOAD +cdef object StreamReader = _StreamReader +cdef object DeflateBuffer = _DeflateBuffer +cdef bytes EMPTY_BYTES = b"" + +# RFC 9110 singleton headers — duplicates are rejected in strict mode. +# In lax mode (response parser default), the check is skipped entirely +# since real-world servers (e.g. Google APIs, Werkzeug) commonly send +# duplicate headers like Content-Type or Server. +cdef frozenset SINGLETON_HEADERS = frozenset({ + hdrs.CONTENT_LENGTH, + hdrs.CONTENT_LOCATION, + hdrs.CONTENT_RANGE, + hdrs.CONTENT_TYPE, + hdrs.ETAG, + hdrs.HOST, + hdrs.MAX_FORWARDS, + hdrs.SERVER, + hdrs.TRANSFER_ENCODING, + hdrs.USER_AGENT, +}) + +cdef inline object extend(object buf, const char* at, size_t length): + cdef Py_ssize_t s + cdef char* ptr + s = PyByteArray_Size(buf) + PyByteArray_Resize(buf, s + length) + ptr = PyByteArray_AsString(buf) + memcpy(ptr + s, at, length) + + +DEF METHODS_COUNT = 46; + +cdef list _http_method = [] + +for i in range(METHODS_COUNT): + _http_method.append( + cparser.llhttp_method_name( i).decode('ascii')) + + +cdef inline str http_method_str(int i): + if i < METHODS_COUNT: + return _http_method[i] + else: + return "" + +cdef inline object find_header(bytes raw_header): + cdef Py_ssize_t size + cdef char *buf + cdef int idx + PyBytes_AsStringAndSize(raw_header, &buf, &size) + idx = _find_header.find_header(buf, size) + if idx == -1: + return raw_header.decode('utf-8', 'surrogateescape') + return headers[idx] + + +@cython.freelist(DEFAULT_FREELIST_SIZE) +cdef class RawRequestMessage: + cdef readonly str method + cdef readonly str path + cdef readonly object version # HttpVersion + cdef readonly object headers # CIMultiDict + cdef readonly object raw_headers # tuple + cdef readonly object should_close + cdef readonly object compression + cdef readonly object upgrade + cdef readonly object chunked + cdef readonly object url # yarl.URL + + def __init__(self, method, path, version, headers, raw_headers, + should_close, compression, upgrade, chunked, url): + self.method = method + self.path = path + self.version = version + self.headers = headers + self.raw_headers = raw_headers + self.should_close = should_close + self.compression = compression + self.upgrade = upgrade + self.chunked = chunked + self.url = url + + def __repr__(self): + info = [] + info.append(("method", self.method)) + info.append(("path", self.path)) + info.append(("version", self.version)) + info.append(("headers", self.headers)) + info.append(("raw_headers", self.raw_headers)) + info.append(("should_close", self.should_close)) + info.append(("compression", self.compression)) + info.append(("upgrade", self.upgrade)) + info.append(("chunked", self.chunked)) + info.append(("url", self.url)) + sinfo = ', '.join(name + '=' + repr(val) for name, val in info) + return '' + + def _replace(self, **dct): + cdef RawRequestMessage ret + ret = _new_request_message(self.method, + self.path, + self.version, + self.headers, + self.raw_headers, + self.should_close, + self.compression, + self.upgrade, + self.chunked, + self.url) + if "method" in dct: + ret.method = dct["method"] + if "path" in dct: + ret.path = dct["path"] + if "version" in dct: + ret.version = dct["version"] + if "headers" in dct: + ret.headers = dct["headers"] + if "raw_headers" in dct: + ret.raw_headers = dct["raw_headers"] + if "should_close" in dct: + ret.should_close = dct["should_close"] + if "compression" in dct: + ret.compression = dct["compression"] + if "upgrade" in dct: + ret.upgrade = dct["upgrade"] + if "chunked" in dct: + ret.chunked = dct["chunked"] + if "url" in dct: + ret.url = dct["url"] + return ret + +cdef _new_request_message(str method, + str path, + object version, + object headers, + object raw_headers, + bint should_close, + object compression, + bint upgrade, + bint chunked, + object url): + cdef RawRequestMessage ret + ret = RawRequestMessage.__new__(RawRequestMessage) + ret.method = method + ret.path = path + ret.version = version + ret.headers = headers + ret.raw_headers = raw_headers + ret.should_close = should_close + ret.compression = compression + ret.upgrade = upgrade + ret.chunked = chunked + ret.url = url + return ret + + +@cython.freelist(DEFAULT_FREELIST_SIZE) +cdef class RawResponseMessage: + cdef readonly object version # HttpVersion + cdef readonly int code + cdef readonly str reason + cdef readonly object headers # CIMultiDict + cdef readonly object raw_headers # tuple + cdef readonly object should_close + cdef readonly object compression + cdef readonly object upgrade + cdef readonly object chunked + + def __init__(self, version, code, reason, headers, raw_headers, + should_close, compression, upgrade, chunked): + self.version = version + self.code = code + self.reason = reason + self.headers = headers + self.raw_headers = raw_headers + self.should_close = should_close + self.compression = compression + self.upgrade = upgrade + self.chunked = chunked + + def __repr__(self): + info = [] + info.append(("version", self.version)) + info.append(("code", self.code)) + info.append(("reason", self.reason)) + info.append(("headers", self.headers)) + info.append(("raw_headers", self.raw_headers)) + info.append(("should_close", self.should_close)) + info.append(("compression", self.compression)) + info.append(("upgrade", self.upgrade)) + info.append(("chunked", self.chunked)) + sinfo = ', '.join(name + '=' + repr(val) for name, val in info) + return '' + + +cdef _new_response_message(object version, + int code, + str reason, + object headers, + object raw_headers, + bint should_close, + object compression, + bint upgrade, + bint chunked): + cdef RawResponseMessage ret + ret = RawResponseMessage.__new__(RawResponseMessage) + ret.version = version + ret.code = code + ret.reason = reason + ret.headers = headers + ret.raw_headers = raw_headers + ret.should_close = should_close + ret.compression = compression + ret.upgrade = upgrade + ret.chunked = chunked + return ret + + +@cython.internal +cdef class HttpParser: + + cdef: + cparser.llhttp_t* _cparser + cparser.llhttp_settings_t* _csettings + + bytes _raw_name + object _name + bytes _raw_value + bint _has_value + int _header_name_size + + object _protocol + object _loop + object _timer + + size_t _max_line_size + size_t _max_field_size + size_t _max_headers + bint _response_with_body + bint _read_until_eof + bint _lax + + bint _started + object _url + bytearray _buf + str _path + str _reason + list _headers + set _seen_singletons + list _raw_headers + bint _upgraded + list _messages + object _payload + bint _payload_error + object _payload_exception + object _last_error + bint _auto_decompress + int _limit + + str _content_encoding + + Py_buffer py_buf + + def __cinit__(self): + self._cparser = \ + PyMem_Malloc(sizeof(cparser.llhttp_t)) + if self._cparser is NULL: + raise MemoryError() + + self._csettings = \ + PyMem_Malloc(sizeof(cparser.llhttp_settings_t)) + if self._csettings is NULL: + raise MemoryError() + + def __dealloc__(self): + PyMem_Free(self._cparser) + PyMem_Free(self._csettings) + + cdef _init( + self, cparser.llhttp_type mode, + object protocol, object loop, int limit, + object timer=None, + size_t max_line_size=8190, size_t max_headers=128, + size_t max_field_size=8190, payload_exception=None, + bint response_with_body=True, bint read_until_eof=False, + bint auto_decompress=True, + ): + cparser.llhttp_settings_init(self._csettings) + cparser.llhttp_init(self._cparser, mode, self._csettings) + self._cparser.data = self + self._cparser.content_length = 0 + + self._protocol = protocol + self._loop = loop + self._timer = timer + + self._buf = bytearray() + self._payload = None + self._payload_error = 0 + self._payload_exception = payload_exception + self._messages = [] + + self._raw_name = EMPTY_BYTES + self._raw_value = EMPTY_BYTES + self._has_value = False + self._header_name_size = 0 + + self._max_line_size = max_line_size + self._max_headers = max_headers + self._max_field_size = max_field_size + self._response_with_body = response_with_body + self._read_until_eof = read_until_eof + self._upgraded = False + self._auto_decompress = auto_decompress + self._content_encoding = None + self._lax = False + self._seen_singletons = set() + + self._csettings.on_url = cb_on_url + self._csettings.on_status = cb_on_status + self._csettings.on_header_field = cb_on_header_field + self._csettings.on_header_value = cb_on_header_value + self._csettings.on_headers_complete = cb_on_headers_complete + self._csettings.on_body = cb_on_body + self._csettings.on_message_begin = cb_on_message_begin + self._csettings.on_message_complete = cb_on_message_complete + self._csettings.on_chunk_header = cb_on_chunk_header + self._csettings.on_chunk_complete = cb_on_chunk_complete + + self._last_error = None + self._limit = limit + + cdef _process_header(self): + cdef str value + if self._raw_name is not EMPTY_BYTES: + name = find_header(self._raw_name) + value = self._raw_value.decode('utf-8', 'surrogateescape') + + # reject null bytes in header values - matches the Python parser + # check at http_parser.py. llhttp in lenient mode doesn't reject + # these itself, so we need to catch them here. + # ref: RFC 9110 section 5.5 (CTL chars forbidden in field values) + if "\x00" in value: + raise InvalidHeader(self._raw_value) + + if not self._lax and name in SINGLETON_HEADERS: + if name in self._seen_singletons: + raise BadHttpMessage(f"Duplicate '{name}' header found.") + self._seen_singletons.add(name) + self._headers.append((name, value)) + if len(self._headers) > self._max_headers: + raise BadHttpMessage("Too many headers received") + + if name is CONTENT_ENCODING: + self._content_encoding = value + + self._has_value = False + self._header_name_size = 0 + self._raw_headers.append((self._raw_name, self._raw_value)) + self._raw_name = EMPTY_BYTES + self._raw_value = EMPTY_BYTES + + cdef _on_header_field(self, char* at, size_t length): + if self._has_value: + self._process_header() + + if self._raw_name is EMPTY_BYTES: + self._raw_name = at[:length] + else: + self._raw_name += at[:length] + + cdef _on_header_value(self, char* at, size_t length): + if self._raw_value is EMPTY_BYTES: + self._raw_value = at[:length] + else: + self._raw_value += at[:length] + self._has_value = True + + cdef _on_headers_complete(self): + self._process_header() + + should_close = not cparser.llhttp_should_keep_alive(self._cparser) + upgrade = self._cparser.upgrade + chunked = self._cparser.flags & cparser.F_CHUNKED + + raw_headers = tuple(self._raw_headers) + headers = CIMultiDictProxy(CIMultiDict(self._headers)) + + if self._cparser.type == cparser.HTTP_REQUEST: + h_upg = headers.get("upgrade", "") + allowed = upgrade and h_upg.isascii() and h_upg.lower() in ALLOWED_UPGRADES + if allowed or self._cparser.method == cparser.HTTP_CONNECT: + self._upgraded = True + else: + if upgrade and self._cparser.status_code == 101: + self._upgraded = True + + # do not support old websocket spec + if SEC_WEBSOCKET_KEY1 in headers: + raise InvalidHeader(SEC_WEBSOCKET_KEY1) + + encoding = None + enc = self._content_encoding + if enc is not None: + self._content_encoding = None + if enc.isascii() and enc.lower() in {"gzip", "deflate", "br", "zstd"}: + encoding = enc + + if self._cparser.type == cparser.HTTP_REQUEST: + method = http_method_str(self._cparser.method) + msg = _new_request_message( + method, self._path, + self.http_version(), headers, raw_headers, + should_close, encoding, upgrade, chunked, self._url) + else: + msg = _new_response_message( + self.http_version(), self._cparser.status_code, self._reason, + headers, raw_headers, should_close, encoding, + upgrade, chunked) + + if ( + ULLONG_MAX > self._cparser.content_length > 0 or chunked or + self._cparser.method == cparser.HTTP_CONNECT or + (self._cparser.status_code >= 199 and + self._cparser.content_length == 0 and + self._read_until_eof) + ): + payload = StreamReader( + self._protocol, timer=self._timer, loop=self._loop, + limit=self._limit) + else: + payload = EMPTY_PAYLOAD + + self._payload = payload + if encoding is not None and self._auto_decompress: + self._payload = DeflateBuffer(payload, encoding) + + if not self._response_with_body: + payload = EMPTY_PAYLOAD + + self._messages.append((msg, payload)) + + cdef _on_message_complete(self): + self._payload.feed_eof() + self._payload = None + + cdef _on_chunk_header(self): + self._payload.begin_http_chunk_receiving() + + cdef _on_chunk_complete(self): + self._payload.end_http_chunk_receiving() + + cdef object _on_status_complete(self): + pass + + cdef inline http_version(self): + cdef cparser.llhttp_t* parser = self._cparser + + if parser.http_major == 1: + if parser.http_minor == 0: + return HttpVersion10 + elif parser.http_minor == 1: + return HttpVersion11 + + return HttpVersion(parser.http_major, parser.http_minor) + + ### Public API ### + + def feed_eof(self): + cdef bytes desc + + if self._payload is not None: + if self._cparser.flags & cparser.F_CHUNKED: + raise TransferEncodingError( + "Not enough data to satisfy transfer length header.") + elif self._cparser.flags & cparser.F_CONTENT_LENGTH: + raise ContentLengthError( + "Not enough data to satisfy content length header.") + elif cparser.llhttp_get_errno(self._cparser) != cparser.HPE_OK: + desc = cparser.llhttp_get_error_reason(self._cparser) + raise PayloadEncodingError(desc.decode('latin-1')) + else: + self._payload.feed_eof() + elif self._started: + self._on_headers_complete() + if self._messages: + return self._messages[-1][0] + + def feed_data(self, data): + cdef: + size_t data_len + size_t nb + char* base + cdef cparser.llhttp_errno_t errno + + PyObject_GetBuffer(data, &self.py_buf, PyBUF_SIMPLE) + # Cache buffer pointer before PyBuffer_Release to avoid use-after-release. + base = self.py_buf.buf + data_len = self.py_buf.len + + errno = cparser.llhttp_execute( + self._cparser, + base, + data_len) + + if errno is cparser.HPE_PAUSED_UPGRADE: + cparser.llhttp_resume_after_upgrade(self._cparser) + + nb = cparser.llhttp_get_error_pos(self._cparser) - base + + PyBuffer_Release(&self.py_buf) + + if errno not in (cparser.HPE_OK, cparser.HPE_PAUSED_UPGRADE): + if self._payload_error == 0: + if self._last_error is not None: + ex = self._last_error + self._last_error = None + else: + after = cparser.llhttp_get_error_pos(self._cparser) + before = data[:after - base] + after_b = after.split(b"\r\n", 1)[0] + before = before.rsplit(b"\r\n", 1)[-1] + data = before + after_b + pointer = " " * (len(repr(before))-1) + "^" + ex = parser_error_from_errno(self._cparser, data, pointer) + self._payload = None + raise ex + + if self._messages: + messages = self._messages + self._messages = [] + else: + messages = () + + if self._upgraded: + return messages, True, data[nb:] + else: + return messages, False, b"" + + def set_upgraded(self, val): + self._upgraded = val + + +cdef class HttpRequestParser(HttpParser): + + def __init__( + self, protocol, loop, int limit, timer=None, + size_t max_line_size=8190, size_t max_headers=128, + size_t max_field_size=8190, payload_exception=None, + bint response_with_body=True, bint read_until_eof=False, + bint auto_decompress=True, + ): + self._init(cparser.HTTP_REQUEST, protocol, loop, limit, timer, + max_line_size, max_headers, max_field_size, + payload_exception, response_with_body, read_until_eof, + auto_decompress) + + cdef object _on_status_complete(self): + cdef int idx1, idx2 + if not self._buf: + return + self._path = self._buf.decode('utf-8', 'surrogateescape') + try: + idx3 = len(self._path) + if self._cparser.method == cparser.HTTP_CONNECT: + # authority-form, + # https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.3 + self._url = URL.build(authority=self._path, encoded=True) + elif idx3 > 1 and self._path[0] == '/': + # origin-form, + # https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.1 + idx1 = self._path.find("?") + if idx1 == -1: + query = "" + idx2 = self._path.find("#") + if idx2 == -1: + path = self._path + fragment = "" + else: + path = self._path[0: idx2] + fragment = self._path[idx2+1:] + + else: + path = self._path[0:idx1] + idx1 += 1 + idx2 = self._path.find("#", idx1+1) + if idx2 == -1: + query = self._path[idx1:] + fragment = "" + else: + query = self._path[idx1: idx2] + fragment = self._path[idx2+1:] + + self._url = URL.build( + path=path, + query_string=query, + fragment=fragment, + encoded=True, + ) + else: + # absolute-form for proxy maybe, + # https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.2 + self._url = URL(self._path, encoded=True) + finally: + PyByteArray_Resize(self._buf, 0) + + +cdef class HttpResponseParser(HttpParser): + + def __init__( + self, protocol, loop, int limit, timer=None, + size_t max_line_size=8190, size_t max_headers=128, + size_t max_field_size=8190, payload_exception=None, + bint response_with_body=True, bint read_until_eof=False, + bint auto_decompress=True + ): + self._init(cparser.HTTP_RESPONSE, protocol, loop, limit, timer, + max_line_size, max_headers, max_field_size, + payload_exception, response_with_body, read_until_eof, + auto_decompress) + # Use strict parsing on dev mode, so users are warned about broken servers. + if not DEBUG: + cparser.llhttp_set_lenient_headers(self._cparser, 1) + cparser.llhttp_set_lenient_optional_cr_before_lf(self._cparser, 1) + cparser.llhttp_set_lenient_spaces_after_chunk_size(self._cparser, 1) + self._lax = True + + cdef object _on_status_complete(self): + if self._buf: + self._reason = self._buf.decode('utf-8', 'surrogateescape') + PyByteArray_Resize(self._buf, 0) + else: + self._reason = self._reason or '' + +cdef int cb_on_message_begin(cparser.llhttp_t* parser) except -1: + cdef HttpParser pyparser = parser.data + + pyparser._started = True + pyparser._headers = [] + pyparser._seen_singletons = set() + pyparser._raw_headers = [] + PyByteArray_Resize(pyparser._buf, 0) + pyparser._path = None + pyparser._reason = None + return 0 + + +cdef int cb_on_url(cparser.llhttp_t* parser, + const char *at, size_t length) except -1: + cdef HttpParser pyparser = parser.data + try: + if length > pyparser._max_line_size: + status = pyparser._buf + at[:length] + raise LineTooLong(status[:100] + b"...", pyparser._max_line_size) + extend(pyparser._buf, at, length) + except BaseException as ex: + pyparser._last_error = ex + return -1 + else: + return 0 + + +cdef int cb_on_status(cparser.llhttp_t* parser, + const char *at, size_t length) except -1: + cdef HttpParser pyparser = parser.data + try: + if length > pyparser._max_line_size: + reason = pyparser._buf + at[:length] + raise LineTooLong(reason[:100] + b"...", pyparser._max_line_size) + extend(pyparser._buf, at, length) + except BaseException as ex: + pyparser._last_error = ex + return -1 + else: + return 0 + + +cdef int cb_on_header_field(cparser.llhttp_t* parser, + const char *at, size_t length) except -1: + cdef HttpParser pyparser = parser.data + cdef Py_ssize_t size + try: + pyparser._on_status_complete() + size = len(pyparser._raw_name) + length + if size > pyparser._max_field_size: + name = pyparser._raw_name + at[:length] + raise LineTooLong(name[:100] + b"...", pyparser._max_field_size) + pyparser._header_name_size = size + pyparser._on_header_field(at, length) + except BaseException as ex: + pyparser._last_error = ex + return -1 + else: + return 0 + + +cdef int cb_on_header_value(cparser.llhttp_t* parser, + const char *at, size_t length) except -1: + cdef HttpParser pyparser = parser.data + cdef Py_ssize_t size + try: + size = len(pyparser._raw_value) + length + if pyparser._header_name_size + size > pyparser._max_field_size: + value = pyparser._raw_value + at[:length] + raise LineTooLong(value[:100] + b"...", pyparser._max_field_size) + pyparser._on_header_value(at, length) + except BaseException as ex: + pyparser._last_error = ex + return -1 + else: + return 0 + + +cdef int cb_on_headers_complete(cparser.llhttp_t* parser) except -1: + cdef HttpParser pyparser = parser.data + try: + pyparser._on_status_complete() + pyparser._on_headers_complete() + except BaseException as exc: + pyparser._last_error = exc + return -1 + else: + if pyparser._upgraded or pyparser._cparser.method == cparser.HTTP_CONNECT: + return 2 + else: + return 0 + + +cdef int cb_on_body(cparser.llhttp_t* parser, + const char *at, size_t length) except -1: + cdef HttpParser pyparser = parser.data + cdef bytes body = at[:length] + try: + pyparser._payload.feed_data(body, length) + except BaseException as underlying_exc: + reraised_exc = underlying_exc + if pyparser._payload_exception is not None: + reraised_exc = pyparser._payload_exception(str(underlying_exc)) + + set_exception(pyparser._payload, reraised_exc, underlying_exc) + + pyparser._payload_error = 1 + return -1 + else: + return 0 + + +cdef int cb_on_message_complete(cparser.llhttp_t* parser) except -1: + cdef HttpParser pyparser = parser.data + try: + pyparser._started = False + pyparser._on_message_complete() + except BaseException as exc: + pyparser._last_error = exc + return -1 + else: + return 0 + + +cdef int cb_on_chunk_header(cparser.llhttp_t* parser) except -1: + cdef HttpParser pyparser = parser.data + try: + pyparser._on_chunk_header() + except BaseException as exc: + pyparser._last_error = exc + return -1 + else: + return 0 + + +cdef int cb_on_chunk_complete(cparser.llhttp_t* parser) except -1: + cdef HttpParser pyparser = parser.data + try: + pyparser._on_chunk_complete() + except BaseException as exc: + pyparser._last_error = exc + return -1 + else: + return 0 + + +cdef parser_error_from_errno(cparser.llhttp_t* parser, data, pointer): + cdef cparser.llhttp_errno_t errno = cparser.llhttp_get_errno(parser) + cdef bytes desc = cparser.llhttp_get_error_reason(parser) + + err_msg = "{}:\n\n {!r}\n {}".format(desc.decode("latin-1"), data, pointer) + + if errno in {cparser.HPE_CB_MESSAGE_BEGIN, + cparser.HPE_CB_HEADERS_COMPLETE, + cparser.HPE_CB_MESSAGE_COMPLETE, + cparser.HPE_CB_CHUNK_HEADER, + cparser.HPE_CB_CHUNK_COMPLETE, + cparser.HPE_INVALID_CONSTANT, + cparser.HPE_INVALID_HEADER_TOKEN, + cparser.HPE_INVALID_CONTENT_LENGTH, + cparser.HPE_INVALID_CHUNK_SIZE, + cparser.HPE_INVALID_EOF_STATE, + cparser.HPE_INVALID_TRANSFER_ENCODING}: + return BadHttpMessage(err_msg) + elif errno == cparser.HPE_INVALID_METHOD: + return BadHttpMethod(error=err_msg) + elif errno in {cparser.HPE_INVALID_STATUS, + cparser.HPE_INVALID_VERSION}: + return BadStatusLine(error=err_msg) + elif errno == cparser.HPE_INVALID_URL: + return InvalidURLError(err_msg) + + return BadHttpMessage(err_msg) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_http_writer.pyx b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_http_writer.pyx new file mode 100644 index 0000000000000000000000000000000000000000..ee8dcd7d2e97cba201c34c0cfab830b00496b6bc --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_http_writer.pyx @@ -0,0 +1,162 @@ +from cpython.bytes cimport PyBytes_FromStringAndSize +from cpython.exc cimport PyErr_NoMemory +from cpython.mem cimport PyMem_Free, PyMem_Malloc, PyMem_Realloc +from cpython.object cimport PyObject_Str +from libc.stdint cimport uint8_t, uint64_t +from libc.string cimport memcpy + +from multidict import istr + +DEF BUF_SIZE = 16 * 1024 # 16KiB + +cdef object _istr = istr + + +# ----------------- writer --------------------------- + +cdef struct Writer: + char *buf + Py_ssize_t size + Py_ssize_t pos + bint heap_allocated + +cdef inline void _init_writer(Writer* writer, char *buf): + writer.buf = buf + writer.size = BUF_SIZE + writer.pos = 0 + writer.heap_allocated = 0 + + +cdef inline void _release_writer(Writer* writer): + if writer.heap_allocated: + PyMem_Free(writer.buf) + + +cdef inline int _write_byte(Writer* writer, uint8_t ch): + cdef char * buf + cdef Py_ssize_t size + + if writer.pos == writer.size: + # reallocate + size = writer.size + BUF_SIZE + if not writer.heap_allocated: + buf = PyMem_Malloc(size) + if buf == NULL: + PyErr_NoMemory() + return -1 + memcpy(buf, writer.buf, writer.size) + else: + buf = PyMem_Realloc(writer.buf, size) + if buf == NULL: + PyErr_NoMemory() + return -1 + writer.buf = buf + writer.size = size + writer.heap_allocated = 1 + writer.buf[writer.pos] = ch + writer.pos += 1 + return 0 + + +cdef inline int _write_utf8(Writer* writer, Py_UCS4 symbol): + cdef uint64_t utf = symbol + + if utf < 0x80: + return _write_byte(writer, utf) + elif utf < 0x800: + if _write_byte(writer, (0xc0 | (utf >> 6))) < 0: + return -1 + return _write_byte(writer, (0x80 | (utf & 0x3f))) + elif 0xD800 <= utf <= 0xDFFF: + # surogate pair, ignored + return 0 + elif utf < 0x10000: + if _write_byte(writer, (0xe0 | (utf >> 12))) < 0: + return -1 + if _write_byte(writer, (0x80 | ((utf >> 6) & 0x3f))) < 0: + return -1 + return _write_byte(writer, (0x80 | (utf & 0x3f))) + elif utf > 0x10FFFF: + # symbol is too large + return 0 + else: + if _write_byte(writer, (0xf0 | (utf >> 18))) < 0: + return -1 + if _write_byte(writer, + (0x80 | ((utf >> 12) & 0x3f))) < 0: + return -1 + if _write_byte(writer, + (0x80 | ((utf >> 6) & 0x3f))) < 0: + return -1 + return _write_byte(writer, (0x80 | (utf & 0x3f))) + + +cdef inline int _write_str(Writer* writer, str s): + cdef Py_UCS4 ch + for ch in s: + if _write_utf8(writer, ch) < 0: + return -1 + + +cdef inline int _write_str_raise_on_nlcr(Writer* writer, object s): + cdef Py_UCS4 ch + cdef str out_str + if type(s) is str: + out_str = s + elif type(s) is _istr: + out_str = PyObject_Str(s) + elif not isinstance(s, str): + raise TypeError("Cannot serialize non-str key {!r}".format(s)) + else: + out_str = str(s) + + for ch in out_str: + if ch in {0x0D, 0x0A, 0x00}: + raise ValueError( + "Newline, carriage return, or null byte detected in headers. " + "Potential header injection attack." + ) + if _write_utf8(writer, ch) < 0: + return -1 + + +# --------------- _serialize_headers ---------------------- + +def _serialize_headers(str status_line, headers): + cdef Writer writer + cdef object key + cdef object val + cdef char buf[BUF_SIZE] + + _init_writer(&writer, buf) + + try: + if _write_str_raise_on_nlcr(&writer, status_line) < 0: + raise + if _write_byte(&writer, b'\r') < 0: + raise + if _write_byte(&writer, b'\n') < 0: + raise + + for key, val in headers.items(): + if _write_str_raise_on_nlcr(&writer, key) < 0: + raise + if _write_byte(&writer, b':') < 0: + raise + if _write_byte(&writer, b' ') < 0: + raise + if _write_str_raise_on_nlcr(&writer, val) < 0: + raise + if _write_byte(&writer, b'\r') < 0: + raise + if _write_byte(&writer, b'\n') < 0: + raise + + if _write_byte(&writer, b'\r') < 0: + raise + if _write_byte(&writer, b'\n') < 0: + raise + + return PyBytes_FromStringAndSize(writer.buf, writer.pos) + finally: + _release_writer(&writer) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_websocket/.hash/mask.pxd.hash b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_websocket/.hash/mask.pxd.hash new file mode 100644 index 0000000000000000000000000000000000000000..eadfed3d48b4781b271296d8864672a9b46b73c8 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_websocket/.hash/mask.pxd.hash @@ -0,0 +1 @@ +b01999d409b29bd916e067bc963d5f2d9ee63cfc9ae0bccb769910131417bf93 /home/runner/work/aiohttp/aiohttp/aiohttp/_websocket/mask.pxd diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_websocket/.hash/mask.pyx.hash b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_websocket/.hash/mask.pyx.hash new file mode 100644 index 0000000000000000000000000000000000000000..5cd7ae67746cbdcc347183a9fdb1aa24a6f89063 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_websocket/.hash/mask.pyx.hash @@ -0,0 +1 @@ +0478ceb55d0ed30ef1a7da742cd003449bc69a07cf9fdb06789bd2b347cbfffe /home/runner/work/aiohttp/aiohttp/aiohttp/_websocket/mask.pyx diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_websocket/.hash/reader_c.pxd.hash b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_websocket/.hash/reader_c.pxd.hash new file mode 100644 index 0000000000000000000000000000000000000000..f2feb6aad915eb2dfbe1803aec0f0095f67fb5d6 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_websocket/.hash/reader_c.pxd.hash @@ -0,0 +1 @@ +9e5fe78ed0ebce5414d2b8e01868d90c1facc20b84d2d5ff6c23e86e44a155ae /home/runner/work/aiohttp/aiohttp/aiohttp/_websocket/reader_c.pxd diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_websocket/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_websocket/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..836257cc47aba3e74863c7de0e098d0835bcee1f --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_websocket/__init__.py @@ -0,0 +1 @@ +"""WebSocket protocol versions 13 and 8.""" diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_websocket/helpers.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_websocket/helpers.py new file mode 100644 index 0000000000000000000000000000000000000000..0bb58df9228603fc1eb79a7a2cac8301217a36a1 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_websocket/helpers.py @@ -0,0 +1,147 @@ +"""Helpers for WebSocket protocol versions 13 and 8.""" + +import functools +import re +from struct import Struct +from typing import TYPE_CHECKING, Final, List, Optional, Pattern, Tuple + +from ..helpers import NO_EXTENSIONS +from .models import WSHandshakeError + +UNPACK_LEN3 = Struct("!Q").unpack_from +UNPACK_CLOSE_CODE = Struct("!H").unpack +PACK_LEN1 = Struct("!BB").pack +PACK_LEN2 = Struct("!BBH").pack +PACK_LEN3 = Struct("!BBQ").pack +PACK_CLOSE_CODE = Struct("!H").pack +PACK_RANDBITS = Struct("!L").pack +MSG_SIZE: Final[int] = 2**14 +MASK_LEN: Final[int] = 4 + +WS_KEY: Final[bytes] = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11" + + +# Used by _websocket_mask_python +@functools.lru_cache +def _xor_table() -> List[bytes]: + return [bytes(a ^ b for a in range(256)) for b in range(256)] + + +def _websocket_mask_python(mask: bytes, data: bytearray) -> None: + """Websocket masking function. + + `mask` is a `bytes` object of length 4; `data` is a `bytearray` + object of any length. The contents of `data` are masked with `mask`, + as specified in section 5.3 of RFC 6455. + + Note that this function mutates the `data` argument. + + This pure-python implementation may be replaced by an optimized + version when available. + + """ + assert isinstance(data, bytearray), data + assert len(mask) == 4, mask + + if data: + _XOR_TABLE = _xor_table() + a, b, c, d = (_XOR_TABLE[n] for n in mask) + data[::4] = data[::4].translate(a) + data[1::4] = data[1::4].translate(b) + data[2::4] = data[2::4].translate(c) + data[3::4] = data[3::4].translate(d) + + +if TYPE_CHECKING or NO_EXTENSIONS: # pragma: no cover + websocket_mask = _websocket_mask_python +else: + try: + from .mask import _websocket_mask_cython # type: ignore[import-not-found] + + websocket_mask = _websocket_mask_cython + except ImportError: # pragma: no cover + websocket_mask = _websocket_mask_python + + +_WS_EXT_RE: Final[Pattern[str]] = re.compile( + r"^(?:;\s*(?:" + r"(server_no_context_takeover)|" + r"(client_no_context_takeover)|" + r"(server_max_window_bits(?:=(\d+))?)|" + r"(client_max_window_bits(?:=(\d+))?)))*$" +) + +_WS_EXT_RE_SPLIT: Final[Pattern[str]] = re.compile(r"permessage-deflate([^,]+)?") + + +def ws_ext_parse(extstr: Optional[str], isserver: bool = False) -> Tuple[int, bool]: + if not extstr: + return 0, False + + compress = 0 + notakeover = False + for ext in _WS_EXT_RE_SPLIT.finditer(extstr): + defext = ext.group(1) + # Return compress = 15 when get `permessage-deflate` + if not defext: + compress = 15 + break + match = _WS_EXT_RE.match(defext) + if match: + compress = 15 + if isserver: + # Server never fail to detect compress handshake. + # Server does not need to send max wbit to client + if match.group(4): + compress = int(match.group(4)) + # Group3 must match if group4 matches + # Compress wbit 8 does not support in zlib + # If compress level not support, + # CONTINUE to next extension + if compress > 15 or compress < 9: + compress = 0 + continue + if match.group(1): + notakeover = True + # Ignore regex group 5 & 6 for client_max_window_bits + break + else: + if match.group(6): + compress = int(match.group(6)) + # Group5 must match if group6 matches + # Compress wbit 8 does not support in zlib + # If compress level not support, + # FAIL the parse progress + if compress > 15 or compress < 9: + raise WSHandshakeError("Invalid window size") + if match.group(2): + notakeover = True + # Ignore regex group 5 & 6 for client_max_window_bits + break + # Return Fail if client side and not match + elif not isserver: + raise WSHandshakeError("Extension for deflate not supported" + ext.group(1)) + + return compress, notakeover + + +def ws_ext_gen( + compress: int = 15, isserver: bool = False, server_notakeover: bool = False +) -> str: + # client_notakeover=False not used for server + # compress wbit 8 does not support in zlib + if compress < 9 or compress > 15: + raise ValueError( + "Compress wbits must between 9 and 15, zlib does not support wbits=8" + ) + enabledext = ["permessage-deflate"] + if not isserver: + enabledext.append("client_max_window_bits") + + if compress < 15: + enabledext.append("server_max_window_bits=" + str(compress)) + if server_notakeover: + enabledext.append("server_no_context_takeover") + # if client_notakeover: + # enabledext.append('client_no_context_takeover') + return "; ".join(enabledext) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_websocket/mask.pxd b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_websocket/mask.pxd new file mode 100644 index 0000000000000000000000000000000000000000..90983de9ac7e59dfceb639c1e7b656abd5fbb305 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_websocket/mask.pxd @@ -0,0 +1,3 @@ +"""Cython declarations for websocket masking.""" + +cpdef void _websocket_mask_cython(bytes mask, bytearray data) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_websocket/mask.pyx b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_websocket/mask.pyx new file mode 100644 index 0000000000000000000000000000000000000000..2d956c8899644d4c6bce042b928be1f23e51293a --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_websocket/mask.pyx @@ -0,0 +1,48 @@ +from cpython cimport PyBytes_AsString + + +#from cpython cimport PyByteArray_AsString # cython still not exports that +cdef extern from "Python.h": + char* PyByteArray_AsString(bytearray ba) except NULL + +from libc.stdint cimport uint32_t, uint64_t, uintmax_t + + +cpdef void _websocket_mask_cython(bytes mask, bytearray data): + """Note, this function mutates its `data` argument + """ + cdef: + Py_ssize_t data_len, i + # bit operations on signed integers are implementation-specific + unsigned char * in_buf + const unsigned char * mask_buf + uint32_t uint32_msk + uint64_t uint64_msk + + assert len(mask) == 4 + + data_len = len(data) + in_buf = PyByteArray_AsString(data) + mask_buf = PyBytes_AsString(mask) + uint32_msk = (mask_buf)[0] + + # TODO: align in_data ptr to achieve even faster speeds + # does it need in python ?! malloc() always aligns to sizeof(long) bytes + + if sizeof(size_t) >= 8: + uint64_msk = uint32_msk + uint64_msk = (uint64_msk << 32) | uint32_msk + + while data_len >= 8: + (in_buf)[0] ^= uint64_msk + in_buf += 8 + data_len -= 8 + + + while data_len >= 4: + (in_buf)[0] ^= uint32_msk + in_buf += 4 + data_len -= 4 + + for i in range(0, data_len): + in_buf[i] ^= mask_buf[i] diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_websocket/models.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_websocket/models.py new file mode 100644 index 0000000000000000000000000000000000000000..7e89b9652957e8f4e73916e18048368e8d75911e --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_websocket/models.py @@ -0,0 +1,84 @@ +"""Models for WebSocket protocol versions 13 and 8.""" + +import json +from enum import IntEnum +from typing import Any, Callable, Final, NamedTuple, Optional, cast + +WS_DEFLATE_TRAILING: Final[bytes] = bytes([0x00, 0x00, 0xFF, 0xFF]) + + +class WSCloseCode(IntEnum): + OK = 1000 + GOING_AWAY = 1001 + PROTOCOL_ERROR = 1002 + UNSUPPORTED_DATA = 1003 + ABNORMAL_CLOSURE = 1006 + INVALID_TEXT = 1007 + POLICY_VIOLATION = 1008 + MESSAGE_TOO_BIG = 1009 + MANDATORY_EXTENSION = 1010 + INTERNAL_ERROR = 1011 + SERVICE_RESTART = 1012 + TRY_AGAIN_LATER = 1013 + BAD_GATEWAY = 1014 + + +class WSMsgType(IntEnum): + # websocket spec types + CONTINUATION = 0x0 + TEXT = 0x1 + BINARY = 0x2 + PING = 0x9 + PONG = 0xA + CLOSE = 0x8 + + # aiohttp specific types + CLOSING = 0x100 + CLOSED = 0x101 + ERROR = 0x102 + + text = TEXT + binary = BINARY + ping = PING + pong = PONG + close = CLOSE + closing = CLOSING + closed = CLOSED + error = ERROR + + +class WSMessage(NamedTuple): + type: WSMsgType + # To type correctly, this would need some kind of tagged union for each type. + data: Any + extra: Optional[str] + + def json(self, *, loads: Callable[[Any], Any] = json.loads) -> Any: + """Return parsed JSON data. + + .. versionadded:: 0.22 + """ + return loads(self.data) + + +# Constructing the tuple directly to avoid the overhead of +# the lambda and arg processing since NamedTuples are constructed +# with a run time built lambda +# https://github.com/python/cpython/blob/d83fcf8371f2f33c7797bc8f5423a8bca8c46e5c/Lib/collections/__init__.py#L441 +WS_CLOSED_MESSAGE = tuple.__new__(WSMessage, (WSMsgType.CLOSED, None, None)) +WS_CLOSING_MESSAGE = tuple.__new__(WSMessage, (WSMsgType.CLOSING, None, None)) + + +class WebSocketError(Exception): + """WebSocket protocol parser error.""" + + def __init__(self, code: int, message: str) -> None: + self.code = code + super().__init__(code, message) + + def __str__(self) -> str: + return cast(str, self.args[1]) + + +class WSHandshakeError(Exception): + """WebSocket protocol handshake error.""" diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_websocket/reader.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_websocket/reader.py new file mode 100644 index 0000000000000000000000000000000000000000..23f32265cfccbdc8c8fe2f1600accbfb6f816efa --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_websocket/reader.py @@ -0,0 +1,31 @@ +"""Reader for WebSocket protocol versions 13 and 8.""" + +from typing import TYPE_CHECKING + +from ..helpers import NO_EXTENSIONS + +if TYPE_CHECKING or NO_EXTENSIONS: # pragma: no cover + from .reader_py import ( + WebSocketDataQueue as WebSocketDataQueuePython, + WebSocketReader as WebSocketReaderPython, + ) + + WebSocketReader = WebSocketReaderPython + WebSocketDataQueue = WebSocketDataQueuePython +else: + try: + from .reader_c import ( # type: ignore[import-not-found] + WebSocketDataQueue as WebSocketDataQueueCython, + WebSocketReader as WebSocketReaderCython, + ) + + WebSocketReader = WebSocketReaderCython + WebSocketDataQueue = WebSocketDataQueueCython + except ImportError: # pragma: no cover + from .reader_py import ( + WebSocketDataQueue as WebSocketDataQueuePython, + WebSocketReader as WebSocketReaderPython, + ) + + WebSocketReader = WebSocketReaderPython + WebSocketDataQueue = WebSocketDataQueuePython diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_websocket/reader_c.pxd b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_websocket/reader_c.pxd new file mode 100644 index 0000000000000000000000000000000000000000..a7620d8e87fddd189ed9dbb062e6ec34aa8d673e --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_websocket/reader_c.pxd @@ -0,0 +1,110 @@ +import cython + +from .mask cimport _websocket_mask_cython as websocket_mask + + +cdef unsigned int READ_HEADER +cdef unsigned int READ_PAYLOAD_LENGTH +cdef unsigned int READ_PAYLOAD_MASK +cdef unsigned int READ_PAYLOAD + +cdef int OP_CODE_NOT_SET +cdef int OP_CODE_CONTINUATION +cdef int OP_CODE_TEXT +cdef int OP_CODE_BINARY +cdef int OP_CODE_CLOSE +cdef int OP_CODE_PING +cdef int OP_CODE_PONG + +cdef int COMPRESSED_NOT_SET +cdef int COMPRESSED_FALSE +cdef int COMPRESSED_TRUE + +cdef object UNPACK_LEN3 +cdef object UNPACK_CLOSE_CODE +cdef object TUPLE_NEW + +cdef object WSMsgType +cdef object WSMessage + +cdef object WS_MSG_TYPE_TEXT +cdef object WS_MSG_TYPE_BINARY + +cdef set ALLOWED_CLOSE_CODES +cdef set MESSAGE_TYPES_WITH_CONTENT + +cdef tuple EMPTY_FRAME +cdef tuple EMPTY_FRAME_ERROR + +cdef class WebSocketDataQueue: + + cdef unsigned int _size + cdef public object _protocol + cdef unsigned int _limit + cdef object _loop + cdef bint _eof + cdef object _waiter + cdef object _exception + cdef public object _buffer + cdef object _get_buffer + cdef object _put_buffer + + cdef void _release_waiter(self) + + cpdef void feed_data(self, object data, unsigned int size) + + @cython.locals(size="unsigned int") + cdef _read_from_buffer(self) + +cdef class WebSocketReader: + + cdef WebSocketDataQueue queue + cdef unsigned int _max_msg_size + + cdef Exception _exc + cdef bytearray _partial + cdef unsigned int _state + + cdef int _opcode + cdef bint _frame_fin + cdef int _frame_opcode + cdef list _payload_fragments + cdef Py_ssize_t _frame_payload_len + + cdef bytes _tail + cdef bint _has_mask + cdef bytes _frame_mask + cdef Py_ssize_t _payload_bytes_to_read + cdef unsigned int _payload_len_flag + cdef int _compressed + cdef object _decompressobj + cdef bint _compress + + cpdef tuple feed_data(self, object data) + + @cython.locals( + is_continuation=bint, + fin=bint, + has_partial=bint, + payload_merged=bytes, + ) + cpdef void _handle_frame(self, bint fin, int opcode, object payload, int compressed) except * + + @cython.locals( + start_pos=Py_ssize_t, + data_len=Py_ssize_t, + length=Py_ssize_t, + chunk_size=Py_ssize_t, + chunk_len=Py_ssize_t, + data_len=Py_ssize_t, + data_cstr="const unsigned char *", + first_byte="unsigned char", + second_byte="unsigned char", + f_start_pos=Py_ssize_t, + f_end_pos=Py_ssize_t, + has_mask=bint, + fin=bint, + had_fragments=Py_ssize_t, + payload_bytearray=bytearray, + ) + cpdef void _feed_data(self, bytes data) except * diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_websocket/reader_c.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_websocket/reader_c.py new file mode 100644 index 0000000000000000000000000000000000000000..5166d7ec26091a7183db7d9297511d3e62fd639c --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_websocket/reader_c.py @@ -0,0 +1,478 @@ +"""Reader for WebSocket protocol versions 13 and 8.""" + +import asyncio +import builtins +from collections import deque +from typing import Deque, Final, Optional, Set, Tuple, Union + +from ..base_protocol import BaseProtocol +from ..compression_utils import ZLibDecompressor +from ..helpers import _EXC_SENTINEL, set_exception +from ..streams import EofStream +from .helpers import UNPACK_CLOSE_CODE, UNPACK_LEN3, websocket_mask +from .models import ( + WS_DEFLATE_TRAILING, + WebSocketError, + WSCloseCode, + WSMessage, + WSMsgType, +) + +ALLOWED_CLOSE_CODES: Final[Set[int]] = {int(i) for i in WSCloseCode} + +# States for the reader, used to parse the WebSocket frame +# integer values are used so they can be cythonized +READ_HEADER = 1 +READ_PAYLOAD_LENGTH = 2 +READ_PAYLOAD_MASK = 3 +READ_PAYLOAD = 4 + +WS_MSG_TYPE_BINARY = WSMsgType.BINARY +WS_MSG_TYPE_TEXT = WSMsgType.TEXT + +# WSMsgType values unpacked so they can by cythonized to ints +OP_CODE_NOT_SET = -1 +OP_CODE_CONTINUATION = WSMsgType.CONTINUATION.value +OP_CODE_TEXT = WSMsgType.TEXT.value +OP_CODE_BINARY = WSMsgType.BINARY.value +OP_CODE_CLOSE = WSMsgType.CLOSE.value +OP_CODE_PING = WSMsgType.PING.value +OP_CODE_PONG = WSMsgType.PONG.value + +EMPTY_FRAME_ERROR = (True, b"") +EMPTY_FRAME = (False, b"") + +COMPRESSED_NOT_SET = -1 +COMPRESSED_FALSE = 0 +COMPRESSED_TRUE = 1 + +TUPLE_NEW = tuple.__new__ + +cython_int = int # Typed to int in Python, but cython with use a signed int in the pxd + + +class WebSocketDataQueue: + """WebSocketDataQueue resumes and pauses an underlying stream. + + It is a destination for WebSocket data. + """ + + def __init__( + self, protocol: BaseProtocol, limit: int, *, loop: asyncio.AbstractEventLoop + ) -> None: + self._size = 0 + self._protocol = protocol + self._limit = limit * 2 + self._loop = loop + self._eof = False + self._waiter: Optional[asyncio.Future[None]] = None + self._exception: Union[BaseException, None] = None + self._buffer: Deque[Tuple[WSMessage, int]] = deque() + self._get_buffer = self._buffer.popleft + self._put_buffer = self._buffer.append + + def is_eof(self) -> bool: + return self._eof + + def exception(self) -> Optional[BaseException]: + return self._exception + + def set_exception( + self, + exc: BaseException, + exc_cause: builtins.BaseException = _EXC_SENTINEL, + ) -> None: + self._eof = True + self._exception = exc + if (waiter := self._waiter) is not None: + self._waiter = None + set_exception(waiter, exc, exc_cause) + + def _release_waiter(self) -> None: + if (waiter := self._waiter) is None: + return + self._waiter = None + if not waiter.done(): + waiter.set_result(None) + + def feed_eof(self) -> None: + self._eof = True + self._release_waiter() + self._exception = None # Break cyclic references + + def feed_data(self, data: "WSMessage", size: "cython_int") -> None: + self._size += size + self._put_buffer((data, size)) + self._release_waiter() + if self._size > self._limit and not self._protocol._reading_paused: + self._protocol.pause_reading() + + async def read(self) -> WSMessage: + if not self._buffer and not self._eof: + assert not self._waiter + self._waiter = self._loop.create_future() + try: + await self._waiter + except (asyncio.CancelledError, asyncio.TimeoutError): + self._waiter = None + raise + return self._read_from_buffer() + + def _read_from_buffer(self) -> WSMessage: + if self._buffer: + data, size = self._get_buffer() + self._size -= size + if self._size < self._limit and self._protocol._reading_paused: + self._protocol.resume_reading() + return data + if self._exception is not None: + raise self._exception + raise EofStream + + +class WebSocketReader: + def __init__( + self, queue: WebSocketDataQueue, max_msg_size: int, compress: bool = True + ) -> None: + self.queue = queue + self._max_msg_size = max_msg_size + + self._exc: Optional[Exception] = None + self._partial = bytearray() + self._state = READ_HEADER + + self._opcode: int = OP_CODE_NOT_SET + self._frame_fin = False + self._frame_opcode: int = OP_CODE_NOT_SET + self._payload_fragments: list[bytes] = [] + self._frame_payload_len = 0 + + self._tail: bytes = b"" + self._has_mask = False + self._frame_mask: Optional[bytes] = None + self._payload_bytes_to_read = 0 + self._payload_len_flag = 0 + self._compressed: int = COMPRESSED_NOT_SET + self._decompressobj: Optional[ZLibDecompressor] = None + self._compress = compress + + def feed_eof(self) -> None: + self.queue.feed_eof() + + # data can be bytearray on Windows because proactor event loop uses bytearray + # and asyncio types this to Union[bytes, bytearray, memoryview] so we need + # coerce data to bytes if it is not + def feed_data( + self, data: Union[bytes, bytearray, memoryview] + ) -> Tuple[bool, bytes]: + if type(data) is not bytes: + data = bytes(data) + + if self._exc is not None: + return True, data + + try: + self._feed_data(data) + except Exception as exc: + self._exc = exc + set_exception(self.queue, exc) + return EMPTY_FRAME_ERROR + + return EMPTY_FRAME + + def _handle_frame( + self, + fin: bool, + opcode: Union[int, cython_int], # Union intended: Cython pxd uses C int + payload: Union[bytes, bytearray], + compressed: Union[int, cython_int], # Union intended: Cython pxd uses C int + ) -> None: + msg: WSMessage + if opcode in {OP_CODE_TEXT, OP_CODE_BINARY, OP_CODE_CONTINUATION}: + # Validate continuation frames before processing + if opcode == OP_CODE_CONTINUATION and self._opcode == OP_CODE_NOT_SET: + raise WebSocketError( + WSCloseCode.PROTOCOL_ERROR, + "Continuation frame for non started message", + ) + + # load text/binary + if not fin: + # got partial frame payload + if opcode != OP_CODE_CONTINUATION: + self._opcode = opcode + self._partial += payload + if self._max_msg_size and len(self._partial) >= self._max_msg_size: + raise WebSocketError( + WSCloseCode.MESSAGE_TOO_BIG, + f"Message size {len(self._partial)} " + f"exceeds limit {self._max_msg_size}", + ) + return + + has_partial = bool(self._partial) + if opcode == OP_CODE_CONTINUATION: + opcode = self._opcode + self._opcode = OP_CODE_NOT_SET + # previous frame was non finished + # we should get continuation opcode + elif has_partial: + raise WebSocketError( + WSCloseCode.PROTOCOL_ERROR, + "The opcode in non-fin frame is expected " + f"to be zero, got {opcode!r}", + ) + + assembled_payload: Union[bytes, bytearray] + if has_partial: + assembled_payload = self._partial + payload + self._partial.clear() + else: + assembled_payload = payload + + if self._max_msg_size and len(assembled_payload) >= self._max_msg_size: + raise WebSocketError( + WSCloseCode.MESSAGE_TOO_BIG, + f"Message size {len(assembled_payload)} " + f"exceeds limit {self._max_msg_size}", + ) + + # Decompress process must to be done after all packets + # received. + if compressed: + if not self._decompressobj: + self._decompressobj = ZLibDecompressor(suppress_deflate_header=True) + # XXX: It's possible that the zlib backend (isal is known to + # do this, maybe others too?) will return max_length bytes, + # but internally buffer more data such that the payload is + # >max_length, so we return one extra byte and if we're able + # to do that, then the message is too big. + payload_merged = self._decompressobj.decompress_sync( + assembled_payload + WS_DEFLATE_TRAILING, + ( + self._max_msg_size + 1 + if self._max_msg_size + else self._max_msg_size + ), + ) + if self._max_msg_size and len(payload_merged) > self._max_msg_size: + raise WebSocketError( + WSCloseCode.MESSAGE_TOO_BIG, + f"Decompressed message exceeds size limit {self._max_msg_size}", + ) + elif type(assembled_payload) is bytes: + payload_merged = assembled_payload + else: + payload_merged = bytes(assembled_payload) + + if opcode == OP_CODE_TEXT: + try: + text = payload_merged.decode("utf-8") + except UnicodeDecodeError as exc: + raise WebSocketError( + WSCloseCode.INVALID_TEXT, "Invalid UTF-8 text message" + ) from exc + + # XXX: The Text and Binary messages here can be a performance + # bottleneck, so we use tuple.__new__ to improve performance. + # This is not type safe, but many tests should fail in + # test_client_ws_functional.py if this is wrong. + self.queue.feed_data( + TUPLE_NEW(WSMessage, (WS_MSG_TYPE_TEXT, text, "")), + len(payload_merged), + ) + else: + self.queue.feed_data( + TUPLE_NEW(WSMessage, (WS_MSG_TYPE_BINARY, payload_merged, "")), + len(payload_merged), + ) + elif opcode == OP_CODE_CLOSE: + if len(payload) >= 2: + close_code = UNPACK_CLOSE_CODE(payload[:2])[0] + if close_code < 3000 and close_code not in ALLOWED_CLOSE_CODES: + raise WebSocketError( + WSCloseCode.PROTOCOL_ERROR, + f"Invalid close code: {close_code}", + ) + try: + close_message = payload[2:].decode("utf-8") + except UnicodeDecodeError as exc: + raise WebSocketError( + WSCloseCode.INVALID_TEXT, "Invalid UTF-8 text message" + ) from exc + msg = TUPLE_NEW(WSMessage, (WSMsgType.CLOSE, close_code, close_message)) + elif payload: + raise WebSocketError( + WSCloseCode.PROTOCOL_ERROR, + f"Invalid close frame: {fin} {opcode} {payload!r}", + ) + else: + msg = TUPLE_NEW(WSMessage, (WSMsgType.CLOSE, 0, "")) + + self.queue.feed_data(msg, 0) + elif opcode == OP_CODE_PING: + msg = TUPLE_NEW(WSMessage, (WSMsgType.PING, payload, "")) + self.queue.feed_data(msg, len(payload)) + elif opcode == OP_CODE_PONG: + msg = TUPLE_NEW(WSMessage, (WSMsgType.PONG, payload, "")) + self.queue.feed_data(msg, len(payload)) + else: + raise WebSocketError( + WSCloseCode.PROTOCOL_ERROR, f"Unexpected opcode={opcode!r}" + ) + + def _feed_data(self, data: bytes) -> None: + """Return the next frame from the socket.""" + if self._tail: + data, self._tail = self._tail + data, b"" + + start_pos: int = 0 + data_len = len(data) + data_cstr = data + + while True: + # read header + if self._state == READ_HEADER: + if data_len - start_pos < 2: + break + first_byte = data_cstr[start_pos] + second_byte = data_cstr[start_pos + 1] + start_pos += 2 + + fin = (first_byte >> 7) & 1 + rsv1 = (first_byte >> 6) & 1 + rsv2 = (first_byte >> 5) & 1 + rsv3 = (first_byte >> 4) & 1 + opcode = first_byte & 0xF + + # frame-fin = %x0 ; more frames of this message follow + # / %x1 ; final frame of this message + # frame-rsv1 = %x0 ; + # 1 bit, MUST be 0 unless negotiated otherwise + # frame-rsv2 = %x0 ; + # 1 bit, MUST be 0 unless negotiated otherwise + # frame-rsv3 = %x0 ; + # 1 bit, MUST be 0 unless negotiated otherwise + # + # Remove rsv1 from this test for deflate development + if rsv2 or rsv3 or (rsv1 and not self._compress): + raise WebSocketError( + WSCloseCode.PROTOCOL_ERROR, + "Received frame with non-zero reserved bits", + ) + + if opcode > 0x7 and fin == 0: + raise WebSocketError( + WSCloseCode.PROTOCOL_ERROR, + "Received fragmented control frame", + ) + + has_mask = (second_byte >> 7) & 1 + length = second_byte & 0x7F + + # Control frames MUST have a payload + # length of 125 bytes or less + if opcode > 0x7 and length > 125: + raise WebSocketError( + WSCloseCode.PROTOCOL_ERROR, + "Control frame payload cannot be larger than 125 bytes", + ) + + # Set compress status if last package is FIN + # OR set compress status if this is first fragment + # Raise error if not first fragment with rsv1 = 0x1 + if self._frame_fin or self._compressed == COMPRESSED_NOT_SET: + self._compressed = COMPRESSED_TRUE if rsv1 else COMPRESSED_FALSE + elif rsv1: + raise WebSocketError( + WSCloseCode.PROTOCOL_ERROR, + "Received frame with non-zero reserved bits", + ) + + self._frame_fin = bool(fin) + self._frame_opcode = opcode + self._has_mask = bool(has_mask) + self._payload_len_flag = length + self._state = READ_PAYLOAD_LENGTH + + # read payload length + if self._state == READ_PAYLOAD_LENGTH: + len_flag = self._payload_len_flag + if len_flag == 126: + if data_len - start_pos < 2: + break + first_byte = data_cstr[start_pos] + second_byte = data_cstr[start_pos + 1] + start_pos += 2 + self._payload_bytes_to_read = first_byte << 8 | second_byte + elif len_flag > 126: + if data_len - start_pos < 8: + break + self._payload_bytes_to_read = UNPACK_LEN3(data, start_pos)[0] + start_pos += 8 + else: + self._payload_bytes_to_read = len_flag + + self._state = READ_PAYLOAD_MASK if self._has_mask else READ_PAYLOAD + + # read payload mask + if self._state == READ_PAYLOAD_MASK: + if data_len - start_pos < 4: + break + self._frame_mask = data_cstr[start_pos : start_pos + 4] + start_pos += 4 + self._state = READ_PAYLOAD + + if self._state == READ_PAYLOAD: + chunk_len = data_len - start_pos + if self._payload_bytes_to_read >= chunk_len: + f_end_pos = data_len + self._payload_bytes_to_read -= chunk_len + else: + f_end_pos = start_pos + self._payload_bytes_to_read + self._payload_bytes_to_read = 0 + + had_fragments = self._frame_payload_len + self._frame_payload_len += f_end_pos - start_pos + f_start_pos = start_pos + start_pos = f_end_pos + + if self._payload_bytes_to_read != 0: + # If we don't have a complete frame, we need to save the + # data for the next call to feed_data. + self._payload_fragments.append(data_cstr[f_start_pos:f_end_pos]) + break + + payload: Union[bytes, bytearray] + if had_fragments: + # We have to join the payload fragments get the payload + self._payload_fragments.append(data_cstr[f_start_pos:f_end_pos]) + if self._has_mask: + assert self._frame_mask is not None + payload_bytearray = bytearray(b"".join(self._payload_fragments)) + websocket_mask(self._frame_mask, payload_bytearray) + payload = payload_bytearray + else: + payload = b"".join(self._payload_fragments) + self._payload_fragments.clear() + elif self._has_mask: + assert self._frame_mask is not None + payload_bytearray = data_cstr[f_start_pos:f_end_pos] # type: ignore[assignment] + if type(payload_bytearray) is not bytearray: # pragma: no branch + # Cython will do the conversion for us + # but we need to do it for Python and we + # will always get here in Python + payload_bytearray = bytearray(payload_bytearray) + websocket_mask(self._frame_mask, payload_bytearray) + payload = payload_bytearray + else: + payload = data_cstr[f_start_pos:f_end_pos] + + self._handle_frame( + self._frame_fin, self._frame_opcode, payload, self._compressed + ) + self._frame_payload_len = 0 + self._state = READ_HEADER + + # XXX: Cython needs slices to be bounded, so we can't omit the slice end here. + self._tail = data_cstr[start_pos:data_len] if start_pos < data_len else b"" diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_websocket/reader_py.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_websocket/reader_py.py new file mode 100644 index 0000000000000000000000000000000000000000..5166d7ec26091a7183db7d9297511d3e62fd639c --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_websocket/reader_py.py @@ -0,0 +1,478 @@ +"""Reader for WebSocket protocol versions 13 and 8.""" + +import asyncio +import builtins +from collections import deque +from typing import Deque, Final, Optional, Set, Tuple, Union + +from ..base_protocol import BaseProtocol +from ..compression_utils import ZLibDecompressor +from ..helpers import _EXC_SENTINEL, set_exception +from ..streams import EofStream +from .helpers import UNPACK_CLOSE_CODE, UNPACK_LEN3, websocket_mask +from .models import ( + WS_DEFLATE_TRAILING, + WebSocketError, + WSCloseCode, + WSMessage, + WSMsgType, +) + +ALLOWED_CLOSE_CODES: Final[Set[int]] = {int(i) for i in WSCloseCode} + +# States for the reader, used to parse the WebSocket frame +# integer values are used so they can be cythonized +READ_HEADER = 1 +READ_PAYLOAD_LENGTH = 2 +READ_PAYLOAD_MASK = 3 +READ_PAYLOAD = 4 + +WS_MSG_TYPE_BINARY = WSMsgType.BINARY +WS_MSG_TYPE_TEXT = WSMsgType.TEXT + +# WSMsgType values unpacked so they can by cythonized to ints +OP_CODE_NOT_SET = -1 +OP_CODE_CONTINUATION = WSMsgType.CONTINUATION.value +OP_CODE_TEXT = WSMsgType.TEXT.value +OP_CODE_BINARY = WSMsgType.BINARY.value +OP_CODE_CLOSE = WSMsgType.CLOSE.value +OP_CODE_PING = WSMsgType.PING.value +OP_CODE_PONG = WSMsgType.PONG.value + +EMPTY_FRAME_ERROR = (True, b"") +EMPTY_FRAME = (False, b"") + +COMPRESSED_NOT_SET = -1 +COMPRESSED_FALSE = 0 +COMPRESSED_TRUE = 1 + +TUPLE_NEW = tuple.__new__ + +cython_int = int # Typed to int in Python, but cython with use a signed int in the pxd + + +class WebSocketDataQueue: + """WebSocketDataQueue resumes and pauses an underlying stream. + + It is a destination for WebSocket data. + """ + + def __init__( + self, protocol: BaseProtocol, limit: int, *, loop: asyncio.AbstractEventLoop + ) -> None: + self._size = 0 + self._protocol = protocol + self._limit = limit * 2 + self._loop = loop + self._eof = False + self._waiter: Optional[asyncio.Future[None]] = None + self._exception: Union[BaseException, None] = None + self._buffer: Deque[Tuple[WSMessage, int]] = deque() + self._get_buffer = self._buffer.popleft + self._put_buffer = self._buffer.append + + def is_eof(self) -> bool: + return self._eof + + def exception(self) -> Optional[BaseException]: + return self._exception + + def set_exception( + self, + exc: BaseException, + exc_cause: builtins.BaseException = _EXC_SENTINEL, + ) -> None: + self._eof = True + self._exception = exc + if (waiter := self._waiter) is not None: + self._waiter = None + set_exception(waiter, exc, exc_cause) + + def _release_waiter(self) -> None: + if (waiter := self._waiter) is None: + return + self._waiter = None + if not waiter.done(): + waiter.set_result(None) + + def feed_eof(self) -> None: + self._eof = True + self._release_waiter() + self._exception = None # Break cyclic references + + def feed_data(self, data: "WSMessage", size: "cython_int") -> None: + self._size += size + self._put_buffer((data, size)) + self._release_waiter() + if self._size > self._limit and not self._protocol._reading_paused: + self._protocol.pause_reading() + + async def read(self) -> WSMessage: + if not self._buffer and not self._eof: + assert not self._waiter + self._waiter = self._loop.create_future() + try: + await self._waiter + except (asyncio.CancelledError, asyncio.TimeoutError): + self._waiter = None + raise + return self._read_from_buffer() + + def _read_from_buffer(self) -> WSMessage: + if self._buffer: + data, size = self._get_buffer() + self._size -= size + if self._size < self._limit and self._protocol._reading_paused: + self._protocol.resume_reading() + return data + if self._exception is not None: + raise self._exception + raise EofStream + + +class WebSocketReader: + def __init__( + self, queue: WebSocketDataQueue, max_msg_size: int, compress: bool = True + ) -> None: + self.queue = queue + self._max_msg_size = max_msg_size + + self._exc: Optional[Exception] = None + self._partial = bytearray() + self._state = READ_HEADER + + self._opcode: int = OP_CODE_NOT_SET + self._frame_fin = False + self._frame_opcode: int = OP_CODE_NOT_SET + self._payload_fragments: list[bytes] = [] + self._frame_payload_len = 0 + + self._tail: bytes = b"" + self._has_mask = False + self._frame_mask: Optional[bytes] = None + self._payload_bytes_to_read = 0 + self._payload_len_flag = 0 + self._compressed: int = COMPRESSED_NOT_SET + self._decompressobj: Optional[ZLibDecompressor] = None + self._compress = compress + + def feed_eof(self) -> None: + self.queue.feed_eof() + + # data can be bytearray on Windows because proactor event loop uses bytearray + # and asyncio types this to Union[bytes, bytearray, memoryview] so we need + # coerce data to bytes if it is not + def feed_data( + self, data: Union[bytes, bytearray, memoryview] + ) -> Tuple[bool, bytes]: + if type(data) is not bytes: + data = bytes(data) + + if self._exc is not None: + return True, data + + try: + self._feed_data(data) + except Exception as exc: + self._exc = exc + set_exception(self.queue, exc) + return EMPTY_FRAME_ERROR + + return EMPTY_FRAME + + def _handle_frame( + self, + fin: bool, + opcode: Union[int, cython_int], # Union intended: Cython pxd uses C int + payload: Union[bytes, bytearray], + compressed: Union[int, cython_int], # Union intended: Cython pxd uses C int + ) -> None: + msg: WSMessage + if opcode in {OP_CODE_TEXT, OP_CODE_BINARY, OP_CODE_CONTINUATION}: + # Validate continuation frames before processing + if opcode == OP_CODE_CONTINUATION and self._opcode == OP_CODE_NOT_SET: + raise WebSocketError( + WSCloseCode.PROTOCOL_ERROR, + "Continuation frame for non started message", + ) + + # load text/binary + if not fin: + # got partial frame payload + if opcode != OP_CODE_CONTINUATION: + self._opcode = opcode + self._partial += payload + if self._max_msg_size and len(self._partial) >= self._max_msg_size: + raise WebSocketError( + WSCloseCode.MESSAGE_TOO_BIG, + f"Message size {len(self._partial)} " + f"exceeds limit {self._max_msg_size}", + ) + return + + has_partial = bool(self._partial) + if opcode == OP_CODE_CONTINUATION: + opcode = self._opcode + self._opcode = OP_CODE_NOT_SET + # previous frame was non finished + # we should get continuation opcode + elif has_partial: + raise WebSocketError( + WSCloseCode.PROTOCOL_ERROR, + "The opcode in non-fin frame is expected " + f"to be zero, got {opcode!r}", + ) + + assembled_payload: Union[bytes, bytearray] + if has_partial: + assembled_payload = self._partial + payload + self._partial.clear() + else: + assembled_payload = payload + + if self._max_msg_size and len(assembled_payload) >= self._max_msg_size: + raise WebSocketError( + WSCloseCode.MESSAGE_TOO_BIG, + f"Message size {len(assembled_payload)} " + f"exceeds limit {self._max_msg_size}", + ) + + # Decompress process must to be done after all packets + # received. + if compressed: + if not self._decompressobj: + self._decompressobj = ZLibDecompressor(suppress_deflate_header=True) + # XXX: It's possible that the zlib backend (isal is known to + # do this, maybe others too?) will return max_length bytes, + # but internally buffer more data such that the payload is + # >max_length, so we return one extra byte and if we're able + # to do that, then the message is too big. + payload_merged = self._decompressobj.decompress_sync( + assembled_payload + WS_DEFLATE_TRAILING, + ( + self._max_msg_size + 1 + if self._max_msg_size + else self._max_msg_size + ), + ) + if self._max_msg_size and len(payload_merged) > self._max_msg_size: + raise WebSocketError( + WSCloseCode.MESSAGE_TOO_BIG, + f"Decompressed message exceeds size limit {self._max_msg_size}", + ) + elif type(assembled_payload) is bytes: + payload_merged = assembled_payload + else: + payload_merged = bytes(assembled_payload) + + if opcode == OP_CODE_TEXT: + try: + text = payload_merged.decode("utf-8") + except UnicodeDecodeError as exc: + raise WebSocketError( + WSCloseCode.INVALID_TEXT, "Invalid UTF-8 text message" + ) from exc + + # XXX: The Text and Binary messages here can be a performance + # bottleneck, so we use tuple.__new__ to improve performance. + # This is not type safe, but many tests should fail in + # test_client_ws_functional.py if this is wrong. + self.queue.feed_data( + TUPLE_NEW(WSMessage, (WS_MSG_TYPE_TEXT, text, "")), + len(payload_merged), + ) + else: + self.queue.feed_data( + TUPLE_NEW(WSMessage, (WS_MSG_TYPE_BINARY, payload_merged, "")), + len(payload_merged), + ) + elif opcode == OP_CODE_CLOSE: + if len(payload) >= 2: + close_code = UNPACK_CLOSE_CODE(payload[:2])[0] + if close_code < 3000 and close_code not in ALLOWED_CLOSE_CODES: + raise WebSocketError( + WSCloseCode.PROTOCOL_ERROR, + f"Invalid close code: {close_code}", + ) + try: + close_message = payload[2:].decode("utf-8") + except UnicodeDecodeError as exc: + raise WebSocketError( + WSCloseCode.INVALID_TEXT, "Invalid UTF-8 text message" + ) from exc + msg = TUPLE_NEW(WSMessage, (WSMsgType.CLOSE, close_code, close_message)) + elif payload: + raise WebSocketError( + WSCloseCode.PROTOCOL_ERROR, + f"Invalid close frame: {fin} {opcode} {payload!r}", + ) + else: + msg = TUPLE_NEW(WSMessage, (WSMsgType.CLOSE, 0, "")) + + self.queue.feed_data(msg, 0) + elif opcode == OP_CODE_PING: + msg = TUPLE_NEW(WSMessage, (WSMsgType.PING, payload, "")) + self.queue.feed_data(msg, len(payload)) + elif opcode == OP_CODE_PONG: + msg = TUPLE_NEW(WSMessage, (WSMsgType.PONG, payload, "")) + self.queue.feed_data(msg, len(payload)) + else: + raise WebSocketError( + WSCloseCode.PROTOCOL_ERROR, f"Unexpected opcode={opcode!r}" + ) + + def _feed_data(self, data: bytes) -> None: + """Return the next frame from the socket.""" + if self._tail: + data, self._tail = self._tail + data, b"" + + start_pos: int = 0 + data_len = len(data) + data_cstr = data + + while True: + # read header + if self._state == READ_HEADER: + if data_len - start_pos < 2: + break + first_byte = data_cstr[start_pos] + second_byte = data_cstr[start_pos + 1] + start_pos += 2 + + fin = (first_byte >> 7) & 1 + rsv1 = (first_byte >> 6) & 1 + rsv2 = (first_byte >> 5) & 1 + rsv3 = (first_byte >> 4) & 1 + opcode = first_byte & 0xF + + # frame-fin = %x0 ; more frames of this message follow + # / %x1 ; final frame of this message + # frame-rsv1 = %x0 ; + # 1 bit, MUST be 0 unless negotiated otherwise + # frame-rsv2 = %x0 ; + # 1 bit, MUST be 0 unless negotiated otherwise + # frame-rsv3 = %x0 ; + # 1 bit, MUST be 0 unless negotiated otherwise + # + # Remove rsv1 from this test for deflate development + if rsv2 or rsv3 or (rsv1 and not self._compress): + raise WebSocketError( + WSCloseCode.PROTOCOL_ERROR, + "Received frame with non-zero reserved bits", + ) + + if opcode > 0x7 and fin == 0: + raise WebSocketError( + WSCloseCode.PROTOCOL_ERROR, + "Received fragmented control frame", + ) + + has_mask = (second_byte >> 7) & 1 + length = second_byte & 0x7F + + # Control frames MUST have a payload + # length of 125 bytes or less + if opcode > 0x7 and length > 125: + raise WebSocketError( + WSCloseCode.PROTOCOL_ERROR, + "Control frame payload cannot be larger than 125 bytes", + ) + + # Set compress status if last package is FIN + # OR set compress status if this is first fragment + # Raise error if not first fragment with rsv1 = 0x1 + if self._frame_fin or self._compressed == COMPRESSED_NOT_SET: + self._compressed = COMPRESSED_TRUE if rsv1 else COMPRESSED_FALSE + elif rsv1: + raise WebSocketError( + WSCloseCode.PROTOCOL_ERROR, + "Received frame with non-zero reserved bits", + ) + + self._frame_fin = bool(fin) + self._frame_opcode = opcode + self._has_mask = bool(has_mask) + self._payload_len_flag = length + self._state = READ_PAYLOAD_LENGTH + + # read payload length + if self._state == READ_PAYLOAD_LENGTH: + len_flag = self._payload_len_flag + if len_flag == 126: + if data_len - start_pos < 2: + break + first_byte = data_cstr[start_pos] + second_byte = data_cstr[start_pos + 1] + start_pos += 2 + self._payload_bytes_to_read = first_byte << 8 | second_byte + elif len_flag > 126: + if data_len - start_pos < 8: + break + self._payload_bytes_to_read = UNPACK_LEN3(data, start_pos)[0] + start_pos += 8 + else: + self._payload_bytes_to_read = len_flag + + self._state = READ_PAYLOAD_MASK if self._has_mask else READ_PAYLOAD + + # read payload mask + if self._state == READ_PAYLOAD_MASK: + if data_len - start_pos < 4: + break + self._frame_mask = data_cstr[start_pos : start_pos + 4] + start_pos += 4 + self._state = READ_PAYLOAD + + if self._state == READ_PAYLOAD: + chunk_len = data_len - start_pos + if self._payload_bytes_to_read >= chunk_len: + f_end_pos = data_len + self._payload_bytes_to_read -= chunk_len + else: + f_end_pos = start_pos + self._payload_bytes_to_read + self._payload_bytes_to_read = 0 + + had_fragments = self._frame_payload_len + self._frame_payload_len += f_end_pos - start_pos + f_start_pos = start_pos + start_pos = f_end_pos + + if self._payload_bytes_to_read != 0: + # If we don't have a complete frame, we need to save the + # data for the next call to feed_data. + self._payload_fragments.append(data_cstr[f_start_pos:f_end_pos]) + break + + payload: Union[bytes, bytearray] + if had_fragments: + # We have to join the payload fragments get the payload + self._payload_fragments.append(data_cstr[f_start_pos:f_end_pos]) + if self._has_mask: + assert self._frame_mask is not None + payload_bytearray = bytearray(b"".join(self._payload_fragments)) + websocket_mask(self._frame_mask, payload_bytearray) + payload = payload_bytearray + else: + payload = b"".join(self._payload_fragments) + self._payload_fragments.clear() + elif self._has_mask: + assert self._frame_mask is not None + payload_bytearray = data_cstr[f_start_pos:f_end_pos] # type: ignore[assignment] + if type(payload_bytearray) is not bytearray: # pragma: no branch + # Cython will do the conversion for us + # but we need to do it for Python and we + # will always get here in Python + payload_bytearray = bytearray(payload_bytearray) + websocket_mask(self._frame_mask, payload_bytearray) + payload = payload_bytearray + else: + payload = data_cstr[f_start_pos:f_end_pos] + + self._handle_frame( + self._frame_fin, self._frame_opcode, payload, self._compressed + ) + self._frame_payload_len = 0 + self._state = READ_HEADER + + # XXX: Cython needs slices to be bounded, so we can't omit the slice end here. + self._tail = data_cstr[start_pos:data_len] if start_pos < data_len else b"" diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_websocket/writer.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_websocket/writer.py new file mode 100644 index 0000000000000000000000000000000000000000..9604202357cddd5714242144e30f4a05c4f638fa --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/_websocket/writer.py @@ -0,0 +1,262 @@ +"""WebSocket protocol versions 13 and 8.""" + +import asyncio +import random +import sys +from functools import partial +from typing import Final, Optional, Set, Union + +from ..base_protocol import BaseProtocol +from ..client_exceptions import ClientConnectionResetError +from ..compression_utils import ZLibBackend, ZLibCompressor +from .helpers import ( + MASK_LEN, + MSG_SIZE, + PACK_CLOSE_CODE, + PACK_LEN1, + PACK_LEN2, + PACK_LEN3, + PACK_RANDBITS, + websocket_mask, +) +from .models import WS_DEFLATE_TRAILING, WSMsgType + +DEFAULT_LIMIT: Final[int] = 2**16 + +# WebSocket opcode boundary: opcodes 0-7 are data frames, 8-15 are control frames +# Control frames (ping, pong, close) are never compressed +WS_CONTROL_FRAME_OPCODE: Final[int] = 8 + +# For websockets, keeping latency low is extremely important as implementations +# generally expect to be able to send and receive messages quickly. We use a +# larger chunk size to reduce the number of executor calls and avoid task +# creation overhead, since both are significant sources of latency when chunks +# are small. A size of 16KiB was chosen as a balance between avoiding task +# overhead and not blocking the event loop too long with synchronous compression. + +WEBSOCKET_MAX_SYNC_CHUNK_SIZE = 16 * 1024 + + +class WebSocketWriter: + """WebSocket writer. + + The writer is responsible for sending messages to the client. It is + created by the protocol when a connection is established. The writer + should avoid implementing any application logic and should only be + concerned with the low-level details of the WebSocket protocol. + """ + + def __init__( + self, + protocol: BaseProtocol, + transport: asyncio.Transport, + *, + use_mask: bool = False, + limit: int = DEFAULT_LIMIT, + random: random.Random = random.Random(), + compress: int = 0, + notakeover: bool = False, + ) -> None: + """Initialize a WebSocket writer.""" + self.protocol = protocol + self.transport = transport + self.use_mask = use_mask + self.get_random_bits = partial(random.getrandbits, 32) + self.compress = compress + self.notakeover = notakeover + self._closing = False + self._limit = limit + self._output_size = 0 + self._compressobj: Optional[ZLibCompressor] = None + self._send_lock = asyncio.Lock() + self._background_tasks: Set[asyncio.Task[None]] = set() + + async def send_frame( + self, message: bytes, opcode: int, compress: Optional[int] = None + ) -> None: + """Send a frame over the websocket with message as its payload.""" + if self._closing and not (opcode & WSMsgType.CLOSE): + raise ClientConnectionResetError("Cannot write to closing transport") + + if not (compress or self.compress) or opcode >= WS_CONTROL_FRAME_OPCODE: + # Non-compressed frames don't need lock or shield + self._write_websocket_frame(message, opcode, 0) + elif len(message) <= WEBSOCKET_MAX_SYNC_CHUNK_SIZE: + # Small compressed payloads - compress synchronously in event loop + # We need the lock even though sync compression has no await points. + # This prevents small frames from interleaving with large frames that + # compress in the executor, avoiding compressor state corruption. + async with self._send_lock: + self._send_compressed_frame_sync(message, opcode, compress) + else: + # Large compressed frames need shield to prevent corruption + # For large compressed frames, the entire compress+send + # operation must be atomic. If cancelled after compression but + # before send, the compressor state would be advanced but data + # not sent, corrupting subsequent frames. + # Create a task to shield from cancellation + # The lock is acquired inside the shielded task so the entire + # operation (lock + compress + send) completes atomically. + # Use eager_start on Python 3.12+ to avoid scheduling overhead + loop = asyncio.get_running_loop() + coro = self._send_compressed_frame_async_locked(message, opcode, compress) + if sys.version_info >= (3, 12): + send_task = asyncio.Task(coro, loop=loop, eager_start=True) + else: + send_task = loop.create_task(coro) + # Keep a strong reference to prevent garbage collection + self._background_tasks.add(send_task) + send_task.add_done_callback(self._background_tasks.discard) + await asyncio.shield(send_task) + + # It is safe to return control to the event loop when using compression + # after this point as we have already sent or buffered all the data. + # Once we have written output_size up to the limit, we call the + # drain helper which waits for the transport to be ready to accept + # more data. This is a flow control mechanism to prevent the buffer + # from growing too large. The drain helper will return right away + # if the writer is not paused. + if self._output_size > self._limit: + self._output_size = 0 + if self.protocol._paused: + await self.protocol._drain_helper() + + def _write_websocket_frame(self, message: bytes, opcode: int, rsv: int) -> None: + """ + Write a websocket frame to the transport. + + This method handles frame header construction, masking, and writing to transport. + It does not handle compression or flow control - those are the responsibility + of the caller. + """ + msg_length = len(message) + + use_mask = self.use_mask + mask_bit = 0x80 if use_mask else 0 + + # Depending on the message length, the header is assembled differently. + # The first byte is reserved for the opcode and the RSV bits. + first_byte = 0x80 | rsv | opcode + if msg_length < 126: + header = PACK_LEN1(first_byte, msg_length | mask_bit) + header_len = 2 + elif msg_length < 65536: + header = PACK_LEN2(first_byte, 126 | mask_bit, msg_length) + header_len = 4 + else: + header = PACK_LEN3(first_byte, 127 | mask_bit, msg_length) + header_len = 10 + + if self.transport.is_closing(): + raise ClientConnectionResetError("Cannot write to closing transport") + + # https://datatracker.ietf.org/doc/html/rfc6455#section-5.3 + # If we are using a mask, we need to generate it randomly + # and apply it to the message before sending it. A mask is + # a 32-bit value that is applied to the message using a + # bitwise XOR operation. It is used to prevent certain types + # of attacks on the websocket protocol. The mask is only used + # when aiohttp is acting as a client. Servers do not use a mask. + if use_mask: + mask = PACK_RANDBITS(self.get_random_bits()) + message = bytearray(message) + websocket_mask(mask, message) + self.transport.write(header + mask + message) + self._output_size += MASK_LEN + elif msg_length > MSG_SIZE: + self.transport.write(header) + self.transport.write(message) + else: + self.transport.write(header + message) + + self._output_size += header_len + msg_length + + def _get_compressor(self, compress: Optional[int]) -> ZLibCompressor: + """Get or create a compressor object for the given compression level.""" + if compress: + # Do not set self._compress if compressing is for this frame + return ZLibCompressor( + level=ZLibBackend.Z_BEST_SPEED, + wbits=-compress, + max_sync_chunk_size=WEBSOCKET_MAX_SYNC_CHUNK_SIZE, + ) + if not self._compressobj: + self._compressobj = ZLibCompressor( + level=ZLibBackend.Z_BEST_SPEED, + wbits=-self.compress, + max_sync_chunk_size=WEBSOCKET_MAX_SYNC_CHUNK_SIZE, + ) + return self._compressobj + + def _send_compressed_frame_sync( + self, message: bytes, opcode: int, compress: Optional[int] + ) -> None: + """ + Synchronous send for small compressed frames. + + This is used for small compressed payloads that compress synchronously in the event loop. + Since there are no await points, this is inherently cancellation-safe. + """ + # RSV are the reserved bits in the frame header. They are used to + # indicate that the frame is using an extension. + # https://datatracker.ietf.org/doc/html/rfc6455#section-5.2 + compressobj = self._get_compressor(compress) + # (0x40) RSV1 is set for compressed frames + # https://datatracker.ietf.org/doc/html/rfc7692#section-7.2.3.1 + self._write_websocket_frame( + ( + compressobj.compress_sync(message) + + compressobj.flush( + ZLibBackend.Z_FULL_FLUSH + if self.notakeover + else ZLibBackend.Z_SYNC_FLUSH + ) + ).removesuffix(WS_DEFLATE_TRAILING), + opcode, + 0x40, + ) + + async def _send_compressed_frame_async_locked( + self, message: bytes, opcode: int, compress: Optional[int] + ) -> None: + """ + Async send for large compressed frames with lock. + + Acquires the lock and compresses large payloads asynchronously in + the executor. The lock is held for the entire operation to ensure + the compressor state is not corrupted by concurrent sends. + + MUST be run shielded from cancellation. If cancelled after + compression but before sending, the compressor state would be + advanced but data not sent, corrupting subsequent frames. + """ + async with self._send_lock: + # RSV are the reserved bits in the frame header. They are used to + # indicate that the frame is using an extension. + # https://datatracker.ietf.org/doc/html/rfc6455#section-5.2 + compressobj = self._get_compressor(compress) + # (0x40) RSV1 is set for compressed frames + # https://datatracker.ietf.org/doc/html/rfc7692#section-7.2.3.1 + self._write_websocket_frame( + ( + await compressobj.compress(message) + + compressobj.flush( + ZLibBackend.Z_FULL_FLUSH + if self.notakeover + else ZLibBackend.Z_SYNC_FLUSH + ) + ).removesuffix(WS_DEFLATE_TRAILING), + opcode, + 0x40, + ) + + async def close(self, code: int = 1000, message: Union[bytes, str] = b"") -> None: + """Close the websocket, sending the specified code and message.""" + if isinstance(message, str): + message = message.encode("utf-8") + try: + await self.send_frame( + PACK_CLOSE_CODE(code) + message, opcode=WSMsgType.CLOSE + ) + finally: + self._closing = True diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/abc.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/abc.py new file mode 100644 index 0000000000000000000000000000000000000000..faf09575afbb2d6842ad639dada0fdebc2e28945 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/abc.py @@ -0,0 +1,268 @@ +import asyncio +import logging +import socket +from abc import ABC, abstractmethod +from collections.abc import Sized +from http.cookies import BaseCookie, Morsel +from typing import ( + TYPE_CHECKING, + Any, + Awaitable, + Callable, + Dict, + Generator, + Iterable, + List, + Optional, + Sequence, + Tuple, + TypedDict, + Union, +) + +from multidict import CIMultiDict +from yarl import URL + +from ._cookie_helpers import parse_set_cookie_headers +from .typedefs import LooseCookies + +if TYPE_CHECKING: + from .web_app import Application + from .web_exceptions import HTTPException + from .web_request import BaseRequest, Request + from .web_response import StreamResponse +else: + BaseRequest = Request = Application = StreamResponse = None + HTTPException = None + + +class AbstractRouter(ABC): + def __init__(self) -> None: + self._frozen = False + + def post_init(self, app: Application) -> None: + """Post init stage. + + Not an abstract method for sake of backward compatibility, + but if the router wants to be aware of the application + it can override this. + """ + + @property + def frozen(self) -> bool: + return self._frozen + + def freeze(self) -> None: + """Freeze router.""" + self._frozen = True + + @abstractmethod + async def resolve(self, request: Request) -> "AbstractMatchInfo": + """Return MATCH_INFO for given request""" + + +class AbstractMatchInfo(ABC): + + __slots__ = () + + @property # pragma: no branch + @abstractmethod + def handler(self) -> Callable[[Request], Awaitable[StreamResponse]]: + """Execute matched request handler""" + + @property + @abstractmethod + def expect_handler( + self, + ) -> Callable[[Request], Awaitable[Optional[StreamResponse]]]: + """Expect handler for 100-continue processing""" + + @property # pragma: no branch + @abstractmethod + def http_exception(self) -> Optional[HTTPException]: + """HTTPException instance raised on router's resolving, or None""" + + @abstractmethod # pragma: no branch + def get_info(self) -> Dict[str, Any]: + """Return a dict with additional info useful for introspection""" + + @property # pragma: no branch + @abstractmethod + def apps(self) -> Tuple[Application, ...]: + """Stack of nested applications. + + Top level application is left-most element. + + """ + + @abstractmethod + def add_app(self, app: Application) -> None: + """Add application to the nested apps stack.""" + + @abstractmethod + def freeze(self) -> None: + """Freeze the match info. + + The method is called after route resolution. + + After the call .add_app() is forbidden. + + """ + + +class AbstractView(ABC): + """Abstract class based view.""" + + def __init__(self, request: Request) -> None: + self._request = request + + @property + def request(self) -> Request: + """Request instance.""" + return self._request + + @abstractmethod + def __await__(self) -> Generator[None, None, StreamResponse]: + """Execute the view handler.""" + + +class ResolveResult(TypedDict): + """Resolve result. + + This is the result returned from an AbstractResolver's + resolve method. + + :param hostname: The hostname that was provided. + :param host: The IP address that was resolved. + :param port: The port that was resolved. + :param family: The address family that was resolved. + :param proto: The protocol that was resolved. + :param flags: The flags that were resolved. + """ + + hostname: str + host: str + port: int + family: int + proto: int + flags: int + + +class AbstractResolver(ABC): + """Abstract DNS resolver.""" + + @abstractmethod + async def resolve( + self, host: str, port: int = 0, family: socket.AddressFamily = socket.AF_INET + ) -> List[ResolveResult]: + """Return IP address for given hostname""" + + @abstractmethod + async def close(self) -> None: + """Release resolver""" + + +if TYPE_CHECKING: + IterableBase = Iterable[Morsel[str]] +else: + IterableBase = Iterable + + +ClearCookiePredicate = Callable[["Morsel[str]"], bool] + + +class AbstractCookieJar(Sized, IterableBase): + """Abstract Cookie Jar.""" + + def __init__(self, *, loop: Optional[asyncio.AbstractEventLoop] = None) -> None: + self._loop = loop or asyncio.get_running_loop() + + @property + @abstractmethod + def quote_cookie(self) -> bool: + """Return True if cookies should be quoted.""" + + @abstractmethod + def clear(self, predicate: Optional[ClearCookiePredicate] = None) -> None: + """Clear all cookies if no predicate is passed.""" + + @abstractmethod + def clear_domain(self, domain: str) -> None: + """Clear all cookies for domain and all subdomains.""" + + @abstractmethod + def update_cookies(self, cookies: LooseCookies, response_url: URL = URL()) -> None: + """Update cookies.""" + + def update_cookies_from_headers( + self, headers: Sequence[str], response_url: URL + ) -> None: + """Update cookies from raw Set-Cookie headers.""" + if headers and (cookies_to_update := parse_set_cookie_headers(headers)): + self.update_cookies(cookies_to_update, response_url) + + @abstractmethod + def filter_cookies(self, request_url: URL) -> "BaseCookie[str]": + """Return the jar's cookies filtered by their attributes.""" + + +class AbstractStreamWriter(ABC): + """Abstract stream writer.""" + + buffer_size: int = 0 + output_size: int = 0 + length: Optional[int] = 0 + + @abstractmethod + async def write(self, chunk: Union[bytes, bytearray, memoryview]) -> None: + """Write chunk into stream.""" + + @abstractmethod + async def write_eof(self, chunk: bytes = b"") -> None: + """Write last chunk.""" + + @abstractmethod + async def drain(self) -> None: + """Flush the write buffer.""" + + @abstractmethod + def enable_compression( + self, encoding: str = "deflate", strategy: Optional[int] = None + ) -> None: + """Enable HTTP body compression""" + + @abstractmethod + def enable_chunking(self) -> None: + """Enable HTTP chunked mode""" + + @abstractmethod + async def write_headers( + self, status_line: str, headers: "CIMultiDict[str]" + ) -> None: + """Write HTTP headers""" + + def send_headers(self) -> None: + """Force sending buffered headers if not already sent. + + Required only if write_headers() buffers headers instead of sending immediately. + For backwards compatibility, this method does nothing by default. + """ + + +class AbstractAccessLogger(ABC): + """Abstract writer to access log.""" + + __slots__ = ("logger", "log_format") + + def __init__(self, logger: logging.Logger, log_format: str) -> None: + self.logger = logger + self.log_format = log_format + + @abstractmethod + def log(self, request: BaseRequest, response: StreamResponse, time: float) -> None: + """Emit log to logger.""" + + @property + def enabled(self) -> bool: + """Check if logger is enabled.""" + return True diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/base_protocol.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/base_protocol.py new file mode 100644 index 0000000000000000000000000000000000000000..b0a67ed6ff68ca5bc48be9ac472ee755369b2720 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/base_protocol.py @@ -0,0 +1,100 @@ +import asyncio +from typing import Optional, cast + +from .client_exceptions import ClientConnectionResetError +from .helpers import set_exception +from .tcp_helpers import tcp_nodelay + + +class BaseProtocol(asyncio.Protocol): + __slots__ = ( + "_loop", + "_paused", + "_drain_waiter", + "_connection_lost", + "_reading_paused", + "transport", + ) + + def __init__(self, loop: asyncio.AbstractEventLoop) -> None: + self._loop: asyncio.AbstractEventLoop = loop + self._paused = False + self._drain_waiter: Optional[asyncio.Future[None]] = None + self._reading_paused = False + + self.transport: Optional[asyncio.Transport] = None + + @property + def connected(self) -> bool: + """Return True if the connection is open.""" + return self.transport is not None + + @property + def writing_paused(self) -> bool: + return self._paused + + def pause_writing(self) -> None: + assert not self._paused + self._paused = True + + def resume_writing(self) -> None: + assert self._paused + self._paused = False + + waiter = self._drain_waiter + if waiter is not None: + self._drain_waiter = None + if not waiter.done(): + waiter.set_result(None) + + def pause_reading(self) -> None: + if not self._reading_paused and self.transport is not None: + try: + self.transport.pause_reading() + except (AttributeError, NotImplementedError, RuntimeError): + pass + self._reading_paused = True + + def resume_reading(self) -> None: + if self._reading_paused and self.transport is not None: + try: + self.transport.resume_reading() + except (AttributeError, NotImplementedError, RuntimeError): + pass + self._reading_paused = False + + def connection_made(self, transport: asyncio.BaseTransport) -> None: + tr = cast(asyncio.Transport, transport) + tcp_nodelay(tr, True) + self.transport = tr + + def connection_lost(self, exc: Optional[BaseException]) -> None: + # Wake up the writer if currently paused. + self.transport = None + if not self._paused: + return + waiter = self._drain_waiter + if waiter is None: + return + self._drain_waiter = None + if waiter.done(): + return + if exc is None: + waiter.set_result(None) + else: + set_exception( + waiter, + ConnectionError("Connection lost"), + exc, + ) + + async def _drain_helper(self) -> None: + if self.transport is None: + raise ClientConnectionResetError("Connection lost") + if not self._paused: + return + waiter = self._drain_waiter + if waiter is None: + waiter = self._loop.create_future() + self._drain_waiter = waiter + await asyncio.shield(waiter) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/client.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/client.py new file mode 100644 index 0000000000000000000000000000000000000000..5d10d3e43da260acd1b9596b3eac8825f3fbc88f --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/client.py @@ -0,0 +1,1646 @@ +"""HTTP Client for asyncio.""" + +import asyncio +import base64 +import hashlib +import json +import os +import sys +import traceback +import warnings +from contextlib import suppress +from types import TracebackType +from typing import ( + TYPE_CHECKING, + Any, + Awaitable, + Callable, + Coroutine, + Final, + FrozenSet, + Generator, + Generic, + Iterable, + List, + Mapping, + Optional, + Sequence, + Set, + Tuple, + Type, + TypedDict, + TypeVar, + Union, +) + +import attr +from multidict import CIMultiDict, MultiDict, MultiDictProxy, istr +from yarl import URL + +from . import hdrs, http, payload +from ._websocket.reader import WebSocketDataQueue +from .abc import AbstractCookieJar +from .client_exceptions import ( + ClientConnectionError, + ClientConnectionResetError, + ClientConnectorCertificateError, + ClientConnectorDNSError, + ClientConnectorError, + ClientConnectorSSLError, + ClientError, + ClientHttpProxyError, + ClientOSError, + ClientPayloadError, + ClientProxyConnectionError, + ClientResponseError, + ClientSSLError, + ConnectionTimeoutError, + ContentTypeError, + InvalidURL, + InvalidUrlClientError, + InvalidUrlRedirectClientError, + NonHttpUrlClientError, + NonHttpUrlRedirectClientError, + RedirectClientError, + ServerConnectionError, + ServerDisconnectedError, + ServerFingerprintMismatch, + ServerTimeoutError, + SocketTimeoutError, + TooManyRedirects, + WSMessageTypeError, + WSServerHandshakeError, +) +from .client_middlewares import ClientMiddlewareType, build_client_middlewares +from .client_reqrep import ( + ClientRequest as ClientRequest, + ClientResponse as ClientResponse, + Fingerprint as Fingerprint, + RequestInfo as RequestInfo, + _merge_ssl_params, +) +from .client_ws import ( + DEFAULT_WS_CLIENT_TIMEOUT, + ClientWebSocketResponse as ClientWebSocketResponse, + ClientWSTimeout as ClientWSTimeout, +) +from .connector import ( + HTTP_AND_EMPTY_SCHEMA_SET, + BaseConnector as BaseConnector, + NamedPipeConnector as NamedPipeConnector, + TCPConnector as TCPConnector, + UnixConnector as UnixConnector, +) +from .cookiejar import CookieJar +from .helpers import ( + _SENTINEL, + DEBUG, + EMPTY_BODY_METHODS, + BasicAuth, + TimeoutHandle, + basicauth_from_netrc, + get_env_proxy_for_url, + netrc_from_env, + sentinel, + strip_auth_from_url, +) +from .http import WS_KEY, HttpVersion, WebSocketReader, WebSocketWriter +from .http_websocket import WSHandshakeError, ws_ext_gen, ws_ext_parse +from .tracing import Trace, TraceConfig +from .typedefs import JSONEncoder, LooseCookies, LooseHeaders, Query, StrOrURL + +__all__ = ( + # client_exceptions + "ClientConnectionError", + "ClientConnectionResetError", + "ClientConnectorCertificateError", + "ClientConnectorDNSError", + "ClientConnectorError", + "ClientConnectorSSLError", + "ClientError", + "ClientHttpProxyError", + "ClientOSError", + "ClientPayloadError", + "ClientProxyConnectionError", + "ClientResponseError", + "ClientSSLError", + "ConnectionTimeoutError", + "ContentTypeError", + "InvalidURL", + "InvalidUrlClientError", + "RedirectClientError", + "NonHttpUrlClientError", + "InvalidUrlRedirectClientError", + "NonHttpUrlRedirectClientError", + "ServerConnectionError", + "ServerDisconnectedError", + "ServerFingerprintMismatch", + "ServerTimeoutError", + "SocketTimeoutError", + "TooManyRedirects", + "WSServerHandshakeError", + # client_reqrep + "ClientRequest", + "ClientResponse", + "Fingerprint", + "RequestInfo", + # connector + "BaseConnector", + "TCPConnector", + "UnixConnector", + "NamedPipeConnector", + # client_ws + "ClientWebSocketResponse", + # client + "ClientSession", + "ClientTimeout", + "ClientWSTimeout", + "request", + "WSMessageTypeError", +) + + +if TYPE_CHECKING: + from ssl import SSLContext +else: + SSLContext = None + +if sys.version_info >= (3, 11) and TYPE_CHECKING: + from typing import Unpack + + +class _RequestOptions(TypedDict, total=False): + params: Query + data: Any + json: Any + cookies: Union[LooseCookies, None] + headers: Union[LooseHeaders, None] + skip_auto_headers: Union[Iterable[str], None] + auth: Union[BasicAuth, None] + allow_redirects: bool + max_redirects: int + compress: Union[str, bool, None] + chunked: Union[bool, None] + expect100: bool + raise_for_status: Union[None, bool, Callable[[ClientResponse], Awaitable[None]]] + read_until_eof: bool + proxy: Union[StrOrURL, None] + proxy_auth: Union[BasicAuth, None] + timeout: "Union[ClientTimeout, _SENTINEL, None]" + ssl: Union[SSLContext, bool, Fingerprint] + server_hostname: Union[str, None] + proxy_headers: Union[LooseHeaders, None] + trace_request_ctx: Union[Mapping[str, Any], None] + read_bufsize: Union[int, None] + auto_decompress: Union[bool, None] + max_line_size: Union[int, None] + max_field_size: Union[int, None] + max_headers: Union[int, None] + middlewares: Optional[Sequence[ClientMiddlewareType]] + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class ClientTimeout: + total: Optional[float] = None + connect: Optional[float] = None + sock_read: Optional[float] = None + sock_connect: Optional[float] = None + ceil_threshold: float = 5 + + # pool_queue_timeout: Optional[float] = None + # dns_resolution_timeout: Optional[float] = None + # socket_connect_timeout: Optional[float] = None + # connection_acquiring_timeout: Optional[float] = None + # new_connection_timeout: Optional[float] = None + # http_header_timeout: Optional[float] = None + # response_body_timeout: Optional[float] = None + + # to create a timeout specific for a single request, either + # - create a completely new one to overwrite the default + # - or use http://www.attrs.org/en/stable/api.html#attr.evolve + # to overwrite the defaults + + +# 5 Minute default read timeout +DEFAULT_TIMEOUT: Final[ClientTimeout] = ClientTimeout(total=5 * 60, sock_connect=30) + +# https://www.rfc-editor.org/rfc/rfc9110#section-9.2.2 +IDEMPOTENT_METHODS = frozenset({"GET", "HEAD", "OPTIONS", "TRACE", "PUT", "DELETE"}) + +_RetType = TypeVar("_RetType", ClientResponse, ClientWebSocketResponse) +_CharsetResolver = Callable[[ClientResponse, bytes], str] + + +class ClientSession: + """First-class interface for making HTTP requests.""" + + ATTRS = frozenset( + [ + "_base_url", + "_base_url_origin", + "_source_traceback", + "_connector", + "_loop", + "_cookie_jar", + "_connector_owner", + "_default_auth", + "_version", + "_json_serialize", + "_requote_redirect_url", + "_timeout", + "_raise_for_status", + "_auto_decompress", + "_trust_env", + "_default_headers", + "_skip_auto_headers", + "_request_class", + "_response_class", + "_ws_response_class", + "_trace_configs", + "_read_bufsize", + "_max_line_size", + "_max_field_size", + "_max_headers", + "_resolve_charset", + "_default_proxy", + "_default_proxy_auth", + "_retry_connection", + "_middlewares", + "requote_redirect_url", + ] + ) + + _source_traceback: Optional[traceback.StackSummary] = None + _connector: Optional[BaseConnector] = None + + def __init__( + self, + base_url: Optional[StrOrURL] = None, + *, + connector: Optional[BaseConnector] = None, + loop: Optional[asyncio.AbstractEventLoop] = None, + cookies: Optional[LooseCookies] = None, + headers: Optional[LooseHeaders] = None, + proxy: Optional[StrOrURL] = None, + proxy_auth: Optional[BasicAuth] = None, + skip_auto_headers: Optional[Iterable[str]] = None, + auth: Optional[BasicAuth] = None, + json_serialize: JSONEncoder = json.dumps, + request_class: Type[ClientRequest] = ClientRequest, + response_class: Type[ClientResponse] = ClientResponse, + ws_response_class: Type[ClientWebSocketResponse] = ClientWebSocketResponse, + version: HttpVersion = http.HttpVersion11, + cookie_jar: Optional[AbstractCookieJar] = None, + connector_owner: bool = True, + raise_for_status: Union[ + bool, Callable[[ClientResponse], Awaitable[None]] + ] = False, + read_timeout: Union[float, _SENTINEL] = sentinel, + conn_timeout: Optional[float] = None, + timeout: Union[object, ClientTimeout] = sentinel, + auto_decompress: bool = True, + trust_env: bool = False, + requote_redirect_url: bool = True, + trace_configs: Optional[List[TraceConfig]] = None, + read_bufsize: int = 2**16, + max_line_size: int = 8190, + max_field_size: int = 8190, + max_headers: int = 128, + fallback_charset_resolver: _CharsetResolver = lambda r, b: "utf-8", + middlewares: Sequence[ClientMiddlewareType] = (), + ssl_shutdown_timeout: Union[_SENTINEL, None, float] = sentinel, + ) -> None: + # We initialise _connector to None immediately, as it's referenced in __del__() + # and could cause issues if an exception occurs during initialisation. + self._connector: Optional[BaseConnector] = None + + if loop is None: + if connector is not None: + loop = connector._loop + + loop = loop or asyncio.get_running_loop() + + if base_url is None or isinstance(base_url, URL): + self._base_url: Optional[URL] = base_url + self._base_url_origin = None if base_url is None else base_url.origin() + else: + self._base_url = URL(base_url) + self._base_url_origin = self._base_url.origin() + assert self._base_url.absolute, "Only absolute URLs are supported" + if self._base_url is not None and not self._base_url.path.endswith("/"): + raise ValueError("base_url must have a trailing '/'") + + if timeout is sentinel or timeout is None: + self._timeout = DEFAULT_TIMEOUT + if read_timeout is not sentinel: + warnings.warn( + "read_timeout is deprecated, use timeout argument instead", + DeprecationWarning, + stacklevel=2, + ) + self._timeout = attr.evolve(self._timeout, total=read_timeout) + if conn_timeout is not None: + self._timeout = attr.evolve(self._timeout, connect=conn_timeout) + warnings.warn( + "conn_timeout is deprecated, use timeout argument instead", + DeprecationWarning, + stacklevel=2, + ) + else: + if not isinstance(timeout, ClientTimeout): + raise ValueError( + f"timeout parameter cannot be of {type(timeout)} type, " + "please use 'timeout=ClientTimeout(...)'", + ) + self._timeout = timeout + if read_timeout is not sentinel: + raise ValueError( + "read_timeout and timeout parameters " + "conflict, please setup " + "timeout.read" + ) + if conn_timeout is not None: + raise ValueError( + "conn_timeout and timeout parameters " + "conflict, please setup " + "timeout.connect" + ) + + if ssl_shutdown_timeout is not sentinel: + warnings.warn( + "The ssl_shutdown_timeout parameter is deprecated and will be removed in aiohttp 4.0", + DeprecationWarning, + stacklevel=2, + ) + + if connector is None: + connector = TCPConnector( + loop=loop, ssl_shutdown_timeout=ssl_shutdown_timeout + ) + + if connector._loop is not loop: + raise RuntimeError("Session and connector has to use same event loop") + + self._loop = loop + + if loop.get_debug(): + self._source_traceback = traceback.extract_stack(sys._getframe(1)) + + if cookie_jar is None: + cookie_jar = CookieJar(loop=loop) + self._cookie_jar = cookie_jar + + if cookies: + self._cookie_jar.update_cookies(cookies) + + self._connector = connector + self._connector_owner = connector_owner + self._default_auth = auth + self._version = version + self._json_serialize = json_serialize + self._raise_for_status = raise_for_status + self._auto_decompress = auto_decompress + self._trust_env = trust_env + self._requote_redirect_url = requote_redirect_url + self._read_bufsize = read_bufsize + self._max_line_size = max_line_size + self._max_field_size = max_field_size + self._max_headers = max_headers + + # Convert to list of tuples + if headers: + real_headers: CIMultiDict[str] = CIMultiDict(headers) + else: + real_headers = CIMultiDict() + self._default_headers: CIMultiDict[str] = real_headers + if skip_auto_headers is not None: + self._skip_auto_headers = frozenset(istr(i) for i in skip_auto_headers) + else: + self._skip_auto_headers = frozenset() + + self._request_class = request_class + self._response_class = response_class + self._ws_response_class = ws_response_class + + self._trace_configs = trace_configs or [] + for trace_config in self._trace_configs: + trace_config.freeze() + + self._resolve_charset = fallback_charset_resolver + + self._default_proxy = proxy + self._default_proxy_auth = proxy_auth + self._retry_connection: bool = True + self._middlewares = middlewares + + def __init_subclass__(cls: Type["ClientSession"]) -> None: + warnings.warn( + "Inheritance class {} from ClientSession " + "is discouraged".format(cls.__name__), + DeprecationWarning, + stacklevel=2, + ) + + if DEBUG: + + def __setattr__(self, name: str, val: Any) -> None: + if name not in self.ATTRS: + warnings.warn( + "Setting custom ClientSession.{} attribute " + "is discouraged".format(name), + DeprecationWarning, + stacklevel=2, + ) + super().__setattr__(name, val) + + def __del__(self, _warnings: Any = warnings) -> None: + if not self.closed: + kwargs = {"source": self} + _warnings.warn( + f"Unclosed client session {self!r}", ResourceWarning, **kwargs + ) + context = {"client_session": self, "message": "Unclosed client session"} + if self._source_traceback is not None: + context["source_traceback"] = self._source_traceback + self._loop.call_exception_handler(context) + + if sys.version_info >= (3, 11) and TYPE_CHECKING: + + def request( + self, + method: str, + url: StrOrURL, + **kwargs: Unpack[_RequestOptions], + ) -> "_RequestContextManager": ... + + else: + + def request( + self, method: str, url: StrOrURL, **kwargs: Any + ) -> "_RequestContextManager": + """Perform HTTP request.""" + return _RequestContextManager(self._request(method, url, **kwargs)) + + def _build_url(self, str_or_url: StrOrURL) -> URL: + url = URL(str_or_url) + if self._base_url and not url.absolute: + return self._base_url.join(url) + return url + + async def _request( + self, + method: str, + str_or_url: StrOrURL, + *, + params: Query = None, + data: Any = None, + json: Any = None, + cookies: Optional[LooseCookies] = None, + headers: Optional[LooseHeaders] = None, + skip_auto_headers: Optional[Iterable[str]] = None, + auth: Optional[BasicAuth] = None, + allow_redirects: bool = True, + max_redirects: int = 10, + compress: Union[str, bool, None] = None, + chunked: Optional[bool] = None, + expect100: bool = False, + raise_for_status: Union[ + None, bool, Callable[[ClientResponse], Awaitable[None]] + ] = None, + read_until_eof: bool = True, + proxy: Optional[StrOrURL] = None, + proxy_auth: Optional[BasicAuth] = None, + timeout: Union[ClientTimeout, _SENTINEL] = sentinel, + verify_ssl: Optional[bool] = None, + fingerprint: Optional[bytes] = None, + ssl_context: Optional[SSLContext] = None, + ssl: Union[SSLContext, bool, Fingerprint] = True, + server_hostname: Optional[str] = None, + proxy_headers: Optional[LooseHeaders] = None, + trace_request_ctx: Optional[Mapping[str, Any]] = None, + read_bufsize: Optional[int] = None, + auto_decompress: Optional[bool] = None, + max_line_size: Optional[int] = None, + max_field_size: Optional[int] = None, + max_headers: Optional[int] = None, + middlewares: Optional[Sequence[ClientMiddlewareType]] = None, + ) -> ClientResponse: + + # NOTE: timeout clamps existing connect and read timeouts. We cannot + # set the default to None because we need to detect if the user wants + # to use the existing timeouts by setting timeout to None. + + if self.closed: + raise RuntimeError("Session is closed") + + ssl = _merge_ssl_params(ssl, verify_ssl, ssl_context, fingerprint) + + if data is not None and json is not None: + raise ValueError( + "data and json parameters can not be used at the same time" + ) + elif json is not None: + data = payload.JsonPayload(json, dumps=self._json_serialize) + + if not isinstance(chunked, bool) and chunked is not None: + warnings.warn("Chunk size is deprecated #1615", DeprecationWarning) + + redirects = 0 + history: List[ClientResponse] = [] + version = self._version + params = params or {} + + # Merge with default headers and transform to CIMultiDict + headers = self._prepare_headers(headers) + + try: + url = self._build_url(str_or_url) + except ValueError as e: + raise InvalidUrlClientError(str_or_url) from e + + assert self._connector is not None + if url.scheme not in self._connector.allowed_protocol_schema_set: + raise NonHttpUrlClientError(url) + + skip_headers: Optional[Iterable[istr]] + if skip_auto_headers is not None: + skip_headers = { + istr(i) for i in skip_auto_headers + } | self._skip_auto_headers + elif self._skip_auto_headers: + skip_headers = self._skip_auto_headers + else: + skip_headers = None + + if proxy is None: + proxy = self._default_proxy + if proxy_auth is None: + proxy_auth = self._default_proxy_auth + + if proxy is None: + proxy_headers = None + else: + proxy_headers = self._prepare_headers(proxy_headers) + try: + proxy = URL(proxy) + except ValueError as e: + raise InvalidURL(proxy) from e + + if timeout is sentinel: + real_timeout: ClientTimeout = self._timeout + else: + if not isinstance(timeout, ClientTimeout): + real_timeout = ClientTimeout(total=timeout) + else: + real_timeout = timeout + # timeout is cumulative for all request operations + # (request, redirects, responses, data consuming) + tm = TimeoutHandle( + self._loop, real_timeout.total, ceil_threshold=real_timeout.ceil_threshold + ) + handle = tm.start() + + if read_bufsize is None: + read_bufsize = self._read_bufsize + + if auto_decompress is None: + auto_decompress = self._auto_decompress + + if max_line_size is None: + max_line_size = self._max_line_size + + if max_field_size is None: + max_field_size = self._max_field_size + + if max_headers is None: + max_headers = self._max_headers + + traces = [ + Trace( + self, + trace_config, + trace_config.trace_config_ctx(trace_request_ctx=trace_request_ctx), + ) + for trace_config in self._trace_configs + ] + + for trace in traces: + await trace.send_request_start(method, url.update_query(params), headers) + + timer = tm.timer() + try: + with timer: + # https://www.rfc-editor.org/rfc/rfc9112.html#name-retrying-requests + retry_persistent_connection = ( + self._retry_connection and method in IDEMPOTENT_METHODS + ) + while True: + url, auth_from_url = strip_auth_from_url(url) + if not url.raw_host: + # NOTE: Bail early, otherwise, causes `InvalidURL` through + # NOTE: `self._request_class()` below. + err_exc_cls = ( + InvalidUrlRedirectClientError + if redirects + else InvalidUrlClientError + ) + raise err_exc_cls(url) + # If `auth` was passed for an already authenticated URL, + # disallow only if this is the initial URL; this is to avoid issues + # with sketchy redirects that are not the caller's responsibility + if not history and (auth and auth_from_url): + raise ValueError( + "Cannot combine AUTH argument with " + "credentials encoded in URL" + ) + + # Override the auth with the one from the URL only if we + # have no auth, or if we got an auth from a redirect URL + if auth is None or (history and auth_from_url is not None): + auth = auth_from_url + + if ( + auth is None + and self._default_auth + and ( + not self._base_url or self._base_url_origin == url.origin() + ) + ): + auth = self._default_auth + + # Try netrc if auth is still None and trust_env is enabled. + if auth is None and self._trust_env and url.host is not None: + auth = await self._loop.run_in_executor( + None, self._get_netrc_auth, url.host + ) + + # It would be confusing if we support explicit + # Authorization header with auth argument + if ( + headers is not None + and auth is not None + and hdrs.AUTHORIZATION in headers + ): + raise ValueError( + "Cannot combine AUTHORIZATION header " + "with AUTH argument or credentials " + "encoded in URL" + ) + + all_cookies = self._cookie_jar.filter_cookies(url) + + if cookies is not None: + tmp_cookie_jar = CookieJar( + quote_cookie=self._cookie_jar.quote_cookie + ) + tmp_cookie_jar.update_cookies(cookies) + req_cookies = tmp_cookie_jar.filter_cookies(url) + if req_cookies: + all_cookies.load(req_cookies) + + proxy_: Optional[URL] = None + if proxy is not None: + proxy_ = URL(proxy) + elif self._trust_env: + with suppress(LookupError): + proxy_, proxy_auth = await asyncio.to_thread( + get_env_proxy_for_url, url + ) + + req = self._request_class( + method, + url, + params=params, + headers=headers, + skip_auto_headers=skip_headers, + data=data, + cookies=all_cookies, + auth=auth, + version=version, + compress=compress, + chunked=chunked, + expect100=expect100, + loop=self._loop, + response_class=self._response_class, + proxy=proxy_, + proxy_auth=proxy_auth, + timer=timer, + session=self, + ssl=ssl if ssl is not None else True, + server_hostname=server_hostname, + proxy_headers=proxy_headers, + traces=traces, + trust_env=self.trust_env, + ) + + async def _connect_and_send_request( + req: ClientRequest, + ) -> ClientResponse: + # connection timeout + assert self._connector is not None + try: + conn = await self._connector.connect( + req, traces=traces, timeout=real_timeout + ) + except asyncio.TimeoutError as exc: + raise ConnectionTimeoutError( + f"Connection timeout to host {req.url}" + ) from exc + + assert conn.protocol is not None + conn.protocol.set_response_params( + timer=timer, + skip_payload=req.method in EMPTY_BODY_METHODS, + read_until_eof=read_until_eof, + auto_decompress=auto_decompress, + read_timeout=real_timeout.sock_read, + read_bufsize=read_bufsize, + timeout_ceil_threshold=self._connector._timeout_ceil_threshold, + max_line_size=max_line_size, + max_field_size=max_field_size, + max_headers=max_headers, + ) + try: + resp = await req.send(conn) + try: + await resp.start(conn) + except BaseException: + resp.close() + raise + except BaseException: + conn.close() + raise + return resp + + # Apply middleware (if any) - per-request middleware overrides session middleware + effective_middlewares = ( + self._middlewares if middlewares is None else middlewares + ) + + if effective_middlewares: + handler = build_client_middlewares( + _connect_and_send_request, effective_middlewares + ) + else: + handler = _connect_and_send_request + + try: + resp = await handler(req) + # Client connector errors should not be retried + except ( + ConnectionTimeoutError, + ClientConnectorError, + ClientConnectorCertificateError, + ClientConnectorSSLError, + ): + raise + except (ClientOSError, ServerDisconnectedError): + if retry_persistent_connection: + retry_persistent_connection = False + continue + raise + except ClientError: + raise + except OSError as exc: + if exc.errno is None and isinstance(exc, asyncio.TimeoutError): + raise + raise ClientOSError(*exc.args) from exc + + # Update cookies from raw headers to preserve duplicates + if resp._raw_cookie_headers: + self._cookie_jar.update_cookies_from_headers( + resp._raw_cookie_headers, resp.url + ) + + # redirects + if resp.status in (301, 302, 303, 307, 308) and allow_redirects: + + for trace in traces: + await trace.send_request_redirect( + method, url.update_query(params), headers, resp + ) + + redirects += 1 + history.append(resp) + if max_redirects and redirects >= max_redirects: + if req._body is not None: + await req._body.close() + resp.close() + raise TooManyRedirects( + history[0].request_info, tuple(history) + ) + + # For 301 and 302, mimic IE, now changed in RFC + # https://github.com/kennethreitz/requests/pull/269 + if (resp.status == 303 and resp.method != hdrs.METH_HEAD) or ( + resp.status in (301, 302) and resp.method == hdrs.METH_POST + ): + method = hdrs.METH_GET + data = None + if headers.get(hdrs.CONTENT_LENGTH): + headers.pop(hdrs.CONTENT_LENGTH) + else: + # For 307/308, always preserve the request body + # For 301/302 with non-POST methods, preserve the request body + # https://www.rfc-editor.org/rfc/rfc9110#section-15.4.3-3.1 + # Use the existing payload to avoid recreating it from a potentially consumed file + data = req._body + + r_url = resp.headers.get(hdrs.LOCATION) or resp.headers.get( + hdrs.URI + ) + if r_url is None: + # see github.com/aio-libs/aiohttp/issues/2022 + break + else: + # reading from correct redirection + # response is forbidden + resp.release() + + try: + parsed_redirect_url = URL( + r_url, encoded=not self._requote_redirect_url + ) + except ValueError as e: + if req._body is not None: + await req._body.close() + resp.close() + raise InvalidUrlRedirectClientError( + r_url, + "Server attempted redirecting to a location that does not look like a URL", + ) from e + + scheme = parsed_redirect_url.scheme + if scheme not in HTTP_AND_EMPTY_SCHEMA_SET: + if req._body is not None: + await req._body.close() + resp.close() + raise NonHttpUrlRedirectClientError(r_url) + elif not scheme: + parsed_redirect_url = url.join(parsed_redirect_url) + + try: + redirect_origin = parsed_redirect_url.origin() + except ValueError as origin_val_err: + if req._body is not None: + await req._body.close() + resp.close() + raise InvalidUrlRedirectClientError( + parsed_redirect_url, + "Invalid redirect URL origin", + ) from origin_val_err + + if url.origin() != redirect_origin: + auth = None + headers.pop(hdrs.AUTHORIZATION, None) + headers.pop(hdrs.COOKIE, None) + headers.pop(hdrs.PROXY_AUTHORIZATION, None) + + url = parsed_redirect_url + params = {} + resp.release() + continue + + break + + if req._body is not None: + await req._body.close() + # check response status + if raise_for_status is None: + raise_for_status = self._raise_for_status + + if raise_for_status is None: + pass + elif callable(raise_for_status): + await raise_for_status(resp) + elif raise_for_status: + resp.raise_for_status() + + # register connection + if handle is not None: + if resp.connection is not None: + resp.connection.add_callback(handle.cancel) + else: + handle.cancel() + + resp._history = tuple(history) + + for trace in traces: + await trace.send_request_end( + method, url.update_query(params), headers, resp + ) + return resp + + except BaseException as e: + # cleanup timer + tm.close() + if handle: + handle.cancel() + handle = None + + for trace in traces: + await trace.send_request_exception( + method, url.update_query(params), headers, e + ) + raise + + def ws_connect( + self, + url: StrOrURL, + *, + method: str = hdrs.METH_GET, + protocols: Iterable[str] = (), + timeout: Union[ClientWSTimeout, _SENTINEL] = sentinel, + receive_timeout: Optional[float] = None, + autoclose: bool = True, + autoping: bool = True, + heartbeat: Optional[float] = None, + auth: Optional[BasicAuth] = None, + origin: Optional[str] = None, + params: Query = None, + headers: Optional[LooseHeaders] = None, + proxy: Optional[StrOrURL] = None, + proxy_auth: Optional[BasicAuth] = None, + ssl: Union[SSLContext, bool, Fingerprint] = True, + verify_ssl: Optional[bool] = None, + fingerprint: Optional[bytes] = None, + ssl_context: Optional[SSLContext] = None, + server_hostname: Optional[str] = None, + proxy_headers: Optional[LooseHeaders] = None, + compress: int = 0, + max_msg_size: int = 4 * 1024 * 1024, + ) -> "_WSRequestContextManager": + """Initiate websocket connection.""" + return _WSRequestContextManager( + self._ws_connect( + url, + method=method, + protocols=protocols, + timeout=timeout, + receive_timeout=receive_timeout, + autoclose=autoclose, + autoping=autoping, + heartbeat=heartbeat, + auth=auth, + origin=origin, + params=params, + headers=headers, + proxy=proxy, + proxy_auth=proxy_auth, + ssl=ssl, + verify_ssl=verify_ssl, + fingerprint=fingerprint, + ssl_context=ssl_context, + server_hostname=server_hostname, + proxy_headers=proxy_headers, + compress=compress, + max_msg_size=max_msg_size, + ) + ) + + async def _ws_connect( + self, + url: StrOrURL, + *, + method: str = hdrs.METH_GET, + protocols: Iterable[str] = (), + timeout: Union[ClientWSTimeout, _SENTINEL] = sentinel, + receive_timeout: Optional[float] = None, + autoclose: bool = True, + autoping: bool = True, + heartbeat: Optional[float] = None, + auth: Optional[BasicAuth] = None, + origin: Optional[str] = None, + params: Query = None, + headers: Optional[LooseHeaders] = None, + proxy: Optional[StrOrURL] = None, + proxy_auth: Optional[BasicAuth] = None, + ssl: Union[SSLContext, bool, Fingerprint] = True, + verify_ssl: Optional[bool] = None, + fingerprint: Optional[bytes] = None, + ssl_context: Optional[SSLContext] = None, + server_hostname: Optional[str] = None, + proxy_headers: Optional[LooseHeaders] = None, + compress: int = 0, + max_msg_size: int = 4 * 1024 * 1024, + ) -> ClientWebSocketResponse: + if timeout is not sentinel: + if isinstance(timeout, ClientWSTimeout): + ws_timeout = timeout + else: + warnings.warn( + "parameter 'timeout' of type 'float' " + "is deprecated, please use " + "'timeout=ClientWSTimeout(ws_close=...)'", + DeprecationWarning, + stacklevel=2, + ) + ws_timeout = ClientWSTimeout(ws_close=timeout) + else: + ws_timeout = DEFAULT_WS_CLIENT_TIMEOUT + if receive_timeout is not None: + warnings.warn( + "float parameter 'receive_timeout' " + "is deprecated, please use parameter " + "'timeout=ClientWSTimeout(ws_receive=...)'", + DeprecationWarning, + stacklevel=2, + ) + ws_timeout = attr.evolve(ws_timeout, ws_receive=receive_timeout) + + if headers is None: + real_headers: CIMultiDict[str] = CIMultiDict() + else: + real_headers = CIMultiDict(headers) + + default_headers = { + hdrs.UPGRADE: "websocket", + hdrs.CONNECTION: "Upgrade", + hdrs.SEC_WEBSOCKET_VERSION: "13", + } + + for key, value in default_headers.items(): + real_headers.setdefault(key, value) + + sec_key = base64.b64encode(os.urandom(16)) + real_headers[hdrs.SEC_WEBSOCKET_KEY] = sec_key.decode() + + if protocols: + real_headers[hdrs.SEC_WEBSOCKET_PROTOCOL] = ",".join(protocols) + if origin is not None: + real_headers[hdrs.ORIGIN] = origin + if compress: + extstr = ws_ext_gen(compress=compress) + real_headers[hdrs.SEC_WEBSOCKET_EXTENSIONS] = extstr + + # For the sake of backward compatibility, if user passes in None, convert it to True + if ssl is None: + warnings.warn( + "ssl=None is deprecated, please use ssl=True", + DeprecationWarning, + stacklevel=2, + ) + ssl = True + ssl = _merge_ssl_params(ssl, verify_ssl, ssl_context, fingerprint) + + # send request + resp = await self.request( + method, + url, + params=params, + headers=real_headers, + read_until_eof=False, + auth=auth, + proxy=proxy, + proxy_auth=proxy_auth, + ssl=ssl, + server_hostname=server_hostname, + proxy_headers=proxy_headers, + ) + + try: + # check handshake + if resp.status != 101: + raise WSServerHandshakeError( + resp.request_info, + resp.history, + message="Invalid response status", + status=resp.status, + headers=resp.headers, + ) + + if resp.headers.get(hdrs.UPGRADE, "").lower() != "websocket": + raise WSServerHandshakeError( + resp.request_info, + resp.history, + message="Invalid upgrade header", + status=resp.status, + headers=resp.headers, + ) + + if resp.headers.get(hdrs.CONNECTION, "").lower() != "upgrade": + raise WSServerHandshakeError( + resp.request_info, + resp.history, + message="Invalid connection header", + status=resp.status, + headers=resp.headers, + ) + + # key calculation + r_key = resp.headers.get(hdrs.SEC_WEBSOCKET_ACCEPT, "") + match = base64.b64encode(hashlib.sha1(sec_key + WS_KEY).digest()).decode() + if r_key != match: + raise WSServerHandshakeError( + resp.request_info, + resp.history, + message="Invalid challenge response", + status=resp.status, + headers=resp.headers, + ) + + # websocket protocol + protocol = None + if protocols and hdrs.SEC_WEBSOCKET_PROTOCOL in resp.headers: + resp_protocols = [ + proto.strip() + for proto in resp.headers[hdrs.SEC_WEBSOCKET_PROTOCOL].split(",") + ] + + for proto in resp_protocols: + if proto in protocols: + protocol = proto + break + + # websocket compress + notakeover = False + if compress: + compress_hdrs = resp.headers.get(hdrs.SEC_WEBSOCKET_EXTENSIONS) + if compress_hdrs: + try: + compress, notakeover = ws_ext_parse(compress_hdrs) + except WSHandshakeError as exc: + raise WSServerHandshakeError( + resp.request_info, + resp.history, + message=exc.args[0], + status=resp.status, + headers=resp.headers, + ) from exc + else: + compress = 0 + notakeover = False + + conn = resp.connection + assert conn is not None + conn_proto = conn.protocol + assert conn_proto is not None + + # For WS connection the read_timeout must be either receive_timeout or greater + # None == no timeout, i.e. infinite timeout, so None is the max timeout possible + if ws_timeout.ws_receive is None: + # Reset regardless + conn_proto.read_timeout = None + elif conn_proto.read_timeout is not None: + conn_proto.read_timeout = max( + ws_timeout.ws_receive, conn_proto.read_timeout + ) + + transport = conn.transport + assert transport is not None + reader = WebSocketDataQueue(conn_proto, 2**16, loop=self._loop) + conn_proto.set_parser(WebSocketReader(reader, max_msg_size), reader) + writer = WebSocketWriter( + conn_proto, + transport, + use_mask=True, + compress=compress, + notakeover=notakeover, + ) + except BaseException: + resp.close() + raise + else: + return self._ws_response_class( + reader, + writer, + protocol, + resp, + ws_timeout, + autoclose, + autoping, + self._loop, + heartbeat=heartbeat, + compress=compress, + client_notakeover=notakeover, + ) + + def _prepare_headers(self, headers: Optional[LooseHeaders]) -> "CIMultiDict[str]": + """Add default headers and transform it to CIMultiDict""" + # Convert headers to MultiDict + result = CIMultiDict(self._default_headers) + if headers: + if not isinstance(headers, (MultiDictProxy, MultiDict)): + headers = CIMultiDict(headers) + added_names: Set[str] = set() + for key, value in headers.items(): + if key in added_names: + result.add(key, value) + else: + result[key] = value + added_names.add(key) + return result + + def _get_netrc_auth(self, host: str) -> Optional[BasicAuth]: + """ + Get auth from netrc for the given host. + + This method is designed to be called in an executor to avoid + blocking I/O in the event loop. + """ + netrc_obj = netrc_from_env() + try: + return basicauth_from_netrc(netrc_obj, host) + except LookupError: + return None + + if sys.version_info >= (3, 11) and TYPE_CHECKING: + + def get( + self, + url: StrOrURL, + **kwargs: Unpack[_RequestOptions], + ) -> "_RequestContextManager": ... + + def options( + self, + url: StrOrURL, + **kwargs: Unpack[_RequestOptions], + ) -> "_RequestContextManager": ... + + def head( + self, + url: StrOrURL, + **kwargs: Unpack[_RequestOptions], + ) -> "_RequestContextManager": ... + + def post( + self, + url: StrOrURL, + **kwargs: Unpack[_RequestOptions], + ) -> "_RequestContextManager": ... + + def put( + self, + url: StrOrURL, + **kwargs: Unpack[_RequestOptions], + ) -> "_RequestContextManager": ... + + def patch( + self, + url: StrOrURL, + **kwargs: Unpack[_RequestOptions], + ) -> "_RequestContextManager": ... + + def delete( + self, + url: StrOrURL, + **kwargs: Unpack[_RequestOptions], + ) -> "_RequestContextManager": ... + + else: + + def get( + self, url: StrOrURL, *, allow_redirects: bool = True, **kwargs: Any + ) -> "_RequestContextManager": + """Perform HTTP GET request.""" + return _RequestContextManager( + self._request( + hdrs.METH_GET, url, allow_redirects=allow_redirects, **kwargs + ) + ) + + def options( + self, url: StrOrURL, *, allow_redirects: bool = True, **kwargs: Any + ) -> "_RequestContextManager": + """Perform HTTP OPTIONS request.""" + return _RequestContextManager( + self._request( + hdrs.METH_OPTIONS, url, allow_redirects=allow_redirects, **kwargs + ) + ) + + def head( + self, url: StrOrURL, *, allow_redirects: bool = False, **kwargs: Any + ) -> "_RequestContextManager": + """Perform HTTP HEAD request.""" + return _RequestContextManager( + self._request( + hdrs.METH_HEAD, url, allow_redirects=allow_redirects, **kwargs + ) + ) + + def post( + self, url: StrOrURL, *, data: Any = None, **kwargs: Any + ) -> "_RequestContextManager": + """Perform HTTP POST request.""" + return _RequestContextManager( + self._request(hdrs.METH_POST, url, data=data, **kwargs) + ) + + def put( + self, url: StrOrURL, *, data: Any = None, **kwargs: Any + ) -> "_RequestContextManager": + """Perform HTTP PUT request.""" + return _RequestContextManager( + self._request(hdrs.METH_PUT, url, data=data, **kwargs) + ) + + def patch( + self, url: StrOrURL, *, data: Any = None, **kwargs: Any + ) -> "_RequestContextManager": + """Perform HTTP PATCH request.""" + return _RequestContextManager( + self._request(hdrs.METH_PATCH, url, data=data, **kwargs) + ) + + def delete(self, url: StrOrURL, **kwargs: Any) -> "_RequestContextManager": + """Perform HTTP DELETE request.""" + return _RequestContextManager( + self._request(hdrs.METH_DELETE, url, **kwargs) + ) + + async def close(self) -> None: + """Close underlying connector. + + Release all acquired resources. + """ + if not self.closed: + if self._connector is not None and self._connector_owner: + await self._connector.close() + self._connector = None + + @property + def closed(self) -> bool: + """Is client session closed. + + A readonly property. + """ + return self._connector is None or self._connector.closed + + @property + def connector(self) -> Optional[BaseConnector]: + """Connector instance used for the session.""" + return self._connector + + @property + def cookie_jar(self) -> AbstractCookieJar: + """The session cookies.""" + return self._cookie_jar + + @property + def version(self) -> Tuple[int, int]: + """The session HTTP protocol version.""" + return self._version + + @property + def requote_redirect_url(self) -> bool: + """Do URL requoting on redirection handling.""" + return self._requote_redirect_url + + @requote_redirect_url.setter + def requote_redirect_url(self, val: bool) -> None: + """Do URL requoting on redirection handling.""" + warnings.warn( + "session.requote_redirect_url modification is deprecated #2778", + DeprecationWarning, + stacklevel=2, + ) + self._requote_redirect_url = val + + @property + def loop(self) -> asyncio.AbstractEventLoop: + """Session's loop.""" + warnings.warn( + "client.loop property is deprecated", DeprecationWarning, stacklevel=2 + ) + return self._loop + + @property + def timeout(self) -> ClientTimeout: + """Timeout for the session.""" + return self._timeout + + @property + def headers(self) -> "CIMultiDict[str]": + """The default headers of the client session.""" + return self._default_headers + + @property + def skip_auto_headers(self) -> FrozenSet[istr]: + """Headers for which autogeneration should be skipped""" + return self._skip_auto_headers + + @property + def auth(self) -> Optional[BasicAuth]: + """An object that represents HTTP Basic Authorization""" + return self._default_auth + + @property + def json_serialize(self) -> JSONEncoder: + """Json serializer callable""" + return self._json_serialize + + @property + def connector_owner(self) -> bool: + """Should connector be closed on session closing""" + return self._connector_owner + + @property + def raise_for_status( + self, + ) -> Union[bool, Callable[[ClientResponse], Awaitable[None]]]: + """Should `ClientResponse.raise_for_status()` be called for each response.""" + return self._raise_for_status + + @property + def auto_decompress(self) -> bool: + """Should the body response be automatically decompressed.""" + return self._auto_decompress + + @property + def trust_env(self) -> bool: + """ + Should proxies information from environment or netrc be trusted. + + Information is from HTTP_PROXY / HTTPS_PROXY environment variables + or ~/.netrc file if present. + """ + return self._trust_env + + @property + def trace_configs(self) -> List[TraceConfig]: + """A list of TraceConfig instances used for client tracing""" + return self._trace_configs + + def detach(self) -> None: + """Detach connector from session without closing the former. + + Session is switched to closed state anyway. + """ + self._connector = None + + def __enter__(self) -> None: + raise TypeError("Use async with instead") + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + # __exit__ should exist in pair with __enter__ but never executed + pass # pragma: no cover + + async def __aenter__(self) -> "ClientSession": + return self + + async def __aexit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + await self.close() + + +class _BaseRequestContextManager(Coroutine[Any, Any, _RetType], Generic[_RetType]): + + __slots__ = ("_coro", "_resp") + + def __init__(self, coro: Coroutine["asyncio.Future[Any]", None, _RetType]) -> None: + self._coro: Coroutine["asyncio.Future[Any]", None, _RetType] = coro + + def send(self, arg: None) -> "asyncio.Future[Any]": + return self._coro.send(arg) + + def throw(self, *args: Any, **kwargs: Any) -> "asyncio.Future[Any]": + return self._coro.throw(*args, **kwargs) + + def close(self) -> None: + return self._coro.close() + + def __await__(self) -> Generator[Any, None, _RetType]: + ret = self._coro.__await__() + return ret + + def __iter__(self) -> Generator[Any, None, _RetType]: + return self.__await__() + + async def __aenter__(self) -> _RetType: + self._resp: _RetType = await self._coro + return await self._resp.__aenter__() + + async def __aexit__( + self, + exc_type: Optional[Type[BaseException]], + exc: Optional[BaseException], + tb: Optional[TracebackType], + ) -> None: + await self._resp.__aexit__(exc_type, exc, tb) + + +_RequestContextManager = _BaseRequestContextManager[ClientResponse] +_WSRequestContextManager = _BaseRequestContextManager[ClientWebSocketResponse] + + +class _SessionRequestContextManager: + + __slots__ = ("_coro", "_resp", "_session") + + def __init__( + self, + coro: Coroutine["asyncio.Future[Any]", None, ClientResponse], + session: ClientSession, + ) -> None: + self._coro = coro + self._resp: Optional[ClientResponse] = None + self._session = session + + async def __aenter__(self) -> ClientResponse: + try: + self._resp = await self._coro + except BaseException: + await self._session.close() + raise + else: + return self._resp + + async def __aexit__( + self, + exc_type: Optional[Type[BaseException]], + exc: Optional[BaseException], + tb: Optional[TracebackType], + ) -> None: + assert self._resp is not None + self._resp.close() + await self._session.close() + + +if sys.version_info >= (3, 11) and TYPE_CHECKING: + + def request( + method: str, + url: StrOrURL, + *, + version: HttpVersion = http.HttpVersion11, + connector: Optional[BaseConnector] = None, + loop: Optional[asyncio.AbstractEventLoop] = None, + **kwargs: Unpack[_RequestOptions], + ) -> _SessionRequestContextManager: ... + +else: + + def request( + method: str, + url: StrOrURL, + *, + version: HttpVersion = http.HttpVersion11, + connector: Optional[BaseConnector] = None, + loop: Optional[asyncio.AbstractEventLoop] = None, + **kwargs: Any, + ) -> _SessionRequestContextManager: + """Constructs and sends a request. + + Returns response object. + method - HTTP method + url - request url + params - (optional) Dictionary or bytes to be sent in the query + string of the new request + data - (optional) Dictionary, bytes, or file-like object to + send in the body of the request + json - (optional) Any json compatible python object + headers - (optional) Dictionary of HTTP Headers to send with + the request + cookies - (optional) Dict object to send with the request + auth - (optional) BasicAuth named tuple represent HTTP Basic Auth + auth - aiohttp.helpers.BasicAuth + allow_redirects - (optional) If set to False, do not follow + redirects + version - Request HTTP version. + compress - Set to True if request has to be compressed + with deflate encoding. + chunked - Set to chunk size for chunked transfer encoding. + expect100 - Expect 100-continue response from server. + connector - BaseConnector sub-class instance to support + connection pooling. + read_until_eof - Read response until eof if response + does not have Content-Length header. + loop - Optional event loop. + timeout - Optional ClientTimeout settings structure, 5min + total timeout by default. + Usage:: + >>> import aiohttp + >>> async with aiohttp.request('GET', 'http://python.org/') as resp: + ... print(resp) + ... data = await resp.read() + + """ + connector_owner = False + if connector is None: + connector_owner = True + connector = TCPConnector(loop=loop, force_close=True) + + session = ClientSession( + loop=loop, + cookies=kwargs.pop("cookies", None), + version=version, + timeout=kwargs.pop("timeout", sentinel), + connector=connector, + connector_owner=connector_owner, + ) + + return _SessionRequestContextManager( + session._request(method, url, **kwargs), + session, + ) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/client_exceptions.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/client_exceptions.py new file mode 100644 index 0000000000000000000000000000000000000000..4702da8d0ae21dc3575ed2403e27fc0a3c28bc27 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/client_exceptions.py @@ -0,0 +1,432 @@ +"""HTTP related errors.""" + +import asyncio +import warnings +from typing import TYPE_CHECKING, Optional, Tuple, Union + +from multidict import MultiMapping + +from .typedefs import StrOrURL + +if TYPE_CHECKING: + import ssl + + SSLContext = ssl.SSLContext +else: + try: + import ssl + + SSLContext = ssl.SSLContext + except ImportError: # pragma: no cover + ssl = SSLContext = None # type: ignore[assignment] + +if TYPE_CHECKING: + from .client_reqrep import ClientResponse, ConnectionKey, Fingerprint, RequestInfo + from .http_parser import RawResponseMessage +else: + RequestInfo = ClientResponse = ConnectionKey = RawResponseMessage = None + +__all__ = ( + "ClientError", + "ClientConnectionError", + "ClientConnectionResetError", + "ClientOSError", + "ClientConnectorError", + "ClientProxyConnectionError", + "ClientSSLError", + "ClientConnectorDNSError", + "ClientConnectorSSLError", + "ClientConnectorCertificateError", + "ConnectionTimeoutError", + "SocketTimeoutError", + "ServerConnectionError", + "ServerTimeoutError", + "ServerDisconnectedError", + "ServerFingerprintMismatch", + "ClientResponseError", + "ClientHttpProxyError", + "WSServerHandshakeError", + "ContentTypeError", + "ClientPayloadError", + "InvalidURL", + "InvalidUrlClientError", + "RedirectClientError", + "NonHttpUrlClientError", + "InvalidUrlRedirectClientError", + "NonHttpUrlRedirectClientError", + "WSMessageTypeError", +) + + +class ClientError(Exception): + """Base class for client connection errors.""" + + +class ClientResponseError(ClientError): + """Base class for exceptions that occur after getting a response. + + request_info: An instance of RequestInfo. + history: A sequence of responses, if redirects occurred. + status: HTTP status code. + message: Error message. + headers: Response headers. + """ + + def __init__( + self, + request_info: RequestInfo, + history: Tuple[ClientResponse, ...], + *, + code: Optional[int] = None, + status: Optional[int] = None, + message: str = "", + headers: Optional[MultiMapping[str]] = None, + ) -> None: + self.request_info = request_info + if code is not None: + if status is not None: + raise ValueError( + "Both code and status arguments are provided; " + "code is deprecated, use status instead" + ) + warnings.warn( + "code argument is deprecated, use status instead", + DeprecationWarning, + stacklevel=2, + ) + if status is not None: + self.status = status + elif code is not None: + self.status = code + else: + self.status = 0 + self.message = message + self.headers = headers + self.history = history + self.args = (request_info, history) + + def __str__(self) -> str: + return "{}, message={!r}, url={!r}".format( + self.status, + self.message, + str(self.request_info.real_url), + ) + + def __repr__(self) -> str: + args = f"{self.request_info!r}, {self.history!r}" + if self.status != 0: + args += f", status={self.status!r}" + if self.message != "": + args += f", message={self.message!r}" + if self.headers is not None: + args += f", headers={self.headers!r}" + return f"{type(self).__name__}({args})" + + @property + def code(self) -> int: + warnings.warn( + "code property is deprecated, use status instead", + DeprecationWarning, + stacklevel=2, + ) + return self.status + + @code.setter + def code(self, value: int) -> None: + warnings.warn( + "code property is deprecated, use status instead", + DeprecationWarning, + stacklevel=2, + ) + self.status = value + + +class ContentTypeError(ClientResponseError): + """ContentType found is not valid.""" + + +class WSServerHandshakeError(ClientResponseError): + """websocket server handshake error.""" + + +class ClientHttpProxyError(ClientResponseError): + """HTTP proxy error. + + Raised in :class:`aiohttp.connector.TCPConnector` if + proxy responds with status other than ``200 OK`` + on ``CONNECT`` request. + """ + + +class TooManyRedirects(ClientResponseError): + """Client was redirected too many times.""" + + +class ClientConnectionError(ClientError): + """Base class for client socket errors.""" + + +class ClientConnectionResetError(ClientConnectionError, ConnectionResetError): + """ConnectionResetError""" + + +class ClientOSError(ClientConnectionError, OSError): + """OSError error.""" + + +class ClientConnectorError(ClientOSError): + """Client connector error. + + Raised in :class:`aiohttp.connector.TCPConnector` if + a connection can not be established. + """ + + def __init__(self, connection_key: ConnectionKey, os_error: OSError) -> None: + self._conn_key = connection_key + self._os_error = os_error + super().__init__(os_error.errno, os_error.strerror) + self.args = (connection_key, os_error) + + @property + def os_error(self) -> OSError: + return self._os_error + + @property + def host(self) -> str: + return self._conn_key.host + + @property + def port(self) -> Optional[int]: + return self._conn_key.port + + @property + def ssl(self) -> Union[SSLContext, bool, "Fingerprint"]: + return self._conn_key.ssl + + def __str__(self) -> str: + return "Cannot connect to host {0.host}:{0.port} ssl:{1} [{2}]".format( + self, "default" if self.ssl is True else self.ssl, self.strerror + ) + + # OSError.__reduce__ does too much black magick + __reduce__ = BaseException.__reduce__ + + +class ClientConnectorDNSError(ClientConnectorError): + """DNS resolution failed during client connection. + + Raised in :class:`aiohttp.connector.TCPConnector` if + DNS resolution fails. + """ + + +class ClientProxyConnectionError(ClientConnectorError): + """Proxy connection error. + + Raised in :class:`aiohttp.connector.TCPConnector` if + connection to proxy can not be established. + """ + + +class UnixClientConnectorError(ClientConnectorError): + """Unix connector error. + + Raised in :py:class:`aiohttp.connector.UnixConnector` + if connection to unix socket can not be established. + """ + + def __init__( + self, path: str, connection_key: ConnectionKey, os_error: OSError + ) -> None: + self._path = path + super().__init__(connection_key, os_error) + + @property + def path(self) -> str: + return self._path + + def __str__(self) -> str: + return "Cannot connect to unix socket {0.path} ssl:{1} [{2}]".format( + self, "default" if self.ssl is True else self.ssl, self.strerror + ) + + +class ServerConnectionError(ClientConnectionError): + """Server connection errors.""" + + +class ServerDisconnectedError(ServerConnectionError): + """Server disconnected.""" + + def __init__(self, message: Union[RawResponseMessage, str, None] = None) -> None: + if message is None: + message = "Server disconnected" + + self.args = (message,) + self.message = message + + +class ServerTimeoutError(ServerConnectionError, asyncio.TimeoutError): + """Server timeout error.""" + + +class ConnectionTimeoutError(ServerTimeoutError): + """Connection timeout error.""" + + +class SocketTimeoutError(ServerTimeoutError): + """Socket timeout error.""" + + +class ServerFingerprintMismatch(ServerConnectionError): + """SSL certificate does not match expected fingerprint.""" + + def __init__(self, expected: bytes, got: bytes, host: str, port: int) -> None: + self.expected = expected + self.got = got + self.host = host + self.port = port + self.args = (expected, got, host, port) + + def __repr__(self) -> str: + return "<{} expected={!r} got={!r} host={!r} port={!r}>".format( + self.__class__.__name__, self.expected, self.got, self.host, self.port + ) + + +class ClientPayloadError(ClientError): + """Response payload error.""" + + +class InvalidURL(ClientError, ValueError): + """Invalid URL. + + URL used for fetching is malformed, e.g. it doesn't contains host + part. + """ + + # Derive from ValueError for backward compatibility + + def __init__(self, url: StrOrURL, description: Union[str, None] = None) -> None: + # The type of url is not yarl.URL because the exception can be raised + # on URL(url) call + self._url = url + self._description = description + + if description: + super().__init__(url, description) + else: + super().__init__(url) + + @property + def url(self) -> StrOrURL: + return self._url + + @property + def description(self) -> "str | None": + return self._description + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} {self}>" + + def __str__(self) -> str: + if self._description: + return f"{self._url} - {self._description}" + return str(self._url) + + +class InvalidUrlClientError(InvalidURL): + """Invalid URL client error.""" + + +class RedirectClientError(ClientError): + """Client redirect error.""" + + +class NonHttpUrlClientError(ClientError): + """Non http URL client error.""" + + +class InvalidUrlRedirectClientError(InvalidUrlClientError, RedirectClientError): + """Invalid URL redirect client error.""" + + +class NonHttpUrlRedirectClientError(NonHttpUrlClientError, RedirectClientError): + """Non http URL redirect client error.""" + + +class ClientSSLError(ClientConnectorError): + """Base error for ssl.*Errors.""" + + +if ssl is not None: + cert_errors = (ssl.CertificateError,) + cert_errors_bases = ( + ClientSSLError, + ssl.CertificateError, + ) + + ssl_errors = (ssl.SSLError,) + ssl_error_bases = (ClientSSLError, ssl.SSLError) +else: # pragma: no cover + cert_errors = tuple() + cert_errors_bases = ( + ClientSSLError, + ValueError, + ) + + ssl_errors = tuple() + ssl_error_bases = (ClientSSLError,) + + +class ClientConnectorSSLError(*ssl_error_bases): # type: ignore[misc] + """Response ssl error.""" + + +class ClientConnectorCertificateError(*cert_errors_bases): # type: ignore[misc] + """Response certificate error.""" + + _conn_key: ConnectionKey + + def __init__( + # TODO: If we require ssl in future, this can become ssl.CertificateError + self, + connection_key: ConnectionKey, + certificate_error: Exception, + ) -> None: + if isinstance(certificate_error, cert_errors + (OSError,)): + # ssl.CertificateError has errno and strerror, so we should be fine + os_error = certificate_error + else: + os_error = OSError() + + super().__init__(connection_key, os_error) + self._certificate_error = certificate_error + self.args = (connection_key, certificate_error) + + @property + def certificate_error(self) -> Exception: + return self._certificate_error + + @property + def host(self) -> str: + return self._conn_key.host + + @property + def port(self) -> Optional[int]: + return self._conn_key.port + + @property + def ssl(self) -> bool: + return self._conn_key.is_ssl + + def __str__(self) -> str: + return ( + "Cannot connect to host {0.host}:{0.port} ssl:{0.ssl} " + "[{0.certificate_error.__class__.__name__}: " + "{0.certificate_error.args}]".format(self) + ) + + +class WSMessageTypeError(TypeError): + """WebSocket message type is not valid.""" diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/client_middleware_digest_auth.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/client_middleware_digest_auth.py new file mode 100644 index 0000000000000000000000000000000000000000..d7f2f1eb9f8403a6d0035e37783f72e1ab1e60f6 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/client_middleware_digest_auth.py @@ -0,0 +1,480 @@ +""" +Digest authentication middleware for aiohttp client. + +This middleware implements HTTP Digest Authentication according to RFC 7616, +providing a more secure alternative to Basic Authentication. It supports all +standard hash algorithms including MD5, SHA, SHA-256, SHA-512 and their session +variants, as well as both 'auth' and 'auth-int' quality of protection (qop) options. +""" + +import hashlib +import os +import re +import sys +import time +from typing import ( + Callable, + Dict, + Final, + FrozenSet, + List, + Literal, + Tuple, + TypedDict, + Union, +) + +from yarl import URL + +from . import hdrs +from .client_exceptions import ClientError +from .client_middlewares import ClientHandlerType +from .client_reqrep import ClientRequest, ClientResponse +from .payload import Payload + + +class DigestAuthChallenge(TypedDict, total=False): + realm: str + nonce: str + qop: str + algorithm: str + opaque: str + domain: str + stale: str + + +DigestFunctions: Dict[str, Callable[[bytes], "hashlib._Hash"]] = { + "MD5": hashlib.md5, + "MD5-SESS": hashlib.md5, + "SHA": hashlib.sha1, + "SHA-SESS": hashlib.sha1, + "SHA256": hashlib.sha256, + "SHA256-SESS": hashlib.sha256, + "SHA-256": hashlib.sha256, + "SHA-256-SESS": hashlib.sha256, + "SHA512": hashlib.sha512, + "SHA512-SESS": hashlib.sha512, + "SHA-512": hashlib.sha512, + "SHA-512-SESS": hashlib.sha512, +} + + +# Compile the regex pattern once at module level for performance +_HEADER_PAIRS_PATTERN = re.compile( + r'(?:^|\s|,\s*)(\w+)\s*=\s*(?:"((?:[^"\\]|\\.)*)"|([^\s,]+))' + if sys.version_info < (3, 11) + else r'(?:^|\s|,\s*)((?>\w+))\s*=\s*(?:"((?:[^"\\]|\\.)*)"|([^\s,]+))' + # +------------|--------|--|-|-|--|----|------|----|--||-----|-> Match valid start/sep + # +--------|--|-|-|--|----|------|----|--||-----|-> alphanumeric key (atomic + # | | | | | | | | || | group reduces backtracking) + # +--|-|-|--|----|------|----|--||-----|-> maybe whitespace + # | | | | | | | || | + # +-|-|--|----|------|----|--||-----|-> = (delimiter) + # +-|--|----|------|----|--||-----|-> maybe whitespace + # | | | | | || | + # +--|----|------|----|--||-----|-> group quoted or unquoted + # | | | | || | + # +----|------|----|--||-----|-> if quoted... + # +------|----|--||-----|-> anything but " or \ + # +----|--||-----|-> escaped characters allowed + # +--||-----|-> or can be empty string + # || | + # +|-----|-> if unquoted... + # +-----|-> anything but , or + # +-> at least one char req'd +) + + +# RFC 7616: Challenge parameters to extract +CHALLENGE_FIELDS: Final[ + Tuple[ + Literal["realm", "nonce", "qop", "algorithm", "opaque", "domain", "stale"], ... + ] +] = ( + "realm", + "nonce", + "qop", + "algorithm", + "opaque", + "domain", + "stale", +) + +# Supported digest authentication algorithms +# Use a tuple of sorted keys for predictable documentation and error messages +SUPPORTED_ALGORITHMS: Final[Tuple[str, ...]] = tuple(sorted(DigestFunctions.keys())) + +# RFC 7616: Fields that require quoting in the Digest auth header +# These fields must be enclosed in double quotes in the Authorization header. +# Algorithm, qop, and nc are never quoted per RFC specifications. +# This frozen set is used by the template-based header construction to +# automatically determine which fields need quotes. +QUOTED_AUTH_FIELDS: Final[FrozenSet[str]] = frozenset( + {"username", "realm", "nonce", "uri", "response", "opaque", "cnonce"} +) + + +def escape_quotes(value: str) -> str: + """Escape double quotes for HTTP header values.""" + return value.replace('"', '\\"') + + +def unescape_quotes(value: str) -> str: + """Unescape double quotes in HTTP header values.""" + return value.replace('\\"', '"') + + +def parse_header_pairs(header: str) -> Dict[str, str]: + """ + Parse key-value pairs from WWW-Authenticate or similar HTTP headers. + + This function handles the complex format of WWW-Authenticate header values, + supporting both quoted and unquoted values, proper handling of commas in + quoted values, and whitespace variations per RFC 7616. + + Examples of supported formats: + - key1="value1", key2=value2 + - key1 = "value1" , key2="value, with, commas" + - key1=value1,key2="value2" + - realm="example.com", nonce="12345", qop="auth" + + Args: + header: The header value string to parse + + Returns: + Dictionary mapping parameter names to their values + """ + return { + stripped_key: unescape_quotes(quoted_val) if quoted_val else unquoted_val + for key, quoted_val, unquoted_val in _HEADER_PAIRS_PATTERN.findall(header) + if (stripped_key := key.strip()) + } + + +class DigestAuthMiddleware: + """ + HTTP digest authentication middleware for aiohttp client. + + This middleware intercepts 401 Unauthorized responses containing a Digest + authentication challenge, calculates the appropriate digest credentials, + and automatically retries the request with the proper Authorization header. + + Features: + - Handles all aspects of Digest authentication handshake automatically + - Supports all standard hash algorithms: + - MD5, MD5-SESS + - SHA, SHA-SESS + - SHA256, SHA256-SESS, SHA-256, SHA-256-SESS + - SHA512, SHA512-SESS, SHA-512, SHA-512-SESS + - Supports 'auth' and 'auth-int' quality of protection modes + - Properly handles quoted strings and parameter parsing + - Includes replay attack protection with client nonce count tracking + - Supports preemptive authentication per RFC 7616 Section 3.6 + + Standards compliance: + - RFC 7616: HTTP Digest Access Authentication (primary reference) + - RFC 2617: HTTP Authentication (deprecated by RFC 7616) + - RFC 1945: Section 11.1 (username restrictions) + + Implementation notes: + The core digest calculation is inspired by the implementation in + https://github.com/requests/requests/blob/v2.18.4/requests/auth.py + with added support for modern digest auth features and error handling. + """ + + def __init__( + self, + login: str, + password: str, + preemptive: bool = True, + ) -> None: + if login is None: + raise ValueError("None is not allowed as login value") + + if password is None: + raise ValueError("None is not allowed as password value") + + if ":" in login: + raise ValueError('A ":" is not allowed in username (RFC 1945#section-11.1)') + + self._login_str: Final[str] = login + self._login_bytes: Final[bytes] = login.encode("utf-8") + self._password_bytes: Final[bytes] = password.encode("utf-8") + + self._last_nonce_bytes = b"" + self._nonce_count = 0 + self._challenge: DigestAuthChallenge = {} + self._preemptive: bool = preemptive + # Set of URLs defining the protection space + self._protection_space: List[str] = [] + + async def _encode( + self, method: str, url: URL, body: Union[Payload, Literal[b""]] + ) -> str: + """ + Build digest authorization header for the current challenge. + + Args: + method: The HTTP method (GET, POST, etc.) + url: The request URL + body: The request body (used for qop=auth-int) + + Returns: + A fully formatted Digest authorization header string + + Raises: + ClientError: If the challenge is missing required parameters or + contains unsupported values + + """ + challenge = self._challenge + if "realm" not in challenge: + raise ClientError( + "Malformed Digest auth challenge: Missing 'realm' parameter" + ) + + if "nonce" not in challenge: + raise ClientError( + "Malformed Digest auth challenge: Missing 'nonce' parameter" + ) + + # Empty realm values are allowed per RFC 7616 (SHOULD, not MUST, contain host name) + realm = challenge["realm"] + nonce = challenge["nonce"] + + # Empty nonce values are not allowed as they are security-critical for replay protection + if not nonce: + raise ClientError( + "Security issue: Digest auth challenge contains empty 'nonce' value" + ) + + qop_raw = challenge.get("qop", "") + # Preserve original algorithm case for response while using uppercase for processing + algorithm_original = challenge.get("algorithm", "MD5") + algorithm = algorithm_original.upper() + opaque = challenge.get("opaque", "") + + # Convert string values to bytes once + nonce_bytes = nonce.encode("utf-8") + realm_bytes = realm.encode("utf-8") + path = URL(url).path_qs + + # Process QoP + qop = "" + qop_bytes = b"" + if qop_raw: + valid_qops = {"auth", "auth-int"}.intersection( + {q.strip() for q in qop_raw.split(",") if q.strip()} + ) + if not valid_qops: + raise ClientError( + f"Digest auth error: Unsupported Quality of Protection (qop) value(s): {qop_raw}" + ) + + qop = "auth-int" if "auth-int" in valid_qops else "auth" + qop_bytes = qop.encode("utf-8") + + if algorithm not in DigestFunctions: + raise ClientError( + f"Digest auth error: Unsupported hash algorithm: {algorithm}. " + f"Supported algorithms: {', '.join(SUPPORTED_ALGORITHMS)}" + ) + hash_fn: Final = DigestFunctions[algorithm] + + def H(x: bytes) -> bytes: + """RFC 7616 Section 3: Hash function H(data) = hex(hash(data)).""" + return hash_fn(x).hexdigest().encode() + + def KD(s: bytes, d: bytes) -> bytes: + """RFC 7616 Section 3: KD(secret, data) = H(concat(secret, ":", data)).""" + return H(b":".join((s, d))) + + # Calculate A1 and A2 + A1 = b":".join((self._login_bytes, realm_bytes, self._password_bytes)) + A2 = f"{method.upper()}:{path}".encode() + if qop == "auth-int": + if isinstance(body, Payload): # will always be empty bytes unless Payload + entity_bytes = await body.as_bytes() # Get bytes from Payload + else: + entity_bytes = body + entity_hash = H(entity_bytes) + A2 = b":".join((A2, entity_hash)) + + HA1 = H(A1) + HA2 = H(A2) + + # Nonce count handling + if nonce_bytes == self._last_nonce_bytes: + self._nonce_count += 1 + else: + self._nonce_count = 1 + + self._last_nonce_bytes = nonce_bytes + ncvalue = f"{self._nonce_count:08x}" + ncvalue_bytes = ncvalue.encode("utf-8") + + # Generate client nonce + cnonce = hashlib.sha1( + b"".join( + [ + str(self._nonce_count).encode("utf-8"), + nonce_bytes, + time.ctime().encode("utf-8"), + os.urandom(8), + ] + ) + ).hexdigest()[:16] + cnonce_bytes = cnonce.encode("utf-8") + + # Special handling for session-based algorithms + if algorithm.upper().endswith("-SESS"): + HA1 = H(b":".join((HA1, nonce_bytes, cnonce_bytes))) + + # Calculate the response digest + if qop: + noncebit = b":".join( + (nonce_bytes, ncvalue_bytes, cnonce_bytes, qop_bytes, HA2) + ) + response_digest = KD(HA1, noncebit) + else: + response_digest = KD(HA1, b":".join((nonce_bytes, HA2))) + + # Define a dict mapping of header fields to their values + # Group fields into always-present, optional, and qop-dependent + header_fields = { + # Always present fields + "username": escape_quotes(self._login_str), + "realm": escape_quotes(realm), + "nonce": escape_quotes(nonce), + "uri": path, + "response": response_digest.decode(), + "algorithm": algorithm_original, + } + + # Optional fields + if opaque: + header_fields["opaque"] = escape_quotes(opaque) + + # QoP-dependent fields + if qop: + header_fields["qop"] = qop + header_fields["nc"] = ncvalue + header_fields["cnonce"] = cnonce + + # Build header using templates for each field type + pairs: List[str] = [] + for field, value in header_fields.items(): + if field in QUOTED_AUTH_FIELDS: + pairs.append(f'{field}="{value}"') + else: + pairs.append(f"{field}={value}") + + return f"Digest {', '.join(pairs)}" + + def _in_protection_space(self, url: URL) -> bool: + """ + Check if the given URL is within the current protection space. + + According to RFC 7616, a URI is in the protection space if any URI + in the protection space is a prefix of it (after both have been made absolute). + """ + request_str = str(url) + for space_str in self._protection_space: + # Check if request starts with space URL + if not request_str.startswith(space_str): + continue + # Exact match or space ends with / (proper directory prefix) + if len(request_str) == len(space_str) or space_str[-1] == "/": + return True + # Check next char is / to ensure proper path boundary + if request_str[len(space_str)] == "/": + return True + return False + + def _authenticate(self, response: ClientResponse) -> bool: + """ + Takes the given response and tries digest-auth, if needed. + + Returns true if the original request must be resent. + """ + if response.status != 401: + return False + + auth_header = response.headers.get("www-authenticate", "") + if not auth_header: + return False # No authentication header present + + method, sep, headers = auth_header.partition(" ") + if not sep: + # No space found in www-authenticate header + return False # Malformed auth header, missing scheme separator + + if method.lower() != "digest": + # Not a digest auth challenge (could be Basic, Bearer, etc.) + return False + + if not headers: + # We have a digest scheme but no parameters + return False # Malformed digest header, missing parameters + + # We have a digest auth header with content + if not (header_pairs := parse_header_pairs(headers)): + # Failed to parse any key-value pairs + return False # Malformed digest header, no valid parameters + + # Extract challenge parameters + self._challenge = {} + for field in CHALLENGE_FIELDS: + if (value := header_pairs.get(field)) is not None: + self._challenge[field] = value + + # Update protection space based on domain parameter or default to origin + origin = response.url.origin() + + if domain := self._challenge.get("domain"): + # Parse space-separated list of URIs + self._protection_space = [] + for uri in domain.split(): + # Remove quotes if present + uri = uri.strip('"') + if uri.startswith("/"): + # Path-absolute, relative to origin + self._protection_space.append(str(origin.join(URL(uri)))) + else: + # Absolute URI + self._protection_space.append(str(URL(uri))) + else: + # No domain specified, protection space is entire origin + self._protection_space = [str(origin)] + + # Return True only if we found at least one challenge parameter + return bool(self._challenge) + + async def __call__( + self, request: ClientRequest, handler: ClientHandlerType + ) -> ClientResponse: + """Run the digest auth middleware.""" + response = None + for retry_count in range(2): + # Apply authorization header if: + # 1. This is a retry after 401 (retry_count > 0), OR + # 2. Preemptive auth is enabled AND we have a challenge AND the URL is in protection space + if retry_count > 0 or ( + self._preemptive + and self._challenge + and self._in_protection_space(request.url) + ): + request.headers[hdrs.AUTHORIZATION] = await self._encode( + request.method, request.url, request.body + ) + + # Send the request + response = await handler(request) + + # Check if we need to authenticate + if not self._authenticate(response): + break + + # At this point, response is guaranteed to be defined + assert response is not None + return response diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/client_middlewares.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/client_middlewares.py new file mode 100644 index 0000000000000000000000000000000000000000..3ca2cb202ad93963369f2a10fd1d118a194c4405 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/client_middlewares.py @@ -0,0 +1,55 @@ +"""Client middleware support.""" + +from collections.abc import Awaitable, Callable, Sequence + +from .client_reqrep import ClientRequest, ClientResponse + +__all__ = ("ClientMiddlewareType", "ClientHandlerType", "build_client_middlewares") + +# Type alias for client request handlers - functions that process requests and return responses +ClientHandlerType = Callable[[ClientRequest], Awaitable[ClientResponse]] + +# Type for client middleware - similar to server but uses ClientRequest/ClientResponse +ClientMiddlewareType = Callable[ + [ClientRequest, ClientHandlerType], Awaitable[ClientResponse] +] + + +def build_client_middlewares( + handler: ClientHandlerType, + middlewares: Sequence[ClientMiddlewareType], +) -> ClientHandlerType: + """ + Apply middlewares to request handler. + + The middlewares are applied in reverse order, so the first middleware + in the list wraps all subsequent middlewares and the handler. + + This implementation avoids using partial/update_wrapper to minimize overhead + and doesn't cache to avoid holding references to stateful middleware. + """ + # Optimize for single middleware case + if len(middlewares) == 1: + middleware = middlewares[0] + + async def single_middleware_handler(req: ClientRequest) -> ClientResponse: + return await middleware(req, handler) + + return single_middleware_handler + + # Build the chain for multiple middlewares + current_handler = handler + + for middleware in reversed(middlewares): + # Create a new closure that captures the current state + def make_wrapper( + mw: ClientMiddlewareType, next_h: ClientHandlerType + ) -> ClientHandlerType: + async def wrapped(req: ClientRequest) -> ClientResponse: + return await mw(req, next_h) + + return wrapped + + current_handler = make_wrapper(middleware, current_handler) + + return current_handler diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/client_proto.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/client_proto.py new file mode 100644 index 0000000000000000000000000000000000000000..aea31917f484eb3d67ca24c46105971ee17fa08b --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/client_proto.py @@ -0,0 +1,361 @@ +import asyncio +from contextlib import suppress +from typing import Any, Optional, Tuple, Union + +from .base_protocol import BaseProtocol +from .client_exceptions import ( + ClientConnectionError, + ClientOSError, + ClientPayloadError, + ServerDisconnectedError, + SocketTimeoutError, +) +from .helpers import ( + _EXC_SENTINEL, + EMPTY_BODY_STATUS_CODES, + BaseTimerContext, + set_exception, + set_result, +) +from .http import HttpResponseParser, RawResponseMessage +from .http_exceptions import HttpProcessingError +from .streams import EMPTY_PAYLOAD, DataQueue, StreamReader + + +class ResponseHandler(BaseProtocol, DataQueue[Tuple[RawResponseMessage, StreamReader]]): + """Helper class to adapt between Protocol and StreamReader.""" + + def __init__(self, loop: asyncio.AbstractEventLoop) -> None: + BaseProtocol.__init__(self, loop=loop) + DataQueue.__init__(self, loop) + + self._should_close = False + + self._payload: Optional[StreamReader] = None + self._skip_payload = False + self._payload_parser = None + + self._timer = None + + self._tail = b"" + self._upgraded = False + self._parser: Optional[HttpResponseParser] = None + + self._read_timeout: Optional[float] = None + self._read_timeout_handle: Optional[asyncio.TimerHandle] = None + + self._timeout_ceil_threshold: Optional[float] = 5 + + self._closed: Union[None, asyncio.Future[None]] = None + self._connection_lost_called = False + + @property + def closed(self) -> Union[None, asyncio.Future[None]]: + """Future that is set when the connection is closed. + + This property returns a Future that will be completed when the connection + is closed. The Future is created lazily on first access to avoid creating + futures that will never be awaited. + + Returns: + - A Future[None] if the connection is still open or was closed after + this property was accessed + - None if connection_lost() was already called before this property + was ever accessed (indicating no one is waiting for the closure) + """ + if self._closed is None and not self._connection_lost_called: + self._closed = self._loop.create_future() + return self._closed + + @property + def upgraded(self) -> bool: + return self._upgraded + + @property + def should_close(self) -> bool: + return bool( + self._should_close + or (self._payload is not None and not self._payload.is_eof()) + or self._upgraded + or self._exception is not None + or self._payload_parser is not None + or self._buffer + or self._tail + ) + + def force_close(self) -> None: + self._should_close = True + + def close(self) -> None: + self._exception = None # Break cyclic references + transport = self.transport + if transport is not None: + transport.close() + self.transport = None + self._payload = None + self._drop_timeout() + + def abort(self) -> None: + self._exception = None # Break cyclic references + transport = self.transport + if transport is not None: + transport.abort() + self.transport = None + self._payload = None + self._drop_timeout() + + def is_connected(self) -> bool: + return self.transport is not None and not self.transport.is_closing() + + def connection_lost(self, exc: Optional[BaseException]) -> None: + self._connection_lost_called = True + self._drop_timeout() + + original_connection_error = exc + reraised_exc = original_connection_error + + connection_closed_cleanly = original_connection_error is None + + if self._closed is not None: + # If someone is waiting for the closed future, + # we should set it to None or an exception. If + # self._closed is None, it means that + # connection_lost() was called already + # or nobody is waiting for it. + if connection_closed_cleanly: + set_result(self._closed, None) + else: + assert original_connection_error is not None + set_exception( + self._closed, + ClientConnectionError( + f"Connection lost: {original_connection_error !s}", + ), + original_connection_error, + ) + + if self._payload_parser is not None: + with suppress(Exception): # FIXME: log this somehow? + self._payload_parser.feed_eof() + + uncompleted = None + if self._parser is not None: + try: + uncompleted = self._parser.feed_eof() + except Exception as underlying_exc: + if self._payload is not None: + client_payload_exc_msg = ( + f"Response payload is not completed: {underlying_exc !r}" + ) + if not connection_closed_cleanly: + client_payload_exc_msg = ( + f"{client_payload_exc_msg !s}. " + f"{original_connection_error !r}" + ) + set_exception( + self._payload, + ClientPayloadError(client_payload_exc_msg), + underlying_exc, + ) + + if not self.is_eof(): + if isinstance(original_connection_error, OSError): + reraised_exc = ClientOSError(*original_connection_error.args) + if connection_closed_cleanly: + reraised_exc = ServerDisconnectedError(uncompleted) + # assigns self._should_close to True as side effect, + # we do it anyway below + underlying_non_eof_exc = ( + _EXC_SENTINEL + if connection_closed_cleanly + else original_connection_error + ) + assert underlying_non_eof_exc is not None + assert reraised_exc is not None + self.set_exception(reraised_exc, underlying_non_eof_exc) + + self._should_close = True + self._parser = None + self._payload = None + self._payload_parser = None + self._reading_paused = False + + super().connection_lost(reraised_exc) + + def eof_received(self) -> None: + # should call parser.feed_eof() most likely + self._drop_timeout() + + def pause_reading(self) -> None: + super().pause_reading() + self._drop_timeout() + + def resume_reading(self) -> None: + super().resume_reading() + self._reschedule_timeout() + + def set_exception( + self, + exc: BaseException, + exc_cause: BaseException = _EXC_SENTINEL, + ) -> None: + self._should_close = True + self._drop_timeout() + super().set_exception(exc, exc_cause) + + def set_parser(self, parser: Any, payload: Any) -> None: + # TODO: actual types are: + # parser: WebSocketReader + # payload: WebSocketDataQueue + # but they are not generi enough + # Need an ABC for both types + self._payload = payload + self._payload_parser = parser + + self._drop_timeout() + + if self._tail: + data, self._tail = self._tail, b"" + self.data_received(data) + + def set_response_params( + self, + *, + timer: Optional[BaseTimerContext] = None, + skip_payload: bool = False, + read_until_eof: bool = False, + auto_decompress: bool = True, + read_timeout: Optional[float] = None, + read_bufsize: int = 2**16, + timeout_ceil_threshold: float = 5, + max_line_size: int = 8190, + max_field_size: int = 8190, + max_headers: int = 128, + ) -> None: + self._skip_payload = skip_payload + + self._read_timeout = read_timeout + + self._timeout_ceil_threshold = timeout_ceil_threshold + + self._parser = HttpResponseParser( + self, + self._loop, + read_bufsize, + timer=timer, + payload_exception=ClientPayloadError, + response_with_body=not skip_payload, + read_until_eof=read_until_eof, + auto_decompress=auto_decompress, + max_line_size=max_line_size, + max_field_size=max_field_size, + max_headers=max_headers, + ) + + if self._tail: + data, self._tail = self._tail, b"" + self.data_received(data) + + def _drop_timeout(self) -> None: + if self._read_timeout_handle is not None: + self._read_timeout_handle.cancel() + self._read_timeout_handle = None + + def _reschedule_timeout(self) -> None: + timeout = self._read_timeout + if self._read_timeout_handle is not None: + self._read_timeout_handle.cancel() + + if timeout: + self._read_timeout_handle = self._loop.call_later( + timeout, self._on_read_timeout + ) + else: + self._read_timeout_handle = None + + def start_timeout(self) -> None: + self._reschedule_timeout() + + @property + def read_timeout(self) -> Optional[float]: + return self._read_timeout + + @read_timeout.setter + def read_timeout(self, read_timeout: Optional[float]) -> None: + self._read_timeout = read_timeout + + def _on_read_timeout(self) -> None: + exc = SocketTimeoutError("Timeout on reading data from socket") + self.set_exception(exc) + if self._payload is not None: + set_exception(self._payload, exc) + + def data_received(self, data: bytes) -> None: + self._reschedule_timeout() + + if not data: + return + + # custom payload parser - currently always WebSocketReader + if self._payload_parser is not None: + eof, tail = self._payload_parser.feed_data(data) + if eof: + self._payload = None + self._payload_parser = None + + if tail: + self.data_received(tail) + return + + if self._upgraded or self._parser is None: + # i.e. websocket connection, websocket parser is not set yet + self._tail += data + return + + # parse http messages + try: + messages, upgraded, tail = self._parser.feed_data(data) + except BaseException as underlying_exc: + if self.transport is not None: + # connection.release() could be called BEFORE + # data_received(), the transport is already + # closed in this case + self.transport.close() + # should_close is True after the call + if isinstance(underlying_exc, HttpProcessingError): + exc = HttpProcessingError( + code=underlying_exc.code, + message=underlying_exc.message, + headers=underlying_exc.headers, + ) + else: + exc = HttpProcessingError() + self.set_exception(exc, underlying_exc) + return + + self._upgraded = upgraded + + payload: Optional[StreamReader] = None + for message, payload in messages: + if message.should_close: + self._should_close = True + + self._payload = payload + + if self._skip_payload or message.code in EMPTY_BODY_STATUS_CODES: + self.feed_data((message, EMPTY_PAYLOAD), 0) + else: + self.feed_data((message, payload), 0) + + if payload is not None: + # new message(s) was processed + # register timeout handler unsubscribing + # either on end-of-stream or immediately for + # EMPTY_PAYLOAD + if payload is not EMPTY_PAYLOAD: + payload.on_eof(self._drop_timeout) + else: + self._drop_timeout() + + if upgraded and tail: + self.data_received(tail) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/client_reqrep.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/client_reqrep.py new file mode 100644 index 0000000000000000000000000000000000000000..a9e0795893d6a56595f6f7b04b62cc4486d48a0f --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/client_reqrep.py @@ -0,0 +1,1536 @@ +import asyncio +import codecs +import contextlib +import functools +import io +import re +import sys +import traceback +import warnings +from collections.abc import Mapping +from hashlib import md5, sha1, sha256 +from http.cookies import Morsel, SimpleCookie +from types import MappingProxyType, TracebackType +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Dict, + Iterable, + List, + Literal, + NamedTuple, + Optional, + Tuple, + Type, + Union, +) + +import attr +from multidict import CIMultiDict, CIMultiDictProxy, MultiDict, MultiDictProxy +from yarl import URL + +from . import hdrs, helpers, http, multipart, payload +from ._cookie_helpers import ( + parse_cookie_header, + parse_set_cookie_headers, + preserve_morsel_with_coded_value, +) +from .abc import AbstractStreamWriter +from .client_exceptions import ( + ClientConnectionError, + ClientOSError, + ClientResponseError, + ContentTypeError, + InvalidURL, + ServerFingerprintMismatch, +) +from .compression_utils import HAS_BROTLI, HAS_ZSTD +from .formdata import FormData +from .helpers import ( + _SENTINEL, + BaseTimerContext, + BasicAuth, + HeadersMixin, + TimerNoop, + noop, + reify, + sentinel, + set_exception, + set_result, +) +from .http import ( + SERVER_SOFTWARE, + HttpVersion, + HttpVersion10, + HttpVersion11, + StreamWriter, +) +from .streams import StreamReader +from .typedefs import ( + DEFAULT_JSON_DECODER, + JSONDecoder, + LooseCookies, + LooseHeaders, + Query, + RawHeaders, +) + +if TYPE_CHECKING: + import ssl + from ssl import SSLContext +else: + try: + import ssl + from ssl import SSLContext + except ImportError: # pragma: no cover + ssl = None # type: ignore[assignment] + SSLContext = object # type: ignore[misc,assignment] + + +__all__ = ("ClientRequest", "ClientResponse", "RequestInfo", "Fingerprint") + + +if TYPE_CHECKING: + from .client import ClientSession + from .connector import Connection + from .tracing import Trace + + +_CONNECTION_CLOSED_EXCEPTION = ClientConnectionError("Connection closed") +_CONTAINS_CONTROL_CHAR_RE = re.compile(r"[^-!#$%&'*+.^_`|~0-9a-zA-Z]") +json_re = re.compile(r"^application/(?:[\w.+-]+?\+)?json") + + +def _gen_default_accept_encoding() -> str: + encodings = [ + "gzip", + "deflate", + ] + if HAS_BROTLI: + encodings.append("br") + if HAS_ZSTD: + encodings.append("zstd") + return ", ".join(encodings) + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class ContentDisposition: + type: Optional[str] + parameters: "MappingProxyType[str, str]" + filename: Optional[str] + + +class _RequestInfo(NamedTuple): + url: URL + method: str + headers: "CIMultiDictProxy[str]" + real_url: URL + + +class RequestInfo(_RequestInfo): + + def __new__( + cls, + url: URL, + method: str, + headers: "CIMultiDictProxy[str]", + real_url: Union[URL, _SENTINEL] = sentinel, + ) -> "RequestInfo": + """Create a new RequestInfo instance. + + For backwards compatibility, the real_url parameter is optional. + """ + return tuple.__new__( + cls, (url, method, headers, url if real_url is sentinel else real_url) + ) + + +class Fingerprint: + HASHFUNC_BY_DIGESTLEN = { + 16: md5, + 20: sha1, + 32: sha256, + } + + def __init__(self, fingerprint: bytes) -> None: + digestlen = len(fingerprint) + hashfunc = self.HASHFUNC_BY_DIGESTLEN.get(digestlen) + if not hashfunc: + raise ValueError("fingerprint has invalid length") + elif hashfunc is md5 or hashfunc is sha1: + raise ValueError("md5 and sha1 are insecure and not supported. Use sha256.") + self._hashfunc = hashfunc + self._fingerprint = fingerprint + + @property + def fingerprint(self) -> bytes: + return self._fingerprint + + def check(self, transport: asyncio.Transport) -> None: + if not transport.get_extra_info("sslcontext"): + return + sslobj = transport.get_extra_info("ssl_object") + cert = sslobj.getpeercert(binary_form=True) + got = self._hashfunc(cert).digest() + if got != self._fingerprint: + host, port, *_ = transport.get_extra_info("peername") + raise ServerFingerprintMismatch(self._fingerprint, got, host, port) + + +if ssl is not None: + SSL_ALLOWED_TYPES = (ssl.SSLContext, bool, Fingerprint, type(None)) +else: # pragma: no cover + SSL_ALLOWED_TYPES = (bool, type(None)) + + +def _merge_ssl_params( + ssl: Union["SSLContext", bool, Fingerprint], + verify_ssl: Optional[bool], + ssl_context: Optional["SSLContext"], + fingerprint: Optional[bytes], +) -> Union["SSLContext", bool, Fingerprint]: + if ssl is None: + ssl = True # Double check for backwards compatibility + if verify_ssl is not None and not verify_ssl: + warnings.warn( + "verify_ssl is deprecated, use ssl=False instead", + DeprecationWarning, + stacklevel=3, + ) + if ssl is not True: + raise ValueError( + "verify_ssl, ssl_context, fingerprint and ssl " + "parameters are mutually exclusive" + ) + else: + ssl = False + if ssl_context is not None: + warnings.warn( + "ssl_context is deprecated, use ssl=context instead", + DeprecationWarning, + stacklevel=3, + ) + if ssl is not True: + raise ValueError( + "verify_ssl, ssl_context, fingerprint and ssl " + "parameters are mutually exclusive" + ) + else: + ssl = ssl_context + if fingerprint is not None: + warnings.warn( + "fingerprint is deprecated, use ssl=Fingerprint(fingerprint) instead", + DeprecationWarning, + stacklevel=3, + ) + if ssl is not True: + raise ValueError( + "verify_ssl, ssl_context, fingerprint and ssl " + "parameters are mutually exclusive" + ) + else: + ssl = Fingerprint(fingerprint) + if not isinstance(ssl, SSL_ALLOWED_TYPES): + raise TypeError( + "ssl should be SSLContext, bool, Fingerprint or None, " + "got {!r} instead.".format(ssl) + ) + return ssl + + +_SSL_SCHEMES = frozenset(("https", "wss")) + + +# ConnectionKey is a NamedTuple because it is used as a key in a dict +# and a set in the connector. Since a NamedTuple is a tuple it uses +# the fast native tuple __hash__ and __eq__ implementation in CPython. +class ConnectionKey(NamedTuple): + # the key should contain an information about used proxy / TLS + # to prevent reusing wrong connections from a pool + host: str + port: Optional[int] + is_ssl: bool + ssl: Union[SSLContext, bool, Fingerprint] + proxy: Optional[URL] + proxy_auth: Optional[BasicAuth] + proxy_headers_hash: Optional[int] # hash(CIMultiDict) + + +def _is_expected_content_type( + response_content_type: str, expected_content_type: str +) -> bool: + if expected_content_type == "application/json": + return json_re.match(response_content_type) is not None + return expected_content_type in response_content_type + + +def _warn_if_unclosed_payload(payload: payload.Payload, stacklevel: int = 2) -> None: + """Warn if the payload is not closed. + + Callers must check that the body is a Payload before calling this method. + + Args: + payload: The payload to check + stacklevel: Stack level for the warning (default 2 for direct callers) + """ + if not payload.autoclose and not payload.consumed: + warnings.warn( + "The previous request body contains unclosed resources. " + "Use await request.update_body() instead of setting request.body " + "directly to properly close resources and avoid leaks.", + ResourceWarning, + stacklevel=stacklevel, + ) + + +class ClientResponse(HeadersMixin): + + # Some of these attributes are None when created, + # but will be set by the start() method. + # As the end user will likely never see the None values, we cheat the types below. + # from the Status-Line of the response + version: Optional[HttpVersion] = None # HTTP-Version + status: int = None # type: ignore[assignment] # Status-Code + reason: Optional[str] = None # Reason-Phrase + + content: StreamReader = None # type: ignore[assignment] # Payload stream + _body: Optional[bytes] = None + _headers: CIMultiDictProxy[str] = None # type: ignore[assignment] + _history: Tuple["ClientResponse", ...] = () + _raw_headers: RawHeaders = None # type: ignore[assignment] + + _connection: Optional["Connection"] = None # current connection + _cookies: Optional[SimpleCookie] = None + _raw_cookie_headers: Optional[Tuple[str, ...]] = None + _continue: Optional["asyncio.Future[bool]"] = None + _source_traceback: Optional[traceback.StackSummary] = None + _session: Optional["ClientSession"] = None + # set up by ClientRequest after ClientResponse object creation + # post-init stage allows to not change ctor signature + _closed = True # to allow __del__ for non-initialized properly response + _released = False + _in_context = False + + _resolve_charset: Callable[["ClientResponse", bytes], str] = lambda *_: "utf-8" + + __writer: Optional["asyncio.Task[None]"] = None + + def __init__( + self, + method: str, + url: URL, + *, + writer: "Optional[asyncio.Task[None]]", + continue100: Optional["asyncio.Future[bool]"], + timer: BaseTimerContext, + request_info: RequestInfo, + traces: List["Trace"], + loop: asyncio.AbstractEventLoop, + session: "ClientSession", + ) -> None: + # URL forbids subclasses, so a simple type check is enough. + assert type(url) is URL + + self.method = method + + self._real_url = url + self._url = url.with_fragment(None) if url.raw_fragment else url + if writer is not None: + self._writer = writer + if continue100 is not None: + self._continue = continue100 + self._request_info = request_info + self._timer = timer if timer is not None else TimerNoop() + self._cache: Dict[str, Any] = {} + self._traces = traces + self._loop = loop + # Save reference to _resolve_charset, so that get_encoding() will still + # work after the response has finished reading the body. + # TODO: Fix session=None in tests (see ClientRequest.__init__). + if session is not None: + # store a reference to session #1985 + self._session = session + self._resolve_charset = session._resolve_charset + if loop.get_debug(): + self._source_traceback = traceback.extract_stack(sys._getframe(1)) + + def __reset_writer(self, _: object = None) -> None: + self.__writer = None + + @property + def _writer(self) -> Optional["asyncio.Task[None]"]: + """The writer task for streaming data. + + _writer is only provided for backwards compatibility + for subclasses that may need to access it. + """ + return self.__writer + + @_writer.setter + def _writer(self, writer: Optional["asyncio.Task[None]"]) -> None: + """Set the writer task for streaming data.""" + if self.__writer is not None: + self.__writer.remove_done_callback(self.__reset_writer) + self.__writer = writer + if writer is None: + return + if writer.done(): + # The writer is already done, so we can clear it immediately. + self.__writer = None + else: + writer.add_done_callback(self.__reset_writer) + + @property + def cookies(self) -> SimpleCookie: + if self._cookies is None: + if self._raw_cookie_headers is not None: + # Parse cookies for response.cookies (SimpleCookie for backward compatibility) + cookies = SimpleCookie() + # Use parse_set_cookie_headers for more lenient parsing that handles + # malformed cookies better than SimpleCookie.load + cookies.update(parse_set_cookie_headers(self._raw_cookie_headers)) + self._cookies = cookies + else: + self._cookies = SimpleCookie() + return self._cookies + + @cookies.setter + def cookies(self, cookies: SimpleCookie) -> None: + self._cookies = cookies + # Generate raw cookie headers from the SimpleCookie + if cookies: + self._raw_cookie_headers = tuple( + morsel.OutputString() for morsel in cookies.values() + ) + else: + self._raw_cookie_headers = None + + @reify + def url(self) -> URL: + return self._url + + @reify + def url_obj(self) -> URL: + warnings.warn("Deprecated, use .url #1654", DeprecationWarning, stacklevel=2) + return self._url + + @reify + def real_url(self) -> URL: + return self._real_url + + @reify + def host(self) -> str: + assert self._url.host is not None + return self._url.host + + @reify + def headers(self) -> "CIMultiDictProxy[str]": + return self._headers + + @reify + def raw_headers(self) -> RawHeaders: + return self._raw_headers + + @reify + def request_info(self) -> RequestInfo: + return self._request_info + + @reify + def content_disposition(self) -> Optional[ContentDisposition]: + raw = self._headers.get(hdrs.CONTENT_DISPOSITION) + if raw is None: + return None + disposition_type, params_dct = multipart.parse_content_disposition(raw) + params = MappingProxyType(params_dct) + filename = multipart.content_disposition_filename(params) + return ContentDisposition(disposition_type, params, filename) + + def __del__(self, _warnings: Any = warnings) -> None: + if self._closed: + return + + if self._connection is not None: + self._connection.release() + self._cleanup_writer() + + if self._loop.get_debug(): + kwargs = {"source": self} + _warnings.warn(f"Unclosed response {self!r}", ResourceWarning, **kwargs) + context = {"client_response": self, "message": "Unclosed response"} + if self._source_traceback: + context["source_traceback"] = self._source_traceback + self._loop.call_exception_handler(context) + + def __repr__(self) -> str: + out = io.StringIO() + ascii_encodable_url = str(self.url) + if self.reason: + ascii_encodable_reason = self.reason.encode( + "ascii", "backslashreplace" + ).decode("ascii") + else: + ascii_encodable_reason = "None" + print( + "".format( + ascii_encodable_url, self.status, ascii_encodable_reason + ), + file=out, + ) + print(self.headers, file=out) + return out.getvalue() + + @property + def connection(self) -> Optional["Connection"]: + return self._connection + + @reify + def history(self) -> Tuple["ClientResponse", ...]: + """A sequence of of responses, if redirects occurred.""" + return self._history + + @reify + def links(self) -> "MultiDictProxy[MultiDictProxy[Union[str, URL]]]": + links_str = ", ".join(self.headers.getall("link", [])) + + if not links_str: + return MultiDictProxy(MultiDict()) + + links: MultiDict[MultiDictProxy[Union[str, URL]]] = MultiDict() + + for val in re.split(r",(?=\s*<)", links_str): + match = re.match(r"\s*<(.*)>(.*)", val) + if match is None: # pragma: no cover + # the check exists to suppress mypy error + continue + url, params_str = match.groups() + params = params_str.split(";")[1:] + + link: MultiDict[Union[str, URL]] = MultiDict() + + for param in params: + match = re.match(r"^\s*(\S*)\s*=\s*(['\"]?)(.*?)(\2)\s*$", param, re.M) + if match is None: # pragma: no cover + # the check exists to suppress mypy error + continue + key, _, value, _ = match.groups() + + link.add(key, value) + + key = link.get("rel", url) + + link.add("url", self.url.join(URL(url))) + + links.add(str(key), MultiDictProxy(link)) + + return MultiDictProxy(links) + + async def start(self, connection: "Connection") -> "ClientResponse": + """Start response processing.""" + self._closed = False + self._protocol = connection.protocol + self._connection = connection + + with self._timer: + while True: + # read response + try: + protocol = self._protocol + message, payload = await protocol.read() # type: ignore[union-attr] + except http.HttpProcessingError as exc: + raise ClientResponseError( + self.request_info, + self.history, + status=exc.code, + message=exc.message, + headers=exc.headers, + ) from exc + + if message.code < 100 or message.code > 199 or message.code == 101: + break + + if self._continue is not None: + set_result(self._continue, True) + self._continue = None + + # payload eof handler + payload.on_eof(self._response_eof) + + # response status + self.version = message.version + self.status = message.code + self.reason = message.reason + + # headers + self._headers = message.headers # type is CIMultiDictProxy + self._raw_headers = message.raw_headers # type is Tuple[bytes, bytes] + + # payload + self.content = payload + + # cookies + if cookie_hdrs := self.headers.getall(hdrs.SET_COOKIE, ()): + # Store raw cookie headers for CookieJar + self._raw_cookie_headers = tuple(cookie_hdrs) + return self + + def _response_eof(self) -> None: + if self._closed: + return + + # protocol could be None because connection could be detached + protocol = self._connection and self._connection.protocol + if protocol is not None and protocol.upgraded: + return + + self._closed = True + self._cleanup_writer() + self._release_connection() + + @property + def closed(self) -> bool: + return self._closed + + def close(self) -> None: + if not self._released: + self._notify_content() + + self._closed = True + if self._loop is None or self._loop.is_closed(): + return + + self._cleanup_writer() + if self._connection is not None: + self._connection.close() + self._connection = None + + def release(self) -> Any: + if not self._released: + self._notify_content() + + self._closed = True + + self._cleanup_writer() + self._release_connection() + return noop() + + @property + def ok(self) -> bool: + """Returns ``True`` if ``status`` is less than ``400``, ``False`` if not. + + This is **not** a check for ``200 OK`` but a check that the response + status is under 400. + """ + return 400 > self.status + + def raise_for_status(self) -> None: + if not self.ok: + # reason should always be not None for a started response + assert self.reason is not None + + # If we're in a context we can rely on __aexit__() to release as the + # exception propagates. + if not self._in_context: + self.release() + + raise ClientResponseError( + self.request_info, + self.history, + status=self.status, + message=self.reason, + headers=self.headers, + ) + + def _release_connection(self) -> None: + if self._connection is not None: + if self.__writer is None: + self._connection.release() + self._connection = None + else: + self.__writer.add_done_callback(lambda f: self._release_connection()) + + async def _wait_released(self) -> None: + if self.__writer is not None: + try: + await self.__writer + except asyncio.CancelledError: + if ( + sys.version_info >= (3, 11) + and (task := asyncio.current_task()) + and task.cancelling() + ): + raise + self._release_connection() + + def _cleanup_writer(self) -> None: + if self.__writer is not None: + self.__writer.cancel() + self._session = None + + def _notify_content(self) -> None: + content = self.content + if content and content.exception() is None: + set_exception(content, _CONNECTION_CLOSED_EXCEPTION) + self._released = True + + async def wait_for_close(self) -> None: + if self.__writer is not None: + try: + await self.__writer + except asyncio.CancelledError: + if ( + sys.version_info >= (3, 11) + and (task := asyncio.current_task()) + and task.cancelling() + ): + raise + self.release() + + async def read(self) -> bytes: + """Read response payload.""" + if self._body is None: + try: + self._body = await self.content.read() + for trace in self._traces: + await trace.send_response_chunk_received( + self.method, self.url, self._body + ) + except BaseException: + self.close() + raise + elif self._released: # Response explicitly released + raise ClientConnectionError("Connection closed") + + protocol = self._connection and self._connection.protocol + if protocol is None or not protocol.upgraded: + await self._wait_released() # Underlying connection released + return self._body + + def get_encoding(self) -> str: + ctype = self.headers.get(hdrs.CONTENT_TYPE, "").lower() + mimetype = helpers.parse_mimetype(ctype) + + encoding = mimetype.parameters.get("charset") + if encoding: + with contextlib.suppress(LookupError, ValueError): + return codecs.lookup(encoding).name + + if mimetype.type == "application" and ( + mimetype.subtype == "json" or mimetype.subtype == "rdap" + ): + # RFC 7159 states that the default encoding is UTF-8. + # RFC 7483 defines application/rdap+json + return "utf-8" + + if self._body is None: + raise RuntimeError( + "Cannot compute fallback encoding of a not yet read body" + ) + + return self._resolve_charset(self, self._body) + + async def text(self, encoding: Optional[str] = None, errors: str = "strict") -> str: + """Read response payload and decode.""" + if self._body is None: + await self.read() + + if encoding is None: + encoding = self.get_encoding() + + return self._body.decode(encoding, errors=errors) # type: ignore[union-attr] + + async def json( + self, + *, + encoding: Optional[str] = None, + loads: JSONDecoder = DEFAULT_JSON_DECODER, + content_type: Optional[str] = "application/json", + ) -> Any: + """Read and decodes JSON response.""" + if self._body is None: + await self.read() + + if content_type: + ctype = self.headers.get(hdrs.CONTENT_TYPE, "").lower() + if not _is_expected_content_type(ctype, content_type): + raise ContentTypeError( + self.request_info, + self.history, + status=self.status, + message=( + "Attempt to decode JSON with unexpected mimetype: %s" % ctype + ), + headers=self.headers, + ) + + stripped = self._body.strip() # type: ignore[union-attr] + if not stripped: + return None + + if encoding is None: + encoding = self.get_encoding() + + return loads(stripped.decode(encoding)) + + async def __aenter__(self) -> "ClientResponse": + self._in_context = True + return self + + async def __aexit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + self._in_context = False + # similar to _RequestContextManager, we do not need to check + # for exceptions, response object can close connection + # if state is broken + self.release() + await self.wait_for_close() + + +class ClientRequest: + GET_METHODS = { + hdrs.METH_GET, + hdrs.METH_HEAD, + hdrs.METH_OPTIONS, + hdrs.METH_TRACE, + } + POST_METHODS = {hdrs.METH_PATCH, hdrs.METH_POST, hdrs.METH_PUT} + ALL_METHODS = GET_METHODS.union(POST_METHODS).union({hdrs.METH_DELETE}) + + DEFAULT_HEADERS = { + hdrs.ACCEPT: "*/*", + hdrs.ACCEPT_ENCODING: _gen_default_accept_encoding(), + } + + # Type of body depends on PAYLOAD_REGISTRY, which is dynamic. + _body: Union[None, payload.Payload] = None + auth = None + response = None + + __writer: Optional["asyncio.Task[None]"] = None # async task for streaming data + + # These class defaults help create_autospec() work correctly. + # If autospec is improved in future, maybe these can be removed. + url = URL() + method = "GET" + + _continue = None # waiter future for '100 Continue' response + + _skip_auto_headers: Optional["CIMultiDict[None]"] = None + + # N.B. + # Adding __del__ method with self._writer closing doesn't make sense + # because _writer is instance method, thus it keeps a reference to self. + # Until writer has finished finalizer will not be called. + + def __init__( + self, + method: str, + url: URL, + *, + params: Query = None, + headers: Optional[LooseHeaders] = None, + skip_auto_headers: Optional[Iterable[str]] = None, + data: Any = None, + cookies: Optional[LooseCookies] = None, + auth: Optional[BasicAuth] = None, + version: http.HttpVersion = http.HttpVersion11, + compress: Union[str, bool, None] = None, + chunked: Optional[bool] = None, + expect100: bool = False, + loop: Optional[asyncio.AbstractEventLoop] = None, + response_class: Optional[Type["ClientResponse"]] = None, + proxy: Optional[URL] = None, + proxy_auth: Optional[BasicAuth] = None, + timer: Optional[BaseTimerContext] = None, + session: Optional["ClientSession"] = None, + ssl: Union[SSLContext, bool, Fingerprint] = True, + proxy_headers: Optional[LooseHeaders] = None, + traces: Optional[List["Trace"]] = None, + trust_env: bool = False, + server_hostname: Optional[str] = None, + ): + if loop is None: + loop = asyncio.get_event_loop() + if match := _CONTAINS_CONTROL_CHAR_RE.search(method): + raise ValueError( + f"Method cannot contain non-token characters {method!r} " + f"(found at least {match.group()!r})" + ) + # URL forbids subclasses, so a simple type check is enough. + assert type(url) is URL, url + if proxy is not None: + assert type(proxy) is URL, proxy + # FIXME: session is None in tests only, need to fix tests + # assert session is not None + if TYPE_CHECKING: + assert session is not None + self._session = session + if params: + url = url.extend_query(params) + self.original_url = url + self.url = url.with_fragment(None) if url.raw_fragment else url + self.method = method.upper() + self.chunked = chunked + self.compress = compress + self.loop = loop + self.length = None + if response_class is None: + real_response_class = ClientResponse + else: + real_response_class = response_class + self.response_class: Type[ClientResponse] = real_response_class + self._timer = timer if timer is not None else TimerNoop() + self._ssl = ssl if ssl is not None else True + self.server_hostname = server_hostname + + if loop.get_debug(): + self._source_traceback = traceback.extract_stack(sys._getframe(1)) + + self.update_version(version) + self.update_host(url) + self.update_headers(headers) + self.update_auto_headers(skip_auto_headers) + self.update_cookies(cookies) + self.update_content_encoding(data) + self.update_auth(auth, trust_env) + self.update_proxy(proxy, proxy_auth, proxy_headers) + + self.update_body_from_data(data) + if data is not None or self.method not in self.GET_METHODS: + self.update_transfer_encoding() + self.update_expect_continue(expect100) + self._traces = [] if traces is None else traces + + def __reset_writer(self, _: object = None) -> None: + self.__writer = None + + def _get_content_length(self) -> Optional[int]: + """Extract and validate Content-Length header value. + + Returns parsed Content-Length value or None if not set. + Raises ValueError if header exists but cannot be parsed as an integer. + """ + if hdrs.CONTENT_LENGTH not in self.headers: + return None + + content_length_hdr = self.headers[hdrs.CONTENT_LENGTH] + try: + return int(content_length_hdr) + except ValueError: + raise ValueError( + f"Invalid Content-Length header: {content_length_hdr}" + ) from None + + @property + def skip_auto_headers(self) -> CIMultiDict[None]: + return self._skip_auto_headers or CIMultiDict() + + @property + def _writer(self) -> Optional["asyncio.Task[None]"]: + return self.__writer + + @_writer.setter + def _writer(self, writer: "asyncio.Task[None]") -> None: + if self.__writer is not None: + self.__writer.remove_done_callback(self.__reset_writer) + self.__writer = writer + writer.add_done_callback(self.__reset_writer) + + def is_ssl(self) -> bool: + return self.url.scheme in _SSL_SCHEMES + + @property + def ssl(self) -> Union["SSLContext", bool, Fingerprint]: + return self._ssl + + @property + def connection_key(self) -> ConnectionKey: + if proxy_headers := self.proxy_headers: + h: Optional[int] = hash(tuple(proxy_headers.items())) + else: + h = None + url = self.url + return tuple.__new__( + ConnectionKey, + ( + url.raw_host or "", + url.port, + url.scheme in _SSL_SCHEMES, + self._ssl, + self.proxy, + self.proxy_auth, + h, + ), + ) + + @property + def host(self) -> str: + ret = self.url.raw_host + assert ret is not None + return ret + + @property + def port(self) -> Optional[int]: + return self.url.port + + @property + def body(self) -> Union[payload.Payload, Literal[b""]]: + """Request body.""" + # empty body is represented as bytes for backwards compatibility + return self._body or b"" + + @body.setter + def body(self, value: Any) -> None: + """Set request body with warning for non-autoclose payloads. + + WARNING: This setter must be called from within an event loop and is not + thread-safe. Setting body outside of an event loop may raise RuntimeError + when closing file-based payloads. + + DEPRECATED: Direct assignment to body is deprecated and will be removed + in a future version. Use await update_body() instead for proper resource + management. + """ + # Close existing payload if present + if self._body is not None: + # Warn if the payload needs manual closing + # stacklevel=3: user code -> body setter -> _warn_if_unclosed_payload + _warn_if_unclosed_payload(self._body, stacklevel=3) + # NOTE: In the future, when we remove sync close support, + # this setter will need to be removed and only the async + # update_body() method will be available. For now, we call + # _close() for backwards compatibility. + self._body._close() + self._update_body(value) + + @property + def request_info(self) -> RequestInfo: + headers: CIMultiDictProxy[str] = CIMultiDictProxy(self.headers) + # These are created on every request, so we use a NamedTuple + # for performance reasons. We don't use the RequestInfo.__new__ + # method because it has a different signature which is provided + # for backwards compatibility only. + return tuple.__new__( + RequestInfo, (self.url, self.method, headers, self.original_url) + ) + + @property + def session(self) -> "ClientSession": + """Return the ClientSession instance. + + This property provides access to the ClientSession that initiated + this request, allowing middleware to make additional requests + using the same session. + """ + return self._session + + def update_host(self, url: URL) -> None: + """Update destination host, port and connection type (ssl).""" + # get host/port + if not url.raw_host: + raise InvalidURL(url) + + # basic auth info + if url.raw_user or url.raw_password: + self.auth = helpers.BasicAuth(url.user or "", url.password or "") + + def update_version(self, version: Union[http.HttpVersion, str]) -> None: + """Convert request version to two elements tuple. + + parser HTTP version '1.1' => (1, 1) + """ + if isinstance(version, str): + v = [part.strip() for part in version.split(".", 1)] + try: + version = http.HttpVersion(int(v[0]), int(v[1])) + except ValueError: + raise ValueError( + f"Can not parse http version number: {version}" + ) from None + self.version = version + + def update_headers(self, headers: Optional[LooseHeaders]) -> None: + """Update request headers.""" + self.headers: CIMultiDict[str] = CIMultiDict() + + # Build the host header + host = self.url.host_port_subcomponent + + # host_port_subcomponent is None when the URL is a relative URL. + # but we know we do not have a relative URL here. + assert host is not None + self.headers[hdrs.HOST] = host + + if not headers: + return + + if isinstance(headers, (dict, MultiDictProxy, MultiDict)): + headers = headers.items() + + for key, value in headers: # type: ignore[misc] + # A special case for Host header + if key in hdrs.HOST_ALL: + self.headers[key] = value + else: + self.headers.add(key, value) + + def update_auto_headers(self, skip_auto_headers: Optional[Iterable[str]]) -> None: + if skip_auto_headers is not None: + self._skip_auto_headers = CIMultiDict( + (hdr, None) for hdr in sorted(skip_auto_headers) + ) + used_headers = self.headers.copy() + used_headers.extend(self._skip_auto_headers) # type: ignore[arg-type] + else: + # Fast path when there are no headers to skip + # which is the most common case. + used_headers = self.headers + + for hdr, val in self.DEFAULT_HEADERS.items(): + if hdr not in used_headers: + self.headers[hdr] = val + + if hdrs.USER_AGENT not in used_headers: + self.headers[hdrs.USER_AGENT] = SERVER_SOFTWARE + + def update_cookies(self, cookies: Optional[LooseCookies]) -> None: + """Update request cookies header.""" + if not cookies: + return + + c = SimpleCookie() + if hdrs.COOKIE in self.headers: + # parse_cookie_header for RFC 6265 compliant Cookie header parsing + c.update(parse_cookie_header(self.headers.get(hdrs.COOKIE, ""))) + del self.headers[hdrs.COOKIE] + + if isinstance(cookies, Mapping): + iter_cookies = cookies.items() + else: + iter_cookies = cookies # type: ignore[assignment] + for name, value in iter_cookies: + if isinstance(value, Morsel): + # Use helper to preserve coded_value exactly as sent by server + c[name] = preserve_morsel_with_coded_value(value) + else: + c[name] = value # type: ignore[assignment] + + self.headers[hdrs.COOKIE] = c.output(header="", sep=";").strip() + + def update_content_encoding(self, data: Any) -> None: + """Set request content encoding.""" + if not data: + # Don't compress an empty body. + self.compress = None + return + + if self.headers.get(hdrs.CONTENT_ENCODING): + if self.compress: + raise ValueError( + "compress can not be set if Content-Encoding header is set" + ) + elif self.compress: + if not isinstance(self.compress, str): + self.compress = "deflate" + self.headers[hdrs.CONTENT_ENCODING] = self.compress + self.chunked = True # enable chunked, no need to deal with length + + def update_transfer_encoding(self) -> None: + """Analyze transfer-encoding header.""" + te = self.headers.get(hdrs.TRANSFER_ENCODING, "").lower() + + if "chunked" in te: + if self.chunked: + raise ValueError( + "chunked can not be set " + 'if "Transfer-Encoding: chunked" header is set' + ) + + elif self.chunked: + if hdrs.CONTENT_LENGTH in self.headers: + raise ValueError( + "chunked can not be set if Content-Length header is set" + ) + + self.headers[hdrs.TRANSFER_ENCODING] = "chunked" + + def update_auth(self, auth: Optional[BasicAuth], trust_env: bool = False) -> None: + """Set basic auth.""" + if auth is None: + auth = self.auth + if auth is None: + return + + if not isinstance(auth, helpers.BasicAuth): + raise TypeError("BasicAuth() tuple is required instead") + + self.headers[hdrs.AUTHORIZATION] = auth.encode() + + def update_body_from_data(self, body: Any, _stacklevel: int = 3) -> None: + """Update request body from data.""" + if self._body is not None: + _warn_if_unclosed_payload(self._body, stacklevel=_stacklevel) + + if body is None: + self._body = None + # Set Content-Length to 0 when body is None for methods that expect a body + if ( + self.method not in self.GET_METHODS + and not self.chunked + and hdrs.CONTENT_LENGTH not in self.headers + ): + self.headers[hdrs.CONTENT_LENGTH] = "0" + return + + # FormData + maybe_payload = body() if isinstance(body, FormData) else body + + try: + body_payload = payload.PAYLOAD_REGISTRY.get(maybe_payload, disposition=None) + except payload.LookupError: + body_payload = FormData(maybe_payload)() # type: ignore[arg-type] + + self._body = body_payload + # enable chunked encoding if needed + if not self.chunked and hdrs.CONTENT_LENGTH not in self.headers: + if (size := body_payload.size) is not None: + self.headers[hdrs.CONTENT_LENGTH] = str(size) + else: + self.chunked = True + + # copy payload headers + assert body_payload.headers + headers = self.headers + skip_headers = self._skip_auto_headers + for key, value in body_payload.headers.items(): + if key in headers or (skip_headers is not None and key in skip_headers): + continue + headers[key] = value + + def _update_body(self, body: Any) -> None: + """Update request body after its already been set.""" + # Remove existing Content-Length header since body is changing + if hdrs.CONTENT_LENGTH in self.headers: + del self.headers[hdrs.CONTENT_LENGTH] + + # Remove existing Transfer-Encoding header to avoid conflicts + if self.chunked and hdrs.TRANSFER_ENCODING in self.headers: + del self.headers[hdrs.TRANSFER_ENCODING] + + # Now update the body using the existing method + # Called from _update_body, add 1 to stacklevel from caller + self.update_body_from_data(body, _stacklevel=4) + + # Update transfer encoding headers if needed (same logic as __init__) + if body is not None or self.method not in self.GET_METHODS: + self.update_transfer_encoding() + + async def update_body(self, body: Any) -> None: + """ + Update request body and close previous payload if needed. + + This method safely updates the request body by first closing any existing + payload to prevent resource leaks, then setting the new body. + + IMPORTANT: Always use this method instead of setting request.body directly. + Direct assignment to request.body will leak resources if the previous body + contains file handles, streams, or other resources that need cleanup. + + Args: + body: The new body content. Can be: + - bytes/bytearray: Raw binary data + - str: Text data (will be encoded using charset from Content-Type) + - FormData: Form data that will be encoded as multipart/form-data + - Payload: A pre-configured payload object + - AsyncIterable: An async iterable of bytes chunks + - File-like object: Will be read and sent as binary data + - None: Clears the body + + Usage: + # CORRECT: Use update_body + await request.update_body(b"new request data") + + # WRONG: Don't set body directly + # request.body = b"new request data" # This will leak resources! + + # Update with form data + form_data = FormData() + form_data.add_field('field', 'value') + await request.update_body(form_data) + + # Clear body + await request.update_body(None) + + Note: + This method is async because it may need to close file handles or + other resources associated with the previous payload. Always await + this method to ensure proper cleanup. + + Warning: + Setting request.body directly is highly discouraged and can lead to: + - Resource leaks (unclosed file handles, streams) + - Memory leaks (unreleased buffers) + - Unexpected behavior with streaming payloads + + It is not recommended to change the payload type in middleware. If the + body was already set (e.g., as bytes), it's best to keep the same type + rather than converting it (e.g., to str) as this may result in unexpected + behavior. + + See Also: + - update_body_from_data: Synchronous body update without cleanup + - body property: Direct body access (STRONGLY DISCOURAGED) + + """ + # Close existing payload if it exists and needs closing + if self._body is not None: + await self._body.close() + self._update_body(body) + + def update_expect_continue(self, expect: bool = False) -> None: + if expect: + self.headers[hdrs.EXPECT] = "100-continue" + elif ( + hdrs.EXPECT in self.headers + and self.headers[hdrs.EXPECT].lower() == "100-continue" + ): + expect = True + + if expect: + self._continue = self.loop.create_future() + + def update_proxy( + self, + proxy: Optional[URL], + proxy_auth: Optional[BasicAuth], + proxy_headers: Optional[LooseHeaders], + ) -> None: + self.proxy = proxy + if proxy is None: + self.proxy_auth = None + self.proxy_headers = None + return + + if proxy_auth and not isinstance(proxy_auth, helpers.BasicAuth): + raise ValueError("proxy_auth must be None or BasicAuth() tuple") + self.proxy_auth = proxy_auth + + if proxy_headers is not None and not isinstance( + proxy_headers, (MultiDict, MultiDictProxy) + ): + proxy_headers = CIMultiDict(proxy_headers) + self.proxy_headers = proxy_headers + + async def write_bytes( + self, + writer: AbstractStreamWriter, + conn: "Connection", + content_length: Optional[int] = None, + ) -> None: + """ + Write the request body to the connection stream. + + This method handles writing different types of request bodies: + 1. Payload objects (using their specialized write_with_length method) + 2. Bytes/bytearray objects + 3. Iterable body content + + Args: + writer: The stream writer to write the body to + conn: The connection being used for this request + content_length: Optional maximum number of bytes to write from the body + (None means write the entire body) + + The method properly handles: + - Waiting for 100-Continue responses if required + - Content length constraints for chunked encoding + - Error handling for network issues, cancellation, and other exceptions + - Signaling EOF and timeout management + + Raises: + ClientOSError: When there's an OS-level error writing the body + ClientConnectionError: When there's a general connection error + asyncio.CancelledError: When the operation is cancelled + + """ + # 100 response + if self._continue is not None: + # Force headers to be sent before waiting for 100-continue + writer.send_headers() + await writer.drain() + await self._continue + + protocol = conn.protocol + assert protocol is not None + try: + # This should be a rare case but the + # self._body can be set to None while + # the task is being started or we wait above + # for the 100-continue response. + # The more likely case is we have an empty + # payload, but 100-continue is still expected. + if self._body is not None: + await self._body.write_with_length(writer, content_length) + except OSError as underlying_exc: + reraised_exc = underlying_exc + + # Distinguish between timeout and other OS errors for better error reporting + exc_is_not_timeout = underlying_exc.errno is not None or not isinstance( + underlying_exc, asyncio.TimeoutError + ) + if exc_is_not_timeout: + reraised_exc = ClientOSError( + underlying_exc.errno, + f"Can not write request body for {self.url !s}", + ) + + set_exception(protocol, reraised_exc, underlying_exc) + except asyncio.CancelledError: + # Body hasn't been fully sent, so connection can't be reused + conn.close() + raise + except Exception as underlying_exc: + set_exception( + protocol, + ClientConnectionError( + "Failed to send bytes into the underlying connection " + f"{conn !s}: {underlying_exc!r}", + ), + underlying_exc, + ) + else: + # Successfully wrote the body, signal EOF and start response timeout + await writer.write_eof() + protocol.start_timeout() + + async def send(self, conn: "Connection") -> "ClientResponse": + # Specify request target: + # - CONNECT request must send authority form URI + # - not CONNECT proxy must send absolute form URI + # - most common is origin form URI + if self.method == hdrs.METH_CONNECT: + connect_host = self.url.host_subcomponent + assert connect_host is not None + path = f"{connect_host}:{self.url.port}" + elif self.proxy and not self.is_ssl(): + path = str(self.url) + else: + path = self.url.raw_path_qs + + protocol = conn.protocol + assert protocol is not None + writer = StreamWriter( + protocol, + self.loop, + on_chunk_sent=( + functools.partial(self._on_chunk_request_sent, self.method, self.url) + if self._traces + else None + ), + on_headers_sent=( + functools.partial(self._on_headers_request_sent, self.method, self.url) + if self._traces + else None + ), + ) + + if self.compress: + writer.enable_compression(self.compress) # type: ignore[arg-type] + + if self.chunked is not None: + writer.enable_chunking() + + # set default content-type + if ( + self.method in self.POST_METHODS + and ( + self._skip_auto_headers is None + or hdrs.CONTENT_TYPE not in self._skip_auto_headers + ) + and hdrs.CONTENT_TYPE not in self.headers + ): + self.headers[hdrs.CONTENT_TYPE] = "application/octet-stream" + + v = self.version + if hdrs.CONNECTION not in self.headers: + if conn._connector.force_close: + if v == HttpVersion11: + self.headers[hdrs.CONNECTION] = "close" + elif v == HttpVersion10: + self.headers[hdrs.CONNECTION] = "keep-alive" + + # status + headers + status_line = f"{self.method} {path} HTTP/{v.major}.{v.minor}" + + # Buffer headers for potential coalescing with body + await writer.write_headers(status_line, self.headers) + + task: Optional["asyncio.Task[None]"] + if self._body or self._continue is not None or protocol.writing_paused: + coro = self.write_bytes(writer, conn, self._get_content_length()) + if sys.version_info >= (3, 12): + # Optimization for Python 3.12, try to write + # bytes immediately to avoid having to schedule + # the task on the event loop. + task = asyncio.Task(coro, loop=self.loop, eager_start=True) + else: + task = self.loop.create_task(coro) + if task.done(): + task = None + else: + self._writer = task + else: + # We have nothing to write because + # - there is no body + # - the protocol does not have writing paused + # - we are not waiting for a 100-continue response + protocol.start_timeout() + writer.set_eof() + task = None + response_class = self.response_class + assert response_class is not None + self.response = response_class( + self.method, + self.original_url, + writer=task, + continue100=self._continue, + timer=self._timer, + request_info=self.request_info, + traces=self._traces, + loop=self.loop, + session=self._session, + ) + return self.response + + async def close(self) -> None: + if self.__writer is not None: + try: + await self.__writer + except asyncio.CancelledError: + if ( + sys.version_info >= (3, 11) + and (task := asyncio.current_task()) + and task.cancelling() + ): + raise + + def terminate(self) -> None: + if self.__writer is not None: + if not self.loop.is_closed(): + self.__writer.cancel() + self.__writer.remove_done_callback(self.__reset_writer) + self.__writer = None + + async def _on_chunk_request_sent(self, method: str, url: URL, chunk: bytes) -> None: + for trace in self._traces: + await trace.send_request_chunk_sent(method, url, chunk) + + async def _on_headers_request_sent( + self, method: str, url: URL, headers: "CIMultiDict[str]" + ) -> None: + for trace in self._traces: + await trace.send_request_headers(method, url, headers) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/client_ws.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/client_ws.py new file mode 100644 index 0000000000000000000000000000000000000000..daa57d1930b1b0cfc03594aaae62bd93da5da165 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/client_ws.py @@ -0,0 +1,428 @@ +"""WebSocket client for asyncio.""" + +import asyncio +import sys +from types import TracebackType +from typing import Any, Optional, Type, cast + +import attr + +from ._websocket.reader import WebSocketDataQueue +from .client_exceptions import ClientError, ServerTimeoutError, WSMessageTypeError +from .client_reqrep import ClientResponse +from .helpers import calculate_timeout_when, set_result +from .http import ( + WS_CLOSED_MESSAGE, + WS_CLOSING_MESSAGE, + WebSocketError, + WSCloseCode, + WSMessage, + WSMsgType, +) +from .http_websocket import _INTERNAL_RECEIVE_TYPES, WebSocketWriter +from .streams import EofStream +from .typedefs import ( + DEFAULT_JSON_DECODER, + DEFAULT_JSON_ENCODER, + JSONDecoder, + JSONEncoder, +) + +if sys.version_info >= (3, 11): + import asyncio as async_timeout +else: + import async_timeout + + +@attr.s(frozen=True, slots=True) +class ClientWSTimeout: + ws_receive = attr.ib(type=Optional[float], default=None) + ws_close = attr.ib(type=Optional[float], default=None) + + +DEFAULT_WS_CLIENT_TIMEOUT = ClientWSTimeout(ws_receive=None, ws_close=10.0) + + +class ClientWebSocketResponse: + def __init__( + self, + reader: WebSocketDataQueue, + writer: WebSocketWriter, + protocol: Optional[str], + response: ClientResponse, + timeout: ClientWSTimeout, + autoclose: bool, + autoping: bool, + loop: asyncio.AbstractEventLoop, + *, + heartbeat: Optional[float] = None, + compress: int = 0, + client_notakeover: bool = False, + ) -> None: + self._response = response + self._conn = response.connection + + self._writer = writer + self._reader = reader + self._protocol = protocol + self._closed = False + self._closing = False + self._close_code: Optional[int] = None + self._timeout = timeout + self._autoclose = autoclose + self._autoping = autoping + self._heartbeat = heartbeat + self._heartbeat_cb: Optional[asyncio.TimerHandle] = None + self._heartbeat_when: float = 0.0 + if heartbeat is not None: + self._pong_heartbeat = heartbeat / 2.0 + self._pong_response_cb: Optional[asyncio.TimerHandle] = None + self._loop = loop + self._waiting: bool = False + self._close_wait: Optional[asyncio.Future[None]] = None + self._exception: Optional[BaseException] = None + self._compress = compress + self._client_notakeover = client_notakeover + self._ping_task: Optional[asyncio.Task[None]] = None + + self._reset_heartbeat() + + def _cancel_heartbeat(self) -> None: + self._cancel_pong_response_cb() + if self._heartbeat_cb is not None: + self._heartbeat_cb.cancel() + self._heartbeat_cb = None + if self._ping_task is not None: + self._ping_task.cancel() + self._ping_task = None + + def _cancel_pong_response_cb(self) -> None: + if self._pong_response_cb is not None: + self._pong_response_cb.cancel() + self._pong_response_cb = None + + def _reset_heartbeat(self) -> None: + if self._heartbeat is None: + return + self._cancel_pong_response_cb() + loop = self._loop + assert loop is not None + conn = self._conn + timeout_ceil_threshold = ( + conn._connector._timeout_ceil_threshold if conn is not None else 5 + ) + now = loop.time() + when = calculate_timeout_when(now, self._heartbeat, timeout_ceil_threshold) + self._heartbeat_when = when + if self._heartbeat_cb is None: + # We do not cancel the previous heartbeat_cb here because + # it generates a significant amount of TimerHandle churn + # which causes asyncio to rebuild the heap frequently. + # Instead _send_heartbeat() will reschedule the next + # heartbeat if it fires too early. + self._heartbeat_cb = loop.call_at(when, self._send_heartbeat) + + def _send_heartbeat(self) -> None: + self._heartbeat_cb = None + loop = self._loop + now = loop.time() + if now < self._heartbeat_when: + # Heartbeat fired too early, reschedule + self._heartbeat_cb = loop.call_at( + self._heartbeat_when, self._send_heartbeat + ) + return + + conn = self._conn + timeout_ceil_threshold = ( + conn._connector._timeout_ceil_threshold if conn is not None else 5 + ) + when = calculate_timeout_when(now, self._pong_heartbeat, timeout_ceil_threshold) + self._cancel_pong_response_cb() + self._pong_response_cb = loop.call_at(when, self._pong_not_received) + + coro = self._writer.send_frame(b"", WSMsgType.PING) + if sys.version_info >= (3, 12): + # Optimization for Python 3.12, try to send the ping + # immediately to avoid having to schedule + # the task on the event loop. + ping_task = asyncio.Task(coro, loop=loop, eager_start=True) + else: + ping_task = loop.create_task(coro) + + if not ping_task.done(): + self._ping_task = ping_task + ping_task.add_done_callback(self._ping_task_done) + else: + self._ping_task_done(ping_task) + + def _ping_task_done(self, task: "asyncio.Task[None]") -> None: + """Callback for when the ping task completes.""" + if not task.cancelled() and (exc := task.exception()): + self._handle_ping_pong_exception(exc) + self._ping_task = None + + def _pong_not_received(self) -> None: + self._handle_ping_pong_exception( + ServerTimeoutError(f"No PONG received after {self._pong_heartbeat} seconds") + ) + + def _handle_ping_pong_exception(self, exc: BaseException) -> None: + """Handle exceptions raised during ping/pong processing.""" + if self._closed: + return + self._set_closed() + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + self._exception = exc + self._response.close() + if self._waiting and not self._closing: + self._reader.feed_data(WSMessage(WSMsgType.ERROR, exc, None), 0) + + def _set_closed(self) -> None: + """Set the connection to closed. + + Cancel any heartbeat timers and set the closed flag. + """ + self._closed = True + self._cancel_heartbeat() + + def _set_closing(self) -> None: + """Set the connection to closing. + + Cancel any heartbeat timers and set the closing flag. + """ + self._closing = True + self._cancel_heartbeat() + + @property + def closed(self) -> bool: + return self._closed + + @property + def close_code(self) -> Optional[int]: + return self._close_code + + @property + def protocol(self) -> Optional[str]: + return self._protocol + + @property + def compress(self) -> int: + return self._compress + + @property + def client_notakeover(self) -> bool: + return self._client_notakeover + + def get_extra_info(self, name: str, default: Any = None) -> Any: + """extra info from connection transport""" + conn = self._response.connection + if conn is None: + return default + transport = conn.transport + if transport is None: + return default + return transport.get_extra_info(name, default) + + def exception(self) -> Optional[BaseException]: + return self._exception + + async def ping(self, message: bytes = b"") -> None: + await self._writer.send_frame(message, WSMsgType.PING) + + async def pong(self, message: bytes = b"") -> None: + await self._writer.send_frame(message, WSMsgType.PONG) + + async def send_frame( + self, message: bytes, opcode: WSMsgType, compress: Optional[int] = None + ) -> None: + """Send a frame over the websocket.""" + await self._writer.send_frame(message, opcode, compress) + + async def send_str(self, data: str, compress: Optional[int] = None) -> None: + if not isinstance(data, str): + raise TypeError("data argument must be str (%r)" % type(data)) + await self._writer.send_frame( + data.encode("utf-8"), WSMsgType.TEXT, compress=compress + ) + + async def send_bytes(self, data: bytes, compress: Optional[int] = None) -> None: + if not isinstance(data, (bytes, bytearray, memoryview)): + raise TypeError("data argument must be byte-ish (%r)" % type(data)) + await self._writer.send_frame(data, WSMsgType.BINARY, compress=compress) + + async def send_json( + self, + data: Any, + compress: Optional[int] = None, + *, + dumps: JSONEncoder = DEFAULT_JSON_ENCODER, + ) -> None: + await self.send_str(dumps(data), compress=compress) + + async def close(self, *, code: int = WSCloseCode.OK, message: bytes = b"") -> bool: + # we need to break `receive()` cycle first, + # `close()` may be called from different task + if self._waiting and not self._closing: + assert self._loop is not None + self._close_wait = self._loop.create_future() + self._set_closing() + self._reader.feed_data(WS_CLOSING_MESSAGE, 0) + await self._close_wait + + if self._closed: + return False + + self._set_closed() + try: + await self._writer.close(code, message) + except asyncio.CancelledError: + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + self._response.close() + raise + except Exception as exc: + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + self._exception = exc + self._response.close() + return True + + if self._close_code: + self._response.close() + return True + + while True: + try: + async with async_timeout.timeout(self._timeout.ws_close): + msg = await self._reader.read() + except asyncio.CancelledError: + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + self._response.close() + raise + except Exception as exc: + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + self._exception = exc + self._response.close() + return True + + if msg.type is WSMsgType.CLOSE: + self._close_code = msg.data + self._response.close() + return True + + async def receive(self, timeout: Optional[float] = None) -> WSMessage: + receive_timeout = timeout or self._timeout.ws_receive + + while True: + if self._waiting: + raise RuntimeError("Concurrent call to receive() is not allowed") + + if self._closed: + return WS_CLOSED_MESSAGE + elif self._closing: + await self.close() + return WS_CLOSED_MESSAGE + + try: + self._waiting = True + try: + if receive_timeout: + # Entering the context manager and creating + # Timeout() object can take almost 50% of the + # run time in this loop so we avoid it if + # there is no read timeout. + async with async_timeout.timeout(receive_timeout): + msg = await self._reader.read() + else: + msg = await self._reader.read() + self._reset_heartbeat() + finally: + self._waiting = False + if self._close_wait: + set_result(self._close_wait, None) + except (asyncio.CancelledError, asyncio.TimeoutError): + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + raise + except EofStream: + self._close_code = WSCloseCode.OK + await self.close() + return WSMessage(WSMsgType.CLOSED, None, None) + except ClientError: + # Likely ServerDisconnectedError when connection is lost + self._set_closed() + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + return WS_CLOSED_MESSAGE + except WebSocketError as exc: + self._close_code = exc.code + await self.close(code=exc.code) + return WSMessage(WSMsgType.ERROR, exc, None) + except Exception as exc: + self._exception = exc + self._set_closing() + self._close_code = WSCloseCode.ABNORMAL_CLOSURE + await self.close() + return WSMessage(WSMsgType.ERROR, exc, None) + + if msg.type not in _INTERNAL_RECEIVE_TYPES: + # If its not a close/closing/ping/pong message + # we can return it immediately + return msg + + if msg.type is WSMsgType.CLOSE: + self._set_closing() + self._close_code = msg.data + if not self._closed and self._autoclose: + await self.close() + elif msg.type is WSMsgType.CLOSING: + self._set_closing() + elif msg.type is WSMsgType.PING and self._autoping: + await self.pong(msg.data) + continue + elif msg.type is WSMsgType.PONG and self._autoping: + continue + + return msg + + async def receive_str(self, *, timeout: Optional[float] = None) -> str: + msg = await self.receive(timeout) + if msg.type is not WSMsgType.TEXT: + raise WSMessageTypeError( + f"Received message {msg.type}:{msg.data!r} is not WSMsgType.TEXT" + ) + return cast(str, msg.data) + + async def receive_bytes(self, *, timeout: Optional[float] = None) -> bytes: + msg = await self.receive(timeout) + if msg.type is not WSMsgType.BINARY: + raise WSMessageTypeError( + f"Received message {msg.type}:{msg.data!r} is not WSMsgType.BINARY" + ) + return cast(bytes, msg.data) + + async def receive_json( + self, + *, + loads: JSONDecoder = DEFAULT_JSON_DECODER, + timeout: Optional[float] = None, + ) -> Any: + data = await self.receive_str(timeout=timeout) + return loads(data) + + def __aiter__(self) -> "ClientWebSocketResponse": + return self + + async def __anext__(self) -> WSMessage: + msg = await self.receive() + if msg.type in (WSMsgType.CLOSE, WSMsgType.CLOSING, WSMsgType.CLOSED): + raise StopAsyncIteration + return msg + + async def __aenter__(self) -> "ClientWebSocketResponse": + return self + + async def __aexit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + await self.close() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/compression_utils.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/compression_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..e478d24c3d70105e6796007808462c4159f8c833 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/compression_utils.py @@ -0,0 +1,348 @@ +import asyncio +import sys +import zlib +from abc import ABC, abstractmethod +from concurrent.futures import Executor +from typing import Any, Final, Optional, Protocol, TypedDict, cast + +if sys.version_info >= (3, 12): + from collections.abc import Buffer +else: + from typing import Union + + Buffer = Union[bytes, bytearray, "memoryview[int]", "memoryview[bytes]"] + +try: + try: + import brotlicffi as brotli + except ImportError: + import brotli + + HAS_BROTLI = True +except ImportError: # pragma: no cover + HAS_BROTLI = False + +try: + if sys.version_info >= (3, 14): + from compression.zstd import ZstdDecompressor # noqa: I900 + else: # TODO(PY314): Remove mentions of backports.zstd across codebase + from backports.zstd import ZstdDecompressor + + HAS_ZSTD = True +except ImportError: + HAS_ZSTD = False + + +MAX_SYNC_CHUNK_SIZE = 4096 +DEFAULT_MAX_DECOMPRESS_SIZE = 2**25 # 32MiB + +# Unlimited decompression constants - different libraries use different conventions +ZLIB_MAX_LENGTH_UNLIMITED = 0 # zlib uses 0 to mean unlimited +ZSTD_MAX_LENGTH_UNLIMITED = -1 # zstd uses -1 to mean unlimited + + +class ZLibCompressObjProtocol(Protocol): + def compress(self, data: Buffer) -> bytes: ... + def flush(self, mode: int = ..., /) -> bytes: ... + + +class ZLibDecompressObjProtocol(Protocol): + def decompress(self, data: Buffer, max_length: int = ...) -> bytes: ... + def flush(self, length: int = ..., /) -> bytes: ... + + @property + def eof(self) -> bool: ... + + +class ZLibBackendProtocol(Protocol): + MAX_WBITS: int + Z_FULL_FLUSH: int + Z_SYNC_FLUSH: int + Z_BEST_SPEED: int + Z_FINISH: int + + def compressobj( + self, + level: int = ..., + method: int = ..., + wbits: int = ..., + memLevel: int = ..., + strategy: int = ..., + zdict: Optional[Buffer] = ..., + ) -> ZLibCompressObjProtocol: ... + def decompressobj( + self, wbits: int = ..., zdict: Buffer = ... + ) -> ZLibDecompressObjProtocol: ... + + def compress( + self, data: Buffer, /, level: int = ..., wbits: int = ... + ) -> bytes: ... + def decompress( + self, data: Buffer, /, wbits: int = ..., bufsize: int = ... + ) -> bytes: ... + + +class CompressObjArgs(TypedDict, total=False): + wbits: int + strategy: int + level: int + + +class ZLibBackendWrapper: + def __init__(self, _zlib_backend: ZLibBackendProtocol): + self._zlib_backend: ZLibBackendProtocol = _zlib_backend + + @property + def name(self) -> str: + return getattr(self._zlib_backend, "__name__", "undefined") + + @property + def MAX_WBITS(self) -> int: + return self._zlib_backend.MAX_WBITS + + @property + def Z_FULL_FLUSH(self) -> int: + return self._zlib_backend.Z_FULL_FLUSH + + @property + def Z_SYNC_FLUSH(self) -> int: + return self._zlib_backend.Z_SYNC_FLUSH + + @property + def Z_BEST_SPEED(self) -> int: + return self._zlib_backend.Z_BEST_SPEED + + @property + def Z_FINISH(self) -> int: + return self._zlib_backend.Z_FINISH + + def compressobj(self, *args: Any, **kwargs: Any) -> ZLibCompressObjProtocol: + return self._zlib_backend.compressobj(*args, **kwargs) + + def decompressobj(self, *args: Any, **kwargs: Any) -> ZLibDecompressObjProtocol: + return self._zlib_backend.decompressobj(*args, **kwargs) + + def compress(self, data: Buffer, *args: Any, **kwargs: Any) -> bytes: + return self._zlib_backend.compress(data, *args, **kwargs) + + def decompress(self, data: Buffer, *args: Any, **kwargs: Any) -> bytes: + return self._zlib_backend.decompress(data, *args, **kwargs) + + # Everything not explicitly listed in the Protocol we just pass through + def __getattr__(self, attrname: str) -> Any: + return getattr(self._zlib_backend, attrname) + + +ZLibBackend: ZLibBackendWrapper = ZLibBackendWrapper(zlib) + + +def set_zlib_backend(new_zlib_backend: ZLibBackendProtocol) -> None: + ZLibBackend._zlib_backend = new_zlib_backend + + +def encoding_to_mode( + encoding: Optional[str] = None, + suppress_deflate_header: bool = False, +) -> int: + if encoding == "gzip": + return 16 + ZLibBackend.MAX_WBITS + + return -ZLibBackend.MAX_WBITS if suppress_deflate_header else ZLibBackend.MAX_WBITS + + +class DecompressionBaseHandler(ABC): + def __init__( + self, + executor: Optional[Executor] = None, + max_sync_chunk_size: Optional[int] = MAX_SYNC_CHUNK_SIZE, + ): + """Base class for decompression handlers.""" + self._executor = executor + self._max_sync_chunk_size = max_sync_chunk_size + + @abstractmethod + def decompress_sync( + self, data: bytes, max_length: int = ZLIB_MAX_LENGTH_UNLIMITED + ) -> bytes: + """Decompress the given data.""" + + async def decompress( + self, data: bytes, max_length: int = ZLIB_MAX_LENGTH_UNLIMITED + ) -> bytes: + """Decompress the given data.""" + if ( + self._max_sync_chunk_size is not None + and len(data) > self._max_sync_chunk_size + ): + return await asyncio.get_event_loop().run_in_executor( + self._executor, self.decompress_sync, data, max_length + ) + return self.decompress_sync(data, max_length) + + +class ZLibCompressor: + def __init__( + self, + encoding: Optional[str] = None, + suppress_deflate_header: bool = False, + level: Optional[int] = None, + wbits: Optional[int] = None, + strategy: Optional[int] = None, + executor: Optional[Executor] = None, + max_sync_chunk_size: Optional[int] = MAX_SYNC_CHUNK_SIZE, + ): + self._executor = executor + self._max_sync_chunk_size = max_sync_chunk_size + self._mode = ( + encoding_to_mode(encoding, suppress_deflate_header) + if wbits is None + else wbits + ) + self._zlib_backend: Final = ZLibBackendWrapper(ZLibBackend._zlib_backend) + + kwargs: CompressObjArgs = {} + kwargs["wbits"] = self._mode + if strategy is not None: + kwargs["strategy"] = strategy + if level is not None: + kwargs["level"] = level + self._compressor = self._zlib_backend.compressobj(**kwargs) + + def compress_sync(self, data: bytes) -> bytes: + return self._compressor.compress(data) + + async def compress(self, data: bytes) -> bytes: + """Compress the data and returned the compressed bytes. + + Note that flush() must be called after the last call to compress() + + If the data size is large than the max_sync_chunk_size, the compression + will be done in the executor. Otherwise, the compression will be done + in the event loop. + + **WARNING: This method is NOT cancellation-safe when used with flush().** + If this operation is cancelled, the compressor state may be corrupted. + The connection MUST be closed after cancellation to avoid data corruption + in subsequent compress operations. + + For cancellation-safe compression (e.g., WebSocket), the caller MUST wrap + compress() + flush() + send operations in a shield and lock to ensure atomicity. + """ + # For large payloads, offload compression to executor to avoid blocking event loop + should_use_executor = ( + self._max_sync_chunk_size is not None + and len(data) > self._max_sync_chunk_size + ) + if should_use_executor: + return await asyncio.get_running_loop().run_in_executor( + self._executor, self._compressor.compress, data + ) + return self.compress_sync(data) + + def flush(self, mode: Optional[int] = None) -> bytes: + """Flush the compressor synchronously. + + **WARNING: This method is NOT cancellation-safe when called after compress().** + The flush() operation accesses shared compressor state. If compress() was + cancelled, calling flush() may result in corrupted data. The connection MUST + be closed after compress() cancellation. + + For cancellation-safe compression (e.g., WebSocket), the caller MUST wrap + compress() + flush() + send operations in a shield and lock to ensure atomicity. + """ + return self._compressor.flush( + mode if mode is not None else self._zlib_backend.Z_FINISH + ) + + +class ZLibDecompressor(DecompressionBaseHandler): + def __init__( + self, + encoding: Optional[str] = None, + suppress_deflate_header: bool = False, + executor: Optional[Executor] = None, + max_sync_chunk_size: Optional[int] = MAX_SYNC_CHUNK_SIZE, + ): + super().__init__(executor=executor, max_sync_chunk_size=max_sync_chunk_size) + self._mode = encoding_to_mode(encoding, suppress_deflate_header) + self._zlib_backend: Final = ZLibBackendWrapper(ZLibBackend._zlib_backend) + self._decompressor = self._zlib_backend.decompressobj(wbits=self._mode) + + def decompress_sync( + self, data: Buffer, max_length: int = ZLIB_MAX_LENGTH_UNLIMITED + ) -> bytes: + return self._decompressor.decompress(data, max_length) + + def flush(self, length: int = 0) -> bytes: + return ( + self._decompressor.flush(length) + if length > 0 + else self._decompressor.flush() + ) + + @property + def eof(self) -> bool: + return self._decompressor.eof + + +class BrotliDecompressor(DecompressionBaseHandler): + # Supports both 'brotlipy' and 'Brotli' packages + # since they share an import name. The top branches + # are for 'brotlipy' and bottom branches for 'Brotli' + def __init__( + self, + executor: Optional[Executor] = None, + max_sync_chunk_size: Optional[int] = MAX_SYNC_CHUNK_SIZE, + ) -> None: + """Decompress data using the Brotli library.""" + if not HAS_BROTLI: + raise RuntimeError( + "The brotli decompression is not available. " + "Please install `Brotli` module" + ) + self._obj = brotli.Decompressor() + super().__init__(executor=executor, max_sync_chunk_size=max_sync_chunk_size) + + def decompress_sync( + self, data: Buffer, max_length: int = ZLIB_MAX_LENGTH_UNLIMITED + ) -> bytes: + """Decompress the given data.""" + if hasattr(self._obj, "decompress"): + return cast(bytes, self._obj.decompress(data, max_length)) + return cast(bytes, self._obj.process(data, max_length)) + + def flush(self) -> bytes: + """Flush the decompressor.""" + if hasattr(self._obj, "flush"): + return cast(bytes, self._obj.flush()) + return b"" + + +class ZSTDDecompressor(DecompressionBaseHandler): + def __init__( + self, + executor: Optional[Executor] = None, + max_sync_chunk_size: Optional[int] = MAX_SYNC_CHUNK_SIZE, + ) -> None: + if not HAS_ZSTD: + raise RuntimeError( + "The zstd decompression is not available. " + "Please install `backports.zstd` module" + ) + self._obj = ZstdDecompressor() + super().__init__(executor=executor, max_sync_chunk_size=max_sync_chunk_size) + + def decompress_sync( + self, data: bytes, max_length: int = ZLIB_MAX_LENGTH_UNLIMITED + ) -> bytes: + # zstd uses -1 for unlimited, while zlib uses 0 for unlimited + # Convert the zlib convention (0=unlimited) to zstd convention (-1=unlimited) + zstd_max_length = ( + ZSTD_MAX_LENGTH_UNLIMITED + if max_length == ZLIB_MAX_LENGTH_UNLIMITED + else max_length + ) + return self._obj.decompress(data, zstd_max_length) + + def flush(self) -> bytes: + return b"" diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/connector.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/connector.py new file mode 100644 index 0000000000000000000000000000000000000000..ce28ce032c5f27b6a3e1b69d119dc9330ccac5f4 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/connector.py @@ -0,0 +1,1854 @@ +import asyncio +import functools +import random +import socket +import sys +import traceback +import warnings +from collections import OrderedDict, defaultdict, deque +from contextlib import suppress +from http import HTTPStatus +from itertools import chain, cycle, islice +from time import monotonic +from types import TracebackType +from typing import ( + TYPE_CHECKING, + Any, + Awaitable, + Callable, + DefaultDict, + Deque, + Dict, + Iterator, + List, + Literal, + Optional, + Sequence, + Set, + Tuple, + Type, + Union, + cast, +) + +import aiohappyeyeballs +from aiohappyeyeballs import AddrInfoType, SocketFactoryType + +from . import hdrs, helpers +from .abc import AbstractResolver, ResolveResult +from .client_exceptions import ( + ClientConnectionError, + ClientConnectorCertificateError, + ClientConnectorDNSError, + ClientConnectorError, + ClientConnectorSSLError, + ClientHttpProxyError, + ClientProxyConnectionError, + ServerFingerprintMismatch, + UnixClientConnectorError, + cert_errors, + ssl_errors, +) +from .client_proto import ResponseHandler +from .client_reqrep import ClientRequest, Fingerprint, _merge_ssl_params +from .helpers import ( + _SENTINEL, + ceil_timeout, + is_ip_address, + noop, + sentinel, + set_exception, + set_result, +) +from .log import client_logger +from .resolver import DefaultResolver + +if sys.version_info >= (3, 12): + from collections.abc import Buffer +else: + Buffer = Union[bytes, bytearray, "memoryview[int]", "memoryview[bytes]"] + +if TYPE_CHECKING: + import ssl + + SSLContext = ssl.SSLContext +else: + try: + import ssl + + SSLContext = ssl.SSLContext + except ImportError: # pragma: no cover + ssl = None # type: ignore[assignment] + SSLContext = object # type: ignore[misc,assignment] + +EMPTY_SCHEMA_SET = frozenset({""}) +HTTP_SCHEMA_SET = frozenset({"http", "https"}) +WS_SCHEMA_SET = frozenset({"ws", "wss"}) + +HTTP_AND_EMPTY_SCHEMA_SET = HTTP_SCHEMA_SET | EMPTY_SCHEMA_SET +HIGH_LEVEL_SCHEMA_SET = HTTP_AND_EMPTY_SCHEMA_SET | WS_SCHEMA_SET + +NEEDS_CLEANUP_CLOSED = (3, 13, 0) <= sys.version_info < ( + 3, + 13, + 1, +) or sys.version_info < (3, 12, 8) +# Cleanup closed is no longer needed after https://github.com/python/cpython/pull/118960 +# which first appeared in Python 3.12.8 and 3.13.1 + + +__all__ = ( + "BaseConnector", + "TCPConnector", + "UnixConnector", + "NamedPipeConnector", + "AddrInfoType", + "SocketFactoryType", +) + + +if TYPE_CHECKING: + from .client import ClientTimeout + from .client_reqrep import ConnectionKey + from .tracing import Trace + + +class _DeprecationWaiter: + __slots__ = ("_awaitable", "_awaited") + + def __init__(self, awaitable: Awaitable[Any]) -> None: + self._awaitable = awaitable + self._awaited = False + + def __await__(self) -> Any: + self._awaited = True + return self._awaitable.__await__() + + def __del__(self) -> None: + if not self._awaited: + warnings.warn( + "Connector.close() is a coroutine, " + "please use await connector.close()", + DeprecationWarning, + ) + + +async def _wait_for_close(waiters: List[Awaitable[object]]) -> None: + """Wait for all waiters to finish closing.""" + results = await asyncio.gather(*waiters, return_exceptions=True) + for res in results: + if isinstance(res, Exception): + client_logger.debug("Error while closing connector: %r", res) + + +class Connection: + + _source_traceback = None + + def __init__( + self, + connector: "BaseConnector", + key: "ConnectionKey", + protocol: ResponseHandler, + loop: asyncio.AbstractEventLoop, + ) -> None: + self._key = key + self._connector = connector + self._loop = loop + self._protocol: Optional[ResponseHandler] = protocol + self._callbacks: List[Callable[[], None]] = [] + + if loop.get_debug(): + self._source_traceback = traceback.extract_stack(sys._getframe(1)) + + def __repr__(self) -> str: + return f"Connection<{self._key}>" + + def __del__(self, _warnings: Any = warnings) -> None: + if self._protocol is not None: + kwargs = {"source": self} + _warnings.warn(f"Unclosed connection {self!r}", ResourceWarning, **kwargs) + if self._loop.is_closed(): + return + + self._connector._release(self._key, self._protocol, should_close=True) + + context = {"client_connection": self, "message": "Unclosed connection"} + if self._source_traceback is not None: + context["source_traceback"] = self._source_traceback + self._loop.call_exception_handler(context) + + def __bool__(self) -> Literal[True]: + """Force subclasses to not be falsy, to make checks simpler.""" + return True + + @property + def loop(self) -> asyncio.AbstractEventLoop: + warnings.warn( + "connector.loop property is deprecated", DeprecationWarning, stacklevel=2 + ) + return self._loop + + @property + def transport(self) -> Optional[asyncio.Transport]: + if self._protocol is None: + return None + return self._protocol.transport + + @property + def protocol(self) -> Optional[ResponseHandler]: + return self._protocol + + def add_callback(self, callback: Callable[[], None]) -> None: + if callback is not None: + self._callbacks.append(callback) + + def _notify_release(self) -> None: + callbacks, self._callbacks = self._callbacks[:], [] + + for cb in callbacks: + with suppress(Exception): + cb() + + def close(self) -> None: + self._notify_release() + + if self._protocol is not None: + self._connector._release(self._key, self._protocol, should_close=True) + self._protocol = None + + def release(self) -> None: + self._notify_release() + + if self._protocol is not None: + self._connector._release(self._key, self._protocol) + self._protocol = None + + @property + def closed(self) -> bool: + return self._protocol is None or not self._protocol.is_connected() + + +class _ConnectTunnelConnection(Connection): + """Special connection wrapper for CONNECT tunnels that must never be pooled. + + This connection wraps the proxy connection that will be upgraded with TLS. + It must never be released to the pool because: + 1. Its 'closed' future will never complete, causing session.close() to hang + 2. It represents an intermediate state, not a reusable connection + 3. The real connection (with TLS) will be created separately + """ + + def release(self) -> None: + """Do nothing - don't pool or close the connection. + + These connections are an intermediate state during the CONNECT tunnel + setup and will be cleaned up naturally after the TLS upgrade. If they + were to be pooled, they would never be properly closed, causing + session.close() to wait forever for their 'closed' future. + """ + + +class _TransportPlaceholder: + """placeholder for BaseConnector.connect function""" + + __slots__ = ("closed", "transport") + + def __init__(self, closed_future: asyncio.Future[Optional[Exception]]) -> None: + """Initialize a placeholder for a transport.""" + self.closed = closed_future + self.transport = None + + def close(self) -> None: + """Close the placeholder.""" + + def abort(self) -> None: + """Abort the placeholder (does nothing).""" + + +class BaseConnector: + """Base connector class. + + keepalive_timeout - (optional) Keep-alive timeout. + force_close - Set to True to force close and do reconnect + after each request (and between redirects). + limit - The total number of simultaneous connections. + limit_per_host - Number of simultaneous connections to one host. + enable_cleanup_closed - Enables clean-up closed ssl transports. + Disabled by default. + timeout_ceil_threshold - Trigger ceiling of timeout values when + it's above timeout_ceil_threshold. + loop - Optional event loop. + """ + + _closed = True # prevent AttributeError in __del__ if ctor was failed + _source_traceback = None + + # abort transport after 2 seconds (cleanup broken connections) + _cleanup_closed_period = 2.0 + + allowed_protocol_schema_set = HIGH_LEVEL_SCHEMA_SET + + def __init__( + self, + *, + keepalive_timeout: Union[object, None, float] = sentinel, + force_close: bool = False, + limit: int = 100, + limit_per_host: int = 0, + enable_cleanup_closed: bool = False, + loop: Optional[asyncio.AbstractEventLoop] = None, + timeout_ceil_threshold: float = 5, + ) -> None: + + if force_close: + if keepalive_timeout is not None and keepalive_timeout is not sentinel: + raise ValueError( + "keepalive_timeout cannot be set if force_close is True" + ) + else: + if keepalive_timeout is sentinel: + keepalive_timeout = 15.0 + + loop = loop or asyncio.get_running_loop() + self._timeout_ceil_threshold = timeout_ceil_threshold + + self._closed = False + if loop.get_debug(): + self._source_traceback = traceback.extract_stack(sys._getframe(1)) + + # Connection pool of reusable connections. + # We use a deque to store connections because it has O(1) popleft() + # and O(1) append() operations to implement a FIFO queue. + self._conns: DefaultDict[ + ConnectionKey, Deque[Tuple[ResponseHandler, float]] + ] = defaultdict(deque) + self._limit = limit + self._limit_per_host = limit_per_host + self._acquired: Set[ResponseHandler] = set() + self._acquired_per_host: DefaultDict[ConnectionKey, Set[ResponseHandler]] = ( + defaultdict(set) + ) + self._keepalive_timeout = cast(float, keepalive_timeout) + self._force_close = force_close + + # {host_key: FIFO list of waiters} + # The FIFO is implemented with an OrderedDict with None keys because + # python does not have an ordered set. + self._waiters: DefaultDict[ + ConnectionKey, OrderedDict[asyncio.Future[None], None] + ] = defaultdict(OrderedDict) + + self._loop = loop + self._factory = functools.partial(ResponseHandler, loop=loop) + + # start keep-alive connection cleanup task + self._cleanup_handle: Optional[asyncio.TimerHandle] = None + + # start cleanup closed transports task + self._cleanup_closed_handle: Optional[asyncio.TimerHandle] = None + + if enable_cleanup_closed and not NEEDS_CLEANUP_CLOSED: + warnings.warn( + "enable_cleanup_closed ignored because " + "https://github.com/python/cpython/pull/118960 is fixed " + f"in Python version {sys.version_info}", + DeprecationWarning, + stacklevel=2, + ) + enable_cleanup_closed = False + + self._cleanup_closed_disabled = not enable_cleanup_closed + self._cleanup_closed_transports: List[Optional[asyncio.Transport]] = [] + self._placeholder_future: asyncio.Future[Optional[Exception]] = ( + loop.create_future() + ) + self._placeholder_future.set_result(None) + self._cleanup_closed() + + def __del__(self, _warnings: Any = warnings) -> None: + if self._closed: + return + if not self._conns: + return + + conns = [repr(c) for c in self._conns.values()] + + self._close() + + kwargs = {"source": self} + _warnings.warn(f"Unclosed connector {self!r}", ResourceWarning, **kwargs) + context = { + "connector": self, + "connections": conns, + "message": "Unclosed connector", + } + if self._source_traceback is not None: + context["source_traceback"] = self._source_traceback + self._loop.call_exception_handler(context) + + def __enter__(self) -> "BaseConnector": + warnings.warn( + '"with Connector():" is deprecated, ' + 'use "async with Connector():" instead', + DeprecationWarning, + ) + return self + + def __exit__(self, *exc: Any) -> None: + self._close() + + async def __aenter__(self) -> "BaseConnector": + return self + + async def __aexit__( + self, + exc_type: Optional[Type[BaseException]] = None, + exc_value: Optional[BaseException] = None, + exc_traceback: Optional[TracebackType] = None, + ) -> None: + await self.close() + + @property + def force_close(self) -> bool: + """Ultimately close connection on releasing if True.""" + return self._force_close + + @property + def limit(self) -> int: + """The total number for simultaneous connections. + + If limit is 0 the connector has no limit. + The default limit size is 100. + """ + return self._limit + + @property + def limit_per_host(self) -> int: + """The limit for simultaneous connections to the same endpoint. + + Endpoints are the same if they are have equal + (host, port, is_ssl) triple. + """ + return self._limit_per_host + + def _cleanup(self) -> None: + """Cleanup unused transports.""" + if self._cleanup_handle: + self._cleanup_handle.cancel() + # _cleanup_handle should be unset, otherwise _release() will not + # recreate it ever! + self._cleanup_handle = None + + now = monotonic() + timeout = self._keepalive_timeout + + if self._conns: + connections = defaultdict(deque) + deadline = now - timeout + for key, conns in self._conns.items(): + alive: Deque[Tuple[ResponseHandler, float]] = deque() + for proto, use_time in conns: + if proto.is_connected() and use_time - deadline >= 0: + alive.append((proto, use_time)) + continue + transport = proto.transport + proto.close() + if not self._cleanup_closed_disabled and key.is_ssl: + self._cleanup_closed_transports.append(transport) + + if alive: + connections[key] = alive + + self._conns = connections + + if self._conns: + self._cleanup_handle = helpers.weakref_handle( + self, + "_cleanup", + timeout, + self._loop, + timeout_ceil_threshold=self._timeout_ceil_threshold, + ) + + def _cleanup_closed(self) -> None: + """Double confirmation for transport close. + + Some broken ssl servers may leave socket open without proper close. + """ + if self._cleanup_closed_handle: + self._cleanup_closed_handle.cancel() + + for transport in self._cleanup_closed_transports: + if transport is not None: + transport.abort() + + self._cleanup_closed_transports = [] + + if not self._cleanup_closed_disabled: + self._cleanup_closed_handle = helpers.weakref_handle( + self, + "_cleanup_closed", + self._cleanup_closed_period, + self._loop, + timeout_ceil_threshold=self._timeout_ceil_threshold, + ) + + def close(self, *, abort_ssl: bool = False) -> Awaitable[None]: + """Close all opened transports. + + :param abort_ssl: If True, SSL connections will be aborted immediately + without performing the shutdown handshake. This provides + faster cleanup at the cost of less graceful disconnection. + """ + if not (waiters := self._close(abort_ssl=abort_ssl)): + # If there are no connections to close, we can return a noop + # awaitable to avoid scheduling a task on the event loop. + return _DeprecationWaiter(noop()) + coro = _wait_for_close(waiters) + if sys.version_info >= (3, 12): + # Optimization for Python 3.12, try to close connections + # immediately to avoid having to schedule the task on the event loop. + task = asyncio.Task(coro, loop=self._loop, eager_start=True) + else: + task = self._loop.create_task(coro) + return _DeprecationWaiter(task) + + def _close(self, *, abort_ssl: bool = False) -> List[Awaitable[object]]: + waiters: List[Awaitable[object]] = [] + + if self._closed: + return waiters + + self._closed = True + + try: + if self._loop.is_closed(): + return waiters + + # cancel cleanup task + if self._cleanup_handle: + self._cleanup_handle.cancel() + + # cancel cleanup close task + if self._cleanup_closed_handle: + self._cleanup_closed_handle.cancel() + + for data in self._conns.values(): + for proto, _ in data: + if ( + abort_ssl + and proto.transport + and proto.transport.get_extra_info("sslcontext") is not None + ): + proto.abort() + else: + proto.close() + if closed := proto.closed: + waiters.append(closed) + + for proto in self._acquired: + if ( + abort_ssl + and proto.transport + and proto.transport.get_extra_info("sslcontext") is not None + ): + proto.abort() + else: + proto.close() + if closed := proto.closed: + waiters.append(closed) + + for transport in self._cleanup_closed_transports: + if transport is not None: + transport.abort() + + return waiters + + finally: + self._conns.clear() + self._acquired.clear() + for keyed_waiters in self._waiters.values(): + for keyed_waiter in keyed_waiters: + keyed_waiter.cancel() + self._waiters.clear() + self._cleanup_handle = None + self._cleanup_closed_transports.clear() + self._cleanup_closed_handle = None + + @property + def closed(self) -> bool: + """Is connector closed. + + A readonly property. + """ + return self._closed + + def _available_connections(self, key: "ConnectionKey") -> int: + """ + Return number of available connections. + + The limit, limit_per_host and the connection key are taken into account. + + If it returns less than 1 means that there are no connections + available. + """ + # check total available connections + # If there are no limits, this will always return 1 + total_remain = 1 + + if self._limit and (total_remain := self._limit - len(self._acquired)) <= 0: + return total_remain + + # check limit per host + if host_remain := self._limit_per_host: + if acquired := self._acquired_per_host.get(key): + host_remain -= len(acquired) + if total_remain > host_remain: + return host_remain + + return total_remain + + def _update_proxy_auth_header_and_build_proxy_req( + self, req: ClientRequest + ) -> ClientRequest: + """Set Proxy-Authorization header for non-SSL proxy requests and builds the proxy request for SSL proxy requests.""" + url = req.proxy + assert url is not None + headers: Dict[str, str] = {} + if req.proxy_headers is not None: + headers = req.proxy_headers # type: ignore[assignment] + headers[hdrs.HOST] = req.headers[hdrs.HOST] + proxy_req = ClientRequest( + hdrs.METH_GET, + url, + headers=headers, + auth=req.proxy_auth, + loop=self._loop, + ssl=req.ssl, + ) + auth = proxy_req.headers.pop(hdrs.AUTHORIZATION, None) + if auth is not None: + if not req.is_ssl(): + req.headers[hdrs.PROXY_AUTHORIZATION] = auth + else: + proxy_req.headers[hdrs.PROXY_AUTHORIZATION] = auth + return proxy_req + + async def connect( + self, req: ClientRequest, traces: List["Trace"], timeout: "ClientTimeout" + ) -> Connection: + """Get from pool or create new connection.""" + key = req.connection_key + if (conn := await self._get(key, traces)) is not None: + # If we do not have to wait and we can get a connection from the pool + # we can avoid the timeout ceil logic and directly return the connection + if req.proxy: + self._update_proxy_auth_header_and_build_proxy_req(req) + return conn + + async with ceil_timeout(timeout.connect, timeout.ceil_threshold): + if self._available_connections(key) <= 0: + await self._wait_for_available_connection(key, traces) + if (conn := await self._get(key, traces)) is not None: + if req.proxy: + self._update_proxy_auth_header_and_build_proxy_req(req) + return conn + + placeholder = cast( + ResponseHandler, _TransportPlaceholder(self._placeholder_future) + ) + self._acquired.add(placeholder) + if self._limit_per_host: + self._acquired_per_host[key].add(placeholder) + + try: + # Traces are done inside the try block to ensure that the + # that the placeholder is still cleaned up if an exception + # is raised. + if traces: + for trace in traces: + await trace.send_connection_create_start() + proto = await self._create_connection(req, traces, timeout) + if traces: + for trace in traces: + await trace.send_connection_create_end() + except BaseException: + self._release_acquired(key, placeholder) + raise + else: + if self._closed: + proto.close() + raise ClientConnectionError("Connector is closed.") + + # The connection was successfully created, drop the placeholder + # and add the real connection to the acquired set. There should + # be no awaits after the proto is added to the acquired set + # to ensure that the connection is not left in the acquired set + # on cancellation. + self._acquired.remove(placeholder) + self._acquired.add(proto) + if self._limit_per_host: + acquired_per_host = self._acquired_per_host[key] + acquired_per_host.remove(placeholder) + acquired_per_host.add(proto) + return Connection(self, key, proto, self._loop) + + async def _wait_for_available_connection( + self, key: "ConnectionKey", traces: List["Trace"] + ) -> None: + """Wait for an available connection slot.""" + # We loop here because there is a race between + # the connection limit check and the connection + # being acquired. If the connection is acquired + # between the check and the await statement, we + # need to loop again to check if the connection + # slot is still available. + attempts = 0 + while True: + fut: asyncio.Future[None] = self._loop.create_future() + keyed_waiters = self._waiters[key] + keyed_waiters[fut] = None + if attempts: + # If we have waited before, we need to move the waiter + # to the front of the queue as otherwise we might get + # starved and hit the timeout. + keyed_waiters.move_to_end(fut, last=False) + + try: + # Traces happen in the try block to ensure that the + # the waiter is still cleaned up if an exception is raised. + if traces: + for trace in traces: + await trace.send_connection_queued_start() + await fut + if traces: + for trace in traces: + await trace.send_connection_queued_end() + finally: + # pop the waiter from the queue if its still + # there and not already removed by _release_waiter + keyed_waiters.pop(fut, None) + if not self._waiters.get(key, True): + del self._waiters[key] + + if self._available_connections(key) > 0: + break + attempts += 1 + + async def _get( + self, key: "ConnectionKey", traces: List["Trace"] + ) -> Optional[Connection]: + """Get next reusable connection for the key or None. + + The connection will be marked as acquired. + """ + if (conns := self._conns.get(key)) is None: + return None + + t1 = monotonic() + while conns: + proto, t0 = conns.popleft() + # We will we reuse the connection if its connected and + # the keepalive timeout has not been exceeded + if proto.is_connected() and t1 - t0 <= self._keepalive_timeout: + if not conns: + # The very last connection was reclaimed: drop the key + del self._conns[key] + self._acquired.add(proto) + if self._limit_per_host: + self._acquired_per_host[key].add(proto) + if traces: + for trace in traces: + try: + await trace.send_connection_reuseconn() + except BaseException: + self._release_acquired(key, proto) + raise + return Connection(self, key, proto, self._loop) + + # Connection cannot be reused, close it + transport = proto.transport + proto.close() + # only for SSL transports + if not self._cleanup_closed_disabled and key.is_ssl: + self._cleanup_closed_transports.append(transport) + + # No more connections: drop the key + del self._conns[key] + return None + + def _release_waiter(self) -> None: + """ + Iterates over all waiters until one to be released is found. + + The one to be released is not finished and + belongs to a host that has available connections. + """ + if not self._waiters: + return + + # Having the dict keys ordered this avoids to iterate + # at the same order at each call. + queues = list(self._waiters) + random.shuffle(queues) + + for key in queues: + if self._available_connections(key) < 1: + continue + + waiters = self._waiters[key] + while waiters: + waiter, _ = waiters.popitem(last=False) + if not waiter.done(): + waiter.set_result(None) + return + + def _release_acquired(self, key: "ConnectionKey", proto: ResponseHandler) -> None: + """Release acquired connection.""" + if self._closed: + # acquired connection is already released on connector closing + return + + self._acquired.discard(proto) + if self._limit_per_host and (conns := self._acquired_per_host.get(key)): + conns.discard(proto) + if not conns: + del self._acquired_per_host[key] + self._release_waiter() + + def _release( + self, + key: "ConnectionKey", + protocol: ResponseHandler, + *, + should_close: bool = False, + ) -> None: + if self._closed: + # acquired connection is already released on connector closing + return + + self._release_acquired(key, protocol) + + if self._force_close or should_close or protocol.should_close: + transport = protocol.transport + protocol.close() + + if key.is_ssl and not self._cleanup_closed_disabled: + self._cleanup_closed_transports.append(transport) + return + + self._conns[key].append((protocol, monotonic())) + + if self._cleanup_handle is None: + self._cleanup_handle = helpers.weakref_handle( + self, + "_cleanup", + self._keepalive_timeout, + self._loop, + timeout_ceil_threshold=self._timeout_ceil_threshold, + ) + + async def _create_connection( + self, req: ClientRequest, traces: List["Trace"], timeout: "ClientTimeout" + ) -> ResponseHandler: + raise NotImplementedError() + + +class _DNSCacheTable: + def __init__(self, ttl: Optional[float] = None, max_size: int = 1000) -> None: + self._addrs_rr: OrderedDict[ + Tuple[str, int], Tuple[Iterator[ResolveResult], int] + ] = OrderedDict() + self._timestamps: Dict[Tuple[str, int], float] = {} + self._ttl = ttl + self._max_size = max_size + + def __contains__(self, host: object) -> bool: + return host in self._addrs_rr + + def add(self, key: Tuple[str, int], addrs: List[ResolveResult]) -> None: + if key in self._addrs_rr: + self._addrs_rr.move_to_end(key) + + self._addrs_rr[key] = (cycle(addrs), len(addrs)) + + if self._ttl is not None: + self._timestamps[key] = monotonic() + + if len(self._addrs_rr) > self._max_size: + oldest_key, _ = self._addrs_rr.popitem(last=False) + self._timestamps.pop(oldest_key, None) + + def remove(self, key: Tuple[str, int]) -> None: + self._addrs_rr.pop(key, None) + self._timestamps.pop(key, None) + + def clear(self) -> None: + self._addrs_rr.clear() + self._timestamps.clear() + + def next_addrs(self, key: Tuple[str, int]) -> List[ResolveResult]: + loop, length = self._addrs_rr[key] + addrs = list(islice(loop, length)) + # Consume one more element to shift internal state of `cycle` + next(loop) + self._addrs_rr.move_to_end(key) + return addrs + + def expired(self, key: Tuple[str, int]) -> bool: + if self._ttl is None: + return False + + return self._timestamps[key] + self._ttl < monotonic() + + +def _make_ssl_context(verified: bool) -> SSLContext: + """Create SSL context. + + This method is not async-friendly and should be called from a thread + because it will load certificates from disk and do other blocking I/O. + """ + if ssl is None: + # No ssl support + return None + if verified: + sslcontext = ssl.create_default_context() + else: + sslcontext = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) + sslcontext.options |= ssl.OP_NO_SSLv2 + sslcontext.options |= ssl.OP_NO_SSLv3 + sslcontext.check_hostname = False + sslcontext.verify_mode = ssl.CERT_NONE + sslcontext.options |= ssl.OP_NO_COMPRESSION + sslcontext.set_default_verify_paths() + sslcontext.set_alpn_protocols(("http/1.1",)) + return sslcontext + + +# The default SSLContext objects are created at import time +# since they do blocking I/O to load certificates from disk, +# and imports should always be done before the event loop starts +# or in a thread. +_SSL_CONTEXT_VERIFIED = _make_ssl_context(True) +_SSL_CONTEXT_UNVERIFIED = _make_ssl_context(False) + + +class TCPConnector(BaseConnector): + """TCP connector. + + verify_ssl - Set to True to check ssl certifications. + fingerprint - Pass the binary sha256 + digest of the expected certificate in DER format to verify + that the certificate the server presents matches. See also + https://en.wikipedia.org/wiki/HTTP_Public_Key_Pinning + resolver - Enable DNS lookups and use this + resolver + use_dns_cache - Use memory cache for DNS lookups. + ttl_dns_cache - Max seconds having cached a DNS entry, None forever. + family - socket address family + local_addr - local tuple of (host, port) to bind socket to + + keepalive_timeout - (optional) Keep-alive timeout. + force_close - Set to True to force close and do reconnect + after each request (and between redirects). + limit - The total number of simultaneous connections. + limit_per_host - Number of simultaneous connections to one host. + enable_cleanup_closed - Enables clean-up closed ssl transports. + Disabled by default. + happy_eyeballs_delay - This is the “Connection Attempt Delay” + as defined in RFC 8305. To disable + the happy eyeballs algorithm, set to None. + interleave - “First Address Family Count” as defined in RFC 8305 + loop - Optional event loop. + socket_factory - A SocketFactoryType function that, if supplied, + will be used to create sockets given an + AddrInfoType. + ssl_shutdown_timeout - DEPRECATED. Will be removed in aiohttp 4.0. + Grace period for SSL shutdown handshake on TLS + connections. Default is 0 seconds (immediate abort). + This parameter allowed for a clean SSL shutdown by + notifying the remote peer of connection closure, + while avoiding excessive delays during connector cleanup. + Note: Only takes effect on Python 3.11+. + """ + + allowed_protocol_schema_set = HIGH_LEVEL_SCHEMA_SET | frozenset({"tcp"}) + + def __init__( + self, + *, + verify_ssl: bool = True, + fingerprint: Optional[bytes] = None, + use_dns_cache: bool = True, + ttl_dns_cache: Optional[int] = 10, + dns_cache_max_size: int = 1000, + family: socket.AddressFamily = socket.AddressFamily.AF_UNSPEC, + ssl_context: Optional[SSLContext] = None, + ssl: Union[bool, Fingerprint, SSLContext] = True, + local_addr: Optional[Tuple[str, int]] = None, + resolver: Optional[AbstractResolver] = None, + keepalive_timeout: Union[None, float, object] = sentinel, + force_close: bool = False, + limit: int = 100, + limit_per_host: int = 0, + enable_cleanup_closed: bool = False, + loop: Optional[asyncio.AbstractEventLoop] = None, + timeout_ceil_threshold: float = 5, + happy_eyeballs_delay: Optional[float] = 0.25, + interleave: Optional[int] = None, + socket_factory: Optional[SocketFactoryType] = None, + ssl_shutdown_timeout: Union[_SENTINEL, None, float] = sentinel, + ): + super().__init__( + keepalive_timeout=keepalive_timeout, + force_close=force_close, + limit=limit, + limit_per_host=limit_per_host, + enable_cleanup_closed=enable_cleanup_closed, + loop=loop, + timeout_ceil_threshold=timeout_ceil_threshold, + ) + + self._ssl = _merge_ssl_params(ssl, verify_ssl, ssl_context, fingerprint) + + self._resolver: AbstractResolver + if resolver is None: + self._resolver = DefaultResolver(loop=self._loop) + self._resolver_owner = True + else: + self._resolver = resolver + self._resolver_owner = False + + self._use_dns_cache = use_dns_cache + self._cached_hosts = _DNSCacheTable( + ttl=ttl_dns_cache, max_size=dns_cache_max_size + ) + self._throttle_dns_futures: Dict[ + Tuple[str, int], Set["asyncio.Future[None]"] + ] = {} + self._family = family + self._local_addr_infos = aiohappyeyeballs.addr_to_addr_infos(local_addr) + self._happy_eyeballs_delay = happy_eyeballs_delay + self._interleave = interleave + self._resolve_host_tasks: Set["asyncio.Task[List[ResolveResult]]"] = set() + self._socket_factory = socket_factory + self._ssl_shutdown_timeout: Optional[float] + # Handle ssl_shutdown_timeout with warning for Python < 3.11 + if ssl_shutdown_timeout is sentinel: + self._ssl_shutdown_timeout = 0 + else: + # Deprecation warning for ssl_shutdown_timeout parameter + warnings.warn( + "The ssl_shutdown_timeout parameter is deprecated and will be removed in aiohttp 4.0", + DeprecationWarning, + stacklevel=2, + ) + if ( + sys.version_info < (3, 11) + and ssl_shutdown_timeout is not None + and ssl_shutdown_timeout != 0 + ): + warnings.warn( + f"ssl_shutdown_timeout={ssl_shutdown_timeout} is ignored on Python < 3.11; " + "only ssl_shutdown_timeout=0 is supported. The timeout will be ignored.", + RuntimeWarning, + stacklevel=2, + ) + self._ssl_shutdown_timeout = ssl_shutdown_timeout + + def _close(self, *, abort_ssl: bool = False) -> List[Awaitable[object]]: + """Close all ongoing DNS calls.""" + for fut in chain.from_iterable(self._throttle_dns_futures.values()): + fut.cancel() + + waiters = super()._close(abort_ssl=abort_ssl) + + for t in self._resolve_host_tasks: + t.cancel() + waiters.append(t) + + return waiters + + async def close(self, *, abort_ssl: bool = False) -> None: + """ + Close all opened transports. + + :param abort_ssl: If True, SSL connections will be aborted immediately + without performing the shutdown handshake. If False (default), + the behavior is determined by ssl_shutdown_timeout: + - If ssl_shutdown_timeout=0: connections are aborted + - If ssl_shutdown_timeout>0: graceful shutdown is performed + """ + if self._resolver_owner: + await self._resolver.close() + # Use abort_ssl param if explicitly set, otherwise use ssl_shutdown_timeout default + await super().close(abort_ssl=abort_ssl or self._ssl_shutdown_timeout == 0) + + @property + def family(self) -> int: + """Socket family like AF_INET.""" + return self._family + + @property + def use_dns_cache(self) -> bool: + """True if local DNS caching is enabled.""" + return self._use_dns_cache + + def clear_dns_cache( + self, host: Optional[str] = None, port: Optional[int] = None + ) -> None: + """Remove specified host/port or clear all dns local cache.""" + if host is not None and port is not None: + self._cached_hosts.remove((host, port)) + elif host is not None or port is not None: + raise ValueError("either both host and port or none of them are allowed") + else: + self._cached_hosts.clear() + + async def _resolve_host( + self, host: str, port: int, traces: Optional[Sequence["Trace"]] = None + ) -> List[ResolveResult]: + """Resolve host and return list of addresses.""" + if is_ip_address(host): + return [ + { + "hostname": host, + "host": host, + "port": port, + "family": self._family, + "proto": 0, + "flags": 0, + } + ] + + if not self._use_dns_cache: + + if traces: + for trace in traces: + await trace.send_dns_resolvehost_start(host) + + res = await self._resolver.resolve(host, port, family=self._family) + + if traces: + for trace in traces: + await trace.send_dns_resolvehost_end(host) + + return res + + key = (host, port) + if key in self._cached_hosts and not self._cached_hosts.expired(key): + # get result early, before any await (#4014) + result = self._cached_hosts.next_addrs(key) + + if traces: + for trace in traces: + await trace.send_dns_cache_hit(host) + return result + + futures: Set["asyncio.Future[None]"] + # + # If multiple connectors are resolving the same host, we wait + # for the first one to resolve and then use the result for all of them. + # We use a throttle to ensure that we only resolve the host once + # and then use the result for all the waiters. + # + if key in self._throttle_dns_futures: + # get futures early, before any await (#4014) + futures = self._throttle_dns_futures[key] + future: asyncio.Future[None] = self._loop.create_future() + futures.add(future) + if traces: + for trace in traces: + await trace.send_dns_cache_hit(host) + try: + await future + finally: + futures.discard(future) + return self._cached_hosts.next_addrs(key) + + # update dict early, before any await (#4014) + self._throttle_dns_futures[key] = futures = set() + # In this case we need to create a task to ensure that we can shield + # the task from cancellation as cancelling this lookup should not cancel + # the underlying lookup or else the cancel event will get broadcast to + # all the waiters across all connections. + # + coro = self._resolve_host_with_throttle(key, host, port, futures, traces) + loop = asyncio.get_running_loop() + if sys.version_info >= (3, 12): + # Optimization for Python 3.12, try to send immediately + resolved_host_task = asyncio.Task(coro, loop=loop, eager_start=True) + else: + resolved_host_task = loop.create_task(coro) + + if not resolved_host_task.done(): + self._resolve_host_tasks.add(resolved_host_task) + resolved_host_task.add_done_callback(self._resolve_host_tasks.discard) + + try: + return await asyncio.shield(resolved_host_task) + except asyncio.CancelledError: + + def drop_exception(fut: "asyncio.Future[List[ResolveResult]]") -> None: + with suppress(Exception, asyncio.CancelledError): + fut.result() + + resolved_host_task.add_done_callback(drop_exception) + raise + + async def _resolve_host_with_throttle( + self, + key: Tuple[str, int], + host: str, + port: int, + futures: Set["asyncio.Future[None]"], + traces: Optional[Sequence["Trace"]], + ) -> List[ResolveResult]: + """Resolve host and set result for all waiters. + + This method must be run in a task and shielded from cancellation + to avoid cancelling the underlying lookup. + """ + try: + if traces: + for trace in traces: + await trace.send_dns_cache_miss(host) + + for trace in traces: + await trace.send_dns_resolvehost_start(host) + + addrs = await self._resolver.resolve(host, port, family=self._family) + if traces: + for trace in traces: + await trace.send_dns_resolvehost_end(host) + + self._cached_hosts.add(key, addrs) + for fut in futures: + set_result(fut, None) + except BaseException as e: + # any DNS exception is set for the waiters to raise the same exception. + # This coro is always run in task that is shielded from cancellation so + # we should never be propagating cancellation here. + for fut in futures: + set_exception(fut, e) + raise + finally: + self._throttle_dns_futures.pop(key) + + return self._cached_hosts.next_addrs(key) + + async def _create_connection( + self, req: ClientRequest, traces: List["Trace"], timeout: "ClientTimeout" + ) -> ResponseHandler: + """Create connection. + + Has same keyword arguments as BaseEventLoop.create_connection. + """ + if req.proxy: + _, proto = await self._create_proxy_connection(req, traces, timeout) + else: + _, proto = await self._create_direct_connection(req, traces, timeout) + + return proto + + def _get_ssl_context(self, req: ClientRequest) -> Optional[SSLContext]: + """Logic to get the correct SSL context + + 0. if req.ssl is false, return None + + 1. if ssl_context is specified in req, use it + 2. if _ssl_context is specified in self, use it + 3. otherwise: + 1. if verify_ssl is not specified in req, use self.ssl_context + (will generate a default context according to self.verify_ssl) + 2. if verify_ssl is True in req, generate a default SSL context + 3. if verify_ssl is False in req, generate a SSL context that + won't verify + """ + if not req.is_ssl(): + return None + + if ssl is None: # pragma: no cover + raise RuntimeError("SSL is not supported.") + sslcontext = req.ssl + if isinstance(sslcontext, ssl.SSLContext): + return sslcontext + if sslcontext is not True: + # not verified or fingerprinted + return _SSL_CONTEXT_UNVERIFIED + sslcontext = self._ssl + if isinstance(sslcontext, ssl.SSLContext): + return sslcontext + if sslcontext is not True: + # not verified or fingerprinted + return _SSL_CONTEXT_UNVERIFIED + return _SSL_CONTEXT_VERIFIED + + def _get_fingerprint(self, req: ClientRequest) -> Optional["Fingerprint"]: + ret = req.ssl + if isinstance(ret, Fingerprint): + return ret + ret = self._ssl + if isinstance(ret, Fingerprint): + return ret + return None + + async def _wrap_create_connection( + self, + *args: Any, + addr_infos: List[AddrInfoType], + req: ClientRequest, + timeout: "ClientTimeout", + client_error: Type[Exception] = ClientConnectorError, + **kwargs: Any, + ) -> Tuple[asyncio.Transport, ResponseHandler]: + try: + async with ceil_timeout( + timeout.sock_connect, ceil_threshold=timeout.ceil_threshold + ): + sock = await aiohappyeyeballs.start_connection( + addr_infos=addr_infos, + local_addr_infos=self._local_addr_infos, + happy_eyeballs_delay=self._happy_eyeballs_delay, + interleave=self._interleave, + loop=self._loop, + socket_factory=self._socket_factory, + ) + # Add ssl_shutdown_timeout for Python 3.11+ when SSL is used + if ( + kwargs.get("ssl") + and self._ssl_shutdown_timeout + and sys.version_info >= (3, 11) + ): + kwargs["ssl_shutdown_timeout"] = self._ssl_shutdown_timeout + return await self._loop.create_connection(*args, **kwargs, sock=sock) + except cert_errors as exc: + raise ClientConnectorCertificateError(req.connection_key, exc) from exc + except ssl_errors as exc: + raise ClientConnectorSSLError(req.connection_key, exc) from exc + except OSError as exc: + if exc.errno is None and isinstance(exc, asyncio.TimeoutError): + raise + raise client_error(req.connection_key, exc) from exc + + async def _wrap_existing_connection( + self, + *args: Any, + req: ClientRequest, + timeout: "ClientTimeout", + client_error: Type[Exception] = ClientConnectorError, + **kwargs: Any, + ) -> Tuple[asyncio.Transport, ResponseHandler]: + try: + async with ceil_timeout( + timeout.sock_connect, ceil_threshold=timeout.ceil_threshold + ): + return await self._loop.create_connection(*args, **kwargs) + except cert_errors as exc: + raise ClientConnectorCertificateError(req.connection_key, exc) from exc + except ssl_errors as exc: + raise ClientConnectorSSLError(req.connection_key, exc) from exc + except OSError as exc: + if exc.errno is None and isinstance(exc, asyncio.TimeoutError): + raise + raise client_error(req.connection_key, exc) from exc + + def _fail_on_no_start_tls(self, req: "ClientRequest") -> None: + """Raise a :py:exc:`RuntimeError` on missing ``start_tls()``. + + It is necessary for TLS-in-TLS so that it is possible to + send HTTPS queries through HTTPS proxies. + + This doesn't affect regular HTTP requests, though. + """ + if not req.is_ssl(): + return + + proxy_url = req.proxy + assert proxy_url is not None + if proxy_url.scheme != "https": + return + + self._check_loop_for_start_tls() + + def _check_loop_for_start_tls(self) -> None: + try: + self._loop.start_tls + except AttributeError as attr_exc: + raise RuntimeError( + "An HTTPS request is being sent through an HTTPS proxy. " + "This needs support for TLS in TLS but it is not implemented " + "in your runtime for the stdlib asyncio.\n\n" + "Please upgrade to Python 3.11 or higher. For more details, " + "please see:\n" + "* https://bugs.python.org/issue37179\n" + "* https://github.com/python/cpython/pull/28073\n" + "* https://docs.aiohttp.org/en/stable/" + "client_advanced.html#proxy-support\n" + "* https://github.com/aio-libs/aiohttp/discussions/6044\n", + ) from attr_exc + + def _loop_supports_start_tls(self) -> bool: + try: + self._check_loop_for_start_tls() + except RuntimeError: + return False + else: + return True + + def _warn_about_tls_in_tls( + self, + underlying_transport: asyncio.Transport, + req: ClientRequest, + ) -> None: + """Issue a warning if the requested URL has HTTPS scheme.""" + if req.request_info.url.scheme != "https": + return + + # Check if uvloop is being used, which supports TLS in TLS, + # otherwise assume that asyncio's native transport is being used. + if type(underlying_transport).__module__.startswith("uvloop"): + return + + # Support in asyncio was added in Python 3.11 (bpo-44011) + asyncio_supports_tls_in_tls = sys.version_info >= (3, 11) or getattr( + underlying_transport, + "_start_tls_compatible", + False, + ) + + if asyncio_supports_tls_in_tls: + return + + warnings.warn( + "An HTTPS request is being sent through an HTTPS proxy. " + "This support for TLS in TLS is known to be disabled " + "in the stdlib asyncio (Python <3.11). This is why you'll probably see " + "an error in the log below.\n\n" + "It is possible to enable it via monkeypatching. " + "For more details, see:\n" + "* https://bugs.python.org/issue37179\n" + "* https://github.com/python/cpython/pull/28073\n\n" + "You can temporarily patch this as follows:\n" + "* https://docs.aiohttp.org/en/stable/client_advanced.html#proxy-support\n" + "* https://github.com/aio-libs/aiohttp/discussions/6044\n", + RuntimeWarning, + source=self, + # Why `4`? At least 3 of the calls in the stack originate + # from the methods in this class. + stacklevel=3, + ) + + async def _start_tls_connection( + self, + underlying_transport: asyncio.Transport, + req: ClientRequest, + timeout: "ClientTimeout", + client_error: Type[Exception] = ClientConnectorError, + ) -> Tuple[asyncio.BaseTransport, ResponseHandler]: + """Wrap the raw TCP transport with TLS.""" + tls_proto = self._factory() # Create a brand new proto for TLS + sslcontext = self._get_ssl_context(req) + if TYPE_CHECKING: + # _start_tls_connection is unreachable in the current code path + # if sslcontext is None. + assert sslcontext is not None + + try: + async with ceil_timeout( + timeout.sock_connect, ceil_threshold=timeout.ceil_threshold + ): + try: + # ssl_shutdown_timeout is only available in Python 3.11+ + if sys.version_info >= (3, 11) and self._ssl_shutdown_timeout: + tls_transport = await self._loop.start_tls( + underlying_transport, + tls_proto, + sslcontext, + server_hostname=req.server_hostname or req.host, + ssl_handshake_timeout=timeout.total or None, + ssl_shutdown_timeout=self._ssl_shutdown_timeout, + ) + else: + tls_transport = await self._loop.start_tls( + underlying_transport, + tls_proto, + sslcontext, + server_hostname=req.server_hostname or req.host, + ssl_handshake_timeout=timeout.total or None, + ) + except BaseException: + # We need to close the underlying transport since + # `start_tls()` probably failed before it had a + # chance to do this: + if self._ssl_shutdown_timeout == 0: + underlying_transport.abort() + else: + underlying_transport.close() + raise + if isinstance(tls_transport, asyncio.Transport): + fingerprint = self._get_fingerprint(req) + if fingerprint: + try: + fingerprint.check(tls_transport) + except ServerFingerprintMismatch: + tls_transport.close() + if not self._cleanup_closed_disabled: + self._cleanup_closed_transports.append(tls_transport) + raise + except cert_errors as exc: + raise ClientConnectorCertificateError(req.connection_key, exc) from exc + except ssl_errors as exc: + raise ClientConnectorSSLError(req.connection_key, exc) from exc + except OSError as exc: + if exc.errno is None and isinstance(exc, asyncio.TimeoutError): + raise + raise client_error(req.connection_key, exc) from exc + except TypeError as type_err: + # Example cause looks like this: + # TypeError: transport is not supported by start_tls() + + raise ClientConnectionError( + "Cannot initialize a TLS-in-TLS connection to host " + f"{req.host!s}:{req.port:d} through an underlying connection " + f"to an HTTPS proxy {req.proxy!s} ssl:{req.ssl or 'default'} " + f"[{type_err!s}]" + ) from type_err + else: + if tls_transport is None: + msg = "Failed to start TLS (possibly caused by closing transport)" + raise client_error(req.connection_key, OSError(msg)) + tls_proto.connection_made( + tls_transport + ) # Kick the state machine of the new TLS protocol + + return tls_transport, tls_proto + + def _convert_hosts_to_addr_infos( + self, hosts: List[ResolveResult] + ) -> List[AddrInfoType]: + """Converts the list of hosts to a list of addr_infos. + + The list of hosts is the result of a DNS lookup. The list of + addr_infos is the result of a call to `socket.getaddrinfo()`. + """ + addr_infos: List[AddrInfoType] = [] + for hinfo in hosts: + host = hinfo["host"] + is_ipv6 = ":" in host + family = socket.AF_INET6 if is_ipv6 else socket.AF_INET + if self._family and self._family != family: + continue + addr = (host, hinfo["port"], 0, 0) if is_ipv6 else (host, hinfo["port"]) + addr_infos.append( + (family, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", addr) + ) + return addr_infos + + async def _create_direct_connection( + self, + req: ClientRequest, + traces: List["Trace"], + timeout: "ClientTimeout", + *, + client_error: Type[Exception] = ClientConnectorError, + ) -> Tuple[asyncio.Transport, ResponseHandler]: + sslcontext = self._get_ssl_context(req) + fingerprint = self._get_fingerprint(req) + + host = req.url.raw_host + assert host is not None + # Replace multiple trailing dots with a single one. + # A trailing dot is only present for fully-qualified domain names. + # See https://github.com/aio-libs/aiohttp/pull/7364. + if host.endswith(".."): + host = host.rstrip(".") + "." + port = req.port + assert port is not None + try: + # Cancelling this lookup should not cancel the underlying lookup + # or else the cancel event will get broadcast to all the waiters + # across all connections. + hosts = await self._resolve_host(host, port, traces=traces) + except OSError as exc: + if exc.errno is None and isinstance(exc, asyncio.TimeoutError): + raise + # in case of proxy it is not ClientProxyConnectionError + # it is problem of resolving proxy ip itself + raise ClientConnectorDNSError(req.connection_key, exc) from exc + + last_exc: Optional[Exception] = None + addr_infos = self._convert_hosts_to_addr_infos(hosts) + while addr_infos: + # Strip trailing dots, certificates contain FQDN without dots. + # See https://github.com/aio-libs/aiohttp/issues/3636 + server_hostname = ( + (req.server_hostname or host).rstrip(".") if sslcontext else None + ) + + try: + transp, proto = await self._wrap_create_connection( + self._factory, + timeout=timeout, + ssl=sslcontext, + addr_infos=addr_infos, + server_hostname=server_hostname, + req=req, + client_error=client_error, + ) + except (ClientConnectorError, asyncio.TimeoutError) as exc: + last_exc = exc + aiohappyeyeballs.pop_addr_infos_interleave(addr_infos, self._interleave) + continue + + if req.is_ssl() and fingerprint: + try: + fingerprint.check(transp) + except ServerFingerprintMismatch as exc: + transp.close() + if not self._cleanup_closed_disabled: + self._cleanup_closed_transports.append(transp) + last_exc = exc + # Remove the bad peer from the list of addr_infos + sock: socket.socket = transp.get_extra_info("socket") + bad_peer = sock.getpeername() + aiohappyeyeballs.remove_addr_infos(addr_infos, bad_peer) + continue + + return transp, proto + else: + assert last_exc is not None + raise last_exc + + async def _create_proxy_connection( + self, req: ClientRequest, traces: List["Trace"], timeout: "ClientTimeout" + ) -> Tuple[asyncio.BaseTransport, ResponseHandler]: + self._fail_on_no_start_tls(req) + runtime_has_start_tls = self._loop_supports_start_tls() + proxy_req = self._update_proxy_auth_header_and_build_proxy_req(req) + + # create connection to proxy server + transport, proto = await self._create_direct_connection( + proxy_req, [], timeout, client_error=ClientProxyConnectionError + ) + + if req.is_ssl(): + if runtime_has_start_tls: + self._warn_about_tls_in_tls(transport, req) + + # For HTTPS requests over HTTP proxy + # we must notify proxy to tunnel connection + # so we send CONNECT command: + # CONNECT www.python.org:443 HTTP/1.1 + # Host: www.python.org + # + # next we must do TLS handshake and so on + # to do this we must wrap raw socket into secure one + # asyncio handles this perfectly + proxy_req.method = hdrs.METH_CONNECT + proxy_req.url = req.url + key = req.connection_key._replace( + proxy=None, proxy_auth=None, proxy_headers_hash=None + ) + conn = _ConnectTunnelConnection(self, key, proto, self._loop) + proxy_resp = await proxy_req.send(conn) + try: + protocol = conn._protocol + assert protocol is not None + + # read_until_eof=True will ensure the connection isn't closed + # once the response is received and processed allowing + # START_TLS to work on the connection below. + protocol.set_response_params( + read_until_eof=runtime_has_start_tls, + timeout_ceil_threshold=self._timeout_ceil_threshold, + ) + resp = await proxy_resp.start(conn) + except BaseException: + proxy_resp.close() + conn.close() + raise + else: + conn._protocol = None + try: + if resp.status != 200: + message = resp.reason + if message is None: + message = HTTPStatus(resp.status).phrase + raise ClientHttpProxyError( + proxy_resp.request_info, + resp.history, + status=resp.status, + message=message, + headers=resp.headers, + ) + if not runtime_has_start_tls: + rawsock = transport.get_extra_info("socket", default=None) + if rawsock is None: + raise RuntimeError( + "Transport does not expose socket instance" + ) + # Duplicate the socket, so now we can close proxy transport + rawsock = rawsock.dup() + except BaseException: + # It shouldn't be closed in `finally` because it's fed to + # `loop.start_tls()` and the docs say not to touch it after + # passing there. + transport.close() + raise + finally: + if not runtime_has_start_tls: + transport.close() + + if not runtime_has_start_tls: + # HTTP proxy with support for upgrade to HTTPS + sslcontext = self._get_ssl_context(req) + return await self._wrap_existing_connection( + self._factory, + timeout=timeout, + ssl=sslcontext, + sock=rawsock, + server_hostname=req.host, + req=req, + ) + + return await self._start_tls_connection( + # Access the old transport for the last time before it's + # closed and forgotten forever: + transport, + req=req, + timeout=timeout, + ) + finally: + proxy_resp.close() + + return transport, proto + + +class UnixConnector(BaseConnector): + """Unix socket connector. + + path - Unix socket path. + keepalive_timeout - (optional) Keep-alive timeout. + force_close - Set to True to force close and do reconnect + after each request (and between redirects). + limit - The total number of simultaneous connections. + limit_per_host - Number of simultaneous connections to one host. + loop - Optional event loop. + """ + + allowed_protocol_schema_set = HIGH_LEVEL_SCHEMA_SET | frozenset({"unix"}) + + def __init__( + self, + path: str, + force_close: bool = False, + keepalive_timeout: Union[object, float, None] = sentinel, + limit: int = 100, + limit_per_host: int = 0, + loop: Optional[asyncio.AbstractEventLoop] = None, + ) -> None: + super().__init__( + force_close=force_close, + keepalive_timeout=keepalive_timeout, + limit=limit, + limit_per_host=limit_per_host, + loop=loop, + ) + self._path = path + + @property + def path(self) -> str: + """Path to unix socket.""" + return self._path + + async def _create_connection( + self, req: ClientRequest, traces: List["Trace"], timeout: "ClientTimeout" + ) -> ResponseHandler: + try: + async with ceil_timeout( + timeout.sock_connect, ceil_threshold=timeout.ceil_threshold + ): + _, proto = await self._loop.create_unix_connection( + self._factory, self._path + ) + except OSError as exc: + if exc.errno is None and isinstance(exc, asyncio.TimeoutError): + raise + raise UnixClientConnectorError(self.path, req.connection_key, exc) from exc + + return proto + + +class NamedPipeConnector(BaseConnector): + """Named pipe connector. + + Only supported by the proactor event loop. + See also: https://docs.python.org/3/library/asyncio-eventloop.html + + path - Windows named pipe path. + keepalive_timeout - (optional) Keep-alive timeout. + force_close - Set to True to force close and do reconnect + after each request (and between redirects). + limit - The total number of simultaneous connections. + limit_per_host - Number of simultaneous connections to one host. + loop - Optional event loop. + """ + + allowed_protocol_schema_set = HIGH_LEVEL_SCHEMA_SET | frozenset({"npipe"}) + + def __init__( + self, + path: str, + force_close: bool = False, + keepalive_timeout: Union[object, float, None] = sentinel, + limit: int = 100, + limit_per_host: int = 0, + loop: Optional[asyncio.AbstractEventLoop] = None, + ) -> None: + super().__init__( + force_close=force_close, + keepalive_timeout=keepalive_timeout, + limit=limit, + limit_per_host=limit_per_host, + loop=loop, + ) + if not isinstance( + self._loop, + asyncio.ProactorEventLoop, # type: ignore[attr-defined] + ): + raise RuntimeError( + "Named Pipes only available in proactor loop under windows" + ) + self._path = path + + @property + def path(self) -> str: + """Path to the named pipe.""" + return self._path + + async def _create_connection( + self, req: ClientRequest, traces: List["Trace"], timeout: "ClientTimeout" + ) -> ResponseHandler: + try: + async with ceil_timeout( + timeout.sock_connect, ceil_threshold=timeout.ceil_threshold + ): + _, proto = await self._loop.create_pipe_connection( # type: ignore[attr-defined] + self._factory, self._path + ) + # the drain is required so that the connection_made is called + # and transport is set otherwise it is not set before the + # `assert conn.transport is not None` + # in client.py's _request method + await asyncio.sleep(0) + # other option is to manually set transport like + # `proto.transport = trans` + except OSError as exc: + if exc.errno is None and isinstance(exc, asyncio.TimeoutError): + raise + raise ClientConnectorError(req.connection_key, exc) from exc + + return cast(ResponseHandler, proto) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/cookiejar.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/cookiejar.py new file mode 100644 index 0000000000000000000000000000000000000000..193648d4309fc9252332c0c1241ab6aafbd2fe05 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/cookiejar.py @@ -0,0 +1,522 @@ +import asyncio +import calendar +import contextlib +import datetime +import heapq +import itertools +import os # noqa +import pathlib +import pickle +import re +import time +import warnings +from collections import defaultdict +from collections.abc import Mapping +from http.cookies import BaseCookie, Morsel, SimpleCookie +from typing import ( + DefaultDict, + Dict, + Iterable, + Iterator, + List, + Optional, + Set, + Tuple, + Union, +) + +from yarl import URL + +from ._cookie_helpers import preserve_morsel_with_coded_value +from .abc import AbstractCookieJar, ClearCookiePredicate +from .helpers import is_ip_address +from .typedefs import LooseCookies, PathLike, StrOrURL + +__all__ = ("CookieJar", "DummyCookieJar") + + +CookieItem = Union[str, "Morsel[str]"] + +# We cache these string methods here as their use is in performance critical code. +_FORMAT_PATH = "{}/{}".format +_FORMAT_DOMAIN_REVERSED = "{1}.{0}".format + +# The minimum number of scheduled cookie expirations before we start cleaning up +# the expiration heap. This is a performance optimization to avoid cleaning up the +# heap too often when there are only a few scheduled expirations. +_MIN_SCHEDULED_COOKIE_EXPIRATION = 100 +_SIMPLE_COOKIE = SimpleCookie() + + +class CookieJar(AbstractCookieJar): + """Implements cookie storage adhering to RFC 6265.""" + + DATE_TOKENS_RE = re.compile( + r"[\x09\x20-\x2F\x3B-\x40\x5B-\x60\x7B-\x7E]*" + r"(?P[\x00-\x08\x0A-\x1F\d:a-zA-Z\x7F-\xFF]+)" + ) + + DATE_HMS_TIME_RE = re.compile(r"(\d{1,2}):(\d{1,2}):(\d{1,2})") + + DATE_DAY_OF_MONTH_RE = re.compile(r"(\d{1,2})") + + DATE_MONTH_RE = re.compile( + "(jan)|(feb)|(mar)|(apr)|(may)|(jun)|(jul)|(aug)|(sep)|(oct)|(nov)|(dec)", + re.I, + ) + + DATE_YEAR_RE = re.compile(r"(\d{2,4})") + + # calendar.timegm() fails for timestamps after datetime.datetime.max + # Minus one as a loss of precision occurs when timestamp() is called. + MAX_TIME = ( + int(datetime.datetime.max.replace(tzinfo=datetime.timezone.utc).timestamp()) - 1 + ) + try: + calendar.timegm(time.gmtime(MAX_TIME)) + except (OSError, ValueError): + # Hit the maximum representable time on Windows + # https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/localtime-localtime32-localtime64 + # Throws ValueError on PyPy 3.9, OSError elsewhere + MAX_TIME = calendar.timegm((3000, 12, 31, 23, 59, 59, -1, -1, -1)) + except OverflowError: + # #4515: datetime.max may not be representable on 32-bit platforms + MAX_TIME = 2**31 - 1 + # Avoid minuses in the future, 3x faster + SUB_MAX_TIME = MAX_TIME - 1 + + def __init__( + self, + *, + unsafe: bool = False, + quote_cookie: bool = True, + treat_as_secure_origin: Union[StrOrURL, List[StrOrURL], None] = None, + loop: Optional[asyncio.AbstractEventLoop] = None, + ) -> None: + super().__init__(loop=loop) + self._cookies: DefaultDict[Tuple[str, str], SimpleCookie] = defaultdict( + SimpleCookie + ) + self._morsel_cache: DefaultDict[Tuple[str, str], Dict[str, Morsel[str]]] = ( + defaultdict(dict) + ) + self._host_only_cookies: Set[Tuple[str, str]] = set() + self._unsafe = unsafe + self._quote_cookie = quote_cookie + if treat_as_secure_origin is None: + treat_as_secure_origin = [] + elif isinstance(treat_as_secure_origin, URL): + treat_as_secure_origin = [treat_as_secure_origin.origin()] + elif isinstance(treat_as_secure_origin, str): + treat_as_secure_origin = [URL(treat_as_secure_origin).origin()] + else: + treat_as_secure_origin = [ + URL(url).origin() if isinstance(url, str) else url.origin() + for url in treat_as_secure_origin + ] + self._treat_as_secure_origin = treat_as_secure_origin + self._expire_heap: List[Tuple[float, Tuple[str, str, str]]] = [] + self._expirations: Dict[Tuple[str, str, str], float] = {} + + @property + def quote_cookie(self) -> bool: + return self._quote_cookie + + def save(self, file_path: PathLike) -> None: + file_path = pathlib.Path(file_path) + with file_path.open(mode="wb") as f: + pickle.dump(self._cookies, f, pickle.HIGHEST_PROTOCOL) + + def load(self, file_path: PathLike) -> None: + file_path = pathlib.Path(file_path) + with file_path.open(mode="rb") as f: + self._cookies = pickle.load(f) + + def clear(self, predicate: Optional[ClearCookiePredicate] = None) -> None: + if predicate is None: + self._expire_heap.clear() + self._cookies.clear() + self._morsel_cache.clear() + self._host_only_cookies.clear() + self._expirations.clear() + return + + now = time.time() + to_del = [ + key + for (domain, path), cookie in self._cookies.items() + for name, morsel in cookie.items() + if ( + (key := (domain, path, name)) in self._expirations + and self._expirations[key] <= now + ) + or predicate(morsel) + ] + if to_del: + self._delete_cookies(to_del) + + def clear_domain(self, domain: str) -> None: + self.clear(lambda x: self._is_domain_match(domain, x["domain"])) + + def __iter__(self) -> "Iterator[Morsel[str]]": + self._do_expiration() + for val in self._cookies.values(): + yield from val.values() + + def __len__(self) -> int: + """Return number of cookies. + + This function does not iterate self to avoid unnecessary expiration + checks. + """ + return sum(len(cookie.values()) for cookie in self._cookies.values()) + + def _do_expiration(self) -> None: + """Remove expired cookies.""" + if not (expire_heap_len := len(self._expire_heap)): + return + + # If the expiration heap grows larger than the number expirations + # times two, we clean it up to avoid keeping expired entries in + # the heap and consuming memory. We guard this with a minimum + # threshold to avoid cleaning up the heap too often when there are + # only a few scheduled expirations. + if ( + expire_heap_len > _MIN_SCHEDULED_COOKIE_EXPIRATION + and expire_heap_len > len(self._expirations) * 2 + ): + # Remove any expired entries from the expiration heap + # that do not match the expiration time in the expirations + # as it means the cookie has been re-added to the heap + # with a different expiration time. + self._expire_heap = [ + entry + for entry in self._expire_heap + if self._expirations.get(entry[1]) == entry[0] + ] + heapq.heapify(self._expire_heap) + + now = time.time() + to_del: List[Tuple[str, str, str]] = [] + # Find any expired cookies and add them to the to-delete list + while self._expire_heap: + when, cookie_key = self._expire_heap[0] + if when > now: + break + heapq.heappop(self._expire_heap) + # Check if the cookie hasn't been re-added to the heap + # with a different expiration time as it will be removed + # later when it reaches the top of the heap and its + # expiration time is met. + if self._expirations.get(cookie_key) == when: + to_del.append(cookie_key) + + if to_del: + self._delete_cookies(to_del) + + def _delete_cookies(self, to_del: List[Tuple[str, str, str]]) -> None: + for domain, path, name in to_del: + self._host_only_cookies.discard((domain, name)) + self._cookies[(domain, path)].pop(name, None) + self._morsel_cache[(domain, path)].pop(name, None) + self._expirations.pop((domain, path, name), None) + + def _expire_cookie(self, when: float, domain: str, path: str, name: str) -> None: + cookie_key = (domain, path, name) + if self._expirations.get(cookie_key) == when: + # Avoid adding duplicates to the heap + return + heapq.heappush(self._expire_heap, (when, cookie_key)) + self._expirations[cookie_key] = when + + def update_cookies(self, cookies: LooseCookies, response_url: URL = URL()) -> None: + """Update cookies.""" + hostname = response_url.raw_host + + if not self._unsafe and is_ip_address(hostname): + # Don't accept cookies from IPs + return + + if isinstance(cookies, Mapping): + cookies = cookies.items() + + for name, cookie in cookies: + if not isinstance(cookie, Morsel): + tmp = SimpleCookie() + tmp[name] = cookie # type: ignore[assignment] + cookie = tmp[name] + + domain = cookie["domain"] + + # ignore domains with trailing dots + if domain and domain[-1] == ".": + domain = "" + del cookie["domain"] + + if not domain and hostname is not None: + # Set the cookie's domain to the response hostname + # and set its host-only-flag + self._host_only_cookies.add((hostname, name)) + domain = cookie["domain"] = hostname + + if domain and domain[0] == ".": + # Remove leading dot + domain = domain[1:] + cookie["domain"] = domain + + if hostname and not self._is_domain_match(domain, hostname): + # Setting cookies for different domains is not allowed + continue + + path = cookie["path"] + if not path or path[0] != "/": + # Set the cookie's path to the response path + path = response_url.path + if not path.startswith("/"): + path = "/" + else: + # Cut everything from the last slash to the end + path = "/" + path[1 : path.rfind("/")] + cookie["path"] = path + path = path.rstrip("/") + + if max_age := cookie["max-age"]: + try: + delta_seconds = int(max_age) + max_age_expiration = min(time.time() + delta_seconds, self.MAX_TIME) + self._expire_cookie(max_age_expiration, domain, path, name) + except ValueError: + cookie["max-age"] = "" + + elif expires := cookie["expires"]: + if expire_time := self._parse_date(expires): + self._expire_cookie(expire_time, domain, path, name) + else: + cookie["expires"] = "" + + key = (domain, path) + if self._cookies[key].get(name) != cookie: + # Don't blow away the cache if the same + # cookie gets set again + self._cookies[key][name] = cookie + self._morsel_cache[key].pop(name, None) + + self._do_expiration() + + def filter_cookies(self, request_url: URL = URL()) -> "BaseCookie[str]": + """Returns this jar's cookies filtered by their attributes.""" + # We always use BaseCookie now since all + # cookies set on on filtered are fully constructed + # Morsels, not just names and values. + filtered: BaseCookie[str] = BaseCookie() + if not self._cookies: + # Skip do_expiration() if there are no cookies. + return filtered + self._do_expiration() + if not self._cookies: + # Skip rest of function if no non-expired cookies. + return filtered + if type(request_url) is not URL: + warnings.warn( + "filter_cookies expects yarl.URL instances only," + f"and will stop working in 4.x, got {type(request_url)}", + DeprecationWarning, + stacklevel=2, + ) + request_url = URL(request_url) + hostname = request_url.raw_host or "" + + is_not_secure = request_url.scheme not in ("https", "wss") + if is_not_secure and self._treat_as_secure_origin: + request_origin = URL() + with contextlib.suppress(ValueError): + request_origin = request_url.origin() + is_not_secure = request_origin not in self._treat_as_secure_origin + + # Send shared cookie + key = ("", "") + for c in self._cookies[key].values(): + # Check cache first + if c.key in self._morsel_cache[key]: + filtered[c.key] = self._morsel_cache[key][c.key] + continue + + # Build and cache the morsel + mrsl_val = self._build_morsel(c) + self._morsel_cache[key][c.key] = mrsl_val + filtered[c.key] = mrsl_val + + if is_ip_address(hostname): + if not self._unsafe: + return filtered + domains: Iterable[str] = (hostname,) + else: + # Get all the subdomains that might match a cookie (e.g. "foo.bar.com", "bar.com", "com") + domains = itertools.accumulate( + reversed(hostname.split(".")), _FORMAT_DOMAIN_REVERSED + ) + + # Get all the path prefixes that might match a cookie (e.g. "", "/foo", "/foo/bar") + paths = itertools.accumulate(request_url.path.split("/"), _FORMAT_PATH) + # Create every combination of (domain, path) pairs. + pairs = itertools.product(domains, paths) + + path_len = len(request_url.path) + # Point 2: https://www.rfc-editor.org/rfc/rfc6265.html#section-5.4 + for p in pairs: + if p not in self._cookies: + continue + for name, cookie in self._cookies[p].items(): + domain = cookie["domain"] + + if (domain, name) in self._host_only_cookies and domain != hostname: + continue + + # Skip edge case when the cookie has a trailing slash but request doesn't. + if len(cookie["path"]) > path_len: + continue + + if is_not_secure and cookie["secure"]: + continue + + # We already built the Morsel so reuse it here + if name in self._morsel_cache[p]: + filtered[name] = self._morsel_cache[p][name] + continue + + # Build and cache the morsel + mrsl_val = self._build_morsel(cookie) + self._morsel_cache[p][name] = mrsl_val + filtered[name] = mrsl_val + + return filtered + + def _build_morsel(self, cookie: Morsel[str]) -> Morsel[str]: + """Build a morsel for sending, respecting quote_cookie setting.""" + if self._quote_cookie and cookie.coded_value and cookie.coded_value[0] == '"': + return preserve_morsel_with_coded_value(cookie) + morsel: Morsel[str] = Morsel() + if self._quote_cookie: + value, coded_value = _SIMPLE_COOKIE.value_encode(cookie.value) + else: + coded_value = value = cookie.value + # We use __setstate__ instead of the public set() API because it allows us to + # bypass validation and set already validated state. This is more stable than + # setting protected attributes directly and unlikely to change since it would + # break pickling. + morsel.__setstate__({"key": cookie.key, "value": value, "coded_value": coded_value}) # type: ignore[attr-defined] + return morsel + + @staticmethod + def _is_domain_match(domain: str, hostname: str) -> bool: + """Implements domain matching adhering to RFC 6265.""" + if hostname == domain: + return True + + if not hostname.endswith(domain): + return False + + non_matching = hostname[: -len(domain)] + + if not non_matching.endswith("."): + return False + + return not is_ip_address(hostname) + + @classmethod + def _parse_date(cls, date_str: str) -> Optional[int]: + """Implements date string parsing adhering to RFC 6265.""" + if not date_str: + return None + + found_time = False + found_day = False + found_month = False + found_year = False + + hour = minute = second = 0 + day = 0 + month = 0 + year = 0 + + for token_match in cls.DATE_TOKENS_RE.finditer(date_str): + + token = token_match.group("token") + + if not found_time: + time_match = cls.DATE_HMS_TIME_RE.match(token) + if time_match: + found_time = True + hour, minute, second = (int(s) for s in time_match.groups()) + continue + + if not found_day: + day_match = cls.DATE_DAY_OF_MONTH_RE.match(token) + if day_match: + found_day = True + day = int(day_match.group()) + continue + + if not found_month: + month_match = cls.DATE_MONTH_RE.match(token) + if month_match: + found_month = True + assert month_match.lastindex is not None + month = month_match.lastindex + continue + + if not found_year: + year_match = cls.DATE_YEAR_RE.match(token) + if year_match: + found_year = True + year = int(year_match.group()) + + if 70 <= year <= 99: + year += 1900 + elif 0 <= year <= 69: + year += 2000 + + if False in (found_day, found_month, found_year, found_time): + return None + + if not 1 <= day <= 31: + return None + + if year < 1601 or hour > 23 or minute > 59 or second > 59: + return None + + return calendar.timegm((year, month, day, hour, minute, second, -1, -1, -1)) + + +class DummyCookieJar(AbstractCookieJar): + """Implements a dummy cookie storage. + + It can be used with the ClientSession when no cookie processing is needed. + + """ + + def __init__(self, *, loop: Optional[asyncio.AbstractEventLoop] = None) -> None: + super().__init__(loop=loop) + + def __iter__(self) -> "Iterator[Morsel[str]]": + while False: + yield None + + def __len__(self) -> int: + return 0 + + @property + def quote_cookie(self) -> bool: + return True + + def clear(self, predicate: Optional[ClearCookiePredicate] = None) -> None: + pass + + def clear_domain(self, domain: str) -> None: + pass + + def update_cookies(self, cookies: LooseCookies, response_url: URL = URL()) -> None: + pass + + def filter_cookies(self, request_url: URL) -> "BaseCookie[str]": + return SimpleCookie() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/formdata.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/formdata.py new file mode 100644 index 0000000000000000000000000000000000000000..17d219a32c0861dbe0c13c19c608d88e8cf4cfab --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/formdata.py @@ -0,0 +1,184 @@ +import io +import warnings +from typing import Any, Iterable, List, Optional +from urllib.parse import urlencode + +from multidict import MultiDict, MultiDictProxy + +from . import hdrs, multipart, payload +from .helpers import guess_filename +from .payload import Payload + +__all__ = ("FormData",) + + +class FormData: + """Helper class for form body generation. + + Supports multipart/form-data and application/x-www-form-urlencoded. + """ + + def __init__( + self, + fields: Iterable[Any] = (), + quote_fields: bool = True, + charset: Optional[str] = None, + *, + default_to_multipart: bool = False, + ) -> None: + self._writer = multipart.MultipartWriter("form-data") + self._fields: List[Any] = [] + self._is_multipart = default_to_multipart + self._quote_fields = quote_fields + self._charset = charset + + if isinstance(fields, dict): + fields = list(fields.items()) + elif not isinstance(fields, (list, tuple)): + fields = (fields,) + self.add_fields(*fields) + + @property + def is_multipart(self) -> bool: + return self._is_multipart + + def add_field( + self, + name: str, + value: Any, + *, + content_type: Optional[str] = None, + filename: Optional[str] = None, + content_transfer_encoding: Optional[str] = None, + ) -> None: + + if isinstance(value, io.IOBase): + self._is_multipart = True + elif isinstance(value, (bytes, bytearray, memoryview)): + msg = ( + "In v4, passing bytes will no longer create a file field. " + "Please explicitly use the filename parameter or pass a BytesIO object." + ) + if filename is None and content_transfer_encoding is None: + warnings.warn(msg, DeprecationWarning) + filename = name + + type_options: MultiDict[str] = MultiDict({"name": name}) + if filename is not None and not isinstance(filename, str): + raise TypeError("filename must be an instance of str. Got: %s" % filename) + if filename is None and isinstance(value, io.IOBase): + filename = guess_filename(value, name) + if filename is not None: + type_options["filename"] = filename + self._is_multipart = True + + headers = {} + if content_type is not None: + if not isinstance(content_type, str): + raise TypeError( + "content_type must be an instance of str. Got: %s" % content_type + ) + if "\r" in content_type or "\n" in content_type: + raise ValueError( + "Newline or carriage return detected in headers. " + "Potential header injection attack." + ) + headers[hdrs.CONTENT_TYPE] = content_type + self._is_multipart = True + if content_transfer_encoding is not None: + if not isinstance(content_transfer_encoding, str): + raise TypeError( + "content_transfer_encoding must be an instance" + " of str. Got: %s" % content_transfer_encoding + ) + msg = ( + "content_transfer_encoding is deprecated. " + "To maintain compatibility with v4 please pass a BytesPayload." + ) + warnings.warn(msg, DeprecationWarning) + self._is_multipart = True + + self._fields.append((type_options, headers, value)) + + def add_fields(self, *fields: Any) -> None: + to_add = list(fields) + + while to_add: + rec = to_add.pop(0) + + if isinstance(rec, io.IOBase): + k = guess_filename(rec, "unknown") + self.add_field(k, rec) # type: ignore[arg-type] + + elif isinstance(rec, (MultiDictProxy, MultiDict)): + to_add.extend(rec.items()) + + elif isinstance(rec, (list, tuple)) and len(rec) == 2: + k, fp = rec + self.add_field(k, fp) + + else: + raise TypeError( + "Only io.IOBase, multidict and (name, file) " + "pairs allowed, use .add_field() for passing " + "more complex parameters, got {!r}".format(rec) + ) + + def _gen_form_urlencoded(self) -> payload.BytesPayload: + # form data (x-www-form-urlencoded) + data = [] + for type_options, _, value in self._fields: + data.append((type_options["name"], value)) + + charset = self._charset if self._charset is not None else "utf-8" + + if charset == "utf-8": + content_type = "application/x-www-form-urlencoded" + else: + content_type = "application/x-www-form-urlencoded; charset=%s" % charset + + return payload.BytesPayload( + urlencode(data, doseq=True, encoding=charset).encode(), + content_type=content_type, + ) + + def _gen_form_data(self) -> multipart.MultipartWriter: + """Encode a list of fields using the multipart/form-data MIME format""" + for dispparams, headers, value in self._fields: + try: + if hdrs.CONTENT_TYPE in headers: + part = payload.get_payload( + value, + content_type=headers[hdrs.CONTENT_TYPE], + headers=headers, + encoding=self._charset, + ) + else: + part = payload.get_payload( + value, headers=headers, encoding=self._charset + ) + except Exception as exc: + raise TypeError( + "Can not serialize value type: %r\n " + "headers: %r\n value: %r" % (type(value), headers, value) + ) from exc + + if dispparams: + part.set_content_disposition( + "form-data", quote_fields=self._quote_fields, **dispparams + ) + # FIXME cgi.FieldStorage doesn't likes body parts with + # Content-Length which were sent via chunked transfer encoding + assert part.headers is not None + part.headers.popall(hdrs.CONTENT_LENGTH, None) + + self._writer.append_payload(part) + + self._fields.clear() + return self._writer + + def __call__(self) -> Payload: + if self._is_multipart: + return self._gen_form_data() + else: + return self._gen_form_urlencoded() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/hdrs.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/hdrs.py new file mode 100644 index 0000000000000000000000000000000000000000..c8d6b35f33ae4be537d7776ce4085982618d1305 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/hdrs.py @@ -0,0 +1,121 @@ +"""HTTP Headers constants.""" + +# After changing the file content call ./tools/gen.py +# to regenerate the headers parser +import itertools +from typing import Final, Set + +from multidict import istr + +METH_ANY: Final[str] = "*" +METH_CONNECT: Final[str] = "CONNECT" +METH_HEAD: Final[str] = "HEAD" +METH_GET: Final[str] = "GET" +METH_DELETE: Final[str] = "DELETE" +METH_OPTIONS: Final[str] = "OPTIONS" +METH_PATCH: Final[str] = "PATCH" +METH_POST: Final[str] = "POST" +METH_PUT: Final[str] = "PUT" +METH_TRACE: Final[str] = "TRACE" + +METH_ALL: Final[Set[str]] = { + METH_CONNECT, + METH_HEAD, + METH_GET, + METH_DELETE, + METH_OPTIONS, + METH_PATCH, + METH_POST, + METH_PUT, + METH_TRACE, +} + +ACCEPT: Final[istr] = istr("Accept") +ACCEPT_CHARSET: Final[istr] = istr("Accept-Charset") +ACCEPT_ENCODING: Final[istr] = istr("Accept-Encoding") +ACCEPT_LANGUAGE: Final[istr] = istr("Accept-Language") +ACCEPT_RANGES: Final[istr] = istr("Accept-Ranges") +ACCESS_CONTROL_MAX_AGE: Final[istr] = istr("Access-Control-Max-Age") +ACCESS_CONTROL_ALLOW_CREDENTIALS: Final[istr] = istr("Access-Control-Allow-Credentials") +ACCESS_CONTROL_ALLOW_HEADERS: Final[istr] = istr("Access-Control-Allow-Headers") +ACCESS_CONTROL_ALLOW_METHODS: Final[istr] = istr("Access-Control-Allow-Methods") +ACCESS_CONTROL_ALLOW_ORIGIN: Final[istr] = istr("Access-Control-Allow-Origin") +ACCESS_CONTROL_EXPOSE_HEADERS: Final[istr] = istr("Access-Control-Expose-Headers") +ACCESS_CONTROL_REQUEST_HEADERS: Final[istr] = istr("Access-Control-Request-Headers") +ACCESS_CONTROL_REQUEST_METHOD: Final[istr] = istr("Access-Control-Request-Method") +AGE: Final[istr] = istr("Age") +ALLOW: Final[istr] = istr("Allow") +AUTHORIZATION: Final[istr] = istr("Authorization") +CACHE_CONTROL: Final[istr] = istr("Cache-Control") +CONNECTION: Final[istr] = istr("Connection") +CONTENT_DISPOSITION: Final[istr] = istr("Content-Disposition") +CONTENT_ENCODING: Final[istr] = istr("Content-Encoding") +CONTENT_LANGUAGE: Final[istr] = istr("Content-Language") +CONTENT_LENGTH: Final[istr] = istr("Content-Length") +CONTENT_LOCATION: Final[istr] = istr("Content-Location") +CONTENT_MD5: Final[istr] = istr("Content-MD5") +CONTENT_RANGE: Final[istr] = istr("Content-Range") +CONTENT_TRANSFER_ENCODING: Final[istr] = istr("Content-Transfer-Encoding") +CONTENT_TYPE: Final[istr] = istr("Content-Type") +COOKIE: Final[istr] = istr("Cookie") +DATE: Final[istr] = istr("Date") +DESTINATION: Final[istr] = istr("Destination") +DIGEST: Final[istr] = istr("Digest") +ETAG: Final[istr] = istr("Etag") +EXPECT: Final[istr] = istr("Expect") +EXPIRES: Final[istr] = istr("Expires") +FORWARDED: Final[istr] = istr("Forwarded") +FROM: Final[istr] = istr("From") +HOST: Final[istr] = istr("Host") +IF_MATCH: Final[istr] = istr("If-Match") +IF_MODIFIED_SINCE: Final[istr] = istr("If-Modified-Since") +IF_NONE_MATCH: Final[istr] = istr("If-None-Match") +IF_RANGE: Final[istr] = istr("If-Range") +IF_UNMODIFIED_SINCE: Final[istr] = istr("If-Unmodified-Since") +KEEP_ALIVE: Final[istr] = istr("Keep-Alive") +LAST_EVENT_ID: Final[istr] = istr("Last-Event-ID") +LAST_MODIFIED: Final[istr] = istr("Last-Modified") +LINK: Final[istr] = istr("Link") +LOCATION: Final[istr] = istr("Location") +MAX_FORWARDS: Final[istr] = istr("Max-Forwards") +ORIGIN: Final[istr] = istr("Origin") +PRAGMA: Final[istr] = istr("Pragma") +PROXY_AUTHENTICATE: Final[istr] = istr("Proxy-Authenticate") +PROXY_AUTHORIZATION: Final[istr] = istr("Proxy-Authorization") +RANGE: Final[istr] = istr("Range") +REFERER: Final[istr] = istr("Referer") +RETRY_AFTER: Final[istr] = istr("Retry-After") +SEC_WEBSOCKET_ACCEPT: Final[istr] = istr("Sec-WebSocket-Accept") +SEC_WEBSOCKET_VERSION: Final[istr] = istr("Sec-WebSocket-Version") +SEC_WEBSOCKET_PROTOCOL: Final[istr] = istr("Sec-WebSocket-Protocol") +SEC_WEBSOCKET_EXTENSIONS: Final[istr] = istr("Sec-WebSocket-Extensions") +SEC_WEBSOCKET_KEY: Final[istr] = istr("Sec-WebSocket-Key") +SEC_WEBSOCKET_KEY1: Final[istr] = istr("Sec-WebSocket-Key1") +SERVER: Final[istr] = istr("Server") +SET_COOKIE: Final[istr] = istr("Set-Cookie") +TE: Final[istr] = istr("TE") +TRAILER: Final[istr] = istr("Trailer") +TRANSFER_ENCODING: Final[istr] = istr("Transfer-Encoding") +UPGRADE: Final[istr] = istr("Upgrade") +URI: Final[istr] = istr("URI") +USER_AGENT: Final[istr] = istr("User-Agent") +VARY: Final[istr] = istr("Vary") +VIA: Final[istr] = istr("Via") +WANT_DIGEST: Final[istr] = istr("Want-Digest") +WARNING: Final[istr] = istr("Warning") +WWW_AUTHENTICATE: Final[istr] = istr("WWW-Authenticate") +X_FORWARDED_FOR: Final[istr] = istr("X-Forwarded-For") +X_FORWARDED_HOST: Final[istr] = istr("X-Forwarded-Host") +X_FORWARDED_PROTO: Final[istr] = istr("X-Forwarded-Proto") + +# These are the upper/lower case variants of the headers/methods +# Example: {'hOst', 'host', 'HoST', 'HOSt', 'hOsT', 'HosT', 'hoSt', ...} +METH_HEAD_ALL: Final = frozenset( + map("".join, itertools.product(*zip(METH_HEAD.upper(), METH_HEAD.lower()))) +) +METH_CONNECT_ALL: Final = frozenset( + map("".join, itertools.product(*zip(METH_CONNECT.upper(), METH_CONNECT.lower()))) +) +HOST_ALL: Final = frozenset( + map("".join, itertools.product(*zip(HOST.upper(), HOST.lower()))) +) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/helpers.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/helpers.py new file mode 100644 index 0000000000000000000000000000000000000000..dfab9877d398a9c8807ad2614397b3e54316ed1f --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/helpers.py @@ -0,0 +1,986 @@ +"""Various helper functions""" + +import asyncio +import base64 +import binascii +import contextlib +import datetime +import enum +import functools +import inspect +import netrc +import os +import platform +import re +import sys +import time +import weakref +from collections import namedtuple +from contextlib import suppress +from email.message import EmailMessage +from email.parser import HeaderParser +from email.policy import HTTP +from email.utils import parsedate +from math import ceil +from pathlib import Path +from types import MappingProxyType, TracebackType +from typing import ( + Any, + Callable, + ContextManager, + Dict, + Generator, + Generic, + Iterable, + Iterator, + List, + Mapping, + Optional, + Protocol, + Tuple, + Type, + TypeVar, + Union, + get_args, + overload, +) +from urllib.parse import quote +from urllib.request import getproxies, proxy_bypass + +import attr +from multidict import MultiDict, MultiDictProxy, MultiMapping +from propcache.api import under_cached_property as reify +from yarl import URL + +from . import hdrs +from .log import client_logger + +if sys.version_info >= (3, 11): + import asyncio as async_timeout +else: + import async_timeout + +__all__ = ("BasicAuth", "ChainMapProxy", "ETag", "reify") + +IS_MACOS = platform.system() == "Darwin" +IS_WINDOWS = platform.system() == "Windows" + +PY_310 = sys.version_info >= (3, 10) +PY_311 = sys.version_info >= (3, 11) + + +_T = TypeVar("_T") +_S = TypeVar("_S") + +_SENTINEL = enum.Enum("_SENTINEL", "sentinel") +sentinel = _SENTINEL.sentinel + +NO_EXTENSIONS = bool(os.environ.get("AIOHTTP_NO_EXTENSIONS")) + +# https://datatracker.ietf.org/doc/html/rfc9112#section-6.3-2.1 +EMPTY_BODY_STATUS_CODES = frozenset((204, 304, *range(100, 200))) +# https://datatracker.ietf.org/doc/html/rfc9112#section-6.3-2.1 +# https://datatracker.ietf.org/doc/html/rfc9112#section-6.3-2.2 +EMPTY_BODY_METHODS = hdrs.METH_HEAD_ALL + +DEBUG = sys.flags.dev_mode or ( + not sys.flags.ignore_environment and bool(os.environ.get("PYTHONASYNCIODEBUG")) +) + + +CHAR = {chr(i) for i in range(0, 128)} +CTL = {chr(i) for i in range(0, 32)} | { + chr(127), +} +SEPARATORS = { + "(", + ")", + "<", + ">", + "@", + ",", + ";", + ":", + "\\", + '"', + "/", + "[", + "]", + "?", + "=", + "{", + "}", + " ", + chr(9), +} +TOKEN = CHAR ^ CTL ^ SEPARATORS + + +class noop: + def __await__(self) -> Generator[None, None, None]: + yield + + +class BasicAuth(namedtuple("BasicAuth", ["login", "password", "encoding"])): + """Http basic authentication helper.""" + + def __new__( + cls, login: str, password: str = "", encoding: str = "latin1" + ) -> "BasicAuth": + if login is None: + raise ValueError("None is not allowed as login value") + + if password is None: + raise ValueError("None is not allowed as password value") + + if ":" in login: + raise ValueError('A ":" is not allowed in login (RFC 1945#section-11.1)') + + return super().__new__(cls, login, password, encoding) + + @classmethod + def decode(cls, auth_header: str, encoding: str = "latin1") -> "BasicAuth": + """Create a BasicAuth object from an Authorization HTTP header.""" + try: + auth_type, encoded_credentials = auth_header.split(" ", 1) + except ValueError: + raise ValueError("Could not parse authorization header.") + + if auth_type.lower() != "basic": + raise ValueError("Unknown authorization method %s" % auth_type) + + try: + decoded = base64.b64decode( + encoded_credentials.encode("ascii"), validate=True + ).decode(encoding) + except binascii.Error: + raise ValueError("Invalid base64 encoding.") + + try: + # RFC 2617 HTTP Authentication + # https://www.ietf.org/rfc/rfc2617.txt + # the colon must be present, but the username and password may be + # otherwise blank. + username, password = decoded.split(":", 1) + except ValueError: + raise ValueError("Invalid credentials.") + + return cls(username, password, encoding=encoding) + + @classmethod + def from_url(cls, url: URL, *, encoding: str = "latin1") -> Optional["BasicAuth"]: + """Create BasicAuth from url.""" + if not isinstance(url, URL): + raise TypeError("url should be yarl.URL instance") + # Check raw_user and raw_password first as yarl is likely + # to already have these values parsed from the netloc in the cache. + if url.raw_user is None and url.raw_password is None: + return None + return cls(url.user or "", url.password or "", encoding=encoding) + + def encode(self) -> str: + """Encode credentials.""" + creds = (f"{self.login}:{self.password}").encode(self.encoding) + return "Basic %s" % base64.b64encode(creds).decode(self.encoding) + + +def strip_auth_from_url(url: URL) -> Tuple[URL, Optional[BasicAuth]]: + """Remove user and password from URL if present and return BasicAuth object.""" + # Check raw_user and raw_password first as yarl is likely + # to already have these values parsed from the netloc in the cache. + if url.raw_user is None and url.raw_password is None: + return url, None + return url.with_user(None), BasicAuth(url.user or "", url.password or "") + + +def netrc_from_env() -> Optional[netrc.netrc]: + """Load netrc from file. + + Attempt to load it from the path specified by the env-var + NETRC or in the default location in the user's home directory. + + Returns None if it couldn't be found or fails to parse. + """ + netrc_env = os.environ.get("NETRC") + + if netrc_env is not None: + netrc_path = Path(netrc_env) + else: + try: + home_dir = Path.home() + except RuntimeError as e: # pragma: no cover + # if pathlib can't resolve home, it may raise a RuntimeError + client_logger.debug( + "Could not resolve home directory when " + "trying to look for .netrc file: %s", + e, + ) + return None + + netrc_path = home_dir / ("_netrc" if IS_WINDOWS else ".netrc") + + try: + return netrc.netrc(str(netrc_path)) + except netrc.NetrcParseError as e: + client_logger.warning("Could not parse .netrc file: %s", e) + except OSError as e: + netrc_exists = False + with contextlib.suppress(OSError): + netrc_exists = netrc_path.is_file() + # we couldn't read the file (doesn't exist, permissions, etc.) + if netrc_env or netrc_exists: + # only warn if the environment wanted us to load it, + # or it appears like the default file does actually exist + client_logger.warning("Could not read .netrc file: %s", e) + + return None + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class ProxyInfo: + proxy: URL + proxy_auth: Optional[BasicAuth] + + +def basicauth_from_netrc(netrc_obj: Optional[netrc.netrc], host: str) -> BasicAuth: + """ + Return :py:class:`~aiohttp.BasicAuth` credentials for ``host`` from ``netrc_obj``. + + :raises LookupError: if ``netrc_obj`` is :py:data:`None` or if no + entry is found for the ``host``. + """ + if netrc_obj is None: + raise LookupError("No .netrc file found") + auth_from_netrc = netrc_obj.authenticators(host) + + if auth_from_netrc is None: + raise LookupError(f"No entry for {host!s} found in the `.netrc` file.") + login, account, password = auth_from_netrc + + # TODO(PY311): username = login or account + # Up to python 3.10, account could be None if not specified, + # and login will be empty string if not specified. From 3.11, + # login and account will be empty string if not specified. + username = login if (login or account is None) else account + + # TODO(PY311): Remove this, as password will be empty string + # if not specified + if password is None: + password = "" + + return BasicAuth(username, password) + + +def proxies_from_env() -> Dict[str, ProxyInfo]: + proxy_urls = { + k: URL(v) + for k, v in getproxies().items() + if k in ("http", "https", "ws", "wss") + } + netrc_obj = netrc_from_env() + stripped = {k: strip_auth_from_url(v) for k, v in proxy_urls.items()} + ret = {} + for proto, val in stripped.items(): + proxy, auth = val + if proxy.scheme in ("https", "wss"): + client_logger.warning( + "%s proxies %s are not supported, ignoring", proxy.scheme.upper(), proxy + ) + continue + if netrc_obj and auth is None: + if proxy.host is not None: + try: + auth = basicauth_from_netrc(netrc_obj, proxy.host) + except LookupError: + auth = None + ret[proto] = ProxyInfo(proxy, auth) + return ret + + +def get_env_proxy_for_url(url: URL) -> Tuple[URL, Optional[BasicAuth]]: + """Get a permitted proxy for the given URL from the env.""" + if url.host is not None and proxy_bypass(url.host): + raise LookupError(f"Proxying is disallowed for `{url.host!r}`") + + proxies_in_env = proxies_from_env() + try: + proxy_info = proxies_in_env[url.scheme] + except KeyError: + raise LookupError(f"No proxies found for `{url!s}` in the env") + else: + return proxy_info.proxy, proxy_info.proxy_auth + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class MimeType: + type: str + subtype: str + suffix: str + parameters: "MultiDictProxy[str]" + + +@functools.lru_cache(maxsize=56) +def parse_mimetype(mimetype: str) -> MimeType: + """Parses a MIME type into its components. + + mimetype is a MIME type string. + + Returns a MimeType object. + + Example: + + >>> parse_mimetype('text/html; charset=utf-8') + MimeType(type='text', subtype='html', suffix='', + parameters={'charset': 'utf-8'}) + + """ + if not mimetype: + return MimeType( + type="", subtype="", suffix="", parameters=MultiDictProxy(MultiDict()) + ) + + parts = mimetype.split(";") + params: MultiDict[str] = MultiDict() + for item in parts[1:]: + if not item: + continue + key, _, value = item.partition("=") + params.add(key.lower().strip(), value.strip(' "')) + + fulltype = parts[0].strip().lower() + if fulltype == "*": + fulltype = "*/*" + + mtype, _, stype = fulltype.partition("/") + stype, _, suffix = stype.partition("+") + + return MimeType( + type=mtype, subtype=stype, suffix=suffix, parameters=MultiDictProxy(params) + ) + + +class EnsureOctetStream(EmailMessage): + def __init__(self) -> None: + super().__init__() + # https://www.rfc-editor.org/rfc/rfc9110#section-8.3-5 + self.set_default_type("application/octet-stream") + + def get_content_type(self) -> str: + """Re-implementation from Message + + Returns application/octet-stream in place of plain/text when + value is wrong. + + The way this class is used guarantees that content-type will + be present so simplify the checks wrt to the base implementation. + """ + value = self.get("content-type", "").lower() + + # Based on the implementation of _splitparam in the standard library + ctype, _, _ = value.partition(";") + ctype = ctype.strip() + if ctype.count("/") != 1: + return self.get_default_type() + return ctype + + +@functools.lru_cache(maxsize=56) +def parse_content_type(raw: str) -> Tuple[str, MappingProxyType[str, str]]: + """Parse Content-Type header. + + Returns a tuple of the parsed content type and a + MappingProxyType of parameters. The default returned value + is `application/octet-stream` + """ + msg = HeaderParser(EnsureOctetStream, policy=HTTP).parsestr(f"Content-Type: {raw}") + content_type = msg.get_content_type() + params = msg.get_params(()) + content_dict = dict(params[1:]) # First element is content type again + return content_type, MappingProxyType(content_dict) + + +def guess_filename(obj: Any, default: Optional[str] = None) -> Optional[str]: + name = getattr(obj, "name", None) + if name and isinstance(name, str) and name[0] != "<" and name[-1] != ">": + return Path(name).name + return default + + +not_qtext_re = re.compile(r"[^\041\043-\133\135-\176]") +QCONTENT = {chr(i) for i in range(0x20, 0x7F)} | {"\t"} + + +def quoted_string(content: str) -> str: + """Return 7-bit content as quoted-string. + + Format content into a quoted-string as defined in RFC5322 for + Internet Message Format. Notice that this is not the 8-bit HTTP + format, but the 7-bit email format. Content must be in usascii or + a ValueError is raised. + """ + if not (QCONTENT > set(content)): + raise ValueError(f"bad content for quoted-string {content!r}") + return not_qtext_re.sub(lambda x: "\\" + x.group(0), content) + + +def content_disposition_header( + disptype: str, quote_fields: bool = True, _charset: str = "utf-8", **params: str +) -> str: + """Sets ``Content-Disposition`` header for MIME. + + This is the MIME payload Content-Disposition header from RFC 2183 + and RFC 7579 section 4.2, not the HTTP Content-Disposition from + RFC 6266. + + disptype is a disposition type: inline, attachment, form-data. + Should be valid extension token (see RFC 2183) + + quote_fields performs value quoting to 7-bit MIME headers + according to RFC 7578. Set to quote_fields to False if recipient + can take 8-bit file names and field values. + + _charset specifies the charset to use when quote_fields is True. + + params is a dict with disposition params. + """ + if not disptype or not (TOKEN > set(disptype)): + raise ValueError(f"bad content disposition type {disptype!r}") + + value = disptype + if params: + lparams = [] + for key, val in params.items(): + if not key or not (TOKEN > set(key)): + raise ValueError(f"bad content disposition parameter {key!r}={val!r}") + if quote_fields: + if key.lower() == "filename": + qval = quote(val, "", encoding=_charset) + lparams.append((key, '"%s"' % qval)) + else: + try: + qval = quoted_string(val) + except ValueError: + qval = "".join( + (_charset, "''", quote(val, "", encoding=_charset)) + ) + lparams.append((key + "*", qval)) + else: + lparams.append((key, '"%s"' % qval)) + else: + qval = val.replace("\\", "\\\\").replace('"', '\\"') + lparams.append((key, '"%s"' % qval)) + sparams = "; ".join("=".join(pair) for pair in lparams) + value = "; ".join((value, sparams)) + return value + + +def is_ip_address(host: Optional[str]) -> bool: + """Check if host looks like an IP Address. + + This check is only meant as a heuristic to ensure that + a host is not a domain name. + """ + if not host: + return False + # For a host to be an ipv4 address, it must be all numeric. + # The host must contain a colon to be an IPv6 address. + return ":" in host or host.replace(".", "").isdigit() + + +_cached_current_datetime: Optional[int] = None +_cached_formatted_datetime = "" + + +def rfc822_formatted_time() -> str: + global _cached_current_datetime + global _cached_formatted_datetime + + now = int(time.time()) + if now != _cached_current_datetime: + # Weekday and month names for HTTP date/time formatting; + # always English! + # Tuples are constants stored in codeobject! + _weekdayname = ("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun") + _monthname = ( + "", # Dummy so we can use 1-based month numbers + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", + ) + + year, month, day, hh, mm, ss, wd, *tail = time.gmtime(now) + _cached_formatted_datetime = "%s, %02d %3s %4d %02d:%02d:%02d GMT" % ( + _weekdayname[wd], + day, + _monthname[month], + year, + hh, + mm, + ss, + ) + _cached_current_datetime = now + return _cached_formatted_datetime + + +def _weakref_handle(info: "Tuple[weakref.ref[object], str]") -> None: + ref, name = info + ob = ref() + if ob is not None: + with suppress(Exception): + getattr(ob, name)() + + +def weakref_handle( + ob: object, + name: str, + timeout: float, + loop: asyncio.AbstractEventLoop, + timeout_ceil_threshold: float = 5, +) -> Optional[asyncio.TimerHandle]: + if timeout is not None and timeout > 0: + when = loop.time() + timeout + if timeout >= timeout_ceil_threshold: + when = ceil(when) + + return loop.call_at(when, _weakref_handle, (weakref.ref(ob), name)) + return None + + +def call_later( + cb: Callable[[], Any], + timeout: float, + loop: asyncio.AbstractEventLoop, + timeout_ceil_threshold: float = 5, +) -> Optional[asyncio.TimerHandle]: + if timeout is None or timeout <= 0: + return None + now = loop.time() + when = calculate_timeout_when(now, timeout, timeout_ceil_threshold) + return loop.call_at(when, cb) + + +def calculate_timeout_when( + loop_time: float, + timeout: float, + timeout_ceiling_threshold: float, +) -> float: + """Calculate when to execute a timeout.""" + when = loop_time + timeout + if timeout > timeout_ceiling_threshold: + return ceil(when) + return when + + +class TimeoutHandle: + """Timeout handle""" + + __slots__ = ("_timeout", "_loop", "_ceil_threshold", "_callbacks") + + def __init__( + self, + loop: asyncio.AbstractEventLoop, + timeout: Optional[float], + ceil_threshold: float = 5, + ) -> None: + self._timeout = timeout + self._loop = loop + self._ceil_threshold = ceil_threshold + self._callbacks: List[ + Tuple[Callable[..., None], Tuple[Any, ...], Dict[str, Any]] + ] = [] + + def register( + self, callback: Callable[..., None], *args: Any, **kwargs: Any + ) -> None: + self._callbacks.append((callback, args, kwargs)) + + def close(self) -> None: + self._callbacks.clear() + + def start(self) -> Optional[asyncio.TimerHandle]: + timeout = self._timeout + if timeout is not None and timeout > 0: + when = self._loop.time() + timeout + if timeout >= self._ceil_threshold: + when = ceil(when) + return self._loop.call_at(when, self.__call__) + else: + return None + + def timer(self) -> "BaseTimerContext": + if self._timeout is not None and self._timeout > 0: + timer = TimerContext(self._loop) + self.register(timer.timeout) + return timer + else: + return TimerNoop() + + def __call__(self) -> None: + for cb, args, kwargs in self._callbacks: + with suppress(Exception): + cb(*args, **kwargs) + + self._callbacks.clear() + + +class BaseTimerContext(ContextManager["BaseTimerContext"]): + + __slots__ = () + + def assert_timeout(self) -> None: + """Raise TimeoutError if timeout has been exceeded.""" + + +class TimerNoop(BaseTimerContext): + + __slots__ = () + + def __enter__(self) -> BaseTimerContext: + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + return + + +class TimerContext(BaseTimerContext): + """Low resolution timeout context manager""" + + __slots__ = ("_loop", "_tasks", "_cancelled", "_cancelling") + + def __init__(self, loop: asyncio.AbstractEventLoop) -> None: + self._loop = loop + self._tasks: List[asyncio.Task[Any]] = [] + self._cancelled = False + self._cancelling = 0 + + def assert_timeout(self) -> None: + """Raise TimeoutError if timer has already been cancelled.""" + if self._cancelled: + raise asyncio.TimeoutError from None + + def __enter__(self) -> BaseTimerContext: + task = asyncio.current_task(loop=self._loop) + if task is None: + raise RuntimeError("Timeout context manager should be used inside a task") + + if sys.version_info >= (3, 11): + # Remember if the task was already cancelling + # so when we __exit__ we can decide if we should + # raise asyncio.TimeoutError or let the cancellation propagate + self._cancelling = task.cancelling() + + if self._cancelled: + raise asyncio.TimeoutError from None + + self._tasks.append(task) + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> Optional[bool]: + enter_task: Optional[asyncio.Task[Any]] = None + if self._tasks: + enter_task = self._tasks.pop() + + if exc_type is asyncio.CancelledError and self._cancelled: + assert enter_task is not None + # The timeout was hit, and the task was cancelled + # so we need to uncancel the last task that entered the context manager + # since the cancellation should not leak out of the context manager + if sys.version_info >= (3, 11): + # If the task was already cancelling don't raise + # asyncio.TimeoutError and instead return None + # to allow the cancellation to propagate + if enter_task.uncancel() > self._cancelling: + return None + raise asyncio.TimeoutError from exc_val + return None + + def timeout(self) -> None: + if not self._cancelled: + for task in set(self._tasks): + task.cancel() + + self._cancelled = True + + +def ceil_timeout( + delay: Optional[float], ceil_threshold: float = 5 +) -> async_timeout.Timeout: + if delay is None or delay <= 0: + return async_timeout.timeout(None) + + loop = asyncio.get_running_loop() + now = loop.time() + when = now + delay + if delay > ceil_threshold: + when = ceil(when) + return async_timeout.timeout_at(when) + + +class HeadersMixin: + """Mixin for handling headers.""" + + ATTRS = frozenset(["_content_type", "_content_dict", "_stored_content_type"]) + + _headers: MultiMapping[str] + _content_type: Optional[str] = None + _content_dict: Optional[Dict[str, str]] = None + _stored_content_type: Union[str, None, _SENTINEL] = sentinel + + def _parse_content_type(self, raw: Optional[str]) -> None: + self._stored_content_type = raw + if raw is None: + # default value according to RFC 2616 + self._content_type = "application/octet-stream" + self._content_dict = {} + else: + content_type, content_mapping_proxy = parse_content_type(raw) + self._content_type = content_type + # _content_dict needs to be mutable so we can update it + self._content_dict = content_mapping_proxy.copy() + + @property + def content_type(self) -> str: + """The value of content part for Content-Type HTTP header.""" + raw = self._headers.get(hdrs.CONTENT_TYPE) + if self._stored_content_type != raw: + self._parse_content_type(raw) + assert self._content_type is not None + return self._content_type + + @property + def charset(self) -> Optional[str]: + """The value of charset part for Content-Type HTTP header.""" + raw = self._headers.get(hdrs.CONTENT_TYPE) + if self._stored_content_type != raw: + self._parse_content_type(raw) + assert self._content_dict is not None + return self._content_dict.get("charset") + + @property + def content_length(self) -> Optional[int]: + """The value of Content-Length HTTP header.""" + content_length = self._headers.get(hdrs.CONTENT_LENGTH) + return None if content_length is None else int(content_length) + + +def set_result(fut: "asyncio.Future[_T]", result: _T) -> None: + if not fut.done(): + fut.set_result(result) + + +_EXC_SENTINEL = BaseException() + + +class ErrorableProtocol(Protocol): + def set_exception( + self, + exc: BaseException, + exc_cause: BaseException = ..., + ) -> None: ... # pragma: no cover + + +def set_exception( + fut: "asyncio.Future[_T] | ErrorableProtocol", + exc: BaseException, + exc_cause: BaseException = _EXC_SENTINEL, +) -> None: + """Set future exception. + + If the future is marked as complete, this function is a no-op. + + :param exc_cause: An exception that is a direct cause of ``exc``. + Only set if provided. + """ + if asyncio.isfuture(fut) and fut.done(): + return + + exc_is_sentinel = exc_cause is _EXC_SENTINEL + exc_causes_itself = exc is exc_cause + if not exc_is_sentinel and not exc_causes_itself: + exc.__cause__ = exc_cause + + fut.set_exception(exc) + + +@functools.total_ordering +class AppKey(Generic[_T]): + """Keys for static typing support in Application.""" + + __slots__ = ("_name", "_t", "__orig_class__") + + # This may be set by Python when instantiating with a generic type. We need to + # support this, in order to support types that are not concrete classes, + # like Iterable, which can't be passed as the second parameter to __init__. + __orig_class__: Type[object] + + def __init__(self, name: str, t: Optional[Type[_T]] = None): + # Prefix with module name to help deduplicate key names. + frame = inspect.currentframe() + while frame: + if frame.f_code.co_name == "": + module: str = frame.f_globals["__name__"] + break + frame = frame.f_back + + self._name = module + "." + name + self._t = t + + def __lt__(self, other: object) -> bool: + if isinstance(other, AppKey): + return self._name < other._name + return True # Order AppKey above other types. + + def __repr__(self) -> str: + t = self._t + if t is None: + with suppress(AttributeError): + # Set to type arg. + t = get_args(self.__orig_class__)[0] + + if t is None: + t_repr = "<>" + elif isinstance(t, type): + if t.__module__ == "builtins": + t_repr = t.__qualname__ + else: + t_repr = f"{t.__module__}.{t.__qualname__}" + else: + t_repr = repr(t) + return f"" + + +class ChainMapProxy(Mapping[Union[str, AppKey[Any]], Any]): + __slots__ = ("_maps",) + + def __init__(self, maps: Iterable[Mapping[Union[str, AppKey[Any]], Any]]) -> None: + self._maps = tuple(maps) + + def __init_subclass__(cls) -> None: + raise TypeError( + "Inheritance class {} from ChainMapProxy " + "is forbidden".format(cls.__name__) + ) + + @overload # type: ignore[override] + def __getitem__(self, key: AppKey[_T]) -> _T: ... + + @overload + def __getitem__(self, key: str) -> Any: ... + + def __getitem__(self, key: Union[str, AppKey[_T]]) -> Any: + for mapping in self._maps: + try: + return mapping[key] + except KeyError: + pass + raise KeyError(key) + + @overload # type: ignore[override] + def get(self, key: AppKey[_T], default: _S) -> Union[_T, _S]: ... + + @overload + def get(self, key: AppKey[_T], default: None = ...) -> Optional[_T]: ... + + @overload + def get(self, key: str, default: Any = ...) -> Any: ... + + def get(self, key: Union[str, AppKey[_T]], default: Any = None) -> Any: + try: + return self[key] + except KeyError: + return default + + def __len__(self) -> int: + # reuses stored hash values if possible + return len(set().union(*self._maps)) + + def __iter__(self) -> Iterator[Union[str, AppKey[Any]]]: + d: Dict[Union[str, AppKey[Any]], Any] = {} + for mapping in reversed(self._maps): + # reuses stored hash values if possible + d.update(mapping) + return iter(d) + + def __contains__(self, key: object) -> bool: + return any(key in m for m in self._maps) + + def __bool__(self) -> bool: + return any(self._maps) + + def __repr__(self) -> str: + content = ", ".join(map(repr, self._maps)) + return f"ChainMapProxy({content})" + + +# https://tools.ietf.org/html/rfc7232#section-2.3 +_ETAGC = r"[!\x23-\x7E\x80-\xff]+" +_ETAGC_RE = re.compile(_ETAGC) +_QUOTED_ETAG = rf'(W/)?"({_ETAGC})"' +QUOTED_ETAG_RE = re.compile(_QUOTED_ETAG) +LIST_QUOTED_ETAG_RE = re.compile(rf"({_QUOTED_ETAG})(?:\s*,\s*|$)|(.)") + +ETAG_ANY = "*" + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class ETag: + value: str + is_weak: bool = False + + +def validate_etag_value(value: str) -> None: + if value != ETAG_ANY and not _ETAGC_RE.fullmatch(value): + raise ValueError( + f"Value {value!r} is not a valid etag. Maybe it contains '\"'?" + ) + + +def parse_http_date(date_str: Optional[str]) -> Optional[datetime.datetime]: + """Process a date string, return a datetime object""" + if date_str is not None: + timetuple = parsedate(date_str) + if timetuple is not None: + with suppress(ValueError): + return datetime.datetime(*timetuple[:6], tzinfo=datetime.timezone.utc) + return None + + +@functools.lru_cache +def must_be_empty_body(method: str, code: int) -> bool: + """Check if a request must return an empty body.""" + return ( + code in EMPTY_BODY_STATUS_CODES + or method in EMPTY_BODY_METHODS + or (200 <= code < 300 and method in hdrs.METH_CONNECT_ALL) + ) + + +def should_remove_content_length(method: str, code: int) -> bool: + """Check if a Content-Length header should be removed. + + This should always be a subset of must_be_empty_body + """ + # https://www.rfc-editor.org/rfc/rfc9110.html#section-8.6-8 + # https://www.rfc-editor.org/rfc/rfc9110.html#section-15.4.5-4 + return code in EMPTY_BODY_STATUS_CODES or ( + 200 <= code < 300 and method in hdrs.METH_CONNECT_ALL + ) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/http.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/http.py new file mode 100644 index 0000000000000000000000000000000000000000..a1feae2d9b8fe631d539a15dbf8e5ea2914d70d5 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/http.py @@ -0,0 +1,72 @@ +import sys +from http import HTTPStatus +from typing import Mapping, Tuple + +from . import __version__ +from .http_exceptions import HttpProcessingError as HttpProcessingError +from .http_parser import ( + HeadersParser as HeadersParser, + HttpParser as HttpParser, + HttpRequestParser as HttpRequestParser, + HttpResponseParser as HttpResponseParser, + RawRequestMessage as RawRequestMessage, + RawResponseMessage as RawResponseMessage, +) +from .http_websocket import ( + WS_CLOSED_MESSAGE as WS_CLOSED_MESSAGE, + WS_CLOSING_MESSAGE as WS_CLOSING_MESSAGE, + WS_KEY as WS_KEY, + WebSocketError as WebSocketError, + WebSocketReader as WebSocketReader, + WebSocketWriter as WebSocketWriter, + WSCloseCode as WSCloseCode, + WSMessage as WSMessage, + WSMsgType as WSMsgType, + ws_ext_gen as ws_ext_gen, + ws_ext_parse as ws_ext_parse, +) +from .http_writer import ( + HttpVersion as HttpVersion, + HttpVersion10 as HttpVersion10, + HttpVersion11 as HttpVersion11, + StreamWriter as StreamWriter, +) + +__all__ = ( + "HttpProcessingError", + "RESPONSES", + "SERVER_SOFTWARE", + # .http_writer + "StreamWriter", + "HttpVersion", + "HttpVersion10", + "HttpVersion11", + # .http_parser + "HeadersParser", + "HttpParser", + "HttpRequestParser", + "HttpResponseParser", + "RawRequestMessage", + "RawResponseMessage", + # .http_websocket + "WS_CLOSED_MESSAGE", + "WS_CLOSING_MESSAGE", + "WS_KEY", + "WebSocketReader", + "WebSocketWriter", + "ws_ext_gen", + "ws_ext_parse", + "WSMessage", + "WebSocketError", + "WSMsgType", + "WSCloseCode", +) + + +SERVER_SOFTWARE: str = "Python/{0[0]}.{0[1]} aiohttp/{1}".format( + sys.version_info, __version__ +) + +RESPONSES: Mapping[int, Tuple[str, str]] = { + v: (v.phrase, v.description) for v in HTTPStatus.__members__.values() +} diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/http_exceptions.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/http_exceptions.py new file mode 100644 index 0000000000000000000000000000000000000000..a389e94c0b5401a104b05d1f9b1fff326c4911ba --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/http_exceptions.py @@ -0,0 +1,117 @@ +"""Low-level http related exceptions.""" + +from textwrap import indent +from typing import Optional, Union + +from .typedefs import _CIMultiDict + +__all__ = ("HttpProcessingError",) + + +class HttpProcessingError(Exception): + """HTTP error. + + Shortcut for raising HTTP errors with custom code, message and headers. + + code: HTTP Error code. + message: (optional) Error message. + headers: (optional) Headers to be sent in response, a list of pairs + """ + + code = 0 + message = "" + headers = None + + def __init__( + self, + *, + code: Optional[int] = None, + message: str = "", + headers: Optional[_CIMultiDict] = None, + ) -> None: + if code is not None: + self.code = code + self.headers = headers + self.message = message + + def __str__(self) -> str: + msg = indent(self.message, " ") + return f"{self.code}, message:\n{msg}" + + def __repr__(self) -> str: + return f"<{self.__class__.__name__}: {self.code}, message={self.message!r}>" + + +class BadHttpMessage(HttpProcessingError): + + code = 400 + message = "Bad Request" + + def __init__(self, message: str, *, headers: Optional[_CIMultiDict] = None) -> None: + super().__init__(message=message, headers=headers) + self.args = (message,) + + +class HttpBadRequest(BadHttpMessage): + + code = 400 + message = "Bad Request" + + +class PayloadEncodingError(BadHttpMessage): + """Base class for payload errors""" + + +class ContentEncodingError(PayloadEncodingError): + """Content encoding error.""" + + +class TransferEncodingError(PayloadEncodingError): + """transfer encoding error.""" + + +class ContentLengthError(PayloadEncodingError): + """Not enough data to satisfy content length header.""" + + +class DecompressSizeError(PayloadEncodingError): + """Decompressed size exceeds the configured limit.""" + + +class LineTooLong(BadHttpMessage): + def __init__( + self, + line: Union[str, bytes], + limit: Union[str, int] = "Unknown", + actual_size: str = "Unknown", + ) -> None: + super().__init__(f"Got more than {limit} bytes when reading: {line!r}.") + self.args = (line, limit, actual_size) + + +class InvalidHeader(BadHttpMessage): + def __init__(self, hdr: Union[bytes, str]) -> None: + hdr_s = hdr.decode(errors="backslashreplace") if isinstance(hdr, bytes) else hdr + super().__init__(f"Invalid HTTP header: {hdr!r}") + self.hdr = hdr_s + self.args = (hdr,) + + +class BadStatusLine(BadHttpMessage): + def __init__(self, line: str = "", error: Optional[str] = None) -> None: + if not isinstance(line, str): + line = repr(line) + super().__init__(error or f"Bad status line {line!r}") + self.args = (line,) + self.line = line + + +class BadHttpMethod(BadStatusLine): + """Invalid HTTP method in status line.""" + + def __init__(self, line: str = "", error: Optional[str] = None) -> None: + super().__init__(line, error or f"Bad HTTP method in status line {line!r}") + + +class InvalidURLError(BadHttpMessage): + pass diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/http_parser.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/http_parser.py new file mode 100644 index 0000000000000000000000000000000000000000..6a471cd7996a5aacd6c45cb35644f1f785f5fc7c --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/http_parser.py @@ -0,0 +1,1125 @@ +import abc +import asyncio +import re +import string +from contextlib import suppress +from enum import IntEnum +from typing import ( + Any, + ClassVar, + Final, + Generic, + List, + Literal, + NamedTuple, + Optional, + Pattern, + Set, + Tuple, + Type, + TypeVar, + Union, +) + +from multidict import CIMultiDict, CIMultiDictProxy, istr +from yarl import URL + +from . import hdrs +from .base_protocol import BaseProtocol +from .compression_utils import ( + DEFAULT_MAX_DECOMPRESS_SIZE, + HAS_BROTLI, + HAS_ZSTD, + BrotliDecompressor, + ZLibDecompressor, + ZSTDDecompressor, +) +from .helpers import ( + _EXC_SENTINEL, + DEBUG, + EMPTY_BODY_METHODS, + EMPTY_BODY_STATUS_CODES, + NO_EXTENSIONS, + BaseTimerContext, + set_exception, +) +from .http_exceptions import ( + BadHttpMessage, + BadHttpMethod, + BadStatusLine, + ContentEncodingError, + ContentLengthError, + DecompressSizeError, + InvalidHeader, + InvalidURLError, + LineTooLong, + TransferEncodingError, +) +from .http_writer import HttpVersion, HttpVersion10 +from .streams import EMPTY_PAYLOAD, StreamReader +from .typedefs import RawHeaders + +__all__ = ( + "HeadersParser", + "HttpParser", + "HttpRequestParser", + "HttpResponseParser", + "RawRequestMessage", + "RawResponseMessage", +) + +_SEP = Literal[b"\r\n", b"\n"] + +ASCIISET: Final[Set[str]] = set(string.printable) + +# See https://www.rfc-editor.org/rfc/rfc9110.html#name-overview +# and https://www.rfc-editor.org/rfc/rfc9110.html#name-tokens +# +# method = token +# tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / +# "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA +# token = 1*tchar +_TCHAR_SPECIALS: Final[str] = re.escape("!#$%&'*+-.^_`|~") +TOKENRE: Final[Pattern[str]] = re.compile(f"[0-9A-Za-z{_TCHAR_SPECIALS}]+") +# https://www.rfc-editor.org/rfc/rfc9110#section-5.5-5 +_FIELD_VALUE_FORBIDDEN_CTL_RE: Final[Pattern[str]] = re.compile( + r"[\x00-\x08\x0a-\x1f\x7f]" +) +VERSRE: Final[Pattern[str]] = re.compile(r"HTTP/(\d)\.(\d)", re.ASCII) +DIGITS: Final[Pattern[str]] = re.compile(r"\d+", re.ASCII) +HEXDIGITS: Final[Pattern[bytes]] = re.compile(rb"[0-9a-fA-F]+") + +# RFC 9110 singleton headers — duplicates are rejected in strict mode. +# In lax mode (response parser default), the check is skipped entirely +# since real-world servers (e.g. Google APIs, Werkzeug) commonly send +# duplicate headers like Content-Type or Server. +# Lowercased for case-insensitive matching against wire names. +SINGLETON_HEADERS: Final[frozenset[str]] = frozenset( + { + "content-length", + "content-location", + "content-range", + "content-type", + "etag", + "host", + "max-forwards", + "server", + "transfer-encoding", + "user-agent", + } +) + + +class RawRequestMessage(NamedTuple): + method: str + path: str + version: HttpVersion + headers: "CIMultiDictProxy[str]" + raw_headers: RawHeaders + should_close: bool + compression: Optional[str] + upgrade: bool + chunked: bool + url: URL + + +class RawResponseMessage(NamedTuple): + version: HttpVersion + code: int + reason: str + headers: CIMultiDictProxy[str] + raw_headers: RawHeaders + should_close: bool + compression: Optional[str] + upgrade: bool + chunked: bool + + +_MsgT = TypeVar("_MsgT", RawRequestMessage, RawResponseMessage) + + +class ParseState(IntEnum): + + PARSE_NONE = 0 + PARSE_LENGTH = 1 + PARSE_CHUNKED = 2 + PARSE_UNTIL_EOF = 3 + + +class ChunkState(IntEnum): + PARSE_CHUNKED_SIZE = 0 + PARSE_CHUNKED_CHUNK = 1 + PARSE_CHUNKED_CHUNK_EOF = 2 + PARSE_MAYBE_TRAILERS = 3 + PARSE_TRAILERS = 4 + + +class HeadersParser: + def __init__( + self, + max_line_size: int = 8190, + max_headers: int = 32768, + max_field_size: int = 8190, + lax: bool = False, + ) -> None: + self.max_line_size = max_line_size + self.max_headers = max_headers + self.max_field_size = max_field_size + self._lax = lax + + def parse_headers( + self, lines: List[bytes] + ) -> Tuple["CIMultiDictProxy[str]", RawHeaders]: + headers: CIMultiDict[str] = CIMultiDict() + # note: "raw" does not mean inclusion of OWS before/after the field value + raw_headers = [] + + lines_idx = 0 + line = lines[lines_idx] + line_count = len(lines) + + while line: + # Parse initial header name : value pair. + try: + bname, bvalue = line.split(b":", 1) + except ValueError: + raise InvalidHeader(line) from None + + if len(bname) == 0: + raise InvalidHeader(bname) + + # https://www.rfc-editor.org/rfc/rfc9112.html#section-5.1-2 + if {bname[0], bname[-1]} & {32, 9}: # {" ", "\t"} + raise InvalidHeader(line) + + bvalue = bvalue.lstrip(b" \t") + name = bname.decode("utf-8", "surrogateescape") + if not TOKENRE.fullmatch(name): + raise InvalidHeader(bname) + + # next line + lines_idx += 1 + line = lines[lines_idx] + + # consume continuation lines + continuation = self._lax and line and line[0] in (32, 9) # (' ', '\t') + + # Deprecated: https://www.rfc-editor.org/rfc/rfc9112.html#name-obsolete-line-folding + if continuation: + header_length = len(bvalue) + bvalue_lst = [bvalue] + while continuation: + header_length += len(line) + if header_length > self.max_field_size: + header_line = bname + b": " + b"".join(bvalue_lst) + raise LineTooLong( + header_line[:100] + b"...", self.max_field_size + ) + bvalue_lst.append(line) + + # next line + lines_idx += 1 + if lines_idx < line_count: + line = lines[lines_idx] + if line: + continuation = line[0] in (32, 9) # (' ', '\t') + else: + line = b"" + break + bvalue = b"".join(bvalue_lst) + + bvalue = bvalue.strip(b" \t") + value = bvalue.decode("utf-8", "surrogateescape") + + # https://www.rfc-editor.org/rfc/rfc9110.html#section-5.5-5 + if self._lax: + if "\n" in value or "\r" in value or "\x00" in value: + raise InvalidHeader(bvalue) + elif _FIELD_VALUE_FORBIDDEN_CTL_RE.search(value): + raise InvalidHeader(bvalue) + + if not self._lax and name in headers and name.lower() in SINGLETON_HEADERS: + raise BadHttpMessage(f"Duplicate '{name}' header found.") + headers.add(name, value) + raw_headers.append((bname, bvalue)) + + return (CIMultiDictProxy(headers), tuple(raw_headers)) + + +def _is_supported_upgrade(headers: CIMultiDictProxy[str]) -> bool: + """Check if the upgrade header is supported.""" + u = headers.get(hdrs.UPGRADE, "") + # .lower() can transform non-ascii characters. + return u.isascii() and u.lower() in {"tcp", "websocket"} + + +class HttpParser(abc.ABC, Generic[_MsgT]): + lax: ClassVar[bool] = False + + def __init__( + self, + protocol: Optional[BaseProtocol] = None, + loop: Optional[asyncio.AbstractEventLoop] = None, + limit: int = 2**16, + max_line_size: int = 8190, + max_headers: int = 128, + max_field_size: int = 8190, + timer: Optional[BaseTimerContext] = None, + code: Optional[int] = None, + method: Optional[str] = None, + payload_exception: Optional[Type[BaseException]] = None, + response_with_body: bool = True, + read_until_eof: bool = False, + auto_decompress: bool = True, + ) -> None: + self.protocol = protocol + self.loop = loop + self.max_line_size = max_line_size + self.max_headers = max_headers + self.max_field_size = max_field_size + self.max_headers = max_headers + self.timer = timer + self.code = code + self.method = method + self.payload_exception = payload_exception + self.response_with_body = response_with_body + self.read_until_eof = read_until_eof + + self._lines: List[bytes] = [] + self._tail = b"" + self._upgraded = False + self._payload = None + self._payload_parser: Optional[HttpPayloadParser] = None + self._auto_decompress = auto_decompress + self._limit = limit + self._headers_parser = HeadersParser( + max_line_size, max_headers, max_field_size, self.lax + ) + + @abc.abstractmethod + def parse_message(self, lines: List[bytes]) -> _MsgT: ... + + @abc.abstractmethod + def _is_chunked_te(self, te: str) -> bool: ... + + def feed_eof(self) -> Optional[_MsgT]: + if self._payload_parser is not None: + self._payload_parser.feed_eof() + self._payload_parser = None + else: + # try to extract partial message + if self._tail: + self._lines.append(self._tail) + + if self._lines: + if self._lines[-1] != "\r\n": + self._lines.append(b"") + with suppress(Exception): + return self.parse_message(self._lines) + return None + + def feed_data( + self, + data: bytes, + SEP: _SEP = b"\r\n", + EMPTY: bytes = b"", + CONTENT_LENGTH: istr = hdrs.CONTENT_LENGTH, + METH_CONNECT: str = hdrs.METH_CONNECT, + SEC_WEBSOCKET_KEY1: istr = hdrs.SEC_WEBSOCKET_KEY1, + ) -> Tuple[List[Tuple[_MsgT, StreamReader]], bool, bytes]: + + messages = [] + + if self._tail: + data, self._tail = self._tail + data, b"" + + data_len = len(data) + start_pos = 0 + loop = self.loop + max_line_length = self.max_line_size + + should_close = False + while start_pos < data_len: + + # read HTTP message (request/response line + headers), \r\n\r\n + # and split by lines + if self._payload_parser is None and not self._upgraded: + pos = data.find(SEP, start_pos) + # consume \r\n + if pos == start_pos and not self._lines: + start_pos = pos + len(SEP) + continue + + if pos >= start_pos: + if should_close: + raise BadHttpMessage("Data after `Connection: close`") + + # line found + line = data[start_pos:pos] + if SEP == b"\n": # For lax response parsing + line = line.rstrip(b"\r") + if len(line) > max_line_length: + raise LineTooLong(line[:100] + b"...", max_line_length) + + self._lines.append(line) + # After processing the status/request line, everything is a header. + max_line_length = self.max_field_size + + if len(self._lines) > self.max_headers: + raise BadHttpMessage("Too many headers received") + + start_pos = pos + len(SEP) + + # \r\n\r\n found + if self._lines[-1] == EMPTY: + max_trailers = self.max_headers - len(self._lines) + try: + msg: _MsgT = self.parse_message(self._lines) + finally: + self._lines.clear() + + def get_content_length() -> Optional[int]: + # payload length + length_hdr = msg.headers.get(CONTENT_LENGTH) + if length_hdr is None: + return None + + # Shouldn't allow +/- or other number formats. + # https://www.rfc-editor.org/rfc/rfc9110#section-8.6-2 + # msg.headers is already stripped of leading/trailing wsp + if not DIGITS.fullmatch(length_hdr): + raise InvalidHeader(CONTENT_LENGTH) + + return int(length_hdr) + + length = get_content_length() + # do not support old websocket spec + if SEC_WEBSOCKET_KEY1 in msg.headers: + raise InvalidHeader(SEC_WEBSOCKET_KEY1) + + self._upgraded = msg.upgrade and _is_supported_upgrade( + msg.headers + ) + + method = getattr(msg, "method", self.method) + # code is only present on responses + code = getattr(msg, "code", 0) + + assert self.protocol is not None + # calculate payload + empty_body = code in EMPTY_BODY_STATUS_CODES or bool( + method and method in EMPTY_BODY_METHODS + ) + if not empty_body and ( + ((length is not None and length > 0) or msg.chunked) + and not self._upgraded + ): + payload = StreamReader( + self.protocol, + timer=self.timer, + loop=loop, + limit=self._limit, + ) + payload_parser = HttpPayloadParser( + payload, + length=length, + chunked=msg.chunked, + method=method, + compression=msg.compression, + code=self.code, + response_with_body=self.response_with_body, + auto_decompress=self._auto_decompress, + lax=self.lax, + headers_parser=self._headers_parser, + max_line_size=self.max_line_size, + max_field_size=self.max_field_size, + max_trailers=max_trailers, + ) + if not payload_parser.done: + self._payload_parser = payload_parser + elif method == METH_CONNECT: + assert isinstance(msg, RawRequestMessage) + payload = StreamReader( + self.protocol, + timer=self.timer, + loop=loop, + limit=self._limit, + ) + self._upgraded = True + self._payload_parser = HttpPayloadParser( + payload, + method=msg.method, + compression=msg.compression, + auto_decompress=self._auto_decompress, + lax=self.lax, + headers_parser=self._headers_parser, + max_line_size=self.max_line_size, + max_field_size=self.max_field_size, + max_trailers=max_trailers, + ) + elif not empty_body and length is None and self.read_until_eof: + payload = StreamReader( + self.protocol, + timer=self.timer, + loop=loop, + limit=self._limit, + ) + payload_parser = HttpPayloadParser( + payload, + length=length, + chunked=msg.chunked, + method=method, + compression=msg.compression, + code=self.code, + response_with_body=self.response_with_body, + auto_decompress=self._auto_decompress, + lax=self.lax, + headers_parser=self._headers_parser, + max_line_size=self.max_line_size, + max_field_size=self.max_field_size, + max_trailers=max_trailers, + ) + if not payload_parser.done: + self._payload_parser = payload_parser + else: + payload = EMPTY_PAYLOAD + + messages.append((msg, payload)) + should_close = msg.should_close + else: + self._tail = data[start_pos:] + if len(self._tail) > self.max_line_size: + raise LineTooLong(self._tail[:100] + b"...", self.max_line_size) + data = EMPTY + break + + # no parser, just store + elif self._payload_parser is None and self._upgraded: + assert not self._lines + break + + # feed payload + elif data and start_pos < data_len: + assert not self._lines + assert self._payload_parser is not None + try: + eof, data = self._payload_parser.feed_data(data[start_pos:], SEP) + except BaseException as underlying_exc: + reraised_exc = underlying_exc + if self.payload_exception is not None: + reraised_exc = self.payload_exception(str(underlying_exc)) + + set_exception( + self._payload_parser.payload, + reraised_exc, + underlying_exc, + ) + + eof = True + data = b"" + if isinstance( + underlying_exc, (InvalidHeader, TransferEncodingError) + ): + raise + + if eof: + start_pos = 0 + data_len = len(data) + self._payload_parser = None + continue + else: + break + + if data and start_pos < data_len: + data = data[start_pos:] + else: + data = EMPTY + + return messages, self._upgraded, data + + def parse_headers( + self, lines: List[bytes] + ) -> Tuple[ + "CIMultiDictProxy[str]", RawHeaders, Optional[bool], Optional[str], bool, bool + ]: + """Parses RFC 5322 headers from a stream. + + Line continuations are supported. Returns list of header name + and value pairs. Header name is in upper case. + """ + headers, raw_headers = self._headers_parser.parse_headers(lines) + close_conn = None + encoding = None + upgrade = False + chunked = False + + # keep-alive and protocol switching + # RFC 9110 section 7.6.1 defines Connection as a comma-separated list. + conn_values = headers.getall(hdrs.CONNECTION, ()) + if conn_values: + conn_tokens = { + token.lower() + for conn_value in conn_values + for token in (part.strip(" \t") for part in conn_value.split(",")) + if token and token.isascii() + } + + if "close" in conn_tokens: + close_conn = True + elif "keep-alive" in conn_tokens: + close_conn = False + + # https://www.rfc-editor.org/rfc/rfc9110.html#name-101-switching-protocols + if "upgrade" in conn_tokens and headers.get(hdrs.UPGRADE): + upgrade = True + + # encoding + enc = headers.get(hdrs.CONTENT_ENCODING, "") + if enc.isascii() and enc.lower() in {"gzip", "deflate", "br", "zstd"}: + encoding = enc + + # chunking + te = headers.get(hdrs.TRANSFER_ENCODING) + if te is not None: + if self._is_chunked_te(te): + chunked = True + + if hdrs.CONTENT_LENGTH in headers: + raise BadHttpMessage( + "Transfer-Encoding can't be present with Content-Length", + ) + + return (headers, raw_headers, close_conn, encoding, upgrade, chunked) + + def set_upgraded(self, val: bool) -> None: + """Set connection upgraded (to websocket) mode. + + :param bool val: new state. + """ + self._upgraded = val + + +class HttpRequestParser(HttpParser[RawRequestMessage]): + """Read request status line. + + Exception .http_exceptions.BadStatusLine + could be raised in case of any errors in status line. + Returns RawRequestMessage. + """ + + def parse_message(self, lines: List[bytes]) -> RawRequestMessage: + # request line + line = lines[0].decode("utf-8", "surrogateescape") + try: + method, path, version = line.split(" ", maxsplit=2) + except ValueError: + raise BadHttpMethod(line) from None + + # method + if not TOKENRE.fullmatch(method): + raise BadHttpMethod(method) + + # version + match = VERSRE.fullmatch(version) + if match is None: + raise BadStatusLine(line) + version_o = HttpVersion(int(match.group(1)), int(match.group(2))) + + if method == "CONNECT": + # authority-form, + # https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.3 + url = URL.build(authority=path, encoded=True) + elif path.startswith("/"): + # origin-form, + # https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.1 + path_part, _hash_separator, url_fragment = path.partition("#") + path_part, _question_mark_separator, qs_part = path_part.partition("?") + + # NOTE: `yarl.URL.build()` is used to mimic what the Cython-based + # NOTE: parser does, otherwise it results into the same + # NOTE: HTTP Request-Line input producing different + # NOTE: `yarl.URL()` objects + url = URL.build( + path=path_part, + query_string=qs_part, + fragment=url_fragment, + encoded=True, + ) + elif path == "*" and method == "OPTIONS": + # asterisk-form, + url = URL(path, encoded=True) + else: + # absolute-form for proxy maybe, + # https://datatracker.ietf.org/doc/html/rfc7230#section-5.3.2 + url = URL(path, encoded=True) + if url.scheme == "": + # not absolute-form + raise InvalidURLError( + path.encode(errors="surrogateescape").decode("latin1") + ) + + # read headers + ( + headers, + raw_headers, + close, + compression, + upgrade, + chunked, + ) = self.parse_headers(lines[1:]) + + if close is None: # then the headers weren't set in the request + if version_o <= HttpVersion10: # HTTP 1.0 must asks to not close + close = True + else: # HTTP 1.1 must ask to close. + close = False + + return RawRequestMessage( + method, + path, + version_o, + headers, + raw_headers, + close, + compression, + upgrade, + chunked, + url, + ) + + def _is_chunked_te(self, te: str) -> bool: + te = te.rsplit(",", maxsplit=1)[-1].strip(" \t") + # .lower() transforms some non-ascii chars, so must check first. + if te.isascii() and te.lower() == "chunked": + return True + # https://www.rfc-editor.org/rfc/rfc9112#section-6.3-2.4.3 + raise BadHttpMessage("Request has invalid `Transfer-Encoding`") + + +class HttpResponseParser(HttpParser[RawResponseMessage]): + """Read response status line and headers. + + BadStatusLine could be raised in case of any errors in status line. + Returns RawResponseMessage. + """ + + # Lax mode should only be enabled on response parser. + lax = not DEBUG + + def feed_data( + self, + data: bytes, + SEP: Optional[_SEP] = None, + *args: Any, + **kwargs: Any, + ) -> Tuple[List[Tuple[RawResponseMessage, StreamReader]], bool, bytes]: + if SEP is None: + SEP = b"\r\n" if DEBUG else b"\n" + return super().feed_data(data, SEP, *args, **kwargs) + + def parse_message(self, lines: List[bytes]) -> RawResponseMessage: + line = lines[0].decode("utf-8", "surrogateescape") + try: + version, status = line.split(maxsplit=1) + except ValueError: + raise BadStatusLine(line) from None + + try: + status, reason = status.split(maxsplit=1) + except ValueError: + status = status.strip() + reason = "" + + # version + match = VERSRE.fullmatch(version) + if match is None: + raise BadStatusLine(line) + version_o = HttpVersion(int(match.group(1)), int(match.group(2))) + + # The status code is a three-digit ASCII number, no padding + if len(status) != 3 or not DIGITS.fullmatch(status): + raise BadStatusLine(line) + status_i = int(status) + + # read headers + ( + headers, + raw_headers, + close, + compression, + upgrade, + chunked, + ) = self.parse_headers(lines[1:]) + + if close is None: + if version_o <= HttpVersion10: + close = True + # https://www.rfc-editor.org/rfc/rfc9112.html#name-message-body-length + elif 100 <= status_i < 200 or status_i in {204, 304}: + close = False + elif hdrs.CONTENT_LENGTH in headers or hdrs.TRANSFER_ENCODING in headers: + close = False + else: + # https://www.rfc-editor.org/rfc/rfc9112.html#section-6.3-2.8 + close = True + + return RawResponseMessage( + version_o, + status_i, + reason.strip(), + headers, + raw_headers, + close, + compression, + upgrade, + chunked, + ) + + def _is_chunked_te(self, te: str) -> bool: + # https://www.rfc-editor.org/rfc/rfc9112#section-6.3-2.4.2 + return te.rsplit(",", maxsplit=1)[-1].strip(" \t").lower() == "chunked" + + +class HttpPayloadParser: + def __init__( + self, + payload: StreamReader, + length: Optional[int] = None, + chunked: bool = False, + compression: Optional[str] = None, + code: Optional[int] = None, + method: Optional[str] = None, + response_with_body: bool = True, + auto_decompress: bool = True, + lax: bool = False, + *, + headers_parser: HeadersParser, + max_line_size: int = 8190, + max_field_size: int = 8190, + max_trailers: int = 128, + ) -> None: + self._length = 0 + self._type = ParseState.PARSE_UNTIL_EOF + self._chunk = ChunkState.PARSE_CHUNKED_SIZE + self._chunk_size = 0 + self._chunk_tail = b"" + self._auto_decompress = auto_decompress + self._lax = lax + self._headers_parser = headers_parser + self._max_line_size = max_line_size + self._max_field_size = max_field_size + self._max_trailers = max_trailers + self._trailer_lines: list[bytes] = [] + self.done = False + + # payload decompression wrapper + if response_with_body and compression and self._auto_decompress: + real_payload: Union[StreamReader, DeflateBuffer] = DeflateBuffer( + payload, compression + ) + else: + real_payload = payload + + # payload parser + if not response_with_body: + # don't parse payload if it's not expected to be received + self._type = ParseState.PARSE_NONE + real_payload.feed_eof() + self.done = True + elif chunked: + self._type = ParseState.PARSE_CHUNKED + elif length is not None: + self._type = ParseState.PARSE_LENGTH + self._length = length + if self._length == 0: + real_payload.feed_eof() + self.done = True + + self.payload = real_payload + + def feed_eof(self) -> None: + if self._type == ParseState.PARSE_UNTIL_EOF: + self.payload.feed_eof() + elif self._type == ParseState.PARSE_LENGTH: + raise ContentLengthError( + "Not enough data to satisfy content length header." + ) + elif self._type == ParseState.PARSE_CHUNKED: + raise TransferEncodingError( + "Not enough data to satisfy transfer length header." + ) + + def feed_data( + self, chunk: bytes, SEP: _SEP = b"\r\n", CHUNK_EXT: bytes = b";" + ) -> Tuple[bool, bytes]: + # Read specified amount of bytes + if self._type == ParseState.PARSE_LENGTH: + required = self._length + chunk_len = len(chunk) + + if required >= chunk_len: + self._length = required - chunk_len + self.payload.feed_data(chunk, chunk_len) + if self._length == 0: + self.payload.feed_eof() + return True, b"" + else: + self._length = 0 + self.payload.feed_data(chunk[:required], required) + self.payload.feed_eof() + return True, chunk[required:] + + # Chunked transfer encoding parser + elif self._type == ParseState.PARSE_CHUNKED: + if self._chunk_tail: + # We should never have a tail if we're inside the payload body. + assert self._chunk != ChunkState.PARSE_CHUNKED_CHUNK + # We should check the length is sane. + max_line_length = self._max_line_size + if self._chunk == ChunkState.PARSE_TRAILERS: + max_line_length = self._max_field_size + if len(self._chunk_tail) > max_line_length: + raise LineTooLong(self._chunk_tail[:100] + b"...", max_line_length) + + chunk = self._chunk_tail + chunk + self._chunk_tail = b"" + + while chunk: + + # read next chunk size + if self._chunk == ChunkState.PARSE_CHUNKED_SIZE: + pos = chunk.find(SEP) + if pos >= 0: + i = chunk.find(CHUNK_EXT, 0, pos) + if i >= 0: + size_b = chunk[:i] # strip chunk-extensions + # Verify no LF in the chunk-extension + if b"\n" in (ext := chunk[i:pos]): + exc = TransferEncodingError( + f"Unexpected LF in chunk-extension: {ext!r}" + ) + set_exception(self.payload, exc) + raise exc + else: + size_b = chunk[:pos] + + if self._lax: # Allow whitespace in lax mode. + size_b = size_b.strip() + + if not re.fullmatch(HEXDIGITS, size_b): + exc = TransferEncodingError( + chunk[:pos].decode("ascii", "surrogateescape") + ) + set_exception(self.payload, exc) + raise exc + size = int(bytes(size_b), 16) + + chunk = chunk[pos + len(SEP) :] + if size == 0: # eof marker + self._chunk = ChunkState.PARSE_TRAILERS + if self._lax and chunk.startswith(b"\r"): + chunk = chunk[1:] + else: + self._chunk = ChunkState.PARSE_CHUNKED_CHUNK + self._chunk_size = size + self.payload.begin_http_chunk_receiving() + else: + self._chunk_tail = chunk + return False, b"" + + # read chunk and feed buffer + if self._chunk == ChunkState.PARSE_CHUNKED_CHUNK: + required = self._chunk_size + chunk_len = len(chunk) + + if required > chunk_len: + self._chunk_size = required - chunk_len + self.payload.feed_data(chunk, chunk_len) + return False, b"" + else: + self._chunk_size = 0 + self.payload.feed_data(chunk[:required], required) + chunk = chunk[required:] + self._chunk = ChunkState.PARSE_CHUNKED_CHUNK_EOF + self.payload.end_http_chunk_receiving() + + # toss the CRLF at the end of the chunk + if self._chunk == ChunkState.PARSE_CHUNKED_CHUNK_EOF: + if self._lax and chunk.startswith(b"\r"): + chunk = chunk[1:] + if chunk[: len(SEP)] == SEP: + chunk = chunk[len(SEP) :] + self._chunk = ChunkState.PARSE_CHUNKED_SIZE + elif len(chunk) >= len(SEP) or chunk != SEP[: len(chunk)]: + exc = TransferEncodingError( + "Chunk size mismatch: expected CRLF after chunk data" + ) + set_exception(self.payload, exc) + raise exc + else: + self._chunk_tail = chunk + return False, b"" + + if self._chunk == ChunkState.PARSE_TRAILERS: + pos = chunk.find(SEP) + if pos < 0: # No line found + self._chunk_tail = chunk + return False, b"" + + line = chunk[:pos] + chunk = chunk[pos + len(SEP) :] + if SEP == b"\n": # For lax response parsing + line = line.rstrip(b"\r") + + if len(line) > self._max_field_size: + raise LineTooLong(line[:100] + b"...", self._max_field_size) + + self._trailer_lines.append(line) + + if len(self._trailer_lines) > self._max_trailers: + raise BadHttpMessage("Too many trailers received") + + # \r\n\r\n found, end of stream + if self._trailer_lines[-1] == b"": + # Headers and trailers are defined the same way, + # so we reuse the HeadersParser here. + try: + trailers, raw_trailers = self._headers_parser.parse_headers( + self._trailer_lines + ) + finally: + self._trailer_lines.clear() + self.payload.feed_eof() + return True, chunk + + # Read all bytes until eof + elif self._type == ParseState.PARSE_UNTIL_EOF: + self.payload.feed_data(chunk, len(chunk)) + + return False, b"" + + +class DeflateBuffer: + """DeflateStream decompress stream and feed data into specified stream.""" + + decompressor: Any + + def __init__( + self, + out: StreamReader, + encoding: Optional[str], + max_decompress_size: int = DEFAULT_MAX_DECOMPRESS_SIZE, + ) -> None: + self.out = out + self.size = 0 + out.total_compressed_bytes = self.size + self.encoding = encoding + self._started_decoding = False + + self.decompressor: Union[BrotliDecompressor, ZLibDecompressor, ZSTDDecompressor] + if encoding == "br": + if not HAS_BROTLI: # pragma: no cover + raise ContentEncodingError( + "Can not decode content-encoding: brotli (br). " + "Please install `Brotli`" + ) + self.decompressor = BrotliDecompressor() + elif encoding == "zstd": + if not HAS_ZSTD: + raise ContentEncodingError( + "Can not decode content-encoding: zstandard (zstd). " + "Please install `backports.zstd`" + ) + self.decompressor = ZSTDDecompressor() + else: + self.decompressor = ZLibDecompressor(encoding=encoding) + + self._max_decompress_size = max_decompress_size + + def set_exception( + self, + exc: BaseException, + exc_cause: BaseException = _EXC_SENTINEL, + ) -> None: + set_exception(self.out, exc, exc_cause) + + def feed_data(self, chunk: bytes, size: int) -> None: + if not size: + return + + self.size += size + self.out.total_compressed_bytes = self.size + + # RFC1950 + # bits 0..3 = CM = 0b1000 = 8 = "deflate" + # bits 4..7 = CINFO = 1..7 = windows size. + if ( + not self._started_decoding + and self.encoding == "deflate" + and chunk[0] & 0xF != 8 + ): + # Change the decoder to decompress incorrectly compressed data + # Actually we should issue a warning about non-RFC-compliant data. + self.decompressor = ZLibDecompressor( + encoding=self.encoding, suppress_deflate_header=True + ) + + try: + # Decompress with limit + 1 so we can detect if output exceeds limit + chunk = self.decompressor.decompress_sync( + chunk, max_length=self._max_decompress_size + 1 + ) + except Exception: + raise ContentEncodingError( + "Can not decode content-encoding: %s" % self.encoding + ) + + self._started_decoding = True + + # Check if decompression limit was exceeded + if len(chunk) > self._max_decompress_size: + raise DecompressSizeError( + "Decompressed data exceeds the configured limit of %d bytes" + % self._max_decompress_size + ) + + if chunk: + self.out.feed_data(chunk, len(chunk)) + + def feed_eof(self) -> None: + chunk = self.decompressor.flush() + + if chunk or self.size > 0: + self.out.feed_data(chunk, len(chunk)) + if self.encoding == "deflate" and not self.decompressor.eof: + raise ContentEncodingError("deflate") + + self.out.feed_eof() + + def begin_http_chunk_receiving(self) -> None: + self.out.begin_http_chunk_receiving() + + def end_http_chunk_receiving(self) -> None: + self.out.end_http_chunk_receiving() + + +HttpRequestParserPy = HttpRequestParser +HttpResponseParserPy = HttpResponseParser +RawRequestMessagePy = RawRequestMessage +RawResponseMessagePy = RawResponseMessage + +try: + if not NO_EXTENSIONS: + from ._http_parser import ( # type: ignore[import-not-found,no-redef] + HttpRequestParser, + HttpResponseParser, + RawRequestMessage, + RawResponseMessage, + ) + + HttpRequestParserC = HttpRequestParser + HttpResponseParserC = HttpResponseParser + RawRequestMessageC = RawRequestMessage + RawResponseMessageC = RawResponseMessage +except ImportError: # pragma: no cover + pass diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/http_websocket.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/http_websocket.py new file mode 100644 index 0000000000000000000000000000000000000000..6b4b30e02b247e30e0c84d3eb118b749bbe52079 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/http_websocket.py @@ -0,0 +1,36 @@ +"""WebSocket protocol versions 13 and 8.""" + +from ._websocket.helpers import WS_KEY, ws_ext_gen, ws_ext_parse +from ._websocket.models import ( + WS_CLOSED_MESSAGE, + WS_CLOSING_MESSAGE, + WebSocketError, + WSCloseCode, + WSHandshakeError, + WSMessage, + WSMsgType, +) +from ._websocket.reader import WebSocketReader +from ._websocket.writer import WebSocketWriter + +# Messages that the WebSocketResponse.receive needs to handle internally +_INTERNAL_RECEIVE_TYPES = frozenset( + (WSMsgType.CLOSE, WSMsgType.CLOSING, WSMsgType.PING, WSMsgType.PONG) +) + + +__all__ = ( + "WS_CLOSED_MESSAGE", + "WS_CLOSING_MESSAGE", + "WS_KEY", + "WebSocketReader", + "WebSocketWriter", + "WSMessage", + "WebSocketError", + "WSMsgType", + "WSCloseCode", + "ws_ext_gen", + "ws_ext_parse", + "WSHandshakeError", + "WSMessage", +) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/http_writer.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/http_writer.py new file mode 100644 index 0000000000000000000000000000000000000000..8c1c910d741fec861645661cd5859948cbdf104b --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/http_writer.py @@ -0,0 +1,379 @@ +"""Http related parsers and protocol.""" + +import asyncio +import sys +from typing import ( # noqa + TYPE_CHECKING, + Any, + Awaitable, + Callable, + Iterable, + List, + NamedTuple, + Optional, + Union, +) + +from multidict import CIMultiDict + +from .abc import AbstractStreamWriter +from .base_protocol import BaseProtocol +from .client_exceptions import ClientConnectionResetError +from .compression_utils import ZLibCompressor +from .helpers import NO_EXTENSIONS + +__all__ = ("StreamWriter", "HttpVersion", "HttpVersion10", "HttpVersion11") + + +MIN_PAYLOAD_FOR_WRITELINES = 2048 +IS_PY313_BEFORE_313_2 = (3, 13, 0) <= sys.version_info < (3, 13, 2) +IS_PY_BEFORE_312_9 = sys.version_info < (3, 12, 9) +SKIP_WRITELINES = IS_PY313_BEFORE_313_2 or IS_PY_BEFORE_312_9 +# writelines is not safe for use +# on Python 3.12+ until 3.12.9 +# on Python 3.13+ until 3.13.2 +# and on older versions it not any faster than write +# CVE-2024-12254: https://github.com/python/cpython/pull/127656 + + +class HttpVersion(NamedTuple): + major: int + minor: int + + +HttpVersion10 = HttpVersion(1, 0) +HttpVersion11 = HttpVersion(1, 1) + + +_T_OnChunkSent = Optional[Callable[[bytes], Awaitable[None]]] +_T_OnHeadersSent = Optional[Callable[["CIMultiDict[str]"], Awaitable[None]]] + + +class StreamWriter(AbstractStreamWriter): + + length: Optional[int] = None + chunked: bool = False + _eof: bool = False + _compress: Optional[ZLibCompressor] = None + + def __init__( + self, + protocol: BaseProtocol, + loop: asyncio.AbstractEventLoop, + on_chunk_sent: _T_OnChunkSent = None, + on_headers_sent: _T_OnHeadersSent = None, + ) -> None: + self._protocol = protocol + self.loop = loop + self._on_chunk_sent: _T_OnChunkSent = on_chunk_sent + self._on_headers_sent: _T_OnHeadersSent = on_headers_sent + self._headers_buf: Optional[bytes] = None + self._headers_written: bool = False + + @property + def transport(self) -> Optional[asyncio.Transport]: + return self._protocol.transport + + @property + def protocol(self) -> BaseProtocol: + return self._protocol + + def enable_chunking(self) -> None: + self.chunked = True + + def enable_compression( + self, encoding: str = "deflate", strategy: Optional[int] = None + ) -> None: + self._compress = ZLibCompressor(encoding=encoding, strategy=strategy) + + def _write(self, chunk: Union[bytes, bytearray, memoryview]) -> None: + size = len(chunk) + self.buffer_size += size + self.output_size += size + transport = self._protocol.transport + if transport is None or transport.is_closing(): + raise ClientConnectionResetError("Cannot write to closing transport") + transport.write(chunk) + + def _writelines(self, chunks: Iterable[bytes]) -> None: + size = 0 + for chunk in chunks: + size += len(chunk) + self.buffer_size += size + self.output_size += size + transport = self._protocol.transport + if transport is None or transport.is_closing(): + raise ClientConnectionResetError("Cannot write to closing transport") + if SKIP_WRITELINES or size < MIN_PAYLOAD_FOR_WRITELINES: + transport.write(b"".join(chunks)) + else: + transport.writelines(chunks) + + def _write_chunked_payload( + self, chunk: Union[bytes, bytearray, "memoryview[int]", "memoryview[bytes]"] + ) -> None: + """Write a chunk with proper chunked encoding.""" + chunk_len_pre = f"{len(chunk):x}\r\n".encode("ascii") + self._writelines((chunk_len_pre, chunk, b"\r\n")) + + def _send_headers_with_payload( + self, + chunk: Union[bytes, bytearray, "memoryview[int]", "memoryview[bytes]"], + is_eof: bool, + ) -> None: + """Send buffered headers with payload, coalescing into single write.""" + # Mark headers as written + self._headers_written = True + headers_buf = self._headers_buf + self._headers_buf = None + + if TYPE_CHECKING: + # Safe because callers (write() and write_eof()) only invoke this method + # after checking that self._headers_buf is truthy + assert headers_buf is not None + + if not self.chunked: + # Non-chunked: coalesce headers with body + if chunk: + self._writelines((headers_buf, chunk)) + else: + self._write(headers_buf) + return + + # Coalesce headers with chunked data + if chunk: + chunk_len_pre = f"{len(chunk):x}\r\n".encode("ascii") + if is_eof: + self._writelines((headers_buf, chunk_len_pre, chunk, b"\r\n0\r\n\r\n")) + else: + self._writelines((headers_buf, chunk_len_pre, chunk, b"\r\n")) + elif is_eof: + self._writelines((headers_buf, b"0\r\n\r\n")) + else: + self._write(headers_buf) + + async def write( + self, + chunk: Union[bytes, bytearray, memoryview], + *, + drain: bool = True, + LIMIT: int = 0x10000, + ) -> None: + """ + Writes chunk of data to a stream. + + write_eof() indicates end of stream. + writer can't be used after write_eof() method being called. + write() return drain future. + """ + if self._on_chunk_sent is not None: + await self._on_chunk_sent(chunk) + + if isinstance(chunk, memoryview): + if chunk.nbytes != len(chunk): + # just reshape it + chunk = chunk.cast("c") + + if self._compress is not None: + chunk = await self._compress.compress(chunk) + if not chunk: + return + + if self.length is not None: + chunk_len = len(chunk) + if self.length >= chunk_len: + self.length = self.length - chunk_len + else: + chunk = chunk[: self.length] + self.length = 0 + if not chunk: + return + + # Handle buffered headers for small payload optimization + if self._headers_buf and not self._headers_written: + self._send_headers_with_payload(chunk, False) + if drain and self.buffer_size > LIMIT: + self.buffer_size = 0 + await self.drain() + return + + if chunk: + if self.chunked: + self._write_chunked_payload(chunk) + else: + self._write(chunk) + + if drain and self.buffer_size > LIMIT: + self.buffer_size = 0 + await self.drain() + + async def write_headers( + self, status_line: str, headers: "CIMultiDict[str]" + ) -> None: + """Write headers to the stream.""" + if self._on_headers_sent is not None: + await self._on_headers_sent(headers) + # status + headers + buf = _serialize_headers(status_line, headers) + self._headers_written = False + self._headers_buf = buf + + def send_headers(self) -> None: + """Force sending buffered headers if not already sent.""" + if not self._headers_buf or self._headers_written: + return + + self._headers_written = True + headers_buf = self._headers_buf + self._headers_buf = None + + if TYPE_CHECKING: + # Safe because we only enter this block when self._headers_buf is truthy + assert headers_buf is not None + + self._write(headers_buf) + + def set_eof(self) -> None: + """Indicate that the message is complete.""" + if self._eof: + return + + # If headers haven't been sent yet, send them now + # This handles the case where there's no body at all + if self._headers_buf and not self._headers_written: + self._headers_written = True + headers_buf = self._headers_buf + self._headers_buf = None + + if TYPE_CHECKING: + # Safe because we only enter this block when self._headers_buf is truthy + assert headers_buf is not None + + # Combine headers and chunked EOF marker in a single write + if self.chunked: + self._writelines((headers_buf, b"0\r\n\r\n")) + else: + self._write(headers_buf) + elif self.chunked and self._headers_written: + # Headers already sent, just send the final chunk marker + self._write(b"0\r\n\r\n") + + self._eof = True + + async def write_eof(self, chunk: bytes = b"") -> None: + if self._eof: + return + + if chunk and self._on_chunk_sent is not None: + await self._on_chunk_sent(chunk) + + # Handle body/compression + if self._compress: + chunks: List[bytes] = [] + chunks_len = 0 + if chunk and (compressed_chunk := await self._compress.compress(chunk)): + chunks_len = len(compressed_chunk) + chunks.append(compressed_chunk) + + flush_chunk = self._compress.flush() + chunks_len += len(flush_chunk) + chunks.append(flush_chunk) + assert chunks_len + + # Send buffered headers with compressed data if not yet sent + if self._headers_buf and not self._headers_written: + self._headers_written = True + headers_buf = self._headers_buf + self._headers_buf = None + + if self.chunked: + # Coalesce headers with compressed chunked data + chunk_len_pre = f"{chunks_len:x}\r\n".encode("ascii") + self._writelines( + (headers_buf, chunk_len_pre, *chunks, b"\r\n0\r\n\r\n") + ) + else: + # Coalesce headers with compressed data + self._writelines((headers_buf, *chunks)) + await self.drain() + self._eof = True + return + + # Headers already sent, just write compressed data + if self.chunked: + chunk_len_pre = f"{chunks_len:x}\r\n".encode("ascii") + self._writelines((chunk_len_pre, *chunks, b"\r\n0\r\n\r\n")) + elif len(chunks) > 1: + self._writelines(chunks) + else: + self._write(chunks[0]) + await self.drain() + self._eof = True + return + + # No compression - send buffered headers if not yet sent + if self._headers_buf and not self._headers_written: + # Use helper to send headers with payload + self._send_headers_with_payload(chunk, True) + await self.drain() + self._eof = True + return + + # Handle remaining body + if self.chunked: + if chunk: + # Write final chunk with EOF marker + self._writelines( + (f"{len(chunk):x}\r\n".encode("ascii"), chunk, b"\r\n0\r\n\r\n") + ) + else: + self._write(b"0\r\n\r\n") + await self.drain() + self._eof = True + return + + if chunk: + self._write(chunk) + await self.drain() + + self._eof = True + + async def drain(self) -> None: + """Flush the write buffer. + + The intended use is to write + + await w.write(data) + await w.drain() + """ + protocol = self._protocol + if protocol.transport is not None and protocol._paused: + await protocol._drain_helper() + + +def _safe_header(string: str) -> str: + if "\r" in string or "\n" in string or "\x00" in string: + raise ValueError( + "Newline, carriage return, or null byte detected in headers. " + "Potential header injection attack." + ) + return string + + +def _py_serialize_headers(status_line: str, headers: "CIMultiDict[str]") -> bytes: + _safe_header(status_line) + headers_gen = (_safe_header(k) + ": " + _safe_header(v) for k, v in headers.items()) + line = status_line + "\r\n" + "\r\n".join(headers_gen) + "\r\n\r\n" + return line.encode("utf-8") + + +_serialize_headers = _py_serialize_headers + +try: + import aiohttp._http_writer as _http_writer # type: ignore[import-not-found] + + _c_serialize_headers = _http_writer._serialize_headers + if not NO_EXTENSIONS: + _serialize_headers = _c_serialize_headers +except ImportError: + pass diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/log.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/log.py new file mode 100644 index 0000000000000000000000000000000000000000..3cecea2bac185df741bccd0a32a5fef9cfe23299 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/log.py @@ -0,0 +1,8 @@ +import logging + +access_logger = logging.getLogger("aiohttp.access") +client_logger = logging.getLogger("aiohttp.client") +internal_logger = logging.getLogger("aiohttp.internal") +server_logger = logging.getLogger("aiohttp.server") +web_logger = logging.getLogger("aiohttp.web") +ws_logger = logging.getLogger("aiohttp.websocket") diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/multipart.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/multipart.py new file mode 100644 index 0000000000000000000000000000000000000000..ec8b82b7ed4d10b5ac5b6cc9eedf651e30afdc86 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/multipart.py @@ -0,0 +1,1213 @@ +import base64 +import binascii +import json +import re +import sys +import uuid +import warnings +from collections import deque +from collections.abc import Mapping, Sequence +from types import TracebackType +from typing import ( + TYPE_CHECKING, + Any, + AsyncIterator, + Deque, + Dict, + Iterator, + List, + Optional, + Tuple, + Type, + Union, + cast, +) +from urllib.parse import parse_qsl, unquote, urlencode + +from multidict import CIMultiDict, CIMultiDictProxy + +from .abc import AbstractStreamWriter +from .compression_utils import ( + DEFAULT_MAX_DECOMPRESS_SIZE, + ZLibCompressor, + ZLibDecompressor, +) +from .hdrs import ( + CONTENT_DISPOSITION, + CONTENT_ENCODING, + CONTENT_LENGTH, + CONTENT_TRANSFER_ENCODING, + CONTENT_TYPE, +) +from .helpers import CHAR, TOKEN, parse_mimetype, reify +from .http import HeadersParser +from .http_exceptions import BadHttpMessage +from .log import internal_logger +from .payload import ( + JsonPayload, + LookupError, + Order, + Payload, + StringPayload, + get_payload, + payload_type, +) +from .streams import StreamReader + +if sys.version_info >= (3, 11): + from typing import Self +else: + from typing import TypeVar + + Self = TypeVar("Self", bound="BodyPartReader") + +__all__ = ( + "MultipartReader", + "MultipartWriter", + "BodyPartReader", + "BadContentDispositionHeader", + "BadContentDispositionParam", + "parse_content_disposition", + "content_disposition_filename", +) + + +if TYPE_CHECKING: + from .client_reqrep import ClientResponse + + +class BadContentDispositionHeader(RuntimeWarning): + pass + + +class BadContentDispositionParam(RuntimeWarning): + pass + + +def parse_content_disposition( + header: Optional[str], +) -> Tuple[Optional[str], Dict[str, str]]: + def is_token(string: str) -> bool: + return bool(string) and TOKEN >= set(string) + + def is_quoted(string: str) -> bool: + return string[0] == string[-1] == '"' + + def is_rfc5987(string: str) -> bool: + return is_token(string) and string.count("'") == 2 + + def is_extended_param(string: str) -> bool: + return string.endswith("*") + + def is_continuous_param(string: str) -> bool: + pos = string.find("*") + 1 + if not pos: + return False + substring = string[pos:-1] if string.endswith("*") else string[pos:] + return substring.isdigit() + + def unescape(text: str, *, chars: str = "".join(map(re.escape, CHAR))) -> str: + return re.sub(f"\\\\([{chars}])", "\\1", text) + + if not header: + return None, {} + + disptype, *parts = header.split(";") + if not is_token(disptype): + warnings.warn(BadContentDispositionHeader(header)) + return None, {} + + params: Dict[str, str] = {} + while parts: + item = parts.pop(0) + + if not item: # To handle trailing semicolons + warnings.warn(BadContentDispositionHeader(header)) + continue + + if "=" not in item: + warnings.warn(BadContentDispositionHeader(header)) + return None, {} + + key, value = item.split("=", 1) + key = key.lower().strip() + value = value.lstrip() + + if key in params: + warnings.warn(BadContentDispositionHeader(header)) + return None, {} + + if not is_token(key): + warnings.warn(BadContentDispositionParam(item)) + continue + + elif is_continuous_param(key): + if is_quoted(value): + value = unescape(value[1:-1]) + elif not is_token(value): + warnings.warn(BadContentDispositionParam(item)) + continue + + elif is_extended_param(key): + if is_rfc5987(value): + encoding, _, value = value.split("'", 2) + encoding = encoding or "utf-8" + else: + warnings.warn(BadContentDispositionParam(item)) + continue + + try: + value = unquote(value, encoding, "strict") + except UnicodeDecodeError: # pragma: nocover + warnings.warn(BadContentDispositionParam(item)) + continue + + else: + failed = True + if is_quoted(value): + failed = False + value = unescape(value[1:-1].lstrip("\\/")) + elif is_token(value): + failed = False + elif parts: + # maybe just ; in filename, in any case this is just + # one case fix, for proper fix we need to redesign parser + _value = f"{value};{parts[0]}" + if is_quoted(_value): + parts.pop(0) + value = unescape(_value[1:-1].lstrip("\\/")) + failed = False + + if failed: + warnings.warn(BadContentDispositionHeader(header)) + return None, {} + + params[key] = value + + return disptype.lower(), params + + +def content_disposition_filename( + params: Mapping[str, str], name: str = "filename" +) -> Optional[str]: + name_suf = "%s*" % name + if not params: + return None + elif name_suf in params: + return params[name_suf] + elif name in params: + return params[name] + else: + parts = [] + fnparams = sorted( + (key, value) for key, value in params.items() if key.startswith(name_suf) + ) + for num, (key, value) in enumerate(fnparams): + _, tail = key.split("*", 1) + if tail.endswith("*"): + tail = tail[:-1] + if tail == str(num): + parts.append(value) + else: + break + if not parts: + return None + value = "".join(parts) + if "'" in value: + encoding, _, value = value.split("'", 2) + encoding = encoding or "utf-8" + return unquote(value, encoding, "strict") + return value + + +class MultipartResponseWrapper: + """Wrapper around the MultipartReader. + + It takes care about + underlying connection and close it when it needs in. + """ + + def __init__( + self, + resp: "ClientResponse", + stream: "MultipartReader", + ) -> None: + self.resp = resp + self.stream = stream + + def __aiter__(self) -> "MultipartResponseWrapper": + return self + + async def __anext__( + self, + ) -> Union["MultipartReader", "BodyPartReader"]: + part = await self.next() + if part is None: + raise StopAsyncIteration + return part + + def at_eof(self) -> bool: + """Returns True when all response data had been read.""" + return self.resp.content.at_eof() + + async def next( + self, + ) -> Optional[Union["MultipartReader", "BodyPartReader"]]: + """Emits next multipart reader object.""" + item = await self.stream.next() + if self.stream.at_eof(): + await self.release() + return item + + async def release(self) -> None: + """Release the connection gracefully. + + All remaining content is read to the void. + """ + await self.resp.release() + + +class BodyPartReader: + """Multipart reader for single body part.""" + + chunk_size = 8192 + + def __init__( + self, + boundary: bytes, + headers: "CIMultiDictProxy[str]", + content: StreamReader, + *, + subtype: str = "mixed", + default_charset: Optional[str] = None, + max_decompress_size: int = DEFAULT_MAX_DECOMPRESS_SIZE, + ) -> None: + self.headers = headers + self._boundary = boundary + self._boundary_len = len(boundary) + 2 # Boundary + \r\n + self._content = content + self._default_charset = default_charset + self._at_eof = False + self._is_form_data = subtype == "form-data" + # https://datatracker.ietf.org/doc/html/rfc7578#section-4.8 + length = None if self._is_form_data else self.headers.get(CONTENT_LENGTH, None) + self._length = int(length) if length is not None else None + self._read_bytes = 0 + self._unread: Deque[bytes] = deque() + self._prev_chunk: Optional[bytes] = None + self._content_eof = 0 + self._cache: Dict[str, Any] = {} + self._max_decompress_size = max_decompress_size + + def __aiter__(self: Self) -> Self: + return self + + async def __anext__(self) -> bytes: + part = await self.next() + if part is None: + raise StopAsyncIteration + return part + + async def next(self) -> Optional[bytes]: + item = await self.read() + if not item: + return None + return item + + async def read(self, *, decode: bool = False) -> bytes: + """Reads body part data. + + decode: Decodes data following by encoding + method from Content-Encoding header. If it missed + data remains untouched + """ + if self._at_eof: + return b"" + data = bytearray() + while not self._at_eof: + data.extend(await self.read_chunk(self.chunk_size)) + if decode: + decoded_data = bytearray() + async for d in self.decode_iter(data): + decoded_data.extend(d) + return decoded_data + return data + + async def read_chunk(self, size: int = chunk_size) -> bytes: + """Reads body part content chunk of the specified size. + + size: chunk size + """ + if self._at_eof: + return b"" + if self._length: + chunk = await self._read_chunk_from_length(size) + else: + chunk = await self._read_chunk_from_stream(size) + + # For the case of base64 data, we must read a fragment of size with a + # remainder of 0 by dividing by 4 for string without symbols \n or \r + encoding = self.headers.get(CONTENT_TRANSFER_ENCODING) + if encoding and encoding.lower() == "base64": + stripped_chunk = b"".join(chunk.split()) + remainder = len(stripped_chunk) % 4 + + while remainder != 0 and not self.at_eof(): + over_chunk_size = 4 - remainder + over_chunk = b"" + + if self._prev_chunk: + over_chunk = self._prev_chunk[:over_chunk_size] + self._prev_chunk = self._prev_chunk[len(over_chunk) :] + + if len(over_chunk) != over_chunk_size: + over_chunk += await self._content.read(4 - len(over_chunk)) + + if not over_chunk: + self._at_eof = True + + stripped_chunk += b"".join(over_chunk.split()) + chunk += over_chunk + remainder = len(stripped_chunk) % 4 + + self._read_bytes += len(chunk) + if self._read_bytes == self._length: + self._at_eof = True + if self._at_eof and await self._content.readline() != b"\r\n": + raise ValueError("Reader did not read all the data or it is malformed") + return chunk + + async def _read_chunk_from_length(self, size: int) -> bytes: + # Reads body part content chunk of the specified size. + # The body part must has Content-Length header with proper value. + assert self._length is not None, "Content-Length required for chunked read" + chunk_size = min(size, self._length - self._read_bytes) + chunk = await self._content.read(chunk_size) + if self._content.at_eof(): + self._at_eof = True + return chunk + + async def _read_chunk_from_stream(self, size: int) -> bytes: + # Reads content chunk of body part with unknown length. + # The Content-Length header for body part is not necessary. + assert ( + size >= self._boundary_len + ), "Chunk size must be greater or equal than boundary length + 2" + first_chunk = self._prev_chunk is None + if first_chunk: + # We need to re-add the CRLF that got removed from headers parsing. + self._prev_chunk = b"\r\n" + await self._content.read(size) + + chunk = b"" + # content.read() may return less than size, so we need to loop to ensure + # we have enough data to detect the boundary. + while len(chunk) < self._boundary_len: + chunk += await self._content.read(size) + self._content_eof += int(self._content.at_eof()) + if self._content_eof > 2: + raise ValueError("Reading after EOF") + if self._content_eof: + break + if len(chunk) > size: + self._content.unread_data(chunk[size:]) + chunk = chunk[:size] + + assert self._prev_chunk is not None + window = self._prev_chunk + chunk + sub = b"\r\n" + self._boundary + if first_chunk: + idx = window.find(sub) + else: + idx = window.find(sub, max(0, len(self._prev_chunk) - len(sub))) + if idx >= 0: + # pushing boundary back to content + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=DeprecationWarning) + self._content.unread_data(window[idx:]) + self._prev_chunk = self._prev_chunk[:idx] + chunk = window[len(self._prev_chunk) : idx] + if not chunk: + self._at_eof = True + result = self._prev_chunk[2 if first_chunk else 0 :] # Strip initial CRLF + self._prev_chunk = chunk + return result + + async def readline(self) -> bytes: + """Reads body part by line by line.""" + if self._at_eof: + return b"" + + if self._unread: + line = self._unread.popleft() + else: + line = await self._content.readline() + + if line.startswith(self._boundary): + # the very last boundary may not come with \r\n, + # so set single rules for everyone + sline = line.rstrip(b"\r\n") + boundary = self._boundary + last_boundary = self._boundary + b"--" + # ensure that we read exactly the boundary, not something alike + if sline == boundary or sline == last_boundary: + self._at_eof = True + self._unread.append(line) + return b"" + else: + next_line = await self._content.readline() + if next_line.startswith(self._boundary): + line = line[:-2] # strip CRLF but only once + self._unread.append(next_line) + + return line + + async def release(self) -> None: + """Like read(), but reads all the data to the void.""" + if self._at_eof: + return + while not self._at_eof: + await self.read_chunk(self.chunk_size) + + async def text(self, *, encoding: Optional[str] = None) -> str: + """Like read(), but assumes that body part contains text data.""" + data = await self.read(decode=True) + # see https://www.w3.org/TR/html5/forms.html#multipart/form-data-encoding-algorithm + # and https://dvcs.w3.org/hg/xhr/raw-file/tip/Overview.html#dom-xmlhttprequest-send + encoding = encoding or self.get_charset(default="utf-8") + return data.decode(encoding) + + async def json(self, *, encoding: Optional[str] = None) -> Optional[Dict[str, Any]]: + """Like read(), but assumes that body parts contains JSON data.""" + data = await self.read(decode=True) + if not data: + return None + encoding = encoding or self.get_charset(default="utf-8") + return cast(Dict[str, Any], json.loads(data.decode(encoding))) + + async def form(self, *, encoding: Optional[str] = None) -> List[Tuple[str, str]]: + """Like read(), but assumes that body parts contain form urlencoded data.""" + data = await self.read(decode=True) + if not data: + return [] + if encoding is not None: + real_encoding = encoding + else: + real_encoding = self.get_charset(default="utf-8") + try: + decoded_data = data.rstrip().decode(real_encoding) + except UnicodeDecodeError: + raise ValueError("data cannot be decoded with %s encoding" % real_encoding) + + return parse_qsl( + decoded_data, + keep_blank_values=True, + encoding=real_encoding, + ) + + def at_eof(self) -> bool: + """Returns True if the boundary was reached or False otherwise.""" + return self._at_eof + + def _apply_content_transfer_decoding(self, data: bytes) -> bytes: + """Apply Content-Transfer-Encoding decoding if header is present.""" + if CONTENT_TRANSFER_ENCODING in self.headers: + return self._decode_content_transfer(data) + return data + + def _needs_content_decoding(self) -> bool: + """Check if Content-Encoding decoding should be applied.""" + # https://datatracker.ietf.org/doc/html/rfc7578#section-4.8 + return not self._is_form_data and CONTENT_ENCODING in self.headers + + def decode(self, data: bytes) -> bytes: + """Decodes data synchronously. + + Decodes data according the specified Content-Encoding + or Content-Transfer-Encoding headers value. + + Note: For large payloads, consider using decode_iter() instead + to avoid blocking the event loop during decompression. + """ + data = self._apply_content_transfer_decoding(data) + if self._needs_content_decoding(): + return self._decode_content(data) + return data + + async def decode_iter(self, data: bytes) -> AsyncIterator[bytes]: + """Async generator that yields decoded data chunks. + + Decodes data according the specified Content-Encoding + or Content-Transfer-Encoding headers value. + + This method offloads decompression to an executor for large payloads + to avoid blocking the event loop. + """ + data = self._apply_content_transfer_decoding(data) + if self._needs_content_decoding(): + async for d in self._decode_content_async(data): + yield d + else: + yield data + + def _decode_content(self, data: bytes) -> bytes: + encoding = self.headers.get(CONTENT_ENCODING, "").lower() + if encoding == "identity": + return data + if encoding in {"deflate", "gzip"}: + return ZLibDecompressor( + encoding=encoding, + suppress_deflate_header=True, + ).decompress_sync(data, max_length=self._max_decompress_size) + + raise RuntimeError(f"unknown content encoding: {encoding}") + + async def _decode_content_async(self, data: bytes) -> AsyncIterator[bytes]: + encoding = self.headers.get(CONTENT_ENCODING, "").lower() + if encoding == "identity": + yield data + elif encoding in {"deflate", "gzip"}: + d = ZLibDecompressor( + encoding=encoding, + suppress_deflate_header=True, + ) + yield await d.decompress(data, max_length=self._max_decompress_size) + else: + raise RuntimeError(f"unknown content encoding: {encoding}") + + def _decode_content_transfer(self, data: bytes) -> bytes: + encoding = self.headers.get(CONTENT_TRANSFER_ENCODING, "").lower() + + if encoding == "base64": + return base64.b64decode(data) + elif encoding == "quoted-printable": + return binascii.a2b_qp(data) + elif encoding in ("binary", "8bit", "7bit"): + return data + else: + raise RuntimeError(f"unknown content transfer encoding: {encoding}") + + def get_charset(self, default: str) -> str: + """Returns charset parameter from Content-Type header or default.""" + ctype = self.headers.get(CONTENT_TYPE, "") + mimetype = parse_mimetype(ctype) + return mimetype.parameters.get("charset", self._default_charset or default) + + @reify + def name(self) -> Optional[str]: + """Returns name specified in Content-Disposition header. + + If the header is missing or malformed, returns None. + """ + _, params = parse_content_disposition(self.headers.get(CONTENT_DISPOSITION)) + return content_disposition_filename(params, "name") + + @reify + def filename(self) -> Optional[str]: + """Returns filename specified in Content-Disposition header. + + Returns None if the header is missing or malformed. + """ + _, params = parse_content_disposition(self.headers.get(CONTENT_DISPOSITION)) + return content_disposition_filename(params, "filename") + + +@payload_type(BodyPartReader, order=Order.try_first) +class BodyPartReaderPayload(Payload): + _value: BodyPartReader + # _autoclose = False (inherited) - Streaming reader that may have resources + + def __init__(self, value: BodyPartReader, *args: Any, **kwargs: Any) -> None: + super().__init__(value, *args, **kwargs) + + params: Dict[str, str] = {} + if value.name is not None: + params["name"] = value.name + if value.filename is not None: + params["filename"] = value.filename + + if params: + self.set_content_disposition("attachment", True, **params) + + def decode(self, encoding: str = "utf-8", errors: str = "strict") -> str: + raise TypeError("Unable to decode.") + + async def as_bytes(self, encoding: str = "utf-8", errors: str = "strict") -> bytes: + """Raises TypeError as body parts should be consumed via write(). + + This is intentional: BodyPartReader payloads are designed for streaming + large data (potentially gigabytes) and must be consumed only once via + the write() method to avoid memory exhaustion. They cannot be buffered + in memory for reuse. + """ + raise TypeError("Unable to read body part as bytes. Use write() to consume.") + + async def write(self, writer: AbstractStreamWriter) -> None: + field = self._value + while chunk := await field.read_chunk(size=2**18): + async for d in field.decode_iter(chunk): + await writer.write(d) + + +class MultipartReader: + """Multipart body reader.""" + + #: Response wrapper, used when multipart readers constructs from response. + response_wrapper_cls = MultipartResponseWrapper + #: Multipart reader class, used to handle multipart/* body parts. + #: None points to type(self) + multipart_reader_cls: Optional[Type["MultipartReader"]] = None + #: Body part reader class for non multipart/* content types. + part_reader_cls = BodyPartReader + + def __init__( + self, + headers: Mapping[str, str], + content: StreamReader, + *, + max_field_size: int = 8190, + max_headers: int = 128, + ) -> None: + self._mimetype = parse_mimetype(headers[CONTENT_TYPE]) + assert self._mimetype.type == "multipart", "multipart/* content type expected" + if "boundary" not in self._mimetype.parameters: + raise ValueError( + "boundary missed for Content-Type: %s" % headers[CONTENT_TYPE] + ) + + self.headers = headers + self._boundary = ("--" + self._get_boundary()).encode() + self._content = content + self._default_charset: str | None = None + self._last_part: MultipartReader | BodyPartReader | None = None + self._max_field_size = max_field_size + self._max_headers = max_headers + self._at_eof = False + self._at_bof = True + self._unread: List[bytes] = [] + + def __aiter__(self: Self) -> Self: + return self + + async def __anext__( + self, + ) -> Optional[Union["MultipartReader", BodyPartReader]]: + part = await self.next() + if part is None: + raise StopAsyncIteration + return part + + @classmethod + def from_response( + cls, + response: "ClientResponse", + ) -> MultipartResponseWrapper: + """Constructs reader instance from HTTP response. + + :param response: :class:`~aiohttp.client.ClientResponse` instance + """ + obj = cls.response_wrapper_cls( + response, cls(response.headers, response.content) + ) + return obj + + def at_eof(self) -> bool: + """Returns True if the final boundary was reached, false otherwise.""" + return self._at_eof + + async def next( + self, + ) -> Optional[Union["MultipartReader", BodyPartReader]]: + """Emits the next multipart body part.""" + # So, if we're at BOF, we need to skip till the boundary. + if self._at_eof: + return None + await self._maybe_release_last_part() + if self._at_bof: + await self._read_until_first_boundary() + self._at_bof = False + else: + await self._read_boundary() + if self._at_eof: # we just read the last boundary, nothing to do there + return None + + part = await self.fetch_next_part() + # https://datatracker.ietf.org/doc/html/rfc7578#section-4.6 + if ( + self._last_part is None + and self._mimetype.subtype == "form-data" + and isinstance(part, BodyPartReader) + ): + _, params = parse_content_disposition(part.headers.get(CONTENT_DISPOSITION)) + if params.get("name") == "_charset_": + # Longest encoding in https://encoding.spec.whatwg.org/encodings.json + # is 19 characters, so 32 should be more than enough for any valid encoding. + charset = await part.read_chunk(32) + if len(charset) > 31: + raise RuntimeError("Invalid default charset") + self._default_charset = charset.strip().decode() + part = await self.fetch_next_part() + self._last_part = part + return self._last_part + + async def release(self) -> None: + """Reads all the body parts to the void till the final boundary.""" + while not self._at_eof: + item = await self.next() + if item is None: + break + await item.release() + + async def fetch_next_part( + self, + ) -> Union["MultipartReader", BodyPartReader]: + """Returns the next body part reader.""" + headers = await self._read_headers() + return self._get_part_reader(headers) + + def _get_part_reader( + self, + headers: "CIMultiDictProxy[str]", + ) -> Union["MultipartReader", BodyPartReader]: + """Dispatches the response by the `Content-Type` header. + + Returns a suitable reader instance. + + :param dict headers: Response headers + """ + ctype = headers.get(CONTENT_TYPE, "") + mimetype = parse_mimetype(ctype) + + if mimetype.type == "multipart": + if self.multipart_reader_cls is None: + return type(self)(headers, self._content) + return self.multipart_reader_cls( + headers, + self._content, + max_field_size=self._max_field_size, + max_headers=self._max_headers, + ) + else: + return self.part_reader_cls( + self._boundary, + headers, + self._content, + subtype=self._mimetype.subtype, + default_charset=self._default_charset, + ) + + def _get_boundary(self) -> str: + boundary = self._mimetype.parameters["boundary"] + if len(boundary) > 70: + raise ValueError("boundary %r is too long (70 chars max)" % boundary) + + return boundary + + async def _readline(self) -> bytes: + if self._unread: + return self._unread.pop() + return await self._content.readline() + + async def _read_until_first_boundary(self) -> None: + while True: + chunk = await self._readline() + if chunk == b"": + raise ValueError( + "Could not find starting boundary %r" % (self._boundary) + ) + chunk = chunk.rstrip() + if chunk == self._boundary: + return + elif chunk == self._boundary + b"--": + self._at_eof = True + return + + async def _read_boundary(self) -> None: + chunk = (await self._readline()).rstrip() + if chunk == self._boundary: + pass + elif chunk == self._boundary + b"--": + self._at_eof = True + epilogue = await self._readline() + next_line = await self._readline() + + # the epilogue is expected and then either the end of input or the + # parent multipart boundary, if the parent boundary is found then + # it should be marked as unread and handed to the parent for + # processing + if next_line[:2] == b"--": + self._unread.append(next_line) + # otherwise the request is likely missing an epilogue and both + # lines should be passed to the parent for processing + # (this handles the old behavior gracefully) + else: + self._unread.extend([next_line, epilogue]) + else: + raise ValueError(f"Invalid boundary {chunk!r}, expected {self._boundary!r}") + + async def _read_headers(self) -> "CIMultiDictProxy[str]": + lines = [] + while True: + chunk = await self._content.readline(max_line_length=self._max_field_size) + chunk = chunk.rstrip(b"\r\n") + lines.append(chunk) + if not chunk: + break + if len(lines) > self._max_headers: + raise BadHttpMessage("Too many headers received") + parser = HeadersParser(max_field_size=self._max_field_size) + headers, raw_headers = parser.parse_headers(lines) + return headers + + async def _maybe_release_last_part(self) -> None: + """Ensures that the last read body part is read completely.""" + if self._last_part is not None: + if not self._last_part.at_eof(): + await self._last_part.release() + self._unread.extend(self._last_part._unread) + self._last_part = None + + +_Part = Tuple[Payload, str, str] + + +class MultipartWriter(Payload): + """Multipart body writer.""" + + _value: None + # _consumed = False (inherited) - Can be encoded multiple times + _autoclose = True # No file handles, just collects parts in memory + + def __init__(self, subtype: str = "mixed", boundary: Optional[str] = None) -> None: + boundary = boundary if boundary is not None else uuid.uuid4().hex + # The underlying Payload API demands a str (utf-8), not bytes, + # so we need to ensure we don't lose anything during conversion. + # As a result, require the boundary to be ASCII only. + # In both situations. + + try: + self._boundary = boundary.encode("ascii") + except UnicodeEncodeError: + raise ValueError("boundary should contain ASCII only chars") from None + ctype = f"multipart/{subtype}; boundary={self._boundary_value}" + + super().__init__(None, content_type=ctype) + + self._parts: List[_Part] = [] + self._is_form_data = subtype == "form-data" + + def __enter__(self) -> "MultipartWriter": + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + pass + + def __iter__(self) -> Iterator[_Part]: + return iter(self._parts) + + def __len__(self) -> int: + return len(self._parts) + + def __bool__(self) -> bool: + return True + + _valid_tchar_regex = re.compile(rb"\A[!#$%&'*+\-.^_`|~\w]+\Z") + _invalid_qdtext_char_regex = re.compile(rb"[\x00-\x08\x0A-\x1F\x7F]") + + @property + def _boundary_value(self) -> str: + """Wrap boundary parameter value in quotes, if necessary. + + Reads self.boundary and returns a unicode string. + """ + # Refer to RFCs 7231, 7230, 5234. + # + # parameter = token "=" ( token / quoted-string ) + # token = 1*tchar + # quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE + # qdtext = HTAB / SP / %x21 / %x23-5B / %x5D-7E / obs-text + # obs-text = %x80-FF + # quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text ) + # tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" + # / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~" + # / DIGIT / ALPHA + # ; any VCHAR, except delimiters + # VCHAR = %x21-7E + value = self._boundary + if re.match(self._valid_tchar_regex, value): + return value.decode("ascii") # cannot fail + + if re.search(self._invalid_qdtext_char_regex, value): + raise ValueError("boundary value contains invalid characters") + + # escape %x5C and %x22 + quoted_value_content = value.replace(b"\\", b"\\\\") + quoted_value_content = quoted_value_content.replace(b'"', b'\\"') + + return '"' + quoted_value_content.decode("ascii") + '"' + + @property + def boundary(self) -> str: + return self._boundary.decode("ascii") + + def append(self, obj: Any, headers: Optional[Mapping[str, str]] = None) -> Payload: + if headers is None: + headers = CIMultiDict() + + if isinstance(obj, Payload): + obj.headers.update(headers) + return self.append_payload(obj) + else: + try: + payload = get_payload(obj, headers=headers) + except LookupError: + raise TypeError("Cannot create payload from %r" % obj) + else: + return self.append_payload(payload) + + def append_payload(self, payload: Payload) -> Payload: + """Adds a new body part to multipart writer.""" + encoding: Optional[str] = None + te_encoding: Optional[str] = None + if self._is_form_data: + # https://datatracker.ietf.org/doc/html/rfc7578#section-4.7 + # https://datatracker.ietf.org/doc/html/rfc7578#section-4.8 + assert ( + not {CONTENT_ENCODING, CONTENT_LENGTH, CONTENT_TRANSFER_ENCODING} + & payload.headers.keys() + ) + # Set default Content-Disposition in case user doesn't create one + if CONTENT_DISPOSITION not in payload.headers: + name = f"section-{len(self._parts)}" + payload.set_content_disposition("form-data", name=name) + else: + # compression + encoding = payload.headers.get(CONTENT_ENCODING, "").lower() + if encoding and encoding not in ("deflate", "gzip", "identity"): + raise RuntimeError(f"unknown content encoding: {encoding}") + if encoding == "identity": + encoding = None + + # te encoding + te_encoding = payload.headers.get(CONTENT_TRANSFER_ENCODING, "").lower() + if te_encoding not in ("", "base64", "quoted-printable", "binary"): + raise RuntimeError(f"unknown content transfer encoding: {te_encoding}") + if te_encoding == "binary": + te_encoding = None + + # size + size = payload.size + if size is not None and not (encoding or te_encoding): + payload.headers[CONTENT_LENGTH] = str(size) + + self._parts.append((payload, encoding, te_encoding)) # type: ignore[arg-type] + return payload + + def append_json( + self, obj: Any, headers: Optional[Mapping[str, str]] = None + ) -> Payload: + """Helper to append JSON part.""" + if headers is None: + headers = CIMultiDict() + + return self.append_payload(JsonPayload(obj, headers=headers)) + + def append_form( + self, + obj: Union[Sequence[Tuple[str, str]], Mapping[str, str]], + headers: Optional[Mapping[str, str]] = None, + ) -> Payload: + """Helper to append form urlencoded part.""" + assert isinstance(obj, (Sequence, Mapping)) + + if headers is None: + headers = CIMultiDict() + + if isinstance(obj, Mapping): + obj = list(obj.items()) + data = urlencode(obj, doseq=True) + + return self.append_payload( + StringPayload( + data, headers=headers, content_type="application/x-www-form-urlencoded" + ) + ) + + @property + def size(self) -> Optional[int]: + """Size of the payload.""" + total = 0 + for part, encoding, te_encoding in self._parts: + part_size = part.size + if encoding or te_encoding or part_size is None: + return None + + total += int( + 2 + + len(self._boundary) + + 2 + + part_size # b'--'+self._boundary+b'\r\n' + + len(part._binary_headers) + + 2 # b'\r\n' + ) + + total += 2 + len(self._boundary) + 4 # b'--'+self._boundary+b'--\r\n' + return total + + def decode(self, encoding: str = "utf-8", errors: str = "strict") -> str: + """Return string representation of the multipart data. + + WARNING: This method may do blocking I/O if parts contain file payloads. + It should not be called in the event loop. Use as_bytes().decode() instead. + """ + return "".join( + "--" + + self.boundary + + "\r\n" + + part._binary_headers.decode(encoding, errors) + + part.decode() + for part, _e, _te in self._parts + ) + + async def as_bytes(self, encoding: str = "utf-8", errors: str = "strict") -> bytes: + """Return bytes representation of the multipart data. + + This method is async-safe and calls as_bytes on underlying payloads. + """ + parts: List[bytes] = [] + + # Process each part + for part, _e, _te in self._parts: + # Add boundary + parts.append(b"--" + self._boundary + b"\r\n") + + # Add headers + parts.append(part._binary_headers) + + # Add payload content using as_bytes for async safety + part_bytes = await part.as_bytes(encoding, errors) + parts.append(part_bytes) + + # Add trailing CRLF + parts.append(b"\r\n") + + # Add closing boundary + parts.append(b"--" + self._boundary + b"--\r\n") + + return b"".join(parts) + + async def write( + self, writer: AbstractStreamWriter, close_boundary: bool = True + ) -> None: + """Write body.""" + for part, encoding, te_encoding in self._parts: + if self._is_form_data: + # https://datatracker.ietf.org/doc/html/rfc7578#section-4.2 + assert CONTENT_DISPOSITION in part.headers + assert "name=" in part.headers[CONTENT_DISPOSITION] + + await writer.write(b"--" + self._boundary + b"\r\n") + await writer.write(part._binary_headers) + + if encoding or te_encoding: + w = MultipartPayloadWriter(writer) + if encoding: + w.enable_compression(encoding) + if te_encoding: + w.enable_encoding(te_encoding) + await part.write(w) # type: ignore[arg-type] + await w.write_eof() + else: + await part.write(writer) + + await writer.write(b"\r\n") + + if close_boundary: + await writer.write(b"--" + self._boundary + b"--\r\n") + + async def close(self) -> None: + """ + Close all part payloads that need explicit closing. + + IMPORTANT: This method must not await anything that might not finish + immediately, as it may be called during cleanup/cancellation. Schedule + any long-running operations without awaiting them. + """ + if self._consumed: + return + self._consumed = True + + # Close all parts that need explicit closing + # We catch and log exceptions to ensure all parts get a chance to close + # we do not use asyncio.gather() here because we are not allowed + # to suspend given we may be called during cleanup + for idx, (part, _, _) in enumerate(self._parts): + if not part.autoclose and not part.consumed: + try: + await part.close() + except Exception as exc: + internal_logger.error( + "Failed to close multipart part %d: %s", idx, exc, exc_info=True + ) + + +class MultipartPayloadWriter: + def __init__(self, writer: AbstractStreamWriter) -> None: + self._writer = writer + self._encoding: Optional[str] = None + self._compress: Optional[ZLibCompressor] = None + self._encoding_buffer: Optional[bytearray] = None + + def enable_encoding(self, encoding: str) -> None: + if encoding == "base64": + self._encoding = encoding + self._encoding_buffer = bytearray() + elif encoding == "quoted-printable": + self._encoding = "quoted-printable" + + def enable_compression( + self, encoding: str = "deflate", strategy: Optional[int] = None + ) -> None: + self._compress = ZLibCompressor( + encoding=encoding, + suppress_deflate_header=True, + strategy=strategy, + ) + + async def write_eof(self) -> None: + if self._compress is not None: + chunk = self._compress.flush() + if chunk: + self._compress = None + await self.write(chunk) + + if self._encoding == "base64": + if self._encoding_buffer: + await self._writer.write(base64.b64encode(self._encoding_buffer)) + + async def write(self, chunk: bytes) -> None: + if self._compress is not None: + if chunk: + chunk = await self._compress.compress(chunk) + if not chunk: + return + + if self._encoding == "base64": + buf = self._encoding_buffer + assert buf is not None + buf.extend(chunk) + + if buf: + div, mod = divmod(len(buf), 3) + enc_chunk, self._encoding_buffer = (buf[: div * 3], buf[div * 3 :]) + if enc_chunk: + b64chunk = base64.b64encode(enc_chunk) + await self._writer.write(b64chunk) + elif self._encoding == "quoted-printable": + await self._writer.write(binascii.b2a_qp(chunk)) + else: + await self._writer.write(chunk) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/payload.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/payload.py new file mode 100644 index 0000000000000000000000000000000000000000..5b88fa094050fcef280050b54429f6bd43b166d3 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/payload.py @@ -0,0 +1,1120 @@ +import asyncio +import enum +import io +import json +import mimetypes +import os +import sys +import warnings +from abc import ABC, abstractmethod +from collections.abc import Iterable +from itertools import chain +from typing import ( + IO, + TYPE_CHECKING, + Any, + Dict, + Final, + List, + Optional, + Set, + TextIO, + Tuple, + Type, + Union, +) + +from multidict import CIMultiDict + +from . import hdrs +from .abc import AbstractStreamWriter +from .helpers import ( + _SENTINEL, + content_disposition_header, + guess_filename, + parse_mimetype, + sentinel, +) +from .streams import StreamReader +from .typedefs import JSONEncoder, _CIMultiDict + +__all__ = ( + "PAYLOAD_REGISTRY", + "get_payload", + "payload_type", + "Payload", + "BytesPayload", + "StringPayload", + "IOBasePayload", + "BytesIOPayload", + "BufferedReaderPayload", + "TextIOPayload", + "StringIOPayload", + "JsonPayload", + "AsyncIterablePayload", +) + +TOO_LARGE_BYTES_BODY: Final[int] = 2**20 # 1 MB +READ_SIZE: Final[int] = 2**16 # 64 KB +_CLOSE_FUTURES: Set[asyncio.Future[None]] = set() + + +class LookupError(Exception): + """Raised when no payload factory is found for the given data type.""" + + +class Order(str, enum.Enum): + normal = "normal" + try_first = "try_first" + try_last = "try_last" + + +def get_payload(data: Any, *args: Any, **kwargs: Any) -> "Payload": + return PAYLOAD_REGISTRY.get(data, *args, **kwargs) + + +def register_payload( + factory: Type["Payload"], type: Any, *, order: Order = Order.normal +) -> None: + PAYLOAD_REGISTRY.register(factory, type, order=order) + + +class payload_type: + def __init__(self, type: Any, *, order: Order = Order.normal) -> None: + self.type = type + self.order = order + + def __call__(self, factory: Type["Payload"]) -> Type["Payload"]: + register_payload(factory, self.type, order=self.order) + return factory + + +PayloadType = Type["Payload"] +_PayloadRegistryItem = Tuple[PayloadType, Any] + + +class PayloadRegistry: + """Payload registry. + + note: we need zope.interface for more efficient adapter search + """ + + __slots__ = ("_first", "_normal", "_last", "_normal_lookup") + + def __init__(self) -> None: + self._first: List[_PayloadRegistryItem] = [] + self._normal: List[_PayloadRegistryItem] = [] + self._last: List[_PayloadRegistryItem] = [] + self._normal_lookup: Dict[Any, PayloadType] = {} + + def get( + self, + data: Any, + *args: Any, + _CHAIN: "Type[chain[_PayloadRegistryItem]]" = chain, + **kwargs: Any, + ) -> "Payload": + if self._first: + for factory, type_ in self._first: + if isinstance(data, type_): + return factory(data, *args, **kwargs) + # Try the fast lookup first + if lookup_factory := self._normal_lookup.get(type(data)): + return lookup_factory(data, *args, **kwargs) + # Bail early if its already a Payload + if isinstance(data, Payload): + return data + # Fallback to the slower linear search + for factory, type_ in _CHAIN(self._normal, self._last): + if isinstance(data, type_): + return factory(data, *args, **kwargs) + raise LookupError() + + def register( + self, factory: PayloadType, type: Any, *, order: Order = Order.normal + ) -> None: + if order is Order.try_first: + self._first.append((factory, type)) + elif order is Order.normal: + self._normal.append((factory, type)) + if isinstance(type, Iterable): + for t in type: + self._normal_lookup[t] = factory + else: + self._normal_lookup[type] = factory + elif order is Order.try_last: + self._last.append((factory, type)) + else: + raise ValueError(f"Unsupported order {order!r}") + + +class Payload(ABC): + + _default_content_type: str = "application/octet-stream" + _size: Optional[int] = None + _consumed: bool = False # Default: payload has not been consumed yet + _autoclose: bool = False # Default: assume resource needs explicit closing + + def __init__( + self, + value: Any, + headers: Optional[ + Union[_CIMultiDict, Dict[str, str], Iterable[Tuple[str, str]]] + ] = None, + content_type: Union[str, None, _SENTINEL] = sentinel, + filename: Optional[str] = None, + encoding: Optional[str] = None, + **kwargs: Any, + ) -> None: + self._encoding = encoding + self._filename = filename + self._headers: _CIMultiDict = CIMultiDict() + self._value = value + if content_type is not sentinel and content_type is not None: + self._headers[hdrs.CONTENT_TYPE] = content_type + elif self._filename is not None: + if sys.version_info >= (3, 13): + guesser = mimetypes.guess_file_type + else: + guesser = mimetypes.guess_type + content_type = guesser(self._filename)[0] + if content_type is None: + content_type = self._default_content_type + self._headers[hdrs.CONTENT_TYPE] = content_type + else: + self._headers[hdrs.CONTENT_TYPE] = self._default_content_type + if headers: + self._headers.update(headers) + + @property + def size(self) -> Optional[int]: + """Size of the payload in bytes. + + Returns the number of bytes that will be transmitted when the payload + is written. For string payloads, this is the size after encoding to bytes, + not the length of the string. + """ + return self._size + + @property + def filename(self) -> Optional[str]: + """Filename of the payload.""" + return self._filename + + @property + def headers(self) -> _CIMultiDict: + """Custom item headers""" + return self._headers + + @property + def _binary_headers(self) -> bytes: + return ( + "".join([k + ": " + v + "\r\n" for k, v in self.headers.items()]).encode( + "utf-8" + ) + + b"\r\n" + ) + + @property + def encoding(self) -> Optional[str]: + """Payload encoding""" + return self._encoding + + @property + def content_type(self) -> str: + """Content type""" + return self._headers[hdrs.CONTENT_TYPE] + + @property + def consumed(self) -> bool: + """Whether the payload has been consumed and cannot be reused.""" + return self._consumed + + @property + def autoclose(self) -> bool: + """ + Whether the payload can close itself automatically. + + Returns True if the payload has no file handles or resources that need + explicit closing. If False, callers must await close() to release resources. + """ + return self._autoclose + + def set_content_disposition( + self, + disptype: str, + quote_fields: bool = True, + _charset: str = "utf-8", + **params: Any, + ) -> None: + """Sets ``Content-Disposition`` header.""" + self._headers[hdrs.CONTENT_DISPOSITION] = content_disposition_header( + disptype, quote_fields=quote_fields, _charset=_charset, **params + ) + + @abstractmethod + def decode(self, encoding: str = "utf-8", errors: str = "strict") -> str: + """ + Return string representation of the value. + + This is named decode() to allow compatibility with bytes objects. + """ + + @abstractmethod + async def write(self, writer: AbstractStreamWriter) -> None: + """ + Write payload to the writer stream. + + Args: + writer: An AbstractStreamWriter instance that handles the actual writing + + This is a legacy method that writes the entire payload without length constraints. + + Important: + For new implementations, use write_with_length() instead of this method. + This method is maintained for backwards compatibility and will eventually + delegate to write_with_length(writer, None) in all implementations. + + All payload subclasses must override this method for backwards compatibility, + but new code should use write_with_length for more flexibility and control. + + """ + + # write_with_length is new in aiohttp 3.12 + # it should be overridden by subclasses + async def write_with_length( + self, writer: AbstractStreamWriter, content_length: Optional[int] + ) -> None: + """ + Write payload with a specific content length constraint. + + Args: + writer: An AbstractStreamWriter instance that handles the actual writing + content_length: Maximum number of bytes to write (None for unlimited) + + This method allows writing payload content with a specific length constraint, + which is particularly useful for HTTP responses with Content-Length header. + + Note: + This is the base implementation that provides backwards compatibility + for subclasses that don't override this method. Specific payload types + should override this method to implement proper length-constrained writing. + + """ + # Backwards compatibility for subclasses that don't override this method + # and for the default implementation + await self.write(writer) + + async def as_bytes(self, encoding: str = "utf-8", errors: str = "strict") -> bytes: + """ + Return bytes representation of the value. + + This is a convenience method that calls decode() and encodes the result + to bytes using the specified encoding. + """ + # Use instance encoding if available, otherwise use parameter + actual_encoding = self._encoding or encoding + return self.decode(actual_encoding, errors).encode(actual_encoding) + + def _close(self) -> None: + """ + Async safe synchronous close operations for backwards compatibility. + + This method exists only for backwards compatibility with code that + needs to clean up payloads synchronously. In the future, we will + drop this method and only support the async close() method. + + WARNING: This method must be safe to call from within the event loop + without blocking. Subclasses should not perform any blocking I/O here. + + WARNING: This method must be called from within an event loop for + certain payload types (e.g., IOBasePayload). Calling it outside an + event loop may raise RuntimeError. + """ + # This is a no-op by default, but subclasses can override it + # for non-blocking cleanup operations. + + async def close(self) -> None: + """ + Close the payload if it holds any resources. + + IMPORTANT: This method must not await anything that might not finish + immediately, as it may be called during cleanup/cancellation. Schedule + any long-running operations without awaiting them. + + In the future, this will be the only close method supported. + """ + self._close() + + +class BytesPayload(Payload): + _value: bytes + # _consumed = False (inherited) - Bytes are immutable and can be reused + _autoclose = True # No file handle, just bytes in memory + + def __init__( + self, value: Union[bytes, bytearray, memoryview], *args: Any, **kwargs: Any + ) -> None: + if "content_type" not in kwargs: + kwargs["content_type"] = "application/octet-stream" + + super().__init__(value, *args, **kwargs) + + if isinstance(value, memoryview): + self._size = value.nbytes + elif isinstance(value, (bytes, bytearray)): + self._size = len(value) + else: + raise TypeError(f"value argument must be byte-ish, not {type(value)!r}") + + if self._size > TOO_LARGE_BYTES_BODY: + kwargs = {"source": self} + warnings.warn( + "Sending a large body directly with raw bytes might" + " lock the event loop. You should probably pass an " + "io.BytesIO object instead", + ResourceWarning, + **kwargs, + ) + + def decode(self, encoding: str = "utf-8", errors: str = "strict") -> str: + return self._value.decode(encoding, errors) + + async def as_bytes(self, encoding: str = "utf-8", errors: str = "strict") -> bytes: + """ + Return bytes representation of the value. + + This method returns the raw bytes content of the payload. + It is equivalent to accessing the _value attribute directly. + """ + return self._value + + async def write(self, writer: AbstractStreamWriter) -> None: + """ + Write the entire bytes payload to the writer stream. + + Args: + writer: An AbstractStreamWriter instance that handles the actual writing + + This method writes the entire bytes content without any length constraint. + + Note: + For new implementations that need length control, use write_with_length(). + This method is maintained for backwards compatibility and is equivalent + to write_with_length(writer, None). + + """ + await writer.write(self._value) + + async def write_with_length( + self, writer: AbstractStreamWriter, content_length: Optional[int] + ) -> None: + """ + Write bytes payload with a specific content length constraint. + + Args: + writer: An AbstractStreamWriter instance that handles the actual writing + content_length: Maximum number of bytes to write (None for unlimited) + + This method writes either the entire byte sequence or a slice of it + up to the specified content_length. For BytesPayload, this operation + is performed efficiently using array slicing. + + """ + if content_length is not None: + await writer.write(self._value[:content_length]) + else: + await writer.write(self._value) + + +class StringPayload(BytesPayload): + def __init__( + self, + value: str, + *args: Any, + encoding: Optional[str] = None, + content_type: Optional[str] = None, + **kwargs: Any, + ) -> None: + + if encoding is None: + if content_type is None: + real_encoding = "utf-8" + content_type = "text/plain; charset=utf-8" + else: + mimetype = parse_mimetype(content_type) + real_encoding = mimetype.parameters.get("charset", "utf-8") + else: + if content_type is None: + content_type = "text/plain; charset=%s" % encoding + real_encoding = encoding + + super().__init__( + value.encode(real_encoding), + encoding=real_encoding, + content_type=content_type, + *args, + **kwargs, + ) + + +class StringIOPayload(StringPayload): + def __init__(self, value: IO[str], *args: Any, **kwargs: Any) -> None: + super().__init__(value.read(), *args, **kwargs) + + +class IOBasePayload(Payload): + _value: io.IOBase + # _consumed = False (inherited) - File can be re-read from the same position + _start_position: Optional[int] = None + # _autoclose = False (inherited) - Has file handle that needs explicit closing + + def __init__( + self, value: IO[Any], disposition: str = "attachment", *args: Any, **kwargs: Any + ) -> None: + if "filename" not in kwargs: + kwargs["filename"] = guess_filename(value) + + super().__init__(value, *args, **kwargs) + + if self._filename is not None and disposition is not None: + if hdrs.CONTENT_DISPOSITION not in self.headers: + self.set_content_disposition(disposition, filename=self._filename) + + def _set_or_restore_start_position(self) -> None: + """Set or restore the start position of the file-like object.""" + if self._start_position is None: + try: + self._start_position = self._value.tell() + except (OSError, AttributeError): + self._consumed = True # Cannot seek, mark as consumed + return + try: + self._value.seek(self._start_position) + except (OSError, AttributeError): + # Failed to seek back - mark as consumed since we've already read + self._consumed = True + + def _read_and_available_len( + self, remaining_content_len: Optional[int] + ) -> Tuple[Optional[int], bytes]: + """ + Read the file-like object and return both its total size and the first chunk. + + Args: + remaining_content_len: Optional limit on how many bytes to read in this operation. + If None, READ_SIZE will be used as the default chunk size. + + Returns: + A tuple containing: + - The total size of the remaining unread content (None if size cannot be determined) + - The first chunk of bytes read from the file object + + This method is optimized to perform both size calculation and initial read + in a single operation, which is executed in a single executor job to minimize + context switches and file operations when streaming content. + + """ + self._set_or_restore_start_position() + size = self.size # Call size only once since it does I/O + return size, self._value.read( + min(READ_SIZE, size or READ_SIZE, remaining_content_len or READ_SIZE) + ) + + def _read(self, remaining_content_len: Optional[int]) -> bytes: + """ + Read a chunk of data from the file-like object. + + Args: + remaining_content_len: Optional maximum number of bytes to read. + If None, READ_SIZE will be used as the default chunk size. + + Returns: + A chunk of bytes read from the file object, respecting the + remaining_content_len limit if specified. + + This method is used for subsequent reads during streaming after + the initial _read_and_available_len call has been made. + + """ + return self._value.read(remaining_content_len or READ_SIZE) # type: ignore[no-any-return] + + @property + def size(self) -> Optional[int]: + """ + Size of the payload in bytes. + + Returns the total size of the payload content from the initial position. + This ensures consistent Content-Length for requests, including 307/308 redirects + where the same payload instance is reused. + + Returns None if the size cannot be determined (e.g., for unseekable streams). + """ + try: + # Store the start position on first access. + # This is critical when the same payload instance is reused (e.g., 307/308 + # redirects). Without storing the initial position, after the payload is + # read once, the file position would be at EOF, which would cause the + # size calculation to return 0 (file_size - EOF position). + # By storing the start position, we ensure the size calculation always + # returns the correct total size for any subsequent use. + if self._start_position is None: + self._start_position = self._value.tell() + + # Return the total size from the start position + # This ensures Content-Length is correct even after reading + return os.fstat(self._value.fileno()).st_size - self._start_position + except (AttributeError, OSError): + return None + + async def write(self, writer: AbstractStreamWriter) -> None: + """ + Write the entire file-like payload to the writer stream. + + Args: + writer: An AbstractStreamWriter instance that handles the actual writing + + This method writes the entire file content without any length constraint. + It delegates to write_with_length() with no length limit for implementation + consistency. + + Note: + For new implementations that need length control, use write_with_length() directly. + This method is maintained for backwards compatibility with existing code. + + """ + await self.write_with_length(writer, None) + + async def write_with_length( + self, writer: AbstractStreamWriter, content_length: Optional[int] + ) -> None: + """ + Write file-like payload with a specific content length constraint. + + Args: + writer: An AbstractStreamWriter instance that handles the actual writing + content_length: Maximum number of bytes to write (None for unlimited) + + This method implements optimized streaming of file content with length constraints: + + 1. File reading is performed in a thread pool to avoid blocking the event loop + 2. Content is read and written in chunks to maintain memory efficiency + 3. Writing stops when either: + - All available file content has been written (when size is known) + - The specified content_length has been reached + 4. File resources are properly closed even if the operation is cancelled + + The implementation carefully handles both known-size and unknown-size payloads, + as well as constrained and unconstrained content lengths. + + """ + loop = asyncio.get_running_loop() + total_written_len = 0 + remaining_content_len = content_length + + # Get initial data and available length + available_len, chunk = await loop.run_in_executor( + None, self._read_and_available_len, remaining_content_len + ) + # Process data chunks until done + while chunk: + chunk_len = len(chunk) + + # Write data with or without length constraint + if remaining_content_len is None: + await writer.write(chunk) + else: + await writer.write(chunk[:remaining_content_len]) + remaining_content_len -= chunk_len + + total_written_len += chunk_len + + # Check if we're done writing + if self._should_stop_writing( + available_len, total_written_len, remaining_content_len + ): + return + + # Read next chunk + chunk = await loop.run_in_executor( + None, + self._read, + ( + min(READ_SIZE, remaining_content_len) + if remaining_content_len is not None + else READ_SIZE + ), + ) + + def _should_stop_writing( + self, + available_len: Optional[int], + total_written_len: int, + remaining_content_len: Optional[int], + ) -> bool: + """ + Determine if we should stop writing data. + + Args: + available_len: Known size of the payload if available (None if unknown) + total_written_len: Number of bytes already written + remaining_content_len: Remaining bytes to be written for content-length limited responses + + Returns: + True if we should stop writing data, based on either: + - Having written all available data (when size is known) + - Having written all requested content (when content-length is specified) + + """ + return (available_len is not None and total_written_len >= available_len) or ( + remaining_content_len is not None and remaining_content_len <= 0 + ) + + def _close(self) -> None: + """ + Async safe synchronous close operations for backwards compatibility. + + This method exists only for backwards + compatibility. Use the async close() method instead. + + WARNING: This method MUST be called from within an event loop. + Calling it outside an event loop will raise RuntimeError. + """ + # Skip if already consumed + if self._consumed: + return + self._consumed = True # Mark as consumed to prevent further writes + # Schedule file closing without awaiting to prevent cancellation issues + loop = asyncio.get_running_loop() + close_future = loop.run_in_executor(None, self._value.close) + # Hold a strong reference to the future to prevent it from being + # garbage collected before it completes. + _CLOSE_FUTURES.add(close_future) + close_future.add_done_callback(_CLOSE_FUTURES.remove) + + async def close(self) -> None: + """ + Close the payload if it holds any resources. + + IMPORTANT: This method must not await anything that might not finish + immediately, as it may be called during cleanup/cancellation. Schedule + any long-running operations without awaiting them. + """ + self._close() + + def decode(self, encoding: str = "utf-8", errors: str = "strict") -> str: + """ + Return string representation of the value. + + WARNING: This method does blocking I/O and should not be called in the event loop. + """ + return self._read_all().decode(encoding, errors) + + def _read_all(self) -> bytes: + """Read the entire file-like object and return its content as bytes.""" + self._set_or_restore_start_position() + # Use readlines() to ensure we get all content + return b"".join(self._value.readlines()) + + async def as_bytes(self, encoding: str = "utf-8", errors: str = "strict") -> bytes: + """ + Return bytes representation of the value. + + This method reads the entire file content and returns it as bytes. + It is equivalent to reading the file-like object directly. + The file reading is performed in an executor to avoid blocking the event loop. + """ + loop = asyncio.get_running_loop() + return await loop.run_in_executor(None, self._read_all) + + +class TextIOPayload(IOBasePayload): + _value: io.TextIOBase + # _autoclose = False (inherited) - Has text file handle that needs explicit closing + + def __init__( + self, + value: TextIO, + *args: Any, + encoding: Optional[str] = None, + content_type: Optional[str] = None, + **kwargs: Any, + ) -> None: + + if encoding is None: + if content_type is None: + encoding = "utf-8" + content_type = "text/plain; charset=utf-8" + else: + mimetype = parse_mimetype(content_type) + encoding = mimetype.parameters.get("charset", "utf-8") + else: + if content_type is None: + content_type = "text/plain; charset=%s" % encoding + + super().__init__( + value, + content_type=content_type, + encoding=encoding, + *args, + **kwargs, + ) + + def _read_and_available_len( + self, remaining_content_len: Optional[int] + ) -> Tuple[Optional[int], bytes]: + """ + Read the text file-like object and return both its total size and the first chunk. + + Args: + remaining_content_len: Optional limit on how many bytes to read in this operation. + If None, READ_SIZE will be used as the default chunk size. + + Returns: + A tuple containing: + - The total size of the remaining unread content (None if size cannot be determined) + - The first chunk of bytes read from the file object, encoded using the payload's encoding + + This method is optimized to perform both size calculation and initial read + in a single operation, which is executed in a single executor job to minimize + context switches and file operations when streaming content. + + Note: + TextIOPayload handles encoding of the text content before writing it + to the stream. If no encoding is specified, UTF-8 is used as the default. + + """ + self._set_or_restore_start_position() + size = self.size + chunk = self._value.read( + min(READ_SIZE, size or READ_SIZE, remaining_content_len or READ_SIZE) + ) + return size, chunk.encode(self._encoding) if self._encoding else chunk.encode() + + def _read(self, remaining_content_len: Optional[int]) -> bytes: + """ + Read a chunk of data from the text file-like object. + + Args: + remaining_content_len: Optional maximum number of bytes to read. + If None, READ_SIZE will be used as the default chunk size. + + Returns: + A chunk of bytes read from the file object and encoded using the payload's + encoding. The data is automatically converted from text to bytes. + + This method is used for subsequent reads during streaming after + the initial _read_and_available_len call has been made. It properly + handles text encoding, converting the text content to bytes using + the specified encoding (or UTF-8 if none was provided). + + """ + chunk = self._value.read(remaining_content_len or READ_SIZE) + return chunk.encode(self._encoding) if self._encoding else chunk.encode() + + def decode(self, encoding: str = "utf-8", errors: str = "strict") -> str: + """ + Return string representation of the value. + + WARNING: This method does blocking I/O and should not be called in the event loop. + """ + self._set_or_restore_start_position() + return self._value.read() + + async def as_bytes(self, encoding: str = "utf-8", errors: str = "strict") -> bytes: + """ + Return bytes representation of the value. + + This method reads the entire text file content and returns it as bytes. + It encodes the text content using the specified encoding. + The file reading is performed in an executor to avoid blocking the event loop. + """ + loop = asyncio.get_running_loop() + + # Use instance encoding if available, otherwise use parameter + actual_encoding = self._encoding or encoding + + def _read_and_encode() -> bytes: + self._set_or_restore_start_position() + # TextIO read() always returns the full content + return self._value.read().encode(actual_encoding, errors) + + return await loop.run_in_executor(None, _read_and_encode) + + +class BytesIOPayload(IOBasePayload): + _value: io.BytesIO + _size: int # Always initialized in __init__ + _autoclose = True # BytesIO is in-memory, safe to auto-close + + def __init__(self, value: io.BytesIO, *args: Any, **kwargs: Any) -> None: + super().__init__(value, *args, **kwargs) + # Calculate size once during initialization + self._size = len(self._value.getbuffer()) - self._value.tell() + + @property + def size(self) -> int: + """Size of the payload in bytes. + + Returns the number of bytes in the BytesIO buffer that will be transmitted. + This is calculated once during initialization for efficiency. + """ + return self._size + + def decode(self, encoding: str = "utf-8", errors: str = "strict") -> str: + self._set_or_restore_start_position() + return self._value.read().decode(encoding, errors) + + async def write(self, writer: AbstractStreamWriter) -> None: + return await self.write_with_length(writer, None) + + async def write_with_length( + self, writer: AbstractStreamWriter, content_length: Optional[int] + ) -> None: + """ + Write BytesIO payload with a specific content length constraint. + + Args: + writer: An AbstractStreamWriter instance that handles the actual writing + content_length: Maximum number of bytes to write (None for unlimited) + + This implementation is specifically optimized for BytesIO objects: + + 1. Reads content in chunks to maintain memory efficiency + 2. Yields control back to the event loop periodically to prevent blocking + when dealing with large BytesIO objects + 3. Respects content_length constraints when specified + 4. Properly cleans up by closing the BytesIO object when done or on error + + The periodic yielding to the event loop is important for maintaining + responsiveness when processing large in-memory buffers. + + """ + self._set_or_restore_start_position() + loop_count = 0 + remaining_bytes = content_length + while chunk := self._value.read(READ_SIZE): + if loop_count > 0: + # Avoid blocking the event loop + # if they pass a large BytesIO object + # and we are not in the first iteration + # of the loop + await asyncio.sleep(0) + if remaining_bytes is None: + await writer.write(chunk) + else: + await writer.write(chunk[:remaining_bytes]) + remaining_bytes -= len(chunk) + if remaining_bytes <= 0: + return + loop_count += 1 + + async def as_bytes(self, encoding: str = "utf-8", errors: str = "strict") -> bytes: + """ + Return bytes representation of the value. + + This method reads the entire BytesIO content and returns it as bytes. + It is equivalent to accessing the _value attribute directly. + """ + self._set_or_restore_start_position() + return self._value.read() + + async def close(self) -> None: + """ + Close the BytesIO payload. + + This does nothing since BytesIO is in-memory and does not require explicit closing. + """ + + +class BufferedReaderPayload(IOBasePayload): + _value: io.BufferedIOBase + # _autoclose = False (inherited) - Has buffered file handle that needs explicit closing + + def decode(self, encoding: str = "utf-8", errors: str = "strict") -> str: + self._set_or_restore_start_position() + return self._value.read().decode(encoding, errors) + + +class JsonPayload(BytesPayload): + def __init__( + self, + value: Any, + encoding: str = "utf-8", + content_type: str = "application/json", + dumps: JSONEncoder = json.dumps, + *args: Any, + **kwargs: Any, + ) -> None: + + super().__init__( + dumps(value).encode(encoding), + content_type=content_type, + encoding=encoding, + *args, + **kwargs, + ) + + +if TYPE_CHECKING: + from typing import AsyncIterable, AsyncIterator + + _AsyncIterator = AsyncIterator[bytes] + _AsyncIterable = AsyncIterable[bytes] +else: + from collections.abc import AsyncIterable, AsyncIterator + + _AsyncIterator = AsyncIterator + _AsyncIterable = AsyncIterable + + +class AsyncIterablePayload(Payload): + + _iter: Optional[_AsyncIterator] = None + _value: _AsyncIterable + _cached_chunks: Optional[List[bytes]] = None + # _consumed stays False to allow reuse with cached content + _autoclose = True # Iterator doesn't need explicit closing + + def __init__(self, value: _AsyncIterable, *args: Any, **kwargs: Any) -> None: + if not isinstance(value, AsyncIterable): + raise TypeError( + "value argument must support " + "collections.abc.AsyncIterable interface, " + "got {!r}".format(type(value)) + ) + + if "content_type" not in kwargs: + kwargs["content_type"] = "application/octet-stream" + + super().__init__(value, *args, **kwargs) + + self._iter = value.__aiter__() + + async def write(self, writer: AbstractStreamWriter) -> None: + """ + Write the entire async iterable payload to the writer stream. + + Args: + writer: An AbstractStreamWriter instance that handles the actual writing + + This method iterates through the async iterable and writes each chunk + to the writer without any length constraint. + + Note: + For new implementations that need length control, use write_with_length() directly. + This method is maintained for backwards compatibility with existing code. + + """ + await self.write_with_length(writer, None) + + async def write_with_length( + self, writer: AbstractStreamWriter, content_length: Optional[int] + ) -> None: + """ + Write async iterable payload with a specific content length constraint. + + Args: + writer: An AbstractStreamWriter instance that handles the actual writing + content_length: Maximum number of bytes to write (None for unlimited) + + This implementation handles streaming of async iterable content with length constraints: + + 1. If cached chunks are available, writes from them + 2. Otherwise iterates through the async iterable one chunk at a time + 3. Respects content_length constraints when specified + 4. Does NOT generate cache - that's done by as_bytes() + + """ + # If we have cached chunks, use them + if self._cached_chunks is not None: + remaining_bytes = content_length + for chunk in self._cached_chunks: + if remaining_bytes is None: + await writer.write(chunk) + elif remaining_bytes > 0: + await writer.write(chunk[:remaining_bytes]) + remaining_bytes -= len(chunk) + else: + break + return + + # If iterator is exhausted and we don't have cached chunks, nothing to write + if self._iter is None: + return + + # Stream from the iterator + remaining_bytes = content_length + + try: + while True: + if sys.version_info >= (3, 10): + chunk = await anext(self._iter) + else: + chunk = await self._iter.__anext__() + if remaining_bytes is None: + await writer.write(chunk) + # If we have a content length limit + elif remaining_bytes > 0: + await writer.write(chunk[:remaining_bytes]) + remaining_bytes -= len(chunk) + # We still want to exhaust the iterator even + # if we have reached the content length limit + # since the file handle may not get closed by + # the iterator if we don't do this + except StopAsyncIteration: + # Iterator is exhausted + self._iter = None + self._consumed = True # Mark as consumed when streamed without caching + + def decode(self, encoding: str = "utf-8", errors: str = "strict") -> str: + """Decode the payload content as a string if cached chunks are available.""" + if self._cached_chunks is not None: + return b"".join(self._cached_chunks).decode(encoding, errors) + raise TypeError("Unable to decode - content not cached. Call as_bytes() first.") + + async def as_bytes(self, encoding: str = "utf-8", errors: str = "strict") -> bytes: + """ + Return bytes representation of the value. + + This method reads the entire async iterable content and returns it as bytes. + It generates and caches the chunks for future reuse. + """ + # If we have cached chunks, return them joined + if self._cached_chunks is not None: + return b"".join(self._cached_chunks) + + # If iterator is exhausted and no cache, return empty + if self._iter is None: + return b"" + + # Read all chunks and cache them + chunks: List[bytes] = [] + async for chunk in self._iter: + chunks.append(chunk) + + # Iterator is exhausted, cache the chunks + self._iter = None + self._cached_chunks = chunks + # Keep _consumed as False to allow reuse with cached chunks + + return b"".join(chunks) + + +class StreamReaderPayload(AsyncIterablePayload): + def __init__(self, value: StreamReader, *args: Any, **kwargs: Any) -> None: + super().__init__(value.iter_any(), *args, **kwargs) + + +PAYLOAD_REGISTRY = PayloadRegistry() +PAYLOAD_REGISTRY.register(BytesPayload, (bytes, bytearray, memoryview)) +PAYLOAD_REGISTRY.register(StringPayload, str) +PAYLOAD_REGISTRY.register(StringIOPayload, io.StringIO) +PAYLOAD_REGISTRY.register(TextIOPayload, io.TextIOBase) +PAYLOAD_REGISTRY.register(BytesIOPayload, io.BytesIO) +PAYLOAD_REGISTRY.register(BufferedReaderPayload, (io.BufferedReader, io.BufferedRandom)) +PAYLOAD_REGISTRY.register(IOBasePayload, io.IOBase) +PAYLOAD_REGISTRY.register(StreamReaderPayload, StreamReader) +# try_last for giving a chance to more specialized async interables like +# multipart.BodyPartReaderPayload override the default +PAYLOAD_REGISTRY.register(AsyncIterablePayload, AsyncIterable, order=Order.try_last) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/payload_streamer.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/payload_streamer.py new file mode 100644 index 0000000000000000000000000000000000000000..831fdc0a77f302acaf9a000be408fe7c9a9035aa --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/payload_streamer.py @@ -0,0 +1,78 @@ +""" +Payload implementation for coroutines as data provider. + +As a simple case, you can upload data from file:: + + @aiohttp.streamer + async def file_sender(writer, file_name=None): + with open(file_name, 'rb') as f: + chunk = f.read(2**16) + while chunk: + await writer.write(chunk) + + chunk = f.read(2**16) + +Then you can use `file_sender` like this: + + async with session.post('http://httpbin.org/post', + data=file_sender(file_name='huge_file')) as resp: + print(await resp.text()) + +..note:: Coroutine must accept `writer` as first argument + +""" + +import types +import warnings +from typing import Any, Awaitable, Callable, Dict, Tuple + +from .abc import AbstractStreamWriter +from .payload import Payload, payload_type + +__all__ = ("streamer",) + + +class _stream_wrapper: + def __init__( + self, + coro: Callable[..., Awaitable[None]], + args: Tuple[Any, ...], + kwargs: Dict[str, Any], + ) -> None: + self.coro = types.coroutine(coro) + self.args = args + self.kwargs = kwargs + + async def __call__(self, writer: AbstractStreamWriter) -> None: + await self.coro(writer, *self.args, **self.kwargs) + + +class streamer: + def __init__(self, coro: Callable[..., Awaitable[None]]) -> None: + warnings.warn( + "@streamer is deprecated, use async generators instead", + DeprecationWarning, + stacklevel=2, + ) + self.coro = coro + + def __call__(self, *args: Any, **kwargs: Any) -> _stream_wrapper: + return _stream_wrapper(self.coro, args, kwargs) + + +@payload_type(_stream_wrapper) +class StreamWrapperPayload(Payload): + async def write(self, writer: AbstractStreamWriter) -> None: + await self._value(writer) + + def decode(self, encoding: str = "utf-8", errors: str = "strict") -> str: + raise TypeError("Unable to decode.") + + +@payload_type(streamer) +class StreamPayload(StreamWrapperPayload): + def __init__(self, value: Any, *args: Any, **kwargs: Any) -> None: + super().__init__(value(), *args, **kwargs) + + async def write(self, writer: AbstractStreamWriter) -> None: + await self._value(writer) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/py.typed b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/py.typed new file mode 100644 index 0000000000000000000000000000000000000000..f5642f79f21d872f010979dcf6f0c4a415acc19d --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/py.typed @@ -0,0 +1 @@ +Marker diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/pytest_plugin.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/pytest_plugin.py new file mode 100644 index 0000000000000000000000000000000000000000..7d59fe820d697632c9a3311ff96841ebc0ee735b --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/pytest_plugin.py @@ -0,0 +1,444 @@ +import asyncio +import contextlib +import inspect +import warnings +from typing import ( + Any, + Awaitable, + Callable, + Dict, + Iterator, + Optional, + Protocol, + Union, + overload, +) + +import pytest + +from .test_utils import ( + BaseTestServer, + RawTestServer, + TestClient, + TestServer, + loop_context, + setup_test_loop, + teardown_test_loop, + unused_port as _unused_port, +) +from .web import Application, BaseRequest, Request +from .web_protocol import _RequestHandler + +try: + import uvloop +except ImportError: # pragma: no cover + uvloop = None # type: ignore[assignment] + + +class AiohttpClient(Protocol): + @overload + async def __call__( + self, + __param: Application, + *, + server_kwargs: Optional[Dict[str, Any]] = None, + **kwargs: Any, + ) -> TestClient[Request, Application]: ... + @overload + async def __call__( + self, + __param: BaseTestServer, + *, + server_kwargs: Optional[Dict[str, Any]] = None, + **kwargs: Any, + ) -> TestClient[BaseRequest, None]: ... + + +class AiohttpServer(Protocol): + def __call__( + self, app: Application, *, port: Optional[int] = None, **kwargs: Any + ) -> Awaitable[TestServer]: ... + + +class AiohttpRawServer(Protocol): + def __call__( + self, handler: _RequestHandler, *, port: Optional[int] = None, **kwargs: Any + ) -> Awaitable[RawTestServer]: ... + + +def pytest_addoption(parser): # type: ignore[no-untyped-def] + parser.addoption( + "--aiohttp-fast", + action="store_true", + default=False, + help="run tests faster by disabling extra checks", + ) + parser.addoption( + "--aiohttp-loop", + action="store", + default="pyloop", + help="run tests with specific loop: pyloop, uvloop or all", + ) + parser.addoption( + "--aiohttp-enable-loop-debug", + action="store_true", + default=False, + help="enable event loop debug mode", + ) + + +def pytest_fixture_setup(fixturedef): # type: ignore[no-untyped-def] + """Set up pytest fixture. + + Allow fixtures to be coroutines. Run coroutine fixtures in an event loop. + """ + func = fixturedef.func + + if inspect.isasyncgenfunction(func): + # async generator fixture + is_async_gen = True + elif inspect.iscoroutinefunction(func): + # regular async fixture + is_async_gen = False + else: + # not an async fixture, nothing to do + return + + strip_request = False + if "request" not in fixturedef.argnames: + fixturedef.argnames += ("request",) + strip_request = True + + def wrapper(*args, **kwargs): # type: ignore[no-untyped-def] + request = kwargs["request"] + if strip_request: + del kwargs["request"] + + # if neither the fixture nor the test use the 'loop' fixture, + # 'getfixturevalue' will fail because the test is not parameterized + # (this can be removed someday if 'loop' is no longer parameterized) + if "loop" not in request.fixturenames: + raise Exception( + "Asynchronous fixtures must depend on the 'loop' fixture or " + "be used in tests depending from it." + ) + + _loop = request.getfixturevalue("loop") + + if is_async_gen: + # for async generators, we need to advance the generator once, + # then advance it again in a finalizer + gen = func(*args, **kwargs) + + def finalizer(): # type: ignore[no-untyped-def] + try: + return _loop.run_until_complete(gen.__anext__()) + except StopAsyncIteration: + pass + + request.addfinalizer(finalizer) + return _loop.run_until_complete(gen.__anext__()) + else: + return _loop.run_until_complete(func(*args, **kwargs)) + + fixturedef.func = wrapper + + +@pytest.fixture +def fast(request): # type: ignore[no-untyped-def] + """--fast config option""" + return request.config.getoption("--aiohttp-fast") + + +@pytest.fixture +def loop_debug(request): # type: ignore[no-untyped-def] + """--enable-loop-debug config option""" + return request.config.getoption("--aiohttp-enable-loop-debug") + + +@contextlib.contextmanager +def _runtime_warning_context(): # type: ignore[no-untyped-def] + """Context manager which checks for RuntimeWarnings. + + This exists specifically to + avoid "coroutine 'X' was never awaited" warnings being missed. + + If RuntimeWarnings occur in the context a RuntimeError is raised. + """ + with warnings.catch_warnings(record=True) as _warnings: + yield + rw = [ + "{w.filename}:{w.lineno}:{w.message}".format(w=w) + for w in _warnings + if w.category == RuntimeWarning + ] + if rw: + raise RuntimeError( + "{} Runtime Warning{},\n{}".format( + len(rw), "" if len(rw) == 1 else "s", "\n".join(rw) + ) + ) + + +@contextlib.contextmanager +def _passthrough_loop_context(loop, fast=False): # type: ignore[no-untyped-def] + """Passthrough loop context. + + Sets up and tears down a loop unless one is passed in via the loop + argument when it's passed straight through. + """ + if loop: + # loop already exists, pass it straight through + yield loop + else: + # this shadows loop_context's standard behavior + loop = setup_test_loop() + yield loop + teardown_test_loop(loop, fast=fast) + + +def pytest_pycollect_makeitem(collector, name, obj): # type: ignore[no-untyped-def] + """Fix pytest collecting for coroutines.""" + if collector.funcnamefilter(name) and inspect.iscoroutinefunction(obj): + return list(collector._genfunctions(name, obj)) + + +def pytest_pyfunc_call(pyfuncitem): # type: ignore[no-untyped-def] + """Run coroutines in an event loop instead of a normal function call.""" + fast = pyfuncitem.config.getoption("--aiohttp-fast") + if inspect.iscoroutinefunction(pyfuncitem.function): + existing_loop = ( + pyfuncitem.funcargs.get("proactor_loop") + or pyfuncitem.funcargs.get("selector_loop") + or pyfuncitem.funcargs.get("uvloop_loop") + or pyfuncitem.funcargs.get("loop", None) + ) + + with _runtime_warning_context(): + with _passthrough_loop_context(existing_loop, fast=fast) as _loop: + testargs = { + arg: pyfuncitem.funcargs[arg] + for arg in pyfuncitem._fixtureinfo.argnames + } + _loop.run_until_complete(pyfuncitem.obj(**testargs)) + + return True + + +def pytest_generate_tests(metafunc): # type: ignore[no-untyped-def] + if "loop_factory" not in metafunc.fixturenames: + return + + loops = metafunc.config.option.aiohttp_loop + avail_factories: dict[str, Callable[[], asyncio.AbstractEventLoop]] + avail_factories = {"pyloop": asyncio.new_event_loop} + + if uvloop is not None: # pragma: no cover + avail_factories["uvloop"] = uvloop.new_event_loop + + if loops == "all": + loops = "pyloop,uvloop?" + + factories = {} # type: ignore[var-annotated] + for name in loops.split(","): + required = not name.endswith("?") + name = name.strip(" ?") + if name not in avail_factories: # pragma: no cover + if required: + raise ValueError( + "Unknown loop '%s', available loops: %s" + % (name, list(factories.keys())) + ) + else: + continue + factories[name] = avail_factories[name] + metafunc.parametrize( + "loop_factory", list(factories.values()), ids=list(factories.keys()) + ) + + +@pytest.fixture +def loop( + loop_factory: Callable[[], asyncio.AbstractEventLoop], + fast: bool, + loop_debug: bool, +) -> Iterator[asyncio.AbstractEventLoop]: + """Return an instance of the event loop.""" + with loop_context(loop_factory, fast=fast) as _loop: + if loop_debug: + _loop.set_debug(True) # pragma: no cover + asyncio.set_event_loop(_loop) + yield _loop + + +@pytest.fixture +def proactor_loop() -> Iterator[asyncio.AbstractEventLoop]: + factory = asyncio.ProactorEventLoop # type: ignore[attr-defined] + + with loop_context(factory) as _loop: + asyncio.set_event_loop(_loop) + yield _loop + + +@pytest.fixture +def unused_port(aiohttp_unused_port: Callable[[], int]) -> Callable[[], int]: + warnings.warn( + "Deprecated, use aiohttp_unused_port fixture instead", + DeprecationWarning, + stacklevel=2, + ) + return aiohttp_unused_port + + +@pytest.fixture +def aiohttp_unused_port() -> Callable[[], int]: + """Return a port that is unused on the current host.""" + return _unused_port + + +@pytest.fixture +def aiohttp_server(loop: asyncio.AbstractEventLoop) -> Iterator[AiohttpServer]: + """Factory to create a TestServer instance, given an app. + + aiohttp_server(app, **kwargs) + """ + servers = [] + + async def go( + app: Application, + *, + host: str = "127.0.0.1", + port: Optional[int] = None, + **kwargs: Any, + ) -> TestServer: + server = TestServer(app, host=host, port=port) + await server.start_server(loop=loop, **kwargs) + servers.append(server) + return server + + yield go + + async def finalize() -> None: + while servers: + await servers.pop().close() + + loop.run_until_complete(finalize()) + + +@pytest.fixture +def test_server(aiohttp_server): # type: ignore[no-untyped-def] # pragma: no cover + warnings.warn( + "Deprecated, use aiohttp_server fixture instead", + DeprecationWarning, + stacklevel=2, + ) + return aiohttp_server + + +@pytest.fixture +def aiohttp_raw_server(loop: asyncio.AbstractEventLoop) -> Iterator[AiohttpRawServer]: + """Factory to create a RawTestServer instance, given a web handler. + + aiohttp_raw_server(handler, **kwargs) + """ + servers = [] + + async def go( + handler: _RequestHandler, *, port: Optional[int] = None, **kwargs: Any + ) -> RawTestServer: + server = RawTestServer(handler, port=port) + await server.start_server(loop=loop, **kwargs) + servers.append(server) + return server + + yield go + + async def finalize() -> None: + while servers: + await servers.pop().close() + + loop.run_until_complete(finalize()) + + +@pytest.fixture +def raw_test_server( # type: ignore[no-untyped-def] # pragma: no cover + aiohttp_raw_server, +): + warnings.warn( + "Deprecated, use aiohttp_raw_server fixture instead", + DeprecationWarning, + stacklevel=2, + ) + return aiohttp_raw_server + + +@pytest.fixture +def aiohttp_client(loop: asyncio.AbstractEventLoop) -> Iterator[AiohttpClient]: + """Factory to create a TestClient instance. + + aiohttp_client(app, **kwargs) + aiohttp_client(server, **kwargs) + aiohttp_client(raw_server, **kwargs) + """ + clients = [] + + @overload + async def go( + __param: Application, + *, + server_kwargs: Optional[Dict[str, Any]] = None, + **kwargs: Any, + ) -> TestClient[Request, Application]: ... + + @overload + async def go( + __param: BaseTestServer, + *, + server_kwargs: Optional[Dict[str, Any]] = None, + **kwargs: Any, + ) -> TestClient[BaseRequest, None]: ... + + async def go( + __param: Union[Application, BaseTestServer], + *args: Any, + server_kwargs: Optional[Dict[str, Any]] = None, + **kwargs: Any, + ) -> TestClient[Any, Any]: + if isinstance(__param, Callable) and not isinstance( # type: ignore[arg-type] + __param, (Application, BaseTestServer) + ): + __param = __param(loop, *args, **kwargs) + kwargs = {} + else: + assert not args, "args should be empty" + + if isinstance(__param, Application): + server_kwargs = server_kwargs or {} + server = TestServer(__param, loop=loop, **server_kwargs) + client = TestClient(server, loop=loop, **kwargs) + elif isinstance(__param, BaseTestServer): + client = TestClient(__param, loop=loop, **kwargs) + else: + raise ValueError("Unknown argument type: %r" % type(__param)) + + await client.start_server() + clients.append(client) + return client + + yield go + + async def finalize() -> None: + while clients: + await clients.pop().close() + + loop.run_until_complete(finalize()) + + +@pytest.fixture +def test_client(aiohttp_client): # type: ignore[no-untyped-def] # pragma: no cover + warnings.warn( + "Deprecated, use aiohttp_client fixture instead", + DeprecationWarning, + stacklevel=2, + ) + return aiohttp_client diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/resolver.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/resolver.py new file mode 100644 index 0000000000000000000000000000000000000000..b20e5672ce51a1aadd7768301a3a1c8eb6007bf4 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/resolver.py @@ -0,0 +1,274 @@ +import asyncio +import socket +import weakref +from typing import Any, Dict, Final, List, Optional, Tuple, Type, Union + +from .abc import AbstractResolver, ResolveResult + +__all__ = ("ThreadedResolver", "AsyncResolver", "DefaultResolver") + + +try: + import aiodns + + aiodns_default = hasattr(aiodns.DNSResolver, "getaddrinfo") +except ImportError: # pragma: no cover + aiodns = None # type: ignore[assignment] + aiodns_default = False + + +_NUMERIC_SOCKET_FLAGS = socket.AI_NUMERICHOST | socket.AI_NUMERICSERV +_NAME_SOCKET_FLAGS = socket.NI_NUMERICHOST | socket.NI_NUMERICSERV +_AI_ADDRCONFIG = socket.AI_ADDRCONFIG +if hasattr(socket, "AI_MASK"): + _AI_ADDRCONFIG &= socket.AI_MASK + + +class ThreadedResolver(AbstractResolver): + """Threaded resolver. + + Uses an Executor for synchronous getaddrinfo() calls. + concurrent.futures.ThreadPoolExecutor is used by default. + """ + + def __init__(self, loop: Optional[asyncio.AbstractEventLoop] = None) -> None: + self._loop = loop or asyncio.get_running_loop() + + async def resolve( + self, host: str, port: int = 0, family: socket.AddressFamily = socket.AF_INET + ) -> List[ResolveResult]: + infos = await self._loop.getaddrinfo( + host, + port, + type=socket.SOCK_STREAM, + family=family, + flags=_AI_ADDRCONFIG, + ) + + hosts: List[ResolveResult] = [] + for family, _, proto, _, address in infos: + if family == socket.AF_INET6: + if len(address) < 3: + # IPv6 is not supported by Python build, + # or IPv6 is not enabled in the host + continue + if address[3]: + # This is essential for link-local IPv6 addresses. + # LL IPv6 is a VERY rare case. Strictly speaking, we should use + # getnameinfo() unconditionally, but performance makes sense. + resolved_host, _port = await self._loop.getnameinfo( + address, _NAME_SOCKET_FLAGS + ) + port = int(_port) + else: + resolved_host, port = address[:2] + else: # IPv4 + assert family == socket.AF_INET + resolved_host, port = address # type: ignore[misc] + hosts.append( + ResolveResult( + hostname=host, + host=resolved_host, + port=port, + family=family, + proto=proto, + flags=_NUMERIC_SOCKET_FLAGS, + ) + ) + + return hosts + + async def close(self) -> None: + pass + + +class AsyncResolver(AbstractResolver): + """Use the `aiodns` package to make asynchronous DNS lookups""" + + def __init__( + self, + loop: Optional[asyncio.AbstractEventLoop] = None, + *args: Any, + **kwargs: Any, + ) -> None: + if aiodns is None: + raise RuntimeError("Resolver requires aiodns library") + + self._loop = loop or asyncio.get_running_loop() + self._manager: Optional[_DNSResolverManager] = None + # If custom args are provided, create a dedicated resolver instance + # This means each AsyncResolver with custom args gets its own + # aiodns.DNSResolver instance + if args or kwargs: + self._resolver = aiodns.DNSResolver(*args, **kwargs) + return + # Use the shared resolver from the manager for default arguments + self._manager = _DNSResolverManager() + self._resolver = self._manager.get_resolver(self, self._loop) + + if not hasattr(self._resolver, "gethostbyname"): + # aiodns 1.1 is not available, fallback to DNSResolver.query + self.resolve = self._resolve_with_query # type: ignore + + async def resolve( + self, host: str, port: int = 0, family: socket.AddressFamily = socket.AF_INET + ) -> List[ResolveResult]: + try: + resp = await self._resolver.getaddrinfo( + host, + port=port, + type=socket.SOCK_STREAM, + family=family, + flags=_AI_ADDRCONFIG, + ) + except aiodns.error.DNSError as exc: + msg = exc.args[1] if len(exc.args) >= 1 else "DNS lookup failed" + raise OSError(None, msg) from exc + hosts: List[ResolveResult] = [] + for node in resp.nodes: + address: Union[Tuple[bytes, int], Tuple[bytes, int, int, int]] = node.addr + family = node.family + if family == socket.AF_INET6: + if len(address) > 3 and address[3]: + # This is essential for link-local IPv6 addresses. + # LL IPv6 is a VERY rare case. Strictly speaking, we should use + # getnameinfo() unconditionally, but performance makes sense. + result = await self._resolver.getnameinfo( + (address[0].decode("ascii"), *address[1:]), + _NAME_SOCKET_FLAGS, + ) + resolved_host = result.node + else: + resolved_host = address[0].decode("ascii") + port = address[1] + else: # IPv4 + assert family == socket.AF_INET + resolved_host = address[0].decode("ascii") + port = address[1] + hosts.append( + ResolveResult( + hostname=host, + host=resolved_host, + port=port, + family=family, + proto=0, + flags=_NUMERIC_SOCKET_FLAGS, + ) + ) + + if not hosts: + raise OSError(None, "DNS lookup failed") + + return hosts + + async def _resolve_with_query( + self, host: str, port: int = 0, family: int = socket.AF_INET + ) -> List[Dict[str, Any]]: + qtype: Final = "AAAA" if family == socket.AF_INET6 else "A" + + try: + resp = await self._resolver.query(host, qtype) + except aiodns.error.DNSError as exc: + msg = exc.args[1] if len(exc.args) >= 1 else "DNS lookup failed" + raise OSError(None, msg) from exc + + hosts = [] + for rr in resp: + hosts.append( + { + "hostname": host, + "host": rr.host, + "port": port, + "family": family, + "proto": 0, + "flags": socket.AI_NUMERICHOST, + } + ) + + if not hosts: + raise OSError(None, "DNS lookup failed") + + return hosts + + async def close(self) -> None: + if self._manager: + # Release the resolver from the manager if using the shared resolver + self._manager.release_resolver(self, self._loop) + self._manager = None # Clear reference to manager + self._resolver = None # type: ignore[assignment] # Clear reference to resolver + return + # Otherwise cancel our dedicated resolver + if self._resolver is not None: + self._resolver.cancel() + self._resolver = None # type: ignore[assignment] # Clear reference + + +class _DNSResolverManager: + """Manager for aiodns.DNSResolver objects. + + This class manages shared aiodns.DNSResolver instances + with no custom arguments across different event loops. + """ + + _instance: Optional["_DNSResolverManager"] = None + + def __new__(cls) -> "_DNSResolverManager": + if cls._instance is None: + cls._instance = super().__new__(cls) + cls._instance._init() + return cls._instance + + def _init(self) -> None: + # Use WeakKeyDictionary to allow event loops to be garbage collected + self._loop_data: weakref.WeakKeyDictionary[ + asyncio.AbstractEventLoop, + tuple["aiodns.DNSResolver", weakref.WeakSet["AsyncResolver"]], + ] = weakref.WeakKeyDictionary() + + def get_resolver( + self, client: "AsyncResolver", loop: asyncio.AbstractEventLoop + ) -> "aiodns.DNSResolver": + """Get or create the shared aiodns.DNSResolver instance for a specific event loop. + + Args: + client: The AsyncResolver instance requesting the resolver. + This is required to track resolver usage. + loop: The event loop to use for the resolver. + """ + # Create a new resolver and client set for this loop if it doesn't exist + if loop not in self._loop_data: + resolver = aiodns.DNSResolver(loop=loop) + client_set: weakref.WeakSet["AsyncResolver"] = weakref.WeakSet() + self._loop_data[loop] = (resolver, client_set) + else: + # Get the existing resolver and client set + resolver, client_set = self._loop_data[loop] + + # Register this client with the loop + client_set.add(client) + return resolver + + def release_resolver( + self, client: "AsyncResolver", loop: asyncio.AbstractEventLoop + ) -> None: + """Release the resolver for an AsyncResolver client when it's closed. + + Args: + client: The AsyncResolver instance to release. + loop: The event loop the resolver was using. + """ + # Remove client from its loop's tracking + current_loop_data = self._loop_data.get(loop) + if current_loop_data is None: + return + resolver, client_set = current_loop_data + client_set.discard(client) + # If no more clients for this loop, cancel and remove its resolver + if not client_set: + if resolver is not None: + resolver.cancel() + del self._loop_data[loop] + + +_DefaultType = Type[Union[AsyncResolver, ThreadedResolver]] +DefaultResolver: _DefaultType = AsyncResolver if aiodns_default else ThreadedResolver diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/streams.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/streams.py new file mode 100644 index 0000000000000000000000000000000000000000..921827eb34300a596ff19b4452c5a392eb85695c --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/streams.py @@ -0,0 +1,762 @@ +import asyncio +import collections +import warnings +from typing import ( + Awaitable, + Callable, + Deque, + Final, + Generic, + List, + Optional, + Tuple, + TypeVar, +) + +from .base_protocol import BaseProtocol +from .helpers import ( + _EXC_SENTINEL, + BaseTimerContext, + TimerNoop, + set_exception, + set_result, +) +from .http_exceptions import LineTooLong +from .log import internal_logger + +__all__ = ( + "EMPTY_PAYLOAD", + "EofStream", + "StreamReader", + "DataQueue", +) + +_T = TypeVar("_T") + + +class EofStream(Exception): + """eof stream indication.""" + + +class AsyncStreamIterator(Generic[_T]): + + __slots__ = ("read_func",) + + def __init__(self, read_func: Callable[[], Awaitable[_T]]) -> None: + self.read_func = read_func + + def __aiter__(self) -> "AsyncStreamIterator[_T]": + return self + + async def __anext__(self) -> _T: + try: + rv = await self.read_func() + except EofStream: + raise StopAsyncIteration + if rv == b"": + raise StopAsyncIteration + return rv + + +class ChunkTupleAsyncStreamIterator: + + __slots__ = ("_stream",) + + def __init__(self, stream: "StreamReader") -> None: + self._stream = stream + + def __aiter__(self) -> "ChunkTupleAsyncStreamIterator": + return self + + async def __anext__(self) -> Tuple[bytes, bool]: + rv = await self._stream.readchunk() + if rv == (b"", False): + raise StopAsyncIteration + return rv + + +class AsyncStreamReaderMixin: + + __slots__ = () + + def __aiter__(self) -> AsyncStreamIterator[bytes]: + return AsyncStreamIterator(self.readline) # type: ignore[attr-defined] + + def iter_chunked(self, n: int) -> AsyncStreamIterator[bytes]: + """Returns an asynchronous iterator that yields chunks of size n.""" + return AsyncStreamIterator(lambda: self.read(n)) # type: ignore[attr-defined] + + def iter_any(self) -> AsyncStreamIterator[bytes]: + """Yield all available data as soon as it is received.""" + return AsyncStreamIterator(self.readany) # type: ignore[attr-defined] + + def iter_chunks(self) -> ChunkTupleAsyncStreamIterator: + """Yield chunks of data as they are received by the server. + + The yielded objects are tuples + of (bytes, bool) as returned by the StreamReader.readchunk method. + """ + return ChunkTupleAsyncStreamIterator(self) # type: ignore[arg-type] + + +class StreamReader(AsyncStreamReaderMixin): + """An enhancement of asyncio.StreamReader. + + Supports asynchronous iteration by line, chunk or as available:: + + async for line in reader: + ... + async for chunk in reader.iter_chunked(1024): + ... + async for slice in reader.iter_any(): + ... + + """ + + __slots__ = ( + "_protocol", + "_low_water", + "_high_water", + "_low_water_chunks", + "_high_water_chunks", + "_loop", + "_size", + "_cursor", + "_http_chunk_splits", + "_buffer", + "_buffer_offset", + "_eof", + "_waiter", + "_eof_waiter", + "_exception", + "_timer", + "_eof_callbacks", + "_eof_counter", + "total_bytes", + "total_compressed_bytes", + ) + + def __init__( + self, + protocol: BaseProtocol, + limit: int, + *, + timer: Optional[BaseTimerContext] = None, + loop: Optional[asyncio.AbstractEventLoop] = None, + ) -> None: + self._protocol = protocol + self._low_water = limit + self._high_water = limit * 2 + if loop is None: + loop = asyncio.get_event_loop() + # Ensure high_water_chunks >= 3 so it's always > low_water_chunks. + self._high_water_chunks = max(3, limit // 4) + # Use max(2, ...) because there's always at least 1 chunk split remaining + # (the current position), so we need low_water >= 2 to allow resume. + self._low_water_chunks = max(2, self._high_water_chunks // 2) + self._loop = loop + self._size = 0 + self._cursor = 0 + self._http_chunk_splits: Optional[Deque[int]] = None + self._buffer: Deque[bytes] = collections.deque() + self._buffer_offset = 0 + self._eof = False + self._waiter: Optional[asyncio.Future[None]] = None + self._eof_waiter: Optional[asyncio.Future[None]] = None + self._exception: Optional[BaseException] = None + self._timer = TimerNoop() if timer is None else timer + self._eof_callbacks: List[Callable[[], None]] = [] + self._eof_counter = 0 + self.total_bytes = 0 + self.total_compressed_bytes: Optional[int] = None + + def __repr__(self) -> str: + info = [self.__class__.__name__] + if self._size: + info.append("%d bytes" % self._size) + if self._eof: + info.append("eof") + if self._low_water != 2**16: # default limit + info.append("low=%d high=%d" % (self._low_water, self._high_water)) + if self._waiter: + info.append("w=%r" % self._waiter) + if self._exception: + info.append("e=%r" % self._exception) + return "<%s>" % " ".join(info) + + def get_read_buffer_limits(self) -> Tuple[int, int]: + return (self._low_water, self._high_water) + + def exception(self) -> Optional[BaseException]: + return self._exception + + def set_exception( + self, + exc: BaseException, + exc_cause: BaseException = _EXC_SENTINEL, + ) -> None: + self._exception = exc + self._eof_callbacks.clear() + + waiter = self._waiter + if waiter is not None: + self._waiter = None + set_exception(waiter, exc, exc_cause) + + waiter = self._eof_waiter + if waiter is not None: + self._eof_waiter = None + set_exception(waiter, exc, exc_cause) + + def on_eof(self, callback: Callable[[], None]) -> None: + if self._eof: + try: + callback() + except Exception: + internal_logger.exception("Exception in eof callback") + else: + self._eof_callbacks.append(callback) + + def feed_eof(self) -> None: + self._eof = True + + waiter = self._waiter + if waiter is not None: + self._waiter = None + set_result(waiter, None) + + waiter = self._eof_waiter + if waiter is not None: + self._eof_waiter = None + set_result(waiter, None) + + if self._protocol._reading_paused: + self._protocol.resume_reading() + + for cb in self._eof_callbacks: + try: + cb() + except Exception: + internal_logger.exception("Exception in eof callback") + + self._eof_callbacks.clear() + + def is_eof(self) -> bool: + """Return True if 'feed_eof' was called.""" + return self._eof + + def at_eof(self) -> bool: + """Return True if the buffer is empty and 'feed_eof' was called.""" + return self._eof and not self._buffer + + async def wait_eof(self) -> None: + if self._eof: + return + + assert self._eof_waiter is None + self._eof_waiter = self._loop.create_future() + try: + await self._eof_waiter + finally: + self._eof_waiter = None + + @property + def total_raw_bytes(self) -> int: + if self.total_compressed_bytes is None: + return self.total_bytes + return self.total_compressed_bytes + + def unread_data(self, data: bytes) -> None: + """rollback reading some data from stream, inserting it to buffer head.""" + warnings.warn( + "unread_data() is deprecated " + "and will be removed in future releases (#3260)", + DeprecationWarning, + stacklevel=2, + ) + if not data: + return + + if self._buffer_offset: + self._buffer[0] = self._buffer[0][self._buffer_offset :] + self._buffer_offset = 0 + self._size += len(data) + self._cursor -= len(data) + self._buffer.appendleft(data) + self._eof_counter = 0 + + # TODO: size is ignored, remove the param later + def feed_data(self, data: bytes, size: int = 0) -> None: + assert not self._eof, "feed_data after feed_eof" + + if not data: + return + + data_len = len(data) + self._size += data_len + self._buffer.append(data) + self.total_bytes += data_len + + waiter = self._waiter + if waiter is not None: + self._waiter = None + set_result(waiter, None) + + if self._size > self._high_water and not self._protocol._reading_paused: + self._protocol.pause_reading() + + def begin_http_chunk_receiving(self) -> None: + if self._http_chunk_splits is None: + if self.total_bytes: + raise RuntimeError( + "Called begin_http_chunk_receiving when some data was already fed" + ) + self._http_chunk_splits = collections.deque() + + def end_http_chunk_receiving(self) -> None: + if self._http_chunk_splits is None: + raise RuntimeError( + "Called end_chunk_receiving without calling " + "begin_chunk_receiving first" + ) + + # self._http_chunk_splits contains logical byte offsets from start of + # the body transfer. Each offset is the offset of the end of a chunk. + # "Logical" means bytes, accessible for a user. + # If no chunks containing logical data were received, current position + # is difinitely zero. + pos = self._http_chunk_splits[-1] if self._http_chunk_splits else 0 + + if self.total_bytes == pos: + # We should not add empty chunks here. So we check for that. + # Note, when chunked + gzip is used, we can receive a chunk + # of compressed data, but that data may not be enough for gzip FSM + # to yield any uncompressed data. That's why current position may + # not change after receiving a chunk. + return + + self._http_chunk_splits.append(self.total_bytes) + + # If we get too many small chunks before self._high_water is reached, then any + # .read() call becomes computationally expensive, and could block the event loop + # for too long, hence an additional self._high_water_chunks here. + if ( + len(self._http_chunk_splits) > self._high_water_chunks + and not self._protocol._reading_paused + ): + self._protocol.pause_reading() + + # wake up readchunk when end of http chunk received + waiter = self._waiter + if waiter is not None: + self._waiter = None + set_result(waiter, None) + + async def _wait(self, func_name: str) -> None: + if not self._protocol.connected: + raise RuntimeError("Connection closed.") + + # StreamReader uses a future to link the protocol feed_data() method + # to a read coroutine. Running two read coroutines at the same time + # would have an unexpected behaviour. It would not possible to know + # which coroutine would get the next data. + if self._waiter is not None: + raise RuntimeError( + "%s() called while another coroutine is " + "already waiting for incoming data" % func_name + ) + + waiter = self._waiter = self._loop.create_future() + try: + with self._timer: + await waiter + finally: + self._waiter = None + + async def readline(self, *, max_line_length: Optional[int] = None) -> bytes: + return await self.readuntil(max_size=max_line_length) + + async def readuntil( + self, separator: bytes = b"\n", *, max_size: Optional[int] = None + ) -> bytes: + seplen = len(separator) + if seplen == 0: + raise ValueError("Separator should be at least one-byte string") + + if self._exception is not None: + raise self._exception + + chunk = b"" + chunk_size = 0 + not_enough = True + max_size = max_size or self._high_water + + while not_enough: + while self._buffer and not_enough: + offset = self._buffer_offset + ichar = self._buffer[0].find(separator, offset) + 1 + # Read from current offset to found separator or to the end. + data = self._read_nowait_chunk( + ichar - offset + seplen - 1 if ichar else -1 + ) + chunk += data + chunk_size += len(data) + if ichar: + not_enough = False + + if chunk_size > max_size: + raise LineTooLong(chunk[:100] + b"...", max_size) + + if self._eof: + break + + if not_enough: + await self._wait("readuntil") + + return chunk + + async def read(self, n: int = -1) -> bytes: + if self._exception is not None: + raise self._exception + + # migration problem; with DataQueue you have to catch + # EofStream exception, so common way is to run payload.read() inside + # infinite loop. what can cause real infinite loop with StreamReader + # lets keep this code one major release. + if __debug__: + if self._eof and not self._buffer: + self._eof_counter = getattr(self, "_eof_counter", 0) + 1 + if self._eof_counter > 5: + internal_logger.warning( + "Multiple access to StreamReader in eof state, " + "might be infinite loop.", + stack_info=True, + ) + + if not n: + return b"" + + if n < 0: + # This used to just loop creating a new waiter hoping to + # collect everything in self._buffer, but that would + # deadlock if the subprocess sends more than self.limit + # bytes. So just call self.readany() until EOF. + blocks = [] + while True: + block = await self.readany() + if not block: + break + blocks.append(block) + return b"".join(blocks) + + # TODO: should be `if` instead of `while` + # because waiter maybe triggered on chunk end, + # without feeding any data + while not self._buffer and not self._eof: + await self._wait("read") + + return self._read_nowait(n) + + async def readany(self) -> bytes: + if self._exception is not None: + raise self._exception + + # TODO: should be `if` instead of `while` + # because waiter maybe triggered on chunk end, + # without feeding any data + while not self._buffer and not self._eof: + await self._wait("readany") + + return self._read_nowait(-1) + + async def readchunk(self) -> Tuple[bytes, bool]: + """Returns a tuple of (data, end_of_http_chunk). + + When chunked transfer + encoding is used, end_of_http_chunk is a boolean indicating if the end + of the data corresponds to the end of a HTTP chunk , otherwise it is + always False. + """ + while True: + if self._exception is not None: + raise self._exception + + while self._http_chunk_splits: + pos = self._http_chunk_splits.popleft() + if pos == self._cursor: + return (b"", True) + if pos > self._cursor: + return (self._read_nowait(pos - self._cursor), True) + internal_logger.warning( + "Skipping HTTP chunk end due to data " + "consumption beyond chunk boundary" + ) + + if self._buffer: + return (self._read_nowait_chunk(-1), False) + # return (self._read_nowait(-1), False) + + if self._eof: + # Special case for signifying EOF. + # (b'', True) is not a final return value actually. + return (b"", False) + + await self._wait("readchunk") + + async def readexactly(self, n: int) -> bytes: + if self._exception is not None: + raise self._exception + + blocks: List[bytes] = [] + while n > 0: + block = await self.read(n) + if not block: + partial = b"".join(blocks) + raise asyncio.IncompleteReadError(partial, len(partial) + n) + blocks.append(block) + n -= len(block) + + return b"".join(blocks) + + def read_nowait(self, n: int = -1) -> bytes: + # default was changed to be consistent with .read(-1) + # + # I believe the most users don't know about the method and + # they are not affected. + if self._exception is not None: + raise self._exception + + if self._waiter and not self._waiter.done(): + raise RuntimeError( + "Called while some coroutine is waiting for incoming data." + ) + + return self._read_nowait(n) + + def _read_nowait_chunk(self, n: int) -> bytes: + first_buffer = self._buffer[0] + offset = self._buffer_offset + if n != -1 and len(first_buffer) - offset > n: + data = first_buffer[offset : offset + n] + self._buffer_offset += n + + elif offset: + self._buffer.popleft() + data = first_buffer[offset:] + self._buffer_offset = 0 + + else: + data = self._buffer.popleft() + + data_len = len(data) + self._size -= data_len + self._cursor += data_len + + chunk_splits = self._http_chunk_splits + # Prevent memory leak: drop useless chunk splits + while chunk_splits and chunk_splits[0] < self._cursor: + chunk_splits.popleft() + + if ( + self._protocol._reading_paused + and self._size < self._low_water + and ( + self._http_chunk_splits is None + or len(self._http_chunk_splits) < self._low_water_chunks + ) + ): + self._protocol.resume_reading() + return data + + def _read_nowait(self, n: int) -> bytes: + """Read not more than n bytes, or whole buffer if n == -1""" + self._timer.assert_timeout() + + chunks = [] + while self._buffer: + chunk = self._read_nowait_chunk(n) + chunks.append(chunk) + if n != -1: + n -= len(chunk) + if n == 0: + break + + return b"".join(chunks) if chunks else b"" + + +class EmptyStreamReader(StreamReader): # lgtm [py/missing-call-to-init] + + __slots__ = ("_read_eof_chunk",) + + def __init__(self) -> None: + self._read_eof_chunk = False + self.total_bytes = 0 + + def __repr__(self) -> str: + return "<%s>" % self.__class__.__name__ + + def exception(self) -> Optional[BaseException]: + return None + + def set_exception( + self, + exc: BaseException, + exc_cause: BaseException = _EXC_SENTINEL, + ) -> None: + pass + + def on_eof(self, callback: Callable[[], None]) -> None: + try: + callback() + except Exception: + internal_logger.exception("Exception in eof callback") + + def feed_eof(self) -> None: + pass + + def is_eof(self) -> bool: + return True + + def at_eof(self) -> bool: + return True + + async def wait_eof(self) -> None: + return + + def feed_data(self, data: bytes, n: int = 0) -> None: + pass + + async def readline(self, *, max_line_length: Optional[int] = None) -> bytes: + return b"" + + async def read(self, n: int = -1) -> bytes: + return b"" + + # TODO add async def readuntil + + async def readany(self) -> bytes: + return b"" + + async def readchunk(self) -> Tuple[bytes, bool]: + if not self._read_eof_chunk: + self._read_eof_chunk = True + return (b"", False) + + return (b"", True) + + async def readexactly(self, n: int) -> bytes: + raise asyncio.IncompleteReadError(b"", n) + + def read_nowait(self, n: int = -1) -> bytes: + return b"" + + +EMPTY_PAYLOAD: Final[StreamReader] = EmptyStreamReader() + + +class DataQueue(Generic[_T]): + """DataQueue is a general-purpose blocking queue with one reader.""" + + def __init__(self, loop: asyncio.AbstractEventLoop) -> None: + self._loop = loop + self._eof = False + self._waiter: Optional[asyncio.Future[None]] = None + self._exception: Optional[BaseException] = None + self._buffer: Deque[Tuple[_T, int]] = collections.deque() + + def __len__(self) -> int: + return len(self._buffer) + + def is_eof(self) -> bool: + return self._eof + + def at_eof(self) -> bool: + return self._eof and not self._buffer + + def exception(self) -> Optional[BaseException]: + return self._exception + + def set_exception( + self, + exc: BaseException, + exc_cause: BaseException = _EXC_SENTINEL, + ) -> None: + self._eof = True + self._exception = exc + if (waiter := self._waiter) is not None: + self._waiter = None + set_exception(waiter, exc, exc_cause) + + def feed_data(self, data: _T, size: int = 0) -> None: + self._buffer.append((data, size)) + if (waiter := self._waiter) is not None: + self._waiter = None + set_result(waiter, None) + + def feed_eof(self) -> None: + self._eof = True + if (waiter := self._waiter) is not None: + self._waiter = None + set_result(waiter, None) + + async def read(self) -> _T: + if not self._buffer and not self._eof: + assert not self._waiter + self._waiter = self._loop.create_future() + try: + await self._waiter + except (asyncio.CancelledError, asyncio.TimeoutError): + self._waiter = None + raise + if self._buffer: + data, _ = self._buffer.popleft() + return data + if self._exception is not None: + raise self._exception + raise EofStream + + def __aiter__(self) -> AsyncStreamIterator[_T]: + return AsyncStreamIterator(self.read) + + +class FlowControlDataQueue(DataQueue[_T]): + """FlowControlDataQueue resumes and pauses an underlying stream. + + It is a destination for parsed data. + + This class is deprecated and will be removed in version 4.0. + """ + + def __init__( + self, protocol: BaseProtocol, limit: int, *, loop: asyncio.AbstractEventLoop + ) -> None: + super().__init__(loop=loop) + self._size = 0 + self._protocol = protocol + self._limit = limit * 2 + + def feed_data(self, data: _T, size: int = 0) -> None: + super().feed_data(data, size) + self._size += size + + if self._size > self._limit and not self._protocol._reading_paused: + self._protocol.pause_reading() + + async def read(self) -> _T: + if not self._buffer and not self._eof: + assert not self._waiter + self._waiter = self._loop.create_future() + try: + await self._waiter + except (asyncio.CancelledError, asyncio.TimeoutError): + self._waiter = None + raise + if self._buffer: + data, size = self._buffer.popleft() + self._size -= size + if self._size < self._limit and self._protocol._reading_paused: + self._protocol.resume_reading() + return data + if self._exception is not None: + raise self._exception + raise EofStream diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/tcp_helpers.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/tcp_helpers.py new file mode 100644 index 0000000000000000000000000000000000000000..88b244223741ad2decb6cb612eae644fae88b2b2 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/tcp_helpers.py @@ -0,0 +1,37 @@ +"""Helper methods to tune a TCP connection""" + +import asyncio +import socket +from contextlib import suppress +from typing import Optional # noqa + +__all__ = ("tcp_keepalive", "tcp_nodelay") + + +if hasattr(socket, "SO_KEEPALIVE"): + + def tcp_keepalive(transport: asyncio.Transport) -> None: + sock = transport.get_extra_info("socket") + if sock is not None: + sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) + +else: + + def tcp_keepalive(transport: asyncio.Transport) -> None: # pragma: no cover + pass + + +def tcp_nodelay(transport: asyncio.Transport, value: bool) -> None: + sock = transport.get_extra_info("socket") + + if sock is None: + return + + if sock.family not in (socket.AF_INET, socket.AF_INET6): + return + + value = bool(value) + + # socket may be closed already, on windows OSError get raised + with suppress(OSError): + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, value) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/test_utils.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/test_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..34b77d47cd59b10170864a10a6b8d03c2abb830b --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/test_utils.py @@ -0,0 +1,777 @@ +"""Utilities shared by tests.""" + +import asyncio +import contextlib +import gc +import inspect +import ipaddress +import os +import socket +import sys +import warnings +from abc import ABC, abstractmethod +from types import TracebackType +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Generic, + Iterator, + List, + Optional, + Type, + TypeVar, + cast, + overload, +) +from unittest import IsolatedAsyncioTestCase, mock + +from aiosignal import Signal +from multidict import CIMultiDict, CIMultiDictProxy +from yarl import URL + +import aiohttp +from aiohttp.client import ( + _RequestContextManager, + _RequestOptions, + _WSRequestContextManager, +) + +from . import ClientSession, hdrs +from .abc import AbstractCookieJar +from .client_reqrep import ClientResponse +from .client_ws import ClientWebSocketResponse +from .helpers import sentinel +from .http import HttpVersion, RawRequestMessage +from .streams import EMPTY_PAYLOAD, StreamReader +from .typedefs import StrOrURL +from .web import ( + Application, + AppRunner, + BaseRequest, + BaseRunner, + Request, + Server, + ServerRunner, + SockSite, + UrlMappingMatchInfo, +) +from .web_protocol import _RequestHandler + +if TYPE_CHECKING: + from ssl import SSLContext +else: + SSLContext = None + +if sys.version_info >= (3, 11) and TYPE_CHECKING: + from typing import Unpack + +if sys.version_info >= (3, 11): + from typing import Self +else: + Self = Any + +_ApplicationNone = TypeVar("_ApplicationNone", Application, None) +_Request = TypeVar("_Request", bound=BaseRequest) + +REUSE_ADDRESS = os.name == "posix" and sys.platform != "cygwin" + + +def get_unused_port_socket( + host: str, family: socket.AddressFamily = socket.AF_INET +) -> socket.socket: + return get_port_socket(host, 0, family) + + +def get_port_socket( + host: str, port: int, family: socket.AddressFamily +) -> socket.socket: + s = socket.socket(family, socket.SOCK_STREAM) + if REUSE_ADDRESS: + # Windows has different semantics for SO_REUSEADDR, + # so don't set it. Ref: + # https://docs.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind((host, port)) + return s + + +def unused_port() -> int: + """Return a port that is unused on the current host.""" + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind(("127.0.0.1", 0)) + return cast(int, s.getsockname()[1]) + + +class BaseTestServer(ABC): + __test__ = False + + def __init__( + self, + *, + scheme: str = "", + loop: Optional[asyncio.AbstractEventLoop] = None, + host: str = "127.0.0.1", + port: Optional[int] = None, + skip_url_asserts: bool = False, + socket_factory: Callable[ + [str, int, socket.AddressFamily], socket.socket + ] = get_port_socket, + **kwargs: Any, + ) -> None: + self._loop = loop + self.runner: Optional[BaseRunner] = None + self._root: Optional[URL] = None + self.host = host + self.port = port + self._closed = False + self.scheme = scheme + self.skip_url_asserts = skip_url_asserts + self.socket_factory = socket_factory + + async def start_server( + self, loop: Optional[asyncio.AbstractEventLoop] = None, **kwargs: Any + ) -> None: + if self.runner: + return + self._loop = loop + self._ssl = kwargs.pop("ssl", None) + self.runner = await self._make_runner(handler_cancellation=True, **kwargs) + await self.runner.setup() + if not self.port: + self.port = 0 + absolute_host = self.host + try: + version = ipaddress.ip_address(self.host).version + except ValueError: + version = 4 + if version == 6: + absolute_host = f"[{self.host}]" + family = socket.AF_INET6 if version == 6 else socket.AF_INET + _sock = self.socket_factory(self.host, self.port, family) + self.host, self.port = _sock.getsockname()[:2] + site = SockSite(self.runner, sock=_sock, ssl_context=self._ssl) + await site.start() + server = site._server + assert server is not None + sockets = server.sockets # type: ignore[attr-defined] + assert sockets is not None + self.port = sockets[0].getsockname()[1] + if not self.scheme: + self.scheme = "https" if self._ssl else "http" + self._root = URL(f"{self.scheme}://{absolute_host}:{self.port}") + + @abstractmethod # pragma: no cover + async def _make_runner(self, **kwargs: Any) -> BaseRunner: + pass + + def make_url(self, path: StrOrURL) -> URL: + assert self._root is not None + url = URL(path) + if not self.skip_url_asserts: + assert not url.absolute + return self._root.join(url) + else: + return URL(str(self._root) + str(path)) + + @property + def started(self) -> bool: + return self.runner is not None + + @property + def closed(self) -> bool: + return self._closed + + @property + def handler(self) -> Server: + # for backward compatibility + # web.Server instance + runner = self.runner + assert runner is not None + assert runner.server is not None + return runner.server + + async def close(self) -> None: + """Close all fixtures created by the test client. + + After that point, the TestClient is no longer usable. + + This is an idempotent function: running close multiple times + will not have any additional effects. + + close is also run when the object is garbage collected, and on + exit when used as a context manager. + + """ + if self.started and not self.closed: + assert self.runner is not None + await self.runner.cleanup() + self._root = None + self.port = None + self._closed = True + + def __enter__(self) -> None: + raise TypeError("Use async with instead") + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_value: Optional[BaseException], + traceback: Optional[TracebackType], + ) -> None: + # __exit__ should exist in pair with __enter__ but never executed + pass # pragma: no cover + + async def __aenter__(self) -> "BaseTestServer": + await self.start_server(loop=self._loop) + return self + + async def __aexit__( + self, + exc_type: Optional[Type[BaseException]], + exc_value: Optional[BaseException], + traceback: Optional[TracebackType], + ) -> None: + await self.close() + + +class TestServer(BaseTestServer): + def __init__( + self, + app: Application, + *, + scheme: str = "", + host: str = "127.0.0.1", + port: Optional[int] = None, + **kwargs: Any, + ): + self.app = app + super().__init__(scheme=scheme, host=host, port=port, **kwargs) + + async def _make_runner(self, **kwargs: Any) -> BaseRunner: + return AppRunner(self.app, **kwargs) + + +class RawTestServer(BaseTestServer): + def __init__( + self, + handler: _RequestHandler, + *, + scheme: str = "", + host: str = "127.0.0.1", + port: Optional[int] = None, + **kwargs: Any, + ) -> None: + self._handler = handler + super().__init__(scheme=scheme, host=host, port=port, **kwargs) + + async def _make_runner(self, debug: bool = True, **kwargs: Any) -> ServerRunner: + srv = Server(self._handler, loop=self._loop, debug=debug, **kwargs) + return ServerRunner(srv, debug=debug, **kwargs) + + +class TestClient(Generic[_Request, _ApplicationNone]): + """ + A test client implementation. + + To write functional tests for aiohttp based servers. + + """ + + __test__ = False + + @overload + def __init__( + self: "TestClient[Request, Application]", + server: TestServer, + *, + cookie_jar: Optional[AbstractCookieJar] = None, + **kwargs: Any, + ) -> None: ... + @overload + def __init__( + self: "TestClient[_Request, None]", + server: BaseTestServer, + *, + cookie_jar: Optional[AbstractCookieJar] = None, + **kwargs: Any, + ) -> None: ... + def __init__( + self, + server: BaseTestServer, + *, + cookie_jar: Optional[AbstractCookieJar] = None, + loop: Optional[asyncio.AbstractEventLoop] = None, + **kwargs: Any, + ) -> None: + if not isinstance(server, BaseTestServer): + raise TypeError( + "server must be TestServer instance, found type: %r" % type(server) + ) + self._server = server + self._loop = loop + if cookie_jar is None: + cookie_jar = aiohttp.CookieJar(unsafe=True, loop=loop) + self._session = ClientSession(loop=loop, cookie_jar=cookie_jar, **kwargs) + self._session._retry_connection = False + self._closed = False + self._responses: List[ClientResponse] = [] + self._websockets: List[ClientWebSocketResponse] = [] + + async def start_server(self) -> None: + await self._server.start_server(loop=self._loop) + + @property + def host(self) -> str: + return self._server.host + + @property + def port(self) -> Optional[int]: + return self._server.port + + @property + def server(self) -> BaseTestServer: + return self._server + + @property + def app(self) -> _ApplicationNone: + return getattr(self._server, "app", None) # type: ignore[return-value] + + @property + def session(self) -> ClientSession: + """An internal aiohttp.ClientSession. + + Unlike the methods on the TestClient, client session requests + do not automatically include the host in the url queried, and + will require an absolute path to the resource. + + """ + return self._session + + def make_url(self, path: StrOrURL) -> URL: + return self._server.make_url(path) + + async def _request( + self, method: str, path: StrOrURL, **kwargs: Any + ) -> ClientResponse: + resp = await self._session.request(method, self.make_url(path), **kwargs) + # save it to close later + self._responses.append(resp) + return resp + + if sys.version_info >= (3, 11) and TYPE_CHECKING: + + def request( + self, method: str, path: StrOrURL, **kwargs: Unpack[_RequestOptions] + ) -> _RequestContextManager: ... + + def get( + self, + path: StrOrURL, + **kwargs: Unpack[_RequestOptions], + ) -> _RequestContextManager: ... + + def options( + self, + path: StrOrURL, + **kwargs: Unpack[_RequestOptions], + ) -> _RequestContextManager: ... + + def head( + self, + path: StrOrURL, + **kwargs: Unpack[_RequestOptions], + ) -> _RequestContextManager: ... + + def post( + self, + path: StrOrURL, + **kwargs: Unpack[_RequestOptions], + ) -> _RequestContextManager: ... + + def put( + self, + path: StrOrURL, + **kwargs: Unpack[_RequestOptions], + ) -> _RequestContextManager: ... + + def patch( + self, + path: StrOrURL, + **kwargs: Unpack[_RequestOptions], + ) -> _RequestContextManager: ... + + def delete( + self, + path: StrOrURL, + **kwargs: Unpack[_RequestOptions], + ) -> _RequestContextManager: ... + + else: + + def request( + self, method: str, path: StrOrURL, **kwargs: Any + ) -> _RequestContextManager: + """Routes a request to tested http server. + + The interface is identical to aiohttp.ClientSession.request, + except the loop kwarg is overridden by the instance used by the + test server. + + """ + return _RequestContextManager(self._request(method, path, **kwargs)) + + def get(self, path: StrOrURL, **kwargs: Any) -> _RequestContextManager: + """Perform an HTTP GET request.""" + return _RequestContextManager(self._request(hdrs.METH_GET, path, **kwargs)) + + def post(self, path: StrOrURL, **kwargs: Any) -> _RequestContextManager: + """Perform an HTTP POST request.""" + return _RequestContextManager(self._request(hdrs.METH_POST, path, **kwargs)) + + def options(self, path: StrOrURL, **kwargs: Any) -> _RequestContextManager: + """Perform an HTTP OPTIONS request.""" + return _RequestContextManager( + self._request(hdrs.METH_OPTIONS, path, **kwargs) + ) + + def head(self, path: StrOrURL, **kwargs: Any) -> _RequestContextManager: + """Perform an HTTP HEAD request.""" + return _RequestContextManager(self._request(hdrs.METH_HEAD, path, **kwargs)) + + def put(self, path: StrOrURL, **kwargs: Any) -> _RequestContextManager: + """Perform an HTTP PUT request.""" + return _RequestContextManager(self._request(hdrs.METH_PUT, path, **kwargs)) + + def patch(self, path: StrOrURL, **kwargs: Any) -> _RequestContextManager: + """Perform an HTTP PATCH request.""" + return _RequestContextManager( + self._request(hdrs.METH_PATCH, path, **kwargs) + ) + + def delete(self, path: StrOrURL, **kwargs: Any) -> _RequestContextManager: + """Perform an HTTP PATCH request.""" + return _RequestContextManager( + self._request(hdrs.METH_DELETE, path, **kwargs) + ) + + def ws_connect(self, path: StrOrURL, **kwargs: Any) -> _WSRequestContextManager: + """Initiate websocket connection. + + The api corresponds to aiohttp.ClientSession.ws_connect. + + """ + return _WSRequestContextManager(self._ws_connect(path, **kwargs)) + + async def _ws_connect( + self, path: StrOrURL, **kwargs: Any + ) -> ClientWebSocketResponse: + ws = await self._session.ws_connect(self.make_url(path), **kwargs) + self._websockets.append(ws) + return ws + + async def close(self) -> None: + """Close all fixtures created by the test client. + + After that point, the TestClient is no longer usable. + + This is an idempotent function: running close multiple times + will not have any additional effects. + + close is also run on exit when used as a(n) (asynchronous) + context manager. + + """ + if not self._closed: + for resp in self._responses: + resp.close() + for ws in self._websockets: + await ws.close() + await self._session.close() + await self._server.close() + self._closed = True + + def __enter__(self) -> None: + raise TypeError("Use async with instead") + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc: Optional[BaseException], + tb: Optional[TracebackType], + ) -> None: + # __exit__ should exist in pair with __enter__ but never executed + pass # pragma: no cover + + async def __aenter__(self) -> Self: + await self.start_server() + return self + + async def __aexit__( + self, + exc_type: Optional[Type[BaseException]], + exc: Optional[BaseException], + tb: Optional[TracebackType], + ) -> None: + await self.close() + + +class AioHTTPTestCase(IsolatedAsyncioTestCase): + """A base class to allow for unittest web applications using aiohttp. + + Provides the following: + + * self.client (aiohttp.test_utils.TestClient): an aiohttp test client. + * self.loop (asyncio.BaseEventLoop): the event loop in which the + application and server are running. + * self.app (aiohttp.web.Application): the application returned by + self.get_application() + + Note that the TestClient's methods are asynchronous: you have to + execute function on the test client using asynchronous methods. + """ + + async def get_application(self) -> Application: + """Get application. + + This method should be overridden + to return the aiohttp.web.Application + object to test. + """ + return self.get_app() + + def get_app(self) -> Application: + """Obsolete method used to constructing web application. + + Use .get_application() coroutine instead. + """ + raise RuntimeError("Did you forget to define get_application()?") + + async def asyncSetUp(self) -> None: + self.loop = asyncio.get_running_loop() + return await self.setUpAsync() + + async def setUpAsync(self) -> None: + self.app = await self.get_application() + self.server = await self.get_server(self.app) + self.client = await self.get_client(self.server) + + await self.client.start_server() + + async def asyncTearDown(self) -> None: + return await self.tearDownAsync() + + async def tearDownAsync(self) -> None: + await self.client.close() + + async def get_server(self, app: Application) -> TestServer: + """Return a TestServer instance.""" + return TestServer(app, loop=self.loop) + + async def get_client(self, server: TestServer) -> TestClient[Request, Application]: + """Return a TestClient instance.""" + return TestClient(server, loop=self.loop) + + +def unittest_run_loop(func: Any, *args: Any, **kwargs: Any) -> Any: + """ + A decorator dedicated to use with asynchronous AioHTTPTestCase test methods. + + In 3.8+, this does nothing. + """ + warnings.warn( + "Decorator `@unittest_run_loop` is no longer needed in aiohttp 3.8+", + DeprecationWarning, + stacklevel=2, + ) + return func + + +_LOOP_FACTORY = Callable[[], asyncio.AbstractEventLoop] + + +@contextlib.contextmanager +def loop_context( + loop_factory: _LOOP_FACTORY = asyncio.new_event_loop, fast: bool = False +) -> Iterator[asyncio.AbstractEventLoop]: + """A contextmanager that creates an event_loop, for test purposes. + + Handles the creation and cleanup of a test loop. + """ + loop = setup_test_loop(loop_factory) + yield loop + teardown_test_loop(loop, fast=fast) + + +def setup_test_loop( + loop_factory: _LOOP_FACTORY = asyncio.new_event_loop, +) -> asyncio.AbstractEventLoop: + """Create and return an asyncio.BaseEventLoop instance. + + The caller should also call teardown_test_loop, + once they are done with the loop. + """ + loop = loop_factory() + asyncio.set_event_loop(loop) + return loop + + +def teardown_test_loop(loop: asyncio.AbstractEventLoop, fast: bool = False) -> None: + """Teardown and cleanup an event_loop created by setup_test_loop.""" + closed = loop.is_closed() + if not closed: + loop.call_soon(loop.stop) + loop.run_forever() + loop.close() + + if not fast: + gc.collect() + + asyncio.set_event_loop(None) + + +def _create_app_mock() -> mock.MagicMock: + def get_dict(app: Any, key: str) -> Any: + return app.__app_dict[key] + + def set_dict(app: Any, key: str, value: Any) -> None: + app.__app_dict[key] = value + + app = mock.MagicMock(spec=Application) + app.__app_dict = {} + app.__getitem__ = get_dict + app.__setitem__ = set_dict + + app._debug = False + app.on_response_prepare = Signal(app) + app.on_response_prepare.freeze() + return app + + +def _create_transport(sslcontext: Optional[SSLContext] = None) -> mock.Mock: + transport = mock.Mock() + + def get_extra_info(key: str) -> Optional[SSLContext]: + if key == "sslcontext": + return sslcontext + else: + return None + + transport.get_extra_info.side_effect = get_extra_info + return transport + + +def make_mocked_request( + method: str, + path: str, + headers: Any = None, + *, + match_info: Any = sentinel, + version: HttpVersion = HttpVersion(1, 1), + closing: bool = False, + app: Any = None, + writer: Any = sentinel, + protocol: Any = sentinel, + transport: Any = sentinel, + payload: StreamReader = EMPTY_PAYLOAD, + sslcontext: Optional[SSLContext] = None, + client_max_size: int = 1024**2, + loop: Any = ..., +) -> Request: + """Creates mocked web.Request testing purposes. + + Useful in unit tests, when spinning full web server is overkill or + specific conditions and errors are hard to trigger. + """ + task = mock.Mock() + if loop is ...: + # no loop passed, try to get the current one if + # its is running as we need a real loop to create + # executor jobs to be able to do testing + # with a real executor + try: + loop = asyncio.get_running_loop() + except RuntimeError: + loop = mock.Mock() + loop.create_future.return_value = () + + if version < HttpVersion(1, 1): + closing = True + + if headers: + headers = CIMultiDictProxy(CIMultiDict(headers)) + raw_hdrs = tuple( + (k.encode("utf-8"), v.encode("utf-8")) for k, v in headers.items() + ) + else: + headers = CIMultiDictProxy(CIMultiDict()) + raw_hdrs = () + + chunked = "chunked" in headers.get(hdrs.TRANSFER_ENCODING, "").lower() + + message = RawRequestMessage( + method, + path, + version, + headers, + raw_hdrs, + closing, + None, + False, + chunked, + URL(path), + ) + if app is None: + app = _create_app_mock() + + if transport is sentinel: + transport = _create_transport(sslcontext) + + if protocol is sentinel: + protocol = mock.Mock() + protocol.max_field_size = 8190 + protocol.max_line_length = 8190 + protocol.max_headers = 128 + protocol.transport = transport + type(protocol).peername = mock.PropertyMock( + return_value=transport.get_extra_info("peername") + ) + type(protocol).ssl_context = mock.PropertyMock(return_value=sslcontext) + + if writer is sentinel: + writer = mock.Mock() + writer.write_headers = make_mocked_coro(None) + writer.write = make_mocked_coro(None) + writer.write_eof = make_mocked_coro(None) + writer.drain = make_mocked_coro(None) + writer.transport = transport + + protocol.transport = transport + protocol.writer = writer + + req = Request( + message, payload, protocol, writer, task, loop, client_max_size=client_max_size + ) + + match_info = UrlMappingMatchInfo( + {} if match_info is sentinel else match_info, mock.Mock() + ) + match_info.add_app(app) + req._match_info = match_info + + return req + + +def make_mocked_coro( + return_value: Any = sentinel, raise_exception: Any = sentinel +) -> Any: + """Creates a coroutine mock.""" + + async def mock_coro(*args: Any, **kwargs: Any) -> Any: + if raise_exception is not sentinel: + raise raise_exception + if not inspect.isawaitable(return_value): + return return_value + await return_value + + return mock.Mock(wraps=mock_coro) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/tracing.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/tracing.py new file mode 100644 index 0000000000000000000000000000000000000000..568fa7f9e38090e0c0a4738db4d51656ce31b99a --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/tracing.py @@ -0,0 +1,455 @@ +from types import SimpleNamespace +from typing import TYPE_CHECKING, Mapping, Optional, Type, TypeVar + +import attr +from aiosignal import Signal +from multidict import CIMultiDict +from yarl import URL + +from .client_reqrep import ClientResponse + +if TYPE_CHECKING: + from .client import ClientSession + + _ParamT_contra = TypeVar("_ParamT_contra", contravariant=True) + _TracingSignal = Signal[ClientSession, SimpleNamespace, _ParamT_contra] + + +__all__ = ( + "TraceConfig", + "TraceRequestStartParams", + "TraceRequestEndParams", + "TraceRequestExceptionParams", + "TraceConnectionQueuedStartParams", + "TraceConnectionQueuedEndParams", + "TraceConnectionCreateStartParams", + "TraceConnectionCreateEndParams", + "TraceConnectionReuseconnParams", + "TraceDnsResolveHostStartParams", + "TraceDnsResolveHostEndParams", + "TraceDnsCacheHitParams", + "TraceDnsCacheMissParams", + "TraceRequestRedirectParams", + "TraceRequestChunkSentParams", + "TraceResponseChunkReceivedParams", + "TraceRequestHeadersSentParams", +) + + +class TraceConfig: + """First-class used to trace requests launched via ClientSession objects.""" + + def __init__( + self, trace_config_ctx_factory: Type[SimpleNamespace] = SimpleNamespace + ) -> None: + self._on_request_start: _TracingSignal[TraceRequestStartParams] = Signal(self) + self._on_request_chunk_sent: _TracingSignal[TraceRequestChunkSentParams] = ( + Signal(self) + ) + self._on_response_chunk_received: _TracingSignal[ + TraceResponseChunkReceivedParams + ] = Signal(self) + self._on_request_end: _TracingSignal[TraceRequestEndParams] = Signal(self) + self._on_request_exception: _TracingSignal[TraceRequestExceptionParams] = ( + Signal(self) + ) + self._on_request_redirect: _TracingSignal[TraceRequestRedirectParams] = Signal( + self + ) + self._on_connection_queued_start: _TracingSignal[ + TraceConnectionQueuedStartParams + ] = Signal(self) + self._on_connection_queued_end: _TracingSignal[ + TraceConnectionQueuedEndParams + ] = Signal(self) + self._on_connection_create_start: _TracingSignal[ + TraceConnectionCreateStartParams + ] = Signal(self) + self._on_connection_create_end: _TracingSignal[ + TraceConnectionCreateEndParams + ] = Signal(self) + self._on_connection_reuseconn: _TracingSignal[ + TraceConnectionReuseconnParams + ] = Signal(self) + self._on_dns_resolvehost_start: _TracingSignal[ + TraceDnsResolveHostStartParams + ] = Signal(self) + self._on_dns_resolvehost_end: _TracingSignal[TraceDnsResolveHostEndParams] = ( + Signal(self) + ) + self._on_dns_cache_hit: _TracingSignal[TraceDnsCacheHitParams] = Signal(self) + self._on_dns_cache_miss: _TracingSignal[TraceDnsCacheMissParams] = Signal(self) + self._on_request_headers_sent: _TracingSignal[TraceRequestHeadersSentParams] = ( + Signal(self) + ) + + self._trace_config_ctx_factory = trace_config_ctx_factory + + def trace_config_ctx( + self, trace_request_ctx: Optional[Mapping[str, str]] = None + ) -> SimpleNamespace: + """Return a new trace_config_ctx instance""" + return self._trace_config_ctx_factory(trace_request_ctx=trace_request_ctx) + + def freeze(self) -> None: + self._on_request_start.freeze() + self._on_request_chunk_sent.freeze() + self._on_response_chunk_received.freeze() + self._on_request_end.freeze() + self._on_request_exception.freeze() + self._on_request_redirect.freeze() + self._on_connection_queued_start.freeze() + self._on_connection_queued_end.freeze() + self._on_connection_create_start.freeze() + self._on_connection_create_end.freeze() + self._on_connection_reuseconn.freeze() + self._on_dns_resolvehost_start.freeze() + self._on_dns_resolvehost_end.freeze() + self._on_dns_cache_hit.freeze() + self._on_dns_cache_miss.freeze() + self._on_request_headers_sent.freeze() + + @property + def on_request_start(self) -> "_TracingSignal[TraceRequestStartParams]": + return self._on_request_start + + @property + def on_request_chunk_sent( + self, + ) -> "_TracingSignal[TraceRequestChunkSentParams]": + return self._on_request_chunk_sent + + @property + def on_response_chunk_received( + self, + ) -> "_TracingSignal[TraceResponseChunkReceivedParams]": + return self._on_response_chunk_received + + @property + def on_request_end(self) -> "_TracingSignal[TraceRequestEndParams]": + return self._on_request_end + + @property + def on_request_exception( + self, + ) -> "_TracingSignal[TraceRequestExceptionParams]": + return self._on_request_exception + + @property + def on_request_redirect( + self, + ) -> "_TracingSignal[TraceRequestRedirectParams]": + return self._on_request_redirect + + @property + def on_connection_queued_start( + self, + ) -> "_TracingSignal[TraceConnectionQueuedStartParams]": + return self._on_connection_queued_start + + @property + def on_connection_queued_end( + self, + ) -> "_TracingSignal[TraceConnectionQueuedEndParams]": + return self._on_connection_queued_end + + @property + def on_connection_create_start( + self, + ) -> "_TracingSignal[TraceConnectionCreateStartParams]": + return self._on_connection_create_start + + @property + def on_connection_create_end( + self, + ) -> "_TracingSignal[TraceConnectionCreateEndParams]": + return self._on_connection_create_end + + @property + def on_connection_reuseconn( + self, + ) -> "_TracingSignal[TraceConnectionReuseconnParams]": + return self._on_connection_reuseconn + + @property + def on_dns_resolvehost_start( + self, + ) -> "_TracingSignal[TraceDnsResolveHostStartParams]": + return self._on_dns_resolvehost_start + + @property + def on_dns_resolvehost_end( + self, + ) -> "_TracingSignal[TraceDnsResolveHostEndParams]": + return self._on_dns_resolvehost_end + + @property + def on_dns_cache_hit(self) -> "_TracingSignal[TraceDnsCacheHitParams]": + return self._on_dns_cache_hit + + @property + def on_dns_cache_miss(self) -> "_TracingSignal[TraceDnsCacheMissParams]": + return self._on_dns_cache_miss + + @property + def on_request_headers_sent( + self, + ) -> "_TracingSignal[TraceRequestHeadersSentParams]": + return self._on_request_headers_sent + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class TraceRequestStartParams: + """Parameters sent by the `on_request_start` signal""" + + method: str + url: URL + headers: "CIMultiDict[str]" + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class TraceRequestChunkSentParams: + """Parameters sent by the `on_request_chunk_sent` signal""" + + method: str + url: URL + chunk: bytes + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class TraceResponseChunkReceivedParams: + """Parameters sent by the `on_response_chunk_received` signal""" + + method: str + url: URL + chunk: bytes + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class TraceRequestEndParams: + """Parameters sent by the `on_request_end` signal""" + + method: str + url: URL + headers: "CIMultiDict[str]" + response: ClientResponse + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class TraceRequestExceptionParams: + """Parameters sent by the `on_request_exception` signal""" + + method: str + url: URL + headers: "CIMultiDict[str]" + exception: BaseException + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class TraceRequestRedirectParams: + """Parameters sent by the `on_request_redirect` signal""" + + method: str + url: URL + headers: "CIMultiDict[str]" + response: ClientResponse + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class TraceConnectionQueuedStartParams: + """Parameters sent by the `on_connection_queued_start` signal""" + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class TraceConnectionQueuedEndParams: + """Parameters sent by the `on_connection_queued_end` signal""" + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class TraceConnectionCreateStartParams: + """Parameters sent by the `on_connection_create_start` signal""" + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class TraceConnectionCreateEndParams: + """Parameters sent by the `on_connection_create_end` signal""" + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class TraceConnectionReuseconnParams: + """Parameters sent by the `on_connection_reuseconn` signal""" + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class TraceDnsResolveHostStartParams: + """Parameters sent by the `on_dns_resolvehost_start` signal""" + + host: str + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class TraceDnsResolveHostEndParams: + """Parameters sent by the `on_dns_resolvehost_end` signal""" + + host: str + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class TraceDnsCacheHitParams: + """Parameters sent by the `on_dns_cache_hit` signal""" + + host: str + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class TraceDnsCacheMissParams: + """Parameters sent by the `on_dns_cache_miss` signal""" + + host: str + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class TraceRequestHeadersSentParams: + """Parameters sent by the `on_request_headers_sent` signal""" + + method: str + url: URL + headers: "CIMultiDict[str]" + + +class Trace: + """Internal dependency holder class. + + Used to keep together the main dependencies used + at the moment of send a signal. + """ + + def __init__( + self, + session: "ClientSession", + trace_config: TraceConfig, + trace_config_ctx: SimpleNamespace, + ) -> None: + self._trace_config = trace_config + self._trace_config_ctx = trace_config_ctx + self._session = session + + async def send_request_start( + self, method: str, url: URL, headers: "CIMultiDict[str]" + ) -> None: + return await self._trace_config.on_request_start.send( + self._session, + self._trace_config_ctx, + TraceRequestStartParams(method, url, headers), + ) + + async def send_request_chunk_sent( + self, method: str, url: URL, chunk: bytes + ) -> None: + return await self._trace_config.on_request_chunk_sent.send( + self._session, + self._trace_config_ctx, + TraceRequestChunkSentParams(method, url, chunk), + ) + + async def send_response_chunk_received( + self, method: str, url: URL, chunk: bytes + ) -> None: + return await self._trace_config.on_response_chunk_received.send( + self._session, + self._trace_config_ctx, + TraceResponseChunkReceivedParams(method, url, chunk), + ) + + async def send_request_end( + self, + method: str, + url: URL, + headers: "CIMultiDict[str]", + response: ClientResponse, + ) -> None: + return await self._trace_config.on_request_end.send( + self._session, + self._trace_config_ctx, + TraceRequestEndParams(method, url, headers, response), + ) + + async def send_request_exception( + self, + method: str, + url: URL, + headers: "CIMultiDict[str]", + exception: BaseException, + ) -> None: + return await self._trace_config.on_request_exception.send( + self._session, + self._trace_config_ctx, + TraceRequestExceptionParams(method, url, headers, exception), + ) + + async def send_request_redirect( + self, + method: str, + url: URL, + headers: "CIMultiDict[str]", + response: ClientResponse, + ) -> None: + return await self._trace_config._on_request_redirect.send( + self._session, + self._trace_config_ctx, + TraceRequestRedirectParams(method, url, headers, response), + ) + + async def send_connection_queued_start(self) -> None: + return await self._trace_config.on_connection_queued_start.send( + self._session, self._trace_config_ctx, TraceConnectionQueuedStartParams() + ) + + async def send_connection_queued_end(self) -> None: + return await self._trace_config.on_connection_queued_end.send( + self._session, self._trace_config_ctx, TraceConnectionQueuedEndParams() + ) + + async def send_connection_create_start(self) -> None: + return await self._trace_config.on_connection_create_start.send( + self._session, self._trace_config_ctx, TraceConnectionCreateStartParams() + ) + + async def send_connection_create_end(self) -> None: + return await self._trace_config.on_connection_create_end.send( + self._session, self._trace_config_ctx, TraceConnectionCreateEndParams() + ) + + async def send_connection_reuseconn(self) -> None: + return await self._trace_config.on_connection_reuseconn.send( + self._session, self._trace_config_ctx, TraceConnectionReuseconnParams() + ) + + async def send_dns_resolvehost_start(self, host: str) -> None: + return await self._trace_config.on_dns_resolvehost_start.send( + self._session, self._trace_config_ctx, TraceDnsResolveHostStartParams(host) + ) + + async def send_dns_resolvehost_end(self, host: str) -> None: + return await self._trace_config.on_dns_resolvehost_end.send( + self._session, self._trace_config_ctx, TraceDnsResolveHostEndParams(host) + ) + + async def send_dns_cache_hit(self, host: str) -> None: + return await self._trace_config.on_dns_cache_hit.send( + self._session, self._trace_config_ctx, TraceDnsCacheHitParams(host) + ) + + async def send_dns_cache_miss(self, host: str) -> None: + return await self._trace_config.on_dns_cache_miss.send( + self._session, self._trace_config_ctx, TraceDnsCacheMissParams(host) + ) + + async def send_request_headers( + self, method: str, url: URL, headers: "CIMultiDict[str]" + ) -> None: + return await self._trace_config._on_request_headers_sent.send( + self._session, + self._trace_config_ctx, + TraceRequestHeadersSentParams(method, url, headers), + ) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/typedefs.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/typedefs.py new file mode 100644 index 0000000000000000000000000000000000000000..cc8c0825b4e522f7d1b6cf0058564f322fb5a905 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/typedefs.py @@ -0,0 +1,69 @@ +import json +import os +from typing import ( + TYPE_CHECKING, + Any, + Awaitable, + Callable, + Iterable, + Mapping, + Protocol, + Tuple, + Union, +) + +from multidict import CIMultiDict, CIMultiDictProxy, MultiDict, MultiDictProxy, istr +from yarl import URL, Query as _Query + +Query = _Query + +DEFAULT_JSON_ENCODER = json.dumps +DEFAULT_JSON_DECODER = json.loads + +if TYPE_CHECKING: + _CIMultiDict = CIMultiDict[str] + _CIMultiDictProxy = CIMultiDictProxy[str] + _MultiDict = MultiDict[str] + _MultiDictProxy = MultiDictProxy[str] + from http.cookies import BaseCookie, Morsel + + from .web import Request, StreamResponse +else: + _CIMultiDict = CIMultiDict + _CIMultiDictProxy = CIMultiDictProxy + _MultiDict = MultiDict + _MultiDictProxy = MultiDictProxy + +Byteish = Union[bytes, bytearray, memoryview] +JSONEncoder = Callable[[Any], str] +JSONDecoder = Callable[[str], Any] +LooseHeaders = Union[ + Mapping[str, str], + Mapping[istr, str], + _CIMultiDict, + _CIMultiDictProxy, + Iterable[Tuple[Union[str, istr], str]], +] +RawHeaders = Tuple[Tuple[bytes, bytes], ...] +StrOrURL = Union[str, URL] + +LooseCookiesMappings = Mapping[str, Union[str, "BaseCookie[str]", "Morsel[Any]"]] +LooseCookiesIterables = Iterable[ + Tuple[str, Union[str, "BaseCookie[str]", "Morsel[Any]"]] +] +LooseCookies = Union[ + LooseCookiesMappings, + LooseCookiesIterables, + "BaseCookie[str]", +] + +Handler = Callable[["Request"], Awaitable["StreamResponse"]] + + +class Middleware(Protocol): + def __call__( + self, request: "Request", handler: Handler + ) -> Awaitable["StreamResponse"]: ... + + +PathLike = Union[str, "os.PathLike[str]"] diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web.py new file mode 100644 index 0000000000000000000000000000000000000000..5a1fc964172be24b03ffaf3982ba6fccea7b6515 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web.py @@ -0,0 +1,592 @@ +import asyncio +import logging +import os +import socket +import sys +import warnings +from argparse import ArgumentParser +from collections.abc import Iterable +from contextlib import suppress +from importlib import import_module +from typing import ( + TYPE_CHECKING, + Any, + Awaitable, + Callable, + Iterable as TypingIterable, + List, + Optional, + Set, + Type, + Union, + cast, +) + +from .abc import AbstractAccessLogger +from .helpers import AppKey as AppKey +from .log import access_logger +from .typedefs import PathLike +from .web_app import Application as Application, CleanupError as CleanupError +from .web_exceptions import ( + HTTPAccepted as HTTPAccepted, + HTTPBadGateway as HTTPBadGateway, + HTTPBadRequest as HTTPBadRequest, + HTTPClientError as HTTPClientError, + HTTPConflict as HTTPConflict, + HTTPCreated as HTTPCreated, + HTTPError as HTTPError, + HTTPException as HTTPException, + HTTPExpectationFailed as HTTPExpectationFailed, + HTTPFailedDependency as HTTPFailedDependency, + HTTPForbidden as HTTPForbidden, + HTTPFound as HTTPFound, + HTTPGatewayTimeout as HTTPGatewayTimeout, + HTTPGone as HTTPGone, + HTTPInsufficientStorage as HTTPInsufficientStorage, + HTTPInternalServerError as HTTPInternalServerError, + HTTPLengthRequired as HTTPLengthRequired, + HTTPMethodNotAllowed as HTTPMethodNotAllowed, + HTTPMisdirectedRequest as HTTPMisdirectedRequest, + HTTPMove as HTTPMove, + HTTPMovedPermanently as HTTPMovedPermanently, + HTTPMultipleChoices as HTTPMultipleChoices, + HTTPNetworkAuthenticationRequired as HTTPNetworkAuthenticationRequired, + HTTPNoContent as HTTPNoContent, + HTTPNonAuthoritativeInformation as HTTPNonAuthoritativeInformation, + HTTPNotAcceptable as HTTPNotAcceptable, + HTTPNotExtended as HTTPNotExtended, + HTTPNotFound as HTTPNotFound, + HTTPNotImplemented as HTTPNotImplemented, + HTTPNotModified as HTTPNotModified, + HTTPOk as HTTPOk, + HTTPPartialContent as HTTPPartialContent, + HTTPPaymentRequired as HTTPPaymentRequired, + HTTPPermanentRedirect as HTTPPermanentRedirect, + HTTPPreconditionFailed as HTTPPreconditionFailed, + HTTPPreconditionRequired as HTTPPreconditionRequired, + HTTPProxyAuthenticationRequired as HTTPProxyAuthenticationRequired, + HTTPRedirection as HTTPRedirection, + HTTPRequestEntityTooLarge as HTTPRequestEntityTooLarge, + HTTPRequestHeaderFieldsTooLarge as HTTPRequestHeaderFieldsTooLarge, + HTTPRequestRangeNotSatisfiable as HTTPRequestRangeNotSatisfiable, + HTTPRequestTimeout as HTTPRequestTimeout, + HTTPRequestURITooLong as HTTPRequestURITooLong, + HTTPResetContent as HTTPResetContent, + HTTPSeeOther as HTTPSeeOther, + HTTPServerError as HTTPServerError, + HTTPServiceUnavailable as HTTPServiceUnavailable, + HTTPSuccessful as HTTPSuccessful, + HTTPTemporaryRedirect as HTTPTemporaryRedirect, + HTTPTooManyRequests as HTTPTooManyRequests, + HTTPUnauthorized as HTTPUnauthorized, + HTTPUnavailableForLegalReasons as HTTPUnavailableForLegalReasons, + HTTPUnprocessableEntity as HTTPUnprocessableEntity, + HTTPUnsupportedMediaType as HTTPUnsupportedMediaType, + HTTPUpgradeRequired as HTTPUpgradeRequired, + HTTPUseProxy as HTTPUseProxy, + HTTPVariantAlsoNegotiates as HTTPVariantAlsoNegotiates, + HTTPVersionNotSupported as HTTPVersionNotSupported, + NotAppKeyWarning as NotAppKeyWarning, +) +from .web_fileresponse import FileResponse as FileResponse +from .web_log import AccessLogger +from .web_middlewares import ( + middleware as middleware, + normalize_path_middleware as normalize_path_middleware, +) +from .web_protocol import ( + PayloadAccessError as PayloadAccessError, + RequestHandler as RequestHandler, + RequestPayloadError as RequestPayloadError, +) +from .web_request import ( + BaseRequest as BaseRequest, + FileField as FileField, + Request as Request, +) +from .web_response import ( + ContentCoding as ContentCoding, + Response as Response, + StreamResponse as StreamResponse, + json_response as json_response, +) +from .web_routedef import ( + AbstractRouteDef as AbstractRouteDef, + RouteDef as RouteDef, + RouteTableDef as RouteTableDef, + StaticDef as StaticDef, + delete as delete, + get as get, + head as head, + options as options, + patch as patch, + post as post, + put as put, + route as route, + static as static, + view as view, +) +from .web_runner import ( + AppRunner as AppRunner, + BaseRunner as BaseRunner, + BaseSite as BaseSite, + GracefulExit as GracefulExit, + NamedPipeSite as NamedPipeSite, + ServerRunner as ServerRunner, + SockSite as SockSite, + TCPSite as TCPSite, + UnixSite as UnixSite, +) +from .web_server import Server as Server +from .web_urldispatcher import ( + AbstractResource as AbstractResource, + AbstractRoute as AbstractRoute, + DynamicResource as DynamicResource, + PlainResource as PlainResource, + PrefixedSubAppResource as PrefixedSubAppResource, + Resource as Resource, + ResourceRoute as ResourceRoute, + StaticResource as StaticResource, + UrlDispatcher as UrlDispatcher, + UrlMappingMatchInfo as UrlMappingMatchInfo, + View as View, +) +from .web_ws import ( + WebSocketReady as WebSocketReady, + WebSocketResponse as WebSocketResponse, + WSMsgType as WSMsgType, +) + +__all__ = ( + # web_app + "AppKey", + "Application", + "CleanupError", + # web_exceptions + "NotAppKeyWarning", + "HTTPAccepted", + "HTTPBadGateway", + "HTTPBadRequest", + "HTTPClientError", + "HTTPConflict", + "HTTPCreated", + "HTTPError", + "HTTPException", + "HTTPExpectationFailed", + "HTTPFailedDependency", + "HTTPForbidden", + "HTTPFound", + "HTTPGatewayTimeout", + "HTTPGone", + "HTTPInsufficientStorage", + "HTTPInternalServerError", + "HTTPLengthRequired", + "HTTPMethodNotAllowed", + "HTTPMisdirectedRequest", + "HTTPMove", + "HTTPMovedPermanently", + "HTTPMultipleChoices", + "HTTPNetworkAuthenticationRequired", + "HTTPNoContent", + "HTTPNonAuthoritativeInformation", + "HTTPNotAcceptable", + "HTTPNotExtended", + "HTTPNotFound", + "HTTPNotImplemented", + "HTTPNotModified", + "HTTPOk", + "HTTPPartialContent", + "HTTPPaymentRequired", + "HTTPPermanentRedirect", + "HTTPPreconditionFailed", + "HTTPPreconditionRequired", + "HTTPProxyAuthenticationRequired", + "HTTPRedirection", + "HTTPRequestEntityTooLarge", + "HTTPRequestHeaderFieldsTooLarge", + "HTTPRequestRangeNotSatisfiable", + "HTTPRequestTimeout", + "HTTPRequestURITooLong", + "HTTPResetContent", + "HTTPSeeOther", + "HTTPServerError", + "HTTPServiceUnavailable", + "HTTPSuccessful", + "HTTPTemporaryRedirect", + "HTTPTooManyRequests", + "HTTPUnauthorized", + "HTTPUnavailableForLegalReasons", + "HTTPUnprocessableEntity", + "HTTPUnsupportedMediaType", + "HTTPUpgradeRequired", + "HTTPUseProxy", + "HTTPVariantAlsoNegotiates", + "HTTPVersionNotSupported", + # web_fileresponse + "FileResponse", + # web_middlewares + "middleware", + "normalize_path_middleware", + # web_protocol + "PayloadAccessError", + "RequestHandler", + "RequestPayloadError", + # web_request + "BaseRequest", + "FileField", + "Request", + # web_response + "ContentCoding", + "Response", + "StreamResponse", + "json_response", + # web_routedef + "AbstractRouteDef", + "RouteDef", + "RouteTableDef", + "StaticDef", + "delete", + "get", + "head", + "options", + "patch", + "post", + "put", + "route", + "static", + "view", + # web_runner + "AppRunner", + "BaseRunner", + "BaseSite", + "GracefulExit", + "ServerRunner", + "SockSite", + "TCPSite", + "UnixSite", + "NamedPipeSite", + # web_server + "Server", + # web_urldispatcher + "AbstractResource", + "AbstractRoute", + "DynamicResource", + "PlainResource", + "PrefixedSubAppResource", + "Resource", + "ResourceRoute", + "StaticResource", + "UrlDispatcher", + "UrlMappingMatchInfo", + "View", + # web_ws + "WebSocketReady", + "WebSocketResponse", + "WSMsgType", + # web + "run_app", +) + + +if TYPE_CHECKING: + from ssl import SSLContext +else: + try: + from ssl import SSLContext + except ImportError: # pragma: no cover + SSLContext = object # type: ignore[misc,assignment] + +# Only display warning when using -Wdefault, -We, -X dev or similar. +warnings.filterwarnings("ignore", category=NotAppKeyWarning, append=True) + +HostSequence = TypingIterable[str] + + +async def _run_app( + app: Union[Application, Awaitable[Application]], + *, + host: Optional[Union[str, HostSequence]] = None, + port: Optional[int] = None, + path: Union[PathLike, TypingIterable[PathLike], None] = None, + sock: Optional[Union[socket.socket, TypingIterable[socket.socket]]] = None, + ssl_context: Optional[SSLContext] = None, + print: Optional[Callable[..., None]] = print, + backlog: int = 128, + reuse_address: Optional[bool] = None, + reuse_port: Optional[bool] = None, + **kwargs: Any, # TODO(PY311): Use Unpack +) -> None: + # An internal function to actually do all dirty job for application running + if asyncio.iscoroutine(app): + app = await app + + app = cast(Application, app) + + runner = AppRunner(app, **kwargs) + + await runner.setup() + + sites: List[BaseSite] = [] + + try: + if host is not None: + if isinstance(host, str): + sites.append( + TCPSite( + runner, + host, + port, + ssl_context=ssl_context, + backlog=backlog, + reuse_address=reuse_address, + reuse_port=reuse_port, + ) + ) + else: + for h in host: + sites.append( + TCPSite( + runner, + h, + port, + ssl_context=ssl_context, + backlog=backlog, + reuse_address=reuse_address, + reuse_port=reuse_port, + ) + ) + elif path is None and sock is None or port is not None: + sites.append( + TCPSite( + runner, + port=port, + ssl_context=ssl_context, + backlog=backlog, + reuse_address=reuse_address, + reuse_port=reuse_port, + ) + ) + + if path is not None: + if isinstance(path, (str, os.PathLike)): + sites.append( + UnixSite( + runner, + path, + ssl_context=ssl_context, + backlog=backlog, + ) + ) + else: + for p in path: + sites.append( + UnixSite( + runner, + p, + ssl_context=ssl_context, + backlog=backlog, + ) + ) + + if sock is not None: + if not isinstance(sock, Iterable): + sites.append( + SockSite( + runner, + sock, + ssl_context=ssl_context, + backlog=backlog, + ) + ) + else: + for s in sock: + sites.append( + SockSite( + runner, + s, + ssl_context=ssl_context, + backlog=backlog, + ) + ) + for site in sites: + await site.start() + + if print: # pragma: no branch + names = sorted(str(s.name) for s in runner.sites) + print( + "======== Running on {} ========\n" + "(Press CTRL+C to quit)".format(", ".join(names)) + ) + + # sleep forever by 1 hour intervals, + while True: + await asyncio.sleep(3600) + finally: + await runner.cleanup() + + +def _cancel_tasks( + to_cancel: Set["asyncio.Task[Any]"], loop: asyncio.AbstractEventLoop +) -> None: + if not to_cancel: + return + + for task in to_cancel: + task.cancel() + + loop.run_until_complete(asyncio.gather(*to_cancel, return_exceptions=True)) + + for task in to_cancel: + if task.cancelled(): + continue + if task.exception() is not None: + loop.call_exception_handler( + { + "message": "unhandled exception during asyncio.run() shutdown", + "exception": task.exception(), + "task": task, + } + ) + + +def run_app( + app: Union[Application, Awaitable[Application]], + *, + host: Optional[Union[str, HostSequence]] = None, + port: Optional[int] = None, + path: Union[PathLike, TypingIterable[PathLike], None] = None, + sock: Optional[Union[socket.socket, TypingIterable[socket.socket]]] = None, + shutdown_timeout: float = 60.0, + keepalive_timeout: float = 75.0, + ssl_context: Optional[SSLContext] = None, + print: Optional[Callable[..., None]] = print, + backlog: int = 128, + access_log_class: Type[AbstractAccessLogger] = AccessLogger, + access_log_format: str = AccessLogger.LOG_FORMAT, + access_log: Optional[logging.Logger] = access_logger, + handle_signals: bool = True, + reuse_address: Optional[bool] = None, + reuse_port: Optional[bool] = None, + handler_cancellation: bool = False, + loop: Optional[asyncio.AbstractEventLoop] = None, + **kwargs: Any, +) -> None: + """Run an app locally""" + if loop is None: + loop = asyncio.new_event_loop() + + # Configure if and only if in debugging mode and using the default logger + if loop.get_debug() and access_log and access_log.name == "aiohttp.access": + if access_log.level == logging.NOTSET: + access_log.setLevel(logging.DEBUG) + if not access_log.hasHandlers(): + access_log.addHandler(logging.StreamHandler()) + + main_task = loop.create_task( + _run_app( + app, + host=host, + port=port, + path=path, + sock=sock, + shutdown_timeout=shutdown_timeout, + keepalive_timeout=keepalive_timeout, + ssl_context=ssl_context, + print=print, + backlog=backlog, + access_log_class=access_log_class, + access_log_format=access_log_format, + access_log=access_log, + handle_signals=handle_signals, + reuse_address=reuse_address, + reuse_port=reuse_port, + handler_cancellation=handler_cancellation, + **kwargs, + ) + ) + + try: + asyncio.set_event_loop(loop) + loop.run_until_complete(main_task) + except (GracefulExit, KeyboardInterrupt): # pragma: no cover + pass + finally: + try: + main_task.cancel() + with suppress(asyncio.CancelledError): + loop.run_until_complete(main_task) + finally: + _cancel_tasks(asyncio.all_tasks(loop), loop) + loop.run_until_complete(loop.shutdown_asyncgens()) + loop.close() + + +def main(argv: List[str]) -> None: + arg_parser = ArgumentParser( + description="aiohttp.web Application server", prog="aiohttp.web" + ) + arg_parser.add_argument( + "entry_func", + help=( + "Callable returning the `aiohttp.web.Application` instance to " + "run. Should be specified in the 'module:function' syntax." + ), + metavar="entry-func", + ) + arg_parser.add_argument( + "-H", + "--hostname", + help="TCP/IP hostname to serve on (default: localhost)", + default=None, + ) + arg_parser.add_argument( + "-P", + "--port", + help="TCP/IP port to serve on (default: %(default)r)", + type=int, + default=8080, + ) + arg_parser.add_argument( + "-U", + "--path", + help="Unix file system path to serve on. Can be combined with hostname " + "to serve on both Unix and TCP.", + ) + args, extra_argv = arg_parser.parse_known_args(argv) + + # Import logic + mod_str, _, func_str = args.entry_func.partition(":") + if not func_str or not mod_str: + arg_parser.error("'entry-func' not in 'module:function' syntax") + if mod_str.startswith("."): + arg_parser.error("relative module names not supported") + try: + module = import_module(mod_str) + except ImportError as ex: + arg_parser.error(f"unable to import {mod_str}: {ex}") + try: + func = getattr(module, func_str) + except AttributeError: + arg_parser.error(f"module {mod_str!r} has no attribute {func_str!r}") + + # Compatibility logic + if args.path is not None and not hasattr(socket, "AF_UNIX"): + arg_parser.error( + "file system paths not supported by your operating environment" + ) + + logging.basicConfig(level=logging.DEBUG) + + if args.path and args.hostname is None: + host = port = None + else: + host = args.hostname or "localhost" + port = args.port + + app = func(extra_argv) + run_app(app, host=host, port=port, path=args.path) + arg_parser.exit(message="Stopped\n") + + +if __name__ == "__main__": # pragma: no branch + main(sys.argv[1:]) # pragma: no cover diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web_app.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web_app.py new file mode 100644 index 0000000000000000000000000000000000000000..619c0085da1985b97f7e222d30c1870cb010a128 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web_app.py @@ -0,0 +1,620 @@ +import asyncio +import logging +import warnings +from functools import lru_cache, partial, update_wrapper +from typing import ( + TYPE_CHECKING, + Any, + AsyncIterator, + Awaitable, + Callable, + Dict, + Iterable, + Iterator, + List, + Mapping, + MutableMapping, + Optional, + Sequence, + Tuple, + Type, + TypeVar, + Union, + cast, + overload, +) + +from aiosignal import Signal +from frozenlist import FrozenList + +from . import hdrs +from .abc import ( + AbstractAccessLogger, + AbstractMatchInfo, + AbstractRouter, + AbstractStreamWriter, +) +from .helpers import DEBUG, AppKey +from .http_parser import RawRequestMessage +from .log import web_logger +from .streams import StreamReader +from .typedefs import Handler, Middleware +from .web_exceptions import NotAppKeyWarning +from .web_log import AccessLogger +from .web_middlewares import _fix_request_current_app +from .web_protocol import RequestHandler +from .web_request import Request +from .web_response import StreamResponse +from .web_routedef import AbstractRouteDef +from .web_server import Server +from .web_urldispatcher import ( + AbstractResource, + AbstractRoute, + Domain, + MaskDomain, + MatchedSubAppResource, + PrefixedSubAppResource, + SystemRoute, + UrlDispatcher, +) + +__all__ = ("Application", "CleanupError") + + +if TYPE_CHECKING: + _AppSignal = Signal["Application"] + _RespPrepareSignal = Signal[Request, StreamResponse] + _Middlewares = FrozenList[Middleware] + _MiddlewaresHandlers = Optional[Sequence[Tuple[Middleware, bool]]] + _Subapps = List["Application"] +else: + # No type checker mode, skip types + _AppSignal = Signal + _RespPrepareSignal = Signal + _Middlewares = FrozenList + _MiddlewaresHandlers = Optional[Sequence] + _Subapps = List + +_T = TypeVar("_T") +_U = TypeVar("_U") +_Resource = TypeVar("_Resource", bound=AbstractResource) + + +def _build_middlewares( + handler: Handler, apps: Tuple["Application", ...] +) -> Callable[[Request], Awaitable[StreamResponse]]: + """Apply middlewares to handler.""" + for app in apps[::-1]: + for m, _ in app._middlewares_handlers: # type: ignore[union-attr] + handler = update_wrapper(partial(m, handler=handler), handler) + return handler + + +_cached_build_middleware = lru_cache(maxsize=1024)(_build_middlewares) + + +class Application(MutableMapping[Union[str, AppKey[Any]], Any]): + ATTRS = frozenset( + [ + "logger", + "_debug", + "_router", + "_loop", + "_handler_args", + "_middlewares", + "_middlewares_handlers", + "_has_legacy_middlewares", + "_run_middlewares", + "_state", + "_frozen", + "_pre_frozen", + "_subapps", + "_on_response_prepare", + "_on_startup", + "_on_shutdown", + "_on_cleanup", + "_client_max_size", + "_cleanup_ctx", + ] + ) + + def __init__( + self, + *, + logger: logging.Logger = web_logger, + router: Optional[UrlDispatcher] = None, + middlewares: Iterable[Middleware] = (), + handler_args: Optional[Mapping[str, Any]] = None, + client_max_size: int = 1024**2, + loop: Optional[asyncio.AbstractEventLoop] = None, + debug: Any = ..., # mypy doesn't support ellipsis + ) -> None: + if router is None: + router = UrlDispatcher() + else: + warnings.warn( + "router argument is deprecated", DeprecationWarning, stacklevel=2 + ) + assert isinstance(router, AbstractRouter), router + + if loop is not None: + warnings.warn( + "loop argument is deprecated", DeprecationWarning, stacklevel=2 + ) + + if debug is not ...: + warnings.warn( + "debug argument is deprecated", DeprecationWarning, stacklevel=2 + ) + self._debug = debug + self._router: UrlDispatcher = router + self._loop = loop + self._handler_args = handler_args + self.logger = logger + + self._middlewares: _Middlewares = FrozenList(middlewares) + + # initialized on freezing + self._middlewares_handlers: _MiddlewaresHandlers = None + # initialized on freezing + self._run_middlewares: Optional[bool] = None + self._has_legacy_middlewares: bool = True + + self._state: Dict[Union[AppKey[Any], str], object] = {} + self._frozen = False + self._pre_frozen = False + self._subapps: _Subapps = [] + + self._on_response_prepare: _RespPrepareSignal = Signal(self) + self._on_startup: _AppSignal = Signal(self) + self._on_shutdown: _AppSignal = Signal(self) + self._on_cleanup: _AppSignal = Signal(self) + self._cleanup_ctx = CleanupContext() + self._on_startup.append(self._cleanup_ctx._on_startup) + self._on_cleanup.append(self._cleanup_ctx._on_cleanup) + self._client_max_size = client_max_size + + def __init_subclass__(cls: Type["Application"]) -> None: + warnings.warn( + "Inheritance class {} from web.Application " + "is discouraged".format(cls.__name__), + DeprecationWarning, + stacklevel=3, + ) + + if DEBUG: # pragma: no cover + + def __setattr__(self, name: str, val: Any) -> None: + if name not in self.ATTRS: + warnings.warn( + "Setting custom web.Application.{} attribute " + "is discouraged".format(name), + DeprecationWarning, + stacklevel=2, + ) + super().__setattr__(name, val) + + # MutableMapping API + + def __eq__(self, other: object) -> bool: + return self is other + + @overload # type: ignore[override] + def __getitem__(self, key: AppKey[_T]) -> _T: ... + + @overload + def __getitem__(self, key: str) -> Any: ... + + def __getitem__(self, key: Union[str, AppKey[_T]]) -> Any: + return self._state[key] + + def _check_frozen(self) -> None: + if self._frozen: + warnings.warn( + "Changing state of started or joined application is deprecated", + DeprecationWarning, + stacklevel=3, + ) + + @overload # type: ignore[override] + def __setitem__(self, key: AppKey[_T], value: _T) -> None: ... + + @overload + def __setitem__(self, key: str, value: Any) -> None: ... + + def __setitem__(self, key: Union[str, AppKey[_T]], value: Any) -> None: + self._check_frozen() + if not isinstance(key, AppKey): + warnings.warn( + "It is recommended to use web.AppKey instances for keys.\n" + + "https://docs.aiohttp.org/en/stable/web_advanced.html" + + "#application-s-config", + category=NotAppKeyWarning, + stacklevel=2, + ) + self._state[key] = value + + def __delitem__(self, key: Union[str, AppKey[_T]]) -> None: + self._check_frozen() + del self._state[key] + + def __len__(self) -> int: + return len(self._state) + + def __iter__(self) -> Iterator[Union[str, AppKey[Any]]]: + return iter(self._state) + + def __hash__(self) -> int: + return id(self) + + @overload # type: ignore[override] + def get(self, key: AppKey[_T], default: None = ...) -> Optional[_T]: ... + + @overload + def get(self, key: AppKey[_T], default: _U) -> Union[_T, _U]: ... + + @overload + def get(self, key: str, default: Any = ...) -> Any: ... + + def get(self, key: Union[str, AppKey[_T]], default: Any = None) -> Any: + return self._state.get(key, default) + + ######## + @property + def loop(self) -> asyncio.AbstractEventLoop: + # Technically the loop can be None + # but we mask it by explicit type cast + # to provide more convenient type annotation + warnings.warn("loop property is deprecated", DeprecationWarning, stacklevel=2) + return cast(asyncio.AbstractEventLoop, self._loop) + + def _set_loop(self, loop: Optional[asyncio.AbstractEventLoop]) -> None: + if loop is None: + loop = asyncio.get_event_loop() + if self._loop is not None and self._loop is not loop: + raise RuntimeError( + "web.Application instance initialized with different loop" + ) + + self._loop = loop + + # set loop debug + if self._debug is ...: + self._debug = loop.get_debug() + + # set loop to sub applications + for subapp in self._subapps: + subapp._set_loop(loop) + + @property + def pre_frozen(self) -> bool: + return self._pre_frozen + + def pre_freeze(self) -> None: + if self._pre_frozen: + return + + self._pre_frozen = True + self._middlewares.freeze() + self._router.freeze() + self._on_response_prepare.freeze() + self._cleanup_ctx.freeze() + self._on_startup.freeze() + self._on_shutdown.freeze() + self._on_cleanup.freeze() + self._middlewares_handlers = tuple(self._prepare_middleware()) + self._has_legacy_middlewares = any( + not new_style for _, new_style in self._middlewares_handlers + ) + + # If current app and any subapp do not have middlewares avoid run all + # of the code footprint that it implies, which have a middleware + # hardcoded per app that sets up the current_app attribute. If no + # middlewares are configured the handler will receive the proper + # current_app without needing all of this code. + self._run_middlewares = True if self.middlewares else False + + for subapp in self._subapps: + subapp.pre_freeze() + self._run_middlewares = self._run_middlewares or subapp._run_middlewares + + @property + def frozen(self) -> bool: + return self._frozen + + def freeze(self) -> None: + if self._frozen: + return + + self.pre_freeze() + self._frozen = True + for subapp in self._subapps: + subapp.freeze() + + @property + def debug(self) -> bool: + warnings.warn("debug property is deprecated", DeprecationWarning, stacklevel=2) + return self._debug # type: ignore[no-any-return] + + def _reg_subapp_signals(self, subapp: "Application") -> None: + def reg_handler(signame: str) -> None: + subsig = getattr(subapp, signame) + + async def handler(app: "Application") -> None: + await subsig.send(subapp) + + appsig = getattr(self, signame) + appsig.append(handler) + + reg_handler("on_startup") + reg_handler("on_shutdown") + reg_handler("on_cleanup") + + def add_subapp(self, prefix: str, subapp: "Application") -> PrefixedSubAppResource: + if not isinstance(prefix, str): + raise TypeError("Prefix must be str") + prefix = prefix.rstrip("/") + if not prefix: + raise ValueError("Prefix cannot be empty") + factory = partial(PrefixedSubAppResource, prefix, subapp) + return self._add_subapp(factory, subapp) + + def _add_subapp( + self, resource_factory: Callable[[], _Resource], subapp: "Application" + ) -> _Resource: + if self.frozen: + raise RuntimeError("Cannot add sub application to frozen application") + if subapp.frozen: + raise RuntimeError("Cannot add frozen application") + resource = resource_factory() + self.router.register_resource(resource) + self._reg_subapp_signals(subapp) + self._subapps.append(subapp) + subapp.pre_freeze() + if self._loop is not None: + subapp._set_loop(self._loop) + return resource + + def add_domain(self, domain: str, subapp: "Application") -> MatchedSubAppResource: + if not isinstance(domain, str): + raise TypeError("Domain must be str") + elif "*" in domain: + rule: Domain = MaskDomain(domain) + else: + rule = Domain(domain) + factory = partial(MatchedSubAppResource, rule, subapp) + return self._add_subapp(factory, subapp) + + def add_routes(self, routes: Iterable[AbstractRouteDef]) -> List[AbstractRoute]: + return self.router.add_routes(routes) + + @property + def on_response_prepare(self) -> _RespPrepareSignal: + return self._on_response_prepare + + @property + def on_startup(self) -> _AppSignal: + return self._on_startup + + @property + def on_shutdown(self) -> _AppSignal: + return self._on_shutdown + + @property + def on_cleanup(self) -> _AppSignal: + return self._on_cleanup + + @property + def cleanup_ctx(self) -> "CleanupContext": + return self._cleanup_ctx + + @property + def router(self) -> UrlDispatcher: + return self._router + + @property + def middlewares(self) -> _Middlewares: + return self._middlewares + + def _make_handler( + self, + *, + loop: Optional[asyncio.AbstractEventLoop] = None, + access_log_class: Type[AbstractAccessLogger] = AccessLogger, + **kwargs: Any, + ) -> Server: + + if not issubclass(access_log_class, AbstractAccessLogger): + raise TypeError( + "access_log_class must be subclass of " + "aiohttp.abc.AbstractAccessLogger, got {}".format(access_log_class) + ) + + self._set_loop(loop) + self.freeze() + + kwargs["debug"] = self._debug + kwargs["access_log_class"] = access_log_class + if self._handler_args: + for k, v in self._handler_args.items(): + kwargs[k] = v + + return Server( + self._handle, # type: ignore[arg-type] + request_factory=self._make_request, + loop=self._loop, + **kwargs, + ) + + def make_handler( + self, + *, + loop: Optional[asyncio.AbstractEventLoop] = None, + access_log_class: Type[AbstractAccessLogger] = AccessLogger, + **kwargs: Any, + ) -> Server: + + warnings.warn( + "Application.make_handler(...) is deprecated, use AppRunner API instead", + DeprecationWarning, + stacklevel=2, + ) + + return self._make_handler( + loop=loop, access_log_class=access_log_class, **kwargs + ) + + async def startup(self) -> None: + """Causes on_startup signal + + Should be called in the event loop along with the request handler. + """ + await self.on_startup.send(self) + + async def shutdown(self) -> None: + """Causes on_shutdown signal + + Should be called before cleanup() + """ + await self.on_shutdown.send(self) + + async def cleanup(self) -> None: + """Causes on_cleanup signal + + Should be called after shutdown() + """ + if self.on_cleanup.frozen: + await self.on_cleanup.send(self) + else: + # If an exception occurs in startup, ensure cleanup contexts are completed. + await self._cleanup_ctx._on_cleanup(self) + + def _make_request( + self, + message: RawRequestMessage, + payload: StreamReader, + protocol: RequestHandler, + writer: AbstractStreamWriter, + task: "asyncio.Task[None]", + _cls: Type[Request] = Request, + ) -> Request: + if TYPE_CHECKING: + assert self._loop is not None + return _cls( + message, + payload, + protocol, + writer, + task, + self._loop, + client_max_size=self._client_max_size, + ) + + def _prepare_middleware(self) -> Iterator[Tuple[Middleware, bool]]: + for m in reversed(self._middlewares): + if getattr(m, "__middleware_version__", None) == 1: + yield m, True + else: + warnings.warn( + f'old-style middleware "{m!r}" deprecated, see #2252', + DeprecationWarning, + stacklevel=2, + ) + yield m, False + + yield _fix_request_current_app(self), True + + async def _handle(self, request: Request) -> StreamResponse: + loop = asyncio.get_event_loop() + debug = loop.get_debug() + match_info = await self._router.resolve(request) + if debug: # pragma: no cover + if not isinstance(match_info, AbstractMatchInfo): + raise TypeError( + "match_info should be AbstractMatchInfo " + "instance, not {!r}".format(match_info) + ) + match_info.add_app(self) + + match_info.freeze() + + request._match_info = match_info + + if request.headers.get(hdrs.EXPECT): + resp = await match_info.expect_handler(request) + await request.writer.drain() + if resp is not None: + return resp + + handler = match_info.handler + + if self._run_middlewares: + # If its a SystemRoute, don't cache building the middlewares since + # they are constructed for every MatchInfoError as a new handler + # is made each time. + if not self._has_legacy_middlewares and not isinstance( + match_info.route, SystemRoute + ): + handler = _cached_build_middleware(handler, match_info.apps) + else: + for app in match_info.apps[::-1]: + for m, new_style in app._middlewares_handlers: # type: ignore[union-attr] + if new_style: + handler = update_wrapper( + partial(m, handler=handler), handler + ) + else: + handler = await m(app, handler) # type: ignore[arg-type,assignment] + + return await handler(request) + + def __call__(self) -> "Application": + """gunicorn compatibility""" + return self + + def __repr__(self) -> str: + return f"" + + def __bool__(self) -> bool: + return True + + +class CleanupError(RuntimeError): + @property + def exceptions(self) -> List[BaseException]: + return cast(List[BaseException], self.args[1]) + + +if TYPE_CHECKING: + _CleanupContextBase = FrozenList[Callable[[Application], AsyncIterator[None]]] +else: + _CleanupContextBase = FrozenList + + +class CleanupContext(_CleanupContextBase): + def __init__(self) -> None: + super().__init__() + self._exits: List[AsyncIterator[None]] = [] + + async def _on_startup(self, app: Application) -> None: + for cb in self: + it = cb(app).__aiter__() + await it.__anext__() + self._exits.append(it) + + async def _on_cleanup(self, app: Application) -> None: + errors = [] + for it in reversed(self._exits): + try: + await it.__anext__() + except StopAsyncIteration: + pass + except (Exception, asyncio.CancelledError) as exc: + errors.append(exc) + else: + errors.append(RuntimeError(f"{it!r} has more than one 'yield'")) + if errors: + if len(errors) == 1: + raise errors[0] + else: + raise CleanupError("Multiple errors on cleanup stage", errors) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web_exceptions.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web_exceptions.py new file mode 100644 index 0000000000000000000000000000000000000000..30792c28100aad1d626bace0ae9c54b4233924e2 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web_exceptions.py @@ -0,0 +1,454 @@ +import warnings +from typing import Any, Dict, Iterable, List, Optional, Set # noqa + +from yarl import URL + +from .typedefs import LooseHeaders, StrOrURL +from .web_response import Response + +__all__ = ( + "HTTPException", + "HTTPError", + "HTTPRedirection", + "HTTPSuccessful", + "HTTPOk", + "HTTPCreated", + "HTTPAccepted", + "HTTPNonAuthoritativeInformation", + "HTTPNoContent", + "HTTPResetContent", + "HTTPPartialContent", + "HTTPMove", + "HTTPMultipleChoices", + "HTTPMovedPermanently", + "HTTPFound", + "HTTPSeeOther", + "HTTPNotModified", + "HTTPUseProxy", + "HTTPTemporaryRedirect", + "HTTPPermanentRedirect", + "HTTPClientError", + "HTTPBadRequest", + "HTTPUnauthorized", + "HTTPPaymentRequired", + "HTTPForbidden", + "HTTPNotFound", + "HTTPMethodNotAllowed", + "HTTPNotAcceptable", + "HTTPProxyAuthenticationRequired", + "HTTPRequestTimeout", + "HTTPConflict", + "HTTPGone", + "HTTPLengthRequired", + "HTTPPreconditionFailed", + "HTTPRequestEntityTooLarge", + "HTTPRequestURITooLong", + "HTTPUnsupportedMediaType", + "HTTPRequestRangeNotSatisfiable", + "HTTPExpectationFailed", + "HTTPMisdirectedRequest", + "HTTPUnprocessableEntity", + "HTTPFailedDependency", + "HTTPUpgradeRequired", + "HTTPPreconditionRequired", + "HTTPTooManyRequests", + "HTTPRequestHeaderFieldsTooLarge", + "HTTPUnavailableForLegalReasons", + "HTTPServerError", + "HTTPInternalServerError", + "HTTPNotImplemented", + "HTTPBadGateway", + "HTTPServiceUnavailable", + "HTTPGatewayTimeout", + "HTTPVersionNotSupported", + "HTTPVariantAlsoNegotiates", + "HTTPInsufficientStorage", + "HTTPNotExtended", + "HTTPNetworkAuthenticationRequired", +) + + +class NotAppKeyWarning(UserWarning): + """Warning when not using AppKey in Application.""" + + +############################################################ +# HTTP Exceptions +############################################################ + + +class HTTPException(Response, Exception): + + # You should set in subclasses: + # status = 200 + + status_code = -1 + empty_body = False + + __http_exception__ = True + + def __init__( + self, + *, + headers: Optional[LooseHeaders] = None, + reason: Optional[str] = None, + body: Any = None, + text: Optional[str] = None, + content_type: Optional[str] = None, + ) -> None: + if body is not None: + warnings.warn( + "body argument is deprecated for http web exceptions", + DeprecationWarning, + ) + if reason is not None and ("\r" in reason or "\n" in reason): + raise ValueError("Reason cannot contain \\r or \\n") + Response.__init__( + self, + status=self.status_code, + headers=headers, + reason=reason, + body=body, + text=text, + content_type=content_type, + ) + Exception.__init__(self, self.reason) + if self.body is None and not self.empty_body: + self.text = f"{self.status}: {self.reason}" + + def __bool__(self) -> bool: + return True + + +class HTTPError(HTTPException): + """Base class for exceptions with status codes in the 400s and 500s.""" + + +class HTTPRedirection(HTTPException): + """Base class for exceptions with status codes in the 300s.""" + + +class HTTPSuccessful(HTTPException): + """Base class for exceptions with status codes in the 200s.""" + + +class HTTPOk(HTTPSuccessful): + status_code = 200 + + +class HTTPCreated(HTTPSuccessful): + status_code = 201 + + +class HTTPAccepted(HTTPSuccessful): + status_code = 202 + + +class HTTPNonAuthoritativeInformation(HTTPSuccessful): + status_code = 203 + + +class HTTPNoContent(HTTPSuccessful): + status_code = 204 + empty_body = True + + +class HTTPResetContent(HTTPSuccessful): + status_code = 205 + empty_body = True + + +class HTTPPartialContent(HTTPSuccessful): + status_code = 206 + + +############################################################ +# 3xx redirection +############################################################ + + +class HTTPMove(HTTPRedirection): + def __init__( + self, + location: StrOrURL, + *, + headers: Optional[LooseHeaders] = None, + reason: Optional[str] = None, + body: Any = None, + text: Optional[str] = None, + content_type: Optional[str] = None, + ) -> None: + if not location: + raise ValueError("HTTP redirects need a location to redirect to.") + super().__init__( + headers=headers, + reason=reason, + body=body, + text=text, + content_type=content_type, + ) + self.headers["Location"] = str(URL(location)) + self.location = location + + +class HTTPMultipleChoices(HTTPMove): + status_code = 300 + + +class HTTPMovedPermanently(HTTPMove): + status_code = 301 + + +class HTTPFound(HTTPMove): + status_code = 302 + + +# This one is safe after a POST (the redirected location will be +# retrieved with GET): +class HTTPSeeOther(HTTPMove): + status_code = 303 + + +class HTTPNotModified(HTTPRedirection): + # FIXME: this should include a date or etag header + status_code = 304 + empty_body = True + + +class HTTPUseProxy(HTTPMove): + # Not a move, but looks a little like one + status_code = 305 + + +class HTTPTemporaryRedirect(HTTPMove): + status_code = 307 + + +class HTTPPermanentRedirect(HTTPMove): + status_code = 308 + + +############################################################ +# 4xx client error +############################################################ + + +class HTTPClientError(HTTPError): + pass + + +class HTTPBadRequest(HTTPClientError): + status_code = 400 + + +class HTTPUnauthorized(HTTPClientError): + status_code = 401 + + +class HTTPPaymentRequired(HTTPClientError): + status_code = 402 + + +class HTTPForbidden(HTTPClientError): + status_code = 403 + + +class HTTPNotFound(HTTPClientError): + status_code = 404 + + +class HTTPMethodNotAllowed(HTTPClientError): + status_code = 405 + + def __init__( + self, + method: str, + allowed_methods: Iterable[str], + *, + headers: Optional[LooseHeaders] = None, + reason: Optional[str] = None, + body: Any = None, + text: Optional[str] = None, + content_type: Optional[str] = None, + ) -> None: + allow = ",".join(sorted(allowed_methods)) + super().__init__( + headers=headers, + reason=reason, + body=body, + text=text, + content_type=content_type, + ) + self.headers["Allow"] = allow + self.allowed_methods: Set[str] = set(allowed_methods) + self.method = method.upper() + + +class HTTPNotAcceptable(HTTPClientError): + status_code = 406 + + +class HTTPProxyAuthenticationRequired(HTTPClientError): + status_code = 407 + + +class HTTPRequestTimeout(HTTPClientError): + status_code = 408 + + +class HTTPConflict(HTTPClientError): + status_code = 409 + + +class HTTPGone(HTTPClientError): + status_code = 410 + + +class HTTPLengthRequired(HTTPClientError): + status_code = 411 + + +class HTTPPreconditionFailed(HTTPClientError): + status_code = 412 + + +class HTTPRequestEntityTooLarge(HTTPClientError): + status_code = 413 + + def __init__(self, max_size: float, actual_size: float, **kwargs: Any) -> None: + kwargs.setdefault( + "text", + "Maximum request body size {} exceeded, " + "actual body size {}".format(max_size, actual_size), + ) + super().__init__(**kwargs) + + +class HTTPRequestURITooLong(HTTPClientError): + status_code = 414 + + +class HTTPUnsupportedMediaType(HTTPClientError): + status_code = 415 + + +class HTTPRequestRangeNotSatisfiable(HTTPClientError): + status_code = 416 + + +class HTTPExpectationFailed(HTTPClientError): + status_code = 417 + + +class HTTPMisdirectedRequest(HTTPClientError): + status_code = 421 + + +class HTTPUnprocessableEntity(HTTPClientError): + status_code = 422 + + +class HTTPFailedDependency(HTTPClientError): + status_code = 424 + + +class HTTPUpgradeRequired(HTTPClientError): + status_code = 426 + + +class HTTPPreconditionRequired(HTTPClientError): + status_code = 428 + + +class HTTPTooManyRequests(HTTPClientError): + status_code = 429 + + +class HTTPRequestHeaderFieldsTooLarge(HTTPClientError): + status_code = 431 + + +class HTTPUnavailableForLegalReasons(HTTPClientError): + status_code = 451 + + def __init__( + self, + link: Optional[StrOrURL], + *, + headers: Optional[LooseHeaders] = None, + reason: Optional[str] = None, + body: Any = None, + text: Optional[str] = None, + content_type: Optional[str] = None, + ) -> None: + super().__init__( + headers=headers, + reason=reason, + body=body, + text=text, + content_type=content_type, + ) + self._link = None + if link: + self._link = URL(link) + self.headers["Link"] = f'<{str(self._link)}>; rel="blocked-by"' + + @property + def link(self) -> Optional[URL]: + return self._link + + +############################################################ +# 5xx Server Error +############################################################ +# Response status codes beginning with the digit "5" indicate cases in +# which the server is aware that it has erred or is incapable of +# performing the request. Except when responding to a HEAD request, the +# server SHOULD include an entity containing an explanation of the error +# situation, and whether it is a temporary or permanent condition. User +# agents SHOULD display any included entity to the user. These response +# codes are applicable to any request method. + + +class HTTPServerError(HTTPError): + pass + + +class HTTPInternalServerError(HTTPServerError): + status_code = 500 + + +class HTTPNotImplemented(HTTPServerError): + status_code = 501 + + +class HTTPBadGateway(HTTPServerError): + status_code = 502 + + +class HTTPServiceUnavailable(HTTPServerError): + status_code = 503 + + +class HTTPGatewayTimeout(HTTPServerError): + status_code = 504 + + +class HTTPVersionNotSupported(HTTPServerError): + status_code = 505 + + +class HTTPVariantAlsoNegotiates(HTTPServerError): + status_code = 506 + + +class HTTPInsufficientStorage(HTTPServerError): + status_code = 507 + + +class HTTPNotExtended(HTTPServerError): + status_code = 510 + + +class HTTPNetworkAuthenticationRequired(HTTPServerError): + status_code = 511 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web_fileresponse.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web_fileresponse.py new file mode 100644 index 0000000000000000000000000000000000000000..bba2c2c0d0047dd2a8ee0df4366203d0c9fa61af --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web_fileresponse.py @@ -0,0 +1,418 @@ +import asyncio +import io +import os +import pathlib +import sys +from contextlib import suppress +from enum import Enum, auto +from mimetypes import MimeTypes +from stat import S_ISREG +from types import MappingProxyType +from typing import ( # noqa + IO, + TYPE_CHECKING, + Any, + Awaitable, + Callable, + Final, + Iterator, + List, + Optional, + Set, + Tuple, + Union, + cast, +) + +from . import hdrs +from .abc import AbstractStreamWriter +from .helpers import ETAG_ANY, ETag, must_be_empty_body +from .typedefs import LooseHeaders, PathLike +from .web_exceptions import ( + HTTPForbidden, + HTTPNotFound, + HTTPNotModified, + HTTPPartialContent, + HTTPPreconditionFailed, + HTTPRequestRangeNotSatisfiable, +) +from .web_response import StreamResponse + +__all__ = ("FileResponse",) + +if TYPE_CHECKING: + from .web_request import BaseRequest + + +_T_OnChunkSent = Optional[Callable[[bytes], Awaitable[None]]] + + +NOSENDFILE: Final[bool] = bool(os.environ.get("AIOHTTP_NOSENDFILE")) + +CONTENT_TYPES: Final[MimeTypes] = MimeTypes() + +# File extension to IANA encodings map that will be checked in the order defined. +ENCODING_EXTENSIONS = MappingProxyType( + {ext: CONTENT_TYPES.encodings_map[ext] for ext in (".br", ".gz")} +) + +FALLBACK_CONTENT_TYPE = "application/octet-stream" + +# Provide additional MIME type/extension pairs to be recognized. +# https://en.wikipedia.org/wiki/List_of_archive_formats#Compression_only +ADDITIONAL_CONTENT_TYPES = MappingProxyType( + { + "application/gzip": ".gz", + "application/x-brotli": ".br", + "application/x-bzip2": ".bz2", + "application/x-compress": ".Z", + "application/x-xz": ".xz", + } +) + + +class _FileResponseResult(Enum): + """The result of the file response.""" + + SEND_FILE = auto() # Ie a regular file to send + NOT_ACCEPTABLE = auto() # Ie a socket, or non-regular file + PRE_CONDITION_FAILED = auto() # Ie If-Match or If-None-Match failed + NOT_MODIFIED = auto() # 304 Not Modified + + +# Add custom pairs and clear the encodings map so guess_type ignores them. +CONTENT_TYPES.encodings_map.clear() +for content_type, extension in ADDITIONAL_CONTENT_TYPES.items(): + CONTENT_TYPES.add_type(content_type, extension) + + +_CLOSE_FUTURES: Set[asyncio.Future[None]] = set() + + +class FileResponse(StreamResponse): + """A response object can be used to send files.""" + + def __init__( + self, + path: PathLike, + chunk_size: int = 256 * 1024, + status: int = 200, + reason: Optional[str] = None, + headers: Optional[LooseHeaders] = None, + ) -> None: + super().__init__(status=status, reason=reason, headers=headers) + + self._path = pathlib.Path(path) + self._chunk_size = chunk_size + + def _seek_and_read(self, fobj: IO[Any], offset: int, chunk_size: int) -> bytes: + fobj.seek(offset) + return fobj.read(chunk_size) # type: ignore[no-any-return] + + async def _sendfile_fallback( + self, writer: AbstractStreamWriter, fobj: IO[Any], offset: int, count: int + ) -> AbstractStreamWriter: + # To keep memory usage low,fobj is transferred in chunks + # controlled by the constructor's chunk_size argument. + + chunk_size = self._chunk_size + loop = asyncio.get_event_loop() + chunk = await loop.run_in_executor( + None, self._seek_and_read, fobj, offset, min(chunk_size, count) + ) + while chunk: + await writer.write(chunk) + count = count - len(chunk) + if count <= 0: + break + chunk = await loop.run_in_executor(None, fobj.read, min(chunk_size, count)) + + await writer.drain() + return writer + + async def _sendfile( + self, request: "BaseRequest", fobj: IO[Any], offset: int, count: int + ) -> AbstractStreamWriter: + writer = await super().prepare(request) + assert writer is not None + + if NOSENDFILE or self.compression: + return await self._sendfile_fallback(writer, fobj, offset, count) + + loop = request._loop + transport = request.transport + assert transport is not None + + try: + await loop.sendfile(transport, fobj, offset, count) + except NotImplementedError: + return await self._sendfile_fallback(writer, fobj, offset, count) + + await super().write_eof() + return writer + + @staticmethod + def _etag_match(etag_value: str, etags: Tuple[ETag, ...], *, weak: bool) -> bool: + if len(etags) == 1 and etags[0].value == ETAG_ANY: + return True + return any( + etag.value == etag_value for etag in etags if weak or not etag.is_weak + ) + + async def _not_modified( + self, request: "BaseRequest", etag_value: str, last_modified: float + ) -> Optional[AbstractStreamWriter]: + self.set_status(HTTPNotModified.status_code) + self._length_check = False + self.etag = etag_value + self.last_modified = last_modified + # Delete any Content-Length headers provided by user. HTTP 304 + # should always have empty response body + return await super().prepare(request) + + async def _precondition_failed( + self, request: "BaseRequest" + ) -> Optional[AbstractStreamWriter]: + self.set_status(HTTPPreconditionFailed.status_code) + self.content_length = 0 + return await super().prepare(request) + + def _make_response( + self, request: "BaseRequest", accept_encoding: str + ) -> Tuple[ + _FileResponseResult, Optional[io.BufferedReader], os.stat_result, Optional[str] + ]: + """Return the response result, io object, stat result, and encoding. + + If an uncompressed file is returned, the encoding is set to + :py:data:`None`. + + This method should be called from a thread executor + since it calls os.stat which may block. + """ + file_path, st, file_encoding = self._get_file_path_stat_encoding( + accept_encoding + ) + if not file_path: + return _FileResponseResult.NOT_ACCEPTABLE, None, st, None + + etag_value = f"{st.st_mtime_ns:x}-{st.st_size:x}" + + # https://www.rfc-editor.org/rfc/rfc9110#section-13.1.1-2 + if (ifmatch := request.if_match) is not None and not self._etag_match( + etag_value, ifmatch, weak=False + ): + return _FileResponseResult.PRE_CONDITION_FAILED, None, st, file_encoding + + if ( + (unmodsince := request.if_unmodified_since) is not None + and ifmatch is None + and st.st_mtime > unmodsince.timestamp() + ): + return _FileResponseResult.PRE_CONDITION_FAILED, None, st, file_encoding + + # https://www.rfc-editor.org/rfc/rfc9110#section-13.1.2-2 + if (ifnonematch := request.if_none_match) is not None and self._etag_match( + etag_value, ifnonematch, weak=True + ): + return _FileResponseResult.NOT_MODIFIED, None, st, file_encoding + + if ( + (modsince := request.if_modified_since) is not None + and ifnonematch is None + and st.st_mtime <= modsince.timestamp() + ): + return _FileResponseResult.NOT_MODIFIED, None, st, file_encoding + + fobj = file_path.open("rb") + with suppress(OSError): + # fstat() may not be available on all platforms + # Once we open the file, we want the fstat() to ensure + # the file has not changed between the first stat() + # and the open(). + st = os.stat(fobj.fileno()) + return _FileResponseResult.SEND_FILE, fobj, st, file_encoding + + def _get_file_path_stat_encoding( + self, accept_encoding: str + ) -> Tuple[Optional[pathlib.Path], os.stat_result, Optional[str]]: + file_path = self._path + for file_extension, file_encoding in ENCODING_EXTENSIONS.items(): + if file_encoding not in accept_encoding: + continue + + compressed_path = file_path.with_suffix(file_path.suffix + file_extension) + with suppress(OSError): + # Do not follow symlinks and ignore any non-regular files. + st = compressed_path.lstat() + if S_ISREG(st.st_mode): + return compressed_path, st, file_encoding + + # Fallback to the uncompressed file + st = file_path.stat() + return file_path if S_ISREG(st.st_mode) else None, st, None + + async def prepare(self, request: "BaseRequest") -> Optional[AbstractStreamWriter]: + loop = asyncio.get_running_loop() + # Encoding comparisons should be case-insensitive + # https://www.rfc-editor.org/rfc/rfc9110#section-8.4.1 + accept_encoding = request.headers.get(hdrs.ACCEPT_ENCODING, "").lower() + try: + response_result, fobj, st, file_encoding = await loop.run_in_executor( + None, self._make_response, request, accept_encoding + ) + except PermissionError: + self.set_status(HTTPForbidden.status_code) + return await super().prepare(request) + except OSError: + # Most likely to be FileNotFoundError or OSError for circular + # symlinks in python >= 3.13, so respond with 404. + self.set_status(HTTPNotFound.status_code) + return await super().prepare(request) + + # Forbid special files like sockets, pipes, devices, etc. + if response_result is _FileResponseResult.NOT_ACCEPTABLE: + self.set_status(HTTPForbidden.status_code) + return await super().prepare(request) + + if response_result is _FileResponseResult.PRE_CONDITION_FAILED: + return await self._precondition_failed(request) + + if response_result is _FileResponseResult.NOT_MODIFIED: + etag_value = f"{st.st_mtime_ns:x}-{st.st_size:x}" + last_modified = st.st_mtime + return await self._not_modified(request, etag_value, last_modified) + + assert fobj is not None + try: + return await self._prepare_open_file(request, fobj, st, file_encoding) + finally: + # We do not await here because we do not want to wait + # for the executor to finish before returning the response + # so the connection can begin servicing another request + # as soon as possible. + close_future = loop.run_in_executor(None, fobj.close) + # Hold a strong reference to the future to prevent it from being + # garbage collected before it completes. + _CLOSE_FUTURES.add(close_future) + close_future.add_done_callback(_CLOSE_FUTURES.remove) + + async def _prepare_open_file( + self, + request: "BaseRequest", + fobj: io.BufferedReader, + st: os.stat_result, + file_encoding: Optional[str], + ) -> Optional[AbstractStreamWriter]: + status = self._status + file_size: int = st.st_size + file_mtime: float = st.st_mtime + count: int = file_size + start: Optional[int] = None + + if (ifrange := request.if_range) is None or file_mtime <= ifrange.timestamp(): + # If-Range header check: + # condition = cached date >= last modification date + # return 206 if True else 200. + # if False: + # Range header would not be processed, return 200 + # if True but Range header missing + # return 200 + try: + rng = request.http_range + start = rng.start + end: Optional[int] = rng.stop + except ValueError: + # https://tools.ietf.org/html/rfc7233: + # A server generating a 416 (Range Not Satisfiable) response to + # a byte-range request SHOULD send a Content-Range header field + # with an unsatisfied-range value. + # The complete-length in a 416 response indicates the current + # length of the selected representation. + # + # Will do the same below. Many servers ignore this and do not + # send a Content-Range header with HTTP 416 + self._headers[hdrs.CONTENT_RANGE] = f"bytes */{file_size}" + self.set_status(HTTPRequestRangeNotSatisfiable.status_code) + return await super().prepare(request) + + # If a range request has been made, convert start, end slice + # notation into file pointer offset and count + if start is not None: + if start < 0 and end is None: # return tail of file + start += file_size + if start < 0: + # if Range:bytes=-1000 in request header but file size + # is only 200, there would be trouble without this + start = 0 + count = file_size - start + else: + # rfc7233:If the last-byte-pos value is + # absent, or if the value is greater than or equal to + # the current length of the representation data, + # the byte range is interpreted as the remainder + # of the representation (i.e., the server replaces the + # value of last-byte-pos with a value that is one less than + # the current length of the selected representation). + count = ( + min(end if end is not None else file_size, file_size) - start + ) + + if start >= file_size: + # HTTP 416 should be returned in this case. + # + # According to https://tools.ietf.org/html/rfc7233: + # If a valid byte-range-set includes at least one + # byte-range-spec with a first-byte-pos that is less than + # the current length of the representation, or at least one + # suffix-byte-range-spec with a non-zero suffix-length, + # then the byte-range-set is satisfiable. Otherwise, the + # byte-range-set is unsatisfiable. + self._headers[hdrs.CONTENT_RANGE] = f"bytes */{file_size}" + self.set_status(HTTPRequestRangeNotSatisfiable.status_code) + return await super().prepare(request) + + status = HTTPPartialContent.status_code + # Even though you are sending the whole file, you should still + # return a HTTP 206 for a Range request. + self.set_status(status) + + # If the Content-Type header is not already set, guess it based on the + # extension of the request path. The encoding returned by guess_type + # can be ignored since the map was cleared above. + if hdrs.CONTENT_TYPE not in self._headers: + if sys.version_info >= (3, 13): + guesser = CONTENT_TYPES.guess_file_type + else: + guesser = CONTENT_TYPES.guess_type + self.content_type = guesser(self._path)[0] or FALLBACK_CONTENT_TYPE + + if file_encoding: + self._headers[hdrs.CONTENT_ENCODING] = file_encoding + self._headers[hdrs.VARY] = hdrs.ACCEPT_ENCODING + # Disable compression if we are already sending + # a compressed file since we don't want to double + # compress. + self._compression = False + + self.etag = f"{st.st_mtime_ns:x}-{st.st_size:x}" + self.last_modified = file_mtime + self.content_length = count + + self._headers[hdrs.ACCEPT_RANGES] = "bytes" + + if status == HTTPPartialContent.status_code: + real_start = start + assert real_start is not None + self._headers[hdrs.CONTENT_RANGE] = "bytes {}-{}/{}".format( + real_start, real_start + count - 1, file_size + ) + + # If we are sending 0 bytes calling sendfile() will throw a ValueError + if count == 0 or must_be_empty_body(request.method, status): + return await super().prepare(request) + + # be aware that start could be None or int=0 here. + offset = start or 0 + + return await self._sendfile(request, fobj, offset, count) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web_log.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web_log.py new file mode 100644 index 0000000000000000000000000000000000000000..02a545ae85564321680611528ea21f83c08e9b3e --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web_log.py @@ -0,0 +1,234 @@ +import datetime +import functools +import logging +import os +import re +import time as time_mod +from collections import namedtuple +from collections.abc import Iterable +from typing import Callable, ClassVar, Dict, List, Optional, Tuple + +from .abc import AbstractAccessLogger +from .web_request import BaseRequest +from .web_response import StreamResponse + +KeyMethod = namedtuple("KeyMethod", "key method") + + +class AccessLogger(AbstractAccessLogger): + """Helper object to log access. + + Usage: + log = logging.getLogger("spam") + log_format = "%a %{User-Agent}i" + access_logger = AccessLogger(log, log_format) + access_logger.log(request, response, time) + + Format: + %% The percent sign + %a Remote IP-address (IP-address of proxy if using reverse proxy) + %t Time when the request was started to process + %P The process ID of the child that serviced the request + %r First line of request + %s Response status code + %b Size of response in bytes, including HTTP headers + %T Time taken to serve the request, in seconds + %Tf Time taken to serve the request, in seconds with floating fraction + in .06f format + %D Time taken to serve the request, in microseconds + %{FOO}i request.headers['FOO'] + %{FOO}o response.headers['FOO'] + %{FOO}e os.environ['FOO'] + + """ + + LOG_FORMAT_MAP = { + "a": "remote_address", + "t": "request_start_time", + "P": "process_id", + "r": "first_request_line", + "s": "response_status", + "b": "response_size", + "T": "request_time", + "Tf": "request_time_frac", + "D": "request_time_micro", + "i": "request_header", + "o": "response_header", + } + + LOG_FORMAT = '%a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i"' + FORMAT_RE = re.compile(r"%(\{([A-Za-z0-9\-_]+)\}([ioe])|[atPrsbOD]|Tf?)") + CLEANUP_RE = re.compile(r"(%[^s])") + _FORMAT_CACHE: Dict[str, Tuple[str, List[KeyMethod]]] = {} + + _cached_tz: ClassVar[Optional[datetime.timezone]] = None + _cached_tz_expires: ClassVar[float] = 0.0 + + def __init__(self, logger: logging.Logger, log_format: str = LOG_FORMAT) -> None: + """Initialise the logger. + + logger is a logger object to be used for logging. + log_format is a string with apache compatible log format description. + + """ + super().__init__(logger, log_format=log_format) + + _compiled_format = AccessLogger._FORMAT_CACHE.get(log_format) + if not _compiled_format: + _compiled_format = self.compile_format(log_format) + AccessLogger._FORMAT_CACHE[log_format] = _compiled_format + + self._log_format, self._methods = _compiled_format + + def compile_format(self, log_format: str) -> Tuple[str, List[KeyMethod]]: + """Translate log_format into form usable by modulo formatting + + All known atoms will be replaced with %s + Also methods for formatting of those atoms will be added to + _methods in appropriate order + + For example we have log_format = "%a %t" + This format will be translated to "%s %s" + Also contents of _methods will be + [self._format_a, self._format_t] + These method will be called and results will be passed + to translated string format. + + Each _format_* method receive 'args' which is list of arguments + given to self.log + + Exceptions are _format_e, _format_i and _format_o methods which + also receive key name (by functools.partial) + + """ + # list of (key, method) tuples, we don't use an OrderedDict as users + # can repeat the same key more than once + methods = list() + + for atom in self.FORMAT_RE.findall(log_format): + if atom[1] == "": + format_key1 = self.LOG_FORMAT_MAP[atom[0]] + m = getattr(AccessLogger, "_format_%s" % atom[0]) + key_method = KeyMethod(format_key1, m) + else: + format_key2 = (self.LOG_FORMAT_MAP[atom[2]], atom[1]) + m = getattr(AccessLogger, "_format_%s" % atom[2]) + key_method = KeyMethod(format_key2, functools.partial(m, atom[1])) + + methods.append(key_method) + + log_format = self.FORMAT_RE.sub(r"%s", log_format) + log_format = self.CLEANUP_RE.sub(r"%\1", log_format) + return log_format, methods + + @staticmethod + def _format_i( + key: str, request: BaseRequest, response: StreamResponse, time: float + ) -> str: + if request is None: + return "(no headers)" + + # suboptimal, make istr(key) once + return request.headers.get(key, "-") + + @staticmethod + def _format_o( + key: str, request: BaseRequest, response: StreamResponse, time: float + ) -> str: + # suboptimal, make istr(key) once + return response.headers.get(key, "-") + + @staticmethod + def _format_a(request: BaseRequest, response: StreamResponse, time: float) -> str: + if request is None: + return "-" + ip = request.remote + return ip if ip is not None else "-" + + @classmethod + def _get_local_time(cls) -> datetime.datetime: + if cls._cached_tz is None or time_mod.time() >= cls._cached_tz_expires: + gmtoff = time_mod.localtime().tm_gmtoff + cls._cached_tz = tz = datetime.timezone(datetime.timedelta(seconds=gmtoff)) + + now = datetime.datetime.now(tz) + # Expire at every 30 mins, as any DST change should occur at 0/30 mins past. + d = now + datetime.timedelta(minutes=30) + d = d.replace(minute=30 if d.minute >= 30 else 0, second=0, microsecond=0) + cls._cached_tz_expires = d.timestamp() + return now + + return datetime.datetime.now(cls._cached_tz) + + @staticmethod + def _format_t(request: BaseRequest, response: StreamResponse, time: float) -> str: + now = AccessLogger._get_local_time() + start_time = now - datetime.timedelta(seconds=time) + return start_time.strftime("[%d/%b/%Y:%H:%M:%S %z]") + + @staticmethod + def _format_P(request: BaseRequest, response: StreamResponse, time: float) -> str: + return "<%s>" % os.getpid() + + @staticmethod + def _format_r(request: BaseRequest, response: StreamResponse, time: float) -> str: + if request is None: + return "-" + return "{} {} HTTP/{}.{}".format( + request.method, + request.path_qs, + request.version.major, + request.version.minor, + ) + + @staticmethod + def _format_s(request: BaseRequest, response: StreamResponse, time: float) -> int: + return response.status + + @staticmethod + def _format_b(request: BaseRequest, response: StreamResponse, time: float) -> int: + return response.body_length + + @staticmethod + def _format_T(request: BaseRequest, response: StreamResponse, time: float) -> str: + return str(round(time)) + + @staticmethod + def _format_Tf(request: BaseRequest, response: StreamResponse, time: float) -> str: + return "%06f" % time + + @staticmethod + def _format_D(request: BaseRequest, response: StreamResponse, time: float) -> str: + return str(round(time * 1000000)) + + def _format_line( + self, request: BaseRequest, response: StreamResponse, time: float + ) -> Iterable[Tuple[str, Callable[[BaseRequest, StreamResponse, float], str]]]: + return [(key, method(request, response, time)) for key, method in self._methods] + + @property + def enabled(self) -> bool: + """Check if logger is enabled.""" + # Avoid formatting the log line if it will not be emitted. + return self.logger.isEnabledFor(logging.INFO) + + def log(self, request: BaseRequest, response: StreamResponse, time: float) -> None: + try: + fmt_info = self._format_line(request, response, time) + + values = list() + extra = dict() + for key, value in fmt_info: + values.append(value) + + if key.__class__ is str: + extra[key] = value + else: + k1, k2 = key # type: ignore[misc] + dct = extra.get(k1, {}) # type: ignore[var-annotated,has-type] + dct[k2] = value # type: ignore[index,has-type] + extra[k1] = dct # type: ignore[has-type,assignment] + + self.logger.info(self._log_format % tuple(values), extra=extra) + except Exception: + self.logger.exception("Error in logging") diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web_middlewares.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web_middlewares.py new file mode 100644 index 0000000000000000000000000000000000000000..2f1f5f58e6e38845d4d2d4ffdd2748fc519fa5bf --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web_middlewares.py @@ -0,0 +1,121 @@ +import re +from typing import TYPE_CHECKING, Tuple, Type, TypeVar + +from .typedefs import Handler, Middleware +from .web_exceptions import HTTPMove, HTTPPermanentRedirect +from .web_request import Request +from .web_response import StreamResponse +from .web_urldispatcher import SystemRoute + +__all__ = ( + "middleware", + "normalize_path_middleware", +) + +if TYPE_CHECKING: + from .web_app import Application + +_Func = TypeVar("_Func") + + +async def _check_request_resolves(request: Request, path: str) -> Tuple[bool, Request]: + alt_request = request.clone(rel_url=path) + + match_info = await request.app.router.resolve(alt_request) + alt_request._match_info = match_info + + if match_info.http_exception is None: + return True, alt_request + + return False, request + + +def middleware(f: _Func) -> _Func: + f.__middleware_version__ = 1 # type: ignore[attr-defined] + return f + + +def normalize_path_middleware( + *, + append_slash: bool = True, + remove_slash: bool = False, + merge_slashes: bool = True, + redirect_class: Type[HTTPMove] = HTTPPermanentRedirect, +) -> Middleware: + """Factory for producing a middleware that normalizes the path of a request. + + Normalizing means: + - Add or remove a trailing slash to the path. + - Double slashes are replaced by one. + + The middleware returns as soon as it finds a path that resolves + correctly. The order if both merge and append/remove are enabled is + 1) merge slashes + 2) append/remove slash + 3) both merge slashes and append/remove slash. + If the path resolves with at least one of those conditions, it will + redirect to the new path. + + Only one of `append_slash` and `remove_slash` can be enabled. If both + are `True` the factory will raise an assertion error + + If `append_slash` is `True` the middleware will append a slash when + needed. If a resource is defined with trailing slash and the request + comes without it, it will append it automatically. + + If `remove_slash` is `True`, `append_slash` must be `False`. When enabled + the middleware will remove trailing slashes and redirect if the resource + is defined + + If merge_slashes is True, merge multiple consecutive slashes in the + path into one. + """ + correct_configuration = not (append_slash and remove_slash) + assert correct_configuration, "Cannot both remove and append slash" + + @middleware + async def impl(request: Request, handler: Handler) -> StreamResponse: + if isinstance(request.match_info.route, SystemRoute): + paths_to_check = [] + if "?" in request.raw_path: + path, query = request.raw_path.split("?", 1) + query = "?" + query + else: + query = "" + path = request.raw_path + + if merge_slashes: + paths_to_check.append(re.sub("//+", "/", path)) + if append_slash and not request.path.endswith("/"): + paths_to_check.append(path + "/") + if remove_slash and request.path.endswith("/"): + paths_to_check.append(path[:-1]) + if merge_slashes and append_slash: + paths_to_check.append(re.sub("//+", "/", path + "/")) + if merge_slashes and remove_slash: + merged_slashes = re.sub("//+", "/", path) + paths_to_check.append(merged_slashes[:-1]) + + for path in paths_to_check: + path = re.sub("^//+", "/", path) # SECURITY: GHSA-v6wp-4m6f-gcjg + resolves, request = await _check_request_resolves(request, path) + if resolves: + raise redirect_class(request.raw_path + query) + + return await handler(request) + + return impl + + +def _fix_request_current_app(app: "Application") -> Middleware: + @middleware + async def impl(request: Request, handler: Handler) -> StreamResponse: + match_info = request.match_info + prev = match_info.current_app + match_info.current_app = app + try: + return await handler(request) + finally: + match_info.current_app = prev + + return impl diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web_protocol.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web_protocol.py new file mode 100644 index 0000000000000000000000000000000000000000..84e70e5908858995c1fce15c22dafe37b0c72de6 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web_protocol.py @@ -0,0 +1,799 @@ +import asyncio +import asyncio.streams +import sys +import traceback +import warnings +from collections import deque +from contextlib import suppress +from html import escape as html_escape +from http import HTTPStatus +from logging import Logger +from typing import ( + TYPE_CHECKING, + Any, + Awaitable, + Callable, + Deque, + Optional, + Sequence, + Tuple, + Type, + Union, + cast, +) + +import attr +import yarl +from propcache import under_cached_property + +from .abc import AbstractAccessLogger, AbstractStreamWriter +from .base_protocol import BaseProtocol +from .helpers import ceil_timeout +from .http import ( + HttpProcessingError, + HttpRequestParser, + HttpVersion10, + RawRequestMessage, + StreamWriter, +) +from .http_exceptions import BadHttpMethod +from .log import access_logger, server_logger +from .streams import EMPTY_PAYLOAD, StreamReader +from .tcp_helpers import tcp_keepalive +from .web_exceptions import HTTPException, HTTPInternalServerError +from .web_log import AccessLogger +from .web_request import BaseRequest +from .web_response import Response, StreamResponse + +__all__ = ("RequestHandler", "RequestPayloadError", "PayloadAccessError") + +if TYPE_CHECKING: + import ssl + + from .web_server import Server + + +_RequestFactory = Callable[ + [ + RawRequestMessage, + StreamReader, + "RequestHandler", + AbstractStreamWriter, + "asyncio.Task[None]", + ], + BaseRequest, +] + +_RequestHandler = Callable[[BaseRequest], Awaitable[StreamResponse]] + +ERROR = RawRequestMessage( + "UNKNOWN", + "/", + HttpVersion10, + {}, # type: ignore[arg-type] + {}, # type: ignore[arg-type] + True, + None, + False, + False, + yarl.URL("/"), +) + + +class RequestPayloadError(Exception): + """Payload parsing error.""" + + +class PayloadAccessError(Exception): + """Payload was accessed after response was sent.""" + + +_PAYLOAD_ACCESS_ERROR = PayloadAccessError() + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class _ErrInfo: + status: int + exc: BaseException + message: str + + +_MsgType = Tuple[Union[RawRequestMessage, _ErrInfo], StreamReader] + + +class RequestHandler(BaseProtocol): + """HTTP protocol implementation. + + RequestHandler handles incoming HTTP request. It reads request line, + request headers and request payload and calls handle_request() method. + By default it always returns with 404 response. + + RequestHandler handles errors in incoming request, like bad + status line, bad headers or incomplete payload. If any error occurs, + connection gets closed. + + keepalive_timeout -- number of seconds before closing + keep-alive connection + + tcp_keepalive -- TCP keep-alive is on, default is on + + debug -- enable debug mode + + logger -- custom logger object + + access_log_class -- custom class for access_logger + + access_log -- custom logging object + + access_log_format -- access log format string + + loop -- Optional event loop + + max_line_size -- Optional maximum header line size + + max_field_size -- Optional maximum header field size + + max_headers -- Optional maximum header size + + timeout_ceil_threshold -- Optional value to specify + threshold to ceil() timeout + values + + """ + + __slots__ = ( + "max_field_size", + "max_headers", + "max_line_size", + "_request_count", + "_keepalive", + "_manager", + "_request_handler", + "_request_factory", + "_tcp_keepalive", + "_next_keepalive_close_time", + "_keepalive_handle", + "_keepalive_timeout", + "_lingering_time", + "_messages", + "_message_tail", + "_handler_waiter", + "_waiter", + "_task_handler", + "_upgrade", + "_payload_parser", + "_request_parser", + "_reading_paused", + "logger", + "debug", + "access_log", + "access_logger", + "_close", + "_force_close", + "_current_request", + "_timeout_ceil_threshold", + "_request_in_progress", + "_logging_enabled", + "_cache", + ) + + def __init__( + self, + manager: "Server", + *, + loop: asyncio.AbstractEventLoop, + # Default should be high enough that it's likely longer than a reverse proxy. + keepalive_timeout: float = 3630, + tcp_keepalive: bool = True, + logger: Logger = server_logger, + access_log_class: Type[AbstractAccessLogger] = AccessLogger, + access_log: Logger = access_logger, + access_log_format: str = AccessLogger.LOG_FORMAT, + debug: bool = False, + max_line_size: int = 8190, + max_headers: int = 128, + max_field_size: int = 8190, + lingering_time: float = 10.0, + read_bufsize: int = 2**16, + auto_decompress: bool = True, + timeout_ceil_threshold: float = 5, + ): + super().__init__(loop) + + # _request_count is the number of requests processed with the same connection. + self._request_count = 0 + self._keepalive = False + self._current_request: Optional[BaseRequest] = None + self._manager: Optional[Server] = manager + self._request_handler: Optional[_RequestHandler] = manager.request_handler + self._request_factory: Optional[_RequestFactory] = manager.request_factory + + self.max_line_size = max_line_size + self.max_headers = max_headers + self.max_field_size = max_field_size + + self._tcp_keepalive = tcp_keepalive + # placeholder to be replaced on keepalive timeout setup + self._next_keepalive_close_time = 0.0 + self._keepalive_handle: Optional[asyncio.Handle] = None + self._keepalive_timeout = keepalive_timeout + self._lingering_time = float(lingering_time) + + self._messages: Deque[_MsgType] = deque() + self._message_tail = b"" + + self._waiter: Optional[asyncio.Future[None]] = None + self._handler_waiter: Optional[asyncio.Future[None]] = None + self._task_handler: Optional[asyncio.Task[None]] = None + + self._upgrade = False + self._payload_parser: Any = None + self._request_parser: Optional[HttpRequestParser] = HttpRequestParser( + self, + loop, + read_bufsize, + max_line_size=max_line_size, + max_field_size=max_field_size, + max_headers=max_headers, + payload_exception=RequestPayloadError, + auto_decompress=auto_decompress, + ) + + self._timeout_ceil_threshold: float = 5 + try: + self._timeout_ceil_threshold = float(timeout_ceil_threshold) + except (TypeError, ValueError): + pass + + self.logger = logger + self.debug = debug + self.access_log = access_log + if access_log: + self.access_logger: Optional[AbstractAccessLogger] = access_log_class( + access_log, access_log_format + ) + self._logging_enabled = self.access_logger.enabled + else: + self.access_logger = None + self._logging_enabled = False + + self._close = False + self._force_close = False + self._request_in_progress = False + self._cache: dict[str, Any] = {} + + def __repr__(self) -> str: + return "<{} {}>".format( + self.__class__.__name__, + "connected" if self.transport is not None else "disconnected", + ) + + @under_cached_property + def ssl_context(self) -> Optional["ssl.SSLContext"]: + """Return SSLContext if available.""" + return ( + None + if self.transport is None + else self.transport.get_extra_info("sslcontext") + ) + + @under_cached_property + def peername( + self, + ) -> Optional[Union[str, Tuple[str, int, int, int], Tuple[str, int]]]: + """Return peername if available.""" + return ( + None + if self.transport is None + else self.transport.get_extra_info("peername") + ) + + @property + def keepalive_timeout(self) -> float: + return self._keepalive_timeout + + async def shutdown(self, timeout: Optional[float] = 15.0) -> None: + """Do worker process exit preparations. + + We need to clean up everything and stop accepting requests. + It is especially important for keep-alive connections. + """ + self._force_close = True + + if self._keepalive_handle is not None: + self._keepalive_handle.cancel() + + # Wait for graceful handler completion + if self._request_in_progress: + # The future is only created when we are shutting + # down while the handler is still processing a request + # to avoid creating a future for every request. + self._handler_waiter = self._loop.create_future() + try: + async with ceil_timeout(timeout): + await self._handler_waiter + except (asyncio.CancelledError, asyncio.TimeoutError): + self._handler_waiter = None + if ( + sys.version_info >= (3, 11) + and (task := asyncio.current_task()) + and task.cancelling() + ): + raise + # Then cancel handler and wait + try: + async with ceil_timeout(timeout): + if self._current_request is not None: + self._current_request._cancel(asyncio.CancelledError()) + + if self._task_handler is not None and not self._task_handler.done(): + await asyncio.shield(self._task_handler) + except (asyncio.CancelledError, asyncio.TimeoutError): + if ( + sys.version_info >= (3, 11) + and (task := asyncio.current_task()) + and task.cancelling() + ): + raise + + # force-close non-idle handler + if self._task_handler is not None: + self._task_handler.cancel() + + self.force_close() + + def connection_made(self, transport: asyncio.BaseTransport) -> None: + super().connection_made(transport) + + real_transport = cast(asyncio.Transport, transport) + if self._tcp_keepalive: + tcp_keepalive(real_transport) + + assert self._manager is not None + self._manager.connection_made(self, real_transport) + + loop = self._loop + if sys.version_info >= (3, 12): + task = asyncio.Task(self.start(), loop=loop, eager_start=True) + else: + task = loop.create_task(self.start()) + self._task_handler = task + + def connection_lost(self, exc: Optional[BaseException]) -> None: + if self._manager is None: + return + self._manager.connection_lost(self, exc) + + # Grab value before setting _manager to None. + handler_cancellation = self._manager.handler_cancellation + + self.force_close() + super().connection_lost(exc) + self._manager = None + self._request_factory = None + self._request_handler = None + self._request_parser = None + + if self._keepalive_handle is not None: + self._keepalive_handle.cancel() + + if self._current_request is not None: + if exc is None: + exc = ConnectionResetError("Connection lost") + self._current_request._cancel(exc) + + if handler_cancellation and self._task_handler is not None: + self._task_handler.cancel() + + self._task_handler = None + + if self._payload_parser is not None: + self._payload_parser.feed_eof() + self._payload_parser = None + + def set_parser(self, parser: Any) -> None: + # Actual type is WebReader + assert self._payload_parser is None + + self._payload_parser = parser + + if self._message_tail: + self._payload_parser.feed_data(self._message_tail) + self._message_tail = b"" + + def eof_received(self) -> None: + pass + + def data_received(self, data: bytes) -> None: + if self._force_close or self._close: + return + # parse http messages + messages: Sequence[_MsgType] + if self._payload_parser is None and not self._upgrade: + assert self._request_parser is not None + try: + messages, upgraded, tail = self._request_parser.feed_data(data) + except HttpProcessingError as exc: + messages = [ + (_ErrInfo(status=400, exc=exc, message=exc.message), EMPTY_PAYLOAD) + ] + upgraded = False + tail = b"" + + for msg, payload in messages or (): + self._request_count += 1 + self._messages.append((msg, payload)) + + waiter = self._waiter + if messages and waiter is not None and not waiter.done(): + # don't set result twice + waiter.set_result(None) + + self._upgrade = upgraded + if upgraded and tail: + self._message_tail = tail + + # no parser, just store + elif self._payload_parser is None and self._upgrade and data: + self._message_tail += data + + # feed payload + elif data: + eof, tail = self._payload_parser.feed_data(data) + if eof: + self.close() + + def keep_alive(self, val: bool) -> None: + """Set keep-alive connection mode. + + :param bool val: new state. + """ + self._keepalive = val + if self._keepalive_handle: + self._keepalive_handle.cancel() + self._keepalive_handle = None + + def close(self) -> None: + """Close connection. + + Stop accepting new pipelining messages and close + connection when handlers done processing messages. + """ + self._close = True + if self._waiter: + self._waiter.cancel() + + def force_close(self) -> None: + """Forcefully close connection.""" + self._force_close = True + if self._waiter: + self._waiter.cancel() + if self.transport is not None: + self.transport.close() + self.transport = None + + def log_access( + self, request: BaseRequest, response: StreamResponse, time: Optional[float] + ) -> None: + if self._logging_enabled and self.access_logger is not None: + if TYPE_CHECKING: + assert time is not None + self.access_logger.log(request, response, self._loop.time() - time) + + def log_debug(self, *args: Any, **kw: Any) -> None: + if self.debug: + self.logger.debug(*args, **kw) + + def log_exception(self, *args: Any, **kw: Any) -> None: + self.logger.exception(*args, **kw) + + def _process_keepalive(self) -> None: + self._keepalive_handle = None + if self._force_close or not self._keepalive: + return + + loop = self._loop + now = loop.time() + close_time = self._next_keepalive_close_time + if now < close_time: + # Keep alive close check fired too early, reschedule + self._keepalive_handle = loop.call_at(close_time, self._process_keepalive) + return + + # handler in idle state + if self._waiter and not self._waiter.done(): + self.force_close() + + async def _handle_request( + self, + request: BaseRequest, + start_time: Optional[float], + request_handler: Callable[[BaseRequest], Awaitable[StreamResponse]], + ) -> Tuple[StreamResponse, bool]: + self._request_in_progress = True + try: + try: + self._current_request = request + resp = await request_handler(request) + finally: + self._current_request = None + except HTTPException as exc: + resp = exc + resp, reset = await self.finish_response(request, resp, start_time) + except asyncio.CancelledError: + raise + except asyncio.TimeoutError as exc: + self.log_debug("Request handler timed out.", exc_info=exc) + resp = self.handle_error(request, 504) + resp, reset = await self.finish_response(request, resp, start_time) + except Exception as exc: + resp = self.handle_error(request, 500, exc) + resp, reset = await self.finish_response(request, resp, start_time) + else: + # Deprecation warning (See #2415) + if getattr(resp, "__http_exception__", False): + warnings.warn( + "returning HTTPException object is deprecated " + "(#2415) and will be removed, " + "please raise the exception instead", + DeprecationWarning, + ) + + resp, reset = await self.finish_response(request, resp, start_time) + finally: + self._request_in_progress = False + if self._handler_waiter is not None: + self._handler_waiter.set_result(None) + + return resp, reset + + async def start(self) -> None: + """Process incoming request. + + It reads request line, request headers and request payload, then + calls handle_request() method. Subclass has to override + handle_request(). start() handles various exceptions in request + or response handling. Connection is being closed always unless + keep_alive(True) specified. + """ + loop = self._loop + manager = self._manager + assert manager is not None + keepalive_timeout = self._keepalive_timeout + resp = None + assert self._request_factory is not None + assert self._request_handler is not None + + while not self._force_close: + if not self._messages: + try: + # wait for next request + self._waiter = loop.create_future() + await self._waiter + finally: + self._waiter = None + + message, payload = self._messages.popleft() + + # time is only fetched if logging is enabled as otherwise + # its thrown away and never used. + start = loop.time() if self._logging_enabled else None + + manager.requests_count += 1 + writer = StreamWriter(self, loop) + if isinstance(message, _ErrInfo): + # make request_factory work + request_handler = self._make_error_handler(message) + message = ERROR + else: + request_handler = self._request_handler + + # Important don't hold a reference to the current task + # as on traceback it will prevent the task from being + # collected and will cause a memory leak. + request = self._request_factory( + message, + payload, + self, + writer, + self._task_handler or asyncio.current_task(loop), # type: ignore[arg-type] + ) + try: + # a new task is used for copy context vars (#3406) + coro = self._handle_request(request, start, request_handler) + if sys.version_info >= (3, 12): + task = asyncio.Task(coro, loop=loop, eager_start=True) + else: + task = loop.create_task(coro) + try: + resp, reset = await task + except ConnectionError: + self.log_debug("Ignored premature client disconnection") + break + + # Drop the processed task from asyncio.Task.all_tasks() early + del task + if reset: + self.log_debug("Ignored premature client disconnection 2") + break + + # notify server about keep-alive + self._keepalive = bool(resp.keep_alive) + + # check payload + if not payload.is_eof(): + lingering_time = self._lingering_time + if not self._force_close and lingering_time: + self.log_debug( + "Start lingering close timer for %s sec.", lingering_time + ) + + now = loop.time() + end_t = now + lingering_time + + try: + while not payload.is_eof() and now < end_t: + async with ceil_timeout(end_t - now): + # read and ignore + await payload.readany() + now = loop.time() + except (asyncio.CancelledError, asyncio.TimeoutError): + if ( + sys.version_info >= (3, 11) + and (t := asyncio.current_task()) + and t.cancelling() + ): + raise + + # if payload still uncompleted + if not payload.is_eof() and not self._force_close: + self.log_debug("Uncompleted request.") + self.close() + + payload.set_exception(_PAYLOAD_ACCESS_ERROR) + + except asyncio.CancelledError: + self.log_debug("Ignored premature client disconnection") + self.force_close() + raise + except Exception as exc: + self.log_exception("Unhandled exception", exc_info=exc) + self.force_close() + except BaseException: + self.force_close() + raise + finally: + request._task = None # type: ignore[assignment] # Break reference cycle in case of exception + if self.transport is None and resp is not None: + self.log_debug("Ignored premature client disconnection.") + + if self._keepalive and not self._close and not self._force_close: + # start keep-alive timer + close_time = loop.time() + keepalive_timeout + self._next_keepalive_close_time = close_time + if self._keepalive_handle is None: + self._keepalive_handle = loop.call_at( + close_time, self._process_keepalive + ) + else: + break + + # remove handler, close transport if no handlers left + if not self._force_close: + self._task_handler = None + if self.transport is not None: + self.transport.close() + + async def finish_response( + self, request: BaseRequest, resp: StreamResponse, start_time: Optional[float] + ) -> Tuple[StreamResponse, bool]: + """Prepare the response and write_eof, then log access. + + This has to + be called within the context of any exception so the access logger + can get exception information. Returns True if the client disconnects + prematurely. + """ + request._finish() + if self._request_parser is not None: + self._request_parser.set_upgraded(False) + self._upgrade = False + if self._message_tail: + self._request_parser.feed_data(self._message_tail) + self._message_tail = b"" + try: + prepare_meth = resp.prepare + except AttributeError: + if resp is None: + self.log_exception("Missing return statement on request handler") + else: + self.log_exception( + "Web-handler should return a response instance, " + "got {!r}".format(resp) + ) + exc = HTTPInternalServerError() + resp = Response( + status=exc.status, reason=exc.reason, text=exc.text, headers=exc.headers + ) + prepare_meth = resp.prepare + try: + await prepare_meth(request) + await resp.write_eof() + except ConnectionError: + self.log_access(request, resp, start_time) + return resp, True + + self.log_access(request, resp, start_time) + return resp, False + + def handle_error( + self, + request: BaseRequest, + status: int = 500, + exc: Optional[BaseException] = None, + message: Optional[str] = None, + ) -> StreamResponse: + """Handle errors. + + Returns HTTP response with specific status code. Logs additional + information. It always closes current connection. + """ + if self._request_count == 1 and isinstance(exc, BadHttpMethod): + # BadHttpMethod is common when a client sends non-HTTP + # or encrypted traffic to an HTTP port. This is expected + # to happen when connected to the public internet so we log + # it at the debug level as to not fill logs with noise. + self.logger.debug( + "Error handling request from %s", request.remote, exc_info=exc + ) + else: + self.log_exception( + "Error handling request from %s", request.remote, exc_info=exc + ) + + # some data already got sent, connection is broken + if request.writer.output_size > 0: + raise ConnectionError( + "Response is sent already, cannot send another response " + "with the error message" + ) + + ct = "text/plain" + if status == HTTPStatus.INTERNAL_SERVER_ERROR: + title = "{0.value} {0.phrase}".format(HTTPStatus.INTERNAL_SERVER_ERROR) + msg = HTTPStatus.INTERNAL_SERVER_ERROR.description + tb = None + if self.debug: + with suppress(Exception): + tb = traceback.format_exc() + + if "text/html" in request.headers.get("Accept", ""): + if tb: + tb = html_escape(tb) + msg = f"

    Traceback:

    \n
    {tb}
    " + message = ( + "" + "{title}" + "\n

    {title}

    " + "\n{msg}\n\n" + ).format(title=title, msg=msg) + ct = "text/html" + else: + if tb: + msg = tb + message = title + "\n\n" + msg + + resp = Response(status=status, text=message, content_type=ct) + resp.force_close() + + return resp + + def _make_error_handler( + self, err_info: _ErrInfo + ) -> Callable[[BaseRequest], Awaitable[StreamResponse]]: + async def handler(request: BaseRequest) -> StreamResponse: + return self.handle_error( + request, err_info.status, err_info.exc, err_info.message + ) + + return handler diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web_request.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web_request.py new file mode 100644 index 0000000000000000000000000000000000000000..f2d4c511e44e36d1fdb9c7f489ee00b0c586bc07 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web_request.py @@ -0,0 +1,927 @@ +import asyncio +import datetime +import io +import re +import socket +import string +import tempfile +import types +import warnings +from types import MappingProxyType +from typing import ( + TYPE_CHECKING, + Any, + Dict, + Final, + Iterator, + Mapping, + MutableMapping, + Optional, + Pattern, + Tuple, + Union, + cast, +) +from urllib.parse import parse_qsl + +import attr +from multidict import ( + CIMultiDict, + CIMultiDictProxy, + MultiDict, + MultiDictProxy, + MultiMapping, +) +from yarl import URL + +from . import hdrs +from ._cookie_helpers import parse_cookie_header +from .abc import AbstractStreamWriter +from .helpers import ( + _SENTINEL, + DEBUG, + ETAG_ANY, + LIST_QUOTED_ETAG_RE, + ChainMapProxy, + ETag, + HeadersMixin, + parse_http_date, + reify, + sentinel, + set_exception, +) +from .http_parser import RawRequestMessage +from .http_writer import HttpVersion +from .multipart import BodyPartReader, MultipartReader +from .streams import EmptyStreamReader, StreamReader +from .typedefs import ( + DEFAULT_JSON_DECODER, + JSONDecoder, + LooseHeaders, + RawHeaders, + StrOrURL, +) +from .web_exceptions import HTTPRequestEntityTooLarge +from .web_response import StreamResponse + +__all__ = ("BaseRequest", "FileField", "Request") + + +if TYPE_CHECKING: + from .web_app import Application + from .web_protocol import RequestHandler + from .web_urldispatcher import UrlMappingMatchInfo + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class FileField: + name: str + filename: str + file: io.BufferedReader + content_type: str + headers: CIMultiDictProxy[str] + + +_TCHAR: Final[str] = string.digits + string.ascii_letters + r"!#$%&'*+.^_`|~-" +# '-' at the end to prevent interpretation as range in a char class + +_TOKEN: Final[str] = rf"[{_TCHAR}]+" + +_QDTEXT: Final[str] = r"[{}]".format( + r"".join(chr(c) for c in (0x09, 0x20, 0x21) + tuple(range(0x23, 0x7F))) +) +# qdtext includes 0x5C to escape 0x5D ('\]') +# qdtext excludes obs-text (because obsoleted, and encoding not specified) + +_QUOTED_PAIR: Final[str] = r"\\[\t !-~]" + +_QUOTED_STRING: Final[str] = r'"(?:{quoted_pair}|{qdtext})*"'.format( + qdtext=_QDTEXT, quoted_pair=_QUOTED_PAIR +) + +_FORWARDED_PAIR: Final[str] = ( + r"({token})=({token}|{quoted_string})(:\d{{1,4}})?".format( + token=_TOKEN, quoted_string=_QUOTED_STRING + ) +) + +_QUOTED_PAIR_REPLACE_RE: Final[Pattern[str]] = re.compile(r"\\([\t !-~])") +# same pattern as _QUOTED_PAIR but contains a capture group + +_FORWARDED_PAIR_RE: Final[Pattern[str]] = re.compile(_FORWARDED_PAIR) + +############################################################ +# HTTP Request +############################################################ + + +class BaseRequest(MutableMapping[str, Any], HeadersMixin): + + POST_METHODS = { + hdrs.METH_PATCH, + hdrs.METH_POST, + hdrs.METH_PUT, + hdrs.METH_TRACE, + hdrs.METH_DELETE, + } + + ATTRS = HeadersMixin.ATTRS | frozenset( + [ + "_message", + "_protocol", + "_payload_writer", + "_payload", + "_headers", + "_method", + "_version", + "_rel_url", + "_post", + "_read_bytes", + "_state", + "_cache", + "_task", + "_client_max_size", + "_loop", + "_transport_sslcontext", + "_transport_peername", + ] + ) + _post: Optional[MultiDictProxy[Union[str, bytes, FileField]]] = None + _read_bytes: Optional[bytes] = None + + def __init__( + self, + message: RawRequestMessage, + payload: StreamReader, + protocol: "RequestHandler", + payload_writer: AbstractStreamWriter, + task: "asyncio.Task[None]", + loop: asyncio.AbstractEventLoop, + *, + client_max_size: int = 1024**2, + state: Optional[Dict[str, Any]] = None, + scheme: Optional[str] = None, + host: Optional[str] = None, + remote: Optional[str] = None, + ) -> None: + self._message = message + self._protocol = protocol + self._payload_writer = payload_writer + + self._payload = payload + self._headers: CIMultiDictProxy[str] = message.headers + self._method = message.method + self._version = message.version + self._cache: Dict[str, Any] = {} + url = message.url + if url.absolute: + if scheme is not None: + url = url.with_scheme(scheme) + if host is not None: + url = url.with_host(host) + # absolute URL is given, + # override auto-calculating url, host, and scheme + # all other properties should be good + self._cache["url"] = url + self._cache["host"] = url.host + self._cache["scheme"] = url.scheme + self._rel_url = url.relative() + else: + self._rel_url = url + if scheme is not None: + self._cache["scheme"] = scheme + if host is not None: + self._cache["host"] = host + + self._state = {} if state is None else state + self._task = task + self._client_max_size = client_max_size + self._loop = loop + + self._transport_sslcontext = protocol.ssl_context + self._transport_peername = protocol.peername + + if remote is not None: + self._cache["remote"] = remote + + def clone( + self, + *, + method: Union[str, _SENTINEL] = sentinel, + rel_url: Union[StrOrURL, _SENTINEL] = sentinel, + headers: Union[LooseHeaders, _SENTINEL] = sentinel, + scheme: Union[str, _SENTINEL] = sentinel, + host: Union[str, _SENTINEL] = sentinel, + remote: Union[str, _SENTINEL] = sentinel, + client_max_size: Union[int, _SENTINEL] = sentinel, + ) -> "BaseRequest": + """Clone itself with replacement some attributes. + + Creates and returns a new instance of Request object. If no parameters + are given, an exact copy is returned. If a parameter is not passed, it + will reuse the one from the current request object. + """ + if self._read_bytes: + raise RuntimeError("Cannot clone request after reading its content") + + dct: Dict[str, Any] = {} + if method is not sentinel: + dct["method"] = method + if rel_url is not sentinel: + new_url: URL = URL(rel_url) + dct["url"] = new_url + dct["path"] = str(new_url) + if headers is not sentinel: + # a copy semantic + dct["headers"] = CIMultiDictProxy(CIMultiDict(headers)) + dct["raw_headers"] = tuple( + (k.encode("utf-8"), v.encode("utf-8")) + for k, v in dct["headers"].items() + ) + + message = self._message._replace(**dct) + + kwargs = {} + if scheme is not sentinel: + kwargs["scheme"] = scheme + if host is not sentinel: + kwargs["host"] = host + if remote is not sentinel: + kwargs["remote"] = remote + if client_max_size is sentinel: + client_max_size = self._client_max_size + + return self.__class__( + message, + self._payload, + self._protocol, + self._payload_writer, + self._task, + self._loop, + client_max_size=client_max_size, + state=self._state.copy(), + **kwargs, + ) + + @property + def task(self) -> "asyncio.Task[None]": + return self._task + + @property + def protocol(self) -> "RequestHandler": + return self._protocol + + @property + def transport(self) -> Optional[asyncio.Transport]: + if self._protocol is None: + return None + return self._protocol.transport + + @property + def writer(self) -> AbstractStreamWriter: + return self._payload_writer + + @property + def client_max_size(self) -> int: + return self._client_max_size + + @reify + def message(self) -> RawRequestMessage: + warnings.warn("Request.message is deprecated", DeprecationWarning, stacklevel=3) + return self._message + + @reify + def rel_url(self) -> URL: + return self._rel_url + + @reify + def loop(self) -> asyncio.AbstractEventLoop: + warnings.warn( + "request.loop property is deprecated", DeprecationWarning, stacklevel=2 + ) + return self._loop + + # MutableMapping API + + def __getitem__(self, key: str) -> Any: + return self._state[key] + + def __setitem__(self, key: str, value: Any) -> None: + self._state[key] = value + + def __delitem__(self, key: str) -> None: + del self._state[key] + + def __len__(self) -> int: + return len(self._state) + + def __iter__(self) -> Iterator[str]: + return iter(self._state) + + ######## + + @reify + def secure(self) -> bool: + """A bool indicating if the request is handled with SSL.""" + return self.scheme == "https" + + @reify + def forwarded(self) -> Tuple[Mapping[str, str], ...]: + """A tuple containing all parsed Forwarded header(s). + + Makes an effort to parse Forwarded headers as specified by RFC 7239: + + - It adds one (immutable) dictionary per Forwarded 'field-value', ie + per proxy. The element corresponds to the data in the Forwarded + field-value added by the first proxy encountered by the client. Each + subsequent item corresponds to those added by later proxies. + - It checks that every value has valid syntax in general as specified + in section 4: either a 'token' or a 'quoted-string'. + - It un-escapes found escape sequences. + - It does NOT validate 'by' and 'for' contents as specified in section + 6. + - It does NOT validate 'host' contents (Host ABNF). + - It does NOT validate 'proto' contents for valid URI scheme names. + + Returns a tuple containing one or more immutable dicts + """ + elems = [] + for field_value in self._message.headers.getall(hdrs.FORWARDED, ()): + length = len(field_value) + pos = 0 + need_separator = False + elem: Dict[str, str] = {} + elems.append(types.MappingProxyType(elem)) + while 0 <= pos < length: + match = _FORWARDED_PAIR_RE.match(field_value, pos) + if match is not None: # got a valid forwarded-pair + if need_separator: + # bad syntax here, skip to next comma + pos = field_value.find(",", pos) + else: + name, value, port = match.groups() + if value[0] == '"': + # quoted string: remove quotes and unescape + value = _QUOTED_PAIR_REPLACE_RE.sub(r"\1", value[1:-1]) + if port: + value += port + elem[name.lower()] = value + pos += len(match.group(0)) + need_separator = True + elif field_value[pos] == ",": # next forwarded-element + need_separator = False + elem = {} + elems.append(types.MappingProxyType(elem)) + pos += 1 + elif field_value[pos] == ";": # next forwarded-pair + need_separator = False + pos += 1 + elif field_value[pos] in " \t": + # Allow whitespace even between forwarded-pairs, though + # RFC 7239 doesn't. This simplifies code and is in line + # with Postel's law. + pos += 1 + else: + # bad syntax here, skip to next comma + pos = field_value.find(",", pos) + return tuple(elems) + + @reify + def scheme(self) -> str: + """A string representing the scheme of the request. + + Hostname is resolved in this order: + + - overridden value by .clone(scheme=new_scheme) call. + - type of connection to peer: HTTPS if socket is SSL, HTTP otherwise. + + 'http' or 'https'. + """ + if self._transport_sslcontext: + return "https" + else: + return "http" + + @reify + def method(self) -> str: + """Read only property for getting HTTP method. + + The value is upper-cased str like 'GET', 'POST', 'PUT' etc. + """ + return self._method + + @reify + def version(self) -> HttpVersion: + """Read only property for getting HTTP version of request. + + Returns aiohttp.protocol.HttpVersion instance. + """ + return self._version + + @reify + def host(self) -> str: + """Hostname of the request. + + Hostname is resolved in this order: + + - overridden value by .clone(host=new_host) call. + - HOST HTTP header + - socket.getfqdn() value + + For example, 'example.com' or 'localhost:8080'. + + For historical reasons, the port number may be included. + """ + host = self._message.headers.get(hdrs.HOST) + if host is not None: + return host + return socket.getfqdn() + + @reify + def remote(self) -> Optional[str]: + """Remote IP of client initiated HTTP request. + + The IP is resolved in this order: + + - overridden value by .clone(remote=new_remote) call. + - peername of opened socket + """ + if self._transport_peername is None: + return None + if isinstance(self._transport_peername, (list, tuple)): + return str(self._transport_peername[0]) + return str(self._transport_peername) + + @reify + def url(self) -> URL: + """The full URL of the request.""" + # authority is used here because it may include the port number + # and we want yarl to parse it correctly + return URL.build(scheme=self.scheme, authority=self.host).join(self._rel_url) + + @reify + def path(self) -> str: + """The URL including *PATH INFO* without the host or scheme. + + E.g., ``/app/blog`` + """ + return self._rel_url.path + + @reify + def path_qs(self) -> str: + """The URL including PATH_INFO and the query string. + + E.g, /app/blog?id=10 + """ + return str(self._rel_url) + + @reify + def raw_path(self) -> str: + """The URL including raw *PATH INFO* without the host or scheme. + + Warning, the path is unquoted and may contains non valid URL characters + + E.g., ``/my%2Fpath%7Cwith%21some%25strange%24characters`` + """ + return self._message.path + + @reify + def query(self) -> "MultiMapping[str]": + """A multidict with all the variables in the query string.""" + return self._rel_url.query + + @reify + def query_string(self) -> str: + """The query string in the URL. + + E.g., id=10 + """ + return self._rel_url.query_string + + @reify + def headers(self) -> CIMultiDictProxy[str]: + """A case-insensitive multidict proxy with all headers.""" + return self._headers + + @reify + def raw_headers(self) -> RawHeaders: + """A sequence of pairs for all headers.""" + return self._message.raw_headers + + @reify + def if_modified_since(self) -> Optional[datetime.datetime]: + """The value of If-Modified-Since HTTP header, or None. + + This header is represented as a `datetime` object. + """ + return parse_http_date(self.headers.get(hdrs.IF_MODIFIED_SINCE)) + + @reify + def if_unmodified_since(self) -> Optional[datetime.datetime]: + """The value of If-Unmodified-Since HTTP header, or None. + + This header is represented as a `datetime` object. + """ + return parse_http_date(self.headers.get(hdrs.IF_UNMODIFIED_SINCE)) + + @staticmethod + def _etag_values(etag_header: str) -> Iterator[ETag]: + """Extract `ETag` objects from raw header.""" + if etag_header == ETAG_ANY: + yield ETag( + is_weak=False, + value=ETAG_ANY, + ) + else: + for match in LIST_QUOTED_ETAG_RE.finditer(etag_header): + is_weak, value, garbage = match.group(2, 3, 4) + # Any symbol captured by 4th group means + # that the following sequence is invalid. + if garbage: + break + + yield ETag( + is_weak=bool(is_weak), + value=value, + ) + + @classmethod + def _if_match_or_none_impl( + cls, header_value: Optional[str] + ) -> Optional[Tuple[ETag, ...]]: + if not header_value: + return None + + return tuple(cls._etag_values(header_value)) + + @reify + def if_match(self) -> Optional[Tuple[ETag, ...]]: + """The value of If-Match HTTP header, or None. + + This header is represented as a `tuple` of `ETag` objects. + """ + return self._if_match_or_none_impl(self.headers.get(hdrs.IF_MATCH)) + + @reify + def if_none_match(self) -> Optional[Tuple[ETag, ...]]: + """The value of If-None-Match HTTP header, or None. + + This header is represented as a `tuple` of `ETag` objects. + """ + return self._if_match_or_none_impl(self.headers.get(hdrs.IF_NONE_MATCH)) + + @reify + def if_range(self) -> Optional[datetime.datetime]: + """The value of If-Range HTTP header, or None. + + This header is represented as a `datetime` object. + """ + return parse_http_date(self.headers.get(hdrs.IF_RANGE)) + + @reify + def keep_alive(self) -> bool: + """Is keepalive enabled by client?""" + return not self._message.should_close + + @reify + def cookies(self) -> Mapping[str, str]: + """Return request cookies. + + A read-only dictionary-like object. + """ + # Use parse_cookie_header for RFC 6265 compliant Cookie header parsing + # that accepts special characters in cookie names (fixes #2683) + parsed = parse_cookie_header(self.headers.get(hdrs.COOKIE, "")) + # Extract values from Morsel objects + return MappingProxyType({name: morsel.value for name, morsel in parsed}) + + @reify + def http_range(self) -> slice: + """The content of Range HTTP header. + + Return a slice instance. + + """ + rng = self._headers.get(hdrs.RANGE) + start, end = None, None + if rng is not None: + try: + pattern = r"^bytes=(\d*)-(\d*)$" + start, end = re.findall(pattern, rng, re.ASCII)[0] + except IndexError: # pattern was not found in header + raise ValueError("range not in acceptable format") + + end = int(end) if end else None + start = int(start) if start else None + + if start is None and end is not None: + # end with no start is to return tail of content + start = -end + end = None + + if start is not None and end is not None: + # end is inclusive in range header, exclusive for slice + end += 1 + + if start >= end: + raise ValueError("start cannot be after end") + + if start is end is None: # No valid range supplied + raise ValueError("No start or end of range specified") + + return slice(start, end, 1) + + @reify + def content(self) -> StreamReader: + """Return raw payload stream.""" + return self._payload + + @property + def has_body(self) -> bool: + """Return True if request's HTTP BODY can be read, False otherwise.""" + warnings.warn( + "Deprecated, use .can_read_body #2005", DeprecationWarning, stacklevel=2 + ) + return not self._payload.at_eof() + + @property + def can_read_body(self) -> bool: + """Return True if request's HTTP BODY can be read, False otherwise.""" + return not self._payload.at_eof() + + @reify + def body_exists(self) -> bool: + """Return True if request has HTTP BODY, False otherwise.""" + return type(self._payload) is not EmptyStreamReader + + async def release(self) -> None: + """Release request. + + Eat unread part of HTTP BODY if present. + """ + while not self._payload.at_eof(): + await self._payload.readany() + + async def read(self) -> bytes: + """Read request body if present. + + Returns bytes object with full request content. + """ + if self._read_bytes is None: + body = bytearray() + while True: + chunk = await self._payload.readany() + body.extend(chunk) + if self._client_max_size: + body_size = len(body) + if body_size >= self._client_max_size: + raise HTTPRequestEntityTooLarge( + max_size=self._client_max_size, actual_size=body_size + ) + if not chunk: + break + self._read_bytes = bytes(body) + return self._read_bytes + + async def text(self) -> str: + """Return BODY as text using encoding from .charset.""" + bytes_body = await self.read() + encoding = self.charset or "utf-8" + return bytes_body.decode(encoding) + + async def json(self, *, loads: JSONDecoder = DEFAULT_JSON_DECODER) -> Any: + """Return BODY as JSON.""" + body = await self.text() + return loads(body) + + async def multipart(self) -> MultipartReader: + """Return async iterator to process BODY as multipart.""" + return MultipartReader( + self._headers, + self._payload, + max_field_size=self._protocol.max_field_size, + max_headers=self._protocol.max_headers, + ) + + async def post(self) -> "MultiDictProxy[Union[str, bytes, FileField]]": + """Return POST parameters.""" + if self._post is not None: + return self._post + if self._method not in self.POST_METHODS: + self._post = MultiDictProxy(MultiDict()) + return self._post + + content_type = self.content_type + if content_type not in ( + "", + "application/x-www-form-urlencoded", + "multipart/form-data", + ): + self._post = MultiDictProxy(MultiDict()) + return self._post + + out: MultiDict[Union[str, bytes, FileField]] = MultiDict() + + if content_type == "multipart/form-data": + multipart = await self.multipart() + max_size = self._client_max_size + + size = 0 + while (field := await multipart.next()) is not None: + field_ct = field.headers.get(hdrs.CONTENT_TYPE) + + if isinstance(field, BodyPartReader): + if field.name is None: + raise ValueError("Multipart field missing name.") + + # Note that according to RFC 7578, the Content-Type header + # is optional, even for files, so we can't assume it's + # present. + # https://tools.ietf.org/html/rfc7578#section-4.4 + if field.filename: + # store file in temp file + tmp = await self._loop.run_in_executor( + None, tempfile.TemporaryFile + ) + while chunk := await field.read_chunk(size=2**18): + async for decoded_chunk in field.decode_iter(chunk): + await self._loop.run_in_executor( + None, tmp.write, decoded_chunk + ) + size += len(decoded_chunk) + if 0 < max_size < size: + await self._loop.run_in_executor(None, tmp.close) + raise HTTPRequestEntityTooLarge( + max_size=max_size, actual_size=size + ) + await self._loop.run_in_executor(None, tmp.seek, 0) + + if field_ct is None: + field_ct = "application/octet-stream" + + ff = FileField( + field.name, + field.filename, + cast(io.BufferedReader, tmp), + field_ct, + field.headers, + ) + out.add(field.name, ff) + else: + # deal with ordinary data + raw_data = bytearray() + while chunk := await field.read_chunk(): + size += len(chunk) + if 0 < max_size < size: + raise HTTPRequestEntityTooLarge( + max_size=max_size, actual_size=size + ) + raw_data.extend(chunk) + + value = bytearray() + # form-data doesn't support compression, so don't need to check size again. + async for d in field.decode_iter(raw_data): + value.extend(d) + + if field_ct is None or field_ct.startswith("text/"): + charset = field.get_charset(default="utf-8") + out.add(field.name, value.decode(charset)) + else: + out.add(field.name, value) + else: + raise ValueError( + "To decode nested multipart you need to use custom reader", + ) + else: + data = await self.read() + if data: + charset = self.charset or "utf-8" + out.extend( + parse_qsl( + data.rstrip().decode(charset), + keep_blank_values=True, + encoding=charset, + ) + ) + + self._post = MultiDictProxy(out) + return self._post + + def get_extra_info(self, name: str, default: Any = None) -> Any: + """Extra info from protocol transport""" + protocol = self._protocol + if protocol is None: + return default + + transport = protocol.transport + if transport is None: + return default + + return transport.get_extra_info(name, default) + + def __repr__(self) -> str: + ascii_encodable_path = self.path.encode("ascii", "backslashreplace").decode( + "ascii" + ) + return "<{} {} {} >".format( + self.__class__.__name__, self._method, ascii_encodable_path + ) + + def __eq__(self, other: object) -> bool: + return id(self) == id(other) + + def __bool__(self) -> bool: + return True + + async def _prepare_hook(self, response: StreamResponse) -> None: + return + + def _cancel(self, exc: BaseException) -> None: + set_exception(self._payload, exc) + + def _finish(self) -> None: + if self._post is None or self.content_type != "multipart/form-data": + return + + # NOTE: Release file descriptors for the + # NOTE: `tempfile.Temporaryfile`-created `_io.BufferedRandom` + # NOTE: instances of files sent within multipart request body + # NOTE: via HTTP POST request. + for file_name, file_field_object in self._post.items(): + if isinstance(file_field_object, FileField): + file_field_object.file.close() + + +class Request(BaseRequest): + + ATTRS = BaseRequest.ATTRS | frozenset(["_match_info"]) + + _match_info: Optional["UrlMappingMatchInfo"] = None + + if DEBUG: + + def __setattr__(self, name: str, val: Any) -> None: + if name not in self.ATTRS: + warnings.warn( + "Setting custom {}.{} attribute " + "is discouraged".format(self.__class__.__name__, name), + DeprecationWarning, + stacklevel=2, + ) + super().__setattr__(name, val) + + def clone( + self, + *, + method: Union[str, _SENTINEL] = sentinel, + rel_url: Union[StrOrURL, _SENTINEL] = sentinel, + headers: Union[LooseHeaders, _SENTINEL] = sentinel, + scheme: Union[str, _SENTINEL] = sentinel, + host: Union[str, _SENTINEL] = sentinel, + remote: Union[str, _SENTINEL] = sentinel, + client_max_size: Union[int, _SENTINEL] = sentinel, + ) -> "Request": + ret = super().clone( + method=method, + rel_url=rel_url, + headers=headers, + scheme=scheme, + host=host, + remote=remote, + client_max_size=client_max_size, + ) + new_ret = cast(Request, ret) + new_ret._match_info = self._match_info + return new_ret + + @reify + def match_info(self) -> "UrlMappingMatchInfo": + """Result of route resolving.""" + match_info = self._match_info + assert match_info is not None + return match_info + + @property + def app(self) -> "Application": + """Application instance.""" + match_info = self._match_info + assert match_info is not None + return match_info.current_app + + @property + def config_dict(self) -> ChainMapProxy: + match_info = self._match_info + assert match_info is not None + lst = match_info.apps + app = self.app + idx = lst.index(app) + sublist = list(reversed(lst[: idx + 1])) + return ChainMapProxy(sublist) + + async def _prepare_hook(self, response: StreamResponse) -> None: + match_info = self._match_info + if match_info is None: + return + for app in match_info._apps: + if on_response_prepare := app.on_response_prepare: + await on_response_prepare.send(self, response) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web_response.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web_response.py new file mode 100644 index 0000000000000000000000000000000000000000..364270e4dcaae7c66be068efc1394fd57a901b09 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web_response.py @@ -0,0 +1,856 @@ +import asyncio +import collections.abc +import datetime +import enum +import json +import math +import time +import warnings +from concurrent.futures import Executor +from http import HTTPStatus +from http.cookies import SimpleCookie +from typing import ( + TYPE_CHECKING, + Any, + Dict, + Iterator, + MutableMapping, + Optional, + Union, + cast, +) + +from multidict import CIMultiDict, istr + +from . import hdrs, payload +from .abc import AbstractStreamWriter +from .compression_utils import ZLibCompressor +from .helpers import ( + ETAG_ANY, + QUOTED_ETAG_RE, + ETag, + HeadersMixin, + must_be_empty_body, + parse_http_date, + rfc822_formatted_time, + sentinel, + should_remove_content_length, + validate_etag_value, +) +from .http import SERVER_SOFTWARE, HttpVersion10, HttpVersion11 +from .payload import Payload +from .typedefs import JSONEncoder, LooseHeaders + +REASON_PHRASES = {http_status.value: http_status.phrase for http_status in HTTPStatus} +LARGE_BODY_SIZE = 1024**2 + +__all__ = ("ContentCoding", "StreamResponse", "Response", "json_response") + + +if TYPE_CHECKING: + from .web_request import BaseRequest + + BaseClass = MutableMapping[str, Any] +else: + BaseClass = collections.abc.MutableMapping + + +# TODO(py311): Convert to StrEnum for wider use +class ContentCoding(enum.Enum): + # The content codings that we have support for. + # + # Additional registered codings are listed at: + # https://www.iana.org/assignments/http-parameters/http-parameters.xhtml#content-coding + deflate = "deflate" + gzip = "gzip" + identity = "identity" + + +CONTENT_CODINGS = {coding.value: coding for coding in ContentCoding} + +############################################################ +# HTTP Response classes +############################################################ + + +class StreamResponse(BaseClass, HeadersMixin): + + _body: Union[None, bytes, bytearray, Payload] + _length_check = True + _body = None + _keep_alive: Optional[bool] = None + _chunked: bool = False + _compression: bool = False + _compression_strategy: Optional[int] = None + _compression_force: Optional[ContentCoding] = None + _req: Optional["BaseRequest"] = None + _payload_writer: Optional[AbstractStreamWriter] = None + _eof_sent: bool = False + _must_be_empty_body: Optional[bool] = None + _body_length = 0 + _cookies: Optional[SimpleCookie] = None + _send_headers_immediately = True + + def __init__( + self, + *, + status: int = 200, + reason: Optional[str] = None, + headers: Optional[LooseHeaders] = None, + _real_headers: Optional[CIMultiDict[str]] = None, + ) -> None: + """Initialize a new stream response object. + + _real_headers is an internal parameter used to pass a pre-populated + headers object. It is used by the `Response` class to avoid copying + the headers when creating a new response object. It is not intended + to be used by external code. + """ + self._state: Dict[str, Any] = {} + + if _real_headers is not None: + self._headers = _real_headers + elif headers is not None: + self._headers: CIMultiDict[str] = CIMultiDict(headers) + else: + self._headers = CIMultiDict() + + self._set_status(status, reason) + + @property + def prepared(self) -> bool: + return self._eof_sent or self._payload_writer is not None + + @property + def task(self) -> "Optional[asyncio.Task[None]]": + if self._req: + return self._req.task + else: + return None + + @property + def status(self) -> int: + return self._status + + @property + def chunked(self) -> bool: + return self._chunked + + @property + def compression(self) -> bool: + return self._compression + + @property + def reason(self) -> str: + return self._reason + + def set_status( + self, + status: int, + reason: Optional[str] = None, + ) -> None: + assert ( + not self.prepared + ), "Cannot change the response status code after the headers have been sent" + self._set_status(status, reason) + + def _set_status(self, status: int, reason: Optional[str]) -> None: + self._status = int(status) + if reason is None: + reason = REASON_PHRASES.get(self._status, "") + elif "\r" in reason or "\n" in reason: + raise ValueError("Reason cannot contain \\r or \\n") + self._reason = reason + + @property + def keep_alive(self) -> Optional[bool]: + return self._keep_alive + + def force_close(self) -> None: + self._keep_alive = False + + @property + def body_length(self) -> int: + return self._body_length + + @property + def output_length(self) -> int: + warnings.warn("output_length is deprecated", DeprecationWarning) + assert self._payload_writer + return self._payload_writer.buffer_size + + def enable_chunked_encoding(self, chunk_size: Optional[int] = None) -> None: + """Enables automatic chunked transfer encoding.""" + if hdrs.CONTENT_LENGTH in self._headers: + raise RuntimeError( + "You can't enable chunked encoding when a content length is set" + ) + if chunk_size is not None: + warnings.warn("Chunk size is deprecated #1615", DeprecationWarning) + self._chunked = True + + def enable_compression( + self, + force: Optional[Union[bool, ContentCoding]] = None, + strategy: Optional[int] = None, + ) -> None: + """Enables response compression encoding.""" + # Backwards compatibility for when force was a bool <0.17. + if isinstance(force, bool): + force = ContentCoding.deflate if force else ContentCoding.identity + warnings.warn( + "Using boolean for force is deprecated #3318", DeprecationWarning + ) + elif force is not None: + assert isinstance( + force, ContentCoding + ), "force should one of None, bool or ContentEncoding" + + self._compression = True + self._compression_force = force + self._compression_strategy = strategy + + @property + def headers(self) -> "CIMultiDict[str]": + return self._headers + + @property + def cookies(self) -> SimpleCookie: + if self._cookies is None: + self._cookies = SimpleCookie() + return self._cookies + + def set_cookie( + self, + name: str, + value: str, + *, + expires: Optional[str] = None, + domain: Optional[str] = None, + max_age: Optional[Union[int, str]] = None, + path: str = "/", + secure: Optional[bool] = None, + httponly: Optional[bool] = None, + version: Optional[str] = None, + samesite: Optional[str] = None, + partitioned: Optional[bool] = None, + ) -> None: + """Set or update response cookie. + + Sets new cookie or updates existent with new value. + Also updates only those params which are not None. + """ + if self._cookies is None: + self._cookies = SimpleCookie() + + self._cookies[name] = value + c = self._cookies[name] + + if expires is not None: + c["expires"] = expires + elif c.get("expires") == "Thu, 01 Jan 1970 00:00:00 GMT": + del c["expires"] + + if domain is not None: + c["domain"] = domain + + if max_age is not None: + c["max-age"] = str(max_age) + elif "max-age" in c: + del c["max-age"] + + c["path"] = path + + if secure is not None: + c["secure"] = secure + if httponly is not None: + c["httponly"] = httponly + if version is not None: + c["version"] = version + if samesite is not None: + c["samesite"] = samesite + + if partitioned is not None: + c["partitioned"] = partitioned + + def del_cookie( + self, + name: str, + *, + domain: Optional[str] = None, + path: str = "/", + secure: Optional[bool] = None, + httponly: Optional[bool] = None, + samesite: Optional[str] = None, + ) -> None: + """Delete cookie. + + Creates new empty expired cookie. + """ + # TODO: do we need domain/path here? + if self._cookies is not None: + self._cookies.pop(name, None) + self.set_cookie( + name, + "", + max_age=0, + expires="Thu, 01 Jan 1970 00:00:00 GMT", + domain=domain, + path=path, + secure=secure, + httponly=httponly, + samesite=samesite, + ) + + @property + def content_length(self) -> Optional[int]: + # Just a placeholder for adding setter + return super().content_length + + @content_length.setter + def content_length(self, value: Optional[int]) -> None: + if value is not None: + value = int(value) + if self._chunked: + raise RuntimeError( + "You can't set content length when chunked encoding is enable" + ) + self._headers[hdrs.CONTENT_LENGTH] = str(value) + else: + self._headers.pop(hdrs.CONTENT_LENGTH, None) + + @property + def content_type(self) -> str: + # Just a placeholder for adding setter + return super().content_type + + @content_type.setter + def content_type(self, value: str) -> None: + self.content_type # read header values if needed + self._content_type = str(value) + self._generate_content_type_header() + + @property + def charset(self) -> Optional[str]: + # Just a placeholder for adding setter + return super().charset + + @charset.setter + def charset(self, value: Optional[str]) -> None: + ctype = self.content_type # read header values if needed + if ctype == "application/octet-stream": + raise RuntimeError( + "Setting charset for application/octet-stream " + "doesn't make sense, setup content_type first" + ) + assert self._content_dict is not None + if value is None: + self._content_dict.pop("charset", None) + else: + self._content_dict["charset"] = str(value).lower() + self._generate_content_type_header() + + @property + def last_modified(self) -> Optional[datetime.datetime]: + """The value of Last-Modified HTTP header, or None. + + This header is represented as a `datetime` object. + """ + return parse_http_date(self._headers.get(hdrs.LAST_MODIFIED)) + + @last_modified.setter + def last_modified( + self, value: Optional[Union[int, float, datetime.datetime, str]] + ) -> None: + if value is None: + self._headers.pop(hdrs.LAST_MODIFIED, None) + elif isinstance(value, (int, float)): + self._headers[hdrs.LAST_MODIFIED] = time.strftime( + "%a, %d %b %Y %H:%M:%S GMT", time.gmtime(math.ceil(value)) + ) + elif isinstance(value, datetime.datetime): + self._headers[hdrs.LAST_MODIFIED] = time.strftime( + "%a, %d %b %Y %H:%M:%S GMT", value.utctimetuple() + ) + elif isinstance(value, str): + self._headers[hdrs.LAST_MODIFIED] = value + else: + msg = f"Unsupported type for last_modified: {type(value).__name__}" + raise TypeError(msg) + + @property + def etag(self) -> Optional[ETag]: + quoted_value = self._headers.get(hdrs.ETAG) + if not quoted_value: + return None + elif quoted_value == ETAG_ANY: + return ETag(value=ETAG_ANY) + match = QUOTED_ETAG_RE.fullmatch(quoted_value) + if not match: + return None + is_weak, value = match.group(1, 2) + return ETag( + is_weak=bool(is_weak), + value=value, + ) + + @etag.setter + def etag(self, value: Optional[Union[ETag, str]]) -> None: + if value is None: + self._headers.pop(hdrs.ETAG, None) + elif (isinstance(value, str) and value == ETAG_ANY) or ( + isinstance(value, ETag) and value.value == ETAG_ANY + ): + self._headers[hdrs.ETAG] = ETAG_ANY + elif isinstance(value, str): + validate_etag_value(value) + self._headers[hdrs.ETAG] = f'"{value}"' + elif isinstance(value, ETag) and isinstance(value.value, str): + validate_etag_value(value.value) + hdr_value = f'W/"{value.value}"' if value.is_weak else f'"{value.value}"' + self._headers[hdrs.ETAG] = hdr_value + else: + raise ValueError( + f"Unsupported etag type: {type(value)}. " + f"etag must be str, ETag or None" + ) + + def _generate_content_type_header( + self, CONTENT_TYPE: istr = hdrs.CONTENT_TYPE + ) -> None: + assert self._content_dict is not None + assert self._content_type is not None + params = "; ".join(f"{k}={v}" for k, v in self._content_dict.items()) + if params: + ctype = self._content_type + "; " + params + else: + ctype = self._content_type + self._headers[CONTENT_TYPE] = ctype + + async def _do_start_compression(self, coding: ContentCoding) -> None: + if coding is ContentCoding.identity: + return + assert self._payload_writer is not None + self._headers[hdrs.CONTENT_ENCODING] = coding.value + self._payload_writer.enable_compression( + coding.value, self._compression_strategy + ) + # Compressed payload may have different content length, + # remove the header + self._headers.popall(hdrs.CONTENT_LENGTH, None) + + async def _start_compression(self, request: "BaseRequest") -> None: + if self._compression_force: + await self._do_start_compression(self._compression_force) + return + # Encoding comparisons should be case-insensitive + # https://www.rfc-editor.org/rfc/rfc9110#section-8.4.1 + accept_encoding = request.headers.get(hdrs.ACCEPT_ENCODING, "").lower() + for value, coding in CONTENT_CODINGS.items(): + if value in accept_encoding: + await self._do_start_compression(coding) + return + + async def prepare(self, request: "BaseRequest") -> Optional[AbstractStreamWriter]: + if self._eof_sent: + return None + if self._payload_writer is not None: + return self._payload_writer + self._must_be_empty_body = must_be_empty_body(request.method, self.status) + return await self._start(request) + + async def _start(self, request: "BaseRequest") -> AbstractStreamWriter: + self._req = request + writer = self._payload_writer = request._payload_writer + + await self._prepare_headers() + await request._prepare_hook(self) + await self._write_headers() + + return writer + + async def _prepare_headers(self) -> None: + request = self._req + assert request is not None + writer = self._payload_writer + assert writer is not None + keep_alive = self._keep_alive + if keep_alive is None: + keep_alive = request.keep_alive + self._keep_alive = keep_alive + + version = request.version + + headers = self._headers + if self._cookies: + for cookie in self._cookies.values(): + value = cookie.output(header="")[1:] + headers.add(hdrs.SET_COOKIE, value) + + if self._compression: + await self._start_compression(request) + + if self._chunked: + if version != HttpVersion11: + raise RuntimeError( + "Using chunked encoding is forbidden " + "for HTTP/{0.major}.{0.minor}".format(request.version) + ) + if not self._must_be_empty_body: + writer.enable_chunking() + headers[hdrs.TRANSFER_ENCODING] = "chunked" + elif self._length_check: # Disabled for WebSockets + writer.length = self.content_length + if writer.length is None: + if version >= HttpVersion11: + if not self._must_be_empty_body: + writer.enable_chunking() + headers[hdrs.TRANSFER_ENCODING] = "chunked" + elif not self._must_be_empty_body: + keep_alive = False + + # HTTP 1.1: https://tools.ietf.org/html/rfc7230#section-3.3.2 + # HTTP 1.0: https://tools.ietf.org/html/rfc1945#section-10.4 + if self._must_be_empty_body: + if hdrs.CONTENT_LENGTH in headers and should_remove_content_length( + request.method, self.status + ): + del headers[hdrs.CONTENT_LENGTH] + # https://datatracker.ietf.org/doc/html/rfc9112#section-6.1-10 + # https://datatracker.ietf.org/doc/html/rfc9112#section-6.1-13 + if hdrs.TRANSFER_ENCODING in headers: + del headers[hdrs.TRANSFER_ENCODING] + elif (writer.length if self._length_check else self.content_length) != 0: + # https://www.rfc-editor.org/rfc/rfc9110#section-8.3-5 + headers.setdefault(hdrs.CONTENT_TYPE, "application/octet-stream") + headers.setdefault(hdrs.DATE, rfc822_formatted_time()) + headers.setdefault(hdrs.SERVER, SERVER_SOFTWARE) + + # connection header + if hdrs.CONNECTION not in headers: + if keep_alive: + if version == HttpVersion10: + headers[hdrs.CONNECTION] = "keep-alive" + elif version == HttpVersion11: + headers[hdrs.CONNECTION] = "close" + + async def _write_headers(self) -> None: + request = self._req + assert request is not None + writer = self._payload_writer + assert writer is not None + # status line + version = request.version + status_line = f"HTTP/{version[0]}.{version[1]} {self._status} {self._reason}" + await writer.write_headers(status_line, self._headers) + # Send headers immediately if not opted into buffering + if self._send_headers_immediately: + writer.send_headers() + + async def write(self, data: Union[bytes, bytearray, memoryview]) -> None: + assert isinstance( + data, (bytes, bytearray, memoryview) + ), "data argument must be byte-ish (%r)" % type(data) + + if self._eof_sent: + raise RuntimeError("Cannot call write() after write_eof()") + if self._payload_writer is None: + raise RuntimeError("Cannot call write() before prepare()") + + await self._payload_writer.write(data) + + async def drain(self) -> None: + assert not self._eof_sent, "EOF has already been sent" + assert self._payload_writer is not None, "Response has not been started" + warnings.warn( + "drain method is deprecated, use await resp.write()", + DeprecationWarning, + stacklevel=2, + ) + await self._payload_writer.drain() + + async def write_eof(self, data: bytes = b"") -> None: + assert isinstance( + data, (bytes, bytearray, memoryview) + ), "data argument must be byte-ish (%r)" % type(data) + + if self._eof_sent: + return + + assert self._payload_writer is not None, "Response has not been started" + + await self._payload_writer.write_eof(data) + self._eof_sent = True + self._req = None + self._body_length = self._payload_writer.output_size + self._payload_writer = None + + def __repr__(self) -> str: + if self._eof_sent: + info = "eof" + elif self.prepared: + assert self._req is not None + info = f"{self._req.method} {self._req.path} " + else: + info = "not prepared" + return f"<{self.__class__.__name__} {self.reason} {info}>" + + def __getitem__(self, key: str) -> Any: + return self._state[key] + + def __setitem__(self, key: str, value: Any) -> None: + self._state[key] = value + + def __delitem__(self, key: str) -> None: + del self._state[key] + + def __len__(self) -> int: + return len(self._state) + + def __iter__(self) -> Iterator[str]: + return iter(self._state) + + def __hash__(self) -> int: + return hash(id(self)) + + def __eq__(self, other: object) -> bool: + return self is other + + def __bool__(self) -> bool: + return True + + +class Response(StreamResponse): + + _compressed_body: Optional[bytes] = None + _send_headers_immediately = False + + def __init__( + self, + *, + body: Any = None, + status: int = 200, + reason: Optional[str] = None, + text: Optional[str] = None, + headers: Optional[LooseHeaders] = None, + content_type: Optional[str] = None, + charset: Optional[str] = None, + zlib_executor_size: Optional[int] = None, + zlib_executor: Optional[Executor] = None, + ) -> None: + if body is not None and text is not None: + raise ValueError("body and text are not allowed together") + + if headers is None: + real_headers: CIMultiDict[str] = CIMultiDict() + else: + real_headers = CIMultiDict(headers) + + if content_type is not None and "charset" in content_type: + raise ValueError("charset must not be in content_type argument") + + if text is not None: + if hdrs.CONTENT_TYPE in real_headers: + if content_type or charset: + raise ValueError( + "passing both Content-Type header and " + "content_type or charset params " + "is forbidden" + ) + else: + # fast path for filling headers + if not isinstance(text, str): + raise TypeError("text argument must be str (%r)" % type(text)) + if content_type is None: + content_type = "text/plain" + if charset is None: + charset = "utf-8" + real_headers[hdrs.CONTENT_TYPE] = content_type + "; charset=" + charset + body = text.encode(charset) + text = None + elif hdrs.CONTENT_TYPE in real_headers: + if content_type is not None or charset is not None: + raise ValueError( + "passing both Content-Type header and " + "content_type or charset params " + "is forbidden" + ) + elif content_type is not None: + if charset is not None: + content_type += "; charset=" + charset + real_headers[hdrs.CONTENT_TYPE] = content_type + + super().__init__(status=status, reason=reason, _real_headers=real_headers) + + if text is not None: + self.text = text + else: + self.body = body + + self._zlib_executor_size = zlib_executor_size + self._zlib_executor = zlib_executor + + @property + def body(self) -> Optional[Union[bytes, Payload]]: + return self._body + + @body.setter + def body(self, body: Any) -> None: + if body is None: + self._body = None + elif isinstance(body, (bytes, bytearray)): + self._body = body + else: + try: + self._body = body = payload.PAYLOAD_REGISTRY.get(body) + except payload.LookupError: + raise ValueError("Unsupported body type %r" % type(body)) + + headers = self._headers + + # set content-type + if hdrs.CONTENT_TYPE not in headers: + headers[hdrs.CONTENT_TYPE] = body.content_type + + # copy payload headers + if body.headers: + for key, value in body.headers.items(): + if key not in headers: + headers[key] = value + + self._compressed_body = None + + @property + def text(self) -> Optional[str]: + if self._body is None: + return None + # Note: When _body is a Payload (e.g. FilePayload), this may do blocking I/O + # This is generally safe as most common payloads (BytesPayload, StringPayload) + # don't do blocking I/O, but be careful with file-based payloads + return self._body.decode(self.charset or "utf-8") + + @text.setter + def text(self, text: str) -> None: + assert text is None or isinstance( + text, str + ), "text argument must be str (%r)" % type(text) + + if self.content_type == "application/octet-stream": + self.content_type = "text/plain" + if self.charset is None: + self.charset = "utf-8" + + self._body = text.encode(self.charset) + self._compressed_body = None + + @property + def content_length(self) -> Optional[int]: + if self._chunked: + return None + + if hdrs.CONTENT_LENGTH in self._headers: + return int(self._headers[hdrs.CONTENT_LENGTH]) + + if self._compressed_body is not None: + # Return length of the compressed body + return len(self._compressed_body) + elif isinstance(self._body, Payload): + # A payload without content length, or a compressed payload + return None + elif self._body is not None: + return len(self._body) + else: + return 0 + + @content_length.setter + def content_length(self, value: Optional[int]) -> None: + raise RuntimeError("Content length is set automatically") + + async def write_eof(self, data: bytes = b"") -> None: + if self._eof_sent: + return + if self._compressed_body is None: + body: Optional[Union[bytes, Payload]] = self._body + else: + body = self._compressed_body + assert not data, f"data arg is not supported, got {data!r}" + assert self._req is not None + assert self._payload_writer is not None + if body is None or self._must_be_empty_body: + await super().write_eof() + elif isinstance(self._body, Payload): + await self._body.write(self._payload_writer) + await self._body.close() + await super().write_eof() + else: + await super().write_eof(cast(bytes, body)) + + async def _start(self, request: "BaseRequest") -> AbstractStreamWriter: + if hdrs.CONTENT_LENGTH in self._headers: + if should_remove_content_length(request.method, self.status): + del self._headers[hdrs.CONTENT_LENGTH] + elif not self._chunked: + if isinstance(self._body, Payload): + if (size := self._body.size) is not None: + self._headers[hdrs.CONTENT_LENGTH] = str(size) + else: + body_len = len(self._body) if self._body else "0" + # https://www.rfc-editor.org/rfc/rfc9110.html#section-8.6-7 + if body_len != "0" or ( + self.status != 304 and request.method not in hdrs.METH_HEAD_ALL + ): + self._headers[hdrs.CONTENT_LENGTH] = str(body_len) + + return await super()._start(request) + + async def _do_start_compression(self, coding: ContentCoding) -> None: + if self._chunked or isinstance(self._body, Payload): + return await super()._do_start_compression(coding) + if coding is ContentCoding.identity: + return + # Instead of using _payload_writer.enable_compression, + # compress the whole body + compressor = ZLibCompressor( + encoding=coding.value, + max_sync_chunk_size=self._zlib_executor_size, + executor=self._zlib_executor, + ) + assert self._body is not None + if self._zlib_executor_size is None and len(self._body) > LARGE_BODY_SIZE: + warnings.warn( + "Synchronous compression of large response bodies " + f"({len(self._body)} bytes) might block the async event loop. " + "Consider providing a custom value to zlib_executor_size/" + "zlib_executor response properties or disabling compression on it." + ) + self._compressed_body = ( + await compressor.compress(self._body) + compressor.flush() + ) + self._headers[hdrs.CONTENT_ENCODING] = coding.value + self._headers[hdrs.CONTENT_LENGTH] = str(len(self._compressed_body)) + + +def json_response( + data: Any = sentinel, + *, + text: Optional[str] = None, + body: Optional[bytes] = None, + status: int = 200, + reason: Optional[str] = None, + headers: Optional[LooseHeaders] = None, + content_type: str = "application/json", + dumps: JSONEncoder = json.dumps, +) -> Response: + if data is not sentinel: + if text or body: + raise ValueError("only one of data, text, or body should be specified") + else: + text = dumps(data) + return Response( + text=text, + body=body, + status=status, + reason=reason, + headers=headers, + content_type=content_type, + ) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web_routedef.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web_routedef.py new file mode 100644 index 0000000000000000000000000000000000000000..f51b6cd00815a4daeabf7ef269a3225b2b764503 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web_routedef.py @@ -0,0 +1,214 @@ +import abc +import os # noqa +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Dict, + Iterator, + List, + Optional, + Sequence, + Type, + Union, + overload, +) + +import attr + +from . import hdrs +from .abc import AbstractView +from .typedefs import Handler, PathLike + +if TYPE_CHECKING: + from .web_request import Request + from .web_response import StreamResponse + from .web_urldispatcher import AbstractRoute, UrlDispatcher +else: + Request = StreamResponse = UrlDispatcher = AbstractRoute = None + + +__all__ = ( + "AbstractRouteDef", + "RouteDef", + "StaticDef", + "RouteTableDef", + "head", + "options", + "get", + "post", + "patch", + "put", + "delete", + "route", + "view", + "static", +) + + +class AbstractRouteDef(abc.ABC): + @abc.abstractmethod + def register(self, router: UrlDispatcher) -> List[AbstractRoute]: + pass # pragma: no cover + + +_HandlerType = Union[Type[AbstractView], Handler] + + +@attr.s(auto_attribs=True, frozen=True, repr=False, slots=True) +class RouteDef(AbstractRouteDef): + method: str + path: str + handler: _HandlerType + kwargs: Dict[str, Any] + + def __repr__(self) -> str: + info = [] + for name, value in sorted(self.kwargs.items()): + info.append(f", {name}={value!r}") + return " {handler.__name__!r}{info}>".format( + method=self.method, path=self.path, handler=self.handler, info="".join(info) + ) + + def register(self, router: UrlDispatcher) -> List[AbstractRoute]: + if self.method in hdrs.METH_ALL: + reg = getattr(router, "add_" + self.method.lower()) + return [reg(self.path, self.handler, **self.kwargs)] + else: + return [ + router.add_route(self.method, self.path, self.handler, **self.kwargs) + ] + + +@attr.s(auto_attribs=True, frozen=True, repr=False, slots=True) +class StaticDef(AbstractRouteDef): + prefix: str + path: PathLike + kwargs: Dict[str, Any] + + def __repr__(self) -> str: + info = [] + for name, value in sorted(self.kwargs.items()): + info.append(f", {name}={value!r}") + return " {path}{info}>".format( + prefix=self.prefix, path=self.path, info="".join(info) + ) + + def register(self, router: UrlDispatcher) -> List[AbstractRoute]: + resource = router.add_static(self.prefix, self.path, **self.kwargs) + routes = resource.get_info().get("routes", {}) + return list(routes.values()) + + +def route(method: str, path: str, handler: _HandlerType, **kwargs: Any) -> RouteDef: + return RouteDef(method, path, handler, kwargs) + + +def head(path: str, handler: _HandlerType, **kwargs: Any) -> RouteDef: + return route(hdrs.METH_HEAD, path, handler, **kwargs) + + +def options(path: str, handler: _HandlerType, **kwargs: Any) -> RouteDef: + return route(hdrs.METH_OPTIONS, path, handler, **kwargs) + + +def get( + path: str, + handler: _HandlerType, + *, + name: Optional[str] = None, + allow_head: bool = True, + **kwargs: Any, +) -> RouteDef: + return route( + hdrs.METH_GET, path, handler, name=name, allow_head=allow_head, **kwargs + ) + + +def post(path: str, handler: _HandlerType, **kwargs: Any) -> RouteDef: + return route(hdrs.METH_POST, path, handler, **kwargs) + + +def put(path: str, handler: _HandlerType, **kwargs: Any) -> RouteDef: + return route(hdrs.METH_PUT, path, handler, **kwargs) + + +def patch(path: str, handler: _HandlerType, **kwargs: Any) -> RouteDef: + return route(hdrs.METH_PATCH, path, handler, **kwargs) + + +def delete(path: str, handler: _HandlerType, **kwargs: Any) -> RouteDef: + return route(hdrs.METH_DELETE, path, handler, **kwargs) + + +def view(path: str, handler: Type[AbstractView], **kwargs: Any) -> RouteDef: + return route(hdrs.METH_ANY, path, handler, **kwargs) + + +def static(prefix: str, path: PathLike, **kwargs: Any) -> StaticDef: + return StaticDef(prefix, path, kwargs) + + +_Deco = Callable[[_HandlerType], _HandlerType] + + +class RouteTableDef(Sequence[AbstractRouteDef]): + """Route definition table""" + + def __init__(self) -> None: + self._items: List[AbstractRouteDef] = [] + + def __repr__(self) -> str: + return f"" + + @overload + def __getitem__(self, index: int) -> AbstractRouteDef: ... + + @overload + def __getitem__(self, index: slice) -> List[AbstractRouteDef]: ... + + def __getitem__(self, index): # type: ignore[no-untyped-def] + return self._items[index] + + def __iter__(self) -> Iterator[AbstractRouteDef]: + return iter(self._items) + + def __len__(self) -> int: + return len(self._items) + + def __contains__(self, item: object) -> bool: + return item in self._items + + def route(self, method: str, path: str, **kwargs: Any) -> _Deco: + def inner(handler: _HandlerType) -> _HandlerType: + self._items.append(RouteDef(method, path, handler, kwargs)) + return handler + + return inner + + def head(self, path: str, **kwargs: Any) -> _Deco: + return self.route(hdrs.METH_HEAD, path, **kwargs) + + def get(self, path: str, **kwargs: Any) -> _Deco: + return self.route(hdrs.METH_GET, path, **kwargs) + + def post(self, path: str, **kwargs: Any) -> _Deco: + return self.route(hdrs.METH_POST, path, **kwargs) + + def put(self, path: str, **kwargs: Any) -> _Deco: + return self.route(hdrs.METH_PUT, path, **kwargs) + + def patch(self, path: str, **kwargs: Any) -> _Deco: + return self.route(hdrs.METH_PATCH, path, **kwargs) + + def delete(self, path: str, **kwargs: Any) -> _Deco: + return self.route(hdrs.METH_DELETE, path, **kwargs) + + def options(self, path: str, **kwargs: Any) -> _Deco: + return self.route(hdrs.METH_OPTIONS, path, **kwargs) + + def view(self, path: str, **kwargs: Any) -> _Deco: + return self.route(hdrs.METH_ANY, path, **kwargs) + + def static(self, prefix: str, path: PathLike, **kwargs: Any) -> None: + self._items.append(StaticDef(prefix, path, kwargs)) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web_runner.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web_runner.py new file mode 100644 index 0000000000000000000000000000000000000000..bcfec727c8419bbc6518085ecedde1f7de8992c9 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web_runner.py @@ -0,0 +1,399 @@ +import asyncio +import signal +import socket +import warnings +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING, Any, List, Optional, Set + +from yarl import URL + +from .typedefs import PathLike +from .web_app import Application +from .web_server import Server + +if TYPE_CHECKING: + from ssl import SSLContext +else: + try: + from ssl import SSLContext + except ImportError: # pragma: no cover + SSLContext = object # type: ignore[misc,assignment] + +__all__ = ( + "BaseSite", + "TCPSite", + "UnixSite", + "NamedPipeSite", + "SockSite", + "BaseRunner", + "AppRunner", + "ServerRunner", + "GracefulExit", +) + + +class GracefulExit(SystemExit): + code = 1 + + +def _raise_graceful_exit() -> None: + raise GracefulExit() + + +class BaseSite(ABC): + __slots__ = ("_runner", "_ssl_context", "_backlog", "_server") + + def __init__( + self, + runner: "BaseRunner", + *, + shutdown_timeout: float = 60.0, + ssl_context: Optional[SSLContext] = None, + backlog: int = 128, + ) -> None: + if runner.server is None: + raise RuntimeError("Call runner.setup() before making a site") + if shutdown_timeout != 60.0: + msg = "shutdown_timeout should be set on BaseRunner" + warnings.warn(msg, DeprecationWarning, stacklevel=2) + runner._shutdown_timeout = shutdown_timeout + self._runner = runner + self._ssl_context = ssl_context + self._backlog = backlog + self._server: Optional[asyncio.AbstractServer] = None + + @property + @abstractmethod + def name(self) -> str: + pass # pragma: no cover + + @abstractmethod + async def start(self) -> None: + self._runner._reg_site(self) + + async def stop(self) -> None: + self._runner._check_site(self) + if self._server is not None: # Maybe not started yet + self._server.close() + + self._runner._unreg_site(self) + + +class TCPSite(BaseSite): + __slots__ = ("_host", "_port", "_reuse_address", "_reuse_port") + + def __init__( + self, + runner: "BaseRunner", + host: Optional[str] = None, + port: Optional[int] = None, + *, + shutdown_timeout: float = 60.0, + ssl_context: Optional[SSLContext] = None, + backlog: int = 128, + reuse_address: Optional[bool] = None, + reuse_port: Optional[bool] = None, + ) -> None: + super().__init__( + runner, + shutdown_timeout=shutdown_timeout, + ssl_context=ssl_context, + backlog=backlog, + ) + self._host = host + if port is None: + port = 8443 if self._ssl_context else 8080 + self._port = port + self._reuse_address = reuse_address + self._reuse_port = reuse_port + + @property + def name(self) -> str: + scheme = "https" if self._ssl_context else "http" + host = "0.0.0.0" if not self._host else self._host + return str(URL.build(scheme=scheme, host=host, port=self._port)) + + async def start(self) -> None: + await super().start() + loop = asyncio.get_event_loop() + server = self._runner.server + assert server is not None + self._server = await loop.create_server( + server, + self._host, + self._port, + ssl=self._ssl_context, + backlog=self._backlog, + reuse_address=self._reuse_address, + reuse_port=self._reuse_port, + ) + + +class UnixSite(BaseSite): + __slots__ = ("_path",) + + def __init__( + self, + runner: "BaseRunner", + path: PathLike, + *, + shutdown_timeout: float = 60.0, + ssl_context: Optional[SSLContext] = None, + backlog: int = 128, + ) -> None: + super().__init__( + runner, + shutdown_timeout=shutdown_timeout, + ssl_context=ssl_context, + backlog=backlog, + ) + self._path = path + + @property + def name(self) -> str: + scheme = "https" if self._ssl_context else "http" + return f"{scheme}://unix:{self._path}:" + + async def start(self) -> None: + await super().start() + loop = asyncio.get_event_loop() + server = self._runner.server + assert server is not None + self._server = await loop.create_unix_server( + server, + self._path, + ssl=self._ssl_context, + backlog=self._backlog, + ) + + +class NamedPipeSite(BaseSite): + __slots__ = ("_path",) + + def __init__( + self, runner: "BaseRunner", path: str, *, shutdown_timeout: float = 60.0 + ) -> None: + loop = asyncio.get_event_loop() + if not isinstance( + loop, asyncio.ProactorEventLoop # type: ignore[attr-defined] + ): + raise RuntimeError( + "Named Pipes only available in proactor loop under windows" + ) + super().__init__(runner, shutdown_timeout=shutdown_timeout) + self._path = path + + @property + def name(self) -> str: + return self._path + + async def start(self) -> None: + await super().start() + loop = asyncio.get_event_loop() + server = self._runner.server + assert server is not None + _server = await loop.start_serving_pipe( # type: ignore[attr-defined] + server, self._path + ) + self._server = _server[0] + + +class SockSite(BaseSite): + __slots__ = ("_sock", "_name") + + def __init__( + self, + runner: "BaseRunner", + sock: socket.socket, + *, + shutdown_timeout: float = 60.0, + ssl_context: Optional[SSLContext] = None, + backlog: int = 128, + ) -> None: + super().__init__( + runner, + shutdown_timeout=shutdown_timeout, + ssl_context=ssl_context, + backlog=backlog, + ) + self._sock = sock + scheme = "https" if self._ssl_context else "http" + if hasattr(socket, "AF_UNIX") and sock.family == socket.AF_UNIX: + name = f"{scheme}://unix:{sock.getsockname()}:" + else: + host, port = sock.getsockname()[:2] + name = str(URL.build(scheme=scheme, host=host, port=port)) + self._name = name + + @property + def name(self) -> str: + return self._name + + async def start(self) -> None: + await super().start() + loop = asyncio.get_event_loop() + server = self._runner.server + assert server is not None + self._server = await loop.create_server( + server, sock=self._sock, ssl=self._ssl_context, backlog=self._backlog + ) + + +class BaseRunner(ABC): + __slots__ = ("_handle_signals", "_kwargs", "_server", "_sites", "_shutdown_timeout") + + def __init__( + self, + *, + handle_signals: bool = False, + shutdown_timeout: float = 60.0, + **kwargs: Any, + ) -> None: + self._handle_signals = handle_signals + self._kwargs = kwargs + self._server: Optional[Server] = None + self._sites: List[BaseSite] = [] + self._shutdown_timeout = shutdown_timeout + + @property + def server(self) -> Optional[Server]: + return self._server + + @property + def addresses(self) -> List[Any]: + ret: List[Any] = [] + for site in self._sites: + server = site._server + if server is not None: + sockets = server.sockets # type: ignore[attr-defined] + if sockets is not None: + for sock in sockets: + ret.append(sock.getsockname()) + return ret + + @property + def sites(self) -> Set[BaseSite]: + return set(self._sites) + + async def setup(self) -> None: + loop = asyncio.get_event_loop() + + if self._handle_signals: + try: + loop.add_signal_handler(signal.SIGINT, _raise_graceful_exit) + loop.add_signal_handler(signal.SIGTERM, _raise_graceful_exit) + except NotImplementedError: # pragma: no cover + # add_signal_handler is not implemented on Windows + pass + + self._server = await self._make_server() + + @abstractmethod + async def shutdown(self) -> None: + """Call any shutdown hooks to help server close gracefully.""" + + async def cleanup(self) -> None: + # The loop over sites is intentional, an exception on gather() + # leaves self._sites in unpredictable state. + # The loop guaranties that a site is either deleted on success or + # still present on failure + for site in list(self._sites): + await site.stop() + + if self._server: # If setup succeeded + # Yield to event loop to ensure incoming requests prior to stopping the sites + # have all started to be handled before we proceed to close idle connections. + await asyncio.sleep(0) + self._server.pre_shutdown() + await self.shutdown() + await self._server.shutdown(self._shutdown_timeout) + await self._cleanup_server() + + self._server = None + if self._handle_signals: + loop = asyncio.get_running_loop() + try: + loop.remove_signal_handler(signal.SIGINT) + loop.remove_signal_handler(signal.SIGTERM) + except NotImplementedError: # pragma: no cover + # remove_signal_handler is not implemented on Windows + pass + + @abstractmethod + async def _make_server(self) -> Server: + pass # pragma: no cover + + @abstractmethod + async def _cleanup_server(self) -> None: + pass # pragma: no cover + + def _reg_site(self, site: BaseSite) -> None: + if site in self._sites: + raise RuntimeError(f"Site {site} is already registered in runner {self}") + self._sites.append(site) + + def _check_site(self, site: BaseSite) -> None: + if site not in self._sites: + raise RuntimeError(f"Site {site} is not registered in runner {self}") + + def _unreg_site(self, site: BaseSite) -> None: + if site not in self._sites: + raise RuntimeError(f"Site {site} is not registered in runner {self}") + self._sites.remove(site) + + +class ServerRunner(BaseRunner): + """Low-level web server runner""" + + __slots__ = ("_web_server",) + + def __init__( + self, web_server: Server, *, handle_signals: bool = False, **kwargs: Any + ) -> None: + super().__init__(handle_signals=handle_signals, **kwargs) + self._web_server = web_server + + async def shutdown(self) -> None: + pass + + async def _make_server(self) -> Server: + return self._web_server + + async def _cleanup_server(self) -> None: + pass + + +class AppRunner(BaseRunner): + """Web Application runner""" + + __slots__ = ("_app",) + + def __init__( + self, app: Application, *, handle_signals: bool = False, **kwargs: Any + ) -> None: + super().__init__(handle_signals=handle_signals, **kwargs) + if not isinstance(app, Application): + raise TypeError( + "The first argument should be web.Application " + "instance, got {!r}".format(app) + ) + self._app = app + + @property + def app(self) -> Application: + return self._app + + async def shutdown(self) -> None: + await self._app.shutdown() + + async def _make_server(self) -> Server: + loop = asyncio.get_event_loop() + self._app._set_loop(loop) + self._app.on_startup.freeze() + await self._app.startup() + self._app.freeze() + + return self._app._make_handler(loop=loop, **self._kwargs) + + async def _cleanup_server(self) -> None: + await self._app.cleanup() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web_server.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web_server.py new file mode 100644 index 0000000000000000000000000000000000000000..328aca1e405ef87e4df8a992c32eac092b4af8f0 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web_server.py @@ -0,0 +1,84 @@ +"""Low level HTTP server.""" + +import asyncio +from typing import Any, Awaitable, Callable, Dict, List, Optional # noqa + +from .abc import AbstractStreamWriter +from .http_parser import RawRequestMessage +from .streams import StreamReader +from .web_protocol import RequestHandler, _RequestFactory, _RequestHandler +from .web_request import BaseRequest + +__all__ = ("Server",) + + +class Server: + def __init__( + self, + handler: _RequestHandler, + *, + request_factory: Optional[_RequestFactory] = None, + handler_cancellation: bool = False, + loop: Optional[asyncio.AbstractEventLoop] = None, + **kwargs: Any, + ) -> None: + self._loop = loop or asyncio.get_running_loop() + self._connections: Dict[RequestHandler, asyncio.Transport] = {} + self._kwargs = kwargs + # requests_count is the number of requests being processed by the server + # for the lifetime of the server. + self.requests_count = 0 + self.request_handler = handler + self.request_factory = request_factory or self._make_request + self.handler_cancellation = handler_cancellation + + @property + def connections(self) -> List[RequestHandler]: + return list(self._connections.keys()) + + def connection_made( + self, handler: RequestHandler, transport: asyncio.Transport + ) -> None: + self._connections[handler] = transport + + def connection_lost( + self, handler: RequestHandler, exc: Optional[BaseException] = None + ) -> None: + if handler in self._connections: + if handler._task_handler: + handler._task_handler.add_done_callback( + lambda f: self._connections.pop(handler, None) + ) + else: + del self._connections[handler] + + def _make_request( + self, + message: RawRequestMessage, + payload: StreamReader, + protocol: RequestHandler, + writer: AbstractStreamWriter, + task: "asyncio.Task[None]", + ) -> BaseRequest: + return BaseRequest(message, payload, protocol, writer, task, self._loop) + + def pre_shutdown(self) -> None: + for conn in self._connections: + conn.close() + + async def shutdown(self, timeout: Optional[float] = None) -> None: + coros = (conn.shutdown(timeout) for conn in self._connections) + await asyncio.gather(*coros) + self._connections.clear() + + def __call__(self) -> RequestHandler: + try: + return RequestHandler(self, loop=self._loop, **self._kwargs) + except TypeError: + # Failsafe creation: remove all custom handler_args + kwargs = { + k: v + for k, v in self._kwargs.items() + if k in ["debug", "access_log_class"] + } + return RequestHandler(self, loop=self._loop, **kwargs) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web_urldispatcher.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web_urldispatcher.py new file mode 100644 index 0000000000000000000000000000000000000000..48465b5ee21157b570a8ad2727733d01fc73ccfc --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web_urldispatcher.py @@ -0,0 +1,1309 @@ +import abc +import asyncio +import base64 +import functools +import hashlib +import html +import inspect +import keyword +import os +import platform +import re +import sys +import warnings +from functools import wraps +from pathlib import Path +from types import MappingProxyType +from typing import ( + TYPE_CHECKING, + Any, + Awaitable, + Callable, + Container, + Dict, + Final, + Generator, + Iterable, + Iterator, + List, + Mapping, + NoReturn, + Optional, + Pattern, + Set, + Sized, + Tuple, + Type, + TypedDict, + Union, + cast, +) + +from yarl import URL, __version__ as yarl_version + +from . import hdrs +from .abc import AbstractMatchInfo, AbstractRouter, AbstractView +from .helpers import DEBUG +from .http import HttpVersion11 +from .typedefs import Handler, PathLike +from .web_exceptions import ( + HTTPException, + HTTPExpectationFailed, + HTTPForbidden, + HTTPMethodNotAllowed, + HTTPNotFound, +) +from .web_fileresponse import FileResponse +from .web_request import Request +from .web_response import Response, StreamResponse +from .web_routedef import AbstractRouteDef + +__all__ = ( + "UrlDispatcher", + "UrlMappingMatchInfo", + "AbstractResource", + "Resource", + "PlainResource", + "DynamicResource", + "AbstractRoute", + "ResourceRoute", + "StaticResource", + "View", +) + + +if TYPE_CHECKING: + from .web_app import Application + + BaseDict = Dict[str, str] +else: + BaseDict = dict + +CIRCULAR_SYMLINK_ERROR = ( + (OSError,) + if sys.version_info < (3, 10) and sys.platform.startswith("win32") + else (RuntimeError,) if sys.version_info < (3, 13) else () +) + +YARL_VERSION: Final[Tuple[int, ...]] = tuple(map(int, yarl_version.split(".")[:2])) + +HTTP_METHOD_RE: Final[Pattern[str]] = re.compile( + r"^[0-9A-Za-z!#\$%&'\*\+\-\.\^_`\|~]+$" +) +ROUTE_RE: Final[Pattern[str]] = re.compile( + r"(\{[_a-zA-Z][^{}]*(?:\{[^{}]*\}[^{}]*)*\})" +) +PATH_SEP: Final[str] = re.escape("/") + +IS_WINDOWS: Final[bool] = platform.system() == "Windows" + +_ExpectHandler = Callable[[Request], Awaitable[Optional[StreamResponse]]] +_Resolve = Tuple[Optional["UrlMappingMatchInfo"], Set[str]] + +html_escape = functools.partial(html.escape, quote=True) + + +class _InfoDict(TypedDict, total=False): + path: str + + formatter: str + pattern: Pattern[str] + + directory: Path + prefix: str + routes: Mapping[str, "AbstractRoute"] + + app: "Application" + + domain: str + + rule: "AbstractRuleMatching" + + http_exception: HTTPException + + +class AbstractResource(Sized, Iterable["AbstractRoute"]): + def __init__(self, *, name: Optional[str] = None) -> None: + self._name = name + + @property + def name(self) -> Optional[str]: + return self._name + + @property + @abc.abstractmethod + def canonical(self) -> str: + """Exposes the resource's canonical path. + + For example '/foo/bar/{name}' + + """ + + @abc.abstractmethod # pragma: no branch + def url_for(self, **kwargs: str) -> URL: + """Construct url for resource with additional params.""" + + @abc.abstractmethod # pragma: no branch + async def resolve(self, request: Request) -> _Resolve: + """Resolve resource. + + Return (UrlMappingMatchInfo, allowed_methods) pair. + """ + + @abc.abstractmethod + def add_prefix(self, prefix: str) -> None: + """Add a prefix to processed URLs. + + Required for subapplications support. + """ + + @abc.abstractmethod + def get_info(self) -> _InfoDict: + """Return a dict with additional info useful for introspection""" + + def freeze(self) -> None: + pass + + @abc.abstractmethod + def raw_match(self, path: str) -> bool: + """Perform a raw match against path""" + + +class AbstractRoute(abc.ABC): + def __init__( + self, + method: str, + handler: Union[Handler, Type[AbstractView]], + *, + expect_handler: Optional[_ExpectHandler] = None, + resource: Optional[AbstractResource] = None, + ) -> None: + + if expect_handler is None: + expect_handler = _default_expect_handler + + assert inspect.iscoroutinefunction(expect_handler) or ( + sys.version_info < (3, 14) and asyncio.iscoroutinefunction(expect_handler) + ), f"Coroutine is expected, got {expect_handler!r}" + + method = method.upper() + if not HTTP_METHOD_RE.match(method): + raise ValueError(f"{method} is not allowed HTTP method") + + assert callable(handler), handler + if inspect.iscoroutinefunction(handler) or ( + sys.version_info < (3, 14) and asyncio.iscoroutinefunction(handler) + ): + pass + elif inspect.isgeneratorfunction(handler): + if TYPE_CHECKING: + assert False + warnings.warn( + "Bare generators are deprecated, use @coroutine wrapper", + DeprecationWarning, + ) + elif isinstance(handler, type) and issubclass(handler, AbstractView): + pass + else: + warnings.warn( + "Bare functions are deprecated, use async ones", DeprecationWarning + ) + + @wraps(handler) + async def handler_wrapper(request: Request) -> StreamResponse: + result = old_handler(request) # type: ignore[call-arg] + if asyncio.iscoroutine(result): + result = await result + assert isinstance(result, StreamResponse) + return result + + old_handler = handler + handler = handler_wrapper + + self._method = method + self._handler = handler + self._expect_handler = expect_handler + self._resource = resource + + @property + def method(self) -> str: + return self._method + + @property + def handler(self) -> Handler: + return self._handler + + @property + @abc.abstractmethod + def name(self) -> Optional[str]: + """Optional route's name, always equals to resource's name.""" + + @property + def resource(self) -> Optional[AbstractResource]: + return self._resource + + @abc.abstractmethod + def get_info(self) -> _InfoDict: + """Return a dict with additional info useful for introspection""" + + @abc.abstractmethod # pragma: no branch + def url_for(self, *args: str, **kwargs: str) -> URL: + """Construct url for route with additional params.""" + + async def handle_expect_header(self, request: Request) -> Optional[StreamResponse]: + return await self._expect_handler(request) + + +class UrlMappingMatchInfo(BaseDict, AbstractMatchInfo): + + __slots__ = ("_route", "_apps", "_current_app", "_frozen") + + def __init__(self, match_dict: Dict[str, str], route: AbstractRoute) -> None: + super().__init__(match_dict) + self._route = route + self._apps: List[Application] = [] + self._current_app: Optional[Application] = None + self._frozen = False + + @property + def handler(self) -> Handler: + return self._route.handler + + @property + def route(self) -> AbstractRoute: + return self._route + + @property + def expect_handler(self) -> _ExpectHandler: + return self._route.handle_expect_header + + @property + def http_exception(self) -> Optional[HTTPException]: + return None + + def get_info(self) -> _InfoDict: # type: ignore[override] + return self._route.get_info() + + @property + def apps(self) -> Tuple["Application", ...]: + return tuple(self._apps) + + def add_app(self, app: "Application") -> None: + if self._frozen: + raise RuntimeError("Cannot change apps stack after .freeze() call") + if self._current_app is None: + self._current_app = app + self._apps.insert(0, app) + + @property + def current_app(self) -> "Application": + app = self._current_app + assert app is not None + return app + + @current_app.setter + def current_app(self, app: "Application") -> None: + if DEBUG: # pragma: no cover + if app not in self._apps: + raise RuntimeError( + "Expected one of the following apps {!r}, got {!r}".format( + self._apps, app + ) + ) + self._current_app = app + + def freeze(self) -> None: + self._frozen = True + + def __repr__(self) -> str: + return f"" + + +class MatchInfoError(UrlMappingMatchInfo): + + __slots__ = ("_exception",) + + def __init__(self, http_exception: HTTPException) -> None: + self._exception = http_exception + super().__init__({}, SystemRoute(self._exception)) + + @property + def http_exception(self) -> HTTPException: + return self._exception + + def __repr__(self) -> str: + return "".format( + self._exception.status, self._exception.reason + ) + + +async def _default_expect_handler(request: Request) -> None: + """Default handler for Expect header. + + Just send "100 Continue" to client. + raise HTTPExpectationFailed if value of header is not "100-continue" + """ + expect = request.headers.get(hdrs.EXPECT, "") + if request.version == HttpVersion11: + if expect.lower() == "100-continue": + await request.writer.write(b"HTTP/1.1 100 Continue\r\n\r\n") + # Reset output_size as we haven't started the main body yet. + request.writer.output_size = 0 + else: + raise HTTPExpectationFailed(text="Unknown Expect: %s" % expect) + + +class Resource(AbstractResource): + def __init__(self, *, name: Optional[str] = None) -> None: + super().__init__(name=name) + self._routes: Dict[str, ResourceRoute] = {} + self._any_route: Optional[ResourceRoute] = None + self._allowed_methods: Set[str] = set() + + def add_route( + self, + method: str, + handler: Union[Type[AbstractView], Handler], + *, + expect_handler: Optional[_ExpectHandler] = None, + ) -> "ResourceRoute": + if route := self._routes.get(method, self._any_route): + raise RuntimeError( + "Added route will never be executed, " + f"method {route.method} is already " + "registered" + ) + + route_obj = ResourceRoute(method, handler, self, expect_handler=expect_handler) + self.register_route(route_obj) + return route_obj + + def register_route(self, route: "ResourceRoute") -> None: + assert isinstance( + route, ResourceRoute + ), f"Instance of Route class is required, got {route!r}" + if route.method == hdrs.METH_ANY: + self._any_route = route + self._allowed_methods.add(route.method) + self._routes[route.method] = route + + async def resolve(self, request: Request) -> _Resolve: + if (match_dict := self._match(request.rel_url.path_safe)) is None: + return None, set() + if route := self._routes.get(request.method, self._any_route): + return UrlMappingMatchInfo(match_dict, route), self._allowed_methods + return None, self._allowed_methods + + @abc.abstractmethod + def _match(self, path: str) -> Optional[Dict[str, str]]: + pass # pragma: no cover + + def __len__(self) -> int: + return len(self._routes) + + def __iter__(self) -> Iterator["ResourceRoute"]: + return iter(self._routes.values()) + + # TODO: implement all abstract methods + + +class PlainResource(Resource): + def __init__(self, path: str, *, name: Optional[str] = None) -> None: + super().__init__(name=name) + assert not path or path.startswith("/") + self._path = path + + @property + def canonical(self) -> str: + return self._path + + def freeze(self) -> None: + if not self._path: + self._path = "/" + + def add_prefix(self, prefix: str) -> None: + assert prefix.startswith("/") + assert not prefix.endswith("/") + assert len(prefix) > 1 + self._path = prefix + self._path + + def _match(self, path: str) -> Optional[Dict[str, str]]: + # string comparison is about 10 times faster than regexp matching + if self._path == path: + return {} + return None + + def raw_match(self, path: str) -> bool: + return self._path == path + + def get_info(self) -> _InfoDict: + return {"path": self._path} + + def url_for(self) -> URL: # type: ignore[override] + return URL.build(path=self._path, encoded=True) + + def __repr__(self) -> str: + name = "'" + self.name + "' " if self.name is not None else "" + return f"" + + +class DynamicResource(Resource): + + DYN = re.compile(r"\{(?P[_a-zA-Z][_a-zA-Z0-9]*)\}") + DYN_WITH_RE = re.compile(r"\{(?P[_a-zA-Z][_a-zA-Z0-9]*):(?P.+)\}") + GOOD = r"[^{}/]+" + + def __init__(self, path: str, *, name: Optional[str] = None) -> None: + super().__init__(name=name) + self._orig_path = path + pattern = "" + formatter = "" + for part in ROUTE_RE.split(path): + match = self.DYN.fullmatch(part) + if match: + pattern += "(?P<{}>{})".format(match.group("var"), self.GOOD) + formatter += "{" + match.group("var") + "}" + continue + + match = self.DYN_WITH_RE.fullmatch(part) + if match: + pattern += "(?P<{var}>{re})".format(**match.groupdict()) + formatter += "{" + match.group("var") + "}" + continue + + if "{" in part or "}" in part: + raise ValueError(f"Invalid path '{path}'['{part}']") + + part = _requote_path(part) + formatter += part + pattern += re.escape(part) + + try: + compiled = re.compile(pattern) + except re.error as exc: + raise ValueError(f"Bad pattern '{pattern}': {exc}") from None + assert compiled.pattern.startswith(PATH_SEP) + assert formatter.startswith("/") + self._pattern = compiled + self._formatter = formatter + + @property + def canonical(self) -> str: + return self._formatter + + def add_prefix(self, prefix: str) -> None: + assert prefix.startswith("/") + assert not prefix.endswith("/") + assert len(prefix) > 1 + self._pattern = re.compile(re.escape(prefix) + self._pattern.pattern) + self._formatter = prefix + self._formatter + + def _match(self, path: str) -> Optional[Dict[str, str]]: + match = self._pattern.fullmatch(path) + if match is None: + return None + return { + key: _unquote_path_safe(value) for key, value in match.groupdict().items() + } + + def raw_match(self, path: str) -> bool: + return self._orig_path == path + + def get_info(self) -> _InfoDict: + return {"formatter": self._formatter, "pattern": self._pattern} + + def url_for(self, **parts: str) -> URL: + url = self._formatter.format_map({k: _quote_path(v) for k, v in parts.items()}) + return URL.build(path=url, encoded=True) + + def __repr__(self) -> str: + name = "'" + self.name + "' " if self.name is not None else "" + return "".format( + name=name, formatter=self._formatter + ) + + +class PrefixResource(AbstractResource): + def __init__(self, prefix: str, *, name: Optional[str] = None) -> None: + assert not prefix or prefix.startswith("/"), prefix + assert prefix in ("", "/") or not prefix.endswith("/"), prefix + super().__init__(name=name) + self._prefix = _requote_path(prefix) + self._prefix2 = self._prefix + "/" + + @property + def canonical(self) -> str: + return self._prefix + + def add_prefix(self, prefix: str) -> None: + assert prefix.startswith("/") + assert not prefix.endswith("/") + assert len(prefix) > 1 + self._prefix = prefix + self._prefix + self._prefix2 = self._prefix + "/" + + def raw_match(self, prefix: str) -> bool: + return False + + # TODO: impl missing abstract methods + + +class StaticResource(PrefixResource): + VERSION_KEY = "v" + + def __init__( + self, + prefix: str, + directory: PathLike, + *, + name: Optional[str] = None, + expect_handler: Optional[_ExpectHandler] = None, + chunk_size: int = 256 * 1024, + show_index: bool = False, + follow_symlinks: bool = False, + append_version: bool = False, + ) -> None: + super().__init__(prefix, name=name) + try: + directory = Path(directory).expanduser().resolve(strict=True) + except FileNotFoundError as error: + raise ValueError(f"'{directory}' does not exist") from error + if not directory.is_dir(): + raise ValueError(f"'{directory}' is not a directory") + self._directory = directory + self._show_index = show_index + self._chunk_size = chunk_size + self._follow_symlinks = follow_symlinks + self._expect_handler = expect_handler + self._append_version = append_version + + self._routes = { + "GET": ResourceRoute( + "GET", self._handle, self, expect_handler=expect_handler + ), + "HEAD": ResourceRoute( + "HEAD", self._handle, self, expect_handler=expect_handler + ), + } + self._allowed_methods = set(self._routes) + + def url_for( # type: ignore[override] + self, + *, + filename: PathLike, + append_version: Optional[bool] = None, + ) -> URL: + if append_version is None: + append_version = self._append_version + filename = str(filename).lstrip("/") + + url = URL.build(path=self._prefix, encoded=True) + # filename is not encoded + if YARL_VERSION < (1, 6): + url = url / filename.replace("%", "%25") + else: + url = url / filename + + if append_version: + unresolved_path = self._directory.joinpath(filename) + try: + if self._follow_symlinks: + normalized_path = Path(os.path.normpath(unresolved_path)) + normalized_path.relative_to(self._directory) + filepath = normalized_path.resolve() + else: + filepath = unresolved_path.resolve() + filepath.relative_to(self._directory) + except (ValueError, FileNotFoundError): + # ValueError for case when path point to symlink + # with follow_symlinks is False + return url # relatively safe + if filepath.is_file(): + # TODO cache file content + # with file watcher for cache invalidation + with filepath.open("rb") as f: + file_bytes = f.read() + h = self._get_file_hash(file_bytes) + url = url.with_query({self.VERSION_KEY: h}) + return url + return url + + @staticmethod + def _get_file_hash(byte_array: bytes) -> str: + m = hashlib.sha256() # todo sha256 can be configurable param + m.update(byte_array) + b64 = base64.urlsafe_b64encode(m.digest()) + return b64.decode("ascii") + + def get_info(self) -> _InfoDict: + return { + "directory": self._directory, + "prefix": self._prefix, + "routes": self._routes, + } + + def set_options_route(self, handler: Handler) -> None: + if "OPTIONS" in self._routes: + raise RuntimeError("OPTIONS route was set already") + self._routes["OPTIONS"] = ResourceRoute( + "OPTIONS", handler, self, expect_handler=self._expect_handler + ) + self._allowed_methods.add("OPTIONS") + + async def resolve(self, request: Request) -> _Resolve: + path = request.rel_url.path_safe + method = request.method + # We normalise here to avoid matches that traverse below the static root. + # e.g. /static/../../../../home/user/webapp/static/ + norm_path = os.path.normpath(path) + if IS_WINDOWS: + norm_path = norm_path.replace("\\", "/") + if not norm_path.startswith(self._prefix2) and norm_path != self._prefix: + return None, set() + + allowed_methods = self._allowed_methods + if method not in allowed_methods: + return None, allowed_methods + + match_dict = {"filename": _unquote_path_safe(path[len(self._prefix) + 1 :])} + return (UrlMappingMatchInfo(match_dict, self._routes[method]), allowed_methods) + + def __len__(self) -> int: + return len(self._routes) + + def __iter__(self) -> Iterator[AbstractRoute]: + return iter(self._routes.values()) + + async def _handle(self, request: Request) -> StreamResponse: + filename = request.match_info["filename"] + if Path(filename).is_absolute(): + # filename is an absolute path e.g. //network/share or D:\path + # which could be a UNC path leading to NTLM credential theft + raise HTTPNotFound() + unresolved_path = self._directory.joinpath(filename) + loop = asyncio.get_running_loop() + return await loop.run_in_executor( + None, self._resolve_path_to_response, unresolved_path + ) + + def _resolve_path_to_response(self, unresolved_path: Path) -> StreamResponse: + """Take the unresolved path and query the file system to form a response.""" + # Check for access outside the root directory. For follow symlinks, URI + # cannot traverse out, but symlinks can. Otherwise, no access outside + # root is permitted. + try: + if self._follow_symlinks: + normalized_path = Path(os.path.normpath(unresolved_path)) + normalized_path.relative_to(self._directory) + file_path = normalized_path.resolve() + else: + file_path = unresolved_path.resolve() + file_path.relative_to(self._directory) + except (ValueError, *CIRCULAR_SYMLINK_ERROR) as error: + # ValueError is raised for the relative check. Circular symlinks + # raise here on resolving for python < 3.13. + raise HTTPNotFound() from error + + # if path is a directory, return the contents if permitted. Note the + # directory check will raise if a segment is not readable. + try: + if file_path.is_dir(): + if self._show_index: + return Response( + text=self._directory_as_html(file_path), + content_type="text/html", + ) + else: + raise HTTPForbidden() + except PermissionError as error: + raise HTTPForbidden() from error + + # Return the file response, which handles all other checks. + return FileResponse(file_path, chunk_size=self._chunk_size) + + def _directory_as_html(self, dir_path: Path) -> str: + """returns directory's index as html.""" + assert dir_path.is_dir() + + relative_path_to_dir = dir_path.relative_to(self._directory).as_posix() + index_of = f"Index of /{html_escape(relative_path_to_dir)}" + h1 = f"

    {index_of}

    " + + index_list = [] + dir_index = dir_path.iterdir() + for _file in sorted(dir_index): + # show file url as relative to static path + rel_path = _file.relative_to(self._directory).as_posix() + quoted_file_url = _quote_path(f"{self._prefix}/{rel_path}") + + # if file is a directory, add '/' to the end of the name + if _file.is_dir(): + file_name = f"{_file.name}/" + else: + file_name = _file.name + + index_list.append( + f'
  • {html_escape(file_name)}
  • ' + ) + ul = "
      \n{}\n
    ".format("\n".join(index_list)) + body = f"\n{h1}\n{ul}\n" + + head_str = f"\n{index_of}\n" + html = f"\n{head_str}\n{body}\n" + + return html + + def __repr__(self) -> str: + name = "'" + self.name + "'" if self.name is not None else "" + return " {directory!r}>".format( + name=name, path=self._prefix, directory=self._directory + ) + + +class PrefixedSubAppResource(PrefixResource): + def __init__(self, prefix: str, app: "Application") -> None: + super().__init__(prefix) + self._app = app + self._add_prefix_to_resources(prefix) + + def add_prefix(self, prefix: str) -> None: + super().add_prefix(prefix) + self._add_prefix_to_resources(prefix) + + def _add_prefix_to_resources(self, prefix: str) -> None: + router = self._app.router + for resource in router.resources(): + # Since the canonical path of a resource is about + # to change, we need to unindex it and then reindex + router.unindex_resource(resource) + resource.add_prefix(prefix) + router.index_resource(resource) + + def url_for(self, *args: str, **kwargs: str) -> URL: + raise RuntimeError(".url_for() is not supported by sub-application root") + + def get_info(self) -> _InfoDict: + return {"app": self._app, "prefix": self._prefix} + + async def resolve(self, request: Request) -> _Resolve: + match_info = await self._app.router.resolve(request) + match_info.add_app(self._app) + if isinstance(match_info.http_exception, HTTPMethodNotAllowed): + methods = match_info.http_exception.allowed_methods + else: + methods = set() + return match_info, methods + + def __len__(self) -> int: + return len(self._app.router.routes()) + + def __iter__(self) -> Iterator[AbstractRoute]: + return iter(self._app.router.routes()) + + def __repr__(self) -> str: + return " {app!r}>".format( + prefix=self._prefix, app=self._app + ) + + +class AbstractRuleMatching(abc.ABC): + @abc.abstractmethod # pragma: no branch + async def match(self, request: Request) -> bool: + """Return bool if the request satisfies the criteria""" + + @abc.abstractmethod # pragma: no branch + def get_info(self) -> _InfoDict: + """Return a dict with additional info useful for introspection""" + + @property + @abc.abstractmethod # pragma: no branch + def canonical(self) -> str: + """Return a str""" + + +class Domain(AbstractRuleMatching): + re_part = re.compile(r"(?!-)[a-z\d-]{1,63}(? None: + super().__init__() + self._domain = self.validation(domain) + + @property + def canonical(self) -> str: + return self._domain + + def validation(self, domain: str) -> str: + if not isinstance(domain, str): + raise TypeError("Domain must be str") + domain = domain.rstrip(".").lower() + if not domain: + raise ValueError("Domain cannot be empty") + elif "://" in domain: + raise ValueError("Scheme not supported") + url = URL("http://" + domain) + assert url.raw_host is not None + if not all(self.re_part.fullmatch(x) for x in url.raw_host.split(".")): + raise ValueError("Domain not valid") + if url.port == 80: + return url.raw_host + return f"{url.raw_host}:{url.port}" + + async def match(self, request: Request) -> bool: + host = request.headers.get(hdrs.HOST) + if not host: + return False + return self.match_domain(host) + + def match_domain(self, host: str) -> bool: + return host.lower() == self._domain + + def get_info(self) -> _InfoDict: + return {"domain": self._domain} + + +class MaskDomain(Domain): + re_part = re.compile(r"(?!-)[a-z\d\*-]{1,63}(? None: + super().__init__(domain) + mask = self._domain.replace(".", r"\.").replace("*", ".*") + self._mask = re.compile(mask) + + @property + def canonical(self) -> str: + return self._mask.pattern + + def match_domain(self, host: str) -> bool: + return self._mask.fullmatch(host) is not None + + +class MatchedSubAppResource(PrefixedSubAppResource): + def __init__(self, rule: AbstractRuleMatching, app: "Application") -> None: + AbstractResource.__init__(self) + self._prefix = "" + self._app = app + self._rule = rule + + @property + def canonical(self) -> str: + return self._rule.canonical + + def get_info(self) -> _InfoDict: + return {"app": self._app, "rule": self._rule} + + async def resolve(self, request: Request) -> _Resolve: + if not await self._rule.match(request): + return None, set() + match_info = await self._app.router.resolve(request) + match_info.add_app(self._app) + if isinstance(match_info.http_exception, HTTPMethodNotAllowed): + methods = match_info.http_exception.allowed_methods + else: + methods = set() + return match_info, methods + + def __repr__(self) -> str: + return f" {self._app!r}>" + + +class ResourceRoute(AbstractRoute): + """A route with resource""" + + def __init__( + self, + method: str, + handler: Union[Handler, Type[AbstractView]], + resource: AbstractResource, + *, + expect_handler: Optional[_ExpectHandler] = None, + ) -> None: + super().__init__( + method, handler, expect_handler=expect_handler, resource=resource + ) + + def __repr__(self) -> str: + return " {handler!r}".format( + method=self.method, resource=self._resource, handler=self.handler + ) + + @property + def name(self) -> Optional[str]: + if self._resource is None: + return None + return self._resource.name + + def url_for(self, *args: str, **kwargs: str) -> URL: + """Construct url for route with additional params.""" + assert self._resource is not None + return self._resource.url_for(*args, **kwargs) + + def get_info(self) -> _InfoDict: + assert self._resource is not None + return self._resource.get_info() + + +class SystemRoute(AbstractRoute): + def __init__(self, http_exception: HTTPException) -> None: + super().__init__(hdrs.METH_ANY, self._handle) + self._http_exception = http_exception + + def url_for(self, *args: str, **kwargs: str) -> URL: + raise RuntimeError(".url_for() is not allowed for SystemRoute") + + @property + def name(self) -> Optional[str]: + return None + + def get_info(self) -> _InfoDict: + return {"http_exception": self._http_exception} + + async def _handle(self, request: Request) -> StreamResponse: + raise self._http_exception + + @property + def status(self) -> int: + return self._http_exception.status + + @property + def reason(self) -> str: + return self._http_exception.reason + + def __repr__(self) -> str: + return "".format(self=self) + + +class View(AbstractView): + async def _iter(self) -> StreamResponse: + if self.request.method not in hdrs.METH_ALL: + self._raise_allowed_methods() + method: Optional[Callable[[], Awaitable[StreamResponse]]] + method = getattr(self, self.request.method.lower(), None) + if method is None: + self._raise_allowed_methods() + ret = await method() + assert isinstance(ret, StreamResponse) + return ret + + def __await__(self) -> Generator[None, None, StreamResponse]: + return self._iter().__await__() + + def _raise_allowed_methods(self) -> NoReturn: + allowed_methods = {m for m in hdrs.METH_ALL if hasattr(self, m.lower())} + raise HTTPMethodNotAllowed(self.request.method, allowed_methods) + + +class ResourcesView(Sized, Iterable[AbstractResource], Container[AbstractResource]): + def __init__(self, resources: List[AbstractResource]) -> None: + self._resources = resources + + def __len__(self) -> int: + return len(self._resources) + + def __iter__(self) -> Iterator[AbstractResource]: + yield from self._resources + + def __contains__(self, resource: object) -> bool: + return resource in self._resources + + +class RoutesView(Sized, Iterable[AbstractRoute], Container[AbstractRoute]): + def __init__(self, resources: List[AbstractResource]): + self._routes: List[AbstractRoute] = [] + for resource in resources: + for route in resource: + self._routes.append(route) + + def __len__(self) -> int: + return len(self._routes) + + def __iter__(self) -> Iterator[AbstractRoute]: + yield from self._routes + + def __contains__(self, route: object) -> bool: + return route in self._routes + + +class UrlDispatcher(AbstractRouter, Mapping[str, AbstractResource]): + + NAME_SPLIT_RE = re.compile(r"[.:-]") + + def __init__(self) -> None: + super().__init__() + self._resources: List[AbstractResource] = [] + self._named_resources: Dict[str, AbstractResource] = {} + self._resource_index: dict[str, list[AbstractResource]] = {} + self._matched_sub_app_resources: List[MatchedSubAppResource] = [] + + async def resolve(self, request: Request) -> UrlMappingMatchInfo: + resource_index = self._resource_index + allowed_methods: Set[str] = set() + + # MatchedSubAppResource is primarily used to match on domain names + # (though custom rules could match on other things). This means that + # the traversal algorithm below can't be applied, and that we likely + # need to check these first so a sub app that defines the same path + # as a parent app will get priority if there's a domain match. + # + # For most cases we do not expect there to be many of these since + # currently they are only added by `.add_domain()`. + for resource in self._matched_sub_app_resources: + match_dict, allowed = await resource.resolve(request) + if match_dict is not None: + return match_dict + else: + allowed_methods |= allowed + + # Walk the url parts looking for candidates. We walk the url backwards + # to ensure the most explicit match is found first. If there are multiple + # candidates for a given url part because there are multiple resources + # registered for the same canonical path, we resolve them in a linear + # fashion to ensure registration order is respected. + url_part = request.rel_url.path_safe + while url_part: + for candidate in resource_index.get(url_part, ()): + match_dict, allowed = await candidate.resolve(request) + if match_dict is not None: + return match_dict + else: + allowed_methods |= allowed + if url_part == "/": + break + url_part = url_part.rpartition("/")[0] or "/" + + if allowed_methods: + return MatchInfoError(HTTPMethodNotAllowed(request.method, allowed_methods)) + + return MatchInfoError(HTTPNotFound()) + + def __iter__(self) -> Iterator[str]: + return iter(self._named_resources) + + def __len__(self) -> int: + return len(self._named_resources) + + def __contains__(self, resource: object) -> bool: + return resource in self._named_resources + + def __getitem__(self, name: str) -> AbstractResource: + return self._named_resources[name] + + def resources(self) -> ResourcesView: + return ResourcesView(self._resources) + + def routes(self) -> RoutesView: + return RoutesView(self._resources) + + def named_resources(self) -> Mapping[str, AbstractResource]: + return MappingProxyType(self._named_resources) + + def register_resource(self, resource: AbstractResource) -> None: + assert isinstance( + resource, AbstractResource + ), f"Instance of AbstractResource class is required, got {resource!r}" + if self.frozen: + raise RuntimeError("Cannot register a resource into frozen router.") + + name = resource.name + + if name is not None: + parts = self.NAME_SPLIT_RE.split(name) + for part in parts: + if keyword.iskeyword(part): + raise ValueError( + f"Incorrect route name {name!r}, " + "python keywords cannot be used " + "for route name" + ) + if not part.isidentifier(): + raise ValueError( + "Incorrect route name {!r}, " + "the name should be a sequence of " + "python identifiers separated " + "by dash, dot or column".format(name) + ) + if name in self._named_resources: + raise ValueError( + "Duplicate {!r}, " + "already handled by {!r}".format(name, self._named_resources[name]) + ) + self._named_resources[name] = resource + self._resources.append(resource) + + if isinstance(resource, MatchedSubAppResource): + # We cannot index match sub-app resources because they have match rules + self._matched_sub_app_resources.append(resource) + else: + self.index_resource(resource) + + def _get_resource_index_key(self, resource: AbstractResource) -> str: + """Return a key to index the resource in the resource index.""" + if "{" in (index_key := resource.canonical): + # strip at the first { to allow for variables, and than + # rpartition at / to allow for variable parts in the path + # For example if the canonical path is `/core/locations{tail:.*}` + # the index key will be `/core` since index is based on the + # url parts split by `/` + index_key = index_key.partition("{")[0].rpartition("/")[0] + return index_key.rstrip("/") or "/" + + def index_resource(self, resource: AbstractResource) -> None: + """Add a resource to the resource index.""" + resource_key = self._get_resource_index_key(resource) + # There may be multiple resources for a canonical path + # so we keep them in a list to ensure that registration + # order is respected. + self._resource_index.setdefault(resource_key, []).append(resource) + + def unindex_resource(self, resource: AbstractResource) -> None: + """Remove a resource from the resource index.""" + resource_key = self._get_resource_index_key(resource) + self._resource_index[resource_key].remove(resource) + + def add_resource(self, path: str, *, name: Optional[str] = None) -> Resource: + if path and not path.startswith("/"): + raise ValueError("path should be started with / or be empty") + # Reuse last added resource if path and name are the same + if self._resources: + resource = self._resources[-1] + if resource.name == name and resource.raw_match(path): + return cast(Resource, resource) + if not ("{" in path or "}" in path or ROUTE_RE.search(path)): + resource = PlainResource(path, name=name) + self.register_resource(resource) + return resource + resource = DynamicResource(path, name=name) + self.register_resource(resource) + return resource + + def add_route( + self, + method: str, + path: str, + handler: Union[Handler, Type[AbstractView]], + *, + name: Optional[str] = None, + expect_handler: Optional[_ExpectHandler] = None, + ) -> AbstractRoute: + resource = self.add_resource(path, name=name) + return resource.add_route(method, handler, expect_handler=expect_handler) + + def add_static( + self, + prefix: str, + path: PathLike, + *, + name: Optional[str] = None, + expect_handler: Optional[_ExpectHandler] = None, + chunk_size: int = 256 * 1024, + show_index: bool = False, + follow_symlinks: bool = False, + append_version: bool = False, + ) -> AbstractResource: + """Add static files view. + + prefix - url prefix + path - folder with files + + """ + assert prefix.startswith("/") + if prefix.endswith("/"): + prefix = prefix[:-1] + resource = StaticResource( + prefix, + path, + name=name, + expect_handler=expect_handler, + chunk_size=chunk_size, + show_index=show_index, + follow_symlinks=follow_symlinks, + append_version=append_version, + ) + self.register_resource(resource) + return resource + + def add_head(self, path: str, handler: Handler, **kwargs: Any) -> AbstractRoute: + """Shortcut for add_route with method HEAD.""" + return self.add_route(hdrs.METH_HEAD, path, handler, **kwargs) + + def add_options(self, path: str, handler: Handler, **kwargs: Any) -> AbstractRoute: + """Shortcut for add_route with method OPTIONS.""" + return self.add_route(hdrs.METH_OPTIONS, path, handler, **kwargs) + + def add_get( + self, + path: str, + handler: Handler, + *, + name: Optional[str] = None, + allow_head: bool = True, + **kwargs: Any, + ) -> AbstractRoute: + """Shortcut for add_route with method GET. + + If allow_head is true, another + route is added allowing head requests to the same endpoint. + """ + resource = self.add_resource(path, name=name) + if allow_head: + resource.add_route(hdrs.METH_HEAD, handler, **kwargs) + return resource.add_route(hdrs.METH_GET, handler, **kwargs) + + def add_post(self, path: str, handler: Handler, **kwargs: Any) -> AbstractRoute: + """Shortcut for add_route with method POST.""" + return self.add_route(hdrs.METH_POST, path, handler, **kwargs) + + def add_put(self, path: str, handler: Handler, **kwargs: Any) -> AbstractRoute: + """Shortcut for add_route with method PUT.""" + return self.add_route(hdrs.METH_PUT, path, handler, **kwargs) + + def add_patch(self, path: str, handler: Handler, **kwargs: Any) -> AbstractRoute: + """Shortcut for add_route with method PATCH.""" + return self.add_route(hdrs.METH_PATCH, path, handler, **kwargs) + + def add_delete(self, path: str, handler: Handler, **kwargs: Any) -> AbstractRoute: + """Shortcut for add_route with method DELETE.""" + return self.add_route(hdrs.METH_DELETE, path, handler, **kwargs) + + def add_view( + self, path: str, handler: Type[AbstractView], **kwargs: Any + ) -> AbstractRoute: + """Shortcut for add_route with ANY methods for a class-based view.""" + return self.add_route(hdrs.METH_ANY, path, handler, **kwargs) + + def freeze(self) -> None: + super().freeze() + for resource in self._resources: + resource.freeze() + + def add_routes(self, routes: Iterable[AbstractRouteDef]) -> List[AbstractRoute]: + """Append routes to route table. + + Parameter should be a sequence of RouteDef objects. + + Returns a list of registered AbstractRoute instances. + """ + registered_routes = [] + for route_def in routes: + registered_routes.extend(route_def.register(self)) + return registered_routes + + +def _quote_path(value: str) -> str: + if YARL_VERSION < (1, 6): + value = value.replace("%", "%25") + return URL.build(path=value, encoded=False).raw_path + + +def _unquote_path_safe(value: str) -> str: + if "%" not in value: + return value + return value.replace("%2F", "/").replace("%25", "%") + + +def _requote_path(value: str) -> str: + # Quote non-ascii characters and other characters which must be quoted, + # but preserve existing %-sequences. + result = _quote_path(value) + if "%" in value: + result = result.replace("%25", "%") + return result diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web_ws.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web_ws.py new file mode 100644 index 0000000000000000000000000000000000000000..575f9a3dc8507d1e6b766333c9daec389313febd --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/web_ws.py @@ -0,0 +1,631 @@ +import asyncio +import base64 +import binascii +import hashlib +import json +import sys +from typing import Any, Final, Iterable, Optional, Tuple, Union, cast + +import attr +from multidict import CIMultiDict + +from . import hdrs +from ._websocket.reader import WebSocketDataQueue +from ._websocket.writer import DEFAULT_LIMIT +from .abc import AbstractStreamWriter +from .client_exceptions import WSMessageTypeError +from .helpers import calculate_timeout_when, set_exception, set_result +from .http import ( + WS_CLOSED_MESSAGE, + WS_CLOSING_MESSAGE, + WS_KEY, + WebSocketError, + WebSocketReader, + WebSocketWriter, + WSCloseCode, + WSMessage, + WSMsgType as WSMsgType, + ws_ext_gen, + ws_ext_parse, +) +from .http_websocket import _INTERNAL_RECEIVE_TYPES +from .log import ws_logger +from .streams import EofStream +from .typedefs import JSONDecoder, JSONEncoder +from .web_exceptions import HTTPBadRequest, HTTPException +from .web_request import BaseRequest +from .web_response import StreamResponse + +if sys.version_info >= (3, 11): + import asyncio as async_timeout +else: + import async_timeout + +__all__ = ( + "WebSocketResponse", + "WebSocketReady", + "WSMsgType", +) + +THRESHOLD_CONNLOST_ACCESS: Final[int] = 5 + + +@attr.s(auto_attribs=True, frozen=True, slots=True) +class WebSocketReady: + ok: bool + protocol: Optional[str] + + def __bool__(self) -> bool: + return self.ok + + +class WebSocketResponse(StreamResponse): + + _length_check: bool = False + _ws_protocol: Optional[str] = None + _writer: Optional[WebSocketWriter] = None + _reader: Optional[WebSocketDataQueue] = None + _closed: bool = False + _closing: bool = False + _conn_lost: int = 0 + _close_code: Optional[int] = None + _loop: Optional[asyncio.AbstractEventLoop] = None + _waiting: bool = False + _close_wait: Optional[asyncio.Future[None]] = None + _exception: Optional[BaseException] = None + _heartbeat_when: float = 0.0 + _heartbeat_cb: Optional[asyncio.TimerHandle] = None + _pong_response_cb: Optional[asyncio.TimerHandle] = None + _ping_task: Optional[asyncio.Task[None]] = None + + def __init__( + self, + *, + timeout: float = 10.0, + receive_timeout: Optional[float] = None, + autoclose: bool = True, + autoping: bool = True, + heartbeat: Optional[float] = None, + protocols: Iterable[str] = (), + compress: bool = True, + max_msg_size: int = 4 * 1024 * 1024, + writer_limit: int = DEFAULT_LIMIT, + ) -> None: + super().__init__(status=101) + self._protocols = protocols + self._timeout = timeout + self._receive_timeout = receive_timeout + self._autoclose = autoclose + self._autoping = autoping + self._heartbeat = heartbeat + if heartbeat is not None: + self._pong_heartbeat = heartbeat / 2.0 + self._compress: Union[bool, int] = compress + self._max_msg_size = max_msg_size + self._writer_limit = writer_limit + + def _cancel_heartbeat(self) -> None: + self._cancel_pong_response_cb() + if self._heartbeat_cb is not None: + self._heartbeat_cb.cancel() + self._heartbeat_cb = None + if self._ping_task is not None: + self._ping_task.cancel() + self._ping_task = None + + def _cancel_pong_response_cb(self) -> None: + if self._pong_response_cb is not None: + self._pong_response_cb.cancel() + self._pong_response_cb = None + + def _reset_heartbeat(self) -> None: + if self._heartbeat is None: + return + self._cancel_pong_response_cb() + req = self._req + timeout_ceil_threshold = ( + req._protocol._timeout_ceil_threshold if req is not None else 5 + ) + loop = self._loop + assert loop is not None + now = loop.time() + when = calculate_timeout_when(now, self._heartbeat, timeout_ceil_threshold) + self._heartbeat_when = when + if self._heartbeat_cb is None: + # We do not cancel the previous heartbeat_cb here because + # it generates a significant amount of TimerHandle churn + # which causes asyncio to rebuild the heap frequently. + # Instead _send_heartbeat() will reschedule the next + # heartbeat if it fires too early. + self._heartbeat_cb = loop.call_at(when, self._send_heartbeat) + + def _send_heartbeat(self) -> None: + self._heartbeat_cb = None + loop = self._loop + assert loop is not None and self._writer is not None + now = loop.time() + if now < self._heartbeat_when: + # Heartbeat fired too early, reschedule + self._heartbeat_cb = loop.call_at( + self._heartbeat_when, self._send_heartbeat + ) + return + + req = self._req + timeout_ceil_threshold = ( + req._protocol._timeout_ceil_threshold if req is not None else 5 + ) + when = calculate_timeout_when(now, self._pong_heartbeat, timeout_ceil_threshold) + self._cancel_pong_response_cb() + self._pong_response_cb = loop.call_at(when, self._pong_not_received) + + coro = self._writer.send_frame(b"", WSMsgType.PING) + if sys.version_info >= (3, 12): + # Optimization for Python 3.12, try to send the ping + # immediately to avoid having to schedule + # the task on the event loop. + ping_task = asyncio.Task(coro, loop=loop, eager_start=True) + else: + ping_task = loop.create_task(coro) + + if not ping_task.done(): + self._ping_task = ping_task + ping_task.add_done_callback(self._ping_task_done) + else: + self._ping_task_done(ping_task) + + def _ping_task_done(self, task: "asyncio.Task[None]") -> None: + """Callback for when the ping task completes.""" + if not task.cancelled() and (exc := task.exception()): + self._handle_ping_pong_exception(exc) + self._ping_task = None + + def _pong_not_received(self) -> None: + if self._req is not None and self._req.transport is not None: + self._handle_ping_pong_exception( + asyncio.TimeoutError( + f"No PONG received after {self._pong_heartbeat} seconds" + ) + ) + + def _handle_ping_pong_exception(self, exc: BaseException) -> None: + """Handle exceptions raised during ping/pong processing.""" + if self._closed: + return + self._set_closed() + self._set_code_close_transport(WSCloseCode.ABNORMAL_CLOSURE) + self._exception = exc + if self._waiting and not self._closing and self._reader is not None: + self._reader.feed_data(WSMessage(WSMsgType.ERROR, exc, None), 0) + + def _set_closed(self) -> None: + """Set the connection to closed. + + Cancel any heartbeat timers and set the closed flag. + """ + self._closed = True + self._cancel_heartbeat() + + async def prepare(self, request: BaseRequest) -> AbstractStreamWriter: + # make pre-check to don't hide it by do_handshake() exceptions + if self._payload_writer is not None: + return self._payload_writer + + protocol, writer = self._pre_start(request) + payload_writer = await super().prepare(request) + assert payload_writer is not None + self._post_start(request, protocol, writer) + await payload_writer.drain() + return payload_writer + + def _handshake( + self, request: BaseRequest + ) -> Tuple["CIMultiDict[str]", Optional[str], int, bool]: + headers = request.headers + if "websocket" != headers.get(hdrs.UPGRADE, "").lower().strip(): + raise HTTPBadRequest( + text=( + "No WebSocket UPGRADE hdr: {}\n Can " + '"Upgrade" only to "WebSocket".' + ).format(headers.get(hdrs.UPGRADE)) + ) + + if "upgrade" not in headers.get(hdrs.CONNECTION, "").lower(): + raise HTTPBadRequest( + text="No CONNECTION upgrade hdr: {}".format( + headers.get(hdrs.CONNECTION) + ) + ) + + # find common sub-protocol between client and server + protocol: Optional[str] = None + if hdrs.SEC_WEBSOCKET_PROTOCOL in headers: + req_protocols = [ + str(proto.strip()) + for proto in headers[hdrs.SEC_WEBSOCKET_PROTOCOL].split(",") + ] + + for proto in req_protocols: + if proto in self._protocols: + protocol = proto + break + else: + # No overlap found: Return no protocol as per spec + ws_logger.warning( + "%s: Client protocols %r don’t overlap server-known ones %r", + request.remote, + req_protocols, + self._protocols, + ) + + # check supported version + version = headers.get(hdrs.SEC_WEBSOCKET_VERSION, "") + if version not in ("13", "8", "7"): + raise HTTPBadRequest(text=f"Unsupported version: {version}") + + # check client handshake for validity + key = headers.get(hdrs.SEC_WEBSOCKET_KEY) + try: + if not key or len(base64.b64decode(key)) != 16: + raise HTTPBadRequest(text=f"Handshake error: {key!r}") + except binascii.Error: + raise HTTPBadRequest(text=f"Handshake error: {key!r}") from None + + accept_val = base64.b64encode( + hashlib.sha1(key.encode() + WS_KEY).digest() + ).decode() + response_headers = CIMultiDict( + { + hdrs.UPGRADE: "websocket", + hdrs.CONNECTION: "upgrade", + hdrs.SEC_WEBSOCKET_ACCEPT: accept_val, + } + ) + + notakeover = False + compress = 0 + if self._compress: + extensions = headers.get(hdrs.SEC_WEBSOCKET_EXTENSIONS) + # Server side always get return with no exception. + # If something happened, just drop compress extension + compress, notakeover = ws_ext_parse(extensions, isserver=True) + if compress: + enabledext = ws_ext_gen( + compress=compress, isserver=True, server_notakeover=notakeover + ) + response_headers[hdrs.SEC_WEBSOCKET_EXTENSIONS] = enabledext + + if protocol: + response_headers[hdrs.SEC_WEBSOCKET_PROTOCOL] = protocol + return ( + response_headers, + protocol, + compress, + notakeover, + ) + + def _pre_start(self, request: BaseRequest) -> Tuple[Optional[str], WebSocketWriter]: + self._loop = request._loop + + headers, protocol, compress, notakeover = self._handshake(request) + + self.set_status(101) + self.headers.update(headers) + self.force_close() + self._compress = compress + transport = request._protocol.transport + assert transport is not None + writer = WebSocketWriter( + request._protocol, + transport, + compress=compress, + notakeover=notakeover, + limit=self._writer_limit, + ) + + return protocol, writer + + def _post_start( + self, request: BaseRequest, protocol: Optional[str], writer: WebSocketWriter + ) -> None: + self._ws_protocol = protocol + self._writer = writer + + self._reset_heartbeat() + + loop = self._loop + assert loop is not None + self._reader = WebSocketDataQueue(request._protocol, 2**16, loop=loop) + request.protocol.set_parser( + WebSocketReader( + self._reader, self._max_msg_size, compress=bool(self._compress) + ) + ) + # disable HTTP keepalive for WebSocket + request.protocol.keep_alive(False) + + def can_prepare(self, request: BaseRequest) -> WebSocketReady: + if self._writer is not None: + raise RuntimeError("Already started") + try: + _, protocol, _, _ = self._handshake(request) + except HTTPException: + return WebSocketReady(False, None) + else: + return WebSocketReady(True, protocol) + + @property + def prepared(self) -> bool: + return self._writer is not None + + @property + def closed(self) -> bool: + return self._closed + + @property + def close_code(self) -> Optional[int]: + return self._close_code + + @property + def ws_protocol(self) -> Optional[str]: + return self._ws_protocol + + @property + def compress(self) -> Union[int, bool]: + return self._compress + + def get_extra_info(self, name: str, default: Any = None) -> Any: + """Get optional transport information. + + If no value associated with ``name`` is found, ``default`` is returned. + """ + writer = self._writer + if writer is None: + return default + transport = writer.transport + if transport is None: + return default + return transport.get_extra_info(name, default) + + def exception(self) -> Optional[BaseException]: + return self._exception + + async def ping(self, message: bytes = b"") -> None: + if self._writer is None: + raise RuntimeError("Call .prepare() first") + await self._writer.send_frame(message, WSMsgType.PING) + + async def pong(self, message: bytes = b"") -> None: + # unsolicited pong + if self._writer is None: + raise RuntimeError("Call .prepare() first") + await self._writer.send_frame(message, WSMsgType.PONG) + + async def send_frame( + self, message: bytes, opcode: WSMsgType, compress: Optional[int] = None + ) -> None: + """Send a frame over the websocket.""" + if self._writer is None: + raise RuntimeError("Call .prepare() first") + await self._writer.send_frame(message, opcode, compress) + + async def send_str(self, data: str, compress: Optional[int] = None) -> None: + if self._writer is None: + raise RuntimeError("Call .prepare() first") + if not isinstance(data, str): + raise TypeError("data argument must be str (%r)" % type(data)) + await self._writer.send_frame( + data.encode("utf-8"), WSMsgType.TEXT, compress=compress + ) + + async def send_bytes(self, data: bytes, compress: Optional[int] = None) -> None: + if self._writer is None: + raise RuntimeError("Call .prepare() first") + if not isinstance(data, (bytes, bytearray, memoryview)): + raise TypeError("data argument must be byte-ish (%r)" % type(data)) + await self._writer.send_frame(data, WSMsgType.BINARY, compress=compress) + + async def send_json( + self, + data: Any, + compress: Optional[int] = None, + *, + dumps: JSONEncoder = json.dumps, + ) -> None: + await self.send_str(dumps(data), compress=compress) + + async def write_eof(self) -> None: # type: ignore[override] + if self._eof_sent: + return + if self._payload_writer is None: + raise RuntimeError("Response has not been started") + + await self.close() + self._eof_sent = True + + async def close( + self, *, code: int = WSCloseCode.OK, message: bytes = b"", drain: bool = True + ) -> bool: + """Close websocket connection.""" + if self._writer is None: + raise RuntimeError("Call .prepare() first") + + if self._closed: + return False + self._set_closed() + + try: + await self._writer.close(code, message) + writer = self._payload_writer + assert writer is not None + if drain: + await writer.drain() + except (asyncio.CancelledError, asyncio.TimeoutError): + self._set_code_close_transport(WSCloseCode.ABNORMAL_CLOSURE) + raise + except Exception as exc: + self._exception = exc + self._set_code_close_transport(WSCloseCode.ABNORMAL_CLOSURE) + return True + + reader = self._reader + assert reader is not None + # we need to break `receive()` cycle before we can call + # `reader.read()` as `close()` may be called from different task + if self._waiting: + assert self._loop is not None + assert self._close_wait is None + self._close_wait = self._loop.create_future() + reader.feed_data(WS_CLOSING_MESSAGE, 0) + await self._close_wait + + if self._closing: + self._close_transport() + return True + + try: + async with async_timeout.timeout(self._timeout): + while True: + msg = await reader.read() + if msg.type is WSMsgType.CLOSE: + self._set_code_close_transport(msg.data) + return True + except asyncio.CancelledError: + self._set_code_close_transport(WSCloseCode.ABNORMAL_CLOSURE) + raise + except Exception as exc: + self._exception = exc + self._set_code_close_transport(WSCloseCode.ABNORMAL_CLOSURE) + return True + + def _set_closing(self, code: WSCloseCode) -> None: + """Set the close code and mark the connection as closing.""" + self._closing = True + self._close_code = code + self._cancel_heartbeat() + + def _set_code_close_transport(self, code: WSCloseCode) -> None: + """Set the close code and close the transport.""" + self._close_code = code + self._close_transport() + + def _close_transport(self) -> None: + """Close the transport.""" + if self._req is not None and self._req.transport is not None: + self._req.transport.close() + + async def receive(self, timeout: Optional[float] = None) -> WSMessage: + if self._reader is None: + raise RuntimeError("Call .prepare() first") + + receive_timeout = timeout or self._receive_timeout + while True: + if self._waiting: + raise RuntimeError("Concurrent call to receive() is not allowed") + + if self._closed: + self._conn_lost += 1 + if self._conn_lost >= THRESHOLD_CONNLOST_ACCESS: + raise RuntimeError("WebSocket connection is closed.") + return WS_CLOSED_MESSAGE + elif self._closing: + return WS_CLOSING_MESSAGE + + try: + self._waiting = True + try: + if receive_timeout: + # Entering the context manager and creating + # Timeout() object can take almost 50% of the + # run time in this loop so we avoid it if + # there is no read timeout. + async with async_timeout.timeout(receive_timeout): + msg = await self._reader.read() + else: + msg = await self._reader.read() + self._reset_heartbeat() + finally: + self._waiting = False + if self._close_wait: + set_result(self._close_wait, None) + except asyncio.TimeoutError: + raise + except EofStream: + self._close_code = WSCloseCode.OK + await self.close() + return WSMessage(WSMsgType.CLOSED, None, None) + except WebSocketError as exc: + self._close_code = exc.code + await self.close(code=exc.code) + return WSMessage(WSMsgType.ERROR, exc, None) + except Exception as exc: + self._exception = exc + self._set_closing(WSCloseCode.ABNORMAL_CLOSURE) + await self.close() + return WSMessage(WSMsgType.ERROR, exc, None) + + if msg.type not in _INTERNAL_RECEIVE_TYPES: + # If its not a close/closing/ping/pong message + # we can return it immediately + return msg + + if msg.type is WSMsgType.CLOSE: + self._set_closing(msg.data) + # Could be closed while awaiting reader. + if not self._closed and self._autoclose: + # The client is likely going to close the + # connection out from under us so we do not + # want to drain any pending writes as it will + # likely result writing to a broken pipe. + await self.close(drain=False) + elif msg.type is WSMsgType.CLOSING: + self._set_closing(WSCloseCode.OK) + elif msg.type is WSMsgType.PING and self._autoping: + await self.pong(msg.data) + continue + elif msg.type is WSMsgType.PONG and self._autoping: + continue + + return msg + + async def receive_str(self, *, timeout: Optional[float] = None) -> str: + msg = await self.receive(timeout) + if msg.type is not WSMsgType.TEXT: + raise WSMessageTypeError( + f"Received message {msg.type}:{msg.data!r} is not WSMsgType.TEXT" + ) + return cast(str, msg.data) + + async def receive_bytes(self, *, timeout: Optional[float] = None) -> bytes: + msg = await self.receive(timeout) + if msg.type is not WSMsgType.BINARY: + raise WSMessageTypeError( + f"Received message {msg.type}:{msg.data!r} is not WSMsgType.BINARY" + ) + return cast(bytes, msg.data) + + async def receive_json( + self, *, loads: JSONDecoder = json.loads, timeout: Optional[float] = None + ) -> Any: + data = await self.receive_str(timeout=timeout) + return loads(data) + + async def write(self, data: bytes) -> None: + raise RuntimeError("Cannot call .write() for websocket") + + def __aiter__(self) -> "WebSocketResponse": + return self + + async def __anext__(self) -> WSMessage: + msg = await self.receive() + if msg.type in (WSMsgType.CLOSE, WSMsgType.CLOSING, WSMsgType.CLOSED): + raise StopAsyncIteration + return msg + + def _cancel(self, exc: BaseException) -> None: + # web_protocol calls this from connection_lost + # or when the server is shutting down. + self._closing = True + self._cancel_heartbeat() + if self._reader is not None: + set_exception(self._reader, exc) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/worker.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/worker.py new file mode 100644 index 0000000000000000000000000000000000000000..00ec79ce131456e6604d5df4b3a34d494d355699 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiohttp/worker.py @@ -0,0 +1,262 @@ +"""Async gunicorn worker for aiohttp.web""" + +import asyncio +import inspect +import os +import re +import signal +import sys +from types import FrameType +from typing import TYPE_CHECKING, Any, Optional + +from gunicorn.config import AccessLogFormat as GunicornAccessLogFormat +from gunicorn.workers import base + +from aiohttp import web + +from .helpers import set_result +from .web_app import Application +from .web_log import AccessLogger + +if TYPE_CHECKING: + import ssl + + SSLContext = ssl.SSLContext +else: + try: + import ssl + + SSLContext = ssl.SSLContext + except ImportError: # pragma: no cover + ssl = None # type: ignore[assignment] + SSLContext = object # type: ignore[misc,assignment] + + +__all__ = ("GunicornWebWorker", "GunicornUVLoopWebWorker") + + +class GunicornWebWorker(base.Worker): # type: ignore[misc,no-any-unimported] + DEFAULT_AIOHTTP_LOG_FORMAT = AccessLogger.LOG_FORMAT + DEFAULT_GUNICORN_LOG_FORMAT = GunicornAccessLogFormat.default + + def __init__(self, *args: Any, **kw: Any) -> None: # pragma: no cover + super().__init__(*args, **kw) + + self._task: Optional[asyncio.Task[None]] = None + self.exit_code = 0 + self._notify_waiter: Optional[asyncio.Future[bool]] = None + + def init_process(self) -> None: + # create new event_loop after fork + try: + asyncio.get_event_loop().close() + except RuntimeError: + # No loop was running + pass + + self.loop = asyncio.new_event_loop() + asyncio.set_event_loop(self.loop) + + super().init_process() + + def run(self) -> None: + self._task = self.loop.create_task(self._run()) + + try: # ignore all finalization problems + self.loop.run_until_complete(self._task) + except Exception: + self.log.exception("Exception in gunicorn worker") + self.loop.run_until_complete(self.loop.shutdown_asyncgens()) + self.loop.close() + + sys.exit(self.exit_code) + + async def _run(self) -> None: + runner = None + if isinstance(self.wsgi, Application): + app = self.wsgi + elif inspect.iscoroutinefunction(self.wsgi) or ( + sys.version_info < (3, 14) and asyncio.iscoroutinefunction(self.wsgi) + ): + wsgi = await self.wsgi() + if isinstance(wsgi, web.AppRunner): + runner = wsgi + app = runner.app + else: + app = wsgi + else: + raise RuntimeError( + "wsgi app should be either Application or " + "async function returning Application, got {}".format(self.wsgi) + ) + + if runner is None: + access_log = self.log.access_log if self.cfg.accesslog else None + runner = web.AppRunner( + app, + logger=self.log, + keepalive_timeout=self.cfg.keepalive, + access_log=access_log, + access_log_format=self._get_valid_log_format( + self.cfg.access_log_format + ), + shutdown_timeout=self.cfg.graceful_timeout / 100 * 95, + ) + await runner.setup() + + ctx = self._create_ssl_context(self.cfg) if self.cfg.is_ssl else None + + runner = runner + assert runner is not None + server = runner.server + assert server is not None + for sock in self.sockets: + site = web.SockSite( + runner, + sock, + ssl_context=ctx, + ) + await site.start() + + # If our parent changed then we shut down. + pid = os.getpid() + try: + while self.alive: # type: ignore[has-type] + self.notify() + + cnt = server.requests_count + if self.max_requests and cnt > self.max_requests: + self.alive = False + self.log.info("Max requests, shutting down: %s", self) + + elif pid == os.getpid() and self.ppid != os.getppid(): + self.alive = False + self.log.info("Parent changed, shutting down: %s", self) + else: + await self._wait_next_notify() + except BaseException: + pass + + await runner.cleanup() + + def _wait_next_notify(self) -> "asyncio.Future[bool]": + self._notify_waiter_done() + + loop = self.loop + assert loop is not None + self._notify_waiter = waiter = loop.create_future() + self.loop.call_later(1.0, self._notify_waiter_done, waiter) + + return waiter + + def _notify_waiter_done( + self, waiter: Optional["asyncio.Future[bool]"] = None + ) -> None: + if waiter is None: + waiter = self._notify_waiter + if waiter is not None: + set_result(waiter, True) + + if waiter is self._notify_waiter: + self._notify_waiter = None + + def init_signals(self) -> None: + # Set up signals through the event loop API. + + self.loop.add_signal_handler( + signal.SIGQUIT, self.handle_quit, signal.SIGQUIT, None + ) + + self.loop.add_signal_handler( + signal.SIGTERM, self.handle_exit, signal.SIGTERM, None + ) + + self.loop.add_signal_handler( + signal.SIGINT, self.handle_quit, signal.SIGINT, None + ) + + self.loop.add_signal_handler( + signal.SIGWINCH, self.handle_winch, signal.SIGWINCH, None + ) + + self.loop.add_signal_handler( + signal.SIGUSR1, self.handle_usr1, signal.SIGUSR1, None + ) + + self.loop.add_signal_handler( + signal.SIGABRT, self.handle_abort, signal.SIGABRT, None + ) + + # Don't let SIGTERM and SIGUSR1 disturb active requests + # by interrupting system calls + signal.siginterrupt(signal.SIGTERM, False) + signal.siginterrupt(signal.SIGUSR1, False) + # Reset signals so Gunicorn doesn't swallow subprocess return codes + # See: https://github.com/aio-libs/aiohttp/issues/6130 + + def handle_quit(self, sig: int, frame: Optional[FrameType]) -> None: + self.alive = False + + # worker_int callback + self.cfg.worker_int(self) + + # wakeup closing process + self._notify_waiter_done() + + def handle_abort(self, sig: int, frame: Optional[FrameType]) -> None: + self.alive = False + self.exit_code = 1 + self.cfg.worker_abort(self) + sys.exit(1) + + @staticmethod + def _create_ssl_context(cfg: Any) -> "SSLContext": + """Creates SSLContext instance for usage in asyncio.create_server. + + See ssl.SSLSocket.__init__ for more details. + """ + if ssl is None: # pragma: no cover + raise RuntimeError("SSL is not supported.") + + ctx = ssl.SSLContext(cfg.ssl_version) + ctx.load_cert_chain(cfg.certfile, cfg.keyfile) + ctx.verify_mode = cfg.cert_reqs + if cfg.ca_certs: + ctx.load_verify_locations(cfg.ca_certs) + if cfg.ciphers: + ctx.set_ciphers(cfg.ciphers) + return ctx + + def _get_valid_log_format(self, source_format: str) -> str: + if source_format == self.DEFAULT_GUNICORN_LOG_FORMAT: + return self.DEFAULT_AIOHTTP_LOG_FORMAT + elif re.search(r"%\([^\)]+\)", source_format): + raise ValueError( + "Gunicorn's style options in form of `%(name)s` are not " + "supported for the log formatting. Please use aiohttp's " + "format specification to configure access log formatting: " + "http://docs.aiohttp.org/en/stable/logging.html" + "#format-specification" + ) + else: + return source_format + + +class GunicornUVLoopWebWorker(GunicornWebWorker): + def init_process(self) -> None: + import uvloop + + # Close any existing event loop before setting a + # new policy. + try: + asyncio.get_event_loop().close() + except RuntimeError: + # No loop was running + pass + + # Setup uvloop policy, so that every + # asyncio.get_event_loop() will create an instance + # of uvloop event loop. + asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) + + super().init_process() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiosignal/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/aiosignal/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..5ede0090ad93cacd642b11645c1c71bcd3021e2b --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/aiosignal/__init__.py @@ -0,0 +1,59 @@ +import sys +from typing import Any, Awaitable, Callable, TypeVar + +from frozenlist import FrozenList + +if sys.version_info >= (3, 11): + from typing import Unpack +else: + from typing_extensions import Unpack + +if sys.version_info >= (3, 13): + from typing import TypeVarTuple +else: + from typing_extensions import TypeVarTuple + +_T = TypeVar("_T") +_Ts = TypeVarTuple("_Ts", default=Unpack[tuple[()]]) + +__version__ = "1.4.0" + +__all__ = ("Signal",) + + +class Signal(FrozenList[Callable[[Unpack[_Ts]], Awaitable[object]]]): + """Coroutine-based signal implementation. + + To connect a callback to a signal, use any list method. + + Signals are fired using the send() coroutine, which takes named + arguments. + """ + + __slots__ = ("_owner",) + + def __init__(self, owner: object): + super().__init__() + self._owner = owner + + def __repr__(self) -> str: + return "".format( + self._owner, self.frozen, list(self) + ) + + async def send(self, *args: Unpack[_Ts], **kwargs: Any) -> None: + """ + Sends data to all registered receivers. + """ + if not self.frozen: + raise RuntimeError("Cannot send non-frozen signal.") + + for receiver in self: + await receiver(*args, **kwargs) + + def __call__( + self, func: Callable[[Unpack[_Ts]], Awaitable[_T]] + ) -> Callable[[Unpack[_Ts]], Awaitable[_T]]: + """Decorator to add a function to this Signal.""" + self.append(func) + return func diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/aiosignal/py.typed b/bioagent-bench/.venv/lib/python3.14/site-packages/aiosignal/py.typed new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/annotated_doc/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/annotated_doc/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..a0152a7d12abc2db37fb26e764a61e0c894a43f3 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/annotated_doc/__init__.py @@ -0,0 +1,3 @@ +from .main import Doc as Doc + +__version__ = "0.0.4" diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/annotated_doc/main.py b/bioagent-bench/.venv/lib/python3.14/site-packages/annotated_doc/main.py new file mode 100644 index 0000000000000000000000000000000000000000..7063c59e4500a1d02bfc9b41887f9e95f8163507 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/annotated_doc/main.py @@ -0,0 +1,36 @@ +class Doc: + """Define the documentation of a type annotation using `Annotated`, to be + used in class attributes, function and method parameters, return values, + and variables. + + The value should be a positional-only string literal to allow static tools + like editors and documentation generators to use it. + + This complements docstrings. + + The string value passed is available in the attribute `documentation`. + + Example: + + ```Python + from typing import Annotated + from annotated_doc import Doc + + def hi(name: Annotated[str, Doc("Who to say hi to")]) -> None: + print(f"Hi, {name}!") + ``` + """ + + def __init__(self, documentation: str, /) -> None: + self.documentation = documentation + + def __repr__(self) -> str: + return f"Doc({self.documentation!r})" + + def __hash__(self) -> int: + return hash(self.documentation) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Doc): + return NotImplemented + return self.documentation == other.documentation diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/annotated_doc/py.typed b/bioagent-bench/.venv/lib/python3.14/site-packages/annotated_doc/py.typed new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/anyio-4.13.0.dist-info/INSTALLER b/bioagent-bench/.venv/lib/python3.14/site-packages/anyio-4.13.0.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..5c69047b2eb8235994febeeae1da4a82365a240a --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/anyio-4.13.0.dist-info/INSTALLER @@ -0,0 +1 @@ +uv \ No newline at end of file diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/anyio-4.13.0.dist-info/METADATA b/bioagent-bench/.venv/lib/python3.14/site-packages/anyio-4.13.0.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..2de1cd5521ede953e5f08c09a09a7a6e125049d5 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/anyio-4.13.0.dist-info/METADATA @@ -0,0 +1,105 @@ +Metadata-Version: 2.4 +Name: anyio +Version: 4.13.0 +Summary: High-level concurrency and networking framework on top of asyncio or Trio +Author-email: Alex Grönholm +License-Expression: MIT +Project-URL: Documentation, https://anyio.readthedocs.io/en/latest/ +Project-URL: Changelog, https://anyio.readthedocs.io/en/stable/versionhistory.html +Project-URL: Source code, https://github.com/agronholm/anyio +Project-URL: Issue tracker, https://github.com/agronholm/anyio/issues +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Framework :: AnyIO +Classifier: Typing :: Typed +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Requires-Python: >=3.10 +Description-Content-Type: text/x-rst +License-File: LICENSE +Requires-Dist: exceptiongroup>=1.0.2; python_version < "3.11" +Requires-Dist: idna>=2.8 +Requires-Dist: typing_extensions>=4.5; python_version < "3.13" +Provides-Extra: trio +Requires-Dist: trio>=0.32.0; extra == "trio" +Dynamic: license-file + +.. image:: https://github.com/agronholm/anyio/actions/workflows/test.yml/badge.svg + :target: https://github.com/agronholm/anyio/actions/workflows/test.yml + :alt: Build Status +.. image:: https://coveralls.io/repos/github/agronholm/anyio/badge.svg?branch=master + :target: https://coveralls.io/github/agronholm/anyio?branch=master + :alt: Code Coverage +.. image:: https://readthedocs.org/projects/anyio/badge/?version=latest + :target: https://anyio.readthedocs.io/en/latest/?badge=latest + :alt: Documentation +.. image:: https://badges.gitter.im/gitterHQ/gitter.svg + :target: https://gitter.im/python-trio/AnyIO + :alt: Gitter chat +.. image:: https://tidelift.com/badges/package/pypi/anyio + :target: https://tidelift.com/subscription/pkg/pypi-anyio + :alt: Tidelift + +AnyIO is an asynchronous networking and concurrency library that works on top of either asyncio_ or +Trio_. It implements Trio-like `structured concurrency`_ (SC) on top of asyncio and works in harmony +with the native SC of Trio itself. + +Applications and libraries written against AnyIO's API will run unmodified on either asyncio_ or +Trio_. AnyIO can also be adopted into a library or application incrementally – bit by bit, no full +refactoring necessary. It will blend in with the native libraries of your chosen backend. + +To find out why you might want to use AnyIO's APIs instead of asyncio's, you can read about it +`here `_. + +Documentation +------------- + +View full documentation at: https://anyio.readthedocs.io/ + +Features +-------- + +AnyIO offers the following functionality: + +* Task groups (nurseries_ in trio terminology) +* High-level networking (TCP, UDP and UNIX sockets) + + * `Happy eyeballs`_ algorithm for TCP connections (more robust than that of asyncio on Python + 3.8) + * async/await style UDP sockets (unlike asyncio where you still have to use Transports and + Protocols) + +* A versatile API for byte streams and object streams +* Inter-task synchronization and communication (locks, conditions, events, semaphores, object + streams) +* Worker threads +* Subprocesses +* Subinterpreter support for code parallelization (on Python 3.13 and later) +* Asynchronous file I/O (using worker threads) +* Signal handling +* Asynchronous version of the functools_ module + +AnyIO also comes with its own pytest_ plugin which also supports asynchronous fixtures. +It even works with the popular Hypothesis_ library. + +.. _asyncio: https://docs.python.org/3/library/asyncio.html +.. _Trio: https://github.com/python-trio/trio +.. _structured concurrency: https://en.wikipedia.org/wiki/Structured_concurrency +.. _nurseries: https://trio.readthedocs.io/en/stable/reference-core.html#nurseries-and-spawning +.. _Happy eyeballs: https://en.wikipedia.org/wiki/Happy_Eyeballs +.. _pytest: https://docs.pytest.org/en/latest/ +.. _functools: https://docs.python.org/3/library/functools.html +.. _Hypothesis: https://hypothesis.works/ + +Security contact information +---------------------------- + +To report a security vulnerability, please use the `Tidelift security contact`_. +Tidelift will coordinate the fix and disclosure. + +.. _Tidelift security contact: https://tidelift.com/security diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/anyio-4.13.0.dist-info/RECORD b/bioagent-bench/.venv/lib/python3.14/site-packages/anyio-4.13.0.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..d5d05bd20c203a404cbfd82ee77bf22ab4f67a96 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/anyio-4.13.0.dist-info/RECORD @@ -0,0 +1,51 @@ +anyio-4.13.0.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2 +anyio-4.13.0.dist-info/METADATA,sha256=F0EYfiPlmTRwmJN2JktNxJg1GNnl0wHhzOWmz7pFvjM,4513 +anyio-4.13.0.dist-info/RECORD,, +anyio-4.13.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +anyio-4.13.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91 +anyio-4.13.0.dist-info/entry_points.txt,sha256=_d6Yu6uiaZmNe0CydowirE9Cmg7zUL2g08tQpoS3Qvc,39 +anyio-4.13.0.dist-info/licenses/LICENSE,sha256=U2GsncWPLvX9LpsJxoKXwX8ElQkJu8gCO9uC6s8iwrA,1081 +anyio-4.13.0.dist-info/top_level.txt,sha256=QglSMiWX8_5dpoVAEIHdEYzvqFMdSYWmCj6tYw2ITkQ,6 +anyio/__init__.py,sha256=7iDVqMUprUuKNY91FuoKqayAhR-OY136YDPI6P78HHk,6170 +anyio/_backends/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +anyio/_backends/_asyncio.py,sha256=kuqlg2sBUsFdgY80xSDAw60Gx_4WNCl9iSL5XlY6lCU,99476 +anyio/_backends/_trio.py,sha256=l9U-TsKRxzmTQxSMvOhn0bNeFn_iRx3Ho30jvR5Bdu0,41366 +anyio/_core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +anyio/_core/_asyncio_selector_thread.py,sha256=2PdxFM3cs02Kp6BSppbvmRT7q7asreTW5FgBxEsflBo,5626 +anyio/_core/_contextmanagers.py,sha256=YInBCabiEeS-UaP_Jdxa1CaFC71ETPW8HZTHIM8Rsc8,7215 +anyio/_core/_eventloop.py,sha256=c2EdcBX-xnKwxPcC4Pjn3_qG9I-x4IWFO2R9RqCGjM4,6448 +anyio/_core/_exceptions.py,sha256=Y3aq-Wxd7Q2HqwSg7nZPvRsHEuGazv_qeet6gqEBdPk,4407 +anyio/_core/_fileio.py,sha256=CKi1gFNiW2G4knWeBE7He7-rptQwgYjDUWfG8DSlvLs,25665 +anyio/_core/_resources.py,sha256=NbmU5O5UX3xEyACnkmYX28Fmwdl-f-ny0tHym26e0w0,435 +anyio/_core/_signals.py,sha256=mjTBB2hTKNPRlU0IhnijeQedpWOGERDiMjSlJQsFrug,1016 +anyio/_core/_sockets.py,sha256=RBXHcUqZt5gg_-OOfgHVv8uq2FSKk1uVUzTdpjBoI1o,34977 +anyio/_core/_streams.py,sha256=FczFwIgDpnkK0bODWJXMpsUJYdvAD04kaUaGzJU8DK0,1806 +anyio/_core/_subprocesses.py,sha256=tkmkPKEkEaiMD8C9WRZBlmgjOYRDRbZdte6e-unay2E,7916 +anyio/_core/_synchronization.py,sha256=9G3fvRsPNrrWJ_Z6gD_80wXq8I8qgAyhwM8PvHQnT2c,21061 +anyio/_core/_tasks.py,sha256=pVB7K6AAulzUM8YgXAeqNZG44nSyZ1bYJjH8GznC00I,5435 +anyio/_core/_tempfile.py,sha256=jE2w59FRF3yRo4vjkjfZF2YcqsBZvc66VWRwrJGDYGk,19624 +anyio/_core/_testing.py,sha256=u7MPqGXwpTxqI7hclSdNA30z2GH1Nw258uwKvy_RfBg,2340 +anyio/_core/_typedattr.py,sha256=P4ozZikn3-DbpoYcvyghS_FOYAgbmUxeoU8-L_07pZM,2508 +anyio/abc/__init__.py,sha256=6mWhcl_pGXhrgZVHP_TCfMvIXIOp9mroEFM90fYCU_U,2869 +anyio/abc/_eventloop.py,sha256=39lYnmtvoHaZw22sWBKOTA_zv7bamOnr8O49PqgDXdw,10629 +anyio/abc/_resources.py,sha256=DrYvkNN1hH6Uvv5_5uKySvDsnknGVDe8FCKfko0VtN8,783 +anyio/abc/_sockets.py,sha256=OmVDrfemVvF9c5K1tpBgQyV6fn5v0XyCExLAqBOGz9o,13124 +anyio/abc/_streams.py,sha256=HYvna1iZbWcwLROTO6IhLX79RTRLPShZMWe0sG1q54I,7481 +anyio/abc/_subprocesses.py,sha256=cumAPJTktOQtw63IqG0lDpyZqu_l1EElvQHMiwJgL08,2067 +anyio/abc/_tasks.py,sha256=KC7wrciE48AINOI-AhPutnFhe1ewfP7QnamFlDzqesQ,3721 +anyio/abc/_testing.py,sha256=tBJUzkSfOXJw23fe8qSJ03kJlShOYjjaEyFB6k6MYT8,1821 +anyio/from_thread.py,sha256=L-0w1HxJ6BSb-KuVi57k5Tkc3yzQrx3QK5tAxMPcY-0,19141 +anyio/functools.py,sha256=5AWM1iYTKkTzptvUhQDdLSh5GvbBW-vcs-SAUfIfA9A,12076 +anyio/lowlevel.py,sha256=AyKLVK3LaWSoK39LkCKxE4_GDMLKZBNqTrLUgk63y80,5158 +anyio/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +anyio/pytest_plugin.py,sha256=t6h4KJstqIxfxwTZ1YO8vpUVuB99nfCLltn0NHfatHo,12775 +anyio/streams/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +anyio/streams/buffered.py,sha256=2R3PeJhe4EXrdYqz44Y6-Eg9R6DrmlsYrP36Ir43-po,6263 +anyio/streams/file.py,sha256=msnrotVKGMQomUu_Rj2qz9MvIdUp6d3JGr7MOEO8kV4,4428 +anyio/streams/memory.py,sha256=F0zwzvFJKAhX_LRZGoKzzqDC2oMM-f-yyTBrEYEGOaU,10740 +anyio/streams/stapled.py,sha256=T8Xqwf8K6EgURPxbt1N4i7A8BAk-gScv-GRhjLXIf_o,4390 +anyio/streams/text.py,sha256=BcVAGJw1VRvtIqnv-o0Rb0pwH7p8vwlvl21xHq522ag,5765 +anyio/streams/tls.py,sha256=DQVkXUvsTEYKkBO8dlVU7j_5H8QOtLy4sGi1Wrjqevo,15303 +anyio/to_interpreter.py,sha256=_mLngrMy97TMR6VbW4Y6YzDUk9ZuPcQMPlkuyRh3C9k,7100 +anyio/to_process.py,sha256=J7gAA_YOuoHqnpDAf5fm1Qu6kOmTzdFbiDNvnV755vk,9798 +anyio/to_thread.py,sha256=f6h_k2d743GBv9FhAnhM_YpTvWgIrzBy9cOE0eJ1UJw,2693 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/anyio-4.13.0.dist-info/REQUESTED b/bioagent-bench/.venv/lib/python3.14/site-packages/anyio-4.13.0.dist-info/REQUESTED new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/anyio-4.13.0.dist-info/WHEEL b/bioagent-bench/.venv/lib/python3.14/site-packages/anyio-4.13.0.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..14a883f292bc96b20c2b76a3081991f2676523a9 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/anyio-4.13.0.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (82.0.1) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/anyio-4.13.0.dist-info/entry_points.txt b/bioagent-bench/.venv/lib/python3.14/site-packages/anyio-4.13.0.dist-info/entry_points.txt new file mode 100644 index 0000000000000000000000000000000000000000..44dd9bdc3039122cc98014c1439ca254313fd014 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/anyio-4.13.0.dist-info/entry_points.txt @@ -0,0 +1,2 @@ +[pytest11] +anyio = anyio.pytest_plugin diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/anyio-4.13.0.dist-info/licenses/LICENSE b/bioagent-bench/.venv/lib/python3.14/site-packages/anyio-4.13.0.dist-info/licenses/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..104eebf5a3002fccdaceef3a4cb936173c1c2035 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/anyio-4.13.0.dist-info/licenses/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2018 Alex Grönholm + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/anyio-4.13.0.dist-info/top_level.txt b/bioagent-bench/.venv/lib/python3.14/site-packages/anyio-4.13.0.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..c77c069ecc9b7f8b1f97dbcfec905725db0253a8 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/anyio-4.13.0.dist-info/top_level.txt @@ -0,0 +1 @@ +anyio diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer-3.4.7.dist-info/INSTALLER b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer-3.4.7.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..5c69047b2eb8235994febeeae1da4a82365a240a --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer-3.4.7.dist-info/INSTALLER @@ -0,0 +1 @@ +uv \ No newline at end of file diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer-3.4.7.dist-info/METADATA b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer-3.4.7.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..6b5a36018e67b70c47cb1603483566fba87f2178 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer-3.4.7.dist-info/METADATA @@ -0,0 +1,808 @@ +Metadata-Version: 2.4 +Name: charset-normalizer +Version: 3.4.7 +Summary: The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet. +Author-email: "Ahmed R. TAHRI" +Maintainer-email: "Ahmed R. TAHRI" +License: MIT +Project-URL: Changelog, https://github.com/jawah/charset_normalizer/blob/master/CHANGELOG.md +Project-URL: Documentation, https://charset-normalizer.readthedocs.io/ +Project-URL: Code, https://github.com/jawah/charset_normalizer +Project-URL: Issue tracker, https://github.com/jawah/charset_normalizer/issues +Keywords: encoding,charset,charset-detector,detector,normalization,unicode,chardet,detect +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Programming Language :: Python :: Free Threading :: 4 - Resilient +Classifier: Topic :: Text Processing :: Linguistic +Classifier: Topic :: Utilities +Classifier: Typing :: Typed +Requires-Python: >=3.7 +Description-Content-Type: text/markdown +License-File: LICENSE +Provides-Extra: unicode-backport +Dynamic: license-file + +

    Charset Detection, for Everyone 👋

    + +

    + The Real First Universal Charset Detector
    + + + + + Download Count Total + + + + +

    +

    + Featured Packages
    + + Static Badge + + + Static Badge + +

    +

    + In other language (unofficial port - by the community)
    + + Static Badge + +

    + +> A library that helps you read text from an unknown charset encoding.
    Motivated by `chardet`, +> I'm trying to resolve the issue by taking a new approach. +> All IANA character set names for which the Python core library provides codecs are supported. +> You can also register your own set of codecs, and yes, it would work as-is. + +

    + >>>>> 👉 Try Me Online Now, Then Adopt Me 👈 <<<<< +

    + +This project offers you an alternative to **Universal Charset Encoding Detector**, also known as **Chardet**. + +| Feature | [Chardet](https://github.com/chardet/chardet) | Charset Normalizer | [cChardet](https://github.com/PyYoshi/cChardet) | +|--------------------------------------------------|:---------------------------------------------:|:-----------------------------------------------------------------------------------------------:|:-----------------------------------------------:| +| `Fast` | ✅ | ✅ | ✅ | +| `Universal`[^1] | ❌ | ✅ | ❌ | +| `Reliable` **without** distinguishable standards | ✅ | ✅ | ✅ | +| `Reliable` **with** distinguishable standards | ✅ | ✅ | ✅ | +| `License` | _Disputed_[^2]
    _restrictive_ | MIT | MPL-1.1
    _restrictive_ | +| `Native Python` | ✅ | ✅ | ❌ | +| `Detect spoken language` | ✅ | ✅ | N/A | +| `UnicodeDecodeError Safety` | ✅ | ✅ | ❌ | +| `Whl Size (min)` | 500 kB | 150 kB | ~200 kB | +| `Supported Encoding` | 99 | [99](https://charset-normalizer.readthedocs.io/en/latest/user/support.html#supported-encodings) | 40 | +| `Can register custom encoding` | ❌ | ✅ | ❌ | + +

    +Reading Normalized TextCat Reading Text +

    + +[^1]: They are clearly using specific code for a specific encoding even if covering most of used one. +[^2]: Chardet 7.0+ was relicensed from LGPL-2.1 to MIT following an AI-assisted rewrite. This relicensing is disputed on two independent grounds: **(a)** the original author [contests](https://github.com/chardet/chardet/issues/327) that the maintainer had the right to relicense, arguing the rewrite is a derivative work of the LGPL-licensed codebase since it was not a clean room implementation; **(b)** the copyright claim itself is [questionable](https://github.com/chardet/chardet/issues/334) given the code was primarily generated by an LLM, and AI-generated output may not be copyrightable under most jurisdictions. Either issue alone could undermine the MIT license. Beyond licensing, the rewrite raises questions about responsible use of AI in open source: key architectural ideas pioneered by charset-normalizer - notably decode-first validity filtering (our foundational approach since v1) and encoding pairwise similarity with the same algorithm and threshold — surfaced in chardet 7 without acknowledgment. The project also imported test files from charset-normalizer to train and benchmark against it, then claimed superior accuracy on those very files. Charset-normalizer has always been MIT-licensed, encoding-agnostic by design, and built on a verifiable human-authored history. + +## ⚡ Performance + +This package offer better performances (99th, and 95th) against Chardet. Here are some numbers. + +| Package | Accuracy | Mean per file (ms) | File per sec (est) | +|---------------------------------------------------|:--------:|:------------------:|:------------------:| +| [chardet 7.1](https://github.com/chardet/chardet) | 89 % | 3 ms | 333 file/sec | +| charset-normalizer | **97 %** | 3 ms | 333 file/sec | + +| Package | 99th percentile | 95th percentile | 50th percentile | +|---------------------------------------------------|:---------------:|:---------------:|:---------------:| +| [chardet 7.1](https://github.com/chardet/chardet) | 32 ms | 17 ms | < 1 ms | +| charset-normalizer | 16 ms | 10 ms | 1 ms | + +_updated as of March 2026 using CPython 3.12, Charset-Normalizer 3.4.6, and Chardet 7.1.0_ + +~Chardet's performance on larger file (1MB+) are very poor. Expect huge difference on large payload.~ No longer the case since Chardet 7.0+ + +> Stats are generated using 400+ files using default parameters. More details on used files, see GHA workflows. +> And yes, these results might change at any time. The dataset can be updated to include more files. +> The actual delays heavily depends on your CPU capabilities. The factors should remain the same. +> Chardet claims on his documentation to have a greater accuracy than us based on the dataset they trained Chardet on(...) +> Well, it's normal, the opposite would have been worrying. Whereas charset-normalizer don't train on anything, our solution +> is based on a completely different algorithm, still heuristic through, it does not need weights across every encoding tables. + +## ✨ Installation + +Using pip: + +```sh +pip install charset-normalizer -U +``` + +## 🚀 Basic Usage + +### CLI +This package comes with a CLI. + +``` +usage: normalizer [-h] [-v] [-a] [-n] [-m] [-r] [-f] [-t THRESHOLD] + file [file ...] + +The Real First Universal Charset Detector. Discover originating encoding used +on text file. Normalize text to unicode. + +positional arguments: + files File(s) to be analysed + +optional arguments: + -h, --help show this help message and exit + -v, --verbose Display complementary information about file if any. + Stdout will contain logs about the detection process. + -a, --with-alternative + Output complementary possibilities if any. Top-level + JSON WILL be a list. + -n, --normalize Permit to normalize input file. If not set, program + does not write anything. + -m, --minimal Only output the charset detected to STDOUT. Disabling + JSON output. + -r, --replace Replace file when trying to normalize it instead of + creating a new one. + -f, --force Replace file without asking if you are sure, use this + flag with caution. + -t THRESHOLD, --threshold THRESHOLD + Define a custom maximum amount of chaos allowed in + decoded content. 0. <= chaos <= 1. + --version Show version information and exit. +``` + +```bash +normalizer ./data/sample.1.fr.srt +``` + +or + +```bash +python -m charset_normalizer ./data/sample.1.fr.srt +``` + +🎉 Since version 1.4.0 the CLI produce easily usable stdout result in JSON format. + +```json +{ + "path": "/home/default/projects/charset_normalizer/data/sample.1.fr.srt", + "encoding": "cp1252", + "encoding_aliases": [ + "1252", + "windows_1252" + ], + "alternative_encodings": [ + "cp1254", + "cp1256", + "cp1258", + "iso8859_14", + "iso8859_15", + "iso8859_16", + "iso8859_3", + "iso8859_9", + "latin_1", + "mbcs" + ], + "language": "French", + "alphabets": [ + "Basic Latin", + "Latin-1 Supplement" + ], + "has_sig_or_bom": false, + "chaos": 0.149, + "coherence": 97.152, + "unicode_path": null, + "is_preferred": true +} +``` + +### Python +*Just print out normalized text* +```python +from charset_normalizer import from_path + +results = from_path('./my_subtitle.srt') + +print(str(results.best())) +``` + +*Upgrade your code without effort* +```python +from charset_normalizer import detect +``` + +The above code will behave the same as **chardet**. We ensure that we offer the best (reasonable) BC result possible. + +See the docs for advanced usage : [readthedocs.io](https://charset-normalizer.readthedocs.io/en/latest/) + +## 😇 Why + +When I started using Chardet, I noticed that it was not suited to my expectations, and I wanted to propose a +reliable alternative using a completely different method. Also! I never back down on a good challenge! + +I **don't care** about the **originating charset** encoding, because **two different tables** can +produce **two identical rendered string.** +What I want is to get readable text, the best I can. + +In a way, **I'm brute forcing text decoding.** How cool is that ? 😎 + +Don't confuse package **ftfy** with charset-normalizer or chardet. ftfy goal is to repair Unicode string whereas charset-normalizer to convert raw file in unknown encoding to unicode. + +## 🍰 How + + - Discard all charset encoding table that could not fit the binary content. + - Measure noise, or the mess once opened (by chunks) with a corresponding charset encoding. + - Extract matches with the lowest mess detected. + - Additionally, we measure coherence / probe for a language. + +**Wait a minute**, what is noise/mess and coherence according to **YOU ?** + +*Noise :* I opened hundred of text files, **written by humans**, with the wrong encoding table. **I observed**, then +**I established** some ground rules about **what is obvious** when **it seems like** a mess (aka. defining noise in rendered text). + I know that my interpretation of what is noise is probably incomplete, feel free to contribute in order to + improve or rewrite it. + +*Coherence :* For each language there is on earth, we have computed ranked letter appearance occurrences (the best we can). So I thought +that intel is worth something here. So I use those records against decoded text to check if I can detect intelligent design. + +## ⚡ Known limitations + + - Language detection is unreliable when text contains two or more languages sharing identical letters. (eg. HTML (english tags) + Turkish content (Sharing Latin characters)) + - Every charset detector heavily depends on sufficient content. In common cases, do not bother run detection on very tiny content. + +## ⚠️ About Python EOLs + +**If you are running:** + +- Python >=2.7,<3.5: Unsupported +- Python 3.5: charset-normalizer < 2.1 +- Python 3.6: charset-normalizer < 3.1 + +Upgrade your Python interpreter as soon as possible. + +## 👤 Contributing + +Contributions, issues and feature requests are very much welcome.
    +Feel free to check [issues page](https://github.com/ousret/charset_normalizer/issues) if you want to contribute. + +## 📝 License + +Copyright © [Ahmed TAHRI @Ousret](https://github.com/Ousret).
    +This project is [MIT](https://github.com/Ousret/charset_normalizer/blob/master/LICENSE) licensed. + +Characters frequencies used in this project © 2012 [Denny Vrandečić](http://simia.net/letters/) + +## 💼 For Enterprise + +Professional support for charset-normalizer is available as part of the [Tidelift +Subscription][1]. Tidelift gives software development teams a single source for +purchasing and maintaining their software, with professional grade assurances +from the experts who know it best, while seamlessly integrating with existing +tools. + +[1]: https://tidelift.com/subscription/pkg/pypi-charset-normalizer?utm_source=pypi-charset-normalizer&utm_medium=readme + +[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/7297/badge)](https://www.bestpractices.dev/projects/7297) + +# Changelog +All notable changes to charset-normalizer will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). + +## [3.4.7](https://github.com/Ousret/charset_normalizer/compare/3.4.6...3.4.7) (2026-04-02) + +### Changed +- Pre-built optimized version using mypy[c] v1.20. +- Relax `setuptools` constraint to `setuptools>=68,<82.1`. + +### Fixed +- Correctly remove SIG remnant in utf-7 decoded string. (#718) (#716) + +## [3.4.6](https://github.com/Ousret/charset_normalizer/compare/3.4.5...3.4.6) (2026-03-15) + +### Changed +- Flattened the logic in `charset_normalizer.md` for higher performance. Removed `eligible(..)` and `feed(...)` + in favor of `feed_info(...)`. +- Raised upper bound for mypy[c] to 1.20, for our optimized version. +- Updated `UNICODE_RANGES_COMBINED` using Unicode blocks v17. + +### Fixed +- Edge case where noise difference between two candidates can be almost insignificant. (#672) +- CLI `--normalize` writing to wrong path when passing multiple files in. (#702) + +### Misc +- Freethreaded pre-built wheels now shipped in PyPI starting with 3.14t. (#616) + +## [3.4.5](https://github.com/Ousret/charset_normalizer/compare/3.4.4...3.4.5) (2026-03-06) + +### Changed +- Update `setuptools` constraint to `setuptools>=68,<=82`. +- Raised upper bound of mypyc for the optional pre-built extension to v1.19.1 + +### Fixed +- Add explicit link to lib math in our optimized build. (#692) +- Logger level not restored correctly for empty byte sequences. (#701) +- TypeError when passing bytearray to from_bytes. (#703) + +### Misc +- Applied safe micro-optimizations in both our noise detector and language detector. +- Rewrote the `query_yes_no` function (inside CLI) to avoid using ambiguous licensed code. +- Added `cd.py` submodule into mypyc optional compilation to reduce further the performance impact. + +## [3.4.4](https://github.com/Ousret/charset_normalizer/compare/3.4.2...3.4.4) (2025-10-13) + +### Changed +- Bound `setuptools` to a specific constraint `setuptools>=68,<=81`. +- Raised upper bound of mypyc for the optional pre-built extension to v1.18.2 + +### Removed +- `setuptools-scm` as a build dependency. + +### Misc +- Enforced hashes in `dev-requirements.txt` and created `ci-requirements.txt` for security purposes. +- Additional pre-built wheels for riscv64, s390x, and armv7l architectures. +- Restore ` multiple.intoto.jsonl` in GitHub releases in addition to individual attestation file per wheel. + +## [3.4.3](https://github.com/Ousret/charset_normalizer/compare/3.4.2...3.4.3) (2025-08-09) + +### Changed +- mypy(c) is no longer a required dependency at build time if `CHARSET_NORMALIZER_USE_MYPYC` isn't set to `1`. (#595) (#583) +- automatically lower confidence on small bytes samples that are not Unicode in `detect` output legacy function. (#391) + +### Added +- Custom build backend to overcome inability to mark mypy as an optional dependency in the build phase. +- Support for Python 3.14 + +### Fixed +- sdist archive contained useless directories. +- automatically fallback on valid UTF-16 or UTF-32 even if the md says it's noisy. (#633) + +### Misc +- SBOM are automatically published to the relevant GitHub release to comply with regulatory changes. + Each published wheel comes with its SBOM. We choose CycloneDX as the format. +- Prebuilt optimized wheel are no longer distributed by default for CPython 3.7 due to a change in cibuildwheel. + +## [3.4.2](https://github.com/Ousret/charset_normalizer/compare/3.4.1...3.4.2) (2025-05-02) + +### Fixed +- Addressed the DeprecationWarning in our CLI regarding `argparse.FileType` by backporting the target class into the package. (#591) +- Improved the overall reliability of the detector with CJK Ideographs. (#605) (#587) + +### Changed +- Optional mypyc compilation upgraded to version 1.15 for Python >= 3.8 + +## [3.4.1](https://github.com/Ousret/charset_normalizer/compare/3.4.0...3.4.1) (2024-12-24) + +### Changed +- Project metadata are now stored using `pyproject.toml` instead of `setup.cfg` using setuptools as the build backend. +- Enforce annotation delayed loading for a simpler and consistent types in the project. +- Optional mypyc compilation upgraded to version 1.14 for Python >= 3.8 + +### Added +- pre-commit configuration. +- noxfile. + +### Removed +- `build-requirements.txt` as per using `pyproject.toml` native build configuration. +- `bin/integration.py` and `bin/serve.py` in favor of downstream integration test (see noxfile). +- `setup.cfg` in favor of `pyproject.toml` metadata configuration. +- Unused `utils.range_scan` function. + +### Fixed +- Converting content to Unicode bytes may insert `utf_8` instead of preferred `utf-8`. (#572) +- Deprecation warning "'count' is passed as positional argument" when converting to Unicode bytes on Python 3.13+ + +## [3.4.0](https://github.com/Ousret/charset_normalizer/compare/3.3.2...3.4.0) (2024-10-08) + +### Added +- Argument `--no-preemptive` in the CLI to prevent the detector to search for hints. +- Support for Python 3.13 (#512) + +### Fixed +- Relax the TypeError exception thrown when trying to compare a CharsetMatch with anything else than a CharsetMatch. +- Improved the general reliability of the detector based on user feedbacks. (#520) (#509) (#498) (#407) (#537) +- Declared charset in content (preemptive detection) not changed when converting to utf-8 bytes. (#381) + +## [3.3.2](https://github.com/Ousret/charset_normalizer/compare/3.3.1...3.3.2) (2023-10-31) + +### Fixed +- Unintentional memory usage regression when using large payload that match several encoding (#376) +- Regression on some detection case showcased in the documentation (#371) + +### Added +- Noise (md) probe that identify malformed arabic representation due to the presence of letters in isolated form (credit to my wife) + +## [3.3.1](https://github.com/Ousret/charset_normalizer/compare/3.3.0...3.3.1) (2023-10-22) + +### Changed +- Optional mypyc compilation upgraded to version 1.6.1 for Python >= 3.8 +- Improved the general detection reliability based on reports from the community + +## [3.3.0](https://github.com/Ousret/charset_normalizer/compare/3.2.0...3.3.0) (2023-09-30) + +### Added +- Allow to execute the CLI (e.g. normalizer) through `python -m charset_normalizer.cli` or `python -m charset_normalizer` +- Support for 9 forgotten encoding that are supported by Python but unlisted in `encoding.aliases` as they have no alias (#323) + +### Removed +- (internal) Redundant utils.is_ascii function and unused function is_private_use_only +- (internal) charset_normalizer.assets is moved inside charset_normalizer.constant + +### Changed +- (internal) Unicode code blocks in constants are updated using the latest v15.0.0 definition to improve detection +- Optional mypyc compilation upgraded to version 1.5.1 for Python >= 3.8 + +### Fixed +- Unable to properly sort CharsetMatch when both chaos/noise and coherence were close due to an unreachable condition in \_\_lt\_\_ (#350) + +## [3.2.0](https://github.com/Ousret/charset_normalizer/compare/3.1.0...3.2.0) (2023-06-07) + +### Changed +- Typehint for function `from_path` no longer enforce `PathLike` as its first argument +- Minor improvement over the global detection reliability + +### Added +- Introduce function `is_binary` that relies on main capabilities, and optimized to detect binaries +- Propagate `enable_fallback` argument throughout `from_bytes`, `from_path`, and `from_fp` that allow a deeper control over the detection (default True) +- Explicit support for Python 3.12 + +### Fixed +- Edge case detection failure where a file would contain 'very-long' camel cased word (Issue #289) + +## [3.1.0](https://github.com/Ousret/charset_normalizer/compare/3.0.1...3.1.0) (2023-03-06) + +### Added +- Argument `should_rename_legacy` for legacy function `detect` and disregard any new arguments without errors (PR #262) + +### Removed +- Support for Python 3.6 (PR #260) + +### Changed +- Optional speedup provided by mypy/c 1.0.1 + +## [3.0.1](https://github.com/Ousret/charset_normalizer/compare/3.0.0...3.0.1) (2022-11-18) + +### Fixed +- Multi-bytes cutter/chunk generator did not always cut correctly (PR #233) + +### Changed +- Speedup provided by mypy/c 0.990 on Python >= 3.7 + +## [3.0.0](https://github.com/Ousret/charset_normalizer/compare/2.1.1...3.0.0) (2022-10-20) + +### Added +- Extend the capability of explain=True when cp_isolation contains at most two entries (min one), will log in details of the Mess-detector results +- Support for alternative language frequency set in charset_normalizer.assets.FREQUENCIES +- Add parameter `language_threshold` in `from_bytes`, `from_path` and `from_fp` to adjust the minimum expected coherence ratio +- `normalizer --version` now specify if current version provide extra speedup (meaning mypyc compilation whl) + +### Changed +- Build with static metadata using 'build' frontend +- Make the language detection stricter +- Optional: Module `md.py` can be compiled using Mypyc to provide an extra speedup up to 4x faster than v2.1 + +### Fixed +- CLI with opt --normalize fail when using full path for files +- TooManyAccentuatedPlugin induce false positive on the mess detection when too few alpha character have been fed to it +- Sphinx warnings when generating the documentation + +### Removed +- Coherence detector no longer return 'Simple English' instead return 'English' +- Coherence detector no longer return 'Classical Chinese' instead return 'Chinese' +- Breaking: Method `first()` and `best()` from CharsetMatch +- UTF-7 will no longer appear as "detected" without a recognized SIG/mark (is unreliable/conflict with ASCII) +- Breaking: Class aliases CharsetDetector, CharsetDoctor, CharsetNormalizerMatch and CharsetNormalizerMatches +- Breaking: Top-level function `normalize` +- Breaking: Properties `chaos_secondary_pass`, `coherence_non_latin` and `w_counter` from CharsetMatch +- Support for the backport `unicodedata2` + +## [3.0.0rc1](https://github.com/Ousret/charset_normalizer/compare/3.0.0b2...3.0.0rc1) (2022-10-18) + +### Added +- Extend the capability of explain=True when cp_isolation contains at most two entries (min one), will log in details of the Mess-detector results +- Support for alternative language frequency set in charset_normalizer.assets.FREQUENCIES +- Add parameter `language_threshold` in `from_bytes`, `from_path` and `from_fp` to adjust the minimum expected coherence ratio + +### Changed +- Build with static metadata using 'build' frontend +- Make the language detection stricter + +### Fixed +- CLI with opt --normalize fail when using full path for files +- TooManyAccentuatedPlugin induce false positive on the mess detection when too few alpha character have been fed to it + +### Removed +- Coherence detector no longer return 'Simple English' instead return 'English' +- Coherence detector no longer return 'Classical Chinese' instead return 'Chinese' + +## [3.0.0b2](https://github.com/Ousret/charset_normalizer/compare/3.0.0b1...3.0.0b2) (2022-08-21) + +### Added +- `normalizer --version` now specify if current version provide extra speedup (meaning mypyc compilation whl) + +### Removed +- Breaking: Method `first()` and `best()` from CharsetMatch +- UTF-7 will no longer appear as "detected" without a recognized SIG/mark (is unreliable/conflict with ASCII) + +### Fixed +- Sphinx warnings when generating the documentation + +## [3.0.0b1](https://github.com/Ousret/charset_normalizer/compare/2.1.0...3.0.0b1) (2022-08-15) + +### Changed +- Optional: Module `md.py` can be compiled using Mypyc to provide an extra speedup up to 4x faster than v2.1 + +### Removed +- Breaking: Class aliases CharsetDetector, CharsetDoctor, CharsetNormalizerMatch and CharsetNormalizerMatches +- Breaking: Top-level function `normalize` +- Breaking: Properties `chaos_secondary_pass`, `coherence_non_latin` and `w_counter` from CharsetMatch +- Support for the backport `unicodedata2` + +## [2.1.1](https://github.com/Ousret/charset_normalizer/compare/2.1.0...2.1.1) (2022-08-19) + +### Deprecated +- Function `normalize` scheduled for removal in 3.0 + +### Changed +- Removed useless call to decode in fn is_unprintable (#206) + +### Fixed +- Third-party library (i18n xgettext) crashing not recognizing utf_8 (PEP 263) with underscore from [@aleksandernovikov](https://github.com/aleksandernovikov) (#204) + +## [2.1.0](https://github.com/Ousret/charset_normalizer/compare/2.0.12...2.1.0) (2022-06-19) + +### Added +- Output the Unicode table version when running the CLI with `--version` (PR #194) + +### Changed +- Re-use decoded buffer for single byte character sets from [@nijel](https://github.com/nijel) (PR #175) +- Fixing some performance bottlenecks from [@deedy5](https://github.com/deedy5) (PR #183) + +### Fixed +- Workaround potential bug in cpython with Zero Width No-Break Space located in Arabic Presentation Forms-B, Unicode 1.1 not acknowledged as space (PR #175) +- CLI default threshold aligned with the API threshold from [@oleksandr-kuzmenko](https://github.com/oleksandr-kuzmenko) (PR #181) + +### Removed +- Support for Python 3.5 (PR #192) + +### Deprecated +- Use of backport unicodedata from `unicodedata2` as Python is quickly catching up, scheduled for removal in 3.0 (PR #194) + +## [2.0.12](https://github.com/Ousret/charset_normalizer/compare/2.0.11...2.0.12) (2022-02-12) + +### Fixed +- ASCII miss-detection on rare cases (PR #170) + +## [2.0.11](https://github.com/Ousret/charset_normalizer/compare/2.0.10...2.0.11) (2022-01-30) + +### Added +- Explicit support for Python 3.11 (PR #164) + +### Changed +- The logging behavior have been completely reviewed, now using only TRACE and DEBUG levels (PR #163 #165) + +## [2.0.10](https://github.com/Ousret/charset_normalizer/compare/2.0.9...2.0.10) (2022-01-04) + +### Fixed +- Fallback match entries might lead to UnicodeDecodeError for large bytes sequence (PR #154) + +### Changed +- Skipping the language-detection (CD) on ASCII (PR #155) + +## [2.0.9](https://github.com/Ousret/charset_normalizer/compare/2.0.8...2.0.9) (2021-12-03) + +### Changed +- Moderating the logging impact (since 2.0.8) for specific environments (PR #147) + +### Fixed +- Wrong logging level applied when setting kwarg `explain` to True (PR #146) + +## [2.0.8](https://github.com/Ousret/charset_normalizer/compare/2.0.7...2.0.8) (2021-11-24) +### Changed +- Improvement over Vietnamese detection (PR #126) +- MD improvement on trailing data and long foreign (non-pure latin) data (PR #124) +- Efficiency improvements in cd/alphabet_languages from [@adbar](https://github.com/adbar) (PR #122) +- call sum() without an intermediary list following PEP 289 recommendations from [@adbar](https://github.com/adbar) (PR #129) +- Code style as refactored by Sourcery-AI (PR #131) +- Minor adjustment on the MD around european words (PR #133) +- Remove and replace SRTs from assets / tests (PR #139) +- Initialize the library logger with a `NullHandler` by default from [@nmaynes](https://github.com/nmaynes) (PR #135) +- Setting kwarg `explain` to True will add provisionally (bounded to function lifespan) a specific stream handler (PR #135) + +### Fixed +- Fix large (misleading) sequence giving UnicodeDecodeError (PR #137) +- Avoid using too insignificant chunk (PR #137) + +### Added +- Add and expose function `set_logging_handler` to configure a specific StreamHandler from [@nmaynes](https://github.com/nmaynes) (PR #135) +- Add `CHANGELOG.md` entries, format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) (PR #141) + +## [2.0.7](https://github.com/Ousret/charset_normalizer/compare/2.0.6...2.0.7) (2021-10-11) +### Added +- Add support for Kazakh (Cyrillic) language detection (PR #109) + +### Changed +- Further, improve inferring the language from a given single-byte code page (PR #112) +- Vainly trying to leverage PEP263 when PEP3120 is not supported (PR #116) +- Refactoring for potential performance improvements in loops from [@adbar](https://github.com/adbar) (PR #113) +- Various detection improvement (MD+CD) (PR #117) + +### Removed +- Remove redundant logging entry about detected language(s) (PR #115) + +### Fixed +- Fix a minor inconsistency between Python 3.5 and other versions regarding language detection (PR #117 #102) + +## [2.0.6](https://github.com/Ousret/charset_normalizer/compare/2.0.5...2.0.6) (2021-09-18) +### Fixed +- Unforeseen regression with the loss of the backward-compatibility with some older minor of Python 3.5.x (PR #100) +- Fix CLI crash when using --minimal output in certain cases (PR #103) + +### Changed +- Minor improvement to the detection efficiency (less than 1%) (PR #106 #101) + +## [2.0.5](https://github.com/Ousret/charset_normalizer/compare/2.0.4...2.0.5) (2021-09-14) +### Changed +- The project now comply with: flake8, mypy, isort and black to ensure a better overall quality (PR #81) +- The BC-support with v1.x was improved, the old staticmethods are restored (PR #82) +- The Unicode detection is slightly improved (PR #93) +- Add syntax sugar \_\_bool\_\_ for results CharsetMatches list-container (PR #91) + +### Removed +- The project no longer raise warning on tiny content given for detection, will be simply logged as warning instead (PR #92) + +### Fixed +- In some rare case, the chunks extractor could cut in the middle of a multi-byte character and could mislead the mess detection (PR #95) +- Some rare 'space' characters could trip up the UnprintablePlugin/Mess detection (PR #96) +- The MANIFEST.in was not exhaustive (PR #78) + +## [2.0.4](https://github.com/Ousret/charset_normalizer/compare/2.0.3...2.0.4) (2021-07-30) +### Fixed +- The CLI no longer raise an unexpected exception when no encoding has been found (PR #70) +- Fix accessing the 'alphabets' property when the payload contains surrogate characters (PR #68) +- The logger could mislead (explain=True) on detected languages and the impact of one MBCS match (PR #72) +- Submatch factoring could be wrong in rare edge cases (PR #72) +- Multiple files given to the CLI were ignored when publishing results to STDOUT. (After the first path) (PR #72) +- Fix line endings from CRLF to LF for certain project files (PR #67) + +### Changed +- Adjust the MD to lower the sensitivity, thus improving the global detection reliability (PR #69 #76) +- Allow fallback on specified encoding if any (PR #71) + +## [2.0.3](https://github.com/Ousret/charset_normalizer/compare/2.0.2...2.0.3) (2021-07-16) +### Changed +- Part of the detection mechanism has been improved to be less sensitive, resulting in more accurate detection results. Especially ASCII. (PR #63) +- According to the community wishes, the detection will fall back on ASCII or UTF-8 in a last-resort case. (PR #64) + +## [2.0.2](https://github.com/Ousret/charset_normalizer/compare/2.0.1...2.0.2) (2021-07-15) +### Fixed +- Empty/Too small JSON payload miss-detection fixed. Report from [@tseaver](https://github.com/tseaver) (PR #59) + +### Changed +- Don't inject unicodedata2 into sys.modules from [@akx](https://github.com/akx) (PR #57) + +## [2.0.1](https://github.com/Ousret/charset_normalizer/compare/2.0.0...2.0.1) (2021-07-13) +### Fixed +- Make it work where there isn't a filesystem available, dropping assets frequencies.json. Report from [@sethmlarson](https://github.com/sethmlarson). (PR #55) +- Using explain=False permanently disable the verbose output in the current runtime (PR #47) +- One log entry (language target preemptive) was not show in logs when using explain=True (PR #47) +- Fix undesired exception (ValueError) on getitem of instance CharsetMatches (PR #52) + +### Changed +- Public function normalize default args values were not aligned with from_bytes (PR #53) + +### Added +- You may now use charset aliases in cp_isolation and cp_exclusion arguments (PR #47) + +## [2.0.0](https://github.com/Ousret/charset_normalizer/compare/1.4.1...2.0.0) (2021-07-02) +### Changed +- 4x to 5 times faster than the previous 1.4.0 release. At least 2x faster than Chardet. +- Accent has been made on UTF-8 detection, should perform rather instantaneous. +- The backward compatibility with Chardet has been greatly improved. The legacy detect function returns an identical charset name whenever possible. +- The detection mechanism has been slightly improved, now Turkish content is detected correctly (most of the time) +- The program has been rewritten to ease the readability and maintainability. (+Using static typing)+ +- utf_7 detection has been reinstated. + +### Removed +- This package no longer require anything when used with Python 3.5 (Dropped cached_property) +- Removed support for these languages: Catalan, Esperanto, Kazakh, Baque, Volapük, Azeri, Galician, Nynorsk, Macedonian, and Serbocroatian. +- The exception hook on UnicodeDecodeError has been removed. + +### Deprecated +- Methods coherence_non_latin, w_counter, chaos_secondary_pass of the class CharsetMatch are now deprecated and scheduled for removal in v3.0 + +### Fixed +- The CLI output used the relative path of the file(s). Should be absolute. + +## [1.4.1](https://github.com/Ousret/charset_normalizer/compare/1.4.0...1.4.1) (2021-05-28) +### Fixed +- Logger configuration/usage no longer conflict with others (PR #44) + +## [1.4.0](https://github.com/Ousret/charset_normalizer/compare/1.3.9...1.4.0) (2021-05-21) +### Removed +- Using standard logging instead of using the package loguru. +- Dropping nose test framework in favor of the maintained pytest. +- Choose to not use dragonmapper package to help with gibberish Chinese/CJK text. +- Require cached_property only for Python 3.5 due to constraint. Dropping for every other interpreter version. +- Stop support for UTF-7 that does not contain a SIG. +- Dropping PrettyTable, replaced with pure JSON output in CLI. + +### Fixed +- BOM marker in a CharsetNormalizerMatch instance could be False in rare cases even if obviously present. Due to the sub-match factoring process. +- Not searching properly for the BOM when trying utf32/16 parent codec. + +### Changed +- Improving the package final size by compressing frequencies.json. +- Huge improvement over the larges payload. + +### Added +- CLI now produces JSON consumable output. +- Return ASCII if given sequences fit. Given reasonable confidence. + +## [1.3.9](https://github.com/Ousret/charset_normalizer/compare/1.3.8...1.3.9) (2021-05-13) + +### Fixed +- In some very rare cases, you may end up getting encode/decode errors due to a bad bytes payload (PR #40) + +## [1.3.8](https://github.com/Ousret/charset_normalizer/compare/1.3.7...1.3.8) (2021-05-12) + +### Fixed +- Empty given payload for detection may cause an exception if trying to access the `alphabets` property. (PR #39) + +## [1.3.7](https://github.com/Ousret/charset_normalizer/compare/1.3.6...1.3.7) (2021-05-12) + +### Fixed +- The legacy detect function should return UTF-8-SIG if sig is present in the payload. (PR #38) + +## [1.3.6](https://github.com/Ousret/charset_normalizer/compare/1.3.5...1.3.6) (2021-02-09) + +### Changed +- Amend the previous release to allow prettytable 2.0 (PR #35) + +## [1.3.5](https://github.com/Ousret/charset_normalizer/compare/1.3.4...1.3.5) (2021-02-08) + +### Fixed +- Fix error while using the package with a python pre-release interpreter (PR #33) + +### Changed +- Dependencies refactoring, constraints revised. + +### Added +- Add python 3.9 and 3.10 to the supported interpreters + +MIT License + +Copyright (c) 2025 TAHRI Ahmed R. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer-3.4.7.dist-info/RECORD b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer-3.4.7.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..53237232a0ecc85678ae33f14dbd16f0069c5fb5 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer-3.4.7.dist-info/RECORD @@ -0,0 +1,25 @@ +../../../bin/normalizer,sha256=QnDUvI82eFC19PZ0Q8DnHvjbqLuGqaRsOF4zLYVM2ik,350 +81d243bd2c585b0f4821__mypyc.cpython-314-x86_64-linux-gnu.so,sha256=Eoq-hHZwIu-gKwLFiL0uwZVcWqoi9v3GVa5pD5WS3sE,433360 +charset_normalizer-3.4.7.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2 +charset_normalizer-3.4.7.dist-info/METADATA,sha256=K8lK8L8LaZ1YmKvWLt3zEkpIxiCOC58xNhzFQrfQJxQ,40931 +charset_normalizer-3.4.7.dist-info/RECORD,, +charset_normalizer-3.4.7.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +charset_normalizer-3.4.7.dist-info/WHEEL,sha256=a6EB0SZmvnUxj7CP1sYpBVvB6TTNx-Vzw2sye5KrYcM,190 +charset_normalizer-3.4.7.dist-info/entry_points.txt,sha256=ADSTKrkXZ3hhdOVFi6DcUEHQRS0xfxDIE_pEz4wLIXA,65 +charset_normalizer-3.4.7.dist-info/licenses/LICENSE,sha256=bQ1Bv-FwrGx9wkjJpj4lTQ-0WmDVCoJX0K-SxuJJuIc,1071 +charset_normalizer-3.4.7.dist-info/top_level.txt,sha256=c_vZbitqecT2GfK3zdxSTLCn8C-6pGnHQY5o_5Y32M0,47 +charset_normalizer/__init__.py,sha256=OKRxRv2Zhnqk00tqkN0c1BtJjm165fWXLydE52IKuHc,1590 +charset_normalizer/__main__.py,sha256=yzYxMR-IhKRHYwcSlavEv8oGdwxsR89mr2X09qXGdps,109 +charset_normalizer/api.py,sha256=387F3n23MlMu-xfSbFULW2DLGsBmVrZVGhnkiGXeKBo,38844 +charset_normalizer/cd.cpython-314-x86_64-linux-gnu.so,sha256=-T9Bunt3lkMVS1l8kZ4yh236yozvPpBlw7WuRUEg-Xg,15912 +charset_normalizer/cd.py,sha256=v0iPJweGsRegXywrM1LzUgqW9bJ1KFvIblQHP1jm5FQ,15174 +charset_normalizer/cli/__init__.py,sha256=D8I86lFk2-py45JvqxniTirSj_sFyE6sjaY_0-G1shc,136 +charset_normalizer/cli/__main__.py,sha256=E9FFSV1E2iOE_B2B1tJHQT9ExJqc60Ks_c-08sNawh8,11940 +charset_normalizer/constant.py,sha256=yvLAWDrdSC743Cu4amhwHLIO-FGuRTOTZouCzZKGikc,44431 +charset_normalizer/legacy.py,sha256=yBIFMNABNPE5JkdKOWyVo36fZtV9nm8bf37LrDWulz8,2661 +charset_normalizer/md.cpython-314-x86_64-linux-gnu.so,sha256=h3N9skDXMMXSiDNC3DzjTBixkgUrvILRHKaFZLYDL_Y,15912 +charset_normalizer/md.py,sha256=AYCdfDX79FrgoId3zXqmbCuDcbGr1NRuGqgJN94Rx9Q,30441 +charset_normalizer/models.py,sha256=FbaQnI6ECmVmyHRSvVM5fHNeMAQ3KSGdwLjGcQqWDws,12821 +charset_normalizer/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +charset_normalizer/utils.py,sha256=9cpi-_0-vC9pGDfuoarhC6VlF_Jxwx5Jsa_8I4w2D8k,12282 +charset_normalizer/version.py,sha256=2LxFuGp3BBuIwt95cp64y7v8bCNHcMAi08IfXt_47Co,115 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer-3.4.7.dist-info/REQUESTED b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer-3.4.7.dist-info/REQUESTED new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer-3.4.7.dist-info/WHEEL b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer-3.4.7.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..ebc70a69c0555f6054ce8510274071383de66de8 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer-3.4.7.dist-info/WHEEL @@ -0,0 +1,7 @@ +Wheel-Version: 1.0 +Generator: setuptools (82.0.1) +Root-Is-Purelib: false +Tag: cp314-cp314-manylinux_2_17_x86_64 +Tag: cp314-cp314-manylinux2014_x86_64 +Tag: cp314-cp314-manylinux_2_28_x86_64 + diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer-3.4.7.dist-info/entry_points.txt b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer-3.4.7.dist-info/entry_points.txt new file mode 100644 index 0000000000000000000000000000000000000000..65619e73ec06c20c2a70c9507b872ad624d1a85c --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer-3.4.7.dist-info/entry_points.txt @@ -0,0 +1,2 @@ +[console_scripts] +normalizer = charset_normalizer.cli:cli_detect diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer-3.4.7.dist-info/licenses/LICENSE b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer-3.4.7.dist-info/licenses/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..9725772c7967075d97dc78d60f3735435eccba63 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer-3.4.7.dist-info/licenses/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 TAHRI Ahmed R. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer-3.4.7.dist-info/top_level.txt b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer-3.4.7.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..89847bead5357e0f635d338a19f78f50e41d2080 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer-3.4.7.dist-info/top_level.txt @@ -0,0 +1,2 @@ +81d243bd2c585b0f4821__mypyc +charset_normalizer diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..0d3a37990145e94ad85406166dbaf52f4c311e5e --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/__init__.py @@ -0,0 +1,48 @@ +""" +Charset-Normalizer +~~~~~~~~~~~~~~ +The Real First Universal Charset Detector. +A library that helps you read text from an unknown charset encoding. +Motivated by chardet, This package is trying to resolve the issue by taking a new approach. +All IANA character set names for which the Python core library provides codecs are supported. + +Basic usage: + >>> from charset_normalizer import from_bytes + >>> results = from_bytes('Bсеки човек има право на образование. Oбразованието!'.encode('utf_8')) + >>> best_guess = results.best() + >>> str(best_guess) + 'Bсеки човек има право на образование. Oбразованието!' + +Others methods and usages are available - see the full documentation +at . +:copyright: (c) 2021 by Ahmed TAHRI +:license: MIT, see LICENSE for more details. +""" + +from __future__ import annotations + +import logging + +from .api import from_bytes, from_fp, from_path, is_binary +from .legacy import detect +from .models import CharsetMatch, CharsetMatches +from .utils import set_logging_handler +from .version import VERSION, __version__ + +__all__ = ( + "from_fp", + "from_path", + "from_bytes", + "is_binary", + "detect", + "CharsetMatch", + "CharsetMatches", + "__version__", + "VERSION", + "set_logging_handler", +) + +# Attach a NullHandler to the top level logger by default +# https://docs.python.org/3.3/howto/logging.html#configuring-logging-for-a-library + +logging.getLogger("charset_normalizer").addHandler(logging.NullHandler()) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/__main__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/__main__.py new file mode 100644 index 0000000000000000000000000000000000000000..e0e76f7bfbb411d4424d3a1834b0ea803d80ea7e --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/__main__.py @@ -0,0 +1,6 @@ +from __future__ import annotations + +from .cli import cli_detect + +if __name__ == "__main__": + cli_detect() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/__pycache__/__init__.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ff86b5762bdd1276503f70987bf2f866bff155d5 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/__pycache__/__init__.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/__pycache__/api.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/__pycache__/api.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..41b8d56d662020f62c17eacbe38bc390718dc09d Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/__pycache__/api.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/__pycache__/constant.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/__pycache__/constant.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..403e1669ab05d279445e5d64abdd0d6e2166d5e7 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/__pycache__/constant.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/__pycache__/legacy.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/__pycache__/legacy.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a9edfdf63d281ad90ae744aca15f1ae063b32e64 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/__pycache__/legacy.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/__pycache__/models.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/__pycache__/models.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9a9ceb7875fd454c0defea2c5c627458e4b6ee32 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/__pycache__/models.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/__pycache__/utils.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/__pycache__/utils.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ee70f721c56c62598702e454bf36fe74da947fb4 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/__pycache__/utils.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/__pycache__/version.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/__pycache__/version.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a32f935e464c368352e711be6e6813c66c4e990a Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/__pycache__/version.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/api.py b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/api.py new file mode 100644 index 0000000000000000000000000000000000000000..50cb955092a9b9d1cf65004353d1d3748fd9b53e --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/api.py @@ -0,0 +1,988 @@ +from __future__ import annotations + +import logging +from os import PathLike +from typing import BinaryIO + +from .cd import ( + coherence_ratio, + encoding_languages, + mb_encoding_languages, + merge_coherence_ratios, +) +from .constant import ( + IANA_SUPPORTED, + IANA_SUPPORTED_SIMILAR, + TOO_BIG_SEQUENCE, + TOO_SMALL_SEQUENCE, + TRACE, +) +from .md import mess_ratio +from .models import CharsetMatch, CharsetMatches +from .utils import ( + any_specified_encoding, + cut_sequence_chunks, + iana_name, + identify_sig_or_bom, + is_multi_byte_encoding, + should_strip_sig_or_bom, +) + +logger = logging.getLogger("charset_normalizer") +explain_handler = logging.StreamHandler() +explain_handler.setFormatter( + logging.Formatter("%(asctime)s | %(levelname)s | %(message)s") +) + +# Pre-compute a reordered encoding list: multibyte first, then single-byte. +# This allows the mb_definitive_match optimization to fire earlier, skipping +# all single-byte encodings for genuine CJK content. Multibyte codecs +# hard-fail (UnicodeDecodeError) on single-byte data almost instantly, so +# testing them first costs negligible time for non-CJK files. +_mb_supported: list[str] = [] +_sb_supported: list[str] = [] + +for _supported_enc in IANA_SUPPORTED: + try: + if is_multi_byte_encoding(_supported_enc): + _mb_supported.append(_supported_enc) + else: + _sb_supported.append(_supported_enc) + except ImportError: + _sb_supported.append(_supported_enc) + +IANA_SUPPORTED_MB_FIRST: list[str] = _mb_supported + _sb_supported + + +def from_bytes( + sequences: bytes | bytearray, + steps: int = 5, + chunk_size: int = 512, + threshold: float = 0.2, + cp_isolation: list[str] | None = None, + cp_exclusion: list[str] | None = None, + preemptive_behaviour: bool = True, + explain: bool = False, + language_threshold: float = 0.1, + enable_fallback: bool = True, +) -> CharsetMatches: + """ + Given a raw bytes sequence, return the best possibles charset usable to render str objects. + If there is no results, it is a strong indicator that the source is binary/not text. + By default, the process will extract 5 blocks of 512o each to assess the mess and coherence of a given sequence. + And will give up a particular code page after 20% of measured mess. Those criteria are customizable at will. + + The preemptive behavior DOES NOT replace the traditional detection workflow, it prioritize a particular code page + but never take it for granted. Can improve the performance. + + You may want to focus your attention to some code page or/and not others, use cp_isolation and cp_exclusion for that + purpose. + + This function will strip the SIG in the payload/sequence every time except on UTF-16, UTF-32. + By default the library does not setup any handler other than the NullHandler, if you choose to set the 'explain' + toggle to True it will alter the logger configuration to add a StreamHandler that is suitable for debugging. + Custom logging format and handler can be set manually. + """ + + if not isinstance(sequences, (bytearray, bytes)): + raise TypeError( + "Expected object of type bytes or bytearray, got: {}".format( + type(sequences) + ) + ) + + if explain: + previous_logger_level: int = logger.level + logger.addHandler(explain_handler) + logger.setLevel(TRACE) + + length: int = len(sequences) + + if length == 0: + logger.debug("Encoding detection on empty bytes, assuming utf_8 intention.") + if explain: # Defensive: ensure exit path clean handler + logger.removeHandler(explain_handler) + logger.setLevel(previous_logger_level) + return CharsetMatches([CharsetMatch(sequences, "utf_8", 0.0, False, [], "")]) + + if cp_isolation is not None: + logger.log( + TRACE, + "cp_isolation is set. use this flag for debugging purpose. " + "limited list of encoding allowed : %s.", + ", ".join(cp_isolation), + ) + cp_isolation = [iana_name(cp, False) for cp in cp_isolation] + else: + cp_isolation = [] + + if cp_exclusion is not None: + logger.log( + TRACE, + "cp_exclusion is set. use this flag for debugging purpose. " + "limited list of encoding excluded : %s.", + ", ".join(cp_exclusion), + ) + cp_exclusion = [iana_name(cp, False) for cp in cp_exclusion] + else: + cp_exclusion = [] + + if length <= (chunk_size * steps): + logger.log( + TRACE, + "override steps (%i) and chunk_size (%i) as content does not fit (%i byte(s) given) parameters.", + steps, + chunk_size, + length, + ) + steps = 1 + chunk_size = length + + if steps > 1 and length / steps < chunk_size: + chunk_size = int(length / steps) + + is_too_small_sequence: bool = len(sequences) < TOO_SMALL_SEQUENCE + is_too_large_sequence: bool = len(sequences) >= TOO_BIG_SEQUENCE + + if is_too_small_sequence: + logger.log( + TRACE, + "Trying to detect encoding from a tiny portion of ({}) byte(s).".format( + length + ), + ) + elif is_too_large_sequence: + logger.log( + TRACE, + "Using lazy str decoding because the payload is quite large, ({}) byte(s).".format( + length + ), + ) + + prioritized_encodings: list[str] = [] + + specified_encoding: str | None = ( + any_specified_encoding(sequences) if preemptive_behaviour else None + ) + + if specified_encoding is not None: + prioritized_encodings.append(specified_encoding) + logger.log( + TRACE, + "Detected declarative mark in sequence. Priority +1 given for %s.", + specified_encoding, + ) + + tested: set[str] = set() + tested_but_hard_failure: list[str] = [] + tested_but_soft_failure: list[str] = [] + soft_failure_skip: set[str] = set() + success_fast_tracked: set[str] = set() + + # Cache for decoded payload deduplication: hash(decoded_payload) -> (mean_mess_ratio, cd_ratios_merged, passed) + # When multiple encodings decode to the exact same string, we can skip the expensive + # mess_ratio and coherence_ratio analysis and reuse the results from the first encoding. + payload_result_cache: dict[int, tuple[float, list[tuple[str, float]], bool]] = {} + + # When a definitive result (chaos=0.0 and good coherence) is found after testing + # the prioritized encodings (ascii, utf_8), we can significantly reduce the remaining + # work. Encodings that target completely different language families (e.g., Cyrillic + # when the definitive match is Latin) are skipped entirely. + # Additionally, for same-family encodings that pass chaos probing, we reuse the + # definitive match's coherence ratios instead of recomputing them — a major savings + # since coherence_ratio accounts for ~30% of total time on slow Latin files. + definitive_match_found: bool = False + definitive_target_languages: set[str] = set() + # After the definitive match fires, we cap the number of additional same-family + # single-byte encodings that pass chaos probing. Once we've accumulated enough + # good candidates (N), further same-family SB encodings are unlikely to produce + # a better best() result and just waste mess_ratio + coherence_ratio time. + # The first encoding to trigger the definitive match is NOT counted (it's already in). + post_definitive_sb_success_count: int = 0 + POST_DEFINITIVE_SB_CAP: int = 7 + + # When a non-UTF multibyte encoding passes chaos probing with significant multibyte + # content (decoded length < 98% of raw length), skip all remaining single-byte encodings. + # Rationale: multi-byte decoders (CJK) have strict byte-sequence validation — if they + # decode without error AND pass chaos probing with substantial multibyte content, the + # data is genuinely multibyte encoded. Single-byte encodings will always decode (every + # byte maps to something) but waste time on mess_ratio before failing. + # The 98% threshold prevents false triggers on files that happen to have a few valid + # multibyte pairs (e.g., cp424/_ude_1.txt where big5 decodes with 99% ratio). + mb_definitive_match_found: bool = False + + fallback_ascii: CharsetMatch | None = None + fallback_u8: CharsetMatch | None = None + fallback_specified: CharsetMatch | None = None + + results: CharsetMatches = CharsetMatches() + + early_stop_results: CharsetMatches = CharsetMatches() + + sig_encoding, sig_payload = identify_sig_or_bom(sequences) + + if sig_encoding is not None: + prioritized_encodings.append(sig_encoding) + logger.log( + TRACE, + "Detected a SIG or BOM mark on first %i byte(s). Priority +1 given for %s.", + len(sig_payload), + sig_encoding, + ) + + prioritized_encodings.append("ascii") + + if "utf_8" not in prioritized_encodings: + prioritized_encodings.append("utf_8") + + for encoding_iana in prioritized_encodings + IANA_SUPPORTED_MB_FIRST: + if cp_isolation and encoding_iana not in cp_isolation: + continue + + if cp_exclusion and encoding_iana in cp_exclusion: + continue + + if encoding_iana in tested: + continue + + tested.add(encoding_iana) + + decoded_payload: str | None = None + bom_or_sig_available: bool = sig_encoding == encoding_iana + strip_sig_or_bom: bool = bom_or_sig_available and should_strip_sig_or_bom( + encoding_iana + ) + + if encoding_iana in {"utf_16", "utf_32"} and not bom_or_sig_available: + logger.log( + TRACE, + "Encoding %s won't be tested as-is because it require a BOM. Will try some sub-encoder LE/BE.", + encoding_iana, + ) + continue + if encoding_iana in {"utf_7"} and not bom_or_sig_available: + logger.log( + TRACE, + "Encoding %s won't be tested as-is because detection is unreliable without BOM/SIG.", + encoding_iana, + ) + continue + + # Skip encodings similar to ones that already soft-failed (high mess ratio). + # Checked BEFORE the expensive decode attempt. + if encoding_iana in soft_failure_skip: + logger.log( + TRACE, + "%s is deemed too similar to a code page that was already considered unsuited. Continuing!", + encoding_iana, + ) + continue + + # Skip encodings that were already fast-tracked from a similar successful encoding. + if encoding_iana in success_fast_tracked: + logger.log( + TRACE, + "Skipping %s: already fast-tracked from a similar successful encoding.", + encoding_iana, + ) + continue + + try: + is_multi_byte_decoder: bool = is_multi_byte_encoding(encoding_iana) + except (ModuleNotFoundError, ImportError): # Defensive: + logger.log( + TRACE, + "Encoding %s does not provide an IncrementalDecoder", + encoding_iana, + ) + continue + + # When we've already found a definitive match (chaos=0.0 with good coherence) + # after testing the prioritized encodings, skip encodings that target + # completely different language families. This avoids running expensive + # mess_ratio + coherence_ratio on clearly unrelated candidates (e.g., Cyrillic + # when the definitive match is Latin-based). + if definitive_match_found: + if not is_multi_byte_decoder: + enc_languages = set(encoding_languages(encoding_iana)) + else: + enc_languages = set(mb_encoding_languages(encoding_iana)) + if not enc_languages.intersection(definitive_target_languages): + logger.log( + TRACE, + "Skipping %s: definitive match already found, this encoding targets different languages (%s vs %s).", + encoding_iana, + enc_languages, + definitive_target_languages, + ) + continue + + # After the definitive match, cap the number of additional same-family + # single-byte encodings that pass chaos probing. This avoids testing the + # tail of rare, low-value same-family encodings (mac_iceland, cp860, etc.) + # that almost never change best() but each cost ~1-2ms of mess_ratio + coherence. + if ( + definitive_match_found + and not is_multi_byte_decoder + and post_definitive_sb_success_count >= POST_DEFINITIVE_SB_CAP + ): + logger.log( + TRACE, + "Skipping %s: already accumulated %d same-family results after definitive match (cap=%d).", + encoding_iana, + post_definitive_sb_success_count, + POST_DEFINITIVE_SB_CAP, + ) + continue + + # When a multibyte encoding with significant multibyte content has already + # passed chaos probing, skip all single-byte encodings. They will either fail + # chaos probing (wasting mess_ratio time) or produce inferior results. + if mb_definitive_match_found and not is_multi_byte_decoder: + logger.log( + TRACE, + "Skipping single-byte %s: multi-byte definitive match already found.", + encoding_iana, + ) + continue + + try: + if is_too_large_sequence and is_multi_byte_decoder is False: + str( + ( + sequences[: int(50e4)] + if strip_sig_or_bom is False + else sequences[len(sig_payload) : int(50e4)] + ), + encoding=encoding_iana, + ) + else: + # UTF-7 BOM is encoded in modified Base64 whose byte boundary + # can overlap with the next character. Stripping raw SIG bytes + # before decoding may leave stray bytes that decode as garbage. + # Decode the full sequence and remove the leading BOM char instead. + # see https://github.com/jawah/charset_normalizer/issues/718 + # and https://github.com/jawah/charset_normalizer/issues/716 + if encoding_iana == "utf_7" and bom_or_sig_available: + decoded_payload = str( + sequences, + encoding=encoding_iana, + ) + if decoded_payload and decoded_payload[0] == "\ufeff": + decoded_payload = decoded_payload[1:] + else: + decoded_payload = str( + ( + sequences + if strip_sig_or_bom is False + else sequences[len(sig_payload) :] + ), + encoding=encoding_iana, + ) + except (UnicodeDecodeError, LookupError) as e: + if not isinstance(e, LookupError): + logger.log( + TRACE, + "Code page %s does not fit given bytes sequence at ALL. %s", + encoding_iana, + str(e), + ) + tested_but_hard_failure.append(encoding_iana) + continue + + r_ = range( + 0 if not bom_or_sig_available else len(sig_payload), + length, + int(length / steps), + ) + + multi_byte_bonus: bool = ( + is_multi_byte_decoder + and decoded_payload is not None + and len(decoded_payload) < length + ) + + if multi_byte_bonus: + logger.log( + TRACE, + "Code page %s is a multi byte encoding table and it appear that at least one character " + "was encoded using n-bytes.", + encoding_iana, + ) + + # Payload-hash deduplication: if another encoding already decoded to the + # exact same string, reuse its mess_ratio and coherence results entirely. + # This is strictly more general than the old IANA_SUPPORTED_SIMILAR approach + # because it catches ALL identical decoding, not just pre-mapped ones. + if decoded_payload is not None and not is_multi_byte_decoder: + payload_hash: int = hash(decoded_payload) + cached = payload_result_cache.get(payload_hash) + if cached is not None: + cached_mess, cached_cd, cached_passed = cached + if cached_passed: + # The previous encoding with identical output passed chaos probing. + fast_match = CharsetMatch( + sequences, + encoding_iana, + cached_mess, + bom_or_sig_available, + cached_cd, + ( + decoded_payload + if ( + is_too_large_sequence is False + or encoding_iana + in [specified_encoding, "ascii", "utf_8"] + ) + else None + ), + preemptive_declaration=specified_encoding, + ) + results.append(fast_match) + success_fast_tracked.add(encoding_iana) + logger.log( + TRACE, + "%s fast-tracked (identical decoded payload to a prior encoding, chaos=%f %%).", + encoding_iana, + round(cached_mess * 100, ndigits=3), + ) + + if ( + encoding_iana in [specified_encoding, "ascii", "utf_8"] + and cached_mess < 0.1 + ): + if cached_mess == 0.0: + logger.debug( + "Encoding detection: %s is most likely the one.", + fast_match.encoding, + ) + if explain: + logger.removeHandler(explain_handler) + logger.setLevel(previous_logger_level) + return CharsetMatches([fast_match]) + early_stop_results.append(fast_match) + + if ( + len(early_stop_results) + and (specified_encoding is None or specified_encoding in tested) + and "ascii" in tested + and "utf_8" in tested + ): + probable_result: CharsetMatch = early_stop_results.best() # type: ignore[assignment] + logger.debug( + "Encoding detection: %s is most likely the one.", + probable_result.encoding, + ) + if explain: + logger.removeHandler(explain_handler) + logger.setLevel(previous_logger_level) + return CharsetMatches([probable_result]) + + continue + else: + # The previous encoding with identical output failed chaos probing. + tested_but_soft_failure.append(encoding_iana) + logger.log( + TRACE, + "%s fast-skipped (identical decoded payload to a prior encoding that failed chaos probing).", + encoding_iana, + ) + # Prepare fallbacks for special encodings even when skipped. + if enable_fallback and encoding_iana in [ + "ascii", + "utf_8", + specified_encoding, + "utf_16", + "utf_32", + ]: + fallback_entry = CharsetMatch( + sequences, + encoding_iana, + threshold, + bom_or_sig_available, + [], + decoded_payload, + preemptive_declaration=specified_encoding, + ) + if encoding_iana == specified_encoding: + fallback_specified = fallback_entry + elif encoding_iana == "ascii": + fallback_ascii = fallback_entry + else: + fallback_u8 = fallback_entry + continue + + max_chunk_gave_up: int = int(len(r_) / 4) + + max_chunk_gave_up = max(max_chunk_gave_up, 2) + early_stop_count: int = 0 + lazy_str_hard_failure = False + + md_chunks: list[str] = [] + md_ratios = [] + + try: + for chunk in cut_sequence_chunks( + sequences, + encoding_iana, + r_, + chunk_size, + bom_or_sig_available, + strip_sig_or_bom, + sig_payload, + is_multi_byte_decoder, + decoded_payload, + ): + md_chunks.append(chunk) + + md_ratios.append( + mess_ratio( + chunk, + threshold, + explain is True and 1 <= len(cp_isolation) <= 2, + ) + ) + + if md_ratios[-1] >= threshold: + early_stop_count += 1 + + if (early_stop_count >= max_chunk_gave_up) or ( + bom_or_sig_available and strip_sig_or_bom is False + ): + break + except ( + UnicodeDecodeError + ) as e: # Lazy str loading may have missed something there + logger.log( + TRACE, + "LazyStr Loading: After MD chunk decode, code page %s does not fit given bytes sequence at ALL. %s", + encoding_iana, + str(e), + ) + early_stop_count = max_chunk_gave_up + lazy_str_hard_failure = True + + # We might want to check the sequence again with the whole content + # Only if initial MD tests passes + if ( + not lazy_str_hard_failure + and is_too_large_sequence + and not is_multi_byte_decoder + ): + try: + sequences[int(50e3) :].decode(encoding_iana, errors="strict") + except UnicodeDecodeError as e: + logger.log( + TRACE, + "LazyStr Loading: After final lookup, code page %s does not fit given bytes sequence at ALL. %s", + encoding_iana, + str(e), + ) + tested_but_hard_failure.append(encoding_iana) + continue + + mean_mess_ratio: float = sum(md_ratios) / len(md_ratios) if md_ratios else 0.0 + if mean_mess_ratio >= threshold or early_stop_count >= max_chunk_gave_up: + tested_but_soft_failure.append(encoding_iana) + if encoding_iana in IANA_SUPPORTED_SIMILAR: + soft_failure_skip.update(IANA_SUPPORTED_SIMILAR[encoding_iana]) + # Cache this soft-failure so identical decoding from other encodings + # can be skipped immediately. + if decoded_payload is not None and not is_multi_byte_decoder: + payload_result_cache.setdefault( + hash(decoded_payload), (mean_mess_ratio, [], False) + ) + logger.log( + TRACE, + "%s was excluded because of initial chaos probing. Gave up %i time(s). " + "Computed mean chaos is %f %%.", + encoding_iana, + early_stop_count, + round(mean_mess_ratio * 100, ndigits=3), + ) + # Preparing those fallbacks in case we got nothing. + if ( + enable_fallback + and encoding_iana + in ["ascii", "utf_8", specified_encoding, "utf_16", "utf_32"] + and not lazy_str_hard_failure + ): + fallback_entry = CharsetMatch( + sequences, + encoding_iana, + threshold, + bom_or_sig_available, + [], + decoded_payload, + preemptive_declaration=specified_encoding, + ) + if encoding_iana == specified_encoding: + fallback_specified = fallback_entry + elif encoding_iana == "ascii": + fallback_ascii = fallback_entry + else: + fallback_u8 = fallback_entry + continue + + logger.log( + TRACE, + "%s passed initial chaos probing. Mean measured chaos is %f %%", + encoding_iana, + round(mean_mess_ratio * 100, ndigits=3), + ) + + if not is_multi_byte_decoder: + target_languages: list[str] = encoding_languages(encoding_iana) + else: + target_languages = mb_encoding_languages(encoding_iana) + + if target_languages: + logger.log( + TRACE, + "{} should target any language(s) of {}".format( + encoding_iana, str(target_languages) + ), + ) + + cd_ratios = [] + + # Run coherence detection on all chunks. We previously tried limiting to + # 1-2 chunks for post-definitive encodings to save time, but this caused + # coverage regressions by producing unrepresentative coherence scores. + # The SB cap and language-family skip optimizations provide sufficient + # speedup without sacrificing coherence accuracy. + if encoding_iana != "ascii": + # We shall skip the CD when its about ASCII + # Most of the time its not relevant to run "language-detection" on it. + for chunk in md_chunks: + chunk_languages = coherence_ratio( + chunk, + language_threshold, + ",".join(target_languages) if target_languages else None, + ) + + cd_ratios.append(chunk_languages) + cd_ratios_merged = merge_coherence_ratios(cd_ratios) + else: + cd_ratios_merged = merge_coherence_ratios(cd_ratios) + + if cd_ratios_merged: + logger.log( + TRACE, + "We detected language {} using {}".format( + cd_ratios_merged, encoding_iana + ), + ) + + current_match = CharsetMatch( + sequences, + encoding_iana, + mean_mess_ratio, + bom_or_sig_available, + cd_ratios_merged, + ( + decoded_payload + if ( + is_too_large_sequence is False + or encoding_iana in [specified_encoding, "ascii", "utf_8"] + ) + else None + ), + preemptive_declaration=specified_encoding, + ) + + results.append(current_match) + + # Cache the successful result for payload-hash deduplication. + if decoded_payload is not None and not is_multi_byte_decoder: + payload_result_cache.setdefault( + hash(decoded_payload), + (mean_mess_ratio, cd_ratios_merged, True), + ) + + # Count post-definitive same-family SB successes for the early termination cap. + # Only count low-mess encodings (< 2%) toward the cap. High-mess encodings are + # marginal results that shouldn't prevent better-quality candidates from being + # tested. For example, iso8859_4 (mess=0%) should not be skipped just because + # 7 high-mess Latin encodings (cp1252 at 8%, etc.) were tried first. + if ( + definitive_match_found + and not is_multi_byte_decoder + and mean_mess_ratio < 0.02 + ): + post_definitive_sb_success_count += 1 + + if ( + encoding_iana in [specified_encoding, "ascii", "utf_8"] + and mean_mess_ratio < 0.1 + ): + # If md says nothing to worry about, then... stop immediately! + if mean_mess_ratio == 0.0: + logger.debug( + "Encoding detection: %s is most likely the one.", + current_match.encoding, + ) + if explain: # Defensive: ensure exit path clean handler + logger.removeHandler(explain_handler) + logger.setLevel(previous_logger_level) + return CharsetMatches([current_match]) + + early_stop_results.append(current_match) + + if ( + len(early_stop_results) + and (specified_encoding is None or specified_encoding in tested) + and "ascii" in tested + and "utf_8" in tested + ): + probable_result = early_stop_results.best() # type: ignore[assignment] + logger.debug( + "Encoding detection: %s is most likely the one.", + probable_result.encoding, # type: ignore[union-attr] + ) + if explain: # Defensive: ensure exit path clean handler + logger.removeHandler(explain_handler) + logger.setLevel(previous_logger_level) + + return CharsetMatches([probable_result]) + + # Once we find a result with good coherence (>= 0.5) after testing the + # prioritized encodings (ascii, utf_8), activate "definitive mode": skip + # encodings that target completely different language families. This avoids + # running expensive mess_ratio + coherence_ratio on clearly unrelated + # candidates (e.g., Cyrillic encodings when the match is Latin-based). + # We require coherence >= 0.5 to avoid false positives (e.g., cp1251 decoding + # Hebrew text with 0.0 chaos but wrong language detection at coherence 0.33). + if not definitive_match_found and not is_multi_byte_decoder: + best_coherence = ( + max((v for _, v in cd_ratios_merged), default=0.0) + if cd_ratios_merged + else 0.0 + ) + if best_coherence >= 0.5 and "ascii" in tested and "utf_8" in tested: + definitive_match_found = True + definitive_target_languages.update(target_languages) + logger.log( + TRACE, + "Definitive match found: %s (chaos=%.3f, coherence=%.2f). Encodings targeting different language families will be skipped.", + encoding_iana, + mean_mess_ratio, + best_coherence, + ) + + # When a non-UTF multibyte encoding passes chaos probing with significant + # multibyte content (decoded < 98% of raw), activate mb_definitive_match. + # This skips all remaining single-byte encodings which would either soft-fail + # (running expensive mess_ratio for nothing) or produce inferior results. + if ( + not mb_definitive_match_found + and is_multi_byte_decoder + and multi_byte_bonus + and decoded_payload is not None + and len(decoded_payload) < length * 0.98 + and encoding_iana + not in { + "utf_8", + "utf_8_sig", + "utf_16", + "utf_16_be", + "utf_16_le", + "utf_32", + "utf_32_be", + "utf_32_le", + "utf_7", + } + and "ascii" in tested + and "utf_8" in tested + ): + mb_definitive_match_found = True + logger.log( + TRACE, + "Multi-byte definitive match: %s (chaos=%.3f, decoded=%d/%d=%.1f%%). Single-byte encodings will be skipped.", + encoding_iana, + mean_mess_ratio, + len(decoded_payload), + length, + len(decoded_payload) / length * 100, + ) + + if encoding_iana == sig_encoding: + logger.debug( + "Encoding detection: %s is most likely the one as we detected a BOM or SIG within " + "the beginning of the sequence.", + encoding_iana, + ) + if explain: # Defensive: ensure exit path clean handler + logger.removeHandler(explain_handler) + logger.setLevel(previous_logger_level) + return CharsetMatches([results[encoding_iana]]) + + if len(results) == 0: + if fallback_u8 or fallback_ascii or fallback_specified: + logger.log( + TRACE, + "Nothing got out of the detection process. Using ASCII/UTF-8/Specified fallback.", + ) + + if fallback_specified: + logger.debug( + "Encoding detection: %s will be used as a fallback match", + fallback_specified.encoding, + ) + results.append(fallback_specified) + elif ( + (fallback_u8 and fallback_ascii is None) + or ( + fallback_u8 + and fallback_ascii + and fallback_u8.fingerprint != fallback_ascii.fingerprint + ) + or (fallback_u8 is not None) + ): + logger.debug("Encoding detection: utf_8 will be used as a fallback match") + results.append(fallback_u8) + elif fallback_ascii: + logger.debug("Encoding detection: ascii will be used as a fallback match") + results.append(fallback_ascii) + + if results: + logger.debug( + "Encoding detection: Found %s as plausible (best-candidate) for content. With %i alternatives.", + results.best().encoding, # type: ignore + len(results) - 1, + ) + else: + logger.debug("Encoding detection: Unable to determine any suitable charset.") + + if explain: + logger.removeHandler(explain_handler) + logger.setLevel(previous_logger_level) + + return results + + +def from_fp( + fp: BinaryIO, + steps: int = 5, + chunk_size: int = 512, + threshold: float = 0.20, + cp_isolation: list[str] | None = None, + cp_exclusion: list[str] | None = None, + preemptive_behaviour: bool = True, + explain: bool = False, + language_threshold: float = 0.1, + enable_fallback: bool = True, +) -> CharsetMatches: + """ + Same thing than the function from_bytes but using a file pointer that is already ready. + Will not close the file pointer. + """ + return from_bytes( + fp.read(), + steps, + chunk_size, + threshold, + cp_isolation, + cp_exclusion, + preemptive_behaviour, + explain, + language_threshold, + enable_fallback, + ) + + +def from_path( + path: str | bytes | PathLike, # type: ignore[type-arg] + steps: int = 5, + chunk_size: int = 512, + threshold: float = 0.20, + cp_isolation: list[str] | None = None, + cp_exclusion: list[str] | None = None, + preemptive_behaviour: bool = True, + explain: bool = False, + language_threshold: float = 0.1, + enable_fallback: bool = True, +) -> CharsetMatches: + """ + Same thing than the function from_bytes but with one extra step. Opening and reading given file path in binary mode. + Can raise IOError. + """ + with open(path, "rb") as fp: + return from_fp( + fp, + steps, + chunk_size, + threshold, + cp_isolation, + cp_exclusion, + preemptive_behaviour, + explain, + language_threshold, + enable_fallback, + ) + + +def is_binary( + fp_or_path_or_payload: PathLike | str | BinaryIO | bytes, # type: ignore[type-arg] + steps: int = 5, + chunk_size: int = 512, + threshold: float = 0.20, + cp_isolation: list[str] | None = None, + cp_exclusion: list[str] | None = None, + preemptive_behaviour: bool = True, + explain: bool = False, + language_threshold: float = 0.1, + enable_fallback: bool = False, +) -> bool: + """ + Detect if the given input (file, bytes, or path) points to a binary file. aka. not a string. + Based on the same main heuristic algorithms and default kwargs at the sole exception that fallbacks match + are disabled to be stricter around ASCII-compatible but unlikely to be a string. + """ + if isinstance(fp_or_path_or_payload, (str, PathLike)): + guesses = from_path( + fp_or_path_or_payload, + steps=steps, + chunk_size=chunk_size, + threshold=threshold, + cp_isolation=cp_isolation, + cp_exclusion=cp_exclusion, + preemptive_behaviour=preemptive_behaviour, + explain=explain, + language_threshold=language_threshold, + enable_fallback=enable_fallback, + ) + elif isinstance( + fp_or_path_or_payload, + ( + bytes, + bytearray, + ), + ): + guesses = from_bytes( + fp_or_path_or_payload, + steps=steps, + chunk_size=chunk_size, + threshold=threshold, + cp_isolation=cp_isolation, + cp_exclusion=cp_exclusion, + preemptive_behaviour=preemptive_behaviour, + explain=explain, + language_threshold=language_threshold, + enable_fallback=enable_fallback, + ) + else: + guesses = from_fp( + fp_or_path_or_payload, + steps=steps, + chunk_size=chunk_size, + threshold=threshold, + cp_isolation=cp_isolation, + cp_exclusion=cp_exclusion, + preemptive_behaviour=preemptive_behaviour, + explain=explain, + language_threshold=language_threshold, + enable_fallback=enable_fallback, + ) + + return not guesses diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/cd.cpython-314-x86_64-linux-gnu.so b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/cd.cpython-314-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..6472dd8dd3fcf2a8d11b1e36c960834b374e0877 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/cd.cpython-314-x86_64-linux-gnu.so differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/cd.py b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/cd.py new file mode 100644 index 0000000000000000000000000000000000000000..9545d35d1ec3f0c170dc6243325955db3856033b --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/cd.py @@ -0,0 +1,454 @@ +from __future__ import annotations + +import importlib +from codecs import IncrementalDecoder +from collections import Counter +from functools import lru_cache +from typing import Counter as TypeCounter + +from .constant import ( + FREQUENCIES, + KO_NAMES, + LANGUAGE_SUPPORTED_COUNT, + TOO_SMALL_SEQUENCE, + ZH_NAMES, + _FREQUENCIES_SET, + _FREQUENCIES_RANK, +) +from .md import is_suspiciously_successive_range +from .models import CoherenceMatches +from .utils import ( + is_accentuated, + is_latin, + is_multi_byte_encoding, + is_unicode_range_secondary, + unicode_range, +) + + +def encoding_unicode_range(iana_name: str) -> list[str]: + """ + Return associated unicode ranges in a single byte code page. + """ + if is_multi_byte_encoding(iana_name): + raise OSError( # Defensive: + "Function not supported on multi-byte code page" + ) + + decoder = importlib.import_module(f"encodings.{iana_name}").IncrementalDecoder + + p: IncrementalDecoder = decoder(errors="ignore") + seen_ranges: dict[str, int] = {} + character_count: int = 0 + + for i in range(0x40, 0xFF): + chunk: str = p.decode(bytes([i])) + + if chunk: + character_range: str | None = unicode_range(chunk) + + if character_range is None: + continue + + if is_unicode_range_secondary(character_range) is False: + if character_range not in seen_ranges: + seen_ranges[character_range] = 0 + seen_ranges[character_range] += 1 + character_count += 1 + + return sorted( + [ + character_range + for character_range in seen_ranges + if seen_ranges[character_range] / character_count >= 0.15 + ] + ) + + +def unicode_range_languages(primary_range: str) -> list[str]: + """ + Return inferred languages used with a unicode range. + """ + languages: list[str] = [] + + for language, characters in FREQUENCIES.items(): + for character in characters: + if unicode_range(character) == primary_range: + languages.append(language) + break + + return languages + + +@lru_cache() +def encoding_languages(iana_name: str) -> list[str]: + """ + Single-byte encoding language association. Some code page are heavily linked to particular language(s). + This function does the correspondence. + """ + unicode_ranges: list[str] = encoding_unicode_range(iana_name) + primary_range: str | None = None + + for specified_range in unicode_ranges: + if "Latin" not in specified_range: + primary_range = specified_range + break + + if primary_range is None: + return ["Latin Based"] + + return unicode_range_languages(primary_range) + + +@lru_cache() +def mb_encoding_languages(iana_name: str) -> list[str]: + """ + Multi-byte encoding language association. Some code page are heavily linked to particular language(s). + This function does the correspondence. + """ + if ( + iana_name.startswith("shift_") + or iana_name.startswith("iso2022_jp") + or iana_name.startswith("euc_j") + or iana_name == "cp932" + ): + return ["Japanese"] + if iana_name.startswith("gb") or iana_name in ZH_NAMES: + return ["Chinese"] + if iana_name.startswith("iso2022_kr") or iana_name in KO_NAMES: + return ["Korean"] + + return [] + + +@lru_cache(maxsize=LANGUAGE_SUPPORTED_COUNT) +def get_target_features(language: str) -> tuple[bool, bool]: + """ + Determine main aspects from a supported language if it contains accents and if is pure Latin. + """ + target_have_accents: bool = False + target_pure_latin: bool = True + + for character in FREQUENCIES[language]: + if not target_have_accents and is_accentuated(character): + target_have_accents = True + if target_pure_latin and is_latin(character) is False: + target_pure_latin = False + + return target_have_accents, target_pure_latin + + +def alphabet_languages( + characters: list[str], ignore_non_latin: bool = False +) -> list[str]: + """ + Return associated languages associated to given characters. + """ + languages: list[tuple[str, float]] = [] + + characters_set: frozenset[str] = frozenset(characters) + source_have_accents = any(is_accentuated(character) for character in characters) + + for language, language_characters in FREQUENCIES.items(): + target_have_accents, target_pure_latin = get_target_features(language) + + if ignore_non_latin and target_pure_latin is False: + continue + + if target_have_accents is False and source_have_accents: + continue + + character_count: int = len(language_characters) + + character_match_count: int = len(_FREQUENCIES_SET[language] & characters_set) + + ratio: float = character_match_count / character_count + + if ratio >= 0.2: + languages.append((language, ratio)) + + languages = sorted(languages, key=lambda x: x[1], reverse=True) + + return [compatible_language[0] for compatible_language in languages] + + +def characters_popularity_compare( + language: str, ordered_characters: list[str] +) -> float: + """ + Determine if a ordered characters list (by occurrence from most appearance to rarest) match a particular language. + The result is a ratio between 0. (absolutely no correspondence) and 1. (near perfect fit). + Beware that is function is not strict on the match in order to ease the detection. (Meaning close match is 1.) + """ + if language not in FREQUENCIES: + raise ValueError(f"{language} not available") # Defensive: + + character_approved_count: int = 0 + frequencies_language_set: frozenset[str] = _FREQUENCIES_SET[language] + lang_rank: dict[str, int] = _FREQUENCIES_RANK[language] + + ordered_characters_count: int = len(ordered_characters) + target_language_characters_count: int = len(FREQUENCIES[language]) + + large_alphabet: bool = target_language_characters_count > 26 + + expected_projection_ratio: float = ( + target_language_characters_count / ordered_characters_count + ) + + # Pre-built rank dict for ordered_characters (avoids repeated list slicing). + ordered_rank: dict[str, int] = { + char: rank for rank, char in enumerate(ordered_characters) + } + + # Pre-compute characters common to both orderings. + # Avoids repeated `c in ordered_rank` dict lookups in the inner counts. + common_chars: list[tuple[int, int]] = [ + (lr, ordered_rank[c]) for c, lr in lang_rank.items() if c in ordered_rank + ] + + # Pre-extract lr and orr arrays for faster iteration in the inner loop. + # Plain integer loops with local arrays are much faster under mypyc than + # generator expression sums over a list of tuples. + common_count: int = len(common_chars) + common_lr: list[int] = [p[0] for p in common_chars] + common_orr: list[int] = [p[1] for p in common_chars] + + for character, character_rank in zip( + ordered_characters, range(0, ordered_characters_count) + ): + if character not in frequencies_language_set: + continue + + character_rank_in_language: int = lang_rank[character] + character_rank_projection: int = int(character_rank * expected_projection_ratio) + + if ( + large_alphabet is False + and abs(character_rank_projection - character_rank_in_language) > 4 + ): + continue + + if ( + large_alphabet is True + and abs(character_rank_projection - character_rank_in_language) + < target_language_characters_count / 3 + ): + character_approved_count += 1 + continue + + # Count how many characters appear "before" in both orderings, + # and how many appear "at or after" in both orderings. + # Single pass over pre-extracted arrays — much faster under mypyc + # than two generator expression sums. + before_match_count: int = 0 + after_match_count: int = 0 + for i in range(common_count): + lr_i: int = common_lr[i] + orr_i: int = common_orr[i] + if lr_i < character_rank_in_language: + if orr_i < character_rank: + before_match_count += 1 + else: + if orr_i >= character_rank: + after_match_count += 1 + + after_len: int = target_language_characters_count - character_rank_in_language + + if character_rank_in_language == 0 and before_match_count <= 4: + character_approved_count += 1 + continue + + if after_len == 0 and after_match_count <= 4: + character_approved_count += 1 + continue + + if ( + character_rank_in_language > 0 + and before_match_count / character_rank_in_language >= 0.4 + ) or (after_len > 0 and after_match_count / after_len >= 0.4): + character_approved_count += 1 + continue + + return character_approved_count / len(ordered_characters) + + +def alpha_unicode_split(decoded_sequence: str) -> list[str]: + """ + Given a decoded text sequence, return a list of str. Unicode range / alphabet separation. + Ex. a text containing English/Latin with a bit a Hebrew will return two items in the resulting list; + One containing the latin letters and the other hebrew. + """ + layers: dict[str, list[str]] = {} + + # Fast path: track single-layer key to skip dict iteration for single-script text. + single_layer_key: str | None = None + multi_layer: bool = False + + # Cache the last character_range and its resolved layer to avoid repeated + # is_suspiciously_successive_range calls for consecutive same-range chars. + prev_character_range: str | None = None + prev_layer_target: str | None = None + + for character in decoded_sequence: + if character.isalpha() is False: + continue + + # ASCII fast-path: a-z and A-Z are always "Basic Latin". + # Avoids unicode_range() function call overhead for the most common case. + character_ord: int = ord(character) + if character_ord < 128: + character_range: str | None = "Basic Latin" + else: + character_range = unicode_range(character) + + if character_range is None: + continue + + # Fast path: same range as previous character → reuse cached layer target. + if character_range == prev_character_range: + if prev_layer_target is not None: + layers[prev_layer_target].append(character) + continue + + layer_target_range: str | None = None + + if multi_layer: + for discovered_range in layers: + if ( + is_suspiciously_successive_range(discovered_range, character_range) + is False + ): + layer_target_range = discovered_range + break + elif single_layer_key is not None: + if ( + is_suspiciously_successive_range(single_layer_key, character_range) + is False + ): + layer_target_range = single_layer_key + + if layer_target_range is None: + layer_target_range = character_range + + if layer_target_range not in layers: + layers[layer_target_range] = [] + if single_layer_key is None: + single_layer_key = layer_target_range + else: + multi_layer = True + + layers[layer_target_range].append(character) + + # Cache for next iteration + prev_character_range = character_range + prev_layer_target = layer_target_range + + return ["".join(chars).lower() for chars in layers.values()] + + +def merge_coherence_ratios(results: list[CoherenceMatches]) -> CoherenceMatches: + """ + This function merge results previously given by the function coherence_ratio. + The return type is the same as coherence_ratio. + """ + per_language_ratios: dict[str, list[float]] = {} + for result in results: + for sub_result in result: + language, ratio = sub_result + if language not in per_language_ratios: + per_language_ratios[language] = [ratio] + continue + per_language_ratios[language].append(ratio) + + merge = [ + ( + language, + round( + sum(per_language_ratios[language]) / len(per_language_ratios[language]), + 4, + ), + ) + for language in per_language_ratios + ] + + return sorted(merge, key=lambda x: x[1], reverse=True) + + +def filter_alt_coherence_matches(results: CoherenceMatches) -> CoherenceMatches: + """ + We shall NOT return "English—" in CoherenceMatches because it is an alternative + of "English". This function only keeps the best match and remove the em-dash in it. + """ + index_results: dict[str, list[float]] = dict() + + for result in results: + language, ratio = result + no_em_name: str = language.replace("—", "") + + if no_em_name not in index_results: + index_results[no_em_name] = [] + + index_results[no_em_name].append(ratio) + + if any(len(index_results[e]) > 1 for e in index_results): + filtered_results: CoherenceMatches = [] + + for language in index_results: + filtered_results.append((language, max(index_results[language]))) + + return filtered_results + + return results + + +@lru_cache(maxsize=2048) +def coherence_ratio( + decoded_sequence: str, threshold: float = 0.1, lg_inclusion: str | None = None +) -> CoherenceMatches: + """ + Detect ANY language that can be identified in given sequence. The sequence will be analysed by layers. + A layer = Character extraction by alphabets/ranges. + """ + + results: list[tuple[str, float]] = [] + ignore_non_latin: bool = False + + sufficient_match_count: int = 0 + + lg_inclusion_list = lg_inclusion.split(",") if lg_inclusion is not None else [] + if "Latin Based" in lg_inclusion_list: + ignore_non_latin = True + lg_inclusion_list.remove("Latin Based") + + for layer in alpha_unicode_split(decoded_sequence): + sequence_frequencies: TypeCounter[str] = Counter(layer) + most_common = sequence_frequencies.most_common() + + character_count: int = len(layer) + + if character_count <= TOO_SMALL_SEQUENCE: + continue + + popular_character_ordered: list[str] = [c for c, o in most_common] + + for language in lg_inclusion_list or alphabet_languages( + popular_character_ordered, ignore_non_latin + ): + ratio: float = characters_popularity_compare( + language, popular_character_ordered + ) + + if ratio < threshold: + continue + elif ratio >= 0.8: + sufficient_match_count += 1 + + results.append((language, round(ratio, 4))) + + if sufficient_match_count >= 3: + break + + return sorted( + filter_alt_coherence_matches(results), key=lambda x: x[1], reverse=True + ) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/cli/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/cli/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..543a5a4de49d07690e73df778aa580589d0789c6 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/cli/__init__.py @@ -0,0 +1,8 @@ +from __future__ import annotations + +from .__main__ import cli_detect, query_yes_no + +__all__ = ( + "cli_detect", + "query_yes_no", +) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/cli/__main__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/cli/__main__.py new file mode 100644 index 0000000000000000000000000000000000000000..ad843c1d0f2b8df7c467724a20f6457c68e49261 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/cli/__main__.py @@ -0,0 +1,362 @@ +from __future__ import annotations + +import argparse +import sys +import typing +from json import dumps +from os.path import abspath, basename, dirname, join, realpath +from platform import python_version +from unicodedata import unidata_version + +import charset_normalizer.md as md_module +from charset_normalizer import from_fp +from charset_normalizer.models import CliDetectionResult +from charset_normalizer.version import __version__ + + +def query_yes_no(question: str, default: str = "yes") -> bool: # Defensive: + """Ask a yes/no question via input() and return the answer as a bool.""" + prompt = " [Y/n] " if default == "yes" else " [y/N] " + + while True: + choice = input(question + prompt).strip().lower() + if not choice: + return default == "yes" + if choice in ("y", "yes"): + return True + if choice in ("n", "no"): + return False + print("Please respond with 'y' or 'n'.") + + +class FileType: + """Factory for creating file object types + + Instances of FileType are typically passed as type= arguments to the + ArgumentParser add_argument() method. + + Keyword Arguments: + - mode -- A string indicating how the file is to be opened. Accepts the + same values as the builtin open() function. + - bufsize -- The file's desired buffer size. Accepts the same values as + the builtin open() function. + - encoding -- The file's encoding. Accepts the same values as the + builtin open() function. + - errors -- A string indicating how encoding and decoding errors are to + be handled. Accepts the same value as the builtin open() function. + + Backported from CPython 3.12 + """ + + def __init__( + self, + mode: str = "r", + bufsize: int = -1, + encoding: str | None = None, + errors: str | None = None, + ): + self._mode = mode + self._bufsize = bufsize + self._encoding = encoding + self._errors = errors + + def __call__(self, string: str) -> typing.IO: # type: ignore[type-arg] + # the special argument "-" means sys.std{in,out} + if string == "-": + if "r" in self._mode: + return sys.stdin.buffer if "b" in self._mode else sys.stdin + elif any(c in self._mode for c in "wax"): + return sys.stdout.buffer if "b" in self._mode else sys.stdout + else: + msg = f'argument "-" with mode {self._mode}' + raise ValueError(msg) + + # all other arguments are used as file names + try: + return open(string, self._mode, self._bufsize, self._encoding, self._errors) + except OSError as e: + message = f"can't open '{string}': {e}" + raise argparse.ArgumentTypeError(message) + + def __repr__(self) -> str: + args = self._mode, self._bufsize + kwargs = [("encoding", self._encoding), ("errors", self._errors)] + args_str = ", ".join( + [repr(arg) for arg in args if arg != -1] + + [f"{kw}={arg!r}" for kw, arg in kwargs if arg is not None] + ) + return f"{type(self).__name__}({args_str})" + + +def cli_detect(argv: list[str] | None = None) -> int: + """ + CLI assistant using ARGV and ArgumentParser + :param argv: + :return: 0 if everything is fine, anything else equal trouble + """ + parser = argparse.ArgumentParser( + description="The Real First Universal Charset Detector. " + "Discover originating encoding used on text file. " + "Normalize text to unicode." + ) + + parser.add_argument( + "files", type=FileType("rb"), nargs="+", help="File(s) to be analysed" + ) + parser.add_argument( + "-v", + "--verbose", + action="store_true", + default=False, + dest="verbose", + help="Display complementary information about file if any. " + "Stdout will contain logs about the detection process.", + ) + parser.add_argument( + "-a", + "--with-alternative", + action="store_true", + default=False, + dest="alternatives", + help="Output complementary possibilities if any. Top-level JSON WILL be a list.", + ) + parser.add_argument( + "-n", + "--normalize", + action="store_true", + default=False, + dest="normalize", + help="Permit to normalize input file. If not set, program does not write anything.", + ) + parser.add_argument( + "-m", + "--minimal", + action="store_true", + default=False, + dest="minimal", + help="Only output the charset detected to STDOUT. Disabling JSON output.", + ) + parser.add_argument( + "-r", + "--replace", + action="store_true", + default=False, + dest="replace", + help="Replace file when trying to normalize it instead of creating a new one.", + ) + parser.add_argument( + "-f", + "--force", + action="store_true", + default=False, + dest="force", + help="Replace file without asking if you are sure, use this flag with caution.", + ) + parser.add_argument( + "-i", + "--no-preemptive", + action="store_true", + default=False, + dest="no_preemptive", + help="Disable looking at a charset declaration to hint the detector.", + ) + parser.add_argument( + "-t", + "--threshold", + action="store", + default=0.2, + type=float, + dest="threshold", + help="Define a custom maximum amount of noise allowed in decoded content. 0. <= noise <= 1.", + ) + parser.add_argument( + "--version", + action="version", + version="Charset-Normalizer {} - Python {} - Unicode {} - SpeedUp {}".format( + __version__, + python_version(), + unidata_version, + "OFF" if md_module.__file__.lower().endswith(".py") else "ON", + ), + help="Show version information and exit.", + ) + + args = parser.parse_args(argv) + + if args.replace is True and args.normalize is False: + if args.files: + for my_file in args.files: + my_file.close() + print("Use --replace in addition of --normalize only.", file=sys.stderr) + return 1 + + if args.force is True and args.replace is False: + if args.files: + for my_file in args.files: + my_file.close() + print("Use --force in addition of --replace only.", file=sys.stderr) + return 1 + + if args.threshold < 0.0 or args.threshold > 1.0: + if args.files: + for my_file in args.files: + my_file.close() + print("--threshold VALUE should be between 0. AND 1.", file=sys.stderr) + return 1 + + x_ = [] + + for my_file in args.files: + matches = from_fp( + my_file, + threshold=args.threshold, + explain=args.verbose, + preemptive_behaviour=args.no_preemptive is False, + ) + + best_guess = matches.best() + + if best_guess is None: + print( + 'Unable to identify originating encoding for "{}". {}'.format( + my_file.name, + ( + "Maybe try increasing maximum amount of chaos." + if args.threshold < 1.0 + else "" + ), + ), + file=sys.stderr, + ) + x_.append( + CliDetectionResult( + abspath(my_file.name), + None, + [], + [], + "Unknown", + [], + False, + 1.0, + 0.0, + None, + True, + ) + ) + else: + cli_result = CliDetectionResult( + abspath(my_file.name), + best_guess.encoding, + best_guess.encoding_aliases, + [ + cp + for cp in best_guess.could_be_from_charset + if cp != best_guess.encoding + ], + best_guess.language, + best_guess.alphabets, + best_guess.bom, + best_guess.percent_chaos, + best_guess.percent_coherence, + None, + True, + ) + x_.append(cli_result) + + if len(matches) > 1 and args.alternatives: + for el in matches: + if el != best_guess: + x_.append( + CliDetectionResult( + abspath(my_file.name), + el.encoding, + el.encoding_aliases, + [ + cp + for cp in el.could_be_from_charset + if cp != el.encoding + ], + el.language, + el.alphabets, + el.bom, + el.percent_chaos, + el.percent_coherence, + None, + False, + ) + ) + + if args.normalize is True: + if best_guess.encoding.startswith("utf") is True: + print( + '"{}" file does not need to be normalized, as it already came from unicode.'.format( + my_file.name + ), + file=sys.stderr, + ) + if my_file.closed is False: + my_file.close() + continue + + dir_path = dirname(realpath(my_file.name)) + file_name = basename(realpath(my_file.name)) + + o_: list[str] = file_name.split(".") + + if args.replace is False: + o_.insert(-1, best_guess.encoding) + if my_file.closed is False: + my_file.close() + elif ( + args.force is False + and query_yes_no( + 'Are you sure to normalize "{}" by replacing it ?'.format( + my_file.name + ), + "no", + ) + is False + ): + if my_file.closed is False: + my_file.close() + continue + + try: + cli_result.unicode_path = join(dir_path, ".".join(o_)) + + with open(cli_result.unicode_path, "wb") as fp: + fp.write(best_guess.output()) + except OSError as e: # Defensive: + print(str(e), file=sys.stderr) + if my_file.closed is False: + my_file.close() + return 2 + + if my_file.closed is False: + my_file.close() + + if args.minimal is False: + print( + dumps( + [el.__dict__ for el in x_] if len(x_) > 1 else x_[0].__dict__, + ensure_ascii=True, + indent=4, + ) + ) + else: + for my_file in args.files: + print( + ", ".join( + [ + el.encoding or "undefined" + for el in x_ + if el.path == abspath(my_file.name) + ] + ) + ) + + return 0 + + +if __name__ == "__main__": # Defensive: + cli_detect() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/constant.py b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/constant.py new file mode 100644 index 0000000000000000000000000000000000000000..e1297d2d4ed68f3a2cef1056d2d989846a52005f --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/constant.py @@ -0,0 +1,2050 @@ +from __future__ import annotations + +from codecs import BOM_UTF8, BOM_UTF16_BE, BOM_UTF16_LE, BOM_UTF32_BE, BOM_UTF32_LE +from encodings.aliases import aliases +from re import IGNORECASE +from re import compile as re_compile + +# Contain for each eligible encoding a list of/item bytes SIG/BOM +ENCODING_MARKS: dict[str, bytes | list[bytes]] = { + "utf_8": BOM_UTF8, + "utf_7": [ + b"\x2b\x2f\x76\x38\x2d", + b"\x2b\x2f\x76\x38", + b"\x2b\x2f\x76\x39", + b"\x2b\x2f\x76\x2b", + b"\x2b\x2f\x76\x2f", + ], + "gb18030": b"\x84\x31\x95\x33", + "utf_32": [BOM_UTF32_BE, BOM_UTF32_LE], + "utf_16": [BOM_UTF16_BE, BOM_UTF16_LE], +} + +TOO_SMALL_SEQUENCE: int = 32 +TOO_BIG_SEQUENCE: int = int(10e6) + +UTF8_MAXIMAL_ALLOCATION: int = 1_112_064 + +# Up-to-date Unicode ucd/17.0.0 +UNICODE_RANGES_COMBINED: dict[str, range] = { + "Control character": range(32), + "Basic Latin": range(32, 128), + "Latin-1 Supplement": range(128, 256), + "Latin Extended-A": range(256, 384), + "Latin Extended-B": range(384, 592), + "IPA Extensions": range(592, 688), + "Spacing Modifier Letters": range(688, 768), + "Combining Diacritical Marks": range(768, 880), + "Greek and Coptic": range(880, 1024), + "Cyrillic": range(1024, 1280), + "Cyrillic Supplement": range(1280, 1328), + "Armenian": range(1328, 1424), + "Hebrew": range(1424, 1536), + "Arabic": range(1536, 1792), + "Syriac": range(1792, 1872), + "Arabic Supplement": range(1872, 1920), + "Thaana": range(1920, 1984), + "NKo": range(1984, 2048), + "Samaritan": range(2048, 2112), + "Mandaic": range(2112, 2144), + "Syriac Supplement": range(2144, 2160), + "Arabic Extended-B": range(2160, 2208), + "Arabic Extended-A": range(2208, 2304), + "Devanagari": range(2304, 2432), + "Bengali": range(2432, 2560), + "Gurmukhi": range(2560, 2688), + "Gujarati": range(2688, 2816), + "Oriya": range(2816, 2944), + "Tamil": range(2944, 3072), + "Telugu": range(3072, 3200), + "Kannada": range(3200, 3328), + "Malayalam": range(3328, 3456), + "Sinhala": range(3456, 3584), + "Thai": range(3584, 3712), + "Lao": range(3712, 3840), + "Tibetan": range(3840, 4096), + "Myanmar": range(4096, 4256), + "Georgian": range(4256, 4352), + "Hangul Jamo": range(4352, 4608), + "Ethiopic": range(4608, 4992), + "Ethiopic Supplement": range(4992, 5024), + "Cherokee": range(5024, 5120), + "Unified Canadian Aboriginal Syllabics": range(5120, 5760), + "Ogham": range(5760, 5792), + "Runic": range(5792, 5888), + "Tagalog": range(5888, 5920), + "Hanunoo": range(5920, 5952), + "Buhid": range(5952, 5984), + "Tagbanwa": range(5984, 6016), + "Khmer": range(6016, 6144), + "Mongolian": range(6144, 6320), + "Unified Canadian Aboriginal Syllabics Extended": range(6320, 6400), + "Limbu": range(6400, 6480), + "Tai Le": range(6480, 6528), + "New Tai Lue": range(6528, 6624), + "Khmer Symbols": range(6624, 6656), + "Buginese": range(6656, 6688), + "Tai Tham": range(6688, 6832), + "Combining Diacritical Marks Extended": range(6832, 6912), + "Balinese": range(6912, 7040), + "Sundanese": range(7040, 7104), + "Batak": range(7104, 7168), + "Lepcha": range(7168, 7248), + "Ol Chiki": range(7248, 7296), + "Cyrillic Extended-C": range(7296, 7312), + "Georgian Extended": range(7312, 7360), + "Sundanese Supplement": range(7360, 7376), + "Vedic Extensions": range(7376, 7424), + "Phonetic Extensions": range(7424, 7552), + "Phonetic Extensions Supplement": range(7552, 7616), + "Combining Diacritical Marks Supplement": range(7616, 7680), + "Latin Extended Additional": range(7680, 7936), + "Greek Extended": range(7936, 8192), + "General Punctuation": range(8192, 8304), + "Superscripts and Subscripts": range(8304, 8352), + "Currency Symbols": range(8352, 8400), + "Combining Diacritical Marks for Symbols": range(8400, 8448), + "Letterlike Symbols": range(8448, 8528), + "Number Forms": range(8528, 8592), + "Arrows": range(8592, 8704), + "Mathematical Operators": range(8704, 8960), + "Miscellaneous Technical": range(8960, 9216), + "Control Pictures": range(9216, 9280), + "Optical Character Recognition": range(9280, 9312), + "Enclosed Alphanumerics": range(9312, 9472), + "Box Drawing": range(9472, 9600), + "Block Elements": range(9600, 9632), + "Geometric Shapes": range(9632, 9728), + "Miscellaneous Symbols": range(9728, 9984), + "Dingbats": range(9984, 10176), + "Miscellaneous Mathematical Symbols-A": range(10176, 10224), + "Supplemental Arrows-A": range(10224, 10240), + "Braille Patterns": range(10240, 10496), + "Supplemental Arrows-B": range(10496, 10624), + "Miscellaneous Mathematical Symbols-B": range(10624, 10752), + "Supplemental Mathematical Operators": range(10752, 11008), + "Miscellaneous Symbols and Arrows": range(11008, 11264), + "Glagolitic": range(11264, 11360), + "Latin Extended-C": range(11360, 11392), + "Coptic": range(11392, 11520), + "Georgian Supplement": range(11520, 11568), + "Tifinagh": range(11568, 11648), + "Ethiopic Extended": range(11648, 11744), + "Cyrillic Extended-A": range(11744, 11776), + "Supplemental Punctuation": range(11776, 11904), + "CJK Radicals Supplement": range(11904, 12032), + "Kangxi Radicals": range(12032, 12256), + "Ideographic Description Characters": range(12272, 12288), + "CJK Symbols and Punctuation": range(12288, 12352), + "Hiragana": range(12352, 12448), + "Katakana": range(12448, 12544), + "Bopomofo": range(12544, 12592), + "Hangul Compatibility Jamo": range(12592, 12688), + "Kanbun": range(12688, 12704), + "Bopomofo Extended": range(12704, 12736), + "CJK Strokes": range(12736, 12784), + "Katakana Phonetic Extensions": range(12784, 12800), + "Enclosed CJK Letters and Months": range(12800, 13056), + "CJK Compatibility": range(13056, 13312), + "CJK Unified Ideographs Extension A": range(13312, 19904), + "Yijing Hexagram Symbols": range(19904, 19968), + "CJK Unified Ideographs": range(19968, 40960), + "Yi Syllables": range(40960, 42128), + "Yi Radicals": range(42128, 42192), + "Lisu": range(42192, 42240), + "Vai": range(42240, 42560), + "Cyrillic Extended-B": range(42560, 42656), + "Bamum": range(42656, 42752), + "Modifier Tone Letters": range(42752, 42784), + "Latin Extended-D": range(42784, 43008), + "Syloti Nagri": range(43008, 43056), + "Common Indic Number Forms": range(43056, 43072), + "Phags-pa": range(43072, 43136), + "Saurashtra": range(43136, 43232), + "Devanagari Extended": range(43232, 43264), + "Kayah Li": range(43264, 43312), + "Rejang": range(43312, 43360), + "Hangul Jamo Extended-A": range(43360, 43392), + "Javanese": range(43392, 43488), + "Myanmar Extended-B": range(43488, 43520), + "Cham": range(43520, 43616), + "Myanmar Extended-A": range(43616, 43648), + "Tai Viet": range(43648, 43744), + "Meetei Mayek Extensions": range(43744, 43776), + "Ethiopic Extended-A": range(43776, 43824), + "Latin Extended-E": range(43824, 43888), + "Cherokee Supplement": range(43888, 43968), + "Meetei Mayek": range(43968, 44032), + "Hangul Syllables": range(44032, 55216), + "Hangul Jamo Extended-B": range(55216, 55296), + "High Surrogates": range(55296, 56192), + "High Private Use Surrogates": range(56192, 56320), + "Low Surrogates": range(56320, 57344), + "Private Use Area": range(57344, 63744), + "CJK Compatibility Ideographs": range(63744, 64256), + "Alphabetic Presentation Forms": range(64256, 64336), + "Arabic Presentation Forms-A": range(64336, 65024), + "Variation Selectors": range(65024, 65040), + "Vertical Forms": range(65040, 65056), + "Combining Half Marks": range(65056, 65072), + "CJK Compatibility Forms": range(65072, 65104), + "Small Form Variants": range(65104, 65136), + "Arabic Presentation Forms-B": range(65136, 65280), + "Halfwidth and Fullwidth Forms": range(65280, 65520), + "Specials": range(65520, 65536), + "Linear B Syllabary": range(65536, 65664), + "Linear B Ideograms": range(65664, 65792), + "Aegean Numbers": range(65792, 65856), + "Ancient Greek Numbers": range(65856, 65936), + "Ancient Symbols": range(65936, 66000), + "Phaistos Disc": range(66000, 66048), + "Lycian": range(66176, 66208), + "Carian": range(66208, 66272), + "Coptic Epact Numbers": range(66272, 66304), + "Old Italic": range(66304, 66352), + "Gothic": range(66352, 66384), + "Old Permic": range(66384, 66432), + "Ugaritic": range(66432, 66464), + "Old Persian": range(66464, 66528), + "Deseret": range(66560, 66640), + "Shavian": range(66640, 66688), + "Osmanya": range(66688, 66736), + "Osage": range(66736, 66816), + "Elbasan": range(66816, 66864), + "Caucasian Albanian": range(66864, 66928), + "Vithkuqi": range(66928, 67008), + "Todhri": range(67008, 67072), + "Linear A": range(67072, 67456), + "Latin Extended-F": range(67456, 67520), + "Cypriot Syllabary": range(67584, 67648), + "Imperial Aramaic": range(67648, 67680), + "Palmyrene": range(67680, 67712), + "Nabataean": range(67712, 67760), + "Hatran": range(67808, 67840), + "Phoenician": range(67840, 67872), + "Lydian": range(67872, 67904), + "Sidetic": range(67904, 67936), + "Meroitic Hieroglyphs": range(67968, 68000), + "Meroitic Cursive": range(68000, 68096), + "Kharoshthi": range(68096, 68192), + "Old South Arabian": range(68192, 68224), + "Old North Arabian": range(68224, 68256), + "Manichaean": range(68288, 68352), + "Avestan": range(68352, 68416), + "Inscriptional Parthian": range(68416, 68448), + "Inscriptional Pahlavi": range(68448, 68480), + "Psalter Pahlavi": range(68480, 68528), + "Old Turkic": range(68608, 68688), + "Old Hungarian": range(68736, 68864), + "Hanifi Rohingya": range(68864, 68928), + "Garay": range(68928, 69008), + "Rumi Numeral Symbols": range(69216, 69248), + "Yezidi": range(69248, 69312), + "Arabic Extended-C": range(69312, 69376), + "Old Sogdian": range(69376, 69424), + "Sogdian": range(69424, 69488), + "Old Uyghur": range(69488, 69552), + "Chorasmian": range(69552, 69600), + "Elymaic": range(69600, 69632), + "Brahmi": range(69632, 69760), + "Kaithi": range(69760, 69840), + "Sora Sompeng": range(69840, 69888), + "Chakma": range(69888, 69968), + "Mahajani": range(69968, 70016), + "Sharada": range(70016, 70112), + "Sinhala Archaic Numbers": range(70112, 70144), + "Khojki": range(70144, 70224), + "Multani": range(70272, 70320), + "Khudawadi": range(70320, 70400), + "Grantha": range(70400, 70528), + "Tulu-Tigalari": range(70528, 70656), + "Newa": range(70656, 70784), + "Tirhuta": range(70784, 70880), + "Siddham": range(71040, 71168), + "Modi": range(71168, 71264), + "Mongolian Supplement": range(71264, 71296), + "Takri": range(71296, 71376), + "Myanmar Extended-C": range(71376, 71424), + "Ahom": range(71424, 71504), + "Dogra": range(71680, 71760), + "Warang Citi": range(71840, 71936), + "Dives Akuru": range(71936, 72032), + "Nandinagari": range(72096, 72192), + "Zanabazar Square": range(72192, 72272), + "Soyombo": range(72272, 72368), + "Unified Canadian Aboriginal Syllabics Extended-A": range(72368, 72384), + "Pau Cin Hau": range(72384, 72448), + "Devanagari Extended-A": range(72448, 72544), + "Sharada Supplement": range(72544, 72576), + "Sunuwar": range(72640, 72704), + "Bhaiksuki": range(72704, 72816), + "Marchen": range(72816, 72896), + "Masaram Gondi": range(72960, 73056), + "Gunjala Gondi": range(73056, 73136), + "Tolong Siki": range(73136, 73200), + "Makasar": range(73440, 73472), + "Kawi": range(73472, 73568), + "Lisu Supplement": range(73648, 73664), + "Tamil Supplement": range(73664, 73728), + "Cuneiform": range(73728, 74752), + "Cuneiform Numbers and Punctuation": range(74752, 74880), + "Early Dynastic Cuneiform": range(74880, 75088), + "Cypro-Minoan": range(77712, 77824), + "Egyptian Hieroglyphs": range(77824, 78896), + "Egyptian Hieroglyph Format Controls": range(78896, 78944), + "Egyptian Hieroglyphs Extended-A": range(78944, 82944), + "Anatolian Hieroglyphs": range(82944, 83584), + "Gurung Khema": range(90368, 90432), + "Bamum Supplement": range(92160, 92736), + "Mro": range(92736, 92784), + "Tangsa": range(92784, 92880), + "Bassa Vah": range(92880, 92928), + "Pahawh Hmong": range(92928, 93072), + "Kirat Rai": range(93504, 93568), + "Medefaidrin": range(93760, 93856), + "Beria Erfe": range(93856, 93920), + "Miao": range(93952, 94112), + "Ideographic Symbols and Punctuation": range(94176, 94208), + "Tangut": range(94208, 100352), + "Tangut Components": range(100352, 101120), + "Khitan Small Script": range(101120, 101632), + "Tangut Supplement": range(101632, 101760), + "Tangut Components Supplement": range(101760, 101888), + "Kana Extended-B": range(110576, 110592), + "Kana Supplement": range(110592, 110848), + "Kana Extended-A": range(110848, 110896), + "Small Kana Extension": range(110896, 110960), + "Nushu": range(110960, 111360), + "Duployan": range(113664, 113824), + "Shorthand Format Controls": range(113824, 113840), + "Symbols for Legacy Computing Supplement": range(117760, 118464), + "Miscellaneous Symbols Supplement": range(118464, 118528), + "Znamenny Musical Notation": range(118528, 118736), + "Byzantine Musical Symbols": range(118784, 119040), + "Musical Symbols": range(119040, 119296), + "Ancient Greek Musical Notation": range(119296, 119376), + "Kaktovik Numerals": range(119488, 119520), + "Mayan Numerals": range(119520, 119552), + "Tai Xuan Jing Symbols": range(119552, 119648), + "Counting Rod Numerals": range(119648, 119680), + "Mathematical Alphanumeric Symbols": range(119808, 120832), + "Sutton SignWriting": range(120832, 121520), + "Latin Extended-G": range(122624, 122880), + "Glagolitic Supplement": range(122880, 122928), + "Cyrillic Extended-D": range(122928, 123024), + "Nyiakeng Puachue Hmong": range(123136, 123216), + "Toto": range(123536, 123584), + "Wancho": range(123584, 123648), + "Nag Mundari": range(124112, 124160), + "Ol Onal": range(124368, 124416), + "Tai Yo": range(124608, 124672), + "Ethiopic Extended-B": range(124896, 124928), + "Mende Kikakui": range(124928, 125152), + "Adlam": range(125184, 125280), + "Indic Siyaq Numbers": range(126064, 126144), + "Ottoman Siyaq Numbers": range(126208, 126288), + "Arabic Mathematical Alphabetic Symbols": range(126464, 126720), + "Mahjong Tiles": range(126976, 127024), + "Domino Tiles": range(127024, 127136), + "Playing Cards": range(127136, 127232), + "Enclosed Alphanumeric Supplement": range(127232, 127488), + "Enclosed Ideographic Supplement": range(127488, 127744), + "Miscellaneous Symbols and Pictographs": range(127744, 128512), + "Emoticons": range(128512, 128592), + "Ornamental Dingbats": range(128592, 128640), + "Transport and Map Symbols": range(128640, 128768), + "Alchemical Symbols": range(128768, 128896), + "Geometric Shapes Extended": range(128896, 129024), + "Supplemental Arrows-C": range(129024, 129280), + "Supplemental Symbols and Pictographs": range(129280, 129536), + "Chess Symbols": range(129536, 129648), + "Symbols and Pictographs Extended-A": range(129648, 129792), + "Symbols for Legacy Computing": range(129792, 130048), + "CJK Unified Ideographs Extension B": range(131072, 173792), + "CJK Unified Ideographs Extension C": range(173824, 177984), + "CJK Unified Ideographs Extension D": range(177984, 178208), + "CJK Unified Ideographs Extension E": range(178208, 183984), + "CJK Unified Ideographs Extension F": range(183984, 191472), + "CJK Unified Ideographs Extension I": range(191472, 192096), + "CJK Compatibility Ideographs Supplement": range(194560, 195104), + "CJK Unified Ideographs Extension G": range(196608, 201552), + "CJK Unified Ideographs Extension H": range(201552, 205744), + "CJK Unified Ideographs Extension J": range(205744, 210048), + "Tags": range(917504, 917632), + "Variation Selectors Supplement": range(917760, 918000), + "Supplementary Private Use Area-A": range(983040, 1048576), + "Supplementary Private Use Area-B": range(1048576, 1114112), +} + + +UNICODE_SECONDARY_RANGE_KEYWORD: list[str] = [ + "Supplement", + "Extended", + "Extensions", + "Modifier", + "Marks", + "Punctuation", + "Symbols", + "Forms", + "Operators", + "Miscellaneous", + "Drawing", + "Block", + "Shapes", + "Supplemental", + "Tags", +] + +RE_POSSIBLE_ENCODING_INDICATION = re_compile( + r"(?:(?:encoding)|(?:charset)|(?:coding))(?:[\:= ]{1,10})(?:[\"\']?)([a-zA-Z0-9\-_]+)(?:[\"\']?)", + IGNORECASE, +) + +IANA_NO_ALIASES = [ + "cp720", + "cp737", + "cp856", + "cp874", + "cp875", + "cp1006", + "koi8_r", + "koi8_t", + "koi8_u", +] + +IANA_SUPPORTED: list[str] = sorted( + filter( + lambda x: x.endswith("_codec") is False + and x not in {"rot_13", "tactis", "mbcs"}, + list(set(aliases.values())) + IANA_NO_ALIASES, + ) +) + +IANA_SUPPORTED_COUNT: int = len(IANA_SUPPORTED) + +# pre-computed code page that are similar using the function cp_similarity. +IANA_SUPPORTED_SIMILAR: dict[str, list[str]] = { + "cp037": ["cp1026", "cp1140", "cp273", "cp500"], + "cp1026": ["cp037", "cp1140", "cp273", "cp500"], + "cp1125": ["cp866"], + "cp1140": ["cp037", "cp1026", "cp273", "cp500"], + "cp1250": ["iso8859_2"], + "cp1251": ["kz1048", "ptcp154"], + "cp1252": ["iso8859_15", "iso8859_9", "latin_1"], + "cp1253": ["iso8859_7"], + "cp1254": ["iso8859_15", "iso8859_9", "latin_1"], + "cp1257": ["iso8859_13"], + "cp273": ["cp037", "cp1026", "cp1140", "cp500"], + "cp437": ["cp850", "cp858", "cp860", "cp861", "cp862", "cp863", "cp865"], + "cp500": ["cp037", "cp1026", "cp1140", "cp273"], + "cp850": ["cp437", "cp857", "cp858", "cp865"], + "cp857": ["cp850", "cp858", "cp865"], + "cp858": ["cp437", "cp850", "cp857", "cp865"], + "cp860": ["cp437", "cp861", "cp862", "cp863", "cp865"], + "cp861": ["cp437", "cp860", "cp862", "cp863", "cp865"], + "cp862": ["cp437", "cp860", "cp861", "cp863", "cp865"], + "cp863": ["cp437", "cp860", "cp861", "cp862", "cp865"], + "cp865": ["cp437", "cp850", "cp857", "cp858", "cp860", "cp861", "cp862", "cp863"], + "cp866": ["cp1125"], + "iso8859_10": ["iso8859_14", "iso8859_15", "iso8859_4", "iso8859_9", "latin_1"], + "iso8859_11": ["tis_620"], + "iso8859_13": ["cp1257"], + "iso8859_14": [ + "iso8859_10", + "iso8859_15", + "iso8859_16", + "iso8859_3", + "iso8859_9", + "latin_1", + ], + "iso8859_15": [ + "cp1252", + "cp1254", + "iso8859_10", + "iso8859_14", + "iso8859_16", + "iso8859_3", + "iso8859_9", + "latin_1", + ], + "iso8859_16": [ + "iso8859_14", + "iso8859_15", + "iso8859_2", + "iso8859_3", + "iso8859_9", + "latin_1", + ], + "iso8859_2": ["cp1250", "iso8859_16", "iso8859_4"], + "iso8859_3": ["iso8859_14", "iso8859_15", "iso8859_16", "iso8859_9", "latin_1"], + "iso8859_4": ["iso8859_10", "iso8859_2", "iso8859_9", "latin_1"], + "iso8859_7": ["cp1253"], + "iso8859_9": [ + "cp1252", + "cp1254", + "cp1258", + "iso8859_10", + "iso8859_14", + "iso8859_15", + "iso8859_16", + "iso8859_3", + "iso8859_4", + "latin_1", + ], + "kz1048": ["cp1251", "ptcp154"], + "latin_1": [ + "cp1252", + "cp1254", + "cp1258", + "iso8859_10", + "iso8859_14", + "iso8859_15", + "iso8859_16", + "iso8859_3", + "iso8859_4", + "iso8859_9", + ], + "mac_iceland": ["mac_roman", "mac_turkish"], + "mac_roman": ["mac_iceland", "mac_turkish"], + "mac_turkish": ["mac_iceland", "mac_roman"], + "ptcp154": ["cp1251", "kz1048"], + "tis_620": ["iso8859_11"], +} + + +CHARDET_CORRESPONDENCE: dict[str, str] = { + "iso2022_kr": "ISO-2022-KR", + "iso2022_jp": "ISO-2022-JP", + "euc_kr": "EUC-KR", + "tis_620": "TIS-620", + "utf_32": "UTF-32", + "euc_jp": "EUC-JP", + "koi8_r": "KOI8-R", + "iso8859_1": "ISO-8859-1", + "iso8859_2": "ISO-8859-2", + "iso8859_5": "ISO-8859-5", + "iso8859_6": "ISO-8859-6", + "iso8859_7": "ISO-8859-7", + "iso8859_8": "ISO-8859-8", + "utf_16": "UTF-16", + "cp855": "IBM855", + "mac_cyrillic": "MacCyrillic", + "gb2312": "GB2312", + "gb18030": "GB18030", + "cp932": "CP932", + "cp866": "IBM866", + "utf_8": "utf-8", + "utf_8_sig": "UTF-8-SIG", + "shift_jis": "SHIFT_JIS", + "big5": "Big5", + "cp1250": "windows-1250", + "cp1251": "windows-1251", + "cp1252": "Windows-1252", + "cp1253": "windows-1253", + "cp1255": "windows-1255", + "cp1256": "windows-1256", + "cp1254": "Windows-1254", + "cp949": "CP949", +} + + +COMMON_SAFE_ASCII_CHARACTERS: frozenset[str] = frozenset( + { + "<", + ">", + "=", + ":", + "/", + "&", + ";", + "{", + "}", + "[", + "]", + ",", + "|", + '"', + "-", + "(", + ")", + } +) + +# Sample character sets — replace with full lists if needed +COMMON_CHINESE_CHARACTERS = "的一是在不了有和人这中大为上个国我以要他时来用们生到作地于出就分对成会可主发年动同工也能下过子说产种面而方后多定行学法所民得经十三之进着等部度家电力里如水化高自二理起小物现实加量都两体制机当使点从业本去把性好应开它合还因由其些然前外天政四日那社义事平形相全表间样与关各重新线内数正心反你明看原又么利比或但质气第向道命此变条只没结解问意建月公无系军很情者最立代想已通并提直题党程展五果料象员革位入常文总次品式活设及管特件长求老头基资边流路级少图山统接知较将组见计别她手角期根论运农指几九区强放决西被干做必战先回则任取据处队南给色光门即保治北造百规热领七海口东导器压志世金增争济阶油思术极交受联什认六共权收证改清己美再采转更单风切打白教速花带安场身车例真务具万每目至达走积示议声报斗完类八离华名确才科张信马节话米整空元况今集温传土许步群广石记需段研界拉林律叫且究观越织装影算低持音众书布复容儿须际商非验连断深难近矿千周委素技备半办青省列习响约支般史感劳便团往酸历市克何除消构府太准精值号率族维划选标写存候毛亲快效斯院查江型眼王按格养易置派层片始却专状育厂京识适属圆包火住调满县局照参红细引听该铁价严龙飞" + +COMMON_JAPANESE_CHARACTERS = "日一国年大十二本中長出三時行見月分後前生五間上東四今金九入学高円子外八六下来気小七山話女北午百書先名川千水半男西電校語土木聞食車何南万毎白天母火右読友左休父雨" + +COMMON_KOREAN_CHARACTERS = "一二三四五六七八九十百千萬上下左右中人女子大小山川日月火水木金土父母天地國名年時文校學生" + +# Combine all into a frozenset +COMMON_CJK_CHARACTERS = frozenset( + "".join( + [ + COMMON_CHINESE_CHARACTERS, + COMMON_JAPANESE_CHARACTERS, + COMMON_KOREAN_CHARACTERS, + ] + ) +) + +KO_NAMES: frozenset[str] = frozenset({"johab", "cp949", "euc_kr"}) +ZH_NAMES: frozenset[str] = frozenset({"big5", "cp950", "big5hkscs", "hz"}) + +# Logging LEVEL below DEBUG +TRACE: int = 5 + + +# Language label that contain the em dash "—" +# character are to be considered alternative seq to origin +FREQUENCIES: dict[str, list[str]] = { + "English": [ + "e", + "a", + "t", + "i", + "o", + "n", + "s", + "r", + "h", + "l", + "d", + "c", + "u", + "m", + "f", + "p", + "g", + "w", + "y", + "b", + "v", + "k", + "x", + "j", + "z", + "q", + ], + "English—": [ + "e", + "a", + "t", + "i", + "o", + "n", + "s", + "r", + "h", + "l", + "d", + "c", + "m", + "u", + "f", + "p", + "g", + "w", + "b", + "y", + "v", + "k", + "j", + "x", + "z", + "q", + ], + "German": [ + "e", + "n", + "i", + "r", + "s", + "t", + "a", + "d", + "h", + "u", + "l", + "g", + "o", + "c", + "m", + "b", + "f", + "k", + "w", + "z", + "p", + "v", + "ü", + "ä", + "ö", + "j", + ], + "French": [ + "e", + "a", + "s", + "n", + "i", + "t", + "r", + "l", + "u", + "o", + "d", + "c", + "p", + "m", + "é", + "v", + "g", + "f", + "b", + "h", + "q", + "à", + "x", + "è", + "y", + "j", + ], + "Dutch": [ + "e", + "n", + "a", + "i", + "r", + "t", + "o", + "d", + "s", + "l", + "g", + "h", + "v", + "m", + "u", + "k", + "c", + "p", + "b", + "w", + "j", + "z", + "f", + "y", + "x", + "ë", + ], + "Italian": [ + "e", + "i", + "a", + "o", + "n", + "l", + "t", + "r", + "s", + "c", + "d", + "u", + "p", + "m", + "g", + "v", + "f", + "b", + "z", + "h", + "q", + "è", + "à", + "k", + "y", + "ò", + ], + "Polish": [ + "a", + "i", + "o", + "e", + "n", + "r", + "z", + "w", + "s", + "c", + "t", + "k", + "y", + "d", + "p", + "m", + "u", + "l", + "j", + "ł", + "g", + "b", + "h", + "ą", + "ę", + "ó", + ], + "Spanish": [ + "e", + "a", + "o", + "n", + "s", + "r", + "i", + "l", + "d", + "t", + "c", + "u", + "m", + "p", + "b", + "g", + "v", + "f", + "y", + "ó", + "h", + "q", + "í", + "j", + "z", + "á", + ], + "Russian": [ + "о", + "е", + "а", + "и", + "н", + "т", + "с", + "р", + "в", + "л", + "к", + "м", + "д", + "п", + "у", + "г", + "я", + "ы", + "з", + "б", + "й", + "ь", + "ч", + "х", + "ж", + "ц", + ], + # Jap-Kanji + "Japanese": [ + "日", + "一", + "人", + "年", + "大", + "十", + "二", + "本", + "中", + "長", + "出", + "三", + "時", + "行", + "見", + "月", + "分", + "後", + "前", + "生", + "五", + "間", + "上", + "東", + "四", + "今", + "金", + "九", + "入", + "学", + "高", + "円", + "子", + "外", + "八", + "六", + "下", + "来", + "気", + "小", + "七", + "山", + "話", + "女", + "北", + "午", + "百", + "書", + "先", + "名", + "川", + "千", + "水", + "半", + "男", + "西", + "電", + "校", + "語", + "土", + "木", + "聞", + "食", + "車", + "何", + "南", + "万", + "毎", + "白", + "天", + "母", + "火", + "右", + "読", + "友", + "左", + "休", + "父", + "雨", + ], + # Jap-Katakana + "Japanese—": [ + "ー", + "ン", + "ス", + "・", + "ル", + "ト", + "リ", + "イ", + "ア", + "ラ", + "ッ", + "ク", + "ド", + "シ", + "レ", + "ジ", + "タ", + "フ", + "ロ", + "カ", + "テ", + "マ", + "ィ", + "グ", + "バ", + "ム", + "プ", + "オ", + "コ", + "デ", + "ニ", + "ウ", + "メ", + "サ", + "ビ", + "ナ", + "ブ", + "ャ", + "エ", + "ュ", + "チ", + "キ", + "ズ", + "ダ", + "パ", + "ミ", + "ェ", + "ョ", + "ハ", + "セ", + "ベ", + "ガ", + "モ", + "ツ", + "ネ", + "ボ", + "ソ", + "ノ", + "ァ", + "ヴ", + "ワ", + "ポ", + "ペ", + "ピ", + "ケ", + "ゴ", + "ギ", + "ザ", + "ホ", + "ゲ", + "ォ", + "ヤ", + "ヒ", + "ユ", + "ヨ", + "ヘ", + "ゼ", + "ヌ", + "ゥ", + "ゾ", + "ヶ", + "ヂ", + "ヲ", + "ヅ", + "ヵ", + "ヱ", + "ヰ", + "ヮ", + "ヽ", + "゠", + "ヾ", + "ヷ", + "ヿ", + "ヸ", + "ヹ", + "ヺ", + ], + # Jap-Hiragana + "Japanese——": [ + "の", + "に", + "る", + "た", + "と", + "は", + "し", + "い", + "を", + "で", + "て", + "が", + "な", + "れ", + "か", + "ら", + "さ", + "っ", + "り", + "す", + "あ", + "も", + "こ", + "ま", + "う", + "く", + "よ", + "き", + "ん", + "め", + "お", + "け", + "そ", + "つ", + "だ", + "や", + "え", + "ど", + "わ", + "ち", + "み", + "せ", + "じ", + "ば", + "へ", + "び", + "ず", + "ろ", + "ほ", + "げ", + "む", + "べ", + "ひ", + "ょ", + "ゆ", + "ぶ", + "ご", + "ゃ", + "ね", + "ふ", + "ぐ", + "ぎ", + "ぼ", + "ゅ", + "づ", + "ざ", + "ぞ", + "ぬ", + "ぜ", + "ぱ", + "ぽ", + "ぷ", + "ぴ", + "ぃ", + "ぁ", + "ぇ", + "ぺ", + "ゞ", + "ぢ", + "ぉ", + "ぅ", + "ゐ", + "ゝ", + "ゑ", + "゛", + "゜", + "ゎ", + "ゔ", + "゚", + "ゟ", + "゙", + "ゕ", + "ゖ", + ], + "Portuguese": [ + "a", + "e", + "o", + "s", + "i", + "r", + "d", + "n", + "t", + "m", + "u", + "c", + "l", + "p", + "g", + "v", + "b", + "f", + "h", + "ã", + "q", + "é", + "ç", + "á", + "z", + "í", + ], + "Swedish": [ + "e", + "a", + "n", + "r", + "t", + "s", + "i", + "l", + "d", + "o", + "m", + "k", + "g", + "v", + "h", + "f", + "u", + "p", + "ä", + "c", + "b", + "ö", + "å", + "y", + "j", + "x", + ], + "Chinese": [ + "的", + "一", + "是", + "不", + "了", + "在", + "人", + "有", + "我", + "他", + "这", + "个", + "们", + "中", + "来", + "上", + "大", + "为", + "和", + "国", + "地", + "到", + "以", + "说", + "时", + "要", + "就", + "出", + "会", + "可", + "也", + "你", + "对", + "生", + "能", + "而", + "子", + "那", + "得", + "于", + "着", + "下", + "自", + "之", + "年", + "过", + "发", + "后", + "作", + "里", + "用", + "道", + "行", + "所", + "然", + "家", + "种", + "事", + "成", + "方", + "多", + "经", + "么", + "去", + "法", + "学", + "如", + "都", + "同", + "现", + "当", + "没", + "动", + "面", + "起", + "看", + "定", + "天", + "分", + "还", + "进", + "好", + "小", + "部", + "其", + "些", + "主", + "样", + "理", + "心", + "她", + "本", + "前", + "开", + "但", + "因", + "只", + "从", + "想", + "实", + ], + "Ukrainian": [ + "о", + "а", + "н", + "і", + "и", + "р", + "в", + "т", + "е", + "с", + "к", + "л", + "у", + "д", + "м", + "п", + "з", + "я", + "ь", + "б", + "г", + "й", + "ч", + "х", + "ц", + "ї", + ], + "Norwegian": [ + "e", + "r", + "n", + "t", + "a", + "s", + "i", + "o", + "l", + "d", + "g", + "k", + "m", + "v", + "f", + "p", + "u", + "b", + "h", + "å", + "y", + "j", + "ø", + "c", + "æ", + "w", + ], + "Finnish": [ + "a", + "i", + "n", + "t", + "e", + "s", + "l", + "o", + "u", + "k", + "ä", + "m", + "r", + "v", + "j", + "h", + "p", + "y", + "d", + "ö", + "g", + "c", + "b", + "f", + "w", + "z", + ], + "Vietnamese": [ + "n", + "h", + "t", + "i", + "c", + "g", + "a", + "o", + "u", + "m", + "l", + "r", + "à", + "đ", + "s", + "e", + "v", + "p", + "b", + "y", + "ư", + "d", + "á", + "k", + "ộ", + "ế", + ], + "Czech": [ + "o", + "e", + "a", + "n", + "t", + "s", + "i", + "l", + "v", + "r", + "k", + "d", + "u", + "m", + "p", + "í", + "c", + "h", + "z", + "á", + "y", + "j", + "b", + "ě", + "é", + "ř", + ], + "Hungarian": [ + "e", + "a", + "t", + "l", + "s", + "n", + "k", + "r", + "i", + "o", + "z", + "á", + "é", + "g", + "m", + "b", + "y", + "v", + "d", + "h", + "u", + "p", + "j", + "ö", + "f", + "c", + ], + "Korean": [ + "이", + "다", + "에", + "의", + "는", + "로", + "하", + "을", + "가", + "고", + "지", + "서", + "한", + "은", + "기", + "으", + "년", + "대", + "사", + "시", + "를", + "리", + "도", + "인", + "스", + "일", + ], + "Indonesian": [ + "a", + "n", + "e", + "i", + "r", + "t", + "u", + "s", + "d", + "k", + "m", + "l", + "g", + "p", + "b", + "o", + "h", + "y", + "j", + "c", + "w", + "f", + "v", + "z", + "x", + "q", + ], + "Turkish": [ + "a", + "e", + "i", + "n", + "r", + "l", + "ı", + "k", + "d", + "t", + "s", + "m", + "y", + "u", + "o", + "b", + "ü", + "ş", + "v", + "g", + "z", + "h", + "c", + "p", + "ç", + "ğ", + ], + "Romanian": [ + "e", + "i", + "a", + "r", + "n", + "t", + "u", + "l", + "o", + "c", + "s", + "d", + "p", + "m", + "ă", + "f", + "v", + "î", + "g", + "b", + "ș", + "ț", + "z", + "h", + "â", + "j", + ], + "Farsi": [ + "ا", + "ی", + "ر", + "د", + "ن", + "ه", + "و", + "م", + "ت", + "ب", + "س", + "ل", + "ک", + "ش", + "ز", + "ف", + "گ", + "ع", + "خ", + "ق", + "ج", + "آ", + "پ", + "ح", + "ط", + "ص", + ], + "Arabic": [ + "ا", + "ل", + "ي", + "م", + "و", + "ن", + "ر", + "ت", + "ب", + "ة", + "ع", + "د", + "س", + "ف", + "ه", + "ك", + "ق", + "أ", + "ح", + "ج", + "ش", + "ط", + "ص", + "ى", + "خ", + "إ", + ], + "Danish": [ + "e", + "r", + "n", + "t", + "a", + "i", + "s", + "d", + "l", + "o", + "g", + "m", + "k", + "f", + "v", + "u", + "b", + "h", + "p", + "å", + "y", + "ø", + "æ", + "c", + "j", + "w", + ], + "Serbian": [ + "а", + "и", + "о", + "е", + "н", + "р", + "с", + "у", + "т", + "к", + "ј", + "в", + "д", + "м", + "п", + "л", + "г", + "з", + "б", + "a", + "i", + "e", + "o", + "n", + "ц", + "ш", + ], + "Lithuanian": [ + "i", + "a", + "s", + "o", + "r", + "e", + "t", + "n", + "u", + "k", + "m", + "l", + "p", + "v", + "d", + "j", + "g", + "ė", + "b", + "y", + "ų", + "š", + "ž", + "c", + "ą", + "į", + ], + "Slovene": [ + "e", + "a", + "i", + "o", + "n", + "r", + "s", + "l", + "t", + "j", + "v", + "k", + "d", + "p", + "m", + "u", + "z", + "b", + "g", + "h", + "č", + "c", + "š", + "ž", + "f", + "y", + ], + "Slovak": [ + "o", + "a", + "e", + "n", + "i", + "r", + "v", + "t", + "s", + "l", + "k", + "d", + "m", + "p", + "u", + "c", + "h", + "j", + "b", + "z", + "á", + "y", + "ý", + "í", + "č", + "é", + ], + "Hebrew": [ + "י", + "ו", + "ה", + "ל", + "ר", + "ב", + "ת", + "מ", + "א", + "ש", + "נ", + "ע", + "ם", + "ד", + "ק", + "ח", + "פ", + "ס", + "כ", + "ג", + "ט", + "צ", + "ן", + "ז", + "ך", + ], + "Bulgarian": [ + "а", + "и", + "о", + "е", + "н", + "т", + "р", + "с", + "в", + "л", + "к", + "д", + "п", + "м", + "з", + "г", + "я", + "ъ", + "у", + "б", + "ч", + "ц", + "й", + "ж", + "щ", + "х", + ], + "Croatian": [ + "a", + "i", + "o", + "e", + "n", + "r", + "j", + "s", + "t", + "u", + "k", + "l", + "v", + "d", + "m", + "p", + "g", + "z", + "b", + "c", + "č", + "h", + "š", + "ž", + "ć", + "f", + ], + "Hindi": [ + "क", + "र", + "स", + "न", + "त", + "म", + "ह", + "प", + "य", + "ल", + "व", + "ज", + "द", + "ग", + "ब", + "श", + "ट", + "अ", + "ए", + "थ", + "भ", + "ड", + "च", + "ध", + "ष", + "इ", + ], + "Estonian": [ + "a", + "i", + "e", + "s", + "t", + "l", + "u", + "n", + "o", + "k", + "r", + "d", + "m", + "v", + "g", + "p", + "j", + "h", + "ä", + "b", + "õ", + "ü", + "f", + "c", + "ö", + "y", + ], + "Thai": [ + "า", + "น", + "ร", + "อ", + "ก", + "เ", + "ง", + "ม", + "ย", + "ล", + "ว", + "ด", + "ท", + "ส", + "ต", + "ะ", + "ป", + "บ", + "ค", + "ห", + "แ", + "จ", + "พ", + "ช", + "ข", + "ใ", + ], + "Greek": [ + "α", + "τ", + "ο", + "ι", + "ε", + "ν", + "ρ", + "σ", + "κ", + "η", + "π", + "ς", + "υ", + "μ", + "λ", + "ί", + "ό", + "ά", + "γ", + "έ", + "δ", + "ή", + "ω", + "χ", + "θ", + "ύ", + ], + "Tamil": [ + "க", + "த", + "ப", + "ட", + "ர", + "ம", + "ல", + "ன", + "வ", + "ற", + "ய", + "ள", + "ச", + "ந", + "இ", + "ண", + "அ", + "ஆ", + "ழ", + "ங", + "எ", + "உ", + "ஒ", + "ஸ", + ], + "Kazakh": [ + "а", + "ы", + "е", + "н", + "т", + "р", + "л", + "і", + "д", + "с", + "м", + "қ", + "к", + "о", + "б", + "и", + "у", + "ғ", + "ж", + "ң", + "з", + "ш", + "й", + "п", + "г", + "ө", + ], +} + +LANGUAGE_SUPPORTED_COUNT: int = len(FREQUENCIES) + +# Bit flags for unified character classification. +# A single unicodedata.name() call sets all relevant flags at once. +_LATIN: int = 1 +_ACCENTUATED: int = 1 << 1 +_CJK: int = 1 << 2 +_HANGUL: int = 1 << 3 +_KATAKANA: int = 1 << 4 +_HIRAGANA: int = 1 << 5 +_THAI: int = 1 << 6 +_ARABIC: int = 1 << 7 +_ARABIC_ISOLATED_FORM: int = 1 << 8 + +_ACCENT_KEYWORDS: tuple[str, ...] = ( + "WITH GRAVE", + "WITH ACUTE", + "WITH CEDILLA", + "WITH DIAERESIS", + "WITH CIRCUMFLEX", + "WITH TILDE", + "WITH MACRON", + "WITH RING ABOVE", +) + +# Pre-built lookup structures for FREQUENCIES (computed once at import time). +# character -> rank mapping per language (replaces list .index() calls). +_FREQUENCIES_RANK: dict[str, dict[str, int]] = { + lang: {char: rank for rank, char in enumerate(chars)} + for lang, chars in FREQUENCIES.items() +} + +# frozenset per language (avoids rebuilding set() per call). +_FREQUENCIES_SET: dict[str, frozenset[str]] = { + lang: frozenset(chars) for lang, chars in FREQUENCIES.items() +} diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/legacy.py b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/legacy.py new file mode 100644 index 0000000000000000000000000000000000000000..293c1efaf045559ea088e7991b318571a4ff71a8 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/legacy.py @@ -0,0 +1,79 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any +from warnings import warn + +from .api import from_bytes +from .constant import CHARDET_CORRESPONDENCE, TOO_SMALL_SEQUENCE + +if TYPE_CHECKING: + from typing import TypedDict + + class ResultDict(TypedDict): + encoding: str | None + language: str + confidence: float | None + + +def detect( + byte_str: bytes, should_rename_legacy: bool = False, **kwargs: Any +) -> ResultDict: + """ + chardet legacy method + Detect the encoding of the given byte string. It should be mostly backward-compatible. + Encoding name will match Chardet own writing whenever possible. (Not on encoding name unsupported by it) + This function is deprecated and should be used to migrate your project easily, consult the documentation for + further information. Not planned for removal. + + :param byte_str: The byte sequence to examine. + :param should_rename_legacy: Should we rename legacy encodings + to their more modern equivalents? + """ + if len(kwargs): + warn( + f"charset-normalizer disregard arguments '{','.join(list(kwargs.keys()))}' in legacy function detect()" + ) + + if not isinstance(byte_str, (bytearray, bytes)): + raise TypeError( # pragma: nocover + f"Expected object of type bytes or bytearray, got: {type(byte_str)}" + ) + + if isinstance(byte_str, bytearray): + byte_str = bytes(byte_str) + + r = from_bytes(byte_str).best() + + encoding = r.encoding if r is not None else None + language = r.language if r is not None and r.language != "Unknown" else "" + confidence = 1.0 - r.chaos if r is not None else None + + # automatically lower confidence + # on small bytes samples. + # https://github.com/jawah/charset_normalizer/issues/391 + if ( + confidence is not None + and confidence >= 0.9 + and encoding + not in { + "utf_8", + "ascii", + } + and r.bom is False # type: ignore[union-attr] + and len(byte_str) < TOO_SMALL_SEQUENCE + ): + confidence -= 0.2 + + # Note: CharsetNormalizer does not return 'UTF-8-SIG' as the sig get stripped in the detection/normalization process + # but chardet does return 'utf-8-sig' and it is a valid codec name. + if r is not None and encoding == "utf_8" and r.bom: + encoding += "_sig" + + if should_rename_legacy is False and encoding in CHARDET_CORRESPONDENCE: + encoding = CHARDET_CORRESPONDENCE[encoding] + + return { + "encoding": encoding, + "language": language, + "confidence": confidence, + } diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/md.cpython-314-x86_64-linux-gnu.so b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/md.cpython-314-x86_64-linux-gnu.so new file mode 100644 index 0000000000000000000000000000000000000000..59e2930857b98da828242083bede8f8215e72359 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/md.cpython-314-x86_64-linux-gnu.so differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/md.py b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/md.py new file mode 100644 index 0000000000000000000000000000000000000000..b41d9cfc575d5ea2dd0dab5b0e4350b8485bd70b --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/md.py @@ -0,0 +1,936 @@ +from __future__ import annotations + +import sys +from functools import lru_cache +from logging import getLogger + +if sys.version_info >= (3, 8): + from typing import final +else: + try: + from typing_extensions import final + except ImportError: + + def final(cls): # type: ignore[misc,no-untyped-def] + return cls + + +from .constant import ( + COMMON_CJK_CHARACTERS, + COMMON_SAFE_ASCII_CHARACTERS, + TRACE, + UNICODE_SECONDARY_RANGE_KEYWORD, + _ACCENTUATED, + _ARABIC, + _ARABIC_ISOLATED_FORM, + _CJK, + _HANGUL, + _HIRAGANA, + _KATAKANA, + _LATIN, + _THAI, +) +from .utils import ( + _character_flags, + is_emoticon, + is_punctuation, + is_separator, + is_symbol, + remove_accent, + unicode_range, +) + +# Combined bitmask for CJK/Hangul/Katakana/Hiragana/Thai glyph detection. +_GLYPH_MASK: int = _CJK | _HANGUL | _KATAKANA | _HIRAGANA | _THAI + + +@final +class CharInfo: + """Pre-computed character properties shared across all detectors. + + Instantiated once and reused via :meth:`update` on every character + in the hot loop so that redundant calls to str methods + (``isalpha``, ``isupper``, …) and cached utility functions + (``_character_flags``, ``is_punctuation``, …) are avoided when + several plugins need the same information. + """ + + __slots__ = ( + "character", + "printable", + "alpha", + "upper", + "lower", + "space", + "digit", + "is_ascii", + "case_variable", + "flags", + "accentuated", + "latin", + "is_cjk", + "is_arabic", + "is_glyph", + "punct", + "sym", + ) + + def __init__(self) -> None: + self.character: str = "" + self.printable: bool = False + self.alpha: bool = False + self.upper: bool = False + self.lower: bool = False + self.space: bool = False + self.digit: bool = False + self.is_ascii: bool = False + self.case_variable: bool = False + self.flags: int = 0 + self.accentuated: bool = False + self.latin: bool = False + self.is_cjk: bool = False + self.is_arabic: bool = False + self.is_glyph: bool = False + self.punct: bool = False + self.sym: bool = False + + def update(self, character: str) -> None: + """Update all properties for *character* (called once per character).""" + self.character = character + + # ASCII fast-path: for characters with ord < 128, we can skip + # _character_flags() entirely and derive most properties from ord. + o: int = ord(character) + if o < 128: + self.is_ascii = True + self.accentuated = False + self.is_cjk = False + self.is_arabic = False + self.is_glyph = False + # ASCII alpha: a-z (97-122) or A-Z (65-90) + if 65 <= o <= 90: + # Uppercase ASCII letter + self.alpha = True + self.upper = True + self.lower = False + self.space = False + self.digit = False + self.printable = True + self.case_variable = True + self.flags = _LATIN + self.latin = True + self.punct = False + self.sym = False + elif 97 <= o <= 122: + # Lowercase ASCII letter + self.alpha = True + self.upper = False + self.lower = True + self.space = False + self.digit = False + self.printable = True + self.case_variable = True + self.flags = _LATIN + self.latin = True + self.punct = False + self.sym = False + elif 48 <= o <= 57: + # ASCII digit 0-9 + self.alpha = False + self.upper = False + self.lower = False + self.space = False + self.digit = True + self.printable = True + self.case_variable = False + self.flags = 0 + self.latin = False + self.punct = False + self.sym = False + elif o == 32 or (9 <= o <= 13): + # Space, tab, newline, etc. + self.alpha = False + self.upper = False + self.lower = False + self.space = True + self.digit = False + self.printable = o == 32 + self.case_variable = False + self.flags = 0 + self.latin = False + self.punct = False + self.sym = False + else: + # Other ASCII (punctuation, symbols, control chars) + self.printable = character.isprintable() + self.alpha = False + self.upper = False + self.lower = False + self.space = False + self.digit = False + self.case_variable = False + self.flags = 0 + self.latin = False + self.punct = is_punctuation(character) if self.printable else False + self.sym = is_symbol(character) if self.printable else False + else: + # Non-ASCII path + self.is_ascii = False + self.printable = character.isprintable() + self.alpha = character.isalpha() + self.upper = character.isupper() + self.lower = character.islower() + self.space = character.isspace() + self.digit = character.isdigit() + self.case_variable = self.lower != self.upper + + # Flag-based classification (single unicodedata.name() call, lru-cached) + flags: int + if self.alpha: + flags = _character_flags(character) + else: + flags = 0 + self.flags = flags + self.accentuated = bool(flags & _ACCENTUATED) + self.latin = bool(flags & _LATIN) + self.is_cjk = bool(flags & _CJK) + self.is_arabic = bool(flags & _ARABIC) + self.is_glyph = bool(flags & _GLYPH_MASK) + + # Eagerly compute punct and sym (avoids property dispatch overhead + # on 300K+ accesses in the hot loop). + self.punct = is_punctuation(character) if self.printable else False + self.sym = is_symbol(character) if self.printable else False + + +class MessDetectorPlugin: + """ + Base abstract class used for mess detection plugins. + All detectors MUST extend and implement given methods. + """ + + __slots__ = () + + def feed_info(self, character: str, info: CharInfo) -> None: + """ + The main routine to be executed upon character. + Insert the logic in witch the text would be considered chaotic. + """ + raise NotImplementedError # Defensive: + + def reset(self) -> None: # Defensive: + """ + Permit to reset the plugin to the initial state. + """ + raise NotImplementedError + + @property + def ratio(self) -> float: + """ + Compute the chaos ratio based on what your feed() has seen. + Must NOT be lower than 0.; No restriction gt 0. + """ + raise NotImplementedError # Defensive: + + +@final +class TooManySymbolOrPunctuationPlugin(MessDetectorPlugin): + __slots__ = ( + "_punctuation_count", + "_symbol_count", + "_character_count", + "_last_printable_char", + "_frenzy_symbol_in_word", + ) + + def __init__(self) -> None: + self._punctuation_count: int = 0 + self._symbol_count: int = 0 + self._character_count: int = 0 + + self._last_printable_char: str | None = None + self._frenzy_symbol_in_word: bool = False + + def feed_info(self, character: str, info: CharInfo) -> None: + """Optimized feed using pre-computed character info.""" + self._character_count += 1 + + if ( + character != self._last_printable_char + and character not in COMMON_SAFE_ASCII_CHARACTERS + ): + if info.punct: + self._punctuation_count += 1 + elif not info.digit and info.sym and not is_emoticon(character): + self._symbol_count += 2 + + self._last_printable_char = character + + def reset(self) -> None: # Abstract + self._punctuation_count = 0 + self._character_count = 0 + self._symbol_count = 0 + + @property + def ratio(self) -> float: + if self._character_count == 0: + return 0.0 + + ratio_of_punctuation: float = ( + self._punctuation_count + self._symbol_count + ) / self._character_count + + return ratio_of_punctuation if ratio_of_punctuation >= 0.3 else 0.0 + + +@final +class TooManyAccentuatedPlugin(MessDetectorPlugin): + __slots__ = ("_character_count", "_accentuated_count") + + def __init__(self) -> None: + self._character_count: int = 0 + self._accentuated_count: int = 0 + + def feed_info(self, character: str, info: CharInfo) -> None: + """Optimized feed using pre-computed character info.""" + self._character_count += 1 + + if info.accentuated: + self._accentuated_count += 1 + + def reset(self) -> None: # Abstract + self._character_count = 0 + self._accentuated_count = 0 + + @property + def ratio(self) -> float: + if self._character_count < 8: + return 0.0 + + ratio_of_accentuation: float = self._accentuated_count / self._character_count + return ratio_of_accentuation if ratio_of_accentuation >= 0.35 else 0.0 + + +@final +class UnprintablePlugin(MessDetectorPlugin): + __slots__ = ("_unprintable_count", "_character_count") + + def __init__(self) -> None: + self._unprintable_count: int = 0 + self._character_count: int = 0 + + def feed_info(self, character: str, info: CharInfo) -> None: + """Optimized feed using pre-computed character info.""" + if ( + not info.space + and not info.printable + and character != "\x1a" + and character != "\ufeff" + ): + self._unprintable_count += 1 + self._character_count += 1 + + def reset(self) -> None: # Abstract + self._unprintable_count = 0 + + @property + def ratio(self) -> float: + if self._character_count == 0: # Defensive: + return 0.0 + + return (self._unprintable_count * 8) / self._character_count + + +@final +class SuspiciousDuplicateAccentPlugin(MessDetectorPlugin): + __slots__ = ( + "_successive_count", + "_character_count", + "_last_latin_character", + "_last_was_accentuated", + ) + + def __init__(self) -> None: + self._successive_count: int = 0 + self._character_count: int = 0 + + self._last_latin_character: str | None = None + self._last_was_accentuated: bool = False + + def feed_info(self, character: str, info: CharInfo) -> None: + """Optimized feed using pre-computed character info.""" + self._character_count += 1 + if ( + self._last_latin_character is not None + and info.accentuated + and self._last_was_accentuated + ): + if info.upper and self._last_latin_character.isupper(): + self._successive_count += 1 + if remove_accent(character) == remove_accent(self._last_latin_character): + self._successive_count += 1 + self._last_latin_character = character + self._last_was_accentuated = info.accentuated + + def reset(self) -> None: # Abstract + self._successive_count = 0 + self._character_count = 0 + self._last_latin_character = None + self._last_was_accentuated = False + + @property + def ratio(self) -> float: + if self._character_count == 0: + return 0.0 + + return (self._successive_count * 2) / self._character_count + + +@final +class SuspiciousRange(MessDetectorPlugin): + __slots__ = ( + "_suspicious_successive_range_count", + "_character_count", + "_last_printable_seen", + "_last_printable_range", + ) + + def __init__(self) -> None: + self._suspicious_successive_range_count: int = 0 + self._character_count: int = 0 + self._last_printable_seen: str | None = None + self._last_printable_range: str | None = None + + def feed_info(self, character: str, info: CharInfo) -> None: + """Optimized feed using pre-computed character info.""" + self._character_count += 1 + + if info.space or info.punct or character in COMMON_SAFE_ASCII_CHARACTERS: + self._last_printable_seen = None + self._last_printable_range = None + return + + if self._last_printable_seen is None: + self._last_printable_seen = character + self._last_printable_range = unicode_range(character) + return + + unicode_range_a: str | None = self._last_printable_range + unicode_range_b: str | None = unicode_range(character) + + if is_suspiciously_successive_range(unicode_range_a, unicode_range_b): + self._suspicious_successive_range_count += 1 + + self._last_printable_seen = character + self._last_printable_range = unicode_range_b + + def reset(self) -> None: # Abstract + self._character_count = 0 + self._suspicious_successive_range_count = 0 + self._last_printable_seen = None + self._last_printable_range = None + + @property + def ratio(self) -> float: + if self._character_count <= 13: + return 0.0 + + ratio_of_suspicious_range_usage: float = ( + self._suspicious_successive_range_count * 2 + ) / self._character_count + + return ratio_of_suspicious_range_usage + + +@final +class SuperWeirdWordPlugin(MessDetectorPlugin): + __slots__ = ( + "_word_count", + "_bad_word_count", + "_foreign_long_count", + "_is_current_word_bad", + "_foreign_long_watch", + "_character_count", + "_bad_character_count", + "_buffer_length", + "_buffer_last_char", + "_buffer_last_char_accentuated", + "_buffer_accent_count", + "_buffer_glyph_count", + "_buffer_upper_count", + ) + + def __init__(self) -> None: + self._word_count: int = 0 + self._bad_word_count: int = 0 + self._foreign_long_count: int = 0 + + self._is_current_word_bad: bool = False + self._foreign_long_watch: bool = False + + self._character_count: int = 0 + self._bad_character_count: int = 0 + + self._buffer_length: int = 0 + self._buffer_last_char: str | None = None + self._buffer_last_char_accentuated: bool = False + self._buffer_accent_count: int = 0 + self._buffer_glyph_count: int = 0 + self._buffer_upper_count: int = 0 + + def feed_info(self, character: str, info: CharInfo) -> None: + """Optimized feed using pre-computed character info.""" + if info.alpha: + self._buffer_length += 1 + self._buffer_last_char = character + + if info.upper: + self._buffer_upper_count += 1 + + self._buffer_last_char_accentuated = info.accentuated + + if info.accentuated: + self._buffer_accent_count += 1 + if ( + not self._foreign_long_watch + and (not info.latin or info.accentuated) + and not info.is_glyph + ): + self._foreign_long_watch = True + if info.is_glyph: + self._buffer_glyph_count += 1 + return + if not self._buffer_length: + return + if info.space or info.punct or is_separator(character): + self._word_count += 1 + buffer_length: int = self._buffer_length + + self._character_count += buffer_length + + if buffer_length >= 4: + if self._buffer_accent_count / buffer_length >= 0.5: + self._is_current_word_bad = True + elif ( + self._buffer_last_char_accentuated + and self._buffer_last_char.isupper() # type: ignore[union-attr] + and self._buffer_upper_count != buffer_length + ): + self._foreign_long_count += 1 + self._is_current_word_bad = True + elif self._buffer_glyph_count == 1: + self._is_current_word_bad = True + self._foreign_long_count += 1 + if buffer_length >= 24 and self._foreign_long_watch: + probable_camel_cased: bool = ( + self._buffer_upper_count > 0 + and self._buffer_upper_count / buffer_length <= 0.3 + ) + + if not probable_camel_cased: + self._foreign_long_count += 1 + self._is_current_word_bad = True + + if self._is_current_word_bad: + self._bad_word_count += 1 + self._bad_character_count += buffer_length + self._is_current_word_bad = False + + self._foreign_long_watch = False + self._buffer_length = 0 + self._buffer_last_char = None + self._buffer_last_char_accentuated = False + self._buffer_accent_count = 0 + self._buffer_glyph_count = 0 + self._buffer_upper_count = 0 + elif ( + character not in {"<", ">", "-", "=", "~", "|", "_"} + and not info.digit + and info.sym + ): + self._is_current_word_bad = True + self._buffer_length += 1 + self._buffer_last_char = character + self._buffer_last_char_accentuated = False + + def reset(self) -> None: # Abstract + self._buffer_length = 0 + self._buffer_last_char = None + self._buffer_last_char_accentuated = False + self._is_current_word_bad = False + self._foreign_long_watch = False + self._bad_word_count = 0 + self._word_count = 0 + self._character_count = 0 + self._bad_character_count = 0 + self._foreign_long_count = 0 + self._buffer_accent_count = 0 + self._buffer_glyph_count = 0 + self._buffer_upper_count = 0 + + @property + def ratio(self) -> float: + if self._word_count <= 10 and self._foreign_long_count == 0: + return 0.0 + + return self._bad_character_count / self._character_count + + +@final +class CjkUncommonPlugin(MessDetectorPlugin): + """ + Detect messy CJK text that probably means nothing. + """ + + __slots__ = ("_character_count", "_uncommon_count") + + def __init__(self) -> None: + self._character_count: int = 0 + self._uncommon_count: int = 0 + + def feed_info(self, character: str, info: CharInfo) -> None: + """Optimized feed using pre-computed character info.""" + self._character_count += 1 + + if character not in COMMON_CJK_CHARACTERS: + self._uncommon_count += 1 + + def reset(self) -> None: # Abstract + self._character_count = 0 + self._uncommon_count = 0 + + @property + def ratio(self) -> float: + if self._character_count < 8: + return 0.0 + + uncommon_form_usage: float = self._uncommon_count / self._character_count + + # we can be pretty sure it's garbage when uncommon characters are widely + # used. otherwise it could just be traditional chinese for example. + return uncommon_form_usage / 10 if uncommon_form_usage > 0.5 else 0.0 + + +@final +class ArchaicUpperLowerPlugin(MessDetectorPlugin): + __slots__ = ( + "_buf", + "_character_count_since_last_sep", + "_successive_upper_lower_count", + "_successive_upper_lower_count_final", + "_character_count", + "_last_alpha_seen", + "_last_alpha_seen_upper", + "_last_alpha_seen_lower", + "_current_ascii_only", + ) + + def __init__(self) -> None: + self._buf: bool = False + + self._character_count_since_last_sep: int = 0 + + self._successive_upper_lower_count: int = 0 + self._successive_upper_lower_count_final: int = 0 + + self._character_count: int = 0 + + self._last_alpha_seen: str | None = None + self._last_alpha_seen_upper: bool = False + self._last_alpha_seen_lower: bool = False + self._current_ascii_only: bool = True + + def feed_info(self, character: str, info: CharInfo) -> None: + """Optimized feed using pre-computed character info.""" + is_concerned: bool = info.alpha and info.case_variable + chunk_sep: bool = not is_concerned + + if chunk_sep and self._character_count_since_last_sep > 0: + if ( + self._character_count_since_last_sep <= 64 + and not info.digit + and not self._current_ascii_only + ): + self._successive_upper_lower_count_final += ( + self._successive_upper_lower_count + ) + + self._successive_upper_lower_count = 0 + self._character_count_since_last_sep = 0 + self._last_alpha_seen = None + self._buf = False + self._character_count += 1 + self._current_ascii_only = True + + return + + if self._current_ascii_only and not info.is_ascii: + self._current_ascii_only = False + + if self._last_alpha_seen is not None: + if (info.upper and self._last_alpha_seen_lower) or ( + info.lower and self._last_alpha_seen_upper + ): + if self._buf: + self._successive_upper_lower_count += 2 + self._buf = False + else: + self._buf = True + else: + self._buf = False + + self._character_count += 1 + self._character_count_since_last_sep += 1 + self._last_alpha_seen = character + self._last_alpha_seen_upper = info.upper + self._last_alpha_seen_lower = info.lower + + def reset(self) -> None: # Abstract + self._character_count = 0 + self._character_count_since_last_sep = 0 + self._successive_upper_lower_count = 0 + self._successive_upper_lower_count_final = 0 + self._last_alpha_seen = None + self._last_alpha_seen_upper = False + self._last_alpha_seen_lower = False + self._buf = False + self._current_ascii_only = True + + @property + def ratio(self) -> float: + if self._character_count == 0: # Defensive: + return 0.0 + + return self._successive_upper_lower_count_final / self._character_count + + +@final +class ArabicIsolatedFormPlugin(MessDetectorPlugin): + __slots__ = ("_character_count", "_isolated_form_count") + + def __init__(self) -> None: + self._character_count: int = 0 + self._isolated_form_count: int = 0 + + def reset(self) -> None: # Abstract + self._character_count = 0 + self._isolated_form_count = 0 + + def feed_info(self, character: str, info: CharInfo) -> None: + """Optimized feed using pre-computed character info.""" + self._character_count += 1 + + if info.flags & _ARABIC_ISOLATED_FORM: + self._isolated_form_count += 1 + + @property + def ratio(self) -> float: + if self._character_count < 8: + return 0.0 + + isolated_form_usage: float = self._isolated_form_count / self._character_count + + return isolated_form_usage + + +@lru_cache(maxsize=1024) +def is_suspiciously_successive_range( + unicode_range_a: str | None, unicode_range_b: str | None +) -> bool: + """ + Determine if two Unicode range seen next to each other can be considered as suspicious. + """ + if unicode_range_a is None or unicode_range_b is None: + return True + + if unicode_range_a == unicode_range_b: + return False + + if "Latin" in unicode_range_a and "Latin" in unicode_range_b: + return False + + if "Emoticons" in unicode_range_a or "Emoticons" in unicode_range_b: + return False + + # Latin characters can be accompanied with a combining diacritical mark + # eg. Vietnamese. + if ("Latin" in unicode_range_a or "Latin" in unicode_range_b) and ( + "Combining" in unicode_range_a or "Combining" in unicode_range_b + ): + return False + + keywords_range_a, keywords_range_b = ( + unicode_range_a.split(" "), + unicode_range_b.split(" "), + ) + + for el in keywords_range_a: + if el in UNICODE_SECONDARY_RANGE_KEYWORD: + continue + if el in keywords_range_b: + return False + + # Japanese Exception + range_a_jp_chars, range_b_jp_chars = ( + unicode_range_a + in ( + "Hiragana", + "Katakana", + ), + unicode_range_b in ("Hiragana", "Katakana"), + ) + if (range_a_jp_chars or range_b_jp_chars) and ( + "CJK" in unicode_range_a or "CJK" in unicode_range_b + ): + return False + if range_a_jp_chars and range_b_jp_chars: + return False + + if "Hangul" in unicode_range_a or "Hangul" in unicode_range_b: + if "CJK" in unicode_range_a or "CJK" in unicode_range_b: + return False + if unicode_range_a == "Basic Latin" or unicode_range_b == "Basic Latin": + return False + + # Chinese/Japanese use dedicated range for punctuation and/or separators. + if ("CJK" in unicode_range_a or "CJK" in unicode_range_b) or ( + unicode_range_a in ["Katakana", "Hiragana"] + and unicode_range_b in ["Katakana", "Hiragana"] + ): + if "Punctuation" in unicode_range_a or "Punctuation" in unicode_range_b: + return False + if "Forms" in unicode_range_a or "Forms" in unicode_range_b: + return False + if unicode_range_a == "Basic Latin" or unicode_range_b == "Basic Latin": + return False + + return True + + +@lru_cache(maxsize=2048) +def mess_ratio( + decoded_sequence: str, maximum_threshold: float = 0.2, debug: bool = False +) -> float: + """ + Compute a mess ratio given a decoded bytes sequence. The maximum threshold does stop the computation earlier. + """ + + seq_len: int = len(decoded_sequence) + + if seq_len < 511: + step: int = 32 + elif seq_len < 1024: + step = 64 + else: + step = 128 + + # Create each detector as a named local variable (unrolled from the generic loop). + # This eliminates per-character iteration over the detector list and + # per-character eligible() virtual dispatch, while keeping every plugin class + # intact and fully readable. + d_sp: TooManySymbolOrPunctuationPlugin = TooManySymbolOrPunctuationPlugin() + d_ta: TooManyAccentuatedPlugin = TooManyAccentuatedPlugin() + d_up: UnprintablePlugin = UnprintablePlugin() + d_sda: SuspiciousDuplicateAccentPlugin = SuspiciousDuplicateAccentPlugin() + d_sr: SuspiciousRange = SuspiciousRange() + d_sw: SuperWeirdWordPlugin = SuperWeirdWordPlugin() + d_cu: CjkUncommonPlugin = CjkUncommonPlugin() + d_au: ArchaicUpperLowerPlugin = ArchaicUpperLowerPlugin() + d_ai: ArabicIsolatedFormPlugin = ArabicIsolatedFormPlugin() + + # Local references for feed_info methods called in the hot loop. + d_sp_feed = d_sp.feed_info + d_ta_feed = d_ta.feed_info + d_up_feed = d_up.feed_info + d_sda_feed = d_sda.feed_info + d_sr_feed = d_sr.feed_info + d_sw_feed = d_sw.feed_info + d_cu_feed = d_cu.feed_info + d_au_feed = d_au.feed_info + d_ai_feed = d_ai.feed_info + + # Single reusable CharInfo object (avoids per-character allocation). + info: CharInfo = CharInfo() + info_update = info.update + + mean_mess_ratio: float + + for block_start in range(0, seq_len, step): + for character in decoded_sequence[block_start : block_start + step]: + # Pre-compute all character properties once (shared across all plugins). + info_update(character) + + # Detectors with eligible() == always True + d_up_feed(character, info) + d_sw_feed(character, info) + d_au_feed(character, info) + + # Detectors with eligible() == isprintable + if info.printable: + d_sp_feed(character, info) + d_sr_feed(character, info) + + # Detectors with eligible() == isalpha + if info.alpha: + d_ta_feed(character, info) + # SuspiciousDuplicateAccent: isalpha() and is_latin() + if info.latin: + d_sda_feed(character, info) + # CjkUncommon: is_cjk() + if info.is_cjk: + d_cu_feed(character, info) + # ArabicIsolatedForm: is_arabic() + if info.is_arabic: + d_ai_feed(character, info) + + mean_mess_ratio = ( + d_sp.ratio + + d_ta.ratio + + d_up.ratio + + d_sda.ratio + + d_sr.ratio + + d_sw.ratio + + d_cu.ratio + + d_au.ratio + + d_ai.ratio + ) + + if mean_mess_ratio >= maximum_threshold: + break + else: + # Flush last word buffer in SuperWeirdWordPlugin via trailing newline. + info_update("\n") + d_sw_feed("\n", info) + d_au_feed("\n", info) + d_up_feed("\n", info) + + mean_mess_ratio = ( + d_sp.ratio + + d_ta.ratio + + d_up.ratio + + d_sda.ratio + + d_sr.ratio + + d_sw.ratio + + d_cu.ratio + + d_au.ratio + + d_ai.ratio + ) + + if debug: # Defensive: + logger = getLogger("charset_normalizer") + + logger.log( + TRACE, + "Mess-detector extended-analysis start. " + f"intermediary_mean_mess_ratio_calc={step} mean_mess_ratio={mean_mess_ratio} " + f"maximum_threshold={maximum_threshold}", + ) + + if seq_len > 16: + logger.log(TRACE, f"Starting with: {decoded_sequence[:16]}") + logger.log(TRACE, f"Ending with: {decoded_sequence[-16::]}") + + for dt in [d_sp, d_ta, d_up, d_sda, d_sr, d_sw, d_cu, d_au, d_ai]: + logger.log(TRACE, f"{dt.__class__}: {dt.ratio}") + + return round(mean_mess_ratio, 3) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/models.py b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/models.py new file mode 100644 index 0000000000000000000000000000000000000000..382de159ece3ca60ee129def777bbd29b23e1d59 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/models.py @@ -0,0 +1,369 @@ +from __future__ import annotations + +from encodings.aliases import aliases +from json import dumps +from re import sub +from typing import Any, Iterator, List, Tuple + +from .constant import RE_POSSIBLE_ENCODING_INDICATION, TOO_BIG_SEQUENCE +from .utils import iana_name, is_multi_byte_encoding, unicode_range + + +class CharsetMatch: + def __init__( + self, + payload: bytes | bytearray, + guessed_encoding: str, + mean_mess_ratio: float, + has_sig_or_bom: bool, + languages: CoherenceMatches, + decoded_payload: str | None = None, + preemptive_declaration: str | None = None, + ): + self._payload: bytes | bytearray = payload + + self._encoding: str = guessed_encoding + self._mean_mess_ratio: float = mean_mess_ratio + self._languages: CoherenceMatches = languages + self._has_sig_or_bom: bool = has_sig_or_bom + self._unicode_ranges: list[str] | None = None + + self._leaves: list[CharsetMatch] = [] + self._mean_coherence_ratio: float = 0.0 + + self._output_payload: bytes | None = None + self._output_encoding: str | None = None + + self._string: str | None = decoded_payload + + self._preemptive_declaration: str | None = preemptive_declaration + + def __eq__(self, other: object) -> bool: + if not isinstance(other, CharsetMatch): + if isinstance(other, str): + return iana_name(other) == self.encoding + return False + return self.encoding == other.encoding and self.fingerprint == other.fingerprint + + def __lt__(self, other: object) -> bool: + """ + Implemented to make sorted available upon CharsetMatches items. + """ + if not isinstance(other, CharsetMatch): + raise ValueError + + chaos_difference: float = abs(self.chaos - other.chaos) + coherence_difference: float = abs(self.coherence - other.coherence) + + # Below 0.5% difference --> Use Coherence + if chaos_difference < 0.005 and coherence_difference > 0.02: + return self.coherence > other.coherence + elif chaos_difference < 0.005 and coherence_difference <= 0.02: + # When having a difficult decision, use the result that decoded as many multi-byte as possible. + # preserve RAM usage! + if len(self._payload) >= TOO_BIG_SEQUENCE: + return self.chaos < other.chaos + return self.multi_byte_usage > other.multi_byte_usage + + return self.chaos < other.chaos + + @property + def multi_byte_usage(self) -> float: + return 1.0 - (len(str(self)) / len(self.raw)) + + def __str__(self) -> str: + # Lazy Str Loading + if self._string is None: + self._string = str(self._payload, self._encoding, "strict") + # UTF-7 BOM is encoded in modified Base64 whose byte boundary + # can overlap with the next character, so raw-byte stripping + # is unreliable. Strip the decoded BOM character instead. + if ( + self._has_sig_or_bom + and self._encoding == "utf_7" + and self._string + and self._string[0] == "\ufeff" + ): + self._string = self._string[1:] + return self._string + + def __repr__(self) -> str: + return f"" + + def add_submatch(self, other: CharsetMatch) -> None: + if not isinstance(other, CharsetMatch) or other == self: + raise ValueError( + "Unable to add instance <{}> as a submatch of a CharsetMatch".format( + other.__class__ + ) + ) + + other._string = None # Unload RAM usage; dirty trick. + self._leaves.append(other) + + @property + def encoding(self) -> str: + return self._encoding + + @property + def encoding_aliases(self) -> list[str]: + """ + Encoding name are known by many name, using this could help when searching for IBM855 when it's listed as CP855. + """ + also_known_as: list[str] = [] + for u, p in aliases.items(): + if self.encoding == u: + also_known_as.append(p) + elif self.encoding == p: + also_known_as.append(u) + return also_known_as + + @property + def bom(self) -> bool: + return self._has_sig_or_bom + + @property + def byte_order_mark(self) -> bool: + return self._has_sig_or_bom + + @property + def languages(self) -> list[str]: + """ + Return the complete list of possible languages found in decoded sequence. + Usually not really useful. Returned list may be empty even if 'language' property return something != 'Unknown'. + """ + return [e[0] for e in self._languages] + + @property + def language(self) -> str: + """ + Most probable language found in decoded sequence. If none were detected or inferred, the property will return + "Unknown". + """ + if not self._languages: + # Trying to infer the language based on the given encoding + # Its either English or we should not pronounce ourselves in certain cases. + if "ascii" in self.could_be_from_charset: + return "English" + + # doing it there to avoid circular import + from charset_normalizer.cd import encoding_languages, mb_encoding_languages + + languages = ( + mb_encoding_languages(self.encoding) + if is_multi_byte_encoding(self.encoding) + else encoding_languages(self.encoding) + ) + + if len(languages) == 0 or "Latin Based" in languages: + return "Unknown" + + return languages[0] + + return self._languages[0][0] + + @property + def chaos(self) -> float: + return self._mean_mess_ratio + + @property + def coherence(self) -> float: + if not self._languages: + return 0.0 + return self._languages[0][1] + + @property + def percent_chaos(self) -> float: + return round(self.chaos * 100, ndigits=3) + + @property + def percent_coherence(self) -> float: + return round(self.coherence * 100, ndigits=3) + + @property + def raw(self) -> bytes | bytearray: + """ + Original untouched bytes. + """ + return self._payload + + @property + def submatch(self) -> list[CharsetMatch]: + return self._leaves + + @property + def has_submatch(self) -> bool: + return len(self._leaves) > 0 + + @property + def alphabets(self) -> list[str]: + if self._unicode_ranges is not None: + return self._unicode_ranges + # list detected ranges + detected_ranges: list[str | None] = [unicode_range(char) for char in str(self)] + # filter and sort + self._unicode_ranges = sorted(list({r for r in detected_ranges if r})) + return self._unicode_ranges + + @property + def could_be_from_charset(self) -> list[str]: + """ + The complete list of encoding that output the exact SAME str result and therefore could be the originating + encoding. + This list does include the encoding available in property 'encoding'. + """ + return [self._encoding] + [m.encoding for m in self._leaves] + + def output(self, encoding: str = "utf_8") -> bytes: + """ + Method to get re-encoded bytes payload using given target encoding. Default to UTF-8. + Any errors will be simply ignored by the encoder NOT replaced. + """ + if self._output_encoding is None or self._output_encoding != encoding: + self._output_encoding = encoding + decoded_string = str(self) + if ( + self._preemptive_declaration is not None + and self._preemptive_declaration.lower() + not in ["utf-8", "utf8", "utf_8"] + ): + patched_header = sub( + RE_POSSIBLE_ENCODING_INDICATION, + lambda m: m.string[m.span()[0] : m.span()[1]].replace( + m.groups()[0], + iana_name(self._output_encoding).replace("_", "-"), # type: ignore[arg-type] + ), + decoded_string[:8192], + count=1, + ) + + decoded_string = patched_header + decoded_string[8192:] + + self._output_payload = decoded_string.encode(encoding, "replace") + + return self._output_payload # type: ignore + + @property + def fingerprint(self) -> int: + """ + Retrieve a hash fingerprint of the decoded payload, used for deduplication. + """ + return hash(str(self)) + + +class CharsetMatches: + """ + Container with every CharsetMatch items ordered by default from most probable to the less one. + Act like a list(iterable) but does not implements all related methods. + """ + + def __init__(self, results: list[CharsetMatch] | None = None): + self._results: list[CharsetMatch] = sorted(results) if results else [] + + def __iter__(self) -> Iterator[CharsetMatch]: + yield from self._results + + def __getitem__(self, item: int | str) -> CharsetMatch: + """ + Retrieve a single item either by its position or encoding name (alias may be used here). + Raise KeyError upon invalid index or encoding not present in results. + """ + if isinstance(item, int): + return self._results[item] + if isinstance(item, str): + item = iana_name(item, False) + for result in self._results: + if item in result.could_be_from_charset: + return result + raise KeyError + + def __len__(self) -> int: + return len(self._results) + + def __bool__(self) -> bool: + return len(self._results) > 0 + + def append(self, item: CharsetMatch) -> None: + """ + Insert a single match. Will be inserted accordingly to preserve sort. + Can be inserted as a submatch. + """ + if not isinstance(item, CharsetMatch): + raise ValueError( + "Cannot append instance '{}' to CharsetMatches".format( + str(item.__class__) + ) + ) + # We should disable the submatch factoring when the input file is too heavy (conserve RAM usage) + if len(item.raw) < TOO_BIG_SEQUENCE: + for match in self._results: + if match.fingerprint == item.fingerprint and match.chaos == item.chaos: + match.add_submatch(item) + return + self._results.append(item) + self._results = sorted(self._results) + + def best(self) -> CharsetMatch | None: + """ + Simply return the first match. Strict equivalent to matches[0]. + """ + if not self._results: + return None + return self._results[0] + + def first(self) -> CharsetMatch | None: + """ + Redundant method, call the method best(). Kept for BC reasons. + """ + return self.best() + + +CoherenceMatch = Tuple[str, float] +CoherenceMatches = List[CoherenceMatch] + + +class CliDetectionResult: + def __init__( + self, + path: str, + encoding: str | None, + encoding_aliases: list[str], + alternative_encodings: list[str], + language: str, + alphabets: list[str], + has_sig_or_bom: bool, + chaos: float, + coherence: float, + unicode_path: str | None, + is_preferred: bool, + ): + self.path: str = path + self.unicode_path: str | None = unicode_path + self.encoding: str | None = encoding + self.encoding_aliases: list[str] = encoding_aliases + self.alternative_encodings: list[str] = alternative_encodings + self.language: str = language + self.alphabets: list[str] = alphabets + self.has_sig_or_bom: bool = has_sig_or_bom + self.chaos: float = chaos + self.coherence: float = coherence + self.is_preferred: bool = is_preferred + + @property + def __dict__(self) -> dict[str, Any]: # type: ignore + return { + "path": self.path, + "encoding": self.encoding, + "encoding_aliases": self.encoding_aliases, + "alternative_encodings": self.alternative_encodings, + "language": self.language, + "alphabets": self.alphabets, + "has_sig_or_bom": self.has_sig_or_bom, + "chaos": self.chaos, + "coherence": self.coherence, + "unicode_path": self.unicode_path, + "is_preferred": self.is_preferred, + } + + def to_json(self) -> str: + return dumps(self.__dict__, ensure_ascii=True, indent=4) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/py.typed b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/py.typed new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/utils.py b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..0f529b59cfdf6f2b4f6aca39b439b2e35dd93d00 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/utils.py @@ -0,0 +1,422 @@ +from __future__ import annotations + +import importlib +import logging +import unicodedata +from bisect import bisect_right +from codecs import IncrementalDecoder +from encodings.aliases import aliases +from functools import lru_cache +from re import findall +from typing import Generator + +from _multibytecodec import ( # type: ignore[import-not-found,import] + MultibyteIncrementalDecoder, +) + +from .constant import ( + ENCODING_MARKS, + IANA_SUPPORTED_SIMILAR, + RE_POSSIBLE_ENCODING_INDICATION, + UNICODE_RANGES_COMBINED, + UNICODE_SECONDARY_RANGE_KEYWORD, + UTF8_MAXIMAL_ALLOCATION, + COMMON_CJK_CHARACTERS, + _LATIN, + _CJK, + _HANGUL, + _KATAKANA, + _HIRAGANA, + _THAI, + _ARABIC, + _ARABIC_ISOLATED_FORM, + _ACCENT_KEYWORDS, + _ACCENTUATED, +) + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def _character_flags(character: str) -> int: + """Compute all name-based classification flags with a single unicodedata.name() call.""" + try: + desc: str = unicodedata.name(character) + except ValueError: + return 0 + + flags: int = 0 + + if "LATIN" in desc: + flags |= _LATIN + if "CJK" in desc: + flags |= _CJK + if "HANGUL" in desc: + flags |= _HANGUL + if "KATAKANA" in desc: + flags |= _KATAKANA + if "HIRAGANA" in desc: + flags |= _HIRAGANA + if "THAI" in desc: + flags |= _THAI + if "ARABIC" in desc: + flags |= _ARABIC + if "ISOLATED FORM" in desc: + flags |= _ARABIC_ISOLATED_FORM + + for kw in _ACCENT_KEYWORDS: + if kw in desc: + flags |= _ACCENTUATED + break + + return flags + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_accentuated(character: str) -> bool: + return bool(_character_flags(character) & _ACCENTUATED) + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def remove_accent(character: str) -> str: + decomposed: str = unicodedata.decomposition(character) + if not decomposed: + return character + + codes: list[str] = decomposed.split(" ") + + return chr(int(codes[0], 16)) + + +# Pre-built sorted lookup table for O(log n) binary search in unicode_range(). +# Each entry is (range_start, range_end_exclusive, range_name). +_UNICODE_RANGES_SORTED: list[tuple[int, int, str]] = sorted( + (ord_range.start, ord_range.stop, name) + for name, ord_range in UNICODE_RANGES_COMBINED.items() +) +_UNICODE_RANGE_STARTS: list[int] = [e[0] for e in _UNICODE_RANGES_SORTED] + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def unicode_range(character: str) -> str | None: + """ + Retrieve the Unicode range official name from a single character. + """ + character_ord: int = ord(character) + + # Binary search: find the rightmost range whose start <= character_ord + idx = bisect_right(_UNICODE_RANGE_STARTS, character_ord) - 1 + if idx >= 0: + start, stop, name = _UNICODE_RANGES_SORTED[idx] + if character_ord < stop: + return name + + return None + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_latin(character: str) -> bool: + return bool(_character_flags(character) & _LATIN) + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_punctuation(character: str) -> bool: + character_category: str = unicodedata.category(character) + + if "P" in character_category: + return True + + character_range: str | None = unicode_range(character) + + if character_range is None: + return False + + return "Punctuation" in character_range + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_symbol(character: str) -> bool: + character_category: str = unicodedata.category(character) + + if "S" in character_category or "N" in character_category: + return True + + character_range: str | None = unicode_range(character) + + if character_range is None: + return False + + return "Forms" in character_range and character_category != "Lo" + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_emoticon(character: str) -> bool: + character_range: str | None = unicode_range(character) + + if character_range is None: + return False + + return "Emoticons" in character_range or "Pictographs" in character_range + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_separator(character: str) -> bool: + if character.isspace() or character in {"|", "+", "<", ">"}: + return True + + character_category: str = unicodedata.category(character) + + return "Z" in character_category or character_category in {"Po", "Pd", "Pc"} + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_case_variable(character: str) -> bool: + return character.islower() != character.isupper() + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_cjk(character: str) -> bool: + return bool(_character_flags(character) & _CJK) + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_hiragana(character: str) -> bool: + return bool(_character_flags(character) & _HIRAGANA) + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_katakana(character: str) -> bool: + return bool(_character_flags(character) & _KATAKANA) + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_hangul(character: str) -> bool: + return bool(_character_flags(character) & _HANGUL) + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_thai(character: str) -> bool: + return bool(_character_flags(character) & _THAI) + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_arabic(character: str) -> bool: + return bool(_character_flags(character) & _ARABIC) + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_arabic_isolated_form(character: str) -> bool: + return bool(_character_flags(character) & _ARABIC_ISOLATED_FORM) + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_cjk_uncommon(character: str) -> bool: + return character not in COMMON_CJK_CHARACTERS + + +@lru_cache(maxsize=len(UNICODE_RANGES_COMBINED)) +def is_unicode_range_secondary(range_name: str) -> bool: + return any(keyword in range_name for keyword in UNICODE_SECONDARY_RANGE_KEYWORD) + + +@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) +def is_unprintable(character: str) -> bool: + return ( + character.isspace() is False # includes \n \t \r \v + and character.isprintable() is False + and character != "\x1a" # Why? Its the ASCII substitute character. + and character != "\ufeff" # bug discovered in Python, + # Zero Width No-Break Space located in Arabic Presentation Forms-B, Unicode 1.1 not acknowledged as space. + ) + + +def any_specified_encoding( + sequence: bytes | bytearray, search_zone: int = 8192 +) -> str | None: + """ + Extract using ASCII-only decoder any specified encoding in the first n-bytes. + """ + if not isinstance(sequence, (bytes, bytearray)): + raise TypeError + + seq_len: int = len(sequence) + + results: list[str] = findall( + RE_POSSIBLE_ENCODING_INDICATION, + sequence[: min(seq_len, search_zone)].decode("ascii", errors="ignore"), + ) + + if len(results) == 0: + return None + + for specified_encoding in results: + specified_encoding = specified_encoding.lower().replace("-", "_") + + encoding_alias: str + encoding_iana: str + + for encoding_alias, encoding_iana in aliases.items(): + if encoding_alias == specified_encoding: + return encoding_iana + if encoding_iana == specified_encoding: + return encoding_iana + + return None + + +@lru_cache(maxsize=128) +def is_multi_byte_encoding(name: str) -> bool: + """ + Verify is a specific encoding is a multi byte one based on it IANA name + """ + return name in { + "utf_8", + "utf_8_sig", + "utf_16", + "utf_16_be", + "utf_16_le", + "utf_32", + "utf_32_le", + "utf_32_be", + "utf_7", + } or issubclass( + importlib.import_module(f"encodings.{name}").IncrementalDecoder, + MultibyteIncrementalDecoder, + ) + + +def identify_sig_or_bom(sequence: bytes | bytearray) -> tuple[str | None, bytes]: + """ + Identify and extract SIG/BOM in given sequence. + """ + + for iana_encoding in ENCODING_MARKS: + marks: bytes | list[bytes] = ENCODING_MARKS[iana_encoding] + + if isinstance(marks, bytes): + marks = [marks] + + for mark in marks: + if sequence.startswith(mark): + return iana_encoding, mark + + return None, b"" + + +def should_strip_sig_or_bom(iana_encoding: str) -> bool: + return iana_encoding not in {"utf_16", "utf_32"} + + +def iana_name(cp_name: str, strict: bool = True) -> str: + """Returns the Python normalized encoding name (Not the IANA official name).""" + cp_name = cp_name.lower().replace("-", "_") + + encoding_alias: str + encoding_iana: str + + for encoding_alias, encoding_iana in aliases.items(): + if cp_name in [encoding_alias, encoding_iana]: + return encoding_iana + + if strict: + raise ValueError(f"Unable to retrieve IANA for '{cp_name}'") + + return cp_name + + +def cp_similarity(iana_name_a: str, iana_name_b: str) -> float: + if is_multi_byte_encoding(iana_name_a) or is_multi_byte_encoding(iana_name_b): + return 0.0 + + decoder_a = importlib.import_module(f"encodings.{iana_name_a}").IncrementalDecoder + decoder_b = importlib.import_module(f"encodings.{iana_name_b}").IncrementalDecoder + + id_a: IncrementalDecoder = decoder_a(errors="ignore") + id_b: IncrementalDecoder = decoder_b(errors="ignore") + + character_match_count: int = 0 + + for i in range(256): + to_be_decoded: bytes = bytes([i]) + if id_a.decode(to_be_decoded) == id_b.decode(to_be_decoded): + character_match_count += 1 + + return character_match_count / 256 + + +def is_cp_similar(iana_name_a: str, iana_name_b: str) -> bool: + """ + Determine if two code page are at least 80% similar. IANA_SUPPORTED_SIMILAR dict was generated using + the function cp_similarity. + """ + return ( + iana_name_a in IANA_SUPPORTED_SIMILAR + and iana_name_b in IANA_SUPPORTED_SIMILAR[iana_name_a] + ) + + +def set_logging_handler( + name: str = "charset_normalizer", + level: int = logging.INFO, + format_string: str = "%(asctime)s | %(levelname)s | %(message)s", +) -> None: + logger = logging.getLogger(name) + logger.setLevel(level) + + handler = logging.StreamHandler() + handler.setFormatter(logging.Formatter(format_string)) + logger.addHandler(handler) + + +def cut_sequence_chunks( + sequences: bytes | bytearray, + encoding_iana: str, + offsets: range, + chunk_size: int, + bom_or_sig_available: bool, + strip_sig_or_bom: bool, + sig_payload: bytes, + is_multi_byte_decoder: bool, + decoded_payload: str | None = None, +) -> Generator[str, None, None]: + if decoded_payload and is_multi_byte_decoder is False: + for i in offsets: + chunk = decoded_payload[i : i + chunk_size] + if not chunk: + break + yield chunk + else: + for i in offsets: + chunk_end = i + chunk_size + if chunk_end > len(sequences) + 8: + continue + + cut_sequence = sequences[i : i + chunk_size] + + if bom_or_sig_available and strip_sig_or_bom is False: + cut_sequence = sig_payload + cut_sequence + + chunk = cut_sequence.decode( + encoding_iana, + errors="ignore" if is_multi_byte_decoder else "strict", + ) + + # multi-byte bad cutting detector and adjustment + # not the cleanest way to perform that fix but clever enough for now. + if is_multi_byte_decoder and i > 0: + chunk_partial_size_chk: int = min(chunk_size, 16) + + if ( + decoded_payload + and chunk[:chunk_partial_size_chk] not in decoded_payload + ): + for j in range(i, i - 4, -1): + cut_sequence = sequences[j:chunk_end] + + if bom_or_sig_available and strip_sig_or_bom is False: + cut_sequence = sig_payload + cut_sequence + + chunk = cut_sequence.decode(encoding_iana, errors="ignore") + + if chunk[:chunk_partial_size_chk] in decoded_payload: + break + + yield chunk diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/version.py b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/version.py new file mode 100644 index 0000000000000000000000000000000000000000..a93d3672d95e5ce5f4e83c191f06dc66505a4a4c --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/charset_normalizer/version.py @@ -0,0 +1,8 @@ +""" +Expose version +""" + +from __future__ import annotations + +__version__ = "3.4.7" +VERSION = __version__.split(".") diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/click/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/click/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..fa485a0715907a7046518c03d4b7d2b2b4961c91 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/click/__init__.py @@ -0,0 +1,124 @@ +""" +Click is a simple Python module inspired by the stdlib optparse to make +writing command line scripts fun. Unlike other modules, it's based +around a simple API that does not come with too much magic and is +composable. +""" + +from __future__ import annotations + +from .core import Argument as Argument +from .core import Command as Command +from .core import CommandCollection as CommandCollection +from .core import Context as Context +from .core import Group as Group +from .core import Option as Option +from .core import Parameter as Parameter +from .core import ParameterSource as ParameterSource +from .decorators import argument as argument +from .decorators import command as command +from .decorators import confirmation_option as confirmation_option +from .decorators import group as group +from .decorators import help_option as help_option +from .decorators import make_pass_decorator as make_pass_decorator +from .decorators import option as option +from .decorators import pass_context as pass_context +from .decorators import pass_obj as pass_obj +from .decorators import password_option as password_option +from .decorators import version_option as version_option +from .exceptions import Abort as Abort +from .exceptions import BadArgumentUsage as BadArgumentUsage +from .exceptions import BadOptionUsage as BadOptionUsage +from .exceptions import BadParameter as BadParameter +from .exceptions import ClickException as ClickException +from .exceptions import FileError as FileError +from .exceptions import MissingParameter as MissingParameter +from .exceptions import NoSuchOption as NoSuchOption +from .exceptions import UsageError as UsageError +from .formatting import HelpFormatter as HelpFormatter +from .formatting import wrap_text as wrap_text +from .globals import get_current_context as get_current_context +from .termui import clear as clear +from .termui import confirm as confirm +from .termui import echo_via_pager as echo_via_pager +from .termui import edit as edit +from .termui import getchar as getchar +from .termui import launch as launch +from .termui import pause as pause +from .termui import progressbar as progressbar +from .termui import prompt as prompt +from .termui import secho as secho +from .termui import style as style +from .termui import unstyle as unstyle +from .types import BOOL as BOOL +from .types import Choice as Choice +from .types import DateTime as DateTime +from .types import File as File +from .types import FLOAT as FLOAT +from .types import FloatRange as FloatRange +from .types import INT as INT +from .types import IntRange as IntRange +from .types import ParamType as ParamType +from .types import Path as Path +from .types import STRING as STRING +from .types import Tuple as Tuple +from .types import UNPROCESSED as UNPROCESSED +from .types import UUID as UUID +from .utils import echo as echo +from .utils import format_filename as format_filename +from .utils import get_app_dir as get_app_dir +from .utils import get_binary_stream as get_binary_stream +from .utils import get_text_stream as get_text_stream +from .utils import open_file as open_file + + +def __getattr__(name: str) -> object: + import warnings + + if name == "BaseCommand": + from .core import _BaseCommand + + warnings.warn( + "'BaseCommand' is deprecated and will be removed in Click 9.0. Use" + " 'Command' instead.", + DeprecationWarning, + stacklevel=2, + ) + return _BaseCommand + + if name == "MultiCommand": + from .core import _MultiCommand + + warnings.warn( + "'MultiCommand' is deprecated and will be removed in Click 9.0. Use" + " 'Group' instead.", + DeprecationWarning, + stacklevel=2, + ) + return _MultiCommand + + if name == "OptionParser": + from .parser import _OptionParser + + warnings.warn( + "'OptionParser' is deprecated and will be removed in Click 9.0. The" + " old parser is available in 'optparse'.", + DeprecationWarning, + stacklevel=2, + ) + return _OptionParser + + if name == "__version__": + import importlib.metadata + import warnings + + warnings.warn( + "The '__version__' attribute is deprecated and will be removed in" + " Click 9.1. Use feature detection or" + " 'importlib.metadata.version(\"click\")' instead.", + DeprecationWarning, + stacklevel=2, + ) + return importlib.metadata.version("click") + + raise AttributeError(name) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/click/__pycache__/__init__.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/click/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9c7475db40d1b0b4da17eb93554dad39a556f862 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/click/__pycache__/__init__.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/click/__pycache__/_compat.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/click/__pycache__/_compat.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cc20c09919924354cd193588386fe4e287d9d6f4 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/click/__pycache__/_compat.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/click/__pycache__/_termui_impl.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/click/__pycache__/_termui_impl.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ee526a3efeac292aabd7fc28c4cc4b05479522f6 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/click/__pycache__/_termui_impl.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/click/__pycache__/_utils.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/click/__pycache__/_utils.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f76eb799e199ad94a13eebcb9560845cc9a32824 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/click/__pycache__/_utils.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/click/__pycache__/decorators.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/click/__pycache__/decorators.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..eb106f1774eb09120010d1887b175dd43bc97baa Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/click/__pycache__/decorators.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/click/__pycache__/exceptions.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/click/__pycache__/exceptions.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..610b41f8a1e28ef6be9d602b8f70ff955e6a6c5d Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/click/__pycache__/exceptions.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/click/__pycache__/formatting.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/click/__pycache__/formatting.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bebbed86aa9ebb81cde23c13f721f82f1ae37593 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/click/__pycache__/formatting.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/click/__pycache__/globals.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/click/__pycache__/globals.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1f1da64e50b35e36e6e67f039937764f58b00d59 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/click/__pycache__/globals.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/click/__pycache__/parser.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/click/__pycache__/parser.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dd8bd7a1a24462e33cea9d3bf043daa9f7db0367 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/click/__pycache__/parser.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/click/__pycache__/termui.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/click/__pycache__/termui.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..79d48340e40f9ac13bc1453c30fc4de0b011e64b Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/click/__pycache__/termui.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/click/__pycache__/types.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/click/__pycache__/types.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..75c8ba7b8dcc4eacacda50f87d3038ad6ebd7a8f Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/click/__pycache__/types.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/click/__pycache__/utils.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/click/__pycache__/utils.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..83f13219884d79ef1a28de927245d11c4f9aadc3 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/click/__pycache__/utils.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/click/_compat.py b/bioagent-bench/.venv/lib/python3.14/site-packages/click/_compat.py new file mode 100644 index 0000000000000000000000000000000000000000..f2726b93afe2ed69ba20d8e709c9cbd22b9d2339 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/click/_compat.py @@ -0,0 +1,622 @@ +from __future__ import annotations + +import codecs +import collections.abc as cabc +import io +import os +import re +import sys +import typing as t +from types import TracebackType +from weakref import WeakKeyDictionary + +CYGWIN = sys.platform.startswith("cygwin") +WIN = sys.platform.startswith("win") +auto_wrap_for_ansi: t.Callable[[t.TextIO], t.TextIO] | None = None +_ansi_re = re.compile(r"\033\[[;?0-9]*[a-zA-Z]") + + +def _make_text_stream( + stream: t.BinaryIO, + encoding: str | None, + errors: str | None, + force_readable: bool = False, + force_writable: bool = False, +) -> t.TextIO: + if encoding is None: + encoding = get_best_encoding(stream) + if errors is None: + errors = "replace" + return _NonClosingTextIOWrapper( + stream, + encoding, + errors, + line_buffering=True, + force_readable=force_readable, + force_writable=force_writable, + ) + + +def is_ascii_encoding(encoding: str) -> bool: + """Checks if a given encoding is ascii.""" + try: + return codecs.lookup(encoding).name == "ascii" + except LookupError: + return False + + +def get_best_encoding(stream: t.IO[t.Any]) -> str: + """Returns the default stream encoding if not found.""" + rv = getattr(stream, "encoding", None) or sys.getdefaultencoding() + if is_ascii_encoding(rv): + return "utf-8" + return rv + + +class _NonClosingTextIOWrapper(io.TextIOWrapper): + def __init__( + self, + stream: t.BinaryIO, + encoding: str | None, + errors: str | None, + force_readable: bool = False, + force_writable: bool = False, + **extra: t.Any, + ) -> None: + self._stream = stream = t.cast( + t.BinaryIO, _FixupStream(stream, force_readable, force_writable) + ) + super().__init__(stream, encoding, errors, **extra) + + def __del__(self) -> None: + try: + self.detach() + except Exception: + pass + + def isatty(self) -> bool: + # https://bitbucket.org/pypy/pypy/issue/1803 + return self._stream.isatty() + + +class _FixupStream: + """The new io interface needs more from streams than streams + traditionally implement. As such, this fix-up code is necessary in + some circumstances. + + The forcing of readable and writable flags are there because some tools + put badly patched objects on sys (one such offender are certain version + of jupyter notebook). + """ + + def __init__( + self, + stream: t.BinaryIO, + force_readable: bool = False, + force_writable: bool = False, + ): + self._stream = stream + self._force_readable = force_readable + self._force_writable = force_writable + + def __getattr__(self, name: str) -> t.Any: + return getattr(self._stream, name) + + def read1(self, size: int) -> bytes: + f = getattr(self._stream, "read1", None) + + if f is not None: + return t.cast(bytes, f(size)) + + return self._stream.read(size) + + def readable(self) -> bool: + if self._force_readable: + return True + x = getattr(self._stream, "readable", None) + if x is not None: + return t.cast(bool, x()) + try: + self._stream.read(0) + except Exception: + return False + return True + + def writable(self) -> bool: + if self._force_writable: + return True + x = getattr(self._stream, "writable", None) + if x is not None: + return t.cast(bool, x()) + try: + self._stream.write(b"") + except Exception: + try: + self._stream.write(b"") + except Exception: + return False + return True + + def seekable(self) -> bool: + x = getattr(self._stream, "seekable", None) + if x is not None: + return t.cast(bool, x()) + try: + self._stream.seek(self._stream.tell()) + except Exception: + return False + return True + + +def _is_binary_reader(stream: t.IO[t.Any], default: bool = False) -> bool: + try: + return isinstance(stream.read(0), bytes) + except Exception: + return default + # This happens in some cases where the stream was already + # closed. In this case, we assume the default. + + +def _is_binary_writer(stream: t.IO[t.Any], default: bool = False) -> bool: + try: + stream.write(b"") + except Exception: + try: + stream.write("") + return False + except Exception: + pass + return default + return True + + +def _find_binary_reader(stream: t.IO[t.Any]) -> t.BinaryIO | None: + # We need to figure out if the given stream is already binary. + # This can happen because the official docs recommend detaching + # the streams to get binary streams. Some code might do this, so + # we need to deal with this case explicitly. + if _is_binary_reader(stream, False): + return t.cast(t.BinaryIO, stream) + + buf = getattr(stream, "buffer", None) + + # Same situation here; this time we assume that the buffer is + # actually binary in case it's closed. + if buf is not None and _is_binary_reader(buf, True): + return t.cast(t.BinaryIO, buf) + + return None + + +def _find_binary_writer(stream: t.IO[t.Any]) -> t.BinaryIO | None: + # We need to figure out if the given stream is already binary. + # This can happen because the official docs recommend detaching + # the streams to get binary streams. Some code might do this, so + # we need to deal with this case explicitly. + if _is_binary_writer(stream, False): + return t.cast(t.BinaryIO, stream) + + buf = getattr(stream, "buffer", None) + + # Same situation here; this time we assume that the buffer is + # actually binary in case it's closed. + if buf is not None and _is_binary_writer(buf, True): + return t.cast(t.BinaryIO, buf) + + return None + + +def _stream_is_misconfigured(stream: t.TextIO) -> bool: + """A stream is misconfigured if its encoding is ASCII.""" + # If the stream does not have an encoding set, we assume it's set + # to ASCII. This appears to happen in certain unittest + # environments. It's not quite clear what the correct behavior is + # but this at least will force Click to recover somehow. + return is_ascii_encoding(getattr(stream, "encoding", None) or "ascii") + + +def _is_compat_stream_attr(stream: t.TextIO, attr: str, value: str | None) -> bool: + """A stream attribute is compatible if it is equal to the + desired value or the desired value is unset and the attribute + has a value. + """ + stream_value = getattr(stream, attr, None) + return stream_value == value or (value is None and stream_value is not None) + + +def _is_compatible_text_stream( + stream: t.TextIO, encoding: str | None, errors: str | None +) -> bool: + """Check if a stream's encoding and errors attributes are + compatible with the desired values. + """ + return _is_compat_stream_attr( + stream, "encoding", encoding + ) and _is_compat_stream_attr(stream, "errors", errors) + + +def _force_correct_text_stream( + text_stream: t.IO[t.Any], + encoding: str | None, + errors: str | None, + is_binary: t.Callable[[t.IO[t.Any], bool], bool], + find_binary: t.Callable[[t.IO[t.Any]], t.BinaryIO | None], + force_readable: bool = False, + force_writable: bool = False, +) -> t.TextIO: + if is_binary(text_stream, False): + binary_reader = t.cast(t.BinaryIO, text_stream) + else: + text_stream = t.cast(t.TextIO, text_stream) + # If the stream looks compatible, and won't default to a + # misconfigured ascii encoding, return it as-is. + if _is_compatible_text_stream(text_stream, encoding, errors) and not ( + encoding is None and _stream_is_misconfigured(text_stream) + ): + return text_stream + + # Otherwise, get the underlying binary reader. + possible_binary_reader = find_binary(text_stream) + + # If that's not possible, silently use the original reader + # and get mojibake instead of exceptions. + if possible_binary_reader is None: + return text_stream + + binary_reader = possible_binary_reader + + # Default errors to replace instead of strict in order to get + # something that works. + if errors is None: + errors = "replace" + + # Wrap the binary stream in a text stream with the correct + # encoding parameters. + return _make_text_stream( + binary_reader, + encoding, + errors, + force_readable=force_readable, + force_writable=force_writable, + ) + + +def _force_correct_text_reader( + text_reader: t.IO[t.Any], + encoding: str | None, + errors: str | None, + force_readable: bool = False, +) -> t.TextIO: + return _force_correct_text_stream( + text_reader, + encoding, + errors, + _is_binary_reader, + _find_binary_reader, + force_readable=force_readable, + ) + + +def _force_correct_text_writer( + text_writer: t.IO[t.Any], + encoding: str | None, + errors: str | None, + force_writable: bool = False, +) -> t.TextIO: + return _force_correct_text_stream( + text_writer, + encoding, + errors, + _is_binary_writer, + _find_binary_writer, + force_writable=force_writable, + ) + + +def get_binary_stdin() -> t.BinaryIO: + reader = _find_binary_reader(sys.stdin) + if reader is None: + raise RuntimeError("Was not able to determine binary stream for sys.stdin.") + return reader + + +def get_binary_stdout() -> t.BinaryIO: + writer = _find_binary_writer(sys.stdout) + if writer is None: + raise RuntimeError("Was not able to determine binary stream for sys.stdout.") + return writer + + +def get_binary_stderr() -> t.BinaryIO: + writer = _find_binary_writer(sys.stderr) + if writer is None: + raise RuntimeError("Was not able to determine binary stream for sys.stderr.") + return writer + + +def get_text_stdin(encoding: str | None = None, errors: str | None = None) -> t.TextIO: + rv = _get_windows_console_stream(sys.stdin, encoding, errors) + if rv is not None: + return rv + return _force_correct_text_reader(sys.stdin, encoding, errors, force_readable=True) + + +def get_text_stdout(encoding: str | None = None, errors: str | None = None) -> t.TextIO: + rv = _get_windows_console_stream(sys.stdout, encoding, errors) + if rv is not None: + return rv + return _force_correct_text_writer(sys.stdout, encoding, errors, force_writable=True) + + +def get_text_stderr(encoding: str | None = None, errors: str | None = None) -> t.TextIO: + rv = _get_windows_console_stream(sys.stderr, encoding, errors) + if rv is not None: + return rv + return _force_correct_text_writer(sys.stderr, encoding, errors, force_writable=True) + + +def _wrap_io_open( + file: str | os.PathLike[str] | int, + mode: str, + encoding: str | None, + errors: str | None, +) -> t.IO[t.Any]: + """Handles not passing ``encoding`` and ``errors`` in binary mode.""" + if "b" in mode: + return open(file, mode) + + return open(file, mode, encoding=encoding, errors=errors) + + +def open_stream( + filename: str | os.PathLike[str], + mode: str = "r", + encoding: str | None = None, + errors: str | None = "strict", + atomic: bool = False, +) -> tuple[t.IO[t.Any], bool]: + binary = "b" in mode + filename = os.fspath(filename) + + # Standard streams first. These are simple because they ignore the + # atomic flag. Use fsdecode to handle Path("-"). + if os.fsdecode(filename) == "-": + if any(m in mode for m in ["w", "a", "x"]): + if binary: + return get_binary_stdout(), False + return get_text_stdout(encoding=encoding, errors=errors), False + if binary: + return get_binary_stdin(), False + return get_text_stdin(encoding=encoding, errors=errors), False + + # Non-atomic writes directly go out through the regular open functions. + if not atomic: + return _wrap_io_open(filename, mode, encoding, errors), True + + # Some usability stuff for atomic writes + if "a" in mode: + raise ValueError( + "Appending to an existing file is not supported, because that" + " would involve an expensive `copy`-operation to a temporary" + " file. Open the file in normal `w`-mode and copy explicitly" + " if that's what you're after." + ) + if "x" in mode: + raise ValueError("Use the `overwrite`-parameter instead.") + if "w" not in mode: + raise ValueError("Atomic writes only make sense with `w`-mode.") + + # Atomic writes are more complicated. They work by opening a file + # as a proxy in the same folder and then using the fdopen + # functionality to wrap it in a Python file. Then we wrap it in an + # atomic file that moves the file over on close. + import errno + import random + + try: + perm: int | None = os.stat(filename).st_mode + except OSError: + perm = None + + flags = os.O_RDWR | os.O_CREAT | os.O_EXCL + + if binary: + flags |= getattr(os, "O_BINARY", 0) + + while True: + tmp_filename = os.path.join( + os.path.dirname(filename), + f".__atomic-write{random.randrange(1 << 32):08x}", + ) + try: + fd = os.open(tmp_filename, flags, 0o666 if perm is None else perm) + break + except OSError as e: + if e.errno == errno.EEXIST or ( + os.name == "nt" + and e.errno == errno.EACCES + and os.path.isdir(e.filename) + and os.access(e.filename, os.W_OK) + ): + continue + raise + + if perm is not None: + os.chmod(tmp_filename, perm) # in case perm includes bits in umask + + f = _wrap_io_open(fd, mode, encoding, errors) + af = _AtomicFile(f, tmp_filename, os.path.realpath(filename)) + return t.cast(t.IO[t.Any], af), True + + +class _AtomicFile: + def __init__(self, f: t.IO[t.Any], tmp_filename: str, real_filename: str) -> None: + self._f = f + self._tmp_filename = tmp_filename + self._real_filename = real_filename + self.closed = False + + @property + def name(self) -> str: + return self._real_filename + + def close(self, delete: bool = False) -> None: + if self.closed: + return + self._f.close() + os.replace(self._tmp_filename, self._real_filename) + self.closed = True + + def __getattr__(self, name: str) -> t.Any: + return getattr(self._f, name) + + def __enter__(self) -> _AtomicFile: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> None: + self.close(delete=exc_type is not None) + + def __repr__(self) -> str: + return repr(self._f) + + +def strip_ansi(value: str) -> str: + return _ansi_re.sub("", value) + + +def _is_jupyter_kernel_output(stream: t.IO[t.Any]) -> bool: + while isinstance(stream, (_FixupStream, _NonClosingTextIOWrapper)): + stream = stream._stream + + return stream.__class__.__module__.startswith("ipykernel.") + + +def should_strip_ansi( + stream: t.IO[t.Any] | None = None, color: bool | None = None +) -> bool: + if color is None: + if stream is None: + stream = sys.stdin + return not isatty(stream) and not _is_jupyter_kernel_output(stream) + return not color + + +# On Windows, wrap the output streams with colorama to support ANSI +# color codes. +# NOTE: double check is needed so mypy does not analyze this on Linux +if sys.platform.startswith("win") and WIN: + from ._winconsole import _get_windows_console_stream + + def _get_argv_encoding() -> str: + import locale + + return locale.getpreferredencoding() + + _ansi_stream_wrappers: cabc.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary() + + def auto_wrap_for_ansi(stream: t.TextIO, color: bool | None = None) -> t.TextIO: + """Support ANSI color and style codes on Windows by wrapping a + stream with colorama. + """ + try: + cached = _ansi_stream_wrappers.get(stream) + except Exception: + cached = None + + if cached is not None: + return cached + + import colorama + + strip = should_strip_ansi(stream, color) + ansi_wrapper = colorama.AnsiToWin32(stream, strip=strip) + rv = t.cast(t.TextIO, ansi_wrapper.stream) + _write = rv.write + + def _safe_write(s: str) -> int: + try: + return _write(s) + except BaseException: + ansi_wrapper.reset_all() + raise + + rv.write = _safe_write # type: ignore[method-assign] + + try: + _ansi_stream_wrappers[stream] = rv + except Exception: + pass + + return rv + +else: + + def _get_argv_encoding() -> str: + return getattr(sys.stdin, "encoding", None) or sys.getfilesystemencoding() + + def _get_windows_console_stream( + f: t.TextIO, encoding: str | None, errors: str | None + ) -> t.TextIO | None: + return None + + +def term_len(x: str) -> int: + return len(strip_ansi(x)) + + +def isatty(stream: t.IO[t.Any]) -> bool: + try: + return stream.isatty() + except Exception: + return False + + +def _make_cached_stream_func( + src_func: t.Callable[[], t.TextIO | None], + wrapper_func: t.Callable[[], t.TextIO], +) -> t.Callable[[], t.TextIO | None]: + cache: cabc.MutableMapping[t.TextIO, t.TextIO] = WeakKeyDictionary() + + def func() -> t.TextIO | None: + stream = src_func() + + if stream is None: + return None + + try: + rv = cache.get(stream) + except Exception: + rv = None + if rv is not None: + return rv + rv = wrapper_func() + try: + cache[stream] = rv + except Exception: + pass + return rv + + return func + + +_default_text_stdin = _make_cached_stream_func(lambda: sys.stdin, get_text_stdin) +_default_text_stdout = _make_cached_stream_func(lambda: sys.stdout, get_text_stdout) +_default_text_stderr = _make_cached_stream_func(lambda: sys.stderr, get_text_stderr) + + +binary_streams: cabc.Mapping[str, t.Callable[[], t.BinaryIO]] = { + "stdin": get_binary_stdin, + "stdout": get_binary_stdout, + "stderr": get_binary_stderr, +} + +text_streams: cabc.Mapping[str, t.Callable[[str | None, str | None], t.TextIO]] = { + "stdin": get_text_stdin, + "stdout": get_text_stdout, + "stderr": get_text_stderr, +} diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/click/_termui_impl.py b/bioagent-bench/.venv/lib/python3.14/site-packages/click/_termui_impl.py new file mode 100644 index 0000000000000000000000000000000000000000..c9f1e1c530b3ac1b4ae2d08984a031c18e8f5778 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/click/_termui_impl.py @@ -0,0 +1,870 @@ +""" +This module contains implementations for the termui module. To keep the +import time of Click down, some infrequently used functionality is +placed in this module and only imported as needed. +""" + +from __future__ import annotations + +import collections.abc as cabc +import contextlib +import math +import os +import shlex +import sys +import time +import typing as t +from gettext import gettext as _ +from io import StringIO +from pathlib import Path +from types import TracebackType + +from ._compat import _default_text_stdout +from ._compat import CYGWIN +from ._compat import get_best_encoding +from ._compat import isatty +from ._compat import open_stream +from ._compat import strip_ansi +from ._compat import term_len +from ._compat import WIN +from .exceptions import ClickException +from .utils import echo + +V = t.TypeVar("V") + +if os.name == "nt": + BEFORE_BAR = "\r" + AFTER_BAR = "\n" +else: + BEFORE_BAR = "\r\033[?25l" + AFTER_BAR = "\033[?25h\n" + + +class ProgressBar(t.Generic[V]): + def __init__( + self, + iterable: cabc.Iterable[V] | None, + length: int | None = None, + fill_char: str = "#", + empty_char: str = " ", + bar_template: str = "%(bar)s", + info_sep: str = " ", + hidden: bool = False, + show_eta: bool = True, + show_percent: bool | None = None, + show_pos: bool = False, + item_show_func: t.Callable[[V | None], str | None] | None = None, + label: str | None = None, + file: t.TextIO | None = None, + color: bool | None = None, + update_min_steps: int = 1, + width: int = 30, + ) -> None: + self.fill_char = fill_char + self.empty_char = empty_char + self.bar_template = bar_template + self.info_sep = info_sep + self.hidden = hidden + self.show_eta = show_eta + self.show_percent = show_percent + self.show_pos = show_pos + self.item_show_func = item_show_func + self.label: str = label or "" + + if file is None: + file = _default_text_stdout() + + # There are no standard streams attached to write to. For example, + # pythonw on Windows. + if file is None: + file = StringIO() + + self.file = file + self.color = color + self.update_min_steps = update_min_steps + self._completed_intervals = 0 + self.width: int = width + self.autowidth: bool = width == 0 + + if length is None: + from operator import length_hint + + length = length_hint(iterable, -1) + + if length == -1: + length = None + if iterable is None: + if length is None: + raise TypeError("iterable or length is required") + iterable = t.cast("cabc.Iterable[V]", range(length)) + self.iter: cabc.Iterable[V] = iter(iterable) + self.length = length + self.pos: int = 0 + self.avg: list[float] = [] + self.last_eta: float + self.start: float + self.start = self.last_eta = time.time() + self.eta_known: bool = False + self.finished: bool = False + self.max_width: int | None = None + self.entered: bool = False + self.current_item: V | None = None + self._is_atty = isatty(self.file) + self._last_line: str | None = None + + def __enter__(self) -> ProgressBar[V]: + self.entered = True + self.render_progress() + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> None: + self.render_finish() + + def __iter__(self) -> cabc.Iterator[V]: + if not self.entered: + raise RuntimeError("You need to use progress bars in a with block.") + self.render_progress() + return self.generator() + + def __next__(self) -> V: + # Iteration is defined in terms of a generator function, + # returned by iter(self); use that to define next(). This works + # because `self.iter` is an iterable consumed by that generator, + # so it is re-entry safe. Calling `next(self.generator())` + # twice works and does "what you want". + return next(iter(self)) + + def render_finish(self) -> None: + if self.hidden or not self._is_atty: + return + self.file.write(AFTER_BAR) + self.file.flush() + + @property + def pct(self) -> float: + if self.finished: + return 1.0 + return min(self.pos / (float(self.length or 1) or 1), 1.0) + + @property + def time_per_iteration(self) -> float: + if not self.avg: + return 0.0 + return sum(self.avg) / float(len(self.avg)) + + @property + def eta(self) -> float: + if self.length is not None and not self.finished: + return self.time_per_iteration * (self.length - self.pos) + return 0.0 + + def format_eta(self) -> str: + if self.eta_known: + t = int(self.eta) + seconds = t % 60 + t //= 60 + minutes = t % 60 + t //= 60 + hours = t % 24 + t //= 24 + if t > 0: + return f"{t}d {hours:02}:{minutes:02}:{seconds:02}" + else: + return f"{hours:02}:{minutes:02}:{seconds:02}" + return "" + + def format_pos(self) -> str: + pos = str(self.pos) + if self.length is not None: + pos += f"/{self.length}" + return pos + + def format_pct(self) -> str: + return f"{int(self.pct * 100): 4}%"[1:] + + def format_bar(self) -> str: + if self.length is not None: + bar_length = int(self.pct * self.width) + bar = self.fill_char * bar_length + bar += self.empty_char * (self.width - bar_length) + elif self.finished: + bar = self.fill_char * self.width + else: + chars = list(self.empty_char * (self.width or 1)) + if self.time_per_iteration != 0: + chars[ + int( + (math.cos(self.pos * self.time_per_iteration) / 2.0 + 0.5) + * self.width + ) + ] = self.fill_char + bar = "".join(chars) + return bar + + def format_progress_line(self) -> str: + show_percent = self.show_percent + + info_bits = [] + if self.length is not None and show_percent is None: + show_percent = not self.show_pos + + if self.show_pos: + info_bits.append(self.format_pos()) + if show_percent: + info_bits.append(self.format_pct()) + if self.show_eta and self.eta_known and not self.finished: + info_bits.append(self.format_eta()) + if self.item_show_func is not None: + item_info = self.item_show_func(self.current_item) + if item_info is not None: + info_bits.append(item_info) + + return ( + self.bar_template + % { + "label": self.label, + "bar": self.format_bar(), + "info": self.info_sep.join(info_bits), + } + ).rstrip() + + def render_progress(self) -> None: + if self.hidden: + return + + if not self._is_atty: + # Only output the label once if the output is not a TTY. + if self._last_line != self.label: + self._last_line = self.label + echo(self.label, file=self.file, color=self.color) + return + + buf = [] + # Update width in case the terminal has been resized + if self.autowidth: + import shutil + + old_width = self.width + self.width = 0 + clutter_length = term_len(self.format_progress_line()) + new_width = max(0, shutil.get_terminal_size().columns - clutter_length) + if new_width < old_width and self.max_width is not None: + buf.append(BEFORE_BAR) + buf.append(" " * self.max_width) + self.max_width = new_width + self.width = new_width + + clear_width = self.width + if self.max_width is not None: + clear_width = self.max_width + + buf.append(BEFORE_BAR) + line = self.format_progress_line() + line_len = term_len(line) + if self.max_width is None or self.max_width < line_len: + self.max_width = line_len + + buf.append(line) + buf.append(" " * (clear_width - line_len)) + line = "".join(buf) + # Render the line only if it changed. + + if line != self._last_line: + self._last_line = line + echo(line, file=self.file, color=self.color, nl=False) + self.file.flush() + + def make_step(self, n_steps: int) -> None: + self.pos += n_steps + if self.length is not None and self.pos >= self.length: + self.finished = True + + if (time.time() - self.last_eta) < 1.0: + return + + self.last_eta = time.time() + + # self.avg is a rolling list of length <= 7 of steps where steps are + # defined as time elapsed divided by the total progress through + # self.length. + if self.pos: + step = (time.time() - self.start) / self.pos + else: + step = time.time() - self.start + + self.avg = self.avg[-6:] + [step] + + self.eta_known = self.length is not None + + def update(self, n_steps: int, current_item: V | None = None) -> None: + """Update the progress bar by advancing a specified number of + steps, and optionally set the ``current_item`` for this new + position. + + :param n_steps: Number of steps to advance. + :param current_item: Optional item to set as ``current_item`` + for the updated position. + + .. versionchanged:: 8.0 + Added the ``current_item`` optional parameter. + + .. versionchanged:: 8.0 + Only render when the number of steps meets the + ``update_min_steps`` threshold. + """ + if current_item is not None: + self.current_item = current_item + + self._completed_intervals += n_steps + + if self._completed_intervals >= self.update_min_steps: + self.make_step(self._completed_intervals) + self.render_progress() + self._completed_intervals = 0 + + def finish(self) -> None: + self.eta_known = False + self.current_item = None + self.finished = True + + def generator(self) -> cabc.Iterator[V]: + """Return a generator which yields the items added to the bar + during construction, and updates the progress bar *after* the + yielded block returns. + """ + # WARNING: the iterator interface for `ProgressBar` relies on + # this and only works because this is a simple generator which + # doesn't create or manage additional state. If this function + # changes, the impact should be evaluated both against + # `iter(bar)` and `next(bar)`. `next()` in particular may call + # `self.generator()` repeatedly, and this must remain safe in + # order for that interface to work. + if not self.entered: + raise RuntimeError("You need to use progress bars in a with block.") + + if not self._is_atty: + yield from self.iter + else: + for rv in self.iter: + self.current_item = rv + + # This allows show_item_func to be updated before the + # item is processed. Only trigger at the beginning of + # the update interval. + if self._completed_intervals == 0: + self.render_progress() + + yield rv + self.update(1) + + self.finish() + self.render_progress() + + +def pager(generator: cabc.Iterable[str], color: bool | None = None) -> None: + """Decide what method to use for paging through text.""" + stdout = _default_text_stdout() + + # There are no standard streams attached to write to. For example, + # pythonw on Windows. + if stdout is None: + stdout = StringIO() + + if not isatty(sys.stdin) or not isatty(stdout): + return _nullpager(stdout, generator, color) + + # Split using POSIX mode (the default) so that quote characters are + # stripped from tokens and quoted Windows paths are preserved. + # Non-POSIX mode retains quotes in tokens, and wrapping tokens + # with shlex.quote re-introduces quoting issues on Windows. + pager_cmd_parts = shlex.split(os.environ.get("PAGER", "")) + if pager_cmd_parts: + if WIN: + if _tempfilepager(generator, pager_cmd_parts, color): + return + elif _pipepager(generator, pager_cmd_parts, color): + return + + if os.environ.get("TERM") in ("dumb", "emacs"): + return _nullpager(stdout, generator, color) + if (WIN or sys.platform.startswith("os2")) and _tempfilepager( + generator, ["more"], color + ): + return + if _pipepager(generator, ["less"], color): + return + + import tempfile + + fd, filename = tempfile.mkstemp() + os.close(fd) + try: + if _pipepager(generator, ["more"], color): + return + return _nullpager(stdout, generator, color) + finally: + os.unlink(filename) + + +def _pipepager( + generator: cabc.Iterable[str], cmd_parts: list[str], color: bool | None +) -> bool: + """Page through text by feeding it to another program. + + Invokes the pager via :class:`subprocess.Popen` with an ``argv`` list + produced by :func:`shlex.split`. The command is resolved to an absolute + path with :func:`shutil.which` as recommended by the + :mod:`subprocess` docs for Windows compatibility. + + Invoking a pager through this might support colors: if piping to + ``less`` and the user hasn't decided on colors, ``LESS=-R`` is set + automatically. + + Returns ``True`` if the command was found and executed, ``False`` + otherwise so another pager can be attempted. + """ + # Split the command into the invoked CLI and its parameters. + if not cmd_parts: + return False + + import shutil + + cmd = cmd_parts[0] + cmd_params = cmd_parts[1:] + + cmd_filepath = shutil.which(cmd) + if not cmd_filepath: + return False + + # Produces a normalized absolute path string. + # multi-call binaries such as busybox derive their identity from the symlink + # less -> busybox. resolve() causes them to misbehave. (eg. less becomes busybox) + cmd_path = Path(cmd_filepath).absolute() + cmd_name = cmd_path.name + + import subprocess + + # Make a local copy of the environment to not affect the global one. + env = dict(os.environ) + + # If we're piping to less and the user hasn't decided on colors, we enable + # them by default we find the -R flag in the command line arguments. + if color is None and cmd_name == "less": + less_flags = f"{os.environ.get('LESS', '')}{' '.join(cmd_params)}" + if not less_flags: + env["LESS"] = "-R" + color = True + elif "r" in less_flags or "R" in less_flags: + color = True + + c = subprocess.Popen( + [str(cmd_path)] + cmd_params, + shell=False, + stdin=subprocess.PIPE, + env=env, + errors="replace", + text=True, + ) + assert c.stdin is not None + try: + for text in generator: + if not color: + text = strip_ansi(text) + + c.stdin.write(text) + except BrokenPipeError: + # In case the pager exited unexpectedly, ignore the broken pipe error. + pass + except Exception as e: + # In case there is an exception we want to close the pager immediately + # and let the caller handle it. + # Otherwise the pager will keep running, and the user may not notice + # the error message, or worse yet it may leave the terminal in a broken state. + c.terminate() + raise e + finally: + # We must close stdin and wait for the pager to exit before we continue + try: + c.stdin.close() + # Close implies flush, so it might throw a BrokenPipeError if the pager + # process exited already. + except BrokenPipeError: + pass + + # Less doesn't respect ^C, but catches it for its own UI purposes (aborting + # search or other commands inside less). + # + # That means when the user hits ^C, the parent process (click) terminates, + # but less is still alive, paging the output and messing up the terminal. + # + # If the user wants to make the pager exit on ^C, they should set + # `LESS='-K'`. It's not our decision to make. + while True: + try: + c.wait() + except KeyboardInterrupt: + pass + else: + break + + return True + + +def _tempfilepager( + generator: cabc.Iterable[str], cmd_parts: list[str], color: bool | None +) -> bool: + """Page through text by invoking a program on a temporary file. + + Used as the primary pager strategy on Windows (where piping to + ``more`` adds spurious ``\\r\\n``), and as a fallback on other + platforms. The command is resolved to an absolute path with + :func:`shutil.which`. + + Returns ``True`` if the command was found and executed, ``False`` + otherwise so another pager can be attempted. + """ + # Split the command into the invoked CLI and its parameters. + if not cmd_parts: + return False + + import shutil + + cmd = cmd_parts[0] + + cmd_filepath = shutil.which(cmd) + if not cmd_filepath: + return False + # Produces a normalized absolute path string. + # multi-call binaries such as busybox derive their identity from the symlink + # less -> busybox. resolve() causes them to misbehave. (eg. less becomes busybox) + cmd_path = Path(cmd_filepath).absolute() + + import subprocess + import tempfile + + fd, filename = tempfile.mkstemp() + # TODO: This never terminates if the passed generator never terminates. + text = "".join(generator) + if not color: + text = strip_ansi(text) + encoding = get_best_encoding(sys.stdout) + with open_stream(filename, "wb")[0] as f: + f.write(text.encode(encoding)) + try: + subprocess.call([str(cmd_path), filename]) + except OSError: + # Command not found + pass + finally: + os.close(fd) + os.unlink(filename) + + return True + + +def _nullpager( + stream: t.TextIO, generator: cabc.Iterable[str], color: bool | None +) -> None: + """Simply print unformatted text. This is the ultimate fallback.""" + for text in generator: + if not color: + text = strip_ansi(text) + stream.write(text) + + +class Editor: + def __init__( + self, + editor: str | None = None, + env: cabc.Mapping[str, str] | None = None, + require_save: bool = True, + extension: str = ".txt", + ) -> None: + self.editor = editor + self.env = env + self.require_save = require_save + self.extension = extension + + def get_editor(self) -> str: + if self.editor is not None: + return self.editor + for key in "VISUAL", "EDITOR": + rv = os.environ.get(key) + if rv: + return rv + if WIN: + return "notepad" + + from shutil import which + + for editor in "sensible-editor", "vim", "nano": + if which(editor) is not None: + return editor + return "vi" + + def edit_files(self, filenames: cabc.Iterable[str]) -> None: + """Open files in the user's editor.""" + import shlex + import subprocess + + editor = self.get_editor() + environ: dict[str, str] | None = None + + if self.env: + environ = os.environ.copy() + environ.update(self.env) + + try: + # Split in POSIX mode (the default) for the same reasons as + # in pager(): strips quotes from tokens and preserves quoted + # Windows paths. + c = subprocess.Popen( + args=shlex.split(editor) + list(filenames), + env=environ, + ) + exit_code = c.wait() + if exit_code != 0: + raise ClickException( + _("{editor}: Editing failed").format(editor=editor) + ) + except OSError as e: + raise ClickException( + _("{editor}: Editing failed: {e}").format(editor=editor, e=e) + ) from e + + @t.overload + def edit(self, text: bytes | bytearray) -> bytes | None: ... + + # We cannot know whether or not the type expected is str or bytes when None + # is passed, so str is returned as that was what was done before. + @t.overload + def edit(self, text: str | None) -> str | None: ... + + def edit(self, text: str | bytes | bytearray | None) -> str | bytes | None: + import tempfile + + if text is None: + data: bytes | bytearray = b"" + elif isinstance(text, (bytes, bytearray)): + data = text + else: + if text and not text.endswith("\n"): + text += "\n" + + if WIN: + data = text.replace("\n", "\r\n").encode("utf-8-sig") + else: + data = text.encode("utf-8") + + fd, name = tempfile.mkstemp(prefix="editor-", suffix=self.extension) + f: t.BinaryIO + + try: + with os.fdopen(fd, "wb") as f: + f.write(data) + + # If the filesystem resolution is 1 second, like Mac OS + # 10.12 Extended, or 2 seconds, like FAT32, and the editor + # closes very fast, require_save can fail. Set the modified + # time to be 2 seconds in the past to work around this. + os.utime(name, (os.path.getatime(name), os.path.getmtime(name) - 2)) + # Depending on the resolution, the exact value might not be + # recorded, so get the new recorded value. + timestamp = os.path.getmtime(name) + + self.edit_files((name,)) + + if self.require_save and os.path.getmtime(name) == timestamp: + return None + + with open(name, "rb") as f: + rv = f.read() + + if isinstance(text, (bytes, bytearray)): + return rv + + return rv.decode("utf-8-sig").replace("\r\n", "\n") + finally: + os.unlink(name) + + +def open_url(url: str, wait: bool = False, locate: bool = False) -> int: + import subprocess + + def _unquote_file(url: str) -> str: + from urllib.parse import unquote + + if url.startswith("file://"): + url = unquote(url[7:]) + + return url + + if sys.platform == "darwin": + args = ["open"] + if wait: + args.append("-W") + if locate: + args.append("-R") + args.append(_unquote_file(url)) + null = open("/dev/null", "w") + try: + return subprocess.Popen(args, stderr=null).wait() + finally: + null.close() + elif WIN: + if locate: + url = _unquote_file(url) + args = ["explorer", f"/select,{url}"] + else: + args = ["start"] + if wait: + args.append("/WAIT") + args.append("") + args.append(url) + try: + return subprocess.call(args) + except OSError: + # Command not found + return 127 + elif CYGWIN: + if locate: + url = _unquote_file(url) + args = ["cygstart", os.path.dirname(url)] + else: + args = ["cygstart"] + if wait: + args.append("-w") + args.append(url) + try: + return subprocess.call(args) + except OSError: + # Command not found + return 127 + + try: + if locate: + url = os.path.dirname(_unquote_file(url)) or "." + else: + url = _unquote_file(url) + c = subprocess.Popen(["xdg-open", url]) + if wait: + return c.wait() + return 0 + except OSError: + if url.startswith(("http://", "https://")) and not locate and not wait: + import webbrowser + + webbrowser.open(url) + return 0 + return 1 + + +def _translate_ch_to_exc(ch: str) -> None: + if ch == "\x03": + raise KeyboardInterrupt() + + if ch == "\x04" and not WIN: # Unix-like, Ctrl+D + raise EOFError() + + if ch == "\x1a" and WIN: # Windows, Ctrl+Z + raise EOFError() + + +if sys.platform == "win32": + import msvcrt + + @contextlib.contextmanager + def raw_terminal() -> cabc.Iterator[int]: + yield -1 + + def getchar(echo: bool) -> str: + # The function `getch` will return a bytes object corresponding to + # the pressed character. Since Windows 10 build 1803, it will also + # return \x00 when called a second time after pressing a regular key. + # + # `getwch` does not share this probably-bugged behavior. Moreover, it + # returns a Unicode object by default, which is what we want. + # + # Either of these functions will return \x00 or \xe0 to indicate + # a special key, and you need to call the same function again to get + # the "rest" of the code. The fun part is that \u00e0 is + # "latin small letter a with grave", so if you type that on a French + # keyboard, you _also_ get a \xe0. + # E.g., consider the Up arrow. This returns \xe0 and then \x48. The + # resulting Unicode string reads as "a with grave" + "capital H". + # This is indistinguishable from when the user actually types + # "a with grave" and then "capital H". + # + # When \xe0 is returned, we assume it's part of a special-key sequence + # and call `getwch` again, but that means that when the user types + # the \u00e0 character, `getchar` doesn't return until a second + # character is typed. + # The alternative is returning immediately, but that would mess up + # cross-platform handling of arrow keys and others that start with + # \xe0. Another option is using `getch`, but then we can't reliably + # read non-ASCII characters, because return values of `getch` are + # limited to the current 8-bit codepage. + # + # Anyway, Click doesn't claim to do this Right(tm), and using `getwch` + # is doing the right thing in more situations than with `getch`. + + if echo: + func = t.cast(t.Callable[[], str], msvcrt.getwche) + else: + func = t.cast(t.Callable[[], str], msvcrt.getwch) + + rv = func() + + if rv in ("\x00", "\xe0"): + # \x00 and \xe0 are control characters that indicate special key, + # see above. + rv += func() + + _translate_ch_to_exc(rv) + return rv + +else: + import termios + import tty + + @contextlib.contextmanager + def raw_terminal() -> cabc.Iterator[int]: + f: t.TextIO | None + fd: int + + if not isatty(sys.stdin): + f = open("/dev/tty") + fd = f.fileno() + else: + fd = sys.stdin.fileno() + f = None + + try: + old_settings = termios.tcgetattr(fd) + + try: + tty.setraw(fd) + yield fd + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) + sys.stdout.flush() + + if f is not None: + f.close() + except termios.error: + pass + + def getchar(echo: bool) -> str: + with raw_terminal() as fd: + ch = os.read(fd, 32).decode(get_best_encoding(sys.stdin), "replace") + + if echo and isatty(sys.stdout): + sys.stdout.write(ch) + + _translate_ch_to_exc(ch) + return ch diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/click/_textwrap.py b/bioagent-bench/.venv/lib/python3.14/site-packages/click/_textwrap.py new file mode 100644 index 0000000000000000000000000000000000000000..97fbee3dc6ce372600e702d38d9139bcf6a40bec --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/click/_textwrap.py @@ -0,0 +1,51 @@ +from __future__ import annotations + +import collections.abc as cabc +import textwrap +from contextlib import contextmanager + + +class TextWrapper(textwrap.TextWrapper): + def _handle_long_word( + self, + reversed_chunks: list[str], + cur_line: list[str], + cur_len: int, + width: int, + ) -> None: + space_left = max(width - cur_len, 1) + + if self.break_long_words: + last = reversed_chunks[-1] + cut = last[:space_left] + res = last[space_left:] + cur_line.append(cut) + reversed_chunks[-1] = res + elif not cur_line: + cur_line.append(reversed_chunks.pop()) + + @contextmanager + def extra_indent(self, indent: str) -> cabc.Iterator[None]: + old_initial_indent = self.initial_indent + old_subsequent_indent = self.subsequent_indent + self.initial_indent += indent + self.subsequent_indent += indent + + try: + yield + finally: + self.initial_indent = old_initial_indent + self.subsequent_indent = old_subsequent_indent + + def indent_only(self, text: str) -> str: + rv = [] + + for idx, line in enumerate(text.splitlines()): + indent = self.initial_indent + + if idx > 0: + indent = self.subsequent_indent + + rv.append(f"{indent}{line}") + + return "\n".join(rv) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/click/_utils.py b/bioagent-bench/.venv/lib/python3.14/site-packages/click/_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..09fb00855e68840d18187f3de3a604e7defc37e8 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/click/_utils.py @@ -0,0 +1,36 @@ +from __future__ import annotations + +import enum +import typing as t + + +class Sentinel(enum.Enum): + """Enum used to define sentinel values. + + .. seealso:: + + `PEP 661 - Sentinel Values `_. + """ + + UNSET = object() + FLAG_NEEDS_VALUE = object() + + def __repr__(self) -> str: + return f"{self.__class__.__name__}.{self.name}" + + +UNSET = Sentinel.UNSET +"""Sentinel used to indicate that a value is not set.""" + +FLAG_NEEDS_VALUE = Sentinel.FLAG_NEEDS_VALUE +"""Sentinel used to indicate an option was passed as a flag without a +value but is not a flag option. + +``Option.consume_value`` uses this to prompt or use the ``flag_value``. +""" + +T_UNSET = t.Literal[UNSET] # type: ignore[valid-type] +"""Type hint for the :data:`UNSET` sentinel value.""" + +T_FLAG_NEEDS_VALUE = t.Literal[FLAG_NEEDS_VALUE] # type: ignore[valid-type] +"""Type hint for the :data:`FLAG_NEEDS_VALUE` sentinel value.""" diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/click/_winconsole.py b/bioagent-bench/.venv/lib/python3.14/site-packages/click/_winconsole.py new file mode 100644 index 0000000000000000000000000000000000000000..e56c7c6ae7ee7e577db6ace659cd58c0c652074c --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/click/_winconsole.py @@ -0,0 +1,296 @@ +# This module is based on the excellent work by Adam Bartoš who +# provided a lot of what went into the implementation here in +# the discussion to issue1602 in the Python bug tracker. +# +# There are some general differences in regards to how this works +# compared to the original patches as we do not need to patch +# the entire interpreter but just work in our little world of +# echo and prompt. +from __future__ import annotations + +import collections.abc as cabc +import io +import sys +import time +import typing as t +from ctypes import Array +from ctypes import byref +from ctypes import c_char +from ctypes import c_char_p +from ctypes import c_int +from ctypes import c_ssize_t +from ctypes import c_ulong +from ctypes import c_void_p +from ctypes import POINTER +from ctypes import py_object +from ctypes import Structure +from ctypes.wintypes import DWORD +from ctypes.wintypes import HANDLE +from ctypes.wintypes import LPCWSTR +from ctypes.wintypes import LPWSTR + +from ._compat import _NonClosingTextIOWrapper + +assert sys.platform == "win32" +import msvcrt # noqa: E402 +from ctypes import windll # noqa: E402 +from ctypes import WINFUNCTYPE # noqa: E402 + +c_ssize_p = POINTER(c_ssize_t) + +kernel32 = windll.kernel32 +GetStdHandle = kernel32.GetStdHandle +ReadConsoleW = kernel32.ReadConsoleW +WriteConsoleW = kernel32.WriteConsoleW +GetConsoleMode = kernel32.GetConsoleMode +GetLastError = kernel32.GetLastError +GetCommandLineW = WINFUNCTYPE(LPWSTR)(("GetCommandLineW", windll.kernel32)) +CommandLineToArgvW = WINFUNCTYPE(POINTER(LPWSTR), LPCWSTR, POINTER(c_int))( + ("CommandLineToArgvW", windll.shell32) +) +LocalFree = WINFUNCTYPE(c_void_p, c_void_p)(("LocalFree", windll.kernel32)) + +STDIN_HANDLE = GetStdHandle(-10) +STDOUT_HANDLE = GetStdHandle(-11) +STDERR_HANDLE = GetStdHandle(-12) + +PyBUF_SIMPLE = 0 +PyBUF_WRITABLE = 1 + +ERROR_SUCCESS = 0 +ERROR_NOT_ENOUGH_MEMORY = 8 +ERROR_OPERATION_ABORTED = 995 + +STDIN_FILENO = 0 +STDOUT_FILENO = 1 +STDERR_FILENO = 2 + +EOF = b"\x1a" +MAX_BYTES_WRITTEN = 32767 + +if t.TYPE_CHECKING: + try: + # Using `typing_extensions.Buffer` instead of `collections.abc` + # on Windows for some reason does not have `Sized` implemented. + from collections.abc import Buffer # type: ignore + except ImportError: + from typing_extensions import Buffer + +try: + from ctypes import pythonapi +except ImportError: + # On PyPy we cannot get buffers so our ability to operate here is + # severely limited. + get_buffer = None +else: + + class Py_buffer(Structure): + _fields_ = [ # noqa: RUF012 + ("buf", c_void_p), + ("obj", py_object), + ("len", c_ssize_t), + ("itemsize", c_ssize_t), + ("readonly", c_int), + ("ndim", c_int), + ("format", c_char_p), + ("shape", c_ssize_p), + ("strides", c_ssize_p), + ("suboffsets", c_ssize_p), + ("internal", c_void_p), + ] + + PyObject_GetBuffer = pythonapi.PyObject_GetBuffer + PyBuffer_Release = pythonapi.PyBuffer_Release + + def get_buffer(obj: Buffer, writable: bool = False) -> Array[c_char]: + buf = Py_buffer() + flags: int = PyBUF_WRITABLE if writable else PyBUF_SIMPLE + PyObject_GetBuffer(py_object(obj), byref(buf), flags) + + try: + buffer_type = c_char * buf.len + out: Array[c_char] = buffer_type.from_address(buf.buf) + return out + finally: + PyBuffer_Release(byref(buf)) + + +class _WindowsConsoleRawIOBase(io.RawIOBase): + def __init__(self, handle: int | None) -> None: + self.handle = handle + + def isatty(self) -> t.Literal[True]: + super().isatty() + return True + + +class _WindowsConsoleReader(_WindowsConsoleRawIOBase): + def readable(self) -> t.Literal[True]: + return True + + def readinto(self, b: Buffer) -> int: + bytes_to_be_read = len(b) + if not bytes_to_be_read: + return 0 + elif bytes_to_be_read % 2: + raise ValueError( + "cannot read odd number of bytes from UTF-16-LE encoded console" + ) + + buffer = get_buffer(b, writable=True) + code_units_to_be_read = bytes_to_be_read // 2 + code_units_read = c_ulong() + + rv = ReadConsoleW( + HANDLE(self.handle), + buffer, + code_units_to_be_read, + byref(code_units_read), + None, + ) + if GetLastError() == ERROR_OPERATION_ABORTED: + # wait for KeyboardInterrupt + time.sleep(0.1) + if not rv: + raise OSError(f"Windows error: {GetLastError()}") + + if buffer[0] == EOF: + return 0 + return 2 * code_units_read.value + + +class _WindowsConsoleWriter(_WindowsConsoleRawIOBase): + def writable(self) -> t.Literal[True]: + return True + + @staticmethod + def _get_error_message(errno: int) -> str: + if errno == ERROR_SUCCESS: + return "ERROR_SUCCESS" + elif errno == ERROR_NOT_ENOUGH_MEMORY: + return "ERROR_NOT_ENOUGH_MEMORY" + return f"Windows error {errno}" + + def write(self, b: Buffer) -> int: + bytes_to_be_written = len(b) + buf = get_buffer(b) + code_units_to_be_written = min(bytes_to_be_written, MAX_BYTES_WRITTEN) // 2 + code_units_written = c_ulong() + + WriteConsoleW( + HANDLE(self.handle), + buf, + code_units_to_be_written, + byref(code_units_written), + None, + ) + bytes_written = 2 * code_units_written.value + + if bytes_written == 0 and bytes_to_be_written > 0: + raise OSError(self._get_error_message(GetLastError())) + return bytes_written + + +class ConsoleStream: + def __init__(self, text_stream: t.TextIO, byte_stream: t.BinaryIO) -> None: + self._text_stream = text_stream + self.buffer = byte_stream + + @property + def name(self) -> str: + return self.buffer.name + + def write(self, x: t.AnyStr) -> int: + if isinstance(x, str): + return self._text_stream.write(x) + try: + self.flush() + except Exception: + pass + return self.buffer.write(x) + + def writelines(self, lines: cabc.Iterable[t.AnyStr]) -> None: + for line in lines: + self.write(line) + + def __getattr__(self, name: str) -> t.Any: + return getattr(self._text_stream, name) + + def isatty(self) -> bool: + return self.buffer.isatty() + + def __repr__(self) -> str: + return f"" + + +def _get_text_stdin(buffer_stream: t.BinaryIO) -> t.TextIO: + text_stream = _NonClosingTextIOWrapper( + io.BufferedReader(_WindowsConsoleReader(STDIN_HANDLE)), + "utf-16-le", + "strict", + line_buffering=True, + ) + return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream)) + + +def _get_text_stdout(buffer_stream: t.BinaryIO) -> t.TextIO: + text_stream = _NonClosingTextIOWrapper( + io.BufferedWriter(_WindowsConsoleWriter(STDOUT_HANDLE)), + "utf-16-le", + "strict", + line_buffering=True, + ) + return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream)) + + +def _get_text_stderr(buffer_stream: t.BinaryIO) -> t.TextIO: + text_stream = _NonClosingTextIOWrapper( + io.BufferedWriter(_WindowsConsoleWriter(STDERR_HANDLE)), + "utf-16-le", + "strict", + line_buffering=True, + ) + return t.cast(t.TextIO, ConsoleStream(text_stream, buffer_stream)) + + +_stream_factories: cabc.Mapping[int, t.Callable[[t.BinaryIO], t.TextIO]] = { + 0: _get_text_stdin, + 1: _get_text_stdout, + 2: _get_text_stderr, +} + + +def _is_console(f: t.TextIO) -> bool: + if not hasattr(f, "fileno"): + return False + + try: + fileno = f.fileno() + except (OSError, io.UnsupportedOperation): + return False + + handle = msvcrt.get_osfhandle(fileno) + return bool(GetConsoleMode(handle, byref(DWORD()))) + + +def _get_windows_console_stream( + f: t.TextIO, encoding: str | None, errors: str | None +) -> t.TextIO | None: + if ( + get_buffer is None + or encoding not in {"utf-16-le", None} + or errors not in {"strict", None} + or not _is_console(f) + ): + return None + + func = _stream_factories.get(f.fileno()) + if func is None: + return None + + b = getattr(f, "buffer", None) + + if b is None: + return None + + return func(b) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/click/core.py b/bioagent-bench/.venv/lib/python3.14/site-packages/click/core.py new file mode 100644 index 0000000000000000000000000000000000000000..d940dd80e1d21cc5ef9bbb5df51f908629229428 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/click/core.py @@ -0,0 +1,3476 @@ +from __future__ import annotations + +import collections.abc as cabc +import enum +import errno +import inspect +import os +import sys +import typing as t +from collections import abc +from collections import Counter +from contextlib import AbstractContextManager +from contextlib import contextmanager +from contextlib import ExitStack +from functools import update_wrapper +from gettext import gettext as _ +from gettext import ngettext +from itertools import repeat +from types import TracebackType + +from . import types +from ._utils import FLAG_NEEDS_VALUE +from ._utils import UNSET +from .exceptions import Abort +from .exceptions import BadParameter +from .exceptions import ClickException +from .exceptions import Exit +from .exceptions import MissingParameter +from .exceptions import NoArgsIsHelpError +from .exceptions import UsageError +from .formatting import HelpFormatter +from .formatting import join_options +from .globals import pop_context +from .globals import push_context +from .parser import _OptionParser +from .parser import _split_opt +from .termui import confirm +from .termui import prompt +from .termui import style +from .utils import _detect_program_name +from .utils import _expand_args +from .utils import echo +from .utils import make_default_short_help +from .utils import make_str +from .utils import PacifyFlushWrapper + +if t.TYPE_CHECKING: + from .shell_completion import CompletionItem + +F = t.TypeVar("F", bound="t.Callable[..., t.Any]") +V = t.TypeVar("V") + + +def _complete_visible_commands( + ctx: Context, incomplete: str +) -> cabc.Iterator[tuple[str, Command]]: + """List all the subcommands of a group that start with the + incomplete value and aren't hidden. + + :param ctx: Invocation context for the group. + :param incomplete: Value being completed. May be empty. + """ + multi = t.cast(Group, ctx.command) + + for name in multi.list_commands(ctx): + if name.startswith(incomplete): + command = multi.get_command(ctx, name) + + if command is not None and not command.hidden: + yield name, command + + +def _check_nested_chain( + base_command: Group, cmd_name: str, cmd: Command, register: bool = False +) -> None: + if not base_command.chain or not isinstance(cmd, Group): + return + + if register: + message = ( + f"It is not possible to add the group {cmd_name!r} to another" + f" group {base_command.name!r} that is in chain mode." + ) + else: + message = ( + f"Found the group {cmd_name!r} as subcommand to another group " + f" {base_command.name!r} that is in chain mode. This is not supported." + ) + + raise RuntimeError(message) + + +def batch(iterable: cabc.Iterable[V], batch_size: int) -> list[tuple[V, ...]]: + return list(zip(*repeat(iter(iterable), batch_size), strict=False)) + + +@contextmanager +def augment_usage_errors( + ctx: Context, param: Parameter | None = None +) -> cabc.Iterator[None]: + """Context manager that attaches extra information to exceptions.""" + try: + yield + except BadParameter as e: + if e.ctx is None: + e.ctx = ctx + if param is not None and e.param is None: + e.param = param + raise + except UsageError as e: + if e.ctx is None: + e.ctx = ctx + raise + + +def iter_params_for_processing( + invocation_order: cabc.Sequence[Parameter], + declaration_order: cabc.Sequence[Parameter], +) -> list[Parameter]: + """Returns all declared parameters in the order they should be processed. + + The declared parameters are re-shuffled depending on the order in which + they were invoked, as well as the eagerness of each parameters. + + The invocation order takes precedence over the declaration order. I.e. the + order in which the user provided them to the CLI is respected. + + This behavior and its effect on callback evaluation is detailed at: + https://click.palletsprojects.com/en/stable/advanced/#callback-evaluation-order + """ + + def sort_key(item: Parameter) -> tuple[bool, float]: + try: + idx: float = invocation_order.index(item) + except ValueError: + idx = float("inf") + + return not item.is_eager, idx + + return sorted(declaration_order, key=sort_key) + + +class ParameterSource(enum.IntEnum): + """This is an :class:`~enum.IntEnum` that indicates the source of a + parameter's value. + + Use :meth:`click.Context.get_parameter_source` to get the + source for a parameter by name. + + Members are ordered from most explicit to least explicit source. + This allows comparison to check if a value was explicitly provided: + + .. code-block:: python + + source = ctx.get_parameter_source("port") + if source < click.ParameterSource.DEFAULT_MAP: + ... # value was explicitly set + + .. versionchanged:: 8.3.3 + Use :class:`~enum.IntEnum` and reorder members from most to + least explicit. Supports comparison operators. + + .. versionchanged:: 8.0 + Use :class:`~enum.Enum` and drop the ``validate`` method. + + .. versionchanged:: 8.0 + Added the ``PROMPT`` value. + """ + + PROMPT = enum.auto() + """Used a prompt to confirm a default or provide a value.""" + COMMANDLINE = enum.auto() + """The value was provided by the command line args.""" + ENVIRONMENT = enum.auto() + """The value was provided with an environment variable.""" + DEFAULT_MAP = enum.auto() + """Used a default provided by :attr:`Context.default_map`.""" + DEFAULT = enum.auto() + """Used the default specified by the parameter.""" + + +class Context: + """The context is a special internal object that holds state relevant + for the script execution at every single level. It's normally invisible + to commands unless they opt-in to getting access to it. + + The context is useful as it can pass internal objects around and can + control special execution features such as reading data from + environment variables. + + A context can be used as context manager in which case it will call + :meth:`close` on teardown. + + :param command: the command class for this context. + :param parent: the parent context. + :param info_name: the info name for this invocation. Generally this + is the most descriptive name for the script or + command. For the toplevel script it is usually + the name of the script, for commands below it it's + the name of the script. + :param obj: an arbitrary object of user data. + :param auto_envvar_prefix: the prefix to use for automatic environment + variables. If this is `None` then reading + from environment variables is disabled. This + does not affect manually set environment + variables which are always read. + :param default_map: a dictionary (like object) with default values + for parameters. + :param terminal_width: the width of the terminal. The default is + inherit from parent context. If no context + defines the terminal width then auto + detection will be applied. + :param max_content_width: the maximum width for content rendered by + Click (this currently only affects help + pages). This defaults to 80 characters if + not overridden. In other words: even if the + terminal is larger than that, Click will not + format things wider than 80 characters by + default. In addition to that, formatters might + add some safety mapping on the right. + :param resilient_parsing: if this flag is enabled then Click will + parse without any interactivity or callback + invocation. Default values will also be + ignored. This is useful for implementing + things such as completion support. + :param allow_extra_args: if this is set to `True` then extra arguments + at the end will not raise an error and will be + kept on the context. The default is to inherit + from the command. + :param allow_interspersed_args: if this is set to `False` then options + and arguments cannot be mixed. The + default is to inherit from the command. + :param ignore_unknown_options: instructs click to ignore options it does + not know and keeps them for later + processing. + :param help_option_names: optionally a list of strings that define how + the default help parameter is named. The + default is ``['--help']``. + :param token_normalize_func: an optional function that is used to + normalize tokens (options, choices, + etc.). This for instance can be used to + implement case insensitive behavior. + :param color: controls if the terminal supports ANSI colors or not. The + default is autodetection. This is only needed if ANSI + codes are used in texts that Click prints which is by + default not the case. This for instance would affect + help output. + :param show_default: Show the default value for commands. If this + value is not set, it defaults to the value from the parent + context. ``Command.show_default`` overrides this default for the + specific command. + + .. versionchanged:: 8.2 + The ``protected_args`` attribute is deprecated and will be removed in + Click 9.0. ``args`` will contain remaining unparsed tokens. + + .. versionchanged:: 8.1 + The ``show_default`` parameter is overridden by + ``Command.show_default``, instead of the other way around. + + .. versionchanged:: 8.0 + The ``show_default`` parameter defaults to the value from the + parent context. + + .. versionchanged:: 7.1 + Added the ``show_default`` parameter. + + .. versionchanged:: 4.0 + Added the ``color``, ``ignore_unknown_options``, and + ``max_content_width`` parameters. + + .. versionchanged:: 3.0 + Added the ``allow_extra_args`` and ``allow_interspersed_args`` + parameters. + + .. versionchanged:: 2.0 + Added the ``resilient_parsing``, ``help_option_names``, and + ``token_normalize_func`` parameters. + """ + + #: The formatter class to create with :meth:`make_formatter`. + #: + #: .. versionadded:: 8.0 + formatter_class: type[HelpFormatter] = HelpFormatter + + def __init__( + self, + command: Command, + parent: Context | None = None, + info_name: str | None = None, + obj: t.Any | None = None, + auto_envvar_prefix: str | None = None, + default_map: cabc.MutableMapping[str, t.Any] | None = None, + terminal_width: int | None = None, + max_content_width: int | None = None, + resilient_parsing: bool = False, + allow_extra_args: bool | None = None, + allow_interspersed_args: bool | None = None, + ignore_unknown_options: bool | None = None, + help_option_names: list[str] | None = None, + token_normalize_func: t.Callable[[str], str] | None = None, + color: bool | None = None, + show_default: bool | None = None, + ) -> None: + #: the parent context or `None` if none exists. + self.parent = parent + #: the :class:`Command` for this context. + self.command = command + #: the descriptive information name + self.info_name = info_name + #: Map of parameter names to their parsed values. Parameters + #: with ``expose_value=False`` are not stored. + self.params: dict[str, t.Any] = {} + #: the leftover arguments. + self.args: list[str] = [] + #: protected arguments. These are arguments that are prepended + #: to `args` when certain parsing scenarios are encountered but + #: must be never propagated to another arguments. This is used + #: to implement nested parsing. + self._protected_args: list[str] = [] + #: the collected prefixes of the command's options. + self._opt_prefixes: set[str] = set(parent._opt_prefixes) if parent else set() + + if obj is None and parent is not None: + obj = parent.obj + + #: the user object stored. + self.obj: t.Any = obj + self._meta: dict[str, t.Any] = getattr(parent, "meta", {}) + + #: A dictionary (-like object) with defaults for parameters. + if ( + default_map is None + and info_name is not None + and parent is not None + and parent.default_map is not None + ): + default_map = parent.default_map.get(info_name) + + self.default_map: cabc.MutableMapping[str, t.Any] | None = default_map + + #: This flag indicates if a subcommand is going to be executed. A + #: group callback can use this information to figure out if it's + #: being executed directly or because the execution flow passes + #: onwards to a subcommand. By default it's None, but it can be + #: the name of the subcommand to execute. + #: + #: If chaining is enabled this will be set to ``'*'`` in case + #: any commands are executed. It is however not possible to + #: figure out which ones. If you require this knowledge you + #: should use a :func:`result_callback`. + self.invoked_subcommand: str | None = None + + if terminal_width is None and parent is not None: + terminal_width = parent.terminal_width + + #: The width of the terminal (None is autodetection). + self.terminal_width: int | None = terminal_width + + if max_content_width is None and parent is not None: + max_content_width = parent.max_content_width + + #: The maximum width of formatted content (None implies a sensible + #: default which is 80 for most things). + self.max_content_width: int | None = max_content_width + + if allow_extra_args is None: + allow_extra_args = command.allow_extra_args + + #: Indicates if the context allows extra args or if it should + #: fail on parsing. + #: + #: .. versionadded:: 3.0 + self.allow_extra_args = allow_extra_args + + if allow_interspersed_args is None: + allow_interspersed_args = command.allow_interspersed_args + + #: Indicates if the context allows mixing of arguments and + #: options or not. + #: + #: .. versionadded:: 3.0 + self.allow_interspersed_args: bool = allow_interspersed_args + + if ignore_unknown_options is None: + ignore_unknown_options = command.ignore_unknown_options + + #: Instructs click to ignore options that a command does not + #: understand and will store it on the context for later + #: processing. This is primarily useful for situations where you + #: want to call into external programs. Generally this pattern is + #: strongly discouraged because it's not possibly to losslessly + #: forward all arguments. + #: + #: .. versionadded:: 4.0 + self.ignore_unknown_options: bool = ignore_unknown_options + + if help_option_names is None: + if parent is not None: + help_option_names = parent.help_option_names + else: + help_option_names = ["--help"] + + #: The names for the help options. + self.help_option_names: list[str] = help_option_names + + if token_normalize_func is None and parent is not None: + token_normalize_func = parent.token_normalize_func + + #: An optional normalization function for tokens. This is + #: options, choices, commands etc. + self.token_normalize_func: t.Callable[[str], str] | None = token_normalize_func + + #: Indicates if resilient parsing is enabled. In that case Click + #: will do its best to not cause any failures and default values + #: will be ignored. Useful for completion. + self.resilient_parsing: bool = resilient_parsing + + # If there is no envvar prefix yet, but the parent has one and + # the command on this level has a name, we can expand the envvar + # prefix automatically. + if auto_envvar_prefix is None: + if ( + parent is not None + and parent.auto_envvar_prefix is not None + and self.info_name is not None + ): + auto_envvar_prefix = ( + f"{parent.auto_envvar_prefix}_{self.info_name.upper()}" + ) + else: + auto_envvar_prefix = auto_envvar_prefix.upper() + + if auto_envvar_prefix is not None: + auto_envvar_prefix = auto_envvar_prefix.replace("-", "_") + + self.auto_envvar_prefix: str | None = auto_envvar_prefix + + if color is None and parent is not None: + color = parent.color + + #: Controls if styling output is wanted or not. + self.color: bool | None = color + + if show_default is None and parent is not None: + show_default = parent.show_default + + #: Show option default values when formatting help text. + self.show_default: bool | None = show_default + + self._close_callbacks: list[t.Callable[[], t.Any]] = [] + self._depth = 0 + self._parameter_source: dict[str, ParameterSource] = {} + self._exit_stack = ExitStack() + + @property + def protected_args(self) -> list[str]: + import warnings + + warnings.warn( + "'protected_args' is deprecated and will be removed in Click 9.0." + " 'args' will contain remaining unparsed tokens.", + DeprecationWarning, + stacklevel=2, + ) + return self._protected_args + + def to_info_dict(self) -> dict[str, t.Any]: + """Gather information that could be useful for a tool generating + user-facing documentation. This traverses the entire CLI + structure. + + .. code-block:: python + + with Context(cli) as ctx: + info = ctx.to_info_dict() + + .. versionadded:: 8.0 + """ + return { + "command": self.command.to_info_dict(self), + "info_name": self.info_name, + "allow_extra_args": self.allow_extra_args, + "allow_interspersed_args": self.allow_interspersed_args, + "ignore_unknown_options": self.ignore_unknown_options, + "auto_envvar_prefix": self.auto_envvar_prefix, + } + + def __enter__(self) -> Context: + self._depth += 1 + push_context(self) + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> bool | None: + self._depth -= 1 + exit_result: bool | None = None + if self._depth == 0: + exit_result = self._close_with_exception_info(exc_type, exc_value, tb) + pop_context() + + return exit_result + + @contextmanager + def scope(self, cleanup: bool = True) -> cabc.Iterator[Context]: + """This helper method can be used with the context object to promote + it to the current thread local (see :func:`get_current_context`). + The default behavior of this is to invoke the cleanup functions which + can be disabled by setting `cleanup` to `False`. The cleanup + functions are typically used for things such as closing file handles. + + If the cleanup is intended the context object can also be directly + used as a context manager. + + Example usage:: + + with ctx.scope(): + assert get_current_context() is ctx + + This is equivalent:: + + with ctx: + assert get_current_context() is ctx + + .. versionadded:: 5.0 + + :param cleanup: controls if the cleanup functions should be run or + not. The default is to run these functions. In + some situations the context only wants to be + temporarily pushed in which case this can be disabled. + Nested pushes automatically defer the cleanup. + """ + if not cleanup: + self._depth += 1 + try: + with self as rv: + yield rv + finally: + if not cleanup: + self._depth -= 1 + + @property + def meta(self) -> dict[str, t.Any]: + """This is a dictionary which is shared with all the contexts + that are nested. It exists so that click utilities can store some + state here if they need to. It is however the responsibility of + that code to manage this dictionary well. + + The keys are supposed to be unique dotted strings. For instance + module paths are a good choice for it. What is stored in there is + irrelevant for the operation of click. However what is important is + that code that places data here adheres to the general semantics of + the system. + + Example usage:: + + LANG_KEY = f'{__name__}.lang' + + def set_language(value): + ctx = get_current_context() + ctx.meta[LANG_KEY] = value + + def get_language(): + return get_current_context().meta.get(LANG_KEY, 'en_US') + + .. versionadded:: 5.0 + """ + return self._meta + + def make_formatter(self) -> HelpFormatter: + """Creates the :class:`~click.HelpFormatter` for the help and + usage output. + + To quickly customize the formatter class used without overriding + this method, set the :attr:`formatter_class` attribute. + + .. versionchanged:: 8.0 + Added the :attr:`formatter_class` attribute. + """ + return self.formatter_class( + width=self.terminal_width, max_width=self.max_content_width + ) + + def with_resource(self, context_manager: AbstractContextManager[V]) -> V: + """Register a resource as if it were used in a ``with`` + statement. The resource will be cleaned up when the context is + popped. + + Uses :meth:`contextlib.ExitStack.enter_context`. It calls the + resource's ``__enter__()`` method and returns the result. When + the context is popped, it closes the stack, which calls the + resource's ``__exit__()`` method. + + To register a cleanup function for something that isn't a + context manager, use :meth:`call_on_close`. Or use something + from :mod:`contextlib` to turn it into a context manager first. + + .. code-block:: python + + @click.group() + @click.option("--name") + @click.pass_context + def cli(ctx): + ctx.obj = ctx.with_resource(connect_db(name)) + + :param context_manager: The context manager to enter. + :return: Whatever ``context_manager.__enter__()`` returns. + + .. versionadded:: 8.0 + """ + return self._exit_stack.enter_context(context_manager) + + def call_on_close(self, f: t.Callable[..., t.Any]) -> t.Callable[..., t.Any]: + """Register a function to be called when the context tears down. + + This can be used to close resources opened during the script + execution. Resources that support Python's context manager + protocol which would be used in a ``with`` statement should be + registered with :meth:`with_resource` instead. + + :param f: The function to execute on teardown. + """ + return self._exit_stack.callback(f) + + def close(self) -> None: + """Invoke all close callbacks registered with + :meth:`call_on_close`, and exit all context managers entered + with :meth:`with_resource`. + """ + self._close_with_exception_info(None, None, None) + + def _close_with_exception_info( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> bool | None: + """Unwind the exit stack by calling its :meth:`__exit__` providing the exception + information to allow for exception handling by the various resources registered + using :meth;`with_resource` + + :return: Whatever ``exit_stack.__exit__()`` returns. + """ + exit_result = self._exit_stack.__exit__(exc_type, exc_value, tb) + # In case the context is reused, create a new exit stack. + self._exit_stack = ExitStack() + + return exit_result + + @property + def command_path(self) -> str: + """The computed command path. This is used for the ``usage`` + information on the help page. It's automatically created by + combining the info names of the chain of contexts to the root. + """ + rv = "" + if self.info_name is not None: + rv = self.info_name + if self.parent is not None: + parent_command_path = [self.parent.command_path] + + if isinstance(self.parent.command, Command): + for param in self.parent.command.get_params(self): + parent_command_path.extend(param.get_usage_pieces(self)) + + rv = f"{' '.join(parent_command_path)} {rv}" + return rv.lstrip() + + def find_root(self) -> Context: + """Finds the outermost context.""" + node = self + while node.parent is not None: + node = node.parent + return node + + def find_object(self, object_type: type[V]) -> V | None: + """Finds the closest object of a given type.""" + node: Context | None = self + + while node is not None: + if isinstance(node.obj, object_type): + return node.obj + + node = node.parent + + return None + + def ensure_object(self, object_type: type[V]) -> V: + """Like :meth:`find_object` but sets the innermost object to a + new instance of `object_type` if it does not exist. + """ + rv = self.find_object(object_type) + if rv is None: + self.obj = rv = object_type() + return rv + + def _default_map_has(self, name: str | None) -> bool: + """Check if :attr:`default_map` contains a real value for ``name``. + + Returns ``False`` when the key is absent, the map is ``None``, + ``name`` is ``None``, or the stored value is the internal + :data:`UNSET` sentinel. + """ + return ( + name is not None + and self.default_map is not None + and name in self.default_map + and self.default_map[name] is not UNSET + ) + + @t.overload + def lookup_default( + self, name: str, call: t.Literal[True] = True + ) -> t.Any | None: ... + + @t.overload + def lookup_default( + self, name: str, call: t.Literal[False] = ... + ) -> t.Any | t.Callable[[], t.Any] | None: ... + + def lookup_default(self, name: str, call: bool = True) -> t.Any | None: + """Get the default for a parameter from :attr:`default_map`. + + :param name: Name of the parameter. + :param call: If the default is a callable, call it. Disable to + return the callable instead. + + .. versionchanged:: 8.0 + Added the ``call`` parameter. + """ + if not self._default_map_has(name): + return None + + # Assert to make the type checker happy. + assert self.default_map is not None + value = self.default_map[name] + + if call and callable(value): + return value() + + return value + + def fail(self, message: str) -> t.NoReturn: + """Aborts the execution of the program with a specific error + message. + + :param message: the error message to fail with. + """ + raise UsageError(message, self) + + def abort(self) -> t.NoReturn: + """Aborts the script.""" + raise Abort() + + def exit(self, code: int = 0) -> t.NoReturn: + """Exits the application with a given exit code. + + .. versionchanged:: 8.2 + Callbacks and context managers registered with :meth:`call_on_close` + and :meth:`with_resource` are closed before exiting. + """ + self.close() + raise Exit(code) + + def get_usage(self) -> str: + """Helper method to get formatted usage string for the current + context and command. + """ + return self.command.get_usage(self) + + def get_help(self) -> str: + """Helper method to get formatted help page for the current + context and command. + """ + return self.command.get_help(self) + + def _make_sub_context(self, command: Command) -> Context: + """Create a new context of the same type as this context, but + for a new command. + + :meta private: + """ + return type(self)(command, info_name=command.name, parent=self) + + @t.overload + def invoke( + self, callback: t.Callable[..., V], /, *args: t.Any, **kwargs: t.Any + ) -> V: ... + + @t.overload + def invoke(self, callback: Command, /, *args: t.Any, **kwargs: t.Any) -> t.Any: ... + + def invoke( + self, callback: Command | t.Callable[..., V], /, *args: t.Any, **kwargs: t.Any + ) -> t.Any | V: + """Invokes a command callback in exactly the way it expects. There + are two ways to invoke this method: + + 1. the first argument can be a callback and all other arguments and + keyword arguments are forwarded directly to the function. + 2. the first argument is a click command object. In that case all + arguments are forwarded as well but proper click parameters + (options and click arguments) must be keyword arguments and Click + will fill in defaults. + + .. versionchanged:: 8.0 + All ``kwargs`` are tracked in :attr:`params` so they will be + passed if :meth:`forward` is called at multiple levels. + + .. versionchanged:: 3.2 + A new context is created, and missing arguments use default values. + """ + if isinstance(callback, Command): + other_cmd = callback + + if other_cmd.callback is None: + raise TypeError( + "The given command does not have a callback that can be invoked." + ) + else: + callback = t.cast("t.Callable[..., V]", other_cmd.callback) + + ctx = self._make_sub_context(other_cmd) + + for param in other_cmd.params: + if param.name not in kwargs and param.expose_value: + default_value = param.get_default(ctx) + # We explicitly hide the :attr:`UNSET` value to the user, as we + # choose to make it an implementation detail. And because ``invoke`` + # has been designed as part of Click public API, we return ``None`` + # instead. Refs: + # https://github.com/pallets/click/issues/3066 + # https://github.com/pallets/click/issues/3065 + # https://github.com/pallets/click/pull/3068 + if default_value is UNSET: + default_value = None + kwargs[param.name] = param.type_cast_value( # type: ignore + ctx, default_value + ) + + # Track all kwargs as params, so that forward() will pass + # them on in subsequent calls. + ctx.params.update(kwargs) + else: + ctx = self + + with augment_usage_errors(self): + with ctx: + return callback(*args, **kwargs) + + def forward(self, cmd: Command, /, *args: t.Any, **kwargs: t.Any) -> t.Any: + """Similar to :meth:`invoke` but fills in default keyword + arguments from the current context if the other command expects + it. This cannot invoke callbacks directly, only other commands. + + .. versionchanged:: 8.0 + All ``kwargs`` are tracked in :attr:`params` so they will be + passed if ``forward`` is called at multiple levels. + """ + # Can only forward to other commands, not direct callbacks. + if not isinstance(cmd, Command): + raise TypeError("Callback is not a command.") + + for param in self.params: + if param not in kwargs: + kwargs[param] = self.params[param] + + return self.invoke(cmd, *args, **kwargs) + + def set_parameter_source(self, name: str, source: ParameterSource) -> None: + """Set the source of a parameter. This indicates the location + from which the value of the parameter was obtained. + + :param name: The name of the parameter. + :param source: A member of :class:`~click.core.ParameterSource`. + """ + self._parameter_source[name] = source + + def get_parameter_source(self, name: str) -> ParameterSource | None: + """Get the source of a parameter. This indicates the location + from which the value of the parameter was obtained. + + This can be useful for determining when a user specified a value + on the command line that is the same as the default value. It + will be :attr:`~click.core.ParameterSource.DEFAULT` only if the + value was actually taken from the default. + + :param name: The name of the parameter. + :rtype: ParameterSource + + .. versionchanged:: 8.0 + Returns ``None`` if the parameter was not provided from any + source. + """ + return self._parameter_source.get(name) + + +class Command: + """Commands are the basic building block of command line interfaces in + Click. A basic command handles command line parsing and might dispatch + more parsing to commands nested below it. + + :param name: the name of the command to use unless a group overrides it. + :param context_settings: an optional dictionary with defaults that are + passed to the context object. + :param callback: the callback to invoke. This is optional. + :param params: the parameters to register with this command. This can + be either :class:`Option` or :class:`Argument` objects. + :param help: the help string to use for this command. + :param epilog: like the help string but it's printed at the end of the + help page after everything else. + :param short_help: the short help to use for this command. This is + shown on the command listing of the parent command. + :param add_help_option: by default each command registers a ``--help`` + option. This can be disabled by this parameter. + :param no_args_is_help: this controls what happens if no arguments are + provided. This option is disabled by default. + If enabled this will add ``--help`` as argument + if no arguments are passed + :param hidden: hide this command from help outputs. + :param deprecated: If ``True`` or non-empty string, issues a message + indicating that the command is deprecated and highlights + its deprecation in --help. The message can be customized + by using a string as the value. + + .. versionchanged:: 8.2 + This is the base class for all commands, not ``BaseCommand``. + ``deprecated`` can be set to a string as well to customize the + deprecation message. + + .. versionchanged:: 8.1 + ``help``, ``epilog``, and ``short_help`` are stored unprocessed, + all formatting is done when outputting help text, not at init, + and is done even if not using the ``@command`` decorator. + + .. versionchanged:: 8.0 + Added a ``repr`` showing the command name. + + .. versionchanged:: 7.1 + Added the ``no_args_is_help`` parameter. + + .. versionchanged:: 2.0 + Added the ``context_settings`` parameter. + """ + + #: The context class to create with :meth:`make_context`. + #: + #: .. versionadded:: 8.0 + context_class: type[Context] = Context + + #: the default for the :attr:`Context.allow_extra_args` flag. + allow_extra_args = False + + #: the default for the :attr:`Context.allow_interspersed_args` flag. + allow_interspersed_args = True + + #: the default for the :attr:`Context.ignore_unknown_options` flag. + ignore_unknown_options = False + + def __init__( + self, + name: str | None, + context_settings: cabc.MutableMapping[str, t.Any] | None = None, + callback: t.Callable[..., t.Any] | None = None, + params: list[Parameter] | None = None, + help: str | None = None, + epilog: str | None = None, + short_help: str | None = None, + options_metavar: str | None = "[OPTIONS]", + add_help_option: bool = True, + no_args_is_help: bool = False, + hidden: bool = False, + deprecated: bool | str = False, + ) -> None: + #: the name the command thinks it has. Upon registering a command + #: on a :class:`Group` the group will default the command name + #: with this information. You should instead use the + #: :class:`Context`\'s :attr:`~Context.info_name` attribute. + self.name = name + + if context_settings is None: + context_settings = {} + + #: an optional dictionary with defaults passed to the context. + self.context_settings: cabc.MutableMapping[str, t.Any] = context_settings + + #: the callback to execute when the command fires. This might be + #: `None` in which case nothing happens. + self.callback = callback + #: the list of parameters for this command in the order they + #: should show up in the help page and execute. Eager parameters + #: will automatically be handled before non eager ones. + self.params: list[Parameter] = params or [] + self.help = help + self.epilog = epilog + self.options_metavar = options_metavar + self.short_help = short_help + self.add_help_option = add_help_option + self._help_option = None + self.no_args_is_help = no_args_is_help + self.hidden = hidden + self.deprecated = deprecated + + def to_info_dict(self, ctx: Context) -> dict[str, t.Any]: + return { + "name": self.name, + "params": [param.to_info_dict() for param in self.get_params(ctx)], + "help": self.help, + "epilog": self.epilog, + "short_help": self.short_help, + "hidden": self.hidden, + "deprecated": self.deprecated, + } + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} {self.name}>" + + def get_usage(self, ctx: Context) -> str: + """Formats the usage line into a string and returns it. + + Calls :meth:`format_usage` internally. + """ + formatter = ctx.make_formatter() + self.format_usage(ctx, formatter) + return formatter.getvalue().rstrip("\n") + + def get_params(self, ctx: Context) -> list[Parameter]: + params = self.params + help_option = self.get_help_option(ctx) + + if help_option is not None: + params = [*params, help_option] + + if __debug__: + import warnings + + opts = [opt for param in params for opt in param.opts] + opts_counter = Counter(opts) + duplicate_opts = (opt for opt, count in opts_counter.items() if count > 1) + + for duplicate_opt in duplicate_opts: + warnings.warn( + ( + f"The parameter {duplicate_opt} is used more than once. " + "Remove its duplicate as parameters should be unique." + ), + stacklevel=3, + ) + + return params + + def format_usage(self, ctx: Context, formatter: HelpFormatter) -> None: + """Writes the usage line into the formatter. + + This is a low-level method called by :meth:`get_usage`. + """ + pieces = self.collect_usage_pieces(ctx) + formatter.write_usage(ctx.command_path, " ".join(pieces)) + + def collect_usage_pieces(self, ctx: Context) -> list[str]: + """Returns all the pieces that go into the usage line and returns + it as a list of strings. + """ + rv = [self.options_metavar] if self.options_metavar else [] + + for param in self.get_params(ctx): + rv.extend(param.get_usage_pieces(ctx)) + + return rv + + def get_help_option_names(self, ctx: Context) -> list[str]: + """Returns the names for the help option.""" + all_names = set(ctx.help_option_names) + for param in self.params: + all_names.difference_update(param.opts) + all_names.difference_update(param.secondary_opts) + return list(all_names) + + def get_help_option(self, ctx: Context) -> Option | None: + """Returns the help option object. + + Skipped if :attr:`add_help_option` is ``False``. + + .. versionchanged:: 8.1.8 + The help option is now cached to avoid creating it multiple times. + """ + help_option_names = self.get_help_option_names(ctx) + + if not help_option_names or not self.add_help_option: + return None + + # Cache the help option object in private _help_option attribute to + # avoid creating it multiple times. Not doing this will break the + # callback odering by iter_params_for_processing(), which relies on + # object comparison. + if self._help_option is None: + # Avoid circular import. + from .decorators import help_option + + # Apply help_option decorator and pop resulting option + help_option(*help_option_names)(self) + self._help_option = self.params.pop() # type: ignore[assignment] + + return self._help_option + + def make_parser(self, ctx: Context) -> _OptionParser: + """Creates the underlying option parser for this command.""" + parser = _OptionParser(ctx) + for param in self.get_params(ctx): + param.add_to_parser(parser, ctx) + return parser + + def get_help(self, ctx: Context) -> str: + """Formats the help into a string and returns it. + + Calls :meth:`format_help` internally. + """ + formatter = ctx.make_formatter() + self.format_help(ctx, formatter) + return formatter.getvalue().rstrip("\n") + + def get_short_help_str(self, limit: int = 45) -> str: + """Gets short help for the command or makes it by shortening the + long help string. + """ + if self.short_help: + text = inspect.cleandoc(self.short_help) + elif self.help: + text = make_default_short_help(self.help, limit) + else: + text = "" + + if self.deprecated: + deprecated_message = ( + f"(DEPRECATED: {self.deprecated})" + if isinstance(self.deprecated, str) + else "(DEPRECATED)" + ) + text = _("{text} {deprecated_message}").format( + text=text, deprecated_message=deprecated_message + ) + + return text.strip() + + def format_help(self, ctx: Context, formatter: HelpFormatter) -> None: + """Writes the help into the formatter if it exists. + + This is a low-level method called by :meth:`get_help`. + + This calls the following methods: + + - :meth:`format_usage` + - :meth:`format_help_text` + - :meth:`format_options` + - :meth:`format_epilog` + """ + self.format_usage(ctx, formatter) + self.format_help_text(ctx, formatter) + self.format_options(ctx, formatter) + self.format_epilog(ctx, formatter) + + def format_help_text(self, ctx: Context, formatter: HelpFormatter) -> None: + """Writes the help text to the formatter if it exists.""" + if self.help is not None: + # truncate the help text to the first form feed + text = inspect.cleandoc(self.help).partition("\f")[0] + else: + text = "" + + if self.deprecated: + deprecated_message = ( + f"(DEPRECATED: {self.deprecated})" + if isinstance(self.deprecated, str) + else "(DEPRECATED)" + ) + text = _("{text} {deprecated_message}").format( + text=text, deprecated_message=deprecated_message + ) + + if text: + formatter.write_paragraph() + + with formatter.indentation(): + formatter.write_text(text) + + def format_options(self, ctx: Context, formatter: HelpFormatter) -> None: + """Writes all the options into the formatter if they exist.""" + opts = [] + for param in self.get_params(ctx): + rv = param.get_help_record(ctx) + if rv is not None: + opts.append(rv) + + if opts: + with formatter.section(_("Options")): + formatter.write_dl(opts) + + def format_epilog(self, ctx: Context, formatter: HelpFormatter) -> None: + """Writes the epilog into the formatter if it exists.""" + if self.epilog: + epilog = inspect.cleandoc(self.epilog) + formatter.write_paragraph() + + with formatter.indentation(): + formatter.write_text(epilog) + + def make_context( + self, + info_name: str | None, + args: list[str], + parent: Context | None = None, + **extra: t.Any, + ) -> Context: + """This function when given an info name and arguments will kick + off the parsing and create a new :class:`Context`. It does not + invoke the actual command callback though. + + To quickly customize the context class used without overriding + this method, set the :attr:`context_class` attribute. + + :param info_name: the info name for this invocation. Generally this + is the most descriptive name for the script or + command. For the toplevel script it's usually + the name of the script, for commands below it's + the name of the command. + :param args: the arguments to parse as list of strings. + :param parent: the parent context if available. + :param extra: extra keyword arguments forwarded to the context + constructor. + + .. versionchanged:: 8.0 + Added the :attr:`context_class` attribute. + """ + for key, value in self.context_settings.items(): + if key not in extra: + extra[key] = value + + ctx = self.context_class(self, info_name=info_name, parent=parent, **extra) + + with ctx.scope(cleanup=False): + self.parse_args(ctx, args) + return ctx + + def parse_args(self, ctx: Context, args: list[str]) -> list[str]: + if not args and self.no_args_is_help and not ctx.resilient_parsing: + raise NoArgsIsHelpError(ctx) + + parser = self.make_parser(ctx) + opts, args, param_order = parser.parse_args(args=args) + + for param in iter_params_for_processing(param_order, self.get_params(ctx)): + _, args = param.handle_parse_result(ctx, opts, args) + + # We now have all parameters' values into `ctx.params`, but the data may contain + # the `UNSET` sentinel. + # Convert `UNSET` to `None` to ensure that the user doesn't see `UNSET`. + # + # Waiting until after the initial parse to convert allows us to treat `UNSET` + # more like a missing value when multiple params use the same name. + # Refs: + # https://github.com/pallets/click/issues/3071 + # https://github.com/pallets/click/pull/3079 + for name, value in ctx.params.items(): + if value is UNSET: + ctx.params[name] = None + + if args and not ctx.allow_extra_args and not ctx.resilient_parsing: + ctx.fail( + ngettext( + "Got unexpected extra argument ({args})", + "Got unexpected extra arguments ({args})", + len(args), + ).format(args=" ".join(map(str, args))) + ) + + ctx.args = args + ctx._opt_prefixes.update(parser._opt_prefixes) + return args + + def invoke(self, ctx: Context) -> t.Any: + """Given a context, this invokes the attached callback (if it exists) + in the right way. + """ + if self.deprecated: + extra_message = ( + f" {self.deprecated}" if isinstance(self.deprecated, str) else "" + ) + message = _( + "DeprecationWarning: The command {name!r} is deprecated.{extra_message}" + ).format(name=self.name, extra_message=extra_message) + echo(style(message, fg="red"), err=True) + + if self.callback is not None: + return ctx.invoke(self.callback, **ctx.params) + + def shell_complete(self, ctx: Context, incomplete: str) -> list[CompletionItem]: + """Return a list of completions for the incomplete value. Looks + at the names of options and chained multi-commands. + + Any command could be part of a chained multi-command, so sibling + commands are valid at any point during command completion. + + :param ctx: Invocation context for this command. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from click.shell_completion import CompletionItem + + results: list[CompletionItem] = [] + + if incomplete and not incomplete[0].isalnum(): + for param in self.get_params(ctx): + if ( + not isinstance(param, Option) + or param.hidden + or ( + not param.multiple + and ctx.get_parameter_source(param.name) # type: ignore + is ParameterSource.COMMANDLINE + ) + ): + continue + + results.extend( + CompletionItem(name, help=param.help) + for name in [*param.opts, *param.secondary_opts] + if name.startswith(incomplete) + ) + + while ctx.parent is not None: + ctx = ctx.parent + + if isinstance(ctx.command, Group) and ctx.command.chain: + results.extend( + CompletionItem(name, help=command.get_short_help_str()) + for name, command in _complete_visible_commands(ctx, incomplete) + if name not in ctx._protected_args + ) + + return results + + @t.overload + def main( + self, + args: cabc.Sequence[str] | None = None, + prog_name: str | None = None, + complete_var: str | None = None, + standalone_mode: t.Literal[True] = True, + **extra: t.Any, + ) -> t.NoReturn: ... + + @t.overload + def main( + self, + args: cabc.Sequence[str] | None = None, + prog_name: str | None = None, + complete_var: str | None = None, + standalone_mode: bool = ..., + **extra: t.Any, + ) -> t.Any: ... + + def main( + self, + args: cabc.Sequence[str] | None = None, + prog_name: str | None = None, + complete_var: str | None = None, + standalone_mode: bool = True, + windows_expand_args: bool = True, + **extra: t.Any, + ) -> t.Any: + """This is the way to invoke a script with all the bells and + whistles as a command line application. This will always terminate + the application after a call. If this is not wanted, ``SystemExit`` + needs to be caught. + + This method is also available by directly calling the instance of + a :class:`Command`. + + :param args: the arguments that should be used for parsing. If not + provided, ``sys.argv[1:]`` is used. + :param prog_name: the program name that should be used. By default + the program name is constructed by taking the file + name from ``sys.argv[0]``. + :param complete_var: the environment variable that controls the + bash completion support. The default is + ``"__COMPLETE"`` with prog_name in + uppercase. + :param standalone_mode: the default behavior is to invoke the script + in standalone mode. Click will then + handle exceptions and convert them into + error messages and the function will never + return but shut down the interpreter. If + this is set to `False` they will be + propagated to the caller and the return + value of this function is the return value + of :meth:`invoke`. + :param windows_expand_args: Expand glob patterns, user dir, and + env vars in command line args on Windows. + :param extra: extra keyword arguments are forwarded to the context + constructor. See :class:`Context` for more information. + + .. versionchanged:: 8.0.1 + Added the ``windows_expand_args`` parameter to allow + disabling command line arg expansion on Windows. + + .. versionchanged:: 8.0 + When taking arguments from ``sys.argv`` on Windows, glob + patterns, user dir, and env vars are expanded. + + .. versionchanged:: 3.0 + Added the ``standalone_mode`` parameter. + """ + if args is None: + args = sys.argv[1:] + + if os.name == "nt" and windows_expand_args: + args = _expand_args(args) + else: + args = list(args) + + if prog_name is None: + prog_name = _detect_program_name() + + # Process shell completion requests and exit early. + self._main_shell_completion(extra, prog_name, complete_var) + + try: + try: + with self.make_context(prog_name, args, **extra) as ctx: + rv = self.invoke(ctx) + if not standalone_mode: + return rv + # it's not safe to `ctx.exit(rv)` here! + # note that `rv` may actually contain data like "1" which + # has obvious effects + # more subtle case: `rv=[None, None]` can come out of + # chained commands which all returned `None` -- so it's not + # even always obvious that `rv` indicates success/failure + # by its truthiness/falsiness + ctx.exit() + except (EOFError, KeyboardInterrupt) as e: + echo(file=sys.stderr) + raise Abort() from e + except ClickException as e: + if not standalone_mode: + raise + e.show() + sys.exit(e.exit_code) + except OSError as e: + if e.errno == errno.EPIPE: + sys.stdout = t.cast(t.TextIO, PacifyFlushWrapper(sys.stdout)) + sys.stderr = t.cast(t.TextIO, PacifyFlushWrapper(sys.stderr)) + sys.exit(1) + else: + raise + except Exit as e: + if standalone_mode: + sys.exit(e.exit_code) + else: + # in non-standalone mode, return the exit code + # note that this is only reached if `self.invoke` above raises + # an Exit explicitly -- thus bypassing the check there which + # would return its result + # the results of non-standalone execution may therefore be + # somewhat ambiguous: if there are codepaths which lead to + # `ctx.exit(1)` and to `return 1`, the caller won't be able to + # tell the difference between the two + return e.exit_code + except Abort: + if not standalone_mode: + raise + echo(_("Aborted!"), file=sys.stderr) + sys.exit(1) + + def _main_shell_completion( + self, + ctx_args: cabc.MutableMapping[str, t.Any], + prog_name: str, + complete_var: str | None = None, + ) -> None: + """Check if the shell is asking for tab completion, process + that, then exit early. Called from :meth:`main` before the + program is invoked. + + :param prog_name: Name of the executable in the shell. + :param complete_var: Name of the environment variable that holds + the completion instruction. Defaults to + ``_{PROG_NAME}_COMPLETE``. + + .. versionchanged:: 8.2.0 + Dots (``.``) in ``prog_name`` are replaced with underscores (``_``). + """ + if complete_var is None: + complete_name = prog_name.replace("-", "_").replace(".", "_") + complete_var = f"_{complete_name}_COMPLETE".upper() + + instruction = os.environ.get(complete_var) + + if not instruction: + return + + from .shell_completion import shell_complete + + rv = shell_complete(self, ctx_args, prog_name, complete_var, instruction) + sys.exit(rv) + + def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Any: + """Alias for :meth:`main`.""" + return self.main(*args, **kwargs) + + +class _FakeSubclassCheck(type): + def __subclasscheck__(cls, subclass: type) -> bool: + return issubclass(subclass, cls.__bases__[0]) + + def __instancecheck__(cls, instance: t.Any) -> bool: + return isinstance(instance, cls.__bases__[0]) + + +class _BaseCommand(Command, metaclass=_FakeSubclassCheck): + """ + .. deprecated:: 8.2 + Will be removed in Click 9.0. Use ``Command`` instead. + """ + + +class Group(Command): + """A group is a command that nests other commands (or more groups). + + :param name: The name of the group command. + :param commands: Map names to :class:`Command` objects. Can be a list, which + will use :attr:`Command.name` as the keys. + :param invoke_without_command: Invoke the group's callback even if a + subcommand is not given. + :param no_args_is_help: If no arguments are given, show the group's help and + exit. Defaults to the opposite of ``invoke_without_command``. + :param subcommand_metavar: How to represent the subcommand argument in help. + The default will represent whether ``chain`` is set or not. + :param chain: Allow passing more than one subcommand argument. After parsing + a command's arguments, if any arguments remain another command will be + matched, and so on. + :param result_callback: A function to call after the group's and + subcommand's callbacks. The value returned by the subcommand is passed. + If ``chain`` is enabled, the value will be a list of values returned by + all the commands. If ``invoke_without_command`` is enabled, the value + will be the value returned by the group's callback, or an empty list if + ``chain`` is enabled. + :param kwargs: Other arguments passed to :class:`Command`. + + .. versionchanged:: 8.0 + The ``commands`` argument can be a list of command objects. + + .. versionchanged:: 8.2 + Merged with and replaces the ``MultiCommand`` base class. + """ + + allow_extra_args = True + allow_interspersed_args = False + + #: If set, this is used by the group's :meth:`command` decorator + #: as the default :class:`Command` class. This is useful to make all + #: subcommands use a custom command class. + #: + #: .. versionadded:: 8.0 + command_class: type[Command] | None = None + + #: If set, this is used by the group's :meth:`group` decorator + #: as the default :class:`Group` class. This is useful to make all + #: subgroups use a custom group class. + #: + #: If set to the special value :class:`type` (literally + #: ``group_class = type``), this group's class will be used as the + #: default class. This makes a custom group class continue to make + #: custom groups. + #: + #: .. versionadded:: 8.0 + group_class: type[Group] | type[type] | None = None + # Literal[type] isn't valid, so use Type[type] + + def __init__( + self, + name: str | None = None, + commands: cabc.MutableMapping[str, Command] + | cabc.Sequence[Command] + | None = None, + invoke_without_command: bool = False, + no_args_is_help: bool | None = None, + subcommand_metavar: str | None = None, + chain: bool = False, + result_callback: t.Callable[..., t.Any] | None = None, + **kwargs: t.Any, + ) -> None: + super().__init__(name, **kwargs) + + if commands is None: + commands = {} + elif isinstance(commands, abc.Sequence): + commands = {c.name: c for c in commands if c.name is not None} + + #: The registered subcommands by their exported names. + self.commands: cabc.MutableMapping[str, Command] = commands + + if no_args_is_help is None: + no_args_is_help = not invoke_without_command + + self.no_args_is_help = no_args_is_help + self.invoke_without_command = invoke_without_command + + if subcommand_metavar is None: + if chain: + subcommand_metavar = "COMMAND1 [ARGS]... [COMMAND2 [ARGS]...]..." + else: + subcommand_metavar = "COMMAND [ARGS]..." + + self.subcommand_metavar = subcommand_metavar + self.chain = chain + # The result callback that is stored. This can be set or + # overridden with the :func:`result_callback` decorator. + self._result_callback = result_callback + + if self.chain: + for param in self.params: + if isinstance(param, Argument) and not param.required: + raise RuntimeError( + "A group in chain mode cannot have optional arguments." + ) + + def to_info_dict(self, ctx: Context) -> dict[str, t.Any]: + info_dict = super().to_info_dict(ctx) + commands = {} + + for name in self.list_commands(ctx): + command = self.get_command(ctx, name) + + if command is None: + continue + + sub_ctx = ctx._make_sub_context(command) + + with sub_ctx.scope(cleanup=False): + commands[name] = command.to_info_dict(sub_ctx) + + info_dict.update(commands=commands, chain=self.chain) + return info_dict + + def add_command(self, cmd: Command, name: str | None = None) -> None: + """Registers another :class:`Command` with this group. If the name + is not provided, the name of the command is used. + """ + name = name or cmd.name + if name is None: + raise TypeError("Command has no name.") + _check_nested_chain(self, name, cmd, register=True) + self.commands[name] = cmd + + @t.overload + def command(self, __func: t.Callable[..., t.Any]) -> Command: ... + + @t.overload + def command( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Callable[[t.Callable[..., t.Any]], Command]: ... + + def command( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Callable[[t.Callable[..., t.Any]], Command] | Command: + """A shortcut decorator for declaring and attaching a command to + the group. This takes the same arguments as :func:`command` and + immediately registers the created command with this group by + calling :meth:`add_command`. + + To customize the command class used, set the + :attr:`command_class` attribute. + + .. versionchanged:: 8.1 + This decorator can be applied without parentheses. + + .. versionchanged:: 8.0 + Added the :attr:`command_class` attribute. + """ + from .decorators import command + + func: t.Callable[..., t.Any] | None = None + + if args and callable(args[0]): + assert len(args) == 1 and not kwargs, ( + "Use 'command(**kwargs)(callable)' to provide arguments." + ) + (func,) = args + args = () + + if self.command_class and kwargs.get("cls") is None: + kwargs["cls"] = self.command_class + + def decorator(f: t.Callable[..., t.Any]) -> Command: + cmd: Command = command(*args, **kwargs)(f) + self.add_command(cmd) + return cmd + + if func is not None: + return decorator(func) + + return decorator + + @t.overload + def group(self, __func: t.Callable[..., t.Any]) -> Group: ... + + @t.overload + def group( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Callable[[t.Callable[..., t.Any]], Group]: ... + + def group( + self, *args: t.Any, **kwargs: t.Any + ) -> t.Callable[[t.Callable[..., t.Any]], Group] | Group: + """A shortcut decorator for declaring and attaching a group to + the group. This takes the same arguments as :func:`group` and + immediately registers the created group with this group by + calling :meth:`add_command`. + + To customize the group class used, set the :attr:`group_class` + attribute. + + .. versionchanged:: 8.1 + This decorator can be applied without parentheses. + + .. versionchanged:: 8.0 + Added the :attr:`group_class` attribute. + """ + from .decorators import group + + func: t.Callable[..., t.Any] | None = None + + if args and callable(args[0]): + assert len(args) == 1 and not kwargs, ( + "Use 'group(**kwargs)(callable)' to provide arguments." + ) + (func,) = args + args = () + + if self.group_class is not None and kwargs.get("cls") is None: + if self.group_class is type: + kwargs["cls"] = type(self) + else: + kwargs["cls"] = self.group_class + + def decorator(f: t.Callable[..., t.Any]) -> Group: + cmd: Group = group(*args, **kwargs)(f) + self.add_command(cmd) + return cmd + + if func is not None: + return decorator(func) + + return decorator + + def result_callback(self, replace: bool = False) -> t.Callable[[F], F]: + """Adds a result callback to the command. By default if a + result callback is already registered this will chain them but + this can be disabled with the `replace` parameter. The result + callback is invoked with the return value of the subcommand + (or the list of return values from all subcommands if chaining + is enabled) as well as the parameters as they would be passed + to the main callback. + + Example:: + + @click.group() + @click.option('-i', '--input', default=23) + def cli(input): + return 42 + + @cli.result_callback() + def process_result(result, input): + return result + input + + :param replace: if set to `True` an already existing result + callback will be removed. + + .. versionchanged:: 8.0 + Renamed from ``resultcallback``. + + .. versionadded:: 3.0 + """ + + def decorator(f: F) -> F: + old_callback = self._result_callback + + if old_callback is None or replace: + self._result_callback = f + return f + + def function(value: t.Any, /, *args: t.Any, **kwargs: t.Any) -> t.Any: + inner = old_callback(value, *args, **kwargs) + return f(inner, *args, **kwargs) + + self._result_callback = rv = update_wrapper(t.cast(F, function), f) + return rv # type: ignore[return-value] + + return decorator + + def get_command(self, ctx: Context, cmd_name: str) -> Command | None: + """Given a context and a command name, this returns a :class:`Command` + object if it exists or returns ``None``. + """ + return self.commands.get(cmd_name) + + def list_commands(self, ctx: Context) -> list[str]: + """Returns a list of subcommand names in the order they should appear.""" + return sorted(self.commands) + + def collect_usage_pieces(self, ctx: Context) -> list[str]: + rv = super().collect_usage_pieces(ctx) + rv.append(self.subcommand_metavar) + return rv + + def format_options(self, ctx: Context, formatter: HelpFormatter) -> None: + super().format_options(ctx, formatter) + self.format_commands(ctx, formatter) + + def format_commands(self, ctx: Context, formatter: HelpFormatter) -> None: + """Extra format methods for multi methods that adds all the commands + after the options. + """ + commands = [] + for subcommand in self.list_commands(ctx): + cmd = self.get_command(ctx, subcommand) + # What is this, the tool lied about a command. Ignore it + if cmd is None: + continue + if cmd.hidden: + continue + + commands.append((subcommand, cmd)) + + # allow for 3 times the default spacing + if len(commands): + limit = formatter.width - 6 - max(len(cmd[0]) for cmd in commands) + + rows = [] + for subcommand, cmd in commands: + help = cmd.get_short_help_str(limit) + rows.append((subcommand, help)) + + if rows: + with formatter.section(_("Commands")): + formatter.write_dl(rows) + + def parse_args(self, ctx: Context, args: list[str]) -> list[str]: + if not args and self.no_args_is_help and not ctx.resilient_parsing: + raise NoArgsIsHelpError(ctx) + + rest = super().parse_args(ctx, args) + + if self.chain: + ctx._protected_args = rest + ctx.args = [] + elif rest: + ctx._protected_args, ctx.args = rest[:1], rest[1:] + + return ctx.args + + def invoke(self, ctx: Context) -> t.Any: + def _process_result(value: t.Any) -> t.Any: + if self._result_callback is not None: + value = ctx.invoke(self._result_callback, value, **ctx.params) + return value + + if not ctx._protected_args: + if self.invoke_without_command: + # No subcommand was invoked, so the result callback is + # invoked with the group return value for regular + # groups, or an empty list for chained groups. + with ctx: + rv = super().invoke(ctx) + return _process_result([] if self.chain else rv) + ctx.fail(_("Missing command.")) + + # Fetch args back out + args = [*ctx._protected_args, *ctx.args] + ctx.args = [] + ctx._protected_args = [] + + # If we're not in chain mode, we only allow the invocation of a + # single command but we also inform the current context about the + # name of the command to invoke. + if not self.chain: + # Make sure the context is entered so we do not clean up + # resources until the result processor has worked. + with ctx: + cmd_name, cmd, args = self.resolve_command(ctx, args) + assert cmd is not None + ctx.invoked_subcommand = cmd_name + super().invoke(ctx) + sub_ctx = cmd.make_context(cmd_name, args, parent=ctx) + with sub_ctx: + return _process_result(sub_ctx.command.invoke(sub_ctx)) + + # In chain mode we create the contexts step by step, but after the + # base command has been invoked. Because at that point we do not + # know the subcommands yet, the invoked subcommand attribute is + # set to ``*`` to inform the command that subcommands are executed + # but nothing else. + with ctx: + ctx.invoked_subcommand = "*" if args else None + super().invoke(ctx) + + # Otherwise we make every single context and invoke them in a + # chain. In that case the return value to the result processor + # is the list of all invoked subcommand's results. + contexts = [] + while args: + cmd_name, cmd, args = self.resolve_command(ctx, args) + assert cmd is not None + sub_ctx = cmd.make_context( + cmd_name, + args, + parent=ctx, + allow_extra_args=True, + allow_interspersed_args=False, + ) + contexts.append(sub_ctx) + args, sub_ctx.args = sub_ctx.args, [] + + rv = [] + for sub_ctx in contexts: + with sub_ctx: + rv.append(sub_ctx.command.invoke(sub_ctx)) + return _process_result(rv) + + def resolve_command( + self, ctx: Context, args: list[str] + ) -> tuple[str | None, Command | None, list[str]]: + cmd_name = make_str(args[0]) + original_cmd_name = cmd_name + + # Get the command + cmd = self.get_command(ctx, cmd_name) + + # If we can't find the command but there is a normalization + # function available, we try with that one. + if cmd is None and ctx.token_normalize_func is not None: + cmd_name = ctx.token_normalize_func(cmd_name) + cmd = self.get_command(ctx, cmd_name) + + # If we don't find the command we want to show an error message + # to the user that it was not provided. However, there is + # something else we should do: if the first argument looks like + # an option we want to kick off parsing again for arguments to + # resolve things like --help which now should go to the main + # place. + if cmd is None and not ctx.resilient_parsing: + if _split_opt(cmd_name)[0]: + self.parse_args(ctx, args) + ctx.fail(_("No such command {name!r}.").format(name=original_cmd_name)) + return cmd_name if cmd else None, cmd, args[1:] + + def shell_complete(self, ctx: Context, incomplete: str) -> list[CompletionItem]: + """Return a list of completions for the incomplete value. Looks + at the names of options, subcommands, and chained + multi-commands. + + :param ctx: Invocation context for this command. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from click.shell_completion import CompletionItem + + results = [ + CompletionItem(name, help=command.get_short_help_str()) + for name, command in _complete_visible_commands(ctx, incomplete) + ] + results.extend(super().shell_complete(ctx, incomplete)) + return results + + +class _MultiCommand(Group, metaclass=_FakeSubclassCheck): + """ + .. deprecated:: 8.2 + Will be removed in Click 9.0. Use ``Group`` instead. + """ + + +class CommandCollection(Group): + """A :class:`Group` that looks up subcommands on other groups. If a command + is not found on this group, each registered source is checked in order. + Parameters on a source are not added to this group, and a source's callback + is not invoked when invoking its commands. In other words, this "flattens" + commands in many groups into this one group. + + :param name: The name of the group command. + :param sources: A list of :class:`Group` objects to look up commands from. + :param kwargs: Other arguments passed to :class:`Group`. + + .. versionchanged:: 8.2 + This is a subclass of ``Group``. Commands are looked up first on this + group, then each of its sources. + """ + + def __init__( + self, + name: str | None = None, + sources: list[Group] | None = None, + **kwargs: t.Any, + ) -> None: + super().__init__(name, **kwargs) + #: The list of registered groups. + self.sources: list[Group] = sources or [] + + def add_source(self, group: Group) -> None: + """Add a group as a source of commands.""" + self.sources.append(group) + + def get_command(self, ctx: Context, cmd_name: str) -> Command | None: + rv = super().get_command(ctx, cmd_name) + + if rv is not None: + return rv + + for source in self.sources: + rv = source.get_command(ctx, cmd_name) + + if rv is not None: + if self.chain: + _check_nested_chain(self, cmd_name, rv) + + return rv + + return None + + def list_commands(self, ctx: Context) -> list[str]: + rv: set[str] = set(super().list_commands(ctx)) + + for source in self.sources: + rv.update(source.list_commands(ctx)) + + return sorted(rv) + + +def _check_iter(value: t.Any) -> cabc.Iterator[t.Any]: + """Check if the value is iterable but not a string. Raises a type + error, or return an iterator over the value. + """ + if isinstance(value, str): + raise TypeError + + return iter(value) + + +class Parameter: + r"""A parameter to a command comes in two versions: they are either + :class:`Option`\s or :class:`Argument`\s. Other subclasses are currently + not supported by design as some of the internals for parsing are + intentionally not finalized. + + Some settings are supported by both options and arguments. + + :param param_decls: the parameter declarations for this option or + argument. This is a list of flags or argument + names. + :param type: the type that should be used. Either a :class:`ParamType` + or a Python type. The latter is converted into the former + automatically if supported. + :param required: controls if this is optional or not. + :param default: the default value if omitted. This can also be a callable, + in which case it's invoked when the default is needed + without any arguments. + :param callback: A function to further process or validate the value + after type conversion. It is called as ``f(ctx, param, value)`` + and must return the value. It is called for all sources, + including prompts. + :param nargs: the number of arguments to match. If not ``1`` the return + value is a tuple instead of single value. The default for + nargs is ``1`` (except if the type is a tuple, then it's + the arity of the tuple). If ``nargs=-1``, all remaining + parameters are collected. + :param metavar: how the value is represented in the help page. + :param expose_value: if this is `True` then the value is passed onwards + to the command callback and stored on the context, + otherwise it's skipped. + :param is_eager: eager values are processed before non eager ones. This + should not be set for arguments or it will inverse the + order of processing. + :param envvar: environment variable(s) that are used to provide a default value for + this parameter. This can be a string or a sequence of strings. If a sequence is + given, only the first non-empty environment variable is used for the parameter. + :param shell_complete: A function that returns custom shell + completions. Used instead of the param's type completion if + given. Takes ``ctx, param, incomplete`` and must return a list + of :class:`~click.shell_completion.CompletionItem` or a list of + strings. + :param deprecated: If ``True`` or non-empty string, issues a message + indicating that the argument is deprecated and highlights + its deprecation in --help. The message can be customized + by using a string as the value. A deprecated parameter + cannot be required, a ValueError will be raised otherwise. + + .. versionchanged:: 8.2.0 + Introduction of ``deprecated``. + + .. versionchanged:: 8.2 + Adding duplicate parameter names to a :class:`~click.core.Command` will + result in a ``UserWarning`` being shown. + + .. versionchanged:: 8.2 + Adding duplicate parameter names to a :class:`~click.core.Command` will + result in a ``UserWarning`` being shown. + + .. versionchanged:: 8.0 + ``process_value`` validates required parameters and bounded + ``nargs``, and invokes the parameter callback before returning + the value. This allows the callback to validate prompts. + ``full_process_value`` is removed. + + .. versionchanged:: 8.0 + ``autocompletion`` is renamed to ``shell_complete`` and has new + semantics described above. The old name is deprecated and will + be removed in 8.1, until then it will be wrapped to match the + new requirements. + + .. versionchanged:: 8.0 + For ``multiple=True, nargs>1``, the default must be a list of + tuples. + + .. versionchanged:: 8.0 + Setting a default is no longer required for ``nargs>1``, it will + default to ``None``. ``multiple=True`` or ``nargs=-1`` will + default to ``()``. + + .. versionchanged:: 7.1 + Empty environment variables are ignored rather than taking the + empty string value. This makes it possible for scripts to clear + variables if they can't unset them. + + .. versionchanged:: 2.0 + Changed signature for parameter callback to also be passed the + parameter. The old callback format will still work, but it will + raise a warning to give you a chance to migrate the code easier. + """ + + param_type_name = "parameter" + + def __init__( + self, + param_decls: cabc.Sequence[str] | None = None, + type: types.ParamType | t.Any | None = None, + required: bool = False, + # XXX The default historically embed two concepts: + # - the declaration of a Parameter object carrying the default (handy to + # arbitrage the default value of coupled Parameters sharing the same + # self.name, like flag options), + # - and the actual value of the default. + # It is confusing and is the source of many issues discussed in: + # https://github.com/pallets/click/pull/3030 + # In the future, we might think of splitting it in two, not unlike + # Option.is_flag and Option.flag_value: we could have something like + # Parameter.is_default and Parameter.default_value. + default: t.Any | t.Callable[[], t.Any] | None = UNSET, + callback: t.Callable[[Context, Parameter, t.Any], t.Any] | None = None, + nargs: int | None = None, + multiple: bool = False, + metavar: str | None = None, + expose_value: bool = True, + is_eager: bool = False, + envvar: str | cabc.Sequence[str] | None = None, + shell_complete: t.Callable[ + [Context, Parameter, str], list[CompletionItem] | list[str] + ] + | None = None, + deprecated: bool | str = False, + ) -> None: + self.name: str | None + self.opts: list[str] + self.secondary_opts: list[str] + self.name, self.opts, self.secondary_opts = self._parse_decls( + param_decls or (), expose_value + ) + self.type: types.ParamType = types.convert_type(type, default) + + # Default nargs to what the type tells us if we have that + # information available. + if nargs is None: + if self.type.is_composite: + nargs = self.type.arity + else: + nargs = 1 + + self.required = required + self.callback = callback + self.nargs = nargs + self.multiple = multiple + self.expose_value = expose_value + self.default: t.Any | t.Callable[[], t.Any] | None = default + self.is_eager = is_eager + self.metavar = metavar + self.envvar = envvar + self._custom_shell_complete = shell_complete + self.deprecated = deprecated + + if __debug__: + if self.type.is_composite and nargs != self.type.arity: + raise ValueError( + f"'nargs' must be {self.type.arity} (or None) for" + f" type {self.type!r}, but it was {nargs}." + ) + + if required and deprecated: + raise ValueError( + f"The {self.param_type_name} '{self.human_readable_name}' " + "is deprecated and still required. A deprecated " + f"{self.param_type_name} cannot be required." + ) + + def to_info_dict(self) -> dict[str, t.Any]: + """Gather information that could be useful for a tool generating + user-facing documentation. + + Use :meth:`click.Context.to_info_dict` to traverse the entire + CLI structure. + + .. versionchanged:: 8.3.0 + Returns ``None`` for the :attr:`default` if it was not set. + + .. versionadded:: 8.0 + """ + return { + "name": self.name, + "param_type_name": self.param_type_name, + "opts": self.opts, + "secondary_opts": self.secondary_opts, + "type": self.type.to_info_dict(), + "required": self.required, + "nargs": self.nargs, + "multiple": self.multiple, + # We explicitly hide the :attr:`UNSET` value to the user, as we choose to + # make it an implementation detail. And because ``to_info_dict`` has been + # designed for documentation purposes, we return ``None`` instead. + "default": self.default if self.default is not UNSET else None, + "envvar": self.envvar, + } + + def __repr__(self) -> str: + return f"<{self.__class__.__name__} {self.name}>" + + def _parse_decls( + self, decls: cabc.Sequence[str], expose_value: bool + ) -> tuple[str | None, list[str], list[str]]: + raise NotImplementedError() + + @property + def human_readable_name(self) -> str: + """Returns the human readable name of this parameter. This is the + same as the name for options, but the metavar for arguments. + """ + return self.name # type: ignore + + def make_metavar(self, ctx: Context) -> str: + if self.metavar is not None: + return self.metavar + + metavar = self.type.get_metavar(param=self, ctx=ctx) + + if metavar is None: + metavar = self.type.name.upper() + + if self.nargs != 1: + metavar += "..." + + return metavar + + @t.overload + def get_default( + self, ctx: Context, call: t.Literal[True] = True + ) -> t.Any | None: ... + + @t.overload + def get_default( + self, ctx: Context, call: bool = ... + ) -> t.Any | t.Callable[[], t.Any] | None: ... + + def get_default( + self, ctx: Context, call: bool = True + ) -> t.Any | t.Callable[[], t.Any] | None: + """Get the default for the parameter. Tries + :meth:`Context.lookup_default` first, then the local default. + + :param ctx: Current context. + :param call: If the default is a callable, call it. Disable to + return the callable instead. + + .. versionchanged:: 8.0.2 + Type casting is no longer performed when getting a default. + + .. versionchanged:: 8.0.1 + Type casting can fail in resilient parsing mode. Invalid + defaults will not prevent showing help text. + + .. versionchanged:: 8.0 + Looks at ``ctx.default_map`` first. + + .. versionchanged:: 8.0 + Added the ``call`` parameter. + """ + name = self.name + value = ctx.lookup_default(name, call=False) if name is not None else None + + if value is None and not ctx._default_map_has(name): + value = self.default + + if call and callable(value): + value = value() + + return value + + def add_to_parser(self, parser: _OptionParser, ctx: Context) -> None: + raise NotImplementedError() + + def consume_value( + self, ctx: Context, opts: cabc.Mapping[str, t.Any] + ) -> tuple[t.Any, ParameterSource]: + """Returns the parameter value produced by the parser. + + If the parser did not produce a value from user input, the value is either + sourced from the environment variable, the default map, or the parameter's + default value. In that order of precedence. + + If no value is found, an internal sentinel value is returned. + + :meta private: + """ + # Collect from the parse the value passed by the user to the CLI. + value = opts.get(self.name, UNSET) # type: ignore + # If the value is set, it means it was sourced from the command line by the + # parser, otherwise it left unset by default. + source = ( + ParameterSource.COMMANDLINE + if value is not UNSET + else ParameterSource.DEFAULT + ) + + if value is UNSET: + envvar_value = self.value_from_envvar(ctx) + if envvar_value is not None: + value = envvar_value + source = ParameterSource.ENVIRONMENT + + if value is UNSET: + default_map_value = ctx.lookup_default(self.name) # type: ignore[arg-type] + if default_map_value is not None or ctx._default_map_has(self.name): + value = default_map_value + source = ParameterSource.DEFAULT_MAP + + if value is UNSET: + default_value = self.get_default(ctx) + if default_value is not UNSET: + value = default_value + source = ParameterSource.DEFAULT + + return value, source + + def type_cast_value(self, ctx: Context, value: t.Any) -> t.Any: + """Convert and validate a value against the parameter's + :attr:`type`, :attr:`multiple`, and :attr:`nargs`. + """ + if value is None: + if self.multiple or self.nargs == -1: + return () + else: + return value + + def check_iter(value: t.Any) -> cabc.Iterator[t.Any]: + try: + return _check_iter(value) + except TypeError: + # This should only happen when passing in args manually, + # the parser should construct an iterable when parsing + # the command line. + raise BadParameter( + _("Value must be an iterable."), ctx=ctx, param=self + ) from None + + # Define the conversion function based on nargs and type. + + if self.nargs == 1 or self.type.is_composite: + + def convert(value: t.Any) -> t.Any: + return self.type(value, param=self, ctx=ctx) + + elif self.nargs == -1: + + def convert(value: t.Any) -> t.Any: # tuple[t.Any, ...] + return tuple(self.type(x, self, ctx) for x in check_iter(value)) + + else: # nargs > 1 + + def convert(value: t.Any) -> t.Any: # tuple[t.Any, ...] + value = tuple(check_iter(value)) + + if len(value) != self.nargs: + raise BadParameter( + ngettext( + "Takes {nargs} values but 1 was given.", + "Takes {nargs} values but {len} were given.", + len(value), + ).format(nargs=self.nargs, len=len(value)), + ctx=ctx, + param=self, + ) + + return tuple(self.type(x, self, ctx) for x in value) + + if self.multiple: + return tuple(convert(x) for x in check_iter(value)) + + return convert(value) + + def value_is_missing(self, value: t.Any) -> bool: + """A value is considered missing if: + + - it is :attr:`UNSET`, + - or if it is an empty sequence while the parameter is suppose to have + non-single value (i.e. :attr:`nargs` is not ``1`` or :attr:`multiple` is + set). + + :meta private: + """ + if value is UNSET: + return True + + if (self.nargs != 1 or self.multiple) and value == (): + return True + + return False + + def process_value(self, ctx: Context, value: t.Any) -> t.Any: + """Process the value of this parameter: + + 1. Type cast the value using :meth:`type_cast_value`. + 2. Check if the value is missing (see: :meth:`value_is_missing`), and raise + :exc:`MissingParameter` if it is required. + 3. If a :attr:`callback` is set, call it to have the value replaced by the + result of the callback. If the value was not set, the callback receive + ``None``. This keep the legacy behavior as it was before the introduction of + the :attr:`UNSET` sentinel. + + :meta private: + """ + # shelter `type_cast_value` from ever seeing an `UNSET` value by handling the + # cases in which `UNSET` gets special treatment explicitly at this layer + # + # Refs: + # https://github.com/pallets/click/issues/3069 + if value is UNSET: + if self.multiple or self.nargs == -1: + value = () + else: + value = self.type_cast_value(ctx, value) + + if self.required and self.value_is_missing(value): + raise MissingParameter(ctx=ctx, param=self) + + if self.callback is not None: + # Legacy case: UNSET is not exposed directly to the callback, but converted + # to None. + if value is UNSET: + value = None + + # Search for parameters with UNSET values in the context. + unset_keys = {k: None for k, v in ctx.params.items() if v is UNSET} + # No UNSET values, call the callback as usual. + if not unset_keys: + value = self.callback(ctx, self, value) + + # Legacy case: provide a temporarily manipulated context to the callback + # to hide UNSET values as None. + # + # Refs: + # https://github.com/pallets/click/issues/3136 + # https://github.com/pallets/click/pull/3137 + else: + # Add another layer to the context stack to clearly hint that the + # context is temporarily modified. + with ctx: + # Update the context parameters to replace UNSET with None. + ctx.params.update(unset_keys) + # Feed these fake context parameters to the callback. + value = self.callback(ctx, self, value) + # Restore the UNSET values in the context parameters. + ctx.params.update( + { + k: UNSET + for k in unset_keys + # Only restore keys that are present and still None, in case + # the callback modified other parameters. + if k in ctx.params and ctx.params[k] is None + } + ) + + return value + + def resolve_envvar_value(self, ctx: Context) -> str | None: + """Returns the value found in the environment variable(s) attached to this + parameter. + + Environment variables values are `always returned as strings + `_. + + This method returns ``None`` if: + + - the :attr:`envvar` property is not set on the :class:`Parameter`, + - the environment variable is not found in the environment, + - the variable is found in the environment but its value is empty (i.e. the + environment variable is present but has an empty string). + + If :attr:`envvar` is setup with multiple environment variables, + then only the first non-empty value is returned. + + .. caution:: + + The raw value extracted from the environment is not normalized and is + returned as-is. Any normalization or reconciliation is performed later by + the :class:`Parameter`'s :attr:`type`. + + :meta private: + """ + if not self.envvar: + return None + + if isinstance(self.envvar, str): + rv = os.environ.get(self.envvar) + + if rv: + return rv + else: + for envvar in self.envvar: + rv = os.environ.get(envvar) + + # Return the first non-empty value of the list of environment variables. + if rv: + return rv + # Else, absence of value is interpreted as an environment variable that + # is not set, so proceed to the next one. + + return None + + def value_from_envvar(self, ctx: Context) -> str | cabc.Sequence[str] | None: + """Process the raw environment variable string for this parameter. + + Returns the string as-is or splits it into a sequence of strings if the + parameter is expecting multiple values (i.e. its :attr:`nargs` property is set + to a value other than ``1``). + + :meta private: + """ + rv = self.resolve_envvar_value(ctx) + + if rv is not None and self.nargs != 1: + return self.type.split_envvar_value(rv) + + return rv + + def handle_parse_result( + self, ctx: Context, opts: cabc.Mapping[str, t.Any], args: list[str] + ) -> tuple[t.Any, list[str]]: + """Process the value produced by the parser from user input. + + Always process the value through the Parameter's :attr:`type`, wherever it + comes from. + + If the parameter is deprecated, this method warn the user about it. But only if + the value has been explicitly set by the user (and as such, is not coming from + a default). + + :meta private: + """ + with augment_usage_errors(ctx, param=self): + value, source = self.consume_value(ctx, opts) + + ctx.set_parameter_source(self.name, source) # type: ignore + + # Display a deprecation warning if necessary. + if ( + self.deprecated + and value is not UNSET + and source < ParameterSource.DEFAULT_MAP + ): + extra_message = ( + f" {self.deprecated}" if isinstance(self.deprecated, str) else "" + ) + message = _( + "DeprecationWarning: The {param_type} {name!r} is deprecated." + "{extra_message}" + ).format( + param_type=self.param_type_name, + name=self.human_readable_name, + extra_message=extra_message, + ) + echo(style(message, fg="red"), err=True) + + # Process the value through the parameter's type. + try: + value = self.process_value(ctx, value) + except Exception: + if not ctx.resilient_parsing: + raise + # In resilient parsing mode, we do not want to fail the command if the + # value is incompatible with the parameter type, so we reset the value + # to UNSET, which will be interpreted as a missing value. + value = UNSET + + # Add parameter's value to the context. + if ( + self.expose_value + # We skip adding the value if it was previously set by another parameter + # targeting the same variable name. This prevents parameters competing for + # the same name to override each other. + and (self.name not in ctx.params or ctx.params[self.name] is UNSET) + ): + # Click is logically enforcing that the name is None if the parameter is + # not to be exposed. We still assert it here to please the type checker. + assert self.name is not None, ( + f"{self!r} parameter's name should not be None when exposing value." + ) + ctx.params[self.name] = value + + return value, args + + def get_help_record(self, ctx: Context) -> tuple[str, str] | None: + pass + + def get_usage_pieces(self, ctx: Context) -> list[str]: + return [] + + def get_error_hint(self, ctx: Context) -> str: + """Get a stringified version of the param for use in error messages to + indicate which param caused the error. + """ + hint_list = self.opts or [self.human_readable_name] + return " / ".join(f"'{x}'" for x in hint_list) + + def shell_complete(self, ctx: Context, incomplete: str) -> list[CompletionItem]: + """Return a list of completions for the incomplete value. If a + ``shell_complete`` function was given during init, it is used. + Otherwise, the :attr:`type` + :meth:`~click.types.ParamType.shell_complete` function is used. + + :param ctx: Invocation context for this command. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + if self._custom_shell_complete is not None: + results = self._custom_shell_complete(ctx, self, incomplete) + + if results and isinstance(results[0], str): + from click.shell_completion import CompletionItem + + results = [CompletionItem(c) for c in results] + + return t.cast("list[CompletionItem]", results) + + return self.type.shell_complete(ctx, self, incomplete) + + +class Option(Parameter): + """Options are usually optional values on the command line and + have some extra features that arguments don't have. + + All other parameters are passed onwards to the parameter constructor. + + :param show_default: Show the default value for this option in its + help text. Values are not shown by default, unless + :attr:`Context.show_default` is ``True``. If this value is a + string, it shows that string in parentheses instead of the + actual value. This is particularly useful for dynamic options. + For single option boolean flags, the default remains hidden if + its value is ``False``. + :param show_envvar: Controls if an environment variable should be + shown on the help page and error messages. + Normally, environment variables are not shown. + :param prompt: If set to ``True`` or a non empty string then the + user will be prompted for input. If set to ``True`` the prompt + will be the option name capitalized. A deprecated option cannot be + prompted. + :param confirmation_prompt: Prompt a second time to confirm the + value if it was prompted for. Can be set to a string instead of + ``True`` to customize the message. + :param prompt_required: If set to ``False``, the user will be + prompted for input only when the option was specified as a flag + without a value. + :param hide_input: If this is ``True`` then the input on the prompt + will be hidden from the user. This is useful for password input. + :param is_flag: forces this option to act as a flag. The default is + auto detection. + :param flag_value: which value should be used for this flag if it's + enabled. This is set to a boolean automatically if + the option string contains a slash to mark two options. + :param multiple: if this is set to `True` then the argument is accepted + multiple times and recorded. This is similar to ``nargs`` + in how it works but supports arbitrary number of + arguments. + :param count: this flag makes an option increment an integer. + :param allow_from_autoenv: if this is enabled then the value of this + parameter will be pulled from an environment + variable in case a prefix is defined on the + context. + :param help: the help string. + :param hidden: hide this option from help outputs. + :param attrs: Other command arguments described in :class:`Parameter`. + + .. versionchanged:: 8.2 + ``envvar`` used with ``flag_value`` will always use the ``flag_value``, + previously it would use the value of the environment variable. + + .. versionchanged:: 8.1 + Help text indentation is cleaned here instead of only in the + ``@option`` decorator. + + .. versionchanged:: 8.1 + The ``show_default`` parameter overrides + ``Context.show_default``. + + .. versionchanged:: 8.1 + The default of a single option boolean flag is not shown if the + default value is ``False``. + + .. versionchanged:: 8.0.1 + ``type`` is detected from ``flag_value`` if given. + """ + + param_type_name = "option" + + def __init__( + self, + param_decls: cabc.Sequence[str] | None = None, + show_default: bool | str | None = None, + prompt: bool | str = False, + confirmation_prompt: bool | str = False, + prompt_required: bool = True, + hide_input: bool = False, + is_flag: bool | None = None, + flag_value: t.Any = UNSET, + multiple: bool = False, + count: bool = False, + allow_from_autoenv: bool = True, + type: types.ParamType | t.Any | None = None, + help: str | None = None, + hidden: bool = False, + show_choices: bool = True, + show_envvar: bool = False, + deprecated: bool | str = False, + **attrs: t.Any, + ) -> None: + if help: + help = inspect.cleandoc(help) + + super().__init__( + param_decls, type=type, multiple=multiple, deprecated=deprecated, **attrs + ) + + if prompt is True: + if self.name is None: + raise TypeError("'name' is required with 'prompt=True'.") + + prompt_text: str | None = self.name.replace("_", " ").capitalize() + elif prompt is False: + prompt_text = None + else: + prompt_text = prompt + + if deprecated: + deprecated_message = ( + f"(DEPRECATED: {deprecated})" + if isinstance(deprecated, str) + else "(DEPRECATED)" + ) + help = help + deprecated_message if help is not None else deprecated_message + + self.prompt = prompt_text + self.confirmation_prompt = confirmation_prompt + self.prompt_required = prompt_required + self.hide_input = hide_input + self.hidden = hidden + + # The _flag_needs_value property tells the parser that this option is a flag + # that cannot be used standalone and needs a value. With this information, the + # parser can determine whether to consider the next user-provided argument in + # the CLI as a value for this flag or as a new option. + # If prompt is enabled but not required, then it opens the possibility for the + # option to gets its value from the user. + self._flag_needs_value = self.prompt is not None and not self.prompt_required + + # Auto-detect if this is a flag or not. + if is_flag is None: + # Implicitly a flag because flag_value was set. + if flag_value is not UNSET: + is_flag = True + # Not a flag, but when used as a flag it shows a prompt. + elif self._flag_needs_value: + is_flag = False + # Implicitly a flag because secondary options names were given. + elif self.secondary_opts: + is_flag = True + + # The option is explicitly not a flag, but to determine whether or not it needs + # value, we need to check if `flag_value` or `default` was set. Either one is + # sufficient. + # Ref: https://github.com/pallets/click/issues/3084 + elif is_flag is False and not self._flag_needs_value: + self._flag_needs_value = flag_value is not UNSET or self.default is UNSET + + if is_flag: + # Set missing default for flags if not explicitly required or prompted. + if self.default is UNSET and not self.required and not self.prompt: + if multiple: + self.default = () + + # Auto-detect the type of the flag based on the flag_value. + if type is None: + # A flag without a flag_value is a boolean flag. + if flag_value is UNSET: + self.type: types.ParamType = types.BoolParamType() + # If the flag value is a boolean, use BoolParamType. + elif isinstance(flag_value, bool): + self.type = types.BoolParamType() + # Otherwise, guess the type from the flag value. + else: + self.type = types.convert_type(None, flag_value) + + self.is_flag: bool = bool(is_flag) + self.is_bool_flag: bool = bool( + is_flag and isinstance(self.type, types.BoolParamType) + ) + self.flag_value: t.Any = flag_value + + # Set boolean flag default to False if unset and not required. + if self.is_bool_flag: + if self.default is UNSET and not self.required: + self.default = False + + # The alignement of default to the flag_value is resolved lazily in + # get_default() to prevent callable flag_values (like classes) from + # being instantiated. Refs: + # https://github.com/pallets/click/issues/3121 + # https://github.com/pallets/click/issues/3024#issuecomment-3146199461 + # https://github.com/pallets/click/pull/3030/commits/06847da + + # Set the default flag_value if it is not set. + if self.flag_value is UNSET: + if self.is_flag: + self.flag_value = True + else: + self.flag_value = None + + # Counting. + self.count = count + if count: + if type is None: + self.type = types.IntRange(min=0) + if self.default is UNSET: + self.default = 0 + + self.allow_from_autoenv = allow_from_autoenv + self.help = help + self.show_default = show_default + self.show_choices = show_choices + self.show_envvar = show_envvar + + if __debug__: + if deprecated and prompt: + raise ValueError("`deprecated` options cannot use `prompt`.") + + if self.nargs == -1: + raise TypeError("nargs=-1 is not supported for options.") + + if not self.is_bool_flag and self.secondary_opts: + raise TypeError("Secondary flag is not valid for non-boolean flag.") + + if self.is_bool_flag and self.hide_input and self.prompt is not None: + raise TypeError( + "'prompt' with 'hide_input' is not valid for boolean flag." + ) + + if self.count: + if self.multiple: + raise TypeError("'count' is not valid with 'multiple'.") + + if self.is_flag: + raise TypeError("'count' is not valid with 'is_flag'.") + + def to_info_dict(self) -> dict[str, t.Any]: + """ + .. versionchanged:: 8.3.0 + Returns ``None`` for the :attr:`flag_value` if it was not set. + """ + info_dict = super().to_info_dict() + info_dict.update( + help=self.help, + prompt=self.prompt, + is_flag=self.is_flag, + # We explicitly hide the :attr:`UNSET` value to the user, as we choose to + # make it an implementation detail. And because ``to_info_dict`` has been + # designed for documentation purposes, we return ``None`` instead. + flag_value=self.flag_value if self.flag_value is not UNSET else None, + count=self.count, + hidden=self.hidden, + ) + return info_dict + + def get_default( + self, ctx: Context, call: bool = True + ) -> t.Any | t.Callable[[], t.Any] | None: + """Return the default value for this option. + + For non-boolean flag options, ``default=True`` is treated as a sentinel + meaning "activate this flag by default" and is resolved to + :attr:`flag_value`. For example, with ``--upper/--lower`` feature + switches where ``flag_value="upper"`` and ``default=True``, the default + resolves to ``"upper"``. + + .. caution:: + This substitution only applies to non-boolean flags + (:attr:`is_bool_flag` is ``False``). For boolean flags, ``True`` is + a legitimate Python value and ``default=True`` is returned as-is. + + .. versionchanged:: 8.3.3 + ``default=True`` is no longer substituted with ``flag_value`` for + boolean flags, fixing negative boolean flags like + ``flag_value=False, default=True``. + """ + value = super().get_default(ctx, call=False) + + # Resolve default=True to flag_value lazily (here instead of + # __init__) to prevent callable flag_values (like classes) from + # being instantiated by the callable check below. + if value is True and self.is_flag and not self.is_bool_flag: + value = self.flag_value + elif call and callable(value): + value = value() + + return value + + def get_error_hint(self, ctx: Context) -> str: + result = super().get_error_hint(ctx) + if self.show_envvar and self.envvar is not None: + result += f" (env var: '{self.envvar}')" + return result + + def _parse_decls( + self, decls: cabc.Sequence[str], expose_value: bool + ) -> tuple[str | None, list[str], list[str]]: + opts = [] + secondary_opts = [] + name = None + possible_names = [] + + for decl in decls: + if decl.isidentifier(): + if name is not None: + raise TypeError(f"Name '{name}' defined twice") + name = decl + else: + split_char = ";" if decl[:1] == "/" else "/" + if split_char in decl: + first, second = decl.split(split_char, 1) + first = first.rstrip() + if first: + possible_names.append(_split_opt(first)) + opts.append(first) + second = second.lstrip() + if second: + secondary_opts.append(second.lstrip()) + if first == second: + raise ValueError( + f"Boolean option {decl!r} cannot use the" + " same flag for true/false." + ) + else: + possible_names.append(_split_opt(decl)) + opts.append(decl) + + if name is None and possible_names: + possible_names.sort(key=lambda x: -len(x[0])) # group long options first + name = possible_names[0][1].replace("-", "_").lower() + if not name.isidentifier(): + name = None + + if name is None: + if not expose_value: + return None, opts, secondary_opts + raise TypeError( + f"Could not determine name for option with declarations {decls!r}" + ) + + if not opts and not secondary_opts: + raise TypeError( + f"No options defined but a name was passed ({name})." + " Did you mean to declare an argument instead? Did" + f" you mean to pass '--{name}'?" + ) + + return name, opts, secondary_opts + + def add_to_parser(self, parser: _OptionParser, ctx: Context) -> None: + if self.multiple: + action = "append" + elif self.count: + action = "count" + else: + action = "store" + + if self.is_flag: + action = f"{action}_const" + + if self.is_bool_flag and self.secondary_opts: + parser.add_option( + obj=self, opts=self.opts, dest=self.name, action=action, const=True + ) + parser.add_option( + obj=self, + opts=self.secondary_opts, + dest=self.name, + action=action, + const=False, + ) + else: + parser.add_option( + obj=self, + opts=self.opts, + dest=self.name, + action=action, + const=self.flag_value, + ) + else: + parser.add_option( + obj=self, + opts=self.opts, + dest=self.name, + action=action, + nargs=self.nargs, + ) + + def get_help_record(self, ctx: Context) -> tuple[str, str] | None: + if self.hidden: + return None + + any_prefix_is_slash = False + + def _write_opts(opts: cabc.Sequence[str]) -> str: + nonlocal any_prefix_is_slash + + rv, any_slashes = join_options(opts) + + if any_slashes: + any_prefix_is_slash = True + + if not self.is_flag and not self.count: + rv += f" {self.make_metavar(ctx=ctx)}" + + return rv + + rv = [_write_opts(self.opts)] + + if self.secondary_opts: + rv.append(_write_opts(self.secondary_opts)) + + help = self.help or "" + + extra = self.get_help_extra(ctx) + extra_items = [] + if "envvars" in extra: + extra_items.append( + _("env var: {var}").format(var=", ".join(extra["envvars"])) + ) + if "default" in extra: + extra_items.append(_("default: {default}").format(default=extra["default"])) + if "range" in extra: + extra_items.append(extra["range"]) + if "required" in extra: + extra_items.append(_(extra["required"])) + + if extra_items: + extra_str = "; ".join(extra_items) + help = f"{help} [{extra_str}]" if help else f"[{extra_str}]" + + return ("; " if any_prefix_is_slash else " / ").join(rv), help + + def get_help_extra(self, ctx: Context) -> types.OptionHelpExtra: + extra: types.OptionHelpExtra = {} + + if self.show_envvar: + envvar = self.envvar + + if envvar is None: + if ( + self.allow_from_autoenv + and ctx.auto_envvar_prefix is not None + and self.name is not None + ): + envvar = f"{ctx.auto_envvar_prefix}_{self.name.upper()}" + + if envvar is not None: + if isinstance(envvar, str): + extra["envvars"] = (envvar,) + else: + extra["envvars"] = tuple(str(d) for d in envvar) + + # Temporarily enable resilient parsing to avoid type casting + # failing for the default. Might be possible to extend this to + # help formatting in general. + resilient = ctx.resilient_parsing + ctx.resilient_parsing = True + + try: + default_value = self.get_default(ctx, call=False) + finally: + ctx.resilient_parsing = resilient + + show_default = False + show_default_is_str = False + + if self.show_default is not None: + if isinstance(self.show_default, str): + show_default_is_str = show_default = True + else: + show_default = self.show_default + elif ctx.show_default is not None: + show_default = ctx.show_default + + if show_default_is_str or ( + show_default and (default_value not in (None, UNSET)) + ): + if show_default_is_str: + default_string = f"({self.show_default})" + elif isinstance(default_value, (list, tuple)): + default_string = ", ".join(str(d) for d in default_value) + elif isinstance(default_value, enum.Enum): + default_string = default_value.name + elif inspect.isfunction(default_value): + default_string = _("(dynamic)") + elif self.is_bool_flag and self.secondary_opts: + # For boolean flags that have distinct True/False opts, + # use the opt without prefix instead of the value. + default_string = _split_opt( + (self.opts if default_value else self.secondary_opts)[0] + )[1] + elif self.is_bool_flag and not self.secondary_opts and not default_value: + default_string = "" + elif isinstance(default_value, str) and default_value == "": + default_string = '""' + else: + default_string = str(default_value) + + if default_string: + extra["default"] = default_string + + if ( + isinstance(self.type, types._NumberRangeBase) + # skip count with default range type + and not (self.count and self.type.min == 0 and self.type.max is None) + ): + range_str = self.type._describe_range() + + if range_str: + extra["range"] = range_str + + if self.required: + extra["required"] = "required" + + return extra + + def prompt_for_value(self, ctx: Context) -> t.Any: + """This is an alternative flow that can be activated in the full + value processing if a value does not exist. It will prompt the + user until a valid value exists and then returns the processed + value as result. + """ + assert self.prompt is not None + + # Calculate the default before prompting anything to lock in the value before + # attempting any user interaction. + default = self.get_default(ctx) + + # A boolean flag can use a simplified [y/n] confirmation prompt. + if self.is_bool_flag: + # If we have no boolean default, we force the user to explicitly provide + # one. + if default in (UNSET, None): + default = None + # Nothing prevent you to declare an option that is simultaneously: + # 1) auto-detected as a boolean flag, + # 2) allowed to prompt, and + # 3) still declare a non-boolean default. + # This forced casting into a boolean is necessary to align any non-boolean + # default to the prompt, which is going to be a [y/n]-style confirmation + # because the option is still a boolean flag. That way, instead of [y/n], + # we get [Y/n] or [y/N] depending on the truthy value of the default. + # Refs: https://github.com/pallets/click/pull/3030#discussion_r2289180249 + else: + default = bool(default) + return confirm(self.prompt, default) + + # If show_default is given, provide this to `prompt` as well, + # otherwise we use `prompt`'s default behavior + prompt_kwargs: t.Any = {} + if self.show_default is not None: + prompt_kwargs["show_default"] = self.show_default + + return prompt( + self.prompt, + # Use ``None`` to inform the prompt() function to reiterate until a valid + # value is provided by the user if we have no default. + default=None if default is UNSET else default, + type=self.type, + hide_input=self.hide_input, + show_choices=self.show_choices, + confirmation_prompt=self.confirmation_prompt, + value_proc=lambda x: self.process_value(ctx, x), + **prompt_kwargs, + ) + + def resolve_envvar_value(self, ctx: Context) -> str | None: + """:class:`Option` resolves its environment variable the same way as + :func:`Parameter.resolve_envvar_value`, but it also supports + :attr:`Context.auto_envvar_prefix`. If we could not find an environment from + the :attr:`envvar` property, we fallback on :attr:`Context.auto_envvar_prefix` + to build dynamiccaly the environment variable name using the + :python:`{ctx.auto_envvar_prefix}_{self.name.upper()}` template. + + :meta private: + """ + rv = super().resolve_envvar_value(ctx) + + if rv is not None: + return rv + + if ( + self.allow_from_autoenv + and ctx.auto_envvar_prefix is not None + and self.name is not None + ): + envvar = f"{ctx.auto_envvar_prefix}_{self.name.upper()}" + rv = os.environ.get(envvar) + + if rv: + return rv + + return None + + def value_from_envvar(self, ctx: Context) -> t.Any: + """For :class:`Option`, this method processes the raw environment variable + string the same way as :func:`Parameter.value_from_envvar` does. + + But in the case of non-boolean flags, the value is analyzed to determine if the + flag is activated or not, and returns a boolean of its activation, or the + :attr:`flag_value` if the latter is set. + + This method also takes care of repeated options (i.e. options with + :attr:`multiple` set to ``True``). + + :meta private: + """ + rv = self.resolve_envvar_value(ctx) + + # Absent environment variable or an empty string is interpreted as unset. + if rv is None: + return None + + # Non-boolean flags are more liberal in what they accept. But a flag being a + # flag, its envvar value still needs to be analyzed to determine if the flag is + # activated or not. + if self.is_flag and not self.is_bool_flag: + # If the flag_value is set and match the envvar value, return it + # directly. + if self.flag_value is not UNSET and rv == self.flag_value: + return self.flag_value + # Analyze the envvar value as a boolean to know if the flag is + # activated or not. + return types.BoolParamType.str_to_bool(rv) + + # Split the envvar value if it is allowed to be repeated. + value_depth = (self.nargs != 1) + bool(self.multiple) + if value_depth > 0: + multi_rv = self.type.split_envvar_value(rv) + if self.multiple and self.nargs != 1: + multi_rv = batch(multi_rv, self.nargs) # type: ignore[assignment] + + return multi_rv + + return rv + + def consume_value( + self, ctx: Context, opts: cabc.Mapping[str, Parameter] + ) -> tuple[t.Any, ParameterSource]: + """For :class:`Option`, the value can be collected from an interactive prompt + if the option is a flag that needs a value (and the :attr:`prompt` property is + set). + + Additionally, this method handles flag option that are activated without a + value, in which case the :attr:`flag_value` is returned. + + :meta private: + """ + value, source = super().consume_value(ctx, opts) + + # The parser will emit a sentinel value if the option is allowed to as a flag + # without a value. + if value is FLAG_NEEDS_VALUE: + # If the option allows for a prompt, we start an interaction with the user. + if self.prompt is not None and not ctx.resilient_parsing: + value = self.prompt_for_value(ctx) + source = ParameterSource.PROMPT + # Else the flag takes its flag_value as value. + else: + value = self.flag_value + source = ParameterSource.COMMANDLINE + + # A flag which is activated always returns the flag value, unless the value + # comes from the explicitly sets default. + elif ( + self.is_flag + and value is True + and not self.is_bool_flag + and source < ParameterSource.DEFAULT_MAP + ): + value = self.flag_value + + # Re-interpret a multiple option which has been sent as-is by the parser. + # Here we replace each occurrence of value-less flags (marked by the + # FLAG_NEEDS_VALUE sentinel) with the flag_value. + elif ( + self.multiple + and value is not UNSET + and source < ParameterSource.DEFAULT_MAP + and any(v is FLAG_NEEDS_VALUE for v in value) + ): + value = [self.flag_value if v is FLAG_NEEDS_VALUE else v for v in value] + source = ParameterSource.COMMANDLINE + + # The value wasn't set, or used the param's default, prompt for one to the user + # if prompting is enabled. + elif ( + (value is UNSET or source >= ParameterSource.DEFAULT_MAP) + and self.prompt is not None + and (self.required or self.prompt_required) + and not ctx.resilient_parsing + ): + value = self.prompt_for_value(ctx) + source = ParameterSource.PROMPT + + return value, source + + def process_value(self, ctx: Context, value: t.Any) -> t.Any: + # process_value has to be overridden on Options in order to capture + # `value == UNSET` cases before `type_cast_value()` gets called. + # + # Refs: + # https://github.com/pallets/click/issues/3069 + if self.is_flag and not self.required and self.is_bool_flag and value is UNSET: + value = False + + if self.callback is not None: + value = self.callback(ctx, self, value) + + return value + + # in the normal case, rely on Parameter.process_value + return super().process_value(ctx, value) + + +class Argument(Parameter): + """Arguments are positional parameters to a command. They generally + provide fewer features than options but can have infinite ``nargs`` + and are required by default. + + All parameters are passed onwards to the constructor of :class:`Parameter`. + """ + + param_type_name = "argument" + + def __init__( + self, + param_decls: cabc.Sequence[str], + required: bool | None = None, + **attrs: t.Any, + ) -> None: + # Auto-detect the requirement status of the argument if not explicitly set. + if required is None: + # The argument gets automatically required if it has no explicit default + # value set and is setup to match at least one value. + if attrs.get("default", UNSET) is UNSET: + required = attrs.get("nargs", 1) > 0 + # If the argument has a default value, it is not required. + else: + required = False + + if "multiple" in attrs: + raise TypeError("__init__() got an unexpected keyword argument 'multiple'.") + + super().__init__(param_decls, required=required, **attrs) + + @property + def human_readable_name(self) -> str: + if self.metavar is not None: + return self.metavar + return self.name.upper() # type: ignore + + def make_metavar(self, ctx: Context) -> str: + if self.metavar is not None: + return self.metavar + var = self.type.get_metavar(param=self, ctx=ctx) + if not var: + var = self.name.upper() # type: ignore + if self.deprecated: + var += "!" + if not self.required: + var = f"[{var}]" + if self.nargs != 1: + var += "..." + return var + + def _parse_decls( + self, decls: cabc.Sequence[str], expose_value: bool + ) -> tuple[str | None, list[str], list[str]]: + if not decls: + if not expose_value: + return None, [], [] + raise TypeError("Argument is marked as exposed, but does not have a name.") + if len(decls) == 1: + name = arg = decls[0] + name = name.replace("-", "_").lower() + else: + raise TypeError( + "Arguments take exactly one parameter declaration, got" + f" {len(decls)}: {decls}." + ) + return name, [arg], [] + + def get_usage_pieces(self, ctx: Context) -> list[str]: + return [self.make_metavar(ctx)] + + def get_error_hint(self, ctx: Context) -> str: + return f"'{self.make_metavar(ctx)}'" + + def add_to_parser(self, parser: _OptionParser, ctx: Context) -> None: + parser.add_argument(dest=self.name, nargs=self.nargs, obj=self) + + +def __getattr__(name: str) -> object: + import warnings + + if name == "BaseCommand": + warnings.warn( + "'BaseCommand' is deprecated and will be removed in Click 9.0. Use" + " 'Command' instead.", + DeprecationWarning, + stacklevel=2, + ) + return _BaseCommand + + if name == "MultiCommand": + warnings.warn( + "'MultiCommand' is deprecated and will be removed in Click 9.0. Use" + " 'Group' instead.", + DeprecationWarning, + stacklevel=2, + ) + return _MultiCommand + + raise AttributeError(name) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/click/decorators.py b/bioagent-bench/.venv/lib/python3.14/site-packages/click/decorators.py new file mode 100644 index 0000000000000000000000000000000000000000..21f4c342245fa93a7946b17d7661e0d2fa6a0c29 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/click/decorators.py @@ -0,0 +1,551 @@ +from __future__ import annotations + +import inspect +import typing as t +from functools import update_wrapper +from gettext import gettext as _ + +from .core import Argument +from .core import Command +from .core import Context +from .core import Group +from .core import Option +from .core import Parameter +from .globals import get_current_context +from .utils import echo + +if t.TYPE_CHECKING: + import typing_extensions as te + + P = te.ParamSpec("P") + +R = t.TypeVar("R") +T = t.TypeVar("T") +_AnyCallable = t.Callable[..., t.Any] +FC = t.TypeVar("FC", bound="_AnyCallable | Command") + + +def pass_context(f: t.Callable[te.Concatenate[Context, P], R]) -> t.Callable[P, R]: + """Marks a callback as wanting to receive the current context + object as first argument. + """ + + def new_func(*args: P.args, **kwargs: P.kwargs) -> R: + return f(get_current_context(), *args, **kwargs) + + return update_wrapper(new_func, f) + + +def pass_obj(f: t.Callable[te.Concatenate[T, P], R]) -> t.Callable[P, R]: + """Similar to :func:`pass_context`, but only pass the object on the + context onwards (:attr:`Context.obj`). This is useful if that object + represents the state of a nested system. + """ + + def new_func(*args: P.args, **kwargs: P.kwargs) -> R: + return f(get_current_context().obj, *args, **kwargs) + + return update_wrapper(new_func, f) + + +def make_pass_decorator( + object_type: type[T], ensure: bool = False +) -> t.Callable[[t.Callable[te.Concatenate[T, P], R]], t.Callable[P, R]]: + """Given an object type this creates a decorator that will work + similar to :func:`pass_obj` but instead of passing the object of the + current context, it will find the innermost context of type + :func:`object_type`. + + This generates a decorator that works roughly like this:: + + from functools import update_wrapper + + def decorator(f): + @pass_context + def new_func(ctx, *args, **kwargs): + obj = ctx.find_object(object_type) + return ctx.invoke(f, obj, *args, **kwargs) + return update_wrapper(new_func, f) + return decorator + + :param object_type: the type of the object to pass. + :param ensure: if set to `True`, a new object will be created and + remembered on the context if it's not there yet. + """ + + def decorator(f: t.Callable[te.Concatenate[T, P], R]) -> t.Callable[P, R]: + def new_func(*args: P.args, **kwargs: P.kwargs) -> R: + ctx = get_current_context() + + obj: T | None + if ensure: + obj = ctx.ensure_object(object_type) + else: + obj = ctx.find_object(object_type) + + if obj is None: + raise RuntimeError( + "Managed to invoke callback without a context" + f" object of type {object_type.__name__!r}" + " existing." + ) + + return ctx.invoke(f, obj, *args, **kwargs) + + return update_wrapper(new_func, f) + + return decorator + + +def pass_meta_key( + key: str, *, doc_description: str | None = None +) -> t.Callable[[t.Callable[te.Concatenate[T, P], R]], t.Callable[P, R]]: + """Create a decorator that passes a key from + :attr:`click.Context.meta` as the first argument to the decorated + function. + + :param key: Key in ``Context.meta`` to pass. + :param doc_description: Description of the object being passed, + inserted into the decorator's docstring. Defaults to "the 'key' + key from Context.meta". + + .. versionadded:: 8.0 + """ + + def decorator(f: t.Callable[te.Concatenate[T, P], R]) -> t.Callable[P, R]: + def new_func(*args: P.args, **kwargs: P.kwargs) -> R: + ctx = get_current_context() + obj = ctx.meta[key] + return ctx.invoke(f, obj, *args, **kwargs) + + return update_wrapper(new_func, f) + + if doc_description is None: + doc_description = f"the {key!r} key from :attr:`click.Context.meta`" + + decorator.__doc__ = ( + f"Decorator that passes {doc_description} as the first argument" + " to the decorated function." + ) + return decorator + + +CmdType = t.TypeVar("CmdType", bound=Command) + + +# variant: no call, directly as decorator for a function. +@t.overload +def command(name: _AnyCallable) -> Command: ... + + +# variant: with positional name and with positional or keyword cls argument: +# @command(namearg, CommandCls, ...) or @command(namearg, cls=CommandCls, ...) +@t.overload +def command( + name: str | None, + cls: type[CmdType], + **attrs: t.Any, +) -> t.Callable[[_AnyCallable], CmdType]: ... + + +# variant: name omitted, cls _must_ be a keyword argument, @command(cls=CommandCls, ...) +@t.overload +def command( + name: None = None, + *, + cls: type[CmdType], + **attrs: t.Any, +) -> t.Callable[[_AnyCallable], CmdType]: ... + + +# variant: with optional string name, no cls argument provided. +@t.overload +def command( + name: str | None = ..., cls: None = None, **attrs: t.Any +) -> t.Callable[[_AnyCallable], Command]: ... + + +def command( + name: str | _AnyCallable | None = None, + cls: type[CmdType] | None = None, + **attrs: t.Any, +) -> Command | t.Callable[[_AnyCallable], Command | CmdType]: + r"""Creates a new :class:`Command` and uses the decorated function as + callback. This will also automatically attach all decorated + :func:`option`\s and :func:`argument`\s as parameters to the command. + + The name of the command defaults to the name of the function, converted to + lowercase, with underscores ``_`` replaced by dashes ``-``, and the suffixes + ``_command``, ``_cmd``, ``_group``, and ``_grp`` are removed. For example, + ``init_data_command`` becomes ``init-data``. + + All keyword arguments are forwarded to the underlying command class. + For the ``params`` argument, any decorated params are appended to + the end of the list. + + Once decorated the function turns into a :class:`Command` instance + that can be invoked as a command line utility or be attached to a + command :class:`Group`. + + :param name: The name of the command. Defaults to modifying the function's + name as described above. + :param cls: The command class to create. Defaults to :class:`Command`. + + .. versionchanged:: 8.2 + The suffixes ``_command``, ``_cmd``, ``_group``, and ``_grp`` are + removed when generating the name. + + .. versionchanged:: 8.1 + This decorator can be applied without parentheses. + + .. versionchanged:: 8.1 + The ``params`` argument can be used. Decorated params are + appended to the end of the list. + """ + + func: t.Callable[[_AnyCallable], t.Any] | None = None + + if callable(name): + func = name + name = None + assert cls is None, "Use 'command(cls=cls)(callable)' to specify a class." + assert not attrs, "Use 'command(**kwargs)(callable)' to provide arguments." + + if cls is None: + cls = t.cast("type[CmdType]", Command) + + def decorator(f: _AnyCallable) -> CmdType: + if isinstance(f, Command): + raise TypeError("Attempted to convert a callback into a command twice.") + + attr_params = attrs.pop("params", None) + params = attr_params if attr_params is not None else [] + + try: + decorator_params = f.__click_params__ # type: ignore + except AttributeError: + pass + else: + del f.__click_params__ # type: ignore + params.extend(reversed(decorator_params)) + + if attrs.get("help") is None: + attrs["help"] = f.__doc__ + + if t.TYPE_CHECKING: + assert cls is not None + assert not callable(name) + + if name is not None: + cmd_name = name + else: + cmd_name = f.__name__.lower().replace("_", "-") + cmd_left, sep, suffix = cmd_name.rpartition("-") + + if sep and suffix in {"command", "cmd", "group", "grp"}: + cmd_name = cmd_left + + cmd = cls(name=cmd_name, callback=f, params=params, **attrs) + cmd.__doc__ = f.__doc__ + return cmd + + if func is not None: + return decorator(func) + + return decorator + + +GrpType = t.TypeVar("GrpType", bound=Group) + + +# variant: no call, directly as decorator for a function. +@t.overload +def group(name: _AnyCallable) -> Group: ... + + +# variant: with positional name and with positional or keyword cls argument: +# @group(namearg, GroupCls, ...) or @group(namearg, cls=GroupCls, ...) +@t.overload +def group( + name: str | None, + cls: type[GrpType], + **attrs: t.Any, +) -> t.Callable[[_AnyCallable], GrpType]: ... + + +# variant: name omitted, cls _must_ be a keyword argument, @group(cmd=GroupCls, ...) +@t.overload +def group( + name: None = None, + *, + cls: type[GrpType], + **attrs: t.Any, +) -> t.Callable[[_AnyCallable], GrpType]: ... + + +# variant: with optional string name, no cls argument provided. +@t.overload +def group( + name: str | None = ..., cls: None = None, **attrs: t.Any +) -> t.Callable[[_AnyCallable], Group]: ... + + +def group( + name: str | _AnyCallable | None = None, + cls: type[GrpType] | None = None, + **attrs: t.Any, +) -> Group | t.Callable[[_AnyCallable], Group | GrpType]: + """Creates a new :class:`Group` with a function as callback. This + works otherwise the same as :func:`command` just that the `cls` + parameter is set to :class:`Group`. + + .. versionchanged:: 8.1 + This decorator can be applied without parentheses. + """ + if cls is None: + cls = t.cast("type[GrpType]", Group) + + if callable(name): + return command(cls=cls, **attrs)(name) + + return command(name, cls, **attrs) + + +def _param_memo(f: t.Callable[..., t.Any], param: Parameter) -> None: + if isinstance(f, Command): + f.params.append(param) + else: + if not hasattr(f, "__click_params__"): + f.__click_params__ = [] # type: ignore + + f.__click_params__.append(param) # type: ignore + + +def argument( + *param_decls: str, cls: type[Argument] | None = None, **attrs: t.Any +) -> t.Callable[[FC], FC]: + """Attaches an argument to the command. All positional arguments are + passed as parameter declarations to :class:`Argument`; all keyword + arguments are forwarded unchanged (except ``cls``). + This is equivalent to creating an :class:`Argument` instance manually + and attaching it to the :attr:`Command.params` list. + + For the default argument class, refer to :class:`Argument` and + :class:`Parameter` for descriptions of parameters. + + :param cls: the argument class to instantiate. This defaults to + :class:`Argument`. + :param param_decls: Passed as positional arguments to the constructor of + ``cls``. + :param attrs: Passed as keyword arguments to the constructor of ``cls``. + """ + if cls is None: + cls = Argument + + def decorator(f: FC) -> FC: + _param_memo(f, cls(param_decls, **attrs)) + return f + + return decorator + + +def option( + *param_decls: str, cls: type[Option] | None = None, **attrs: t.Any +) -> t.Callable[[FC], FC]: + """Attaches an option to the command. All positional arguments are + passed as parameter declarations to :class:`Option`; all keyword + arguments are forwarded unchanged (except ``cls``). + This is equivalent to creating an :class:`Option` instance manually + and attaching it to the :attr:`Command.params` list. + + For the default option class, refer to :class:`Option` and + :class:`Parameter` for descriptions of parameters. + + :param cls: the option class to instantiate. This defaults to + :class:`Option`. + :param param_decls: Passed as positional arguments to the constructor of + ``cls``. + :param attrs: Passed as keyword arguments to the constructor of ``cls``. + """ + if cls is None: + cls = Option + + def decorator(f: FC) -> FC: + _param_memo(f, cls(param_decls, **attrs)) + return f + + return decorator + + +def confirmation_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]: + """Add a ``--yes`` option which shows a prompt before continuing if + not passed. If the prompt is declined, the program will exit. + + :param param_decls: One or more option names. Defaults to the single + value ``"--yes"``. + :param kwargs: Extra arguments are passed to :func:`option`. + """ + + def callback(ctx: Context, param: Parameter, value: bool) -> None: + if not value: + ctx.abort() + + if not param_decls: + param_decls = ("--yes",) + + kwargs.setdefault("is_flag", True) + kwargs.setdefault("callback", callback) + kwargs.setdefault("expose_value", False) + kwargs.setdefault("prompt", "Do you want to continue?") + kwargs.setdefault("help", "Confirm the action without prompting.") + return option(*param_decls, **kwargs) + + +def password_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]: + """Add a ``--password`` option which prompts for a password, hiding + input and asking to enter the value again for confirmation. + + :param param_decls: One or more option names. Defaults to the single + value ``"--password"``. + :param kwargs: Extra arguments are passed to :func:`option`. + """ + if not param_decls: + param_decls = ("--password",) + + kwargs.setdefault("prompt", True) + kwargs.setdefault("confirmation_prompt", True) + kwargs.setdefault("hide_input", True) + return option(*param_decls, **kwargs) + + +def version_option( + version: str | None = None, + *param_decls: str, + package_name: str | None = None, + prog_name: str | None = None, + message: str | None = None, + **kwargs: t.Any, +) -> t.Callable[[FC], FC]: + """Add a ``--version`` option which immediately prints the version + number and exits the program. + + If ``version`` is not provided, Click will try to detect it using + :func:`importlib.metadata.version` to get the version for the + ``package_name``. + + If ``package_name`` is not provided, Click will try to detect it by + inspecting the stack frames. This will be used to detect the + version, so it must match the name of the installed package. + + :param version: The version number to show. If not provided, Click + will try to detect it. + :param param_decls: One or more option names. Defaults to the single + value ``"--version"``. + :param package_name: The package name to detect the version from. If + not provided, Click will try to detect it. + :param prog_name: The name of the CLI to show in the message. If not + provided, it will be detected from the command. + :param message: The message to show. The values ``%(prog)s``, + ``%(package)s``, and ``%(version)s`` are available. Defaults to + ``"%(prog)s, version %(version)s"``. + :param kwargs: Extra arguments are passed to :func:`option`. + :raise RuntimeError: ``version`` could not be detected. + + .. versionchanged:: 8.0 + Add the ``package_name`` parameter, and the ``%(package)s`` + value for messages. + + .. versionchanged:: 8.0 + Use :mod:`importlib.metadata` instead of ``pkg_resources``. The + version is detected based on the package name, not the entry + point name. The Python package name must match the installed + package name, or be passed with ``package_name=``. + """ + if message is None: + message = _("%(prog)s, version %(version)s") + + if version is None and package_name is None: + frame = inspect.currentframe() + f_back = frame.f_back if frame is not None else None + f_globals = f_back.f_globals if f_back is not None else None + # break reference cycle + # https://docs.python.org/3/library/inspect.html#the-interpreter-stack + del frame + + if f_globals is not None: + package_name = f_globals.get("__name__") + + if package_name == "__main__": + package_name = f_globals.get("__package__") + + if package_name: + package_name = package_name.partition(".")[0] + + def callback(ctx: Context, param: Parameter, value: bool) -> None: + if not value or ctx.resilient_parsing: + return + + nonlocal prog_name + nonlocal version + + if prog_name is None: + prog_name = ctx.find_root().info_name + + if version is None and package_name is not None: + import importlib.metadata + + try: + version = importlib.metadata.version(package_name) + except importlib.metadata.PackageNotFoundError: + raise RuntimeError( + f"{package_name!r} is not installed. Try passing" + " 'package_name' instead." + ) from None + + if version is None: + raise RuntimeError( + f"Could not determine the version for {package_name!r} automatically." + ) + + echo( + message % {"prog": prog_name, "package": package_name, "version": version}, + color=ctx.color, + ) + ctx.exit() + + if not param_decls: + param_decls = ("--version",) + + kwargs.setdefault("is_flag", True) + kwargs.setdefault("expose_value", False) + kwargs.setdefault("is_eager", True) + kwargs.setdefault("help", _("Show the version and exit.")) + kwargs["callback"] = callback + return option(*param_decls, **kwargs) + + +def help_option(*param_decls: str, **kwargs: t.Any) -> t.Callable[[FC], FC]: + """Pre-configured ``--help`` option which immediately prints the help page + and exits the program. + + :param param_decls: One or more option names. Defaults to the single + value ``"--help"``. + :param kwargs: Extra arguments are passed to :func:`option`. + """ + + def show_help(ctx: Context, param: Parameter, value: bool) -> None: + """Callback that print the help page on ```` and exits.""" + if value and not ctx.resilient_parsing: + echo(ctx.get_help(), color=ctx.color) + ctx.exit() + + if not param_decls: + param_decls = ("--help",) + + kwargs.setdefault("is_flag", True) + kwargs.setdefault("expose_value", False) + kwargs.setdefault("is_eager", True) + kwargs.setdefault("help", _("Show this message and exit.")) + kwargs.setdefault("callback", show_help) + + return option(*param_decls, **kwargs) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/click/exceptions.py b/bioagent-bench/.venv/lib/python3.14/site-packages/click/exceptions.py new file mode 100644 index 0000000000000000000000000000000000000000..4d782ee361442e4aafeac7e371ab12789c8a3751 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/click/exceptions.py @@ -0,0 +1,308 @@ +from __future__ import annotations + +import collections.abc as cabc +import typing as t +from gettext import gettext as _ +from gettext import ngettext + +from ._compat import get_text_stderr +from .globals import resolve_color_default +from .utils import echo +from .utils import format_filename + +if t.TYPE_CHECKING: + from .core import Command + from .core import Context + from .core import Parameter + + +def _join_param_hints(param_hint: cabc.Sequence[str] | str | None) -> str | None: + if param_hint is not None and not isinstance(param_hint, str): + return " / ".join(repr(x) for x in param_hint) + + return param_hint + + +class ClickException(Exception): + """An exception that Click can handle and show to the user.""" + + #: The exit code for this exception. + exit_code = 1 + + def __init__(self, message: str) -> None: + super().__init__(message) + # The context will be removed by the time we print the message, so cache + # the color settings here to be used later on (in `show`) + self.show_color: bool | None = resolve_color_default() + self.message = message + + def format_message(self) -> str: + return self.message + + def __str__(self) -> str: + return self.message + + def show(self, file: t.IO[t.Any] | None = None) -> None: + if file is None: + file = get_text_stderr() + + echo( + _("Error: {message}").format(message=self.format_message()), + file=file, + color=self.show_color, + ) + + +class UsageError(ClickException): + """An internal exception that signals a usage error. This typically + aborts any further handling. + + :param message: the error message to display. + :param ctx: optionally the context that caused this error. Click will + fill in the context automatically in some situations. + """ + + exit_code = 2 + + def __init__(self, message: str, ctx: Context | None = None) -> None: + super().__init__(message) + self.ctx = ctx + self.cmd: Command | None = self.ctx.command if self.ctx else None + + def show(self, file: t.IO[t.Any] | None = None) -> None: + if file is None: + file = get_text_stderr() + color = None + hint = "" + if ( + self.ctx is not None + and self.ctx.command.get_help_option(self.ctx) is not None + ): + hint = _("Try '{command} {option}' for help.").format( + command=self.ctx.command_path, option=self.ctx.help_option_names[0] + ) + hint = f"{hint}\n" + if self.ctx is not None: + color = self.ctx.color + echo(f"{self.ctx.get_usage()}\n{hint}", file=file, color=color) + echo( + _("Error: {message}").format(message=self.format_message()), + file=file, + color=color, + ) + + +class BadParameter(UsageError): + """An exception that formats out a standardized error message for a + bad parameter. This is useful when thrown from a callback or type as + Click will attach contextual information to it (for instance, which + parameter it is). + + .. versionadded:: 2.0 + + :param param: the parameter object that caused this error. This can + be left out, and Click will attach this info itself + if possible. + :param param_hint: a string that shows up as parameter name. This + can be used as alternative to `param` in cases + where custom validation should happen. If it is + a string it's used as such, if it's a list then + each item is quoted and separated. + """ + + def __init__( + self, + message: str, + ctx: Context | None = None, + param: Parameter | None = None, + param_hint: cabc.Sequence[str] | str | None = None, + ) -> None: + super().__init__(message, ctx) + self.param = param + self.param_hint = param_hint + + def format_message(self) -> str: + if self.param_hint is not None: + param_hint = self.param_hint + elif self.param is not None: + param_hint = self.param.get_error_hint(self.ctx) # type: ignore + else: + return _("Invalid value: {message}").format(message=self.message) + + return _("Invalid value for {param_hint}: {message}").format( + param_hint=_join_param_hints(param_hint), message=self.message + ) + + +class MissingParameter(BadParameter): + """Raised if click required an option or argument but it was not + provided when invoking the script. + + .. versionadded:: 4.0 + + :param param_type: a string that indicates the type of the parameter. + The default is to inherit the parameter type from + the given `param`. Valid values are ``'parameter'``, + ``'option'`` or ``'argument'``. + """ + + def __init__( + self, + message: str | None = None, + ctx: Context | None = None, + param: Parameter | None = None, + param_hint: cabc.Sequence[str] | str | None = None, + param_type: str | None = None, + ) -> None: + super().__init__(message or "", ctx, param, param_hint) + self.param_type = param_type + + def format_message(self) -> str: + if self.param_hint is not None: + param_hint: cabc.Sequence[str] | str | None = self.param_hint + elif self.param is not None: + param_hint = self.param.get_error_hint(self.ctx) # type: ignore + else: + param_hint = None + + param_hint = _join_param_hints(param_hint) + param_hint = f" {param_hint}" if param_hint else "" + + param_type = self.param_type + if param_type is None and self.param is not None: + param_type = self.param.param_type_name + + msg = self.message + if self.param is not None: + msg_extra = self.param.type.get_missing_message( + param=self.param, ctx=self.ctx + ) + if msg_extra: + if msg: + msg += f". {msg_extra}" + else: + msg = msg_extra + + msg = f" {msg}" if msg else "" + + # Translate param_type for known types. + if param_type == "argument": + missing = _("Missing argument") + elif param_type == "option": + missing = _("Missing option") + elif param_type == "parameter": + missing = _("Missing parameter") + else: + missing = _("Missing {param_type}").format(param_type=param_type) + + return f"{missing}{param_hint}.{msg}" + + def __str__(self) -> str: + if not self.message: + param_name = self.param.name if self.param else None + return _("Missing parameter: {param_name}").format(param_name=param_name) + else: + return self.message + + +class NoSuchOption(UsageError): + """Raised if click attempted to handle an option that does not + exist. + + .. versionadded:: 4.0 + """ + + def __init__( + self, + option_name: str, + message: str | None = None, + possibilities: cabc.Sequence[str] | None = None, + ctx: Context | None = None, + ) -> None: + if message is None: + message = _("No such option: {name}").format(name=option_name) + + super().__init__(message, ctx) + self.option_name = option_name + self.possibilities = possibilities + + def format_message(self) -> str: + if not self.possibilities: + return self.message + + possibility_str = ", ".join(sorted(self.possibilities)) + suggest = ngettext( + "Did you mean {possibility}?", + "(Possible options: {possibilities})", + len(self.possibilities), + ).format(possibility=possibility_str, possibilities=possibility_str) + return f"{self.message} {suggest}" + + +class BadOptionUsage(UsageError): + """Raised if an option is generally supplied but the use of the option + was incorrect. This is for instance raised if the number of arguments + for an option is not correct. + + .. versionadded:: 4.0 + + :param option_name: the name of the option being used incorrectly. + """ + + def __init__( + self, option_name: str, message: str, ctx: Context | None = None + ) -> None: + super().__init__(message, ctx) + self.option_name = option_name + + +class BadArgumentUsage(UsageError): + """Raised if an argument is generally supplied but the use of the argument + was incorrect. This is for instance raised if the number of values + for an argument is not correct. + + .. versionadded:: 6.0 + """ + + +class NoArgsIsHelpError(UsageError): + def __init__(self, ctx: Context) -> None: + self.ctx: Context + super().__init__(ctx.get_help(), ctx=ctx) + + def show(self, file: t.IO[t.Any] | None = None) -> None: + echo(self.format_message(), file=file, err=True, color=self.ctx.color) + + +class FileError(ClickException): + """Raised if a file cannot be opened.""" + + def __init__(self, filename: str, hint: str | None = None) -> None: + if hint is None: + hint = _("unknown error") + + super().__init__(hint) + self.ui_filename: str = format_filename(filename) + self.filename = filename + + def format_message(self) -> str: + return _("Could not open file {filename!r}: {message}").format( + filename=self.ui_filename, message=self.message + ) + + +class Abort(RuntimeError): + """An internal signalling exception that signals Click to abort.""" + + +class Exit(RuntimeError): + """An exception that indicates that the application should exit with some + status code. + + :param code: the status code to exit with. + """ + + __slots__ = ("exit_code",) + + def __init__(self, code: int = 0) -> None: + self.exit_code: int = code diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/click/formatting.py b/bioagent-bench/.venv/lib/python3.14/site-packages/click/formatting.py new file mode 100644 index 0000000000000000000000000000000000000000..0b64f831b5578e7262302ec9218fa498a2336794 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/click/formatting.py @@ -0,0 +1,301 @@ +from __future__ import annotations + +import collections.abc as cabc +from contextlib import contextmanager +from gettext import gettext as _ + +from ._compat import term_len +from .parser import _split_opt + +# Can force a width. This is used by the test system +FORCED_WIDTH: int | None = None + + +def measure_table(rows: cabc.Iterable[tuple[str, str]]) -> tuple[int, ...]: + widths: dict[int, int] = {} + + for row in rows: + for idx, col in enumerate(row): + widths[idx] = max(widths.get(idx, 0), term_len(col)) + + return tuple(y for x, y in sorted(widths.items())) + + +def iter_rows( + rows: cabc.Iterable[tuple[str, str]], col_count: int +) -> cabc.Iterator[tuple[str, ...]]: + for row in rows: + yield row + ("",) * (col_count - len(row)) + + +def wrap_text( + text: str, + width: int = 78, + initial_indent: str = "", + subsequent_indent: str = "", + preserve_paragraphs: bool = False, +) -> str: + """A helper function that intelligently wraps text. By default, it + assumes that it operates on a single paragraph of text but if the + `preserve_paragraphs` parameter is provided it will intelligently + handle paragraphs (defined by two empty lines). + + If paragraphs are handled, a paragraph can be prefixed with an empty + line containing the ``\\b`` character (``\\x08``) to indicate that + no rewrapping should happen in that block. + + :param text: the text that should be rewrapped. + :param width: the maximum width for the text. + :param initial_indent: the initial indent that should be placed on the + first line as a string. + :param subsequent_indent: the indent string that should be placed on + each consecutive line. + :param preserve_paragraphs: if this flag is set then the wrapping will + intelligently handle paragraphs. + """ + from ._textwrap import TextWrapper + + text = text.expandtabs() + wrapper = TextWrapper( + width, + initial_indent=initial_indent, + subsequent_indent=subsequent_indent, + replace_whitespace=False, + ) + if not preserve_paragraphs: + return wrapper.fill(text) + + p: list[tuple[int, bool, str]] = [] + buf: list[str] = [] + indent = None + + def _flush_par() -> None: + if not buf: + return + if buf[0].strip() == "\b": + p.append((indent or 0, True, "\n".join(buf[1:]))) + else: + p.append((indent or 0, False, " ".join(buf))) + del buf[:] + + for line in text.splitlines(): + if not line: + _flush_par() + indent = None + else: + if indent is None: + orig_len = term_len(line) + line = line.lstrip() + indent = orig_len - term_len(line) + buf.append(line) + _flush_par() + + rv = [] + for indent, raw, text in p: + with wrapper.extra_indent(" " * indent): + if raw: + rv.append(wrapper.indent_only(text)) + else: + rv.append(wrapper.fill(text)) + + return "\n\n".join(rv) + + +class HelpFormatter: + """This class helps with formatting text-based help pages. It's + usually just needed for very special internal cases, but it's also + exposed so that developers can write their own fancy outputs. + + At present, it always writes into memory. + + :param indent_increment: the additional increment for each level. + :param width: the width for the text. This defaults to the terminal + width clamped to a maximum of 78. + """ + + def __init__( + self, + indent_increment: int = 2, + width: int | None = None, + max_width: int | None = None, + ) -> None: + self.indent_increment = indent_increment + if max_width is None: + max_width = 80 + if width is None: + import shutil + + width = FORCED_WIDTH + if width is None: + width = max(min(shutil.get_terminal_size().columns, max_width) - 2, 50) + self.width = width + self.current_indent: int = 0 + self.buffer: list[str] = [] + + def write(self, string: str) -> None: + """Writes a unicode string into the internal buffer.""" + self.buffer.append(string) + + def indent(self) -> None: + """Increases the indentation.""" + self.current_indent += self.indent_increment + + def dedent(self) -> None: + """Decreases the indentation.""" + self.current_indent -= self.indent_increment + + def write_usage(self, prog: str, args: str = "", prefix: str | None = None) -> None: + """Writes a usage line into the buffer. + + :param prog: the program name. + :param args: whitespace separated list of arguments. + :param prefix: The prefix for the first line. Defaults to + ``"Usage: "``. + """ + if prefix is None: + prefix = f"{_('Usage:')} " + + usage_prefix = f"{prefix:>{self.current_indent}}{prog} " + text_width = self.width - self.current_indent + + if text_width >= (term_len(usage_prefix) + 20): + # The arguments will fit to the right of the prefix. + indent = " " * term_len(usage_prefix) + self.write( + wrap_text( + args, + text_width, + initial_indent=usage_prefix, + subsequent_indent=indent, + ) + ) + else: + # The prefix is too long, put the arguments on the next line. + self.write(usage_prefix) + self.write("\n") + indent = " " * (max(self.current_indent, term_len(prefix)) + 4) + self.write( + wrap_text( + args, text_width, initial_indent=indent, subsequent_indent=indent + ) + ) + + self.write("\n") + + def write_heading(self, heading: str) -> None: + """Writes a heading into the buffer.""" + self.write(f"{'':>{self.current_indent}}{heading}:\n") + + def write_paragraph(self) -> None: + """Writes a paragraph into the buffer.""" + if self.buffer: + self.write("\n") + + def write_text(self, text: str) -> None: + """Writes re-indented text into the buffer. This rewraps and + preserves paragraphs. + """ + indent = " " * self.current_indent + self.write( + wrap_text( + text, + self.width, + initial_indent=indent, + subsequent_indent=indent, + preserve_paragraphs=True, + ) + ) + self.write("\n") + + def write_dl( + self, + rows: cabc.Sequence[tuple[str, str]], + col_max: int = 30, + col_spacing: int = 2, + ) -> None: + """Writes a definition list into the buffer. This is how options + and commands are usually formatted. + + :param rows: a list of two item tuples for the terms and values. + :param col_max: the maximum width of the first column. + :param col_spacing: the number of spaces between the first and + second column. + """ + rows = list(rows) + widths = measure_table(rows) + if len(widths) != 2: + raise TypeError("Expected two columns for definition list") + + first_col = min(widths[0], col_max) + col_spacing + + for first, second in iter_rows(rows, len(widths)): + self.write(f"{'':>{self.current_indent}}{first}") + if not second: + self.write("\n") + continue + if term_len(first) <= first_col - col_spacing: + self.write(" " * (first_col - term_len(first))) + else: + self.write("\n") + self.write(" " * (first_col + self.current_indent)) + + text_width = max(self.width - first_col - 2, 10) + wrapped_text = wrap_text(second, text_width, preserve_paragraphs=True) + lines = wrapped_text.splitlines() + + if lines: + self.write(f"{lines[0]}\n") + + for line in lines[1:]: + self.write(f"{'':>{first_col + self.current_indent}}{line}\n") + else: + self.write("\n") + + @contextmanager + def section(self, name: str) -> cabc.Iterator[None]: + """Helpful context manager that writes a paragraph, a heading, + and the indents. + + :param name: the section name that is written as heading. + """ + self.write_paragraph() + self.write_heading(name) + self.indent() + try: + yield + finally: + self.dedent() + + @contextmanager + def indentation(self) -> cabc.Iterator[None]: + """A context manager that increases the indentation.""" + self.indent() + try: + yield + finally: + self.dedent() + + def getvalue(self) -> str: + """Returns the buffer contents.""" + return "".join(self.buffer) + + +def join_options(options: cabc.Sequence[str]) -> tuple[str, bool]: + """Given a list of option strings this joins them in the most appropriate + way and returns them in the form ``(formatted_string, + any_prefix_is_slash)`` where the second item in the tuple is a flag that + indicates if any of the option prefixes was a slash. + """ + rv = [] + any_prefix_is_slash = False + + for opt in options: + prefix = _split_opt(opt)[0] + + if prefix == "/": + any_prefix_is_slash = True + + rv.append((len(prefix), opt)) + + rv.sort(key=lambda x: x[0]) + return ", ".join(x[1] for x in rv), any_prefix_is_slash diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/click/globals.py b/bioagent-bench/.venv/lib/python3.14/site-packages/click/globals.py new file mode 100644 index 0000000000000000000000000000000000000000..a2f91723d21cefdf658be327174e1f8ccdc20fcd --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/click/globals.py @@ -0,0 +1,67 @@ +from __future__ import annotations + +import typing as t +from threading import local + +if t.TYPE_CHECKING: + from .core import Context + +_local = local() + + +@t.overload +def get_current_context(silent: t.Literal[False] = False) -> Context: ... + + +@t.overload +def get_current_context(silent: bool = ...) -> Context | None: ... + + +def get_current_context(silent: bool = False) -> Context | None: + """Returns the current click context. This can be used as a way to + access the current context object from anywhere. This is a more implicit + alternative to the :func:`pass_context` decorator. This function is + primarily useful for helpers such as :func:`echo` which might be + interested in changing its behavior based on the current context. + + To push the current context, :meth:`Context.scope` can be used. + + .. versionadded:: 5.0 + + :param silent: if set to `True` the return value is `None` if no context + is available. The default behavior is to raise a + :exc:`RuntimeError`. + """ + try: + return t.cast("Context", _local.stack[-1]) + except (AttributeError, IndexError) as e: + if not silent: + raise RuntimeError("There is no active click context.") from e + + return None + + +def push_context(ctx: Context) -> None: + """Pushes a new context to the current stack.""" + _local.__dict__.setdefault("stack", []).append(ctx) + + +def pop_context() -> None: + """Removes the top level from the stack.""" + _local.stack.pop() + + +def resolve_color_default(color: bool | None = None) -> bool | None: + """Internal helper to get the default value of the color flag. If a + value is passed it's returned unchanged, otherwise it's looked up from + the current context. + """ + if color is not None: + return color + + ctx = get_current_context(silent=True) + + if ctx is not None: + return ctx.color + + return None diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/click/parser.py b/bioagent-bench/.venv/lib/python3.14/site-packages/click/parser.py new file mode 100644 index 0000000000000000000000000000000000000000..1ea1f7166e4b9eac5bcb632ecd86fe0331829141 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/click/parser.py @@ -0,0 +1,532 @@ +""" +This module started out as largely a copy paste from the stdlib's +optparse module with the features removed that we do not need from +optparse because we implement them in Click on a higher level (for +instance type handling, help formatting and a lot more). + +The plan is to remove more and more from here over time. + +The reason this is a different module and not optparse from the stdlib +is that there are differences in 2.x and 3.x about the error messages +generated and optparse in the stdlib uses gettext for no good reason +and might cause us issues. + +Click uses parts of optparse written by Gregory P. Ward and maintained +by the Python Software Foundation. This is limited to code in parser.py. + +Copyright 2001-2006 Gregory P. Ward. All rights reserved. +Copyright 2002-2006 Python Software Foundation. All rights reserved. +""" + +# This code uses parts of optparse written by Gregory P. Ward and +# maintained by the Python Software Foundation. +# Copyright 2001-2006 Gregory P. Ward +# Copyright 2002-2006 Python Software Foundation +from __future__ import annotations + +import collections.abc as cabc +import typing as t +from collections import deque +from gettext import gettext as _ +from gettext import ngettext + +from ._utils import FLAG_NEEDS_VALUE +from ._utils import UNSET +from .exceptions import BadArgumentUsage +from .exceptions import BadOptionUsage +from .exceptions import NoSuchOption +from .exceptions import UsageError + +if t.TYPE_CHECKING: + from ._utils import T_FLAG_NEEDS_VALUE + from ._utils import T_UNSET + from .core import Argument as CoreArgument + from .core import Context + from .core import Option as CoreOption + from .core import Parameter as CoreParameter + +V = t.TypeVar("V") + + +def _unpack_args( + args: cabc.Sequence[str], nargs_spec: cabc.Sequence[int] +) -> tuple[cabc.Sequence[str | cabc.Sequence[str | None] | None], list[str]]: + """Given an iterable of arguments and an iterable of nargs specifications, + it returns a tuple with all the unpacked arguments at the first index + and all remaining arguments as the second. + + The nargs specification is the number of arguments that should be consumed + or `-1` to indicate that this position should eat up all the remainders. + + Missing items are filled with ``UNSET``. + """ + args = deque(args) + nargs_spec = deque(nargs_spec) + rv: list[str | tuple[str | T_UNSET, ...] | T_UNSET] = [] + spos: int | None = None + + def _fetch(c: deque[V]) -> V | T_UNSET: + try: + if spos is None: + return c.popleft() + else: + return c.pop() + except IndexError: + return UNSET + + while nargs_spec: + nargs = _fetch(nargs_spec) + + if nargs is None: + continue + + if nargs == 1: + rv.append(_fetch(args)) # type: ignore[arg-type] + elif nargs > 1: + x = [_fetch(args) for _ in range(nargs)] + + # If we're reversed, we're pulling in the arguments in reverse, + # so we need to turn them around. + if spos is not None: + x.reverse() + + rv.append(tuple(x)) + elif nargs < 0: + if spos is not None: + raise TypeError("Cannot have two nargs < 0") + + spos = len(rv) + rv.append(UNSET) + + # spos is the position of the wildcard (star). If it's not `None`, + # we fill it with the remainder. + if spos is not None: + rv[spos] = tuple(args) + args = [] + rv[spos + 1 :] = reversed(rv[spos + 1 :]) + + return tuple(rv), list(args) + + +def _split_opt(opt: str) -> tuple[str, str]: + first = opt[:1] + if first.isalnum(): + return "", opt + if opt[1:2] == first: + return opt[:2], opt[2:] + return first, opt[1:] + + +def _normalize_opt(opt: str, ctx: Context | None) -> str: + if ctx is None or ctx.token_normalize_func is None: + return opt + prefix, opt = _split_opt(opt) + return f"{prefix}{ctx.token_normalize_func(opt)}" + + +class _Option: + def __init__( + self, + obj: CoreOption, + opts: cabc.Sequence[str], + dest: str | None, + action: str | None = None, + nargs: int = 1, + const: t.Any | None = None, + ): + self._short_opts = [] + self._long_opts = [] + self.prefixes: set[str] = set() + + for opt in opts: + prefix, value = _split_opt(opt) + if not prefix: + raise ValueError(f"Invalid start character for option ({opt})") + self.prefixes.add(prefix[0]) + if len(prefix) == 1 and len(value) == 1: + self._short_opts.append(opt) + else: + self._long_opts.append(opt) + self.prefixes.add(prefix) + + if action is None: + action = "store" + + self.dest = dest + self.action = action + self.nargs = nargs + self.const = const + self.obj = obj + + @property + def takes_value(self) -> bool: + return self.action in ("store", "append") + + def process(self, value: t.Any, state: _ParsingState) -> None: + if self.action == "store": + state.opts[self.dest] = value # type: ignore + elif self.action == "store_const": + state.opts[self.dest] = self.const # type: ignore + elif self.action == "append": + state.opts.setdefault(self.dest, []).append(value) # type: ignore + elif self.action == "append_const": + state.opts.setdefault(self.dest, []).append(self.const) # type: ignore + elif self.action == "count": + state.opts[self.dest] = state.opts.get(self.dest, 0) + 1 # type: ignore + else: + raise ValueError(f"unknown action '{self.action}'") + state.order.append(self.obj) + + +class _Argument: + def __init__(self, obj: CoreArgument, dest: str | None, nargs: int = 1): + self.dest = dest + self.nargs = nargs + self.obj = obj + + def process( + self, + value: str | cabc.Sequence[str | None] | None | T_UNSET, + state: _ParsingState, + ) -> None: + if self.nargs > 1: + assert isinstance(value, cabc.Sequence) + holes = sum(1 for x in value if x is UNSET) + if holes == len(value): + value = UNSET + elif holes != 0: + raise BadArgumentUsage( + _("Argument {name!r} takes {nargs} values.").format( + name=self.dest, nargs=self.nargs + ) + ) + + # We failed to collect any argument value so we consider the argument as unset. + if value == (): + value = UNSET + + state.opts[self.dest] = value # type: ignore + state.order.append(self.obj) + + +class _ParsingState: + def __init__(self, rargs: list[str]) -> None: + self.opts: dict[str, t.Any] = {} + self.largs: list[str] = [] + self.rargs = rargs + self.order: list[CoreParameter] = [] + + +class _OptionParser: + """The option parser is an internal class that is ultimately used to + parse options and arguments. It's modelled after optparse and brings + a similar but vastly simplified API. It should generally not be used + directly as the high level Click classes wrap it for you. + + It's not nearly as extensible as optparse or argparse as it does not + implement features that are implemented on a higher level (such as + types or defaults). + + :param ctx: optionally the :class:`~click.Context` where this parser + should go with. + + .. deprecated:: 8.2 + Will be removed in Click 9.0. + """ + + def __init__(self, ctx: Context | None = None) -> None: + #: The :class:`~click.Context` for this parser. This might be + #: `None` for some advanced use cases. + self.ctx = ctx + #: This controls how the parser deals with interspersed arguments. + #: If this is set to `False`, the parser will stop on the first + #: non-option. Click uses this to implement nested subcommands + #: safely. + self.allow_interspersed_args: bool = True + #: This tells the parser how to deal with unknown options. By + #: default it will error out (which is sensible), but there is a + #: second mode where it will ignore it and continue processing + #: after shifting all the unknown options into the resulting args. + self.ignore_unknown_options: bool = False + + if ctx is not None: + self.allow_interspersed_args = ctx.allow_interspersed_args + self.ignore_unknown_options = ctx.ignore_unknown_options + + self._short_opt: dict[str, _Option] = {} + self._long_opt: dict[str, _Option] = {} + self._opt_prefixes = {"-", "--"} + self._args: list[_Argument] = [] + + def add_option( + self, + obj: CoreOption, + opts: cabc.Sequence[str], + dest: str | None, + action: str | None = None, + nargs: int = 1, + const: t.Any | None = None, + ) -> None: + """Adds a new option named `dest` to the parser. The destination + is not inferred (unlike with optparse) and needs to be explicitly + provided. Action can be any of ``store``, ``store_const``, + ``append``, ``append_const`` or ``count``. + + The `obj` can be used to identify the option in the order list + that is returned from the parser. + """ + opts = [_normalize_opt(opt, self.ctx) for opt in opts] + option = _Option(obj, opts, dest, action=action, nargs=nargs, const=const) + self._opt_prefixes.update(option.prefixes) + for opt in option._short_opts: + self._short_opt[opt] = option + for opt in option._long_opts: + self._long_opt[opt] = option + + def add_argument(self, obj: CoreArgument, dest: str | None, nargs: int = 1) -> None: + """Adds a positional argument named `dest` to the parser. + + The `obj` can be used to identify the option in the order list + that is returned from the parser. + """ + self._args.append(_Argument(obj, dest=dest, nargs=nargs)) + + def parse_args( + self, args: list[str] + ) -> tuple[dict[str, t.Any], list[str], list[CoreParameter]]: + """Parses positional arguments and returns ``(values, args, order)`` + for the parsed options and arguments as well as the leftover + arguments if there are any. The order is a list of objects as they + appear on the command line. If arguments appear multiple times they + will be memorized multiple times as well. + """ + state = _ParsingState(args) + try: + self._process_args_for_options(state) + self._process_args_for_args(state) + except UsageError: + if self.ctx is None or not self.ctx.resilient_parsing: + raise + return state.opts, state.largs, state.order + + def _process_args_for_args(self, state: _ParsingState) -> None: + pargs, args = _unpack_args( + state.largs + state.rargs, [x.nargs for x in self._args] + ) + + for idx, arg in enumerate(self._args): + arg.process(pargs[idx], state) + + state.largs = args + state.rargs = [] + + def _process_args_for_options(self, state: _ParsingState) -> None: + while state.rargs: + arg = state.rargs.pop(0) + arglen = len(arg) + # Double dashes always handled explicitly regardless of what + # prefixes are valid. + if arg == "--": + return + elif arg[:1] in self._opt_prefixes and arglen > 1: + self._process_opts(arg, state) + elif self.allow_interspersed_args: + state.largs.append(arg) + else: + state.rargs.insert(0, arg) + return + + # Say this is the original argument list: + # [arg0, arg1, ..., arg(i-1), arg(i), arg(i+1), ..., arg(N-1)] + # ^ + # (we are about to process arg(i)). + # + # Then rargs is [arg(i), ..., arg(N-1)] and largs is a *subset* of + # [arg0, ..., arg(i-1)] (any options and their arguments will have + # been removed from largs). + # + # The while loop will usually consume 1 or more arguments per pass. + # If it consumes 1 (eg. arg is an option that takes no arguments), + # then after _process_arg() is done the situation is: + # + # largs = subset of [arg0, ..., arg(i)] + # rargs = [arg(i+1), ..., arg(N-1)] + # + # If allow_interspersed_args is false, largs will always be + # *empty* -- still a subset of [arg0, ..., arg(i-1)], but + # not a very interesting subset! + + def _match_long_opt( + self, opt: str, explicit_value: str | None, state: _ParsingState + ) -> None: + if opt not in self._long_opt: + from difflib import get_close_matches + + possibilities = get_close_matches(opt, self._long_opt) + raise NoSuchOption(opt, possibilities=possibilities, ctx=self.ctx) + + option = self._long_opt[opt] + if option.takes_value: + # At this point it's safe to modify rargs by injecting the + # explicit value, because no exception is raised in this + # branch. This means that the inserted value will be fully + # consumed. + if explicit_value is not None: + state.rargs.insert(0, explicit_value) + + value = self._get_value_from_state(opt, option, state) + + elif explicit_value is not None: + raise BadOptionUsage( + opt, _("Option {name!r} does not take a value.").format(name=opt) + ) + + else: + value = UNSET + + option.process(value, state) + + def _match_short_opt(self, arg: str, state: _ParsingState) -> None: + stop = False + i = 1 + prefix = arg[0] + unknown_options = [] + + for ch in arg[1:]: + opt = _normalize_opt(f"{prefix}{ch}", self.ctx) + option = self._short_opt.get(opt) + i += 1 + + if not option: + if self.ignore_unknown_options: + unknown_options.append(ch) + continue + raise NoSuchOption(opt, ctx=self.ctx) + if option.takes_value: + # Any characters left in arg? Pretend they're the + # next arg, and stop consuming characters of arg. + if i < len(arg): + state.rargs.insert(0, arg[i:]) + stop = True + + value = self._get_value_from_state(opt, option, state) + + else: + value = UNSET + + option.process(value, state) + + if stop: + break + + # If we got any unknown options we recombine the string of the + # remaining options and re-attach the prefix, then report that + # to the state as new larg. This way there is basic combinatorics + # that can be achieved while still ignoring unknown arguments. + if self.ignore_unknown_options and unknown_options: + state.largs.append(f"{prefix}{''.join(unknown_options)}") + + def _get_value_from_state( + self, option_name: str, option: _Option, state: _ParsingState + ) -> str | cabc.Sequence[str] | T_FLAG_NEEDS_VALUE: + nargs = option.nargs + + value: str | cabc.Sequence[str] | T_FLAG_NEEDS_VALUE + + if len(state.rargs) < nargs: + if option.obj._flag_needs_value: + # Option allows omitting the value. + value = FLAG_NEEDS_VALUE + else: + raise BadOptionUsage( + option_name, + ngettext( + "Option {name!r} requires an argument.", + "Option {name!r} requires {nargs} arguments.", + nargs, + ).format(name=option_name, nargs=nargs), + ) + elif nargs == 1: + next_rarg = state.rargs[0] + + if ( + option.obj._flag_needs_value + and isinstance(next_rarg, str) + and next_rarg[:1] in self._opt_prefixes + and len(next_rarg) > 1 + ): + # The next arg looks like the start of an option, don't + # use it as the value if omitting the value is allowed. + value = FLAG_NEEDS_VALUE + else: + value = state.rargs.pop(0) + else: + value = tuple(state.rargs[:nargs]) + del state.rargs[:nargs] + + return value + + def _process_opts(self, arg: str, state: _ParsingState) -> None: + explicit_value = None + # Long option handling happens in two parts. The first part is + # supporting explicitly attached values. In any case, we will try + # to long match the option first. + if "=" in arg: + long_opt, explicit_value = arg.split("=", 1) + else: + long_opt = arg + norm_long_opt = _normalize_opt(long_opt, self.ctx) + + # At this point we will match the (assumed) long option through + # the long option matching code. Note that this allows options + # like "-foo" to be matched as long options. + try: + self._match_long_opt(norm_long_opt, explicit_value, state) + except NoSuchOption: + # At this point the long option matching failed, and we need + # to try with short options. However there is a special rule + # which says, that if we have a two character options prefix + # (applies to "--foo" for instance), we do not dispatch to the + # short option code and will instead raise the no option + # error. + if arg[:2] not in self._opt_prefixes: + self._match_short_opt(arg, state) + return + + if not self.ignore_unknown_options: + raise + + state.largs.append(arg) + + +def __getattr__(name: str) -> object: + import warnings + + if name in { + "OptionParser", + "Argument", + "Option", + "split_opt", + "normalize_opt", + "ParsingState", + }: + warnings.warn( + f"'parser.{name}' is deprecated and will be removed in Click 9.0." + " The old parser is available in 'optparse'.", + DeprecationWarning, + stacklevel=2, + ) + return globals()[f"_{name}"] + + if name == "split_arg_string": + from .shell_completion import split_arg_string + + warnings.warn( + "Importing 'parser.split_arg_string' is deprecated, it will only be" + " available in 'shell_completion' in Click 9.0.", + DeprecationWarning, + stacklevel=2, + ) + return split_arg_string + + raise AttributeError(name) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/click/py.typed b/bioagent-bench/.venv/lib/python3.14/site-packages/click/py.typed new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/click/shell_completion.py b/bioagent-bench/.venv/lib/python3.14/site-packages/click/shell_completion.py new file mode 100644 index 0000000000000000000000000000000000000000..8f1564c49be8e138014841d8f7cb35a34265674f --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/click/shell_completion.py @@ -0,0 +1,667 @@ +from __future__ import annotations + +import collections.abc as cabc +import os +import re +import typing as t +from gettext import gettext as _ + +from .core import Argument +from .core import Command +from .core import Context +from .core import Group +from .core import Option +from .core import Parameter +from .core import ParameterSource +from .utils import echo + + +def shell_complete( + cli: Command, + ctx_args: cabc.MutableMapping[str, t.Any], + prog_name: str, + complete_var: str, + instruction: str, +) -> int: + """Perform shell completion for the given CLI program. + + :param cli: Command being called. + :param ctx_args: Extra arguments to pass to + ``cli.make_context``. + :param prog_name: Name of the executable in the shell. + :param complete_var: Name of the environment variable that holds + the completion instruction. + :param instruction: Value of ``complete_var`` with the completion + instruction and shell, in the form ``instruction_shell``. + :return: Status code to exit with. + """ + shell, _, instruction = instruction.partition("_") + comp_cls = get_completion_class(shell) + + if comp_cls is None: + return 1 + + comp = comp_cls(cli, ctx_args, prog_name, complete_var) + + if instruction == "source": + echo(comp.source()) + return 0 + + if instruction == "complete": + echo(comp.complete()) + return 0 + + return 1 + + +class CompletionItem: + """Represents a completion value and metadata about the value. The + default metadata is ``type`` to indicate special shell handling, + and ``help`` if a shell supports showing a help string next to the + value. + + Arbitrary parameters can be passed when creating the object, and + accessed using ``item.attr``. If an attribute wasn't passed, + accessing it returns ``None``. + + :param value: The completion suggestion. + :param type: Tells the shell script to provide special completion + support for the type. Click uses ``"dir"`` and ``"file"``. + :param help: String shown next to the value if supported. + :param kwargs: Arbitrary metadata. The built-in implementations + don't use this, but custom type completions paired with custom + shell support could use it. + """ + + __slots__ = ("value", "type", "help", "_info") + + def __init__( + self, + value: t.Any, + type: str = "plain", + help: str | None = None, + **kwargs: t.Any, + ) -> None: + self.value: t.Any = value + self.type: str = type + self.help: str | None = help + self._info = kwargs + + def __getattr__(self, name: str) -> t.Any: + return self._info.get(name) + + +# Only Bash >= 4.4 has the nosort option. +_SOURCE_BASH = """\ +%(complete_func)s() { + local IFS=$'\\n' + local response + + response=$(env COMP_WORDS="${COMP_WORDS[*]}" COMP_CWORD=$COMP_CWORD \ +%(complete_var)s=bash_complete $1) + + for completion in $response; do + IFS=',' read type value <<< "$completion" + + if [[ $type == 'dir' ]]; then + COMPREPLY=() + compopt -o dirnames + elif [[ $type == 'file' ]]; then + COMPREPLY=() + compopt -o default + elif [[ $type == 'plain' ]]; then + COMPREPLY+=($value) + fi + done + + return 0 +} + +%(complete_func)s_setup() { + complete -o nosort -F %(complete_func)s %(prog_name)s +} + +%(complete_func)s_setup; +""" + +# See ZshComplete.format_completion below, and issue #2703, before +# changing this script. +# +# (TL;DR: _describe is picky about the format, but this Zsh script snippet +# is already widely deployed. So freeze this script, and use clever-ish +# handling of colons in ZshComplet.format_completion.) +_SOURCE_ZSH = """\ +#compdef %(prog_name)s + +%(complete_func)s() { + local -a completions + local -a completions_with_descriptions + local -a response + (( ! $+commands[%(prog_name)s] )) && return 1 + + response=("${(@f)$(env COMP_WORDS="${words[*]}" COMP_CWORD=$((CURRENT-1)) \ +%(complete_var)s=zsh_complete %(prog_name)s)}") + + for type key descr in ${response}; do + if [[ "$type" == "plain" ]]; then + if [[ "$descr" == "_" ]]; then + completions+=("$key") + else + completions_with_descriptions+=("$key":"$descr") + fi + elif [[ "$type" == "dir" ]]; then + _path_files -/ + elif [[ "$type" == "file" ]]; then + _path_files -f + fi + done + + if [ -n "$completions_with_descriptions" ]; then + _describe -V unsorted completions_with_descriptions -U + fi + + if [ -n "$completions" ]; then + compadd -U -V unsorted -a completions + fi +} + +if [[ $zsh_eval_context[-1] == loadautofunc ]]; then + # autoload from fpath, call function directly + %(complete_func)s "$@" +else + # eval/source/. command, register function for later + compdef %(complete_func)s %(prog_name)s +fi +""" + +_SOURCE_FISH = """\ +function %(complete_func)s; + set -l response (env %(complete_var)s=fish_complete COMP_WORDS=(commandline -cp) \ +COMP_CWORD=(commandline -t) %(prog_name)s); + + for completion in $response; + set -l metadata (string split "," $completion); + + if test $metadata[1] = "dir"; + __fish_complete_directories $metadata[2]; + else if test $metadata[1] = "file"; + __fish_complete_path $metadata[2]; + else if test $metadata[1] = "plain"; + echo $metadata[2]; + end; + end; +end; + +complete --no-files --command %(prog_name)s --arguments \ +"(%(complete_func)s)"; +""" + + +class ShellComplete: + """Base class for providing shell completion support. A subclass for + a given shell will override attributes and methods to implement the + completion instructions (``source`` and ``complete``). + + :param cli: Command being called. + :param prog_name: Name of the executable in the shell. + :param complete_var: Name of the environment variable that holds + the completion instruction. + + .. versionadded:: 8.0 + """ + + name: t.ClassVar[str] + """Name to register the shell as with :func:`add_completion_class`. + This is used in completion instructions (``{name}_source`` and + ``{name}_complete``). + """ + + source_template: t.ClassVar[str] + """Completion script template formatted by :meth:`source`. This must + be provided by subclasses. + """ + + def __init__( + self, + cli: Command, + ctx_args: cabc.MutableMapping[str, t.Any], + prog_name: str, + complete_var: str, + ) -> None: + self.cli = cli + self.ctx_args = ctx_args + self.prog_name = prog_name + self.complete_var = complete_var + + @property + def func_name(self) -> str: + """The name of the shell function defined by the completion + script. + """ + safe_name = re.sub(r"\W*", "", self.prog_name.replace("-", "_"), flags=re.ASCII) + return f"_{safe_name}_completion" + + def source_vars(self) -> dict[str, t.Any]: + """Vars for formatting :attr:`source_template`. + + By default this provides ``complete_func``, ``complete_var``, + and ``prog_name``. + """ + return { + "complete_func": self.func_name, + "complete_var": self.complete_var, + "prog_name": self.prog_name, + } + + def source(self) -> str: + """Produce the shell script that defines the completion + function. By default this ``%``-style formats + :attr:`source_template` with the dict returned by + :meth:`source_vars`. + """ + return self.source_template % self.source_vars() + + def get_completion_args(self) -> tuple[list[str], str]: + """Use the env vars defined by the shell script to return a + tuple of ``args, incomplete``. This must be implemented by + subclasses. + """ + raise NotImplementedError + + def get_completions(self, args: list[str], incomplete: str) -> list[CompletionItem]: + """Determine the context and last complete command or parameter + from the complete args. Call that object's ``shell_complete`` + method to get the completions for the incomplete value. + + :param args: List of complete args before the incomplete value. + :param incomplete: Value being completed. May be empty. + """ + ctx = _resolve_context(self.cli, self.ctx_args, self.prog_name, args) + obj, incomplete = _resolve_incomplete(ctx, args, incomplete) + return obj.shell_complete(ctx, incomplete) + + def format_completion(self, item: CompletionItem) -> str: + """Format a completion item into the form recognized by the + shell script. This must be implemented by subclasses. + + :param item: Completion item to format. + """ + raise NotImplementedError + + def complete(self) -> str: + """Produce the completion data to send back to the shell. + + By default this calls :meth:`get_completion_args`, gets the + completions, then calls :meth:`format_completion` for each + completion. + """ + args, incomplete = self.get_completion_args() + completions = self.get_completions(args, incomplete) + out = [self.format_completion(item) for item in completions] + return "\n".join(out) + + +class BashComplete(ShellComplete): + """Shell completion for Bash.""" + + name = "bash" + source_template = _SOURCE_BASH + + @staticmethod + def _check_version() -> None: + import shutil + import subprocess + + bash_exe = shutil.which("bash") + + if bash_exe is None: + match = None + else: + output = subprocess.run( + [bash_exe, "--norc", "-c", 'echo "${BASH_VERSION}"'], + stdout=subprocess.PIPE, + ) + match = re.search(r"^(\d+)\.(\d+)\.\d+", output.stdout.decode()) + + if match is not None: + major, minor = match.groups() + + if major < "4" or major == "4" and minor < "4": + echo( + _( + "Shell completion is not supported for Bash" + " versions older than 4.4." + ), + err=True, + ) + else: + echo( + _("Couldn't detect Bash version, shell completion is not supported."), + err=True, + ) + + def source(self) -> str: + self._check_version() + return super().source() + + def get_completion_args(self) -> tuple[list[str], str]: + cwords = split_arg_string(os.environ["COMP_WORDS"]) + cword = int(os.environ["COMP_CWORD"]) + args = cwords[1:cword] + + try: + incomplete = cwords[cword] + except IndexError: + incomplete = "" + + return args, incomplete + + def format_completion(self, item: CompletionItem) -> str: + return f"{item.type},{item.value}" + + +class ZshComplete(ShellComplete): + """Shell completion for Zsh.""" + + name = "zsh" + source_template = _SOURCE_ZSH + + def get_completion_args(self) -> tuple[list[str], str]: + cwords = split_arg_string(os.environ["COMP_WORDS"]) + cword = int(os.environ["COMP_CWORD"]) + args = cwords[1:cword] + + try: + incomplete = cwords[cword] + except IndexError: + incomplete = "" + + return args, incomplete + + def format_completion(self, item: CompletionItem) -> str: + help_ = item.help or "_" + # The zsh completion script uses `_describe` on items with help + # texts (which splits the item help from the item value at the + # first unescaped colon) and `compadd` on items without help + # text (which uses the item value as-is and does not support + # colon escaping). So escape colons in the item value if and + # only if the item help is not the sentinel "_" value, as used + # by the completion script. + # + # (The zsh completion script is potentially widely deployed, and + # thus harder to fix than this method.) + # + # See issue #1812 and issue #2703 for further context. + value = item.value.replace(":", r"\:") if help_ != "_" else item.value + return f"{item.type}\n{value}\n{help_}" + + +class FishComplete(ShellComplete): + """Shell completion for Fish.""" + + name = "fish" + source_template = _SOURCE_FISH + + def get_completion_args(self) -> tuple[list[str], str]: + cwords = split_arg_string(os.environ["COMP_WORDS"]) + incomplete = os.environ["COMP_CWORD"] + if incomplete: + incomplete = split_arg_string(incomplete)[0] + args = cwords[1:] + + # Fish stores the partial word in both COMP_WORDS and + # COMP_CWORD, remove it from complete args. + if incomplete and args and args[-1] == incomplete: + args.pop() + + return args, incomplete + + def format_completion(self, item: CompletionItem) -> str: + if item.help: + return f"{item.type},{item.value}\t{item.help}" + + return f"{item.type},{item.value}" + + +ShellCompleteType = t.TypeVar("ShellCompleteType", bound="type[ShellComplete]") + + +_available_shells: dict[str, type[ShellComplete]] = { + "bash": BashComplete, + "fish": FishComplete, + "zsh": ZshComplete, +} + + +def add_completion_class( + cls: ShellCompleteType, name: str | None = None +) -> ShellCompleteType: + """Register a :class:`ShellComplete` subclass under the given name. + The name will be provided by the completion instruction environment + variable during completion. + + :param cls: The completion class that will handle completion for the + shell. + :param name: Name to register the class under. Defaults to the + class's ``name`` attribute. + """ + if name is None: + name = cls.name + + _available_shells[name] = cls + + return cls + + +def get_completion_class(shell: str) -> type[ShellComplete] | None: + """Look up a registered :class:`ShellComplete` subclass by the name + provided by the completion instruction environment variable. If the + name isn't registered, returns ``None``. + + :param shell: Name the class is registered under. + """ + return _available_shells.get(shell) + + +def split_arg_string(string: str) -> list[str]: + """Split an argument string as with :func:`shlex.split`, but don't + fail if the string is incomplete. Ignores a missing closing quote or + incomplete escape sequence and uses the partial token as-is. + + .. code-block:: python + + split_arg_string("example 'my file") + ["example", "my file"] + + split_arg_string("example my\\") + ["example", "my"] + + :param string: String to split. + + .. versionchanged:: 8.2 + Moved to ``shell_completion`` from ``parser``. + """ + import shlex + + lex = shlex.shlex(string, posix=True) + lex.whitespace_split = True + lex.commenters = "" + out = [] + + try: + for token in lex: + out.append(token) + except ValueError: + # Raised when end-of-string is reached in an invalid state. Use + # the partial token as-is. The quote or escape character is in + # lex.state, not lex.token. + out.append(lex.token) + + return out + + +def _is_incomplete_argument(ctx: Context, param: Parameter) -> bool: + """Determine if the given parameter is an argument that can still + accept values. + + :param ctx: Invocation context for the command represented by the + parsed complete args. + :param param: Argument object being checked. + """ + if not isinstance(param, Argument): + return False + + assert param.name is not None + # Will be None if expose_value is False. + value = ctx.params.get(param.name) + return ( + param.nargs == -1 + or ctx.get_parameter_source(param.name) is not ParameterSource.COMMANDLINE + or ( + param.nargs > 1 + and isinstance(value, (tuple, list)) + and len(value) < param.nargs + ) + ) + + +def _start_of_option(ctx: Context, value: str) -> bool: + """Check if the value looks like the start of an option.""" + if not value: + return False + + c = value[0] + return c in ctx._opt_prefixes + + +def _is_incomplete_option(ctx: Context, args: list[str], param: Parameter) -> bool: + """Determine if the given parameter is an option that needs a value. + + :param args: List of complete args before the incomplete value. + :param param: Option object being checked. + """ + if not isinstance(param, Option): + return False + + if param.is_flag or param.count: + return False + + last_option = None + + for index, arg in enumerate(reversed(args)): + if index + 1 > param.nargs: + break + + if _start_of_option(ctx, arg): + last_option = arg + break + + return last_option is not None and last_option in param.opts + + +def _resolve_context( + cli: Command, + ctx_args: cabc.MutableMapping[str, t.Any], + prog_name: str, + args: list[str], +) -> Context: + """Produce the context hierarchy starting with the command and + traversing the complete arguments. This only follows the commands, + it doesn't trigger input prompts or callbacks. + + :param cli: Command being called. + :param prog_name: Name of the executable in the shell. + :param args: List of complete args before the incomplete value. + """ + ctx_args["resilient_parsing"] = True + with cli.make_context(prog_name, args.copy(), **ctx_args) as ctx: + args = ctx._protected_args + ctx.args + + while args: + command = ctx.command + + if isinstance(command, Group): + if not command.chain: + name, cmd, args = command.resolve_command(ctx, args) + + if cmd is None: + return ctx + + with cmd.make_context( + name, args, parent=ctx, resilient_parsing=True + ) as sub_ctx: + ctx = sub_ctx + args = ctx._protected_args + ctx.args + else: + sub_ctx = ctx + + while args: + name, cmd, args = command.resolve_command(ctx, args) + + if cmd is None: + return ctx + + with cmd.make_context( + name, + args, + parent=ctx, + allow_extra_args=True, + allow_interspersed_args=False, + resilient_parsing=True, + ) as sub_sub_ctx: + sub_ctx = sub_sub_ctx + args = sub_ctx.args + + ctx = sub_ctx + args = [*sub_ctx._protected_args, *sub_ctx.args] + else: + break + + return ctx + + +def _resolve_incomplete( + ctx: Context, args: list[str], incomplete: str +) -> tuple[Command | Parameter, str]: + """Find the Click object that will handle the completion of the + incomplete value. Return the object and the incomplete value. + + :param ctx: Invocation context for the command represented by + the parsed complete args. + :param args: List of complete args before the incomplete value. + :param incomplete: Value being completed. May be empty. + """ + # Different shells treat an "=" between a long option name and + # value differently. Might keep the value joined, return the "=" + # as a separate item, or return the split name and value. Always + # split and discard the "=" to make completion easier. + if incomplete == "=": + incomplete = "" + elif "=" in incomplete and _start_of_option(ctx, incomplete): + name, _, incomplete = incomplete.partition("=") + args.append(name) + + # The "--" marker tells Click to stop treating values as options + # even if they start with the option character. If it hasn't been + # given and the incomplete arg looks like an option, the current + # command will provide option name completions. + if "--" not in args and _start_of_option(ctx, incomplete): + return ctx.command, incomplete + + params = ctx.command.get_params(ctx) + + # If the last complete arg is an option name with an incomplete + # value, the option will provide value completions. + for param in params: + if _is_incomplete_option(ctx, args, param): + return param, incomplete + + # It's not an option name or value. The first argument without a + # parsed value will provide value completions. + for param in params: + if _is_incomplete_argument(ctx, param): + return param, incomplete + + # There were no unparsed arguments, the command may be a group that + # will provide command name completions. + return ctx.command, incomplete diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/click/termui.py b/bioagent-bench/.venv/lib/python3.14/site-packages/click/termui.py new file mode 100644 index 0000000000000000000000000000000000000000..48f671b2173cab3612fbe4d5c8a662cc953eb286 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/click/termui.py @@ -0,0 +1,891 @@ +from __future__ import annotations + +import collections.abc as cabc +import inspect +import io +import itertools +import sys +import typing as t +from contextlib import AbstractContextManager +from gettext import gettext as _ + +from ._compat import isatty +from ._compat import strip_ansi +from .exceptions import Abort +from .exceptions import UsageError +from .globals import resolve_color_default +from .types import Choice +from .types import convert_type +from .types import ParamType +from .utils import echo +from .utils import LazyFile + +if t.TYPE_CHECKING: + from ._termui_impl import ProgressBar + +V = t.TypeVar("V") + +# The prompt functions to use. The doc tools currently override these +# functions to customize how they work. +visible_prompt_func: t.Callable[[str], str] = input + +_ansi_colors = { + "black": 30, + "red": 31, + "green": 32, + "yellow": 33, + "blue": 34, + "magenta": 35, + "cyan": 36, + "white": 37, + "reset": 39, + "bright_black": 90, + "bright_red": 91, + "bright_green": 92, + "bright_yellow": 93, + "bright_blue": 94, + "bright_magenta": 95, + "bright_cyan": 96, + "bright_white": 97, +} +_ansi_reset_all = "\033[0m" + + +def hidden_prompt_func(prompt: str) -> str: + import getpass + + return getpass.getpass(prompt) + + +def _build_prompt( + text: str, + suffix: str, + show_default: bool | str = False, + default: t.Any | None = None, + show_choices: bool = True, + type: ParamType | None = None, +) -> str: + prompt = text + if type is not None and show_choices and isinstance(type, Choice): + prompt += f" ({', '.join(map(str, type.choices))})" + if isinstance(show_default, str): + default = f"({show_default})" + if default is not None and show_default: + prompt = f"{prompt} [{_format_default(default)}]" + return f"{prompt}{suffix}" + + +def _format_default(default: t.Any) -> t.Any: + if isinstance(default, (io.IOBase, LazyFile)) and hasattr(default, "name"): + return default.name + + return default + + +def prompt( + text: str, + default: t.Any | None = None, + hide_input: bool = False, + confirmation_prompt: bool | str = False, + type: ParamType | t.Any | None = None, + value_proc: t.Callable[[str], t.Any] | None = None, + prompt_suffix: str = ": ", + show_default: bool | str = True, + err: bool = False, + show_choices: bool = True, +) -> t.Any: + """Prompts a user for input. This is a convenience function that can + be used to prompt a user for input later. + + If the user aborts the input by sending an interrupt signal, this + function will catch it and raise a :exc:`Abort` exception. + + :param text: the text to show for the prompt. + :param default: the default value to use if no input happens. If this + is not given it will prompt until it's aborted. + :param hide_input: if this is set to true then the input value will + be hidden. + :param confirmation_prompt: Prompt a second time to confirm the + value. Can be set to a string instead of ``True`` to customize + the message. + :param type: the type to use to check the value against. + :param value_proc: if this parameter is provided it's a function that + is invoked instead of the type conversion to + convert a value. + :param prompt_suffix: a suffix that should be added to the prompt. + :param show_default: shows or hides the default value in the prompt. + If this value is a string, it shows that string + in parentheses instead of the actual value. + :param err: if set to true the file defaults to ``stderr`` instead of + ``stdout``, the same as with echo. + :param show_choices: Show or hide choices if the passed type is a Choice. + For example if type is a Choice of either day or week, + show_choices is true and text is "Group by" then the + prompt will be "Group by (day, week): ". + + .. versionchanged:: 8.3.3 + ``show_default`` can be a string to show a custom value instead + of the actual default, matching the help text behavior. + + .. versionchanged:: 8.3.1 + A space is no longer appended to the prompt. + + .. versionadded:: 8.0 + ``confirmation_prompt`` can be a custom string. + + .. versionadded:: 7.0 + Added the ``show_choices`` parameter. + + .. versionadded:: 6.0 + Added unicode support for cmd.exe on Windows. + + .. versionadded:: 4.0 + Added the `err` parameter. + + """ + + def prompt_func(text: str) -> str: + f = hidden_prompt_func if hide_input else visible_prompt_func + try: + # Write the prompt separately so that we get nice + # coloring through colorama on Windows + echo(text[:-1], nl=False, err=err) + # Echo the last character to stdout to work around an issue where + # readline causes backspace to clear the whole line. + return f(text[-1:]) + except (KeyboardInterrupt, EOFError): + # getpass doesn't print a newline if the user aborts input with ^C. + # Allegedly this behavior is inherited from getpass(3). + # A doc bug has been filed at https://bugs.python.org/issue24711 + if hide_input: + echo(None, err=err) + raise Abort() from None + + if value_proc is None: + value_proc = convert_type(type, default) + + prompt = _build_prompt( + text, prompt_suffix, show_default, default, show_choices, type + ) + + if confirmation_prompt: + if confirmation_prompt is True: + confirmation_prompt = _("Repeat for confirmation") + + confirmation_prompt = _build_prompt(confirmation_prompt, prompt_suffix) + + while True: + while True: + value = prompt_func(prompt) + if value: + break + elif default is not None: + value = default + break + try: + result = value_proc(value) + except UsageError as e: + if hide_input: + echo(_("Error: The value you entered was invalid."), err=err) + else: + echo(_("Error: {e.message}").format(e=e), err=err) + continue + if not confirmation_prompt: + return result + while True: + value2 = prompt_func(confirmation_prompt) + is_empty = not value and not value2 + if value2 or is_empty: + break + if value == value2: + return result + echo(_("Error: The two entered values do not match."), err=err) + + +def confirm( + text: str, + default: bool | None = False, + abort: bool = False, + prompt_suffix: str = ": ", + show_default: bool = True, + err: bool = False, +) -> bool: + """Prompts for confirmation (yes/no question). + + If the user aborts the input by sending a interrupt signal this + function will catch it and raise a :exc:`Abort` exception. + + :param text: the question to ask. + :param default: The default value to use when no input is given. If + ``None``, repeat until input is given. + :param abort: if this is set to `True` a negative answer aborts the + exception by raising :exc:`Abort`. + :param prompt_suffix: a suffix that should be added to the prompt. + :param show_default: shows or hides the default value in the prompt. + :param err: if set to true the file defaults to ``stderr`` instead of + ``stdout``, the same as with echo. + + .. versionchanged:: 8.3.1 + A space is no longer appended to the prompt. + + .. versionchanged:: 8.0 + Repeat until input is given if ``default`` is ``None``. + + .. versionadded:: 4.0 + Added the ``err`` parameter. + """ + prompt = _build_prompt( + text, + prompt_suffix, + show_default, + "y/n" if default is None else ("Y/n" if default else "y/N"), + ) + + while True: + try: + # Write the prompt separately so that we get nice + # coloring through colorama on Windows + echo(prompt[:-1], nl=False, err=err) + # Echo the last character to stdout to work around an issue where + # readline causes backspace to clear the whole line. + value = visible_prompt_func(prompt[-1:]).lower().strip() + except (KeyboardInterrupt, EOFError): + raise Abort() from None + if value in ("y", "yes"): + rv = True + elif value in ("n", "no"): + rv = False + elif default is not None and value == "": + rv = default + else: + echo(_("Error: invalid input"), err=err) + continue + break + if abort and not rv: + raise Abort() + return rv + + +def echo_via_pager( + text_or_generator: cabc.Iterable[str] | t.Callable[[], cabc.Iterable[str]] | str, + color: bool | None = None, +) -> None: + """This function takes a text and shows it via an environment specific + pager on stdout. + + .. versionchanged:: 3.0 + Added the `color` flag. + + :param text_or_generator: the text to page, or alternatively, a + generator emitting the text to page. + :param color: controls if the pager supports ANSI colors or not. The + default is autodetection. + """ + color = resolve_color_default(color) + + if inspect.isgeneratorfunction(text_or_generator): + i = t.cast("t.Callable[[], cabc.Iterable[str]]", text_or_generator)() + elif isinstance(text_or_generator, str): + i = [text_or_generator] + else: + i = iter(t.cast("cabc.Iterable[str]", text_or_generator)) + + # convert every element of i to a text type if necessary + text_generator = (el if isinstance(el, str) else str(el) for el in i) + + from ._termui_impl import pager + + return pager(itertools.chain(text_generator, "\n"), color) + + +@t.overload +def progressbar( + *, + length: int, + label: str | None = None, + hidden: bool = False, + show_eta: bool = True, + show_percent: bool | None = None, + show_pos: bool = False, + fill_char: str = "#", + empty_char: str = "-", + bar_template: str = "%(label)s [%(bar)s] %(info)s", + info_sep: str = " ", + width: int = 36, + file: t.TextIO | None = None, + color: bool | None = None, + update_min_steps: int = 1, +) -> ProgressBar[int]: ... + + +@t.overload +def progressbar( + iterable: cabc.Iterable[V] | None = None, + length: int | None = None, + label: str | None = None, + hidden: bool = False, + show_eta: bool = True, + show_percent: bool | None = None, + show_pos: bool = False, + item_show_func: t.Callable[[V | None], str | None] | None = None, + fill_char: str = "#", + empty_char: str = "-", + bar_template: str = "%(label)s [%(bar)s] %(info)s", + info_sep: str = " ", + width: int = 36, + file: t.TextIO | None = None, + color: bool | None = None, + update_min_steps: int = 1, +) -> ProgressBar[V]: ... + + +def progressbar( + iterable: cabc.Iterable[V] | None = None, + length: int | None = None, + label: str | None = None, + hidden: bool = False, + show_eta: bool = True, + show_percent: bool | None = None, + show_pos: bool = False, + item_show_func: t.Callable[[V | None], str | None] | None = None, + fill_char: str = "#", + empty_char: str = "-", + bar_template: str = "%(label)s [%(bar)s] %(info)s", + info_sep: str = " ", + width: int = 36, + file: t.TextIO | None = None, + color: bool | None = None, + update_min_steps: int = 1, +) -> ProgressBar[V]: + """This function creates an iterable context manager that can be used + to iterate over something while showing a progress bar. It will + either iterate over the `iterable` or `length` items (that are counted + up). While iteration happens, this function will print a rendered + progress bar to the given `file` (defaults to stdout) and will attempt + to calculate remaining time and more. By default, this progress bar + will not be rendered if the file is not a terminal. + + The context manager creates the progress bar. When the context + manager is entered the progress bar is already created. With every + iteration over the progress bar, the iterable passed to the bar is + advanced and the bar is updated. When the context manager exits, + a newline is printed and the progress bar is finalized on screen. + + Note: The progress bar is currently designed for use cases where the + total progress can be expected to take at least several seconds. + Because of this, the ProgressBar class object won't display + progress that is considered too fast, and progress where the time + between steps is less than a second. + + No printing must happen or the progress bar will be unintentionally + destroyed. + + Example usage:: + + with progressbar(items) as bar: + for item in bar: + do_something_with(item) + + Alternatively, if no iterable is specified, one can manually update the + progress bar through the `update()` method instead of directly + iterating over the progress bar. The update method accepts the number + of steps to increment the bar with:: + + with progressbar(length=chunks.total_bytes) as bar: + for chunk in chunks: + process_chunk(chunk) + bar.update(chunks.bytes) + + The ``update()`` method also takes an optional value specifying the + ``current_item`` at the new position. This is useful when used + together with ``item_show_func`` to customize the output for each + manual step:: + + with click.progressbar( + length=total_size, + label='Unzipping archive', + item_show_func=lambda a: a.filename + ) as bar: + for archive in zip_file: + archive.extract() + bar.update(archive.size, archive) + + :param iterable: an iterable to iterate over. If not provided the length + is required. + :param length: the number of items to iterate over. By default the + progressbar will attempt to ask the iterator about its + length, which might or might not work. If an iterable is + also provided this parameter can be used to override the + length. If an iterable is not provided the progress bar + will iterate over a range of that length. + :param label: the label to show next to the progress bar. + :param hidden: hide the progressbar. Defaults to ``False``. When no tty is + detected, it will only print the progressbar label. Setting this to + ``False`` also disables that. + :param show_eta: enables or disables the estimated time display. This is + automatically disabled if the length cannot be + determined. + :param show_percent: enables or disables the percentage display. The + default is `True` if the iterable has a length or + `False` if not. + :param show_pos: enables or disables the absolute position display. The + default is `False`. + :param item_show_func: A function called with the current item which + can return a string to show next to the progress bar. If the + function returns ``None`` nothing is shown. The current item can + be ``None``, such as when entering and exiting the bar. + :param fill_char: the character to use to show the filled part of the + progress bar. + :param empty_char: the character to use to show the non-filled part of + the progress bar. + :param bar_template: the format string to use as template for the bar. + The parameters in it are ``label`` for the label, + ``bar`` for the progress bar and ``info`` for the + info section. + :param info_sep: the separator between multiple info items (eta etc.) + :param width: the width of the progress bar in characters, 0 means full + terminal width + :param file: The file to write to. If this is not a terminal then + only the label is printed. + :param color: controls if the terminal supports ANSI colors or not. The + default is autodetection. This is only needed if ANSI + codes are included anywhere in the progress bar output + which is not the case by default. + :param update_min_steps: Render only when this many updates have + completed. This allows tuning for very fast iterators. + + .. versionadded:: 8.2 + The ``hidden`` argument. + + .. versionchanged:: 8.0 + Output is shown even if execution time is less than 0.5 seconds. + + .. versionchanged:: 8.0 + ``item_show_func`` shows the current item, not the previous one. + + .. versionchanged:: 8.0 + Labels are echoed if the output is not a TTY. Reverts a change + in 7.0 that removed all output. + + .. versionadded:: 8.0 + The ``update_min_steps`` parameter. + + .. versionadded:: 4.0 + The ``color`` parameter and ``update`` method. + + .. versionadded:: 2.0 + """ + from ._termui_impl import ProgressBar + + color = resolve_color_default(color) + return ProgressBar( + iterable=iterable, + length=length, + hidden=hidden, + show_eta=show_eta, + show_percent=show_percent, + show_pos=show_pos, + item_show_func=item_show_func, + fill_char=fill_char, + empty_char=empty_char, + bar_template=bar_template, + info_sep=info_sep, + file=file, + label=label, + width=width, + color=color, + update_min_steps=update_min_steps, + ) + + +def clear() -> None: + """Clears the terminal screen. This will have the effect of clearing + the whole visible space of the terminal and moving the cursor to the + top left. This does not do anything if not connected to a terminal. + + .. versionadded:: 2.0 + """ + if not isatty(sys.stdout): + return + + # ANSI escape \033[2J clears the screen, \033[1;1H moves the cursor + echo("\033[2J\033[1;1H", nl=False) + + +def _interpret_color(color: int | tuple[int, int, int] | str, offset: int = 0) -> str: + if isinstance(color, int): + return f"{38 + offset};5;{color:d}" + + if isinstance(color, (tuple, list)): + r, g, b = color + return f"{38 + offset};2;{r:d};{g:d};{b:d}" + + return str(_ansi_colors[color] + offset) + + +def style( + text: t.Any, + fg: int | tuple[int, int, int] | str | None = None, + bg: int | tuple[int, int, int] | str | None = None, + bold: bool | None = None, + dim: bool | None = None, + underline: bool | None = None, + overline: bool | None = None, + italic: bool | None = None, + blink: bool | None = None, + reverse: bool | None = None, + strikethrough: bool | None = None, + reset: bool = True, +) -> str: + """Styles a text with ANSI styles and returns the new string. By + default the styling is self contained which means that at the end + of the string a reset code is issued. This can be prevented by + passing ``reset=False``. + + Examples:: + + click.echo(click.style('Hello World!', fg='green')) + click.echo(click.style('ATTENTION!', blink=True)) + click.echo(click.style('Some things', reverse=True, fg='cyan')) + click.echo(click.style('More colors', fg=(255, 12, 128), bg=117)) + + Supported color names: + + * ``black`` (might be a gray) + * ``red`` + * ``green`` + * ``yellow`` (might be an orange) + * ``blue`` + * ``magenta`` + * ``cyan`` + * ``white`` (might be light gray) + * ``bright_black`` + * ``bright_red`` + * ``bright_green`` + * ``bright_yellow`` + * ``bright_blue`` + * ``bright_magenta`` + * ``bright_cyan`` + * ``bright_white`` + * ``reset`` (reset the color code only) + + If the terminal supports it, color may also be specified as: + + - An integer in the interval [0, 255]. The terminal must support + 8-bit/256-color mode. + - An RGB tuple of three integers in [0, 255]. The terminal must + support 24-bit/true-color mode. + + See https://en.wikipedia.org/wiki/ANSI_color and + https://gist.github.com/XVilka/8346728 for more information. + + :param text: the string to style with ansi codes. + :param fg: if provided this will become the foreground color. + :param bg: if provided this will become the background color. + :param bold: if provided this will enable or disable bold mode. + :param dim: if provided this will enable or disable dim mode. This is + badly supported. + :param underline: if provided this will enable or disable underline. + :param overline: if provided this will enable or disable overline. + :param italic: if provided this will enable or disable italic. + :param blink: if provided this will enable or disable blinking. + :param reverse: if provided this will enable or disable inverse + rendering (foreground becomes background and the + other way round). + :param strikethrough: if provided this will enable or disable + striking through text. + :param reset: by default a reset-all code is added at the end of the + string which means that styles do not carry over. This + can be disabled to compose styles. + + .. versionchanged:: 8.0 + A non-string ``message`` is converted to a string. + + .. versionchanged:: 8.0 + Added support for 256 and RGB color codes. + + .. versionchanged:: 8.0 + Added the ``strikethrough``, ``italic``, and ``overline`` + parameters. + + .. versionchanged:: 7.0 + Added support for bright colors. + + .. versionadded:: 2.0 + """ + if not isinstance(text, str): + text = str(text) + + bits = [] + + if fg: + try: + bits.append(f"\033[{_interpret_color(fg)}m") + except KeyError: + raise TypeError(f"Unknown color {fg!r}") from None + + if bg: + try: + bits.append(f"\033[{_interpret_color(bg, 10)}m") + except KeyError: + raise TypeError(f"Unknown color {bg!r}") from None + + if bold is not None: + bits.append(f"\033[{1 if bold else 22}m") + if dim is not None: + bits.append(f"\033[{2 if dim else 22}m") + if underline is not None: + bits.append(f"\033[{4 if underline else 24}m") + if overline is not None: + bits.append(f"\033[{53 if overline else 55}m") + if italic is not None: + bits.append(f"\033[{3 if italic else 23}m") + if blink is not None: + bits.append(f"\033[{5 if blink else 25}m") + if reverse is not None: + bits.append(f"\033[{7 if reverse else 27}m") + if strikethrough is not None: + bits.append(f"\033[{9 if strikethrough else 29}m") + bits.append(text) + if reset: + bits.append(_ansi_reset_all) + return "".join(bits) + + +def unstyle(text: str) -> str: + """Removes ANSI styling information from a string. Usually it's not + necessary to use this function as Click's echo function will + automatically remove styling if necessary. + + .. versionadded:: 2.0 + + :param text: the text to remove style information from. + """ + return strip_ansi(text) + + +def secho( + message: t.Any | None = None, + file: t.IO[t.AnyStr] | None = None, + nl: bool = True, + err: bool = False, + color: bool | None = None, + **styles: t.Any, +) -> None: + """This function combines :func:`echo` and :func:`style` into one + call. As such the following two calls are the same:: + + click.secho('Hello World!', fg='green') + click.echo(click.style('Hello World!', fg='green')) + + All keyword arguments are forwarded to the underlying functions + depending on which one they go with. + + Non-string types will be converted to :class:`str`. However, + :class:`bytes` are passed directly to :meth:`echo` without applying + style. If you want to style bytes that represent text, call + :meth:`bytes.decode` first. + + .. versionchanged:: 8.0 + A non-string ``message`` is converted to a string. Bytes are + passed through without style applied. + + .. versionadded:: 2.0 + """ + if message is not None and not isinstance(message, (bytes, bytearray)): + message = style(message, **styles) + + return echo(message, file=file, nl=nl, err=err, color=color) + + +@t.overload +def edit( + text: bytes | bytearray, + editor: str | None = None, + env: cabc.Mapping[str, str] | None = None, + require_save: bool = False, + extension: str = ".txt", +) -> bytes | None: ... + + +@t.overload +def edit( + text: str, + editor: str | None = None, + env: cabc.Mapping[str, str] | None = None, + require_save: bool = True, + extension: str = ".txt", +) -> str | None: ... + + +@t.overload +def edit( + text: None = None, + editor: str | None = None, + env: cabc.Mapping[str, str] | None = None, + require_save: bool = True, + extension: str = ".txt", + filename: str | cabc.Iterable[str] | None = None, +) -> None: ... + + +def edit( + text: str | bytes | bytearray | None = None, + editor: str | None = None, + env: cabc.Mapping[str, str] | None = None, + require_save: bool = True, + extension: str = ".txt", + filename: str | cabc.Iterable[str] | None = None, +) -> str | bytes | bytearray | None: + r"""Edits the given text in the defined editor. If an editor is given + (should be the full path to the executable but the regular operating + system search path is used for finding the executable) it overrides + the detected editor. Optionally, some environment variables can be + used. If the editor is closed without changes, `None` is returned. In + case a file is edited directly the return value is always `None` and + `require_save` and `extension` are ignored. + + If the editor cannot be opened a :exc:`UsageError` is raised. + + Note for Windows: to simplify cross-platform usage, the newlines are + automatically converted from POSIX to Windows and vice versa. As such, + the message here will have ``\n`` as newline markers. + + :param text: the text to edit. + :param editor: optionally the editor to use. Defaults to automatic + detection. + :param env: environment variables to forward to the editor. + :param require_save: if this is true, then not saving in the editor + will make the return value become `None`. + :param extension: the extension to tell the editor about. This defaults + to `.txt` but changing this might change syntax + highlighting. + :param filename: if provided it will edit this file instead of the + provided text contents. It will not use a temporary + file as an indirection in that case. If the editor supports + editing multiple files at once, a sequence of files may be + passed as well. Invoke `click.file` once per file instead + if multiple files cannot be managed at once or editing the + files serially is desired. + + .. versionchanged:: 8.2.0 + ``filename`` now accepts any ``Iterable[str]`` in addition to a ``str`` + if the ``editor`` supports editing multiple files at once. + + """ + from ._termui_impl import Editor + + ed = Editor(editor=editor, env=env, require_save=require_save, extension=extension) + + if filename is None: + return ed.edit(text) + + if isinstance(filename, str): + filename = (filename,) + + ed.edit_files(filenames=filename) + return None + + +def launch(url: str, wait: bool = False, locate: bool = False) -> int: + """This function launches the given URL (or filename) in the default + viewer application for this file type. If this is an executable, it + might launch the executable in a new session. The return value is + the exit code of the launched application. Usually, ``0`` indicates + success. + + Examples:: + + click.launch('https://click.palletsprojects.com/') + click.launch('/my/downloaded/file', locate=True) + + .. versionadded:: 2.0 + + :param url: URL or filename of the thing to launch. + :param wait: Wait for the program to exit before returning. This + only works if the launched program blocks. In particular, + ``xdg-open`` on Linux does not block. + :param locate: if this is set to `True` then instead of launching the + application associated with the URL it will attempt to + launch a file manager with the file located. This + might have weird effects if the URL does not point to + the filesystem. + """ + from ._termui_impl import open_url + + return open_url(url, wait=wait, locate=locate) + + +# If this is provided, getchar() calls into this instead. This is used +# for unittesting purposes. +_getchar: t.Callable[[bool], str] | None = None + + +def getchar(echo: bool = False) -> str: + """Fetches a single character from the terminal and returns it. This + will always return a unicode character and under certain rare + circumstances this might return more than one character. The + situations which more than one character is returned is when for + whatever reason multiple characters end up in the terminal buffer or + standard input was not actually a terminal. + + Note that this will always read from the terminal, even if something + is piped into the standard input. + + Note for Windows: in rare cases when typing non-ASCII characters, this + function might wait for a second character and then return both at once. + This is because certain Unicode characters look like special-key markers. + + .. versionadded:: 2.0 + + :param echo: if set to `True`, the character read will also show up on + the terminal. The default is to not show it. + """ + global _getchar + + if _getchar is None: + from ._termui_impl import getchar as f + + _getchar = f + + return _getchar(echo) + + +def raw_terminal() -> AbstractContextManager[int]: + from ._termui_impl import raw_terminal as f + + return f() + + +def pause(info: str | None = None, err: bool = False) -> None: + """This command stops execution and waits for the user to press any + key to continue. This is similar to the Windows batch "pause" + command. If the program is not run through a terminal, this command + will instead do nothing. + + .. versionadded:: 2.0 + + .. versionadded:: 4.0 + Added the `err` parameter. + + :param info: The message to print before pausing. Defaults to + ``"Press any key to continue..."``. + :param err: if set to message goes to ``stderr`` instead of + ``stdout``, the same as with echo. + """ + if not isatty(sys.stdin) or not isatty(sys.stdout): + return + + if info is None: + info = _("Press any key to continue...") + + try: + if info: + echo(info, nl=False, err=err) + try: + getchar() + except (KeyboardInterrupt, EOFError): + pass + finally: + if info: + echo(err=err) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/click/testing.py b/bioagent-bench/.venv/lib/python3.14/site-packages/click/testing.py new file mode 100644 index 0000000000000000000000000000000000000000..04e7f1d92569e89595e1776afd4426919ae01fda --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/click/testing.py @@ -0,0 +1,669 @@ +from __future__ import annotations + +import collections.abc as cabc +import contextlib +import io +import os +import pdb +import shlex +import sys +import tempfile +import typing as t +from types import TracebackType + +from . import _compat +from . import formatting +from . import termui +from . import utils +from ._compat import _find_binary_reader + +if t.TYPE_CHECKING: + from _typeshed import ReadableBuffer + + from .core import Command + + +class EchoingStdin: + def __init__(self, input: t.BinaryIO, output: t.BinaryIO) -> None: + self._input = input + self._output = output + self._paused = False + + def __getattr__(self, x: str) -> t.Any: + return getattr(self._input, x) + + def _echo(self, rv: bytes) -> bytes: + if not self._paused: + self._output.write(rv) + + return rv + + def read(self, n: int = -1) -> bytes: + return self._echo(self._input.read(n)) + + def read1(self, n: int = -1) -> bytes: + return self._echo(self._input.read1(n)) # type: ignore + + def readline(self, n: int = -1) -> bytes: + return self._echo(self._input.readline(n)) + + def readlines(self) -> list[bytes]: + return [self._echo(x) for x in self._input.readlines()] + + def __iter__(self) -> cabc.Iterator[bytes]: + return iter(self._echo(x) for x in self._input) + + def __repr__(self) -> str: + return repr(self._input) + + +@contextlib.contextmanager +def _pause_echo(stream: EchoingStdin | None) -> cabc.Iterator[None]: + if stream is None: + yield + else: + stream._paused = True + yield + stream._paused = False + + +class BytesIOCopy(io.BytesIO): + """Patch ``io.BytesIO`` to let the written stream be copied to another. + + .. versionadded:: 8.2 + """ + + def __init__(self, copy_to: io.BytesIO) -> None: + super().__init__() + self.copy_to = copy_to + + def flush(self) -> None: + super().flush() + self.copy_to.flush() + + def write(self, b: ReadableBuffer) -> int: + self.copy_to.write(b) + return super().write(b) + + +class StreamMixer: + """Mixes `` and `` streams. + + The result is available in the ``output`` attribute. + + .. versionadded:: 8.2 + """ + + def __init__(self) -> None: + self.output: io.BytesIO = io.BytesIO() + self.stdout: io.BytesIO = BytesIOCopy(copy_to=self.output) + self.stderr: io.BytesIO = BytesIOCopy(copy_to=self.output) + + +class _NamedTextIOWrapper(io.TextIOWrapper): + """A :class:`~io.TextIOWrapper` with custom ``name`` and ``mode`` + that does not close its underlying buffer. + + An optional ``original_fd`` preserves the file descriptor of the + stream being replaced, so that C-level consumers that call + :meth:`fileno` (``faulthandler``, ``subprocess``, ...) still work. + Inspired by pytest's ``capsys``/``capfd`` split: see :doc:`/testing` + for details. + + .. versionchanged:: 8.3.3 + Added ``original_fd`` parameter and :meth:`fileno` override. + """ + + def __init__( + self, + buffer: t.BinaryIO, + name: str, + mode: str, + *, + original_fd: int = -1, + **kwargs: t.Any, + ) -> None: + super().__init__(buffer, **kwargs) + self._name = name + self._mode = mode + self._original_fd = original_fd + + def close(self) -> None: + """The buffer this object contains belongs to some other object, + so prevent the default ``__del__`` implementation from closing + that buffer. + + .. versionadded:: 8.3.2 + """ + + def fileno(self) -> int: + """Return the file descriptor of the original stream, if one was + provided at construction time. + + This allows C-level consumers (``faulthandler``, ``subprocess``, + signal handlers, ...) to obtain a valid fd without crashing, even + though the Python-level writes are redirected to an in-memory + buffer. + + .. versionadded:: 8.3.3 + """ + if self._original_fd >= 0: + return self._original_fd + return super().fileno() + + @property + def name(self) -> str: + return self._name + + @property + def mode(self) -> str: + return self._mode + + +def make_input_stream( + input: str | bytes | t.IO[t.Any] | None, charset: str +) -> t.BinaryIO: + # Is already an input stream. + if hasattr(input, "read"): + rv = _find_binary_reader(t.cast("t.IO[t.Any]", input)) + + if rv is not None: + return rv + + raise TypeError("Could not find binary reader for input stream.") + + if input is None: + input = b"" + elif isinstance(input, str): + input = input.encode(charset) + + return io.BytesIO(input) + + +class Result: + """Holds the captured result of an invoked CLI script. + + :param runner: The runner that created the result + :param stdout_bytes: The standard output as bytes. + :param stderr_bytes: The standard error as bytes. + :param output_bytes: A mix of ``stdout_bytes`` and ``stderr_bytes``, as the + user would see it in its terminal. + :param return_value: The value returned from the invoked command. + :param exit_code: The exit code as integer. + :param exception: The exception that happened if one did. + :param exc_info: Exception information (exception type, exception instance, + traceback type). + + .. versionchanged:: 8.2 + ``stderr_bytes`` no longer optional, ``output_bytes`` introduced and + ``mix_stderr`` has been removed. + + .. versionadded:: 8.0 + Added ``return_value``. + """ + + def __init__( + self, + runner: CliRunner, + stdout_bytes: bytes, + stderr_bytes: bytes, + output_bytes: bytes, + return_value: t.Any, + exit_code: int, + exception: BaseException | None, + exc_info: tuple[type[BaseException], BaseException, TracebackType] + | None = None, + ): + self.runner = runner + self.stdout_bytes = stdout_bytes + self.stderr_bytes = stderr_bytes + self.output_bytes = output_bytes + self.return_value = return_value + self.exit_code = exit_code + self.exception = exception + self.exc_info = exc_info + + @property + def output(self) -> str: + """The terminal output as unicode string, as the user would see it. + + .. versionchanged:: 8.2 + No longer a proxy for ``self.stdout``. Now has its own independent stream + that is mixing `` and ``, in the order they were written. + """ + return self.output_bytes.decode(self.runner.charset, "replace").replace( + "\r\n", "\n" + ) + + @property + def stdout(self) -> str: + """The standard output as unicode string.""" + return self.stdout_bytes.decode(self.runner.charset, "replace").replace( + "\r\n", "\n" + ) + + @property + def stderr(self) -> str: + """The standard error as unicode string. + + .. versionchanged:: 8.2 + No longer raise an exception, always returns the `` string. + """ + return self.stderr_bytes.decode(self.runner.charset, "replace").replace( + "\r\n", "\n" + ) + + def __repr__(self) -> str: + exc_str = repr(self.exception) if self.exception else "okay" + return f"<{type(self).__name__} {exc_str}>" + + +class CliRunner: + """The CLI runner provides functionality to invoke a Click command line + script for unittesting purposes in a isolated environment. This only + works in single-threaded systems without any concurrency as it changes the + global interpreter state. + + :param charset: the character set for the input and output data. + :param env: a dictionary with environment variables for overriding. + :param echo_stdin: if this is set to `True`, then reading from `` writes + to ``. This is useful for showing examples in + some circumstances. Note that regular prompts + will automatically echo the input. + :param catch_exceptions: Whether to catch any exceptions other than + ``SystemExit`` when running :meth:`~CliRunner.invoke`. + + .. versionchanged:: 8.2 + Added the ``catch_exceptions`` parameter. + + .. versionchanged:: 8.2 + ``mix_stderr`` parameter has been removed. + """ + + def __init__( + self, + charset: str = "utf-8", + env: cabc.Mapping[str, str | None] | None = None, + echo_stdin: bool = False, + catch_exceptions: bool = True, + ) -> None: + self.charset = charset + self.env: cabc.Mapping[str, str | None] = env or {} + self.echo_stdin = echo_stdin + self.catch_exceptions = catch_exceptions + + def get_default_prog_name(self, cli: Command) -> str: + """Given a command object it will return the default program name + for it. The default is the `name` attribute or ``"root"`` if not + set. + """ + return cli.name or "root" + + def make_env( + self, overrides: cabc.Mapping[str, str | None] | None = None + ) -> cabc.Mapping[str, str | None]: + """Returns the environment overrides for invoking a script.""" + rv = dict(self.env) + if overrides: + rv.update(overrides) + return rv + + @contextlib.contextmanager + def isolation( + self, + input: str | bytes | t.IO[t.Any] | None = None, + env: cabc.Mapping[str, str | None] | None = None, + color: bool = False, + ) -> cabc.Iterator[tuple[io.BytesIO, io.BytesIO, io.BytesIO]]: + """A context manager that sets up the isolation for invoking of a + command line tool. This sets up `` with the given input data + and `os.environ` with the overrides from the given dictionary. + This also rebinds some internals in Click to be mocked (like the + prompt functionality). + + This is automatically done in the :meth:`invoke` method. + + :param input: the input stream to put into `sys.stdin`. + :param env: the environment overrides as dictionary. + :param color: whether the output should contain color codes. The + application can still override this explicitly. + + .. versionadded:: 8.2 + An additional output stream is returned, which is a mix of + `` and `` streams. + + .. versionchanged:: 8.2 + Always returns the `` stream. + + .. versionchanged:: 8.0 + `` is opened with ``errors="backslashreplace"`` + instead of the default ``"strict"``. + + .. versionchanged:: 4.0 + Added the ``color`` parameter. + """ + bytes_input = make_input_stream(input, self.charset) + echo_input = None + + old_stdin = sys.stdin + old_stdout = sys.stdout + old_stderr = sys.stderr + old_forced_width = formatting.FORCED_WIDTH + formatting.FORCED_WIDTH = 80 + + env = self.make_env(env) + + stream_mixer = StreamMixer() + + # Preserve the original file descriptors so that C-level + # consumers (faulthandler, subprocess, etc.) can still obtain a + # valid fd from the redirected streams. The original streams + # may themselves lack a fileno() (e.g. when CliRunner is used + # inside pytest's capsys), so we fall back to -1. + def _safe_fileno(stream: t.IO[t.Any]) -> int: + try: + return stream.fileno() + except (AttributeError, io.UnsupportedOperation): + return -1 + + old_stdout_fd = _safe_fileno(old_stdout) + old_stderr_fd = _safe_fileno(old_stderr) + + if self.echo_stdin: + bytes_input = echo_input = t.cast( + t.BinaryIO, EchoingStdin(bytes_input, stream_mixer.stdout) + ) + + sys.stdin = text_input = _NamedTextIOWrapper( + bytes_input, encoding=self.charset, name="", mode="r" + ) + + if self.echo_stdin: + # Force unbuffered reads, otherwise TextIOWrapper reads a + # large chunk which is echoed early. + text_input._CHUNK_SIZE = 1 # type: ignore + + sys.stdout = _NamedTextIOWrapper( + stream_mixer.stdout, + encoding=self.charset, + name="", + mode="w", + original_fd=old_stdout_fd, + ) + + sys.stderr = _NamedTextIOWrapper( + stream_mixer.stderr, + encoding=self.charset, + name="", + mode="w", + errors="backslashreplace", + original_fd=old_stderr_fd, + ) + + @_pause_echo(echo_input) # type: ignore + def visible_input(prompt: str | None = None) -> str: + sys.stdout.write(prompt or "") + try: + val = next(text_input).rstrip("\r\n") + except StopIteration as e: + raise EOFError() from e + sys.stdout.write(f"{val}\n") + sys.stdout.flush() + return val + + @_pause_echo(echo_input) # type: ignore + def hidden_input(prompt: str | None = None) -> str: + sys.stdout.write(f"{prompt or ''}\n") + sys.stdout.flush() + try: + return next(text_input).rstrip("\r\n") + except StopIteration as e: + raise EOFError() from e + + @_pause_echo(echo_input) # type: ignore + def _getchar(echo: bool) -> str: + char = sys.stdin.read(1) + + if echo: + sys.stdout.write(char) + + sys.stdout.flush() + return char + + default_color = color + + def should_strip_ansi( + stream: t.IO[t.Any] | None = None, color: bool | None = None + ) -> bool: + if color is None: + return not default_color + return not color + + old_visible_prompt_func = termui.visible_prompt_func + old_hidden_prompt_func = termui.hidden_prompt_func + old__getchar_func = termui._getchar + old_should_strip_ansi = utils.should_strip_ansi # type: ignore + old__compat_should_strip_ansi = _compat.should_strip_ansi + old_pdb_init = pdb.Pdb.__init__ + termui.visible_prompt_func = visible_input + termui.hidden_prompt_func = hidden_input + termui._getchar = _getchar + utils.should_strip_ansi = should_strip_ansi # type: ignore + _compat.should_strip_ansi = should_strip_ansi + + def _patched_pdb_init( + self: pdb.Pdb, + completekey: str = "tab", + stdin: t.IO[str] | None = None, + stdout: t.IO[str] | None = None, + **kwargs: t.Any, + ) -> None: + """Default ``pdb.Pdb`` to real terminal streams during + ``CliRunner`` isolation. + + Without this patch, ``pdb.Pdb.__init__`` inherits from + ``cmd.Cmd`` which falls back to ``sys.stdin``/``sys.stdout`` + when no explicit streams are provided. During isolation + those are ``BytesIO``-backed wrappers, so the debugger + reads from an empty buffer and writes to captured output, + making interactive debugging impossible. + + By defaulting to ``sys.__stdin__``/``sys.__stdout__`` (the + original terminal streams Python preserves regardless of + redirection), debuggers can interact with the user while + ``click.echo`` output is still captured normally. + + This covers ``pdb.set_trace()``, ``breakpoint()``, + ``pdb.post_mortem()``, and debuggers that subclass + ``pdb.Pdb`` (ipdb, pdbpp). Explicit ``stdin``/``stdout`` + arguments are honored and not overridden. Debuggers that + do not subclass ``pdb.Pdb`` (pudb, debugpy) are not + covered. + """ + if stdin is None: + stdin = sys.__stdin__ + if stdout is None: + stdout = sys.__stdout__ + old_pdb_init( + self, completekey=completekey, stdin=stdin, stdout=stdout, **kwargs + ) + + pdb.Pdb.__init__ = _patched_pdb_init # type: ignore[assignment] + + old_env = {} + try: + for key, value in env.items(): + old_env[key] = os.environ.get(key) + if value is None: + try: + del os.environ[key] + except Exception: + pass + else: + os.environ[key] = value + yield (stream_mixer.stdout, stream_mixer.stderr, stream_mixer.output) + finally: + for key, value in old_env.items(): + if value is None: + try: + del os.environ[key] + except Exception: + pass + else: + os.environ[key] = value + sys.stdout = old_stdout + sys.stderr = old_stderr + sys.stdin = old_stdin + termui.visible_prompt_func = old_visible_prompt_func + termui.hidden_prompt_func = old_hidden_prompt_func + termui._getchar = old__getchar_func + utils.should_strip_ansi = old_should_strip_ansi # type: ignore + _compat.should_strip_ansi = old__compat_should_strip_ansi + formatting.FORCED_WIDTH = old_forced_width + pdb.Pdb.__init__ = old_pdb_init # type: ignore[method-assign] + + def invoke( + self, + cli: Command, + args: str | cabc.Sequence[str] | None = None, + input: str | bytes | t.IO[t.Any] | None = None, + env: cabc.Mapping[str, str | None] | None = None, + catch_exceptions: bool | None = None, + color: bool = False, + **extra: t.Any, + ) -> Result: + """Invokes a command in an isolated environment. The arguments are + forwarded directly to the command line script, the `extra` keyword + arguments are passed to the :meth:`~clickpkg.Command.main` function of + the command. + + This returns a :class:`Result` object. + + :param cli: the command to invoke + :param args: the arguments to invoke. It may be given as an iterable + or a string. When given as string it will be interpreted + as a Unix shell command. More details at + :func:`shlex.split`. + :param input: the input data for `sys.stdin`. + :param env: the environment overrides. + :param catch_exceptions: Whether to catch any other exceptions than + ``SystemExit``. If :data:`None`, the value + from :class:`CliRunner` is used. + :param extra: the keyword arguments to pass to :meth:`main`. + :param color: whether the output should contain color codes. The + application can still override this explicitly. + + .. versionadded:: 8.2 + The result object has the ``output_bytes`` attribute with + the mix of ``stdout_bytes`` and ``stderr_bytes``, as the user would + see it in its terminal. + + .. versionchanged:: 8.2 + The result object always returns the ``stderr_bytes`` stream. + + .. versionchanged:: 8.0 + The result object has the ``return_value`` attribute with + the value returned from the invoked command. + + .. versionchanged:: 4.0 + Added the ``color`` parameter. + + .. versionchanged:: 3.0 + Added the ``catch_exceptions`` parameter. + + .. versionchanged:: 3.0 + The result object has the ``exc_info`` attribute with the + traceback if available. + """ + exc_info = None + if catch_exceptions is None: + catch_exceptions = self.catch_exceptions + + with self.isolation(input=input, env=env, color=color) as outstreams: + return_value = None + exception: BaseException | None = None + exit_code = 0 + + if isinstance(args, str): + args = shlex.split(args) + + try: + prog_name = extra.pop("prog_name") + except KeyError: + prog_name = self.get_default_prog_name(cli) + + try: + return_value = cli.main(args=args or (), prog_name=prog_name, **extra) + except SystemExit as e: + exc_info = sys.exc_info() + e_code = t.cast("int | t.Any | None", e.code) + + if e_code is None: + e_code = 0 + + if e_code != 0: + exception = e + + if not isinstance(e_code, int): + sys.stdout.write(str(e_code)) + sys.stdout.write("\n") + e_code = 1 + + exit_code = e_code + + except Exception as e: + if not catch_exceptions: + raise + exception = e + exit_code = 1 + exc_info = sys.exc_info() + finally: + sys.stdout.flush() + sys.stderr.flush() + stdout = outstreams[0].getvalue() + stderr = outstreams[1].getvalue() + output = outstreams[2].getvalue() + + return Result( + runner=self, + stdout_bytes=stdout, + stderr_bytes=stderr, + output_bytes=output, + return_value=return_value, + exit_code=exit_code, + exception=exception, + exc_info=exc_info, # type: ignore + ) + + @contextlib.contextmanager + def isolated_filesystem( + self, temp_dir: str | os.PathLike[str] | None = None + ) -> cabc.Iterator[str]: + """A context manager that creates a temporary directory and + changes the current working directory to it. This isolates tests + that affect the contents of the CWD to prevent them from + interfering with each other. + + :param temp_dir: Create the temporary directory under this + directory. If given, the created directory is not removed + when exiting. + + .. versionchanged:: 8.0 + Added the ``temp_dir`` parameter. + """ + cwd = os.getcwd() + dt = tempfile.mkdtemp(dir=temp_dir) + os.chdir(dt) + + try: + yield dt + finally: + os.chdir(cwd) + + if temp_dir is None: + import shutil + + try: + shutil.rmtree(dt) + except OSError: + pass diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/click/types.py b/bioagent-bench/.venv/lib/python3.14/site-packages/click/types.py new file mode 100644 index 0000000000000000000000000000000000000000..e71c1c21e4c2e1f954ceeed228507478f138993a --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/click/types.py @@ -0,0 +1,1209 @@ +from __future__ import annotations + +import collections.abc as cabc +import enum +import os +import stat +import sys +import typing as t +from datetime import datetime +from gettext import gettext as _ +from gettext import ngettext + +from ._compat import _get_argv_encoding +from ._compat import open_stream +from .exceptions import BadParameter +from .utils import format_filename +from .utils import LazyFile +from .utils import safecall + +if t.TYPE_CHECKING: + import typing_extensions as te + + from .core import Context + from .core import Parameter + from .shell_completion import CompletionItem + +ParamTypeValue = t.TypeVar("ParamTypeValue") + + +class ParamType: + """Represents the type of a parameter. Validates and converts values + from the command line or Python into the correct type. + + To implement a custom type, subclass and implement at least the + following: + + - The :attr:`name` class attribute must be set. + - Calling an instance of the type with ``None`` must return + ``None``. This is already implemented by default. + - :meth:`convert` must convert string values to the correct type. + - :meth:`convert` must accept values that are already the correct + type. + - It must be able to convert a value if the ``ctx`` and ``param`` + arguments are ``None``. This can occur when converting prompt + input. + """ + + is_composite: t.ClassVar[bool] = False + arity: t.ClassVar[int] = 1 + + #: the descriptive name of this type + name: str + + #: if a list of this type is expected and the value is pulled from a + #: string environment variable, this is what splits it up. `None` + #: means any whitespace. For all parameters the general rule is that + #: whitespace splits them up. The exception are paths and files which + #: are split by ``os.path.pathsep`` by default (":" on Unix and ";" on + #: Windows). + envvar_list_splitter: t.ClassVar[str | None] = None + + def to_info_dict(self) -> dict[str, t.Any]: + """Gather information that could be useful for a tool generating + user-facing documentation. + + Use :meth:`click.Context.to_info_dict` to traverse the entire + CLI structure. + + .. versionadded:: 8.0 + """ + # The class name without the "ParamType" suffix. + param_type = type(self).__name__.partition("ParamType")[0] + param_type = param_type.partition("ParameterType")[0] + + # Custom subclasses might not remember to set a name. + if hasattr(self, "name"): + name = self.name + else: + name = param_type + + return {"param_type": param_type, "name": name} + + def __call__( + self, + value: t.Any, + param: Parameter | None = None, + ctx: Context | None = None, + ) -> t.Any: + if value is not None: + return self.convert(value, param, ctx) + + def get_metavar(self, param: Parameter, ctx: Context) -> str | None: + """Returns the metavar default for this param if it provides one.""" + + def get_missing_message(self, param: Parameter, ctx: Context | None) -> str | None: + """Optionally might return extra information about a missing + parameter. + + .. versionadded:: 2.0 + """ + + def convert( + self, value: t.Any, param: Parameter | None, ctx: Context | None + ) -> t.Any: + """Convert the value to the correct type. This is not called if + the value is ``None`` (the missing value). + + This must accept string values from the command line, as well as + values that are already the correct type. It may also convert + other compatible types. + + The ``param`` and ``ctx`` arguments may be ``None`` in certain + situations, such as when converting prompt input. + + If the value cannot be converted, call :meth:`fail` with a + descriptive message. + + :param value: The value to convert. + :param param: The parameter that is using this type to convert + its value. May be ``None``. + :param ctx: The current context that arrived at this value. May + be ``None``. + """ + return value + + def split_envvar_value(self, rv: str) -> cabc.Sequence[str]: + """Given a value from an environment variable this splits it up + into small chunks depending on the defined envvar list splitter. + + If the splitter is set to `None`, which means that whitespace splits, + then leading and trailing whitespace is ignored. Otherwise, leading + and trailing splitters usually lead to empty items being included. + """ + return (rv or "").split(self.envvar_list_splitter) + + def fail( + self, + message: str, + param: Parameter | None = None, + ctx: Context | None = None, + ) -> t.NoReturn: + """Helper method to fail with an invalid value message.""" + raise BadParameter(message, ctx=ctx, param=param) + + def shell_complete( + self, ctx: Context, param: Parameter, incomplete: str + ) -> list[CompletionItem]: + """Return a list of + :class:`~click.shell_completion.CompletionItem` objects for the + incomplete value. Most types do not provide completions, but + some do, and this allows custom types to provide custom + completions as well. + + :param ctx: Invocation context for this command. + :param param: The parameter that is requesting completion. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + return [] + + +class CompositeParamType(ParamType): + is_composite = True + + @property + def arity(self) -> int: # type: ignore + raise NotImplementedError() + + +class FuncParamType(ParamType): + def __init__(self, func: t.Callable[[t.Any], t.Any]) -> None: + self.name: str = func.__name__ + self.func = func + + def to_info_dict(self) -> dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict["func"] = self.func + return info_dict + + def convert( + self, value: t.Any, param: Parameter | None, ctx: Context | None + ) -> t.Any: + try: + return self.func(value) + except ValueError: + try: + value = str(value) + except UnicodeError: + value = value.decode("utf-8", "replace") + + self.fail(value, param, ctx) + + +class UnprocessedParamType(ParamType): + name = "text" + + def convert( + self, value: t.Any, param: Parameter | None, ctx: Context | None + ) -> t.Any: + return value + + def __repr__(self) -> str: + return "UNPROCESSED" + + +class StringParamType(ParamType): + name = "text" + + def convert( + self, value: t.Any, param: Parameter | None, ctx: Context | None + ) -> t.Any: + if isinstance(value, bytes): + enc = _get_argv_encoding() + try: + value = value.decode(enc) + except UnicodeError: + fs_enc = sys.getfilesystemencoding() + if fs_enc != enc: + try: + value = value.decode(fs_enc) + except UnicodeError: + value = value.decode("utf-8", "replace") + else: + value = value.decode("utf-8", "replace") + return value + return str(value) + + def __repr__(self) -> str: + return "STRING" + + +class Choice(ParamType, t.Generic[ParamTypeValue]): + """The choice type allows a value to be checked against a fixed set + of supported values. + + You may pass any iterable value which will be converted to a tuple + and thus will only be iterated once. + + The resulting value will always be one of the originally passed choices. + See :meth:`normalize_choice` for more info on the mapping of strings + to choices. See :ref:`choice-opts` for an example. + + :param case_sensitive: Set to false to make choices case + insensitive. Defaults to true. + + .. versionchanged:: 8.2.0 + Non-``str`` ``choices`` are now supported. It can additionally be any + iterable. Before you were not recommended to pass anything but a list or + tuple. + + .. versionadded:: 8.2.0 + Choice normalization can be overridden via :meth:`normalize_choice`. + """ + + name = "choice" + + def __init__( + self, choices: cabc.Iterable[ParamTypeValue], case_sensitive: bool = True + ) -> None: + self.choices: cabc.Sequence[ParamTypeValue] = tuple(choices) + self.case_sensitive = case_sensitive + + def to_info_dict(self) -> dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict["choices"] = self.choices + info_dict["case_sensitive"] = self.case_sensitive + return info_dict + + def _normalized_mapping( + self, ctx: Context | None = None + ) -> cabc.Mapping[ParamTypeValue, str]: + """ + Returns mapping where keys are the original choices and the values are + the normalized values that are accepted via the command line. + + This is a simple wrapper around :meth:`normalize_choice`, use that + instead which is supported. + """ + return { + choice: self.normalize_choice( + choice=choice, + ctx=ctx, + ) + for choice in self.choices + } + + def normalize_choice(self, choice: ParamTypeValue, ctx: Context | None) -> str: + """ + Normalize a choice value, used to map a passed string to a choice. + Each choice must have a unique normalized value. + + By default uses :meth:`Context.token_normalize_func` and if not case + sensitive, convert it to a casefolded value. + + .. versionadded:: 8.2.0 + """ + normed_value = choice.name if isinstance(choice, enum.Enum) else str(choice) + + if ctx is not None and ctx.token_normalize_func is not None: + normed_value = ctx.token_normalize_func(normed_value) + + if not self.case_sensitive: + normed_value = normed_value.casefold() + + return normed_value + + def get_metavar(self, param: Parameter, ctx: Context) -> str | None: + if param.param_type_name == "option" and not param.show_choices: # type: ignore + choice_metavars = [ + convert_type(type(choice)).name.upper() for choice in self.choices + ] + choices_str = "|".join([*dict.fromkeys(choice_metavars)]) + else: + choices_str = "|".join( + [str(i) for i in self._normalized_mapping(ctx=ctx).values()] + ) + + # Use curly braces to indicate a required argument. + if param.required and param.param_type_name == "argument": + return f"{{{choices_str}}}" + + # Use square braces to indicate an option or optional argument. + return f"[{choices_str}]" + + def get_missing_message(self, param: Parameter, ctx: Context | None) -> str: + """ + Message shown when no choice is passed. + + .. versionchanged:: 8.2.0 Added ``ctx`` argument. + """ + return _("Choose from:\n\t{choices}").format( + choices=",\n\t".join(self._normalized_mapping(ctx=ctx).values()) + ) + + def convert( + self, value: t.Any, param: Parameter | None, ctx: Context | None + ) -> ParamTypeValue: + """ + For a given value from the parser, normalize it and find its + matching normalized value in the list of choices. Then return the + matched "original" choice. + """ + normed_value = self.normalize_choice(choice=value, ctx=ctx) + normalized_mapping = self._normalized_mapping(ctx=ctx) + + try: + return next( + original + for original, normalized in normalized_mapping.items() + if normalized == normed_value + ) + except StopIteration: + self.fail( + self.get_invalid_choice_message(value=value, ctx=ctx), + param=param, + ctx=ctx, + ) + + def get_invalid_choice_message(self, value: t.Any, ctx: Context | None) -> str: + """Get the error message when the given choice is invalid. + + :param value: The invalid value. + + .. versionadded:: 8.2 + """ + choices_str = ", ".join(map(repr, self._normalized_mapping(ctx=ctx).values())) + return ngettext( + "{value!r} is not {choice}.", + "{value!r} is not one of {choices}.", + len(self.choices), + ).format(value=value, choice=choices_str, choices=choices_str) + + def __repr__(self) -> str: + return f"Choice({list(self.choices)})" + + def shell_complete( + self, ctx: Context, param: Parameter, incomplete: str + ) -> list[CompletionItem]: + """Complete choices that start with the incomplete value. + + :param ctx: Invocation context for this command. + :param param: The parameter that is requesting completion. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from click.shell_completion import CompletionItem + + str_choices = map(str, self.choices) + + if self.case_sensitive: + matched = (c for c in str_choices if c.startswith(incomplete)) + else: + incomplete = incomplete.lower() + matched = (c for c in str_choices if c.lower().startswith(incomplete)) + + return [CompletionItem(c) for c in matched] + + +class DateTime(ParamType): + """The DateTime type converts date strings into `datetime` objects. + + The format strings which are checked are configurable, but default to some + common (non-timezone aware) ISO 8601 formats. + + When specifying *DateTime* formats, you should only pass a list or a tuple. + Other iterables, like generators, may lead to surprising results. + + The format strings are processed using ``datetime.strptime``, and this + consequently defines the format strings which are allowed. + + Parsing is tried using each format, in order, and the first format which + parses successfully is used. + + :param formats: A list or tuple of date format strings, in the order in + which they should be tried. Defaults to + ``'%Y-%m-%d'``, ``'%Y-%m-%dT%H:%M:%S'``, + ``'%Y-%m-%d %H:%M:%S'``. + """ + + name = "datetime" + + def __init__(self, formats: cabc.Sequence[str] | None = None): + self.formats: cabc.Sequence[str] = formats or [ + "%Y-%m-%d", + "%Y-%m-%dT%H:%M:%S", + "%Y-%m-%d %H:%M:%S", + ] + + def to_info_dict(self) -> dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict["formats"] = self.formats + return info_dict + + def get_metavar(self, param: Parameter, ctx: Context) -> str | None: + return f"[{'|'.join(self.formats)}]" + + def _try_to_convert_date(self, value: t.Any, format: str) -> datetime | None: + try: + return datetime.strptime(value, format) + except ValueError: + return None + + def convert( + self, value: t.Any, param: Parameter | None, ctx: Context | None + ) -> t.Any: + if isinstance(value, datetime): + return value + + for format in self.formats: + converted = self._try_to_convert_date(value, format) + + if converted is not None: + return converted + + formats_str = ", ".join(map(repr, self.formats)) + self.fail( + ngettext( + "{value!r} does not match the format {format}.", + "{value!r} does not match the formats {formats}.", + len(self.formats), + ).format(value=value, format=formats_str, formats=formats_str), + param, + ctx, + ) + + def __repr__(self) -> str: + return "DateTime" + + +class _NumberParamTypeBase(ParamType): + _number_class: t.ClassVar[type[t.Any]] + + def convert( + self, value: t.Any, param: Parameter | None, ctx: Context | None + ) -> t.Any: + try: + return self._number_class(value) + except ValueError: + self.fail( + _("{value!r} is not a valid {number_type}.").format( + value=value, number_type=self.name + ), + param, + ctx, + ) + + +class _NumberRangeBase(_NumberParamTypeBase): + def __init__( + self, + min: float | None = None, + max: float | None = None, + min_open: bool = False, + max_open: bool = False, + clamp: bool = False, + ) -> None: + self.min = min + self.max = max + self.min_open = min_open + self.max_open = max_open + self.clamp = clamp + + def to_info_dict(self) -> dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict.update( + min=self.min, + max=self.max, + min_open=self.min_open, + max_open=self.max_open, + clamp=self.clamp, + ) + return info_dict + + def convert( + self, value: t.Any, param: Parameter | None, ctx: Context | None + ) -> t.Any: + import operator + + rv = super().convert(value, param, ctx) + lt_min: bool = self.min is not None and ( + operator.le if self.min_open else operator.lt + )(rv, self.min) + gt_max: bool = self.max is not None and ( + operator.ge if self.max_open else operator.gt + )(rv, self.max) + + if self.clamp: + if lt_min: + return self._clamp(self.min, 1, self.min_open) # type: ignore + + if gt_max: + return self._clamp(self.max, -1, self.max_open) # type: ignore + + if lt_min or gt_max: + self.fail( + _("{value} is not in the range {range}.").format( + value=rv, range=self._describe_range() + ), + param, + ctx, + ) + + return rv + + def _clamp(self, bound: float, dir: t.Literal[1, -1], open: bool) -> float: + """Find the valid value to clamp to bound in the given + direction. + + :param bound: The boundary value. + :param dir: 1 or -1 indicating the direction to move. + :param open: If true, the range does not include the bound. + """ + raise NotImplementedError + + def _describe_range(self) -> str: + """Describe the range for use in help text.""" + if self.min is None: + op = "<" if self.max_open else "<=" + return f"x{op}{self.max}" + + if self.max is None: + op = ">" if self.min_open else ">=" + return f"x{op}{self.min}" + + lop = "<" if self.min_open else "<=" + rop = "<" if self.max_open else "<=" + return f"{self.min}{lop}x{rop}{self.max}" + + def __repr__(self) -> str: + clamp = " clamped" if self.clamp else "" + return f"<{type(self).__name__} {self._describe_range()}{clamp}>" + + +class IntParamType(_NumberParamTypeBase): + name = "integer" + _number_class = int + + def __repr__(self) -> str: + return "INT" + + +class IntRange(_NumberRangeBase, IntParamType): + """Restrict an :data:`click.INT` value to a range of accepted + values. See :ref:`ranges`. + + If ``min`` or ``max`` are not passed, any value is accepted in that + direction. If ``min_open`` or ``max_open`` are enabled, the + corresponding boundary is not included in the range. + + If ``clamp`` is enabled, a value outside the range is clamped to the + boundary instead of failing. + + .. versionchanged:: 8.0 + Added the ``min_open`` and ``max_open`` parameters. + """ + + name = "integer range" + + def _clamp( # type: ignore + self, bound: int, dir: t.Literal[1, -1], open: bool + ) -> int: + if not open: + return bound + + return bound + dir + + +class FloatParamType(_NumberParamTypeBase): + name = "float" + _number_class = float + + def __repr__(self) -> str: + return "FLOAT" + + +class FloatRange(_NumberRangeBase, FloatParamType): + """Restrict a :data:`click.FLOAT` value to a range of accepted + values. See :ref:`ranges`. + + If ``min`` or ``max`` are not passed, any value is accepted in that + direction. If ``min_open`` or ``max_open`` are enabled, the + corresponding boundary is not included in the range. + + If ``clamp`` is enabled, a value outside the range is clamped to the + boundary instead of failing. This is not supported if either + boundary is marked ``open``. + + .. versionchanged:: 8.0 + Added the ``min_open`` and ``max_open`` parameters. + """ + + name = "float range" + + def __init__( + self, + min: float | None = None, + max: float | None = None, + min_open: bool = False, + max_open: bool = False, + clamp: bool = False, + ) -> None: + super().__init__( + min=min, max=max, min_open=min_open, max_open=max_open, clamp=clamp + ) + + if (min_open or max_open) and clamp: + raise TypeError("Clamping is not supported for open bounds.") + + def _clamp(self, bound: float, dir: t.Literal[1, -1], open: bool) -> float: + if not open: + return bound + + # Could use math.nextafter here, but clamping an + # open float range doesn't seem to be particularly useful. It's + # left up to the user to write a callback to do it if needed. + raise RuntimeError("Clamping is not supported for open bounds.") + + +class BoolParamType(ParamType): + name = "boolean" + + bool_states: dict[str, bool] = { + "1": True, + "0": False, + "yes": True, + "no": False, + "true": True, + "false": False, + "on": True, + "off": False, + "t": True, + "f": False, + "y": True, + "n": False, + # Absence of value is considered False. + "": False, + } + """A mapping of string values to boolean states. + + Mapping is inspired by :py:attr:`configparser.ConfigParser.BOOLEAN_STATES` + and extends it. + + .. caution:: + String values are lower-cased, as the ``str_to_bool`` comparison function + below is case-insensitive. + + .. warning:: + The mapping is not exhaustive, and does not cover all possible boolean strings + representations. It will remains as it is to avoid endless bikeshedding. + + Future work my be considered to make this mapping user-configurable from public + API. + """ + + @staticmethod + def str_to_bool(value: str | bool) -> bool | None: + """Convert a string to a boolean value. + + If the value is already a boolean, it is returned as-is. If the value is a + string, it is stripped of whitespaces and lower-cased, then checked against + the known boolean states pre-defined in the `BoolParamType.bool_states` mapping + above. + + Returns `None` if the value does not match any known boolean state. + """ + if isinstance(value, bool): + return value + return BoolParamType.bool_states.get(value.strip().lower()) + + def convert( + self, value: t.Any, param: Parameter | None, ctx: Context | None + ) -> bool: + normalized = self.str_to_bool(value) + if normalized is None: + self.fail( + _( + "{value!r} is not a valid boolean. Recognized values: {states}" + ).format(value=value, states=", ".join(sorted(self.bool_states))), + param, + ctx, + ) + return normalized + + def __repr__(self) -> str: + return "BOOL" + + +class UUIDParameterType(ParamType): + name = "uuid" + + def convert( + self, value: t.Any, param: Parameter | None, ctx: Context | None + ) -> t.Any: + import uuid + + if isinstance(value, uuid.UUID): + return value + + value = value.strip() + + try: + return uuid.UUID(value) + except ValueError: + self.fail( + _("{value!r} is not a valid UUID.").format(value=value), param, ctx + ) + + def __repr__(self) -> str: + return "UUID" + + +class File(ParamType): + """Declares a parameter to be a file for reading or writing. The file + is automatically closed once the context tears down (after the command + finished working). + + Files can be opened for reading or writing. The special value ``-`` + indicates stdin or stdout depending on the mode. + + By default, the file is opened for reading text data, but it can also be + opened in binary mode or for writing. The encoding parameter can be used + to force a specific encoding. + + The `lazy` flag controls if the file should be opened immediately or upon + first IO. The default is to be non-lazy for standard input and output + streams as well as files opened for reading, `lazy` otherwise. When opening a + file lazily for reading, it is still opened temporarily for validation, but + will not be held open until first IO. lazy is mainly useful when opening + for writing to avoid creating the file until it is needed. + + Files can also be opened atomically in which case all writes go into a + separate file in the same folder and upon completion the file will + be moved over to the original location. This is useful if a file + regularly read by other users is modified. + + See :ref:`file-args` for more information. + + .. versionchanged:: 2.0 + Added the ``atomic`` parameter. + """ + + name = "filename" + envvar_list_splitter: t.ClassVar[str] = os.path.pathsep + + def __init__( + self, + mode: str = "r", + encoding: str | None = None, + errors: str | None = "strict", + lazy: bool | None = None, + atomic: bool = False, + ) -> None: + self.mode = mode + self.encoding = encoding + self.errors = errors + self.lazy = lazy + self.atomic = atomic + + def to_info_dict(self) -> dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict.update(mode=self.mode, encoding=self.encoding) + return info_dict + + def resolve_lazy_flag(self, value: str | os.PathLike[str]) -> bool: + if self.lazy is not None: + return self.lazy + if os.fspath(value) == "-": + return False + elif "w" in self.mode: + return True + return False + + def convert( + self, + value: str | os.PathLike[str] | t.IO[t.Any], + param: Parameter | None, + ctx: Context | None, + ) -> t.IO[t.Any]: + if _is_file_like(value): + return value + + value = t.cast("str | os.PathLike[str]", value) + + try: + lazy = self.resolve_lazy_flag(value) + + if lazy: + lf = LazyFile( + value, self.mode, self.encoding, self.errors, atomic=self.atomic + ) + + if ctx is not None: + ctx.call_on_close(lf.close_intelligently) + + return t.cast("t.IO[t.Any]", lf) + + f, should_close = open_stream( + value, self.mode, self.encoding, self.errors, atomic=self.atomic + ) + + # If a context is provided, we automatically close the file + # at the end of the context execution (or flush out). If a + # context does not exist, it's the caller's responsibility to + # properly close the file. This for instance happens when the + # type is used with prompts. + if ctx is not None: + if should_close: + ctx.call_on_close(safecall(f.close)) + else: + ctx.call_on_close(safecall(f.flush)) + + return f + except OSError as e: + self.fail(f"'{format_filename(value)}': {e.strerror}", param, ctx) + + def shell_complete( + self, ctx: Context, param: Parameter, incomplete: str + ) -> list[CompletionItem]: + """Return a special completion marker that tells the completion + system to use the shell to provide file path completions. + + :param ctx: Invocation context for this command. + :param param: The parameter that is requesting completion. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from click.shell_completion import CompletionItem + + return [CompletionItem(incomplete, type="file")] + + +def _is_file_like(value: t.Any) -> te.TypeGuard[t.IO[t.Any]]: + return hasattr(value, "read") or hasattr(value, "write") + + +class Path(ParamType): + """The ``Path`` type is similar to the :class:`File` type, but + returns the filename instead of an open file. Various checks can be + enabled to validate the type of file and permissions. + + :param exists: The file or directory needs to exist for the value to + be valid. If this is not set to ``True``, and the file does not + exist, then all further checks are silently skipped. + :param file_okay: Allow a file as a value. + :param dir_okay: Allow a directory as a value. + :param readable: if true, a readable check is performed. + :param writable: if true, a writable check is performed. + :param executable: if true, an executable check is performed. + :param resolve_path: Make the value absolute and resolve any + symlinks. A ``~`` is not expanded, as this is supposed to be + done by the shell only. + :param allow_dash: Allow a single dash as a value, which indicates + a standard stream (but does not open it). Use + :func:`~click.open_file` to handle opening this value. + :param path_type: Convert the incoming path value to this type. If + ``None``, keep Python's default, which is ``str``. Useful to + convert to :class:`pathlib.Path`. + + .. versionchanged:: 8.1 + Added the ``executable`` parameter. + + .. versionchanged:: 8.0 + Allow passing ``path_type=pathlib.Path``. + + .. versionchanged:: 6.0 + Added the ``allow_dash`` parameter. + """ + + envvar_list_splitter: t.ClassVar[str] = os.path.pathsep + + def __init__( + self, + exists: bool = False, + file_okay: bool = True, + dir_okay: bool = True, + writable: bool = False, + readable: bool = True, + resolve_path: bool = False, + allow_dash: bool = False, + path_type: type[t.Any] | None = None, + executable: bool = False, + ): + self.exists = exists + self.file_okay = file_okay + self.dir_okay = dir_okay + self.readable = readable + self.writable = writable + self.executable = executable + self.resolve_path = resolve_path + self.allow_dash = allow_dash + self.type = path_type + + if self.file_okay and not self.dir_okay: + self.name: str = _("file") + elif self.dir_okay and not self.file_okay: + self.name = _("directory") + else: + self.name = _("path") + + def to_info_dict(self) -> dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict.update( + exists=self.exists, + file_okay=self.file_okay, + dir_okay=self.dir_okay, + writable=self.writable, + readable=self.readable, + allow_dash=self.allow_dash, + ) + return info_dict + + def coerce_path_result( + self, value: str | os.PathLike[str] + ) -> str | bytes | os.PathLike[str]: + if self.type is not None and not isinstance(value, self.type): + if self.type is str: + return os.fsdecode(value) + elif self.type is bytes: + return os.fsencode(value) + else: + return t.cast("os.PathLike[str]", self.type(value)) + + return value + + def convert( + self, + value: str | os.PathLike[str], + param: Parameter | None, + ctx: Context | None, + ) -> str | bytes | os.PathLike[str]: + rv = value + + is_dash = self.file_okay and self.allow_dash and rv in (b"-", "-") + + if not is_dash: + if self.resolve_path: + rv = os.path.realpath(rv) + + try: + st = os.stat(rv) + except OSError: + if not self.exists: + return self.coerce_path_result(rv) + self.fail( + _("{name} {filename!r} does not exist.").format( + name=self.name.title(), filename=format_filename(value) + ), + param, + ctx, + ) + + if not self.file_okay and stat.S_ISREG(st.st_mode): + self.fail( + _("{name} {filename!r} is a file.").format( + name=self.name.title(), filename=format_filename(value) + ), + param, + ctx, + ) + if not self.dir_okay and stat.S_ISDIR(st.st_mode): + self.fail( + _("{name} {filename!r} is a directory.").format( + name=self.name.title(), filename=format_filename(value) + ), + param, + ctx, + ) + + if self.readable and not os.access(rv, os.R_OK): + self.fail( + _("{name} {filename!r} is not readable.").format( + name=self.name.title(), filename=format_filename(value) + ), + param, + ctx, + ) + + if self.writable and not os.access(rv, os.W_OK): + self.fail( + _("{name} {filename!r} is not writable.").format( + name=self.name.title(), filename=format_filename(value) + ), + param, + ctx, + ) + + if self.executable and not os.access(value, os.X_OK): + self.fail( + _("{name} {filename!r} is not executable.").format( + name=self.name.title(), filename=format_filename(value) + ), + param, + ctx, + ) + + return self.coerce_path_result(rv) + + def shell_complete( + self, ctx: Context, param: Parameter, incomplete: str + ) -> list[CompletionItem]: + """Return a special completion marker that tells the completion + system to use the shell to provide path completions for only + directories or any paths. + + :param ctx: Invocation context for this command. + :param param: The parameter that is requesting completion. + :param incomplete: Value being completed. May be empty. + + .. versionadded:: 8.0 + """ + from click.shell_completion import CompletionItem + + type = "dir" if self.dir_okay and not self.file_okay else "file" + return [CompletionItem(incomplete, type=type)] + + +class Tuple(CompositeParamType): + """The default behavior of Click is to apply a type on a value directly. + This works well in most cases, except for when `nargs` is set to a fixed + count and different types should be used for different items. In this + case the :class:`Tuple` type can be used. This type can only be used + if `nargs` is set to a fixed number. + + For more information see :ref:`tuple-type`. + + This can be selected by using a Python tuple literal as a type. + + :param types: a list of types that should be used for the tuple items. + """ + + def __init__(self, types: cabc.Sequence[type[t.Any] | ParamType]) -> None: + self.types: cabc.Sequence[ParamType] = [convert_type(ty) for ty in types] + + def to_info_dict(self) -> dict[str, t.Any]: + info_dict = super().to_info_dict() + info_dict["types"] = [t.to_info_dict() for t in self.types] + return info_dict + + @property + def name(self) -> str: # type: ignore + return f"<{' '.join(ty.name for ty in self.types)}>" + + @property + def arity(self) -> int: # type: ignore + return len(self.types) + + def convert( + self, value: t.Any, param: Parameter | None, ctx: Context | None + ) -> t.Any: + len_type = len(self.types) + len_value = len(value) + + if len_value != len_type: + self.fail( + ngettext( + "{len_type} values are required, but {len_value} was given.", + "{len_type} values are required, but {len_value} were given.", + len_value, + ).format(len_type=len_type, len_value=len_value), + param=param, + ctx=ctx, + ) + + return tuple( + ty(x, param, ctx) for ty, x in zip(self.types, value, strict=False) + ) + + +def convert_type(ty: t.Any | None, default: t.Any | None = None) -> ParamType: + """Find the most appropriate :class:`ParamType` for the given Python + type. If the type isn't provided, it can be inferred from a default + value. + """ + guessed_type = False + + if ty is None and default is not None: + if isinstance(default, (tuple, list)): + # If the default is empty, ty will remain None and will + # return STRING. + if default: + item = default[0] + + # A tuple of tuples needs to detect the inner types. + # Can't call convert recursively because that would + # incorrectly unwind the tuple to a single type. + if isinstance(item, (tuple, list)): + ty = tuple(map(type, item)) + else: + ty = type(item) + else: + ty = type(default) + + guessed_type = True + + if isinstance(ty, tuple): + return Tuple(ty) + + if isinstance(ty, ParamType): + return ty + + if ty is str or ty is None: + return STRING + + if ty is int: + return INT + + if ty is float: + return FLOAT + + if ty is bool: + return BOOL + + if guessed_type: + return STRING + + if __debug__: + try: + if issubclass(ty, ParamType): + raise AssertionError( + f"Attempted to use an uninstantiated parameter type ({ty})." + ) + except TypeError: + # ty is an instance (correct), so issubclass fails. + pass + + return FuncParamType(ty) + + +#: A dummy parameter type that just does nothing. From a user's +#: perspective this appears to just be the same as `STRING` but +#: internally no string conversion takes place if the input was bytes. +#: This is usually useful when working with file paths as they can +#: appear in bytes and unicode. +#: +#: For path related uses the :class:`Path` type is a better choice but +#: there are situations where an unprocessed type is useful which is why +#: it is is provided. +#: +#: .. versionadded:: 4.0 +UNPROCESSED = UnprocessedParamType() + +#: A unicode string parameter type which is the implicit default. This +#: can also be selected by using ``str`` as type. +STRING = StringParamType() + +#: An integer parameter. This can also be selected by using ``int`` as +#: type. +INT = IntParamType() + +#: A floating point value parameter. This can also be selected by using +#: ``float`` as type. +FLOAT = FloatParamType() + +#: A boolean parameter. This is the default for boolean flags. This can +#: also be selected by using ``bool`` as a type. +BOOL = BoolParamType() + +#: A UUID parameter. +UUID = UUIDParameterType() + + +class OptionHelpExtra(t.TypedDict, total=False): + envvars: tuple[str, ...] + default: str + range: str + required: str diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/click/utils.py b/bioagent-bench/.venv/lib/python3.14/site-packages/click/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..89cae3b99c5e85e52c3e84f025015832947ed7e8 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/click/utils.py @@ -0,0 +1,630 @@ +from __future__ import annotations + +import collections.abc as cabc +import os +import re +import sys +import typing as t +from functools import update_wrapper +from types import ModuleType +from types import TracebackType + +from ._compat import _default_text_stderr +from ._compat import _default_text_stdout +from ._compat import _find_binary_writer +from ._compat import auto_wrap_for_ansi +from ._compat import binary_streams +from ._compat import open_stream +from ._compat import should_strip_ansi +from ._compat import strip_ansi +from ._compat import text_streams +from ._compat import WIN +from .globals import resolve_color_default + +if t.TYPE_CHECKING: + import typing_extensions as te + + P = te.ParamSpec("P") + +R = t.TypeVar("R") + + +def _posixify(name: str) -> str: + return "-".join(name.split()).lower() + + +def safecall(func: t.Callable[P, R]) -> t.Callable[P, R | None]: + """Wraps a function so that it swallows exceptions.""" + + def wrapper(*args: P.args, **kwargs: P.kwargs) -> R | None: + try: + return func(*args, **kwargs) + except Exception: + pass + return None + + return update_wrapper(wrapper, func) + + +def make_str(value: t.Any) -> str: + """Converts a value into a valid string.""" + if isinstance(value, bytes): + try: + return value.decode(sys.getfilesystemencoding()) + except UnicodeError: + return value.decode("utf-8", "replace") + return str(value) + + +def make_default_short_help(help: str, max_length: int = 45) -> str: + """Returns a condensed version of help string. + + :meta private: + """ + # Consider only the first paragraph. + paragraph_end = help.find("\n\n") + + if paragraph_end != -1: + help = help[:paragraph_end] + + # Collapse newlines, tabs, and spaces. + words = help.split() + + if not words: + return "" + + # The first paragraph started with a "no rewrap" marker, ignore it. + if words[0] == "\b": + words = words[1:] + + total_length = 0 + last_index = len(words) - 1 + + for i, word in enumerate(words): + total_length += len(word) + (i > 0) + + if total_length > max_length: # too long, truncate + break + + if word[-1] == ".": # sentence end, truncate without "..." + return " ".join(words[: i + 1]) + + if total_length == max_length and i != last_index: + break # not at sentence end, truncate with "..." + else: + return " ".join(words) # no truncation needed + + # Account for the length of the suffix. + total_length += len("...") + + # remove words until the length is short enough + while i > 0: + total_length -= len(words[i]) + (i > 0) + + if total_length <= max_length: + break + + i -= 1 + + return " ".join(words[:i]) + "..." + + +class LazyFile: + """A lazy file works like a regular file but it does not fully open + the file but it does perform some basic checks early to see if the + filename parameter does make sense. This is useful for safely opening + files for writing. + """ + + def __init__( + self, + filename: str | os.PathLike[str], + mode: str = "r", + encoding: str | None = None, + errors: str | None = "strict", + atomic: bool = False, + ): + self.name: str = os.fspath(filename) + self.mode = mode + self.encoding = encoding + self.errors = errors + self.atomic = atomic + self._f: t.IO[t.Any] | None + self.should_close: bool + + if self.name == "-": + self._f, self.should_close = open_stream(filename, mode, encoding, errors) + else: + if "r" in mode: + # Open and close the file in case we're opening it for + # reading so that we can catch at least some errors in + # some cases early. + open(filename, mode).close() + self._f = None + self.should_close = True + + def __getattr__(self, name: str) -> t.Any: + return getattr(self.open(), name) + + def __repr__(self) -> str: + if self._f is not None: + return repr(self._f) + return f"" + + def open(self) -> t.IO[t.Any]: + """Opens the file if it's not yet open. This call might fail with + a :exc:`FileError`. Not handling this error will produce an error + that Click shows. + """ + if self._f is not None: + return self._f + try: + rv, self.should_close = open_stream( + self.name, self.mode, self.encoding, self.errors, atomic=self.atomic + ) + except OSError as e: + from .exceptions import FileError + + raise FileError(self.name, hint=e.strerror) from e + self._f = rv + return rv + + def close(self) -> None: + """Closes the underlying file, no matter what.""" + if self._f is not None: + self._f.close() + + def close_intelligently(self) -> None: + """This function only closes the file if it was opened by the lazy + file wrapper. For instance this will never close stdin. + """ + if self.should_close: + self.close() + + def __enter__(self) -> LazyFile: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> None: + self.close_intelligently() + + def __iter__(self) -> cabc.Iterator[t.AnyStr]: + self.open() + return iter(self._f) # type: ignore + + +class KeepOpenFile: + def __init__(self, file: t.IO[t.Any]) -> None: + self._file: t.IO[t.Any] = file + + def __getattr__(self, name: str) -> t.Any: + return getattr(self._file, name) + + def __enter__(self) -> KeepOpenFile: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + tb: TracebackType | None, + ) -> None: + pass + + def __repr__(self) -> str: + return repr(self._file) + + def __iter__(self) -> cabc.Iterator[t.AnyStr]: + return iter(self._file) + + +def echo( + message: t.Any | None = None, + file: t.IO[t.Any] | None = None, + nl: bool = True, + err: bool = False, + color: bool | None = None, +) -> None: + """Print a message and newline to stdout or a file. This should be + used instead of :func:`print` because it provides better support + for different data, files, and environments. + + Compared to :func:`print`, this does the following: + + - Ensures that the output encoding is not misconfigured on Linux. + - Supports Unicode in the Windows console. + - Supports writing to binary outputs, and supports writing bytes + to text outputs. + - Supports colors and styles on Windows. + - Removes ANSI color and style codes if the output does not look + like an interactive terminal. + - Always flushes the output. + + :param message: The string or bytes to output. Other objects are + converted to strings. + :param file: The file to write to. Defaults to ``stdout``. + :param err: Write to ``stderr`` instead of ``stdout``. + :param nl: Print a newline after the message. Enabled by default. + :param color: Force showing or hiding colors and other styles. By + default Click will remove color if the output does not look like + an interactive terminal. + + .. versionchanged:: 6.0 + Support Unicode output on the Windows console. Click does not + modify ``sys.stdout``, so ``sys.stdout.write()`` and ``print()`` + will still not support Unicode. + + .. versionchanged:: 4.0 + Added the ``color`` parameter. + + .. versionadded:: 3.0 + Added the ``err`` parameter. + + .. versionchanged:: 2.0 + Support colors on Windows if colorama is installed. + """ + if file is None: + if err: + file = _default_text_stderr() + else: + file = _default_text_stdout() + + # There are no standard streams attached to write to. For example, + # pythonw on Windows. + if file is None: + return + + # Convert non bytes/text into the native string type. + if message is not None and not isinstance(message, (str, bytes, bytearray)): + out: str | bytes | bytearray | None = str(message) + else: + out = message + + if nl: + out = out or "" + if isinstance(out, str): + out += "\n" + else: + out += b"\n" + + if not out: + file.flush() + return + + # If there is a message and the value looks like bytes, we manually + # need to find the binary stream and write the message in there. + # This is done separately so that most stream types will work as you + # would expect. Eg: you can write to StringIO for other cases. + if isinstance(out, (bytes, bytearray)): + binary_file = _find_binary_writer(file) + + if binary_file is not None: + file.flush() + binary_file.write(out) + binary_file.flush() + return + + # ANSI style code support. For no message or bytes, nothing happens. + # When outputting to a file instead of a terminal, strip codes. + else: + color = resolve_color_default(color) + + if should_strip_ansi(file, color): + out = strip_ansi(out) + elif WIN: + if auto_wrap_for_ansi is not None: + file = auto_wrap_for_ansi(file, color) # type: ignore + elif not color: + out = strip_ansi(out) + + file.write(out) # type: ignore + file.flush() + + +def get_binary_stream(name: t.Literal["stdin", "stdout", "stderr"]) -> t.BinaryIO: + """Returns a system stream for byte processing. + + :param name: the name of the stream to open. Valid names are ``'stdin'``, + ``'stdout'`` and ``'stderr'`` + """ + opener = binary_streams.get(name) + if opener is None: + raise TypeError(f"Unknown standard stream '{name}'") + return opener() + + +def get_text_stream( + name: t.Literal["stdin", "stdout", "stderr"], + encoding: str | None = None, + errors: str | None = "strict", +) -> t.TextIO: + """Returns a system stream for text processing. This usually returns + a wrapped stream around a binary stream returned from + :func:`get_binary_stream` but it also can take shortcuts for already + correctly configured streams. + + :param name: the name of the stream to open. Valid names are ``'stdin'``, + ``'stdout'`` and ``'stderr'`` + :param encoding: overrides the detected default encoding. + :param errors: overrides the default error mode. + """ + opener = text_streams.get(name) + if opener is None: + raise TypeError(f"Unknown standard stream '{name}'") + return opener(encoding, errors) + + +def open_file( + filename: str | os.PathLike[str], + mode: str = "r", + encoding: str | None = None, + errors: str | None = "strict", + lazy: bool = False, + atomic: bool = False, +) -> t.IO[t.Any]: + """Open a file, with extra behavior to handle ``'-'`` to indicate + a standard stream, lazy open on write, and atomic write. Similar to + the behavior of the :class:`~click.File` param type. + + If ``'-'`` is given to open ``stdout`` or ``stdin``, the stream is + wrapped so that using it in a context manager will not close it. + This makes it possible to use the function without accidentally + closing a standard stream: + + .. code-block:: python + + with open_file(filename) as f: + ... + + :param filename: The name or Path of the file to open, or ``'-'`` for + ``stdin``/``stdout``. + :param mode: The mode in which to open the file. + :param encoding: The encoding to decode or encode a file opened in + text mode. + :param errors: The error handling mode. + :param lazy: Wait to open the file until it is accessed. For read + mode, the file is temporarily opened to raise access errors + early, then closed until it is read again. + :param atomic: Write to a temporary file and replace the given file + on close. + + .. versionadded:: 3.0 + """ + if lazy: + return t.cast( + "t.IO[t.Any]", LazyFile(filename, mode, encoding, errors, atomic=atomic) + ) + + f, should_close = open_stream(filename, mode, encoding, errors, atomic=atomic) + + if not should_close: + f = t.cast("t.IO[t.Any]", KeepOpenFile(f)) + + return f + + +def format_filename( + filename: str | bytes | os.PathLike[str] | os.PathLike[bytes], + shorten: bool = False, +) -> str: + """Format a filename as a string for display. Ensures the filename can be + displayed by replacing any invalid bytes or surrogate escapes in the name + with the replacement character ``�``. + + Invalid bytes or surrogate escapes will raise an error when written to a + stream with ``errors="strict"``. This will typically happen with ``stdout`` + when the locale is something like ``en_GB.UTF-8``. + + Many scenarios *are* safe to write surrogates though, due to PEP 538 and + PEP 540, including: + + - Writing to ``stderr``, which uses ``errors="backslashreplace"``. + - The system has ``LANG=C.UTF-8``, ``C``, or ``POSIX``. Python opens + stdout and stderr with ``errors="surrogateescape"``. + - None of ``LANG/LC_*`` are set. Python assumes ``LANG=C.UTF-8``. + - Python is started in UTF-8 mode with ``PYTHONUTF8=1`` or ``-X utf8``. + Python opens stdout and stderr with ``errors="surrogateescape"``. + + :param filename: formats a filename for UI display. This will also convert + the filename into unicode without failing. + :param shorten: this optionally shortens the filename to strip of the + path that leads up to it. + """ + if shorten: + filename = os.path.basename(filename) + else: + filename = os.fspath(filename) + + if isinstance(filename, bytes): + filename = filename.decode(sys.getfilesystemencoding(), "replace") + else: + filename = filename.encode("utf-8", "surrogateescape").decode( + "utf-8", "replace" + ) + + return filename + + +def get_app_dir(app_name: str, roaming: bool = True, force_posix: bool = False) -> str: + r"""Returns the config folder for the application. The default behavior + is to return whatever is most appropriate for the operating system. + + To give you an idea, for an app called ``"Foo Bar"``, something like + the following folders could be returned: + + Mac OS X: + ``~/Library/Application Support/Foo Bar`` + Mac OS X (POSIX): + ``~/.foo-bar`` + Unix: + ``~/.config/foo-bar`` + Unix (POSIX): + ``~/.foo-bar`` + Windows (roaming): + ``C:\Users\\AppData\Roaming\Foo Bar`` + Windows (not roaming): + ``C:\Users\\AppData\Local\Foo Bar`` + + .. versionadded:: 2.0 + + :param app_name: the application name. This should be properly capitalized + and can contain whitespace. + :param roaming: controls if the folder should be roaming or not on Windows. + Has no effect otherwise. + :param force_posix: if this is set to `True` then on any POSIX system the + folder will be stored in the home folder with a leading + dot instead of the XDG config home or darwin's + application support folder. + """ + if WIN: + key = "APPDATA" if roaming else "LOCALAPPDATA" + folder = os.environ.get(key) + if folder is None: + folder = os.path.expanduser("~") + return os.path.join(folder, app_name) + if force_posix: + return os.path.join(os.path.expanduser(f"~/.{_posixify(app_name)}")) + if sys.platform == "darwin": + return os.path.join( + os.path.expanduser("~/Library/Application Support"), app_name + ) + return os.path.join( + os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")), + _posixify(app_name), + ) + + +class PacifyFlushWrapper: + """This wrapper is used to catch and suppress BrokenPipeErrors resulting + from ``.flush()`` being called on broken pipe during the shutdown/final-GC + of the Python interpreter. Notably ``.flush()`` is always called on + ``sys.stdout`` and ``sys.stderr``. So as to have minimal impact on any + other cleanup code, and the case where the underlying file is not a broken + pipe, all calls and attributes are proxied. + """ + + def __init__(self, wrapped: t.IO[t.Any]) -> None: + self.wrapped = wrapped + + def flush(self) -> None: + try: + self.wrapped.flush() + except OSError as e: + import errno + + if e.errno != errno.EPIPE: + raise + + def __getattr__(self, attr: str) -> t.Any: + return getattr(self.wrapped, attr) + + +def _detect_program_name( + path: str | None = None, _main: ModuleType | None = None +) -> str: + """Determine the command used to run the program, for use in help + text. If a file or entry point was executed, the file name is + returned. If ``python -m`` was used to execute a module or package, + ``python -m name`` is returned. + + This doesn't try to be too precise, the goal is to give a concise + name for help text. Files are only shown as their name without the + path. ``python`` is only shown for modules, and the full path to + ``sys.executable`` is not shown. + + :param path: The Python file being executed. Python puts this in + ``sys.argv[0]``, which is used by default. + :param _main: The ``__main__`` module. This should only be passed + during internal testing. + + .. versionadded:: 8.0 + Based on command args detection in the Werkzeug reloader. + + :meta private: + """ + if _main is None: + _main = sys.modules["__main__"] + + if not path: + path = sys.argv[0] + + # The value of __package__ indicates how Python was called. It may + # not exist if a setuptools script is installed as an egg. It may be + # set incorrectly for entry points created with pip on Windows. + # It is set to "" inside a Shiv or PEX zipapp. + if getattr(_main, "__package__", None) in {None, ""} or ( + os.name == "nt" + and _main.__package__ == "" + and not os.path.exists(path) + and os.path.exists(f"{path}.exe") + ): + # Executed a file, like "python app.py". + return os.path.basename(path) + + # Executed a module, like "python -m example". + # Rewritten by Python from "-m script" to "/path/to/script.py". + # Need to look at main module to determine how it was executed. + py_module = t.cast(str, _main.__package__) + name = os.path.splitext(os.path.basename(path))[0] + + # A submodule like "example.cli". + if name != "__main__": + py_module = f"{py_module}.{name}" + + return f"python -m {py_module.lstrip('.')}" + + +def _expand_args( + args: cabc.Iterable[str], + *, + user: bool = True, + env: bool = True, + glob_recursive: bool = True, +) -> list[str]: + """Simulate Unix shell expansion with Python functions. + + See :func:`glob.glob`, :func:`os.path.expanduser`, and + :func:`os.path.expandvars`. + + This is intended for use on Windows, where the shell does not do any + expansion. It may not exactly match what a Unix shell would do. + + :param args: List of command line arguments to expand. + :param user: Expand user home directory. + :param env: Expand environment variables. + :param glob_recursive: ``**`` matches directories recursively. + + .. versionchanged:: 8.1 + Invalid glob patterns are treated as empty expansions rather + than raising an error. + + .. versionadded:: 8.0 + + :meta private: + """ + from glob import glob + + out = [] + + for arg in args: + if user: + arg = os.path.expanduser(arg) + + if env: + arg = os.path.expandvars(arg) + + try: + matches = glob(arg, recursive=glob_recursive) + except re.error: + matches = [] + + if not matches: + out.append(arg) + else: + out.extend(matches) + + return out diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..a2c19c06fe14476a9bfa4f1f60de7a997a41191c --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +import sys + +try: + from ._version import version as __version__ +except ImportError: + __version__ = 'unknown' + +__all__ = ['easter', 'parser', 'relativedelta', 'rrule', 'tz', + 'utils', 'zoneinfo'] + +def __getattr__(name): + import importlib + + if name in __all__: + return importlib.import_module("." + name, __name__) + raise AttributeError( + "module {!r} has not attribute {!r}".format(__name__, name) + ) + + +def __dir__(): + # __dir__ should include all the lazy-importable modules as well. + return [x for x in globals() if x not in sys.modules] + __all__ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/_common.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/_common.py new file mode 100644 index 0000000000000000000000000000000000000000..4eb2659bd2986125fcfb4afea5bae9efc2dcd1a0 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/_common.py @@ -0,0 +1,43 @@ +""" +Common code used in multiple modules. +""" + + +class weekday(object): + __slots__ = ["weekday", "n"] + + def __init__(self, weekday, n=None): + self.weekday = weekday + self.n = n + + def __call__(self, n): + if n == self.n: + return self + else: + return self.__class__(self.weekday, n) + + def __eq__(self, other): + try: + if self.weekday != other.weekday or self.n != other.n: + return False + except AttributeError: + return False + return True + + def __hash__(self): + return hash(( + self.weekday, + self.n, + )) + + def __ne__(self, other): + return not (self == other) + + def __repr__(self): + s = ("MO", "TU", "WE", "TH", "FR", "SA", "SU")[self.weekday] + if not self.n: + return s + else: + return "%s(%+d)" % (s, self.n) + +# vim:ts=4:sw=4:et diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/_version.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/_version.py new file mode 100644 index 0000000000000000000000000000000000000000..ddda98098527a73348e694c2edb691fd625475fc --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/_version.py @@ -0,0 +1,4 @@ +# file generated by setuptools_scm +# don't change, don't track in version control +__version__ = version = '2.9.0.post0' +__version_tuple__ = version_tuple = (2, 9, 0) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/easter.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/easter.py new file mode 100644 index 0000000000000000000000000000000000000000..f74d1f7442473997245ac683b8a269a3574d1ba4 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/easter.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +""" +This module offers a generic Easter computing method for any given year, using +Western, Orthodox or Julian algorithms. +""" + +import datetime + +__all__ = ["easter", "EASTER_JULIAN", "EASTER_ORTHODOX", "EASTER_WESTERN"] + +EASTER_JULIAN = 1 +EASTER_ORTHODOX = 2 +EASTER_WESTERN = 3 + + +def easter(year, method=EASTER_WESTERN): + """ + This method was ported from the work done by GM Arts, + on top of the algorithm by Claus Tondering, which was + based in part on the algorithm of Ouding (1940), as + quoted in "Explanatory Supplement to the Astronomical + Almanac", P. Kenneth Seidelmann, editor. + + This algorithm implements three different Easter + calculation methods: + + 1. Original calculation in Julian calendar, valid in + dates after 326 AD + 2. Original method, with date converted to Gregorian + calendar, valid in years 1583 to 4099 + 3. Revised method, in Gregorian calendar, valid in + years 1583 to 4099 as well + + These methods are represented by the constants: + + * ``EASTER_JULIAN = 1`` + * ``EASTER_ORTHODOX = 2`` + * ``EASTER_WESTERN = 3`` + + The default method is method 3. + + More about the algorithm may be found at: + + `GM Arts: Easter Algorithms `_ + + and + + `The Calendar FAQ: Easter `_ + + """ + + if not (1 <= method <= 3): + raise ValueError("invalid method") + + # g - Golden year - 1 + # c - Century + # h - (23 - Epact) mod 30 + # i - Number of days from March 21 to Paschal Full Moon + # j - Weekday for PFM (0=Sunday, etc) + # p - Number of days from March 21 to Sunday on or before PFM + # (-6 to 28 methods 1 & 3, to 56 for method 2) + # e - Extra days to add for method 2 (converting Julian + # date to Gregorian date) + + y = year + g = y % 19 + e = 0 + if method < 3: + # Old method + i = (19*g + 15) % 30 + j = (y + y//4 + i) % 7 + if method == 2: + # Extra dates to convert Julian to Gregorian date + e = 10 + if y > 1600: + e = e + y//100 - 16 - (y//100 - 16)//4 + else: + # New method + c = y//100 + h = (c - c//4 - (8*c + 13)//25 + 19*g + 15) % 30 + i = h - (h//28)*(1 - (h//28)*(29//(h + 1))*((21 - g)//11)) + j = (y + y//4 + i + 2 - c + c//4) % 7 + + # p can be from -6 to 56 corresponding to dates 22 March to 23 May + # (later dates apply to method 2, although 23 May never actually occurs) + p = i - j + e + d = 1 + (p + 27 + (p + 6)//40) % 31 + m = 3 + (p + 26)//30 + return datetime.date(int(y), int(m), int(d)) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/parser/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/parser/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..d174b0e4dcc472999b75e55ebb88af320ae38081 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/parser/__init__.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +from ._parser import parse, parser, parserinfo, ParserError +from ._parser import DEFAULTPARSER, DEFAULTTZPARSER +from ._parser import UnknownTimezoneWarning + +from ._parser import __doc__ + +from .isoparser import isoparser, isoparse + +__all__ = ['parse', 'parser', 'parserinfo', + 'isoparse', 'isoparser', + 'ParserError', + 'UnknownTimezoneWarning'] + + +### +# Deprecate portions of the private interface so that downstream code that +# is improperly relying on it is given *some* notice. + + +def __deprecated_private_func(f): + from functools import wraps + import warnings + + msg = ('{name} is a private function and may break without warning, ' + 'it will be moved and or renamed in future versions.') + msg = msg.format(name=f.__name__) + + @wraps(f) + def deprecated_func(*args, **kwargs): + warnings.warn(msg, DeprecationWarning) + return f(*args, **kwargs) + + return deprecated_func + +def __deprecate_private_class(c): + import warnings + + msg = ('{name} is a private class and may break without warning, ' + 'it will be moved and or renamed in future versions.') + msg = msg.format(name=c.__name__) + + class private_class(c): + __doc__ = c.__doc__ + + def __init__(self, *args, **kwargs): + warnings.warn(msg, DeprecationWarning) + super(private_class, self).__init__(*args, **kwargs) + + private_class.__name__ = c.__name__ + + return private_class + + +from ._parser import _timelex, _resultbase +from ._parser import _tzparser, _parsetz + +_timelex = __deprecate_private_class(_timelex) +_tzparser = __deprecate_private_class(_tzparser) +_resultbase = __deprecate_private_class(_resultbase) +_parsetz = __deprecated_private_func(_parsetz) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/parser/_parser.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/parser/_parser.py new file mode 100644 index 0000000000000000000000000000000000000000..37d1663b2f72447800d9a553929e3de932244289 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/parser/_parser.py @@ -0,0 +1,1613 @@ +# -*- coding: utf-8 -*- +""" +This module offers a generic date/time string parser which is able to parse +most known formats to represent a date and/or time. + +This module attempts to be forgiving with regards to unlikely input formats, +returning a datetime object even for dates which are ambiguous. If an element +of a date/time stamp is omitted, the following rules are applied: + +- If AM or PM is left unspecified, a 24-hour clock is assumed, however, an hour + on a 12-hour clock (``0 <= hour <= 12``) *must* be specified if AM or PM is + specified. +- If a time zone is omitted, a timezone-naive datetime is returned. + +If any other elements are missing, they are taken from the +:class:`datetime.datetime` object passed to the parameter ``default``. If this +results in a day number exceeding the valid number of days per month, the +value falls back to the end of the month. + +Additional resources about date/time string formats can be found below: + +- `A summary of the international standard date and time notation + `_ +- `W3C Date and Time Formats `_ +- `Time Formats (Planetary Rings Node) `_ +- `CPAN ParseDate module + `_ +- `Java SimpleDateFormat Class + `_ +""" +from __future__ import unicode_literals + +import datetime +import re +import string +import time +import warnings + +from calendar import monthrange +from io import StringIO + +import six +from six import integer_types, text_type + +from decimal import Decimal + +from warnings import warn + +from .. import relativedelta +from .. import tz + +__all__ = ["parse", "parserinfo", "ParserError"] + + +# TODO: pandas.core.tools.datetimes imports this explicitly. Might be worth +# making public and/or figuring out if there is something we can +# take off their plate. +class _timelex(object): + # Fractional seconds are sometimes split by a comma + _split_decimal = re.compile("([.,])") + + def __init__(self, instream): + if isinstance(instream, (bytes, bytearray)): + instream = instream.decode() + + if isinstance(instream, text_type): + instream = StringIO(instream) + elif getattr(instream, 'read', None) is None: + raise TypeError('Parser must be a string or character stream, not ' + '{itype}'.format(itype=instream.__class__.__name__)) + + self.instream = instream + self.charstack = [] + self.tokenstack = [] + self.eof = False + + def get_token(self): + """ + This function breaks the time string into lexical units (tokens), which + can be parsed by the parser. Lexical units are demarcated by changes in + the character set, so any continuous string of letters is considered + one unit, any continuous string of numbers is considered one unit. + + The main complication arises from the fact that dots ('.') can be used + both as separators (e.g. "Sep.20.2009") or decimal points (e.g. + "4:30:21.447"). As such, it is necessary to read the full context of + any dot-separated strings before breaking it into tokens; as such, this + function maintains a "token stack", for when the ambiguous context + demands that multiple tokens be parsed at once. + """ + if self.tokenstack: + return self.tokenstack.pop(0) + + seenletters = False + token = None + state = None + + while not self.eof: + # We only realize that we've reached the end of a token when we + # find a character that's not part of the current token - since + # that character may be part of the next token, it's stored in the + # charstack. + if self.charstack: + nextchar = self.charstack.pop(0) + else: + nextchar = self.instream.read(1) + while nextchar == '\x00': + nextchar = self.instream.read(1) + + if not nextchar: + self.eof = True + break + elif not state: + # First character of the token - determines if we're starting + # to parse a word, a number or something else. + token = nextchar + if self.isword(nextchar): + state = 'a' + elif self.isnum(nextchar): + state = '0' + elif self.isspace(nextchar): + token = ' ' + break # emit token + else: + break # emit token + elif state == 'a': + # If we've already started reading a word, we keep reading + # letters until we find something that's not part of a word. + seenletters = True + if self.isword(nextchar): + token += nextchar + elif nextchar == '.': + token += nextchar + state = 'a.' + else: + self.charstack.append(nextchar) + break # emit token + elif state == '0': + # If we've already started reading a number, we keep reading + # numbers until we find something that doesn't fit. + if self.isnum(nextchar): + token += nextchar + elif nextchar == '.' or (nextchar == ',' and len(token) >= 2): + token += nextchar + state = '0.' + else: + self.charstack.append(nextchar) + break # emit token + elif state == 'a.': + # If we've seen some letters and a dot separator, continue + # parsing, and the tokens will be broken up later. + seenletters = True + if nextchar == '.' or self.isword(nextchar): + token += nextchar + elif self.isnum(nextchar) and token[-1] == '.': + token += nextchar + state = '0.' + else: + self.charstack.append(nextchar) + break # emit token + elif state == '0.': + # If we've seen at least one dot separator, keep going, we'll + # break up the tokens later. + if nextchar == '.' or self.isnum(nextchar): + token += nextchar + elif self.isword(nextchar) and token[-1] == '.': + token += nextchar + state = 'a.' + else: + self.charstack.append(nextchar) + break # emit token + + if (state in ('a.', '0.') and (seenletters or token.count('.') > 1 or + token[-1] in '.,')): + l = self._split_decimal.split(token) + token = l[0] + for tok in l[1:]: + if tok: + self.tokenstack.append(tok) + + if state == '0.' and token.count('.') == 0: + token = token.replace(',', '.') + + return token + + def __iter__(self): + return self + + def __next__(self): + token = self.get_token() + if token is None: + raise StopIteration + + return token + + def next(self): + return self.__next__() # Python 2.x support + + @classmethod + def split(cls, s): + return list(cls(s)) + + @classmethod + def isword(cls, nextchar): + """ Whether or not the next character is part of a word """ + return nextchar.isalpha() + + @classmethod + def isnum(cls, nextchar): + """ Whether the next character is part of a number """ + return nextchar.isdigit() + + @classmethod + def isspace(cls, nextchar): + """ Whether the next character is whitespace """ + return nextchar.isspace() + + +class _resultbase(object): + + def __init__(self): + for attr in self.__slots__: + setattr(self, attr, None) + + def _repr(self, classname): + l = [] + for attr in self.__slots__: + value = getattr(self, attr) + if value is not None: + l.append("%s=%s" % (attr, repr(value))) + return "%s(%s)" % (classname, ", ".join(l)) + + def __len__(self): + return (sum(getattr(self, attr) is not None + for attr in self.__slots__)) + + def __repr__(self): + return self._repr(self.__class__.__name__) + + +class parserinfo(object): + """ + Class which handles what inputs are accepted. Subclass this to customize + the language and acceptable values for each parameter. + + :param dayfirst: + Whether to interpret the first value in an ambiguous 3-integer date + (e.g. 01/05/09) as the day (``True``) or month (``False``). If + ``yearfirst`` is set to ``True``, this distinguishes between YDM + and YMD. Default is ``False``. + + :param yearfirst: + Whether to interpret the first value in an ambiguous 3-integer date + (e.g. 01/05/09) as the year. If ``True``, the first number is taken + to be the year, otherwise the last number is taken to be the year. + Default is ``False``. + """ + + # m from a.m/p.m, t from ISO T separator + JUMP = [" ", ".", ",", ";", "-", "/", "'", + "at", "on", "and", "ad", "m", "t", "of", + "st", "nd", "rd", "th"] + + WEEKDAYS = [("Mon", "Monday"), + ("Tue", "Tuesday"), # TODO: "Tues" + ("Wed", "Wednesday"), + ("Thu", "Thursday"), # TODO: "Thurs" + ("Fri", "Friday"), + ("Sat", "Saturday"), + ("Sun", "Sunday")] + MONTHS = [("Jan", "January"), + ("Feb", "February"), # TODO: "Febr" + ("Mar", "March"), + ("Apr", "April"), + ("May", "May"), + ("Jun", "June"), + ("Jul", "July"), + ("Aug", "August"), + ("Sep", "Sept", "September"), + ("Oct", "October"), + ("Nov", "November"), + ("Dec", "December")] + HMS = [("h", "hour", "hours"), + ("m", "minute", "minutes"), + ("s", "second", "seconds")] + AMPM = [("am", "a"), + ("pm", "p")] + UTCZONE = ["UTC", "GMT", "Z", "z"] + PERTAIN = ["of"] + TZOFFSET = {} + # TODO: ERA = ["AD", "BC", "CE", "BCE", "Stardate", + # "Anno Domini", "Year of Our Lord"] + + def __init__(self, dayfirst=False, yearfirst=False): + self._jump = self._convert(self.JUMP) + self._weekdays = self._convert(self.WEEKDAYS) + self._months = self._convert(self.MONTHS) + self._hms = self._convert(self.HMS) + self._ampm = self._convert(self.AMPM) + self._utczone = self._convert(self.UTCZONE) + self._pertain = self._convert(self.PERTAIN) + + self.dayfirst = dayfirst + self.yearfirst = yearfirst + + self._year = time.localtime().tm_year + self._century = self._year // 100 * 100 + + def _convert(self, lst): + dct = {} + for i, v in enumerate(lst): + if isinstance(v, tuple): + for v in v: + dct[v.lower()] = i + else: + dct[v.lower()] = i + return dct + + def jump(self, name): + return name.lower() in self._jump + + def weekday(self, name): + try: + return self._weekdays[name.lower()] + except KeyError: + pass + return None + + def month(self, name): + try: + return self._months[name.lower()] + 1 + except KeyError: + pass + return None + + def hms(self, name): + try: + return self._hms[name.lower()] + except KeyError: + return None + + def ampm(self, name): + try: + return self._ampm[name.lower()] + except KeyError: + return None + + def pertain(self, name): + return name.lower() in self._pertain + + def utczone(self, name): + return name.lower() in self._utczone + + def tzoffset(self, name): + if name in self._utczone: + return 0 + + return self.TZOFFSET.get(name) + + def convertyear(self, year, century_specified=False): + """ + Converts two-digit years to year within [-50, 49] + range of self._year (current local time) + """ + + # Function contract is that the year is always positive + assert year >= 0 + + if year < 100 and not century_specified: + # assume current century to start + year += self._century + + if year >= self._year + 50: # if too far in future + year -= 100 + elif year < self._year - 50: # if too far in past + year += 100 + + return year + + def validate(self, res): + # move to info + if res.year is not None: + res.year = self.convertyear(res.year, res.century_specified) + + if ((res.tzoffset == 0 and not res.tzname) or + (res.tzname == 'Z' or res.tzname == 'z')): + res.tzname = "UTC" + res.tzoffset = 0 + elif res.tzoffset != 0 and res.tzname and self.utczone(res.tzname): + res.tzoffset = 0 + return True + + +class _ymd(list): + def __init__(self, *args, **kwargs): + super(self.__class__, self).__init__(*args, **kwargs) + self.century_specified = False + self.dstridx = None + self.mstridx = None + self.ystridx = None + + @property + def has_year(self): + return self.ystridx is not None + + @property + def has_month(self): + return self.mstridx is not None + + @property + def has_day(self): + return self.dstridx is not None + + def could_be_day(self, value): + if self.has_day: + return False + elif not self.has_month: + return 1 <= value <= 31 + elif not self.has_year: + # Be permissive, assume leap year + month = self[self.mstridx] + return 1 <= value <= monthrange(2000, month)[1] + else: + month = self[self.mstridx] + year = self[self.ystridx] + return 1 <= value <= monthrange(year, month)[1] + + def append(self, val, label=None): + if hasattr(val, '__len__'): + if val.isdigit() and len(val) > 2: + self.century_specified = True + if label not in [None, 'Y']: # pragma: no cover + raise ValueError(label) + label = 'Y' + elif val > 100: + self.century_specified = True + if label not in [None, 'Y']: # pragma: no cover + raise ValueError(label) + label = 'Y' + + super(self.__class__, self).append(int(val)) + + if label == 'M': + if self.has_month: + raise ValueError('Month is already set') + self.mstridx = len(self) - 1 + elif label == 'D': + if self.has_day: + raise ValueError('Day is already set') + self.dstridx = len(self) - 1 + elif label == 'Y': + if self.has_year: + raise ValueError('Year is already set') + self.ystridx = len(self) - 1 + + def _resolve_from_stridxs(self, strids): + """ + Try to resolve the identities of year/month/day elements using + ystridx, mstridx, and dstridx, if enough of these are specified. + """ + if len(self) == 3 and len(strids) == 2: + # we can back out the remaining stridx value + missing = [x for x in range(3) if x not in strids.values()] + key = [x for x in ['y', 'm', 'd'] if x not in strids] + assert len(missing) == len(key) == 1 + key = key[0] + val = missing[0] + strids[key] = val + + assert len(self) == len(strids) # otherwise this should not be called + out = {key: self[strids[key]] for key in strids} + return (out.get('y'), out.get('m'), out.get('d')) + + def resolve_ymd(self, yearfirst, dayfirst): + len_ymd = len(self) + year, month, day = (None, None, None) + + strids = (('y', self.ystridx), + ('m', self.mstridx), + ('d', self.dstridx)) + + strids = {key: val for key, val in strids if val is not None} + if (len(self) == len(strids) > 0 or + (len(self) == 3 and len(strids) == 2)): + return self._resolve_from_stridxs(strids) + + mstridx = self.mstridx + + if len_ymd > 3: + raise ValueError("More than three YMD values") + elif len_ymd == 1 or (mstridx is not None and len_ymd == 2): + # One member, or two members with a month string + if mstridx is not None: + month = self[mstridx] + # since mstridx is 0 or 1, self[mstridx-1] always + # looks up the other element + other = self[mstridx - 1] + else: + other = self[0] + + if len_ymd > 1 or mstridx is None: + if other > 31: + year = other + else: + day = other + + elif len_ymd == 2: + # Two members with numbers + if self[0] > 31: + # 99-01 + year, month = self + elif self[1] > 31: + # 01-99 + month, year = self + elif dayfirst and self[1] <= 12: + # 13-01 + day, month = self + else: + # 01-13 + month, day = self + + elif len_ymd == 3: + # Three members + if mstridx == 0: + if self[1] > 31: + # Apr-2003-25 + month, year, day = self + else: + month, day, year = self + elif mstridx == 1: + if self[0] > 31 or (yearfirst and self[2] <= 31): + # 99-Jan-01 + year, month, day = self + else: + # 01-Jan-01 + # Give precedence to day-first, since + # two-digit years is usually hand-written. + day, month, year = self + + elif mstridx == 2: + # WTF!? + if self[1] > 31: + # 01-99-Jan + day, year, month = self + else: + # 99-01-Jan + year, day, month = self + + else: + if (self[0] > 31 or + self.ystridx == 0 or + (yearfirst and self[1] <= 12 and self[2] <= 31)): + # 99-01-01 + if dayfirst and self[2] <= 12: + year, day, month = self + else: + year, month, day = self + elif self[0] > 12 or (dayfirst and self[1] <= 12): + # 13-01-01 + day, month, year = self + else: + # 01-13-01 + month, day, year = self + + return year, month, day + + +class parser(object): + def __init__(self, info=None): + self.info = info or parserinfo() + + def parse(self, timestr, default=None, + ignoretz=False, tzinfos=None, **kwargs): + """ + Parse the date/time string into a :class:`datetime.datetime` object. + + :param timestr: + Any date/time string using the supported formats. + + :param default: + The default datetime object, if this is a datetime object and not + ``None``, elements specified in ``timestr`` replace elements in the + default object. + + :param ignoretz: + If set ``True``, time zones in parsed strings are ignored and a + naive :class:`datetime.datetime` object is returned. + + :param tzinfos: + Additional time zone names / aliases which may be present in the + string. This argument maps time zone names (and optionally offsets + from those time zones) to time zones. This parameter can be a + dictionary with timezone aliases mapping time zone names to time + zones or a function taking two parameters (``tzname`` and + ``tzoffset``) and returning a time zone. + + The timezones to which the names are mapped can be an integer + offset from UTC in seconds or a :class:`tzinfo` object. + + .. doctest:: + :options: +NORMALIZE_WHITESPACE + + >>> from dateutil.parser import parse + >>> from dateutil.tz import gettz + >>> tzinfos = {"BRST": -7200, "CST": gettz("America/Chicago")} + >>> parse("2012-01-19 17:21:00 BRST", tzinfos=tzinfos) + datetime.datetime(2012, 1, 19, 17, 21, tzinfo=tzoffset(u'BRST', -7200)) + >>> parse("2012-01-19 17:21:00 CST", tzinfos=tzinfos) + datetime.datetime(2012, 1, 19, 17, 21, + tzinfo=tzfile('/usr/share/zoneinfo/America/Chicago')) + + This parameter is ignored if ``ignoretz`` is set. + + :param \\*\\*kwargs: + Keyword arguments as passed to ``_parse()``. + + :return: + Returns a :class:`datetime.datetime` object or, if the + ``fuzzy_with_tokens`` option is ``True``, returns a tuple, the + first element being a :class:`datetime.datetime` object, the second + a tuple containing the fuzzy tokens. + + :raises ParserError: + Raised for invalid or unknown string format, if the provided + :class:`tzinfo` is not in a valid format, or if an invalid date + would be created. + + :raises TypeError: + Raised for non-string or character stream input. + + :raises OverflowError: + Raised if the parsed date exceeds the largest valid C integer on + your system. + """ + + if default is None: + default = datetime.datetime.now().replace(hour=0, minute=0, + second=0, microsecond=0) + + res, skipped_tokens = self._parse(timestr, **kwargs) + + if res is None: + raise ParserError("Unknown string format: %s", timestr) + + if len(res) == 0: + raise ParserError("String does not contain a date: %s", timestr) + + try: + ret = self._build_naive(res, default) + except ValueError as e: + six.raise_from(ParserError(str(e) + ": %s", timestr), e) + + if not ignoretz: + ret = self._build_tzaware(ret, res, tzinfos) + + if kwargs.get('fuzzy_with_tokens', False): + return ret, skipped_tokens + else: + return ret + + class _result(_resultbase): + __slots__ = ["year", "month", "day", "weekday", + "hour", "minute", "second", "microsecond", + "tzname", "tzoffset", "ampm","any_unused_tokens"] + + def _parse(self, timestr, dayfirst=None, yearfirst=None, fuzzy=False, + fuzzy_with_tokens=False): + """ + Private method which performs the heavy lifting of parsing, called from + ``parse()``, which passes on its ``kwargs`` to this function. + + :param timestr: + The string to parse. + + :param dayfirst: + Whether to interpret the first value in an ambiguous 3-integer date + (e.g. 01/05/09) as the day (``True``) or month (``False``). If + ``yearfirst`` is set to ``True``, this distinguishes between YDM + and YMD. If set to ``None``, this value is retrieved from the + current :class:`parserinfo` object (which itself defaults to + ``False``). + + :param yearfirst: + Whether to interpret the first value in an ambiguous 3-integer date + (e.g. 01/05/09) as the year. If ``True``, the first number is taken + to be the year, otherwise the last number is taken to be the year. + If this is set to ``None``, the value is retrieved from the current + :class:`parserinfo` object (which itself defaults to ``False``). + + :param fuzzy: + Whether to allow fuzzy parsing, allowing for string like "Today is + January 1, 2047 at 8:21:00AM". + + :param fuzzy_with_tokens: + If ``True``, ``fuzzy`` is automatically set to True, and the parser + will return a tuple where the first element is the parsed + :class:`datetime.datetime` datetimestamp and the second element is + a tuple containing the portions of the string which were ignored: + + .. doctest:: + + >>> from dateutil.parser import parse + >>> parse("Today is January 1, 2047 at 8:21:00AM", fuzzy_with_tokens=True) + (datetime.datetime(2047, 1, 1, 8, 21), (u'Today is ', u' ', u'at ')) + + """ + if fuzzy_with_tokens: + fuzzy = True + + info = self.info + + if dayfirst is None: + dayfirst = info.dayfirst + + if yearfirst is None: + yearfirst = info.yearfirst + + res = self._result() + l = _timelex.split(timestr) # Splits the timestr into tokens + + skipped_idxs = [] + + # year/month/day list + ymd = _ymd() + + len_l = len(l) + i = 0 + try: + while i < len_l: + + # Check if it's a number + value_repr = l[i] + try: + value = float(value_repr) + except ValueError: + value = None + + if value is not None: + # Numeric token + i = self._parse_numeric_token(l, i, info, ymd, res, fuzzy) + + # Check weekday + elif info.weekday(l[i]) is not None: + value = info.weekday(l[i]) + res.weekday = value + + # Check month name + elif info.month(l[i]) is not None: + value = info.month(l[i]) + ymd.append(value, 'M') + + if i + 1 < len_l: + if l[i + 1] in ('-', '/'): + # Jan-01[-99] + sep = l[i + 1] + ymd.append(l[i + 2]) + + if i + 3 < len_l and l[i + 3] == sep: + # Jan-01-99 + ymd.append(l[i + 4]) + i += 2 + + i += 2 + + elif (i + 4 < len_l and l[i + 1] == l[i + 3] == ' ' and + info.pertain(l[i + 2])): + # Jan of 01 + # In this case, 01 is clearly year + if l[i + 4].isdigit(): + # Convert it here to become unambiguous + value = int(l[i + 4]) + year = str(info.convertyear(value)) + ymd.append(year, 'Y') + else: + # Wrong guess + pass + # TODO: not hit in tests + i += 4 + + # Check am/pm + elif info.ampm(l[i]) is not None: + value = info.ampm(l[i]) + val_is_ampm = self._ampm_valid(res.hour, res.ampm, fuzzy) + + if val_is_ampm: + res.hour = self._adjust_ampm(res.hour, value) + res.ampm = value + + elif fuzzy: + skipped_idxs.append(i) + + # Check for a timezone name + elif self._could_be_tzname(res.hour, res.tzname, res.tzoffset, l[i]): + res.tzname = l[i] + res.tzoffset = info.tzoffset(res.tzname) + + # Check for something like GMT+3, or BRST+3. Notice + # that it doesn't mean "I am 3 hours after GMT", but + # "my time +3 is GMT". If found, we reverse the + # logic so that timezone parsing code will get it + # right. + if i + 1 < len_l and l[i + 1] in ('+', '-'): + l[i + 1] = ('+', '-')[l[i + 1] == '+'] + res.tzoffset = None + if info.utczone(res.tzname): + # With something like GMT+3, the timezone + # is *not* GMT. + res.tzname = None + + # Check for a numbered timezone + elif res.hour is not None and l[i] in ('+', '-'): + signal = (-1, 1)[l[i] == '+'] + len_li = len(l[i + 1]) + + # TODO: check that l[i + 1] is integer? + if len_li == 4: + # -0300 + hour_offset = int(l[i + 1][:2]) + min_offset = int(l[i + 1][2:]) + elif i + 2 < len_l and l[i + 2] == ':': + # -03:00 + hour_offset = int(l[i + 1]) + min_offset = int(l[i + 3]) # TODO: Check that l[i+3] is minute-like? + i += 2 + elif len_li <= 2: + # -[0]3 + hour_offset = int(l[i + 1][:2]) + min_offset = 0 + else: + raise ValueError(timestr) + + res.tzoffset = signal * (hour_offset * 3600 + min_offset * 60) + + # Look for a timezone name between parenthesis + if (i + 5 < len_l and + info.jump(l[i + 2]) and l[i + 3] == '(' and + l[i + 5] == ')' and + 3 <= len(l[i + 4]) and + self._could_be_tzname(res.hour, res.tzname, + None, l[i + 4])): + # -0300 (BRST) + res.tzname = l[i + 4] + i += 4 + + i += 1 + + # Check jumps + elif not (info.jump(l[i]) or fuzzy): + raise ValueError(timestr) + + else: + skipped_idxs.append(i) + i += 1 + + # Process year/month/day + year, month, day = ymd.resolve_ymd(yearfirst, dayfirst) + + res.century_specified = ymd.century_specified + res.year = year + res.month = month + res.day = day + + except (IndexError, ValueError): + return None, None + + if not info.validate(res): + return None, None + + if fuzzy_with_tokens: + skipped_tokens = self._recombine_skipped(l, skipped_idxs) + return res, tuple(skipped_tokens) + else: + return res, None + + def _parse_numeric_token(self, tokens, idx, info, ymd, res, fuzzy): + # Token is a number + value_repr = tokens[idx] + try: + value = self._to_decimal(value_repr) + except Exception as e: + six.raise_from(ValueError('Unknown numeric token'), e) + + len_li = len(value_repr) + + len_l = len(tokens) + + if (len(ymd) == 3 and len_li in (2, 4) and + res.hour is None and + (idx + 1 >= len_l or + (tokens[idx + 1] != ':' and + info.hms(tokens[idx + 1]) is None))): + # 19990101T23[59] + s = tokens[idx] + res.hour = int(s[:2]) + + if len_li == 4: + res.minute = int(s[2:]) + + elif len_li == 6 or (len_li > 6 and tokens[idx].find('.') == 6): + # YYMMDD or HHMMSS[.ss] + s = tokens[idx] + + if not ymd and '.' not in tokens[idx]: + ymd.append(s[:2]) + ymd.append(s[2:4]) + ymd.append(s[4:]) + else: + # 19990101T235959[.59] + + # TODO: Check if res attributes already set. + res.hour = int(s[:2]) + res.minute = int(s[2:4]) + res.second, res.microsecond = self._parsems(s[4:]) + + elif len_li in (8, 12, 14): + # YYYYMMDD + s = tokens[idx] + ymd.append(s[:4], 'Y') + ymd.append(s[4:6]) + ymd.append(s[6:8]) + + if len_li > 8: + res.hour = int(s[8:10]) + res.minute = int(s[10:12]) + + if len_li > 12: + res.second = int(s[12:]) + + elif self._find_hms_idx(idx, tokens, info, allow_jump=True) is not None: + # HH[ ]h or MM[ ]m or SS[.ss][ ]s + hms_idx = self._find_hms_idx(idx, tokens, info, allow_jump=True) + (idx, hms) = self._parse_hms(idx, tokens, info, hms_idx) + if hms is not None: + # TODO: checking that hour/minute/second are not + # already set? + self._assign_hms(res, value_repr, hms) + + elif idx + 2 < len_l and tokens[idx + 1] == ':': + # HH:MM[:SS[.ss]] + res.hour = int(value) + value = self._to_decimal(tokens[idx + 2]) # TODO: try/except for this? + (res.minute, res.second) = self._parse_min_sec(value) + + if idx + 4 < len_l and tokens[idx + 3] == ':': + res.second, res.microsecond = self._parsems(tokens[idx + 4]) + + idx += 2 + + idx += 2 + + elif idx + 1 < len_l and tokens[idx + 1] in ('-', '/', '.'): + sep = tokens[idx + 1] + ymd.append(value_repr) + + if idx + 2 < len_l and not info.jump(tokens[idx + 2]): + if tokens[idx + 2].isdigit(): + # 01-01[-01] + ymd.append(tokens[idx + 2]) + else: + # 01-Jan[-01] + value = info.month(tokens[idx + 2]) + + if value is not None: + ymd.append(value, 'M') + else: + raise ValueError() + + if idx + 3 < len_l and tokens[idx + 3] == sep: + # We have three members + value = info.month(tokens[idx + 4]) + + if value is not None: + ymd.append(value, 'M') + else: + ymd.append(tokens[idx + 4]) + idx += 2 + + idx += 1 + idx += 1 + + elif idx + 1 >= len_l or info.jump(tokens[idx + 1]): + if idx + 2 < len_l and info.ampm(tokens[idx + 2]) is not None: + # 12 am + hour = int(value) + res.hour = self._adjust_ampm(hour, info.ampm(tokens[idx + 2])) + idx += 1 + else: + # Year, month or day + ymd.append(value) + idx += 1 + + elif info.ampm(tokens[idx + 1]) is not None and (0 <= value < 24): + # 12am + hour = int(value) + res.hour = self._adjust_ampm(hour, info.ampm(tokens[idx + 1])) + idx += 1 + + elif ymd.could_be_day(value): + ymd.append(value) + + elif not fuzzy: + raise ValueError() + + return idx + + def _find_hms_idx(self, idx, tokens, info, allow_jump): + len_l = len(tokens) + + if idx+1 < len_l and info.hms(tokens[idx+1]) is not None: + # There is an "h", "m", or "s" label following this token. We take + # assign the upcoming label to the current token. + # e.g. the "12" in 12h" + hms_idx = idx + 1 + + elif (allow_jump and idx+2 < len_l and tokens[idx+1] == ' ' and + info.hms(tokens[idx+2]) is not None): + # There is a space and then an "h", "m", or "s" label. + # e.g. the "12" in "12 h" + hms_idx = idx + 2 + + elif idx > 0 and info.hms(tokens[idx-1]) is not None: + # There is a "h", "m", or "s" preceding this token. Since neither + # of the previous cases was hit, there is no label following this + # token, so we use the previous label. + # e.g. the "04" in "12h04" + hms_idx = idx-1 + + elif (1 < idx == len_l-1 and tokens[idx-1] == ' ' and + info.hms(tokens[idx-2]) is not None): + # If we are looking at the final token, we allow for a + # backward-looking check to skip over a space. + # TODO: Are we sure this is the right condition here? + hms_idx = idx - 2 + + else: + hms_idx = None + + return hms_idx + + def _assign_hms(self, res, value_repr, hms): + # See GH issue #427, fixing float rounding + value = self._to_decimal(value_repr) + + if hms == 0: + # Hour + res.hour = int(value) + if value % 1: + res.minute = int(60*(value % 1)) + + elif hms == 1: + (res.minute, res.second) = self._parse_min_sec(value) + + elif hms == 2: + (res.second, res.microsecond) = self._parsems(value_repr) + + def _could_be_tzname(self, hour, tzname, tzoffset, token): + return (hour is not None and + tzname is None and + tzoffset is None and + len(token) <= 5 and + (all(x in string.ascii_uppercase for x in token) + or token in self.info.UTCZONE)) + + def _ampm_valid(self, hour, ampm, fuzzy): + """ + For fuzzy parsing, 'a' or 'am' (both valid English words) + may erroneously trigger the AM/PM flag. Deal with that + here. + """ + val_is_ampm = True + + # If there's already an AM/PM flag, this one isn't one. + if fuzzy and ampm is not None: + val_is_ampm = False + + # If AM/PM is found and hour is not, raise a ValueError + if hour is None: + if fuzzy: + val_is_ampm = False + else: + raise ValueError('No hour specified with AM or PM flag.') + elif not 0 <= hour <= 12: + # If AM/PM is found, it's a 12 hour clock, so raise + # an error for invalid range + if fuzzy: + val_is_ampm = False + else: + raise ValueError('Invalid hour specified for 12-hour clock.') + + return val_is_ampm + + def _adjust_ampm(self, hour, ampm): + if hour < 12 and ampm == 1: + hour += 12 + elif hour == 12 and ampm == 0: + hour = 0 + return hour + + def _parse_min_sec(self, value): + # TODO: Every usage of this function sets res.second to the return + # value. Are there any cases where second will be returned as None and + # we *don't* want to set res.second = None? + minute = int(value) + second = None + + sec_remainder = value % 1 + if sec_remainder: + second = int(60 * sec_remainder) + return (minute, second) + + def _parse_hms(self, idx, tokens, info, hms_idx): + # TODO: Is this going to admit a lot of false-positives for when we + # just happen to have digits and "h", "m" or "s" characters in non-date + # text? I guess hex hashes won't have that problem, but there's plenty + # of random junk out there. + if hms_idx is None: + hms = None + new_idx = idx + elif hms_idx > idx: + hms = info.hms(tokens[hms_idx]) + new_idx = hms_idx + else: + # Looking backwards, increment one. + hms = info.hms(tokens[hms_idx]) + 1 + new_idx = idx + + return (new_idx, hms) + + # ------------------------------------------------------------------ + # Handling for individual tokens. These are kept as methods instead + # of functions for the sake of customizability via subclassing. + + def _parsems(self, value): + """Parse a I[.F] seconds value into (seconds, microseconds).""" + if "." not in value: + return int(value), 0 + else: + i, f = value.split(".") + return int(i), int(f.ljust(6, "0")[:6]) + + def _to_decimal(self, val): + try: + decimal_value = Decimal(val) + # See GH 662, edge case, infinite value should not be converted + # via `_to_decimal` + if not decimal_value.is_finite(): + raise ValueError("Converted decimal value is infinite or NaN") + except Exception as e: + msg = "Could not convert %s to decimal" % val + six.raise_from(ValueError(msg), e) + else: + return decimal_value + + # ------------------------------------------------------------------ + # Post-Parsing construction of datetime output. These are kept as + # methods instead of functions for the sake of customizability via + # subclassing. + + def _build_tzinfo(self, tzinfos, tzname, tzoffset): + if callable(tzinfos): + tzdata = tzinfos(tzname, tzoffset) + else: + tzdata = tzinfos.get(tzname) + # handle case where tzinfo is paased an options that returns None + # eg tzinfos = {'BRST' : None} + if isinstance(tzdata, datetime.tzinfo) or tzdata is None: + tzinfo = tzdata + elif isinstance(tzdata, text_type): + tzinfo = tz.tzstr(tzdata) + elif isinstance(tzdata, integer_types): + tzinfo = tz.tzoffset(tzname, tzdata) + else: + raise TypeError("Offset must be tzinfo subclass, tz string, " + "or int offset.") + return tzinfo + + def _build_tzaware(self, naive, res, tzinfos): + if (callable(tzinfos) or (tzinfos and res.tzname in tzinfos)): + tzinfo = self._build_tzinfo(tzinfos, res.tzname, res.tzoffset) + aware = naive.replace(tzinfo=tzinfo) + aware = self._assign_tzname(aware, res.tzname) + + elif res.tzname and res.tzname in time.tzname: + aware = naive.replace(tzinfo=tz.tzlocal()) + + # Handle ambiguous local datetime + aware = self._assign_tzname(aware, res.tzname) + + # This is mostly relevant for winter GMT zones parsed in the UK + if (aware.tzname() != res.tzname and + res.tzname in self.info.UTCZONE): + aware = aware.replace(tzinfo=tz.UTC) + + elif res.tzoffset == 0: + aware = naive.replace(tzinfo=tz.UTC) + + elif res.tzoffset: + aware = naive.replace(tzinfo=tz.tzoffset(res.tzname, res.tzoffset)) + + elif not res.tzname and not res.tzoffset: + # i.e. no timezone information was found. + aware = naive + + elif res.tzname: + # tz-like string was parsed but we don't know what to do + # with it + warnings.warn("tzname {tzname} identified but not understood. " + "Pass `tzinfos` argument in order to correctly " + "return a timezone-aware datetime. In a future " + "version, this will raise an " + "exception.".format(tzname=res.tzname), + category=UnknownTimezoneWarning) + aware = naive + + return aware + + def _build_naive(self, res, default): + repl = {} + for attr in ("year", "month", "day", "hour", + "minute", "second", "microsecond"): + value = getattr(res, attr) + if value is not None: + repl[attr] = value + + if 'day' not in repl: + # If the default day exceeds the last day of the month, fall back + # to the end of the month. + cyear = default.year if res.year is None else res.year + cmonth = default.month if res.month is None else res.month + cday = default.day if res.day is None else res.day + + if cday > monthrange(cyear, cmonth)[1]: + repl['day'] = monthrange(cyear, cmonth)[1] + + naive = default.replace(**repl) + + if res.weekday is not None and not res.day: + naive = naive + relativedelta.relativedelta(weekday=res.weekday) + + return naive + + def _assign_tzname(self, dt, tzname): + if dt.tzname() != tzname: + new_dt = tz.enfold(dt, fold=1) + if new_dt.tzname() == tzname: + return new_dt + + return dt + + def _recombine_skipped(self, tokens, skipped_idxs): + """ + >>> tokens = ["foo", " ", "bar", " ", "19June2000", "baz"] + >>> skipped_idxs = [0, 1, 2, 5] + >>> _recombine_skipped(tokens, skipped_idxs) + ["foo bar", "baz"] + """ + skipped_tokens = [] + for i, idx in enumerate(sorted(skipped_idxs)): + if i > 0 and idx - 1 == skipped_idxs[i - 1]: + skipped_tokens[-1] = skipped_tokens[-1] + tokens[idx] + else: + skipped_tokens.append(tokens[idx]) + + return skipped_tokens + + +DEFAULTPARSER = parser() + + +def parse(timestr, parserinfo=None, **kwargs): + """ + + Parse a string in one of the supported formats, using the + ``parserinfo`` parameters. + + :param timestr: + A string containing a date/time stamp. + + :param parserinfo: + A :class:`parserinfo` object containing parameters for the parser. + If ``None``, the default arguments to the :class:`parserinfo` + constructor are used. + + The ``**kwargs`` parameter takes the following keyword arguments: + + :param default: + The default datetime object, if this is a datetime object and not + ``None``, elements specified in ``timestr`` replace elements in the + default object. + + :param ignoretz: + If set ``True``, time zones in parsed strings are ignored and a naive + :class:`datetime` object is returned. + + :param tzinfos: + Additional time zone names / aliases which may be present in the + string. This argument maps time zone names (and optionally offsets + from those time zones) to time zones. This parameter can be a + dictionary with timezone aliases mapping time zone names to time + zones or a function taking two parameters (``tzname`` and + ``tzoffset``) and returning a time zone. + + The timezones to which the names are mapped can be an integer + offset from UTC in seconds or a :class:`tzinfo` object. + + .. doctest:: + :options: +NORMALIZE_WHITESPACE + + >>> from dateutil.parser import parse + >>> from dateutil.tz import gettz + >>> tzinfos = {"BRST": -7200, "CST": gettz("America/Chicago")} + >>> parse("2012-01-19 17:21:00 BRST", tzinfos=tzinfos) + datetime.datetime(2012, 1, 19, 17, 21, tzinfo=tzoffset(u'BRST', -7200)) + >>> parse("2012-01-19 17:21:00 CST", tzinfos=tzinfos) + datetime.datetime(2012, 1, 19, 17, 21, + tzinfo=tzfile('/usr/share/zoneinfo/America/Chicago')) + + This parameter is ignored if ``ignoretz`` is set. + + :param dayfirst: + Whether to interpret the first value in an ambiguous 3-integer date + (e.g. 01/05/09) as the day (``True``) or month (``False``). If + ``yearfirst`` is set to ``True``, this distinguishes between YDM and + YMD. If set to ``None``, this value is retrieved from the current + :class:`parserinfo` object (which itself defaults to ``False``). + + :param yearfirst: + Whether to interpret the first value in an ambiguous 3-integer date + (e.g. 01/05/09) as the year. If ``True``, the first number is taken to + be the year, otherwise the last number is taken to be the year. If + this is set to ``None``, the value is retrieved from the current + :class:`parserinfo` object (which itself defaults to ``False``). + + :param fuzzy: + Whether to allow fuzzy parsing, allowing for string like "Today is + January 1, 2047 at 8:21:00AM". + + :param fuzzy_with_tokens: + If ``True``, ``fuzzy`` is automatically set to True, and the parser + will return a tuple where the first element is the parsed + :class:`datetime.datetime` datetimestamp and the second element is + a tuple containing the portions of the string which were ignored: + + .. doctest:: + + >>> from dateutil.parser import parse + >>> parse("Today is January 1, 2047 at 8:21:00AM", fuzzy_with_tokens=True) + (datetime.datetime(2047, 1, 1, 8, 21), (u'Today is ', u' ', u'at ')) + + :return: + Returns a :class:`datetime.datetime` object or, if the + ``fuzzy_with_tokens`` option is ``True``, returns a tuple, the + first element being a :class:`datetime.datetime` object, the second + a tuple containing the fuzzy tokens. + + :raises ParserError: + Raised for invalid or unknown string formats, if the provided + :class:`tzinfo` is not in a valid format, or if an invalid date would + be created. + + :raises OverflowError: + Raised if the parsed date exceeds the largest valid C integer on + your system. + """ + if parserinfo: + return parser(parserinfo).parse(timestr, **kwargs) + else: + return DEFAULTPARSER.parse(timestr, **kwargs) + + +class _tzparser(object): + + class _result(_resultbase): + + __slots__ = ["stdabbr", "stdoffset", "dstabbr", "dstoffset", + "start", "end"] + + class _attr(_resultbase): + __slots__ = ["month", "week", "weekday", + "yday", "jyday", "day", "time"] + + def __repr__(self): + return self._repr("") + + def __init__(self): + _resultbase.__init__(self) + self.start = self._attr() + self.end = self._attr() + + def parse(self, tzstr): + res = self._result() + l = [x for x in re.split(r'([,:.]|[a-zA-Z]+|[0-9]+)',tzstr) if x] + used_idxs = list() + try: + + len_l = len(l) + + i = 0 + while i < len_l: + # BRST+3[BRDT[+2]] + j = i + while j < len_l and not [x for x in l[j] + if x in "0123456789:,-+"]: + j += 1 + if j != i: + if not res.stdabbr: + offattr = "stdoffset" + res.stdabbr = "".join(l[i:j]) + else: + offattr = "dstoffset" + res.dstabbr = "".join(l[i:j]) + + for ii in range(j): + used_idxs.append(ii) + i = j + if (i < len_l and (l[i] in ('+', '-') or l[i][0] in + "0123456789")): + if l[i] in ('+', '-'): + # Yes, that's right. See the TZ variable + # documentation. + signal = (1, -1)[l[i] == '+'] + used_idxs.append(i) + i += 1 + else: + signal = -1 + len_li = len(l[i]) + if len_li == 4: + # -0300 + setattr(res, offattr, (int(l[i][:2]) * 3600 + + int(l[i][2:]) * 60) * signal) + elif i + 1 < len_l and l[i + 1] == ':': + # -03:00 + setattr(res, offattr, + (int(l[i]) * 3600 + + int(l[i + 2]) * 60) * signal) + used_idxs.append(i) + i += 2 + elif len_li <= 2: + # -[0]3 + setattr(res, offattr, + int(l[i][:2]) * 3600 * signal) + else: + return None + used_idxs.append(i) + i += 1 + if res.dstabbr: + break + else: + break + + + if i < len_l: + for j in range(i, len_l): + if l[j] == ';': + l[j] = ',' + + assert l[i] == ',' + + i += 1 + + if i >= len_l: + pass + elif (8 <= l.count(',') <= 9 and + not [y for x in l[i:] if x != ',' + for y in x if y not in "0123456789+-"]): + # GMT0BST,3,0,30,3600,10,0,26,7200[,3600] + for x in (res.start, res.end): + x.month = int(l[i]) + used_idxs.append(i) + i += 2 + if l[i] == '-': + value = int(l[i + 1]) * -1 + used_idxs.append(i) + i += 1 + else: + value = int(l[i]) + used_idxs.append(i) + i += 2 + if value: + x.week = value + x.weekday = (int(l[i]) - 1) % 7 + else: + x.day = int(l[i]) + used_idxs.append(i) + i += 2 + x.time = int(l[i]) + used_idxs.append(i) + i += 2 + if i < len_l: + if l[i] in ('-', '+'): + signal = (-1, 1)[l[i] == "+"] + used_idxs.append(i) + i += 1 + else: + signal = 1 + used_idxs.append(i) + res.dstoffset = (res.stdoffset + int(l[i]) * signal) + + # This was a made-up format that is not in normal use + warn(('Parsed time zone "%s"' % tzstr) + + 'is in a non-standard dateutil-specific format, which ' + + 'is now deprecated; support for parsing this format ' + + 'will be removed in future versions. It is recommended ' + + 'that you switch to a standard format like the GNU ' + + 'TZ variable format.', tz.DeprecatedTzFormatWarning) + elif (l.count(',') == 2 and l[i:].count('/') <= 2 and + not [y for x in l[i:] if x not in (',', '/', 'J', 'M', + '.', '-', ':') + for y in x if y not in "0123456789"]): + for x in (res.start, res.end): + if l[i] == 'J': + # non-leap year day (1 based) + used_idxs.append(i) + i += 1 + x.jyday = int(l[i]) + elif l[i] == 'M': + # month[-.]week[-.]weekday + used_idxs.append(i) + i += 1 + x.month = int(l[i]) + used_idxs.append(i) + i += 1 + assert l[i] in ('-', '.') + used_idxs.append(i) + i += 1 + x.week = int(l[i]) + if x.week == 5: + x.week = -1 + used_idxs.append(i) + i += 1 + assert l[i] in ('-', '.') + used_idxs.append(i) + i += 1 + x.weekday = (int(l[i]) - 1) % 7 + else: + # year day (zero based) + x.yday = int(l[i]) + 1 + + used_idxs.append(i) + i += 1 + + if i < len_l and l[i] == '/': + used_idxs.append(i) + i += 1 + # start time + len_li = len(l[i]) + if len_li == 4: + # -0300 + x.time = (int(l[i][:2]) * 3600 + + int(l[i][2:]) * 60) + elif i + 1 < len_l and l[i + 1] == ':': + # -03:00 + x.time = int(l[i]) * 3600 + int(l[i + 2]) * 60 + used_idxs.append(i) + i += 2 + if i + 1 < len_l and l[i + 1] == ':': + used_idxs.append(i) + i += 2 + x.time += int(l[i]) + elif len_li <= 2: + # -[0]3 + x.time = (int(l[i][:2]) * 3600) + else: + return None + used_idxs.append(i) + i += 1 + + assert i == len_l or l[i] == ',' + + i += 1 + + assert i >= len_l + + except (IndexError, ValueError, AssertionError): + return None + + unused_idxs = set(range(len_l)).difference(used_idxs) + res.any_unused_tokens = not {l[n] for n in unused_idxs}.issubset({",",":"}) + return res + + +DEFAULTTZPARSER = _tzparser() + + +def _parsetz(tzstr): + return DEFAULTTZPARSER.parse(tzstr) + + +class ParserError(ValueError): + """Exception subclass used for any failure to parse a datetime string. + + This is a subclass of :py:exc:`ValueError`, and should be raised any time + earlier versions of ``dateutil`` would have raised ``ValueError``. + + .. versionadded:: 2.8.1 + """ + def __str__(self): + try: + return self.args[0] % self.args[1:] + except (TypeError, IndexError): + return super(ParserError, self).__str__() + + def __repr__(self): + args = ", ".join("'%s'" % arg for arg in self.args) + return "%s(%s)" % (self.__class__.__name__, args) + + +class UnknownTimezoneWarning(RuntimeWarning): + """Raised when the parser finds a timezone it cannot parse into a tzinfo. + + .. versionadded:: 2.7.0 + """ +# vim:ts=4:sw=4:et diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/relativedelta.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/relativedelta.py new file mode 100644 index 0000000000000000000000000000000000000000..cd323a549e0f182541ebcde2d2ea1adfbbd9701e --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/relativedelta.py @@ -0,0 +1,599 @@ +# -*- coding: utf-8 -*- +import datetime +import calendar + +import operator +from math import copysign + +from six import integer_types +from warnings import warn + +from ._common import weekday + +MO, TU, WE, TH, FR, SA, SU = weekdays = tuple(weekday(x) for x in range(7)) + +__all__ = ["relativedelta", "MO", "TU", "WE", "TH", "FR", "SA", "SU"] + + +class relativedelta(object): + """ + The relativedelta type is designed to be applied to an existing datetime and + can replace specific components of that datetime, or represents an interval + of time. + + It is based on the specification of the excellent work done by M.-A. Lemburg + in his + `mx.DateTime `_ extension. + However, notice that this type does *NOT* implement the same algorithm as + his work. Do *NOT* expect it to behave like mx.DateTime's counterpart. + + There are two different ways to build a relativedelta instance. The + first one is passing it two date/datetime classes:: + + relativedelta(datetime1, datetime2) + + The second one is passing it any number of the following keyword arguments:: + + relativedelta(arg1=x,arg2=y,arg3=z...) + + year, month, day, hour, minute, second, microsecond: + Absolute information (argument is singular); adding or subtracting a + relativedelta with absolute information does not perform an arithmetic + operation, but rather REPLACES the corresponding value in the + original datetime with the value(s) in relativedelta. + + years, months, weeks, days, hours, minutes, seconds, microseconds: + Relative information, may be negative (argument is plural); adding + or subtracting a relativedelta with relative information performs + the corresponding arithmetic operation on the original datetime value + with the information in the relativedelta. + + weekday: + One of the weekday instances (MO, TU, etc) available in the + relativedelta module. These instances may receive a parameter N, + specifying the Nth weekday, which could be positive or negative + (like MO(+1) or MO(-2)). Not specifying it is the same as specifying + +1. You can also use an integer, where 0=MO. This argument is always + relative e.g. if the calculated date is already Monday, using MO(1) + or MO(-1) won't change the day. To effectively make it absolute, use + it in combination with the day argument (e.g. day=1, MO(1) for first + Monday of the month). + + leapdays: + Will add given days to the date found, if year is a leap + year, and the date found is post 28 of february. + + yearday, nlyearday: + Set the yearday or the non-leap year day (jump leap days). + These are converted to day/month/leapdays information. + + There are relative and absolute forms of the keyword + arguments. The plural is relative, and the singular is + absolute. For each argument in the order below, the absolute form + is applied first (by setting each attribute to that value) and + then the relative form (by adding the value to the attribute). + + The order of attributes considered when this relativedelta is + added to a datetime is: + + 1. Year + 2. Month + 3. Day + 4. Hours + 5. Minutes + 6. Seconds + 7. Microseconds + + Finally, weekday is applied, using the rule described above. + + For example + + >>> from datetime import datetime + >>> from dateutil.relativedelta import relativedelta, MO + >>> dt = datetime(2018, 4, 9, 13, 37, 0) + >>> delta = relativedelta(hours=25, day=1, weekday=MO(1)) + >>> dt + delta + datetime.datetime(2018, 4, 2, 14, 37) + + First, the day is set to 1 (the first of the month), then 25 hours + are added, to get to the 2nd day and 14th hour, finally the + weekday is applied, but since the 2nd is already a Monday there is + no effect. + + """ + + def __init__(self, dt1=None, dt2=None, + years=0, months=0, days=0, leapdays=0, weeks=0, + hours=0, minutes=0, seconds=0, microseconds=0, + year=None, month=None, day=None, weekday=None, + yearday=None, nlyearday=None, + hour=None, minute=None, second=None, microsecond=None): + + if dt1 and dt2: + # datetime is a subclass of date. So both must be date + if not (isinstance(dt1, datetime.date) and + isinstance(dt2, datetime.date)): + raise TypeError("relativedelta only diffs datetime/date") + + # We allow two dates, or two datetimes, so we coerce them to be + # of the same type + if (isinstance(dt1, datetime.datetime) != + isinstance(dt2, datetime.datetime)): + if not isinstance(dt1, datetime.datetime): + dt1 = datetime.datetime.fromordinal(dt1.toordinal()) + elif not isinstance(dt2, datetime.datetime): + dt2 = datetime.datetime.fromordinal(dt2.toordinal()) + + self.years = 0 + self.months = 0 + self.days = 0 + self.leapdays = 0 + self.hours = 0 + self.minutes = 0 + self.seconds = 0 + self.microseconds = 0 + self.year = None + self.month = None + self.day = None + self.weekday = None + self.hour = None + self.minute = None + self.second = None + self.microsecond = None + self._has_time = 0 + + # Get year / month delta between the two + months = (dt1.year - dt2.year) * 12 + (dt1.month - dt2.month) + self._set_months(months) + + # Remove the year/month delta so the timedelta is just well-defined + # time units (seconds, days and microseconds) + dtm = self.__radd__(dt2) + + # If we've overshot our target, make an adjustment + if dt1 < dt2: + compare = operator.gt + increment = 1 + else: + compare = operator.lt + increment = -1 + + while compare(dt1, dtm): + months += increment + self._set_months(months) + dtm = self.__radd__(dt2) + + # Get the timedelta between the "months-adjusted" date and dt1 + delta = dt1 - dtm + self.seconds = delta.seconds + delta.days * 86400 + self.microseconds = delta.microseconds + else: + # Check for non-integer values in integer-only quantities + if any(x is not None and x != int(x) for x in (years, months)): + raise ValueError("Non-integer years and months are " + "ambiguous and not currently supported.") + + # Relative information + self.years = int(years) + self.months = int(months) + self.days = days + weeks * 7 + self.leapdays = leapdays + self.hours = hours + self.minutes = minutes + self.seconds = seconds + self.microseconds = microseconds + + # Absolute information + self.year = year + self.month = month + self.day = day + self.hour = hour + self.minute = minute + self.second = second + self.microsecond = microsecond + + if any(x is not None and int(x) != x + for x in (year, month, day, hour, + minute, second, microsecond)): + # For now we'll deprecate floats - later it'll be an error. + warn("Non-integer value passed as absolute information. " + + "This is not a well-defined condition and will raise " + + "errors in future versions.", DeprecationWarning) + + if isinstance(weekday, integer_types): + self.weekday = weekdays[weekday] + else: + self.weekday = weekday + + yday = 0 + if nlyearday: + yday = nlyearday + elif yearday: + yday = yearday + if yearday > 59: + self.leapdays = -1 + if yday: + ydayidx = [31, 59, 90, 120, 151, 181, 212, + 243, 273, 304, 334, 366] + for idx, ydays in enumerate(ydayidx): + if yday <= ydays: + self.month = idx+1 + if idx == 0: + self.day = yday + else: + self.day = yday-ydayidx[idx-1] + break + else: + raise ValueError("invalid year day (%d)" % yday) + + self._fix() + + def _fix(self): + if abs(self.microseconds) > 999999: + s = _sign(self.microseconds) + div, mod = divmod(self.microseconds * s, 1000000) + self.microseconds = mod * s + self.seconds += div * s + if abs(self.seconds) > 59: + s = _sign(self.seconds) + div, mod = divmod(self.seconds * s, 60) + self.seconds = mod * s + self.minutes += div * s + if abs(self.minutes) > 59: + s = _sign(self.minutes) + div, mod = divmod(self.minutes * s, 60) + self.minutes = mod * s + self.hours += div * s + if abs(self.hours) > 23: + s = _sign(self.hours) + div, mod = divmod(self.hours * s, 24) + self.hours = mod * s + self.days += div * s + if abs(self.months) > 11: + s = _sign(self.months) + div, mod = divmod(self.months * s, 12) + self.months = mod * s + self.years += div * s + if (self.hours or self.minutes or self.seconds or self.microseconds + or self.hour is not None or self.minute is not None or + self.second is not None or self.microsecond is not None): + self._has_time = 1 + else: + self._has_time = 0 + + @property + def weeks(self): + return int(self.days / 7.0) + + @weeks.setter + def weeks(self, value): + self.days = self.days - (self.weeks * 7) + value * 7 + + def _set_months(self, months): + self.months = months + if abs(self.months) > 11: + s = _sign(self.months) + div, mod = divmod(self.months * s, 12) + self.months = mod * s + self.years = div * s + else: + self.years = 0 + + def normalized(self): + """ + Return a version of this object represented entirely using integer + values for the relative attributes. + + >>> relativedelta(days=1.5, hours=2).normalized() + relativedelta(days=+1, hours=+14) + + :return: + Returns a :class:`dateutil.relativedelta.relativedelta` object. + """ + # Cascade remainders down (rounding each to roughly nearest microsecond) + days = int(self.days) + + hours_f = round(self.hours + 24 * (self.days - days), 11) + hours = int(hours_f) + + minutes_f = round(self.minutes + 60 * (hours_f - hours), 10) + minutes = int(minutes_f) + + seconds_f = round(self.seconds + 60 * (minutes_f - minutes), 8) + seconds = int(seconds_f) + + microseconds = round(self.microseconds + 1e6 * (seconds_f - seconds)) + + # Constructor carries overflow back up with call to _fix() + return self.__class__(years=self.years, months=self.months, + days=days, hours=hours, minutes=minutes, + seconds=seconds, microseconds=microseconds, + leapdays=self.leapdays, year=self.year, + month=self.month, day=self.day, + weekday=self.weekday, hour=self.hour, + minute=self.minute, second=self.second, + microsecond=self.microsecond) + + def __add__(self, other): + if isinstance(other, relativedelta): + return self.__class__(years=other.years + self.years, + months=other.months + self.months, + days=other.days + self.days, + hours=other.hours + self.hours, + minutes=other.minutes + self.minutes, + seconds=other.seconds + self.seconds, + microseconds=(other.microseconds + + self.microseconds), + leapdays=other.leapdays or self.leapdays, + year=(other.year if other.year is not None + else self.year), + month=(other.month if other.month is not None + else self.month), + day=(other.day if other.day is not None + else self.day), + weekday=(other.weekday if other.weekday is not None + else self.weekday), + hour=(other.hour if other.hour is not None + else self.hour), + minute=(other.minute if other.minute is not None + else self.minute), + second=(other.second if other.second is not None + else self.second), + microsecond=(other.microsecond if other.microsecond + is not None else + self.microsecond)) + if isinstance(other, datetime.timedelta): + return self.__class__(years=self.years, + months=self.months, + days=self.days + other.days, + hours=self.hours, + minutes=self.minutes, + seconds=self.seconds + other.seconds, + microseconds=self.microseconds + other.microseconds, + leapdays=self.leapdays, + year=self.year, + month=self.month, + day=self.day, + weekday=self.weekday, + hour=self.hour, + minute=self.minute, + second=self.second, + microsecond=self.microsecond) + if not isinstance(other, datetime.date): + return NotImplemented + elif self._has_time and not isinstance(other, datetime.datetime): + other = datetime.datetime.fromordinal(other.toordinal()) + year = (self.year or other.year)+self.years + month = self.month or other.month + if self.months: + assert 1 <= abs(self.months) <= 12 + month += self.months + if month > 12: + year += 1 + month -= 12 + elif month < 1: + year -= 1 + month += 12 + day = min(calendar.monthrange(year, month)[1], + self.day or other.day) + repl = {"year": year, "month": month, "day": day} + for attr in ["hour", "minute", "second", "microsecond"]: + value = getattr(self, attr) + if value is not None: + repl[attr] = value + days = self.days + if self.leapdays and month > 2 and calendar.isleap(year): + days += self.leapdays + ret = (other.replace(**repl) + + datetime.timedelta(days=days, + hours=self.hours, + minutes=self.minutes, + seconds=self.seconds, + microseconds=self.microseconds)) + if self.weekday: + weekday, nth = self.weekday.weekday, self.weekday.n or 1 + jumpdays = (abs(nth) - 1) * 7 + if nth > 0: + jumpdays += (7 - ret.weekday() + weekday) % 7 + else: + jumpdays += (ret.weekday() - weekday) % 7 + jumpdays *= -1 + ret += datetime.timedelta(days=jumpdays) + return ret + + def __radd__(self, other): + return self.__add__(other) + + def __rsub__(self, other): + return self.__neg__().__radd__(other) + + def __sub__(self, other): + if not isinstance(other, relativedelta): + return NotImplemented # In case the other object defines __rsub__ + return self.__class__(years=self.years - other.years, + months=self.months - other.months, + days=self.days - other.days, + hours=self.hours - other.hours, + minutes=self.minutes - other.minutes, + seconds=self.seconds - other.seconds, + microseconds=self.microseconds - other.microseconds, + leapdays=self.leapdays or other.leapdays, + year=(self.year if self.year is not None + else other.year), + month=(self.month if self.month is not None else + other.month), + day=(self.day if self.day is not None else + other.day), + weekday=(self.weekday if self.weekday is not None else + other.weekday), + hour=(self.hour if self.hour is not None else + other.hour), + minute=(self.minute if self.minute is not None else + other.minute), + second=(self.second if self.second is not None else + other.second), + microsecond=(self.microsecond if self.microsecond + is not None else + other.microsecond)) + + def __abs__(self): + return self.__class__(years=abs(self.years), + months=abs(self.months), + days=abs(self.days), + hours=abs(self.hours), + minutes=abs(self.minutes), + seconds=abs(self.seconds), + microseconds=abs(self.microseconds), + leapdays=self.leapdays, + year=self.year, + month=self.month, + day=self.day, + weekday=self.weekday, + hour=self.hour, + minute=self.minute, + second=self.second, + microsecond=self.microsecond) + + def __neg__(self): + return self.__class__(years=-self.years, + months=-self.months, + days=-self.days, + hours=-self.hours, + minutes=-self.minutes, + seconds=-self.seconds, + microseconds=-self.microseconds, + leapdays=self.leapdays, + year=self.year, + month=self.month, + day=self.day, + weekday=self.weekday, + hour=self.hour, + minute=self.minute, + second=self.second, + microsecond=self.microsecond) + + def __bool__(self): + return not (not self.years and + not self.months and + not self.days and + not self.hours and + not self.minutes and + not self.seconds and + not self.microseconds and + not self.leapdays and + self.year is None and + self.month is None and + self.day is None and + self.weekday is None and + self.hour is None and + self.minute is None and + self.second is None and + self.microsecond is None) + # Compatibility with Python 2.x + __nonzero__ = __bool__ + + def __mul__(self, other): + try: + f = float(other) + except TypeError: + return NotImplemented + + return self.__class__(years=int(self.years * f), + months=int(self.months * f), + days=int(self.days * f), + hours=int(self.hours * f), + minutes=int(self.minutes * f), + seconds=int(self.seconds * f), + microseconds=int(self.microseconds * f), + leapdays=self.leapdays, + year=self.year, + month=self.month, + day=self.day, + weekday=self.weekday, + hour=self.hour, + minute=self.minute, + second=self.second, + microsecond=self.microsecond) + + __rmul__ = __mul__ + + def __eq__(self, other): + if not isinstance(other, relativedelta): + return NotImplemented + if self.weekday or other.weekday: + if not self.weekday or not other.weekday: + return False + if self.weekday.weekday != other.weekday.weekday: + return False + n1, n2 = self.weekday.n, other.weekday.n + if n1 != n2 and not ((not n1 or n1 == 1) and (not n2 or n2 == 1)): + return False + return (self.years == other.years and + self.months == other.months and + self.days == other.days and + self.hours == other.hours and + self.minutes == other.minutes and + self.seconds == other.seconds and + self.microseconds == other.microseconds and + self.leapdays == other.leapdays and + self.year == other.year and + self.month == other.month and + self.day == other.day and + self.hour == other.hour and + self.minute == other.minute and + self.second == other.second and + self.microsecond == other.microsecond) + + def __hash__(self): + return hash(( + self.weekday, + self.years, + self.months, + self.days, + self.hours, + self.minutes, + self.seconds, + self.microseconds, + self.leapdays, + self.year, + self.month, + self.day, + self.hour, + self.minute, + self.second, + self.microsecond, + )) + + def __ne__(self, other): + return not self.__eq__(other) + + def __div__(self, other): + try: + reciprocal = 1 / float(other) + except TypeError: + return NotImplemented + + return self.__mul__(reciprocal) + + __truediv__ = __div__ + + def __repr__(self): + l = [] + for attr in ["years", "months", "days", "leapdays", + "hours", "minutes", "seconds", "microseconds"]: + value = getattr(self, attr) + if value: + l.append("{attr}={value:+g}".format(attr=attr, value=value)) + for attr in ["year", "month", "day", "weekday", + "hour", "minute", "second", "microsecond"]: + value = getattr(self, attr) + if value is not None: + l.append("{attr}={value}".format(attr=attr, value=repr(value))) + return "{classname}({attrs})".format(classname=self.__class__.__name__, + attrs=", ".join(l)) + + +def _sign(x): + return int(copysign(1, x)) + +# vim:ts=4:sw=4:et diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/rrule.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/rrule.py new file mode 100644 index 0000000000000000000000000000000000000000..571a0d2bc886a7ea4c06196b2f52e740c2ed6e9f --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/rrule.py @@ -0,0 +1,1737 @@ +# -*- coding: utf-8 -*- +""" +The rrule module offers a small, complete, and very fast, implementation of +the recurrence rules documented in the +`iCalendar RFC `_, +including support for caching of results. +""" +import calendar +import datetime +import heapq +import itertools +import re +import sys +from functools import wraps +# For warning about deprecation of until and count +from warnings import warn + +from six import advance_iterator, integer_types + +from six.moves import _thread, range + +from ._common import weekday as weekdaybase + +try: + from math import gcd +except ImportError: + from fractions import gcd + +__all__ = ["rrule", "rruleset", "rrulestr", + "YEARLY", "MONTHLY", "WEEKLY", "DAILY", + "HOURLY", "MINUTELY", "SECONDLY", + "MO", "TU", "WE", "TH", "FR", "SA", "SU"] + +# Every mask is 7 days longer to handle cross-year weekly periods. +M366MASK = tuple([1]*31+[2]*29+[3]*31+[4]*30+[5]*31+[6]*30 + + [7]*31+[8]*31+[9]*30+[10]*31+[11]*30+[12]*31+[1]*7) +M365MASK = list(M366MASK) +M29, M30, M31 = list(range(1, 30)), list(range(1, 31)), list(range(1, 32)) +MDAY366MASK = tuple(M31+M29+M31+M30+M31+M30+M31+M31+M30+M31+M30+M31+M31[:7]) +MDAY365MASK = list(MDAY366MASK) +M29, M30, M31 = list(range(-29, 0)), list(range(-30, 0)), list(range(-31, 0)) +NMDAY366MASK = tuple(M31+M29+M31+M30+M31+M30+M31+M31+M30+M31+M30+M31+M31[:7]) +NMDAY365MASK = list(NMDAY366MASK) +M366RANGE = (0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366) +M365RANGE = (0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365) +WDAYMASK = [0, 1, 2, 3, 4, 5, 6]*55 +del M29, M30, M31, M365MASK[59], MDAY365MASK[59], NMDAY365MASK[31] +MDAY365MASK = tuple(MDAY365MASK) +M365MASK = tuple(M365MASK) + +FREQNAMES = ['YEARLY', 'MONTHLY', 'WEEKLY', 'DAILY', 'HOURLY', 'MINUTELY', 'SECONDLY'] + +(YEARLY, + MONTHLY, + WEEKLY, + DAILY, + HOURLY, + MINUTELY, + SECONDLY) = list(range(7)) + +# Imported on demand. +easter = None +parser = None + + +class weekday(weekdaybase): + """ + This version of weekday does not allow n = 0. + """ + def __init__(self, wkday, n=None): + if n == 0: + raise ValueError("Can't create weekday with n==0") + + super(weekday, self).__init__(wkday, n) + + +MO, TU, WE, TH, FR, SA, SU = weekdays = tuple(weekday(x) for x in range(7)) + + +def _invalidates_cache(f): + """ + Decorator for rruleset methods which may invalidate the + cached length. + """ + @wraps(f) + def inner_func(self, *args, **kwargs): + rv = f(self, *args, **kwargs) + self._invalidate_cache() + return rv + + return inner_func + + +class rrulebase(object): + def __init__(self, cache=False): + if cache: + self._cache = [] + self._cache_lock = _thread.allocate_lock() + self._invalidate_cache() + else: + self._cache = None + self._cache_complete = False + self._len = None + + def __iter__(self): + if self._cache_complete: + return iter(self._cache) + elif self._cache is None: + return self._iter() + else: + return self._iter_cached() + + def _invalidate_cache(self): + if self._cache is not None: + self._cache = [] + self._cache_complete = False + self._cache_gen = self._iter() + + if self._cache_lock.locked(): + self._cache_lock.release() + + self._len = None + + def _iter_cached(self): + i = 0 + gen = self._cache_gen + cache = self._cache + acquire = self._cache_lock.acquire + release = self._cache_lock.release + while gen: + if i == len(cache): + acquire() + if self._cache_complete: + break + try: + for j in range(10): + cache.append(advance_iterator(gen)) + except StopIteration: + self._cache_gen = gen = None + self._cache_complete = True + break + release() + yield cache[i] + i += 1 + while i < self._len: + yield cache[i] + i += 1 + + def __getitem__(self, item): + if self._cache_complete: + return self._cache[item] + elif isinstance(item, slice): + if item.step and item.step < 0: + return list(iter(self))[item] + else: + return list(itertools.islice(self, + item.start or 0, + item.stop or sys.maxsize, + item.step or 1)) + elif item >= 0: + gen = iter(self) + try: + for i in range(item+1): + res = advance_iterator(gen) + except StopIteration: + raise IndexError + return res + else: + return list(iter(self))[item] + + def __contains__(self, item): + if self._cache_complete: + return item in self._cache + else: + for i in self: + if i == item: + return True + elif i > item: + return False + return False + + # __len__() introduces a large performance penalty. + def count(self): + """ Returns the number of recurrences in this set. It will have go + through the whole recurrence, if this hasn't been done before. """ + if self._len is None: + for x in self: + pass + return self._len + + def before(self, dt, inc=False): + """ Returns the last recurrence before the given datetime instance. The + inc keyword defines what happens if dt is an occurrence. With + inc=True, if dt itself is an occurrence, it will be returned. """ + if self._cache_complete: + gen = self._cache + else: + gen = self + last = None + if inc: + for i in gen: + if i > dt: + break + last = i + else: + for i in gen: + if i >= dt: + break + last = i + return last + + def after(self, dt, inc=False): + """ Returns the first recurrence after the given datetime instance. The + inc keyword defines what happens if dt is an occurrence. With + inc=True, if dt itself is an occurrence, it will be returned. """ + if self._cache_complete: + gen = self._cache + else: + gen = self + if inc: + for i in gen: + if i >= dt: + return i + else: + for i in gen: + if i > dt: + return i + return None + + def xafter(self, dt, count=None, inc=False): + """ + Generator which yields up to `count` recurrences after the given + datetime instance, equivalent to `after`. + + :param dt: + The datetime at which to start generating recurrences. + + :param count: + The maximum number of recurrences to generate. If `None` (default), + dates are generated until the recurrence rule is exhausted. + + :param inc: + If `dt` is an instance of the rule and `inc` is `True`, it is + included in the output. + + :yields: Yields a sequence of `datetime` objects. + """ + + if self._cache_complete: + gen = self._cache + else: + gen = self + + # Select the comparison function + if inc: + comp = lambda dc, dtc: dc >= dtc + else: + comp = lambda dc, dtc: dc > dtc + + # Generate dates + n = 0 + for d in gen: + if comp(d, dt): + if count is not None: + n += 1 + if n > count: + break + + yield d + + def between(self, after, before, inc=False, count=1): + """ Returns all the occurrences of the rrule between after and before. + The inc keyword defines what happens if after and/or before are + themselves occurrences. With inc=True, they will be included in the + list, if they are found in the recurrence set. """ + if self._cache_complete: + gen = self._cache + else: + gen = self + started = False + l = [] + if inc: + for i in gen: + if i > before: + break + elif not started: + if i >= after: + started = True + l.append(i) + else: + l.append(i) + else: + for i in gen: + if i >= before: + break + elif not started: + if i > after: + started = True + l.append(i) + else: + l.append(i) + return l + + +class rrule(rrulebase): + """ + That's the base of the rrule operation. It accepts all the keywords + defined in the RFC as its constructor parameters (except byday, + which was renamed to byweekday) and more. The constructor prototype is:: + + rrule(freq) + + Where freq must be one of YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, + or SECONDLY. + + .. note:: + Per RFC section 3.3.10, recurrence instances falling on invalid dates + and times are ignored rather than coerced: + + Recurrence rules may generate recurrence instances with an invalid + date (e.g., February 30) or nonexistent local time (e.g., 1:30 AM + on a day where the local time is moved forward by an hour at 1:00 + AM). Such recurrence instances MUST be ignored and MUST NOT be + counted as part of the recurrence set. + + This can lead to possibly surprising behavior when, for example, the + start date occurs at the end of the month: + + >>> from dateutil.rrule import rrule, MONTHLY + >>> from datetime import datetime + >>> start_date = datetime(2014, 12, 31) + >>> list(rrule(freq=MONTHLY, count=4, dtstart=start_date)) + ... # doctest: +NORMALIZE_WHITESPACE + [datetime.datetime(2014, 12, 31, 0, 0), + datetime.datetime(2015, 1, 31, 0, 0), + datetime.datetime(2015, 3, 31, 0, 0), + datetime.datetime(2015, 5, 31, 0, 0)] + + Additionally, it supports the following keyword arguments: + + :param dtstart: + The recurrence start. Besides being the base for the recurrence, + missing parameters in the final recurrence instances will also be + extracted from this date. If not given, datetime.now() will be used + instead. + :param interval: + The interval between each freq iteration. For example, when using + YEARLY, an interval of 2 means once every two years, but with HOURLY, + it means once every two hours. The default interval is 1. + :param wkst: + The week start day. Must be one of the MO, TU, WE constants, or an + integer, specifying the first day of the week. This will affect + recurrences based on weekly periods. The default week start is got + from calendar.firstweekday(), and may be modified by + calendar.setfirstweekday(). + :param count: + If given, this determines how many occurrences will be generated. + + .. note:: + As of version 2.5.0, the use of the keyword ``until`` in conjunction + with ``count`` is deprecated, to make sure ``dateutil`` is fully + compliant with `RFC-5545 Sec. 3.3.10 `_. Therefore, ``until`` and ``count`` + **must not** occur in the same call to ``rrule``. + :param until: + If given, this must be a datetime instance specifying the upper-bound + limit of the recurrence. The last recurrence in the rule is the greatest + datetime that is less than or equal to the value specified in the + ``until`` parameter. + + .. note:: + As of version 2.5.0, the use of the keyword ``until`` in conjunction + with ``count`` is deprecated, to make sure ``dateutil`` is fully + compliant with `RFC-5545 Sec. 3.3.10 `_. Therefore, ``until`` and ``count`` + **must not** occur in the same call to ``rrule``. + :param bysetpos: + If given, it must be either an integer, or a sequence of integers, + positive or negative. Each given integer will specify an occurrence + number, corresponding to the nth occurrence of the rule inside the + frequency period. For example, a bysetpos of -1 if combined with a + MONTHLY frequency, and a byweekday of (MO, TU, WE, TH, FR), will + result in the last work day of every month. + :param bymonth: + If given, it must be either an integer, or a sequence of integers, + meaning the months to apply the recurrence to. + :param bymonthday: + If given, it must be either an integer, or a sequence of integers, + meaning the month days to apply the recurrence to. + :param byyearday: + If given, it must be either an integer, or a sequence of integers, + meaning the year days to apply the recurrence to. + :param byeaster: + If given, it must be either an integer, or a sequence of integers, + positive or negative. Each integer will define an offset from the + Easter Sunday. Passing the offset 0 to byeaster will yield the Easter + Sunday itself. This is an extension to the RFC specification. + :param byweekno: + If given, it must be either an integer, or a sequence of integers, + meaning the week numbers to apply the recurrence to. Week numbers + have the meaning described in ISO8601, that is, the first week of + the year is that containing at least four days of the new year. + :param byweekday: + If given, it must be either an integer (0 == MO), a sequence of + integers, one of the weekday constants (MO, TU, etc), or a sequence + of these constants. When given, these variables will define the + weekdays where the recurrence will be applied. It's also possible to + use an argument n for the weekday instances, which will mean the nth + occurrence of this weekday in the period. For example, with MONTHLY, + or with YEARLY and BYMONTH, using FR(+1) in byweekday will specify the + first friday of the month where the recurrence happens. Notice that in + the RFC documentation, this is specified as BYDAY, but was renamed to + avoid the ambiguity of that keyword. + :param byhour: + If given, it must be either an integer, or a sequence of integers, + meaning the hours to apply the recurrence to. + :param byminute: + If given, it must be either an integer, or a sequence of integers, + meaning the minutes to apply the recurrence to. + :param bysecond: + If given, it must be either an integer, or a sequence of integers, + meaning the seconds to apply the recurrence to. + :param cache: + If given, it must be a boolean value specifying to enable or disable + caching of results. If you will use the same rrule instance multiple + times, enabling caching will improve the performance considerably. + """ + def __init__(self, freq, dtstart=None, + interval=1, wkst=None, count=None, until=None, bysetpos=None, + bymonth=None, bymonthday=None, byyearday=None, byeaster=None, + byweekno=None, byweekday=None, + byhour=None, byminute=None, bysecond=None, + cache=False): + super(rrule, self).__init__(cache) + global easter + if not dtstart: + if until and until.tzinfo: + dtstart = datetime.datetime.now(tz=until.tzinfo).replace(microsecond=0) + else: + dtstart = datetime.datetime.now().replace(microsecond=0) + elif not isinstance(dtstart, datetime.datetime): + dtstart = datetime.datetime.fromordinal(dtstart.toordinal()) + else: + dtstart = dtstart.replace(microsecond=0) + self._dtstart = dtstart + self._tzinfo = dtstart.tzinfo + self._freq = freq + self._interval = interval + self._count = count + + # Cache the original byxxx rules, if they are provided, as the _byxxx + # attributes do not necessarily map to the inputs, and this can be + # a problem in generating the strings. Only store things if they've + # been supplied (the string retrieval will just use .get()) + self._original_rule = {} + + if until and not isinstance(until, datetime.datetime): + until = datetime.datetime.fromordinal(until.toordinal()) + self._until = until + + if self._dtstart and self._until: + if (self._dtstart.tzinfo is not None) != (self._until.tzinfo is not None): + # According to RFC5545 Section 3.3.10: + # https://tools.ietf.org/html/rfc5545#section-3.3.10 + # + # > If the "DTSTART" property is specified as a date with UTC + # > time or a date with local time and time zone reference, + # > then the UNTIL rule part MUST be specified as a date with + # > UTC time. + raise ValueError( + 'RRULE UNTIL values must be specified in UTC when DTSTART ' + 'is timezone-aware' + ) + + if count is not None and until: + warn("Using both 'count' and 'until' is inconsistent with RFC 5545" + " and has been deprecated in dateutil. Future versions will " + "raise an error.", DeprecationWarning) + + if wkst is None: + self._wkst = calendar.firstweekday() + elif isinstance(wkst, integer_types): + self._wkst = wkst + else: + self._wkst = wkst.weekday + + if bysetpos is None: + self._bysetpos = None + elif isinstance(bysetpos, integer_types): + if bysetpos == 0 or not (-366 <= bysetpos <= 366): + raise ValueError("bysetpos must be between 1 and 366, " + "or between -366 and -1") + self._bysetpos = (bysetpos,) + else: + self._bysetpos = tuple(bysetpos) + for pos in self._bysetpos: + if pos == 0 or not (-366 <= pos <= 366): + raise ValueError("bysetpos must be between 1 and 366, " + "or between -366 and -1") + + if self._bysetpos: + self._original_rule['bysetpos'] = self._bysetpos + + if (byweekno is None and byyearday is None and bymonthday is None and + byweekday is None and byeaster is None): + if freq == YEARLY: + if bymonth is None: + bymonth = dtstart.month + self._original_rule['bymonth'] = None + bymonthday = dtstart.day + self._original_rule['bymonthday'] = None + elif freq == MONTHLY: + bymonthday = dtstart.day + self._original_rule['bymonthday'] = None + elif freq == WEEKLY: + byweekday = dtstart.weekday() + self._original_rule['byweekday'] = None + + # bymonth + if bymonth is None: + self._bymonth = None + else: + if isinstance(bymonth, integer_types): + bymonth = (bymonth,) + + self._bymonth = tuple(sorted(set(bymonth))) + + if 'bymonth' not in self._original_rule: + self._original_rule['bymonth'] = self._bymonth + + # byyearday + if byyearday is None: + self._byyearday = None + else: + if isinstance(byyearday, integer_types): + byyearday = (byyearday,) + + self._byyearday = tuple(sorted(set(byyearday))) + self._original_rule['byyearday'] = self._byyearday + + # byeaster + if byeaster is not None: + if not easter: + from dateutil import easter + if isinstance(byeaster, integer_types): + self._byeaster = (byeaster,) + else: + self._byeaster = tuple(sorted(byeaster)) + + self._original_rule['byeaster'] = self._byeaster + else: + self._byeaster = None + + # bymonthday + if bymonthday is None: + self._bymonthday = () + self._bynmonthday = () + else: + if isinstance(bymonthday, integer_types): + bymonthday = (bymonthday,) + + bymonthday = set(bymonthday) # Ensure it's unique + + self._bymonthday = tuple(sorted(x for x in bymonthday if x > 0)) + self._bynmonthday = tuple(sorted(x for x in bymonthday if x < 0)) + + # Storing positive numbers first, then negative numbers + if 'bymonthday' not in self._original_rule: + self._original_rule['bymonthday'] = tuple( + itertools.chain(self._bymonthday, self._bynmonthday)) + + # byweekno + if byweekno is None: + self._byweekno = None + else: + if isinstance(byweekno, integer_types): + byweekno = (byweekno,) + + self._byweekno = tuple(sorted(set(byweekno))) + + self._original_rule['byweekno'] = self._byweekno + + # byweekday / bynweekday + if byweekday is None: + self._byweekday = None + self._bynweekday = None + else: + # If it's one of the valid non-sequence types, convert to a + # single-element sequence before the iterator that builds the + # byweekday set. + if isinstance(byweekday, integer_types) or hasattr(byweekday, "n"): + byweekday = (byweekday,) + + self._byweekday = set() + self._bynweekday = set() + for wday in byweekday: + if isinstance(wday, integer_types): + self._byweekday.add(wday) + elif not wday.n or freq > MONTHLY: + self._byweekday.add(wday.weekday) + else: + self._bynweekday.add((wday.weekday, wday.n)) + + if not self._byweekday: + self._byweekday = None + elif not self._bynweekday: + self._bynweekday = None + + if self._byweekday is not None: + self._byweekday = tuple(sorted(self._byweekday)) + orig_byweekday = [weekday(x) for x in self._byweekday] + else: + orig_byweekday = () + + if self._bynweekday is not None: + self._bynweekday = tuple(sorted(self._bynweekday)) + orig_bynweekday = [weekday(*x) for x in self._bynweekday] + else: + orig_bynweekday = () + + if 'byweekday' not in self._original_rule: + self._original_rule['byweekday'] = tuple(itertools.chain( + orig_byweekday, orig_bynweekday)) + + # byhour + if byhour is None: + if freq < HOURLY: + self._byhour = {dtstart.hour} + else: + self._byhour = None + else: + if isinstance(byhour, integer_types): + byhour = (byhour,) + + if freq == HOURLY: + self._byhour = self.__construct_byset(start=dtstart.hour, + byxxx=byhour, + base=24) + else: + self._byhour = set(byhour) + + self._byhour = tuple(sorted(self._byhour)) + self._original_rule['byhour'] = self._byhour + + # byminute + if byminute is None: + if freq < MINUTELY: + self._byminute = {dtstart.minute} + else: + self._byminute = None + else: + if isinstance(byminute, integer_types): + byminute = (byminute,) + + if freq == MINUTELY: + self._byminute = self.__construct_byset(start=dtstart.minute, + byxxx=byminute, + base=60) + else: + self._byminute = set(byminute) + + self._byminute = tuple(sorted(self._byminute)) + self._original_rule['byminute'] = self._byminute + + # bysecond + if bysecond is None: + if freq < SECONDLY: + self._bysecond = ((dtstart.second,)) + else: + self._bysecond = None + else: + if isinstance(bysecond, integer_types): + bysecond = (bysecond,) + + self._bysecond = set(bysecond) + + if freq == SECONDLY: + self._bysecond = self.__construct_byset(start=dtstart.second, + byxxx=bysecond, + base=60) + else: + self._bysecond = set(bysecond) + + self._bysecond = tuple(sorted(self._bysecond)) + self._original_rule['bysecond'] = self._bysecond + + if self._freq >= HOURLY: + self._timeset = None + else: + self._timeset = [] + for hour in self._byhour: + for minute in self._byminute: + for second in self._bysecond: + self._timeset.append( + datetime.time(hour, minute, second, + tzinfo=self._tzinfo)) + self._timeset.sort() + self._timeset = tuple(self._timeset) + + def __str__(self): + """ + Output a string that would generate this RRULE if passed to rrulestr. + This is mostly compatible with RFC5545, except for the + dateutil-specific extension BYEASTER. + """ + + output = [] + h, m, s = [None] * 3 + if self._dtstart: + output.append(self._dtstart.strftime('DTSTART:%Y%m%dT%H%M%S')) + h, m, s = self._dtstart.timetuple()[3:6] + + parts = ['FREQ=' + FREQNAMES[self._freq]] + if self._interval != 1: + parts.append('INTERVAL=' + str(self._interval)) + + if self._wkst: + parts.append('WKST=' + repr(weekday(self._wkst))[0:2]) + + if self._count is not None: + parts.append('COUNT=' + str(self._count)) + + if self._until: + parts.append(self._until.strftime('UNTIL=%Y%m%dT%H%M%S')) + + if self._original_rule.get('byweekday') is not None: + # The str() method on weekday objects doesn't generate + # RFC5545-compliant strings, so we should modify that. + original_rule = dict(self._original_rule) + wday_strings = [] + for wday in original_rule['byweekday']: + if wday.n: + wday_strings.append('{n:+d}{wday}'.format( + n=wday.n, + wday=repr(wday)[0:2])) + else: + wday_strings.append(repr(wday)) + + original_rule['byweekday'] = wday_strings + else: + original_rule = self._original_rule + + partfmt = '{name}={vals}' + for name, key in [('BYSETPOS', 'bysetpos'), + ('BYMONTH', 'bymonth'), + ('BYMONTHDAY', 'bymonthday'), + ('BYYEARDAY', 'byyearday'), + ('BYWEEKNO', 'byweekno'), + ('BYDAY', 'byweekday'), + ('BYHOUR', 'byhour'), + ('BYMINUTE', 'byminute'), + ('BYSECOND', 'bysecond'), + ('BYEASTER', 'byeaster')]: + value = original_rule.get(key) + if value: + parts.append(partfmt.format(name=name, vals=(','.join(str(v) + for v in value)))) + + output.append('RRULE:' + ';'.join(parts)) + return '\n'.join(output) + + def replace(self, **kwargs): + """Return new rrule with same attributes except for those attributes given new + values by whichever keyword arguments are specified.""" + new_kwargs = {"interval": self._interval, + "count": self._count, + "dtstart": self._dtstart, + "freq": self._freq, + "until": self._until, + "wkst": self._wkst, + "cache": False if self._cache is None else True } + new_kwargs.update(self._original_rule) + new_kwargs.update(kwargs) + return rrule(**new_kwargs) + + def _iter(self): + year, month, day, hour, minute, second, weekday, yearday, _ = \ + self._dtstart.timetuple() + + # Some local variables to speed things up a bit + freq = self._freq + interval = self._interval + wkst = self._wkst + until = self._until + bymonth = self._bymonth + byweekno = self._byweekno + byyearday = self._byyearday + byweekday = self._byweekday + byeaster = self._byeaster + bymonthday = self._bymonthday + bynmonthday = self._bynmonthday + bysetpos = self._bysetpos + byhour = self._byhour + byminute = self._byminute + bysecond = self._bysecond + + ii = _iterinfo(self) + ii.rebuild(year, month) + + getdayset = {YEARLY: ii.ydayset, + MONTHLY: ii.mdayset, + WEEKLY: ii.wdayset, + DAILY: ii.ddayset, + HOURLY: ii.ddayset, + MINUTELY: ii.ddayset, + SECONDLY: ii.ddayset}[freq] + + if freq < HOURLY: + timeset = self._timeset + else: + gettimeset = {HOURLY: ii.htimeset, + MINUTELY: ii.mtimeset, + SECONDLY: ii.stimeset}[freq] + if ((freq >= HOURLY and + self._byhour and hour not in self._byhour) or + (freq >= MINUTELY and + self._byminute and minute not in self._byminute) or + (freq >= SECONDLY and + self._bysecond and second not in self._bysecond)): + timeset = () + else: + timeset = gettimeset(hour, minute, second) + + total = 0 + count = self._count + while True: + # Get dayset with the right frequency + dayset, start, end = getdayset(year, month, day) + + # Do the "hard" work ;-) + filtered = False + for i in dayset[start:end]: + if ((bymonth and ii.mmask[i] not in bymonth) or + (byweekno and not ii.wnomask[i]) or + (byweekday and ii.wdaymask[i] not in byweekday) or + (ii.nwdaymask and not ii.nwdaymask[i]) or + (byeaster and not ii.eastermask[i]) or + ((bymonthday or bynmonthday) and + ii.mdaymask[i] not in bymonthday and + ii.nmdaymask[i] not in bynmonthday) or + (byyearday and + ((i < ii.yearlen and i+1 not in byyearday and + -ii.yearlen+i not in byyearday) or + (i >= ii.yearlen and i+1-ii.yearlen not in byyearday and + -ii.nextyearlen+i-ii.yearlen not in byyearday)))): + dayset[i] = None + filtered = True + + # Output results + if bysetpos and timeset: + poslist = [] + for pos in bysetpos: + if pos < 0: + daypos, timepos = divmod(pos, len(timeset)) + else: + daypos, timepos = divmod(pos-1, len(timeset)) + try: + i = [x for x in dayset[start:end] + if x is not None][daypos] + time = timeset[timepos] + except IndexError: + pass + else: + date = datetime.date.fromordinal(ii.yearordinal+i) + res = datetime.datetime.combine(date, time) + if res not in poslist: + poslist.append(res) + poslist.sort() + for res in poslist: + if until and res > until: + self._len = total + return + elif res >= self._dtstart: + if count is not None: + count -= 1 + if count < 0: + self._len = total + return + total += 1 + yield res + else: + for i in dayset[start:end]: + if i is not None: + date = datetime.date.fromordinal(ii.yearordinal + i) + for time in timeset: + res = datetime.datetime.combine(date, time) + if until and res > until: + self._len = total + return + elif res >= self._dtstart: + if count is not None: + count -= 1 + if count < 0: + self._len = total + return + + total += 1 + yield res + + # Handle frequency and interval + fixday = False + if freq == YEARLY: + year += interval + if year > datetime.MAXYEAR: + self._len = total + return + ii.rebuild(year, month) + elif freq == MONTHLY: + month += interval + if month > 12: + div, mod = divmod(month, 12) + month = mod + year += div + if month == 0: + month = 12 + year -= 1 + if year > datetime.MAXYEAR: + self._len = total + return + ii.rebuild(year, month) + elif freq == WEEKLY: + if wkst > weekday: + day += -(weekday+1+(6-wkst))+self._interval*7 + else: + day += -(weekday-wkst)+self._interval*7 + weekday = wkst + fixday = True + elif freq == DAILY: + day += interval + fixday = True + elif freq == HOURLY: + if filtered: + # Jump to one iteration before next day + hour += ((23-hour)//interval)*interval + + if byhour: + ndays, hour = self.__mod_distance(value=hour, + byxxx=self._byhour, + base=24) + else: + ndays, hour = divmod(hour+interval, 24) + + if ndays: + day += ndays + fixday = True + + timeset = gettimeset(hour, minute, second) + elif freq == MINUTELY: + if filtered: + # Jump to one iteration before next day + minute += ((1439-(hour*60+minute))//interval)*interval + + valid = False + rep_rate = (24*60) + for j in range(rep_rate // gcd(interval, rep_rate)): + if byminute: + nhours, minute = \ + self.__mod_distance(value=minute, + byxxx=self._byminute, + base=60) + else: + nhours, minute = divmod(minute+interval, 60) + + div, hour = divmod(hour+nhours, 24) + if div: + day += div + fixday = True + filtered = False + + if not byhour or hour in byhour: + valid = True + break + + if not valid: + raise ValueError('Invalid combination of interval and ' + + 'byhour resulting in empty rule.') + + timeset = gettimeset(hour, minute, second) + elif freq == SECONDLY: + if filtered: + # Jump to one iteration before next day + second += (((86399 - (hour * 3600 + minute * 60 + second)) + // interval) * interval) + + rep_rate = (24 * 3600) + valid = False + for j in range(0, rep_rate // gcd(interval, rep_rate)): + if bysecond: + nminutes, second = \ + self.__mod_distance(value=second, + byxxx=self._bysecond, + base=60) + else: + nminutes, second = divmod(second+interval, 60) + + div, minute = divmod(minute+nminutes, 60) + if div: + hour += div + div, hour = divmod(hour, 24) + if div: + day += div + fixday = True + + if ((not byhour or hour in byhour) and + (not byminute or minute in byminute) and + (not bysecond or second in bysecond)): + valid = True + break + + if not valid: + raise ValueError('Invalid combination of interval, ' + + 'byhour and byminute resulting in empty' + + ' rule.') + + timeset = gettimeset(hour, minute, second) + + if fixday and day > 28: + daysinmonth = calendar.monthrange(year, month)[1] + if day > daysinmonth: + while day > daysinmonth: + day -= daysinmonth + month += 1 + if month == 13: + month = 1 + year += 1 + if year > datetime.MAXYEAR: + self._len = total + return + daysinmonth = calendar.monthrange(year, month)[1] + ii.rebuild(year, month) + + def __construct_byset(self, start, byxxx, base): + """ + If a `BYXXX` sequence is passed to the constructor at the same level as + `FREQ` (e.g. `FREQ=HOURLY,BYHOUR={2,4,7},INTERVAL=3`), there are some + specifications which cannot be reached given some starting conditions. + + This occurs whenever the interval is not coprime with the base of a + given unit and the difference between the starting position and the + ending position is not coprime with the greatest common denominator + between the interval and the base. For example, with a FREQ of hourly + starting at 17:00 and an interval of 4, the only valid values for + BYHOUR would be {21, 1, 5, 9, 13, 17}, because 4 and 24 are not + coprime. + + :param start: + Specifies the starting position. + :param byxxx: + An iterable containing the list of allowed values. + :param base: + The largest allowable value for the specified frequency (e.g. + 24 hours, 60 minutes). + + This does not preserve the type of the iterable, returning a set, since + the values should be unique and the order is irrelevant, this will + speed up later lookups. + + In the event of an empty set, raises a :exception:`ValueError`, as this + results in an empty rrule. + """ + + cset = set() + + # Support a single byxxx value. + if isinstance(byxxx, integer_types): + byxxx = (byxxx, ) + + for num in byxxx: + i_gcd = gcd(self._interval, base) + # Use divmod rather than % because we need to wrap negative nums. + if i_gcd == 1 or divmod(num - start, i_gcd)[1] == 0: + cset.add(num) + + if len(cset) == 0: + raise ValueError("Invalid rrule byxxx generates an empty set.") + + return cset + + def __mod_distance(self, value, byxxx, base): + """ + Calculates the next value in a sequence where the `FREQ` parameter is + specified along with a `BYXXX` parameter at the same "level" + (e.g. `HOURLY` specified with `BYHOUR`). + + :param value: + The old value of the component. + :param byxxx: + The `BYXXX` set, which should have been generated by + `rrule._construct_byset`, or something else which checks that a + valid rule is present. + :param base: + The largest allowable value for the specified frequency (e.g. + 24 hours, 60 minutes). + + If a valid value is not found after `base` iterations (the maximum + number before the sequence would start to repeat), this raises a + :exception:`ValueError`, as no valid values were found. + + This returns a tuple of `divmod(n*interval, base)`, where `n` is the + smallest number of `interval` repetitions until the next specified + value in `byxxx` is found. + """ + accumulator = 0 + for ii in range(1, base + 1): + # Using divmod() over % to account for negative intervals + div, value = divmod(value + self._interval, base) + accumulator += div + if value in byxxx: + return (accumulator, value) + + +class _iterinfo(object): + __slots__ = ["rrule", "lastyear", "lastmonth", + "yearlen", "nextyearlen", "yearordinal", "yearweekday", + "mmask", "mrange", "mdaymask", "nmdaymask", + "wdaymask", "wnomask", "nwdaymask", "eastermask"] + + def __init__(self, rrule): + for attr in self.__slots__: + setattr(self, attr, None) + self.rrule = rrule + + def rebuild(self, year, month): + # Every mask is 7 days longer to handle cross-year weekly periods. + rr = self.rrule + if year != self.lastyear: + self.yearlen = 365 + calendar.isleap(year) + self.nextyearlen = 365 + calendar.isleap(year + 1) + firstyday = datetime.date(year, 1, 1) + self.yearordinal = firstyday.toordinal() + self.yearweekday = firstyday.weekday() + + wday = datetime.date(year, 1, 1).weekday() + if self.yearlen == 365: + self.mmask = M365MASK + self.mdaymask = MDAY365MASK + self.nmdaymask = NMDAY365MASK + self.wdaymask = WDAYMASK[wday:] + self.mrange = M365RANGE + else: + self.mmask = M366MASK + self.mdaymask = MDAY366MASK + self.nmdaymask = NMDAY366MASK + self.wdaymask = WDAYMASK[wday:] + self.mrange = M366RANGE + + if not rr._byweekno: + self.wnomask = None + else: + self.wnomask = [0]*(self.yearlen+7) + # no1wkst = firstwkst = self.wdaymask.index(rr._wkst) + no1wkst = firstwkst = (7-self.yearweekday+rr._wkst) % 7 + if no1wkst >= 4: + no1wkst = 0 + # Number of days in the year, plus the days we got + # from last year. + wyearlen = self.yearlen+(self.yearweekday-rr._wkst) % 7 + else: + # Number of days in the year, minus the days we + # left in last year. + wyearlen = self.yearlen-no1wkst + div, mod = divmod(wyearlen, 7) + numweeks = div+mod//4 + for n in rr._byweekno: + if n < 0: + n += numweeks+1 + if not (0 < n <= numweeks): + continue + if n > 1: + i = no1wkst+(n-1)*7 + if no1wkst != firstwkst: + i -= 7-firstwkst + else: + i = no1wkst + for j in range(7): + self.wnomask[i] = 1 + i += 1 + if self.wdaymask[i] == rr._wkst: + break + if 1 in rr._byweekno: + # Check week number 1 of next year as well + # TODO: Check -numweeks for next year. + i = no1wkst+numweeks*7 + if no1wkst != firstwkst: + i -= 7-firstwkst + if i < self.yearlen: + # If week starts in next year, we + # don't care about it. + for j in range(7): + self.wnomask[i] = 1 + i += 1 + if self.wdaymask[i] == rr._wkst: + break + if no1wkst: + # Check last week number of last year as + # well. If no1wkst is 0, either the year + # started on week start, or week number 1 + # got days from last year, so there are no + # days from last year's last week number in + # this year. + if -1 not in rr._byweekno: + lyearweekday = datetime.date(year-1, 1, 1).weekday() + lno1wkst = (7-lyearweekday+rr._wkst) % 7 + lyearlen = 365+calendar.isleap(year-1) + if lno1wkst >= 4: + lno1wkst = 0 + lnumweeks = 52+(lyearlen + + (lyearweekday-rr._wkst) % 7) % 7//4 + else: + lnumweeks = 52+(self.yearlen-no1wkst) % 7//4 + else: + lnumweeks = -1 + if lnumweeks in rr._byweekno: + for i in range(no1wkst): + self.wnomask[i] = 1 + + if (rr._bynweekday and (month != self.lastmonth or + year != self.lastyear)): + ranges = [] + if rr._freq == YEARLY: + if rr._bymonth: + for month in rr._bymonth: + ranges.append(self.mrange[month-1:month+1]) + else: + ranges = [(0, self.yearlen)] + elif rr._freq == MONTHLY: + ranges = [self.mrange[month-1:month+1]] + if ranges: + # Weekly frequency won't get here, so we may not + # care about cross-year weekly periods. + self.nwdaymask = [0]*self.yearlen + for first, last in ranges: + last -= 1 + for wday, n in rr._bynweekday: + if n < 0: + i = last+(n+1)*7 + i -= (self.wdaymask[i]-wday) % 7 + else: + i = first+(n-1)*7 + i += (7-self.wdaymask[i]+wday) % 7 + if first <= i <= last: + self.nwdaymask[i] = 1 + + if rr._byeaster: + self.eastermask = [0]*(self.yearlen+7) + eyday = easter.easter(year).toordinal()-self.yearordinal + for offset in rr._byeaster: + self.eastermask[eyday+offset] = 1 + + self.lastyear = year + self.lastmonth = month + + def ydayset(self, year, month, day): + return list(range(self.yearlen)), 0, self.yearlen + + def mdayset(self, year, month, day): + dset = [None]*self.yearlen + start, end = self.mrange[month-1:month+1] + for i in range(start, end): + dset[i] = i + return dset, start, end + + def wdayset(self, year, month, day): + # We need to handle cross-year weeks here. + dset = [None]*(self.yearlen+7) + i = datetime.date(year, month, day).toordinal()-self.yearordinal + start = i + for j in range(7): + dset[i] = i + i += 1 + # if (not (0 <= i < self.yearlen) or + # self.wdaymask[i] == self.rrule._wkst): + # This will cross the year boundary, if necessary. + if self.wdaymask[i] == self.rrule._wkst: + break + return dset, start, i + + def ddayset(self, year, month, day): + dset = [None] * self.yearlen + i = datetime.date(year, month, day).toordinal() - self.yearordinal + dset[i] = i + return dset, i, i + 1 + + def htimeset(self, hour, minute, second): + tset = [] + rr = self.rrule + for minute in rr._byminute: + for second in rr._bysecond: + tset.append(datetime.time(hour, minute, second, + tzinfo=rr._tzinfo)) + tset.sort() + return tset + + def mtimeset(self, hour, minute, second): + tset = [] + rr = self.rrule + for second in rr._bysecond: + tset.append(datetime.time(hour, minute, second, tzinfo=rr._tzinfo)) + tset.sort() + return tset + + def stimeset(self, hour, minute, second): + return (datetime.time(hour, minute, second, + tzinfo=self.rrule._tzinfo),) + + +class rruleset(rrulebase): + """ The rruleset type allows more complex recurrence setups, mixing + multiple rules, dates, exclusion rules, and exclusion dates. The type + constructor takes the following keyword arguments: + + :param cache: If True, caching of results will be enabled, improving + performance of multiple queries considerably. """ + + class _genitem(object): + def __init__(self, genlist, gen): + try: + self.dt = advance_iterator(gen) + genlist.append(self) + except StopIteration: + pass + self.genlist = genlist + self.gen = gen + + def __next__(self): + try: + self.dt = advance_iterator(self.gen) + except StopIteration: + if self.genlist[0] is self: + heapq.heappop(self.genlist) + else: + self.genlist.remove(self) + heapq.heapify(self.genlist) + + next = __next__ + + def __lt__(self, other): + return self.dt < other.dt + + def __gt__(self, other): + return self.dt > other.dt + + def __eq__(self, other): + return self.dt == other.dt + + def __ne__(self, other): + return self.dt != other.dt + + def __init__(self, cache=False): + super(rruleset, self).__init__(cache) + self._rrule = [] + self._rdate = [] + self._exrule = [] + self._exdate = [] + + @_invalidates_cache + def rrule(self, rrule): + """ Include the given :py:class:`rrule` instance in the recurrence set + generation. """ + self._rrule.append(rrule) + + @_invalidates_cache + def rdate(self, rdate): + """ Include the given :py:class:`datetime` instance in the recurrence + set generation. """ + self._rdate.append(rdate) + + @_invalidates_cache + def exrule(self, exrule): + """ Include the given rrule instance in the recurrence set exclusion + list. Dates which are part of the given recurrence rules will not + be generated, even if some inclusive rrule or rdate matches them. + """ + self._exrule.append(exrule) + + @_invalidates_cache + def exdate(self, exdate): + """ Include the given datetime instance in the recurrence set + exclusion list. Dates included that way will not be generated, + even if some inclusive rrule or rdate matches them. """ + self._exdate.append(exdate) + + def _iter(self): + rlist = [] + self._rdate.sort() + self._genitem(rlist, iter(self._rdate)) + for gen in [iter(x) for x in self._rrule]: + self._genitem(rlist, gen) + exlist = [] + self._exdate.sort() + self._genitem(exlist, iter(self._exdate)) + for gen in [iter(x) for x in self._exrule]: + self._genitem(exlist, gen) + lastdt = None + total = 0 + heapq.heapify(rlist) + heapq.heapify(exlist) + while rlist: + ritem = rlist[0] + if not lastdt or lastdt != ritem.dt: + while exlist and exlist[0] < ritem: + exitem = exlist[0] + advance_iterator(exitem) + if exlist and exlist[0] is exitem: + heapq.heapreplace(exlist, exitem) + if not exlist or ritem != exlist[0]: + total += 1 + yield ritem.dt + lastdt = ritem.dt + advance_iterator(ritem) + if rlist and rlist[0] is ritem: + heapq.heapreplace(rlist, ritem) + self._len = total + + + + +class _rrulestr(object): + """ Parses a string representation of a recurrence rule or set of + recurrence rules. + + :param s: + Required, a string defining one or more recurrence rules. + + :param dtstart: + If given, used as the default recurrence start if not specified in the + rule string. + + :param cache: + If set ``True`` caching of results will be enabled, improving + performance of multiple queries considerably. + + :param unfold: + If set ``True`` indicates that a rule string is split over more + than one line and should be joined before processing. + + :param forceset: + If set ``True`` forces a :class:`dateutil.rrule.rruleset` to + be returned. + + :param compatible: + If set ``True`` forces ``unfold`` and ``forceset`` to be ``True``. + + :param ignoretz: + If set ``True``, time zones in parsed strings are ignored and a naive + :class:`datetime.datetime` object is returned. + + :param tzids: + If given, a callable or mapping used to retrieve a + :class:`datetime.tzinfo` from a string representation. + Defaults to :func:`dateutil.tz.gettz`. + + :param tzinfos: + Additional time zone names / aliases which may be present in a string + representation. See :func:`dateutil.parser.parse` for more + information. + + :return: + Returns a :class:`dateutil.rrule.rruleset` or + :class:`dateutil.rrule.rrule` + """ + + _freq_map = {"YEARLY": YEARLY, + "MONTHLY": MONTHLY, + "WEEKLY": WEEKLY, + "DAILY": DAILY, + "HOURLY": HOURLY, + "MINUTELY": MINUTELY, + "SECONDLY": SECONDLY} + + _weekday_map = {"MO": 0, "TU": 1, "WE": 2, "TH": 3, + "FR": 4, "SA": 5, "SU": 6} + + def _handle_int(self, rrkwargs, name, value, **kwargs): + rrkwargs[name.lower()] = int(value) + + def _handle_int_list(self, rrkwargs, name, value, **kwargs): + rrkwargs[name.lower()] = [int(x) for x in value.split(',')] + + _handle_INTERVAL = _handle_int + _handle_COUNT = _handle_int + _handle_BYSETPOS = _handle_int_list + _handle_BYMONTH = _handle_int_list + _handle_BYMONTHDAY = _handle_int_list + _handle_BYYEARDAY = _handle_int_list + _handle_BYEASTER = _handle_int_list + _handle_BYWEEKNO = _handle_int_list + _handle_BYHOUR = _handle_int_list + _handle_BYMINUTE = _handle_int_list + _handle_BYSECOND = _handle_int_list + + def _handle_FREQ(self, rrkwargs, name, value, **kwargs): + rrkwargs["freq"] = self._freq_map[value] + + def _handle_UNTIL(self, rrkwargs, name, value, **kwargs): + global parser + if not parser: + from dateutil import parser + try: + rrkwargs["until"] = parser.parse(value, + ignoretz=kwargs.get("ignoretz"), + tzinfos=kwargs.get("tzinfos")) + except ValueError: + raise ValueError("invalid until date") + + def _handle_WKST(self, rrkwargs, name, value, **kwargs): + rrkwargs["wkst"] = self._weekday_map[value] + + def _handle_BYWEEKDAY(self, rrkwargs, name, value, **kwargs): + """ + Two ways to specify this: +1MO or MO(+1) + """ + l = [] + for wday in value.split(','): + if '(' in wday: + # If it's of the form TH(+1), etc. + splt = wday.split('(') + w = splt[0] + n = int(splt[1][:-1]) + elif len(wday): + # If it's of the form +1MO + for i in range(len(wday)): + if wday[i] not in '+-0123456789': + break + n = wday[:i] or None + w = wday[i:] + if n: + n = int(n) + else: + raise ValueError("Invalid (empty) BYDAY specification.") + + l.append(weekdays[self._weekday_map[w]](n)) + rrkwargs["byweekday"] = l + + _handle_BYDAY = _handle_BYWEEKDAY + + def _parse_rfc_rrule(self, line, + dtstart=None, + cache=False, + ignoretz=False, + tzinfos=None): + if line.find(':') != -1: + name, value = line.split(':') + if name != "RRULE": + raise ValueError("unknown parameter name") + else: + value = line + rrkwargs = {} + for pair in value.split(';'): + name, value = pair.split('=') + name = name.upper() + value = value.upper() + try: + getattr(self, "_handle_"+name)(rrkwargs, name, value, + ignoretz=ignoretz, + tzinfos=tzinfos) + except AttributeError: + raise ValueError("unknown parameter '%s'" % name) + except (KeyError, ValueError): + raise ValueError("invalid '%s': %s" % (name, value)) + return rrule(dtstart=dtstart, cache=cache, **rrkwargs) + + def _parse_date_value(self, date_value, parms, rule_tzids, + ignoretz, tzids, tzinfos): + global parser + if not parser: + from dateutil import parser + + datevals = [] + value_found = False + TZID = None + + for parm in parms: + if parm.startswith("TZID="): + try: + tzkey = rule_tzids[parm.split('TZID=')[-1]] + except KeyError: + continue + if tzids is None: + from . import tz + tzlookup = tz.gettz + elif callable(tzids): + tzlookup = tzids + else: + tzlookup = getattr(tzids, 'get', None) + if tzlookup is None: + msg = ('tzids must be a callable, mapping, or None, ' + 'not %s' % tzids) + raise ValueError(msg) + + TZID = tzlookup(tzkey) + continue + + # RFC 5445 3.8.2.4: The VALUE parameter is optional, but may be found + # only once. + if parm not in {"VALUE=DATE-TIME", "VALUE=DATE"}: + raise ValueError("unsupported parm: " + parm) + else: + if value_found: + msg = ("Duplicate value parameter found in: " + parm) + raise ValueError(msg) + value_found = True + + for datestr in date_value.split(','): + date = parser.parse(datestr, ignoretz=ignoretz, tzinfos=tzinfos) + if TZID is not None: + if date.tzinfo is None: + date = date.replace(tzinfo=TZID) + else: + raise ValueError('DTSTART/EXDATE specifies multiple timezone') + datevals.append(date) + + return datevals + + def _parse_rfc(self, s, + dtstart=None, + cache=False, + unfold=False, + forceset=False, + compatible=False, + ignoretz=False, + tzids=None, + tzinfos=None): + global parser + if compatible: + forceset = True + unfold = True + + TZID_NAMES = dict(map( + lambda x: (x.upper(), x), + re.findall('TZID=(?P[^:]+):', s) + )) + s = s.upper() + if not s.strip(): + raise ValueError("empty string") + if unfold: + lines = s.splitlines() + i = 0 + while i < len(lines): + line = lines[i].rstrip() + if not line: + del lines[i] + elif i > 0 and line[0] == " ": + lines[i-1] += line[1:] + del lines[i] + else: + i += 1 + else: + lines = s.split() + if (not forceset and len(lines) == 1 and (s.find(':') == -1 or + s.startswith('RRULE:'))): + return self._parse_rfc_rrule(lines[0], cache=cache, + dtstart=dtstart, ignoretz=ignoretz, + tzinfos=tzinfos) + else: + rrulevals = [] + rdatevals = [] + exrulevals = [] + exdatevals = [] + for line in lines: + if not line: + continue + if line.find(':') == -1: + name = "RRULE" + value = line + else: + name, value = line.split(':', 1) + parms = name.split(';') + if not parms: + raise ValueError("empty property name") + name = parms[0] + parms = parms[1:] + if name == "RRULE": + for parm in parms: + raise ValueError("unsupported RRULE parm: "+parm) + rrulevals.append(value) + elif name == "RDATE": + for parm in parms: + if parm != "VALUE=DATE-TIME": + raise ValueError("unsupported RDATE parm: "+parm) + rdatevals.append(value) + elif name == "EXRULE": + for parm in parms: + raise ValueError("unsupported EXRULE parm: "+parm) + exrulevals.append(value) + elif name == "EXDATE": + exdatevals.extend( + self._parse_date_value(value, parms, + TZID_NAMES, ignoretz, + tzids, tzinfos) + ) + elif name == "DTSTART": + dtvals = self._parse_date_value(value, parms, TZID_NAMES, + ignoretz, tzids, tzinfos) + if len(dtvals) != 1: + raise ValueError("Multiple DTSTART values specified:" + + value) + dtstart = dtvals[0] + else: + raise ValueError("unsupported property: "+name) + if (forceset or len(rrulevals) > 1 or rdatevals + or exrulevals or exdatevals): + if not parser and (rdatevals or exdatevals): + from dateutil import parser + rset = rruleset(cache=cache) + for value in rrulevals: + rset.rrule(self._parse_rfc_rrule(value, dtstart=dtstart, + ignoretz=ignoretz, + tzinfos=tzinfos)) + for value in rdatevals: + for datestr in value.split(','): + rset.rdate(parser.parse(datestr, + ignoretz=ignoretz, + tzinfos=tzinfos)) + for value in exrulevals: + rset.exrule(self._parse_rfc_rrule(value, dtstart=dtstart, + ignoretz=ignoretz, + tzinfos=tzinfos)) + for value in exdatevals: + rset.exdate(value) + if compatible and dtstart: + rset.rdate(dtstart) + return rset + else: + return self._parse_rfc_rrule(rrulevals[0], + dtstart=dtstart, + cache=cache, + ignoretz=ignoretz, + tzinfos=tzinfos) + + def __call__(self, s, **kwargs): + return self._parse_rfc(s, **kwargs) + + +rrulestr = _rrulestr() + +# vim:ts=4:sw=4:et diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/tz/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/tz/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..af1352c47292f4eebc5cae8da45641b5544558e3 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/tz/__init__.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +from .tz import * +from .tz import __doc__ + +__all__ = ["tzutc", "tzoffset", "tzlocal", "tzfile", "tzrange", + "tzstr", "tzical", "tzwin", "tzwinlocal", "gettz", + "enfold", "datetime_ambiguous", "datetime_exists", + "resolve_imaginary", "UTC", "DeprecatedTzFormatWarning"] + + +class DeprecatedTzFormatWarning(Warning): + """Warning raised when time zones are parsed from deprecated formats.""" diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/tz/_factories.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/tz/_factories.py new file mode 100644 index 0000000000000000000000000000000000000000..f8a65891a023ebf9eb0c24d391ba67541b7133f1 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/tz/_factories.py @@ -0,0 +1,80 @@ +from datetime import timedelta +import weakref +from collections import OrderedDict + +from six.moves import _thread + + +class _TzSingleton(type): + def __init__(cls, *args, **kwargs): + cls.__instance = None + super(_TzSingleton, cls).__init__(*args, **kwargs) + + def __call__(cls): + if cls.__instance is None: + cls.__instance = super(_TzSingleton, cls).__call__() + return cls.__instance + + +class _TzFactory(type): + def instance(cls, *args, **kwargs): + """Alternate constructor that returns a fresh instance""" + return type.__call__(cls, *args, **kwargs) + + +class _TzOffsetFactory(_TzFactory): + def __init__(cls, *args, **kwargs): + cls.__instances = weakref.WeakValueDictionary() + cls.__strong_cache = OrderedDict() + cls.__strong_cache_size = 8 + + cls._cache_lock = _thread.allocate_lock() + + def __call__(cls, name, offset): + if isinstance(offset, timedelta): + key = (name, offset.total_seconds()) + else: + key = (name, offset) + + instance = cls.__instances.get(key, None) + if instance is None: + instance = cls.__instances.setdefault(key, + cls.instance(name, offset)) + + # This lock may not be necessary in Python 3. See GH issue #901 + with cls._cache_lock: + cls.__strong_cache[key] = cls.__strong_cache.pop(key, instance) + + # Remove an item if the strong cache is overpopulated + if len(cls.__strong_cache) > cls.__strong_cache_size: + cls.__strong_cache.popitem(last=False) + + return instance + + +class _TzStrFactory(_TzFactory): + def __init__(cls, *args, **kwargs): + cls.__instances = weakref.WeakValueDictionary() + cls.__strong_cache = OrderedDict() + cls.__strong_cache_size = 8 + + cls.__cache_lock = _thread.allocate_lock() + + def __call__(cls, s, posix_offset=False): + key = (s, posix_offset) + instance = cls.__instances.get(key, None) + + if instance is None: + instance = cls.__instances.setdefault(key, + cls.instance(s, posix_offset)) + + # This lock may not be necessary in Python 3. See GH issue #901 + with cls.__cache_lock: + cls.__strong_cache[key] = cls.__strong_cache.pop(key, instance) + + # Remove an item if the strong cache is overpopulated + if len(cls.__strong_cache) > cls.__strong_cache_size: + cls.__strong_cache.popitem(last=False) + + return instance + diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/tz/tz.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/tz/tz.py new file mode 100644 index 0000000000000000000000000000000000000000..617591446bd92eb1cc7b7d67fa3f17435e691cdd --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/tz/tz.py @@ -0,0 +1,1849 @@ +# -*- coding: utf-8 -*- +""" +This module offers timezone implementations subclassing the abstract +:py:class:`datetime.tzinfo` type. There are classes to handle tzfile format +files (usually are in :file:`/etc/localtime`, :file:`/usr/share/zoneinfo`, +etc), TZ environment string (in all known formats), given ranges (with help +from relative deltas), local machine timezone, fixed offset timezone, and UTC +timezone. +""" +import datetime +import struct +import time +import sys +import os +import bisect +import weakref +from collections import OrderedDict + +import six +from six import string_types +from six.moves import _thread +from ._common import tzname_in_python2, _tzinfo +from ._common import tzrangebase, enfold +from ._common import _validate_fromutc_inputs + +from ._factories import _TzSingleton, _TzOffsetFactory +from ._factories import _TzStrFactory +try: + from .win import tzwin, tzwinlocal +except ImportError: + tzwin = tzwinlocal = None + +# For warning about rounding tzinfo +from warnings import warn + +ZERO = datetime.timedelta(0) +EPOCH = datetime.datetime(1970, 1, 1, 0, 0) +EPOCHORDINAL = EPOCH.toordinal() + + +@six.add_metaclass(_TzSingleton) +class tzutc(datetime.tzinfo): + """ + This is a tzinfo object that represents the UTC time zone. + + **Examples:** + + .. doctest:: + + >>> from datetime import * + >>> from dateutil.tz import * + + >>> datetime.now() + datetime.datetime(2003, 9, 27, 9, 40, 1, 521290) + + >>> datetime.now(tzutc()) + datetime.datetime(2003, 9, 27, 12, 40, 12, 156379, tzinfo=tzutc()) + + >>> datetime.now(tzutc()).tzname() + 'UTC' + + .. versionchanged:: 2.7.0 + ``tzutc()`` is now a singleton, so the result of ``tzutc()`` will + always return the same object. + + .. doctest:: + + >>> from dateutil.tz import tzutc, UTC + >>> tzutc() is tzutc() + True + >>> tzutc() is UTC + True + """ + def utcoffset(self, dt): + return ZERO + + def dst(self, dt): + return ZERO + + @tzname_in_python2 + def tzname(self, dt): + return "UTC" + + def is_ambiguous(self, dt): + """ + Whether or not the "wall time" of a given datetime is ambiguous in this + zone. + + :param dt: + A :py:class:`datetime.datetime`, naive or time zone aware. + + + :return: + Returns ``True`` if ambiguous, ``False`` otherwise. + + .. versionadded:: 2.6.0 + """ + return False + + @_validate_fromutc_inputs + def fromutc(self, dt): + """ + Fast track version of fromutc() returns the original ``dt`` object for + any valid :py:class:`datetime.datetime` object. + """ + return dt + + def __eq__(self, other): + if not isinstance(other, (tzutc, tzoffset)): + return NotImplemented + + return (isinstance(other, tzutc) or + (isinstance(other, tzoffset) and other._offset == ZERO)) + + __hash__ = None + + def __ne__(self, other): + return not (self == other) + + def __repr__(self): + return "%s()" % self.__class__.__name__ + + __reduce__ = object.__reduce__ + + +#: Convenience constant providing a :class:`tzutc()` instance +#: +#: .. versionadded:: 2.7.0 +UTC = tzutc() + + +@six.add_metaclass(_TzOffsetFactory) +class tzoffset(datetime.tzinfo): + """ + A simple class for representing a fixed offset from UTC. + + :param name: + The timezone name, to be returned when ``tzname()`` is called. + :param offset: + The time zone offset in seconds, or (since version 2.6.0, represented + as a :py:class:`datetime.timedelta` object). + """ + def __init__(self, name, offset): + self._name = name + + try: + # Allow a timedelta + offset = offset.total_seconds() + except (TypeError, AttributeError): + pass + + self._offset = datetime.timedelta(seconds=_get_supported_offset(offset)) + + def utcoffset(self, dt): + return self._offset + + def dst(self, dt): + return ZERO + + @tzname_in_python2 + def tzname(self, dt): + return self._name + + @_validate_fromutc_inputs + def fromutc(self, dt): + return dt + self._offset + + def is_ambiguous(self, dt): + """ + Whether or not the "wall time" of a given datetime is ambiguous in this + zone. + + :param dt: + A :py:class:`datetime.datetime`, naive or time zone aware. + :return: + Returns ``True`` if ambiguous, ``False`` otherwise. + + .. versionadded:: 2.6.0 + """ + return False + + def __eq__(self, other): + if not isinstance(other, tzoffset): + return NotImplemented + + return self._offset == other._offset + + __hash__ = None + + def __ne__(self, other): + return not (self == other) + + def __repr__(self): + return "%s(%s, %s)" % (self.__class__.__name__, + repr(self._name), + int(self._offset.total_seconds())) + + __reduce__ = object.__reduce__ + + +class tzlocal(_tzinfo): + """ + A :class:`tzinfo` subclass built around the ``time`` timezone functions. + """ + def __init__(self): + super(tzlocal, self).__init__() + + self._std_offset = datetime.timedelta(seconds=-time.timezone) + if time.daylight: + self._dst_offset = datetime.timedelta(seconds=-time.altzone) + else: + self._dst_offset = self._std_offset + + self._dst_saved = self._dst_offset - self._std_offset + self._hasdst = bool(self._dst_saved) + self._tznames = tuple(time.tzname) + + def utcoffset(self, dt): + if dt is None and self._hasdst: + return None + + if self._isdst(dt): + return self._dst_offset + else: + return self._std_offset + + def dst(self, dt): + if dt is None and self._hasdst: + return None + + if self._isdst(dt): + return self._dst_offset - self._std_offset + else: + return ZERO + + @tzname_in_python2 + def tzname(self, dt): + return self._tznames[self._isdst(dt)] + + def is_ambiguous(self, dt): + """ + Whether or not the "wall time" of a given datetime is ambiguous in this + zone. + + :param dt: + A :py:class:`datetime.datetime`, naive or time zone aware. + + + :return: + Returns ``True`` if ambiguous, ``False`` otherwise. + + .. versionadded:: 2.6.0 + """ + naive_dst = self._naive_is_dst(dt) + return (not naive_dst and + (naive_dst != self._naive_is_dst(dt - self._dst_saved))) + + def _naive_is_dst(self, dt): + timestamp = _datetime_to_timestamp(dt) + return time.localtime(timestamp + time.timezone).tm_isdst + + def _isdst(self, dt, fold_naive=True): + # We can't use mktime here. It is unstable when deciding if + # the hour near to a change is DST or not. + # + # timestamp = time.mktime((dt.year, dt.month, dt.day, dt.hour, + # dt.minute, dt.second, dt.weekday(), 0, -1)) + # return time.localtime(timestamp).tm_isdst + # + # The code above yields the following result: + # + # >>> import tz, datetime + # >>> t = tz.tzlocal() + # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname() + # 'BRDT' + # >>> datetime.datetime(2003,2,16,0,tzinfo=t).tzname() + # 'BRST' + # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname() + # 'BRST' + # >>> datetime.datetime(2003,2,15,22,tzinfo=t).tzname() + # 'BRDT' + # >>> datetime.datetime(2003,2,15,23,tzinfo=t).tzname() + # 'BRDT' + # + # Here is a more stable implementation: + # + if not self._hasdst: + return False + + # Check for ambiguous times: + dstval = self._naive_is_dst(dt) + fold = getattr(dt, 'fold', None) + + if self.is_ambiguous(dt): + if fold is not None: + return not self._fold(dt) + else: + return True + + return dstval + + def __eq__(self, other): + if isinstance(other, tzlocal): + return (self._std_offset == other._std_offset and + self._dst_offset == other._dst_offset) + elif isinstance(other, tzutc): + return (not self._hasdst and + self._tznames[0] in {'UTC', 'GMT'} and + self._std_offset == ZERO) + elif isinstance(other, tzoffset): + return (not self._hasdst and + self._tznames[0] == other._name and + self._std_offset == other._offset) + else: + return NotImplemented + + __hash__ = None + + def __ne__(self, other): + return not (self == other) + + def __repr__(self): + return "%s()" % self.__class__.__name__ + + __reduce__ = object.__reduce__ + + +class _ttinfo(object): + __slots__ = ["offset", "delta", "isdst", "abbr", + "isstd", "isgmt", "dstoffset"] + + def __init__(self): + for attr in self.__slots__: + setattr(self, attr, None) + + def __repr__(self): + l = [] + for attr in self.__slots__: + value = getattr(self, attr) + if value is not None: + l.append("%s=%s" % (attr, repr(value))) + return "%s(%s)" % (self.__class__.__name__, ", ".join(l)) + + def __eq__(self, other): + if not isinstance(other, _ttinfo): + return NotImplemented + + return (self.offset == other.offset and + self.delta == other.delta and + self.isdst == other.isdst and + self.abbr == other.abbr and + self.isstd == other.isstd and + self.isgmt == other.isgmt and + self.dstoffset == other.dstoffset) + + __hash__ = None + + def __ne__(self, other): + return not (self == other) + + def __getstate__(self): + state = {} + for name in self.__slots__: + state[name] = getattr(self, name, None) + return state + + def __setstate__(self, state): + for name in self.__slots__: + if name in state: + setattr(self, name, state[name]) + + +class _tzfile(object): + """ + Lightweight class for holding the relevant transition and time zone + information read from binary tzfiles. + """ + attrs = ['trans_list', 'trans_list_utc', 'trans_idx', 'ttinfo_list', + 'ttinfo_std', 'ttinfo_dst', 'ttinfo_before', 'ttinfo_first'] + + def __init__(self, **kwargs): + for attr in self.attrs: + setattr(self, attr, kwargs.get(attr, None)) + + +class tzfile(_tzinfo): + """ + This is a ``tzinfo`` subclass that allows one to use the ``tzfile(5)`` + format timezone files to extract current and historical zone information. + + :param fileobj: + This can be an opened file stream or a file name that the time zone + information can be read from. + + :param filename: + This is an optional parameter specifying the source of the time zone + information in the event that ``fileobj`` is a file object. If omitted + and ``fileobj`` is a file stream, this parameter will be set either to + ``fileobj``'s ``name`` attribute or to ``repr(fileobj)``. + + See `Sources for Time Zone and Daylight Saving Time Data + `_ for more information. + Time zone files can be compiled from the `IANA Time Zone database files + `_ with the `zic time zone compiler + `_ + + .. note:: + + Only construct a ``tzfile`` directly if you have a specific timezone + file on disk that you want to read into a Python ``tzinfo`` object. + If you want to get a ``tzfile`` representing a specific IANA zone, + (e.g. ``'America/New_York'``), you should call + :func:`dateutil.tz.gettz` with the zone identifier. + + + **Examples:** + + Using the US Eastern time zone as an example, we can see that a ``tzfile`` + provides time zone information for the standard Daylight Saving offsets: + + .. testsetup:: tzfile + + from dateutil.tz import gettz + from datetime import datetime + + .. doctest:: tzfile + + >>> NYC = gettz('America/New_York') + >>> NYC + tzfile('/usr/share/zoneinfo/America/New_York') + + >>> print(datetime(2016, 1, 3, tzinfo=NYC)) # EST + 2016-01-03 00:00:00-05:00 + + >>> print(datetime(2016, 7, 7, tzinfo=NYC)) # EDT + 2016-07-07 00:00:00-04:00 + + + The ``tzfile`` structure contains a fully history of the time zone, + so historical dates will also have the right offsets. For example, before + the adoption of the UTC standards, New York used local solar mean time: + + .. doctest:: tzfile + + >>> print(datetime(1901, 4, 12, tzinfo=NYC)) # LMT + 1901-04-12 00:00:00-04:56 + + And during World War II, New York was on "Eastern War Time", which was a + state of permanent daylight saving time: + + .. doctest:: tzfile + + >>> print(datetime(1944, 2, 7, tzinfo=NYC)) # EWT + 1944-02-07 00:00:00-04:00 + + """ + + def __init__(self, fileobj, filename=None): + super(tzfile, self).__init__() + + file_opened_here = False + if isinstance(fileobj, string_types): + self._filename = fileobj + fileobj = open(fileobj, 'rb') + file_opened_here = True + elif filename is not None: + self._filename = filename + elif hasattr(fileobj, "name"): + self._filename = fileobj.name + else: + self._filename = repr(fileobj) + + if fileobj is not None: + if not file_opened_here: + fileobj = _nullcontext(fileobj) + + with fileobj as file_stream: + tzobj = self._read_tzfile(file_stream) + + self._set_tzdata(tzobj) + + def _set_tzdata(self, tzobj): + """ Set the time zone data of this object from a _tzfile object """ + # Copy the relevant attributes over as private attributes + for attr in _tzfile.attrs: + setattr(self, '_' + attr, getattr(tzobj, attr)) + + def _read_tzfile(self, fileobj): + out = _tzfile() + + # From tzfile(5): + # + # The time zone information files used by tzset(3) + # begin with the magic characters "TZif" to identify + # them as time zone information files, followed by + # sixteen bytes reserved for future use, followed by + # six four-byte values of type long, written in a + # ``standard'' byte order (the high-order byte + # of the value is written first). + if fileobj.read(4).decode() != "TZif": + raise ValueError("magic not found") + + fileobj.read(16) + + ( + # The number of UTC/local indicators stored in the file. + ttisgmtcnt, + + # The number of standard/wall indicators stored in the file. + ttisstdcnt, + + # The number of leap seconds for which data is + # stored in the file. + leapcnt, + + # The number of "transition times" for which data + # is stored in the file. + timecnt, + + # The number of "local time types" for which data + # is stored in the file (must not be zero). + typecnt, + + # The number of characters of "time zone + # abbreviation strings" stored in the file. + charcnt, + + ) = struct.unpack(">6l", fileobj.read(24)) + + # The above header is followed by tzh_timecnt four-byte + # values of type long, sorted in ascending order. + # These values are written in ``standard'' byte order. + # Each is used as a transition time (as returned by + # time(2)) at which the rules for computing local time + # change. + + if timecnt: + out.trans_list_utc = list(struct.unpack(">%dl" % timecnt, + fileobj.read(timecnt*4))) + else: + out.trans_list_utc = [] + + # Next come tzh_timecnt one-byte values of type unsigned + # char; each one tells which of the different types of + # ``local time'' types described in the file is associated + # with the same-indexed transition time. These values + # serve as indices into an array of ttinfo structures that + # appears next in the file. + + if timecnt: + out.trans_idx = struct.unpack(">%dB" % timecnt, + fileobj.read(timecnt)) + else: + out.trans_idx = [] + + # Each ttinfo structure is written as a four-byte value + # for tt_gmtoff of type long, in a standard byte + # order, followed by a one-byte value for tt_isdst + # and a one-byte value for tt_abbrind. In each + # structure, tt_gmtoff gives the number of + # seconds to be added to UTC, tt_isdst tells whether + # tm_isdst should be set by localtime(3), and + # tt_abbrind serves as an index into the array of + # time zone abbreviation characters that follow the + # ttinfo structure(s) in the file. + + ttinfo = [] + + for i in range(typecnt): + ttinfo.append(struct.unpack(">lbb", fileobj.read(6))) + + abbr = fileobj.read(charcnt).decode() + + # Then there are tzh_leapcnt pairs of four-byte + # values, written in standard byte order; the + # first value of each pair gives the time (as + # returned by time(2)) at which a leap second + # occurs; the second gives the total number of + # leap seconds to be applied after the given time. + # The pairs of values are sorted in ascending order + # by time. + + # Not used, for now (but seek for correct file position) + if leapcnt: + fileobj.seek(leapcnt * 8, os.SEEK_CUR) + + # Then there are tzh_ttisstdcnt standard/wall + # indicators, each stored as a one-byte value; + # they tell whether the transition times associated + # with local time types were specified as standard + # time or wall clock time, and are used when + # a time zone file is used in handling POSIX-style + # time zone environment variables. + + if ttisstdcnt: + isstd = struct.unpack(">%db" % ttisstdcnt, + fileobj.read(ttisstdcnt)) + + # Finally, there are tzh_ttisgmtcnt UTC/local + # indicators, each stored as a one-byte value; + # they tell whether the transition times associated + # with local time types were specified as UTC or + # local time, and are used when a time zone file + # is used in handling POSIX-style time zone envi- + # ronment variables. + + if ttisgmtcnt: + isgmt = struct.unpack(">%db" % ttisgmtcnt, + fileobj.read(ttisgmtcnt)) + + # Build ttinfo list + out.ttinfo_list = [] + for i in range(typecnt): + gmtoff, isdst, abbrind = ttinfo[i] + gmtoff = _get_supported_offset(gmtoff) + tti = _ttinfo() + tti.offset = gmtoff + tti.dstoffset = datetime.timedelta(0) + tti.delta = datetime.timedelta(seconds=gmtoff) + tti.isdst = isdst + tti.abbr = abbr[abbrind:abbr.find('\x00', abbrind)] + tti.isstd = (ttisstdcnt > i and isstd[i] != 0) + tti.isgmt = (ttisgmtcnt > i and isgmt[i] != 0) + out.ttinfo_list.append(tti) + + # Replace ttinfo indexes for ttinfo objects. + out.trans_idx = [out.ttinfo_list[idx] for idx in out.trans_idx] + + # Set standard, dst, and before ttinfos. before will be + # used when a given time is before any transitions, + # and will be set to the first non-dst ttinfo, or to + # the first dst, if all of them are dst. + out.ttinfo_std = None + out.ttinfo_dst = None + out.ttinfo_before = None + if out.ttinfo_list: + if not out.trans_list_utc: + out.ttinfo_std = out.ttinfo_first = out.ttinfo_list[0] + else: + for i in range(timecnt-1, -1, -1): + tti = out.trans_idx[i] + if not out.ttinfo_std and not tti.isdst: + out.ttinfo_std = tti + elif not out.ttinfo_dst and tti.isdst: + out.ttinfo_dst = tti + + if out.ttinfo_std and out.ttinfo_dst: + break + else: + if out.ttinfo_dst and not out.ttinfo_std: + out.ttinfo_std = out.ttinfo_dst + + for tti in out.ttinfo_list: + if not tti.isdst: + out.ttinfo_before = tti + break + else: + out.ttinfo_before = out.ttinfo_list[0] + + # Now fix transition times to become relative to wall time. + # + # I'm not sure about this. In my tests, the tz source file + # is setup to wall time, and in the binary file isstd and + # isgmt are off, so it should be in wall time. OTOH, it's + # always in gmt time. Let me know if you have comments + # about this. + lastdst = None + lastoffset = None + lastdstoffset = None + lastbaseoffset = None + out.trans_list = [] + + for i, tti in enumerate(out.trans_idx): + offset = tti.offset + dstoffset = 0 + + if lastdst is not None: + if tti.isdst: + if not lastdst: + dstoffset = offset - lastoffset + + if not dstoffset and lastdstoffset: + dstoffset = lastdstoffset + + tti.dstoffset = datetime.timedelta(seconds=dstoffset) + lastdstoffset = dstoffset + + # If a time zone changes its base offset during a DST transition, + # then you need to adjust by the previous base offset to get the + # transition time in local time. Otherwise you use the current + # base offset. Ideally, I would have some mathematical proof of + # why this is true, but I haven't really thought about it enough. + baseoffset = offset - dstoffset + adjustment = baseoffset + if (lastbaseoffset is not None and baseoffset != lastbaseoffset + and tti.isdst != lastdst): + # The base DST has changed + adjustment = lastbaseoffset + + lastdst = tti.isdst + lastoffset = offset + lastbaseoffset = baseoffset + + out.trans_list.append(out.trans_list_utc[i] + adjustment) + + out.trans_idx = tuple(out.trans_idx) + out.trans_list = tuple(out.trans_list) + out.trans_list_utc = tuple(out.trans_list_utc) + + return out + + def _find_last_transition(self, dt, in_utc=False): + # If there's no list, there are no transitions to find + if not self._trans_list: + return None + + timestamp = _datetime_to_timestamp(dt) + + # Find where the timestamp fits in the transition list - if the + # timestamp is a transition time, it's part of the "after" period. + trans_list = self._trans_list_utc if in_utc else self._trans_list + idx = bisect.bisect_right(trans_list, timestamp) + + # We want to know when the previous transition was, so subtract off 1 + return idx - 1 + + def _get_ttinfo(self, idx): + # For no list or after the last transition, default to _ttinfo_std + if idx is None or (idx + 1) >= len(self._trans_list): + return self._ttinfo_std + + # If there is a list and the time is before it, return _ttinfo_before + if idx < 0: + return self._ttinfo_before + + return self._trans_idx[idx] + + def _find_ttinfo(self, dt): + idx = self._resolve_ambiguous_time(dt) + + return self._get_ttinfo(idx) + + def fromutc(self, dt): + """ + The ``tzfile`` implementation of :py:func:`datetime.tzinfo.fromutc`. + + :param dt: + A :py:class:`datetime.datetime` object. + + :raises TypeError: + Raised if ``dt`` is not a :py:class:`datetime.datetime` object. + + :raises ValueError: + Raised if this is called with a ``dt`` which does not have this + ``tzinfo`` attached. + + :return: + Returns a :py:class:`datetime.datetime` object representing the + wall time in ``self``'s time zone. + """ + # These isinstance checks are in datetime.tzinfo, so we'll preserve + # them, even if we don't care about duck typing. + if not isinstance(dt, datetime.datetime): + raise TypeError("fromutc() requires a datetime argument") + + if dt.tzinfo is not self: + raise ValueError("dt.tzinfo is not self") + + # First treat UTC as wall time and get the transition we're in. + idx = self._find_last_transition(dt, in_utc=True) + tti = self._get_ttinfo(idx) + + dt_out = dt + datetime.timedelta(seconds=tti.offset) + + fold = self.is_ambiguous(dt_out, idx=idx) + + return enfold(dt_out, fold=int(fold)) + + def is_ambiguous(self, dt, idx=None): + """ + Whether or not the "wall time" of a given datetime is ambiguous in this + zone. + + :param dt: + A :py:class:`datetime.datetime`, naive or time zone aware. + + + :return: + Returns ``True`` if ambiguous, ``False`` otherwise. + + .. versionadded:: 2.6.0 + """ + if idx is None: + idx = self._find_last_transition(dt) + + # Calculate the difference in offsets from current to previous + timestamp = _datetime_to_timestamp(dt) + tti = self._get_ttinfo(idx) + + if idx is None or idx <= 0: + return False + + od = self._get_ttinfo(idx - 1).offset - tti.offset + tt = self._trans_list[idx] # Transition time + + return timestamp < tt + od + + def _resolve_ambiguous_time(self, dt): + idx = self._find_last_transition(dt) + + # If we have no transitions, return the index + _fold = self._fold(dt) + if idx is None or idx == 0: + return idx + + # If it's ambiguous and we're in a fold, shift to a different index. + idx_offset = int(not _fold and self.is_ambiguous(dt, idx)) + + return idx - idx_offset + + def utcoffset(self, dt): + if dt is None: + return None + + if not self._ttinfo_std: + return ZERO + + return self._find_ttinfo(dt).delta + + def dst(self, dt): + if dt is None: + return None + + if not self._ttinfo_dst: + return ZERO + + tti = self._find_ttinfo(dt) + + if not tti.isdst: + return ZERO + + # The documentation says that utcoffset()-dst() must + # be constant for every dt. + return tti.dstoffset + + @tzname_in_python2 + def tzname(self, dt): + if not self._ttinfo_std or dt is None: + return None + return self._find_ttinfo(dt).abbr + + def __eq__(self, other): + if not isinstance(other, tzfile): + return NotImplemented + return (self._trans_list == other._trans_list and + self._trans_idx == other._trans_idx and + self._ttinfo_list == other._ttinfo_list) + + __hash__ = None + + def __ne__(self, other): + return not (self == other) + + def __repr__(self): + return "%s(%s)" % (self.__class__.__name__, repr(self._filename)) + + def __reduce__(self): + return self.__reduce_ex__(None) + + def __reduce_ex__(self, protocol): + return (self.__class__, (None, self._filename), self.__dict__) + + +class tzrange(tzrangebase): + """ + The ``tzrange`` object is a time zone specified by a set of offsets and + abbreviations, equivalent to the way the ``TZ`` variable can be specified + in POSIX-like systems, but using Python delta objects to specify DST + start, end and offsets. + + :param stdabbr: + The abbreviation for standard time (e.g. ``'EST'``). + + :param stdoffset: + An integer or :class:`datetime.timedelta` object or equivalent + specifying the base offset from UTC. + + If unspecified, +00:00 is used. + + :param dstabbr: + The abbreviation for DST / "Summer" time (e.g. ``'EDT'``). + + If specified, with no other DST information, DST is assumed to occur + and the default behavior or ``dstoffset``, ``start`` and ``end`` is + used. If unspecified and no other DST information is specified, it + is assumed that this zone has no DST. + + If this is unspecified and other DST information is *is* specified, + DST occurs in the zone but the time zone abbreviation is left + unchanged. + + :param dstoffset: + A an integer or :class:`datetime.timedelta` object or equivalent + specifying the UTC offset during DST. If unspecified and any other DST + information is specified, it is assumed to be the STD offset +1 hour. + + :param start: + A :class:`relativedelta.relativedelta` object or equivalent specifying + the time and time of year that daylight savings time starts. To + specify, for example, that DST starts at 2AM on the 2nd Sunday in + March, pass: + + ``relativedelta(hours=2, month=3, day=1, weekday=SU(+2))`` + + If unspecified and any other DST information is specified, the default + value is 2 AM on the first Sunday in April. + + :param end: + A :class:`relativedelta.relativedelta` object or equivalent + representing the time and time of year that daylight savings time + ends, with the same specification method as in ``start``. One note is + that this should point to the first time in the *standard* zone, so if + a transition occurs at 2AM in the DST zone and the clocks are set back + 1 hour to 1AM, set the ``hours`` parameter to +1. + + + **Examples:** + + .. testsetup:: tzrange + + from dateutil.tz import tzrange, tzstr + + .. doctest:: tzrange + + >>> tzstr('EST5EDT') == tzrange("EST", -18000, "EDT") + True + + >>> from dateutil.relativedelta import * + >>> range1 = tzrange("EST", -18000, "EDT") + >>> range2 = tzrange("EST", -18000, "EDT", -14400, + ... relativedelta(hours=+2, month=4, day=1, + ... weekday=SU(+1)), + ... relativedelta(hours=+1, month=10, day=31, + ... weekday=SU(-1))) + >>> tzstr('EST5EDT') == range1 == range2 + True + + """ + def __init__(self, stdabbr, stdoffset=None, + dstabbr=None, dstoffset=None, + start=None, end=None): + + global relativedelta + from dateutil import relativedelta + + self._std_abbr = stdabbr + self._dst_abbr = dstabbr + + try: + stdoffset = stdoffset.total_seconds() + except (TypeError, AttributeError): + pass + + try: + dstoffset = dstoffset.total_seconds() + except (TypeError, AttributeError): + pass + + if stdoffset is not None: + self._std_offset = datetime.timedelta(seconds=stdoffset) + else: + self._std_offset = ZERO + + if dstoffset is not None: + self._dst_offset = datetime.timedelta(seconds=dstoffset) + elif dstabbr and stdoffset is not None: + self._dst_offset = self._std_offset + datetime.timedelta(hours=+1) + else: + self._dst_offset = ZERO + + if dstabbr and start is None: + self._start_delta = relativedelta.relativedelta( + hours=+2, month=4, day=1, weekday=relativedelta.SU(+1)) + else: + self._start_delta = start + + if dstabbr and end is None: + self._end_delta = relativedelta.relativedelta( + hours=+1, month=10, day=31, weekday=relativedelta.SU(-1)) + else: + self._end_delta = end + + self._dst_base_offset_ = self._dst_offset - self._std_offset + self.hasdst = bool(self._start_delta) + + def transitions(self, year): + """ + For a given year, get the DST on and off transition times, expressed + always on the standard time side. For zones with no transitions, this + function returns ``None``. + + :param year: + The year whose transitions you would like to query. + + :return: + Returns a :class:`tuple` of :class:`datetime.datetime` objects, + ``(dston, dstoff)`` for zones with an annual DST transition, or + ``None`` for fixed offset zones. + """ + if not self.hasdst: + return None + + base_year = datetime.datetime(year, 1, 1) + + start = base_year + self._start_delta + end = base_year + self._end_delta + + return (start, end) + + def __eq__(self, other): + if not isinstance(other, tzrange): + return NotImplemented + + return (self._std_abbr == other._std_abbr and + self._dst_abbr == other._dst_abbr and + self._std_offset == other._std_offset and + self._dst_offset == other._dst_offset and + self._start_delta == other._start_delta and + self._end_delta == other._end_delta) + + @property + def _dst_base_offset(self): + return self._dst_base_offset_ + + +@six.add_metaclass(_TzStrFactory) +class tzstr(tzrange): + """ + ``tzstr`` objects are time zone objects specified by a time-zone string as + it would be passed to a ``TZ`` variable on POSIX-style systems (see + the `GNU C Library: TZ Variable`_ for more details). + + There is one notable exception, which is that POSIX-style time zones use an + inverted offset format, so normally ``GMT+3`` would be parsed as an offset + 3 hours *behind* GMT. The ``tzstr`` time zone object will parse this as an + offset 3 hours *ahead* of GMT. If you would like to maintain the POSIX + behavior, pass a ``True`` value to ``posix_offset``. + + The :class:`tzrange` object provides the same functionality, but is + specified using :class:`relativedelta.relativedelta` objects. rather than + strings. + + :param s: + A time zone string in ``TZ`` variable format. This can be a + :class:`bytes` (2.x: :class:`str`), :class:`str` (2.x: + :class:`unicode`) or a stream emitting unicode characters + (e.g. :class:`StringIO`). + + :param posix_offset: + Optional. If set to ``True``, interpret strings such as ``GMT+3`` or + ``UTC+3`` as being 3 hours *behind* UTC rather than ahead, per the + POSIX standard. + + .. caution:: + + Prior to version 2.7.0, this function also supported time zones + in the format: + + * ``EST5EDT,4,0,6,7200,10,0,26,7200,3600`` + * ``EST5EDT,4,1,0,7200,10,-1,0,7200,3600`` + + This format is non-standard and has been deprecated; this function + will raise a :class:`DeprecatedTZFormatWarning` until + support is removed in a future version. + + .. _`GNU C Library: TZ Variable`: + https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html + """ + def __init__(self, s, posix_offset=False): + global parser + from dateutil.parser import _parser as parser + + self._s = s + + res = parser._parsetz(s) + if res is None or res.any_unused_tokens: + raise ValueError("unknown string format") + + # Here we break the compatibility with the TZ variable handling. + # GMT-3 actually *means* the timezone -3. + if res.stdabbr in ("GMT", "UTC") and not posix_offset: + res.stdoffset *= -1 + + # We must initialize it first, since _delta() needs + # _std_offset and _dst_offset set. Use False in start/end + # to avoid building it two times. + tzrange.__init__(self, res.stdabbr, res.stdoffset, + res.dstabbr, res.dstoffset, + start=False, end=False) + + if not res.dstabbr: + self._start_delta = None + self._end_delta = None + else: + self._start_delta = self._delta(res.start) + if self._start_delta: + self._end_delta = self._delta(res.end, isend=1) + + self.hasdst = bool(self._start_delta) + + def _delta(self, x, isend=0): + from dateutil import relativedelta + kwargs = {} + if x.month is not None: + kwargs["month"] = x.month + if x.weekday is not None: + kwargs["weekday"] = relativedelta.weekday(x.weekday, x.week) + if x.week > 0: + kwargs["day"] = 1 + else: + kwargs["day"] = 31 + elif x.day: + kwargs["day"] = x.day + elif x.yday is not None: + kwargs["yearday"] = x.yday + elif x.jyday is not None: + kwargs["nlyearday"] = x.jyday + if not kwargs: + # Default is to start on first sunday of april, and end + # on last sunday of october. + if not isend: + kwargs["month"] = 4 + kwargs["day"] = 1 + kwargs["weekday"] = relativedelta.SU(+1) + else: + kwargs["month"] = 10 + kwargs["day"] = 31 + kwargs["weekday"] = relativedelta.SU(-1) + if x.time is not None: + kwargs["seconds"] = x.time + else: + # Default is 2AM. + kwargs["seconds"] = 7200 + if isend: + # Convert to standard time, to follow the documented way + # of working with the extra hour. See the documentation + # of the tzinfo class. + delta = self._dst_offset - self._std_offset + kwargs["seconds"] -= delta.seconds + delta.days * 86400 + return relativedelta.relativedelta(**kwargs) + + def __repr__(self): + return "%s(%s)" % (self.__class__.__name__, repr(self._s)) + + +class _tzicalvtzcomp(object): + def __init__(self, tzoffsetfrom, tzoffsetto, isdst, + tzname=None, rrule=None): + self.tzoffsetfrom = datetime.timedelta(seconds=tzoffsetfrom) + self.tzoffsetto = datetime.timedelta(seconds=tzoffsetto) + self.tzoffsetdiff = self.tzoffsetto - self.tzoffsetfrom + self.isdst = isdst + self.tzname = tzname + self.rrule = rrule + + +class _tzicalvtz(_tzinfo): + def __init__(self, tzid, comps=[]): + super(_tzicalvtz, self).__init__() + + self._tzid = tzid + self._comps = comps + self._cachedate = [] + self._cachecomp = [] + self._cache_lock = _thread.allocate_lock() + + def _find_comp(self, dt): + if len(self._comps) == 1: + return self._comps[0] + + dt = dt.replace(tzinfo=None) + + try: + with self._cache_lock: + return self._cachecomp[self._cachedate.index( + (dt, self._fold(dt)))] + except ValueError: + pass + + lastcompdt = None + lastcomp = None + + for comp in self._comps: + compdt = self._find_compdt(comp, dt) + + if compdt and (not lastcompdt or lastcompdt < compdt): + lastcompdt = compdt + lastcomp = comp + + if not lastcomp: + # RFC says nothing about what to do when a given + # time is before the first onset date. We'll look for the + # first standard component, or the first component, if + # none is found. + for comp in self._comps: + if not comp.isdst: + lastcomp = comp + break + else: + lastcomp = comp[0] + + with self._cache_lock: + self._cachedate.insert(0, (dt, self._fold(dt))) + self._cachecomp.insert(0, lastcomp) + + if len(self._cachedate) > 10: + self._cachedate.pop() + self._cachecomp.pop() + + return lastcomp + + def _find_compdt(self, comp, dt): + if comp.tzoffsetdiff < ZERO and self._fold(dt): + dt -= comp.tzoffsetdiff + + compdt = comp.rrule.before(dt, inc=True) + + return compdt + + def utcoffset(self, dt): + if dt is None: + return None + + return self._find_comp(dt).tzoffsetto + + def dst(self, dt): + comp = self._find_comp(dt) + if comp.isdst: + return comp.tzoffsetdiff + else: + return ZERO + + @tzname_in_python2 + def tzname(self, dt): + return self._find_comp(dt).tzname + + def __repr__(self): + return "" % repr(self._tzid) + + __reduce__ = object.__reduce__ + + +class tzical(object): + """ + This object is designed to parse an iCalendar-style ``VTIMEZONE`` structure + as set out in `RFC 5545`_ Section 4.6.5 into one or more `tzinfo` objects. + + :param `fileobj`: + A file or stream in iCalendar format, which should be UTF-8 encoded + with CRLF endings. + + .. _`RFC 5545`: https://tools.ietf.org/html/rfc5545 + """ + def __init__(self, fileobj): + global rrule + from dateutil import rrule + + if isinstance(fileobj, string_types): + self._s = fileobj + # ical should be encoded in UTF-8 with CRLF + fileobj = open(fileobj, 'r') + else: + self._s = getattr(fileobj, 'name', repr(fileobj)) + fileobj = _nullcontext(fileobj) + + self._vtz = {} + + with fileobj as fobj: + self._parse_rfc(fobj.read()) + + def keys(self): + """ + Retrieves the available time zones as a list. + """ + return list(self._vtz.keys()) + + def get(self, tzid=None): + """ + Retrieve a :py:class:`datetime.tzinfo` object by its ``tzid``. + + :param tzid: + If there is exactly one time zone available, omitting ``tzid`` + or passing :py:const:`None` value returns it. Otherwise a valid + key (which can be retrieved from :func:`keys`) is required. + + :raises ValueError: + Raised if ``tzid`` is not specified but there are either more + or fewer than 1 zone defined. + + :returns: + Returns either a :py:class:`datetime.tzinfo` object representing + the relevant time zone or :py:const:`None` if the ``tzid`` was + not found. + """ + if tzid is None: + if len(self._vtz) == 0: + raise ValueError("no timezones defined") + elif len(self._vtz) > 1: + raise ValueError("more than one timezone available") + tzid = next(iter(self._vtz)) + + return self._vtz.get(tzid) + + def _parse_offset(self, s): + s = s.strip() + if not s: + raise ValueError("empty offset") + if s[0] in ('+', '-'): + signal = (-1, +1)[s[0] == '+'] + s = s[1:] + else: + signal = +1 + if len(s) == 4: + return (int(s[:2]) * 3600 + int(s[2:]) * 60) * signal + elif len(s) == 6: + return (int(s[:2]) * 3600 + int(s[2:4]) * 60 + int(s[4:])) * signal + else: + raise ValueError("invalid offset: " + s) + + def _parse_rfc(self, s): + lines = s.splitlines() + if not lines: + raise ValueError("empty string") + + # Unfold + i = 0 + while i < len(lines): + line = lines[i].rstrip() + if not line: + del lines[i] + elif i > 0 and line[0] == " ": + lines[i-1] += line[1:] + del lines[i] + else: + i += 1 + + tzid = None + comps = [] + invtz = False + comptype = None + for line in lines: + if not line: + continue + name, value = line.split(':', 1) + parms = name.split(';') + if not parms: + raise ValueError("empty property name") + name = parms[0].upper() + parms = parms[1:] + if invtz: + if name == "BEGIN": + if value in ("STANDARD", "DAYLIGHT"): + # Process component + pass + else: + raise ValueError("unknown component: "+value) + comptype = value + founddtstart = False + tzoffsetfrom = None + tzoffsetto = None + rrulelines = [] + tzname = None + elif name == "END": + if value == "VTIMEZONE": + if comptype: + raise ValueError("component not closed: "+comptype) + if not tzid: + raise ValueError("mandatory TZID not found") + if not comps: + raise ValueError( + "at least one component is needed") + # Process vtimezone + self._vtz[tzid] = _tzicalvtz(tzid, comps) + invtz = False + elif value == comptype: + if not founddtstart: + raise ValueError("mandatory DTSTART not found") + if tzoffsetfrom is None: + raise ValueError( + "mandatory TZOFFSETFROM not found") + if tzoffsetto is None: + raise ValueError( + "mandatory TZOFFSETFROM not found") + # Process component + rr = None + if rrulelines: + rr = rrule.rrulestr("\n".join(rrulelines), + compatible=True, + ignoretz=True, + cache=True) + comp = _tzicalvtzcomp(tzoffsetfrom, tzoffsetto, + (comptype == "DAYLIGHT"), + tzname, rr) + comps.append(comp) + comptype = None + else: + raise ValueError("invalid component end: "+value) + elif comptype: + if name == "DTSTART": + # DTSTART in VTIMEZONE takes a subset of valid RRULE + # values under RFC 5545. + for parm in parms: + if parm != 'VALUE=DATE-TIME': + msg = ('Unsupported DTSTART param in ' + + 'VTIMEZONE: ' + parm) + raise ValueError(msg) + rrulelines.append(line) + founddtstart = True + elif name in ("RRULE", "RDATE", "EXRULE", "EXDATE"): + rrulelines.append(line) + elif name == "TZOFFSETFROM": + if parms: + raise ValueError( + "unsupported %s parm: %s " % (name, parms[0])) + tzoffsetfrom = self._parse_offset(value) + elif name == "TZOFFSETTO": + if parms: + raise ValueError( + "unsupported TZOFFSETTO parm: "+parms[0]) + tzoffsetto = self._parse_offset(value) + elif name == "TZNAME": + if parms: + raise ValueError( + "unsupported TZNAME parm: "+parms[0]) + tzname = value + elif name == "COMMENT": + pass + else: + raise ValueError("unsupported property: "+name) + else: + if name == "TZID": + if parms: + raise ValueError( + "unsupported TZID parm: "+parms[0]) + tzid = value + elif name in ("TZURL", "LAST-MODIFIED", "COMMENT"): + pass + else: + raise ValueError("unsupported property: "+name) + elif name == "BEGIN" and value == "VTIMEZONE": + tzid = None + comps = [] + invtz = True + + def __repr__(self): + return "%s(%s)" % (self.__class__.__name__, repr(self._s)) + + +if sys.platform != "win32": + TZFILES = ["/etc/localtime", "localtime"] + TZPATHS = ["/usr/share/zoneinfo", + "/usr/lib/zoneinfo", + "/usr/share/lib/zoneinfo", + "/etc/zoneinfo"] +else: + TZFILES = [] + TZPATHS = [] + + +def __get_gettz(): + tzlocal_classes = (tzlocal,) + if tzwinlocal is not None: + tzlocal_classes += (tzwinlocal,) + + class GettzFunc(object): + """ + Retrieve a time zone object from a string representation + + This function is intended to retrieve the :py:class:`tzinfo` subclass + that best represents the time zone that would be used if a POSIX + `TZ variable`_ were set to the same value. + + If no argument or an empty string is passed to ``gettz``, local time + is returned: + + .. code-block:: python3 + + >>> gettz() + tzfile('/etc/localtime') + + This function is also the preferred way to map IANA tz database keys + to :class:`tzfile` objects: + + .. code-block:: python3 + + >>> gettz('Pacific/Kiritimati') + tzfile('/usr/share/zoneinfo/Pacific/Kiritimati') + + On Windows, the standard is extended to include the Windows-specific + zone names provided by the operating system: + + .. code-block:: python3 + + >>> gettz('Egypt Standard Time') + tzwin('Egypt Standard Time') + + Passing a GNU ``TZ`` style string time zone specification returns a + :class:`tzstr` object: + + .. code-block:: python3 + + >>> gettz('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3') + tzstr('AEST-10AEDT-11,M10.1.0/2,M4.1.0/3') + + :param name: + A time zone name (IANA, or, on Windows, Windows keys), location of + a ``tzfile(5)`` zoneinfo file or ``TZ`` variable style time zone + specifier. An empty string, no argument or ``None`` is interpreted + as local time. + + :return: + Returns an instance of one of ``dateutil``'s :py:class:`tzinfo` + subclasses. + + .. versionchanged:: 2.7.0 + + After version 2.7.0, any two calls to ``gettz`` using the same + input strings will return the same object: + + .. code-block:: python3 + + >>> tz.gettz('America/Chicago') is tz.gettz('America/Chicago') + True + + In addition to improving performance, this ensures that + `"same zone" semantics`_ are used for datetimes in the same zone. + + + .. _`TZ variable`: + https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html + + .. _`"same zone" semantics`: + https://blog.ganssle.io/articles/2018/02/aware-datetime-arithmetic.html + """ + def __init__(self): + + self.__instances = weakref.WeakValueDictionary() + self.__strong_cache_size = 8 + self.__strong_cache = OrderedDict() + self._cache_lock = _thread.allocate_lock() + + def __call__(self, name=None): + with self._cache_lock: + rv = self.__instances.get(name, None) + + if rv is None: + rv = self.nocache(name=name) + if not (name is None + or isinstance(rv, tzlocal_classes) + or rv is None): + # tzlocal is slightly more complicated than the other + # time zone providers because it depends on environment + # at construction time, so don't cache that. + # + # We also cannot store weak references to None, so we + # will also not store that. + self.__instances[name] = rv + else: + # No need for strong caching, return immediately + return rv + + self.__strong_cache[name] = self.__strong_cache.pop(name, rv) + + if len(self.__strong_cache) > self.__strong_cache_size: + self.__strong_cache.popitem(last=False) + + return rv + + def set_cache_size(self, size): + with self._cache_lock: + self.__strong_cache_size = size + while len(self.__strong_cache) > size: + self.__strong_cache.popitem(last=False) + + def cache_clear(self): + with self._cache_lock: + self.__instances = weakref.WeakValueDictionary() + self.__strong_cache.clear() + + @staticmethod + def nocache(name=None): + """A non-cached version of gettz""" + tz = None + if not name: + try: + name = os.environ["TZ"] + except KeyError: + pass + if name is None or name in ("", ":"): + for filepath in TZFILES: + if not os.path.isabs(filepath): + filename = filepath + for path in TZPATHS: + filepath = os.path.join(path, filename) + if os.path.isfile(filepath): + break + else: + continue + if os.path.isfile(filepath): + try: + tz = tzfile(filepath) + break + except (IOError, OSError, ValueError): + pass + else: + tz = tzlocal() + else: + try: + if name.startswith(":"): + name = name[1:] + except TypeError as e: + if isinstance(name, bytes): + new_msg = "gettz argument should be str, not bytes" + six.raise_from(TypeError(new_msg), e) + else: + raise + if os.path.isabs(name): + if os.path.isfile(name): + tz = tzfile(name) + else: + tz = None + else: + for path in TZPATHS: + filepath = os.path.join(path, name) + if not os.path.isfile(filepath): + filepath = filepath.replace(' ', '_') + if not os.path.isfile(filepath): + continue + try: + tz = tzfile(filepath) + break + except (IOError, OSError, ValueError): + pass + else: + tz = None + if tzwin is not None: + try: + tz = tzwin(name) + except (WindowsError, UnicodeEncodeError): + # UnicodeEncodeError is for Python 2.7 compat + tz = None + + if not tz: + from dateutil.zoneinfo import get_zonefile_instance + tz = get_zonefile_instance().get(name) + + if not tz: + for c in name: + # name is not a tzstr unless it has at least + # one offset. For short values of "name", an + # explicit for loop seems to be the fastest way + # To determine if a string contains a digit + if c in "0123456789": + try: + tz = tzstr(name) + except ValueError: + pass + break + else: + if name in ("GMT", "UTC"): + tz = UTC + elif name in time.tzname: + tz = tzlocal() + return tz + + return GettzFunc() + + +gettz = __get_gettz() +del __get_gettz + + +def datetime_exists(dt, tz=None): + """ + Given a datetime and a time zone, determine whether or not a given datetime + would fall in a gap. + + :param dt: + A :class:`datetime.datetime` (whose time zone will be ignored if ``tz`` + is provided.) + + :param tz: + A :class:`datetime.tzinfo` with support for the ``fold`` attribute. If + ``None`` or not provided, the datetime's own time zone will be used. + + :return: + Returns a boolean value whether or not the "wall time" exists in + ``tz``. + + .. versionadded:: 2.7.0 + """ + if tz is None: + if dt.tzinfo is None: + raise ValueError('Datetime is naive and no time zone provided.') + tz = dt.tzinfo + + dt = dt.replace(tzinfo=None) + + # This is essentially a test of whether or not the datetime can survive + # a round trip to UTC. + dt_rt = dt.replace(tzinfo=tz).astimezone(UTC).astimezone(tz) + dt_rt = dt_rt.replace(tzinfo=None) + + return dt == dt_rt + + +def datetime_ambiguous(dt, tz=None): + """ + Given a datetime and a time zone, determine whether or not a given datetime + is ambiguous (i.e if there are two times differentiated only by their DST + status). + + :param dt: + A :class:`datetime.datetime` (whose time zone will be ignored if ``tz`` + is provided.) + + :param tz: + A :class:`datetime.tzinfo` with support for the ``fold`` attribute. If + ``None`` or not provided, the datetime's own time zone will be used. + + :return: + Returns a boolean value whether or not the "wall time" is ambiguous in + ``tz``. + + .. versionadded:: 2.6.0 + """ + if tz is None: + if dt.tzinfo is None: + raise ValueError('Datetime is naive and no time zone provided.') + + tz = dt.tzinfo + + # If a time zone defines its own "is_ambiguous" function, we'll use that. + is_ambiguous_fn = getattr(tz, 'is_ambiguous', None) + if is_ambiguous_fn is not None: + try: + return tz.is_ambiguous(dt) + except Exception: + pass + + # If it doesn't come out and tell us it's ambiguous, we'll just check if + # the fold attribute has any effect on this particular date and time. + dt = dt.replace(tzinfo=tz) + wall_0 = enfold(dt, fold=0) + wall_1 = enfold(dt, fold=1) + + same_offset = wall_0.utcoffset() == wall_1.utcoffset() + same_dst = wall_0.dst() == wall_1.dst() + + return not (same_offset and same_dst) + + +def resolve_imaginary(dt): + """ + Given a datetime that may be imaginary, return an existing datetime. + + This function assumes that an imaginary datetime represents what the + wall time would be in a zone had the offset transition not occurred, so + it will always fall forward by the transition's change in offset. + + .. doctest:: + + >>> from dateutil import tz + >>> from datetime import datetime + >>> NYC = tz.gettz('America/New_York') + >>> print(tz.resolve_imaginary(datetime(2017, 3, 12, 2, 30, tzinfo=NYC))) + 2017-03-12 03:30:00-04:00 + + >>> KIR = tz.gettz('Pacific/Kiritimati') + >>> print(tz.resolve_imaginary(datetime(1995, 1, 1, 12, 30, tzinfo=KIR))) + 1995-01-02 12:30:00+14:00 + + As a note, :func:`datetime.astimezone` is guaranteed to produce a valid, + existing datetime, so a round-trip to and from UTC is sufficient to get + an extant datetime, however, this generally "falls back" to an earlier time + rather than falling forward to the STD side (though no guarantees are made + about this behavior). + + :param dt: + A :class:`datetime.datetime` which may or may not exist. + + :return: + Returns an existing :class:`datetime.datetime`. If ``dt`` was not + imaginary, the datetime returned is guaranteed to be the same object + passed to the function. + + .. versionadded:: 2.7.0 + """ + if dt.tzinfo is not None and not datetime_exists(dt): + + curr_offset = (dt + datetime.timedelta(hours=24)).utcoffset() + old_offset = (dt - datetime.timedelta(hours=24)).utcoffset() + + dt += curr_offset - old_offset + + return dt + + +def _datetime_to_timestamp(dt): + """ + Convert a :class:`datetime.datetime` object to an epoch timestamp in + seconds since January 1, 1970, ignoring the time zone. + """ + return (dt.replace(tzinfo=None) - EPOCH).total_seconds() + + +if sys.version_info >= (3, 6): + def _get_supported_offset(second_offset): + return second_offset +else: + def _get_supported_offset(second_offset): + # For python pre-3.6, round to full-minutes if that's not the case. + # Python's datetime doesn't accept sub-minute timezones. Check + # http://python.org/sf/1447945 or https://bugs.python.org/issue5288 + # for some information. + old_offset = second_offset + calculated_offset = 60 * ((second_offset + 30) // 60) + return calculated_offset + + +try: + # Python 3.7 feature + from contextlib import nullcontext as _nullcontext +except ImportError: + class _nullcontext(object): + """ + Class for wrapping contexts so that they are passed through in a + with statement. + """ + def __init__(self, context): + self.context = context + + def __enter__(self): + return self.context + + def __exit__(*args, **kwargs): + pass + +# vim:ts=4:sw=4:et diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/tz/win.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/tz/win.py new file mode 100644 index 0000000000000000000000000000000000000000..cde07ba792c40903f0c334839140173b39fd8124 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/tz/win.py @@ -0,0 +1,370 @@ +# -*- coding: utf-8 -*- +""" +This module provides an interface to the native time zone data on Windows, +including :py:class:`datetime.tzinfo` implementations. + +Attempting to import this module on a non-Windows platform will raise an +:py:obj:`ImportError`. +""" +# This code was originally contributed by Jeffrey Harris. +import datetime +import struct + +from six.moves import winreg +from six import text_type + +try: + import ctypes + from ctypes import wintypes +except ValueError: + # ValueError is raised on non-Windows systems for some horrible reason. + raise ImportError("Running tzwin on non-Windows system") + +from ._common import tzrangebase + +__all__ = ["tzwin", "tzwinlocal", "tzres"] + +ONEWEEK = datetime.timedelta(7) + +TZKEYNAMENT = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones" +TZKEYNAME9X = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Time Zones" +TZLOCALKEYNAME = r"SYSTEM\CurrentControlSet\Control\TimeZoneInformation" + + +def _settzkeyname(): + handle = winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) + try: + winreg.OpenKey(handle, TZKEYNAMENT).Close() + TZKEYNAME = TZKEYNAMENT + except WindowsError: + TZKEYNAME = TZKEYNAME9X + handle.Close() + return TZKEYNAME + + +TZKEYNAME = _settzkeyname() + + +class tzres(object): + """ + Class for accessing ``tzres.dll``, which contains timezone name related + resources. + + .. versionadded:: 2.5.0 + """ + p_wchar = ctypes.POINTER(wintypes.WCHAR) # Pointer to a wide char + + def __init__(self, tzres_loc='tzres.dll'): + # Load the user32 DLL so we can load strings from tzres + user32 = ctypes.WinDLL('user32') + + # Specify the LoadStringW function + user32.LoadStringW.argtypes = (wintypes.HINSTANCE, + wintypes.UINT, + wintypes.LPWSTR, + ctypes.c_int) + + self.LoadStringW = user32.LoadStringW + self._tzres = ctypes.WinDLL(tzres_loc) + self.tzres_loc = tzres_loc + + def load_name(self, offset): + """ + Load a timezone name from a DLL offset (integer). + + >>> from dateutil.tzwin import tzres + >>> tzr = tzres() + >>> print(tzr.load_name(112)) + 'Eastern Standard Time' + + :param offset: + A positive integer value referring to a string from the tzres dll. + + .. note:: + + Offsets found in the registry are generally of the form + ``@tzres.dll,-114``. The offset in this case is 114, not -114. + + """ + resource = self.p_wchar() + lpBuffer = ctypes.cast(ctypes.byref(resource), wintypes.LPWSTR) + nchar = self.LoadStringW(self._tzres._handle, offset, lpBuffer, 0) + return resource[:nchar] + + def name_from_string(self, tzname_str): + """ + Parse strings as returned from the Windows registry into the time zone + name as defined in the registry. + + >>> from dateutil.tzwin import tzres + >>> tzr = tzres() + >>> print(tzr.name_from_string('@tzres.dll,-251')) + 'Dateline Daylight Time' + >>> print(tzr.name_from_string('Eastern Standard Time')) + 'Eastern Standard Time' + + :param tzname_str: + A timezone name string as returned from a Windows registry key. + + :return: + Returns the localized timezone string from tzres.dll if the string + is of the form `@tzres.dll,-offset`, else returns the input string. + """ + if not tzname_str.startswith('@'): + return tzname_str + + name_splt = tzname_str.split(',-') + try: + offset = int(name_splt[1]) + except: + raise ValueError("Malformed timezone string.") + + return self.load_name(offset) + + +class tzwinbase(tzrangebase): + """tzinfo class based on win32's timezones available in the registry.""" + def __init__(self): + raise NotImplementedError('tzwinbase is an abstract base class') + + def __eq__(self, other): + # Compare on all relevant dimensions, including name. + if not isinstance(other, tzwinbase): + return NotImplemented + + return (self._std_offset == other._std_offset and + self._dst_offset == other._dst_offset and + self._stddayofweek == other._stddayofweek and + self._dstdayofweek == other._dstdayofweek and + self._stdweeknumber == other._stdweeknumber and + self._dstweeknumber == other._dstweeknumber and + self._stdhour == other._stdhour and + self._dsthour == other._dsthour and + self._stdminute == other._stdminute and + self._dstminute == other._dstminute and + self._std_abbr == other._std_abbr and + self._dst_abbr == other._dst_abbr) + + @staticmethod + def list(): + """Return a list of all time zones known to the system.""" + with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle: + with winreg.OpenKey(handle, TZKEYNAME) as tzkey: + result = [winreg.EnumKey(tzkey, i) + for i in range(winreg.QueryInfoKey(tzkey)[0])] + return result + + def display(self): + """ + Return the display name of the time zone. + """ + return self._display + + def transitions(self, year): + """ + For a given year, get the DST on and off transition times, expressed + always on the standard time side. For zones with no transitions, this + function returns ``None``. + + :param year: + The year whose transitions you would like to query. + + :return: + Returns a :class:`tuple` of :class:`datetime.datetime` objects, + ``(dston, dstoff)`` for zones with an annual DST transition, or + ``None`` for fixed offset zones. + """ + + if not self.hasdst: + return None + + dston = picknthweekday(year, self._dstmonth, self._dstdayofweek, + self._dsthour, self._dstminute, + self._dstweeknumber) + + dstoff = picknthweekday(year, self._stdmonth, self._stddayofweek, + self._stdhour, self._stdminute, + self._stdweeknumber) + + # Ambiguous dates default to the STD side + dstoff -= self._dst_base_offset + + return dston, dstoff + + def _get_hasdst(self): + return self._dstmonth != 0 + + @property + def _dst_base_offset(self): + return self._dst_base_offset_ + + +class tzwin(tzwinbase): + """ + Time zone object created from the zone info in the Windows registry + + These are similar to :py:class:`dateutil.tz.tzrange` objects in that + the time zone data is provided in the format of a single offset rule + for either 0 or 2 time zone transitions per year. + + :param: name + The name of a Windows time zone key, e.g. "Eastern Standard Time". + The full list of keys can be retrieved with :func:`tzwin.list`. + """ + + def __init__(self, name): + self._name = name + + with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle: + tzkeyname = text_type("{kn}\\{name}").format(kn=TZKEYNAME, name=name) + with winreg.OpenKey(handle, tzkeyname) as tzkey: + keydict = valuestodict(tzkey) + + self._std_abbr = keydict["Std"] + self._dst_abbr = keydict["Dlt"] + + self._display = keydict["Display"] + + # See http://ww_winreg.jsiinc.com/SUBA/tip0300/rh0398.htm + tup = struct.unpack("=3l16h", keydict["TZI"]) + stdoffset = -tup[0]-tup[1] # Bias + StandardBias * -1 + dstoffset = stdoffset-tup[2] # + DaylightBias * -1 + self._std_offset = datetime.timedelta(minutes=stdoffset) + self._dst_offset = datetime.timedelta(minutes=dstoffset) + + # for the meaning see the win32 TIME_ZONE_INFORMATION structure docs + # http://msdn.microsoft.com/en-us/library/windows/desktop/ms725481(v=vs.85).aspx + (self._stdmonth, + self._stddayofweek, # Sunday = 0 + self._stdweeknumber, # Last = 5 + self._stdhour, + self._stdminute) = tup[4:9] + + (self._dstmonth, + self._dstdayofweek, # Sunday = 0 + self._dstweeknumber, # Last = 5 + self._dsthour, + self._dstminute) = tup[12:17] + + self._dst_base_offset_ = self._dst_offset - self._std_offset + self.hasdst = self._get_hasdst() + + def __repr__(self): + return "tzwin(%s)" % repr(self._name) + + def __reduce__(self): + return (self.__class__, (self._name,)) + + +class tzwinlocal(tzwinbase): + """ + Class representing the local time zone information in the Windows registry + + While :class:`dateutil.tz.tzlocal` makes system calls (via the :mod:`time` + module) to retrieve time zone information, ``tzwinlocal`` retrieves the + rules directly from the Windows registry and creates an object like + :class:`dateutil.tz.tzwin`. + + Because Windows does not have an equivalent of :func:`time.tzset`, on + Windows, :class:`dateutil.tz.tzlocal` instances will always reflect the + time zone settings *at the time that the process was started*, meaning + changes to the machine's time zone settings during the run of a program + on Windows will **not** be reflected by :class:`dateutil.tz.tzlocal`. + Because ``tzwinlocal`` reads the registry directly, it is unaffected by + this issue. + """ + def __init__(self): + with winreg.ConnectRegistry(None, winreg.HKEY_LOCAL_MACHINE) as handle: + with winreg.OpenKey(handle, TZLOCALKEYNAME) as tzlocalkey: + keydict = valuestodict(tzlocalkey) + + self._std_abbr = keydict["StandardName"] + self._dst_abbr = keydict["DaylightName"] + + try: + tzkeyname = text_type('{kn}\\{sn}').format(kn=TZKEYNAME, + sn=self._std_abbr) + with winreg.OpenKey(handle, tzkeyname) as tzkey: + _keydict = valuestodict(tzkey) + self._display = _keydict["Display"] + except OSError: + self._display = None + + stdoffset = -keydict["Bias"]-keydict["StandardBias"] + dstoffset = stdoffset-keydict["DaylightBias"] + + self._std_offset = datetime.timedelta(minutes=stdoffset) + self._dst_offset = datetime.timedelta(minutes=dstoffset) + + # For reasons unclear, in this particular key, the day of week has been + # moved to the END of the SYSTEMTIME structure. + tup = struct.unpack("=8h", keydict["StandardStart"]) + + (self._stdmonth, + self._stdweeknumber, # Last = 5 + self._stdhour, + self._stdminute) = tup[1:5] + + self._stddayofweek = tup[7] + + tup = struct.unpack("=8h", keydict["DaylightStart"]) + + (self._dstmonth, + self._dstweeknumber, # Last = 5 + self._dsthour, + self._dstminute) = tup[1:5] + + self._dstdayofweek = tup[7] + + self._dst_base_offset_ = self._dst_offset - self._std_offset + self.hasdst = self._get_hasdst() + + def __repr__(self): + return "tzwinlocal()" + + def __str__(self): + # str will return the standard name, not the daylight name. + return "tzwinlocal(%s)" % repr(self._std_abbr) + + def __reduce__(self): + return (self.__class__, ()) + + +def picknthweekday(year, month, dayofweek, hour, minute, whichweek): + """ dayofweek == 0 means Sunday, whichweek 5 means last instance """ + first = datetime.datetime(year, month, 1, hour, minute) + + # This will work if dayofweek is ISO weekday (1-7) or Microsoft-style (0-6), + # Because 7 % 7 = 0 + weekdayone = first.replace(day=((dayofweek - first.isoweekday()) % 7) + 1) + wd = weekdayone + ((whichweek - 1) * ONEWEEK) + if (wd.month != month): + wd -= ONEWEEK + + return wd + + +def valuestodict(key): + """Convert a registry key's values to a dictionary.""" + dout = {} + size = winreg.QueryInfoKey(key)[1] + tz_res = None + + for i in range(size): + key_name, value, dtype = winreg.EnumValue(key, i) + if dtype == winreg.REG_DWORD or dtype == winreg.REG_DWORD_LITTLE_ENDIAN: + # If it's a DWORD (32-bit integer), it's stored as unsigned - convert + # that to a proper signed integer + if value & (1 << 31): + value = value - (1 << 32) + elif dtype == winreg.REG_SZ: + # If it's a reference to the tzres DLL, load the actual string + if value.startswith('@tzres'): + tz_res = tz_res or tzres() + value = tz_res.name_from_string(value) + + value = value.rstrip('\x00') # Remove trailing nulls + + dout[key_name] = value + + return dout diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/tzwin.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/tzwin.py new file mode 100644 index 0000000000000000000000000000000000000000..cebc673e40fc376653ebf037e96f0a6d0b33e906 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/tzwin.py @@ -0,0 +1,2 @@ +# tzwin has moved to dateutil.tz.win +from .tz.win import * diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/utils.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..dd2d245a0bebcd5fc37ac20526aabbd5358dab0e --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/utils.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +""" +This module offers general convenience and utility functions for dealing with +datetimes. + +.. versionadded:: 2.7.0 +""" +from __future__ import unicode_literals + +from datetime import datetime, time + + +def today(tzinfo=None): + """ + Returns a :py:class:`datetime` representing the current day at midnight + + :param tzinfo: + The time zone to attach (also used to determine the current day). + + :return: + A :py:class:`datetime.datetime` object representing the current day + at midnight. + """ + + dt = datetime.now(tzinfo) + return datetime.combine(dt.date(), time(0, tzinfo=tzinfo)) + + +def default_tzinfo(dt, tzinfo): + """ + Sets the ``tzinfo`` parameter on naive datetimes only + + This is useful for example when you are provided a datetime that may have + either an implicit or explicit time zone, such as when parsing a time zone + string. + + .. doctest:: + + >>> from dateutil.tz import tzoffset + >>> from dateutil.parser import parse + >>> from dateutil.utils import default_tzinfo + >>> dflt_tz = tzoffset("EST", -18000) + >>> print(default_tzinfo(parse('2014-01-01 12:30 UTC'), dflt_tz)) + 2014-01-01 12:30:00+00:00 + >>> print(default_tzinfo(parse('2014-01-01 12:30'), dflt_tz)) + 2014-01-01 12:30:00-05:00 + + :param dt: + The datetime on which to replace the time zone + + :param tzinfo: + The :py:class:`datetime.tzinfo` subclass instance to assign to + ``dt`` if (and only if) it is naive. + + :return: + Returns an aware :py:class:`datetime.datetime`. + """ + if dt.tzinfo is not None: + return dt + else: + return dt.replace(tzinfo=tzinfo) + + +def within_delta(dt1, dt2, delta): + """ + Useful for comparing two datetimes that may have a negligible difference + to be considered equal. + """ + delta = abs(delta) + difference = dt1 - dt2 + return -delta <= difference <= delta diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/zoneinfo/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/zoneinfo/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..34f11ad66c88047f2c049a4cdcc937b4b78ea6d6 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/zoneinfo/__init__.py @@ -0,0 +1,167 @@ +# -*- coding: utf-8 -*- +import warnings +import json + +from tarfile import TarFile +from pkgutil import get_data +from io import BytesIO + +from dateutil.tz import tzfile as _tzfile + +__all__ = ["get_zonefile_instance", "gettz", "gettz_db_metadata"] + +ZONEFILENAME = "dateutil-zoneinfo.tar.gz" +METADATA_FN = 'METADATA' + + +class tzfile(_tzfile): + def __reduce__(self): + return (gettz, (self._filename,)) + + +def getzoneinfofile_stream(): + try: + return BytesIO(get_data(__name__, ZONEFILENAME)) + except IOError as e: # TODO switch to FileNotFoundError? + warnings.warn("I/O error({0}): {1}".format(e.errno, e.strerror)) + return None + + +class ZoneInfoFile(object): + def __init__(self, zonefile_stream=None): + if zonefile_stream is not None: + with TarFile.open(fileobj=zonefile_stream) as tf: + self.zones = {zf.name: tzfile(tf.extractfile(zf), filename=zf.name) + for zf in tf.getmembers() + if zf.isfile() and zf.name != METADATA_FN} + # deal with links: They'll point to their parent object. Less + # waste of memory + links = {zl.name: self.zones[zl.linkname] + for zl in tf.getmembers() if + zl.islnk() or zl.issym()} + self.zones.update(links) + try: + metadata_json = tf.extractfile(tf.getmember(METADATA_FN)) + metadata_str = metadata_json.read().decode('UTF-8') + self.metadata = json.loads(metadata_str) + except KeyError: + # no metadata in tar file + self.metadata = None + else: + self.zones = {} + self.metadata = None + + def get(self, name, default=None): + """ + Wrapper for :func:`ZoneInfoFile.zones.get`. This is a convenience method + for retrieving zones from the zone dictionary. + + :param name: + The name of the zone to retrieve. (Generally IANA zone names) + + :param default: + The value to return in the event of a missing key. + + .. versionadded:: 2.6.0 + + """ + return self.zones.get(name, default) + + +# The current API has gettz as a module function, although in fact it taps into +# a stateful class. So as a workaround for now, without changing the API, we +# will create a new "global" class instance the first time a user requests a +# timezone. Ugly, but adheres to the api. +# +# TODO: Remove after deprecation period. +_CLASS_ZONE_INSTANCE = [] + + +def get_zonefile_instance(new_instance=False): + """ + This is a convenience function which provides a :class:`ZoneInfoFile` + instance using the data provided by the ``dateutil`` package. By default, it + caches a single instance of the ZoneInfoFile object and returns that. + + :param new_instance: + If ``True``, a new instance of :class:`ZoneInfoFile` is instantiated and + used as the cached instance for the next call. Otherwise, new instances + are created only as necessary. + + :return: + Returns a :class:`ZoneInfoFile` object. + + .. versionadded:: 2.6 + """ + if new_instance: + zif = None + else: + zif = getattr(get_zonefile_instance, '_cached_instance', None) + + if zif is None: + zif = ZoneInfoFile(getzoneinfofile_stream()) + + get_zonefile_instance._cached_instance = zif + + return zif + + +def gettz(name): + """ + This retrieves a time zone from the local zoneinfo tarball that is packaged + with dateutil. + + :param name: + An IANA-style time zone name, as found in the zoneinfo file. + + :return: + Returns a :class:`dateutil.tz.tzfile` time zone object. + + .. warning:: + It is generally inadvisable to use this function, and it is only + provided for API compatibility with earlier versions. This is *not* + equivalent to ``dateutil.tz.gettz()``, which selects an appropriate + time zone based on the inputs, favoring system zoneinfo. This is ONLY + for accessing the dateutil-specific zoneinfo (which may be out of + date compared to the system zoneinfo). + + .. deprecated:: 2.6 + If you need to use a specific zoneinfofile over the system zoneinfo, + instantiate a :class:`dateutil.zoneinfo.ZoneInfoFile` object and call + :func:`dateutil.zoneinfo.ZoneInfoFile.get(name)` instead. + + Use :func:`get_zonefile_instance` to retrieve an instance of the + dateutil-provided zoneinfo. + """ + warnings.warn("zoneinfo.gettz() will be removed in future versions, " + "to use the dateutil-provided zoneinfo files, instantiate a " + "ZoneInfoFile object and use ZoneInfoFile.zones.get() " + "instead. See the documentation for details.", + DeprecationWarning) + + if len(_CLASS_ZONE_INSTANCE) == 0: + _CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream())) + return _CLASS_ZONE_INSTANCE[0].zones.get(name) + + +def gettz_db_metadata(): + """ Get the zonefile metadata + + See `zonefile_metadata`_ + + :returns: + A dictionary with the database metadata + + .. deprecated:: 2.6 + See deprecation warning in :func:`zoneinfo.gettz`. To get metadata, + query the attribute ``zoneinfo.ZoneInfoFile.metadata``. + """ + warnings.warn("zoneinfo.gettz_db_metadata() will be removed in future " + "versions, to use the dateutil-provided zoneinfo files, " + "ZoneInfoFile object and query the 'metadata' attribute " + "instead. See the documentation for details.", + DeprecationWarning) + + if len(_CLASS_ZONE_INSTANCE) == 0: + _CLASS_ZONE_INSTANCE.append(ZoneInfoFile(getzoneinfofile_stream())) + return _CLASS_ZONE_INSTANCE[0].metadata diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/zoneinfo/rebuild.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/zoneinfo/rebuild.py new file mode 100644 index 0000000000000000000000000000000000000000..684c6586f091350c347f2b6150935f5214ffec27 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dateutil/zoneinfo/rebuild.py @@ -0,0 +1,75 @@ +import logging +import os +import tempfile +import shutil +import json +from subprocess import check_call, check_output +from tarfile import TarFile + +from dateutil.zoneinfo import METADATA_FN, ZONEFILENAME + + +def rebuild(filename, tag=None, format="gz", zonegroups=[], metadata=None): + """Rebuild the internal timezone info in dateutil/zoneinfo/zoneinfo*tar* + + filename is the timezone tarball from ``ftp.iana.org/tz``. + + """ + tmpdir = tempfile.mkdtemp() + zonedir = os.path.join(tmpdir, "zoneinfo") + moduledir = os.path.dirname(__file__) + try: + with TarFile.open(filename) as tf: + for name in zonegroups: + tf.extract(name, tmpdir) + filepaths = [os.path.join(tmpdir, n) for n in zonegroups] + + _run_zic(zonedir, filepaths) + + # write metadata file + with open(os.path.join(zonedir, METADATA_FN), 'w') as f: + json.dump(metadata, f, indent=4, sort_keys=True) + target = os.path.join(moduledir, ZONEFILENAME) + with TarFile.open(target, "w:%s" % format) as tf: + for entry in os.listdir(zonedir): + entrypath = os.path.join(zonedir, entry) + tf.add(entrypath, entry) + finally: + shutil.rmtree(tmpdir) + + +def _run_zic(zonedir, filepaths): + """Calls the ``zic`` compiler in a compatible way to get a "fat" binary. + + Recent versions of ``zic`` default to ``-b slim``, while older versions + don't even have the ``-b`` option (but default to "fat" binaries). The + current version of dateutil does not support Version 2+ TZif files, which + causes problems when used in conjunction with "slim" binaries, so this + function is used to ensure that we always get a "fat" binary. + """ + + try: + help_text = check_output(["zic", "--help"]) + except OSError as e: + _print_on_nosuchfile(e) + raise + + if b"-b " in help_text: + bloat_args = ["-b", "fat"] + else: + bloat_args = [] + + check_call(["zic"] + bloat_args + ["-d", zonedir] + filepaths) + + +def _print_on_nosuchfile(e): + """Print helpful troubleshooting message + + e is an exception raised by subprocess.check_call() + + """ + if e.errno == 2: + logging.error( + "Could not find zic. Perhaps you need to install " + "libc-bin or some other package that provides it, " + "or it's not in your PATH?") diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dill/_shims.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/_shims.py new file mode 100644 index 0000000000000000000000000000000000000000..a7a5bdf2f7c9dd6d05882337b7ad462a079551fc --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/_shims.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python +# +# Author: Mike McKerns (mmckerns @caltech and @uqfoundation) +# Author: Anirudh Vegesana (avegesan@cs.stanford.edu) +# Copyright (c) 2021-2026 The Uncertainty Quantification Foundation. +# License: 3-clause BSD. The full license text is available at: +# - https://github.com/uqfoundation/dill/blob/master/LICENSE +""" +Provides shims for compatibility between versions of dill and Python. + +Compatibility shims should be provided in this file. Here are two simple example +use cases. + +Deprecation of constructor function: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Assume that we were transitioning _import_module in _dill.py to +the builtin function importlib.import_module when present. + +@move_to(_dill) +def _import_module(import_name): + ... # code already in _dill.py + +_import_module = Getattr(importlib, 'import_module', Getattr(_dill, '_import_module', None)) + +The code will attempt to find import_module in the importlib module. If not +present, it will use the _import_module function in _dill. + +Emulate new Python behavior in older Python versions: +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +CellType.cell_contents behaves differently in Python 3.6 and 3.7. It is +read-only in Python 3.6 and writable and deletable in 3.7. + +if _dill.OLD37 and _dill.HAS_CTYPES and ...: + @move_to(_dill) + def _setattr(object, name, value): + if type(object) is _dill.CellType and name == 'cell_contents': + _PyCell_Set.argtypes = (ctypes.py_object, ctypes.py_object) + _PyCell_Set(object, value) + else: + setattr(object, name, value) +... # more cases below + +_setattr = Getattr(_dill, '_setattr', setattr) + +_dill._setattr will be used when present to emulate Python 3.7 functionality in +older versions of Python while defaulting to the standard setattr in 3.7+. + +See this PR for the discussion that lead to this system: +https://github.com/uqfoundation/dill/pull/443 +""" + +import inspect +import sys + +_dill = sys.modules['dill._dill'] + + +class Reduce(object): + """ + Reduce objects are wrappers used for compatibility enforcement during + unpickle-time. They should only be used in calls to pickler.save and + other Reduce objects. They are only evaluated within unpickler.load. + + Pickling a Reduce object makes the two implementations equivalent: + + pickler.save(Reduce(*reduction)) + + pickler.save_reduce(*reduction, obj=reduction) + """ + __slots__ = ['reduction'] + def __new__(cls, *reduction, **kwargs): + """ + Args: + *reduction: a tuple that matches the format given here: + https://docs.python.org/3/library/pickle.html#object.__reduce__ + is_callable: a bool to indicate that the object created by + unpickling `reduction` is callable. If true, the current Reduce + is allowed to be used as the function in further save_reduce calls + or Reduce objects. + """ + is_callable = kwargs.get('is_callable', False) # Pleases Py2. Can be removed later + if is_callable: + self = object.__new__(_CallableReduce) + else: + self = object.__new__(Reduce) + self.reduction = reduction + return self + def __repr__(self): + return 'Reduce%s' % (self.reduction,) + def __copy__(self): + return self # pragma: no cover + def __deepcopy__(self, memo): + return self # pragma: no cover + def __reduce__(self): + return self.reduction + def __reduce_ex__(self, protocol): + return self.__reduce__() + +class _CallableReduce(Reduce): + # A version of Reduce for functions. Used to trick pickler.save_reduce into + # thinking that Reduce objects of functions are themselves meaningful functions. + def __call__(self, *args, **kwargs): + reduction = self.__reduce__() + func = reduction[0] + f_args = reduction[1] + obj = func(*f_args) + return obj(*args, **kwargs) + +__NO_DEFAULT = _dill.Sentinel('Getattr.NO_DEFAULT') + +def Getattr(object, name, default=__NO_DEFAULT): + """ + A Reduce object that represents the getattr operation. When unpickled, the + Getattr will access an attribute 'name' of 'object' and return the value + stored there. If the attribute doesn't exist, the default value will be + returned if present. + + The following statements are equivalent: + + Getattr(collections, 'OrderedDict') + Getattr(collections, 'spam', None) + Getattr(*args) + + Reduce(getattr, (collections, 'OrderedDict')) + Reduce(getattr, (collections, 'spam', None)) + Reduce(getattr, args) + + During unpickling, the first two will result in collections.OrderedDict and + None respectively because the first attribute exists and the second one does + not, forcing it to use the default value given in the third argument. + """ + + if default is Getattr.NO_DEFAULT: + reduction = (getattr, (object, name)) + else: + reduction = (getattr, (object, name, default)) + + return Reduce(*reduction, is_callable=callable(default)) + +Getattr.NO_DEFAULT = __NO_DEFAULT +del __NO_DEFAULT + +def move_to(module, name=None): + def decorator(func): + if name is None: + fname = func.__name__ + else: + fname = name + module.__dict__[fname] = func + func.__module__ = module.__name__ + return func + return decorator + +def register_shim(name, default): + """ + A easier to understand and more compact way of "softly" defining a function. + These two pieces of code are equivalent: + + if _dill.OLD3X: + def _create_class(): + ... + _create_class = register_shim('_create_class', types.new_class) + + if _dill.OLD3X: + @move_to(_dill) + def _create_class(): + ... + _create_class = Getattr(_dill, '_create_class', types.new_class) + + Intuitively, it creates a function or object in the versions of dill/python + that require special reimplementations, and use a core library or default + implementation if that function or object does not exist. + """ + func = globals().get(name) + if func is not None: + _dill.__dict__[name] = func + func.__module__ = _dill.__name__ + + if default is Getattr.NO_DEFAULT: + reduction = (getattr, (_dill, name)) + else: + reduction = (getattr, (_dill, name, default)) + + return Reduce(*reduction, is_callable=callable(default)) + +###################### +## Compatibility Shims are defined below +###################### + +_CELL_EMPTY = register_shim('_CELL_EMPTY', None) + +_setattr = register_shim('_setattr', setattr) +_delattr = register_shim('_delattr', delattr) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dill/logger.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/logger.py new file mode 100644 index 0000000000000000000000000000000000000000..a33011227beaf8f6b8d11ec7b4c2247fcb6ef143 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/logger.py @@ -0,0 +1,285 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Author: Leonardo Gama (@leogama) +# Copyright (c) 2022-2026 The Uncertainty Quantification Foundation. +# License: 3-clause BSD. The full license text is available at: +# - https://github.com/uqfoundation/dill/blob/master/LICENSE +""" +Logging utilities for dill. + +The 'logger' object is dill's top-level logger. + +The 'adapter' object wraps the logger and implements a 'trace()' method that +generates a detailed tree-style trace for the pickling call at log level INFO. + +The 'trace()' function sets and resets dill's logger log level, enabling and +disabling the pickling trace. + +The trace shows a tree structure depicting the depth of each object serialized +*with dill save functions*, but not the ones that use save functions from +'pickle._Pickler.dispatch'. If the information is available, it also displays +the size in bytes that the object contributed to the pickle stream (including +its child objects). Sample trace output: + + >>> import dill, dill.tests + >>> dill.detect.trace(True) + >>> dill.dump_session(main=dill.tests) + ┬ M1: + ├┬ F2: + │└ # F2 [32 B] + ├┬ D2: + │├┬ T4: + ││└ # T4 [35 B] + │├┬ D2: + ││├┬ T4: + │││└ # T4 [50 B] + ││├┬ D2: + │││└ # D2 [84 B] + ││└ # D2 [413 B] + │└ # D2 [763 B] + └ # M1 [813 B] +""" + +__all__ = ['adapter', 'logger', 'trace'] + +import codecs +import contextlib +import locale +import logging +import math +import os +from functools import partial +from typing import TextIO, Union + +import dill + +# Tree drawing characters: Unicode to ASCII map. +ASCII_MAP = str.maketrans({"│": "|", "├": "|", "┬": "+", "└": "`"}) + +## Notes about the design choices ## + +# Here is some domumentation of the Standard Library's logging internals that +# can't be found completely in the official documentation. dill's logger is +# obtained by calling logging.getLogger('dill') and therefore is an instance of +# logging.getLoggerClass() at the call time. As this is controlled by the user, +# in order to add some functionality to it it's necessary to use a LoggerAdapter +# to wrap it, overriding some of the adapter's methods and creating new ones. +# +# Basic calling sequence +# ====================== +# +# Python's logging functionality can be conceptually divided into five steps: +# 0. Check logging level -> abort if call level is greater than logger level +# 1. Gather information -> construct a LogRecord from passed arguments and context +# 2. Filter (optional) -> discard message if the record matches a filter +# 3. Format -> format message with args, then format output string with message plus record +# 4. Handle -> write the formatted string to output as defined in the handler +# +# dill.logging.logger.log -> # or logger.info, etc. +# Logger.log -> \ +# Logger._log -> }- accept 'extra' parameter for custom record entries +# Logger.makeRecord -> / +# LogRecord.__init__ +# Logger.handle -> +# Logger.callHandlers -> +# Handler.handle -> +# Filterer.filter -> +# Filter.filter +# StreamHandler.emit -> +# Handler.format -> +# Formatter.format -> +# LogRecord.getMessage # does: record.message = msg % args +# Formatter.formatMessage -> +# PercentStyle.format # does: self._fmt % vars(record) +# +# NOTE: All methods from the second line on are from logging.__init__.py + +class TraceAdapter(logging.LoggerAdapter): + """ + Tracks object tree depth and calculates pickled object size. + + A single instance of this wraps the module's logger, as the logging API + doesn't allow setting it directly with a custom Logger subclass. The added + 'trace()' method receives a pickle instance as the first argument and + creates extra values to be added in the LogRecord from it, then calls + 'info()'. + + Usage of logger with 'trace()' method: + + >>> from dill.logger import adapter as logger #NOTE: not dill.logger.logger + >>> ... + >>> def save_atype(pickler, obj): + >>> logger.trace(pickler, "Message with %s and %r etc. placeholders", 'text', obj) + >>> ... + """ + def __init__(self, logger): + self.logger = logger + def addHandler(self, handler): + formatter = TraceFormatter("%(prefix)s%(message)s%(suffix)s", handler=handler) + handler.setFormatter(formatter) + self.logger.addHandler(handler) + def removeHandler(self, handler): + self.logger.removeHandler(handler) + def process(self, msg, kwargs): + # A no-op override, as we don't have self.extra. + return msg, kwargs + def trace_setup(self, pickler): + # Called by Pickler.dump(). + if not dill._dill.is_dill(pickler, child=False): + return + if self.isEnabledFor(logging.INFO): + pickler._trace_depth = 1 + pickler._size_stack = [] + else: + pickler._trace_depth = None + def trace(self, pickler, msg, *args, **kwargs): + if not hasattr(pickler, '_trace_depth'): + logger.info(msg, *args, **kwargs) + return + if pickler._trace_depth is None: + return + extra = kwargs.get('extra', {}) + pushed_obj = msg.startswith('#') + size = None + try: + # Streams are not required to be tellable. + size = pickler._file.tell() + frame = pickler.framer.current_frame + try: + size += frame.tell() + except AttributeError: + # PyPy may use a BytesBuilder as frame + size += len(frame) + except (AttributeError, TypeError): + pass + if size is not None: + if not pushed_obj: + pickler._size_stack.append(size) + else: + size -= pickler._size_stack.pop() + extra['size'] = size + if pushed_obj: + pickler._trace_depth -= 1 + extra['depth'] = pickler._trace_depth + kwargs['extra'] = extra + self.info(msg, *args, **kwargs) + if not pushed_obj: + pickler._trace_depth += 1 + +class TraceFormatter(logging.Formatter): + """ + Generates message prefix and suffix from record. + + This Formatter adds prefix and suffix strings to the log message in trace + mode (an also provides empty string defaults for normal logs). + """ + def __init__(self, *args, handler=None, **kwargs): + super().__init__(*args, **kwargs) + try: + encoding = handler.stream.encoding + if encoding is None: + raise AttributeError + except AttributeError: + encoding = locale.getpreferredencoding() + try: + encoding = codecs.lookup(encoding).name + except LookupError: + self.is_utf8 = False + else: + self.is_utf8 = (encoding == codecs.lookup('utf-8').name) + def format(self, record): + fields = {'prefix': "", 'suffix': ""} + if getattr(record, 'depth', 0) > 0: + if record.msg.startswith("#"): + prefix = (record.depth - 1)*"│" + "└" + elif record.depth == 1: + prefix = "┬" + else: + prefix = (record.depth - 2)*"│" + "├┬" + if not self.is_utf8: + prefix = prefix.translate(ASCII_MAP) + "-" + fields['prefix'] = prefix + " " + if hasattr(record, 'size') and record.size is not None and record.size >= 1: + # Show object size in human-readable form. + power = int(math.log(record.size, 2)) // 10 + size = record.size >> power*10 + fields['suffix'] = " [%d %sB]" % (size, "KMGTP"[power] + "i" if power else "") + vars(record).update(fields) + return super().format(record) + +logger = logging.getLogger('dill') +logger.propagate = False +adapter = TraceAdapter(logger) +stderr_handler = logging._StderrHandler() +adapter.addHandler(stderr_handler) + +def trace(arg: Union[bool, TextIO, str, os.PathLike] = None, *, mode: str = 'a') -> None: + """print a trace through the stack when pickling; useful for debugging + + With a single boolean argument, enable or disable the tracing. + + Example usage: + + >>> import dill + >>> dill.detect.trace(True) + >>> dill.dump_session() + + Alternatively, ``trace()`` can be used as a context manager. With no + arguments, it just takes care of restoring the tracing state on exit. + Either a file handle, or a file name and (optionally) a file mode may be + specitfied to redirect the tracing output in the ``with`` block context. A + log function is yielded by the manager so the user can write extra + information to the file. + + Example usage: + + >>> from dill import detect + >>> D = {'a': 42, 'b': {'x': None}} + >>> with detect.trace(): + >>> dumps(D) + ┬ D2: + ├┬ D2: + │└ # D2 [8 B] + └ # D2 [22 B] + >>> squared = lambda x: x**2 + >>> with detect.trace('output.txt', mode='w') as log: + >>> log("> D = %r", D) + >>> dumps(D) + >>> log("> squared = %r", squared) + >>> dumps(squared) + + Arguments: + arg: a boolean value, or an optional file-like or path-like object for the context manager + mode: mode string for ``open()`` if a file name is passed as the first argument + """ + if repr(arg) not in ('False', 'True'): + return TraceManager(file=arg, mode=mode) + logger.setLevel(logging.INFO if arg else logging.WARNING) + +class TraceManager(contextlib.AbstractContextManager): + """context manager version of trace(); can redirect the trace to a file""" + def __init__(self, file, mode): + self.file = file + self.mode = mode + self.redirect = file is not None + self.file_is_stream = hasattr(file, 'write') + def __enter__(self): + if self.redirect: + stderr_handler.flush() + if self.file_is_stream: + self.handler = logging.StreamHandler(self.file) + else: + self.handler = logging.FileHandler(self.file, self.mode) + adapter.removeHandler(stderr_handler) + adapter.addHandler(self.handler) + self.old_level = adapter.getEffectiveLevel() + adapter.setLevel(logging.INFO) + return adapter.info + def __exit__(self, *exc_info): + adapter.setLevel(self.old_level) + if self.redirect: + adapter.removeHandler(self.handler) + adapter.addHandler(stderr_handler) + if not self.file_is_stream: + self.handler.close() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..f97361a616dc1c75dfa85a88198be94714895c63 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/__init__.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +# +# Author: Mike McKerns (mmckerns @caltech and @uqfoundation) +# Copyright (c) 2018-2026 The Uncertainty Quantification Foundation. +# License: 3-clause BSD. The full license text is available at: +# - https://github.com/uqfoundation/dill/blob/master/LICENSE +""" +to run this test suite, first build and install `dill`. + + $ python -m pip install ../.. + + +then run the tests with: + + $ python -m dill.tests + + +or, if `nose` is installed: + + $ nosetests + +""" diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/__main__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/__main__.py new file mode 100644 index 0000000000000000000000000000000000000000..0c0f96826112595910e105c5fb6efcd29768304f --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/__main__.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +# +# Author: Mike McKerns (mmckerns @caltech and @uqfoundation) +# Copyright (c) 2018-2026 The Uncertainty Quantification Foundation. +# License: 3-clause BSD. The full license text is available at: +# - https://github.com/uqfoundation/dill/blob/master/LICENSE + +import glob +import os +import sys +import subprocess as sp +python = sys.executable +try: + import pox + python = pox.which_python(version=True) or python +except ImportError: + pass +shell = sys.platform[:3] == 'win' + +suite = os.path.dirname(__file__) or os.path.curdir +tests = glob.glob(suite + os.path.sep + 'test_*.py') + + +if __name__ == '__main__': + + failed = 0 + for test in tests: + p = sp.Popen([python, test], shell=shell).wait() + if p: + print('F', end='', flush=True) + failed = 1 + else: + print('.', end='', flush=True) + print('') + exit(failed) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_check.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_check.py new file mode 100644 index 0000000000000000000000000000000000000000..f052f83aad681bc0e19d19578902b5012b553fec --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_check.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python +# +# Author: Mike McKerns (mmckerns @caltech and @uqfoundation) +# Copyright (c) 2008-2016 California Institute of Technology. +# Copyright (c) 2016-2026 The Uncertainty Quantification Foundation. +# License: 3-clause BSD. The full license text is available at: +# - https://github.com/uqfoundation/dill/blob/master/LICENSE + +from dill import check +import sys + +from dill.temp import capture + + +#FIXME: this doesn't catch output... it's from the internal call +def raise_check(func, **kwds): + try: + with capture('stdout') as out: + check(func, **kwds) + except Exception: + e = sys.exc_info()[1] + raise AssertionError(str(e)) + else: + assert 'Traceback' not in out.getvalue() + finally: + out.close() + + +f = lambda x:x**2 + + +def test_simple(verbose=None): + raise_check(f, verbose=verbose) + + +def test_recurse(verbose=None): + raise_check(f, recurse=True, verbose=verbose) + + +def test_byref(verbose=None): + raise_check(f, byref=True, verbose=verbose) + + +def test_protocol(verbose=None): + raise_check(f, protocol=True, verbose=verbose) + + +def test_python(verbose=None): + raise_check(f, python=None, verbose=verbose) + + +#TODO: test incompatible versions +#TODO: test dump failure +#TODO: test load failure + + +if __name__ == '__main__': + test_simple() + test_recurse() + test_byref() + test_protocol() + test_python() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_classdef.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_classdef.py new file mode 100644 index 0000000000000000000000000000000000000000..ec7f268277e52683840fcf30b4927b32ce067bc4 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_classdef.py @@ -0,0 +1,340 @@ +#!/usr/bin/env python +# +# Author: Mike McKerns (mmckerns @caltech and @uqfoundation) +# Copyright (c) 2008-2016 California Institute of Technology. +# Copyright (c) 2016-2026 The Uncertainty Quantification Foundation. +# License: 3-clause BSD. The full license text is available at: +# - https://github.com/uqfoundation/dill/blob/master/LICENSE + +import dill +from enum import EnumMeta +import sys +dill.settings['recurse'] = True + +# test classdefs +class _class: + def _method(self): + pass + def ok(self): + return True + +class _class2: + def __call__(self): + pass + def ok(self): + return True + +class _newclass(object): + def _method(self): + pass + def ok(self): + return True + +class _newclass2(object): + def __call__(self): + pass + def ok(self): + return True + +class _meta(type): + pass + +def __call__(self): + pass +def ok(self): + return True + +_mclass = _meta("_mclass", (object,), {"__call__": __call__, "ok": ok}) + +del __call__ +del ok + +o = _class() +oc = _class2() +n = _newclass() +nc = _newclass2() +m = _mclass() + +if sys.hexversion < 0x03090000: + import typing + class customIntList(typing.List[int]): + pass +else: + class customIntList(list[int]): + pass + +# test pickles for class instances +def test_class_instances(): + assert dill.pickles(o) + assert dill.pickles(oc) + assert dill.pickles(n) + assert dill.pickles(nc) + assert dill.pickles(m) + +def test_class_objects(): + clslist = [_class,_class2,_newclass,_newclass2,_mclass] + objlist = [o,oc,n,nc,m] + _clslist = [dill.dumps(obj) for obj in clslist] + _objlist = [dill.dumps(obj) for obj in objlist] + + for obj in clslist: + globals().pop(obj.__name__) + del clslist + for obj in ['o','oc','n','nc']: + globals().pop(obj) + del objlist + del obj + + for obj,cls in zip(_objlist,_clslist): + _cls = dill.loads(cls) + _obj = dill.loads(obj) + assert _obj.ok() + assert _cls.ok(_cls()) + if _cls.__name__ == "_mclass": + assert type(_cls).__name__ == "_meta" + +# test NoneType +def test_specialtypes(): + assert dill.pickles(type(None)) + assert dill.pickles(type(NotImplemented)) + assert dill.pickles(type(Ellipsis)) + assert dill.pickles(type(EnumMeta)) + +from collections import namedtuple +Z = namedtuple("Z", ['a','b']) +Zi = Z(0,1) +X = namedtuple("Y", ['a','b']) +X.__name__ = "X" +X.__qualname__ = "X" #XXX: name must 'match' or fails to pickle +Xi = X(0,1) +Bad = namedtuple("FakeName", ['a','b']) +Badi = Bad(0,1) +Defaults = namedtuple('Defaults', ['x', 'y'], defaults=[1]) +Defaultsi = Defaults(2) + +# test namedtuple +def test_namedtuple(): + assert Z is dill.loads(dill.dumps(Z)) + assert Zi == dill.loads(dill.dumps(Zi)) + assert X is dill.loads(dill.dumps(X)) + assert Xi == dill.loads(dill.dumps(Xi)) + assert Defaults is dill.loads(dill.dumps(Defaults)) + assert Defaultsi == dill.loads(dill.dumps(Defaultsi)) + assert Bad is not dill.loads(dill.dumps(Bad)) + assert Bad._fields == dill.loads(dill.dumps(Bad))._fields + assert tuple(Badi) == tuple(dill.loads(dill.dumps(Badi))) + + class A: + class B(namedtuple("C", ["one", "two"])): + '''docstring''' + B.__module__ = 'testing' + + a = A() + assert dill.copy(a) + + assert dill.copy(A.B).__name__ == 'B' + assert dill.copy(A.B).__qualname__.endswith('..A.B') + assert dill.copy(A.B).__doc__ == 'docstring' + assert dill.copy(A.B).__module__ == 'testing' + + from typing import NamedTuple + + def A(): + class B(NamedTuple): + x: int + return B + + assert type(dill.copy(A()(8))).__qualname__ == type(A()(8)).__qualname__ + +def test_dtype(): + try: + import numpy as np + + dti = np.dtype('int') + assert np.dtype == dill.copy(np.dtype) + assert dti == dill.copy(dti) + except ImportError: pass + + +def test_array_nested(): + try: + import numpy as np + + x = np.array([1]) + y = (x,) + assert y == dill.copy(y) + + except ImportError: pass + + +def test_array_subclass(): + try: + import numpy as np + + class TestArray(np.ndarray): + def __new__(cls, input_array, color): + obj = np.asarray(input_array).view(cls) + obj.color = color + return obj + def __array_finalize__(self, obj): + if obj is None: + return + if isinstance(obj, type(self)): + self.color = obj.color + def __getnewargs__(self): + return np.asarray(self), self.color + + a1 = TestArray(np.zeros(100), color='green') + if not dill._dill.IS_PYPY: + assert dill.pickles(a1) + assert a1.__dict__ == dill.copy(a1).__dict__ + + a2 = a1[0:9] + if not dill._dill.IS_PYPY: + assert dill.pickles(a2) + assert a2.__dict__ == dill.copy(a2).__dict__ + + class TestArray2(np.ndarray): + color = 'blue' + + a3 = TestArray2([1,2,3,4,5]) + a3.color = 'green' + if not dill._dill.IS_PYPY: + assert dill.pickles(a3) + assert a3.__dict__ == dill.copy(a3).__dict__ + + except ImportError: pass + + +def test_method_decorator(): + class A(object): + @classmethod + def test(cls): + pass + + a = A() + + res = dill.dumps(a) + new_obj = dill.loads(res) + new_obj.__class__.test() + +# test slots +class Y(object): + __slots__ = ('y', '__weakref__') + def __init__(self, y): + self.y = y + +value = 123 +y = Y(value) + +class Y2(object): + __slots__ = 'y' + def __init__(self, y): + self.y = y + +def test_slots(): + assert dill.pickles(Y) + assert dill.pickles(y) + assert dill.pickles(Y.y) + assert dill.copy(y).y == value + assert dill.copy(Y2(value)).y == value + +def test_origbases(): + assert dill.copy(customIntList).__orig_bases__ == customIntList.__orig_bases__ + +def test_attr(): + import attr + @attr.s + class A: + a = attr.ib() + + v = A(1) + assert dill.copy(v) == v + +def test_metaclass(): + class metaclass_with_new(type): + def __new__(mcls, name, bases, ns, **kwds): + cls = super().__new__(mcls, name, bases, ns, **kwds) + assert mcls is not None + assert cls.method(mcls) + return cls + def method(cls, mcls): + return isinstance(cls, mcls) + + l = locals() + exec("""class subclass_with_new(metaclass=metaclass_with_new): + def __new__(cls): + self = super().__new__(cls) + return self""", None, l) + subclass_with_new = l['subclass_with_new'] + + assert dill.copy(subclass_with_new()) + +def test_enummeta(): + from http import HTTPStatus + import enum + assert dill.copy(HTTPStatus.OK) is HTTPStatus.OK + assert dill.copy(enum.EnumMeta) is enum.EnumMeta + +def test_inherit(): #NOTE: see issue #612 + class Foo: + w = 0 + x = 1 + y = 1.1 + a = () + b = (1,) + n = None + + class Bar(Foo): + w = 2 + x = 1 + y = 1.1 + z = 0.2 + a = () + b = (1,) + c = (2,) + n = None + + Baz = dill.copy(Bar) + + import platform + is_pypy = platform.python_implementation() == 'PyPy' + assert Bar.__dict__ == Baz.__dict__ + # ints + assert 'w' in Bar.__dict__ and 'w' in Baz.__dict__ + assert Bar.__dict__['w'] is Baz.__dict__['w'] + assert 'x' in Bar.__dict__ and 'x' in Baz.__dict__ + assert Bar.__dict__['x'] is Baz.__dict__['x'] + # floats + assert 'y' in Bar.__dict__ and 'y' in Baz.__dict__ + same = Bar.__dict__['y'] is Baz.__dict__['y'] + assert same if is_pypy else not same + assert 'z' in Bar.__dict__ and 'z' in Baz.__dict__ + same = Bar.__dict__['z'] is Baz.__dict__['z'] + assert same if is_pypy else not same + # tuples + assert 'a' in Bar.__dict__ and 'a' in Baz.__dict__ + assert Bar.__dict__['a'] is Baz.__dict__['a'] + assert 'b' in Bar.__dict__ and 'b' in Baz.__dict__ + assert Bar.__dict__['b'] is not Baz.__dict__['b'] + assert 'c' in Bar.__dict__ and 'c' in Baz.__dict__ + assert Bar.__dict__['c'] is not Baz.__dict__['c'] + # None + assert 'n' in Bar.__dict__ and 'n' in Baz.__dict__ + assert Bar.__dict__['n'] is Baz.__dict__['n'] + + +if __name__ == '__main__': + test_class_instances() + test_class_objects() + test_specialtypes() + test_namedtuple() + test_dtype() + test_array_nested() + test_array_subclass() + test_method_decorator() + test_slots() + test_origbases() + test_metaclass() + test_enummeta() + test_inherit() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_detect.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_detect.py new file mode 100644 index 0000000000000000000000000000000000000000..b232b6acc89c74058fc2c708f98a39ca3fb6db9a --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_detect.py @@ -0,0 +1,163 @@ +#!/usr/bin/env python +# +# Author: Mike McKerns (mmckerns @caltech and @uqfoundation) +# Copyright (c) 2008-2016 California Institute of Technology. +# Copyright (c) 2016-2026 The Uncertainty Quantification Foundation. +# License: 3-clause BSD. The full license text is available at: +# - https://github.com/uqfoundation/dill/blob/master/LICENSE + +from dill.detect import baditems, badobjects, badtypes, errors, parent, at, globalvars +from dill import settings +from dill._dill import IS_PYPY +from pickle import PicklingError + +import inspect +import sys +import os + +def test_bad_things(): + f = inspect.currentframe() + assert baditems(f) == [f] + #assert baditems(globals()) == [f] #XXX + assert badobjects(f) is f + assert badtypes(f) == type(f) + assert type(errors(f)) is TypeError + d = badtypes(f, 1) + assert isinstance(d, dict) + assert list(badobjects(f, 1).keys()) == list(d.keys()) + assert list(errors(f, 1).keys()) == list(d.keys()) + s = set([(err.__class__.__name__,err.args[0]) for err in list(errors(f, 1).values())]) + a = dict(s) + if not os.environ.get('COVERAGE'): #XXX: travis-ci + proxy = 0 if type(f.f_locals) is dict else 1 + assert len(s) == len(a) + proxy # TypeError (and possibly PicklingError) + n = 2 + assert len(a) is n if 'PicklingError' in a.keys() else n-1 + +def test_parent(): + x = [4,5,6,7] + listiter = iter(x) + obj = parent(listiter, list) + assert obj is x + + if IS_PYPY: assert parent(obj, int) is None + else: assert parent(obj, int) is x[-1] # python oddly? finds last int + assert at(id(at)) is at + +a, b, c = 1, 2, 3 + +def squared(x): + return a+x**2 + +def foo(x): + def bar(y): + return squared(x)+y + return bar + +class _class: + def _method(self): + pass + def ok(self): + return True + +def test_globals(): + def f(): + a + def g(): + b + def h(): + c + assert globalvars(f) == dict(a=1, b=2, c=3) + + res = globalvars(foo, recurse=True) + assert set(res) == set(['squared', 'a']) + res = globalvars(foo, recurse=False) + assert res == {} + zap = foo(2) + res = globalvars(zap, recurse=True) + assert set(res) == set(['squared', 'a']) + res = globalvars(zap, recurse=False) + assert set(res) == set(['squared']) + del zap + res = globalvars(squared) + assert set(res) == set(['a']) + # FIXME: should find referenced __builtins__ + #res = globalvars(_class, recurse=True) + #assert set(res) == set(['True']) + #res = globalvars(_class, recurse=False) + #assert res == {} + #res = globalvars(_class.ok, recurse=True) + #assert set(res) == set(['True']) + #res = globalvars(_class.ok, recurse=False) + #assert set(res) == set(['True']) + + +#98 dill ignores __getstate__ in interactive lambdas +bar = [0] + +class Foo(object): + def __init__(self): + pass + def __getstate__(self): + bar[0] = bar[0]+1 + return {} + def __setstate__(self, data): + pass + +f = Foo() + +def test_getstate(): + from dill import dumps, loads + dumps(f) + b = bar[0] + dumps(lambda: f, recurse=False) # doesn't call __getstate__ + assert bar[0] == b + dumps(lambda: f, recurse=True) # calls __getstate__ + assert bar[0] == b + 1 + +#97 serialize lambdas in test files +def test_deleted(): + global sin + from dill import dumps, loads + from math import sin, pi + + def sinc(x): + return sin(x)/x + + settings['recurse'] = True + _sinc = dumps(sinc) + sin = globals().pop('sin') + sin = 1 + del sin + sinc_ = loads(_sinc) # no NameError... pickling preserves 'sin' + res = sinc_(1) + from math import sin + assert sinc(1) == res + + +def test_lambdify(): + try: + from sympy import symbols, lambdify + from numpy import __version__ as numversion + if numversion < '2.4.0' and sys.hexversion == 0x30f00a3: + return #NOTE: numpy Segfaults for the above combination + except ImportError: + return + settings['recurse'] = True + x = symbols("x") + y = x**2 + f = lambdify([x], y) + z = min + d = globals() + globalvars(f, recurse=True, builtin=True) + assert z is min + assert d is globals() + + +if __name__ == '__main__': + test_bad_things() + test_parent() + test_globals() + test_getstate() + test_deleted() + test_lambdify() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_dictviews.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_dictviews.py new file mode 100644 index 0000000000000000000000000000000000000000..d54441510bd28b33028ba8a25d7e6b9d23e20f1b --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_dictviews.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python +# +# Author: Mike McKerns (mmckerns @caltech and @uqfoundation) +# Author: Anirudh Vegesana (avegesan@cs.stanford.edu) +# Copyright (c) 2021-2026 The Uncertainty Quantification Foundation. +# License: 3-clause BSD. The full license text is available at: +# - https://github.com/uqfoundation/dill/blob/master/LICENSE + +import dill +from dill._dill import OLD310, MAPPING_PROXY_TRICK, DictProxyType + +def test_dictproxy(): + assert dill.copy(DictProxyType({'a': 2})) + +def test_dictviews(): + x = {'a': 1} + assert dill.copy(x.keys()) + assert dill.copy(x.values()) + assert dill.copy(x.items()) + +def test_dictproxy_trick(): + if not OLD310 and MAPPING_PROXY_TRICK: + x = {'a': 1} + all_views = (x.values(), x.items(), x.keys(), x) + seperate_views = dill.copy(all_views) + new_x = seperate_views[-1] + new_x['b'] = 2 + new_x['c'] = 1 + assert len(new_x) == 3 and len(x) == 1 + assert len(seperate_views[0]) == 3 and len(all_views[0]) == 1 + assert len(seperate_views[1]) == 3 and len(all_views[1]) == 1 + assert len(seperate_views[2]) == 3 and len(all_views[2]) == 1 + assert dict(all_views[1]) == x + assert dict(seperate_views[1]) == new_x + +if __name__ == '__main__': + test_dictproxy() + test_dictviews() + test_dictproxy_trick() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_diff.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_diff.py new file mode 100644 index 0000000000000000000000000000000000000000..bb8874c7fc5704717d022d28d204635df5e9186d --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_diff.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python +# +# Author: Mike McKerns (mmckerns @caltech and @uqfoundation) +# Copyright (c) 2008-2016 California Institute of Technology. +# Copyright (c) 2016-2026 The Uncertainty Quantification Foundation. +# License: 3-clause BSD. The full license text is available at: +# - https://github.com/uqfoundation/dill/blob/master/LICENSE + +from dill import __diff as diff + +import sys +IS_PYPY = not hasattr(sys, 'getrefcount') + +class A: + pass + +def test_diff(): + a = A() + b = A() + c = A() + a.a = b + b.a = c + diff.memorise(a) + assert not diff.has_changed(a) + c.a = 1 + assert diff.has_changed(a) + diff.memorise(c, force=True) + assert not diff.has_changed(a) + c.a = 2 + assert diff.has_changed(a) + changed = diff.whats_changed(a) + assert list(changed[0].keys()) == ["a"] + assert not changed[1] + + a2 = [] + b2 = [a2] + c2 = [b2] + diff.memorise(c2) + assert not diff.has_changed(c2) + a2.append(1) + assert diff.has_changed(c2) + changed = diff.whats_changed(c2) + assert changed[0] == {} + assert changed[1] + + a3 = {} + b3 = {1: a3} + c3 = {1: b3} + diff.memorise(c3) + assert not diff.has_changed(c3) + a3[1] = 1 + assert diff.has_changed(c3) + changed = diff.whats_changed(c3) + assert changed[0] == {} + assert changed[1] + + if not IS_PYPY: + import abc + # make sure the "_abc_invaldation_counter" doesn't make test fail + diff.memorise(abc.ABCMeta, force=True) + assert not diff.has_changed(abc) + abc.ABCMeta.zzz = 1 + assert diff.has_changed(abc) + changed = diff.whats_changed(abc) + assert list(changed[0].keys()) == ["ABCMeta"] + assert not changed[1] + + ''' + import Queue + diff.memorise(Queue, force=True) + assert not diff.has_changed(Queue) + Queue.Queue.zzz = 1 + assert diff.has_changed(Queue) + changed = diff.whats_changed(Queue) + assert list(changed[0].keys()) == ["Queue"] + assert not changed[1] + + import math + diff.memorise(math, force=True) + assert not diff.has_changed(math) + math.zzz = 1 + assert diff.has_changed(math) + changed = diff.whats_changed(math) + assert list(changed[0].keys()) == ["zzz"] + assert not changed[1] + ''' + + a = A() + b = A() + c = A() + a.a = b + b.a = c + diff.memorise(a) + assert not diff.has_changed(a) + c.a = 1 + assert diff.has_changed(a) + diff.memorise(c, force=True) + assert not diff.has_changed(a) + del c.a + assert diff.has_changed(a) + changed = diff.whats_changed(a) + assert list(changed[0].keys()) == ["a"] + assert not changed[1] + + +if __name__ == '__main__': + test_diff() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_extendpickle.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_extendpickle.py new file mode 100644 index 0000000000000000000000000000000000000000..f86e44a55a1c8ff57ff878efcc1bf5c38091da5c --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_extendpickle.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python +# +# Author: Mike McKerns (mmckerns @caltech and @uqfoundation) +# Copyright (c) 2008-2016 California Institute of Technology. +# Copyright (c) 2016-2026 The Uncertainty Quantification Foundation. +# License: 3-clause BSD. The full license text is available at: +# - https://github.com/uqfoundation/dill/blob/master/LICENSE + +import dill as pickle +from io import BytesIO as StringIO + + +def my_fn(x): + return x * 17 + + +def test_extend(): + obj = lambda : my_fn(34) + assert obj() == 578 + + obj_io = StringIO() + pickler = pickle.Pickler(obj_io) + pickler.dump(obj) + + obj_str = obj_io.getvalue() + + obj2_io = StringIO(obj_str) + unpickler = pickle.Unpickler(obj2_io) + obj2 = unpickler.load() + + assert obj2() == 578 + + +def test_isdill(): + obj_io = StringIO() + pickler = pickle.Pickler(obj_io) + assert pickle._dill.is_dill(pickler) is True + + pickler = pickle._dill.StockPickler(obj_io) + assert pickle._dill.is_dill(pickler) is False + + try: + import multiprocess as mp + pickler = mp.reduction.ForkingPickler(obj_io) + assert pickle._dill.is_dill(pickler, child=True) is True + assert pickle._dill.is_dill(pickler, child=False) is False + except Exception: + pass + + +if __name__ == '__main__': + test_extend() + test_isdill() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_fglobals.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_fglobals.py new file mode 100644 index 0000000000000000000000000000000000000000..aa93850e3e5cbf89dfa8f90169b0f285f470e153 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_fglobals.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python +# +# Author: Mike McKerns (mmckerns @caltech and @uqfoundation) +# Copyright (c) 2021-2026 The Uncertainty Quantification Foundation. +# License: 3-clause BSD. The full license text is available at: +# - https://github.com/uqfoundation/dill/blob/master/LICENSE + +import dill +dill.settings['recurse'] = True + +def get_fun_with_strftime(): + def fun_with_strftime(): + import datetime + return datetime.datetime.strptime("04-01-1943", "%d-%m-%Y").strftime( + "%Y-%m-%d %H:%M:%S" + ) + return fun_with_strftime + + +def get_fun_with_strftime2(): + import datetime + return datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + + +def test_doc_dill_issue_219(): + back_fn = dill.loads(dill.dumps(get_fun_with_strftime())) + assert back_fn() == "1943-01-04 00:00:00" + dupl = dill.loads(dill.dumps(get_fun_with_strftime2)) + assert dupl() == get_fun_with_strftime2() + + +def get_fun_with_internal_import(): + def fun_with_import(): + import re + return re.compile("$") + return fun_with_import + + +def test_method_with_internal_import_should_work(): + import re + back_fn = dill.loads(dill.dumps(get_fun_with_internal_import())) + import inspect + if hasattr(inspect, 'getclosurevars'): + vars = inspect.getclosurevars(back_fn) + assert vars.globals == {} + assert vars.nonlocals == {} + assert back_fn() == re.compile("$") + assert "__builtins__" in back_fn.__globals__ + + +if __name__ == "__main__": + import sys + if (sys.version_info[:3] != (3,10,0) or sys.version_info[3] != 'alpha'): + test_doc_dill_issue_219() + test_method_with_internal_import_should_work() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_file.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_file.py new file mode 100644 index 0000000000000000000000000000000000000000..6d2b2565201a1b5cab622aafe5062df36ed30b9e --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_file.py @@ -0,0 +1,500 @@ +#!/usr/bin/env python +# +# Author: Mike McKerns (mmckerns @caltech and @uqfoundation) +# Copyright (c) 2008-2016 California Institute of Technology. +# Copyright (c) 2016-2026 The Uncertainty Quantification Foundation. +# License: 3-clause BSD. The full license text is available at: +# - https://github.com/uqfoundation/dill/blob/master/LICENSE + +import os +import sys +import string +import random + +import dill + + +dill.settings['recurse'] = True + +fname = "_test_file.txt" +rand_chars = list(string.ascii_letters) + ["\n"] * 40 # bias newline + +buffer_error = ValueError("invalid buffer size") +dne_error = FileNotFoundError("[Errno 2] No such file or directory: '%s'" % fname) + + +def write_randomness(number=200): + f = open(fname, "w") + for i in range(number): + f.write(random.choice(rand_chars)) + f.close() + f = open(fname, "r") + contents = f.read() + f.close() + return contents + + +def trunc_file(): + open(fname, "w").close() + + +def throws(op, args, exc): + try: + op(*args) + except type(exc): + return sys.exc_info()[1].args == exc.args + else: + return False + + +def teardown_module(): + if os.path.exists(fname): + os.remove(fname) + + +def bench(strictio, fmode, skippypy): + import platform + if skippypy and platform.python_implementation() == 'PyPy': + # Skip for PyPy... + return + + # file exists, with same contents + # read + + write_randomness() + + f = open(fname, "r") + _f = dill.loads(dill.dumps(f, fmode=fmode))#, strictio=strictio)) + assert _f.mode == f.mode + assert _f.tell() == f.tell() + assert _f.read() == f.read() + f.close() + _f.close() + + # write + + f = open(fname, "w") + f.write("hello") + f_dumped = dill.dumps(f, fmode=fmode)#, strictio=strictio) + f1mode = f.mode + ftell = f.tell() + f.close() + f2 = dill.loads(f_dumped) #FIXME: fails due to pypy/issues/1233 + # TypeError: expected py_object instance instead of str + f2mode = f2.mode + f2tell = f2.tell() + f2name = f2.name + f2.write(" world!") + f2.close() + + if fmode == dill.HANDLE_FMODE: + assert open(fname).read() == " world!" + assert f2mode == f1mode + assert f2tell == 0 + elif fmode == dill.CONTENTS_FMODE: + assert open(fname).read() == "hello world!" + assert f2mode == f1mode + assert f2tell == ftell + assert f2name == fname + elif fmode == dill.FILE_FMODE: + assert open(fname).read() == "hello world!" + assert f2mode == f1mode + assert f2tell == ftell + else: + raise RuntimeError("Unknown file mode '%s'" % fmode) + + # append + + trunc_file() + + f = open(fname, "a") + f.write("hello") + f_dumped = dill.dumps(f, fmode=fmode)#, strictio=strictio) + f1mode = f.mode + ftell = f.tell() + f.close() + f2 = dill.loads(f_dumped) + f2mode = f2.mode + f2tell = f2.tell() + f2.write(" world!") + f2.close() + + assert f2mode == f1mode + if fmode == dill.CONTENTS_FMODE: + assert open(fname).read() == "hello world!" + assert f2tell == ftell + elif fmode == dill.HANDLE_FMODE: + assert open(fname).read() == "hello world!" + assert f2tell == ftell + elif fmode == dill.FILE_FMODE: + assert open(fname).read() == "hello world!" + assert f2tell == ftell + else: + raise RuntimeError("Unknown file mode '%s'" % fmode) + + # file exists, with different contents (smaller size) + # read + + write_randomness() + + f = open(fname, "r") + fstr = f.read() + f_dumped = dill.dumps(f, fmode=fmode)#, strictio=strictio) + f1mode = f.mode + ftell = f.tell() + f.close() + _flen = 150 + _fstr = write_randomness(number=_flen) + + if strictio: # throw error if ftell > EOF + assert throws(dill.loads, (f_dumped,), buffer_error) + else: + f2 = dill.loads(f_dumped) + assert f2.mode == f1mode + if fmode == dill.CONTENTS_FMODE: + assert f2.tell() == _flen + assert f2.read() == "" + f2.seek(0) + assert f2.read() == _fstr + assert f2.tell() == _flen # 150 + elif fmode == dill.HANDLE_FMODE: + assert f2.tell() == 0 + assert f2.read() == _fstr + assert f2.tell() == _flen # 150 + elif fmode == dill.FILE_FMODE: + assert f2.tell() == ftell # 200 + assert f2.read() == "" + f2.seek(0) + assert f2.read() == fstr + assert f2.tell() == ftell # 200 + else: + raise RuntimeError("Unknown file mode '%s'" % fmode) + f2.close() + + # write + + write_randomness() + + f = open(fname, "w") + f.write("hello") + f_dumped = dill.dumps(f, fmode=fmode)#, strictio=strictio) + f1mode = f.mode + ftell = f.tell() + f.close() + fstr = open(fname).read() + + f = open(fname, "w") + f.write("h") + _ftell = f.tell() + f.close() + + if strictio: # throw error if ftell > EOF + assert throws(dill.loads, (f_dumped,), buffer_error) + else: + f2 = dill.loads(f_dumped) + f2mode = f2.mode + f2tell = f2.tell() + f2.write(" world!") + f2.close() + if fmode == dill.CONTENTS_FMODE: + assert open(fname).read() == "h world!" + assert f2mode == f1mode + assert f2tell == _ftell + elif fmode == dill.HANDLE_FMODE: + assert open(fname).read() == " world!" + assert f2mode == f1mode + assert f2tell == 0 + elif fmode == dill.FILE_FMODE: + assert open(fname).read() == "hello world!" + assert f2mode == f1mode + assert f2tell == ftell + else: + raise RuntimeError("Unknown file mode '%s'" % fmode) + f2.close() + + # append + + trunc_file() + + f = open(fname, "a") + f.write("hello") + f_dumped = dill.dumps(f, fmode=fmode)#, strictio=strictio) + f1mode = f.mode + ftell = f.tell() + f.close() + fstr = open(fname).read() + + f = open(fname, "w") + f.write("h") + _ftell = f.tell() + f.close() + + if strictio: # throw error if ftell > EOF + assert throws(dill.loads, (f_dumped,), buffer_error) + else: + f2 = dill.loads(f_dumped) + f2mode = f2.mode + f2tell = f2.tell() + f2.write(" world!") + f2.close() + assert f2mode == f1mode + if fmode == dill.CONTENTS_FMODE: + # position of writes cannot be changed on some OSs + assert open(fname).read() == "h world!" + assert f2tell == _ftell + elif fmode == dill.HANDLE_FMODE: + assert open(fname).read() == "h world!" + assert f2tell == _ftell + elif fmode == dill.FILE_FMODE: + assert open(fname).read() == "hello world!" + assert f2tell == ftell + else: + raise RuntimeError("Unknown file mode '%s'" % fmode) + f2.close() + + # file does not exist + # read + + write_randomness() + + f = open(fname, "r") + fstr = f.read() + f_dumped = dill.dumps(f, fmode=fmode)#, strictio=strictio) + f1mode = f.mode + ftell = f.tell() + f.close() + + os.remove(fname) + + if strictio: # throw error if file DNE + assert throws(dill.loads, (f_dumped,), dne_error) + else: + f2 = dill.loads(f_dumped) + assert f2.mode == f1mode + if fmode == dill.CONTENTS_FMODE: + # FIXME: this fails on systems where f2.tell() always returns 0 + # assert f2.tell() == ftell # 200 + assert f2.read() == "" + f2.seek(0) + assert f2.read() == "" + assert f2.tell() == 0 + elif fmode == dill.FILE_FMODE: + assert f2.tell() == ftell # 200 + assert f2.read() == "" + f2.seek(0) + assert f2.read() == fstr + assert f2.tell() == ftell # 200 + elif fmode == dill.HANDLE_FMODE: + assert f2.tell() == 0 + assert f2.read() == "" + assert f2.tell() == 0 + else: + raise RuntimeError("Unknown file mode '%s'" % fmode) + f2.close() + + # write + + write_randomness() + + f = open(fname, "w+") + f.write("hello") + f_dumped = dill.dumps(f, fmode=fmode)#, strictio=strictio) + ftell = f.tell() + f1mode = f.mode + f.close() + + os.remove(fname) + + if strictio: # throw error if file DNE + assert throws(dill.loads, (f_dumped,), dne_error) + else: + f2 = dill.loads(f_dumped) + f2mode = f2.mode + f2tell = f2.tell() + f2.write(" world!") + f2.close() + if fmode == dill.CONTENTS_FMODE: + assert open(fname).read() == " world!" + assert f2mode == 'w+' + assert f2tell == 0 + elif fmode == dill.HANDLE_FMODE: + assert open(fname).read() == " world!" + assert f2mode == f1mode + assert f2tell == 0 + elif fmode == dill.FILE_FMODE: + assert open(fname).read() == "hello world!" + assert f2mode == f1mode + assert f2tell == ftell + else: + raise RuntimeError("Unknown file mode '%s'" % fmode) + + # append + + trunc_file() + + f = open(fname, "a") + f.write("hello") + f_dumped = dill.dumps(f, fmode=fmode)#, strictio=strictio) + ftell = f.tell() + f1mode = f.mode + f.close() + + os.remove(fname) + + if strictio: # throw error if file DNE + assert throws(dill.loads, (f_dumped,), dne_error) + else: + f2 = dill.loads(f_dumped) + f2mode = f2.mode + f2tell = f2.tell() + f2.write(" world!") + f2.close() + assert f2mode == f1mode + if fmode == dill.CONTENTS_FMODE: + assert open(fname).read() == " world!" + assert f2tell == 0 + elif fmode == dill.HANDLE_FMODE: + assert open(fname).read() == " world!" + assert f2tell == 0 + elif fmode == dill.FILE_FMODE: + assert open(fname).read() == "hello world!" + assert f2tell == ftell + else: + raise RuntimeError("Unknown file mode '%s'" % fmode) + + # file exists, with different contents (larger size) + # read + + write_randomness() + + f = open(fname, "r") + fstr = f.read() + f_dumped = dill.dumps(f, fmode=fmode)#, strictio=strictio) + f1mode = f.mode + ftell = f.tell() + f.close() + _flen = 250 + _fstr = write_randomness(number=_flen) + + # XXX: no safe_file: no way to be 'safe'? + + f2 = dill.loads(f_dumped) + assert f2.mode == f1mode + if fmode == dill.CONTENTS_FMODE: + assert f2.tell() == ftell # 200 + assert f2.read() == _fstr[ftell:] + f2.seek(0) + assert f2.read() == _fstr + assert f2.tell() == _flen # 250 + elif fmode == dill.HANDLE_FMODE: + assert f2.tell() == 0 + assert f2.read() == _fstr + assert f2.tell() == _flen # 250 + elif fmode == dill.FILE_FMODE: + assert f2.tell() == ftell # 200 + assert f2.read() == "" + f2.seek(0) + assert f2.read() == fstr + assert f2.tell() == ftell # 200 + else: + raise RuntimeError("Unknown file mode '%s'" % fmode) + f2.close() # XXX: other alternatives? + + # write + + f = open(fname, "w") + f.write("hello") + f_dumped = dill.dumps(f, fmode=fmode)#, strictio=strictio) + f1mode = f.mode + ftell = f.tell() + + fstr = open(fname).read() + + f.write(" and goodbye!") + _ftell = f.tell() + f.close() + + # XXX: no safe_file: no way to be 'safe'? + + f2 = dill.loads(f_dumped) + f2mode = f2.mode + f2tell = f2.tell() + f2.write(" world!") + f2.close() + if fmode == dill.CONTENTS_FMODE: + assert open(fname).read() == "hello world!odbye!" + assert f2mode == f1mode + assert f2tell == ftell + elif fmode == dill.HANDLE_FMODE: + assert open(fname).read() == " world!" + assert f2mode == f1mode + assert f2tell == 0 + elif fmode == dill.FILE_FMODE: + assert open(fname).read() == "hello world!" + assert f2mode == f1mode + assert f2tell == ftell + else: + raise RuntimeError("Unknown file mode '%s'" % fmode) + f2.close() + + # append + + trunc_file() + + f = open(fname, "a") + f.write("hello") + f_dumped = dill.dumps(f, fmode=fmode)#, strictio=strictio) + f1mode = f.mode + ftell = f.tell() + fstr = open(fname).read() + + f.write(" and goodbye!") + _ftell = f.tell() + f.close() + + # XXX: no safe_file: no way to be 'safe'? + + f2 = dill.loads(f_dumped) + f2mode = f2.mode + f2tell = f2.tell() + f2.write(" world!") + f2.close() + assert f2mode == f1mode + if fmode == dill.CONTENTS_FMODE: + assert open(fname).read() == "hello and goodbye! world!" + assert f2tell == ftell + elif fmode == dill.HANDLE_FMODE: + assert open(fname).read() == "hello and goodbye! world!" + assert f2tell == _ftell + elif fmode == dill.FILE_FMODE: + assert open(fname).read() == "hello world!" + assert f2tell == ftell + else: + raise RuntimeError("Unknown file mode '%s'" % fmode) + f2.close() + + +def test_nostrictio_handlefmode(): + bench(False, dill.HANDLE_FMODE, False) + teardown_module() + + +def test_nostrictio_filefmode(): + bench(False, dill.FILE_FMODE, False) + teardown_module() + + +def test_nostrictio_contentsfmode(): + bench(False, dill.CONTENTS_FMODE, True) + teardown_module() + + +#bench(True, dill.HANDLE_FMODE, False) +#bench(True, dill.FILE_FMODE, False) +#bench(True, dill.CONTENTS_FMODE, True) + + +if __name__ == '__main__': + test_nostrictio_handlefmode() + test_nostrictio_filefmode() + test_nostrictio_contentsfmode() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_functions.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_functions.py new file mode 100644 index 0000000000000000000000000000000000000000..f3db1a4f0a328b7d16025356ae5147a062764df7 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_functions.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python +# +# Author: Mike McKerns (mmckerns @caltech and @uqfoundation) +# Copyright (c) 2019-2026 The Uncertainty Quantification Foundation. +# License: 3-clause BSD. The full license text is available at: +# - https://github.com/uqfoundation/dill/blob/master/LICENSE + +import functools +import dill +import sys +dill.settings['recurse'] = True + + +def function_a(a): + return a + + +def function_b(b, b1): + return b + b1 + + +def function_c(c, c1=1): + return c + c1 + + +def function_d(d, d1, d2=1): + """doc string""" + return d + d1 + d2 + +function_d.__module__ = 'a module' + + +exec(''' +def function_e(e, *e1, e2=1, e3=2): + return e + sum(e1) + e2 + e3''') + +globalvar = 0 + +@functools.lru_cache(None) +def function_with_cache(x): + global globalvar + globalvar += x + return globalvar + + +def function_with_unassigned_variable(): + if False: + value = None + return (lambda: value) + + +def test_issue_510(): + # A very bizzare use of functions and methods that pickle doesn't get + # correctly for odd reasons. + class Foo: + def __init__(self): + def f2(self): + return self + self.f2 = f2.__get__(self) + + import dill, pickletools + f = Foo() + f1 = dill.copy(f) + assert f1.f2() is f1 + + +def test_functions(): + dumped_func_a = dill.dumps(function_a) + assert dill.loads(dumped_func_a)(0) == 0 + + dumped_func_b = dill.dumps(function_b) + assert dill.loads(dumped_func_b)(1,2) == 3 + + dumped_func_c = dill.dumps(function_c) + assert dill.loads(dumped_func_c)(1) == 2 + assert dill.loads(dumped_func_c)(1, 2) == 3 + + dumped_func_d = dill.dumps(function_d) + assert dill.loads(dumped_func_d).__doc__ == function_d.__doc__ + assert dill.loads(dumped_func_d).__module__ == function_d.__module__ + assert dill.loads(dumped_func_d)(1, 2) == 4 + assert dill.loads(dumped_func_d)(1, 2, 3) == 6 + assert dill.loads(dumped_func_d)(1, 2, d2=3) == 6 + + function_with_cache(1) + globalvar = 0 + dumped_func_cache = dill.dumps(function_with_cache) + assert function_with_cache(2) == 3 + assert function_with_cache(1) == 1 + assert function_with_cache(3) == 6 + assert function_with_cache(2) == 3 + + empty_cell = function_with_unassigned_variable() + cell_copy = dill.loads(dill.dumps(empty_cell)) + assert 'empty' in str(cell_copy.__closure__[0]) + try: + cell_copy() + except Exception: + # this is good + pass + else: + raise AssertionError('cell_copy() did not read an empty cell') + + exec(''' +dumped_func_e = dill.dumps(function_e) +assert dill.loads(dumped_func_e)(1, 2) == 6 +assert dill.loads(dumped_func_e)(1, 2, 3) == 9 +assert dill.loads(dumped_func_e)(1, 2, e2=3) == 8 +assert dill.loads(dumped_func_e)(1, 2, e2=3, e3=4) == 10 +assert dill.loads(dumped_func_e)(1, 2, 3, e2=4) == 12 +assert dill.loads(dumped_func_e)(1, 2, 3, e2=4, e3=5) == 15''') + +def test_code_object(): + import warnings + from dill._dill import ALL_CODE_PARAMS, CODE_PARAMS, CODE_VERSION, _create_code + code = function_c.__code__ + warnings.filterwarnings('ignore', category=DeprecationWarning) # issue 597 + LNOTAB = getattr(code, 'co_lnotab', b'') + if warnings.filters: del warnings.filters[0] + fields = {f: getattr(code, 'co_'+f) for f in CODE_PARAMS} + fields.setdefault('posonlyargcount', 0) # python >= 3.8 + fields.setdefault('lnotab', LNOTAB) # python <= 3.9 + fields.setdefault('linetable', b'') # python >= 3.10 + fields.setdefault('qualname', fields['name']) # python >= 3.11 + fields.setdefault('exceptiontable', b'') # python >= 3.11 + fields.setdefault('endlinetable', None) # python == 3.11a + fields.setdefault('columntable', None) # python == 3.11a + + for version, _, params in ALL_CODE_PARAMS: + args = tuple(fields[p] for p in params.split()) + try: + _create_code(*args) + if version >= (3,10): + _create_code(fields['lnotab'], *args) + except Exception as error: + raise Exception("failed to construct code object with format version {}".format(version)) from error + +if __name__ == '__main__': + test_functions() + test_issue_510() + test_code_object() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_functors.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_functors.py new file mode 100644 index 0000000000000000000000000000000000000000..882d24d00dccd79572f36be16b4804e9db177b81 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_functors.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python +# +# Author: Mike McKerns (mmckerns @caltech and @uqfoundation) +# Copyright (c) 2008-2016 California Institute of Technology. +# Copyright (c) 2016-2026 The Uncertainty Quantification Foundation. +# License: 3-clause BSD. The full license text is available at: +# - https://github.com/uqfoundation/dill/blob/master/LICENSE + +import functools +import dill +dill.settings['recurse'] = True + + +def f(a, b, c): # without keywords + pass + + +def g(a, b, c=2): # with keywords + pass + + +def h(a=1, b=2, c=3): # without args + pass + + +def test_functools(): + fp = functools.partial(f, 1, 2) + gp = functools.partial(g, 1, c=2) + hp = functools.partial(h, 1, c=2) + bp = functools.partial(int, base=2) + + assert dill.pickles(fp, safe=True) + assert dill.pickles(gp, safe=True) + assert dill.pickles(hp, safe=True) + assert dill.pickles(bp, safe=True) + + +if __name__ == '__main__': + test_functools() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_logger.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_logger.py new file mode 100644 index 0000000000000000000000000000000000000000..ec1d5e66bfdb023fd371c761a9de9a04ad735d53 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_logger.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python + +# Author: Leonardo Gama (@leogama) +# Copyright (c) 2022-2026 The Uncertainty Quantification Foundation. +# License: 3-clause BSD. The full license text is available at: +# - https://github.com/uqfoundation/dill/blob/master/LICENSE + +import logging +import re +import tempfile + +import dill +from dill import detect +from dill.logger import stderr_handler, adapter as logger + +try: + from StringIO import StringIO +except ImportError: + from io import StringIO + +test_obj = {'a': (1, 2), 'b': object(), 'f': lambda x: x**2, 'big': list(range(10))} + +def test_logging(should_trace): + buffer = StringIO() + handler = logging.StreamHandler(buffer) + logger.addHandler(handler) + try: + dill.dumps(test_obj) + if should_trace: + regex = re.compile(r'(\S*┬ \w.*[^)]' # begin pickling object + r'|│*└ # \w.* \[\d+ (\wi)?B])' # object written (with size) + ) + for line in buffer.getvalue().splitlines(): + assert regex.fullmatch(line) + return buffer.getvalue() + else: + assert buffer.getvalue() == "" + finally: + logger.removeHandler(handler) + buffer.close() + +def test_trace_to_file(stream_trace): + file = tempfile.NamedTemporaryFile(mode='r') + with detect.trace(file.name, mode='w'): + dill.dumps(test_obj) + file_trace = file.read() + file.close() + # Apparently, objects can change location in memory... + reghex = re.compile(r'0x[0-9A-Za-z]+') + file_trace, stream_trace = reghex.sub('0x', file_trace), reghex.sub('0x', stream_trace) + # PyPy prints dictionary contents with repr(dict)... + regdict = re.compile(r'(dict\.__repr__ of ).*') + file_trace, stream_trace = regdict.sub(r'\1{}>', file_trace), regdict.sub(r'\1{}>', stream_trace) + assert file_trace == stream_trace + +if __name__ == '__main__': + logger.removeHandler(stderr_handler) + test_logging(should_trace=False) + detect.trace(True) + test_logging(should_trace=True) + detect.trace(False) + test_logging(should_trace=False) + + loglevel = logging.ERROR + logger.setLevel(loglevel) + with detect.trace(): + stream_trace = test_logging(should_trace=True) + test_logging(should_trace=False) + assert logger.getEffectiveLevel() == loglevel + test_trace_to_file(stream_trace) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_mixins.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_mixins.py new file mode 100644 index 0000000000000000000000000000000000000000..447811dd5b57a663195d3d47e7afd542515991a7 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_mixins.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python +# +# Author: Mike McKerns (mmckerns @caltech and @uqfoundation) +# Copyright (c) 2008-2016 California Institute of Technology. +# Copyright (c) 2016-2026 The Uncertainty Quantification Foundation. +# License: 3-clause BSD. The full license text is available at: +# - https://github.com/uqfoundation/dill/blob/master/LICENSE + +import dill +dill.settings['recurse'] = True + + +def wtf(x,y,z): + def zzz(): + return x + def yyy(): + return y + def xxx(): + return z + return zzz,yyy + + +def quad(a=1, b=1, c=0): + inverted = [False] + def invert(): + inverted[0] = not inverted[0] + def dec(f): + def func(*args, **kwds): + x = f(*args, **kwds) + if inverted[0]: x = -x + return a*x**2 + b*x + c + func.__wrapped__ = f + func.invert = invert + func.inverted = inverted + return func + return dec + + +@quad(a=0,b=2) +def double_add(*args): + return sum(args) + + +fx = sum([1,2,3]) + + +### to make it interesting... +def quad_factory(a=1,b=1,c=0): + def dec(f): + def func(*args,**kwds): + fx = f(*args,**kwds) + return a*fx**2 + b*fx + c + return func + return dec + + +@quad_factory(a=0,b=4,c=0) +def quadish(x): + return x+1 + + +quadratic = quad_factory() + + +def doubler(f): + def inner(*args, **kwds): + fx = f(*args, **kwds) + return 2*fx + return inner + + +@doubler +def quadruple(x): + return 2*x + + +def test_mixins(): + # test mixins + assert double_add(1,2,3) == 2*fx + double_add.invert() + assert double_add(1,2,3) == -2*fx + + _d = dill.copy(double_add) + assert _d(1,2,3) == -2*fx + #_d.invert() #FIXME: fails seemingly randomly + #assert _d(1,2,3) == 2*fx + + assert _d.__wrapped__(1,2,3) == fx + + # XXX: issue or feature? in python3.4, inverted is linked through copy + if not double_add.inverted[0]: + double_add.invert() + + # test some stuff from source and pointers + ds = dill.source + dd = dill.detect + assert ds.getsource(dd.freevars(quadish)['f']) == '@quad_factory(a=0,b=4,c=0)\ndef quadish(x):\n return x+1\n' + assert ds.getsource(dd.freevars(quadruple)['f']) == '@doubler\ndef quadruple(x):\n return 2*x\n' + assert ds.importable(quadish, source=False) == 'from %s import quadish\n' % __name__ + assert ds.importable(quadruple, source=False) == 'from %s import quadruple\n' % __name__ + assert ds.importable(quadratic, source=False) == 'from %s import quadratic\n' % __name__ + assert ds.importable(double_add, source=False) == 'from %s import double_add\n' % __name__ + assert ds.importable(quadruple, source=True) == 'def doubler(f):\n def inner(*args, **kwds):\n fx = f(*args, **kwds)\n return 2*fx\n return inner\n\n@doubler\ndef quadruple(x):\n return 2*x\n' + #***** #FIXME: this needs work + result = ds.importable(quadish, source=True) + a,b,c,_,result = result.split('\n',4) + assert result == 'def quad_factory(a=1,b=1,c=0):\n def dec(f):\n def func(*args,**kwds):\n fx = f(*args,**kwds)\n return a*fx**2 + b*fx + c\n return func\n return dec\n\n@quad_factory(a=0,b=4,c=0)\ndef quadish(x):\n return x+1\n' + assert set([a,b,c]) == set(['a = 0', 'c = 0', 'b = 4']) + result = ds.importable(quadratic, source=True) + a,b,c,result = result.split('\n',3) + assert result == '\ndef dec(f):\n def func(*args,**kwds):\n fx = f(*args,**kwds)\n return a*fx**2 + b*fx + c\n return func\n' + assert set([a,b,c]) == set(['a = 1', 'c = 0', 'b = 1']) + result = ds.importable(double_add, source=True) + a,b,c,d,_,result = result.split('\n',5) + assert result == 'def quad(a=1, b=1, c=0):\n inverted = [False]\n def invert():\n inverted[0] = not inverted[0]\n def dec(f):\n def func(*args, **kwds):\n x = f(*args, **kwds)\n if inverted[0]: x = -x\n return a*x**2 + b*x + c\n func.__wrapped__ = f\n func.invert = invert\n func.inverted = inverted\n return func\n return dec\n\n@quad(a=0,b=2)\ndef double_add(*args):\n return sum(args)\n' + assert set([a,b,c,d]) == set(['a = 0', 'c = 0', 'b = 2', 'inverted = [True]']) + #***** + + +if __name__ == '__main__': + test_mixins() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_nested.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_nested.py new file mode 100644 index 0000000000000000000000000000000000000000..0e863ab510eddfaa4730d6da1f9f2fd088902db5 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_nested.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python +# +# Author: Mike McKerns (mmckerns @caltech and @uqfoundation) +# Copyright (c) 2008-2016 California Institute of Technology. +# Copyright (c) 2016-2026 The Uncertainty Quantification Foundation. +# License: 3-clause BSD. The full license text is available at: +# - https://github.com/uqfoundation/dill/blob/master/LICENSE +""" +test dill's ability to handle nested functions +""" + +import os +import math + +import dill as pickle +pickle.settings['recurse'] = True + + +# the nested function: pickle should fail here, but dill is ok. +def adder(augend): + zero = [0] + + def inner(addend): + return addend + augend + zero[0] + return inner + + +# rewrite the nested function using a class: standard pickle should work here. +class cadder(object): + def __init__(self, augend): + self.augend = augend + self.zero = [0] + + def __call__(self, addend): + return addend + self.augend + self.zero[0] + + +# rewrite again, but as an old-style class +class c2adder: + def __init__(self, augend): + self.augend = augend + self.zero = [0] + + def __call__(self, addend): + return addend + self.augend + self.zero[0] + + +# some basic class stuff +class basic(object): + pass + + +class basic2: + pass + + +x = 5 +y = 1 + + +def test_basic(): + a = [0, 1, 2] + pa = pickle.dumps(a) + pmath = pickle.dumps(math) #XXX: FAILS in pickle + pmap = pickle.dumps(map) + # ... + la = pickle.loads(pa) + lmath = pickle.loads(pmath) + lmap = pickle.loads(pmap) + assert list(map(math.sin, a)) == list(lmap(lmath.sin, la)) + + +def test_basic_class(): + pbasic2 = pickle.dumps(basic2) + _pbasic2 = pickle.loads(pbasic2)() + pbasic = pickle.dumps(basic) + _pbasic = pickle.loads(pbasic)() + + +def test_c2adder(): + pc2adder = pickle.dumps(c2adder) + pc2add5 = pickle.loads(pc2adder)(x) + assert pc2add5(y) == x+y + + +def test_pickled_cadder(): + pcadder = pickle.dumps(cadder) + pcadd5 = pickle.loads(pcadder)(x) + assert pcadd5(y) == x+y + + +def test_raw_adder_and_inner(): + add5 = adder(x) + assert add5(y) == x+y + + +def test_pickled_adder(): + padder = pickle.dumps(adder) + padd5 = pickle.loads(padder)(x) + assert padd5(y) == x+y + + +def test_pickled_inner(): + add5 = adder(x) + pinner = pickle.dumps(add5) #XXX: FAILS in pickle + p5add = pickle.loads(pinner) + assert p5add(y) == x+y + + +def test_moduledict_where_not_main(): + try: + from . import test_moduledict + except ImportError: + import test_moduledict + name = 'test_moduledict.py' + if os.path.exists(name) and os.path.exists(name+'c'): + os.remove(name+'c') + + if os.path.exists(name) and hasattr(test_moduledict, "__cached__") \ + and os.path.exists(test_moduledict.__cached__): + os.remove(getattr(test_moduledict, "__cached__")) + + if os.path.exists("__pycache__") and not os.listdir("__pycache__"): + os.removedirs("__pycache__") + + +if __name__ == '__main__': + test_basic() + test_basic_class() + test_c2adder() + test_pickled_cadder() + test_raw_adder_and_inner() + test_pickled_adder() + test_pickled_inner() + test_moduledict_where_not_main() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_objects.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_objects.py new file mode 100644 index 0000000000000000000000000000000000000000..0828cf925986663c88be5a1d908827b33d946f3a --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_objects.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python +# +# Author: Mike McKerns (mmckerns @caltech and @uqfoundation) +# Copyright (c) 2008-2016 California Institute of Technology. +# Copyright (c) 2016-2026 The Uncertainty Quantification Foundation. +# License: 3-clause BSD. The full license text is available at: +# - https://github.com/uqfoundation/dill/blob/master/LICENSE +""" +demonstrate dill's ability to pickle different python types +test pickling of all Python Standard Library objects (currently: CH 1-14 @ 2.7) +""" + +import dill as pickle +pickle.settings['recurse'] = True +#pickle.detect.trace(True) +#import pickle + +# get all objects for testing +from dill import load_types, objects, extend +load_types(pickleable=True,unpickleable=False) + +# uncomment the next two lines to test cloudpickle +#extend(False) +#import cloudpickle as pickle + +# helper objects +class _class: + def _method(self): + pass + +# objects that *fail* if imported +special = {} +special['LambdaType'] = _lambda = lambda x: lambda y: x +special['MethodType'] = _method = _class()._method +special['UnboundMethodType'] = _class._method +objects.update(special) + +def pickles(name, exact=False, verbose=True): + """quick check if object pickles with dill""" + obj = objects[name] + try: + pik = pickle.loads(pickle.dumps(obj)) + if exact: + try: + assert pik == obj + except AssertionError: + assert type(obj) == type(pik) + if verbose: print ("weak: %s %s" % (name, type(obj))) + else: + assert type(obj) == type(pik) + except Exception: + if verbose: print ("fails: %s %s" % (name, type(obj))) + + +def test_objects(verbose=True): + for member in objects.keys(): + #pickles(member, exact=True, verbose=verbose) + pickles(member, exact=False, verbose=verbose) + +if __name__ == '__main__': + import warnings + warnings.simplefilter('ignore') + test_objects(verbose=False) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_properties.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_properties.py new file mode 100644 index 0000000000000000000000000000000000000000..2b55b39631827ac7938b6e7d6314c97cbc0fbdd1 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_properties.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python +# +# Author: Mike McKerns (mmckerns @caltech and @uqfoundation) +# Copyright (c) 2008-2016 California Institute of Technology. +# Copyright (c) 2016-2026 The Uncertainty Quantification Foundation. +# License: 3-clause BSD. The full license text is available at: +# - https://github.com/uqfoundation/dill/blob/master/LICENSE + +import sys + +import dill +dill.settings['recurse'] = True + + +class Foo(object): + def __init__(self): + self._data = 1 + + def _get_data(self): + return self._data + + def _set_data(self, x): + self._data = x + + data = property(_get_data, _set_data) + + +def test_data_not_none(): + FooS = dill.copy(Foo) + assert FooS.data.fget is not None + assert FooS.data.fset is not None + assert FooS.data.fdel is None + + +def test_data_unchanged(): + FooS = dill.copy(Foo) + try: + res = FooS().data + except Exception: + e = sys.exc_info()[1] + raise AssertionError(str(e)) + else: + assert res == 1 + + +def test_data_changed(): + FooS = dill.copy(Foo) + try: + f = FooS() + f.data = 1024 + res = f.data + except Exception: + e = sys.exc_info()[1] + raise AssertionError(str(e)) + else: + assert res == 1024 + + +if __name__ == '__main__': + test_data_not_none() + test_data_unchanged() + test_data_changed() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_pycapsule.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_pycapsule.py new file mode 100644 index 0000000000000000000000000000000000000000..735801a07adb89dffd27ba37ab262e29eaf046e1 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_pycapsule.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python +# +# Author: Mike McKerns (mmckerns @caltech and @uqfoundation) +# Author: Anirudh Vegesana (avegesan@cs.stanford.edu) +# Copyright (c) 2022-2026 The Uncertainty Quantification Foundation. +# License: 3-clause BSD. The full license text is available at: +# - https://github.com/uqfoundation/dill/blob/master/LICENSE +""" +test pickling a PyCapsule object +""" + +import dill +import warnings + +test_pycapsule = None + +if dill._dill._testcapsule is not None: + import ctypes + def test_pycapsule(): + name = ctypes.create_string_buffer(b'dill._testcapsule') + capsule = dill._dill._PyCapsule_New( + ctypes.cast(dill._dill._PyCapsule_New, ctypes.c_void_p), + name, + None + ) + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + dill.copy(capsule) + dill._testcapsule = capsule + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + dill.copy(capsule) + dill._testcapsule = None + try: + with warnings.catch_warnings(): + warnings.simplefilter("ignore", dill.PicklingWarning) + dill.copy(capsule) + except dill.UnpicklingError: + pass + else: + raise AssertionError("Expected a different error") + +if __name__ == '__main__': + if test_pycapsule is not None: + test_pycapsule() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_recursive.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_recursive.py new file mode 100644 index 0000000000000000000000000000000000000000..5237f68fdf166a41b06486284a934c38b467a1c0 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_recursive.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python +# +# Author: Mike McKerns (mmckerns @caltech and @uqfoundation) +# Copyright (c) 2019-2026 The Uncertainty Quantification Foundation. +# License: 3-clause BSD. The full license text is available at: +# - https://github.com/uqfoundation/dill/blob/master/LICENSE + +import dill +from functools import partial +import warnings + + +def copy(obj, byref=False, recurse=False): + if byref: + try: + return dill.copy(obj, byref=byref, recurse=recurse) + except Exception: + pass + else: + raise AssertionError('Copy of %s with byref=True should have given a warning!' % (obj,)) + + warnings.simplefilter('ignore') + val = dill.copy(obj, byref=byref, recurse=recurse) + warnings.simplefilter('error') + return val + else: + return dill.copy(obj, byref=byref, recurse=recurse) + + +class obj1(object): + def __init__(self): + super(obj1, self).__init__() + +class obj2(object): + def __init__(self): + super(obj2, self).__init__() + +class obj3(object): + super_ = super + def __init__(self): + obj3.super_(obj3, self).__init__() + + +def test_super(): + assert copy(obj1(), byref=True) + assert copy(obj1(), byref=True, recurse=True) + assert copy(obj1(), recurse=True) + assert copy(obj1()) + + assert copy(obj2(), byref=True) + assert copy(obj2(), byref=True, recurse=True) + assert copy(obj2(), recurse=True) + assert copy(obj2()) + + assert copy(obj3(), byref=True) + assert copy(obj3(), byref=True, recurse=True) + assert copy(obj3(), recurse=True) + assert copy(obj3()) + + +def get_trigger(model): + pass + +class Machine(object): + def __init__(self): + self.child = Model() + self.trigger = partial(get_trigger, self) + self.child.trigger = partial(get_trigger, self.child) + +class Model(object): + pass + + + +def test_partial(): + assert copy(Machine(), byref=True) + assert copy(Machine(), byref=True, recurse=True) + assert copy(Machine(), recurse=True) + assert copy(Machine()) + + +class Machine2(object): + def __init__(self): + self.go = partial(self.member, self) + def member(self, model): + pass + + +class SubMachine(Machine2): + def __init__(self): + super(SubMachine, self).__init__() + + +def test_partials(): + assert copy(SubMachine(), byref=True) + assert copy(SubMachine(), byref=True, recurse=True) + assert copy(SubMachine(), recurse=True) + assert copy(SubMachine()) + + +class obj4(object): + def __init__(self): + super(obj4, self).__init__() + a = self + class obj5(object): + def __init__(self): + super(obj5, self).__init__() + self.a = a + self.b = obj5() + + +def test_circular_reference(): + assert copy(obj4()) + obj4_copy = dill.loads(dill.dumps(obj4())) + assert type(obj4_copy) is type(obj4_copy).__init__.__closure__[0].cell_contents + assert type(obj4_copy.b) is type(obj4_copy.b).__init__.__closure__[0].cell_contents + + +def f(): + def g(): + return g + return g + + +def test_function_cells(): + assert copy(f()) + + +def fib(n): + assert n >= 0 + if n <= 1: + return n + else: + return fib(n-1) + fib(n-2) + + +def test_recursive_function(): + global fib + fib2 = copy(fib, recurse=True) + fib3 = copy(fib) + fib4 = fib + del fib + assert fib2(5) == 5 + for _fib in (fib3, fib4): + try: + _fib(5) + except Exception: + # This is expected to fail because fib no longer exists + pass + else: + raise AssertionError("Function fib shouldn't have been found") + fib = fib4 + + +def collection_function_recursion(): + d = {} + def g(): + return d + d['g'] = g + return g + + +def test_collection_function_recursion(): + g = copy(collection_function_recursion()) + assert g()['g'] is g + + +if __name__ == '__main__': + with warnings.catch_warnings(): + warnings.simplefilter('error') + test_super() + test_partial() + test_partials() + test_circular_reference() + test_function_cells() + test_recursive_function() + test_collection_function_recursion() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_restricted.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_restricted.py new file mode 100644 index 0000000000000000000000000000000000000000..45bba916e2b6edf6fed9c5bad63d73bef8b5181a --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_restricted.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python +# +# Author: Kirill Makhonin (@kirillmakhonin) +# Copyright (c) 2008-2016 California Institute of Technology. +# Copyright (c) 2016-2026 The Uncertainty Quantification Foundation. +# License: 3-clause BSD. The full license text is available at: +# - https://github.com/uqfoundation/dill/blob/master/LICENSE + +import dill + +class RestrictedType: + def __bool__(*args, **kwargs): + raise Exception('Restricted function') + + __eq__ = __lt__ = __le__ = __ne__ = __gt__ = __ge__ = __hash__ = __bool__ + +glob_obj = RestrictedType() + +def restricted_func(): + a = glob_obj + +def test_function_with_restricted_object(): + deserialized = dill.loads(dill.dumps(restricted_func, recurse=True)) + + +if __name__ == '__main__': + test_function_with_restricted_object() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_session.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_session.py new file mode 100644 index 0000000000000000000000000000000000000000..0ec6bb562b349473e9b41db0fca8684606959659 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_session.py @@ -0,0 +1,280 @@ +#!/usr/bin/env python + +# Author: Leonardo Gama (@leogama) +# Copyright (c) 2022-2026 The Uncertainty Quantification Foundation. +# License: 3-clause BSD. The full license text is available at: +# - https://github.com/uqfoundation/dill/blob/master/LICENSE + +import atexit +import os +import sys +import __main__ +from contextlib import suppress +from io import BytesIO + +import dill + +session_file = os.path.join(os.path.dirname(__file__), 'session-refimported-%s.pkl') + +################### +# Child process # +################### + +def _error_line(error, obj, refimported): + import traceback + line = traceback.format_exc().splitlines()[-2].replace('[obj]', '['+repr(obj)+']') + return "while testing (with refimported=%s): %s" % (refimported, line.lstrip()) + +if __name__ == '__main__' and len(sys.argv) >= 3 and sys.argv[1] == '--child': + # Test session loading in a fresh interpreter session. + refimported = (sys.argv[2] == 'True') + dill.load_module(session_file % refimported, module='__main__') + + def test_modules(refimported): + # FIXME: In this test setting with CPython 3.7, 'calendar' is not included + # in sys.modules, independent of the value of refimported. Tried to + # run garbage collection just before loading the session with no luck. It + # fails even when preceding them with 'import calendar'. Needed to run + # these kinds of tests in a supbrocess. Failing test sample: + # assert globals()['day_name'] is sys.modules['calendar'].__dict__['day_name'] + try: + for obj in ('json', 'url', 'local_mod', 'sax', 'dom'): + assert globals()[obj].__name__ in sys.modules + assert 'calendar' in sys.modules and 'cmath' in sys.modules + import calendar, cmath + + for obj in ('Calendar', 'isleap'): + assert globals()[obj] is sys.modules['calendar'].__dict__[obj] + assert __main__.day_name.__module__ == 'calendar' + if refimported: + assert __main__.day_name is calendar.day_name + + assert __main__.complex_log is cmath.log + + except AssertionError as error: + error.args = (_error_line(error, obj, refimported),) + raise + + test_modules(refimported) + sys.exit() + +#################### +# Parent process # +#################### + +# Create various kinds of objects to test different internal logics. + +## Modules. +import json # top-level module +import urllib as url # top-level module under alias +from xml import sax # submodule +import xml.dom.minidom as dom # submodule under alias +import test_dictviews as local_mod # non-builtin top-level module + +## Imported objects. +from calendar import Calendar, isleap, day_name # class, function, other object +from cmath import log as complex_log # imported with alias + +## Local objects. +x = 17 +empty = None +names = ['Alice', 'Bob', 'Carol'] +def squared(x): return x**2 +cubed = lambda x: x**3 +class Person: + def __init__(self, name, age): + self.name = name + self.age = age +person = Person(names[0], x) +class CalendarSubclass(Calendar): + def weekdays(self): + return [day_name[i] for i in self.iterweekdays()] +cal = CalendarSubclass() +selfref = __main__ + +# Setup global namespace for session saving tests. +class TestNamespace: + test_globals = globals().copy() + def __init__(self, **extra): + self.extra = extra + def __enter__(self): + self.backup = globals().copy() + globals().clear() + globals().update(self.test_globals) + globals().update(self.extra) + return self + def __exit__(self, *exc_info): + globals().clear() + globals().update(self.backup) + +def _clean_up_cache(module): + cached = module.__file__.split('.', 1)[0] + '.pyc' + cached = module.__cached__ if hasattr(module, '__cached__') else cached + pycache = os.path.join(os.path.dirname(module.__file__), '__pycache__') + for remove, file in [(os.remove, cached), (os.removedirs, pycache)]: + with suppress(OSError): + remove(file) + +atexit.register(_clean_up_cache, local_mod) + +def _test_objects(main, globals_copy, refimported): + try: + main_dict = __main__.__dict__ + global Person, person, Calendar, CalendarSubclass, cal, selfref + + for obj in ('json', 'url', 'local_mod', 'sax', 'dom'): + assert globals()[obj].__name__ == globals_copy[obj].__name__ + + for obj in ('x', 'empty', 'names'): + assert main_dict[obj] == globals_copy[obj] + + for obj in ['squared', 'cubed']: + assert main_dict[obj].__globals__ is main_dict + assert main_dict[obj](3) == globals_copy[obj](3) + + assert Person.__module__ == __main__.__name__ + assert isinstance(person, Person) + assert person.age == globals_copy['person'].age + + assert issubclass(CalendarSubclass, Calendar) + assert isinstance(cal, CalendarSubclass) + assert cal.weekdays() == globals_copy['cal'].weekdays() + + assert selfref is __main__ + + except AssertionError as error: + error.args = (_error_line(error, obj, refimported),) + raise + +def test_session_main(refimported): + """test dump/load_module() for __main__, both in this process and in a subprocess""" + extra_objects = {} + if refimported: + # Test unpickleable imported object in main. + from sys import flags + extra_objects['flags'] = flags + + with TestNamespace(**extra_objects) as ns: + try: + # Test session loading in a new session. + dill.dump_module(session_file % refimported, refimported=refimported) + from dill.tests.__main__ import python, shell, sp + error = sp.call([python, __file__, '--child', str(refimported)], shell=shell) + if error: sys.exit(error) + finally: + with suppress(OSError): + os.remove(session_file % refimported) + + # Test session loading in the same session. + session_buffer = BytesIO() + dill.dump_module(session_buffer, refimported=refimported) + session_buffer.seek(0) + dill.load_module(session_buffer, module='__main__') + ns.backup['_test_objects'](__main__, ns.backup, refimported) + +def test_session_other(): + """test dump/load_module() for a module other than __main__""" + import test_classdef as module + atexit.register(_clean_up_cache, module) + module.selfref = module + dict_objects = [obj for obj in module.__dict__.keys() if not obj.startswith('__')] + + session_buffer = BytesIO() + dill.dump_module(session_buffer, module) + + for obj in dict_objects: + del module.__dict__[obj] + + session_buffer.seek(0) + dill.load_module(session_buffer, module) + + assert all(obj in module.__dict__ for obj in dict_objects) + assert module.selfref is module + +def test_runtime_module(): + from types import ModuleType + modname = '__runtime__' + runtime = ModuleType(modname) + runtime.x = 42 + + mod = dill.session._stash_modules(runtime) + if mod is not runtime: + print("There are objects to save by referenece that shouldn't be:", + mod.__dill_imported, mod.__dill_imported_as, mod.__dill_imported_top_level, + file=sys.stderr) + + # This is also for code coverage, tests the use case of dump_module(refimported=True) + # without imported objects in the namespace. It's a contrived example because + # even dill can't be in it. This should work after fixing #462. + session_buffer = BytesIO() + dill.dump_module(session_buffer, module=runtime, refimported=True) + session_dump = session_buffer.getvalue() + + # Pass a new runtime created module with the same name. + runtime = ModuleType(modname) # empty + return_val = dill.load_module(BytesIO(session_dump), module=runtime) + assert return_val is None + assert runtime.__name__ == modname + assert runtime.x == 42 + assert runtime not in sys.modules.values() + + # Pass nothing as main. load_module() must create it. + session_buffer.seek(0) + runtime = dill.load_module(BytesIO(session_dump)) + assert runtime.__name__ == modname + assert runtime.x == 42 + assert runtime not in sys.modules.values() + +def test_refimported_imported_as(): + import collections + import concurrent.futures + import types + import typing + mod = sys.modules['__test__'] = types.ModuleType('__test__') + dill.executor = concurrent.futures.ThreadPoolExecutor(max_workers=1) + mod.Dict = collections.UserDict # select by type + mod.AsyncCM = typing.AsyncContextManager # select by __module__ + mod.thread_exec = dill.executor # select by __module__ with regex + + session_buffer = BytesIO() + dill.dump_module(session_buffer, mod, refimported=True) + session_buffer.seek(0) + mod = dill.load(session_buffer) + del sys.modules['__test__'] + + assert set(mod.__dill_imported_as) == { + ('collections', 'UserDict', 'Dict'), + ('typing', 'AsyncContextManager', 'AsyncCM'), + ('dill', 'executor', 'thread_exec'), + } + +def test_load_module_asdict(): + with TestNamespace(): + session_buffer = BytesIO() + dill.dump_module(session_buffer) + + global empty, names, x, y + x = y = 0 # change x and create y + del empty + globals_state = globals().copy() + + session_buffer.seek(0) + main_vars = dill.load_module_asdict(session_buffer) + + assert main_vars is not globals() + assert globals() == globals_state + + assert main_vars['__name__'] == '__main__' + assert main_vars['names'] == names + assert main_vars['names'] is not names + assert main_vars['x'] != x + assert 'y' not in main_vars + assert 'empty' in main_vars + +if __name__ == '__main__': + test_session_main(refimported=False) + test_session_main(refimported=True) + test_session_other() + test_runtime_module() + test_refimported_imported_as() + test_load_module_asdict() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_source.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_source.py new file mode 100644 index 0000000000000000000000000000000000000000..260ce6a6dc9ab4e21f592a33f3c75caa67ec6313 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_source.py @@ -0,0 +1,186 @@ +#!/usr/bin/env python +# +# Author: Mike McKerns (mmckerns @caltech and @uqfoundation) +# Copyright (c) 2008-2016 California Institute of Technology. +# Copyright (c) 2016-2026 The Uncertainty Quantification Foundation. +# License: 3-clause BSD. The full license text is available at: +# - https://github.com/uqfoundation/dill/blob/master/LICENSE + +from dill.source import getsource, getname, _wrap, getimport +from dill.source import importable +from dill._dill import IS_PYPY + +import sys +PY310b = 0x30a00b1 + +f = lambda x: x**2 +def g(x): return f(x) - x + +def h(x): + def g(x): return x + return g(x) - x + +class Foo(object): + def bar(self, x): + return x*x+x +_foo = Foo() + +def add(x,y): + return x+y + +# yes, same as 'f', but things are tricky when it comes to pointers +squared = lambda x:x**2 + +class Bar: + pass +_bar = Bar() + + # inspect.getsourcelines # dill.source.getblocks +def test_getsource(): + assert getsource(f) == 'f = lambda x: x**2\n' + assert getsource(g) == 'def g(x): return f(x) - x\n' + assert getsource(h) == 'def h(x):\n def g(x): return x\n return g(x) - x\n' + assert getname(f) == 'f' + assert getname(g) == 'g' + assert getname(h) == 'h' + assert _wrap(f)(4) == 16 + assert _wrap(g)(4) == 12 + assert _wrap(h)(4) == 0 + + assert getname(Foo) == 'Foo' + assert getname(Bar) == 'Bar' + assert getsource(Bar) == 'class Bar:\n pass\n' + assert getsource(Foo) == 'class Foo(object):\n def bar(self, x):\n return x*x+x\n' + #XXX: add getsource for _foo, _bar + +# test itself +def test_itself(): + assert getimport(getimport)=='from dill.source import getimport\n' + +# builtin functions and objects +def test_builtin(): + assert getimport(pow) == 'pow\n' + assert getimport(100) == '100\n' + assert getimport(True) == 'True\n' + assert getimport(pow, builtin=True) == 'from builtins import pow\n' + assert getimport(100, builtin=True) == '100\n' + assert getimport(True, builtin=True) == 'True\n' + # this is kinda BS... you can't import a None + assert getimport(None) == 'None\n' + assert getimport(None, builtin=True) == 'None\n' + + +# other imported functions +def test_imported(): + from math import sin + assert getimport(sin) == 'from math import sin\n' + +# interactively defined functions +def test_dynamic(): + assert getimport(add) == 'from %s import add\n' % __name__ + # interactive lambdas + assert getimport(squared) == 'from %s import squared\n' % __name__ + +# classes and class instances +def test_classes(): + from io import BytesIO as StringIO + y = "from _io import BytesIO\n" + x = y if (IS_PYPY or sys.hexversion >= PY310b) else "from io import BytesIO\n" + s = StringIO() + + assert getimport(StringIO) == x + assert getimport(s) == y + # interactively defined classes and class instances + assert getimport(Foo) == 'from %s import Foo\n' % __name__ + assert getimport(_foo) == 'from %s import Foo\n' % __name__ + + +# test importable +def test_importable(): + assert importable(add, source=False) == 'from %s import add\n' % __name__ + assert importable(squared, source=False) == 'from %s import squared\n' % __name__ + assert importable(Foo, source=False) == 'from %s import Foo\n' % __name__ + assert importable(Foo.bar, source=False) == 'from %s import bar\n' % __name__ + assert importable(_foo.bar, source=False) == 'from %s import bar\n' % __name__ + assert importable(None, source=False) == 'None\n' + assert importable(100, source=False) == '100\n' + + assert importable(add, source=True) == 'def add(x,y):\n return x+y\n' + assert importable(squared, source=True) == 'squared = lambda x:x**2\n' + assert importable(None, source=True) == 'None\n' + assert importable(Bar, source=True) == 'class Bar:\n pass\n' + assert importable(Foo, source=True) == 'class Foo(object):\n def bar(self, x):\n return x*x+x\n' + assert importable(Foo.bar, source=True) == 'def bar(self, x):\n return x*x+x\n' + assert importable(Foo.bar, source=False) == 'from %s import bar\n' % __name__ + assert importable(Foo.bar, alias='memo', source=False) == 'from %s import bar as memo\n' % __name__ + assert importable(Foo, alias='memo', source=False) == 'from %s import Foo as memo\n' % __name__ + assert importable(squared, alias='memo', source=False) == 'from %s import squared as memo\n' % __name__ + assert importable(squared, alias='memo', source=True) == 'memo = squared = lambda x:x**2\n' + assert importable(add, alias='memo', source=True) == 'def add(x,y):\n return x+y\n\nmemo = add\n' + assert importable(None, alias='memo', source=True) == 'memo = None\n' + assert importable(100, alias='memo', source=True) == 'memo = 100\n' + assert importable(add, builtin=True, source=False) == 'from %s import add\n' % __name__ + assert importable(squared, builtin=True, source=False) == 'from %s import squared\n' % __name__ + assert importable(Foo, builtin=True, source=False) == 'from %s import Foo\n' % __name__ + assert importable(Foo.bar, builtin=True, source=False) == 'from %s import bar\n' % __name__ + assert importable(_foo.bar, builtin=True, source=False) == 'from %s import bar\n' % __name__ + assert importable(None, builtin=True, source=False) == 'None\n' + assert importable(100, builtin=True, source=False) == '100\n' + + +def test_numpy(): + try: + import numpy as np + y = np.array + x = y([1,2,3]) + assert importable(x, source=False) == 'from numpy import array\narray([1, 2, 3])\n' + assert importable(y, source=False) == 'from %s import array\n' % y.__module__ + assert importable(x, source=True) == 'from numpy import array\narray([1, 2, 3])\n' + assert importable(y, source=True) == 'from %s import array\n' % y.__module__ + y = np.int64 + x = y(0) + assert importable(x, source=False) == 'from numpy import int64\nint64(0)\n' + assert importable(y, source=False) == 'from %s import int64\n' % y.__module__ + assert importable(x, source=True) == 'from numpy import int64\nint64(0)\n' + assert importable(y, source=True) == 'from %s import int64\n' % y.__module__ + y = np.bool_ + x = y(0) + import warnings + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', category=FutureWarning) + warnings.filterwarnings('ignore', category=DeprecationWarning) + if hasattr(np, 'bool'): b = 'bool_' if np.bool is bool else 'bool' + else: b = 'bool_' + assert importable(x, source=False) == 'from numpy import %s\n%s(False)\n' % (b,b) + assert importable(y, source=False) == 'from %s import %s\n' % (y.__module__,b) + assert importable(x, source=True) == 'from numpy import %s\n%s(False)\n' % (b,b) + assert importable(y, source=True) == 'from %s import %s\n' % (y.__module__,b) + except ImportError: pass + +#NOTE: if before getimport(pow), will cause pow to throw AssertionError +def test_foo(): + assert importable(_foo, source=True).startswith("import dill\nclass Foo(object):\n def bar(self, x):\n return x*x+x\ndill.loads(") + +def test_safe(): + import abc + obj = abc.ABC() + obj.__class__.__name__ = "(abc' + print('foo')]) # " + try: + source = getsource(obj, force=True) + assert False + except TypeError: + assert False + except SyntaxError: + pass + +if __name__ == '__main__': + test_getsource() + test_itself() + test_builtin() + test_imported() + test_dynamic() + test_classes() + test_importable() + test_numpy() + test_foo() + test_safe() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_sources.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_sources.py new file mode 100644 index 0000000000000000000000000000000000000000..2e978dc0a0b74eef2332b187d035937143ff82fe --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_sources.py @@ -0,0 +1,190 @@ +#!/usr/bin/env python +# +# Author: Mike McKerns (mmckerns @uqfoundation) +# Copyright (c) 2024-2026 The Uncertainty Quantification Foundation. +# License: 3-clause BSD. The full license text is available at: +# - https://github.com/uqfoundation/dill/blob/master/LICENSE +""" +check that dill.source performs as expected with changes to locals in 3.13.0b1 +see: https://github.com/python/cpython/issues/118888 +""" +# repeat functions from test_source.py +f = lambda x: x**2 +def g(x): return f(x) - x + +def h(x): + def g(x): return x + return g(x) - x + +class Foo(object): + def bar(self, x): + return x*x+x +_foo = Foo() + +def add(x,y): + return x+y + +squared = lambda x:x**2 + +class Bar: + pass +_bar = Bar() + +# repeat, but from test_source.py +import test_source as ts + +# test objects created in other test modules +import test_mixins as tm + +import dill.source as ds + + +def test_isfrommain(): + assert ds.isfrommain(add) == True + assert ds.isfrommain(squared) == True + assert ds.isfrommain(Bar) == True + assert ds.isfrommain(_bar) == True + assert ds.isfrommain(ts.add) == False + assert ds.isfrommain(ts.squared) == False + assert ds.isfrommain(ts.Bar) == False + assert ds.isfrommain(ts._bar) == False + assert ds.isfrommain(tm.quad) == False + assert ds.isfrommain(tm.double_add) == False + assert ds.isfrommain(tm.quadratic) == False + assert ds.isdynamic(add) == False + assert ds.isdynamic(squared) == False + assert ds.isdynamic(ts.add) == False + assert ds.isdynamic(ts.squared) == False + assert ds.isdynamic(tm.double_add) == False + assert ds.isdynamic(tm.quadratic) == False + + +def test_matchlambda(): + assert ds._matchlambda(f, 'f = lambda x: x**2\n') + assert ds._matchlambda(squared, 'squared = lambda x:x**2\n') + assert ds._matchlambda(ts.f, 'f = lambda x: x**2\n') + assert ds._matchlambda(ts.squared, 'squared = lambda x:x**2\n') + + +def test_findsource(): + lines, lineno = ds.findsource(add) + assert lines[lineno] == 'def add(x,y):\n' + lines, lineno = ds.findsource(ts.add) + assert lines[lineno] == 'def add(x,y):\n' + lines, lineno = ds.findsource(squared) + assert lines[lineno] == 'squared = lambda x:x**2\n' + lines, lineno = ds.findsource(ts.squared) + assert lines[lineno] == 'squared = lambda x:x**2\n' + lines, lineno = ds.findsource(Bar) + assert lines[lineno] == 'class Bar:\n' + lines, lineno = ds.findsource(ts.Bar) + assert lines[lineno] == 'class Bar:\n' + lines, lineno = ds.findsource(_bar) + assert lines[lineno] == 'class Bar:\n' + lines, lineno = ds.findsource(ts._bar) + assert lines[lineno] == 'class Bar:\n' + lines, lineno = ds.findsource(tm.quad) + assert lines[lineno] == 'def quad(a=1, b=1, c=0):\n' + lines, lineno = ds.findsource(tm.double_add) + assert lines[lineno] == ' def func(*args, **kwds):\n' + lines, lineno = ds.findsource(tm.quadratic) + assert lines[lineno] == ' def dec(f):\n' + + +def test_getsourcelines(): + assert ''.join(ds.getsourcelines(add)[0]) == 'def add(x,y):\n return x+y\n' + assert ''.join(ds.getsourcelines(ts.add)[0]) == 'def add(x,y):\n return x+y\n' + assert ''.join(ds.getsourcelines(squared)[0]) == 'squared = lambda x:x**2\n' + assert ''.join(ds.getsourcelines(ts.squared)[0]) == 'squared = lambda x:x**2\n' + assert ''.join(ds.getsourcelines(Bar)[0]) == 'class Bar:\n pass\n' + assert ''.join(ds.getsourcelines(ts.Bar)[0]) == 'class Bar:\n pass\n' + assert ''.join(ds.getsourcelines(_bar)[0]) == 'class Bar:\n pass\n' #XXX: ? + assert ''.join(ds.getsourcelines(ts._bar)[0]) == 'class Bar:\n pass\n' #XXX: ? + assert ''.join(ds.getsourcelines(tm.quad)[0]) == 'def quad(a=1, b=1, c=0):\n inverted = [False]\n def invert():\n inverted[0] = not inverted[0]\n def dec(f):\n def func(*args, **kwds):\n x = f(*args, **kwds)\n if inverted[0]: x = -x\n return a*x**2 + b*x + c\n func.__wrapped__ = f\n func.invert = invert\n func.inverted = inverted\n return func\n return dec\n' + assert ''.join(ds.getsourcelines(tm.quadratic)[0]) == ' def dec(f):\n def func(*args,**kwds):\n fx = f(*args,**kwds)\n return a*fx**2 + b*fx + c\n return func\n' + assert ''.join(ds.getsourcelines(tm.quadratic, lstrip=True)[0]) == 'def dec(f):\n def func(*args,**kwds):\n fx = f(*args,**kwds)\n return a*fx**2 + b*fx + c\n return func\n' + assert ''.join(ds.getsourcelines(tm.quadratic, enclosing=True)[0]) == 'def quad_factory(a=1,b=1,c=0):\n def dec(f):\n def func(*args,**kwds):\n fx = f(*args,**kwds)\n return a*fx**2 + b*fx + c\n return func\n return dec\n' + assert ''.join(ds.getsourcelines(tm.double_add)[0]) == ' def func(*args, **kwds):\n x = f(*args, **kwds)\n if inverted[0]: x = -x\n return a*x**2 + b*x + c\n' + assert ''.join(ds.getsourcelines(tm.double_add, enclosing=True)[0]) == 'def quad(a=1, b=1, c=0):\n inverted = [False]\n def invert():\n inverted[0] = not inverted[0]\n def dec(f):\n def func(*args, **kwds):\n x = f(*args, **kwds)\n if inverted[0]: x = -x\n return a*x**2 + b*x + c\n func.__wrapped__ = f\n func.invert = invert\n func.inverted = inverted\n return func\n return dec\n' + + +def test_indent(): + assert ds.outdent(''.join(ds.getsourcelines(tm.quadratic)[0])) == ''.join(ds.getsourcelines(tm.quadratic, lstrip=True)[0]) + assert ds.indent(''.join(ds.getsourcelines(tm.quadratic, lstrip=True)[0]), 2) == ''.join(ds.getsourcelines(tm.quadratic)[0]) + + +def test_dumpsource(): + local = {} + exec(ds.dumpsource(add, alias='raw'), {}, local) + exec(ds.dumpsource(ts.add, alias='mod'), {}, local) + assert local['raw'](1,2) == local['mod'](1,2) + exec(ds.dumpsource(squared, alias='raw'), {}, local) + exec(ds.dumpsource(ts.squared, alias='mod'), {}, local) + assert local['raw'](3) == local['mod'](3) + assert ds._wrap(add)(1,2) == ds._wrap(ts.add)(1,2) + assert ds._wrap(squared)(3) == ds._wrap(ts.squared)(3) + + +def test_name(): + assert ds._namespace(add) == ds.getname(add, fqn=True).split('.') + assert ds._namespace(ts.add) == ds.getname(ts.add, fqn=True).split('.') + assert ds._namespace(squared) == ds.getname(squared, fqn=True).split('.') + assert ds._namespace(ts.squared) == ds.getname(ts.squared, fqn=True).split('.') + assert ds._namespace(Bar) == ds.getname(Bar, fqn=True).split('.') + assert ds._namespace(ts.Bar) == ds.getname(ts.Bar, fqn=True).split('.') + assert ds._namespace(tm.quad) == ds.getname(tm.quad, fqn=True).split('.') + #XXX: the following also works, however behavior may be wrong for nested functions + #assert ds._namespace(tm.double_add) == ds.getname(tm.double_add, fqn=True).split('.') + #assert ds._namespace(tm.quadratic) == ds.getname(tm.quadratic, fqn=True).split('.') + assert ds.getname(add) == 'add' + assert ds.getname(ts.add) == 'add' + assert ds.getname(squared) == 'squared' + assert ds.getname(ts.squared) == 'squared' + assert ds.getname(Bar) == 'Bar' + assert ds.getname(ts.Bar) == 'Bar' + assert ds.getname(tm.quad) == 'quad' + assert ds.getname(tm.double_add) == 'func' #XXX: ? + assert ds.getname(tm.quadratic) == 'dec' #XXX: ? + + +def test_getimport(): + local = {} + exec(ds.getimport(add, alias='raw'), {}, local) + exec(ds.getimport(ts.add, alias='mod'), {}, local) + assert local['raw'](1,2) == local['mod'](1,2) + exec(ds.getimport(squared, alias='raw'), {}, local) + exec(ds.getimport(ts.squared, alias='mod'), {}, local) + assert local['raw'](3) == local['mod'](3) + exec(ds.getimport(Bar, alias='raw'), {}, local) + exec(ds.getimport(ts.Bar, alias='mod'), {}, local) + assert ds.getname(local['raw']) == ds.getname(local['mod']) + exec(ds.getimport(tm.quad, alias='mod'), {}, local) + assert local['mod']()(sum)([1,2,3]) == tm.quad()(sum)([1,2,3]) + #FIXME: wrong results for nested functions (e.g. tm.double_add, tm.quadratic) + + +def test_importable(): + assert ds.importable(add, source=False) == ds.getimport(add) + assert ds.importable(add) == ds.getsource(add) + assert ds.importable(squared, source=False) == ds.getimport(squared) + assert ds.importable(squared) == ds.getsource(squared) + assert ds.importable(Bar, source=False) == ds.getimport(Bar) + assert ds.importable(Bar) == ds.getsource(Bar) + assert ds.importable(ts.add) == ds.getimport(ts.add) + assert ds.importable(ts.add, source=True) == ds.getsource(ts.add) + assert ds.importable(ts.squared) == ds.getimport(ts.squared) + assert ds.importable(ts.squared, source=True) == ds.getsource(ts.squared) + assert ds.importable(ts.Bar) == ds.getimport(ts.Bar) + assert ds.importable(ts.Bar, source=True) == ds.getsource(ts.Bar) + + +if __name__ == '__main__': + test_isfrommain() + test_matchlambda() + test_findsource() + test_getsourcelines() + test_indent() + test_dumpsource() + test_name() + test_getimport() + test_importable() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_temp.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_temp.py new file mode 100644 index 0000000000000000000000000000000000000000..6de1995b37d097030a7f4916b715680b37fa8dcc --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_temp.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python +# +# Author: Mike McKerns (mmckerns @caltech and @uqfoundation) +# Copyright (c) 2008-2016 California Institute of Technology. +# Copyright (c) 2016-2026 The Uncertainty Quantification Foundation. +# License: 3-clause BSD. The full license text is available at: +# - https://github.com/uqfoundation/dill/blob/master/LICENSE + +import sys +from dill.temp import dump, dump_source, dumpIO, dumpIO_source +from dill.temp import load, load_source, loadIO, loadIO_source +WINDOWS = sys.platform[:3] == 'win' + + +f = lambda x: x**2 +x = [1,2,3,4,5] + +# source code to tempfile +def test_code_to_tempfile(): + if not WINDOWS: #see: https://bugs.python.org/issue14243 + pyfile = dump_source(f, alias='_f') + _f = load_source(pyfile) + assert _f(4) == f(4) + +# source code to stream +def test_code_to_stream(): + pyfile = dumpIO_source(f, alias='_f') + _f = loadIO_source(pyfile) + assert _f(4) == f(4) + +# pickle to tempfile +def test_pickle_to_tempfile(): + if not WINDOWS: #see: https://bugs.python.org/issue14243 + dumpfile = dump(x) + _x = load(dumpfile) + assert _x == x + +# pickle to stream +def test_pickle_to_stream(): + dumpfile = dumpIO(x) + _x = loadIO(dumpfile) + assert _x == x + +### now testing the objects ### +f = lambda x: x**2 +def g(x): return f(x) - x + +def h(x): + def g(x): return x + return g(x) - x + +class Foo(object): + def bar(self, x): + return x*x+x +_foo = Foo() + +def add(x,y): + return x+y + +# yes, same as 'f', but things are tricky when it comes to pointers +squared = lambda x:x**2 + +class Bar: + pass +_bar = Bar() + + +# test function-type objects that take 2 args +def test_two_arg_functions(): + for obj in [add]: + pyfile = dumpIO_source(obj, alias='_obj') + _obj = loadIO_source(pyfile) + assert _obj(4,2) == obj(4,2) + +# test function-type objects that take 1 arg +def test_one_arg_functions(): + for obj in [g, h, squared]: + pyfile = dumpIO_source(obj, alias='_obj') + _obj = loadIO_source(pyfile) + assert _obj(4) == obj(4) + +# test instance-type objects +#for obj in [_bar, _foo]: +# pyfile = dumpIO_source(obj, alias='_obj') +# _obj = loadIO_source(pyfile) +# assert type(_obj) == type(obj) + +# test the rest of the objects +def test_the_rest(): + for obj in [Bar, Foo, Foo.bar, _foo.bar]: + pyfile = dumpIO_source(obj, alias='_obj') + _obj = loadIO_source(pyfile) + assert _obj.__name__ == obj.__name__ + + +if __name__ == '__main__': + test_code_to_tempfile() + test_code_to_stream() + test_pickle_to_tempfile() + test_pickle_to_stream() + test_two_arg_functions() + test_one_arg_functions() + test_the_rest() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_threads.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_threads.py new file mode 100644 index 0000000000000000000000000000000000000000..c8843d9ea9406ab23e3314f0fc18ccfa5938de97 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_threads.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +# +# Author: Mike McKerns (mmckerns @caltech and @uqfoundation) +# Copyright (c) 2024-2026 The Uncertainty Quantification Foundation. +# License: 3-clause BSD. The full license text is available at: +# - https://github.com/uqfoundation/dill/blob/master/LICENSE + +import dill +dill.settings['recurse'] = True + + +def test_new_thread(): + import threading + t = threading.Thread() + t_ = dill.copy(t) + assert t.is_alive() == t_.is_alive() + for i in ['daemon','name','ident','native_id']: + if hasattr(t, i): + assert getattr(t, i) == getattr(t_, i) + +def test_run_thread(): + import threading + t = threading.Thread() + t.start() + t_ = dill.copy(t) + assert t.is_alive() == t_.is_alive() + for i in ['daemon','name','ident','native_id']: + if hasattr(t, i): + assert getattr(t, i) == getattr(t_, i) + +def test_join_thread(): + import threading + t = threading.Thread() + t.start() + t.join() + t_ = dill.copy(t) + assert t.is_alive() == t_.is_alive() + for i in ['daemon','name','ident','native_id']: + if hasattr(t, i): + assert getattr(t, i) == getattr(t_, i) + + +if __name__ == '__main__': + test_new_thread() + test_run_thread() + test_join_thread() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_weakref.py b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_weakref.py new file mode 100644 index 0000000000000000000000000000000000000000..5962de59d50db22eb25adcf4d23787edcf2742fb --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/dill/tests/test_weakref.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python +# +# Author: Mike McKerns (mmckerns @caltech and @uqfoundation) +# Copyright (c) 2008-2016 California Institute of Technology. +# Copyright (c) 2016-2026 The Uncertainty Quantification Foundation. +# License: 3-clause BSD. The full license text is available at: +# - https://github.com/uqfoundation/dill/blob/master/LICENSE + +import dill +dill.settings['recurse'] = True +import weakref + +class _class: + def _method(self): + pass + +class _callable_class: + def __call__(self): + pass + +def _function(): + pass + + +def test_weakref(): + o = _class() + oc = _callable_class() + f = _function + x = _class + + # ReferenceType + r = weakref.ref(o) + d_r = weakref.ref(_class()) + fr = weakref.ref(f) + xr = weakref.ref(x) + + # ProxyType + p = weakref.proxy(o) + d_p = weakref.proxy(_class()) + + # CallableProxyType + cp = weakref.proxy(oc) + d_cp = weakref.proxy(_callable_class()) + fp = weakref.proxy(f) + xp = weakref.proxy(x) + + objlist = [r,d_r,fr,xr, p,d_p, cp,d_cp,fp,xp] + #dill.detect.trace(True) + + for obj in objlist: + res = dill.detect.errors(obj) + if res: + print ("%r:\n %s" % (obj, res)) + # else: + # print ("PASS: %s" % obj) + assert not res + +def test_dictproxy(): + from dill._dill import DictProxyType + try: + m = DictProxyType({"foo": "bar"}) + except Exception: + m = type.__dict__ + mp = dill.copy(m) + assert mp.items() == m.items() + + +if __name__ == '__main__': + test_weakref() + from dill._dill import IS_PYPY + if not IS_PYPY: + test_dictproxy() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..8d7739e471f8da3c5afb6307d288d878142c6fc9 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/__init__.py @@ -0,0 +1,93 @@ +""" +A platform independent file lock that supports the with-statement. + +.. autodata:: filelock.__version__ + :no-value: + +""" + +from __future__ import annotations + +import sys +import warnings +from typing import TYPE_CHECKING + +from ._api import AcquireReturnProxy, BaseFileLock +from ._error import Timeout + +if TYPE_CHECKING: + from ._async_read_write import ( + AsyncAcquireReadWriteReturnProxy, + AsyncReadWriteLock, + ) + from ._read_write import ReadWriteLock +else: + try: + from ._async_read_write import AsyncAcquireReadWriteReturnProxy, AsyncReadWriteLock + from ._read_write import ReadWriteLock + except ImportError: # sqlite3 may be unavailable if Python was built without it or the C library is missing + AsyncAcquireReadWriteReturnProxy = None + AsyncReadWriteLock = None + ReadWriteLock = None + +from ._soft import SoftFileLock +from ._soft_rw import AsyncAcquireSoftReadWriteReturnProxy, AsyncSoftReadWriteLock, SoftReadWriteLock +from ._unix import UnixFileLock, has_fcntl +from ._windows import WindowsFileLock +from .asyncio import ( + AsyncAcquireReturnProxy, + AsyncSoftFileLock, + AsyncUnixFileLock, + AsyncWindowsFileLock, + BaseAsyncFileLock, +) +from .version import version + +#: version of the project as a string +__version__: str = version + + +if sys.platform == "win32": # pragma: win32 cover + _FileLock: type[BaseFileLock] = WindowsFileLock + _AsyncFileLock: type[BaseAsyncFileLock] = AsyncWindowsFileLock +else: # pragma: win32 no cover # noqa: PLR5501 + if has_fcntl: + _FileLock: type[BaseFileLock] = UnixFileLock + _AsyncFileLock: type[BaseAsyncFileLock] = AsyncUnixFileLock + else: + _FileLock = SoftFileLock + _AsyncFileLock = AsyncSoftFileLock + if warnings is not None: + warnings.warn("only soft file lock is available", stacklevel=2) + +if TYPE_CHECKING: + FileLock = SoftFileLock + AsyncFileLock = AsyncSoftFileLock +else: + #: Alias for the lock, which should be used for the current platform. + FileLock = _FileLock + AsyncFileLock = _AsyncFileLock + + +__all__ = [ + "AcquireReturnProxy", + "AsyncAcquireReadWriteReturnProxy", + "AsyncAcquireReturnProxy", + "AsyncAcquireSoftReadWriteReturnProxy", + "AsyncFileLock", + "AsyncReadWriteLock", + "AsyncSoftFileLock", + "AsyncSoftReadWriteLock", + "AsyncUnixFileLock", + "AsyncWindowsFileLock", + "BaseAsyncFileLock", + "BaseFileLock", + "FileLock", + "ReadWriteLock", + "SoftFileLock", + "SoftReadWriteLock", + "Timeout", + "UnixFileLock", + "WindowsFileLock", + "__version__", +] diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/_api.py b/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/_api.py new file mode 100644 index 0000000000000000000000000000000000000000..d5793c2e39f266536359496e7989f866499b8601 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/_api.py @@ -0,0 +1,582 @@ +from __future__ import annotations + +import contextlib +import inspect +import logging +import os +import pathlib +import sys +import time +import warnings +from abc import ABCMeta, abstractmethod +from dataclasses import dataclass +from threading import local +from typing import TYPE_CHECKING, Any, TypeVar +from weakref import WeakValueDictionary + +from ._error import Timeout + +#: Sentinel indicating that no explicit file permission mode was passed. +#: When used, lock files are created with 0o666 (letting umask and default ACLs control the final permissions) +#: and fchmod is skipped so that POSIX default ACL inheritance is preserved. +_UNSET_FILE_MODE: int = -1 + +if TYPE_CHECKING: + from collections.abc import Callable + from types import TracebackType + + from ._read_write import ReadWriteLock + from ._soft_rw import SoftReadWriteLock + + if sys.version_info >= (3, 11): # pragma: no cover (py311+) + from typing import Self + else: # pragma: no cover ( None: + super().__init__() + self.held: dict[str, int] = {} + + +_registry = _ThreadLocalRegistry() + + +# This is a helper class which is returned by :meth:`BaseFileLock.acquire` and wraps the lock to make sure __enter__ +# is not called twice when entering the with statement. If we would simply return *self*, the lock would be acquired +# again in the *__enter__* method of the BaseFileLock, but not released again automatically. issue #37 (memory leak) +class AcquireReturnProxy: + """A context-aware object that will release the lock file when exiting.""" + + def __init__(self, lock: BaseFileLock | ReadWriteLock | SoftReadWriteLock) -> None: + self.lock: BaseFileLock | ReadWriteLock | SoftReadWriteLock = lock + + def __enter__(self) -> BaseFileLock | ReadWriteLock | SoftReadWriteLock: + return self.lock + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, + ) -> None: + self.lock.release() + + +@dataclass +class FileLockContext: + """A dataclass which holds the context for a ``BaseFileLock`` object.""" + + # The context is held in a separate class to allow optional use of thread local storage via the + # ThreadLocalFileContext class. + + #: The path to the lock file. + lock_file: str + + #: The default timeout value. + timeout: float + + #: The mode for the lock files + mode: int + + #: Whether the lock should be blocking or not + blocking: bool + + #: The default polling interval value. + poll_interval: float + + #: The lock lifetime in seconds; ``None`` means the lock never expires. + lifetime: float | None = None + + #: The file descriptor for the *_lock_file* as it is returned by the os.open() function, not None when lock held + lock_file_fd: int | None = None + + #: The lock counter is used for implementing the nested locking mechanism. + lock_counter: int = 0 # When the lock is acquired is increased and the lock is only released, when this value is 0 + + +class ThreadLocalFileContext(FileLockContext, local): + """A thread local version of the ``FileLockContext`` class.""" + + +_T = TypeVar("_T", bound="BaseFileLock") + + +class FileLockMeta(ABCMeta): + _instances: WeakValueDictionary[str, BaseFileLock] + + def __call__( # noqa: PLR0913 + cls: type[_T], + lock_file: str | os.PathLike[str], + timeout: float = -1, + mode: int = _UNSET_FILE_MODE, + thread_local: bool = True, # noqa: FBT001, FBT002 + *, + blocking: bool = True, + is_singleton: bool = False, + poll_interval: float = 0.05, + lifetime: float | None = None, + **kwargs: Any, # capture remaining kwargs for subclasses # noqa: ANN401 + ) -> _T: + if is_singleton: + instance = cls._instances.get(str(lock_file)) + if instance: + params_to_check = { + "thread_local": (thread_local, instance.is_thread_local()), + "timeout": (timeout, instance.timeout), + "mode": (mode, instance._context.mode), # noqa: SLF001 + "blocking": (blocking, instance.blocking), + "poll_interval": (poll_interval, instance.poll_interval), + "lifetime": (lifetime, instance.lifetime), + } + + non_matching_params = { + name: (passed_param, set_param) + for name, (passed_param, set_param) in params_to_check.items() + if passed_param != set_param + } + if not non_matching_params: + return instance # ty: ignore[invalid-return-type] # https://github.com/astral-sh/ty/issues/3231 + + # parameters do not match; raise error + msg = "Singleton lock instances cannot be initialized with differing arguments" + msg += "\nNon-matching arguments: " + for param_name, (passed_param, set_param) in non_matching_params.items(): + msg += f"\n\t{param_name} (existing lock has {set_param} but {passed_param} was passed)" + raise ValueError(msg) + + # Workaround to make `__init__`'s params optional in subclasses + # E.g. virtualenv changes the signature of the `__init__` method in the `BaseFileLock` class descendant + # (https://github.com/tox-dev/filelock/pull/340) + + all_params = { + "timeout": timeout, + "mode": mode, + "thread_local": thread_local, + "blocking": blocking, + "is_singleton": is_singleton, + "poll_interval": poll_interval, + "lifetime": lifetime, + **kwargs, + } + + present_params = inspect.signature(cls.__init__).parameters + init_params = {key: value for key, value in all_params.items() if key in present_params} + + instance = super().__call__(lock_file, **init_params) + + if is_singleton: + cls._instances[str(lock_file)] = instance + + return instance + + +class BaseFileLock(contextlib.ContextDecorator, metaclass=FileLockMeta): + """ + Abstract base class for a file lock object. + + Provides a reentrant, cross-process exclusive lock backed by OS-level primitives. Subclasses implement the actual + locking mechanism (:class:`UnixFileLock `, :class:`WindowsFileLock + `, :class:`SoftFileLock `). + + """ + + _instances: WeakValueDictionary[str, BaseFileLock] + + def __init_subclass__(cls, **kwargs: dict[str, Any]) -> None: + """Setup unique state for lock subclasses.""" + super().__init_subclass__(**kwargs) + cls._instances = WeakValueDictionary() + + def __init__( # noqa: PLR0913 + self, + lock_file: str | os.PathLike[str], + timeout: float = -1, + mode: int = _UNSET_FILE_MODE, + thread_local: bool = True, # noqa: FBT001, FBT002 + *, + blocking: bool = True, + is_singleton: bool = False, + poll_interval: float = 0.05, + lifetime: float | None = None, + ) -> None: + """ + Create a new lock object. + + :param lock_file: path to the file + :param timeout: default timeout when acquiring the lock, in seconds. It will be used as fallback value in the + acquire method, if no timeout value (``None``) is given. If you want to disable the timeout, set it to a + negative value. A timeout of 0 means that there is exactly one attempt to acquire the file lock. + :param mode: file permissions for the lockfile. When not specified, the OS controls permissions via umask and + default ACLs, preserving POSIX default ACL inheritance in shared directories. + :param thread_local: Whether this object's internal context should be thread local or not. If this is set to + ``False`` then the lock will be reentrant across threads. + :param blocking: whether the lock should be blocking or not + :param is_singleton: If this is set to ``True`` then only one instance of this class will be created per lock + file. This is useful if you want to use the lock object for reentrant locking without needing to pass the + same object around. + :param poll_interval: default interval for polling the lock file, in seconds. It will be used as fallback value + in the acquire method, if no poll_interval value (``None``) is given. + :param lifetime: maximum time in seconds a lock can be held before it is considered expired. When set, a waiting + process will break a lock whose file modification time is older than ``lifetime`` seconds. ``None`` (the + default) means locks never expire. + + """ + self._is_thread_local = thread_local + self._is_singleton = is_singleton + + # Create the context. Note that external code should not work with the context directly and should instead use + # properties of this class. + kwargs: dict[str, Any] = { + "lock_file": os.fspath(lock_file), + "timeout": timeout, + "mode": mode, + "blocking": blocking, + "poll_interval": poll_interval, + "lifetime": lifetime, + } + self._context: FileLockContext = (ThreadLocalFileContext if thread_local else FileLockContext)(**kwargs) + + def is_thread_local(self) -> bool: + """:returns: a flag indicating if this lock is thread local or not""" + return self._is_thread_local + + @property + def is_singleton(self) -> bool: + """ + :returns: a flag indicating if this lock is singleton or not + + .. versionadded:: 3.13.0 + + """ + return self._is_singleton + + @property + def lock_file(self) -> str: + """:returns: path to the lock file""" + return self._context.lock_file + + @property + def timeout(self) -> float: + """ + :returns: the default timeout value, in seconds + + .. versionadded:: 2.0.0 + + """ + return self._context.timeout + + @timeout.setter + def timeout(self, value: float | str) -> None: + """ + Change the default timeout value. + + :param value: the new value, in seconds + + """ + self._context.timeout = float(value) + + @property + def blocking(self) -> bool: + """ + :returns: whether the locking is blocking or not + + .. versionadded:: 3.14.0 + + """ + return self._context.blocking + + @blocking.setter + def blocking(self, value: bool) -> None: + """ + Change the default blocking value. + + :param value: the new value as bool + + """ + self._context.blocking = value + + @property + def poll_interval(self) -> float: + """ + :returns: the default polling interval, in seconds + + .. versionadded:: 3.24.0 + + """ + return self._context.poll_interval + + @poll_interval.setter + def poll_interval(self, value: float) -> None: + """ + Change the default polling interval. + + :param value: the new value, in seconds + + """ + self._context.poll_interval = value + + @property + def lifetime(self) -> float | None: + """ + :returns: the lock lifetime in seconds, or ``None`` if the lock never expires + + .. versionadded:: 3.24.0 + + """ + return self._context.lifetime + + @lifetime.setter + def lifetime(self, value: float | None) -> None: + """ + Change the lock lifetime. + + :param value: the new value in seconds, or ``None`` to disable expiration + + """ + self._context.lifetime = value + + @property + def mode(self) -> int: + """:returns: the file permissions for the lockfile""" + return 0o644 if self._context.mode == _UNSET_FILE_MODE else self._context.mode + + @property + def has_explicit_mode(self) -> bool: + """:returns: whether the file permissions were explicitly set""" + return self._context.mode != _UNSET_FILE_MODE + + def _open_mode(self) -> int: + """:returns: the mode for os.open() — 0o666 when unset (let umask/ACLs decide), else the explicit mode""" + return 0o666 if self._context.mode == _UNSET_FILE_MODE else self._context.mode + + def _try_break_expired_lock(self) -> None: + """Remove the lock file if its modification time exceeds the configured :attr:`lifetime`.""" + if (lifetime := self._context.lifetime) is None: + return + with contextlib.suppress(OSError): + if time.time() - pathlib.Path(self.lock_file).stat().st_mtime < lifetime: + return + break_path = f"{self.lock_file}.break.{os.getpid()}" + pathlib.Path(self.lock_file).rename(break_path) + pathlib.Path(break_path).unlink() + + @abstractmethod + def _acquire(self) -> None: + """If the file lock could be acquired, self._context.lock_file_fd holds the file descriptor of the lock file.""" + raise NotImplementedError + + @abstractmethod + def _release(self) -> None: + """Releases the lock and sets self._context.lock_file_fd to None.""" + raise NotImplementedError + + @property + def is_locked(self) -> bool: + """ + :returns: A boolean indicating if the lock file is holding the lock currently. + + .. versionchanged:: 2.0.0 + + This was previously a method and is now a property. + + """ + return self._context.lock_file_fd is not None + + @property + def lock_counter(self) -> int: + """:returns: The number of times this lock has been acquired (but not yet released).""" + return self._context.lock_counter + + @staticmethod + def _check_give_up( # noqa: PLR0913 + lock_id: int, + lock_filename: str, + *, + blocking: bool, + cancel_check: Callable[[], bool] | None, + timeout: float, + start_time: float, + ) -> bool: + if blocking is False: + _LOGGER.debug("Failed to immediately acquire lock %s on %s", lock_id, lock_filename) + return True + if cancel_check is not None and cancel_check(): + _LOGGER.debug("Cancellation requested for lock %s on %s", lock_id, lock_filename) + return True + if 0 <= timeout < time.perf_counter() - start_time: + _LOGGER.debug("Timeout on acquiring lock %s on %s", lock_id, lock_filename) + return True + return False + + def acquire( # noqa: C901 + self, + timeout: float | None = None, + poll_interval: float | None = None, + *, + poll_intervall: float | None = None, + blocking: bool | None = None, + cancel_check: Callable[[], bool] | None = None, + ) -> AcquireReturnProxy: + """ + Try to acquire the file lock. + + :param timeout: maximum wait time for acquiring the lock, ``None`` means use the default :attr:`~timeout` is and + if ``timeout < 0``, there is no timeout and this method will block until the lock could be acquired + :param poll_interval: interval of trying to acquire the lock file, ``None`` means use the default + :attr:`~poll_interval` + :param poll_intervall: deprecated, kept for backwards compatibility, use ``poll_interval`` instead + :param blocking: defaults to True. If False, function will return immediately if it cannot obtain a lock on the + first attempt. Otherwise, this method will block until the timeout expires or the lock is acquired. + :param cancel_check: a callable returning ``True`` when the acquisition should be canceled. Checked on each poll + iteration. When triggered, raises :class:`~Timeout` just like an expired timeout. + + :returns: a context object that will unlock the file when the context is exited + + :raises Timeout: if fails to acquire lock within the timeout period + + .. code-block:: python + + # You can use this method in the context manager (recommended) + with lock.acquire(): + pass + + # Or use an equivalent try-finally construct: + lock.acquire() + try: + pass + finally: + lock.release() + + .. versionchanged:: 2.0.0 + + This method returns now a *proxy* object instead of *self*, so that it can be used in a with statement + without side effects. + + """ + # Use the default timeout, if no timeout is provided. + if timeout is None: + timeout = self._context.timeout + + if blocking is None: + blocking = self._context.blocking + + if poll_intervall is not None: + msg = "use poll_interval instead of poll_intervall" + warnings.warn(msg, DeprecationWarning, stacklevel=2) + poll_interval = poll_intervall + + poll_interval = poll_interval if poll_interval is not None else self._context.poll_interval + + # Increment the number right at the beginning. We can still undo it, if something fails. + self._context.lock_counter += 1 + + lock_id = id(self) + lock_filename = self.lock_file + canonical = _canonical(lock_filename) + + would_block = self._context.lock_counter == 1 and not self.is_locked and timeout < 0 and blocking + if would_block and (existing := _registry.held.get(canonical)) is not None and existing != lock_id: + self._context.lock_counter -= 1 + msg = ( + f"Deadlock: lock '{lock_filename}' is already held by a different " + f"FileLock instance in this thread. Use is_singleton=True to " + f"enable reentrant locking across instances." + ) + raise RuntimeError(msg) + + start_time = time.perf_counter() + try: + while True: + if not self.is_locked: + self._try_break_expired_lock() + _LOGGER.debug("Attempting to acquire lock %s on %s", lock_id, lock_filename) + self._acquire() + if self.is_locked: + _LOGGER.debug("Lock %s acquired on %s", lock_id, lock_filename) + break + if self._check_give_up( + lock_id, + lock_filename, + blocking=blocking, + cancel_check=cancel_check, + timeout=timeout, + start_time=start_time, + ): + raise Timeout(lock_filename) # noqa: TRY301 + msg = "Lock %s not acquired on %s, waiting %s seconds ..." + _LOGGER.debug(msg, lock_id, lock_filename, poll_interval) + time.sleep(poll_interval) + except BaseException: + self._context.lock_counter = max(0, self._context.lock_counter - 1) + if self._context.lock_counter == 0: + _registry.held.pop(canonical, None) + raise + if self._context.lock_counter == 1: + _registry.held[canonical] = lock_id + return AcquireReturnProxy(lock=self) + + def release(self, force: bool = False) -> None: # noqa: FBT001, FBT002 + """ + Release the file lock. The lock is only completely released when the lock counter reaches 0. The lock file + itself is not automatically deleted. + + :param force: If true, the lock counter is ignored and the lock is released in every case. + + """ + if self.is_locked: + self._context.lock_counter -= 1 + + if self._context.lock_counter == 0 or force: + lock_id, lock_filename = id(self), self.lock_file + + _LOGGER.debug("Attempting to release lock %s on %s", lock_id, lock_filename) + self._release() + self._context.lock_counter = 0 + _registry.held.pop(_canonical(lock_filename), None) + _LOGGER.debug("Lock %s released on %s", lock_id, lock_filename) + + def __enter__(self) -> Self: + """ + Acquire the lock. + + :returns: the lock object + + """ + self.acquire() + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, + ) -> None: + """ + Release the lock. + + :param exc_type: the exception type if raised + :param exc_value: the exception value if raised + :param traceback: the exception traceback if raised + + """ + self.release() + + def __del__(self) -> None: + """Called when the lock object is deleted.""" + self.release(force=True) + + +__all__ = [ + "_UNSET_FILE_MODE", + "AcquireReturnProxy", + "BaseFileLock", +] diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/_async_read_write.py b/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/_async_read_write.py new file mode 100644 index 0000000000000000000000000000000000000000..05d4e156b606ee58368fa67140c8bfe1d1daa221 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/_async_read_write.py @@ -0,0 +1,207 @@ +"""Async wrapper around :class:`ReadWriteLock` for use with ``asyncio``.""" + +from __future__ import annotations + +import asyncio +import functools +from concurrent.futures import ThreadPoolExecutor +from contextlib import asynccontextmanager +from typing import TYPE_CHECKING + +from ._read_write import ReadWriteLock + +if TYPE_CHECKING: + import os + from collections.abc import AsyncGenerator, Callable + from concurrent import futures + from types import TracebackType + + +class AsyncAcquireReadWriteReturnProxy: + """Context-aware object that releases the async read/write lock on exit.""" + + def __init__(self, lock: AsyncReadWriteLock) -> None: + self.lock = lock + + async def __aenter__(self) -> AsyncReadWriteLock: + return self.lock + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, + ) -> None: + await self.lock.release() + + +class AsyncReadWriteLock: + """ + Async wrapper around :class:`ReadWriteLock` for use in ``asyncio`` applications. + + Because Python's :mod:`sqlite3` module has no async API, all blocking SQLite operations are dispatched to a thread + pool via ``loop.run_in_executor()``. Reentrancy, upgrade/downgrade rules, and singleton behavior are delegated + to the underlying :class:`ReadWriteLock`. + + :param lock_file: path to the SQLite database file used as the lock + :param timeout: maximum wait time in seconds; ``-1`` means block indefinitely + :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately when the lock is unavailable + :param is_singleton: if ``True``, reuse existing :class:`ReadWriteLock` instances for the same resolved path + :param loop: event loop for ``run_in_executor``; ``None`` uses the running loop + :param executor: executor for ``run_in_executor``; ``None`` uses the default executor + + .. versionadded:: 3.21.0 + + """ + + def __init__( # noqa: PLR0913 + self, + lock_file: str | os.PathLike[str], + timeout: float = -1, + *, + blocking: bool = True, + is_singleton: bool = True, + loop: asyncio.AbstractEventLoop | None = None, + executor: futures.Executor | None = None, + ) -> None: + self._lock = ReadWriteLock(lock_file, timeout, blocking=blocking, is_singleton=is_singleton) + self._loop = loop + self._owns_executor = executor is None + self._executor = executor or ThreadPoolExecutor(max_workers=1) + + @property + def lock_file(self) -> str: + """:returns: the path to the lock file.""" + return self._lock.lock_file + + @property + def timeout(self) -> float: + """:returns: the default timeout.""" + return self._lock.timeout + + @property + def blocking(self) -> bool: + """:returns: whether blocking is enabled by default.""" + return self._lock.blocking + + @property + def loop(self) -> asyncio.AbstractEventLoop | None: + """:returns: the event loop (or ``None`` for the running loop).""" + return self._loop + + @property + def executor(self) -> futures.Executor | None: + """:returns: the executor (or ``None`` for the default).""" + return self._executor + + async def _run(self, func: Callable[..., object], *args: object, **kwargs: object) -> object: + loop = self._loop or asyncio.get_running_loop() + return await loop.run_in_executor(self._executor, functools.partial(func, *args, **kwargs)) + + async def acquire_read(self, timeout: float = -1, *, blocking: bool = True) -> AsyncAcquireReadWriteReturnProxy: + """ + Acquire a shared read lock. + + See :meth:`ReadWriteLock.acquire_read` for full semantics. + + :param timeout: maximum wait time in seconds; ``-1`` means block indefinitely + :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately when the lock is unavailable + + :returns: a proxy that can be used as an async context manager to release the lock + + :raises RuntimeError: if a write lock is already held on this instance + :raises Timeout: if the lock cannot be acquired within *timeout* seconds + + """ + await self._run(self._lock.acquire_read, timeout, blocking=blocking) + return AsyncAcquireReadWriteReturnProxy(lock=self) + + async def acquire_write(self, timeout: float = -1, *, blocking: bool = True) -> AsyncAcquireReadWriteReturnProxy: + """ + Acquire an exclusive write lock. + + See :meth:`ReadWriteLock.acquire_write` for full semantics. + + :param timeout: maximum wait time in seconds; ``-1`` means block indefinitely + :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately when the lock is unavailable + + :returns: a proxy that can be used as an async context manager to release the lock + + :raises RuntimeError: if a read lock is already held, or a write lock is held by a different thread + :raises Timeout: if the lock cannot be acquired within *timeout* seconds + + """ + await self._run(self._lock.acquire_write, timeout, blocking=blocking) + return AsyncAcquireReadWriteReturnProxy(lock=self) + + async def release(self, *, force: bool = False) -> None: + """ + Release one level of the current lock. + + See :meth:`ReadWriteLock.release` for full semantics. + + :param force: if ``True``, release the lock completely regardless of the current lock level + + :raises RuntimeError: if no lock is currently held and *force* is ``False`` + + """ + await self._run(self._lock.release, force=force) + + @asynccontextmanager + async def read_lock(self, timeout: float | None = None, *, blocking: bool | None = None) -> AsyncGenerator[None]: + """ + Async context manager that acquires and releases a shared read lock. + + Falls back to instance defaults for *timeout* and *blocking* when ``None``. + + :param timeout: maximum wait time in seconds, or ``None`` to use the instance default + :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately; ``None`` uses the instance default + + """ + if timeout is None: + timeout = self._lock.timeout + if blocking is None: + blocking = self._lock.blocking + await self.acquire_read(timeout, blocking=blocking) + try: + yield + finally: + await self.release() + + @asynccontextmanager + async def write_lock(self, timeout: float | None = None, *, blocking: bool | None = None) -> AsyncGenerator[None]: + """ + Async context manager that acquires and releases an exclusive write lock. + + Falls back to instance defaults for *timeout* and *blocking* when ``None``. + + :param timeout: maximum wait time in seconds, or ``None`` to use the instance default + :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately; ``None`` uses the instance default + + """ + if timeout is None: + timeout = self._lock.timeout + if blocking is None: + blocking = self._lock.blocking + await self.acquire_write(timeout, blocking=blocking) + try: + yield + finally: + await self.release() + + async def close(self) -> None: + """ + Release the lock (if held) and close the underlying SQLite connection. + + After calling this method, the lock instance is no longer usable. + + """ + await self._run(self._lock.close) + if self._owns_executor: + self._executor.shutdown(wait=False) + + +__all__ = [ + "AsyncAcquireReadWriteReturnProxy", + "AsyncReadWriteLock", +] diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/_error.py b/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/_error.py new file mode 100644 index 0000000000000000000000000000000000000000..7aaac6b005091938e4907fedb6889a00475c082e --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/_error.py @@ -0,0 +1,30 @@ +from __future__ import annotations + +from typing import Any + + +class Timeout(TimeoutError): # noqa: N818 + """Raised when the lock could not be acquired in *timeout* seconds.""" + + def __init__(self, lock_file: str) -> None: + super().__init__() + self._lock_file = lock_file + + def __reduce__(self) -> str | tuple[Any, ...]: + return self.__class__, (self._lock_file,) # Properly pickle the exception + + def __str__(self) -> str: + return f"The file lock '{self._lock_file}' could not be acquired." + + def __repr__(self) -> str: + return f"{self.__class__.__name__}({self.lock_file!r})" + + @property + def lock_file(self) -> str: + """:returns: The path of the file lock.""" + return self._lock_file + + +__all__ = [ + "Timeout", +] diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/_read_write.py b/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/_read_write.py new file mode 100644 index 0000000000000000000000000000000000000000..9e26cbf3dba1b5bd84ab34d6a781ed1f826496be --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/_read_write.py @@ -0,0 +1,364 @@ +from __future__ import annotations + +import atexit +import logging +import os +import pathlib +import sqlite3 +import threading +import time +from contextlib import contextmanager, suppress +from typing import TYPE_CHECKING, Literal +from weakref import WeakValueDictionary + +from ._api import AcquireReturnProxy +from ._error import Timeout + +if TYPE_CHECKING: + from collections.abc import Generator + +_LOGGER = logging.getLogger("filelock") + +_all_connections: set[sqlite3.Connection] = set() +_all_connections_lock = threading.Lock() + + +def _cleanup_connections() -> None: + with _all_connections_lock: + for con in list(_all_connections): + with suppress(Exception): + con.close() + _all_connections.clear() + + +atexit.register(_cleanup_connections) + +# sqlite3_busy_timeout() accepts a C int, max 2_147_483_647 on 32-bit. Use a lower value to be safe (~23 days). +_MAX_SQLITE_TIMEOUT_MS = 2_000_000_000 - 1 + + +def timeout_for_sqlite(timeout: float, *, blocking: bool, already_waited: float) -> int: + if blocking is False: + return 0 + + if timeout == -1: + return _MAX_SQLITE_TIMEOUT_MS + + if timeout < 0: + msg = "timeout must be a non-negative number or -1" + raise ValueError(msg) + + remaining = max(timeout - already_waited, 0) if timeout > 0 else timeout + timeout_ms = int(remaining * 1000) + if timeout_ms > _MAX_SQLITE_TIMEOUT_MS or timeout_ms < 0: + _LOGGER.warning("timeout %s is too large for SQLite, using %s ms instead", timeout, _MAX_SQLITE_TIMEOUT_MS) + return _MAX_SQLITE_TIMEOUT_MS + return timeout_ms + + +class _ReadWriteLockMeta(type): + """ + Metaclass that handles singleton resolution when is_singleton=True. + + Singleton logic lives here rather than in ReadWriteLock.get_lock so that ``ReadWriteLock(path)`` transparently + returns cached instances without a 2-arg ``super()`` call that type checkers cannot verify. + + """ + + _instances: WeakValueDictionary[pathlib.Path, ReadWriteLock] + _instances_lock: threading.Lock + + def __call__( + cls, + lock_file: str | os.PathLike[str], + timeout: float = -1, + *, + blocking: bool = True, + is_singleton: bool = True, + ) -> ReadWriteLock: + if not is_singleton: + return super().__call__(lock_file, timeout, blocking=blocking, is_singleton=is_singleton) + + normalized = pathlib.Path(lock_file).resolve() + with cls._instances_lock: + if normalized not in cls._instances: + instance = super().__call__(lock_file, timeout, blocking=blocking, is_singleton=is_singleton) + cls._instances[normalized] = instance + else: + instance = cls._instances[normalized] + + if instance.timeout != timeout or instance.blocking != blocking: + msg = ( + f"Singleton lock created with timeout={instance.timeout}, blocking={instance.blocking}," + f" cannot be changed to timeout={timeout}, blocking={blocking}" + ) + raise ValueError(msg) + return instance + + +class ReadWriteLock(metaclass=_ReadWriteLockMeta): + """ + Cross-process read-write lock backed by SQLite. + + Allows concurrent shared readers or a single exclusive writer. The lock is reentrant within the same mode (multiple + ``acquire_read`` calls nest, as do multiple ``acquire_write`` calls from the same thread), but upgrading from read + to write or downgrading from write to read raises :class:`RuntimeError`. Write locks are pinned to the thread that + acquired them. + + By default, ``is_singleton=True``: calling ``ReadWriteLock(path)`` with the same resolved path returns the same + instance. The lock file must use a ``.db`` extension (SQLite database). + + :param lock_file: path to the SQLite database file used as the lock + :param timeout: maximum wait time in seconds; ``-1`` means block indefinitely + :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately when the lock is unavailable + :param is_singleton: if ``True``, reuse existing instances for the same resolved path + + .. versionadded:: 3.21.0 + + """ + + _instances: WeakValueDictionary[pathlib.Path, ReadWriteLock] = WeakValueDictionary() + _instances_lock = threading.Lock() + + @classmethod + def get_lock( + cls, lock_file: str | os.PathLike[str], timeout: float = -1, *, blocking: bool = True + ) -> ReadWriteLock: + """ + Return the singleton :class:`ReadWriteLock` for *lock_file*. + + :param lock_file: path to the SQLite database file used as the lock + :param timeout: maximum wait time in seconds; ``-1`` means block indefinitely + :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately when the lock is unavailable + + :returns: the singleton lock instance + + :raises ValueError: if an instance already exists for this path with different *timeout* or *blocking* values + + """ + return cls(lock_file, timeout, blocking=blocking) + + def __init__( + self, + lock_file: str | os.PathLike[str], + timeout: float = -1, + *, + blocking: bool = True, + is_singleton: bool = True, # noqa: ARG002 # consumed by _ReadWriteLockMeta.__call__ + ) -> None: + self.lock_file = os.fspath(lock_file) + self.timeout = timeout + self.blocking = blocking + self._transaction_lock = threading.Lock() # serializes the (possibly blocking) SQLite transaction work + self._internal_lock = threading.Lock() # protects _lock_level / _current_mode updates and rollback + self._lock_level = 0 + self._current_mode: Literal["read", "write"] | None = None + self._write_thread_id: int | None = None + self._con = sqlite3.connect(self.lock_file, check_same_thread=False) + with _all_connections_lock: + _all_connections.add(self._con) + + def _acquire_transaction_lock(self, *, blocking: bool, timeout: float) -> None: + if not blocking: + acquired = self._transaction_lock.acquire(blocking=False) + elif timeout == -1: + acquired = self._transaction_lock.acquire(blocking=True) + else: + acquired = self._transaction_lock.acquire(blocking=True, timeout=timeout) + if not acquired: + raise Timeout(self.lock_file) from None + + def _validate_reentrant(self, mode: Literal["read", "write"], opposite: str, direction: str) -> AcquireReturnProxy: + if self._current_mode != mode: + msg = ( + f"Cannot acquire {mode} lock on {self.lock_file} (lock id: {id(self)}): " + f"already holding a {opposite} lock ({direction} not allowed)" + ) + raise RuntimeError(msg) + if mode == "write" and (cur := threading.get_ident()) != self._write_thread_id: + msg = ( + f"Cannot acquire write lock on {self.lock_file} (lock id: {id(self)}) " + f"from thread {cur} while it is held by thread {self._write_thread_id}" + ) + raise RuntimeError(msg) + self._lock_level += 1 + return AcquireReturnProxy(lock=self) + + def _configure_and_begin( + self, mode: Literal["read", "write"], timeout: float, *, blocking: bool, start_time: float + ) -> None: + waited = time.perf_counter() - start_time + timeout_ms = timeout_for_sqlite(timeout, blocking=blocking, already_waited=waited) + self._con.execute(f"PRAGMA busy_timeout={timeout_ms};").close() + # Use legacy journal mode (not WAL) because WAL does not block readers when a concurrent EXCLUSIVE + # write transaction is active, making read-write locking impossible without modifying table data. + # MEMORY is safe here since no actual writes happen — crashes cannot corrupt the DB. + # See https://sqlite.org/lang_transaction.html#deferred_immediate_and_exclusive_transactions + # + # Set here (not in __init__) because this pragma itself may block on a locked database, + # so it must run after busy_timeout is configured above. + self._con.execute("PRAGMA journal_mode=MEMORY;").close() + # Recompute remaining timeout after the potentially blocking journal_mode pragma. + waited = time.perf_counter() - start_time + if (recomputed := timeout_for_sqlite(timeout, blocking=blocking, already_waited=waited)) != timeout_ms: + self._con.execute(f"PRAGMA busy_timeout={recomputed};").close() + stmt = "BEGIN EXCLUSIVE TRANSACTION;" if mode == "write" else "BEGIN TRANSACTION;" + self._con.execute(stmt).close() + if mode == "read": + # A SELECT is needed to force SQLite to actually acquire the SHARED lock on the database. + # https://www.sqlite.org/lockingv3.html#transaction_control + self._con.execute("SELECT name FROM sqlite_schema LIMIT 1;").close() + + def _acquire(self, mode: Literal["read", "write"], timeout: float, *, blocking: bool) -> AcquireReturnProxy: + opposite = "write" if mode == "read" else "read" + direction = "downgrade" if mode == "read" else "upgrade" + + with self._internal_lock: + if self._lock_level > 0: + return self._validate_reentrant(mode, opposite, direction) + + start_time = time.perf_counter() + self._acquire_transaction_lock(blocking=blocking, timeout=timeout) + try: + # Double-check: another thread may have acquired the lock while we waited on _transaction_lock. + with self._internal_lock: + if self._lock_level > 0: + return self._validate_reentrant(mode, opposite, direction) + + self._configure_and_begin(mode, timeout, blocking=blocking, start_time=start_time) + + with self._internal_lock: + self._current_mode = mode + self._lock_level = 1 + if mode == "write": + self._write_thread_id = threading.get_ident() + + return AcquireReturnProxy(lock=self) + + except sqlite3.OperationalError as exc: + if "database is locked" not in str(exc): + raise + raise Timeout(self.lock_file) from None + finally: + self._transaction_lock.release() + + def acquire_read(self, timeout: float = -1, *, blocking: bool = True) -> AcquireReturnProxy: + """ + Acquire a shared read lock. + + If this instance already holds a read lock, the lock level is incremented (reentrant). Attempting to acquire a + read lock while holding a write lock raises :class:`RuntimeError` (downgrade not allowed). + + :param timeout: maximum wait time in seconds; ``-1`` means block indefinitely + :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately when the lock is unavailable + + :returns: a proxy that can be used as a context manager to release the lock + + :raises RuntimeError: if a write lock is already held on this instance + :raises Timeout: if the lock cannot be acquired within *timeout* seconds + + """ + return self._acquire("read", timeout, blocking=blocking) + + def acquire_write(self, timeout: float = -1, *, blocking: bool = True) -> AcquireReturnProxy: + """ + Acquire an exclusive write lock. + + If this instance already holds a write lock from the same thread, the lock level is incremented (reentrant). + Attempting to acquire a write lock while holding a read lock raises :class:`RuntimeError` (upgrade not allowed). + Write locks are pinned to the acquiring thread: a different thread trying to re-enter also raises + :class:`RuntimeError`. + + :param timeout: maximum wait time in seconds; ``-1`` means block indefinitely + :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately when the lock is unavailable + + :returns: a proxy that can be used as a context manager to release the lock + + :raises RuntimeError: if a read lock is already held, or a write lock is held by a different thread + :raises Timeout: if the lock cannot be acquired within *timeout* seconds + + """ + return self._acquire("write", timeout, blocking=blocking) + + def release(self, *, force: bool = False) -> None: + """ + Release one level of the current lock. + + When the lock level reaches zero the underlying SQLite transaction is rolled back, releasing the database lock. + + :param force: if ``True``, release the lock completely regardless of the current lock level + + :raises RuntimeError: if no lock is currently held and *force* is ``False`` + + """ + should_rollback = False + with self._internal_lock: + if self._lock_level == 0: + if force: + return + msg = f"Cannot release a lock on {self.lock_file} (lock id: {id(self)}) that is not held" + raise RuntimeError(msg) + if force: + self._lock_level = 0 + else: + self._lock_level -= 1 + if self._lock_level == 0: + self._current_mode = None + self._write_thread_id = None + should_rollback = True + if should_rollback: + self._con.rollback() + + @contextmanager + def read_lock(self, timeout: float | None = None, *, blocking: bool | None = None) -> Generator[None]: + """ + Context manager that acquires and releases a shared read lock. + + Falls back to instance defaults for *timeout* and *blocking* when ``None``. + + :param timeout: maximum wait time in seconds, or ``None`` to use the instance default + :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately; ``None`` uses the instance default + + """ + if timeout is None: + timeout = self.timeout + if blocking is None: + blocking = self.blocking + self.acquire_read(timeout, blocking=blocking) + try: + yield + finally: + self.release() + + @contextmanager + def write_lock(self, timeout: float | None = None, *, blocking: bool | None = None) -> Generator[None]: + """ + Context manager that acquires and releases an exclusive write lock. + + Falls back to instance defaults for *timeout* and *blocking* when ``None``. + + :param timeout: maximum wait time in seconds, or ``None`` to use the instance default + :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately; ``None`` uses the instance default + + """ + if timeout is None: + timeout = self.timeout + if blocking is None: + blocking = self.blocking + self.acquire_write(timeout, blocking=blocking) + try: + yield + finally: + self.release() + + def close(self) -> None: + """ + Release the lock (if held) and close the underlying SQLite connection. + + After calling this method, the lock instance is no longer usable. + + """ + self.release(force=True) + self._con.close() + with _all_connections_lock: + _all_connections.discard(self._con) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/_soft.py b/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/_soft.py new file mode 100644 index 0000000000000000000000000000000000000000..1ce3cb3b363929d76e670db816e1143bef6f82eb --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/_soft.py @@ -0,0 +1,214 @@ +from __future__ import annotations + +import os +import socket +import sys +import time +from contextlib import suppress +from errno import EACCES, EEXIST, EPERM, ESRCH +from pathlib import Path + +from ._api import BaseFileLock +from ._util import ensure_directory_exists, raise_on_not_writable_file + +_WIN_SYNCHRONIZE = 0x100000 +_WIN_ERROR_INVALID_PARAMETER = 87 +_WIN_PROCESS_QUERY_LIMITED_INFORMATION = 0x1000 +_MALFORMED_LOCK_AGE_THRESHOLD = 2.0 + + +class SoftFileLock(BaseFileLock): + """ + Portable file lock based on file existence. + + Unlike :class:`UnixFileLock ` and :class:`WindowsFileLock `, this + lock does not use OS-level locking primitives. Instead, it creates the lock file with ``O_CREAT | O_EXCL`` and + treats its existence as the lock indicator. This makes it work on any filesystem but leaves stale lock files behind + if the process crashes without releasing the lock. + + To mitigate stale locks, the lock file contains the PID and hostname of the holding process. On contention, if the + holder is on the same host and its PID no longer exists, the stale lock is broken automatically. + + """ + + def _acquire(self) -> None: + raise_on_not_writable_file(self.lock_file) + ensure_directory_exists(self.lock_file) + flags = ( + os.O_WRONLY # open for writing only + | os.O_CREAT + | os.O_EXCL # together with above raise EEXIST if the file specified by filename exists + | os.O_TRUNC # truncate the file to zero byte + ) + if (o_nofollow := getattr(os, "O_NOFOLLOW", None)) is not None: + flags |= o_nofollow + try: + file_handler = os.open(self.lock_file, flags, self._open_mode()) + except OSError as exception: + if not ( + exception.errno == EEXIST or (exception.errno == EACCES and sys.platform == "win32") + ): # pragma: win32 no cover + raise + self._try_break_stale_lock() + else: + self._write_lock_info(file_handler) + self._context.lock_file_fd = file_handler + + def _try_break_stale_lock(self) -> None: + with suppress(OSError, ValueError): + lock_path = Path(self.lock_file) + stat_result = lock_path.stat() + content = lock_path.read_text(encoding="utf-8") + lines = content.strip().splitlines() + + if len(lines) not in {2, 3}: + if time.time() - stat_result.st_mtime >= _MALFORMED_LOCK_AGE_THRESHOLD: + self._evict_lock_file() + return + + pid_str, hostname = lines[0], lines[1] + creation_time_str = lines[2] if len(lines) == 3 else None # noqa: PLR2004 + + if hostname != socket.gethostname(): + return + + pid = int(pid_str) + + if self._is_process_alive(pid): + if sys.platform == "win32" and creation_time_str is not None: # pragma: win32 cover + stored = int(creation_time_str) + actual = self._get_process_creation_time(pid) + if actual is not None and actual != stored: + pass # PID recycled, fall through to evict + else: + return # same process or can't verify — don't evict + else: + return + + self._evict_lock_file() + + def _evict_lock_file(self) -> None: + break_path = f"{self.lock_file}.break.{os.getpid()}" + Path(self.lock_file).rename(break_path) + Path(break_path).unlink() + + @staticmethod + def _is_process_alive(pid: int) -> bool: + if sys.platform == "win32": # pragma: win32 cover + import ctypes # noqa: PLC0415 + + kernel32 = ctypes.windll.kernel32 + handle = kernel32.OpenProcess(_WIN_SYNCHRONIZE, 0, pid) + if handle: + kernel32.CloseHandle(handle) + return True + return kernel32.GetLastError() != _WIN_ERROR_INVALID_PARAMETER + try: + os.kill(pid, 0) + except OSError as exc: + if exc.errno == ESRCH: + return False + if exc.errno == EPERM: + return True + raise + return True + + @staticmethod + def _get_process_creation_time(pid: int) -> int | None: + """Return the process creation FILETIME as an integer on Windows, ``None`` otherwise.""" + if sys.platform != "win32": # pragma: win32 no cover + return None + import ctypes # pragma: win32 cover # noqa: PLC0415 + from ctypes import wintypes # noqa: PLC0415 + + kernel32 = ctypes.windll.kernel32 + handle = kernel32.OpenProcess(_WIN_PROCESS_QUERY_LIMITED_INFORMATION, 0, pid) + if not handle: + return None + try: + creation = wintypes.FILETIME() + exit_time = wintypes.FILETIME() + kernel_time = wintypes.FILETIME() + user_time = wintypes.FILETIME() + if not kernel32.GetProcessTimes( + handle, + ctypes.byref(creation), + ctypes.byref(exit_time), + ctypes.byref(kernel_time), + ctypes.byref(user_time), + ): + return None + finally: + kernel32.CloseHandle(handle) + return (creation.dwHighDateTime << 32) | creation.dwLowDateTime + + @staticmethod + def _write_lock_info(fd: int) -> None: + with suppress(OSError): + info = f"{os.getpid()}\n{socket.gethostname()}\n" + if sys.platform == "win32" and (ct := SoftFileLock._get_process_creation_time(os.getpid())) is not None: + info += f"{ct}\n" + os.write(fd, info.encode()) + + @property + def pid(self) -> int | None: + """ + The PID of the process holding this lock, read from the lock file. + + :returns: the PID as an integer, or ``None`` if the lock file does not exist or cannot be parsed + + """ + try: + content = Path(self.lock_file).read_text(encoding="utf-8") + lines = content.strip().splitlines() + if lines: + return int(lines[0]) + except (OSError, ValueError): + pass + return None + + @property + def is_lock_held_by_us(self) -> bool: + """ + Whether this lock is held by the current process. + + :returns: ``True`` if the lock file exists and contains the current process's PID + + """ + return self.pid == os.getpid() + + def break_lock(self) -> None: + """Forcibly break the lock by removing the lock file, regardless of who holds it.""" + with suppress(OSError): + Path(self.lock_file).unlink() + + def _release(self) -> None: + assert self._context.lock_file_fd is not None # noqa: S101 + os.close(self._context.lock_file_fd) + self._context.lock_file_fd = None + if sys.platform == "win32": + self._windows_unlink_with_retry() + else: + with suppress(OSError): + Path(self.lock_file).unlink() + + def _windows_unlink_with_retry(self) -> None: + max_retries = 10 + retry_delay = 0.001 + for attempt in range(max_retries): + # Windows doesn't immediately release file handles after close, causing EACCES/EPERM on unlink + try: + Path(self.lock_file).unlink() + except OSError as exc: # noqa: PERF203 + if exc.errno not in {EACCES, EPERM}: + return + if attempt < max_retries - 1: + time.sleep(retry_delay) + retry_delay *= 2 + else: + return + + +__all__ = [ + "SoftFileLock", +] diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/_soft_rw/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/_soft_rw/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..0b737bcee9d257307ed31865e43023b967b116ab --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/_soft_rw/__init__.py @@ -0,0 +1,12 @@ +"""Cross-process and cross-host reader/writer lock on :class:`~filelock.SoftFileLock` primitives.""" + +from __future__ import annotations + +from ._async import AsyncAcquireSoftReadWriteReturnProxy, AsyncSoftReadWriteLock +from ._sync import SoftReadWriteLock + +__all__ = [ + "AsyncAcquireSoftReadWriteReturnProxy", + "AsyncSoftReadWriteLock", + "SoftReadWriteLock", +] diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/_soft_rw/_async.py b/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/_soft_rw/_async.py new file mode 100644 index 0000000000000000000000000000000000000000..8b5ae2ef9ba16988f9b6c49312d008ab995d7e0e --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/_soft_rw/_async.py @@ -0,0 +1,213 @@ +"""Async wrapper around :class:`SoftReadWriteLock` for use with ``asyncio``.""" + +from __future__ import annotations + +import asyncio +import functools +from contextlib import asynccontextmanager +from typing import TYPE_CHECKING + +from ._sync import SoftReadWriteLock + +if TYPE_CHECKING: + import os + from collections.abc import AsyncGenerator, Callable + from concurrent import futures + from types import TracebackType + + +class AsyncAcquireSoftReadWriteReturnProxy: + """Async context-aware object that releases an :class:`AsyncSoftReadWriteLock` on exit.""" + + def __init__(self, lock: AsyncSoftReadWriteLock) -> None: + self.lock = lock + + async def __aenter__(self) -> AsyncSoftReadWriteLock: + return self.lock + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, + ) -> None: + await self.lock.release() + + +class AsyncSoftReadWriteLock: + """ + Async wrapper around :class:`SoftReadWriteLock` for ``asyncio`` applications. + + The sync class's blocking filesystem operations run on a thread pool via ``loop.run_in_executor()``. + Reentrancy, upgrade/downgrade rules, fork handling, heartbeat and TTL stale detection, and singleton + behavior are delegated to the underlying :class:`SoftReadWriteLock`. + + :param lock_file: path to the lock file; sidecar state/write/readers live next to it + :param timeout: maximum wait time in seconds; ``-1`` means block indefinitely + :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately on contention + :param is_singleton: if ``True``, reuse existing :class:`SoftReadWriteLock` instances per resolved path + :param heartbeat_interval: seconds between heartbeat refreshes; default 30 s + :param stale_threshold: seconds of mtime inactivity before a marker is stale; defaults to ``3 * heartbeat_interval`` + :param poll_interval: seconds between acquire retries under contention; default 0.25 s + :param loop: event loop for ``run_in_executor``; ``None`` uses the running loop + :param executor: executor for ``run_in_executor``; ``None`` uses the default executor + + .. versionadded:: 3.27.0 + + """ + + def __init__( # noqa: PLR0913 + self, + lock_file: str | os.PathLike[str], + timeout: float = -1, + *, + blocking: bool = True, + is_singleton: bool = True, + heartbeat_interval: float = 30.0, + stale_threshold: float | None = None, + poll_interval: float = 0.25, + loop: asyncio.AbstractEventLoop | None = None, + executor: futures.Executor | None = None, + ) -> None: + self._lock = SoftReadWriteLock( + lock_file, + timeout, + blocking=blocking, + is_singleton=is_singleton, + heartbeat_interval=heartbeat_interval, + stale_threshold=stale_threshold, + poll_interval=poll_interval, + ) + self._loop = loop + self._executor = executor + + @property + def lock_file(self) -> str: + """:returns: the path to the lock file passed to the constructor.""" + return self._lock.lock_file + + @property + def timeout(self) -> float: + """:returns: the default timeout applied when ``acquire_read`` / ``acquire_write`` is called without one.""" + return self._lock.timeout + + @property + def blocking(self) -> bool: + """:returns: whether ``acquire_*`` defaults to blocking; ``False`` makes contention raise immediately.""" + return self._lock.blocking + + @property + def loop(self) -> asyncio.AbstractEventLoop | None: + """:returns: the event loop used for ``run_in_executor``, or ``None`` for the running loop.""" + return self._loop + + @property + def executor(self) -> futures.Executor | None: + """:returns: the executor used for ``run_in_executor``, or ``None`` for the default executor.""" + return self._executor + + async def acquire_read( + self, timeout: float | None = None, *, blocking: bool | None = None + ) -> AsyncAcquireSoftReadWriteReturnProxy: + """ + Acquire a shared read lock. + + See :meth:`SoftReadWriteLock.acquire_read` for the full reentrancy / upgrade / fork semantics. The blocking + work runs inside ``run_in_executor`` so other coroutines on the same loop continue to progress while this + call waits. + + :param timeout: maximum wait time in seconds, or ``None`` to use the instance default + :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately; ``None`` uses the instance default + + :returns: a proxy usable as an async context manager to release the lock + + :raises RuntimeError: if a write lock is already held, if this instance was invalidated by + :func:`os.fork`, or if :meth:`close` was called + :raises Timeout: if the lock cannot be acquired within *timeout* seconds + + """ + await self._run(self._lock.acquire_read, timeout, blocking=blocking) + return AsyncAcquireSoftReadWriteReturnProxy(lock=self) + + async def acquire_write( + self, timeout: float | None = None, *, blocking: bool | None = None + ) -> AsyncAcquireSoftReadWriteReturnProxy: + """ + Acquire an exclusive write lock. + + See :meth:`SoftReadWriteLock.acquire_write` for the two-phase writer-preferring semantics. The blocking + work runs inside ``run_in_executor``. + + :param timeout: maximum wait time in seconds, or ``None`` to use the instance default + :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately; ``None`` uses the instance default + + :returns: a proxy usable as an async context manager to release the lock + + :raises RuntimeError: if a read lock is already held, if a write lock is held by a different thread, if + this instance was invalidated by :func:`os.fork`, or if :meth:`close` was called + :raises Timeout: if the lock cannot be acquired within *timeout* seconds + + """ + await self._run(self._lock.acquire_write, timeout, blocking=blocking) + return AsyncAcquireSoftReadWriteReturnProxy(lock=self) + + async def release(self, *, force: bool = False) -> None: + """ + Release one level of the current lock. + + :param force: if ``True``, release the lock completely regardless of the current lock level + + :raises RuntimeError: if no lock is currently held and *force* is ``False`` + + """ + await self._run(self._lock.release, force=force) + + @asynccontextmanager + async def read_lock(self, timeout: float | None = None, *, blocking: bool | None = None) -> AsyncGenerator[None]: + """ + Async context manager that acquires and releases a shared read lock. + + :param timeout: maximum wait time in seconds, or ``None`` to use the instance default + :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately; ``None`` uses the instance default + + :raises RuntimeError: if a write lock is already held on this instance + :raises Timeout: if the lock cannot be acquired within *timeout* seconds + + """ + await self.acquire_read(timeout, blocking=blocking) + try: + yield + finally: + await self.release() + + @asynccontextmanager + async def write_lock(self, timeout: float | None = None, *, blocking: bool | None = None) -> AsyncGenerator[None]: + """ + Async context manager that acquires and releases an exclusive write lock. + + :param timeout: maximum wait time in seconds, or ``None`` to use the instance default + :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately; ``None`` uses the instance default + + :raises RuntimeError: if a read lock is already held, or a write lock is held by a different thread + :raises Timeout: if the lock cannot be acquired within *timeout* seconds + + """ + await self.acquire_write(timeout, blocking=blocking) + try: + yield + finally: + await self.release() + + async def close(self) -> None: + """Release any held lock and release the underlying filesystem resources. Idempotent.""" + await self._run(self._lock.close) + + async def _run(self, func: Callable[..., object], *args: object, **kwargs: object) -> object: + loop = self._loop or asyncio.get_running_loop() + return await loop.run_in_executor(self._executor, functools.partial(func, *args, **kwargs)) + + +__all__ = [ + "AsyncAcquireSoftReadWriteReturnProxy", + "AsyncSoftReadWriteLock", +] diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/_soft_rw/_sync.py b/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/_soft_rw/_sync.py new file mode 100644 index 0000000000000000000000000000000000000000..06d0c9d25524857036a129f406ae2f91785c1bd7 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/_soft_rw/_sync.py @@ -0,0 +1,847 @@ +"""Cross-process and cross-host reader/writer lock built on :class:`SoftFileLock` primitives.""" + +from __future__ import annotations + +import atexit +import hmac +import os +import re +import secrets +import socket +import stat +import sys +import threading +import time +import uuid +from contextlib import contextmanager, suppress +from dataclasses import dataclass +from pathlib import Path +from typing import TYPE_CHECKING, Literal +from weakref import WeakValueDictionary + +from filelock._api import AcquireReturnProxy +from filelock._error import Timeout +from filelock._soft import SoftFileLock +from filelock._util import ensure_directory_exists + +if TYPE_CHECKING: + from collections.abc import Callable, Generator + + +_Mode = Literal["read", "write"] +_BREAK_SUFFIX = ".break" +_MAX_MARKER_SIZE = 1024 +_O_NOFOLLOW = getattr(os, "O_NOFOLLOW", 0) +# dirfd-relative I/O is a Unix-only optimization; Windows cannot ``os.open()`` a directory at all, and +# its ``os`` module skips dir_fd support entirely. When disabled, callers fall back to full-path ops. +_SUPPORTS_DIR_FD = sys.platform != "win32" and os.open in os.supports_dir_fd + +_all_instances: WeakValueDictionary[Path, SoftReadWriteLock] = WeakValueDictionary() +_all_instances_lock = threading.Lock() +_atexit_registered = False +_fork_registered = False + + +@dataclass(frozen=True) +class _Paths: + state: str + write: str + readers: str + + +@dataclass +class _Locks: + internal: threading.Lock + transaction: threading.Lock + state: SoftFileLock + + +@dataclass(frozen=True) +class _MarkerInfo: + token: str + pid: int + hostname: str + + +@dataclass +class _Hold: + """Everything that exists only while a lock is held; ``None`` when the instance has no lock.""" + + level: int + mode: _Mode + write_thread_id: int | None + marker_name: str + is_reader: bool + token: str + heartbeat_thread: _HeartbeatThread + heartbeat_stop: threading.Event + + +class _SoftRWMeta(type): + _instances: WeakValueDictionary[Path, SoftReadWriteLock] + _instances_lock: threading.Lock + + def __call__( # noqa: PLR0913 + cls, + lock_file: str | os.PathLike[str], + timeout: float = -1, + *, + blocking: bool = True, + is_singleton: bool = True, + heartbeat_interval: float = 30.0, + stale_threshold: float | None = None, + poll_interval: float = 0.25, + ) -> SoftReadWriteLock: + if not is_singleton: + return super().__call__( + lock_file, + timeout, + blocking=blocking, + is_singleton=is_singleton, + heartbeat_interval=heartbeat_interval, + stale_threshold=stale_threshold, + poll_interval=poll_interval, + ) + + normalized = Path(lock_file).resolve() + with cls._instances_lock: + instance = cls._instances.get(normalized) + if instance is None: + instance = super().__call__( + lock_file, + timeout, + blocking=blocking, + is_singleton=is_singleton, + heartbeat_interval=heartbeat_interval, + stale_threshold=stale_threshold, + poll_interval=poll_interval, + ) + cls._instances[normalized] = instance + elif instance.timeout != timeout or instance.blocking != blocking: + msg = ( + f"Singleton lock created with timeout={instance.timeout}, blocking={instance.blocking}," + f" cannot be changed to timeout={timeout}, blocking={blocking}" + ) + raise ValueError(msg) + return instance + + +class SoftReadWriteLock(metaclass=_SoftRWMeta): + """ + Cross-process and cross-host reader/writer lock built on :class:`SoftFileLock` primitives. + + Use this class instead of :class:`~filelock.ReadWriteLock` when the lock file lives on a network + filesystem (NFS, Lustre with ``-o flock``, HPC cluster shared storage). ``ReadWriteLock`` is backed + by SQLite and cannot run on NFS because SQLite's ``fcntl`` locking is unreliable there. + + Layout on disk for a lock at ``foo.lock``: + + - ``foo.lock.state`` — a :class:`SoftFileLock` taken only during state transitions (microseconds). + - ``foo.lock.write`` — writer marker; its presence means a writer is claiming or holding the lock. + - ``foo.lock.readers/..`` — one file per reader. + + Each marker stores a random token (``secrets.token_hex(16)``), the holder's pid, and the holder's + hostname. A daemon heartbeat thread refreshes ``mtime`` on every held marker. A marker whose mtime + has not advanced in ``stale_threshold`` seconds may be evicted by any process on any host, giving + correct behavior when a compute node crashes with a lock held. + + Writer acquire is two-phase and writer-preferring: phase 1 claims ``.write`` (blocking any new + reader), phase 2 waits for existing readers to drain. Writer starvation is impossible. + + Reentrancy, upgrade/downgrade rules, thread pinning, and singleton caching by resolved path match + :class:`~filelock.ReadWriteLock`. + + Forking while holding a lock invalidates the inherited instance in the child so the child cannot + double-own the lock with its parent; ``release()`` on a fork-invalidated instance is a no-op, and + the child must re-acquire if it needs a lock. + + Trust boundary: protects against same-UID non-cooperating processes (one host or cross-host) and + same-host different-UID users via ``0o600`` / ``0o700`` permissions. Does not protect against root + compromise, NTP tampering on same-UID cross-host nodes, or multi-tenant mounts where hostile + co-tenants share the UID. + + :param lock_file: path to the lock file; sidecar state/write/readers live next to it + :param timeout: maximum wait time in seconds; ``-1`` means block indefinitely + :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately on contention + :param is_singleton: if ``True``, reuse existing instances for the same resolved path + :param heartbeat_interval: seconds between heartbeat refreshes; default 30 s + :param stale_threshold: seconds of ``mtime`` inactivity before a marker is stale; defaults to + ``3 * heartbeat_interval``, matching etcd's ``LeaseKeepAlive`` convention + :param poll_interval: seconds between acquire retries under contention; default 0.25 s + + .. versionadded:: 3.27.0 + + """ + + _instances: WeakValueDictionary[Path, SoftReadWriteLock] = WeakValueDictionary() + _instances_lock = threading.Lock() + + def __init__( # noqa: PLR0913 + self, + lock_file: str | os.PathLike[str], + timeout: float = -1, + *, + blocking: bool = True, + is_singleton: bool = True, # noqa: ARG002 + heartbeat_interval: float = 30.0, + stale_threshold: float | None = None, + poll_interval: float = 0.25, + ) -> None: + if heartbeat_interval <= 0: + msg = f"heartbeat_interval must be positive, got {heartbeat_interval}" + raise ValueError(msg) + if stale_threshold is None: + stale_threshold = heartbeat_interval * 3 + if stale_threshold <= heartbeat_interval: + msg = f"stale_threshold must exceed heartbeat_interval ({stale_threshold} <= {heartbeat_interval})" + raise ValueError(msg) + if poll_interval <= 0: + msg = f"poll_interval must be positive, got {poll_interval}" + raise ValueError(msg) + + self.lock_file: str = os.fspath(lock_file) + self.timeout: float = timeout + self.blocking: bool = blocking + self.heartbeat_interval: float = heartbeat_interval + self.stale_threshold: float = stale_threshold + self.poll_interval: float = poll_interval + + self._paths = _Paths( + state=f"{self.lock_file}.state", + write=f"{self.lock_file}.write", + readers=f"{self.lock_file}.readers", + ) + ensure_directory_exists(self.lock_file) + self._locks = _Locks( + internal=threading.Lock(), + transaction=threading.Lock(), + state=SoftFileLock(self._paths.state, timeout=-1), + ) + self._readers_dir_fd: int | None = None + self._hold: _Hold | None = None + self._fork_invalidated: bool = False + self._closed: bool = False + + with _all_instances_lock: + _all_instances[Path(self.lock_file).resolve()] = self + _register_hooks() + + @contextmanager + def read_lock(self, timeout: float | None = None, *, blocking: bool | None = None) -> Generator[None]: + """ + Context manager that acquires and releases a shared read lock. + + Falls back to instance defaults for *timeout* and *blocking* when ``None``. + + :param timeout: maximum wait time in seconds, or ``None`` to use the instance default + :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately; ``None`` uses the instance default + + :raises RuntimeError: if a write lock is already held on this instance + :raises Timeout: if the lock cannot be acquired within *timeout* seconds + + """ + self.acquire_read(timeout, blocking=blocking) + try: + yield + finally: + self.release() + + @contextmanager + def write_lock(self, timeout: float | None = None, *, blocking: bool | None = None) -> Generator[None]: + """ + Context manager that acquires and releases an exclusive write lock. + + Falls back to instance defaults for *timeout* and *blocking* when ``None``. + + :param timeout: maximum wait time in seconds, or ``None`` to use the instance default + :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately; ``None`` uses the instance default + + :raises RuntimeError: if a read lock is already held, or a write lock is held by a different thread + :raises Timeout: if the lock cannot be acquired within *timeout* seconds + + """ + self.acquire_write(timeout, blocking=blocking) + try: + yield + finally: + self.release() + + def acquire_read(self, timeout: float | None = None, *, blocking: bool | None = None) -> AcquireReturnProxy: + """ + Acquire a shared read lock. + + If this instance already holds a read lock, the lock level is incremented (reentrant). Attempting to acquire a + read lock while holding a write lock raises :class:`RuntimeError` (downgrade not allowed). On the 0→1 + transition a daemon heartbeat thread is started that refreshes the reader marker's ``mtime`` every + ``heartbeat_interval`` seconds so peers on other hosts do not evict the marker as stale. + + :param timeout: maximum wait time in seconds, or ``None`` to use the instance default; ``-1`` means block + indefinitely + :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately when the lock is unavailable; + ``None`` uses the instance default + + :returns: a proxy that can be used as a context manager to release the lock + + :raises RuntimeError: if a write lock is already held on this instance, if this instance was invalidated by + :func:`os.fork`, or if :meth:`close` was called + :raises Timeout: if the lock cannot be acquired within *timeout* seconds + + """ + return self._acquire("read", timeout, blocking=blocking) + + def acquire_write(self, timeout: float | None = None, *, blocking: bool | None = None) -> AcquireReturnProxy: + """ + Acquire an exclusive write lock. + + If this instance already holds a write lock from the same thread, the lock level is incremented (reentrant). + Attempting to acquire a write lock while holding a read lock raises :class:`RuntimeError` (upgrade not + allowed). Write locks are pinned to the acquiring thread: a different thread trying to re-enter also raises + :class:`RuntimeError`. + + Writer acquisition runs in two phases. Phase 1 atomically claims ``.write`` via ``O_CREAT | O_EXCL``, + which immediately blocks any new reader on any host. Phase 2 waits for existing readers to drain. Writer + starvation is impossible: new readers see ``.write`` during phase 2 and wait behind the pending writer. + + :param timeout: maximum wait time in seconds, or ``None`` to use the instance default; ``-1`` means block + indefinitely + :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately when the lock is unavailable; + ``None`` uses the instance default + + :returns: a proxy that can be used as a context manager to release the lock + + :raises RuntimeError: if a read lock is already held, if a write lock is held by a different thread, if this + instance was invalidated by :func:`os.fork`, or if :meth:`close` was called + :raises Timeout: if the lock cannot be acquired within *timeout* seconds + + """ + return self._acquire("write", timeout, blocking=blocking) + + def close(self) -> None: + """ + Release any held lock and release internal filesystem resources. + + Idempotent. After calling this method the instance can no longer acquire locks — subsequent acquires raise + :class:`RuntimeError`. A fork-invalidated instance is closed without raising. + """ + self.release(force=True) + with self._locks.internal: + if self._closed: + return + self._closed = True + if self._readers_dir_fd is not None: + with suppress(OSError): + os.close(self._readers_dir_fd) + self._readers_dir_fd = None + + def release(self, *, force: bool = False) -> None: + """ + Release one level of the current lock. + + When the lock level reaches zero the heartbeat thread is stopped and the held marker file is unlinked. On a + fork-invalidated instance (that is, the child of a :func:`os.fork` call made while the parent held a lock) + this method is a no-op so inherited ``with`` blocks can unwind cleanly in the child. + + :param force: if ``True``, release the lock completely regardless of the current lock level + + :raises RuntimeError: if no lock is currently held and *force* is ``False`` + + """ + with self._locks.internal: + if self._fork_invalidated: + # Inherited state from the parent is meaningless in the child; clear any counters and return. + self._hold = None + return + hold = self._hold + if hold is None: + if force: + return + msg = f"Cannot release a lock on {self.lock_file} (lock id: {id(self)}) that is not held" + raise RuntimeError(msg) + if force: + hold.level = 0 + else: + hold.level -= 1 + if hold.level > 0: + return + self._hold = None + + # Order matters: signal → join → unlink. A late tick on a deleted marker is harmless, and the + # token check in the heartbeat callback would catch any re-acquisition race, but joining first + # removes even that theoretical race. + hold.heartbeat_stop.set() + hold.heartbeat_thread.join(timeout=self.heartbeat_interval + 1.0) + if hold.is_reader: + _unlink(hold.marker_name, dir_fd=self._readers_dir_fd) + else: + _unlink(hold.marker_name) + + @classmethod + def get_lock( + cls, + lock_file: str | os.PathLike[str], + timeout: float = -1, + *, + blocking: bool = True, + ) -> SoftReadWriteLock: + """ + Return the singleton :class:`SoftReadWriteLock` for *lock_file*. + + :param lock_file: path to the lock file; sidecar state/write/readers live next to it + :param timeout: maximum wait time in seconds; ``-1`` means block indefinitely + :param blocking: if ``False``, raise :class:`~filelock.Timeout` immediately when the lock is unavailable + + :returns: the singleton lock instance + + :raises ValueError: if an instance already exists for this path with different *timeout* or *blocking* values + + """ + return cls(lock_file, timeout, blocking=blocking) + + def _acquire( + self, + mode: _Mode, + timeout: float | None, + *, + blocking: bool | None, + ) -> AcquireReturnProxy: + effective_timeout = self.timeout if timeout is None else timeout + effective_blocking = self.blocking if blocking is None else blocking + + with self._locks.internal: + if self._fork_invalidated: + msg = f"SoftReadWriteLock on {self.lock_file} was invalidated by fork(); construct a new instance" + raise RuntimeError(msg) + if self._closed: + msg = f"SoftReadWriteLock on {self.lock_file} has been closed" + raise RuntimeError(msg) + if self._hold is not None: + return self._validate_reentrant(mode) + + start = time.perf_counter() + if not effective_blocking: + acquired = self._locks.transaction.acquire(blocking=False) + elif effective_timeout == -1: + acquired = self._locks.transaction.acquire(blocking=True) + else: + acquired = self._locks.transaction.acquire(blocking=True, timeout=effective_timeout) + if not acquired: + raise Timeout(self.lock_file) from None + try: + with self._locks.internal: + if self._hold is not None: + return self._validate_reentrant(mode) + + deadline = None if effective_timeout == -1 else start + effective_timeout + token = secrets.token_hex(16) + if mode == "write": + marker_name, is_reader = self._acquire_writer_slot( + token, deadline=deadline, blocking=effective_blocking + ) + else: + marker_name, is_reader = self._acquire_reader_slot( + token, deadline=deadline, blocking=effective_blocking + ) + + stop_event = threading.Event() + heartbeat = _HeartbeatThread( + refresh=self._refresh_marker, + interval=self.heartbeat_interval, + stop_event=stop_event, + name=f"filelock-heartbeat-{id(self):x}", + ) + + with self._locks.internal: + self._hold = _Hold( + level=1, + mode=mode, + write_thread_id=threading.get_ident() if mode == "write" else None, + marker_name=marker_name, + is_reader=is_reader, + token=token, + heartbeat_thread=heartbeat, + heartbeat_stop=stop_event, + ) + + heartbeat.start() + return AcquireReturnProxy(lock=self) + finally: + self._locks.transaction.release() + + def _validate_reentrant(self, mode: _Mode) -> AcquireReturnProxy: + hold = self._hold + assert hold is not None # noqa: S101 + if hold.mode != mode: + opposite = "write" if mode == "read" else "read" + direction = "downgrade" if mode == "read" else "upgrade" + msg = ( + f"Cannot acquire {mode} lock on {self.lock_file} (lock id: {id(self)}): " + f"already holding a {opposite} lock ({direction} not allowed)" + ) + raise RuntimeError(msg) + if mode == "write" and (cur := threading.get_ident()) != hold.write_thread_id: + msg = ( + f"Cannot acquire write lock on {self.lock_file} (lock id: {id(self)}) " + f"from thread {cur} while it is held by thread {hold.write_thread_id}" + ) + raise RuntimeError(msg) + hold.level += 1 + return AcquireReturnProxy(lock=self) + + def _acquire_writer_slot( + self, + token: str, + *, + deadline: float | None, + blocking: bool, + ) -> tuple[str, bool]: + # Phase 2 scans readers/ via dirfd (where supported), so we need it open even though writers never + # create files inside. + self._open_readers_dir() + + def try_claim_writer() -> bool: + with self._locks.state: + _break_stale_marker(self._paths.write, stale_threshold=self.stale_threshold, now=time.time()) + if _file_exists(self._paths.write): + return False + try: + _atomic_create_marker(self._paths.write, token) + except FileExistsError: + return False + return True + + def readers_drained_touching() -> bool: + with self._locks.state: + # Refresh our writer marker on every scan iteration. Otherwise phase 2 can exceed + # ``stale_threshold`` under contention and a peer would treat us as stale and evict us. + with suppress(OSError): + _touch(self._paths.write) + self._break_stale_readers(time.time()) + return not self._any_readers() + + self._wait_for(try_claim_writer, deadline=deadline, blocking=blocking) + try: + self._wait_for(readers_drained_touching, deadline=deadline, blocking=blocking) + except Timeout: + # Give up our writer claim so readers can make progress again. + _unlink(self._paths.write) + raise + return self._paths.write, False + + def _acquire_reader_slot( + self, + token: str, + *, + deadline: float | None, + blocking: bool, + ) -> tuple[str, bool]: + self._open_readers_dir() + reader_name = f"{uuid.uuid4().hex}.{os.getpid()}" + dir_fd = self._readers_dir_fd + full_reader_path = str(Path(self._paths.readers) / reader_name) + + def try_claim_reader() -> bool: + with self._locks.state: + _break_stale_marker(self._paths.write, stale_threshold=self.stale_threshold, now=time.time()) + if _file_exists(self._paths.write): + return False + if dir_fd is not None: + _atomic_create_marker(reader_name, token, dir_fd=dir_fd) + else: # pragma: win32 cover + _atomic_create_marker(full_reader_path, token) + return True + + self._wait_for(try_claim_reader, deadline=deadline, blocking=blocking) + return (reader_name if dir_fd is not None else full_reader_path), True + + def _wait_for( + self, + predicate: Callable[[], bool], + *, + deadline: float | None, + blocking: bool, + ) -> None: + while True: + if predicate(): + return + now = time.perf_counter() + if not blocking: + raise Timeout(self.lock_file) + if deadline is not None and now >= deadline: + raise Timeout(self.lock_file) + sleep_for = self.poll_interval + if deadline is not None: + sleep_for = min(sleep_for, max(deadline - now, 0.0)) + time.sleep(sleep_for) + + def _open_readers_dir(self) -> None: + readers_path = Path(self._paths.readers) + with suppress(FileExistsError): + readers_path.mkdir(mode=0o700) + # mkdir has no O_NOFOLLOW, so verify via lstat that we did not land on an attacker-placed symlink + # or a regular file before we open or scan inside. + st = os.lstat(self._paths.readers) + if stat.S_ISLNK(st.st_mode) or not stat.S_ISDIR(st.st_mode): + msg = f"{self._paths.readers} exists but is not a directory or is a symlink; refusing to use it" + raise RuntimeError(msg) + if self._readers_dir_fd is None and _SUPPORTS_DIR_FD: + flags = os.O_RDONLY | getattr(os, "O_DIRECTORY", 0) | _O_NOFOLLOW + self._readers_dir_fd = os.open(self._paths.readers, flags) + + def _any_readers(self) -> bool: + for _ in self._iter_reader_entries(): + return True + return False + + def _iter_reader_entries(self) -> Generator[tuple[str, bool]]: + """ + Yield ``(name, dirfd_relative)`` pairs for every live reader marker. + + ``dirfd_relative`` is ``True`` when *name* should be passed to ``dir_fd=``-aware syscalls; ``False`` + when *name* is a full path because dirfd-relative I/O is unavailable on this platform. + """ + if self._readers_dir_fd is not None: + with os.scandir(self._readers_dir_fd) as it: + for entry in it: + if not _is_housekeeping_name(entry.name): + yield entry.name, True + return + readers_path = Path(self._paths.readers) # pragma: win32 cover + with os.scandir(readers_path) as it: # pragma: win32 cover + for entry in it: # pragma: win32 cover + if not _is_housekeeping_name(entry.name): # pragma: win32 cover + yield str(readers_path / entry.name), False # pragma: win32 cover + + def _break_stale_readers(self, now: float) -> None: + names: list[tuple[str, int | None]] = [] + try: + for name, dirfd_relative in self._iter_reader_entries(): + names.append((name, self._readers_dir_fd if dirfd_relative else None)) + except OSError: # pragma: no cover - transient NFS scandir hiccup + return + for name, fd in names: + _break_stale_marker(name, stale_threshold=self.stale_threshold, now=now, dir_fd=fd) + + def _refresh_marker(self) -> bool: + with self._locks.internal: + hold = self._hold + if hold is None: # pragma: no cover - race between stop_event.set and join + return False + marker_name = hold.marker_name + token = hold.token + dir_fd = self._readers_dir_fd if hold.is_reader else None + + read_result = _read_marker(marker_name, dir_fd=dir_fd) + if read_result is None: + return False + info, _mtime = read_result + # Token mismatch means another process already evicted our marker and created its own; stop the + # thread so it does not touch a stranger's file. + if info is None or not hmac.compare_digest(info.token, token): + return False + try: + _touch(marker_name, dir_fd=dir_fd) + except FileNotFoundError: # pragma: no cover - race between successful read and touch + return False + return True + + def _reset_after_fork_in_child(self) -> None: # pragma: no cover - fork child not tracked + # Replace every lock this instance owns with a fresh one; the inherited locks may still be held + # by threads that no longer exist in the child. The readers dirfd and the SoftFileLock state + # mutex both get dropped for the same reason — the child re-creates them on its next acquire. + self._locks = _Locks( + internal=threading.Lock(), + transaction=threading.Lock(), + state=SoftFileLock(self._paths.state, timeout=-1), + ) + self._hold = None + self._readers_dir_fd = None + self._fork_invalidated = True + + +class _HeartbeatThread(threading.Thread): + def __init__( + self, + refresh: Callable[[], bool], + interval: float, + stop_event: threading.Event, + name: str, + ) -> None: + super().__init__(name=name, daemon=True) + self._refresh = refresh + self._interval = interval + self._stop_event = stop_event + + def run(self) -> None: + while not self._stop_event.wait(self._interval): + if not self._refresh(): + self._stop_event.set() + return + + +def _atomic_create_marker(name: str, token: str, *, dir_fd: int | None = None) -> None: + # O_NOFOLLOW blocks the symlink-overwrite attack where an attacker pre-creates the marker path as a + # symlink pointing at a victim file. Mode 0o600 keeps the token unreadable to other users. + flags = os.O_CREAT | os.O_EXCL | os.O_WRONLY | _O_NOFOLLOW + if _SUPPORTS_DIR_FD and dir_fd is not None: + fd = os.open(name, flags, 0o600, dir_fd=dir_fd) + else: + fd = os.open(name, flags, 0o600) + try: + content = f"{token}\n{os.getpid()}\n{socket.gethostname()}\n".encode("ascii") + os.write(fd, content) + finally: + os.close(fd) + + +def _read_marker(name: str, *, dir_fd: int | None = None) -> tuple[_MarkerInfo | None, float] | None: + # O_NOFOLLOW is defense in depth: we already created this file, but a hostile replacement by symlink + # between create and read would be caught here. + flags = os.O_RDONLY | _O_NOFOLLOW + try: + fd = os.open(name, flags, dir_fd=dir_fd) if _SUPPORTS_DIR_FD and dir_fd is not None else os.open(name, flags) + except OSError: + return None + try: + try: + st = os.fstat(fd) + data = os.read(fd, _MAX_MARKER_SIZE + 1) + except OSError: # pragma: no cover - fstat/read after a successful open is hard to provoke + return None + finally: + os.close(fd) + return _parse_marker_bytes(data), st.st_mtime + + +def _parse_marker_bytes(data: bytes) -> _MarkerInfo | None: + # Trust nothing about attacker-controlled markers; any deviation returns None so callers fall through + # to stale cleanup. ``re.match`` caches compiled patterns internally, so the regex is built only once + # despite being defined inline. + if not data or len(data) > _MAX_MARKER_SIZE: + return None + try: + text = data.decode("ascii") + except UnicodeDecodeError: + return None + match = re.match( + r""" + \A # start of string + (?P [0-9a-f]{32} ) \n # 128-bit hex token + (?P [1-9][0-9]{0,9} ) \n # decimal pid: no leading zero, ≤ 10 digits + (?P [\x21-\x7e]{1,253}) # printable non-whitespace ASCII (RFC 1123 hostname limit) + \n* # tolerate sloppy writers that append extra newlines + \Z # end of string + """, + text, + re.VERBOSE, + ) + if match is None: + return None + pid = int(match["pid"], 10) + if pid > 2**31 - 1: + return None + return _MarkerInfo(token=match["token"], pid=pid, hostname=match["hostname"]) + + +def _break_stale_marker( # noqa: PLR0911 + name: str, + *, + stale_threshold: float, + now: float, + dir_fd: int | None = None, +) -> bool: + # Atomic break pattern: read → rename to unique break-name → re-verify → unlink. The rename gives us a + # private name nobody else can touch; if the re-verify sees a newer mtime or a different token, the + # legitimate holder's heartbeat fired between read and rename and we must abort (leaving the .break.* + # file behind rather than rollback-renaming, because rollback is itself racy). + read_result = _read_marker(name, dir_fd=dir_fd) + if read_result is None: + return False + info_before, mtime_before = read_result + if now - mtime_before <= stale_threshold: + return False + if info_before is None: + _unlink(name, dir_fd=dir_fd) + return True + + break_name = f"{name}{_BREAK_SUFFIX}.{os.getpid()}.{secrets.token_hex(16)}" + try: + if _SUPPORTS_DIR_FD and dir_fd is not None: + os.rename(name, break_name, src_dir_fd=dir_fd, dst_dir_fd=dir_fd) + else: + Path(name).rename(break_name) + except OSError: # pragma: no cover - race where the marker vanishes between read and rename + return False + + read_after = _read_marker(break_name, dir_fd=dir_fd) + if read_after is None: # pragma: no cover - race where a peer unlinks the break-name file + return False + info_after, mtime_after = read_after + if info_after is None: # pragma: no cover - content replaced post-rename by a racing peer + _unlink(break_name, dir_fd=dir_fd) + return True + if not hmac.compare_digest(info_before.token, info_after.token): # pragma: no cover - race only + return False + if mtime_after > mtime_before: # pragma: no cover - heartbeat raced our rename + return False + _unlink(break_name, dir_fd=dir_fd) + return True + + +def _unlink(name: str, *, dir_fd: int | None = None) -> None: + with suppress(FileNotFoundError): + if _SUPPORTS_DIR_FD and dir_fd is not None: + # Path.unlink has no dir_fd support, so we stay on os.unlink for the dirfd path. + os.unlink(name, dir_fd=dir_fd) + else: + Path(name).unlink() + + +def _touch(name: str, *, dir_fd: int | None = None) -> None: + if _SUPPORTS_DIR_FD and dir_fd is not None: + os.utime(name, None, dir_fd=dir_fd) + else: + os.utime(name, None) + + +def _file_exists(path: str) -> bool: + try: + st = os.lstat(path) + except FileNotFoundError: + return False + return stat.S_ISREG(st.st_mode) + + +def _is_housekeeping_name(name: str) -> bool: + return name.startswith(".") or _BREAK_SUFFIX in name + + +def _reset_all_after_fork() -> None: # pragma: no cover - fork child, not tracked by coverage + global _all_instances_lock # noqa: PLW0603 + # User-created threading locks do not auto-reset across fork: any lock held by a parent thread stays + # locked in the child with no owner to release it. Replace the module-level lock and every instance's + # locks with fresh ones; the child is single-threaded at this point so no synchronization is needed. + _all_instances_lock = threading.Lock() + for instance in list(_all_instances.values()): + instance._reset_after_fork_in_child() # noqa: SLF001 + + +def _cleanup_all_instances() -> None: # pragma: no cover - runs from atexit at interpreter shutdown + for instance in list(_all_instances.values()): + with suppress(Exception): + instance.release(force=True) + + +def _register_hooks() -> None: + global _atexit_registered, _fork_registered # noqa: PLW0603 + if not _atexit_registered: + atexit.register(_cleanup_all_instances) + _atexit_registered = True + # after_in_child replaces inherited state so the child cannot double-own any lock the parent held. + if not _fork_registered and hasattr(os, "register_at_fork"): + os.register_at_fork(after_in_child=_reset_all_after_fork) + _fork_registered = True + + +__all__ = [ + "SoftReadWriteLock", +] diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/_unix.py b/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/_unix.py new file mode 100644 index 0000000000000000000000000000000000000000..ac5f26a9e05fa2e11834224b979ecb2d97b29101 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/_unix.py @@ -0,0 +1,116 @@ +from __future__ import annotations + +import os +import sys +import warnings +from contextlib import suppress +from errno import EAGAIN, ENOSYS, EWOULDBLOCK +from pathlib import Path +from typing import cast + +from ._api import BaseFileLock +from ._util import ensure_directory_exists + +#: a flag to indicate if the fcntl API is available +has_fcntl = False +if sys.platform == "win32": # pragma: win32 cover + + class UnixFileLock(BaseFileLock): + """Uses the :func:`fcntl.flock` to hard lock the lock file on unix systems.""" + + def _acquire(self) -> None: + raise NotImplementedError + + def _release(self) -> None: + raise NotImplementedError + +else: # pragma: win32 no cover + try: + import fcntl + + _ = (fcntl.flock, fcntl.LOCK_EX, fcntl.LOCK_NB, fcntl.LOCK_UN) + except (ImportError, AttributeError): + pass + else: + has_fcntl = True + + class UnixFileLock(BaseFileLock): + """ + Uses the :func:`fcntl.flock` to hard lock the lock file on unix systems. + + Lock file cleanup: Unix and macOS delete the lock file reliably after release, even in + multi-threaded scenarios. Unlike Windows, Unix allows unlinking files that other processes + have open. + """ + + def _acquire(self) -> None: # noqa: C901, PLR0912 + ensure_directory_exists(self.lock_file) + open_flags = os.O_RDWR | os.O_TRUNC + o_nofollow = getattr(os, "O_NOFOLLOW", None) + if o_nofollow is not None: + open_flags |= o_nofollow + open_flags |= os.O_CREAT + open_mode = self._open_mode() + try: + fd = os.open(self.lock_file, open_flags, open_mode) + except FileNotFoundError: + # On FUSE/NFS, os.open(O_CREAT) is not atomic: LOOKUP + CREATE can be split, allowing a concurrent + # unlink() to delete the file between them. For valid paths, treat ENOENT as transient contention. + # For invalid paths (e.g., empty string), re-raise to avoid infinite retry loops. + if self.lock_file and Path(self.lock_file).parent.exists(): + return + raise + except PermissionError: + # Sticky-bit dirs (e.g. /tmp): O_CREAT fails if the file is owned by another user (#317). + # Fall back to opening the existing file without O_CREAT. + if not Path(self.lock_file).exists(): + raise + try: + fd = os.open(self.lock_file, open_flags & ~os.O_CREAT, open_mode) + except FileNotFoundError: + return + if self.has_explicit_mode: + with suppress(PermissionError): + os.fchmod(fd, self._context.mode) + try: + fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) + except OSError as exception: + os.close(fd) + if exception.errno == ENOSYS: + with suppress(OSError): + Path(self.lock_file).unlink() + self._fallback_to_soft_lock() + self._acquire() + return + if exception.errno not in {EAGAIN, EWOULDBLOCK}: + raise + else: + # The file may have been unlinked by a concurrent _release() between our open() and flock(). + # A lock on an unlinked inode is useless — discard and let the retry loop start fresh. + if os.fstat(fd).st_nlink == 0: + os.close(fd) + else: + self._context.lock_file_fd = fd + + def _fallback_to_soft_lock(self) -> None: + from ._soft import SoftFileLock # noqa: PLC0415 + + warnings.warn("flock not supported on this filesystem, falling back to SoftFileLock", stacklevel=2) + from .asyncio import AsyncSoftFileLock, BaseAsyncFileLock # noqa: PLC0415 + + self.__class__ = AsyncSoftFileLock if isinstance(self, BaseAsyncFileLock) else SoftFileLock + + def _release(self) -> None: + fd = cast("int", self._context.lock_file_fd) + self._context.lock_file_fd = None + with suppress(OSError): + Path(self.lock_file).unlink() + fcntl.flock(fd, fcntl.LOCK_UN) + with suppress(OSError): # close can raise EIO on FUSE/Docker bind-mount filesystems after unlink + os.close(fd) + + +__all__ = [ + "UnixFileLock", + "has_fcntl", +] diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/_util.py b/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/_util.py new file mode 100644 index 0000000000000000000000000000000000000000..670152393ed76d2cecf4cf5e774360af4380f319 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/_util.py @@ -0,0 +1,53 @@ +from __future__ import annotations + +import os +import stat +import sys +from errno import EACCES, EISDIR +from pathlib import Path + + +def raise_on_not_writable_file(filename: str) -> None: + """ + Raise an exception if attempting to open the file for writing would fail. + + This is done so files that will never be writable can be separated from files that are writable but currently + locked. + + :param filename: file to check + + :raises OSError: as if the file was opened for writing. + + """ + try: # use stat to do exists + can write to check without race condition + file_stat = os.stat(filename) # noqa: PTH116 + except OSError: + return # swallow does not exist or other errors + + if file_stat.st_mtime != 0: # if os.stat returns but modification is zero that's an invalid os.stat - ignore it + if not (file_stat.st_mode & stat.S_IWUSR): + raise PermissionError(EACCES, "Permission denied", filename) + + if stat.S_ISDIR(file_stat.st_mode): + if sys.platform == "win32": # pragma: win32 cover + # On Windows, this is PermissionError + raise PermissionError(EACCES, "Permission denied", filename) + else: # pragma: win32 no cover # noqa: RET506 + # On linux / macOS, this is IsADirectoryError + raise IsADirectoryError(EISDIR, "Is a directory", filename) + + +def ensure_directory_exists(filename: Path | str) -> None: + """ + Ensure the directory containing the file exists (create it if necessary). + + :param filename: file. + + """ + Path(filename).parent.mkdir(parents=True, exist_ok=True) + + +__all__ = [ + "ensure_directory_exists", + "raise_on_not_writable_file", +] diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/_windows.py b/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/_windows.py new file mode 100644 index 0000000000000000000000000000000000000000..fe4903aed7b156e7a72ff4ee4ed5c99dac3f354c --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/_windows.py @@ -0,0 +1,111 @@ +from __future__ import annotations + +import os +import sys +from contextlib import suppress +from errno import EACCES +from pathlib import Path +from typing import cast + +from ._api import BaseFileLock +from ._util import ensure_directory_exists, raise_on_not_writable_file + +if sys.platform == "win32": # pragma: win32 cover + import ctypes + import msvcrt + from ctypes import wintypes + + # Windows API constants for reparse point detection + FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400 + INVALID_FILE_ATTRIBUTES = 0xFFFFFFFF + + # Load kernel32.dll + _kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) + _kernel32.GetFileAttributesW.argtypes = [wintypes.LPCWSTR] + _kernel32.GetFileAttributesW.restype = wintypes.DWORD + + def _is_reparse_point(path: str) -> bool: + """ + Check if a path is a reparse point (symlink, junction, etc.) on Windows. + + :param path: Path to check + + :returns: True if path is a reparse point, False otherwise + + :raises OSError: If GetFileAttributesW fails for reasons other than file-not-found + + """ + attrs = _kernel32.GetFileAttributesW(path) + if attrs == INVALID_FILE_ATTRIBUTES: + # File doesn't exist yet - that's fine, we'll create it + err = ctypes.get_last_error() + if err == 2: # noqa: PLR2004 # ERROR_FILE_NOT_FOUND + return False + if err == 3: # noqa: PLR2004 # ERROR_PATH_NOT_FOUND + return False + # Some other error - let caller handle it + return False + return bool(attrs & FILE_ATTRIBUTE_REPARSE_POINT) + + class WindowsFileLock(BaseFileLock): + """ + Uses the :func:`msvcrt.locking` function to hard lock the lock file on Windows systems. + + Lock file cleanup: Windows attempts to delete the lock file after release, but deletion is + not guaranteed in multi-threaded scenarios where another thread holds an open handle. The lock + file may persist on disk, which does not affect lock correctness. + """ + + def _acquire(self) -> None: + raise_on_not_writable_file(self.lock_file) + ensure_directory_exists(self.lock_file) + + # Security check: Refuse to open reparse points (symlinks, junctions) + # This prevents TOCTOU symlink attacks (CVE-TBD) + if _is_reparse_point(self.lock_file): + msg = f"Lock file is a reparse point (symlink/junction): {self.lock_file}" + raise OSError(msg) + + flags = ( + os.O_RDWR # open for read and write + | os.O_CREAT # create file if not exists + ) + try: + fd = os.open(self.lock_file, flags, self._open_mode()) + except OSError as exception: + if exception.errno != EACCES: # has no access to this lock + raise + else: + try: + msvcrt.locking(fd, msvcrt.LK_NBLCK, 1) + except OSError as exception: + os.close(fd) # close file first + if exception.errno != EACCES: # file is already locked + raise + else: + self._context.lock_file_fd = fd + + def _release(self) -> None: + fd = cast("int", self._context.lock_file_fd) + self._context.lock_file_fd = None + msvcrt.locking(fd, msvcrt.LK_UNLCK, 1) + os.close(fd) + + with suppress(OSError): + Path(self.lock_file).unlink() + +else: # pragma: win32 no cover + + class WindowsFileLock(BaseFileLock): + """Uses the :func:`msvcrt.locking` function to hard lock the lock file on Windows systems.""" + + def _acquire(self) -> None: + raise NotImplementedError + + def _release(self) -> None: + raise NotImplementedError + + +__all__ = [ + "WindowsFileLock", +] diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/asyncio.py b/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/asyncio.py new file mode 100644 index 0000000000000000000000000000000000000000..6b7725e05429867c9db99aec9b9eba13347a2ca7 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/asyncio.py @@ -0,0 +1,385 @@ +"""An asyncio-based implementation of the file lock.""" + +from __future__ import annotations + +import asyncio +import contextlib +import logging +import os +import time +from dataclasses import dataclass +from inspect import iscoroutinefunction +from threading import local +from typing import TYPE_CHECKING, Any, NoReturn, TypeVar + +from ._api import _UNSET_FILE_MODE, BaseFileLock, FileLockContext, FileLockMeta +from ._error import Timeout +from ._soft import SoftFileLock +from ._unix import UnixFileLock +from ._windows import WindowsFileLock + +if TYPE_CHECKING: + import sys + from collections.abc import Callable + from concurrent import futures + from types import TracebackType + + if sys.version_info >= (3, 11): # pragma: no cover (py311+) + from typing import Self + else: # pragma: no cover ( None: # noqa: D107 + self.lock = lock + + async def __aenter__(self) -> BaseAsyncFileLock: # noqa: D105 + return self.lock + + async def __aexit__( # noqa: D105 + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, + ) -> None: + await self.lock.release() + + +_AT = TypeVar("_AT", bound="BaseAsyncFileLock") + + +class AsyncFileLockMeta(FileLockMeta): + def __call__( # ty: ignore[invalid-method-override] # noqa: PLR0913 + cls: type[_AT], # noqa: N805 + lock_file: str | os.PathLike[str], + timeout: float = -1, + mode: int = _UNSET_FILE_MODE, + thread_local: bool = False, # noqa: FBT001, FBT002 + *, + blocking: bool = True, + is_singleton: bool = False, + poll_interval: float = 0.05, + lifetime: float | None = None, + loop: asyncio.AbstractEventLoop | None = None, + run_in_executor: bool = True, + executor: futures.Executor | None = None, + ) -> _AT: + if thread_local and run_in_executor: + msg = "run_in_executor is not supported when thread_local is True" + raise ValueError(msg) + return super().__call__( + lock_file=lock_file, + timeout=timeout, + mode=mode, + thread_local=thread_local, + blocking=blocking, + is_singleton=is_singleton, + poll_interval=poll_interval, + lifetime=lifetime, + loop=loop, + run_in_executor=run_in_executor, + executor=executor, + ) + + +class BaseAsyncFileLock(BaseFileLock, metaclass=AsyncFileLockMeta): + """ + Base class for asynchronous file locks. + + .. versionadded:: 3.15.0 + + """ + + def __init__( # noqa: PLR0913 + self, + lock_file: str | os.PathLike[str], + timeout: float = -1, + mode: int = _UNSET_FILE_MODE, + thread_local: bool = False, # noqa: FBT001, FBT002 + *, + blocking: bool = True, + is_singleton: bool = False, + poll_interval: float = 0.05, + lifetime: float | None = None, + loop: asyncio.AbstractEventLoop | None = None, + run_in_executor: bool = True, + executor: futures.Executor | None = None, + ) -> None: + """ + Create a new lock object. + + :param lock_file: path to the file + :param timeout: default timeout when acquiring the lock, in seconds. It will be used as fallback value in the + acquire method, if no timeout value (``None``) is given. If you want to disable the timeout, set it to a + negative value. A timeout of 0 means that there is exactly one attempt to acquire the file lock. + :param mode: file permissions for the lockfile. When not specified, the OS controls permissions via umask and + default ACLs, preserving POSIX default ACL inheritance in shared directories. + :param thread_local: Whether this object's internal context should be thread local or not. If this is set to + ``False`` then the lock will be reentrant across threads. + :param blocking: whether the lock should be blocking or not + :param is_singleton: If this is set to ``True`` then only one instance of this class will be created per lock + file. This is useful if you want to use the lock object for reentrant locking without needing to pass the + same object around. + :param poll_interval: default interval for polling the lock file, in seconds. It will be used as fallback value + in the acquire method, if no poll_interval value (``None``) is given. + :param lifetime: maximum time in seconds a lock can be held before it is considered expired. When set, a waiting + process will break a lock whose file modification time is older than ``lifetime`` seconds. ``None`` (the + default) means locks never expire. + :param loop: The event loop to use. If not specified, the running event loop will be used. + :param run_in_executor: If this is set to ``True`` then the lock will be acquired in an executor. + :param executor: The executor to use. If not specified, the default executor will be used. + + """ + self._is_thread_local = thread_local + self._is_singleton = is_singleton + + # Create the context. Note that external code should not work with the context directly and should instead use + # properties of this class. + kwargs: dict[str, Any] = { + "lock_file": os.fspath(lock_file), + "timeout": timeout, + "mode": mode, + "blocking": blocking, + "poll_interval": poll_interval, + "lifetime": lifetime, + "loop": loop, + "run_in_executor": run_in_executor, + "executor": executor, + } + self._context: AsyncFileLockContext = (AsyncThreadLocalFileContext if thread_local else AsyncFileLockContext)( + **kwargs + ) + + @property + def run_in_executor(self) -> bool: + """:returns: whether run in executor.""" + return self._context.run_in_executor + + @property + def executor(self) -> futures.Executor | None: + """:returns: the executor.""" + return self._context.executor + + @executor.setter + def executor(self, value: futures.Executor | None) -> None: # pragma: no cover + """ + Change the executor. + + :param futures.Executor | None value: the new executor or ``None`` + + """ + self._context.executor = value + + @property + def loop(self) -> asyncio.AbstractEventLoop | None: + """:returns: the event loop.""" + return self._context.loop + + async def acquire( # ty: ignore[invalid-method-override] + self, + timeout: float | None = None, + poll_interval: float | None = None, + *, + blocking: bool | None = None, + cancel_check: Callable[[], bool] | None = None, + ) -> AsyncAcquireReturnProxy: + """ + Try to acquire the file lock. + + :param timeout: maximum wait time for acquiring the lock, ``None`` means use the default + :attr:`~BaseFileLock.timeout` is and if ``timeout < 0``, there is no timeout and this method will block + until the lock could be acquired + :param poll_interval: interval of trying to acquire the lock file, ``None`` means use the default + :attr:`~BaseFileLock.poll_interval` + :param blocking: defaults to True. If False, function will return immediately if it cannot obtain a lock on the + first attempt. Otherwise, this method will block until the timeout expires or the lock is acquired. + :param cancel_check: a callable returning ``True`` when the acquisition should be canceled. Checked on each poll + iteration. When triggered, raises :class:`~Timeout` just like an expired timeout. + + :returns: a context object that will unlock the file when the context is exited + + :raises Timeout: if fails to acquire lock within the timeout period + + .. code-block:: python + + # You can use this method in the context manager (recommended) + with lock.acquire(): + pass + + # Or use an equivalent try-finally construct: + lock.acquire() + try: + pass + finally: + lock.release() + + """ + # Use the default timeout, if no timeout is provided. + if timeout is None: + timeout = self._context.timeout + + if blocking is None: + blocking = self._context.blocking + + if poll_interval is None: + poll_interval = self._context.poll_interval + + # Increment the number right at the beginning. We can still undo it, if something fails. + self._context.lock_counter += 1 + + lock_id = id(self) + lock_filename = self.lock_file + start_time = time.perf_counter() + try: + while True: + if not self.is_locked: + self._try_break_expired_lock() + _LOGGER.debug("Attempting to acquire lock %s on %s", lock_id, lock_filename) + await self._run_internal_method(self._acquire) + if self.is_locked: + _LOGGER.debug("Lock %s acquired on %s", lock_id, lock_filename) + break + if self._check_give_up( + lock_id, + lock_filename, + blocking=blocking, + cancel_check=cancel_check, + timeout=timeout, + start_time=start_time, + ): + raise Timeout(lock_filename) # noqa: TRY301 + msg = "Lock %s not acquired on %s, waiting %s seconds ..." + _LOGGER.debug(msg, lock_id, lock_filename, poll_interval) + await asyncio.sleep(poll_interval) + except BaseException: # Something did go wrong, so decrement the counter. + self._context.lock_counter = max(0, self._context.lock_counter - 1) + raise + return AsyncAcquireReturnProxy(lock=self) + + async def release(self, force: bool = False) -> None: # ty: ignore[invalid-method-override] # noqa: FBT001, FBT002 + """ + Release the file lock. The lock is only completely released when the lock counter reaches 0. The lock file + itself is not automatically deleted. + + :param force: If true, the lock counter is ignored and the lock is released in every case. + + """ + if self.is_locked: + self._context.lock_counter -= 1 + + if self._context.lock_counter == 0 or force: + lock_id, lock_filename = id(self), self.lock_file + + _LOGGER.debug("Attempting to release lock %s on %s", lock_id, lock_filename) + await self._run_internal_method(self._release) + self._context.lock_counter = 0 + _LOGGER.debug("Lock %s released on %s", lock_id, lock_filename) + + async def _run_internal_method(self, method: Callable[[], Any]) -> None: + if iscoroutinefunction(method): + await method() + elif self.run_in_executor: + await asyncio.get_running_loop().run_in_executor(self.executor, method) + else: + method() + + def __enter__(self) -> NoReturn: + """Sync context manager entry is not supported because lock acquisition is a coroutine.""" + msg = "Use `async with` — acquire/release are coroutines and cannot be awaited in a sync context manager." + raise NotImplementedError(msg) + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: object, + ) -> None: + """Sync context manager exit is not supported because lock release is a coroutine.""" + msg = "Use `async with` — acquire/release are coroutines and cannot be awaited in a sync context manager." + raise NotImplementedError(msg) + + async def __aenter__(self) -> Self: + """ + Acquire the lock. + + :returns: the lock object + + """ + await self.acquire() + return self + + async def __aexit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, + ) -> None: + """ + Release the lock. + + :param exc_type: the exception type if raised + :param exc_value: the exception value if raised + :param traceback: the exception traceback if raised + + """ + await self.release() + + def __del__(self) -> None: + """Release on deletion — safe to call during GC even when no event loop is running.""" + with contextlib.suppress(Exception): + try: + loop = asyncio.get_running_loop() + except RuntimeError: + # No running loop — try stored loop or create one + loop = self._context.loop if self._context.loop and not self._context.loop.is_closed() else None + if loop is None: + return + if not loop.is_running(): # pragma: no cover + loop.run_until_complete(self.release(force=True)) + else: + loop.create_task(self.release(force=True)) + + +class AsyncSoftFileLock(SoftFileLock, BaseAsyncFileLock): + """Simply watches the existence of the lock file.""" + + +class AsyncUnixFileLock(UnixFileLock, BaseAsyncFileLock): + """Uses the :func:`fcntl.flock` to hard lock the lock file on unix systems.""" + + +class AsyncWindowsFileLock(WindowsFileLock, BaseAsyncFileLock): + """Uses the :func:`msvcrt.locking` to hard lock the lock file on windows systems.""" + + +__all__ = [ + "AsyncAcquireReturnProxy", + "AsyncSoftFileLock", + "AsyncUnixFileLock", + "AsyncWindowsFileLock", + "BaseAsyncFileLock", +] diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/py.typed b/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/py.typed new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/version.py b/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/version.py new file mode 100644 index 0000000000000000000000000000000000000000..6297b7f6588debe5e6ca2b0e770c3db3bb809151 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/filelock/version.py @@ -0,0 +1,24 @@ +# file generated by vcs-versioning +# don't change, don't track in version control +from __future__ import annotations + +__all__ = [ + "__version__", + "__version_tuple__", + "version", + "version_tuple", + "__commit_id__", + "commit_id", +] + +version: str +__version__: str +__version_tuple__: tuple[int | str, ...] +version_tuple: tuple[int | str, ...] +commit_id: str | None +__commit_id__: str | None + +__version__ = version = '3.29.0' +__version_tuple__ = version_tuple = (3, 29, 0) + +__commit_id__ = commit_id = None diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..452c78a055e72a6d04f1013d1a98fda33fdc449e --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/__init__.py @@ -0,0 +1,71 @@ +from . import caching +from ._version import __version__ # noqa: F401 +from .callbacks import Callback +from .compression import available_compressions +from .core import get_fs_token_paths, open, open_files, open_local, url_to_fs +from .exceptions import FSTimeoutError +from .mapping import FSMap, get_mapper +from .registry import ( + available_protocols, + filesystem, + get_filesystem_class, + register_implementation, + registry, +) +from .spec import AbstractFileSystem + +__all__ = [ + "AbstractFileSystem", + "FSTimeoutError", + "FSMap", + "filesystem", + "register_implementation", + "get_filesystem_class", + "get_fs_token_paths", + "get_mapper", + "open", + "open_files", + "open_local", + "registry", + "caching", + "Callback", + "available_protocols", + "available_compressions", + "url_to_fs", +] + + +def process_entries(): + try: + from importlib.metadata import entry_points + except ImportError: + return + if entry_points is not None: + try: + eps = entry_points() + except TypeError: + pass # importlib-metadata < 0.8 + else: + if hasattr(eps, "select"): # Python 3.10+ / importlib_metadata >= 3.9.0 + specs = eps.select(group="fsspec.specs") + else: + specs = eps.get("fsspec.specs", []) + registered_names = {} + for spec in specs: + err_msg = f"Unable to load filesystem from {spec}" + name = spec.name + if name in registered_names: + continue + registered_names[name] = True + register_implementation( + name, + spec.value.replace(":", "."), + errtxt=err_msg, + # We take our implementations as the ones to overload with if + # for some reason we encounter some, may be the same, already + # registered + clobber=True, + ) + + +process_entries() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/_version.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/_version.py new file mode 100644 index 0000000000000000000000000000000000000000..43aa383e6690a59bd71080d1dcffa276ea25ad29 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/_version.py @@ -0,0 +1,34 @@ +# file generated by setuptools-scm +# don't change, don't track in version control + +__all__ = [ + "__version__", + "__version_tuple__", + "version", + "version_tuple", + "__commit_id__", + "commit_id", +] + +TYPE_CHECKING = False +if TYPE_CHECKING: + from typing import Tuple + from typing import Union + + VERSION_TUPLE = Tuple[Union[int, str], ...] + COMMIT_ID = Union[str, None] +else: + VERSION_TUPLE = object + COMMIT_ID = object + +version: str +__version__: str +__version_tuple__: VERSION_TUPLE +version_tuple: VERSION_TUPLE +commit_id: COMMIT_ID +__commit_id__: COMMIT_ID + +__version__ = version = '2026.2.0' +__version_tuple__ = version_tuple = (2026, 2, 0) + +__commit_id__ = commit_id = None diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/archive.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/archive.py new file mode 100644 index 0000000000000000000000000000000000000000..13a4da8df7c9405297cdd7d37476be2f725b2f57 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/archive.py @@ -0,0 +1,75 @@ +import operator + +from fsspec import AbstractFileSystem +from fsspec.utils import tokenize + + +class AbstractArchiveFileSystem(AbstractFileSystem): + """ + A generic superclass for implementing Archive-based filesystems. + + Currently, it is shared amongst + :class:`~fsspec.implementations.zip.ZipFileSystem`, + :class:`~fsspec.implementations.libarchive.LibArchiveFileSystem` and + :class:`~fsspec.implementations.tar.TarFileSystem`. + """ + + def __str__(self): + return f"" + + __repr__ = __str__ + + def ukey(self, path): + return tokenize(path, self.fo, self.protocol) + + def _all_dirnames(self, paths): + """Returns *all* directory names for each path in paths, including intermediate + ones. + + Parameters + ---------- + paths: Iterable of path strings + """ + if len(paths) == 0: + return set() + + dirnames = {self._parent(path) for path in paths} - {self.root_marker} + return dirnames | self._all_dirnames(dirnames) + + def info(self, path, **kwargs): + self._get_dirs() + path = self._strip_protocol(path) + if path in {"", "/"} and self.dir_cache: + return {"name": "", "type": "directory", "size": 0} + if path in self.dir_cache: + return self.dir_cache[path] + elif path + "/" in self.dir_cache: + return self.dir_cache[path + "/"] + else: + raise FileNotFoundError(path) + + def ls(self, path, detail=True, **kwargs): + self._get_dirs() + paths = {} + for p, f in self.dir_cache.items(): + p = p.rstrip("/") + if "/" in p: + root = p.rsplit("/", 1)[0] + else: + root = "" + if root == path.rstrip("/"): + paths[p] = f + elif all( + (a == b) + for a, b in zip(path.split("/"), [""] + p.strip("/").split("/")) + ): + # root directory entry + ppath = p.rstrip("/").split("/", 1)[0] + if ppath not in paths: + out = {"name": ppath, "size": 0, "type": "directory"} + paths[ppath] = out + if detail: + out = sorted(paths.values(), key=operator.itemgetter("name")) + return out + else: + return sorted(paths) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/asyn.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/asyn.py new file mode 100644 index 0000000000000000000000000000000000000000..360758ac64608331d0976b8ee17c2c02d1f3e6d7 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/asyn.py @@ -0,0 +1,1103 @@ +import asyncio +import asyncio.events +import functools +import inspect +import io +import numbers +import os +import re +import threading +from collections.abc import Iterable +from glob import has_magic +from typing import TYPE_CHECKING + +from .callbacks import DEFAULT_CALLBACK +from .exceptions import FSTimeoutError +from .implementations.local import LocalFileSystem, make_path_posix, trailing_sep +from .spec import AbstractBufferedFile, AbstractFileSystem +from .utils import glob_translate, is_exception, other_paths + +private = re.compile("_[^_]") +iothread = [None] # dedicated fsspec IO thread +loop = [None] # global event loop for any non-async instance +_lock = None # global lock placeholder +get_running_loop = asyncio.get_running_loop + + +def get_lock(): + """Allocate or return a threading lock. + + The lock is allocated on first use to allow setting one lock per forked process. + """ + global _lock + if not _lock: + _lock = threading.Lock() + return _lock + + +def reset_lock(): + """Reset the global lock. + + This should be called only on the init of a forked process to reset the lock to + None, enabling the new forked process to get a new lock. + """ + global _lock + + iothread[0] = None + loop[0] = None + _lock = None + + +async def _runner(event, coro, result, timeout=None): + timeout = timeout if timeout else None # convert 0 or 0.0 to None + if timeout is not None: + coro = asyncio.wait_for(coro, timeout=timeout) + try: + result[0] = await coro + except Exception as ex: + result[0] = ex + finally: + event.set() + + +def sync(loop, func, *args, timeout=None, **kwargs): + """ + Make loop run coroutine until it returns. Runs in other thread + + Examples + -------- + >>> fsspec.asyn.sync(fsspec.asyn.get_loop(), func, *args, + timeout=timeout, **kwargs) + """ + timeout = timeout if timeout else None # convert 0 or 0.0 to None + # NB: if the loop is not running *yet*, it is OK to submit work + # and we will wait for it + if loop is None or loop.is_closed(): + raise RuntimeError("Loop is not running") + try: + loop0 = asyncio.events.get_running_loop() + if loop0 is loop: + raise NotImplementedError("Calling sync() from within a running loop") + except NotImplementedError: + raise + except RuntimeError: + pass + coro = func(*args, **kwargs) + result = [None] + event = threading.Event() + asyncio.run_coroutine_threadsafe(_runner(event, coro, result, timeout), loop) + while True: + # this loops allows thread to get interrupted + if event.wait(1): + break + if timeout is not None: + timeout -= 1 + if timeout < 0: + raise FSTimeoutError + + return_result = result[0] + if isinstance(return_result, asyncio.TimeoutError): + # suppress asyncio.TimeoutError, raise FSTimeoutError + raise FSTimeoutError from return_result + elif isinstance(return_result, BaseException): + raise return_result + else: + return return_result + + +def sync_wrapper(func, obj=None): + """Given a function, make so can be called in blocking contexts + + Leave obj=None if defining within a class. Pass the instance if attaching + as an attribute of the instance. + """ + + @functools.wraps(func) + def wrapper(*args, **kwargs): + self = obj or args[0] + return sync(self.loop, func, *args, **kwargs) + + return wrapper + + +def get_loop(): + """Create or return the default fsspec IO loop + + The loop will be running on a separate thread. + """ + if loop[0] is None: + with get_lock(): + # repeat the check just in case the loop got filled between the + # previous two calls from another thread + if loop[0] is None: + loop[0] = asyncio.new_event_loop() + th = threading.Thread(target=loop[0].run_forever, name="fsspecIO") + th.daemon = True + th.start() + iothread[0] = th + return loop[0] + + +def reset_after_fork(): + global lock + loop[0] = None + iothread[0] = None + lock = None + + +if hasattr(os, "register_at_fork"): + # should be posix; this will do nothing for spawn or forkserver subprocesses + os.register_at_fork(after_in_child=reset_after_fork) + + +if TYPE_CHECKING: + import resource + + ResourceError = resource.error +else: + try: + import resource + except ImportError: + resource = None + ResourceError = OSError + else: + ResourceError = getattr(resource, "error", OSError) + +_DEFAULT_BATCH_SIZE = 128 +_NOFILES_DEFAULT_BATCH_SIZE = 1280 + + +def _get_batch_size(nofiles=False): + from fsspec.config import conf + + if nofiles: + if "nofiles_gather_batch_size" in conf: + return conf["nofiles_gather_batch_size"] + else: + if "gather_batch_size" in conf: + return conf["gather_batch_size"] + if nofiles: + return _NOFILES_DEFAULT_BATCH_SIZE + if resource is None: + return _DEFAULT_BATCH_SIZE + + try: + soft_limit, _ = resource.getrlimit(resource.RLIMIT_NOFILE) + except (ImportError, ValueError, ResourceError): + return _DEFAULT_BATCH_SIZE + + if soft_limit == resource.RLIM_INFINITY: + return -1 + else: + return soft_limit // 8 + + +def running_async() -> bool: + """Being executed by an event loop?""" + try: + asyncio.get_running_loop() + return True + except RuntimeError: + return False + + +async def _run_coros_in_chunks( + coros, + batch_size=None, + callback=DEFAULT_CALLBACK, + timeout=None, + return_exceptions=False, + nofiles=False, +): + """Run the given coroutines in chunks. + + Parameters + ---------- + coros: list of coroutines to run + batch_size: int or None + Number of coroutines to submit/wait on simultaneously. + If -1, then it will not be any throttling. If + None, it will be inferred from _get_batch_size() + callback: fsspec.callbacks.Callback instance + Gets a relative_update when each coroutine completes + timeout: number or None + If given, each coroutine times out after this time. Note that, since + there are multiple batches, the total run time of this function will in + general be longer + return_exceptions: bool + Same meaning as in asyncio.gather + nofiles: bool + If inferring the batch_size, does this operation involve local files? + If yes, you normally expect smaller batches. + """ + + if batch_size is None: + batch_size = _get_batch_size(nofiles=nofiles) + + if batch_size == -1: + batch_size = len(coros) + + assert batch_size > 0 + + async def _run_coro(coro, i): + try: + return await asyncio.wait_for(coro, timeout=timeout), i + except Exception as e: + if not return_exceptions: + raise + return e, i + finally: + callback.relative_update(1) + + i = 0 + n = len(coros) + results = [None] * n + pending = set() + + while pending or i < n: + while len(pending) < batch_size and i < n: + pending.add(asyncio.ensure_future(_run_coro(coros[i], i))) + i += 1 + + if not pending: + break + + done, pending = await asyncio.wait(pending, return_when=asyncio.FIRST_COMPLETED) + while done: + result, k = await done.pop() + results[k] = result + + return results + + +# these methods should be implemented as async by any async-able backend +async_methods = [ + "_ls", + "_cat_file", + "_get_file", + "_put_file", + "_rm_file", + "_cp_file", + "_pipe_file", + "_expand_path", + "_info", + "_isfile", + "_isdir", + "_exists", + "_walk", + "_glob", + "_find", + "_du", + "_size", + "_mkdir", + "_makedirs", +] + + +class AsyncFileSystem(AbstractFileSystem): + """Async file operations, default implementations + + Passes bulk operations to asyncio.gather for concurrent operation. + + Implementations that have concurrent batch operations and/or async methods + should inherit from this class instead of AbstractFileSystem. Docstrings are + copied from the un-underscored method in AbstractFileSystem, if not given. + """ + + # note that methods do not have docstring here; they will be copied + # for _* methods and inferred for overridden methods. + + async_impl = True + mirror_sync_methods = True + disable_throttling = False + + def __init__(self, *args, asynchronous=False, loop=None, batch_size=None, **kwargs): + self.asynchronous = asynchronous + self._pid = os.getpid() + if not asynchronous: + self._loop = loop or get_loop() + else: + self._loop = None + self.batch_size = batch_size + super().__init__(*args, **kwargs) + + @property + def loop(self): + if self._pid != os.getpid(): + raise RuntimeError("This class is not fork-safe") + return self._loop + + async def _rm_file(self, path, **kwargs): + if ( + inspect.iscoroutinefunction(self._rm) + and type(self)._rm is not AsyncFileSystem._rm + ): + return await self._rm(path, recursive=False, batch_size=1, **kwargs) + raise NotImplementedError + + async def _rm(self, path, recursive=False, batch_size=None, **kwargs): + # TODO: implement on_error + batch_size = batch_size or self.batch_size + path = await self._expand_path(path, recursive=recursive) + return await _run_coros_in_chunks( + [self._rm_file(p, **kwargs) for p in reversed(path)], + batch_size=batch_size, + nofiles=True, + ) + + async def _cp_file(self, path1, path2, **kwargs): + raise NotImplementedError + + async def _mv_file(self, path1, path2): + await self._cp_file(path1, path2) + await self._rm_file(path1) + + async def _copy( + self, + path1, + path2, + recursive=False, + on_error=None, + maxdepth=None, + batch_size=None, + **kwargs, + ): + if on_error is None and recursive: + on_error = "ignore" + elif on_error is None: + on_error = "raise" + + if isinstance(path1, list) and isinstance(path2, list): + # No need to expand paths when both source and destination + # are provided as lists + paths1 = path1 + paths2 = path2 + else: + source_is_str = isinstance(path1, str) + paths1 = await self._expand_path( + path1, maxdepth=maxdepth, recursive=recursive + ) + if source_is_str and (not recursive or maxdepth is not None): + # Non-recursive glob does not copy directories + paths1 = [ + p for p in paths1 if not (trailing_sep(p) or await self._isdir(p)) + ] + if not paths1: + return + + source_is_file = len(paths1) == 1 + dest_is_dir = isinstance(path2, str) and ( + trailing_sep(path2) or await self._isdir(path2) + ) + + exists = source_is_str and ( + (has_magic(path1) and source_is_file) + or (not has_magic(path1) and dest_is_dir and not trailing_sep(path1)) + ) + paths2 = other_paths( + paths1, + path2, + exists=exists, + flatten=not source_is_str, + ) + + batch_size = batch_size or self.batch_size + coros = [self._cp_file(p1, p2, **kwargs) for p1, p2 in zip(paths1, paths2)] + result = await _run_coros_in_chunks( + coros, batch_size=batch_size, return_exceptions=True, nofiles=True + ) + + for ex in filter(is_exception, result): + if on_error == "ignore" and isinstance(ex, FileNotFoundError): + continue + raise ex + + async def _pipe_file(self, path, value, mode="overwrite", **kwargs): + raise NotImplementedError + + async def _pipe(self, path, value=None, batch_size=None, **kwargs): + if isinstance(path, str): + path = {path: value} + batch_size = batch_size or self.batch_size + return await _run_coros_in_chunks( + [self._pipe_file(k, v, **kwargs) for k, v in path.items()], + batch_size=batch_size, + nofiles=True, + ) + + async def _process_limits(self, url, start, end): + """Helper for "Range"-based _cat_file""" + size = None + suff = False + if start is not None and start < 0: + # if start is negative and end None, end is the "suffix length" + if end is None: + end = -start + start = "" + suff = True + else: + size = size or (await self._info(url))["size"] + start = size + start + elif start is None: + start = 0 + if not suff: + if end is not None and end < 0: + if start is not None: + size = size or (await self._info(url))["size"] + end = size + end + elif end is None: + end = "" + if isinstance(end, numbers.Integral): + end -= 1 # bytes range is inclusive + return f"bytes={start}-{end}" + + async def _cat_file(self, path, start=None, end=None, **kwargs): + raise NotImplementedError + + async def _cat( + self, path, recursive=False, on_error="raise", batch_size=None, **kwargs + ): + paths = await self._expand_path(path, recursive=recursive) + coros = [self._cat_file(path, **kwargs) for path in paths] + batch_size = batch_size or self.batch_size + out = await _run_coros_in_chunks( + coros, batch_size=batch_size, nofiles=True, return_exceptions=True + ) + if on_error == "raise": + ex = next(filter(is_exception, out), False) + if ex: + raise ex + if ( + len(paths) > 1 + or isinstance(path, list) + or paths[0] != self._strip_protocol(path) + ): + return { + k: v + for k, v in zip(paths, out) + if on_error != "omit" or not is_exception(v) + } + else: + return out[0] + + async def _cat_ranges( + self, + paths, + starts, + ends, + max_gap=None, + batch_size=None, + on_error="return", + **kwargs, + ): + """Get the contents of byte ranges from one or more files + + Parameters + ---------- + paths: list + A list of of filepaths on this filesystems + starts, ends: int or list + Bytes limits of the read. If using a single int, the same value will be + used to read all the specified files. + """ + # TODO: on_error + if max_gap is not None: + # use utils.merge_offset_ranges + raise NotImplementedError + if not isinstance(paths, list): + raise TypeError + if not isinstance(starts, Iterable): + starts = [starts] * len(paths) + if not isinstance(ends, Iterable): + ends = [ends] * len(paths) + if len(starts) != len(paths) or len(ends) != len(paths): + raise ValueError + coros = [ + self._cat_file(p, start=s, end=e, **kwargs) + for p, s, e in zip(paths, starts, ends) + ] + batch_size = batch_size or self.batch_size + return await _run_coros_in_chunks( + coros, batch_size=batch_size, nofiles=True, return_exceptions=True + ) + + async def _put_file(self, lpath, rpath, mode="overwrite", **kwargs): + raise NotImplementedError + + async def _put( + self, + lpath, + rpath, + recursive=False, + callback=DEFAULT_CALLBACK, + batch_size=None, + maxdepth=None, + **kwargs, + ): + """Copy file(s) from local. + + Copies a specific file or tree of files (if recursive=True). If rpath + ends with a "/", it will be assumed to be a directory, and target files + will go within. + + The put_file method will be called concurrently on a batch of files. The + batch_size option can configure the amount of futures that can be executed + at the same time. If it is -1, then all the files will be uploaded concurrently. + The default can be set for this instance by passing "batch_size" in the + constructor, or for all instances by setting the "gather_batch_size" key + in ``fsspec.config.conf``, falling back to 1/8th of the system limit . + """ + if isinstance(lpath, list) and isinstance(rpath, list): + # No need to expand paths when both source and destination + # are provided as lists + rpaths = rpath + lpaths = lpath + else: + source_is_str = isinstance(lpath, str) + if source_is_str: + lpath = make_path_posix(lpath) + fs = LocalFileSystem() + lpaths = fs.expand_path(lpath, recursive=recursive, maxdepth=maxdepth) + if source_is_str and (not recursive or maxdepth is not None): + # Non-recursive glob does not copy directories + lpaths = [p for p in lpaths if not (trailing_sep(p) or fs.isdir(p))] + if not lpaths: + return + + source_is_file = len(lpaths) == 1 + dest_is_dir = isinstance(rpath, str) and ( + trailing_sep(rpath) or await self._isdir(rpath) + ) + + rpath = self._strip_protocol(rpath) + exists = source_is_str and ( + (has_magic(lpath) and source_is_file) + or (not has_magic(lpath) and dest_is_dir and not trailing_sep(lpath)) + ) + rpaths = other_paths( + lpaths, + rpath, + exists=exists, + flatten=not source_is_str, + ) + + is_dir = {l: os.path.isdir(l) for l in lpaths} + rdirs = [r for l, r in zip(lpaths, rpaths) if is_dir[l]] + file_pairs = [(l, r) for l, r in zip(lpaths, rpaths) if not is_dir[l]] + + await asyncio.gather(*[self._makedirs(d, exist_ok=True) for d in rdirs]) + batch_size = batch_size or self.batch_size + + coros = [] + callback.set_size(len(file_pairs)) + for lfile, rfile in file_pairs: + put_file = callback.branch_coro(self._put_file) + coros.append(put_file(lfile, rfile, **kwargs)) + + return await _run_coros_in_chunks( + coros, batch_size=batch_size, callback=callback + ) + + async def _get_file(self, rpath, lpath, **kwargs): + raise NotImplementedError + + async def _get( + self, + rpath, + lpath, + recursive=False, + callback=DEFAULT_CALLBACK, + maxdepth=None, + **kwargs, + ): + """Copy file(s) to local. + + Copies a specific file or tree of files (if recursive=True). If lpath + ends with a "/", it will be assumed to be a directory, and target files + will go within. Can submit a list of paths, which may be glob-patterns + and will be expanded. + + The get_file method will be called concurrently on a batch of files. The + batch_size option can configure the amount of futures that can be executed + at the same time. If it is -1, then all the files will be uploaded concurrently. + The default can be set for this instance by passing "batch_size" in the + constructor, or for all instances by setting the "gather_batch_size" key + in ``fsspec.config.conf``, falling back to 1/8th of the system limit . + """ + if isinstance(lpath, list) and isinstance(rpath, list): + # No need to expand paths when both source and destination + # are provided as lists + rpaths = rpath + lpaths = lpath + else: + source_is_str = isinstance(rpath, str) + # First check for rpath trailing slash as _strip_protocol removes it. + source_not_trailing_sep = source_is_str and not trailing_sep(rpath) + rpath = self._strip_protocol(rpath) + rpaths = await self._expand_path( + rpath, recursive=recursive, maxdepth=maxdepth + ) + if source_is_str and (not recursive or maxdepth is not None): + # Non-recursive glob does not copy directories + rpaths = [ + p for p in rpaths if not (trailing_sep(p) or await self._isdir(p)) + ] + if not rpaths: + return + + lpath = make_path_posix(lpath) + source_is_file = len(rpaths) == 1 + dest_is_dir = isinstance(lpath, str) and ( + trailing_sep(lpath) or LocalFileSystem().isdir(lpath) + ) + + exists = source_is_str and ( + (has_magic(rpath) and source_is_file) + or (not has_magic(rpath) and dest_is_dir and source_not_trailing_sep) + ) + lpaths = other_paths( + rpaths, + lpath, + exists=exists, + flatten=not source_is_str, + ) + + [os.makedirs(os.path.dirname(lp), exist_ok=True) for lp in lpaths] + batch_size = kwargs.pop("batch_size", self.batch_size) + + coros = [] + callback.set_size(len(lpaths)) + for lpath, rpath in zip(lpaths, rpaths): + get_file = callback.branch_coro(self._get_file) + coros.append(get_file(rpath, lpath, **kwargs)) + return await _run_coros_in_chunks( + coros, batch_size=batch_size, callback=callback + ) + + async def _isfile(self, path): + try: + return (await self._info(path))["type"] == "file" + except: # noqa: E722 + return False + + async def _isdir(self, path): + try: + return (await self._info(path))["type"] == "directory" + except OSError: + return False + + async def _size(self, path): + return (await self._info(path)).get("size", None) + + async def _sizes(self, paths, batch_size=None): + batch_size = batch_size or self.batch_size + return await _run_coros_in_chunks( + [self._size(p) for p in paths], batch_size=batch_size + ) + + async def _exists(self, path, **kwargs): + try: + await self._info(path, **kwargs) + return True + except FileNotFoundError: + return False + + async def _info(self, path, **kwargs): + raise NotImplementedError + + async def _ls(self, path, detail=True, **kwargs): + raise NotImplementedError + + async def _walk(self, path, maxdepth=None, on_error="omit", **kwargs): + if maxdepth is not None and maxdepth < 1: + raise ValueError("maxdepth must be at least 1") + + path = self._strip_protocol(path) + full_dirs = {} + dirs = {} + files = {} + + detail = kwargs.pop("detail", False) + try: + listing = await self._ls(path, detail=True, **kwargs) + except (FileNotFoundError, OSError) as e: + if on_error == "raise": + raise + elif callable(on_error): + on_error(e) + if detail: + yield path, {}, {} + else: + yield path, [], [] + return + + for info in listing: + # each info name must be at least [path]/part , but here + # we check also for names like [path]/part/ + pathname = info["name"].rstrip("/") + name = pathname.rsplit("/", 1)[-1] + if info["type"] == "directory" and pathname != path: + # do not include "self" path + full_dirs[name] = pathname + dirs[name] = info + elif pathname == path: + # file-like with same name as give path + files[""] = info + else: + files[name] = info + + if detail: + yield path, dirs, files + else: + yield path, list(dirs), list(files) + + if maxdepth is not None: + maxdepth -= 1 + if maxdepth < 1: + return + + for d in dirs: + async for _ in self._walk( + full_dirs[d], maxdepth=maxdepth, detail=detail, **kwargs + ): + yield _ + + async def _glob(self, path, maxdepth=None, **kwargs): + if maxdepth is not None and maxdepth < 1: + raise ValueError("maxdepth must be at least 1") + + import re + + seps = (os.path.sep, os.path.altsep) if os.path.altsep else (os.path.sep,) + ends_with_sep = path.endswith(seps) # _strip_protocol strips trailing slash + path = self._strip_protocol(path) + append_slash_to_dirname = ends_with_sep or path.endswith( + tuple(sep + "**" for sep in seps) + ) + idx_star = path.find("*") if path.find("*") >= 0 else len(path) + idx_qmark = path.find("?") if path.find("?") >= 0 else len(path) + idx_brace = path.find("[") if path.find("[") >= 0 else len(path) + + min_idx = min(idx_star, idx_qmark, idx_brace) + + detail = kwargs.pop("detail", False) + withdirs = kwargs.pop("withdirs", True) + + if not has_magic(path): + if await self._exists(path, **kwargs): + if not detail: + return [path] + else: + return {path: await self._info(path, **kwargs)} + else: + if not detail: + return [] # glob of non-existent returns empty + else: + return {} + elif "/" in path[:min_idx]: + min_idx = path[:min_idx].rindex("/") + root = path[: min_idx + 1] + depth = path[min_idx + 1 :].count("/") + 1 + else: + root = "" + depth = path[min_idx + 1 :].count("/") + 1 + + if "**" in path: + if maxdepth is not None: + idx_double_stars = path.find("**") + depth_double_stars = path[idx_double_stars:].count("/") + 1 + depth = depth - depth_double_stars + maxdepth + else: + depth = None + + allpaths = await self._find( + root, maxdepth=depth, withdirs=withdirs, detail=True, **kwargs + ) + + pattern = glob_translate(path + ("/" if ends_with_sep else "")) + pattern = re.compile(pattern) + + out = { + p: info + for p, info in sorted(allpaths.items()) + if pattern.match( + p + "/" + if append_slash_to_dirname and info["type"] == "directory" + else p + ) + } + + if detail: + return out + else: + return list(out) + + async def _du(self, path, total=True, maxdepth=None, **kwargs): + sizes = {} + # async for? + for f in await self._find(path, maxdepth=maxdepth, **kwargs): + info = await self._info(f) + sizes[info["name"]] = info["size"] + if total: + return sum(sizes.values()) + else: + return sizes + + async def _find(self, path, maxdepth=None, withdirs=False, **kwargs): + path = self._strip_protocol(path) + out = {} + detail = kwargs.pop("detail", False) + + # Add the root directory if withdirs is requested + # This is needed for posix glob compliance + if withdirs and path != "" and await self._isdir(path): + out[path] = await self._info(path) + + # async for? + async for _, dirs, files in self._walk(path, maxdepth, detail=True, **kwargs): + if withdirs: + files.update(dirs) + out.update({info["name"]: info for name, info in files.items()}) + if not out and (await self._isfile(path)): + # walk works on directories, but find should also return [path] + # when path happens to be a file + out[path] = {} + names = sorted(out) + if not detail: + return names + else: + return {name: out[name] for name in names} + + async def _expand_path(self, path, recursive=False, maxdepth=None): + if maxdepth is not None and maxdepth < 1: + raise ValueError("maxdepth must be at least 1") + + if isinstance(path, str): + out = await self._expand_path([path], recursive, maxdepth) + else: + out = set() + path = [self._strip_protocol(p) for p in path] + for p in path: # can gather here + if has_magic(p): + bit = set(await self._glob(p, maxdepth=maxdepth)) + out |= bit + if recursive: + # glob call above expanded one depth so if maxdepth is defined + # then decrement it in expand_path call below. If it is zero + # after decrementing then avoid expand_path call. + if maxdepth is not None and maxdepth <= 1: + continue + out |= set( + await self._expand_path( + list(bit), + recursive=recursive, + maxdepth=maxdepth - 1 if maxdepth is not None else None, + ) + ) + continue + elif recursive: + rec = set(await self._find(p, maxdepth=maxdepth, withdirs=True)) + out |= rec + if p not in out and (recursive is False or (await self._exists(p))): + # should only check once, for the root + out.add(p) + if not out: + raise FileNotFoundError(path) + return sorted(out) + + async def _mkdir(self, path, create_parents=True, **kwargs): + pass # not necessary to implement, may not have directories + + async def _makedirs(self, path, exist_ok=False): + pass # not necessary to implement, may not have directories + + async def open_async(self, path, mode="rb", **kwargs): + if "b" not in mode or kwargs.get("compression"): + raise ValueError + raise NotImplementedError + + +def mirror_sync_methods(obj): + """Populate sync and async methods for obj + + For each method will create a sync version if the name refers to an async method + (coroutine) and there is no override in the child class; will create an async + method for the corresponding sync method if there is no implementation. + + Uses the methods specified in + - async_methods: the set that an implementation is expected to provide + - default_async_methods: that can be derived from their sync version in + AbstractFileSystem + - AsyncFileSystem: async-specific default coroutines + """ + from fsspec import AbstractFileSystem + + for method in async_methods + dir(AsyncFileSystem): + if not method.startswith("_"): + continue + smethod = method[1:] + if private.match(method): + isco = inspect.iscoroutinefunction(getattr(obj, method, None)) + unsync = getattr(getattr(obj, smethod, False), "__func__", None) + is_default = unsync is getattr(AbstractFileSystem, smethod, "") + if isco and is_default: + mth = sync_wrapper(getattr(obj, method), obj=obj) + setattr(obj, smethod, mth) + if not mth.__doc__: + mth.__doc__ = getattr( + getattr(AbstractFileSystem, smethod, None), "__doc__", "" + ) + + +class FSSpecCoroutineCancel(Exception): + pass + + +def _dump_running_tasks( + printout=True, cancel=True, exc=FSSpecCoroutineCancel, with_task=False +): + import traceback + + tasks = [t for t in asyncio.tasks.all_tasks(loop[0]) if not t.done()] + if printout: + [task.print_stack() for task in tasks] + out = [ + { + "locals": task._coro.cr_frame.f_locals, + "file": task._coro.cr_frame.f_code.co_filename, + "firstline": task._coro.cr_frame.f_code.co_firstlineno, + "linelo": task._coro.cr_frame.f_lineno, + "stack": traceback.format_stack(task._coro.cr_frame), + "task": task if with_task else None, + } + for task in tasks + ] + if cancel: + for t in tasks: + cbs = t._callbacks + t.cancel() + asyncio.futures.Future.set_exception(t, exc) + asyncio.futures.Future.cancel(t) + [cb[0](t) for cb in cbs] # cancels any dependent concurrent.futures + try: + t._coro.throw(exc) # exits coro, unless explicitly handled + except exc: + pass + return out + + +class AbstractAsyncStreamedFile(AbstractBufferedFile): + # no read buffering, and always auto-commit + # TODO: readahead might still be useful here, but needs async version + + async def read(self, length=-1): + """ + Return data from cache, or fetch pieces as necessary + + Parameters + ---------- + length: int (-1) + Number of bytes to read; if <0, all remaining bytes. + """ + length = -1 if length is None else int(length) + if self.mode != "rb": + raise ValueError("File not in read mode") + if length < 0: + length = self.size - self.loc + if self.closed: + raise ValueError("I/O operation on closed file.") + if length == 0: + # don't even bother calling fetch + return b"" + out = await self._fetch_range(self.loc, self.loc + length) + self.loc += len(out) + return out + + async def write(self, data): + """ + Write data to buffer. + + Buffer only sent on flush() or if buffer is greater than + or equal to blocksize. + + Parameters + ---------- + data: bytes + Set of bytes to be written. + """ + if self.mode not in {"wb", "ab"}: + raise ValueError("File not in write mode") + if self.closed: + raise ValueError("I/O operation on closed file.") + if self.forced: + raise ValueError("This file has been force-flushed, can only close") + out = self.buffer.write(data) + self.loc += out + if self.buffer.tell() >= self.blocksize: + await self.flush() + return out + + async def close(self): + """Close file + + Finalizes writes, discards cache + """ + if getattr(self, "_unclosable", False): + return + if self.closed: + return + if self.mode == "rb": + self.cache = None + else: + if not self.forced: + await self.flush(force=True) + + if self.fs is not None: + self.fs.invalidate_cache(self.path) + self.fs.invalidate_cache(self.fs._parent(self.path)) + + self.closed = True + + async def flush(self, force=False): + if self.closed: + raise ValueError("Flush on closed file") + if force and self.forced: + raise ValueError("Force flush cannot be called more than once") + if force: + self.forced = True + + if self.mode not in {"wb", "ab"}: + # no-op to flush on read-mode + return + + if not force and self.buffer.tell() < self.blocksize: + # Defer write on small block + return + + if self.offset is None: + # Initialize a multipart upload + self.offset = 0 + try: + await self._initiate_upload() + except: + self.closed = True + raise + + if await self._upload_chunk(final=force) is not False: + self.offset += self.buffer.seek(0, 2) + self.buffer = io.BytesIO() + + async def __aenter__(self): + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb): + await self.close() + + async def _fetch_range(self, start, end): + raise NotImplementedError + + async def _initiate_upload(self): + pass + + async def _upload_chunk(self, final=False): + raise NotImplementedError diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/caching.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/caching.py new file mode 100644 index 0000000000000000000000000000000000000000..9b49d7f5e236c11f6ff625721d02e8ab42086a7f --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/caching.py @@ -0,0 +1,1004 @@ +from __future__ import annotations + +import collections +import functools +import logging +import math +import os +import threading +from collections import OrderedDict +from collections.abc import Callable +from concurrent.futures import Future, ThreadPoolExecutor +from itertools import groupby +from operator import itemgetter +from typing import TYPE_CHECKING, Any, ClassVar, Generic, NamedTuple, TypeVar + +if TYPE_CHECKING: + import mmap + + from typing_extensions import ParamSpec + + P = ParamSpec("P") +else: + P = TypeVar("P") + +T = TypeVar("T") + + +logger = logging.getLogger("fsspec.caching") + +Fetcher = Callable[[int, int], bytes] # Maps (start, end) to bytes +MultiFetcher = Callable[[list[int, int]], bytes] # Maps [(start, end)] to bytes + + +class BaseCache: + """Pass-though cache: doesn't keep anything, calls every time + + Acts as base class for other cachers + + Parameters + ---------- + blocksize: int + How far to read ahead in numbers of bytes + fetcher: func + Function of the form f(start, end) which gets bytes from remote as + specified + size: int + How big this file is + """ + + name: ClassVar[str] = "none" + + def __init__(self, blocksize: int, fetcher: Fetcher, size: int) -> None: + self.blocksize = blocksize + self.nblocks = 0 + self.fetcher = fetcher + self.size = size + self.hit_count = 0 + self.miss_count = 0 + # the bytes that we actually requested + self.total_requested_bytes = 0 + + def _fetch(self, start: int | None, stop: int | None) -> bytes: + if start is None: + start = 0 + if stop is None: + stop = self.size + if start >= self.size or start >= stop: + return b"" + return self.fetcher(start, stop) + + def _reset_stats(self) -> None: + """Reset hit and miss counts for a more ganular report e.g. by file.""" + self.hit_count = 0 + self.miss_count = 0 + self.total_requested_bytes = 0 + + def _log_stats(self) -> str: + """Return a formatted string of the cache statistics.""" + if self.hit_count == 0 and self.miss_count == 0: + # a cache that does nothing, this is for logs only + return "" + return f" , {self.name}: {self.hit_count} hits, {self.miss_count} misses, {self.total_requested_bytes} total requested bytes" + + def __repr__(self) -> str: + # TODO: use rich for better formatting + return f""" + <{self.__class__.__name__}: + block size : {self.blocksize} + block count : {self.nblocks} + file size : {self.size} + cache hits : {self.hit_count} + cache misses: {self.miss_count} + total requested bytes: {self.total_requested_bytes}> + """ + + +class MMapCache(BaseCache): + """memory-mapped sparse file cache + + Opens temporary file, which is filled blocks-wise when data is requested. + Ensure there is enough disc space in the temporary location. + + This cache method might only work on posix + + Parameters + ---------- + blocksize: int + How far to read ahead in numbers of bytes + fetcher: Fetcher + Function of the form f(start, end) which gets bytes from remote as + specified + size: int + How big this file is + location: str + Where to create the temporary file. If None, a temporary file is + created using tempfile.TemporaryFile(). + blocks: set[int] + Set of block numbers that have already been fetched. If None, an empty + set is created. + multi_fetcher: MultiFetcher + Function of the form f([(start, end)]) which gets bytes from remote + as specified. This function is used to fetch multiple blocks at once. + If not specified, the fetcher function is used instead. + """ + + name = "mmap" + + def __init__( + self, + blocksize: int, + fetcher: Fetcher, + size: int, + location: str | None = None, + blocks: set[int] | None = None, + multi_fetcher: MultiFetcher | None = None, + ) -> None: + super().__init__(blocksize, fetcher, size) + self.blocks = set() if blocks is None else blocks + self.location = location + self.multi_fetcher = multi_fetcher + self.cache = self._makefile() + + def _makefile(self) -> mmap.mmap | bytearray: + import mmap + import tempfile + + if self.size == 0: + return bytearray() + + # posix version + if self.location is None or not os.path.exists(self.location): + if self.location is None: + fd = tempfile.TemporaryFile() + self.blocks = set() + else: + fd = open(self.location, "wb+") + fd.seek(self.size - 1) + fd.write(b"1") + fd.flush() + else: + fd = open(self.location, "r+b") + + return mmap.mmap(fd.fileno(), self.size) + + def _fetch(self, start: int | None, end: int | None) -> bytes: + logger.debug(f"MMap cache fetching {start}-{end}") + if start is None: + start = 0 + if end is None: + end = self.size + if start >= self.size or start >= end: + return b"" + start_block = start // self.blocksize + end_block = end // self.blocksize + block_range = range(start_block, end_block + 1) + # Determine which blocks need to be fetched. This sequence is sorted by construction. + need = (i for i in block_range if i not in self.blocks) + # Count the number of blocks already cached + self.hit_count += sum(1 for i in block_range if i in self.blocks) + + ranges = [] + + # Consolidate needed blocks. + # Algorithm adapted from Python 2.x itertools documentation. + # We are grouping an enumerated sequence of blocks. By comparing when the difference + # between an ascending range (provided by enumerate) and the needed block numbers + # we can detect when the block number skips values. The key computes this difference. + # Whenever the difference changes, we know that we have previously cached block(s), + # and a new group is started. In other words, this algorithm neatly groups + # runs of consecutive block numbers so they can be fetched together. + for _, _blocks in groupby(enumerate(need), key=lambda x: x[0] - x[1]): + # Extract the blocks from the enumerated sequence + _blocks = tuple(map(itemgetter(1), _blocks)) + # Compute start of first block + sstart = _blocks[0] * self.blocksize + # Compute the end of the last block. Last block may not be full size. + send = min(_blocks[-1] * self.blocksize + self.blocksize, self.size) + + # Fetch bytes (could be multiple consecutive blocks) + self.total_requested_bytes += send - sstart + logger.debug( + f"MMap get blocks {_blocks[0]}-{_blocks[-1]} ({sstart}-{send})" + ) + ranges.append((sstart, send)) + + # Update set of cached blocks + self.blocks.update(_blocks) + # Update cache statistics with number of blocks we had to cache + self.miss_count += len(_blocks) + + if not ranges: + return self.cache[start:end] + + if self.multi_fetcher: + logger.debug(f"MMap get blocks {ranges}") + for idx, r in enumerate(self.multi_fetcher(ranges)): + sstart, send = ranges[idx] + logger.debug(f"MMap copy block ({sstart}-{send}") + self.cache[sstart:send] = r + else: + for sstart, send in ranges: + logger.debug(f"MMap get block ({sstart}-{send}") + self.cache[sstart:send] = self.fetcher(sstart, send) + + return self.cache[start:end] + + def __getstate__(self) -> dict[str, Any]: + state = self.__dict__.copy() + # Remove the unpicklable entries. + del state["cache"] + return state + + def __setstate__(self, state: dict[str, Any]) -> None: + # Restore instance attributes + self.__dict__.update(state) + self.cache = self._makefile() + + +class ReadAheadCache(BaseCache): + """Cache which reads only when we get beyond a block of data + + This is a much simpler version of BytesCache, and does not attempt to + fill holes in the cache or keep fragments alive. It is best suited to + many small reads in a sequential order (e.g., reading lines from a file). + """ + + name = "readahead" + + def __init__(self, blocksize: int, fetcher: Fetcher, size: int) -> None: + super().__init__(blocksize, fetcher, size) + self.cache = b"" + self.start = 0 + self.end = 0 + + def _fetch(self, start: int | None, end: int | None) -> bytes: + if start is None: + start = 0 + if end is None or end > self.size: + end = self.size + if start >= self.size or start >= end: + return b"" + l = end - start + if start >= self.start and end <= self.end: + # cache hit + self.hit_count += 1 + return self.cache[start - self.start : end - self.start] + elif self.start <= start < self.end: + # partial hit + self.miss_count += 1 + part = self.cache[start - self.start :] + l -= len(part) + start = self.end + else: + # miss + self.miss_count += 1 + part = b"" + end = min(self.size, end + self.blocksize) + self.total_requested_bytes += end - start + self.cache = self.fetcher(start, end) # new block replaces old + self.start = start + self.end = self.start + len(self.cache) + return part + self.cache[:l] + + +class FirstChunkCache(BaseCache): + """Caches the first block of a file only + + This may be useful for file types where the metadata is stored in the header, + but is randomly accessed. + """ + + name = "first" + + def __init__(self, blocksize: int, fetcher: Fetcher, size: int) -> None: + if blocksize > size: + # this will buffer the whole thing + blocksize = size + super().__init__(blocksize, fetcher, size) + self.cache: bytes | None = None + + def _fetch(self, start: int | None, end: int | None) -> bytes: + start = start or 0 + if start > self.size: + logger.debug("FirstChunkCache: requested start > file size") + return b"" + + end = min(end, self.size) + + if start < self.blocksize: + if self.cache is None: + self.miss_count += 1 + if end > self.blocksize: + self.total_requested_bytes += end + data = self.fetcher(0, end) + self.cache = data[: self.blocksize] + return data[start:] + self.cache = self.fetcher(0, self.blocksize) + self.total_requested_bytes += self.blocksize + part = self.cache[start:end] + if end > self.blocksize: + self.total_requested_bytes += end - self.blocksize + part += self.fetcher(self.blocksize, end) + self.hit_count += 1 + return part + else: + self.miss_count += 1 + self.total_requested_bytes += end - start + return self.fetcher(start, end) + + +class BlockCache(BaseCache): + """ + Cache holding memory as a set of blocks. + + Requests are only ever made ``blocksize`` at a time, and are + stored in an LRU cache. The least recently accessed block is + discarded when more than ``maxblocks`` are stored. + + Parameters + ---------- + blocksize : int + The number of bytes to store in each block. + Requests are only ever made for ``blocksize``, so this + should balance the overhead of making a request against + the granularity of the blocks. + fetcher : Callable + size : int + The total size of the file being cached. + maxblocks : int + The maximum number of blocks to cache for. The maximum memory + use for this cache is then ``blocksize * maxblocks``. + """ + + name = "blockcache" + + def __init__( + self, blocksize: int, fetcher: Fetcher, size: int, maxblocks: int = 32 + ) -> None: + super().__init__(blocksize, fetcher, size) + self.nblocks = math.ceil(size / blocksize) + self.maxblocks = maxblocks + self._fetch_block_cached = functools.lru_cache(maxblocks)(self._fetch_block) + + def cache_info(self): + """ + The statistics on the block cache. + + Returns + ------- + NamedTuple + Returned directly from the LRU Cache used internally. + """ + return self._fetch_block_cached.cache_info() + + def __getstate__(self) -> dict[str, Any]: + state = self.__dict__ + del state["_fetch_block_cached"] + return state + + def __setstate__(self, state: dict[str, Any]) -> None: + self.__dict__.update(state) + self._fetch_block_cached = functools.lru_cache(state["maxblocks"])( + self._fetch_block + ) + + def _fetch(self, start: int | None, end: int | None) -> bytes: + if start is None: + start = 0 + if end is None: + end = self.size + if start >= self.size or start >= end: + return b"" + + return self._read_cache( + start, end, start // self.blocksize, (end - 1) // self.blocksize + ) + + def _fetch_block(self, block_number: int) -> bytes: + """ + Fetch the block of data for `block_number`. + """ + if block_number > self.nblocks: + raise ValueError( + f"'block_number={block_number}' is greater than " + f"the number of blocks ({self.nblocks})" + ) + + start = block_number * self.blocksize + end = start + self.blocksize + self.total_requested_bytes += end - start + self.miss_count += 1 + logger.info("BlockCache fetching block %d", block_number) + block_contents = super()._fetch(start, end) + return block_contents + + def _read_cache( + self, start: int, end: int, start_block_number: int, end_block_number: int + ) -> bytes: + """ + Read from our block cache. + + Parameters + ---------- + start, end : int + The start and end byte positions. + start_block_number, end_block_number : int + The start and end block numbers. + """ + start_pos = start % self.blocksize + end_pos = end % self.blocksize + if end_pos == 0: + end_pos = self.blocksize + + self.hit_count += 1 + if start_block_number == end_block_number: + block: bytes = self._fetch_block_cached(start_block_number) + return block[start_pos:end_pos] + + else: + # read from the initial + out = [self._fetch_block_cached(start_block_number)[start_pos:]] + + # intermediate blocks + # Note: it'd be nice to combine these into one big request. However + # that doesn't play nicely with our LRU cache. + out.extend( + map( + self._fetch_block_cached, + range(start_block_number + 1, end_block_number), + ) + ) + + # final block + out.append(self._fetch_block_cached(end_block_number)[:end_pos]) + + return b"".join(out) + + +class BytesCache(BaseCache): + """Cache which holds data in a in-memory bytes object + + Implements read-ahead by the block size, for semi-random reads progressing + through the file. + + Parameters + ---------- + trim: bool + As we read more data, whether to discard the start of the buffer when + we are more than a blocksize ahead of it. + """ + + name: ClassVar[str] = "bytes" + + def __init__( + self, blocksize: int, fetcher: Fetcher, size: int, trim: bool = True + ) -> None: + super().__init__(blocksize, fetcher, size) + self.cache = b"" + self.start: int | None = None + self.end: int | None = None + self.trim = trim + + def _fetch(self, start: int | None, end: int | None) -> bytes: + # TODO: only set start/end after fetch, in case it fails? + # is this where retry logic might go? + if start is None: + start = 0 + if end is None: + end = self.size + if start >= self.size or start >= end: + return b"" + if ( + self.start is not None + and start >= self.start + and self.end is not None + and end < self.end + ): + # cache hit: we have all the required data + offset = start - self.start + self.hit_count += 1 + return self.cache[offset : offset + end - start] + + if self.blocksize: + bend = min(self.size, end + self.blocksize) + else: + bend = end + + if bend == start or start > self.size: + return b"" + + if (self.start is None or start < self.start) and ( + self.end is None or end > self.end + ): + # First read, or extending both before and after + self.total_requested_bytes += bend - start + self.miss_count += 1 + self.cache = self.fetcher(start, bend) + self.start = start + else: + assert self.start is not None + assert self.end is not None + self.miss_count += 1 + + if start < self.start: + if self.end is None or self.end - end > self.blocksize: + self.total_requested_bytes += bend - start + self.cache = self.fetcher(start, bend) + self.start = start + else: + self.total_requested_bytes += self.start - start + new = self.fetcher(start, self.start) + self.start = start + self.cache = new + self.cache + elif self.end is not None and bend > self.end: + if self.end > self.size: + pass + elif end - self.end > self.blocksize: + self.total_requested_bytes += bend - start + self.cache = self.fetcher(start, bend) + self.start = start + else: + self.total_requested_bytes += bend - self.end + new = self.fetcher(self.end, bend) + self.cache = self.cache + new + + self.end = self.start + len(self.cache) + offset = start - self.start + out = self.cache[offset : offset + end - start] + if self.trim: + num = (self.end - self.start) // (self.blocksize + 1) + if num > 1: + self.start += self.blocksize * num + self.cache = self.cache[self.blocksize * num :] + return out + + def __len__(self) -> int: + return len(self.cache) + + +class AllBytes(BaseCache): + """Cache entire contents of the file""" + + name: ClassVar[str] = "all" + + def __init__( + self, + blocksize: int | None = None, + fetcher: Fetcher | None = None, + size: int | None = None, + data: bytes | None = None, + ) -> None: + super().__init__(blocksize, fetcher, size) # type: ignore[arg-type] + if data is None: + self.miss_count += 1 + self.total_requested_bytes += self.size + data = self.fetcher(0, self.size) + self.data = data + + def _fetch(self, start: int | None, stop: int | None) -> bytes: + self.hit_count += 1 + return self.data[start:stop] + + +class KnownPartsOfAFile(BaseCache): + """ + Cache holding known file parts. + + Parameters + ---------- + blocksize: int + How far to read ahead in numbers of bytes + fetcher: func + Function of the form f(start, end) which gets bytes from remote as + specified + size: int + How big this file is + data: dict + A dictionary mapping explicit `(start, stop)` file-offset tuples + with known bytes. + strict: bool, default True + Whether to fetch reads that go beyond a known byte-range boundary. + If `False`, any read that ends outside a known part will be zero + padded. Note that zero padding will not be used for reads that + begin outside a known byte-range. + """ + + name: ClassVar[str] = "parts" + + def __init__( + self, + blocksize: int, + fetcher: Fetcher, + size: int, + data: dict[tuple[int, int], bytes] | None = None, + strict: bool = False, + **_: Any, + ): + super().__init__(blocksize, fetcher, size) + self.strict = strict + + # simple consolidation of contiguous blocks + if data: + old_offsets = sorted(data.keys()) + offsets = [old_offsets[0]] + blocks = [data.pop(old_offsets[0])] + for start, stop in old_offsets[1:]: + start0, stop0 = offsets[-1] + if start == stop0: + offsets[-1] = (start0, stop) + blocks[-1] += data.pop((start, stop)) + else: + offsets.append((start, stop)) + blocks.append(data.pop((start, stop))) + + self.data = dict(zip(offsets, blocks)) + else: + self.data = {} + + @property + def size(self): + return sum(_[1] - _[0] for _ in self.data) + + @size.setter + def size(self, value): + pass + + @property + def nblocks(self): + return len(self.data) + + @nblocks.setter + def nblocks(self, value): + pass + + def _fetch(self, start: int | None, stop: int | None) -> bytes: + logger.debug("Known parts request %s %s", start, stop) + if start is None: + start = 0 + if stop is None: + stop = self.size + self.total_requested_bytes += stop - start + out = b"" + started = False + loc_old = 0 + for loc0, loc1 in sorted(self.data): + if (loc0 <= start < loc1) and (loc0 <= stop <= loc1): + # entirely within the block + off = start - loc0 + self.hit_count += 1 + return self.data[(loc0, loc1)][off : off + stop - start] + if stop <= loc0: + break + if started and loc0 > loc_old: + # a gap where we need data + self.miss_count += 1 + if self.strict: + raise ValueError + out += b"\x00" * (loc0 - loc_old) + if loc0 <= start < loc1: + # found the start + self.hit_count += 1 + off = start - loc0 + out = self.data[(loc0, loc1)][off : off + stop - start] + started = True + elif start < loc0 and stop > loc1: + # the whole block + self.hit_count += 1 + out += self.data[(loc0, loc1)] + elif loc0 <= stop <= loc1: + # end block + self.hit_count += 1 + out = out + self.data[(loc0, loc1)][: stop - loc0] + return out + loc_old = loc1 + self.miss_count += 1 + if started and not self.strict: + out = out + b"\x00" * (stop - loc_old) + return out + raise ValueError + + +class UpdatableLRU(Generic[P, T]): + """ + Custom implementation of LRU cache that allows updating keys + + Used by BackgroudBlockCache + """ + + class CacheInfo(NamedTuple): + hits: int + misses: int + maxsize: int + currsize: int + + def __init__(self, func: Callable[P, T], max_size: int = 128) -> None: + self._cache: OrderedDict[Any, T] = collections.OrderedDict() + self._func = func + self._max_size = max_size + self._hits = 0 + self._misses = 0 + self._lock = threading.Lock() + + def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T: + if kwargs: + raise TypeError(f"Got unexpected keyword argument {kwargs.keys()}") + with self._lock: + if args in self._cache: + self._cache.move_to_end(args) + self._hits += 1 + return self._cache[args] + + result = self._func(*args, **kwargs) + + with self._lock: + self._cache[args] = result + self._misses += 1 + if len(self._cache) > self._max_size: + self._cache.popitem(last=False) + + return result + + def is_key_cached(self, *args: Any) -> bool: + with self._lock: + return args in self._cache + + def add_key(self, result: T, *args: Any) -> None: + with self._lock: + self._cache[args] = result + if len(self._cache) > self._max_size: + self._cache.popitem(last=False) + + def cache_info(self) -> UpdatableLRU.CacheInfo: + with self._lock: + return self.CacheInfo( + maxsize=self._max_size, + currsize=len(self._cache), + hits=self._hits, + misses=self._misses, + ) + + +class BackgroundBlockCache(BaseCache): + """ + Cache holding memory as a set of blocks with pre-loading of + the next block in the background. + + Requests are only ever made ``blocksize`` at a time, and are + stored in an LRU cache. The least recently accessed block is + discarded when more than ``maxblocks`` are stored. If the + next block is not in cache, it is loaded in a separate thread + in non-blocking way. + + Parameters + ---------- + blocksize : int + The number of bytes to store in each block. + Requests are only ever made for ``blocksize``, so this + should balance the overhead of making a request against + the granularity of the blocks. + fetcher : Callable + size : int + The total size of the file being cached. + maxblocks : int + The maximum number of blocks to cache for. The maximum memory + use for this cache is then ``blocksize * maxblocks``. + """ + + name: ClassVar[str] = "background" + + def __init__( + self, blocksize: int, fetcher: Fetcher, size: int, maxblocks: int = 32 + ) -> None: + super().__init__(blocksize, fetcher, size) + self.nblocks = math.ceil(size / blocksize) + self.maxblocks = maxblocks + self._fetch_block_cached = UpdatableLRU(self._fetch_block, maxblocks) + + self._thread_executor = ThreadPoolExecutor(max_workers=1) + self._fetch_future_block_number: int | None = None + self._fetch_future: Future[bytes] | None = None + self._fetch_future_lock = threading.Lock() + + def cache_info(self) -> UpdatableLRU.CacheInfo: + """ + The statistics on the block cache. + + Returns + ------- + NamedTuple + Returned directly from the LRU Cache used internally. + """ + return self._fetch_block_cached.cache_info() + + def __getstate__(self) -> dict[str, Any]: + state = self.__dict__ + del state["_fetch_block_cached"] + del state["_thread_executor"] + del state["_fetch_future_block_number"] + del state["_fetch_future"] + del state["_fetch_future_lock"] + return state + + def __setstate__(self, state) -> None: + self.__dict__.update(state) + self._fetch_block_cached = UpdatableLRU(self._fetch_block, state["maxblocks"]) + self._thread_executor = ThreadPoolExecutor(max_workers=1) + self._fetch_future_block_number = None + self._fetch_future = None + self._fetch_future_lock = threading.Lock() + + def _fetch(self, start: int | None, end: int | None) -> bytes: + if start is None: + start = 0 + if end is None: + end = self.size + if start >= self.size or start >= end: + return b"" + + # byte position -> block numbers + start_block_number = start // self.blocksize + end_block_number = end // self.blocksize + + fetch_future_block_number = None + fetch_future = None + with self._fetch_future_lock: + # Background thread is running. Check we we can or must join it. + if self._fetch_future is not None: + assert self._fetch_future_block_number is not None + if self._fetch_future.done(): + logger.info("BlockCache joined background fetch without waiting.") + self._fetch_block_cached.add_key( + self._fetch_future.result(), self._fetch_future_block_number + ) + # Cleanup the fetch variables. Done with fetching the block. + self._fetch_future_block_number = None + self._fetch_future = None + else: + # Must join if we need the block for the current fetch + must_join = bool( + start_block_number + <= self._fetch_future_block_number + <= end_block_number + ) + if must_join: + # Copy to the local variables to release lock + # before waiting for result + fetch_future_block_number = self._fetch_future_block_number + fetch_future = self._fetch_future + + # Cleanup the fetch variables. Have a local copy. + self._fetch_future_block_number = None + self._fetch_future = None + + # Need to wait for the future for the current read + if fetch_future is not None: + logger.info("BlockCache waiting for background fetch.") + # Wait until result and put it in cache + self._fetch_block_cached.add_key( + fetch_future.result(), fetch_future_block_number + ) + + # these are cached, so safe to do multiple calls for the same start and end. + for block_number in range(start_block_number, end_block_number + 1): + self._fetch_block_cached(block_number) + + # fetch next block in the background if nothing is running in the background, + # the block is within file and it is not already cached + end_block_plus_1 = end_block_number + 1 + with self._fetch_future_lock: + if ( + self._fetch_future is None + and end_block_plus_1 <= self.nblocks + and not self._fetch_block_cached.is_key_cached(end_block_plus_1) + ): + self._fetch_future_block_number = end_block_plus_1 + self._fetch_future = self._thread_executor.submit( + self._fetch_block, end_block_plus_1, "async" + ) + + return self._read_cache( + start, + end, + start_block_number=start_block_number, + end_block_number=end_block_number, + ) + + def _fetch_block(self, block_number: int, log_info: str = "sync") -> bytes: + """ + Fetch the block of data for `block_number`. + """ + if block_number > self.nblocks: + raise ValueError( + f"'block_number={block_number}' is greater than " + f"the number of blocks ({self.nblocks})" + ) + + start = block_number * self.blocksize + end = start + self.blocksize + logger.info("BlockCache fetching block (%s) %d", log_info, block_number) + self.total_requested_bytes += end - start + self.miss_count += 1 + block_contents = super()._fetch(start, end) + return block_contents + + def _read_cache( + self, start: int, end: int, start_block_number: int, end_block_number: int + ) -> bytes: + """ + Read from our block cache. + + Parameters + ---------- + start, end : int + The start and end byte positions. + start_block_number, end_block_number : int + The start and end block numbers. + """ + start_pos = start % self.blocksize + end_pos = end % self.blocksize + + # kind of pointless to count this as a hit, but it is + self.hit_count += 1 + + if start_block_number == end_block_number: + block = self._fetch_block_cached(start_block_number) + return block[start_pos:end_pos] + + else: + # read from the initial + out = [self._fetch_block_cached(start_block_number)[start_pos:]] + + # intermediate blocks + # Note: it'd be nice to combine these into one big request. However + # that doesn't play nicely with our LRU cache. + out.extend( + map( + self._fetch_block_cached, + range(start_block_number + 1, end_block_number), + ) + ) + + # final block + out.append(self._fetch_block_cached(end_block_number)[:end_pos]) + + return b"".join(out) + + +caches: dict[str | None, type[BaseCache]] = { + # one custom case + None: BaseCache, +} + + +def register_cache(cls: type[BaseCache], clobber: bool = False) -> None: + """'Register' cache implementation. + + Parameters + ---------- + clobber: bool, optional + If set to True (default is False) - allow to overwrite existing + entry. + + Raises + ------ + ValueError + """ + name = cls.name + if not clobber and name in caches: + raise ValueError(f"Cache with name {name!r} is already known: {caches[name]}") + caches[name] = cls + + +for c in ( + BaseCache, + MMapCache, + BytesCache, + ReadAheadCache, + BlockCache, + FirstChunkCache, + AllBytes, + KnownPartsOfAFile, + BackgroundBlockCache, +): + register_cache(c) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/callbacks.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/callbacks.py new file mode 100644 index 0000000000000000000000000000000000000000..7ca99ca6ac3cd69b28bcd1550f6550e8e648c5fe --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/callbacks.py @@ -0,0 +1,324 @@ +from functools import wraps + + +class Callback: + """ + Base class and interface for callback mechanism + + This class can be used directly for monitoring file transfers by + providing ``callback=Callback(hooks=...)`` (see the ``hooks`` argument, + below), or subclassed for more specialised behaviour. + + Parameters + ---------- + size: int (optional) + Nominal quantity for the value that corresponds to a complete + transfer, e.g., total number of tiles or total number of + bytes + value: int (0) + Starting internal counter value + hooks: dict or None + A dict of named functions to be called on each update. The signature + of these must be ``f(size, value, **kwargs)`` + """ + + def __init__(self, size=None, value=0, hooks=None, **kwargs): + self.size = size + self.value = value + self.hooks = hooks or {} + self.kw = kwargs + + def __enter__(self): + return self + + def __exit__(self, *exc_args): + self.close() + + def close(self): + """Close callback.""" + + def branched(self, path_1, path_2, **kwargs): + """ + Return callback for child transfers + + If this callback is operating at a higher level, e.g., put, which may + trigger transfers that can also be monitored. The function returns a callback + that has to be passed to the child method, e.g., put_file, + as `callback=` argument. + + The implementation uses `callback.branch` for compatibility. + When implementing callbacks, it is recommended to override this function instead + of `branch` and avoid calling `super().branched(...)`. + + Prefer using this function over `branch`. + + Parameters + ---------- + path_1: str + Child's source path + path_2: str + Child's destination path + **kwargs: + Arbitrary keyword arguments + + Returns + ------- + callback: Callback + A callback instance to be passed to the child method + """ + self.branch(path_1, path_2, kwargs) + # mutate kwargs so that we can force the caller to pass "callback=" explicitly + return kwargs.pop("callback", DEFAULT_CALLBACK) + + def branch_coro(self, fn): + """ + Wraps a coroutine, and pass a new child callback to it. + """ + + @wraps(fn) + async def func(path1, path2: str, **kwargs): + with self.branched(path1, path2, **kwargs) as child: + return await fn(path1, path2, callback=child, **kwargs) + + return func + + def set_size(self, size): + """ + Set the internal maximum size attribute + + Usually called if not initially set at instantiation. Note that this + triggers a ``call()``. + + Parameters + ---------- + size: int + """ + self.size = size + self.call() + + def absolute_update(self, value): + """ + Set the internal value state + + Triggers ``call()`` + + Parameters + ---------- + value: int + """ + self.value = value + self.call() + + def relative_update(self, inc=1): + """ + Delta increment the internal counter + + Triggers ``call()`` + + Parameters + ---------- + inc: int + """ + self.value += inc + self.call() + + def call(self, hook_name=None, **kwargs): + """ + Execute hook(s) with current state + + Each function is passed the internal size and current value + + Parameters + ---------- + hook_name: str or None + If given, execute on this hook + kwargs: passed on to (all) hook(s) + """ + if not self.hooks: + return + kw = self.kw.copy() + kw.update(kwargs) + if hook_name: + if hook_name not in self.hooks: + return + return self.hooks[hook_name](self.size, self.value, **kw) + for hook in self.hooks.values() or []: + hook(self.size, self.value, **kw) + + def wrap(self, iterable): + """ + Wrap an iterable to call ``relative_update`` on each iterations + + Parameters + ---------- + iterable: Iterable + The iterable that is being wrapped + """ + for item in iterable: + self.relative_update() + yield item + + def branch(self, path_1, path_2, kwargs): + """ + Set callbacks for child transfers + + If this callback is operating at a higher level, e.g., put, which may + trigger transfers that can also be monitored. The passed kwargs are + to be *mutated* to add ``callback=``, if this class supports branching + to children. + + Parameters + ---------- + path_1: str + Child's source path + path_2: str + Child's destination path + kwargs: dict + arguments passed to child method, e.g., put_file. + + Returns + ------- + + """ + return None + + def no_op(self, *_, **__): + pass + + def __getattr__(self, item): + """ + If undefined methods are called on this class, nothing happens + """ + return self.no_op + + @classmethod + def as_callback(cls, maybe_callback=None): + """Transform callback=... into Callback instance + + For the special value of ``None``, return the global instance of + ``NoOpCallback``. This is an alternative to including + ``callback=DEFAULT_CALLBACK`` directly in a method signature. + """ + if maybe_callback is None: + return DEFAULT_CALLBACK + return maybe_callback + + +class NoOpCallback(Callback): + """ + This implementation of Callback does exactly nothing + """ + + def call(self, *args, **kwargs): + return None + + +class DotPrinterCallback(Callback): + """ + Simple example Callback implementation + + Almost identical to Callback with a hook that prints a char; here we + demonstrate how the outer layer may print "#" and the inner layer "." + """ + + def __init__(self, chr_to_print="#", **kwargs): + self.chr = chr_to_print + super().__init__(**kwargs) + + def branch(self, path_1, path_2, kwargs): + """Mutate kwargs to add new instance with different print char""" + kwargs["callback"] = DotPrinterCallback(".") + + def call(self, **kwargs): + """Just outputs a character""" + print(self.chr, end="") + + +class TqdmCallback(Callback): + """ + A callback to display a progress bar using tqdm + + Parameters + ---------- + tqdm_kwargs : dict, (optional) + Any argument accepted by the tqdm constructor. + See the `tqdm doc `_. + Will be forwarded to `tqdm_cls`. + tqdm_cls: (optional) + subclass of `tqdm.tqdm`. If not passed, it will default to `tqdm.tqdm`. + + Examples + -------- + >>> import fsspec + >>> from fsspec.callbacks import TqdmCallback + >>> fs = fsspec.filesystem("memory") + >>> path2distant_data = "/your-path" + >>> fs.upload( + ".", + path2distant_data, + recursive=True, + callback=TqdmCallback(), + ) + + You can forward args to tqdm using the ``tqdm_kwargs`` parameter. + + >>> fs.upload( + ".", + path2distant_data, + recursive=True, + callback=TqdmCallback(tqdm_kwargs={"desc": "Your tqdm description"}), + ) + + You can also customize the progress bar by passing a subclass of `tqdm`. + + .. code-block:: python + + class TqdmFormat(tqdm): + '''Provides a `total_time` format parameter''' + @property + def format_dict(self): + d = super().format_dict + total_time = d["elapsed"] * (d["total"] or 0) / max(d["n"], 1) + d.update(total_time=self.format_interval(total_time) + " in total") + return d + + >>> with TqdmCallback( + tqdm_kwargs={ + "desc": "desc", + "bar_format": "{total_time}: {percentage:.0f}%|{bar}{r_bar}", + }, + tqdm_cls=TqdmFormat, + ) as callback: + fs.upload(".", path2distant_data, recursive=True, callback=callback) + """ + + def __init__(self, tqdm_kwargs=None, *args, **kwargs): + try: + from tqdm import tqdm + + except ImportError as exce: + raise ImportError( + "Using TqdmCallback requires tqdm to be installed" + ) from exce + + self._tqdm_cls = kwargs.pop("tqdm_cls", tqdm) + self._tqdm_kwargs = tqdm_kwargs or {} + self.tqdm = None + super().__init__(*args, **kwargs) + + def call(self, *args, **kwargs): + if self.tqdm is None: + self.tqdm = self._tqdm_cls(total=self.size, **self._tqdm_kwargs) + self.tqdm.total = self.size + self.tqdm.update(self.value - self.tqdm.n) + + def close(self): + if self.tqdm is not None: + self.tqdm.close() + self.tqdm = None + + def __del__(self): + return self.close() + + +DEFAULT_CALLBACK = _DEFAULT_CALLBACK = NoOpCallback() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/compression.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/compression.py new file mode 100644 index 0000000000000000000000000000000000000000..11c2e3d3f142d95186663fa5a747911e66832266 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/compression.py @@ -0,0 +1,185 @@ +"""Helper functions for a standard streaming compression API""" + +import sys +from zipfile import ZipFile + +import fsspec.utils +from fsspec.spec import AbstractBufferedFile + + +def noop_file(file, mode, **kwargs): + return file + + +# TODO: files should also be available as contexts +# should be functions of the form func(infile, mode=, **kwargs) -> file-like +compr = {None: noop_file} + + +def register_compression(name, callback, extensions, force=False): + """Register an "inferable" file compression type. + + Registers transparent file compression type for use with fsspec.open. + Compression can be specified by name in open, or "infer"-ed for any files + ending with the given extensions. + + Args: + name: (str) The compression type name. Eg. "gzip". + callback: A callable of form (infile, mode, **kwargs) -> file-like. + Accepts an input file-like object, the target mode and kwargs. + Returns a wrapped file-like object. + extensions: (str, Iterable[str]) A file extension, or list of file + extensions for which to infer this compression scheme. Eg. "gz". + force: (bool) Force re-registration of compression type or extensions. + + Raises: + ValueError: If name or extensions already registered, and not force. + + """ + if isinstance(extensions, str): + extensions = [extensions] + + # Validate registration + if name in compr and not force: + raise ValueError(f"Duplicate compression registration: {name}") + + for ext in extensions: + if ext in fsspec.utils.compressions and not force: + raise ValueError(f"Duplicate compression file extension: {ext} ({name})") + + compr[name] = callback + + for ext in extensions: + fsspec.utils.compressions[ext] = name + + +def unzip(infile, mode="rb", filename=None, **kwargs): + if "r" not in mode: + filename = filename or "file" + z = ZipFile(infile, mode="w", **kwargs) + fo = z.open(filename, mode="w") + fo.close = lambda closer=fo.close: closer() or z.close() + return fo + z = ZipFile(infile) + if filename is None: + filename = z.namelist()[0] + return z.open(filename, mode="r", **kwargs) + + +register_compression("zip", unzip, "zip") + +try: + from bz2 import BZ2File +except ImportError: + pass +else: + register_compression("bz2", BZ2File, "bz2") + +try: # pragma: no cover + from isal import igzip + + def isal(infile, mode="rb", **kwargs): + return igzip.IGzipFile(fileobj=infile, mode=mode, **kwargs) + + register_compression("gzip", isal, "gz") +except ImportError: + from gzip import GzipFile + + register_compression( + "gzip", lambda f, **kwargs: GzipFile(fileobj=f, **kwargs), "gz" + ) + +try: + from lzma import LZMAFile + + register_compression("lzma", LZMAFile, "lzma") + register_compression("xz", LZMAFile, "xz") +except ImportError: + pass + +try: + import lzmaffi + + register_compression("lzma", lzmaffi.LZMAFile, "lzma", force=True) + register_compression("xz", lzmaffi.LZMAFile, "xz", force=True) +except ImportError: + pass + + +class SnappyFile(AbstractBufferedFile): + def __init__(self, infile, mode, **kwargs): + import snappy + + super().__init__( + fs=None, path="snappy", mode=mode.strip("b") + "b", size=999999999, **kwargs + ) + self.infile = infile + if "r" in mode: + self.codec = snappy.StreamDecompressor() + else: + self.codec = snappy.StreamCompressor() + + def _upload_chunk(self, final=False): + self.buffer.seek(0) + out = self.codec.add_chunk(self.buffer.read()) + self.infile.write(out) + return True + + def seek(self, loc, whence=0): + raise NotImplementedError("SnappyFile is not seekable") + + def seekable(self): + return False + + def _fetch_range(self, start, end): + """Get the specified set of bytes from remote""" + data = self.infile.read(end - start) + return self.codec.decompress(data) + + +try: + import snappy + + snappy.compress(b"") + # Snappy may use the .sz file extension, but this is not part of the + # standard implementation. + register_compression("snappy", SnappyFile, []) + +except (ImportError, NameError, AttributeError): + pass + +try: + import lz4.frame + + register_compression("lz4", lz4.frame.open, "lz4") +except ImportError: + pass + +try: + if sys.version_info >= (3, 14): + from compression import zstd + else: + from backports import zstd + + register_compression("zstd", zstd.ZstdFile, "zst") +except ImportError: + try: + import zstandard as zstd + + def zstandard_file(infile, mode="rb"): + if "r" in mode: + cctx = zstd.ZstdDecompressor() + return cctx.stream_reader(infile) + else: + cctx = zstd.ZstdCompressor(level=10) + return cctx.stream_writer(infile) + + register_compression("zstd", zstandard_file, "zst") + except ImportError: + pass + pass + + +def available_compressions(): + """Return a list of the implemented compressions.""" + return list(compr) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/config.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/config.py new file mode 100644 index 0000000000000000000000000000000000000000..76d9af14aaf7df47c4551c169f27b05abf9c269e --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/config.py @@ -0,0 +1,131 @@ +from __future__ import annotations + +import configparser +import json +import os +import warnings +from typing import Any + +conf: dict[str, dict[str, Any]] = {} +default_conf_dir = os.path.join(os.path.expanduser("~"), ".config/fsspec") +conf_dir = os.environ.get("FSSPEC_CONFIG_DIR", default_conf_dir) + + +def set_conf_env(conf_dict, envdict=os.environ): + """Set config values from environment variables + + Looks for variables of the form ``FSSPEC_`` and + ``FSSPEC__``. For ``FSSPEC_`` the value is parsed + as a json dictionary and used to ``update`` the config of the + corresponding protocol. For ``FSSPEC__`` there is no + attempt to convert the string value, but the kwarg keys will be lower-cased. + + The ``FSSPEC__`` variables are applied after the + ``FSSPEC_`` ones. + + Parameters + ---------- + conf_dict : dict(str, dict) + This dict will be mutated + envdict : dict-like(str, str) + Source for the values - usually the real environment + """ + kwarg_keys = [] + for key in envdict: + if key.startswith("FSSPEC_") and len(key) > 7 and key[7] != "_": + if key.count("_") > 1: + kwarg_keys.append(key) + continue + try: + value = json.loads(envdict[key]) + except json.decoder.JSONDecodeError as ex: + warnings.warn( + f"Ignoring environment variable {key} due to a parse failure: {ex}" + ) + else: + if isinstance(value, dict): + _, proto = key.split("_", 1) + conf_dict.setdefault(proto.lower(), {}).update(value) + else: + warnings.warn( + f"Ignoring environment variable {key} due to not being a dict:" + f" {type(value)}" + ) + elif key.startswith("FSSPEC"): + warnings.warn( + f"Ignoring environment variable {key} due to having an unexpected name" + ) + + for key in kwarg_keys: + _, proto, kwarg = key.split("_", 2) + conf_dict.setdefault(proto.lower(), {})[kwarg.lower()] = envdict[key] + + +def set_conf_files(cdir, conf_dict): + """Set config values from files + + Scans for INI and JSON files in the given dictionary, and uses their + contents to set the config. In case of repeated values, later values + win. + + In the case of INI files, all values are strings, and these will not + be converted. + + Parameters + ---------- + cdir : str + Directory to search + conf_dict : dict(str, dict) + This dict will be mutated + """ + if not os.path.isdir(cdir): + return + allfiles = sorted(os.listdir(cdir)) + for fn in allfiles: + if fn.endswith(".ini"): + ini = configparser.ConfigParser() + ini.read(os.path.join(cdir, fn)) + for key in ini: + if key == "DEFAULT": + continue + conf_dict.setdefault(key, {}).update(dict(ini[key])) + if fn.endswith(".json"): + with open(os.path.join(cdir, fn)) as f: + js = json.load(f) + for key in js: + conf_dict.setdefault(key, {}).update(dict(js[key])) + + +def apply_config(cls, kwargs, conf_dict=None): + """Supply default values for kwargs when instantiating class + + Augments the passed kwargs, by finding entries in the config dict + which match the classes ``.protocol`` attribute (one or more str) + + Parameters + ---------- + cls : file system implementation + kwargs : dict + conf_dict : dict of dict + Typically this is the global configuration + + Returns + ------- + dict : the modified set of kwargs + """ + if conf_dict is None: + conf_dict = conf + protos = cls.protocol if isinstance(cls.protocol, (tuple, list)) else [cls.protocol] + kw = {} + for proto in protos: + # default kwargs from the current state of the config + if proto in conf_dict: + kw.update(conf_dict[proto]) + # explicit kwargs always win + kw.update(**kwargs) + kwargs = kw + return kwargs + + +set_conf_files(conf_dir, conf) +set_conf_env(conf) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/conftest.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/conftest.py new file mode 100644 index 0000000000000000000000000000000000000000..f05eb5c30d42b0c1c5cc432f9c217d8f0e01f412 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/conftest.py @@ -0,0 +1,125 @@ +import os +import shutil +import subprocess +import sys +import time +from collections import deque +from collections.abc import Generator, Sequence + +import pytest + +import fsspec + + +@pytest.fixture() +def m(): + """ + Fixture providing a memory filesystem. + """ + m = fsspec.filesystem("memory") + m.store.clear() + m.pseudo_dirs.clear() + m.pseudo_dirs.append("") + try: + yield m + finally: + m.store.clear() + m.pseudo_dirs.clear() + m.pseudo_dirs.append("") + + +class InstanceCacheInspector: + """ + Helper class to inspect instance caches of filesystem classes in tests. + """ + + def clear(self) -> None: + """ + Clear instance caches of all currently imported filesystem classes. + """ + classes = deque([fsspec.spec.AbstractFileSystem]) + while classes: + cls = classes.popleft() + cls.clear_instance_cache() + classes.extend(cls.__subclasses__()) + + def gather_counts(self, *, omit_zero: bool = True) -> dict[str, int]: + """ + Gather counts of filesystem instances in the instance caches + of all currently imported filesystem classes. + + Parameters + ---------- + omit_zero: + Whether to omit instance types with no cached instances. + """ + out: dict[str, int] = {} + classes = deque([fsspec.spec.AbstractFileSystem]) + while classes: + cls = classes.popleft() + count = len(cls._cache) # there is no public interface for the cache + # note: skip intermediate AbstractFileSystem subclasses + # if they proxy the protocol attribute via a property. + if isinstance(cls.protocol, (Sequence, str)): + key = cls.protocol if isinstance(cls.protocol, str) else cls.protocol[0] + if count or not omit_zero: + out[key] = count + classes.extend(cls.__subclasses__()) + return out + + +@pytest.fixture(scope="function", autouse=True) +def instance_caches() -> Generator[InstanceCacheInspector, None, None]: + """ + Fixture to ensure empty filesystem instance caches before and after a test. + + Used by default for all tests. + Clears caches of all imported filesystem classes. + Can be used to write test assertions about instance caches. + + Usage: + + def test_something(instance_caches): + # Test code here + fsspec.open("file://abc") + fsspec.open("memory://foo/bar") + + # Test assertion + assert instance_caches.gather_counts() == {"file": 1, "memory": 1} + + Returns + ------- + instance_caches: An instance cache inspector for clearing and inspecting caches. + """ + ic = InstanceCacheInspector() + + ic.clear() + try: + yield ic + finally: + ic.clear() + + +@pytest.fixture(scope="function") +def ftp_writable(tmpdir): + """ + Fixture providing a writable FTP filesystem. + """ + pytest.importorskip("pyftpdlib") + + d = str(tmpdir) + with open(os.path.join(d, "out"), "wb") as f: + f.write(b"hello" * 10000) + P = subprocess.Popen( + [sys.executable, "-m", "pyftpdlib", "-d", d, "-u", "user", "-P", "pass", "-w"] + ) + try: + time.sleep(1) + yield "localhost", 2121, "user", "pass" + finally: + P.terminate() + P.wait() + try: + shutil.rmtree(tmpdir) + except Exception: + pass diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/core.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/core.py new file mode 100644 index 0000000000000000000000000000000000000000..5876bfcefc176b3b3aed0e16b54fa3809a5a0eee --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/core.py @@ -0,0 +1,760 @@ +from __future__ import annotations + +import io +import logging +import os +import re +from glob import has_magic +from pathlib import Path + +# for backwards compat, we export cache things from here too +from fsspec.caching import ( # noqa: F401 + BaseCache, + BlockCache, + BytesCache, + MMapCache, + ReadAheadCache, + caches, +) +from fsspec.compression import compr +from fsspec.config import conf +from fsspec.registry import available_protocols, filesystem, get_filesystem_class +from fsspec.utils import ( + _unstrip_protocol, + build_name_function, + infer_compression, + stringify_path, +) + +logger = logging.getLogger("fsspec") + + +class OpenFile: + """ + File-like object to be used in a context + + Can layer (buffered) text-mode and compression over any file-system, which + are typically binary-only. + + These instances are safe to serialize, as the low-level file object + is not created until invoked using ``with``. + + Parameters + ---------- + fs: FileSystem + The file system to use for opening the file. Should be a subclass or duck-type + with ``fsspec.spec.AbstractFileSystem`` + path: str + Location to open + mode: str like 'rb', optional + Mode of the opened file + compression: str or None, optional + Compression to apply + encoding: str or None, optional + The encoding to use if opened in text mode. + errors: str or None, optional + How to handle encoding errors if opened in text mode. + newline: None or str + Passed to TextIOWrapper in text mode, how to handle line endings. + autoopen: bool + If True, calls open() immediately. Mostly used by pickle + pos: int + If given and autoopen is True, seek to this location immediately + """ + + def __init__( + self, + fs, + path, + mode="rb", + compression=None, + encoding=None, + errors=None, + newline=None, + ): + self.fs = fs + self.path = path + self.mode = mode + self.compression = get_compression(path, compression) + self.encoding = encoding + self.errors = errors + self.newline = newline + self.fobjects = [] + + def __reduce__(self): + return ( + OpenFile, + ( + self.fs, + self.path, + self.mode, + self.compression, + self.encoding, + self.errors, + self.newline, + ), + ) + + def __repr__(self): + return f"" + + def __enter__(self): + mode = self.mode.replace("t", "").replace("b", "") + "b" + + try: + f = self.fs.open(self.path, mode=mode) + except FileNotFoundError as e: + if has_magic(self.path): + raise FileNotFoundError( + "%s not found. The URL contains glob characters: you maybe needed\n" + "to pass expand=True in fsspec.open() or the storage_options of \n" + "your library. You can also set the config value 'open_expand'\n" + "before import, or fsspec.core.DEFAULT_EXPAND at runtime, to True.", + self.path, + ) from e + raise + + self.fobjects = [f] + + if self.compression is not None: + compress = compr[self.compression] + f = compress(f, mode=mode[0]) + self.fobjects.append(f) + + if "b" not in self.mode: + # assume, for example, that 'r' is equivalent to 'rt' as in builtin + f = PickleableTextIOWrapper( + f, encoding=self.encoding, errors=self.errors, newline=self.newline + ) + self.fobjects.append(f) + + return self.fobjects[-1] + + def __exit__(self, *args): + self.close() + + @property + def full_name(self): + return _unstrip_protocol(self.path, self.fs) + + def open(self): + """Materialise this as a real open file without context + + The OpenFile object should be explicitly closed to avoid enclosed file + instances persisting. You must, therefore, keep a reference to the OpenFile + during the life of the file-like it generates. + """ + return self.__enter__() + + def close(self): + """Close all encapsulated file objects""" + for f in reversed(self.fobjects): + if "r" not in self.mode and not f.closed: + f.flush() + f.close() + self.fobjects.clear() + + +class OpenFiles(list): + """List of OpenFile instances + + Can be used in a single context, which opens and closes all of the + contained files. Normal list access to get the elements works as + normal. + + A special case is made for caching filesystems - the files will + be down/uploaded together at the start or end of the context, and + this may happen concurrently, if the target filesystem supports it. + """ + + def __init__(self, *args, mode="rb", fs=None): + self.mode = mode + self.fs = fs + self.files = [] + super().__init__(*args) + + def __enter__(self): + if self.fs is None: + raise ValueError("Context has already been used") + + fs = self.fs + while True: + if hasattr(fs, "open_many"): + # check for concurrent cache download; or set up for upload + self.files = fs.open_many(self) + return self.files + if hasattr(fs, "fs") and fs.fs is not None: + fs = fs.fs + else: + break + return [s.__enter__() for s in self] + + def __exit__(self, *args): + fs = self.fs + [s.__exit__(*args) for s in self] + if "r" not in self.mode: + while True: + if hasattr(fs, "open_many"): + # check for concurrent cache upload + fs.commit_many(self.files) + return + if hasattr(fs, "fs") and fs.fs is not None: + fs = fs.fs + else: + break + + def __getitem__(self, item): + out = super().__getitem__(item) + if isinstance(item, slice): + return OpenFiles(out, mode=self.mode, fs=self.fs) + return out + + def __repr__(self): + return f"" + + +def open_files( + urlpath, + mode="rb", + compression=None, + encoding="utf8", + errors=None, + name_function=None, + num=1, + protocol=None, + newline=None, + auto_mkdir=True, + expand=True, + **kwargs, +): + """Given a path or paths, return a list of ``OpenFile`` objects. + + For writing, a str path must contain the "*" character, which will be filled + in by increasing numbers, e.g., "part*" -> "part1", "part2" if num=2. + + For either reading or writing, can instead provide explicit list of paths. + + Parameters + ---------- + urlpath: string or list + Absolute or relative filepath(s). Prefix with a protocol like ``s3://`` + to read from alternative filesystems. To read from multiple files you + can pass a globstring or a list of paths, with the caveat that they + must all have the same protocol. + mode: 'rb', 'wt', etc. + compression: string or None + If given, open file using compression codec. Can either be a compression + name (a key in ``fsspec.compression.compr``) or "infer" to guess the + compression from the filename suffix. + encoding: str + For text mode only + errors: None or str + Passed to TextIOWrapper in text mode + name_function: function or None + if opening a set of files for writing, those files do not yet exist, + so we need to generate their names by formatting the urlpath for + each sequence number + num: int [1] + if writing mode, number of files we expect to create (passed to + name+function) + protocol: str or None + If given, overrides the protocol found in the URL. + newline: bytes or None + Used for line terminator in text mode. If None, uses system default; + if blank, uses no translation. + auto_mkdir: bool (True) + If in write mode, this will ensure the target directory exists before + writing, by calling ``fs.mkdirs(exist_ok=True)``. + expand: bool + **kwargs: dict + Extra options that make sense to a particular storage connection, e.g. + host, port, username, password, etc. + + Examples + -------- + >>> files = open_files('2015-*-*.csv') # doctest: +SKIP + >>> files = open_files( + ... 's3://bucket/2015-*-*.csv.gz', compression='gzip' + ... ) # doctest: +SKIP + + Returns + ------- + An ``OpenFiles`` instance, which is a list of ``OpenFile`` objects that can + be used as a single context + + Notes + ----- + For a full list of the available protocols and the implementations that + they map across to see the latest online documentation: + + - For implementations built into ``fsspec`` see + https://filesystem-spec.readthedocs.io/en/latest/api.html#built-in-implementations + - For implementations in separate packages see + https://filesystem-spec.readthedocs.io/en/latest/api.html#other-known-implementations + """ + fs, fs_token, paths = get_fs_token_paths( + urlpath, + mode, + num=num, + name_function=name_function, + storage_options=kwargs, + protocol=protocol, + expand=expand, + ) + if fs.protocol == "file": + fs.auto_mkdir = auto_mkdir + elif "r" not in mode and auto_mkdir: + parents = {fs._parent(path) for path in paths} + for parent in parents: + try: + fs.makedirs(parent, exist_ok=True) + except PermissionError: + pass + return OpenFiles( + [ + OpenFile( + fs, + path, + mode=mode, + compression=compression, + encoding=encoding, + errors=errors, + newline=newline, + ) + for path in paths + ], + mode=mode, + fs=fs, + ) + + +def _un_chain(path, kwargs): + # Avoid a circular import + from fsspec.implementations.chained import ChainedFileSystem + + if "::" in path: + x = re.compile(".*[^a-z]+.*") # test for non protocol-like single word + known_protocols = set(available_protocols()) + bits = [] + + # split on '::', then ensure each bit has a protocol + for p in path.split("::"): + if p in known_protocols: + bits.append(p + "://") + elif "://" in p or x.match(p): + bits.append(p) + else: + bits.append(p + "://") + else: + bits = [path] + + # [[url, protocol, kwargs], ...] + out = [] + previous_bit = None + kwargs = kwargs.copy() + + for bit in reversed(bits): + protocol = kwargs.pop("protocol", None) or split_protocol(bit)[0] or "file" + cls = get_filesystem_class(protocol) + extra_kwargs = cls._get_kwargs_from_urls(bit) + kws = kwargs.pop(protocol, {}) + + if bit is bits[0]: + kws.update(kwargs) + + kw = dict( + **{k: v for k, v in extra_kwargs.items() if k not in kws or v != kws[k]}, + **kws, + ) + bit = cls._strip_protocol(bit) + + if ( + "target_protocol" not in kw + and issubclass(cls, ChainedFileSystem) + and not bit + ): + # replace bit if we are chaining and no path given + bit = previous_bit + + out.append((bit, protocol, kw)) + previous_bit = bit + + out.reverse() + return out + + +def url_to_fs(url, **kwargs): + """ + Turn fully-qualified and potentially chained URL into filesystem instance + + Parameters + ---------- + url : str + The fsspec-compatible URL + **kwargs: dict + Extra options that make sense to a particular storage connection, e.g. + host, port, username, password, etc. + + Returns + ------- + filesystem : FileSystem + The new filesystem discovered from ``url`` and created with + ``**kwargs``. + urlpath : str + The file-systems-specific URL for ``url``. + """ + url = stringify_path(url) + # non-FS arguments that appear in fsspec.open() + # inspect could keep this in sync with open()'s signature + known_kwargs = { + "compression", + "encoding", + "errors", + "expand", + "mode", + "name_function", + "newline", + "num", + } + kwargs = {k: v for k, v in kwargs.items() if k not in known_kwargs} + chain = _un_chain(url, kwargs) + inkwargs = {} + # Reverse iterate the chain, creating a nested target_* structure + for i, ch in enumerate(reversed(chain)): + urls, protocol, kw = ch + if i == len(chain) - 1: + inkwargs = dict(**kw, **inkwargs) + continue + inkwargs["target_options"] = dict(**kw, **inkwargs) + inkwargs["target_protocol"] = protocol + inkwargs["fo"] = urls + urlpath, protocol, _ = chain[0] + fs = filesystem(protocol, **inkwargs) + return fs, urlpath + + +DEFAULT_EXPAND = conf.get("open_expand", False) + + +def open( + urlpath, + mode="rb", + compression=None, + encoding="utf8", + errors=None, + protocol=None, + newline=None, + expand=None, + **kwargs, +): + """Given a path or paths, return one ``OpenFile`` object. + + Parameters + ---------- + urlpath: string or list + Absolute or relative filepath. Prefix with a protocol like ``s3://`` + to read from alternative filesystems. Should not include glob + character(s). + mode: 'rb', 'wt', etc. + compression: string or None + If given, open file using compression codec. Can either be a compression + name (a key in ``fsspec.compression.compr``) or "infer" to guess the + compression from the filename suffix. + encoding: str + For text mode only + errors: None or str + Passed to TextIOWrapper in text mode + protocol: str or None + If given, overrides the protocol found in the URL. + newline: bytes or None + Used for line terminator in text mode. If None, uses system default; + if blank, uses no translation. + expand: bool or None + Whether to regard file paths containing special glob characters as needing + expansion (finding the first match) or absolute. Setting False allows using + paths which do embed such characters. If None (default), this argument + takes its value from the DEFAULT_EXPAND module variable, which takes + its initial value from the "open_expand" config value at startup, which will + be False if not set. + **kwargs: dict + Extra options that make sense to a particular storage connection, e.g. + host, port, username, password, etc. + + Examples + -------- + >>> openfile = open('2015-01-01.csv') # doctest: +SKIP + >>> openfile = open( + ... 's3://bucket/2015-01-01.csv.gz', compression='gzip' + ... ) # doctest: +SKIP + >>> with openfile as f: + ... df = pd.read_csv(f) # doctest: +SKIP + ... + + Returns + ------- + ``OpenFile`` object. + + Notes + ----- + For a full list of the available protocols and the implementations that + they map across to see the latest online documentation: + + - For implementations built into ``fsspec`` see + https://filesystem-spec.readthedocs.io/en/latest/api.html#built-in-implementations + - For implementations in separate packages see + https://filesystem-spec.readthedocs.io/en/latest/api.html#other-known-implementations + """ + expand = DEFAULT_EXPAND if expand is None else expand + out = open_files( + urlpath=[urlpath], + mode=mode, + compression=compression, + encoding=encoding, + errors=errors, + protocol=protocol, + newline=newline, + expand=expand, + **kwargs, + ) + if not out: + raise FileNotFoundError(urlpath) + return out[0] + + +def open_local( + url: str | list[str] | Path | list[Path], + mode: str = "rb", + **storage_options: dict, +) -> str | list[str]: + """Open file(s) which can be resolved to local + + For files which either are local, or get downloaded upon open + (e.g., by file caching) + + Parameters + ---------- + url: str or list(str) + mode: str + Must be read mode + storage_options: + passed on to FS for or used by open_files (e.g., compression) + """ + if "r" not in mode: + raise ValueError("Can only ensure local files when reading") + of = open_files(url, mode=mode, **storage_options) + if not getattr(of[0].fs, "local_file", False): + raise ValueError( + "open_local can only be used on a filesystem which" + " has attribute local_file=True" + ) + with of as files: + paths = [f.name for f in files] + if (isinstance(url, str) and not has_magic(url)) or isinstance(url, Path): + return paths[0] + return paths + + +def get_compression(urlpath, compression): + if compression == "infer": + compression = infer_compression(urlpath) + if compression is not None and compression not in compr: + raise ValueError(f"Compression type {compression} not supported") + return compression + + +def split_protocol(urlpath): + """Return protocol, path pair""" + urlpath = stringify_path(urlpath) + if "://" in urlpath: + protocol, path = urlpath.split("://", 1) + if len(protocol) > 1: + # excludes Windows paths + return protocol, path + if urlpath.startswith("data:"): + return urlpath.split(":", 1) + return None, urlpath + + +def strip_protocol(urlpath): + """Return only path part of full URL, according to appropriate backend""" + protocol, _ = split_protocol(urlpath) + cls = get_filesystem_class(protocol) + return cls._strip_protocol(urlpath) + + +def expand_paths_if_needed(paths, mode, num, fs, name_function): + """Expand paths if they have a ``*`` in them (write mode) or any of ``*?[]`` + in them (read mode). + + :param paths: list of paths + mode: str + Mode in which to open files. + num: int + If opening in writing mode, number of files we expect to create. + fs: filesystem object + name_function: callable + If opening in writing mode, this callable is used to generate path + names. Names are generated for each partition by + ``urlpath.replace('*', name_function(partition_index))``. + :return: list of paths + """ + expanded_paths = [] + paths = list(paths) + + if "w" in mode: # read mode + if sum(1 for p in paths if "*" in p) > 1: + raise ValueError( + "When writing data, only one filename mask can be specified." + ) + num = max(num, len(paths)) + + for curr_path in paths: + if "*" in curr_path: + # expand using name_function + expanded_paths.extend(_expand_paths(curr_path, name_function, num)) + else: + expanded_paths.append(curr_path) + # if we generated more paths that asked for, trim the list + if len(expanded_paths) > num: + expanded_paths = expanded_paths[:num] + + else: # read mode + for curr_path in paths: + if has_magic(curr_path): + # expand using glob + expanded_paths.extend(fs.glob(curr_path)) + else: + expanded_paths.append(curr_path) + + return expanded_paths + + +def get_fs_token_paths( + urlpath, + mode="rb", + num=1, + name_function=None, + storage_options=None, + protocol=None, + expand=True, +): + """Filesystem, deterministic token, and paths from a urlpath and options. + + Parameters + ---------- + urlpath: string or iterable + Absolute or relative filepath, URL (may include protocols like + ``s3://``), or globstring pointing to data. + mode: str, optional + Mode in which to open files. + num: int, optional + If opening in writing mode, number of files we expect to create. + name_function: callable, optional + If opening in writing mode, this callable is used to generate path + names. Names are generated for each partition by + ``urlpath.replace('*', name_function(partition_index))``. + storage_options: dict, optional + Additional keywords to pass to the filesystem class. + protocol: str or None + To override the protocol specifier in the URL + expand: bool + Expand string paths for writing, assuming the path is a directory + """ + if isinstance(urlpath, (list, tuple, set)): + if not urlpath: + raise ValueError("empty urlpath sequence") + urlpath0 = stringify_path(next(iter(urlpath))) + else: + urlpath0 = stringify_path(urlpath) + storage_options = storage_options or {} + if protocol: + storage_options["protocol"] = protocol + chain = _un_chain(urlpath0, storage_options or {}) + inkwargs = {} + # Reverse iterate the chain, creating a nested target_* structure + for i, ch in enumerate(reversed(chain)): + urls, nested_protocol, kw = ch + if i == len(chain) - 1: + inkwargs = dict(**kw, **inkwargs) + continue + inkwargs["target_options"] = dict(**kw, **inkwargs) + inkwargs["target_protocol"] = nested_protocol + inkwargs["fo"] = urls + paths, protocol, _ = chain[0] + fs = filesystem(protocol, **inkwargs) + if isinstance(urlpath, (list, tuple, set)): + pchains = [ + _un_chain(stringify_path(u), storage_options or {})[0] for u in urlpath + ] + if len({pc[1] for pc in pchains}) > 1: + raise ValueError("Protocol mismatch getting fs from %s", urlpath) + paths = [pc[0] for pc in pchains] + else: + paths = fs._strip_protocol(paths) + if isinstance(paths, (list, tuple, set)): + if expand: + paths = expand_paths_if_needed(paths, mode, num, fs, name_function) + elif not isinstance(paths, list): + paths = list(paths) + else: + if ("w" in mode or "x" in mode) and expand: + paths = _expand_paths(paths, name_function, num) + elif "*" in paths: + paths = [f for f in sorted(fs.glob(paths)) if not fs.isdir(f)] + else: + paths = [paths] + + return fs, fs._fs_token, paths + + +def _expand_paths(path, name_function, num): + if isinstance(path, str): + if path.count("*") > 1: + raise ValueError("Output path spec must contain exactly one '*'.") + elif "*" not in path: + path = os.path.join(path, "*.part") + + if name_function is None: + name_function = build_name_function(num - 1) + + paths = [path.replace("*", name_function(i)) for i in range(num)] + if paths != sorted(paths): + logger.warning( + "In order to preserve order between partitions" + " paths created with ``name_function`` should " + "sort to partition order" + ) + elif isinstance(path, (tuple, list)): + assert len(path) == num + paths = list(path) + else: + raise ValueError( + "Path should be either\n" + "1. A list of paths: ['foo.json', 'bar.json', ...]\n" + "2. A directory: 'foo/\n" + "3. A path with a '*' in it: 'foo.*.json'" + ) + return paths + + +class PickleableTextIOWrapper(io.TextIOWrapper): + """TextIOWrapper cannot be pickled. This solves it. + + Requires that ``buffer`` be pickleable, which all instances of + AbstractBufferedFile are. + """ + + def __init__( + self, + buffer, + encoding=None, + errors=None, + newline=None, + line_buffering=False, + write_through=False, + ): + self.args = buffer, encoding, errors, newline, line_buffering, write_through + super().__init__(*self.args) + + def __reduce__(self): + return PickleableTextIOWrapper, self.args diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/dircache.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/dircache.py new file mode 100644 index 0000000000000000000000000000000000000000..eca19566b135e5a7a4f6e7407d56411ec58bfe44 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/dircache.py @@ -0,0 +1,98 @@ +import time +from collections.abc import MutableMapping +from functools import lru_cache + + +class DirCache(MutableMapping): + """ + Caching of directory listings, in a structure like:: + + {"path0": [ + {"name": "path0/file0", + "size": 123, + "type": "file", + ... + }, + {"name": "path0/file1", + }, + ... + ], + "path1": [...] + } + + Parameters to this class control listing expiry or indeed turn + caching off + """ + + def __init__( + self, + use_listings_cache=True, + listings_expiry_time=None, + max_paths=None, + **kwargs, + ): + """ + + Parameters + ---------- + use_listings_cache: bool + If False, this cache never returns items, but always reports KeyError, + and setting items has no effect + listings_expiry_time: int or float (optional) + Time in seconds that a listing is considered valid. If None, + listings do not expire. + max_paths: int (optional) + The number of most recent listings that are considered valid; 'recent' + refers to when the entry was set. + """ + self._cache = {} + self._times = {} + if max_paths: + self._q = lru_cache(max_paths + 1)(lambda key: self._cache.pop(key, None)) + self.use_listings_cache = use_listings_cache + self.listings_expiry_time = listings_expiry_time + self.max_paths = max_paths + + def __getitem__(self, item): + if self.listings_expiry_time is not None: + if self._times.get(item, 0) - time.time() < -self.listings_expiry_time: + del self._cache[item] + if self.max_paths: + self._q(item) + return self._cache[item] # maybe raises KeyError + + def clear(self): + self._cache.clear() + + def __len__(self): + return len(self._cache) + + def __contains__(self, item): + try: + self[item] + return True + except KeyError: + return False + + def __setitem__(self, key, value): + if not self.use_listings_cache: + return + if self.max_paths: + self._q(key) + self._cache[key] = value + if self.listings_expiry_time is not None: + self._times[key] = time.time() + + def __delitem__(self, key): + del self._cache[key] + + def __iter__(self): + entries = list(self._cache) + + return (k for k in entries if k in self) + + def __reduce__(self): + return ( + DirCache, + (self.use_listings_cache, self.listings_expiry_time, self.max_paths), + ) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/exceptions.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/exceptions.py new file mode 100644 index 0000000000000000000000000000000000000000..ae8905475f02655f4fc5863931d99ca9da55db78 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/exceptions.py @@ -0,0 +1,18 @@ +""" +fsspec user-defined exception classes +""" + +import asyncio + + +class BlocksizeMismatchError(ValueError): + """ + Raised when a cached file is opened with a different blocksize than it was + written with + """ + + +class FSTimeoutError(asyncio.TimeoutError): + """ + Raised when a fsspec function timed out occurs + """ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/fuse.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/fuse.py new file mode 100644 index 0000000000000000000000000000000000000000..566d520fce3e94e3bbaee48c3c6acc9f1db315a8 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/fuse.py @@ -0,0 +1,324 @@ +import argparse +import logging +import os +import stat +import threading +import time +from errno import EIO, ENOENT + +from fuse import FUSE, FuseOSError, LoggingMixIn, Operations + +from fsspec import __version__ +from fsspec.core import url_to_fs + +logger = logging.getLogger("fsspec.fuse") + + +class FUSEr(Operations): + def __init__(self, fs, path, ready_file=False): + self.fs = fs + self.cache = {} + self.root = path.rstrip("/") + "/" + self.counter = 0 + logger.info("Starting FUSE at %s", path) + self._ready_file = ready_file + + def getattr(self, path, fh=None): + logger.debug("getattr %s", path) + if self._ready_file and path in ["/.fuse_ready", ".fuse_ready"]: + return {"type": "file", "st_size": 5} + + path = "".join([self.root, path.lstrip("/")]).rstrip("/") + try: + info = self.fs.info(path) + except FileNotFoundError as exc: + raise FuseOSError(ENOENT) from exc + + data = {"st_uid": info.get("uid", 1000), "st_gid": info.get("gid", 1000)} + perm = info.get("mode", 0o777) + + if info["type"] != "file": + data["st_mode"] = stat.S_IFDIR | perm + data["st_size"] = 0 + data["st_blksize"] = 0 + else: + data["st_mode"] = stat.S_IFREG | perm + data["st_size"] = info["size"] + data["st_blksize"] = 5 * 2**20 + data["st_nlink"] = 1 + data["st_atime"] = info["atime"] if "atime" in info else time.time() + data["st_ctime"] = info["ctime"] if "ctime" in info else time.time() + data["st_mtime"] = info["mtime"] if "mtime" in info else time.time() + return data + + def readdir(self, path, fh): + logger.debug("readdir %s", path) + path = "".join([self.root, path.lstrip("/")]) + files = self.fs.ls(path, False) + files = [os.path.basename(f.rstrip("/")) for f in files] + return [".", ".."] + files + + def mkdir(self, path, mode): + path = "".join([self.root, path.lstrip("/")]) + self.fs.mkdir(path) + return 0 + + def rmdir(self, path): + path = "".join([self.root, path.lstrip("/")]) + self.fs.rmdir(path) + return 0 + + def read(self, path, size, offset, fh): + logger.debug("read %s", (path, size, offset)) + if self._ready_file and path in ["/.fuse_ready", ".fuse_ready"]: + # status indicator + return b"ready" + + f = self.cache[fh] + f.seek(offset) + out = f.read(size) + return out + + def write(self, path, data, offset, fh): + logger.debug("write %s", (path, offset)) + f = self.cache[fh] + f.seek(offset) + f.write(data) + return len(data) + + def create(self, path, flags, fi=None): + logger.debug("create %s", (path, flags)) + fn = "".join([self.root, path.lstrip("/")]) + self.fs.touch(fn) # OS will want to get attributes immediately + f = self.fs.open(fn, "wb") + self.cache[self.counter] = f + self.counter += 1 + return self.counter - 1 + + def open(self, path, flags): + logger.debug("open %s", (path, flags)) + fn = "".join([self.root, path.lstrip("/")]) + if flags % 2 == 0: + # read + mode = "rb" + else: + # write/create + mode = "wb" + self.cache[self.counter] = self.fs.open(fn, mode) + self.counter += 1 + return self.counter - 1 + + def truncate(self, path, length, fh=None): + fn = "".join([self.root, path.lstrip("/")]) + if length != 0: + raise NotImplementedError + # maybe should be no-op since open with write sets size to zero anyway + self.fs.touch(fn) + + def unlink(self, path): + fn = "".join([self.root, path.lstrip("/")]) + try: + self.fs.rm(fn, False) + except (OSError, FileNotFoundError) as exc: + raise FuseOSError(EIO) from exc + + def release(self, path, fh): + try: + if fh in self.cache: + f = self.cache[fh] + f.close() + self.cache.pop(fh) + except Exception as e: + print(e) + return 0 + + def chmod(self, path, mode): + if hasattr(self.fs, "chmod"): + path = "".join([self.root, path.lstrip("/")]) + return self.fs.chmod(path, mode) + raise NotImplementedError + + +def run( + fs, + path, + mount_point, + foreground=True, + threads=False, + ready_file=False, + ops_class=FUSEr, +): + """Mount stuff in a local directory + + This uses fusepy to make it appear as if a given path on an fsspec + instance is in fact resident within the local file-system. + + This requires that fusepy by installed, and that FUSE be available on + the system (typically requiring a package to be installed with + apt, yum, brew, etc.). + + Parameters + ---------- + fs: file-system instance + From one of the compatible implementations + path: str + Location on that file-system to regard as the root directory to + mount. Note that you typically should include the terminating "/" + character. + mount_point: str + An empty directory on the local file-system where the contents of + the remote path will appear. + foreground: bool + Whether or not calling this function will block. Operation will + typically be more stable if True. + threads: bool + Whether or not to create threads when responding to file operations + within the mounter directory. Operation will typically be more + stable if False. + ready_file: bool + Whether the FUSE process is ready. The ``.fuse_ready`` file will + exist in the ``mount_point`` directory if True. Debugging purpose. + ops_class: FUSEr or Subclass of FUSEr + To override the default behavior of FUSEr. For Example, logging + to file. + + """ + func = lambda: FUSE( + ops_class(fs, path, ready_file=ready_file), + mount_point, + nothreads=not threads, + foreground=foreground, + ) + if not foreground: + th = threading.Thread(target=func) + th.daemon = True + th.start() + return th + else: # pragma: no cover + try: + func() + except KeyboardInterrupt: + pass + + +def main(args): + """Mount filesystem from chained URL to MOUNT_POINT. + + Examples: + + python3 -m fsspec.fuse memory /usr/share /tmp/mem + + python3 -m fsspec.fuse local /tmp/source /tmp/local \\ + -l /tmp/fsspecfuse.log + + You can also mount chained-URLs and use special settings: + + python3 -m fsspec.fuse 'filecache::zip::file://data.zip' \\ + / /tmp/zip \\ + -o 'filecache-cache_storage=/tmp/simplecache' + + You can specify the type of the setting by using `[int]` or `[bool]`, + (`true`, `yes`, `1` represents the Boolean value `True`): + + python3 -m fsspec.fuse 'simplecache::ftp://ftp1.at.proftpd.org' \\ + /historic/packages/RPMS /tmp/ftp \\ + -o 'simplecache-cache_storage=/tmp/simplecache' \\ + -o 'simplecache-check_files=false[bool]' \\ + -o 'ftp-listings_expiry_time=60[int]' \\ + -o 'ftp-username=anonymous' \\ + -o 'ftp-password=xieyanbo' + """ + + class RawDescriptionArgumentParser(argparse.ArgumentParser): + def format_help(self): + usage = super().format_help() + parts = usage.split("\n\n") + parts[1] = self.description.rstrip() + return "\n\n".join(parts) + + parser = RawDescriptionArgumentParser(prog="fsspec.fuse", description=main.__doc__) + parser.add_argument("--version", action="version", version=__version__) + parser.add_argument("url", type=str, help="fs url") + parser.add_argument("source_path", type=str, help="source directory in fs") + parser.add_argument("mount_point", type=str, help="local directory") + parser.add_argument( + "-o", + "--option", + action="append", + help="Any options of protocol included in the chained URL", + ) + parser.add_argument( + "-l", "--log-file", type=str, help="Logging FUSE debug info (Default: '')" + ) + parser.add_argument( + "-f", + "--foreground", + action="store_false", + help="Running in foreground or not (Default: False)", + ) + parser.add_argument( + "-t", + "--threads", + action="store_false", + help="Running with threads support (Default: False)", + ) + parser.add_argument( + "-r", + "--ready-file", + action="store_false", + help="The `.fuse_ready` file will exist after FUSE is ready. " + "(Debugging purpose, Default: False)", + ) + args = parser.parse_args(args) + + kwargs = {} + for item in args.option or []: + key, sep, value = item.partition("=") + if not sep: + parser.error(message=f"Wrong option: {item!r}") + val = value.lower() + if val.endswith("[int]"): + value = int(value[: -len("[int]")]) + elif val.endswith("[bool]"): + value = val[: -len("[bool]")] in ["1", "yes", "true"] + + if "-" in key: + fs_name, setting_name = key.split("-", 1) + if fs_name in kwargs: + kwargs[fs_name][setting_name] = value + else: + kwargs[fs_name] = {setting_name: value} + else: + kwargs[key] = value + + if args.log_file: + logging.basicConfig( + level=logging.DEBUG, + filename=args.log_file, + format="%(asctime)s %(message)s", + ) + + class LoggingFUSEr(FUSEr, LoggingMixIn): + pass + + fuser = LoggingFUSEr + else: + fuser = FUSEr + + fs, url_path = url_to_fs(args.url, **kwargs) + logger.debug("Mounting %s to %s", url_path, str(args.mount_point)) + run( + fs, + args.source_path, + args.mount_point, + foreground=args.foreground, + threads=args.threads, + ready_file=args.ready_file, + ops_class=fuser, + ) + + +if __name__ == "__main__": + import sys + + main(sys.argv[1:]) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/generic.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/generic.py new file mode 100644 index 0000000000000000000000000000000000000000..0a641b0e2bcf70729a44064319eecb3647450379 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/generic.py @@ -0,0 +1,396 @@ +from __future__ import annotations + +import inspect +import logging +import os +import shutil +import uuid + +from .asyn import AsyncFileSystem, _run_coros_in_chunks, sync_wrapper +from .callbacks import DEFAULT_CALLBACK +from .core import filesystem, get_filesystem_class, split_protocol, url_to_fs + +_generic_fs = {} +logger = logging.getLogger("fsspec.generic") + + +def set_generic_fs(protocol, **storage_options): + """Populate the dict used for method=="generic" lookups""" + _generic_fs[protocol] = filesystem(protocol, **storage_options) + + +def _resolve_fs(url, method, protocol=None, storage_options=None): + """Pick instance of backend FS""" + url = url[0] if isinstance(url, (list, tuple)) else url + protocol = protocol or split_protocol(url)[0] + storage_options = storage_options or {} + if method == "default": + return filesystem(protocol) + if method == "generic": + return _generic_fs[protocol] + if method == "current": + cls = get_filesystem_class(protocol) + return cls.current() + if method == "options": + fs, _ = url_to_fs(url, **storage_options.get(protocol, {})) + return fs + raise ValueError(f"Unknown FS resolution method: {method}") + + +def rsync( + source, + destination, + delete_missing=False, + source_field="size", + dest_field="size", + update_cond="different", + inst_kwargs=None, + fs=None, + **kwargs, +): + """Sync files between two directory trees + + (experimental) + + Parameters + ---------- + source: str + Root of the directory tree to take files from. This must be a directory, but + do not include any terminating "/" character + destination: str + Root path to copy into. The contents of this location should be + identical to the contents of ``source`` when done. This will be made a + directory, and the terminal "/" should not be included. + delete_missing: bool + If there are paths in the destination that don't exist in the + source and this is True, delete them. Otherwise, leave them alone. + source_field: str | callable + If ``update_field`` is "different", this is the key in the info + of source files to consider for difference. Maybe a function of the + info dict. + dest_field: str | callable + If ``update_field`` is "different", this is the key in the info + of destination files to consider for difference. May be a function of + the info dict. + update_cond: "different"|"always"|"never" + If "always", every file is copied, regardless of whether it exists in + the destination. If "never", files that exist in the destination are + not copied again. If "different" (default), only copy if the info + fields given by ``source_field`` and ``dest_field`` (usually "size") + are different. Other comparisons may be added in the future. + inst_kwargs: dict|None + If ``fs`` is None, use this set of keyword arguments to make a + GenericFileSystem instance + fs: GenericFileSystem|None + Instance to use if explicitly given. The instance defines how to + to make downstream file system instances from paths. + + Returns + ------- + dict of the copy operations that were performed, {source: destination} + """ + fs = fs or GenericFileSystem(**(inst_kwargs or {})) + source = fs._strip_protocol(source) + destination = fs._strip_protocol(destination) + allfiles = fs.find(source, withdirs=True, detail=True) + if not fs.isdir(source): + raise ValueError("Can only rsync on a directory") + otherfiles = fs.find(destination, withdirs=True, detail=True) + dirs = [ + a + for a, v in allfiles.items() + if v["type"] == "directory" and a.replace(source, destination) not in otherfiles + ] + logger.debug(f"{len(dirs)} directories to create") + if dirs: + fs.make_many_dirs( + [dirn.replace(source, destination) for dirn in dirs], exist_ok=True + ) + allfiles = {a: v for a, v in allfiles.items() if v["type"] == "file"} + logger.debug(f"{len(allfiles)} files to consider for copy") + to_delete = [ + o + for o, v in otherfiles.items() + if o.replace(destination, source) not in allfiles and v["type"] == "file" + ] + for k, v in allfiles.copy().items(): + otherfile = k.replace(source, destination) + if otherfile in otherfiles: + if update_cond == "always": + allfiles[k] = otherfile + elif update_cond == "never": + allfiles.pop(k) + elif update_cond == "different": + inf1 = source_field(v) if callable(source_field) else v[source_field] + v2 = otherfiles[otherfile] + inf2 = dest_field(v2) if callable(dest_field) else v2[dest_field] + if inf1 != inf2: + # details mismatch, make copy + allfiles[k] = otherfile + else: + # details match, don't copy + allfiles.pop(k) + else: + # file not in target yet + allfiles[k] = otherfile + logger.debug(f"{len(allfiles)} files to copy") + if allfiles: + source_files, target_files = zip(*allfiles.items()) + fs.cp(source_files, target_files, **kwargs) + logger.debug(f"{len(to_delete)} files to delete") + if delete_missing and to_delete: + fs.rm(to_delete) + return allfiles + + +class GenericFileSystem(AsyncFileSystem): + """Wrapper over all other FS types + + + + This implementation is a single unified interface to be able to run FS operations + over generic URLs, and dispatch to the specific implementations using the URL + protocol prefix. + + Note: instances of this FS are always async, even if you never use it with any async + backend. + """ + + protocol = "generic" # there is no real reason to ever use a protocol with this FS + + def __init__(self, default_method="default", storage_options=None, **kwargs): + """ + + Parameters + ---------- + default_method: str (optional) + Defines how to configure backend FS instances. Options are: + - "default": instantiate like FSClass(), with no + extra arguments; this is the default instance of that FS, and can be + configured via the config system + - "generic": takes instances from the `_generic_fs` dict in this module, + which you must populate before use. Keys are by protocol + - "options": expects storage_options, a dict mapping protocol to + kwargs to use when constructing the filesystem + - "current": takes the most recently instantiated version of each FS + """ + self.method = default_method + self.st_opts = storage_options + super().__init__(**kwargs) + + def _parent(self, path): + fs = _resolve_fs(path, self.method, storage_options=self.st_opts) + return fs.unstrip_protocol(fs._parent(path)) + + def _strip_protocol(self, path): + # normalization only + fs = _resolve_fs(path, self.method, storage_options=self.st_opts) + return fs.unstrip_protocol(fs._strip_protocol(path)) + + async def _find(self, path, maxdepth=None, withdirs=False, detail=False, **kwargs): + fs = _resolve_fs(path, self.method, storage_options=self.st_opts) + if fs.async_impl: + out = await fs._find( + path, maxdepth=maxdepth, withdirs=withdirs, detail=True, **kwargs + ) + else: + out = fs.find( + path, maxdepth=maxdepth, withdirs=withdirs, detail=True, **kwargs + ) + result = {} + for k, v in out.items(): + v = v.copy() # don't corrupt target FS dircache + name = fs.unstrip_protocol(k) + v["name"] = name + result[name] = v + if detail: + return result + return list(result) + + async def _info(self, url, **kwargs): + fs = _resolve_fs(url, self.method) + if fs.async_impl: + out = await fs._info(url, **kwargs) + else: + out = fs.info(url, **kwargs) + out = out.copy() # don't edit originals + out["name"] = fs.unstrip_protocol(out["name"]) + return out + + async def _ls( + self, + url, + detail=True, + **kwargs, + ): + fs = _resolve_fs(url, self.method) + if fs.async_impl: + out = await fs._ls(url, detail=True, **kwargs) + else: + out = fs.ls(url, detail=True, **kwargs) + out = [o.copy() for o in out] # don't edit originals + for o in out: + o["name"] = fs.unstrip_protocol(o["name"]) + if detail: + return out + else: + return [o["name"] for o in out] + + async def _cat_file( + self, + url, + **kwargs, + ): + fs = _resolve_fs(url, self.method) + if fs.async_impl: + return await fs._cat_file(url, **kwargs) + else: + return fs.cat_file(url, **kwargs) + + async def _pipe_file( + self, + path, + value, + **kwargs, + ): + fs = _resolve_fs(path, self.method, storage_options=self.st_opts) + if fs.async_impl: + return await fs._pipe_file(path, value, **kwargs) + else: + return fs.pipe_file(path, value, **kwargs) + + async def _rm(self, url, **kwargs): + urls = url + if isinstance(urls, str): + urls = [urls] + fs = _resolve_fs(urls[0], self.method) + if fs.async_impl: + await fs._rm(urls, **kwargs) + else: + fs.rm(url, **kwargs) + + async def _makedirs(self, path, exist_ok=False): + logger.debug("Make dir %s", path) + fs = _resolve_fs(path, self.method, storage_options=self.st_opts) + if fs.async_impl: + await fs._makedirs(path, exist_ok=exist_ok) + else: + fs.makedirs(path, exist_ok=exist_ok) + + def rsync(self, source, destination, **kwargs): + """Sync files between two directory trees + + See `func:rsync` for more details. + """ + rsync(source, destination, fs=self, **kwargs) + + async def _cp_file( + self, + url, + url2, + blocksize=2**20, + callback=DEFAULT_CALLBACK, + tempdir: str | None = None, + **kwargs, + ): + fs = _resolve_fs(url, self.method) + fs2 = _resolve_fs(url2, self.method) + if fs is fs2: + # pure remote + if fs.async_impl: + return await fs._copy(url, url2, **kwargs) + else: + return fs.copy(url, url2, **kwargs) + await copy_file_op(fs, [url], fs2, [url2], tempdir, 1, on_error="raise") + + async def _make_many_dirs(self, urls, exist_ok=True): + fs = _resolve_fs(urls[0], self.method) + if fs.async_impl: + coros = [fs._makedirs(u, exist_ok=exist_ok) for u in urls] + await _run_coros_in_chunks(coros) + else: + for u in urls: + fs.makedirs(u, exist_ok=exist_ok) + + make_many_dirs = sync_wrapper(_make_many_dirs) + + async def _copy( + self, + path1: list[str], + path2: list[str], + recursive: bool = False, + on_error: str = "ignore", + maxdepth: int | None = None, + batch_size: int | None = None, + tempdir: str | None = None, + **kwargs, + ): + # TODO: special case for one FS being local, which can use get/put + # TODO: special case for one being memFS, which can use cat/pipe + if recursive: + raise NotImplementedError("Please use fsspec.generic.rsync") + path1 = [path1] if isinstance(path1, str) else path1 + path2 = [path2] if isinstance(path2, str) else path2 + + fs = _resolve_fs(path1, self.method) + fs2 = _resolve_fs(path2, self.method) + + if fs is fs2: + if fs.async_impl: + return await fs._copy(path1, path2, **kwargs) + else: + return fs.copy(path1, path2, **kwargs) + + await copy_file_op( + fs, path1, fs2, path2, tempdir, batch_size, on_error=on_error + ) + + +async def copy_file_op( + fs1, url1, fs2, url2, tempdir=None, batch_size=20, on_error="ignore" +): + import tempfile + + tempdir = tempdir or tempfile.mkdtemp() + try: + coros = [ + _copy_file_op( + fs1, + u1, + fs2, + u2, + os.path.join(tempdir, uuid.uuid4().hex), + ) + for u1, u2 in zip(url1, url2) + ] + out = await _run_coros_in_chunks( + coros, batch_size=batch_size, return_exceptions=True + ) + finally: + shutil.rmtree(tempdir) + if on_error == "return": + return out + elif on_error == "raise": + for o in out: + if isinstance(o, Exception): + raise o + + +async def _copy_file_op(fs1, url1, fs2, url2, local, on_error="ignore"): + if fs1.async_impl: + await fs1._get_file(url1, local) + else: + fs1.get_file(url1, local) + if fs2.async_impl: + await fs2._put_file(local, url2) + else: + fs2.put_file(local, url2) + os.unlink(local) + logger.debug("Copy %s -> %s; done", url1, url2) + + +async def maybe_await(cor): + if inspect.iscoroutine(cor): + return await cor + else: + return cor diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/gui.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/gui.py new file mode 100644 index 0000000000000000000000000000000000000000..9d914c8beb6cabb2c2700eb8eee31028559be2bd --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/gui.py @@ -0,0 +1,417 @@ +import ast +import contextlib +import logging +import os +import re +from collections.abc import Sequence +from typing import ClassVar + +import panel as pn + +from .core import OpenFile, get_filesystem_class, split_protocol +from .registry import known_implementations + +pn.extension() +logger = logging.getLogger("fsspec.gui") + + +class SigSlot: + """Signal-slot mixin, for Panel event passing + + Include this class in a widget manager's superclasses to be able to + register events and callbacks on Panel widgets managed by that class. + + The method ``_register`` should be called as widgets are added, and external + code should call ``connect`` to associate callbacks. + + By default, all signals emit a DEBUG logging statement. + """ + + # names of signals that this class may emit each of which must be + # set by _register for any new instance + signals: ClassVar[Sequence[str]] = [] + # names of actions that this class may respond to + slots: ClassVar[Sequence[str]] = [] + + # each of which must be a method name + + def __init__(self): + self._ignoring_events = False + self._sigs = {} + self._map = {} + self._setup() + + def _setup(self): + """Create GUI elements and register signals""" + self.panel = pn.pane.PaneBase() + # no signals to set up in the base class + + def _register( + self, widget, name, thing="value", log_level=logging.DEBUG, auto=False + ): + """Watch the given attribute of a widget and assign it a named event + + This is normally called at the time a widget is instantiated, in the + class which owns it. + + Parameters + ---------- + widget : pn.layout.Panel or None + Widget to watch. If None, an anonymous signal not associated with + any widget. + name : str + Name of this event + thing : str + Attribute of the given widget to watch + log_level : int + When the signal is triggered, a logging event of the given level + will be fired in the dfviz logger. + auto : bool + If True, automatically connects with a method in this class of the + same name. + """ + if name not in self.signals: + raise ValueError(f"Attempt to assign an undeclared signal: {name}") + self._sigs[name] = { + "widget": widget, + "callbacks": [], + "thing": thing, + "log": log_level, + } + wn = "-".join( + [ + getattr(widget, "name", str(widget)) if widget is not None else "none", + thing, + ] + ) + self._map[wn] = name + if widget is not None: + widget.param.watch(self._signal, thing, onlychanged=True) + if auto and hasattr(self, name): + self.connect(name, getattr(self, name)) + + def _repr_mimebundle_(self, *args, **kwargs): + """Display in a notebook or a server""" + try: + return self.panel._repr_mimebundle_(*args, **kwargs) + except (ValueError, AttributeError) as exc: + raise NotImplementedError( + "Panel does not seem to be set up properly" + ) from exc + + def connect(self, signal, slot): + """Associate call back with given event + + The callback must be a function which takes the "new" value of the + watched attribute as the only parameter. If the callback return False, + this cancels any further processing of the given event. + + Alternatively, the callback can be a string, in which case it means + emitting the correspondingly-named event (i.e., connect to self) + """ + self._sigs[signal]["callbacks"].append(slot) + + def _signal(self, event): + """This is called by a an action on a widget + + Within an self.ignore_events context, nothing happens. + + Tests can execute this method by directly changing the values of + widget components. + """ + if not self._ignoring_events: + wn = "-".join([event.obj.name, event.name]) + if wn in self._map and self._map[wn] in self._sigs: + self._emit(self._map[wn], event.new) + + @contextlib.contextmanager + def ignore_events(self): + """Temporarily turn off events processing in this instance + + (does not propagate to children) + """ + self._ignoring_events = True + try: + yield + finally: + self._ignoring_events = False + + def _emit(self, sig, value=None): + """An event happened, call its callbacks + + This method can be used in tests to simulate message passing without + directly changing visual elements. + + Calling of callbacks will halt whenever one returns False. + """ + logger.log(self._sigs[sig]["log"], f"{sig}: {value}") + for callback in self._sigs[sig]["callbacks"]: + if isinstance(callback, str): + self._emit(callback) + else: + try: + # running callbacks should not break the interface + ret = callback(value) + if ret is False: + break + except Exception as e: + logger.exception( + "Exception (%s) while executing callback for signal: %s", + e, + sig, + ) + + def show(self, threads=False): + """Open a new browser tab and display this instance's interface""" + self.panel.show(threads=threads, verbose=False) + return self + + +class SingleSelect(SigSlot): + """A multiselect which only allows you to select one item for an event""" + + signals = ["_selected", "selected"] # the first is internal + slots = ["set_options", "set_selection", "add", "clear", "select"] + + def __init__(self, **kwargs): + self.kwargs = kwargs + super().__init__() + + def _setup(self): + self.panel = pn.widgets.MultiSelect(**self.kwargs) + self._register(self.panel, "_selected", "value") + self._register(None, "selected") + self.connect("_selected", self.select_one) + + def _signal(self, *args, **kwargs): + super()._signal(*args, **kwargs) + + def select_one(self, *_): + with self.ignore_events(): + val = [self.panel.value[-1]] if self.panel.value else [] + self.panel.value = val + self._emit("selected", self.panel.value) + + def set_options(self, options): + self.panel.options = options + + def clear(self): + self.panel.options = [] + + @property + def value(self): + return self.panel.value + + def set_selection(self, selection): + self.panel.value = [selection] + + +class FileSelector(SigSlot): + """Panel-based graphical file selector widget + + Instances of this widget are interactive and can be displayed in jupyter by having + them as the output of a cell, or in a separate browser tab using ``.show()``. + """ + + signals = [ + "protocol_changed", + "selection_changed", + "directory_entered", + "home_clicked", + "up_clicked", + "go_clicked", + "filters_changed", + ] + slots = ["set_filters", "go_home"] + + def __init__(self, url=None, filters=None, ignore=None, kwargs=None): + """ + + Parameters + ---------- + url : str (optional) + Initial value of the URL to populate the dialog; should include protocol + filters : list(str) (optional) + File endings to include in the listings. If not included, all files are + allowed. Does not affect directories. + If given, the endings will appear as checkboxes in the interface + ignore : list(str) (optional) + Regex(s) of file basename patterns to ignore, e.g., "\\." for typical + hidden files on posix + kwargs : dict (optional) + To pass to file system instance + """ + if url: + self.init_protocol, url = split_protocol(url) + else: + self.init_protocol, url = "file", os.getcwd() + self.init_url = url + self.init_kwargs = (kwargs if isinstance(kwargs, str) else str(kwargs)) or "{}" + self.filters = filters + self.ignore = [re.compile(i) for i in ignore or []] + self._fs = None + super().__init__() + + def _setup(self): + self.url = pn.widgets.TextInput( + name="url", + value=self.init_url, + align="end", + sizing_mode="stretch_width", + width_policy="max", + ) + self.protocol = pn.widgets.Select( + options=sorted(known_implementations), + value=self.init_protocol, + name="protocol", + align="center", + ) + self.kwargs = pn.widgets.TextInput( + name="kwargs", value=self.init_kwargs, align="center" + ) + self.go = pn.widgets.Button(name="⇨", align="end", width=45) + self.main = SingleSelect(size=10) + self.home = pn.widgets.Button(name="🏠", width=40, height=30, align="end") + self.up = pn.widgets.Button(name="‹", width=30, height=30, align="end") + + self._register(self.protocol, "protocol_changed", auto=True) + self._register(self.go, "go_clicked", "clicks", auto=True) + self._register(self.up, "up_clicked", "clicks", auto=True) + self._register(self.home, "home_clicked", "clicks", auto=True) + self._register(None, "selection_changed") + self.main.connect("selected", self.selection_changed) + self._register(None, "directory_entered") + self.prev_protocol = self.protocol.value + self.prev_kwargs = self.storage_options + + self.filter_sel = pn.widgets.CheckBoxGroup( + value=[], options=[], inline=False, align="end", width_policy="min" + ) + self._register(self.filter_sel, "filters_changed", auto=True) + + self.panel = pn.Column( + pn.Row(self.protocol, self.kwargs), + pn.Row(self.home, self.up, self.url, self.go, self.filter_sel), + self.main.panel, + ) + self.set_filters(self.filters) + self.go_clicked() + + def set_filters(self, filters=None): + self.filters = filters + if filters: + self.filter_sel.options = filters + self.filter_sel.value = filters + else: + self.filter_sel.options = [] + self.filter_sel.value = [] + + @property + def storage_options(self): + """Value of the kwargs box as a dictionary""" + return ast.literal_eval(self.kwargs.value) or {} + + @property + def fs(self): + """Current filesystem instance""" + if self._fs is None: + cls = get_filesystem_class(self.protocol.value) + self._fs = cls(**self.storage_options) + return self._fs + + @property + def urlpath(self): + """URL of currently selected item""" + return ( + (f"{self.protocol.value}://{self.main.value[0]}") + if self.main.value + else None + ) + + def open_file(self, mode="rb", compression=None, encoding=None): + """Create OpenFile instance for the currently selected item + + For example, in a notebook you might do something like + + .. code-block:: + + [ ]: sel = FileSelector(); sel + + # user selects their file + + [ ]: with sel.open_file('rb') as f: + ... out = f.read() + + Parameters + ---------- + mode: str (optional) + Open mode for the file. + compression: str (optional) + The interact with the file as compressed. Set to 'infer' to guess + compression from the file ending + encoding: str (optional) + If using text mode, use this encoding; defaults to UTF8. + """ + if self.urlpath is None: + raise ValueError("No file selected") + return OpenFile(self.fs, self.urlpath, mode, compression, encoding) + + def filters_changed(self, values): + self.filters = values + self.go_clicked() + + def selection_changed(self, *_): + if self.urlpath is None: + return + if self.fs.isdir(self.urlpath): + self.url.value = self.fs._strip_protocol(self.urlpath) + self.go_clicked() + + def go_clicked(self, *_): + if ( + self.prev_protocol != self.protocol.value + or self.prev_kwargs != self.storage_options + ): + self._fs = None # causes fs to be recreated + self.prev_protocol = self.protocol.value + self.prev_kwargs = self.storage_options + listing = sorted( + self.fs.ls(self.url.value, detail=True), key=lambda x: x["name"] + ) + listing = [ + l + for l in listing + if not any(i.match(l["name"].rsplit("/", 1)[-1]) for i in self.ignore) + ] + folders = { + "📁 " + o["name"].rsplit("/", 1)[-1]: o["name"] + for o in listing + if o["type"] == "directory" + } + files = { + "📄 " + o["name"].rsplit("/", 1)[-1]: o["name"] + for o in listing + if o["type"] == "file" + } + if self.filters: + files = { + k: v + for k, v in files.items() + if any(v.endswith(ext) for ext in self.filters) + } + self.main.set_options(dict(**folders, **files)) + + def protocol_changed(self, *_): + self._fs = None + self.main.options = [] + self.url.value = "" + + def home_clicked(self, *_): + self.protocol.value = self.init_protocol + self.kwargs.value = self.init_kwargs + self.url.value = self.init_url + self.go_clicked() + + def up_clicked(self, *_): + self.url.value = self.fs._parent(self.url.value) + self.go_clicked() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/arrow.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/arrow.py new file mode 100644 index 0000000000000000000000000000000000000000..227d50930763f56bc8c01556249eb1306347350b --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/arrow.py @@ -0,0 +1,312 @@ +import errno +import io +import os +import secrets +import shutil +from contextlib import suppress +from functools import cached_property, wraps +from urllib.parse import parse_qs + +from fsspec.spec import AbstractFileSystem +from fsspec.utils import ( + get_package_version_without_import, + infer_storage_options, + mirror_from, + tokenize, +) + + +def wrap_exceptions(func): + @wraps(func) + def wrapper(*args, **kwargs): + try: + return func(*args, **kwargs) + except OSError as exception: + if not exception.args: + raise + + message, *args = exception.args + if isinstance(message, str) and "does not exist" in message: + raise FileNotFoundError(errno.ENOENT, message) from exception + else: + raise + + return wrapper + + +PYARROW_VERSION = None + + +class ArrowFSWrapper(AbstractFileSystem): + """FSSpec-compatible wrapper of pyarrow.fs.FileSystem. + + Parameters + ---------- + fs : pyarrow.fs.FileSystem + + """ + + root_marker = "/" + + def __init__(self, fs, **kwargs): + global PYARROW_VERSION + PYARROW_VERSION = get_package_version_without_import("pyarrow") + self.fs = fs + super().__init__(**kwargs) + + @property + def protocol(self): + return self.fs.type_name + + @cached_property + def fsid(self): + return "hdfs_" + tokenize(self.fs.host, self.fs.port) + + @classmethod + def _strip_protocol(cls, path): + ops = infer_storage_options(path) + path = ops["path"] + if path.startswith("//"): + # special case for "hdfs://path" (without the triple slash) + path = path[1:] + return path + + def ls(self, path, detail=False, **kwargs): + path = self._strip_protocol(path) + from pyarrow.fs import FileSelector + + try: + entries = [ + self._make_entry(entry) + for entry in self.fs.get_file_info(FileSelector(path)) + ] + except (FileNotFoundError, NotADirectoryError): + entries = [self.info(path, **kwargs)] + if detail: + return entries + else: + return [entry["name"] for entry in entries] + + def info(self, path, **kwargs): + path = self._strip_protocol(path) + [info] = self.fs.get_file_info([path]) + return self._make_entry(info) + + def exists(self, path): + path = self._strip_protocol(path) + try: + self.info(path) + except FileNotFoundError: + return False + else: + return True + + def _make_entry(self, info): + from pyarrow.fs import FileType + + if info.type is FileType.Directory: + kind = "directory" + elif info.type is FileType.File: + kind = "file" + elif info.type is FileType.NotFound: + raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), info.path) + else: + kind = "other" + + return { + "name": info.path, + "size": info.size, + "type": kind, + "mtime": info.mtime, + } + + @wrap_exceptions + def cp_file(self, path1, path2, **kwargs): + path1 = self._strip_protocol(path1).rstrip("/") + path2 = self._strip_protocol(path2).rstrip("/") + + with self._open(path1, "rb") as lstream: + tmp_fname = f"{path2}.tmp.{secrets.token_hex(6)}" + try: + with self.open(tmp_fname, "wb") as rstream: + shutil.copyfileobj(lstream, rstream) + self.fs.move(tmp_fname, path2) + except BaseException: + with suppress(FileNotFoundError): + self.fs.delete_file(tmp_fname) + raise + + @wrap_exceptions + def mv(self, path1, path2, **kwargs): + path1 = self._strip_protocol(path1).rstrip("/") + path2 = self._strip_protocol(path2).rstrip("/") + self.fs.move(path1, path2) + + @wrap_exceptions + def rm_file(self, path): + path = self._strip_protocol(path) + self.fs.delete_file(path) + + @wrap_exceptions + def rm(self, path, recursive=False, maxdepth=None): + path = self._strip_protocol(path).rstrip("/") + if self.isdir(path): + if recursive: + self.fs.delete_dir(path) + else: + raise ValueError("Can't delete directories without recursive=False") + else: + self.fs.delete_file(path) + + @wrap_exceptions + def _open(self, path, mode="rb", block_size=None, seekable=True, **kwargs): + if mode == "rb": + if seekable: + method = self.fs.open_input_file + else: + method = self.fs.open_input_stream + elif mode == "wb": + method = self.fs.open_output_stream + elif mode == "ab": + method = self.fs.open_append_stream + else: + raise ValueError(f"unsupported mode for Arrow filesystem: {mode!r}") + + _kwargs = {} + if mode != "rb" or not seekable: + if int(PYARROW_VERSION.split(".")[0]) >= 4: + # disable compression auto-detection + _kwargs["compression"] = None + stream = method(path, **_kwargs) + + return ArrowFile(self, stream, path, mode, block_size, **kwargs) + + @wrap_exceptions + def mkdir(self, path, create_parents=True, **kwargs): + path = self._strip_protocol(path) + if create_parents: + self.makedirs(path, exist_ok=True) + else: + self.fs.create_dir(path, recursive=False) + + @wrap_exceptions + def makedirs(self, path, exist_ok=False): + path = self._strip_protocol(path) + self.fs.create_dir(path, recursive=True) + + @wrap_exceptions + def rmdir(self, path): + path = self._strip_protocol(path) + self.fs.delete_dir(path) + + @wrap_exceptions + def modified(self, path): + path = self._strip_protocol(path) + return self.fs.get_file_info(path).mtime + + def cat_file(self, path, start=None, end=None, **kwargs): + kwargs.setdefault("seekable", start not in [None, 0]) + return super().cat_file(path, start=None, end=None, **kwargs) + + def get_file(self, rpath, lpath, **kwargs): + kwargs.setdefault("seekable", False) + super().get_file(rpath, lpath, **kwargs) + + +@mirror_from( + "stream", + [ + "read", + "seek", + "tell", + "write", + "readable", + "writable", + "close", + "seekable", + ], +) +class ArrowFile(io.IOBase): + def __init__(self, fs, stream, path, mode, block_size=None, **kwargs): + self.path = path + self.mode = mode + + self.fs = fs + self.stream = stream + + self.blocksize = self.block_size = block_size + self.kwargs = kwargs + + def __enter__(self): + return self + + @property + def size(self): + if self.stream.seekable(): + return self.stream.size() + return None + + def __exit__(self, *args): + return self.close() + + +class HadoopFileSystem(ArrowFSWrapper): + """A wrapper on top of the pyarrow.fs.HadoopFileSystem + to connect it's interface with fsspec""" + + protocol = "hdfs" + + def __init__( + self, + host="default", + port=0, + user=None, + kerb_ticket=None, + replication=3, + extra_conf=None, + **kwargs, + ): + """ + + Parameters + ---------- + host: str + Hostname, IP or "default" to try to read from Hadoop config + port: int + Port to connect on, or default from Hadoop config if 0 + user: str or None + If given, connect as this username + kerb_ticket: str or None + If given, use this ticket for authentication + replication: int + set replication factor of file for write operations. default value is 3. + extra_conf: None or dict + Passed on to HadoopFileSystem + """ + from pyarrow.fs import HadoopFileSystem + + fs = HadoopFileSystem( + host=host, + port=port, + user=user, + kerb_ticket=kerb_ticket, + replication=replication, + extra_conf=extra_conf, + ) + super().__init__(fs=fs, **kwargs) + + @staticmethod + def _get_kwargs_from_urls(path): + ops = infer_storage_options(path) + out = {} + if ops.get("host", None): + out["host"] = ops["host"] + if ops.get("username", None): + out["user"] = ops["username"] + if ops.get("port", None): + out["port"] = ops["port"] + if ops.get("url_query", None): + queries = parse_qs(ops["url_query"]) + if queries.get("replication", None): + out["replication"] = int(queries["replication"][0]) + return out diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/asyn_wrapper.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/asyn_wrapper.py new file mode 100644 index 0000000000000000000000000000000000000000..91db5eb48d00e36b46d9deb49504a7d2ad76d690 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/asyn_wrapper.py @@ -0,0 +1,124 @@ +import asyncio +import functools +import inspect + +import fsspec +from fsspec.asyn import AsyncFileSystem, running_async + +from .chained import ChainedFileSystem + + +def async_wrapper(func, obj=None, semaphore=None): + """ + Wraps a synchronous function to make it awaitable. + + Parameters + ---------- + func : callable + The synchronous function to wrap. + obj : object, optional + The instance to bind the function to, if applicable. + semaphore : asyncio.Semaphore, optional + A semaphore to limit concurrent calls. + + Returns + ------- + coroutine + An awaitable version of the function. + """ + + @functools.wraps(func) + async def wrapper(*args, **kwargs): + if semaphore: + async with semaphore: + return await asyncio.to_thread(func, *args, **kwargs) + return await asyncio.to_thread(func, *args, **kwargs) + + return wrapper + + +class AsyncFileSystemWrapper(AsyncFileSystem, ChainedFileSystem): + """ + A wrapper class to convert a synchronous filesystem into an asynchronous one. + + This class takes an existing synchronous filesystem implementation and wraps all + its methods to provide an asynchronous interface. + + Parameters + ---------- + sync_fs : AbstractFileSystem + The synchronous filesystem instance to wrap. + """ + + protocol = "asyncwrapper", "async_wrapper" + cachable = False + + def __init__( + self, + fs=None, + asynchronous=None, + target_protocol=None, + target_options=None, + semaphore=None, + max_concurrent_tasks=None, + **kwargs, + ): + if asynchronous is None: + asynchronous = running_async() + super().__init__(asynchronous=asynchronous, **kwargs) + if fs is not None: + self.sync_fs = fs + else: + self.sync_fs = fsspec.filesystem(target_protocol, **target_options) + self.protocol = self.sync_fs.protocol + self.semaphore = semaphore + self._wrap_all_sync_methods() + + @property + def fsid(self): + return f"async_{self.sync_fs.fsid}" + + def _wrap_all_sync_methods(self): + """ + Wrap all synchronous methods of the underlying filesystem with asynchronous versions. + """ + excluded_methods = {"open"} + for method_name in dir(self.sync_fs): + if method_name.startswith("_") or method_name in excluded_methods: + continue + + attr = inspect.getattr_static(self.sync_fs, method_name) + if isinstance(attr, property): + continue + + method = getattr(self.sync_fs, method_name) + if callable(method) and not inspect.iscoroutinefunction(method): + async_method = async_wrapper(method, obj=self, semaphore=self.semaphore) + setattr(self, f"_{method_name}", async_method) + + @classmethod + def wrap_class(cls, sync_fs_class): + """ + Create a new class that can be used to instantiate an AsyncFileSystemWrapper + with lazy instantiation of the underlying synchronous filesystem. + + Parameters + ---------- + sync_fs_class : type + The class of the synchronous filesystem to wrap. + + Returns + ------- + type + A new class that wraps the provided synchronous filesystem class. + """ + + class GeneratedAsyncFileSystemWrapper(cls): + def __init__(self, *args, **kwargs): + sync_fs = sync_fs_class(*args, **kwargs) + super().__init__(sync_fs) + + GeneratedAsyncFileSystemWrapper.__name__ = ( + f"Async{sync_fs_class.__name__}Wrapper" + ) + return GeneratedAsyncFileSystemWrapper diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/cache_mapper.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/cache_mapper.py new file mode 100644 index 0000000000000000000000000000000000000000..6e7c7d88afdddf12f77b26bb635bd8bf1e2bd7f1 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/cache_mapper.py @@ -0,0 +1,75 @@ +from __future__ import annotations + +import abc +import hashlib + +from fsspec.implementations.local import make_path_posix + + +class AbstractCacheMapper(abc.ABC): + """Abstract super-class for mappers from remote URLs to local cached + basenames. + """ + + @abc.abstractmethod + def __call__(self, path: str) -> str: ... + + def __eq__(self, other: object) -> bool: + # Identity only depends on class. When derived classes have attributes + # they will need to be included. + return isinstance(other, type(self)) + + def __hash__(self) -> int: + # Identity only depends on class. When derived classes have attributes + # they will need to be included. + return hash(type(self)) + + +class BasenameCacheMapper(AbstractCacheMapper): + """Cache mapper that uses the basename of the remote URL and a fixed number + of directory levels above this. + + The default is zero directory levels, meaning different paths with the same + basename will have the same cached basename. + """ + + def __init__(self, directory_levels: int = 0): + if directory_levels < 0: + raise ValueError( + "BasenameCacheMapper requires zero or positive directory_levels" + ) + self.directory_levels = directory_levels + + # Separator for directories when encoded as strings. + self._separator = "_@_" + + def __call__(self, path: str) -> str: + path = make_path_posix(path) + prefix, *bits = path.rsplit("/", self.directory_levels + 1) + if bits: + return self._separator.join(bits) + else: + return prefix # No separator found, simple filename + + def __eq__(self, other: object) -> bool: + return super().__eq__(other) and self.directory_levels == other.directory_levels + + def __hash__(self) -> int: + return super().__hash__() ^ hash(self.directory_levels) + + +class HashCacheMapper(AbstractCacheMapper): + """Cache mapper that uses a hash of the remote URL.""" + + def __call__(self, path: str) -> str: + return hashlib.sha256(path.encode()).hexdigest() + + +def create_cache_mapper(same_names: bool) -> AbstractCacheMapper: + """Factory method to create cache mapper for backward compatibility with + ``CachingFileSystem`` constructor using ``same_names`` kwarg. + """ + if same_names: + return BasenameCacheMapper() + else: + return HashCacheMapper() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/cache_metadata.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/cache_metadata.py new file mode 100644 index 0000000000000000000000000000000000000000..9d1f7eb7f846186606921ff6a1539442a0899506 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/cache_metadata.py @@ -0,0 +1,231 @@ +from __future__ import annotations + +import os +import pickle +import time +from typing import TYPE_CHECKING + +from fsspec.utils import atomic_write + +try: + import ujson as json +except ImportError: + if not TYPE_CHECKING: + import json + +if TYPE_CHECKING: + from collections.abc import Iterator + from typing import Any, Literal, TypeAlias + + from .cached import CachingFileSystem + + Detail: TypeAlias = dict[str, Any] + + +class CacheMetadata: + """Cache metadata. + + All reading and writing of cache metadata is performed by this class, + accessing the cached files and blocks is not. + + Metadata is stored in a single file per storage directory in JSON format. + For backward compatibility, also reads metadata stored in pickle format + which is converted to JSON when next saved. + """ + + def __init__(self, storage: list[str]): + """ + + Parameters + ---------- + storage: list[str] + Directories containing cached files, must be at least one. Metadata + is stored in the last of these directories by convention. + """ + if not storage: + raise ValueError("CacheMetadata expects at least one storage location") + + self._storage = storage + self.cached_files: list[Detail] = [{}] + + # Private attribute to force saving of metadata in pickle format rather than + # JSON for use in tests to confirm can read both pickle and JSON formats. + self._force_save_pickle = False + + def _load(self, fn: str) -> Detail: + """Low-level function to load metadata from specific file""" + try: + with open(fn, "r") as f: + loaded = json.load(f) + except ValueError: + with open(fn, "rb") as f: + loaded = pickle.load(f) + for c in loaded.values(): + if isinstance(c.get("blocks"), list): + c["blocks"] = set(c["blocks"]) + return loaded + + def _save(self, metadata_to_save: Detail, fn: str) -> None: + """Low-level function to save metadata to specific file""" + if self._force_save_pickle: + with atomic_write(fn) as f: + pickle.dump(metadata_to_save, f) + else: + with atomic_write(fn, mode="w") as f: + json.dump(metadata_to_save, f) + + def _scan_locations( + self, writable_only: bool = False + ) -> Iterator[tuple[str, str, bool]]: + """Yield locations (filenames) where metadata is stored, and whether + writable or not. + + Parameters + ---------- + writable: bool + Set to True to only yield writable locations. + + Returns + ------- + Yields (str, str, bool) + """ + n = len(self._storage) + for i, storage in enumerate(self._storage): + writable = i == n - 1 + if writable_only and not writable: + continue + yield os.path.join(storage, "cache"), storage, writable + + def check_file( + self, path: str, cfs: CachingFileSystem | None + ) -> Literal[False] | tuple[Detail, str]: + """If path is in cache return its details, otherwise return ``False``. + + If the optional CachingFileSystem is specified then it is used to + perform extra checks to reject possible matches, such as if they are + too old. + """ + for (fn, base, _), cache in zip(self._scan_locations(), self.cached_files): + if path not in cache: + continue + detail = cache[path].copy() + + if cfs is not None: + if cfs.check_files and detail["uid"] != cfs.fs.ukey(path): + # Wrong file as determined by hash of file properties + continue + if cfs.expiry and time.time() - detail["time"] > cfs.expiry: + # Cached file has expired + continue + + fn = os.path.join(base, detail["fn"]) + if os.path.exists(fn): + return detail, fn + return False + + def clear_expired(self, expiry_time: int) -> tuple[list[str], bool]: + """Remove expired metadata from the cache. + + Returns names of files corresponding to expired metadata and a boolean + flag indicating whether the writable cache is empty. Caller is + responsible for deleting the expired files. + """ + expired_files = [] + for path, detail in self.cached_files[-1].copy().items(): + if time.time() - detail["time"] > expiry_time: + fn = detail.get("fn", "") + if not fn: + raise RuntimeError( + f"Cache metadata does not contain 'fn' for {path}" + ) + fn = os.path.join(self._storage[-1], fn) + expired_files.append(fn) + self.cached_files[-1].pop(path) + + if self.cached_files[-1]: + cache_path = os.path.join(self._storage[-1], "cache") + self._save(self.cached_files[-1], cache_path) + + writable_cache_empty = not self.cached_files[-1] + return expired_files, writable_cache_empty + + def load(self) -> None: + """Load all metadata from disk and store in ``self.cached_files``""" + cached_files = [] + for fn, _, _ in self._scan_locations(): + if os.path.exists(fn): + # TODO: consolidate blocks here + cached_files.append(self._load(fn)) + else: + cached_files.append({}) + self.cached_files = cached_files or [{}] + + def on_close_cached_file(self, f: Any, path: str) -> None: + """Perform side-effect actions on closing a cached file. + + The actual closing of the file is the responsibility of the caller. + """ + # File must be writeble, so in self.cached_files[-1] + c = self.cached_files[-1][path] + if c["blocks"] is not True and len(c["blocks"]) * f.blocksize >= f.size: + c["blocks"] = True + + def pop_file(self, path: str) -> str | None: + """Remove metadata of cached file. + + If path is in the cache, return the filename of the cached file, + otherwise return ``None``. Caller is responsible for deleting the + cached file. + """ + details = self.check_file(path, None) + if not details: + return None + _, fn = details + if fn.startswith(self._storage[-1]): + self.cached_files[-1].pop(path) + self.save() + else: + raise PermissionError( + "Can only delete cached file in last, writable cache location" + ) + return fn + + def save(self) -> None: + """Save metadata to disk""" + for (fn, _, writable), cache in zip(self._scan_locations(), self.cached_files): + if not writable: + continue + + if os.path.exists(fn): + cached_files = self._load(fn) + for k, c in cached_files.items(): + if k in cache: + if c["blocks"] is True or cache[k]["blocks"] is True: + c["blocks"] = True + else: + # self.cached_files[*][*]["blocks"] must continue to + # point to the same set object so that updates + # performed by MMapCache are propagated back to + # self.cached_files. + blocks = cache[k]["blocks"] + blocks.update(c["blocks"]) + c["blocks"] = blocks + c["time"] = max(c["time"], cache[k]["time"]) + c["uid"] = cache[k]["uid"] + + # Files can be added to cache after it was written once + for k, c in cache.items(): + if k not in cached_files: + cached_files[k] = c + else: + cached_files = cache + cache = {k: v.copy() for k, v in cached_files.items()} + for c in cache.values(): + if isinstance(c["blocks"], set): + c["blocks"] = list(c["blocks"]) + self._save(cache, fn) + self.cached_files[-1] = cached_files + + def update_file(self, path: str, detail: Detail) -> None: + """Update metadata for specific file in memory, do not save""" + self.cached_files[-1][path] = detail diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/cached.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/cached.py new file mode 100644 index 0000000000000000000000000000000000000000..3f4fc0f662d6989da346a5e672e06466156f5f21 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/cached.py @@ -0,0 +1,1021 @@ +from __future__ import annotations + +import inspect +import logging +import os +import tempfile +import time +import weakref +from collections.abc import Callable +from shutil import rmtree +from typing import TYPE_CHECKING, Any, ClassVar + +from fsspec import filesystem +from fsspec.callbacks import DEFAULT_CALLBACK +from fsspec.compression import compr +from fsspec.core import BaseCache, MMapCache +from fsspec.exceptions import BlocksizeMismatchError +from fsspec.implementations.cache_mapper import create_cache_mapper +from fsspec.implementations.cache_metadata import CacheMetadata +from fsspec.implementations.chained import ChainedFileSystem +from fsspec.implementations.local import LocalFileSystem +from fsspec.spec import AbstractBufferedFile +from fsspec.transaction import Transaction +from fsspec.utils import infer_compression + +if TYPE_CHECKING: + from fsspec.implementations.cache_mapper import AbstractCacheMapper + +logger = logging.getLogger("fsspec.cached") + + +class WriteCachedTransaction(Transaction): + def complete(self, commit=True): + rpaths = [f.path for f in self.files] + lpaths = [f.fn for f in self.files] + if commit: + self.fs.put(lpaths, rpaths) + self.files.clear() + self.fs._intrans = False + self.fs._transaction = None + self.fs = None # break cycle + + +class CachingFileSystem(ChainedFileSystem): + """Locally caching filesystem, layer over any other FS + + This class implements chunk-wise local storage of remote files, for quick + access after the initial download. The files are stored in a given + directory with hashes of URLs for the filenames. If no directory is given, + a temporary one is used, which should be cleaned up by the OS after the + process ends. The files themselves are sparse (as implemented in + :class:`~fsspec.caching.MMapCache`), so only the data which is accessed + takes up space. + + Restrictions: + + - the block-size must be the same for each access of a given file, unless + all blocks of the file have already been read + - caching can only be applied to file-systems which produce files + derived from fsspec.spec.AbstractBufferedFile ; LocalFileSystem is also + allowed, for testing + """ + + protocol: ClassVar[str | tuple[str, ...]] = ("blockcache", "cached") + _strip_tokenize_options = ("fo",) + + def __init__( + self, + target_protocol=None, + cache_storage="TMP", + cache_check=10, + check_files=False, + expiry_time=604800, + target_options=None, + fs=None, + same_names: bool | None = None, + compression=None, + cache_mapper: AbstractCacheMapper | None = None, + **kwargs, + ): + """ + + Parameters + ---------- + target_protocol: str (optional) + Target filesystem protocol. Provide either this or ``fs``. + cache_storage: str or list(str) + Location to store files. If "TMP", this is a temporary directory, + and will be cleaned up by the OS when this process ends (or later). + If a list, each location will be tried in the order given, but + only the last will be considered writable. + cache_check: int + Number of seconds between reload of cache metadata + check_files: bool + Whether to explicitly see if the UID of the remote file matches + the stored one before using. Warning: some file systems such as + HTTP cannot reliably give a unique hash of the contents of some + path, so be sure to set this option to False. + expiry_time: int + The time in seconds after which a local copy is considered useless. + Set to falsy to prevent expiry. The default is equivalent to one + week. + target_options: dict or None + Passed to the instantiation of the FS, if fs is None. + fs: filesystem instance + The target filesystem to run against. Provide this or ``protocol``. + same_names: bool (optional) + By default, target URLs are hashed using a ``HashCacheMapper`` so + that files from different backends with the same basename do not + conflict. If this argument is ``true``, a ``BasenameCacheMapper`` + is used instead. Other cache mapper options are available by using + the ``cache_mapper`` keyword argument. Only one of this and + ``cache_mapper`` should be specified. + compression: str (optional) + To decompress on download. Can be 'infer' (guess from the URL name), + one of the entries in ``fsspec.compression.compr``, or None for no + decompression. + cache_mapper: AbstractCacheMapper (optional) + The object use to map from original filenames to cached filenames. + Only one of this and ``same_names`` should be specified. + """ + super().__init__(**kwargs) + if fs is None and target_protocol is None: + raise ValueError( + "Please provide filesystem instance(fs) or target_protocol" + ) + if not (fs is None) ^ (target_protocol is None): + raise ValueError( + "Both filesystems (fs) and target_protocol may not be both given." + ) + if cache_storage == "TMP": + tempdir = tempfile.mkdtemp() + storage = [tempdir] + weakref.finalize(self, self._remove_tempdir, tempdir) + else: + if isinstance(cache_storage, str): + storage = [cache_storage] + else: + storage = cache_storage + os.makedirs(storage[-1], exist_ok=True) + self.storage = storage + self.kwargs = target_options or {} + self.cache_check = cache_check + self.check_files = check_files + self.expiry = expiry_time + self.compression = compression + + # Size of cache in bytes. If None then the size is unknown and will be + # recalculated the next time cache_size() is called. On writes to the + # cache this is reset to None. + self._cache_size = None + + if same_names is not None and cache_mapper is not None: + raise ValueError( + "Cannot specify both same_names and cache_mapper in " + "CachingFileSystem.__init__" + ) + if cache_mapper is not None: + self._mapper = cache_mapper + else: + self._mapper = create_cache_mapper( + same_names if same_names is not None else False + ) + + self.target_protocol = ( + target_protocol + if isinstance(target_protocol, str) + else (fs.protocol if isinstance(fs.protocol, str) else fs.protocol[0]) + ) + self._metadata = CacheMetadata(self.storage) + self.load_cache() + self.fs = fs if fs is not None else filesystem(target_protocol, **self.kwargs) + + def _strip_protocol(path): + # acts as a method, since each instance has a difference target + return self.fs._strip_protocol(type(self)._strip_protocol(path)) + + self._strip_protocol: Callable = _strip_protocol + + @staticmethod + def _remove_tempdir(tempdir): + try: + rmtree(tempdir) + except Exception: + pass + + def _mkcache(self): + os.makedirs(self.storage[-1], exist_ok=True) + + def cache_size(self): + """Return size of cache in bytes. + + If more than one cache directory is in use, only the size of the last + one (the writable cache directory) is returned. + """ + if self._cache_size is None: + cache_dir = self.storage[-1] + self._cache_size = filesystem("file").du(cache_dir, withdirs=True) + return self._cache_size + + def load_cache(self): + """Read set of stored blocks from file""" + self._metadata.load() + self._mkcache() + self.last_cache = time.time() + + def save_cache(self): + """Save set of stored blocks from file""" + self._mkcache() + self._metadata.save() + self.last_cache = time.time() + self._cache_size = None + + def _check_cache(self): + """Reload caches if time elapsed or any disappeared""" + self._mkcache() + if not self.cache_check: + # explicitly told not to bother checking + return + timecond = time.time() - self.last_cache > self.cache_check + existcond = all(os.path.exists(storage) for storage in self.storage) + if timecond or not existcond: + self.load_cache() + + def _check_file(self, path): + """Is path in cache and still valid""" + path = self._strip_protocol(path) + self._check_cache() + return self._metadata.check_file(path, self) + + def clear_cache(self): + """Remove all files and metadata from the cache + + In the case of multiple cache locations, this clears only the last one, + which is assumed to be the read/write one. + """ + rmtree(self.storage[-1]) + self.load_cache() + self._cache_size = None + + def clear_expired_cache(self, expiry_time=None): + """Remove all expired files and metadata from the cache + + In the case of multiple cache locations, this clears only the last one, + which is assumed to be the read/write one. + + Parameters + ---------- + expiry_time: int + The time in seconds after which a local copy is considered useless. + If not defined the default is equivalent to the attribute from the + file caching instantiation. + """ + + if not expiry_time: + expiry_time = self.expiry + + self._check_cache() + + expired_files, writable_cache_empty = self._metadata.clear_expired(expiry_time) + for fn in expired_files: + if os.path.exists(fn): + os.remove(fn) + + if writable_cache_empty: + rmtree(self.storage[-1]) + self.load_cache() + + self._cache_size = None + + def pop_from_cache(self, path): + """Remove cached version of given file + + Deletes local copy of the given (remote) path. If it is found in a cache + location which is not the last, it is assumed to be read-only, and + raises PermissionError + """ + path = self._strip_protocol(path) + fn = self._metadata.pop_file(path) + if fn is not None: + os.remove(fn) + self._cache_size = None + + def _open( + self, + path, + mode="rb", + block_size=None, + autocommit=True, + cache_options=None, + **kwargs, + ): + """Wrap the target _open + + If the whole file exists in the cache, just open it locally and + return that. + + Otherwise, open the file on the target FS, and make it have a mmap + cache pointing to the location which we determine, in our cache. + The ``blocks`` instance is shared, so as the mmap cache instance + updates, so does the entry in our ``cached_files`` attribute. + We monkey-patch this file, so that when it closes, we call + ``close_and_update`` to save the state of the blocks. + """ + path = self._strip_protocol(path) + + path = self.fs._strip_protocol(path) + if "r" not in mode: + return self.fs._open( + path, + mode=mode, + block_size=block_size, + autocommit=autocommit, + cache_options=cache_options, + **kwargs, + ) + detail = self._check_file(path) + if detail: + # file is in cache + detail, fn = detail + hash, blocks = detail["fn"], detail["blocks"] + if blocks is True: + # stored file is complete + logger.debug("Opening local copy of %s", path) + return open(fn, mode) + # TODO: action where partial file exists in read-only cache + logger.debug("Opening partially cached copy of %s", path) + else: + hash = self._mapper(path) + fn = os.path.join(self.storage[-1], hash) + blocks = set() + detail = { + "original": path, + "fn": hash, + "blocks": blocks, + "time": time.time(), + "uid": self.fs.ukey(path), + } + self._metadata.update_file(path, detail) + logger.debug("Creating local sparse file for %s", path) + + # explicitly submitting the size to the open call will avoid extra + # operations when opening. This is particularly relevant + # for any file that is read over a network, e.g. S3. + size = detail.get("size") + + # call target filesystems open + self._mkcache() + f = self.fs._open( + path, + mode=mode, + block_size=block_size, + autocommit=autocommit, + cache_options=cache_options, + cache_type="none", + size=size, + **kwargs, + ) + + # set size if not already set + if size is None: + detail["size"] = f.size + self._metadata.update_file(path, detail) + + if self.compression: + comp = ( + infer_compression(path) + if self.compression == "infer" + else self.compression + ) + f = compr[comp](f, mode="rb") + if "blocksize" in detail: + if detail["blocksize"] != f.blocksize: + raise BlocksizeMismatchError( + f"Cached file must be reopened with same block" + f" size as original (old: {detail['blocksize']}," + f" new {f.blocksize})" + ) + else: + detail["blocksize"] = f.blocksize + + def _fetch_ranges(ranges): + return self.fs.cat_ranges( + [path] * len(ranges), + [r[0] for r in ranges], + [r[1] for r in ranges], + **kwargs, + ) + + multi_fetcher = None if self.compression else _fetch_ranges + f.cache = MMapCache( + f.blocksize, f._fetch_range, f.size, fn, blocks, multi_fetcher=multi_fetcher + ) + close = f.close + f.close = lambda: self.close_and_update(f, close) + self.save_cache() + return f + + def _parent(self, path): + return self.fs._parent(path) + + def hash_name(self, path: str, *args: Any) -> str: + # Kept for backward compatibility with downstream libraries. + # Ignores extra arguments, previously same_name boolean. + return self._mapper(path) + + def close_and_update(self, f, close): + """Called when a file is closing, so store the set of blocks""" + if f.closed: + return + path = self._strip_protocol(f.path) + self._metadata.on_close_cached_file(f, path) + try: + logger.debug("going to save") + self.save_cache() + logger.debug("saved") + except OSError: + logger.debug("Cache saving failed while closing file") + except NameError: + logger.debug("Cache save failed due to interpreter shutdown") + close() + f.closed = True + + def ls(self, path, detail=True): + return self.fs.ls(path, detail) + + def __getattribute__(self, item): + if item in { + "load_cache", + "_get_cached_file_before_open", + "_open", + "save_cache", + "close_and_update", + "__init__", + "__getattribute__", + "__reduce__", + "_make_local_details", + "open", + "cat", + "cat_file", + "_cat_file", + "cat_ranges", + "_cat_ranges", + "get", + "read_block", + "tail", + "head", + "info", + "ls", + "exists", + "isfile", + "isdir", + "_check_file", + "_check_cache", + "_mkcache", + "clear_cache", + "clear_expired_cache", + "pop_from_cache", + "local_file", + "_paths_from_path", + "get_mapper", + "open_many", + "commit_many", + "hash_name", + "__hash__", + "__eq__", + "to_json", + "to_dict", + "cache_size", + "pipe_file", + "pipe", + "start_transaction", + "end_transaction", + }: + # all the methods defined in this class. Note `open` here, since + # it calls `_open`, but is actually in superclass + return lambda *args, **kw: getattr(type(self), item).__get__(self)( + *args, **kw + ) + if item in ["__reduce_ex__"]: + raise AttributeError + if item in ["transaction"]: + # property + return type(self).transaction.__get__(self) + if item in {"_cache", "transaction_type", "protocol"}: + # class attributes + return getattr(type(self), item) + if item == "__class__": + return type(self) + d = object.__getattribute__(self, "__dict__") + fs = d.get("fs", None) # fs is not immediately defined + if item in d: + return d[item] + elif fs is not None: + if item in fs.__dict__: + # attribute of instance + return fs.__dict__[item] + # attributed belonging to the target filesystem + cls = type(fs) + m = getattr(cls, item) + if (inspect.isfunction(m) or inspect.isdatadescriptor(m)) and ( + not hasattr(m, "__self__") or m.__self__ is None + ): + # instance method + return m.__get__(fs, cls) + return m # class method or attribute + else: + # attributes of the superclass, while target is being set up + return super().__getattribute__(item) + + def __eq__(self, other): + """Test for equality.""" + if self is other: + return True + if not isinstance(other, type(self)): + return False + return ( + self.storage == other.storage + and self.kwargs == other.kwargs + and self.cache_check == other.cache_check + and self.check_files == other.check_files + and self.expiry == other.expiry + and self.compression == other.compression + and self._mapper == other._mapper + and self.target_protocol == other.target_protocol + ) + + def __hash__(self): + """Calculate hash.""" + return ( + hash(tuple(self.storage)) + ^ hash(str(self.kwargs)) + ^ hash(self.cache_check) + ^ hash(self.check_files) + ^ hash(self.expiry) + ^ hash(self.compression) + ^ hash(self._mapper) + ^ hash(self.target_protocol) + ) + + +class WholeFileCacheFileSystem(CachingFileSystem): + """Caches whole remote files on first access + + This class is intended as a layer over any other file system, and + will make a local copy of each file accessed, so that all subsequent + reads are local. This is similar to ``CachingFileSystem``, but without + the block-wise functionality and so can work even when sparse files + are not allowed. See its docstring for definition of the init + arguments. + + The class still needs access to the remote store for listing files, + and may refresh cached files. + """ + + protocol = "filecache" + local_file = True + + def open_many(self, open_files, **kwargs): + paths = [of.path for of in open_files] + if "r" in open_files.mode: + self._mkcache() + else: + return [ + LocalTempFile( + self.fs, + path, + mode=open_files.mode, + fn=os.path.join(self.storage[-1], self._mapper(path)), + **kwargs, + ) + for path in paths + ] + + if self.compression: + raise NotImplementedError + details = [self._check_file(sp) for sp in paths] + downpath = [p for p, d in zip(paths, details) if not d] + downfn0 = [ + os.path.join(self.storage[-1], self._mapper(p)) + for p, d in zip(paths, details) + ] # keep these path names for opening later + downfn = [fn for fn, d in zip(downfn0, details) if not d] + if downpath: + # skip if all files are already cached and up to date + self.fs.get(downpath, downfn) + + # update metadata - only happens when downloads are successful + newdetail = [ + { + "original": path, + "fn": self._mapper(path), + "blocks": True, + "time": time.time(), + "uid": self.fs.ukey(path), + } + for path in downpath + ] + for path, detail in zip(downpath, newdetail): + self._metadata.update_file(path, detail) + self.save_cache() + + def firstpart(fn): + # helper to adapt both whole-file and simple-cache + return fn[1] if isinstance(fn, tuple) else fn + + return [ + open(firstpart(fn0) if fn0 else fn1, mode=open_files.mode) + for fn0, fn1 in zip(details, downfn0) + ] + + def commit_many(self, open_files): + self.fs.put([f.fn for f in open_files], [f.path for f in open_files]) + [f.close() for f in open_files] + for f in open_files: + # in case autocommit is off, and so close did not already delete + try: + os.remove(f.name) + except FileNotFoundError: + pass + self._cache_size = None + + def _make_local_details(self, path): + hash = self._mapper(path) + fn = os.path.join(self.storage[-1], hash) + detail = { + "original": path, + "fn": hash, + "blocks": True, + "time": time.time(), + "uid": self.fs.ukey(path), + } + self._metadata.update_file(path, detail) + logger.debug("Copying %s to local cache", path) + return fn + + def cat( + self, + path, + recursive=False, + on_error="raise", + callback=DEFAULT_CALLBACK, + **kwargs, + ): + paths = self.expand_path( + path, recursive=recursive, maxdepth=kwargs.get("maxdepth") + ) + getpaths = [] + storepaths = [] + fns = [] + out = {} + for p in paths.copy(): + try: + detail = self._check_file(p) + if not detail: + fn = self._make_local_details(p) + getpaths.append(p) + storepaths.append(fn) + else: + detail, fn = detail if isinstance(detail, tuple) else (None, detail) + fns.append(fn) + except Exception as e: + if on_error == "raise": + raise + if on_error == "return": + out[p] = e + paths.remove(p) + + if getpaths: + self.fs.get(getpaths, storepaths) + self.save_cache() + + callback.set_size(len(paths)) + for p, fn in zip(paths, fns): + with open(fn, "rb") as f: + out[p] = f.read() + callback.relative_update(1) + if isinstance(path, str) and len(paths) == 1 and recursive is False: + out = out[paths[0]] + return out + + def _get_cached_file_before_open(self, path, **kwargs): + fn = self._make_local_details(path) + # call target filesystems open + self._mkcache() + if self.compression: + with self.fs._open(path, mode="rb", **kwargs) as f, open(fn, "wb") as f2: + if isinstance(f, AbstractBufferedFile): + # want no type of caching if just downloading whole thing + f.cache = BaseCache(0, f.cache.fetcher, f.size) + comp = ( + infer_compression(path) + if self.compression == "infer" + else self.compression + ) + f = compr[comp](f, mode="rb") + data = True + while data: + block = getattr(f, "blocksize", 5 * 2**20) + data = f.read(block) + f2.write(data) + else: + self.fs.get_file(path, fn) + self.save_cache() + + def _open(self, path, mode="rb", **kwargs): + path = self._strip_protocol(path) + # For read (or append), (try) download from remote + if "r" in mode or "a" in mode: + if not self._check_file(path): + if self.fs.exists(path): + self._get_cached_file_before_open(path, **kwargs) + elif "r" in mode: + raise FileNotFoundError(path) + + detail, fn = self._check_file(path) + _, blocks = detail["fn"], detail["blocks"] + if blocks is True: + logger.debug("Opening local copy of %s", path) + else: + raise ValueError( + f"Attempt to open partially cached file {path}" + f" as a wholly cached file" + ) + + # Just reading does not need special file handling + if "r" in mode and "+" not in mode: + # In order to support downstream filesystems to be able to + # infer the compression from the original filename, like + # the `TarFileSystem`, let's extend the `io.BufferedReader` + # fileobject protocol by adding a dedicated attribute + # `original`. + f = open(fn, mode) + f.original = detail.get("original") + return f + + hash = self._mapper(path) + fn = os.path.join(self.storage[-1], hash) + user_specified_kwargs = { + k: v + for k, v in kwargs.items() + # those kwargs were added by open(), we don't want them + if k not in ["autocommit", "block_size", "cache_options"] + } + return LocalTempFile(self, path, mode=mode, fn=fn, **user_specified_kwargs) + + +class SimpleCacheFileSystem(WholeFileCacheFileSystem): + """Caches whole remote files on first access + + This class is intended as a layer over any other file system, and + will make a local copy of each file accessed, so that all subsequent + reads are local. This implementation only copies whole files, and + does not keep any metadata about the download time or file details. + It is therefore safer to use in multi-threaded/concurrent situations. + + This is the only of the caching filesystems that supports write: you will + be given a real local open file, and upon close and commit, it will be + uploaded to the target filesystem; the writability or the target URL is + not checked until that time. + + """ + + protocol = "simplecache" + local_file = True + transaction_type = WriteCachedTransaction + + def __init__(self, **kwargs): + kw = kwargs.copy() + for key in ["cache_check", "expiry_time", "check_files"]: + kw[key] = False + super().__init__(**kw) + for storage in self.storage: + if not os.path.exists(storage): + os.makedirs(storage, exist_ok=True) + + def _check_file(self, path): + self._check_cache() + sha = self._mapper(path) + for storage in self.storage: + fn = os.path.join(storage, sha) + if os.path.exists(fn): + return fn + + def save_cache(self): + pass + + def load_cache(self): + pass + + def pipe_file(self, path, value=None, **kwargs): + if self._intrans: + with self.open(path, "wb") as f: + f.write(value) + else: + super().pipe_file(path, value) + + def ls(self, path, detail=True, **kwargs): + path = self._strip_protocol(path) + details = [] + try: + details = self.fs.ls( + path, detail=True, **kwargs + ).copy() # don't edit original! + except FileNotFoundError as e: + ex = e + else: + ex = None + if self._intrans: + path1 = path.rstrip("/") + "/" + for f in self.transaction.files: + if f.path == path: + details.append( + {"name": path, "size": f.size or f.tell(), "type": "file"} + ) + elif f.path.startswith(path1): + if f.path.count("/") == path1.count("/"): + details.append( + {"name": f.path, "size": f.size or f.tell(), "type": "file"} + ) + else: + dname = "/".join(f.path.split("/")[: path1.count("/") + 1]) + details.append({"name": dname, "size": 0, "type": "directory"}) + if ex is not None and not details: + raise ex + if detail: + return details + return sorted(_["name"] for _ in details) + + def info(self, path, **kwargs): + path = self._strip_protocol(path) + if self._intrans: + f = [_ for _ in self.transaction.files if _.path == path] + if f: + size = os.path.getsize(f[0].fn) if f[0].closed else f[0].tell() + return {"name": path, "size": size, "type": "file"} + f = any(_.path.startswith(path + "/") for _ in self.transaction.files) + if f: + return {"name": path, "size": 0, "type": "directory"} + return self.fs.info(path, **kwargs) + + def pipe(self, path, value=None, **kwargs): + if isinstance(path, str): + self.pipe_file(self._strip_protocol(path), value, **kwargs) + elif isinstance(path, dict): + for k, v in path.items(): + self.pipe_file(self._strip_protocol(k), v, **kwargs) + else: + raise ValueError("path must be str or dict") + + async def _cat_file(self, path, start=None, end=None, **kwargs): + logger.debug("async cat_file %s", path) + path = self._strip_protocol(path) + sha = self._mapper(path) + fn = self._check_file(path) + + if not fn: + fn = os.path.join(self.storage[-1], sha) + await self.fs._get_file(path, fn, **kwargs) + + with open(fn, "rb") as f: # noqa ASYNC230 + if start: + f.seek(start) + size = -1 if end is None else end - f.tell() + return f.read(size) + + async def _cat_ranges( + self, paths, starts, ends, max_gap=None, on_error="return", **kwargs + ): + logger.debug("async cat ranges %s", paths) + lpaths = [] + rset = set() + download = [] + rpaths = [] + for p in paths: + fn = self._check_file(p) + if fn is None and p not in rset: + sha = self._mapper(p) + fn = os.path.join(self.storage[-1], sha) + download.append(fn) + rset.add(p) + rpaths.append(p) + lpaths.append(fn) + if download: + await self.fs._get(rpaths, download, on_error=on_error) + + return LocalFileSystem().cat_ranges( + lpaths, starts, ends, max_gap=max_gap, on_error=on_error, **kwargs + ) + + def cat_ranges( + self, paths, starts, ends, max_gap=None, on_error="return", **kwargs + ): + logger.debug("cat ranges %s", paths) + lpaths = [self._check_file(p) for p in paths] + rpaths = [p for l, p in zip(lpaths, paths) if l is False] + lpaths = [l for l, p in zip(lpaths, paths) if l is False] + self.fs.get(rpaths, lpaths) + paths = [self._check_file(p) for p in paths] + return LocalFileSystem().cat_ranges( + paths, starts, ends, max_gap=max_gap, on_error=on_error, **kwargs + ) + + def _get_cached_file_before_open(self, path, **kwargs): + sha = self._mapper(path) + fn = os.path.join(self.storage[-1], sha) + logger.debug("Copying %s to local cache", path) + + self._mkcache() + self._cache_size = None + + if self.compression: + with self.fs._open(path, mode="rb", **kwargs) as f, open(fn, "wb") as f2: + if isinstance(f, AbstractBufferedFile): + # want no type of caching if just downloading whole thing + f.cache = BaseCache(0, f.cache.fetcher, f.size) + comp = ( + infer_compression(path) + if self.compression == "infer" + else self.compression + ) + f = compr[comp](f, mode="rb") + data = True + while data: + block = getattr(f, "blocksize", 5 * 2**20) + data = f.read(block) + f2.write(data) + else: + self.fs.get_file(path, fn) + + def _open(self, path, mode="rb", **kwargs): + path = self._strip_protocol(path) + sha = self._mapper(path) + + # For read (or append), (try) download from remote + if "r" in mode or "a" in mode: + if not self._check_file(path): + # append does not require an existing file but read does + if self.fs.exists(path): + self._get_cached_file_before_open(path, **kwargs) + elif "r" in mode: + raise FileNotFoundError(path) + + fn = self._check_file(path) + # Just reading does not need special file handling + if "r" in mode and "+" not in mode: + return open(fn, mode) + + fn = os.path.join(self.storage[-1], sha) + user_specified_kwargs = { + k: v + for k, v in kwargs.items() + if k not in ["autocommit", "block_size", "cache_options"] + } # those were added by open() + return LocalTempFile( + self, + path, + mode=mode, + autocommit=not self._intrans, + fn=fn, + **user_specified_kwargs, + ) + + +class LocalTempFile: + """A temporary local file, which will be uploaded on commit""" + + def __init__(self, fs, path, fn, mode="wb", autocommit=True, seek=0, **kwargs): + self.fn = fn + self.fh = open(fn, mode) + self.mode = mode + if seek: + self.fh.seek(seek) + self.path = path + self.size = None + self.fs = fs + self.closed = False + self.autocommit = autocommit + self.kwargs = kwargs + + def __reduce__(self): + # always open in r+b to allow continuing writing at a location + return ( + LocalTempFile, + (self.fs, self.path, self.fn, "r+b", self.autocommit, self.tell()), + ) + + def __enter__(self): + return self.fh + + def __exit__(self, exc_type, exc_val, exc_tb): + self.close() + + def close(self): + # self.size = self.fh.tell() + if self.closed: + return + self.fh.close() + self.closed = True + if self.autocommit: + self.commit() + + def discard(self): + self.fh.close() + os.remove(self.fn) + + def commit(self): + # calling put() with list arguments avoids path expansion and additional operations + # like isdir() + self.fs.put([self.fn], [self.path], **self.kwargs) + # we do not delete the local copy, it's still in the cache. + + @property + def name(self): + return self.fn + + def __repr__(self) -> str: + return f"LocalTempFile: {self.path}" + + def __getattr__(self, item): + return getattr(self.fh, item) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/chained.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/chained.py new file mode 100644 index 0000000000000000000000000000000000000000..bfce64334e8db0272eefa96b4428b23524b059f0 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/chained.py @@ -0,0 +1,23 @@ +from typing import ClassVar + +from fsspec import AbstractFileSystem + +__all__ = ("ChainedFileSystem",) + + +class ChainedFileSystem(AbstractFileSystem): + """Chained filesystem base class. + + A chained filesystem is designed to be layered over another FS. + This is useful to implement things like caching. + + This base class does very little on its own, but is used as a marker + that the class is designed for chaining. + + Right now this is only used in `url_to_fs` to provide the path argument + (`fo`) to the chained filesystem from the underlying filesystem. + + Additional functionality may be added in the future. + """ + + protocol: ClassVar[str] = "chained" diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/dask.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/dask.py new file mode 100644 index 0000000000000000000000000000000000000000..3e1276463db6866665e6a0fe114efc247971b57e --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/dask.py @@ -0,0 +1,152 @@ +import dask +from distributed.client import Client, _get_global_client +from distributed.worker import Worker + +from fsspec import filesystem +from fsspec.spec import AbstractBufferedFile, AbstractFileSystem +from fsspec.utils import infer_storage_options + + +def _get_client(client): + if client is None: + return _get_global_client() + elif isinstance(client, Client): + return client + else: + # e.g., connection string + return Client(client) + + +def _in_worker(): + return bool(Worker._instances) + + +class DaskWorkerFileSystem(AbstractFileSystem): + """View files accessible to a worker as any other remote file-system + + When instances are run on the worker, uses the real filesystem. When + run on the client, they call the worker to provide information or data. + + **Warning** this implementation is experimental, and read-only for now. + """ + + def __init__( + self, target_protocol=None, target_options=None, fs=None, client=None, **kwargs + ): + super().__init__(**kwargs) + if not (fs is None) ^ (target_protocol is None): + raise ValueError( + "Please provide one of filesystem instance (fs) or" + " target_protocol, not both" + ) + self.target_protocol = target_protocol + self.target_options = target_options + self.worker = None + self.client = client + self.fs = fs + self._determine_worker() + + @staticmethod + def _get_kwargs_from_urls(path): + so = infer_storage_options(path) + if "host" in so and "port" in so: + return {"client": f"{so['host']}:{so['port']}"} + else: + return {} + + def _determine_worker(self): + if _in_worker(): + self.worker = True + if self.fs is None: + self.fs = filesystem( + self.target_protocol, **(self.target_options or {}) + ) + else: + self.worker = False + self.client = _get_client(self.client) + self.rfs = dask.delayed(self) + + def mkdir(self, *args, **kwargs): + if self.worker: + self.fs.mkdir(*args, **kwargs) + else: + self.rfs.mkdir(*args, **kwargs).compute() + + def rm(self, *args, **kwargs): + if self.worker: + self.fs.rm(*args, **kwargs) + else: + self.rfs.rm(*args, **kwargs).compute() + + def copy(self, *args, **kwargs): + if self.worker: + self.fs.copy(*args, **kwargs) + else: + self.rfs.copy(*args, **kwargs).compute() + + def mv(self, *args, **kwargs): + if self.worker: + self.fs.mv(*args, **kwargs) + else: + self.rfs.mv(*args, **kwargs).compute() + + def ls(self, *args, **kwargs): + if self.worker: + return self.fs.ls(*args, **kwargs) + else: + return self.rfs.ls(*args, **kwargs).compute() + + def _open( + self, + path, + mode="rb", + block_size=None, + autocommit=True, + cache_options=None, + **kwargs, + ): + if self.worker: + return self.fs._open( + path, + mode=mode, + block_size=block_size, + autocommit=autocommit, + cache_options=cache_options, + **kwargs, + ) + else: + return DaskFile( + fs=self, + path=path, + mode=mode, + block_size=block_size, + autocommit=autocommit, + cache_options=cache_options, + **kwargs, + ) + + def fetch_range(self, path, mode, start, end): + if self.worker: + with self._open(path, mode) as f: + f.seek(start) + return f.read(end - start) + else: + return self.rfs.fetch_range(path, mode, start, end).compute() + + +class DaskFile(AbstractBufferedFile): + def __init__(self, mode="rb", **kwargs): + if mode != "rb": + raise ValueError('Remote dask files can only be opened in "rb" mode') + super().__init__(**kwargs) + + def _upload_chunk(self, final=False): + pass + + def _initiate_upload(self): + """Create remote file/upload""" + pass + + def _fetch_range(self, start, end): + """Get the specified set of bytes from remote""" + return self.fs.fetch_range(self.path, self.mode, start, end) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/data.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/data.py new file mode 100644 index 0000000000000000000000000000000000000000..f11542b48c98fd53fc367ade7425a00b38487619 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/data.py @@ -0,0 +1,57 @@ +import base64 +import io +from urllib.parse import unquote + +from fsspec import AbstractFileSystem + + +class DataFileSystem(AbstractFileSystem): + """A handy decoder for data-URLs + + Example + ------- + >>> with fsspec.open("data:,Hello%2C%20World%21") as f: + ... print(f.read()) + b"Hello, World!" + + See https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs + """ + + protocol = "data" + + def __init__(self, **kwargs): + """No parameters for this filesystem""" + super().__init__(**kwargs) + + def cat_file(self, path, start=None, end=None, **kwargs): + pref, data = path.split(",", 1) + if pref.endswith("base64"): + return base64.b64decode(data)[start:end] + return unquote(data).encode()[start:end] + + def info(self, path, **kwargs): + pref, name = path.split(",", 1) + data = self.cat_file(path) + mime = pref.split(":", 1)[1].split(";", 1)[0] + return {"name": name, "size": len(data), "type": "file", "mimetype": mime} + + def _open( + self, + path, + mode="rb", + block_size=None, + autocommit=True, + cache_options=None, + **kwargs, + ): + if "r" not in mode: + raise ValueError("Read only filesystem") + return io.BytesIO(self.cat_file(path)) + + @staticmethod + def encode(data: bytes, mime: str | None = None): + """Format the given data into data-URL syntax + + This version always base64 encodes, even when the data is ascii/url-safe. + """ + return f"data:{mime or ''};base64,{base64.b64encode(data).decode()}" diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/dbfs.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/dbfs.py new file mode 100644 index 0000000000000000000000000000000000000000..1a7fc93d7389c894ecb5fc6267ce20abe4087068 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/dbfs.py @@ -0,0 +1,496 @@ +from __future__ import annotations + +import base64 +import urllib + +import requests +from requests.adapters import HTTPAdapter, Retry +from typing_extensions import override + +from fsspec import AbstractFileSystem +from fsspec.spec import AbstractBufferedFile + + +class DatabricksException(Exception): + """ + Helper class for exceptions raised in this module. + """ + + def __init__(self, error_code, message, details=None): + """Create a new DatabricksException""" + super().__init__(message) + + self.error_code = error_code + self.message = message + self.details = details + + +class DatabricksFileSystem(AbstractFileSystem): + """ + Get access to the Databricks filesystem implementation over HTTP. + Can be used inside and outside of a databricks cluster. + """ + + def __init__(self, instance, token, **kwargs): + """ + Create a new DatabricksFileSystem. + + Parameters + ---------- + instance: str + The instance URL of the databricks cluster. + For example for an Azure databricks cluster, this + has the form adb-..azuredatabricks.net. + token: str + Your personal token. Find out more + here: https://docs.databricks.com/dev-tools/api/latest/authentication.html + """ + self.instance = instance + self.token = token + self.session = requests.Session() + self.retries = Retry( + total=10, + backoff_factor=0.05, + status_forcelist=[408, 429, 500, 502, 503, 504], + ) + + self.session.mount("https://", HTTPAdapter(max_retries=self.retries)) + self.session.headers.update({"Authorization": f"Bearer {self.token}"}) + + super().__init__(**kwargs) + + @override + def _ls_from_cache(self, path) -> list[dict[str, str | int]] | None: + """Check cache for listing + + Returns listing, if found (may be empty list for a directory that + exists but contains nothing), None if not in cache. + """ + self.dircache.pop(path.rstrip("/"), None) + + parent = self._parent(path) + if parent in self.dircache: + for entry in self.dircache[parent]: + if entry["name"] == path.rstrip("/"): + if entry["type"] != "directory": + return [entry] + return [] + raise FileNotFoundError(path) + + def ls(self, path, detail=True, **kwargs): + """ + List the contents of the given path. + + Parameters + ---------- + path: str + Absolute path + detail: bool + Return not only the list of filenames, + but also additional information on file sizes + and types. + """ + try: + out = self._ls_from_cache(path) + except FileNotFoundError: + # This happens if the `path`'s parent was cached, but `path` is not + # there. This suggests that `path` is new since the parent was + # cached. Attempt to invalidate parent's cache before continuing. + self.dircache.pop(self._parent(path), None) + out = None + + if not out: + try: + r = self._send_to_api( + method="get", endpoint="list", json={"path": path} + ) + except DatabricksException as e: + if e.error_code == "RESOURCE_DOES_NOT_EXIST": + raise FileNotFoundError(e.message) from e + + raise + files = r.get("files", []) + out = [ + { + "name": o["path"], + "type": "directory" if o["is_dir"] else "file", + "size": o["file_size"], + } + for o in files + ] + self.dircache[path] = out + + if detail: + return out + return [o["name"] for o in out] + + def makedirs(self, path, exist_ok=True): + """ + Create a given absolute path and all of its parents. + + Parameters + ---------- + path: str + Absolute path to create + exist_ok: bool + If false, checks if the folder + exists before creating it (and raises an + Exception if this is the case) + """ + if not exist_ok: + try: + # If the following succeeds, the path is already present + self._send_to_api( + method="get", endpoint="get-status", json={"path": path} + ) + raise FileExistsError(f"Path {path} already exists") + except DatabricksException as e: + if e.error_code == "RESOURCE_DOES_NOT_EXIST": + pass + + try: + self._send_to_api(method="post", endpoint="mkdirs", json={"path": path}) + except DatabricksException as e: + if e.error_code == "RESOURCE_ALREADY_EXISTS": + raise FileExistsError(e.message) from e + + raise + self.invalidate_cache(self._parent(path)) + + def mkdir(self, path, create_parents=True, **kwargs): + """ + Create a given absolute path and all of its parents. + + Parameters + ---------- + path: str + Absolute path to create + create_parents: bool + Whether to create all parents or not. + "False" is not implemented so far. + """ + if not create_parents: + raise NotImplementedError + + self.mkdirs(path, **kwargs) + + def rm(self, path, recursive=False, **kwargs): + """ + Remove the file or folder at the given absolute path. + + Parameters + ---------- + path: str + Absolute path what to remove + recursive: bool + Recursively delete all files in a folder. + """ + try: + self._send_to_api( + method="post", + endpoint="delete", + json={"path": path, "recursive": recursive}, + ) + except DatabricksException as e: + # This is not really an exception, it just means + # not everything was deleted so far + if e.error_code == "PARTIAL_DELETE": + self.rm(path=path, recursive=recursive) + elif e.error_code == "IO_ERROR": + # Using the same exception as the os module would use here + raise OSError(e.message) from e + + raise + self.invalidate_cache(self._parent(path)) + + def mv( + self, source_path, destination_path, recursive=False, maxdepth=None, **kwargs + ): + """ + Move a source to a destination path. + + A note from the original [databricks API manual] + (https://docs.databricks.com/dev-tools/api/latest/dbfs.html#move). + + When moving a large number of files the API call will time out after + approximately 60s, potentially resulting in partially moved data. + Therefore, for operations that move more than 10k files, we strongly + discourage using the DBFS REST API. + + Parameters + ---------- + source_path: str + From where to move (absolute path) + destination_path: str + To where to move (absolute path) + recursive: bool + Not implemented to far. + maxdepth: + Not implemented to far. + """ + if recursive: + raise NotImplementedError + if maxdepth: + raise NotImplementedError + + try: + self._send_to_api( + method="post", + endpoint="move", + json={"source_path": source_path, "destination_path": destination_path}, + ) + except DatabricksException as e: + if e.error_code == "RESOURCE_DOES_NOT_EXIST": + raise FileNotFoundError(e.message) from e + elif e.error_code == "RESOURCE_ALREADY_EXISTS": + raise FileExistsError(e.message) from e + + raise + self.invalidate_cache(self._parent(source_path)) + self.invalidate_cache(self._parent(destination_path)) + + def _open(self, path, mode="rb", block_size="default", **kwargs): + """ + Overwrite the base class method to make sure to create a DBFile. + All arguments are copied from the base method. + + Only the default blocksize is allowed. + """ + return DatabricksFile(self, path, mode=mode, block_size=block_size, **kwargs) + + def _send_to_api(self, method, endpoint, json): + """ + Send the given json to the DBFS API + using a get or post request (specified by the argument `method`). + + Parameters + ---------- + method: str + Which http method to use for communication; "get" or "post". + endpoint: str + Where to send the request to (last part of the API URL) + json: dict + Dictionary of information to send + """ + if method == "post": + session_call = self.session.post + elif method == "get": + session_call = self.session.get + else: + raise ValueError(f"Do not understand method {method}") + + url = urllib.parse.urljoin(f"https://{self.instance}/api/2.0/dbfs/", endpoint) + + r = session_call(url, json=json) + + # The DBFS API will return a json, also in case of an exception. + # We want to preserve this information as good as possible. + try: + r.raise_for_status() + except requests.HTTPError as e: + # try to extract json error message + # if that fails, fall back to the original exception + try: + exception_json = e.response.json() + except Exception: + raise e from None + + raise DatabricksException(**exception_json) from e + + return r.json() + + def _create_handle(self, path, overwrite=True): + """ + Internal function to create a handle, which can be used to + write blocks of a file to DBFS. + A handle has a unique identifier which needs to be passed + whenever written during this transaction. + The handle is active for 10 minutes - after that a new + write transaction needs to be created. + Make sure to close the handle after you are finished. + + Parameters + ---------- + path: str + Absolute path for this file. + overwrite: bool + If a file already exist at this location, either overwrite + it or raise an exception. + """ + try: + r = self._send_to_api( + method="post", + endpoint="create", + json={"path": path, "overwrite": overwrite}, + ) + return r["handle"] + except DatabricksException as e: + if e.error_code == "RESOURCE_ALREADY_EXISTS": + raise FileExistsError(e.message) from e + + raise + + def _close_handle(self, handle): + """ + Close a handle, which was opened by :func:`_create_handle`. + + Parameters + ---------- + handle: str + Which handle to close. + """ + try: + self._send_to_api(method="post", endpoint="close", json={"handle": handle}) + except DatabricksException as e: + if e.error_code == "RESOURCE_DOES_NOT_EXIST": + raise FileNotFoundError(e.message) from e + + raise + + def _add_data(self, handle, data): + """ + Upload data to an already opened file handle + (opened by :func:`_create_handle`). + The maximal allowed data size is 1MB after + conversion to base64. + Remember to close the handle when you are finished. + + Parameters + ---------- + handle: str + Which handle to upload data to. + data: bytes + Block of data to add to the handle. + """ + data = base64.b64encode(data).decode() + try: + self._send_to_api( + method="post", + endpoint="add-block", + json={"handle": handle, "data": data}, + ) + except DatabricksException as e: + if e.error_code == "RESOURCE_DOES_NOT_EXIST": + raise FileNotFoundError(e.message) from e + elif e.error_code == "MAX_BLOCK_SIZE_EXCEEDED": + raise ValueError(e.message) from e + + raise + + def _get_data(self, path, start, end): + """ + Download data in bytes from a given absolute path in a block + from [start, start+length]. + The maximum number of allowed bytes to read is 1MB. + + Parameters + ---------- + path: str + Absolute path to download data from + start: int + Start position of the block + end: int + End position of the block + """ + try: + r = self._send_to_api( + method="get", + endpoint="read", + json={"path": path, "offset": start, "length": end - start}, + ) + return base64.b64decode(r["data"]) + except DatabricksException as e: + if e.error_code == "RESOURCE_DOES_NOT_EXIST": + raise FileNotFoundError(e.message) from e + elif e.error_code in ["INVALID_PARAMETER_VALUE", "MAX_READ_SIZE_EXCEEDED"]: + raise ValueError(e.message) from e + + raise + + def invalidate_cache(self, path=None): + if path is None: + self.dircache.clear() + else: + self.dircache.pop(path, None) + super().invalidate_cache(path) + + +class DatabricksFile(AbstractBufferedFile): + """ + Helper class for files referenced in the DatabricksFileSystem. + """ + + DEFAULT_BLOCK_SIZE = 1 * 2**20 # only allowed block size + + def __init__( + self, + fs, + path, + mode="rb", + block_size="default", + autocommit=True, + cache_type="readahead", + cache_options=None, + **kwargs, + ): + """ + Create a new instance of the DatabricksFile. + + The blocksize needs to be the default one. + """ + if block_size is None or block_size == "default": + block_size = self.DEFAULT_BLOCK_SIZE + + assert block_size == self.DEFAULT_BLOCK_SIZE, ( + f"Only the default block size is allowed, not {block_size}" + ) + + super().__init__( + fs, + path, + mode=mode, + block_size=block_size, + autocommit=autocommit, + cache_type=cache_type, + cache_options=cache_options or {}, + **kwargs, + ) + + def _initiate_upload(self): + """Internal function to start a file upload""" + self.handle = self.fs._create_handle(self.path) + + def _upload_chunk(self, final=False): + """Internal function to add a chunk of data to a started upload""" + self.buffer.seek(0) + data = self.buffer.getvalue() + + data_chunks = [ + data[start:end] for start, end in self._to_sized_blocks(len(data)) + ] + + for data_chunk in data_chunks: + self.fs._add_data(handle=self.handle, data=data_chunk) + + if final: + self.fs._close_handle(handle=self.handle) + return True + + def _fetch_range(self, start, end): + """Internal function to download a block of data""" + return_buffer = b"" + length = end - start + for chunk_start, chunk_end in self._to_sized_blocks(length, start): + return_buffer += self.fs._get_data( + path=self.path, start=chunk_start, end=chunk_end + ) + + return return_buffer + + def _to_sized_blocks(self, length, start=0): + """Helper function to split a range from 0 to total_length into blocksizes""" + end = start + length + for data_chunk in range(start, end, self.blocksize): + data_start = data_chunk + data_end = min(end, data_chunk + self.blocksize) + yield data_start, data_end diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/dirfs.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/dirfs.py new file mode 100644 index 0000000000000000000000000000000000000000..0f3dd3cf4c2f421292ba5d9fab8b733a60550496 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/dirfs.py @@ -0,0 +1,389 @@ +from .. import filesystem +from ..asyn import AsyncFileSystem +from .chained import ChainedFileSystem + + +class DirFileSystem(AsyncFileSystem, ChainedFileSystem): + """Directory prefix filesystem + + The DirFileSystem is a filesystem-wrapper. It assumes every path it is dealing with + is relative to the `path`. After performing the necessary paths operation it + delegates everything to the wrapped filesystem. + """ + + protocol = "dir" + + def __init__( + self, + path=None, + fs=None, + fo=None, + target_protocol=None, + target_options=None, + **storage_options, + ): + """ + Parameters + ---------- + path: str + Path to the directory. + fs: AbstractFileSystem + An instantiated filesystem to wrap. + target_protocol, target_options: + if fs is none, construct it from these + fo: str + Alternate for path; do not provide both + """ + super().__init__(**storage_options) + if fs is None: + fs = filesystem(protocol=target_protocol, **(target_options or {})) + path = path or fo + + if self.asynchronous and not fs.async_impl: + raise ValueError("can't use asynchronous with non-async fs") + + if fs.async_impl and self.asynchronous != fs.asynchronous: + raise ValueError("both dirfs and fs should be in the same sync/async mode") + + self.path = fs._strip_protocol(path) + self.fs = fs + + def _join(self, path): + if isinstance(path, str): + if not self.path: + return path + if not path: + return self.path + return self.fs.sep.join((self.path, self._strip_protocol(path))) + if isinstance(path, dict): + return {self._join(_path): value for _path, value in path.items()} + return [self._join(_path) for _path in path] + + def _relpath(self, path): + if isinstance(path, str): + if not self.path: + return path + # We need to account for S3FileSystem returning paths that do not + # start with a '/' + if path == self.path or ( + self.path.startswith(self.fs.sep) and path == self.path[1:] + ): + return "" + prefix = self.path + self.fs.sep + if self.path.startswith(self.fs.sep) and not path.startswith(self.fs.sep): + prefix = prefix[1:] + assert path.startswith(prefix) + return path[len(prefix) :] + return [self._relpath(_path) for _path in path] + + # Wrappers below + + @property + def sep(self): + return self.fs.sep + + async def set_session(self, *args, **kwargs): + return await self.fs.set_session(*args, **kwargs) + + async def _rm_file(self, path, **kwargs): + return await self.fs._rm_file(self._join(path), **kwargs) + + def rm_file(self, path, **kwargs): + return self.fs.rm_file(self._join(path), **kwargs) + + async def _rm(self, path, *args, **kwargs): + return await self.fs._rm(self._join(path), *args, **kwargs) + + def rm(self, path, *args, **kwargs): + return self.fs.rm(self._join(path), *args, **kwargs) + + async def _cp_file(self, path1, path2, **kwargs): + return await self.fs._cp_file(self._join(path1), self._join(path2), **kwargs) + + def cp_file(self, path1, path2, **kwargs): + return self.fs.cp_file(self._join(path1), self._join(path2), **kwargs) + + async def _copy( + self, + path1, + path2, + *args, + **kwargs, + ): + return await self.fs._copy( + self._join(path1), + self._join(path2), + *args, + **kwargs, + ) + + def copy(self, path1, path2, *args, **kwargs): + return self.fs.copy( + self._join(path1), + self._join(path2), + *args, + **kwargs, + ) + + async def _pipe(self, path, *args, **kwargs): + return await self.fs._pipe(self._join(path), *args, **kwargs) + + def pipe(self, path, *args, **kwargs): + return self.fs.pipe(self._join(path), *args, **kwargs) + + async def _pipe_file(self, path, *args, **kwargs): + return await self.fs._pipe_file(self._join(path), *args, **kwargs) + + def pipe_file(self, path, *args, **kwargs): + return self.fs.pipe_file(self._join(path), *args, **kwargs) + + async def _cat_file(self, path, *args, **kwargs): + return await self.fs._cat_file(self._join(path), *args, **kwargs) + + def cat_file(self, path, *args, **kwargs): + return self.fs.cat_file(self._join(path), *args, **kwargs) + + async def _cat(self, path, *args, **kwargs): + ret = await self.fs._cat( + self._join(path), + *args, + **kwargs, + ) + + if isinstance(ret, dict): + return {self._relpath(key): value for key, value in ret.items()} + + return ret + + def cat(self, path, *args, **kwargs): + ret = self.fs.cat( + self._join(path), + *args, + **kwargs, + ) + + if isinstance(ret, dict): + return {self._relpath(key): value for key, value in ret.items()} + + return ret + + async def _put_file(self, lpath, rpath, **kwargs): + return await self.fs._put_file(lpath, self._join(rpath), **kwargs) + + def put_file(self, lpath, rpath, **kwargs): + return self.fs.put_file(lpath, self._join(rpath), **kwargs) + + async def _put( + self, + lpath, + rpath, + *args, + **kwargs, + ): + return await self.fs._put( + lpath, + self._join(rpath), + *args, + **kwargs, + ) + + def put(self, lpath, rpath, *args, **kwargs): + return self.fs.put( + lpath, + self._join(rpath), + *args, + **kwargs, + ) + + async def _get_file(self, rpath, lpath, **kwargs): + return await self.fs._get_file(self._join(rpath), lpath, **kwargs) + + def get_file(self, rpath, lpath, **kwargs): + return self.fs.get_file(self._join(rpath), lpath, **kwargs) + + async def _get(self, rpath, *args, **kwargs): + return await self.fs._get(self._join(rpath), *args, **kwargs) + + def get(self, rpath, *args, **kwargs): + return self.fs.get(self._join(rpath), *args, **kwargs) + + async def _isfile(self, path): + return await self.fs._isfile(self._join(path)) + + def isfile(self, path): + return self.fs.isfile(self._join(path)) + + async def _isdir(self, path): + return await self.fs._isdir(self._join(path)) + + def isdir(self, path): + return self.fs.isdir(self._join(path)) + + async def _size(self, path): + return await self.fs._size(self._join(path)) + + def size(self, path): + return self.fs.size(self._join(path)) + + async def _exists(self, path): + return await self.fs._exists(self._join(path)) + + def exists(self, path): + return self.fs.exists(self._join(path)) + + async def _info(self, path, **kwargs): + info = await self.fs._info(self._join(path), **kwargs) + info = info.copy() + info["name"] = self._relpath(info["name"]) + return info + + def info(self, path, **kwargs): + info = self.fs.info(self._join(path), **kwargs) + info = info.copy() + info["name"] = self._relpath(info["name"]) + return info + + async def _ls(self, path, detail=True, **kwargs): + ret = (await self.fs._ls(self._join(path), detail=detail, **kwargs)).copy() + if detail: + out = [] + for entry in ret: + entry = entry.copy() + entry["name"] = self._relpath(entry["name"]) + out.append(entry) + return out + + return self._relpath(ret) + + def ls(self, path, detail=True, **kwargs): + ret = self.fs.ls(self._join(path), detail=detail, **kwargs).copy() + if detail: + out = [] + for entry in ret: + entry = entry.copy() + entry["name"] = self._relpath(entry["name"]) + out.append(entry) + return out + + return self._relpath(ret) + + async def _walk(self, path, *args, **kwargs): + async for root, dirs, files in self.fs._walk(self._join(path), *args, **kwargs): + yield self._relpath(root), dirs, files + + def walk(self, path, *args, **kwargs): + for root, dirs, files in self.fs.walk(self._join(path), *args, **kwargs): + yield self._relpath(root), dirs, files + + async def _glob(self, path, **kwargs): + detail = kwargs.get("detail", False) + ret = await self.fs._glob(self._join(path), **kwargs) + if detail: + return {self._relpath(path): info for path, info in ret.items()} + return self._relpath(ret) + + def glob(self, path, **kwargs): + detail = kwargs.get("detail", False) + ret = self.fs.glob(self._join(path), **kwargs) + if detail: + return {self._relpath(path): info for path, info in ret.items()} + return self._relpath(ret) + + async def _du(self, path, *args, **kwargs): + total = kwargs.get("total", True) + ret = await self.fs._du(self._join(path), *args, **kwargs) + if total: + return ret + + return {self._relpath(path): size for path, size in ret.items()} + + def du(self, path, *args, **kwargs): + total = kwargs.get("total", True) + ret = self.fs.du(self._join(path), *args, **kwargs) + if total: + return ret + + return {self._relpath(path): size for path, size in ret.items()} + + async def _find(self, path, *args, **kwargs): + detail = kwargs.get("detail", False) + ret = await self.fs._find(self._join(path), *args, **kwargs) + if detail: + return {self._relpath(path): info for path, info in ret.items()} + return self._relpath(ret) + + def find(self, path, *args, **kwargs): + detail = kwargs.get("detail", False) + ret = self.fs.find(self._join(path), *args, **kwargs) + if detail: + return {self._relpath(path): info for path, info in ret.items()} + return self._relpath(ret) + + async def _expand_path(self, path, *args, **kwargs): + return self._relpath( + await self.fs._expand_path(self._join(path), *args, **kwargs) + ) + + def expand_path(self, path, *args, **kwargs): + return self._relpath(self.fs.expand_path(self._join(path), *args, **kwargs)) + + async def _mkdir(self, path, *args, **kwargs): + return await self.fs._mkdir(self._join(path), *args, **kwargs) + + def mkdir(self, path, *args, **kwargs): + return self.fs.mkdir(self._join(path), *args, **kwargs) + + async def _makedirs(self, path, *args, **kwargs): + return await self.fs._makedirs(self._join(path), *args, **kwargs) + + def makedirs(self, path, *args, **kwargs): + return self.fs.makedirs(self._join(path), *args, **kwargs) + + def rmdir(self, path): + return self.fs.rmdir(self._join(path)) + + def mv(self, path1, path2, **kwargs): + return self.fs.mv( + self._join(path1), + self._join(path2), + **kwargs, + ) + + def touch(self, path, **kwargs): + return self.fs.touch(self._join(path), **kwargs) + + def created(self, path): + return self.fs.created(self._join(path)) + + def modified(self, path): + return self.fs.modified(self._join(path)) + + def sign(self, path, *args, **kwargs): + return self.fs.sign(self._join(path), *args, **kwargs) + + def __repr__(self): + return f"{self.__class__.__qualname__}(path='{self.path}', fs={self.fs})" + + def open( + self, + path, + *args, + **kwargs, + ): + return self.fs.open( + self._join(path), + *args, + **kwargs, + ) + + async def open_async( + self, + path, + *args, + **kwargs, + ): + return await self.fs.open_async( + self._join(path), + *args, + **kwargs, + ) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/ftp.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/ftp.py new file mode 100644 index 0000000000000000000000000000000000000000..ca151c6ea3cc2bb514701cd95fd5258bed3c9899 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/ftp.py @@ -0,0 +1,437 @@ +import os +import ssl +import uuid +from ftplib import FTP, FTP_TLS, Error, error_perm +from typing import Any + +from ..spec import AbstractBufferedFile, AbstractFileSystem +from ..utils import infer_storage_options, isfilelike + +SECURITY_PROTOCOL_MAP = { + "tls": ssl.PROTOCOL_TLS, + "tlsv1": ssl.PROTOCOL_TLSv1, + "tlsv1_1": ssl.PROTOCOL_TLSv1_1, + "tlsv1_2": ssl.PROTOCOL_TLSv1_2, + "sslv23": ssl.PROTOCOL_SSLv23, +} + + +class ImplicitFTPTLS(FTP_TLS): + """ + FTP_TLS subclass that automatically wraps sockets in SSL + to support implicit FTPS. + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._sock = None + + @property + def sock(self): + """Return the socket.""" + return self._sock + + @sock.setter + def sock(self, value): + """When modifying the socket, ensure that it is ssl wrapped.""" + if value is not None and not isinstance(value, ssl.SSLSocket): + value = self.context.wrap_socket(value) + self._sock = value + + +class FTPFileSystem(AbstractFileSystem): + """A filesystem over classic FTP""" + + root_marker = "/" + cachable = False + protocol = "ftp" + + def __init__( + self, + host, + port=21, + username=None, + password=None, + acct=None, + block_size=None, + tempdir=None, + timeout=30, + encoding="utf-8", + tls=False, + **kwargs, + ): + """ + You can use _get_kwargs_from_urls to get some kwargs from + a reasonable FTP url. + + Authentication will be anonymous if username/password are not + given. + + Parameters + ---------- + host: str + The remote server name/ip to connect to + port: int + Port to connect with + username: str or None + If authenticating, the user's identifier + password: str of None + User's password on the server, if using + acct: str or None + Some servers also need an "account" string for auth + block_size: int or None + If given, the read-ahead or write buffer size. + tempdir: str + Directory on remote to put temporary files when in a transaction + timeout: int + Timeout of the ftp connection in seconds + encoding: str + Encoding to use for directories and filenames in FTP connection + tls: bool or str + Enable FTP-TLS for secure connections: + - False: Plain FTP (default) + - True: Explicit TLS (FTPS with AUTH TLS command) + - "tls": Auto-negotiate highest protocol + - "tlsv1": TLS v1.0 + - "tlsv1_1": TLS v1.1 + - "tlsv1_2": TLS v1.2 + """ + super().__init__(**kwargs) + self.host = host + self.port = port + self.tempdir = tempdir or "/tmp" + self.cred = username or "", password or "", acct or "" + self.timeout = timeout + self.encoding = encoding + if block_size is not None: + self.blocksize = block_size + else: + self.blocksize = 2**16 + self.tls = tls + self._connect() + if isinstance(self.tls, bool) and self.tls: + self.ftp.prot_p() + + def _connect(self): + security = None + if self.tls: + if isinstance(self.tls, str): + ftp_cls = ImplicitFTPTLS + security = SECURITY_PROTOCOL_MAP.get( + self.tls, + f"Not supported {self.tls} protocol", + ) + if isinstance(security, str): + raise ValueError(security) + else: + ftp_cls = FTP_TLS + else: + ftp_cls = FTP + self.ftp = ftp_cls(timeout=self.timeout, encoding=self.encoding) + if security: + self.ftp.ssl_version = security + self.ftp.connect(self.host, self.port) + self.ftp.login(*self.cred) + + @classmethod + def _strip_protocol(cls, path): + return "/" + infer_storage_options(path)["path"].lstrip("/").rstrip("/") + + @staticmethod + def _get_kwargs_from_urls(urlpath): + out = infer_storage_options(urlpath) + out.pop("path", None) + out.pop("protocol", None) + return out + + def ls(self, path, detail=True, **kwargs): + path = self._strip_protocol(path) + out = [] + if path not in self.dircache: + try: + try: + out = [ + (fn, details) + for (fn, details) in self.ftp.mlsd(path) + if fn not in [".", ".."] + and details["type"] not in ["pdir", "cdir"] + ] + except error_perm: + out = _mlsd2(self.ftp, path) # Not platform independent + for fn, details in out: + details["name"] = "/".join( + ["" if path == "/" else path, fn.lstrip("/")] + ) + if details["type"] == "file": + details["size"] = int(details["size"]) + else: + details["size"] = 0 + if details["type"] == "dir": + details["type"] = "directory" + self.dircache[path] = out + except Error: + try: + info = self.info(path) + if info["type"] == "file": + out = [(path, info)] + except (Error, IndexError) as exc: + raise FileNotFoundError(path) from exc + files = self.dircache.get(path, out) + if not detail: + return sorted([fn for fn, details in files]) + return [details for fn, details in files] + + def info(self, path, **kwargs): + # implement with direct method + path = self._strip_protocol(path) + if path == "/": + # special case, since this dir has no real entry + return {"name": "/", "size": 0, "type": "directory"} + files = self.ls(self._parent(path).lstrip("/"), True) + try: + out = next(f for f in files if f["name"] == path) + except StopIteration as exc: + raise FileNotFoundError(path) from exc + return out + + def get_file(self, rpath, lpath, **kwargs): + if self.isdir(rpath): + if not os.path.exists(lpath): + os.mkdir(lpath) + return + if isfilelike(lpath): + outfile = lpath + else: + outfile = open(lpath, "wb") + + def cb(x): + outfile.write(x) + + self.ftp.retrbinary( + f"RETR {rpath}", + blocksize=self.blocksize, + callback=cb, + ) + if not isfilelike(lpath): + outfile.close() + + def cat_file(self, path, start=None, end=None, **kwargs): + if end is not None: + return super().cat_file(path, start, end, **kwargs) + out = [] + + def cb(x): + out.append(x) + + try: + self.ftp.retrbinary( + f"RETR {path}", + blocksize=self.blocksize, + rest=start, + callback=cb, + ) + except (Error, error_perm) as orig_exc: + raise FileNotFoundError(path) from orig_exc + return b"".join(out) + + def _open( + self, + path, + mode="rb", + block_size=None, + cache_options=None, + autocommit=True, + **kwargs, + ): + path = self._strip_protocol(path) + block_size = block_size or self.blocksize + return FTPFile( + self, + path, + mode=mode, + block_size=block_size, + tempdir=self.tempdir, + autocommit=autocommit, + cache_options=cache_options, + ) + + def _rm(self, path): + path = self._strip_protocol(path) + self.ftp.delete(path) + self.invalidate_cache(self._parent(path)) + + def rm(self, path, recursive=False, maxdepth=None): + paths = self.expand_path(path, recursive=recursive, maxdepth=maxdepth) + for p in reversed(paths): + if self.isfile(p): + self.rm_file(p) + else: + self.rmdir(p) + + def mkdir(self, path: str, create_parents: bool = True, **kwargs: Any) -> None: + path = self._strip_protocol(path) + parent = self._parent(path) + if parent != self.root_marker and not self.exists(parent) and create_parents: + self.mkdir(parent, create_parents=create_parents) + + self.ftp.mkd(path) + self.invalidate_cache(self._parent(path)) + + def makedirs(self, path: str, exist_ok: bool = False) -> None: + path = self._strip_protocol(path) + if self.exists(path): + # NB: "/" does not "exist" as it has no directory entry + if not exist_ok: + raise FileExistsError(f"{path} exists without `exist_ok`") + # exists_ok=True -> no-op + else: + self.mkdir(path, create_parents=True) + + def rmdir(self, path): + path = self._strip_protocol(path) + self.ftp.rmd(path) + self.invalidate_cache(self._parent(path)) + + def mv(self, path1, path2, **kwargs): + path1 = self._strip_protocol(path1) + path2 = self._strip_protocol(path2) + self.ftp.rename(path1, path2) + self.invalidate_cache(self._parent(path1)) + self.invalidate_cache(self._parent(path2)) + + def __del__(self): + self.ftp.close() + + def invalidate_cache(self, path=None): + if path is None: + self.dircache.clear() + else: + self.dircache.pop(path, None) + super().invalidate_cache(path) + + +class TransferDone(Exception): + """Internal exception to break out of transfer""" + + pass + + +class FTPFile(AbstractBufferedFile): + """Interact with a remote FTP file with read/write buffering""" + + def __init__( + self, + fs, + path, + mode="rb", + block_size="default", + autocommit=True, + cache_type="readahead", + cache_options=None, + **kwargs, + ): + super().__init__( + fs, + path, + mode=mode, + block_size=block_size, + autocommit=autocommit, + cache_type=cache_type, + cache_options=cache_options, + **kwargs, + ) + if not autocommit: + self.target = self.path + self.path = "/".join([kwargs["tempdir"], str(uuid.uuid4())]) + + def commit(self): + self.fs.mv(self.path, self.target) + + def discard(self): + self.fs.rm(self.path) + + def _fetch_range(self, start, end): + """Get bytes between given byte limits + + Implemented by raising an exception in the fetch callback when the + number of bytes received reaches the requested amount. + + Will fail if the server does not respect the REST command on + retrieve requests. + """ + out = [] + total = [0] + + def callback(x): + total[0] += len(x) + if total[0] > end - start: + out.append(x[: (end - start) - total[0]]) + if end < self.size: + raise TransferDone + else: + out.append(x) + + if total[0] == end - start and end < self.size: + raise TransferDone + + try: + self.fs.ftp.retrbinary( + f"RETR {self.path}", + blocksize=self.blocksize, + rest=start, + callback=callback, + ) + except TransferDone: + try: + # stop transfer, we got enough bytes for this block + self.fs.ftp.abort() + self.fs.ftp.getmultiline() + except Error: + self.fs._connect() + + return b"".join(out) + + def _upload_chunk(self, final=False): + self.buffer.seek(0) + self.fs.ftp.storbinary( + f"STOR {self.path}", self.buffer, blocksize=self.blocksize, rest=self.offset + ) + return True + + +def _mlsd2(ftp, path="."): + """ + Fall back to using `dir` instead of `mlsd` if not supported. + + This parses a Linux style `ls -l` response to `dir`, but the response may + be platform dependent. + + Parameters + ---------- + ftp: ftplib.FTP + path: str + Expects to be given path, but defaults to ".". + """ + lines = [] + minfo = [] + ftp.dir(path, lines.append) + for line in lines: + split_line = line.split() + if len(split_line) < 9: + continue + this = ( + split_line[-1], + { + "modify": " ".join(split_line[5:8]), + "unix.owner": split_line[2], + "unix.group": split_line[3], + "unix.mode": split_line[0], + "size": split_line[4], + }, + ) + if this[1]["unix.mode"][0] == "d": + this[1]["type"] = "dir" + else: + this[1]["type"] = "file" + minfo.append(this) + return minfo diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/gist.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/gist.py new file mode 100644 index 0000000000000000000000000000000000000000..ad9ac0b6a1cdbcfba6188e2cdeab2350bb9aad0a --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/gist.py @@ -0,0 +1,241 @@ +import requests + +from ..spec import AbstractFileSystem +from ..utils import infer_storage_options +from .memory import MemoryFile + + +class GistFileSystem(AbstractFileSystem): + """ + Interface to files in a single GitHub Gist. + + Provides read-only access to a gist's files. Gists do not contain + subdirectories, so file listing is straightforward. + + Parameters + ---------- + gist_id: str + The ID of the gist you want to access (the long hex value from the URL). + filenames: list[str] (optional) + If provided, only make a file system representing these files, and do not fetch + the list of all files for this gist. + sha: str (optional) + If provided, fetch a particular revision of the gist. If omitted, + the latest revision is used. + username: str (optional) + GitHub username for authentication. + token: str (optional) + GitHub personal access token (required if username is given), or. + timeout: (float, float) or float, optional + Connect and read timeouts for requests (default 60s each). + kwargs: dict + Stored on `self.request_kw` and passed to `requests.get` when fetching Gist + metadata or reading ("opening") a file. + """ + + protocol = "gist" + gist_url = "https://api.github.com/gists/{gist_id}" + gist_rev_url = "https://api.github.com/gists/{gist_id}/{sha}" + + def __init__( + self, + gist_id, + filenames=None, + sha=None, + username=None, + token=None, + timeout=None, + **kwargs, + ): + super().__init__() + self.gist_id = gist_id + self.filenames = filenames + self.sha = sha # revision of the gist (optional) + if username is not None and token is None: + raise ValueError("User auth requires a token") + self.username = username + self.token = token + self.request_kw = kwargs + # Default timeouts to 60s connect/read if none provided + self.timeout = timeout if timeout is not None else (60, 60) + + # We use a single-level "directory" cache, because a gist is essentially flat + self.dircache[""] = self._fetch_file_list() + + @property + def kw(self): + """Auth parameters passed to 'requests' if we have username/token.""" + kw = { + "headers": { + "Accept": "application/vnd.github+json", + "X-GitHub-Api-Version": "2022-11-28", + } + } + kw.update(self.request_kw) + if self.username and self.token: + kw["auth"] = (self.username, self.token) + elif self.token: + kw["headers"]["Authorization"] = f"Bearer {self.token}" + return kw + + def _fetch_gist_metadata(self): + """ + Fetch the JSON metadata for this gist (possibly for a specific revision). + """ + if self.sha: + url = self.gist_rev_url.format(gist_id=self.gist_id, sha=self.sha) + else: + url = self.gist_url.format(gist_id=self.gist_id) + + r = requests.get(url, timeout=self.timeout, **self.kw) + if r.status_code == 404: + raise FileNotFoundError( + f"Gist not found: {self.gist_id}@{self.sha or 'latest'}" + ) + r.raise_for_status() + return r.json() + + def _fetch_file_list(self): + """ + Returns a list of dicts describing each file in the gist. These get stored + in self.dircache[""]. + """ + meta = self._fetch_gist_metadata() + if self.filenames: + available_files = meta.get("files", {}) + files = {} + for fn in self.filenames: + if fn not in available_files: + raise FileNotFoundError(fn) + files[fn] = available_files[fn] + else: + files = meta.get("files", {}) + + out = [] + for fname, finfo in files.items(): + if finfo is None: + # Occasionally GitHub returns a file entry with null if it was deleted + continue + # Build a directory entry + out.append( + { + "name": fname, # file's name + "type": "file", # gists have no subdirectories + "size": finfo.get("size", 0), # file size in bytes + "raw_url": finfo.get("raw_url"), + } + ) + return out + + @classmethod + def _strip_protocol(cls, path): + """ + Remove 'gist://' from the path, if present. + """ + # The default infer_storage_options can handle gist://username:token@id/file + # or gist://id/file, but let's ensure we handle a normal usage too. + # We'll just strip the protocol prefix if it exists. + path = infer_storage_options(path).get("path", path) + return path.lstrip("/") + + @staticmethod + def _get_kwargs_from_urls(path): + """ + Parse 'gist://' style URLs into GistFileSystem constructor kwargs. + For example: + gist://:TOKEN@/file.txt + gist://username:TOKEN@/file.txt + """ + so = infer_storage_options(path) + out = {} + if "username" in so and so["username"]: + out["username"] = so["username"] + if "password" in so and so["password"]: + out["token"] = so["password"] + if "host" in so and so["host"]: + # We interpret 'host' as the gist ID + out["gist_id"] = so["host"] + + # Extract SHA and filename from path + if "path" in so and so["path"]: + path_parts = so["path"].rsplit("/", 2)[-2:] + if len(path_parts) == 2: + if path_parts[0]: # SHA present + out["sha"] = path_parts[0] + if path_parts[1]: # filename also present + out["filenames"] = [path_parts[1]] + + return out + + def ls(self, path="", detail=False, **kwargs): + """ + List files in the gist. Gists are single-level, so any 'path' is basically + the filename, or empty for all files. + + Parameters + ---------- + path : str, optional + The filename to list. If empty, returns all files in the gist. + detail : bool, default False + If True, return a list of dicts; if False, return a list of filenames. + """ + path = self._strip_protocol(path or "") + # If path is empty, return all + if path == "": + results = self.dircache[""] + else: + # We want just the single file with this name + all_files = self.dircache[""] + results = [f for f in all_files if f["name"] == path] + if not results: + raise FileNotFoundError(path) + if detail: + return results + else: + return sorted(f["name"] for f in results) + + def _open(self, path, mode="rb", block_size=None, **kwargs): + """ + Read a single file from the gist. + """ + if mode != "rb": + raise NotImplementedError("GitHub Gist FS is read-only (no write).") + + path = self._strip_protocol(path) + # Find the file entry in our dircache + matches = [f for f in self.dircache[""] if f["name"] == path] + if not matches: + raise FileNotFoundError(path) + finfo = matches[0] + + raw_url = finfo.get("raw_url") + if not raw_url: + raise FileNotFoundError(f"No raw_url for file: {path}") + + r = requests.get(raw_url, timeout=self.timeout, **self.kw) + if r.status_code == 404: + raise FileNotFoundError(path) + r.raise_for_status() + return MemoryFile(path, None, r.content) + + def cat(self, path, recursive=False, on_error="raise", **kwargs): + """ + Return {path: contents} for the given file or files. If 'recursive' is True, + and path is empty, returns all files in the gist. + """ + paths = self.expand_path(path, recursive=recursive) + out = {} + for p in paths: + try: + with self.open(p, "rb") as f: + out[p] = f.read() + except FileNotFoundError as e: + if on_error == "raise": + raise e + elif on_error == "omit": + pass # skip + else: + out[p] = e + if len(paths) == 1 and paths[0] == path: + return out[path] + return out diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/git.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/git.py new file mode 100644 index 0000000000000000000000000000000000000000..808d293a1c991ea87d19a2129f3e56d9b813daaa --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/git.py @@ -0,0 +1,114 @@ +import os + +import pygit2 + +from fsspec.spec import AbstractFileSystem + +from .memory import MemoryFile + + +class GitFileSystem(AbstractFileSystem): + """Browse the files of a local git repo at any hash/tag/branch + + (experimental backend) + """ + + root_marker = "" + cachable = True + + def __init__(self, path=None, fo=None, ref=None, **kwargs): + """ + + Parameters + ---------- + path: str (optional) + Local location of the repo (uses current directory if not given). + May be deprecated in favour of ``fo``. When used with a higher + level function such as fsspec.open(), may be of the form + "git://[path-to-repo[:]][ref@]path/to/file" (but the actual + file path should not contain "@" or ":"). + fo: str (optional) + Same as ``path``, but passed as part of a chained URL. This one + takes precedence if both are given. + ref: str (optional) + Reference to work with, could be a hash, tag or branch name. Defaults + to current working tree. Note that ``ls`` and ``open`` also take hash, + so this becomes the default for those operations + kwargs + """ + super().__init__(**kwargs) + self.repo = pygit2.Repository(fo or path or os.getcwd()) + self.ref = ref or "master" + + @classmethod + def _strip_protocol(cls, path): + path = super()._strip_protocol(path).lstrip("/") + if ":" in path: + path = path.split(":", 1)[1] + if "@" in path: + path = path.split("@", 1)[1] + return path.lstrip("/") + + def _path_to_object(self, path, ref): + comm, ref = self.repo.resolve_refish(ref or self.ref) + parts = path.split("/") + tree = comm.tree + for part in parts: + if part and isinstance(tree, pygit2.Tree): + if part not in tree: + raise FileNotFoundError(path) + tree = tree[part] + return tree + + @staticmethod + def _get_kwargs_from_urls(path): + path = path.removeprefix("git://") + out = {} + if ":" in path: + out["path"], path = path.split(":", 1) + if "@" in path: + out["ref"], path = path.split("@", 1) + return out + + @staticmethod + def _object_to_info(obj, path=None): + # obj.name and obj.filemode are None for the root tree! + is_dir = isinstance(obj, pygit2.Tree) + return { + "type": "directory" if is_dir else "file", + "name": ( + "/".join([path, obj.name or ""]).lstrip("/") if path else obj.name + ), + "hex": str(obj.id), + "mode": "100644" if obj.filemode is None else f"{obj.filemode:o}", + "size": 0 if is_dir else obj.size, + } + + def ls(self, path, detail=True, ref=None, **kwargs): + tree = self._path_to_object(self._strip_protocol(path), ref) + return [ + GitFileSystem._object_to_info(obj, path) + if detail + else GitFileSystem._object_to_info(obj, path)["name"] + for obj in (tree if isinstance(tree, pygit2.Tree) else [tree]) + ] + + def info(self, path, ref=None, **kwargs): + tree = self._path_to_object(self._strip_protocol(path), ref) + return GitFileSystem._object_to_info(tree, path) + + def ukey(self, path, ref=None): + return self.info(path, ref=ref)["hex"] + + def _open( + self, + path, + mode="rb", + block_size=None, + autocommit=True, + cache_options=None, + ref=None, + **kwargs, + ): + obj = self._path_to_object(path, ref or self.ref) + return MemoryFile(data=obj.data) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/github.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/github.py new file mode 100644 index 0000000000000000000000000000000000000000..3630f6db54413e2c396f6cc1b6b10cd379200043 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/github.py @@ -0,0 +1,333 @@ +import base64 +import re + +import requests + +from ..spec import AbstractFileSystem +from ..utils import infer_storage_options +from .memory import MemoryFile + + +class GithubFileSystem(AbstractFileSystem): + """Interface to files in github + + An instance of this class provides the files residing within a remote github + repository. You may specify a point in the repos history, by SHA, branch + or tag (default is current master). + + For files less than 1 MB in size, file content is returned directly in a + MemoryFile. For larger files, or for files tracked by git-lfs, file content + is returned as an HTTPFile wrapping the ``download_url`` provided by the + GitHub API. + + When using fsspec.open, allows URIs of the form: + + - "github://path/file", in which case you must specify org, repo and + may specify sha in the extra args + - 'github://org:repo@/precip/catalog.yml', where the org and repo are + part of the URI + - 'github://org:repo@sha/precip/catalog.yml', where the sha is also included + + ``sha`` can be the full or abbreviated hex of the commit you want to fetch + from, or a branch or tag name (so long as it doesn't contain special characters + like "/", "?", which would have to be HTTP-encoded). + + For authorised access, you must provide username and token, which can be made + at https://github.com/settings/tokens + """ + + url = "https://api.github.com/repos/{org}/{repo}/git/trees/{sha}" + content_url = "https://api.github.com/repos/{org}/{repo}/contents/{path}?ref={sha}" + protocol = "github" + timeout = (60, 60) # connect, read timeouts + + def __init__( + self, org, repo, sha=None, username=None, token=None, timeout=None, **kwargs + ): + super().__init__(**kwargs) + self.org = org + self.repo = repo + if (username is None) ^ (token is None): + raise ValueError("Auth required both username and token") + self.username = username + self.token = token + if timeout is not None: + self.timeout = timeout + if sha is None: + # look up default branch (not necessarily "master") + u = "https://api.github.com/repos/{org}/{repo}" + r = requests.get( + u.format(org=org, repo=repo), timeout=self.timeout, **self.kw + ) + r.raise_for_status() + sha = r.json()["default_branch"] + + self.root = sha + self.ls("") + try: + from .http import HTTPFileSystem + + self.http_fs = HTTPFileSystem(**kwargs) + except ImportError: + self.http_fs = None + + @property + def kw(self): + if self.username: + return {"auth": (self.username, self.token)} + return {} + + @classmethod + def repos(cls, org_or_user, is_org=True): + """List repo names for given org or user + + This may become the top level of the FS + + Parameters + ---------- + org_or_user: str + Name of the github org or user to query + is_org: bool (default True) + Whether the name is an organisation (True) or user (False) + + Returns + ------- + List of string + """ + r = requests.get( + f"https://api.github.com/{['users', 'orgs'][is_org]}/{org_or_user}/repos", + timeout=cls.timeout, + ) + r.raise_for_status() + return [repo["name"] for repo in r.json()] + + @property + def tags(self): + """Names of tags in the repo""" + r = requests.get( + f"https://api.github.com/repos/{self.org}/{self.repo}/tags", + timeout=self.timeout, + **self.kw, + ) + r.raise_for_status() + return [t["name"] for t in r.json()] + + @property + def branches(self): + """Names of branches in the repo""" + r = requests.get( + f"https://api.github.com/repos/{self.org}/{self.repo}/branches", + timeout=self.timeout, + **self.kw, + ) + r.raise_for_status() + return [t["name"] for t in r.json()] + + @property + def refs(self): + """Named references, tags and branches""" + return {"tags": self.tags, "branches": self.branches} + + def ls(self, path, detail=False, sha=None, _sha=None, **kwargs): + """List files at given path + + Parameters + ---------- + path: str + Location to list, relative to repo root + detail: bool + If True, returns list of dicts, one per file; if False, returns + list of full filenames only + sha: str (optional) + List at the given point in the repo history, branch or tag name or commit + SHA + _sha: str (optional) + List this specific tree object (used internally to descend into trees) + """ + path = self._strip_protocol(path) + if path == "": + _sha = sha or self.root + if _sha is None: + parts = path.rstrip("/").split("/") + so_far = "" + _sha = sha or self.root + for part in parts: + out = self.ls(so_far, True, sha=sha, _sha=_sha) + so_far += "/" + part if so_far else part + out = [o for o in out if o["name"] == so_far] + if not out: + raise FileNotFoundError(path) + out = out[0] + if out["type"] == "file": + if detail: + return [out] + else: + return path + _sha = out["sha"] + if path not in self.dircache or sha not in [self.root, None]: + r = requests.get( + self.url.format(org=self.org, repo=self.repo, sha=_sha), + timeout=self.timeout, + **self.kw, + ) + if r.status_code == 404: + raise FileNotFoundError(path) + r.raise_for_status() + types = {"blob": "file", "tree": "directory"} + out = [ + { + "name": path + "/" + f["path"] if path else f["path"], + "mode": f["mode"], + "type": types[f["type"]], + "size": f.get("size", 0), + "sha": f["sha"], + } + for f in r.json()["tree"] + if f["type"] in types + ] + if sha in [self.root, None]: + self.dircache[path] = out + else: + out = self.dircache[path] + if detail: + return out + else: + return sorted([f["name"] for f in out]) + + def invalidate_cache(self, path=None): + self.dircache.clear() + + @classmethod + def _strip_protocol(cls, path): + opts = infer_storage_options(path) + if "username" not in opts: + return super()._strip_protocol(path) + return opts["path"].lstrip("/") + + @staticmethod + def _get_kwargs_from_urls(path): + opts = infer_storage_options(path) + if "username" not in opts: + return {} + out = {"org": opts["username"], "repo": opts["password"]} + if opts["host"]: + out["sha"] = opts["host"] + return out + + def _open( + self, + path, + mode="rb", + block_size=None, + cache_options=None, + sha=None, + **kwargs, + ): + if mode != "rb": + raise NotImplementedError + + # construct a url to hit the GitHub API's repo contents API + url = self.content_url.format( + org=self.org, repo=self.repo, path=path, sha=sha or self.root + ) + + # make a request to this API, and parse the response as JSON + r = requests.get(url, timeout=self.timeout, **self.kw) + if r.status_code == 404: + raise FileNotFoundError(path) + r.raise_for_status() + content_json = r.json() + + # if the response's content key is not empty, try to parse it as base64 + if content_json["content"]: + content = base64.b64decode(content_json["content"]) + + # as long as the content does not start with the string + # "version https://git-lfs.github.com/" + # then it is probably not a git-lfs pointer and we can just return + # the content directly + if not content.startswith(b"version https://git-lfs.github.com/"): + return MemoryFile(None, None, content) + + # we land here if the content was not present in the first response + # (regular file over 1MB or git-lfs tracked file) + # in this case, we get let the HTTPFileSystem handle the download + if self.http_fs is None: + raise ImportError( + "Please install fsspec[http] to access github files >1 MB " + "or git-lfs tracked files." + ) + return self.http_fs.open( + content_json["download_url"], + mode=mode, + block_size=block_size, + cache_options=cache_options, + **kwargs, + ) + + def rm(self, path, recursive=False, maxdepth=None, message=None): + path = self.expand_path(path, recursive=recursive, maxdepth=maxdepth) + for p in reversed(path): + self.rm_file(p, message=message) + + def rm_file(self, path, message=None, **kwargs): + """ + Remove a file from a specified branch using a given commit message. + + Since Github DELETE operation requires a branch name, and we can't reliably + determine whether the provided SHA refers to a branch, tag, or commit, we + assume it's a branch. If it's not, the user will encounter an error when + attempting to retrieve the file SHA or delete the file. + + Parameters + ---------- + path: str + The file's location relative to the repository root. + message: str, optional + The commit message for the deletion. + """ + + if not self.username: + raise ValueError("Authentication required") + + path = self._strip_protocol(path) + + # Attempt to get SHA from cache or Github API + sha = self._get_sha_from_cache(path) + if not sha: + url = self.content_url.format( + org=self.org, repo=self.repo, path=path.lstrip("/"), sha=self.root + ) + r = requests.get(url, timeout=self.timeout, **self.kw) + if r.status_code == 404: + raise FileNotFoundError(path) + r.raise_for_status() + sha = r.json()["sha"] + + # Delete the file + delete_url = self.content_url.format( + org=self.org, repo=self.repo, path=path, sha=self.root + ) + branch = self.root + data = { + "message": message or f"Delete {path}", + "sha": sha, + **({"branch": branch} if branch else {}), + } + + r = requests.delete(delete_url, json=data, timeout=self.timeout, **self.kw) + error_message = r.json().get("message", "") + if re.search(r"Branch .+ not found", error_message): + error = "Remove only works when the filesystem is initialised from a branch or default (None)" + raise ValueError(error) + r.raise_for_status() + + self.invalidate_cache(path) + + def _get_sha_from_cache(self, path): + for entries in self.dircache.values(): + for entry in entries: + entry_path = entry.get("name") + if entry_path and entry_path == path and "sha" in entry: + return entry["sha"] + return None diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/http.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/http.py new file mode 100644 index 0000000000000000000000000000000000000000..dfb1bc36074ff4c85463133387601ae16ae1280e --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/http.py @@ -0,0 +1,897 @@ +import asyncio +import io +import logging +import re +import weakref +from copy import copy +from urllib.parse import urlparse + +import aiohttp +import yarl + +from fsspec.asyn import AbstractAsyncStreamedFile, AsyncFileSystem, sync, sync_wrapper +from fsspec.callbacks import DEFAULT_CALLBACK +from fsspec.exceptions import FSTimeoutError +from fsspec.spec import AbstractBufferedFile +from fsspec.utils import ( + DEFAULT_BLOCK_SIZE, + glob_translate, + isfilelike, + nullcontext, + tokenize, +) + +from ..caching import AllBytes + +# https://stackoverflow.com/a/15926317/3821154 +ex = re.compile(r"""<(a|A)\s+(?:[^>]*?\s+)?(href|HREF)=["'](?P[^"']+)""") +ex2 = re.compile(r"""(?Phttp[s]?://[-a-zA-Z0-9@:%_+.~#?&/=]+)""") +logger = logging.getLogger("fsspec.http") + + +async def get_client(**kwargs): + return aiohttp.ClientSession(**kwargs) + + +class HTTPFileSystem(AsyncFileSystem): + """ + Simple File-System for fetching data via HTTP(S) + + ``ls()`` is implemented by loading the parent page and doing a regex + match on the result. If simple_link=True, anything of the form + "http(s)://server.com/stuff?thing=other"; otherwise only links within + HTML href tags will be used. + """ + + protocol = ("http", "https") + sep = "/" + + def __init__( + self, + simple_links=True, + block_size=None, + same_scheme=True, + size_policy=None, + cache_type="bytes", + cache_options=None, + asynchronous=False, + loop=None, + client_kwargs=None, + get_client=get_client, + encoded=False, + **storage_options, + ): + """ + NB: if this is called async, you must await set_client + + Parameters + ---------- + block_size: int + Blocks to read bytes; if 0, will default to raw requests file-like + objects instead of HTTPFile instances + simple_links: bool + If True, will consider both HTML tags and anything that looks + like a URL; if False, will consider only the former. + same_scheme: True + When doing ls/glob, if this is True, only consider paths that have + http/https matching the input URLs. + size_policy: this argument is deprecated + client_kwargs: dict + Passed to aiohttp.ClientSession, see + https://docs.aiohttp.org/en/stable/client_reference.html + For example, ``{'auth': aiohttp.BasicAuth('user', 'pass')}`` + get_client: Callable[..., aiohttp.ClientSession] + A callable, which takes keyword arguments and constructs + an aiohttp.ClientSession. Its state will be managed by + the HTTPFileSystem class. + storage_options: key-value + Any other parameters passed on to requests + cache_type, cache_options: defaults used in open() + """ + super().__init__(self, asynchronous=asynchronous, loop=loop, **storage_options) + self.block_size = block_size if block_size is not None else DEFAULT_BLOCK_SIZE + self.simple_links = simple_links + self.same_schema = same_scheme + self.cache_type = cache_type + self.cache_options = cache_options + self.client_kwargs = client_kwargs or {} + self.get_client = get_client + self.encoded = encoded + self.kwargs = storage_options + self._session = None + + # Clean caching-related parameters from `storage_options` + # before propagating them as `request_options` through `self.kwargs`. + # TODO: Maybe rename `self.kwargs` to `self.request_options` to make + # it clearer. + request_options = copy(storage_options) + self.use_listings_cache = request_options.pop("use_listings_cache", False) + request_options.pop("listings_expiry_time", None) + request_options.pop("max_paths", None) + request_options.pop("skip_instance_cache", None) + self.kwargs = request_options + + @property + def fsid(self): + return "http" + + def encode_url(self, url): + return yarl.URL(url, encoded=self.encoded) + + @staticmethod + def close_session(loop, session): + if loop is not None and loop.is_running(): + try: + sync(loop, session.close, timeout=0.1) + return + except (TimeoutError, FSTimeoutError, NotImplementedError): + pass + connector = getattr(session, "_connector", None) + if connector is not None: + # close after loop is dead + connector._close() + + async def set_session(self): + if self._session is None: + self._session = await self.get_client(loop=self.loop, **self.client_kwargs) + if not self.asynchronous: + weakref.finalize(self, self.close_session, self.loop, self._session) + return self._session + + @classmethod + def _strip_protocol(cls, path): + """For HTTP, we always want to keep the full URL""" + return path + + @classmethod + def _parent(cls, path): + # override, since _strip_protocol is different for URLs + par = super()._parent(path) + if len(par) > 7: # "http://..." + return par + return "" + + async def _ls_real(self, url, detail=True, **kwargs): + # ignoring URL-encoded arguments + kw = self.kwargs.copy() + kw.update(kwargs) + logger.debug(url) + session = await self.set_session() + async with session.get(self.encode_url(url), **self.kwargs) as r: + self._raise_not_found_for_status(r, url) + + if "Content-Type" in r.headers: + mimetype = r.headers["Content-Type"].partition(";")[0] + else: + mimetype = None + + if mimetype in ("text/html", None): + try: + text = await r.text(errors="ignore") + if self.simple_links: + links = ex2.findall(text) + [u[2] for u in ex.findall(text)] + else: + links = [u[2] for u in ex.findall(text)] + except UnicodeDecodeError: + links = [] # binary, not HTML + else: + links = [] + + out = set() + parts = urlparse(url) + for l in links: + if isinstance(l, tuple): + l = l[1] + if l.startswith("/") and len(l) > 1: + # absolute URL on this server + l = f"{parts.scheme}://{parts.netloc}{l}" + if l.startswith("http"): + if self.same_schema and l.startswith(url.rstrip("/") + "/"): + out.add(l) + elif l.replace("https", "http").startswith( + url.replace("https", "http").rstrip("/") + "/" + ): + # allowed to cross http <-> https + out.add(l) + else: + if l not in ["..", "../"]: + # Ignore FTP-like "parent" + out.add("/".join([url.rstrip("/"), l.lstrip("/")])) + if not out and url.endswith("/"): + out = await self._ls_real(url.rstrip("/"), detail=False) + if detail: + return [ + { + "name": u, + "size": None, + "type": "directory" if u.endswith("/") else "file", + } + for u in out + ] + else: + return sorted(out) + + async def _ls(self, url, detail=True, **kwargs): + if self.use_listings_cache and url in self.dircache: + out = self.dircache[url] + else: + out = await self._ls_real(url, detail=detail, **kwargs) + self.dircache[url] = out + return out + + ls = sync_wrapper(_ls) + + def _raise_not_found_for_status(self, response, url): + """ + Raises FileNotFoundError for 404s, otherwise uses raise_for_status. + """ + if response.status == 404: + raise FileNotFoundError(url) + response.raise_for_status() + + async def _cat_file(self, url, start=None, end=None, **kwargs): + kw = self.kwargs.copy() + kw.update(kwargs) + logger.debug(url) + + if start is not None or end is not None: + if start == end: + return b"" + headers = kw.pop("headers", {}).copy() + + headers["Range"] = await self._process_limits(url, start, end) + kw["headers"] = headers + session = await self.set_session() + async with session.get(self.encode_url(url), **kw) as r: + out = await r.read() + self._raise_not_found_for_status(r, url) + return out + + async def _get_file( + self, rpath, lpath, chunk_size=5 * 2**20, callback=DEFAULT_CALLBACK, **kwargs + ): + kw = self.kwargs.copy() + kw.update(kwargs) + logger.debug(rpath) + session = await self.set_session() + async with session.get(self.encode_url(rpath), **kw) as r: + try: + size = int(r.headers["content-length"]) + except (ValueError, KeyError): + size = None + + callback.set_size(size) + self._raise_not_found_for_status(r, rpath) + if isfilelike(lpath): + outfile = lpath + else: + outfile = open(lpath, "wb") # noqa: ASYNC230 + + try: + chunk = True + while chunk: + chunk = await r.content.read(chunk_size) + outfile.write(chunk) + callback.relative_update(len(chunk)) + finally: + if not isfilelike(lpath): + outfile.close() + + async def _put_file( + self, + lpath, + rpath, + chunk_size=5 * 2**20, + callback=DEFAULT_CALLBACK, + method="post", + mode="overwrite", + **kwargs, + ): + if mode != "overwrite": + raise NotImplementedError("Exclusive write") + + async def gen_chunks(): + # Support passing arbitrary file-like objects + # and use them instead of streams. + if isinstance(lpath, io.IOBase): + context = nullcontext(lpath) + use_seek = False # might not support seeking + else: + context = open(lpath, "rb") # noqa: ASYNC230 + use_seek = True + + with context as f: + if use_seek: + callback.set_size(f.seek(0, 2)) + f.seek(0) + else: + callback.set_size(getattr(f, "size", None)) + + chunk = f.read(chunk_size) + while chunk: + yield chunk + callback.relative_update(len(chunk)) + chunk = f.read(chunk_size) + + kw = self.kwargs.copy() + kw.update(kwargs) + session = await self.set_session() + + method = method.lower() + if method not in ("post", "put"): + raise ValueError( + f"method has to be either 'post' or 'put', not: {method!r}" + ) + + meth = getattr(session, method) + async with meth(self.encode_url(rpath), data=gen_chunks(), **kw) as resp: + self._raise_not_found_for_status(resp, rpath) + + async def _exists(self, path, strict=False, **kwargs): + kw = self.kwargs.copy() + kw.update(kwargs) + try: + logger.debug(path) + session = await self.set_session() + r = await session.get(self.encode_url(path), **kw) + async with r: + if strict: + self._raise_not_found_for_status(r, path) + return r.status < 400 + except FileNotFoundError: + return False + except aiohttp.ClientError: + if strict: + raise + return False + + async def _isfile(self, path, **kwargs): + return await self._exists(path, **kwargs) + + def _open( + self, + path, + mode="rb", + block_size=None, + autocommit=None, # XXX: This differs from the base class. + cache_type=None, + cache_options=None, + size=None, + **kwargs, + ): + """Make a file-like object + + Parameters + ---------- + path: str + Full URL with protocol + mode: string + must be "rb" + block_size: int or None + Bytes to download in one request; use instance value if None. If + zero, will return a streaming Requests file-like instance. + kwargs: key-value + Any other parameters, passed to requests calls + """ + if mode != "rb": + raise NotImplementedError + block_size = block_size if block_size is not None else self.block_size + kw = self.kwargs.copy() + kw["asynchronous"] = self.asynchronous + kw.update(kwargs) + info = {} + size = size or info.update(self.info(path, **kwargs)) or info["size"] + session = sync(self.loop, self.set_session) + if block_size and size and info.get("partial", True): + return HTTPFile( + self, + path, + session=session, + block_size=block_size, + mode=mode, + size=size, + cache_type=cache_type or self.cache_type, + cache_options=cache_options or self.cache_options, + loop=self.loop, + **kw, + ) + else: + return HTTPStreamFile( + self, + path, + mode=mode, + loop=self.loop, + session=session, + **kw, + ) + + async def open_async(self, path, mode="rb", size=None, **kwargs): + session = await self.set_session() + if size is None: + try: + size = (await self._info(path, **kwargs))["size"] + except FileNotFoundError: + pass + return AsyncStreamFile( + self, + path, + loop=self.loop, + session=session, + size=size, + **kwargs, + ) + + def ukey(self, url): + """Unique identifier; assume HTTP files are static, unchanging""" + return tokenize(url, self.kwargs, self.protocol) + + async def _info(self, url, **kwargs): + """Get info of URL + + Tries to access location via HEAD, and then GET methods, but does + not fetch the data. + + It is possible that the server does not supply any size information, in + which case size will be given as None (and certain operations on the + corresponding file will not work). + """ + info = {} + session = await self.set_session() + + for policy in ["head", "get"]: + try: + info.update( + await _file_info( + self.encode_url(url), + size_policy=policy, + session=session, + **self.kwargs, + **kwargs, + ) + ) + if info.get("size") is not None: + break + except Exception as exc: + if policy == "get": + # If get failed, then raise a FileNotFoundError + raise FileNotFoundError(url) from exc + logger.debug("", exc_info=exc) + + return {"name": url, "size": None, **info, "type": "file"} + + async def _glob(self, path, maxdepth=None, **kwargs): + """ + Find files by glob-matching. + + This implementation is idntical to the one in AbstractFileSystem, + but "?" is not considered as a character for globbing, because it is + so common in URLs, often identifying the "query" part. + """ + if maxdepth is not None and maxdepth < 1: + raise ValueError("maxdepth must be at least 1") + import re + + ends_with_slash = path.endswith("/") # _strip_protocol strips trailing slash + path = self._strip_protocol(path) + append_slash_to_dirname = ends_with_slash or path.endswith(("/**", "/*")) + idx_star = path.find("*") if path.find("*") >= 0 else len(path) + idx_brace = path.find("[") if path.find("[") >= 0 else len(path) + + min_idx = min(idx_star, idx_brace) + + detail = kwargs.pop("detail", False) + + if not has_magic(path): + if await self._exists(path, **kwargs): + if not detail: + return [path] + else: + return {path: await self._info(path, **kwargs)} + else: + if not detail: + return [] # glob of non-existent returns empty + else: + return {} + elif "/" in path[:min_idx]: + min_idx = path[:min_idx].rindex("/") + root = path[: min_idx + 1] + depth = path[min_idx + 1 :].count("/") + 1 + else: + root = "" + depth = path[min_idx + 1 :].count("/") + 1 + + if "**" in path: + if maxdepth is not None: + idx_double_stars = path.find("**") + depth_double_stars = path[idx_double_stars:].count("/") + 1 + depth = depth - depth_double_stars + maxdepth + else: + depth = None + + allpaths = await self._find( + root, maxdepth=depth, withdirs=True, detail=True, **kwargs + ) + + pattern = glob_translate(path + ("/" if ends_with_slash else "")) + pattern = re.compile(pattern) + + out = { + ( + p.rstrip("/") + if not append_slash_to_dirname + and info["type"] == "directory" + and p.endswith("/") + else p + ): info + for p, info in sorted(allpaths.items()) + if pattern.match(p.rstrip("/")) + } + + if detail: + return out + else: + return list(out) + + async def _isdir(self, path): + # override, since all URLs are (also) files + try: + return bool(await self._ls(path)) + except (FileNotFoundError, ValueError): + return False + + async def _pipe_file(self, path, value, mode="overwrite", **kwargs): + """ + Write bytes to a remote file over HTTP. + + Parameters + ---------- + path : str + Target URL where the data should be written + value : bytes + Data to be written + mode : str + How to write to the file - 'overwrite' or 'append' + **kwargs : dict + Additional parameters to pass to the HTTP request + """ + url = self._strip_protocol(path) + headers = kwargs.pop("headers", {}) + headers["Content-Length"] = str(len(value)) + + session = await self.set_session() + + async with session.put(url, data=value, headers=headers, **kwargs) as r: + r.raise_for_status() + + +class HTTPFile(AbstractBufferedFile): + """ + A file-like object pointing to a remote HTTP(S) resource + + Supports only reading, with read-ahead of a predetermined block-size. + + In the case that the server does not supply the filesize, only reading of + the complete file in one go is supported. + + Parameters + ---------- + url: str + Full URL of the remote resource, including the protocol + session: aiohttp.ClientSession or None + All calls will be made within this session, to avoid restarting + connections where the server allows this + block_size: int or None + The amount of read-ahead to do, in bytes. Default is 5MB, or the value + configured for the FileSystem creating this file + size: None or int + If given, this is the size of the file in bytes, and we don't attempt + to call the server to find the value. + kwargs: all other key-values are passed to requests calls. + """ + + def __init__( + self, + fs, + url, + session=None, + block_size=None, + mode="rb", + cache_type="bytes", + cache_options=None, + size=None, + loop=None, + asynchronous=False, + **kwargs, + ): + if mode != "rb": + raise NotImplementedError("File mode not supported") + self.asynchronous = asynchronous + self.loop = loop + self.url = url + self.session = session + self.details = {"name": url, "size": size, "type": "file"} + super().__init__( + fs=fs, + path=url, + mode=mode, + block_size=block_size, + cache_type=cache_type, + cache_options=cache_options, + **kwargs, + ) + + def read(self, length=-1): + """Read bytes from file + + Parameters + ---------- + length: int + Read up to this many bytes. If negative, read all content to end of + file. If the server has not supplied the filesize, attempting to + read only part of the data will raise a ValueError. + """ + if ( + (length < 0 and self.loc == 0) # explicit read all + # but not when the size is known and fits into a block anyways + and not (self.size is not None and self.size <= self.blocksize) + ): + self._fetch_all() + if self.size is None: + if length < 0: + self._fetch_all() + else: + length = min(self.size - self.loc, length) + return super().read(length) + + async def async_fetch_all(self): + """Read whole file in one shot, without caching + + This is only called when position is still at zero, + and read() is called without a byte-count. + """ + logger.debug(f"Fetch all for {self}") + if not isinstance(self.cache, AllBytes): + r = await self.session.get(self.fs.encode_url(self.url), **self.kwargs) + async with r: + r.raise_for_status() + out = await r.read() + self.cache = AllBytes( + size=len(out), fetcher=None, blocksize=None, data=out + ) + self.size = len(out) + + _fetch_all = sync_wrapper(async_fetch_all) + + def _parse_content_range(self, headers): + """Parse the Content-Range header""" + s = headers.get("Content-Range", "") + m = re.match(r"bytes (\d+-\d+|\*)/(\d+|\*)", s) + if not m: + return None, None, None + + if m[1] == "*": + start = end = None + else: + start, end = [int(x) for x in m[1].split("-")] + total = None if m[2] == "*" else int(m[2]) + return start, end, total + + async def async_fetch_range(self, start, end): + """Download a block of data + + The expectation is that the server returns only the requested bytes, + with HTTP code 206. If this is not the case, we first check the headers, + and then stream the output - if the data size is bigger than we + requested, an exception is raised. + """ + logger.debug(f"Fetch range for {self}: {start}-{end}") + kwargs = self.kwargs.copy() + headers = kwargs.pop("headers", {}).copy() + headers["Range"] = f"bytes={start}-{end - 1}" + logger.debug(f"{self.url} : {headers['Range']}") + r = await self.session.get( + self.fs.encode_url(self.url), headers=headers, **kwargs + ) + async with r: + if r.status == 416: + # range request outside file + return b"" + r.raise_for_status() + + # If the server has handled the range request, it should reply + # with status 206 (partial content). But we'll guess that a suitable + # Content-Range header or a Content-Length no more than the + # requested range also mean we have got the desired range. + response_is_range = ( + r.status == 206 + or self._parse_content_range(r.headers)[0] == start + or int(r.headers.get("Content-Length", end + 1)) <= end - start + ) + + if response_is_range: + # partial content, as expected + out = await r.read() + elif start > 0: + raise ValueError( + "The HTTP server doesn't appear to support range requests. " + "Only reading this file from the beginning is supported. " + "Open with block_size=0 for a streaming file interface." + ) + else: + # Response is not a range, but we want the start of the file, + # so we can read the required amount anyway. + cl = 0 + out = [] + while True: + chunk = await r.content.read(2**20) + # data size unknown, let's read until we have enough + if chunk: + out.append(chunk) + cl += len(chunk) + if cl > end - start: + break + else: + break + out = b"".join(out)[: end - start] + return out + + _fetch_range = sync_wrapper(async_fetch_range) + + +magic_check = re.compile("([*[])") + + +def has_magic(s): + match = magic_check.search(s) + return match is not None + + +class HTTPStreamFile(AbstractBufferedFile): + def __init__(self, fs, url, mode="rb", loop=None, session=None, **kwargs): + self.asynchronous = kwargs.pop("asynchronous", False) + self.url = url + self.loop = loop + self.session = session + if mode != "rb": + raise ValueError + self.details = {"name": url, "size": None} + super().__init__(fs=fs, path=url, mode=mode, cache_type="none", **kwargs) + + async def cor(): + r = await self.session.get(self.fs.encode_url(url), **kwargs).__aenter__() + self.fs._raise_not_found_for_status(r, url) + return r + + self.r = sync(self.loop, cor) + self.loop = fs.loop + + def seek(self, loc, whence=0): + if loc == 0 and whence == 1: + return + if loc == self.loc and whence == 0: + return + raise ValueError("Cannot seek streaming HTTP file") + + async def _read(self, num=-1): + out = await self.r.content.read(num) + self.loc += len(out) + return out + + read = sync_wrapper(_read) + + async def _close(self): + self.r.close() + + def close(self): + asyncio.run_coroutine_threadsafe(self._close(), self.loop) + super().close() + + +class AsyncStreamFile(AbstractAsyncStreamedFile): + def __init__( + self, fs, url, mode="rb", loop=None, session=None, size=None, **kwargs + ): + self.url = url + self.session = session + self.r = None + if mode != "rb": + raise ValueError + self.details = {"name": url, "size": None} + self.kwargs = kwargs + super().__init__(fs=fs, path=url, mode=mode, cache_type="none") + self.size = size + + async def read(self, num=-1): + if self.r is None: + r = await self.session.get( + self.fs.encode_url(self.url), **self.kwargs + ).__aenter__() + self.fs._raise_not_found_for_status(r, self.url) + self.r = r + out = await self.r.content.read(num) + self.loc += len(out) + return out + + async def close(self): + if self.r is not None: + self.r.close() + self.r = None + await super().close() + + +async def get_range(session, url, start, end, file=None, **kwargs): + # explicit get a range when we know it must be safe + kwargs = kwargs.copy() + headers = kwargs.pop("headers", {}).copy() + headers["Range"] = f"bytes={start}-{end - 1}" + r = await session.get(url, headers=headers, **kwargs) + r.raise_for_status() + async with r: + out = await r.read() + if file: + with open(file, "r+b") as f: # noqa: ASYNC230 + f.seek(start) + f.write(out) + else: + return out + + +async def _file_info(url, session, size_policy="head", **kwargs): + """Call HEAD on the server to get details about the file (size/checksum etc.) + + Default operation is to explicitly allow redirects and use encoding + 'identity' (no compression) to get the true size of the target. + """ + logger.debug("Retrieve file size for %s", url) + kwargs = kwargs.copy() + ar = kwargs.pop("allow_redirects", True) + head = kwargs.get("headers", {}).copy() + head["Accept-Encoding"] = "identity" + kwargs["headers"] = head + + info = {} + if size_policy == "head": + r = await session.head(url, allow_redirects=ar, **kwargs) + elif size_policy == "get": + r = await session.get(url, allow_redirects=ar, **kwargs) + else: + raise TypeError(f'size_policy must be "head" or "get", got {size_policy}') + async with r: + r.raise_for_status() + + if "Content-Length" in r.headers: + # Some servers may choose to ignore Accept-Encoding and return + # compressed content, in which case the returned size is unreliable. + if "Content-Encoding" not in r.headers or r.headers["Content-Encoding"] in [ + "identity", + "", + ]: + info["size"] = int(r.headers["Content-Length"]) + elif "Content-Range" in r.headers: + info["size"] = int(r.headers["Content-Range"].split("/")[1]) + + if "Content-Type" in r.headers: + info["mimetype"] = r.headers["Content-Type"].partition(";")[0] + + if r.headers.get("Accept-Ranges") == "none": + # Some servers may explicitly discourage partial content requests, but + # the lack of "Accept-Ranges" does not always indicate they would fail + info["partial"] = False + + info["url"] = str(r.url) + + for checksum_field in ["ETag", "Content-MD5", "Digest", "Last-Modified"]: + if r.headers.get(checksum_field): + info[checksum_field] = r.headers[checksum_field] + + return info + + +async def _file_size(url, session=None, *args, **kwargs): + if session is None: + session = await get_client() + info = await _file_info(url, session=session, *args, **kwargs) + return info.get("size") + + +file_size = sync_wrapper(_file_size) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/http_sync.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/http_sync.py new file mode 100644 index 0000000000000000000000000000000000000000..a67ea3ea5fee9e6b51f7f3f66773e8cf65735e52 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/http_sync.py @@ -0,0 +1,937 @@ +"""This file is largely copied from http.py""" + +import io +import logging +import re +import urllib.error +import urllib.parse +from copy import copy +from json import dumps, loads +from urllib.parse import urlparse + +try: + import yarl +except (ImportError, ModuleNotFoundError, OSError): + yarl = False + +from fsspec.callbacks import _DEFAULT_CALLBACK +from fsspec.registry import register_implementation +from fsspec.spec import AbstractBufferedFile, AbstractFileSystem +from fsspec.utils import DEFAULT_BLOCK_SIZE, isfilelike, nullcontext, tokenize + +from ..caching import AllBytes + +# https://stackoverflow.com/a/15926317/3821154 +ex = re.compile(r"""<(a|A)\s+(?:[^>]*?\s+)?(href|HREF)=["'](?P[^"']+)""") +ex2 = re.compile(r"""(?Phttp[s]?://[-a-zA-Z0-9@:%_+.~#?&/=]+)""") +logger = logging.getLogger("fsspec.http") + + +class JsHttpException(urllib.error.HTTPError): ... + + +class StreamIO(io.BytesIO): + # fake class, so you can set attributes on it + # will eventually actually stream + ... + + +class ResponseProxy: + """Looks like a requests response""" + + def __init__(self, req, stream=False): + self.request = req + self.stream = stream + self._data = None + self._headers = None + + @property + def raw(self): + if self._data is None: + b = self.request.response.to_bytes() + if self.stream: + self._data = StreamIO(b) + else: + self._data = b + return self._data + + def close(self): + if hasattr(self, "_data"): + del self._data + + @property + def headers(self): + if self._headers is None: + self._headers = dict( + [ + _.split(": ") + for _ in self.request.getAllResponseHeaders().strip().split("\r\n") + ] + ) + return self._headers + + @property + def status_code(self): + return int(self.request.status) + + def raise_for_status(self): + if not self.ok: + raise JsHttpException( + self.url, self.status_code, self.reason, self.headers, None + ) + + def iter_content(self, chunksize, *_, **__): + while True: + out = self.raw.read(chunksize) + if out: + yield out + else: + break + + @property + def reason(self): + return self.request.statusText + + @property + def ok(self): + return self.status_code < 400 + + @property + def url(self): + return self.request.response.responseURL + + @property + def text(self): + # TODO: encoding from headers + return self.content.decode() + + @property + def content(self): + self.stream = False + return self.raw + + def json(self): + return loads(self.text) + + +class RequestsSessionShim: + def __init__(self): + self.headers = {} + + def request( + self, + method, + url, + params=None, + data=None, + headers=None, + cookies=None, + files=None, + auth=None, + timeout=None, + allow_redirects=None, + proxies=None, + hooks=None, + stream=None, + verify=None, + cert=None, + json=None, + ): + from js import Blob, XMLHttpRequest + + logger.debug("JS request: %s %s", method, url) + + if cert or verify or proxies or files or cookies or hooks: + raise NotImplementedError + if data and json: + raise ValueError("Use json= or data=, not both") + req = XMLHttpRequest.new() + extra = auth if auth else () + if params: + url = f"{url}?{urllib.parse.urlencode(params)}" + req.open(method, url, False, *extra) + if timeout: + req.timeout = timeout + if headers: + for k, v in headers.items(): + req.setRequestHeader(k, v) + + req.setRequestHeader("Accept", "application/octet-stream") + req.responseType = "arraybuffer" + if json: + blob = Blob.new([dumps(data)], {type: "application/json"}) + req.send(blob) + elif data: + if isinstance(data, io.IOBase): + data = data.read() + blob = Blob.new([data], {type: "application/octet-stream"}) + req.send(blob) + else: + req.send(None) + return ResponseProxy(req, stream=stream) + + def get(self, url, **kwargs): + return self.request("GET", url, **kwargs) + + def head(self, url, **kwargs): + return self.request("HEAD", url, **kwargs) + + def post(self, url, **kwargs): + return self.request("POST}", url, **kwargs) + + def put(self, url, **kwargs): + return self.request("PUT", url, **kwargs) + + def patch(self, url, **kwargs): + return self.request("PATCH", url, **kwargs) + + def delete(self, url, **kwargs): + return self.request("DELETE", url, **kwargs) + + +class HTTPFileSystem(AbstractFileSystem): + """ + Simple File-System for fetching data via HTTP(S) + + This is the BLOCKING version of the normal HTTPFileSystem. It uses + requests in normal python and the JS runtime in pyodide. + + ***This implementation is extremely experimental, do not use unless + you are testing pyodide/pyscript integration*** + """ + + protocol = ("http", "https", "sync-http", "sync-https") + sep = "/" + + def __init__( + self, + simple_links=True, + block_size=None, + same_scheme=True, + cache_type="readahead", + cache_options=None, + client_kwargs=None, + encoded=False, + **storage_options, + ): + """ + + Parameters + ---------- + block_size: int + Blocks to read bytes; if 0, will default to raw requests file-like + objects instead of HTTPFile instances + simple_links: bool + If True, will consider both HTML tags and anything that looks + like a URL; if False, will consider only the former. + same_scheme: True + When doing ls/glob, if this is True, only consider paths that have + http/https matching the input URLs. + size_policy: this argument is deprecated + client_kwargs: dict + Passed to aiohttp.ClientSession, see + https://docs.aiohttp.org/en/stable/client_reference.html + For example, ``{'auth': aiohttp.BasicAuth('user', 'pass')}`` + storage_options: key-value + Any other parameters passed on to requests + cache_type, cache_options: defaults used in open + """ + super().__init__(self, **storage_options) + self.block_size = block_size if block_size is not None else DEFAULT_BLOCK_SIZE + self.simple_links = simple_links + self.same_schema = same_scheme + self.cache_type = cache_type + self.cache_options = cache_options + self.client_kwargs = client_kwargs or {} + self.encoded = encoded + self.kwargs = storage_options + + try: + import js # noqa: F401 + + logger.debug("Starting JS session") + self.session = RequestsSessionShim() + self.js = True + except Exception as e: + import requests + + logger.debug("Starting cpython session because of: %s", e) + self.session = requests.Session(**(client_kwargs or {})) + self.js = False + + request_options = copy(storage_options) + self.use_listings_cache = request_options.pop("use_listings_cache", False) + request_options.pop("listings_expiry_time", None) + request_options.pop("max_paths", None) + request_options.pop("skip_instance_cache", None) + self.kwargs = request_options + + @property + def fsid(self): + return "sync-http" + + def encode_url(self, url): + if yarl: + return yarl.URL(url, encoded=self.encoded) + return url + + @classmethod + def _strip_protocol(cls, path: str) -> str: + """For HTTP, we always want to keep the full URL""" + path = path.replace("sync-http://", "http://").replace( + "sync-https://", "https://" + ) + return path + + @classmethod + def _parent(cls, path): + # override, since _strip_protocol is different for URLs + par = super()._parent(path) + if len(par) > 7: # "http://..." + return par + return "" + + def _ls_real(self, url, detail=True, **kwargs): + # ignoring URL-encoded arguments + kw = self.kwargs.copy() + kw.update(kwargs) + logger.debug(url) + r = self.session.get(self.encode_url(url), **self.kwargs) + self._raise_not_found_for_status(r, url) + text = r.text + if self.simple_links: + links = ex2.findall(text) + [u[2] for u in ex.findall(text)] + else: + links = [u[2] for u in ex.findall(text)] + out = set() + parts = urlparse(url) + for l in links: + if isinstance(l, tuple): + l = l[1] + if l.startswith("/") and len(l) > 1: + # absolute URL on this server + l = parts.scheme + "://" + parts.netloc + l + if l.startswith("http"): + if self.same_schema and l.startswith(url.rstrip("/") + "/"): + out.add(l) + elif l.replace("https", "http").startswith( + url.replace("https", "http").rstrip("/") + "/" + ): + # allowed to cross http <-> https + out.add(l) + else: + if l not in ["..", "../"]: + # Ignore FTP-like "parent" + out.add("/".join([url.rstrip("/"), l.lstrip("/")])) + if not out and url.endswith("/"): + out = self._ls_real(url.rstrip("/"), detail=False) + if detail: + return [ + { + "name": u, + "size": None, + "type": "directory" if u.endswith("/") else "file", + } + for u in out + ] + else: + return sorted(out) + + def ls(self, url, detail=True, **kwargs): + if self.use_listings_cache and url in self.dircache: + out = self.dircache[url] + else: + out = self._ls_real(url, detail=detail, **kwargs) + self.dircache[url] = out + return out + + def _raise_not_found_for_status(self, response, url): + """ + Raises FileNotFoundError for 404s, otherwise uses raise_for_status. + """ + if response.status_code == 404: + raise FileNotFoundError(url) + response.raise_for_status() + + def cat_file(self, url, start=None, end=None, **kwargs): + kw = self.kwargs.copy() + kw.update(kwargs) + logger.debug(url) + + if start is not None or end is not None: + if start == end: + return b"" + headers = kw.pop("headers", {}).copy() + + headers["Range"] = self._process_limits(url, start, end) + kw["headers"] = headers + r = self.session.get(self.encode_url(url), **kw) + self._raise_not_found_for_status(r, url) + return r.content + + def get_file( + self, rpath, lpath, chunk_size=5 * 2**20, callback=_DEFAULT_CALLBACK, **kwargs + ): + kw = self.kwargs.copy() + kw.update(kwargs) + logger.debug(rpath) + r = self.session.get(self.encode_url(rpath), **kw) + try: + size = int( + r.headers.get("content-length", None) + or r.headers.get("Content-Length", None) + ) + except (ValueError, KeyError, TypeError): + size = None + + callback.set_size(size) + self._raise_not_found_for_status(r, rpath) + if not isfilelike(lpath): + lpath = open(lpath, "wb") + for chunk in r.iter_content(chunk_size, decode_unicode=False): + lpath.write(chunk) + callback.relative_update(len(chunk)) + + def put_file( + self, + lpath, + rpath, + chunk_size=5 * 2**20, + callback=_DEFAULT_CALLBACK, + method="post", + **kwargs, + ): + def gen_chunks(): + # Support passing arbitrary file-like objects + # and use them instead of streams. + if isinstance(lpath, io.IOBase): + context = nullcontext(lpath) + use_seek = False # might not support seeking + else: + context = open(lpath, "rb") + use_seek = True + + with context as f: + if use_seek: + callback.set_size(f.seek(0, 2)) + f.seek(0) + else: + callback.set_size(getattr(f, "size", None)) + + chunk = f.read(chunk_size) + while chunk: + yield chunk + callback.relative_update(len(chunk)) + chunk = f.read(chunk_size) + + kw = self.kwargs.copy() + kw.update(kwargs) + + method = method.lower() + if method not in ("post", "put"): + raise ValueError( + f"method has to be either 'post' or 'put', not: {method!r}" + ) + + meth = getattr(self.session, method) + resp = meth(rpath, data=gen_chunks(), **kw) + self._raise_not_found_for_status(resp, rpath) + + def _process_limits(self, url, start, end): + """Helper for "Range"-based _cat_file""" + size = None + suff = False + if start is not None and start < 0: + # if start is negative and end None, end is the "suffix length" + if end is None: + end = -start + start = "" + suff = True + else: + size = size or self.info(url)["size"] + start = size + start + elif start is None: + start = 0 + if not suff: + if end is not None and end < 0: + if start is not None: + size = size or self.info(url)["size"] + end = size + end + elif end is None: + end = "" + if isinstance(end, int): + end -= 1 # bytes range is inclusive + return f"bytes={start}-{end}" + + def exists(self, path, strict=False, **kwargs): + kw = self.kwargs.copy() + kw.update(kwargs) + try: + logger.debug(path) + r = self.session.get(self.encode_url(path), **kw) + if strict: + self._raise_not_found_for_status(r, path) + return r.status_code < 400 + except FileNotFoundError: + return False + except Exception: + if strict: + raise + return False + + def isfile(self, path, **kwargs): + return self.exists(path, **kwargs) + + def _open( + self, + path, + mode="rb", + block_size=None, + autocommit=None, # XXX: This differs from the base class. + cache_type=None, + cache_options=None, + size=None, + **kwargs, + ): + """Make a file-like object + + Parameters + ---------- + path: str + Full URL with protocol + mode: string + must be "rb" + block_size: int or None + Bytes to download in one request; use instance value if None. If + zero, will return a streaming Requests file-like instance. + kwargs: key-value + Any other parameters, passed to requests calls + """ + if mode != "rb": + raise NotImplementedError + block_size = block_size if block_size is not None else self.block_size + kw = self.kwargs.copy() + kw.update(kwargs) + size = size or self.info(path, **kwargs)["size"] + if block_size and size: + return HTTPFile( + self, + path, + session=self.session, + block_size=block_size, + mode=mode, + size=size, + cache_type=cache_type or self.cache_type, + cache_options=cache_options or self.cache_options, + **kw, + ) + else: + return HTTPStreamFile( + self, + path, + mode=mode, + session=self.session, + **kw, + ) + + def ukey(self, url): + """Unique identifier; assume HTTP files are static, unchanging""" + return tokenize(url, self.kwargs, self.protocol) + + def info(self, url, **kwargs): + """Get info of URL + + Tries to access location via HEAD, and then GET methods, but does + not fetch the data. + + It is possible that the server does not supply any size information, in + which case size will be given as None (and certain operations on the + corresponding file will not work). + """ + info = {} + for policy in ["head", "get"]: + try: + info.update( + _file_info( + self.encode_url(url), + size_policy=policy, + session=self.session, + **self.kwargs, + **kwargs, + ) + ) + if info.get("size") is not None: + break + except Exception as exc: + if policy == "get": + # If get failed, then raise a FileNotFoundError + raise FileNotFoundError(url) from exc + logger.debug(str(exc)) + + return {"name": url, "size": None, **info, "type": "file"} + + def glob(self, path, maxdepth=None, **kwargs): + """ + Find files by glob-matching. + + This implementation is idntical to the one in AbstractFileSystem, + but "?" is not considered as a character for globbing, because it is + so common in URLs, often identifying the "query" part. + """ + import re + + ends = path.endswith("/") + path = self._strip_protocol(path) + indstar = path.find("*") if path.find("*") >= 0 else len(path) + indbrace = path.find("[") if path.find("[") >= 0 else len(path) + + ind = min(indstar, indbrace) + + detail = kwargs.pop("detail", False) + + if not has_magic(path): + root = path + depth = 1 + if ends: + path += "/*" + elif self.exists(path): + if not detail: + return [path] + else: + return {path: self.info(path)} + else: + if not detail: + return [] # glob of non-existent returns empty + else: + return {} + elif "/" in path[:ind]: + ind2 = path[:ind].rindex("/") + root = path[: ind2 + 1] + depth = None if "**" in path else path[ind2 + 1 :].count("/") + 1 + else: + root = "" + depth = None if "**" in path else path[ind + 1 :].count("/") + 1 + + allpaths = self.find( + root, maxdepth=maxdepth or depth, withdirs=True, detail=True, **kwargs + ) + # Escape characters special to python regex, leaving our supported + # special characters in place. + # See https://www.gnu.org/software/bash/manual/html_node/Pattern-Matching.html + # for shell globbing details. + pattern = ( + "^" + + ( + path.replace("\\", r"\\") + .replace(".", r"\.") + .replace("+", r"\+") + .replace("//", "/") + .replace("(", r"\(") + .replace(")", r"\)") + .replace("|", r"\|") + .replace("^", r"\^") + .replace("$", r"\$") + .replace("{", r"\{") + .replace("}", r"\}") + .rstrip("/") + ) + + "$" + ) + pattern = re.sub("[*]{2}", "=PLACEHOLDER=", pattern) + pattern = re.sub("[*]", "[^/]*", pattern) + pattern = re.compile(pattern.replace("=PLACEHOLDER=", ".*")) + out = { + p: allpaths[p] + for p in sorted(allpaths) + if pattern.match(p.replace("//", "/").rstrip("/")) + } + if detail: + return out + else: + return list(out) + + def isdir(self, path): + # override, since all URLs are (also) files + try: + return bool(self.ls(path)) + except (FileNotFoundError, ValueError): + return False + + +class HTTPFile(AbstractBufferedFile): + """ + A file-like object pointing to a remove HTTP(S) resource + + Supports only reading, with read-ahead of a predermined block-size. + + In the case that the server does not supply the filesize, only reading of + the complete file in one go is supported. + + Parameters + ---------- + url: str + Full URL of the remote resource, including the protocol + session: requests.Session or None + All calls will be made within this session, to avoid restarting + connections where the server allows this + block_size: int or None + The amount of read-ahead to do, in bytes. Default is 5MB, or the value + configured for the FileSystem creating this file + size: None or int + If given, this is the size of the file in bytes, and we don't attempt + to call the server to find the value. + kwargs: all other key-values are passed to requests calls. + """ + + def __init__( + self, + fs, + url, + session=None, + block_size=None, + mode="rb", + cache_type="bytes", + cache_options=None, + size=None, + **kwargs, + ): + if mode != "rb": + raise NotImplementedError("File mode not supported") + self.url = url + self.session = session + self.details = {"name": url, "size": size, "type": "file"} + super().__init__( + fs=fs, + path=url, + mode=mode, + block_size=block_size, + cache_type=cache_type, + cache_options=cache_options, + **kwargs, + ) + + def read(self, length=-1): + """Read bytes from file + + Parameters + ---------- + length: int + Read up to this many bytes. If negative, read all content to end of + file. If the server has not supplied the filesize, attempting to + read only part of the data will raise a ValueError. + """ + if ( + (length < 0 and self.loc == 0) # explicit read all + # but not when the size is known and fits into a block anyways + and not (self.size is not None and self.size <= self.blocksize) + ): + self._fetch_all() + if self.size is None: + if length < 0: + self._fetch_all() + else: + length = min(self.size - self.loc, length) + return super().read(length) + + def _fetch_all(self): + """Read whole file in one shot, without caching + + This is only called when position is still at zero, + and read() is called without a byte-count. + """ + logger.debug(f"Fetch all for {self}") + if not isinstance(self.cache, AllBytes): + r = self.session.get(self.fs.encode_url(self.url), **self.kwargs) + r.raise_for_status() + out = r.content + self.cache = AllBytes(size=len(out), fetcher=None, blocksize=None, data=out) + self.size = len(out) + + def _parse_content_range(self, headers): + """Parse the Content-Range header""" + s = headers.get("Content-Range", "") + m = re.match(r"bytes (\d+-\d+|\*)/(\d+|\*)", s) + if not m: + return None, None, None + + if m[1] == "*": + start = end = None + else: + start, end = [int(x) for x in m[1].split("-")] + total = None if m[2] == "*" else int(m[2]) + return start, end, total + + def _fetch_range(self, start, end): + """Download a block of data + + The expectation is that the server returns only the requested bytes, + with HTTP code 206. If this is not the case, we first check the headers, + and then stream the output - if the data size is bigger than we + requested, an exception is raised. + """ + logger.debug(f"Fetch range for {self}: {start}-{end}") + kwargs = self.kwargs.copy() + headers = kwargs.pop("headers", {}).copy() + headers["Range"] = f"bytes={start}-{end - 1}" + logger.debug("%s : %s", self.url, headers["Range"]) + r = self.session.get(self.fs.encode_url(self.url), headers=headers, **kwargs) + if r.status_code == 416: + # range request outside file + return b"" + r.raise_for_status() + + # If the server has handled the range request, it should reply + # with status 206 (partial content). But we'll guess that a suitable + # Content-Range header or a Content-Length no more than the + # requested range also mean we have got the desired range. + cl = r.headers.get("Content-Length", r.headers.get("content-length", end + 1)) + response_is_range = ( + r.status_code == 206 + or self._parse_content_range(r.headers)[0] == start + or int(cl) <= end - start + ) + + if response_is_range: + # partial content, as expected + out = r.content + elif start > 0: + raise ValueError( + "The HTTP server doesn't appear to support range requests. " + "Only reading this file from the beginning is supported. " + "Open with block_size=0 for a streaming file interface." + ) + else: + # Response is not a range, but we want the start of the file, + # so we can read the required amount anyway. + cl = 0 + out = [] + for chunk in r.iter_content(2**20, False): + out.append(chunk) + cl += len(chunk) + out = b"".join(out)[: end - start] + return out + + +magic_check = re.compile("([*[])") + + +def has_magic(s): + match = magic_check.search(s) + return match is not None + + +class HTTPStreamFile(AbstractBufferedFile): + def __init__(self, fs, url, mode="rb", session=None, **kwargs): + self.url = url + self.session = session + if mode != "rb": + raise ValueError + self.details = {"name": url, "size": None} + super().__init__(fs=fs, path=url, mode=mode, cache_type="readahead", **kwargs) + + r = self.session.get(self.fs.encode_url(url), stream=True, **kwargs) + self.fs._raise_not_found_for_status(r, url) + self.it = r.iter_content(1024, False) + self.leftover = b"" + + self.r = r + + def seek(self, *args, **kwargs): + raise ValueError("Cannot seek streaming HTTP file") + + def read(self, num=-1): + bufs = [self.leftover] + leng = len(self.leftover) + while leng < num or num < 0: + try: + out = self.it.__next__() + except StopIteration: + break + if out: + bufs.append(out) + else: + break + leng += len(out) + out = b"".join(bufs) + if num >= 0: + self.leftover = out[num:] + out = out[:num] + else: + self.leftover = b"" + self.loc += len(out) + return out + + def close(self): + self.r.close() + self.closed = True + + +def get_range(session, url, start, end, **kwargs): + # explicit get a range when we know it must be safe + kwargs = kwargs.copy() + headers = kwargs.pop("headers", {}).copy() + headers["Range"] = f"bytes={start}-{end - 1}" + r = session.get(url, headers=headers, **kwargs) + r.raise_for_status() + return r.content + + +def _file_info(url, session, size_policy="head", **kwargs): + """Call HEAD on the server to get details about the file (size/checksum etc.) + + Default operation is to explicitly allow redirects and use encoding + 'identity' (no compression) to get the true size of the target. + """ + logger.debug("Retrieve file size for %s", url) + kwargs = kwargs.copy() + ar = kwargs.pop("allow_redirects", True) + head = kwargs.get("headers", {}).copy() + # TODO: not allowed in JS + # head["Accept-Encoding"] = "identity" + kwargs["headers"] = head + + info = {} + if size_policy == "head": + r = session.head(url, allow_redirects=ar, **kwargs) + elif size_policy == "get": + r = session.get(url, allow_redirects=ar, **kwargs) + else: + raise TypeError(f'size_policy must be "head" or "get", got {size_policy}') + r.raise_for_status() + + # TODO: + # recognise lack of 'Accept-Ranges', + # or 'Accept-Ranges': 'none' (not 'bytes') + # to mean streaming only, no random access => return None + if "Content-Length" in r.headers: + info["size"] = int(r.headers["Content-Length"]) + elif "Content-Range" in r.headers: + info["size"] = int(r.headers["Content-Range"].split("/")[1]) + elif "content-length" in r.headers: + info["size"] = int(r.headers["content-length"]) + elif "content-range" in r.headers: + info["size"] = int(r.headers["content-range"].split("/")[1]) + + for checksum_field in ["ETag", "Content-MD5", "Digest"]: + if r.headers.get(checksum_field): + info[checksum_field] = r.headers[checksum_field] + + return info + + +# importing this is enough to register it +def register(): + register_implementation("http", HTTPFileSystem, clobber=True) + register_implementation("https", HTTPFileSystem, clobber=True) + register_implementation("sync-http", HTTPFileSystem, clobber=True) + register_implementation("sync-https", HTTPFileSystem, clobber=True) + + +register() + + +def unregister(): + from fsspec.implementations.http import HTTPFileSystem + + register_implementation("http", HTTPFileSystem, clobber=True) + register_implementation("https", HTTPFileSystem, clobber=True) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/jupyter.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/jupyter.py new file mode 100644 index 0000000000000000000000000000000000000000..e5571ed56582170051f3b7cd903093eed4c65244 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/jupyter.py @@ -0,0 +1,129 @@ +import base64 +import io +import re + +import requests + +import fsspec + + +class JupyterFileSystem(fsspec.AbstractFileSystem): + """View of the files as seen by a Jupyter server (notebook or lab)""" + + protocol = ("jupyter", "jlab") + + def __init__(self, url, tok=None, **kwargs): + """ + + Parameters + ---------- + url : str + Base URL of the server, like "http://127.0.0.1:8888". May include + token in the string, which is given by the process when starting up + tok : str + If the token is obtained separately, can be given here + kwargs + """ + if "?" in url: + if tok is None: + try: + tok = re.findall("token=([a-z0-9]+)", url)[0] + except IndexError as e: + raise ValueError("Could not determine token") from e + url = url.split("?", 1)[0] + self.url = url.rstrip("/") + "/api/contents" + self.session = requests.Session() + if tok: + self.session.headers["Authorization"] = f"token {tok}" + + super().__init__(**kwargs) + + def ls(self, path, detail=True, **kwargs): + path = self._strip_protocol(path) + r = self.session.get(f"{self.url}/{path}") + if r.status_code == 404: + raise FileNotFoundError(path) + r.raise_for_status() + out = r.json() + + if out["type"] == "directory": + out = out["content"] + else: + out = [out] + for o in out: + o["name"] = o.pop("path") + o.pop("content") + if o["type"] == "notebook": + o["type"] = "file" + if detail: + return out + return [o["name"] for o in out] + + def cat_file(self, path, start=None, end=None, **kwargs): + path = self._strip_protocol(path) + r = self.session.get(f"{self.url}/{path}") + if r.status_code == 404: + raise FileNotFoundError(path) + r.raise_for_status() + out = r.json() + if out["format"] == "text": + # data should be binary + b = out["content"].encode() + else: + b = base64.b64decode(out["content"]) + return b[start:end] + + def pipe_file(self, path, value, **_): + path = self._strip_protocol(path) + json = { + "name": path.rsplit("/", 1)[-1], + "path": path, + "size": len(value), + "content": base64.b64encode(value).decode(), + "format": "base64", + "type": "file", + } + self.session.put(f"{self.url}/{path}", json=json) + + def mkdir(self, path, create_parents=True, **kwargs): + path = self._strip_protocol(path) + if create_parents and "/" in path: + self.mkdir(path.rsplit("/", 1)[0], True) + json = { + "name": path.rsplit("/", 1)[-1], + "path": path, + "size": None, + "content": None, + "type": "directory", + } + self.session.put(f"{self.url}/{path}", json=json) + + def mv(self, path1, path2, recursive=False, maxdepth=None, **kwargs): + if path1 == path2: + return + self.session.patch(f"{self.url}/{path1}", json={"path": path2}) + + def _rm(self, path): + path = self._strip_protocol(path) + self.session.delete(f"{self.url}/{path}") + + def _open(self, path, mode="rb", **kwargs): + path = self._strip_protocol(path) + if mode == "rb": + data = self.cat_file(path) + return io.BytesIO(data) + else: + return SimpleFileWriter(self, path, mode="wb") + + +class SimpleFileWriter(fsspec.spec.AbstractBufferedFile): + def _upload_chunk(self, final=False): + """Never uploads a chunk until file is done + + Not suitable for large files + """ + if final is False: + return False + self.buffer.seek(0) + data = self.buffer.read() + self.fs.pipe_file(self.path, data) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/libarchive.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/libarchive.py new file mode 100644 index 0000000000000000000000000000000000000000..6f8e750002df72865d611b48022e6634f9572614 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/libarchive.py @@ -0,0 +1,213 @@ +from contextlib import contextmanager +from ctypes import ( + CFUNCTYPE, + POINTER, + c_int, + c_longlong, + c_void_p, + cast, + create_string_buffer, +) + +import libarchive +import libarchive.ffi as ffi + +from fsspec import open_files +from fsspec.archive import AbstractArchiveFileSystem +from fsspec.implementations.memory import MemoryFile +from fsspec.utils import DEFAULT_BLOCK_SIZE + +# Libarchive requires seekable files or memory only for certain archive +# types. However, since we read the directory first to cache the contents +# and also allow random access to any file, the file-like object needs +# to be seekable no matter what. + +# Seek call-backs (not provided in the libarchive python wrapper) +SEEK_CALLBACK = CFUNCTYPE(c_longlong, c_int, c_void_p, c_longlong, c_int) +read_set_seek_callback = ffi.ffi( + "read_set_seek_callback", [ffi.c_archive_p, SEEK_CALLBACK], c_int, ffi.check_int +) +new_api = hasattr(ffi, "NO_OPEN_CB") + + +@contextmanager +def custom_reader(file, format_name="all", filter_name="all", block_size=ffi.page_size): + """Read an archive from a seekable file-like object. + + The `file` object must support the standard `readinto` and 'seek' methods. + """ + buf = create_string_buffer(block_size) + buf_p = cast(buf, c_void_p) + + def read_func(archive_p, context, ptrptr): + # readinto the buffer, returns number of bytes read + length = file.readinto(buf) + # write the address of the buffer into the pointer + ptrptr = cast(ptrptr, POINTER(c_void_p)) + ptrptr[0] = buf_p + # tell libarchive how much data was written into the buffer + return length + + def seek_func(archive_p, context, offset, whence): + file.seek(offset, whence) + # tell libarchvie the current position + return file.tell() + + read_cb = ffi.READ_CALLBACK(read_func) + seek_cb = SEEK_CALLBACK(seek_func) + + if new_api: + open_cb = ffi.NO_OPEN_CB + close_cb = ffi.NO_CLOSE_CB + else: + open_cb = libarchive.read.OPEN_CALLBACK(ffi.VOID_CB) + close_cb = libarchive.read.CLOSE_CALLBACK(ffi.VOID_CB) + + with libarchive.read.new_archive_read(format_name, filter_name) as archive_p: + read_set_seek_callback(archive_p, seek_cb) + ffi.read_open(archive_p, None, open_cb, read_cb, close_cb) + yield libarchive.read.ArchiveRead(archive_p) + + +class LibArchiveFileSystem(AbstractArchiveFileSystem): + """Compressed archives as a file-system (read-only) + + Supports the following formats: + tar, pax , cpio, ISO9660, zip, mtree, shar, ar, raw, xar, lha/lzh, rar + Microsoft CAB, 7-Zip, WARC + + See the libarchive documentation for further restrictions. + https://www.libarchive.org/ + + Keeps file object open while instance lives. It only works in seekable + file-like objects. In case the filesystem does not support this kind of + file object, it is recommended to cache locally. + + This class is pickleable, but not necessarily thread-safe (depends on the + platform). See libarchive documentation for details. + """ + + root_marker = "" + protocol = "libarchive" + cachable = False + + def __init__( + self, + fo="", + mode="r", + target_protocol=None, + target_options=None, + block_size=DEFAULT_BLOCK_SIZE, + **kwargs, + ): + """ + Parameters + ---------- + fo: str or file-like + Contains ZIP, and must exist. If a str, will fetch file using + :meth:`~fsspec.open_files`, which must return one file exactly. + mode: str + Currently, only 'r' accepted + target_protocol: str (optional) + If ``fo`` is a string, this value can be used to override the + FS protocol inferred from a URL + target_options: dict (optional) + Kwargs passed when instantiating the target FS, if ``fo`` is + a string. + """ + super().__init__(self, **kwargs) + if mode != "r": + raise ValueError("Only read from archive files accepted") + if isinstance(fo, str): + files = open_files(fo, protocol=target_protocol, **(target_options or {})) + if len(files) != 1: + raise ValueError( + f'Path "{fo}" did not resolve to exactly one file: "{files}"' + ) + fo = files[0] + self.of = fo + self.fo = fo.__enter__() # the whole instance is a context + self.block_size = block_size + self.dir_cache = None + + @contextmanager + def _open_archive(self): + self.fo.seek(0) + with custom_reader(self.fo, block_size=self.block_size) as arc: + yield arc + + @classmethod + def _strip_protocol(cls, path): + # file paths are always relative to the archive root + return super()._strip_protocol(path).lstrip("/") + + def _get_dirs(self): + fields = { + "name": "pathname", + "size": "size", + "created": "ctime", + "mode": "mode", + "uid": "uid", + "gid": "gid", + "mtime": "mtime", + } + + if self.dir_cache is not None: + return + + self.dir_cache = {} + list_names = [] + with self._open_archive() as arc: + for entry in arc: + if not entry.isdir and not entry.isfile: + # Skip symbolic links, fifo entries, etc. + continue + self.dir_cache.update( + { + dirname: {"name": dirname, "size": 0, "type": "directory"} + for dirname in self._all_dirnames(set(entry.name)) + } + ) + f = {key: getattr(entry, fields[key]) for key in fields} + f["type"] = "directory" if entry.isdir else "file" + list_names.append(entry.name) + + self.dir_cache[f["name"]] = f + # libarchive does not seem to return an entry for the directories (at least + # not in all formats), so get the directories names from the files names + self.dir_cache.update( + { + dirname: {"name": dirname, "size": 0, "type": "directory"} + for dirname in self._all_dirnames(list_names) + } + ) + + def _open( + self, + path, + mode="rb", + block_size=None, + autocommit=True, + cache_options=None, + **kwargs, + ): + path = self._strip_protocol(path) + if mode != "rb": + raise NotImplementedError + + data = b"" + with self._open_archive() as arc: + for entry in arc: + if entry.pathname != path: + continue + + if entry.size == 0: + # empty file, so there are no blocks + break + + for block in entry.get_blocks(entry.size): + data = block + break + else: + raise ValueError + return MemoryFile(fs=self, path=path, data=data) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/local.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/local.py new file mode 100644 index 0000000000000000000000000000000000000000..417447e5bed17525fe89985052b8c2d8319e1aa6 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/local.py @@ -0,0 +1,518 @@ +import datetime +import io +import logging +import os +import os.path as osp +import shutil +import stat +import tempfile +from functools import lru_cache + +from fsspec import AbstractFileSystem +from fsspec.compression import compr +from fsspec.core import get_compression +from fsspec.utils import isfilelike, stringify_path + +logger = logging.getLogger("fsspec.local") + + +class LocalFileSystem(AbstractFileSystem): + """Interface to files on local storage + + Parameters + ---------- + auto_mkdir: bool + Whether, when opening a file, the directory containing it should + be created (if it doesn't already exist). This is assumed by pyarrow + code. + """ + + root_marker = "/" + protocol = "file", "local" + local_file = True + + def __init__(self, auto_mkdir=False, **kwargs): + super().__init__(**kwargs) + self.auto_mkdir = auto_mkdir + + @property + def fsid(self): + return "local" + + def mkdir(self, path, create_parents=True, **kwargs): + path = self._strip_protocol(path) + if self.exists(path): + raise FileExistsError(path) + if create_parents: + self.makedirs(path, exist_ok=True) + else: + os.mkdir(path, **kwargs) + + def makedirs(self, path, exist_ok=False): + path = self._strip_protocol(path) + os.makedirs(path, exist_ok=exist_ok) + + def rmdir(self, path): + path = self._strip_protocol(path) + os.rmdir(path) + + def ls(self, path, detail=False, **kwargs): + path = self._strip_protocol(path) + path_info = self.info(path) + infos = [] + if path_info["type"] == "directory": + with os.scandir(path) as it: + for f in it: + try: + # Only get the info if requested since it is a bit expensive (the stat call inside) + # The strip_protocol is also used in info() and calls make_path_posix to always return posix paths + info = self.info(f) if detail else self._strip_protocol(f.path) + infos.append(info) + except FileNotFoundError: + pass + else: + infos = [path_info] if detail else [path_info["name"]] + + return infos + + def info(self, path, **kwargs): + if isinstance(path, os.DirEntry): + # scandir DirEntry + out = path.stat(follow_symlinks=False) + link = path.is_symlink() + if path.is_dir(follow_symlinks=False): + t = "directory" + elif path.is_file(follow_symlinks=False): + t = "file" + else: + t = "other" + + size = out.st_size + if link: + try: + out2 = path.stat(follow_symlinks=True) + size = out2.st_size + except OSError: + size = 0 + path = self._strip_protocol(path.path) + else: + # str or path-like + path = self._strip_protocol(path) + out = os.stat(path, follow_symlinks=False) + link = stat.S_ISLNK(out.st_mode) + if link: + out = os.stat(path, follow_symlinks=True) + size = out.st_size + if stat.S_ISDIR(out.st_mode): + t = "directory" + elif stat.S_ISREG(out.st_mode): + t = "file" + else: + t = "other" + + # Check for the 'st_birthtime' attribute, which is not always present; fallback to st_ctime + created_time = getattr(out, "st_birthtime", out.st_ctime) + + result = { + "name": path, + "size": size, + "type": t, + "created": created_time, + "islink": link, + } + for field in ["mode", "uid", "gid", "mtime", "ino", "nlink"]: + result[field] = getattr(out, f"st_{field}") + if link: + result["destination"] = os.readlink(path) + return result + + def lexists(self, path, **kwargs): + return osp.lexists(path) + + def cp_file(self, path1, path2, **kwargs): + path1 = self._strip_protocol(path1) + path2 = self._strip_protocol(path2) + if self.auto_mkdir: + self.makedirs(self._parent(path2), exist_ok=True) + if self.isfile(path1): + shutil.copyfile(path1, path2) + elif self.isdir(path1): + self.mkdirs(path2, exist_ok=True) + else: + raise FileNotFoundError(path1) + + def isfile(self, path): + path = self._strip_protocol(path) + return os.path.isfile(path) + + def isdir(self, path): + path = self._strip_protocol(path) + return os.path.isdir(path) + + def get_file(self, path1, path2, callback=None, **kwargs): + if isfilelike(path2): + with open(path1, "rb") as f: + shutil.copyfileobj(f, path2) + else: + return self.cp_file(path1, path2, **kwargs) + + def put_file(self, path1, path2, callback=None, **kwargs): + return self.cp_file(path1, path2, **kwargs) + + def mv(self, path1, path2, recursive: bool = True, **kwargs): + """Move files/directories + For the specific case of local, all ops on directories are recursive and + the recursive= kwarg is ignored. + """ + path1 = self._strip_protocol(path1) + path2 = self._strip_protocol(path2) + + if self.auto_mkdir: + self.makedirs(self._parent(path2), exist_ok=True) + + shutil.move(path1, path2) + + def link(self, src, dst, **kwargs): + src = self._strip_protocol(src) + dst = self._strip_protocol(dst) + os.link(src, dst, **kwargs) + + def symlink(self, src, dst, **kwargs): + src = self._strip_protocol(src) + dst = self._strip_protocol(dst) + os.symlink(src, dst, **kwargs) + + def islink(self, path) -> bool: + return os.path.islink(self._strip_protocol(path)) + + def rm_file(self, path): + os.remove(self._strip_protocol(path)) + + def rm(self, path, recursive=False, maxdepth=None): + if not isinstance(path, list): + path = [path] + + for p in path: + p = self._strip_protocol(p) + if self.isdir(p): + if not recursive: + raise ValueError("Cannot delete directory, set recursive=True") + if osp.abspath(p) == os.getcwd(): + raise ValueError("Cannot delete current working directory") + shutil.rmtree(p) + else: + os.remove(p) + + def unstrip_protocol(self, name): + name = self._strip_protocol(name) # normalise for local/win/... + return f"file://{name}" + + def _open(self, path, mode="rb", block_size=None, **kwargs): + path = self._strip_protocol(path) + if self.auto_mkdir and "w" in mode: + self.makedirs(self._parent(path), exist_ok=True) + return LocalFileOpener(path, mode, fs=self, **kwargs) + + def touch(self, path, truncate=True, **kwargs): + path = self._strip_protocol(path) + if self.auto_mkdir: + self.makedirs(self._parent(path), exist_ok=True) + if self.exists(path): + os.utime(path, None) + else: + open(path, "a").close() + if truncate: + os.truncate(path, 0) + + def created(self, path): + info = self.info(path=path) + return datetime.datetime.fromtimestamp( + info["created"], tz=datetime.timezone.utc + ) + + def modified(self, path): + info = self.info(path=path) + return datetime.datetime.fromtimestamp(info["mtime"], tz=datetime.timezone.utc) + + @classmethod + def _parent(cls, path): + path = cls._strip_protocol(path) + if os.sep == "/": + # posix native + return path.rsplit("/", 1)[0] or "/" + else: + # NT + path_ = path.rsplit("/", 1)[0] + if len(path_) <= 3: + if path_[1:2] == ":": + # nt root (something like c:/) + return path_[0] + ":/" + # More cases may be required here + return path_ + + @classmethod + def _strip_protocol(cls, path): + path = stringify_path(path) + if path.startswith("file://"): + path = path[7:] + elif path.startswith("file:"): + path = path[5:] + elif path.startswith("local://"): + path = path[8:] + elif path.startswith("local:"): + path = path[6:] + + path = make_path_posix(path) + if os.sep != "/": + # This code-path is a stripped down version of + # > drive, path = ntpath.splitdrive(path) + if path[1:2] == ":": + # Absolute drive-letter path, e.g. X:\Windows + # Relative path with drive, e.g. X:Windows + drive, path = path[:2], path[2:] + elif path[:2] == "//": + # UNC drives, e.g. \\server\share or \\?\UNC\server\share + # Device drives, e.g. \\.\device or \\?\device + if (index1 := path.find("/", 2)) == -1 or ( + index2 := path.find("/", index1 + 1) + ) == -1: + drive, path = path, "" + else: + drive, path = path[:index2], path[index2:] + else: + # Relative path, e.g. Windows + drive = "" + + path = path.rstrip("/") or cls.root_marker + return drive + path + + else: + return path.rstrip("/") or cls.root_marker + + def _isfilestore(self): + # Inheriting from DaskFileSystem makes this False (S3, etc. were) + # the original motivation. But we are a posix-like file system. + # See https://github.com/dask/dask/issues/5526 + return True + + def chmod(self, path, mode): + path = stringify_path(path) + return os.chmod(path, mode) + + +def make_path_posix(path): + """Make path generic and absolute for current OS""" + if not isinstance(path, str): + if isinstance(path, (list, set, tuple)): + return type(path)(make_path_posix(p) for p in path) + else: + path = stringify_path(path) + if not isinstance(path, str): + raise TypeError(f"could not convert {path!r} to string") + if os.sep == "/": + # Native posix + if path.startswith("/"): + # most common fast case for posix + return path + elif path.startswith("~"): + return osp.expanduser(path) + elif path.startswith("./"): + path = path[2:] + elif path == ".": + path = "" + return f"{os.getcwd()}/{path}" + else: + # NT handling + if path[0:1] == "/" and path[2:3] == ":": + # path is like "/c:/local/path" + path = path[1:] + if path[1:2] == ":": + # windows full path like "C:\\local\\path" + if len(path) <= 3: + # nt root (something like c:/) + return path[0] + ":/" + path = path.replace("\\", "/") + return path + elif path[0:1] == "~": + return make_path_posix(osp.expanduser(path)) + elif path.startswith(("\\\\", "//")): + # windows UNC/DFS-style paths + return "//" + path[2:].replace("\\", "/") + elif path.startswith(("\\", "/")): + # windows relative path with root + path = path.replace("\\", "/") + return f"{osp.splitdrive(os.getcwd())[0]}{path}" + else: + path = path.replace("\\", "/") + if path.startswith("./"): + path = path[2:] + elif path == ".": + path = "" + return f"{make_path_posix(os.getcwd())}/{path}" + + +def trailing_sep(path): + """Return True if the path ends with a path separator. + + A forward slash is always considered a path separator, even on Operating + Systems that normally use a backslash. + """ + # TODO: if all incoming paths were posix-compliant then separator would + # always be a forward slash, simplifying this function. + # See https://github.com/fsspec/filesystem_spec/pull/1250 + return path.endswith(os.sep) or (os.altsep is not None and path.endswith(os.altsep)) + + +@lru_cache(maxsize=1) +def get_umask(mask: int = 0o666) -> int: + """Get the current umask. + + Follows https://stackoverflow.com/a/44130549 to get the umask. + Temporarily sets the umask to the given value, and then resets it to the + original value. + """ + value = os.umask(mask) + os.umask(value) + return value + + +class LocalFileOpener(io.IOBase): + def __init__( + self, path, mode, autocommit=True, fs=None, compression=None, **kwargs + ): + logger.debug("open file: %s", path) + self.path = path + self.mode = mode + self.fs = fs + self.f = None + self.autocommit = autocommit + self.compression = get_compression(path, compression) + self.blocksize = io.DEFAULT_BUFFER_SIZE + self._open() + + def _open(self): + if self.f is None or self.f.closed: + if self.autocommit or "w" not in self.mode: + self.f = open(self.path, mode=self.mode) + if self.compression: + compress = compr[self.compression] + self.f = compress(self.f, mode=self.mode) + else: + # TODO: check if path is writable? + i, name = tempfile.mkstemp() + os.close(i) # we want normal open and normal buffered file + self.temp = name + self.f = open(name, mode=self.mode) + if "w" not in self.mode: + self.size = self.f.seek(0, 2) + self.f.seek(0) + self.f.size = self.size + + def _fetch_range(self, start, end): + # probably only used by cached FS + if "r" not in self.mode: + raise ValueError + self._open() + self.f.seek(start) + return self.f.read(end - start) + + def __setstate__(self, state): + self.f = None + loc = state.pop("loc", None) + self.__dict__.update(state) + if "r" in state["mode"]: + self.f = None + self._open() + self.f.seek(loc) + + def __getstate__(self): + d = self.__dict__.copy() + d.pop("f") + if "r" in self.mode: + d["loc"] = self.f.tell() + else: + if not self.f.closed: + raise ValueError("Cannot serialise open write-mode local file") + return d + + def commit(self): + if self.autocommit: + raise RuntimeError("Can only commit if not already set to autocommit") + try: + shutil.move(self.temp, self.path) + except PermissionError as e: + # shutil.move raises PermissionError if os.rename + # and the default copy2 fallback with shutil.copystats fail. + # The file should be there nonetheless, but without copied permissions. + # If it doesn't exist, there was no permission to create the file. + if not os.path.exists(self.path): + raise e + else: + # If PermissionError is not raised, permissions can be set. + try: + mask = 0o666 + os.chmod(self.path, mask & ~get_umask(mask)) + except RuntimeError: + pass + + def discard(self): + if self.autocommit: + raise RuntimeError("Cannot discard if set to autocommit") + os.remove(self.temp) + + def readable(self) -> bool: + return True + + def writable(self) -> bool: + return "r" not in self.mode + + def read(self, *args, **kwargs): + return self.f.read(*args, **kwargs) + + def write(self, *args, **kwargs): + return self.f.write(*args, **kwargs) + + def tell(self, *args, **kwargs): + return self.f.tell(*args, **kwargs) + + def seek(self, *args, **kwargs): + return self.f.seek(*args, **kwargs) + + def seekable(self, *args, **kwargs): + return self.f.seekable(*args, **kwargs) + + def readline(self, *args, **kwargs): + return self.f.readline(*args, **kwargs) + + def readlines(self, *args, **kwargs): + return self.f.readlines(*args, **kwargs) + + def close(self): + return self.f.close() + + def truncate(self, size=None) -> int: + return self.f.truncate(size) + + @property + def closed(self): + return self.f.closed + + def fileno(self): + return self.raw.fileno() + + def flush(self) -> None: + self.f.flush() + + def __iter__(self): + return self.f.__iter__() + + def __getattr__(self, item): + return getattr(self.f, item) + + def __enter__(self): + self._incontext = True + return self + + def __exit__(self, exc_type, exc_value, traceback): + self._incontext = False + self.f.__exit__(exc_type, exc_value, traceback) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/memory.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/memory.py new file mode 100644 index 0000000000000000000000000000000000000000..f6b67bbc84e7aa625eee5609c20f6a893ddd349e --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/memory.py @@ -0,0 +1,311 @@ +from __future__ import annotations + +import logging +from datetime import datetime, timezone +from errno import ENOTEMPTY +from io import BytesIO +from pathlib import PurePath, PureWindowsPath +from typing import Any, ClassVar + +from fsspec import AbstractFileSystem +from fsspec.implementations.local import LocalFileSystem +from fsspec.utils import stringify_path + +logger = logging.getLogger("fsspec.memoryfs") + + +class MemoryFileSystem(AbstractFileSystem): + """A filesystem based on a dict of BytesIO objects + + This is a global filesystem so instances of this class all point to the same + in memory filesystem. + """ + + store: ClassVar[dict[str, Any]] = {} # global, do not overwrite! + pseudo_dirs = [""] # global, do not overwrite! + protocol = "memory" + root_marker = "/" + + @classmethod + def _strip_protocol(cls, path): + if isinstance(path, PurePath): + if isinstance(path, PureWindowsPath): + return LocalFileSystem._strip_protocol(path) + else: + path = stringify_path(path) + + path = path.removeprefix("memory://") + if "::" in path or "://" in path: + return path.rstrip("/") + path = path.lstrip("/").rstrip("/") + return "/" + path if path else "" + + def ls(self, path, detail=True, **kwargs): + path = self._strip_protocol(path) + if path in self.store: + # there is a key with this exact name + if not detail: + return [path] + return [ + { + "name": path, + "size": self.store[path].size, + "type": "file", + "created": self.store[path].created.timestamp(), + } + ] + paths = set() + starter = path + "/" + out = [] + for p2 in tuple(self.store): + if p2.startswith(starter): + if "/" not in p2[len(starter) :]: + # exact child + out.append( + { + "name": p2, + "size": self.store[p2].size, + "type": "file", + "created": self.store[p2].created.timestamp(), + } + ) + elif len(p2) > len(starter): + # implied child directory + ppath = starter + p2[len(starter) :].split("/", 1)[0] + if ppath not in paths: + out = out or [] + out.append( + { + "name": ppath, + "size": 0, + "type": "directory", + } + ) + paths.add(ppath) + for p2 in self.pseudo_dirs: + if p2.startswith(starter): + if "/" not in p2[len(starter) :]: + # exact child pdir + if p2 not in paths: + out.append({"name": p2, "size": 0, "type": "directory"}) + paths.add(p2) + else: + # directory implied by deeper pdir + ppath = starter + p2[len(starter) :].split("/", 1)[0] + if ppath not in paths: + out.append({"name": ppath, "size": 0, "type": "directory"}) + paths.add(ppath) + if not out: + if path in self.pseudo_dirs: + # empty dir + return [] + raise FileNotFoundError(path) + if detail: + return out + return sorted([f["name"] for f in out]) + + def mkdir(self, path, create_parents=True, **kwargs): + path = self._strip_protocol(path) + if path in self.store or path in self.pseudo_dirs: + raise FileExistsError(path) + if self._parent(path).strip("/") and self.isfile(self._parent(path)): + raise NotADirectoryError(self._parent(path)) + if create_parents and self._parent(path).strip("/"): + try: + self.mkdir(self._parent(path), create_parents, **kwargs) + except FileExistsError: + pass + if path and path not in self.pseudo_dirs: + self.pseudo_dirs.append(path) + + def makedirs(self, path, exist_ok=False): + try: + self.mkdir(path, create_parents=True) + except FileExistsError: + if not exist_ok: + raise + + def pipe_file(self, path, value, mode="overwrite", **kwargs): + """Set the bytes of given file + + Avoids copies of the data if possible + """ + mode = "xb" if mode == "create" else "wb" + self.open(path, mode=mode, data=value) + + def rmdir(self, path): + path = self._strip_protocol(path) + if path == "": + # silently avoid deleting FS root + return + if path in self.pseudo_dirs: + if not self.ls(path): + self.pseudo_dirs.remove(path) + else: + raise OSError(ENOTEMPTY, "Directory not empty", path) + else: + raise FileNotFoundError(path) + + def info(self, path, **kwargs): + logger.debug("info: %s", path) + path = self._strip_protocol(path) + if path in self.pseudo_dirs or any( + p.startswith(path + "/") for p in list(self.store) + self.pseudo_dirs + ): + return { + "name": path, + "size": 0, + "type": "directory", + } + elif path in self.store: + filelike = self.store[path] + return { + "name": path, + "size": filelike.size, + "type": "file", + "created": getattr(filelike, "created", None), + } + else: + raise FileNotFoundError(path) + + def _open( + self, + path, + mode="rb", + block_size=None, + autocommit=True, + cache_options=None, + **kwargs, + ): + path = self._strip_protocol(path) + if "x" in mode and self.exists(path): + raise FileExistsError + if path in self.pseudo_dirs: + raise IsADirectoryError(path) + parent = path + while len(parent) > 1: + parent = self._parent(parent) + if self.isfile(parent): + raise FileExistsError(parent) + if mode in ["rb", "ab", "r+b", "a+b"]: + if path in self.store: + f = self.store[path] + if "a" in mode: + # position at the end of file + f.seek(0, 2) + else: + # position at the beginning of file + f.seek(0) + return f + else: + raise FileNotFoundError(path) + elif mode in {"wb", "w+b", "xb", "x+b"}: + if "x" in mode and self.exists(path): + raise FileExistsError + m = MemoryFile(self, path, kwargs.get("data")) + if not self._intrans: + m.commit() + return m + else: + name = self.__class__.__name__ + raise ValueError(f"unsupported file mode for {name}: {mode!r}") + + def cp_file(self, path1, path2, **kwargs): + path1 = self._strip_protocol(path1) + path2 = self._strip_protocol(path2) + if self.isfile(path1): + self.store[path2] = MemoryFile( + self, path2, self.store[path1].getvalue() + ) # implicit copy + elif self.isdir(path1): + if path2 not in self.pseudo_dirs: + self.pseudo_dirs.append(path2) + else: + raise FileNotFoundError(path1) + + def cat_file(self, path, start=None, end=None, **kwargs): + logger.debug("cat: %s", path) + path = self._strip_protocol(path) + try: + return bytes(self.store[path].getbuffer()[start:end]) + except KeyError as e: + raise FileNotFoundError(path) from e + + def _rm(self, path): + path = self._strip_protocol(path) + try: + del self.store[path] + except KeyError as e: + raise FileNotFoundError(path) from e + + def modified(self, path): + path = self._strip_protocol(path) + try: + return self.store[path].modified + except KeyError as e: + raise FileNotFoundError(path) from e + + def created(self, path): + path = self._strip_protocol(path) + try: + return self.store[path].created + except KeyError as e: + raise FileNotFoundError(path) from e + + def isfile(self, path): + path = self._strip_protocol(path) + return path in self.store + + def rm(self, path, recursive=False, maxdepth=None): + if isinstance(path, str): + path = self._strip_protocol(path) + else: + path = [self._strip_protocol(p) for p in path] + paths = self.expand_path(path, recursive=recursive, maxdepth=maxdepth) + for p in reversed(paths): + if self.isfile(p): + self.rm_file(p) + # If the expanded path doesn't exist, it is only because the expanded + # path was a directory that does not exist in self.pseudo_dirs. This + # is possible if you directly create files without making the + # directories first. + elif not self.exists(p): + continue + else: + self.rmdir(p) + + +class MemoryFile(BytesIO): + """A BytesIO which can't close and works as a context manager + + Can initialise with data. Each path should only be active once at any moment. + + No need to provide fs, path if auto-committing (default) + """ + + def __init__(self, fs=None, path=None, data=None): + logger.debug("open file %s", path) + self.fs = fs + self.path = path + self.created = datetime.now(tz=timezone.utc) + self.modified = datetime.now(tz=timezone.utc) + if data: + super().__init__(data) + self.seek(0) + + @property + def size(self): + return self.getbuffer().nbytes + + def __enter__(self): + return self + + def close(self): + pass + + def discard(self): + pass + + def commit(self): + self.fs.store[self.path] = self + self.modified = datetime.now(tz=timezone.utc) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/reference.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/reference.py new file mode 100644 index 0000000000000000000000000000000000000000..54e81224bd2c6aa182319c189aae26090f92572b --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/reference.py @@ -0,0 +1,1311 @@ +import base64 +import collections +import io +import itertools +import logging +import math +import os +from functools import lru_cache +from itertools import chain +from typing import TYPE_CHECKING, Literal + +import fsspec.core +from fsspec.spec import AbstractBufferedFile + +try: + import ujson as json +except ImportError: + if not TYPE_CHECKING: + import json + +from fsspec.asyn import AsyncFileSystem +from fsspec.callbacks import DEFAULT_CALLBACK +from fsspec.core import filesystem, open, split_protocol +from fsspec.implementations.asyn_wrapper import AsyncFileSystemWrapper +from fsspec.utils import ( + isfilelike, + merge_offset_ranges, + other_paths, +) + +logger = logging.getLogger("fsspec.reference") + + +class ReferenceNotReachable(RuntimeError): + def __init__(self, reference, target, *args): + super().__init__(*args) + self.reference = reference + self.target = target + + def __str__(self): + return f'Reference "{self.reference}" failed to fetch target {self.target}' + + +def _first(d): + return next(iter(d.values())) + + +def _prot_in_references(path, references): + ref = references.get(path) + if isinstance(ref, (list, tuple)) and isinstance(ref[0], str): + return split_protocol(ref[0])[0] if ref[0] else ref[0] + + +def _protocol_groups(paths, references): + if isinstance(paths, str): + return {_prot_in_references(paths, references): [paths]} + out = {} + for path in paths: + protocol = _prot_in_references(path, references) + out.setdefault(protocol, []).append(path) + return out + + +class RefsValuesView(collections.abc.ValuesView): + def __iter__(self): + for val in self._mapping.zmetadata.values(): + yield json.dumps(val).encode() + yield from self._mapping._items.values() + for field in self._mapping.listdir(): + chunk_sizes = self._mapping._get_chunk_sizes(field) + if len(chunk_sizes) == 0: + yield self._mapping[field + "/0"] + continue + yield from self._mapping._generate_all_records(field) + + +class RefsItemsView(collections.abc.ItemsView): + def __iter__(self): + return zip(self._mapping.keys(), self._mapping.values()) + + +def ravel_multi_index(idx, sizes): + val = 0 + mult = 1 + for i, s in zip(idx[::-1], sizes[::-1]): + val += i * mult + mult *= s + return val + + +class LazyReferenceMapper(collections.abc.MutableMapping): + """This interface can be used to read/write references from Parquet stores. + It is not intended for other types of references. + It can be used with Kerchunk's MultiZarrToZarr method to combine + references into a parquet store. + Examples of this use-case can be found here: + https://fsspec.github.io/kerchunk/advanced.html?highlight=parquet#parquet-storage""" + + # import is class level to prevent numpy dep requirement for fsspec + @property + def np(self): + import numpy as np + + return np + + @property + def pd(self): + import pandas as pd + + return pd + + def __init__( + self, + root, + fs=None, + out_root=None, + cache_size=128, + categorical_threshold=10, + engine: Literal["fastparquet", "pyarrow"] = "fastparquet", + ): + """ + + This instance will be writable, storing changes in memory until full partitions + are accumulated or .flush() is called. + + To create an empty lazy store, use .create() + + Parameters + ---------- + root : str + Root of parquet store + fs : fsspec.AbstractFileSystem + fsspec filesystem object, default is local filesystem. + cache_size : int, default=128 + Maximum size of LRU cache, where cache_size*record_size denotes + the total number of references that can be loaded in memory at once. + categorical_threshold : int + Encode urls as pandas.Categorical to reduce memory footprint if the ratio + of the number of unique urls to total number of refs for each variable + is greater than or equal to this number. (default 10) + engine: Literal["fastparquet","pyarrow"] + Engine choice for reading parquet files. (default is "fastparquet") + """ + + self.root = root + self.chunk_sizes = {} + self.cat_thresh = categorical_threshold + self.engine = engine + self.cache_size = cache_size + self.url = self.root + "/{field}/refs.{record}.parq" + # TODO: derive fs from `root` + self.fs = fsspec.filesystem("file") if fs is None else fs + self.out_root = self.fs.unstrip_protocol(out_root or self.root) + + from importlib.util import find_spec + + if self.engine == "pyarrow" and find_spec("pyarrow") is None: + raise ImportError("engine choice `pyarrow` is not installed.") + + def __getattr__(self, item): + if item in ("_items", "record_size", "zmetadata"): + self.setup() + # avoid possible recursion if setup fails somehow + return self.__dict__[item] + raise AttributeError(item) + + def setup(self): + self._items = {} + self._items[".zmetadata"] = self.fs.cat_file( + "/".join([self.root, ".zmetadata"]) + ) + met = json.loads(self._items[".zmetadata"]) + self.record_size = met["record_size"] + self.zmetadata = met["metadata"] + + # Define function to open and decompress refs + @lru_cache(maxsize=self.cache_size) + def open_refs(field, record): + """cached parquet file loader""" + path = self.url.format(field=field, record=record) + data = io.BytesIO(self.fs.cat_file(path)) + try: + df = self.pd.read_parquet(data, engine=self.engine) + refs = {c: df[c].to_numpy() for c in df.columns} + except OSError: + refs = None + return refs + + self.open_refs = open_refs + + @staticmethod + def create(root, storage_options=None, fs=None, record_size=10000, **kwargs): + """Make empty parquet reference set + + First deletes the contents of the given directory, if it exists. + + Parameters + ---------- + root: str + Directory to contain the output; will be created + storage_options: dict | None + For making the filesystem to use for writing is fs is None + fs: FileSystem | None + Filesystem for writing + record_size: int + Number of references per parquet file + kwargs: passed to __init__ + + Returns + ------- + LazyReferenceMapper instance + """ + met = {"metadata": {}, "record_size": record_size} + if fs is None: + fs, root = fsspec.core.url_to_fs(root, **(storage_options or {})) + if fs.exists(root): + fs.rm(root, recursive=True) + fs.makedirs(root, exist_ok=True) + fs.pipe("/".join([root, ".zmetadata"]), json.dumps(met).encode()) + return LazyReferenceMapper(root, fs, **kwargs) + + @lru_cache + def listdir(self): + """List top-level directories""" + dirs = (p.rsplit("/", 1)[0] for p in self.zmetadata if not p.startswith(".z")) + return set(dirs) + + def ls(self, path="", detail=True): + """Shortcut file listings""" + path = path.rstrip("/") + pathdash = path + "/" if path else "" + dirnames = self.listdir() + dirs = [ + d + for d in dirnames + if d.startswith(pathdash) and "/" not in d.lstrip(pathdash) + ] + if dirs: + others = { + f + for f in chain( + [".zmetadata"], + (name for name in self.zmetadata), + (name for name in self._items), + ) + if f.startswith(pathdash) and "/" not in f.lstrip(pathdash) + } + if detail is False: + others.update(dirs) + return sorted(others) + dirinfo = [{"name": name, "type": "directory", "size": 0} for name in dirs] + fileinfo = [ + { + "name": name, + "type": "file", + "size": len( + json.dumps(self.zmetadata[name]) + if name in self.zmetadata + else self._items[name] + ), + } + for name in others + ] + return sorted(dirinfo + fileinfo, key=lambda s: s["name"]) + field = path + others = set( + [name for name in self.zmetadata if name.startswith(f"{path}/")] + + [name for name in self._items if name.startswith(f"{path}/")] + ) + fileinfo = [ + { + "name": name, + "type": "file", + "size": len( + json.dumps(self.zmetadata[name]) + if name in self.zmetadata + else self._items[name] + ), + } + for name in others + ] + keys = self._keys_in_field(field) + + if detail is False: + return list(others) + list(keys) + recs = self._generate_all_records(field) + recinfo = [ + {"name": name, "type": "file", "size": rec[-1]} + for name, rec in zip(keys, recs) + if rec[0] # filters out path==None, deleted/missing + ] + return fileinfo + recinfo + + def _load_one_key(self, key): + """Get the reference for one key + + Returns bytes, one-element list or three-element list. + """ + if key in self._items: + return self._items[key] + elif key in self.zmetadata: + return json.dumps(self.zmetadata[key]).encode() + elif "/" not in key or self._is_meta(key): + raise KeyError(key) + field, _ = key.rsplit("/", 1) + record, ri, chunk_size = self._key_to_record(key) + maybe = self._items.get((field, record), {}).get(ri, False) + if maybe is None: + # explicitly deleted + raise KeyError + elif maybe: + return maybe + elif chunk_size == 0: + return b"" + + # Chunk keys can be loaded from row group and cached in LRU cache + try: + refs = self.open_refs(field, record) + except (ValueError, TypeError, FileNotFoundError) as exc: + raise KeyError(key) from exc + columns = ["path", "offset", "size", "raw"] + selection = [refs[c][ri] if c in refs else None for c in columns] + raw = selection[-1] + if raw is not None: + return raw + if selection[0] is None: + raise KeyError("This reference does not exist or has been deleted") + if selection[1:3] == [0, 0]: + # URL only + return selection[:1] + # URL, offset, size + return selection[:3] + + @lru_cache(4096) + def _key_to_record(self, key): + """Details needed to construct a reference for one key""" + field, chunk = key.rsplit("/", 1) + chunk_sizes = self._get_chunk_sizes(field) + if len(chunk_sizes) == 0: + return 0, 0, 0 + chunk_idx = [int(c) for c in chunk.split(".")] + chunk_number = ravel_multi_index(chunk_idx, chunk_sizes) + record = chunk_number // self.record_size + ri = chunk_number % self.record_size + return record, ri, len(chunk_sizes) + + def _get_chunk_sizes(self, field): + """The number of chunks along each axis for a given field""" + if field not in self.chunk_sizes: + zarray = self.zmetadata[f"{field}/.zarray"] + size_ratio = [ + math.ceil(s / c) for s, c in zip(zarray["shape"], zarray["chunks"]) + ] + self.chunk_sizes[field] = size_ratio or [1] + return self.chunk_sizes[field] + + def _generate_record(self, field, record): + """The references for a given parquet file of a given field""" + refs = self.open_refs(field, record) + it = iter(zip(*refs.values())) + if len(refs) == 3: + # All urls + return (list(t) for t in it) + elif len(refs) == 1: + # All raws + return refs["raw"] + else: + # Mix of urls and raws + return (list(t[:3]) if not t[3] else t[3] for t in it) + + def _generate_all_records(self, field): + """Load all the references within a field by iterating over the parquet files""" + nrec = 1 + for ch in self._get_chunk_sizes(field): + nrec *= ch + nrec = math.ceil(nrec / self.record_size) + for record in range(nrec): + yield from self._generate_record(field, record) + + def values(self): + return RefsValuesView(self) + + def items(self): + return RefsItemsView(self) + + def __hash__(self): + return id(self) + + def __getitem__(self, key): + return self._load_one_key(key) + + def __setitem__(self, key, value): + if "/" in key and not self._is_meta(key): + field, chunk = key.rsplit("/", 1) + record, i, _ = self._key_to_record(key) + subdict = self._items.setdefault((field, record), {}) + subdict[i] = value + if len(subdict) == self.record_size: + self.write(field, record) + else: + # metadata or top-level + if hasattr(value, "to_bytes"): + val = value.to_bytes().decode() + elif isinstance(value, bytes): + val = value.decode() + else: + val = value + self._items[key] = val + new_value = json.loads(val) + self.zmetadata[key] = {**self.zmetadata.get(key, {}), **new_value} + + @staticmethod + def _is_meta(key): + return key.startswith(".z") or "/.z" in key + + def __delitem__(self, key): + if key in self._items: + del self._items[key] + elif key in self.zmetadata: + del self.zmetadata[key] + else: + if "/" in key and not self._is_meta(key): + field, _ = key.rsplit("/", 1) + record, i, _ = self._key_to_record(key) + subdict = self._items.setdefault((field, record), {}) + subdict[i] = None + if len(subdict) == self.record_size: + self.write(field, record) + else: + # metadata or top-level + self._items[key] = None + + def write(self, field, record, base_url=None, storage_options=None): + # extra requirements if writing + import kerchunk.df + import numpy as np + import pandas as pd + + partition = self._items[(field, record)] + original = False + if len(partition) < self.record_size: + try: + original = self.open_refs(field, record) + except OSError: + pass + + if original: + paths = original["path"] + offsets = original["offset"] + sizes = original["size"] + raws = original["raw"] + else: + paths = np.full(self.record_size, np.nan, dtype="O") + offsets = np.zeros(self.record_size, dtype="int64") + sizes = np.zeros(self.record_size, dtype="int64") + raws = np.full(self.record_size, np.nan, dtype="O") + for j, data in partition.items(): + if isinstance(data, list): + if ( + str(paths.dtype) == "category" + and data[0] not in paths.dtype.categories + ): + paths = paths.add_categories(data[0]) + paths[j] = data[0] + if len(data) > 1: + offsets[j] = data[1] + sizes[j] = data[2] + elif data is None: + # delete + paths[j] = None + offsets[j] = 0 + sizes[j] = 0 + raws[j] = None + else: + # this is the only call into kerchunk, could remove + raws[j] = kerchunk.df._proc_raw(data) + # TODO: only save needed columns + df = pd.DataFrame( + { + "path": paths, + "offset": offsets, + "size": sizes, + "raw": raws, + }, + copy=False, + ) + if df.path.count() / (df.path.nunique() or 1) > self.cat_thresh: + df["path"] = df["path"].astype("category") + object_encoding = {"raw": "bytes", "path": "utf8"} + has_nulls = ["path", "raw"] + + fn = f"{base_url or self.out_root}/{field}/refs.{record}.parq" + self.fs.mkdirs(f"{base_url or self.out_root}/{field}", exist_ok=True) + + if self.engine == "pyarrow": + df_backend_kwargs = {"write_statistics": False} + elif self.engine == "fastparquet": + df_backend_kwargs = { + "stats": False, + "object_encoding": object_encoding, + "has_nulls": has_nulls, + } + else: + raise NotImplementedError(f"{self.engine} not supported") + df.to_parquet( + fn, + engine=self.engine, + storage_options=storage_options + or getattr(self.fs, "storage_options", None), + compression="zstd", + index=False, + **df_backend_kwargs, + ) + + partition.clear() + self._items.pop((field, record)) + + def flush(self, base_url=None, storage_options=None): + """Output any modified or deleted keys + + Parameters + ---------- + base_url: str + Location of the output + """ + + # write what we have so far and clear sub chunks + for thing in list(self._items): + if isinstance(thing, tuple): + field, record = thing + self.write( + field, + record, + base_url=base_url, + storage_options=storage_options, + ) + + # gather .zmetadata from self._items and write that too + for k in list(self._items): + if k != ".zmetadata" and ".z" in k: + self.zmetadata[k] = json.loads(self._items.pop(k)) + met = {"metadata": self.zmetadata, "record_size": self.record_size} + self._items.clear() + self._items[".zmetadata"] = json.dumps(met).encode() + self.fs.pipe( + "/".join([base_url or self.out_root, ".zmetadata"]), + self._items[".zmetadata"], + ) + + # TODO: only clear those that we wrote to? + self.open_refs.cache_clear() + + def __len__(self): + # Caveat: This counts expected references, not actual - but is fast + count = 0 + for field in self.listdir(): + if field.startswith("."): + count += 1 + else: + count += math.prod(self._get_chunk_sizes(field)) + count += len(self.zmetadata) # all metadata keys + # any other files not in reference partitions + count += sum(1 for _ in self._items if not isinstance(_, tuple)) + return count + + def __iter__(self): + # Caveat: returns only existing keys, so the number of these does not + # match len(self) + metas = set(self.zmetadata) + metas.update(self._items) + for bit in metas: + if isinstance(bit, str): + yield bit + for field in self.listdir(): + for k in self._keys_in_field(field): + if k in self: + yield k + + def __contains__(self, item): + try: + self._load_one_key(item) + return True + except KeyError: + return False + + def _keys_in_field(self, field): + """List key names in given field + + Produces strings like "field/x.y" appropriate from the chunking of the array + """ + chunk_sizes = self._get_chunk_sizes(field) + if len(chunk_sizes) == 0: + yield field + "/0" + return + inds = itertools.product(*(range(i) for i in chunk_sizes)) + for ind in inds: + yield field + "/" + ".".join([str(c) for c in ind]) + + +class ReferenceFileSystem(AsyncFileSystem): + """View byte ranges of some other file as a file system + Initial version: single file system target, which must support + async, and must allow start and end args in _cat_file. Later versions + may allow multiple arbitrary URLs for the targets. + This FileSystem is read-only. It is designed to be used with async + targets (for now). We do not get original file details from the target FS. + Configuration is by passing a dict of references at init, or a URL to + a JSON file containing the same; this dict + can also contain concrete data for some set of paths. + Reference dict format: + {path0: bytes_data, path1: (target_url, offset, size)} + https://github.com/fsspec/kerchunk/blob/main/README.md + """ + + protocol = "reference" + cachable = False + + def __init__( + self, + fo, + target=None, + ref_storage_args=None, + target_protocol=None, + target_options=None, + remote_protocol=None, + remote_options=None, + fs=None, + template_overrides=None, + simple_templates=True, + max_gap=64_000, + max_block=256_000_000, + cache_size=128, + **kwargs, + ): + """ + Parameters + ---------- + fo : dict or str + The set of references to use for this instance, with a structure as above. + If str referencing a JSON file, will use fsspec.open, in conjunction + with target_options and target_protocol to open and parse JSON at this + location. If a directory, then assume references are a set of parquet + files to be loaded lazily. + target : str + For any references having target_url as None, this is the default file + target to use + ref_storage_args : dict + If references is a str, use these kwargs for loading the JSON file. + Deprecated: use target_options instead. + target_protocol : str + Used for loading the reference file, if it is a path. If None, protocol + will be derived from the given path + target_options : dict + Extra FS options for loading the reference file ``fo``, if given as a path + remote_protocol : str + The protocol of the filesystem on which the references will be evaluated + (unless fs is provided). If not given, will be derived from the first + URL that has a protocol in the templates or in the references, in that + order. + remote_options : dict + kwargs to go with remote_protocol + fs : AbstractFileSystem | dict(str, (AbstractFileSystem | dict)) + Directly provide a file system(s): + - a single filesystem instance + - a dict of protocol:filesystem, where each value is either a filesystem + instance, or a dict of kwargs that can be used to create in + instance for the given protocol + + If this is given, remote_options and remote_protocol are ignored. + template_overrides : dict + Swap out any templates in the references file with these - useful for + testing. + simple_templates: bool + Whether templates can be processed with simple replace (True) or if + jinja is needed (False, much slower). All reference sets produced by + ``kerchunk`` are simple in this sense, but the spec allows for complex. + max_gap, max_block: int + For merging multiple concurrent requests to the same remote file. + Neighboring byte ranges will only be merged when their + inter-range gap is <= ``max_gap``. Default is 64KB. Set to 0 + to only merge when it requires no extra bytes. Pass a negative + number to disable merging, appropriate for local target files. + Neighboring byte ranges will only be merged when the size of + the aggregated range is <= ``max_block``. Default is 256MB. + cache_size : int + Maximum size of LRU cache, where cache_size*record_size denotes + the total number of references that can be loaded in memory at once. + Only used for lazily loaded references. + kwargs : passed to parent class + """ + super().__init__(**kwargs) + self.target = target + self.template_overrides = template_overrides + self.simple_templates = simple_templates + self.templates = {} + self.fss = {} + self._dircache = {} + self.max_gap = max_gap + self.max_block = max_block + if isinstance(fo, str): + dic = dict( + **(ref_storage_args or target_options or {}), protocol=target_protocol + ) + ref_fs, fo2 = fsspec.core.url_to_fs(fo, **dic) + if ".json" not in fo2 and ( + fo.endswith(("parq", "parquet", "/")) or ref_fs.isdir(fo2) + ): + # Lazy parquet refs + logger.info("Open lazy reference dict from URL %s", fo) + self.references = LazyReferenceMapper( + fo2, + fs=ref_fs, + cache_size=cache_size, + ) + else: + # text JSON + with fsspec.open(fo, "rb", **dic) as f: + logger.info("Read reference from URL %s", fo) + text = json.load(f) + self._process_references(text, template_overrides) + else: + # dictionaries + self._process_references(fo, template_overrides) + if isinstance(fs, dict): + self.fss = { + k: ( + fsspec.filesystem(k.split(":", 1)[0], **opts) + if isinstance(opts, dict) + else opts + ) + for k, opts in fs.items() + } + if None not in self.fss: + self.fss[None] = filesystem("file") + return + if fs is not None: + # single remote FS + remote_protocol = ( + fs.protocol[0] if isinstance(fs.protocol, tuple) else fs.protocol + ) + self.fss[remote_protocol] = fs + + if remote_protocol is None: + # get single protocol from any templates + for ref in self.templates.values(): + if callable(ref): + ref = ref() + protocol, _ = fsspec.core.split_protocol(ref) + if protocol and protocol not in self.fss: + fs = filesystem(protocol, **(remote_options or {})) + self.fss[protocol] = fs + if remote_protocol is None: + # get single protocol from references + # TODO: warning here, since this can be very expensive? + for ref in self.references.values(): + if callable(ref): + ref = ref() + if isinstance(ref, list) and ref[0]: + protocol, _ = fsspec.core.split_protocol(ref[0]) + if protocol not in self.fss: + fs = filesystem(protocol, **(remote_options or {})) + self.fss[protocol] = fs + # only use first remote URL + break + + if remote_protocol and remote_protocol not in self.fss: + fs = filesystem(remote_protocol, **(remote_options or {})) + self.fss[remote_protocol] = fs + + self.fss[None] = fs or filesystem("file") # default one + # Wrap any non-async filesystems to ensure async methods are available below + for k, f in self.fss.items(): + if not f.async_impl: + self.fss[k] = AsyncFileSystemWrapper(f, asynchronous=self.asynchronous) + elif self.asynchronous ^ f.asynchronous: + raise ValueError( + "Reference-FS's target filesystem must have same value " + "of asynchronous" + ) + + def _cat_common(self, path, start=None, end=None): + path = self._strip_protocol(path) + logger.debug(f"cat: {path}") + try: + part = self.references[path] + except KeyError as exc: + raise FileNotFoundError(path) from exc + if isinstance(part, str): + part = part.encode() + if hasattr(part, "to_bytes"): + part = part.to_bytes() + if isinstance(part, bytes): + logger.debug(f"Reference: {path}, type bytes") + if part.startswith(b"base64:"): + part = base64.b64decode(part[7:]) + return part, None, None + + if len(part) == 1: + logger.debug(f"Reference: {path}, whole file => {part}") + url = part[0] + start1, end1 = start, end + else: + url, start0, size = part + logger.debug(f"Reference: {path} => {url}, offset {start0}, size {size}") + end0 = start0 + size + + if start is not None: + if start >= 0: + start1 = start0 + start + else: + start1 = end0 + start + else: + start1 = start0 + if end is not None: + if end >= 0: + end1 = start0 + end + else: + end1 = end0 + end + else: + end1 = end0 + if url is None: + url = self.target + return url, start1, end1 + + async def _cat_file(self, path, start=None, end=None, **kwargs): + part_or_url, start0, end0 = self._cat_common(path, start=start, end=end) + if isinstance(part_or_url, bytes): + return part_or_url[start:end] + protocol, _ = split_protocol(part_or_url) + try: + return await self.fss[protocol]._cat_file( + part_or_url, start=start0, end=end0 + ) + except Exception as e: + raise ReferenceNotReachable(path, part_or_url) from e + + def cat_file(self, path, start=None, end=None, **kwargs): + part_or_url, start0, end0 = self._cat_common(path, start=start, end=end) + if isinstance(part_or_url, bytes): + return part_or_url[start:end] + protocol, _ = split_protocol(part_or_url) + try: + return self.fss[protocol].cat_file(part_or_url, start=start0, end=end0) + except Exception as e: + raise ReferenceNotReachable(path, part_or_url) from e + + def pipe_file(self, path, value, **_): + """Temporarily add binary data or reference as a file""" + self.references[path] = value + + async def _get_file(self, rpath, lpath, **kwargs): + if self.isdir(rpath): + return os.makedirs(lpath, exist_ok=True) + data = await self._cat_file(rpath) + with open(lpath, "wb") as f: + f.write(data) + + def get_file(self, rpath, lpath, callback=DEFAULT_CALLBACK, **kwargs): + if self.isdir(rpath): + return os.makedirs(lpath, exist_ok=True) + data = self.cat_file(rpath, **kwargs) + callback.set_size(len(data)) + if isfilelike(lpath): + lpath.write(data) + else: + with open(lpath, "wb") as f: + f.write(data) + callback.absolute_update(len(data)) + + def get(self, rpath, lpath, recursive=False, **kwargs): + if recursive: + # trigger directory build + self.ls("") + rpath = self.expand_path(rpath, recursive=recursive) + fs = fsspec.filesystem("file", auto_mkdir=True) + targets = other_paths(rpath, lpath) + if recursive: + data = self.cat([r for r in rpath if not self.isdir(r)]) + else: + data = self.cat(rpath) + for remote, local in zip(rpath, targets): + if remote in data: + fs.pipe_file(local, data[remote]) + + def cat(self, path, recursive=False, on_error="raise", **kwargs): + if isinstance(path, str) and recursive: + raise NotImplementedError + if isinstance(path, list) and (recursive or any("*" in p for p in path)): + raise NotImplementedError + # TODO: if references is lazy, pre-fetch all paths in batch before access + proto_dict = _protocol_groups(path, self.references) + out = {} + for proto, paths in proto_dict.items(): + fs = self.fss[proto] + urls, starts, ends, valid_paths = [], [], [], [] + for p in paths: + # find references or label not-found. Early exit if any not + # found and on_error is "raise" + try: + u, s, e = self._cat_common(p) + if not isinstance(u, (bytes, str)): + # nan/None from parquet + continue + except FileNotFoundError as err: + if on_error == "raise": + raise + if on_error != "omit": + out[p] = err + else: + urls.append(u) + starts.append(s) + ends.append(e) + valid_paths.append(p) + + # process references into form for merging + urls2 = [] + starts2 = [] + ends2 = [] + paths2 = [] + whole_files = set() + for u, s, e, p in zip(urls, starts, ends, valid_paths): + if isinstance(u, bytes): + # data + out[p] = u + elif s is None: + # whole file - limits are None, None, but no further + # entries take for this file + whole_files.add(u) + urls2.append(u) + starts2.append(s) + ends2.append(e) + paths2.append(p) + for u, s, e, p in zip(urls, starts, ends, valid_paths): + # second run to account for files that are to be loaded whole + if s is not None and u not in whole_files: + urls2.append(u) + starts2.append(s) + ends2.append(e) + paths2.append(p) + + # merge and fetch consolidated ranges + new_paths, new_starts, new_ends = merge_offset_ranges( + list(urls2), + list(starts2), + list(ends2), + sort=True, + max_gap=self.max_gap, + max_block=self.max_block, + ) + bytes_out = fs.cat_ranges(new_paths, new_starts, new_ends) + + # unbundle from merged bytes - simple approach + for u, s, e, p in zip(urls, starts, ends, valid_paths): + if p in out: + continue # was bytes, already handled + for np, ns, ne, b in zip(new_paths, new_starts, new_ends, bytes_out): + if np == u and (ns is None or ne is None): + if isinstance(b, Exception): + out[p] = b + else: + out[p] = b[s:e] + elif np == u and s >= ns and e <= ne: + if isinstance(b, Exception): + out[p] = b + else: + out[p] = b[s - ns : (e - ne) or None] + + for k, v in out.copy().items(): + # these were valid references, but fetch failed, so transform exc + if isinstance(v, Exception) and k in self.references: + ex = out[k] + new_ex = ReferenceNotReachable(k, self.references[k]) + new_ex.__cause__ = ex + if on_error == "raise": + raise new_ex + elif on_error != "omit": + out[k] = new_ex + + if len(out) == 1 and isinstance(path, str) and "*" not in path: + return _first(out) + return out + + def _process_references(self, references, template_overrides=None): + vers = references.get("version", None) + if vers is None: + self._process_references0(references) + elif vers == 1: + self._process_references1(references, template_overrides=template_overrides) + else: + raise ValueError(f"Unknown reference spec version: {vers}") + # TODO: we make dircache by iterating over all entries, but for Spec >= 1, + # can replace with programmatic. Is it even needed for mapper interface? + + def _process_references0(self, references): + """Make reference dict for Spec Version 0""" + if isinstance(references, dict): + # do not do this for lazy/parquet backend, which will not make dicts, + # but must remain writable in the original object + references = { + key: json.dumps(val) if isinstance(val, dict) else val + for key, val in references.items() + } + self.references = references + + def _process_references1(self, references, template_overrides=None): + if not self.simple_templates or self.templates: + import jinja2 + self.references = {} + self._process_templates(references.get("templates", {})) + + @lru_cache(1000) + def _render_jinja(u): + return jinja2.Template(u).render(**self.templates) + + for k, v in references.get("refs", {}).items(): + if isinstance(v, str): + if v.startswith("base64:"): + self.references[k] = base64.b64decode(v[7:]) + self.references[k] = v + elif isinstance(v, dict): + self.references[k] = json.dumps(v) + elif self.templates: + u = v[0] + if "{{" in u: + if self.simple_templates: + u = ( + u.replace("{{", "{") + .replace("}}", "}") + .format(**self.templates) + ) + else: + u = _render_jinja(u) + self.references[k] = [u] if len(v) == 1 else [u, v[1], v[2]] + else: + self.references[k] = v + self.references.update(self._process_gen(references.get("gen", []))) + + def _process_templates(self, tmp): + self.templates = {} + if self.template_overrides is not None: + tmp.update(self.template_overrides) + for k, v in tmp.items(): + if "{{" in v: + import jinja2 + + self.templates[k] = lambda temp=v, **kwargs: jinja2.Template( + temp + ).render(**kwargs) + else: + self.templates[k] = v + + def _process_gen(self, gens): + out = {} + for gen in gens: + dimension = { + k: ( + v + if isinstance(v, list) + else range(v.get("start", 0), v["stop"], v.get("step", 1)) + ) + for k, v in gen["dimensions"].items() + } + products = ( + dict(zip(dimension.keys(), values)) + for values in itertools.product(*dimension.values()) + ) + for pr in products: + import jinja2 + + key = jinja2.Template(gen["key"]).render(**pr, **self.templates) + url = jinja2.Template(gen["url"]).render(**pr, **self.templates) + if ("offset" in gen) and ("length" in gen): + offset = int( + jinja2.Template(gen["offset"]).render(**pr, **self.templates) + ) + length = int( + jinja2.Template(gen["length"]).render(**pr, **self.templates) + ) + out[key] = [url, offset, length] + elif ("offset" in gen) ^ ("length" in gen): + raise ValueError( + "Both 'offset' and 'length' are required for a " + "reference generator entry if either is provided." + ) + else: + out[key] = [url] + return out + + def _dircache_from_items(self): + self.dircache = {"": []} + it = self.references.items() + for path, part in it: + if isinstance(part, (bytes, str)) or hasattr(part, "to_bytes"): + size = len(part) + elif len(part) == 1: + size = None + else: + _, _, size = part + par = path.rsplit("/", 1)[0] if "/" in path else "" + par0 = par + subdirs = [par0] + while par0 and par0 not in self.dircache: + # collect parent directories + par0 = self._parent(par0) + subdirs.append(par0) + + subdirs.reverse() + for parent, child in zip(subdirs, subdirs[1:]): + # register newly discovered directories + assert child not in self.dircache + assert parent in self.dircache + self.dircache[parent].append( + {"name": child, "type": "directory", "size": 0} + ) + self.dircache[child] = [] + + self.dircache[par].append({"name": path, "type": "file", "size": size}) + + def _open(self, path, mode="rb", block_size=None, cache_options=None, **kwargs): + part_or_url, start0, end0 = self._cat_common(path) + # This logic is kept outside `ReferenceFile` to avoid unnecessary redirection. + # That does mean `_cat_common` gets called twice if it eventually reaches `ReferenceFile`. + if isinstance(part_or_url, bytes): + return io.BytesIO(part_or_url[start0:end0]) + + protocol, _ = split_protocol(part_or_url) + if start0 is None and end0 is None: + return self.fss[protocol]._open( + part_or_url, + mode, + block_size=block_size, + cache_options=cache_options, + **kwargs, + ) + + return ReferenceFile( + self, + path, + mode, + block_size=block_size, + cache_options=cache_options, + **kwargs, + ) + + def ls(self, path, detail=True, **kwargs): + logger.debug("list %s", path) + path = self._strip_protocol(path) + if isinstance(self.references, LazyReferenceMapper): + try: + return self.references.ls(path, detail) + except KeyError: + pass + raise FileNotFoundError(f"'{path}' is not a known key") + if not self.dircache: + self._dircache_from_items() + out = self._ls_from_cache(path) + if out is None: + raise FileNotFoundError(path) + if detail: + return out + return [o["name"] for o in out] + + def exists(self, path, **kwargs): # overwrite auto-sync version + return self.isdir(path) or self.isfile(path) + + def isdir(self, path): # overwrite auto-sync version + if self.dircache: + return path in self.dircache + elif isinstance(self.references, LazyReferenceMapper): + return path in self.references.listdir() + else: + # this may be faster than building dircache for single calls, but + # by looping will be slow for many calls; could cache it? + return any(_.startswith(f"{path}/") for _ in self.references) + + def isfile(self, path): # overwrite auto-sync version + return path in self.references + + async def _ls(self, path, detail=True, **kwargs): # calls fast sync code + return self.ls(path, detail, **kwargs) + + def find(self, path, maxdepth=None, withdirs=False, detail=False, **kwargs): + if withdirs: + return super().find( + path, maxdepth=maxdepth, withdirs=withdirs, detail=detail, **kwargs + ) + if path: + path = self._strip_protocol(path) + r = sorted(k for k in self.references if k.startswith(path)) + else: + r = sorted(self.references) + if detail: + if not self.dircache: + self._dircache_from_items() + return {k: self._ls_from_cache(k)[0] for k in r} + else: + return r + + def info(self, path, **kwargs): + out = self.references.get(path) + if out is not None: + if isinstance(out, (str, bytes)): + # decode base64 here + return {"name": path, "type": "file", "size": len(out)} + elif len(out) > 1: + return {"name": path, "type": "file", "size": out[2]} + else: + out0 = [{"name": path, "type": "file", "size": None}] + else: + out = self.ls(path, True) + out0 = [o for o in out if o["name"] == path] + if not out0: + return {"name": path, "type": "directory", "size": 0} + if out0[0]["size"] is None: + # if this is a whole remote file, update size using remote FS + prot, _ = split_protocol(self.references[path][0]) + out0[0]["size"] = self.fss[prot].size(self.references[path][0]) + return out0[0] + + async def _info(self, path, **kwargs): # calls fast sync code + return self.info(path) + + async def _rm_file(self, path, **kwargs): + self.references.pop( + path, None + ) # ignores FileNotFound, just as well for directories + self.dircache.clear() # this is a bit heavy handed + + async def _pipe_file(self, path, data, mode="overwrite", **kwargs): + if mode == "create" and self.exists(path): + raise FileExistsError + # can be str or bytes + self.references[path] = data + self.dircache.clear() # this is a bit heavy handed + + async def _put_file(self, lpath, rpath, mode="overwrite", **kwargs): + # puts binary + if mode == "create" and self.exists(rpath): + raise FileExistsError + with open(lpath, "rb") as f: + self.references[rpath] = f.read() + self.dircache.clear() # this is a bit heavy handed + + def save_json(self, url, **storage_options): + """Write modified references into new location""" + out = {} + for k, v in self.references.items(): + if isinstance(v, bytes): + try: + out[k] = v.decode("ascii") + except UnicodeDecodeError: + out[k] = (b"base64:" + base64.b64encode(v)).decode() + else: + out[k] = v + with fsspec.open(url, "wb", **storage_options) as f: + f.write(json.dumps({"version": 1, "refs": out}).encode()) + + +class ReferenceFile(AbstractBufferedFile): + def __init__( + self, + fs, + path, + mode="rb", + block_size="default", + autocommit=True, + cache_type="readahead", + cache_options=None, + size=None, + **kwargs, + ): + super().__init__( + fs, + path, + mode=mode, + block_size=block_size, + autocommit=autocommit, + size=size, + cache_type=cache_type, + cache_options=cache_options, + **kwargs, + ) + part_or_url, self.start, self.end = self.fs._cat_common(self.path) + protocol, _ = split_protocol(part_or_url) + self.src_fs = self.fs.fss[protocol] + self.src_path = part_or_url + self._f = None + + @property + def f(self): + if self._f is None or self._f.closed: + self._f = self.src_fs._open( + self.src_path, + mode=self.mode, + block_size=self.blocksize, + autocommit=self.autocommit, + cache_type="none", + **self.kwargs, + ) + return self._f + + def close(self): + if self._f is not None: + self._f.close() + return super().close() + + def _fetch_range(self, start, end): + start = start + self.start + end = min(end + self.start, self.end) + self.f.seek(start) + return self.f.read(end - start) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/sftp.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/sftp.py new file mode 100644 index 0000000000000000000000000000000000000000..6a6db5b569c6f5c4d154372e7fb3c02741388fad --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/sftp.py @@ -0,0 +1,187 @@ +import datetime +import logging +import os +import types +import uuid +from stat import S_ISDIR, S_ISLNK + +import paramiko + +from .. import AbstractFileSystem +from ..utils import infer_storage_options + +logger = logging.getLogger("fsspec.sftp") + + +class SFTPFileSystem(AbstractFileSystem): + """Files over SFTP/SSH + + Peer-to-peer filesystem over SSH using paramiko. + + Note: if using this with the ``open`` or ``open_files``, with full URLs, + there is no way to tell if a path is relative, so all paths are assumed + to be absolute. + """ + + protocol = "sftp", "ssh" + + def __init__(self, host, **ssh_kwargs): + """ + + Parameters + ---------- + host: str + Hostname or IP as a string + temppath: str + Location on the server to put files, when within a transaction + ssh_kwargs: dict + Parameters passed on to connection. See details in + https://docs.paramiko.org/en/3.3/api/client.html#paramiko.client.SSHClient.connect + May include port, username, password... + """ + if self._cached: + return + super().__init__(**ssh_kwargs) + self.temppath = ssh_kwargs.pop("temppath", "/tmp") # remote temp directory + self.host = host + self.ssh_kwargs = ssh_kwargs + self._connect() + + def _connect(self): + logger.debug("Connecting to SFTP server %s", self.host) + self.client = paramiko.SSHClient() + self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + self.client.connect(self.host, **self.ssh_kwargs) + self.ftp = self.client.open_sftp() + + @classmethod + def _strip_protocol(cls, path): + return infer_storage_options(path)["path"] + + @staticmethod + def _get_kwargs_from_urls(urlpath): + out = infer_storage_options(urlpath) + out.pop("path", None) + out.pop("protocol", None) + return out + + def mkdir(self, path, create_parents=True, mode=511): + path = self._strip_protocol(path) + logger.debug("Creating folder %s", path) + if self.exists(path): + raise FileExistsError(f"File exists: {path}") + + if create_parents: + self.makedirs(path) + else: + self.ftp.mkdir(path, mode) + + def makedirs(self, path, exist_ok=False, mode=511): + if self.exists(path) and not exist_ok: + raise FileExistsError(f"File exists: {path}") + + parts = path.split("/") + new_path = "/" if path[:1] == "/" else "" + + for part in parts: + if part: + new_path = f"{new_path}/{part}" if new_path else part + if not self.exists(new_path): + self.ftp.mkdir(new_path, mode) + + def rmdir(self, path): + path = self._strip_protocol(path) + logger.debug("Removing folder %s", path) + self.ftp.rmdir(path) + + def info(self, path): + path = self._strip_protocol(path) + stat = self._decode_stat(self.ftp.stat(path)) + stat["name"] = path + return stat + + @staticmethod + def _decode_stat(stat, parent_path=None): + if S_ISDIR(stat.st_mode): + t = "directory" + elif S_ISLNK(stat.st_mode): + t = "link" + else: + t = "file" + out = { + "name": "", + "size": stat.st_size, + "type": t, + "uid": stat.st_uid, + "gid": stat.st_gid, + "time": datetime.datetime.fromtimestamp( + stat.st_atime, tz=datetime.timezone.utc + ), + "mtime": datetime.datetime.fromtimestamp( + stat.st_mtime, tz=datetime.timezone.utc + ), + } + if parent_path: + out["name"] = "/".join([parent_path.rstrip("/"), stat.filename]) + return out + + def ls(self, path, detail=False): + path = self._strip_protocol(path) + logger.debug("Listing folder %s", path) + stats = [self._decode_stat(stat, path) for stat in self.ftp.listdir_iter(path)] + if detail: + return stats + else: + paths = [stat["name"] for stat in stats] + return sorted(paths) + + def put(self, lpath, rpath, callback=None, **kwargs): + rpath = self._strip_protocol(rpath) + logger.debug("Put file %s into %s", lpath, rpath) + self.ftp.put(lpath, rpath) + + def get_file(self, rpath, lpath, **kwargs): + if self.isdir(rpath): + os.makedirs(lpath, exist_ok=True) + else: + self.ftp.get(self._strip_protocol(rpath), lpath) + + def _open(self, path, mode="rb", block_size=None, **kwargs): + """ + block_size: int or None + If 0, no buffering, if 1, line buffering, if >1, buffer that many + bytes, if None use default from paramiko. + """ + logger.debug("Opening file %s", path) + if kwargs.get("autocommit", True) is False: + # writes to temporary file, move on commit + path2 = "/".join([self.temppath, str(uuid.uuid4())]) + f = self.ftp.open(path2, mode, bufsize=block_size if block_size else -1) + f.temppath = path2 + f.targetpath = path + f.fs = self + f.commit = types.MethodType(commit_a_file, f) + f.discard = types.MethodType(discard_a_file, f) + else: + f = self.ftp.open(path, mode, bufsize=block_size if block_size else -1) + return f + + def _rm(self, path): + if self.isdir(path): + self.ftp.rmdir(path) + else: + self.ftp.remove(path) + + def mv(self, old, new): + new = self._strip_protocol(new) + old = self._strip_protocol(old) + logger.debug("Renaming %s into %s", old, new) + self.ftp.posix_rename(old, new) + + +def commit_a_file(self): + self.fs.mv(self.temppath, self.targetpath) + + +def discard_a_file(self): + self.fs._rm(self.temppath) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/smb.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/smb.py new file mode 100644 index 0000000000000000000000000000000000000000..db6b3f5c3702de90cf121ccca49f3ca2b580df9f --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/smb.py @@ -0,0 +1,416 @@ +""" +This module contains SMBFileSystem class responsible for handling access to +Windows Samba network shares by using package smbprotocol +""" + +import datetime +import re +import uuid +from stat import S_ISDIR, S_ISLNK + +import smbclient +import smbprotocol.exceptions + +from .. import AbstractFileSystem +from ..utils import infer_storage_options + +# ! pylint: disable=bad-continuation + + +class SMBFileSystem(AbstractFileSystem): + """Allow reading and writing to Windows and Samba network shares. + + When using `fsspec.open()` for getting a file-like object the URI + should be specified as this format: + ``smb://workgroup;user:password@server:port/share/folder/file.csv``. + + Example:: + + >>> import fsspec + >>> with fsspec.open( + ... 'smb://myuser:mypassword@myserver.com/' 'share/folder/file.csv' + ... ) as smbfile: + ... df = pd.read_csv(smbfile, sep='|', header=None) + + Note that you need to pass in a valid hostname or IP address for the host + component of the URL. Do not use the Windows/NetBIOS machine name for the + host component. + + The first component of the path in the URL points to the name of the shared + folder. Subsequent path components will point to the directory/folder/file. + + The URL components ``workgroup`` , ``user``, ``password`` and ``port`` may be + optional. + + .. note:: + + For working this source require `smbprotocol`_ to be installed, e.g.:: + + $ pip install smbprotocol + # or + # pip install smbprotocol[kerberos] + + .. _smbprotocol: https://github.com/jborean93/smbprotocol#requirements + + Note: if using this with the ``open`` or ``open_files``, with full URLs, + there is no way to tell if a path is relative, so all paths are assumed + to be absolute. + """ + + protocol = "smb" + + # pylint: disable=too-many-arguments + def __init__( + self, + host, + port=None, + username=None, + password=None, + timeout=60, + encrypt=None, + share_access=None, + register_session_retries=4, + register_session_retry_wait=1, + register_session_retry_factor=10, + auto_mkdir=False, + **kwargs, + ): + """ + You can use _get_kwargs_from_urls to get some kwargs from + a reasonable SMB url. + + Authentication will be anonymous or integrated if username/password are not + given. + + Parameters + ---------- + host: str + The remote server name/ip to connect to + port: int or None + Port to connect with. Usually 445, sometimes 139. + username: str or None + Username to connect with. Required if Kerberos auth is not being used. + password: str or None + User's password on the server, if using username + timeout: int + Connection timeout in seconds + encrypt: bool + Whether to force encryption or not, once this has been set to True + the session cannot be changed back to False. + share_access: str or None + Specifies the default access applied to file open operations + performed with this file system object. + This affects whether other processes can concurrently open a handle + to the same file. + + - None (the default): exclusively locks the file until closed. + - 'r': Allow other handles to be opened with read access. + - 'w': Allow other handles to be opened with write access. + - 'd': Allow other handles to be opened with delete access. + register_session_retries: int + Number of retries to register a session with the server. Retries are not performed + for authentication errors, as they are considered as invalid credentials and not network + issues. If set to negative value, no register attempts will be performed. + register_session_retry_wait: int + Time in seconds to wait between each retry. Number must be non-negative. + register_session_retry_factor: int + Base factor for the wait time between each retry. The wait time + is calculated using exponential function. For factor=1 all wait times + will be equal to `register_session_retry_wait`. For any number of retries, + the last wait time will be equal to `register_session_retry_wait` and for retries>1 + the first wait time will be equal to `register_session_retry_wait / factor`. + Number must be equal to or greater than 1. Optimal factor is 10. + auto_mkdir: bool + Whether, when opening a file, the directory containing it should + be created (if it doesn't already exist). This is assumed by pyarrow + and zarr-python code. + """ + super().__init__(**kwargs) + self.host = host + self.port = port + self.username = username + self.password = password + self.timeout = timeout + self.encrypt = encrypt + self.temppath = kwargs.pop("temppath", "") + self.share_access = share_access + self.register_session_retries = register_session_retries + if register_session_retry_wait < 0: + raise ValueError( + "register_session_retry_wait must be a non-negative integer" + ) + self.register_session_retry_wait = register_session_retry_wait + if register_session_retry_factor < 1: + raise ValueError( + "register_session_retry_factor must be a positive " + "integer equal to or greater than 1" + ) + self.register_session_retry_factor = register_session_retry_factor + self.auto_mkdir = auto_mkdir + self._connect() + + @property + def _port(self): + return 445 if self.port is None else self.port + + def _connect(self): + import time + + if self.register_session_retries <= -1: + return + + retried_errors = [] + + wait_time = self.register_session_retry_wait + n_waits = ( + self.register_session_retries - 1 + ) # -1 = No wait time after the last retry + factor = self.register_session_retry_factor + + # Generate wait times for each retry attempt. + # Wait times are calculated using exponential function. For factor=1 all wait times + # will be equal to `wait`. For any number of retries the last wait time will be + # equal to `wait` and for retries>2 the first wait time will be equal to `wait / factor`. + wait_times = iter( + factor ** (n / n_waits - 1) * wait_time for n in range(0, n_waits + 1) + ) + + for attempt in range(self.register_session_retries + 1): + try: + smbclient.register_session( + self.host, + username=self.username, + password=self.password, + port=self._port, + encrypt=self.encrypt, + connection_timeout=self.timeout, + ) + return + except ( + smbprotocol.exceptions.SMBAuthenticationError, + smbprotocol.exceptions.LogonFailure, + ): + # These exceptions should not be repeated, as they clearly indicate + # that the credentials are invalid and not a network issue. + raise + except ValueError as exc: + if re.findall(r"\[Errno -\d+]", str(exc)): + # This exception is raised by the smbprotocol.transport:Tcp.connect + # and originates from socket.gaierror (OSError). These exceptions might + # be raised due to network instability. We will retry to connect. + retried_errors.append(exc) + else: + # All another ValueError exceptions should be raised, as they are not + # related to network issues. + raise + except Exception as exc: + # Save the exception and retry to connect. This except might be dropped + # in the future, once all exceptions suited for retry are identified. + retried_errors.append(exc) + + if attempt < self.register_session_retries: + time.sleep(next(wait_times)) + + # Raise last exception to inform user about the connection issues. + # Note: Should we use ExceptionGroup to raise all exceptions? + raise retried_errors[-1] + + @classmethod + def _strip_protocol(cls, path): + return infer_storage_options(path)["path"] + + @staticmethod + def _get_kwargs_from_urls(path): + # smb://workgroup;user:password@host:port/share/folder/file.csv + out = infer_storage_options(path) + out.pop("path", None) + out.pop("protocol", None) + return out + + def mkdir(self, path, create_parents=True, **kwargs): + wpath = _as_unc_path(self.host, path) + if create_parents: + smbclient.makedirs(wpath, exist_ok=False, port=self._port, **kwargs) + else: + smbclient.mkdir(wpath, port=self._port, **kwargs) + + def makedirs(self, path, exist_ok=False): + if _share_has_path(path): + wpath = _as_unc_path(self.host, path) + smbclient.makedirs(wpath, exist_ok=exist_ok, port=self._port) + + def rmdir(self, path): + if _share_has_path(path): + wpath = _as_unc_path(self.host, path) + smbclient.rmdir(wpath, port=self._port) + + def info(self, path, **kwargs): + wpath = _as_unc_path(self.host, path) + stats = smbclient.stat(wpath, port=self._port, **kwargs) + if S_ISDIR(stats.st_mode): + stype = "directory" + elif S_ISLNK(stats.st_mode): + stype = "link" + else: + stype = "file" + res = { + "name": path + "/" if stype == "directory" else path, + "size": stats.st_size, + "type": stype, + "uid": stats.st_uid, + "gid": stats.st_gid, + "time": stats.st_atime, + "mtime": stats.st_mtime, + } + return res + + def created(self, path): + """Return the created timestamp of a file as a datetime.datetime""" + wpath = _as_unc_path(self.host, path) + stats = smbclient.stat(wpath, port=self._port) + return datetime.datetime.fromtimestamp(stats.st_ctime, tz=datetime.timezone.utc) + + def modified(self, path): + """Return the modified timestamp of a file as a datetime.datetime""" + wpath = _as_unc_path(self.host, path) + stats = smbclient.stat(wpath, port=self._port) + return datetime.datetime.fromtimestamp(stats.st_mtime, tz=datetime.timezone.utc) + + def ls(self, path, detail=True, **kwargs): + unc = _as_unc_path(self.host, path) + listed = smbclient.listdir(unc, port=self._port, **kwargs) + dirs = ["/".join([path.rstrip("/"), p]) for p in listed] + if detail: + dirs = [self.info(d) for d in dirs] + return dirs + + # pylint: disable=too-many-arguments + def _open( + self, + path, + mode="rb", + block_size=-1, + autocommit=True, + cache_options=None, + **kwargs, + ): + """ + block_size: int or None + If 0, no buffering, 1, line buffering, >1, buffer that many bytes + + Notes + ----- + By specifying 'share_access' in 'kwargs' it is possible to override the + default shared access setting applied in the constructor of this object. + """ + if self.auto_mkdir and "w" in mode: + self.makedirs(self._parent(path), exist_ok=True) + bls = block_size if block_size is not None and block_size >= 0 else -1 + wpath = _as_unc_path(self.host, path) + share_access = kwargs.pop("share_access", self.share_access) + if "w" in mode and autocommit is False: + temp = _as_temp_path(self.host, path, self.temppath) + return SMBFileOpener( + wpath, temp, mode, port=self._port, block_size=bls, **kwargs + ) + return smbclient.open_file( + wpath, + mode, + buffering=bls, + share_access=share_access, + port=self._port, + **kwargs, + ) + + def copy(self, path1, path2, **kwargs): + """Copy within two locations in the same filesystem""" + wpath1 = _as_unc_path(self.host, path1) + wpath2 = _as_unc_path(self.host, path2) + if self.auto_mkdir: + self.makedirs(self._parent(path2), exist_ok=True) + smbclient.copyfile(wpath1, wpath2, port=self._port, **kwargs) + + def _rm(self, path): + if _share_has_path(path): + wpath = _as_unc_path(self.host, path) + stats = smbclient.stat(wpath, port=self._port) + if S_ISDIR(stats.st_mode): + smbclient.rmdir(wpath, port=self._port) + else: + smbclient.remove(wpath, port=self._port) + + def mv(self, path1, path2, recursive=None, maxdepth=None, **kwargs): + wpath1 = _as_unc_path(self.host, path1) + wpath2 = _as_unc_path(self.host, path2) + smbclient.rename(wpath1, wpath2, port=self._port, **kwargs) + + +def _as_unc_path(host, path): + rpath = path.replace("/", "\\") + unc = f"\\\\{host}{rpath}" + return unc + + +def _as_temp_path(host, path, temppath): + share = path.split("/")[1] + temp_file = f"/{share}{temppath}/{uuid.uuid4()}" + unc = _as_unc_path(host, temp_file) + return unc + + +def _share_has_path(path): + parts = path.count("/") + if path.endswith("/"): + return parts > 2 + return parts > 1 + + +class SMBFileOpener: + """writes to remote temporary file, move on commit""" + + def __init__(self, path, temp, mode, port=445, block_size=-1, **kwargs): + self.path = path + self.temp = temp + self.mode = mode + self.block_size = block_size + self.kwargs = kwargs + self.smbfile = None + self._incontext = False + self.port = port + self._open() + + def _open(self): + if self.smbfile is None or self.smbfile.closed: + self.smbfile = smbclient.open_file( + self.temp, + self.mode, + port=self.port, + buffering=self.block_size, + **self.kwargs, + ) + + def commit(self): + """Move temp file to definitive on success.""" + # TODO: use transaction support in SMB protocol + smbclient.replace(self.temp, self.path, port=self.port) + + def discard(self): + """Remove the temp file on failure.""" + smbclient.remove(self.temp, port=self.port) + + def __fspath__(self): + return self.path + + def __iter__(self): + return self.smbfile.__iter__() + + def __getattr__(self, item): + return getattr(self.smbfile, item) + + def __enter__(self): + self._incontext = True + return self.smbfile.__enter__() + + def __exit__(self, exc_type, exc_value, traceback): + self._incontext = False + self.smbfile.__exit__(exc_type, exc_value, traceback) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/tar.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/tar.py new file mode 100644 index 0000000000000000000000000000000000000000..412e5ba4d2cdea7db090dc96412e697909a38d78 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/tar.py @@ -0,0 +1,124 @@ +import logging +import tarfile + +import fsspec +from fsspec.archive import AbstractArchiveFileSystem +from fsspec.compression import compr +from fsspec.utils import infer_compression + +typemap = {b"0": "file", b"5": "directory"} + +logger = logging.getLogger("tar") + + +class TarFileSystem(AbstractArchiveFileSystem): + """Compressed Tar archives as a file-system (read-only) + + Supports the following formats: + tar.gz, tar.bz2, tar.xz + """ + + root_marker = "" + protocol = "tar" + cachable = False + + def __init__( + self, + fo="", + index_store=None, + target_options=None, + target_protocol=None, + compression=None, + **kwargs, + ): + super().__init__(**kwargs) + target_options = target_options or {} + + if isinstance(fo, str): + self.of = fsspec.open(fo, protocol=target_protocol, **target_options) + fo = self.of.open() # keep the reference + + # Try to infer compression. + if compression is None: + name = None + + # Try different ways to get hold of the filename. `fo` might either + # be a `fsspec.LocalFileOpener`, an `io.BufferedReader` or an + # `fsspec.AbstractFileSystem` instance. + try: + # Amended io.BufferedReader or similar. + # This uses a "protocol extension" where original filenames are + # propagated to archive-like filesystems in order to let them + # infer the right compression appropriately. + if hasattr(fo, "original"): + name = fo.original + + # fsspec.LocalFileOpener + elif hasattr(fo, "path"): + name = fo.path + + # io.BufferedReader + elif hasattr(fo, "name"): + name = fo.name + + # fsspec.AbstractFileSystem + elif hasattr(fo, "info"): + name = fo.info()["name"] + + except Exception as ex: + logger.warning( + f"Unable to determine file name, not inferring compression: {ex}" + ) + + if name is not None: + compression = infer_compression(name) + logger.info(f"Inferred compression {compression} from file name {name}") + + if compression is not None: + # TODO: tarfile already implements compression with modes like "'r:gz'", + # but then would seek to offset in the file work? + fo = compr[compression](fo) + + self._fo_ref = fo + self.fo = fo # the whole instance is a context + self.tar = tarfile.TarFile(fileobj=self.fo) + self.dir_cache = None + + self.index_store = index_store + self.index = None + self._index() + + def _index(self): + # TODO: load and set saved index, if exists + out = {} + for ti in self.tar: + info = ti.get_info() + info["type"] = typemap.get(info["type"], "file") + name = ti.get_info()["name"].rstrip("/") + out[name] = (info, ti.offset_data) + + self.index = out + # TODO: save index to self.index_store here, if set + + def _get_dirs(self): + if self.dir_cache is not None: + return + + # This enables ls to get directories as children as well as files + self.dir_cache = { + dirname: {"name": dirname, "size": 0, "type": "directory"} + for dirname in self._all_dirnames(self.tar.getnames()) + } + for member in self.tar.getmembers(): + info = member.get_info() + info["name"] = info["name"].rstrip("/") + info["type"] = typemap.get(info["type"], "file") + self.dir_cache[info["name"]] = info + + def _open(self, path, mode="rb", **kwargs): + if mode != "rb": + raise ValueError("Read-only filesystem implementation") + details, offset = self.index[path] + if details["type"] != "file": + raise ValueError("Can only handle regular files") + return self.tar.extractfile(path) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/webhdfs.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/webhdfs.py new file mode 100644 index 0000000000000000000000000000000000000000..e3048b6a3638cf34e400804a3057521e720b6e81 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/webhdfs.py @@ -0,0 +1,503 @@ +# https://hadoop.apache.org/docs/r1.0.4/webhdfs.html + +import logging +import os +import secrets +import shutil +import tempfile +import uuid +from contextlib import suppress +from datetime import datetime +from urllib.parse import quote + +import requests + +from ..spec import AbstractBufferedFile, AbstractFileSystem +from ..utils import infer_storage_options, tokenize + +logger = logging.getLogger("webhdfs") + + +class WebHDFS(AbstractFileSystem): + """ + Interface to HDFS over HTTP using the WebHDFS API. Supports also HttpFS gateways. + + Four auth mechanisms are supported: + + insecure: no auth is done, and the user is assumed to be whoever they + say they are (parameter ``user``), or a predefined value such as + "dr.who" if not given + spnego: when kerberos authentication is enabled, auth is negotiated by + requests_kerberos https://github.com/requests/requests-kerberos . + This establishes a session based on existing kinit login and/or + specified principal/password; parameters are passed with ``kerb_kwargs`` + token: uses an existing Hadoop delegation token from another secured + service. Indeed, this client can also generate such tokens when + not insecure. Note that tokens expire, but can be renewed (by a + previously specified user) and may allow for proxying. + basic-auth: used when both parameter ``user`` and parameter ``password`` + are provided. + + """ + + tempdir = str(tempfile.gettempdir()) + protocol = "webhdfs", "webHDFS" + + def __init__( + self, + host, + port=50070, + kerberos=False, + token=None, + user=None, + password=None, + proxy_to=None, + kerb_kwargs=None, + data_proxy=None, + use_https=False, + session_cert=None, + session_verify=True, + **kwargs, + ): + """ + Parameters + ---------- + host: str + Name-node address + port: int + Port for webHDFS + kerberos: bool + Whether to authenticate with kerberos for this connection + token: str or None + If given, use this token on every call to authenticate. A user + and user-proxy may be encoded in the token and should not be also + given + user: str or None + If given, assert the user name to connect with + password: str or None + If given, assert the password to use for basic auth. If password + is provided, user must be provided also + proxy_to: str or None + If given, the user has the authority to proxy, and this value is + the user in who's name actions are taken + kerb_kwargs: dict + Any extra arguments for HTTPKerberosAuth, see + ``_ + data_proxy: dict, callable or None + If given, map data-node addresses. This can be necessary if the + HDFS cluster is behind a proxy, running on Docker or otherwise has + a mismatch between the host-names given by the name-node and the + address by which to refer to them from the client. If a dict, + maps host names ``host->data_proxy[host]``; if a callable, full + URLs are passed, and function must conform to + ``url->data_proxy(url)``. + use_https: bool + Whether to connect to the Name-node using HTTPS instead of HTTP + session_cert: str or Tuple[str, str] or None + Path to a certificate file, or tuple of (cert, key) files to use + for the requests.Session + session_verify: str, bool or None + Path to a certificate file to use for verifying the requests.Session. + kwargs + """ + if self._cached: + return + super().__init__(**kwargs) + self.url = f"{'https' if use_https else 'http'}://{host}:{port}/webhdfs/v1" + self.kerb = kerberos + self.kerb_kwargs = kerb_kwargs or {} + self.pars = {} + self.proxy = data_proxy or {} + if token is not None: + if user is not None or proxy_to is not None: + raise ValueError( + "If passing a delegation token, must not set " + "user or proxy_to, as these are encoded in the" + " token" + ) + self.pars["delegation"] = token + self.user = user + self.password = password + + if password is not None: + if user is None: + raise ValueError( + "If passing a password, the user must also be" + "set in order to set up the basic-auth" + ) + else: + if user is not None: + self.pars["user.name"] = user + + if proxy_to is not None: + self.pars["doas"] = proxy_to + if kerberos and user is not None: + raise ValueError( + "If using Kerberos auth, do not specify the " + "user, this is handled by kinit." + ) + + self.session_cert = session_cert + self.session_verify = session_verify + + self._connect() + + self._fsid = f"webhdfs_{tokenize(host, port)}" + + @property + def fsid(self): + return self._fsid + + def _connect(self): + self.session = requests.Session() + + if self.session_cert: + self.session.cert = self.session_cert + + self.session.verify = self.session_verify + + if self.kerb: + from requests_kerberos import HTTPKerberosAuth + + self.session.auth = HTTPKerberosAuth(**self.kerb_kwargs) + + if self.user is not None and self.password is not None: + from requests.auth import HTTPBasicAuth + + self.session.auth = HTTPBasicAuth(self.user, self.password) + + def _call(self, op, method="get", path=None, data=None, redirect=True, **kwargs): + path = self._strip_protocol(path) if path is not None else "" + url = self._apply_proxy(self.url + quote(path, safe="/=")) + args = kwargs.copy() + args.update(self.pars) + args["op"] = op.upper() + logger.debug("sending %s with %s", url, method) + out = self.session.request( + method=method.upper(), + url=url, + params=args, + data=data, + allow_redirects=redirect, + ) + if out.status_code in [400, 401, 403, 404, 500]: + try: + err = out.json() + msg = err["RemoteException"]["message"] + exp = err["RemoteException"]["exception"] + except (ValueError, KeyError): + pass + else: + if exp in ["IllegalArgumentException", "UnsupportedOperationException"]: + raise ValueError(msg) + elif exp in ["SecurityException", "AccessControlException"]: + raise PermissionError(msg) + elif exp in ["FileNotFoundException"]: + raise FileNotFoundError(msg) + else: + raise RuntimeError(msg) + out.raise_for_status() + return out + + def _open( + self, + path, + mode="rb", + block_size=None, + autocommit=True, + replication=None, + permissions=None, + **kwargs, + ): + """ + + Parameters + ---------- + path: str + File location + mode: str + 'rb', 'wb', etc. + block_size: int + Client buffer size for read-ahead or write buffer + autocommit: bool + If False, writes to temporary file that only gets put in final + location upon commit + replication: int + Number of copies of file on the cluster, write mode only + permissions: str or int + posix permissions, write mode only + kwargs + + Returns + ------- + WebHDFile instance + """ + block_size = block_size or self.blocksize + return WebHDFile( + self, + path, + mode=mode, + block_size=block_size, + tempdir=self.tempdir, + autocommit=autocommit, + replication=replication, + permissions=permissions, + ) + + @staticmethod + def _process_info(info): + info["type"] = info["type"].lower() + info["size"] = info["length"] + return info + + @classmethod + def _strip_protocol(cls, path): + return infer_storage_options(path)["path"] + + @staticmethod + def _get_kwargs_from_urls(urlpath): + out = infer_storage_options(urlpath) + out.pop("path", None) + out.pop("protocol", None) + if "username" in out: + out["user"] = out.pop("username") + return out + + def info(self, path): + out = self._call("GETFILESTATUS", path=path) + info = out.json()["FileStatus"] + info["name"] = path + return self._process_info(info) + + def created(self, path): + """Return the created timestamp of a file as a datetime.datetime""" + # The API does not provide creation time, so we use modification time + info = self.info(path) + mtime = info.get("modificationTime", None) + if mtime is not None: + return datetime.fromtimestamp(mtime / 1000) + raise RuntimeError("Could not retrieve creation time (modification time).") + + def modified(self, path): + """Return the modified timestamp of a file as a datetime.datetime""" + info = self.info(path) + mtime = info.get("modificationTime", None) + if mtime is not None: + return datetime.fromtimestamp(mtime / 1000) + raise RuntimeError("Could not retrieve modification time.") + + def ls(self, path, detail=False, **kwargs): + out = self._call("LISTSTATUS", path=path) + infos = out.json()["FileStatuses"]["FileStatus"] + for info in infos: + self._process_info(info) + info["name"] = path.rstrip("/") + "/" + info["pathSuffix"] + if detail: + return sorted(infos, key=lambda i: i["name"]) + else: + return sorted(info["name"] for info in infos) + + def content_summary(self, path): + """Total numbers of files, directories and bytes under path""" + out = self._call("GETCONTENTSUMMARY", path=path) + return out.json()["ContentSummary"] + + def ukey(self, path): + """Checksum info of file, giving method and result""" + out = self._call("GETFILECHECKSUM", path=path, redirect=False) + if "Location" in out.headers: + location = self._apply_proxy(out.headers["Location"]) + out2 = self.session.get(location) + out2.raise_for_status() + return out2.json()["FileChecksum"] + else: + out.raise_for_status() + return out.json()["FileChecksum"] + + def home_directory(self): + """Get user's home directory""" + out = self._call("GETHOMEDIRECTORY") + return out.json()["Path"] + + def get_delegation_token(self, renewer=None): + """Retrieve token which can give the same authority to other uses + + Parameters + ---------- + renewer: str or None + User who may use this token; if None, will be current user + """ + if renewer: + out = self._call("GETDELEGATIONTOKEN", renewer=renewer) + else: + out = self._call("GETDELEGATIONTOKEN") + t = out.json()["Token"] + if t is None: + raise ValueError("No token available for this user/security context") + return t["urlString"] + + def renew_delegation_token(self, token): + """Make token live longer. Returns new expiry time""" + out = self._call("RENEWDELEGATIONTOKEN", method="put", token=token) + return out.json()["long"] + + def cancel_delegation_token(self, token): + """Stop the token from being useful""" + self._call("CANCELDELEGATIONTOKEN", method="put", token=token) + + def chmod(self, path, mod): + """Set the permission at path + + Parameters + ---------- + path: str + location to set (file or directory) + mod: str or int + posix epresentation or permission, give as oct string, e.g, '777' + or 0o777 + """ + self._call("SETPERMISSION", method="put", path=path, permission=mod) + + def chown(self, path, owner=None, group=None): + """Change owning user and/or group""" + kwargs = {} + if owner is not None: + kwargs["owner"] = owner + if group is not None: + kwargs["group"] = group + self._call("SETOWNER", method="put", path=path, **kwargs) + + def set_replication(self, path, replication): + """ + Set file replication factor + + Parameters + ---------- + path: str + File location (not for directories) + replication: int + Number of copies of file on the cluster. Should be smaller than + number of data nodes; normally 3 on most systems. + """ + self._call("SETREPLICATION", path=path, method="put", replication=replication) + + def mkdir(self, path, **kwargs): + self._call("MKDIRS", method="put", path=path) + + def makedirs(self, path, exist_ok=False): + if exist_ok is False and self.exists(path): + raise FileExistsError(path) + self.mkdir(path) + + def mv(self, path1, path2, **kwargs): + self._call("RENAME", method="put", path=path1, destination=path2) + + def rm(self, path, recursive=False, **kwargs): + self._call( + "DELETE", + method="delete", + path=path, + recursive="true" if recursive else "false", + ) + + def rm_file(self, path, **kwargs): + self.rm(path) + + def cp_file(self, lpath, rpath, **kwargs): + with self.open(lpath) as lstream: + tmp_fname = "/".join([self._parent(rpath), f".tmp.{secrets.token_hex(16)}"]) + # Perform an atomic copy (stream to a temporary file and + # move it to the actual destination). + try: + with self.open(tmp_fname, "wb") as rstream: + shutil.copyfileobj(lstream, rstream) + self.mv(tmp_fname, rpath) + except BaseException: + with suppress(FileNotFoundError): + self.rm(tmp_fname) + raise + + def _apply_proxy(self, location): + if self.proxy and callable(self.proxy): + location = self.proxy(location) + elif self.proxy: + # as a dict + for k, v in self.proxy.items(): + location = location.replace(k, v, 1) + return location + + +class WebHDFile(AbstractBufferedFile): + """A file living in HDFS over webHDFS""" + + def __init__(self, fs, path, **kwargs): + super().__init__(fs, path, **kwargs) + kwargs = kwargs.copy() + if kwargs.get("permissions", None) is None: + kwargs.pop("permissions", None) + if kwargs.get("replication", None) is None: + kwargs.pop("replication", None) + self.permissions = kwargs.pop("permissions", 511) + tempdir = kwargs.pop("tempdir") + if kwargs.pop("autocommit", False) is False: + self.target = self.path + self.path = os.path.join(tempdir, str(uuid.uuid4())) + + def _upload_chunk(self, final=False): + """Write one part of a multi-block file upload + + Parameters + ========== + final: bool + This is the last block, so should complete file, if + self.autocommit is True. + """ + out = self.fs.session.post( + self.location, + data=self.buffer.getvalue(), + headers={"content-type": "application/octet-stream"}, + ) + out.raise_for_status() + return True + + def _initiate_upload(self): + """Create remote file/upload""" + kwargs = self.kwargs.copy() + if "a" in self.mode: + op, method = "APPEND", "POST" + else: + op, method = "CREATE", "PUT" + kwargs["overwrite"] = "true" + out = self.fs._call(op, method, self.path, redirect=False, **kwargs) + location = self.fs._apply_proxy(out.headers["Location"]) + if "w" in self.mode: + # create empty file to append to + out2 = self.fs.session.put( + location, headers={"content-type": "application/octet-stream"} + ) + out2.raise_for_status() + # after creating empty file, change location to append to + out2 = self.fs._call("APPEND", "POST", self.path, redirect=False, **kwargs) + self.location = self.fs._apply_proxy(out2.headers["Location"]) + + def _fetch_range(self, start, end): + start = max(start, 0) + end = min(self.size, end) + if start >= end or start >= self.size: + return b"" + out = self.fs._call( + "OPEN", path=self.path, offset=start, length=end - start, redirect=False + ) + out.raise_for_status() + if "Location" in out.headers: + location = out.headers["Location"] + out2 = self.fs.session.get(self.fs._apply_proxy(location)) + return out2.content + else: + return out.content + + def commit(self): + self.fs.mv(self.path, self.target) + + def discard(self): + self.fs.rm(self.path) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/zip.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/zip.py new file mode 100644 index 0000000000000000000000000000000000000000..d55d54665c1f7c097c287e7432e0aa615955053c --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/implementations/zip.py @@ -0,0 +1,181 @@ +import os +import zipfile + +import fsspec +from fsspec.archive import AbstractArchiveFileSystem + + +class ZipFileSystem(AbstractArchiveFileSystem): + """Read/Write contents of ZIP archive as a file-system + + Keeps file object open while instance lives. + + This class is pickleable, but not necessarily thread-safe + """ + + root_marker = "" + protocol = "zip" + cachable = False + + def __init__( + self, + fo="", + mode="r", + target_protocol=None, + target_options=None, + compression=zipfile.ZIP_STORED, + allowZip64=True, + compresslevel=None, + **kwargs, + ): + """ + Parameters + ---------- + fo: str or file-like + Contains ZIP, and must exist. If a str, will fetch file using + :meth:`~fsspec.open_files`, which must return one file exactly. + mode: str + Accept: "r", "w", "a" + target_protocol: str (optional) + If ``fo`` is a string, this value can be used to override the + FS protocol inferred from a URL + target_options: dict (optional) + Kwargs passed when instantiating the target FS, if ``fo`` is + a string. + compression, allowZip64, compresslevel: passed to ZipFile + Only relevant when creating a ZIP + """ + super().__init__(self, **kwargs) + if mode not in set("rwa"): + raise ValueError(f"mode '{mode}' no understood") + self.mode = mode + if isinstance(fo, (str, os.PathLike)): + if mode == "a": + m = "r+b" + else: + m = mode + "b" + fo = fsspec.open( + fo, mode=m, protocol=target_protocol, **(target_options or {}) + ) + self.force_zip_64 = allowZip64 + self.of = fo + self.fo = fo.__enter__() # the whole instance is a context + self.zip = zipfile.ZipFile( + self.fo, + mode=mode, + compression=compression, + allowZip64=allowZip64, + compresslevel=compresslevel, + ) + self.dir_cache = None + + @classmethod + def _strip_protocol(cls, path): + # zip file paths are always relative to the archive root + return super()._strip_protocol(path).lstrip("/") + + def __del__(self): + if hasattr(self, "zip"): + self.close() + del self.zip + + def close(self): + """Commits any write changes to the file. Done on ``del`` too.""" + self.zip.close() + + def _get_dirs(self): + if self.dir_cache is None or self.mode in set("wa"): + # when writing, dir_cache is always in the ZipFile's attributes, + # not read from the file. + files = self.zip.infolist() + self.dir_cache = { + dirname.rstrip("/"): { + "name": dirname.rstrip("/"), + "size": 0, + "type": "directory", + } + for dirname in self._all_dirnames(self.zip.namelist()) + } + for z in files: + f = {s: getattr(z, s, None) for s in zipfile.ZipInfo.__slots__} + f.update( + { + "name": z.filename.rstrip("/"), + "size": z.file_size, + "type": ("directory" if z.is_dir() else "file"), + } + ) + self.dir_cache[f["name"]] = f + + def pipe_file(self, path, value, **kwargs): + # override upstream, because we know the exact file size in this case + self.zip.writestr(path, value, **kwargs) + + def _open( + self, + path, + mode="rb", + block_size=None, + autocommit=True, + cache_options=None, + **kwargs, + ): + path = self._strip_protocol(path) + if "r" in mode and self.mode in set("wa"): + if self.exists(path): + raise OSError("ZipFS can only be open for reading or writing, not both") + raise FileNotFoundError(path) + if "r" in self.mode and "w" in mode: + raise OSError("ZipFS can only be open for reading or writing, not both") + out = self.zip.open(path, mode.strip("b"), force_zip64=self.force_zip_64) + if "r" in mode: + info = self.info(path) + out.size = info["size"] + out.name = info["name"] + return out + + def find(self, path, maxdepth=None, withdirs=False, detail=False, **kwargs): + if maxdepth is not None and maxdepth < 1: + raise ValueError("maxdepth must be at least 1") + + def to_parts(_path: str): + return list(filter(None, _path.replace("\\", "/").split("/"))) + + if not isinstance(path, str): + path = str(path) + + # Remove the leading slash, as the zip file paths are always + # given without a leading slash + path = path.lstrip("/") + path_parts = to_parts(path) + path_depth = len(path_parts) + + self._get_dirs() + + result = {} + # To match posix find, if an exact file name is given, we should + # return only that file + if path in self.dir_cache and self.dir_cache[path]["type"] == "file": + result[path] = self.dir_cache[path] + return result if detail else [path] + + for file_path, file_info in self.dir_cache.items(): + if len(file_parts := to_parts(file_path)) < path_depth or any( + a != b for a, b in zip(path_parts, file_parts) + ): + # skip parent folders and mismatching paths + continue + + if file_info["type"] == "directory": + if withdirs and file_path not in result: + result[file_path.strip("/")] = file_info + continue + + if file_path not in result: + result[file_path] = file_info if detail else None + + if maxdepth: + result = { + k: v for k, v in result.items() if k.count("/") < maxdepth + path_depth + } + return result if detail else sorted(result) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/json.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/json.py new file mode 100644 index 0000000000000000000000000000000000000000..5c53a24913d0b28f4b53a163b97ff8f58abeb031 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/json.py @@ -0,0 +1,112 @@ +import json +from collections.abc import Callable, Mapping, Sequence +from contextlib import suppress +from pathlib import PurePath +from typing import Any, ClassVar + +from .registry import _import_class, get_filesystem_class +from .spec import AbstractFileSystem + + +class FilesystemJSONEncoder(json.JSONEncoder): + include_password: ClassVar[bool] = True + + def default(self, o: Any) -> Any: + if isinstance(o, AbstractFileSystem): + return o.to_dict(include_password=self.include_password) + if isinstance(o, PurePath): + cls = type(o) + return {"cls": f"{cls.__module__}.{cls.__name__}", "str": str(o)} + + return super().default(o) + + def make_serializable(self, obj: Any) -> Any: + """ + Recursively converts an object so that it can be JSON serialized via + :func:`json.dumps` and :func:`json.dump`, without actually calling + said functions. + """ + if isinstance(obj, (str, int, float, bool)): + return obj + if isinstance(obj, Mapping): + return {k: self.make_serializable(v) for k, v in obj.items()} + if isinstance(obj, Sequence): + return [self.make_serializable(v) for v in obj] + + return self.default(obj) + + +class FilesystemJSONDecoder(json.JSONDecoder): + def __init__( + self, + *, + object_hook: Callable[[dict[str, Any]], Any] | None = None, + parse_float: Callable[[str], Any] | None = None, + parse_int: Callable[[str], Any] | None = None, + parse_constant: Callable[[str], Any] | None = None, + strict: bool = True, + object_pairs_hook: Callable[[list[tuple[str, Any]]], Any] | None = None, + ) -> None: + self.original_object_hook = object_hook + + super().__init__( + object_hook=self.custom_object_hook, + parse_float=parse_float, + parse_int=parse_int, + parse_constant=parse_constant, + strict=strict, + object_pairs_hook=object_pairs_hook, + ) + + @classmethod + def try_resolve_path_cls(cls, dct: dict[str, Any]): + with suppress(Exception): + fqp = dct["cls"] + + path_cls = _import_class(fqp) + + if issubclass(path_cls, PurePath): + return path_cls + + return None + + @classmethod + def try_resolve_fs_cls(cls, dct: dict[str, Any]): + with suppress(Exception): + if "cls" in dct: + try: + fs_cls = _import_class(dct["cls"]) + if issubclass(fs_cls, AbstractFileSystem): + return fs_cls + except Exception: + if "protocol" in dct: # Fallback if cls cannot be imported + return get_filesystem_class(dct["protocol"]) + + raise + + return None + + def custom_object_hook(self, dct: dict[str, Any]): + if "cls" in dct: + if (obj_cls := self.try_resolve_fs_cls(dct)) is not None: + return AbstractFileSystem.from_dict(dct) + if (obj_cls := self.try_resolve_path_cls(dct)) is not None: + return obj_cls(dct["str"]) + + if self.original_object_hook is not None: + return self.original_object_hook(dct) + + return dct + + def unmake_serializable(self, obj: Any) -> Any: + """ + Inverse function of :meth:`FilesystemJSONEncoder.make_serializable`. + """ + if isinstance(obj, dict): + obj = self.custom_object_hook(obj) + if isinstance(obj, dict): + return {k: self.unmake_serializable(v) for k, v in obj.items()} + if isinstance(obj, (list, tuple)): + return [self.unmake_serializable(v) for v in obj] + + return obj diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/mapping.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/mapping.py new file mode 100644 index 0000000000000000000000000000000000000000..752eef35273b13eded7297e2e801b58e436a25b1 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/mapping.py @@ -0,0 +1,251 @@ +import array +import logging +import posixpath +import warnings +from collections.abc import MutableMapping +from functools import cached_property + +from fsspec.core import url_to_fs + +logger = logging.getLogger("fsspec.mapping") + + +class FSMap(MutableMapping): + """Wrap a FileSystem instance as a mutable wrapping. + + The keys of the mapping become files under the given root, and the + values (which must be bytes) the contents of those files. + + Parameters + ---------- + root: string + prefix for all the files + fs: FileSystem instance + check: bool (=True) + performs a touch at the location, to check for write access. + + Examples + -------- + >>> fs = FileSystem(**parameters) # doctest: +SKIP + >>> d = FSMap('my-data/path/', fs) # doctest: +SKIP + or, more likely + >>> d = fs.get_mapper('my-data/path/') + + >>> d['loc1'] = b'Hello World' # doctest: +SKIP + >>> list(d.keys()) # doctest: +SKIP + ['loc1'] + >>> d['loc1'] # doctest: +SKIP + b'Hello World' + """ + + def __init__(self, root, fs, check=False, create=False, missing_exceptions=None): + self.fs = fs + self.root = fs._strip_protocol(root) + self._root_key_to_str = fs._strip_protocol(posixpath.join(root, "x"))[:-1] + if missing_exceptions is None: + missing_exceptions = ( + FileNotFoundError, + IsADirectoryError, + NotADirectoryError, + ) + self.missing_exceptions = missing_exceptions + self.check = check + self.create = create + if create: + if not self.fs.exists(root): + self.fs.mkdir(root) + if check: + if not self.fs.exists(root): + raise ValueError( + f"Path {root} does not exist. Create " + f" with the ``create=True`` keyword" + ) + self.fs.touch(root + "/a") + self.fs.rm(root + "/a") + + @cached_property + def dirfs(self): + """dirfs instance that can be used with the same keys as the mapper""" + from .implementations.dirfs import DirFileSystem + + return DirFileSystem(path=self._root_key_to_str, fs=self.fs) + + def clear(self): + """Remove all keys below root - empties out mapping""" + logger.info("Clear mapping at %s", self.root) + try: + self.fs.rm(self.root, True) + self.fs.mkdir(self.root) + except: # noqa: E722 + pass + + def getitems(self, keys, on_error="raise"): + """Fetch multiple items from the store + + If the backend is async-able, this might proceed concurrently + + Parameters + ---------- + keys: list(str) + They keys to be fetched + on_error : "raise", "omit", "return" + If raise, an underlying exception will be raised (converted to KeyError + if the type is in self.missing_exceptions); if omit, keys with exception + will simply not be included in the output; if "return", all keys are + included in the output, but the value will be bytes or an exception + instance. + + Returns + ------- + dict(key, bytes|exception) + """ + keys2 = [self._key_to_str(k) for k in keys] + oe = on_error if on_error == "raise" else "return" + try: + out = self.fs.cat(keys2, on_error=oe) + if isinstance(out, bytes): + out = {keys2[0]: out} + except self.missing_exceptions as e: + raise KeyError from e + out = { + k: (KeyError() if isinstance(v, self.missing_exceptions) else v) + for k, v in out.items() + } + return { + key: out[k2] if on_error == "raise" else out.get(k2, KeyError(k2)) + for key, k2 in zip(keys, keys2) + if on_error == "return" or not isinstance(out[k2], BaseException) + } + + def setitems(self, values_dict): + """Set the values of multiple items in the store + + Parameters + ---------- + values_dict: dict(str, bytes) + """ + values = {self._key_to_str(k): maybe_convert(v) for k, v in values_dict.items()} + self.fs.pipe(values) + + def delitems(self, keys): + """Remove multiple keys from the store""" + self.fs.rm([self._key_to_str(k) for k in keys]) + + def _key_to_str(self, key): + """Generate full path for the key""" + if not isinstance(key, str): + # raise TypeError("key must be of type `str`, got `{type(key).__name__}`" + warnings.warn( + "from fsspec 2023.5 onward FSMap non-str keys will raise TypeError", + DeprecationWarning, + ) + if isinstance(key, list): + key = tuple(key) + key = str(key) + return f"{self._root_key_to_str}{key}".rstrip("/") + + def _str_to_key(self, s): + """Strip path of to leave key name""" + return s[len(self.root) :].lstrip("/") + + def __getitem__(self, key, default=None): + """Retrieve data""" + k = self._key_to_str(key) + try: + result = self.fs.cat(k) + except self.missing_exceptions as exc: + if default is not None: + return default + raise KeyError(key) from exc + return result + + def pop(self, key, default=None): + """Pop data""" + result = self.__getitem__(key, default) + try: + del self[key] + except KeyError: + pass + return result + + def __setitem__(self, key, value): + """Store value in key""" + key = self._key_to_str(key) + self.fs.mkdirs(self.fs._parent(key), exist_ok=True) + self.fs.pipe_file(key, maybe_convert(value)) + + def __iter__(self): + return (self._str_to_key(x) for x in self.fs.find(self.root)) + + def __len__(self): + return len(self.fs.find(self.root)) + + def __delitem__(self, key): + """Remove key""" + try: + self.fs.rm(self._key_to_str(key)) + except Exception as exc: + raise KeyError from exc + + def __contains__(self, key): + """Does key exist in mapping?""" + path = self._key_to_str(key) + return self.fs.isfile(path) + + def __reduce__(self): + return FSMap, (self.root, self.fs, False, False, self.missing_exceptions) + + +def maybe_convert(value): + if isinstance(value, array.array) or hasattr(value, "__array__"): + # bytes-like things + if hasattr(value, "dtype") and value.dtype.kind in "Mm": + # The buffer interface doesn't support datetime64/timdelta64 numpy + # arrays + value = value.view("int64") + value = bytes(memoryview(value)) + return value + + +def get_mapper( + url="", + check=False, + create=False, + missing_exceptions=None, + alternate_root=None, + **kwargs, +): + """Create key-value interface for given URL and options + + The URL will be of the form "protocol://location" and point to the root + of the mapper required. All keys will be file-names below this location, + and their values the contents of each key. + + Also accepts compound URLs like zip::s3://bucket/file.zip , see ``fsspec.open``. + + Parameters + ---------- + url: str + Root URL of mapping + check: bool + Whether to attempt to read from the location before instantiation, to + check that the mapping does exist + create: bool + Whether to make the directory corresponding to the root before + instantiating + missing_exceptions: None or tuple + If given, these exception types will be regarded as missing keys and + return KeyError when trying to read data. By default, you get + (FileNotFoundError, IsADirectoryError, NotADirectoryError) + alternate_root: None or str + In cases of complex URLs, the parser may fail to pick the correct part + for the mapper root, so this arg can override + + Returns + ------- + ``FSMap`` instance, the dict-like key-value store. + """ + # Removing protocol here - could defer to each open() on the backend + fs, urlpath = url_to_fs(url, **kwargs) + root = alternate_root if alternate_root is not None else urlpath + return FSMap(root, fs, check, create, missing_exceptions=missing_exceptions) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/parquet.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/parquet.py new file mode 100644 index 0000000000000000000000000000000000000000..25f1b702d36a047f15a2095c151ecf86e4fe99ab --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/parquet.py @@ -0,0 +1,572 @@ +import io +import json +import warnings + +import fsspec + +from .core import url_to_fs +from .spec import AbstractBufferedFile +from .utils import merge_offset_ranges + +# Parquet-Specific Utilities for fsspec +# +# Most of the functions defined in this module are NOT +# intended for public consumption. The only exception +# to this is `open_parquet_file`, which should be used +# place of `fs.open()` to open parquet-formatted files +# on remote file systems. + + +class AlreadyBufferedFile(AbstractBufferedFile): + def _fetch_range(self, start, end): + raise NotImplementedError + + +def open_parquet_files( + path: list[str], + fs: None | fsspec.AbstractFileSystem = None, + metadata=None, + columns: None | list[str] = None, + row_groups: None | list[int] = None, + storage_options: None | dict = None, + engine: str = "auto", + max_gap: int = 64_000, + max_block: int = 256_000_000, + footer_sample_size: int = 1_000_000, + filters: None | list[list[list[str]]] = None, + **kwargs, +): + """ + Return a file-like object for a single Parquet file. + + The specified parquet `engine` will be used to parse the + footer metadata, and determine the required byte ranges + from the file. The target path will then be opened with + the "parts" (`KnownPartsOfAFile`) caching strategy. + + Note that this method is intended for usage with remote + file systems, and is unlikely to improve parquet-read + performance on local file systems. + + Parameters + ---------- + path: str + Target file path. + metadata: Any, optional + Parquet metadata object. Object type must be supported + by the backend parquet engine. For now, only the "fastparquet" + engine supports an explicit `ParquetFile` metadata object. + If a metadata object is supplied, the remote footer metadata + will not need to be transferred into local memory. + fs: AbstractFileSystem, optional + Filesystem object to use for opening the file. If nothing is + specified, an `AbstractFileSystem` object will be inferred. + engine : str, default "auto" + Parquet engine to use for metadata parsing. Allowed options + include "fastparquet", "pyarrow", and "auto". The specified + engine must be installed in the current environment. If + "auto" is specified, and both engines are installed, + "fastparquet" will take precedence over "pyarrow". + columns: list, optional + List of all column names that may be read from the file. + row_groups : list, optional + List of all row-groups that may be read from the file. This + may be a list of row-group indices (integers), or it may be + a list of `RowGroup` metadata objects (if the "fastparquet" + engine is used). + storage_options : dict, optional + Used to generate an `AbstractFileSystem` object if `fs` was + not specified. + max_gap : int, optional + Neighboring byte ranges will only be merged when their + inter-range gap is <= `max_gap`. Default is 64KB. + max_block : int, optional + Neighboring byte ranges will only be merged when the size of + the aggregated range is <= `max_block`. Default is 256MB. + footer_sample_size : int, optional + Number of bytes to read from the end of the path to look + for the footer metadata. If the sampled bytes do not contain + the footer, a second read request will be required, and + performance will suffer. Default is 1MB. + filters : list[list], optional + List of filters to apply to prevent reading row groups, of the + same format as accepted by the loading engines. Ignored if + ``row_groups`` is specified. + **kwargs : + Optional key-word arguments to pass to `fs.open` + """ + + # Make sure we have an `AbstractFileSystem` object + # to work with + if fs is None: + path0 = path + if isinstance(path, (list, tuple)): + path = path[0] + fs, path = url_to_fs(path, **(storage_options or {})) + else: + path0 = path + + # For now, `columns == []` not supported, is the same + # as all columns + if columns is not None and len(columns) == 0: + columns = None + + # Set the engine + engine = _set_engine(engine) + + if isinstance(path0, (list, tuple)): + paths = path0 + elif "*" in path: + paths = fs.glob(path) + elif path0.endswith("/"): # or fs.isdir(path): + paths = [ + _ + for _ in fs.find(path, withdirs=False, detail=False) + if _.endswith((".parquet", ".parq")) + ] + else: + paths = [path] + + data = _get_parquet_byte_ranges( + paths, + fs, + metadata=metadata, + columns=columns, + row_groups=row_groups, + engine=engine, + max_gap=max_gap, + max_block=max_block, + footer_sample_size=footer_sample_size, + filters=filters, + ) + + # Call self.open with "parts" caching + options = kwargs.pop("cache_options", {}).copy() + return [ + AlreadyBufferedFile( + fs=None, + path=fn, + mode="rb", + cache_type="parts", + cache_options={ + **options, + "data": ranges, + }, + size=max(_[1] for _ in ranges), + **kwargs, + ) + for fn, ranges in data.items() + ] + + +def open_parquet_file(*args, **kwargs): + """Create files tailed to reading specific parts of parquet files + + Please see ``open_parquet_files`` for details of the arguments. The + difference is, this function always returns a single ``AleadyBufferedFile``, + whereas `open_parquet_files`` always returns a list of files, even if + there are one or zero matching parquet files. + """ + return open_parquet_files(*args, **kwargs)[0] + + +def _get_parquet_byte_ranges( + paths, + fs, + metadata=None, + columns=None, + row_groups=None, + max_gap=64_000, + max_block=256_000_000, + footer_sample_size=1_000_000, + engine="auto", + filters=None, +): + """Get a dictionary of the known byte ranges needed + to read a specific column/row-group selection from a + Parquet dataset. Each value in the output dictionary + is intended for use as the `data` argument for the + `KnownPartsOfAFile` caching strategy of a single path. + """ + + # Set engine if necessary + if isinstance(engine, str): + engine = _set_engine(engine) + + # Pass to a specialized function if metadata is defined + if metadata is not None: + # Use the provided parquet metadata object + # to avoid transferring/parsing footer metadata + return _get_parquet_byte_ranges_from_metadata( + metadata, + fs, + engine, + columns=columns, + row_groups=row_groups, + max_gap=max_gap, + max_block=max_block, + filters=filters, + ) + + # Populate global paths, starts, & ends + if columns is None and row_groups is None and filters is None: + # We are NOT selecting specific columns or row-groups. + # + # We can avoid sampling the footers, and just transfer + # all file data with cat_ranges + result = {path: {(0, len(data)): data} for path, data in fs.cat(paths).items()} + else: + # We ARE selecting specific columns or row-groups. + # + # Get file sizes asynchronously + file_sizes = fs.sizes(paths) + data_paths = [] + data_starts = [] + data_ends = [] + # Gather file footers. + # We just take the last `footer_sample_size` bytes of each + # file (or the entire file if it is smaller than that) + footer_starts = [ + max(0, file_size - footer_sample_size) for file_size in file_sizes + ] + footer_samples = fs.cat_ranges(paths, footer_starts, file_sizes) + + # Check our footer samples and re-sample if necessary. + large_footer = [] + for i, path in enumerate(paths): + footer_size = int.from_bytes(footer_samples[i][-8:-4], "little") + real_footer_start = file_sizes[i] - (footer_size + 8) + if real_footer_start < footer_starts[i]: + large_footer.append((i, real_footer_start)) + if large_footer: + warnings.warn( + f"Not enough data was used to sample the parquet footer. " + f"Try setting footer_sample_size >= {large_footer}." + ) + path0 = [paths[i] for i, _ in large_footer] + starts = [_[1] for _ in large_footer] + ends = [file_sizes[i] - footer_sample_size for i, _ in large_footer] + data = fs.cat_ranges(path0, starts, ends) + for i, (path, start, block) in enumerate(zip(path0, starts, data)): + footer_samples[i] = block + footer_samples[i] + footer_starts[i] = start + result = { + path: {(start, size): data} + for path, start, size, data in zip( + paths, footer_starts, file_sizes, footer_samples + ) + } + + # Calculate required byte ranges for each path + for i, path in enumerate(paths): + # Use "engine" to collect data byte ranges + path_data_starts, path_data_ends = engine._parquet_byte_ranges( + columns, + row_groups=row_groups, + footer=footer_samples[i], + footer_start=footer_starts[i], + filters=filters, + ) + + data_paths += [path] * len(path_data_starts) + data_starts += path_data_starts + data_ends += path_data_ends + + # Merge adjacent offset ranges + data_paths, data_starts, data_ends = merge_offset_ranges( + data_paths, + data_starts, + data_ends, + max_gap=max_gap, + max_block=max_block, + sort=True, + ) + + # Transfer the data byte-ranges into local memory + _transfer_ranges(fs, result, data_paths, data_starts, data_ends) + + # Add b"PAR1" to headers + _add_header_magic(result) + + return result + + +def _get_parquet_byte_ranges_from_metadata( + metadata, + fs, + engine, + columns=None, + row_groups=None, + max_gap=64_000, + max_block=256_000_000, + filters=None, +): + """Simplified version of `_get_parquet_byte_ranges` for + the case that an engine-specific `metadata` object is + provided, and the remote footer metadata does not need to + be transferred before calculating the required byte ranges. + """ + + # Use "engine" to collect data byte ranges + data_paths, data_starts, data_ends = engine._parquet_byte_ranges( + columns, row_groups=row_groups, metadata=metadata, filters=filters + ) + + # Merge adjacent offset ranges + data_paths, data_starts, data_ends = merge_offset_ranges( + data_paths, + data_starts, + data_ends, + max_gap=max_gap, + max_block=max_block, + sort=False, # Should be sorted + ) + + # Transfer the data byte-ranges into local memory + result = {fn: {} for fn in list(set(data_paths))} + _transfer_ranges(fs, result, data_paths, data_starts, data_ends) + + # Add b"PAR1" to header + _add_header_magic(result) + + return result + + +def _transfer_ranges(fs, blocks, paths, starts, ends): + # Use cat_ranges to gather the data byte_ranges + ranges = (paths, starts, ends) + for path, start, stop, data in zip(*ranges, fs.cat_ranges(*ranges)): + blocks[path][(start, stop)] = data + + +def _add_header_magic(data): + # Add b"PAR1" to file headers + for path in list(data): + add_magic = True + for k in data[path]: + if k[0] == 0 and k[1] >= 4: + add_magic = False + break + if add_magic: + data[path][(0, 4)] = b"PAR1" + + +def _set_engine(engine_str): + # Define a list of parquet engines to try + if engine_str == "auto": + try_engines = ("fastparquet", "pyarrow") + elif not isinstance(engine_str, str): + raise ValueError( + "Failed to set parquet engine! " + "Please pass 'fastparquet', 'pyarrow', or 'auto'" + ) + elif engine_str not in ("fastparquet", "pyarrow"): + raise ValueError(f"{engine_str} engine not supported by `fsspec.parquet`") + else: + try_engines = [engine_str] + + # Try importing the engines in `try_engines`, + # and choose the first one that succeeds + for engine in try_engines: + try: + if engine == "fastparquet": + return FastparquetEngine() + elif engine == "pyarrow": + return PyarrowEngine() + except ImportError: + pass + + # Raise an error if a supported parquet engine + # was not found + raise ImportError( + f"The following parquet engines are not installed " + f"in your python environment: {try_engines}." + f"Please install 'fastparquert' or 'pyarrow' to " + f"utilize the `fsspec.parquet` module." + ) + + +class FastparquetEngine: + # The purpose of the FastparquetEngine class is + # to check if fastparquet can be imported (on initialization) + # and to define a `_parquet_byte_ranges` method. In the + # future, this class may also be used to define other + # methods/logic that are specific to fastparquet. + + def __init__(self): + import fastparquet as fp + + self.fp = fp + + def _parquet_byte_ranges( + self, + columns, + row_groups=None, + metadata=None, + footer=None, + footer_start=None, + filters=None, + ): + # Initialize offset ranges and define ParqetFile metadata + pf = metadata + data_paths, data_starts, data_ends = [], [], [] + if filters and row_groups: + raise ValueError("filters and row_groups cannot be used together") + if pf is None: + pf = self.fp.ParquetFile(io.BytesIO(footer)) + + # Convert columns to a set and add any index columns + # specified in the pandas metadata (just in case) + column_set = None if columns is None else {c.split(".", 1)[0] for c in columns} + if column_set is not None and hasattr(pf, "pandas_metadata"): + md_index = [ + ind + for ind in pf.pandas_metadata.get("index_columns", []) + # Ignore RangeIndex information + if not isinstance(ind, dict) + ] + column_set |= set(md_index) + + # Check if row_groups is a list of integers + # or a list of row-group metadata + if filters: + from fastparquet.api import filter_row_groups + + row_group_indices = None + row_groups = filter_row_groups(pf, filters) + elif row_groups and not isinstance(row_groups[0], int): + # Input row_groups contains row-group metadata + row_group_indices = None + else: + # Input row_groups contains row-group indices + row_group_indices = row_groups + row_groups = pf.row_groups + if column_set is not None: + column_set = [ + _ if isinstance(_, list) else _.split(".") for _ in column_set + ] + + # Loop through column chunks to add required byte ranges + for r, row_group in enumerate(row_groups): + # Skip this row-group if we are targeting + # specific row-groups + if row_group_indices is None or r in row_group_indices: + # Find the target parquet-file path for `row_group` + fn = pf.row_group_filename(row_group) + + for column in row_group.columns: + name = column.meta_data.path_in_schema + # Skip this column if we are targeting specific columns + if column_set is None or _cmp(name, column_set): + file_offset0 = column.meta_data.dictionary_page_offset + if file_offset0 is None: + file_offset0 = column.meta_data.data_page_offset + num_bytes = column.meta_data.total_compressed_size + if footer_start is None or file_offset0 < footer_start: + data_paths.append(fn) + data_starts.append(file_offset0) + data_ends.append( + min( + file_offset0 + num_bytes, + footer_start or (file_offset0 + num_bytes), + ) + ) + + if metadata: + # The metadata in this call may map to multiple + # file paths. Need to include `data_paths` + return data_paths, data_starts, data_ends + return data_starts, data_ends + + +class PyarrowEngine: + # The purpose of the PyarrowEngine class is + # to check if pyarrow can be imported (on initialization) + # and to define a `_parquet_byte_ranges` method. In the + # future, this class may also be used to define other + # methods/logic that are specific to pyarrow. + + def __init__(self): + import pyarrow.parquet as pq + + self.pq = pq + + def _parquet_byte_ranges( + self, + columns, + row_groups=None, + metadata=None, + footer=None, + footer_start=None, + filters=None, + ): + if metadata is not None: + raise ValueError("metadata input not supported for PyarrowEngine") + if filters: + # there must be a way! + raise NotImplementedError + + data_starts, data_ends = [], [] + md = self.pq.ParquetFile(io.BytesIO(footer)).metadata + + # Convert columns to a set and add any index columns + # specified in the pandas metadata (just in case) + column_set = None if columns is None else set(columns) + if column_set is not None: + schema = md.schema.to_arrow_schema() + has_pandas_metadata = ( + schema.metadata is not None and b"pandas" in schema.metadata + ) + if has_pandas_metadata: + md_index = [ + ind + for ind in json.loads( + schema.metadata[b"pandas"].decode("utf8") + ).get("index_columns", []) + # Ignore RangeIndex information + if not isinstance(ind, dict) + ] + column_set |= set(md_index) + if column_set is not None: + column_set = [ + _[:1] if isinstance(_, list) else _.split(".")[:1] for _ in column_set + ] + + # Loop through column chunks to add required byte ranges + for r in range(md.num_row_groups): + # Skip this row-group if we are targeting + # specific row-groups + if row_groups is None or r in row_groups: + row_group = md.row_group(r) + for c in range(row_group.num_columns): + column = row_group.column(c) + name = column.path_in_schema.split(".") + # Skip this column if we are targeting specific columns + if column_set is None or _cmp(name, column_set): + meta = column.to_dict() + # Any offset could be the first one + file_offset0 = min( + _ + for _ in [ + meta.get("dictionary_page_offset"), + meta.get("data_page_offset"), + meta.get("index_page_offset"), + ] + if _ is not None + ) + if file_offset0 < footer_start: + data_starts.append(file_offset0) + data_ends.append( + min( + meta["total_compressed_size"] + file_offset0, + footer_start, + ) + ) + + data_starts.append(footer_start) + data_ends.append(footer_start + len(footer)) + return data_starts, data_ends + + +def _cmp(name, column_set): + return any(all(a == b for a, b in zip(name, _)) for _ in column_set) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/registry.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/registry.py new file mode 100644 index 0000000000000000000000000000000000000000..597c6ba57a7cb70c65fd6daeb97ce7ab703a5121 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/registry.py @@ -0,0 +1,333 @@ +from __future__ import annotations + +import importlib +import types +import warnings + +__all__ = ["registry", "get_filesystem_class", "default"] + +# internal, mutable +_registry: dict[str, type] = {} + +# external, immutable +registry = types.MappingProxyType(_registry) +default = "file" + + +def register_implementation(name, cls, clobber=False, errtxt=None): + """Add implementation class to the registry + + Parameters + ---------- + name: str + Protocol name to associate with the class + cls: class or str + if a class: fsspec-compliant implementation class (normally inherits from + ``fsspec.AbstractFileSystem``, gets added straight to the registry. If a + str, the full path to an implementation class like package.module.class, + which gets added to known_implementations, + so the import is deferred until the filesystem is actually used. + clobber: bool (optional) + Whether to overwrite a protocol with the same name; if False, will raise + instead. + errtxt: str (optional) + If given, then a failure to import the given class will result in this + text being given. + """ + if isinstance(cls, str): + if name in known_implementations and clobber is False: + if cls != known_implementations[name]["class"]: + raise ValueError( + f"Name ({name}) already in the known_implementations and clobber " + f"is False" + ) + else: + known_implementations[name] = { + "class": cls, + "err": errtxt or f"{cls} import failed for protocol {name}", + } + + else: + if name in registry and clobber is False: + if _registry[name] is not cls: + raise ValueError( + f"Name ({name}) already in the registry and clobber is False" + ) + else: + _registry[name] = cls + + +# protocols mapped to the class which implements them. This dict can be +# updated with register_implementation +known_implementations = { + "abfs": { + "class": "adlfs.AzureBlobFileSystem", + "err": "Install adlfs to access Azure Datalake Gen2 and Azure Blob Storage", + }, + "adl": { + "class": "adlfs.AzureDatalakeFileSystem", + "err": "Install adlfs to access Azure Datalake Gen1", + }, + "arrow_hdfs": { + "class": "fsspec.implementations.arrow.HadoopFileSystem", + "err": "pyarrow and local java libraries required for HDFS", + }, + "async_wrapper": { + "class": "fsspec.implementations.asyn_wrapper.AsyncFileSystemWrapper", + }, + "asynclocal": { + "class": "morefs.asyn_local.AsyncLocalFileSystem", + "err": "Install 'morefs[asynclocalfs]' to use AsyncLocalFileSystem", + }, + "asyncwrapper": { + "class": "fsspec.implementations.asyn_wrapper.AsyncFileSystemWrapper", + }, + "az": { + "class": "adlfs.AzureBlobFileSystem", + "err": "Install adlfs to access Azure Datalake Gen2 and Azure Blob Storage", + }, + "blockcache": {"class": "fsspec.implementations.cached.CachingFileSystem"}, + "box": { + "class": "boxfs.BoxFileSystem", + "err": "Please install boxfs to access BoxFileSystem", + }, + "cached": {"class": "fsspec.implementations.cached.CachingFileSystem"}, + "dask": { + "class": "fsspec.implementations.dask.DaskWorkerFileSystem", + "err": "Install dask distributed to access worker file system", + }, + "data": {"class": "fsspec.implementations.data.DataFileSystem"}, + "dbfs": { + "class": "fsspec.implementations.dbfs.DatabricksFileSystem", + "err": "Install the requests package to use the DatabricksFileSystem", + }, + "dir": {"class": "fsspec.implementations.dirfs.DirFileSystem"}, + "dropbox": { + "class": "dropboxdrivefs.DropboxDriveFileSystem", + "err": ( + 'DropboxFileSystem requires "dropboxdrivefs","requests" and "' + '"dropbox" to be installed' + ), + }, + "dvc": { + "class": "dvc.api.DVCFileSystem", + "err": "Install dvc to access DVCFileSystem", + }, + "file": {"class": "fsspec.implementations.local.LocalFileSystem"}, + "filecache": {"class": "fsspec.implementations.cached.WholeFileCacheFileSystem"}, + "ftp": {"class": "fsspec.implementations.ftp.FTPFileSystem"}, + "gcs": { + "class": "gcsfs.GCSFileSystem", + "err": "Please install gcsfs to access Google Storage", + }, + "gdrive": { + "class": "gdrive_fsspec.GoogleDriveFileSystem", + "err": "Please install gdrive_fs for access to Google Drive", + }, + "generic": {"class": "fsspec.generic.GenericFileSystem"}, + "gist": { + "class": "fsspec.implementations.gist.GistFileSystem", + "err": "Install the requests package to use the gist FS", + }, + "git": { + "class": "fsspec.implementations.git.GitFileSystem", + "err": "Install pygit2 to browse local git repos", + }, + "github": { + "class": "fsspec.implementations.github.GithubFileSystem", + "err": "Install the requests package to use the github FS", + }, + "gs": { + "class": "gcsfs.GCSFileSystem", + "err": "Please install gcsfs to access Google Storage", + }, + "hdfs": { + "class": "fsspec.implementations.arrow.HadoopFileSystem", + "err": "pyarrow and local java libraries required for HDFS", + }, + "hf": { + "class": "huggingface_hub.HfFileSystem", + "err": "Install huggingface_hub to access HfFileSystem", + }, + "http": { + "class": "fsspec.implementations.http.HTTPFileSystem", + "err": 'HTTPFileSystem requires "requests" and "aiohttp" to be installed', + }, + "https": { + "class": "fsspec.implementations.http.HTTPFileSystem", + "err": 'HTTPFileSystem requires "requests" and "aiohttp" to be installed', + }, + "jlab": { + "class": "fsspec.implementations.jupyter.JupyterFileSystem", + "err": "Jupyter FS requires requests to be installed", + }, + "jupyter": { + "class": "fsspec.implementations.jupyter.JupyterFileSystem", + "err": "Jupyter FS requires requests to be installed", + }, + "lakefs": { + "class": "lakefs_spec.LakeFSFileSystem", + "err": "Please install lakefs-spec to access LakeFSFileSystem", + }, + "libarchive": { + "class": "fsspec.implementations.libarchive.LibArchiveFileSystem", + "err": "LibArchive requires to be installed", + }, + "local": {"class": "fsspec.implementations.local.LocalFileSystem"}, + "memory": {"class": "fsspec.implementations.memory.MemoryFileSystem"}, + "oci": { + "class": "ocifs.OCIFileSystem", + "err": "Install ocifs to access OCI Object Storage", + }, + "ocilake": { + "class": "ocifs.OCIFileSystem", + "err": "Install ocifs to access OCI Data Lake", + }, + "oss": { + "class": "ossfs.OSSFileSystem", + "err": "Install ossfs to access Alibaba Object Storage System", + }, + "pyscript": { + "class": "pyscript_fsspec_client.client.PyscriptFileSystem", + "err": "This only runs in a pyscript context", + }, + "reference": {"class": "fsspec.implementations.reference.ReferenceFileSystem"}, + "root": { + "class": "fsspec_xrootd.XRootDFileSystem", + "err": ( + "Install fsspec-xrootd to access xrootd storage system. " + "Note: 'root' is the protocol name for xrootd storage systems, " + "not referring to root directories" + ), + }, + "s3": {"class": "s3fs.S3FileSystem", "err": "Install s3fs to access S3"}, + "s3a": {"class": "s3fs.S3FileSystem", "err": "Install s3fs to access S3"}, + "sftp": { + "class": "fsspec.implementations.sftp.SFTPFileSystem", + "err": 'SFTPFileSystem requires "paramiko" to be installed', + }, + "simplecache": {"class": "fsspec.implementations.cached.SimpleCacheFileSystem"}, + "smb": { + "class": "fsspec.implementations.smb.SMBFileSystem", + "err": 'SMB requires "smbprotocol" or "smbprotocol[kerberos]" installed', + }, + "ssh": { + "class": "fsspec.implementations.sftp.SFTPFileSystem", + "err": 'SFTPFileSystem requires "paramiko" to be installed', + }, + "tar": {"class": "fsspec.implementations.tar.TarFileSystem"}, + "tos": { + "class": "tosfs.TosFileSystem", + "err": "Install tosfs to access ByteDance volcano engine Tinder Object Storage", + }, + "tosfs": { + "class": "tosfs.TosFileSystem", + "err": "Install tosfs to access ByteDance volcano engine Tinder Object Storage", + }, + "wandb": {"class": "wandbfs.WandbFS", "err": "Install wandbfs to access wandb"}, + "webdav": { + "class": "webdav4.fsspec.WebdavFileSystem", + "err": "Install webdav4 to access WebDAV", + }, + "webhdfs": { + "class": "fsspec.implementations.webhdfs.WebHDFS", + "err": 'webHDFS access requires "requests" to be installed', + }, + "zip": {"class": "fsspec.implementations.zip.ZipFileSystem"}, +} + +assert list(known_implementations) == sorted(known_implementations), ( + "Not in alphabetical order" +) + + +def get_filesystem_class(protocol): + """Fetch named protocol implementation from the registry + + The dict ``known_implementations`` maps protocol names to the locations + of classes implementing the corresponding file-system. When used for the + first time, appropriate imports will happen and the class will be placed in + the registry. All subsequent calls will fetch directly from the registry. + + Some protocol implementations require additional dependencies, and so the + import may fail. In this case, the string in the "err" field of the + ``known_implementations`` will be given as the error message. + """ + if not protocol: + protocol = default + + if protocol not in registry: + if protocol not in known_implementations: + raise ValueError(f"Protocol not known: {protocol}") + bit = known_implementations[protocol] + try: + register_implementation(protocol, _import_class(bit["class"])) + except ImportError as e: + raise ImportError(bit.get("err")) from e + cls = registry[protocol] + if getattr(cls, "protocol", None) in ("abstract", None): + cls.protocol = protocol + + return cls + + +s3_msg = """Your installed version of s3fs is very old and known to cause +severe performance issues, see also https://github.com/dask/dask/issues/10276 + +To fix, you should specify a lower version bound on s3fs, or +update the current installation. +""" + + +def _import_class(fqp: str): + """Take a fully-qualified path and return the imported class or identifier. + + ``fqp`` is of the form "package.module.klass" or + "package.module:subobject.klass". + + Warnings + -------- + This can import arbitrary modules. Make sure you haven't installed any modules + that may execute malicious code at import time. + """ + if ":" in fqp: + mod, name = fqp.rsplit(":", 1) + else: + mod, name = fqp.rsplit(".", 1) + + is_s3 = mod == "s3fs" + mod = importlib.import_module(mod) + if is_s3 and mod.__version__.split(".") < ["0", "5"]: + warnings.warn(s3_msg) + for part in name.split("."): + mod = getattr(mod, part) + + if not isinstance(mod, type): + raise TypeError(f"{fqp} is not a class") + + return mod + + +def filesystem(protocol, **storage_options): + """Instantiate filesystems for given protocol and arguments + + ``storage_options`` are specific to the protocol being chosen, and are + passed directly to the class. + """ + if protocol == "arrow_hdfs": + warnings.warn( + "The 'arrow_hdfs' protocol has been deprecated and will be " + "removed in the future. Specify it as 'hdfs'.", + DeprecationWarning, + ) + + cls = get_filesystem_class(protocol) + return cls(**storage_options) + + +def available_protocols(): + """Return a list of the implemented protocols. + + Note that any given protocol may require extra packages to be importable. + """ + return list(known_implementations) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/spec.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/spec.py new file mode 100644 index 0000000000000000000000000000000000000000..b67d5c16fcdf09ce6f9e1354727196042cde3c4c --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/spec.py @@ -0,0 +1,2281 @@ +from __future__ import annotations + +import io +import json +import logging +import os +import threading +import warnings +import weakref +from errno import ESPIPE +from glob import has_magic +from hashlib import sha256 +from typing import Any, ClassVar + +from .callbacks import DEFAULT_CALLBACK +from .config import apply_config, conf +from .dircache import DirCache +from .transaction import Transaction +from .utils import ( + _unstrip_protocol, + glob_translate, + isfilelike, + other_paths, + read_block, + stringify_path, + tokenize, +) + +logger = logging.getLogger("fsspec") + + +def make_instance(cls, args, kwargs): + return cls(*args, **kwargs) + + +class _Cached(type): + """ + Metaclass for caching file system instances. + + Notes + ----- + Instances are cached according to + + * The values of the class attributes listed in `_extra_tokenize_attributes` + * The arguments passed to ``__init__``. + + This creates an additional reference to the filesystem, which prevents the + filesystem from being garbage collected when all *user* references go away. + A call to the :meth:`AbstractFileSystem.clear_instance_cache` must *also* + be made for a filesystem instance to be garbage collected. + """ + + def __init__(cls, *args, **kwargs): + super().__init__(*args, **kwargs) + # Note: we intentionally create a reference here, to avoid garbage + # collecting instances when all other references are gone. To really + # delete a FileSystem, the cache must be cleared. + if conf.get("weakref_instance_cache"): # pragma: no cover + # debug option for analysing fork/spawn conditions + cls._cache = weakref.WeakValueDictionary() + else: + cls._cache = {} + cls._pid = os.getpid() + + def __call__(cls, *args, **kwargs): + kwargs = apply_config(cls, kwargs) + extra_tokens = tuple( + getattr(cls, attr, None) for attr in cls._extra_tokenize_attributes + ) + strip_tokenize_options = { + k: kwargs.pop(k) for k in cls._strip_tokenize_options if k in kwargs + } + token = tokenize( + cls, cls._pid, threading.get_ident(), *args, *extra_tokens, **kwargs + ) + skip = kwargs.pop("skip_instance_cache", False) + if os.getpid() != cls._pid: + cls._cache.clear() + cls._pid = os.getpid() + if not skip and cls.cachable and token in cls._cache: + cls._latest = token + return cls._cache[token] + else: + obj = super().__call__(*args, **kwargs, **strip_tokenize_options) + # Setting _fs_token here causes some static linters to complain. + obj._fs_token_ = token + obj.storage_args = args + obj.storage_options = kwargs + if obj.async_impl and obj.mirror_sync_methods: + from .asyn import mirror_sync_methods + + mirror_sync_methods(obj) + + if cls.cachable and not skip: + cls._latest = token + cls._cache[token] = obj + return obj + + +class AbstractFileSystem(metaclass=_Cached): + """ + An abstract super-class for pythonic file-systems + + Implementations are expected to be compatible with or, better, subclass + from here. + """ + + cachable = True # this class can be cached, instances reused + _cached = False + blocksize = 2**22 + sep = "/" + protocol: ClassVar[str | tuple[str, ...]] = "abstract" + _latest = None + async_impl = False + mirror_sync_methods = False + root_marker = "" # For some FSs, may require leading '/' or other character + transaction_type = Transaction + + #: Extra *class attributes* that should be considered when hashing. + _extra_tokenize_attributes = () + #: *storage options* that should not be considered when hashing. + _strip_tokenize_options = () + + # Set by _Cached metaclass + storage_args: tuple[Any, ...] + storage_options: dict[str, Any] + + def __init__(self, *args, **storage_options): + """Create and configure file-system instance + + Instances may be cachable, so if similar enough arguments are seen + a new instance is not required. The token attribute exists to allow + implementations to cache instances if they wish. + + A reasonable default should be provided if there are no arguments. + + Subclasses should call this method. + + Parameters + ---------- + use_listings_cache, listings_expiry_time, max_paths: + passed to ``DirCache``, if the implementation supports + directory listing caching. Pass use_listings_cache=False + to disable such caching. + skip_instance_cache: bool + If this is a cachable implementation, pass True here to force + creating a new instance even if a matching instance exists, and prevent + storing this instance. + asynchronous: bool + loop: asyncio-compatible IOLoop or None + """ + if self._cached: + # reusing instance, don't change + return + self._cached = True + self._intrans = False + self._transaction = None + self._invalidated_caches_in_transaction = [] + self.dircache = DirCache(**storage_options) + + if storage_options.pop("add_docs", None): + warnings.warn("add_docs is no longer supported.", FutureWarning) + + if storage_options.pop("add_aliases", None): + warnings.warn("add_aliases has been removed.", FutureWarning) + # This is set in _Cached + self._fs_token_ = None + + @property + def fsid(self): + """Persistent filesystem id that can be used to compare filesystems + across sessions. + """ + raise NotImplementedError + + @property + def _fs_token(self): + return self._fs_token_ + + def __dask_tokenize__(self): + return self._fs_token + + def __hash__(self): + return int(self._fs_token, 16) + + def __eq__(self, other): + return isinstance(other, type(self)) and self._fs_token == other._fs_token + + def __reduce__(self): + return make_instance, (type(self), self.storage_args, self.storage_options) + + @classmethod + def _strip_protocol(cls, path): + """Turn path from fully-qualified to file-system-specific + + May require FS-specific handling, e.g., for relative paths or links. + """ + if isinstance(path, list): + return [cls._strip_protocol(p) for p in path] + path = stringify_path(path) + protos = (cls.protocol,) if isinstance(cls.protocol, str) else cls.protocol + for protocol in protos: + if path.startswith(protocol + "://"): + path = path[len(protocol) + 3 :] + elif path.startswith(protocol + "::"): + path = path[len(protocol) + 2 :] + path = path.rstrip("/") + # use of root_marker to make minimum required path, e.g., "/" + return path or cls.root_marker + + def unstrip_protocol(self, name: str) -> str: + """Format FS-specific path to generic, including protocol""" + protos = (self.protocol,) if isinstance(self.protocol, str) else self.protocol + for protocol in protos: + if name.startswith(f"{protocol}://"): + return name + return f"{protos[0]}://{name}" + + @staticmethod + def _get_kwargs_from_urls(path): + """If kwargs can be encoded in the paths, extract them here + + This should happen before instantiation of the class; incoming paths + then should be amended to strip the options in methods. + + Examples may look like an sftp path "sftp://user@host:/my/path", where + the user and host should become kwargs and later get stripped. + """ + # by default, nothing happens + return {} + + @classmethod + def current(cls): + """Return the most recently instantiated FileSystem + + If no instance has been created, then create one with defaults + """ + if cls._latest in cls._cache: + return cls._cache[cls._latest] + return cls() + + @property + def transaction(self): + """A context within which files are committed together upon exit + + Requires the file class to implement `.commit()` and `.discard()` + for the normal and exception cases. + """ + if self._transaction is None: + self._transaction = self.transaction_type(self) + return self._transaction + + def start_transaction(self): + """Begin write transaction for deferring files, non-context version""" + self._intrans = True + self._transaction = self.transaction_type(self) + return self.transaction + + def end_transaction(self): + """Finish write transaction, non-context version""" + self.transaction.complete() + self._transaction = None + # The invalid cache must be cleared after the transaction is completed. + for path in self._invalidated_caches_in_transaction: + self.invalidate_cache(path) + self._invalidated_caches_in_transaction.clear() + + def invalidate_cache(self, path=None): + """ + Discard any cached directory information + + Parameters + ---------- + path: string or None + If None, clear all listings cached else listings at or under given + path. + """ + # Not necessary to implement invalidation mechanism, may have no cache. + # But if have, you should call this method of parent class from your + # subclass to ensure expiring caches after transacations correctly. + # See the implementation of FTPFileSystem in ftp.py + if self._intrans: + self._invalidated_caches_in_transaction.append(path) + + def mkdir(self, path, create_parents=True, **kwargs): + """ + Create directory entry at path + + For systems that don't have true directories, may create an for + this instance only and not touch the real filesystem + + Parameters + ---------- + path: str + location + create_parents: bool + if True, this is equivalent to ``makedirs`` + kwargs: + may be permissions, etc. + """ + pass # not necessary to implement, may not have directories + + def makedirs(self, path, exist_ok=False): + """Recursively make directories + + Creates directory at path and any intervening required directories. + Raises exception if, for instance, the path already exists but is a + file. + + Parameters + ---------- + path: str + leaf directory name + exist_ok: bool (False) + If False, will error if the target already exists + """ + pass # not necessary to implement, may not have directories + + def rmdir(self, path): + """Remove a directory, if empty""" + pass # not necessary to implement, may not have directories + + def ls(self, path, detail=True, **kwargs): + """List objects at path. + + This should include subdirectories and files at that location. The + difference between a file and a directory must be clear when details + are requested. + + The specific keys, or perhaps a FileInfo class, or similar, is TBD, + but must be consistent across implementations. + Must include: + + - full path to the entry (without protocol) + - size of the entry, in bytes. If the value cannot be determined, will + be ``None``. + - type of entry, "file", "directory" or other + + Additional information + may be present, appropriate to the file-system, e.g., generation, + checksum, etc. + + May use refresh=True|False to allow use of self._ls_from_cache to + check for a saved listing and avoid calling the backend. This would be + common where listing may be expensive. + + Parameters + ---------- + path: str + detail: bool + if True, gives a list of dictionaries, where each is the same as + the result of ``info(path)``. If False, gives a list of paths + (str). + kwargs: may have additional backend-specific options, such as version + information + + Returns + ------- + List of strings if detail is False, or list of directory information + dicts if detail is True. + """ + raise NotImplementedError + + def _ls_from_cache(self, path): + """Check cache for listing + + Returns listing, if found (may be empty list for a directly that exists + but contains nothing), None if not in cache. + """ + parent = self._parent(path) + try: + return self.dircache[path.rstrip("/")] + except KeyError: + pass + try: + files = [ + f + for f in self.dircache[parent] + if f["name"] == path + or (f["name"] == path.rstrip("/") and f["type"] == "directory") + ] + if len(files) == 0: + # parent dir was listed but did not contain this file + raise FileNotFoundError(path) + return files + except KeyError: + pass + + def walk(self, path, maxdepth=None, topdown=True, on_error="omit", **kwargs): + """Return all files under the given path. + + List all files, recursing into subdirectories; output is iterator-style, + like ``os.walk()``. For a simple list of files, ``find()`` is available. + + When topdown is True, the caller can modify the dirnames list in-place (perhaps + using del or slice assignment), and walk() will + only recurse into the subdirectories whose names remain in dirnames; + this can be used to prune the search, impose a specific order of visiting, + or even to inform walk() about directories the caller creates or renames before + it resumes walk() again. + Modifying dirnames when topdown is False has no effect. (see os.walk) + + Note that the "files" outputted will include anything that is not + a directory, such as links. + + Parameters + ---------- + path: str + Root to recurse into + maxdepth: int + Maximum recursion depth. None means limitless, but not recommended + on link-based file-systems. + topdown: bool (True) + Whether to walk the directory tree from the top downwards or from + the bottom upwards. + on_error: "omit", "raise", a callable + if omit (default), path with exception will simply be empty; + If raise, an underlying exception will be raised; + if callable, it will be called with a single OSError instance as argument + kwargs: passed to ``ls`` + """ + if maxdepth is not None and maxdepth < 1: + raise ValueError("maxdepth must be at least 1") + + path = self._strip_protocol(path) + full_dirs = {} + dirs = {} + files = {} + + detail = kwargs.pop("detail", False) + try: + listing = self.ls(path, detail=True, **kwargs) + except (FileNotFoundError, OSError) as e: + if on_error == "raise": + raise + if callable(on_error): + on_error(e) + return + + for info in listing: + # each info name must be at least [path]/part , but here + # we check also for names like [path]/part/ + pathname = info["name"].rstrip("/") + name = pathname.rsplit("/", 1)[-1] + if info["type"] == "directory" and pathname != path: + # do not include "self" path + full_dirs[name] = pathname + dirs[name] = info + elif pathname == path: + # file-like with same name as give path + files[""] = info + else: + files[name] = info + + if not detail: + dirs = list(dirs) + files = list(files) + + if topdown: + # Yield before recursion if walking top down + yield path, dirs, files + + if maxdepth is not None: + maxdepth -= 1 + if maxdepth < 1: + if not topdown: + yield path, dirs, files + return + + for d in dirs: + yield from self.walk( + full_dirs[d], + maxdepth=maxdepth, + detail=detail, + topdown=topdown, + **kwargs, + ) + + if not topdown: + # Yield after recursion if walking bottom up + yield path, dirs, files + + def find(self, path, maxdepth=None, withdirs=False, detail=False, **kwargs): + """List all files below path. + + Like posix ``find`` command without conditions + + Parameters + ---------- + path : str + maxdepth: int or None + If not None, the maximum number of levels to descend + withdirs: bool + Whether to include directory paths in the output. This is True + when used by glob, but users usually only want files. + kwargs are passed to ``ls``. + """ + # TODO: allow equivalent of -name parameter + path = self._strip_protocol(path) + out = {} + + # Add the root directory if withdirs is requested + # This is needed for posix glob compliance + if withdirs and path != "" and self.isdir(path): + out[path] = self.info(path) + + for _, dirs, files in self.walk(path, maxdepth, detail=True, **kwargs): + if withdirs: + files.update(dirs) + out.update({info["name"]: info for name, info in files.items()}) + if not out and self.isfile(path): + # walk works on directories, but find should also return [path] + # when path happens to be a file + out[path] = {} + names = sorted(out) + if not detail: + return names + else: + return {name: out[name] for name in names} + + def du(self, path, total=True, maxdepth=None, withdirs=False, **kwargs): + """Space used by files and optionally directories within a path + + Directory size does not include the size of its contents. + + Parameters + ---------- + path: str + total: bool + Whether to sum all the file sizes + maxdepth: int or None + Maximum number of directory levels to descend, None for unlimited. + withdirs: bool + Whether to include directory paths in the output. + kwargs: passed to ``find`` + + Returns + ------- + Dict of {path: size} if total=False, or int otherwise, where numbers + refer to bytes used. + """ + sizes = {} + if withdirs and self.isdir(path): + # Include top-level directory in output + info = self.info(path) + sizes[info["name"]] = info["size"] + for f in self.find(path, maxdepth=maxdepth, withdirs=withdirs, **kwargs): + info = self.info(f) + sizes[info["name"]] = info["size"] + if total: + return sum(sizes.values()) + else: + return sizes + + def glob(self, path, maxdepth=None, **kwargs): + """Find files by glob-matching. + + Pattern matching capabilities for finding files that match the given pattern. + + Parameters + ---------- + path: str + The glob pattern to match against + maxdepth: int or None + Maximum depth for ``'**'`` patterns. Applied on the first ``'**'`` found. + Must be at least 1 if provided. + kwargs: + Additional arguments passed to ``find`` (e.g., detail=True) + + Returns + ------- + List of matched paths, or dict of paths and their info if detail=True + + Notes + ----- + Supported patterns: + - '*': Matches any sequence of characters within a single directory level + - ``'**'``: Matches any number of directory levels (must be an entire path component) + - '?': Matches exactly one character + - '[abc]': Matches any character in the set + - '[a-z]': Matches any character in the range + - '[!abc]': Matches any character NOT in the set + + Special behaviors: + - If the path ends with '/', only folders are returned + - Consecutive '*' characters are compressed into a single '*' + - Empty brackets '[]' never match anything + - Negated empty brackets '[!]' match any single character + - Special characters in character classes are escaped properly + + Limitations: + - ``'**'`` must be a complete path component (e.g., ``'a/**/b'``, not ``'a**b'``) + - No brace expansion ('{a,b}.txt') + - No extended glob patterns ('+(pattern)', '!(pattern)') + """ + if maxdepth is not None and maxdepth < 1: + raise ValueError("maxdepth must be at least 1") + + import re + + seps = (os.path.sep, os.path.altsep) if os.path.altsep else (os.path.sep,) + ends_with_sep = path.endswith(seps) # _strip_protocol strips trailing slash + path = self._strip_protocol(path) + append_slash_to_dirname = ends_with_sep or path.endswith( + tuple(sep + "**" for sep in seps) + ) + idx_star = path.find("*") if path.find("*") >= 0 else len(path) + idx_qmark = path.find("?") if path.find("?") >= 0 else len(path) + idx_brace = path.find("[") if path.find("[") >= 0 else len(path) + + min_idx = min(idx_star, idx_qmark, idx_brace) + + detail = kwargs.pop("detail", False) + + if not has_magic(path): + if self.exists(path, **kwargs): + if not detail: + return [path] + else: + return {path: self.info(path, **kwargs)} + else: + if not detail: + return [] # glob of non-existent returns empty + else: + return {} + elif "/" in path[:min_idx]: + min_idx = path[:min_idx].rindex("/") + root = path[: min_idx + 1] + depth = path[min_idx + 1 :].count("/") + 1 + else: + root = "" + depth = path[min_idx + 1 :].count("/") + 1 + + if "**" in path: + if maxdepth is not None: + idx_double_stars = path.find("**") + depth_double_stars = path[idx_double_stars:].count("/") + 1 + depth = depth - depth_double_stars + maxdepth + else: + depth = None + + allpaths = self.find(root, maxdepth=depth, withdirs=True, detail=True, **kwargs) + + pattern = glob_translate(path + ("/" if ends_with_sep else "")) + pattern = re.compile(pattern) + + out = { + p: info + for p, info in sorted(allpaths.items()) + if pattern.match( + p + "/" + if append_slash_to_dirname and info["type"] == "directory" + else p + ) + } + + if detail: + return out + else: + return list(out) + + def exists(self, path, **kwargs): + """Is there a file at the given path""" + try: + self.info(path, **kwargs) + return True + except: # noqa: E722 + # any exception allowed bar FileNotFoundError? + return False + + def lexists(self, path, **kwargs): + """If there is a file at the given path (including + broken links)""" + return self.exists(path) + + def info(self, path, **kwargs): + """Give details of entry at path + + Returns a single dictionary, with exactly the same information as ``ls`` + would with ``detail=True``. + + The default implementation calls ls and could be overridden by a + shortcut. kwargs are passed on to ```ls()``. + + Some file systems might not be able to measure the file's size, in + which case, the returned dict will include ``'size': None``. + + Returns + ------- + dict with keys: name (full path in the FS), size (in bytes), type (file, + directory, or something else) and other FS-specific keys. + """ + path = self._strip_protocol(path) + out = self.ls(self._parent(path), detail=True, **kwargs) + out = [o for o in out if o["name"].rstrip("/") == path] + if out: + return out[0] + out = self.ls(path, detail=True, **kwargs) + path = path.rstrip("/") + out1 = [o for o in out if o["name"].rstrip("/") == path] + if len(out1) == 1: + if "size" not in out1[0]: + out1[0]["size"] = None + return out1[0] + elif len(out1) > 1 or out: + return {"name": path, "size": 0, "type": "directory"} + else: + raise FileNotFoundError(path) + + def checksum(self, path): + """Unique value for current version of file + + If the checksum is the same from one moment to another, the contents + are guaranteed to be the same. If the checksum changes, the contents + *might* have changed. + + This should normally be overridden; default will probably capture + creation/modification timestamp (which would be good) or maybe + access timestamp (which would be bad) + """ + return int(tokenize(self.info(path)), 16) + + def size(self, path): + """Size in bytes of file""" + return self.info(path).get("size", None) + + def sizes(self, paths): + """Size in bytes of each file in a list of paths""" + return [self.size(p) for p in paths] + + def isdir(self, path): + """Is this entry directory-like?""" + try: + return self.info(path)["type"] == "directory" + except OSError: + return False + + def isfile(self, path): + """Is this entry file-like?""" + try: + return self.info(path)["type"] == "file" + except: # noqa: E722 + return False + + def read_text(self, path, encoding=None, errors=None, newline=None, **kwargs): + """Get the contents of the file as a string. + + Parameters + ---------- + path: str + URL of file on this filesystems + encoding, errors, newline: same as `open`. + """ + with self.open( + path, + mode="r", + encoding=encoding, + errors=errors, + newline=newline, + **kwargs, + ) as f: + return f.read() + + def write_text( + self, path, value, encoding=None, errors=None, newline=None, **kwargs + ): + """Write the text to the given file. + + An existing file will be overwritten. + + Parameters + ---------- + path: str + URL of file on this filesystems + value: str + Text to write. + encoding, errors, newline: same as `open`. + """ + with self.open( + path, + mode="w", + encoding=encoding, + errors=errors, + newline=newline, + **kwargs, + ) as f: + return f.write(value) + + def cat_file(self, path, start=None, end=None, **kwargs): + """Get the content of a file + + Parameters + ---------- + path: URL of file on this filesystems + start, end: int + Bytes limits of the read. If negative, backwards from end, + like usual python slices. Either can be None for start or + end of file, respectively + kwargs: passed to ``open()``. + """ + # explicitly set buffering off? + with self.open(path, "rb", **kwargs) as f: + if start is not None: + if start >= 0: + f.seek(start) + else: + f.seek(max(0, f.size + start)) + if end is not None: + if end < 0: + end = f.size + end + return f.read(end - f.tell()) + return f.read() + + def pipe_file(self, path, value, mode="overwrite", **kwargs): + """Set the bytes of given file""" + if mode == "create" and self.exists(path): + # non-atomic but simple way; or could use "xb" in open(), which is likely + # not as well supported + raise FileExistsError + with self.open(path, "wb", **kwargs) as f: + f.write(value) + + def pipe(self, path, value=None, **kwargs): + """Put value into path + + (counterpart to ``cat``) + + Parameters + ---------- + path: string or dict(str, bytes) + If a string, a single remote location to put ``value`` bytes; if a dict, + a mapping of {path: bytesvalue}. + value: bytes, optional + If using a single path, these are the bytes to put there. Ignored if + ``path`` is a dict + """ + if isinstance(path, str): + self.pipe_file(self._strip_protocol(path), value, **kwargs) + elif isinstance(path, dict): + for k, v in path.items(): + self.pipe_file(self._strip_protocol(k), v, **kwargs) + else: + raise ValueError("path must be str or dict") + + def cat_ranges( + self, paths, starts, ends, max_gap=None, on_error="return", **kwargs + ): + """Get the contents of byte ranges from one or more files + + Parameters + ---------- + paths: list + A list of of filepaths on this filesystems + starts, ends: int or list + Bytes limits of the read. If using a single int, the same value will be + used to read all the specified files. + """ + if max_gap is not None: + raise NotImplementedError + if not isinstance(paths, list): + raise TypeError + if not isinstance(starts, list): + starts = [starts] * len(paths) + if not isinstance(ends, list): + ends = [ends] * len(paths) + if len(starts) != len(paths) or len(ends) != len(paths): + raise ValueError + out = [] + for p, s, e in zip(paths, starts, ends): + try: + out.append(self.cat_file(p, s, e)) + except Exception as e: + if on_error == "return": + out.append(e) + else: + raise + return out + + def cat(self, path, recursive=False, on_error="raise", **kwargs): + """Fetch (potentially multiple) paths' contents + + Parameters + ---------- + recursive: bool + If True, assume the path(s) are directories, and get all the + contained files + on_error : "raise", "omit", "return" + If raise, an underlying exception will be raised (converted to KeyError + if the type is in self.missing_exceptions); if omit, keys with exception + will simply not be included in the output; if "return", all keys are + included in the output, but the value will be bytes or an exception + instance. + kwargs: passed to cat_file + + Returns + ------- + dict of {path: contents} if there are multiple paths + or the path has been otherwise expanded + """ + paths = self.expand_path(path, recursive=recursive, **kwargs) + if ( + len(paths) > 1 + or isinstance(path, list) + or paths[0] != self._strip_protocol(path) + ): + out = {} + for path in paths: + try: + out[path] = self.cat_file(path, **kwargs) + except Exception as e: + if on_error == "raise": + raise + if on_error == "return": + out[path] = e + return out + else: + return self.cat_file(paths[0], **kwargs) + + def get_file(self, rpath, lpath, callback=DEFAULT_CALLBACK, outfile=None, **kwargs): + """Copy single remote file to local""" + from .implementations.local import LocalFileSystem + + if isfilelike(lpath): + outfile = lpath + elif self.isdir(rpath): + os.makedirs(lpath, exist_ok=True) + return None + + fs = LocalFileSystem(auto_mkdir=True) + fs.makedirs(fs._parent(lpath), exist_ok=True) + + with self.open(rpath, "rb", **kwargs) as f1: + if outfile is None: + outfile = open(lpath, "wb") + + try: + callback.set_size(getattr(f1, "size", None)) + data = True + while data: + data = f1.read(self.blocksize) + segment_len = outfile.write(data) + if segment_len is None: + segment_len = len(data) + callback.relative_update(segment_len) + finally: + if not isfilelike(lpath): + outfile.close() + + def get( + self, + rpath, + lpath, + recursive=False, + callback=DEFAULT_CALLBACK, + maxdepth=None, + **kwargs, + ): + """Copy file(s) to local. + + Copies a specific file or tree of files (if recursive=True). If lpath + ends with a "/", it will be assumed to be a directory, and target files + will go within. Can submit a list of paths, which may be glob-patterns + and will be expanded. + + Calls get_file for each source. + """ + if isinstance(lpath, list) and isinstance(rpath, list): + # No need to expand paths when both source and destination + # are provided as lists + rpaths = rpath + lpaths = lpath + else: + from .implementations.local import ( + LocalFileSystem, + make_path_posix, + trailing_sep, + ) + + source_is_str = isinstance(rpath, str) + rpaths = self.expand_path( + rpath, recursive=recursive, maxdepth=maxdepth, **kwargs + ) + if source_is_str and (not recursive or maxdepth is not None): + # Non-recursive glob does not copy directories + rpaths = [p for p in rpaths if not (trailing_sep(p) or self.isdir(p))] + if not rpaths: + return + + if isinstance(lpath, str): + lpath = make_path_posix(lpath) + + source_is_file = len(rpaths) == 1 + dest_is_dir = isinstance(lpath, str) and ( + trailing_sep(lpath) or LocalFileSystem().isdir(lpath) + ) + + exists = source_is_str and ( + (has_magic(rpath) and source_is_file) + or (not has_magic(rpath) and dest_is_dir and not trailing_sep(rpath)) + ) + lpaths = other_paths( + rpaths, + lpath, + exists=exists, + flatten=not source_is_str, + ) + + callback.set_size(len(lpaths)) + for lpath, rpath in callback.wrap(zip(lpaths, rpaths)): + with callback.branched(rpath, lpath) as child: + self.get_file(rpath, lpath, callback=child, **kwargs) + + def put_file( + self, lpath, rpath, callback=DEFAULT_CALLBACK, mode="overwrite", **kwargs + ): + """Copy single file to remote""" + if mode == "create" and self.exists(rpath): + raise FileExistsError + if os.path.isdir(lpath): + self.makedirs(rpath, exist_ok=True) + return None + + with open(lpath, "rb") as f1: + size = f1.seek(0, 2) + callback.set_size(size) + f1.seek(0) + + self.mkdirs(self._parent(os.fspath(rpath)), exist_ok=True) + with self.open(rpath, "wb", **kwargs) as f2: + while f1.tell() < size: + data = f1.read(self.blocksize) + segment_len = f2.write(data) + if segment_len is None: + segment_len = len(data) + callback.relative_update(segment_len) + + def put( + self, + lpath, + rpath, + recursive=False, + callback=DEFAULT_CALLBACK, + maxdepth=None, + **kwargs, + ): + """Copy file(s) from local. + + Copies a specific file or tree of files (if recursive=True). If rpath + ends with a "/", it will be assumed to be a directory, and target files + will go within. + + Calls put_file for each source. + """ + if isinstance(lpath, list) and isinstance(rpath, list): + # No need to expand paths when both source and destination + # are provided as lists + rpaths = rpath + lpaths = lpath + else: + from .implementations.local import ( + LocalFileSystem, + make_path_posix, + trailing_sep, + ) + + source_is_str = isinstance(lpath, str) + if source_is_str: + lpath = make_path_posix(lpath) + fs = LocalFileSystem() + lpaths = fs.expand_path( + lpath, recursive=recursive, maxdepth=maxdepth, **kwargs + ) + if source_is_str and (not recursive or maxdepth is not None): + # Non-recursive glob does not copy directories + lpaths = [p for p in lpaths if not (trailing_sep(p) or fs.isdir(p))] + if not lpaths: + return + + source_is_file = len(lpaths) == 1 + dest_is_dir = isinstance(rpath, str) and ( + trailing_sep(rpath) or self.isdir(rpath) + ) + + rpath = ( + self._strip_protocol(rpath) + if isinstance(rpath, str) + else [self._strip_protocol(p) for p in rpath] + ) + exists = source_is_str and ( + (has_magic(lpath) and source_is_file) + or (not has_magic(lpath) and dest_is_dir and not trailing_sep(lpath)) + ) + rpaths = other_paths( + lpaths, + rpath, + exists=exists, + flatten=not source_is_str, + ) + + callback.set_size(len(rpaths)) + for lpath, rpath in callback.wrap(zip(lpaths, rpaths)): + with callback.branched(lpath, rpath) as child: + self.put_file(lpath, rpath, callback=child, **kwargs) + + def head(self, path, size=1024): + """Get the first ``size`` bytes from file""" + with self.open(path, "rb") as f: + return f.read(size) + + def tail(self, path, size=1024): + """Get the last ``size`` bytes from file""" + with self.open(path, "rb") as f: + f.seek(max(-size, -f.size), 2) + return f.read() + + def cp_file(self, path1, path2, **kwargs): + raise NotImplementedError + + def copy( + self, path1, path2, recursive=False, maxdepth=None, on_error=None, **kwargs + ): + """Copy within two locations in the filesystem + + on_error : "raise", "ignore" + If raise, any not-found exceptions will be raised; if ignore any + not-found exceptions will cause the path to be skipped; defaults to + raise unless recursive is true, where the default is ignore + """ + if on_error is None and recursive: + on_error = "ignore" + elif on_error is None: + on_error = "raise" + + if isinstance(path1, list) and isinstance(path2, list): + # No need to expand paths when both source and destination + # are provided as lists + paths1 = path1 + paths2 = path2 + else: + from .implementations.local import trailing_sep + + source_is_str = isinstance(path1, str) + paths1 = self.expand_path( + path1, recursive=recursive, maxdepth=maxdepth, **kwargs + ) + if source_is_str and (not recursive or maxdepth is not None): + # Non-recursive glob does not copy directories + paths1 = [p for p in paths1 if not (trailing_sep(p) or self.isdir(p))] + if not paths1: + return + + source_is_file = len(paths1) == 1 + dest_is_dir = isinstance(path2, str) and ( + trailing_sep(path2) or self.isdir(path2) + ) + + exists = source_is_str and ( + (has_magic(path1) and source_is_file) + or (not has_magic(path1) and dest_is_dir and not trailing_sep(path1)) + ) + paths2 = other_paths( + paths1, + path2, + exists=exists, + flatten=not source_is_str, + ) + + for p1, p2 in zip(paths1, paths2): + try: + self.cp_file(p1, p2, **kwargs) + except FileNotFoundError: + if on_error == "raise": + raise + + def expand_path(self, path, recursive=False, maxdepth=None, **kwargs): + """Turn one or more globs or directories into a list of all matching paths + to files or directories. + + kwargs are passed to ``glob`` or ``find``, which may in turn call ``ls`` + """ + + if maxdepth is not None and maxdepth < 1: + raise ValueError("maxdepth must be at least 1") + + if isinstance(path, (str, os.PathLike)): + out = self.expand_path([path], recursive, maxdepth, **kwargs) + else: + out = set() + path = [self._strip_protocol(p) for p in path] + for p in path: + if has_magic(p): + bit = set(self.glob(p, maxdepth=maxdepth, **kwargs)) + out |= bit + if recursive: + # glob call above expanded one depth so if maxdepth is defined + # then decrement it in expand_path call below. If it is zero + # after decrementing then avoid expand_path call. + if maxdepth is not None and maxdepth <= 1: + continue + out |= set( + self.expand_path( + list(bit), + recursive=recursive, + maxdepth=maxdepth - 1 if maxdepth is not None else None, + **kwargs, + ) + ) + continue + elif recursive: + rec = set( + self.find( + p, maxdepth=maxdepth, withdirs=True, detail=False, **kwargs + ) + ) + out |= rec + if p not in out and (recursive is False or self.exists(p)): + # should only check once, for the root + out.add(p) + if not out: + raise FileNotFoundError(path) + return sorted(out) + + def mv(self, path1, path2, recursive=False, maxdepth=None, **kwargs): + """Move file(s) from one location to another""" + if path1 == path2: + logger.debug("%s mv: The paths are the same, so no files were moved.", self) + else: + # explicitly raise exception to prevent data corruption + self.copy( + path1, path2, recursive=recursive, maxdepth=maxdepth, onerror="raise" + ) + self.rm(path1, recursive=recursive) + + def rm_file(self, path): + """Delete a file""" + self._rm(path) + + def _rm(self, path): + """Delete one file""" + # this is the old name for the method, prefer rm_file + raise NotImplementedError + + def rm(self, path, recursive=False, maxdepth=None): + """Delete files. + + Parameters + ---------- + path: str or list of str + File(s) to delete. + recursive: bool + If file(s) are directories, recursively delete contents and then + also remove the directory + maxdepth: int or None + Depth to pass to walk for finding files to delete, if recursive. + If None, there will be no limit and infinite recursion may be + possible. + """ + path = self.expand_path(path, recursive=recursive, maxdepth=maxdepth) + for p in reversed(path): + self.rm_file(p) + + @classmethod + def _parent(cls, path): + path = cls._strip_protocol(path) + if "/" in path: + parent = path.rsplit("/", 1)[0].lstrip(cls.root_marker) + return cls.root_marker + parent + else: + return cls.root_marker + + def _open( + self, + path, + mode="rb", + block_size=None, + autocommit=True, + cache_options=None, + **kwargs, + ): + """Return raw bytes-mode file-like from the file-system""" + return AbstractBufferedFile( + self, + path, + mode, + block_size, + autocommit, + cache_options=cache_options, + **kwargs, + ) + + def open( + self, + path, + mode="rb", + block_size=None, + cache_options=None, + compression=None, + **kwargs, + ): + """ + Return a file-like object from the filesystem + + The resultant instance must function correctly in a context ``with`` + block. + + Parameters + ---------- + path: str + Target file + mode: str like 'rb', 'w' + See builtin ``open()`` + Mode "x" (exclusive write) may be implemented by the backend. Even if + it is, whether it is checked up front or on commit, and whether it is + atomic is implementation-dependent. + block_size: int + Some indication of buffering - this is a value in bytes + cache_options : dict, optional + Extra arguments to pass through to the cache. + compression: string or None + If given, open file using compression codec. Can either be a compression + name (a key in ``fsspec.compression.compr``) or "infer" to guess the + compression from the filename suffix. + encoding, errors, newline: passed on to TextIOWrapper for text mode + """ + import io + + path = self._strip_protocol(path) + if "b" not in mode: + mode = mode.replace("t", "") + "b" + + text_kwargs = { + k: kwargs.pop(k) + for k in ["encoding", "errors", "newline"] + if k in kwargs + } + return io.TextIOWrapper( + self.open( + path, + mode, + block_size=block_size, + cache_options=cache_options, + compression=compression, + **kwargs, + ), + **text_kwargs, + ) + else: + ac = kwargs.pop("autocommit", not self._intrans) + f = self._open( + path, + mode=mode, + block_size=block_size, + autocommit=ac, + cache_options=cache_options, + **kwargs, + ) + if compression is not None: + from fsspec.compression import compr + from fsspec.core import get_compression + + compression = get_compression(path, compression) + compress = compr[compression] + f = compress(f, mode=mode[0]) + + if not ac and "r" not in mode: + self.transaction.files.append(f) + return f + + def touch(self, path, truncate=True, **kwargs): + """Create empty file, or update timestamp + + Parameters + ---------- + path: str + file location + truncate: bool + If True, always set file size to 0; if False, update timestamp and + leave file unchanged, if backend allows this + """ + if truncate or not self.exists(path): + with self.open(path, "wb", **kwargs): + pass + else: + raise NotImplementedError # update timestamp, if possible + + def ukey(self, path): + """Hash of file properties, to tell if it has changed""" + return sha256(str(self.info(path)).encode()).hexdigest() + + def read_block(self, fn, offset, length, delimiter=None): + """Read a block of bytes from + + Starting at ``offset`` of the file, read ``length`` bytes. If + ``delimiter`` is set then we ensure that the read starts and stops at + delimiter boundaries that follow the locations ``offset`` and ``offset + + length``. If ``offset`` is zero then we start at zero. The + bytestring returned WILL include the end delimiter string. + + If offset+length is beyond the eof, reads to eof. + + Parameters + ---------- + fn: string + Path to filename + offset: int + Byte offset to start read + length: int + Number of bytes to read. If None, read to end. + delimiter: bytes (optional) + Ensure reading starts and stops at delimiter bytestring + + Examples + -------- + >>> fs.read_block('data/file.csv', 0, 13) # doctest: +SKIP + b'Alice, 100\\nBo' + >>> fs.read_block('data/file.csv', 0, 13, delimiter=b'\\n') # doctest: +SKIP + b'Alice, 100\\nBob, 200\\n' + + Use ``length=None`` to read to the end of the file. + >>> fs.read_block('data/file.csv', 0, None, delimiter=b'\\n') # doctest: +SKIP + b'Alice, 100\\nBob, 200\\nCharlie, 300' + + See Also + -------- + :func:`fsspec.utils.read_block` + """ + with self.open(fn, "rb") as f: + size = f.size + if length is None: + length = size + if size is not None and offset + length > size: + length = size - offset + return read_block(f, offset, length, delimiter) + + def to_json(self, *, include_password: bool = True) -> str: + """ + JSON representation of this filesystem instance. + + Parameters + ---------- + include_password: bool, default True + Whether to include the password (if any) in the output. + + Returns + ------- + JSON string with keys ``cls`` (the python location of this class), + protocol (text name of this class's protocol, first one in case of + multiple), ``args`` (positional args, usually empty), and all other + keyword arguments as their own keys. + + Warnings + -------- + Serialized filesystems may contain sensitive information which have been + passed to the constructor, such as passwords and tokens. Make sure you + store and send them in a secure environment! + """ + from .json import FilesystemJSONEncoder + + return json.dumps( + self, + cls=type( + "_FilesystemJSONEncoder", + (FilesystemJSONEncoder,), + {"include_password": include_password}, + ), + ) + + @staticmethod + def from_json(blob: str) -> AbstractFileSystem: + """ + Recreate a filesystem instance from JSON representation. + + See ``.to_json()`` for the expected structure of the input. + + Parameters + ---------- + blob: str + + Returns + ------- + file system instance, not necessarily of this particular class. + + Warnings + -------- + This can import arbitrary modules (as determined by the ``cls`` key). + Make sure you haven't installed any modules that may execute malicious code + at import time. + """ + from .json import FilesystemJSONDecoder + + return json.loads(blob, cls=FilesystemJSONDecoder) + + def to_dict(self, *, include_password: bool = True) -> dict[str, Any]: + """ + JSON-serializable dictionary representation of this filesystem instance. + + Parameters + ---------- + include_password: bool, default True + Whether to include the password (if any) in the output. + + Returns + ------- + Dictionary with keys ``cls`` (the python location of this class), + protocol (text name of this class's protocol, first one in case of + multiple), ``args`` (positional args, usually empty), and all other + keyword arguments as their own keys. + + Warnings + -------- + Serialized filesystems may contain sensitive information which have been + passed to the constructor, such as passwords and tokens. Make sure you + store and send them in a secure environment! + """ + from .json import FilesystemJSONEncoder + + json_encoder = FilesystemJSONEncoder() + + cls = type(self) + proto = self.protocol + + storage_options = dict(self.storage_options) + if not include_password: + storage_options.pop("password", None) + + return dict( + cls=f"{cls.__module__}:{cls.__name__}", + protocol=proto[0] if isinstance(proto, (tuple, list)) else proto, + args=json_encoder.make_serializable(self.storage_args), + **json_encoder.make_serializable(storage_options), + ) + + @staticmethod + def from_dict(dct: dict[str, Any]) -> AbstractFileSystem: + """ + Recreate a filesystem instance from dictionary representation. + + See ``.to_dict()`` for the expected structure of the input. + + Parameters + ---------- + dct: Dict[str, Any] + + Returns + ------- + file system instance, not necessarily of this particular class. + + Warnings + -------- + This can import arbitrary modules (as determined by the ``cls`` key). + Make sure you haven't installed any modules that may execute malicious code + at import time. + """ + from .json import FilesystemJSONDecoder + + json_decoder = FilesystemJSONDecoder() + + dct = dict(dct) # Defensive copy + + cls = FilesystemJSONDecoder.try_resolve_fs_cls(dct) + if cls is None: + raise ValueError("Not a serialized AbstractFileSystem") + + dct.pop("cls", None) + dct.pop("protocol", None) + + return cls( + *json_decoder.unmake_serializable(dct.pop("args", ())), + **json_decoder.unmake_serializable(dct), + ) + + def _get_pyarrow_filesystem(self): + """ + Make a version of the FS instance which will be acceptable to pyarrow + """ + # all instances already also derive from pyarrow + return self + + def get_mapper(self, root="", check=False, create=False, missing_exceptions=None): + """Create key/value store based on this file-system + + Makes a MutableMapping interface to the FS at the given root path. + See ``fsspec.mapping.FSMap`` for further details. + """ + from .mapping import FSMap + + return FSMap( + root, + self, + check=check, + create=create, + missing_exceptions=missing_exceptions, + ) + + @classmethod + def clear_instance_cache(cls): + """ + Clear the cache of filesystem instances. + + Notes + ----- + Unless overridden by setting the ``cachable`` class attribute to False, + the filesystem class stores a reference to newly created instances. This + prevents Python's normal rules around garbage collection from working, + since the instances refcount will not drop to zero until + ``clear_instance_cache`` is called. + """ + cls._cache.clear() + + def created(self, path): + """Return the created timestamp of a file as a datetime.datetime""" + raise NotImplementedError + + def modified(self, path): + """Return the modified timestamp of a file as a datetime.datetime""" + raise NotImplementedError + + def tree( + self, + path: str = "/", + recursion_limit: int = 2, + max_display: int = 25, + display_size: bool = False, + prefix: str = "", + is_last: bool = True, + first: bool = True, + indent_size: int = 4, + ) -> str: + """ + Return a tree-like structure of the filesystem starting from the given path as a string. + + Parameters + ---------- + path: Root path to start traversal from + recursion_limit: Maximum depth of directory traversal + max_display: Maximum number of items to display per directory + display_size: Whether to display file sizes + prefix: Current line prefix for visual tree structure + is_last: Whether current item is last in its level + first: Whether this is the first call (displays root path) + indent_size: Number of spaces by indent + + Returns + ------- + str: A string representing the tree structure. + + Example + ------- + >>> from fsspec import filesystem + + >>> fs = filesystem('ftp', host='test.rebex.net', user='demo', password='password') + >>> tree = fs.tree(display_size=True, recursion_limit=3, indent_size=8, max_display=10) + >>> print(tree) + """ + + def format_bytes(n: int) -> str: + """Format bytes as text.""" + for prefix, k in ( + ("P", 2**50), + ("T", 2**40), + ("G", 2**30), + ("M", 2**20), + ("k", 2**10), + ): + if n >= 0.9 * k: + return f"{n / k:.2f} {prefix}b" + return f"{n}B" + + result = [] + + if first: + result.append(path) + + if recursion_limit: + indent = " " * indent_size + contents = self.ls(path, detail=True) + contents.sort( + key=lambda x: (x.get("type") != "directory", x.get("name", "")) + ) + + if max_display is not None and len(contents) > max_display: + displayed_contents = contents[:max_display] + remaining_count = len(contents) - max_display + else: + displayed_contents = contents + remaining_count = 0 + + for i, item in enumerate(displayed_contents): + is_last_item = (i == len(displayed_contents) - 1) and ( + remaining_count == 0 + ) + + branch = ( + "└" + ("─" * (indent_size - 2)) + if is_last_item + else "├" + ("─" * (indent_size - 2)) + ) + branch += " " + new_prefix = prefix + ( + indent if is_last_item else "│" + " " * (indent_size - 1) + ) + + name = os.path.basename(item.get("name", "")) + + if display_size and item.get("type") == "directory": + sub_contents = self.ls(item.get("name", ""), detail=True) + num_files = sum( + 1 for sub_item in sub_contents if sub_item.get("type") == "file" + ) + num_folders = sum( + 1 + for sub_item in sub_contents + if sub_item.get("type") == "directory" + ) + + if num_files == 0 and num_folders == 0: + size = " (empty folder)" + elif num_files == 0: + size = f" ({num_folders} subfolder{'s' if num_folders > 1 else ''})" + elif num_folders == 0: + size = f" ({num_files} file{'s' if num_files > 1 else ''})" + else: + size = f" ({num_files} file{'s' if num_files > 1 else ''}, {num_folders} subfolder{'s' if num_folders > 1 else ''})" + elif display_size and item.get("type") == "file": + size = f" ({format_bytes(item.get('size', 0))})" + else: + size = "" + + result.append(f"{prefix}{branch}{name}{size}") + + if item.get("type") == "directory" and recursion_limit > 0: + result.append( + self.tree( + path=item.get("name", ""), + recursion_limit=recursion_limit - 1, + max_display=max_display, + display_size=display_size, + prefix=new_prefix, + is_last=is_last_item, + first=False, + indent_size=indent_size, + ) + ) + + if remaining_count > 0: + more_message = f"{remaining_count} more item(s) not displayed." + result.append( + f"{prefix}{'└' + ('─' * (indent_size - 2))} {more_message}" + ) + + return "\n".join(_ for _ in result if _) + + # ------------------------------------------------------------------------ + # Aliases + + def read_bytes(self, path, start=None, end=None, **kwargs): + """Alias of `AbstractFileSystem.cat_file`.""" + return self.cat_file(path, start=start, end=end, **kwargs) + + def write_bytes(self, path, value, **kwargs): + """Alias of `AbstractFileSystem.pipe_file`.""" + self.pipe_file(path, value, **kwargs) + + def makedir(self, path, create_parents=True, **kwargs): + """Alias of `AbstractFileSystem.mkdir`.""" + return self.mkdir(path, create_parents=create_parents, **kwargs) + + def mkdirs(self, path, exist_ok=False): + """Alias of `AbstractFileSystem.makedirs`.""" + return self.makedirs(path, exist_ok=exist_ok) + + def listdir(self, path, detail=True, **kwargs): + """Alias of `AbstractFileSystem.ls`.""" + return self.ls(path, detail=detail, **kwargs) + + def cp(self, path1, path2, **kwargs): + """Alias of `AbstractFileSystem.copy`.""" + return self.copy(path1, path2, **kwargs) + + def move(self, path1, path2, **kwargs): + """Alias of `AbstractFileSystem.mv`.""" + return self.mv(path1, path2, **kwargs) + + def stat(self, path, **kwargs): + """Alias of `AbstractFileSystem.info`.""" + return self.info(path, **kwargs) + + def disk_usage(self, path, total=True, maxdepth=None, **kwargs): + """Alias of `AbstractFileSystem.du`.""" + return self.du(path, total=total, maxdepth=maxdepth, **kwargs) + + def rename(self, path1, path2, **kwargs): + """Alias of `AbstractFileSystem.mv`.""" + return self.mv(path1, path2, **kwargs) + + def delete(self, path, recursive=False, maxdepth=None): + """Alias of `AbstractFileSystem.rm`.""" + return self.rm(path, recursive=recursive, maxdepth=maxdepth) + + def upload(self, lpath, rpath, recursive=False, **kwargs): + """Alias of `AbstractFileSystem.put`.""" + return self.put(lpath, rpath, recursive=recursive, **kwargs) + + def download(self, rpath, lpath, recursive=False, **kwargs): + """Alias of `AbstractFileSystem.get`.""" + return self.get(rpath, lpath, recursive=recursive, **kwargs) + + def sign(self, path, expiration=100, **kwargs): + """Create a signed URL representing the given path + + Some implementations allow temporary URLs to be generated, as a + way of delegating credentials. + + Parameters + ---------- + path : str + The path on the filesystem + expiration : int + Number of seconds to enable the URL for (if supported) + + Returns + ------- + URL : str + The signed URL + + Raises + ------ + NotImplementedError : if method is not implemented for a filesystem + """ + raise NotImplementedError("Sign is not implemented for this filesystem") + + def _isfilestore(self): + # Originally inherited from pyarrow DaskFileSystem. Keeping this + # here for backwards compatibility as long as pyarrow uses its + # legacy fsspec-compatible filesystems and thus accepts fsspec + # filesystems as well + return False + + +class AbstractBufferedFile(io.IOBase): + """Convenient class to derive from to provide buffering + + In the case that the backend does not provide a pythonic file-like object + already, this class contains much of the logic to build one. The only + methods that need to be overridden are ``_upload_chunk``, + ``_initiate_upload`` and ``_fetch_range``. + """ + + DEFAULT_BLOCK_SIZE = 5 * 2**20 + _details = None + + def __init__( + self, + fs, + path, + mode="rb", + block_size="default", + autocommit=True, + cache_type="readahead", + cache_options=None, + size=None, + **kwargs, + ): + """ + Template for files with buffered reading and writing + + Parameters + ---------- + fs: instance of FileSystem + path: str + location in file-system + mode: str + Normal file modes. Currently only 'wb', 'ab' or 'rb'. Some file + systems may be read-only, and some may not support append. + block_size: int + Buffer size for reading or writing, 'default' for class default + autocommit: bool + Whether to write to final destination; may only impact what + happens when file is being closed. + cache_type: {"readahead", "none", "mmap", "bytes"}, default "readahead" + Caching policy in read mode. See the definitions in ``core``. + cache_options : dict + Additional options passed to the constructor for the cache specified + by `cache_type`. + size: int + If given and in read mode, suppressed having to look up the file size + kwargs: + Gets stored as self.kwargs + """ + from .core import caches + + self.path = path + self.fs = fs + self.mode = mode + self.blocksize = ( + self.DEFAULT_BLOCK_SIZE if block_size in ["default", None] else block_size + ) + self.loc = 0 + self.autocommit = autocommit + self.end = None + self.start = None + self.closed = False + + if cache_options is None: + cache_options = {} + + if "trim" in kwargs: + warnings.warn( + "Passing 'trim' to control the cache behavior has been deprecated. " + "Specify it within the 'cache_options' argument instead.", + FutureWarning, + ) + cache_options["trim"] = kwargs.pop("trim") + + self.kwargs = kwargs + + if mode not in {"ab", "rb", "wb", "xb"}: + raise NotImplementedError("File mode not supported") + if mode == "rb": + if size is not None: + self.size = size + else: + self.size = self.details["size"] + self.cache = caches[cache_type]( + self.blocksize, self._fetch_range, self.size, **cache_options + ) + else: + self.buffer = io.BytesIO() + self.offset = None + self.forced = False + self.location = None + + @property + def details(self): + if self._details is None: + self._details = self.fs.info(self.path) + return self._details + + @details.setter + def details(self, value): + self._details = value + self.size = value["size"] + + @property + def full_name(self): + return _unstrip_protocol(self.path, self.fs) + + @property + def closed(self): + # get around this attr being read-only in IOBase + # use getattr here, since this can be called during del + return getattr(self, "_closed", True) + + @closed.setter + def closed(self, c): + self._closed = c + + def __hash__(self): + if "w" in self.mode: + return id(self) + else: + return int(tokenize(self.details), 16) + + def __eq__(self, other): + """Files are equal if they have the same checksum, only in read mode""" + if self is other: + return True + return ( + isinstance(other, type(self)) + and self.mode == "rb" + and other.mode == "rb" + and hash(self) == hash(other) + ) + + def commit(self): + """Move from temp to final destination""" + + def discard(self): + """Throw away temporary file""" + + def info(self): + """File information about this path""" + if self.readable(): + return self.details + else: + raise ValueError("Info not available while writing") + + def tell(self): + """Current file location""" + return self.loc + + def seek(self, loc, whence=0): + """Set current file location + + Parameters + ---------- + loc: int + byte location + whence: {0, 1, 2} + from start of file, current location or end of file, resp. + """ + loc = int(loc) + if not self.mode == "rb": + raise OSError(ESPIPE, "Seek only available in read mode") + if whence == 0: + nloc = loc + elif whence == 1: + nloc = self.loc + loc + elif whence == 2: + nloc = self.size + loc + else: + raise ValueError(f"invalid whence ({whence}, should be 0, 1 or 2)") + if nloc < 0: + raise ValueError("Seek before start of file") + self.loc = nloc + return self.loc + + def write(self, data): + """ + Write data to buffer. + + Buffer only sent on flush() or if buffer is greater than + or equal to blocksize. + + Parameters + ---------- + data: bytes + Set of bytes to be written. + """ + if not self.writable(): + raise ValueError("File not in write mode") + if self.closed: + raise ValueError("I/O operation on closed file.") + if self.forced: + raise ValueError("This file has been force-flushed, can only close") + out = self.buffer.write(data) + self.loc += out + if self.buffer.tell() >= self.blocksize: + self.flush() + return out + + def flush(self, force=False): + """ + Write buffered data to backend store. + + Writes the current buffer, if it is larger than the block-size, or if + the file is being closed. + + Parameters + ---------- + force: bool + When closing, write the last block even if it is smaller than + blocks are allowed to be. Disallows further writing to this file. + """ + + if self.closed: + raise ValueError("Flush on closed file") + if force and self.forced: + raise ValueError("Force flush cannot be called more than once") + if force: + self.forced = True + + if self.readable(): + # no-op to flush on read-mode + return + + if not force and self.buffer.tell() < self.blocksize: + # Defer write on small block + return + + if self.offset is None: + # Initialize a multipart upload + self.offset = 0 + try: + self._initiate_upload() + except: + self.closed = True + raise + + if self._upload_chunk(final=force) is not False: + self.offset += self.buffer.seek(0, 2) + self.buffer = io.BytesIO() + + def _upload_chunk(self, final=False): + """Write one part of a multi-block file upload + + Parameters + ========== + final: bool + This is the last block, so should complete file, if + self.autocommit is True. + """ + # may not yet have been initialized, may need to call _initialize_upload + + def _initiate_upload(self): + """Create remote file/upload""" + pass + + def _fetch_range(self, start, end): + """Get the specified set of bytes from remote""" + return self.fs.cat_file(self.path, start=start, end=end) + + def read(self, length=-1): + """ + Return data from cache, or fetch pieces as necessary + + Parameters + ---------- + length: int (-1) + Number of bytes to read; if <0, all remaining bytes. + """ + length = -1 if length is None else int(length) + if self.mode != "rb": + raise ValueError("File not in read mode") + if length < 0: + length = self.size - self.loc + if self.closed: + raise ValueError("I/O operation on closed file.") + if length == 0: + # don't even bother calling fetch + return b"" + out = self.cache._fetch(self.loc, self.loc + length) + + logger.debug( + "%s read: %i - %i %s", + self, + self.loc, + self.loc + length, + self.cache._log_stats(), + ) + self.loc += len(out) + return out + + def readinto(self, b): + """mirrors builtin file's readinto method + + https://docs.python.org/3/library/io.html#io.RawIOBase.readinto + """ + out = memoryview(b).cast("B") + data = self.read(out.nbytes) + out[: len(data)] = data + return len(data) + + def readuntil(self, char=b"\n", blocks=None): + """Return data between current position and first occurrence of char + + char is included in the output, except if the end of the tile is + encountered first. + + Parameters + ---------- + char: bytes + Thing to find + blocks: None or int + How much to read in each go. Defaults to file blocksize - which may + mean a new read on every call. + """ + out = [] + while True: + start = self.tell() + part = self.read(blocks or self.blocksize) + if len(part) == 0: + break + found = part.find(char) + if found > -1: + out.append(part[: found + len(char)]) + self.seek(start + found + len(char)) + break + out.append(part) + return b"".join(out) + + def readline(self): + """Read until and including the first occurrence of newline character + + Note that, because of character encoding, this is not necessarily a + true line ending. + """ + return self.readuntil(b"\n") + + def __next__(self): + out = self.readline() + if out: + return out + raise StopIteration + + def __iter__(self): + return self + + def readlines(self): + """Return all data, split by the newline character, including the newline character""" + data = self.read() + lines = data.split(b"\n") + out = [l + b"\n" for l in lines[:-1]] + if data.endswith(b"\n"): + return out + else: + return out + [lines[-1]] + # return list(self) ??? + + def readinto1(self, b): + return self.readinto(b) + + def close(self): + """Close file + + Finalizes writes, discards cache + """ + if getattr(self, "_unclosable", False): + return + if self.closed: + return + try: + if self.mode == "rb": + self.cache = None + else: + if not self.forced: + self.flush(force=True) + + if self.fs is not None: + self.fs.invalidate_cache(self.path) + self.fs.invalidate_cache(self.fs._parent(self.path)) + finally: + self.closed = True + + def readable(self): + """Whether opened for reading""" + return "r" in self.mode and not self.closed + + def seekable(self): + """Whether is seekable (only in read mode)""" + return self.readable() + + def writable(self): + """Whether opened for writing""" + return self.mode in {"wb", "ab", "xb"} and not self.closed + + def __reduce__(self): + if self.mode != "rb": + raise RuntimeError("Pickling a writeable file is not supported") + + return reopen, ( + self.fs, + self.path, + self.mode, + self.blocksize, + self.loc, + self.size, + self.autocommit, + self.cache.name if self.cache else "none", + self.kwargs, + ) + + def __del__(self): + if not self.closed: + self.close() + + def __str__(self): + return f"" + + __repr__ = __str__ + + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + +def reopen(fs, path, mode, blocksize, loc, size, autocommit, cache_type, kwargs): + file = fs.open( + path, + mode=mode, + block_size=blocksize, + autocommit=autocommit, + cache_type=cache_type, + size=size, + **kwargs, + ) + if loc > 0: + file.seek(loc) + return file diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/tests/abstract/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/tests/abstract/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..8ed2ad802ecaf021106c25c03112f29e75c7b2f8 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/tests/abstract/__init__.py @@ -0,0 +1,289 @@ +import os +from hashlib import md5 + +import pytest + +from fsspec.implementations.local import LocalFileSystem +from fsspec.tests.abstract.copy import AbstractCopyTests # noqa: F401 +from fsspec.tests.abstract.get import AbstractGetTests # noqa: F401 +from fsspec.tests.abstract.open import AbstractOpenTests # noqa: F401 +from fsspec.tests.abstract.pipe import AbstractPipeTests # noqa: F401 +from fsspec.tests.abstract.put import AbstractPutTests # noqa: F401 + + +class BaseAbstractFixtures: + """ + Abstract base class containing fixtures that are used by but never need to + be overridden in derived filesystem-specific classes to run the abstract + tests on such filesystems. + """ + + @pytest.fixture + def fs_bulk_operations_scenario_0(self, fs, fs_join, fs_path): + """ + Scenario on remote filesystem that is used for many cp/get/put tests. + + Cleans up at the end of each test it which it is used. + """ + source = self._bulk_operations_scenario_0(fs, fs_join, fs_path) + yield source + fs.rm(source, recursive=True) + + @pytest.fixture + def fs_glob_edge_cases_files(self, fs, fs_join, fs_path): + """ + Scenario on remote filesystem that is used for glob edge cases cp/get/put tests. + + Cleans up at the end of each test it which it is used. + """ + source = self._glob_edge_cases_files(fs, fs_join, fs_path) + yield source + fs.rm(source, recursive=True) + + @pytest.fixture + def fs_dir_and_file_with_same_name_prefix(self, fs, fs_join, fs_path): + """ + Scenario on remote filesystem that is used to check cp/get/put on directory + and file with the same name prefixes. + + Cleans up at the end of each test it which it is used. + """ + source = self._dir_and_file_with_same_name_prefix(fs, fs_join, fs_path) + yield source + fs.rm(source, recursive=True) + + @pytest.fixture + def fs_10_files_with_hashed_names(self, fs, fs_join, fs_path): + """ + Scenario on remote filesystem that is used to check cp/get/put files order + when source and destination are lists. + + Cleans up at the end of each test it which it is used. + """ + source = self._10_files_with_hashed_names(fs, fs_join, fs_path) + yield source + fs.rm(source, recursive=True) + + @pytest.fixture + def fs_target(self, fs, fs_join, fs_path): + """ + Return name of remote directory that does not yet exist to copy into. + + Cleans up at the end of each test it which it is used. + """ + target = fs_join(fs_path, "target") + yield target + if fs.exists(target): + fs.rm(target, recursive=True) + + @pytest.fixture + def local_bulk_operations_scenario_0(self, local_fs, local_join, local_path): + """ + Scenario on local filesystem that is used for many cp/get/put tests. + + Cleans up at the end of each test it which it is used. + """ + source = self._bulk_operations_scenario_0(local_fs, local_join, local_path) + yield source + local_fs.rm(source, recursive=True) + + @pytest.fixture + def local_glob_edge_cases_files(self, local_fs, local_join, local_path): + """ + Scenario on local filesystem that is used for glob edge cases cp/get/put tests. + + Cleans up at the end of each test it which it is used. + """ + source = self._glob_edge_cases_files(local_fs, local_join, local_path) + yield source + local_fs.rm(source, recursive=True) + + @pytest.fixture + def local_dir_and_file_with_same_name_prefix( + self, local_fs, local_join, local_path + ): + """ + Scenario on local filesystem that is used to check cp/get/put on directory + and file with the same name prefixes. + + Cleans up at the end of each test it which it is used. + """ + source = self._dir_and_file_with_same_name_prefix( + local_fs, local_join, local_path + ) + yield source + local_fs.rm(source, recursive=True) + + @pytest.fixture + def local_10_files_with_hashed_names(self, local_fs, local_join, local_path): + """ + Scenario on local filesystem that is used to check cp/get/put files order + when source and destination are lists. + + Cleans up at the end of each test it which it is used. + """ + source = self._10_files_with_hashed_names(local_fs, local_join, local_path) + yield source + local_fs.rm(source, recursive=True) + + @pytest.fixture + def local_target(self, local_fs, local_join, local_path): + """ + Return name of local directory that does not yet exist to copy into. + + Cleans up at the end of each test it which it is used. + """ + target = local_join(local_path, "target") + yield target + if local_fs.exists(target): + local_fs.rm(target, recursive=True) + + def _glob_edge_cases_files(self, some_fs, some_join, some_path): + """ + Scenario that is used for glob edge cases cp/get/put tests. + Creates the following directory and file structure: + + 📁 source + ├── 📄 file1 + ├── 📄 file2 + ├── 📁 subdir0 + │ ├── 📄 subfile1 + │ ├── 📄 subfile2 + │ └── 📁 nesteddir + │ └── 📄 nestedfile + └── 📁 subdir1 + ├── 📄 subfile1 + ├── 📄 subfile2 + └── 📁 nesteddir + └── 📄 nestedfile + """ + source = some_join(some_path, "source") + some_fs.touch(some_join(source, "file1")) + some_fs.touch(some_join(source, "file2")) + + for subdir_idx in range(2): + subdir = some_join(source, f"subdir{subdir_idx}") + nesteddir = some_join(subdir, "nesteddir") + some_fs.makedirs(nesteddir) + some_fs.touch(some_join(subdir, "subfile1")) + some_fs.touch(some_join(subdir, "subfile2")) + some_fs.touch(some_join(nesteddir, "nestedfile")) + + return source + + def _bulk_operations_scenario_0(self, some_fs, some_join, some_path): + """ + Scenario that is used for many cp/get/put tests. Creates the following + directory and file structure: + + 📁 source + ├── 📄 file1 + ├── 📄 file2 + └── 📁 subdir + ├── 📄 subfile1 + ├── 📄 subfile2 + └── 📁 nesteddir + └── 📄 nestedfile + """ + source = some_join(some_path, "source") + subdir = some_join(source, "subdir") + nesteddir = some_join(subdir, "nesteddir") + some_fs.makedirs(nesteddir) + some_fs.touch(some_join(source, "file1")) + some_fs.touch(some_join(source, "file2")) + some_fs.touch(some_join(subdir, "subfile1")) + some_fs.touch(some_join(subdir, "subfile2")) + some_fs.touch(some_join(nesteddir, "nestedfile")) + return source + + def _dir_and_file_with_same_name_prefix(self, some_fs, some_join, some_path): + """ + Scenario that is used to check cp/get/put on directory and file with + the same name prefixes. Creates the following directory and file structure: + + 📁 source + ├── 📄 subdir.txt + └── 📁 subdir + └── 📄 subfile.txt + """ + source = some_join(some_path, "source") + subdir = some_join(source, "subdir") + file = some_join(source, "subdir.txt") + subfile = some_join(subdir, "subfile.txt") + some_fs.makedirs(subdir) + some_fs.touch(file) + some_fs.touch(subfile) + return source + + def _10_files_with_hashed_names(self, some_fs, some_join, some_path): + """ + Scenario that is used to check cp/get/put files order when source and + destination are lists. Creates the following directory and file structure: + + 📁 source + └── 📄 {hashed([0-9])}.txt + """ + source = some_join(some_path, "source") + for i in range(10): + hashed_i = md5(str(i).encode("utf-8")).hexdigest() + path = some_join(source, f"{hashed_i}.txt") + some_fs.pipe(path=path, value=f"{i}".encode()) + return source + + +class AbstractFixtures(BaseAbstractFixtures): + """ + Abstract base class containing fixtures that may be overridden in derived + filesystem-specific classes to run the abstract tests on such filesystems. + + For any particular filesystem some of these fixtures must be overridden, + such as ``fs`` and ``fs_path``, and others may be overridden if the + default functions here are not appropriate, such as ``fs_join``. + """ + + @pytest.fixture + def fs(self): + raise NotImplementedError("This function must be overridden in derived classes") + + @pytest.fixture + def fs_join(self): + """ + Return a function that joins its arguments together into a path. + + Most fsspec implementations join paths in a platform-dependent way, + but some will override this to always use a forward slash. + """ + return os.path.join + + @pytest.fixture + def fs_path(self): + raise NotImplementedError("This function must be overridden in derived classes") + + @pytest.fixture(scope="class") + def local_fs(self): + # Maybe need an option for auto_mkdir=False? This is only relevant + # for certain implementations. + return LocalFileSystem(auto_mkdir=True) + + @pytest.fixture + def local_join(self): + """ + Return a function that joins its arguments together into a path, on + the local filesystem. + """ + return os.path.join + + @pytest.fixture + def local_path(self, tmpdir): + return tmpdir + + @pytest.fixture + def supports_empty_directories(self): + """ + Return whether this implementation supports empty directories. + """ + return True + + @pytest.fixture + def fs_sanitize_path(self): + return lambda x: x diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/tests/abstract/common.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/tests/abstract/common.py new file mode 100644 index 0000000000000000000000000000000000000000..22e7c4140404ab2a8928689721419cf05c2760b9 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/tests/abstract/common.py @@ -0,0 +1,175 @@ +GLOB_EDGE_CASES_TESTS = { + "argnames": ("path", "recursive", "maxdepth", "expected"), + "argvalues": [ + ("fil?1", False, None, ["file1"]), + ("fil?1", True, None, ["file1"]), + ("file[1-2]", False, None, ["file1", "file2"]), + ("file[1-2]", True, None, ["file1", "file2"]), + ("*", False, None, ["file1", "file2"]), + ( + "*", + True, + None, + [ + "file1", + "file2", + "subdir0/subfile1", + "subdir0/subfile2", + "subdir0/nesteddir/nestedfile", + "subdir1/subfile1", + "subdir1/subfile2", + "subdir1/nesteddir/nestedfile", + ], + ), + ("*", True, 1, ["file1", "file2"]), + ( + "*", + True, + 2, + [ + "file1", + "file2", + "subdir0/subfile1", + "subdir0/subfile2", + "subdir1/subfile1", + "subdir1/subfile2", + ], + ), + ("*1", False, None, ["file1"]), + ( + "*1", + True, + None, + [ + "file1", + "subdir1/subfile1", + "subdir1/subfile2", + "subdir1/nesteddir/nestedfile", + ], + ), + ("*1", True, 2, ["file1", "subdir1/subfile1", "subdir1/subfile2"]), + ( + "**", + False, + None, + [ + "file1", + "file2", + "subdir0/subfile1", + "subdir0/subfile2", + "subdir0/nesteddir/nestedfile", + "subdir1/subfile1", + "subdir1/subfile2", + "subdir1/nesteddir/nestedfile", + ], + ), + ( + "**", + True, + None, + [ + "file1", + "file2", + "subdir0/subfile1", + "subdir0/subfile2", + "subdir0/nesteddir/nestedfile", + "subdir1/subfile1", + "subdir1/subfile2", + "subdir1/nesteddir/nestedfile", + ], + ), + ("**", True, 1, ["file1", "file2"]), + ( + "**", + True, + 2, + [ + "file1", + "file2", + "subdir0/subfile1", + "subdir0/subfile2", + "subdir0/nesteddir/nestedfile", + "subdir1/subfile1", + "subdir1/subfile2", + "subdir1/nesteddir/nestedfile", + ], + ), + ( + "**", + False, + 2, + [ + "file1", + "file2", + "subdir0/subfile1", + "subdir0/subfile2", + "subdir1/subfile1", + "subdir1/subfile2", + ], + ), + ("**/*1", False, None, ["file1", "subdir0/subfile1", "subdir1/subfile1"]), + ( + "**/*1", + True, + None, + [ + "file1", + "subdir0/subfile1", + "subdir1/subfile1", + "subdir1/subfile2", + "subdir1/nesteddir/nestedfile", + ], + ), + ("**/*1", True, 1, ["file1"]), + ( + "**/*1", + True, + 2, + ["file1", "subdir0/subfile1", "subdir1/subfile1", "subdir1/subfile2"], + ), + ("**/*1", False, 2, ["file1", "subdir0/subfile1", "subdir1/subfile1"]), + ("**/subdir0", False, None, []), + ("**/subdir0", True, None, ["subfile1", "subfile2", "nesteddir/nestedfile"]), + ("**/subdir0/nested*", False, 2, []), + ("**/subdir0/nested*", True, 2, ["nestedfile"]), + ("subdir[1-2]", False, None, []), + ("subdir[1-2]", True, None, ["subfile1", "subfile2", "nesteddir/nestedfile"]), + ("subdir[1-2]", True, 2, ["subfile1", "subfile2"]), + ("subdir[0-1]", False, None, []), + ( + "subdir[0-1]", + True, + None, + [ + "subdir0/subfile1", + "subdir0/subfile2", + "subdir0/nesteddir/nestedfile", + "subdir1/subfile1", + "subdir1/subfile2", + "subdir1/nesteddir/nestedfile", + ], + ), + ( + "subdir[0-1]/*fil[e]*", + False, + None, + [ + "subdir0/subfile1", + "subdir0/subfile2", + "subdir1/subfile1", + "subdir1/subfile2", + ], + ), + ( + "subdir[0-1]/*fil[e]*", + True, + None, + [ + "subdir0/subfile1", + "subdir0/subfile2", + "subdir1/subfile1", + "subdir1/subfile2", + ], + ), + ], +} diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/tests/abstract/copy.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/tests/abstract/copy.py new file mode 100644 index 0000000000000000000000000000000000000000..e39e57e5f7d52bfda8ab5e2398b04cc2303630a0 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/tests/abstract/copy.py @@ -0,0 +1,557 @@ +from hashlib import md5 +from itertools import product + +import pytest + +from fsspec.tests.abstract.common import GLOB_EDGE_CASES_TESTS + + +class AbstractCopyTests: + def test_copy_file_to_existing_directory( + self, + fs, + fs_join, + fs_bulk_operations_scenario_0, + fs_target, + supports_empty_directories, + ): + # Copy scenario 1a + source = fs_bulk_operations_scenario_0 + + target = fs_target + fs.mkdir(target) + if not supports_empty_directories: + # Force target directory to exist by adding a dummy file + fs.touch(fs_join(target, "dummy")) + assert fs.isdir(target) + + target_file2 = fs_join(target, "file2") + target_subfile1 = fs_join(target, "subfile1") + + # Copy from source directory + fs.cp(fs_join(source, "file2"), target) + assert fs.isfile(target_file2) + + # Copy from sub directory + fs.cp(fs_join(source, "subdir", "subfile1"), target) + assert fs.isfile(target_subfile1) + + # Remove copied files + fs.rm([target_file2, target_subfile1]) + assert not fs.exists(target_file2) + assert not fs.exists(target_subfile1) + + # Repeat with trailing slash on target + fs.cp(fs_join(source, "file2"), target + "/") + assert fs.isdir(target) + assert fs.isfile(target_file2) + + fs.cp(fs_join(source, "subdir", "subfile1"), target + "/") + assert fs.isfile(target_subfile1) + + def test_copy_file_to_new_directory( + self, fs, fs_join, fs_bulk_operations_scenario_0, fs_target + ): + # Copy scenario 1b + source = fs_bulk_operations_scenario_0 + + target = fs_target + fs.mkdir(target) + + fs.cp( + fs_join(source, "subdir", "subfile1"), fs_join(target, "newdir/") + ) # Note trailing slash + assert fs.isdir(target) + assert fs.isdir(fs_join(target, "newdir")) + assert fs.isfile(fs_join(target, "newdir", "subfile1")) + + def test_copy_file_to_file_in_existing_directory( + self, + fs, + fs_join, + fs_bulk_operations_scenario_0, + fs_target, + supports_empty_directories, + ): + # Copy scenario 1c + source = fs_bulk_operations_scenario_0 + + target = fs_target + fs.mkdir(target) + if not supports_empty_directories: + # Force target directory to exist by adding a dummy file + fs.touch(fs_join(target, "dummy")) + assert fs.isdir(target) + + fs.cp(fs_join(source, "subdir", "subfile1"), fs_join(target, "newfile")) + assert fs.isfile(fs_join(target, "newfile")) + + def test_copy_file_to_file_in_new_directory( + self, fs, fs_join, fs_bulk_operations_scenario_0, fs_target + ): + # Copy scenario 1d + source = fs_bulk_operations_scenario_0 + + target = fs_target + fs.mkdir(target) + + fs.cp( + fs_join(source, "subdir", "subfile1"), fs_join(target, "newdir", "newfile") + ) + assert fs.isdir(fs_join(target, "newdir")) + assert fs.isfile(fs_join(target, "newdir", "newfile")) + + def test_copy_directory_to_existing_directory( + self, + fs, + fs_join, + fs_bulk_operations_scenario_0, + fs_target, + supports_empty_directories, + ): + # Copy scenario 1e + source = fs_bulk_operations_scenario_0 + + target = fs_target + fs.mkdir(target) + if not supports_empty_directories: + # Force target directory to exist by adding a dummy file + dummy = fs_join(target, "dummy") + fs.touch(dummy) + assert fs.isdir(target) + + for source_slash, target_slash in zip([False, True], [False, True]): + s = fs_join(source, "subdir") + if source_slash: + s += "/" + t = target + "/" if target_slash else target + + # Without recursive does nothing + fs.cp(s, t) + assert fs.ls(target, detail=False) == ( + [] if supports_empty_directories else [dummy] + ) + + # With recursive + fs.cp(s, t, recursive=True) + if source_slash: + assert fs.isfile(fs_join(target, "subfile1")) + assert fs.isfile(fs_join(target, "subfile2")) + assert fs.isdir(fs_join(target, "nesteddir")) + assert fs.isfile(fs_join(target, "nesteddir", "nestedfile")) + assert not fs.exists(fs_join(target, "subdir")) + + fs.rm( + [ + fs_join(target, "subfile1"), + fs_join(target, "subfile2"), + fs_join(target, "nesteddir"), + ], + recursive=True, + ) + else: + assert fs.isdir(fs_join(target, "subdir")) + assert fs.isfile(fs_join(target, "subdir", "subfile1")) + assert fs.isfile(fs_join(target, "subdir", "subfile2")) + assert fs.isdir(fs_join(target, "subdir", "nesteddir")) + assert fs.isfile(fs_join(target, "subdir", "nesteddir", "nestedfile")) + + fs.rm(fs_join(target, "subdir"), recursive=True) + assert fs.ls(target, detail=False) == ( + [] if supports_empty_directories else [dummy] + ) + + # Limit recursive by maxdepth + fs.cp(s, t, recursive=True, maxdepth=1) + if source_slash: + assert fs.isfile(fs_join(target, "subfile1")) + assert fs.isfile(fs_join(target, "subfile2")) + assert not fs.exists(fs_join(target, "nesteddir")) + assert not fs.exists(fs_join(target, "subdir")) + + fs.rm( + [ + fs_join(target, "subfile1"), + fs_join(target, "subfile2"), + ], + recursive=True, + ) + else: + assert fs.isdir(fs_join(target, "subdir")) + assert fs.isfile(fs_join(target, "subdir", "subfile1")) + assert fs.isfile(fs_join(target, "subdir", "subfile2")) + assert not fs.exists(fs_join(target, "subdir", "nesteddir")) + + fs.rm(fs_join(target, "subdir"), recursive=True) + assert fs.ls(target, detail=False) == ( + [] if supports_empty_directories else [dummy] + ) + + def test_copy_directory_to_new_directory( + self, + fs, + fs_join, + fs_bulk_operations_scenario_0, + fs_target, + supports_empty_directories, + ): + # Copy scenario 1f + source = fs_bulk_operations_scenario_0 + + target = fs_target + fs.mkdir(target) + + for source_slash, target_slash in zip([False, True], [False, True]): + s = fs_join(source, "subdir") + if source_slash: + s += "/" + t = fs_join(target, "newdir") + if target_slash: + t += "/" + + # Without recursive does nothing + fs.cp(s, t) + if supports_empty_directories: + assert fs.ls(target) == [] + else: + with pytest.raises(FileNotFoundError): + fs.ls(target) + + # With recursive + fs.cp(s, t, recursive=True) + assert fs.isdir(fs_join(target, "newdir")) + assert fs.isfile(fs_join(target, "newdir", "subfile1")) + assert fs.isfile(fs_join(target, "newdir", "subfile2")) + assert fs.isdir(fs_join(target, "newdir", "nesteddir")) + assert fs.isfile(fs_join(target, "newdir", "nesteddir", "nestedfile")) + assert not fs.exists(fs_join(target, "subdir")) + + fs.rm(fs_join(target, "newdir"), recursive=True) + assert not fs.exists(fs_join(target, "newdir")) + + # Limit recursive by maxdepth + fs.cp(s, t, recursive=True, maxdepth=1) + assert fs.isdir(fs_join(target, "newdir")) + assert fs.isfile(fs_join(target, "newdir", "subfile1")) + assert fs.isfile(fs_join(target, "newdir", "subfile2")) + assert not fs.exists(fs_join(target, "newdir", "nesteddir")) + assert not fs.exists(fs_join(target, "subdir")) + + fs.rm(fs_join(target, "newdir"), recursive=True) + assert not fs.exists(fs_join(target, "newdir")) + + def test_copy_glob_to_existing_directory( + self, + fs, + fs_join, + fs_bulk_operations_scenario_0, + fs_target, + supports_empty_directories, + ): + # Copy scenario 1g + source = fs_bulk_operations_scenario_0 + + target = fs_target + fs.mkdir(target) + if not supports_empty_directories: + # Force target directory to exist by adding a dummy file + dummy = fs_join(target, "dummy") + fs.touch(dummy) + assert fs.isdir(target) + + for target_slash in [False, True]: + t = target + "/" if target_slash else target + + # Without recursive + fs.cp(fs_join(source, "subdir", "*"), t) + assert fs.isfile(fs_join(target, "subfile1")) + assert fs.isfile(fs_join(target, "subfile2")) + assert not fs.isdir(fs_join(target, "nesteddir")) + assert not fs.exists(fs_join(target, "nesteddir", "nestedfile")) + assert not fs.exists(fs_join(target, "subdir")) + + fs.rm( + [ + fs_join(target, "subfile1"), + fs_join(target, "subfile2"), + ], + recursive=True, + ) + assert fs.ls(target, detail=False) == ( + [] if supports_empty_directories else [dummy] + ) + + # With recursive + for glob, recursive in zip(["*", "**"], [True, False]): + fs.cp(fs_join(source, "subdir", glob), t, recursive=recursive) + assert fs.isfile(fs_join(target, "subfile1")) + assert fs.isfile(fs_join(target, "subfile2")) + assert fs.isdir(fs_join(target, "nesteddir")) + assert fs.isfile(fs_join(target, "nesteddir", "nestedfile")) + assert not fs.exists(fs_join(target, "subdir")) + + fs.rm( + [ + fs_join(target, "subfile1"), + fs_join(target, "subfile2"), + fs_join(target, "nesteddir"), + ], + recursive=True, + ) + assert fs.ls(target, detail=False) == ( + [] if supports_empty_directories else [dummy] + ) + + # Limit recursive by maxdepth + fs.cp( + fs_join(source, "subdir", glob), t, recursive=recursive, maxdepth=1 + ) + assert fs.isfile(fs_join(target, "subfile1")) + assert fs.isfile(fs_join(target, "subfile2")) + assert not fs.exists(fs_join(target, "nesteddir")) + assert not fs.exists(fs_join(target, "subdir")) + + fs.rm( + [ + fs_join(target, "subfile1"), + fs_join(target, "subfile2"), + ], + recursive=True, + ) + assert fs.ls(target, detail=False) == ( + [] if supports_empty_directories else [dummy] + ) + + def test_copy_glob_to_new_directory( + self, fs, fs_join, fs_bulk_operations_scenario_0, fs_target + ): + # Copy scenario 1h + source = fs_bulk_operations_scenario_0 + + target = fs_target + fs.mkdir(target) + + for target_slash in [False, True]: + t = fs_join(target, "newdir") + if target_slash: + t += "/" + + # Without recursive + fs.cp(fs_join(source, "subdir", "*"), t) + assert fs.isdir(fs_join(target, "newdir")) + assert fs.isfile(fs_join(target, "newdir", "subfile1")) + assert fs.isfile(fs_join(target, "newdir", "subfile2")) + assert not fs.exists(fs_join(target, "newdir", "nesteddir")) + assert not fs.exists(fs_join(target, "newdir", "nesteddir", "nestedfile")) + assert not fs.exists(fs_join(target, "subdir")) + assert not fs.exists(fs_join(target, "newdir", "subdir")) + + fs.rm(fs_join(target, "newdir"), recursive=True) + assert not fs.exists(fs_join(target, "newdir")) + + # With recursive + for glob, recursive in zip(["*", "**"], [True, False]): + fs.cp(fs_join(source, "subdir", glob), t, recursive=recursive) + assert fs.isdir(fs_join(target, "newdir")) + assert fs.isfile(fs_join(target, "newdir", "subfile1")) + assert fs.isfile(fs_join(target, "newdir", "subfile2")) + assert fs.isdir(fs_join(target, "newdir", "nesteddir")) + assert fs.isfile(fs_join(target, "newdir", "nesteddir", "nestedfile")) + assert not fs.exists(fs_join(target, "subdir")) + assert not fs.exists(fs_join(target, "newdir", "subdir")) + + fs.rm(fs_join(target, "newdir"), recursive=True) + assert not fs.exists(fs_join(target, "newdir")) + + # Limit recursive by maxdepth + fs.cp( + fs_join(source, "subdir", glob), t, recursive=recursive, maxdepth=1 + ) + assert fs.isdir(fs_join(target, "newdir")) + assert fs.isfile(fs_join(target, "newdir", "subfile1")) + assert fs.isfile(fs_join(target, "newdir", "subfile2")) + assert not fs.exists(fs_join(target, "newdir", "nesteddir")) + assert not fs.exists(fs_join(target, "subdir")) + assert not fs.exists(fs_join(target, "newdir", "subdir")) + + fs.rm(fs_join(target, "newdir"), recursive=True) + assert not fs.exists(fs_join(target, "newdir")) + + @pytest.mark.parametrize( + GLOB_EDGE_CASES_TESTS["argnames"], + GLOB_EDGE_CASES_TESTS["argvalues"], + ) + def test_copy_glob_edge_cases( + self, + path, + recursive, + maxdepth, + expected, + fs, + fs_join, + fs_glob_edge_cases_files, + fs_target, + fs_sanitize_path, + ): + # Copy scenario 1g + source = fs_glob_edge_cases_files + + target = fs_target + + for new_dir, target_slash in product([True, False], [True, False]): + fs.mkdir(target) + + t = fs_join(target, "newdir") if new_dir else target + t = t + "/" if target_slash else t + + fs.copy(fs_join(source, path), t, recursive=recursive, maxdepth=maxdepth) + + output = fs.find(target) + if new_dir: + prefixed_expected = [ + fs_sanitize_path(fs_join(target, "newdir", p)) for p in expected + ] + else: + prefixed_expected = [ + fs_sanitize_path(fs_join(target, p)) for p in expected + ] + assert sorted(output) == sorted(prefixed_expected) + + try: + fs.rm(target, recursive=True) + except FileNotFoundError: + pass + + def test_copy_list_of_files_to_existing_directory( + self, + fs, + fs_join, + fs_bulk_operations_scenario_0, + fs_target, + supports_empty_directories, + ): + # Copy scenario 2a + source = fs_bulk_operations_scenario_0 + + target = fs_target + fs.mkdir(target) + if not supports_empty_directories: + # Force target directory to exist by adding a dummy file + dummy = fs_join(target, "dummy") + fs.touch(dummy) + assert fs.isdir(target) + + source_files = [ + fs_join(source, "file1"), + fs_join(source, "file2"), + fs_join(source, "subdir", "subfile1"), + ] + + for target_slash in [False, True]: + t = target + "/" if target_slash else target + + fs.cp(source_files, t) + assert fs.isfile(fs_join(target, "file1")) + assert fs.isfile(fs_join(target, "file2")) + assert fs.isfile(fs_join(target, "subfile1")) + + fs.rm( + [ + fs_join(target, "file1"), + fs_join(target, "file2"), + fs_join(target, "subfile1"), + ], + recursive=True, + ) + assert fs.ls(target, detail=False) == ( + [] if supports_empty_directories else [dummy] + ) + + def test_copy_list_of_files_to_new_directory( + self, fs, fs_join, fs_bulk_operations_scenario_0, fs_target + ): + # Copy scenario 2b + source = fs_bulk_operations_scenario_0 + + target = fs_target + fs.mkdir(target) + + source_files = [ + fs_join(source, "file1"), + fs_join(source, "file2"), + fs_join(source, "subdir", "subfile1"), + ] + + fs.cp(source_files, fs_join(target, "newdir") + "/") # Note trailing slash + assert fs.isdir(fs_join(target, "newdir")) + assert fs.isfile(fs_join(target, "newdir", "file1")) + assert fs.isfile(fs_join(target, "newdir", "file2")) + assert fs.isfile(fs_join(target, "newdir", "subfile1")) + + def test_copy_two_files_new_directory( + self, fs, fs_join, fs_bulk_operations_scenario_0, fs_target + ): + # This is a duplicate of test_copy_list_of_files_to_new_directory and + # can eventually be removed. + source = fs_bulk_operations_scenario_0 + + target = fs_target + assert not fs.exists(target) + fs.cp([fs_join(source, "file1"), fs_join(source, "file2")], target) + + assert fs.isdir(target) + assert fs.isfile(fs_join(target, "file1")) + assert fs.isfile(fs_join(target, "file2")) + + def test_copy_directory_without_files_with_same_name_prefix( + self, + fs, + fs_join, + fs_target, + fs_dir_and_file_with_same_name_prefix, + supports_empty_directories, + ): + # Create the test dirs + source = fs_dir_and_file_with_same_name_prefix + target = fs_target + + # Test without glob + fs.cp(fs_join(source, "subdir"), target, recursive=True) + + assert fs.isfile(fs_join(target, "subfile.txt")) + assert not fs.isfile(fs_join(target, "subdir.txt")) + + fs.rm([fs_join(target, "subfile.txt")]) + if supports_empty_directories: + assert fs.ls(target) == [] + else: + assert not fs.exists(target) + + # Test with glob + fs.cp(fs_join(source, "subdir*"), target, recursive=True) + + assert fs.isdir(fs_join(target, "subdir")) + assert fs.isfile(fs_join(target, "subdir", "subfile.txt")) + assert fs.isfile(fs_join(target, "subdir.txt")) + + def test_copy_with_source_and_destination_as_list( + self, fs, fs_target, fs_join, fs_10_files_with_hashed_names + ): + # Create the test dir + source = fs_10_files_with_hashed_names + target = fs_target + + # Create list of files for source and destination + source_files = [] + destination_files = [] + for i in range(10): + hashed_i = md5(str(i).encode("utf-8")).hexdigest() + source_files.append(fs_join(source, f"{hashed_i}.txt")) + destination_files.append(fs_join(target, f"{hashed_i}.txt")) + + # Copy and assert order was kept + fs.copy(path1=source_files, path2=destination_files) + + for i in range(10): + file_content = fs.cat(destination_files[i]).decode("utf-8") + assert file_content == str(i) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/tests/abstract/get.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/tests/abstract/get.py new file mode 100644 index 0000000000000000000000000000000000000000..851ab81ee581e74cac41c64c83ef0af75826d6b0 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/tests/abstract/get.py @@ -0,0 +1,587 @@ +from hashlib import md5 +from itertools import product + +import pytest + +from fsspec.implementations.local import make_path_posix +from fsspec.tests.abstract.common import GLOB_EDGE_CASES_TESTS + + +class AbstractGetTests: + def test_get_file_to_existing_directory( + self, + fs, + fs_join, + fs_bulk_operations_scenario_0, + local_fs, + local_join, + local_target, + ): + # Copy scenario 1a + source = fs_bulk_operations_scenario_0 + + target = local_target + local_fs.mkdir(target) + assert local_fs.isdir(target) + + target_file2 = local_join(target, "file2") + target_subfile1 = local_join(target, "subfile1") + + # Copy from source directory + fs.get(fs_join(source, "file2"), target) + assert local_fs.isfile(target_file2) + + # Copy from sub directory + fs.get(fs_join(source, "subdir", "subfile1"), target) + assert local_fs.isfile(target_subfile1) + + # Remove copied files + local_fs.rm([target_file2, target_subfile1]) + assert not local_fs.exists(target_file2) + assert not local_fs.exists(target_subfile1) + + # Repeat with trailing slash on target + fs.get(fs_join(source, "file2"), target + "/") + assert local_fs.isdir(target) + assert local_fs.isfile(target_file2) + + fs.get(fs_join(source, "subdir", "subfile1"), target + "/") + assert local_fs.isfile(target_subfile1) + + def test_get_file_to_new_directory( + self, + fs, + fs_join, + fs_bulk_operations_scenario_0, + local_fs, + local_join, + local_target, + ): + # Copy scenario 1b + source = fs_bulk_operations_scenario_0 + + target = local_target + local_fs.mkdir(target) + + fs.get( + fs_join(source, "subdir", "subfile1"), local_join(target, "newdir/") + ) # Note trailing slash + + assert local_fs.isdir(target) + assert local_fs.isdir(local_join(target, "newdir")) + assert local_fs.isfile(local_join(target, "newdir", "subfile1")) + + def test_get_file_to_file_in_existing_directory( + self, + fs, + fs_join, + fs_bulk_operations_scenario_0, + local_fs, + local_join, + local_target, + ): + # Copy scenario 1c + source = fs_bulk_operations_scenario_0 + + target = local_target + local_fs.mkdir(target) + + fs.get(fs_join(source, "subdir", "subfile1"), local_join(target, "newfile")) + assert local_fs.isfile(local_join(target, "newfile")) + + def test_get_file_to_file_in_new_directory( + self, + fs, + fs_join, + fs_bulk_operations_scenario_0, + local_fs, + local_join, + local_target, + ): + # Copy scenario 1d + source = fs_bulk_operations_scenario_0 + + target = local_target + local_fs.mkdir(target) + + fs.get( + fs_join(source, "subdir", "subfile1"), + local_join(target, "newdir", "newfile"), + ) + assert local_fs.isdir(local_join(target, "newdir")) + assert local_fs.isfile(local_join(target, "newdir", "newfile")) + + def test_get_directory_to_existing_directory( + self, + fs, + fs_join, + fs_bulk_operations_scenario_0, + local_fs, + local_join, + local_target, + ): + # Copy scenario 1e + source = fs_bulk_operations_scenario_0 + + target = local_target + local_fs.mkdir(target) + assert local_fs.isdir(target) + + for source_slash, target_slash in zip([False, True], [False, True]): + s = fs_join(source, "subdir") + if source_slash: + s += "/" + t = target + "/" if target_slash else target + + # Without recursive does nothing + fs.get(s, t) + assert local_fs.ls(target) == [] + + # With recursive + fs.get(s, t, recursive=True) + if source_slash: + assert local_fs.isfile(local_join(target, "subfile1")) + assert local_fs.isfile(local_join(target, "subfile2")) + assert local_fs.isdir(local_join(target, "nesteddir")) + assert local_fs.isfile(local_join(target, "nesteddir", "nestedfile")) + assert not local_fs.exists(local_join(target, "subdir")) + + local_fs.rm( + [ + local_join(target, "subfile1"), + local_join(target, "subfile2"), + local_join(target, "nesteddir"), + ], + recursive=True, + ) + else: + assert local_fs.isdir(local_join(target, "subdir")) + assert local_fs.isfile(local_join(target, "subdir", "subfile1")) + assert local_fs.isfile(local_join(target, "subdir", "subfile2")) + assert local_fs.isdir(local_join(target, "subdir", "nesteddir")) + assert local_fs.isfile( + local_join(target, "subdir", "nesteddir", "nestedfile") + ) + + local_fs.rm(local_join(target, "subdir"), recursive=True) + assert local_fs.ls(target) == [] + + # Limit recursive by maxdepth + fs.get(s, t, recursive=True, maxdepth=1) + if source_slash: + assert local_fs.isfile(local_join(target, "subfile1")) + assert local_fs.isfile(local_join(target, "subfile2")) + assert not local_fs.exists(local_join(target, "nesteddir")) + assert not local_fs.exists(local_join(target, "subdir")) + + local_fs.rm( + [ + local_join(target, "subfile1"), + local_join(target, "subfile2"), + ], + recursive=True, + ) + else: + assert local_fs.isdir(local_join(target, "subdir")) + assert local_fs.isfile(local_join(target, "subdir", "subfile1")) + assert local_fs.isfile(local_join(target, "subdir", "subfile2")) + assert not local_fs.exists(local_join(target, "subdir", "nesteddir")) + + local_fs.rm(local_join(target, "subdir"), recursive=True) + assert local_fs.ls(target) == [] + + def test_get_directory_to_new_directory( + self, + fs, + fs_join, + fs_bulk_operations_scenario_0, + local_fs, + local_join, + local_target, + ): + # Copy scenario 1f + source = fs_bulk_operations_scenario_0 + + target = local_target + local_fs.mkdir(target) + + for source_slash, target_slash in zip([False, True], [False, True]): + s = fs_join(source, "subdir") + if source_slash: + s += "/" + t = local_join(target, "newdir") + if target_slash: + t += "/" + + # Without recursive does nothing + fs.get(s, t) + assert local_fs.ls(target) == [] + + # With recursive + fs.get(s, t, recursive=True) + assert local_fs.isdir(local_join(target, "newdir")) + assert local_fs.isfile(local_join(target, "newdir", "subfile1")) + assert local_fs.isfile(local_join(target, "newdir", "subfile2")) + assert local_fs.isdir(local_join(target, "newdir", "nesteddir")) + assert local_fs.isfile( + local_join(target, "newdir", "nesteddir", "nestedfile") + ) + assert not local_fs.exists(local_join(target, "subdir")) + + local_fs.rm(local_join(target, "newdir"), recursive=True) + assert local_fs.ls(target) == [] + + # Limit recursive by maxdepth + fs.get(s, t, recursive=True, maxdepth=1) + assert local_fs.isdir(local_join(target, "newdir")) + assert local_fs.isfile(local_join(target, "newdir", "subfile1")) + assert local_fs.isfile(local_join(target, "newdir", "subfile2")) + assert not local_fs.exists(local_join(target, "newdir", "nesteddir")) + assert not local_fs.exists(local_join(target, "subdir")) + + local_fs.rm(local_join(target, "newdir"), recursive=True) + assert not local_fs.exists(local_join(target, "newdir")) + + def test_get_glob_to_existing_directory( + self, + fs, + fs_join, + fs_bulk_operations_scenario_0, + local_fs, + local_join, + local_target, + ): + # Copy scenario 1g + source = fs_bulk_operations_scenario_0 + + target = local_target + local_fs.mkdir(target) + + for target_slash in [False, True]: + t = target + "/" if target_slash else target + + # Without recursive + fs.get(fs_join(source, "subdir", "*"), t) + assert local_fs.isfile(local_join(target, "subfile1")) + assert local_fs.isfile(local_join(target, "subfile2")) + assert not local_fs.isdir(local_join(target, "nesteddir")) + assert not local_fs.exists(local_join(target, "nesteddir", "nestedfile")) + assert not local_fs.exists(local_join(target, "subdir")) + + local_fs.rm( + [ + local_join(target, "subfile1"), + local_join(target, "subfile2"), + ], + recursive=True, + ) + assert local_fs.ls(target) == [] + + # With recursive + for glob, recursive in zip(["*", "**"], [True, False]): + fs.get(fs_join(source, "subdir", glob), t, recursive=recursive) + assert local_fs.isfile(local_join(target, "subfile1")) + assert local_fs.isfile(local_join(target, "subfile2")) + assert local_fs.isdir(local_join(target, "nesteddir")) + assert local_fs.isfile(local_join(target, "nesteddir", "nestedfile")) + assert not local_fs.exists(local_join(target, "subdir")) + + local_fs.rm( + [ + local_join(target, "subfile1"), + local_join(target, "subfile2"), + local_join(target, "nesteddir"), + ], + recursive=True, + ) + assert local_fs.ls(target) == [] + + # Limit recursive by maxdepth + fs.get( + fs_join(source, "subdir", glob), t, recursive=recursive, maxdepth=1 + ) + assert local_fs.isfile(local_join(target, "subfile1")) + assert local_fs.isfile(local_join(target, "subfile2")) + assert not local_fs.exists(local_join(target, "nesteddir")) + assert not local_fs.exists(local_join(target, "subdir")) + + local_fs.rm( + [ + local_join(target, "subfile1"), + local_join(target, "subfile2"), + ], + recursive=True, + ) + assert local_fs.ls(target) == [] + + def test_get_glob_to_new_directory( + self, + fs, + fs_join, + fs_bulk_operations_scenario_0, + local_fs, + local_join, + local_target, + ): + # Copy scenario 1h + source = fs_bulk_operations_scenario_0 + + target = local_target + local_fs.mkdir(target) + + for target_slash in [False, True]: + t = fs_join(target, "newdir") + if target_slash: + t += "/" + + # Without recursive + fs.get(fs_join(source, "subdir", "*"), t) + assert local_fs.isdir(local_join(target, "newdir")) + assert local_fs.isfile(local_join(target, "newdir", "subfile1")) + assert local_fs.isfile(local_join(target, "newdir", "subfile2")) + assert not local_fs.exists(local_join(target, "newdir", "nesteddir")) + assert not local_fs.exists( + local_join(target, "newdir", "nesteddir", "nestedfile") + ) + assert not local_fs.exists(local_join(target, "subdir")) + assert not local_fs.exists(local_join(target, "newdir", "subdir")) + + local_fs.rm(local_join(target, "newdir"), recursive=True) + assert local_fs.ls(target) == [] + + # With recursive + for glob, recursive in zip(["*", "**"], [True, False]): + fs.get(fs_join(source, "subdir", glob), t, recursive=recursive) + assert local_fs.isdir(local_join(target, "newdir")) + assert local_fs.isfile(local_join(target, "newdir", "subfile1")) + assert local_fs.isfile(local_join(target, "newdir", "subfile2")) + assert local_fs.isdir(local_join(target, "newdir", "nesteddir")) + assert local_fs.isfile( + local_join(target, "newdir", "nesteddir", "nestedfile") + ) + assert not local_fs.exists(local_join(target, "subdir")) + assert not local_fs.exists(local_join(target, "newdir", "subdir")) + + local_fs.rm(local_join(target, "newdir"), recursive=True) + assert not local_fs.exists(local_join(target, "newdir")) + + # Limit recursive by maxdepth + fs.get( + fs_join(source, "subdir", glob), t, recursive=recursive, maxdepth=1 + ) + assert local_fs.isdir(local_join(target, "newdir")) + assert local_fs.isfile(local_join(target, "newdir", "subfile1")) + assert local_fs.isfile(local_join(target, "newdir", "subfile2")) + assert not local_fs.exists(local_join(target, "newdir", "nesteddir")) + assert not local_fs.exists(local_join(target, "subdir")) + assert not local_fs.exists(local_join(target, "newdir", "subdir")) + + local_fs.rm(local_fs.ls(target, detail=False), recursive=True) + assert not local_fs.exists(local_join(target, "newdir")) + + @pytest.mark.parametrize( + GLOB_EDGE_CASES_TESTS["argnames"], + GLOB_EDGE_CASES_TESTS["argvalues"], + ) + def test_get_glob_edge_cases( + self, + path, + recursive, + maxdepth, + expected, + fs, + fs_join, + fs_glob_edge_cases_files, + local_fs, + local_join, + local_target, + ): + # Copy scenario 1g + source = fs_glob_edge_cases_files + + target = local_target + + for new_dir, target_slash in product([True, False], [True, False]): + local_fs.mkdir(target) + + t = local_join(target, "newdir") if new_dir else target + t = t + "/" if target_slash else t + + fs.get(fs_join(source, path), t, recursive=recursive, maxdepth=maxdepth) + + output = local_fs.find(target) + if new_dir: + prefixed_expected = [ + make_path_posix(local_join(target, "newdir", p)) for p in expected + ] + else: + prefixed_expected = [ + make_path_posix(local_join(target, p)) for p in expected + ] + assert sorted(output) == sorted(prefixed_expected) + + try: + local_fs.rm(target, recursive=True) + except FileNotFoundError: + pass + + def test_get_list_of_files_to_existing_directory( + self, + fs, + fs_join, + fs_bulk_operations_scenario_0, + local_fs, + local_join, + local_target, + ): + # Copy scenario 2a + source = fs_bulk_operations_scenario_0 + + target = local_target + local_fs.mkdir(target) + + source_files = [ + fs_join(source, "file1"), + fs_join(source, "file2"), + fs_join(source, "subdir", "subfile1"), + ] + + for target_slash in [False, True]: + t = target + "/" if target_slash else target + + fs.get(source_files, t) + assert local_fs.isfile(local_join(target, "file1")) + assert local_fs.isfile(local_join(target, "file2")) + assert local_fs.isfile(local_join(target, "subfile1")) + + local_fs.rm( + [ + local_join(target, "file1"), + local_join(target, "file2"), + local_join(target, "subfile1"), + ], + recursive=True, + ) + assert local_fs.ls(target) == [] + + def test_get_list_of_files_to_new_directory( + self, + fs, + fs_join, + fs_bulk_operations_scenario_0, + local_fs, + local_join, + local_target, + ): + # Copy scenario 2b + source = fs_bulk_operations_scenario_0 + + target = local_target + local_fs.mkdir(target) + + source_files = [ + fs_join(source, "file1"), + fs_join(source, "file2"), + fs_join(source, "subdir", "subfile1"), + ] + + fs.get(source_files, local_join(target, "newdir") + "/") # Note trailing slash + assert local_fs.isdir(local_join(target, "newdir")) + assert local_fs.isfile(local_join(target, "newdir", "file1")) + assert local_fs.isfile(local_join(target, "newdir", "file2")) + assert local_fs.isfile(local_join(target, "newdir", "subfile1")) + + def test_get_directory_recursive( + self, fs, fs_join, fs_path, local_fs, local_join, local_target + ): + # https://github.com/fsspec/filesystem_spec/issues/1062 + # Recursive cp/get/put of source directory into non-existent target directory. + src = fs_join(fs_path, "src") + src_file = fs_join(src, "file") + fs.mkdir(src) + fs.touch(src_file) + + target = local_target + + # get without slash + assert not local_fs.exists(target) + for loop in range(2): + fs.get(src, target, recursive=True) + assert local_fs.isdir(target) + + if loop == 0: + assert local_fs.isfile(local_join(target, "file")) + assert not local_fs.exists(local_join(target, "src")) + else: + assert local_fs.isfile(local_join(target, "file")) + assert local_fs.isdir(local_join(target, "src")) + assert local_fs.isfile(local_join(target, "src", "file")) + + local_fs.rm(target, recursive=True) + + # get with slash + assert not local_fs.exists(target) + for loop in range(2): + fs.get(src + "/", target, recursive=True) + assert local_fs.isdir(target) + assert local_fs.isfile(local_join(target, "file")) + assert not local_fs.exists(local_join(target, "src")) + + def test_get_directory_without_files_with_same_name_prefix( + self, + fs, + fs_join, + local_fs, + local_join, + local_target, + fs_dir_and_file_with_same_name_prefix, + ): + # Create the test dirs + source = fs_dir_and_file_with_same_name_prefix + target = local_target + + # Test without glob + fs.get(fs_join(source, "subdir"), target, recursive=True) + + assert local_fs.isfile(local_join(target, "subfile.txt")) + assert not local_fs.isfile(local_join(target, "subdir.txt")) + + local_fs.rm([local_join(target, "subfile.txt")]) + assert local_fs.ls(target) == [] + + # Test with glob + fs.get(fs_join(source, "subdir*"), target, recursive=True) + + assert local_fs.isdir(local_join(target, "subdir")) + assert local_fs.isfile(local_join(target, "subdir", "subfile.txt")) + assert local_fs.isfile(local_join(target, "subdir.txt")) + + def test_get_with_source_and_destination_as_list( + self, + fs, + fs_join, + local_fs, + local_join, + local_target, + fs_10_files_with_hashed_names, + ): + # Create the test dir + source = fs_10_files_with_hashed_names + target = local_target + + # Create list of files for source and destination + source_files = [] + destination_files = [] + for i in range(10): + hashed_i = md5(str(i).encode("utf-8")).hexdigest() + source_files.append(fs_join(source, f"{hashed_i}.txt")) + destination_files.append( + make_path_posix(local_join(target, f"{hashed_i}.txt")) + ) + + # Copy and assert order was kept + fs.get(rpath=source_files, lpath=destination_files) + + for i in range(10): + file_content = local_fs.cat(destination_files[i]).decode("utf-8") + assert file_content == str(i) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/tests/abstract/mv.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/tests/abstract/mv.py new file mode 100644 index 0000000000000000000000000000000000000000..39f6caa3de815e024fa84de2acecc986c823ed29 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/tests/abstract/mv.py @@ -0,0 +1,57 @@ +import os + +import pytest + +import fsspec + + +def test_move_raises_error_with_tmpdir(tmpdir): + # Create a file in the temporary directory + source = tmpdir.join("source_file.txt") + source.write("content") + + # Define a destination that simulates a protected or invalid path + destination = tmpdir.join("non_existent_directory/destination_file.txt") + + # Instantiate the filesystem (assuming the local file system interface) + fs = fsspec.filesystem("file") + + # Use the actual file paths as string + with pytest.raises(FileNotFoundError): + fs.mv(str(source), str(destination)) + + +@pytest.mark.parametrize("recursive", (True, False)) +def test_move_raises_error_with_tmpdir_permission(recursive, tmpdir): + # Create a file in the temporary directory + source = tmpdir.join("source_file.txt") + source.write("content") + + # Create a protected directory (non-writable) + protected_dir = tmpdir.mkdir("protected_directory") + protected_path = str(protected_dir) + + # Set the directory to read-only + if os.name == "nt": + os.system(f'icacls "{protected_path}" /deny Everyone:(W)') + else: + os.chmod(protected_path, 0o555) # Sets the directory to read-only + + # Define a destination inside the protected directory + destination = protected_dir.join("destination_file.txt") + + # Instantiate the filesystem (assuming the local file system interface) + fs = fsspec.filesystem("file") + + # Try to move the file to the read-only directory, expecting a permission error + with pytest.raises(PermissionError): + fs.mv(str(source), str(destination), recursive=recursive) + + # Assert the file was not created in the destination + assert not os.path.exists(destination) + + # Cleanup: Restore permissions so the directory can be cleaned up + if os.name == "nt": + os.system(f'icacls "{protected_path}" /remove:d Everyone') + else: + os.chmod(protected_path, 0o755) # Restore write permission for cleanup diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/tests/abstract/open.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/tests/abstract/open.py new file mode 100644 index 0000000000000000000000000000000000000000..bb75ea852276fb8d834345883813b8e27a0ae24c --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/tests/abstract/open.py @@ -0,0 +1,11 @@ +import pytest + + +class AbstractOpenTests: + def test_open_exclusive(self, fs, fs_target): + with fs.open(fs_target, "wb") as f: + f.write(b"data") + with fs.open(fs_target, "rb") as f: + assert f.read() == b"data" + with pytest.raises(FileExistsError): + fs.open(fs_target, "xb") diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/tests/abstract/pipe.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/tests/abstract/pipe.py new file mode 100644 index 0000000000000000000000000000000000000000..8ecca96e9d23ff268a253c48269d5cca451ea270 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/tests/abstract/pipe.py @@ -0,0 +1,11 @@ +import pytest + + +class AbstractPipeTests: + def test_pipe_exclusive(self, fs, fs_target): + fs.pipe_file(fs_target, b"data") + assert fs.cat_file(fs_target) == b"data" + with pytest.raises(FileExistsError): + fs.pipe_file(fs_target, b"data", mode="create") + fs.pipe_file(fs_target, b"new data", mode="overwrite") + assert fs.cat_file(fs_target) == b"new data" diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/tests/abstract/put.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/tests/abstract/put.py new file mode 100644 index 0000000000000000000000000000000000000000..9fc349977f0384d9fc86126498be5c6ad99a21d3 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/tests/abstract/put.py @@ -0,0 +1,591 @@ +from hashlib import md5 +from itertools import product + +import pytest + +from fsspec.tests.abstract.common import GLOB_EDGE_CASES_TESTS + + +class AbstractPutTests: + def test_put_file_to_existing_directory( + self, + fs, + fs_join, + fs_target, + local_join, + local_bulk_operations_scenario_0, + supports_empty_directories, + ): + # Copy scenario 1a + source = local_bulk_operations_scenario_0 + + target = fs_target + fs.mkdir(target) + if not supports_empty_directories: + # Force target directory to exist by adding a dummy file + fs.touch(fs_join(target, "dummy")) + assert fs.isdir(target) + + target_file2 = fs_join(target, "file2") + target_subfile1 = fs_join(target, "subfile1") + + # Copy from source directory + fs.put(local_join(source, "file2"), target) + assert fs.isfile(target_file2) + + # Copy from sub directory + fs.put(local_join(source, "subdir", "subfile1"), target) + assert fs.isfile(target_subfile1) + + # Remove copied files + fs.rm([target_file2, target_subfile1]) + assert not fs.exists(target_file2) + assert not fs.exists(target_subfile1) + + # Repeat with trailing slash on target + fs.put(local_join(source, "file2"), target + "/") + assert fs.isdir(target) + assert fs.isfile(target_file2) + + fs.put(local_join(source, "subdir", "subfile1"), target + "/") + assert fs.isfile(target_subfile1) + + def test_put_file_to_new_directory( + self, fs, fs_join, fs_target, local_join, local_bulk_operations_scenario_0 + ): + # Copy scenario 1b + source = local_bulk_operations_scenario_0 + + target = fs_target + fs.mkdir(target) + + fs.put( + local_join(source, "subdir", "subfile1"), fs_join(target, "newdir/") + ) # Note trailing slash + assert fs.isdir(target) + assert fs.isdir(fs_join(target, "newdir")) + assert fs.isfile(fs_join(target, "newdir", "subfile1")) + + def test_put_file_to_file_in_existing_directory( + self, + fs, + fs_join, + fs_target, + local_join, + supports_empty_directories, + local_bulk_operations_scenario_0, + ): + # Copy scenario 1c + source = local_bulk_operations_scenario_0 + + target = fs_target + fs.mkdir(target) + if not supports_empty_directories: + # Force target directory to exist by adding a dummy file + fs.touch(fs_join(target, "dummy")) + assert fs.isdir(target) + + fs.put(local_join(source, "subdir", "subfile1"), fs_join(target, "newfile")) + assert fs.isfile(fs_join(target, "newfile")) + + def test_put_file_to_file_in_new_directory( + self, fs, fs_join, fs_target, local_join, local_bulk_operations_scenario_0 + ): + # Copy scenario 1d + source = local_bulk_operations_scenario_0 + + target = fs_target + fs.mkdir(target) + + fs.put( + local_join(source, "subdir", "subfile1"), + fs_join(target, "newdir", "newfile"), + ) + assert fs.isdir(fs_join(target, "newdir")) + assert fs.isfile(fs_join(target, "newdir", "newfile")) + + def test_put_directory_to_existing_directory( + self, + fs, + fs_join, + fs_target, + local_bulk_operations_scenario_0, + supports_empty_directories, + ): + # Copy scenario 1e + source = local_bulk_operations_scenario_0 + + target = fs_target + fs.mkdir(target) + if not supports_empty_directories: + # Force target directory to exist by adding a dummy file + dummy = fs_join(target, "dummy") + fs.touch(dummy) + assert fs.isdir(target) + + for source_slash, target_slash in zip([False, True], [False, True]): + s = fs_join(source, "subdir") + if source_slash: + s += "/" + t = target + "/" if target_slash else target + + # Without recursive does nothing + fs.put(s, t) + assert fs.ls(target, detail=False) == ( + [] if supports_empty_directories else [dummy] + ) + + # With recursive + fs.put(s, t, recursive=True) + if source_slash: + assert fs.isfile(fs_join(target, "subfile1")) + assert fs.isfile(fs_join(target, "subfile2")) + assert fs.isdir(fs_join(target, "nesteddir")) + assert fs.isfile(fs_join(target, "nesteddir", "nestedfile")) + assert not fs.exists(fs_join(target, "subdir")) + + fs.rm( + [ + fs_join(target, "subfile1"), + fs_join(target, "subfile2"), + fs_join(target, "nesteddir"), + ], + recursive=True, + ) + else: + assert fs.isdir(fs_join(target, "subdir")) + assert fs.isfile(fs_join(target, "subdir", "subfile1")) + assert fs.isfile(fs_join(target, "subdir", "subfile2")) + assert fs.isdir(fs_join(target, "subdir", "nesteddir")) + assert fs.isfile(fs_join(target, "subdir", "nesteddir", "nestedfile")) + + fs.rm(fs_join(target, "subdir"), recursive=True) + assert fs.ls(target, detail=False) == ( + [] if supports_empty_directories else [dummy] + ) + + # Limit recursive by maxdepth + fs.put(s, t, recursive=True, maxdepth=1) + if source_slash: + assert fs.isfile(fs_join(target, "subfile1")) + assert fs.isfile(fs_join(target, "subfile2")) + assert not fs.exists(fs_join(target, "nesteddir")) + assert not fs.exists(fs_join(target, "subdir")) + + fs.rm( + [ + fs_join(target, "subfile1"), + fs_join(target, "subfile2"), + ], + recursive=True, + ) + else: + assert fs.isdir(fs_join(target, "subdir")) + assert fs.isfile(fs_join(target, "subdir", "subfile1")) + assert fs.isfile(fs_join(target, "subdir", "subfile2")) + assert not fs.exists(fs_join(target, "subdir", "nesteddir")) + + fs.rm(fs_join(target, "subdir"), recursive=True) + assert fs.ls(target, detail=False) == ( + [] if supports_empty_directories else [dummy] + ) + + def test_put_directory_to_new_directory( + self, + fs, + fs_join, + fs_target, + local_bulk_operations_scenario_0, + supports_empty_directories, + ): + # Copy scenario 1f + source = local_bulk_operations_scenario_0 + + target = fs_target + fs.mkdir(target) + + for source_slash, target_slash in zip([False, True], [False, True]): + s = fs_join(source, "subdir") + if source_slash: + s += "/" + t = fs_join(target, "newdir") + if target_slash: + t += "/" + + # Without recursive does nothing + fs.put(s, t) + if supports_empty_directories: + assert fs.ls(target) == [] + else: + with pytest.raises(FileNotFoundError): + fs.ls(target) + + # With recursive + fs.put(s, t, recursive=True) + assert fs.isdir(fs_join(target, "newdir")) + assert fs.isfile(fs_join(target, "newdir", "subfile1")) + assert fs.isfile(fs_join(target, "newdir", "subfile2")) + assert fs.isdir(fs_join(target, "newdir", "nesteddir")) + assert fs.isfile(fs_join(target, "newdir", "nesteddir", "nestedfile")) + assert not fs.exists(fs_join(target, "subdir")) + + fs.rm(fs_join(target, "newdir"), recursive=True) + assert not fs.exists(fs_join(target, "newdir")) + + # Limit recursive by maxdepth + fs.put(s, t, recursive=True, maxdepth=1) + assert fs.isdir(fs_join(target, "newdir")) + assert fs.isfile(fs_join(target, "newdir", "subfile1")) + assert fs.isfile(fs_join(target, "newdir", "subfile2")) + assert not fs.exists(fs_join(target, "newdir", "nesteddir")) + assert not fs.exists(fs_join(target, "subdir")) + + fs.rm(fs_join(target, "newdir"), recursive=True) + assert not fs.exists(fs_join(target, "newdir")) + + def test_put_glob_to_existing_directory( + self, + fs, + fs_join, + fs_target, + local_join, + supports_empty_directories, + local_bulk_operations_scenario_0, + ): + # Copy scenario 1g + source = local_bulk_operations_scenario_0 + + target = fs_target + fs.mkdir(target) + if not supports_empty_directories: + # Force target directory to exist by adding a dummy file + dummy = fs_join(target, "dummy") + fs.touch(dummy) + assert fs.isdir(target) + + for target_slash in [False, True]: + t = target + "/" if target_slash else target + + # Without recursive + fs.put(local_join(source, "subdir", "*"), t) + assert fs.isfile(fs_join(target, "subfile1")) + assert fs.isfile(fs_join(target, "subfile2")) + assert not fs.isdir(fs_join(target, "nesteddir")) + assert not fs.exists(fs_join(target, "nesteddir", "nestedfile")) + assert not fs.exists(fs_join(target, "subdir")) + + fs.rm( + [ + fs_join(target, "subfile1"), + fs_join(target, "subfile2"), + ], + recursive=True, + ) + assert fs.ls(target, detail=False) == ( + [] if supports_empty_directories else [dummy] + ) + + # With recursive + for glob, recursive in zip(["*", "**"], [True, False]): + fs.put(local_join(source, "subdir", glob), t, recursive=recursive) + assert fs.isfile(fs_join(target, "subfile1")) + assert fs.isfile(fs_join(target, "subfile2")) + assert fs.isdir(fs_join(target, "nesteddir")) + assert fs.isfile(fs_join(target, "nesteddir", "nestedfile")) + assert not fs.exists(fs_join(target, "subdir")) + + fs.rm( + [ + fs_join(target, "subfile1"), + fs_join(target, "subfile2"), + fs_join(target, "nesteddir"), + ], + recursive=True, + ) + assert fs.ls(target, detail=False) == ( + [] if supports_empty_directories else [dummy] + ) + + # Limit recursive by maxdepth + fs.put( + local_join(source, "subdir", glob), + t, + recursive=recursive, + maxdepth=1, + ) + assert fs.isfile(fs_join(target, "subfile1")) + assert fs.isfile(fs_join(target, "subfile2")) + assert not fs.exists(fs_join(target, "nesteddir")) + assert not fs.exists(fs_join(target, "subdir")) + + fs.rm( + [ + fs_join(target, "subfile1"), + fs_join(target, "subfile2"), + ], + recursive=True, + ) + assert fs.ls(target, detail=False) == ( + [] if supports_empty_directories else [dummy] + ) + + def test_put_glob_to_new_directory( + self, fs, fs_join, fs_target, local_join, local_bulk_operations_scenario_0 + ): + # Copy scenario 1h + source = local_bulk_operations_scenario_0 + + target = fs_target + fs.mkdir(target) + + for target_slash in [False, True]: + t = fs_join(target, "newdir") + if target_slash: + t += "/" + + # Without recursive + fs.put(local_join(source, "subdir", "*"), t) + assert fs.isdir(fs_join(target, "newdir")) + assert fs.isfile(fs_join(target, "newdir", "subfile1")) + assert fs.isfile(fs_join(target, "newdir", "subfile2")) + assert not fs.exists(fs_join(target, "newdir", "nesteddir")) + assert not fs.exists(fs_join(target, "newdir", "nesteddir", "nestedfile")) + assert not fs.exists(fs_join(target, "subdir")) + assert not fs.exists(fs_join(target, "newdir", "subdir")) + + fs.rm(fs_join(target, "newdir"), recursive=True) + assert not fs.exists(fs_join(target, "newdir")) + + # With recursive + for glob, recursive in zip(["*", "**"], [True, False]): + fs.put(local_join(source, "subdir", glob), t, recursive=recursive) + assert fs.isdir(fs_join(target, "newdir")) + assert fs.isfile(fs_join(target, "newdir", "subfile1")) + assert fs.isfile(fs_join(target, "newdir", "subfile2")) + assert fs.isdir(fs_join(target, "newdir", "nesteddir")) + assert fs.isfile(fs_join(target, "newdir", "nesteddir", "nestedfile")) + assert not fs.exists(fs_join(target, "subdir")) + assert not fs.exists(fs_join(target, "newdir", "subdir")) + + fs.rm(fs_join(target, "newdir"), recursive=True) + assert not fs.exists(fs_join(target, "newdir")) + + # Limit recursive by maxdepth + fs.put( + local_join(source, "subdir", glob), + t, + recursive=recursive, + maxdepth=1, + ) + assert fs.isdir(fs_join(target, "newdir")) + assert fs.isfile(fs_join(target, "newdir", "subfile1")) + assert fs.isfile(fs_join(target, "newdir", "subfile2")) + assert not fs.exists(fs_join(target, "newdir", "nesteddir")) + assert not fs.exists(fs_join(target, "subdir")) + assert not fs.exists(fs_join(target, "newdir", "subdir")) + + fs.rm(fs_join(target, "newdir"), recursive=True) + assert not fs.exists(fs_join(target, "newdir")) + + @pytest.mark.parametrize( + GLOB_EDGE_CASES_TESTS["argnames"], + GLOB_EDGE_CASES_TESTS["argvalues"], + ) + def test_put_glob_edge_cases( + self, + path, + recursive, + maxdepth, + expected, + fs, + fs_join, + fs_target, + local_glob_edge_cases_files, + local_join, + fs_sanitize_path, + ): + # Copy scenario 1g + source = local_glob_edge_cases_files + + target = fs_target + + for new_dir, target_slash in product([True, False], [True, False]): + fs.mkdir(target) + + t = fs_join(target, "newdir") if new_dir else target + t = t + "/" if target_slash else t + + fs.put(local_join(source, path), t, recursive=recursive, maxdepth=maxdepth) + + output = fs.find(target) + if new_dir: + prefixed_expected = [ + fs_sanitize_path(fs_join(target, "newdir", p)) for p in expected + ] + else: + prefixed_expected = [ + fs_sanitize_path(fs_join(target, p)) for p in expected + ] + assert sorted(output) == sorted(prefixed_expected) + + try: + fs.rm(target, recursive=True) + except FileNotFoundError: + pass + + def test_put_list_of_files_to_existing_directory( + self, + fs, + fs_join, + fs_target, + local_join, + local_bulk_operations_scenario_0, + supports_empty_directories, + ): + # Copy scenario 2a + source = local_bulk_operations_scenario_0 + + target = fs_target + fs.mkdir(target) + if not supports_empty_directories: + # Force target directory to exist by adding a dummy file + dummy = fs_join(target, "dummy") + fs.touch(dummy) + assert fs.isdir(target) + + source_files = [ + local_join(source, "file1"), + local_join(source, "file2"), + local_join(source, "subdir", "subfile1"), + ] + + for target_slash in [False, True]: + t = target + "/" if target_slash else target + + fs.put(source_files, t) + assert fs.isfile(fs_join(target, "file1")) + assert fs.isfile(fs_join(target, "file2")) + assert fs.isfile(fs_join(target, "subfile1")) + + fs.rm( + [ + fs_join(target, "file1"), + fs_join(target, "file2"), + fs_join(target, "subfile1"), + ], + recursive=True, + ) + assert fs.ls(target, detail=False) == ( + [] if supports_empty_directories else [dummy] + ) + + def test_put_list_of_files_to_new_directory( + self, fs, fs_join, fs_target, local_join, local_bulk_operations_scenario_0 + ): + # Copy scenario 2b + source = local_bulk_operations_scenario_0 + + target = fs_target + fs.mkdir(target) + + source_files = [ + local_join(source, "file1"), + local_join(source, "file2"), + local_join(source, "subdir", "subfile1"), + ] + + fs.put(source_files, fs_join(target, "newdir") + "/") # Note trailing slash + assert fs.isdir(fs_join(target, "newdir")) + assert fs.isfile(fs_join(target, "newdir", "file1")) + assert fs.isfile(fs_join(target, "newdir", "file2")) + assert fs.isfile(fs_join(target, "newdir", "subfile1")) + + def test_put_directory_recursive( + self, fs, fs_join, fs_target, local_fs, local_join, local_path + ): + # https://github.com/fsspec/filesystem_spec/issues/1062 + # Recursive cp/get/put of source directory into non-existent target directory. + src = local_join(local_path, "src") + src_file = local_join(src, "file") + local_fs.mkdir(src) + local_fs.touch(src_file) + + target = fs_target + + # put without slash + assert not fs.exists(target) + for loop in range(2): + fs.put(src, target, recursive=True) + assert fs.isdir(target) + + if loop == 0: + assert fs.isfile(fs_join(target, "file")) + assert not fs.exists(fs_join(target, "src")) + else: + assert fs.isfile(fs_join(target, "file")) + assert fs.isdir(fs_join(target, "src")) + assert fs.isfile(fs_join(target, "src", "file")) + + fs.rm(target, recursive=True) + + # put with slash + assert not fs.exists(target) + for loop in range(2): + fs.put(src + "/", target, recursive=True) + assert fs.isdir(target) + assert fs.isfile(fs_join(target, "file")) + assert not fs.exists(fs_join(target, "src")) + + def test_put_directory_without_files_with_same_name_prefix( + self, + fs, + fs_join, + fs_target, + local_join, + local_dir_and_file_with_same_name_prefix, + supports_empty_directories, + ): + # Create the test dirs + source = local_dir_and_file_with_same_name_prefix + target = fs_target + + # Test without glob + fs.put(local_join(source, "subdir"), fs_target, recursive=True) + + assert fs.isfile(fs_join(fs_target, "subfile.txt")) + assert not fs.isfile(fs_join(fs_target, "subdir.txt")) + + fs.rm([fs_join(target, "subfile.txt")]) + if supports_empty_directories: + assert fs.ls(target) == [] + else: + assert not fs.exists(target) + + # Test with glob + fs.put(local_join(source, "subdir*"), fs_target, recursive=True) + + assert fs.isdir(fs_join(fs_target, "subdir")) + assert fs.isfile(fs_join(fs_target, "subdir", "subfile.txt")) + assert fs.isfile(fs_join(fs_target, "subdir.txt")) + + def test_copy_with_source_and_destination_as_list( + self, fs, fs_target, fs_join, local_join, local_10_files_with_hashed_names + ): + # Create the test dir + source = local_10_files_with_hashed_names + target = fs_target + + # Create list of files for source and destination + source_files = [] + destination_files = [] + for i in range(10): + hashed_i = md5(str(i).encode("utf-8")).hexdigest() + source_files.append(local_join(source, f"{hashed_i}.txt")) + destination_files.append(fs_join(target, f"{hashed_i}.txt")) + + # Copy and assert order was kept + fs.put(lpath=source_files, rpath=destination_files) + + for i in range(10): + file_content = fs.cat(destination_files[i]).decode("utf-8") + assert file_content == str(i) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/transaction.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/transaction.py new file mode 100644 index 0000000000000000000000000000000000000000..77293f63ecc5f611e19d849ef236d53e9c258efc --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/transaction.py @@ -0,0 +1,90 @@ +from collections import deque + + +class Transaction: + """Filesystem transaction write context + + Gathers files for deferred commit or discard, so that several write + operations can be finalized semi-atomically. This works by having this + instance as the ``.transaction`` attribute of the given filesystem + """ + + def __init__(self, fs, **kwargs): + """ + Parameters + ---------- + fs: FileSystem instance + """ + self.fs = fs + self.files = deque() + + def __enter__(self): + self.start() + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + """End transaction and commit, if exit is not due to exception""" + # only commit if there was no exception + self.complete(commit=exc_type is None) + if self.fs: + self.fs._intrans = False + self.fs._transaction = None + self.fs = None + + def start(self): + """Start a transaction on this FileSystem""" + self.files = deque() # clean up after previous failed completions + self.fs._intrans = True + + def complete(self, commit=True): + """Finish transaction: commit or discard all deferred files""" + while self.files: + f = self.files.popleft() + if commit: + f.commit() + else: + f.discard() + self.fs._intrans = False + self.fs._transaction = None + self.fs = None + + +class FileActor: + def __init__(self): + self.files = [] + + def commit(self): + for f in self.files: + f.commit() + self.files.clear() + + def discard(self): + for f in self.files: + f.discard() + self.files.clear() + + def append(self, f): + self.files.append(f) + + +class DaskTransaction(Transaction): + def __init__(self, fs): + """ + Parameters + ---------- + fs: FileSystem instance + """ + import distributed + + super().__init__(fs) + client = distributed.default_client() + self.files = client.submit(FileActor, actor=True).result() + + def complete(self, commit=True): + """Finish transaction: commit or discard all deferred files""" + if commit: + self.files.commit().result() + else: + self.files.discard().result() + self.fs._intrans = False + self.fs = None diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/utils.py b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..7b06dd581417eca1e90a19ef25fb14dcd3cf9c9c --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/fsspec/utils.py @@ -0,0 +1,748 @@ +from __future__ import annotations + +import contextlib +import logging +import math +import os +import re +import sys +import tempfile +from collections.abc import Callable, Iterable, Iterator, Sequence +from functools import partial +from hashlib import md5 +from importlib.metadata import version +from typing import IO, TYPE_CHECKING, Any, TypeVar +from urllib.parse import urlsplit + +if TYPE_CHECKING: + import pathlib + from typing import TypeGuard + + from fsspec.spec import AbstractFileSystem + + +DEFAULT_BLOCK_SIZE = 5 * 2**20 + +T = TypeVar("T") + + +def infer_storage_options( + urlpath: str, inherit_storage_options: dict[str, Any] | None = None +) -> dict[str, Any]: + """Infer storage options from URL path and merge it with existing storage + options. + + Parameters + ---------- + urlpath: str or unicode + Either local absolute file path or URL (hdfs://namenode:8020/file.csv) + inherit_storage_options: dict (optional) + Its contents will get merged with the inferred information from the + given path + + Returns + ------- + Storage options dict. + + Examples + -------- + >>> infer_storage_options('/mnt/datasets/test.csv') # doctest: +SKIP + {"protocol": "file", "path", "/mnt/datasets/test.csv"} + >>> infer_storage_options( + ... 'hdfs://username:pwd@node:123/mnt/datasets/test.csv?q=1', + ... inherit_storage_options={'extra': 'value'}, + ... ) # doctest: +SKIP + {"protocol": "hdfs", "username": "username", "password": "pwd", + "host": "node", "port": 123, "path": "/mnt/datasets/test.csv", + "url_query": "q=1", "extra": "value"} + """ + # Handle Windows paths including disk name in this special case + if ( + re.match(r"^[a-zA-Z]:[\\/]", urlpath) + or re.match(r"^[a-zA-Z0-9]+://", urlpath) is None + ): + return {"protocol": "file", "path": urlpath} + + parsed_path = urlsplit(urlpath) + protocol = parsed_path.scheme or "file" + if parsed_path.fragment: + path = "#".join([parsed_path.path, parsed_path.fragment]) + else: + path = parsed_path.path + if protocol == "file": + # Special case parsing file protocol URL on Windows according to: + # https://msdn.microsoft.com/en-us/library/jj710207.aspx + windows_path = re.match(r"^/([a-zA-Z])[:|]([\\/].*)$", path) + if windows_path: + drive, path = windows_path.groups() + path = f"{drive}:{path}" + + if protocol in ["http", "https"]: + # for HTTP, we don't want to parse, as requests will anyway + return {"protocol": protocol, "path": urlpath} + + options: dict[str, Any] = {"protocol": protocol, "path": path} + + if parsed_path.netloc: + # Parse `hostname` from netloc manually because `parsed_path.hostname` + # lowercases the hostname which is not always desirable (e.g. in S3): + # https://github.com/dask/dask/issues/1417 + options["host"] = parsed_path.netloc.rsplit("@", 1)[-1].rsplit(":", 1)[0] + + if protocol in ("s3", "s3a", "gcs", "gs"): + options["path"] = options["host"] + options["path"] + else: + options["host"] = options["host"] + if parsed_path.port: + options["port"] = parsed_path.port + if parsed_path.username: + options["username"] = parsed_path.username + if parsed_path.password: + options["password"] = parsed_path.password + + if parsed_path.query: + options["url_query"] = parsed_path.query + if parsed_path.fragment: + options["url_fragment"] = parsed_path.fragment + + if inherit_storage_options: + update_storage_options(options, inherit_storage_options) + + return options + + +def update_storage_options( + options: dict[str, Any], inherited: dict[str, Any] | None = None +) -> None: + if not inherited: + inherited = {} + collisions = set(options) & set(inherited) + if collisions: + for collision in collisions: + if options.get(collision) != inherited.get(collision): + raise KeyError( + f"Collision between inferred and specified storage " + f"option:\n{collision}" + ) + options.update(inherited) + + +# Compression extensions registered via fsspec.compression.register_compression +compressions: dict[str, str] = {} + + +def infer_compression(filename: str) -> str | None: + """Infer compression, if available, from filename. + + Infer a named compression type, if registered and available, from filename + extension. This includes builtin (gz, bz2, zip) compressions, as well as + optional compressions. See fsspec.compression.register_compression. + """ + extension = os.path.splitext(filename)[-1].strip(".").lower() + if extension in compressions: + return compressions[extension] + return None + + +def build_name_function(max_int: float) -> Callable[[int], str]: + """Returns a function that receives a single integer + and returns it as a string padded by enough zero characters + to align with maximum possible integer + + >>> name_f = build_name_function(57) + + >>> name_f(7) + '07' + >>> name_f(31) + '31' + >>> build_name_function(1000)(42) + '0042' + >>> build_name_function(999)(42) + '042' + >>> build_name_function(0)(0) + '0' + """ + # handle corner cases max_int is 0 or exact power of 10 + max_int += 1e-8 + + pad_length = int(math.ceil(math.log10(max_int))) + + def name_function(i: int) -> str: + return str(i).zfill(pad_length) + + return name_function + + +def seek_delimiter(file: IO[bytes], delimiter: bytes, blocksize: int) -> bool: + r"""Seek current file to file start, file end, or byte after delimiter seq. + + Seeks file to next chunk delimiter, where chunks are defined on file start, + a delimiting sequence, and file end. Use file.tell() to see location afterwards. + Note that file start is a valid split, so must be at offset > 0 to seek for + delimiter. + + Parameters + ---------- + file: a file + delimiter: bytes + a delimiter like ``b'\n'`` or message sentinel, matching file .read() type + blocksize: int + Number of bytes to read from the file at once. + + + Returns + ------- + Returns True if a delimiter was found, False if at file start or end. + + """ + + if file.tell() == 0: + # beginning-of-file, return without seek + return False + + # Interface is for binary IO, with delimiter as bytes, but initialize last + # with result of file.read to preserve compatibility with text IO. + last: bytes | None = None + while True: + current = file.read(blocksize) + if not current: + # end-of-file without delimiter + return False + full = last + current if last else current + try: + if delimiter in full: + i = full.index(delimiter) + file.seek(file.tell() - (len(full) - i) + len(delimiter)) + return True + elif len(current) < blocksize: + # end-of-file without delimiter + return False + except (OSError, ValueError): + pass + last = full[-len(delimiter) :] + + +def read_block( + f: IO[bytes], + offset: int, + length: int | None, + delimiter: bytes | None = None, + split_before: bool = False, +) -> bytes: + """Read a block of bytes from a file + + Parameters + ---------- + f: File + Open file + offset: int + Byte offset to start read + length: int + Number of bytes to read, read through end of file if None + delimiter: bytes (optional) + Ensure reading starts and stops at delimiter bytestring + split_before: bool (optional) + Start/stop read *before* delimiter bytestring. + + + If using the ``delimiter=`` keyword argument we ensure that the read + starts and stops at delimiter boundaries that follow the locations + ``offset`` and ``offset + length``. If ``offset`` is zero then we + start at zero, regardless of delimiter. The bytestring returned WILL + include the terminating delimiter string. + + Examples + -------- + + >>> from io import BytesIO # doctest: +SKIP + >>> f = BytesIO(b'Alice, 100\\nBob, 200\\nCharlie, 300') # doctest: +SKIP + >>> read_block(f, 0, 13) # doctest: +SKIP + b'Alice, 100\\nBo' + + >>> read_block(f, 0, 13, delimiter=b'\\n') # doctest: +SKIP + b'Alice, 100\\nBob, 200\\n' + + >>> read_block(f, 10, 10, delimiter=b'\\n') # doctest: +SKIP + b'Bob, 200\\nCharlie, 300' + """ + if delimiter: + f.seek(offset) + found_start_delim = seek_delimiter(f, delimiter, 2**16) + if length is None: + return f.read() + start = f.tell() + length -= start - offset + + f.seek(start + length) + found_end_delim = seek_delimiter(f, delimiter, 2**16) + end = f.tell() + + # Adjust split location to before delimiter if seek found the + # delimiter sequence, not start or end of file. + if found_start_delim and split_before: + start -= len(delimiter) + + if found_end_delim and split_before: + end -= len(delimiter) + + offset = start + length = end - start + + f.seek(offset) + + # TODO: allow length to be None and read to the end of the file? + assert length is not None + b = f.read(length) + return b + + +def tokenize(*args: Any, **kwargs: Any) -> str: + """Deterministic token + + (modified from dask.base) + + >>> tokenize([1, 2, '3']) + '9d71491b50023b06fc76928e6eddb952' + + >>> tokenize('Hello') == tokenize('Hello') + True + """ + if kwargs: + args += (kwargs,) + try: + h = md5(str(args).encode()) + except ValueError: + # FIPS systems: https://github.com/fsspec/filesystem_spec/issues/380 + h = md5(str(args).encode(), usedforsecurity=False) + return h.hexdigest() + + +def stringify_path(filepath: str | os.PathLike[str] | pathlib.Path) -> str: + """Attempt to convert a path-like object to a string. + + Parameters + ---------- + filepath: object to be converted + + Returns + ------- + filepath_str: maybe a string version of the object + + Notes + ----- + Objects supporting the fspath protocol are coerced according to its + __fspath__ method. + + For backwards compatibility with older Python version, pathlib.Path + objects are specially coerced. + + Any other object is passed through unchanged, which includes bytes, + strings, buffers, or anything else that's not even path-like. + """ + if isinstance(filepath, str): + return filepath + elif hasattr(filepath, "__fspath__"): + return filepath.__fspath__() + elif hasattr(filepath, "path"): + return filepath.path + else: + return filepath # type: ignore[return-value] + + +def make_instance( + cls: Callable[..., T], args: Sequence[Any], kwargs: dict[str, Any] +) -> T: + inst = cls(*args, **kwargs) + inst._determine_worker() # type: ignore[attr-defined] + return inst + + +def common_prefix(paths: Iterable[str]) -> str: + """For a list of paths, find the shortest prefix common to all""" + parts = [p.split("/") for p in paths] + lmax = min(len(p) for p in parts) + end = 0 + for i in range(lmax): + end = all(p[i] == parts[0][i] for p in parts) + if not end: + break + i += end + return "/".join(parts[0][:i]) + + +def other_paths( + paths: list[str], + path2: str | list[str], + exists: bool = False, + flatten: bool = False, +) -> list[str]: + """In bulk file operations, construct a new file tree from a list of files + + Parameters + ---------- + paths: list of str + The input file tree + path2: str or list of str + Root to construct the new list in. If this is already a list of str, we just + assert it has the right number of elements. + exists: bool (optional) + For a str destination, it is already exists (and is a dir), files should + end up inside. + flatten: bool (optional) + Whether to flatten the input directory tree structure so that the output files + are in the same directory. + + Returns + ------- + list of str + """ + + if isinstance(path2, str): + path2 = path2.rstrip("/") + + if flatten: + path2 = ["/".join((path2, p.split("/")[-1])) for p in paths] + else: + cp = common_prefix(paths) + if exists: + cp = cp.rsplit("/", 1)[0] + if not cp and all(not s.startswith("/") for s in paths): + path2 = ["/".join([path2, p]) for p in paths] + else: + path2 = [p.replace(cp, path2, 1) for p in paths] + else: + assert len(paths) == len(path2) + return path2 + + +def is_exception(obj: Any) -> bool: + return isinstance(obj, BaseException) + + +def isfilelike(f: Any) -> TypeGuard[IO[bytes]]: + return all(hasattr(f, attr) for attr in ["read", "close", "tell"]) + + +def get_protocol(url: str) -> str: + url = stringify_path(url) + parts = re.split(r"(\:\:|\://)", url, maxsplit=1) + if len(parts) > 1: + return parts[0] + return "file" + + +def get_file_extension(url: str) -> str: + url = stringify_path(url) + ext_parts = url.rsplit(".", 1) + if len(ext_parts) > 1: + return ext_parts[-1] + return "" + + +def can_be_local(path: str) -> bool: + """Can the given URL be used with open_local?""" + from fsspec import get_filesystem_class + + try: + return getattr(get_filesystem_class(get_protocol(path)), "local_file", False) + except (ValueError, ImportError): + # not in registry or import failed + return False + + +def get_package_version_without_import(name: str) -> str | None: + """For given package name, try to find the version without importing it + + Import and package.__version__ is still the backup here, so an import + *might* happen. + + Returns either the version string, or None if the package + or the version was not readily found. + """ + if name in sys.modules: + mod = sys.modules[name] + if hasattr(mod, "__version__"): + return mod.__version__ + try: + return version(name) + except: # noqa: E722 + pass + try: + import importlib + + mod = importlib.import_module(name) + return mod.__version__ + except (ImportError, AttributeError): + return None + + +def setup_logging( + logger: logging.Logger | None = None, + logger_name: str | None = None, + level: str = "DEBUG", + clear: bool = True, +) -> logging.Logger: + if logger is None and logger_name is None: + raise ValueError("Provide either logger object or logger name") + logger = logger or logging.getLogger(logger_name) + handle = logging.StreamHandler() + formatter = logging.Formatter( + "%(asctime)s - %(name)s - %(levelname)s - %(funcName)s -- %(message)s" + ) + handle.setFormatter(formatter) + if clear: + logger.handlers.clear() + logger.addHandler(handle) + logger.setLevel(level) + return logger + + +def _unstrip_protocol(name: str, fs: AbstractFileSystem) -> str: + return fs.unstrip_protocol(name) + + +def mirror_from( + origin_name: str, methods: Iterable[str] +) -> Callable[[type[T]], type[T]]: + """Mirror attributes and methods from the given + origin_name attribute of the instance to the + decorated class""" + + def origin_getter(method: str, self: Any) -> Any: + origin = getattr(self, origin_name) + return getattr(origin, method) + + def wrapper(cls: type[T]) -> type[T]: + for method in methods: + wrapped_method = partial(origin_getter, method) + setattr(cls, method, property(wrapped_method)) + return cls + + return wrapper + + +@contextlib.contextmanager +def nullcontext(obj: T) -> Iterator[T]: + yield obj + + +def merge_offset_ranges( + paths: list[str], + starts: list[int] | int, + ends: list[int] | int, + max_gap: int = 0, + max_block: int | None = None, + sort: bool = True, +) -> tuple[list[str], list[int], list[int]]: + """Merge adjacent byte-offset ranges when the inter-range + gap is <= `max_gap`, and when the merged byte range does not + exceed `max_block` (if specified). By default, this function + will re-order the input paths and byte ranges to ensure sorted + order. If the user can guarantee that the inputs are already + sorted, passing `sort=False` will skip the re-ordering. + """ + # Check input + if not isinstance(paths, list): + raise TypeError + if not isinstance(starts, list): + starts = [starts] * len(paths) + if not isinstance(ends, list): + ends = [ends] * len(paths) + if len(starts) != len(paths) or len(ends) != len(paths): + raise ValueError + + # Early Return + if len(starts) <= 1: + return paths, starts, ends + + starts = [s or 0 for s in starts] + # Sort by paths and then ranges if `sort=True` + if sort: + paths, starts, ends = ( + list(v) + for v in zip( + *sorted( + zip(paths, starts, ends), + ) + ) + ) + remove = [] + for i, (path, start, end) in enumerate(zip(paths, starts, ends)): + if any( + e is not None and p == path and start >= s and end <= e and i != i2 + for i2, (p, s, e) in enumerate(zip(paths, starts, ends)) + ): + remove.append(i) + paths = [p for i, p in enumerate(paths) if i not in remove] + starts = [s for i, s in enumerate(starts) if i not in remove] + ends = [e for i, e in enumerate(ends) if i not in remove] + + if paths: + # Loop through the coupled `paths`, `starts`, and + # `ends`, and merge adjacent blocks when appropriate + new_paths = paths[:1] + new_starts = starts[:1] + new_ends = ends[:1] + for i in range(1, len(paths)): + if paths[i] == paths[i - 1] and new_ends[-1] is None: + continue + elif ( + paths[i] != paths[i - 1] + or ((starts[i] - new_ends[-1]) > max_gap) + or (max_block is not None and (ends[i] - new_starts[-1]) > max_block) + ): + # Cannot merge with previous block. + # Add new `paths`, `starts`, and `ends` elements + new_paths.append(paths[i]) + new_starts.append(starts[i]) + new_ends.append(ends[i]) + else: + # Merge with the previous block by updating the + # last element of `ends` + new_ends[-1] = ends[i] + return new_paths, new_starts, new_ends + + # `paths` is empty. Just return input lists + return paths, starts, ends + + +def file_size(filelike: IO[bytes]) -> int: + """Find length of any open read-mode file-like""" + pos = filelike.tell() + try: + return filelike.seek(0, 2) + finally: + filelike.seek(pos) + + +@contextlib.contextmanager +def atomic_write(path: str, mode: str = "wb"): + """ + A context manager that opens a temporary file next to `path` and, on exit, + replaces `path` with the temporary file, thereby updating `path` + atomically. + """ + fd, fn = tempfile.mkstemp( + dir=os.path.dirname(path), prefix=os.path.basename(path) + "-" + ) + try: + with open(fd, mode) as fp: + yield fp + except BaseException: + with contextlib.suppress(FileNotFoundError): + os.unlink(fn) + raise + else: + os.replace(fn, path) + + +def _translate(pat, STAR, QUESTION_MARK): + # Copied from: https://github.com/python/cpython/pull/106703. + res: list[str] = [] + add = res.append + i, n = 0, len(pat) + while i < n: + c = pat[i] + i = i + 1 + if c == "*": + # compress consecutive `*` into one + if (not res) or res[-1] is not STAR: + add(STAR) + elif c == "?": + add(QUESTION_MARK) + elif c == "[": + j = i + if j < n and pat[j] == "!": + j = j + 1 + if j < n and pat[j] == "]": + j = j + 1 + while j < n and pat[j] != "]": + j = j + 1 + if j >= n: + add("\\[") + else: + stuff = pat[i:j] + if "-" not in stuff: + stuff = stuff.replace("\\", r"\\") + else: + chunks = [] + k = i + 2 if pat[i] == "!" else i + 1 + while True: + k = pat.find("-", k, j) + if k < 0: + break + chunks.append(pat[i:k]) + i = k + 1 + k = k + 3 + chunk = pat[i:j] + if chunk: + chunks.append(chunk) + else: + chunks[-1] += "-" + # Remove empty ranges -- invalid in RE. + for k in range(len(chunks) - 1, 0, -1): + if chunks[k - 1][-1] > chunks[k][0]: + chunks[k - 1] = chunks[k - 1][:-1] + chunks[k][1:] + del chunks[k] + # Escape backslashes and hyphens for set difference (--). + # Hyphens that create ranges shouldn't be escaped. + stuff = "-".join( + s.replace("\\", r"\\").replace("-", r"\-") for s in chunks + ) + # Escape set operations (&&, ~~ and ||). + stuff = re.sub(r"([&~|])", r"\\\1", stuff) + i = j + 1 + if not stuff: + # Empty range: never match. + add("(?!)") + elif stuff == "!": + # Negated empty range: match any character. + add(".") + else: + if stuff[0] == "!": + stuff = "^" + stuff[1:] + elif stuff[0] in ("^", "["): + stuff = "\\" + stuff + add(f"[{stuff}]") + else: + add(re.escape(c)) + assert i == n + return res + + +def glob_translate(pat): + # Copied from: https://github.com/python/cpython/pull/106703. + # The keyword parameters' values are fixed to: + # recursive=True, include_hidden=True, seps=None + """Translate a pathname with shell wildcards to a regular expression.""" + if os.path.altsep: + seps = os.path.sep + os.path.altsep + else: + seps = os.path.sep + escaped_seps = "".join(map(re.escape, seps)) + any_sep = f"[{escaped_seps}]" if len(seps) > 1 else escaped_seps + not_sep = f"[^{escaped_seps}]" + one_last_segment = f"{not_sep}+" + one_segment = f"{one_last_segment}{any_sep}" + any_segments = f"(?:.+{any_sep})?" + any_last_segments = ".*" + results = [] + parts = re.split(any_sep, pat) + last_part_idx = len(parts) - 1 + for idx, part in enumerate(parts): + if part == "*": + results.append(one_segment if idx < last_part_idx else one_last_segment) + continue + if part == "**": + results.append(any_segments if idx < last_part_idx else any_last_segments) + continue + elif "**" in part: + raise ValueError( + "Invalid pattern: '**' can only be an entire path component" + ) + if part: + results.extend(_translate(part, f"{not_sep}*", not_sep)) + if idx < last_part_idx: + results.append(any_sep) + res = "".join(results) + return rf"(?s:{res})\Z" diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/h11/_connection.py b/bioagent-bench/.venv/lib/python3.14/site-packages/h11/_connection.py new file mode 100644 index 0000000000000000000000000000000000000000..e37d82a82a882c072cb938a90eb4486b51cdad99 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/h11/_connection.py @@ -0,0 +1,659 @@ +# This contains the main Connection class. Everything in h11 revolves around +# this. +from typing import ( + Any, + Callable, + cast, + Dict, + List, + Optional, + overload, + Tuple, + Type, + Union, +) + +from ._events import ( + ConnectionClosed, + Data, + EndOfMessage, + Event, + InformationalResponse, + Request, + Response, +) +from ._headers import get_comma_header, has_expect_100_continue, set_comma_header +from ._readers import READERS, ReadersType +from ._receivebuffer import ReceiveBuffer +from ._state import ( + _SWITCH_CONNECT, + _SWITCH_UPGRADE, + CLIENT, + ConnectionState, + DONE, + ERROR, + MIGHT_SWITCH_PROTOCOL, + SEND_BODY, + SERVER, + SWITCHED_PROTOCOL, +) +from ._util import ( # Import the internal things we need + LocalProtocolError, + RemoteProtocolError, + Sentinel, +) +from ._writers import WRITERS, WritersType + +# Everything in __all__ gets re-exported as part of the h11 public API. +__all__ = ["Connection", "NEED_DATA", "PAUSED"] + + +class NEED_DATA(Sentinel, metaclass=Sentinel): + pass + + +class PAUSED(Sentinel, metaclass=Sentinel): + pass + + +# If we ever have this much buffered without it making a complete parseable +# event, we error out. The only time we really buffer is when reading the +# request/response line + headers together, so this is effectively the limit on +# the size of that. +# +# Some precedents for defaults: +# - node.js: 80 * 1024 +# - tomcat: 8 * 1024 +# - IIS: 16 * 1024 +# - Apache: <8 KiB per line> +DEFAULT_MAX_INCOMPLETE_EVENT_SIZE = 16 * 1024 + + +# RFC 7230's rules for connection lifecycles: +# - If either side says they want to close the connection, then the connection +# must close. +# - HTTP/1.1 defaults to keep-alive unless someone says Connection: close +# - HTTP/1.0 defaults to close unless both sides say Connection: keep-alive +# (and even this is a mess -- e.g. if you're implementing a proxy then +# sending Connection: keep-alive is forbidden). +# +# We simplify life by simply not supporting keep-alive with HTTP/1.0 peers. So +# our rule is: +# - If someone says Connection: close, we will close +# - If someone uses HTTP/1.0, we will close. +def _keep_alive(event: Union[Request, Response]) -> bool: + connection = get_comma_header(event.headers, b"connection") + if b"close" in connection: + return False + if getattr(event, "http_version", b"1.1") < b"1.1": + return False + return True + + +def _body_framing( + request_method: bytes, event: Union[Request, Response] +) -> Tuple[str, Union[Tuple[()], Tuple[int]]]: + # Called when we enter SEND_BODY to figure out framing information for + # this body. + # + # These are the only two events that can trigger a SEND_BODY state: + assert type(event) in (Request, Response) + # Returns one of: + # + # ("content-length", count) + # ("chunked", ()) + # ("http/1.0", ()) + # + # which are (lookup key, *args) for constructing body reader/writer + # objects. + # + # Reference: https://tools.ietf.org/html/rfc7230#section-3.3.3 + # + # Step 1: some responses always have an empty body, regardless of what the + # headers say. + if type(event) is Response: + if ( + event.status_code in (204, 304) + or request_method == b"HEAD" + or (request_method == b"CONNECT" and 200 <= event.status_code < 300) + ): + return ("content-length", (0,)) + # Section 3.3.3 also lists another case -- responses with status_code + # < 200. For us these are InformationalResponses, not Responses, so + # they can't get into this function in the first place. + assert event.status_code >= 200 + + # Step 2: check for Transfer-Encoding (T-E beats C-L): + transfer_encodings = get_comma_header(event.headers, b"transfer-encoding") + if transfer_encodings: + assert transfer_encodings == [b"chunked"] + return ("chunked", ()) + + # Step 3: check for Content-Length + content_lengths = get_comma_header(event.headers, b"content-length") + if content_lengths: + return ("content-length", (int(content_lengths[0]),)) + + # Step 4: no applicable headers; fallback/default depends on type + if type(event) is Request: + return ("content-length", (0,)) + else: + return ("http/1.0", ()) + + +################################################################ +# +# The main Connection class +# +################################################################ + + +class Connection: + """An object encapsulating the state of an HTTP connection. + + Args: + our_role: If you're implementing a client, pass :data:`h11.CLIENT`. If + you're implementing a server, pass :data:`h11.SERVER`. + + max_incomplete_event_size (int): + The maximum number of bytes we're willing to buffer of an + incomplete event. In practice this mostly sets a limit on the + maximum size of the request/response line + headers. If this is + exceeded, then :meth:`next_event` will raise + :exc:`RemoteProtocolError`. + + """ + + def __init__( + self, + our_role: Type[Sentinel], + max_incomplete_event_size: int = DEFAULT_MAX_INCOMPLETE_EVENT_SIZE, + ) -> None: + self._max_incomplete_event_size = max_incomplete_event_size + # State and role tracking + if our_role not in (CLIENT, SERVER): + raise ValueError(f"expected CLIENT or SERVER, not {our_role!r}") + self.our_role = our_role + self.their_role: Type[Sentinel] + if our_role is CLIENT: + self.their_role = SERVER + else: + self.their_role = CLIENT + self._cstate = ConnectionState() + + # Callables for converting data->events or vice-versa given the + # current state + self._writer = self._get_io_object(self.our_role, None, WRITERS) + self._reader = self._get_io_object(self.their_role, None, READERS) + + # Holds any unprocessed received data + self._receive_buffer = ReceiveBuffer() + # If this is true, then it indicates that the incoming connection was + # closed *after* the end of whatever's in self._receive_buffer: + self._receive_buffer_closed = False + + # Extra bits of state that don't fit into the state machine. + # + # These two are only used to interpret framing headers for figuring + # out how to read/write response bodies. their_http_version is also + # made available as a convenient public API. + self.their_http_version: Optional[bytes] = None + self._request_method: Optional[bytes] = None + # This is pure flow-control and doesn't at all affect the set of legal + # transitions, so no need to bother ConnectionState with it: + self.client_is_waiting_for_100_continue = False + + @property + def states(self) -> Dict[Type[Sentinel], Type[Sentinel]]: + """A dictionary like:: + + {CLIENT: , SERVER: } + + See :ref:`state-machine` for details. + + """ + return dict(self._cstate.states) + + @property + def our_state(self) -> Type[Sentinel]: + """The current state of whichever role we are playing. See + :ref:`state-machine` for details. + """ + return self._cstate.states[self.our_role] + + @property + def their_state(self) -> Type[Sentinel]: + """The current state of whichever role we are NOT playing. See + :ref:`state-machine` for details. + """ + return self._cstate.states[self.their_role] + + @property + def they_are_waiting_for_100_continue(self) -> bool: + return self.their_role is CLIENT and self.client_is_waiting_for_100_continue + + def start_next_cycle(self) -> None: + """Attempt to reset our connection state for a new request/response + cycle. + + If both client and server are in :data:`DONE` state, then resets them + both to :data:`IDLE` state in preparation for a new request/response + cycle on this same connection. Otherwise, raises a + :exc:`LocalProtocolError`. + + See :ref:`keepalive-and-pipelining`. + + """ + old_states = dict(self._cstate.states) + self._cstate.start_next_cycle() + self._request_method = None + # self.their_http_version gets left alone, since it presumably lasts + # beyond a single request/response cycle + assert not self.client_is_waiting_for_100_continue + self._respond_to_state_changes(old_states) + + def _process_error(self, role: Type[Sentinel]) -> None: + old_states = dict(self._cstate.states) + self._cstate.process_error(role) + self._respond_to_state_changes(old_states) + + def _server_switch_event(self, event: Event) -> Optional[Type[Sentinel]]: + if type(event) is InformationalResponse and event.status_code == 101: + return _SWITCH_UPGRADE + if type(event) is Response: + if ( + _SWITCH_CONNECT in self._cstate.pending_switch_proposals + and 200 <= event.status_code < 300 + ): + return _SWITCH_CONNECT + return None + + # All events go through here + def _process_event(self, role: Type[Sentinel], event: Event) -> None: + # First, pass the event through the state machine to make sure it + # succeeds. + old_states = dict(self._cstate.states) + if role is CLIENT and type(event) is Request: + if event.method == b"CONNECT": + self._cstate.process_client_switch_proposal(_SWITCH_CONNECT) + if get_comma_header(event.headers, b"upgrade"): + self._cstate.process_client_switch_proposal(_SWITCH_UPGRADE) + server_switch_event = None + if role is SERVER: + server_switch_event = self._server_switch_event(event) + self._cstate.process_event(role, type(event), server_switch_event) + + # Then perform the updates triggered by it. + + if type(event) is Request: + self._request_method = event.method + + if role is self.their_role and type(event) in ( + Request, + Response, + InformationalResponse, + ): + event = cast(Union[Request, Response, InformationalResponse], event) + self.their_http_version = event.http_version + + # Keep alive handling + # + # RFC 7230 doesn't really say what one should do if Connection: close + # shows up on a 1xx InformationalResponse. I think the idea is that + # this is not supposed to happen. In any case, if it does happen, we + # ignore it. + if type(event) in (Request, Response) and not _keep_alive( + cast(Union[Request, Response], event) + ): + self._cstate.process_keep_alive_disabled() + + # 100-continue + if type(event) is Request and has_expect_100_continue(event): + self.client_is_waiting_for_100_continue = True + if type(event) in (InformationalResponse, Response): + self.client_is_waiting_for_100_continue = False + if role is CLIENT and type(event) in (Data, EndOfMessage): + self.client_is_waiting_for_100_continue = False + + self._respond_to_state_changes(old_states, event) + + def _get_io_object( + self, + role: Type[Sentinel], + event: Optional[Event], + io_dict: Union[ReadersType, WritersType], + ) -> Optional[Callable[..., Any]]: + # event may be None; it's only used when entering SEND_BODY + state = self._cstate.states[role] + if state is SEND_BODY: + # Special case: the io_dict has a dict of reader/writer factories + # that depend on the request/response framing. + framing_type, args = _body_framing( + cast(bytes, self._request_method), cast(Union[Request, Response], event) + ) + return io_dict[SEND_BODY][framing_type](*args) # type: ignore[index] + else: + # General case: the io_dict just has the appropriate reader/writer + # for this state + return io_dict.get((role, state)) # type: ignore[return-value] + + # This must be called after any action that might have caused + # self._cstate.states to change. + def _respond_to_state_changes( + self, + old_states: Dict[Type[Sentinel], Type[Sentinel]], + event: Optional[Event] = None, + ) -> None: + # Update reader/writer + if self.our_state != old_states[self.our_role]: + self._writer = self._get_io_object(self.our_role, event, WRITERS) + if self.their_state != old_states[self.their_role]: + self._reader = self._get_io_object(self.their_role, event, READERS) + + @property + def trailing_data(self) -> Tuple[bytes, bool]: + """Data that has been received, but not yet processed, represented as + a tuple with two elements, where the first is a byte-string containing + the unprocessed data itself, and the second is a bool that is True if + the receive connection was closed. + + See :ref:`switching-protocols` for discussion of why you'd want this. + """ + return (bytes(self._receive_buffer), self._receive_buffer_closed) + + def receive_data(self, data: bytes) -> None: + """Add data to our internal receive buffer. + + This does not actually do any processing on the data, just stores + it. To trigger processing, you have to call :meth:`next_event`. + + Args: + data (:term:`bytes-like object`): + The new data that was just received. + + Special case: If *data* is an empty byte-string like ``b""``, + then this indicates that the remote side has closed the + connection (end of file). Normally this is convenient, because + standard Python APIs like :meth:`file.read` or + :meth:`socket.recv` use ``b""`` to indicate end-of-file, while + other failures to read are indicated using other mechanisms + like raising :exc:`TimeoutError`. When using such an API you + can just blindly pass through whatever you get from ``read`` + to :meth:`receive_data`, and everything will work. + + But, if you have an API where reading an empty string is a + valid non-EOF condition, then you need to be aware of this and + make sure to check for such strings and avoid passing them to + :meth:`receive_data`. + + Returns: + Nothing, but after calling this you should call :meth:`next_event` + to parse the newly received data. + + Raises: + RuntimeError: + Raised if you pass an empty *data*, indicating EOF, and then + pass a non-empty *data*, indicating more data that somehow + arrived after the EOF. + + (Calling ``receive_data(b"")`` multiple times is fine, + and equivalent to calling it once.) + + """ + if data: + if self._receive_buffer_closed: + raise RuntimeError("received close, then received more data?") + self._receive_buffer += data + else: + self._receive_buffer_closed = True + + def _extract_next_receive_event( + self, + ) -> Union[Event, Type[NEED_DATA], Type[PAUSED]]: + state = self.their_state + # We don't pause immediately when they enter DONE, because even in + # DONE state we can still process a ConnectionClosed() event. But + # if we have data in our buffer, then we definitely aren't getting + # a ConnectionClosed() immediately and we need to pause. + if state is DONE and self._receive_buffer: + return PAUSED + if state is MIGHT_SWITCH_PROTOCOL or state is SWITCHED_PROTOCOL: + return PAUSED + assert self._reader is not None + event = self._reader(self._receive_buffer) + if event is None: + if not self._receive_buffer and self._receive_buffer_closed: + # In some unusual cases (basically just HTTP/1.0 bodies), EOF + # triggers an actual protocol event; in that case, we want to + # return that event, and then the state will change and we'll + # get called again to generate the actual ConnectionClosed(). + if hasattr(self._reader, "read_eof"): + event = self._reader.read_eof() + else: + event = ConnectionClosed() + if event is None: + event = NEED_DATA + return event # type: ignore[no-any-return] + + def next_event(self) -> Union[Event, Type[NEED_DATA], Type[PAUSED]]: + """Parse the next event out of our receive buffer, update our internal + state, and return it. + + This is a mutating operation -- think of it like calling :func:`next` + on an iterator. + + Returns: + : One of three things: + + 1) An event object -- see :ref:`events`. + + 2) The special constant :data:`NEED_DATA`, which indicates that + you need to read more data from your socket and pass it to + :meth:`receive_data` before this method will be able to return + any more events. + + 3) The special constant :data:`PAUSED`, which indicates that we + are not in a state where we can process incoming data (usually + because the peer has finished their part of the current + request/response cycle, and you have not yet called + :meth:`start_next_cycle`). See :ref:`flow-control` for details. + + Raises: + RemoteProtocolError: + The peer has misbehaved. You should close the connection + (possibly after sending some kind of 4xx response). + + Once this method returns :class:`ConnectionClosed` once, then all + subsequent calls will also return :class:`ConnectionClosed`. + + If this method raises any exception besides :exc:`RemoteProtocolError` + then that's a bug -- if it happens please file a bug report! + + If this method raises any exception then it also sets + :attr:`Connection.their_state` to :data:`ERROR` -- see + :ref:`error-handling` for discussion. + + """ + + if self.their_state is ERROR: + raise RemoteProtocolError("Can't receive data when peer state is ERROR") + try: + event = self._extract_next_receive_event() + if event not in [NEED_DATA, PAUSED]: + self._process_event(self.their_role, cast(Event, event)) + if event is NEED_DATA: + if len(self._receive_buffer) > self._max_incomplete_event_size: + # 431 is "Request header fields too large" which is pretty + # much the only situation where we can get here + raise RemoteProtocolError( + "Receive buffer too long", error_status_hint=431 + ) + if self._receive_buffer_closed: + # We're still trying to complete some event, but that's + # never going to happen because no more data is coming + raise RemoteProtocolError("peer unexpectedly closed connection") + return event + except BaseException as exc: + self._process_error(self.their_role) + if isinstance(exc, LocalProtocolError): + exc._reraise_as_remote_protocol_error() + else: + raise + + @overload + def send(self, event: ConnectionClosed) -> None: + ... + + @overload + def send( + self, event: Union[Request, InformationalResponse, Response, Data, EndOfMessage] + ) -> bytes: + ... + + @overload + def send(self, event: Event) -> Optional[bytes]: + ... + + def send(self, event: Event) -> Optional[bytes]: + """Convert a high-level event into bytes that can be sent to the peer, + while updating our internal state machine. + + Args: + event: The :ref:`event ` to send. + + Returns: + If ``type(event) is ConnectionClosed``, then returns + ``None``. Otherwise, returns a :term:`bytes-like object`. + + Raises: + LocalProtocolError: + Sending this event at this time would violate our + understanding of the HTTP/1.1 protocol. + + If this method raises any exception then it also sets + :attr:`Connection.our_state` to :data:`ERROR` -- see + :ref:`error-handling` for discussion. + + """ + data_list = self.send_with_data_passthrough(event) + if data_list is None: + return None + else: + return b"".join(data_list) + + def send_with_data_passthrough(self, event: Event) -> Optional[List[bytes]]: + """Identical to :meth:`send`, except that in situations where + :meth:`send` returns a single :term:`bytes-like object`, this instead + returns a list of them -- and when sending a :class:`Data` event, this + list is guaranteed to contain the exact object you passed in as + :attr:`Data.data`. See :ref:`sendfile` for discussion. + + """ + if self.our_state is ERROR: + raise LocalProtocolError("Can't send data when our state is ERROR") + try: + if type(event) is Response: + event = self._clean_up_response_headers_for_sending(event) + # We want to call _process_event before calling the writer, + # because if someone tries to do something invalid then this will + # give a sensible error message, while our writers all just assume + # they will only receive valid events. But, _process_event might + # change self._writer. So we have to do a little dance: + writer = self._writer + self._process_event(self.our_role, event) + if type(event) is ConnectionClosed: + return None + else: + # In any situation where writer is None, process_event should + # have raised ProtocolError + assert writer is not None + data_list: List[bytes] = [] + writer(event, data_list.append) + return data_list + except: + self._process_error(self.our_role) + raise + + def send_failed(self) -> None: + """Notify the state machine that we failed to send the data it gave + us. + + This causes :attr:`Connection.our_state` to immediately become + :data:`ERROR` -- see :ref:`error-handling` for discussion. + + """ + self._process_error(self.our_role) + + # When sending a Response, we take responsibility for a few things: + # + # - Sometimes you MUST set Connection: close. We take care of those + # times. (You can also set it yourself if you want, and if you do then + # we'll respect that and close the connection at the right time. But you + # don't have to worry about that unless you want to.) + # + # - The user has to set Content-Length if they want it. Otherwise, for + # responses that have bodies (e.g. not HEAD), then we will automatically + # select the right mechanism for streaming a body of unknown length, + # which depends on depending on the peer's HTTP version. + # + # This function's *only* responsibility is making sure headers are set up + # right -- everything downstream just looks at the headers. There are no + # side channels. + def _clean_up_response_headers_for_sending(self, response: Response) -> Response: + assert type(response) is Response + + headers = response.headers + need_close = False + + # HEAD requests need some special handling: they always act like they + # have Content-Length: 0, and that's how _body_framing treats + # them. But their headers are supposed to match what we would send if + # the request was a GET. (Technically there is one deviation allowed: + # we're allowed to leave out the framing headers -- see + # https://tools.ietf.org/html/rfc7231#section-4.3.2 . But it's just as + # easy to get them right.) + method_for_choosing_headers = cast(bytes, self._request_method) + if method_for_choosing_headers == b"HEAD": + method_for_choosing_headers = b"GET" + framing_type, _ = _body_framing(method_for_choosing_headers, response) + if framing_type in ("chunked", "http/1.0"): + # This response has a body of unknown length. + # If our peer is HTTP/1.1, we use Transfer-Encoding: chunked + # If our peer is HTTP/1.0, we use no framing headers, and close the + # connection afterwards. + # + # Make sure to clear Content-Length (in principle user could have + # set both and then we ignored Content-Length b/c + # Transfer-Encoding overwrote it -- this would be naughty of them, + # but the HTTP spec says that if our peer does this then we have + # to fix it instead of erroring out, so we'll accord the user the + # same respect). + headers = set_comma_header(headers, b"content-length", []) + if self.their_http_version is None or self.their_http_version < b"1.1": + # Either we never got a valid request and are sending back an + # error (their_http_version is None), so we assume the worst; + # or else we did get a valid HTTP/1.0 request, so we know that + # they don't understand chunked encoding. + headers = set_comma_header(headers, b"transfer-encoding", []) + # This is actually redundant ATM, since currently we + # unconditionally disable keep-alive when talking to HTTP/1.0 + # peers. But let's be defensive just in case we add + # Connection: keep-alive support later: + if self._request_method != b"HEAD": + need_close = True + else: + headers = set_comma_header(headers, b"transfer-encoding", [b"chunked"]) + + if not self._cstate.keep_alive or need_close: + # Make sure Connection: close is set + connection = set(get_comma_header(headers, b"connection")) + connection.discard(b"keep-alive") + connection.add(b"close") + headers = set_comma_header(headers, b"connection", sorted(connection)) + + return Response( + headers=headers, + status_code=response.status_code, + http_version=response.http_version, + reason=response.reason, + ) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/h11/_headers.py b/bioagent-bench/.venv/lib/python3.14/site-packages/h11/_headers.py new file mode 100644 index 0000000000000000000000000000000000000000..31da3e2b23b55a624b36f105e62a6902e63286aa --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/h11/_headers.py @@ -0,0 +1,282 @@ +import re +from typing import AnyStr, cast, List, overload, Sequence, Tuple, TYPE_CHECKING, Union + +from ._abnf import field_name, field_value +from ._util import bytesify, LocalProtocolError, validate + +if TYPE_CHECKING: + from ._events import Request + +try: + from typing import Literal +except ImportError: + from typing_extensions import Literal # type: ignore + +CONTENT_LENGTH_MAX_DIGITS = 20 # allow up to 1 billion TB - 1 + + +# Facts +# ----- +# +# Headers are: +# keys: case-insensitive ascii +# values: mixture of ascii and raw bytes +# +# "Historically, HTTP has allowed field content with text in the ISO-8859-1 +# charset [ISO-8859-1], supporting other charsets only through use of +# [RFC2047] encoding. In practice, most HTTP header field values use only a +# subset of the US-ASCII charset [USASCII]. Newly defined header fields SHOULD +# limit their field values to US-ASCII octets. A recipient SHOULD treat other +# octets in field content (obs-text) as opaque data." +# And it deprecates all non-ascii values +# +# Leading/trailing whitespace in header names is forbidden +# +# Values get leading/trailing whitespace stripped +# +# Content-Disposition actually needs to contain unicode semantically; to +# accomplish this it has a terrifically weird way of encoding the filename +# itself as ascii (and even this still has lots of cross-browser +# incompatibilities) +# +# Order is important: +# "a proxy MUST NOT change the order of these field values when forwarding a +# message" +# (and there are several headers where the order indicates a preference) +# +# Multiple occurences of the same header: +# "A sender MUST NOT generate multiple header fields with the same field name +# in a message unless either the entire field value for that header field is +# defined as a comma-separated list [or the header is Set-Cookie which gets a +# special exception]" - RFC 7230. (cookies are in RFC 6265) +# +# So every header aside from Set-Cookie can be merged by b", ".join if it +# occurs repeatedly. But, of course, they can't necessarily be split by +# .split(b","), because quoting. +# +# Given all this mess (case insensitive, duplicates allowed, order is +# important, ...), there doesn't appear to be any standard way to handle +# headers in Python -- they're almost like dicts, but... actually just +# aren't. For now we punt and just use a super simple representation: headers +# are a list of pairs +# +# [(name1, value1), (name2, value2), ...] +# +# where all entries are bytestrings, names are lowercase and have no +# leading/trailing whitespace, and values are bytestrings with no +# leading/trailing whitespace. Searching and updating are done via naive O(n) +# methods. +# +# Maybe a dict-of-lists would be better? + +_content_length_re = re.compile(rb"[0-9]+") +_field_name_re = re.compile(field_name.encode("ascii")) +_field_value_re = re.compile(field_value.encode("ascii")) + + +class Headers(Sequence[Tuple[bytes, bytes]]): + """ + A list-like interface that allows iterating over headers as byte-pairs + of (lowercased-name, value). + + Internally we actually store the representation as three-tuples, + including both the raw original casing, in order to preserve casing + over-the-wire, and the lowercased name, for case-insensitive comparisions. + + r = Request( + method="GET", + target="/", + headers=[("Host", "example.org"), ("Connection", "keep-alive")], + http_version="1.1", + ) + assert r.headers == [ + (b"host", b"example.org"), + (b"connection", b"keep-alive") + ] + assert r.headers.raw_items() == [ + (b"Host", b"example.org"), + (b"Connection", b"keep-alive") + ] + """ + + __slots__ = "_full_items" + + def __init__(self, full_items: List[Tuple[bytes, bytes, bytes]]) -> None: + self._full_items = full_items + + def __bool__(self) -> bool: + return bool(self._full_items) + + def __eq__(self, other: object) -> bool: + return list(self) == list(other) # type: ignore + + def __len__(self) -> int: + return len(self._full_items) + + def __repr__(self) -> str: + return "" % repr(list(self)) + + def __getitem__(self, idx: int) -> Tuple[bytes, bytes]: # type: ignore[override] + _, name, value = self._full_items[idx] + return (name, value) + + def raw_items(self) -> List[Tuple[bytes, bytes]]: + return [(raw_name, value) for raw_name, _, value in self._full_items] + + +HeaderTypes = Union[ + List[Tuple[bytes, bytes]], + List[Tuple[bytes, str]], + List[Tuple[str, bytes]], + List[Tuple[str, str]], +] + + +@overload +def normalize_and_validate(headers: Headers, _parsed: Literal[True]) -> Headers: + ... + + +@overload +def normalize_and_validate(headers: HeaderTypes, _parsed: Literal[False]) -> Headers: + ... + + +@overload +def normalize_and_validate( + headers: Union[Headers, HeaderTypes], _parsed: bool = False +) -> Headers: + ... + + +def normalize_and_validate( + headers: Union[Headers, HeaderTypes], _parsed: bool = False +) -> Headers: + new_headers = [] + seen_content_length = None + saw_transfer_encoding = False + for name, value in headers: + # For headers coming out of the parser, we can safely skip some steps, + # because it always returns bytes and has already run these regexes + # over the data: + if not _parsed: + name = bytesify(name) + value = bytesify(value) + validate(_field_name_re, name, "Illegal header name {!r}", name) + validate(_field_value_re, value, "Illegal header value {!r}", value) + assert isinstance(name, bytes) + assert isinstance(value, bytes) + + raw_name = name + name = name.lower() + if name == b"content-length": + lengths = {length.strip() for length in value.split(b",")} + if len(lengths) != 1: + raise LocalProtocolError("conflicting Content-Length headers") + value = lengths.pop() + validate(_content_length_re, value, "bad Content-Length") + if len(value) > CONTENT_LENGTH_MAX_DIGITS: + raise LocalProtocolError("bad Content-Length") + if seen_content_length is None: + seen_content_length = value + new_headers.append((raw_name, name, value)) + elif seen_content_length != value: + raise LocalProtocolError("conflicting Content-Length headers") + elif name == b"transfer-encoding": + # "A server that receives a request message with a transfer coding + # it does not understand SHOULD respond with 501 (Not + # Implemented)." + # https://tools.ietf.org/html/rfc7230#section-3.3.1 + if saw_transfer_encoding: + raise LocalProtocolError( + "multiple Transfer-Encoding headers", error_status_hint=501 + ) + # "All transfer-coding names are case-insensitive" + # -- https://tools.ietf.org/html/rfc7230#section-4 + value = value.lower() + if value != b"chunked": + raise LocalProtocolError( + "Only Transfer-Encoding: chunked is supported", + error_status_hint=501, + ) + saw_transfer_encoding = True + new_headers.append((raw_name, name, value)) + else: + new_headers.append((raw_name, name, value)) + return Headers(new_headers) + + +def get_comma_header(headers: Headers, name: bytes) -> List[bytes]: + # Should only be used for headers whose value is a list of + # comma-separated, case-insensitive values. + # + # The header name `name` is expected to be lower-case bytes. + # + # Connection: meets these criteria (including cast insensitivity). + # + # Content-Length: technically is just a single value (1*DIGIT), but the + # standard makes reference to implementations that do multiple values, and + # using this doesn't hurt. Ditto, case insensitivity doesn't things either + # way. + # + # Transfer-Encoding: is more complex (allows for quoted strings), so + # splitting on , is actually wrong. For example, this is legal: + # + # Transfer-Encoding: foo; options="1,2", chunked + # + # and should be parsed as + # + # foo; options="1,2" + # chunked + # + # but this naive function will parse it as + # + # foo; options="1 + # 2" + # chunked + # + # However, this is okay because the only thing we are going to do with + # any Transfer-Encoding is reject ones that aren't just "chunked", so + # both of these will be treated the same anyway. + # + # Expect: the only legal value is the literal string + # "100-continue". Splitting on commas is harmless. Case insensitive. + # + out: List[bytes] = [] + for _, found_name, found_raw_value in headers._full_items: + if found_name == name: + found_raw_value = found_raw_value.lower() + for found_split_value in found_raw_value.split(b","): + found_split_value = found_split_value.strip() + if found_split_value: + out.append(found_split_value) + return out + + +def set_comma_header(headers: Headers, name: bytes, new_values: List[bytes]) -> Headers: + # The header name `name` is expected to be lower-case bytes. + # + # Note that when we store the header we use title casing for the header + # names, in order to match the conventional HTTP header style. + # + # Simply calling `.title()` is a blunt approach, but it's correct + # here given the cases where we're using `set_comma_header`... + # + # Connection, Content-Length, Transfer-Encoding. + new_headers: List[Tuple[bytes, bytes]] = [] + for found_raw_name, found_name, found_raw_value in headers._full_items: + if found_name != name: + new_headers.append((found_raw_name, found_raw_value)) + for new_value in new_values: + new_headers.append((name.title(), new_value)) + return normalize_and_validate(new_headers) + + +def has_expect_100_continue(request: "Request") -> bool: + # https://tools.ietf.org/html/rfc7231#section-5.1.1 + # "A server that receives a 100-continue expectation in an HTTP/1.0 request + # MUST ignore that expectation." + if request.http_version < b"1.1": + return False + expect = get_comma_header(request.headers, b"expect") + return b"100-continue" in expect diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/h11/_readers.py b/bioagent-bench/.venv/lib/python3.14/site-packages/h11/_readers.py new file mode 100644 index 0000000000000000000000000000000000000000..576804cc282032526e0a932c9853d586a094bad0 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/h11/_readers.py @@ -0,0 +1,250 @@ +# Code to read HTTP data +# +# Strategy: each reader is a callable which takes a ReceiveBuffer object, and +# either: +# 1) consumes some of it and returns an Event +# 2) raises a LocalProtocolError (for consistency -- e.g. we call validate() +# and it might raise a LocalProtocolError, so simpler just to always use +# this) +# 3) returns None, meaning "I need more data" +# +# If they have a .read_eof attribute, then this will be called if an EOF is +# received -- but this is optional. Either way, the actual ConnectionClosed +# event will be generated afterwards. +# +# READERS is a dict describing how to pick a reader. It maps states to either: +# - a reader +# - or, for body readers, a dict of per-framing reader factories + +import re +from typing import Any, Callable, Dict, Iterable, NoReturn, Optional, Tuple, Type, Union + +from ._abnf import chunk_header, header_field, request_line, status_line +from ._events import Data, EndOfMessage, InformationalResponse, Request, Response +from ._receivebuffer import ReceiveBuffer +from ._state import ( + CLIENT, + CLOSED, + DONE, + IDLE, + MUST_CLOSE, + SEND_BODY, + SEND_RESPONSE, + SERVER, +) +from ._util import LocalProtocolError, RemoteProtocolError, Sentinel, validate + +__all__ = ["READERS"] + +header_field_re = re.compile(header_field.encode("ascii")) +obs_fold_re = re.compile(rb"[ \t]+") + + +def _obsolete_line_fold(lines: Iterable[bytes]) -> Iterable[bytes]: + it = iter(lines) + last: Optional[bytes] = None + for line in it: + match = obs_fold_re.match(line) + if match: + if last is None: + raise LocalProtocolError("continuation line at start of headers") + if not isinstance(last, bytearray): + # Cast to a mutable type, avoiding copy on append to ensure O(n) time + last = bytearray(last) + last += b" " + last += line[match.end() :] + else: + if last is not None: + yield last + last = line + if last is not None: + yield last + + +def _decode_header_lines( + lines: Iterable[bytes], +) -> Iterable[Tuple[bytes, bytes]]: + for line in _obsolete_line_fold(lines): + matches = validate(header_field_re, line, "illegal header line: {!r}", line) + yield (matches["field_name"], matches["field_value"]) + + +request_line_re = re.compile(request_line.encode("ascii")) + + +def maybe_read_from_IDLE_client(buf: ReceiveBuffer) -> Optional[Request]: + lines = buf.maybe_extract_lines() + if lines is None: + if buf.is_next_line_obviously_invalid_request_line(): + raise LocalProtocolError("illegal request line") + return None + if not lines: + raise LocalProtocolError("no request line received") + matches = validate( + request_line_re, lines[0], "illegal request line: {!r}", lines[0] + ) + return Request( + headers=list(_decode_header_lines(lines[1:])), _parsed=True, **matches + ) + + +status_line_re = re.compile(status_line.encode("ascii")) + + +def maybe_read_from_SEND_RESPONSE_server( + buf: ReceiveBuffer, +) -> Union[InformationalResponse, Response, None]: + lines = buf.maybe_extract_lines() + if lines is None: + if buf.is_next_line_obviously_invalid_request_line(): + raise LocalProtocolError("illegal request line") + return None + if not lines: + raise LocalProtocolError("no response line received") + matches = validate(status_line_re, lines[0], "illegal status line: {!r}", lines[0]) + http_version = ( + b"1.1" if matches["http_version"] is None else matches["http_version"] + ) + reason = b"" if matches["reason"] is None else matches["reason"] + status_code = int(matches["status_code"]) + class_: Union[Type[InformationalResponse], Type[Response]] = ( + InformationalResponse if status_code < 200 else Response + ) + return class_( + headers=list(_decode_header_lines(lines[1:])), + _parsed=True, + status_code=status_code, + reason=reason, + http_version=http_version, + ) + + +class ContentLengthReader: + def __init__(self, length: int) -> None: + self._length = length + self._remaining = length + + def __call__(self, buf: ReceiveBuffer) -> Union[Data, EndOfMessage, None]: + if self._remaining == 0: + return EndOfMessage() + data = buf.maybe_extract_at_most(self._remaining) + if data is None: + return None + self._remaining -= len(data) + return Data(data=data) + + def read_eof(self) -> NoReturn: + raise RemoteProtocolError( + "peer closed connection without sending complete message body " + "(received {} bytes, expected {})".format( + self._length - self._remaining, self._length + ) + ) + + +chunk_header_re = re.compile(chunk_header.encode("ascii")) + + +class ChunkedReader: + def __init__(self) -> None: + self._bytes_in_chunk = 0 + # After reading a chunk, we have to throw away the trailing \r\n. + # This tracks the bytes that we need to match and throw away. + self._bytes_to_discard = b"" + self._reading_trailer = False + + def __call__(self, buf: ReceiveBuffer) -> Union[Data, EndOfMessage, None]: + if self._reading_trailer: + lines = buf.maybe_extract_lines() + if lines is None: + return None + return EndOfMessage(headers=list(_decode_header_lines(lines))) + if self._bytes_to_discard: + data = buf.maybe_extract_at_most(len(self._bytes_to_discard)) + if data is None: + return None + if data != self._bytes_to_discard[: len(data)]: + raise LocalProtocolError( + f"malformed chunk footer: {data!r} (expected {self._bytes_to_discard!r})" + ) + self._bytes_to_discard = self._bytes_to_discard[len(data) :] + if self._bytes_to_discard: + return None + # else, fall through and read some more + assert self._bytes_to_discard == b"" + if self._bytes_in_chunk == 0: + # We need to refill our chunk count + chunk_header = buf.maybe_extract_next_line() + if chunk_header is None: + return None + matches = validate( + chunk_header_re, + chunk_header, + "illegal chunk header: {!r}", + chunk_header, + ) + # XX FIXME: we discard chunk extensions. Does anyone care? + self._bytes_in_chunk = int(matches["chunk_size"], base=16) + if self._bytes_in_chunk == 0: + self._reading_trailer = True + return self(buf) + chunk_start = True + else: + chunk_start = False + assert self._bytes_in_chunk > 0 + data = buf.maybe_extract_at_most(self._bytes_in_chunk) + if data is None: + return None + self._bytes_in_chunk -= len(data) + if self._bytes_in_chunk == 0: + self._bytes_to_discard = b"\r\n" + chunk_end = True + else: + chunk_end = False + return Data(data=data, chunk_start=chunk_start, chunk_end=chunk_end) + + def read_eof(self) -> NoReturn: + raise RemoteProtocolError( + "peer closed connection without sending complete message body " + "(incomplete chunked read)" + ) + + +class Http10Reader: + def __call__(self, buf: ReceiveBuffer) -> Optional[Data]: + data = buf.maybe_extract_at_most(999999999) + if data is None: + return None + return Data(data=data) + + def read_eof(self) -> EndOfMessage: + return EndOfMessage() + + +def expect_nothing(buf: ReceiveBuffer) -> None: + if buf: + raise LocalProtocolError("Got data when expecting EOF") + return None + + +ReadersType = Dict[ + Union[Type[Sentinel], Tuple[Type[Sentinel], Type[Sentinel]]], + Union[Callable[..., Any], Dict[str, Callable[..., Any]]], +] + +READERS: ReadersType = { + (CLIENT, IDLE): maybe_read_from_IDLE_client, + (SERVER, IDLE): maybe_read_from_SEND_RESPONSE_server, + (SERVER, SEND_RESPONSE): maybe_read_from_SEND_RESPONSE_server, + (CLIENT, DONE): expect_nothing, + (CLIENT, MUST_CLOSE): expect_nothing, + (CLIENT, CLOSED): expect_nothing, + (SERVER, DONE): expect_nothing, + (SERVER, MUST_CLOSE): expect_nothing, + (SERVER, CLOSED): expect_nothing, + SEND_BODY: { + "chunked": ChunkedReader, + "content-length": ContentLengthReader, + "http/1.0": Http10Reader, + }, +} diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/h11/_receivebuffer.py b/bioagent-bench/.venv/lib/python3.14/site-packages/h11/_receivebuffer.py new file mode 100644 index 0000000000000000000000000000000000000000..e5c4e08a56f5081e87103f38b4add6ce1b730204 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/h11/_receivebuffer.py @@ -0,0 +1,153 @@ +import re +import sys +from typing import List, Optional, Union + +__all__ = ["ReceiveBuffer"] + + +# Operations we want to support: +# - find next \r\n or \r\n\r\n (\n or \n\n are also acceptable), +# or wait until there is one +# - read at-most-N bytes +# Goals: +# - on average, do this fast +# - worst case, do this in O(n) where n is the number of bytes processed +# Plan: +# - store bytearray, offset, how far we've searched for a separator token +# - use the how-far-we've-searched data to avoid rescanning +# - while doing a stream of uninterrupted processing, advance offset instead +# of constantly copying +# WARNING: +# - I haven't benchmarked or profiled any of this yet. +# +# Note that starting in Python 3.4, deleting the initial n bytes from a +# bytearray is amortized O(n), thanks to some excellent work by Antoine +# Martin: +# +# https://bugs.python.org/issue19087 +# +# This means that if we only supported 3.4+, we could get rid of the code here +# involving self._start and self.compress, because it's doing exactly the same +# thing that bytearray now does internally. +# +# BUT unfortunately, we still support 2.7, and reading short segments out of a +# long buffer MUST be O(bytes read) to avoid DoS issues, so we can't actually +# delete this code. Yet: +# +# https://pythonclock.org/ +# +# (Two things to double-check first though: make sure PyPy also has the +# optimization, and benchmark to make sure it's a win, since we do have a +# slightly clever thing where we delay calling compress() until we've +# processed a whole event, which could in theory be slightly more efficient +# than the internal bytearray support.) +blank_line_regex = re.compile(b"\n\r?\n", re.MULTILINE) + + +class ReceiveBuffer: + def __init__(self) -> None: + self._data = bytearray() + self._next_line_search = 0 + self._multiple_lines_search = 0 + + def __iadd__(self, byteslike: Union[bytes, bytearray]) -> "ReceiveBuffer": + self._data += byteslike + return self + + def __bool__(self) -> bool: + return bool(len(self)) + + def __len__(self) -> int: + return len(self._data) + + # for @property unprocessed_data + def __bytes__(self) -> bytes: + return bytes(self._data) + + def _extract(self, count: int) -> bytearray: + # extracting an initial slice of the data buffer and return it + out = self._data[:count] + del self._data[:count] + + self._next_line_search = 0 + self._multiple_lines_search = 0 + + return out + + def maybe_extract_at_most(self, count: int) -> Optional[bytearray]: + """ + Extract a fixed number of bytes from the buffer. + """ + out = self._data[:count] + if not out: + return None + + return self._extract(count) + + def maybe_extract_next_line(self) -> Optional[bytearray]: + """ + Extract the first line, if it is completed in the buffer. + """ + # Only search in buffer space that we've not already looked at. + search_start_index = max(0, self._next_line_search - 1) + partial_idx = self._data.find(b"\r\n", search_start_index) + + if partial_idx == -1: + self._next_line_search = len(self._data) + return None + + # + 2 is to compensate len(b"\r\n") + idx = partial_idx + 2 + + return self._extract(idx) + + def maybe_extract_lines(self) -> Optional[List[bytearray]]: + """ + Extract everything up to the first blank line, and return a list of lines. + """ + # Handle the case where we have an immediate empty line. + if self._data[:1] == b"\n": + self._extract(1) + return [] + + if self._data[:2] == b"\r\n": + self._extract(2) + return [] + + # Only search in buffer space that we've not already looked at. + match = blank_line_regex.search(self._data, self._multiple_lines_search) + if match is None: + self._multiple_lines_search = max(0, len(self._data) - 2) + return None + + # Truncate the buffer and return it. + idx = match.span(0)[-1] + out = self._extract(idx) + lines = out.split(b"\n") + + for line in lines: + if line.endswith(b"\r"): + del line[-1] + + assert lines[-2] == lines[-1] == b"" + + del lines[-2:] + + return lines + + # In theory we should wait until `\r\n` before starting to validate + # incoming data. However it's interesting to detect (very) invalid data + # early given they might not even contain `\r\n` at all (hence only + # timeout will get rid of them). + # This is not a 100% effective detection but more of a cheap sanity check + # allowing for early abort in some useful cases. + # This is especially interesting when peer is messing up with HTTPS and + # sent us a TLS stream where we were expecting plain HTTP given all + # versions of TLS so far start handshake with a 0x16 message type code. + def is_next_line_obviously_invalid_request_line(self) -> bool: + try: + # HTTP header line must not contain non-printable characters + # and should not start with a space + return self._data[0] < 0x21 + except IndexError: + return False diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/h11/_state.py b/bioagent-bench/.venv/lib/python3.14/site-packages/h11/_state.py new file mode 100644 index 0000000000000000000000000000000000000000..3ad444b043e3f3d6c05c2d9d84d5119312bfaa34 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/h11/_state.py @@ -0,0 +1,365 @@ +################################################################ +# The core state machine +################################################################ +# +# Rule 1: everything that affects the state machine and state transitions must +# live here in this file. As much as possible goes into the table-based +# representation, but for the bits that don't quite fit, the actual code and +# state must nonetheless live here. +# +# Rule 2: this file does not know about what role we're playing; it only knows +# about HTTP request/response cycles in the abstract. This ensures that we +# don't cheat and apply different rules to local and remote parties. +# +# +# Theory of operation +# =================== +# +# Possibly the simplest way to think about this is that we actually have 5 +# different state machines here. Yes, 5. These are: +# +# 1) The client state, with its complicated automaton (see the docs) +# 2) The server state, with its complicated automaton (see the docs) +# 3) The keep-alive state, with possible states {True, False} +# 4) The SWITCH_CONNECT state, with possible states {False, True} +# 5) The SWITCH_UPGRADE state, with possible states {False, True} +# +# For (3)-(5), the first state listed is the initial state. +# +# (1)-(3) are stored explicitly in member variables. The last +# two are stored implicitly in the pending_switch_proposals set as: +# (state of 4) == (_SWITCH_CONNECT in pending_switch_proposals) +# (state of 5) == (_SWITCH_UPGRADE in pending_switch_proposals) +# +# And each of these machines has two different kinds of transitions: +# +# a) Event-triggered +# b) State-triggered +# +# Event triggered is the obvious thing that you'd think it is: some event +# happens, and if it's the right event at the right time then a transition +# happens. But there are somewhat complicated rules for which machines can +# "see" which events. (As a rule of thumb, if a machine "sees" an event, this +# means two things: the event can affect the machine, and if the machine is +# not in a state where it expects that event then it's an error.) These rules +# are: +# +# 1) The client machine sees all h11.events objects emitted by the client. +# +# 2) The server machine sees all h11.events objects emitted by the server. +# +# It also sees the client's Request event. +# +# And sometimes, server events are annotated with a _SWITCH_* event. For +# example, we can have a (Response, _SWITCH_CONNECT) event, which is +# different from a regular Response event. +# +# 3) The keep-alive machine sees the process_keep_alive_disabled() event +# (which is derived from Request/Response events), and this event +# transitions it from True -> False, or from False -> False. There's no way +# to transition back. +# +# 4&5) The _SWITCH_* machines transition from False->True when we get a +# Request that proposes the relevant type of switch (via +# process_client_switch_proposals), and they go from True->False when we +# get a Response that has no _SWITCH_* annotation. +# +# So that's event-triggered transitions. +# +# State-triggered transitions are less standard. What they do here is couple +# the machines together. The way this works is, when certain *joint* +# configurations of states are achieved, then we automatically transition to a +# new *joint* state. So, for example, if we're ever in a joint state with +# +# client: DONE +# keep-alive: False +# +# then the client state immediately transitions to: +# +# client: MUST_CLOSE +# +# This is fundamentally different from an event-based transition, because it +# doesn't matter how we arrived at the {client: DONE, keep-alive: False} state +# -- maybe the client transitioned SEND_BODY -> DONE, or keep-alive +# transitioned True -> False. Either way, once this precondition is satisfied, +# this transition is immediately triggered. +# +# What if two conflicting state-based transitions get enabled at the same +# time? In practice there's only one case where this arises (client DONE -> +# MIGHT_SWITCH_PROTOCOL versus DONE -> MUST_CLOSE), and we resolve it by +# explicitly prioritizing the DONE -> MIGHT_SWITCH_PROTOCOL transition. +# +# Implementation +# -------------- +# +# The event-triggered transitions for the server and client machines are all +# stored explicitly in a table. Ditto for the state-triggered transitions that +# involve just the server and client state. +# +# The transitions for the other machines, and the state-triggered transitions +# that involve the other machines, are written out as explicit Python code. +# +# It'd be nice if there were some cleaner way to do all this. This isn't +# *too* terrible, but I feel like it could probably be better. +# +# WARNING +# ------- +# +# The script that generates the state machine diagrams for the docs knows how +# to read out the EVENT_TRIGGERED_TRANSITIONS and STATE_TRIGGERED_TRANSITIONS +# tables. But it can't automatically read the transitions that are written +# directly in Python code. So if you touch those, you need to also update the +# script to keep it in sync! +from typing import cast, Dict, Optional, Set, Tuple, Type, Union + +from ._events import * +from ._util import LocalProtocolError, Sentinel + +# Everything in __all__ gets re-exported as part of the h11 public API. +__all__ = [ + "CLIENT", + "SERVER", + "IDLE", + "SEND_RESPONSE", + "SEND_BODY", + "DONE", + "MUST_CLOSE", + "CLOSED", + "MIGHT_SWITCH_PROTOCOL", + "SWITCHED_PROTOCOL", + "ERROR", +] + + +class CLIENT(Sentinel, metaclass=Sentinel): + pass + + +class SERVER(Sentinel, metaclass=Sentinel): + pass + + +# States +class IDLE(Sentinel, metaclass=Sentinel): + pass + + +class SEND_RESPONSE(Sentinel, metaclass=Sentinel): + pass + + +class SEND_BODY(Sentinel, metaclass=Sentinel): + pass + + +class DONE(Sentinel, metaclass=Sentinel): + pass + + +class MUST_CLOSE(Sentinel, metaclass=Sentinel): + pass + + +class CLOSED(Sentinel, metaclass=Sentinel): + pass + + +class ERROR(Sentinel, metaclass=Sentinel): + pass + + +# Switch types +class MIGHT_SWITCH_PROTOCOL(Sentinel, metaclass=Sentinel): + pass + + +class SWITCHED_PROTOCOL(Sentinel, metaclass=Sentinel): + pass + + +class _SWITCH_UPGRADE(Sentinel, metaclass=Sentinel): + pass + + +class _SWITCH_CONNECT(Sentinel, metaclass=Sentinel): + pass + + +EventTransitionType = Dict[ + Type[Sentinel], + Dict[ + Type[Sentinel], + Dict[Union[Type[Event], Tuple[Type[Event], Type[Sentinel]]], Type[Sentinel]], + ], +] + +EVENT_TRIGGERED_TRANSITIONS: EventTransitionType = { + CLIENT: { + IDLE: {Request: SEND_BODY, ConnectionClosed: CLOSED}, + SEND_BODY: {Data: SEND_BODY, EndOfMessage: DONE}, + DONE: {ConnectionClosed: CLOSED}, + MUST_CLOSE: {ConnectionClosed: CLOSED}, + CLOSED: {ConnectionClosed: CLOSED}, + MIGHT_SWITCH_PROTOCOL: {}, + SWITCHED_PROTOCOL: {}, + ERROR: {}, + }, + SERVER: { + IDLE: { + ConnectionClosed: CLOSED, + Response: SEND_BODY, + # Special case: server sees client Request events, in this form + (Request, CLIENT): SEND_RESPONSE, + }, + SEND_RESPONSE: { + InformationalResponse: SEND_RESPONSE, + Response: SEND_BODY, + (InformationalResponse, _SWITCH_UPGRADE): SWITCHED_PROTOCOL, + (Response, _SWITCH_CONNECT): SWITCHED_PROTOCOL, + }, + SEND_BODY: {Data: SEND_BODY, EndOfMessage: DONE}, + DONE: {ConnectionClosed: CLOSED}, + MUST_CLOSE: {ConnectionClosed: CLOSED}, + CLOSED: {ConnectionClosed: CLOSED}, + SWITCHED_PROTOCOL: {}, + ERROR: {}, + }, +} + +StateTransitionType = Dict[ + Tuple[Type[Sentinel], Type[Sentinel]], Dict[Type[Sentinel], Type[Sentinel]] +] + +# NB: there are also some special-case state-triggered transitions hard-coded +# into _fire_state_triggered_transitions below. +STATE_TRIGGERED_TRANSITIONS: StateTransitionType = { + # (Client state, Server state) -> new states + # Protocol negotiation + (MIGHT_SWITCH_PROTOCOL, SWITCHED_PROTOCOL): {CLIENT: SWITCHED_PROTOCOL}, + # Socket shutdown + (CLOSED, DONE): {SERVER: MUST_CLOSE}, + (CLOSED, IDLE): {SERVER: MUST_CLOSE}, + (ERROR, DONE): {SERVER: MUST_CLOSE}, + (DONE, CLOSED): {CLIENT: MUST_CLOSE}, + (IDLE, CLOSED): {CLIENT: MUST_CLOSE}, + (DONE, ERROR): {CLIENT: MUST_CLOSE}, +} + + +class ConnectionState: + def __init__(self) -> None: + # Extra bits of state that don't quite fit into the state model. + + # If this is False then it enables the automatic DONE -> MUST_CLOSE + # transition. Don't set this directly; call .keep_alive_disabled() + self.keep_alive = True + + # This is a subset of {UPGRADE, CONNECT}, containing the proposals + # made by the client for switching protocols. + self.pending_switch_proposals: Set[Type[Sentinel]] = set() + + self.states: Dict[Type[Sentinel], Type[Sentinel]] = {CLIENT: IDLE, SERVER: IDLE} + + def process_error(self, role: Type[Sentinel]) -> None: + self.states[role] = ERROR + self._fire_state_triggered_transitions() + + def process_keep_alive_disabled(self) -> None: + self.keep_alive = False + self._fire_state_triggered_transitions() + + def process_client_switch_proposal(self, switch_event: Type[Sentinel]) -> None: + self.pending_switch_proposals.add(switch_event) + self._fire_state_triggered_transitions() + + def process_event( + self, + role: Type[Sentinel], + event_type: Type[Event], + server_switch_event: Optional[Type[Sentinel]] = None, + ) -> None: + _event_type: Union[Type[Event], Tuple[Type[Event], Type[Sentinel]]] = event_type + if server_switch_event is not None: + assert role is SERVER + if server_switch_event not in self.pending_switch_proposals: + raise LocalProtocolError( + "Received server _SWITCH_UPGRADE event without a pending proposal" + ) + _event_type = (event_type, server_switch_event) + if server_switch_event is None and _event_type is Response: + self.pending_switch_proposals = set() + self._fire_event_triggered_transitions(role, _event_type) + # Special case: the server state does get to see Request + # events. + if _event_type is Request: + assert role is CLIENT + self._fire_event_triggered_transitions(SERVER, (Request, CLIENT)) + self._fire_state_triggered_transitions() + + def _fire_event_triggered_transitions( + self, + role: Type[Sentinel], + event_type: Union[Type[Event], Tuple[Type[Event], Type[Sentinel]]], + ) -> None: + state = self.states[role] + try: + new_state = EVENT_TRIGGERED_TRANSITIONS[role][state][event_type] + except KeyError: + event_type = cast(Type[Event], event_type) + raise LocalProtocolError( + "can't handle event type {} when role={} and state={}".format( + event_type.__name__, role, self.states[role] + ) + ) from None + self.states[role] = new_state + + def _fire_state_triggered_transitions(self) -> None: + # We apply these rules repeatedly until converging on a fixed point + while True: + start_states = dict(self.states) + + # It could happen that both these special-case transitions are + # enabled at the same time: + # + # DONE -> MIGHT_SWITCH_PROTOCOL + # DONE -> MUST_CLOSE + # + # For example, this will always be true of a HTTP/1.0 client + # requesting CONNECT. If this happens, the protocol switch takes + # priority. From there the client will either go to + # SWITCHED_PROTOCOL, in which case it's none of our business when + # they close the connection, or else the server will deny the + # request, in which case the client will go back to DONE and then + # from there to MUST_CLOSE. + if self.pending_switch_proposals: + if self.states[CLIENT] is DONE: + self.states[CLIENT] = MIGHT_SWITCH_PROTOCOL + + if not self.pending_switch_proposals: + if self.states[CLIENT] is MIGHT_SWITCH_PROTOCOL: + self.states[CLIENT] = DONE + + if not self.keep_alive: + for role in (CLIENT, SERVER): + if self.states[role] is DONE: + self.states[role] = MUST_CLOSE + + # Tabular state-triggered transitions + joint_state = (self.states[CLIENT], self.states[SERVER]) + changes = STATE_TRIGGERED_TRANSITIONS.get(joint_state, {}) + self.states.update(changes) + + if self.states == start_states: + # Fixed point reached + return + + def start_next_cycle(self) -> None: + if self.states != {CLIENT: DONE, SERVER: DONE}: + raise LocalProtocolError( + f"not in a reusable state. self.states={self.states}" + ) + # Can't reach DONE/DONE with any of these active, but still, let's be + # sure. + assert self.keep_alive + assert not self.pending_switch_proposals + self.states = {CLIENT: IDLE, SERVER: IDLE} diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/h11/_util.py b/bioagent-bench/.venv/lib/python3.14/site-packages/h11/_util.py new file mode 100644 index 0000000000000000000000000000000000000000..6718445290770e028ea2f1f662026c9a0b0991db --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/h11/_util.py @@ -0,0 +1,135 @@ +from typing import Any, Dict, NoReturn, Pattern, Tuple, Type, TypeVar, Union + +__all__ = [ + "ProtocolError", + "LocalProtocolError", + "RemoteProtocolError", + "validate", + "bytesify", +] + + +class ProtocolError(Exception): + """Exception indicating a violation of the HTTP/1.1 protocol. + + This as an abstract base class, with two concrete base classes: + :exc:`LocalProtocolError`, which indicates that you tried to do something + that HTTP/1.1 says is illegal, and :exc:`RemoteProtocolError`, which + indicates that the remote peer tried to do something that HTTP/1.1 says is + illegal. See :ref:`error-handling` for details. + + In addition to the normal :exc:`Exception` features, it has one attribute: + + .. attribute:: error_status_hint + + This gives a suggestion as to what status code a server might use if + this error occurred as part of a request. + + For a :exc:`RemoteProtocolError`, this is useful as a suggestion for + how you might want to respond to a misbehaving peer, if you're + implementing a server. + + For a :exc:`LocalProtocolError`, this can be taken as a suggestion for + how your peer might have responded to *you* if h11 had allowed you to + continue. + + The default is 400 Bad Request, a generic catch-all for protocol + violations. + + """ + + def __init__(self, msg: str, error_status_hint: int = 400) -> None: + if type(self) is ProtocolError: + raise TypeError("tried to directly instantiate ProtocolError") + Exception.__init__(self, msg) + self.error_status_hint = error_status_hint + + +# Strategy: there are a number of public APIs where a LocalProtocolError can +# be raised (send(), all the different event constructors, ...), and only one +# public API where RemoteProtocolError can be raised +# (receive_data()). Therefore we always raise LocalProtocolError internally, +# and then receive_data will translate this into a RemoteProtocolError. +# +# Internally: +# LocalProtocolError is the generic "ProtocolError". +# Externally: +# LocalProtocolError is for local errors and RemoteProtocolError is for +# remote errors. +class LocalProtocolError(ProtocolError): + def _reraise_as_remote_protocol_error(self) -> NoReturn: + # After catching a LocalProtocolError, use this method to re-raise it + # as a RemoteProtocolError. This method must be called from inside an + # except: block. + # + # An easy way to get an equivalent RemoteProtocolError is just to + # modify 'self' in place. + self.__class__ = RemoteProtocolError # type: ignore + # But the re-raising is somewhat non-trivial -- you might think that + # now that we've modified the in-flight exception object, that just + # doing 'raise' to re-raise it would be enough. But it turns out that + # this doesn't work, because Python tracks the exception type + # (exc_info[0]) separately from the exception object (exc_info[1]), + # and we only modified the latter. So we really do need to re-raise + # the new type explicitly. + # On py3, the traceback is part of the exception object, so our + # in-place modification preserved it and we can just re-raise: + raise self + + +class RemoteProtocolError(ProtocolError): + pass + + +def validate( + regex: Pattern[bytes], data: bytes, msg: str = "malformed data", *format_args: Any +) -> Dict[str, bytes]: + match = regex.fullmatch(data) + if not match: + if format_args: + msg = msg.format(*format_args) + raise LocalProtocolError(msg) + return match.groupdict() + + +# Sentinel values +# +# - Inherit identity-based comparison and hashing from object +# - Have a nice repr +# - Have a *bonus property*: type(sentinel) is sentinel +# +# The bonus property is useful if you want to take the return value from +# next_event() and do some sort of dispatch based on type(event). + +_T_Sentinel = TypeVar("_T_Sentinel", bound="Sentinel") + + +class Sentinel(type): + def __new__( + cls: Type[_T_Sentinel], + name: str, + bases: Tuple[type, ...], + namespace: Dict[str, Any], + **kwds: Any + ) -> _T_Sentinel: + assert bases == (Sentinel,) + v = super().__new__(cls, name, bases, namespace, **kwds) + v.__class__ = v # type: ignore + return v + + def __repr__(self) -> str: + return self.__name__ + + +# Used for methods, request targets, HTTP versions, header names, and header +# values. Accepts ascii-strings, or bytes/bytearray/memoryview/..., and always +# returns bytes. +def bytesify(s: Union[bytes, bytearray, memoryview, int, str]) -> bytes: + # Fast-path: + if type(s) is bytes: + return s + if isinstance(s, str): + s = s.encode("ascii") + if isinstance(s, int): + raise TypeError("expected bytes-like object, not int") + return bytes(s) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/h11/_version.py b/bioagent-bench/.venv/lib/python3.14/site-packages/h11/_version.py new file mode 100644 index 0000000000000000000000000000000000000000..76e7327b8617c9d12236f511414d5eb58e98a44b --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/h11/_version.py @@ -0,0 +1,16 @@ +# This file must be kept very simple, because it is consumed from several +# places -- it is imported by h11/__init__.py, execfile'd by setup.py, etc. + +# We use a simple scheme: +# 1.0.0 -> 1.0.0+dev -> 1.1.0 -> 1.1.0+dev +# where the +dev versions are never released into the wild, they're just what +# we stick into the VCS in between releases. +# +# This is compatible with PEP 440: +# http://legacy.python.org/dev/peps/pep-0440/ +# via the use of the "local suffix" "+dev", which is disallowed on index +# servers and causes 1.0.0+dev to sort after plain 1.0.0, which is what we +# want. (Contrast with the special suffix 1.0.0.dev, which sorts *before* +# 1.0.0.) + +__version__ = "0.16.0" diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/h11/_writers.py b/bioagent-bench/.venv/lib/python3.14/site-packages/h11/_writers.py new file mode 100644 index 0000000000000000000000000000000000000000..939cdb912a9debaea07fbf3a9ac04549c44d077c --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/h11/_writers.py @@ -0,0 +1,145 @@ +# Code to read HTTP data +# +# Strategy: each writer takes an event + a write-some-bytes function, which is +# calls. +# +# WRITERS is a dict describing how to pick a reader. It maps states to either: +# - a writer +# - or, for body writers, a dict of framin-dependent writer factories + +from typing import Any, Callable, Dict, List, Tuple, Type, Union + +from ._events import Data, EndOfMessage, Event, InformationalResponse, Request, Response +from ._headers import Headers +from ._state import CLIENT, IDLE, SEND_BODY, SEND_RESPONSE, SERVER +from ._util import LocalProtocolError, Sentinel + +__all__ = ["WRITERS"] + +Writer = Callable[[bytes], Any] + + +def write_headers(headers: Headers, write: Writer) -> None: + # "Since the Host field-value is critical information for handling a + # request, a user agent SHOULD generate Host as the first header field + # following the request-line." - RFC 7230 + raw_items = headers._full_items + for raw_name, name, value in raw_items: + if name == b"host": + write(b"%s: %s\r\n" % (raw_name, value)) + for raw_name, name, value in raw_items: + if name != b"host": + write(b"%s: %s\r\n" % (raw_name, value)) + write(b"\r\n") + + +def write_request(request: Request, write: Writer) -> None: + if request.http_version != b"1.1": + raise LocalProtocolError("I only send HTTP/1.1") + write(b"%s %s HTTP/1.1\r\n" % (request.method, request.target)) + write_headers(request.headers, write) + + +# Shared between InformationalResponse and Response +def write_any_response( + response: Union[InformationalResponse, Response], write: Writer +) -> None: + if response.http_version != b"1.1": + raise LocalProtocolError("I only send HTTP/1.1") + status_bytes = str(response.status_code).encode("ascii") + # We don't bother sending ascii status messages like "OK"; they're + # optional and ignored by the protocol. (But the space after the numeric + # status code is mandatory.) + # + # XX FIXME: could at least make an effort to pull out the status message + # from stdlib's http.HTTPStatus table. Or maybe just steal their enums + # (either by import or copy/paste). We already accept them as status codes + # since they're of type IntEnum < int. + write(b"HTTP/1.1 %s %s\r\n" % (status_bytes, response.reason)) + write_headers(response.headers, write) + + +class BodyWriter: + def __call__(self, event: Event, write: Writer) -> None: + if type(event) is Data: + self.send_data(event.data, write) + elif type(event) is EndOfMessage: + self.send_eom(event.headers, write) + else: # pragma: no cover + assert False + + def send_data(self, data: bytes, write: Writer) -> None: + pass + + def send_eom(self, headers: Headers, write: Writer) -> None: + pass + + +# +# These are all careful not to do anything to 'data' except call len(data) and +# write(data). This allows us to transparently pass-through funny objects, +# like placeholder objects referring to files on disk that will be sent via +# sendfile(2). +# +class ContentLengthWriter(BodyWriter): + def __init__(self, length: int) -> None: + self._length = length + + def send_data(self, data: bytes, write: Writer) -> None: + self._length -= len(data) + if self._length < 0: + raise LocalProtocolError("Too much data for declared Content-Length") + write(data) + + def send_eom(self, headers: Headers, write: Writer) -> None: + if self._length != 0: + raise LocalProtocolError("Too little data for declared Content-Length") + if headers: + raise LocalProtocolError("Content-Length and trailers don't mix") + + +class ChunkedWriter(BodyWriter): + def send_data(self, data: bytes, write: Writer) -> None: + # if we encoded 0-length data in the naive way, it would look like an + # end-of-message. + if not data: + return + write(b"%x\r\n" % len(data)) + write(data) + write(b"\r\n") + + def send_eom(self, headers: Headers, write: Writer) -> None: + write(b"0\r\n") + write_headers(headers, write) + + +class Http10Writer(BodyWriter): + def send_data(self, data: bytes, write: Writer) -> None: + write(data) + + def send_eom(self, headers: Headers, write: Writer) -> None: + if headers: + raise LocalProtocolError("can't send trailers to HTTP/1.0 client") + # no need to close the socket ourselves, that will be taken care of by + # Connection: close machinery + + +WritersType = Dict[ + Union[Tuple[Type[Sentinel], Type[Sentinel]], Type[Sentinel]], + Union[ + Dict[str, Type[BodyWriter]], + Callable[[Union[InformationalResponse, Response], Writer], None], + Callable[[Request, Writer], None], + ], +] + +WRITERS: WritersType = { + (CLIENT, IDLE): write_request, + (SERVER, IDLE): write_any_response, + (SERVER, SEND_RESPONSE): write_any_response, + SEND_BODY: { + "chunked": ChunkedWriter, + "content-length": ContentLengthWriter, + "http/1.0": Http10Writer, + }, +} diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/h11/py.typed b/bioagent-bench/.venv/lib/python3.14/site-packages/h11/py.typed new file mode 100644 index 0000000000000000000000000000000000000000..f5642f79f21d872f010979dcf6f0c4a415acc19d --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/h11/py.typed @@ -0,0 +1 @@ +Marker diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/httpx-0.28.1.dist-info/licenses/LICENSE.md b/bioagent-bench/.venv/lib/python3.14/site-packages/httpx-0.28.1.dist-info/licenses/LICENSE.md new file mode 100644 index 0000000000000000000000000000000000000000..ab79d16a3f4c6c894c028d1f7431811e8711b42b --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/httpx-0.28.1.dist-info/licenses/LICENSE.md @@ -0,0 +1,12 @@ +Copyright © 2019, [Encode OSS Ltd](https://www.encode.io/). +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/huggingface_hub-1.14.0.dist-info/INSTALLER b/bioagent-bench/.venv/lib/python3.14/site-packages/huggingface_hub-1.14.0.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..5c69047b2eb8235994febeeae1da4a82365a240a --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/huggingface_hub-1.14.0.dist-info/INSTALLER @@ -0,0 +1 @@ +uv \ No newline at end of file diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/huggingface_hub-1.14.0.dist-info/METADATA b/bioagent-bench/.venv/lib/python3.14/site-packages/huggingface_hub-1.14.0.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..40868c8c1b8a480dbb5856e892cc64b454315232 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/huggingface_hub-1.14.0.dist-info/METADATA @@ -0,0 +1,324 @@ +Metadata-Version: 2.4 +Name: huggingface_hub +Version: 1.14.0 +Summary: Client library to download and publish models, datasets and other repos on the huggingface.co hub +Home-page: https://github.com/huggingface/huggingface_hub +Author: Hugging Face, Inc. +Author-email: julien@huggingface.co +License: Apache-2.0 +Keywords: model-hub machine-learning models natural-language-processing deep-learning pytorch pretrained-models +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: Education +Classifier: Intended Audience :: Science/Research +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence +Requires-Python: >=3.10.0 +Description-Content-Type: text/markdown +License-File: LICENSE +Requires-Dist: filelock>=3.10.0 +Requires-Dist: fsspec>=2023.5.0 +Requires-Dist: hf-xet<2.0.0,>=1.4.3; platform_machine == "x86_64" or platform_machine == "amd64" or platform_machine == "AMD64" or platform_machine == "arm64" or platform_machine == "aarch64" +Requires-Dist: httpx<1,>=0.23.0 +Requires-Dist: packaging>=20.9 +Requires-Dist: pyyaml>=5.1 +Requires-Dist: tqdm>=4.42.1 +Requires-Dist: typer>=0.20.0 +Requires-Dist: typing-extensions>=4.1.0 +Provides-Extra: oauth +Requires-Dist: authlib>=1.3.2; extra == "oauth" +Requires-Dist: fastapi; extra == "oauth" +Requires-Dist: httpx; extra == "oauth" +Requires-Dist: itsdangerous; extra == "oauth" +Provides-Extra: torch +Requires-Dist: torch; extra == "torch" +Requires-Dist: safetensors[torch]; extra == "torch" +Provides-Extra: fastai +Requires-Dist: toml; extra == "fastai" +Requires-Dist: fastai>=2.4; extra == "fastai" +Requires-Dist: fastcore>=1.3.27; extra == "fastai" +Provides-Extra: hf-xet +Requires-Dist: hf-xet<2.0.0,>=1.4.3; extra == "hf-xet" +Provides-Extra: mcp +Requires-Dist: mcp>=1.8.0; extra == "mcp" +Provides-Extra: testing +Requires-Dist: authlib>=1.3.2; extra == "testing" +Requires-Dist: fastapi; extra == "testing" +Requires-Dist: httpx; extra == "testing" +Requires-Dist: itsdangerous; extra == "testing" +Requires-Dist: jedi; extra == "testing" +Requires-Dist: Jinja2; extra == "testing" +Requires-Dist: pytest>=8.4.2; extra == "testing" +Requires-Dist: pytest-cov; extra == "testing" +Requires-Dist: pytest-env; extra == "testing" +Requires-Dist: pytest-xdist; extra == "testing" +Requires-Dist: pytest-vcr; extra == "testing" +Requires-Dist: pytest-asyncio; extra == "testing" +Requires-Dist: pytest-rerunfailures<16.0; extra == "testing" +Requires-Dist: pytest-mock; extra == "testing" +Requires-Dist: urllib3<2.0; extra == "testing" +Requires-Dist: soundfile; extra == "testing" +Requires-Dist: Pillow; extra == "testing" +Requires-Dist: numpy; extra == "testing" +Requires-Dist: duckdb; extra == "testing" +Requires-Dist: fastapi; extra == "testing" +Provides-Extra: gradio +Requires-Dist: gradio>=5.0.0; extra == "gradio" +Requires-Dist: requests; extra == "gradio" +Provides-Extra: typing +Requires-Dist: typing-extensions>=4.8.0; extra == "typing" +Requires-Dist: types-PyYAML; extra == "typing" +Requires-Dist: types-simplejson; extra == "typing" +Requires-Dist: types-toml; extra == "typing" +Requires-Dist: types-tqdm; extra == "typing" +Requires-Dist: types-urllib3; extra == "typing" +Provides-Extra: quality +Requires-Dist: ruff>=0.9.0; extra == "quality" +Requires-Dist: mypy==1.15.0; extra == "quality" +Requires-Dist: libcst>=1.4.0; extra == "quality" +Requires-Dist: ty; extra == "quality" +Provides-Extra: all +Requires-Dist: authlib>=1.3.2; extra == "all" +Requires-Dist: fastapi; extra == "all" +Requires-Dist: httpx; extra == "all" +Requires-Dist: itsdangerous; extra == "all" +Requires-Dist: jedi; extra == "all" +Requires-Dist: Jinja2; extra == "all" +Requires-Dist: pytest>=8.4.2; extra == "all" +Requires-Dist: pytest-cov; extra == "all" +Requires-Dist: pytest-env; extra == "all" +Requires-Dist: pytest-xdist; extra == "all" +Requires-Dist: pytest-vcr; extra == "all" +Requires-Dist: pytest-asyncio; extra == "all" +Requires-Dist: pytest-rerunfailures<16.0; extra == "all" +Requires-Dist: pytest-mock; extra == "all" +Requires-Dist: urllib3<2.0; extra == "all" +Requires-Dist: soundfile; extra == "all" +Requires-Dist: Pillow; extra == "all" +Requires-Dist: numpy; extra == "all" +Requires-Dist: duckdb; extra == "all" +Requires-Dist: fastapi; extra == "all" +Requires-Dist: ruff>=0.9.0; extra == "all" +Requires-Dist: mypy==1.15.0; extra == "all" +Requires-Dist: libcst>=1.4.0; extra == "all" +Requires-Dist: ty; extra == "all" +Requires-Dist: typing-extensions>=4.8.0; extra == "all" +Requires-Dist: types-PyYAML; extra == "all" +Requires-Dist: types-simplejson; extra == "all" +Requires-Dist: types-toml; extra == "all" +Requires-Dist: types-tqdm; extra == "all" +Requires-Dist: types-urllib3; extra == "all" +Provides-Extra: dev +Requires-Dist: authlib>=1.3.2; extra == "dev" +Requires-Dist: fastapi; extra == "dev" +Requires-Dist: httpx; extra == "dev" +Requires-Dist: itsdangerous; extra == "dev" +Requires-Dist: jedi; extra == "dev" +Requires-Dist: Jinja2; extra == "dev" +Requires-Dist: pytest>=8.4.2; extra == "dev" +Requires-Dist: pytest-cov; extra == "dev" +Requires-Dist: pytest-env; extra == "dev" +Requires-Dist: pytest-xdist; extra == "dev" +Requires-Dist: pytest-vcr; extra == "dev" +Requires-Dist: pytest-asyncio; extra == "dev" +Requires-Dist: pytest-rerunfailures<16.0; extra == "dev" +Requires-Dist: pytest-mock; extra == "dev" +Requires-Dist: urllib3<2.0; extra == "dev" +Requires-Dist: soundfile; extra == "dev" +Requires-Dist: Pillow; extra == "dev" +Requires-Dist: numpy; extra == "dev" +Requires-Dist: duckdb; extra == "dev" +Requires-Dist: fastapi; extra == "dev" +Requires-Dist: ruff>=0.9.0; extra == "dev" +Requires-Dist: mypy==1.15.0; extra == "dev" +Requires-Dist: libcst>=1.4.0; extra == "dev" +Requires-Dist: ty; extra == "dev" +Requires-Dist: typing-extensions>=4.8.0; extra == "dev" +Requires-Dist: types-PyYAML; extra == "dev" +Requires-Dist: types-simplejson; extra == "dev" +Requires-Dist: types-toml; extra == "dev" +Requires-Dist: types-tqdm; extra == "dev" +Requires-Dist: types-urllib3; extra == "dev" +Dynamic: author +Dynamic: author-email +Dynamic: classifier +Dynamic: description +Dynamic: description-content-type +Dynamic: home-page +Dynamic: keywords +Dynamic: license +Dynamic: license-file +Dynamic: provides-extra +Dynamic: requires-dist +Dynamic: requires-python +Dynamic: summary + +

    + + + + huggingface_hub library logo + +
    +
    +

    + +

    + The official Python client for the Huggingface Hub. +

    + +

    + Documentation + GitHub release + PyPi version + PyPI - Downloads + Code coverage +

    + +

    +

    + English | + Deutsch | + Français | + हिंदी | + 한국어 | + 中文 (简体) +

    +

    + +--- + +**Documentation**: https://hf.co/docs/huggingface_hub + +**Source Code**: https://github.com/huggingface/huggingface_hub + +--- + +## Welcome to the huggingface_hub library + +The `huggingface_hub` library allows you to interact with the [Hugging Face Hub](https://huggingface.co/), a platform democratizing open-source Machine Learning for creators and collaborators. Discover pre-trained models and datasets for your projects or play with the thousands of machine learning apps hosted on the Hub. You can also create and share your own models, datasets and demos with the community. The `huggingface_hub` library provides a simple way to do all these things with Python. + +## Key features + +- [Download files](https://huggingface.co/docs/huggingface_hub/en/guides/download) from the Hub. +- [Upload files](https://huggingface.co/docs/huggingface_hub/en/guides/upload) to the Hub. +- [Manage your repositories](https://huggingface.co/docs/huggingface_hub/en/guides/repository). +- [Run Inference](https://huggingface.co/docs/huggingface_hub/en/guides/inference) on deployed models. +- [Search](https://huggingface.co/docs/huggingface_hub/en/guides/search) for models, datasets and Spaces. +- [Share Model Cards](https://huggingface.co/docs/huggingface_hub/en/guides/model-cards) to document your models. +- [Engage with the community](https://huggingface.co/docs/huggingface_hub/en/guides/community) through PRs and comments. + +## Installation + +Install the `huggingface_hub` package with [pip](https://pypi.org/project/huggingface-hub/): + +```bash +pip install huggingface_hub +``` + +If you prefer, you can also install it with [conda](https://huggingface.co/docs/huggingface_hub/en/installation#install-with-conda). + +In order to keep the package minimal by default, `huggingface_hub` comes with optional dependencies useful for some use cases. For example, if you want to use the MCP module, run: + +```bash +pip install "huggingface_hub[mcp]" +``` + +To learn more installation and optional dependencies, check out the [installation guide](https://huggingface.co/docs/huggingface_hub/en/installation). + +## Quick start + +### Download files + +Download a single file + +```py +from huggingface_hub import hf_hub_download + +hf_hub_download(repo_id="tiiuae/falcon-7b-instruct", filename="config.json") +``` + +Or an entire repository + +```py +from huggingface_hub import snapshot_download + +snapshot_download("stabilityai/stable-diffusion-2-1") +``` + +Files will be downloaded in a local cache folder. More details in [this guide](https://huggingface.co/docs/huggingface_hub/en/guides/manage-cache). + +### Login + +The Hugging Face Hub uses tokens to authenticate applications (see [docs](https://huggingface.co/docs/hub/security-tokens)). To log in your machine, run the following CLI: + +```bash +hf auth login +# or using an environment variable +hf auth login --token $HUGGINGFACE_TOKEN +``` + +### Create a repository + +```py +from huggingface_hub import create_repo + +create_repo(repo_id="super-cool-model") +``` + +### Upload files + +Upload a single file + +```py +from huggingface_hub import upload_file + +upload_file( + path_or_fileobj="/home/lysandre/dummy-test/README.md", + path_in_repo="README.md", + repo_id="lysandre/test-model", +) +``` + +Or an entire folder + +```py +from huggingface_hub import upload_folder + +upload_folder( + folder_path="/path/to/local/space", + repo_id="username/my-cool-space", + repo_type="space", +) +``` + +For details in the [upload guide](https://huggingface.co/docs/huggingface_hub/en/guides/upload). + +## Integrating to the Hub. + +We're partnering with cool open source ML libraries to provide free model hosting and versioning. You can find the existing integrations [here](https://huggingface.co/docs/hub/libraries). + +The advantages are: + +- Free model or dataset hosting for libraries and their users. +- Built-in file versioning, even with very large files, thanks to a git-based approach. +- In-browser widgets to play with the uploaded models. +- Anyone can upload a new model for your library, they just need to add the corresponding tag for the model to be discoverable. +- Fast downloads! We use Cloudfront (a CDN) to geo-replicate downloads so they're blazing fast from anywhere on the globe. +- Usage stats and more features to come. + +If you would like to integrate your library, feel free to open an issue to begin the discussion. We wrote a [step-by-step guide](https://huggingface.co/docs/hub/adding-a-library) with ❤️ showing how to do this integration. + +## Contributions (feature requests, bugs, etc.) are super welcome 💙💚💛💜🧡❤️ + +Everyone is welcome to contribute, and we value everybody's contribution. Code is not the only way to help the community. +Answering questions, helping others, reaching out and improving the documentations are immensely valuable to the community. +We wrote a [contribution guide](https://github.com/huggingface/huggingface_hub/blob/main/CONTRIBUTING.md) to summarize +how to get started to contribute to this repository. diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/huggingface_hub-1.14.0.dist-info/RECORD b/bioagent-bench/.venv/lib/python3.14/site-packages/huggingface_hub-1.14.0.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..e964a6a57e7ea6a8d71c249b4c2f8540514bbe21 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/huggingface_hub-1.14.0.dist-info/RECORD @@ -0,0 +1,189 @@ +../../../bin/hf,sha256=tPCMkA8LcW70C1F35LYZ-Sk3lDEaoppkGc70iQAqCko,338 +../../../bin/huggingface-cli,sha256=neBJFodOfhchPAXpZQJYObJAvztdhHXj4yqEGbf9deE,350 +../../../bin/tiny-agents,sha256=Wh9DtDkq2RUTiGIjfmwkUWHUikD8oNwWkjnNMG9DpRQ,348 +huggingface_hub-1.14.0.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2 +huggingface_hub-1.14.0.dist-info/METADATA,sha256=NGKGEcwoHsM57iwi1I0nz9hp3WP7M8nTgnQx3W_m0Ik,14025 +huggingface_hub-1.14.0.dist-info/RECORD,, +huggingface_hub-1.14.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +huggingface_hub-1.14.0.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91 +huggingface_hub-1.14.0.dist-info/entry_points.txt,sha256=zP7F_bBSdircPQFysHQZ9F3Lcn5_dCSOEZxVlGCsG0w,212 +huggingface_hub-1.14.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357 +huggingface_hub-1.14.0.dist-info/top_level.txt,sha256=8KzlQJAY4miUvjAssOAJodqKOw3harNzuiwGQ9qLSSk,16 +huggingface_hub/__init__.py,sha256=oI1-0abcGlCY_WJVgCHNQxgObpR0dnVMCpYbyN_ZRPI,58431 +huggingface_hub/_buckets.py,sha256=gqybQRY6R02kwyhMCH6VILQLo3Nd6m5XqhOQnfJMHU8,44899 +huggingface_hub/_commit_api.py,sha256=cEdBcUeL6e2mb2KSTXV9A5bQPuQ48rF1nhJQ4DqC2-c,40992 +huggingface_hub/_commit_scheduler.py,sha256=VG3zjb7xb66NrFMq_pB9dWIsoqSPoF6CntovV7vUiWg,14684 +huggingface_hub/_dataset_viewer.py,sha256=LE9k4s0WJ0JNdriJ8L6qV_-xfbUVC1VEh7CBd0jOjLI,5675 +huggingface_hub/_eval_results.py,sha256=Og7p7bCl2HQG6ZWlSDyir-CRTXjNWPu3S3q65cTGffs,8126 +huggingface_hub/_hot_reload/__init__.py,sha256=nQEuyVZLAS_463Dff1JH5MorTpkYsBVFCyPhYWY3UyI,606 +huggingface_hub/_hot_reload/client.py,sha256=Ax7qT4CUi5wrg3mDzqkAG8ZzDsCwdcIIS1FIXK0SqD8,4269 +huggingface_hub/_hot_reload/sse_client.py,sha256=49kmsB2P9sxgbZ16ed6F4aLDs3rsHrNpbp6iKlvF90c,4948 +huggingface_hub/_hot_reload/types.py,sha256=d3AOJCLUxT41Fhz4jUrFevm9bfpooe_3PQhpBdgNYSs,2725 +huggingface_hub/_inference_endpoints.py,sha256=rwetck_MnbV-MrTLVN7cazIrxUNYi5x2nBkw1devnxY,17643 +huggingface_hub/_jobs_api.py,sha256=XSmWO4MfXalDTfJ9wyrtV6wPW9I_2PiXGEuuMXylAhQ,14479 +huggingface_hub/_local_folder.py,sha256=Fc_hikyMaZRHtXIDZj38hL7MNkq5nZZVxQF-dd-EkMo,18279 +huggingface_hub/_login.py,sha256=Y4n1dlXYBr_6hrTv6X_Sx5iR2SR9_2NgFZ5t8grWmgk,19447 +huggingface_hub/_oauth.py,sha256=S8evEd8JLJHz-JiSjGH4WiIi_A7oDox_xH7E9rN1Jic,18628 +huggingface_hub/_snapshot_download.py,sha256=KqiEBSM8CvVXFAZHUS1lINN94WK05q9-exNWrzYO6kE,20614 +huggingface_hub/_space_api.py,sha256=Sz_v5i8uPl8EUBAzrN47bphQL7ElRd_6B603b5krxDM,12177 +huggingface_hub/_tensorboard_logger.py,sha256=GA-LhoG4Z-0Zq7w5B9vTIDJbWUkAhiG2d8a-ktlwGDY,8377 +huggingface_hub/_upload_large_folder.py,sha256=BWA1zyOK9W_3YUerX9YWQa3wAYtHRe_ypgsO79JEkqo,31235 +huggingface_hub/_webhooks_payload.py,sha256=B9zqljcm20DGeVxJKba570i6RP-cR9CEupMGBuNDHHA,3544 +huggingface_hub/_webhooks_server.py,sha256=A172KmELV-pGjA819D4UgOHcuvHxCFdwOLPlu2kqHa4,15668 +huggingface_hub/cli/__init__.py,sha256=A4zmzuHD2OHjQ5zmdfcnsj0JeCzHVPtpzh-wCjInugA,606 +huggingface_hub/cli/_cli_utils.py,sha256=74tG3KKfBImta8qooCIfdCGwd3jJmqJGsNbyDy_ZHrc,45989 +huggingface_hub/cli/_errors.py,sha256=iVc59bPwNUy7vmE_02vCsqJLbCDli_W7fneHQ-0qVfQ,4494 +huggingface_hub/cli/_file_listing.py,sha256=20SGu3-PRJiGZu3wPEMzf8lKgf1eMTLxuF-q59KJeAE,7950 +huggingface_hub/cli/_output.py,sha256=QmoT4Jngi7XM2zNmumwbZwKmNHzsIvXaZLIVEgooyN4,10635 +huggingface_hub/cli/_skills.py,sha256=MtX4fH3ZhvR4YZ8jw9lzUzBSS7MtOY3T4SfGGYuFx3E,9811 +huggingface_hub/cli/auth.py,sha256=N-u8T1pld-ZrY7X9PKtyFO0d8qcj8c0C9PVoCyTkCSo,5311 +huggingface_hub/cli/buckets.py,sha256=kBhuaAFT6av9WNElmmzuPNeZV4mjfCtR6vrCaMjSL-Y,26668 +huggingface_hub/cli/cache.py,sha256=FHl9lWIA2byWKdjn3eoa8IPoHhFDqmXJDHliAPJiZu4,26198 +huggingface_hub/cli/collections.py,sha256=ttCZBumqb6p3GAqdW36Ux02h2h2ey7m9qCZPZJVelec,10775 +huggingface_hub/cli/datasets.py,sha256=XK_wgrArGuF3AcRIpMJkqv3VwtGppyIiFBE1EwH3ne4,10816 +huggingface_hub/cli/deprecated_cli.py,sha256=vAK_CR4NE9uzNJ0I59SyBnIln5nlypG4CPoohMoPMVg,1090 +huggingface_hub/cli/discussions.py,sha256=mLUZrTW8h40TLt4pIa4qmBkJusVt970z15fcnwo3uSY,12654 +huggingface_hub/cli/download.py,sha256=rXKUBcWayWltonlGQ-vmwFkR-ht036BBE6kHsdf184o,7953 +huggingface_hub/cli/extensions.py,sha256=yQwyQdm-k9OVsezvG99COxl27-YsjoXuB3dR70-Lnqc,22030 +huggingface_hub/cli/hf.py,sha256=71mAgK3VnjV4nRZkVPcSEthnUM-fZgCVvftFAb95kBE,4562 +huggingface_hub/cli/inference_endpoints.py,sha256=0T2FMOo3kIrlJeTjtxgnE1-9BKlelzm0m2g_0jB62UA,14040 +huggingface_hub/cli/jobs.py,sha256=VeshuYeSYos2r4AoIhlenqwvSpJ1PKVXY7A5nW9OyQo,38530 +huggingface_hub/cli/lfs.py,sha256=v2yBjwBNOg0WAr6WtT5wWlTxut8OUVmcQ0ryG99OtCA,5876 +huggingface_hub/cli/models.py,sha256=xdiy8l9ECuY0TSUOmfBdryJAy-hjV7tgwg3T7oAUWms,7747 +huggingface_hub/cli/papers.py,sha256=3MR7hZbvH3BxldCbdbbsNh5o7q85wXSHDQhBn9AK_eY,5747 +huggingface_hub/cli/repo_files.py,sha256=0VdacUlJH_fxCMrVutBt-rtOTjYk_2AhlbW5xlAeKck,2440 +huggingface_hub/cli/repos.py,sha256=77WRElbQ2n4U7fkmAaZg-ryxO4tV5EtvrP0jfYpy-vY,16195 +huggingface_hub/cli/skills.py,sha256=lxJEX3rwoypAzBiKOGMCY5_DlUXJzKtnISmOteoEki0,17909 +huggingface_hub/cli/spaces.py,sha256=GBTcYS7mpNuGUwEIQ0jnNMBBzT-3tgay9YEHLpSHrNY,38098 +huggingface_hub/cli/system.py,sha256=YJmwZcy-ffDKq2MVVXJt1W0_u2Yyua5koPe1Cvma4D4,1723 +huggingface_hub/cli/upload.py,sha256=DXhdx0-Lapiu-HLJg8iP5Mr9xN50sQP2pSNajGH2Lok,10949 +huggingface_hub/cli/upload_large_folder.py,sha256=WDhKyGeH5FqAkesfzCla-nRVQHl6spOVc9VD-iHtcd8,4575 +huggingface_hub/cli/webhooks.py,sha256=uO2t1Kgc4DXy_Ut2nWK3uDlvFrKmqvLUSjb68kbafA0,9268 +huggingface_hub/community.py,sha256=N5OE5Rbc2BMwpNXCFOPQAwn0otqQunsXdEnSE_0J2Xo,12325 +huggingface_hub/constants.py,sha256=zu42eXu8shEy_JGffr7TM83XKWugv_Qca3AK_fn8eNU,11697 +huggingface_hub/dataclasses.py,sha256=LoEOPRQTXgr9V7OPWvieADLuflW47l6THGaduntlqx4,26047 +huggingface_hub/errors.py,sha256=OVdrv_WpfyOHinFP7_RhZncwafVE0CoFNj0tTsHEC8A,14739 +huggingface_hub/fastai_utils.py,sha256=jwKeVXH_VP0zxnOKVNQCVCkF-stH70l-bExbbrYkBKQ,16583 +huggingface_hub/file_download.py,sha256=9_lRpeb7s440EDss4VRlOd2hmwdgQ5oBvtpCpj4W0h8,81520 +huggingface_hub/hf_api.py,sha256=LxRs7YOX4Onz28Kv7qJAwdrRUjMXb3W5Odcezxd8_dc,616527 +huggingface_hub/hf_file_system.py,sha256=hFpVPJgLyfmkOayXEkiYb7yR_hqRS_JYU9o76J2tE6E,61802 +huggingface_hub/hub_mixin.py,sha256=ljKAtm3qQsY-ARtxY-PCFx0koFXLI37JytMrzpqfnQo,36912 +huggingface_hub/inference/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +huggingface_hub/inference/_client.py,sha256=yu99zMNIkXBEpq7WFdRZ8sy1ZICIXI-DwCDxjr17_as,157777 +huggingface_hub/inference/_common.py,sha256=X-fBP8s6xuNTAuQsGVIethE8dNQaa-mK7Ds54qVVDz8,14999 +huggingface_hub/inference/_generated/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +huggingface_hub/inference/_generated/_async_client.py,sha256=xST7gThlcdS50Ky6KiTo5RukiS5FaFECPi33uaHNnQ4,160988 +huggingface_hub/inference/_generated/types/__init__.py,sha256=e625ENOmTUM4uUL1eAlsZOK7CovNoGBDH-TxSNQhaC4,6869 +huggingface_hub/inference/_generated/types/audio_classification.py,sha256=DGWEvrRPX7xpXlbYY4LMojGcULdEZFTshhNR81rTNcQ,1567 +huggingface_hub/inference/_generated/types/audio_to_audio.py,sha256=2Ep4WkePL7oJwcp5nRJqApwviumGHbft9HhXE9XLHj4,891 +huggingface_hub/inference/_generated/types/automatic_speech_recognition.py,sha256=X0uCj9qwAcOmlKtV3vWH5cAgLGY0PuYIy2eSH7GE1qs,5439 +huggingface_hub/inference/_generated/types/base.py,sha256=EPWp1qudjeEimEAyQ-ptdxpAACoRM8tW342-Ds7pP3I,6938 +huggingface_hub/inference/_generated/types/chat_completion.py,sha256=3yc06ZI0NCZzsaeoxSMWyIHqGc8Vn_6aExncuXa1llE,11100 +huggingface_hub/inference/_generated/types/depth_estimation.py,sha256=5Kn4xNGJg235Cofr1lzO59eRGCuLTZun8z5t6a2do8Q,910 +huggingface_hub/inference/_generated/types/document_question_answering.py,sha256=aUZcrDGexjw8Fnm2rACm_3my77V15NnQHZxj-sa7bjo,3146 +huggingface_hub/inference/_generated/types/feature_extraction.py,sha256=DiO47PqwhdNta_KPDBsXfCPIL6BWNhyCLymxORlqUA0,1509 +huggingface_hub/inference/_generated/types/fill_mask.py,sha256=NhEskgoiMst7U_0cV_S-wryga4_2T5LgpbyQ23vHMp0,1680 +huggingface_hub/inference/_generated/types/image_classification.py,sha256=xWr4OY65EcIIhd7MI610a7xlaAVHQeydi8Gc7tlaPTU,1579 +huggingface_hub/inference/_generated/types/image_segmentation.py,sha256=lZKzbn_gkSi1sYGLb9ryeRr0aDAVrfYh0H5Iiv5JNsY,1935 +huggingface_hub/inference/_generated/types/image_text_to_image.py,sha256=pRQgYKXJbIpEjRBxuIE6NRDtk_km9DSTQ2s2qxkYJRM,2585 +huggingface_hub/inference/_generated/types/image_text_to_video.py,sha256=MVhP8D4eYjpT3hWYkkpuiME-hWY09o7C4rIn_wmnAmY,2450 +huggingface_hub/inference/_generated/types/image_to_image.py,sha256=P9TeMFSnZI_pa6DyinzDPWL0PNm_INJwsqFUEzpwIcU,2262 +huggingface_hub/inference/_generated/types/image_to_text.py,sha256=DCDS0Nqi-4AuATitvZm4EgrzYQuQEQ5lzBcaHuCmHnY,4740 +huggingface_hub/inference/_generated/types/image_to_video.py,sha256=iMCjiFTUza9AW_MyJ1videhYgvMY9QSzQ59kNdTE9QY,2206 +huggingface_hub/inference/_generated/types/object_detection.py,sha256=zNILlyZJh59rNNfqXM5x9uprDCPfEU-bvBtabeKSMzI,1965 +huggingface_hub/inference/_generated/types/question_answering.py,sha256=lIRf8DjRiAsjzWUBEsGt354SB4bvejLokf-8luHw6_o,2845 +huggingface_hub/inference/_generated/types/sentence_similarity.py,sha256=eNGiO4dR-Lft9yOLE0E97jfVyOT_ExDWZW5GeJGdatU,1027 +huggingface_hub/inference/_generated/types/summarization.py,sha256=5Dpt6LRteT-hO9esa3Ty_-xSL5U-zTtVZzy3iKwZRng,1472 +huggingface_hub/inference/_generated/types/table_question_answering.py,sha256=bwn616XJ-eLa5JKzn3m9rZZ3z63INGEUwRSOCWlEh-4,2269 +huggingface_hub/inference/_generated/types/text2text_generation.py,sha256=fotSlPWtIlp_LG26AQvNRXxU-jCUfda5PWsxCkbFeKM,1591 +huggingface_hub/inference/_generated/types/text_classification.py,sha256=uamBzz4YzWQ9mb6yuriykX8r47ZHf0K3eJpOLzB2qJw,1439 +huggingface_hub/inference/_generated/types/text_generation.py,sha256=FRUljypvCSQ2RaiylhlHeLJfHNy8qZvuty6SCB_zcaA,5813 +huggingface_hub/inference/_generated/types/text_to_audio.py,sha256=CiaTUSBdFtOs3gGTosqyQrjCwrN8mVJLRxDDWUtwZpQ,4677 +huggingface_hub/inference/_generated/types/text_to_image.py,sha256=Ju25WIDHE-1n8jlZ_udG4rpAaPYpmghR7-cDgVfuf48,1869 +huggingface_hub/inference/_generated/types/text_to_speech.py,sha256=WqDzWkhZsDcjapkoFrkyjF903SKs7aHhjypsYrn8oBw,4693 +huggingface_hub/inference/_generated/types/text_to_video.py,sha256=KjcDbtChwrIxIdTH6vcesUaMcjL2xjPf2nWEnHazj4Y,1756 +huggingface_hub/inference/_generated/types/token_classification.py,sha256=8KRBlqgFDszaUnPMvdRkZNQaZBtDfBhKZ6SggQldQMM,1894 +huggingface_hub/inference/_generated/types/translation.py,sha256=-j2PC1-qfoxvGMaKesqf5jXelqj0BDlEpfvZ7dsIEJM,1742 +huggingface_hub/inference/_generated/types/video_classification.py,sha256=vfaCqp8MuRMMtGvYCX0x4-AcwEd71kPHgSYJTNIOxLQ,1668 +huggingface_hub/inference/_generated/types/visual_question_answering.py,sha256=sWIB-SuEpW21oU6O5qQF3tt5PavUFsFOa8Xg4upTtek,1654 +huggingface_hub/inference/_generated/types/zero_shot_classification.py,sha256=gx-7WHad7xsNa89iSKSteKHjaHrKbIyzN_nSdJN3HCY,1697 +huggingface_hub/inference/_generated/types/zero_shot_image_classification.py,sha256=JZTQvcbSGrOQlWcPuJvAMT8YziDpBRn1O2RQSw507uY,1449 +huggingface_hub/inference/_generated/types/zero_shot_object_detection.py,sha256=sjdpVUN5zW9aYBymLVUs6i5HVk2qkUBO9ysEjHmsXVM,1605 +huggingface_hub/inference/_mcp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +huggingface_hub/inference/_mcp/_cli_hacks.py,sha256=KX9HZJPa1p8ngY3mtYGGlVUXfg4vYbbBRs-8HLToP04,3284 +huggingface_hub/inference/_mcp/agent.py,sha256=ufIzMGHore5n252hV5GZPM0ouDXIl6tv5Jl_5gHXnbg,4250 +huggingface_hub/inference/_mcp/cli.py,sha256=YsabbtVJzQ4vNN5km86BYcLV0SgkgE3hM2pIt6BpyAA,9801 +huggingface_hub/inference/_mcp/constants.py,sha256=lLRgR6gRuqJ4u7jqngVUgVYeOzactgJpkTxx66t1piE,2463 +huggingface_hub/inference/_mcp/mcp_client.py,sha256=dGp8PhN6aVw4bDnuSySFSiguHUiz-nzhgv89CVdO7pI,17243 +huggingface_hub/inference/_mcp/types.py,sha256=yHNfPsM9MhD06oeKdkbmrBsW-3WhUeqA26fyfRfx_bk,929 +huggingface_hub/inference/_mcp/utils.py,sha256=gxSB_rBjQ6VrkApKFsxk6-UzhijxkDVNuZrsjW5pL8k,4318 +huggingface_hub/inference/_providers/__init__.py,sha256=sOsixpAIWprZYEuTmrNGHxMiZ75P7fj94rrIl99hVpI,10688 +huggingface_hub/inference/_providers/_common.py,sha256=9Ykgz6nT5ok-p7PpvnxkuNURR-yr85uP9B1wHwbSJ58,13815 +huggingface_hub/inference/_providers/black_forest_labs.py,sha256=HO3DaD97Hqy6b823b6VUPqiEqhHLKGeMVtJ4CMjGRPA,2817 +huggingface_hub/inference/_providers/cerebras.py,sha256=QOJ-1U-os7uE7p6eUnn_P_APq-yQhx28be7c3Tq2EuA,210 +huggingface_hub/inference/_providers/clarifai.py,sha256=1cEXQwhGk4DRKiPCQUa5y-L6okTo4781EImQC8yJVOw,380 +huggingface_hub/inference/_providers/cohere.py,sha256=P9kbIuvQ2rXI1yNmgbw5VKFFTE0huLq2k-BCyDkDico,1226 +huggingface_hub/inference/_providers/deepinfra.py,sha256=EaeeEMJCiKKuAdOjZwq0lWGonzBISr4ucErb6KpVCgE,1564 +huggingface_hub/inference/_providers/fal_ai.py,sha256=Y2vo5Dl3e8EoehplwNshu8LbDT1e7V4ZyhAMqs0wXws,11705 +huggingface_hub/inference/_providers/featherless_ai.py,sha256=C8OHdFpoFyA--pawLTekUmUSRq4sw_r0D-cSPekD4kE,1347 +huggingface_hub/inference/_providers/fireworks_ai.py,sha256=go6XPum8u0-g768HJ_r0S4Gb445gZJxnuY_acktz-9c,1188 +huggingface_hub/inference/_providers/groq.py,sha256=JTk2JV4ZOlaohho7zLAFQtk92kGVsPmLJ1hmzcwsqvQ,315 +huggingface_hub/inference/_providers/hf_inference.py,sha256=d2vdCKSi5CtyQqYndHHvH7SY131-7YHUK3yE7653eIc,9467 +huggingface_hub/inference/_providers/hyperbolic.py,sha256=Zk2rw2k-R0JxrfpcaHhAUuB5D6s40XK0tuvuKwglsUE,1950 +huggingface_hub/inference/_providers/nebius.py,sha256=OTPXQIYxQSwZc8FzhGAHUaa6Mkp3h6LphSVoa_1gY7I,3513 +huggingface_hub/inference/_providers/novita.py,sha256=ATEoSdPAPLMfx3JpBc0sOyLh4upJYP6xHBuo4YEDYvg,2470 +huggingface_hub/inference/_providers/nscale.py,sha256=T1L2JLI9LqYT9_YEdW76YEtxfDNtdGkoFhH-p2LmSxg,1767 +huggingface_hub/inference/_providers/nvidia.py,sha256=ocMuhycyIo8qiJf-E50oHGYuPvMcJC0BexSXBOYaOFg,251 +huggingface_hub/inference/_providers/openai.py,sha256=wwjaaQ55xLmDsDnHpZk52xbuLoGZfWzJkFsE8AdFVaI,1054 +huggingface_hub/inference/_providers/ovhcloud.py,sha256=tdmymlkbddMJKV7NRZ-tH2wymbLPFDTqUSXpWJUXyDQ,314 +huggingface_hub/inference/_providers/publicai.py,sha256=1I2W6rORloB5QHSvky4njZO2XKLTwA-kPdNoauoT5rg,210 +huggingface_hub/inference/_providers/replicate.py,sha256=hte0ZB2RtGFwpAuLrFp2Gbgsn3EOlccCnJkWTDTW__A,6027 +huggingface_hub/inference/_providers/sambanova.py,sha256=2dkhLf5nKGZ8hOqF-oWYHIN-Wckcwq0cMVf8UYgmDy4,1999 +huggingface_hub/inference/_providers/scaleway.py,sha256=MfIc7ZND1sPr__rOmNHZVg0VpECQo0bVyksgIm_32xQ,1174 +huggingface_hub/inference/_providers/together.py,sha256=OviPGPE6JvJwxf94DKpmsyWlryLU-W9OrHBcr36JT1w,3384 +huggingface_hub/inference/_providers/wavespeed.py,sha256=MGM7Y7r2nQiH_EH0t5UE1o1fT-o3sS7RaQlicS9WHsg,5028 +huggingface_hub/inference/_providers/zai_org.py,sha256=gLJZOEmCPmUZvdM7VDn2nxm4ac3veHdoMgQNR63UeWE,4739 +huggingface_hub/lfs.py,sha256=sVkRZMD8w-3vxaqSDSrsBCog6-lLyWP98u_xR1dIwMo,14123 +huggingface_hub/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +huggingface_hub/repocard.py,sha256=p2b9GiEzqrMJZW3O41nwiC6AseQhFNbqCbSIpoP_8VE,34829 +huggingface_hub/repocard_data.py,sha256=lI-DEIbmjtlOMvkntqwXA9ED4icTeCvNbAOYc2P2ZMk,33879 +huggingface_hub/serialization/__init__.py,sha256=jCiw_vVQYW52gwVfWiqgocf2Q19kGTQlRGVpf-4SLP8,963 +huggingface_hub/serialization/_base.py,sha256=-bhRW21ZIqza5gE_kWkL8ax-EE6L6xabKkkK35wOX0Y,8176 +huggingface_hub/serialization/_dduf.py,sha256=FmGRg5wkXI5sNCgZgCNJFmqb_nVrrQYOHf4Lqq0d64I,15385 +huggingface_hub/serialization/_torch.py,sha256=BtaCP-G3oYRP6Pj9MhI-ygBsk3JbBPnVDW9huSOgtkk,46897 +huggingface_hub/templates/datasetcard_template.md,sha256=W-EMqR6wndbrnZorkVv56URWPG49l7MATGeI015kTvs,5503 +huggingface_hub/templates/modelcard_template.md,sha256=4AqArS3cqdtbit5Bo-DhjcnDFR-pza5hErLLTPM4Yuc,6870 +huggingface_hub/utils/__init__.py,sha256=RW7Cq8t_YPvVk6DeFaV9cPEKXB5g31JbPQCFjYIoaDc,4050 +huggingface_hub/utils/_auth.py,sha256=DeSmNYy8UiAgrETNsHE5VZtzc-3-IKRqtFjxMxXBRdE,8227 +huggingface_hub/utils/_cache_assets.py,sha256=nnzHRtQAR50dQeIK6qKddsmjjTW9v9HZ7b9bq7PJqss,5691 +huggingface_hub/utils/_cache_manager.py,sha256=K3MfxR2we_uU2j5xLD1bMru6zihzDDlyZZm6mGUOxtM,33023 +huggingface_hub/utils/_chunk_utils.py,sha256=pTjy8Z-KLU4W_6D3OUh3E8lCodWDCd6aJwjNDU0C5U8,2121 +huggingface_hub/utils/_datetime.py,sha256=tbNyI0Dkh27oScPUtLIT_8apqIIkXZYbigjOn9S3aMw,2755 +huggingface_hub/utils/_deprecation.py,sha256=n4kNHbGipquSObJ-gxodcfd6lqoe_8s-VIsTuo3Oruk,4865 +huggingface_hub/utils/_detect_agent.py,sha256=tSfJZsIzrPZWYHV77UJY07KLiNkaoxAXaOmF9MXWkdI,3029 +huggingface_hub/utils/_dotenv.py,sha256=QfL6aWFp5NffhJEYFd6FtKHqly5EBpY8Cb-o9t8Ul04,1980 +huggingface_hub/utils/_experimental.py,sha256=q9vUvc1JybVFRQ0GRREG-BLouZEJC_40MxcwbAlOud0,2464 +huggingface_hub/utils/_fixes.py,sha256=jTK1VLmc0ZC9ROSXjKoL5F6kOZFByzirRWIdXxhBfWU,4124 +huggingface_hub/utils/_git_credential.py,sha256=1BhjvIScCOAToDORLOKrR3Szs-m0E5AYHPmC0SD7Nrs,4548 +huggingface_hub/utils/_headers.py,sha256=fHd8JflV1sX02mdQnwc7k1zll3ZXceu-HmmGz0UOEFo,8090 +huggingface_hub/utils/_hf_uris.py,sha256=i_4QCMtffC7XKn8X7N38OLiUyW8pdWhQmOlIECBUVP0,16464 +huggingface_hub/utils/_http.py,sha256=SCTMeZjKM9agYGfVfRjtT_eXKMNK9u1-RCSzBif_ojs,42652 +huggingface_hub/utils/_lfs.py,sha256=xMU-ROgNAUpDkzbH6yRZsE-eVYUYTNMGrgVb_QdTv-k,3942 +huggingface_hub/utils/_pagination.py,sha256=buIKERSyWOJqVSY9nv8hImDmNErD-PPOnXosXm2O87Y,1832 +huggingface_hub/utils/_parsing.py,sha256=Z07oEU-19L_0Mav7_LRZLz0z0NuS4bLDaAY1RPdeqf0,2982 +huggingface_hub/utils/_paths.py,sha256=ZceF9JnRzNrvrMSvPbgriuRQ3_LpLa3EE8W21bvBxaU,5265 +huggingface_hub/utils/_runtime.py,sha256=GVJ_Dt6y_48TZWl0ui9yPdW8M7rqGblRGyUqKaXxkCo,13493 +huggingface_hub/utils/_safetensors.py,sha256=qFE7OA-vjU8X0zBBtOeNRzLu147KF-PdFoaxkYfWB4M,4426 +huggingface_hub/utils/_subprocess.py,sha256=tFVBBNot_HLVqQ79y873TGb12C4PUTMwub-GhzDTemE,4542 +huggingface_hub/utils/_telemetry.py,sha256=Tpa3YmOLhK_MEnL2i2fRLQy8aqAEZWbj6MKC18Hs8BA,4824 +huggingface_hub/utils/_terminal.py,sha256=7jdWt1xvmx7nOlYG416833EjJyukvDzksTVfRFloAtg,3532 +huggingface_hub/utils/_typing.py,sha256=1LeE785YedppXSR9a1fQDu5rhTxX5v9kBYBaHrP65rA,3542 +huggingface_hub/utils/_validators.py,sha256=tuC1U4yxB-4XKIajZMnKZUSKvZ6qPpydY8LItfa0g40,8419 +huggingface_hub/utils/_verification.py,sha256=ZSilnolSkYmHB3vmBlWuuVr53xz59P5uGnxC3H-Hjc0,5434 +huggingface_hub/utils/_xet.py,sha256=v2VI_iVGIp4-mGT44L9nY9R4fdBeVepjUBzsVIeQS1g,9834 +huggingface_hub/utils/_xet_progress_reporting.py,sha256=IlrrHAgHYKsep4o9fhsCtdMFhKxp5rKoHs785x5q21k,6826 +huggingface_hub/utils/endpoint_helpers.py,sha256=9VtIAlxQ5H_4y30sjCAgbu7XCqAtNLC7aRYxaNn0hLI,2366 +huggingface_hub/utils/insecure_hashlib.py,sha256=z3dVUFvdBZ8kQI_8Vzvvlr3ims-EBiY-SYPdnzIKOkw,1008 +huggingface_hub/utils/logging.py,sha256=WaXk5gRa8Ml_LUIH34QCr8suYj8i_8Wot4IDnYJyWyM,4870 +huggingface_hub/utils/sha.py,sha256=h8wxheZpcv671RhtiIFcFmQTDSrogN3kwQx3ZaNEUHg,2121 +huggingface_hub/utils/tqdm.py,sha256=KbHhkB5g76JgGOGCcjAQOaR-oc-wzd4Sr3wqd41ny7k,13350 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/huggingface_hub-1.14.0.dist-info/REQUESTED b/bioagent-bench/.venv/lib/python3.14/site-packages/huggingface_hub-1.14.0.dist-info/REQUESTED new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/huggingface_hub-1.14.0.dist-info/WHEEL b/bioagent-bench/.venv/lib/python3.14/site-packages/huggingface_hub-1.14.0.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..8acb95590701b87bf84eec079cf4e3989f63b098 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/huggingface_hub-1.14.0.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (79.0.1) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/huggingface_hub-1.14.0.dist-info/entry_points.txt b/bioagent-bench/.venv/lib/python3.14/site-packages/huggingface_hub-1.14.0.dist-info/entry_points.txt new file mode 100644 index 0000000000000000000000000000000000000000..62018f2f5622f61920179a20b80488f21dbb1686 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/huggingface_hub-1.14.0.dist-info/entry_points.txt @@ -0,0 +1,7 @@ +[console_scripts] +hf = huggingface_hub.cli.hf:main +huggingface-cli = huggingface_hub.cli.deprecated_cli:main +tiny-agents = huggingface_hub.inference._mcp.cli:app + +[fsspec.specs] +hf = huggingface_hub.HfFileSystem diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/huggingface_hub-1.14.0.dist-info/licenses/LICENSE b/bioagent-bench/.venv/lib/python3.14/site-packages/huggingface_hub-1.14.0.dist-info/licenses/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..261eeb9e9f8b2b4b0d119366dda99c6fd7d35c64 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/huggingface_hub-1.14.0.dist-info/licenses/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/huggingface_hub-1.14.0.dist-info/top_level.txt b/bioagent-bench/.venv/lib/python3.14/site-packages/huggingface_hub-1.14.0.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..6b964ccca3c1b6766042b3fe3b2707ba25372924 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/huggingface_hub-1.14.0.dist-info/top_level.txt @@ -0,0 +1 @@ +huggingface_hub diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/huggingface_hub/cli/_cli_utils.py b/bioagent-bench/.venv/lib/python3.14/site-packages/huggingface_hub/cli/_cli_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..c18cbcecb7584bea02304d122f494c3a70639b5c --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/huggingface_hub/cli/_cli_utils.py @@ -0,0 +1,1208 @@ +# Copyright 2022 The HuggingFace Team. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Contains CLI utilities (styling, helpers).""" + +import dataclasses +import datetime +import difflib +import importlib.metadata +import json +import os +import re +import subprocess +import sys +import time +from collections.abc import Callable, Sequence +from enum import Enum +from pathlib import Path +from typing import TYPE_CHECKING, Annotated, Any, Literal, TypeVar, Union, cast + +import click +import typer +from typer.core import TyperCommand, TyperGroup + +from huggingface_hub import Volume, __version__, constants +from huggingface_hub.errors import CLIError +from huggingface_hub.utils import ( + get_session, + hf_raise_for_status, + installation_method, + logging, + tabulate, +) +from huggingface_hub.utils._dotenv import load_dotenv + +from ._output import OutputFormatWithAuto, out + + +logger = logging.get_logger() + +# Arbitrary maximum length of a cell in a table output +_MAX_CELL_LENGTH = 35 + +# Arbitrary default limit for models/datasets/spaces list commands. +REPO_LIST_DEFAULT_LIMIT = 30 + +if TYPE_CHECKING: + from huggingface_hub.hf_api import HfApi + + +def get_hf_api(token: str | None = None) -> "HfApi": + # Import here to avoid circular import + from huggingface_hub.hf_api import HfApi + + return HfApi(token=token, library_name="huggingface-cli", library_version=__version__) + + +#### TYPER UTILS + +CLI_REFERENCE_URL = "https://huggingface.co/docs/huggingface_hub/en/guides/cli" + + +def generate_epilog(examples: list[str], docs_anchor: str | None = None) -> str: + """Generate an epilog with examples and a Learn More section. + + Args: + examples: List of example commands (without the `$ ` prefix). + docs_anchor: Optional anchor for the docs URL (e.g., "#hf-download"). + + Returns: + Formatted epilog string. + """ + docs_url = f"{CLI_REFERENCE_URL}{docs_anchor}" if docs_anchor else CLI_REFERENCE_URL + examples_str = "\n".join(f" $ {ex}" for ex in examples) + return f"""\ +Examples +{examples_str} + +Learn more + Use `hf --help` for more information about a command. + Read the documentation at {docs_url} +""" + + +TOPIC_T = Literal["main", "help"] | str +FallbackHandlerT = Callable[[list[str], set[str]], int | None] +ExpandPropertyT = TypeVar("ExpandPropertyT", bound=str) + + +def _format_epilog_no_indent(epilog: str | None, ctx: click.Context, formatter: click.HelpFormatter) -> None: + """Write the epilog without indentation.""" + if epilog: + formatter.write_paragraph() + for line in epilog.split("\n"): + formatter.write_text(line) + + +_ALIAS_SPLIT = re.compile(r"\s*\|\s*") + + +class HFCliTyperGroup(TyperGroup): + """ + Typer Group that: + - lists commands alphabetically within sections. + - separates commands by topic (main, help, etc.). + - formats epilog without extra indentation. + - supports aliases via pipe-separated names (e.g. ``name="list | ls"``). + - consumes the global formatting flags (``--format``, ``--json``, ``-q`` / ``--quiet``) + anywhere in the args of a leaf command and applies them to ``out``, so leaf + commands don't need to declare these options themselves. + - rewrites ``spaces/user/repo`` to ``user/repo --type space`` for commands that accept ``--type``. + - enriches "No such option" / "No such command" errors with available options or commands. + """ + + def invoke(self, ctx: click.Context) -> None: + """Enrich unknown-option errors with available options or subcommands. + + Catches `NoSuchOption` raised during subcommand `make_context()` + (option parsing). For leaf commands (e.g. `hf repos create --test`) + we list the command's options; for groups (e.g. `hf cache --test`) + we list subcommands since groups have no user-facing options. + """ + try: + return super().invoke(ctx) + except click.NoSuchOption as e: + if e.ctx is not None and e.ctx.command is not None: + cmd = e.ctx.command + if isinstance(cmd, click.Group): + # Group has no user-facing options -> show subcommands instead + items = [ + (name, sub.get_short_help_str(limit=80)) + for name in cmd.list_commands(e.ctx) + if (sub := cmd.get_command(e.ctx, name)) is not None and not sub.hidden + ] + _enrich_usage_error(e, "commands", items) + else: + # Leaf command -> show its options using Click's rich formatting + items = [ + record + for p in cmd.get_params(e.ctx) + if isinstance(p, click.Option) and not p.hidden and (record := p.get_help_record(e.ctx)) + ] + _enrich_usage_error(e, "options", items) + raise + + def resolve_command(self, ctx: click.Context, args: list[str]) -> tuple: + cmd_name = args[0] if args and not args[0].startswith("-") else None + cmd = self.get_command(ctx, cmd_name) if cmd_name else None + + if cmd is not None: + self._rewrite_repo_type_prefix(cmd, args) + + try: + name, resolved_cmd, sub_args = super().resolve_command(ctx, args) + except click.UsageError as e: + # Unknown subcommand -> add fuzzy suggestions and list available commands. + if cmd is None and cmd_name is not None: + # Expand aliases ("list | ls" → ["list", "ls"]) for accurate fuzzy matching. + visible_names = [ + alias + for key, registered in self.commands.items() + if not registered.hidden + for alias in _ALIAS_SPLIT.split(key) + ] + matches = difflib.get_close_matches(cmd_name, visible_names) + if matches: + suggestions = ", ".join(f"'{m}'" for m in matches) + e.message = f"{e.message.rstrip('.')}. Did you mean {suggestions}?" + items = [ + (name, sub.get_short_help_str(limit=80)) + for name in self.list_commands(ctx) + if (sub := self.get_command(ctx, name)) is not None and not sub.hidden + ] + _enrich_usage_error(e, "commands", items) + raise + + # If we just resolved a leaf command, eagerly consume any global formatting + # flags (--format / --json / -q / --quiet) from its args before click parses + # them. Group resolution is recursive — leaves (and only leaves) need this. + if resolved_cmd is not None and not isinstance(resolved_cmd, click.Group): + _consume_format_flags_for_leaf(resolved_cmd, sub_args) + + return name, resolved_cmd, sub_args + + @staticmethod + def _rewrite_repo_type_prefix(cmd: click.Command, args: list[str]) -> None: + """Rewrite prefixed repo IDs (e.g. ``spaces/user/repo``) to ``user/repo --type space``. + + Only applies to commands that have a ``--type`` / ``--repo-type`` option and + at least one repo-ID positional argument (any ``click.Argument`` whose name + ends with ``_id``, e.g. ``repo_id``, ``from_id``, ``to_id``). When the + token that maps to such an argument matches ``{prefix}/org/repo`` (where + *prefix* is one of ``spaces``, ``datasets``, or ``models``), the prefix is + stripped and an implicit ``--type {type}`` is appended. An error is raised + if ``--type`` is also provided explicitly or if multiple prefixed arguments + disagree on the repo type. + + Only repo-ID positional slots are inspected so that other positional + arguments (filenames, local paths, patterns …) are never misinterpreted as + prefixed repo IDs. + """ + has_type_option = any(isinstance(param, click.Option) and "--type" in param.opts for param in cmd.params) + if not has_type_option: + return + + # Locate all repo-ID positional arguments and their indices among Arguments. + repo_id_positions: set[int] = set() + arg_idx = 0 + for param in cmd.params: + if isinstance(param, click.Argument): + if param.name in ("repo_id", "from_id", "to_id"): + repo_id_positions.add(arg_idx) + arg_idx += 1 + + if not repo_id_positions: + return + + # Build a set of option names that consume a following value token. + value_options: set[str] = set() + for param in cmd.params: + if isinstance(param, click.Option) and not param.is_flag: + for opt in (*param.opts, *param.secondary_opts): + value_options.add(opt) + + # Walk through args (skipping args[0] = command name) to map positional + # slots to their indices in `args`. + positional_count = 0 + repo_id_arg_indices: list[int] = [] + i = 1 + while i < len(args): + arg = args[i] + if arg == "--": + break # everything after -- is positional literal; stop rewriting + if arg.startswith("-"): + if "=" in arg or arg not in value_options: + i += 1 # flag or --opt=val — single token + else: + i += 2 # value-taking option — skip the value too + else: + if positional_count in repo_id_positions: + repo_id_arg_indices.append(i) + positional_count += 1 + i += 1 + + if not repo_id_arg_indices: + return + + # Check each repo-ID arg for a type prefix and collect rewrites. + inferred_type: str | None = None + first_prefix: str | None = None + rewrites: list[tuple[int, str]] = [] # (args index, new value without prefix) + + for arg_index in repo_id_arg_indices: + parts = args[arg_index].split("/", 2) + if len(parts) != 3 or parts[0] not in constants.REPO_TYPES_MAPPING: + continue + prefix = parts[0] + mapped_type = constants.REPO_TYPES_MAPPING[prefix] + if inferred_type is not None and mapped_type != inferred_type: + raise click.UsageError(f"Conflicting repo type prefixes: '{first_prefix}/' and '{prefix}/'.") + inferred_type = mapped_type + first_prefix = prefix + rewrites.append((arg_index, f"{parts[1]}/{parts[2]}")) + + if not rewrites: + return + + # Error if --type / --repo-type was also provided explicitly. + if any( + arg == "--type" or arg.startswith("--type=") or arg == "--repo-type" or arg.startswith("--repo-type=") + for arg in args + ): + raise click.UsageError( + f"Ambiguous repo type: got prefix '{first_prefix}/' in repo ID and explicit --type. Use one or the other." + ) + + # Apply all rewrites and append --type once. + for arg_index, new_value in rewrites: + args[arg_index] = new_value + args.extend(["--type", inferred_type]) # type: ignore + + def get_command(self, ctx: click.Context, cmd_name: str) -> click.Command | None: + # Try exact match first + cmd = super().get_command(ctx, cmd_name) + if cmd is not None: + return cmd + # Fall back to alias lookup: check if cmd_name matches any alias + # taken from https://github.com/fastapi/typer/issues/132#issuecomment-2417492805 + for registered_name, registered_cmd in self.commands.items(): + aliases = _ALIAS_SPLIT.split(registered_name) + if cmd_name in aliases: + return registered_cmd + return None + + def _alias_map(self) -> dict[str, list[str]]: + """Build a mapping from primary command name to its aliases (if any).""" + result: dict[str, list[str]] = {} + for registered_name in self.commands: + parts = _ALIAS_SPLIT.split(registered_name) + primary = parts[0] + result[primary] = parts[1:] + return result + + def format_commands(self, ctx: click.Context, formatter: click.HelpFormatter) -> None: + topics: dict[str, list] = {} + alias_map = self._alias_map() + + for name in self.list_commands(ctx): + cmd = self.get_command(ctx, name) + if cmd is None or cmd.hidden: + continue + help_text = cmd.get_short_help_str(limit=formatter.width) + aliases = alias_map.get(name, []) + if aliases: + help_text = f"{help_text} [alias: {', '.join(aliases)}]" + topic = getattr(cmd, "topic", "main") + topics.setdefault(topic, []).append((name, help_text)) + + with formatter.section("Main commands"): + formatter.write_dl(topics["main"]) + for topic in sorted(topics.keys()): + if topic == "main": + continue + with formatter.section(f"{topic.capitalize()} commands"): + formatter.write_dl(topics[topic]) + + def format_epilog(self, ctx: click.Context, formatter: click.HelpFormatter) -> None: + # Collect only the first example from each command (to keep group help concise) + # Full examples are shown in individual subcommand help (e.g. `hf buckets sync --help`) + all_examples: list[str] = [] + for name in self.list_commands(ctx): + cmd = self.get_command(ctx, name) + if cmd is None or cmd.hidden: + continue + cmd_examples = getattr(cmd, "examples", []) + if cmd_examples: + all_examples.append(cmd_examples[0]) + + if all_examples: + epilog = generate_epilog(all_examples) + _format_epilog_no_indent(epilog, ctx, formatter) + elif self.epilog: + _format_epilog_no_indent(self.epilog, ctx, formatter) + + def list_commands(self, ctx: click.Context) -> list[str]: # type: ignore[name-defined] + # For aliased commands ("list | ls"), use the primary name (first entry). + primary_names: list[str] = [] + for name in self.commands: + primary = _ALIAS_SPLIT.split(name)[0] + primary_names.append(primary) + return sorted(primary_names) + + +_FORMATTING_OPTIONS_HELP_RECORDS: list[tuple[str, str]] = [ + ( + "--format [auto|human|agent|json|quiet]", + "Output format. Defaults to 'auto' which picks 'agent' or 'human' based on the terminal.", + ), + ("--json", "JSON output. Equivalent to '--format json'."), + ("-q, --quiet", "Quiet output (one ID per line). Equivalent to '--format quiet'."), +] + + +def _format_formatting_options_section(formatter: click.HelpFormatter) -> None: + with formatter.section("Formatting options"): + formatter.write_dl(_FORMATTING_OPTIONS_HELP_RECORDS) + + +def _has_local_formatting_option(cmd: click.Command) -> bool: + """Return True if the command defines its own --format, --json or --quiet / -q. + + Used to skip the global formatting flag pre-processor and the duplicated "Formatting options" help section for + legacy commands like 'hf jobs ps' that have their own format/quiet options. + """ + for param in cmd.params: + if not isinstance(param, click.Option): + continue + opts = (*param.opts, *param.secondary_opts) + if "--format" in opts or "--json" in opts or "--quiet" in opts or "-q" in opts: + return True + return False + + +def _consume_format_flags_for_leaf(cmd: click.Command, args: list[str]) -> None: + """Apply global formatting flags from 'args' to a leaf command. + + Two modes, depending on the command: + + * **Pass-through commands** (ignore_unknown_options=True, e.g. 'hf extensions exec'): + args are forwarded verbatim to an external binary; we don't touch them. + + * **Legacy commands with a local --format option** (e.g. 'hf jobs ps' whose '--format' accepts Go templates): + the global flags are rewritten in-place to the legacy form ('--json' → '--format json', '--quiet'/'-q' → '--format quiet' + when the cmd has no own '--quiet') so click can parse them locally. This preserves backwards compatibility with the previous shorthand behavior. + + * **Modern commands** (no local format/quiet/json options): the flags '--format ' / '--json' / '--quiet' / '-q' are stripped from 'args' and applied to the singleton 'out'. + + Raises click.UsageError if multiple conflicting flags are supplied (e.g. '--json' together with '--format table'). + """ + if cmd.context_settings.get("ignore_unknown_options"): + return + + has_local_format = False + has_local_quiet = False + has_local_json = False + for param in cmd.params: + if not isinstance(param, click.Option): + continue + opts = (*param.opts, *param.secondary_opts) + if "--format" in opts: + has_local_format = True + if "--quiet" in opts or "-q" in opts: + has_local_quiet = True + if "--json" in opts: + has_local_json = True + + if has_local_format: + _rewrite_legacy_shorthands(args, rewrite_json=not has_local_json, rewrite_quiet=not has_local_quiet) + return + + # Strip --format/--json/-q/--quiet from 'args' and apply to 'out' + chosen_mode: OutputFormatWithAuto = OutputFormatWithAuto.auto + chosen_flag: str | None = None + + def _check_conflict(new_flag: str) -> None: + # Reject any second formatting flag before parsing values, so the user gets + # a "mutually exclusive" error rather than e.g. an "invalid value" error + # from the second flag's argument. + if chosen_flag is not None: + raise click.UsageError(f"'{chosen_flag}' and '{new_flag}' are mutually exclusive.") + + i = 0 + while i < len(args): + arg = args[i] + if arg == "--": + break # everything after '--' is a positional literal + if arg == "--format": + _check_conflict("--format") + if i + 1 >= len(args): + raise click.UsageError("Option '--format' requires a value.") + chosen_mode = _parse_format_value(args[i + 1]) + chosen_flag = "--format" + del args[i : i + 2] # --format value => 2 args removed + continue + if arg.startswith("--format="): + _check_conflict("--format") + chosen_mode = _parse_format_value(arg[len("--format=") :]) + chosen_flag = "--format" + del args[i : i + 1] + continue + if arg == "--json": + _check_conflict("--json") + chosen_mode = OutputFormatWithAuto.json + chosen_flag = "--json" + del args[i : i + 1] + continue + if arg in ("-q", "--quiet"): + _check_conflict(arg) + chosen_mode = OutputFormatWithAuto.quiet + chosen_flag = arg + del args[i : i + 1] + continue + i += 1 + + out.set_mode(chosen_mode) + + +def _rewrite_legacy_shorthands(args: list[str], *, rewrite_json: bool, rewrite_quiet: bool) -> None: + """Rewrite --json / -q / --quiet to --format ... for legacy commands. + + Used for commands like 'hf jobs ps' that still own their '--format' option. + The rewrite lets users keep using the global shorthand while click parses + '--format ' locally. + """ + has_format_in_args = any(arg == "--format" or arg.startswith("--format=") for arg in args) + + if rewrite_json and "--json" in args: + if has_format_in_args: + raise click.UsageError("'--json' and '--format' are mutually exclusive.") + idx = args.index("--json") + args[idx : idx + 1] = ["--format", "json"] + has_format_in_args = True + + if rewrite_quiet: + flag = "-q" if "-q" in args else ("--quiet" if "--quiet" in args else None) + if flag is not None: + if has_format_in_args: + raise click.UsageError(f"'{flag}' and '--format' are mutually exclusive.") + idx = args.index(flag) + args[idx : idx + 1] = ["--format", "quiet"] + + +def _parse_format_value(value: str) -> "OutputFormatWithAuto": + try: + return OutputFormatWithAuto(value) + except ValueError: + valid = ", ".join(m.value for m in OutputFormatWithAuto) + raise click.UsageError(f"Invalid value for '--format': '{value}'. Valid values: {valid}.") from None + + +def _enrich_usage_error(error: click.UsageError, label: str, items: list[tuple[str, str]]) -> None: + """Append a list of available options or commands to a usage error message.""" + if not items or error.ctx is None or f"Available {label} for" in error.message: + return + cmd_path = error.ctx.command_path + lines = [f"\n\nAvailable {label} for '{cmd_path}':"] + for name, help_text in items: + lines.append(f" {name:30s} {help_text}") + lines.append(f"\nRun '{cmd_path} --help' for full details.") + if isinstance(error, click.NoSuchOption) and error.possibilities: + lines.append(f"\nDid you mean: {', '.join(sorted(error.possibilities))}?") + error.possibilities = [] + error.message += "\n".join(lines) + + +def fallback_typer_group_factory( + fallback_handler: FallbackHandlerT, + extra_commands_provider: Callable[[], list[tuple[str, str]]] | None = None, +) -> type[HFCliTyperGroup]: + """Return a Typer group class that runs a fallback handler before command resolution.""" + + class FallbackTyperGroup(HFCliTyperGroup): + def resolve_command(self, ctx: click.Context, args: list[str]) -> tuple: + fallback_exit_code = fallback_handler(args, set(self.commands.keys())) + if fallback_exit_code is not None: + raise SystemExit(fallback_exit_code) + return super().resolve_command(ctx, args) + + def format_commands(self, ctx: click.Context, formatter: click.HelpFormatter) -> None: + super().format_commands(ctx, formatter) + if extra_commands_provider is not None: + entries = extra_commands_provider() + if entries: + with formatter.section("Extension commands"): + formatter.write_dl(entries) + + return FallbackTyperGroup + + +def HFCliCommand(topic: TOPIC_T, examples: list[str] | None = None) -> type[TyperCommand]: + def format_epilog(self: click.Command, ctx: click.Context, formatter: click.HelpFormatter) -> None: + _format_epilog_no_indent(self.epilog, ctx, formatter) + + def format_options(self: TyperCommand, ctx: click.Context, formatter: click.HelpFormatter) -> None: + TyperCommand.format_options(self, ctx, formatter) + # Skip the section for commands that define their own --format / --quiet / --json, + # or for pass-through commands that forward args to an external binary. + if _has_local_formatting_option(self): + return + if self.context_settings.get("ignore_unknown_options"): + return + _format_formatting_options_section(formatter) + + def parse_args(self: click.Command, ctx: click.Context, args: list[str]) -> list[str]: + # Show help when a command with required arguments is invoked without any args + # (mirrors group behavior: `hf jobs` prints help, so `hf download` should too). + if not args and not ctx.resilient_parsing: + if any(isinstance(p, click.Argument) and p.required for p in self.params): + click.echo(ctx.get_help(), color=ctx.color) + ctx.exit() + return TyperCommand.parse_args(self, ctx, args) + + return type( + f"TyperCommand{topic.capitalize()}", + (TyperCommand,), + { + "topic": topic, + "examples": examples or [], + "format_epilog": format_epilog, + "format_options": format_options, + "parse_args": parse_args, + }, + ) + + +class HFCliApp(typer.Typer): + """Custom Typer app for Hugging Face CLI.""" + + def command( # type: ignore + self, + name: str | None = None, + *, + topic: TOPIC_T = "main", + examples: list[str] | None = None, + context_settings: dict[str, Any] | None = None, + help: str | None = None, + epilog: str | None = None, + short_help: str | None = None, + options_metavar: str = "[OPTIONS]", + add_help_option: bool = True, + no_args_is_help: bool = False, + hidden: bool = False, + deprecated: bool = False, + rich_help_panel: str | None = None, + ) -> Callable[[Callable[..., Any]], Callable[..., Any]]: + # Generate epilog from examples if not explicitly provided + if epilog is None and examples: + epilog = generate_epilog(examples) + + def _inner(func: Callable[..., Any]) -> Callable[..., Any]: + return super(HFCliApp, self).command( + name, + cls=HFCliCommand(topic, examples), + context_settings=context_settings, + help=help, + epilog=epilog, + short_help=short_help, + options_metavar=options_metavar, + add_help_option=add_help_option, + no_args_is_help=no_args_is_help, + hidden=hidden, + deprecated=deprecated, + rich_help_panel=rich_help_panel, + )(func) + + return _inner + + +def typer_factory(help: str, epilog: str | None = None, cls: type[TyperGroup] | None = None) -> "HFCliApp": + """Create a Typer app with consistent settings. + + Args: + help: Help text for the app. + epilog: Optional epilog text (use `generate_epilog` to create one). + cls: Optional Click group class to use (defaults to `HFCliTyperGroup`). + + Returns: + A configured Typer app. + """ + if cls is None: + cls = HFCliTyperGroup + return HFCliApp( + help=help, + epilog=epilog, + add_completion=True, + no_args_is_help=True, + cls=cls, + # Disable rich completely for consistent experience + rich_markup_mode=None, + rich_help_panel=None, + pretty_exceptions_enable=False, + # Disable TyperGroup's suggest_commands, it matches against raw aliased + # keys ("list | ls") leaking pipe syntax into user-facing messages. + # HFCliTyperGroup.resolve_command() handles suggestions with expanded names. + suggest_commands=False, + # Increase max content width for better readability + context_settings={ + "max_content_width": 120, + "help_option_names": ["-h", "--help"], + }, + ) + + +class RepoType(str, Enum): + model = "model" + dataset = "dataset" + space = "space" + + +RepoIdArg = Annotated[ + str, + typer.Argument( + help="The ID of the repo (e.g. `username/repo-name` or `spaces/username/repo-name`).", + ), +] + + +RepoTypeOpt = Annotated[ + RepoType, + typer.Option( + "--type", + "--repo-type", + help="The type of repository (model, dataset, or space).", + ), +] + +TokenOpt = Annotated[ + str | None, + typer.Option( + help="A User Access Token generated from https://huggingface.co/settings/tokens.", + ), +] + +PrivateOpt = Annotated[ + bool | None, + typer.Option( + help="Whether to create a private repo if repo doesn't exist on the Hub. Ignored if the repo already exists.", + ), +] + +RevisionOpt = Annotated[ + str | None, + typer.Option( + help="Git revision id which can be a branch name, a tag, or a commit hash.", + ), +] + + +LimitOpt = Annotated[ + int, + typer.Option(help="Limit the number of results."), +] + +AuthorOpt = Annotated[ + str | None, + typer.Option(help="Filter by author or organization."), +] + +FilterOpt = Annotated[ + list[str] | None, + typer.Option(help="Filter by tags (e.g. 'text-classification'). Can be used multiple times."), +] + +SearchOpt = Annotated[ + str | None, + typer.Option(help="Search query."), +] + + +# --- Env / Secrets shared options and parsing helpers (used by jobs, repos, etc.) --- + +EnvOpt = Annotated[ + list[str] | None, + typer.Option( + "-e", + "--env", + help="Set environment variables. E.g. --env ENV=value", + ), +] + +SecretsOpt = Annotated[ + list[str] | None, + typer.Option( + "-s", + "--secrets", + help=( + "Set secret environment variables. E.g. --secrets SECRET=value" + " or `--secrets HF_TOKEN` to pass your Hugging Face token." + ), + ), +] + +EnvFileOpt = Annotated[ + str | None, + typer.Option( + "--env-file", + help="Read in a file of environment variables.", + ), +] + +SecretsFileOpt = Annotated[ + str | None, + typer.Option( + help="Read in a file of secret environment variables.", + ), +] + + +def _get_extended_environ() -> dict[str, str]: + """Return a copy of ``os.environ`` with the user's HF token injected (if available).""" + from huggingface_hub import get_token + + extended_environ = os.environ.copy() + if (token := get_token()) is not None: + extended_environ["HF_TOKEN"] = token + return extended_environ + + +def parse_env_map( + env: list[str] | None = None, + env_file: str | None = None, +) -> dict[str, str | None]: + """Parse ``-e``/``--env``/``-s``/``--secrets`` and ``--env-file``/``--secrets-file`` CLI args into a dict. + + Uses an extended environment that includes the user's HF token so that + bare ``--secrets HF_TOKEN`` resolves correctly. + """ + extended_environ = _get_extended_environ() + env_map: dict[str, str | None] = {} + if env_file: + env_map.update(load_dotenv(Path(env_file).read_text(), environ=extended_environ)) + for env_value in env or []: + env_map.update(load_dotenv(env_value, environ=extended_environ)) + return env_map + + +def env_map_to_key_value_list(env_map: dict[str, str | None]) -> list[dict[str, str]] | None: + """Convert an env/secrets dict to the ``[{"key": ..., "value": ...}]`` format used by the Hub API.""" + if not env_map: + return None + return [{"key": k, "value": v or ""} for k, v in env_map.items()] + + +VolumesOpt = Annotated[ + list[str] | None, + typer.Option( + "-v", + "--volume", + help="Mount one or more volumes. Format: hf://[TYPE/]SOURCE:/MOUNT_PATH[:ro]. " + "TYPE is one of: models, datasets, spaces, buckets. " + "TYPE defaults to models if omitted. " + "models, datasets and spaces are always mounted read-only. buckets are read+write by default. " + "E.g. -v hf://org/m:/data or -v hf://datasets/org/ds:/data or -v hf://buckets/org/b:/mnt:ro", + ), +] + +_HF_PREFIX = "hf://" +_HF_VOLUME_TYPES = { + "models": constants.REPO_TYPE_MODEL, + "datasets": constants.REPO_TYPE_DATASET, + "spaces": constants.REPO_TYPE_SPACE, + "buckets": "bucket", +} + + +def parse_volumes(volumes: list[str] | None) -> "list[Volume] | None": + """Parse volume specs from CLI arguments. + + Format: hf://[TYPE/]SOURCE[/PATH]:/MOUNT_PATH[:ro|:rw] + Where TYPE is one of: models, datasets, spaces, buckets (defaults to models if omitted). + SOURCE is the repo/bucket identifier (e.g. 'username/my-model'). + PATH is an optional subfolder inside the repo/bucket. + MOUNT_PATH starts with '/'. + Optional ':ro' or ':rw' suffix for read-only or read-write. + + Examples: + hf://my-org/my-model:/data (model, implicit type) + hf://models/my-org/my-model:/data (model, explicit type) + hf://datasets/my-org/my-dataset:/data:ro + hf://buckets/my-org/my-bucket:/mnt + hf://spaces/my-org/my-space:/app + hf://datasets/org/ds/train:/data (with path inside repo) + hf://buckets/org/b/sub/dir:/mnt (with path inside bucket) + """ + + if not volumes: + return None + + result: list[Volume] = [] + for raw_spec in volumes: + # Strip :ro/:rw suffix + spec = raw_spec + read_only = None + if spec.endswith(":ro"): + read_only = True + spec = spec[:-3] + elif spec.endswith(":rw"): + read_only = False + spec = spec[:-3] + + # Validate hf:// prefix + if not spec.startswith(_HF_PREFIX): + raise CLIError( + f"Invalid volume format: '{raw_spec}'. Source must start with 'hf://'. " + f"Expected hf://[TYPE/]SOURCE:/MOUNT_PATH[:ro]. E.g. hf://org/m:/data" + ) + spec = spec[len(_HF_PREFIX) :] + + # Find the mount path: look for :/ pattern + colon_slash_idx = spec.find(":/") + if colon_slash_idx == -1: + raise CLIError( + f"Invalid volume format: '{raw_spec}'. Expected hf://[TYPE/]SOURCE:/MOUNT_PATH[:ro]. E.g. hf://org/m:/data" + ) + source_part = spec[:colon_slash_idx] + mount_path = spec[colon_slash_idx + 1 :] + + # Parse type from source_part (first segment before /) + # Then split remaining into source (namespace/name or name) and optional path. + slash_idx = source_part.find("/") + if slash_idx == -1: + # No slash: bare source like "gpt2" -> model type + vol_type_str = constants.REPO_TYPE_MODEL + source = source_part + path = None + else: + first_segment = source_part[:slash_idx] + if first_segment in _HF_VOLUME_TYPES: + vol_type_str = _HF_VOLUME_TYPES[first_segment] + remaining = source_part[slash_idx + 1 :] + else: + # First segment isn't a known type -> model type + vol_type_str = constants.REPO_TYPE_MODEL + remaining = source_part + + # Split remaining into source (namespace/name) and optional path. + # Repo/bucket IDs are "namespace/name" (2 segments) or "name" (1 segment). + # Any extra segments are the path inside the repo/bucket. + parts = remaining.split("/", 2) + if len(parts) >= 3: + source = parts[0] + "/" + parts[1] + path = parts[2] + else: + source = remaining + path = None + + result.append( + Volume( + type=vol_type_str, + source=source, + mount_path=mount_path, + read_only=read_only, + path=path, + ) + ) + return result + + +class OutputFormat(str, Enum): + """Output format for CLI list commands.""" + + table = "table" + json = "json" + + +FormatOpt = Annotated[ + OutputFormat, + typer.Option( + help="Output format (table or json).", + ), +] + + +def _set_output_mode(value: OutputFormatWithAuto) -> OutputFormatWithAuto: + """Callback for the legacy FormatWithAutoOpt option type. + + Most commands now rely on the global --format / --json / -q flags consumed by _consume_format_flags_for_leaf instead + of declaring FormatWithAutoOpt themselves. This callback is kept for the rare cases where a command still wires + FormatWithAutoOpt explicitly. + """ + out.set_mode(value) + return value + + +FormatWithAutoOpt = Annotated[ + OutputFormatWithAuto, + typer.Option(help="Output format.", callback=_set_output_mode), +] + +QuietOpt = Annotated[ + bool, + typer.Option("-q", "--quiet", help="Print only IDs (one per line)."), +] + + +def _to_header(name: str) -> str: + """Convert a camelCase or PascalCase string to SCREAMING_SNAKE_CASE to be used as table header.""" + s = re.sub(r"([a-z])([A-Z])", r"\1_\2", name) + return s.upper() + + +def _format_value(value: Any) -> str: + """Convert a value to string for terminal display.""" + if not value: + return "" + if isinstance(value, bool): + return "✔" if value else "" + if isinstance(value, datetime.datetime): + return value.strftime("%Y-%m-%d") + if isinstance(value, str) and re.match(r"^\d{4}-\d{2}-\d{2}T", value): + return value[:10] + if isinstance(value, list): + return ", ".join(_format_value(v) for v in value) + elif isinstance(value, dict): + if "name" in value: # Likely to be a user or org => print name + return str(value["name"]) + # TODO: extend if needed + return json.dumps(value) + return str(value) + + +def _format_cell(value: Any, max_len: int = _MAX_CELL_LENGTH) -> str: + """Format a value + truncate it for table display.""" + cell = _format_value(value) + if len(cell) > max_len: + cell = cell[: max_len - 3] + "..." + return cell + + +def print_as_table( + items: Sequence[dict[str, Any]], + headers: list[str], + row_fn: Callable[[dict[str, Any]], list[str]], + alignments: dict[str, str] | None = None, +) -> None: + """Print items as a formatted table. + + Args: + items: Sequence of dictionaries representing the items to display. + headers: List of column headers. + row_fn: Function that takes an item dict and returns a list of string values for each column. + alignments: Optional mapping of header name to "left" or "right". Defaults to "left". + """ + if not items: + print("No results found.") + return + rows = cast(list[list[Union[str, int]]], [row_fn(item) for item in items]) + screaming_headers = [_to_header(h) for h in headers] + # Remap alignments keys to screaming case to match tabulate headers + screaming_alignments = {_to_header(k): v for k, v in (alignments or {}).items()} + print(tabulate(rows, headers=screaming_headers, alignments=screaming_alignments)) + + +def print_list_output( + items: Sequence[dict[str, Any]], + format: OutputFormat, + quiet: bool, + id_key: str = "id", + headers: list[str] | None = None, + row_fn: Callable[[dict[str, Any]], list[str]] | None = None, + alignments: dict[str, str] | None = None, +) -> None: + """Print list command output in the specified format. + + Args: + items: Sequence of dictionaries representing the items to display. + format: Output format. + quiet: If True, print only IDs (one per line). + id_key: Key to use for extracting IDs in quiet mode. + headers: Optional list of column names for headers. If not provided, auto-detected from keys. + row_fn: Optional function to extract row values. If not provided, uses _format_cell on each column. + alignments: Optional mapping of header name to "left" or "right". Defaults to "left". + """ + if quiet: + for item in items: + print(item[id_key]) + return + + if format == OutputFormat.json: + print(json.dumps(list(items), indent=2, default=str)) + return + + if headers is None: + all_columns = list(items[0].keys()) if items else [id_key] + headers = [col for col in all_columns if any(_format_cell(item.get(col)) for item in items)] + + if row_fn is None: + + def row_fn(item: dict[str, Any]) -> list[str]: + return [_format_cell(item.get(col)) for col in headers] # type: ignore[union-attr] + + print_as_table(items, headers=headers, row_fn=row_fn, alignments=alignments) + + +def _serialize_value(v: object) -> object: + """Recursively serialize a value to be JSON-compatible.""" + if isinstance(v, datetime.datetime): + return v.isoformat() + elif isinstance(v, dict): + return {key: _serialize_value(val) for key, val in v.items() if val is not None} + elif isinstance(v, list): + return [_serialize_value(item) for item in v] + return v + + +def api_object_to_dict(info: Any) -> dict[str, Any]: + """Convert repo info dataclasses to json-serializable dicts.""" + return {k: _serialize_value(v) for k, v in dataclasses.asdict(info).items() if v is not None} + + +def make_expand_properties_parser(valid_properties: Sequence[ExpandPropertyT]): + """Create a callback to parse and validate comma-separated expand properties.""" + + def _parse_expand_properties(value: str | None) -> list[ExpandPropertyT] | None: + if value is None: + return None + properties = [p.strip() for p in value.split(",")] + for prop in properties: + if prop not in valid_properties: + raise typer.BadParameter( + f"Invalid expand property: '{prop}'. Valid values are: {', '.join(valid_properties)}" + ) + return [cast(ExpandPropertyT, prop) for prop in properties] + + return _parse_expand_properties + + +### PyPI VERSION CHECKER + + +def check_cli_update(library: Literal["huggingface_hub", "transformers"]) -> None: + """ + Check whether a newer version of a library is available on PyPI. + + If a newer version is found, print a hint pointing at `hf update`. + + If current version is a pre-release (e.g. `1.0.0.rc1`), or a dev version (e.g. `1.0.0.dev1`), no check is performed. + If `HF_HUB_DISABLE_UPDATE_CHECK` is set, the check is skipped entirely. + + This function is called at the entry point of the CLI. It only performs the check once every 24 hours, and any error + during the check is caught and logged, to avoid breaking the CLI. + + Args: + library: The library to check for updates. Currently supports "huggingface_hub" and "transformers". + """ + try: + _check_cli_update(library) + except Exception: + # We don't want the CLI to fail on version checks, no matter the reason. + logger.debug("Error while checking for CLI update.", exc_info=True) + + +def _check_cli_update(library: Literal["huggingface_hub", "transformers"]) -> None: + if constants.HF_HUB_DISABLE_UPDATE_CHECK: + return + + current_version = importlib.metadata.version(library) + + # Skip if current version is a pre-release or dev version + if any(tag in current_version for tag in ["rc", "dev"]): + return + + # Skip if already checked in the last 24 hours + if os.path.exists(constants.CHECK_FOR_UPDATE_DONE_PATH): + mtime = os.path.getmtime(constants.CHECK_FOR_UPDATE_DONE_PATH) + if (time.time() - mtime) < 24 * 3600: + return + + # Touch the file to mark that we did the check now + Path(constants.CHECK_FOR_UPDATE_DONE_PATH).parent.mkdir(parents=True, exist_ok=True) + Path(constants.CHECK_FOR_UPDATE_DONE_PATH).touch() + + # Check latest version from PyPI + latest_version = _fetch_latest_pypi_version(library) + if latest_version is None or current_version == latest_version: + return + + if library == "huggingface_hub": + update_command = _get_huggingface_hub_update_command() + else: + update_command = _get_transformers_update_command() + + message = f"A new version of {library} ({latest_version}) is available! You are using version {current_version}." + if update_command is not None: + match library: + case "huggingface_hub": + message += "\nTo update, run: hf update" + case _: + message += f"\nTo update, run: {' '.join(update_command)}" + out.hint(message) + + +def _fetch_latest_pypi_version(library: str) -> str | None: + """Fetch the latest version of a library from PyPI. Returns None if the request fails.""" + try: + response = get_session().get(f"https://pypi.org/pypi/{library}/json", timeout=2) + hf_raise_for_status(response) + return response.json()["info"]["version"] + except Exception: + logger.debug("Error while fetching latest version from PyPI.", exc_info=True) + return None + + +def run_update() -> int: + """Run the install-method-appropriate update command for the `hf` CLI. + + Raises CLIError if the installation method can't be determined. + Returns the subprocess exit code on success/failure of the update itself. + """ + cmd = _get_huggingface_hub_update_command() + if cmd is None: + raise CLIError( + "Cannot determine how to update huggingface_hub (unknown installation method). Please update manually." + ) + return subprocess.call(cmd) + + +def _get_huggingface_hub_update_command() -> list[str] | None: + """Return the command to update huggingface_hub as an argv list, or None if the installation method is unknown.""" + match installation_method(): + case "brew": + return ["brew", "upgrade", "hf"] + case "hf_installer" if os.name == "nt": + return ["powershell", "-NoProfile", "-Command", "iwr -useb https://hf.co/cli/install.ps1 | iex"] + case "hf_installer": + return ["bash", "-c", "curl -LsSf https://hf.co/cli/install.sh | bash -"] + case "pip": + return [sys.executable, "-m", "pip", "install", "-U", "huggingface_hub"] + case _: + return None + + +def _get_transformers_update_command() -> list[str] | None: + """Return the command to update transformers as an argv list, or None if the installation method is unknown.""" + match installation_method(): + case "hf_installer" if os.name == "nt": + return [ + "powershell", + "-NoProfile", + "-Command", + "iwr -useb https://hf.co/cli/install.ps1 | iex -WithTransformers", + ] + case "hf_installer": + return ["bash", "-c", "curl -LsSf https://hf.co/cli/install.sh | bash -s -- --with-transformers"] + case "pip": + return [sys.executable, "-m", "pip", "install", "-U", "transformers"] + case _: + return None diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/huggingface_hub/cli/upload_large_folder.py b/bioagent-bench/.venv/lib/python3.14/site-packages/huggingface_hub/cli/upload_large_folder.py new file mode 100644 index 0000000000000000000000000000000000000000..a98bcdea47d912f4bcb260a487471e0c82ff000c --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/huggingface_hub/cli/upload_large_folder.py @@ -0,0 +1,129 @@ +# Copyright 2023-present, the HuggingFace Inc. team. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Contains command to upload a large folder with the CLI.""" + +import os +from typing import Annotated + +import typer + +from huggingface_hub import logging +from huggingface_hub.utils import disable_progress_bars + +from ._cli_utils import ( + PrivateOpt, + RepoIdArg, + RepoType, + RepoTypeOpt, + RevisionOpt, + TokenOpt, + get_hf_api, +) +from ._output import out + + +logger = logging.get_logger(__name__) + + +UPLOAD_LARGE_FOLDER_EXAMPLES = [ + "hf upload-large-folder Wauplin/my-cool-model ./large_model_dir", + "hf upload-large-folder Wauplin/my-cool-model ./large_model_dir --revision v1.0", +] + + +def upload_large_folder( + repo_id: RepoIdArg, + local_path: Annotated[ + str, + typer.Argument( + help="Local path to the folder to upload.", + ), + ], + repo_type: RepoTypeOpt = RepoType.model, + revision: RevisionOpt = None, + private: PrivateOpt = None, + include: Annotated[ + list[str] | None, + typer.Option( + help="Glob patterns to match files to upload.", + ), + ] = None, + exclude: Annotated[ + list[str] | None, + typer.Option( + help="Glob patterns to exclude from files to upload.", + ), + ] = None, + token: TokenOpt = None, + num_workers: Annotated[ + int | None, + typer.Option( + help="Number of workers to use to hash, upload and commit files.", + ), + ] = None, + no_report: Annotated[ + bool, + typer.Option( + help="Whether to disable regular status report.", + ), + ] = False, + no_bars: Annotated[ + bool, + typer.Option( + help="Whether to disable progress bars.", + ), + ] = False, +) -> None: + """Upload a large folder to the Hub. Recommended for resumable uploads.""" + if not os.path.isdir(local_path): + raise typer.BadParameter("Large upload is only supported for folders.", param_hint="local_path") + + out.warning( + "You are about to upload a large folder to the Hub using `hf upload-large-folder`. " + "This is a new feature so feedback is very welcome!\n" + "\n" + "A few things to keep in mind:\n" + " - Repository limits still apply: https://huggingface.co/docs/hub/repositories-recommendations\n" + " - Do not start several processes in parallel.\n" + " - You can interrupt and resume the process at any time. " + "The script will pick up where it left off except for partially uploaded files that would have to be entirely reuploaded.\n" + " - Do not upload the same folder to several repositories. If you need to do so, you must delete the `./.cache/huggingface/` folder first.\n" + "\n" + f"Some temporary metadata will be stored under `{local_path}/.cache/huggingface`.\n" + " - You must not modify those files manually.\n" + " - You must not delete the `./.cache/huggingface/` folder while a process is running.\n" + " - You can delete the `./.cache/huggingface/` folder to reinitialize the upload state when process is not running. Files will have to be hashed and preuploaded again, except for already committed files.\n" + "\n" + "If the process output is too verbose, you can disable the progress bars with `--no-bars`. " + "You can also entirely disable the status report with `--no-report`.\n" + "\n" + "For more details, run `hf upload-large-folder --help` or check the documentation at " + "https://huggingface.co/docs/huggingface_hub/guides/upload#upload-a-large-folder." + ) + + if no_bars: + disable_progress_bars() + + api = get_hf_api(token=token) + api.upload_large_folder( + repo_id=repo_id, + folder_path=local_path, + repo_type=repo_type.value, + revision=revision, + private=private, + allow_patterns=include, + ignore_patterns=exclude, + num_workers=num_workers, + print_report=not no_report, + ) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/idna-3.14.dist-info/INSTALLER b/bioagent-bench/.venv/lib/python3.14/site-packages/idna-3.14.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..5c69047b2eb8235994febeeae1da4a82365a240a --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/idna-3.14.dist-info/INSTALLER @@ -0,0 +1 @@ +uv \ No newline at end of file diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/idna-3.14.dist-info/METADATA b/bioagent-bench/.venv/lib/python3.14/site-packages/idna-3.14.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..b3024f7cbec6080ecbc965d785f3526a048864f8 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/idna-3.14.dist-info/METADATA @@ -0,0 +1,204 @@ +Metadata-Version: 2.4 +Name: idna +Version: 3.14 +Summary: Internationalized Domain Names in Applications (IDNA) +Author-email: Kim Davies +Requires-Python: >=3.8 +Description-Content-Type: text/x-rst +License-Expression: BSD-3-Clause +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: System Administrators +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Internet :: Name Service (DNS) +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: Utilities +License-File: LICENSE.md +Requires-Dist: ruff >= 0.6.2 ; extra == "all" +Requires-Dist: mypy >= 1.11.2 ; extra == "all" +Requires-Dist: pytest >= 8.3.2 ; extra == "all" +Project-URL: Changelog, https://github.com/kjd/idna/blob/master/HISTORY.rst +Project-URL: Issue tracker, https://github.com/kjd/idna/issues +Project-URL: Source, https://github.com/kjd/idna +Provides-Extra: all + +Internationalized Domain Names in Applications (IDNA) +===================================================== + +Support for `Internationalized Domain Names in +Applications (IDNA) `_ +and `Unicode IDNA Compatibility Processing +`_. + +The latest versions of these standards supplied here provide +more comprehensive language coverage and reduce the potential of +allowing domains with known security vulnerabilities. This library +is a suitable replacement for the “encodings.idna” +module that comes with the Python standard library, but which +only supports an older superseded IDNA specification from 2003. + +Basic functions are simply executed: + +.. code-block:: pycon + + >>> import idna + >>> idna.encode('ドメイン.テスト') + b'xn--eckwd4c7c.xn--zckzah' + >>> print(idna.decode('xn--eckwd4c7c.xn--zckzah')) + ドメイン.テスト + + +Installation +------------ + +This package is available for installation from PyPI via the +typical mechanisms, such as: + +.. code-block:: bash + + $ python3 -m pip install idna + + +Usage +----- + +For typical usage, the ``encode`` and ``decode`` functions will take a +domain name argument and perform a conversion to ASCII-compatible encoding +(known as A-labels), or to Unicode strings (known as U-labels) +respectively. + +.. code-block:: pycon + + >>> import idna + >>> idna.encode('ドメイン.テスト') + b'xn--eckwd4c7c.xn--zckzah' + >>> print(idna.decode('xn--eckwd4c7c.xn--zckzah')) + ドメイン.テスト + +Conversions can be applied at a per-label basis using the ``ulabel`` or +``alabel`` functions if necessary: + +.. code-block:: pycon + + >>> idna.alabel('测试') + b'xn--0zwm56d' + + +Compatibility Mapping (UTS #46) ++++++++++++++++++++++++++++++++ + +This library provides support for `Unicode IDNA Compatibility +Processing `_ which normalizes input from +different potential ways a user may input a domain prior to performing the IDNA +conversion operations. This functionality, known as a +`mapping `_, is considered by the +specification to be a local user-interface issue distinct from IDNA +conversion functionality. + +For example, “Königsgäßchen” is not a permissible label as *LATIN +CAPITAL LETTER K* is not allowed (nor are capital letters in general). +UTS 46 will convert this into lower case prior to applying the IDNA +conversion. + +.. code-block:: pycon + + >>> import idna + >>> idna.encode('Königsgäßchen') + ... + idna.core.InvalidCodepoint: Codepoint U+004B at position 1 of 'Königsgäßchen' not allowed + >>> idna.encode('Königsgäßchen', uts46=True) + b'xn--knigsgchen-b4a3dun' + >>> print(idna.decode('xn--knigsgchen-b4a3dun')) + königsgäßchen + + +Exceptions +---------- + +All errors raised during the conversion following the specification +should raise an exception derived from the ``idna.IDNAError`` base +class. + +More specific exceptions that may be generated as ``idna.IDNABidiError`` +when the error reflects an illegal combination of left-to-right and +right-to-left characters in a label; ``idna.InvalidCodepoint`` when +a specific codepoint is an illegal character in an IDN label (i.e. +INVALID); and ``idna.InvalidCodepointContext`` when the codepoint is +illegal based on its position in the string (i.e. it is CONTEXTO or CONTEXTJ +but the contextual requirements are not satisfied.) + +Building and Diagnostics +------------------------ + +The IDNA and UTS 46 functionality relies upon pre-calculated lookup +tables for performance. These tables are derived from computing against +eligibility criteria in the respective standards using the command-line +script ``tools/idna-data``. + +This tool will fetch relevant codepoint data from the Unicode repository +and perform the required calculations to identify eligibility. There are +three main modes: + +* ``idna-data make-libdata``. Generates ``idnadata.py`` and + ``uts46data.py``, the pre-calculated lookup tables used for IDNA and + UTS 46 conversions. Implementers who wish to track this library against + a different Unicode version may use this tool to manually generate a + different version of the ``idnadata.py`` and ``uts46data.py`` files. + +* ``idna-data make-table``. Generate a table of the IDNA disposition + (e.g. PVALID, CONTEXTJ, CONTEXTO) in the format found in Appendix + B.1 of RFC 5892 and the pre-computed tables published by `IANA + `_. + +* ``idna-data U+0061``. Prints debugging output on the various + properties associated with an individual Unicode codepoint (in this + case, U+0061), that are used to assess the IDNA and UTS 46 status of a + codepoint. This is helpful in debugging or analysis. + +The tool accepts a number of arguments, described using ``idna-data -h``. +Most notably, the ``--version`` argument allows the specification +of the version of Unicode to be used in computing the table data. For +example, ``idna-data --version 9.0.0 make-libdata`` will generate +library data against Unicode 9.0.0. + + +Additional Notes +---------------- + +* **Packages**. The latest tagged release version is published in the + `Python Package Index `_. + +* **Version support**. This library supports Python 3.8 and higher. + As this library serves as a low-level toolkit for a variety of + applications, many of which strive for broad compatibility with older + Python versions, there is no rush to remove older interpreter support. + Support for older versions are likely to be removed from new releases + as automated tests can no longer easily be run, i.e. once the Python + version is officially end-of-life. + +* **Testing**. The library has a test suite based on each rule of the + IDNA specification, as well as tests that are provided as part of the + Unicode Technical Standard 46, `Unicode IDNA Compatibility Processing + `_. + +* **Emoji**. It is an occasional request to support emoji domains in + this library. Encoding of symbols like emoji is expressly prohibited by + the IDNA technical standard, and emoji domains are broadly phased + out across the domain industry due to associated security risks. + +* **Transitional processing**. Unicode 16.0.0 removed transitional + processing so the `transitional` argument for the encode() method + no longer has any effect and will be removed at a later date. + diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/idna-3.14.dist-info/RECORD b/bioagent-bench/.venv/lib/python3.14/site-packages/idna-3.14.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..dcb0dacc06a435e51e86a7fd77e40c46cf46a2a5 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/idna-3.14.dist-info/RECORD @@ -0,0 +1,15 @@ +idna-3.14.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2 +idna-3.14.dist-info/METADATA,sha256=EWoOFCvCBYtZiNYlO65LffzS4SNpwQDn2yHtpnQ5YXY,8033 +idna-3.14.dist-info/RECORD,, +idna-3.14.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +idna-3.14.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82 +idna-3.14.dist-info/licenses/LICENSE.md,sha256=GppPDj1HmickDd1ZqRN6ZqtKD539yMphiMwL_YUYfwQ,1541 +idna/__init__.py,sha256=MPqNDLZbXqGaNdXxAFhiqFPKEQXju2jNQhCey6-5eJM,868 +idna/codec.py,sha256=lLBsXVex4TPVX6Q9FbZ7pjxAZSDkns573SXLyr6zWQQ,5132 +idna/compat.py,sha256=AepA39ceRHxkfHP41-FvKW5Ki-f4PfUZ90RUMlCNdmo,1353 +idna/core.py,sha256=BNKIXQC90Ewbp4kYR_l1OMRCb3dsKXxNKHSbgVs4KhU,22563 +idna/idnadata.py,sha256=EYkhqhOze0Whw1pIMum_-qEHM-8utUwc_IBoIUWW8Go,92613 +idna/intranges.py,sha256=amUtkdhYcQG8Zr-CoMM_kVRacxkivC1WgxN1b63KKdU,1898 +idna/package_data.py,sha256=2DHRgJ0lp_aZmPxXp1855MPrgGy4LGNz2mjlQ-OCfqw,21 +idna/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +idna/uts46data.py,sha256=JAacELsMTouJI-U6jk_UcKuAM4K7ffGCYpbDLux8iVk,202713 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/idna-3.14.dist-info/REQUESTED b/bioagent-bench/.venv/lib/python3.14/site-packages/idna-3.14.dist-info/REQUESTED new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/idna-3.14.dist-info/WHEEL b/bioagent-bench/.venv/lib/python3.14/site-packages/idna-3.14.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..d8b9936dad9ab2513fa6979f411560d3b6b57e37 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/idna-3.14.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: flit 3.12.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/idna-3.14.dist-info/licenses/LICENSE.md b/bioagent-bench/.venv/lib/python3.14/site-packages/idna-3.14.dist-info/licenses/LICENSE.md new file mode 100644 index 0000000000000000000000000000000000000000..f706835ab3a5dd709a6e1f57aee0a94ae1415df6 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/idna-3.14.dist-info/licenses/LICENSE.md @@ -0,0 +1,31 @@ +BSD 3-Clause License + +Copyright (c) 2013-2026, Kim Davies and contributors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/idna/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/idna/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..cfdc030a751b089fc7e38fc88093b791605d501d --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/idna/__init__.py @@ -0,0 +1,45 @@ +from .core import ( + IDNABidiError, + IDNAError, + InvalidCodepoint, + InvalidCodepointContext, + alabel, + check_bidi, + check_hyphen_ok, + check_initial_combiner, + check_label, + check_nfc, + decode, + encode, + ulabel, + uts46_remap, + valid_contextj, + valid_contexto, + valid_label_length, + valid_string_length, +) +from .intranges import intranges_contain +from .package_data import __version__ + +__all__ = [ + "__version__", + "IDNABidiError", + "IDNAError", + "InvalidCodepoint", + "InvalidCodepointContext", + "alabel", + "check_bidi", + "check_hyphen_ok", + "check_initial_combiner", + "check_label", + "check_nfc", + "decode", + "encode", + "intranges_contain", + "ulabel", + "uts46_remap", + "valid_contextj", + "valid_contexto", + "valid_label_length", + "valid_string_length", +] diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/idna/__pycache__/core.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/idna/__pycache__/core.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ad3285803b243d686943b08e294ab1cc1a05bee6 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/idna/__pycache__/core.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/idna/__pycache__/intranges.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/idna/__pycache__/intranges.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9a900dfab06a9df89fe36621282c86e798d9b61a Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/idna/__pycache__/intranges.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/idna/__pycache__/package_data.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/idna/__pycache__/package_data.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ae7d7e44b9eb49190e358a43e47fc935fa127b82 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/idna/__pycache__/package_data.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/idna/codec.py b/bioagent-bench/.venv/lib/python3.14/site-packages/idna/codec.py new file mode 100644 index 0000000000000000000000000000000000000000..befba40ce5979f84b50e66020d0226bd24378d99 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/idna/codec.py @@ -0,0 +1,162 @@ +import codecs +import re +from typing import Any, Optional, Tuple + +from .core import IDNAError, alabel, decode, encode, ulabel + +_unicode_dots_re = re.compile("[\u002e\u3002\uff0e\uff61]") + + +class Codec(codecs.Codec): + """Stateless IDNA 2008 codec. + + Implements the :class:`codecs.Codec` protocol so that the whole-domain + encoder (:func:`idna.encode`) and decoder (:func:`idna.decode`) are + accessible through the standard codec machinery as ``"idna2008"``. + + Only the ``"strict"`` error handler is supported; any other handler + raises :exc:`~idna.IDNAError`. + """ + + def encode(self, data: str, errors: str = "strict") -> Tuple[bytes, int]: # ty: ignore[invalid-method-override] + if errors != "strict": + raise IDNAError('Unsupported error handling "{}"'.format(errors)) + + if not data: + return b"", 0 + + return encode(data), len(data) + + def decode(self, data: bytes, errors: str = "strict") -> Tuple[str, int]: # ty: ignore[invalid-method-override] + if errors != "strict": + raise IDNAError('Unsupported error handling "{}"'.format(errors)) + + if not data: + return "", 0 + + return decode(data), len(data) + + +class IncrementalEncoder(codecs.BufferedIncrementalEncoder): + """Incremental IDNA 2008 encoder. + + Buffers a partial trailing label across calls until either the next + label separator is seen or ``final=True``, so that streamed input is + encoded one whole label at a time. Any of the four Unicode label + separators (``U+002E``, ``U+3002``, ``U+FF0E``, ``U+FF61``) ends a + label; the result always uses ``U+002E`` as the separator. + + Only the ``"strict"`` error handler is supported. + """ + + def _buffer_encode(self, data: str, errors: str, final: bool) -> Tuple[bytes, int]: # ty: ignore[invalid-method-override] + if errors != "strict": + raise IDNAError('Unsupported error handling "{}"'.format(errors)) + + if not data: + return b"", 0 + + labels = _unicode_dots_re.split(data) + trailing_dot = b"" + if labels: + if not labels[-1]: + trailing_dot = b"." + del labels[-1] + elif not final: + # Keep potentially unfinished label until the next call + del labels[-1] + if labels: + trailing_dot = b"." + + result = [] + size = 0 + for label in labels: + result.append(alabel(label)) + if size: + size += 1 + size += len(label) + + # Join with U+002E + result_bytes = b".".join(result) + trailing_dot + size += len(trailing_dot) + return result_bytes, size + + +class IncrementalDecoder(codecs.BufferedIncrementalDecoder): + """Incremental IDNA 2008 decoder. + + Buffers a partial trailing label across calls until either the next + label separator is seen or ``final=True``, so that streamed input is + decoded one whole label at a time. + + Only the ``"strict"`` error handler is supported. + """ + + def _buffer_decode(self, data: Any, errors: str, final: bool) -> Tuple[str, int]: # ty: ignore[invalid-method-override] + if errors != "strict": + raise IDNAError('Unsupported error handling "{}"'.format(errors)) + + if not data: + return ("", 0) + + if not isinstance(data, str): + data = str(data, "ascii") + + labels = _unicode_dots_re.split(data) + trailing_dot = "" + if labels: + if not labels[-1]: + trailing_dot = "." + del labels[-1] + elif not final: + # Keep potentially unfinished label until the next call + del labels[-1] + if labels: + trailing_dot = "." + + result = [] + size = 0 + for label in labels: + result.append(ulabel(label)) + if size: + size += 1 + size += len(label) + + result_str = ".".join(result) + trailing_dot + size += len(trailing_dot) + return (result_str, size) + + +class StreamWriter(Codec, codecs.StreamWriter): + pass + + +class StreamReader(Codec, codecs.StreamReader): + pass + + +def search_function(name: str) -> Optional[codecs.CodecInfo]: + """Codec search function registered with :mod:`codecs`. + + Returns a :class:`codecs.CodecInfo` for the ``"idna2008"`` codec name + so that ``str.encode("idna2008")`` and ``bytes.decode("idna2008")`` + invoke the IDNA 2008 codec defined in this module. + + :param name: The codec name being looked up. + :returns: A :class:`codecs.CodecInfo` instance if ``name`` is + ``"idna2008"``, otherwise ``None``. + """ + if name != "idna2008": + return None + return codecs.CodecInfo( + name=name, + encode=Codec().encode, + decode=Codec().decode, # type: ignore + incrementalencoder=IncrementalEncoder, + incrementaldecoder=IncrementalDecoder, + streamwriter=StreamWriter, + streamreader=StreamReader, + ) + + +codecs.register(search_function) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/idna/compat.py b/bioagent-bench/.venv/lib/python3.14/site-packages/idna/compat.py new file mode 100644 index 0000000000000000000000000000000000000000..1d01e3d9750860e6ec87fb7c0955cee6bcd9fcc1 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/idna/compat.py @@ -0,0 +1,41 @@ +from typing import Any, Union + +from .core import decode, encode + + +def ToASCII(label: str) -> bytes: + """Compatibility shim for :rfc:`3490` ``ToASCII``. + + Delegates to :func:`idna.encode` (IDNA 2008). Provided to ease porting + of code written against the legacy :mod:`encodings.idna` API; new code + should call :func:`idna.encode` directly. + + :param label: The label or domain to encode. + :returns: The encoded form as ASCII :class:`bytes`. + """ + return encode(label) + + +def ToUnicode(label: Union[bytes, bytearray]) -> str: + """Compatibility shim for :rfc:`3490` ``ToUnicode``. + + Delegates to :func:`idna.decode` (IDNA 2008). Provided to ease porting + of code written against the legacy :mod:`encodings.idna` API; new code + should call :func:`idna.decode` directly. + + :param label: The label or domain to decode. + :returns: The decoded Unicode form. + """ + return decode(label) + + +def nameprep(s: Any) -> None: + """Stub for :rfc:`3491` Nameprep, which is not used by IDNA 2008. + + IDNA 2008 (:rfc:`5891`) replaces Nameprep with the per-codepoint + validity classes from :rfc:`5892`; this function exists only to + return a clear error if legacy code attempts to call it. + + :raises NotImplementedError: Always. + """ + raise NotImplementedError("IDNA 2008 does not utilise nameprep protocol") diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/idna/core.py b/bioagent-bench/.venv/lib/python3.14/site-packages/idna/core.py new file mode 100644 index 0000000000000000000000000000000000000000..1804ccaf758d7407af666f96b4d3b59b028fd0c3 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/idna/core.py @@ -0,0 +1,633 @@ +import bisect +import re +import unicodedata +import warnings +from typing import Optional, Union + +from . import idnadata +from .intranges import intranges_contain + +_virama_combining_class = 9 +_alabel_prefix = b"xn--" +_unicode_dots_re = re.compile("[\u002e\u3002\uff0e\uff61]") + + +class IDNAError(UnicodeError): + """Base exception for all IDNA-encoding related problems""" + + pass + + +class IDNABidiError(IDNAError): + """Exception when bidirectional requirements are not satisfied""" + + pass + + +class InvalidCodepoint(IDNAError): + """Exception when a disallowed or unallocated codepoint is used""" + + pass + + +class InvalidCodepointContext(IDNAError): + """Exception when the codepoint is not valid in the context it is used""" + + pass + + +def _combining_class(cp: int) -> int: + v = unicodedata.combining(chr(cp)) + if v == 0: + if not unicodedata.name(chr(cp)): + raise ValueError("Unknown character in unicodedata") + return v + + +def _is_script(cp: str, script: str) -> bool: + return intranges_contain(ord(cp), idnadata.scripts[script]) + + +def _punycode(s: str) -> bytes: + return s.encode("punycode") + + +def _unot(s: int) -> str: + return "U+{:04X}".format(s) + + +def valid_label_length(label: Union[bytes, str]) -> bool: + """Check that a label does not exceed the maximum permitted length. + + Per :rfc:`1035` (and :rfc:`5891` §4.2.4) a DNS label must not exceed + 63 octets. The argument may be either a :class:`str` (a U-label, where + length is measured in characters) or :class:`bytes` (an A-label, where + length is measured in octets). + + :param label: The label to check. + :returns: ``True`` if the label is within the length limit, otherwise + ``False``. + """ + if len(label) > 63: + return False + return True + + +def valid_string_length(label: Union[bytes, str], trailing_dot: bool) -> bool: + """Check that a full domain name does not exceed the maximum length. + + Per :rfc:`1035`, a domain name is limited to 253 octets when no trailing + dot is present, or 254 octets when one is included. + + :param label: The full (possibly multi-label) domain name. + :param trailing_dot: ``True`` if ``label`` includes a trailing ``.``. + :returns: ``True`` if the domain is within the length limit, otherwise + ``False``. + """ + if len(label) > (254 if trailing_dot else 253): + return False + return True + + +def check_bidi(label: str, check_ltr: bool = False) -> bool: + """Validate the Bidi Rule from :rfc:`5893` for a single label. + + The Bidi Rule constrains how bidirectional characters (Hebrew, Arabic, + etc.) may appear within a label. By default the check is only applied + when the label contains at least one right-to-left character (Unicode + bidirectional categories ``R``, ``AL``, or ``AN``); set ``check_ltr`` + to ``True`` to apply it to LTR-only labels as well. + + :param label: The label to validate, as a Unicode string. + :param check_ltr: If ``True``, apply the rules even when the label + contains no RTL characters. + :returns: ``True`` if the label satisfies the Bidi Rule. + :raises IDNABidiError: If any of Bidi Rule conditions 1-6 are violated, + or if the directional category of a codepoint cannot be determined. + """ + # Bidi rules should only be applied if string contains RTL characters + bidi_label = False + for idx, cp in enumerate(label, 1): + direction = unicodedata.bidirectional(cp) + if direction == "": + # String likely comes from a newer version of Unicode + raise IDNABidiError("Unknown directionality in label {} at position {}".format(repr(label), idx)) + if direction in ["R", "AL", "AN"]: + bidi_label = True + if not bidi_label and not check_ltr: + return True + + # Bidi rule 1 + direction = unicodedata.bidirectional(label[0]) + if direction in ["R", "AL"]: + rtl = True + elif direction == "L": + rtl = False + else: + raise IDNABidiError("First codepoint in label {} must be directionality L, R or AL".format(repr(label))) + + valid_ending = False + number_type: Optional[str] = None + for idx, cp in enumerate(label, 1): + direction = unicodedata.bidirectional(cp) + + if rtl: + # Bidi rule 2 + if direction not in [ + "R", + "AL", + "AN", + "EN", + "ES", + "CS", + "ET", + "ON", + "BN", + "NSM", + ]: + raise IDNABidiError("Invalid direction for codepoint at position {} in a right-to-left label".format(idx)) + # Bidi rule 3 + if direction in ["R", "AL", "EN", "AN"]: + valid_ending = True + elif direction != "NSM": + valid_ending = False + # Bidi rule 4 + if direction in ["AN", "EN"]: + if not number_type: + number_type = direction + else: + if number_type != direction: + raise IDNABidiError("Can not mix numeral types in a right-to-left label") + else: + # Bidi rule 5 + if direction not in ["L", "EN", "ES", "CS", "ET", "ON", "BN", "NSM"]: + raise IDNABidiError("Invalid direction for codepoint at position {} in a left-to-right label".format(idx)) + # Bidi rule 6 + if direction in ["L", "EN"]: + valid_ending = True + elif direction != "NSM": + valid_ending = False + + if not valid_ending: + raise IDNABidiError("Label ends with illegal codepoint directionality") + + return True + + +def check_initial_combiner(label: str) -> bool: + """Reject labels that begin with a combining mark. + + Per :rfc:`5891` §4.2.3.2 a label must not start with a character of + Unicode general category ``M`` (Mark). + + :param label: The label to check. + :returns: ``True`` if the first character is not a combining mark. + :raises IDNAError: If the label begins with a combining character. + """ + if unicodedata.category(label[0])[0] == "M": + raise IDNAError("Label begins with an illegal combining character") + return True + + +def check_hyphen_ok(label: str) -> bool: + """Validate the hyphen restrictions for a label. + + Per :rfc:`5891` §4.2.3.1 a label must not start or end with a hyphen + (``U+002D``), and must not have hyphens in both the third and fourth + positions (the prefix reserved for A-labels). + + :param label: The label to check. + :returns: ``True`` if the hyphen restrictions are satisfied. + :raises IDNAError: If any of the hyphen restrictions are violated. + """ + if label[2:4] == "--": + raise IDNAError("Label has disallowed hyphens in 3rd and 4th position") + if label[0] == "-" or label[-1] == "-": + raise IDNAError("Label must not start or end with a hyphen") + return True + + +def check_nfc(label: str) -> None: + """Require that a label is in Unicode Normalization Form C. + + :param label: The label to check. + :raises IDNAError: If ``label`` differs from its NFC normalisation. + """ + if unicodedata.normalize("NFC", label) != label: + raise IDNAError("Label must be in Normalization Form C") + + +def valid_contextj(label: str, pos: int) -> bool: + """Validate the CONTEXTJ rules from :rfc:`5892` Appendix A. + + These rules govern the contextual use of the joiner codepoints + ``U+200C`` (ZERO WIDTH NON-JOINER, Appendix A.1) and ``U+200D`` + (ZERO WIDTH JOINER, Appendix A.2) within a label. + + :param label: The label containing the codepoint. + :param pos: Index of the joiner codepoint within ``label``. + :returns: ``True`` if the codepoint at ``pos`` satisfies its CONTEXTJ + rule, ``False`` otherwise (including when the codepoint at + ``pos`` is not a recognised joiner). + :raises ValueError: If an adjacent codepoint has no Unicode name when + determining its combining class. + """ + cp_value = ord(label[pos]) + + if cp_value == 0x200C: + if pos > 0: + if _combining_class(ord(label[pos - 1])) == _virama_combining_class: + return True + + ok = False + for i in range(pos - 1, -1, -1): + joining_type = idnadata.joining_types().get(ord(label[i])) + if joining_type == ord("T"): + continue + elif joining_type in [ord("L"), ord("D")]: + ok = True + break + else: + break + + if not ok: + return False + + ok = False + for i in range(pos + 1, len(label)): + joining_type = idnadata.joining_types().get(ord(label[i])) + if joining_type == ord("T"): + continue + elif joining_type in [ord("R"), ord("D")]: + ok = True + break + else: + break + return ok + + if cp_value == 0x200D: + if pos > 0: + if _combining_class(ord(label[pos - 1])) == _virama_combining_class: + return True + return False + + else: + return False + + +def valid_contexto(label: str, pos: int, exception: bool = False) -> bool: + """Validate the CONTEXTO rules from :rfc:`5892` Appendix A. + + Covers the contextual rules for codepoints such as MIDDLE DOT + (``U+00B7``), Greek lower numeral sign, Hebrew punctuation, Katakana + middle dot, and the Arabic-Indic / Extended Arabic-Indic digit ranges. + + :param label: The label containing the codepoint. + :param pos: Index of the codepoint within ``label``. + :param exception: Reserved for forward compatibility; currently unused. + :returns: ``True`` if the codepoint at ``pos`` satisfies its CONTEXTO + rule, ``False`` otherwise (including when the codepoint is not a + recognised CONTEXTO codepoint). + """ + cp_value = ord(label[pos]) + + if cp_value == 0x00B7: + if 0 < pos < len(label) - 1: + if ord(label[pos - 1]) == 0x006C and ord(label[pos + 1]) == 0x006C: + return True + return False + + elif cp_value == 0x0375: + if pos < len(label) - 1 and len(label) > 1: + return _is_script(label[pos + 1], "Greek") + return False + + elif cp_value == 0x05F3 or cp_value == 0x05F4: + if pos > 0: + return _is_script(label[pos - 1], "Hebrew") + return False + + elif cp_value == 0x30FB: + for cp in label: + if cp == "\u30fb": + continue + if _is_script(cp, "Hiragana") or _is_script(cp, "Katakana") or _is_script(cp, "Han"): + return True + return False + + elif 0x660 <= cp_value <= 0x669: + for cp in label: + if 0x6F0 <= ord(cp) <= 0x06F9: + return False + return True + + elif 0x6F0 <= cp_value <= 0x6F9: + for cp in label: + if 0x660 <= ord(cp) <= 0x0669: + return False + return True + + return False + + +def check_label(label: Union[str, bytes, bytearray]) -> None: + """Run the full set of IDNA 2008 validity checks on a single label. + + Applies, in order: NFC normalisation (:func:`check_nfc`), hyphen + restrictions (:func:`check_hyphen_ok`), the no-leading-combiner rule + (:func:`check_initial_combiner`), per-codepoint validity (PVALID, + CONTEXTJ, CONTEXTO classes from :rfc:`5892`), and the Bidi Rule + (:func:`check_bidi`). + + :param label: The label to validate. ``bytes`` or ``bytearray`` input + is decoded as UTF-8 first. + :raises IDNAError: If the label is empty or fails a structural rule. + :raises InvalidCodepoint: If the label contains a DISALLOWED or + UNASSIGNED codepoint. + :raises InvalidCodepointContext: If a CONTEXTJ or CONTEXTO codepoint + is not valid in its context. + :raises IDNABidiError: If the Bidi Rule is violated. + """ + if isinstance(label, (bytes, bytearray)): + label = label.decode("utf-8") + if len(label) == 0: + raise IDNAError("Empty Label") + + check_nfc(label) + check_hyphen_ok(label) + check_initial_combiner(label) + + for pos, cp in enumerate(label): + cp_value = ord(cp) + if intranges_contain(cp_value, idnadata.codepoint_classes["PVALID"]): + continue + elif intranges_contain(cp_value, idnadata.codepoint_classes["CONTEXTJ"]): + try: + if not valid_contextj(label, pos): + raise InvalidCodepointContext( + "Joiner {} not allowed at position {} in {}".format(_unot(cp_value), pos + 1, repr(label)) + ) + except ValueError: + raise IDNAError( + "Unknown codepoint adjacent to joiner {} at position {} in {}".format( + _unot(cp_value), pos + 1, repr(label) + ) + ) + elif intranges_contain(cp_value, idnadata.codepoint_classes["CONTEXTO"]): + if not valid_contexto(label, pos): + raise InvalidCodepointContext( + "Codepoint {} not allowed at position {} in {}".format(_unot(cp_value), pos + 1, repr(label)) + ) + else: + raise InvalidCodepoint( + "Codepoint {} at position {} of {} not allowed".format(_unot(cp_value), pos + 1, repr(label)) + ) + + check_bidi(label) + + +def alabel(label: str) -> bytes: + """Convert a single U-label into its A-label form. + + The result is the ASCII-Compatible Encoding (ACE) form per :rfc:`5891` + §4: the label is validated, Punycode-encoded, and prefixed with + ``xn--``. Pure ASCII labels that are already valid IDNA labels are + returned unchanged (as :class:`bytes`). + + :param label: The label to convert, as a Unicode string. + :returns: The A-label as ASCII-encoded :class:`bytes`. + :raises IDNAError: If the label is invalid or the resulting A-label + exceeds 63 octets. + """ + try: + label_bytes = label.encode("ascii") + ulabel(label_bytes) + if not valid_label_length(label_bytes): + raise IDNAError("Label too long") + return label_bytes + except UnicodeEncodeError: + pass + + check_label(label) + label_bytes = _alabel_prefix + _punycode(label) + + if not valid_label_length(label_bytes): + raise IDNAError("Label too long") + + return label_bytes + + +def ulabel(label: Union[str, bytes, bytearray]) -> str: + """Convert a single A-label into its U-label form. + + Performs the inverse of :func:`alabel`: an ``xn--``-prefixed label is + Punycode-decoded and validated. Labels that are already Unicode (or + plain ASCII without the ACE prefix) are validated and returned as a + Unicode string. + + :param label: The label to convert. ``bytes`` or ``bytearray`` input + is treated as ASCII. + :returns: The U-label as a Unicode string. + :raises IDNAError: If the label is malformed or fails validation. + """ + if not isinstance(label, (bytes, bytearray)): + try: + label_bytes = label.encode("ascii") + except UnicodeEncodeError: + check_label(label) + return label + else: + label_bytes = bytes(label) + + label_bytes = label_bytes.lower() + if label_bytes.startswith(_alabel_prefix): + label_bytes = label_bytes[len(_alabel_prefix) :] + if not label_bytes: + raise IDNAError("Malformed A-label, no Punycode eligible content found") + if label_bytes.decode("ascii")[-1] == "-": + raise IDNAError("A-label must not end with a hyphen") + else: + check_label(label_bytes) + return label_bytes.decode("ascii") + + try: + label = label_bytes.decode("punycode") + except UnicodeError: + raise IDNAError("Invalid A-label") + check_label(label) + return label + + +def uts46_remap(domain: str, std3_rules: bool = True, transitional: bool = False) -> str: + """Apply the UTS #46 character mapping to a domain string. + + Implements the mapping table from `UTS #46 §4 + `_: each character is kept, + replaced, or rejected based on its status (``V``, ``M``, ``D``, ``3``, + ``I``). The result is returned in Normalisation Form C. + + :param domain: The full domain name to remap. + :param std3_rules: If ``True``, apply the stricter STD3 ASCII rules + (status ``3`` codepoints raise instead of being kept or mapped). + :param transitional: If ``True``, use transitional processing (status + ``D`` codepoints are mapped instead of kept). Transitional + processing has been removed from UTS #46 and this option is + retained only for backwards compatibility. + :returns: The remapped domain, in Normalisation Form C. + :raises InvalidCodepoint: If the domain contains a disallowed + codepoint under the chosen rules. + """ + from .uts46data import uts46data + + output = "" + + for pos, char in enumerate(domain): + code_point = ord(char) + uts46row = uts46data[code_point if code_point < 256 else bisect.bisect_left(uts46data, (code_point, "Z")) - 1] + status = uts46row[1] + replacement: Optional[str] = None + if len(uts46row) == 3: + replacement = uts46row[2] # ty: ignore[index-out-of-bounds] + if status == "V" or (status == "D" and not transitional) or (status == "3" and not std3_rules and replacement is None): + output += char + elif replacement is not None and ( + status == "M" or (status == "3" and not std3_rules) or (status == "D" and transitional) + ): + output += replacement + elif status == "I": + continue + else: + raise InvalidCodepoint( + "Codepoint {} not allowed at position {} in {}".format(_unot(code_point), pos + 1, repr(domain)) + ) + + return unicodedata.normalize("NFC", output) + + +def encode( + s: Union[str, bytes, bytearray], + strict: bool = False, + uts46: bool = False, + std3_rules: bool = False, + transitional: bool = False, +) -> bytes: + """Encode a Unicode domain name into its ASCII (A-label) form. + + Splits the input on label separators (only ``U+002E`` if ``strict`` is + set; otherwise also IDEOGRAPHIC FULL STOP ``U+3002``, FULLWIDTH FULL + STOP ``U+FF0E``, and HALFWIDTH IDEOGRAPHIC FULL STOP ``U+FF61``), + encodes each label with :func:`alabel`, and rejoins them with ``.``. + Optionally pre-processes the input through :func:`uts46_remap`. + + :param s: The domain name to encode. + :param strict: If ``True``, only ``U+002E`` is recognised as a label + separator. + :param uts46: If ``True``, apply UTS #46 mapping before encoding. + :param std3_rules: Forwarded to :func:`uts46_remap` when ``uts46`` is + ``True``. + :param transitional: Forwarded to :func:`uts46_remap` when ``uts46`` + is ``True``. Deprecated: emits a :class:`DeprecationWarning` and + will be removed in a future version. + :returns: The encoded domain as ASCII :class:`bytes`. + :raises IDNAError: If the domain is empty, contains an invalid label, + or exceeds the maximum domain length. + """ + if transitional: + warnings.warn( + "Transitional processing has been removed from UTS #46. " + "The transitional argument will be removed in a future version.", + DeprecationWarning, + stacklevel=2, + ) + if not isinstance(s, str): + try: + s = str(s, "ascii") + except (UnicodeDecodeError, TypeError): + raise IDNAError("should pass a unicode string to the function rather than a byte string.") + if uts46: + s = uts46_remap(s, std3_rules, transitional) + + # Reject inputs that exceed the maximum DNS domain length up-front + # to avoid expensive computation on long inputs. + if not valid_string_length(s, trailing_dot=True): + raise IDNAError("Domain too long") + + trailing_dot = False + result = [] + if strict: + labels = s.split(".") + else: + labels = _unicode_dots_re.split(s) + if not labels or labels == [""]: + raise IDNAError("Empty domain") + if labels[-1] == "": + del labels[-1] + trailing_dot = True + for label in labels: + s = alabel(label) + if s: + result.append(s) + else: + raise IDNAError("Empty label") + if trailing_dot: + result.append(b"") + s = b".".join(result) + if not valid_string_length(s, trailing_dot): + raise IDNAError("Domain too long") + return s + + +def decode( + s: Union[str, bytes, bytearray], + strict: bool = False, + uts46: bool = False, + std3_rules: bool = False, +) -> str: + """Decode an A-label-encoded domain name back to Unicode. + + Splits the input on label separators (see :func:`encode` for the + rules), decodes each label with :func:`ulabel`, and rejoins them + with ``.``. Optionally pre-processes the input through + :func:`uts46_remap`. + + :param s: The domain name to decode. + :param strict: If ``True``, only ``U+002E`` is recognised as a label + separator. + :param uts46: If ``True``, apply UTS #46 mapping before decoding. + :param std3_rules: Forwarded to :func:`uts46_remap` when ``uts46`` is + ``True``. + :returns: The decoded domain as a Unicode string. + :raises IDNAError: If the input is not valid ASCII, contains an + invalid label, or is empty. + """ + if not isinstance(s, str): + try: + s = str(s, "ascii") + except (UnicodeDecodeError, TypeError): + raise IDNAError("Invalid ASCII in A-label") + if uts46: + s = uts46_remap(s, std3_rules, False) + # Reject inputs that exceed the maximum DNS domain length up-front + # to avoid expensive computation on long inputs. + if not valid_string_length(s, trailing_dot=True): + raise IDNAError("Domain too long") + trailing_dot = False + result = [] + if not strict: + labels = _unicode_dots_re.split(s) + else: + labels = s.split(".") + if not labels or labels == [""]: + raise IDNAError("Empty domain") + if not labels[-1]: + del labels[-1] + trailing_dot = True + for label in labels: + s = ulabel(label) + if s: + result.append(s) + else: + raise IDNAError("Empty label") + if trailing_dot: + result.append("") + return ".".join(result) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/idna/idnadata.py b/bioagent-bench/.venv/lib/python3.14/site-packages/idna/idnadata.py new file mode 100644 index 0000000000000000000000000000000000000000..8335f26059c8bb932cca0b15a085ec91701b743a --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/idna/idnadata.py @@ -0,0 +1,4367 @@ +# This file is automatically generated by tools/idna-data + +from functools import lru_cache +from typing import Dict + +__version__ = "17.0.0" + +scripts = { + "Greek": ( + 0x37000000374, + 0x37500000378, + 0x37A0000037E, + 0x37F00000380, + 0x38400000385, + 0x38600000387, + 0x3880000038B, + 0x38C0000038D, + 0x38E000003A2, + 0x3A3000003E2, + 0x3F000000400, + 0x1D2600001D2B, + 0x1D5D00001D62, + 0x1D6600001D6B, + 0x1DBF00001DC0, + 0x1F0000001F16, + 0x1F1800001F1E, + 0x1F2000001F46, + 0x1F4800001F4E, + 0x1F5000001F58, + 0x1F5900001F5A, + 0x1F5B00001F5C, + 0x1F5D00001F5E, + 0x1F5F00001F7E, + 0x1F8000001FB5, + 0x1FB600001FC5, + 0x1FC600001FD4, + 0x1FD600001FDC, + 0x1FDD00001FF0, + 0x1FF200001FF5, + 0x1FF600001FFF, + 0x212600002127, + 0xAB650000AB66, + 0x101400001018F, + 0x101A0000101A1, + 0x1D2000001D246, + ), + "Han": ( + 0x2E8000002E9A, + 0x2E9B00002EF4, + 0x2F0000002FD6, + 0x300500003006, + 0x300700003008, + 0x30210000302A, + 0x30380000303C, + 0x340000004DC0, + 0x4E000000A000, + 0xF9000000FA6E, + 0xFA700000FADA, + 0x16FE200016FE4, + 0x16FF000016FF7, + 0x200000002A6E0, + 0x2A7000002B81E, + 0x2B8200002CEAE, + 0x2CEB00002EBE1, + 0x2EBF00002EE5E, + 0x2F8000002FA1E, + 0x300000003134B, + 0x313500003347A, + ), + "Hebrew": ( + 0x591000005C8, + 0x5D0000005EB, + 0x5EF000005F5, + 0xFB1D0000FB37, + 0xFB380000FB3D, + 0xFB3E0000FB3F, + 0xFB400000FB42, + 0xFB430000FB45, + 0xFB460000FB50, + ), + "Hiragana": ( + 0x304100003097, + 0x309D000030A0, + 0x1B0010001B120, + 0x1B1320001B133, + 0x1B1500001B153, + 0x1F2000001F201, + ), + "Katakana": ( + 0x30A1000030FB, + 0x30FD00003100, + 0x31F000003200, + 0x32D0000032FF, + 0x330000003358, + 0xFF660000FF70, + 0xFF710000FF9E, + 0x1AFF00001AFF4, + 0x1AFF50001AFFC, + 0x1AFFD0001AFFF, + 0x1B0000001B001, + 0x1B1200001B123, + 0x1B1550001B156, + 0x1B1640001B168, + ), +} + + +@lru_cache(None) +def joining_types() -> Dict[int, int]: + return { + 0xAD: 84, + 0x300: 84, + 0x301: 84, + 0x302: 84, + 0x303: 84, + 0x304: 84, + 0x305: 84, + 0x306: 84, + 0x307: 84, + 0x308: 84, + 0x309: 84, + 0x30A: 84, + 0x30B: 84, + 0x30C: 84, + 0x30D: 84, + 0x30E: 84, + 0x30F: 84, + 0x310: 84, + 0x311: 84, + 0x312: 84, + 0x313: 84, + 0x314: 84, + 0x315: 84, + 0x316: 84, + 0x317: 84, + 0x318: 84, + 0x319: 84, + 0x31A: 84, + 0x31B: 84, + 0x31C: 84, + 0x31D: 84, + 0x31E: 84, + 0x31F: 84, + 0x320: 84, + 0x321: 84, + 0x322: 84, + 0x323: 84, + 0x324: 84, + 0x325: 84, + 0x326: 84, + 0x327: 84, + 0x328: 84, + 0x329: 84, + 0x32A: 84, + 0x32B: 84, + 0x32C: 84, + 0x32D: 84, + 0x32E: 84, + 0x32F: 84, + 0x330: 84, + 0x331: 84, + 0x332: 84, + 0x333: 84, + 0x334: 84, + 0x335: 84, + 0x336: 84, + 0x337: 84, + 0x338: 84, + 0x339: 84, + 0x33A: 84, + 0x33B: 84, + 0x33C: 84, + 0x33D: 84, + 0x33E: 84, + 0x33F: 84, + 0x340: 84, + 0x341: 84, + 0x342: 84, + 0x343: 84, + 0x344: 84, + 0x345: 84, + 0x346: 84, + 0x347: 84, + 0x348: 84, + 0x349: 84, + 0x34A: 84, + 0x34B: 84, + 0x34C: 84, + 0x34D: 84, + 0x34E: 84, + 0x34F: 84, + 0x350: 84, + 0x351: 84, + 0x352: 84, + 0x353: 84, + 0x354: 84, + 0x355: 84, + 0x356: 84, + 0x357: 84, + 0x358: 84, + 0x359: 84, + 0x35A: 84, + 0x35B: 84, + 0x35C: 84, + 0x35D: 84, + 0x35E: 84, + 0x35F: 84, + 0x360: 84, + 0x361: 84, + 0x362: 84, + 0x363: 84, + 0x364: 84, + 0x365: 84, + 0x366: 84, + 0x367: 84, + 0x368: 84, + 0x369: 84, + 0x36A: 84, + 0x36B: 84, + 0x36C: 84, + 0x36D: 84, + 0x36E: 84, + 0x36F: 84, + 0x483: 84, + 0x484: 84, + 0x485: 84, + 0x486: 84, + 0x487: 84, + 0x488: 84, + 0x489: 84, + 0x591: 84, + 0x592: 84, + 0x593: 84, + 0x594: 84, + 0x595: 84, + 0x596: 84, + 0x597: 84, + 0x598: 84, + 0x599: 84, + 0x59A: 84, + 0x59B: 84, + 0x59C: 84, + 0x59D: 84, + 0x59E: 84, + 0x59F: 84, + 0x5A0: 84, + 0x5A1: 84, + 0x5A2: 84, + 0x5A3: 84, + 0x5A4: 84, + 0x5A5: 84, + 0x5A6: 84, + 0x5A7: 84, + 0x5A8: 84, + 0x5A9: 84, + 0x5AA: 84, + 0x5AB: 84, + 0x5AC: 84, + 0x5AD: 84, + 0x5AE: 84, + 0x5AF: 84, + 0x5B0: 84, + 0x5B1: 84, + 0x5B2: 84, + 0x5B3: 84, + 0x5B4: 84, + 0x5B5: 84, + 0x5B6: 84, + 0x5B7: 84, + 0x5B8: 84, + 0x5B9: 84, + 0x5BA: 84, + 0x5BB: 84, + 0x5BC: 84, + 0x5BD: 84, + 0x5BF: 84, + 0x5C1: 84, + 0x5C2: 84, + 0x5C4: 84, + 0x5C5: 84, + 0x5C7: 84, + 0x610: 84, + 0x611: 84, + 0x612: 84, + 0x613: 84, + 0x614: 84, + 0x615: 84, + 0x616: 84, + 0x617: 84, + 0x618: 84, + 0x619: 84, + 0x61A: 84, + 0x61C: 84, + 0x620: 68, + 0x622: 82, + 0x623: 82, + 0x624: 82, + 0x625: 82, + 0x626: 68, + 0x627: 82, + 0x628: 68, + 0x629: 82, + 0x62A: 68, + 0x62B: 68, + 0x62C: 68, + 0x62D: 68, + 0x62E: 68, + 0x62F: 82, + 0x630: 82, + 0x631: 82, + 0x632: 82, + 0x633: 68, + 0x634: 68, + 0x635: 68, + 0x636: 68, + 0x637: 68, + 0x638: 68, + 0x639: 68, + 0x63A: 68, + 0x63B: 68, + 0x63C: 68, + 0x63D: 68, + 0x63E: 68, + 0x63F: 68, + 0x640: 67, + 0x641: 68, + 0x642: 68, + 0x643: 68, + 0x644: 68, + 0x645: 68, + 0x646: 68, + 0x647: 68, + 0x648: 82, + 0x649: 68, + 0x64A: 68, + 0x64B: 84, + 0x64C: 84, + 0x64D: 84, + 0x64E: 84, + 0x64F: 84, + 0x650: 84, + 0x651: 84, + 0x652: 84, + 0x653: 84, + 0x654: 84, + 0x655: 84, + 0x656: 84, + 0x657: 84, + 0x658: 84, + 0x659: 84, + 0x65A: 84, + 0x65B: 84, + 0x65C: 84, + 0x65D: 84, + 0x65E: 84, + 0x65F: 84, + 0x66E: 68, + 0x66F: 68, + 0x670: 84, + 0x671: 82, + 0x672: 82, + 0x673: 82, + 0x675: 82, + 0x676: 82, + 0x677: 82, + 0x678: 68, + 0x679: 68, + 0x67A: 68, + 0x67B: 68, + 0x67C: 68, + 0x67D: 68, + 0x67E: 68, + 0x67F: 68, + 0x680: 68, + 0x681: 68, + 0x682: 68, + 0x683: 68, + 0x684: 68, + 0x685: 68, + 0x686: 68, + 0x687: 68, + 0x688: 82, + 0x689: 82, + 0x68A: 82, + 0x68B: 82, + 0x68C: 82, + 0x68D: 82, + 0x68E: 82, + 0x68F: 82, + 0x690: 82, + 0x691: 82, + 0x692: 82, + 0x693: 82, + 0x694: 82, + 0x695: 82, + 0x696: 82, + 0x697: 82, + 0x698: 82, + 0x699: 82, + 0x69A: 68, + 0x69B: 68, + 0x69C: 68, + 0x69D: 68, + 0x69E: 68, + 0x69F: 68, + 0x6A0: 68, + 0x6A1: 68, + 0x6A2: 68, + 0x6A3: 68, + 0x6A4: 68, + 0x6A5: 68, + 0x6A6: 68, + 0x6A7: 68, + 0x6A8: 68, + 0x6A9: 68, + 0x6AA: 68, + 0x6AB: 68, + 0x6AC: 68, + 0x6AD: 68, + 0x6AE: 68, + 0x6AF: 68, + 0x6B0: 68, + 0x6B1: 68, + 0x6B2: 68, + 0x6B3: 68, + 0x6B4: 68, + 0x6B5: 68, + 0x6B6: 68, + 0x6B7: 68, + 0x6B8: 68, + 0x6B9: 68, + 0x6BA: 68, + 0x6BB: 68, + 0x6BC: 68, + 0x6BD: 68, + 0x6BE: 68, + 0x6BF: 68, + 0x6C0: 82, + 0x6C1: 68, + 0x6C2: 68, + 0x6C3: 82, + 0x6C4: 82, + 0x6C5: 82, + 0x6C6: 82, + 0x6C7: 82, + 0x6C8: 82, + 0x6C9: 82, + 0x6CA: 82, + 0x6CB: 82, + 0x6CC: 68, + 0x6CD: 82, + 0x6CE: 68, + 0x6CF: 82, + 0x6D0: 68, + 0x6D1: 68, + 0x6D2: 82, + 0x6D3: 82, + 0x6D5: 82, + 0x6D6: 84, + 0x6D7: 84, + 0x6D8: 84, + 0x6D9: 84, + 0x6DA: 84, + 0x6DB: 84, + 0x6DC: 84, + 0x6DF: 84, + 0x6E0: 84, + 0x6E1: 84, + 0x6E2: 84, + 0x6E3: 84, + 0x6E4: 84, + 0x6E7: 84, + 0x6E8: 84, + 0x6EA: 84, + 0x6EB: 84, + 0x6EC: 84, + 0x6ED: 84, + 0x6EE: 82, + 0x6EF: 82, + 0x6FA: 68, + 0x6FB: 68, + 0x6FC: 68, + 0x6FF: 68, + 0x70F: 84, + 0x710: 82, + 0x711: 84, + 0x712: 68, + 0x713: 68, + 0x714: 68, + 0x715: 82, + 0x716: 82, + 0x717: 82, + 0x718: 82, + 0x719: 82, + 0x71A: 68, + 0x71B: 68, + 0x71C: 68, + 0x71D: 68, + 0x71E: 82, + 0x71F: 68, + 0x720: 68, + 0x721: 68, + 0x722: 68, + 0x723: 68, + 0x724: 68, + 0x725: 68, + 0x726: 68, + 0x727: 68, + 0x728: 82, + 0x729: 68, + 0x72A: 82, + 0x72B: 68, + 0x72C: 82, + 0x72D: 68, + 0x72E: 68, + 0x72F: 82, + 0x730: 84, + 0x731: 84, + 0x732: 84, + 0x733: 84, + 0x734: 84, + 0x735: 84, + 0x736: 84, + 0x737: 84, + 0x738: 84, + 0x739: 84, + 0x73A: 84, + 0x73B: 84, + 0x73C: 84, + 0x73D: 84, + 0x73E: 84, + 0x73F: 84, + 0x740: 84, + 0x741: 84, + 0x742: 84, + 0x743: 84, + 0x744: 84, + 0x745: 84, + 0x746: 84, + 0x747: 84, + 0x748: 84, + 0x749: 84, + 0x74A: 84, + 0x74D: 82, + 0x74E: 68, + 0x74F: 68, + 0x750: 68, + 0x751: 68, + 0x752: 68, + 0x753: 68, + 0x754: 68, + 0x755: 68, + 0x756: 68, + 0x757: 68, + 0x758: 68, + 0x759: 82, + 0x75A: 82, + 0x75B: 82, + 0x75C: 68, + 0x75D: 68, + 0x75E: 68, + 0x75F: 68, + 0x760: 68, + 0x761: 68, + 0x762: 68, + 0x763: 68, + 0x764: 68, + 0x765: 68, + 0x766: 68, + 0x767: 68, + 0x768: 68, + 0x769: 68, + 0x76A: 68, + 0x76B: 82, + 0x76C: 82, + 0x76D: 68, + 0x76E: 68, + 0x76F: 68, + 0x770: 68, + 0x771: 82, + 0x772: 68, + 0x773: 82, + 0x774: 82, + 0x775: 68, + 0x776: 68, + 0x777: 68, + 0x778: 82, + 0x779: 82, + 0x77A: 68, + 0x77B: 68, + 0x77C: 68, + 0x77D: 68, + 0x77E: 68, + 0x77F: 68, + 0x7A6: 84, + 0x7A7: 84, + 0x7A8: 84, + 0x7A9: 84, + 0x7AA: 84, + 0x7AB: 84, + 0x7AC: 84, + 0x7AD: 84, + 0x7AE: 84, + 0x7AF: 84, + 0x7B0: 84, + 0x7CA: 68, + 0x7CB: 68, + 0x7CC: 68, + 0x7CD: 68, + 0x7CE: 68, + 0x7CF: 68, + 0x7D0: 68, + 0x7D1: 68, + 0x7D2: 68, + 0x7D3: 68, + 0x7D4: 68, + 0x7D5: 68, + 0x7D6: 68, + 0x7D7: 68, + 0x7D8: 68, + 0x7D9: 68, + 0x7DA: 68, + 0x7DB: 68, + 0x7DC: 68, + 0x7DD: 68, + 0x7DE: 68, + 0x7DF: 68, + 0x7E0: 68, + 0x7E1: 68, + 0x7E2: 68, + 0x7E3: 68, + 0x7E4: 68, + 0x7E5: 68, + 0x7E6: 68, + 0x7E7: 68, + 0x7E8: 68, + 0x7E9: 68, + 0x7EA: 68, + 0x7EB: 84, + 0x7EC: 84, + 0x7ED: 84, + 0x7EE: 84, + 0x7EF: 84, + 0x7F0: 84, + 0x7F1: 84, + 0x7F2: 84, + 0x7F3: 84, + 0x7FA: 67, + 0x7FD: 84, + 0x816: 84, + 0x817: 84, + 0x818: 84, + 0x819: 84, + 0x81B: 84, + 0x81C: 84, + 0x81D: 84, + 0x81E: 84, + 0x81F: 84, + 0x820: 84, + 0x821: 84, + 0x822: 84, + 0x823: 84, + 0x825: 84, + 0x826: 84, + 0x827: 84, + 0x829: 84, + 0x82A: 84, + 0x82B: 84, + 0x82C: 84, + 0x82D: 84, + 0x840: 82, + 0x841: 68, + 0x842: 68, + 0x843: 68, + 0x844: 68, + 0x845: 68, + 0x846: 82, + 0x847: 82, + 0x848: 68, + 0x849: 82, + 0x84A: 68, + 0x84B: 68, + 0x84C: 68, + 0x84D: 68, + 0x84E: 68, + 0x84F: 68, + 0x850: 68, + 0x851: 68, + 0x852: 68, + 0x853: 68, + 0x854: 82, + 0x855: 68, + 0x856: 82, + 0x857: 82, + 0x858: 82, + 0x859: 84, + 0x85A: 84, + 0x85B: 84, + 0x860: 68, + 0x862: 68, + 0x863: 68, + 0x864: 68, + 0x865: 68, + 0x867: 82, + 0x868: 68, + 0x869: 82, + 0x86A: 82, + 0x870: 82, + 0x871: 82, + 0x872: 82, + 0x873: 82, + 0x874: 82, + 0x875: 82, + 0x876: 82, + 0x877: 82, + 0x878: 82, + 0x879: 82, + 0x87A: 82, + 0x87B: 82, + 0x87C: 82, + 0x87D: 82, + 0x87E: 82, + 0x87F: 82, + 0x880: 82, + 0x881: 82, + 0x882: 82, + 0x883: 67, + 0x884: 67, + 0x885: 67, + 0x886: 68, + 0x889: 68, + 0x88A: 68, + 0x88B: 68, + 0x88C: 68, + 0x88D: 68, + 0x88E: 82, + 0x88F: 68, + 0x897: 84, + 0x898: 84, + 0x899: 84, + 0x89A: 84, + 0x89B: 84, + 0x89C: 84, + 0x89D: 84, + 0x89E: 84, + 0x89F: 84, + 0x8A0: 68, + 0x8A1: 68, + 0x8A2: 68, + 0x8A3: 68, + 0x8A4: 68, + 0x8A5: 68, + 0x8A6: 68, + 0x8A7: 68, + 0x8A8: 68, + 0x8A9: 68, + 0x8AA: 82, + 0x8AB: 82, + 0x8AC: 82, + 0x8AE: 82, + 0x8AF: 68, + 0x8B0: 68, + 0x8B1: 82, + 0x8B2: 82, + 0x8B3: 68, + 0x8B4: 68, + 0x8B5: 68, + 0x8B6: 68, + 0x8B7: 68, + 0x8B8: 68, + 0x8B9: 82, + 0x8BA: 68, + 0x8BB: 68, + 0x8BC: 68, + 0x8BD: 68, + 0x8BE: 68, + 0x8BF: 68, + 0x8C0: 68, + 0x8C1: 68, + 0x8C2: 68, + 0x8C3: 68, + 0x8C4: 68, + 0x8C5: 68, + 0x8C6: 68, + 0x8C7: 68, + 0x8C8: 68, + 0x8CA: 84, + 0x8CB: 84, + 0x8CC: 84, + 0x8CD: 84, + 0x8CE: 84, + 0x8CF: 84, + 0x8D0: 84, + 0x8D1: 84, + 0x8D2: 84, + 0x8D3: 84, + 0x8D4: 84, + 0x8D5: 84, + 0x8D6: 84, + 0x8D7: 84, + 0x8D8: 84, + 0x8D9: 84, + 0x8DA: 84, + 0x8DB: 84, + 0x8DC: 84, + 0x8DD: 84, + 0x8DE: 84, + 0x8DF: 84, + 0x8E0: 84, + 0x8E1: 84, + 0x8E3: 84, + 0x8E4: 84, + 0x8E5: 84, + 0x8E6: 84, + 0x8E7: 84, + 0x8E8: 84, + 0x8E9: 84, + 0x8EA: 84, + 0x8EB: 84, + 0x8EC: 84, + 0x8ED: 84, + 0x8EE: 84, + 0x8EF: 84, + 0x8F0: 84, + 0x8F1: 84, + 0x8F2: 84, + 0x8F3: 84, + 0x8F4: 84, + 0x8F5: 84, + 0x8F6: 84, + 0x8F7: 84, + 0x8F8: 84, + 0x8F9: 84, + 0x8FA: 84, + 0x8FB: 84, + 0x8FC: 84, + 0x8FD: 84, + 0x8FE: 84, + 0x8FF: 84, + 0x900: 84, + 0x901: 84, + 0x902: 84, + 0x93A: 84, + 0x93C: 84, + 0x941: 84, + 0x942: 84, + 0x943: 84, + 0x944: 84, + 0x945: 84, + 0x946: 84, + 0x947: 84, + 0x948: 84, + 0x94D: 84, + 0x951: 84, + 0x952: 84, + 0x953: 84, + 0x954: 84, + 0x955: 84, + 0x956: 84, + 0x957: 84, + 0x962: 84, + 0x963: 84, + 0x981: 84, + 0x9BC: 84, + 0x9C1: 84, + 0x9C2: 84, + 0x9C3: 84, + 0x9C4: 84, + 0x9CD: 84, + 0x9E2: 84, + 0x9E3: 84, + 0x9FE: 84, + 0xA01: 84, + 0xA02: 84, + 0xA3C: 84, + 0xA41: 84, + 0xA42: 84, + 0xA47: 84, + 0xA48: 84, + 0xA4B: 84, + 0xA4C: 84, + 0xA4D: 84, + 0xA51: 84, + 0xA70: 84, + 0xA71: 84, + 0xA75: 84, + 0xA81: 84, + 0xA82: 84, + 0xABC: 84, + 0xAC1: 84, + 0xAC2: 84, + 0xAC3: 84, + 0xAC4: 84, + 0xAC5: 84, + 0xAC7: 84, + 0xAC8: 84, + 0xACD: 84, + 0xAE2: 84, + 0xAE3: 84, + 0xAFA: 84, + 0xAFB: 84, + 0xAFC: 84, + 0xAFD: 84, + 0xAFE: 84, + 0xAFF: 84, + 0xB01: 84, + 0xB3C: 84, + 0xB3F: 84, + 0xB41: 84, + 0xB42: 84, + 0xB43: 84, + 0xB44: 84, + 0xB4D: 84, + 0xB55: 84, + 0xB56: 84, + 0xB62: 84, + 0xB63: 84, + 0xB82: 84, + 0xBC0: 84, + 0xBCD: 84, + 0xC00: 84, + 0xC04: 84, + 0xC3C: 84, + 0xC3E: 84, + 0xC3F: 84, + 0xC40: 84, + 0xC46: 84, + 0xC47: 84, + 0xC48: 84, + 0xC4A: 84, + 0xC4B: 84, + 0xC4C: 84, + 0xC4D: 84, + 0xC55: 84, + 0xC56: 84, + 0xC62: 84, + 0xC63: 84, + 0xC81: 84, + 0xCBC: 84, + 0xCBF: 84, + 0xCC6: 84, + 0xCCC: 84, + 0xCCD: 84, + 0xCE2: 84, + 0xCE3: 84, + 0xD00: 84, + 0xD01: 84, + 0xD3B: 84, + 0xD3C: 84, + 0xD41: 84, + 0xD42: 84, + 0xD43: 84, + 0xD44: 84, + 0xD4D: 84, + 0xD62: 84, + 0xD63: 84, + 0xD81: 84, + 0xDCA: 84, + 0xDD2: 84, + 0xDD3: 84, + 0xDD4: 84, + 0xDD6: 84, + 0xE31: 84, + 0xE34: 84, + 0xE35: 84, + 0xE36: 84, + 0xE37: 84, + 0xE38: 84, + 0xE39: 84, + 0xE3A: 84, + 0xE47: 84, + 0xE48: 84, + 0xE49: 84, + 0xE4A: 84, + 0xE4B: 84, + 0xE4C: 84, + 0xE4D: 84, + 0xE4E: 84, + 0xEB1: 84, + 0xEB4: 84, + 0xEB5: 84, + 0xEB6: 84, + 0xEB7: 84, + 0xEB8: 84, + 0xEB9: 84, + 0xEBA: 84, + 0xEBB: 84, + 0xEBC: 84, + 0xEC8: 84, + 0xEC9: 84, + 0xECA: 84, + 0xECB: 84, + 0xECC: 84, + 0xECD: 84, + 0xECE: 84, + 0xF18: 84, + 0xF19: 84, + 0xF35: 84, + 0xF37: 84, + 0xF39: 84, + 0xF71: 84, + 0xF72: 84, + 0xF73: 84, + 0xF74: 84, + 0xF75: 84, + 0xF76: 84, + 0xF77: 84, + 0xF78: 84, + 0xF79: 84, + 0xF7A: 84, + 0xF7B: 84, + 0xF7C: 84, + 0xF7D: 84, + 0xF7E: 84, + 0xF80: 84, + 0xF81: 84, + 0xF82: 84, + 0xF83: 84, + 0xF84: 84, + 0xF86: 84, + 0xF87: 84, + 0xF8D: 84, + 0xF8E: 84, + 0xF8F: 84, + 0xF90: 84, + 0xF91: 84, + 0xF92: 84, + 0xF93: 84, + 0xF94: 84, + 0xF95: 84, + 0xF96: 84, + 0xF97: 84, + 0xF99: 84, + 0xF9A: 84, + 0xF9B: 84, + 0xF9C: 84, + 0xF9D: 84, + 0xF9E: 84, + 0xF9F: 84, + 0xFA0: 84, + 0xFA1: 84, + 0xFA2: 84, + 0xFA3: 84, + 0xFA4: 84, + 0xFA5: 84, + 0xFA6: 84, + 0xFA7: 84, + 0xFA8: 84, + 0xFA9: 84, + 0xFAA: 84, + 0xFAB: 84, + 0xFAC: 84, + 0xFAD: 84, + 0xFAE: 84, + 0xFAF: 84, + 0xFB0: 84, + 0xFB1: 84, + 0xFB2: 84, + 0xFB3: 84, + 0xFB4: 84, + 0xFB5: 84, + 0xFB6: 84, + 0xFB7: 84, + 0xFB8: 84, + 0xFB9: 84, + 0xFBA: 84, + 0xFBB: 84, + 0xFBC: 84, + 0xFC6: 84, + 0x102D: 84, + 0x102E: 84, + 0x102F: 84, + 0x1030: 84, + 0x1032: 84, + 0x1033: 84, + 0x1034: 84, + 0x1035: 84, + 0x1036: 84, + 0x1037: 84, + 0x1039: 84, + 0x103A: 84, + 0x103D: 84, + 0x103E: 84, + 0x1058: 84, + 0x1059: 84, + 0x105E: 84, + 0x105F: 84, + 0x1060: 84, + 0x1071: 84, + 0x1072: 84, + 0x1073: 84, + 0x1074: 84, + 0x1082: 84, + 0x1085: 84, + 0x1086: 84, + 0x108D: 84, + 0x109D: 84, + 0x135D: 84, + 0x135E: 84, + 0x135F: 84, + 0x1712: 84, + 0x1713: 84, + 0x1714: 84, + 0x1732: 84, + 0x1733: 84, + 0x1752: 84, + 0x1753: 84, + 0x1772: 84, + 0x1773: 84, + 0x17B4: 84, + 0x17B5: 84, + 0x17B7: 84, + 0x17B8: 84, + 0x17B9: 84, + 0x17BA: 84, + 0x17BB: 84, + 0x17BC: 84, + 0x17BD: 84, + 0x17C6: 84, + 0x17C9: 84, + 0x17CA: 84, + 0x17CB: 84, + 0x17CC: 84, + 0x17CD: 84, + 0x17CE: 84, + 0x17CF: 84, + 0x17D0: 84, + 0x17D1: 84, + 0x17D2: 84, + 0x17D3: 84, + 0x17DD: 84, + 0x1807: 68, + 0x180A: 67, + 0x180B: 84, + 0x180C: 84, + 0x180D: 84, + 0x180F: 84, + 0x1820: 68, + 0x1821: 68, + 0x1822: 68, + 0x1823: 68, + 0x1824: 68, + 0x1825: 68, + 0x1826: 68, + 0x1827: 68, + 0x1828: 68, + 0x1829: 68, + 0x182A: 68, + 0x182B: 68, + 0x182C: 68, + 0x182D: 68, + 0x182E: 68, + 0x182F: 68, + 0x1830: 68, + 0x1831: 68, + 0x1832: 68, + 0x1833: 68, + 0x1834: 68, + 0x1835: 68, + 0x1836: 68, + 0x1837: 68, + 0x1838: 68, + 0x1839: 68, + 0x183A: 68, + 0x183B: 68, + 0x183C: 68, + 0x183D: 68, + 0x183E: 68, + 0x183F: 68, + 0x1840: 68, + 0x1841: 68, + 0x1842: 68, + 0x1843: 68, + 0x1844: 68, + 0x1845: 68, + 0x1846: 68, + 0x1847: 68, + 0x1848: 68, + 0x1849: 68, + 0x184A: 68, + 0x184B: 68, + 0x184C: 68, + 0x184D: 68, + 0x184E: 68, + 0x184F: 68, + 0x1850: 68, + 0x1851: 68, + 0x1852: 68, + 0x1853: 68, + 0x1854: 68, + 0x1855: 68, + 0x1856: 68, + 0x1857: 68, + 0x1858: 68, + 0x1859: 68, + 0x185A: 68, + 0x185B: 68, + 0x185C: 68, + 0x185D: 68, + 0x185E: 68, + 0x185F: 68, + 0x1860: 68, + 0x1861: 68, + 0x1862: 68, + 0x1863: 68, + 0x1864: 68, + 0x1865: 68, + 0x1866: 68, + 0x1867: 68, + 0x1868: 68, + 0x1869: 68, + 0x186A: 68, + 0x186B: 68, + 0x186C: 68, + 0x186D: 68, + 0x186E: 68, + 0x186F: 68, + 0x1870: 68, + 0x1871: 68, + 0x1872: 68, + 0x1873: 68, + 0x1874: 68, + 0x1875: 68, + 0x1876: 68, + 0x1877: 68, + 0x1878: 68, + 0x1885: 84, + 0x1886: 84, + 0x1887: 68, + 0x1888: 68, + 0x1889: 68, + 0x188A: 68, + 0x188B: 68, + 0x188C: 68, + 0x188D: 68, + 0x188E: 68, + 0x188F: 68, + 0x1890: 68, + 0x1891: 68, + 0x1892: 68, + 0x1893: 68, + 0x1894: 68, + 0x1895: 68, + 0x1896: 68, + 0x1897: 68, + 0x1898: 68, + 0x1899: 68, + 0x189A: 68, + 0x189B: 68, + 0x189C: 68, + 0x189D: 68, + 0x189E: 68, + 0x189F: 68, + 0x18A0: 68, + 0x18A1: 68, + 0x18A2: 68, + 0x18A3: 68, + 0x18A4: 68, + 0x18A5: 68, + 0x18A6: 68, + 0x18A7: 68, + 0x18A8: 68, + 0x18A9: 84, + 0x18AA: 68, + 0x1920: 84, + 0x1921: 84, + 0x1922: 84, + 0x1927: 84, + 0x1928: 84, + 0x1932: 84, + 0x1939: 84, + 0x193A: 84, + 0x193B: 84, + 0x1A17: 84, + 0x1A18: 84, + 0x1A1B: 84, + 0x1A56: 84, + 0x1A58: 84, + 0x1A59: 84, + 0x1A5A: 84, + 0x1A5B: 84, + 0x1A5C: 84, + 0x1A5D: 84, + 0x1A5E: 84, + 0x1A60: 84, + 0x1A62: 84, + 0x1A65: 84, + 0x1A66: 84, + 0x1A67: 84, + 0x1A68: 84, + 0x1A69: 84, + 0x1A6A: 84, + 0x1A6B: 84, + 0x1A6C: 84, + 0x1A73: 84, + 0x1A74: 84, + 0x1A75: 84, + 0x1A76: 84, + 0x1A77: 84, + 0x1A78: 84, + 0x1A79: 84, + 0x1A7A: 84, + 0x1A7B: 84, + 0x1A7C: 84, + 0x1A7F: 84, + 0x1AB0: 84, + 0x1AB1: 84, + 0x1AB2: 84, + 0x1AB3: 84, + 0x1AB4: 84, + 0x1AB5: 84, + 0x1AB6: 84, + 0x1AB7: 84, + 0x1AB8: 84, + 0x1AB9: 84, + 0x1ABA: 84, + 0x1ABB: 84, + 0x1ABC: 84, + 0x1ABD: 84, + 0x1ABE: 84, + 0x1ABF: 84, + 0x1AC0: 84, + 0x1AC1: 84, + 0x1AC2: 84, + 0x1AC3: 84, + 0x1AC4: 84, + 0x1AC5: 84, + 0x1AC6: 84, + 0x1AC7: 84, + 0x1AC8: 84, + 0x1AC9: 84, + 0x1ACA: 84, + 0x1ACB: 84, + 0x1ACC: 84, + 0x1ACD: 84, + 0x1ACE: 84, + 0x1ACF: 84, + 0x1AD0: 84, + 0x1AD1: 84, + 0x1AD2: 84, + 0x1AD3: 84, + 0x1AD4: 84, + 0x1AD5: 84, + 0x1AD6: 84, + 0x1AD7: 84, + 0x1AD8: 84, + 0x1AD9: 84, + 0x1ADA: 84, + 0x1ADB: 84, + 0x1ADC: 84, + 0x1ADD: 84, + 0x1AE0: 84, + 0x1AE1: 84, + 0x1AE2: 84, + 0x1AE3: 84, + 0x1AE4: 84, + 0x1AE5: 84, + 0x1AE6: 84, + 0x1AE7: 84, + 0x1AE8: 84, + 0x1AE9: 84, + 0x1AEA: 84, + 0x1AEB: 84, + 0x1B00: 84, + 0x1B01: 84, + 0x1B02: 84, + 0x1B03: 84, + 0x1B34: 84, + 0x1B36: 84, + 0x1B37: 84, + 0x1B38: 84, + 0x1B39: 84, + 0x1B3A: 84, + 0x1B3C: 84, + 0x1B42: 84, + 0x1B6B: 84, + 0x1B6C: 84, + 0x1B6D: 84, + 0x1B6E: 84, + 0x1B6F: 84, + 0x1B70: 84, + 0x1B71: 84, + 0x1B72: 84, + 0x1B73: 84, + 0x1B80: 84, + 0x1B81: 84, + 0x1BA2: 84, + 0x1BA3: 84, + 0x1BA4: 84, + 0x1BA5: 84, + 0x1BA8: 84, + 0x1BA9: 84, + 0x1BAB: 84, + 0x1BAC: 84, + 0x1BAD: 84, + 0x1BE6: 84, + 0x1BE8: 84, + 0x1BE9: 84, + 0x1BED: 84, + 0x1BEF: 84, + 0x1BF0: 84, + 0x1BF1: 84, + 0x1C2C: 84, + 0x1C2D: 84, + 0x1C2E: 84, + 0x1C2F: 84, + 0x1C30: 84, + 0x1C31: 84, + 0x1C32: 84, + 0x1C33: 84, + 0x1C36: 84, + 0x1C37: 84, + 0x1CD0: 84, + 0x1CD1: 84, + 0x1CD2: 84, + 0x1CD4: 84, + 0x1CD5: 84, + 0x1CD6: 84, + 0x1CD7: 84, + 0x1CD8: 84, + 0x1CD9: 84, + 0x1CDA: 84, + 0x1CDB: 84, + 0x1CDC: 84, + 0x1CDD: 84, + 0x1CDE: 84, + 0x1CDF: 84, + 0x1CE0: 84, + 0x1CE2: 84, + 0x1CE3: 84, + 0x1CE4: 84, + 0x1CE5: 84, + 0x1CE6: 84, + 0x1CE7: 84, + 0x1CE8: 84, + 0x1CED: 84, + 0x1CF4: 84, + 0x1CF8: 84, + 0x1CF9: 84, + 0x1DC0: 84, + 0x1DC1: 84, + 0x1DC2: 84, + 0x1DC3: 84, + 0x1DC4: 84, + 0x1DC5: 84, + 0x1DC6: 84, + 0x1DC7: 84, + 0x1DC8: 84, + 0x1DC9: 84, + 0x1DCA: 84, + 0x1DCB: 84, + 0x1DCC: 84, + 0x1DCD: 84, + 0x1DCE: 84, + 0x1DCF: 84, + 0x1DD0: 84, + 0x1DD1: 84, + 0x1DD2: 84, + 0x1DD3: 84, + 0x1DD4: 84, + 0x1DD5: 84, + 0x1DD6: 84, + 0x1DD7: 84, + 0x1DD8: 84, + 0x1DD9: 84, + 0x1DDA: 84, + 0x1DDB: 84, + 0x1DDC: 84, + 0x1DDD: 84, + 0x1DDE: 84, + 0x1DDF: 84, + 0x1DE0: 84, + 0x1DE1: 84, + 0x1DE2: 84, + 0x1DE3: 84, + 0x1DE4: 84, + 0x1DE5: 84, + 0x1DE6: 84, + 0x1DE7: 84, + 0x1DE8: 84, + 0x1DE9: 84, + 0x1DEA: 84, + 0x1DEB: 84, + 0x1DEC: 84, + 0x1DED: 84, + 0x1DEE: 84, + 0x1DEF: 84, + 0x1DF0: 84, + 0x1DF1: 84, + 0x1DF2: 84, + 0x1DF3: 84, + 0x1DF4: 84, + 0x1DF5: 84, + 0x1DF6: 84, + 0x1DF7: 84, + 0x1DF8: 84, + 0x1DF9: 84, + 0x1DFA: 84, + 0x1DFB: 84, + 0x1DFC: 84, + 0x1DFD: 84, + 0x1DFE: 84, + 0x1DFF: 84, + 0x200B: 84, + 0x200D: 67, + 0x200E: 84, + 0x200F: 84, + 0x202A: 84, + 0x202B: 84, + 0x202C: 84, + 0x202D: 84, + 0x202E: 84, + 0x2060: 84, + 0x2061: 84, + 0x2062: 84, + 0x2063: 84, + 0x2064: 84, + 0x206A: 84, + 0x206B: 84, + 0x206C: 84, + 0x206D: 84, + 0x206E: 84, + 0x206F: 84, + 0x20D0: 84, + 0x20D1: 84, + 0x20D2: 84, + 0x20D3: 84, + 0x20D4: 84, + 0x20D5: 84, + 0x20D6: 84, + 0x20D7: 84, + 0x20D8: 84, + 0x20D9: 84, + 0x20DA: 84, + 0x20DB: 84, + 0x20DC: 84, + 0x20DD: 84, + 0x20DE: 84, + 0x20DF: 84, + 0x20E0: 84, + 0x20E1: 84, + 0x20E2: 84, + 0x20E3: 84, + 0x20E4: 84, + 0x20E5: 84, + 0x20E6: 84, + 0x20E7: 84, + 0x20E8: 84, + 0x20E9: 84, + 0x20EA: 84, + 0x20EB: 84, + 0x20EC: 84, + 0x20ED: 84, + 0x20EE: 84, + 0x20EF: 84, + 0x20F0: 84, + 0x2CEF: 84, + 0x2CF0: 84, + 0x2CF1: 84, + 0x2D7F: 84, + 0x2DE0: 84, + 0x2DE1: 84, + 0x2DE2: 84, + 0x2DE3: 84, + 0x2DE4: 84, + 0x2DE5: 84, + 0x2DE6: 84, + 0x2DE7: 84, + 0x2DE8: 84, + 0x2DE9: 84, + 0x2DEA: 84, + 0x2DEB: 84, + 0x2DEC: 84, + 0x2DED: 84, + 0x2DEE: 84, + 0x2DEF: 84, + 0x2DF0: 84, + 0x2DF1: 84, + 0x2DF2: 84, + 0x2DF3: 84, + 0x2DF4: 84, + 0x2DF5: 84, + 0x2DF6: 84, + 0x2DF7: 84, + 0x2DF8: 84, + 0x2DF9: 84, + 0x2DFA: 84, + 0x2DFB: 84, + 0x2DFC: 84, + 0x2DFD: 84, + 0x2DFE: 84, + 0x2DFF: 84, + 0x302A: 84, + 0x302B: 84, + 0x302C: 84, + 0x302D: 84, + 0x3099: 84, + 0x309A: 84, + 0xA66F: 84, + 0xA670: 84, + 0xA671: 84, + 0xA672: 84, + 0xA674: 84, + 0xA675: 84, + 0xA676: 84, + 0xA677: 84, + 0xA678: 84, + 0xA679: 84, + 0xA67A: 84, + 0xA67B: 84, + 0xA67C: 84, + 0xA67D: 84, + 0xA69E: 84, + 0xA69F: 84, + 0xA6F0: 84, + 0xA6F1: 84, + 0xA802: 84, + 0xA806: 84, + 0xA80B: 84, + 0xA825: 84, + 0xA826: 84, + 0xA82C: 84, + 0xA840: 68, + 0xA841: 68, + 0xA842: 68, + 0xA843: 68, + 0xA844: 68, + 0xA845: 68, + 0xA846: 68, + 0xA847: 68, + 0xA848: 68, + 0xA849: 68, + 0xA84A: 68, + 0xA84B: 68, + 0xA84C: 68, + 0xA84D: 68, + 0xA84E: 68, + 0xA84F: 68, + 0xA850: 68, + 0xA851: 68, + 0xA852: 68, + 0xA853: 68, + 0xA854: 68, + 0xA855: 68, + 0xA856: 68, + 0xA857: 68, + 0xA858: 68, + 0xA859: 68, + 0xA85A: 68, + 0xA85B: 68, + 0xA85C: 68, + 0xA85D: 68, + 0xA85E: 68, + 0xA85F: 68, + 0xA860: 68, + 0xA861: 68, + 0xA862: 68, + 0xA863: 68, + 0xA864: 68, + 0xA865: 68, + 0xA866: 68, + 0xA867: 68, + 0xA868: 68, + 0xA869: 68, + 0xA86A: 68, + 0xA86B: 68, + 0xA86C: 68, + 0xA86D: 68, + 0xA86E: 68, + 0xA86F: 68, + 0xA870: 68, + 0xA871: 68, + 0xA872: 76, + 0xA8C4: 84, + 0xA8C5: 84, + 0xA8E0: 84, + 0xA8E1: 84, + 0xA8E2: 84, + 0xA8E3: 84, + 0xA8E4: 84, + 0xA8E5: 84, + 0xA8E6: 84, + 0xA8E7: 84, + 0xA8E8: 84, + 0xA8E9: 84, + 0xA8EA: 84, + 0xA8EB: 84, + 0xA8EC: 84, + 0xA8ED: 84, + 0xA8EE: 84, + 0xA8EF: 84, + 0xA8F0: 84, + 0xA8F1: 84, + 0xA8FF: 84, + 0xA926: 84, + 0xA927: 84, + 0xA928: 84, + 0xA929: 84, + 0xA92A: 84, + 0xA92B: 84, + 0xA92C: 84, + 0xA92D: 84, + 0xA947: 84, + 0xA948: 84, + 0xA949: 84, + 0xA94A: 84, + 0xA94B: 84, + 0xA94C: 84, + 0xA94D: 84, + 0xA94E: 84, + 0xA94F: 84, + 0xA950: 84, + 0xA951: 84, + 0xA980: 84, + 0xA981: 84, + 0xA982: 84, + 0xA9B3: 84, + 0xA9B6: 84, + 0xA9B7: 84, + 0xA9B8: 84, + 0xA9B9: 84, + 0xA9BC: 84, + 0xA9BD: 84, + 0xA9E5: 84, + 0xAA29: 84, + 0xAA2A: 84, + 0xAA2B: 84, + 0xAA2C: 84, + 0xAA2D: 84, + 0xAA2E: 84, + 0xAA31: 84, + 0xAA32: 84, + 0xAA35: 84, + 0xAA36: 84, + 0xAA43: 84, + 0xAA4C: 84, + 0xAA7C: 84, + 0xAAB0: 84, + 0xAAB2: 84, + 0xAAB3: 84, + 0xAAB4: 84, + 0xAAB7: 84, + 0xAAB8: 84, + 0xAABE: 84, + 0xAABF: 84, + 0xAAC1: 84, + 0xAAEC: 84, + 0xAAED: 84, + 0xAAF6: 84, + 0xABE5: 84, + 0xABE8: 84, + 0xABED: 84, + 0xFB1E: 84, + 0xFE00: 84, + 0xFE01: 84, + 0xFE02: 84, + 0xFE03: 84, + 0xFE04: 84, + 0xFE05: 84, + 0xFE06: 84, + 0xFE07: 84, + 0xFE08: 84, + 0xFE09: 84, + 0xFE0A: 84, + 0xFE0B: 84, + 0xFE0C: 84, + 0xFE0D: 84, + 0xFE0E: 84, + 0xFE0F: 84, + 0xFE20: 84, + 0xFE21: 84, + 0xFE22: 84, + 0xFE23: 84, + 0xFE24: 84, + 0xFE25: 84, + 0xFE26: 84, + 0xFE27: 84, + 0xFE28: 84, + 0xFE29: 84, + 0xFE2A: 84, + 0xFE2B: 84, + 0xFE2C: 84, + 0xFE2D: 84, + 0xFE2E: 84, + 0xFE2F: 84, + 0xFEFF: 84, + 0xFFF9: 84, + 0xFFFA: 84, + 0xFFFB: 84, + 0x101FD: 84, + 0x102E0: 84, + 0x10376: 84, + 0x10377: 84, + 0x10378: 84, + 0x10379: 84, + 0x1037A: 84, + 0x10A01: 84, + 0x10A02: 84, + 0x10A03: 84, + 0x10A05: 84, + 0x10A06: 84, + 0x10A0C: 84, + 0x10A0D: 84, + 0x10A0E: 84, + 0x10A0F: 84, + 0x10A38: 84, + 0x10A39: 84, + 0x10A3A: 84, + 0x10A3F: 84, + 0x10AC0: 68, + 0x10AC1: 68, + 0x10AC2: 68, + 0x10AC3: 68, + 0x10AC4: 68, + 0x10AC5: 82, + 0x10AC7: 82, + 0x10AC9: 82, + 0x10ACA: 82, + 0x10ACD: 76, + 0x10ACE: 82, + 0x10ACF: 82, + 0x10AD0: 82, + 0x10AD1: 82, + 0x10AD2: 82, + 0x10AD3: 68, + 0x10AD4: 68, + 0x10AD5: 68, + 0x10AD6: 68, + 0x10AD7: 76, + 0x10AD8: 68, + 0x10AD9: 68, + 0x10ADA: 68, + 0x10ADB: 68, + 0x10ADC: 68, + 0x10ADD: 82, + 0x10ADE: 68, + 0x10ADF: 68, + 0x10AE0: 68, + 0x10AE1: 82, + 0x10AE4: 82, + 0x10AE5: 84, + 0x10AE6: 84, + 0x10AEB: 68, + 0x10AEC: 68, + 0x10AED: 68, + 0x10AEE: 68, + 0x10AEF: 82, + 0x10B80: 68, + 0x10B81: 82, + 0x10B82: 68, + 0x10B83: 82, + 0x10B84: 82, + 0x10B85: 82, + 0x10B86: 68, + 0x10B87: 68, + 0x10B88: 68, + 0x10B89: 82, + 0x10B8A: 68, + 0x10B8B: 68, + 0x10B8C: 82, + 0x10B8D: 68, + 0x10B8E: 82, + 0x10B8F: 82, + 0x10B90: 68, + 0x10B91: 82, + 0x10BA9: 82, + 0x10BAA: 82, + 0x10BAB: 82, + 0x10BAC: 82, + 0x10BAD: 68, + 0x10BAE: 68, + 0x10D00: 76, + 0x10D01: 68, + 0x10D02: 68, + 0x10D03: 68, + 0x10D04: 68, + 0x10D05: 68, + 0x10D06: 68, + 0x10D07: 68, + 0x10D08: 68, + 0x10D09: 68, + 0x10D0A: 68, + 0x10D0B: 68, + 0x10D0C: 68, + 0x10D0D: 68, + 0x10D0E: 68, + 0x10D0F: 68, + 0x10D10: 68, + 0x10D11: 68, + 0x10D12: 68, + 0x10D13: 68, + 0x10D14: 68, + 0x10D15: 68, + 0x10D16: 68, + 0x10D17: 68, + 0x10D18: 68, + 0x10D19: 68, + 0x10D1A: 68, + 0x10D1B: 68, + 0x10D1C: 68, + 0x10D1D: 68, + 0x10D1E: 68, + 0x10D1F: 68, + 0x10D20: 68, + 0x10D21: 68, + 0x10D22: 82, + 0x10D23: 68, + 0x10D24: 84, + 0x10D25: 84, + 0x10D26: 84, + 0x10D27: 84, + 0x10D69: 84, + 0x10D6A: 84, + 0x10D6B: 84, + 0x10D6C: 84, + 0x10D6D: 84, + 0x10EAB: 84, + 0x10EAC: 84, + 0x10EC2: 82, + 0x10EC3: 68, + 0x10EC4: 68, + 0x10EC6: 68, + 0x10EC7: 68, + 0x10EFA: 84, + 0x10EFB: 84, + 0x10EFC: 84, + 0x10EFD: 84, + 0x10EFE: 84, + 0x10EFF: 84, + 0x10F30: 68, + 0x10F31: 68, + 0x10F32: 68, + 0x10F33: 82, + 0x10F34: 68, + 0x10F35: 68, + 0x10F36: 68, + 0x10F37: 68, + 0x10F38: 68, + 0x10F39: 68, + 0x10F3A: 68, + 0x10F3B: 68, + 0x10F3C: 68, + 0x10F3D: 68, + 0x10F3E: 68, + 0x10F3F: 68, + 0x10F40: 68, + 0x10F41: 68, + 0x10F42: 68, + 0x10F43: 68, + 0x10F44: 68, + 0x10F46: 84, + 0x10F47: 84, + 0x10F48: 84, + 0x10F49: 84, + 0x10F4A: 84, + 0x10F4B: 84, + 0x10F4C: 84, + 0x10F4D: 84, + 0x10F4E: 84, + 0x10F4F: 84, + 0x10F50: 84, + 0x10F51: 68, + 0x10F52: 68, + 0x10F53: 68, + 0x10F54: 82, + 0x10F70: 68, + 0x10F71: 68, + 0x10F72: 68, + 0x10F73: 68, + 0x10F74: 82, + 0x10F75: 82, + 0x10F76: 68, + 0x10F77: 68, + 0x10F78: 68, + 0x10F79: 68, + 0x10F7A: 68, + 0x10F7B: 68, + 0x10F7C: 68, + 0x10F7D: 68, + 0x10F7E: 68, + 0x10F7F: 68, + 0x10F80: 68, + 0x10F81: 68, + 0x10F82: 84, + 0x10F83: 84, + 0x10F84: 84, + 0x10F85: 84, + 0x10FB0: 68, + 0x10FB2: 68, + 0x10FB3: 68, + 0x10FB4: 82, + 0x10FB5: 82, + 0x10FB6: 82, + 0x10FB8: 68, + 0x10FB9: 82, + 0x10FBA: 82, + 0x10FBB: 68, + 0x10FBC: 68, + 0x10FBD: 82, + 0x10FBE: 68, + 0x10FBF: 68, + 0x10FC1: 68, + 0x10FC2: 82, + 0x10FC3: 82, + 0x10FC4: 68, + 0x10FC9: 82, + 0x10FCA: 68, + 0x10FCB: 76, + 0x11001: 84, + 0x11038: 84, + 0x11039: 84, + 0x1103A: 84, + 0x1103B: 84, + 0x1103C: 84, + 0x1103D: 84, + 0x1103E: 84, + 0x1103F: 84, + 0x11040: 84, + 0x11041: 84, + 0x11042: 84, + 0x11043: 84, + 0x11044: 84, + 0x11045: 84, + 0x11046: 84, + 0x11070: 84, + 0x11073: 84, + 0x11074: 84, + 0x1107F: 84, + 0x11080: 84, + 0x11081: 84, + 0x110B3: 84, + 0x110B4: 84, + 0x110B5: 84, + 0x110B6: 84, + 0x110B9: 84, + 0x110BA: 84, + 0x110C2: 84, + 0x11100: 84, + 0x11101: 84, + 0x11102: 84, + 0x11127: 84, + 0x11128: 84, + 0x11129: 84, + 0x1112A: 84, + 0x1112B: 84, + 0x1112D: 84, + 0x1112E: 84, + 0x1112F: 84, + 0x11130: 84, + 0x11131: 84, + 0x11132: 84, + 0x11133: 84, + 0x11134: 84, + 0x11173: 84, + 0x11180: 84, + 0x11181: 84, + 0x111B6: 84, + 0x111B7: 84, + 0x111B8: 84, + 0x111B9: 84, + 0x111BA: 84, + 0x111BB: 84, + 0x111BC: 84, + 0x111BD: 84, + 0x111BE: 84, + 0x111C9: 84, + 0x111CA: 84, + 0x111CB: 84, + 0x111CC: 84, + 0x111CF: 84, + 0x1122F: 84, + 0x11230: 84, + 0x11231: 84, + 0x11234: 84, + 0x11236: 84, + 0x11237: 84, + 0x1123E: 84, + 0x11241: 84, + 0x112DF: 84, + 0x112E3: 84, + 0x112E4: 84, + 0x112E5: 84, + 0x112E6: 84, + 0x112E7: 84, + 0x112E8: 84, + 0x112E9: 84, + 0x112EA: 84, + 0x11300: 84, + 0x11301: 84, + 0x1133B: 84, + 0x1133C: 84, + 0x11340: 84, + 0x11366: 84, + 0x11367: 84, + 0x11368: 84, + 0x11369: 84, + 0x1136A: 84, + 0x1136B: 84, + 0x1136C: 84, + 0x11370: 84, + 0x11371: 84, + 0x11372: 84, + 0x11373: 84, + 0x11374: 84, + 0x113BB: 84, + 0x113BC: 84, + 0x113BD: 84, + 0x113BE: 84, + 0x113BF: 84, + 0x113C0: 84, + 0x113CE: 84, + 0x113D0: 84, + 0x113D2: 84, + 0x113E1: 84, + 0x113E2: 84, + 0x11438: 84, + 0x11439: 84, + 0x1143A: 84, + 0x1143B: 84, + 0x1143C: 84, + 0x1143D: 84, + 0x1143E: 84, + 0x1143F: 84, + 0x11442: 84, + 0x11443: 84, + 0x11444: 84, + 0x11446: 84, + 0x1145E: 84, + 0x114B3: 84, + 0x114B4: 84, + 0x114B5: 84, + 0x114B6: 84, + 0x114B7: 84, + 0x114B8: 84, + 0x114BA: 84, + 0x114BF: 84, + 0x114C0: 84, + 0x114C2: 84, + 0x114C3: 84, + 0x115B2: 84, + 0x115B3: 84, + 0x115B4: 84, + 0x115B5: 84, + 0x115BC: 84, + 0x115BD: 84, + 0x115BF: 84, + 0x115C0: 84, + 0x115DC: 84, + 0x115DD: 84, + 0x11633: 84, + 0x11634: 84, + 0x11635: 84, + 0x11636: 84, + 0x11637: 84, + 0x11638: 84, + 0x11639: 84, + 0x1163A: 84, + 0x1163D: 84, + 0x1163F: 84, + 0x11640: 84, + 0x116AB: 84, + 0x116AD: 84, + 0x116B0: 84, + 0x116B1: 84, + 0x116B2: 84, + 0x116B3: 84, + 0x116B4: 84, + 0x116B5: 84, + 0x116B7: 84, + 0x1171D: 84, + 0x1171F: 84, + 0x11722: 84, + 0x11723: 84, + 0x11724: 84, + 0x11725: 84, + 0x11727: 84, + 0x11728: 84, + 0x11729: 84, + 0x1172A: 84, + 0x1172B: 84, + 0x1182F: 84, + 0x11830: 84, + 0x11831: 84, + 0x11832: 84, + 0x11833: 84, + 0x11834: 84, + 0x11835: 84, + 0x11836: 84, + 0x11837: 84, + 0x11839: 84, + 0x1183A: 84, + 0x1193B: 84, + 0x1193C: 84, + 0x1193E: 84, + 0x11943: 84, + 0x119D4: 84, + 0x119D5: 84, + 0x119D6: 84, + 0x119D7: 84, + 0x119DA: 84, + 0x119DB: 84, + 0x119E0: 84, + 0x11A01: 84, + 0x11A02: 84, + 0x11A03: 84, + 0x11A04: 84, + 0x11A05: 84, + 0x11A06: 84, + 0x11A07: 84, + 0x11A08: 84, + 0x11A09: 84, + 0x11A0A: 84, + 0x11A33: 84, + 0x11A34: 84, + 0x11A35: 84, + 0x11A36: 84, + 0x11A37: 84, + 0x11A38: 84, + 0x11A3B: 84, + 0x11A3C: 84, + 0x11A3D: 84, + 0x11A3E: 84, + 0x11A47: 84, + 0x11A51: 84, + 0x11A52: 84, + 0x11A53: 84, + 0x11A54: 84, + 0x11A55: 84, + 0x11A56: 84, + 0x11A59: 84, + 0x11A5A: 84, + 0x11A5B: 84, + 0x11A8A: 84, + 0x11A8B: 84, + 0x11A8C: 84, + 0x11A8D: 84, + 0x11A8E: 84, + 0x11A8F: 84, + 0x11A90: 84, + 0x11A91: 84, + 0x11A92: 84, + 0x11A93: 84, + 0x11A94: 84, + 0x11A95: 84, + 0x11A96: 84, + 0x11A98: 84, + 0x11A99: 84, + 0x11B60: 84, + 0x11B62: 84, + 0x11B63: 84, + 0x11B64: 84, + 0x11B66: 84, + 0x11C30: 84, + 0x11C31: 84, + 0x11C32: 84, + 0x11C33: 84, + 0x11C34: 84, + 0x11C35: 84, + 0x11C36: 84, + 0x11C38: 84, + 0x11C39: 84, + 0x11C3A: 84, + 0x11C3B: 84, + 0x11C3C: 84, + 0x11C3D: 84, + 0x11C3F: 84, + 0x11C92: 84, + 0x11C93: 84, + 0x11C94: 84, + 0x11C95: 84, + 0x11C96: 84, + 0x11C97: 84, + 0x11C98: 84, + 0x11C99: 84, + 0x11C9A: 84, + 0x11C9B: 84, + 0x11C9C: 84, + 0x11C9D: 84, + 0x11C9E: 84, + 0x11C9F: 84, + 0x11CA0: 84, + 0x11CA1: 84, + 0x11CA2: 84, + 0x11CA3: 84, + 0x11CA4: 84, + 0x11CA5: 84, + 0x11CA6: 84, + 0x11CA7: 84, + 0x11CAA: 84, + 0x11CAB: 84, + 0x11CAC: 84, + 0x11CAD: 84, + 0x11CAE: 84, + 0x11CAF: 84, + 0x11CB0: 84, + 0x11CB2: 84, + 0x11CB3: 84, + 0x11CB5: 84, + 0x11CB6: 84, + 0x11D31: 84, + 0x11D32: 84, + 0x11D33: 84, + 0x11D34: 84, + 0x11D35: 84, + 0x11D36: 84, + 0x11D3A: 84, + 0x11D3C: 84, + 0x11D3D: 84, + 0x11D3F: 84, + 0x11D40: 84, + 0x11D41: 84, + 0x11D42: 84, + 0x11D43: 84, + 0x11D44: 84, + 0x11D45: 84, + 0x11D47: 84, + 0x11D90: 84, + 0x11D91: 84, + 0x11D95: 84, + 0x11D97: 84, + 0x11EF3: 84, + 0x11EF4: 84, + 0x11F00: 84, + 0x11F01: 84, + 0x11F36: 84, + 0x11F37: 84, + 0x11F38: 84, + 0x11F39: 84, + 0x11F3A: 84, + 0x11F40: 84, + 0x11F42: 84, + 0x11F5A: 84, + 0x13430: 84, + 0x13431: 84, + 0x13432: 84, + 0x13433: 84, + 0x13434: 84, + 0x13435: 84, + 0x13436: 84, + 0x13437: 84, + 0x13438: 84, + 0x13439: 84, + 0x1343A: 84, + 0x1343B: 84, + 0x1343C: 84, + 0x1343D: 84, + 0x1343E: 84, + 0x1343F: 84, + 0x13440: 84, + 0x13447: 84, + 0x13448: 84, + 0x13449: 84, + 0x1344A: 84, + 0x1344B: 84, + 0x1344C: 84, + 0x1344D: 84, + 0x1344E: 84, + 0x1344F: 84, + 0x13450: 84, + 0x13451: 84, + 0x13452: 84, + 0x13453: 84, + 0x13454: 84, + 0x13455: 84, + 0x1611E: 84, + 0x1611F: 84, + 0x16120: 84, + 0x16121: 84, + 0x16122: 84, + 0x16123: 84, + 0x16124: 84, + 0x16125: 84, + 0x16126: 84, + 0x16127: 84, + 0x16128: 84, + 0x16129: 84, + 0x1612D: 84, + 0x1612E: 84, + 0x1612F: 84, + 0x16AF0: 84, + 0x16AF1: 84, + 0x16AF2: 84, + 0x16AF3: 84, + 0x16AF4: 84, + 0x16B30: 84, + 0x16B31: 84, + 0x16B32: 84, + 0x16B33: 84, + 0x16B34: 84, + 0x16B35: 84, + 0x16B36: 84, + 0x16F4F: 84, + 0x16F8F: 84, + 0x16F90: 84, + 0x16F91: 84, + 0x16F92: 84, + 0x16FE4: 84, + 0x1BC9D: 84, + 0x1BC9E: 84, + 0x1BCA0: 84, + 0x1BCA1: 84, + 0x1BCA2: 84, + 0x1BCA3: 84, + 0x1CF00: 84, + 0x1CF01: 84, + 0x1CF02: 84, + 0x1CF03: 84, + 0x1CF04: 84, + 0x1CF05: 84, + 0x1CF06: 84, + 0x1CF07: 84, + 0x1CF08: 84, + 0x1CF09: 84, + 0x1CF0A: 84, + 0x1CF0B: 84, + 0x1CF0C: 84, + 0x1CF0D: 84, + 0x1CF0E: 84, + 0x1CF0F: 84, + 0x1CF10: 84, + 0x1CF11: 84, + 0x1CF12: 84, + 0x1CF13: 84, + 0x1CF14: 84, + 0x1CF15: 84, + 0x1CF16: 84, + 0x1CF17: 84, + 0x1CF18: 84, + 0x1CF19: 84, + 0x1CF1A: 84, + 0x1CF1B: 84, + 0x1CF1C: 84, + 0x1CF1D: 84, + 0x1CF1E: 84, + 0x1CF1F: 84, + 0x1CF20: 84, + 0x1CF21: 84, + 0x1CF22: 84, + 0x1CF23: 84, + 0x1CF24: 84, + 0x1CF25: 84, + 0x1CF26: 84, + 0x1CF27: 84, + 0x1CF28: 84, + 0x1CF29: 84, + 0x1CF2A: 84, + 0x1CF2B: 84, + 0x1CF2C: 84, + 0x1CF2D: 84, + 0x1CF30: 84, + 0x1CF31: 84, + 0x1CF32: 84, + 0x1CF33: 84, + 0x1CF34: 84, + 0x1CF35: 84, + 0x1CF36: 84, + 0x1CF37: 84, + 0x1CF38: 84, + 0x1CF39: 84, + 0x1CF3A: 84, + 0x1CF3B: 84, + 0x1CF3C: 84, + 0x1CF3D: 84, + 0x1CF3E: 84, + 0x1CF3F: 84, + 0x1CF40: 84, + 0x1CF41: 84, + 0x1CF42: 84, + 0x1CF43: 84, + 0x1CF44: 84, + 0x1CF45: 84, + 0x1CF46: 84, + 0x1D167: 84, + 0x1D168: 84, + 0x1D169: 84, + 0x1D173: 84, + 0x1D174: 84, + 0x1D175: 84, + 0x1D176: 84, + 0x1D177: 84, + 0x1D178: 84, + 0x1D179: 84, + 0x1D17A: 84, + 0x1D17B: 84, + 0x1D17C: 84, + 0x1D17D: 84, + 0x1D17E: 84, + 0x1D17F: 84, + 0x1D180: 84, + 0x1D181: 84, + 0x1D182: 84, + 0x1D185: 84, + 0x1D186: 84, + 0x1D187: 84, + 0x1D188: 84, + 0x1D189: 84, + 0x1D18A: 84, + 0x1D18B: 84, + 0x1D1AA: 84, + 0x1D1AB: 84, + 0x1D1AC: 84, + 0x1D1AD: 84, + 0x1D242: 84, + 0x1D243: 84, + 0x1D244: 84, + 0x1DA00: 84, + 0x1DA01: 84, + 0x1DA02: 84, + 0x1DA03: 84, + 0x1DA04: 84, + 0x1DA05: 84, + 0x1DA06: 84, + 0x1DA07: 84, + 0x1DA08: 84, + 0x1DA09: 84, + 0x1DA0A: 84, + 0x1DA0B: 84, + 0x1DA0C: 84, + 0x1DA0D: 84, + 0x1DA0E: 84, + 0x1DA0F: 84, + 0x1DA10: 84, + 0x1DA11: 84, + 0x1DA12: 84, + 0x1DA13: 84, + 0x1DA14: 84, + 0x1DA15: 84, + 0x1DA16: 84, + 0x1DA17: 84, + 0x1DA18: 84, + 0x1DA19: 84, + 0x1DA1A: 84, + 0x1DA1B: 84, + 0x1DA1C: 84, + 0x1DA1D: 84, + 0x1DA1E: 84, + 0x1DA1F: 84, + 0x1DA20: 84, + 0x1DA21: 84, + 0x1DA22: 84, + 0x1DA23: 84, + 0x1DA24: 84, + 0x1DA25: 84, + 0x1DA26: 84, + 0x1DA27: 84, + 0x1DA28: 84, + 0x1DA29: 84, + 0x1DA2A: 84, + 0x1DA2B: 84, + 0x1DA2C: 84, + 0x1DA2D: 84, + 0x1DA2E: 84, + 0x1DA2F: 84, + 0x1DA30: 84, + 0x1DA31: 84, + 0x1DA32: 84, + 0x1DA33: 84, + 0x1DA34: 84, + 0x1DA35: 84, + 0x1DA36: 84, + 0x1DA3B: 84, + 0x1DA3C: 84, + 0x1DA3D: 84, + 0x1DA3E: 84, + 0x1DA3F: 84, + 0x1DA40: 84, + 0x1DA41: 84, + 0x1DA42: 84, + 0x1DA43: 84, + 0x1DA44: 84, + 0x1DA45: 84, + 0x1DA46: 84, + 0x1DA47: 84, + 0x1DA48: 84, + 0x1DA49: 84, + 0x1DA4A: 84, + 0x1DA4B: 84, + 0x1DA4C: 84, + 0x1DA4D: 84, + 0x1DA4E: 84, + 0x1DA4F: 84, + 0x1DA50: 84, + 0x1DA51: 84, + 0x1DA52: 84, + 0x1DA53: 84, + 0x1DA54: 84, + 0x1DA55: 84, + 0x1DA56: 84, + 0x1DA57: 84, + 0x1DA58: 84, + 0x1DA59: 84, + 0x1DA5A: 84, + 0x1DA5B: 84, + 0x1DA5C: 84, + 0x1DA5D: 84, + 0x1DA5E: 84, + 0x1DA5F: 84, + 0x1DA60: 84, + 0x1DA61: 84, + 0x1DA62: 84, + 0x1DA63: 84, + 0x1DA64: 84, + 0x1DA65: 84, + 0x1DA66: 84, + 0x1DA67: 84, + 0x1DA68: 84, + 0x1DA69: 84, + 0x1DA6A: 84, + 0x1DA6B: 84, + 0x1DA6C: 84, + 0x1DA75: 84, + 0x1DA84: 84, + 0x1DA9B: 84, + 0x1DA9C: 84, + 0x1DA9D: 84, + 0x1DA9E: 84, + 0x1DA9F: 84, + 0x1DAA1: 84, + 0x1DAA2: 84, + 0x1DAA3: 84, + 0x1DAA4: 84, + 0x1DAA5: 84, + 0x1DAA6: 84, + 0x1DAA7: 84, + 0x1DAA8: 84, + 0x1DAA9: 84, + 0x1DAAA: 84, + 0x1DAAB: 84, + 0x1DAAC: 84, + 0x1DAAD: 84, + 0x1DAAE: 84, + 0x1DAAF: 84, + 0x1E000: 84, + 0x1E001: 84, + 0x1E002: 84, + 0x1E003: 84, + 0x1E004: 84, + 0x1E005: 84, + 0x1E006: 84, + 0x1E008: 84, + 0x1E009: 84, + 0x1E00A: 84, + 0x1E00B: 84, + 0x1E00C: 84, + 0x1E00D: 84, + 0x1E00E: 84, + 0x1E00F: 84, + 0x1E010: 84, + 0x1E011: 84, + 0x1E012: 84, + 0x1E013: 84, + 0x1E014: 84, + 0x1E015: 84, + 0x1E016: 84, + 0x1E017: 84, + 0x1E018: 84, + 0x1E01B: 84, + 0x1E01C: 84, + 0x1E01D: 84, + 0x1E01E: 84, + 0x1E01F: 84, + 0x1E020: 84, + 0x1E021: 84, + 0x1E023: 84, + 0x1E024: 84, + 0x1E026: 84, + 0x1E027: 84, + 0x1E028: 84, + 0x1E029: 84, + 0x1E02A: 84, + 0x1E08F: 84, + 0x1E130: 84, + 0x1E131: 84, + 0x1E132: 84, + 0x1E133: 84, + 0x1E134: 84, + 0x1E135: 84, + 0x1E136: 84, + 0x1E2AE: 84, + 0x1E2EC: 84, + 0x1E2ED: 84, + 0x1E2EE: 84, + 0x1E2EF: 84, + 0x1E4EC: 84, + 0x1E4ED: 84, + 0x1E4EE: 84, + 0x1E4EF: 84, + 0x1E5EE: 84, + 0x1E5EF: 84, + 0x1E6E3: 84, + 0x1E6E6: 84, + 0x1E6EE: 84, + 0x1E6EF: 84, + 0x1E6F5: 84, + 0x1E8D0: 84, + 0x1E8D1: 84, + 0x1E8D2: 84, + 0x1E8D3: 84, + 0x1E8D4: 84, + 0x1E8D5: 84, + 0x1E8D6: 84, + 0x1E900: 68, + 0x1E901: 68, + 0x1E902: 68, + 0x1E903: 68, + 0x1E904: 68, + 0x1E905: 68, + 0x1E906: 68, + 0x1E907: 68, + 0x1E908: 68, + 0x1E909: 68, + 0x1E90A: 68, + 0x1E90B: 68, + 0x1E90C: 68, + 0x1E90D: 68, + 0x1E90E: 68, + 0x1E90F: 68, + 0x1E910: 68, + 0x1E911: 68, + 0x1E912: 68, + 0x1E913: 68, + 0x1E914: 68, + 0x1E915: 68, + 0x1E916: 68, + 0x1E917: 68, + 0x1E918: 68, + 0x1E919: 68, + 0x1E91A: 68, + 0x1E91B: 68, + 0x1E91C: 68, + 0x1E91D: 68, + 0x1E91E: 68, + 0x1E91F: 68, + 0x1E920: 68, + 0x1E921: 68, + 0x1E922: 68, + 0x1E923: 68, + 0x1E924: 68, + 0x1E925: 68, + 0x1E926: 68, + 0x1E927: 68, + 0x1E928: 68, + 0x1E929: 68, + 0x1E92A: 68, + 0x1E92B: 68, + 0x1E92C: 68, + 0x1E92D: 68, + 0x1E92E: 68, + 0x1E92F: 68, + 0x1E930: 68, + 0x1E931: 68, + 0x1E932: 68, + 0x1E933: 68, + 0x1E934: 68, + 0x1E935: 68, + 0x1E936: 68, + 0x1E937: 68, + 0x1E938: 68, + 0x1E939: 68, + 0x1E93A: 68, + 0x1E93B: 68, + 0x1E93C: 68, + 0x1E93D: 68, + 0x1E93E: 68, + 0x1E93F: 68, + 0x1E940: 68, + 0x1E941: 68, + 0x1E942: 68, + 0x1E943: 68, + 0x1E944: 84, + 0x1E945: 84, + 0x1E946: 84, + 0x1E947: 84, + 0x1E948: 84, + 0x1E949: 84, + 0x1E94A: 84, + 0x1E94B: 84, + 0xE0001: 84, + 0xE0020: 84, + 0xE0021: 84, + 0xE0022: 84, + 0xE0023: 84, + 0xE0024: 84, + 0xE0025: 84, + 0xE0026: 84, + 0xE0027: 84, + 0xE0028: 84, + 0xE0029: 84, + 0xE002A: 84, + 0xE002B: 84, + 0xE002C: 84, + 0xE002D: 84, + 0xE002E: 84, + 0xE002F: 84, + 0xE0030: 84, + 0xE0031: 84, + 0xE0032: 84, + 0xE0033: 84, + 0xE0034: 84, + 0xE0035: 84, + 0xE0036: 84, + 0xE0037: 84, + 0xE0038: 84, + 0xE0039: 84, + 0xE003A: 84, + 0xE003B: 84, + 0xE003C: 84, + 0xE003D: 84, + 0xE003E: 84, + 0xE003F: 84, + 0xE0040: 84, + 0xE0041: 84, + 0xE0042: 84, + 0xE0043: 84, + 0xE0044: 84, + 0xE0045: 84, + 0xE0046: 84, + 0xE0047: 84, + 0xE0048: 84, + 0xE0049: 84, + 0xE004A: 84, + 0xE004B: 84, + 0xE004C: 84, + 0xE004D: 84, + 0xE004E: 84, + 0xE004F: 84, + 0xE0050: 84, + 0xE0051: 84, + 0xE0052: 84, + 0xE0053: 84, + 0xE0054: 84, + 0xE0055: 84, + 0xE0056: 84, + 0xE0057: 84, + 0xE0058: 84, + 0xE0059: 84, + 0xE005A: 84, + 0xE005B: 84, + 0xE005C: 84, + 0xE005D: 84, + 0xE005E: 84, + 0xE005F: 84, + 0xE0060: 84, + 0xE0061: 84, + 0xE0062: 84, + 0xE0063: 84, + 0xE0064: 84, + 0xE0065: 84, + 0xE0066: 84, + 0xE0067: 84, + 0xE0068: 84, + 0xE0069: 84, + 0xE006A: 84, + 0xE006B: 84, + 0xE006C: 84, + 0xE006D: 84, + 0xE006E: 84, + 0xE006F: 84, + 0xE0070: 84, + 0xE0071: 84, + 0xE0072: 84, + 0xE0073: 84, + 0xE0074: 84, + 0xE0075: 84, + 0xE0076: 84, + 0xE0077: 84, + 0xE0078: 84, + 0xE0079: 84, + 0xE007A: 84, + 0xE007B: 84, + 0xE007C: 84, + 0xE007D: 84, + 0xE007E: 84, + 0xE007F: 84, + 0xE0100: 84, + 0xE0101: 84, + 0xE0102: 84, + 0xE0103: 84, + 0xE0104: 84, + 0xE0105: 84, + 0xE0106: 84, + 0xE0107: 84, + 0xE0108: 84, + 0xE0109: 84, + 0xE010A: 84, + 0xE010B: 84, + 0xE010C: 84, + 0xE010D: 84, + 0xE010E: 84, + 0xE010F: 84, + 0xE0110: 84, + 0xE0111: 84, + 0xE0112: 84, + 0xE0113: 84, + 0xE0114: 84, + 0xE0115: 84, + 0xE0116: 84, + 0xE0117: 84, + 0xE0118: 84, + 0xE0119: 84, + 0xE011A: 84, + 0xE011B: 84, + 0xE011C: 84, + 0xE011D: 84, + 0xE011E: 84, + 0xE011F: 84, + 0xE0120: 84, + 0xE0121: 84, + 0xE0122: 84, + 0xE0123: 84, + 0xE0124: 84, + 0xE0125: 84, + 0xE0126: 84, + 0xE0127: 84, + 0xE0128: 84, + 0xE0129: 84, + 0xE012A: 84, + 0xE012B: 84, + 0xE012C: 84, + 0xE012D: 84, + 0xE012E: 84, + 0xE012F: 84, + 0xE0130: 84, + 0xE0131: 84, + 0xE0132: 84, + 0xE0133: 84, + 0xE0134: 84, + 0xE0135: 84, + 0xE0136: 84, + 0xE0137: 84, + 0xE0138: 84, + 0xE0139: 84, + 0xE013A: 84, + 0xE013B: 84, + 0xE013C: 84, + 0xE013D: 84, + 0xE013E: 84, + 0xE013F: 84, + 0xE0140: 84, + 0xE0141: 84, + 0xE0142: 84, + 0xE0143: 84, + 0xE0144: 84, + 0xE0145: 84, + 0xE0146: 84, + 0xE0147: 84, + 0xE0148: 84, + 0xE0149: 84, + 0xE014A: 84, + 0xE014B: 84, + 0xE014C: 84, + 0xE014D: 84, + 0xE014E: 84, + 0xE014F: 84, + 0xE0150: 84, + 0xE0151: 84, + 0xE0152: 84, + 0xE0153: 84, + 0xE0154: 84, + 0xE0155: 84, + 0xE0156: 84, + 0xE0157: 84, + 0xE0158: 84, + 0xE0159: 84, + 0xE015A: 84, + 0xE015B: 84, + 0xE015C: 84, + 0xE015D: 84, + 0xE015E: 84, + 0xE015F: 84, + 0xE0160: 84, + 0xE0161: 84, + 0xE0162: 84, + 0xE0163: 84, + 0xE0164: 84, + 0xE0165: 84, + 0xE0166: 84, + 0xE0167: 84, + 0xE0168: 84, + 0xE0169: 84, + 0xE016A: 84, + 0xE016B: 84, + 0xE016C: 84, + 0xE016D: 84, + 0xE016E: 84, + 0xE016F: 84, + 0xE0170: 84, + 0xE0171: 84, + 0xE0172: 84, + 0xE0173: 84, + 0xE0174: 84, + 0xE0175: 84, + 0xE0176: 84, + 0xE0177: 84, + 0xE0178: 84, + 0xE0179: 84, + 0xE017A: 84, + 0xE017B: 84, + 0xE017C: 84, + 0xE017D: 84, + 0xE017E: 84, + 0xE017F: 84, + 0xE0180: 84, + 0xE0181: 84, + 0xE0182: 84, + 0xE0183: 84, + 0xE0184: 84, + 0xE0185: 84, + 0xE0186: 84, + 0xE0187: 84, + 0xE0188: 84, + 0xE0189: 84, + 0xE018A: 84, + 0xE018B: 84, + 0xE018C: 84, + 0xE018D: 84, + 0xE018E: 84, + 0xE018F: 84, + 0xE0190: 84, + 0xE0191: 84, + 0xE0192: 84, + 0xE0193: 84, + 0xE0194: 84, + 0xE0195: 84, + 0xE0196: 84, + 0xE0197: 84, + 0xE0198: 84, + 0xE0199: 84, + 0xE019A: 84, + 0xE019B: 84, + 0xE019C: 84, + 0xE019D: 84, + 0xE019E: 84, + 0xE019F: 84, + 0xE01A0: 84, + 0xE01A1: 84, + 0xE01A2: 84, + 0xE01A3: 84, + 0xE01A4: 84, + 0xE01A5: 84, + 0xE01A6: 84, + 0xE01A7: 84, + 0xE01A8: 84, + 0xE01A9: 84, + 0xE01AA: 84, + 0xE01AB: 84, + 0xE01AC: 84, + 0xE01AD: 84, + 0xE01AE: 84, + 0xE01AF: 84, + 0xE01B0: 84, + 0xE01B1: 84, + 0xE01B2: 84, + 0xE01B3: 84, + 0xE01B4: 84, + 0xE01B5: 84, + 0xE01B6: 84, + 0xE01B7: 84, + 0xE01B8: 84, + 0xE01B9: 84, + 0xE01BA: 84, + 0xE01BB: 84, + 0xE01BC: 84, + 0xE01BD: 84, + 0xE01BE: 84, + 0xE01BF: 84, + 0xE01C0: 84, + 0xE01C1: 84, + 0xE01C2: 84, + 0xE01C3: 84, + 0xE01C4: 84, + 0xE01C5: 84, + 0xE01C6: 84, + 0xE01C7: 84, + 0xE01C8: 84, + 0xE01C9: 84, + 0xE01CA: 84, + 0xE01CB: 84, + 0xE01CC: 84, + 0xE01CD: 84, + 0xE01CE: 84, + 0xE01CF: 84, + 0xE01D0: 84, + 0xE01D1: 84, + 0xE01D2: 84, + 0xE01D3: 84, + 0xE01D4: 84, + 0xE01D5: 84, + 0xE01D6: 84, + 0xE01D7: 84, + 0xE01D8: 84, + 0xE01D9: 84, + 0xE01DA: 84, + 0xE01DB: 84, + 0xE01DC: 84, + 0xE01DD: 84, + 0xE01DE: 84, + 0xE01DF: 84, + 0xE01E0: 84, + 0xE01E1: 84, + 0xE01E2: 84, + 0xE01E3: 84, + 0xE01E4: 84, + 0xE01E5: 84, + 0xE01E6: 84, + 0xE01E7: 84, + 0xE01E8: 84, + 0xE01E9: 84, + 0xE01EA: 84, + 0xE01EB: 84, + 0xE01EC: 84, + 0xE01ED: 84, + 0xE01EE: 84, + 0xE01EF: 84, + } + + +codepoint_classes = { + "PVALID": ( + 0x2D0000002E, + 0x300000003A, + 0x610000007B, + 0xDF000000F7, + 0xF800000100, + 0x10100000102, + 0x10300000104, + 0x10500000106, + 0x10700000108, + 0x1090000010A, + 0x10B0000010C, + 0x10D0000010E, + 0x10F00000110, + 0x11100000112, + 0x11300000114, + 0x11500000116, + 0x11700000118, + 0x1190000011A, + 0x11B0000011C, + 0x11D0000011E, + 0x11F00000120, + 0x12100000122, + 0x12300000124, + 0x12500000126, + 0x12700000128, + 0x1290000012A, + 0x12B0000012C, + 0x12D0000012E, + 0x12F00000130, + 0x13100000132, + 0x13500000136, + 0x13700000139, + 0x13A0000013B, + 0x13C0000013D, + 0x13E0000013F, + 0x14200000143, + 0x14400000145, + 0x14600000147, + 0x14800000149, + 0x14B0000014C, + 0x14D0000014E, + 0x14F00000150, + 0x15100000152, + 0x15300000154, + 0x15500000156, + 0x15700000158, + 0x1590000015A, + 0x15B0000015C, + 0x15D0000015E, + 0x15F00000160, + 0x16100000162, + 0x16300000164, + 0x16500000166, + 0x16700000168, + 0x1690000016A, + 0x16B0000016C, + 0x16D0000016E, + 0x16F00000170, + 0x17100000172, + 0x17300000174, + 0x17500000176, + 0x17700000178, + 0x17A0000017B, + 0x17C0000017D, + 0x17E0000017F, + 0x18000000181, + 0x18300000184, + 0x18500000186, + 0x18800000189, + 0x18C0000018E, + 0x19200000193, + 0x19500000196, + 0x1990000019C, + 0x19E0000019F, + 0x1A1000001A2, + 0x1A3000001A4, + 0x1A5000001A6, + 0x1A8000001A9, + 0x1AA000001AC, + 0x1AD000001AE, + 0x1B0000001B1, + 0x1B4000001B5, + 0x1B6000001B7, + 0x1B9000001BC, + 0x1BD000001C4, + 0x1CE000001CF, + 0x1D0000001D1, + 0x1D2000001D3, + 0x1D4000001D5, + 0x1D6000001D7, + 0x1D8000001D9, + 0x1DA000001DB, + 0x1DC000001DE, + 0x1DF000001E0, + 0x1E1000001E2, + 0x1E3000001E4, + 0x1E5000001E6, + 0x1E7000001E8, + 0x1E9000001EA, + 0x1EB000001EC, + 0x1ED000001EE, + 0x1EF000001F1, + 0x1F5000001F6, + 0x1F9000001FA, + 0x1FB000001FC, + 0x1FD000001FE, + 0x1FF00000200, + 0x20100000202, + 0x20300000204, + 0x20500000206, + 0x20700000208, + 0x2090000020A, + 0x20B0000020C, + 0x20D0000020E, + 0x20F00000210, + 0x21100000212, + 0x21300000214, + 0x21500000216, + 0x21700000218, + 0x2190000021A, + 0x21B0000021C, + 0x21D0000021E, + 0x21F00000220, + 0x22100000222, + 0x22300000224, + 0x22500000226, + 0x22700000228, + 0x2290000022A, + 0x22B0000022C, + 0x22D0000022E, + 0x22F00000230, + 0x23100000232, + 0x2330000023A, + 0x23C0000023D, + 0x23F00000241, + 0x24200000243, + 0x24700000248, + 0x2490000024A, + 0x24B0000024C, + 0x24D0000024E, + 0x24F000002B0, + 0x2B9000002C2, + 0x2C6000002D2, + 0x2EC000002ED, + 0x2EE000002EF, + 0x30000000340, + 0x34200000343, + 0x3460000034F, + 0x35000000370, + 0x37100000372, + 0x37300000374, + 0x37700000378, + 0x37B0000037E, + 0x39000000391, + 0x3AC000003CF, + 0x3D7000003D8, + 0x3D9000003DA, + 0x3DB000003DC, + 0x3DD000003DE, + 0x3DF000003E0, + 0x3E1000003E2, + 0x3E3000003E4, + 0x3E5000003E6, + 0x3E7000003E8, + 0x3E9000003EA, + 0x3EB000003EC, + 0x3ED000003EE, + 0x3EF000003F0, + 0x3F3000003F4, + 0x3F8000003F9, + 0x3FB000003FD, + 0x43000000460, + 0x46100000462, + 0x46300000464, + 0x46500000466, + 0x46700000468, + 0x4690000046A, + 0x46B0000046C, + 0x46D0000046E, + 0x46F00000470, + 0x47100000472, + 0x47300000474, + 0x47500000476, + 0x47700000478, + 0x4790000047A, + 0x47B0000047C, + 0x47D0000047E, + 0x47F00000480, + 0x48100000482, + 0x48300000488, + 0x48B0000048C, + 0x48D0000048E, + 0x48F00000490, + 0x49100000492, + 0x49300000494, + 0x49500000496, + 0x49700000498, + 0x4990000049A, + 0x49B0000049C, + 0x49D0000049E, + 0x49F000004A0, + 0x4A1000004A2, + 0x4A3000004A4, + 0x4A5000004A6, + 0x4A7000004A8, + 0x4A9000004AA, + 0x4AB000004AC, + 0x4AD000004AE, + 0x4AF000004B0, + 0x4B1000004B2, + 0x4B3000004B4, + 0x4B5000004B6, + 0x4B7000004B8, + 0x4B9000004BA, + 0x4BB000004BC, + 0x4BD000004BE, + 0x4BF000004C0, + 0x4C2000004C3, + 0x4C4000004C5, + 0x4C6000004C7, + 0x4C8000004C9, + 0x4CA000004CB, + 0x4CC000004CD, + 0x4CE000004D0, + 0x4D1000004D2, + 0x4D3000004D4, + 0x4D5000004D6, + 0x4D7000004D8, + 0x4D9000004DA, + 0x4DB000004DC, + 0x4DD000004DE, + 0x4DF000004E0, + 0x4E1000004E2, + 0x4E3000004E4, + 0x4E5000004E6, + 0x4E7000004E8, + 0x4E9000004EA, + 0x4EB000004EC, + 0x4ED000004EE, + 0x4EF000004F0, + 0x4F1000004F2, + 0x4F3000004F4, + 0x4F5000004F6, + 0x4F7000004F8, + 0x4F9000004FA, + 0x4FB000004FC, + 0x4FD000004FE, + 0x4FF00000500, + 0x50100000502, + 0x50300000504, + 0x50500000506, + 0x50700000508, + 0x5090000050A, + 0x50B0000050C, + 0x50D0000050E, + 0x50F00000510, + 0x51100000512, + 0x51300000514, + 0x51500000516, + 0x51700000518, + 0x5190000051A, + 0x51B0000051C, + 0x51D0000051E, + 0x51F00000520, + 0x52100000522, + 0x52300000524, + 0x52500000526, + 0x52700000528, + 0x5290000052A, + 0x52B0000052C, + 0x52D0000052E, + 0x52F00000530, + 0x5590000055A, + 0x56000000587, + 0x58800000589, + 0x591000005BE, + 0x5BF000005C0, + 0x5C1000005C3, + 0x5C4000005C6, + 0x5C7000005C8, + 0x5D0000005EB, + 0x5EF000005F3, + 0x6100000061B, + 0x62000000640, + 0x64100000660, + 0x66E00000675, + 0x679000006D4, + 0x6D5000006DD, + 0x6DF000006E9, + 0x6EA000006F0, + 0x6FA00000700, + 0x7100000074B, + 0x74D000007B2, + 0x7C0000007F6, + 0x7FD000007FE, + 0x8000000082E, + 0x8400000085C, + 0x8600000086B, + 0x87000000888, + 0x88900000890, + 0x897000008E2, + 0x8E300000958, + 0x96000000964, + 0x96600000970, + 0x97100000984, + 0x9850000098D, + 0x98F00000991, + 0x993000009A9, + 0x9AA000009B1, + 0x9B2000009B3, + 0x9B6000009BA, + 0x9BC000009C5, + 0x9C7000009C9, + 0x9CB000009CF, + 0x9D7000009D8, + 0x9E0000009E4, + 0x9E6000009F2, + 0x9FC000009FD, + 0x9FE000009FF, + 0xA0100000A04, + 0xA0500000A0B, + 0xA0F00000A11, + 0xA1300000A29, + 0xA2A00000A31, + 0xA3200000A33, + 0xA3500000A36, + 0xA3800000A3A, + 0xA3C00000A3D, + 0xA3E00000A43, + 0xA4700000A49, + 0xA4B00000A4E, + 0xA5100000A52, + 0xA5C00000A5D, + 0xA6600000A76, + 0xA8100000A84, + 0xA8500000A8E, + 0xA8F00000A92, + 0xA9300000AA9, + 0xAAA00000AB1, + 0xAB200000AB4, + 0xAB500000ABA, + 0xABC00000AC6, + 0xAC700000ACA, + 0xACB00000ACE, + 0xAD000000AD1, + 0xAE000000AE4, + 0xAE600000AF0, + 0xAF900000B00, + 0xB0100000B04, + 0xB0500000B0D, + 0xB0F00000B11, + 0xB1300000B29, + 0xB2A00000B31, + 0xB3200000B34, + 0xB3500000B3A, + 0xB3C00000B45, + 0xB4700000B49, + 0xB4B00000B4E, + 0xB5500000B58, + 0xB5F00000B64, + 0xB6600000B70, + 0xB7100000B72, + 0xB8200000B84, + 0xB8500000B8B, + 0xB8E00000B91, + 0xB9200000B96, + 0xB9900000B9B, + 0xB9C00000B9D, + 0xB9E00000BA0, + 0xBA300000BA5, + 0xBA800000BAB, + 0xBAE00000BBA, + 0xBBE00000BC3, + 0xBC600000BC9, + 0xBCA00000BCE, + 0xBD000000BD1, + 0xBD700000BD8, + 0xBE600000BF0, + 0xC0000000C0D, + 0xC0E00000C11, + 0xC1200000C29, + 0xC2A00000C3A, + 0xC3C00000C45, + 0xC4600000C49, + 0xC4A00000C4E, + 0xC5500000C57, + 0xC5800000C5B, + 0xC5C00000C5E, + 0xC6000000C64, + 0xC6600000C70, + 0xC8000000C84, + 0xC8500000C8D, + 0xC8E00000C91, + 0xC9200000CA9, + 0xCAA00000CB4, + 0xCB500000CBA, + 0xCBC00000CC5, + 0xCC600000CC9, + 0xCCA00000CCE, + 0xCD500000CD7, + 0xCDC00000CDF, + 0xCE000000CE4, + 0xCE600000CF0, + 0xCF100000CF4, + 0xD0000000D0D, + 0xD0E00000D11, + 0xD1200000D45, + 0xD4600000D49, + 0xD4A00000D4F, + 0xD5400000D58, + 0xD5F00000D64, + 0xD6600000D70, + 0xD7A00000D80, + 0xD8100000D84, + 0xD8500000D97, + 0xD9A00000DB2, + 0xDB300000DBC, + 0xDBD00000DBE, + 0xDC000000DC7, + 0xDCA00000DCB, + 0xDCF00000DD5, + 0xDD600000DD7, + 0xDD800000DE0, + 0xDE600000DF0, + 0xDF200000DF4, + 0xE0100000E33, + 0xE3400000E3B, + 0xE4000000E4F, + 0xE5000000E5A, + 0xE8100000E83, + 0xE8400000E85, + 0xE8600000E8B, + 0xE8C00000EA4, + 0xEA500000EA6, + 0xEA700000EB3, + 0xEB400000EBE, + 0xEC000000EC5, + 0xEC600000EC7, + 0xEC800000ECF, + 0xED000000EDA, + 0xEDE00000EE0, + 0xF0000000F01, + 0xF0B00000F0C, + 0xF1800000F1A, + 0xF2000000F2A, + 0xF3500000F36, + 0xF3700000F38, + 0xF3900000F3A, + 0xF3E00000F43, + 0xF4400000F48, + 0xF4900000F4D, + 0xF4E00000F52, + 0xF5300000F57, + 0xF5800000F5C, + 0xF5D00000F69, + 0xF6A00000F6D, + 0xF7100000F73, + 0xF7400000F75, + 0xF7A00000F81, + 0xF8200000F85, + 0xF8600000F93, + 0xF9400000F98, + 0xF9900000F9D, + 0xF9E00000FA2, + 0xFA300000FA7, + 0xFA800000FAC, + 0xFAD00000FB9, + 0xFBA00000FBD, + 0xFC600000FC7, + 0x10000000104A, + 0x10500000109E, + 0x10D0000010FB, + 0x10FD00001100, + 0x120000001249, + 0x124A0000124E, + 0x125000001257, + 0x125800001259, + 0x125A0000125E, + 0x126000001289, + 0x128A0000128E, + 0x1290000012B1, + 0x12B2000012B6, + 0x12B8000012BF, + 0x12C0000012C1, + 0x12C2000012C6, + 0x12C8000012D7, + 0x12D800001311, + 0x131200001316, + 0x13180000135B, + 0x135D00001360, + 0x138000001390, + 0x13A0000013F6, + 0x14010000166D, + 0x166F00001680, + 0x16810000169B, + 0x16A0000016EB, + 0x16F1000016F9, + 0x170000001716, + 0x171F00001735, + 0x174000001754, + 0x17600000176D, + 0x176E00001771, + 0x177200001774, + 0x1780000017B4, + 0x17B6000017D4, + 0x17D7000017D8, + 0x17DC000017DE, + 0x17E0000017EA, + 0x18100000181A, + 0x182000001879, + 0x1880000018AB, + 0x18B0000018F6, + 0x19000000191F, + 0x19200000192C, + 0x19300000193C, + 0x19460000196E, + 0x197000001975, + 0x1980000019AC, + 0x19B0000019CA, + 0x19D0000019DA, + 0x1A0000001A1C, + 0x1A2000001A5F, + 0x1A6000001A7D, + 0x1A7F00001A8A, + 0x1A9000001A9A, + 0x1AA700001AA8, + 0x1AB000001ABE, + 0x1ABF00001ADE, + 0x1AE000001AEC, + 0x1B0000001B4D, + 0x1B5000001B5A, + 0x1B6B00001B74, + 0x1B8000001BF4, + 0x1C0000001C38, + 0x1C4000001C4A, + 0x1C4D00001C7E, + 0x1C8A00001C8B, + 0x1CD000001CD3, + 0x1CD400001CFB, + 0x1D0000001D2C, + 0x1D2F00001D30, + 0x1D3B00001D3C, + 0x1D4E00001D4F, + 0x1D6B00001D78, + 0x1D7900001D9B, + 0x1DC000001E00, + 0x1E0100001E02, + 0x1E0300001E04, + 0x1E0500001E06, + 0x1E0700001E08, + 0x1E0900001E0A, + 0x1E0B00001E0C, + 0x1E0D00001E0E, + 0x1E0F00001E10, + 0x1E1100001E12, + 0x1E1300001E14, + 0x1E1500001E16, + 0x1E1700001E18, + 0x1E1900001E1A, + 0x1E1B00001E1C, + 0x1E1D00001E1E, + 0x1E1F00001E20, + 0x1E2100001E22, + 0x1E2300001E24, + 0x1E2500001E26, + 0x1E2700001E28, + 0x1E2900001E2A, + 0x1E2B00001E2C, + 0x1E2D00001E2E, + 0x1E2F00001E30, + 0x1E3100001E32, + 0x1E3300001E34, + 0x1E3500001E36, + 0x1E3700001E38, + 0x1E3900001E3A, + 0x1E3B00001E3C, + 0x1E3D00001E3E, + 0x1E3F00001E40, + 0x1E4100001E42, + 0x1E4300001E44, + 0x1E4500001E46, + 0x1E4700001E48, + 0x1E4900001E4A, + 0x1E4B00001E4C, + 0x1E4D00001E4E, + 0x1E4F00001E50, + 0x1E5100001E52, + 0x1E5300001E54, + 0x1E5500001E56, + 0x1E5700001E58, + 0x1E5900001E5A, + 0x1E5B00001E5C, + 0x1E5D00001E5E, + 0x1E5F00001E60, + 0x1E6100001E62, + 0x1E6300001E64, + 0x1E6500001E66, + 0x1E6700001E68, + 0x1E6900001E6A, + 0x1E6B00001E6C, + 0x1E6D00001E6E, + 0x1E6F00001E70, + 0x1E7100001E72, + 0x1E7300001E74, + 0x1E7500001E76, + 0x1E7700001E78, + 0x1E7900001E7A, + 0x1E7B00001E7C, + 0x1E7D00001E7E, + 0x1E7F00001E80, + 0x1E8100001E82, + 0x1E8300001E84, + 0x1E8500001E86, + 0x1E8700001E88, + 0x1E8900001E8A, + 0x1E8B00001E8C, + 0x1E8D00001E8E, + 0x1E8F00001E90, + 0x1E9100001E92, + 0x1E9300001E94, + 0x1E9500001E9A, + 0x1E9C00001E9E, + 0x1E9F00001EA0, + 0x1EA100001EA2, + 0x1EA300001EA4, + 0x1EA500001EA6, + 0x1EA700001EA8, + 0x1EA900001EAA, + 0x1EAB00001EAC, + 0x1EAD00001EAE, + 0x1EAF00001EB0, + 0x1EB100001EB2, + 0x1EB300001EB4, + 0x1EB500001EB6, + 0x1EB700001EB8, + 0x1EB900001EBA, + 0x1EBB00001EBC, + 0x1EBD00001EBE, + 0x1EBF00001EC0, + 0x1EC100001EC2, + 0x1EC300001EC4, + 0x1EC500001EC6, + 0x1EC700001EC8, + 0x1EC900001ECA, + 0x1ECB00001ECC, + 0x1ECD00001ECE, + 0x1ECF00001ED0, + 0x1ED100001ED2, + 0x1ED300001ED4, + 0x1ED500001ED6, + 0x1ED700001ED8, + 0x1ED900001EDA, + 0x1EDB00001EDC, + 0x1EDD00001EDE, + 0x1EDF00001EE0, + 0x1EE100001EE2, + 0x1EE300001EE4, + 0x1EE500001EE6, + 0x1EE700001EE8, + 0x1EE900001EEA, + 0x1EEB00001EEC, + 0x1EED00001EEE, + 0x1EEF00001EF0, + 0x1EF100001EF2, + 0x1EF300001EF4, + 0x1EF500001EF6, + 0x1EF700001EF8, + 0x1EF900001EFA, + 0x1EFB00001EFC, + 0x1EFD00001EFE, + 0x1EFF00001F08, + 0x1F1000001F16, + 0x1F2000001F28, + 0x1F3000001F38, + 0x1F4000001F46, + 0x1F5000001F58, + 0x1F6000001F68, + 0x1F7000001F71, + 0x1F7200001F73, + 0x1F7400001F75, + 0x1F7600001F77, + 0x1F7800001F79, + 0x1F7A00001F7B, + 0x1F7C00001F7D, + 0x1FB000001FB2, + 0x1FB600001FB7, + 0x1FC600001FC7, + 0x1FD000001FD3, + 0x1FD600001FD8, + 0x1FE000001FE3, + 0x1FE400001FE8, + 0x1FF600001FF7, + 0x214E0000214F, + 0x218400002185, + 0x2C3000002C60, + 0x2C6100002C62, + 0x2C6500002C67, + 0x2C6800002C69, + 0x2C6A00002C6B, + 0x2C6C00002C6D, + 0x2C7100002C72, + 0x2C7300002C75, + 0x2C7600002C7C, + 0x2C8100002C82, + 0x2C8300002C84, + 0x2C8500002C86, + 0x2C8700002C88, + 0x2C8900002C8A, + 0x2C8B00002C8C, + 0x2C8D00002C8E, + 0x2C8F00002C90, + 0x2C9100002C92, + 0x2C9300002C94, + 0x2C9500002C96, + 0x2C9700002C98, + 0x2C9900002C9A, + 0x2C9B00002C9C, + 0x2C9D00002C9E, + 0x2C9F00002CA0, + 0x2CA100002CA2, + 0x2CA300002CA4, + 0x2CA500002CA6, + 0x2CA700002CA8, + 0x2CA900002CAA, + 0x2CAB00002CAC, + 0x2CAD00002CAE, + 0x2CAF00002CB0, + 0x2CB100002CB2, + 0x2CB300002CB4, + 0x2CB500002CB6, + 0x2CB700002CB8, + 0x2CB900002CBA, + 0x2CBB00002CBC, + 0x2CBD00002CBE, + 0x2CBF00002CC0, + 0x2CC100002CC2, + 0x2CC300002CC4, + 0x2CC500002CC6, + 0x2CC700002CC8, + 0x2CC900002CCA, + 0x2CCB00002CCC, + 0x2CCD00002CCE, + 0x2CCF00002CD0, + 0x2CD100002CD2, + 0x2CD300002CD4, + 0x2CD500002CD6, + 0x2CD700002CD8, + 0x2CD900002CDA, + 0x2CDB00002CDC, + 0x2CDD00002CDE, + 0x2CDF00002CE0, + 0x2CE100002CE2, + 0x2CE300002CE5, + 0x2CEC00002CED, + 0x2CEE00002CF2, + 0x2CF300002CF4, + 0x2D0000002D26, + 0x2D2700002D28, + 0x2D2D00002D2E, + 0x2D3000002D68, + 0x2D7F00002D97, + 0x2DA000002DA7, + 0x2DA800002DAF, + 0x2DB000002DB7, + 0x2DB800002DBF, + 0x2DC000002DC7, + 0x2DC800002DCF, + 0x2DD000002DD7, + 0x2DD800002DDF, + 0x2DE000002E00, + 0x2E2F00002E30, + 0x300500003008, + 0x302A0000302E, + 0x303C0000303D, + 0x304100003097, + 0x30990000309B, + 0x309D0000309F, + 0x30A1000030FB, + 0x30FC000030FF, + 0x310500003130, + 0x31A0000031C0, + 0x31F000003200, + 0x340000004DC0, + 0x4E000000A48D, + 0xA4D00000A4FE, + 0xA5000000A60D, + 0xA6100000A62C, + 0xA6410000A642, + 0xA6430000A644, + 0xA6450000A646, + 0xA6470000A648, + 0xA6490000A64A, + 0xA64B0000A64C, + 0xA64D0000A64E, + 0xA64F0000A650, + 0xA6510000A652, + 0xA6530000A654, + 0xA6550000A656, + 0xA6570000A658, + 0xA6590000A65A, + 0xA65B0000A65C, + 0xA65D0000A65E, + 0xA65F0000A660, + 0xA6610000A662, + 0xA6630000A664, + 0xA6650000A666, + 0xA6670000A668, + 0xA6690000A66A, + 0xA66B0000A66C, + 0xA66D0000A670, + 0xA6740000A67E, + 0xA67F0000A680, + 0xA6810000A682, + 0xA6830000A684, + 0xA6850000A686, + 0xA6870000A688, + 0xA6890000A68A, + 0xA68B0000A68C, + 0xA68D0000A68E, + 0xA68F0000A690, + 0xA6910000A692, + 0xA6930000A694, + 0xA6950000A696, + 0xA6970000A698, + 0xA6990000A69A, + 0xA69B0000A69C, + 0xA69E0000A6E6, + 0xA6F00000A6F2, + 0xA7170000A720, + 0xA7230000A724, + 0xA7250000A726, + 0xA7270000A728, + 0xA7290000A72A, + 0xA72B0000A72C, + 0xA72D0000A72E, + 0xA72F0000A732, + 0xA7330000A734, + 0xA7350000A736, + 0xA7370000A738, + 0xA7390000A73A, + 0xA73B0000A73C, + 0xA73D0000A73E, + 0xA73F0000A740, + 0xA7410000A742, + 0xA7430000A744, + 0xA7450000A746, + 0xA7470000A748, + 0xA7490000A74A, + 0xA74B0000A74C, + 0xA74D0000A74E, + 0xA74F0000A750, + 0xA7510000A752, + 0xA7530000A754, + 0xA7550000A756, + 0xA7570000A758, + 0xA7590000A75A, + 0xA75B0000A75C, + 0xA75D0000A75E, + 0xA75F0000A760, + 0xA7610000A762, + 0xA7630000A764, + 0xA7650000A766, + 0xA7670000A768, + 0xA7690000A76A, + 0xA76B0000A76C, + 0xA76D0000A76E, + 0xA76F0000A770, + 0xA7710000A779, + 0xA77A0000A77B, + 0xA77C0000A77D, + 0xA77F0000A780, + 0xA7810000A782, + 0xA7830000A784, + 0xA7850000A786, + 0xA7870000A789, + 0xA78C0000A78D, + 0xA78E0000A790, + 0xA7910000A792, + 0xA7930000A796, + 0xA7970000A798, + 0xA7990000A79A, + 0xA79B0000A79C, + 0xA79D0000A79E, + 0xA79F0000A7A0, + 0xA7A10000A7A2, + 0xA7A30000A7A4, + 0xA7A50000A7A6, + 0xA7A70000A7A8, + 0xA7A90000A7AA, + 0xA7AF0000A7B0, + 0xA7B50000A7B6, + 0xA7B70000A7B8, + 0xA7B90000A7BA, + 0xA7BB0000A7BC, + 0xA7BD0000A7BE, + 0xA7BF0000A7C0, + 0xA7C10000A7C2, + 0xA7C30000A7C4, + 0xA7C80000A7C9, + 0xA7CA0000A7CB, + 0xA7CD0000A7CE, + 0xA7CF0000A7D0, + 0xA7D10000A7D2, + 0xA7D30000A7D4, + 0xA7D50000A7D6, + 0xA7D70000A7D8, + 0xA7D90000A7DA, + 0xA7DB0000A7DC, + 0xA7F60000A7F8, + 0xA7FA0000A828, + 0xA82C0000A82D, + 0xA8400000A874, + 0xA8800000A8C6, + 0xA8D00000A8DA, + 0xA8E00000A8F8, + 0xA8FB0000A8FC, + 0xA8FD0000A92E, + 0xA9300000A954, + 0xA9800000A9C1, + 0xA9CF0000A9DA, + 0xA9E00000A9FF, + 0xAA000000AA37, + 0xAA400000AA4E, + 0xAA500000AA5A, + 0xAA600000AA77, + 0xAA7A0000AAC3, + 0xAADB0000AADE, + 0xAAE00000AAF0, + 0xAAF20000AAF7, + 0xAB010000AB07, + 0xAB090000AB0F, + 0xAB110000AB17, + 0xAB200000AB27, + 0xAB280000AB2F, + 0xAB300000AB5B, + 0xAB600000AB69, + 0xABC00000ABEB, + 0xABEC0000ABEE, + 0xABF00000ABFA, + 0xAC000000D7A4, + 0xFA0E0000FA10, + 0xFA110000FA12, + 0xFA130000FA15, + 0xFA1F0000FA20, + 0xFA210000FA22, + 0xFA230000FA25, + 0xFA270000FA2A, + 0xFB1E0000FB1F, + 0xFE200000FE30, + 0xFE730000FE74, + 0x100000001000C, + 0x1000D00010027, + 0x100280001003B, + 0x1003C0001003E, + 0x1003F0001004E, + 0x100500001005E, + 0x10080000100FB, + 0x101FD000101FE, + 0x102800001029D, + 0x102A0000102D1, + 0x102E0000102E1, + 0x1030000010320, + 0x1032D00010341, + 0x103420001034A, + 0x103500001037B, + 0x103800001039E, + 0x103A0000103C4, + 0x103C8000103D0, + 0x104280001049E, + 0x104A0000104AA, + 0x104D8000104FC, + 0x1050000010528, + 0x1053000010564, + 0x10597000105A2, + 0x105A3000105B2, + 0x105B3000105BA, + 0x105BB000105BD, + 0x105C0000105F4, + 0x1060000010737, + 0x1074000010756, + 0x1076000010768, + 0x1078000010781, + 0x1080000010806, + 0x1080800010809, + 0x1080A00010836, + 0x1083700010839, + 0x1083C0001083D, + 0x1083F00010856, + 0x1086000010877, + 0x108800001089F, + 0x108E0000108F3, + 0x108F4000108F6, + 0x1090000010916, + 0x109200001093A, + 0x109400001095A, + 0x10980000109B8, + 0x109BE000109C0, + 0x10A0000010A04, + 0x10A0500010A07, + 0x10A0C00010A14, + 0x10A1500010A18, + 0x10A1900010A36, + 0x10A3800010A3B, + 0x10A3F00010A40, + 0x10A6000010A7D, + 0x10A8000010A9D, + 0x10AC000010AC8, + 0x10AC900010AE7, + 0x10B0000010B36, + 0x10B4000010B56, + 0x10B6000010B73, + 0x10B8000010B92, + 0x10C0000010C49, + 0x10CC000010CF3, + 0x10D0000010D28, + 0x10D3000010D3A, + 0x10D4000010D50, + 0x10D6900010D6E, + 0x10D6F00010D86, + 0x10E8000010EAA, + 0x10EAB00010EAD, + 0x10EB000010EB2, + 0x10EC200010EC8, + 0x10EFA00010F1D, + 0x10F2700010F28, + 0x10F3000010F51, + 0x10F7000010F86, + 0x10FB000010FC5, + 0x10FE000010FF7, + 0x1100000011047, + 0x1106600011076, + 0x1107F000110BB, + 0x110C2000110C3, + 0x110D0000110E9, + 0x110F0000110FA, + 0x1110000011135, + 0x1113600011140, + 0x1114400011148, + 0x1115000011174, + 0x1117600011177, + 0x11180000111C5, + 0x111C9000111CD, + 0x111CE000111DB, + 0x111DC000111DD, + 0x1120000011212, + 0x1121300011238, + 0x1123E00011242, + 0x1128000011287, + 0x1128800011289, + 0x1128A0001128E, + 0x1128F0001129E, + 0x1129F000112A9, + 0x112B0000112EB, + 0x112F0000112FA, + 0x1130000011304, + 0x113050001130D, + 0x1130F00011311, + 0x1131300011329, + 0x1132A00011331, + 0x1133200011334, + 0x113350001133A, + 0x1133B00011345, + 0x1134700011349, + 0x1134B0001134E, + 0x1135000011351, + 0x1135700011358, + 0x1135D00011364, + 0x113660001136D, + 0x1137000011375, + 0x113800001138A, + 0x1138B0001138C, + 0x1138E0001138F, + 0x11390000113B6, + 0x113B7000113C1, + 0x113C2000113C3, + 0x113C5000113C6, + 0x113C7000113CB, + 0x113CC000113D4, + 0x113E1000113E3, + 0x114000001144B, + 0x114500001145A, + 0x1145E00011462, + 0x11480000114C6, + 0x114C7000114C8, + 0x114D0000114DA, + 0x11580000115B6, + 0x115B8000115C1, + 0x115D8000115DE, + 0x1160000011641, + 0x1164400011645, + 0x116500001165A, + 0x11680000116B9, + 0x116C0000116CA, + 0x116D0000116E4, + 0x117000001171B, + 0x1171D0001172C, + 0x117300001173A, + 0x1174000011747, + 0x118000001183B, + 0x118C0000118EA, + 0x118FF00011907, + 0x119090001190A, + 0x1190C00011914, + 0x1191500011917, + 0x1191800011936, + 0x1193700011939, + 0x1193B00011944, + 0x119500001195A, + 0x119A0000119A8, + 0x119AA000119D8, + 0x119DA000119E2, + 0x119E3000119E5, + 0x11A0000011A3F, + 0x11A4700011A48, + 0x11A5000011A9A, + 0x11A9D00011A9E, + 0x11AB000011AF9, + 0x11B6000011B68, + 0x11BC000011BE1, + 0x11BF000011BFA, + 0x11C0000011C09, + 0x11C0A00011C37, + 0x11C3800011C41, + 0x11C5000011C5A, + 0x11C7200011C90, + 0x11C9200011CA8, + 0x11CA900011CB7, + 0x11D0000011D07, + 0x11D0800011D0A, + 0x11D0B00011D37, + 0x11D3A00011D3B, + 0x11D3C00011D3E, + 0x11D3F00011D48, + 0x11D5000011D5A, + 0x11D6000011D66, + 0x11D6700011D69, + 0x11D6A00011D8F, + 0x11D9000011D92, + 0x11D9300011D99, + 0x11DA000011DAA, + 0x11DB000011DDC, + 0x11DE000011DEA, + 0x11EE000011EF7, + 0x11F0000011F11, + 0x11F1200011F3B, + 0x11F3E00011F43, + 0x11F5000011F5B, + 0x11FB000011FB1, + 0x120000001239A, + 0x1248000012544, + 0x12F9000012FF1, + 0x1300000013430, + 0x1344000013456, + 0x13460000143FB, + 0x1440000014647, + 0x161000001613A, + 0x1680000016A39, + 0x16A4000016A5F, + 0x16A6000016A6A, + 0x16A7000016ABF, + 0x16AC000016ACA, + 0x16AD000016AEE, + 0x16AF000016AF5, + 0x16B0000016B37, + 0x16B4000016B44, + 0x16B5000016B5A, + 0x16B6300016B78, + 0x16B7D00016B90, + 0x16D4000016D6D, + 0x16D7000016D7A, + 0x16E6000016E80, + 0x16EBB00016ED4, + 0x16F0000016F4B, + 0x16F4F00016F88, + 0x16F8F00016FA0, + 0x16FE000016FE2, + 0x16FE300016FE5, + 0x16FF000016FF4, + 0x1700000018CD6, + 0x18CFF00018D1F, + 0x18D8000018DF3, + 0x1AFF00001AFF4, + 0x1AFF50001AFFC, + 0x1AFFD0001AFFF, + 0x1B0000001B123, + 0x1B1320001B133, + 0x1B1500001B153, + 0x1B1550001B156, + 0x1B1640001B168, + 0x1B1700001B2FC, + 0x1BC000001BC6B, + 0x1BC700001BC7D, + 0x1BC800001BC89, + 0x1BC900001BC9A, + 0x1BC9D0001BC9F, + 0x1CF000001CF2E, + 0x1CF300001CF47, + 0x1DA000001DA37, + 0x1DA3B0001DA6D, + 0x1DA750001DA76, + 0x1DA840001DA85, + 0x1DA9B0001DAA0, + 0x1DAA10001DAB0, + 0x1DF000001DF1F, + 0x1DF250001DF2B, + 0x1E0000001E007, + 0x1E0080001E019, + 0x1E01B0001E022, + 0x1E0230001E025, + 0x1E0260001E02B, + 0x1E08F0001E090, + 0x1E1000001E12D, + 0x1E1300001E13E, + 0x1E1400001E14A, + 0x1E14E0001E14F, + 0x1E2900001E2AF, + 0x1E2C00001E2FA, + 0x1E4D00001E4FA, + 0x1E5D00001E5FB, + 0x1E6C00001E6DF, + 0x1E6E00001E6F6, + 0x1E6FE0001E700, + 0x1E7E00001E7E7, + 0x1E7E80001E7EC, + 0x1E7ED0001E7EF, + 0x1E7F00001E7FF, + 0x1E8000001E8C5, + 0x1E8D00001E8D7, + 0x1E9220001E94C, + 0x1E9500001E95A, + 0x200000002A6E0, + 0x2A7000002B81E, + 0x2B8200002CEAE, + 0x2CEB00002EBE1, + 0x2EBF00002EE5E, + 0x300000003134B, + 0x313500003347A, + ), + "CONTEXTJ": (0x200C0000200E,), + "CONTEXTO": ( + 0xB7000000B8, + 0x37500000376, + 0x5F3000005F5, + 0x6600000066A, + 0x6F0000006FA, + 0x30FB000030FC, + ), +} diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/idna/intranges.py b/bioagent-bench/.venv/lib/python3.14/site-packages/idna/intranges.py new file mode 100644 index 0000000000000000000000000000000000000000..7bfaa8d80d7dc471d572db0f949460901126e8bd --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/idna/intranges.py @@ -0,0 +1,57 @@ +""" +Given a list of integers, made up of (hopefully) a small number of long runs +of consecutive integers, compute a representation of the form +((start1, end1), (start2, end2) ...). Then answer the question "was x present +in the original list?" in time O(log(# runs)). +""" + +import bisect +from typing import List, Tuple + + +def intranges_from_list(list_: List[int]) -> Tuple[int, ...]: + """Represent a list of integers as a sequence of ranges: + ((start_0, end_0), (start_1, end_1), ...), such that the original + integers are exactly those x such that start_i <= x < end_i for some i. + + Ranges are encoded as single integers (start << 32 | end), not as tuples. + """ + + sorted_list = sorted(list_) + ranges = [] + last_write = -1 + for i in range(len(sorted_list)): + if i + 1 < len(sorted_list): + if sorted_list[i] == sorted_list[i + 1] - 1: + continue + current_range = sorted_list[last_write + 1 : i + 1] + ranges.append(_encode_range(current_range[0], current_range[-1] + 1)) + last_write = i + + return tuple(ranges) + + +def _encode_range(start: int, end: int) -> int: + return (start << 32) | end + + +def _decode_range(r: int) -> Tuple[int, int]: + return (r >> 32), (r & ((1 << 32) - 1)) + + +def intranges_contain(int_: int, ranges: Tuple[int, ...]) -> bool: + """Determine if `int_` falls into one of the ranges in `ranges`.""" + tuple_ = _encode_range(int_, 0) + pos = bisect.bisect_left(ranges, tuple_) + # we could be immediately ahead of a tuple (start, end) + # with start < int_ <= end + if pos > 0: + left, right = _decode_range(ranges[pos - 1]) + if left <= int_ < right: + return True + # or we could be immediately behind a tuple (int_, end) + if pos < len(ranges): + left, _ = _decode_range(ranges[pos]) + if left == int_: + return True + return False diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/idna/package_data.py b/bioagent-bench/.venv/lib/python3.14/site-packages/idna/package_data.py new file mode 100644 index 0000000000000000000000000000000000000000..2bd6cdb8838c072fb2a60932979d1ba280722a81 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/idna/package_data.py @@ -0,0 +1 @@ +__version__ = "3.14" diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/idna/py.typed b/bioagent-bench/.venv/lib/python3.14/site-packages/idna/py.typed new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..58dfa3bcc4674e522d1155f602ef063041cd4d42 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/__init__.py @@ -0,0 +1,6 @@ +"""A Python port of Markdown-It""" + +__all__ = ("MarkdownIt",) +__version__ = "4.2.0" + +from .main import MarkdownIt diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/_compat.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/_compat.py new file mode 100644 index 0000000000000000000000000000000000000000..9d48db4f9f85e1752cf424c49ee18a6907c3f160 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/_compat.py @@ -0,0 +1 @@ +from __future__ import annotations diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/_punycode.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/_punycode.py new file mode 100644 index 0000000000000000000000000000000000000000..312048bf79c33be8fe58f87453845b2b39614efd --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/_punycode.py @@ -0,0 +1,67 @@ +# Copyright 2014 Mathias Bynens +# Copyright 2021 Taneli Hukkinen +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +import codecs +from collections.abc import Callable +import re + +REGEX_SEPARATORS = re.compile(r"[\x2E\u3002\uFF0E\uFF61]") +REGEX_NON_ASCII = re.compile(r"[^\0-\x7E]") + + +def encode(uni: str) -> str: + return codecs.encode(uni, encoding="punycode").decode() + + +def decode(ascii: str) -> str: + return codecs.decode(ascii, encoding="punycode") # type: ignore + + +def map_domain(string: str, fn: Callable[[str], str]) -> str: + parts = string.split("@") + result = "" + if len(parts) > 1: + # In email addresses, only the domain name should be punycoded. Leave + # the local part (i.e. everything up to `@`) intact. + result = parts[0] + "@" + string = parts[1] + labels = REGEX_SEPARATORS.split(string) + encoded = ".".join(fn(label) for label in labels) + return result + encoded + + +def to_unicode(obj: str) -> str: + def mapping(obj: str) -> str: + if obj.startswith("xn--"): + return decode(obj[4:].lower()) + return obj + + return map_domain(obj, mapping) + + +def to_ascii(obj: str) -> str: + def mapping(obj: str) -> str: + if REGEX_NON_ASCII.search(obj): + return "xn--" + encode(obj) + return obj + + return map_domain(obj, mapping) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/cli/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/cli/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/cli/parse.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/cli/parse.py new file mode 100644 index 0000000000000000000000000000000000000000..5de738b2da7e528e974e9c6be6de44ac74b3d66f --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/cli/parse.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python +""" +CLI interface to markdown-it-py + +Parse one or more markdown files, convert each to HTML, and print to stdout. +""" + +from __future__ import annotations + +import argparse +from collections.abc import Iterable, Sequence +import sys + +from markdown_it import __version__ +from markdown_it.main import MarkdownIt + +version_str = f"markdown-it-py [version {__version__}]" + + +def main(args: Sequence[str] | None = None) -> int: + namespace = parse_args(args) + if namespace.filenames: + convert(namespace.filenames) + elif namespace.stdin: + convert_stdin() + else: + interactive() + return 0 + + +def convert(filenames: Iterable[str]) -> None: + for filename in filenames: + convert_file(filename) + + +def convert_stdin() -> None: + """ + Parse a Markdown file and dump the output to stdout. + """ + try: + rendered = MarkdownIt().render(sys.stdin.read()) + print(rendered, end="") + except OSError: + sys.stderr.write("Cannot parse Markdown from the standard input.\n") + sys.exit(1) + + +def convert_file(filename: str) -> None: + """ + Parse a Markdown file and dump the output to stdout. + """ + try: + with open(filename, encoding="utf8", errors="ignore") as fin: + rendered = MarkdownIt().render(fin.read()) + print(rendered, end="") + except OSError: + sys.stderr.write(f'Cannot open file "{filename}".\n') + sys.exit(1) + + +def interactive() -> None: + """ + Parse user input, dump to stdout, rinse and repeat. + Python REPL style. + """ + print_heading() + contents = [] + more = False + while True: + try: + prompt, more = ("... ", True) if more else (">>> ", True) + contents.append(input(prompt) + "\n") + except EOFError: + print("\n" + MarkdownIt().render("\n".join(contents)), end="") + more = False + contents = [] + except KeyboardInterrupt: + print("\nExiting.") + break + + +def parse_args(args: Sequence[str] | None) -> argparse.Namespace: + """Parse input CLI arguments.""" + parser = argparse.ArgumentParser( + description="Parse one or more markdown files, " + "convert each to HTML, and print to stdout", + # NOTE: Remember to update README.md w/ the output of `markdown-it -h` + epilog=( + f""" +Interactive: + + $ markdown-it + markdown-it-py [version {__version__}] (interactive) + Type Ctrl-D to complete input, or Ctrl-C to exit. + >>> # Example + ... > markdown *input* + ... +

    Example

    +
    +

    markdown input

    +
    + +Batch: + + $ markdown-it README.md README.footer.md > index.html +""" + ), + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + parser.add_argument("-v", "--version", action="version", version=version_str) + parser.add_argument( + "--stdin", action="store_true", help="read Markdown from standard input" + ) + parser.add_argument( + "filenames", nargs="*", help="specify an optional list of files to convert" + ) + return parser.parse_args(args) + + +def print_heading() -> None: + print(f"{version_str} (interactive)") + print("Type Ctrl-D to complete input, or Ctrl-C to exit.") + + +if __name__ == "__main__": + exit_code = main(sys.argv[1:]) + sys.exit(exit_code) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/common/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/common/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/common/entities.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/common/entities.py new file mode 100644 index 0000000000000000000000000000000000000000..14d08ec9546995f8eea3e9b83ea896f29ef020cb --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/common/entities.py @@ -0,0 +1,5 @@ +"""HTML5 entities map: { name -> characters }.""" + +import html.entities + +entities = {name.rstrip(";"): chars for name, chars in html.entities.html5.items()} diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/common/html_blocks.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/common/html_blocks.py new file mode 100644 index 0000000000000000000000000000000000000000..8a3b0b7d5ab7bc174a3c96cbc8976816278a6c21 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/common/html_blocks.py @@ -0,0 +1,69 @@ +"""List of valid html blocks names, according to commonmark spec +http://jgm.github.io/CommonMark/spec.html#html-blocks +""" + +# see https://spec.commonmark.org/0.31.2/#html-blocks +block_names = [ + "address", + "article", + "aside", + "base", + "basefont", + "blockquote", + "body", + "caption", + "center", + "col", + "colgroup", + "dd", + "details", + "dialog", + "dir", + "div", + "dl", + "dt", + "fieldset", + "figcaption", + "figure", + "footer", + "form", + "frame", + "frameset", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "head", + "header", + "hr", + "html", + "iframe", + "legend", + "li", + "link", + "main", + "menu", + "menuitem", + "nav", + "noframes", + "ol", + "optgroup", + "option", + "p", + "param", + "search", + "section", + "summary", + "table", + "tbody", + "td", + "tfoot", + "th", + "thead", + "title", + "tr", + "track", + "ul", +] diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/common/html_re.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/common/html_re.py new file mode 100644 index 0000000000000000000000000000000000000000..ab822c5fc487c5a494966604a1e89b57a06e0564 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/common/html_re.py @@ -0,0 +1,39 @@ +"""Regexps to match html elements""" + +import re + +attr_name = "[a-zA-Z_:][a-zA-Z0-9:._-]*" + +unquoted = "[^\"'=<>`\\x00-\\x20]+" +single_quoted = "'[^']*'" +double_quoted = '"[^"]*"' + +attr_value = "(?:" + unquoted + "|" + single_quoted + "|" + double_quoted + ")" + +attribute = "(?:\\s+" + attr_name + "(?:\\s*=\\s*" + attr_value + ")?)" + +open_tag = "<[A-Za-z][A-Za-z0-9\\-]*" + attribute + "*\\s*\\/?>" + +close_tag = "<\\/[A-Za-z][A-Za-z0-9\\-]*\\s*>" +comment = "" +processing = "<[?][\\s\\S]*?[?]>" +declaration = "]*>" +cdata = "" + +HTML_TAG_RE = re.compile( + "^(?:" + + open_tag + + "|" + + close_tag + + "|" + + comment + + "|" + + processing + + "|" + + declaration + + "|" + + cdata + + ")" +) +HTML_OPEN_CLOSE_TAG_STR = "^(?:" + open_tag + "|" + close_tag + ")" +HTML_OPEN_CLOSE_TAG_RE = re.compile(HTML_OPEN_CLOSE_TAG_STR) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/common/normalize_url.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/common/normalize_url.py new file mode 100644 index 0000000000000000000000000000000000000000..92720b31621b0f6b4ac853179d886cb58e4e2f36 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/common/normalize_url.py @@ -0,0 +1,81 @@ +from __future__ import annotations + +from collections.abc import Callable +from contextlib import suppress +import re +from urllib.parse import quote, unquote, urlparse, urlunparse # noqa: F401 + +import mdurl + +from .. import _punycode + +RECODE_HOSTNAME_FOR = ("http:", "https:", "mailto:") + + +def normalizeLink(url: str) -> str: + """Normalize destination URLs in links + + :: + + [label]: destination 'title' + ^^^^^^^^^^^ + """ + parsed = mdurl.parse(url, slashes_denote_host=True) + + # Encode hostnames in urls like: + # `http://host/`, `https://host/`, `mailto:user@host`, `//host/` + # + # We don't encode unknown schemas, because it's likely that we encode + # something we shouldn't (e.g. `skype:name` treated as `skype:host`) + # + if parsed.hostname and ( + not parsed.protocol or parsed.protocol in RECODE_HOSTNAME_FOR + ): + with suppress(Exception): + parsed = parsed._replace(hostname=_punycode.to_ascii(parsed.hostname)) + + return mdurl.encode(mdurl.format(parsed)) + + +def normalizeLinkText(url: str) -> str: + """Normalize autolink content + + :: + + + ~~~~~~~~~~~ + """ + parsed = mdurl.parse(url, slashes_denote_host=True) + + # Encode hostnames in urls like: + # `http://host/`, `https://host/`, `mailto:user@host`, `//host/` + # + # We don't encode unknown schemas, because it's likely that we encode + # something we shouldn't (e.g. `skype:name` treated as `skype:host`) + # + if parsed.hostname and ( + not parsed.protocol or parsed.protocol in RECODE_HOSTNAME_FOR + ): + with suppress(Exception): + parsed = parsed._replace(hostname=_punycode.to_unicode(parsed.hostname)) + + # add '%' to exclude list because of https://github.com/markdown-it/markdown-it/issues/720 + return mdurl.decode(mdurl.format(parsed), mdurl.DECODE_DEFAULT_CHARS + "%") + + +BAD_PROTO_RE = re.compile(r"^(vbscript|javascript|file|data):") +GOOD_DATA_RE = re.compile(r"^data:image\/(gif|png|jpeg|webp);") + + +def validateLink(url: str, validator: Callable[[str], bool] | None = None) -> bool: + """Validate URL link is allowed in output. + + This validator can prohibit more than really needed to prevent XSS. + It's a tradeoff to keep code simple and to be secure by default. + + Note: url should be normalized at this point, and existing entities decoded. + """ + if validator is not None: + return validator(url) + url = url.strip().lower() + return bool(GOOD_DATA_RE.search(url)) if BAD_PROTO_RE.search(url) else True diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/common/utils.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/common/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..11bda644c260b714cf010ed44d2ed345de80314e --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/common/utils.py @@ -0,0 +1,313 @@ +"""Utilities for parsing source text""" + +from __future__ import annotations + +import re +from re import Match +from typing import TypeVar +import unicodedata + +from .entities import entities + + +def charCodeAt(src: str, pos: int) -> int | None: + """ + Returns the Unicode value of the character at the specified location. + + @param - index The zero-based index of the desired character. + If there is no character at the specified index, NaN is returned. + + This was added for compatibility with python + """ + try: + return ord(src[pos]) + except IndexError: + return None + + +def charStrAt(src: str, pos: int) -> str | None: + """ + Returns the Unicode value of the character at the specified location. + + @param - index The zero-based index of the desired character. + If there is no character at the specified index, NaN is returned. + + This was added for compatibility with python + """ + try: + return src[pos] + except IndexError: + return None + + +_ItemTV = TypeVar("_ItemTV") + + +def arrayReplaceAt( + src: list[_ItemTV], pos: int, newElements: list[_ItemTV] +) -> list[_ItemTV]: + """ + Remove element from array and put another array at those position. + Useful for some operations with tokens + """ + return src[:pos] + newElements + src[pos + 1 :] + + +def isValidEntityCode(c: int) -> bool: + # broken sequence + if c >= 0xD800 and c <= 0xDFFF: + return False + # never used + if c >= 0xFDD0 and c <= 0xFDEF: + return False + if ((c & 0xFFFF) == 0xFFFF) or ((c & 0xFFFF) == 0xFFFE): + return False + # control codes + if c >= 0x00 and c <= 0x08: + return False + if c == 0x0B: + return False + if c >= 0x0E and c <= 0x1F: + return False + if c >= 0x7F and c <= 0x9F: + return False + # out of range + return not (c > 0x10FFFF) + + +def fromCodePoint(c: int) -> str: + """Convert ordinal to unicode. + + Note, in the original Javascript two string characters were required, + for codepoints larger than `0xFFFF`. + But Python 3 can represent any unicode codepoint in one character. + """ + return chr(c) + + +# UNESCAPE_MD_RE = re.compile(r'\\([!"#$%&\'()*+,\-.\/:;<=>?@[\\\]^_`{|}~])') +# ENTITY_RE_g = re.compile(r'&([a-z#][a-z0-9]{1,31})', re.IGNORECASE) +UNESCAPE_ALL_RE = re.compile( + r'\\([!"#$%&\'()*+,\-.\/:;<=>?@[\\\]^_`{|}~])' + "|" + r"&([a-z#][a-z0-9]{1,31});", + re.IGNORECASE, +) +DIGITAL_ENTITY_BASE10_RE = re.compile(r"#([0-9]{1,8})") +DIGITAL_ENTITY_BASE16_RE = re.compile(r"#x([a-f0-9]{1,8})", re.IGNORECASE) + + +def replaceEntityPattern(match: str, name: str) -> str: + """Convert HTML entity patterns, + see https://spec.commonmark.org/0.30/#entity-references + """ + if name in entities: + return entities[name] + + code: None | int = None + if pat := DIGITAL_ENTITY_BASE10_RE.fullmatch(name): + code = int(pat.group(1), 10) + elif pat := DIGITAL_ENTITY_BASE16_RE.fullmatch(name): + code = int(pat.group(1), 16) + + if code is not None and isValidEntityCode(code): + return fromCodePoint(code) + + return match + + +def unescapeAll(string: str) -> str: + def replacer_func(match: Match[str]) -> str: + escaped = match.group(1) + if escaped: + return escaped + entity = match.group(2) + return replaceEntityPattern(match.group(), entity) + + if "\\" not in string and "&" not in string: + return string + return UNESCAPE_ALL_RE.sub(replacer_func, string) + + +ESCAPABLE = r"""\\!"#$%&'()*+,./:;<=>?@\[\]^`{}|_~-""" +ESCAPE_CHAR = re.compile(r"\\([" + ESCAPABLE + r"])") + + +def stripEscape(string: str) -> str: + """Strip escape \\ characters""" + return ESCAPE_CHAR.sub(r"\1", string) + + +def escapeHtml(raw: str) -> str: + """Replace special characters "&", "<", ">" and '"' to HTML-safe sequences.""" + # like html.escape, but without escaping single quotes + raw = raw.replace("&", "&") # Must be done first! + raw = raw.replace("<", "<") + raw = raw.replace(">", ">") + raw = raw.replace('"', """) + return raw + + +# ////////////////////////////////////////////////////////////////////////////// + +REGEXP_ESCAPE_RE = re.compile(r"[.?*+^$[\]\\(){}|-]") + + +def escapeRE(string: str) -> str: + string = REGEXP_ESCAPE_RE.sub("\\$&", string) + return string + + +# ////////////////////////////////////////////////////////////////////////////// + + +def isSpace(code: int | None) -> bool: + """Check if character code is a whitespace.""" + return code in (0x09, 0x20) + + +def isStrSpace(ch: str | None) -> bool: + """Check if character is a whitespace.""" + return ch in ("\t", " ") + + +MD_WHITESPACE = { + 0x09, # \t + 0x0A, # \n + 0x0B, # \v + 0x0C, # \f + 0x0D, # \r + 0x20, # space + 0xA0, + 0x1680, + 0x202F, + 0x205F, + 0x3000, +} + + +def isWhiteSpace(code: int) -> bool: + r"""Zs (unicode class) || [\t\f\v\r\n]""" + if code >= 0x2000 and code <= 0x200A: + return True + return code in MD_WHITESPACE + + +# ////////////////////////////////////////////////////////////////////////////// + + +def isPunctChar(ch: str) -> bool: + """Check if character is a punctuation character.""" + return unicodedata.category(ch).startswith(("P", "S")) + + +MD_ASCII_PUNCT = { + 0x21, # /* ! */ + 0x22, # /* " */ + 0x23, # /* # */ + 0x24, # /* $ */ + 0x25, # /* % */ + 0x26, # /* & */ + 0x27, # /* ' */ + 0x28, # /* ( */ + 0x29, # /* ) */ + 0x2A, # /* * */ + 0x2B, # /* + */ + 0x2C, # /* , */ + 0x2D, # /* - */ + 0x2E, # /* . */ + 0x2F, # /* / */ + 0x3A, # /* : */ + 0x3B, # /* ; */ + 0x3C, # /* < */ + 0x3D, # /* = */ + 0x3E, # /* > */ + 0x3F, # /* ? */ + 0x40, # /* @ */ + 0x5B, # /* [ */ + 0x5C, # /* \ */ + 0x5D, # /* ] */ + 0x5E, # /* ^ */ + 0x5F, # /* _ */ + 0x60, # /* ` */ + 0x7B, # /* { */ + 0x7C, # /* | */ + 0x7D, # /* } */ + 0x7E, # /* ~ */ +} + + +def isMdAsciiPunct(ch: int) -> bool: + """Markdown ASCII punctuation characters. + + :: + + !, ", #, $, %, &, ', (, ), *, +, ,, -, ., /, :, ;, <, =, >, ?, @, [, \\, ], ^, _, `, {, |, }, or ~ + + See http://spec.commonmark.org/0.15/#ascii-punctuation-character + + Don't confuse with unicode punctuation !!! It lacks some chars in ascii range. + + """ + return ch in MD_ASCII_PUNCT + + +def normalizeReference(string: str) -> str: + """Helper to unify [reference labels].""" + # Trim and collapse whitespace + # + string = re.sub(r"\s+", " ", string.strip()) + + # In node v10 'ẞ'.toLowerCase() === 'Ṿ', which is presumed to be a bug + # fixed in v12 (couldn't find any details). + # + # So treat this one as a special case + # (remove this when node v10 is no longer supported). + # + # if ('ẞ'.toLowerCase() === 'Ṿ') { + # str = str.replace(/ẞ/g, 'ß') + # } + + # .toLowerCase().toUpperCase() should get rid of all differences + # between letter variants. + # + # Simple .toLowerCase() doesn't normalize 125 code points correctly, + # and .toUpperCase doesn't normalize 6 of them (list of exceptions: + # İ, ϴ, ẞ, Ω, K, Å - those are already uppercased, but have differently + # uppercased versions). + # + # Here's an example showing how it happens. Lets take greek letter omega: + # uppercase U+0398 (Θ), U+03f4 (ϴ) and lowercase U+03b8 (θ), U+03d1 (ϑ) + # + # Unicode entries: + # 0398;GREEK CAPITAL LETTER THETA;Lu;0;L;;;;;N;;;;03B8 + # 03B8;GREEK SMALL LETTER THETA;Ll;0;L;;;;;N;;;0398;;0398 + # 03D1;GREEK THETA SYMBOL;Ll;0;L; 03B8;;;;N;GREEK SMALL LETTER SCRIPT THETA;;0398;;0398 + # 03F4;GREEK CAPITAL THETA SYMBOL;Lu;0;L; 0398;;;;N;;;;03B8 + # + # Case-insensitive comparison should treat all of them as equivalent. + # + # But .toLowerCase() doesn't change ϑ (it's already lowercase), + # and .toUpperCase() doesn't change ϴ (already uppercase). + # + # Applying first lower then upper case normalizes any character: + # '\u0398\u03f4\u03b8\u03d1'.toLowerCase().toUpperCase() === '\u0398\u0398\u0398\u0398' + # + # Note: this is equivalent to unicode case folding; unicode normalization + # is a different step that is not required here. + # + # Final result should be uppercased, because it's later stored in an object + # (this avoid a conflict with Object.prototype members, + # most notably, `__proto__`) + # + return string.lower().upper() + + +LINK_OPEN_RE = re.compile(r"^\s]", flags=re.IGNORECASE) +LINK_CLOSE_RE = re.compile(r"^", flags=re.IGNORECASE) + + +def isLinkOpen(string: str) -> bool: + return bool(LINK_OPEN_RE.search(string)) + + +def isLinkClose(string: str) -> bool: + return bool(LINK_CLOSE_RE.search(string)) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/helpers/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/helpers/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..f4e2cd21b94b9ee9ecb0cac21da619233a5258b9 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/helpers/__init__.py @@ -0,0 +1,6 @@ +"""Functions for parsing Links""" + +__all__ = ("parseLinkDestination", "parseLinkLabel", "parseLinkTitle") +from .parse_link_destination import parseLinkDestination +from .parse_link_label import parseLinkLabel +from .parse_link_title import parseLinkTitle diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/helpers/parse_link_destination.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/helpers/parse_link_destination.py new file mode 100644 index 0000000000000000000000000000000000000000..c98323c056e0103a12849671fbbac58dc76de4b4 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/helpers/parse_link_destination.py @@ -0,0 +1,83 @@ +""" +Parse link destination +""" + +from ..common.utils import charCodeAt, unescapeAll + + +class _Result: + __slots__ = ("ok", "pos", "str") + + def __init__(self) -> None: + self.ok = False + self.pos = 0 + self.str = "" + + +def parseLinkDestination(string: str, pos: int, maximum: int) -> _Result: + start = pos + result = _Result() + + if charCodeAt(string, pos) == 0x3C: # /* < */ + pos += 1 + while pos < maximum: + code = charCodeAt(string, pos) + if code == 0x0A: # /* \n */) + return result + if code == 0x3C: # / * < * / + return result + if code == 0x3E: # /* > */) { + result.pos = pos + 1 + result.str = unescapeAll(string[start + 1 : pos]) + result.ok = True + return result + + if code == 0x5C and pos + 1 < maximum: # \ + pos += 2 + continue + + pos += 1 + + # no closing '>' + return result + + # this should be ... } else { ... branch + + level = 0 + while pos < maximum: + code = charCodeAt(string, pos) + + if code is None or code == 0x20: + break + + # ascii control characters + if code < 0x20 or code == 0x7F: + break + + if code == 0x5C and pos + 1 < maximum: + if charCodeAt(string, pos + 1) == 0x20: + break + pos += 2 + continue + + if code == 0x28: # /* ( */) + level += 1 + if level > 32: + return result + + if code == 0x29: # /* ) */) + if level == 0: + break + level -= 1 + + pos += 1 + + if start == pos: + return result + if level != 0: + return result + + result.str = unescapeAll(string[start:pos]) + result.pos = pos + result.ok = True + return result diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/helpers/parse_link_label.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/helpers/parse_link_label.py new file mode 100644 index 0000000000000000000000000000000000000000..c80da5a7ec54521281dac45b66977404f5384491 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/helpers/parse_link_label.py @@ -0,0 +1,44 @@ +""" +Parse link label + +this function assumes that first character ("[") already matches +returns the end of the label + +""" + +from markdown_it.rules_inline import StateInline + + +def parseLinkLabel(state: StateInline, start: int, disableNested: bool = False) -> int: + labelEnd = -1 + oldPos = state.pos + found = False + + state.pos = start + 1 + level = 1 + + while state.pos < state.posMax: + marker = state.src[state.pos] + if marker == "]": + level -= 1 + if level == 0: + found = True + break + + prevPos = state.pos + state.md.inline.skipToken(state) + if marker == "[": + if prevPos == state.pos - 1: + # increase level if we find text `[`, + # which is not a part of any token + level += 1 + elif disableNested: + state.pos = oldPos + return -1 + if found: + labelEnd = state.pos + + # restore old state + state.pos = oldPos + + return labelEnd diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/helpers/parse_link_title.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/helpers/parse_link_title.py new file mode 100644 index 0000000000000000000000000000000000000000..a38ff0d98ac7feaba80fd67f3c8f0d0454dde47f --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/helpers/parse_link_title.py @@ -0,0 +1,75 @@ +"""Parse link title""" + +from ..common.utils import charCodeAt, unescapeAll + + +class _State: + __slots__ = ("can_continue", "marker", "ok", "pos", "str") + + def __init__(self) -> None: + self.ok = False + """if `true`, this is a valid link title""" + self.can_continue = False + """if `true`, this link can be continued on the next line""" + self.pos = 0 + """if `ok`, it's the position of the first character after the closing marker""" + self.str = "" + """if `ok`, it's the unescaped title""" + self.marker = 0 + """expected closing marker character code""" + + def __str__(self) -> str: + return self.str + + +def parseLinkTitle( + string: str, start: int, maximum: int, prev_state: _State | None = None +) -> _State: + """Parse link title within `str` in [start, max] range, + or continue previous parsing if `prev_state` is defined (equal to result of last execution). + """ + pos = start + state = _State() + + if prev_state is not None: + # this is a continuation of a previous parseLinkTitle call on the next line, + # used in reference links only + state.str = prev_state.str + state.marker = prev_state.marker + else: + if pos >= maximum: + return state + + marker = charCodeAt(string, pos) + + # /* " */ /* ' */ /* ( */ + if marker != 0x22 and marker != 0x27 and marker != 0x28: + return state + + start += 1 + pos += 1 + + # if opening marker is "(", switch it to closing marker ")" + if marker == 0x28: + marker = 0x29 + + state.marker = marker + + while pos < maximum: + code = charCodeAt(string, pos) + if code == state.marker: + state.pos = pos + 1 + state.str += unescapeAll(string[start:pos]) + state.ok = True + return state + elif code == 0x28 and state.marker == 0x29: # /* ( */ /* ) */ + return state + elif code == 0x5C and pos + 1 < maximum: # /* \ */ + pos += 1 + + pos += 1 + + # no closing marker found, but this link title may continue on the next line (for references) + state.can_continue = True + state.str += unescapeAll(string[start:pos]) + return state diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/main.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/main.py new file mode 100644 index 0000000000000000000000000000000000000000..87835e541f1d8500ef874cbf3450aac19e5e451b --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/main.py @@ -0,0 +1,351 @@ +from __future__ import annotations + +from collections.abc import Callable, Generator, Iterable, Mapping, MutableMapping +from contextlib import contextmanager +from typing import Any, Literal, overload + +from . import helpers, presets +from .common import normalize_url, utils +from .parser_block import ParserBlock +from .parser_core import ParserCore +from .parser_inline import ParserInline +from .renderer import RendererHTML, RendererProtocol +from .rules_core.state_core import StateCore +from .token import Token +from .utils import EnvType, OptionsDict, OptionsType, PresetType + +try: + import linkify_it +except ModuleNotFoundError: + linkify_it = None + + +_PRESETS: dict[str, PresetType] = { + "default": presets.default.make(), + "js-default": presets.js_default.make(), + "zero": presets.zero.make(), + "commonmark": presets.commonmark.make(), + "gfm-like": presets.gfm_like.make(), + "gfm-like2": presets.gfm_like2.make(), +} + + +class MarkdownIt: + def __init__( + self, + config: str | PresetType = "commonmark", + options_update: Mapping[str, Any] | None = None, + *, + renderer_cls: Callable[[MarkdownIt], RendererProtocol] = RendererHTML, + ): + """Main parser class + + :param config: name of configuration to load or a pre-defined dictionary + :param options_update: dictionary that will be merged into ``config["options"]`` + :param renderer_cls: the class to load as the renderer: + ``self.renderer = renderer_cls(self) + """ + # add modules + self.utils = utils + self.helpers = helpers + + # initialise classes + self.inline = ParserInline() + self.block = ParserBlock() + self.core = ParserCore() + self.renderer = renderer_cls(self) + self.linkify = linkify_it.LinkifyIt() if linkify_it else None + + # set the configuration + if options_update and not isinstance(options_update, Mapping): + # catch signature change where renderer_cls was not used as a key-word + raise TypeError( + f"options_update should be a mapping: {options_update}" + "\n(Perhaps you intended this to be the renderer_cls?)" + ) + self.configure(config, options_update=options_update) + + def __repr__(self) -> str: + return f"{self.__class__.__module__}.{self.__class__.__name__}()" + + @overload + def __getitem__(self, name: Literal["inline"]) -> ParserInline: ... + + @overload + def __getitem__(self, name: Literal["block"]) -> ParserBlock: ... + + @overload + def __getitem__(self, name: Literal["core"]) -> ParserCore: ... + + @overload + def __getitem__(self, name: Literal["renderer"]) -> RendererProtocol: ... + + @overload + def __getitem__(self, name: str) -> Any: ... + + def __getitem__(self, name: str) -> Any: + return { + "inline": self.inline, + "block": self.block, + "core": self.core, + "renderer": self.renderer, + }[name] + + def set(self, options: OptionsType) -> None: + """Set parser options (in the same format as in constructor). + Probably, you will never need it, but you can change options after constructor call. + + __Note:__ To achieve the best possible performance, don't modify a + `markdown-it` instance options on the fly. If you need multiple configurations + it's best to create multiple instances and initialize each with separate config. + """ + self.options = OptionsDict(options) + + def configure( + self, presets: str | PresetType, options_update: Mapping[str, Any] | None = None + ) -> MarkdownIt: + """Batch load of all options and component settings. + This is an internal method, and you probably will not need it. + But if you will - see available presets and data structure + [here](https://github.com/markdown-it/markdown-it/tree/master/lib/presets) + + We strongly recommend to use presets instead of direct config loads. + That will give better compatibility with next versions. + """ + if isinstance(presets, str): + if presets not in _PRESETS: + raise KeyError(f"Wrong `markdown-it` preset '{presets}', check name") + config = _PRESETS[presets] + else: + config = presets + + if not config: + raise ValueError("Wrong `markdown-it` config, can't be empty") + + options = config.get("options", {}) or {} + if options_update: + options = {**options, **options_update} # type: ignore + + self.set(options) + + if "components" in config: + for name, component in config["components"].items(): + rules = component.get("rules", None) + if rules: + self[name].ruler.enableOnly(rules) + rules2 = component.get("rules2", None) + if rules2: + self[name].ruler2.enableOnly(rules2) + + return self + + def get_all_rules(self) -> dict[str, list[str]]: + """Return the names of all active rules.""" + rules = { + chain: self[chain].ruler.get_all_rules() + for chain in ["core", "block", "inline"] + } + rules["inline2"] = self.inline.ruler2.get_all_rules() + return rules + + def get_active_rules(self) -> dict[str, list[str]]: + """Return the names of all active rules.""" + rules = { + chain: self[chain].ruler.get_active_rules() + for chain in ["core", "block", "inline"] + } + rules["inline2"] = self.inline.ruler2.get_active_rules() + return rules + + def enable( + self, names: str | Iterable[str], ignoreInvalid: bool = False + ) -> MarkdownIt: + """Enable list or rules. (chainable) + + :param names: rule name or list of rule names to enable. + :param ignoreInvalid: set `true` to ignore errors when rule not found. + + It will automatically find appropriate components, + containing rules with given names. If rule not found, and `ignoreInvalid` + not set - throws exception. + + Example:: + + md = MarkdownIt().enable(['sub', 'sup']).disable('smartquotes') + + """ + result = [] + + if isinstance(names, str): + names = [names] + + for chain in ["core", "block", "inline"]: + result.extend(self[chain].ruler.enable(names, True)) + result.extend(self.inline.ruler2.enable(names, True)) + + missed = [name for name in names if name not in result] + if missed and not ignoreInvalid: + raise ValueError(f"MarkdownIt. Failed to enable unknown rule(s): {missed}") + + return self + + def disable( + self, names: str | Iterable[str], ignoreInvalid: bool = False + ) -> MarkdownIt: + """The same as [[MarkdownIt.enable]], but turn specified rules off. (chainable) + + :param names: rule name or list of rule names to disable. + :param ignoreInvalid: set `true` to ignore errors when rule not found. + + """ + result = [] + + if isinstance(names, str): + names = [names] + + for chain in ["core", "block", "inline"]: + result.extend(self[chain].ruler.disable(names, True)) + result.extend(self.inline.ruler2.disable(names, True)) + + missed = [name for name in names if name not in result] + if missed and not ignoreInvalid: + raise ValueError(f"MarkdownIt. Failed to disable unknown rule(s): {missed}") + return self + + @contextmanager + def reset_rules(self) -> Generator[None, None, None]: + """A context manager, that will reset the current enabled rules on exit.""" + chain_rules = self.get_active_rules() + yield + for chain, rules in chain_rules.items(): + if chain != "inline2": + self[chain].ruler.enableOnly(rules) + self.inline.ruler2.enableOnly(chain_rules["inline2"]) + + def add_render_rule( + self, name: str, function: Callable[..., Any], fmt: str = "html" + ) -> None: + """Add a rule for rendering a particular Token type. + + Only applied when ``renderer.__output__ == fmt`` + """ + if self.renderer.__output__ == fmt: + self.renderer.rules[name] = function.__get__(self.renderer) # type: ignore + + def use( + self, plugin: Callable[..., None], *params: Any, **options: Any + ) -> MarkdownIt: + """Load specified plugin with given params into current parser instance. (chainable) + + It's just a sugar to call `plugin(md, params)` with curring. + + Example:: + + def func(tokens, idx): + tokens[idx].content = tokens[idx].content.replace('foo', 'bar') + md = MarkdownIt().use(plugin, 'foo_replace', 'text', func) + + """ + plugin(self, *params, **options) + return self + + def parse(self, src: str, env: EnvType | None = None) -> list[Token]: + """Parse the source string to a token stream + + :param src: source string + :param env: environment sandbox + + Parse input string and return list of block tokens (special token type + "inline" will contain list of inline tokens). + + `env` is used to pass data between "distributed" rules and return additional + metadata like reference info, needed for the renderer. It also can be used to + inject data in specific cases. Usually, you will be ok to pass `{}`, + and then pass updated object to renderer. + """ + env = {} if env is None else env + if not isinstance(env, MutableMapping): + raise TypeError(f"Input data should be a MutableMapping, not {type(env)}") + if not isinstance(src, str): + raise TypeError(f"Input data should be a string, not {type(src)}") + state = StateCore(src, self, env) + self.core.process(state) + return state.tokens + + def render(self, src: str, env: EnvType | None = None) -> Any: + """Render markdown string into html. It does all magic for you :). + + :param src: source string + :param env: environment sandbox + :returns: The output of the loaded renderer + + `env` can be used to inject additional metadata (`{}` by default). + But you will not need it with high probability. See also comment + in [[MarkdownIt.parse]]. + """ + env = {} if env is None else env + return self.renderer.render(self.parse(src, env), self.options, env) + + def parseInline(self, src: str, env: EnvType | None = None) -> list[Token]: + """The same as [[MarkdownIt.parse]] but skip all block rules. + + :param src: source string + :param env: environment sandbox + + It returns the + block tokens list with the single `inline` element, containing parsed inline + tokens in `children` property. Also updates `env` object. + """ + env = {} if env is None else env + if not isinstance(env, MutableMapping): + raise TypeError(f"Input data should be an MutableMapping, not {type(env)}") + if not isinstance(src, str): + raise TypeError(f"Input data should be a string, not {type(src)}") + state = StateCore(src, self, env) + state.inlineMode = True + self.core.process(state) + return state.tokens + + def renderInline(self, src: str, env: EnvType | None = None) -> Any: + """Similar to [[MarkdownIt.render]] but for single paragraph content. + + :param src: source string + :param env: environment sandbox + + Similar to [[MarkdownIt.render]] but for single paragraph content. Result + will NOT be wrapped into `

    ` tags. + """ + env = {} if env is None else env + return self.renderer.render(self.parseInline(src, env), self.options, env) + + # link methods + + def validateLink(self, url: str) -> bool: + """Validate if the URL link is allowed in output. + + This validator can prohibit more than really needed to prevent XSS. + It's a tradeoff to keep code simple and to be secure by default. + + Note: the url should be normalized at this point, and existing entities decoded. + """ + return normalize_url.validateLink(url) + + def normalizeLink(self, url: str) -> str: + """Normalize destination URLs in links + + :: + + [label]: destination 'title' + ^^^^^^^^^^^ + """ + return normalize_url.normalizeLink(url) + + def normalizeLinkText(self, link: str) -> str: + """Normalize autolink content + + :: + + + ~~~~~~~~~~~ + """ + return normalize_url.normalizeLinkText(link) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/parser_block.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/parser_block.py new file mode 100644 index 0000000000000000000000000000000000000000..50a7184cf4746fc86a4e8032efd0604bf819021c --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/parser_block.py @@ -0,0 +1,113 @@ +"""Block-level tokenizer.""" + +from __future__ import annotations + +from collections.abc import Callable +import logging +from typing import TYPE_CHECKING + +from . import rules_block +from .ruler import Ruler +from .rules_block.state_block import StateBlock +from .token import Token +from .utils import EnvType + +if TYPE_CHECKING: + from markdown_it import MarkdownIt + +LOGGER = logging.getLogger(__name__) + + +RuleFuncBlockType = Callable[[StateBlock, int, int, bool], bool] +"""(state: StateBlock, startLine: int, endLine: int, silent: bool) -> matched: bool) + +`silent` disables token generation, useful for lookahead. +""" + +_rules: list[tuple[str, RuleFuncBlockType, list[str]]] = [ + # First 2 params - rule name & source. Secondary array - list of rules, + # which can be terminated by this one. + ("table", rules_block.table, ["paragraph", "reference"]), + ("code", rules_block.code, []), + ("fence", rules_block.fence, ["paragraph", "reference", "blockquote", "list"]), + ( + "blockquote", + rules_block.blockquote, + ["paragraph", "reference", "blockquote", "list"], + ), + ("hr", rules_block.hr, ["paragraph", "reference", "blockquote", "list"]), + ("list", rules_block.list_block, ["paragraph", "reference", "blockquote"]), + ("reference", rules_block.reference, []), + ("html_block", rules_block.html_block, ["paragraph", "reference", "blockquote"]), + ("heading", rules_block.heading, ["paragraph", "reference", "blockquote"]), + ("lheading", rules_block.lheading, []), + ("paragraph", rules_block.paragraph, []), +] + + +class ParserBlock: + """ + ParserBlock#ruler -> Ruler + + [[Ruler]] instance. Keep configuration of block rules. + """ + + def __init__(self) -> None: + self.ruler = Ruler[RuleFuncBlockType]() + for name, rule, alt in _rules: + self.ruler.push(name, rule, {"alt": alt}) + + def tokenize(self, state: StateBlock, startLine: int, endLine: int) -> None: + """Generate tokens for input range.""" + rules = self.ruler.getRules("") + line = startLine + maxNesting = state.md.options.maxNesting + hasEmptyLines = False + + while line < endLine: + state.line = line = state.skipEmptyLines(line) + if line >= endLine: + break + if state.sCount[line] < state.blkIndent: + # Termination condition for nested calls. + # Nested calls currently used for blockquotes & lists + break + if state.level >= maxNesting: + # If nesting level exceeded - skip tail to the end. + # That's not ordinary situation and we should not care about content. + state.line = endLine + break + + # Try all possible rules. + # On success, rule should: + # - update `state.line` + # - update `state.tokens` + # - return True + for rule in rules: + if rule(state, line, endLine, False): + break + + # set state.tight if we had an empty line before current tag + # i.e. latest empty line should not count + state.tight = not hasEmptyLines + + line = state.line + + # paragraph might "eat" one newline after it in nested lists + if (line - 1) < endLine and state.isEmpty(line - 1): + hasEmptyLines = True + + if line < endLine and state.isEmpty(line): + hasEmptyLines = True + line += 1 + state.line = line + + def parse( + self, src: str, md: MarkdownIt, env: EnvType, outTokens: list[Token] + ) -> list[Token] | None: + """Process input string and push block tokens into `outTokens`.""" + if not src: + return None + state = StateBlock(src, md, env, outTokens) + self.tokenize(state, state.line, state.lineMax) + return state.tokens diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/parser_core.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/parser_core.py new file mode 100644 index 0000000000000000000000000000000000000000..8f5b921cbd3bed2c85734b44ccd989a0fd0e6788 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/parser_core.py @@ -0,0 +1,46 @@ +""" +* class Core +* +* Top-level rules executor. Glues block/inline parsers and does intermediate +* transformations. +""" + +from __future__ import annotations + +from collections.abc import Callable + +from .ruler import Ruler +from .rules_core import ( + block, + inline, + linkify, + normalize, + replace, + smartquotes, + text_join, +) +from .rules_core.state_core import StateCore + +RuleFuncCoreType = Callable[[StateCore], None] + +_rules: list[tuple[str, RuleFuncCoreType]] = [ + ("normalize", normalize), + ("block", block), + ("inline", inline), + ("linkify", linkify), + ("replacements", replace), + ("smartquotes", smartquotes), + ("text_join", text_join), +] + + +class ParserCore: + def __init__(self) -> None: + self.ruler = Ruler[RuleFuncCoreType]() + for name, rule in _rules: + self.ruler.push(name, rule) + + def process(self, state: StateCore) -> None: + """Executes core chain rules.""" + for rule in self.ruler.getRules(""): + rule(state) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/parser_inline.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/parser_inline.py new file mode 100644 index 0000000000000000000000000000000000000000..8fabb9884bb94973ba3f59458f7960be678a38da --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/parser_inline.py @@ -0,0 +1,215 @@ +"""Tokenizes paragraph content.""" + +from __future__ import annotations + +from collections.abc import Callable +import functools +import re +from typing import TYPE_CHECKING + +from . import rules_inline +from .ruler import Ruler +from .rules_inline.state_inline import StateInline +from .token import Token +from .utils import EnvType + +if TYPE_CHECKING: + from markdown_it import MarkdownIt + + +# Default set of characters that terminate a text token and allow inline rules to fire. +# '{}$%@~+=:' reserved for extensions. +# Note: Don't confuse with "Markdown ASCII Punctuation" chars. +# http://spec.commonmark.org/0.15/#ascii-punctuation-character +_DEFAULT_TERMINATORS: frozenset[str] = frozenset( + { + "\n", + "!", + "#", + "$", + "%", + "&", + "*", + "+", + "-", + ":", + "<", + "=", + ">", + "@", + "[", + "\\", + "]", + "^", + "_", + "`", + "{", + "}", + "~", + } +) + + +# Lazily compiled regex for the default terminator set. The @cache ensures it is +# compiled at most once (on first ParserInline instantiation) and shared across all +# instances that have not added extra chars, keeping __init__ cost near zero. +@functools.cache +def _default_terminator_re() -> re.Pattern[str]: + return re.compile("[" + re.escape("".join(_DEFAULT_TERMINATORS)) + "]") + + +# Parser rules +RuleFuncInlineType = Callable[[StateInline, bool], bool] +"""(state: StateInline, silent: bool) -> matched: bool) + +`silent` disables token generation, useful for lookahead. +""" +_rules: list[tuple[str, RuleFuncInlineType]] = [ + ("text", rules_inline.text), + ("linkify", rules_inline.linkify), + ("newline", rules_inline.newline), + ("escape", rules_inline.escape), + ("backticks", rules_inline.backtick), + ("strikethrough", rules_inline.strikethrough.tokenize), + ("emphasis", rules_inline.emphasis.tokenize), + ("link", rules_inline.link), + ("image", rules_inline.image), + ("autolink", rules_inline.autolink), + ("html_inline", rules_inline.html_inline), + ("entity", rules_inline.entity), +] + +# Note `rule2` ruleset was created specifically for emphasis/strikethrough +# post-processing and may be changed in the future. +# +# Don't use this for anything except pairs (plugins working with `balance_pairs`). +# +RuleFuncInline2Type = Callable[[StateInline], None] +_rules2: list[tuple[str, RuleFuncInline2Type]] = [ + ("balance_pairs", rules_inline.link_pairs), + ("strikethrough", rules_inline.strikethrough.postProcess), + ("emphasis", rules_inline.emphasis.postProcess), + # rules for pairs separate '**' into its own text tokens, which may be left unused, + # rule below merges unused segments back with the rest of the text + ("fragments_join", rules_inline.fragments_join), +] + + +class ParserInline: + def __init__(self) -> None: + self.ruler = Ruler[RuleFuncInlineType]() + for name, rule in _rules: + self.ruler.push(name, rule) + # Second ruler used for post-processing (e.g. in emphasis-like rules) + self.ruler2 = Ruler[RuleFuncInline2Type]() + for name, rule2 in _rules2: + self.ruler2.push(name, rule2) + # Characters that stop the text rule, allowing other inline rules to fire. + # _extra_terminator_chars is only allocated when add_terminator_char() is called + # with a char outside the defaults, keeping __init__ allocation-free. + self._extra_terminator_chars: set[str] = set() + # Pre-compiled regex shared with all default instances (no copy in the common path). + self.terminator_re: re.Pattern[str] = _default_terminator_re() + + def add_terminator_char(self, ch: str) -> None: + """Register a character that stops the ``text`` rule, allowing inline rules to fire. + + This lets plugins declare which characters their inline rules react to, + mirroring the ``MARKER`` mechanism in the Rust markdown-it implementation. + + :param ch: A single character to add to the terminator set. + """ + if ch not in _DEFAULT_TERMINATORS and ch not in self._extra_terminator_chars: + self._extra_terminator_chars.add(ch) + self.terminator_re = re.compile( + "[" + + re.escape( + "".join(_DEFAULT_TERMINATORS | self._extra_terminator_chars) + ) + + "]" + ) + + def skipToken(self, state: StateInline) -> None: + """Skip single token by running all rules in validation mode; + returns `True` if any rule reported success + """ + ok = False + pos = state.pos + rules = self.ruler.getRules("") + maxNesting = state.md.options["maxNesting"] + cache = state.cache + + if pos in cache: + state.pos = cache[pos] + return + + if state.level < maxNesting: + for rule in rules: + # Increment state.level and decrement it later to limit recursion. + # It's harmless to do here, because no tokens are created. + # But ideally, we'd need a separate private state variable for this purpose. + state.level += 1 + ok = rule(state, True) + state.level -= 1 + if ok: + break + else: + # Too much nesting, just skip until the end of the paragraph. + # + # NOTE: this will cause links to behave incorrectly in the following case, + # when an amount of `[` is exactly equal to `maxNesting + 1`: + # + # [[[[[[[[[[[[[[[[[[[[[foo]() + # + # TODO: remove this workaround when CM standard will allow nested links + # (we can replace it by preventing links from being parsed in + # validation mode) + # + state.pos = state.posMax + + if not ok: + state.pos += 1 + cache[pos] = state.pos + + def tokenize(self, state: StateInline) -> None: + """Generate tokens for input range.""" + ok = False + rules = self.ruler.getRules("") + end = state.posMax + maxNesting = state.md.options["maxNesting"] + + while state.pos < end: + # Try all possible rules. + # On success, rule should: + # + # - update `state.pos` + # - update `state.tokens` + # - return true + + if state.level < maxNesting: + for rule in rules: + ok = rule(state, False) + if ok: + break + + if ok: + if state.pos >= end: + break + continue + + state.pending += state.src[state.pos] + state.pos += 1 + + if state.pending: + state.pushPending() + + def parse( + self, src: str, md: MarkdownIt, env: EnvType, tokens: list[Token] + ) -> list[Token]: + """Process input string and push inline tokens into `tokens`""" + state = StateInline(src, md, env, tokens) + self.tokenize(state) + rules2 = self.ruler2.getRules("") + for rule in rules2: + rule(state) + return state.tokens diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/port.yaml b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/port.yaml new file mode 100644 index 0000000000000000000000000000000000000000..ce2dde95faa6224dfe63ae1673b6c7363a5b3efb --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/port.yaml @@ -0,0 +1,48 @@ +- package: markdown-it/markdown-it + version: 14.1.0 + commit: 0fe7ccb4b7f30236fb05f623be6924961d296d3d + date: Mar 19, 2024 + notes: + - Rename variables that use python built-in names, e.g. + - `max` -> `maximum` + - `len` -> `length` + - `str` -> `string` + - | + Convert JS `for` loops to `while` loops + this is generally the main difference between the codes, + because in python you can't do e.g. `for {i=1;i PresetType: + config = commonmark.make() + config["components"]["core"]["rules"].append("linkify") + config["components"]["block"]["rules"].append("table") + config["components"]["inline"]["rules"].extend(["strikethrough", "linkify"]) + config["components"]["inline"]["rules2"].append("strikethrough") + config["options"]["linkify"] = True + config["options"]["html"] = True + return config + + +class gfm_like2: # noqa: N801 + """GitHub Flavoured Markdown (GFM) like, extended. + + Builds on ``gfm-like`` and additionally enables: + + - Task lists (``- [x] done``) + - Alerts (``> [!NOTE]``) + - Single-tilde strikethrough (``~text~`` in addition to ``~~text~~``) + """ + + @staticmethod + def make() -> PresetType: + config = gfm_like.make() + config["options"]["tasklists"] = True + config["options"]["tasklists_editable"] = False + config["options"]["alerts"] = True + config["options"]["strikethrough_single_tilde"] = True + return config diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/presets/commonmark.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/presets/commonmark.py new file mode 100644 index 0000000000000000000000000000000000000000..ed0de0fe4dfbad9e3ab82477433805ea0b6650c6 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/presets/commonmark.py @@ -0,0 +1,75 @@ +"""Commonmark default options. + +This differs to presets.default, +primarily in that it allows HTML and does not enable components: + +- block: table +- inline: strikethrough +""" + +from ..utils import PresetType + + +def make() -> PresetType: + return { + "options": { + "maxNesting": 20, # Internal protection, recursion limit + "html": True, # Enable HTML tags in source, + # this is just a shorthand for .enable(["html_inline", "html_block"]) + # used by the linkify rule: + "linkify": False, # autoconvert URL-like texts to links + # used by the replacements and smartquotes rules + # Enable some language-neutral replacements + quotes beautification + "typographer": False, + # used by the smartquotes rule: + # Double + single quotes replacement pairs, when typographer enabled, + # and smartquotes on. Could be either a String or an Array. + # + # For example, you can use '«»„“' for Russian, '„“‚‘' for German, + # and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp). + "quotes": "\u201c\u201d\u2018\u2019", # /* “”‘’ */ + # Renderer specific; these options are used directly in the HTML renderer + "xhtmlOut": True, # Use '/' to close single tags (
    ) + "breaks": False, # Convert '\n' in paragraphs into
    + "langPrefix": "language-", # CSS language prefix for fenced blocks + # Highlighter function. Should return escaped HTML, + # or '' if the source string is not changed and should be escaped externally. + # If result starts with PresetType: + return { + "options": { + "maxNesting": 100, # Internal protection, recursion limit + "html": False, # Enable HTML tags in source + # this is just a shorthand for .disable(["html_inline", "html_block"]) + # used by the linkify rule: + "linkify": False, # autoconvert URL-like texts to links + # used by the replacements and smartquotes rules: + # Enable some language-neutral replacements + quotes beautification + "typographer": False, + # used by the smartquotes rule: + # Double + single quotes replacement pairs, when typographer enabled, + # and smartquotes on. Could be either a String or an Array. + # For example, you can use '«»„“' for Russian, '„“‚‘' for German, + # and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp). + "quotes": "\u201c\u201d\u2018\u2019", # /* “”‘’ */ + # Renderer specific; these options are used directly in the HTML renderer + "xhtmlOut": False, # Use '/' to close single tags (
    ) + "breaks": False, # Convert '\n' in paragraphs into
    + "langPrefix": "language-", # CSS language prefix for fenced blocks + # Highlighter function. Should return escaped HTML, + # or '' if the source string is not changed and should be escaped externally. + # If result starts with PresetType: + return { + "options": { + "maxNesting": 20, # Internal protection, recursion limit + "html": False, # Enable HTML tags in source + # this is just a shorthand for .disable(["html_inline", "html_block"]) + # used by the linkify rule: + "linkify": False, # autoconvert URL-like texts to links + # used by the replacements and smartquotes rules: + # Enable some language-neutral replacements + quotes beautification + "typographer": False, + # used by the smartquotes rule: + # Double + single quotes replacement pairs, when typographer enabled, + # and smartquotes on. Could be either a String or an Array. + # For example, you can use '«»„“' for Russian, '„“‚‘' for German, + # and ['«\xA0', '\xA0»', '‹\xA0', '\xA0›'] for French (including nbsp). + "quotes": "\u201c\u201d\u2018\u2019", # /* “”‘’ */ + # Renderer specific; these options are used directly in the HTML renderer + "xhtmlOut": False, # Use '/' to close single tags (
    ) + "breaks": False, # Convert '\n' in paragraphs into
    + "langPrefix": "language-", # CSS language prefix for fenced blocks + # Highlighter function. Should return escaped HTML, + # or '' if the source string is not changed and should be escaped externally. + # If result starts with Any: ... + + +class RendererHTML(RendererProtocol): + """Contains render rules for tokens. Can be updated and extended. + + Example: + + Each rule is called as independent static function with fixed signature: + + :: + + class Renderer: + def token_type_name(self, tokens, idx, options, env) { + # ... + return renderedHTML + + :: + + class CustomRenderer(RendererHTML): + def strong_open(self, tokens, idx, options, env): + return '' + def strong_close(self, tokens, idx, options, env): + return '' + + md = MarkdownIt(renderer_cls=CustomRenderer) + + result = md.render(...) + + See https://github.com/markdown-it/markdown-it/blob/master/lib/renderer.js + for more details and examples. + """ + + __output__ = "html" + + def __init__(self, parser: Any = None): + self.rules = { + k: v + for k, v in inspect.getmembers(self, predicate=inspect.ismethod) + if not (k.startswith("render") or k.startswith("_")) + } + + def render( + self, tokens: Sequence[Token], options: OptionsDict, env: EnvType + ) -> str: + """Takes token stream and generates HTML. + + :param tokens: list on block tokens to render + :param options: params of parser instance + :param env: additional data from parsed input + + """ + result = "" + + for i, token in enumerate(tokens): + if token.type == "inline": + if token.children: + result += self.renderInline(token.children, options, env) + elif token.type in self.rules: + result += self.rules[token.type](tokens, i, options, env) + else: + result += self.renderToken(tokens, i, options, env) + + return result + + def renderInline( + self, tokens: Sequence[Token], options: OptionsDict, env: EnvType + ) -> str: + """The same as ``render``, but for single token of `inline` type. + + :param tokens: list on block tokens to render + :param options: params of parser instance + :param env: additional data from parsed input (references, for example) + """ + result = "" + + for i, token in enumerate(tokens): + if token.type in self.rules: + result += self.rules[token.type](tokens, i, options, env) + else: + result += self.renderToken(tokens, i, options, env) + + return result + + def renderToken( + self, + tokens: Sequence[Token], + idx: int, + options: OptionsDict, + env: EnvType, + ) -> str: + """Default token renderer. + + Can be overridden by custom function + + :param idx: token index to render + :param options: params of parser instance + """ + result = "" + needLf = False + token = tokens[idx] + + # Tight list paragraphs + if token.hidden: + return "" + + # Insert a newline between hidden paragraph and subsequent opening + # block-level tag. + # + # For example, here we should insert a newline before blockquote: + # - a + # > + # + if token.block and token.nesting != -1 and idx and tokens[idx - 1].hidden: + result += "\n" + + # Add token name, e.g. ``. + # + needLf = False + + result += ">\n" if needLf else ">" + + return result + + @staticmethod + def renderAttrs(token: Token) -> str: + """Render token attributes to string.""" + result = "" + + for key, value in token.attrItems(): + result += " " + escapeHtml(key) + '="' + escapeHtml(str(value)) + '"' + + return result + + def renderInlineAsText( + self, + tokens: Sequence[Token] | None, + options: OptionsDict, + env: EnvType, + ) -> str: + """Special kludge for image `alt` attributes to conform CommonMark spec. + + Don't try to use it! Spec requires to show `alt` content with stripped markup, + instead of simple escaping. + + :param tokens: list on block tokens to render + :param options: params of parser instance + :param env: additional data from parsed input + """ + result = "" + + for token in tokens or []: + if token.type == "text": + result += token.content + elif token.type == "image": + if token.children: + result += self.renderInlineAsText(token.children, options, env) + elif token.type == "softbreak": + result += "\n" + + return result + + ################################################### + + def list_item_open( + self, + tokens: Sequence[Token], + idx: int, + options: OptionsDict, + env: EnvType, + ) -> str: + token = tokens[idx] + result = self.renderToken(tokens, idx, options, env) + if token.meta and "checked" in token.meta: + checked_attr = ' checked=""' if token.meta["checked"] else "" + disabled_attr = ( + "" if options.get("tasklists_editable", False) else ' disabled=""' + ) + result += ( + ' ' + ) + return result + + def code_inline( + self, tokens: Sequence[Token], idx: int, options: OptionsDict, env: EnvType + ) -> str: + token = tokens[idx] + return ( + "" + + escapeHtml(tokens[idx].content) + + "" + ) + + def code_block( + self, + tokens: Sequence[Token], + idx: int, + options: OptionsDict, + env: EnvType, + ) -> str: + token = tokens[idx] + + return ( + "" + + escapeHtml(tokens[idx].content) + + "\n" + ) + + def fence( + self, + tokens: Sequence[Token], + idx: int, + options: OptionsDict, + env: EnvType, + ) -> str: + token = tokens[idx] + info = unescapeAll(token.info).strip() if token.info else "" + langName = "" + langAttrs = "" + + if info: + arr = info.split(maxsplit=1) + langName = arr[0] + if len(arr) == 2: + langAttrs = arr[1] + + if options.highlight: + highlighted = options.highlight( + token.content, langName, langAttrs + ) or escapeHtml(token.content) + else: + highlighted = escapeHtml(token.content) + + if highlighted.startswith("" + + highlighted + + "\n" + ) + + return ( + "

    "
    +            + highlighted
    +            + "
    \n" + ) + + def image( + self, + tokens: Sequence[Token], + idx: int, + options: OptionsDict, + env: EnvType, + ) -> str: + token = tokens[idx] + + # "alt" attr MUST be set, even if empty. Because it's mandatory and + # should be placed on proper position for tests. + if token.children: + token.attrSet("alt", self.renderInlineAsText(token.children, options, env)) + else: + token.attrSet("alt", "") + + return self.renderToken(tokens, idx, options, env) + + def hardbreak( + self, tokens: Sequence[Token], idx: int, options: OptionsDict, env: EnvType + ) -> str: + return "
    \n" if options.xhtmlOut else "
    \n" + + def softbreak( + self, tokens: Sequence[Token], idx: int, options: OptionsDict, env: EnvType + ) -> str: + return ( + ("
    \n" if options.xhtmlOut else "
    \n") if options.breaks else "\n" + ) + + def text( + self, tokens: Sequence[Token], idx: int, options: OptionsDict, env: EnvType + ) -> str: + return escapeHtml(tokens[idx].content) + + def html_block( + self, tokens: Sequence[Token], idx: int, options: OptionsDict, env: EnvType + ) -> str: + return tokens[idx].content + + def html_inline( + self, tokens: Sequence[Token], idx: int, options: OptionsDict, env: EnvType + ) -> str: + return tokens[idx].content diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/ruler.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/ruler.py new file mode 100644 index 0000000000000000000000000000000000000000..91ab58044cc9f93c540fc5d7e49a6c704e805d58 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/ruler.py @@ -0,0 +1,275 @@ +""" +class Ruler + +Helper class, used by [[MarkdownIt#core]], [[MarkdownIt#block]] and +[[MarkdownIt#inline]] to manage sequences of functions (rules): + +- keep rules in defined order +- assign the name to each rule +- enable/disable rules +- add/replace rules +- allow assign rules to additional named chains (in the same) +- caching lists of active rules + +You will not need use this class directly until write plugins. For simple +rules control use [[MarkdownIt.disable]], [[MarkdownIt.enable]] and +[[MarkdownIt.use]]. +""" + +from __future__ import annotations + +from collections.abc import Iterable +from dataclasses import dataclass, field +from typing import TYPE_CHECKING, Generic, TypedDict, TypeVar +import warnings + +from .utils import EnvType + +if TYPE_CHECKING: + from markdown_it import MarkdownIt + + +class StateBase: + def __init__(self, src: str, md: MarkdownIt, env: EnvType): + self.src = src + self.env = env + self.md = md + + @property + def src(self) -> str: + return self._src + + @src.setter + def src(self, value: str) -> None: + self._src = value + self._srcCharCode: tuple[int, ...] | None = None + + @property + def srcCharCode(self) -> tuple[int, ...]: + warnings.warn( + "StateBase.srcCharCode is deprecated. Use StateBase.src instead.", + DeprecationWarning, + stacklevel=2, + ) + if self._srcCharCode is None: + self._srcCharCode = tuple(ord(c) for c in self._src) + return self._srcCharCode + + +class RuleOptionsType(TypedDict, total=False): + alt: list[str] + + +RuleFuncTv = TypeVar("RuleFuncTv") +"""A rule function, whose signature is dependent on the state type.""" + + +@dataclass(slots=True) +class Rule(Generic[RuleFuncTv]): + name: str + enabled: bool + fn: RuleFuncTv = field(repr=False) + alt: list[str] + + +class Ruler(Generic[RuleFuncTv]): + def __init__(self) -> None: + # List of added rules. + self.__rules__: list[Rule[RuleFuncTv]] = [] + # Cached rule chains. + # First level - chain name, '' for default. + # Second level - diginal anchor for fast filtering by charcodes. + self.__cache__: dict[str, list[RuleFuncTv]] | None = None + + def __find__(self, name: str) -> int: + """Find rule index by name""" + for i, rule in enumerate(self.__rules__): + if rule.name == name: + return i + return -1 + + def __compile__(self) -> None: + """Build rules lookup cache""" + chains = {""} + # collect unique names + for rule in self.__rules__: + if not rule.enabled: + continue + for name in rule.alt: + chains.add(name) + self.__cache__ = {} + for chain in chains: + self.__cache__[chain] = [] + for rule in self.__rules__: + if not rule.enabled: + continue + if chain and (chain not in rule.alt): + continue + self.__cache__[chain].append(rule.fn) + + def at( + self, ruleName: str, fn: RuleFuncTv, options: RuleOptionsType | None = None + ) -> None: + """Replace rule by name with new function & options. + + :param ruleName: rule name to replace. + :param fn: new rule function. + :param options: new rule options (not mandatory). + :raises: KeyError if name not found + """ + index = self.__find__(ruleName) + options = options or {} + if index == -1: + raise KeyError(f"Parser rule not found: {ruleName}") + self.__rules__[index].fn = fn + self.__rules__[index].alt = options.get("alt", []) + self.__cache__ = None + + def before( + self, + beforeName: str, + ruleName: str, + fn: RuleFuncTv, + options: RuleOptionsType | None = None, + ) -> None: + """Add new rule to chain before one with given name. + + :param beforeName: new rule will be added before this one. + :param ruleName: new rule will be added before this one. + :param fn: new rule function. + :param options: new rule options (not mandatory). + :raises: KeyError if name not found + """ + index = self.__find__(beforeName) + options = options or {} + if index == -1: + raise KeyError(f"Parser rule not found: {beforeName}") + self.__rules__.insert( + index, Rule[RuleFuncTv](ruleName, True, fn, options.get("alt", [])) + ) + self.__cache__ = None + + def after( + self, + afterName: str, + ruleName: str, + fn: RuleFuncTv, + options: RuleOptionsType | None = None, + ) -> None: + """Add new rule to chain after one with given name. + + :param afterName: new rule will be added after this one. + :param ruleName: new rule will be added after this one. + :param fn: new rule function. + :param options: new rule options (not mandatory). + :raises: KeyError if name not found + """ + index = self.__find__(afterName) + options = options or {} + if index == -1: + raise KeyError(f"Parser rule not found: {afterName}") + self.__rules__.insert( + index + 1, Rule[RuleFuncTv](ruleName, True, fn, options.get("alt", [])) + ) + self.__cache__ = None + + def push( + self, ruleName: str, fn: RuleFuncTv, options: RuleOptionsType | None = None + ) -> None: + """Push new rule to the end of chain. + + :param ruleName: new rule will be added to the end of chain. + :param fn: new rule function. + :param options: new rule options (not mandatory). + + """ + self.__rules__.append( + Rule[RuleFuncTv](ruleName, True, fn, (options or {}).get("alt", [])) + ) + self.__cache__ = None + + def enable( + self, names: str | Iterable[str], ignoreInvalid: bool = False + ) -> list[str]: + """Enable rules with given names. + + :param names: name or list of rule names to enable. + :param ignoreInvalid: ignore errors when rule not found + :raises: KeyError if name not found and not ignoreInvalid + :return: list of found rule names + """ + if isinstance(names, str): + names = [names] + result: list[str] = [] + for name in names: + idx = self.__find__(name) + if (idx < 0) and ignoreInvalid: + continue + if (idx < 0) and not ignoreInvalid: + raise KeyError(f"Rules manager: invalid rule name {name}") + self.__rules__[idx].enabled = True + result.append(name) + self.__cache__ = None + return result + + def enableOnly( + self, names: str | Iterable[str], ignoreInvalid: bool = False + ) -> list[str]: + """Enable rules with given names, and disable everything else. + + :param names: name or list of rule names to enable. + :param ignoreInvalid: ignore errors when rule not found + :raises: KeyError if name not found and not ignoreInvalid + :return: list of found rule names + """ + if isinstance(names, str): + names = [names] + for rule in self.__rules__: + rule.enabled = False + return self.enable(names, ignoreInvalid) + + def disable( + self, names: str | Iterable[str], ignoreInvalid: bool = False + ) -> list[str]: + """Disable rules with given names. + + :param names: name or list of rule names to enable. + :param ignoreInvalid: ignore errors when rule not found + :raises: KeyError if name not found and not ignoreInvalid + :return: list of found rule names + """ + if isinstance(names, str): + names = [names] + result = [] + for name in names: + idx = self.__find__(name) + if (idx < 0) and ignoreInvalid: + continue + if (idx < 0) and not ignoreInvalid: + raise KeyError(f"Rules manager: invalid rule name {name}") + self.__rules__[idx].enabled = False + result.append(name) + self.__cache__ = None + return result + + def getRules(self, chainName: str = "") -> list[RuleFuncTv]: + """Return array of active functions (rules) for given chain name. + It analyzes rules configuration, compiles caches if not exists and returns result. + + Default chain name is `''` (empty string). It can't be skipped. + That's done intentionally, to keep signature monomorphic for high speed. + + """ + if self.__cache__ is None: + self.__compile__() + assert self.__cache__ is not None + # Chain can be empty, if rules disabled. But we still have to return Array. + return self.__cache__.get(chainName, []) or [] + + def get_all_rules(self) -> list[str]: + """Return all available rule names.""" + return [r.name for r in self.__rules__] + + def get_active_rules(self) -> list[str]: + """Return the active rule names.""" + return [r.name for r in self.__rules__ if r.enabled] diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_block/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_block/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..2c5efa85ccf01dc6aaede7dc951e9db504c32a4a --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_block/__init__.py @@ -0,0 +1,28 @@ +__all__ = ( + "StateBlock", + "blockquote", + "code", + "fence", + "heading", + "hr", + "html_block", + "lheading", + "list_block", + "make_fence_rule", + "paragraph", + "reference", + "table", +) + +from .blockquote import blockquote +from .code import code +from .fence import fence, make_fence_rule +from .heading import heading +from .hr import hr +from .html_block import html_block +from .lheading import lheading +from .list import list_block +from .paragraph import paragraph +from .reference import reference +from .state_block import StateBlock +from .table import table diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_block/blockquote.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_block/blockquote.py new file mode 100644 index 0000000000000000000000000000000000000000..de2d4f2d2f1362de23a7a9c02fa113acf8b26be0 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_block/blockquote.py @@ -0,0 +1,368 @@ +# Block quotes +from __future__ import annotations + +import logging + +from ..common.utils import isStrSpace +from .state_block import StateBlock + +LOGGER = logging.getLogger(__name__) + + +def blockquote(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool: + LOGGER.debug( + "entering blockquote: %s, %s, %s, %s", state, startLine, endLine, silent + ) + + oldLineMax = state.lineMax + pos = state.bMarks[startLine] + state.tShift[startLine] + max = state.eMarks[startLine] + + if state.is_code_block(startLine): + return False + + # check the block quote marker + try: + if state.src[pos] != ">": + return False + except IndexError: + return False + pos += 1 + + # we know that it's going to be a valid blockquote, + # so no point trying to find the end of it in silent mode + if silent: + return True + + # set offset past spaces and ">" + initial = offset = state.sCount[startLine] + 1 + + try: + second_char: str | None = state.src[pos] + except IndexError: + second_char = None + + # skip one optional space after '>' + if second_char == " ": + # ' > test ' + # ^ -- position start of line here: + pos += 1 + initial += 1 + offset += 1 + adjustTab = False + spaceAfterMarker = True + elif second_char == "\t": + spaceAfterMarker = True + + if (state.bsCount[startLine] + offset) % 4 == 3: + # ' >\t test ' + # ^ -- position start of line here (tab has width==1) + pos += 1 + initial += 1 + offset += 1 + adjustTab = False + else: + # ' >\t test ' + # ^ -- position start of line here + shift bsCount slightly + # to make extra space appear + adjustTab = True + + else: + spaceAfterMarker = False + + oldBMarks = [state.bMarks[startLine]] + state.bMarks[startLine] = pos + + while pos < max: + ch = state.src[pos] + + if isStrSpace(ch): + if ch == "\t": + offset += ( + 4 + - (offset + state.bsCount[startLine] + (1 if adjustTab else 0)) % 4 + ) + else: + offset += 1 + + else: + break + + pos += 1 + + oldBSCount = [state.bsCount[startLine]] + state.bsCount[startLine] = ( + state.sCount[startLine] + 1 + (1 if spaceAfterMarker else 0) + ) + + lastLineEmpty = pos >= max + + oldSCount = [state.sCount[startLine]] + state.sCount[startLine] = offset - initial + + oldTShift = [state.tShift[startLine]] + state.tShift[startLine] = pos - state.bMarks[startLine] + + terminatorRules = state.md.block.ruler.getRules("blockquote") + + oldParentType = state.parentType + state.parentType = "blockquote" + + # Search the end of the block + # + # Block ends with either: + # 1. an empty line outside: + # ``` + # > test + # + # ``` + # 2. an empty line inside: + # ``` + # > + # test + # ``` + # 3. another tag: + # ``` + # > test + # - - - + # ``` + + # for (nextLine = startLine + 1; nextLine < endLine; nextLine++) { + nextLine = startLine + 1 + while nextLine < endLine: + # check if it's outdented, i.e. it's inside list item and indented + # less than said list item: + # + # ``` + # 1. anything + # > current blockquote + # 2. checking this line + # ``` + isOutdented = state.sCount[nextLine] < state.blkIndent + + pos = state.bMarks[nextLine] + state.tShift[nextLine] + max = state.eMarks[nextLine] + + if pos >= max: + # Case 1: line is not inside the blockquote, and this line is empty. + break + + evaluatesTrue = state.src[pos] == ">" and not isOutdented + pos += 1 + if evaluatesTrue: + # This line is inside the blockquote. + + # set offset past spaces and ">" + initial = offset = state.sCount[nextLine] + 1 + + try: + next_char: str | None = state.src[pos] + except IndexError: + next_char = None + + # skip one optional space after '>' + if next_char == " ": + # ' > test ' + # ^ -- position start of line here: + pos += 1 + initial += 1 + offset += 1 + adjustTab = False + spaceAfterMarker = True + elif next_char == "\t": + spaceAfterMarker = True + + if (state.bsCount[nextLine] + offset) % 4 == 3: + # ' >\t test ' + # ^ -- position start of line here (tab has width==1) + pos += 1 + initial += 1 + offset += 1 + adjustTab = False + else: + # ' >\t test ' + # ^ -- position start of line here + shift bsCount slightly + # to make extra space appear + adjustTab = True + + else: + spaceAfterMarker = False + + oldBMarks.append(state.bMarks[nextLine]) + state.bMarks[nextLine] = pos + + while pos < max: + ch = state.src[pos] + + if isStrSpace(ch): + if ch == "\t": + offset += ( + 4 + - ( + offset + + state.bsCount[nextLine] + + (1 if adjustTab else 0) + ) + % 4 + ) + else: + offset += 1 + else: + break + + pos += 1 + + lastLineEmpty = pos >= max + + oldBSCount.append(state.bsCount[nextLine]) + state.bsCount[nextLine] = ( + state.sCount[nextLine] + 1 + (1 if spaceAfterMarker else 0) + ) + + oldSCount.append(state.sCount[nextLine]) + state.sCount[nextLine] = offset - initial + + oldTShift.append(state.tShift[nextLine]) + state.tShift[nextLine] = pos - state.bMarks[nextLine] + + nextLine += 1 + continue + + # Case 2: line is not inside the blockquote, and the last line was empty. + if lastLineEmpty: + break + + # Case 3: another tag found. + terminate = False + + for terminatorRule in terminatorRules: + if terminatorRule(state, nextLine, endLine, True): + terminate = True + break + + if terminate: + # Quirk to enforce "hard termination mode" for paragraphs; + # normally if you call `tokenize(state, startLine, nextLine)`, + # paragraphs will look below nextLine for paragraph continuation, + # but if blockquote is terminated by another tag, they shouldn't + state.lineMax = nextLine + + if state.blkIndent != 0: + # state.blkIndent was non-zero, we now set it to zero, + # so we need to re-calculate all offsets to appear as + # if indent wasn't changed + oldBMarks.append(state.bMarks[nextLine]) + oldBSCount.append(state.bsCount[nextLine]) + oldTShift.append(state.tShift[nextLine]) + oldSCount.append(state.sCount[nextLine]) + state.sCount[nextLine] -= state.blkIndent + + break + + oldBMarks.append(state.bMarks[nextLine]) + oldBSCount.append(state.bsCount[nextLine]) + oldTShift.append(state.tShift[nextLine]) + oldSCount.append(state.sCount[nextLine]) + + # A negative indentation means that this is a paragraph continuation + # + state.sCount[nextLine] = -1 + + nextLine += 1 + + oldIndent = state.blkIndent + state.blkIndent = 0 + + # Detect GitHub-style alert marker on the first content line. + # Note: `startLine` here refers to the first content line of the + # blockquote, after the `>` prefix has already been stripped by the + # blockquote parser above (bMarks/tShift adjusted to skip `> `). + alert_kind = None + if state.md.options.get("alerts", False) and nextLine > startLine: + alert_kind = _detect_alert(state, startLine) + + lines = [startLine, 0] + + if alert_kind is not None: + # Emit alert tokens instead of blockquote tokens + alert_lower = alert_kind.lower() + token = state.push("alert_open", "div", 1) + token.markup = ">" + token.attrSet("class", f"markdown-alert markdown-alert-{alert_lower}") + token.map = lines + token.info = alert_kind + token.meta = {"kind": alert_kind} + + # Emit a title paragraph:

    Kind

    + token = state.push("alert_title_open", "p", 1) + token.attrSet("class", "markdown-alert-title") + title_token = state.push("inline", "", 0) + title_token.content = alert_kind.capitalize() + title_token.children = [] + token = state.push("alert_title_close", "p", -1) + + # Skip the marker line (startLine) and tokenize from startLine + 1. + contentStart = startLine + 1 + if contentStart < nextLine: + # tokenize() updates state.line to nextLine as part of its + # contract, consistent with the blockquote code path below. + state.md.block.tokenize(state, contentStart, nextLine) + else: + state.line = nextLine + + token = state.push("alert_close", "div", -1) + token.markup = ">" + else: + token = state.push("blockquote_open", "blockquote", 1) + token.markup = ">" + token.map = lines + + state.md.block.tokenize(state, startLine, nextLine) + + token = state.push("blockquote_close", "blockquote", -1) + token.markup = ">" + + state.lineMax = oldLineMax + state.parentType = oldParentType + # Update the opening token map for both alert and blockquote containers. + lines[1] = state.line + + # Restore original tShift; this might not be necessary since the parser + # has already been here, but just to make sure we can do that. + for i, item in enumerate(oldTShift): + state.bMarks[i + startLine] = oldBMarks[i] + state.tShift[i + startLine] = item + state.sCount[i + startLine] = oldSCount[i] + state.bsCount[i + startLine] = oldBSCount[i] + + state.blkIndent = oldIndent + + return True + + +_ALERT_TYPES = {"NOTE", "TIP", "IMPORTANT", "WARNING", "CAUTION"} + + +def _detect_alert(state: StateBlock, startLine: int) -> str | None: + """Detect ``[!TYPE]`` on *startLine* (after ``>`` prefix has been stripped). + + Returns the alert type string (e.g. ``"NOTE"``) or ``None``. + """ + pos = state.bMarks[startLine] + state.tShift[startLine] + maximum = state.eMarks[startLine] + src = state.src + + # Trim trailing whitespace + while maximum > pos and src[maximum - 1] in (" ", "\t"): + maximum -= 1 + + if maximum - pos < 4: + return None + if src[pos] != "[" or src[pos + 1] != "!": + return None + if src[maximum - 1] != "]": + return None + type_str = src[pos + 2 : maximum - 1].upper() + if type_str not in _ALERT_TYPES: + return None + return type_str diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_block/code.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_block/code.py new file mode 100644 index 0000000000000000000000000000000000000000..af8a41c8058b1a4887252469bdc6f20f085ad7d5 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_block/code.py @@ -0,0 +1,36 @@ +"""Code block (4 spaces padded).""" + +import logging + +from .state_block import StateBlock + +LOGGER = logging.getLogger(__name__) + + +def code(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool: + LOGGER.debug("entering code: %s, %s, %s, %s", state, startLine, endLine, silent) + + if not state.is_code_block(startLine): + return False + + last = nextLine = startLine + 1 + + while nextLine < endLine: + if state.isEmpty(nextLine): + nextLine += 1 + continue + + if state.is_code_block(nextLine): + nextLine += 1 + last = nextLine + continue + + break + + state.line = last + + token = state.push("code_block", "code", 0) + token.content = state.getLines(startLine, last, 4 + state.blkIndent, False) + "\n" + token.map = [startLine, state.line] + + return True diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_block/fence.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_block/fence.py new file mode 100644 index 0000000000000000000000000000000000000000..0d7e651eb06b73386ef447f2d19e2aa0042f97e5 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_block/fence.py @@ -0,0 +1,146 @@ +# fences (``` lang, ~~~ lang) +from __future__ import annotations + +from collections.abc import Callable +import logging + +from .state_block import StateBlock + +LOGGER = logging.getLogger(__name__) + + +def make_fence_rule( + *, + markers: tuple[str, ...] = ("~", "`"), + token_type: str = "fence", + exact_match: bool = False, + disallow_marker_in_info: tuple[str, ...] = ("`",), + min_markers: int = 3, +) -> Callable[[StateBlock, int, int, bool], bool]: + """Create a fence parsing rule with configurable options. + + :param markers: Tuple of single characters that can be used as fence markers. + :param token_type: The token type name to emit (e.g. "fence", "colon_fence"). + :param exact_match: If True, the closing fence must have exactly the same + number of marker characters as the opening fence (not "at least as many"). + This enables nesting of fences with different marker counts. + :param disallow_marker_in_info: Tuple of marker characters that are not allowed + to appear in the info string. The check only applies when the actual opening + marker is in this tuple (e.g. a tilde fence is unaffected by ``"`"`` being + listed). Per CommonMark, backtick fences cannot have backticks in the info + string. Use ``()`` to disable this restriction. + :param min_markers: Minimum number of marker characters to form a fence. + :return: A block rule function with signature + ``(state, startLine, endLine, silent) -> bool``. + """ + + closing_matcher: Callable[[int, int], bool] + if exact_match: + # closing code fence must have exactly the same number of markers as the opening one + closing_matcher = lambda opening_len, closing_len: closing_len == opening_len # noqa: E731 + else: + # closing code fence must be at least as long as the opening one + closing_matcher = lambda opening_len, closing_len: closing_len >= opening_len # noqa: E731 + + def _fence_rule( + state: StateBlock, startLine: int, endLine: int, silent: bool + ) -> bool: + LOGGER.debug( + "entering fence: %s, %s, %s, %s", state, startLine, endLine, silent + ) + + haveEndMarker = False + pos = state.bMarks[startLine] + state.tShift[startLine] + maximum = state.eMarks[startLine] + + if state.is_code_block(startLine): + return False + + if pos + min_markers > maximum: + return False + + marker = state.src[pos] + + if marker not in markers: + return False + + # scan marker length + mem = pos + pos = state.skipCharsStr(pos, marker) + + length = pos - mem + + if length < min_markers: + return False + + markup = state.src[mem:pos] + params = state.src[pos:maximum] + + if marker in disallow_marker_in_info and marker in params: + return False + + # Since start is found, we can report success here in validation mode + if silent: + return True + + # search end of block + nextLine = startLine + + while True: + nextLine += 1 + if nextLine >= endLine: + # unclosed block should be autoclosed by end of document. + # also block seems to be autoclosed by end of parent + break + + pos = mem = state.bMarks[nextLine] + state.tShift[nextLine] + maximum = state.eMarks[nextLine] + + if pos < maximum and state.sCount[nextLine] < state.blkIndent: + # non-empty line with negative indent should stop the list: + # - ``` + # test + break + + try: + if state.src[pos] != marker: + continue + except IndexError: + break + + if state.is_code_block(nextLine): + continue + + pos = state.skipCharsStr(pos, marker) + + if not closing_matcher(length, pos - mem): + continue + + # make sure tail has spaces only + pos = state.skipSpaces(pos) + + if pos < maximum: + continue + + haveEndMarker = True + # found! + break + + # If a fence has heading spaces, they should be removed from its inner block + length = state.sCount[startLine] + + state.line = nextLine + (1 if haveEndMarker else 0) + + token = state.push(token_type, "code", 0) + token.info = params + token.content = state.getLines(startLine + 1, nextLine, length, True) + token.markup = markup + token.map = [startLine, state.line] + + return True + + return _fence_rule + + +#: The default fence rule (backtick and tilde markers, CommonMark compliant). +fence = make_fence_rule() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_block/heading.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_block/heading.py new file mode 100644 index 0000000000000000000000000000000000000000..afcf9ed458124e52b9b365a6c17440872ffa0975 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_block/heading.py @@ -0,0 +1,69 @@ +"""Atex heading (#, ##, ...)""" + +from __future__ import annotations + +import logging + +from ..common.utils import isStrSpace +from .state_block import StateBlock + +LOGGER = logging.getLogger(__name__) + + +def heading(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool: + LOGGER.debug("entering heading: %s, %s, %s, %s", state, startLine, endLine, silent) + + pos = state.bMarks[startLine] + state.tShift[startLine] + maximum = state.eMarks[startLine] + + if state.is_code_block(startLine): + return False + + ch: str | None = state.src[pos] + + if ch != "#" or pos >= maximum: + return False + + # count heading level + level = 1 + pos += 1 + try: + ch = state.src[pos] + except IndexError: + ch = None + while ch == "#" and pos < maximum and level <= 6: + level += 1 + pos += 1 + try: + ch = state.src[pos] + except IndexError: + ch = None + + if level > 6 or (pos < maximum and not isStrSpace(ch)): + return False + + if silent: + return True + + # Let's cut tails like ' ### ' from the end of string + + maximum = state.skipSpacesBack(maximum, pos) + tmp = state.skipCharsStrBack(maximum, "#", pos) + if tmp > pos and isStrSpace(state.src[tmp - 1]): + maximum = tmp + + state.line = startLine + 1 + + token = state.push("heading_open", "h" + str(level), 1) + token.markup = "########"[:level] + token.map = [startLine, state.line] + + token = state.push("inline", "", 0) + token.content = state.src[pos:maximum].strip() + token.map = [startLine, state.line] + token.children = [] + + token = state.push("heading_close", "h" + str(level), -1) + token.markup = "########"[:level] + + return True diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_block/hr.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_block/hr.py new file mode 100644 index 0000000000000000000000000000000000000000..fca7d79d2780a5e656e689ac4843b8464b4ab5bd --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_block/hr.py @@ -0,0 +1,56 @@ +"""Horizontal rule + +At least 3 of these characters on a line * - _ +""" + +import logging + +from ..common.utils import isStrSpace +from .state_block import StateBlock + +LOGGER = logging.getLogger(__name__) + + +def hr(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool: + LOGGER.debug("entering hr: %s, %s, %s, %s", state, startLine, endLine, silent) + + pos = state.bMarks[startLine] + state.tShift[startLine] + maximum = state.eMarks[startLine] + + if state.is_code_block(startLine): + return False + + try: + marker = state.src[pos] + except IndexError: + return False + pos += 1 + + # Check hr marker + if marker not in ("*", "-", "_"): + return False + + # markers can be mixed with spaces, but there should be at least 3 of them + + cnt = 1 + while pos < maximum: + ch = state.src[pos] + pos += 1 + if ch != marker and not isStrSpace(ch): + return False + if ch == marker: + cnt += 1 + + if cnt < 3: + return False + + if silent: + return True + + state.line = startLine + 1 + + token = state.push("hr", "hr", 0) + token.map = [startLine, state.line] + token.markup = marker * (cnt + 1) + + return True diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_block/html_block.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_block/html_block.py new file mode 100644 index 0000000000000000000000000000000000000000..3d43f6ee1deb527a42f4d99da40bd052d9b02886 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_block/html_block.py @@ -0,0 +1,90 @@ +# HTML block +from __future__ import annotations + +import logging +import re + +from ..common.html_blocks import block_names +from ..common.html_re import HTML_OPEN_CLOSE_TAG_STR +from .state_block import StateBlock + +LOGGER = logging.getLogger(__name__) + +# An array of opening and corresponding closing sequences for html tags, +# last argument defines whether it can terminate a paragraph or not +HTML_SEQUENCES: list[tuple[re.Pattern[str], re.Pattern[str], bool]] = [ + ( + re.compile(r"^<(script|pre|style|textarea)(?=(\s|>|$))", re.IGNORECASE), + re.compile(r"<\/(script|pre|style|textarea)>", re.IGNORECASE), + True, + ), + (re.compile(r"^"), True), + (re.compile(r"^<\?"), re.compile(r"\?>"), True), + (re.compile(r"^"), True), + (re.compile(r"^"), True), + ( + re.compile("^|$))", re.IGNORECASE), + re.compile(r"^$"), + True, + ), + (re.compile(HTML_OPEN_CLOSE_TAG_STR + "\\s*$"), re.compile(r"^$"), False), +] + + +def html_block(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool: + LOGGER.debug( + "entering html_block: %s, %s, %s, %s", state, startLine, endLine, silent + ) + pos = state.bMarks[startLine] + state.tShift[startLine] + maximum = state.eMarks[startLine] + + if state.is_code_block(startLine): + return False + + if not state.md.options.get("html", None): + return False + + if state.src[pos] != "<": + return False + + lineText = state.src[pos:maximum] + + html_seq = None + for HTML_SEQUENCE in HTML_SEQUENCES: + if HTML_SEQUENCE[0].search(lineText): + html_seq = HTML_SEQUENCE + break + + if not html_seq: + return False + + if silent: + # true if this sequence can be a terminator, false otherwise + return html_seq[2] + + nextLine = startLine + 1 + + # If we are here - we detected HTML block. + # Let's roll down till block end. + if not html_seq[1].search(lineText): + while nextLine < endLine: + if state.sCount[nextLine] < state.blkIndent: + break + + pos = state.bMarks[nextLine] + state.tShift[nextLine] + maximum = state.eMarks[nextLine] + lineText = state.src[pos:maximum] + + if html_seq[1].search(lineText): + if len(lineText) != 0: + nextLine += 1 + break + nextLine += 1 + + state.line = nextLine + + token = state.push("html_block", "", 0) + token.map = [startLine, nextLine] + token.content = state.getLines(startLine, nextLine, state.blkIndent, True) + + return True diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_block/lheading.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_block/lheading.py new file mode 100644 index 0000000000000000000000000000000000000000..3522207abb680510decdd6c54d0be81401128ad7 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_block/lheading.py @@ -0,0 +1,86 @@ +# lheading (---, ==) +import logging + +from .state_block import StateBlock + +LOGGER = logging.getLogger(__name__) + + +def lheading(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool: + LOGGER.debug("entering lheading: %s, %s, %s, %s", state, startLine, endLine, silent) + + level = None + nextLine = startLine + 1 + ruler = state.md.block.ruler + terminatorRules = ruler.getRules("paragraph") + + if state.is_code_block(startLine): + return False + + oldParentType = state.parentType + state.parentType = "paragraph" # use paragraph to match terminatorRules + + # jump line-by-line until empty one or EOF + while nextLine < endLine and not state.isEmpty(nextLine): + # this would be a code block normally, but after paragraph + # it's considered a lazy continuation regardless of what's there + if state.sCount[nextLine] - state.blkIndent > 3: + nextLine += 1 + continue + + # Check for underline in setext header + if state.sCount[nextLine] >= state.blkIndent: + pos = state.bMarks[nextLine] + state.tShift[nextLine] + maximum = state.eMarks[nextLine] + + if pos < maximum: + marker = state.src[pos] + + if marker in ("-", "="): + pos = state.skipCharsStr(pos, marker) + pos = state.skipSpaces(pos) + + # /* = */ + if pos >= maximum: + level = 1 if marker == "=" else 2 + break + + # quirk for blockquotes, this line should already be checked by that rule + if state.sCount[nextLine] < 0: + nextLine += 1 + continue + + # Some tags can terminate paragraph without empty line. + terminate = False + for terminatorRule in terminatorRules: + if terminatorRule(state, nextLine, endLine, True): + terminate = True + break + if terminate: + break + + nextLine += 1 + + if not level: + # Didn't find valid underline + return False + + content = state.getLines(startLine, nextLine, state.blkIndent, False).strip() + + state.line = nextLine + 1 + + token = state.push("heading_open", "h" + str(level), 1) + token.markup = marker + token.map = [startLine, state.line] + + token = state.push("inline", "", 0) + token.content = content + token.map = [startLine, state.line - 1] + token.children = [] + + token = state.push("heading_close", "h" + str(level), -1) + token.markup = marker + + state.parentType = oldParentType + + return True diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_block/list.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_block/list.py new file mode 100644 index 0000000000000000000000000000000000000000..c8fe7af5d5d0a2a1aedb59c09c01a2f8d49b1eea --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_block/list.py @@ -0,0 +1,408 @@ +# Lists +import logging + +from ..common.utils import isStrSpace +from .state_block import StateBlock + +LOGGER = logging.getLogger(__name__) + + +# Search `[-+*][\n ]`, returns next pos after marker on success +# or -1 on fail. +def skipBulletListMarker(state: StateBlock, startLine: int) -> int: + pos = state.bMarks[startLine] + state.tShift[startLine] + maximum = state.eMarks[startLine] + + try: + marker = state.src[pos] + except IndexError: + return -1 + pos += 1 + + if marker not in ("*", "-", "+"): + return -1 + + if pos < maximum: + ch = state.src[pos] + + if not isStrSpace(ch): + # " -test " - is not a list item + return -1 + + return pos + + +# Search `\d+[.)][\n ]`, returns next pos after marker on success +# or -1 on fail. +def skipOrderedListMarker(state: StateBlock, startLine: int) -> int: + start = state.bMarks[startLine] + state.tShift[startLine] + pos = start + maximum = state.eMarks[startLine] + + # List marker should have at least 2 chars (digit + dot) + if pos + 1 >= maximum: + return -1 + + ch = state.src[pos] + pos += 1 + + ch_ord = ord(ch) + # /* 0 */ /* 9 */ + if ch_ord < 0x30 or ch_ord > 0x39: + return -1 + + while True: + # EOL -> fail + if pos >= maximum: + return -1 + + ch = state.src[pos] + pos += 1 + + # /* 0 */ /* 9 */ + ch_ord = ord(ch) + if ch_ord >= 0x30 and ch_ord <= 0x39: + # List marker should have no more than 9 digits + # (prevents integer overflow in browsers) + if pos - start >= 10: + return -1 + + continue + + # found valid marker + if ch in (")", "."): + break + + return -1 + + if pos < maximum: + ch = state.src[pos] + + if not isStrSpace(ch): + # " 1.test " - is not a list item + return -1 + + return pos + + +def markTightParagraphs(state: StateBlock, idx: int) -> None: + level = state.level + 2 + + i = idx + 2 + length = len(state.tokens) - 2 + while i < length: + if state.tokens[i].level == level and state.tokens[i].type == "paragraph_open": + state.tokens[i + 2].hidden = True + state.tokens[i].hidden = True + i += 2 + i += 1 + + +def list_block(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool: + LOGGER.debug("entering list: %s, %s, %s, %s", state, startLine, endLine, silent) + + isTerminatingParagraph = False + tight = True + + if state.is_code_block(startLine): + return False + + # Special case: + # - item 1 + # - item 2 + # - item 3 + # - item 4 + # - this one is a paragraph continuation + if ( + state.listIndent >= 0 + and state.sCount[startLine] - state.listIndent >= 4 + and state.sCount[startLine] < state.blkIndent + ): + return False + + # limit conditions when list can interrupt + # a paragraph (validation mode only) + # Next list item should still terminate previous list item + # + # This code can fail if plugins use blkIndent as well as lists, + # but I hope the spec gets fixed long before that happens. + # + if ( + silent + and state.parentType == "paragraph" + and state.sCount[startLine] >= state.blkIndent + ): + isTerminatingParagraph = True + + # Detect list type and position after marker + posAfterMarker = skipOrderedListMarker(state, startLine) + if posAfterMarker >= 0: + isOrdered = True + start = state.bMarks[startLine] + state.tShift[startLine] + markerValue = int(state.src[start : posAfterMarker - 1]) + + # If we're starting a new ordered list right after + # a paragraph, it should start with 1. + if isTerminatingParagraph and markerValue != 1: + return False + else: + posAfterMarker = skipBulletListMarker(state, startLine) + if posAfterMarker >= 0: + isOrdered = False + else: + return False + + # If we're starting a new unordered list right after + # a paragraph, first line should not be empty. + if ( + isTerminatingParagraph + and state.skipSpaces(posAfterMarker) >= state.eMarks[startLine] + ): + return False + + # We should terminate list on style change. Remember first one to compare. + markerChar = state.src[posAfterMarker - 1] + + # For validation mode we can terminate immediately + if silent: + return True + + # Start list + listTokIdx = len(state.tokens) + + if isOrdered: + token = state.push("ordered_list_open", "ol", 1) + if markerValue != 1: + token.attrs = {"start": markerValue} + + else: + token = state.push("bullet_list_open", "ul", 1) + + token.map = listLines = [startLine, 0] + token.markup = markerChar + + # + # Iterate list items + # + + nextLine = startLine + prevEmptyEnd = False + terminatorRules = state.md.block.ruler.getRules("list") + + oldParentType = state.parentType + state.parentType = "list" + + while nextLine < endLine: + pos = posAfterMarker + maximum = state.eMarks[nextLine] + + initial = offset = ( + state.sCount[nextLine] + + posAfterMarker + - (state.bMarks[startLine] + state.tShift[startLine]) + ) + + while pos < maximum: + ch = state.src[pos] + + if ch == "\t": + offset += 4 - (offset + state.bsCount[nextLine]) % 4 + elif ch == " ": + offset += 1 + else: + break + + pos += 1 + + contentStart = pos + + # trimming space in "- \n 3" case, indent is 1 here + indentAfterMarker = 1 if contentStart >= maximum else offset - initial + + # If we have more than 4 spaces, the indent is 1 + # (the rest is just indented code block) + if indentAfterMarker > 4: + indentAfterMarker = 1 + + # " - test" + # ^^^^^ - calculating total length of this thing + indent = initial + indentAfterMarker + + # Run subparser & write tokens + token = state.push("list_item_open", "li", 1) + token.markup = markerChar + token.map = itemLines = [startLine, 0] + if isOrdered: + token.info = state.src[start : posAfterMarker - 1] + + # Detect GFM task checkbox: `[ ] ` or `[x] `/`[X] ` at content start + checkboxLen = 0 + if state.md.options.get("tasklists", False) and contentStart < maximum: + checked = _detect_task_checkbox(state.src, contentStart, maximum) + if checked is not None: + token.meta = {"checked": checked} + # Advance content past the checkbox: `[x]` (3 chars) + whitespace. + # `_detect_task_checkbox` already guarantees a whitespace char at + # pos+3, so we always consume 4 characters. + checkboxLen = 4 + + # change current state, then restore it after parser subcall + oldTight = state.tight + oldBMark = state.bMarks[startLine] + oldTShift = state.tShift[startLine] + oldSCount = state.sCount[startLine] + + # - example list + # ^ listIndent position will be here + # ^ blkIndent position will be here + # + oldListIndent = state.listIndent + state.listIndent = state.blkIndent + state.blkIndent = indent + + state.tight = True + state.tShift[startLine] = contentStart - state.bMarks[startLine] + state.sCount[startLine] = offset + + # If we detected a checkbox, advance bMarks past it so that + # getLines() doesn't include the checkbox text in the content. + if checkboxLen: + state.bMarks[startLine] = contentStart + checkboxLen + state.tShift[startLine] = 0 + + if contentStart >= maximum and state.isEmpty(startLine + 1): + # workaround for this case + # (list item is empty, list terminates before "foo"): + # ~~~~~~~~ + # - + # + # foo + # ~~~~~~~~ + state.line = min(state.line + 2, endLine) + else: + # NOTE in list.js this was: + # state.md.block.tokenize(state, startLine, endLine, True) + # but tokeniz does not take the final parameter + state.md.block.tokenize(state, startLine, endLine) + + # If any of list item is tight, mark list as tight + if (not state.tight) or prevEmptyEnd: + tight = False + + # Item become loose if finish with empty line, + # but we should filter last element, because it means list finish + prevEmptyEnd = (state.line - startLine) > 1 and state.isEmpty(state.line - 1) + + state.blkIndent = state.listIndent + state.listIndent = oldListIndent + if checkboxLen: + state.bMarks[startLine] = oldBMark + state.tShift[startLine] = oldTShift + state.sCount[startLine] = oldSCount + state.tight = oldTight + + token = state.push("list_item_close", "li", -1) + token.markup = markerChar + + nextLine = startLine = state.line + itemLines[1] = nextLine + + if nextLine >= endLine: + break + + contentStart = state.bMarks[startLine] + + # + # Try to check if list is terminated or continued. + # + if state.sCount[nextLine] < state.blkIndent: + break + + if state.is_code_block(startLine): + break + + # fail if terminating block found + terminate = False + for terminatorRule in terminatorRules: + if terminatorRule(state, nextLine, endLine, True): + terminate = True + break + + if terminate: + break + + # fail if list has another type + if isOrdered: + posAfterMarker = skipOrderedListMarker(state, nextLine) + if posAfterMarker < 0: + break + start = state.bMarks[nextLine] + state.tShift[nextLine] + else: + posAfterMarker = skipBulletListMarker(state, nextLine) + if posAfterMarker < 0: + break + + if markerChar != state.src[posAfterMarker - 1]: + break + + # Finalize list + + # If any direct list item has a task checkbox, add class to the list + if state.md.options.get("tasklists", False): + containsTask = False + level = state.tokens[listTokIdx].level + for j in range(listTokIdx + 1, len(state.tokens)): + tok = state.tokens[j] + if ( + tok.level == level + 1 + and tok.type == "list_item_open" + and tok.meta + and "checked" in tok.meta + ): + tok.attrJoin("class", "task-list-item") + containsTask = True + if containsTask: + state.tokens[listTokIdx].attrJoin("class", "contains-task-list") + + if isOrdered: + token = state.push("ordered_list_close", "ol", -1) + else: + token = state.push("bullet_list_close", "ul", -1) + + token.markup = markerChar + + listLines[1] = nextLine + state.line = nextLine + + state.parentType = oldParentType + + # mark paragraphs tight if needed + if tight: + markTightParagraphs(state, listTokIdx) + + return True + + +def _detect_task_checkbox(src: str, pos: int, maximum: int) -> bool | None: + """Detect ``[ ]``, ``[x]``, or ``[X]`` at *pos*, followed by whitespace. + + Returns ``True`` (checked), ``False`` (unchecked), or ``None`` (no match). + """ + # Need at least 4 chars: `[`, char, `]`, whitespace + if pos + 4 > maximum: + return None + if src[pos] != "[": + return None + inner = src[pos + 1] + if src[pos + 2] != "]": + return None + if inner == " ": + checked = False + elif inner in ("x", "X"): + checked = True + else: + return None + # After `]`, must have whitespace + if src[pos + 3] not in (" ", "\t"): + return None + return checked diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_block/paragraph.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_block/paragraph.py new file mode 100644 index 0000000000000000000000000000000000000000..30ba877799beb764dc0a603caa21fe6e6b641375 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_block/paragraph.py @@ -0,0 +1,66 @@ +"""Paragraph.""" + +import logging + +from .state_block import StateBlock + +LOGGER = logging.getLogger(__name__) + + +def paragraph(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool: + LOGGER.debug( + "entering paragraph: %s, %s, %s, %s", state, startLine, endLine, silent + ) + + nextLine = startLine + 1 + ruler = state.md.block.ruler + terminatorRules = ruler.getRules("paragraph") + endLine = state.lineMax + + oldParentType = state.parentType + state.parentType = "paragraph" + + # jump line-by-line until empty one or EOF + while nextLine < endLine: + if state.isEmpty(nextLine): + break + # this would be a code block normally, but after paragraph + # it's considered a lazy continuation regardless of what's there + if state.sCount[nextLine] - state.blkIndent > 3: + nextLine += 1 + continue + + # quirk for blockquotes, this line should already be checked by that rule + if state.sCount[nextLine] < 0: + nextLine += 1 + continue + + # Some tags can terminate paragraph without empty line. + terminate = False + for terminatorRule in terminatorRules: + if terminatorRule(state, nextLine, endLine, True): + terminate = True + break + + if terminate: + break + + nextLine += 1 + + content = state.getLines(startLine, nextLine, state.blkIndent, False).strip() + + state.line = nextLine + + token = state.push("paragraph_open", "p", 1) + token.map = [startLine, state.line] + + token = state.push("inline", "", 0) + token.content = content + token.map = [startLine, state.line] + token.children = [] + + token = state.push("paragraph_close", "p", -1) + + state.parentType = oldParentType + + return True diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_block/reference.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_block/reference.py new file mode 100644 index 0000000000000000000000000000000000000000..ad94d40941ee7cd43c7d3873ac64276a47c15d8b --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_block/reference.py @@ -0,0 +1,235 @@ +import logging + +from ..common.utils import charCodeAt, isSpace, normalizeReference +from .state_block import StateBlock + +LOGGER = logging.getLogger(__name__) + + +def reference(state: StateBlock, startLine: int, _endLine: int, silent: bool) -> bool: + LOGGER.debug( + "entering reference: %s, %s, %s, %s", state, startLine, _endLine, silent + ) + + pos = state.bMarks[startLine] + state.tShift[startLine] + maximum = state.eMarks[startLine] + nextLine = startLine + 1 + + if state.is_code_block(startLine): + return False + + if state.src[pos] != "[": + return False + + string = state.src[pos : maximum + 1] + + # string = state.getLines(startLine, nextLine, state.blkIndent, False).strip() + maximum = len(string) + + labelEnd = None + pos = 1 + while pos < maximum: + ch = charCodeAt(string, pos) + if ch == 0x5B: # /* [ */ + return False + elif ch == 0x5D: # /* ] */ + labelEnd = pos + break + elif ch == 0x0A: # /* \n */ + if (lineContent := getNextLine(state, nextLine)) is not None: + string += lineContent + maximum = len(string) + nextLine += 1 + elif ch == 0x5C: # /* \ */ + pos += 1 + if ( + pos < maximum + and charCodeAt(string, pos) == 0x0A + and (lineContent := getNextLine(state, nextLine)) is not None + ): + string += lineContent + maximum = len(string) + nextLine += 1 + pos += 1 + + if ( + labelEnd is None or labelEnd < 0 or charCodeAt(string, labelEnd + 1) != 0x3A + ): # /* : */ + return False + + # [label]: destination 'title' + # ^^^ skip optional whitespace here + pos = labelEnd + 2 + while pos < maximum: + ch = charCodeAt(string, pos) + if ch == 0x0A: + if (lineContent := getNextLine(state, nextLine)) is not None: + string += lineContent + maximum = len(string) + nextLine += 1 + elif isSpace(ch): + pass + else: + break + pos += 1 + + # [label]: destination 'title' + # ^^^^^^^^^^^ parse this + destRes = state.md.helpers.parseLinkDestination(string, pos, maximum) + if not destRes.ok: + return False + + href = state.md.normalizeLink(destRes.str) + if not state.md.validateLink(href): + return False + + pos = destRes.pos + + # save cursor state, we could require to rollback later + destEndPos = pos + destEndLineNo = nextLine + + # [label]: destination 'title' + # ^^^ skipping those spaces + start = pos + while pos < maximum: + ch = charCodeAt(string, pos) + if ch == 0x0A: + if (lineContent := getNextLine(state, nextLine)) is not None: + string += lineContent + maximum = len(string) + nextLine += 1 + elif isSpace(ch): + pass + else: + break + pos += 1 + + # [label]: destination 'title' + # ^^^^^^^ parse this + titleRes = state.md.helpers.parseLinkTitle(string, pos, maximum, None) + while titleRes.can_continue: + if (lineContent := getNextLine(state, nextLine)) is None: + break + string += lineContent + pos = maximum + maximum = len(string) + nextLine += 1 + titleRes = state.md.helpers.parseLinkTitle(string, pos, maximum, titleRes) + + if pos < maximum and start != pos and titleRes.ok: + title = titleRes.str + pos = titleRes.pos + else: + title = "" + pos = destEndPos + nextLine = destEndLineNo + + # skip trailing spaces until the rest of the line + while pos < maximum: + ch = charCodeAt(string, pos) + if not isSpace(ch): + break + pos += 1 + + if pos < maximum and charCodeAt(string, pos) != 0x0A and title: + # garbage at the end of the line after title, + # but it could still be a valid reference if we roll back + title = "" + pos = destEndPos + nextLine = destEndLineNo + while pos < maximum: + ch = charCodeAt(string, pos) + if not isSpace(ch): + break + pos += 1 + + if pos < maximum and charCodeAt(string, pos) != 0x0A: + # garbage at the end of the line + return False + + label = normalizeReference(string[1:labelEnd]) + if not label: + # CommonMark 0.20 disallows empty labels + return False + + # Reference can not terminate anything. This check is for safety only. + if silent: + return True + + if "references" not in state.env: + state.env["references"] = {} + + state.line = nextLine + + # note, this is not part of markdown-it JS, but is useful for renderers + if state.md.options.get("inline_definitions", False): + token = state.push("definition", "", 0) + token.meta = { + "id": label, + "title": title, + "url": href, + "label": string[1:labelEnd], + } + token.map = [startLine, state.line] + + if label not in state.env["references"]: + state.env["references"][label] = { + "title": title, + "href": href, + "map": [startLine, state.line], + } + else: + state.env.setdefault("duplicate_refs", []).append( + { + "title": title, + "href": href, + "label": label, + "map": [startLine, state.line], + } + ) + + return True + + +def getNextLine(state: StateBlock, nextLine: int) -> None | str: + endLine = state.lineMax + + if nextLine >= endLine or state.isEmpty(nextLine): + # empty line or end of input + return None + + isContinuation = False + + # this would be a code block normally, but after paragraph + # it's considered a lazy continuation regardless of what's there + if state.is_code_block(nextLine): + isContinuation = True + + # quirk for blockquotes, this line should already be checked by that rule + if state.sCount[nextLine] < 0: + isContinuation = True + + if not isContinuation: + terminatorRules = state.md.block.ruler.getRules("reference") + oldParentType = state.parentType + state.parentType = "reference" + + # Some tags can terminate paragraph without empty line. + terminate = False + for terminatorRule in terminatorRules: + if terminatorRule(state, nextLine, endLine, True): + terminate = True + break + + state.parentType = oldParentType + + if terminate: + # terminated by another block + return None + + pos = state.bMarks[nextLine] + state.tShift[nextLine] + maximum = state.eMarks[nextLine] + + # max + 1 explicitly includes the newline + return state.src[pos : maximum + 1] diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_block/state_block.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_block/state_block.py new file mode 100644 index 0000000000000000000000000000000000000000..445ad265a01e3f1dededf9f72848686a2b5ee901 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_block/state_block.py @@ -0,0 +1,261 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Literal + +from ..common.utils import isStrSpace +from ..ruler import StateBase +from ..token import Token +from ..utils import EnvType + +if TYPE_CHECKING: + from markdown_it.main import MarkdownIt + + +class StateBlock(StateBase): + def __init__( + self, src: str, md: MarkdownIt, env: EnvType, tokens: list[Token] + ) -> None: + self.src = src + + # link to parser instance + self.md = md + + self.env = env + + # + # Internal state variables + # + + self.tokens = tokens + + self.bMarks: list[int] = [] # line begin offsets for fast jumps + self.eMarks: list[int] = [] # line end offsets for fast jumps + # offsets of the first non-space characters (tabs not expanded) + self.tShift: list[int] = [] + self.sCount: list[int] = [] # indents for each line (tabs expanded) + + # An amount of virtual spaces (tabs expanded) between beginning + # of each line (bMarks) and real beginning of that line. + # + # It exists only as a hack because blockquotes override bMarks + # losing information in the process. + # + # It's used only when expanding tabs, you can think about it as + # an initial tab length, e.g. bsCount=21 applied to string `\t123` + # means first tab should be expanded to 4-21%4 === 3 spaces. + # + self.bsCount: list[int] = [] + + # block parser variables + self.blkIndent = 0 # required block content indent (for example, if we are + # inside a list, it would be positioned after list marker) + self.line = 0 # line index in src + self.lineMax = 0 # lines count + self.tight = False # loose/tight mode for lists + self.ddIndent = -1 # indent of the current dd block (-1 if there isn't any) + self.listIndent = -1 # indent of the current list block (-1 if there isn't any) + + # can be 'blockquote', 'list', 'root', 'paragraph' or 'reference' + # used in lists to determine if they interrupt a paragraph + self.parentType = "root" + + self.level = 0 + + # renderer + self.result = "" + + # Create caches + # Generate markers. + indent_found = False + + start = pos = indent = offset = 0 + length = len(self.src) + + for pos, character in enumerate(self.src): + if not indent_found: + if isStrSpace(character): + indent += 1 + + if character == "\t": + offset += 4 - offset % 4 + else: + offset += 1 + continue + else: + indent_found = True + + if character == "\n" or pos == length - 1: + if character != "\n": + pos += 1 + self.bMarks.append(start) + self.eMarks.append(pos) + self.tShift.append(indent) + self.sCount.append(offset) + self.bsCount.append(0) + + indent_found = False + indent = 0 + offset = 0 + start = pos + 1 + + # Push fake entry to simplify cache bounds checks + self.bMarks.append(length) + self.eMarks.append(length) + self.tShift.append(0) + self.sCount.append(0) + self.bsCount.append(0) + + self.lineMax = len(self.bMarks) - 1 # don't count last fake line + + # pre-check if code blocks are enabled, to speed up is_code_block method + self._code_enabled = "code" in self.md["block"].ruler.get_active_rules() + + def __repr__(self) -> str: + return ( + f"{self.__class__.__name__}" + f"(line={self.line},level={self.level},tokens={len(self.tokens)})" + ) + + def push(self, ttype: str, tag: str, nesting: Literal[-1, 0, 1]) -> Token: + """Push new token to "stream".""" + token = Token(ttype, tag, nesting) + token.block = True + if nesting < 0: + self.level -= 1 # closing tag + token.level = self.level + if nesting > 0: + self.level += 1 # opening tag + self.tokens.append(token) + return token + + def isEmpty(self, line: int) -> bool: + """.""" + return (self.bMarks[line] + self.tShift[line]) >= self.eMarks[line] + + def skipEmptyLines(self, from_pos: int) -> int: + """.""" + while from_pos < self.lineMax: + try: + if (self.bMarks[from_pos] + self.tShift[from_pos]) < self.eMarks[ + from_pos + ]: + break + except IndexError: + pass + from_pos += 1 + return from_pos + + def skipSpaces(self, pos: int) -> int: + """Skip spaces from given position.""" + while True: + try: + current = self.src[pos] + except IndexError: + break + if not isStrSpace(current): + break + pos += 1 + return pos + + def skipSpacesBack(self, pos: int, minimum: int) -> int: + """Skip spaces from given position in reverse.""" + if pos <= minimum: + return pos + while pos > minimum: + pos -= 1 + if not isStrSpace(self.src[pos]): + return pos + 1 + return pos + + def skipChars(self, pos: int, code: int) -> int: + """Skip character code from given position.""" + while True: + try: + current = self.srcCharCode[pos] + except IndexError: + break + if current != code: + break + pos += 1 + return pos + + def skipCharsStr(self, pos: int, ch: str) -> int: + """Skip character string from given position.""" + while True: + try: + current = self.src[pos] + except IndexError: + break + if current != ch: + break + pos += 1 + return pos + + def skipCharsBack(self, pos: int, code: int, minimum: int) -> int: + """Skip character code reverse from given position - 1.""" + if pos <= minimum: + return pos + while pos > minimum: + pos -= 1 + if code != self.srcCharCode[pos]: + return pos + 1 + return pos + + def skipCharsStrBack(self, pos: int, ch: str, minimum: int) -> int: + """Skip character string reverse from given position - 1.""" + if pos <= minimum: + return pos + while pos > minimum: + pos -= 1 + if ch != self.src[pos]: + return pos + 1 + return pos + + def getLines(self, begin: int, end: int, indent: int, keepLastLF: bool) -> str: + """Cut lines range from source.""" + line = begin + if begin >= end: + return "" + + queue = [""] * (end - begin) + + i = 1 + while line < end: + lineIndent = 0 + lineStart = first = self.bMarks[line] + last = ( + self.eMarks[line] + 1 + if line + 1 < end or keepLastLF + else self.eMarks[line] + ) + + while (first < last) and (lineIndent < indent): + ch = self.src[first] + if isStrSpace(ch): + if ch == "\t": + lineIndent += 4 - (lineIndent + self.bsCount[line]) % 4 + else: + lineIndent += 1 + elif first - lineStart < self.tShift[line]: + lineIndent += 1 + else: + break + first += 1 + + if lineIndent > indent: + # partially expanding tabs in code blocks, e.g '\t\tfoobar' + # with indent=2 becomes ' \tfoobar' + queue[i - 1] = (" " * (lineIndent - indent)) + self.src[first:last] + else: + queue[i - 1] = self.src[first:last] + + line += 1 + i += 1 + + return "".join(queue) + + def is_code_block(self, line: int) -> bool: + """Check if line is a code block, + i.e. the code block rule is enabled and text is indented by more than 3 spaces. + """ + return self._code_enabled and (self.sCount[line] - self.blkIndent) >= 4 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_block/table.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_block/table.py new file mode 100644 index 0000000000000000000000000000000000000000..c52553d8c21265df65e23be253686ef00b3297eb --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_block/table.py @@ -0,0 +1,250 @@ +# GFM table, https://github.github.com/gfm/#tables-extension- +from __future__ import annotations + +import re + +from ..common.utils import charStrAt, isStrSpace +from .state_block import StateBlock + +headerLineRe = re.compile(r"^:?-+:?$") +enclosingPipesRe = re.compile(r"^\||\|$") + +# Limit the amount of empty autocompleted cells in a table, +# see https://github.com/markdown-it/markdown-it/issues/1000, +# Both pulldown-cmark and commonmark-hs limit the number of cells this way to ~200k. +# We set it to 65k, which can expand user input by a factor of x370 +# (256x256 square is 1.8kB expanded into 650kB). +MAX_AUTOCOMPLETED_CELLS = 0x10000 + + +def getLine(state: StateBlock, line: int) -> str: + pos = state.bMarks[line] + state.tShift[line] + maximum = state.eMarks[line] + + # return state.src.substr(pos, max - pos) + return state.src[pos:maximum] + + +def escapedSplit(string: str) -> list[str]: + result: list[str] = [] + pos = 0 + max = len(string) + isEscaped = False + lastPos = 0 + current = "" + ch = charStrAt(string, pos) + + while pos < max: + if ch == "|": + if not isEscaped: + # pipe separating cells, '|' + result.append(current + string[lastPos:pos]) + current = "" + lastPos = pos + 1 + else: + # escaped pipe, '\|' + current += string[lastPos : pos - 1] + lastPos = pos + + isEscaped = ch == "\\" + pos += 1 + + ch = charStrAt(string, pos) + + result.append(current + string[lastPos:]) + + return result + + +def table(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool: + tbodyLines = None + + # should have at least two lines + if startLine + 2 > endLine: + return False + + nextLine = startLine + 1 + + if state.sCount[nextLine] < state.blkIndent: + return False + + if state.is_code_block(nextLine): + return False + + # first character of the second line should be '|', '-', ':', + # and no other characters are allowed but spaces; + # basically, this is the equivalent of /^[-:|][-:|\s]*$/ regexp + + pos = state.bMarks[nextLine] + state.tShift[nextLine] + if pos >= state.eMarks[nextLine]: + return False + first_ch = state.src[pos] + pos += 1 + if first_ch not in ("|", "-", ":"): + return False + + if pos >= state.eMarks[nextLine]: + return False + second_ch = state.src[pos] + pos += 1 + if second_ch not in ("|", "-", ":") and not isStrSpace(second_ch): + return False + + # if first character is '-', then second character must not be a space + # (due to parsing ambiguity with list) + if first_ch == "-" and isStrSpace(second_ch): + return False + + while pos < state.eMarks[nextLine]: + ch = state.src[pos] + + if ch not in ("|", "-", ":") and not isStrSpace(ch): + return False + + pos += 1 + + lineText = getLine(state, startLine + 1) + + columns = lineText.split("|") + aligns = [] + for i in range(len(columns)): + t = columns[i].strip() + if not t: + # allow empty columns before and after table, but not in between columns; + # e.g. allow ` |---| `, disallow ` ---||--- ` + if i == 0 or i == len(columns) - 1: + continue + else: + return False + + if not headerLineRe.search(t): + return False + if charStrAt(t, len(t) - 1) == ":": + aligns.append("center" if charStrAt(t, 0) == ":" else "right") + elif charStrAt(t, 0) == ":": + aligns.append("left") + else: + aligns.append("") + + lineText = getLine(state, startLine).strip() + if "|" not in lineText: + return False + if state.is_code_block(startLine): + return False + columns = escapedSplit(lineText) + if columns and columns[0] == "": + columns.pop(0) + if columns and columns[-1] == "": + columns.pop() + + # header row will define an amount of columns in the entire table, + # and align row should be exactly the same (the rest of the rows can differ) + columnCount = len(columns) + if columnCount == 0 or columnCount != len(aligns): + return False + + if silent: + return True + + oldParentType = state.parentType + state.parentType = "table" + + # use 'blockquote' lists for termination because it's + # the most similar to tables + terminatorRules = state.md.block.ruler.getRules("blockquote") + + token = state.push("table_open", "table", 1) + token.map = tableLines = [startLine, 0] + + token = state.push("thead_open", "thead", 1) + token.map = [startLine, startLine + 1] + + token = state.push("tr_open", "tr", 1) + token.map = [startLine, startLine + 1] + + for i in range(len(columns)): + token = state.push("th_open", "th", 1) + if aligns[i]: + token.attrs = {"style": "text-align:" + aligns[i]} + + token = state.push("inline", "", 0) + # note in markdown-it this map was removed in v12.0.0 however, we keep it, + # since it is helpful to propagate to children tokens + token.map = [startLine, startLine + 1] + token.content = columns[i].strip() + token.children = [] + + token = state.push("th_close", "th", -1) + + token = state.push("tr_close", "tr", -1) + token = state.push("thead_close", "thead", -1) + + autocompleted_cells = 0 + nextLine = startLine + 2 + while nextLine < endLine: + if state.sCount[nextLine] < state.blkIndent: + break + + terminate = False + for i in range(len(terminatorRules)): + if terminatorRules[i](state, nextLine, endLine, True): + terminate = True + break + + if terminate: + break + lineText = getLine(state, nextLine).strip() + if not lineText: + break + if state.is_code_block(nextLine): + break + columns = escapedSplit(lineText) + if columns and columns[0] == "": + columns.pop(0) + if columns and columns[-1] == "": + columns.pop() + + # note: autocomplete count can be negative if user specifies more columns than header, + # but that does not affect intended use (which is limiting expansion) + autocompleted_cells += columnCount - len(columns) + if autocompleted_cells > MAX_AUTOCOMPLETED_CELLS: + break + + if nextLine == startLine + 2: + token = state.push("tbody_open", "tbody", 1) + token.map = tbodyLines = [startLine + 2, 0] + + token = state.push("tr_open", "tr", 1) + token.map = [nextLine, nextLine + 1] + + for i in range(columnCount): + token = state.push("td_open", "td", 1) + if aligns[i]: + token.attrs = {"style": "text-align:" + aligns[i]} + + token = state.push("inline", "", 0) + # note in markdown-it this map was removed in v12.0.0 however, we keep it, + # since it is helpful to propagate to children tokens + token.map = [nextLine, nextLine + 1] + try: + token.content = columns[i].strip() if columns[i] else "" + except IndexError: + token.content = "" + token.children = [] + + token = state.push("td_close", "td", -1) + + token = state.push("tr_close", "tr", -1) + + nextLine += 1 + + if tbodyLines: + token = state.push("tbody_close", "tbody", -1) + tbodyLines[1] = nextLine + + token = state.push("table_close", "table", -1) + + tableLines[1] = nextLine + state.parentType = oldParentType + state.line = nextLine + return True diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_core/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_core/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e7d775363c6e1d454e73a7e3fff9af3115be4339 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_core/__init__.py @@ -0,0 +1,19 @@ +__all__ = ( + "StateCore", + "block", + "inline", + "linkify", + "normalize", + "replace", + "smartquotes", + "text_join", +) + +from .block import block +from .inline import inline +from .linkify import linkify +from .normalize import normalize +from .replacements import replace +from .smartquotes import smartquotes +from .state_core import StateCore +from .text_join import text_join diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_core/block.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_core/block.py new file mode 100644 index 0000000000000000000000000000000000000000..a6c3bb8d7ae18880fd638690fb5b09beb78b103c --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_core/block.py @@ -0,0 +1,13 @@ +from ..token import Token +from .state_core import StateCore + + +def block(state: StateCore) -> None: + if state.inlineMode: + token = Token("inline", "", 0) + token.content = state.src + token.map = [0, 1] + token.children = [] + state.tokens.append(token) + else: + state.md.block.parse(state.src, state.md, state.env, state.tokens) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_core/inline.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_core/inline.py new file mode 100644 index 0000000000000000000000000000000000000000..c3fd0b5e25dda5d8a5a644cc9e460d0f92ae2d1d --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_core/inline.py @@ -0,0 +1,10 @@ +from .state_core import StateCore + + +def inline(state: StateCore) -> None: + """Parse inlines""" + for token in state.tokens: + if token.type == "inline": + if token.children is None: + token.children = [] + state.md.inline.parse(token.content, state.md, state.env, token.children) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_core/linkify.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_core/linkify.py new file mode 100644 index 0000000000000000000000000000000000000000..efbc9d4c9b1cbada1c936401b3421d73fbff5b64 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_core/linkify.py @@ -0,0 +1,149 @@ +from __future__ import annotations + +import re +from typing import Protocol + +from ..common.utils import arrayReplaceAt, isLinkClose, isLinkOpen +from ..token import Token +from .state_core import StateCore + +HTTP_RE = re.compile(r"^http://") +MAILTO_RE = re.compile(r"^mailto:") +TEST_MAILTO_RE = re.compile(r"^mailto:", flags=re.IGNORECASE) + + +def linkify(state: StateCore) -> None: + """Rule for identifying plain-text links.""" + if not state.md.options.linkify: + return + + if not state.md.linkify: + raise ModuleNotFoundError("Linkify enabled but not installed.") + + for inline_token in state.tokens: + if inline_token.type != "inline" or not state.md.linkify.pretest( + inline_token.content + ): + continue + + tokens = inline_token.children + + htmlLinkLevel = 0 + + # We scan from the end, to keep position when new tags added. + # Use reversed logic in links start/end match + assert tokens is not None + i = len(tokens) + while i >= 1: + i -= 1 + assert isinstance(tokens, list) + currentToken = tokens[i] + + # Skip content of markdown links + if currentToken.type == "link_close": + i -= 1 + while ( + tokens[i].level != currentToken.level + and tokens[i].type != "link_open" + ): + i -= 1 + continue + + # Skip content of html tag links + if currentToken.type == "html_inline": + if isLinkOpen(currentToken.content) and htmlLinkLevel > 0: + htmlLinkLevel -= 1 + if isLinkClose(currentToken.content): + htmlLinkLevel += 1 + if htmlLinkLevel > 0: + continue + + if currentToken.type == "text" and state.md.linkify.test( + currentToken.content + ): + text = currentToken.content + links: list[_LinkType] = state.md.linkify.match(text) or [] + + # Now split string to nodes + nodes = [] + level = currentToken.level + lastPos = 0 + + # forbid escape sequence at the start of the string, + # this avoids http\://example.com/ from being linkified as + # http://example.com/ + if ( + links + and links[0].index == 0 + and i > 0 + and tokens[i - 1].type == "text_special" + ): + links = links[1:] + + for link in links: + url = link.url + fullUrl = state.md.normalizeLink(url) + if not state.md.validateLink(fullUrl): + continue + + urlText = link.text + + # Linkifier might send raw hostnames like "example.com", where url + # starts with domain name. So we prepend http:// in those cases, + # and remove it afterwards. + if not link.schema: + urlText = HTTP_RE.sub( + "", state.md.normalizeLinkText("http://" + urlText) + ) + elif link.schema == "mailto:" and TEST_MAILTO_RE.search(urlText): + urlText = MAILTO_RE.sub( + "", state.md.normalizeLinkText("mailto:" + urlText) + ) + else: + urlText = state.md.normalizeLinkText(urlText) + + pos = link.index + + if pos > lastPos: + token = Token("text", "", 0) + token.content = text[lastPos:pos] + token.level = level + nodes.append(token) + + token = Token("link_open", "a", 1) + token.attrs = {"href": fullUrl} + token.level = level + level += 1 + token.markup = "linkify" + token.info = "auto" + nodes.append(token) + + token = Token("text", "", 0) + token.content = urlText + token.level = level + nodes.append(token) + + token = Token("link_close", "a", -1) + level -= 1 + token.level = level + token.markup = "linkify" + token.info = "auto" + nodes.append(token) + + lastPos = link.last_index + + if lastPos < len(text): + token = Token("text", "", 0) + token.content = text[lastPos:] + token.level = level + nodes.append(token) + + inline_token.children = tokens = arrayReplaceAt(tokens, i, nodes) + + +class _LinkType(Protocol): + url: str + text: str + index: int + last_index: int + schema: str | None diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_core/normalize.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_core/normalize.py new file mode 100644 index 0000000000000000000000000000000000000000..32439243ef6ebfa424d202ed63635983d4c9ea82 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_core/normalize.py @@ -0,0 +1,19 @@ +"""Normalize input string.""" + +import re + +from .state_core import StateCore + +# https://spec.commonmark.org/0.29/#line-ending +NEWLINES_RE = re.compile(r"\r\n?|\n") +NULL_RE = re.compile(r"\0") + + +def normalize(state: StateCore) -> None: + # Normalize newlines + string = NEWLINES_RE.sub("\n", state.src) + + # Replace NULL characters + string = NULL_RE.sub("\ufffd", string) + + state.src = string diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_core/replacements.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_core/replacements.py new file mode 100644 index 0000000000000000000000000000000000000000..bcc9980046bf76723245b1ca2543af132efe5541 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_core/replacements.py @@ -0,0 +1,127 @@ +"""Simple typographic replacements + +* ``(c)``, ``(C)`` → © +* ``(tm)``, ``(TM)`` → ™ +* ``(r)``, ``(R)`` → ® +* ``+-`` → ± +* ``...`` → … +* ``?....`` → ?.. +* ``!....`` → !.. +* ``????????`` → ??? +* ``!!!!!`` → !!! +* ``,,,`` → , +* ``--`` → &ndash +* ``---`` → &mdash +""" + +from __future__ import annotations + +import logging +import re + +from ..token import Token +from .state_core import StateCore + +LOGGER = logging.getLogger(__name__) + +# TODO: +# - fractionals 1/2, 1/4, 3/4 -> ½, ¼, ¾ +# - multiplication 2 x 4 -> 2 × 4 + +RARE_RE = re.compile(r"\+-|\.\.|\?\?\?\?|!!!!|,,|--") + +# Workaround for phantomjs - need regex without /g flag, +# or root check will fail every second time +# SCOPED_ABBR_TEST_RE = r"\((c|tm|r)\)" + +SCOPED_ABBR_RE = re.compile(r"\((c|tm|r)\)", flags=re.IGNORECASE) + +PLUS_MINUS_RE = re.compile(r"\+-") + +ELLIPSIS_RE = re.compile(r"\.{2,}") + +ELLIPSIS_QUESTION_EXCLAMATION_RE = re.compile(r"([?!])…") + +QUESTION_EXCLAMATION_RE = re.compile(r"([?!]){4,}") + +COMMA_RE = re.compile(r",{2,}") + +EM_DASH_RE = re.compile(r"(^|[^-])---(?=[^-]|$)", flags=re.MULTILINE) + +EN_DASH_RE = re.compile(r"(^|\s)--(?=\s|$)", flags=re.MULTILINE) + +EN_DASH_INDENT_RE = re.compile(r"(^|[^-\s])--(?=[^-\s]|$)", flags=re.MULTILINE) + + +SCOPED_ABBR = {"c": "©", "r": "®", "tm": "™"} + + +def replaceFn(match: re.Match[str]) -> str: + return SCOPED_ABBR[match.group(1).lower()] + + +def replace_scoped(inlineTokens: list[Token]) -> None: + inside_autolink = 0 + + for token in inlineTokens: + if token.type == "text" and not inside_autolink: + token.content = SCOPED_ABBR_RE.sub(replaceFn, token.content) + + if token.type == "link_open" and token.info == "auto": + inside_autolink -= 1 + + if token.type == "link_close" and token.info == "auto": + inside_autolink += 1 + + +def replace_rare(inlineTokens: list[Token]) -> None: + inside_autolink = 0 + + for token in inlineTokens: + if ( + token.type == "text" + and (not inside_autolink) + and RARE_RE.search(token.content) + ): + # +- -> ± + token.content = PLUS_MINUS_RE.sub("±", token.content) + + # .., ..., ....... -> … + token.content = ELLIPSIS_RE.sub("…", token.content) + + # but ?..... & !..... -> ?.. & !.. + token.content = ELLIPSIS_QUESTION_EXCLAMATION_RE.sub("\\1..", token.content) + token.content = QUESTION_EXCLAMATION_RE.sub("\\1\\1\\1", token.content) + + # ,, ,,, ,,,, -> , + token.content = COMMA_RE.sub(",", token.content) + + # em-dash + token.content = EM_DASH_RE.sub("\\1\u2014", token.content) + + # en-dash + token.content = EN_DASH_RE.sub("\\1\u2013", token.content) + token.content = EN_DASH_INDENT_RE.sub("\\1\u2013", token.content) + + if token.type == "link_open" and token.info == "auto": + inside_autolink -= 1 + + if token.type == "link_close" and token.info == "auto": + inside_autolink += 1 + + +def replace(state: StateCore) -> None: + if not state.md.options.typographer: + return + + for token in state.tokens: + if token.type != "inline": + continue + if token.children is None: + continue + + if SCOPED_ABBR_RE.search(token.content): + replace_scoped(token.children) + + if RARE_RE.search(token.content): + replace_rare(token.children) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_core/smartquotes.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_core/smartquotes.py new file mode 100644 index 0000000000000000000000000000000000000000..f9b8b457b6c134f5736fabd3b14dc746e75ab86b --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_core/smartquotes.py @@ -0,0 +1,202 @@ +"""Convert straight quotation marks to typographic ones""" + +from __future__ import annotations + +import re +from typing import Any + +from ..common.utils import charCodeAt, isMdAsciiPunct, isPunctChar, isWhiteSpace +from ..token import Token +from .state_core import StateCore + +QUOTE_TEST_RE = re.compile(r"['\"]") +QUOTE_RE = re.compile(r"['\"]") +APOSTROPHE = "\u2019" # ’ + + +def replaceAt(string: str, index: int, ch: str) -> str: + # When the index is negative, the behavior is different from the js version. + # But basically, the index will not be negative. + assert index >= 0 + return string[:index] + ch + string[index + 1 :] + + +def process_inlines(tokens: list[Token], state: StateCore) -> None: + stack: list[dict[str, Any]] = [] + + for i, token in enumerate(tokens): + thisLevel = token.level + + j = 0 + for j in range(len(stack))[::-1]: + if stack[j]["level"] <= thisLevel: + break + else: + # When the loop is terminated without a "break". + # Subtract 1 to get the same index as the js version. + j -= 1 + + stack = stack[: j + 1] + + if token.type != "text": + continue + + text = token.content + pos = 0 + maximum = len(text) + + while pos < maximum: + goto_outer = False + lastIndex = pos + t = QUOTE_RE.search(text[lastIndex:]) + if not t: + break + + canOpen = canClose = True + pos = t.start(0) + lastIndex + 1 + isSingle = t.group(0) == "'" + + # Find previous character, + # default to space if it's the beginning of the line + lastChar: None | int = 0x20 + + if t.start(0) + lastIndex - 1 >= 0: + lastChar = charCodeAt(text, t.start(0) + lastIndex - 1) + else: + for j in range(i)[::-1]: + if tokens[j].type == "softbreak" or tokens[j].type == "hardbreak": + break + # should skip all tokens except 'text', 'html_inline' or 'code_inline' + if not tokens[j].content: + continue + + lastChar = charCodeAt(tokens[j].content, len(tokens[j].content) - 1) + break + + # Find next character, + # default to space if it's the end of the line + nextChar: None | int = 0x20 + + if pos < maximum: + nextChar = charCodeAt(text, pos) + else: + for j in range(i + 1, len(tokens)): + # nextChar defaults to 0x20 + if tokens[j].type == "softbreak" or tokens[j].type == "hardbreak": + break + # should skip all tokens except 'text', 'html_inline' or 'code_inline' + if not tokens[j].content: + continue + + nextChar = charCodeAt(tokens[j].content, 0) + break + + isLastPunctChar = lastChar is not None and ( + isMdAsciiPunct(lastChar) or isPunctChar(chr(lastChar)) + ) + isNextPunctChar = nextChar is not None and ( + isMdAsciiPunct(nextChar) or isPunctChar(chr(nextChar)) + ) + + isLastWhiteSpace = lastChar is not None and isWhiteSpace(lastChar) + isNextWhiteSpace = nextChar is not None and isWhiteSpace(nextChar) + + if isNextWhiteSpace: # noqa: SIM114 + canOpen = False + elif isNextPunctChar and not (isLastWhiteSpace or isLastPunctChar): + canOpen = False + + if isLastWhiteSpace: # noqa: SIM114 + canClose = False + elif isLastPunctChar and not (isNextWhiteSpace or isNextPunctChar): + canClose = False + + if nextChar == 0x22 and t.group(0) == '"': # 0x22: " # noqa: SIM102 + if ( + lastChar is not None and lastChar >= 0x30 and lastChar <= 0x39 + ): # 0x30: 0, 0x39: 9 + # special case: 1"" - count first quote as an inch + canClose = canOpen = False + + if canOpen and canClose: + # Replace quotes in the middle of punctuation sequence, but not + # in the middle of the words, i.e.: + # + # 1. foo " bar " baz - not replaced + # 2. foo-"-bar-"-baz - replaced + # 3. foo"bar"baz - not replaced + canOpen = isLastPunctChar + canClose = isNextPunctChar + + if not canOpen and not canClose: + # middle of word + if isSingle: + token.content = replaceAt( + token.content, t.start(0) + lastIndex, APOSTROPHE + ) + continue + + if canClose: + # this could be a closing quote, rewind the stack to get a match + for j in range(len(stack))[::-1]: + item = stack[j] + if stack[j]["level"] < thisLevel: + break + if item["single"] == isSingle and stack[j]["level"] == thisLevel: + item = stack[j] + + if isSingle: + openQuote = state.md.options.quotes[2] + closeQuote = state.md.options.quotes[3] + else: + openQuote = state.md.options.quotes[0] + closeQuote = state.md.options.quotes[1] + + # replace token.content *before* tokens[item.token].content, + # because, if they are pointing at the same token, replaceAt + # could mess up indices when quote length != 1 + token.content = replaceAt( + token.content, t.start(0) + lastIndex, closeQuote + ) + tokens[item["token"]].content = replaceAt( + tokens[item["token"]].content, item["pos"], openQuote + ) + + pos += len(closeQuote) - 1 + if item["token"] == i: + pos += len(openQuote) - 1 + + text = token.content + maximum = len(text) + + stack = stack[:j] + goto_outer = True + break + if goto_outer: + goto_outer = False + continue + + if canOpen: + stack.append( + { + "token": i, + "pos": t.start(0) + lastIndex, + "single": isSingle, + "level": thisLevel, + } + ) + elif canClose and isSingle: + token.content = replaceAt( + token.content, t.start(0) + lastIndex, APOSTROPHE + ) + + +def smartquotes(state: StateCore) -> None: + if not state.md.options.typographer: + return + + for token in state.tokens: + if token.type != "inline" or not QUOTE_RE.search(token.content): + continue + if token.children is not None: + process_inlines(token.children, state) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_core/state_core.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_core/state_core.py new file mode 100644 index 0000000000000000000000000000000000000000..a938041d992fdf7ae3f2843a2e0f9ef298c45790 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_core/state_core.py @@ -0,0 +1,25 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from ..ruler import StateBase +from ..token import Token +from ..utils import EnvType + +if TYPE_CHECKING: + from markdown_it import MarkdownIt + + +class StateCore(StateBase): + def __init__( + self, + src: str, + md: MarkdownIt, + env: EnvType, + tokens: list[Token] | None = None, + ) -> None: + self.src = src + self.md = md # link to parser instance + self.env = env + self.tokens: list[Token] = tokens or [] + self.inlineMode = False diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_core/text_join.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_core/text_join.py new file mode 100644 index 0000000000000000000000000000000000000000..939b83b29f75fa697a9b0f57ca86302744d639d1 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_core/text_join.py @@ -0,0 +1,53 @@ +"""Join raw text tokens with the rest of the text + +This is set as a separate rule to provide an opportunity for plugins +to run text replacements after text join, but before escape join. + +For example, `\\:)` shouldn't be replaced with an emoji. +""" + +from __future__ import annotations + +from ..token import Token +from .state_core import StateCore + + +def text_join(state: StateCore) -> None: + """Join raw text for escape sequences (`text_special`) tokens with the rest of the text""" + + for inline_token in state.tokens[:]: + if inline_token.type != "inline": + continue + + # convert text_special to text and join all adjacent text nodes + new_tokens: list[Token] = [] + children = inline_token.children or [] + i = 0 + while i < len(children): + child_token = children[i] + if child_token.type == "text_special": + child_token.type = "text" + if ( + child_token.type == "text" + and new_tokens + and new_tokens[-1].type == "text" + ): + # Collapse a run of adjacent text nodes in a single join, instead + # of pairwise `a + b` concatenation. The pairwise form is O(L*k) + # in the size of the run because each step rebuilds the growing + # prefix; "".join is O(L). + parts = [new_tokens[-1].content, child_token.content] + i += 1 + while i < len(children): + next_token = children[i] + if next_token.type == "text_special": + next_token.type = "text" + if next_token.type != "text": + break + parts.append(next_token.content) + i += 1 + new_tokens[-1].content = "".join(parts) + else: + new_tokens.append(child_token) + i += 1 + inline_token.children = new_tokens diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..d82ef8fbcca54eab4bbb40e8410104eef7a27f57 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/__init__.py @@ -0,0 +1,31 @@ +__all__ = ( + "StateInline", + "autolink", + "backtick", + "emphasis", + "entity", + "escape", + "fragments_join", + "html_inline", + "image", + "link", + "link_pairs", + "linkify", + "newline", + "strikethrough", + "text", +) +from . import emphasis, strikethrough +from .autolink import autolink +from .backticks import backtick +from .balance_pairs import link_pairs +from .entity import entity +from .escape import escape +from .fragments_join import fragments_join +from .html_inline import html_inline +from .image import image +from .link import link +from .linkify import linkify +from .newline import newline +from .state_inline import StateInline +from .text import text diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/autolink.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/autolink.py new file mode 100644 index 0000000000000000000000000000000000000000..6546e2502f93a1b38a49c3fd728963a156cf0243 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/autolink.py @@ -0,0 +1,77 @@ +# Process autolinks '' +import re + +from .state_inline import StateInline + +EMAIL_RE = re.compile( + r"^([a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)$" +) +AUTOLINK_RE = re.compile(r"^([a-zA-Z][a-zA-Z0-9+.\-]{1,31}):([^<>\x00-\x20]*)$") + + +def autolink(state: StateInline, silent: bool) -> bool: + pos = state.pos + + if state.src[pos] != "<": + return False + + start = state.pos + maximum = state.posMax + + while True: + pos += 1 + if pos >= maximum: + return False + + ch = state.src[pos] + + if ch == "<": + return False + if ch == ">": + break + + url = state.src[start + 1 : pos] + + if AUTOLINK_RE.search(url) is not None: + fullUrl = state.md.normalizeLink(url) + if not state.md.validateLink(fullUrl): + return False + + if not silent: + token = state.push("link_open", "a", 1) + token.attrs = {"href": fullUrl} + token.markup = "autolink" + token.info = "auto" + + token = state.push("text", "", 0) + token.content = state.md.normalizeLinkText(url) + + token = state.push("link_close", "a", -1) + token.markup = "autolink" + token.info = "auto" + + state.pos += len(url) + 2 + return True + + if EMAIL_RE.search(url) is not None: + fullUrl = state.md.normalizeLink("mailto:" + url) + if not state.md.validateLink(fullUrl): + return False + + if not silent: + token = state.push("link_open", "a", 1) + token.attrs = {"href": fullUrl} + token.markup = "autolink" + token.info = "auto" + + token = state.push("text", "", 0) + token.content = state.md.normalizeLinkText(url) + + token = state.push("link_close", "a", -1) + token.markup = "autolink" + token.info = "auto" + + state.pos += len(url) + 2 + return True + + return False diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/backticks.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/backticks.py new file mode 100644 index 0000000000000000000000000000000000000000..fc60d6b15cdfa7012a05bcf1ccbb06f44d870dfd --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/backticks.py @@ -0,0 +1,72 @@ +# Parse backticks +import re + +from .state_inline import StateInline + +regex = re.compile("^ (.+) $") + + +def backtick(state: StateInline, silent: bool) -> bool: + pos = state.pos + + if state.src[pos] != "`": + return False + + start = pos + pos += 1 + maximum = state.posMax + + # scan marker length + while pos < maximum and (state.src[pos] == "`"): + pos += 1 + + marker = state.src[start:pos] + openerLength = len(marker) + + if state.backticksScanned and state.backticks.get(openerLength, 0) <= start: + if not silent: + state.pending += marker + state.pos += openerLength + return True + + matchStart = matchEnd = pos + + # Nothing found in the cache, scan until the end of the line (or until marker is found) + while True: + try: + matchStart = state.src.index("`", matchEnd) + except ValueError: + break + matchEnd = matchStart + 1 + + # scan marker length + while matchEnd < maximum and (state.src[matchEnd] == "`"): + matchEnd += 1 + + closerLength = matchEnd - matchStart + + if closerLength == openerLength: + # Found matching closer length. + if not silent: + token = state.push("code_inline", "code", 0) + token.markup = marker + token.content = state.src[pos:matchStart].replace("\n", " ") + if ( + token.content.startswith(" ") + and token.content.endswith(" ") + and len(token.content.strip()) > 0 + ): + token.content = token.content[1:-1] + state.pos = matchEnd + return True + + # Some different length found, put it in cache as upper limit of where closer can be found + state.backticks[closerLength] = matchStart + + # Scanned through the end, didn't find anything + state.backticksScanned = True + + if not silent: + state.pending += marker + state.pos += openerLength + return True diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/balance_pairs.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/balance_pairs.py new file mode 100644 index 0000000000000000000000000000000000000000..9c63b27f7186eb99c61938d27309fda7e900b88d --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/balance_pairs.py @@ -0,0 +1,138 @@ +"""Balance paired characters (*, _, etc) in inline tokens.""" + +from __future__ import annotations + +from .state_inline import Delimiter, StateInline + + +def processDelimiters(state: StateInline, delimiters: list[Delimiter]) -> None: + """For each opening emphasis-like marker find a matching closing one.""" + if not delimiters: + return + + openersBottom = {} + maximum = len(delimiters) + + # headerIdx is the first delimiter of the current (where closer is) delimiter run + headerIdx = 0 + lastTokenIdx = -2 # needs any value lower than -1 + jumps: list[int] = [] + closerIdx = 0 + while closerIdx < maximum: + closer = delimiters[closerIdx] + + jumps.append(0) + + # markers belong to same delimiter run if: + # - they have adjacent tokens + # - AND markers are the same + # + if ( + delimiters[headerIdx].marker != closer.marker + or lastTokenIdx != closer.token - 1 + ): + headerIdx = closerIdx + lastTokenIdx = closer.token + + # Length is only used for emphasis-specific "rule of 3", + # if it's not defined (in strikethrough or 3rd party plugins), + # we can default it to 0 to disable those checks. + # + closer.length = closer.length or 0 + + if not closer.close: + closerIdx += 1 + continue + + # Previously calculated lower bounds (previous fails) + # for each marker, each delimiter length modulo 3, + # and for whether this closer can be an opener; + # https://github.com/commonmark/cmark/commit/34250e12ccebdc6372b8b49c44fab57c72443460 + if closer.marker not in openersBottom: + openersBottom[closer.marker] = [-1, -1, -1, -1, -1, -1] + + minOpenerIdx = openersBottom[closer.marker][ + (3 if closer.open else 0) + (closer.length % 3) + ] + + openerIdx = headerIdx - jumps[headerIdx] - 1 + + newMinOpenerIdx = openerIdx + + while openerIdx > minOpenerIdx: + opener = delimiters[openerIdx] + + if opener.marker != closer.marker: + openerIdx -= jumps[openerIdx] + 1 + continue + + if opener.open and opener.end < 0: + isOddMatch = False + + # from spec: + # + # If one of the delimiters can both open and close emphasis, then the + # sum of the lengths of the delimiter runs containing the opening and + # closing delimiters must not be a multiple of 3 unless both lengths + # are multiples of 3. + # + if ( + (opener.close or closer.open) + and ((opener.length + closer.length) % 3 == 0) + and (opener.length % 3 != 0 or closer.length % 3 != 0) + ): + isOddMatch = True + + if not isOddMatch: + # If previous delimiter cannot be an opener, we can safely skip + # the entire sequence in future checks. This is required to make + # sure algorithm has linear complexity (see *_*_*_*_*_... case). + # + if openerIdx > 0 and not delimiters[openerIdx - 1].open: + lastJump = jumps[openerIdx - 1] + 1 + else: + lastJump = 0 + + jumps[closerIdx] = closerIdx - openerIdx + lastJump + jumps[openerIdx] = lastJump + + closer.open = False + opener.end = closerIdx + opener.close = False + newMinOpenerIdx = -1 + + # treat next token as start of run, + # it optimizes skips in **<...>**a**<...>** pathological case + lastTokenIdx = -2 + + break + + openerIdx -= jumps[openerIdx] + 1 + + if newMinOpenerIdx != -1: + # If match for this delimiter run failed, we want to set lower bound for + # future lookups. This is required to make sure algorithm has linear + # complexity. + # + # See details here: + # https:#github.com/commonmark/cmark/issues/178#issuecomment-270417442 + # + openersBottom[closer.marker][ + (3 if closer.open else 0) + ((closer.length or 0) % 3) + ] = newMinOpenerIdx + + closerIdx += 1 + + +def link_pairs(state: StateInline) -> None: + tokens_meta = state.tokens_meta + maximum = len(state.tokens_meta) + + processDelimiters(state, state.delimiters) + + curr = 0 + while curr < maximum: + curr_meta = tokens_meta[curr] + if curr_meta and "delimiters" in curr_meta: + processDelimiters(state, curr_meta["delimiters"]) + curr += 1 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/emphasis.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/emphasis.py new file mode 100644 index 0000000000000000000000000000000000000000..9a98f9e216c94db0217e986270aaaa72fcc99f7f --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/emphasis.py @@ -0,0 +1,102 @@ +# Process *this* and _that_ +# +from __future__ import annotations + +from .state_inline import Delimiter, StateInline + + +def tokenize(state: StateInline, silent: bool) -> bool: + """Insert each marker as a separate text token, and add it to delimiter list""" + start = state.pos + marker = state.src[start] + + if silent: + return False + + if marker not in ("_", "*"): + return False + + scanned = state.scanDelims(state.pos, marker == "*") + + for _ in range(scanned.length): + token = state.push("text", "", 0) + token.content = marker + state.delimiters.append( + Delimiter( + marker=ord(marker), + length=scanned.length, + token=len(state.tokens) - 1, + end=-1, + open=scanned.can_open, + close=scanned.can_close, + ) + ) + + state.pos += scanned.length + + return True + + +def _postProcess(state: StateInline, delimiters: list[Delimiter]) -> None: + i = len(delimiters) - 1 + while i >= 0: + startDelim = delimiters[i] + + # /* _ */ /* * */ + if startDelim.marker != 0x5F and startDelim.marker != 0x2A: + i -= 1 + continue + + # Process only opening markers + if startDelim.end == -1: + i -= 1 + continue + + endDelim = delimiters[startDelim.end] + + # If the previous delimiter has the same marker and is adjacent to this one, + # merge those into one strong delimiter. + # + # `whatever` -> `whatever` + # + isStrong = ( + i > 0 + and delimiters[i - 1].end == startDelim.end + 1 + # check that first two markers match and adjacent + and delimiters[i - 1].marker == startDelim.marker + and delimiters[i - 1].token == startDelim.token - 1 + # check that last two markers are adjacent (we can safely assume they match) + and delimiters[startDelim.end + 1].token == endDelim.token + 1 + ) + + ch = chr(startDelim.marker) + + token = state.tokens[startDelim.token] + token.type = "strong_open" if isStrong else "em_open" + token.tag = "strong" if isStrong else "em" + token.nesting = 1 + token.markup = ch + ch if isStrong else ch + token.content = "" + + token = state.tokens[endDelim.token] + token.type = "strong_close" if isStrong else "em_close" + token.tag = "strong" if isStrong else "em" + token.nesting = -1 + token.markup = ch + ch if isStrong else ch + token.content = "" + + if isStrong: + state.tokens[delimiters[i - 1].token].content = "" + state.tokens[delimiters[startDelim.end + 1].token].content = "" + i -= 1 + + i -= 1 + + +def postProcess(state: StateInline) -> None: + """Walk through delimiter list and replace text tokens with tags.""" + _postProcess(state, state.delimiters) + + for token in state.tokens_meta: + if token and "delimiters" in token: + _postProcess(state, token["delimiters"]) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/entity.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/entity.py new file mode 100644 index 0000000000000000000000000000000000000000..ec9d39650e5bc533e694d3d6699677068d22c69f --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/entity.py @@ -0,0 +1,53 @@ +# Process html entity - {, ¯, ", ... +import re + +from ..common.entities import entities +from ..common.utils import fromCodePoint, isValidEntityCode +from .state_inline import StateInline + +DIGITAL_RE = re.compile(r"^&#((?:x[a-f0-9]{1,6}|[0-9]{1,7}));", re.IGNORECASE) +NAMED_RE = re.compile(r"^&([a-z][a-z0-9]{1,31});", re.IGNORECASE) + + +def entity(state: StateInline, silent: bool) -> bool: + pos = state.pos + maximum = state.posMax + + if state.src[pos] != "&": + return False + + if pos + 1 >= maximum: + return False + + if state.src[pos + 1] == "#": + if match := DIGITAL_RE.search(state.src[pos:]): + if not silent: + match1 = match.group(1) + code = ( + int(match1[1:], 16) if match1[0].lower() == "x" else int(match1, 10) + ) + + token = state.push("text_special", "", 0) + token.content = ( + fromCodePoint(code) + if isValidEntityCode(code) + else fromCodePoint(0xFFFD) + ) + token.markup = match.group(0) + token.info = "entity" + + state.pos += len(match.group(0)) + return True + + else: + if (match := NAMED_RE.search(state.src[pos:])) and match.group(1) in entities: + if not silent: + token = state.push("text_special", "", 0) + token.content = entities[match.group(1)] + token.markup = match.group(0) + token.info = "entity" + + state.pos += len(match.group(0)) + return True + + return False diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/escape.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/escape.py new file mode 100644 index 0000000000000000000000000000000000000000..0fca6c84e035b83b21d5224be92fd4973f25b80b --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/escape.py @@ -0,0 +1,93 @@ +""" +Process escaped chars and hardbreaks +""" + +from ..common.utils import isStrSpace +from .state_inline import StateInline + + +def escape(state: StateInline, silent: bool) -> bool: + """Process escaped chars and hardbreaks.""" + pos = state.pos + maximum = state.posMax + + if state.src[pos] != "\\": + return False + + pos += 1 + + # '\' at the end of the inline block + if pos >= maximum: + return False + + ch1 = state.src[pos] + ch1_ord = ord(ch1) + if ch1 == "\n": + if not silent: + state.push("hardbreak", "br", 0) + pos += 1 + # skip leading whitespaces from next line + while pos < maximum: + ch = state.src[pos] + if not isStrSpace(ch): + break + pos += 1 + + state.pos = pos + return True + + escapedStr = state.src[pos] + + if ch1_ord >= 0xD800 and ch1_ord <= 0xDBFF and pos + 1 < maximum: + ch2 = state.src[pos + 1] + ch2_ord = ord(ch2) + if ch2_ord >= 0xDC00 and ch2_ord <= 0xDFFF: + escapedStr += ch2 + pos += 1 + + origStr = "\\" + escapedStr + + if not silent: + token = state.push("text_special", "", 0) + token.content = escapedStr if ch1 in _ESCAPED else origStr + token.markup = origStr + token.info = "escape" + + state.pos = pos + 1 + return True + + +_ESCAPED = { + "!", + '"', + "#", + "$", + "%", + "&", + "'", + "(", + ")", + "*", + "+", + ",", + "-", + ".", + "/", + ":", + ";", + "<", + "=", + ">", + "?", + "@", + "[", + "\\", + "]", + "^", + "_", + "`", + "{", + "|", + "}", + "~", +} diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/fragments_join.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/fragments_join.py new file mode 100644 index 0000000000000000000000000000000000000000..5eb88a14025793d02188392244c1099042cf3727 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/fragments_join.py @@ -0,0 +1,54 @@ +from .state_inline import StateInline + + +def fragments_join(state: StateInline) -> None: + """ + Clean up tokens after emphasis and strikethrough postprocessing: + merge adjacent text nodes into one and re-calculate all token levels + + This is necessary because initially emphasis delimiter markers (``*, _, ~``) + are treated as their own separate text tokens. Then emphasis rule either + leaves them as text (needed to merge with adjacent text) or turns them + into opening/closing tags (which messes up levels inside). + """ + level = 0 + maximum = len(state.tokens) + + curr = last = 0 + while curr < maximum: + # re-calculate levels after emphasis/strikethrough turns some text nodes + # into opening/closing tags + if state.tokens[curr].nesting < 0: + level -= 1 # closing tag + state.tokens[curr].level = level + if state.tokens[curr].nesting > 0: + level += 1 # opening tag + + if ( + state.tokens[curr].type == "text" + and curr + 1 < maximum + and state.tokens[curr + 1].type == "text" + ): + # Collapse a run of adjacent text nodes in a single join, instead + # of pairwise `a + b` concatenation. The pairwise form is O(L*k) + # in the size of the run because each step rebuilds the growing + # prefix; "".join is O(L). + parts = [state.tokens[curr].content] + curr += 1 + while curr < maximum and state.tokens[curr].type == "text": + parts.append(state.tokens[curr].content) + curr += 1 + merged = state.tokens[curr - 1] + merged.content = "".join(parts) + merged.level = level + state.tokens[last] = merged + last += 1 + continue + + if curr != last: + state.tokens[last] = state.tokens[curr] + last += 1 + curr += 1 + + if curr != last: + del state.tokens[last:] diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/html_inline.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/html_inline.py new file mode 100644 index 0000000000000000000000000000000000000000..9065e1d034da76270f7d3f1ba528132c8d57d341 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/html_inline.py @@ -0,0 +1,43 @@ +# Process html tags +from ..common.html_re import HTML_TAG_RE +from ..common.utils import isLinkClose, isLinkOpen +from .state_inline import StateInline + + +def isLetter(ch: int) -> bool: + lc = ch | 0x20 # to lower case + # /* a */ and /* z */ + return (lc >= 0x61) and (lc <= 0x7A) + + +def html_inline(state: StateInline, silent: bool) -> bool: + pos = state.pos + + if not state.md.options.get("html", None): + return False + + # Check start + maximum = state.posMax + if state.src[pos] != "<" or pos + 2 >= maximum: + return False + + # Quick fail on second char + ch = state.src[pos + 1] + if ch not in ("!", "?", "/") and not isLetter(ord(ch)): # /* / */ + return False + + match = HTML_TAG_RE.search(state.src[pos:]) + if not match: + return False + + if not silent: + token = state.push("html_inline", "", 0) + token.content = state.src[pos : pos + len(match.group(0))] + + if isLinkOpen(token.content): + state.linkLevel += 1 + if isLinkClose(token.content): + state.linkLevel -= 1 + + state.pos += len(match.group(0)) + return True diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/image.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/image.py new file mode 100644 index 0000000000000000000000000000000000000000..005105b1c7ed1a93772c62af8bd5ef54d97dfebe --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/image.py @@ -0,0 +1,148 @@ +# Process ![image]( "title") +from __future__ import annotations + +from ..common.utils import isStrSpace, normalizeReference +from ..token import Token +from .state_inline import StateInline + + +def image(state: StateInline, silent: bool) -> bool: + label = None + href = "" + oldPos = state.pos + max = state.posMax + + if state.src[state.pos] != "!": + return False + + if state.pos + 1 < state.posMax and state.src[state.pos + 1] != "[": + return False + + labelStart = state.pos + 2 + labelEnd = state.md.helpers.parseLinkLabel(state, state.pos + 1, False) + + # parser failed to find ']', so it's not a valid link + if labelEnd < 0: + return False + + pos = labelEnd + 1 + + if pos < max and state.src[pos] == "(": + # + # Inline link + # + + # [link]( "title" ) + # ^^ skipping these spaces + pos += 1 + while pos < max: + ch = state.src[pos] + if not isStrSpace(ch) and ch != "\n": + break + pos += 1 + + if pos >= max: + return False + + # [link]( "title" ) + # ^^^^^^ parsing link destination + start = pos + res = state.md.helpers.parseLinkDestination(state.src, pos, state.posMax) + if res.ok: + href = state.md.normalizeLink(res.str) + if state.md.validateLink(href): + pos = res.pos + else: + href = "" + + # [link]( "title" ) + # ^^ skipping these spaces + start = pos + while pos < max: + ch = state.src[pos] + if not isStrSpace(ch) and ch != "\n": + break + pos += 1 + + # [link]( "title" ) + # ^^^^^^^ parsing link title + res = state.md.helpers.parseLinkTitle(state.src, pos, state.posMax, None) + if pos < max and start != pos and res.ok: + title = res.str + pos = res.pos + + # [link]( "title" ) + # ^^ skipping these spaces + while pos < max: + ch = state.src[pos] + if not isStrSpace(ch) and ch != "\n": + break + pos += 1 + else: + title = "" + + if pos >= max or state.src[pos] != ")": + state.pos = oldPos + return False + + pos += 1 + + else: + # + # Link reference + # + if "references" not in state.env: + return False + + # /* [ */ + if pos < max and state.src[pos] == "[": + start = pos + 1 + pos = state.md.helpers.parseLinkLabel(state, pos) + if pos >= 0: + label = state.src[start:pos] + pos += 1 + else: + pos = labelEnd + 1 + else: + pos = labelEnd + 1 + + # covers label == '' and label == undefined + # (collapsed reference link and shortcut reference link respectively) + if not label: + label = state.src[labelStart:labelEnd] + + label = normalizeReference(label) + + ref = state.env["references"].get(label, None) + if not ref: + state.pos = oldPos + return False + + href = ref["href"] + title = ref["title"] + + # + # We found the end of the link, and know for a fact it's a valid link + # so all that's left to do is to call tokenizer. + # + if not silent: + content = state.src[labelStart:labelEnd] + + tokens: list[Token] = [] + state.md.inline.parse(content, state.md, state.env, tokens) + + token = state.push("image", "img", 0) + token.attrs = {"src": href, "alt": ""} + token.children = tokens or None + token.content = content + + if title: + token.attrSet("title", title) + + # note, this is not part of markdown-it JS, but is useful for renderers + if label and state.md.options.get("store_labels", False): + token.meta["label"] = label + + state.pos = pos + state.posMax = max + return True diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/link.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/link.py new file mode 100644 index 0000000000000000000000000000000000000000..2e92c7d83629f00283b5ed885637c8c4a851ffc7 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/link.py @@ -0,0 +1,149 @@ +# Process [link]( "stuff") + +from ..common.utils import isStrSpace, normalizeReference +from .state_inline import StateInline + + +def link(state: StateInline, silent: bool) -> bool: + href = "" + title = "" + label = None + oldPos = state.pos + maximum = state.posMax + start = state.pos + parseReference = True + + if state.src[state.pos] != "[": + return False + + labelStart = state.pos + 1 + labelEnd = state.md.helpers.parseLinkLabel(state, state.pos, True) + + # parser failed to find ']', so it's not a valid link + if labelEnd < 0: + return False + + pos = labelEnd + 1 + + if pos < maximum and state.src[pos] == "(": + # + # Inline link + # + + # might have found a valid shortcut link, disable reference parsing + parseReference = False + + # [link]( "title" ) + # ^^ skipping these spaces + pos += 1 + while pos < maximum: + ch = state.src[pos] + if not isStrSpace(ch) and ch != "\n": + break + pos += 1 + + if pos >= maximum: + return False + + # [link]( "title" ) + # ^^^^^^ parsing link destination + start = pos + res = state.md.helpers.parseLinkDestination(state.src, pos, state.posMax) + if res.ok: + href = state.md.normalizeLink(res.str) + if state.md.validateLink(href): + pos = res.pos + else: + href = "" + + # [link]( "title" ) + # ^^ skipping these spaces + start = pos + while pos < maximum: + ch = state.src[pos] + if not isStrSpace(ch) and ch != "\n": + break + pos += 1 + + # [link]( "title" ) + # ^^^^^^^ parsing link title + res = state.md.helpers.parseLinkTitle(state.src, pos, state.posMax) + if pos < maximum and start != pos and res.ok: + title = res.str + pos = res.pos + + # [link]( "title" ) + # ^^ skipping these spaces + while pos < maximum: + ch = state.src[pos] + if not isStrSpace(ch) and ch != "\n": + break + pos += 1 + + if pos >= maximum or state.src[pos] != ")": + # parsing a valid shortcut link failed, fallback to reference + parseReference = True + + pos += 1 + + if parseReference: + # + # Link reference + # + if "references" not in state.env: + return False + + if pos < maximum and state.src[pos] == "[": + start = pos + 1 + pos = state.md.helpers.parseLinkLabel(state, pos) + if pos >= 0: + label = state.src[start:pos] + pos += 1 + else: + pos = labelEnd + 1 + + else: + pos = labelEnd + 1 + + # covers label == '' and label == undefined + # (collapsed reference link and shortcut reference link respectively) + if not label: + label = state.src[labelStart:labelEnd] + + label = normalizeReference(label) + + ref = state.env["references"].get(label, None) + if not ref: + state.pos = oldPos + return False + + href = ref["href"] + title = ref["title"] + + # + # We found the end of the link, and know for a fact it's a valid link + # so all that's left to do is to call tokenizer. + # + if not silent: + state.pos = labelStart + state.posMax = labelEnd + + token = state.push("link_open", "a", 1) + token.attrs = {"href": href} + + if title: + token.attrSet("title", title) + + # note, this is not part of markdown-it JS, but is useful for renderers + if label and state.md.options.get("store_labels", False): + token.meta["label"] = label + + state.linkLevel += 1 + state.md.inline.tokenize(state) + state.linkLevel -= 1 + + token = state.push("link_close", "a", -1) + + state.pos = pos + state.posMax = maximum + return True diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/linkify.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/linkify.py new file mode 100644 index 0000000000000000000000000000000000000000..3669396e3e7d51c125678f45a862139fe3b3fd9d --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/linkify.py @@ -0,0 +1,62 @@ +"""Process links like https://example.org/""" + +import re + +from .state_inline import StateInline + +# RFC3986: scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) +SCHEME_RE = re.compile(r"(?:^|[^a-z0-9.+-])([a-z][a-z0-9.+-]*)$", re.IGNORECASE) + + +def linkify(state: StateInline, silent: bool) -> bool: + """Rule for identifying plain-text links.""" + if not state.md.options.linkify: + return False + if state.linkLevel > 0: + return False + if not state.md.linkify: + raise ModuleNotFoundError("Linkify enabled but not installed.") + + pos = state.pos + maximum = state.posMax + + if ( + (pos + 3) > maximum + or state.src[pos] != ":" + or state.src[pos + 1] != "/" + or state.src[pos + 2] != "/" + ): + return False + + if not (match := SCHEME_RE.search(state.pending)): + return False + + proto = match.group(1) + if not (link := state.md.linkify.match_at_start(state.src[pos - len(proto) :])): + return False + url: str = link.url + + # disallow '*' at the end of the link (conflicts with emphasis) + url = url.rstrip("*") + + full_url = state.md.normalizeLink(url) + if not state.md.validateLink(full_url): + return False + + if not silent: + state.pending = state.pending[: -len(proto)] + + token = state.push("link_open", "a", 1) + token.attrs = {"href": full_url} + token.markup = "linkify" + token.info = "auto" + + token = state.push("text", "", 0) + token.content = state.md.normalizeLinkText(url) + + token = state.push("link_close", "a", -1) + token.markup = "linkify" + token.info = "auto" + + state.pos += len(url) - len(proto) + return True diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/newline.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/newline.py new file mode 100644 index 0000000000000000000000000000000000000000..d05ee6dac712a2211ab24f90dbea64a99e4d80b0 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/newline.py @@ -0,0 +1,44 @@ +"""Proceess '\n'.""" + +from ..common.utils import charStrAt, isStrSpace +from .state_inline import StateInline + + +def newline(state: StateInline, silent: bool) -> bool: + pos = state.pos + + if state.src[pos] != "\n": + return False + + pmax = len(state.pending) - 1 + maximum = state.posMax + + # ' \n' -> hardbreak + # Lookup in pending chars is bad practice! Don't copy to other rules! + # Pending string is stored in concat mode, indexed lookups will cause + # conversion to flat mode. + if not silent: + if pmax >= 0 and charStrAt(state.pending, pmax) == " ": + if pmax >= 1 and charStrAt(state.pending, pmax - 1) == " ": + # Find whitespaces tail of pending chars. + ws = pmax - 1 + while ws >= 1 and charStrAt(state.pending, ws - 1) == " ": + ws -= 1 + state.pending = state.pending[:ws] + + state.push("hardbreak", "br", 0) + else: + state.pending = state.pending[:-1] + state.push("softbreak", "br", 0) + + else: + state.push("softbreak", "br", 0) + + pos += 1 + + # skip heading spaces for next line + while pos < maximum and isStrSpace(state.src[pos]): + pos += 1 + + state.pos = pos + return True diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/state_inline.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/state_inline.py new file mode 100644 index 0000000000000000000000000000000000000000..de35287d427969eb9354dd13a74e5372c85cbfab --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/state_inline.py @@ -0,0 +1,167 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import TYPE_CHECKING, Any, Literal, NamedTuple + +from ..common.utils import isMdAsciiPunct, isPunctChar, isWhiteSpace +from ..ruler import StateBase +from ..token import Token +from ..utils import EnvType + +if TYPE_CHECKING: + from markdown_it import MarkdownIt + + +@dataclass(slots=True) +class Delimiter: + # Char code of the starting marker (number). + marker: int + + # Total length of these series of delimiters. + length: int + + # A position of the token this delimiter corresponds to. + token: int + + # If this delimiter is matched as a valid opener, `end` will be + # equal to its position, otherwise it's `-1`. + end: int + + # Boolean flags that determine if this delimiter could open or close + # an emphasis. + open: bool + close: bool + + level: bool | None = None + + +class Scanned(NamedTuple): + can_open: bool + can_close: bool + length: int + + +class StateInline(StateBase): + def __init__( + self, src: str, md: MarkdownIt, env: EnvType, outTokens: list[Token] + ) -> None: + self.src = src + self.env = env + self.md = md + self.tokens = outTokens + self.tokens_meta: list[dict[str, Any] | None] = [None] * len(outTokens) + + self.pos = 0 + self.posMax = len(self.src) + self.level = 0 + self.pending = "" + self.pendingLevel = 0 + + # Stores { start: end } pairs. Useful for backtrack + # optimization of pairs parse (emphasis, strikes). + self.cache: dict[int, int] = {} + + # List of emphasis-like delimiters for current tag + self.delimiters: list[Delimiter] = [] + + # Stack of delimiter lists for upper level tags + self._prev_delimiters: list[list[Delimiter]] = [] + + # backticklength => last seen position + self.backticks: dict[int, int] = {} + self.backticksScanned = False + + # Counter used to disable inline linkify-it execution + # inside and markdown links + self.linkLevel = 0 + + def __repr__(self) -> str: + return ( + f"{self.__class__.__name__}" + f"(pos=[{self.pos} of {self.posMax}], token={len(self.tokens)})" + ) + + def pushPending(self) -> Token: + token = Token("text", "", 0) + token.content = self.pending + token.level = self.pendingLevel + self.tokens.append(token) + self.pending = "" + return token + + def push(self, ttype: str, tag: str, nesting: Literal[-1, 0, 1]) -> Token: + """Push new token to "stream". + If pending text exists - flush it as text token + """ + if self.pending: + self.pushPending() + + token = Token(ttype, tag, nesting) + token_meta = None + + if nesting < 0: + # closing tag + self.level -= 1 + self.delimiters = self._prev_delimiters.pop() + + token.level = self.level + + if nesting > 0: + # opening tag + self.level += 1 + self._prev_delimiters.append(self.delimiters) + self.delimiters = [] + token_meta = {"delimiters": self.delimiters} + + self.pendingLevel = self.level + self.tokens.append(token) + self.tokens_meta.append(token_meta) + return token + + def scanDelims(self, start: int, canSplitWord: bool) -> Scanned: + """ + Scan a sequence of emphasis-like markers, and determine whether + it can start an emphasis sequence or end an emphasis sequence. + + - start - position to scan from (it should point at a valid marker); + - canSplitWord - determine if these markers can be found inside a word + + """ + pos = start + maximum = self.posMax + marker = self.src[start] + + # treat beginning of the line as a whitespace + lastChar = self.src[start - 1] if start > 0 else " " + + while pos < maximum and self.src[pos] == marker: + pos += 1 + + count = pos - start + + # treat end of the line as a whitespace + nextChar = self.src[pos] if pos < maximum else " " + + isLastPunctChar = isMdAsciiPunct(ord(lastChar)) or isPunctChar(lastChar) + isNextPunctChar = isMdAsciiPunct(ord(nextChar)) or isPunctChar(nextChar) + + isLastWhiteSpace = isWhiteSpace(ord(lastChar)) + isNextWhiteSpace = isWhiteSpace(ord(nextChar)) + + left_flanking = not ( + isNextWhiteSpace + or (isNextPunctChar and not (isLastWhiteSpace or isLastPunctChar)) + ) + right_flanking = not ( + isLastWhiteSpace + or (isLastPunctChar and not (isNextWhiteSpace or isNextPunctChar)) + ) + + can_open = left_flanking and ( + canSplitWord or (not right_flanking) or isLastPunctChar + ) + can_close = right_flanking and ( + canSplitWord or (not left_flanking) or isNextPunctChar + ) + + return Scanned(can_open, can_close, count) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/strikethrough.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/strikethrough.py new file mode 100644 index 0000000000000000000000000000000000000000..c9875e043abbacd6ce627964be7d7c70028a8199 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/strikethrough.py @@ -0,0 +1,173 @@ +# ~~strike through~~ (and optionally ~single tilde~) +from __future__ import annotations + +from .state_inline import Delimiter, StateInline + + +def tokenize(state: StateInline, silent: bool) -> bool: + """Insert each marker as a separate text token, and add it to delimiter list. + + When the ``strikethrough_single_tilde`` option is enabled on the + ``MarkdownIt`` instance, single ``~`` delimiters are also accepted and + runs of three or more tildes are rejected (matching GitHub's rendering behaviour). + """ + start = state.pos + ch = state.src[start] + + if silent: + return False + + if ch != "~": + return False + + scanned = state.scanDelims(state.pos, True) + length = scanned.length + + single_tilde = state.md.options.get("strikethrough_single_tilde", False) + + if single_tilde: + # GitHub mode: only accept exactly 1 or 2 tildes. + if length < 1: + return False + if length > 2: + # Consume 3+ tildes as plain text so the parser doesn't + # re-enter and match a subset of them. This intentionally + # matches GitHub's rendering, where ≥3 tildes are literal text. + token = state.push("text", "", 0) + token.content = ch * length + state.pos += scanned.length + return True + + token = state.push("text", "", 0) + token.content = ch * length + state.delimiters.append( + Delimiter( + marker=ord(ch), + length=0, # disable "rule of 3" length checks + token=len(state.tokens) - 1, + end=-1, + open=scanned.can_open, + close=scanned.can_close, + ) + ) + else: + # Original markdown-it behaviour: minimum 2, split odd runs. + if length < 2: + return False + + if length % 2: + token = state.push("text", "", 0) + token.content = ch + length -= 1 + + i = 0 + while i < length: + token = state.push("text", "", 0) + token.content = ch + ch + state.delimiters.append( + Delimiter( + marker=ord(ch), + length=0, # disable "rule of 3" length checks + token=len(state.tokens) - 1, + end=-1, + open=scanned.can_open, + close=scanned.can_close, + ) + ) + + i += 2 + + state.pos += scanned.length + + return True + + +def _postProcess(state: StateInline, delimiters: list[Delimiter]) -> None: + loneMarkers = [] + maximum = len(delimiters) + single_tilde = state.md.options.get("strikethrough_single_tilde", False) + + i = 0 + while i < maximum: + startDelim = delimiters[i] + + if startDelim.marker != 0x7E: # /* ~ */ + i += 1 + continue + + if startDelim.end == -1: + i += 1 + continue + + endDelim = delimiters[startDelim.end] + + # In single-tilde mode, opener and closer must have the same width + # (both `~` or both `~~`). The width is stored in the text token. + if single_tilde: + opener_content = state.tokens[startDelim.token].content + closer_content = state.tokens[endDelim.token].content + if opener_content != closer_content: + i += 1 + continue + + markup = state.tokens[startDelim.token].content + + token = state.tokens[startDelim.token] + token.type = "s_open" + token.tag = "s" + token.nesting = 1 + token.markup = markup + token.content = "" + + token = state.tokens[endDelim.token] + token.type = "s_close" + token.tag = "s" + token.nesting = -1 + token.markup = markup + token.content = "" + + if ( + state.tokens[endDelim.token - 1].type == "text" + and state.tokens[endDelim.token - 1].content == "~" + ): + loneMarkers.append(endDelim.token - 1) + + i += 1 + + # If a marker sequence has an odd number of characters, it's split + # like this: `~~~~~` -> `~` + `~~` + `~~`, leaving one marker at the + # start of the sequence. + # + # So, we have to move all those markers after subsequent s_close tags. + # + while loneMarkers: + i = loneMarkers.pop() + j = i + 1 + + while (j < len(state.tokens)) and (state.tokens[j].type == "s_close"): + j += 1 + + j -= 1 + + if i != j: + token = state.tokens[j] + state.tokens[j] = state.tokens[i] + state.tokens[i] = token + + +def postProcess(state: StateInline) -> None: + """Walk through delimiter list and replace text tokens with tags.""" + tokens_meta = state.tokens_meta + maximum = len(state.tokens_meta) + _postProcess(state, state.delimiters) + + curr = 0 + while curr < maximum: + try: + curr_meta = tokens_meta[curr] + except IndexError: + pass + else: + if curr_meta and "delimiters" in curr_meta: + _postProcess(state, curr_meta["delimiters"]) + curr += 1 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/text.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/text.py new file mode 100644 index 0000000000000000000000000000000000000000..ef0cc9cec55e253a8eb17b37d593b1c320374cf5 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/rules_inline/text.py @@ -0,0 +1,23 @@ +# Skip text characters for text token, place those to pending buffer +# and increment current pos +from .state_inline import StateInline + +# Rule to skip pure text + + +def text(state: StateInline, silent: bool) -> bool: + pos = state.pos + posMax = state.posMax + + terminator_char = state.md.inline.terminator_re.search(state.src, pos) + pos = terminator_char.start() if terminator_char else posMax + + if pos == state.pos: + return False + + if not silent: + state.pending += state.src[state.pos : pos] + + state.pos = pos + + return True diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/token.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/token.py new file mode 100644 index 0000000000000000000000000000000000000000..d6d0b4530d283f4fb925ca4e95c75eef0818117d --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/token.py @@ -0,0 +1,178 @@ +from __future__ import annotations + +from collections.abc import Callable, MutableMapping +import dataclasses as dc +from typing import Any, Literal +import warnings + + +def convert_attrs(value: Any) -> Any: + """Convert Token.attrs set as ``None`` or ``[[key, value], ...]`` to a dict. + + This improves compatibility with upstream markdown-it. + """ + if not value: + return {} + if isinstance(value, list): + return dict(value) + return value + + +@dc.dataclass(slots=True) +class Token: + type: str + """Type of the token (string, e.g. "paragraph_open")""" + + tag: str + """HTML tag name, e.g. 'p'""" + + nesting: Literal[-1, 0, 1] + """Level change (number in {-1, 0, 1} set), where: + - `1` means the tag is opening + - `0` means the tag is self-closing + - `-1` means the tag is closing + """ + + attrs: dict[str, str | int | float] = dc.field(default_factory=dict) + """HTML attributes. + Note this differs from the upstream "list of lists" format, + although than an instance can still be initialised with this format. + """ + + map: list[int] | None = None + """Source map info. Format: `[ line_begin, line_end ]`""" + + level: int = 0 + """Nesting level, the same as `state.level`""" + + children: list[Token] | None = None + """Array of child nodes (inline and img tokens).""" + + content: str = "" + """Inner content, in the case of a self-closing tag (code, html, fence, etc.),""" + + markup: str = "" + """'*' or '_' for emphasis, fence string for fence, etc.""" + + info: str = "" + """Additional information: + - Info string for "fence" tokens + - The value "auto" for autolink "link_open" and "link_close" tokens + - The string value of the item marker for ordered-list "list_item_open" tokens + """ + + meta: dict[Any, Any] = dc.field(default_factory=dict) + """A place for plugins to store any arbitrary data""" + + block: bool = False + """True for block-level tokens, false for inline tokens. + Used in renderer to calculate line breaks + """ + + hidden: bool = False + """If true, ignore this element when rendering. + Used for tight lists to hide paragraphs. + """ + + def __post_init__(self) -> None: + self.attrs = convert_attrs(self.attrs) + + def attrIndex(self, name: str) -> int: + warnings.warn( # noqa: B028 + "Token.attrIndex should not be used, since Token.attrs is a dictionary", + UserWarning, + ) + if name not in self.attrs: + return -1 + return list(self.attrs.keys()).index(name) + + def attrItems(self) -> list[tuple[str, str | int | float]]: + """Get (key, value) list of attrs.""" + return list(self.attrs.items()) + + def attrPush(self, attrData: tuple[str, str | int | float]) -> None: + """Add `[ name, value ]` attribute to list. Init attrs if necessary.""" + name, value = attrData + self.attrSet(name, value) + + def attrSet(self, name: str, value: str | int | float) -> None: + """Set `name` attribute to `value`. Override old value if exists.""" + self.attrs[name] = value + + def attrGet(self, name: str) -> None | str | int | float: + """Get the value of attribute `name`, or null if it does not exist.""" + return self.attrs.get(name, None) + + def attrJoin(self, name: str, value: str) -> None: + """Join value to existing attribute via space. + Or create new attribute if not exists. + Useful to operate with token classes. + """ + if name in self.attrs: + current = self.attrs[name] + if not isinstance(current, str): + raise TypeError( + f"existing attr 'name' is not a str: {self.attrs[name]}" + ) + self.attrs[name] = f"{current} {value}" + else: + self.attrs[name] = value + + def copy(self, **changes: Any) -> Token: + """Return a shallow copy of the instance.""" + return dc.replace(self, **changes) + + def as_dict( + self, + *, + children: bool = True, + as_upstream: bool = True, + meta_serializer: Callable[[dict[Any, Any]], Any] | None = None, + filter: Callable[[str, Any], bool] | None = None, + dict_factory: Callable[..., MutableMapping[str, Any]] = dict, + ) -> MutableMapping[str, Any]: + """Return the token as a dictionary. + + :param children: Also convert children to dicts + :param as_upstream: Ensure the output dictionary is equal to that created by markdown-it + For example, attrs are converted to null or lists + :param meta_serializer: hook for serializing ``Token.meta`` + :param filter: A callable whose return code determines whether an + attribute or element is included (``True``) or dropped (``False``). + Is called with the (key, value) pair. + :param dict_factory: A callable to produce dictionaries from. + For example, to produce ordered dictionaries instead of normal Python + dictionaries, pass in ``collections.OrderedDict``. + + """ + mapping = dict_factory((f.name, getattr(self, f.name)) for f in dc.fields(self)) + if filter: + mapping = dict_factory((k, v) for k, v in mapping.items() if filter(k, v)) + if as_upstream and "attrs" in mapping: + mapping["attrs"] = ( + None + if not mapping["attrs"] + else [[k, v] for k, v in mapping["attrs"].items()] + ) + if meta_serializer and "meta" in mapping: + mapping["meta"] = meta_serializer(mapping["meta"]) + if children and mapping.get("children", None): + mapping["children"] = [ + child.as_dict( + children=children, + filter=filter, + dict_factory=dict_factory, + as_upstream=as_upstream, + meta_serializer=meta_serializer, + ) + for child in mapping["children"] + ] + return mapping + + @classmethod + def from_dict(cls, dct: MutableMapping[str, Any]) -> Token: + """Convert a dict to a Token.""" + token = cls(**dct) + if token.children: + token.children = [cls.from_dict(c) for c in token.children] # type: ignore[arg-type] + return token diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/tree.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/tree.py new file mode 100644 index 0000000000000000000000000000000000000000..24bc24663f915ca81226a70237ae12b7b39321ea --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/tree.py @@ -0,0 +1,333 @@ +"""A tree representation of a linear markdown-it token stream. + +This module is not part of upstream JavaScript markdown-it. +""" + +from __future__ import annotations + +from collections.abc import Generator, Sequence +import textwrap +from typing import Any, NamedTuple, TypeVar, overload + +from .token import Token + + +class _NesterTokens(NamedTuple): + opening: Token + closing: Token + + +_NodeType = TypeVar("_NodeType", bound="SyntaxTreeNode") + + +class SyntaxTreeNode: + """A Markdown syntax tree node. + + A class that can be used to construct a tree representation of a linear + `markdown-it-py` token stream. + + Each node in the tree represents either: + - root of the Markdown document + - a single unnested `Token` + - a `Token` "_open" and "_close" token pair, and the tokens nested in + between + """ + + def __init__( + self, tokens: Sequence[Token] = (), *, create_root: bool = True + ) -> None: + """Initialize a `SyntaxTreeNode` from a token stream. + + If `create_root` is True, create a root node for the document. + """ + # Only nodes representing an unnested token have self.token + self.token: Token | None = None + + # Only containers have nester tokens + self.nester_tokens: _NesterTokens | None = None + + # Root node does not have self.parent + self._parent: Any = None + + # Empty list unless a non-empty container, or unnested token that has + # children (i.e. inline or img) + self._children: list[Any] = [] + + if create_root: + self._set_children_from_tokens(tokens) + return + + if not tokens: + raise ValueError( + "Can only create root from empty token sequence." + " Set `create_root=True`." + ) + elif len(tokens) == 1: + inline_token = tokens[0] + if inline_token.nesting: + raise ValueError( + "Unequal nesting level at the start and end of token stream." + ) + self.token = inline_token + if inline_token.children: + self._set_children_from_tokens(inline_token.children) + else: + self.nester_tokens = _NesterTokens(tokens[0], tokens[-1]) + self._set_children_from_tokens(tokens[1:-1]) + + def __repr__(self) -> str: + return f"{type(self).__name__}({self.type})" + + @overload + def __getitem__(self: _NodeType, item: int) -> _NodeType: ... + + @overload + def __getitem__(self: _NodeType, item: slice) -> list[_NodeType]: ... + + def __getitem__(self: _NodeType, item: int | slice) -> _NodeType | list[_NodeType]: + return self.children[item] + + def to_tokens(self: _NodeType) -> list[Token]: + """Recover the linear token stream.""" + + def recursive_collect_tokens(node: _NodeType, token_list: list[Token]) -> None: + if node.type == "root": + for child in node.children: + recursive_collect_tokens(child, token_list) + elif node.token: + token_list.append(node.token) + else: + assert node.nester_tokens + token_list.append(node.nester_tokens.opening) + for child in node.children: + recursive_collect_tokens(child, token_list) + token_list.append(node.nester_tokens.closing) + + tokens: list[Token] = [] + recursive_collect_tokens(self, tokens) + return tokens + + @property + def children(self: _NodeType) -> list[_NodeType]: + return self._children + + @children.setter + def children(self: _NodeType, value: list[_NodeType]) -> None: + self._children = value + + @property + def parent(self: _NodeType) -> _NodeType | None: + return self._parent # type: ignore + + @parent.setter + def parent(self: _NodeType, value: _NodeType | None) -> None: + self._parent = value + + @property + def is_root(self) -> bool: + """Is the node a special root node?""" + return not (self.token or self.nester_tokens) + + @property + def is_nested(self) -> bool: + """Is this node nested?. + + Returns `True` if the node represents a `Token` pair and tokens in the + sequence between them, where `Token.nesting` of the first `Token` in + the pair is 1 and nesting of the other `Token` is -1. + """ + return bool(self.nester_tokens) + + @property + def siblings(self: _NodeType) -> Sequence[_NodeType]: + """Get siblings of the node. + + Gets the whole group of siblings, including self. + """ + if not self.parent: + return [self] + return self.parent.children + + @property + def type(self) -> str: + """Get a string type of the represented syntax. + + - "root" for root nodes + - `Token.type` if the node represents an unnested token + - `Token.type` of the opening token, with "_open" suffix stripped, if + the node represents a nester token pair + """ + if self.is_root: + return "root" + if self.token: + return self.token.type + assert self.nester_tokens + return self.nester_tokens.opening.type.removesuffix("_open") + + @property + def next_sibling(self: _NodeType) -> _NodeType | None: + """Get the next node in the sequence of siblings. + + Returns `None` if this is the last sibling. + """ + self_index = self.siblings.index(self) + if self_index + 1 < len(self.siblings): + return self.siblings[self_index + 1] + return None + + @property + def previous_sibling(self: _NodeType) -> _NodeType | None: + """Get the previous node in the sequence of siblings. + + Returns `None` if this is the first sibling. + """ + self_index = self.siblings.index(self) + if self_index - 1 >= 0: + return self.siblings[self_index - 1] + return None + + def _add_child( + self, + tokens: Sequence[Token], + ) -> None: + """Make a child node for `self`.""" + child = type(self)(tokens, create_root=False) + child.parent = self + self.children.append(child) + + def _set_children_from_tokens(self, tokens: Sequence[Token]) -> None: + """Convert the token stream to a tree structure and set the resulting + nodes as children of `self`.""" + reversed_tokens = list(reversed(tokens)) + while reversed_tokens: + token = reversed_tokens.pop() + + if not token.nesting: + self._add_child([token]) + continue + if token.nesting != 1: + raise ValueError("Invalid token nesting") + + nested_tokens = [token] + nesting = 1 + while reversed_tokens and nesting: + token = reversed_tokens.pop() + nested_tokens.append(token) + nesting += token.nesting + if nesting: + raise ValueError(f"unclosed tokens starting {nested_tokens[0]}") + + self._add_child(nested_tokens) + + def pretty( + self, *, indent: int = 2, show_text: bool = False, _current: int = 0 + ) -> str: + """Create an XML style string of the tree.""" + prefix = " " * _current + text = prefix + f"<{self.type}" + if not self.is_root and self.attrs: + text += " " + " ".join(f"{k}={v!r}" for k, v in self.attrs.items()) + text += ">" + if ( + show_text + and not self.is_root + and self.type in ("text", "text_special") + and self.content + ): + text += "\n" + textwrap.indent(self.content, prefix + " " * indent) + for child in self.children: + text += "\n" + child.pretty( + indent=indent, show_text=show_text, _current=_current + indent + ) + return text + + def walk( + self: _NodeType, *, include_self: bool = True + ) -> Generator[_NodeType, None, None]: + """Recursively yield all descendant nodes in the tree starting at self. + + The order mimics the order of the underlying linear token + stream (i.e. depth first). + """ + if include_self: + yield self + for child in self.children: + yield from child.walk(include_self=True) + + # NOTE: + # The values of the properties defined below directly map to properties + # of the underlying `Token`s. A root node does not translate to a `Token` + # object, so calling these property getters on a root node will raise an + # `AttributeError`. + # + # There is no mapping for `Token.nesting` because the `is_nested` property + # provides that data, and can be called on any node type, including root. + + def _attribute_token(self) -> Token: + """Return the `Token` that is used as the data source for the + properties defined below.""" + if self.token: + return self.token + if self.nester_tokens: + return self.nester_tokens.opening + raise AttributeError("Root node does not have the accessed attribute") + + @property + def tag(self) -> str: + """html tag name, e.g. \"p\"""" + return self._attribute_token().tag + + @property + def attrs(self) -> dict[str, str | int | float]: + """Html attributes.""" + return self._attribute_token().attrs + + def attrGet(self, name: str) -> None | str | int | float: + """Get the value of attribute `name`, or null if it does not exist.""" + return self._attribute_token().attrGet(name) + + @property + def map(self) -> tuple[int, int] | None: + """Source map info. Format: `tuple[ line_begin, line_end ]`""" + map_ = self._attribute_token().map + if map_: + # Type ignore because `Token`s attribute types are not perfect + return tuple(map_) # type: ignore + return None + + @property + def level(self) -> int: + """nesting level, the same as `state.level`""" + return self._attribute_token().level + + @property + def content(self) -> str: + """In a case of self-closing tag (code, html, fence, etc.), it + has contents of this tag.""" + return self._attribute_token().content + + @property + def markup(self) -> str: + """'*' or '_' for emphasis, fence string for fence, etc.""" + return self._attribute_token().markup + + @property + def info(self) -> str: + """fence infostring""" + return self._attribute_token().info + + @property + def meta(self) -> dict[Any, Any]: + """A place for plugins to store an arbitrary data.""" + return self._attribute_token().meta + + @property + def block(self) -> bool: + """True for block-level tokens, false for inline tokens.""" + return self._attribute_token().block + + @property + def hidden(self) -> bool: + """If it's true, ignore this element when rendering. + Used for tight lists to hide paragraphs.""" + return self._attribute_token().hidden diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/utils.py b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..09e60163160c6ab5d98af36b209d7f4ff9c6b1f8 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it/utils.py @@ -0,0 +1,194 @@ +from __future__ import annotations + +from collections.abc import Callable, Iterable, MutableMapping +from collections.abc import MutableMapping as MutableMappingABC +from pathlib import Path +from typing import TYPE_CHECKING, Any, TypedDict, cast + +if TYPE_CHECKING: + from typing_extensions import NotRequired + + +EnvType = MutableMapping[str, Any] # note: could use TypeAlias in python 3.10 +"""Type for the environment sandbox used in parsing and rendering, +which stores mutable variables for use by plugins and rules. +""" + + +class OptionsType(TypedDict): + """Options for parsing.""" + + maxNesting: int + """Internal protection, recursion limit.""" + html: bool + """Enable HTML tags in source.""" + linkify: bool + """Enable autoconversion of URL-like texts to links.""" + typographer: bool + """Enable smartquotes and replacements.""" + quotes: str + """Quote characters.""" + xhtmlOut: bool + """Use '/' to close single tags (
    ).""" + breaks: bool + """Convert newlines in paragraphs into
    .""" + langPrefix: str + """CSS language prefix for fenced blocks.""" + highlight: Callable[[str, str, str], str] | None + """Highlighter function: (content, lang, attrs) -> str.""" + store_labels: NotRequired[bool] + """Store link label in link/image token's metadata (under Token.meta['label']). + + This is a Python only option, and is intended for the use of round-trip parsing. + """ + tasklists: NotRequired[bool] + """Enable GFM task list checkbox detection in list items.""" + alerts: NotRequired[bool] + """Enable GitHub-style alert detection in blockquotes.""" + tasklists_editable: NotRequired[bool] + """When True, rendered task list checkboxes are interactive (no disabled attribute).""" + strikethrough_single_tilde: NotRequired[bool] + """Allow single tilde ``~text~`` for strikethrough in addition to double.""" + + +class PresetType(TypedDict): + """Preset configuration for markdown-it.""" + + options: OptionsType + """Options for parsing.""" + components: MutableMapping[str, MutableMapping[str, list[str]]] + """Components for parsing and rendering.""" + + +class OptionsDict(MutableMappingABC): # type: ignore + """A dictionary, with attribute access to core markdownit configuration options.""" + + # Note: ideally we would probably just remove attribute access entirely, + # but we keep it for backwards compatibility. + + def __init__(self, options: OptionsType) -> None: + self._options = cast(OptionsType, dict(options)) + + def __getitem__(self, key: str) -> Any: + return self._options[key] # type: ignore[literal-required] + + def __setitem__(self, key: str, value: Any) -> None: + self._options[key] = value # type: ignore[literal-required] + + def __delitem__(self, key: str) -> None: + del self._options[key] # type: ignore + + def __iter__(self) -> Iterable[str]: # type: ignore + return iter(self._options) + + def __len__(self) -> int: + return len(self._options) + + def __repr__(self) -> str: + return repr(self._options) + + def __str__(self) -> str: + return str(self._options) + + @property + def maxNesting(self) -> int: + """Internal protection, recursion limit.""" + return self._options["maxNesting"] + + @maxNesting.setter + def maxNesting(self, value: int) -> None: + self._options["maxNesting"] = value + + @property + def html(self) -> bool: + """Enable HTML tags in source.""" + return self._options["html"] + + @html.setter + def html(self, value: bool) -> None: + self._options["html"] = value + + @property + def linkify(self) -> bool: + """Enable autoconversion of URL-like texts to links.""" + return self._options["linkify"] + + @linkify.setter + def linkify(self, value: bool) -> None: + self._options["linkify"] = value + + @property + def typographer(self) -> bool: + """Enable smartquotes and replacements.""" + return self._options["typographer"] + + @typographer.setter + def typographer(self, value: bool) -> None: + self._options["typographer"] = value + + @property + def quotes(self) -> str: + """Quote characters.""" + return self._options["quotes"] + + @quotes.setter + def quotes(self, value: str) -> None: + self._options["quotes"] = value + + @property + def xhtmlOut(self) -> bool: + """Use '/' to close single tags (
    ).""" + return self._options["xhtmlOut"] + + @xhtmlOut.setter + def xhtmlOut(self, value: bool) -> None: + self._options["xhtmlOut"] = value + + @property + def breaks(self) -> bool: + """Convert newlines in paragraphs into
    .""" + return self._options["breaks"] + + @breaks.setter + def breaks(self, value: bool) -> None: + self._options["breaks"] = value + + @property + def langPrefix(self) -> str: + """CSS language prefix for fenced blocks.""" + return self._options["langPrefix"] + + @langPrefix.setter + def langPrefix(self, value: str) -> None: + self._options["langPrefix"] = value + + @property + def highlight(self) -> Callable[[str, str, str], str] | None: + """Highlighter function: (content, langName, langAttrs) -> escaped HTML.""" + return self._options["highlight"] + + @highlight.setter + def highlight(self, value: Callable[[str, str, str], str] | None) -> None: + self._options["highlight"] = value + + +def read_fixture_file(path: str | Path) -> list[list[Any]]: + text = Path(path).read_text(encoding="utf-8") + tests = [] + section = 0 + last_pos = 0 + lines = text.splitlines(keepends=True) + for i in range(len(lines)): + if lines[i].rstrip() == ".": + if section == 0: + tests.append([i, lines[i - 1].strip()]) + section = 1 + elif section == 1: + tests[-1].append("".join(lines[last_pos + 1 : i])) + section = 2 + elif section == 2: + tests[-1].append("".join(lines[last_pos + 1 : i])) + section = 0 + + last_pos = i + return tests diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it_py-4.2.0.dist-info/INSTALLER b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it_py-4.2.0.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..5c69047b2eb8235994febeeae1da4a82365a240a --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it_py-4.2.0.dist-info/INSTALLER @@ -0,0 +1 @@ +uv \ No newline at end of file diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it_py-4.2.0.dist-info/METADATA b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it_py-4.2.0.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..c44604a66772697fba39a4e24d69d0f3d7bb3c1b --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it_py-4.2.0.dist-info/METADATA @@ -0,0 +1,221 @@ +Metadata-Version: 2.4 +Name: markdown-it-py +Version: 4.2.0 +Summary: Python port of markdown-it. Markdown parsing, done right! +Keywords: markdown,lexer,parser,commonmark,markdown-it +Author-email: Chris Sewell +Requires-Python: >=3.10 +Description-Content-Type: text/markdown +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: Text Processing :: Markup +License-File: LICENSE +License-File: LICENSE.markdown-it +Requires-Dist: mdurl~=0.1 +Requires-Dist: psutil ; extra == "benchmarking" +Requires-Dist: pytest ; extra == "benchmarking" +Requires-Dist: pytest-benchmark ; extra == "benchmarking" +Requires-Dist: commonmark~=0.9 ; extra == "compare" +Requires-Dist: markdown~=3.4 ; extra == "compare" +Requires-Dist: mistletoe~=1.0 ; extra == "compare" +Requires-Dist: mistune~=3.0 ; extra == "compare" +Requires-Dist: panflute~=2.3 ; extra == "compare" +Requires-Dist: markdown-it-pyrs ; extra == "compare" +Requires-Dist: linkify-it-py>=1,<3 ; extra == "linkify" +Requires-Dist: mdit-py-plugins>=0.5.0 ; extra == "plugins" +Requires-Dist: gprof2dot ; extra == "profiling" +Requires-Dist: mdit-py-plugins>=0.5.0 ; extra == "rtd" +Requires-Dist: myst-parser ; extra == "rtd" +Requires-Dist: pyyaml ; extra == "rtd" +Requires-Dist: sphinx ; extra == "rtd" +Requires-Dist: sphinx-copybutton ; extra == "rtd" +Requires-Dist: sphinx-design ; extra == "rtd" +Requires-Dist: sphinx-book-theme~=1.0 ; extra == "rtd" +Requires-Dist: jupyter_sphinx ; extra == "rtd" +Requires-Dist: ipykernel ; extra == "rtd" +Requires-Dist: coverage ; extra == "testing" +Requires-Dist: pytest ; extra == "testing" +Requires-Dist: pytest-cov ; extra == "testing" +Requires-Dist: pytest-regressions ; extra == "testing" +Requires-Dist: pytest-timeout ; extra == "testing" +Requires-Dist: requests ; extra == "testing" +Project-URL: Documentation, https://markdown-it-py.readthedocs.io +Project-URL: Homepage, https://github.com/executablebooks/markdown-it-py +Provides-Extra: benchmarking +Provides-Extra: compare +Provides-Extra: linkify +Provides-Extra: plugins +Provides-Extra: profiling +Provides-Extra: rtd +Provides-Extra: testing + +# markdown-it-py + +[![Github-CI][github-ci]][github-link] +[![Coverage Status][codecov-badge]][codecov-link] +[![PyPI][pypi-badge]][pypi-link] +[![Conda][conda-badge]][conda-link] +[![PyPI - Downloads][install-badge]][install-link] + +

    + markdown-it-py icon +

    + +> Markdown parser done right. + +- Follows the __[CommonMark spec](http://spec.commonmark.org/)__ for baseline parsing +- Configurable syntax: you can add new rules and even replace existing ones. +- Pluggable: Adds syntax extensions to extend the parser (see the [plugin list][md-plugins]). +- High speed (see our [benchmarking tests][md-performance]) +- Easy to configure for [security][md-security] +- Member of [Google's Assured Open Source Software](https://cloud.google.com/assured-open-source-software/docs/supported-packages) + +This is a Python port of [markdown-it], and some of its associated plugins. +For more details see: . + +For details on [markdown-it] itself, see: + +- The __[Live demo](https://markdown-it.github.io)__ +- [The markdown-it README][markdown-it-readme] + +**See also:** [markdown-it-pyrs](https://github.com/chrisjsewell/markdown-it-pyrs) for an experimental Rust binding, +for even more speed! + +## Installation + +### PIP + +```bash +pip install markdown-it-py[plugins] +``` + +or with extras + +```bash +pip install markdown-it-py[linkify,plugins] +``` + +### Conda + +```bash +conda install -c conda-forge markdown-it-py +``` + +or with extras + +```bash +conda install -c conda-forge markdown-it-py linkify-it-py mdit-py-plugins +``` + +## Usage + +### Python API Usage + +Render markdown to HTML with markdown-it-py and a custom configuration +with and without plugins and features: + +```python +from markdown_it import MarkdownIt +from mdit_py_plugins.front_matter import front_matter_plugin +from mdit_py_plugins.footnote import footnote_plugin + +md = ( + MarkdownIt('commonmark', {'breaks':True,'html':True}) + .use(front_matter_plugin) + .use(footnote_plugin) + .enable('table') +) +text = (""" +--- +a: 1 +--- + +a | b +- | - +1 | 2 + +A footnote [^1] + +[^1]: some details +""") +tokens = md.parse(text) +html_text = md.render(text) + +## To export the html to a file, uncomment the lines below: +# from pathlib import Path +# Path("output.html").write_text(html_text) +``` + +### Command-line Usage + +Render markdown to HTML with markdown-it-py from the +command-line: + +```console +usage: markdown-it [-h] [-v] [--stdin|filenames [filenames ...]] + +Parse one or more markdown files, convert each to HTML, and print to stdout + +positional arguments: + --stdin read source Markdown file from standard input + filenames specify an optional list of files to convert + +optional arguments: + -h, --help show this help message and exit + -v, --version show program's version number and exit + +Interactive: + + $ markdown-it + markdown-it-py [version 0.0.0] (interactive) + Type Ctrl-D to complete input, or Ctrl-C to exit. + >>> # Example + ... > markdown *input* + ... +

    Example

    +
    +

    markdown input

    +
    + +Batch: + + $ markdown-it README.md README.footer.md > index.html + +``` + +## References / Thanks + +Big thanks to the authors of [markdown-it]: + +- Alex Kocharin [github/rlidwka](https://github.com/rlidwka) +- Vitaly Puzrin [github/puzrin](https://github.com/puzrin) + +Also [John MacFarlane](https://github.com/jgm) for his work on the CommonMark spec and reference implementations. + +[github-ci]: https://github.com/executablebooks/markdown-it-py/actions/workflows/tests.yml/badge.svg?branch=master +[github-link]: https://github.com/executablebooks/markdown-it-py +[pypi-badge]: https://img.shields.io/pypi/v/markdown-it-py.svg +[pypi-link]: https://pypi.org/project/markdown-it-py +[conda-badge]: https://anaconda.org/conda-forge/markdown-it-py/badges/version.svg +[conda-link]: https://anaconda.org/conda-forge/markdown-it-py +[codecov-badge]: https://codecov.io/gh/executablebooks/markdown-it-py/branch/master/graph/badge.svg +[codecov-link]: https://codecov.io/gh/executablebooks/markdown-it-py +[install-badge]: https://img.shields.io/pypi/dw/markdown-it-py?label=pypi%20installs +[install-link]: https://pypistats.org/packages/markdown-it-py + +[CommonMark spec]: http://spec.commonmark.org/ +[markdown-it]: https://github.com/markdown-it/markdown-it +[markdown-it-readme]: https://github.com/markdown-it/markdown-it/blob/master/README.md +[md-security]: https://markdown-it-py.readthedocs.io/en/latest/security.html +[md-performance]: https://markdown-it-py.readthedocs.io/en/latest/performance.html +[md-plugins]: https://markdown-it-py.readthedocs.io/en/latest/plugins.html + diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it_py-4.2.0.dist-info/RECORD b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it_py-4.2.0.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..0435611304aecd54989f943720d83db4626e19b5 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it_py-4.2.0.dist-info/RECORD @@ -0,0 +1,77 @@ +../../../bin/markdown-it,sha256=K5gyYTQyNJMi53QlY_VFTyAbGNt93UpSKaj9YYef2rA,337 +markdown_it/__init__.py,sha256=rb0zsebNRqT8YvcnYnsQpV60bzn37bdR7omj9fooQ5U,114 +markdown_it/_compat.py,sha256=U4S_2y3zgLZVfMenHRaJFBW8yqh2mUBuI291LGQVOJ8,35 +markdown_it/_punycode.py,sha256=JvSOZJ4VKr58z7unFGM0KhfTxqHMk2w8gglxae2QszM,2373 +markdown_it/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +markdown_it/cli/parse.py,sha256=2UGx9QdPdMGpp6CuDdOVI8BOzQ9m_pgSxzrOKo4oQls,3362 +markdown_it/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +markdown_it/common/entities.py,sha256=EYRCmUL7ZU1FRGLSXQlPx356lY8EUBdFyx96eSGc6d0,157 +markdown_it/common/html_blocks.py,sha256=QXbUDMoN9lXLgYFk2DBYllnLiFukL6dHn2X98Y6Wews,986 +markdown_it/common/html_re.py,sha256=FggAEv9IL8gHQqsGTkHcf333rTojwG0DQJMH9oVu0fU,926 +markdown_it/common/normalize_url.py,sha256=avOXnLd9xw5jU1q5PLftjAM9pvGx8l9QDEkmZSyrMgg,2568 +markdown_it/common/utils.py,sha256=pMgvMOE3ZW-BdJ7HfuzlXNKyD1Ivk7jHErc2J_B8J5M,8734 +markdown_it/helpers/__init__.py,sha256=YH2z7dS0WUc_9l51MWPvrLtFoBPh4JLGw58OuhGRCK0,253 +markdown_it/helpers/parse_link_destination.py,sha256=u-xxWVP3g1s7C1bQuQItiYyDrYoYHJzXaZXPgr-o6mY,1906 +markdown_it/helpers/parse_link_label.py,sha256=PIHG6ZMm3BUw0a2m17lCGqNrl3vaz911tuoGviWD3I4,1037 +markdown_it/helpers/parse_link_title.py,sha256=jkLoYQMKNeX9bvWQHkaSroiEo27HylkEUNmj8xBRlp4,2273 +markdown_it/main.py,sha256=n82mUACD9qmw2ckvWGXIkG9QEVjsw41-V-oNrFhjACM,12759 +markdown_it/parser_block.py,sha256=-MyugXB63Te71s4NcSQZiK5bE6BHkdFyZv_bviuatdI,3939 +markdown_it/parser_core.py,sha256=SRmJjqe8dC6GWzEARpWba59cBmxjCr3Gsg8h29O8sQk,1016 +markdown_it/parser_inline.py,sha256=uAuiZ1zDonEqKB4_sdckJP6WYu8A7njJlFWMAb6C8co,7283 +markdown_it/port.yaml,sha256=jt_rdwOnfocOV5nc35revTybAAQMIp_-1fla_527sVE,2447 +markdown_it/presets/__init__.py,sha256=unRW-8XrXgYzXpo6xEXj7R_BmSKXwVHZ8ElVh3x0U-A,1583 +markdown_it/presets/commonmark.py,sha256=ygfb0R7WQ_ZoyQP3df-B0EnYMqNXCVOSw9SAdMjsGow,2869 +markdown_it/presets/default.py,sha256=FfKVUI0HH3M-_qy6RwotLStdC4PAaAxE7Dq0_KQtRtc,1811 +markdown_it/presets/zero.py,sha256=okXWTBEI-2nmwx5XKeCjxInRf65oC11gahtRl-QNtHM,2113 +markdown_it/py.typed,sha256=8PjyZ1aVoQpRVvt71muvuq5qE-jTFZkK-GLHkhdebmc,26 +markdown_it/renderer.py,sha256=7gdek7-UOV3NZU4-we5lsuQd8a-odgrz7HNcwCh_U1g,10628 +markdown_it/ruler.py,sha256=eMAtWGRAfSM33aiJed0k5923BEkuMVsMq1ct8vU-ql4,9142 +markdown_it/rules_block/__init__.py,sha256=y1PlajI4X2C5YauOsgpj5n-8vX37JepdjAwuzurtJW4,593 +markdown_it/rules_block/blockquote.py,sha256=H4bXyqca87ytUbCBXIEJGJwbPTEh4P1RifWlcG2Y7rY,11506 +markdown_it/rules_block/code.py,sha256=iTAxv0U1-MDhz88M1m1pi2vzOhEMSEROsXMo2Qq--kU,860 +markdown_it/rules_block/fence.py,sha256=USDqhzETLTyDDOk8qcDxNf61gFgQwWzpmfy2V-Y3t1o,4784 +markdown_it/rules_block/heading.py,sha256=4Lh15rwoVsQjE1hVhpbhidQ0k9xKHihgjAeYSbwgO5k,1745 +markdown_it/rules_block/hr.py,sha256=QCoY5kImaQRvF7PyP8OoWft6A8JVH1v6MN-0HR9Ikpg,1227 +markdown_it/rules_block/html_block.py,sha256=wA8pb34LtZr1BkIATgGKQBIGX5jQNOkwZl9UGEqvb5M,2721 +markdown_it/rules_block/lheading.py,sha256=fWoEuUo7S2svr5UMKmyQMkh0hheYAHg2gMM266Mogs4,2625 +markdown_it/rules_block/list.py,sha256=QWHaFxZrhoDbAXBjm9scRCiBuCDxlv2JBI9VgsHyZ4w,12039 +markdown_it/rules_block/paragraph.py,sha256=9pmCwA7eMu4LBdV4fWKzC4EdwaOoaGw2kfeYSQiLye8,1819 +markdown_it/rules_block/reference.py,sha256=ue1qZbUaUP0GIvwTjh6nD1UtCij8uwsIMuYW1xBkckc,6983 +markdown_it/rules_block/state_block.py,sha256=HowsQyy5hGUibH4HRZWKfLIlXeDUnuWL7kpF0-rSwoM,8422 +markdown_it/rules_block/table.py,sha256=8nMd9ONGOffER7BXmc9kbbhxkLjtpX79dVLR0iatGnM,7682 +markdown_it/rules_core/__init__.py,sha256=QFGBe9TUjnRQJDU7xY4SQYpxyTHNwg8beTSwXpNGRjE,394 +markdown_it/rules_core/block.py,sha256=0_JY1CUy-H2OooFtIEZAACtuoGUMohgxo4Z6A_UinSg,372 +markdown_it/rules_core/inline.py,sha256=9oWmeBhJHE7x47oJcN9yp6UsAZtrEY_A-VmfoMvKld4,325 +markdown_it/rules_core/linkify.py,sha256=mjQqpk_lHLh2Nxw4UFaLxa47Fgi-OHnmDamlgXnhmv0,5141 +markdown_it/rules_core/normalize.py,sha256=AJm4femtFJ_QBnM0dzh0UNqTTJk9K6KMtwRPaioZFqM,403 +markdown_it/rules_core/replacements.py,sha256=CH75mie-tdzdLKQtMBuCTcXAl1ijegdZGfbV_Vk7st0,3471 +markdown_it/rules_core/smartquotes.py,sha256=izK9fSyuTzA-zAUGkRkz9KwwCQWo40iRqcCKqOhFbEE,7443 +markdown_it/rules_core/state_core.py,sha256=HqWZCUr5fW7xG6jeQZDdO0hE9hxxyl3_-bawgOy57HY,570 +markdown_it/rules_core/text_join.py,sha256=5y1LLfO0r4XSkA7cJHFuNkkvf3Y9JkyB8Cyjdwm71Fs,1998 +markdown_it/rules_inline/__init__.py,sha256=qqHZk6-YE8Rc12q6PxvVKBaxv2wmZeeo45H1XMR_Vxs,696 +markdown_it/rules_inline/autolink.py,sha256=pPoqJY8i99VtFn7KgUzMackMeq1hytzioVvWs-VQPRo,2065 +markdown_it/rules_inline/backticks.py,sha256=J7bezjjNxiXlKqvHc0fJkHZwH7-2nBsXVjcKydk8E4M,2037 +markdown_it/rules_inline/balance_pairs.py,sha256=5zgBiGidqdiWmt7Io_cuZOYh5EFEfXrYRce8RXg5m7o,4852 +markdown_it/rules_inline/emphasis.py,sha256=7aDLZx0Jlekuvbu3uEUTDhJp00Z0Pj6g4C3-VLhI8Co,3123 +markdown_it/rules_inline/entity.py,sha256=CE8AIGMi5isEa24RNseo0wRmTTaj5YLbgTFdDmBesAU,1651 +markdown_it/rules_inline/escape.py,sha256=KGulwrP5FnqZM7GXY8lf7pyVv0YkR59taZDeHb5cmKg,1659 +markdown_it/rules_inline/fragments_join.py,sha256=y61sHsE-OCy_bhwviILlSTkLk5mEWKcjU1HmowLyR2o,1984 +markdown_it/rules_inline/html_inline.py,sha256=SBg6HR0HRqCdrkkec0dfOYuQdAqyfeLRFLeQggtgjvg,1130 +markdown_it/rules_inline/image.py,sha256=Wbsg7jgnOtKXIwXGNJOlG7ORThkMkBVolxItC0ph6C0,4141 +markdown_it/rules_inline/link.py,sha256=2oD-fAdB0xyxDRtZLTjzLeWbzJ1k9bbPVQmohb58RuI,4258 +markdown_it/rules_inline/linkify.py,sha256=ifH6sb5wE8PGMWEw9Sr4x0DhMVfNOEBCfFSwKll2O-s,1706 +markdown_it/rules_inline/newline.py,sha256=329r0V3aDjzNtJcvzA3lsFYjzgBrShLAV5uf9hwQL_M,1297 +markdown_it/rules_inline/state_inline.py,sha256=XvhjkalK_VSOs8T2r4BRzGkNWpk9pOEwDglIzazIvcQ,4993 +markdown_it/rules_inline/strikethrough.py,sha256=OOsUhecmIXDTSt7x7088T6L7z4TefxUGpevK07VJQzA,5171 +markdown_it/rules_inline/text.py,sha256=JLF6T7DxBUxNKjZPV1EHowduV7E9A1e3lVmIlu7NvlE,559 +markdown_it/token.py,sha256=cWrt9kodfPdizHq_tYrzyIZNtJYNMN1813DPNlunwTg,6381 +markdown_it/tree.py,sha256=j1j_Lo8TzrT9LvbQM09YzgHZhMPo0oFJnBY0BTA41_k,11110 +markdown_it/utils.py,sha256=ldiolLr-41JOooehPZ5-T-QdWXYYC8NHSytiqgeZaf0,6142 +markdown_it_py-4.2.0.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2 +markdown_it_py-4.2.0.dist-info/METADATA,sha256=csNv9RZ1VEOx7LYeF7JWepkCMLSaUEsOsMQtX4JEipw,7410 +markdown_it_py-4.2.0.dist-info/RECORD,, +markdown_it_py-4.2.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +markdown_it_py-4.2.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82 +markdown_it_py-4.2.0.dist-info/entry_points.txt,sha256=T81l7fHQ3pllpQ4wUtQK6a8g_p6wxQbnjKVHCk2WMG4,58 +markdown_it_py-4.2.0.dist-info/licenses/LICENSE,sha256=SiJg1uLND1oVGh6G2_59PtVSseK-q_mUHBulxJy85IQ,1078 +markdown_it_py-4.2.0.dist-info/licenses/LICENSE.markdown-it,sha256=eSxIxahJoV_fnjfovPnm0d0TsytGxkKnSKCkapkZ1HM,1073 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it_py-4.2.0.dist-info/REQUESTED b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it_py-4.2.0.dist-info/REQUESTED new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it_py-4.2.0.dist-info/WHEEL b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it_py-4.2.0.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..d8b9936dad9ab2513fa6979f411560d3b6b57e37 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it_py-4.2.0.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: flit 3.12.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it_py-4.2.0.dist-info/entry_points.txt b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it_py-4.2.0.dist-info/entry_points.txt new file mode 100644 index 0000000000000000000000000000000000000000..7d829cd792a5d754844f433c6a8dd499564fdcbf --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it_py-4.2.0.dist-info/entry_points.txt @@ -0,0 +1,3 @@ +[console_scripts] +markdown-it=markdown_it.cli.parse:main + diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it_py-4.2.0.dist-info/licenses/LICENSE b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it_py-4.2.0.dist-info/licenses/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..582ddf59e08277fe6e78cee924d2c84805fe36fe --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it_py-4.2.0.dist-info/licenses/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 ExecutableBookProject + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it_py-4.2.0.dist-info/licenses/LICENSE.markdown-it b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it_py-4.2.0.dist-info/licenses/LICENSE.markdown-it new file mode 100644 index 0000000000000000000000000000000000000000..7ffa058cb78f8fb9beb974d9fd429004d2d2e585 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/markdown_it_py-4.2.0.dist-info/licenses/LICENSE.markdown-it @@ -0,0 +1,22 @@ +Copyright (c) 2014 Vitaly Puzrin, Alex Kocharin. + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/mdurl-0.1.2.dist-info/INSTALLER b/bioagent-bench/.venv/lib/python3.14/site-packages/mdurl-0.1.2.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..5c69047b2eb8235994febeeae1da4a82365a240a --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/mdurl-0.1.2.dist-info/INSTALLER @@ -0,0 +1 @@ +uv \ No newline at end of file diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/mdurl-0.1.2.dist-info/LICENSE b/bioagent-bench/.venv/lib/python3.14/site-packages/mdurl-0.1.2.dist-info/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..2a920c59d8abdd485a774087915986448495fd7c --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/mdurl-0.1.2.dist-info/LICENSE @@ -0,0 +1,46 @@ +Copyright (c) 2015 Vitaly Puzrin, Alex Kocharin. +Copyright (c) 2021 Taneli Hukkinen + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +-------------------------------------------------------------------------------- + +.parse() is based on Joyent's node.js `url` code: + +Copyright Joyent, Inc. and other Node contributors. All rights reserved. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/mdurl-0.1.2.dist-info/METADATA b/bioagent-bench/.venv/lib/python3.14/site-packages/mdurl-0.1.2.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..b4670e86b6dc207c944c55c5d3b84911fb41157a --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/mdurl-0.1.2.dist-info/METADATA @@ -0,0 +1,32 @@ +Metadata-Version: 2.1 +Name: mdurl +Version: 0.1.2 +Summary: Markdown URL utilities +Keywords: markdown,commonmark +Author-email: Taneli Hukkinen +Requires-Python: >=3.7 +Description-Content-Type: text/markdown +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: MacOS +Classifier: Operating System :: Microsoft :: Windows +Classifier: Operating System :: POSIX :: Linux +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Typing :: Typed +Project-URL: Homepage, https://github.com/executablebooks/mdurl + +# mdurl + +[![Build Status](https://github.com/executablebooks/mdurl/workflows/Tests/badge.svg?branch=master)](https://github.com/executablebooks/mdurl/actions?query=workflow%3ATests+branch%3Amaster+event%3Apush) +[![codecov.io](https://codecov.io/gh/executablebooks/mdurl/branch/master/graph/badge.svg)](https://codecov.io/gh/executablebooks/mdurl) +[![PyPI version](https://img.shields.io/pypi/v/mdurl)](https://pypi.org/project/mdurl) + +This is a Python port of the JavaScript [mdurl](https://www.npmjs.com/package/mdurl) package. +See the [upstream README.md file](https://github.com/markdown-it/mdurl/blob/master/README.md) for API documentation. + diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/mdurl-0.1.2.dist-info/RECORD b/bioagent-bench/.venv/lib/python3.14/site-packages/mdurl-0.1.2.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..74d4c2950c1b50f01a1cc2b871bb993e0641107b --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/mdurl-0.1.2.dist-info/RECORD @@ -0,0 +1,13 @@ +mdurl-0.1.2.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2 +mdurl-0.1.2.dist-info/LICENSE,sha256=fGBd9uKGZ6lgMRjpgnT2SknOPu0NJvzM6VNKNF4O-VU,2338 +mdurl-0.1.2.dist-info/METADATA,sha256=tTsp1I9Jk2cFP9o8gefOJ9JVg4Drv4PmYCOwLrfd0l0,1638 +mdurl-0.1.2.dist-info/RECORD,, +mdurl-0.1.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +mdurl-0.1.2.dist-info/WHEEL,sha256=4TfKIB_xu-04bc2iKz6_zFt-gEFEEDU_31HGhqzOCE8,81 +mdurl/__init__.py,sha256=1vpE89NyXniIRZNC_4f6BPm3Ub4bPntjfyyhLRR7opU,547 +mdurl/_decode.py,sha256=3Q_gDQqU__TvDbu7x-b9LjbVl4QWy5g_qFwljcuvN_Y,3004 +mdurl/_encode.py,sha256=goJLUFt1h4rVZNqqm9t15Nw2W-bFXYQEy3aR01ImWvs,2602 +mdurl/_format.py,sha256=xZct0mdePXA0H3kAqxjGtlB5O86G35DAYMGkA44CmB4,626 +mdurl/_parse.py,sha256=ezZSkM2_4NQ2Zx047sEdcJG7NYQRFHiZK7Y8INHFzwY,11374 +mdurl/_url.py,sha256=5kQnRQN2A_G4svLnRzZcG0bfoD9AbBrYDXousDHZ3z0,284 +mdurl/py.typed,sha256=8PjyZ1aVoQpRVvt71muvuq5qE-jTFZkK-GLHkhdebmc,26 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/mdurl-0.1.2.dist-info/REQUESTED b/bioagent-bench/.venv/lib/python3.14/site-packages/mdurl-0.1.2.dist-info/REQUESTED new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/mdurl-0.1.2.dist-info/WHEEL b/bioagent-bench/.venv/lib/python3.14/site-packages/mdurl-0.1.2.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..668ba4d0151c5c76ed6e758061daa8c1b0bf5d21 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/mdurl-0.1.2.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: flit 3.7.1 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/mdurl/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/mdurl/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..cdbb640e004cef0e950a656a53d92d89d82c7472 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/mdurl/__init__.py @@ -0,0 +1,18 @@ +__all__ = ( + "decode", + "DECODE_DEFAULT_CHARS", + "DECODE_COMPONENT_CHARS", + "encode", + "ENCODE_DEFAULT_CHARS", + "ENCODE_COMPONENT_CHARS", + "format", + "parse", + "URL", +) +__version__ = "0.1.2" # DO NOT EDIT THIS LINE MANUALLY. LET bump2version UTILITY DO IT + +from mdurl._decode import DECODE_COMPONENT_CHARS, DECODE_DEFAULT_CHARS, decode +from mdurl._encode import ENCODE_COMPONENT_CHARS, ENCODE_DEFAULT_CHARS, encode +from mdurl._format import format +from mdurl._parse import url_parse as parse +from mdurl._url import URL diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/mdurl/_decode.py b/bioagent-bench/.venv/lib/python3.14/site-packages/mdurl/_decode.py new file mode 100644 index 0000000000000000000000000000000000000000..9b50a2dde976a6d43491ec6f20d12e60f6f6597f --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/mdurl/_decode.py @@ -0,0 +1,104 @@ +from __future__ import annotations + +from collections.abc import Sequence +import functools +import re + +DECODE_DEFAULT_CHARS = ";/?:@&=+$,#" +DECODE_COMPONENT_CHARS = "" + +decode_cache: dict[str, list[str]] = {} + + +def get_decode_cache(exclude: str) -> Sequence[str]: + if exclude in decode_cache: + return decode_cache[exclude] + + cache: list[str] = [] + decode_cache[exclude] = cache + + for i in range(128): + ch = chr(i) + cache.append(ch) + + for i in range(len(exclude)): + ch_code = ord(exclude[i]) + cache[ch_code] = "%" + ("0" + hex(ch_code)[2:].upper())[-2:] + + return cache + + +# Decode percent-encoded string. +# +def decode(string: str, exclude: str = DECODE_DEFAULT_CHARS) -> str: + cache = get_decode_cache(exclude) + repl_func = functools.partial(repl_func_with_cache, cache=cache) + return re.sub(r"(%[a-f0-9]{2})+", repl_func, string, flags=re.IGNORECASE) + + +def repl_func_with_cache(match: re.Match, cache: Sequence[str]) -> str: + seq = match.group() + result = "" + + i = 0 + l = len(seq) # noqa: E741 + while i < l: + b1 = int(seq[i + 1 : i + 3], 16) + + if b1 < 0x80: + result += cache[b1] + i += 3 # emulate JS for loop statement3 + continue + + if (b1 & 0xE0) == 0xC0 and (i + 3 < l): + # 110xxxxx 10xxxxxx + b2 = int(seq[i + 4 : i + 6], 16) + + if (b2 & 0xC0) == 0x80: + all_bytes = bytes((b1, b2)) + try: + result += all_bytes.decode() + except UnicodeDecodeError: + result += "\ufffd" * 2 + + i += 3 + i += 3 # emulate JS for loop statement3 + continue + + if (b1 & 0xF0) == 0xE0 and (i + 6 < l): + # 1110xxxx 10xxxxxx 10xxxxxx + b2 = int(seq[i + 4 : i + 6], 16) + b3 = int(seq[i + 7 : i + 9], 16) + + if (b2 & 0xC0) == 0x80 and (b3 & 0xC0) == 0x80: + all_bytes = bytes((b1, b2, b3)) + try: + result += all_bytes.decode() + except UnicodeDecodeError: + result += "\ufffd" * 3 + + i += 6 + i += 3 # emulate JS for loop statement3 + continue + + if (b1 & 0xF8) == 0xF0 and (i + 9 < l): + # 111110xx 10xxxxxx 10xxxxxx 10xxxxxx + b2 = int(seq[i + 4 : i + 6], 16) + b3 = int(seq[i + 7 : i + 9], 16) + b4 = int(seq[i + 10 : i + 12], 16) + + if (b2 & 0xC0) == 0x80 and (b3 & 0xC0) == 0x80 and (b4 & 0xC0) == 0x80: + all_bytes = bytes((b1, b2, b3, b4)) + try: + result += all_bytes.decode() + except UnicodeDecodeError: + result += "\ufffd" * 4 + + i += 9 + i += 3 # emulate JS for loop statement3 + continue + + result += "\ufffd" + i += 3 # emulate JS for loop statement3 + + return result diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/mdurl/_encode.py b/bioagent-bench/.venv/lib/python3.14/site-packages/mdurl/_encode.py new file mode 100644 index 0000000000000000000000000000000000000000..bc2e5b917afe9e9ecaa6f11af7a9ac82704d3914 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/mdurl/_encode.py @@ -0,0 +1,85 @@ +from __future__ import annotations + +from collections.abc import Sequence +from string import ascii_letters, digits, hexdigits +from urllib.parse import quote as encode_uri_component + +ASCII_LETTERS_AND_DIGITS = ascii_letters + digits + +ENCODE_DEFAULT_CHARS = ";/?:@&=+$,-_.!~*'()#" +ENCODE_COMPONENT_CHARS = "-_.!~*'()" + +encode_cache: dict[str, list[str]] = {} + + +# Create a lookup array where anything but characters in `chars` string +# and alphanumeric chars is percent-encoded. +def get_encode_cache(exclude: str) -> Sequence[str]: + if exclude in encode_cache: + return encode_cache[exclude] + + cache: list[str] = [] + encode_cache[exclude] = cache + + for i in range(128): + ch = chr(i) + + if ch in ASCII_LETTERS_AND_DIGITS: + # always allow unencoded alphanumeric characters + cache.append(ch) + else: + cache.append("%" + ("0" + hex(i)[2:].upper())[-2:]) + + for i in range(len(exclude)): + cache[ord(exclude[i])] = exclude[i] + + return cache + + +# Encode unsafe characters with percent-encoding, skipping already +# encoded sequences. +# +# - string - string to encode +# - exclude - list of characters to ignore (in addition to a-zA-Z0-9) +# - keepEscaped - don't encode '%' in a correct escape sequence (default: true) +def encode( + string: str, exclude: str = ENCODE_DEFAULT_CHARS, *, keep_escaped: bool = True +) -> str: + result = "" + + cache = get_encode_cache(exclude) + + l = len(string) # noqa: E741 + i = 0 + while i < l: + code = ord(string[i]) + + # % + if keep_escaped and code == 0x25 and i + 2 < l: + if all(c in hexdigits for c in string[i + 1 : i + 3]): + result += string[i : i + 3] + i += 2 + i += 1 # JS for loop statement3 + continue + + if code < 128: + result += cache[code] + i += 1 # JS for loop statement3 + continue + + if code >= 0xD800 and code <= 0xDFFF: + if code >= 0xD800 and code <= 0xDBFF and i + 1 < l: + next_code = ord(string[i + 1]) + if next_code >= 0xDC00 and next_code <= 0xDFFF: + result += encode_uri_component(string[i] + string[i + 1]) + i += 1 + i += 1 # JS for loop statement3 + continue + result += "%EF%BF%BD" + i += 1 # JS for loop statement3 + continue + + result += encode_uri_component(string[i]) + i += 1 # JS for loop statement3 + + return result diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/mdurl/_format.py b/bioagent-bench/.venv/lib/python3.14/site-packages/mdurl/_format.py new file mode 100644 index 0000000000000000000000000000000000000000..12524ca626065183ec9974f3d7d08dadd4a7d3e8 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/mdurl/_format.py @@ -0,0 +1,27 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from mdurl._url import URL + + +def format(url: URL) -> str: # noqa: A001 + result = "" + + result += url.protocol or "" + result += "//" if url.slashes else "" + result += url.auth + "@" if url.auth else "" + + if url.hostname and ":" in url.hostname: + # ipv6 address + result += "[" + url.hostname + "]" + else: + result += url.hostname or "" + + result += ":" + url.port if url.port else "" + result += url.pathname or "" + result += url.search or "" + result += url.hash or "" + + return result diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/mdurl/_parse.py b/bioagent-bench/.venv/lib/python3.14/site-packages/mdurl/_parse.py new file mode 100644 index 0000000000000000000000000000000000000000..ffeeac768dca3bff60c55c9b1f0bc0fbb4cec7b1 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/mdurl/_parse.py @@ -0,0 +1,304 @@ +# Copyright Joyent, Inc. and other Node contributors. +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to permit +# persons to whom the Software is furnished to do so, subject to the +# following conditions: +# +# The above copyright notice and this permission notice shall be included +# in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +# NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +# USE OR OTHER DEALINGS IN THE SOFTWARE. + + +# Changes from joyent/node: +# +# 1. No leading slash in paths, +# e.g. in `url.parse('http://foo?bar')` pathname is ``, not `/` +# +# 2. Backslashes are not replaced with slashes, +# so `http:\\example.org\` is treated like a relative path +# +# 3. Trailing colon is treated like a part of the path, +# i.e. in `http://example.org:foo` pathname is `:foo` +# +# 4. Nothing is URL-encoded in the resulting object, +# (in joyent/node some chars in auth and paths are encoded) +# +# 5. `url.parse()` does not have `parseQueryString` argument +# +# 6. Removed extraneous result properties: `host`, `path`, `query`, etc., +# which can be constructed using other parts of the url. + +from __future__ import annotations + +from collections import defaultdict +import re + +from mdurl._url import URL + +# Reference: RFC 3986, RFC 1808, RFC 2396 + +# define these here so at least they only have to be +# compiled once on the first module load. +PROTOCOL_PATTERN = re.compile(r"^([a-z0-9.+-]+:)", flags=re.IGNORECASE) +PORT_PATTERN = re.compile(r":[0-9]*$") + +# Special case for a simple path URL +SIMPLE_PATH_PATTERN = re.compile(r"^(//?(?!/)[^?\s]*)(\?[^\s]*)?$") + +# RFC 2396: characters reserved for delimiting URLs. +# We actually just auto-escape these. +DELIMS = ("<", ">", '"', "`", " ", "\r", "\n", "\t") + +# RFC 2396: characters not allowed for various reasons. +UNWISE = ("{", "}", "|", "\\", "^", "`") + DELIMS + +# Allowed by RFCs, but cause of XSS attacks. Always escape these. +AUTO_ESCAPE = ("'",) + UNWISE +# Characters that are never ever allowed in a hostname. +# Note that any invalid chars are also handled, but these +# are the ones that are *expected* to be seen, so we fast-path +# them. +NON_HOST_CHARS = ("%", "/", "?", ";", "#") + AUTO_ESCAPE +HOST_ENDING_CHARS = ("/", "?", "#") +HOSTNAME_MAX_LEN = 255 +HOSTNAME_PART_PATTERN = re.compile(r"^[+a-z0-9A-Z_-]{0,63}$") +HOSTNAME_PART_START = re.compile(r"^([+a-z0-9A-Z_-]{0,63})(.*)$") +# protocols that can allow "unsafe" and "unwise" chars. + +# protocols that never have a hostname. +HOSTLESS_PROTOCOL = defaultdict( + bool, + { + "javascript": True, + "javascript:": True, + }, +) +# protocols that always contain a // bit. +SLASHED_PROTOCOL = defaultdict( + bool, + { + "http": True, + "https": True, + "ftp": True, + "gopher": True, + "file": True, + "http:": True, + "https:": True, + "ftp:": True, + "gopher:": True, + "file:": True, + }, +) + + +class MutableURL: + def __init__(self) -> None: + self.protocol: str | None = None + self.slashes: bool = False + self.auth: str | None = None + self.port: str | None = None + self.hostname: str | None = None + self.hash: str | None = None + self.search: str | None = None + self.pathname: str | None = None + + def parse(self, url: str, slashes_denote_host: bool) -> "MutableURL": + lower_proto = "" + slashes = False + rest = url + + # trim before proceeding. + # This is to support parse stuff like " http://foo.com \n" + rest = rest.strip() + + if not slashes_denote_host and len(url.split("#")) == 1: + # Try fast path regexp + simple_path = SIMPLE_PATH_PATTERN.match(rest) + if simple_path: + self.pathname = simple_path.group(1) + if simple_path.group(2): + self.search = simple_path.group(2) + return self + + proto = "" + proto_match = PROTOCOL_PATTERN.match(rest) + if proto_match: + proto = proto_match.group() + lower_proto = proto.lower() + self.protocol = proto + rest = rest[len(proto) :] + + # figure out if it's got a host + # user@server is *always* interpreted as a hostname, and url + # resolution will treat //foo/bar as host=foo,path=bar because that's + # how the browser resolves relative URLs. + if slashes_denote_host or proto or re.search(r"^//[^@/]+@[^@/]+", rest): + slashes = rest.startswith("//") + if slashes and not (proto and HOSTLESS_PROTOCOL[proto]): + rest = rest[2:] + self.slashes = True + + if not HOSTLESS_PROTOCOL[proto] and ( + slashes or (proto and not SLASHED_PROTOCOL[proto]) + ): + + # there's a hostname. + # the first instance of /, ?, ;, or # ends the host. + # + # If there is an @ in the hostname, then non-host chars *are* allowed + # to the left of the last @ sign, unless some host-ending character + # comes *before* the @-sign. + # URLs are obnoxious. + # + # ex: + # http://a@b@c/ => user:a@b host:c + # http://a@b?@c => user:a host:c path:/?@c + + # v0.12 TODO(isaacs): This is not quite how Chrome does things. + # Review our test case against browsers more comprehensively. + + # find the first instance of any hostEndingChars + host_end = -1 + for i in range(len(HOST_ENDING_CHARS)): + hec = rest.find(HOST_ENDING_CHARS[i]) + if hec != -1 and (host_end == -1 or hec < host_end): + host_end = hec + + # at this point, either we have an explicit point where the + # auth portion cannot go past, or the last @ char is the decider. + if host_end == -1: + # atSign can be anywhere. + at_sign = rest.rfind("@") + else: + # atSign must be in auth portion. + # http://a@b/c@d => host:b auth:a path:/c@d + at_sign = rest.rfind("@", 0, host_end + 1) + + # Now we have a portion which is definitely the auth. + # Pull that off. + if at_sign != -1: + auth = rest[:at_sign] + rest = rest[at_sign + 1 :] + self.auth = auth + + # the host is the remaining to the left of the first non-host char + host_end = -1 + for i in range(len(NON_HOST_CHARS)): + hec = rest.find(NON_HOST_CHARS[i]) + if hec != -1 and (host_end == -1 or hec < host_end): + host_end = hec + # if we still have not hit it, then the entire thing is a host. + if host_end == -1: + host_end = len(rest) + + if host_end > 0 and rest[host_end - 1] == ":": + host_end -= 1 + host = rest[:host_end] + rest = rest[host_end:] + + # pull out port. + self.parse_host(host) + + # we've indicated that there is a hostname, + # so even if it's empty, it has to be present. + self.hostname = self.hostname or "" + + # if hostname begins with [ and ends with ] + # assume that it's an IPv6 address. + ipv6_hostname = self.hostname.startswith("[") and self.hostname.endswith( + "]" + ) + + # validate a little. + if not ipv6_hostname: + hostparts = self.hostname.split(".") + l = len(hostparts) # noqa: E741 + i = 0 + while i < l: + part = hostparts[i] + if not part: + i += 1 # emulate statement3 in JS for loop + continue + if not HOSTNAME_PART_PATTERN.search(part): + newpart = "" + k = len(part) + j = 0 + while j < k: + if ord(part[j]) > 127: + # we replace non-ASCII char with a temporary placeholder + # we need this to make sure size of hostname is not + # broken by replacing non-ASCII by nothing + newpart += "x" + else: + newpart += part[j] + j += 1 # emulate statement3 in JS for loop + + # we test again with ASCII char only + if not HOSTNAME_PART_PATTERN.search(newpart): + valid_parts = hostparts[:i] + not_host = hostparts[i + 1 :] + bit = HOSTNAME_PART_START.search(part) + if bit: + valid_parts.append(bit.group(1)) + not_host.insert(0, bit.group(2)) + if not_host: + rest = ".".join(not_host) + rest + self.hostname = ".".join(valid_parts) + break + i += 1 # emulate statement3 in JS for loop + + if len(self.hostname) > HOSTNAME_MAX_LEN: + self.hostname = "" + + # strip [ and ] from the hostname + # the host field still retains them, though + if ipv6_hostname: + self.hostname = self.hostname[1:-1] + + # chop off from the tail first. + hash = rest.find("#") # noqa: A001 + if hash != -1: + # got a fragment string. + self.hash = rest[hash:] + rest = rest[:hash] + qm = rest.find("?") + if qm != -1: + self.search = rest[qm:] + rest = rest[:qm] + if rest: + self.pathname = rest + if SLASHED_PROTOCOL[lower_proto] and self.hostname and not self.pathname: + self.pathname = "" + + return self + + def parse_host(self, host: str) -> None: + port_match = PORT_PATTERN.search(host) + if port_match: + port = port_match.group() + if port != ":": + self.port = port[1:] + host = host[: -len(port)] + if host: + self.hostname = host + + +def url_parse(url: URL | str, *, slashes_denote_host: bool = False) -> URL: + if isinstance(url, URL): + return url + u = MutableURL() + u.parse(url, slashes_denote_host) + return URL( + u.protocol, u.slashes, u.auth, u.port, u.hostname, u.hash, u.search, u.pathname + ) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/mdurl/_url.py b/bioagent-bench/.venv/lib/python3.14/site-packages/mdurl/_url.py new file mode 100644 index 0000000000000000000000000000000000000000..f866e7a179c8854e37c9bba6294f48681e5d99d7 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/mdurl/_url.py @@ -0,0 +1,14 @@ +from __future__ import annotations + +from typing import NamedTuple + + +class URL(NamedTuple): + protocol: str | None + slashes: bool + auth: str | None + port: str | None + hostname: str | None + hash: str | None # noqa: A003 + search: str | None + pathname: str | None diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/mdurl/py.typed b/bioagent-bench/.venv/lib/python3.14/site-packages/mdurl/py.typed new file mode 100644 index 0000000000000000000000000000000000000000..7632ecf77545c5e5501cb3fc5719df0761104ca2 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/mdurl/py.typed @@ -0,0 +1 @@ +# Marker file for PEP 561 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/multidict/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/multidict/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e3db9e199b191c665d5ca56e08b2521bb0f92874 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/multidict/__init__.py @@ -0,0 +1,60 @@ +""" +Multidict implementation. + +HTTP Headers and URL query string require specific data structure: +multidict. It behaves mostly like a dict but it can have +several values for the same key. +""" + +from typing import TYPE_CHECKING + +from ._abc import MultiMapping, MutableMultiMapping +from ._compat import USE_EXTENSIONS + +__all__ = ( + "CIMultiDict", + "CIMultiDictProxy", + "MultiDict", + "MultiDictProxy", + "MultiMapping", + "MutableMultiMapping", + "getversion", + "istr", + "upstr", +) + +__version__ = "6.7.1" + + +if TYPE_CHECKING or not USE_EXTENSIONS: + from ._multidict_py import ( + CIMultiDict, + CIMultiDictProxy, + MultiDict, + MultiDictProxy, + getversion, + istr, + ) +else: + from collections.abc import ItemsView, KeysView, ValuesView + + from ._multidict import ( + CIMultiDict, + CIMultiDictProxy, + MultiDict, + MultiDictProxy, + _ItemsView, + _KeysView, + _ValuesView, + getversion, + istr, + ) + + MultiMapping.register(MultiDictProxy) + MutableMultiMapping.register(MultiDict) + KeysView.register(_KeysView) + ItemsView.register(_ItemsView) + ValuesView.register(_ValuesView) + + +upstr = istr diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/multidict/_abc.py b/bioagent-bench/.venv/lib/python3.14/site-packages/multidict/_abc.py new file mode 100644 index 0000000000000000000000000000000000000000..54253e9e779915aa8741313563359a8d9d87ec16 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/multidict/_abc.py @@ -0,0 +1,73 @@ +import abc +from collections.abc import Iterable, Mapping, MutableMapping +from typing import TYPE_CHECKING, Protocol, TypeVar, Union, overload + +if TYPE_CHECKING: + from ._multidict_py import istr +else: + istr = str + +_V = TypeVar("_V") +_V_co = TypeVar("_V_co", covariant=True) +_T = TypeVar("_T") + + +class SupportsKeys(Protocol[_V_co]): + def keys(self) -> Iterable[str]: ... + def __getitem__(self, key: str, /) -> _V_co: ... + + +class SupportsIKeys(Protocol[_V_co]): + def keys(self) -> Iterable[istr]: ... + def __getitem__(self, key: istr, /) -> _V_co: ... + + +MDArg = Union[SupportsKeys[_V], SupportsIKeys[_V], Iterable[tuple[str, _V]], None] + + +class MultiMapping(Mapping[str, _V_co]): + @overload + def getall(self, key: str) -> list[_V_co]: ... + @overload + def getall(self, key: str, default: _T) -> Union[list[_V_co], _T]: ... + @abc.abstractmethod + def getall(self, key: str, default: _T = ...) -> Union[list[_V_co], _T]: + """Return all values for key.""" + + @overload + def getone(self, key: str) -> _V_co: ... + @overload + def getone(self, key: str, default: _T) -> Union[_V_co, _T]: ... + @abc.abstractmethod + def getone(self, key: str, default: _T = ...) -> Union[_V_co, _T]: + """Return first value for key.""" + + +class MutableMultiMapping(MultiMapping[_V], MutableMapping[str, _V]): + @abc.abstractmethod + def add(self, key: str, value: _V) -> None: + """Add value to list.""" + + @abc.abstractmethod + def extend(self, arg: MDArg[_V] = None, /, **kwargs: _V) -> None: + """Add everything from arg and kwargs to the mapping.""" + + @abc.abstractmethod + def merge(self, arg: MDArg[_V] = None, /, **kwargs: _V) -> None: + """Merge into the mapping, adding non-existing keys.""" + + @overload + def popone(self, key: str) -> _V: ... + @overload + def popone(self, key: str, default: _T) -> Union[_V, _T]: ... + @abc.abstractmethod + def popone(self, key: str, default: _T = ...) -> Union[_V, _T]: + """Remove specified key and return the corresponding value.""" + + @overload + def popall(self, key: str) -> list[_V]: ... + @overload + def popall(self, key: str, default: _T) -> Union[list[_V], _T]: ... + @abc.abstractmethod + def popall(self, key: str, default: _T = ...) -> Union[list[_V], _T]: + """Remove all occurrences of key and return the list of corresponding values.""" diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/multidict/_compat.py b/bioagent-bench/.venv/lib/python3.14/site-packages/multidict/_compat.py new file mode 100644 index 0000000000000000000000000000000000000000..264d327e6a920503a72cc9e6174b26b63677faf5 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/multidict/_compat.py @@ -0,0 +1,15 @@ +import os +import platform + +NO_EXTENSIONS = bool(os.environ.get("MULTIDICT_NO_EXTENSIONS")) + +PYPY = platform.python_implementation() == "PyPy" + +USE_EXTENSIONS = not NO_EXTENSIONS and not PYPY + +if USE_EXTENSIONS: + try: + from . import _multidict # type: ignore[attr-defined] # noqa: F401 + except ImportError: # pragma: no cover + # FIXME: Refactor for coverage. See #837. + USE_EXTENSIONS = False diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/multidict/_multidict_py.py b/bioagent-bench/.venv/lib/python3.14/site-packages/multidict/_multidict_py.py new file mode 100644 index 0000000000000000000000000000000000000000..6b68d52eeff981cc8082e3426ba037ae1aedb2c1 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/multidict/_multidict_py.py @@ -0,0 +1,1242 @@ +import enum +import functools +import reprlib +import sys +from array import array +from collections.abc import ( + ItemsView, + Iterable, + Iterator, + KeysView, + Mapping, + ValuesView, +) +from dataclasses import dataclass +from typing import ( + TYPE_CHECKING, + Any, + ClassVar, + Generic, + NoReturn, + Optional, + TypeVar, + Union, + cast, + overload, +) + +from ._abc import MDArg, MultiMapping, MutableMultiMapping, SupportsKeys + +if sys.version_info >= (3, 11): + from typing import Self +else: + from typing_extensions import Self + + +class istr(str): + """Case insensitive str.""" + + __is_istr__ = True + __istr_identity__: Optional[str] = None + + +_V = TypeVar("_V") +_T = TypeVar("_T") + +_SENTINEL = enum.Enum("_SENTINEL", "sentinel") +sentinel = _SENTINEL.sentinel + +_version = array("Q", [0]) + + +class _Iter(Generic[_T]): + __slots__ = ("_size", "_iter") + + def __init__(self, size: int, iterator: Iterator[_T]): + self._size = size + self._iter = iterator + + def __iter__(self) -> Self: + return self + + def __next__(self) -> _T: + return next(self._iter) + + def __length_hint__(self) -> int: + return self._size + + +class _ViewBase(Generic[_V]): + def __init__( + self, + md: "MultiDict[_V]", + ): + self._md = md + + def __len__(self) -> int: + return len(self._md) + + +class _ItemsView(_ViewBase[_V], ItemsView[str, _V]): + def __contains__(self, item: object) -> bool: + if not isinstance(item, (tuple, list)) or len(item) != 2: + return False + key, value = item + try: + identity = self._md._identity(key) + except TypeError: + return False + hash_ = hash(identity) + for slot, idx, e in self._md._keys.iter_hash(hash_): + if e.identity == identity and value == e.value: + return True + return False + + def __iter__(self) -> _Iter[tuple[str, _V]]: + return _Iter(len(self), self._iter(self._md._version)) + + def _iter(self, version: int) -> Iterator[tuple[str, _V]]: + for e in self._md._keys.iter_entries(): + if version != self._md._version: + raise RuntimeError("Dictionary changed during iteration") + yield self._md._key(e.key), e.value + + @reprlib.recursive_repr() + def __repr__(self) -> str: + lst = [] + for e in self._md._keys.iter_entries(): + lst.append(f"'{e.key}': {e.value!r}") + body = ", ".join(lst) + return f"<{self.__class__.__name__}({body})>" + + def _parse_item( + self, arg: Union[tuple[str, _V], _T] + ) -> Optional[tuple[int, str, str, _V]]: + if not isinstance(arg, tuple): + return None + if len(arg) != 2: + return None + try: + identity = self._md._identity(arg[0]) + return (hash(identity), identity, arg[0], arg[1]) + except TypeError: + return None + + def _tmp_set(self, it: Iterable[_T]) -> set[tuple[str, _V]]: + tmp = set() + for arg in it: + item = self._parse_item(arg) + if item is None: + continue + else: + tmp.add((item[1], item[3])) + return tmp + + def __and__(self, other: Iterable[Any]) -> set[tuple[str, _V]]: + ret = set() + try: + it = iter(other) + except TypeError: + return NotImplemented + for arg in it: + item = self._parse_item(arg) + if item is None: + continue + hash_, identity, key, value = item + for slot, idx, e in self._md._keys.iter_hash(hash_): + e.hash = -1 + if e.identity == identity and e.value == value: + ret.add((e.key, e.value)) + self._md._keys.restore_hash(hash_) + return ret + + def __rand__(self, other: Iterable[_T]) -> set[_T]: + ret = set() + try: + it = iter(other) + except TypeError: + return NotImplemented + for arg in it: + item = self._parse_item(arg) + if item is None: + continue + hash_, identity, key, value = item + for slot, idx, e in self._md._keys.iter_hash(hash_): + if e.identity == identity and e.value == value: + ret.add(arg) + break + return ret + + def __or__(self, other: Iterable[_T]) -> set[Union[tuple[str, _V], _T]]: + ret: set[Union[tuple[str, _V], _T]] = set(self) + try: + it = iter(other) + except TypeError: + return NotImplemented + for arg in it: + item: Optional[tuple[int, str, str, _V]] = self._parse_item(arg) + if item is None: + ret.add(arg) + continue + hash_, identity, key, value = item + for slot, idx, e in self._md._keys.iter_hash(hash_): + if e.identity == identity and e.value == value: # pragma: no branch + break + else: + ret.add(arg) + return ret + + def __ror__(self, other: Iterable[_T]) -> set[Union[tuple[str, _V], _T]]: + try: + ret: set[Union[tuple[str, _V], _T]] = set(other) + except TypeError: + return NotImplemented + tmp = self._tmp_set(ret) + + for e in self._md._keys.iter_entries(): + if (e.identity, e.value) not in tmp: + ret.add((e.key, e.value)) + return ret + + def __sub__(self, other: Iterable[_T]) -> set[Union[tuple[str, _V], _T]]: + ret: set[Union[tuple[str, _V], _T]] = set() + try: + it = iter(other) + except TypeError: + return NotImplemented + tmp = self._tmp_set(it) + + for e in self._md._keys.iter_entries(): + if (e.identity, e.value) not in tmp: + ret.add((e.key, e.value)) + + return ret + + def __rsub__(self, other: Iterable[_T]) -> set[_T]: + ret: set[_T] = set() + try: + it = iter(other) + except TypeError: + return NotImplemented + for arg in it: + item = self._parse_item(arg) + if item is None: + ret.add(arg) + continue + + hash_, identity, key, value = item + for slot, idx, e in self._md._keys.iter_hash(hash_): + if e.identity == identity and e.value == value: # pragma: no branch + break + else: + ret.add(arg) + return ret + + def __xor__(self, other: Iterable[_T]) -> set[Union[tuple[str, _V], _T]]: + try: + rgt = set(other) + except TypeError: + return NotImplemented + ret: set[Union[tuple[str, _V], _T]] = self - rgt + ret |= rgt - self + return ret + + __rxor__ = __xor__ + + def isdisjoint(self, other: Iterable[tuple[str, _V]]) -> bool: + for arg in other: + item = self._parse_item(arg) + if item is None: + continue + + hash_, identity, key, value = item + for slot, idx, e in self._md._keys.iter_hash(hash_): + if e.identity == identity and e.value == value: # pragma: no branch + return False + return True + + +class _ValuesView(_ViewBase[_V], ValuesView[_V]): + def __contains__(self, value: object) -> bool: + for e in self._md._keys.iter_entries(): + if e.value == value: + return True + return False + + def __iter__(self) -> _Iter[_V]: + return _Iter(len(self), self._iter(self._md._version)) + + def _iter(self, version: int) -> Iterator[_V]: + for e in self._md._keys.iter_entries(): + if version != self._md._version: + raise RuntimeError("Dictionary changed during iteration") + yield e.value + + @reprlib.recursive_repr() + def __repr__(self) -> str: + lst = [] + for e in self._md._keys.iter_entries(): + lst.append(repr(e.value)) + body = ", ".join(lst) + return f"<{self.__class__.__name__}({body})>" + + +class _KeysView(_ViewBase[_V], KeysView[str]): + def __contains__(self, key: object) -> bool: + if not isinstance(key, str): + return False + identity = self._md._identity(key) + hash_ = hash(identity) + for slot, idx, e in self._md._keys.iter_hash(hash_): + if e.identity == identity: # pragma: no branch + return True + return False + + def __iter__(self) -> _Iter[str]: + return _Iter(len(self), self._iter(self._md._version)) + + def _iter(self, version: int) -> Iterator[str]: + for e in self._md._keys.iter_entries(): + if version != self._md._version: + raise RuntimeError("Dictionary changed during iteration") + yield self._md._key(e.key) + + def __repr__(self) -> str: + lst = [] + for e in self._md._keys.iter_entries(): + lst.append(f"'{e.key}'") + body = ", ".join(lst) + return f"<{self.__class__.__name__}({body})>" + + def __and__(self, other: Iterable[object]) -> set[str]: + ret = set() + try: + it = iter(other) + except TypeError: + return NotImplemented + for key in it: + if not isinstance(key, str): + continue + identity = self._md._identity(key) + hash_ = hash(identity) + for slot, idx, e in self._md._keys.iter_hash(hash_): + if e.identity == identity: # pragma: no branch + ret.add(e.key) + break + return ret + + def __rand__(self, other: Iterable[_T]) -> set[_T]: + ret = set() + try: + it = iter(other) + except TypeError: + return NotImplemented + for key in it: + if not isinstance(key, str): + continue + if key in self._md: + ret.add(key) + return cast(set[_T], ret) + + def __or__(self, other: Iterable[_T]) -> set[Union[str, _T]]: + ret: set[Union[str, _T]] = set(self) + try: + it = iter(other) + except TypeError: + return NotImplemented + for key in it: + if not isinstance(key, str): + ret.add(key) + continue + if key not in self._md: + ret.add(key) + return ret + + def __ror__(self, other: Iterable[_T]) -> set[Union[str, _T]]: + try: + ret: set[Union[str, _T]] = set(other) + except TypeError: + return NotImplemented + + tmp = set() + for key in ret: + if not isinstance(key, str): + continue + identity = self._md._identity(key) + tmp.add(identity) + + for e in self._md._keys.iter_entries(): + if e.identity not in tmp: + ret.add(e.key) + return ret + + def __sub__(self, other: Iterable[object]) -> set[str]: + ret = set(self) + try: + it = iter(other) + except TypeError: + return NotImplemented + for key in it: + if not isinstance(key, str): + continue + identity = self._md._identity(key) + hash_ = hash(identity) + for slot, idx, e in self._md._keys.iter_hash(hash_): + if e.identity == identity: # pragma: no branch + ret.discard(e.key) + break + return ret + + def __rsub__(self, other: Iterable[_T]) -> set[_T]: + try: + ret: set[_T] = set(other) + except TypeError: + return NotImplemented + for key in other: + if not isinstance(key, str): + continue + if key in self._md: + ret.discard(key) # type: ignore[arg-type] + return ret + + def __xor__(self, other: Iterable[_T]) -> set[Union[str, _T]]: + try: + rgt = set(other) + except TypeError: + return NotImplemented + ret: set[Union[str, _T]] = self - rgt # type: ignore[assignment] + ret |= rgt - self + return ret + + __rxor__ = __xor__ + + def isdisjoint(self, other: Iterable[object]) -> bool: + for key in other: + if not isinstance(key, str): + continue + if key in self._md: + return False + return True + + +class _CSMixin: + _ci: ClassVar[bool] = False + + def _key(self, key: str) -> str: + return key + + def _identity(self, key: str) -> str: + if isinstance(key, str): + return key + else: + raise TypeError("MultiDict keys should be either str or subclasses of str") + + +class _CIMixin: + _ci: ClassVar[bool] = True + + def _key(self, key: str) -> str: + if type(key) is istr: + return key + else: + return istr(key) + + def _identity(self, key: str) -> str: + if isinstance(key, istr): + ret = key.__istr_identity__ + if ret is None: + ret = key.lower() + key.__istr_identity__ = ret + return ret + if isinstance(key, str): + return key.lower() + else: + raise TypeError("MultiDict keys should be either str or subclasses of str") + + +def estimate_log2_keysize(n: int) -> int: + # 7 == HT_MINSIZE - 1 + return (((n * 3 + 1) // 2) | 7).bit_length() + + +@dataclass +class _Entry(Generic[_V]): + hash: int + identity: str + key: str + value: _V + + +@dataclass +class _HtKeys(Generic[_V]): # type: ignore[misc] + LOG_MINSIZE: ClassVar[int] = 3 + MINSIZE: ClassVar[int] = 8 + PREALLOCATED_INDICES: ClassVar[dict[int, array]] = { # type: ignore[type-arg] + log2_size: array( + "b" if log2_size < 8 else "h", (-1 for i in range(1 << log2_size)) + ) + for log2_size in range(3, 10) + } + + log2_size: int + usable: int + + indices: array # type: ignore[type-arg] # in py3.9 array is not generic + entries: list[Optional[_Entry[_V]]] + + @functools.cached_property + def nslots(self) -> int: + return 1 << self.log2_size + + @functools.cached_property + def mask(self) -> int: + return self.nslots - 1 + + if sys.implementation.name != "pypy": + + def __sizeof__(self) -> int: + return ( + object.__sizeof__(self) + + sys.getsizeof(self.indices) + + sys.getsizeof(self.entries) + ) + + @classmethod + def new(cls, log2_size: int, entries: list[Optional[_Entry[_V]]]) -> Self: + size = 1 << log2_size + usable = (size << 1) // 3 + if log2_size < 10: + indices = cls.PREALLOCATED_INDICES[log2_size].__copy__() + elif log2_size < 16: + indices = array("h", (-1 for i in range(size))) + elif log2_size < 32: + indices = array("l", (-1 for i in range(size))) + else: # pragma: no cover # don't test huge multidicts + indices = array("q", (-1 for i in range(size))) + ret = cls( + log2_size=log2_size, + usable=usable, + indices=indices, + entries=entries, + ) + return ret + + def clone(self) -> "_HtKeys[_V]": + entries = [ + _Entry(e.hash, e.identity, e.key, e.value) if e is not None else None + for e in self.entries + ] + + return _HtKeys( + log2_size=self.log2_size, + usable=self.usable, + indices=self.indices.__copy__(), + entries=entries, + ) + + def build_indices(self, update: bool) -> None: + mask = self.mask + indices = self.indices + for idx, e in enumerate(self.entries): + assert e is not None + hash_ = e.hash + if update: + if hash_ == -1: + hash_ = hash(e.identity) + else: + assert hash_ != -1 + i = hash_ & mask + perturb = hash_ & sys.maxsize + while indices[i] != -1: + perturb >>= 5 + i = mask & (i * 5 + perturb + 1) + indices[i] = idx + + def find_empty_slot(self, hash_: int) -> int: + mask = self.mask + indices = self.indices + i = hash_ & mask + perturb = hash_ & sys.maxsize + ix = indices[i] + while ix != -1: + perturb >>= 5 + i = (i * 5 + perturb + 1) & mask + ix = indices[i] + return i + + def iter_hash(self, hash_: int) -> Iterator[tuple[int, int, _Entry[_V]]]: + mask = self.mask + indices = self.indices + entries = self.entries + i = hash_ & mask + perturb = hash_ & sys.maxsize + ix = indices[i] + while ix != -1: + if ix != -2: + e = entries[ix] + if e.hash == hash_: + yield i, ix, e + perturb >>= 5 + i = (i * 5 + perturb + 1) & mask + ix = indices[i] + + def del_idx(self, hash_: int, idx: int) -> None: + mask = self.mask + indices = self.indices + i = hash_ & mask + perturb = hash_ & sys.maxsize + ix = indices[i] + while ix != idx: + perturb >>= 5 + i = (i * 5 + perturb + 1) & mask + ix = indices[i] + indices[i] = -2 + + def iter_entries(self) -> Iterator[_Entry[_V]]: + return filter(None, self.entries) + + def restore_hash(self, hash_: int) -> None: + mask = self.mask + indices = self.indices + entries = self.entries + i = hash_ & mask + perturb = hash_ & sys.maxsize + ix = indices[i] + while ix != -1: + if ix != -2: + entry = entries[ix] + if entry.hash == -1: + entry.hash = hash_ + perturb >>= 5 + i = (i * 5 + perturb + 1) & mask + ix = indices[i] + + +class MultiDict(_CSMixin, MutableMultiMapping[_V]): + """Dictionary with the support for duplicate keys.""" + + __slots__ = ("_keys", "_used", "_version") + + def __init__(self, arg: MDArg[_V] = None, /, **kwargs: _V): + self._used = 0 + v = _version + v[0] += 1 + self._version = v[0] + if not kwargs: + md = None + if isinstance(arg, MultiDictProxy): + md = arg._md + elif isinstance(arg, MultiDict): + md = arg + if md is not None and md._ci is self._ci: + self._from_md(md) + return + + it = self._parse_args(arg, kwargs) + log2_size = estimate_log2_keysize(cast(int, next(it))) + if log2_size > 17: # pragma: no cover + # Don't overallocate really huge keys space in init + log2_size = 17 + self._keys: _HtKeys[_V] = _HtKeys.new(log2_size, []) + self._extend_items(cast(Iterator[_Entry[_V]], it)) + + def _from_md(self, md: "MultiDict[_V]") -> None: + # Copy everything as-is without compacting the new multidict, + # otherwise it requires reindexing + self._keys = md._keys.clone() + self._used = md._used + + @overload + def getall(self, key: str) -> list[_V]: ... + @overload + def getall(self, key: str, default: _T) -> Union[list[_V], _T]: ... + def getall( + self, key: str, default: Union[_T, _SENTINEL] = sentinel + ) -> Union[list[_V], _T]: + """Return a list of all values matching the key.""" + identity = self._identity(key) + hash_ = hash(identity) + res = [] + restore = [] + for slot, idx, e in self._keys.iter_hash(hash_): + if e.identity == identity: # pragma: no branch + res.append(e.value) + e.hash = -1 + restore.append(idx) + + if res: + entries = self._keys.entries + for idx in restore: + entries[idx].hash = hash_ # type: ignore[union-attr] + return res + if not res and default is not sentinel: + return default + raise KeyError("Key not found: %r" % key) + + @overload + def getone(self, key: str) -> _V: ... + @overload + def getone(self, key: str, default: _T) -> Union[_V, _T]: ... + def getone( + self, key: str, default: Union[_T, _SENTINEL] = sentinel + ) -> Union[_V, _T]: + """Get first value matching the key. + + Raises KeyError if the key is not found and no default is provided. + """ + identity = self._identity(key) + hash_ = hash(identity) + for slot, idx, e in self._keys.iter_hash(hash_): + if e.identity == identity: # pragma: no branch + return e.value + if default is not sentinel: + return default + raise KeyError("Key not found: %r" % key) + + # Mapping interface # + + def __getitem__(self, key: str) -> _V: + return self.getone(key) + + @overload + def get(self, key: str, /) -> Union[_V, None]: ... + @overload + def get(self, key: str, /, default: _T) -> Union[_V, _T]: ... + def get(self, key: str, default: Union[_T, None] = None) -> Union[_V, _T, None]: + """Get first value matching the key. + + If the key is not found, returns the default (or None if no default is provided) + """ + return self.getone(key, default) + + def __iter__(self) -> Iterator[str]: + return iter(self.keys()) + + def __len__(self) -> int: + return self._used + + def keys(self) -> KeysView[str]: + """Return a new view of the dictionary's keys.""" + return _KeysView(self) + + def items(self) -> ItemsView[str, _V]: + """Return a new view of the dictionary's items *(key, value) pairs).""" + return _ItemsView(self) + + def values(self) -> _ValuesView[_V]: + """Return a new view of the dictionary's values.""" + return _ValuesView(self) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Mapping): + return NotImplemented + if isinstance(other, MultiDictProxy): + return self == other._md + if isinstance(other, MultiDict): + lft = self._keys + rht = other._keys + if self._used != other._used: + return False + for e1, e2 in zip(lft.iter_entries(), rht.iter_entries()): + if e1.identity != e2.identity or e1.value != e2.value: + return False + return True + if self._used != len(other): + return False + for k, v in self.items(): + nv = other.get(k, sentinel) + if v != nv: + return False + return True + + def __contains__(self, key: object) -> bool: + if not isinstance(key, str): + return False + identity = self._identity(key) + hash_ = hash(identity) + for slot, idx, e in self._keys.iter_hash(hash_): + if e.identity == identity: # pragma: no branch + return True + return False + + @reprlib.recursive_repr() + def __repr__(self) -> str: + body = ", ".join(f"'{e.key}': {e.value!r}" for e in self._keys.iter_entries()) + return f"<{self.__class__.__name__}({body})>" + + if sys.implementation.name != "pypy": + + def __sizeof__(self) -> int: + return object.__sizeof__(self) + sys.getsizeof(self._keys) + + def __reduce__(self) -> tuple[type[Self], tuple[list[tuple[str, _V]]]]: + return (self.__class__, (list(self.items()),)) + + def add(self, key: str, value: _V) -> None: + identity = self._identity(key) + hash_ = hash(identity) + self._add_with_hash(_Entry(hash_, identity, key, value)) + self._incr_version() + + def copy(self) -> Self: + """Return a copy of itself.""" + cls = self.__class__ + return cls(self) + + __copy__ = copy + + def extend(self, arg: MDArg[_V] = None, /, **kwargs: _V) -> None: + """Extend current MultiDict with more values. + + This method must be used instead of update. + """ + it = self._parse_args(arg, kwargs) + newsize = self._used + cast(int, next(it)) + self._resize(estimate_log2_keysize(newsize), False) + self._extend_items(cast(Iterator[_Entry[_V]], it)) + + def _parse_args( + self, + arg: MDArg[_V], + kwargs: Mapping[str, _V], + ) -> Iterator[Union[int, _Entry[_V]]]: + identity_func = self._identity + if arg: + if isinstance(arg, MultiDictProxy): + arg = arg._md + if isinstance(arg, MultiDict): + yield len(arg) + len(kwargs) + if self._ci is not arg._ci: + for e in arg._keys.iter_entries(): + identity = identity_func(e.key) + yield _Entry(hash(identity), identity, e.key, e.value) + else: + for e in arg._keys.iter_entries(): + yield _Entry(e.hash, e.identity, e.key, e.value) + if kwargs: + for key, value in kwargs.items(): + identity = identity_func(key) + yield _Entry(hash(identity), identity, key, value) + else: + if hasattr(arg, "keys"): + arg = cast(SupportsKeys[_V], arg) + arg = [(k, arg[k]) for k in arg.keys()] + if kwargs: + arg = list(arg) + arg.extend(list(kwargs.items())) + try: + yield len(arg) + len(kwargs) # type: ignore[arg-type] + except TypeError: + yield 0 + for pos, item in enumerate(arg): + if not len(item) == 2: + raise ValueError( + f"multidict update sequence element #{pos}" + f"has length {len(item)}; 2 is required" + ) + identity = identity_func(item[0]) + yield _Entry(hash(identity), identity, item[0], item[1]) + else: + yield len(kwargs) + for key, value in kwargs.items(): + identity = identity_func(key) + yield _Entry(hash(identity), identity, key, value) + + def _extend_items(self, items: Iterable[_Entry[_V]]) -> None: + for e in items: + self._add_with_hash(e) + self._incr_version() + + def clear(self) -> None: + """Remove all items from MultiDict.""" + self._used = 0 + self._keys = _HtKeys.new(_HtKeys.LOG_MINSIZE, []) + self._incr_version() + + # Mapping interface # + + def __setitem__(self, key: str, value: _V) -> None: + identity = self._identity(key) + hash_ = hash(identity) + found = False + + for slot, idx, e in self._keys.iter_hash(hash_): + if e.identity == identity: # pragma: no branch + if not found: + e.key = key + e.value = value + e.hash = -1 + found = True + self._incr_version() + elif e.hash != -1: # pragma: no branch + self._del_at(slot, idx) + + if not found: + self._add_with_hash(_Entry(hash_, identity, key, value)) + else: + self._keys.restore_hash(hash_) + + def __delitem__(self, key: str) -> None: + found = False + identity = self._identity(key) + hash_ = hash(identity) + for slot, idx, e in self._keys.iter_hash(hash_): + if e.identity == identity: # pragma: no branch + self._del_at(slot, idx) + found = True + if not found: + raise KeyError(key) + else: + self._incr_version() + + @overload + def setdefault( + self: "MultiDict[Union[_T, None]]", key: str, default: None = None + ) -> Union[_T, None]: ... + @overload + def setdefault(self, key: str, default: _V) -> _V: ... + def setdefault(self, key: str, default: Union[_V, None] = None) -> Union[_V, None]: # type: ignore[misc] + """Return value for key, set value to default if key is not present.""" + identity = self._identity(key) + hash_ = hash(identity) + for slot, idx, e in self._keys.iter_hash(hash_): + if e.identity == identity: # pragma: no branch + return e.value + self.add(key, default) # type: ignore[arg-type] + return default + + @overload + def popone(self, key: str) -> _V: ... + @overload + def popone(self, key: str, default: _T) -> Union[_V, _T]: ... + def popone( + self, key: str, default: Union[_T, _SENTINEL] = sentinel + ) -> Union[_V, _T]: + """Remove specified key and return the corresponding value. + + If key is not found, d is returned if given, otherwise + KeyError is raised. + + """ + identity = self._identity(key) + hash_ = hash(identity) + for slot, idx, e in self._keys.iter_hash(hash_): + if e.identity == identity: # pragma: no branch + value = e.value + self._del_at(slot, idx) + self._incr_version() + return value + if default is sentinel: + raise KeyError(key) + else: + return default + + # Type checking will inherit signature for pop() if we don't confuse it here. + if not TYPE_CHECKING: + pop = popone + + @overload + def popall(self, key: str) -> list[_V]: ... + @overload + def popall(self, key: str, default: _T) -> Union[list[_V], _T]: ... + def popall( + self, key: str, default: Union[_T, _SENTINEL] = sentinel + ) -> Union[list[_V], _T]: + """Remove all occurrences of key and return the list of corresponding + values. + + If key is not found, default is returned if given, otherwise + KeyError is raised. + + """ + found = False + identity = self._identity(key) + hash_ = hash(identity) + ret = [] + for slot, idx, e in self._keys.iter_hash(hash_): + if e.identity == identity: # pragma: no branch + found = True + ret.append(e.value) + self._del_at(slot, idx) + self._incr_version() + + if not found: + if default is sentinel: + raise KeyError(key) + else: + return default + else: + return ret + + def popitem(self) -> tuple[str, _V]: + """Remove and return an arbitrary (key, value) pair.""" + if self._used <= 0: + raise KeyError("empty multidict") + + pos = len(self._keys.entries) - 1 + entry = self._keys.entries.pop() + + while entry is None: + pos -= 1 + entry = self._keys.entries.pop() + + ret = self._key(entry.key), entry.value + self._keys.del_idx(entry.hash, pos) + self._used -= 1 + self._incr_version() + return ret + + def update(self, arg: MDArg[_V] = None, /, **kwargs: _V) -> None: + """Update the dictionary, overwriting existing keys.""" + it = self._parse_args(arg, kwargs) + newsize = self._used + cast(int, next(it)) + log2_size = estimate_log2_keysize(newsize) + if log2_size > 17: # pragma: no cover + # Don't overallocate really huge keys space in update, + # duplicate keys could reduce the resulting anount of entries + log2_size = 17 + if log2_size > self._keys.log2_size: + self._resize(log2_size, False) + try: + self._update_items(cast(Iterator[_Entry[_V]], it)) + finally: + self._post_update() + + def _update_items(self, items: Iterator[_Entry[_V]]) -> None: + for entry in items: + found = False + hash_ = entry.hash + identity = entry.identity + for slot, idx, e in self._keys.iter_hash(hash_): + if e.identity == identity: # pragma: no branch + if not found: + found = True + e.key = entry.key + e.value = entry.value + e.hash = -1 + else: + self._del_at_for_upd(e) + if not found: + self._add_with_hash_for_upd(entry) + + def _post_update(self) -> None: + keys = self._keys + indices = keys.indices + entries = keys.entries + for slot in range(keys.nslots): + idx = indices[slot] + if idx >= 0: + e2 = entries[idx] + assert e2 is not None + if e2.key is None: + entries[idx] = None + indices[slot] = -2 + self._used -= 1 + if e2.hash == -1: + e2.hash = hash(e2.identity) + + self._incr_version() + + def merge(self, arg: MDArg[_V] = None, /, **kwargs: _V) -> None: + """Merge into the dictionary, adding non-existing keys.""" + it = self._parse_args(arg, kwargs) + newsize = self._used + cast(int, next(it)) + log2_size = estimate_log2_keysize(newsize) + if log2_size > 17: # pragma: no cover + # Don't overallocate really huge keys space in update, + # duplicate keys could reduce the resulting anount of entries + log2_size = 17 + if log2_size > self._keys.log2_size: + self._resize(log2_size, False) + try: + self._merge_items(cast(Iterator[_Entry[_V]], it)) + finally: + self._post_update() + + def _merge_items(self, items: Iterator[_Entry[_V]]) -> None: + for entry in items: + hash_ = entry.hash + identity = entry.identity + for slot, idx, e in self._keys.iter_hash(hash_): + if e.identity == identity: # pragma: no branch + break + else: + self._add_with_hash_for_upd(entry) + + def _incr_version(self) -> None: + v = _version + v[0] += 1 + self._version = v[0] + + def _resize(self, log2_newsize: int, update: bool) -> None: + oldkeys = self._keys + newentries = self._used + + if len(oldkeys.entries) == newentries: + entries = oldkeys.entries + else: + entries = [e for e in oldkeys.entries if e is not None] + newkeys: _HtKeys[_V] = _HtKeys.new(log2_newsize, entries) + newkeys.usable -= newentries + newkeys.build_indices(update) + self._keys = newkeys + + def _add_with_hash(self, entry: _Entry[_V]) -> None: + if self._keys.usable <= 0: + self._resize((self._used * 3 | _HtKeys.MINSIZE - 1).bit_length(), False) + keys = self._keys + slot = keys.find_empty_slot(entry.hash) + keys.indices[slot] = len(keys.entries) + keys.entries.append(entry) + self._incr_version() + self._used += 1 + keys.usable -= 1 + + def _add_with_hash_for_upd(self, entry: _Entry[_V]) -> None: + if self._keys.usable <= 0: + self._resize((self._used * 3 | _HtKeys.MINSIZE - 1).bit_length(), True) + keys = self._keys + slot = keys.find_empty_slot(entry.hash) + keys.indices[slot] = len(keys.entries) + entry.hash = -1 + keys.entries.append(entry) + self._incr_version() + self._used += 1 + keys.usable -= 1 + + def _del_at(self, slot: int, idx: int) -> None: + self._keys.entries[idx] = None + self._keys.indices[slot] = -2 + self._used -= 1 + + def _del_at_for_upd(self, entry: _Entry[_V]) -> None: + entry.key = None # type: ignore[assignment] + entry.value = None # type: ignore[assignment] + + +class CIMultiDict(_CIMixin, MultiDict[_V]): + """Dictionary with the support for duplicate case-insensitive keys.""" + + +class MultiDictProxy(_CSMixin, MultiMapping[_V]): + """Read-only proxy for MultiDict instance.""" + + __slots__ = ("_md",) + + _md: MultiDict[_V] + + def __init__(self, arg: Union[MultiDict[_V], "MultiDictProxy[_V]"]): + if not isinstance(arg, (MultiDict, MultiDictProxy)): + raise TypeError( + f"ctor requires MultiDict or MultiDictProxy instance, not {type(arg)}" + ) + if isinstance(arg, MultiDictProxy): + self._md = arg._md + else: + self._md = arg + + def __reduce__(self) -> NoReturn: + raise TypeError(f"can't pickle {self.__class__.__name__} objects") + + @overload + def getall(self, key: str) -> list[_V]: ... + @overload + def getall(self, key: str, default: _T) -> Union[list[_V], _T]: ... + def getall( + self, key: str, default: Union[_T, _SENTINEL] = sentinel + ) -> Union[list[_V], _T]: + """Return a list of all values matching the key.""" + if default is not sentinel: + return self._md.getall(key, default) + else: + return self._md.getall(key) + + @overload + def getone(self, key: str) -> _V: ... + @overload + def getone(self, key: str, default: _T) -> Union[_V, _T]: ... + def getone( + self, key: str, default: Union[_T, _SENTINEL] = sentinel + ) -> Union[_V, _T]: + """Get first value matching the key. + + Raises KeyError if the key is not found and no default is provided. + """ + if default is not sentinel: + return self._md.getone(key, default) + else: + return self._md.getone(key) + + # Mapping interface # + + def __getitem__(self, key: str) -> _V: + return self.getone(key) + + @overload + def get(self, key: str, /) -> Union[_V, None]: ... + @overload + def get(self, key: str, /, default: _T) -> Union[_V, _T]: ... + def get(self, key: str, default: Union[_T, None] = None) -> Union[_V, _T, None]: + """Get first value matching the key. + + If the key is not found, returns the default (or None if no default is provided) + """ + return self._md.getone(key, default) + + def __iter__(self) -> Iterator[str]: + return iter(self._md.keys()) + + def __len__(self) -> int: + return len(self._md) + + def keys(self) -> KeysView[str]: + """Return a new view of the dictionary's keys.""" + return self._md.keys() + + def items(self) -> ItemsView[str, _V]: + """Return a new view of the dictionary's items *(key, value) pairs).""" + return self._md.items() + + def values(self) -> _ValuesView[_V]: + """Return a new view of the dictionary's values.""" + return self._md.values() + + def __eq__(self, other: object) -> bool: + return self._md == other + + def __contains__(self, key: object) -> bool: + return key in self._md + + @reprlib.recursive_repr() + def __repr__(self) -> str: + body = ", ".join(f"'{k}': {v!r}" for k, v in self.items()) + return f"<{self.__class__.__name__}({body})>" + + def copy(self) -> MultiDict[_V]: + """Return a copy of itself.""" + return MultiDict(self._md) + + +class CIMultiDictProxy(_CIMixin, MultiDictProxy[_V]): + """Read-only proxy for CIMultiDict instance.""" + + def __init__(self, arg: Union[MultiDict[_V], MultiDictProxy[_V]]): + if not isinstance(arg, (CIMultiDict, CIMultiDictProxy)): + raise TypeError( + "ctor requires CIMultiDict or CIMultiDictProxy instance" + f", not {type(arg)}" + ) + + super().__init__(arg) + + def copy(self) -> CIMultiDict[_V]: + """Return a copy of itself.""" + return CIMultiDict(self._md) + + +def getversion(md: Union[MultiDict[object], MultiDictProxy[object]]) -> int: + if isinstance(md, MultiDictProxy): + md = md._md + elif not isinstance(md, MultiDict): + raise TypeError("Parameter should be multidict or proxy") + return md._version diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/multidict/py.typed b/bioagent-bench/.venv/lib/python3.14/site-packages/multidict/py.typed new file mode 100644 index 0000000000000000000000000000000000000000..dfe8cc048e7100a97025b954fffa31e4ff859a7d --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/multidict/py.typed @@ -0,0 +1 @@ +PEP-561 marker. \ No newline at end of file diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess-0.70.19.dist-info/COPYING b/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess-0.70.19.dist-info/COPYING new file mode 100644 index 0000000000000000000000000000000000000000..17f34bc3d8ae0889ae327ae0c16bf78870c41527 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess-0.70.19.dist-info/COPYING @@ -0,0 +1,28 @@ +Copyright (c) 2006-2008, R Oudkerk + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of author nor the names of any contributors may be + used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess-0.70.19.dist-info/INSTALLER b/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess-0.70.19.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..5c69047b2eb8235994febeeae1da4a82365a240a --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess-0.70.19.dist-info/INSTALLER @@ -0,0 +1 @@ +uv \ No newline at end of file diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess-0.70.19.dist-info/LICENSE b/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess-0.70.19.dist-info/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..444abd4a6b9d691fb95caf2e4c458860c0e623cf --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess-0.70.19.dist-info/LICENSE @@ -0,0 +1,38 @@ +Copyright (c) 2008-2016 California Institute of Technology. +Copyright (c) 2016-2026 The Uncertainty Quantification Foundation. +All rights reserved. + +This software forks the python package "multiprocessing". Licence and +copyright information for multiprocessing can be found in "COPYING". + +This software is available subject to the conditions and terms laid +out below. By downloading and using this software you are agreeing +to the following conditions. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the names of the copyright holders nor the names of any of + the contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess-0.70.19.dist-info/METADATA b/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess-0.70.19.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..ddffdc582a7df7816cbc270b805876c1b34ea1f8 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess-0.70.19.dist-info/METADATA @@ -0,0 +1,204 @@ +Metadata-Version: 2.1 +Name: multiprocess +Version: 0.70.19 +Summary: better multiprocessing and multithreading in Python +Home-page: https://github.com/uqfoundation/multiprocess +Download-URL: https://pypi.org/project/multiprocess/#files +Author: Mike McKerns +Author-email: mmckerns@uqfoundation.org +Maintainer: Mike McKerns +Maintainer-email: mmckerns@uqfoundation.org +License: BSD-3-Clause +Project-URL: Documentation, http://multiprocess.rtfd.io +Project-URL: Source Code, https://github.com/uqfoundation/multiprocess +Project-URL: Bug Tracker, https://github.com/uqfoundation/multiprocess/issues +Platform: Linux +Platform: Windows +Platform: Mac +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: Science/Research +Classifier: License :: OSI Approved :: BSD License +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Scientific/Engineering +Classifier: Topic :: Software Development +Requires-Python: >=3.9 +License-File: LICENSE +License-File: COPYING +Requires-Dist: dill>=0.4.1 + +----------------------------------------------------------------- +multiprocess: better multiprocessing and multithreading in Python +----------------------------------------------------------------- + +About Multiprocess +================== + +``multiprocess`` is a fork of ``multiprocessing``. ``multiprocess`` extends ``multiprocessing`` to provide enhanced serialization, using ``dill``. ``multiprocess`` leverages ``multiprocessing`` to support the spawning of processes using the API of the Python standard library's ``threading`` module. ``multiprocessing`` has been distributed as part of the standard library since Python 2.6. + +``multiprocess`` is part of ``pathos``, a Python framework for heterogeneous computing. +``multiprocess`` is in active development, so any user feedback, bug reports, comments, +or suggestions are highly appreciated. A list of issues is located at https://github.com/uqfoundation/multiprocess/issues, with a legacy list maintained at https://uqfoundation.github.io/project/pathos/query. + + +Major Features +============== + +``multiprocess`` enables: + + - objects to be transferred between processes using pipes or multi-producer/multi-consumer queues + - objects to be shared between processes using a server process or (for simple data) shared memory + +``multiprocess`` provides: + + - equivalents of all the synchronization primitives in ``threading`` + - a ``Pool`` class to facilitate submitting tasks to worker processes + - enhanced serialization, using ``dill`` + + +Current Release +=============== + +The latest released version of ``multiprocess`` is available from: + + https://pypi.org/project/multiprocess + +``multiprocess`` is distributed under a 3-clause BSD license, and is a fork of ``multiprocessing``. + + +Development Version +=================== + +You can get the latest development version with all the shiny new features at: + + https://github.com/uqfoundation + +If you have a new contribution, please submit a pull request. + + +Installation +============ + +``multiprocess`` can be installed with ``pip``:: + + $ pip install multiprocess + +For Python 2, a C compiler is required to build the included extension module from source. Python 3 and binary installs do not require a C compiler. + + +Requirements +============ + +``multiprocess`` requires: + + - ``python`` (or ``pypy``), **>=3.9** + - ``setuptools``, **>=42** + - ``dill``, **>=0.4.1** + + +Basic Usage +=========== + +The ``multiprocess.Process`` class follows the API of ``threading.Thread``. +For example :: + + from multiprocess import Process, Queue + + def f(q): + q.put('hello world') + + if __name__ == '__main__': + q = Queue() + p = Process(target=f, args=[q]) + p.start() + print (q.get()) + p.join() + +Synchronization primitives like locks, semaphores and conditions are +available, for example :: + + >>> from multiprocess import Condition + >>> c = Condition() + >>> print (c) + ), 0> + >>> c.acquire() + True + >>> print (c) + ), 0> + +One can also use a manager to create shared objects either in shared +memory or in a server process, for example :: + + >>> from multiprocess import Manager + >>> manager = Manager() + >>> l = manager.list(range(10)) + >>> l.reverse() + >>> print (l) + [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] + >>> print (repr(l)) + + +Tasks can be offloaded to a pool of worker processes in various ways, +for example :: + + >>> from multiprocess import Pool + >>> def f(x): return x*x + ... + >>> p = Pool(4) + >>> result = p.map_async(f, range(10)) + >>> print (result.get(timeout=1)) + [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] + +When ``dill`` is installed, serialization is extended to most objects, +for example :: + + >>> from multiprocess import Pool + >>> p = Pool(4) + >>> print (p.map(lambda x: (lambda y:y**2)(x) + x, xrange(10))) + [0, 2, 6, 12, 20, 30, 42, 56, 72, 90] + + +More Information +================ + +Probably the best way to get started is to look at the documentation at +http://multiprocess.rtfd.io. Also see ``multiprocess.tests`` for scripts that +demonstrate how ``multiprocess`` can be used to leverge multiple processes +to execute Python in parallel. You can run the test suite with +``python -m multiprocess.tests``. As ``multiprocess`` conforms to the +``multiprocessing`` interface, the examples and documentation found at +http://docs.python.org/library/multiprocessing.html also apply to +``multiprocess`` if one will ``import multiprocessing as multiprocess``. +See https://github.com/uqfoundation/multiprocess/tree/master/py3.12/examples +for a set of examples that demonstrate some basic use cases and benchmarking +for running Python code in parallel. Please feel free to submit a ticket on +github, or ask a question on stackoverflow (**@Mike McKerns**). If you would +like to share how you use ``multiprocess`` in your work, please send an email +(to **mmckerns at uqfoundation dot org**). + + +Citation +======== + +If you use ``multiprocess`` to do research that leads to publication, we ask that you +acknowledge use of ``multiprocess`` by citing the following in your publication:: + + M.M. McKerns, L. Strand, T. Sullivan, A. Fang, M.A.G. Aivazis, + "Building a framework for predictive science", Proceedings of + the 10th Python in Science Conference, 2011; + http://arxiv.org/pdf/1202.1056 + + Michael McKerns and Michael Aivazis, + "pathos: a framework for heterogeneous computing", 2010- ; + https://uqfoundation.github.io/project/pathos + +Please see https://uqfoundation.github.io/project/pathos or +http://arxiv.org/pdf/1202.1056 for further information. diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess-0.70.19.dist-info/RECORD b/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess-0.70.19.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..783f1a1a54a8c7b97fdf3ea12f6b6e3da4b9a0b7 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess-0.70.19.dist-info/RECORD @@ -0,0 +1,55 @@ +_multiprocess/__init__.py,sha256=e8IWKPCEFNy-WU2WIWhuSvTFEwJj92uugFsbLh_34xI,321 +multiprocess-0.70.19.dist-info/COPYING,sha256=n3_yfLkw0sMgLuB-PS1hRvTeZ20GmjPaMWbJjNuoOpU,1493 +multiprocess-0.70.19.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2 +multiprocess-0.70.19.dist-info/LICENSE,sha256=Tmk-RdRCvvuHwfnSfo1rFWbujdrAg9b1x8aCm6QIeMk,1930 +multiprocess-0.70.19.dist-info/METADATA,sha256=jncgaIRfiCIyvfZnItJwF-TeMG_QUGrSV9wSvlKxLu0,7202 +multiprocess-0.70.19.dist-info/RECORD,, +multiprocess-0.70.19.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +multiprocess-0.70.19.dist-info/WHEEL,sha256=GM0IDW7Y43qJBPbDimsKF86BsyxpyZ813Dji6gq8M58,93 +multiprocess-0.70.19.dist-info/top_level.txt,sha256=qtJc8GNdvi6suNpISX0Myln9AXJBYrNuas1MCqRPPqg,27 +multiprocess/__info__.py,sha256=-1WdiEeqRycnvLcpi0Tw0ESCgwiu0HFwMHfUe4-Dpzw,7999 +multiprocess/__init__.py,sha256=XWUBDGorUkDW04h64xe51pUV9N5gzvSDj3tNT2ekifw,1856 +multiprocess/connection.py,sha256=SeWa7rszDWs0aUtjrISV7usRSWLRuYsxo_Oc-vX2oXo,42809 +multiprocess/context.py,sha256=F6vR6Ge36IVhEe2WDys5nGri4YQc-I-9r-2RhNXyQ-c,11801 +multiprocess/dummy/__init__.py,sha256=fX04TgyWC0dKngc-vk1G3ID7ptmoSCW_NBrssl0q0ag,3066 +multiprocess/dummy/connection.py,sha256=1j3Rl5_enBM-_kMO6HDmum3kPAoFE4Zs485HV5H-V6s,1598 +multiprocess/forkserver.py,sha256=RAk02UQJY6BJp0vveKnQk7IgsPp44-2gf1HJTbXmVPg,15361 +multiprocess/heap.py,sha256=9rt5u5m5rkhJNfDWiCLpYDoWIt0LbElmx52yMqk7phQ,11626 +multiprocess/managers.py,sha256=wlWAcPTrZbzXSxQDF9Hl-0gJqN1Dx-Oqs622kTklGXs,49807 +multiprocess/pool.py,sha256=Kx6Sv8IwAHrEY6oKO9GMlIo8Yvl-fnOFiuiz9h3ROm4,32768 +multiprocess/popen_fork.py,sha256=evqs8eWJxTyfVx34E9Id6bI3gnjupGZ5FkkL5XMlJ6o,2580 +multiprocess/popen_forkserver.py,sha256=SrEbV8Wv0Uu_UegkaW-cayXRdjTGXr560Yyy90pj-yE,2227 +multiprocess/popen_spawn_posix.py,sha256=NzjS2j9fV8gxSSMhIKOJo7A8tO1vJ1lXuISxjrInYoo,2143 +multiprocess/popen_spawn_win32.py,sha256=w75TbIP1nFxrg7t4rwY2YwcAC-KmOwk3Z70sxzL4LSQ,4635 +multiprocess/process.py,sha256=EBZvxM9LQNrmyVy4_ttNvRQKuTKKKE449CBLi1NFLZA,12227 +multiprocess/queues.py,sha256=VjgMBwGGJgwhQpjok_p_vSJRzFvuIpSPA-dGE1VuUB8,12700 +multiprocess/reduction.py,sha256=1nSfiN5XdFEjkWUg1w2CZxFHgzT9IXPWHQzT7DuvQWo,9755 +multiprocess/resource_sharer.py,sha256=u6PH8rdqnPTozrZCgBxAVBHala35GUfYGwBDWGA4KQ4,5145 +multiprocess/resource_tracker.py,sha256=7DkE_XDGxEt6vnyxwTQ4cvVzxKAdtxwN2J1LzNNtMj4,15316 +multiprocess/shared_memory.py,sha256=9sacIZ_HqzarvNOGVzpBiAroaaKGHlRrcq-9M4fMFj4,18916 +multiprocess/sharedctypes.py,sha256=d-9SKRJHRlJJC331IxEoWOUXIeY9zxCbhWejXOmzGw0,6306 +multiprocess/spawn.py,sha256=GPe8Ht5UI4wqLSGcPJvo8rcJmDcEK2A_bVNUEyG6ozI,9641 +multiprocess/synchronize.py,sha256=eROp176rHAw56o63OBm0_aGGUFiIZqd70dYjg6wv-g0,12552 +multiprocess/tests/__init__.py,sha256=jbGS-lOm0bAdezlusEOZ1mWdpwNy_RP5mJRgsVL1GdI,247954 +multiprocess/tests/__main__.py,sha256=PmzuYN3ZOnU5kDLrVT3bz6ZMJBYV5Wn0SJpiK1hrpDk,888 +multiprocess/tests/mp_fork_bomb.py,sha256=6ADOEzh1aXHZ21aOGoBPhKcgB5sj15G9tQVgSc6GrlY,448 +multiprocess/tests/mp_preload.py,sha256=1-WvLFMaPoH-vZbpUaJvvZHFxTpA9tgmct2vblQy99M,365 +multiprocess/tests/mp_preload_flush.py,sha256=PMZ1FOihjj8RYhaFUANU3_jXZRvXljRJLa3lWLdtt4Q,291 +multiprocess/tests/mp_preload_main.py,sha256=T9FjjRzEGO-LDfNFc-Nh0w7kb974aHw9P8G5wFuqTuo,287 +multiprocess/tests/test_multiprocessing_fork/__init__.py,sha256=h4YpM8po-3m2kFeoCuV9ZsKDP8UgY-e1Sx3mat9oI9o,829 +multiprocess/tests/test_multiprocessing_fork/test_manager.py,sha256=A5K0x43Vrfe2jAVqFkBZpyzfuFoYm_Ickby4z31-xPg,194 +multiprocess/tests/test_multiprocessing_fork/test_misc.py,sha256=KhsBZTAEng6Dn-KAww-_eFMnnf14lWCXayQ4cMjx9Mk,193 +multiprocess/tests/test_multiprocessing_fork/test_processes.py,sha256=3bPddzlEnRMkan2cSLfDx77hEydV51Mx-l51ERtwt4Q,196 +multiprocess/tests/test_multiprocessing_fork/test_threads.py,sha256=_qq2fWmU5FuGtsOSoesXRV3DyCn0ptEezKrsnsE6MqI,194 +multiprocess/tests/test_multiprocessing_forkserver/__init__.py,sha256=GnMnSAaJ_y557T6vZsqu7C1Z_H1gOjGbXqklkfNz6Wc,738 +multiprocess/tests/test_multiprocessing_forkserver/test_manager.py,sha256=PQTSGlK3U5B2RVaHbW8MJ2pMWeYJj4arKVLm49Fc9TY,200 +multiprocess/tests/test_multiprocessing_forkserver/test_misc.py,sha256=EbDnh3_2MTNI3fFuC2GjM4pUUJ9cxRWXEwZZBV9FyoQ,199 +multiprocess/tests/test_multiprocessing_forkserver/test_processes.py,sha256=ks2hMY1_y0ZiyEGqgAK340b2M6oupXksCo3QFCsgTVk,202 +multiprocess/tests/test_multiprocessing_forkserver/test_threads.py,sha256=H1OyjLOUPkFsnw6QEEp7aSFPLPVndsZX92MAaU_gBFs,200 +multiprocess/tests/test_multiprocessing_main_handling.py,sha256=gFVw9bMOg8pFZp0QGPIPlXDtrY8K5poeb54PrTGT0Ow,11847 +multiprocess/tests/test_multiprocessing_spawn/__init__.py,sha256=pkhcDlpFcKmrN7-ekKF8Zz69x_X24JWqdIdiDypwJiY,639 +multiprocess/tests/test_multiprocessing_spawn/test_manager.py,sha256=3zMuPfUzqSaRZbNbVpQoU9OU52KaJotd7ps0H-_hdFU,195 +multiprocess/tests/test_multiprocessing_spawn/test_misc.py,sha256=Lb8nqsQL893F8TsvjBEh5s7nV37qAmetcLGk5tuZ3Kc,194 +multiprocess/tests/test_multiprocessing_spawn/test_processes.py,sha256=HoZZlby6gRZONFoV5-wEC50lQFEawvwT_Jh6yZ4SCag,197 +multiprocess/tests/test_multiprocessing_spawn/test_threads.py,sha256=pr9-K0KR8THDzYfGw4PGJ45RcGqREcTNX0OAE14exEw,195 +multiprocess/util.py,sha256=6Fugn0VHfQbY7CqOet_iHVu3Hl-cr6hb0KV-CipVNRM,17439 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess-0.70.19.dist-info/REQUESTED b/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess-0.70.19.dist-info/REQUESTED new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess-0.70.19.dist-info/WHEEL b/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess-0.70.19.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..1e0f52f0cd5855ab17bcf9947a0e184ada6c24e0 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess-0.70.19.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (75.6.0) +Root-Is-Purelib: true +Tag: py314-none-any + diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess-0.70.19.dist-info/top_level.txt b/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess-0.70.19.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..d547cb06bfcaf32e902f6d3c00ec331470ab2f71 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess-0.70.19.dist-info/top_level.txt @@ -0,0 +1,2 @@ +_multiprocess +multiprocess diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess/__info__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess/__info__.py new file mode 100644 index 0000000000000000000000000000000000000000..f4f092f9a4af577207ce587b4e18db0b42d2e799 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess/__info__.py @@ -0,0 +1,221 @@ +#!/usr/bin/env python +# +# Author: Mike McKerns (mmckerns @caltech and @uqfoundation) +# Copyright (c) 2026 The Uncertainty Quantification Foundation. +# License: 3-clause BSD. The full license text is available at: +# - https://github.com/uqfoundation/multiprocess/blob/master/LICENSE +''' +----------------------------------------------------------------- +multiprocess: better multiprocessing and multithreading in Python +----------------------------------------------------------------- + +About Multiprocess +================== + +``multiprocess`` is a fork of ``multiprocessing``. ``multiprocess`` extends ``multiprocessing`` to provide enhanced serialization, using ``dill``. ``multiprocess`` leverages ``multiprocessing`` to support the spawning of processes using the API of the Python standard library's ``threading`` module. ``multiprocessing`` has been distributed as part of the standard library since Python 2.6. + +``multiprocess`` is part of ``pathos``, a Python framework for heterogeneous computing. +``multiprocess`` is in active development, so any user feedback, bug reports, comments, +or suggestions are highly appreciated. A list of issues is located at https://github.com/uqfoundation/multiprocess/issues, with a legacy list maintained at https://uqfoundation.github.io/project/pathos/query. + + +Major Features +============== + +``multiprocess`` enables: + + - objects to be transferred between processes using pipes or multi-producer/multi-consumer queues + - objects to be shared between processes using a server process or (for simple data) shared memory + +``multiprocess`` provides: + + - equivalents of all the synchronization primitives in ``threading`` + - a ``Pool`` class to facilitate submitting tasks to worker processes + - enhanced serialization, using ``dill`` + + +Current Release +=============== + +The latest released version of ``multiprocess`` is available from: + + https://pypi.org/project/multiprocess + +``multiprocess`` is distributed under a 3-clause BSD license, and is a fork of ``multiprocessing``. + + +Development Version +=================== + +You can get the latest development version with all the shiny new features at: + + https://github.com/uqfoundation + +If you have a new contribution, please submit a pull request. + + +Installation +============ + +``multiprocess`` can be installed with ``pip``:: + + $ pip install multiprocess + +For Python 2, a C compiler is required to build the included extension module from source. Python 3 and binary installs do not require a C compiler. + + +Requirements +============ + +``multiprocess`` requires: + + - ``python`` (or ``pypy``), **>=3.9** + - ``setuptools``, **>=42** + - ``dill``, **>=0.4.1** + + +Basic Usage +=========== + +The ``multiprocess.Process`` class follows the API of ``threading.Thread``. +For example :: + + from multiprocess import Process, Queue + + def f(q): + q.put('hello world') + + if __name__ == '__main__': + q = Queue() + p = Process(target=f, args=[q]) + p.start() + print (q.get()) + p.join() + +Synchronization primitives like locks, semaphores and conditions are +available, for example :: + + >>> from multiprocess import Condition + >>> c = Condition() + >>> print (c) + ), 0> + >>> c.acquire() + True + >>> print (c) + ), 0> + +One can also use a manager to create shared objects either in shared +memory or in a server process, for example :: + + >>> from multiprocess import Manager + >>> manager = Manager() + >>> l = manager.list(range(10)) + >>> l.reverse() + >>> print (l) + [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] + >>> print (repr(l)) + + +Tasks can be offloaded to a pool of worker processes in various ways, +for example :: + + >>> from multiprocess import Pool + >>> def f(x): return x*x + ... + >>> p = Pool(4) + >>> result = p.map_async(f, range(10)) + >>> print (result.get(timeout=1)) + [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] + +When ``dill`` is installed, serialization is extended to most objects, +for example :: + + >>> from multiprocess import Pool + >>> p = Pool(4) + >>> print (p.map(lambda x: (lambda y:y**2)(x) + x, xrange(10))) + [0, 2, 6, 12, 20, 30, 42, 56, 72, 90] + + +More Information +================ + +Probably the best way to get started is to look at the documentation at +http://multiprocess.rtfd.io. Also see ``multiprocess.tests`` for scripts that +demonstrate how ``multiprocess`` can be used to leverge multiple processes +to execute Python in parallel. You can run the test suite with +``python -m multiprocess.tests``. As ``multiprocess`` conforms to the +``multiprocessing`` interface, the examples and documentation found at +http://docs.python.org/library/multiprocessing.html also apply to +``multiprocess`` if one will ``import multiprocessing as multiprocess``. +See https://github.com/uqfoundation/multiprocess/tree/master/py3.12/examples +for a set of examples that demonstrate some basic use cases and benchmarking +for running Python code in parallel. Please feel free to submit a ticket on +github, or ask a question on stackoverflow (**@Mike McKerns**). If you would +like to share how you use ``multiprocess`` in your work, please send an email +(to **mmckerns at uqfoundation dot org**). + + +Citation +======== + +If you use ``multiprocess`` to do research that leads to publication, we ask that you +acknowledge use of ``multiprocess`` by citing the following in your publication:: + + M.M. McKerns, L. Strand, T. Sullivan, A. Fang, M.A.G. Aivazis, + "Building a framework for predictive science", Proceedings of + the 10th Python in Science Conference, 2011; + http://arxiv.org/pdf/1202.1056 + + Michael McKerns and Michael Aivazis, + "pathos: a framework for heterogeneous computing", 2010- ; + https://uqfoundation.github.io/project/pathos + +Please see https://uqfoundation.github.io/project/pathos or +http://arxiv.org/pdf/1202.1056 for further information. + +''' + +__all__ = [] +__version__ = '0.70.19' +__author__ = 'Mike McKerns' + +__license__ = ''' +Copyright (c) 2008-2016 California Institute of Technology. +Copyright (c) 2016-2026 The Uncertainty Quantification Foundation. +All rights reserved. + +This software forks the python package "multiprocessing". Licence and +copyright information for multiprocessing can be found in "COPYING". + +This software is available subject to the conditions and terms laid +out below. By downloading and using this software you are agreeing +to the following conditions. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the names of the copyright holders nor the names of any of + the contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +''' diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..8ff0f1ac4d92706328b55fc8be649bc107092bbc --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess/__init__.py @@ -0,0 +1,66 @@ +# +# Package analogous to 'threading.py' but using processes +# +# multiprocessing/__init__.py +# +# This package is intended to duplicate the functionality (and much of +# the API) of threading.py but uses processes instead of threads. A +# subpackage 'multiprocessing.dummy' has the same API but is a simple +# wrapper for 'threading'. +# +# Original: Copyright (c) 2006-2008, R Oudkerk +# Original: Licensed to PSF under a Contributor Agreement. +# Forked by Mike McKerns, to support enhanced serialization. + +# author, version, license, and long description +try: # the package is installed + from .__info__ import __version__, __author__, __doc__, __license__ +except: # pragma: no cover + import os + import sys + root = os.path.dirname(os.path.dirname(os.path.abspath(os.path.dirname(__file__)))) + sys.path.append(root) + # get distribution meta info + from version import (__version__, __author__, + get_license_text, get_readme_as_rst) + __license__ = get_license_text(os.path.join(root, 'LICENSE')) + __license__ = "\n%s" % __license__ + __doc__ = get_readme_as_rst(os.path.join(root, 'README.md')) + del os, sys, root, get_license_text, get_readme_as_rst + + +import sys +from . import context + +# +# Copy stuff from default context +# + +__all__ = [x for x in dir(context._default_context) if not x.startswith('_')] +globals().update((name, getattr(context._default_context, name)) for name in __all__) + +# +# XXX These should not really be documented or public. +# + +SUBDEBUG = 5 +SUBWARNING = 25 + +# +# Alias for main module -- will be reset by bootstrapping child processes +# + +if '__main__' in sys.modules: + sys.modules['__mp_main__'] = sys.modules['__main__'] + + +def license(): + """print license""" + print (__license__) + return + +def citation(): + """print citation""" + print (__doc__[-491:-118]) + return + diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess/context.py b/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess/context.py new file mode 100644 index 0000000000000000000000000000000000000000..773858dba196cd255320fa24d47f417025fca0c1 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess/context.py @@ -0,0 +1,377 @@ +import os +import sys +import threading + +from . import process +from . import reduction + +__all__ = () + +# +# Exceptions +# + +class ProcessError(Exception): + pass + +class BufferTooShort(ProcessError): + pass + +class TimeoutError(ProcessError): + pass + +class AuthenticationError(ProcessError): + pass + +# +# Base type for contexts. Bound methods of an instance of this type are included in __all__ of __init__.py +# + +class BaseContext(object): + + ProcessError = ProcessError + BufferTooShort = BufferTooShort + TimeoutError = TimeoutError + AuthenticationError = AuthenticationError + + current_process = staticmethod(process.current_process) + parent_process = staticmethod(process.parent_process) + active_children = staticmethod(process.active_children) + + def cpu_count(self): + '''Returns the number of CPUs in the system''' + num = os.cpu_count() + if num is None: + raise NotImplementedError('cannot determine number of cpus') + else: + return num + + def Manager(self): + '''Returns a manager associated with a running server process + + The managers methods such as `Lock()`, `Condition()` and `Queue()` + can be used to create shared objects. + ''' + from .managers import SyncManager + m = SyncManager(ctx=self.get_context()) + m.start() + return m + + def Pipe(self, duplex=True): + '''Returns two connection object connected by a pipe''' + from .connection import Pipe + return Pipe(duplex) + + def Lock(self): + '''Returns a non-recursive lock object''' + from .synchronize import Lock + return Lock(ctx=self.get_context()) + + def RLock(self): + '''Returns a recursive lock object''' + from .synchronize import RLock + return RLock(ctx=self.get_context()) + + def Condition(self, lock=None): + '''Returns a condition object''' + from .synchronize import Condition + return Condition(lock, ctx=self.get_context()) + + def Semaphore(self, value=1): + '''Returns a semaphore object''' + from .synchronize import Semaphore + return Semaphore(value, ctx=self.get_context()) + + def BoundedSemaphore(self, value=1): + '''Returns a bounded semaphore object''' + from .synchronize import BoundedSemaphore + return BoundedSemaphore(value, ctx=self.get_context()) + + def Event(self): + '''Returns an event object''' + from .synchronize import Event + return Event(ctx=self.get_context()) + + def Barrier(self, parties, action=None, timeout=None): + '''Returns a barrier object''' + from .synchronize import Barrier + return Barrier(parties, action, timeout, ctx=self.get_context()) + + def Queue(self, maxsize=0): + '''Returns a queue object''' + from .queues import Queue + return Queue(maxsize, ctx=self.get_context()) + + def JoinableQueue(self, maxsize=0): + '''Returns a queue object''' + from .queues import JoinableQueue + return JoinableQueue(maxsize, ctx=self.get_context()) + + def SimpleQueue(self): + '''Returns a queue object''' + from .queues import SimpleQueue + return SimpleQueue(ctx=self.get_context()) + + def Pool(self, processes=None, initializer=None, initargs=(), + maxtasksperchild=None): + '''Returns a process pool object''' + from .pool import Pool + return Pool(processes, initializer, initargs, maxtasksperchild, + context=self.get_context()) + + def RawValue(self, typecode_or_type, *args): + '''Returns a shared object''' + from .sharedctypes import RawValue + return RawValue(typecode_or_type, *args) + + def RawArray(self, typecode_or_type, size_or_initializer): + '''Returns a shared array''' + from .sharedctypes import RawArray + return RawArray(typecode_or_type, size_or_initializer) + + def Value(self, typecode_or_type, *args, lock=True): + '''Returns a synchronized shared object''' + from .sharedctypes import Value + return Value(typecode_or_type, *args, lock=lock, + ctx=self.get_context()) + + def Array(self, typecode_or_type, size_or_initializer, *, lock=True): + '''Returns a synchronized shared array''' + from .sharedctypes import Array + return Array(typecode_or_type, size_or_initializer, lock=lock, + ctx=self.get_context()) + + def freeze_support(self): + '''Check whether this is a fake forked process in a frozen executable. + If so then run code specified by commandline and exit. + ''' + if self.get_start_method() == 'spawn' and getattr(sys, 'frozen', False): + from .spawn import freeze_support + freeze_support() + + def get_logger(self): + '''Return package logger -- if it does not already exist then + it is created. + ''' + from .util import get_logger + return get_logger() + + def log_to_stderr(self, level=None): + '''Turn on logging and add a handler which prints to stderr''' + from .util import log_to_stderr + return log_to_stderr(level) + + def allow_connection_pickling(self): + '''Install support for sending connections and sockets + between processes + ''' + # This is undocumented. In previous versions of multiprocessing + # its only effect was to make socket objects inheritable on Windows. + from . import connection # noqa: F401 + + def set_executable(self, executable): + '''Sets the path to a python.exe or pythonw.exe binary used to run + child processes instead of sys.executable when using the 'spawn' + start method. Useful for people embedding Python. + ''' + from .spawn import set_executable + set_executable(executable) + + def set_forkserver_preload(self, module_names): + '''Set list of module names to try to load in forkserver process. + This is really just a hint. + ''' + from .forkserver import set_forkserver_preload + set_forkserver_preload(module_names) + + def get_context(self, method=None): + if method is None: + return self + try: + ctx = _concrete_contexts[method] + except KeyError: + raise ValueError('cannot find context for %r' % method) from None + ctx._check_available() + return ctx + + def get_start_method(self, allow_none=False): + return self._name + + def set_start_method(self, method, force=False): + raise ValueError('cannot set start method of concrete context') + + @property + def reducer(self): + '''Controls how objects will be reduced to a form that can be + shared with other processes.''' + return globals().get('reduction') + + @reducer.setter + def reducer(self, reduction): + globals()['reduction'] = reduction + + def _check_available(self): + pass + +# +# Type of default context -- underlying context can be set at most once +# + +class Process(process.BaseProcess): + _start_method = None + @staticmethod + def _Popen(process_obj): + return _default_context.get_context().Process._Popen(process_obj) + + @staticmethod + def _after_fork(): + return _default_context.get_context().Process._after_fork() + +class DefaultContext(BaseContext): + Process = Process + + def __init__(self, context): + self._default_context = context + self._actual_context = None + + def get_context(self, method=None): + if method is None: + if self._actual_context is None: + self._actual_context = self._default_context + return self._actual_context + else: + return super().get_context(method) + + def set_start_method(self, method, force=False): + if self._actual_context is not None and not force: + raise RuntimeError('context has already been set') + if method is None and force: + self._actual_context = None + return + self._actual_context = self.get_context(method) + + def get_start_method(self, allow_none=False): + if self._actual_context is None: + if allow_none: + return None + self._actual_context = self._default_context + return self._actual_context._name + + def get_all_start_methods(self): + """Returns a list of the supported start methods, default first.""" + default = self._default_context.get_start_method() + start_method_names = [default] + start_method_names.extend( + name for name in _concrete_contexts if name != default + ) + return start_method_names + + +# +# Context types for fixed start method +# + +if sys.platform != 'win32': + + class ForkProcess(process.BaseProcess): + _start_method = 'fork' + @staticmethod + def _Popen(process_obj): + from .popen_fork import Popen + return Popen(process_obj) + + class SpawnProcess(process.BaseProcess): + _start_method = 'spawn' + @staticmethod + def _Popen(process_obj): + from .popen_spawn_posix import Popen + return Popen(process_obj) + + @staticmethod + def _after_fork(): + # process is spawned, nothing to do + pass + + class ForkServerProcess(process.BaseProcess): + _start_method = 'forkserver' + @staticmethod + def _Popen(process_obj): + from .popen_forkserver import Popen + return Popen(process_obj) + + class ForkContext(BaseContext): + _name = 'fork' + Process = ForkProcess + + class SpawnContext(BaseContext): + _name = 'spawn' + Process = SpawnProcess + + class ForkServerContext(BaseContext): + _name = 'forkserver' + Process = ForkServerProcess + def _check_available(self): + if not reduction.HAVE_SEND_HANDLE: + raise ValueError('forkserver start method not available') + + _concrete_contexts = { + 'fork': ForkContext(), + 'spawn': SpawnContext(), + 'forkserver': ForkServerContext(), + } + # bpo-33725: running arbitrary code after fork() is no longer reliable + # on macOS since macOS 10.14 (Mojave). Use spawn by default instead. + # gh-84559: We changed everyones default to a thread safeish one in 3.14. + if reduction.HAVE_SEND_HANDLE and sys.platform != 'darwin': + _default_context = DefaultContext(_concrete_contexts['fork']) #FIXME: forkserver + else: + _default_context = DefaultContext(_concrete_contexts['fork']) #FIXME: spawn + +else: # Windows + + class SpawnProcess(process.BaseProcess): + _start_method = 'spawn' + @staticmethod + def _Popen(process_obj): + from .popen_spawn_win32 import Popen + return Popen(process_obj) + + @staticmethod + def _after_fork(): + # process is spawned, nothing to do + pass + + class SpawnContext(BaseContext): + _name = 'spawn' + Process = SpawnProcess + + _concrete_contexts = { + 'spawn': SpawnContext(), + } + _default_context = DefaultContext(_concrete_contexts['spawn']) + +# +# Force the start method +# + +def _force_start_method(method): + _default_context._actual_context = _concrete_contexts[method] + +# +# Check that the current thread is spawning a child process +# + +_tls = threading.local() + +def get_spawning_popen(): + return getattr(_tls, 'spawning_popen', None) + +def set_spawning_popen(popen): + _tls.spawning_popen = popen + +def assert_spawning(obj): + if get_spawning_popen() is None: + raise RuntimeError( + '%s objects should only be shared between processes' + ' through inheritance' % type(obj).__name__ + ) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess/popen_forkserver.py b/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess/popen_forkserver.py new file mode 100644 index 0000000000000000000000000000000000000000..2cea1fc7a4602172b87df391fdc831c9d40ac49a --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess/popen_forkserver.py @@ -0,0 +1,74 @@ +import io +import os + +from .context import reduction, set_spawning_popen +if not reduction.HAVE_SEND_HANDLE: + raise ImportError('No support for sending fds between processes') +from . import forkserver +from . import popen_fork +from . import spawn +from . import util + + +__all__ = ['Popen'] + +# +# Wrapper for an fd used while launching a process +# + +class _DupFd(object): + def __init__(self, ind): + self.ind = ind + def detach(self): + return forkserver.get_inherited_fds()[self.ind] + +# +# Start child process using a server process +# + +class Popen(popen_fork.Popen): + method = 'forkserver' + DupFd = _DupFd + + def __init__(self, process_obj): + self._fds = [] + super().__init__(process_obj) + + def duplicate_for_child(self, fd): + self._fds.append(fd) + return len(self._fds) - 1 + + def _launch(self, process_obj): + prep_data = spawn.get_preparation_data(process_obj._name) + buf = io.BytesIO() + set_spawning_popen(self) + try: + reduction.dump(prep_data, buf) + reduction.dump(process_obj, buf) + finally: + set_spawning_popen(None) + + self.sentinel, w = forkserver.connect_to_new_process(self._fds) + # Keep a duplicate of the data pipe's write end as a sentinel of the + # parent process used by the child process. + _parent_w = os.dup(w) + self.finalizer = util.Finalize(self, util.close_fds, + (_parent_w, self.sentinel)) + with open(w, 'wb', closefd=True) as f: + f.write(buf.getbuffer()) + self.pid = forkserver.read_signed(self.sentinel) + + def poll(self, flag=os.WNOHANG): + if self.returncode is None: + from multiprocess.connection import wait + timeout = 0 if flag == os.WNOHANG else None + if not wait([self.sentinel], timeout): + return None + try: + self.returncode = forkserver.read_signed(self.sentinel) + except (OSError, EOFError): + # This should not happen usually, but perhaps the forkserver + # process itself got killed + self.returncode = 255 + + return self.returncode diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess/popen_spawn_posix.py b/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess/popen_spawn_posix.py new file mode 100644 index 0000000000000000000000000000000000000000..cccd659ae776377921d36dc17cd12ec8cd24fee3 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess/popen_spawn_posix.py @@ -0,0 +1,76 @@ +import io +import os + +from .context import reduction, set_spawning_popen +from . import popen_fork +from . import spawn +from . import util + +__all__ = ['Popen'] + + +# +# Wrapper for an fd used while launching a process +# + +class _DupFd(object): + def __init__(self, fd): + self.fd = fd + def detach(self): + return self.fd + +# +# Start child process using a fresh interpreter +# + +class Popen(popen_fork.Popen): + method = 'spawn' + DupFd = _DupFd + + def __init__(self, process_obj): + self._fds = [] + super().__init__(process_obj) + + def duplicate_for_child(self, fd): + self._fds.append(fd) + return fd + + def _launch(self, process_obj): + from . import resource_tracker + tracker_fd = resource_tracker.getfd() + self._fds.append(tracker_fd) + prep_data = spawn.get_preparation_data(process_obj._name) + fp = io.BytesIO() + set_spawning_popen(self) + try: + reduction.dump(prep_data, fp) + reduction.dump(process_obj, fp) + finally: + set_spawning_popen(None) + + parent_r = child_w = child_r = parent_w = None + try: + parent_r, child_w = os.pipe() + child_r, parent_w = os.pipe() + cmd = spawn.get_command_line(tracker_fd=tracker_fd, + pipe_handle=child_r) + self._fds.extend([child_r, child_w]) + self.pid = util.spawnv_passfds(spawn.get_executable(), + cmd, self._fds) + os.close(child_r) + child_r = None + os.close(child_w) + child_w = None + self.sentinel = parent_r + with open(parent_w, 'wb', closefd=False) as f: + f.write(fp.getbuffer()) + finally: + fds_to_close = [] + for fd in (parent_r, parent_w): + if fd is not None: + fds_to_close.append(fd) + self.finalizer = util.Finalize(self, util.close_fds, fds_to_close) + + for fd in (child_r, child_w): + if fd is not None: + os.close(fd) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess/process.py b/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess/process.py new file mode 100644 index 0000000000000000000000000000000000000000..12e069a78b4027944fc424651ec362677578fddc --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess/process.py @@ -0,0 +1,443 @@ +# +# Module providing the `Process` class which emulates `threading.Thread` +# +# multiprocessing/process.py +# +# Copyright (c) 2006-2008, R Oudkerk +# Licensed to PSF under a Contributor Agreement. +# + +__all__ = ['BaseProcess', 'current_process', 'active_children', + 'parent_process'] + +# +# Imports +# + +import os +import sys +import signal +import itertools +import threading +from _weakrefset import WeakSet + +# +# +# + +try: + ORIGINAL_DIR = os.path.abspath(os.getcwd()) +except OSError: + ORIGINAL_DIR = None + +# +# Public functions +# + +def current_process(): + ''' + Return process object representing the current process + ''' + return _current_process + +def active_children(): + ''' + Return list of process objects corresponding to live child processes + ''' + _cleanup() + return list(_children) + + +def parent_process(): + ''' + Return process object representing the parent process + ''' + return _parent_process + +# +# +# + +def _cleanup(): + # check for processes which have finished + for p in list(_children): + if (child_popen := p._popen) and child_popen.poll() is not None: + _children.discard(p) + +# +# The `Process` class +# + +class BaseProcess(object): + ''' + Process objects represent activity that is run in a separate process + + The class is analogous to `threading.Thread` + ''' + def _Popen(self): + raise NotImplementedError + + def __init__(self, group=None, target=None, name=None, args=(), kwargs=None, + *, daemon=None): + assert group is None, 'group argument must be None for now' + count = next(_process_counter) + self._identity = _current_process._identity + (count,) + self._config = _current_process._config.copy() + self._parent_pid = os.getpid() + self._parent_name = _current_process.name + self._popen = None + self._closed = False + self._target = target + self._args = tuple(args) + self._kwargs = dict(kwargs) if kwargs else {} + self._name = name or type(self).__name__ + '-' + \ + ':'.join(str(i) for i in self._identity) + if daemon is not None: + self.daemon = daemon + _dangling.add(self) + + def _check_closed(self): + if self._closed: + raise ValueError("process object is closed") + + def run(self): + ''' + Method to be run in sub-process; can be overridden in sub-class + ''' + if self._target: + self._target(*self._args, **self._kwargs) + + def start(self): + ''' + Start child process + ''' + self._check_closed() + assert self._popen is None, 'cannot start a process twice' + assert self._parent_pid == os.getpid(), \ + 'can only start a process object created by current process' + assert not _current_process._config.get('daemon'), \ + 'daemonic processes are not allowed to have children' + _cleanup() + self._popen = self._Popen(self) + self._sentinel = self._popen.sentinel + # Avoid a refcycle if the target function holds an indirect + # reference to the process object (see bpo-30775) + del self._target, self._args, self._kwargs + _children.add(self) + + def interrupt(self): + ''' + Terminate process; sends SIGINT signal + ''' + self._check_closed() + self._popen.interrupt() + + def terminate(self): + ''' + Terminate process; sends SIGTERM signal or uses TerminateProcess() + ''' + self._check_closed() + self._popen.terminate() + + def kill(self): + ''' + Terminate process; sends SIGKILL signal or uses TerminateProcess() + ''' + self._check_closed() + self._popen.kill() + + def join(self, timeout=None): + ''' + Wait until child process terminates + ''' + self._check_closed() + assert self._parent_pid == os.getpid(), 'can only join a child process' + assert self._popen is not None, 'can only join a started process' + res = self._popen.wait(timeout) + if res is not None: + _children.discard(self) + + def is_alive(self): + ''' + Return whether process is alive + ''' + self._check_closed() + if self is _current_process: + return True + assert self._parent_pid == os.getpid(), 'can only test a child process' + + if self._popen is None: + return False + + returncode = self._popen.poll() + if returncode is None: + return True + else: + _children.discard(self) + return False + + def close(self): + ''' + Close the Process object. + + This method releases resources held by the Process object. It is + an error to call this method if the child process is still running. + ''' + if self._popen is not None: + if self._popen.poll() is None: + raise ValueError("Cannot close a process while it is still running. " + "You should first call join() or terminate().") + self._popen.close() + self._popen = None + del self._sentinel + _children.discard(self) + self._closed = True + + @property + def name(self): + return self._name + + @name.setter + def name(self, name): + assert isinstance(name, str), 'name must be a string' + self._name = name + + @property + def daemon(self): + ''' + Return whether process is a daemon + ''' + return self._config.get('daemon', False) + + @daemon.setter + def daemon(self, daemonic): + ''' + Set whether process is a daemon + ''' + assert self._popen is None, 'process has already started' + self._config['daemon'] = daemonic + + @property + def authkey(self): + return self._config['authkey'] + + @authkey.setter + def authkey(self, authkey): + ''' + Set authorization key of process + ''' + self._config['authkey'] = AuthenticationString(authkey) + + @property + def exitcode(self): + ''' + Return exit code of process or `None` if it has yet to stop + ''' + self._check_closed() + if self._popen is None: + return self._popen + return self._popen.poll() + + @property + def ident(self): + ''' + Return identifier (PID) of process or `None` if it has yet to start + ''' + self._check_closed() + if self is _current_process: + return os.getpid() + else: + return self._popen and self._popen.pid + + pid = ident + + @property + def sentinel(self): + ''' + Return a file descriptor (Unix) or handle (Windows) suitable for + waiting for process termination. + ''' + self._check_closed() + try: + return self._sentinel + except AttributeError: + raise ValueError("process not started") from None + + def __repr__(self): + exitcode = None + if self is _current_process: + status = 'started' + elif self._closed: + status = 'closed' + elif self._parent_pid != os.getpid(): + status = 'unknown' + elif self._popen is None: + status = 'initial' + else: + exitcode = self._popen.poll() + if exitcode is not None: + status = 'stopped' + else: + status = 'started' + + info = [type(self).__name__, 'name=%r' % self._name] + if self._popen is not None: + info.append('pid=%s' % self._popen.pid) + info.append('parent=%s' % self._parent_pid) + info.append(status) + if exitcode is not None: + exitcode = _exitcode_to_name.get(exitcode, exitcode) + info.append('exitcode=%s' % exitcode) + if self.daemon: + info.append('daemon') + return '<%s>' % ' '.join(info) + + ## + + def _bootstrap(self, parent_sentinel=None): + from . import util, context + global _current_process, _parent_process, _process_counter, _children + + try: + if self._start_method is not None: + context._force_start_method(self._start_method) + _process_counter = itertools.count(1) + _children = set() + util._close_stdin() + old_process = _current_process + _current_process = self + _parent_process = _ParentProcess( + self._parent_name, self._parent_pid, parent_sentinel) + if threading._HAVE_THREAD_NATIVE_ID: + threading.main_thread()._set_native_id() + try: + self._after_fork() + finally: + # delay finalization of the old process object until after + # _run_after_forkers() is executed + del old_process + util.info('child process calling self.run()') + self.run() + exitcode = 0 + except SystemExit as e: + if e.code is None: + exitcode = 0 + elif isinstance(e.code, int): + exitcode = e.code + else: + sys.stderr.write(str(e.code) + '\n') + exitcode = 1 + except: + exitcode = 1 + import traceback + sys.stderr.write('Process %s:\n' % self.name) + traceback.print_exc() + finally: + threading._shutdown() + util.info('process exiting with exitcode %d' % exitcode) + util._flush_std_streams() + + return exitcode + + @staticmethod + def _after_fork(): + from . import util + util._finalizer_registry.clear() + util._run_after_forkers() + + +# +# We subclass bytes to avoid accidental transmission of auth keys over network +# + +class AuthenticationString(bytes): + def __reduce__(self): + from .context import get_spawning_popen + if get_spawning_popen() is None: + raise TypeError( + 'Pickling an AuthenticationString object is ' + 'disallowed for security reasons' + ) + return AuthenticationString, (bytes(self),) + + +# +# Create object representing the parent process +# + +class _ParentProcess(BaseProcess): + + def __init__(self, name, pid, sentinel): + self._identity = () + self._name = name + self._pid = pid + self._parent_pid = None + self._popen = None + self._closed = False + self._sentinel = sentinel + self._config = {} + + def is_alive(self): + from multiprocess.connection import wait + return not wait([self._sentinel], timeout=0) + + @property + def ident(self): + return self._pid + + def join(self, timeout=None): + ''' + Wait until parent process terminates + ''' + from multiprocess.connection import wait + wait([self._sentinel], timeout=timeout) + + pid = ident + +# +# Create object representing the main process +# + +class _MainProcess(BaseProcess): + + def __init__(self): + self._identity = () + self._name = 'MainProcess' + self._parent_pid = None + self._popen = None + self._closed = False + self._config = {'authkey': AuthenticationString(os.urandom(32)), + 'semprefix': '/mp'} + # Note that some versions of FreeBSD only allow named + # semaphores to have names of up to 14 characters. Therefore + # we choose a short prefix. + # + # On MacOSX in a sandbox it may be necessary to use a + # different prefix -- see #19478. + # + # Everything in self._config will be inherited by descendant + # processes. + + def close(self): + pass + + +_parent_process = None +_current_process = _MainProcess() +_process_counter = itertools.count(1) +_children = set() +del _MainProcess + +# +# Give names to some return codes +# + +_exitcode_to_name = {} + +for name, signum in list(signal.__dict__.items()): + if name[:3]=='SIG' and '_' not in name: + _exitcode_to_name[-signum] = f'-{name}' +del name, signum + +# For debug and leak testing +_dangling = WeakSet() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess/util.py b/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess/util.py new file mode 100644 index 0000000000000000000000000000000000000000..d01a481192a66f93c11238bc9d84449cd97d2e8d --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/multiprocess/util.py @@ -0,0 +1,560 @@ +# +# Module providing various facilities to other parts of the package +# +# multiprocessing/util.py +# +# Copyright (c) 2006-2008, R Oudkerk +# Licensed to PSF under a Contributor Agreement. +# + +import os +import itertools +import sys +import weakref +import atexit +import threading # we want threading to install it's + # cleanup function before multiprocessing does +from subprocess import _args_from_interpreter_flags # noqa: F401 + +from . import process + +__all__ = [ + 'sub_debug', 'debug', 'info', 'sub_warning', 'warn', 'get_logger', + 'log_to_stderr', 'get_temp_dir', 'register_after_fork', + 'is_exiting', 'Finalize', 'ForkAwareThreadLock', 'ForkAwareLocal', + 'close_all_fds_except', 'SUBDEBUG', 'SUBWARNING', + ] + +# +# Logging +# + +NOTSET = 0 +SUBDEBUG = 5 +DEBUG = 10 +INFO = 20 +SUBWARNING = 25 +WARNING = 30 + +LOGGER_NAME = 'multiprocess' +DEFAULT_LOGGING_FORMAT = '[%(levelname)s/%(processName)s] %(message)s' + +_logger = None +_log_to_stderr = False + +def sub_debug(msg, *args): + if _logger: + _logger.log(SUBDEBUG, msg, *args, stacklevel=2) + +def debug(msg, *args): + if _logger: + _logger.log(DEBUG, msg, *args, stacklevel=2) + +def info(msg, *args): + if _logger: + _logger.log(INFO, msg, *args, stacklevel=2) + +def warn(msg, *args): + if _logger: + _logger.log(WARNING, msg, *args, stacklevel=2) + +def sub_warning(msg, *args): + if _logger: + _logger.log(SUBWARNING, msg, *args, stacklevel=2) + +def get_logger(): + ''' + Returns logger used by multiprocess + ''' + global _logger + import logging + + with logging._lock: + if not _logger: + + _logger = logging.getLogger(LOGGER_NAME) + _logger.propagate = 0 + + # XXX multiprocessing should cleanup before logging + if hasattr(atexit, 'unregister'): + atexit.unregister(_exit_function) + atexit.register(_exit_function) + else: + atexit._exithandlers.remove((_exit_function, (), {})) + atexit._exithandlers.append((_exit_function, (), {})) + + return _logger + +def log_to_stderr(level=None): + ''' + Turn on logging and add a handler which prints to stderr + ''' + global _log_to_stderr + import logging + + logger = get_logger() + formatter = logging.Formatter(DEFAULT_LOGGING_FORMAT) + handler = logging.StreamHandler() + handler.setFormatter(formatter) + logger.addHandler(handler) + + if level: + logger.setLevel(level) + _log_to_stderr = True + return _logger + + +# Abstract socket support + +def _platform_supports_abstract_sockets(): + return sys.platform in ("linux", "android") + + +def is_abstract_socket_namespace(address): + if not address: + return False + if isinstance(address, bytes): + return address[0] == 0 + elif isinstance(address, str): + return address[0] == "\0" + raise TypeError(f'address type of {address!r} unrecognized') + + +abstract_sockets_supported = _platform_supports_abstract_sockets() + +# +# Function returning a temp directory which will be removed on exit +# + +# Maximum length of a NULL-terminated [1] socket file path is usually +# between 92 and 108 [2], but Linux is known to use a size of 108 [3]. +# BSD-based systems usually use a size of 104 or 108 and Windows does +# not create AF_UNIX sockets. +# +# [1]: https://github.com/python/cpython/issues/140734 +# [2]: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/sys_un.h.html +# [3]: https://man7.org/linux/man-pages/man7/unix.7.html + +if sys.platform == 'linux': + _SUN_PATH_MAX = 108 +elif sys.platform.startswith(('openbsd', 'freebsd')): + _SUN_PATH_MAX = 104 +else: + # On Windows platforms, we do not create AF_UNIX sockets. + _SUN_PATH_MAX = None if os.name == 'nt' else 92 + +def _remove_temp_dir(rmtree, tempdir): + rmtree(tempdir) + + current_process = process.current_process() + # current_process() can be None if the finalizer is called + # late during Python finalization + if current_process is not None: + current_process._config['tempdir'] = None + +def _get_base_temp_dir(tempfile): + """Get a temporary directory where socket files will be created. + + To prevent additional imports, pass a pre-imported 'tempfile' module. + """ + if os.name == 'nt': + return None + # Most of the time, the default temporary directory is /tmp. Thus, + # listener sockets files "$TMPDIR/pymp-XXXXXXXX/sock-XXXXXXXX" do + # not have a path length exceeding SUN_PATH_MAX. + # + # If users specify their own temporary directory, we may be unable + # to create those files. Therefore, we fall back to the system-wide + # temporary directory /tmp, assumed to exist on POSIX systems. + # + # See https://github.com/python/cpython/issues/132124. + base_tempdir = tempfile.gettempdir() + # Files created in a temporary directory are suffixed by a string + # generated by tempfile._RandomNameSequence, which, by design, + # is 8 characters long. + # + # Thus, the socket file path length (without NULL terminator) will be: + # + # len(base_tempdir + '/pymp-XXXXXXXX' + '/sock-XXXXXXXX') + sun_path_len = len(base_tempdir) + 14 + 14 + # Strict inequality to account for the NULL terminator. + # See https://github.com/python/cpython/issues/140734. + if sun_path_len < _SUN_PATH_MAX: + return base_tempdir + # Fallback to the default system-wide temporary directory. + # This ignores user-defined environment variables. + # + # On POSIX systems, /tmp MUST be writable by any application [1]. + # We however emit a warning if this is not the case to prevent + # obscure errors later in the execution. + # + # On some legacy systems, /var/tmp and /usr/tmp can be present + # and will be used instead. + # + # [1]: https://refspecs.linuxfoundation.org/FHS_3.0/fhs/ch03s18.html + dirlist = ['/tmp', '/var/tmp', '/usr/tmp'] + try: + base_system_tempdir = tempfile._get_default_tempdir(dirlist) + except FileNotFoundError: + warn("Process-wide temporary directory %s will not be usable for " + "creating socket files and no usable system-wide temporary " + "directory was found in %s", base_tempdir, dirlist) + # At this point, the system-wide temporary directory is not usable + # but we may assume that the user-defined one is, even if we will + # not be able to write socket files out there. + return base_tempdir + warn("Ignoring user-defined temporary directory: %s", base_tempdir) + # at most max(map(len, dirlist)) + 14 + 14 = 36 characters + assert len(base_system_tempdir) + 14 + 14 < _SUN_PATH_MAX + return base_system_tempdir + +def get_temp_dir(): + # get name of a temp directory which will be automatically cleaned up + tempdir = process.current_process()._config.get('tempdir') + if tempdir is None: + import shutil, tempfile + base_tempdir = _get_base_temp_dir(tempfile) + tempdir = tempfile.mkdtemp(prefix='pymp-', dir=base_tempdir) + info('created temp directory %s', tempdir) + # keep a strong reference to shutil.rmtree(), since the finalizer + # can be called late during Python shutdown + Finalize(None, _remove_temp_dir, args=(shutil.rmtree, tempdir), + exitpriority=-100) + process.current_process()._config['tempdir'] = tempdir + return tempdir + +# +# Support for reinitialization of objects when bootstrapping a child process +# + +_afterfork_registry = weakref.WeakValueDictionary() +_afterfork_counter = itertools.count() + +def _run_after_forkers(): + items = list(_afterfork_registry.items()) + items.sort() + for (index, ident, func), obj in items: + try: + func(obj) + except Exception as e: + info('after forker raised exception %s', e) + +def register_after_fork(obj, func): + _afterfork_registry[(next(_afterfork_counter), id(obj), func)] = obj + +# +# Finalization using weakrefs +# + +_finalizer_registry = {} +_finalizer_counter = itertools.count() + + +class Finalize(object): + ''' + Class which supports object finalization using weakrefs + ''' + def __init__(self, obj, callback, args=(), kwargs=None, exitpriority=None): + if (exitpriority is not None) and not isinstance(exitpriority,int): + raise TypeError( + "Exitpriority ({0!r}) must be None or int, not {1!s}".format( + exitpriority, type(exitpriority))) + + if obj is not None: + self._weakref = weakref.ref(obj, self) + elif exitpriority is None: + raise ValueError("Without object, exitpriority cannot be None") + + self._callback = callback + self._args = args + self._kwargs = kwargs or {} + self._key = (exitpriority, next(_finalizer_counter)) + self._pid = os.getpid() + + _finalizer_registry[self._key] = self + + def __call__(self, wr=None, + # Need to bind these locally because the globals can have + # been cleared at shutdown + _finalizer_registry=_finalizer_registry, + sub_debug=sub_debug, getpid=os.getpid): + ''' + Run the callback unless it has already been called or cancelled + ''' + try: + del _finalizer_registry[self._key] + except KeyError: + sub_debug('finalizer no longer registered') + else: + if self._pid != getpid(): + sub_debug('finalizer ignored because different process') + res = None + else: + sub_debug('finalizer calling %s with args %s and kwargs %s', + self._callback, self._args, self._kwargs) + res = self._callback(*self._args, **self._kwargs) + self._weakref = self._callback = self._args = \ + self._kwargs = self._key = None + return res + + def cancel(self): + ''' + Cancel finalization of the object + ''' + try: + del _finalizer_registry[self._key] + except KeyError: + pass + else: + self._weakref = self._callback = self._args = \ + self._kwargs = self._key = None + + def still_active(self): + ''' + Return whether this finalizer is still waiting to invoke callback + ''' + return self._key in _finalizer_registry + + def __repr__(self): + try: + obj = self._weakref() + except (AttributeError, TypeError): + obj = None + + if obj is None: + return '<%s object, dead>' % self.__class__.__name__ + + x = '<%s object, callback=%s' % ( + self.__class__.__name__, + getattr(self._callback, '__name__', self._callback)) + if self._args: + x += ', args=' + str(self._args) + if self._kwargs: + x += ', kwargs=' + str(self._kwargs) + if self._key[0] is not None: + x += ', exitpriority=' + str(self._key[0]) + return x + '>' + + +def _run_finalizers(minpriority=None): + ''' + Run all finalizers whose exit priority is not None and at least minpriority + + Finalizers with highest priority are called first; finalizers with + the same priority will be called in reverse order of creation. + ''' + if _finalizer_registry is None: + # This function may be called after this module's globals are + # destroyed. See the _exit_function function in this module for more + # notes. + return + + if minpriority is None: + f = lambda p : p[0] is not None + else: + f = lambda p : p[0] is not None and p[0] >= minpriority + + # Careful: _finalizer_registry may be mutated while this function + # is running (either by a GC run or by another thread). + + # list(_finalizer_registry) should be atomic, while + # list(_finalizer_registry.items()) is not. + keys = [key for key in list(_finalizer_registry) if f(key)] + keys.sort(reverse=True) + + for key in keys: + finalizer = _finalizer_registry.get(key) + # key may have been removed from the registry + if finalizer is not None: + sub_debug('calling %s', finalizer) + try: + finalizer() + except Exception: + import traceback + traceback.print_exc() + + if minpriority is None: + _finalizer_registry.clear() + +# +# Clean up on exit +# + +def is_exiting(): + ''' + Returns true if the process is shutting down + ''' + return _exiting or _exiting is None + +_exiting = False + +def _exit_function(info=info, debug=debug, _run_finalizers=_run_finalizers, + active_children=process.active_children, + current_process=process.current_process): + # We hold on to references to functions in the arglist due to the + # situation described below, where this function is called after this + # module's globals are destroyed. + + global _exiting + + if not _exiting: + _exiting = True + + info('process shutting down') + debug('running all "atexit" finalizers with priority >= 0') + _run_finalizers(0) + + if current_process() is not None: + # We check if the current process is None here because if + # it's None, any call to ``active_children()`` will raise + # an AttributeError (active_children winds up trying to + # get attributes from util._current_process). One + # situation where this can happen is if someone has + # manipulated sys.modules, causing this module to be + # garbage collected. The destructor for the module type + # then replaces all values in the module dict with None. + # For instance, after setuptools runs a test it replaces + # sys.modules with a copy created earlier. See issues + # #9775 and #15881. Also related: #4106, #9205, and + # #9207. + + for p in active_children(): + if p.daemon: + info('calling terminate() for daemon %s', p.name) + p._popen.terminate() + + for p in active_children(): + info('calling join() for process %s', p.name) + p.join() + + debug('running the remaining "atexit" finalizers') + _run_finalizers() + +atexit.register(_exit_function) + +# +# Some fork aware types +# + +class ForkAwareThreadLock(object): + def __init__(self): + self._lock = threading.Lock() + self.acquire = self._lock.acquire + self.release = self._lock.release + register_after_fork(self, ForkAwareThreadLock._at_fork_reinit) + + def _at_fork_reinit(self): + self._lock._at_fork_reinit() + + def __enter__(self): + return self._lock.__enter__() + + def __exit__(self, *args): + return self._lock.__exit__(*args) + + +class ForkAwareLocal(threading.local): + def __init__(self): + register_after_fork(self, lambda obj : obj.__dict__.clear()) + def __reduce__(self): + return type(self), () + +# +# Close fds except those specified +# + +try: + MAXFD = os.sysconf("SC_OPEN_MAX") +except Exception: + MAXFD = 256 + +def close_all_fds_except(fds): + fds = list(fds) + [-1, MAXFD] + fds.sort() + assert fds[-1] == MAXFD, 'fd too large' + for i in range(len(fds) - 1): + os.closerange(fds[i]+1, fds[i+1]) +# +# Close sys.stdin and replace stdin with os.devnull +# + +def _close_stdin(): + if sys.stdin is None: + return + + try: + sys.stdin.close() + except (OSError, ValueError): + pass + + try: + fd = os.open(os.devnull, os.O_RDONLY) + try: + sys.stdin = open(fd, encoding="utf-8", closefd=False) + except: + os.close(fd) + raise + except (OSError, ValueError): + pass + +# +# Flush standard streams, if any +# + +def _flush_std_streams(): + try: + sys.stdout.flush() + except (AttributeError, ValueError): + pass + try: + sys.stderr.flush() + except (AttributeError, ValueError): + pass + +# +# Start a program with only specified fds kept open +# + +def spawnv_passfds(path, args, passfds): + import _posixsubprocess + passfds = tuple(sorted(map(int, passfds))) + errpipe_read, errpipe_write = os.pipe() + try: + return _posixsubprocess.fork_exec( + args, [path], True, passfds, None, None, + -1, -1, -1, -1, -1, -1, errpipe_read, errpipe_write, + False, False, -1, None, None, None, -1, None) + finally: + os.close(errpipe_read) + os.close(errpipe_write) + + +def close_fds(*fds): + """Close each file descriptor given as an argument""" + for fd in fds: + os.close(fd) + + +def _cleanup_tests(): + """Cleanup multiprocessing resources when multiprocessing tests + completed.""" + + from test import support + + # cleanup multiprocessing + process._cleanup() + + # Stop the ForkServer process if it's running + from multiprocess import forkserver + forkserver._forkserver._stop() + + # Stop the ResourceTracker process if it's running + from multiprocess import resource_tracker + resource_tracker._resource_tracker._stop() + + # bpo-37421: Explicitly call _run_finalizers() to remove immediately + # temporary directories created by multiprocessing.util.get_temp_dir(). + _run_finalizers() + support.gc_collect() + + support.reap_children() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy-2.4.4.dist-info/licenses/numpy/_core/src/common/pythoncapi-compat/COPYING b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy-2.4.4.dist-info/licenses/numpy/_core/src/common/pythoncapi-compat/COPYING new file mode 100644 index 0000000000000000000000000000000000000000..59a39fb411050556ec1b321a42e870417134671a --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy-2.4.4.dist-info/licenses/numpy/_core/src/common/pythoncapi-compat/COPYING @@ -0,0 +1,14 @@ +BSD Zero Clause License + +Copyright Contributors to the pythoncapi_compat project. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy-2.4.4.dist-info/licenses/numpy/linalg/lapack_lite/LICENSE.txt b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy-2.4.4.dist-info/licenses/numpy/linalg/lapack_lite/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..9b379c9e897351cecb1f31f3061192f4319ac73d --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy-2.4.4.dist-info/licenses/numpy/linalg/lapack_lite/LICENSE.txt @@ -0,0 +1,48 @@ +Copyright (c) 1992-2013 The University of Tennessee and The University + of Tennessee Research Foundation. All rights + reserved. +Copyright (c) 2000-2013 The University of California Berkeley. All + rights reserved. +Copyright (c) 2006-2013 The University of Colorado Denver. All rights + reserved. + +$COPYRIGHT$ + +Additional copyrights may follow + +$HEADER$ + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +- Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +- Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer listed + in this license in the documentation and/or other materials + provided with the distribution. + +- Neither the name of the copyright holders nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +The copyright holders provide no reassurances that the source code +provided does not infringe any patent, copyright, or any other +intellectual property rights of third parties. The copyright holders +disclaim any liability to any recipient for claims brought against +recipient by any third party for infringement of that parties +intellectual property rights. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/lib/libnpymath.a b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/lib/libnpymath.a new file mode 100644 index 0000000000000000000000000000000000000000..7a8943f56c1e38da12ca88d6b384d9fc423c586d Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/lib/libnpymath.a differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/lib/npy-pkg-config/mlib.ini b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/lib/npy-pkg-config/mlib.ini new file mode 100644 index 0000000000000000000000000000000000000000..5840f5e1bc167f50ebc9fc98d60b60ee21ecbeec --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/lib/npy-pkg-config/mlib.ini @@ -0,0 +1,12 @@ +[meta] +Name = mlib +Description = Math library used with this version of numpy +Version = 1.0 + +[default] +Libs=-lm +Cflags= + +[msvc] +Libs=m.lib +Cflags= diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/recarray_from_file.fits b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/recarray_from_file.fits new file mode 100644 index 0000000000000000000000000000000000000000..ca48ee85153645a7510e201d574e9b119c089dce Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/recarray_from_file.fits differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-README.txt b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-README.txt new file mode 100644 index 0000000000000000000000000000000000000000..cfc9e4145d10276a4d296f3e189debeb581db780 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-README.txt @@ -0,0 +1,15 @@ +Steps to validate transcendental functions: +1) Add a file 'umath-validation-set-.txt', where ufuncname is name of + the function in NumPy you want to validate +2) The file should contain 4 columns: dtype,input,expected output,ulperror + a. dtype: one of np.float16, np.float32, np.float64 + b. input: floating point input to ufunc in hex. Example: 0x414570a4 + represents 12.340000152587890625 + c. expected output: floating point output for the corresponding input in hex. + This should be computed using a high(er) precision library and then rounded to + same format as the input. + d. ulperror: expected maximum ulp error of the function. This + should be same across all rows of the same dtype. Otherwise, the function is + tested for the maximum ulp error among all entries of that dtype. +3) Add file umath-validation-set-.txt to the test file test_umath_accuracy.py + which will then validate your ufunc. diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-arccos.csv b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-arccos.csv new file mode 100644 index 0000000000000000000000000000000000000000..82c8595cb4a617eb779c18b3e086d8ceb8d231e0 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-arccos.csv @@ -0,0 +1,1429 @@ +dtype,input,output,ulperrortol +np.float32,0xbddd7f50,0x3fd6eec2,3 +np.float32,0xbe32a20c,0x3fdf8182,3 +np.float32,0xbf607c09,0x4028f84f,3 +np.float32,0x3f25d906,0x3f5db544,3 +np.float32,0x3f01cec8,0x3f84febf,3 +np.float32,0x3f1d5c6e,0x3f68a735,3 +np.float32,0xbf0cab89,0x4009c36d,3 +np.float32,0xbf176b40,0x400d0941,3 +np.float32,0x3f3248b2,0x3f4ce6d4,3 +np.float32,0x3f390b48,0x3f434e0d,3 +np.float32,0xbe261698,0x3fddea43,3 +np.float32,0x3f0e1154,0x3f7b848b,3 +np.float32,0xbf379a3c,0x4017b764,3 +np.float32,0xbeda6f2c,0x4000bd62,3 +np.float32,0xbf6a0c3f,0x402e5d5a,3 +np.float32,0x3ef1d700,0x3f8a17b7,3 +np.float32,0xbf6f4f65,0x4031d30d,3 +np.float32,0x3f2c9eee,0x3f54adfd,3 +np.float32,0x3f3cfb18,0x3f3d8a1e,3 +np.float32,0x3ba80800,0x3fc867d2,3 +np.float32,0x3e723b08,0x3faa7e4d,3 +np.float32,0xbf65820f,0x402bb054,3 +np.float32,0xbee64e7a,0x40026410,3 +np.float32,0x3cb15140,0x3fc64a87,3 +np.float32,0x3f193660,0x3f6ddf2a,3 +np.float32,0xbf0e5b52,0x400a44f7,3 +np.float32,0x3ed55f14,0x3f920a4b,3 +np.float32,0x3dd11a80,0x3fbbf85c,3 +np.float32,0xbf4f5c4b,0x4020f4f9,3 +np.float32,0x3f787532,0x3e792e87,3 +np.float32,0x3f40e6ac,0x3f37a74f,3 +np.float32,0x3f1c1318,0x3f6a47b6,3 +np.float32,0xbe3c48d8,0x3fe0bb70,3 +np.float32,0xbe94d4bc,0x3feed08e,3 +np.float32,0xbe5c3688,0x3fe4ce26,3 +np.float32,0xbf6fe026,0x403239cb,3 +np.float32,0x3ea5983c,0x3f9ee7bf,3 +np.float32,0x3f1471e6,0x3f73c5bb,3 +np.float32,0x3f0e2622,0x3f7b6b87,3 +np.float32,0xbf597180,0x40257ad1,3 +np.float32,0xbeb5321c,0x3ff75d34,3 +np.float32,0x3f5afcd2,0x3f0b6012,3 +np.float32,0xbef2ff88,0x40042e14,3 +np.float32,0xbedc747e,0x400104f5,3 +np.float32,0xbee0c2f4,0x40019dfc,3 +np.float32,0xbf152cd8,0x400c57dc,3 +np.float32,0xbf6cf9e2,0x40303bbe,3 +np.float32,0x3ed9cd74,0x3f90d1a1,3 +np.float32,0xbf754406,0x4036767f,3 +np.float32,0x3f59c5c2,0x3f0db42f,3 +np.float32,0x3f2eefd8,0x3f518684,3 +np.float32,0xbf156bf9,0x400c6b49,3 +np.float32,0xbd550790,0x3fcfb8dc,3 +np.float32,0x3ede58fc,0x3f8f8f77,3 +np.float32,0xbf00ac19,0x40063c4b,3 +np.float32,0x3f4d25ba,0x3f24280e,3 +np.float32,0xbe9568be,0x3feef73c,3 +np.float32,0x3f67d154,0x3ee05547,3 +np.float32,0x3f617226,0x3efcb4f4,3 +np.float32,0xbf3ab41a,0x4018d6cc,3 +np.float32,0xbf3186fe,0x401592cd,3 +np.float32,0x3de3ba50,0x3fbacca9,3 +np.float32,0x3e789f98,0x3fa9ab97,3 +np.float32,0x3f016e08,0x3f8536d8,3 +np.float32,0x3e8b618c,0x3fa5c571,3 +np.float32,0x3eff97bc,0x3f8628a9,3 +np.float32,0xbf6729f0,0x402ca32f,3 +np.float32,0xbebec146,0x3ff9eddc,3 +np.float32,0x3ddb2e60,0x3fbb563a,3 +np.float32,0x3caa8e40,0x3fc66595,3 +np.float32,0xbf5973f2,0x40257bfa,3 +np.float32,0xbdd82c70,0x3fd69916,3 +np.float32,0xbedf4c82,0x400169ef,3 +np.float32,0x3ef8f22c,0x3f881184,3 +np.float32,0xbf1d74d4,0x400eedc9,3 +np.float32,0x3f2e10a6,0x3f52b790,3 +np.float32,0xbf08ecc0,0x4008a628,3 +np.float32,0x3ecb7db4,0x3f94be9f,3 +np.float32,0xbf052ded,0x40078bfc,3 +np.float32,0x3f2ee78a,0x3f5191e4,3 +np.float32,0xbf56f4e1,0x40245194,3 +np.float32,0x3f600a3e,0x3f014a25,3 +np.float32,0x3f3836f8,0x3f44808b,3 +np.float32,0x3ecabfbc,0x3f94f25c,3 +np.float32,0x3c70f500,0x3fc72dec,3 +np.float32,0x3f17c444,0x3f6fabf0,3 +np.float32,0xbf4c22a5,0x401f9a09,3 +np.float32,0xbe4205dc,0x3fe1765a,3 +np.float32,0x3ea49138,0x3f9f2d36,3 +np.float32,0xbece0082,0x3ffe106b,3 +np.float32,0xbe387578,0x3fe03eef,3 +np.float32,0xbf2b6466,0x40137a30,3 +np.float32,0xbe9dadb2,0x3ff12204,3 +np.float32,0xbf56b3f2,0x402433bb,3 +np.float32,0xbdf9b4d8,0x3fd8b51f,3 +np.float32,0x3f58a596,0x3f0fd4b4,3 +np.float32,0xbedf5748,0x40016b6e,3 +np.float32,0x3f446442,0x3f32476f,3 +np.float32,0x3f5be886,0x3f099658,3 +np.float32,0x3ea1e44c,0x3f9fe1de,3 +np.float32,0xbf11e9b8,0x400b585f,3 +np.float32,0xbf231f8f,0x4010befb,3 +np.float32,0xbf4395ea,0x401c2dd0,3 +np.float32,0x3e9e7784,0x3fa0c8a6,3 +np.float32,0xbe255184,0x3fddd14c,3 +np.float32,0x3f70d25e,0x3eb13148,3 +np.float32,0x3f220cdc,0x3f62a722,3 +np.float32,0xbd027bf0,0x3fcd23e7,3 +np.float32,0x3e4ef8b8,0x3faf02d2,3 +np.float32,0xbf76fc6b,0x40380728,3 +np.float32,0xbf57e761,0x4024c1cd,3 +np.float32,0x3ed4fc20,0x3f922580,3 +np.float32,0xbf09b64a,0x4008e1db,3 +np.float32,0x3f21ca62,0x3f62fcf5,3 +np.float32,0xbe55f610,0x3fe40170,3 +np.float32,0xbc0def80,0x3fca2bbb,3 +np.float32,0xbebc8764,0x3ff9547b,3 +np.float32,0x3ec1b200,0x3f9766d1,3 +np.float32,0xbf4ee44e,0x4020c1ee,3 +np.float32,0xbea85852,0x3ff3f22a,3 +np.float32,0xbf195c0c,0x400da3d3,3 +np.float32,0xbf754b5d,0x40367ce8,3 +np.float32,0xbdcbfe50,0x3fd5d52b,3 +np.float32,0xbf1adb87,0x400e1be3,3 +np.float32,0xbf6f8491,0x4031f898,3 +np.float32,0xbf6f9ae7,0x4032086e,3 +np.float32,0xbf52b3f0,0x40226790,3 +np.float32,0xbf698452,0x402e09f4,3 +np.float32,0xbf43dc9a,0x401c493a,3 +np.float32,0xbf165f7f,0x400cb664,3 +np.float32,0x3e635468,0x3fac682f,3 +np.float32,0xbe8cf2b6,0x3fecc28a,3 +np.float32,0x7f7fffff,0x7fc00000,3 +np.float32,0xbf4c6513,0x401fb597,3 +np.float32,0xbf02b8f8,0x4006d47e,3 +np.float32,0x3ed3759c,0x3f9290c8,3 +np.float32,0xbf2a7a5f,0x40132b98,3 +np.float32,0xbae65000,0x3fc9496f,3 +np.float32,0x3f65f5ea,0x3ee8ef07,3 +np.float32,0xbe7712fc,0x3fe84106,3 +np.float32,0xbb9ff700,0x3fc9afd2,3 +np.float32,0x3d8d87a0,0x3fc03592,3 +np.float32,0xbefc921c,0x40058c23,3 +np.float32,0xbf286566,0x401279d8,3 +np.float32,0x3f53857e,0x3f192eaf,3 +np.float32,0xbee9b0f4,0x4002dd90,3 +np.float32,0x3f4041f8,0x3f38a14a,3 +np.float32,0x3f54ea96,0x3f16b02d,3 +np.float32,0x3ea50ef8,0x3f9f0c01,3 +np.float32,0xbeaad2dc,0x3ff49a4a,3 +np.float32,0xbec428c8,0x3ffb636f,3 +np.float32,0xbda46178,0x3fd358c7,3 +np.float32,0xbefacfc4,0x40054b7f,3 +np.float32,0xbf7068f9,0x40329c85,3 +np.float32,0x3f70b850,0x3eb1caa7,3 +np.float32,0x7fa00000,0x7fe00000,3 +np.float32,0x80000000,0x3fc90fdb,3 +np.float32,0x3f68d5c8,0x3edb7cf3,3 +np.float32,0x3d9443d0,0x3fbfc98a,3 +np.float32,0xff7fffff,0x7fc00000,3 +np.float32,0xbeee7ba8,0x40038a5e,3 +np.float32,0xbf0aaaba,0x40092a73,3 +np.float32,0x3f36a4e8,0x3f46c0ee,3 +np.float32,0x3ed268e4,0x3f92da82,3 +np.float32,0xbee6002c,0x4002591b,3 +np.float32,0xbe8f2752,0x3fed5576,3 +np.float32,0x3f525912,0x3f1b40e0,3 +np.float32,0xbe8e151e,0x3fed0e16,3 +np.float32,0x1,0x3fc90fdb,3 +np.float32,0x3ee23b84,0x3f8e7ae1,3 +np.float32,0xbf5961ca,0x40257361,3 +np.float32,0x3f6bbca0,0x3ecd14cd,3 +np.float32,0x3e27b230,0x3fb4014d,3 +np.float32,0xbf183bb8,0x400d49fc,3 +np.float32,0x3f57759c,0x3f120b68,3 +np.float32,0xbd6994c0,0x3fd05d84,3 +np.float32,0xbf1dd684,0x400f0cc8,3 +np.float32,0xbececc1c,0x3ffe480a,3 +np.float32,0xbf48855f,0x401e206d,3 +np.float32,0x3f28c922,0x3f59d382,3 +np.float32,0xbf65c094,0x402bd3b0,3 +np.float32,0x3f657d42,0x3eeb11dd,3 +np.float32,0xbed32d4e,0x3fff7b15,3 +np.float32,0xbf31af02,0x4015a0b1,3 +np.float32,0x3d89eb00,0x3fc06f7f,3 +np.float32,0x3dac2830,0x3fbe4a17,3 +np.float32,0x3f7f7cb6,0x3d81a7df,3 +np.float32,0xbedbb570,0x4000ea82,3 +np.float32,0x3db37830,0x3fbdd4a8,3 +np.float32,0xbf376f48,0x4017a7fd,3 +np.float32,0x3f319f12,0x3f4dd2c9,3 +np.float32,0x7fc00000,0x7fc00000,3 +np.float32,0x3f1b4f70,0x3f6b3e31,3 +np.float32,0x3e33c880,0x3fb278d1,3 +np.float32,0x3f2796e0,0x3f5b69bd,3 +np.float32,0x3f4915d6,0x3f2ad4d0,3 +np.float32,0x3e4db120,0x3faf2ca0,3 +np.float32,0x3ef03dd4,0x3f8a8ba9,3 +np.float32,0x3e96ca88,0x3fa2cbf7,3 +np.float32,0xbeb136ce,0x3ff64d2b,3 +np.float32,0xbf2f3938,0x4014c75e,3 +np.float32,0x3f769dde,0x3e8b0d76,3 +np.float32,0x3f67cec8,0x3ee06148,3 +np.float32,0x3f0a1ade,0x3f80204e,3 +np.float32,0x3e4b9718,0x3faf7144,3 +np.float32,0x3cccb480,0x3fc5dcf3,3 +np.float32,0x3caeb740,0x3fc654f0,3 +np.float32,0x3f684e0e,0x3ede0678,3 +np.float32,0x3f0ba93c,0x3f7e6663,3 +np.float32,0xbf12bbc4,0x400b985e,3 +np.float32,0xbf2a8e1a,0x40133235,3 +np.float32,0x3f42029c,0x3f35f5c5,3 +np.float32,0x3eed1728,0x3f8b6f9c,3 +np.float32,0xbe5779ac,0x3fe432fd,3 +np.float32,0x3f6ed8b8,0x3ebc7e4b,3 +np.float32,0x3eea25b0,0x3f8c43c7,3 +np.float32,0x3f1988a4,0x3f6d786b,3 +np.float32,0xbe751674,0x3fe7ff8a,3 +np.float32,0xbe9f7418,0x3ff1997d,3 +np.float32,0x3dca11d0,0x3fbc6979,3 +np.float32,0x3f795226,0x3e6a6cab,3 +np.float32,0xbea780e0,0x3ff3b926,3 +np.float32,0xbed92770,0x4000901e,3 +np.float32,0xbf3e9f8c,0x401a49f8,3 +np.float32,0x3f0f7054,0x3f79ddb2,3 +np.float32,0x3a99d400,0x3fc8e966,3 +np.float32,0xbef082b0,0x4003d3c6,3 +np.float32,0xbf0d0790,0x4009defb,3 +np.float32,0xbf1649da,0x400cafb4,3 +np.float32,0xbea5aca8,0x3ff33d5c,3 +np.float32,0xbf4e1843,0x40206ba1,3 +np.float32,0xbe3d7d5c,0x3fe0e2ad,3 +np.float32,0xbf0e802d,0x400a500e,3 +np.float32,0xbf0de8f0,0x400a2295,3 +np.float32,0xbf3016ba,0x4015137e,3 +np.float32,0x3f36b1ea,0x3f46ae5d,3 +np.float32,0xbd27f170,0x3fce4fc7,3 +np.float32,0x3e96ec54,0x3fa2c31f,3 +np.float32,0x3eb4dfdc,0x3f9ad87d,3 +np.float32,0x3f5cac6c,0x3f0815cc,3 +np.float32,0xbf0489aa,0x40075bf1,3 +np.float32,0x3df010c0,0x3fba05f5,3 +np.float32,0xbf229f4a,0x4010956a,3 +np.float32,0x3f75e474,0x3e905a99,3 +np.float32,0xbcece6a0,0x3fccc397,3 +np.float32,0xbdb41528,0x3fd454e7,3 +np.float32,0x3ec8b2f8,0x3f958118,3 +np.float32,0x3f5eaa70,0x3f041a1d,3 +np.float32,0xbf32e1cc,0x40160b91,3 +np.float32,0xbe8e6026,0x3fed219c,3 +np.float32,0x3e6b3160,0x3fab65e3,3 +np.float32,0x3e6d7460,0x3fab1b81,3 +np.float32,0xbf13fbde,0x400bfa3b,3 +np.float32,0xbe8235ec,0x3fe9f9e3,3 +np.float32,0x3d71c4a0,0x3fc18096,3 +np.float32,0x3eb769d0,0x3f9a2aa0,3 +np.float32,0xbf68cb3b,0x402d99e4,3 +np.float32,0xbd917610,0x3fd22932,3 +np.float32,0x3d3cba60,0x3fc3297f,3 +np.float32,0xbf383cbe,0x4017f1cc,3 +np.float32,0xbeee96d0,0x40038e34,3 +np.float32,0x3ec89cb4,0x3f958725,3 +np.float32,0x3ebf92d8,0x3f97f95f,3 +np.float32,0x3f30f3da,0x3f4ec021,3 +np.float32,0xbd26b560,0x3fce45e4,3 +np.float32,0xbec0eb12,0x3ffa8330,3 +np.float32,0x3f6d592a,0x3ec4a6c1,3 +np.float32,0x3ea6d39c,0x3f9e9463,3 +np.float32,0x3e884184,0x3fa6951e,3 +np.float32,0x3ea566c4,0x3f9ef4d1,3 +np.float32,0x3f0c8f4c,0x3f7d5380,3 +np.float32,0x3f28e1ba,0x3f59b2cb,3 +np.float32,0x3f798538,0x3e66e1c3,3 +np.float32,0xbe2889b8,0x3fde39b8,3 +np.float32,0x3f3da05e,0x3f3c949c,3 +np.float32,0x3f24d700,0x3f5f073e,3 +np.float32,0xbe5b5768,0x3fe4b198,3 +np.float32,0xbed3b03a,0x3fff9f05,3 +np.float32,0x3e8a1c4c,0x3fa619eb,3 +np.float32,0xbf075d24,0x40083030,3 +np.float32,0x3f765648,0x3e8d1f52,3 +np.float32,0xbf70fc5e,0x403308bb,3 +np.float32,0x3f557ae8,0x3f15ab76,3 +np.float32,0x3f02f7ea,0x3f84521c,3 +np.float32,0x3f7ebbde,0x3dcbc5c5,3 +np.float32,0xbefbdfc6,0x40057285,3 +np.float32,0x3ec687ac,0x3f9617d9,3 +np.float32,0x3e4831c8,0x3fafe01b,3 +np.float32,0x3e25cde0,0x3fb43ea8,3 +np.float32,0x3e4f2ab8,0x3faefc70,3 +np.float32,0x3ea60ae4,0x3f9ec973,3 +np.float32,0xbf1ed55f,0x400f5dde,3 +np.float32,0xbf5ad4aa,0x40262479,3 +np.float32,0x3e8b3594,0x3fa5d0de,3 +np.float32,0x3f3a77aa,0x3f413c80,3 +np.float32,0xbf07512b,0x40082ca9,3 +np.float32,0x3f33d990,0x3f4ab5e5,3 +np.float32,0x3f521556,0x3f1bb78f,3 +np.float32,0xbecf6036,0x3ffe7086,3 +np.float32,0x3db91bd0,0x3fbd7a11,3 +np.float32,0x3ef63a74,0x3f88d839,3 +np.float32,0xbf2f1116,0x4014b99c,3 +np.float32,0xbf17fdc0,0x400d36b9,3 +np.float32,0xbe87df2c,0x3feb7117,3 +np.float32,0x80800000,0x3fc90fdb,3 +np.float32,0x3ee24c1c,0x3f8e7641,3 +np.float32,0x3f688dce,0x3edcd644,3 +np.float32,0xbf0f4e1c,0x400a8e1b,3 +np.float32,0x0,0x3fc90fdb,3 +np.float32,0x3f786eba,0x3e7999d4,3 +np.float32,0xbf404f80,0x401aeca8,3 +np.float32,0xbe9ffb6a,0x3ff1bd18,3 +np.float32,0x3f146bfc,0x3f73ccfd,3 +np.float32,0xbe47d630,0x3fe233ee,3 +np.float32,0xbe95847c,0x3feefe7c,3 +np.float32,0xbf135df0,0x400bc9e5,3 +np.float32,0x3ea19f3c,0x3f9ff411,3 +np.float32,0x3f235e20,0x3f60f247,3 +np.float32,0xbec789ec,0x3ffc4def,3 +np.float32,0x3f04b656,0x3f834db6,3 +np.float32,0x3dfaf440,0x3fb95679,3 +np.float32,0xbe4a7f28,0x3fe28abe,3 +np.float32,0x3ed4850c,0x3f92463b,3 +np.float32,0x3ec4ba5c,0x3f9694dd,3 +np.float32,0xbce24ca0,0x3fcc992b,3 +np.float32,0xbf5b7c6e,0x402675a0,3 +np.float32,0xbea3ce2a,0x3ff2bf04,3 +np.float32,0x3db02c60,0x3fbe0998,3 +np.float32,0x3c47b780,0x3fc78069,3 +np.float32,0x3ed33b20,0x3f92a0d5,3 +np.float32,0xbf4556d7,0x401cdcde,3 +np.float32,0xbe1b6e28,0x3fdc90ec,3 +np.float32,0xbf3289b7,0x4015ecd0,3 +np.float32,0x3df3f240,0x3fb9c76d,3 +np.float32,0x3eefa7d0,0x3f8ab61d,3 +np.float32,0xbe945838,0x3feeb006,3 +np.float32,0xbf0b1386,0x400949a3,3 +np.float32,0x3f77e546,0x3e812cc1,3 +np.float32,0x3e804ba0,0x3fa8a480,3 +np.float32,0x3f43dcea,0x3f331a06,3 +np.float32,0x3eb87450,0x3f99e33c,3 +np.float32,0x3e5f4898,0x3facecea,3 +np.float32,0x3f646640,0x3eeff10e,3 +np.float32,0x3f1aa832,0x3f6c1051,3 +np.float32,0xbebf6bfa,0x3ffa1bdc,3 +np.float32,0xbb77f300,0x3fc98bd4,3 +np.float32,0x3f3587fe,0x3f485645,3 +np.float32,0x3ef85f34,0x3f883b8c,3 +np.float32,0x3f50e584,0x3f1dc82c,3 +np.float32,0x3f1d30a8,0x3f68deb0,3 +np.float32,0x3ee75a78,0x3f8d0c86,3 +np.float32,0x3f2c023a,0x3f5581e1,3 +np.float32,0xbf074e34,0x40082bca,3 +np.float32,0xbead71f0,0x3ff54c6d,3 +np.float32,0xbf39ed88,0x40188e69,3 +np.float32,0x3f5d2fe6,0x3f07118b,3 +np.float32,0xbf1f79f8,0x400f9267,3 +np.float32,0x3e900c58,0x3fa48e99,3 +np.float32,0xbf759cb2,0x4036c47b,3 +np.float32,0x3f63329c,0x3ef5359c,3 +np.float32,0xbf5d6755,0x40276709,3 +np.float32,0x3f2ce31c,0x3f54519a,3 +np.float32,0x7f800000,0x7fc00000,3 +np.float32,0x3f1bf50e,0x3f6a6d9a,3 +np.float32,0x3f258334,0x3f5e25d8,3 +np.float32,0xbf661a3f,0x402c06ac,3 +np.float32,0x3d1654c0,0x3fc45cef,3 +np.float32,0xbef14a36,0x4003f009,3 +np.float32,0xbf356051,0x4016ec3a,3 +np.float32,0x3f6ccc42,0x3ec79193,3 +np.float32,0xbf2fe3d6,0x401501f9,3 +np.float32,0x3deedc80,0x3fba195b,3 +np.float32,0x3f2e5a28,0x3f52533e,3 +np.float32,0x3e6b68b8,0x3fab5ec8,3 +np.float32,0x3e458240,0x3fb037b7,3 +np.float32,0xbf24bab0,0x401144cb,3 +np.float32,0x3f600f4c,0x3f013fb2,3 +np.float32,0x3f021a04,0x3f84d316,3 +np.float32,0x3f741732,0x3e9cc948,3 +np.float32,0x3f0788aa,0x3f81a5b0,3 +np.float32,0x3f28802c,0x3f5a347c,3 +np.float32,0x3c9eb400,0x3fc69500,3 +np.float32,0x3e5d11e8,0x3fad357a,3 +np.float32,0x3d921250,0x3fbfecb9,3 +np.float32,0x3f354866,0x3f48b066,3 +np.float32,0xbf72cf43,0x40346d84,3 +np.float32,0x3eecdbb8,0x3f8b805f,3 +np.float32,0xbee585d0,0x400247fd,3 +np.float32,0x3e3607a8,0x3fb22fc6,3 +np.float32,0xbf0cb7d6,0x4009c71c,3 +np.float32,0xbf56b230,0x402432ec,3 +np.float32,0xbf4ced02,0x401fee29,3 +np.float32,0xbf3a325c,0x4018a776,3 +np.float32,0x3ecae8bc,0x3f94e732,3 +np.float32,0xbe48c7e8,0x3fe252bd,3 +np.float32,0xbe175d7c,0x3fdc0d5b,3 +np.float32,0x3ea78dac,0x3f9e632d,3 +np.float32,0xbe7434a8,0x3fe7e279,3 +np.float32,0x3f1f9e02,0x3f65c7b9,3 +np.float32,0xbe150f2c,0x3fdbc2c2,3 +np.float32,0x3ee13480,0x3f8ec423,3 +np.float32,0x3ecb7d54,0x3f94beb9,3 +np.float32,0x3f1cef42,0x3f693181,3 +np.float32,0xbf1ec06a,0x400f5730,3 +np.float32,0xbe112acc,0x3fdb44e8,3 +np.float32,0xbe77b024,0x3fe85545,3 +np.float32,0x3ec86fe0,0x3f959353,3 +np.float32,0x3f36b326,0x3f46ac9a,3 +np.float32,0x3e581a70,0x3fadd829,3 +np.float32,0xbf032c0c,0x4006f5f9,3 +np.float32,0xbf43b1fd,0x401c38b1,3 +np.float32,0x3f3701b4,0x3f463c5c,3 +np.float32,0x3f1a995a,0x3f6c22f1,3 +np.float32,0xbf05de0b,0x4007bf97,3 +np.float32,0x3d4bd960,0x3fc2b063,3 +np.float32,0x3f0e1618,0x3f7b7ed0,3 +np.float32,0x3edfd420,0x3f8f2628,3 +np.float32,0xbf6662fe,0x402c3047,3 +np.float32,0x3ec0690c,0x3f97bf9b,3 +np.float32,0xbeaf4146,0x3ff5c7a0,3 +np.float32,0x3f5e7764,0x3f04816d,3 +np.float32,0xbedd192c,0x40011bc5,3 +np.float32,0x3eb76350,0x3f9a2c5e,3 +np.float32,0xbed8108c,0x400069a5,3 +np.float32,0xbe59f31c,0x3fe48401,3 +np.float32,0xbea3e1e6,0x3ff2c439,3 +np.float32,0x3e26d1f8,0x3fb41db5,3 +np.float32,0x3f3a0a7c,0x3f41dba5,3 +np.float32,0x3ebae068,0x3f993ce4,3 +np.float32,0x3f2d8e30,0x3f536942,3 +np.float32,0xbe838bbe,0x3fea5247,3 +np.float32,0x3ebe4420,0x3f98538f,3 +np.float32,0xbcc59b80,0x3fcc265c,3 +np.float32,0x3eebb5c8,0x3f8bd334,3 +np.float32,0xbafc3400,0x3fc94ee8,3 +np.float32,0xbf63ddc1,0x402ac683,3 +np.float32,0xbeabdf80,0x3ff4e18f,3 +np.float32,0x3ea863f0,0x3f9e2a78,3 +np.float32,0x3f45b292,0x3f303bc1,3 +np.float32,0xbe68aa60,0x3fe666bf,3 +np.float32,0x3eb9de18,0x3f998239,3 +np.float32,0xbf719d85,0x4033815e,3 +np.float32,0x3edef9a8,0x3f8f62db,3 +np.float32,0xbd7781c0,0x3fd0cd1e,3 +np.float32,0x3f0b3b90,0x3f7ee92a,3 +np.float32,0xbe3eb3b4,0x3fe10a27,3 +np.float32,0xbf31a4c4,0x40159d23,3 +np.float32,0x3e929434,0x3fa3e5b0,3 +np.float32,0xbeb1a90e,0x3ff66b9e,3 +np.float32,0xbeba9b5e,0x3ff8d048,3 +np.float32,0xbf272a84,0x4012119e,3 +np.float32,0x3f1ebbd0,0x3f66e889,3 +np.float32,0x3ed3cdc8,0x3f927893,3 +np.float32,0xbf50dfce,0x40219b58,3 +np.float32,0x3f0c02de,0x3f7dfb62,3 +np.float32,0xbf694de3,0x402de8d2,3 +np.float32,0xbeaeb13e,0x3ff5a14f,3 +np.float32,0xbf61aa7a,0x40299702,3 +np.float32,0xbf13d159,0x400bed35,3 +np.float32,0xbeecd034,0x40034e0b,3 +np.float32,0xbe50c2e8,0x3fe35761,3 +np.float32,0x3f714406,0x3eae8e57,3 +np.float32,0xbf1ca486,0x400eabd8,3 +np.float32,0x3f5858cc,0x3f106497,3 +np.float32,0x3f670288,0x3ee41c84,3 +np.float32,0xbf20bd2c,0x400ff9f5,3 +np.float32,0xbe29afd8,0x3fde5eff,3 +np.float32,0xbf635e6a,0x402a80f3,3 +np.float32,0x3e82b7b0,0x3fa80446,3 +np.float32,0x3e982e7c,0x3fa26ece,3 +np.float32,0x3d9f0e00,0x3fbf1c6a,3 +np.float32,0x3e8299b4,0x3fa80c07,3 +np.float32,0xbf0529c1,0x40078ac3,3 +np.float32,0xbf403b8a,0x401ae519,3 +np.float32,0xbe57e09c,0x3fe44027,3 +np.float32,0x3ea1c8f4,0x3f9fe913,3 +np.float32,0xbe216a94,0x3fdd52d0,3 +np.float32,0x3f59c442,0x3f0db709,3 +np.float32,0xbd636260,0x3fd02bdd,3 +np.float32,0xbdbbc788,0x3fd4d08d,3 +np.float32,0x3dd19560,0x3fbbf0a3,3 +np.float32,0x3f060ad4,0x3f828641,3 +np.float32,0x3b102e00,0x3fc8c7c4,3 +np.float32,0x3f42b3b8,0x3f34e5a6,3 +np.float32,0x3f0255ac,0x3f84b071,3 +np.float32,0xbf014898,0x40066996,3 +np.float32,0x3e004dc0,0x3fb8fb51,3 +np.float32,0xbf594ff8,0x40256af2,3 +np.float32,0x3efafddc,0x3f877b80,3 +np.float32,0xbf5f0780,0x40283899,3 +np.float32,0x3ee95e54,0x3f8c7bcc,3 +np.float32,0x3eba2f0c,0x3f996c80,3 +np.float32,0x3f37721c,0x3f459b68,3 +np.float32,0x3e2be780,0x3fb378bf,3 +np.float32,0x3e550270,0x3fae3d69,3 +np.float32,0x3e0f9500,0x3fb70e0a,3 +np.float32,0xbf51974a,0x4021eaf4,3 +np.float32,0x3f393832,0x3f430d05,3 +np.float32,0x3f3df16a,0x3f3c1bd8,3 +np.float32,0xbd662340,0x3fd041ed,3 +np.float32,0x3f7e8418,0x3ddc9fce,3 +np.float32,0xbf392734,0x40184672,3 +np.float32,0x3ee3b278,0x3f8e124e,3 +np.float32,0x3eed4808,0x3f8b61d2,3 +np.float32,0xbf6fccbd,0x40322beb,3 +np.float32,0x3e3ecdd0,0x3fb1123b,3 +np.float32,0x3f4419e0,0x3f32bb45,3 +np.float32,0x3f595e00,0x3f0e7914,3 +np.float32,0xbe8c1486,0x3fec88c6,3 +np.float32,0xbf800000,0x40490fdb,3 +np.float32,0xbdaf5020,0x3fd4084d,3 +np.float32,0xbf407660,0x401afb63,3 +np.float32,0x3f0c3aa8,0x3f7db8b8,3 +np.float32,0xbcdb5980,0x3fcc7d5b,3 +np.float32,0x3f4738d4,0x3f2dd1ed,3 +np.float32,0x3f4d7064,0x3f23ab14,3 +np.float32,0xbeb1d576,0x3ff67774,3 +np.float32,0xbf507166,0x40216bb3,3 +np.float32,0x3e86484c,0x3fa71813,3 +np.float32,0x3f09123e,0x3f80bd35,3 +np.float32,0xbe9abe0e,0x3ff05cb2,3 +np.float32,0x3f3019dc,0x3f4fed21,3 +np.float32,0xbe99e00e,0x3ff0227d,3 +np.float32,0xbf155ec5,0x400c6739,3 +np.float32,0x3f5857ba,0x3f106698,3 +np.float32,0x3edf619c,0x3f8f45fb,3 +np.float32,0xbf5ab76a,0x40261664,3 +np.float32,0x3e54b5a8,0x3fae4738,3 +np.float32,0xbee92772,0x4002ca40,3 +np.float32,0x3f2fd610,0x3f504a7a,3 +np.float32,0xbf38521c,0x4017f97e,3 +np.float32,0xff800000,0x7fc00000,3 +np.float32,0x3e2da348,0x3fb34077,3 +np.float32,0x3f2f85fa,0x3f50b894,3 +np.float32,0x3e88f9c8,0x3fa66551,3 +np.float32,0xbf61e570,0x4029b648,3 +np.float32,0xbeab362c,0x3ff4b4a1,3 +np.float32,0x3ec6c310,0x3f9607bd,3 +np.float32,0x3f0d7bda,0x3f7c3810,3 +np.float32,0xbeba5d36,0x3ff8bf99,3 +np.float32,0x3f4b0554,0x3f27adda,3 +np.float32,0x3f60f5dc,0x3efebfb3,3 +np.float32,0x3f36ce2c,0x3f468603,3 +np.float32,0xbe70afac,0x3fe76e8e,3 +np.float32,0x3f673350,0x3ee339b5,3 +np.float32,0xbe124cf0,0x3fdb698c,3 +np.float32,0xbf1243dc,0x400b73d0,3 +np.float32,0x3f3c8850,0x3f3e3407,3 +np.float32,0x3ea02f24,0x3fa05500,3 +np.float32,0xbeffed34,0x400607db,3 +np.float32,0x3f5c75c2,0x3f08817c,3 +np.float32,0x3f4b2fbe,0x3f27682d,3 +np.float32,0x3ee47c34,0x3f8dd9f9,3 +np.float32,0x3f50d48c,0x3f1de584,3 +np.float32,0x3f12dc5e,0x3f75b628,3 +np.float32,0xbefe7e4a,0x4005d2f4,3 +np.float32,0xbec2e846,0x3ffb0cbc,3 +np.float32,0xbedc3036,0x4000fb80,3 +np.float32,0xbf48aedc,0x401e311f,3 +np.float32,0x3f6e032e,0x3ec11363,3 +np.float32,0xbf60de15,0x40292b72,3 +np.float32,0x3f06585e,0x3f8258ba,3 +np.float32,0x3ef49b98,0x3f894e66,3 +np.float32,0x3cc5fe00,0x3fc5f7cf,3 +np.float32,0xbf7525c5,0x40365c2c,3 +np.float32,0x3f64f9f8,0x3eed5fb2,3 +np.float32,0x3e8849c0,0x3fa692fb,3 +np.float32,0x3e50c878,0x3faec79e,3 +np.float32,0x3ed61530,0x3f91d831,3 +np.float32,0xbf54872e,0x40233724,3 +np.float32,0xbf52ee7f,0x4022815e,3 +np.float32,0xbe708c24,0x3fe769fc,3 +np.float32,0xbf26fc54,0x40120260,3 +np.float32,0x3f226e8a,0x3f6228db,3 +np.float32,0xbef30406,0x40042eb8,3 +np.float32,0x3f5d996c,0x3f063f5f,3 +np.float32,0xbf425f9c,0x401bb618,3 +np.float32,0x3e4bb260,0x3faf6dc9,3 +np.float32,0xbe52d5a4,0x3fe39b29,3 +np.float32,0xbe169cf0,0x3fdbf505,3 +np.float32,0xbedfc422,0x40017a8e,3 +np.float32,0x3d8ffef0,0x3fc00e05,3 +np.float32,0xbf12bdab,0x400b98f2,3 +np.float32,0x3f295d0a,0x3f590e88,3 +np.float32,0x3f49d8e4,0x3f2998aa,3 +np.float32,0xbef914f4,0x40050c12,3 +np.float32,0xbf4ea2b5,0x4020a61e,3 +np.float32,0xbf3a89e5,0x4018c762,3 +np.float32,0x3e8707b4,0x3fa6e67a,3 +np.float32,0x3ac55400,0x3fc8de86,3 +np.float32,0x800000,0x3fc90fdb,3 +np.float32,0xbeb9762c,0x3ff8819b,3 +np.float32,0xbebbe23c,0x3ff92815,3 +np.float32,0xbf598c88,0x402587a1,3 +np.float32,0x3e95d864,0x3fa30b4a,3 +np.float32,0x3f7f6f40,0x3d882486,3 +np.float32,0xbf53658c,0x4022b604,3 +np.float32,0xbf2a35f2,0x401314ad,3 +np.float32,0x3eb14380,0x3f9bcf28,3 +np.float32,0x3f0e0c64,0x3f7b8a7a,3 +np.float32,0x3d349920,0x3fc36a9a,3 +np.float32,0xbec2092c,0x3ffad071,3 +np.float32,0xbe1d08e8,0x3fdcc4e0,3 +np.float32,0xbf008968,0x40063243,3 +np.float32,0xbefad582,0x40054c51,3 +np.float32,0xbe52d010,0x3fe39a72,3 +np.float32,0x3f4afdac,0x3f27ba6b,3 +np.float32,0x3f6c483c,0x3eca4408,3 +np.float32,0xbef3cb68,0x40044b0c,3 +np.float32,0x3e94687c,0x3fa36b6f,3 +np.float32,0xbf64ae5c,0x402b39bb,3 +np.float32,0xbf0022b4,0x40061497,3 +np.float32,0x80000001,0x3fc90fdb,3 +np.float32,0x3f25bcd0,0x3f5dda4b,3 +np.float32,0x3ed91b40,0x3f9102d7,3 +np.float32,0x3f800000,0x0,3 +np.float32,0xbebc6aca,0x3ff94cca,3 +np.float32,0x3f239e9a,0x3f609e7d,3 +np.float32,0xbf7312be,0x4034a305,3 +np.float32,0x3efd16d0,0x3f86e148,3 +np.float32,0x3f52753a,0x3f1b0f72,3 +np.float32,0xbde58960,0x3fd7702c,3 +np.float32,0x3ef88580,0x3f883099,3 +np.float32,0x3eebaefc,0x3f8bd51e,3 +np.float32,0x3e877d2c,0x3fa6c807,3 +np.float32,0x3f1a0324,0x3f6cdf32,3 +np.float32,0xbedfe20a,0x40017eb6,3 +np.float32,0x3f205a3c,0x3f64d69d,3 +np.float32,0xbeed5b7c,0x400361b0,3 +np.float32,0xbf69ba10,0x402e2ad0,3 +np.float32,0x3c4fe200,0x3fc77014,3 +np.float32,0x3f043310,0x3f839a69,3 +np.float32,0xbeaf359a,0x3ff5c485,3 +np.float32,0x3db3f110,0x3fbdcd12,3 +np.float32,0x3e24af88,0x3fb462ed,3 +np.float32,0xbf34e858,0x4016c1c8,3 +np.float32,0x3f3334f2,0x3f4b9cd0,3 +np.float32,0xbf145882,0x400c16a2,3 +np.float32,0xbf541c38,0x40230748,3 +np.float32,0x3eba7e10,0x3f99574b,3 +np.float32,0xbe34c6e0,0x3fdfc731,3 +np.float32,0xbe957abe,0x3feefbf0,3 +np.float32,0xbf595a59,0x40256fdb,3 +np.float32,0xbdedc7b8,0x3fd7f4f0,3 +np.float32,0xbf627c02,0x402a06a9,3 +np.float32,0x3f339b78,0x3f4b0d18,3 +np.float32,0xbf2df6d2,0x40145929,3 +np.float32,0x3f617726,0x3efc9fd8,3 +np.float32,0xbee3a8fc,0x40020561,3 +np.float32,0x3efe9f68,0x3f867043,3 +np.float32,0xbf2c3e76,0x4013c3ba,3 +np.float32,0xbf218f28,0x40103d84,3 +np.float32,0xbf1ea847,0x400f4f7f,3 +np.float32,0x3ded9160,0x3fba2e31,3 +np.float32,0x3bce1b00,0x3fc841bf,3 +np.float32,0xbe90566e,0x3feda46a,3 +np.float32,0xbf5ea2ba,0x4028056b,3 +np.float32,0x3f538e62,0x3f191ee6,3 +np.float32,0xbf59e054,0x4025af74,3 +np.float32,0xbe8c98ba,0x3fecab24,3 +np.float32,0x3ee7bdb0,0x3f8cf0b7,3 +np.float32,0xbf2eb828,0x40149b2b,3 +np.float32,0xbe5eb904,0x3fe52068,3 +np.float32,0xbf16b422,0x400cd08d,3 +np.float32,0x3f1ab9b4,0x3f6bfa58,3 +np.float32,0x3dc23040,0x3fbce82a,3 +np.float32,0xbf29d9e7,0x4012f5e5,3 +np.float32,0xbf38f30a,0x40183393,3 +np.float32,0x3e88e798,0x3fa66a09,3 +np.float32,0x3f1d07e6,0x3f69124f,3 +np.float32,0xbe1d3d34,0x3fdccb7e,3 +np.float32,0xbf1715be,0x400ceec2,3 +np.float32,0x3f7a0eac,0x3e5d11f7,3 +np.float32,0xbe764924,0x3fe82707,3 +np.float32,0xbf01a1f8,0x4006837c,3 +np.float32,0x3f2be730,0x3f55a661,3 +np.float32,0xbf7bb070,0x403d4ce5,3 +np.float32,0xbd602110,0x3fd011c9,3 +np.float32,0x3f5d080c,0x3f07609d,3 +np.float32,0xbda20400,0x3fd332d1,3 +np.float32,0x3f1c62da,0x3f69e308,3 +np.float32,0xbf2c6916,0x4013d223,3 +np.float32,0xbf44f8fd,0x401cb816,3 +np.float32,0x3f4da392,0x3f235539,3 +np.float32,0x3e9e8aa0,0x3fa0c3a0,3 +np.float32,0x3e9633c4,0x3fa2f366,3 +np.float32,0xbf0422ab,0x40073ddd,3 +np.float32,0x3f518386,0x3f1cb603,3 +np.float32,0x3f24307a,0x3f5fe096,3 +np.float32,0xbdfb4220,0x3fd8ce24,3 +np.float32,0x3f179d28,0x3f6fdc7d,3 +np.float32,0xbecc2df0,0x3ffd911e,3 +np.float32,0x3f3dff0c,0x3f3c0782,3 +np.float32,0xbf58c4d8,0x4025295b,3 +np.float32,0xbdcf8438,0x3fd60dd3,3 +np.float32,0xbeeaf1b2,0x40030aa7,3 +np.float32,0xbf298a28,0x4012db45,3 +np.float32,0x3f6c4dec,0x3eca2678,3 +np.float32,0x3f4d1ac8,0x3f243a59,3 +np.float32,0x3f62cdfa,0x3ef6e8f8,3 +np.float32,0xbee8acce,0x4002b909,3 +np.float32,0xbd5f2af0,0x3fd00a15,3 +np.float32,0x3f5fde8e,0x3f01a453,3 +np.float32,0x3e95233c,0x3fa33aa4,3 +np.float32,0x3ecd2a60,0x3f9449be,3 +np.float32,0x3f10aa86,0x3f78619d,3 +np.float32,0x3f3888e8,0x3f440a70,3 +np.float32,0x3eeb5bfc,0x3f8bec7d,3 +np.float32,0xbe12d654,0x3fdb7ae6,3 +np.float32,0x3eca3110,0x3f951931,3 +np.float32,0xbe2d1b7c,0x3fdece05,3 +np.float32,0xbf29e9db,0x4012fb3a,3 +np.float32,0xbf0c50b8,0x4009a845,3 +np.float32,0xbed9f0e4,0x4000abef,3 +np.float64,0x3fd078ec5ba0f1d8,0x3ff4f7c00595a4d3,1 +np.float64,0xbfdbc39743b7872e,0x400027f85bce43b2,1 +np.float64,0xbfacd2707c39a4e0,0x3ffa08ae1075d766,1 +np.float64,0xbfc956890f32ad14,0x3ffc52308e7285fd,1 +np.float64,0xbf939c2298273840,0x3ff9706d18e6ea6b,1 +np.float64,0xbfe0d7048961ae09,0x4000fff4406bd395,1 +np.float64,0xbfe9d19b86f3a337,0x4004139bc683a69f,1 +np.float64,0x3fd35c7f90a6b900,0x3ff437220e9123f8,1 +np.float64,0x3fdddca171bbb944,0x3ff15da61e61ec08,1 +np.float64,0x3feb300de9f6601c,0x3fe1c6fadb68cdca,1 +np.float64,0xbfef1815327e302a,0x400739808fc6f964,1 +np.float64,0xbfe332d78e6665af,0x4001b6c4ef922f7c,1 +np.float64,0xbfedbf4dfb7b7e9c,0x40061cefed62a58b,1 +np.float64,0xbfd8dcc7e3b1b990,0x3fff84307713c2c3,1 +np.float64,0xbfedaf161c7b5e2c,0x400612027c1b2b25,1 +np.float64,0xbfed9bde897b37bd,0x4006053f05bd7d26,1 +np.float64,0xbfe081ebc26103d8,0x4000e70755eb66e0,1 +np.float64,0xbfe0366f9c606cdf,0x4000d11212f29afd,1 +np.float64,0xbfc7c115212f822c,0x3ffc1e8c9d58f7db,1 +np.float64,0x3fd8dd9a78b1bb34,0x3ff2bf8d0f4c9376,1 +np.float64,0xbfe54eff466a9dfe,0x4002655950b611f4,1 +np.float64,0xbfe4aad987e955b3,0x40022efb19882518,1 +np.float64,0x3f70231ca0204600,0x3ff911d834e7abf4,1 +np.float64,0x3fede01d047bc03a,0x3fd773cecbd8561b,1 +np.float64,0xbfd6a00d48ad401a,0x3ffee9fd7051633f,1 +np.float64,0x3fd44f3d50a89e7c,0x3ff3f74dd0fc9c91,1 +np.float64,0x3fe540f0d0ea81e2,0x3feb055a7c7d43d6,1 +np.float64,0xbf3ba2e200374800,0x3ff923b582650c6c,1 +np.float64,0x3fe93b2d3f72765a,0x3fe532fa15331072,1 +np.float64,0x3fee8ce5a17d19cc,0x3fd35666eefbe336,1 +np.float64,0x3fe55d5f8feabac0,0x3feadf3dcfe251d4,1 +np.float64,0xbfd1d2ede8a3a5dc,0x3ffda600041ac884,1 +np.float64,0xbfee41186e7c8231,0x40067a625cc6f64d,1 +np.float64,0x3fe521a8b9ea4352,0x3feb2f1a6c8084e5,1 +np.float64,0x3fc65378ef2ca6f0,0x3ff653dfe81ee9f2,1 +np.float64,0x3fdaba0fbcb57420,0x3ff23d630995c6ba,1 +np.float64,0xbfe6b7441d6d6e88,0x4002e182539a2994,1 +np.float64,0x3fda00b6dcb4016c,0x3ff2703d516f28e7,1 +np.float64,0xbfe8699f01f0d33e,0x400382326920ea9e,1 +np.float64,0xbfef5889367eb112,0x4007832af5983793,1 +np.float64,0x3fefb57c8aff6afa,0x3fc14700ab38dcef,1 +np.float64,0xbfda0dfdaab41bfc,0x3fffd75b6fd497f6,1 +np.float64,0xbfb059c36620b388,0x3ffa27c528b97a42,1 +np.float64,0xbfdd450ab1ba8a16,0x40005dcac6ab50fd,1 +np.float64,0xbfe54d6156ea9ac2,0x400264ce9f3f0fb9,1 +np.float64,0xbfe076e94760edd2,0x4000e3d1374884da,1 +np.float64,0xbfc063286720c650,0x3ffb2fd1d6bff0ef,1 +np.float64,0xbfe24680f2e48d02,0x40016ddfbb5bcc0e,1 +np.float64,0xbfdc9351d2b926a4,0x400044e3756fb765,1 +np.float64,0x3fefb173d8ff62e8,0x3fc1bd5626f80850,1 +np.float64,0x3fe77c117a6ef822,0x3fe7e57089bad2ec,1 +np.float64,0xbfddbcebf7bb79d8,0x40006eadb60406b3,1 +np.float64,0xbfecf6625ff9ecc5,0x40059e6c6961a6db,1 +np.float64,0x3fdc8950b8b912a0,0x3ff1bcfb2e27795b,1 +np.float64,0xbfeb2fa517765f4a,0x4004b00aee3e6888,1 +np.float64,0x3fd0efc88da1df90,0x3ff4d8f7cbd8248a,1 +np.float64,0xbfe6641a2becc834,0x4002c43362c1bd0f,1 +np.float64,0xbfe28aec0fe515d8,0x400182c91d4df039,1 +np.float64,0xbfd5ede8d0abdbd2,0x3ffeba7baef05ae8,1 +np.float64,0xbfbd99702a3b32e0,0x3ffafca21c1053f1,1 +np.float64,0x3f96f043f82de080,0x3ff8c6384d5eb610,1 +np.float64,0xbfe5badbc9eb75b8,0x400289c8cd5873d1,1 +np.float64,0x3fe5c6bf95eb8d80,0x3fea5093e9a3e43e,1 +np.float64,0x3fb1955486232ab0,0x3ff8086d4c3e71d5,1 +np.float64,0xbfea145f397428be,0x4004302237a35871,1 +np.float64,0xbfdabe685db57cd0,0x400003e2e29725fb,1 +np.float64,0xbfefc79758ff8f2f,0x400831814e23bfc8,1 +np.float64,0x3fd7edb66cafdb6c,0x3ff3006c5123bfaf,1 +np.float64,0xbfeaf7644bf5eec8,0x400495a7963ce4ed,1 +np.float64,0x3fdf838d78bf071c,0x3ff0e527eed73800,1 +np.float64,0xbfd1a0165ba3402c,0x3ffd98c5ab76d375,1 +np.float64,0x3fd75b67a9aeb6d0,0x3ff327c8d80b17cf,1 +np.float64,0x3fc2aa9647255530,0x3ff6ca854b157df1,1 +np.float64,0xbfe0957fd4612b00,0x4000ecbf3932becd,1 +np.float64,0x3fda1792c0b42f24,0x3ff269fbb2360487,1 +np.float64,0x3fd480706ca900e0,0x3ff3ea53a6aa3ae8,1 +np.float64,0xbfd0780ed9a0f01e,0x3ffd4bfd544c7d47,1 +np.float64,0x3feeec0cd77dd81a,0x3fd0a8a241fdb441,1 +np.float64,0x3fcfa933e93f5268,0x3ff5223478621a6b,1 +np.float64,0x3fdad2481fb5a490,0x3ff236b86c6b2b49,1 +np.float64,0x3fe03b129de07626,0x3ff09f21fb868451,1 +np.float64,0xbfc01212cd202424,0x3ffb259a07159ae9,1 +np.float64,0x3febdb912df7b722,0x3fe0768e20dac8c9,1 +np.float64,0xbfbf2148763e4290,0x3ffb154c361ce5bf,1 +np.float64,0xbfb1a7eb1e234fd8,0x3ffa3cb37ac4a176,1 +np.float64,0xbfe26ad1ec64d5a4,0x400178f480ecce8d,1 +np.float64,0x3fe6d1cd1b6da39a,0x3fe8dc20ec4dad3b,1 +np.float64,0xbfede0e53dfbc1ca,0x4006340d3bdd7c97,1 +np.float64,0xbfe8fd1bd9f1fa38,0x4003bc3477f93f40,1 +np.float64,0xbfe329d0f26653a2,0x4001b3f345af5648,1 +np.float64,0xbfe4bb20eee97642,0x40023451404d6d08,1 +np.float64,0x3fb574832e2ae900,0x3ff7ca4bed0c7110,1 +np.float64,0xbfdf3c098fbe7814,0x4000a525bb72d659,1 +np.float64,0x3fa453e6d428a7c0,0x3ff87f512bb9b0c6,1 +np.float64,0x3faaec888435d920,0x3ff84a7d9e4def63,1 +np.float64,0xbfcdc240df3b8480,0x3ffce30ece754e7f,1 +np.float64,0xbf8c3220f0386440,0x3ff95a600ae6e157,1 +np.float64,0x3fe806076c700c0e,0x3fe71784a96c76eb,1 +np.float64,0x3fedf9b0e17bf362,0x3fd6e35fc0a7b6c3,1 +np.float64,0xbfe1b48422636908,0x400141bd8ed251bc,1 +np.float64,0xbfe82e2817705c50,0x40036b5a5556d021,1 +np.float64,0xbfc8ef8ff931df20,0x3ffc450ffae7ce58,1 +np.float64,0xbfe919fa94f233f5,0x4003c7cce4697fe8,1 +np.float64,0xbfc3ace4a72759c8,0x3ffb9a197bb22651,1 +np.float64,0x3fe479f71ee8f3ee,0x3fec0bd2f59097aa,1 +np.float64,0xbfeeb54a967d6a95,0x4006da12c83649c5,1 +np.float64,0x3fe5e74ea8ebce9e,0x3fea2407cef0f08c,1 +np.float64,0x3fb382baf2270570,0x3ff7e98213b921ba,1 +np.float64,0xbfdd86fd3cbb0dfa,0x40006712952ddbcf,1 +np.float64,0xbfd250eb52a4a1d6,0x3ffdc6d56253b1cd,1 +np.float64,0x3fea30c4ed74618a,0x3fe3962deba4f30e,1 +np.float64,0x3fc895963d312b30,0x3ff60a5d52fcbccc,1 +np.float64,0x3fe9cc4f6273989e,0x3fe442740942c80f,1 +np.float64,0xbfe8769f5cf0ed3f,0x4003873b4cb5bfce,1 +np.float64,0xbfe382f3726705e7,0x4001cfeb3204d110,1 +np.float64,0x3fbfe9a9163fd350,0x3ff7220bd2b97c8f,1 +np.float64,0xbfca6162bb34c2c4,0x3ffc743f939358f1,1 +np.float64,0x3fe127a014e24f40,0x3ff0147c4bafbc39,1 +np.float64,0x3fee9cdd2a7d39ba,0x3fd2e9ef45ab122f,1 +np.float64,0x3fa9ffb97c33ff80,0x3ff851e69fa3542c,1 +np.float64,0x3fd378f393a6f1e8,0x3ff42faafa77de56,1 +np.float64,0xbfe4df1e1669be3c,0x400240284df1c321,1 +np.float64,0x3fed0ed79bfa1db0,0x3fdba89060aa96fb,1 +np.float64,0x3fdef2ee52bde5dc,0x3ff10e942244f4f1,1 +np.float64,0xbfdab38f3ab5671e,0x40000264d8d5b49b,1 +np.float64,0x3fbe95a96e3d2b50,0x3ff73774cb59ce2d,1 +np.float64,0xbfe945653af28aca,0x4003d9657bf129c2,1 +np.float64,0xbfb18f3f2a231e80,0x3ffa3b27cba23f50,1 +np.float64,0xbfef50bf22fea17e,0x40077998a850082c,1 +np.float64,0xbfc52b8c212a5718,0x3ffbca8d6560a2da,1 +np.float64,0x7ff8000000000000,0x7ff8000000000000,1 +np.float64,0x3fc1e3a02d23c740,0x3ff6e3a5fcac12a4,1 +np.float64,0xbfeb5e4ea5f6bc9d,0x4004c65abef9426f,1 +np.float64,0xbfe425b132684b62,0x400203c29608b00d,1 +np.float64,0xbfbfa1c19e3f4380,0x3ffb1d6367711158,1 +np.float64,0x3fbba2776e3744f0,0x3ff766f6df586fad,1 +np.float64,0xbfb5d0951e2ba128,0x3ffa7f712480b25e,1 +np.float64,0xbfe949fdab7293fb,0x4003db4530a18507,1 +np.float64,0xbfcf13519b3e26a4,0x3ffd0e6f0a6c38ee,1 +np.float64,0x3f91e6d72823cdc0,0x3ff8da5f08909b6e,1 +np.float64,0x3f78a2e360314600,0x3ff909586727caef,1 +np.float64,0xbfe1ae7e8fe35cfd,0x40013fef082caaa3,1 +np.float64,0x3fe97a6dd1f2f4dc,0x3fe4cb4b99863478,1 +np.float64,0xbfcc1e1e69383c3c,0x3ffcad250a949843,1 +np.float64,0x3faccb797c399700,0x3ff83b8066b49330,1 +np.float64,0x3fe7a2647a6f44c8,0x3fe7acceae6ec425,1 +np.float64,0xbfec3bfcf0f877fa,0x4005366af5a7175b,1 +np.float64,0xbfe2310b94646217,0x400167588fceb228,1 +np.float64,0x3feb167372762ce6,0x3fe1f74c0288fad8,1 +np.float64,0xbfb722b4ee2e4568,0x3ffa94a81b94dfca,1 +np.float64,0x3fc58da9712b1b50,0x3ff66cf8f072aa14,1 +np.float64,0xbfe7fff9d6effff4,0x400359d01b8141de,1 +np.float64,0xbfd56691c5aacd24,0x3ffe9686697797e8,1 +np.float64,0x3fe3ab0557e7560a,0x3fed1593959ef8e8,1 +np.float64,0x3fdd458995ba8b14,0x3ff1883d6f22a322,1 +np.float64,0x3fe7bbed2cef77da,0x3fe786d618094cda,1 +np.float64,0x3fa31a30c4263460,0x3ff88920b936fd79,1 +np.float64,0x8010000000000000,0x3ff921fb54442d18,1 +np.float64,0xbfdc5effbdb8be00,0x40003d95fe0dff11,1 +np.float64,0x3febfdad7e77fb5a,0x3fe030b5297dbbdd,1 +np.float64,0x3fe4f3f3b2e9e7e8,0x3feb6bc59eeb2be2,1 +np.float64,0xbfe44469fd6888d4,0x40020daa5488f97a,1 +np.float64,0xbfe19fddb0e33fbc,0x40013b8c902b167b,1 +np.float64,0x3fa36ad17c26d5a0,0x3ff8869b3e828134,1 +np.float64,0x3fcf23e6c93e47d0,0x3ff5336491a65d1e,1 +np.float64,0xffefffffffffffff,0x7ff8000000000000,1 +np.float64,0xbfe375f4cee6ebea,0x4001cbd2ba42e8b5,1 +np.float64,0xbfaef1215c3de240,0x3ffa19ab02081189,1 +np.float64,0xbfec39c59c78738b,0x4005353dc38e3d78,1 +np.float64,0x7ff4000000000000,0x7ffc000000000000,1 +np.float64,0xbfec09bb7b781377,0x40051c0a5754cb3a,1 +np.float64,0x3fe8301f2870603e,0x3fe6d783c5ef0944,1 +np.float64,0xbfed418c987a8319,0x4005cbae1b8693d1,1 +np.float64,0xbfdc16e7adb82dd0,0x4000338b634eaf03,1 +np.float64,0x3fd5d361bdaba6c4,0x3ff390899300a54c,1 +np.float64,0xbff0000000000000,0x400921fb54442d18,1 +np.float64,0x3fd5946232ab28c4,0x3ff3a14767813f29,1 +np.float64,0x3fe833e5fef067cc,0x3fe6d1be720edf2d,1 +np.float64,0x3fedf746a67bee8e,0x3fd6f127fdcadb7b,1 +np.float64,0x3fd90353d3b206a8,0x3ff2b54f7d369ba9,1 +np.float64,0x3fec4b4b72f89696,0x3fdf1b38d2e93532,1 +np.float64,0xbfe9c67596f38ceb,0x40040ee5f524ce03,1 +np.float64,0x3fd350d91aa6a1b4,0x3ff43a303c0da27f,1 +np.float64,0x3fd062603ba0c4c0,0x3ff4fd9514b935d8,1 +np.float64,0xbfe24c075f64980e,0x40016f8e9f2663b3,1 +np.float64,0x3fdaa546eeb54a8c,0x3ff2431a88fef1d5,1 +np.float64,0x3fe92b8151f25702,0x3fe54c67e005cbf9,1 +np.float64,0xbfe1be8b8a637d17,0x400144c078f67c6e,1 +np.float64,0xbfe468a1d7e8d144,0x40021964b118cbf4,1 +np.float64,0xbfdc6de4fab8dbca,0x40003fa9e27893d8,1 +np.float64,0xbfe3c2788ae784f1,0x4001e407ba3aa956,1 +np.float64,0xbfe2bf1542e57e2a,0x400192d4a9072016,1 +np.float64,0xbfe6982f4c6d305e,0x4002d681b1991bbb,1 +np.float64,0x3fdbceb1c4b79d64,0x3ff1f0f117b9d354,1 +np.float64,0x3fdb3705e7b66e0c,0x3ff21af01ca27ace,1 +np.float64,0x3fe3e6358ee7cc6c,0x3fecca4585053983,1 +np.float64,0xbfe16d6a9a62dad5,0x40012c7988aee247,1 +np.float64,0xbfce66e4413ccdc8,0x3ffcf83b08043a0c,1 +np.float64,0xbfeb6cd46876d9a9,0x4004cd61733bfb79,1 +np.float64,0xbfdb1cdd64b639ba,0x400010e6cf087cb7,1 +np.float64,0xbfe09e4e30e13c9c,0x4000ef5277c47721,1 +np.float64,0xbfee88dd127d11ba,0x4006b3cd443643ac,1 +np.float64,0xbf911e06c8223c00,0x3ff966744064fb05,1 +np.float64,0xbfe8f22bc471e458,0x4003b7d5513af295,1 +np.float64,0x3fe3d7329567ae66,0x3fecdd6c241f83ee,1 +np.float64,0x3fc8a9404b315280,0x3ff607dc175edf3f,1 +np.float64,0x3fe7eb80ad6fd702,0x3fe73f8fdb3e6a6c,1 +np.float64,0x3fef0931e37e1264,0x3fcf7fde80a3c5ab,1 +np.float64,0x3fe2ed3c3fe5da78,0x3fee038334cd1860,1 +np.float64,0x3fe251fdb8e4a3fc,0x3feec26dc636ac31,1 +np.float64,0x3feb239436764728,0x3fe1de9462455da7,1 +np.float64,0xbfe63fd7eeec7fb0,0x4002b78cfa3d2fa6,1 +np.float64,0x3fdd639cb5bac738,0x3ff17fc7d92b3eee,1 +np.float64,0x3fd0a7a13fa14f44,0x3ff4eba95c559c84,1 +np.float64,0x3fe804362d70086c,0x3fe71a44cd91ffa4,1 +np.float64,0xbfe0fecf6e61fd9f,0x40010bac8edbdc4f,1 +np.float64,0x3fcb74acfd36e958,0x3ff5ac84437f1b7c,1 +np.float64,0x3fe55053e1eaa0a8,0x3feaf0bf76304c30,1 +np.float64,0x3fc06b508d20d6a0,0x3ff7131da17f3902,1 +np.float64,0x3fdd78750fbaf0ec,0x3ff179e97fbf7f65,1 +np.float64,0x3fe44cb946689972,0x3fec46859b5da6be,1 +np.float64,0xbfeb165a7ff62cb5,0x4004a41c9cc9589e,1 +np.float64,0x3fe01ffb2b603ff6,0x3ff0aed52bf1c3c1,1 +np.float64,0x3f983c60a83078c0,0x3ff8c107805715ab,1 +np.float64,0x3fd8b5ff13b16c00,0x3ff2ca4a837a476a,1 +np.float64,0x3fc80510a1300a20,0x3ff61cc3b4af470b,1 +np.float64,0xbfd3935b06a726b6,0x3ffe1b3a2066f473,1 +np.float64,0xbfdd4a1f31ba943e,0x40005e81979ed445,1 +np.float64,0xbfa76afdd42ed600,0x3ff9dd63ffba72d2,1 +np.float64,0x3fe7e06d496fc0da,0x3fe7503773566707,1 +np.float64,0xbfea5fbfe874bf80,0x40045106af6c538f,1 +np.float64,0x3fee000c487c0018,0x3fd6bef1f8779d88,1 +np.float64,0xbfb39f4ee2273ea0,0x3ffa5c3f2b3888ab,1 +np.float64,0x3feb9247b0772490,0x3fe1092d2905efce,1 +np.float64,0x3fdaa39b4cb54738,0x3ff243901da0da17,1 +np.float64,0x3fcd5b2b493ab658,0x3ff56e262e65b67d,1 +np.float64,0x3fcf82512f3f04a0,0x3ff52738847c55f2,1 +np.float64,0x3fe2af5e0c655ebc,0x3fee4ffab0c82348,1 +np.float64,0xbfec0055d0f800ac,0x4005172d325933e8,1 +np.float64,0x3fe71da9336e3b52,0x3fe86f2e12f6e303,1 +np.float64,0x3fbefab0723df560,0x3ff731188ac716ec,1 +np.float64,0xbfe11dca28623b94,0x400114d3d4ad370d,1 +np.float64,0x3fbcbda8ca397b50,0x3ff755281078abd4,1 +np.float64,0x3fe687c7126d0f8e,0x3fe945099a7855cc,1 +np.float64,0xbfecde510579bca2,0x400590606e244591,1 +np.float64,0xbfd72de681ae5bce,0x3fff0ff797ad1755,1 +np.float64,0xbfe7c0f7386f81ee,0x40034226e0805309,1 +np.float64,0x3fd8d55619b1aaac,0x3ff2c1cb3267b14e,1 +np.float64,0x3fecd7a2ad79af46,0x3fdcabbffeaa279e,1 +np.float64,0x3fee7fb1a8fcff64,0x3fd3ae620286fe19,1 +np.float64,0xbfc5f3a3592be748,0x3ffbe3ed204d9842,1 +np.float64,0x3fec9e5527793caa,0x3fddb00bc8687e4b,1 +np.float64,0x3fc35dc70f26bb90,0x3ff6b3ded7191e33,1 +np.float64,0x3fda91c07ab52380,0x3ff24878848fec8f,1 +np.float64,0xbfe12cde1fe259bc,0x4001194ab99d5134,1 +np.float64,0xbfd35ab736a6b56e,0x3ffe0c5ce8356d16,1 +np.float64,0x3fc9c94123339280,0x3ff5e3239f3ad795,1 +np.float64,0xbfe72f54926e5ea9,0x40030c95d1d02b56,1 +np.float64,0xbfee283186fc5063,0x40066786bd0feb79,1 +np.float64,0xbfe7b383f56f6708,0x40033d23ef0e903d,1 +np.float64,0x3fd6037327ac06e8,0x3ff383bf2f311ddb,1 +np.float64,0x3fe0e344b561c68a,0x3ff03cd90fd4ba65,1 +np.float64,0xbfef0ff54b7e1feb,0x400730fa5fce381e,1 +np.float64,0x3fd269929da4d324,0x3ff476b230136d32,1 +np.float64,0xbfbc5fb9f638bf70,0x3ffae8e63a4e3234,1 +np.float64,0xbfe2e8bc84e5d179,0x40019fb5874f4310,1 +np.float64,0xbfd7017413ae02e8,0x3fff040d843c1531,1 +np.float64,0x3fefd362fa7fa6c6,0x3fbababc3ddbb21d,1 +np.float64,0x3fecb62ed3f96c5e,0x3fdd44ba77ccff94,1 +np.float64,0xbfb16fad5222df58,0x3ffa392d7f02b522,1 +np.float64,0x3fbcf4abc639e950,0x3ff751b23c40e27f,1 +np.float64,0x3fe128adbce2515c,0x3ff013dc91db04b5,1 +np.float64,0x3fa5dd9d842bbb40,0x3ff87300c88d512f,1 +np.float64,0xbfe61efcaf6c3dfa,0x4002ac27117f87c9,1 +np.float64,0x3feffe1233fffc24,0x3f9638d3796a4954,1 +np.float64,0xbfe78548b66f0a92,0x40032c0447b7bfe2,1 +np.float64,0x3fe7bd38416f7a70,0x3fe784e86d6546b6,1 +np.float64,0x3fe0d6bc5961ad78,0x3ff0443899e747ac,1 +np.float64,0xbfd0bb6e47a176dc,0x3ffd5d6dff390d41,1 +np.float64,0xbfec1d16b8f83a2e,0x40052620378d3b78,1 +np.float64,0x3fe9bbec20f377d8,0x3fe45e167c7a3871,1 +np.float64,0xbfeed81d9dfdb03b,0x4006f9dec2db7310,1 +np.float64,0xbfe1e35179e3c6a3,0x40014fd1b1186ac0,1 +np.float64,0xbfc9c7e605338fcc,0x3ffc60a6bd1a7126,1 +np.float64,0x3feec92810fd9250,0x3fd1afde414ab338,1 +np.float64,0xbfeb9f1d90773e3b,0x4004e606b773f5b0,1 +np.float64,0x3fcbabdf6b3757c0,0x3ff5a573866404af,1 +np.float64,0x3fe9f4e1fff3e9c4,0x3fe3fd7b6712dd7b,1 +np.float64,0xbfe6c0175ded802e,0x4002e4a4dc12f3fe,1 +np.float64,0xbfeefc96f37df92e,0x40071d367cd721ff,1 +np.float64,0xbfeaab58dc7556b2,0x400472ce37e31e50,1 +np.float64,0xbfc62668772c4cd0,0x3ffbea5e6c92010a,1 +np.float64,0x3fafe055fc3fc0a0,0x3ff822ce6502519a,1 +np.float64,0x3fd7b648ffaf6c90,0x3ff30f5a42f11418,1 +np.float64,0xbfe934fe827269fd,0x4003d2b9fed9e6ad,1 +np.float64,0xbfe6d691f2edad24,0x4002eca6a4b1797b,1 +np.float64,0x3fc7e62ced2fcc58,0x3ff620b1f44398b7,1 +np.float64,0xbfc89be9f33137d4,0x3ffc3a67a497f59c,1 +np.float64,0xbfe7793d536ef27a,0x40032794bf14dd64,1 +np.float64,0x3fde55a02dbcab40,0x3ff13b5f82d223e4,1 +np.float64,0xbfc8eabd7b31d57c,0x3ffc4472a81cb6d0,1 +np.float64,0x3fddcb5468bb96a8,0x3ff162899c381f2e,1 +np.float64,0xbfec7554d8f8eaaa,0x40055550e18ec463,1 +np.float64,0x3fd0b6e8b6a16dd0,0x3ff4e7b4781a50e3,1 +np.float64,0x3fedaae01b7b55c0,0x3fd8964916cdf53d,1 +np.float64,0x3fe0870f8a610e20,0x3ff072e7db95c2a2,1 +np.float64,0xbfec3e3ce2787c7a,0x4005379d0f6be873,1 +np.float64,0xbfe65502586caa04,0x4002beecff89147f,1 +np.float64,0xbfe0df39a961be74,0x4001025e36d1c061,1 +np.float64,0xbfb5d8edbe2bb1d8,0x3ffa7ff72b7d6a2b,1 +np.float64,0xbfde89574bbd12ae,0x40008ba4cd74544d,1 +np.float64,0xbfe72938f0ee5272,0x40030a5efd1acb6d,1 +np.float64,0xbfcd500d133aa01c,0x3ffcd462f9104689,1 +np.float64,0x3fe0350766606a0e,0x3ff0a2a3664e2c14,1 +np.float64,0xbfc892fb573125f8,0x3ffc3944641cc69d,1 +np.float64,0xbfba7dc7c634fb90,0x3ffaca9a6a0ffe61,1 +np.float64,0xbfeac94478759289,0x40048068a8b83e45,1 +np.float64,0xbfe8f60c1af1ec18,0x4003b961995b6e51,1 +np.float64,0x3fea1c0817743810,0x3fe3ba28c1643cf7,1 +np.float64,0xbfe42a0fefe85420,0x4002052aadd77f01,1 +np.float64,0x3fd2c61c56a58c38,0x3ff45e84cb9a7fa9,1 +np.float64,0xbfd83fb7cdb07f70,0x3fff59ab4790074c,1 +np.float64,0x3fd95e630fb2bcc8,0x3ff29c8bee1335ad,1 +np.float64,0x3feee88f387dd11e,0x3fd0c3ad3ded4094,1 +np.float64,0x3fe061291160c252,0x3ff0890010199bbc,1 +np.float64,0xbfdc7db3b5b8fb68,0x400041dea3759443,1 +np.float64,0x3fee23b320fc4766,0x3fd5ee73d7aa5c56,1 +np.float64,0xbfdc25c590b84b8c,0x4000359cf98a00b4,1 +np.float64,0xbfd63cbfd2ac7980,0x3ffecf7b9cf99b3c,1 +np.float64,0xbfbeb3c29a3d6788,0x3ffb0e66ecc0fc3b,1 +np.float64,0xbfd2f57fd6a5eb00,0x3ffdf1d7c79e1532,1 +np.float64,0xbfab3eda9c367db0,0x3ff9fc0c875f42e9,1 +np.float64,0xbfe12df1c6e25be4,0x4001199c673e698c,1 +np.float64,0x3fef8ab23a7f1564,0x3fc5aff358c59f1c,1 +np.float64,0x3fe562f50feac5ea,0x3fead7bce205f7d9,1 +np.float64,0x3fdc41adbeb8835c,0x3ff1d0f71341b8f2,1 +np.float64,0x3fe2748967e4e912,0x3fee9837f970ff9e,1 +np.float64,0xbfdaa89d57b5513a,0x400000e3889ba4cf,1 +np.float64,0x3fdf2a137dbe5428,0x3ff0fecfbecbbf86,1 +np.float64,0xbfea1fdcd2f43fba,0x4004351974b32163,1 +np.float64,0xbfe34a93a3e69528,0x4001be323946a3e0,1 +np.float64,0x3fe929bacff25376,0x3fe54f47bd7f4cf2,1 +np.float64,0xbfd667fbd6accff8,0x3ffedb04032b3a1a,1 +np.float64,0xbfeb695796f6d2af,0x4004cbb08ec6f525,1 +np.float64,0x3fd204df2ea409c0,0x3ff490f51e6670f5,1 +np.float64,0xbfd89a2757b1344e,0x3fff722127b988c4,1 +np.float64,0xbfd0787187a0f0e4,0x3ffd4c16dbe94f32,1 +np.float64,0x3fd44239bfa88474,0x3ff3fabbfb24b1fa,1 +np.float64,0xbfeb0b3489f61669,0x40049ee33d811d33,1 +np.float64,0x3fdcf04eaab9e09c,0x3ff1a02a29996c4e,1 +np.float64,0x3fd4c51e4fa98a3c,0x3ff3d8302c68fc9a,1 +np.float64,0x3fd1346645a268cc,0x3ff4c72b4970ecaf,1 +np.float64,0x3fd6a89d09ad513c,0x3ff357af6520afac,1 +np.float64,0xbfba0f469a341e90,0x3ffac3a8f41bed23,1 +np.float64,0xbfe13f8ddce27f1c,0x40011ed557719fd6,1 +np.float64,0x3fd43e5e26a87cbc,0x3ff3fbc040fc30dc,1 +np.float64,0x3fe838125a707024,0x3fe6cb5c987248f3,1 +np.float64,0x3fe128c30c625186,0x3ff013cff238dd1b,1 +np.float64,0xbfcd4718833a8e30,0x3ffcd33c96bde6f9,1 +np.float64,0x3fe43fcd08e87f9a,0x3fec573997456ec1,1 +np.float64,0xbfe9a29104734522,0x4003ffd502a1b57f,1 +np.float64,0xbfe4709d7968e13b,0x40021bfc5cd55af4,1 +np.float64,0x3fd21c3925a43874,0x3ff48adf48556cbb,1 +np.float64,0x3fe9a521b2734a44,0x3fe4844fc054e839,1 +np.float64,0xbfdfa6a912bf4d52,0x4000b4730ad8521e,1 +np.float64,0x3fe3740702e6e80e,0x3fed5b106283b6ed,1 +np.float64,0x3fd0a3aa36a14754,0x3ff4ecb02a5e3f49,1 +np.float64,0x3fdcb903d0b97208,0x3ff1afa5d692c5b9,1 +np.float64,0xbfe7d67839efacf0,0x40034a3146abf6f2,1 +np.float64,0x3f9981c6d8330380,0x3ff8bbf1853d7b90,1 +np.float64,0xbfe9d4191673a832,0x400414a9ab453c5d,1 +np.float64,0x3fef0a1e5c7e143c,0x3fcf70b02a54c415,1 +np.float64,0xbfd996dee6b32dbe,0x3fffb6cf707ad8e4,1 +np.float64,0x3fe19bef17e337de,0x3fef9e70d4fcedae,1 +np.float64,0x3fe34a59716694b2,0x3fed8f6d5cfba474,1 +np.float64,0x3fdf27e27cbe4fc4,0x3ff0ff70500e0c7c,1 +np.float64,0xbfe19df87fe33bf1,0x40013afb401de24c,1 +np.float64,0xbfbdfd97ba3bfb30,0x3ffb02ef8c225e57,1 +np.float64,0xbfe3d3417267a683,0x4001e95ed240b0f8,1 +np.float64,0x3fe566498b6acc94,0x3fead342957d4910,1 +np.float64,0x3ff0000000000000,0x0,1 +np.float64,0x3feb329bd8766538,0x3fe1c2225aafe3b4,1 +np.float64,0xbfc19ca703233950,0x3ffb575b5df057b9,1 +np.float64,0x3fe755027d6eaa04,0x3fe81eb99c262e00,1 +np.float64,0xbfe6c2b8306d8570,0x4002e594199f9eec,1 +np.float64,0x3fd69438e6ad2870,0x3ff35d2275ae891d,1 +np.float64,0x3fda3e7285b47ce4,0x3ff25f5573dd47ae,1 +np.float64,0x3fe7928a166f2514,0x3fe7c4490ef4b9a9,1 +np.float64,0xbfd4eb71b9a9d6e4,0x3ffe75e8ccb74be1,1 +np.float64,0xbfcc3a07f1387410,0x3ffcb0b8af914a5b,1 +np.float64,0xbfe6e80225edd004,0x4002f2e26eae8999,1 +np.float64,0xbfb347728a268ee8,0x3ffa56bd526a12db,1 +np.float64,0x3fe5140ead6a281e,0x3feb4132c9140a1c,1 +np.float64,0xbfc147f125228fe4,0x3ffb4cab18b9050f,1 +np.float64,0xbfcb9145b537228c,0x3ffc9b1b6227a8c9,1 +np.float64,0xbfda84ef4bb509de,0x3ffff7f8a674e17d,1 +np.float64,0x3fd2eb6bbfa5d6d8,0x3ff454c225529d7e,1 +np.float64,0x3fe18c95f1e3192c,0x3fefb0cf0efba75a,1 +np.float64,0x3fe78606efef0c0e,0x3fe7d6c3a092d64c,1 +np.float64,0x3fbad5119a35aa20,0x3ff773dffe3ce660,1 +np.float64,0x3fd0cf5903a19eb4,0x3ff4e15fd21fdb42,1 +np.float64,0xbfd85ce90bb0b9d2,0x3fff618ee848e974,1 +np.float64,0x3fe90e11b9f21c24,0x3fe57be62f606f4a,1 +np.float64,0x3fd7a2040faf4408,0x3ff314ce85457ec2,1 +np.float64,0xbfd73fba69ae7f74,0x3fff14bff3504811,1 +np.float64,0x3fa04b4bd42096a0,0x3ff89f9b52f521a2,1 +np.float64,0xbfd7219ce5ae433a,0x3fff0cac0b45cc18,1 +np.float64,0xbfe0cf4661e19e8d,0x4000fdadb14e3c22,1 +np.float64,0x3fd07469fea0e8d4,0x3ff4f8eaa9b2394a,1 +np.float64,0x3f9b05c5d8360b80,0x3ff8b5e10672db5c,1 +np.float64,0x3fe4c25b916984b8,0x3febad29bd0e25e2,1 +np.float64,0xbfde8b4891bd1692,0x40008beb88d5c409,1 +np.float64,0xbfe199a7efe33350,0x400139b089aee21c,1 +np.float64,0x3fecdad25cf9b5a4,0x3fdc9d062867e8c3,1 +np.float64,0xbfe979b277f2f365,0x4003eedb061e25a4,1 +np.float64,0x3fc8c7311f318e60,0x3ff6040b9aeaad9d,1 +np.float64,0x3fd2b605b8a56c0c,0x3ff462b9a955c224,1 +np.float64,0x3fc073b6ad20e770,0x3ff7120e9f2fd63c,1 +np.float64,0xbfec60ede678c1dc,0x40054a3863e24dc2,1 +np.float64,0x3fe225171be44a2e,0x3feef910dca420ea,1 +np.float64,0xbfd7529762aea52e,0x3fff19d00661f650,1 +np.float64,0xbfd781783daf02f0,0x3fff2667b90be461,1 +np.float64,0x3fe3f6ec6d67edd8,0x3fecb4e814a2e33a,1 +np.float64,0x3fece6702df9cce0,0x3fdc6719d92a50d2,1 +np.float64,0xbfb5c602ce2b8c08,0x3ffa7ec761ba856a,1 +np.float64,0xbfd61f0153ac3e02,0x3ffec78e3b1a6c4d,1 +np.float64,0xbfec3462b2f868c5,0x400532630bbd7050,1 +np.float64,0xbfdd248485ba490a,0x400059391c07c1bb,1 +np.float64,0xbfd424921fa84924,0x3ffe416a85d1dcdf,1 +np.float64,0x3fbb23a932364750,0x3ff76eef79209f7f,1 +np.float64,0x3fca248b0f344918,0x3ff5d77c5c1b4e5e,1 +np.float64,0xbfe69af4a4ed35ea,0x4002d77c2e4fbd4e,1 +np.float64,0x3fdafe3cdcb5fc78,0x3ff22a9be6efbbf2,1 +np.float64,0xbfebba3377f77467,0x4004f3836e1fe71a,1 +np.float64,0xbfe650fae06ca1f6,0x4002bd851406377c,1 +np.float64,0x3fda630007b4c600,0x3ff2554f1832bd94,1 +np.float64,0xbfda8107d9b50210,0x3ffff6e6209659f3,1 +np.float64,0x3fea759a02f4eb34,0x3fe31d1a632c9aae,1 +np.float64,0x3fbf88149e3f1030,0x3ff728313aa12ccb,1 +np.float64,0x3f7196d2a0232e00,0x3ff910647e1914c1,1 +np.float64,0x3feeae51d17d5ca4,0x3fd2709698d31f6f,1 +np.float64,0xbfd73cd663ae79ac,0x3fff13f96300b55a,1 +np.float64,0x3fd4fc5f06a9f8c0,0x3ff3c99359854b97,1 +np.float64,0x3fb29f5d6e253ec0,0x3ff7f7c20e396b20,1 +np.float64,0xbfd757c82aaeaf90,0x3fff1b34c6141e98,1 +np.float64,0x3fc56fd4cf2adfa8,0x3ff670c145122909,1 +np.float64,0x3fc609a2f52c1348,0x3ff65d3ef3cade2c,1 +np.float64,0xbfe1de631163bcc6,0x40014e5528fadb73,1 +np.float64,0xbfe7eb4a726fd695,0x40035202f49d95c4,1 +np.float64,0xbfc9223771324470,0x3ffc4b84d5e263b9,1 +np.float64,0x3fee91a8a87d2352,0x3fd3364befde8de6,1 +np.float64,0x3fbc9784fe392f10,0x3ff7578e29f6a1b2,1 +np.float64,0xbfec627c2c78c4f8,0x40054b0ff2cb9c55,1 +np.float64,0xbfb8b406a6316810,0x3ffaadd97062fb8c,1 +np.float64,0xbfecf98384f9f307,0x4005a043d9110d79,1 +np.float64,0xbfe5834bab6b0698,0x400276f114aebee4,1 +np.float64,0xbfd90f391eb21e72,0x3fff91e26a8f48f3,1 +np.float64,0xbfee288ce2fc511a,0x400667cb09aa04b3,1 +np.float64,0x3fd5aa5e32ab54bc,0x3ff39b7080a52214,1 +np.float64,0xbfee7ef907fcfdf2,0x4006ab96a8eba4c5,1 +np.float64,0x3fd6097973ac12f4,0x3ff3822486978bd1,1 +np.float64,0xbfe02d14b8e05a2a,0x4000ce5be53047b1,1 +np.float64,0xbf9c629a6838c540,0x3ff993897728c3f9,1 +np.float64,0xbfee2024667c4049,0x40066188782fb1f0,1 +np.float64,0xbfa42a88fc285510,0x3ff9c35a4bbce104,1 +np.float64,0x3fa407af5c280f60,0x3ff881b360d8eea1,1 +np.float64,0x3fed0ba42cfa1748,0x3fdbb7d55609175f,1 +np.float64,0xbfdd0b5844ba16b0,0x400055b0bb59ebb2,1 +np.float64,0x3fd88d97e6b11b30,0x3ff2d53c1ecb8f8c,1 +np.float64,0xbfeb7a915ef6f523,0x4004d410812eb84c,1 +np.float64,0xbfb5f979ca2bf2f0,0x3ffa8201d73cd4ca,1 +np.float64,0x3fb3b65dd6276cc0,0x3ff7e64576199505,1 +np.float64,0x3fcd47a7793a8f50,0x3ff570a7b672f160,1 +np.float64,0xbfa41dd30c283ba0,0x3ff9c2f488127eb3,1 +np.float64,0x3fe4b1ea1f6963d4,0x3febc2bed7760427,1 +np.float64,0xbfdd0f81d2ba1f04,0x400056463724b768,1 +np.float64,0x3fd15d93f7a2bb28,0x3ff4bc7a24eacfd7,1 +np.float64,0xbfe3213af8e64276,0x4001b14579dfded3,1 +np.float64,0x3fd90dfbeab21bf8,0x3ff2b26a6c2c3bb3,1 +np.float64,0xbfd02d54bca05aaa,0x3ffd38ab3886b203,1 +np.float64,0x3fc218dcad2431b8,0x3ff6dced56d5b417,1 +np.float64,0x3fea5edf71f4bdbe,0x3fe3455ee09f27e6,1 +np.float64,0x3fa74319042e8640,0x3ff867d224545438,1 +np.float64,0x3fd970ad92b2e15c,0x3ff2979084815dc1,1 +np.float64,0x3fce0a4bf73c1498,0x3ff557a4df32df3e,1 +np.float64,0x3fef5c8e10feb91c,0x3fc99ca0eeaaebe4,1 +np.float64,0xbfedae997ffb5d33,0x400611af18f407ab,1 +np.float64,0xbfbcf07d6239e0f8,0x3ffaf201177a2d36,1 +np.float64,0xbfc3c52541278a4c,0x3ffb9d2af0408e4a,1 +np.float64,0x3fe4ef44e4e9de8a,0x3feb71f7331255e5,1 +np.float64,0xbfccd9f5f539b3ec,0x3ffcc53a99339592,1 +np.float64,0xbfda32c745b4658e,0x3fffe16e8727ef89,1 +np.float64,0xbfef54932a7ea926,0x40077e4605e61ca1,1 +np.float64,0x3fe9d4ae3573a95c,0x3fe4344a069a3fd0,1 +np.float64,0x3fda567e73b4acfc,0x3ff258bd77a663c7,1 +np.float64,0xbfd5bcac5eab7958,0x3ffead6379c19c52,1 +np.float64,0xbfee5e56f97cbcae,0x40069131fc54018d,1 +np.float64,0x3fc2d4413925a880,0x3ff6c54163816298,1 +np.float64,0xbfe9ddf6e873bbee,0x400418d8c722f7c5,1 +np.float64,0x3fdaf2a683b5e54c,0x3ff22dcda599d69c,1 +np.float64,0xbfca69789f34d2f0,0x3ffc7547ff10b1a6,1 +np.float64,0x3fed076f62fa0ede,0x3fdbcbda03c1d72a,1 +np.float64,0xbfcb38326f367064,0x3ffc8fb55dadeae5,1 +np.float64,0x3fe1938705e3270e,0x3fefa88130c5adda,1 +np.float64,0x3feaffae3b75ff5c,0x3fe221e3da537c7e,1 +np.float64,0x3fefc94acb7f9296,0x3fbd9a360ace67b4,1 +np.float64,0xbfe8bddeb0f17bbe,0x4003a316685c767e,1 +np.float64,0x3fbe10fbee3c21f0,0x3ff73fceb10650f5,1 +np.float64,0x3fde9126c1bd224c,0x3ff12a742f734d0a,1 +np.float64,0xbfe9686c91f2d0d9,0x4003e7bc6ee77906,1 +np.float64,0xbfb1ba4892237490,0x3ffa3dda064c2509,1 +np.float64,0xbfe2879100e50f22,0x400181c1a5b16f0f,1 +np.float64,0x3fd1cd40b6a39a80,0x3ff49f70e3064e95,1 +np.float64,0xbfc965869132cb0c,0x3ffc5419f3b43701,1 +np.float64,0x3fea7a6f2874f4de,0x3fe31480fb2dd862,1 +np.float64,0x3fc3bc56892778b0,0x3ff6a7e8fa0e8b0e,1 +np.float64,0x3fec1ed451f83da8,0x3fdfd78e564b8ad7,1 +np.float64,0x3feb77d16df6efa2,0x3fe13d083344e45e,1 +np.float64,0xbfe822e7c67045d0,0x400367104a830cf6,1 +np.float64,0x8000000000000001,0x3ff921fb54442d18,1 +np.float64,0xbfd4900918a92012,0x3ffe5dc0e19737b4,1 +np.float64,0x3fed184187fa3084,0x3fdb7b7a39f234f4,1 +np.float64,0x3fecef846179df08,0x3fdc3cb2228c3682,1 +np.float64,0xbfe2d2aed165a55e,0x400198e21c5b861b,1 +np.float64,0x7ff0000000000000,0x7ff8000000000000,1 +np.float64,0xbfee9409a07d2813,0x4006bd358232d073,1 +np.float64,0xbfecedc2baf9db86,0x4005995df566fc21,1 +np.float64,0x3fe6d857396db0ae,0x3fe8d2cb8794aa99,1 +np.float64,0xbf9a579e7834af40,0x3ff98b5cc8021e1c,1 +np.float64,0x3fc664fefb2cca00,0x3ff651a664ccf8fa,1 +np.float64,0xbfe8a7aa0e714f54,0x40039a5b4df938a0,1 +np.float64,0xbfdf27d380be4fa8,0x4000a241074dbae6,1 +np.float64,0x3fe00ddf55e01bbe,0x3ff0b94eb1ea1851,1 +np.float64,0x3feb47edbff68fdc,0x3fe199822d075959,1 +np.float64,0x3fb4993822293270,0x3ff7d80c838186d0,1 +np.float64,0xbfca2cd1473459a4,0x3ffc6d88c8de3d0d,1 +np.float64,0xbfea7d9c7674fb39,0x40045e4559e9e52d,1 +np.float64,0x3fe0dce425e1b9c8,0x3ff04099cab23289,1 +np.float64,0x3fd6bb7e97ad76fc,0x3ff352a30434499c,1 +np.float64,0x3fd4a4f16da949e4,0x3ff3e0b07432c9aa,1 +np.float64,0x8000000000000000,0x3ff921fb54442d18,1 +np.float64,0x3fe688f5b56d11ec,0x3fe9435f63264375,1 +np.float64,0xbfdf5a427ebeb484,0x4000a97a6c5d4abc,1 +np.float64,0xbfd1f3483fa3e690,0x3ffdae6c8a299383,1 +np.float64,0xbfeac920db759242,0x4004805862be51ec,1 +np.float64,0x3fef5bc711feb78e,0x3fc9ac40fba5b93b,1 +np.float64,0x3fe4bd9e12e97b3c,0x3febb363c787d381,1 +np.float64,0x3fef6a59ab7ed4b4,0x3fc880f1324eafce,1 +np.float64,0x3fc07a362120f470,0x3ff7113cf2c672b3,1 +np.float64,0xbfe4d6dbe2e9adb8,0x40023d6f6bea44b7,1 +np.float64,0xbfec2d6a15785ad4,0x40052eb425cc37a2,1 +np.float64,0x3fc90dae05321b60,0x3ff5fb10015d2934,1 +np.float64,0xbfa9239f74324740,0x3ff9eb2d057068ea,1 +np.float64,0xbfeb4fc8baf69f92,0x4004bf5e17fb08a4,1 +np.float64,0x0,0x3ff921fb54442d18,1 +np.float64,0x3faaf1884c35e320,0x3ff84a5591dbe1f3,1 +np.float64,0xbfed842561fb084b,0x4005f5c0a19116ce,1 +np.float64,0xbfc64850c32c90a0,0x3ffbeeac2ee70f9a,1 +np.float64,0x3fd7d879f5afb0f4,0x3ff306254c453436,1 +np.float64,0xbfdabaa586b5754c,0x4000035e6ac83a2b,1 +np.float64,0xbfebfeefa977fddf,0x4005167446fb9faf,1 +np.float64,0xbfe9383462727069,0x4003d407aa6a1577,1 +np.float64,0x3fe108dfb6e211c0,0x3ff026ac924b281d,1 +np.float64,0xbf85096df02a12c0,0x3ff94c0e60a22ede,1 +np.float64,0xbfe3121cd566243a,0x4001ac8f90db5882,1 +np.float64,0xbfd227f62aa44fec,0x3ffdbc26bb175dcc,1 +np.float64,0x3fd931af2cb26360,0x3ff2a8b62dfe003c,1 +np.float64,0xbfd9b794e3b36f2a,0x3fffbfbc89ec013d,1 +np.float64,0x3fc89b2e6f313660,0x3ff609a6e67f15f2,1 +np.float64,0x3fc0b14a8f216298,0x3ff70a4b6905aad2,1 +np.float64,0xbfeda11a657b4235,0x400608b3f9fff574,1 +np.float64,0xbfed2ee9ec7a5dd4,0x4005c040b7c02390,1 +np.float64,0xbfef7819d8fef034,0x4007ac6bf75cf09d,1 +np.float64,0xbfcc4720fb388e40,0x3ffcb2666a00b336,1 +np.float64,0xbfe05dec4be0bbd8,0x4000dc8a25ca3760,1 +np.float64,0x3fb093416e212680,0x3ff81897b6d8b374,1 +np.float64,0xbfc6ab89332d5714,0x3ffbfb4559d143e7,1 +np.float64,0x3fc51948512a3290,0x3ff67bb9df662c0a,1 +np.float64,0x3fed4d94177a9b28,0x3fda76c92f0c0132,1 +np.float64,0x3fdd195fbeba32c0,0x3ff194a5586dd18e,1 +np.float64,0x3fe3f82799e7f050,0x3fecb354c2faf55c,1 +np.float64,0x3fecac2169f95842,0x3fdd7222296cb7a7,1 +np.float64,0x3fe3d3f36fe7a7e6,0x3fece18f45e30dd7,1 +np.float64,0x3fe31ff63d663fec,0x3fedc46c77d30c6a,1 +np.float64,0xbfe3120c83e62419,0x4001ac8a7c4aa742,1 +np.float64,0x3fe7c1a7976f8350,0x3fe77e4a9307c9f8,1 +np.float64,0x3fe226fe9de44dfe,0x3feef6c0f3cb00fa,1 +np.float64,0x3fd5c933baab9268,0x3ff3933e8a37de42,1 +np.float64,0x3feaa98496f5530a,0x3fe2c003832ebf21,1 +np.float64,0xbfc6f80a2f2df014,0x3ffc04fd54cb1317,1 +np.float64,0x3fde5e18d0bcbc30,0x3ff138f7b32a2ca3,1 +np.float64,0xbfe30c8dd566191c,0x4001aad4af935a78,1 +np.float64,0x3fbe8d196e3d1a30,0x3ff737fec8149ecc,1 +np.float64,0x3feaee6731f5dcce,0x3fe241fa42cce22d,1 +np.float64,0x3fef9cc46cff3988,0x3fc3f17b708dbdbb,1 +np.float64,0xbfdb181bdeb63038,0x4000103ecf405602,1 +np.float64,0xbfc58de0ed2b1bc0,0x3ffbd704c14e15cd,1 +np.float64,0xbfee05d5507c0bab,0x40064e480faba6d8,1 +np.float64,0x3fe27d0ffa64fa20,0x3fee8dc71ef79f2c,1 +np.float64,0xbfe4f7ad4c69ef5a,0x400248456cd09a07,1 +np.float64,0xbfe4843e91e9087d,0x4002225f3e139c84,1 +np.float64,0x3fe7158b9c6e2b18,0x3fe87ae845c5ba96,1 +np.float64,0xbfea64316074c863,0x400452fd2bc23a44,1 +np.float64,0xbfc9f3ae4133e75c,0x3ffc663d482afa42,1 +np.float64,0xbfd5e18513abc30a,0x3ffeb72fc76d7071,1 +np.float64,0xbfd52f6438aa5ec8,0x3ffe87e5b18041e5,1 +np.float64,0xbfea970650f52e0d,0x400469a4a6758154,1 +np.float64,0xbfe44321b7e88644,0x40020d404a2141b1,1 +np.float64,0x3fdf5a39bbbeb474,0x3ff0f10453059dbd,1 +np.float64,0xbfa1d4069423a810,0x3ff9b0a2eacd2ce2,1 +np.float64,0xbfc36d16a326da2c,0x3ffb92077d41d26a,1 +np.float64,0x1,0x3ff921fb54442d18,1 +np.float64,0x3feb232a79764654,0x3fe1df5beeb249d0,1 +np.float64,0xbfed2003d5fa4008,0x4005b737c2727583,1 +np.float64,0x3fd5b093a3ab6128,0x3ff399ca2db1d96d,1 +np.float64,0x3fca692c3d34d258,0x3ff5ceb86b79223e,1 +np.float64,0x3fd6bbdf89ad77c0,0x3ff3528916df652d,1 +np.float64,0xbfefdadd46ffb5bb,0x40085ee735e19f19,1 +np.float64,0x3feb69fb2676d3f6,0x3fe157ee0c15691e,1 +np.float64,0x3fe44c931f689926,0x3fec46b6f5e3f265,1 +np.float64,0xbfc43ddbcb287bb8,0x3ffbac71d268d74d,1 +np.float64,0x3fe6e16d43edc2da,0x3fe8c5cf0f0daa66,1 +np.float64,0x3fe489efc76913e0,0x3febf704ca1ac2a6,1 +np.float64,0xbfe590aadceb2156,0x40027b764205cf78,1 +np.float64,0xbf782e8aa0305d00,0x3ff93a29e81928ab,1 +np.float64,0x3fedcb80cffb9702,0x3fd7e5d1f98a418b,1 +np.float64,0x3fe075858060eb0c,0x3ff07d23ab46b60f,1 +np.float64,0x3fe62a68296c54d0,0x3fe9c77f7068043b,1 +np.float64,0x3feff16a3c7fe2d4,0x3fae8e8a739cc67a,1 +np.float64,0xbfd6ed93e3addb28,0x3ffefebab206fa99,1 +np.float64,0x3fe40d8ccf681b1a,0x3fec97e9cd29966d,1 +np.float64,0x3fd6408210ac8104,0x3ff3737a7d374107,1 +np.float64,0x3fec8023b8f90048,0x3fde35ebfb2b3afd,1 +np.float64,0xbfe13babd4627758,0x40011dae5c07c56b,1 +np.float64,0xbfd2183e61a4307c,0x3ffdb80dd747cfbe,1 +np.float64,0x3feae8eb1d75d1d6,0x3fe24c1f6e42ae77,1 +np.float64,0xbfea559b9c74ab37,0x40044c8e5e123b20,1 +np.float64,0xbfd12c9d57a2593a,0x3ffd7ac6222f561c,1 +np.float64,0x3fe32eb697e65d6e,0x3fedb202693875b6,1 +np.float64,0xbfde0808c3bc1012,0x4000794bd8616ea3,1 +np.float64,0x3fe14958a06292b2,0x3ff0007b40ac648a,1 +np.float64,0x3fe3d388a6e7a712,0x3fece21751a6dd7c,1 +np.float64,0x3fe7ad7897ef5af2,0x3fe79c5b3da302a7,1 +np.float64,0x3fec75527e78eaa4,0x3fde655de0cf0508,1 +np.float64,0x3fea920d4c75241a,0x3fe2ea48f031d908,1 +np.float64,0x7fefffffffffffff,0x7ff8000000000000,1 +np.float64,0xbfc17a68cb22f4d0,0x3ffb530925f41aa0,1 +np.float64,0xbfe1c93166e39263,0x400147f3cb435dec,1 +np.float64,0x3feb97c402f72f88,0x3fe0fe5b561bf869,1 +np.float64,0x3fb58ff5162b1ff0,0x3ff7c8933fa969dc,1 +np.float64,0x3fe68e2beded1c58,0x3fe93c075283703b,1 +np.float64,0xbf94564cc828aca0,0x3ff97355e5ee35db,1 +np.float64,0x3fd31061c9a620c4,0x3ff44b150ec96998,1 +np.float64,0xbfc7d0c89f2fa190,0x3ffc208bf4eddc4d,1 +np.float64,0x3fe5736f1d6ae6de,0x3feac18f84992d1e,1 +np.float64,0x3fdb62e480b6c5c8,0x3ff20ecfdc4afe7c,1 +np.float64,0xbfc417228b282e44,0x3ffba78afea35979,1 +np.float64,0x3f8f5ba1303eb780,0x3ff8e343714630ff,1 +np.float64,0x3fe8e99126f1d322,0x3fe5b6511d4c0798,1 +np.float64,0xbfe2ec08a1e5d812,0x4001a0bb28a85875,1 +np.float64,0x3fea3b46cf74768e,0x3fe383dceaa74296,1 +np.float64,0xbfe008b5ed60116c,0x4000c3d62c275d40,1 +np.float64,0xbfcd9f8a4b3b3f14,0x3ffcde98d6484202,1 +np.float64,0xbfdb5fb112b6bf62,0x40001a22137ef1c9,1 +np.float64,0xbfe9079565f20f2b,0x4003c0670c92e401,1 +np.float64,0xbfce250dc53c4a1c,0x3ffcefc2b3dc3332,1 +np.float64,0x3fe9ba85d373750c,0x3fe4607131b28773,1 +np.float64,0x10000000000000,0x3ff921fb54442d18,1 +np.float64,0xbfeb9ef42c773de8,0x4004e5f239203ad8,1 +np.float64,0xbfd6bf457dad7e8a,0x3ffef2563d87b18d,1 +np.float64,0x3fe4de9aa5e9bd36,0x3feb87f97defb04a,1 +np.float64,0x3fedb4f67cfb69ec,0x3fd8603c465bffac,1 +np.float64,0x3fe7b6d9506f6db2,0x3fe78e670c7bdb67,1 +np.float64,0x3fe071717460e2e2,0x3ff07f84472d9cc5,1 +np.float64,0xbfed2e79dbfa5cf4,0x4005bffc6f9ad24f,1 +np.float64,0x3febb8adc377715c,0x3fe0bcebfbd45900,1 +np.float64,0xbfee2cffd87c5a00,0x40066b20a037c478,1 +np.float64,0x3fef7e358d7efc6c,0x3fc6d0ba71a542a8,1 +np.float64,0xbfef027eef7e04fe,0x400723291cb00a7a,1 +np.float64,0x3fac96da34392dc0,0x3ff83d260a936c6a,1 +np.float64,0x3fe9dba94a73b752,0x3fe428736b94885e,1 +np.float64,0x3fed37581efa6eb0,0x3fdae49dcadf1d90,1 +np.float64,0xbfe6e61037edcc20,0x4002f23031b8d522,1 +np.float64,0xbfdea7204dbd4e40,0x40008fe1f37918b7,1 +np.float64,0x3feb9f8edb773f1e,0x3fe0eef20bd4387b,1 +np.float64,0x3feeb0b6ed7d616e,0x3fd25fb3b7a525d6,1 +np.float64,0xbfd7ce9061af9d20,0x3fff3b25d531aa2b,1 +np.float64,0xbfc806b509300d6c,0x3ffc2768743a8360,1 +np.float64,0xbfa283882c250710,0x3ff9b61fda28914a,1 +np.float64,0x3fdec70050bd8e00,0x3ff11b1d769b578f,1 +np.float64,0xbfc858a44930b148,0x3ffc31d6758b4721,1 +np.float64,0x3fdc321150b86424,0x3ff1d5504c3c91e4,1 +np.float64,0x3fd9416870b282d0,0x3ff2a46f3a850f5b,1 +np.float64,0x3fdd756968baead4,0x3ff17ac510a5573f,1 +np.float64,0xbfedfd632cfbfac6,0x400648345a2f89b0,1 +np.float64,0x3fd6874285ad0e84,0x3ff36098ebff763f,1 +np.float64,0x3fe6daacc9edb55a,0x3fe8cf75fae1e35f,1 +np.float64,0x3fe53f19766a7e32,0x3feb07d0e97cd55b,1 +np.float64,0x3fd13cc36ca27988,0x3ff4c4ff801b1faa,1 +np.float64,0x3fe4f21cbce9e43a,0x3feb6e34a72ef529,1 +np.float64,0xbfc21c1cc9243838,0x3ffb67726394ca89,1 +np.float64,0x3fe947a3f2728f48,0x3fe51eae4660e23c,1 +np.float64,0xbfce78cd653cf19c,0x3ffcfa89194b3f5e,1 +np.float64,0x3fe756f049eeade0,0x3fe81be7f2d399e2,1 +np.float64,0xbfcc727cf138e4f8,0x3ffcb7f547841bb0,1 +np.float64,0xbfc2d8d58f25b1ac,0x3ffb7f496cc72458,1 +np.float64,0xbfcfd0e4653fa1c8,0x3ffd26e1309bc80b,1 +np.float64,0xbfe2126c106424d8,0x40015e0e01db6a4a,1 +np.float64,0x3fe580e4306b01c8,0x3feaaf683ce51aa5,1 +np.float64,0x3fcea8a1b93d5140,0x3ff543456c0d28c7,1 +np.float64,0xfff0000000000000,0x7ff8000000000000,1 +np.float64,0xbfd9d5da72b3abb4,0x3fffc8013113f968,1 +np.float64,0xbfe1fdfcea63fbfa,0x400157def2e4808d,1 +np.float64,0xbfc0022e0720045c,0x3ffb239963e7cbf2,1 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-arccosh.csv b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-arccosh.csv new file mode 100644 index 0000000000000000000000000000000000000000..1b3eda484d73df76d5084592c2b38c7ef148de40 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-arccosh.csv @@ -0,0 +1,1429 @@ +dtype,input,output,ulperrortol +np.float32,0x3f83203f,0x3e61d9d6,2 +np.float32,0x3f98dea1,0x3f1d1af6,2 +np.float32,0x7fa00000,0x7fe00000,2 +np.float32,0x7eba99af,0x42b0d032,2 +np.float32,0x3fc95a13,0x3f833650,2 +np.float32,0x3fce9a45,0x3f8771e1,2 +np.float32,0x3fc1bd96,0x3f797811,2 +np.float32,0x7eba2391,0x42b0ceed,2 +np.float32,0x7d4e8f15,0x42acdb8c,2 +np.float32,0x3feca42e,0x3f9cc88e,2 +np.float32,0x7e2b314e,0x42af412e,2 +np.float32,0x7f7fffff,0x42b2d4fc,2 +np.float32,0x3f803687,0x3d6c4380,2 +np.float32,0x3fa0edbd,0x3f33e706,2 +np.float32,0x3faa8074,0x3f4b3d3c,2 +np.float32,0x3fa0c49e,0x3f337af3,2 +np.float32,0x3f8c9ec4,0x3ee18812,2 +np.float32,0x7efef78e,0x42b17006,2 +np.float32,0x3fc75720,0x3f818aa4,2 +np.float32,0x7f52d4c8,0x42b27198,2 +np.float32,0x3f88f21e,0x3ebe52b0,2 +np.float32,0x3ff7a042,0x3fa3a07a,2 +np.float32,0x7f52115c,0x42b26fbd,2 +np.float32,0x3fc6bf6f,0x3f810b42,2 +np.float32,0x3fd105d0,0x3f895649,2 +np.float32,0x3fee7c2a,0x3f9df66e,2 +np.float32,0x7f0ff9a5,0x42b1ae4f,2 +np.float32,0x7e81f075,0x42b016e7,2 +np.float32,0x3fa57d65,0x3f3f70c6,2 +np.float32,0x80800000,0xffc00000,2 +np.float32,0x7da239f5,0x42adc2bf,2 +np.float32,0x3f9e432c,0x3f2cbd80,2 +np.float32,0x3ff2839b,0x3fa07ee4,2 +np.float32,0x3fec8aef,0x3f9cb850,2 +np.float32,0x7d325893,0x42ac905b,2 +np.float32,0x3fa27431,0x3f37dade,2 +np.float32,0x3fce7408,0x3f8753ae,2 +np.float32,0x3fde6684,0x3f93353f,2 +np.float32,0x3feb9a3e,0x3f9c1cff,2 +np.float32,0x7deb34bb,0x42ae80f0,2 +np.float32,0x3fed9300,0x3f9d61b7,2 +np.float32,0x7f35e253,0x42b225fb,2 +np.float32,0x7e6db57f,0x42afe93f,2 +np.float32,0x3fa41f08,0x3f3c10bc,2 +np.float32,0x3fb0d4da,0x3f590de3,2 +np.float32,0x3fb5c690,0x3f632351,2 +np.float32,0x3fcde9ce,0x3f86e638,2 +np.float32,0x3f809c7b,0x3dc81161,2 +np.float32,0x3fd77291,0x3f8e3226,2 +np.float32,0x3fc21a06,0x3f7a1a82,2 +np.float32,0x3fba177e,0x3f6b8139,2 +np.float32,0x7f370dff,0x42b22944,2 +np.float32,0x3fe5bfcc,0x3f9841c1,2 +np.float32,0x3feb0caa,0x3f9bc139,2 +np.float32,0x7f4fe5c3,0x42b26a6c,2 +np.float32,0x7f1e1419,0x42b1de28,2 +np.float32,0x7f5e3c96,0x42b28c92,2 +np.float32,0x3f8cd313,0x3ee3521e,2 +np.float32,0x3fa97824,0x3f48e049,2 +np.float32,0x7d8ca281,0x42ad799e,2 +np.float32,0x3f96b51b,0x3f165193,2 +np.float32,0x3f81328a,0x3e0bf504,2 +np.float32,0x3ff60bf3,0x3fa2ab45,2 +np.float32,0x3ff9b629,0x3fa4e107,2 +np.float32,0x3fecacfc,0x3f9cce37,2 +np.float32,0x3fba8804,0x3f6c5600,2 +np.float32,0x3f81f752,0x3e333fdd,2 +np.float32,0x3fb5b262,0x3f62fb46,2 +np.float32,0x3fa21bc0,0x3f36f7e6,2 +np.float32,0x3fbc87bb,0x3f7011dc,2 +np.float32,0x3fe18b32,0x3f9565ae,2 +np.float32,0x7dfb6dd5,0x42aea316,2 +np.float32,0x3fb7c602,0x3f670ee3,2 +np.float32,0x7efeb6a2,0x42b16f84,2 +np.float32,0x3fa56180,0x3f3f2ca4,2 +np.float32,0x3f8dcaff,0x3eeb9ac0,2 +np.float32,0x7e876238,0x42b02beb,2 +np.float32,0x7f0bb67d,0x42b19eec,2 +np.float32,0x3faca01c,0x3f4fffa5,2 +np.float32,0x3fdb57ee,0x3f9108b8,2 +np.float32,0x3fe3bade,0x3f96e4b7,2 +np.float32,0x7f7aa2dd,0x42b2ca25,2 +np.float32,0x3fed92ec,0x3f9d61aa,2 +np.float32,0x7eb789b1,0x42b0c7b9,2 +np.float32,0x7f7f16e4,0x42b2d329,2 +np.float32,0x3fb6647e,0x3f645b84,2 +np.float32,0x3f99335e,0x3f1e1d96,2 +np.float32,0x7e690a11,0x42afdf17,2 +np.float32,0x7dff2f95,0x42aeaaae,2 +np.float32,0x7f70adfd,0x42b2b564,2 +np.float32,0x3fe92252,0x3f9a80fe,2 +np.float32,0x3fef54ce,0x3f9e7fe5,2 +np.float32,0x3ff24eaa,0x3fa05df9,2 +np.float32,0x7f04565a,0x42b18328,2 +np.float32,0x3fcb8b80,0x3f85007f,2 +np.float32,0x3fcd4d0a,0x3f866983,2 +np.float32,0x3fbe7d82,0x3f73a911,2 +np.float32,0x3f8a7a8a,0x3ecdc8f6,2 +np.float32,0x3f912441,0x3f030d56,2 +np.float32,0x3f9b29d6,0x3f23f663,2 +np.float32,0x3fab7f36,0x3f4d7c6c,2 +np.float32,0x7dfedafc,0x42aeaa04,2 +np.float32,0x3fe190c0,0x3f956982,2 +np.float32,0x3f927515,0x3f07e0bb,2 +np.float32,0x3ff6442a,0x3fa2cd7e,2 +np.float32,0x7f6656d0,0x42b29ee8,2 +np.float32,0x3fe29aa0,0x3f96201f,2 +np.float32,0x3fa4a247,0x3f3d5687,2 +np.float32,0x3fa1cf19,0x3f363226,2 +np.float32,0x3fc20037,0x3f79ed36,2 +np.float32,0x7cc1241a,0x42ab5645,2 +np.float32,0x3fafd540,0x3f56f25a,2 +np.float32,0x7e5b3f5f,0x42afbfdb,2 +np.float32,0x7f48de5f,0x42b258d0,2 +np.float32,0x3fce1ca0,0x3f870e85,2 +np.float32,0x7ee40bb2,0x42b136e4,2 +np.float32,0x7ecdb133,0x42b10212,2 +np.float32,0x3f9f181c,0x3f2f02ca,2 +np.float32,0x3f936cbf,0x3f0b4f63,2 +np.float32,0x3fa4f8ea,0x3f3e2c2f,2 +np.float32,0x3fcc03e2,0x3f8561ac,2 +np.float32,0x3fb801f2,0x3f67831b,2 +np.float32,0x7e141dad,0x42aef70c,2 +np.float32,0x3fe8c04e,0x3f9a4087,2 +np.float32,0x3f8548d5,0x3e929f37,2 +np.float32,0x7f148d7d,0x42b1be56,2 +np.float32,0x3fd2c9a2,0x3f8ab1ed,2 +np.float32,0x7eb374fd,0x42b0bc36,2 +np.float32,0x7f296d36,0x42b201a7,2 +np.float32,0x3ff138e2,0x3f9fb09d,2 +np.float32,0x3ff42898,0x3fa18347,2 +np.float32,0x7da8c5e1,0x42add700,2 +np.float32,0x7dcf72c4,0x42ae40a4,2 +np.float32,0x7ea571fc,0x42b09296,2 +np.float32,0x3fc0953d,0x3f776ba3,2 +np.float32,0x7f1773dd,0x42b1c83c,2 +np.float32,0x7ef53b68,0x42b15c17,2 +np.float32,0x3f85d69f,0x3e9a0f3a,2 +np.float32,0x7e8b9a05,0x42b03ba0,2 +np.float32,0x3ff07d20,0x3f9f3ad2,2 +np.float32,0x7e8da32c,0x42b0430a,2 +np.float32,0x7ef96004,0x42b164ab,2 +np.float32,0x3fdfaa62,0x3f941837,2 +np.float32,0x7f0057c5,0x42b17377,2 +np.float32,0x3fb2663f,0x3f5c5065,2 +np.float32,0x3fd3d8c3,0x3f8b8055,2 +np.float32,0x1,0xffc00000,2 +np.float32,0x3fd536c1,0x3f8c8862,2 +np.float32,0x3f91b953,0x3f053619,2 +np.float32,0x3fb3305c,0x3f5deee1,2 +np.float32,0x7ecd86b9,0x42b101a8,2 +np.float32,0x3fbf71c5,0x3f75624d,2 +np.float32,0x3ff5f0f4,0x3fa29ad2,2 +np.float32,0x3fe50389,0x3f97c328,2 +np.float32,0x3fa325a1,0x3f399e69,2 +np.float32,0x3fe4397a,0x3f973a9f,2 +np.float32,0x3f8684c6,0x3ea2b784,2 +np.float32,0x7f25ae00,0x42b1f634,2 +np.float32,0x3ff7cbf7,0x3fa3badb,2 +np.float32,0x7f73f0e0,0x42b2bc48,2 +np.float32,0x3fc88b70,0x3f828b92,2 +np.float32,0x3fb01c16,0x3f578886,2 +np.float32,0x7e557623,0x42afb229,2 +np.float32,0x3fcbcd5b,0x3f8535b4,2 +np.float32,0x7f7157e4,0x42b2b6cd,2 +np.float32,0x7f51d9d4,0x42b26f36,2 +np.float32,0x7f331a3b,0x42b21e17,2 +np.float32,0x7f777fb5,0x42b2c3b2,2 +np.float32,0x3f832001,0x3e61d11f,2 +np.float32,0x7f2cd055,0x42b20bca,2 +np.float32,0x3f89831f,0x3ec42f76,2 +np.float32,0x7f21da33,0x42b1ea3d,2 +np.float32,0x3f99e416,0x3f20330a,2 +np.float32,0x7f2c8ea1,0x42b20b07,2 +np.float32,0x7f462c98,0x42b251e6,2 +np.float32,0x7f4fdb3f,0x42b26a52,2 +np.float32,0x3fcc1338,0x3f856e07,2 +np.float32,0x3f823673,0x3e3e20da,2 +np.float32,0x7dbfe89d,0x42ae18c6,2 +np.float32,0x3fc9b04c,0x3f837d38,2 +np.float32,0x7dba3213,0x42ae094d,2 +np.float32,0x7ec5a483,0x42b0eda1,2 +np.float32,0x3fbc4d14,0x3f6fa543,2 +np.float32,0x3fc85ce2,0x3f8264f1,2 +np.float32,0x7f77c816,0x42b2c447,2 +np.float32,0x3f9c9281,0x3f280492,2 +np.float32,0x7f49b3e2,0x42b25aef,2 +np.float32,0x3fa7e4da,0x3f45347c,2 +np.float32,0x7e0c9df5,0x42aedc72,2 +np.float32,0x7f21fd1a,0x42b1eaab,2 +np.float32,0x7f7c63ad,0x42b2cdb6,2 +np.float32,0x7f4eb80a,0x42b26783,2 +np.float32,0x7e98038c,0x42b0673c,2 +np.float32,0x7e89ba08,0x42b034b4,2 +np.float32,0x3ffc06ba,0x3fa64094,2 +np.float32,0x3fae63f6,0x3f53db36,2 +np.float32,0x3fbc2d30,0x3f6f6a1c,2 +np.float32,0x7de0e5e5,0x42ae69fe,2 +np.float32,0x7e09ed18,0x42aed28d,2 +np.float32,0x3fea78f8,0x3f9b6129,2 +np.float32,0x7dfe0bcc,0x42aea863,2 +np.float32,0x7ee21d03,0x42b13289,2 +np.float32,0x3fcc3aed,0x3f858dfc,2 +np.float32,0x3fe6b3ba,0x3f98e4ea,2 +np.float32,0x3f90f25f,0x3f025225,2 +np.float32,0x7f1bcaf4,0x42b1d6b3,2 +np.float32,0x3f83ac81,0x3e74c20e,2 +np.float32,0x3f98681d,0x3f1bae16,2 +np.float32,0x3fe1f2d9,0x3f95ad08,2 +np.float32,0x3fa279d7,0x3f37e951,2 +np.float32,0x3feb922a,0x3f9c17c4,2 +np.float32,0x7f1c72e8,0x42b1d8da,2 +np.float32,0x3fea156b,0x3f9b2038,2 +np.float32,0x3fed6bda,0x3f9d48aa,2 +np.float32,0x3fa86142,0x3f46589c,2 +np.float32,0x3ff16bc2,0x3f9fd072,2 +np.float32,0x3fbebf65,0x3f74207b,2 +np.float32,0x7e7b78b5,0x42b00610,2 +np.float32,0x3ff51ab8,0x3fa217f0,2 +np.float32,0x3f8361bb,0x3e6adf07,2 +np.float32,0x7edbceed,0x42b1240e,2 +np.float32,0x7f10e2c0,0x42b1b18a,2 +np.float32,0x3fa7bc58,0x3f44d4ef,2 +np.float32,0x3f813bde,0x3e0e1138,2 +np.float32,0x7f30d5b9,0x42b21791,2 +np.float32,0x3fb4f450,0x3f61806a,2 +np.float32,0x7eee02c4,0x42b14cca,2 +np.float32,0x7ec74b62,0x42b0f1e4,2 +np.float32,0x3ff96bca,0x3fa4b498,2 +np.float32,0x7f50e304,0x42b26cda,2 +np.float32,0x7eb14c57,0x42b0b603,2 +np.float32,0x7c3f0733,0x42a9edbf,2 +np.float32,0x7ea57acb,0x42b092b1,2 +np.float32,0x7f2788dc,0x42b1fbe7,2 +np.float32,0x3fa39f14,0x3f3ad09b,2 +np.float32,0x3fc3a7e0,0x3f7ccfa0,2 +np.float32,0x3fe70a73,0x3f991eb0,2 +np.float32,0x7f4831f7,0x42b25718,2 +np.float32,0x3fe947d0,0x3f9a999c,2 +np.float32,0x7ef2b1c7,0x42b156c4,2 +np.float32,0x3fede0ea,0x3f9d937f,2 +np.float32,0x3f9fef8e,0x3f314637,2 +np.float32,0x3fc313c5,0x3f7bcebd,2 +np.float32,0x7ee99337,0x42b14328,2 +np.float32,0x7eb9042e,0x42b0cbd5,2 +np.float32,0x3fc9d3dc,0x3f839a69,2 +np.float32,0x3fb2c018,0x3f5d091d,2 +np.float32,0x3fcc4e8f,0x3f859dc5,2 +np.float32,0x3fa9363b,0x3f484819,2 +np.float32,0x7f72ce2e,0x42b2b9e4,2 +np.float32,0x7e639326,0x42afd2f1,2 +np.float32,0x7f4595d3,0x42b25060,2 +np.float32,0x7f6d0ac4,0x42b2ad97,2 +np.float32,0x7f1bda0d,0x42b1d6e5,2 +np.float32,0x3fd85ffd,0x3f8ee0ed,2 +np.float32,0x3f91d53f,0x3f059c8e,2 +np.float32,0x7d06e103,0x42ac0155,2 +np.float32,0x3fb83126,0x3f67de6e,2 +np.float32,0x7d81ce1f,0x42ad5097,2 +np.float32,0x7f79cb3b,0x42b2c86b,2 +np.float32,0x7f800000,0x7f800000,2 +np.float32,0x3fdbfffd,0x3f918137,2 +np.float32,0x7f4ecb1c,0x42b267b2,2 +np.float32,0x3fc2c122,0x3f7b3ed3,2 +np.float32,0x7f415854,0x42b24544,2 +np.float32,0x7e3d988b,0x42af7575,2 +np.float32,0x3f83ca99,0x3e789fcb,2 +np.float32,0x7f274f70,0x42b1fb38,2 +np.float32,0x7f0d20e6,0x42b1a416,2 +np.float32,0x3fdf3a1d,0x3f93c9c1,2 +np.float32,0x7efaa13e,0x42b1673d,2 +np.float32,0x3fb20b15,0x3f5b9434,2 +np.float32,0x3f86af9f,0x3ea4c664,2 +np.float32,0x3fe4fcb0,0x3f97be8a,2 +np.float32,0x3f920683,0x3f065085,2 +np.float32,0x3fa4b278,0x3f3d7e8b,2 +np.float32,0x3f8077a8,0x3daef77f,2 +np.float32,0x7e865be4,0x42b02807,2 +np.float32,0x3fcea7e2,0x3f877c9f,2 +np.float32,0x7e7e9db1,0x42b00c6d,2 +np.float32,0x3f9819aa,0x3f1aba7e,2 +np.float32,0x7f2b6c4b,0x42b207a7,2 +np.float32,0x7ef85e3e,0x42b16299,2 +np.float32,0x3fbd8290,0x3f71df8b,2 +np.float32,0x3fbbb615,0x3f6e8c8c,2 +np.float32,0x7f1bc7f5,0x42b1d6a9,2 +np.float32,0x3fbb4fea,0x3f6dcdad,2 +np.float32,0x3fb67e09,0x3f648dd1,2 +np.float32,0x3fc83495,0x3f824374,2 +np.float32,0x3fe52980,0x3f97dcbc,2 +np.float32,0x3f87d893,0x3eb25d7c,2 +np.float32,0x3fdb805a,0x3f9125c0,2 +np.float32,0x3fb33f0f,0x3f5e0ce1,2 +np.float32,0x3facc524,0x3f50516b,2 +np.float32,0x3ff40484,0x3fa16d0e,2 +np.float32,0x3ff078bf,0x3f9f3811,2 +np.float32,0x7f736747,0x42b2bb27,2 +np.float32,0x7f55768b,0x42b277f3,2 +np.float32,0x80000001,0xffc00000,2 +np.float32,0x7f6463d1,0x42b29a8e,2 +np.float32,0x3f8f8b59,0x3ef9d792,2 +np.float32,0x3f8a6f4d,0x3ecd5bf4,2 +np.float32,0x3fe958d9,0x3f9aa4ca,2 +np.float32,0x7f1e2ce2,0x42b1de78,2 +np.float32,0x3fb8584a,0x3f682a05,2 +np.float32,0x7dea3dc6,0x42ae7ed5,2 +np.float32,0x7f53a815,0x42b27399,2 +np.float32,0x7e0cf986,0x42aeddbf,2 +np.float32,0x7f3afb71,0x42b23422,2 +np.float32,0x3fd87d6e,0x3f8ef685,2 +np.float32,0x3ffcaa46,0x3fa6a0d7,2 +np.float32,0x7eecd276,0x42b14a3a,2 +np.float32,0x3ffc30b4,0x3fa65951,2 +np.float32,0x7e9c85e2,0x42b07634,2 +np.float32,0x3f95d862,0x3f1383de,2 +np.float32,0x7ef21410,0x42b15577,2 +np.float32,0x3fbfa1b5,0x3f75b86e,2 +np.float32,0x3fd6d90f,0x3f8dc086,2 +np.float32,0x0,0xffc00000,2 +np.float32,0x7e885dcd,0x42b02f9f,2 +np.float32,0x3fb3e057,0x3f5f54bf,2 +np.float32,0x7f40afdd,0x42b24385,2 +np.float32,0x3fb795c2,0x3f66b120,2 +np.float32,0x3fba7c11,0x3f6c3f73,2 +np.float32,0x3ffef620,0x3fa7f828,2 +np.float32,0x7d430508,0x42acbe1e,2 +np.float32,0x3f8d2892,0x3ee6369f,2 +np.float32,0x3fbea139,0x3f73e9d5,2 +np.float32,0x3ffaa928,0x3fa571b9,2 +np.float32,0x7fc00000,0x7fc00000,2 +np.float32,0x7f16f9ce,0x42b1c69f,2 +np.float32,0x3fa8f753,0x3f47b657,2 +np.float32,0x3fd48a63,0x3f8c06ac,2 +np.float32,0x7f13419e,0x42b1b9d9,2 +np.float32,0x3fdf1526,0x3f93afde,2 +np.float32,0x3f903c8b,0x3eff3be8,2 +np.float32,0x7f085323,0x42b1925b,2 +np.float32,0x7cdbe309,0x42ab98ac,2 +np.float32,0x3fba2cfd,0x3f6ba9f1,2 +np.float32,0x7f5a805d,0x42b283e4,2 +np.float32,0x7f6753dd,0x42b2a119,2 +np.float32,0x3fed9f02,0x3f9d6964,2 +np.float32,0x3f96422c,0x3f14ddba,2 +np.float32,0x7f22f2a9,0x42b1edb1,2 +np.float32,0x3fe3fcfd,0x3f97119d,2 +np.float32,0x7e018ad0,0x42aeb271,2 +np.float32,0x7db896f5,0x42ae04de,2 +np.float32,0x7e55c795,0x42afb2ec,2 +np.float32,0x7f58ef8d,0x42b28036,2 +np.float32,0x7f24a16a,0x42b1f2f3,2 +np.float32,0x3fcf714c,0x3f881b09,2 +np.float32,0x3fcdd056,0x3f86d200,2 +np.float32,0x7f02fad0,0x42b17de0,2 +np.float32,0x7eeab877,0x42b145a9,2 +np.float32,0x3fd6029d,0x3f8d20f7,2 +np.float32,0x3fd4f8cd,0x3f8c59d6,2 +np.float32,0x3fb29d4a,0x3f5cc1a5,2 +np.float32,0x3fb11e2d,0x3f59a77a,2 +np.float32,0x7eded576,0x42b12b0e,2 +np.float32,0x7f26c2a5,0x42b1f988,2 +np.float32,0x3fb6165b,0x3f63c151,2 +np.float32,0x7f3bca47,0x42b23657,2 +np.float32,0x7d8c93bf,0x42ad7968,2 +np.float32,0x3f8ede02,0x3ef47176,2 +np.float32,0x3fbef762,0x3f7485b9,2 +np.float32,0x7f1419af,0x42b1bcc6,2 +np.float32,0x7d9e8c79,0x42adb701,2 +np.float32,0x3fa26336,0x3f37af63,2 +np.float32,0x7f5f5590,0x42b28f18,2 +np.float32,0x3fddc93a,0x3f92c651,2 +np.float32,0x3ff0a5fc,0x3f9f547f,2 +np.float32,0x3fb2f6b8,0x3f5d790e,2 +np.float32,0x3ffe59a4,0x3fa79d2c,2 +np.float32,0x7e4df848,0x42af9fde,2 +np.float32,0x3fb0ab3b,0x3f58b678,2 +np.float32,0x7ea54d47,0x42b09225,2 +np.float32,0x3fdd6404,0x3f927eb2,2 +np.float32,0x3f846dc0,0x3e864caa,2 +np.float32,0x7d046aee,0x42abf7e7,2 +np.float32,0x7f7c5a05,0x42b2cda3,2 +np.float32,0x3faf6126,0x3f55fb21,2 +np.float32,0x7f36a910,0x42b22829,2 +np.float32,0x3fdc7b36,0x3f91d938,2 +np.float32,0x3fff443e,0x3fa82577,2 +np.float32,0x7ee7154a,0x42b13daa,2 +np.float32,0x3f944742,0x3f0e435c,2 +np.float32,0x7f5b510a,0x42b285cc,2 +np.float32,0x3f9bc940,0x3f25c4d2,2 +np.float32,0x3fee4782,0x3f9dd4ea,2 +np.float32,0x3fcfc2dd,0x3f885aea,2 +np.float32,0x7eab65cf,0x42b0a4af,2 +np.float32,0x3f9cf908,0x3f292689,2 +np.float32,0x7ed35501,0x42b10feb,2 +np.float32,0x7dabb70a,0x42addfd9,2 +np.float32,0x7f348919,0x42b2222b,2 +np.float32,0x3fb137d4,0x3f59dd17,2 +np.float32,0x7e7b36c9,0x42b0058a,2 +np.float32,0x7e351fa4,0x42af5e0d,2 +np.float32,0x3f973c0c,0x3f18011e,2 +np.float32,0xff800000,0xffc00000,2 +np.float32,0x3f9b0a4b,0x3f239a33,2 +np.float32,0x3f87c4cf,0x3eb17e7e,2 +np.float32,0x7ef67760,0x42b15eaa,2 +np.float32,0x3fc4d2c8,0x3f7ed20f,2 +np.float32,0x7e940dac,0x42b059b8,2 +np.float32,0x7f6e6a52,0x42b2b08d,2 +np.float32,0x3f838752,0x3e6fe4b2,2 +np.float32,0x3fd8f046,0x3f8f4a94,2 +np.float32,0x3fa82112,0x3f45c223,2 +np.float32,0x3fd49b16,0x3f8c1345,2 +np.float32,0x7f02a941,0x42b17ca1,2 +np.float32,0x3f8a9d2c,0x3ecf1768,2 +np.float32,0x7c9372e3,0x42aacc0f,2 +np.float32,0x3fd260b3,0x3f8a619a,2 +np.float32,0x3f8a1b88,0x3eca27cb,2 +np.float32,0x7d25d510,0x42ac6b1c,2 +np.float32,0x7ef5a578,0x42b15cf5,2 +np.float32,0x3fe6625d,0x3f98ae9a,2 +np.float32,0x3ff53240,0x3fa22658,2 +np.float32,0x3f8bb2e6,0x3ed944cf,2 +np.float32,0x7f4679b1,0x42b252ad,2 +np.float32,0x3fa8db30,0x3f4774fc,2 +np.float32,0x7ee5fafd,0x42b13b37,2 +np.float32,0x3fc405e0,0x3f7d71fb,2 +np.float32,0x3f9303cd,0x3f09ddfd,2 +np.float32,0x7f486e67,0x42b257b2,2 +np.float32,0x7e73f12b,0x42aff680,2 +np.float32,0x3fe80f8b,0x3f99cbe4,2 +np.float32,0x3f84200a,0x3e81a3f3,2 +np.float32,0x3fa14e5c,0x3f34e3ce,2 +np.float32,0x3fda22ec,0x3f9029bb,2 +np.float32,0x3f801772,0x3d1aef98,2 +np.float32,0x7eaa1428,0x42b0a0bb,2 +np.float32,0x3feae0b3,0x3f9ba4aa,2 +np.float32,0x7ea439b4,0x42b08ecc,2 +np.float32,0x3fa28b1c,0x3f381579,2 +np.float32,0x7e8af247,0x42b03937,2 +np.float32,0x3fd19216,0x3f89c2b7,2 +np.float32,0x7f6ea033,0x42b2b100,2 +np.float32,0x3fad4fbf,0x3f518224,2 +np.float32,0x3febd940,0x3f9c45bd,2 +np.float32,0x7f4643a3,0x42b25221,2 +np.float32,0x7ec34478,0x42b0e771,2 +np.float32,0x7f18c83b,0x42b1ccb5,2 +np.float32,0x3fc665ad,0x3f80bf94,2 +np.float32,0x3ff0a999,0x3f9f56c4,2 +np.float32,0x3faf1cd2,0x3f5568fe,2 +np.float32,0x7ecd9dc6,0x42b101e1,2 +np.float32,0x3faad282,0x3f4bf754,2 +np.float32,0x3ff905a0,0x3fa47771,2 +np.float32,0x7f596481,0x42b28149,2 +np.float32,0x7f1cb31f,0x42b1d9ac,2 +np.float32,0x7e266719,0x42af32a6,2 +np.float32,0x7eccce06,0x42b0ffdb,2 +np.float32,0x3f9b6f71,0x3f24c102,2 +np.float32,0x3f80e4ba,0x3df1d6bc,2 +np.float32,0x3f843d51,0x3e836a60,2 +np.float32,0x7f70bd88,0x42b2b585,2 +np.float32,0x3fe4cc96,0x3f979e18,2 +np.float32,0x3ff737c7,0x3fa36151,2 +np.float32,0x3ff1197e,0x3f9f9cf4,2 +np.float32,0x7f08e190,0x42b19471,2 +np.float32,0x3ff1542e,0x3f9fc1b2,2 +np.float32,0x3ff6673c,0x3fa2e2d2,2 +np.float32,0xbf800000,0xffc00000,2 +np.float32,0x7e3f9ba7,0x42af7add,2 +np.float32,0x7f658ff6,0x42b29d2d,2 +np.float32,0x3f93441c,0x3f0ac0d9,2 +np.float32,0x7f526a74,0x42b27096,2 +np.float32,0x7f5b00c8,0x42b28511,2 +np.float32,0x3ff212f8,0x3fa038cf,2 +np.float32,0x7e0bd60d,0x42aed998,2 +np.float32,0x7f71ef7f,0x42b2b80e,2 +np.float32,0x7f7a897e,0x42b2c9f1,2 +np.float32,0x7e8b76a6,0x42b03b1e,2 +np.float32,0x7efa0da3,0x42b1660f,2 +np.float32,0x3fce9166,0x3f876ae0,2 +np.float32,0x3fc4163d,0x3f7d8e30,2 +np.float32,0x3fdb3784,0x3f90f16b,2 +np.float32,0x7c5f177b,0x42aa3d30,2 +np.float32,0x3fc6276d,0x3f808af5,2 +np.float32,0x7bac9cc2,0x42a856f4,2 +np.float32,0x3fe5876f,0x3f981bea,2 +np.float32,0x3fef60e3,0x3f9e878a,2 +np.float32,0x3fb23cd8,0x3f5bfb06,2 +np.float32,0x3fe114e2,0x3f951402,2 +np.float32,0x7ca8ef04,0x42ab11b4,2 +np.float32,0x7d93c2ad,0x42ad92ec,2 +np.float32,0x3fe5bb8a,0x3f983ee6,2 +np.float32,0x7f0182fd,0x42b1781b,2 +np.float32,0x7da63bb2,0x42adcf3d,2 +np.float32,0x3fac46b7,0x3f4f399e,2 +np.float32,0x7f7a5d8f,0x42b2c997,2 +np.float32,0x7f76572e,0x42b2c14b,2 +np.float32,0x7f42d53e,0x42b24931,2 +np.float32,0x7f7ffd00,0x42b2d4f6,2 +np.float32,0x3fc346c3,0x3f7c2756,2 +np.float32,0x7f1f6ae3,0x42b1e27a,2 +np.float32,0x3f87fb56,0x3eb3e2ee,2 +np.float32,0x3fed17a2,0x3f9d12b4,2 +np.float32,0x7f5ea903,0x42b28d8c,2 +np.float32,0x3f967f82,0x3f15a4ab,2 +np.float32,0x7d3b540c,0x42aca984,2 +np.float32,0x7f56711a,0x42b27a4a,2 +np.float32,0x7f122223,0x42b1b5ee,2 +np.float32,0x3fd6fa34,0x3f8dd919,2 +np.float32,0x3fadd62e,0x3f52a7b3,2 +np.float32,0x3fb7bf0c,0x3f67015f,2 +np.float32,0x7edf4ba7,0x42b12c1d,2 +np.float32,0x7e33cc65,0x42af5a4b,2 +np.float32,0x3fa6be17,0x3f427831,2 +np.float32,0x3fa07aa8,0x3f32b7d4,2 +np.float32,0x3fa4a3af,0x3f3d5a01,2 +np.float32,0x3fdbb267,0x3f9149a8,2 +np.float32,0x7ed45e25,0x42b1126c,2 +np.float32,0x3fe3f432,0x3f970ba6,2 +np.float32,0x7f752080,0x42b2bec3,2 +np.float32,0x3f872747,0x3eaa62ea,2 +np.float32,0x7e52175d,0x42afaa03,2 +np.float32,0x3fdc766c,0x3f91d5ce,2 +np.float32,0x7ecd6841,0x42b1015c,2 +np.float32,0x7f3d6c40,0x42b23ac6,2 +np.float32,0x3fb80c14,0x3f6796b9,2 +np.float32,0x3ff6ad56,0x3fa30d68,2 +np.float32,0x3fda44c3,0x3f90423e,2 +np.float32,0x3fdcba0c,0x3f9205fc,2 +np.float32,0x7e14a720,0x42aef8e6,2 +np.float32,0x3fe9e489,0x3f9b0047,2 +np.float32,0x7e69f933,0x42afe123,2 +np.float32,0x3ff3ee6d,0x3fa15f71,2 +np.float32,0x3f8538cd,0x3e91c1a7,2 +np.float32,0x3fdc3f07,0x3f91ae46,2 +np.float32,0x3fba2ef0,0x3f6bada2,2 +np.float32,0x7da64cd8,0x42adcf71,2 +np.float32,0x3fc34bd2,0x3f7c301d,2 +np.float32,0x3fa273aa,0x3f37d984,2 +np.float32,0x3ff0338c,0x3f9f0c86,2 +np.float32,0x7ed62cef,0x42b116c3,2 +np.float32,0x3f911e7e,0x3f02f7c6,2 +np.float32,0x7c8514c9,0x42aa9792,2 +np.float32,0x3fea2a74,0x3f9b2df5,2 +np.float32,0x3fe036f8,0x3f947a25,2 +np.float32,0x7c5654bf,0x42aa28ad,2 +np.float32,0x3fd9e423,0x3f8ffc32,2 +np.float32,0x7eec0439,0x42b1487b,2 +np.float32,0x3fc580f4,0x3f7ffb62,2 +np.float32,0x3fb0e316,0x3f592bbe,2 +np.float32,0x7c4cfb7d,0x42aa11d8,2 +np.float32,0x3faf9704,0x3f566e00,2 +np.float32,0x3fa7cf8a,0x3f45023d,2 +np.float32,0x7f7b724d,0x42b2cbcc,2 +np.float32,0x7f05bfe3,0x42b18897,2 +np.float32,0x3f90bde3,0x3f018bf3,2 +np.float32,0x7c565479,0x42aa28ad,2 +np.float32,0x3f94b517,0x3f0fb8e5,2 +np.float32,0x3fd6aadd,0x3f8d9e3c,2 +np.float32,0x7f09b37c,0x42b1977f,2 +np.float32,0x7f2b45ea,0x42b20734,2 +np.float32,0x3ff1d15e,0x3fa00fe9,2 +np.float32,0x3f99bce6,0x3f1fbd6c,2 +np.float32,0x7ecd1f76,0x42b100a7,2 +np.float32,0x7f443e2b,0x42b24ce2,2 +np.float32,0x7da7d6a5,0x42add428,2 +np.float32,0x7ebe0193,0x42b0d975,2 +np.float32,0x7ee13c43,0x42b1308b,2 +np.float32,0x3f8adf1b,0x3ed18e0c,2 +np.float32,0x7f76ce65,0x42b2c242,2 +np.float32,0x7e34f43d,0x42af5d92,2 +np.float32,0x7f306b76,0x42b2165d,2 +np.float32,0x7e1fd07f,0x42af1df7,2 +np.float32,0x3fab9a41,0x3f4db909,2 +np.float32,0x3fc23d1a,0x3f7a5803,2 +np.float32,0x3f8b7403,0x3ed70245,2 +np.float32,0x3f8c4dd6,0x3edebbae,2 +np.float32,0x3fe5f411,0x3f9864cd,2 +np.float32,0x3f88128b,0x3eb4e508,2 +np.float32,0x3fcb09de,0x3f84976f,2 +np.float32,0x7f32f2f5,0x42b21da6,2 +np.float32,0x3fe75610,0x3f9950f6,2 +np.float32,0x3f993edf,0x3f1e408d,2 +np.float32,0x3fc4a9d7,0x3f7e8be9,2 +np.float32,0x7f74551a,0x42b2bd1a,2 +np.float32,0x7de87129,0x42ae7ae2,2 +np.float32,0x7f18bbbd,0x42b1cc8c,2 +np.float32,0x7e7e1dd4,0x42b00b6c,2 +np.float32,0x3ff6e55b,0x3fa32f64,2 +np.float32,0x3fa634c8,0x3f412df3,2 +np.float32,0x3fd0fb7c,0x3f894e49,2 +np.float32,0x3ff4f6a6,0x3fa201d7,2 +np.float32,0x7f69d418,0x42b2a69a,2 +np.float32,0x7cb9632d,0x42ab414a,2 +np.float32,0x3fc57d36,0x3f7ff503,2 +np.float32,0x7e9e2ed7,0x42b07b9b,2 +np.float32,0x7f2e6868,0x42b2107d,2 +np.float32,0x3fa3169a,0x3f39785d,2 +np.float32,0x7f03cde0,0x42b18117,2 +np.float32,0x7f6d75d2,0x42b2ae7f,2 +np.float32,0x3ff483f2,0x3fa1bb75,2 +np.float32,0x7f1b39f7,0x42b1d4d6,2 +np.float32,0x3f8c7a7d,0x3ee0481e,2 +np.float32,0x3f989095,0x3f1c2b19,2 +np.float32,0x3fa4cbfd,0x3f3dbd87,2 +np.float32,0x7f75b00f,0x42b2bfef,2 +np.float32,0x3f940724,0x3f0d6756,2 +np.float32,0x7f5e5a1a,0x42b28cd6,2 +np.float32,0x800000,0xffc00000,2 +np.float32,0x7edd1d29,0x42b12716,2 +np.float32,0x3fa3e9e4,0x3f3b8c16,2 +np.float32,0x7e46d70e,0x42af8dd5,2 +np.float32,0x3f824745,0x3e40ec1e,2 +np.float32,0x3fd67623,0x3f8d770a,2 +np.float32,0x3fe9a6f3,0x3f9ad7fa,2 +np.float32,0x3fdda67c,0x3f92adc1,2 +np.float32,0x7ccb6c9a,0x42ab70d4,2 +np.float32,0x3ffd364a,0x3fa6f2fe,2 +np.float32,0x7e02424c,0x42aeb545,2 +np.float32,0x3fb6d2f2,0x3f6534a1,2 +np.float32,0x3fe1fe26,0x3f95b4cc,2 +np.float32,0x7e93ac57,0x42b05867,2 +np.float32,0x7f7b3433,0x42b2cb4d,2 +np.float32,0x3fb76803,0x3f66580d,2 +np.float32,0x3f9af881,0x3f23661b,2 +np.float32,0x3fd58062,0x3f8cbf98,2 +np.float32,0x80000000,0xffc00000,2 +np.float32,0x7f1af8f4,0x42b1d3ff,2 +np.float32,0x3fe66bba,0x3f98b4dc,2 +np.float32,0x7f6bd7bf,0x42b2aaff,2 +np.float32,0x3f84f79a,0x3e8e2e49,2 +np.float32,0x7e475b06,0x42af8f28,2 +np.float32,0x3faff89b,0x3f573d5e,2 +np.float32,0x7de5aa77,0x42ae74bb,2 +np.float32,0x3f8e9e42,0x3ef26cd2,2 +np.float32,0x3fb1cec3,0x3f5b1740,2 +np.float32,0x3f8890d6,0x3eba4821,2 +np.float32,0x3f9b39e9,0x3f242547,2 +np.float32,0x3fc895a4,0x3f829407,2 +np.float32,0x7f77943c,0x42b2c3dc,2 +np.float32,0x7f390d58,0x42b22ed2,2 +np.float32,0x3fe7e160,0x3f99ad58,2 +np.float32,0x3f93d2a0,0x3f0cb205,2 +np.float32,0x7f29499b,0x42b2013c,2 +np.float32,0x3f8c11b2,0x3edca10f,2 +np.float32,0x7e898ef8,0x42b03413,2 +np.float32,0x3fdff942,0x3f944f34,2 +np.float32,0x7f3d602f,0x42b23aa5,2 +np.float32,0x3f8a50f3,0x3ecc345b,2 +np.float32,0x3fa1f86d,0x3f369ce4,2 +np.float32,0x3f97ad95,0x3f19681d,2 +np.float32,0x3ffad1e0,0x3fa589e5,2 +np.float32,0x3fa70590,0x3f432311,2 +np.float32,0x7e6840cb,0x42afdd5c,2 +np.float32,0x3fd4036d,0x3f8ba0aa,2 +np.float32,0x7f7cc953,0x42b2ce84,2 +np.float32,0x7f228e1e,0x42b1ec74,2 +np.float32,0x7e37a866,0x42af652a,2 +np.float32,0x3fda22d0,0x3f9029a7,2 +np.float32,0x7f736bff,0x42b2bb31,2 +np.float32,0x3f9833b6,0x3f1b0b8e,2 +np.float32,0x7f466001,0x42b2526a,2 +np.float32,0xff7fffff,0xffc00000,2 +np.float32,0x7dd62bcd,0x42ae50f8,2 +np.float32,0x7f1d2bfe,0x42b1db36,2 +np.float32,0x7ecffe9e,0x42b107c5,2 +np.float32,0x7ebefe0a,0x42b0dc1b,2 +np.float32,0x7f45c63d,0x42b250dd,2 +np.float32,0x7f601af0,0x42b290db,2 +np.float32,0x3fcbb88a,0x3f8524e5,2 +np.float32,0x7ede55ff,0x42b129e8,2 +np.float32,0x7ea5dd5a,0x42b093e2,2 +np.float32,0x3ff53857,0x3fa22a12,2 +np.float32,0x3f8dbd6a,0x3eeb28a4,2 +np.float32,0x3fd1b467,0x3f89dd2c,2 +np.float32,0x3fe0423f,0x3f9481fc,2 +np.float32,0x3f84b421,0x3e8a6174,2 +np.float32,0x7f4efc97,0x42b2682c,2 +np.float32,0x7f601b33,0x42b290dc,2 +np.float32,0x3f94f240,0x3f108719,2 +np.float32,0x7decd251,0x42ae8471,2 +np.float32,0x3fdc457c,0x3f91b2e2,2 +np.float32,0x3f92a966,0x3f089c5a,2 +np.float32,0x3fc9732f,0x3f834afc,2 +np.float32,0x3f97948f,0x3f19194e,2 +np.float32,0x7f0824a1,0x42b191ac,2 +np.float32,0x7f0365a5,0x42b17f81,2 +np.float32,0x3f800000,0x0,2 +np.float32,0x7f0054c6,0x42b1736b,2 +np.float32,0x3fe86544,0x3f9a0484,2 +np.float32,0x7e95f844,0x42b0604e,2 +np.float32,0x3fce8602,0x3f8761e2,2 +np.float32,0x3fc726c8,0x3f81621d,2 +np.float32,0x3fcf6b03,0x3f88161b,2 +np.float32,0x3fceb843,0x3f87898a,2 +np.float32,0x3fe2f8b2,0x3f966071,2 +np.float32,0x7f3c8e7f,0x42b2386d,2 +np.float32,0x3fcee13a,0x3f87a9d2,2 +np.float32,0x3fc4df27,0x3f7ee73c,2 +np.float32,0x3ffde486,0x3fa758e3,2 +np.float32,0x3fa91be0,0x3f480b17,2 +np.float32,0x7f2a5a7d,0x42b20472,2 +np.float32,0x7e278d80,0x42af362d,2 +np.float32,0x3f96d091,0x3f16a9d5,2 +np.float32,0x7e925225,0x42b053b2,2 +np.float32,0x7f7ef83a,0x42b2d2ec,2 +np.float32,0x7eb4923a,0x42b0bf61,2 +np.float32,0x7e98bf19,0x42b069b3,2 +np.float32,0x3fac93a2,0x3f4fe410,2 +np.float32,0x7f46389c,0x42b25205,2 +np.float32,0x3f9fd447,0x3f30fd54,2 +np.float32,0x3fef42d4,0x3f9e7483,2 +np.float32,0x7f482174,0x42b256ed,2 +np.float32,0x3f97aedb,0x3f196c1e,2 +np.float32,0x7f764edd,0x42b2c13a,2 +np.float32,0x3f9117b5,0x3f02de5c,2 +np.float32,0x3fc7984e,0x3f81c12d,2 +np.float64,0x3ff1e2cb7463c597,0x3fdec6caf39e0c0e,1 +np.float64,0x3ffe4f89789c9f13,0x3ff40f4b1da0f3e9,1 +np.float64,0x7f6a5c9ac034b935,0x408605e51703c145,1 +np.float64,0x7fdcb6ece3b96dd9,0x40862d6521e16d60,1 +np.float64,0x3ff6563e182cac7c,0x3feb9d8210f3fa88,1 +np.float64,0x7fde32025f3c6404,0x40862dcc1d1a9b7f,1 +np.float64,0x7fd755ed35aeabd9,0x40862bbc5522b779,1 +np.float64,0x3ff5c81f4bcb903e,0x3fea71f10b954ea3,1 +np.float64,0x3fffe805d35fd00c,0x3ff50463a1ba2938,1 +np.float64,0x7fd045a1c1a08b43,0x408628d9f431f2f5,1 +np.float64,0x3ff49f7dd9893efc,0x3fe7c6736e17ea8e,1 +np.float64,0x7fccfbc1fd39f783,0x408627eca79acf51,1 +np.float64,0x3ff1af0a00035e14,0x3fdd1c0e7d5706ea,1 +np.float64,0x7fe7bd17162f7a2d,0x4086316af683502b,1 +np.float64,0x3ff0941b8d012837,0x3fd128d274065ac0,1 +np.float64,0x3ffa0c5d98b418bb,0x3ff11af9c8edd17f,1 +np.float64,0x3ffad9733355b2e6,0x3ff1b6d1307acb42,1 +np.float64,0x3ffabb2a33d57654,0x3ff1a0442b034e50,1 +np.float64,0x3ff36118b0c6c231,0x3fe472b7dfb23516,1 +np.float64,0x3ff2441d3664883a,0x3fe0d61145608f0c,1 +np.float64,0x7fe039862d20730b,0x40862e5f8ed752d3,1 +np.float64,0x7fb1dde24023bbc4,0x40861e824cdb0664,1 +np.float64,0x7face6335839cc66,0x40861ccf90a26e16,1 +np.float64,0x3ffb5d0e1af6ba1c,0x3ff2170f6f42fafe,1 +np.float64,0x3ff5c2c6a50b858d,0x3fea665aabf04407,1 +np.float64,0x3ffabb409db57681,0x3ff1a054ea32bfc3,1 +np.float64,0x3ff1e054e983c0aa,0x3fdeb30c17286cb6,1 +np.float64,0x7fe467f73268cfed,0x4086303529e52e9b,1 +np.float64,0x7fe0e86bf961d0d7,0x40862eb40788b04a,1 +np.float64,0x3ffb743542f6e86a,0x3ff227b4ea5acee0,1 +np.float64,0x3ff2de6826e5bcd0,0x3fe2e31fcde0a96c,1 +np.float64,0x7fd6b27ccfad64f9,0x40862b8385697c31,1 +np.float64,0x7fe0918e8d21231c,0x40862e8a82d9517a,1 +np.float64,0x7fd0ca0395a19406,0x4086291a0696ed33,1 +np.float64,0x3ffb042496960849,0x3ff1d658c928abfc,1 +np.float64,0x3ffcd0409799a081,0x3ff31877df0cb245,1 +np.float64,0x7fe429bd06685379,0x4086301c9f259934,1 +np.float64,0x3ff933076092660f,0x3ff06d2e5f4d9ab7,1 +np.float64,0x7feaefcb28f5df95,0x4086326dccf88e6f,1 +np.float64,0x7fb5f2c1f82be583,0x40862027ac02a39d,1 +np.float64,0x3ffb5d9e3bd6bb3c,0x3ff21777501d097e,1 +np.float64,0x10000000000000,0xfff8000000000000,1 +np.float64,0x3ff70361596e06c3,0x3fecf675ceda7e19,1 +np.float64,0x3ff71a21b5ee3444,0x3fed224fa048d9a9,1 +np.float64,0x3ffb102b86762057,0x3ff1df2cc9390518,1 +np.float64,0x7feaaeb35c355d66,0x4086325a60704a90,1 +np.float64,0x7fd9a3d0a93347a0,0x40862c7d300fc076,1 +np.float64,0x7fabcf159c379e2a,0x40861c80cdbbff27,1 +np.float64,0x7fd1c066ec2380cd,0x4086298c3006fee6,1 +np.float64,0x3ff3d5ae2d67ab5c,0x3fe5bc16447428db,1 +np.float64,0x3ff4b76add696ed6,0x3fe800f5bbf21376,1 +np.float64,0x3ff60d89ee0c1b14,0x3feb063fdebe1a68,1 +np.float64,0x7f1d2648003a4c8f,0x4085eaf9238af95a,1 +np.float64,0x7fe8b45f6df168be,0x408631bca5abf6d6,1 +np.float64,0x7fe9ea5308f3d4a5,0x4086321ea2bd3af9,1 +np.float64,0x7fcb6ba5a636d74a,0x4086277b208075ed,1 +np.float64,0x3ff621cfd74c43a0,0x3feb30d59baf5919,1 +np.float64,0x3ff7bc8ca0af7919,0x3fee524da8032896,1 +np.float64,0x7fda22dd0c3445b9,0x40862ca47326d063,1 +np.float64,0x7fd02ed4b2a05da8,0x408628ceb6919421,1 +np.float64,0x3ffe64309fdcc861,0x3ff41c1b18940709,1 +np.float64,0x3ffee4042abdc808,0x3ff46a6005bccb41,1 +np.float64,0x3ff078145b00f029,0x3fceeb3d6bfae0eb,1 +np.float64,0x7fda20fd20b441f9,0x40862ca3e03b990b,1 +np.float64,0x3ffa9e9e9af53d3d,0x3ff18ade3cbee789,1 +np.float64,0x3ff0a1062501420c,0x3fd1e32de6d18c0d,1 +np.float64,0x3ff3bdf118477be2,0x3fe57ad89b7fdf8b,1 +np.float64,0x3ff101c0d5c20382,0x3fd6965d3539be47,1 +np.float64,0x7feba3b53b774769,0x408632a28c7aca4d,1 +np.float64,0x3ff598db5d4b31b7,0x3fea0aa65c0b421a,1 +np.float64,0x3ff5fdfbb72bfbf8,0x3feae55accde4a5e,1 +np.float64,0x7fe5bae53aab75c9,0x408630b5e7a5b92a,1 +np.float64,0x3ff8f668afd1ecd2,0x3ff03af686666c9c,1 +np.float64,0x3ff5ba72dd2b74e6,0x3fea5441f223c093,1 +np.float64,0x3ff8498147109302,0x3fef4e45d501601d,1 +np.float64,0x7feddcfa5efbb9f4,0x4086334106a6e76b,1 +np.float64,0x7fd1a30200234603,0x4086297ee5cc562c,1 +np.float64,0x3ffffa8ee07ff51e,0x3ff50f1dc46f1303,1 +np.float64,0x7fef7ed00ebefd9f,0x408633ae01dabe52,1 +np.float64,0x3ffb6e062276dc0c,0x3ff22344c58c2016,1 +np.float64,0x7fcf2b59943e56b2,0x4086288190dd5eeb,1 +np.float64,0x3ffa589f9254b13f,0x3ff155cc081eee0b,1 +np.float64,0x3ff05415ca60a82c,0x3fc9e45565baef0a,1 +np.float64,0x7feb34bed576697d,0x408632822d5a178c,1 +np.float64,0x3ff3993845c73270,0x3fe51423baf246c3,1 +np.float64,0x3ff88367aaf106d0,0x3fefb2d9ca9f1192,1 +np.float64,0x7fef364304fe6c85,0x4086339b7ed82997,1 +np.float64,0x7fcba2c317374585,0x4086278b24e42934,1 +np.float64,0x3ff1aef885e35df1,0x3fdd1b79f55b20c0,1 +np.float64,0x7fe19367886326ce,0x40862f035f867445,1 +np.float64,0x3ff3c8295e279053,0x3fe5970aa670d32e,1 +np.float64,0x3ff6edda164ddbb4,0x3feccca9eb59d6b9,1 +np.float64,0x7fdeaea940bd5d52,0x40862dece02d151b,1 +np.float64,0x7fea9d6324353ac5,0x408632552ddf0d4f,1 +np.float64,0x7fe60e39e66c1c73,0x408630d45b1ad0c4,1 +np.float64,0x7fde06325abc0c64,0x40862dc07910038c,1 +np.float64,0x7f9ec89d303d9139,0x408617c55ea4c576,1 +np.float64,0x3ff9801930530032,0x3ff0abe5be046051,1 +np.float64,0x3ff4d5859689ab0b,0x3fe849a7f7a19fa3,1 +np.float64,0x3ff38afbc48715f8,0x3fe4ebb7710cbab9,1 +np.float64,0x3ffd88a0e77b1142,0x3ff3916964407e21,1 +np.float64,0x1,0xfff8000000000000,1 +np.float64,0x3ff5db59e58bb6b4,0x3fea9b6b5ccc116f,1 +np.float64,0x3ffd4b05b15a960c,0x3ff369792f661a90,1 +np.float64,0x7fdcebc4fb39d789,0x40862d73cd623378,1 +np.float64,0x3ff5b56f944b6adf,0x3fea4955d6b06ca3,1 +np.float64,0x7fd4e4abf2a9c957,0x40862ad9e9da3c61,1 +np.float64,0x7fe08e0d6aa11c1a,0x40862e88d17ef277,1 +np.float64,0x3ff0dfc97da1bf93,0x3fd50f9004136d8f,1 +np.float64,0x7fdec38eaebd871c,0x40862df2511e26b4,1 +np.float64,0x7ff8000000000000,0x7ff8000000000000,1 +np.float64,0x3ff21865504430cb,0x3fe033fe3cf3947a,1 +np.float64,0x7fdc139708b8272d,0x40862d371cfbad03,1 +np.float64,0x7fe1fe3be3a3fc77,0x40862f336e3ba63a,1 +np.float64,0x7fd9fa2493b3f448,0x40862c97f2960be9,1 +np.float64,0x3ff0a027db414050,0x3fd1d6e54a707c87,1 +np.float64,0x3ff568b16f4ad163,0x3fe99f5c6d7b6e18,1 +np.float64,0x3ffe2f82877c5f05,0x3ff3fb54bd0da753,1 +np.float64,0x7fbaf5778435eaee,0x408621ccc9e2c1be,1 +np.float64,0x7fc5aaf8362b55ef,0x40862598e7072a49,1 +np.float64,0x7fe0ebfdd4a1d7fb,0x40862eb5b7bf99d5,1 +np.float64,0x7fd8efeb5931dfd6,0x40862c444636f408,1 +np.float64,0x3ff361a308c6c346,0x3fe4744cae63e6df,1 +np.float64,0x7fef287d39be50f9,0x40863397f65c807e,1 +np.float64,0x7fe72c4a14ae5893,0x4086313992e52082,1 +np.float64,0x3ffd1be44cba37c8,0x3ff34a9a45239eb9,1 +np.float64,0x3ff50369c18a06d4,0x3fe8b69319f091f1,1 +np.float64,0x3ffb333c25766678,0x3ff1f8c78eeb28f1,1 +np.float64,0x7fe12050416240a0,0x40862ece4e2f2f24,1 +np.float64,0x7fe348f5526691ea,0x40862fc16fbe7b6c,1 +np.float64,0x3ff343cc4d068799,0x3fe41c2a30cab7d2,1 +np.float64,0x7fd1b0daaa2361b4,0x408629852b3104ff,1 +np.float64,0x3ff6a41f37ad483e,0x3fec3b36ee6c6d4a,1 +np.float64,0x3ffad9439435b287,0x3ff1b6add9a1b3d7,1 +np.float64,0x7fbeb9a2f23d7345,0x408622d89ac1eaba,1 +np.float64,0x3ffab3d39fb567a7,0x3ff19ac75b4427f3,1 +np.float64,0x3ff890003ed12000,0x3fefc8844471c6ad,1 +np.float64,0x3ffc9f595e593eb2,0x3ff2f7a8699f06d8,1 +np.float64,0x7fe2224ef6e4449d,0x40862f43684a154a,1 +np.float64,0x3ffa67ba08d4cf74,0x3ff161525778df99,1 +np.float64,0x7fe87e24b570fc48,0x408631ab02b159fb,1 +np.float64,0x7fd6e99be92dd337,0x40862b96dba73685,1 +np.float64,0x7fe90f39fdf21e73,0x408631d9dbd36c1e,1 +np.float64,0x3ffb7806abd6f00e,0x3ff22a719b0f4c46,1 +np.float64,0x3ffa511ba3d4a238,0x3ff1500c124f6e17,1 +np.float64,0x3ff5d7a569abaf4b,0x3fea937391c280e8,1 +np.float64,0x7fc4279d20284f39,0x40862504a5cdcb96,1 +np.float64,0x3ffe8791b1fd0f24,0x3ff431f1ed7eaba0,1 +np.float64,0x7fe3b2f5276765e9,0x40862fecf15e2535,1 +np.float64,0x7feeab0e7abd561c,0x408633778044cfbc,1 +np.float64,0x7fdba88531375109,0x40862d1860306d7a,1 +np.float64,0x7fe7b19b3def6335,0x4086316716d6890b,1 +np.float64,0x3ff9e9437413d287,0x3ff0ff89431c748c,1 +np.float64,0x3ff960716a52c0e3,0x3ff092498028f802,1 +np.float64,0x3ff271bf56a4e37f,0x3fe1786fc8dd775d,1 +np.float64,0x3fff2a6578be54cb,0x3ff494bbe303eeb5,1 +np.float64,0x3ffd842eb5fb085e,0x3ff38e8b7ba42bc5,1 +np.float64,0x3ff91600e5d22c02,0x3ff0553c6a6b3d93,1 +np.float64,0x3ff9153f45f22a7e,0x3ff0549c0eaecf95,1 +np.float64,0x7fe0ab319da15662,0x40862e96da3b19f9,1 +np.float64,0x3ff06acd1f60d59a,0x3fcd2aca543d2772,1 +np.float64,0x3ffb3e7a54d67cf4,0x3ff200f288cd391b,1 +np.float64,0x3ffd01356f1a026b,0x3ff339003462a56c,1 +np.float64,0x3ffacd35def59a6c,0x3ff1adb8d32b3ec0,1 +np.float64,0x3ff6f953264df2a6,0x3fece2f992948d6e,1 +np.float64,0x3ff0fa91f5a1f524,0x3fd64609a28f1590,1 +np.float64,0x7fd1b7610ca36ec1,0x408629881e03dc7d,1 +np.float64,0x3ff4317fb7c86300,0x3fe6b086ed265887,1 +np.float64,0x3ff3856198070ac3,0x3fe4dbb6bc88b9e3,1 +np.float64,0x7fed7fc4573aff88,0x40863327e7013a81,1 +np.float64,0x3ffe53cbbf5ca798,0x3ff411f07a29b1f4,1 +np.float64,0x3ff092195b012433,0x3fd10b1c0b4b14fe,1 +np.float64,0x3ff1a3171163462e,0x3fdcb5c301d5d40d,1 +np.float64,0x3ffa1401f1742804,0x3ff120eb319e9faa,1 +np.float64,0x7fd352f6f426a5ed,0x40862a3a048feb6d,1 +np.float64,0x7fd4ee246fa9dc48,0x40862add895d808f,1 +np.float64,0x3ff0675cfa00ceba,0x3fccb2222c5493ca,1 +np.float64,0x3ffe5cb38f3cb967,0x3ff417773483d161,1 +np.float64,0x7fe11469ea2228d3,0x40862ec8bd3e497f,1 +np.float64,0x3fff13cba67e2798,0x3ff4872fe2c26104,1 +np.float64,0x3ffb73d3d316e7a8,0x3ff2276f08612ea2,1 +np.float64,0x7febfb70f237f6e1,0x408632bbc9450721,1 +np.float64,0x3ff84a0d87b0941b,0x3fef4f3b707e3145,1 +np.float64,0x7fd71fd5082e3fa9,0x40862ba9b4091172,1 +np.float64,0x3ff560737d8ac0e7,0x3fe98cc9c9ba2f61,1 +np.float64,0x3ff46a266ae8d44d,0x3fe74190e5234822,1 +np.float64,0x7fe8cc9225719923,0x408631c477db9708,1 +np.float64,0x3ff871de5930e3bc,0x3fef948f7d00fbef,1 +np.float64,0x3ffd0bc7895a178f,0x3ff33ffc18357721,1 +np.float64,0x3ff66099f9ccc134,0x3febb2bc775b4720,1 +np.float64,0x7fe91f1be9723e37,0x408631deec3a5c9e,1 +np.float64,0x7fd60462f12c08c5,0x40862b4537e1c1c6,1 +np.float64,0x3ff053100ba0a620,0x3fc9bc0c21e2284f,1 +np.float64,0x7fd864c611b0c98b,0x40862c1724506255,1 +np.float64,0x7fd191decb2323bd,0x408629771bfb68cc,1 +np.float64,0x3ff792a1656f2543,0x3fee054f2e135fcf,1 +np.float64,0x7fd03625cea06c4b,0x408628d253b840e3,1 +np.float64,0x7fc3967716272ced,0x408624ca35451042,1 +np.float64,0x7fe6636cb32cc6d8,0x408630f3073a22a7,1 +np.float64,0x3ffc2d3976585a73,0x3ff2a9d4c0dae607,1 +np.float64,0x3fffd10ee79fa21e,0x3ff4f70db69888be,1 +np.float64,0x3ff1d4fcae23a9f9,0x3fde57675007b23c,1 +np.float64,0x3ffa5da19e14bb43,0x3ff1599f74d1c113,1 +np.float64,0x3ff7f4eb0d6fe9d6,0x3feeb85189659e99,1 +np.float64,0x7fbcca44d8399489,0x408622536234f7c1,1 +np.float64,0x7fef5f97ec3ebf2f,0x408633a60fdde0d7,1 +np.float64,0x7fde4a66da3c94cd,0x40862dd290ebc184,1 +np.float64,0x3ff072957a40e52b,0x3fce34d913d87613,1 +np.float64,0x3ff2bc4c9dc57899,0x3fe27497e6ebe27d,1 +np.float64,0x7fd7d152b4afa2a4,0x40862be63469eecd,1 +np.float64,0x3ff957d768f2afaf,0x3ff08b4ad8062a73,1 +np.float64,0x7fe4bc5f45a978be,0x40863055fd66e4eb,1 +np.float64,0x7fc90de345321bc6,0x408626c24ce7e370,1 +np.float64,0x3ff2d7a37d85af47,0x3fe2cd6a40b544a0,1 +np.float64,0x7fe536ea1f6a6dd3,0x40863084bade76a3,1 +np.float64,0x3fff970c9cdf2e19,0x3ff4d524572356dd,1 +np.float64,0x3ffe173ae63c2e76,0x3ff3ec1ee35ad28c,1 +np.float64,0x3ff714025cce2805,0x3fed168aedff4a2b,1 +np.float64,0x7fce7b414c3cf682,0x40862853dcdd19d4,1 +np.float64,0x3ff019623f2032c4,0x3fbc7c602df0bbaf,1 +np.float64,0x3ff72f57fd0e5eb0,0x3fed4ae75f697432,1 +np.float64,0x3ff283778e8506ef,0x3fe1b5c5725b0dfd,1 +np.float64,0x3ff685a29aed0b45,0x3febfdfdedd581e2,1 +np.float64,0x3ff942d24fb285a4,0x3ff07a224c3ecfaf,1 +np.float64,0x3ff2e4a9f465c954,0x3fe2f71905399e8f,1 +np.float64,0x7fdfa1c7fa3f438f,0x40862e2b4e06f098,1 +np.float64,0x3ff49b59c26936b4,0x3fe7bc41c8c1e59d,1 +np.float64,0x3ff2102d3704205a,0x3fe014bf7e28924e,1 +np.float64,0x3ff88de3b8311bc8,0x3fefc4e3e0a15a89,1 +np.float64,0x7fea5ba25374b744,0x40863241519c9b66,1 +np.float64,0x3fffe5df637fcbbf,0x3ff5032488f570f9,1 +np.float64,0x7fe67cfefe6cf9fd,0x408630fc25333cb4,1 +np.float64,0x3ff090bf2b01217e,0x3fd0f6fcf1092b4a,1 +np.float64,0x7fecd75bc5f9aeb7,0x408632f9b6c2e013,1 +np.float64,0x7fe15df38c62bbe6,0x40862eeae5ac944b,1 +np.float64,0x3ff4757875a8eaf1,0x3fe75e0eafbe28ce,1 +np.float64,0x7fecca8a51b99514,0x408632f627c23923,1 +np.float64,0x3ff91ca529d2394a,0x3ff05abb327fd1ca,1 +np.float64,0x3ffb962993b72c53,0x3ff23ff831717579,1 +np.float64,0x3ffd548a2c7aa914,0x3ff36fac7f56d716,1 +np.float64,0x7fbafb5cb035f6b8,0x408621ce898a02fb,1 +np.float64,0x3ff1d86daca3b0db,0x3fde73536c29218c,1 +np.float64,0x7fa8d0f8f431a1f1,0x40861b97a03c3a18,1 +np.float64,0x3ff44f1067489e21,0x3fe6fcbd8144ab2a,1 +np.float64,0x7fec062b07380c55,0x408632bed9c6ce85,1 +np.float64,0x3ff7e11e0fcfc23c,0x3fee94ada7efaac4,1 +np.float64,0x7fe77505c1aeea0b,0x4086315287dda0ba,1 +np.float64,0x7fc465af2728cb5d,0x4086251d236107f7,1 +np.float64,0x3ffe811c4a7d0238,0x3ff42df7e8b6cf2d,1 +np.float64,0x7fe05a471260b48d,0x40862e6fa502738b,1 +np.float64,0x7fec32cd9778659a,0x408632cb8d98c5a3,1 +np.float64,0x7fd203a220a40743,0x408629aa43b010c0,1 +np.float64,0x7fed71f7d17ae3ef,0x4086332428207101,1 +np.float64,0x3ff3918999e72313,0x3fe4fe5e8991402f,1 +np.float64,0x3ff3ecae38c7d95c,0x3fe5fa787d887981,1 +np.float64,0x7fd65345b82ca68a,0x40862b61aed8c64e,1 +np.float64,0x3ff1efdd01c3dfba,0x3fdf2eae36139204,1 +np.float64,0x3ffba9344f375268,0x3ff24d7fdcfc313b,1 +np.float64,0x7fd0469b35208d35,0x408628da6ed24bdd,1 +np.float64,0x7fe525782daa4aef,0x4086307e240c8b30,1 +np.float64,0x3ff8e473d371c8e8,0x3ff02beebd4171c7,1 +np.float64,0x3ff59a43898b3487,0x3fea0dc0a6acea0a,1 +np.float64,0x7fef50c7263ea18d,0x408633a247d7cd42,1 +np.float64,0x7fe8b5a301f16b45,0x408631bd0e71c855,1 +np.float64,0x3ff209369de4126d,0x3fdff4264334446b,1 +np.float64,0x3ffbe2ff4437c5fe,0x3ff2763b356814c7,1 +np.float64,0x3ff55938156ab270,0x3fe97c70514f91bf,1 +np.float64,0x3fff5d8bf81ebb18,0x3ff4b333b230672a,1 +np.float64,0x3ff16a317bc2d463,0x3fdab84e7faa468f,1 +np.float64,0x3ff7e64f8dafcc9f,0x3fee9e0bd57e9566,1 +np.float64,0x7fef4dc065be9b80,0x408633a181e25abb,1 +np.float64,0x3ff64a24a62c9449,0x3feb849ced76437e,1 +np.float64,0x7fc3cb85ef27970b,0x408624dfc39c8f74,1 +np.float64,0x7fec2162a77842c4,0x408632c69b0d43b6,1 +np.float64,0x7feccee6dc399dcd,0x408632f75de98c46,1 +np.float64,0x7faff4f5f43fe9eb,0x40861d9d89be14c9,1 +np.float64,0x7fee82df60fd05be,0x4086336cfdeb7317,1 +np.float64,0x3ffe54588d9ca8b1,0x3ff41247eb2f75ca,1 +np.float64,0x3ffe5615b55cac2c,0x3ff4135c4eb11620,1 +np.float64,0x3ffdaf9a6a1b5f35,0x3ff3aa70e50d1692,1 +np.float64,0x3ff69c045f4d3809,0x3fec2b00734e2cde,1 +np.float64,0x7fd049239aa09246,0x408628dbad6dd995,1 +np.float64,0x3ff2acbe8465597d,0x3fe24138652195e1,1 +np.float64,0x3ffb288302365106,0x3ff1f0f86ca7e5d1,1 +np.float64,0x3fff6fe8d87edfd2,0x3ff4be136acf53c5,1 +np.float64,0x3ffc87c8bfb90f92,0x3ff2e7bbd65867cb,1 +np.float64,0x3ff173327ca2e665,0x3fdb0b945abb00d7,1 +np.float64,0x3ff9a5cf7a134b9f,0x3ff0ca2450f07c78,1 +np.float64,0x7faf782b043ef055,0x40861d7e0e9b35ef,1 +np.float64,0x3ffa0874975410e9,0x3ff117ee3dc8f5ba,1 +np.float64,0x7fc710fc7f2e21f8,0x40862618fed167fb,1 +np.float64,0x7feb73f4c876e7e9,0x40863294ae3ac1eb,1 +np.float64,0x8000000000000000,0xfff8000000000000,1 +np.float64,0x7fb46615c028cc2b,0x40861f91bade4dad,1 +np.float64,0x7fc26b064624d60c,0x4086244c1b76c938,1 +np.float64,0x3ff06ab9fa40d574,0x3fcd282fd971d1b4,1 +np.float64,0x3ff61da7410c3b4e,0x3feb28201031af02,1 +np.float64,0x3ffec7ba1b9d8f74,0x3ff459342511f952,1 +np.float64,0x7ff4000000000000,0x7ffc000000000000,1 +np.float64,0x7fe5d570422baae0,0x408630bfa75008c9,1 +np.float64,0x3ffa895832f512b0,0x3ff17ad41555dccb,1 +np.float64,0x7fd343ac21a68757,0x40862a33ad59947a,1 +np.float64,0x3ffc1eeb37383dd6,0x3ff29ff29e55a006,1 +np.float64,0x7fee3c5c507c78b8,0x4086335a6b768090,1 +np.float64,0x7fe96d774a32daee,0x408631f7b9937e36,1 +np.float64,0x7fb878362430f06b,0x40862106603497b6,1 +np.float64,0x7fec0a79c03814f3,0x408632c01479905e,1 +np.float64,0x3ffa2f143c145e28,0x3ff135e25d902e1a,1 +np.float64,0x3ff14ccff80299a0,0x3fd9a0cd3397b14c,1 +np.float64,0x3ff97980dcb2f302,0x3ff0a6942a8133ab,1 +np.float64,0x3ff872e2d1f0e5c6,0x3fef96526eb2f756,1 +np.float64,0x7fdf1c9b46be3936,0x40862e0957fee329,1 +np.float64,0x7fcab6525d356ca4,0x408627458791f029,1 +np.float64,0x3ff964e74a52c9ce,0x3ff095e8845d523c,1 +np.float64,0x3ffb3aa23c967544,0x3ff1fe282d897c13,1 +np.float64,0x7fdd8a36afbb146c,0x40862d9f2b05f61b,1 +np.float64,0x3ffea39f42fd473e,0x3ff4432a48176399,1 +np.float64,0x7fea614f68b4c29e,0x408632430a750385,1 +np.float64,0x7feeafb86abd5f70,0x40863378b79f70cf,1 +np.float64,0x3ff80bc94eb01792,0x3feee138e9d626bd,1 +np.float64,0x7fcaca74743594e8,0x4086274b8ce4d1e1,1 +np.float64,0x3ff8b14815316290,0x3ff000b3526c8321,1 +np.float64,0x7fc698eb5f2d31d6,0x408625eeec86cd2b,1 +np.float64,0x7fe15429a3e2a852,0x40862ee6621205b8,1 +np.float64,0x7fee37f81b7c6fef,0x4086335941ed80dd,1 +np.float64,0x3ff8097ab3f012f6,0x3feedd1bafc3196e,1 +np.float64,0x7fe7c889ceaf9113,0x4086316ed13f2394,1 +np.float64,0x7fceca94513d9528,0x4086286893a06824,1 +np.float64,0x3ff593a103cb2742,0x3fe9ff1af4f63cc9,1 +np.float64,0x7fee237d24bc46f9,0x40863353d4142c87,1 +np.float64,0x3ffbf71e4777ee3c,0x3ff2844c0ed9f4d9,1 +np.float64,0x3ff490c65c09218d,0x3fe7a2216d9f69fd,1 +np.float64,0x3fff5ceaf1feb9d6,0x3ff4b2d430a90110,1 +np.float64,0x3ff55baecceab75e,0x3fe98203980666c4,1 +np.float64,0x3ff511bc306a2378,0x3fe8d81ce7be7b50,1 +np.float64,0x3ff38f83dcc71f08,0x3fe4f89f130d5f87,1 +np.float64,0x3ff73a3676ee746d,0x3fed5f98a65107ee,1 +np.float64,0x7fc27e50c824fca1,0x408624547828bc49,1 +np.float64,0xfff0000000000000,0xfff8000000000000,1 +np.float64,0x3fff38959ebe712b,0x3ff49d362c7ba16a,1 +np.float64,0x3ffad6d23a75ada4,0x3ff1b4dda6394ed0,1 +np.float64,0x3ffe77c6c2dcef8e,0x3ff4283698835ecb,1 +np.float64,0x3fff5feb413ebfd6,0x3ff4b49bcbdb3aa9,1 +np.float64,0x3ff0d30aa161a615,0x3fd4751bcdd7d727,1 +np.float64,0x3ff51e07e00a3c10,0x3fe8f4bd1408d694,1 +np.float64,0x8010000000000000,0xfff8000000000000,1 +np.float64,0x7fd231d2fe2463a5,0x408629beaceafcba,1 +np.float64,0x3fff6b4aee1ed696,0x3ff4bb58544bf8eb,1 +np.float64,0x3ff91fcd2f323f9a,0x3ff05d56e33db6b3,1 +np.float64,0x3ff3b889ab477113,0x3fe56bdeab74cce5,1 +np.float64,0x3ff99bfe30d337fc,0x3ff0c24bbf265561,1 +np.float64,0x3ffbe9e5eaf7d3cc,0x3ff27b0fe60f827a,1 +np.float64,0x7fd65678e92cacf1,0x40862b62d44fe8b6,1 +np.float64,0x7fd9cc477233988e,0x40862c89c638ee48,1 +np.float64,0x3ffc123c72d82479,0x3ff297294d05cbc0,1 +np.float64,0x3ff58abad58b1576,0x3fe9eb65da2a867a,1 +np.float64,0x7fe534887b2a6910,0x40863083d4ec2877,1 +np.float64,0x7fe1d3dcb123a7b8,0x40862f208116c55e,1 +np.float64,0x7fd4d570dba9aae1,0x40862ad412c413cd,1 +np.float64,0x3fffce7d3fdf9cfa,0x3ff4f58f02451928,1 +np.float64,0x3ffa76901c74ed20,0x3ff16c9a5851539c,1 +np.float64,0x7fdd88ffa23b11fe,0x40862d9ed6c6f426,1 +np.float64,0x3ff09fdbb9e13fb7,0x3fd1d2ae4fcbf713,1 +np.float64,0x7fe64567772c8ace,0x408630e845dbc290,1 +np.float64,0x7fb1a849ba235092,0x40861e6a291535b2,1 +np.float64,0x3ffaddb105f5bb62,0x3ff1b9f68f4c419b,1 +np.float64,0x7fd2fc3d5025f87a,0x40862a15cbc1df75,1 +np.float64,0x7fdea7d872bd4fb0,0x40862deb190b2c50,1 +np.float64,0x7fd50ea97eaa1d52,0x40862ae9edc4c812,1 +np.float64,0x3fff659c245ecb38,0x3ff4b7fb18b31aea,1 +np.float64,0x3ff3f1fbb7c7e3f7,0x3fe608bd9d76268c,1 +np.float64,0x3ff76869d9aed0d4,0x3fedb6c23d3a317b,1 +np.float64,0x7fedd4efe93ba9df,0x4086333edeecaa43,1 +np.float64,0x3ff9a5bd4eb34b7a,0x3ff0ca15d02bc960,1 +np.float64,0x3ffd9359cc5b26b4,0x3ff39850cb1a6b6c,1 +np.float64,0x7fe912d0427225a0,0x408631db00e46272,1 +np.float64,0x3ffb3802fe567006,0x3ff1fc4093646465,1 +np.float64,0x3ff02cc38a205987,0x3fc2e8182802a07b,1 +np.float64,0x3ffda953dd1b52a8,0x3ff3a66c504cf207,1 +np.float64,0x7fe0a487e4a1490f,0x40862e93a6f20152,1 +np.float64,0x7fed265ed1fa4cbd,0x4086330f838ae431,1 +np.float64,0x7fd0000114200001,0x408628b76ec48b5c,1 +np.float64,0x3ff2c262786584c5,0x3fe288860d354b0f,1 +np.float64,0x8000000000000001,0xfff8000000000000,1 +np.float64,0x3ffdae9f075b5d3e,0x3ff3a9d006ae55c1,1 +np.float64,0x3ffb69c72156d38e,0x3ff22037cbb85e5b,1 +np.float64,0x7feeae255f7d5c4a,0x408633784e89bc05,1 +np.float64,0x7feb13927c362724,0x408632786630c55d,1 +np.float64,0x7fef49e072be93c0,0x408633a08451d476,1 +np.float64,0x3fff23d6337e47ac,0x3ff490ceb6e634ae,1 +np.float64,0x3ffba82cf8f7505a,0x3ff24cc51c73234d,1 +np.float64,0x7fe948719ef290e2,0x408631ec0b36476e,1 +np.float64,0x3ff41926c5e8324e,0x3fe670e14bbda8cd,1 +np.float64,0x3ff91f09c1523e14,0x3ff05cb5731878da,1 +np.float64,0x3ff6ae6afccd5cd6,0x3fec4fbeca764086,1 +np.float64,0x3ff927f7e0f24ff0,0x3ff06413eeb8eb1e,1 +np.float64,0x3ff19dd2b9e33ba5,0x3fdc882f97994600,1 +np.float64,0x7fe8e502c5b1ca05,0x408631cc56526fff,1 +np.float64,0x7feb49f70fb693ed,0x4086328868486fcd,1 +np.float64,0x3ffd942d535b285a,0x3ff398d8d89f52ca,1 +np.float64,0x7fc3b9c5c627738b,0x408624d893e692ca,1 +np.float64,0x7fea0780ff340f01,0x408632279fa46704,1 +np.float64,0x7fe4c90066a99200,0x4086305adb47a598,1 +np.float64,0x7fdb209113364121,0x40862cf0ab64fd7d,1 +np.float64,0x3ff38617e5470c30,0x3fe4ddc0413b524f,1 +np.float64,0x7fea1b5b803436b6,0x4086322db767f091,1 +np.float64,0x7fe2004898e40090,0x40862f3457795dc5,1 +np.float64,0x3ff3c4360ac7886c,0x3fe58c29843a4c75,1 +np.float64,0x3ff504bc168a0978,0x3fe8b9ada7f698e6,1 +np.float64,0x3ffd3e936fda7d27,0x3ff3615912c5b4ac,1 +np.float64,0x3ffbdc52fb97b8a6,0x3ff2718dae5f1f2b,1 +np.float64,0x3fffef6d84ffdedb,0x3ff508adbc8556cf,1 +np.float64,0x3ff23b65272476ca,0x3fe0b646ed2579eb,1 +np.float64,0x7fe4633068a8c660,0x408630334a4b7ff7,1 +np.float64,0x3ff769b754aed36f,0x3fedb932af0223f9,1 +np.float64,0x7fe7482d92ee905a,0x408631432de1b057,1 +np.float64,0x3ff5dd682aabbad0,0x3fea9fd5e506a86d,1 +np.float64,0x7fd68399a2ad0732,0x40862b72ed89805d,1 +np.float64,0x3ffad7acc3d5af5a,0x3ff1b57fe632c948,1 +np.float64,0x3ffc68e43698d1c8,0x3ff2d2be6f758761,1 +np.float64,0x3ff4e517fbc9ca30,0x3fe86eddf5e63a58,1 +np.float64,0x3ff34c63c56698c8,0x3fe435b74ccd6a13,1 +np.float64,0x7fea9456c17528ad,0x4086325275237015,1 +np.float64,0x7fee6573f2fccae7,0x4086336543760346,1 +np.float64,0x7fd5496fb9aa92de,0x40862b0023235667,1 +np.float64,0x7ff0000000000000,0x7ff0000000000000,1 +np.float64,0x3ffb70e31256e1c6,0x3ff22552f54b13e0,1 +np.float64,0x3ff66a33988cd467,0x3febc656da46a1ca,1 +np.float64,0x3fff0af2eb1e15e6,0x3ff481dec325f5c8,1 +np.float64,0x3ff6a0233d0d4046,0x3fec33400958eda1,1 +np.float64,0x7fdb11e2d5b623c5,0x40862cec55e405f9,1 +np.float64,0x3ffb8a015ad71402,0x3ff2374d7b563a72,1 +np.float64,0x3ff1807d8ce300fb,0x3fdb849e4bce8335,1 +np.float64,0x3ffefd535e3dfaa6,0x3ff479aaac6ffe79,1 +np.float64,0x3ff701e23a6e03c4,0x3fecf39072d96fc7,1 +np.float64,0x3ff4ac809f895901,0x3fe7e6598f2335a5,1 +np.float64,0x3ff0309f26a0613e,0x3fc3b3f4b2783690,1 +np.float64,0x3ff241dd0ce483ba,0x3fe0cde2cb639144,1 +np.float64,0x3ffabce63fb579cc,0x3ff1a18fe2a2da59,1 +np.float64,0x3ffd84b967db0973,0x3ff38ee4f240645d,1 +np.float64,0x7fc3f88b9a27f116,0x408624f1e10cdf3f,1 +np.float64,0x7fe1d5fd5923abfa,0x40862f2175714a3a,1 +np.float64,0x7fe487b145690f62,0x4086304190700183,1 +np.float64,0x7fe7997feaef32ff,0x4086315eeefdddd2,1 +np.float64,0x3ff8f853b671f0a8,0x3ff03c907353a8da,1 +np.float64,0x7fca4c23b5349846,0x408627257ace5778,1 +np.float64,0x7fe0c9bf3a21937d,0x40862ea576c3ea43,1 +np.float64,0x7fc442b389288566,0x4086250f5f126ec9,1 +np.float64,0x7fc6d382ed2da705,0x40862603900431b0,1 +np.float64,0x7fe40b069068160c,0x4086301066468124,1 +np.float64,0x3ff7f62a146fec54,0x3feeba8dfc4363fe,1 +np.float64,0x3ff721e8e94e43d2,0x3fed313a6755d34f,1 +np.float64,0x7fe579feaf2af3fc,0x4086309ddefb6112,1 +np.float64,0x3ffe2c6bde5c58d8,0x3ff3f9665dc9a16e,1 +np.float64,0x7fcf9998ed3f3331,0x4086289dab274788,1 +np.float64,0x7fdb03af2236075d,0x40862ce82252e490,1 +np.float64,0x7fe72799392e4f31,0x40863137f428ee71,1 +np.float64,0x7f9f2190603e4320,0x408617dc5b3b3c3c,1 +np.float64,0x3ff69c56d52d38ae,0x3fec2ba59fe938b2,1 +np.float64,0x7fdcde27bf39bc4e,0x40862d70086cd06d,1 +np.float64,0x3ff654d6b8eca9ae,0x3feb9aa0107609a6,1 +np.float64,0x7fdf69d967bed3b2,0x40862e1d1c2b94c2,1 +np.float64,0xffefffffffffffff,0xfff8000000000000,1 +np.float64,0x7fedfd073f3bfa0d,0x40863349980c2c8b,1 +np.float64,0x7f7c1856803830ac,0x40860bf312b458c7,1 +np.float64,0x7fe9553f1bb2aa7d,0x408631f0173eadd5,1 +np.float64,0x3ff6e92efc2dd25e,0x3fecc38f98e7e1a7,1 +np.float64,0x7fe9719ac532e335,0x408631f906cd79c3,1 +np.float64,0x3ff60e56ae4c1cad,0x3feb07ef8637ec7e,1 +np.float64,0x3ff0d0803501a100,0x3fd455c0af195a9c,1 +np.float64,0x7fe75248a3eea490,0x40863146a614aec1,1 +np.float64,0x7fdff61ead3fec3c,0x40862e408643d7aa,1 +np.float64,0x7fed4ac7a4fa958e,0x408633197b5cf6ea,1 +np.float64,0x7fe58d44562b1a88,0x408630a5098d1bbc,1 +np.float64,0x7fd89dcdb1b13b9a,0x40862c29c2979288,1 +np.float64,0x3ff205deda240bbe,0x3fdfda67c84fd3a8,1 +np.float64,0x7fdf84c15abf0982,0x40862e23f361923d,1 +np.float64,0x3ffe012b3afc0256,0x3ff3de3dfa5f47ce,1 +np.float64,0x3ffe2f3512dc5e6a,0x3ff3fb245206398e,1 +np.float64,0x7fed6174c2bac2e9,0x4086331faa699617,1 +np.float64,0x3ff1f30f8783e61f,0x3fdf47e06f2c40d1,1 +np.float64,0x3ff590da9eab21b5,0x3fe9f8f7b4baf3c2,1 +np.float64,0x3ffb3ca1eb967944,0x3ff1ff9baf66d704,1 +np.float64,0x7fe50ba9a5aa1752,0x408630745ab7fd3c,1 +np.float64,0x3ff43743a4a86e87,0x3fe6bf7ae80b1dda,1 +np.float64,0x3ff47e1a24e8fc34,0x3fe773acca44c7d6,1 +np.float64,0x3ff589ede9eb13dc,0x3fe9e99f28fab3a4,1 +np.float64,0x3ff72f2cbf8e5e5a,0x3fed4a94e7edbf24,1 +np.float64,0x3ffa4f9bbc549f38,0x3ff14ee60aea45d3,1 +np.float64,0x3ff975dae732ebb6,0x3ff0a3a1fbd7284a,1 +np.float64,0x7fbcf14ee039e29d,0x4086225e33f3793e,1 +np.float64,0x3ff10e027f621c05,0x3fd71cce2452b4e0,1 +np.float64,0x3ff33ea193067d43,0x3fe40cbac4daaddc,1 +np.float64,0x7fbef8f2263df1e3,0x408622e905c8e1b4,1 +np.float64,0x3fff7f5bfe3efeb8,0x3ff4c732e83df253,1 +np.float64,0x3ff5700a6b4ae015,0x3fe9afdd7b8b82b0,1 +np.float64,0x3ffd5099da5aa134,0x3ff36d1bf26e55bf,1 +np.float64,0x3ffed8e0f89db1c2,0x3ff4639ff065107a,1 +np.float64,0x3fff9d0c463f3a18,0x3ff4d8a9f297cf52,1 +np.float64,0x3ff23db5b2e47b6b,0x3fe0bebdd48f961a,1 +np.float64,0x3ff042bff1e08580,0x3fc713bf24cc60ef,1 +np.float64,0x7feb4fe97a769fd2,0x4086328a26675646,1 +np.float64,0x3ffeafbfeedd5f80,0x3ff44a955a553b1c,1 +np.float64,0x3ff83fb524507f6a,0x3fef3d1729ae0976,1 +np.float64,0x3ff1992294433245,0x3fdc5f5ce53dd197,1 +np.float64,0x7fe89fe629b13fcb,0x408631b601a83867,1 +np.float64,0x7fe53e4d74aa7c9a,0x40863087839b52f1,1 +np.float64,0x3ff113713e6226e2,0x3fd757631ca7cd09,1 +np.float64,0x7fd4a0b7a629416e,0x40862abfba27a09b,1 +np.float64,0x3ff184c6e2a3098e,0x3fdbab2e3966ae57,1 +np.float64,0x3ffafbbf77f5f77f,0x3ff1d02bb331d9f9,1 +np.float64,0x3ffc6099a358c134,0x3ff2cd16941613d1,1 +np.float64,0x3ffb7c441ef6f888,0x3ff22d7b12e31432,1 +np.float64,0x3ff625ba5eec4b75,0x3feb39060e55fb79,1 +np.float64,0x7fde879acbbd0f35,0x40862de2aab4d72d,1 +np.float64,0x7f930aed982615da,0x408613edb6df8528,1 +np.float64,0x7fa4b82dac29705a,0x40861a261c0a9aae,1 +np.float64,0x7fced5c16b3dab82,0x4086286b7a73e611,1 +np.float64,0x7fe133749d2266e8,0x40862ed73a41b112,1 +np.float64,0x3ff2d8146ea5b029,0x3fe2ced55dbf997d,1 +np.float64,0x3ff60dac77ac1b59,0x3feb0688b0e54c7b,1 +np.float64,0x3ff275d9b024ebb3,0x3fe186b87258b834,1 +np.float64,0x3ff533e6500a67cd,0x3fe92746c8b50ddd,1 +np.float64,0x7fe370896666e112,0x40862fd1ca144736,1 +np.float64,0x7fee7695357ced29,0x40863369c459420e,1 +np.float64,0x7fd1e0528023c0a4,0x4086299a85caffd0,1 +np.float64,0x7fd05c7b24a0b8f5,0x408628e52824386f,1 +np.float64,0x3ff11dcc3b023b98,0x3fd7c56c8cef1be1,1 +np.float64,0x7fc9d9fae933b3f5,0x408627027404bc5f,1 +np.float64,0x7fe2359981246b32,0x40862f4be675e90d,1 +np.float64,0x3ffb10a949962152,0x3ff1df88f83b8cde,1 +np.float64,0x3ffa65b53654cb6a,0x3ff15fc8956ccc87,1 +np.float64,0x3ff0000000000000,0x0,1 +np.float64,0x7fad97ef703b2fde,0x40861d002f3d02da,1 +np.float64,0x3ff57aaf93aaf55f,0x3fe9c7b01f194edb,1 +np.float64,0x7fe9ecd73f33d9ad,0x4086321f69917205,1 +np.float64,0x3ff0dcb79c61b96f,0x3fd4eac86a7a9c38,1 +np.float64,0x7fee9c12ffbd3825,0x4086337396cd706d,1 +np.float64,0x3ff52c40af4a5881,0x3fe915a8a7de8f00,1 +np.float64,0x3ffbcfff59779ffe,0x3ff268e523fe8dda,1 +np.float64,0x7fe014cb4b602996,0x40862e4d5de42a03,1 +np.float64,0x7fae2370e83c46e1,0x40861d258dd5b3ee,1 +np.float64,0x7fe9e33602f3c66b,0x4086321c704ac2bb,1 +np.float64,0x3ff648acd74c915a,0x3feb8195ca53bcaa,1 +np.float64,0x7fe385f507670be9,0x40862fda95ebaf44,1 +np.float64,0x3ffb0e382c361c70,0x3ff1ddbea963e0a7,1 +np.float64,0x3ff47d6b6ae8fad7,0x3fe771f80ad37cd2,1 +np.float64,0x3ffca7d538f94faa,0x3ff2fd5f62e851ac,1 +np.float64,0x3ff83e949c107d29,0x3fef3b1c5bbac99b,1 +np.float64,0x7fc6fb933a2df725,0x408626118e51a286,1 +np.float64,0x7fe43a1454e87428,0x4086302318512d9b,1 +np.float64,0x7fe51fe32aaa3fc5,0x4086307c07271348,1 +np.float64,0x3ff35e563966bcac,0x3fe46aa2856ef85f,1 +np.float64,0x3ff84dd4e4909baa,0x3fef55d86d1d5c2e,1 +np.float64,0x7febe3d84077c7b0,0x408632b507686f03,1 +np.float64,0x3ff6aca2e32d5946,0x3fec4c32a2368ee3,1 +np.float64,0x7fe7070e3e6e0e1b,0x4086312caddb0454,1 +np.float64,0x7fd3657f2aa6cafd,0x40862a41acf47e70,1 +np.float64,0x3ff61534456c2a68,0x3feb1663900af13b,1 +np.float64,0x3ff8bc556eb178ab,0x3ff00a16b5403f88,1 +np.float64,0x3ffa7782e3f4ef06,0x3ff16d529c94a438,1 +np.float64,0x7fc15785ed22af0b,0x408623d0cd94fb86,1 +np.float64,0x3ff2e3eeb6e5c7dd,0x3fe2f4c4876d3edf,1 +np.float64,0x3ff2e4e17e85c9c3,0x3fe2f7c9e437b22e,1 +np.float64,0x7feb3aaf67f6755e,0x40863283ec4a0d76,1 +np.float64,0x7fe89efcf7313df9,0x408631b5b5e41263,1 +np.float64,0x3ffcc6fad4f98df6,0x3ff31245778dff6d,1 +np.float64,0x3ff356114466ac22,0x3fe45253d040a024,1 +np.float64,0x3ff81c70d2d038e2,0x3feefed71ebac776,1 +np.float64,0x7fdb75c96136eb92,0x40862d09a603f03e,1 +np.float64,0x3ff340f91b8681f2,0x3fe413bb6e6d4a54,1 +np.float64,0x3fff906079df20c1,0x3ff4d13869d16bc7,1 +np.float64,0x3ff226a42d644d48,0x3fe0698d316f1ac0,1 +np.float64,0x3ff948abc3b29158,0x3ff07eeb0b3c81ba,1 +np.float64,0x3ffc25df1fb84bbe,0x3ff2a4c13ad4edad,1 +np.float64,0x7fe07ea3b960fd46,0x40862e815b4cf43d,1 +np.float64,0x3ff497d3dae92fa8,0x3fe7b3917bf10311,1 +np.float64,0x7fea561db1f4ac3a,0x4086323fa4aef2a9,1 +np.float64,0x7fd1b49051236920,0x40862986d8759ce5,1 +np.float64,0x7f7ba3bd6037477a,0x40860bd19997fd90,1 +np.float64,0x3ff01126dd00224e,0x3fb76b67938dfb11,1 +np.float64,0x3ff29e1105053c22,0x3fe2102a4c5fa102,1 +np.float64,0x3ff9de2a6553bc55,0x3ff0f6cfe4dea30e,1 +np.float64,0x7fc558e7d42ab1cf,0x4086257a608fc055,1 +np.float64,0x3ff79830a74f3061,0x3fee0f93db153d65,1 +np.float64,0x7fe2661648e4cc2c,0x40862f6117a71eb2,1 +np.float64,0x3ff140cf4262819e,0x3fd92aefedae1ab4,1 +np.float64,0x3ff5f36251abe6c5,0x3feaced481ceaee3,1 +np.float64,0x7fc80911d5301223,0x4086266d4757f768,1 +np.float64,0x3ff9079a6c320f35,0x3ff04949d21ebe1e,1 +np.float64,0x3ffde8d2e09bd1a6,0x3ff3cedca8a5db5d,1 +np.float64,0x3ffadd1de375ba3c,0x3ff1b989790e8d93,1 +np.float64,0x3ffdbc40ee1b7882,0x3ff3b286b1c7da57,1 +np.float64,0x3ff8ff514771fea2,0x3ff04264add00971,1 +np.float64,0x7fefd7d0e63fafa1,0x408633c47d9f7ae4,1 +np.float64,0x3ffc47798c588ef3,0x3ff2bbe441fa783a,1 +np.float64,0x7fe6ebc55b6dd78a,0x408631232d9abf31,1 +np.float64,0xbff0000000000000,0xfff8000000000000,1 +np.float64,0x7fd378e4afa6f1c8,0x40862a49a8f98cb4,1 +np.float64,0x0,0xfff8000000000000,1 +np.float64,0x3ffe88ed7efd11db,0x3ff432c7ecb95492,1 +np.float64,0x3ff4f5509289eaa1,0x3fe8955a11656323,1 +np.float64,0x7fda255b41344ab6,0x40862ca53676a23e,1 +np.float64,0x3ffebe85b9bd7d0c,0x3ff453992cd55dea,1 +np.float64,0x3ff5d6180b8bac30,0x3fea901c2160c3bc,1 +np.float64,0x3ffcdfb8fcf9bf72,0x3ff322c83b3bc735,1 +np.float64,0x3ff3c91c26679238,0x3fe599a652b7cf59,1 +np.float64,0x7fc389f7a62713ee,0x408624c518edef93,1 +np.float64,0x3ffe1245ba1c248c,0x3ff3e901b2c4a47a,1 +np.float64,0x7fe1e76e95e3cedc,0x40862f29446f9eff,1 +np.float64,0x3ff02ae4f92055ca,0x3fc28221abd63daa,1 +np.float64,0x7fbf648a143ec913,0x40862304a0619d03,1 +np.float64,0x3ff2be7ef8657cfe,0x3fe27bcc6c97522e,1 +np.float64,0x3ffa7595e514eb2c,0x3ff16bdc64249ad1,1 +np.float64,0x3ff4ee130049dc26,0x3fe884354cbad8c9,1 +np.float64,0x3ff19211fc232424,0x3fdc2160bf3eae40,1 +np.float64,0x3ffec215aedd842c,0x3ff455c4cdd50c32,1 +np.float64,0x7fe7cb50ffaf96a1,0x4086316fc06a53af,1 +np.float64,0x3fffa679161f4cf2,0x3ff4de30ba7ac5b8,1 +np.float64,0x7fdcb459763968b2,0x40862d646a21011d,1 +np.float64,0x3ff9f338d6d3e672,0x3ff1075835d8f64e,1 +np.float64,0x3ff8de3319d1bc66,0x3ff026ae858c0458,1 +np.float64,0x7fee0199d33c0333,0x4086334ad03ac683,1 +np.float64,0x3ffc06076c380c0f,0x3ff28eaec3814faa,1 +np.float64,0x3ffe9e2e235d3c5c,0x3ff43fd4d2191a7f,1 +np.float64,0x3ffd93b06adb2761,0x3ff398888239cde8,1 +np.float64,0x7fefe4b71cffc96d,0x408633c7ba971b92,1 +np.float64,0x7fb2940352252806,0x40861ed244bcfed6,1 +np.float64,0x3ffba4647e3748c9,0x3ff24a15f02e11b9,1 +np.float64,0x7fd2d9543725b2a7,0x40862a0708446596,1 +np.float64,0x7fc04997f120932f,0x4086235055d35251,1 +np.float64,0x3ff6d14313ada286,0x3fec94b177f5d3fc,1 +np.float64,0x3ff279fc8684f3f9,0x3fe19511c3e5b9a8,1 +np.float64,0x3ff42f4609085e8c,0x3fe6aabe526ce2bc,1 +np.float64,0x7fc1c6c62a238d8b,0x408624037de7f6ec,1 +np.float64,0x7fe31ff4b8e63fe8,0x40862fb05b40fd16,1 +np.float64,0x7fd2a8825fa55104,0x408629f234d460d6,1 +np.float64,0x3ffe8c1d725d183b,0x3ff434bdc444143f,1 +np.float64,0x3ff0e9dc3e21d3b8,0x3fd58676e2c13fc9,1 +np.float64,0x3ffed03172fda063,0x3ff45e59f7aa6c8b,1 +np.float64,0x7fd74621962e8c42,0x40862bb6e90d66f8,1 +np.float64,0x3ff1faa29663f545,0x3fdf833a2c5efde1,1 +np.float64,0x7fda02834db40506,0x40862c9a860d6747,1 +np.float64,0x7f709b2fc021365f,0x408607be328eb3eb,1 +np.float64,0x7fec0d58aa381ab0,0x408632c0e61a1af6,1 +np.float64,0x3ff524d1720a49a3,0x3fe90479968d40fd,1 +np.float64,0x7fd64cb3b32c9966,0x40862b5f53c4b0b4,1 +np.float64,0x3ff9593e3ed2b27c,0x3ff08c6eea5f6e8b,1 +np.float64,0x3ff7de8b1f6fbd16,0x3fee9007abcfdf7b,1 +np.float64,0x7fe8d816d6b1b02d,0x408631c82e38a894,1 +np.float64,0x7fd726bbe22e4d77,0x40862bac16ee8d52,1 +np.float64,0x7fa70b07d42e160f,0x40861affcc4265e2,1 +np.float64,0x7fe18b4091e31680,0x40862effa8bce66f,1 +np.float64,0x3ff830253010604a,0x3fef21b2eaa75758,1 +np.float64,0x3fffcade407f95bc,0x3ff4f3734b24c419,1 +np.float64,0x3ff8c17cecb182fa,0x3ff00e75152d7bda,1 +np.float64,0x7fdad9b9d035b373,0x40862cdbabb793ba,1 +np.float64,0x3ff9f9e154f3f3c2,0x3ff10c8dfdbd2510,1 +np.float64,0x3ff465e162e8cbc3,0x3fe736c751c75b73,1 +np.float64,0x3ff9b4cd8493699b,0x3ff0d616235544b8,1 +np.float64,0x7fe557c4a56aaf88,0x4086309114ed12d9,1 +np.float64,0x7fe5999133eb3321,0x408630a9991a9b54,1 +np.float64,0x7fe7c9009e2f9200,0x4086316ef9359a47,1 +np.float64,0x3ff8545cabd0a8ba,0x3fef6141f1030c36,1 +np.float64,0x3ffa1f1712943e2e,0x3ff129849d492ce3,1 +np.float64,0x7fea803a14750073,0x4086324c652c276c,1 +np.float64,0x3ff5b6f97fcb6df3,0x3fea4cb0b97b18e9,1 +np.float64,0x7fc2efdfc425dfbf,0x40862485036a5c6e,1 +np.float64,0x7fe2c78e5be58f1c,0x40862f8b0a5e7baf,1 +np.float64,0x7fe80d7fff301aff,0x40863185e234060a,1 +np.float64,0x3ffd895d457b12ba,0x3ff391e2cac7a3f8,1 +np.float64,0x3ff44c9764a8992f,0x3fe6f6690396c232,1 +np.float64,0x3ff731688b8e62d1,0x3fed4ed70fac3839,1 +np.float64,0x3ff060200460c040,0x3fcbad4a07d97f0e,1 +np.float64,0x3ffbd2f70a17a5ee,0x3ff26afb46ade929,1 +np.float64,0x7febe9e841f7d3d0,0x408632b6c465ddd9,1 +np.float64,0x3ff2532f8be4a65f,0x3fe10c6cd8d64cf4,1 +np.float64,0x7fefffffffffffff,0x408633ce8fb9f87e,1 +np.float64,0x3ff3a1ae3a47435c,0x3fe52c00210cc459,1 +np.float64,0x7fe9c34ae6b38695,0x408632128d150149,1 +np.float64,0x3fff311029fe6220,0x3ff498b852f30bff,1 +np.float64,0x3ffd4485a1ba890c,0x3ff3653b6fa701cd,1 +np.float64,0x7fd52718b1aa4e30,0x40862af330d9c68c,1 +np.float64,0x3ff10b695a4216d3,0x3fd7009294e367b7,1 +np.float64,0x3ffdf73de59bee7c,0x3ff3d7fa96d2c1ae,1 +np.float64,0x3ff2f1c75965e38f,0x3fe320aaff3db882,1 +np.float64,0x3ff2a56a5a854ad5,0x3fe228cc4ad7e7a5,1 +np.float64,0x7fe60cd1cf6c19a3,0x408630d3d87a04b3,1 +np.float64,0x3ff89fa65c113f4c,0x3fefe3543773180c,1 +np.float64,0x3ffd253130ba4a62,0x3ff350b76ba692a0,1 +np.float64,0x7feaad7051f55ae0,0x40863259ff932d62,1 +np.float64,0x7fd9cc37cf33986f,0x40862c89c15f963b,1 +np.float64,0x3ff8c08de771811c,0x3ff00daa9c17acd7,1 +np.float64,0x7fea58b25d34b164,0x408632406d54cc6f,1 +np.float64,0x7fe5f161fd2be2c3,0x408630c9ddf272a5,1 +np.float64,0x3ff5840dbf8b081c,0x3fe9dc9117b4cbc7,1 +np.float64,0x3ff3fd762307faec,0x3fe6277cd530c640,1 +np.float64,0x3ff9095c98b212b9,0x3ff04abff170ac24,1 +np.float64,0x7feaac66017558cb,0x40863259afb4f8ce,1 +np.float64,0x7fd78f96bcaf1f2c,0x40862bd00175fdf9,1 +np.float64,0x3ffaca27e0959450,0x3ff1ab72b8f8633e,1 +np.float64,0x3ffb7f18cb96fe32,0x3ff22f81bcb8907b,1 +np.float64,0x3ffcce48d1199c92,0x3ff317276f62c0b2,1 +np.float64,0x3ffcb9a7f3797350,0x3ff30958e0d6a34d,1 +np.float64,0x7fda569ef6b4ad3d,0x40862cb43b33275a,1 +np.float64,0x7fde9f0893bd3e10,0x40862de8cc036283,1 +np.float64,0x3ff428be3928517c,0x3fe699bb5ab58904,1 +np.float64,0x7fa4d3344029a668,0x40861a3084989291,1 +np.float64,0x3ff03607bd006c0f,0x3fc4c4840cf35f48,1 +np.float64,0x3ff2b1335c056267,0x3fe25000846b75a2,1 +np.float64,0x7fe0cb8bd8e19717,0x40862ea65237d496,1 +np.float64,0x3fff4b1b7b9e9637,0x3ff4a83fb08e7b24,1 +np.float64,0x7fe7526140aea4c2,0x40863146ae86069c,1 +np.float64,0x7fbfcfb7c23f9f6f,0x4086231fc246ede5,1 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-arcsin.csv b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-arcsin.csv new file mode 100644 index 0000000000000000000000000000000000000000..75d57072158bea7927824b07efb48352ae85b35b --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-arcsin.csv @@ -0,0 +1,1429 @@ +dtype,input,output,ulperrortol +np.float32,0xbe7d3a7c,0xbe7fe217,4 +np.float32,0x3dc102f0,0x3dc14c60,4 +np.float32,0xbe119c28,0xbe121aef,4 +np.float32,0xbe51cd68,0xbe534c75,4 +np.float32,0x3c04a300,0x3c04a35f,4 +np.float32,0xbf4f0b62,0xbf712a69,4 +np.float32,0x3ef61a5c,0x3f005cf6,4 +np.float32,0xbf13024c,0xbf1c97df,4 +np.float32,0x3e93b580,0x3e95d6b5,4 +np.float32,0x3e44e7b8,0x3e4623a5,4 +np.float32,0xbe35df20,0xbe36d773,4 +np.float32,0x3eecd2c0,0x3ef633cf,4 +np.float32,0x3f2772ba,0x3f36862a,4 +np.float32,0x3e211ea8,0x3e21cac5,4 +np.float32,0x3e3b3d90,0x3e3c4cc6,4 +np.float32,0x3f37c962,0x3f4d018c,4 +np.float32,0x3e92ad88,0x3e94c31a,4 +np.float32,0x3f356ffc,0x3f49a766,4 +np.float32,0x3f487ba2,0x3f665254,4 +np.float32,0x3f061c46,0x3f0d27ae,4 +np.float32,0xbee340a2,0xbeeb7722,4 +np.float32,0xbe85aede,0xbe874026,4 +np.float32,0x3f34cf9a,0x3f48c474,4 +np.float32,0x3e29a690,0x3e2a6fbd,4 +np.float32,0xbeb29428,0xbeb669d1,4 +np.float32,0xbe606d40,0xbe624370,4 +np.float32,0x3dae6860,0x3dae9e85,4 +np.float32,0xbf04872b,0xbf0b4d25,4 +np.float32,0x3f2080e2,0x3f2d7ab0,4 +np.float32,0xbec77dcc,0xbecceb27,4 +np.float32,0x3e0dda10,0x3e0e4f38,4 +np.float32,0xbefaf970,0xbf03262c,4 +np.float32,0x3f576a0c,0x3f7ffee6,4 +np.float32,0x3f222382,0x3f2f95d6,4 +np.float32,0x7fc00000,0x7fc00000,4 +np.float32,0x3e41c468,0x3e42f14e,4 +np.float32,0xbf2f64dd,0xbf4139a8,4 +np.float32,0xbf60ef90,0xbf895956,4 +np.float32,0xbf67c855,0xbf90eff0,4 +np.float32,0xbed35aee,0xbed9df00,4 +np.float32,0xbf2c7d92,0xbf3d448f,4 +np.float32,0x3f7b1604,0x3faff122,4 +np.float32,0xbf7c758b,0xbfb3bf87,4 +np.float32,0x3ecda1c8,0x3ed39acf,4 +np.float32,0x3f3af8ae,0x3f519fcb,4 +np.float32,0xbf16e6a3,0xbf2160fd,4 +np.float32,0x3f0c97d2,0x3f14d668,4 +np.float32,0x3f0a8060,0x3f1257b9,4 +np.float32,0x3f27905a,0x3f36ad57,4 +np.float32,0x3eeaeba4,0x3ef40efe,4 +np.float32,0x3e58dde0,0x3e5a8580,4 +np.float32,0xbf0cabe2,0xbf14ee6b,4 +np.float32,0xbe805ca8,0xbe81bf03,4 +np.float32,0x3f5462ba,0x3f7a7b85,4 +np.float32,0xbee235d0,0xbeea4d8b,4 +np.float32,0xbe880cb0,0xbe89b426,4 +np.float32,0x80000001,0x80000001,4 +np.float32,0x3f208c00,0x3f2d88f6,4 +np.float32,0xbf34f3d2,0xbf48f7a2,4 +np.float32,0x3f629428,0x3f8b1763,4 +np.float32,0xbf52a900,0xbf776b4a,4 +np.float32,0xbd17f8d0,0xbd1801be,4 +np.float32,0xbef7cada,0xbf0153d1,4 +np.float32,0x3f7d3b90,0x3fb63967,4 +np.float32,0xbd6a20b0,0xbd6a4160,4 +np.float32,0x3f740496,0x3fa1beb7,4 +np.float32,0x3ed8762c,0x3edf7dd9,4 +np.float32,0x3f53b066,0x3f793d42,4 +np.float32,0xbe9de718,0xbea084f9,4 +np.float32,0x3ea3ae90,0x3ea69b4b,4 +np.float32,0x3f1b8f00,0x3f273183,4 +np.float32,0x3f5cd6ac,0x3f852ead,4 +np.float32,0x3f29d510,0x3f39b169,4 +np.float32,0x3ee2a934,0x3eeace33,4 +np.float32,0x3eecac94,0x3ef608c2,4 +np.float32,0xbea915e2,0xbeac5203,4 +np.float32,0xbd316e90,0xbd317cc8,4 +np.float32,0xbf70b495,0xbf9c97b6,4 +np.float32,0xbe80d976,0xbe823ff3,4 +np.float32,0x3e9205f8,0x3e94143f,4 +np.float32,0x3f49247e,0x3f676296,4 +np.float32,0x3d9030c0,0x3d904f50,4 +np.float32,0x3e4df058,0x3e4f5a5c,4 +np.float32,0xbe1fd360,0xbe207b58,4 +np.float32,0xbf69dc7c,0xbf937006,4 +np.float32,0x3f36babe,0x3f4b7df3,4 +np.float32,0xbe8c9758,0xbe8e6bb7,4 +np.float32,0xbf4de72d,0xbf6f3c20,4 +np.float32,0xbecdad68,0xbed3a780,4 +np.float32,0xbf73e2cf,0xbfa18702,4 +np.float32,0xbece16a8,0xbed41a75,4 +np.float32,0x3f618a96,0x3f89fc6d,4 +np.float32,0xbf325853,0xbf454ea9,4 +np.float32,0x3f138568,0x3f1d3828,4 +np.float32,0xbf56a6e9,0xbf7e9748,4 +np.float32,0x3ef5d594,0x3f0035bf,4 +np.float32,0xbf408220,0xbf59dfaa,4 +np.float32,0xbed120e6,0xbed76dd5,4 +np.float32,0xbf6dbda5,0xbf986cee,4 +np.float32,0x3f744a38,0x3fa23282,4 +np.float32,0xbe4b56d8,0xbe4cb329,4 +np.float32,0x3f54c5f2,0x3f7b2d97,4 +np.float32,0xbd8b1c90,0xbd8b3801,4 +np.float32,0x3ee19a48,0x3ee9a03b,4 +np.float32,0x3f48460e,0x3f65fc3d,4 +np.float32,0x3eb541c0,0x3eb9461e,4 +np.float32,0xbea7d098,0xbeaaf98c,4 +np.float32,0xbda99e40,0xbda9d00c,4 +np.float32,0xbefb2ca6,0xbf03438d,4 +np.float32,0x3f4256be,0x3f5cab0b,4 +np.float32,0xbdbdb198,0xbdbdf74d,4 +np.float32,0xbf325b5f,0xbf4552e9,4 +np.float32,0xbf704d1a,0xbf9c00b4,4 +np.float32,0x3ebb1d04,0x3ebf8cf8,4 +np.float32,0xbed03566,0xbed66bf1,4 +np.float32,0x3e8fcee8,0x3e91c501,4 +np.float32,0xbf2e1eec,0xbf3f7b9d,4 +np.float32,0x3f33c4d2,0x3f474cac,4 +np.float32,0x3f598ef4,0x3f8201b4,4 +np.float32,0x3e09bb30,0x3e0a2660,4 +np.float32,0x3ed4e228,0x3edb8cdb,4 +np.float32,0x3eb7a190,0x3ebbd0a1,4 +np.float32,0xbd9ae630,0xbd9b0c18,4 +np.float32,0x3f43020e,0x3f5db2d7,4 +np.float32,0xbec06ac0,0xbec542d4,4 +np.float32,0x3f3dfde0,0x3f561674,4 +np.float32,0xbf64084a,0xbf8cabe6,4 +np.float32,0xbd6f95b0,0xbd6fb8b7,4 +np.float32,0x3f268640,0x3f354e2d,4 +np.float32,0xbe72b4bc,0xbe7509b2,4 +np.float32,0xbf3414fa,0xbf47bd5a,4 +np.float32,0xbf375218,0xbf4c566b,4 +np.float32,0x3f203c1a,0x3f2d2273,4 +np.float32,0xbd503530,0xbd504c2b,4 +np.float32,0xbc45e540,0xbc45e67b,4 +np.float32,0xbf175c4f,0xbf21f2c6,4 +np.float32,0x3f7432a6,0x3fa20b2b,4 +np.float32,0xbf43367f,0xbf5e03d8,4 +np.float32,0x3eb3997c,0x3eb780c4,4 +np.float32,0x3e5574c8,0x3e570878,4 +np.float32,0xbf04b57b,0xbf0b8349,4 +np.float32,0x3f6216d8,0x3f8a914b,4 +np.float32,0xbf57a237,0xbf80337d,4 +np.float32,0xbee1403a,0xbee93bee,4 +np.float32,0xbeaf9b9a,0xbeb33f3b,4 +np.float32,0xbf109374,0xbf19a223,4 +np.float32,0xbeae6824,0xbeb1f810,4 +np.float32,0xbcff9320,0xbcff9dbe,4 +np.float32,0x3ed205c0,0x3ed868a9,4 +np.float32,0x3d897c30,0x3d8996ad,4 +np.float32,0xbf2899d2,0xbf380d4c,4 +np.float32,0xbf54cb0b,0xbf7b36c2,4 +np.float32,0x3ea8e8ec,0x3eac2262,4 +np.float32,0x3ef5e1a0,0x3f003c9d,4 +np.float32,0xbf00c81e,0xbf06f1e2,4 +np.float32,0xbf346775,0xbf483181,4 +np.float32,0x3f7a4fe4,0x3fae077c,4 +np.float32,0x3f00776e,0x3f06948f,4 +np.float32,0xbe0a3078,0xbe0a9cbc,4 +np.float32,0xbeba0b06,0xbebe66be,4 +np.float32,0xbdff4e38,0xbdfff8b2,4 +np.float32,0xbe927f70,0xbe9492ff,4 +np.float32,0x3ebb07e0,0x3ebf7642,4 +np.float32,0x3ebcf8e0,0x3ec18c95,4 +np.float32,0x3f49bdfc,0x3f685b51,4 +np.float32,0x3cbc29c0,0x3cbc2dfd,4 +np.float32,0xbe9e951a,0xbea13bf1,4 +np.float32,0xbe8c237c,0xbe8df33d,4 +np.float32,0x3e17f198,0x3e1881c4,4 +np.float32,0xbd0b5220,0xbd0b5902,4 +np.float32,0xbf34c4a2,0xbf48b4f5,4 +np.float32,0xbedaa814,0xbee1ea94,4 +np.float32,0x3ebf5d6c,0x3ec42053,4 +np.float32,0x3cd04b40,0x3cd050ff,4 +np.float32,0xbec33fe0,0xbec85244,4 +np.float32,0xbf00b27a,0xbf06d8d8,4 +np.float32,0x3f15d7be,0x3f201243,4 +np.float32,0xbe3debd0,0xbe3f06f7,4 +np.float32,0xbea81704,0xbeab4418,4 +np.float32,0x1,0x1,4 +np.float32,0x3f49e6ba,0x3f689d8b,4 +np.float32,0x3f351030,0x3f491fc0,4 +np.float32,0x3e607de8,0x3e625482,4 +np.float32,0xbe8dbbe4,0xbe8f9c0e,4 +np.float32,0x3edbf350,0x3ee35924,4 +np.float32,0xbf0c84c4,0xbf14bf9c,4 +np.float32,0x3eb218b0,0x3eb5e61a,4 +np.float32,0x3e466dd0,0x3e47b138,4 +np.float32,0xbe8ece94,0xbe90ba01,4 +np.float32,0xbe82ec2a,0xbe84649a,4 +np.float32,0xbf7e1f10,0xbfb98b9e,4 +np.float32,0xbf2d00ea,0xbf3df688,4 +np.float32,0x3db7cdd0,0x3db80d36,4 +np.float32,0xbe388b98,0xbe398f25,4 +np.float32,0xbd86cb40,0xbd86e436,4 +np.float32,0x7f7fffff,0x7fc00000,4 +np.float32,0x3f472a60,0x3f6436c6,4 +np.float32,0xbf5b2c1d,0xbf838d87,4 +np.float32,0x3f0409ea,0x3f0abad8,4 +np.float32,0x3f47dd0e,0x3f6553f0,4 +np.float32,0x3e3eab00,0x3e3fc98a,4 +np.float32,0xbf7c2a7f,0xbfb2e19b,4 +np.float32,0xbeda0048,0xbee13112,4 +np.float32,0x3f46600a,0x3f62f5b2,4 +np.float32,0x3f45aef4,0x3f61de43,4 +np.float32,0x3dd40a50,0x3dd46bc4,4 +np.float32,0xbf6cdd0b,0xbf974191,4 +np.float32,0x3f78de4c,0x3faac725,4 +np.float32,0x3f3c39a4,0x3f53777f,4 +np.float32,0xbe2a30ec,0xbe2afc0b,4 +np.float32,0xbf3c0ef0,0xbf533887,4 +np.float32,0x3ecb6548,0x3ed12a53,4 +np.float32,0x3eb994e8,0x3ebde7fc,4 +np.float32,0x3d4c1ee0,0x3d4c3487,4 +np.float32,0xbf52cb6d,0xbf77a7eb,4 +np.float32,0x3eb905d4,0x3ebd4e80,4 +np.float32,0x3e712428,0x3e736d72,4 +np.float32,0xbf79ee6e,0xbfad22be,4 +np.float32,0x3de6f8b0,0x3de776c1,4 +np.float32,0x3e9b2898,0x3e9da325,4 +np.float32,0x3ea09b20,0x3ea35d20,4 +np.float32,0x3d0ea9a0,0x3d0eb103,4 +np.float32,0xbd911500,0xbd913423,4 +np.float32,0x3e004618,0x3e009c97,4 +np.float32,0x3f5e0e5a,0x3f86654c,4 +np.float32,0x3f2e6300,0x3f3fd88b,4 +np.float32,0x3e0cf5d0,0x3e0d68c3,4 +np.float32,0x3d6a16c0,0x3d6a376c,4 +np.float32,0x3f7174aa,0x3f9db53c,4 +np.float32,0xbe04bba0,0xbe051b81,4 +np.float32,0xbe6fdcb4,0xbe721c92,4 +np.float32,0x3f4379f0,0x3f5e6c31,4 +np.float32,0xbf680098,0xbf913257,4 +np.float32,0xbf3c31ca,0xbf536bea,4 +np.float32,0x3f59db58,0x3f824a4e,4 +np.float32,0xbf3ffc84,0xbf591554,4 +np.float32,0x3d1d5160,0x3d1d5b48,4 +np.float32,0x3f6c64ae,0x3f96a3da,4 +np.float32,0xbf1b49fd,0xbf26daaa,4 +np.float32,0x3ec80be0,0x3ecd8576,4 +np.float32,0x3f3becc0,0x3f530629,4 +np.float32,0xbea93890,0xbeac76c1,4 +np.float32,0x3f5b3acc,0x3f839bbd,4 +np.float32,0xbf5d6818,0xbf85bef9,4 +np.float32,0x3f794266,0x3fab9fa6,4 +np.float32,0xbee8eb7c,0xbef1cf3b,4 +np.float32,0xbf360a06,0xbf4a821e,4 +np.float32,0x3f441cf6,0x3f5f693d,4 +np.float32,0x3e60de40,0x3e62b742,4 +np.float32,0xbebb3d7e,0xbebfafdc,4 +np.float32,0x3e56a3a0,0x3e583e28,4 +np.float32,0x3f375bfe,0x3f4c6499,4 +np.float32,0xbf384d7d,0xbf4dbf9a,4 +np.float32,0x3efb03a4,0x3f032c06,4 +np.float32,0x3f1d5d10,0x3f29794d,4 +np.float32,0xbe25f7dc,0xbe26b41d,4 +np.float32,0x3f6d2f88,0x3f97aebb,4 +np.float32,0xbe9fa100,0xbea255cb,4 +np.float32,0xbf21dafa,0xbf2f382a,4 +np.float32,0x3d3870e0,0x3d3880d9,4 +np.float32,0x3eeaf00c,0x3ef413f4,4 +np.float32,0xbc884ea0,0xbc88503c,4 +np.float32,0xbf7dbdad,0xbfb80b6d,4 +np.float32,0xbf4eb713,0xbf709b46,4 +np.float32,0xbf1c0ad4,0xbf27cd92,4 +np.float32,0x3f323088,0x3f451737,4 +np.float32,0x3e405d88,0x3e4183e1,4 +np.float32,0x3d7ad580,0x3d7afdb4,4 +np.float32,0xbf207338,0xbf2d6927,4 +np.float32,0xbecf7948,0xbed59e1a,4 +np.float32,0x3f16ff94,0x3f217fde,4 +np.float32,0xbdf19588,0xbdf225dd,4 +np.float32,0xbf4d9654,0xbf6eb442,4 +np.float32,0xbf390b9b,0xbf4ed220,4 +np.float32,0xbe155a74,0xbe15e354,4 +np.float32,0x3f519e4c,0x3f759850,4 +np.float32,0xbee3f08c,0xbeec3b84,4 +np.float32,0xbf478be7,0xbf64d23b,4 +np.float32,0xbefdee50,0xbf04d92a,4 +np.float32,0x3e8def78,0x3e8fd1bc,4 +np.float32,0x3e3df2a8,0x3e3f0dee,4 +np.float32,0xbf413e22,0xbf5afd97,4 +np.float32,0xbf1b8bc4,0xbf272d71,4 +np.float32,0xbf31e5be,0xbf44af22,4 +np.float32,0x3de7e080,0x3de86010,4 +np.float32,0xbf5ddf7e,0xbf863645,4 +np.float32,0x3f3eba6a,0x3f57306e,4 +np.float32,0xff7fffff,0x7fc00000,4 +np.float32,0x3ec22d5c,0x3ec72973,4 +np.float32,0x80800000,0x80800000,4 +np.float32,0x3f032e0c,0x3f09ba82,4 +np.float32,0x3d74bd60,0x3d74e2b7,4 +np.float32,0xbea0d61e,0xbea39b42,4 +np.float32,0xbefdfa78,0xbf04e02a,4 +np.float32,0x3e5cb220,0x3e5e70ec,4 +np.float32,0xbe239e54,0xbe2452a4,4 +np.float32,0x3f452738,0x3f61090e,4 +np.float32,0x3e99a2e0,0x3e9c0a66,4 +np.float32,0x3e4394d8,0x3e44ca5f,4 +np.float32,0x3f4472e2,0x3f5fef14,4 +np.float32,0xbf46bc70,0xbf638814,4 +np.float32,0xbf0b910f,0xbf139c7a,4 +np.float32,0x3f36b4a6,0x3f4b753f,4 +np.float32,0x3e0bf478,0x3e0c64f6,4 +np.float32,0x3ce02480,0x3ce02ba9,4 +np.float32,0xbd904b10,0xbd9069b1,4 +np.float32,0xbf7f5d72,0xbfc00b70,4 +np.float32,0x3f62127e,0x3f8a8ca8,4 +np.float32,0xbf320253,0xbf44d6e4,4 +np.float32,0x3f2507be,0x3f335833,4 +np.float32,0x3f299284,0x3f395887,4 +np.float32,0xbd8211b0,0xbd82281d,4 +np.float32,0xbd3374c0,0xbd338376,4 +np.float32,0x3f36c56a,0x3f4b8d30,4 +np.float32,0xbf51f704,0xbf76331f,4 +np.float32,0xbe9871ca,0xbe9acab2,4 +np.float32,0xbe818d8c,0xbe82fa0f,4 +np.float32,0x3f08b958,0x3f103c18,4 +np.float32,0x3f22559a,0x3f2fd698,4 +np.float32,0xbf11f388,0xbf1b4db8,4 +np.float32,0x3ebe1990,0x3ec2c359,4 +np.float32,0xbe75ab38,0xbe7816b6,4 +np.float32,0x3e96102c,0x3e984c99,4 +np.float32,0xbe80d9d2,0xbe824052,4 +np.float32,0x3ef47588,0x3efeda7f,4 +np.float32,0xbe45e524,0xbe4725ea,4 +np.float32,0x3f7f9e7a,0x3fc213ff,4 +np.float32,0x3f1d3c36,0x3f294faa,4 +np.float32,0xbf3c58db,0xbf53a591,4 +np.float32,0x3f0d3d20,0x3f159c69,4 +np.float32,0x3f744be6,0x3fa23552,4 +np.float32,0x3f2e0cea,0x3f3f630e,4 +np.float32,0x3e193c10,0x3e19cff7,4 +np.float32,0xbf4150ac,0xbf5b19dd,4 +np.float32,0xbf145f72,0xbf1e4355,4 +np.float32,0xbb76cc00,0xbb76cc26,4 +np.float32,0x3f756780,0x3fa41b3e,4 +np.float32,0x3ea9b868,0x3eacfe3c,4 +np.float32,0x3d07c920,0x3d07cf7f,4 +np.float32,0xbf2263d4,0xbf2fe8ff,4 +np.float32,0x3e53b3f8,0x3e553daa,4 +np.float32,0xbf785be8,0xbfa9b5ba,4 +np.float32,0x3f324f7a,0x3f454254,4 +np.float32,0xbf2188f2,0xbf2ece5b,4 +np.float32,0xbe33781c,0xbe3466a2,4 +np.float32,0xbd3cf120,0xbd3d024c,4 +np.float32,0x3f06b18a,0x3f0dd70f,4 +np.float32,0x3f40d63e,0x3f5a5f6a,4 +np.float32,0x3f752340,0x3fa3a41e,4 +np.float32,0xbe1cf1c0,0xbe1d90bc,4 +np.float32,0xbf02d948,0xbf0957d7,4 +np.float32,0x3f73bed0,0x3fa14bf7,4 +np.float32,0x3d914920,0x3d916864,4 +np.float32,0x7fa00000,0x7fe00000,4 +np.float32,0xbe67a5d8,0xbe69aba7,4 +np.float32,0x3f689c4a,0x3f91eb9f,4 +np.float32,0xbf196e00,0xbf248601,4 +np.float32,0xbf50dacb,0xbf7444fe,4 +np.float32,0x3f628b86,0x3f8b0e1e,4 +np.float32,0x3f6ee2f2,0x3f99fe7f,4 +np.float32,0x3ee5df40,0x3eee6492,4 +np.float32,0x3f501746,0x3f72f41b,4 +np.float32,0xbf1f0f18,0xbf2ba164,4 +np.float32,0xbf1a8bfd,0xbf25ec01,4 +np.float32,0xbd4926f0,0xbd493ba9,4 +np.float32,0xbf4e364f,0xbf6fc17b,4 +np.float32,0x3e50c578,0x3e523ed4,4 +np.float32,0x3f65bf10,0x3f8e95ce,4 +np.float32,0xbe8d75a2,0xbe8f52f2,4 +np.float32,0xbf3f557e,0xbf581962,4 +np.float32,0xbeff2bfc,0xbf05903a,4 +np.float32,0x3f5e8bde,0x3f86e3d8,4 +np.float32,0xbf7a0012,0xbfad4b9b,4 +np.float32,0x3edefce0,0x3ee6b790,4 +np.float32,0xbf0003de,0xbf060f09,4 +np.float32,0x3efc4650,0x3f03e548,4 +np.float32,0x3f4582e4,0x3f6198f5,4 +np.float32,0x3f10086c,0x3f18f9d0,4 +np.float32,0x3f1cd304,0x3f28ca77,4 +np.float32,0x3f683366,0x3f916e8d,4 +np.float32,0xbed49392,0xbedb3675,4 +np.float32,0xbf6fe5f6,0xbf9b6c0e,4 +np.float32,0xbf59b416,0xbf8224f6,4 +np.float32,0x3d20c960,0x3d20d3f4,4 +np.float32,0x3f6b00d6,0x3f94dbe7,4 +np.float32,0x3f6c26ae,0x3f965352,4 +np.float32,0xbf370ea6,0xbf4bf5dd,4 +np.float32,0x3dfe7230,0x3dff1af1,4 +np.float32,0xbefc21a8,0xbf03d038,4 +np.float32,0x3f16a990,0x3f21156a,4 +np.float32,0xbef8ac0c,0xbf01d48f,4 +np.float32,0x3f170de8,0x3f21919d,4 +np.float32,0x3db9ef80,0x3dba3122,4 +np.float32,0x3d696400,0x3d698461,4 +np.float32,0x3f007aa2,0x3f069843,4 +np.float32,0x3f22827c,0x3f3010a9,4 +np.float32,0x3f3650dc,0x3f4ae6f1,4 +np.float32,0xbf1d8037,0xbf29a5e1,4 +np.float32,0xbf08fdc4,0xbf108d0e,4 +np.float32,0xbd8df350,0xbd8e1079,4 +np.float32,0xbf36bb32,0xbf4b7e98,4 +np.float32,0x3f2e3756,0x3f3f9ced,4 +np.float32,0x3d5a6f20,0x3d5a89aa,4 +np.float32,0x3f55d568,0x3f7d1889,4 +np.float32,0x3e1ed110,0x3e1f75d9,4 +np.float32,0x3e7386b8,0x3e75e1dc,4 +np.float32,0x3f48ea0e,0x3f670434,4 +np.float32,0x3e921fb0,0x3e942f14,4 +np.float32,0xbf0d4d0b,0xbf15af7f,4 +np.float32,0x3f179ed2,0x3f224549,4 +np.float32,0xbf3a328e,0xbf507e6d,4 +np.float32,0xbf74591a,0xbfa24b6e,4 +np.float32,0x3ec7d1c4,0x3ecd4657,4 +np.float32,0xbf6ecbed,0xbf99de85,4 +np.float32,0x3db0bd00,0x3db0f559,4 +np.float32,0x7f800000,0x7fc00000,4 +np.float32,0x3e0373b8,0x3e03d0d6,4 +np.float32,0xbf439784,0xbf5e9a04,4 +np.float32,0xbef97a9e,0xbf024ac6,4 +np.float32,0x3e4d71a8,0x3e4ed90a,4 +np.float32,0xbf14d868,0xbf1ed7e3,4 +np.float32,0xbf776870,0xbfa7ce37,4 +np.float32,0xbe32a500,0xbe339038,4 +np.float32,0xbf326d8a,0xbf456c3d,4 +np.float32,0xbe9b758c,0xbe9df3e7,4 +np.float32,0x3d9515a0,0x3d95376a,4 +np.float32,0x3e3f7320,0x3e40953e,4 +np.float32,0xbee57e7e,0xbeedf84f,4 +np.float32,0x3e821e94,0x3e838ffd,4 +np.float32,0x3f74beaa,0x3fa2f721,4 +np.float32,0xbe9b7672,0xbe9df4d9,4 +np.float32,0x3f4041fc,0x3f597e71,4 +np.float32,0xbe9ea7c4,0xbea14f92,4 +np.float32,0xbf800000,0xbfc90fdb,4 +np.float32,0x3e04fb90,0x3e055bfd,4 +np.float32,0xbf14d3d6,0xbf1ed245,4 +np.float32,0xbe84ebec,0xbe86763e,4 +np.float32,0x3f08e568,0x3f107039,4 +np.float32,0x3d8dc9e0,0x3d8de6ef,4 +np.float32,0x3ea4549c,0x3ea74a94,4 +np.float32,0xbebd2806,0xbec1bf51,4 +np.float32,0x3f311a26,0x3f439498,4 +np.float32,0xbf3d2222,0xbf54cf7e,4 +np.float32,0x3e00c500,0x3e011c81,4 +np.float32,0xbe35ed1c,0xbe36e5a9,4 +np.float32,0xbd4ec020,0xbd4ed6a0,4 +np.float32,0x3e1eb088,0x3e1f54eb,4 +np.float32,0x3cf94840,0x3cf9521a,4 +np.float32,0xbf010c5d,0xbf0740e0,4 +np.float32,0xbf3bd63b,0xbf52e502,4 +np.float32,0x3f233f30,0x3f310542,4 +np.float32,0x3ea24128,0x3ea519d7,4 +np.float32,0x3f478b38,0x3f64d124,4 +np.float32,0x3f1e0c6c,0x3f2a57ec,4 +np.float32,0xbf3ad294,0xbf51680a,4 +np.float32,0x3ede0554,0x3ee5a4b4,4 +np.float32,0x3e451a98,0x3e46577d,4 +np.float32,0x3f520164,0x3f764542,4 +np.float32,0x0,0x0,4 +np.float32,0xbd056cd0,0xbd0572db,4 +np.float32,0xbf58b018,0xbf812f5e,4 +np.float32,0x3e036eb0,0x3e03cbc3,4 +np.float32,0x3d1377a0,0x3d137fc9,4 +np.float32,0xbf692d3a,0xbf929a2c,4 +np.float32,0xbec60fb8,0xbecb5dea,4 +np.float32,0x3ed23340,0x3ed89a8e,4 +np.float32,0x3c87f040,0x3c87f1d9,4 +np.float32,0x3dac62f0,0x3dac9737,4 +np.float32,0xbed97c16,0xbee09f02,4 +np.float32,0xbf2d5f3c,0xbf3e769c,4 +np.float32,0xbc3b7c40,0xbc3b7d4c,4 +np.float32,0x3ed998ec,0x3ee0bedd,4 +np.float32,0x3dd86630,0x3dd8cdcb,4 +np.float32,0x3e8b4304,0x3e8d09ea,4 +np.float32,0x3f51e6b0,0x3f761697,4 +np.float32,0x3ec51f24,0x3eca5923,4 +np.float32,0xbf647430,0xbf8d2307,4 +np.float32,0x3f253d9c,0x3f339eb2,4 +np.float32,0x3dc969d0,0x3dc9bd4b,4 +np.float32,0xbc2f1300,0xbc2f13da,4 +np.float32,0xbf170007,0xbf21806d,4 +np.float32,0x3f757d10,0x3fa4412e,4 +np.float32,0xbe7864ac,0xbe7ae564,4 +np.float32,0x3f2ffe90,0x3f420cfb,4 +np.float32,0xbe576138,0xbe590012,4 +np.float32,0xbf517a21,0xbf755959,4 +np.float32,0xbf159cfe,0xbf1fc9d5,4 +np.float32,0xbf638b2a,0xbf8c22cf,4 +np.float32,0xff800000,0x7fc00000,4 +np.float32,0x3ed19ca0,0x3ed7f569,4 +np.float32,0x3f7c4460,0x3fb32d26,4 +np.float32,0x3ebfae6c,0x3ec477ab,4 +np.float32,0x3dd452d0,0x3dd4b4a8,4 +np.float32,0x3f471482,0x3f6413fb,4 +np.float32,0xbf49d704,0xbf6883fe,4 +np.float32,0xbd42c4e0,0xbd42d7af,4 +np.float32,0xbeb02994,0xbeb3d668,4 +np.float32,0x3f4d1fd8,0x3f6dedd2,4 +np.float32,0x3efb591c,0x3f035d11,4 +np.float32,0x80000000,0x80000000,4 +np.float32,0xbf50f782,0xbf7476ad,4 +np.float32,0x3d7232c0,0x3d7256f0,4 +np.float32,0x3f649460,0x3f8d46bb,4 +np.float32,0x3f5561bc,0x3f7c46a9,4 +np.float32,0x3e64f6a0,0x3e66ea5d,4 +np.float32,0x3e5b0470,0x3e5cb8f9,4 +np.float32,0xbe9b6b2c,0xbe9de904,4 +np.float32,0x3f6c33f4,0x3f966486,4 +np.float32,0x3f5cee54,0x3f854613,4 +np.float32,0x3ed3e044,0x3eda716e,4 +np.float32,0xbf3cac7f,0xbf542131,4 +np.float32,0x3c723500,0x3c723742,4 +np.float32,0x3de59900,0x3de614d3,4 +np.float32,0xbdf292f8,0xbdf32517,4 +np.float32,0x3f05c8b2,0x3f0cc59b,4 +np.float32,0xbf1ab182,0xbf261b14,4 +np.float32,0xbda396f0,0xbda3c39a,4 +np.float32,0xbf270ed0,0xbf360231,4 +np.float32,0x3f2063e6,0x3f2d557e,4 +np.float32,0x3c550280,0x3c550409,4 +np.float32,0xbe103b48,0xbe10b679,4 +np.float32,0xbebae390,0xbebf4f40,4 +np.float32,0x3f3bc868,0x3f52d0aa,4 +np.float32,0xbd62f880,0xbd631647,4 +np.float32,0xbe7a38f4,0xbe7cc833,4 +np.float32,0x3f09d796,0x3f118f39,4 +np.float32,0xbf5fa558,0xbf8802d0,4 +np.float32,0x3f111cc8,0x3f1a48b0,4 +np.float32,0x3e831958,0x3e849356,4 +np.float32,0xbf614dbd,0xbf89bc3b,4 +np.float32,0xbd521510,0xbd522cac,4 +np.float32,0x3f05af22,0x3f0ca7a0,4 +np.float32,0xbf1ac60e,0xbf2634df,4 +np.float32,0xbf6bd05e,0xbf95e3fe,4 +np.float32,0xbd1fa6e0,0xbd1fb13b,4 +np.float32,0xbeb82f7a,0xbebc68b1,4 +np.float32,0xbd92aaf8,0xbd92cb23,4 +np.float32,0xbe073a54,0xbe079fbf,4 +np.float32,0xbf198655,0xbf24a468,4 +np.float32,0x3f62f6d8,0x3f8b81ba,4 +np.float32,0x3eef4310,0x3ef8f4f9,4 +np.float32,0x3e8988e0,0x3e8b3eae,4 +np.float32,0xbf3ddba5,0xbf55e367,4 +np.float32,0x3dc6d2e0,0x3dc7232b,4 +np.float32,0xbf31040e,0xbf437601,4 +np.float32,0x3f1bb74a,0x3f276442,4 +np.float32,0xbf0075d2,0xbf0692b3,4 +np.float32,0xbf606ce0,0xbf88d0ff,4 +np.float32,0xbf083856,0xbf0fa39d,4 +np.float32,0xbdb25b20,0xbdb2950a,4 +np.float32,0xbeb86860,0xbebca5ae,4 +np.float32,0x3de83160,0x3de8b176,4 +np.float32,0xbf33a98f,0xbf472664,4 +np.float32,0x3e7795f8,0x3e7a1058,4 +np.float32,0x3e0ca6f8,0x3e0d192a,4 +np.float32,0xbf1aef60,0xbf2668c3,4 +np.float32,0xbda53b58,0xbda5695e,4 +np.float32,0xbf178096,0xbf221fc5,4 +np.float32,0xbf0a4159,0xbf120ccf,4 +np.float32,0x3f7bca36,0x3fb1d0df,4 +np.float32,0xbef94360,0xbf022b26,4 +np.float32,0xbef16f36,0xbefb6ad6,4 +np.float32,0x3f53a7e6,0x3f792e25,4 +np.float32,0xbf7c536f,0xbfb35993,4 +np.float32,0xbe84aaa0,0xbe8632a2,4 +np.float32,0x3ecb3998,0x3ed0fab9,4 +np.float32,0x3f539304,0x3f79090a,4 +np.float32,0xbf3c7816,0xbf53d3b3,4 +np.float32,0xbe7a387c,0xbe7cc7b7,4 +np.float32,0x3f7000e4,0x3f9b92b1,4 +np.float32,0x3e08fd70,0x3e0966e5,4 +np.float32,0x3db97ba0,0x3db9bcc8,4 +np.float32,0xbee99056,0xbef2886a,4 +np.float32,0xbf0668da,0xbf0d819e,4 +np.float32,0x3e58a408,0x3e5a4a51,4 +np.float32,0x3f3440b8,0x3f47faed,4 +np.float32,0xbf19a2ce,0xbf24c7ff,4 +np.float32,0xbe75e990,0xbe7856ee,4 +np.float32,0x3f3c865c,0x3f53e8cb,4 +np.float32,0x3e5e03d0,0x3e5fcac9,4 +np.float32,0x3edb8e34,0x3ee2e932,4 +np.float32,0xbf7e1f5f,0xbfb98ce4,4 +np.float32,0xbf7372ff,0xbfa0d0ae,4 +np.float32,0xbf3ee850,0xbf577548,4 +np.float32,0x3ef19658,0x3efb9737,4 +np.float32,0xbe8088de,0xbe81ecaf,4 +np.float32,0x800000,0x800000,4 +np.float32,0xbde39dd8,0xbde4167a,4 +np.float32,0xbf065d7a,0xbf0d7441,4 +np.float32,0xbde52c78,0xbde5a79b,4 +np.float32,0xbe3a28c0,0xbe3b333e,4 +np.float32,0x3f6e8b3c,0x3f998516,4 +np.float32,0x3f3485c2,0x3f485c39,4 +np.float32,0x3e6f2c68,0x3e71673e,4 +np.float32,0xbe4ec9cc,0xbe50385e,4 +np.float32,0xbf1c3bb0,0xbf280b39,4 +np.float32,0x3ec8ea18,0x3ece76f7,4 +np.float32,0x3e26b5f8,0x3e2774c9,4 +np.float32,0x3e1e4a38,0x3e1eed5c,4 +np.float32,0xbee7a106,0xbef05c6b,4 +np.float32,0xbf305928,0xbf4289d8,4 +np.float32,0x3f0c431c,0x3f147118,4 +np.float32,0xbe57ba6c,0xbe595b52,4 +np.float32,0x3eabc9cc,0x3eaf2fc7,4 +np.float32,0xbef1ed24,0xbefbf9ae,4 +np.float32,0xbf61b576,0xbf8a29cc,4 +np.float32,0x3e9c1ff4,0x3e9ea6cb,4 +np.float32,0x3f6c53b2,0x3f968dbe,4 +np.float32,0x3e2d1b80,0x3e2df156,4 +np.float32,0x3e9f2f70,0x3ea1de4a,4 +np.float32,0xbf5861ee,0xbf80e61a,4 +np.float32,0x3f429144,0x3f5d0505,4 +np.float32,0x3e235cc8,0x3e24103e,4 +np.float32,0xbf354879,0xbf496f6a,4 +np.float32,0xbf20a146,0xbf2da447,4 +np.float32,0x3e8d8968,0x3e8f6785,4 +np.float32,0x3f3fbc94,0x3f58b4c1,4 +np.float32,0x3f2c5f50,0x3f3d1b9f,4 +np.float32,0x3f7bf0f8,0x3fb23d23,4 +np.float32,0xbf218282,0xbf2ec60f,4 +np.float32,0x3f2545aa,0x3f33a93e,4 +np.float32,0xbf4b17be,0xbf6a9018,4 +np.float32,0xbb9df700,0xbb9df728,4 +np.float32,0x3f685d54,0x3f91a06c,4 +np.float32,0x3efdfe2c,0x3f04e24c,4 +np.float32,0x3ef1c5a0,0x3efbccd9,4 +np.float32,0xbf41d731,0xbf5be76e,4 +np.float32,0x3ebd1360,0x3ec1a919,4 +np.float32,0xbf706bd4,0xbf9c2d58,4 +np.float32,0x3ea525e4,0x3ea8279d,4 +np.float32,0xbe51f1b0,0xbe537186,4 +np.float32,0x3f5e8cf6,0x3f86e4f4,4 +np.float32,0xbdad2520,0xbdad5a19,4 +np.float32,0xbf5c5704,0xbf84b0e5,4 +np.float32,0x3f47b54e,0x3f65145e,4 +np.float32,0x3eb4fc78,0x3eb8fc0c,4 +np.float32,0x3dca1450,0x3dca68a1,4 +np.float32,0x3eb02a74,0x3eb3d757,4 +np.float32,0x3f74ae6a,0x3fa2db75,4 +np.float32,0x3f800000,0x3fc90fdb,4 +np.float32,0xbdb46a00,0xbdb4a5f2,4 +np.float32,0xbe9f2ba6,0xbea1da4e,4 +np.float32,0x3f0afa70,0x3f12e8f7,4 +np.float32,0xbf677b20,0xbf909547,4 +np.float32,0x3eff9188,0x3f05cacf,4 +np.float32,0x3f720562,0x3f9e911b,4 +np.float32,0xbf7180d8,0xbf9dc794,4 +np.float32,0xbee7d076,0xbef0919d,4 +np.float32,0x3f0432ce,0x3f0aea95,4 +np.float32,0x3f3bc4c8,0x3f52cb54,4 +np.float32,0xbea72f30,0xbeaa4ebe,4 +np.float32,0x3e90ed00,0x3e92ef33,4 +np.float32,0xbda63670,0xbda6654a,4 +np.float32,0xbf5a6f85,0xbf82d7e0,4 +np.float32,0x3e6e8808,0x3e70be34,4 +np.float32,0xbf4f3822,0xbf71768f,4 +np.float32,0x3e5c8a68,0x3e5e483f,4 +np.float32,0xbf0669d4,0xbf0d82c4,4 +np.float32,0xbf79f77c,0xbfad37b0,4 +np.float32,0x3f25c82c,0x3f345453,4 +np.float32,0x3f1b2948,0x3f26b188,4 +np.float32,0x3ef7e288,0x3f016159,4 +np.float32,0x3c274280,0x3c27433e,4 +np.float32,0xbf4c8fa0,0xbf6cfd5e,4 +np.float32,0x3ea4ccb4,0x3ea7c966,4 +np.float32,0xbf7b157e,0xbfafefca,4 +np.float32,0xbee4c2b0,0xbeed264d,4 +np.float32,0xbc1fd640,0xbc1fd6e6,4 +np.float32,0x3e892308,0x3e8ad4f6,4 +np.float32,0xbf3f69c7,0xbf5837ed,4 +np.float32,0x3ec879e8,0x3ecdfd05,4 +np.float32,0x3f07a8c6,0x3f0efa30,4 +np.float32,0x3f67b880,0x3f90dd4d,4 +np.float32,0x3e8a11c8,0x3e8bccd5,4 +np.float32,0x3f7df6fc,0x3fb8e935,4 +np.float32,0xbef3e498,0xbefe3599,4 +np.float32,0xbf18ad7d,0xbf2395d8,4 +np.float32,0x3f2bce74,0x3f3c57f5,4 +np.float32,0xbf38086e,0xbf4d5c2e,4 +np.float32,0x3f772d7a,0x3fa75c35,4 +np.float32,0xbf3b6e24,0xbf524c00,4 +np.float32,0xbdd39108,0xbdd3f1d4,4 +np.float32,0xbf691f6b,0xbf928974,4 +np.float32,0x3f146188,0x3f1e45e4,4 +np.float32,0xbf56045b,0xbf7d6e03,4 +np.float32,0xbf4b2ee4,0xbf6ab622,4 +np.float32,0xbf3fa3f6,0xbf588f9d,4 +np.float32,0x3f127bb0,0x3f1bf398,4 +np.float32,0x3ed858a0,0x3edf5d3e,4 +np.float32,0xbd6de3b0,0xbd6e05fa,4 +np.float32,0xbecc662c,0xbed24261,4 +np.float32,0xbd6791d0,0xbd67b170,4 +np.float32,0xbf146016,0xbf1e441e,4 +np.float32,0xbf61f04c,0xbf8a6841,4 +np.float32,0xbe7f16d0,0xbe80e6e7,4 +np.float32,0xbebf93e6,0xbec45b10,4 +np.float32,0xbe8a59fc,0xbe8c17d1,4 +np.float32,0xbebc7a0c,0xbec10426,4 +np.float32,0xbf2a682e,0xbf3a7649,4 +np.float32,0xbe18d0cc,0xbe19637b,4 +np.float32,0x3d7f5100,0x3d7f7b66,4 +np.float32,0xbf10f5fa,0xbf1a1998,4 +np.float32,0x3f25e956,0x3f347fdc,4 +np.float32,0x3e6e8658,0x3e70bc78,4 +np.float32,0x3f21a5de,0x3f2ef3a5,4 +np.float32,0xbf4e71d4,0xbf702607,4 +np.float32,0xbf49d6b6,0xbf688380,4 +np.float32,0xbdb729c0,0xbdb7687c,4 +np.float32,0xbf63e1f4,0xbf8c81c7,4 +np.float32,0x3dda6cb0,0x3ddad73e,4 +np.float32,0x3ee1bc40,0x3ee9c612,4 +np.float32,0x3ebdb5f8,0x3ec2581b,4 +np.float32,0x3f7d9576,0x3fb77646,4 +np.float32,0x3e087140,0x3e08d971,4 +np.float64,0xbfdba523cfb74a48,0xbfdc960ddd9c0506,1 +np.float64,0x3fb51773622a2ee0,0x3fb51d93f77089d5,1 +np.float64,0x3fc839f6d33073f0,0x3fc85f9a47dfe8e6,1 +np.float64,0xbfecba2d82f9745b,0xbff1d55416c6c993,1 +np.float64,0x3fd520fe47aa41fc,0x3fd58867f1179634,1 +np.float64,0x3fe1b369c56366d4,0x3fe2c1ac9dd2c45a,1 +np.float64,0xbfec25a7cd784b50,0xbff133417389b12d,1 +np.float64,0xbfd286342ea50c68,0xbfd2cb0bca22e66d,1 +np.float64,0x3fd5f6fe5eabedfc,0x3fd66bad16680d08,1 +np.float64,0xbfe863a87570c751,0xbfebbb9b637eb6dc,1 +np.float64,0x3fc97f5b4d32feb8,0x3fc9ab5066d8eaec,1 +np.float64,0xbfcb667af936ccf4,0xbfcb9d3017047a1d,1 +np.float64,0xbfd1b7b9afa36f74,0xbfd1f3c175706154,1 +np.float64,0x3fef97385b7f2e70,0x3ff6922a1a6c709f,1 +np.float64,0xbfd13e4205a27c84,0xbfd1757c993cdb74,1 +np.float64,0xbfd18d88aca31b12,0xbfd1c7dd75068f7d,1 +np.float64,0x3fe040ce0f60819c,0x3fe10c59d2a27089,1 +np.float64,0xbfddc7deddbb8fbe,0xbfdef9de5baecdda,1 +np.float64,0xbfcf6e96193edd2c,0xbfcfc1bb7396b9a3,1 +np.float64,0x3fd544f494aa89e8,0x3fd5ae850e2b37dd,1 +np.float64,0x3fe15b381fe2b670,0x3fe25841c7bfe2af,1 +np.float64,0xbfde793420bcf268,0xbfdfc2ddc7b4a341,1 +np.float64,0x3fd0d5db30a1abb8,0x3fd1092cef4aa4fb,1 +np.float64,0x3fe386a08c670d42,0x3fe50059bbf7f491,1 +np.float64,0xbfe0aae3a96155c8,0xbfe1880ef13e95ce,1 +np.float64,0xbfe80eeb03f01dd6,0xbfeb39e9f107e944,1 +np.float64,0xbfd531af3caa635e,0xbfd59a178f17552a,1 +np.float64,0x3fcced14ab39da28,0x3fcd2d9a806337ef,1 +np.float64,0xbfdb4c71bcb698e4,0xbfdc33d9d9daf708,1 +np.float64,0xbfde7375ecbce6ec,0xbfdfbc5611bc48ff,1 +np.float64,0x3fecc5707a798ae0,0x3ff1e2268d778017,1 +np.float64,0x3fe8f210a1f1e422,0x3fec9b3349a5baa2,1 +np.float64,0x3fe357f9b8e6aff4,0x3fe4c5a0b89a9228,1 +np.float64,0xbfe0f863b761f0c8,0xbfe1e3283494c3d4,1 +np.float64,0x3fd017c395a02f88,0x3fd044761f2f4a66,1 +np.float64,0x3febeb4746f7d68e,0x3ff0f6b955e7feb6,1 +np.float64,0xbfbdaaeeae3b55e0,0xbfbdbc0950109261,1 +np.float64,0xbfea013095f40261,0xbfee5b8fe8ad8593,1 +np.float64,0xbfe9f87b7973f0f7,0xbfee4ca3a8438d72,1 +np.float64,0x3fd37f77cfa6fef0,0x3fd3d018c825f057,1 +np.float64,0x3fb0799cee20f340,0x3fb07c879e7cb63f,1 +np.float64,0xbfdcfd581cb9fab0,0xbfde15e35314b52d,1 +np.float64,0xbfd49781b8a92f04,0xbfd4f6fa1516fefc,1 +np.float64,0x3fb3fcb6d627f970,0x3fb401ed44a713a8,1 +np.float64,0x3fd5737ef8aae6fc,0x3fd5dfe42d4416c7,1 +np.float64,0x7ff4000000000000,0x7ffc000000000000,1 +np.float64,0xbfe56ae780ead5cf,0xbfe776ea5721b900,1 +np.float64,0x3fd4567786a8acf0,0x3fd4b255421c161a,1 +np.float64,0x3fef6fb58cfedf6c,0x3ff62012dfcf0a33,1 +np.float64,0xbfd1dbcd3da3b79a,0xbfd2194fd628f74d,1 +np.float64,0x3fd9350016b26a00,0x3fd9e8b01eb023e9,1 +np.float64,0xbfe4fb3a69e9f675,0xbfe6e1d2c9eca56c,1 +np.float64,0x3fe9fe0f73f3fc1e,0x3fee5631cfd39772,1 +np.float64,0xbfd51c1bc6aa3838,0xbfd5833b3bd53543,1 +np.float64,0x3fc64158e12c82b0,0x3fc65e7352f237d7,1 +np.float64,0x3fd0d8ee1ba1b1dc,0x3fd10c5c99a16f0e,1 +np.float64,0x3fd5554e15aaaa9c,0x3fd5bfdb9ec9e873,1 +np.float64,0x3fe61ce209ec39c4,0x3fe869bc4c28437d,1 +np.float64,0xbfe4e42c8c69c859,0xbfe6c356dac7e2db,1 +np.float64,0xbfe157021062ae04,0xbfe2533ed39f4212,1 +np.float64,0x3fe844066cf0880c,0x3feb8aea0b7bd0a4,1 +np.float64,0x3fe55016586aa02c,0x3fe752e4b2a67b9f,1 +np.float64,0x3fdabce619b579cc,0x3fdb95809bc789d9,1 +np.float64,0x3fee03bae37c0776,0x3ff3778ba38ca882,1 +np.float64,0xbfeb2f5844f65eb0,0xbff03dd1b767d3c8,1 +np.float64,0x3fedcfdbaffb9fb8,0x3ff32e81d0639164,1 +np.float64,0x3fe06fc63ee0df8c,0x3fe142fc27f92eaf,1 +np.float64,0x3fe7ce90fd6f9d22,0x3fead8f832bbbf5d,1 +np.float64,0xbfbc0015ce380028,0xbfbc0e7470e06e86,1 +np.float64,0xbfe9b3de90f367bd,0xbfedd857931dfc6b,1 +np.float64,0xbfcb588f5936b120,0xbfcb8ef0124a4f21,1 +np.float64,0x3f8d376a503a6f00,0x3f8d37ab43e7988d,1 +np.float64,0xbfdb123a40b62474,0xbfdbf38b6cf5db92,1 +np.float64,0xbfee7da6be7cfb4e,0xbff433042cd9d5eb,1 +np.float64,0xbfc4c9e01b2993c0,0xbfc4e18dbafe37ef,1 +np.float64,0x3fedd42faffba860,0x3ff334790cd18a19,1 +np.float64,0x3fe9cdf772f39bee,0x3fee044f87b856ab,1 +np.float64,0x3fe0245881e048b2,0x3fe0eb5a1f739c8d,1 +np.float64,0xbfe4712bd9e8e258,0xbfe62cb3d82034aa,1 +np.float64,0x3fe9a16b46f342d6,0x3fedb972b2542551,1 +np.float64,0xbfe57ab4536af568,0xbfe78c34b03569c2,1 +np.float64,0x3fb6d6ceb22dada0,0x3fb6de976964d6dd,1 +np.float64,0x3fc3ac23a3275848,0x3fc3c02de53919b8,1 +np.float64,0xbfccb531e7396a64,0xbfccf43ec69f6281,1 +np.float64,0xbfd2f07fc8a5e100,0xbfd33a35a8c41b62,1 +np.float64,0xbfe3e5dd04e7cbba,0xbfe57940157c27ba,1 +np.float64,0x3feefe40757dfc80,0x3ff51bc72b846af6,1 +np.float64,0x8000000000000001,0x8000000000000001,1 +np.float64,0x3fecb7b766796f6e,0x3ff1d28972a0fc7e,1 +np.float64,0xbfea1bf1357437e2,0xbfee89a6532bfd71,1 +np.float64,0xbfca3983b7347308,0xbfca696463b791ef,1 +np.float64,0x10000000000000,0x10000000000000,1 +np.float64,0xbf886b45d030d680,0xbf886b6bbc04314b,1 +np.float64,0x3fd5224bb5aa4498,0x3fd589c92e82218f,1 +np.float64,0xbfec799874f8f331,0xbff18d5158b8e640,1 +np.float64,0xbf88124410302480,0xbf88126863350a16,1 +np.float64,0xbfe37feaaa66ffd6,0xbfe4f7e24382e79d,1 +np.float64,0x3fd777eca1aeefd8,0x3fd8076ead6d55dc,1 +np.float64,0x3fecaaeb3af955d6,0x3ff1c4159fa3e965,1 +np.float64,0xbfeb81e4e6f703ca,0xbff08d4e4c77fada,1 +np.float64,0xbfd7d0a0edafa142,0xbfd866e37010312e,1 +np.float64,0x3feda48c00fb4918,0x3ff2f3fd33c36307,1 +np.float64,0x3feb87ecc4770fda,0x3ff09336e490deda,1 +np.float64,0xbfefd78ad27faf16,0xbff78abbafb50ac1,1 +np.float64,0x3fe58e918c6b1d24,0x3fe7a70b38cbf016,1 +np.float64,0x3fda163b95b42c78,0x3fdade86b88ba4ee,1 +np.float64,0x3fe8fc1aaf71f836,0x3fecab3f93b59df5,1 +np.float64,0xbf8de56f903bcac0,0xbf8de5b527cec797,1 +np.float64,0xbfec112db2f8225b,0xbff11dd648de706f,1 +np.float64,0x3fc3214713264290,0x3fc333b1c862f7d0,1 +np.float64,0xbfeb5e5836f6bcb0,0xbff06ac364b49177,1 +np.float64,0x3fc23d9777247b30,0x3fc24d8ae3bcb615,1 +np.float64,0xbfdf0eed65be1dda,0xbfe036cea9b9dfb6,1 +np.float64,0xbfb2d5c85a25ab90,0xbfb2da24bb409ff3,1 +np.float64,0xbfecdda0c3f9bb42,0xbff1fdf94fc6e89e,1 +np.float64,0x3fdfe79154bfcf24,0x3fe0b338e0476a9d,1 +np.float64,0xbfd712ac6bae2558,0xbfd79abde21f287b,1 +np.float64,0x3fea3f148a747e2a,0x3feec6bed9d4fa04,1 +np.float64,0x3fd4879e4ca90f3c,0x3fd4e632fa4e2edd,1 +np.float64,0x3fe9137a9e7226f6,0x3fecd0c441088d6a,1 +np.float64,0xbfc75bf4ef2eb7e8,0xbfc77da8347d742d,1 +np.float64,0xbfd94090a0b28122,0xbfd9f5458816ed5a,1 +np.float64,0x3fde439cbcbc8738,0x3fdf85fbf496b61f,1 +np.float64,0xbfe18bacdce3175a,0xbfe29210e01237f7,1 +np.float64,0xbfd58ec413ab1d88,0xbfd5fcd838f0a934,1 +np.float64,0xbfeae5af2d75cb5e,0xbfeff1de1b4a06be,1 +np.float64,0x3fb64d1a162c9a30,0x3fb65458fb831354,1 +np.float64,0x3fc18b1e15231640,0x3fc1994c6ffd7a6a,1 +np.float64,0xbfd7b881bcaf7104,0xbfd84ce89a9ee8c7,1 +np.float64,0x3feb916a40f722d4,0x3ff09c8aa851d7c4,1 +np.float64,0x3fdab5fbb5b56bf8,0x3fdb8de43961bbde,1 +np.float64,0x3fe4f35402e9e6a8,0x3fe6d75dc5082894,1 +np.float64,0x3fe2fdb2e5e5fb66,0x3fe454e32a5d2182,1 +np.float64,0x3fe8607195f0c0e4,0x3febb6a4c3bf6a5c,1 +np.float64,0x3fd543ca9aaa8794,0x3fd5ad49203ae572,1 +np.float64,0x3fe8e05ca1f1c0ba,0x3fec7eff123dcc58,1 +np.float64,0x3fe298b6ca65316e,0x3fe3d81d2927c4dd,1 +np.float64,0x3fcfecea733fd9d8,0x3fd0220f1d0faf78,1 +np.float64,0xbfe2e739f065ce74,0xbfe439004e73772a,1 +np.float64,0xbfd1ae6b82a35cd8,0xbfd1ea129a5ee756,1 +np.float64,0xbfeb7edff576fdc0,0xbff08a5a638b8a8b,1 +np.float64,0x3fe5b645ff6b6c8c,0x3fe7dcee1faefe3f,1 +np.float64,0xbfd478427ba8f084,0xbfd4d5fc7c239e60,1 +np.float64,0xbfe39904e3e7320a,0xbfe517972b30b1e5,1 +np.float64,0xbfd3b75b6ba76eb6,0xbfd40acf20a6e074,1 +np.float64,0x3fd596267aab2c4c,0x3fd604b01faeaf75,1 +np.float64,0x3fe134463762688c,0x3fe229fc36784a72,1 +np.float64,0x3fd25dadf7a4bb5c,0x3fd2a0b9e04ea060,1 +np.float64,0xbfc05d3e0b20ba7c,0xbfc068bd2bb9966f,1 +np.float64,0x3f8cf517b039ea00,0x3f8cf556ed74b163,1 +np.float64,0x3fda87361cb50e6c,0x3fdb5a75af897e7f,1 +np.float64,0x3fe53e1926ea7c32,0x3fe73acf01b8ff31,1 +np.float64,0x3fe2e94857e5d290,0x3fe43b8cc820f9c7,1 +np.float64,0x3fd81fe6acb03fcc,0x3fd8bc623c0068cf,1 +np.float64,0xbfddf662c3bbecc6,0xbfdf2e76dc90786e,1 +np.float64,0x3fece174fbf9c2ea,0x3ff2026a1a889580,1 +np.float64,0xbfdc83c5b8b9078c,0xbfdd8dcf6ee3b7da,1 +np.float64,0x3feaf5448f75ea8a,0x3ff0075b108bcd0d,1 +np.float64,0xbfebf32f7ef7e65f,0xbff0fed42aaa826a,1 +np.float64,0x3fe389e5e8e713cc,0x3fe5047ade055ccb,1 +np.float64,0x3f635cdcc026ba00,0x3f635cddeea082ce,1 +np.float64,0x3fae580f543cb020,0x3fae5c9d5108a796,1 +np.float64,0x3fec9fafce793f60,0x3ff1b77bec654f00,1 +np.float64,0x3fb19d226e233a40,0x3fb1a0b32531f7ee,1 +np.float64,0xbfdf9a71e7bf34e4,0xbfe086cef88626c7,1 +np.float64,0x8010000000000000,0x8010000000000000,1 +np.float64,0xbfef170ba2fe2e17,0xbff54ed4675f5b8a,1 +np.float64,0xbfcc6e2f8f38dc60,0xbfccab65fc34d183,1 +np.float64,0x3fee756c4bfcead8,0x3ff4258782c137e6,1 +np.float64,0xbfd461c218a8c384,0xbfd4be3e391f0ff4,1 +np.float64,0xbfe3b64686e76c8d,0xbfe53caa16d6c90f,1 +np.float64,0xbfc1c65d8d238cbc,0xbfc1d51e58f82403,1 +np.float64,0x3fe6e06c63edc0d8,0x3fe97cb832eeb6a2,1 +np.float64,0xbfc9fc20b933f840,0xbfca2ab004312d85,1 +np.float64,0xbfe29aa6df65354e,0xbfe3da7ecf3ba466,1 +np.float64,0x3fea4df7d1749bf0,0x3feee0d448bd4746,1 +np.float64,0xbfedec6161fbd8c3,0xbff3563e1d943aa2,1 +np.float64,0x3fdb6f0437b6de08,0x3fdc5a1888b1213d,1 +np.float64,0xbfe270cbd3e4e198,0xbfe3a72ac27a0b0c,1 +np.float64,0xbfdfff8068bfff00,0xbfe0c1088e3b8983,1 +np.float64,0xbfd28edbe6a51db8,0xbfd2d416c8ed363e,1 +np.float64,0xbfb4e35f9229c6c0,0xbfb4e9531d2a737f,1 +np.float64,0xbfee6727e97cce50,0xbff40e7717576e46,1 +np.float64,0xbfddb5fbddbb6bf8,0xbfdee5aad78f5361,1 +np.float64,0xbfdf9d3e9dbf3a7e,0xbfe0886b191f2957,1 +np.float64,0x3fa57e77042afce0,0x3fa5801518ea9342,1 +np.float64,0x3f95c4e4882b89c0,0x3f95c55003c8e714,1 +np.float64,0x3fd9b10f61b36220,0x3fda6fe5d635a8aa,1 +np.float64,0xbfe2973411652e68,0xbfe3d641fe9885fd,1 +np.float64,0xbfee87bd5a7d0f7b,0xbff443bea81b3fff,1 +np.float64,0x3f9ea064c83d40c0,0x3f9ea19025085b2f,1 +np.float64,0xbfe4b823dfe97048,0xbfe689623d30dc75,1 +np.float64,0xbfa06a326c20d460,0xbfa06aeacbcd3eb8,1 +np.float64,0x3fe1e5c4c1e3cb8a,0x3fe2fe44b822f20e,1 +np.float64,0x3f99dafaa833b600,0x3f99dbaec10a1a0a,1 +np.float64,0xbfed7cb3877af967,0xbff2bfe9e556aaf9,1 +np.float64,0x3fd604f2e2ac09e4,0x3fd67a89408ce6ba,1 +np.float64,0x3fec57b60f78af6c,0x3ff16881f46d60f7,1 +np.float64,0xbfea2e3a17745c74,0xbfeea95c7190fd42,1 +np.float64,0xbfd60a7c37ac14f8,0xbfd6806ed642de35,1 +np.float64,0xbfe544b9726a8973,0xbfe743ac399d81d7,1 +np.float64,0xbfd13520faa26a42,0xbfd16c02034a8fe0,1 +np.float64,0xbfea9ea59ff53d4b,0xbfef70538ee12e00,1 +np.float64,0x3fd66633f8accc68,0x3fd6e23c13ab0e9e,1 +np.float64,0xbfe4071bd3e80e38,0xbfe5a3c9ba897d81,1 +np.float64,0xbfbe1659fa3c2cb0,0xbfbe2831d4fed196,1 +np.float64,0xbfd3312777a6624e,0xbfd37df09b9baeba,1 +np.float64,0x3fd13997caa27330,0x3fd170a4900c8907,1 +np.float64,0xbfe7cbc235ef9784,0xbfead4c4d6cbf129,1 +np.float64,0xbfe1456571628acb,0xbfe23e4ec768c8e2,1 +np.float64,0xbfedf1a044fbe340,0xbff35da96773e176,1 +np.float64,0x3fce38b1553c7160,0x3fce8270709774f9,1 +np.float64,0xbfecb01761f9602f,0xbff1c9e9d382f1f8,1 +np.float64,0xbfe0a03560e1406b,0xbfe17b8d5a1ca662,1 +np.float64,0x3fe50f37cbea1e70,0x3fe6fc55e1ae7da6,1 +np.float64,0xbfe12d64a0625aca,0xbfe221d3a7834e43,1 +np.float64,0xbf6fb288403f6500,0xbf6fb28d6f389db6,1 +np.float64,0x3fda831765b50630,0x3fdb55eecae58ca9,1 +np.float64,0x3fe1a0fe4c6341fc,0x3fe2ab9564304425,1 +np.float64,0xbfef2678a77e4cf1,0xbff56ff42b2797bb,1 +np.float64,0xbfab269c1c364d40,0xbfab29df1cd48779,1 +np.float64,0x3fe8ec82a271d906,0x3fec92567d7a6675,1 +np.float64,0xbfc235115f246a24,0xbfc244ee567682ea,1 +np.float64,0x3feef5bf8d7deb80,0x3ff50ad4875ee9bd,1 +np.float64,0x3fe768b5486ed16a,0x3fea421356160e65,1 +np.float64,0xbfd4255684a84aae,0xbfd47e8baf7ec7f6,1 +np.float64,0x3fc7f67f2b2fed00,0x3fc81ae83cf92dd5,1 +np.float64,0x3fe9b1b19a736364,0x3fedd4b0e24ee741,1 +np.float64,0x3fb27eb9e624fd70,0x3fb282dacd89ce28,1 +np.float64,0xbfd490b710a9216e,0xbfd4efcdeb213458,1 +np.float64,0xbfd1347b2ca268f6,0xbfd16b55dece2d38,1 +np.float64,0x3fc6a5668d2d4ad0,0x3fc6c41452c0c087,1 +np.float64,0xbfca7b209f34f640,0xbfcaac710486f6bd,1 +np.float64,0x3fc23a1a47247438,0x3fc24a047fd4c27a,1 +np.float64,0x3fdb1413a8b62828,0x3fdbf595e2d994bc,1 +np.float64,0xbfea69b396f4d367,0xbfef11bdd2b0709a,1 +np.float64,0x3fd14c9958a29934,0x3fd1846161b10422,1 +np.float64,0xbfe205f44be40be8,0xbfe325283aa3c6a8,1 +np.float64,0x3fecd03c9ef9a07a,0x3ff1ee85aaf52a01,1 +np.float64,0x3fe34281d7e68504,0x3fe4aab63e6de816,1 +np.float64,0xbfe120e2376241c4,0xbfe213023ab03939,1 +np.float64,0xbfe951edc4f2a3dc,0xbfed3615e38576f8,1 +np.float64,0x3fe5a2286f6b4450,0x3fe7c196e0ec10ed,1 +np.float64,0xbfed7a3e1f7af47c,0xbff2bcc0793555d2,1 +np.float64,0x3fe050274960a04e,0x3fe11e2e256ea5cc,1 +np.float64,0xbfcfa71f653f4e40,0xbfcffc11483d6a06,1 +np.float64,0x3f6ead2e403d5a00,0x3f6ead32f314c052,1 +np.float64,0x3fe3a2a026674540,0x3fe523bfe085f6ec,1 +np.float64,0xbfe294a62e65294c,0xbfe3d31ebd0b4ca2,1 +np.float64,0xbfb4894d06291298,0xbfb48ef4b8e256b8,1 +np.float64,0xbfc0c042c1218084,0xbfc0cc98ac2767c4,1 +np.float64,0xbfc6a32cb52d4658,0xbfc6c1d1597ed06b,1 +np.float64,0xbfd30f7777a61eee,0xbfd35aa39fee34eb,1 +np.float64,0x3fe7fc2c2eeff858,0x3feb1d8a558b5537,1 +np.float64,0x7fefffffffffffff,0x7ff8000000000000,1 +np.float64,0xbfdadf917bb5bf22,0xbfdbbbae9a9f67a0,1 +np.float64,0xbfcf0395e13e072c,0xbfcf5366015f7362,1 +np.float64,0xbfe8644c9170c899,0xbfebbc98e74a227d,1 +np.float64,0x3fc3b2d8e52765b0,0x3fc3c6f7d44cffaa,1 +np.float64,0x3fc57407b92ae810,0x3fc58e12ccdd47a1,1 +np.float64,0x3fd56a560daad4ac,0x3fd5d62b8dfcc058,1 +np.float64,0x3fd595deefab2bbc,0x3fd6046420b2f79b,1 +np.float64,0xbfd5360f50aa6c1e,0xbfd59ebaacd815b8,1 +np.float64,0x3fdfb6aababf6d54,0x3fe0970b8aac9f61,1 +np.float64,0x3ff0000000000000,0x3ff921fb54442d18,1 +np.float64,0xbfeb3a8958f67513,0xbff04872e8278c79,1 +np.float64,0x3f9e1ea6683c3d40,0x3f9e1fc326186705,1 +np.float64,0x3fe6b6d5986d6dac,0x3fe94175bd60b19d,1 +np.float64,0xbfee4d90b77c9b21,0xbff3e60e9134edc2,1 +np.float64,0x3fd806ce0cb00d9c,0x3fd8a14c4855a8f5,1 +np.float64,0x3fd54acc75aa9598,0x3fd5b4b72fcbb5df,1 +np.float64,0xbfe59761f16b2ec4,0xbfe7b2fa5d0244ac,1 +np.float64,0xbfcd4fa3513a9f48,0xbfcd92d0814a5383,1 +np.float64,0xbfdc827523b904ea,0xbfdd8c577b53053c,1 +np.float64,0xbfd4bb7f34a976fe,0xbfd51d00d9a99360,1 +np.float64,0xbfe818bc87f03179,0xbfeb48d1ea0199c5,1 +np.float64,0xbfa8a2e15c3145c0,0xbfa8a5510ba0e45c,1 +np.float64,0xbfb6d15f422da2c0,0xbfb6d922689da015,1 +np.float64,0x3fcd04eaab3a09d8,0x3fcd46131746ef08,1 +np.float64,0x3fcfb5cfbb3f6ba0,0x3fd0059d308237f3,1 +np.float64,0x3fe8dcf609f1b9ec,0x3fec7997973010b6,1 +np.float64,0xbfdf1834d7be306a,0xbfe03c1d4e2b48f0,1 +np.float64,0x3fee82ae50fd055c,0x3ff43b545066fe1a,1 +np.float64,0xbfde039c08bc0738,0xbfdf3d6ed4d2ee5c,1 +np.float64,0x3fec07389bf80e72,0x3ff1137ed0acd161,1 +np.float64,0xbfef44c010fe8980,0xbff5b488ad22a4c5,1 +np.float64,0x3f76e722e02dce00,0x3f76e72ab2759d88,1 +np.float64,0xbfcaa9e6053553cc,0xbfcadc41125fca93,1 +np.float64,0x3fed6088147ac110,0x3ff29c06c4ef35fc,1 +np.float64,0x3fd32bd836a657b0,0x3fd3785fdb75909f,1 +np.float64,0xbfeedbb1d97db764,0xbff4d87f6c82a93c,1 +np.float64,0xbfe40f31d5e81e64,0xbfe5ae292cf258a2,1 +np.float64,0x7ff8000000000000,0x7ff8000000000000,1 +np.float64,0xbfeb2b25bc76564c,0xbff039d81388550c,1 +np.float64,0x3fec5008fa78a012,0x3ff1604195801da3,1 +np.float64,0x3fce2d4f293c5aa0,0x3fce76b99c2db4da,1 +np.float64,0xbfdc435412b886a8,0xbfdd45e7b7813f1e,1 +np.float64,0x3fdf2c9d06be593c,0x3fe047cb03c141b6,1 +np.float64,0x3fddefc61ebbdf8c,0x3fdf26fb8fad9fae,1 +np.float64,0x3fab50218436a040,0x3fab537395eaf3bb,1 +np.float64,0xbfd5b95a8fab72b6,0xbfd62a191a59343a,1 +np.float64,0x3fdbf803b4b7f008,0x3fdcf211578e98c3,1 +np.float64,0xbfec8c255979184b,0xbff1a1bee108ed30,1 +np.float64,0x3fe33cdaffe679b6,0x3fe4a3a318cd994f,1 +np.float64,0x3fd8cf585cb19eb0,0x3fd97a408bf3c38c,1 +np.float64,0x3fe919dde07233bc,0x3fecdb0ea13a2455,1 +np.float64,0xbfd5ba35e4ab746c,0xbfd62b024805542d,1 +np.float64,0x3fd2f933e7a5f268,0x3fd343527565e97c,1 +np.float64,0xbfe5b9f8ddeb73f2,0xbfe7e1f772c3e438,1 +np.float64,0x3fe843cd92f0879c,0x3feb8a92d68eae3e,1 +np.float64,0xbfd096b234a12d64,0xbfd0c7beca2c6605,1 +np.float64,0xbfef3363da7e66c8,0xbff58c98dde6c27c,1 +np.float64,0x3fd51b01ddaa3604,0x3fd582109d89ead1,1 +np.float64,0x3fea0f10ff741e22,0x3fee736c2d2a2067,1 +np.float64,0x3fc276e7b724edd0,0x3fc28774520bc6d4,1 +np.float64,0xbfef9abc9f7f3579,0xbff69d49762b1889,1 +np.float64,0x3fe1539ec0e2a73e,0x3fe24f370b7687d0,1 +np.float64,0x3fad72350c3ae460,0x3fad765e7766682a,1 +np.float64,0x3fa289a47c251340,0x3fa28aae12f41646,1 +np.float64,0xbfe5c488e5eb8912,0xbfe7f05d7e7dcddb,1 +np.float64,0xbfc22ef1d7245de4,0xbfc23ebeb990a1b8,1 +np.float64,0x3fe59a0b80eb3418,0x3fe7b695fdcba1de,1 +np.float64,0xbfe9cad619f395ac,0xbfedff0514d91e2c,1 +np.float64,0x3fc8bc74eb3178e8,0x3fc8e48cb22da666,1 +np.float64,0xbfc5389a3f2a7134,0xbfc551cd6febc544,1 +np.float64,0x3fce82feb33d0600,0x3fceceecce2467ef,1 +np.float64,0x3fda346791b468d0,0x3fdaff95154a4ca6,1 +np.float64,0x3fd04501fea08a04,0x3fd073397b32607e,1 +np.float64,0xbfb6be498a2d7c90,0xbfb6c5f93aeb0e57,1 +np.float64,0x3fe1f030dd63e062,0x3fe30ad8fb97cce0,1 +np.float64,0xbfee3fb36dfc7f67,0xbff3d0a5e380b86f,1 +np.float64,0xbfa876773c30ecf0,0xbfa878d9d3df6a3f,1 +np.float64,0x3fdb58296eb6b054,0x3fdc40ceffb17f82,1 +np.float64,0xbfea16b5d8742d6c,0xbfee809b99fd6adc,1 +np.float64,0xbfdc5062b6b8a0c6,0xbfdd547623275fdb,1 +np.float64,0x3fef6db242fedb64,0x3ff61ab4cdaef467,1 +np.float64,0xbfc9f778f933eef0,0xbfca25eef1088167,1 +np.float64,0xbfd22063eba440c8,0xbfd260c8766c69cf,1 +np.float64,0x3fdd2379f2ba46f4,0x3fde40b025cb1ffa,1 +np.float64,0xbfea967af2f52cf6,0xbfef61a178774636,1 +np.float64,0x3fe4f5b49fe9eb6a,0x3fe6da8311a5520e,1 +np.float64,0x3feccde17b799bc2,0x3ff1ebd0ea228b71,1 +np.float64,0x3fe1bb76506376ec,0x3fe2cb56fca01840,1 +np.float64,0xbfef94e583ff29cb,0xbff68aeab8ba75a2,1 +np.float64,0x3fed024a55fa0494,0x3ff228ea5d456e9d,1 +np.float64,0xbfe877b2a8f0ef65,0xbfebdaa1a4712459,1 +np.float64,0x3fef687a8d7ed0f6,0x3ff60cf5fef8d448,1 +np.float64,0xbfeeb2dc8afd65b9,0xbff48dda6a906cd6,1 +np.float64,0x3fdb2e28aeb65c50,0x3fdc12620655eb7a,1 +np.float64,0x3fedc1863afb830c,0x3ff31ae823315e83,1 +np.float64,0xbfe6b1bb546d6376,0xbfe93a38163e3a59,1 +np.float64,0x3fe479c78468f390,0x3fe637e5c0fc5730,1 +np.float64,0x3fbad1fade35a3f0,0x3fbade9a43ca05cf,1 +np.float64,0xbfe2d1c563e5a38b,0xbfe41e712785900c,1 +np.float64,0xbfc08c33ed211868,0xbfc09817a752d500,1 +np.float64,0xbfecce0935f99c12,0xbff1ebfe84524037,1 +np.float64,0x3fce4ef0e73c9de0,0x3fce995638a3dc48,1 +np.float64,0xbfd2fb2343a5f646,0xbfd345592517ca18,1 +np.float64,0x3fd848f7cdb091f0,0x3fd8e8bee5f7b49a,1 +np.float64,0x3fe532b7d2ea6570,0x3fe72b9ac747926a,1 +np.float64,0x3fd616aadcac2d54,0x3fd68d692c5cad42,1 +np.float64,0x3fd7720eb3aee41c,0x3fd801206a0e1e43,1 +np.float64,0x3fee835a35fd06b4,0x3ff43c7175eb7a54,1 +np.float64,0xbfe2e8f70b65d1ee,0xbfe43b2800a947a7,1 +np.float64,0xbfed38f45d7a71e9,0xbff26acd6bde7174,1 +np.float64,0xbfc0c62661218c4c,0xbfc0d28964d66120,1 +np.float64,0x3fe97940bef2f282,0x3fed76b986a74ee3,1 +np.float64,0x3fc96f7dc532def8,0x3fc99b20044c8fcf,1 +np.float64,0xbfd60201eeac0404,0xbfd677675efaaedc,1 +np.float64,0x3fe63c0867ec7810,0x3fe894f060200140,1 +np.float64,0xbfef6144b37ec289,0xbff5fa589a515ba8,1 +np.float64,0xbfde2da0c8bc5b42,0xbfdf6d0b59e3232a,1 +np.float64,0xbfd7401612ae802c,0xbfd7cb74ddd413b9,1 +np.float64,0x3fe41c012de83802,0x3fe5be9d87da3f82,1 +np.float64,0x3fdf501609bea02c,0x3fe05c1d96a2270b,1 +np.float64,0x3fcf9fa1233f3f40,0x3fcff45598e72f07,1 +np.float64,0x3fd4e3895ea9c714,0x3fd547580d8392a2,1 +np.float64,0x3fe1e8ff5fe3d1fe,0x3fe3022a0b86a2ab,1 +np.float64,0xbfe0aa55956154ab,0xbfe18768823da589,1 +np.float64,0x3fb2a0aa26254150,0x3fb2a4e1faff1c93,1 +np.float64,0x3fd3823417a70468,0x3fd3d2f808dbb167,1 +np.float64,0xbfaed323643da640,0xbfaed7e9bef69811,1 +np.float64,0x3fe661e8c4ecc3d2,0x3fe8c9c535f43c16,1 +np.float64,0xbfa429777c2852f0,0xbfa42acd38ba02a6,1 +np.float64,0x3fb5993ea22b3280,0x3fb59fd353e47397,1 +np.float64,0x3fee62d21efcc5a4,0x3ff40788f9278ade,1 +np.float64,0xbf813fb810227f80,0xbf813fc56d8f3c53,1 +np.float64,0x3fd56205deaac40c,0x3fd5cd59671ef193,1 +np.float64,0x3fd31a4de5a6349c,0x3fd365fe401b66e8,1 +np.float64,0xbfec7cc7a478f98f,0xbff190cf69703ca4,1 +np.float64,0xbf755881a02ab100,0xbf755887f52e7794,1 +np.float64,0x3fdd1c92e6ba3924,0x3fde38efb4e8605c,1 +np.float64,0x3fdf49da80be93b4,0x3fe0588af8dd4a34,1 +np.float64,0x3fe1fcdbf2e3f9b8,0x3fe31a27b9d273f2,1 +np.float64,0x3fe2a0f18be541e4,0x3fe3e23b159ce20f,1 +np.float64,0xbfed0f1561fa1e2b,0xbff23820fc0a54ca,1 +np.float64,0x3fe34a006c669400,0x3fe4b419b9ed2b83,1 +np.float64,0xbfd51be430aa37c8,0xbfd583005a4d62e7,1 +np.float64,0x3fe5ec4e336bd89c,0x3fe826caad6b0f65,1 +np.float64,0xbfdad71b1fb5ae36,0xbfdbb25bef8b53d8,1 +np.float64,0xbfe8eac2d871d586,0xbfec8f8cac7952f9,1 +np.float64,0xbfe1d5aef663ab5e,0xbfe2eae14b7ccdfd,1 +np.float64,0x3fec11d3157823a6,0x3ff11e8279506753,1 +np.float64,0xbfe67ff1166cffe2,0xbfe8f3e61c1dfd32,1 +np.float64,0xbfd101eecda203de,0xbfd136e0e9557022,1 +np.float64,0x3fde6c9e5cbcd93c,0x3fdfb48ee7efe134,1 +np.float64,0x3fec3ede9c787dbe,0x3ff14dead1e5cc1c,1 +np.float64,0x3fe7a022086f4044,0x3fea93ce2980b161,1 +np.float64,0xbfc3b2b1b7276564,0xbfc3c6d02d60bb21,1 +np.float64,0x7ff0000000000000,0x7ff8000000000000,1 +np.float64,0x3fe60b5647ec16ac,0x3fe8517ef0544b40,1 +np.float64,0xbfd20ab654a4156c,0xbfd24a2f1b8e4932,1 +np.float64,0xbfe4aa1e2f69543c,0xbfe677005cbd2646,1 +np.float64,0xbfc831cc0b306398,0xbfc8574910d0b86d,1 +np.float64,0xbfc3143495262868,0xbfc3267961b79198,1 +np.float64,0x3fc14d64c1229ac8,0x3fc15afea90a319d,1 +np.float64,0x3fc0a5a207214b48,0x3fc0b1bd2f15c1b0,1 +np.float64,0xbfc0b8351521706c,0xbfc0c4792672d6db,1 +np.float64,0xbfdc383600b8706c,0xbfdd398429e163bd,1 +np.float64,0x3fd9e17321b3c2e8,0x3fdaa4c4d140a622,1 +np.float64,0xbfd44f079ea89e10,0xbfd4aa7d6deff4ab,1 +np.float64,0xbfc3de52a927bca4,0xbfc3f2f8f65f4c3f,1 +np.float64,0x3fe7779d566eef3a,0x3fea57f8592dbaad,1 +np.float64,0xbfe309039e661207,0xbfe462f47f9a64e5,1 +np.float64,0x3fd8e06d08b1c0dc,0x3fd98cc946e440a6,1 +np.float64,0x3fdde66c9ebbccd8,0x3fdf1c68009a8dc1,1 +np.float64,0x3fd4369c6ba86d38,0x3fd490bf460a69e4,1 +np.float64,0xbfe132252fe2644a,0xbfe22775e109cc2e,1 +np.float64,0x3fee15483c7c2a90,0x3ff39111de89036f,1 +np.float64,0xbfc1d5ee8123abdc,0xbfc1e4d66c6871a5,1 +np.float64,0x3fc851c52b30a388,0x3fc877d93fb4ae1a,1 +np.float64,0x3fdaade707b55bd0,0x3fdb85001661fffe,1 +np.float64,0xbfe79fb7f96f3f70,0xbfea9330ec27ac10,1 +np.float64,0xbfe8b0f725f161ee,0xbfec3411c0e4517a,1 +np.float64,0xbfea79f5f374f3ec,0xbfef2e9dd9270488,1 +np.float64,0x3fe0b5fe5b616bfc,0x3fe19512a36a4534,1 +np.float64,0xbfad7c622c3af8c0,0xbfad808fea96a804,1 +np.float64,0xbfe3e24dbce7c49c,0xbfe574b4c1ea9818,1 +np.float64,0xbfe80b038af01607,0xbfeb33fec279576a,1 +np.float64,0xbfef69e2ea7ed3c6,0xbff610a5593a18bc,1 +np.float64,0x3fdcc0bb39b98178,0x3fddd1f8c9a46430,1 +np.float64,0xbfba39976a347330,0xbfba4563bb5369a4,1 +np.float64,0xbfebf9768ef7f2ed,0xbff10548ab725f74,1 +np.float64,0xbfec21c066f84381,0xbff12f2803ba052f,1 +np.float64,0xbfca216a6b3442d4,0xbfca50c5e1e5748e,1 +np.float64,0x3fd5e40da4abc81c,0x3fd65783f9a22946,1 +np.float64,0x3fc235ca17246b98,0x3fc245a8f453173f,1 +np.float64,0x3fecb5b867796b70,0x3ff1d046a0bfda69,1 +np.float64,0x3fcb457fef368b00,0x3fcb7b6daa8165a7,1 +np.float64,0xbfa5ed6f7c2bdae0,0xbfa5ef27244e2e42,1 +np.float64,0x3fecf618a1f9ec32,0x3ff21a86cc104542,1 +np.float64,0x3fe9d95413f3b2a8,0x3fee178dcafa11fc,1 +np.float64,0xbfe93a5357f274a7,0xbfed0f9a565da84a,1 +np.float64,0xbfeb9e45ff773c8c,0xbff0a93cab8e258d,1 +np.float64,0x3fcbd9d0bd37b3a0,0x3fcc134e87cae241,1 +np.float64,0x3fe55d4db76aba9c,0x3fe764a0e028475a,1 +np.float64,0xbfc8a6fc71314df8,0xbfc8ceaafbfc59a7,1 +np.float64,0x3fe0615fa660c2c0,0x3fe1323611c4cbc2,1 +np.float64,0x3fb965558632cab0,0x3fb9700b84de20ab,1 +np.float64,0x8000000000000000,0x8000000000000000,1 +np.float64,0x3fe76776c6eeceee,0x3fea40403e24a9f1,1 +np.float64,0x3fe3b7f672676fec,0x3fe53ece71a1a1b1,1 +np.float64,0xbfa9b82ba4337050,0xbfa9baf15394ca64,1 +np.float64,0xbfe31faf49663f5e,0xbfe47f31b1ca73dc,1 +np.float64,0xbfcc4c6beb3898d8,0xbfcc88c5f814b2c1,1 +np.float64,0x3fd481530aa902a8,0x3fd4df8df03bc155,1 +np.float64,0x3fd47593b8a8eb28,0x3fd4d327ab78a1a8,1 +np.float64,0x3fd70e6ccbae1cd8,0x3fd7962fe8b63d46,1 +np.float64,0x3fd25191f7a4a324,0x3fd2941623c88e02,1 +np.float64,0x3fd0603ef0a0c07c,0x3fd08f64e97588dc,1 +np.float64,0xbfc653bae52ca774,0xbfc6711e5e0d8ea9,1 +np.float64,0xbfd11db8fea23b72,0xbfd153b63c6e8812,1 +np.float64,0xbfea9bde25f537bc,0xbfef6b52268e139a,1 +np.float64,0x1,0x1,1 +np.float64,0xbfefd3806d7fa701,0xbff776dcef9583ca,1 +np.float64,0xbfe0fb8cfde1f71a,0xbfe1e6e2e774a8f8,1 +np.float64,0x3fea384534f4708a,0x3feebadaa389be0d,1 +np.float64,0x3feff761c97feec4,0x3ff866157b9d072d,1 +np.float64,0x3fe7131ccb6e263a,0x3fe9c58b4389f505,1 +np.float64,0x3fe9084f7872109e,0x3fecbed0355dbc8f,1 +np.float64,0x3f708e89e0211d00,0x3f708e8cd4946b9e,1 +np.float64,0xbfe39185f067230c,0xbfe50e1cd178244d,1 +np.float64,0x3fd67cc1a9acf984,0x3fd6fa514784b48c,1 +np.float64,0xbfecaef005f95de0,0xbff1c89c9c3ef94a,1 +np.float64,0xbfe12eec81e25dd9,0xbfe223a4285bba9a,1 +np.float64,0x3fbe7f9faa3cff40,0x3fbe92363525068d,1 +np.float64,0xbfe1950b2b632a16,0xbfe29d45fc1e4ce9,1 +np.float64,0x3fe45049e6e8a094,0x3fe6020de759e383,1 +np.float64,0x3fe4d10c8969a21a,0x3fe6aa1fe42cbeb9,1 +np.float64,0xbfe9d04658f3a08d,0xbfee08370a0dbf0c,1 +np.float64,0x3fe14fb314e29f66,0x3fe24a8d73663521,1 +np.float64,0xbfef4abfe4fe9580,0xbff5c2c1ff1250ca,1 +np.float64,0xbfe6162b366c2c56,0xbfe86073ac3c6243,1 +np.float64,0x3feffe781e7ffcf0,0x3ff8d2cbedd6a1b5,1 +np.float64,0xbff0000000000000,0xbff921fb54442d18,1 +np.float64,0x3fc1dc45ad23b888,0x3fc1eb3d9bddda58,1 +np.float64,0xbfe793f6fcef27ee,0xbfea81c93d65aa64,1 +np.float64,0x3fdef6d2bbbdeda4,0x3fe029079d42efb5,1 +np.float64,0xbfdf0ac479be1588,0xbfe0346dbc95963f,1 +np.float64,0xbfd33927d7a67250,0xbfd38653f90a5b73,1 +np.float64,0xbfe248b072e49161,0xbfe37631ef6572e1,1 +np.float64,0xbfc8ceb6af319d6c,0xbfc8f7288657f471,1 +np.float64,0x3fdd7277fcbae4f0,0x3fde99886e6766ef,1 +np.float64,0xbfe0d30c6561a619,0xbfe1b72f90bf53d6,1 +np.float64,0xbfcb0fe07d361fc0,0xbfcb448e2eae9542,1 +np.float64,0xbfe351f57fe6a3eb,0xbfe4be13eef250f2,1 +np.float64,0x3fe85ec02cf0bd80,0x3febb407e2e52e4c,1 +np.float64,0x3fc8bc59b53178b0,0x3fc8e470f65800ec,1 +np.float64,0xbfd278d447a4f1a8,0xbfd2bd133c9c0620,1 +np.float64,0x3feda5cfd87b4ba0,0x3ff2f5ab4324f43f,1 +np.float64,0xbfd2b32a36a56654,0xbfd2fa09c36afd34,1 +np.float64,0xbfed4a81cb7a9504,0xbff28077a4f4fff4,1 +np.float64,0x3fdf079bf9be0f38,0x3fe0329f7fb13f54,1 +np.float64,0x3fd14097f6a28130,0x3fd177e9834ec23f,1 +np.float64,0xbfaeab11843d5620,0xbfaeafc5531eb6b5,1 +np.float64,0xbfac3f8c14387f20,0xbfac433893d53360,1 +np.float64,0xbfc139d7ed2273b0,0xbfc14743adbbe660,1 +np.float64,0x3fe78cb02cef1960,0x3fea7707f76edba9,1 +np.float64,0x3fefe16b41ffc2d6,0x3ff7bff36a7aa7b8,1 +np.float64,0x3fec5260d378a4c2,0x3ff162c588b0da38,1 +np.float64,0x3fedb146f17b628e,0x3ff304f90d3a15d1,1 +np.float64,0x3fd1fd45f7a3fa8c,0x3fd23c2dc3929e20,1 +np.float64,0x3fe0898a5ee11314,0x3fe1610c63e726eb,1 +np.float64,0x3fe7719946eee332,0x3fea4f205eecb59f,1 +np.float64,0x3fe955218972aa44,0x3fed3b530c1f7651,1 +np.float64,0x3fe0ccbf4461997e,0x3fe1afc7b4587836,1 +np.float64,0xbfe9204314f24086,0xbfece5605780e346,1 +np.float64,0xbfe552017feaa403,0xbfe755773cbd74d5,1 +np.float64,0x3fd8ce4b32b19c98,0x3fd9791c8dd44eae,1 +np.float64,0x3fef89acd9ff135a,0x3ff668f78adf7ced,1 +np.float64,0x3fc9d713ad33ae28,0x3fca04da6c293bbd,1 +np.float64,0xbfe22d9c4de45b38,0xbfe3553effadcf92,1 +np.float64,0x3fa5cda38c2b9b40,0x3fa5cf53c5787482,1 +np.float64,0x3fa878ebdc30f1e0,0x3fa87b4f2bf1d4c3,1 +np.float64,0x3fe8030353700606,0x3feb27e196928789,1 +np.float64,0x3fb50607222a0c10,0x3fb50c188ce391e6,1 +np.float64,0x3fd9ba4ab4b37494,0x3fda79fa8bd40f45,1 +np.float64,0x3fb564598e2ac8b0,0x3fb56abe42d1ba13,1 +np.float64,0xbfd1177c83a22efa,0xbfd14d3d7ef30cc4,1 +np.float64,0xbfd952cec7b2a59e,0xbfda09215d17c0ac,1 +np.float64,0x3fe1d8066663b00c,0x3fe2edb35770b8dd,1 +np.float64,0xbfc89427a3312850,0xbfc8bb7a7c389497,1 +np.float64,0xbfe86ebfd3f0dd80,0xbfebccc2ba0f506c,1 +np.float64,0x3fc390578b2720b0,0x3fc3a40cb7f5f728,1 +np.float64,0xbfd122f9b8a245f4,0xbfd15929dc57a897,1 +np.float64,0x3f8d0636d03a0c80,0x3f8d06767de576df,1 +np.float64,0xbfe4b55d8b696abb,0xbfe685be537a9637,1 +np.float64,0xbfdfd51cf9bfaa3a,0xbfe0a894fcff0c76,1 +np.float64,0xbfd37c1f52a6f83e,0xbfd3cc9593c37aad,1 +np.float64,0x3fd0e8283ea1d050,0x3fd11c25c800785a,1 +np.float64,0x3fd3160784a62c10,0x3fd36183a6c2880c,1 +np.float64,0x3fd4c66e57a98cdc,0x3fd5288fe3394eff,1 +np.float64,0x3fee2f7e3afc5efc,0x3ff3b8063eb30cdc,1 +np.float64,0xbfe526773a6a4cee,0xbfe71b4364215b18,1 +np.float64,0x3fea01181e740230,0x3fee5b65eccfd130,1 +np.float64,0xbfe51c03f76a3808,0xbfe70d5919d37587,1 +np.float64,0x3fd97e1375b2fc28,0x3fda3845da40b22b,1 +np.float64,0x3fd5c14a14ab8294,0x3fd632890d07ed03,1 +np.float64,0xbfec9b474279368e,0xbff1b28f50584fe3,1 +np.float64,0x3fe0139ca860273a,0x3fe0d7fc377f001c,1 +np.float64,0x3fdb080c9db61018,0x3fdbe85056358fa0,1 +np.float64,0xbfdd72ceb1bae59e,0xbfde99ea171661eb,1 +np.float64,0xbfe64e934fec9d26,0xbfe8aec2ef24be63,1 +np.float64,0x3fd1036a93a206d4,0x3fd1386adabe01bd,1 +np.float64,0x3febc9d4a5f793aa,0x3ff0d4c069f1e67d,1 +np.float64,0xbfe547a16fea8f43,0xbfe747902fe6fb4d,1 +np.float64,0x3fc289b0f9251360,0x3fc29a709de6bdd9,1 +np.float64,0xbfe694494a6d2892,0xbfe9108f3dc133e2,1 +np.float64,0x3fd827dfe4b04fc0,0x3fd8c4fe40532b91,1 +np.float64,0xbfe8b89418f17128,0xbfec400c5a334b2e,1 +np.float64,0x3fed5605147aac0a,0x3ff28ed1f612814a,1 +np.float64,0xbfed36af31fa6d5e,0xbff26804e1f71af0,1 +np.float64,0x3fdbb01c02b76038,0x3fdca2381558bbf0,1 +np.float64,0x3fe2a951666552a2,0x3fe3ec88f780f9e6,1 +np.float64,0x3fe662defbecc5be,0x3fe8cb1dbfca98ab,1 +np.float64,0x3fd098b1b3a13164,0x3fd0c9d064e4eaf2,1 +np.float64,0x3fefa10edeff421e,0x3ff6b1c6187b18a8,1 +np.float64,0xbfec4feb7a789fd7,0xbff16021ef37a219,1 +np.float64,0x3fd8e415bbb1c82c,0x3fd990c1f8b786bd,1 +np.float64,0xbfead5a09275ab41,0xbfefd44fab5b4f6e,1 +np.float64,0xbfe8666c16f0ccd8,0xbfebbfe0c9f2a9ae,1 +np.float64,0x3fdc962132b92c44,0x3fdda2525a6f406c,1 +np.float64,0xbfe2037f03e406fe,0xbfe3222ec2a3449e,1 +np.float64,0xbfec82c27e790585,0xbff197626ea9df1e,1 +np.float64,0x3fd2b4e03ca569c0,0x3fd2fbd3c7fda23e,1 +np.float64,0xbfe9b0dee5f361be,0xbfedd34f6d3dfe8a,1 +np.float64,0x3feef45cd17de8ba,0x3ff508180687b591,1 +np.float64,0x3f82c39bf0258700,0x3f82c3ad24c3b3f1,1 +np.float64,0xbfca848cfd350918,0xbfcab612ce258546,1 +np.float64,0x3fd6442aaaac8854,0x3fd6bdea54016e48,1 +np.float64,0x3fe550799e6aa0f4,0x3fe75369c9ea5b1e,1 +np.float64,0xbfe0e9d5a361d3ac,0xbfe1d20011139d89,1 +np.float64,0x3fbfc9ff1e3f9400,0x3fbfdf0ea6885c80,1 +np.float64,0xbfa187e8b4230fd0,0xbfa188c95072092e,1 +np.float64,0x3fcd28c9533a5190,0x3fcd6ae879c21b47,1 +np.float64,0x3fc6227ec52c4500,0x3fc63f1fbb441d29,1 +np.float64,0x3fe9b7a2ed736f46,0x3feddeab49b2d176,1 +np.float64,0x3fd4aee93da95dd4,0x3fd50fb3b71e0339,1 +np.float64,0xbfe164dacf62c9b6,0xbfe263bb2f7dd5d9,1 +np.float64,0x3fec62e525f8c5ca,0x3ff17496416d9921,1 +np.float64,0x3fdd363ee0ba6c7c,0x3fde55c6a49a5f86,1 +np.float64,0x3fe65cbf75ecb97e,0x3fe8c28d31ff3ebd,1 +np.float64,0xbfe76d27ca6eda50,0xbfea4899e3661425,1 +np.float64,0xbfc305738d260ae8,0xbfc3178dcfc9d30f,1 +np.float64,0xbfd3aa2a54a75454,0xbfd3fcf1e1ce8328,1 +np.float64,0x3fd1609fc9a2c140,0x3fd1992efa539b9f,1 +np.float64,0xbfac1291bc382520,0xbfac162cc7334b4d,1 +np.float64,0xbfedb461ea7b68c4,0xbff309247850455d,1 +np.float64,0xbfe8d2adf8f1a55c,0xbfec6947be90ba92,1 +np.float64,0xbfd7128965ae2512,0xbfd79a9855bcfc5a,1 +np.float64,0x3fe8deb09471bd62,0x3fec7c56b3aee531,1 +np.float64,0xbfe5f4d329ebe9a6,0xbfe8327ea8189af8,1 +np.float64,0xbfd3b46ac9a768d6,0xbfd407b80b12ff17,1 +np.float64,0x3fec899d7cf9133a,0x3ff19ef26baca36f,1 +np.float64,0xbfec192fd5783260,0xbff126306e507fd0,1 +np.float64,0x3fe945bdaef28b7c,0x3fed222f787310bf,1 +np.float64,0xbfeff9635d7ff2c7,0xbff87d6773f318eb,1 +np.float64,0xbfd604b81cac0970,0xbfd67a4aa852559a,1 +np.float64,0x3fcd1cc9d53a3990,0x3fcd5e962e237c24,1 +np.float64,0xbfed77b0fffaef62,0xbff2b97a1c9b6483,1 +np.float64,0xbfc9c69325338d28,0xbfc9f401500402fb,1 +np.float64,0xbfdf97e246bf2fc4,0xbfe0855601ea9db3,1 +np.float64,0x3fc7e6304f2fcc60,0x3fc80a4e718504cd,1 +np.float64,0x3fec3b599e7876b4,0x3ff14a2d1b9c68e6,1 +np.float64,0xbfe98618e1f30c32,0xbfed8bfbb31c394a,1 +np.float64,0xbfe59b3c0feb3678,0xbfe7b832d6df81de,1 +np.float64,0xbfe54ce2fe6a99c6,0xbfe74e9a85be4116,1 +np.float64,0x3fc9db49cb33b690,0x3fca092737ef500a,1 +np.float64,0xbfb4a922ae295248,0xbfb4aee4e39078a9,1 +np.float64,0xbfd0e542e0a1ca86,0xbfd11925208d66af,1 +np.float64,0x3fd70543f2ae0a88,0x3fd78c5e9238a3ee,1 +np.float64,0x3fd67f7a7facfef4,0x3fd6fd3998df8545,1 +np.float64,0xbfe40b643d6816c8,0xbfe5a947e427f298,1 +np.float64,0xbfcd85f69b3b0bec,0xbfcdcaa24b75f1a3,1 +np.float64,0x3fec705fb4f8e0c0,0x3ff1833c82163ee2,1 +np.float64,0x3fb37650ea26eca0,0x3fb37b20c16fb717,1 +np.float64,0x3fe5ebfa55ebd7f4,0x3fe826578d716e70,1 +np.float64,0x3fe991dfe5f323c0,0x3fed9f8a4bf1f588,1 +np.float64,0xbfd658bd0aacb17a,0xbfd6d3dd06e54900,1 +np.float64,0xbfc24860252490c0,0xbfc258701a0b9290,1 +np.float64,0xbfefb8d763ff71af,0xbff705b6ea4a569d,1 +np.float64,0x3fb8fcb4ae31f970,0x3fb906e809e7899f,1 +np.float64,0x3fce6343cb3cc688,0x3fceae41d1629625,1 +np.float64,0xbfd43d5a11a87ab4,0xbfd497da25687e07,1 +np.float64,0xbfe9568851f2ad11,0xbfed3d9e5fe83a76,1 +np.float64,0x3fe1b66153e36cc2,0x3fe2c53c7e016271,1 +np.float64,0x3fef27452bfe4e8a,0x3ff571b3486ed416,1 +np.float64,0x3fca87c0a7350f80,0x3fcab958a7bb82d4,1 +np.float64,0xbfd8776a8fb0eed6,0xbfd91afaf2f50edf,1 +np.float64,0x3fe9522a76f2a454,0x3fed3679264e1525,1 +np.float64,0x3fea14ff2cf429fe,0x3fee7da6431cc316,1 +np.float64,0x3fe970618bf2e0c4,0x3fed68154d54dd97,1 +np.float64,0x3fd3410cfca68218,0x3fd38e9b21792240,1 +np.float64,0xbf6a8070c0350100,0xbf6a8073c7c34517,1 +np.float64,0xbfbe449de23c8938,0xbfbe56c8e5e4d98b,1 +np.float64,0x3fedbc92e27b7926,0x3ff314313216d8e6,1 +np.float64,0xbfe3be4706677c8e,0xbfe546d3ceb85aea,1 +np.float64,0x3fe30cd6d76619ae,0x3fe467b6f2664a8d,1 +np.float64,0x3fd7d69b21afad38,0x3fd86d54284d05ad,1 +np.float64,0xbfe501001fea0200,0xbfe6e978afcff4d9,1 +np.float64,0xbfe44ba3d8e89748,0xbfe5fc0a31cd1e3e,1 +np.float64,0x3fec52f7c078a5f0,0x3ff16367acb209b2,1 +np.float64,0xbfcb19efcb3633e0,0xbfcb4ed9235a7d47,1 +np.float64,0xbfab86796c370cf0,0xbfab89df7bf15710,1 +np.float64,0xbfb962feda32c600,0xbfb96db1e1679c98,1 +np.float64,0x3fe0dd14e861ba2a,0x3fe1c2fc72810567,1 +np.float64,0x3fe41bcc6de83798,0x3fe5be59b7f9003b,1 +np.float64,0x3fc82f4c4f305e98,0x3fc854bd9798939f,1 +np.float64,0xbfcd143a613a2874,0xbfcd55cbd1619d84,1 +np.float64,0xbfd52da61baa5b4c,0xbfd595d0b3543439,1 +np.float64,0xbfb71b4a8e2e3698,0xbfb7235a4ab8432f,1 +np.float64,0xbfec141a19782834,0xbff120e1e39fc856,1 +np.float64,0xbfdba9319db75264,0xbfdc9a8ca2578bb2,1 +np.float64,0xbfbce5d74639cbb0,0xbfbcf5a4878cfa51,1 +np.float64,0x3fde67f7b3bccff0,0x3fdfaf45a9f843ad,1 +np.float64,0xbfe12d87bc625b10,0xbfe221fd4476eb71,1 +np.float64,0x3fe35b8f6be6b71e,0x3fe4ca20f65179e1,1 +np.float64,0xbfdbada1d3b75b44,0xbfdc9f78b19f93d1,1 +np.float64,0xbfc60159c52c02b4,0xbfc61d79b879f598,1 +np.float64,0x3fd6b81c38ad7038,0x3fd739c27bfa16d8,1 +np.float64,0xbfd646a253ac8d44,0xbfd6c08c19612bbb,1 +np.float64,0xbfe6babef0ed757e,0xbfe94703d0bfa311,1 +np.float64,0xbfed5671f1faace4,0xbff28f5a3f3683d0,1 +np.float64,0x3fc01d1e85203a40,0x3fc02817ec0dfd38,1 +np.float64,0xbfe9188a61f23115,0xbfecd8eb5da84223,1 +np.float64,0x3fdca3bab9b94774,0x3fddb1868660c239,1 +np.float64,0xbfa255750c24aaf0,0xbfa25675f7b36343,1 +np.float64,0x3fb3602db626c060,0x3fb364ed2d5b2876,1 +np.float64,0xbfd30a14bda6142a,0xbfd354ff703b8862,1 +np.float64,0xbfe1cfe381639fc7,0xbfe2e3e720b968c8,1 +np.float64,0xbfd2af6a4fa55ed4,0xbfd2f61e190bcd1f,1 +np.float64,0xbfe93c50937278a1,0xbfed12d64bb10d73,1 +np.float64,0x3fddd8bc44bbb178,0x3fdf0ced7f9005cc,1 +np.float64,0x3fdb2bc73cb65790,0x3fdc0fc0e18e425e,1 +np.float64,0xbfd073f6aba0e7ee,0xbfd0a3cb5468a961,1 +np.float64,0x3fed4bad7b7a975a,0x3ff281ebeb75e414,1 +np.float64,0xbfdc75b50bb8eb6a,0xbfdd7e1a7631cb22,1 +np.float64,0x3fd458a90fa8b154,0x3fd4b4a5817248ce,1 +np.float64,0x3feead5db57d5abc,0x3ff484286fab55ff,1 +np.float64,0x3fb3894382271280,0x3fb38e217b4e7905,1 +np.float64,0xffefffffffffffff,0x7ff8000000000000,1 +np.float64,0xbfe428212ae85042,0xbfe5ce36f226bea8,1 +np.float64,0xbfc08b39f7211674,0xbfc0971b93ebc7ad,1 +np.float64,0xbfc2e7cf5525cfa0,0xbfc2f994eb72b623,1 +np.float64,0xbfdb0d85afb61b0c,0xbfdbee5a2de3c5db,1 +np.float64,0xfff0000000000000,0x7ff8000000000000,1 +np.float64,0xbfd0d36af7a1a6d6,0xbfd106a5f05ef6ff,1 +np.float64,0xbfc333d0912667a0,0xbfc3467162b7289a,1 +np.float64,0x3fcdababc53b5758,0x3fcdf16458c20fa8,1 +np.float64,0x3fd0821b38a10438,0x3fd0b26e3e0b9185,1 +np.float64,0x0,0x0,1 +np.float64,0x3feb7f70edf6fee2,0x3ff08ae81854bf20,1 +np.float64,0x3fe6e075716dc0ea,0x3fe97cc5254be6ff,1 +np.float64,0x3fea13b682f4276e,0x3fee7b6f18073b5b,1 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-arcsinh.csv b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-arcsinh.csv new file mode 100644 index 0000000000000000000000000000000000000000..9eedb1a13f03dc5deade22dad00fcc797d2dd248 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-arcsinh.csv @@ -0,0 +1,1429 @@ +dtype,input,output,ulperrortol +np.float32,0xbf24142a,0xbf1a85ef,2 +np.float32,0x3e71cf91,0x3e6f9e37,2 +np.float32,0xe52a7,0xe52a7,2 +np.float32,0x3ef1e074,0x3ee9add9,2 +np.float32,0x806160ac,0x806160ac,2 +np.float32,0x7e2d59a2,0x42af4798,2 +np.float32,0xbf32cac9,0xbf26bf96,2 +np.float32,0x3f081701,0x3f026142,2 +np.float32,0x3f23cc88,0x3f1a499c,2 +np.float32,0xbf090d94,0xbf033ad0,2 +np.float32,0x803af2fc,0x803af2fc,2 +np.float32,0x807eb17e,0x807eb17e,2 +np.float32,0x5c0d8e,0x5c0d8e,2 +np.float32,0x3f7b79d2,0x3f5e6b1d,2 +np.float32,0x806feeae,0x806feeae,2 +np.float32,0x3e4b423a,0x3e49f274,2 +np.float32,0x3f49e5ac,0x3f394a41,2 +np.float32,0x3f18cd4e,0x3f10ef35,2 +np.float32,0xbed75734,0xbed17322,2 +np.float32,0x7f591151,0x42b28085,2 +np.float32,0xfefe9da6,0xc2b16f51,2 +np.float32,0xfeac90fc,0xc2b0a82a,2 +np.float32,0x805c198e,0x805c198e,2 +np.float32,0x7f66d6df,0x42b2a004,2 +np.float32,0x505438,0x505438,2 +np.float32,0xbf39a209,0xbf2c5255,2 +np.float32,0x7fa00000,0x7fe00000,2 +np.float32,0xc84cb,0xc84cb,2 +np.float32,0x7f07d6f5,0x42b19088,2 +np.float32,0x79d7e4,0x79d7e4,2 +np.float32,0xff32f6a0,0xc2b21db1,2 +np.float32,0x7c005c05,0x42a9222e,2 +np.float32,0x3ec449aa,0x3ebfc5ae,2 +np.float32,0x800ec323,0x800ec323,2 +np.float32,0xff1c904c,0xc2b1d93a,2 +np.float32,0x7f4eca52,0x42b267b0,2 +np.float32,0x3ee06540,0x3ed9c514,2 +np.float32,0x6aab4,0x6aab4,2 +np.float32,0x3e298d8c,0x3e28c99e,2 +np.float32,0xbf38d162,0xbf2ba94a,2 +np.float32,0x2d9083,0x2d9083,2 +np.float32,0x7eae5032,0x42b0ad52,2 +np.float32,0x3ead5b3c,0x3eaa3443,2 +np.float32,0x806fef66,0x806fef66,2 +np.float32,0x3f5b614e,0x3f46ca71,2 +np.float32,0xbf4c906a,0xbf3b60fc,2 +np.float32,0x8049453e,0x8049453e,2 +np.float32,0x3d305220,0x3d304432,2 +np.float32,0x2e1a89,0x2e1a89,2 +np.float32,0xbf4e74ec,0xbf3cdacf,2 +np.float32,0x807a827a,0x807a827a,2 +np.float32,0x80070745,0x80070745,2 +np.float32,0xbe1ba2fc,0xbe1b0b28,2 +np.float32,0xbe5131d0,0xbe4fc421,2 +np.float32,0x5bfd98,0x5bfd98,2 +np.float32,0xbd8e1a48,0xbd8dfd27,2 +np.float32,0x8006c160,0x8006c160,2 +np.float32,0x346490,0x346490,2 +np.float32,0xbdbdf060,0xbdbdaaf0,2 +np.float32,0x3ea9d0c4,0x3ea6d8c7,2 +np.float32,0xbf2aaa28,0xbf200916,2 +np.float32,0xbf160c26,0xbf0e9047,2 +np.float32,0x80081fd4,0x80081fd4,2 +np.float32,0x7db44283,0x42adf8b6,2 +np.float32,0xbf1983f8,0xbf118bf5,2 +np.float32,0x2c4a35,0x2c4a35,2 +np.float32,0x6165a7,0x6165a7,2 +np.float32,0xbe776b44,0xbe75129f,2 +np.float32,0xfe81841a,0xc2b0153b,2 +np.float32,0xbf7d1b2f,0xbf5f9461,2 +np.float32,0x80602d36,0x80602d36,2 +np.float32,0xfe8d5046,0xc2b041dd,2 +np.float32,0xfe5037bc,0xc2afa56d,2 +np.float32,0x4bbea6,0x4bbea6,2 +np.float32,0xfea039de,0xc2b0822d,2 +np.float32,0x7ea627a4,0x42b094c7,2 +np.float32,0x3f556198,0x3f423591,2 +np.float32,0xfedbae04,0xc2b123c1,2 +np.float32,0xbe30432c,0xbe2f6744,2 +np.float32,0x80202c77,0x80202c77,2 +np.float32,0xff335cc1,0xc2b21ed5,2 +np.float32,0x3e1e1ebe,0x3e1d7f95,2 +np.float32,0x8021c9c0,0x8021c9c0,2 +np.float32,0x7dc978,0x7dc978,2 +np.float32,0xff6cfabc,0xc2b2ad75,2 +np.float32,0x7f2bd542,0x42b208e0,2 +np.float32,0x53bf33,0x53bf33,2 +np.float32,0x804e04bb,0x804e04bb,2 +np.float32,0x3f30d2f9,0x3f2521ca,2 +np.float32,0x3dfde876,0x3dfd4316,2 +np.float32,0x46f8b1,0x46f8b1,2 +np.float32,0xbd5f9e20,0xbd5f81ba,2 +np.float32,0x807d6a22,0x807d6a22,2 +np.float32,0xff3881da,0xc2b22d50,2 +np.float32,0x1b1cb5,0x1b1cb5,2 +np.float32,0x3f75f2d0,0x3f5a7435,2 +np.float32,0xfee39c1a,0xc2b135e9,2 +np.float32,0x7f79f14a,0x42b2c8b9,2 +np.float32,0x8000e2d1,0x8000e2d1,2 +np.float32,0xab779,0xab779,2 +np.float32,0xbede6690,0xbed7f102,2 +np.float32,0x76e20d,0x76e20d,2 +np.float32,0x3ed714cb,0x3ed135e9,2 +np.float32,0xbeaa6f44,0xbea76f31,2 +np.float32,0x7f7dc8b1,0x42b2d089,2 +np.float32,0x108cb2,0x108cb2,2 +np.float32,0x7d37ba82,0x42ac9f94,2 +np.float32,0x3f31d068,0x3f25f221,2 +np.float32,0x8010a331,0x8010a331,2 +np.float32,0x3f2fdc7c,0x3f2456cd,2 +np.float32,0x7f7a9a67,0x42b2ca13,2 +np.float32,0x3f2acb31,0x3f202492,2 +np.float32,0x7f54fa94,0x42b276c9,2 +np.float32,0x3ebf8a70,0x3ebb553c,2 +np.float32,0x7f75b1a7,0x42b2bff2,2 +np.float32,0x7daebe07,0x42ade8cc,2 +np.float32,0xbd3a3ef0,0xbd3a2e86,2 +np.float32,0x8078ec9e,0x8078ec9e,2 +np.float32,0x3eda206a,0x3ed403ec,2 +np.float32,0x3f7248f2,0x3f57cd77,2 +np.float32,0x805d55ba,0x805d55ba,2 +np.float32,0xff30dc3e,0xc2b217a3,2 +np.float32,0xbe12b27c,0xbe123333,2 +np.float32,0xbf6ed9cf,0xbf554cd0,2 +np.float32,0xbed9eb5c,0xbed3d31c,2 +np.float32,0xbf1c9aea,0xbf14307b,2 +np.float32,0x3f540ac4,0x3f412de2,2 +np.float32,0x800333ac,0x800333ac,2 +np.float32,0x3f74cdb4,0x3f59a09a,2 +np.float32,0xbf41dc41,0xbf32ee6f,2 +np.float32,0xff2c7804,0xc2b20ac4,2 +np.float32,0x514493,0x514493,2 +np.float32,0xbddf1220,0xbddea1cf,2 +np.float32,0xfeaf74de,0xc2b0b0ab,2 +np.float32,0xfe5dfb30,0xc2afc633,2 +np.float32,0xbf4785c4,0xbf376bdb,2 +np.float32,0x80191cd3,0x80191cd3,2 +np.float32,0xfe44f708,0xc2af88fb,2 +np.float32,0x3d4cd8a0,0x3d4cc2ca,2 +np.float32,0x7f572eff,0x42b27c0f,2 +np.float32,0x8031bacb,0x8031bacb,2 +np.float32,0x7f2ea684,0x42b21133,2 +np.float32,0xbea1976a,0xbe9f05bb,2 +np.float32,0x3d677b41,0x3d675bc1,2 +np.float32,0x3f61bf24,0x3f4b9870,2 +np.float32,0x7ef55ddf,0x42b15c5f,2 +np.float32,0x3eabcb20,0x3ea8b91c,2 +np.float32,0xff73d9ec,0xc2b2bc18,2 +np.float32,0x77b9f5,0x77b9f5,2 +np.float32,0x4c6c6c,0x4c6c6c,2 +np.float32,0x7ed09c94,0x42b10949,2 +np.float32,0xdeeec,0xdeeec,2 +np.float32,0x7eac5858,0x42b0a782,2 +np.float32,0x7e190658,0x42af07bd,2 +np.float32,0xbe3c8980,0xbe3b7ce2,2 +np.float32,0x8059e86e,0x8059e86e,2 +np.float32,0xff201836,0xc2b1e4a5,2 +np.float32,0xbeac109c,0xbea8fafb,2 +np.float32,0x7edd1e2b,0x42b12718,2 +np.float32,0x639cd8,0x639cd8,2 +np.float32,0x3f5e4cae,0x3f490059,2 +np.float32,0x3d84c185,0x3d84a9c4,2 +np.float32,0xbe8c1130,0xbe8a605b,2 +np.float32,0x80000000,0x80000000,2 +np.float32,0x3f1da5e4,0x3f151404,2 +np.float32,0x7f75a873,0x42b2bfdf,2 +np.float32,0xbd873540,0xbd871c28,2 +np.float32,0xbe8e5e10,0xbe8c9808,2 +np.float32,0x7f004bf2,0x42b17347,2 +np.float32,0x800000,0x800000,2 +np.float32,0xbf6d6b79,0xbf544095,2 +np.float32,0x7ed7b563,0x42b11a6a,2 +np.float32,0x80693745,0x80693745,2 +np.float32,0x3ee0f608,0x3eda49a8,2 +np.float32,0xfe1285a4,0xc2aef181,2 +np.float32,0x72d946,0x72d946,2 +np.float32,0x6a0dca,0x6a0dca,2 +np.float32,0x3f5c9df6,0x3f47ba99,2 +np.float32,0xff002af6,0xc2b172c4,2 +np.float32,0x3f4ac98f,0x3f39fd0a,2 +np.float32,0x8066acf7,0x8066acf7,2 +np.float32,0xbcaa4e60,0xbcaa4b3c,2 +np.float32,0x80162813,0x80162813,2 +np.float32,0xff34b318,0xc2b222a2,2 +np.float32,0x7f1ce33c,0x42b1da49,2 +np.float32,0x3f0e55ab,0x3f07ddb0,2 +np.float32,0x7c75d996,0x42aa6eec,2 +np.float32,0xbf221bc6,0xbf18dc89,2 +np.float32,0x3f5a1a4c,0x3f45d1d4,2 +np.float32,0x7f2451b8,0x42b1f1fb,2 +np.float32,0x3ec55ca0,0x3ec0c655,2 +np.float32,0x3f752dc2,0x3f59e600,2 +np.float32,0xbe33f638,0xbe330c4d,2 +np.float32,0x3e2a9148,0x3e29c9d8,2 +np.float32,0x3f3362a1,0x3f273c01,2 +np.float32,0x5f83b3,0x5f83b3,2 +np.float32,0x3e362488,0x3e353216,2 +np.float32,0x140bcf,0x140bcf,2 +np.float32,0x7e3e96df,0x42af7822,2 +np.float32,0xbebc7082,0xbeb86ce6,2 +np.float32,0xbe92a92e,0xbe90b9d2,2 +np.float32,0xff3d8afc,0xc2b23b19,2 +np.float32,0x804125e3,0x804125e3,2 +np.float32,0x3f3675d1,0x3f29bedb,2 +np.float32,0xff70bb09,0xc2b2b57f,2 +np.float32,0x3f29681c,0x3f1efcd2,2 +np.float32,0xbdc70380,0xbdc6b3a8,2 +np.float32,0x54e0dd,0x54e0dd,2 +np.float32,0x3d545de0,0x3d54458c,2 +np.float32,0x7f800000,0x7f800000,2 +np.float32,0x8014a4c2,0x8014a4c2,2 +np.float32,0xbe93f58a,0xbe91f938,2 +np.float32,0x17de33,0x17de33,2 +np.float32,0xfefb679a,0xc2b168d2,2 +np.float32,0xbf23423e,0xbf19d511,2 +np.float32,0x7e893fa1,0x42b032ec,2 +np.float32,0x3f44fe2d,0x3f356bda,2 +np.float32,0xbebb2e78,0xbeb73e8f,2 +np.float32,0x3f5632e0,0x3f42d633,2 +np.float32,0x3ddd8698,0x3ddd1896,2 +np.float32,0x80164ea7,0x80164ea7,2 +np.float32,0x80087b37,0x80087b37,2 +np.float32,0xbf06ab1e,0xbf011f95,2 +np.float32,0x3db95524,0x3db9149f,2 +np.float32,0x7aa1fbb3,0x42a570a1,2 +np.float32,0xbd84fc48,0xbd84e467,2 +np.float32,0x3d65c6f5,0x3d65a826,2 +np.float32,0xfe987800,0xc2b068c4,2 +np.float32,0x7ec59532,0x42b0ed7a,2 +np.float32,0x3ea0232c,0x3e9da29a,2 +np.float32,0x80292a08,0x80292a08,2 +np.float32,0x734cfe,0x734cfe,2 +np.float32,0x3f3b6d63,0x3f2dc596,2 +np.float32,0x3f27bcc1,0x3f1d97e6,2 +np.float32,0xfe1da554,0xc2af16f9,2 +np.float32,0x7c91f5,0x7c91f5,2 +np.float32,0xfe4e78cc,0xc2afa11e,2 +np.float32,0x7e4b4e08,0x42af9933,2 +np.float32,0xfe0949ec,0xc2aed02e,2 +np.float32,0x7e2f057f,0x42af4c81,2 +np.float32,0xbf200ae0,0xbf171ce1,2 +np.float32,0x3ebcc244,0x3eb8b99e,2 +np.float32,0xbf68f58d,0xbf50f7aa,2 +np.float32,0x4420b1,0x4420b1,2 +np.float32,0x3f5b61bf,0x3f46cac7,2 +np.float32,0x3fec78,0x3fec78,2 +np.float32,0x7f4183c8,0x42b245b7,2 +np.float32,0xbf10587c,0xbf099ee2,2 +np.float32,0x0,0x0,2 +np.float32,0x7ec84dc3,0x42b0f47a,2 +np.float32,0x3f5fbd7b,0x3f4a166d,2 +np.float32,0xbd884eb8,0xbd883502,2 +np.float32,0xfe3f10a4,0xc2af7969,2 +np.float32,0xff3f4920,0xc2b23fc9,2 +np.float32,0x8013900f,0x8013900f,2 +np.float32,0x8003529d,0x8003529d,2 +np.float32,0xbf032384,0xbefbfb3c,2 +np.float32,0xff418c7c,0xc2b245ce,2 +np.float32,0xbec0aad0,0xbebc633b,2 +np.float32,0xfdbff178,0xc2ae18de,2 +np.float32,0x68ab15,0x68ab15,2 +np.float32,0xbdfc4a88,0xbdfba848,2 +np.float32,0xbf5adec6,0xbf466747,2 +np.float32,0x807d5dcc,0x807d5dcc,2 +np.float32,0x61d144,0x61d144,2 +np.float32,0x807e3a03,0x807e3a03,2 +np.float32,0x1872f2,0x1872f2,2 +np.float32,0x7f2a272c,0x42b203d8,2 +np.float32,0xfe7f8314,0xc2b00e3a,2 +np.float32,0xbe42aeac,0xbe418737,2 +np.float32,0x8024b614,0x8024b614,2 +np.float32,0xbe41b6b8,0xbe40939a,2 +np.float32,0xa765c,0xa765c,2 +np.float32,0x7ea74f4b,0x42b09853,2 +np.float32,0x7f7ef631,0x42b2d2e7,2 +np.float32,0x7eaef5e6,0x42b0af38,2 +np.float32,0xff733d85,0xc2b2bacf,2 +np.float32,0x537ac0,0x537ac0,2 +np.float32,0xbeca4790,0xbec55b1d,2 +np.float32,0x80117314,0x80117314,2 +np.float32,0xfe958536,0xc2b05ec5,2 +np.float32,0x8066ecc2,0x8066ecc2,2 +np.float32,0xbf56baf3,0xbf433e82,2 +np.float32,0x1f7fd7,0x1f7fd7,2 +np.float32,0x3e942104,0x3e9222fc,2 +np.float32,0xfeaffe82,0xc2b0b23c,2 +np.float32,0xfe0e02b0,0xc2aee17e,2 +np.float32,0xbf800000,0xbf61a1b3,2 +np.float32,0x800b7e49,0x800b7e49,2 +np.float32,0x6c514f,0x6c514f,2 +np.float32,0xff800000,0xff800000,2 +np.float32,0x7f7d9a45,0x42b2d02b,2 +np.float32,0x800c9c69,0x800c9c69,2 +np.float32,0x274b14,0x274b14,2 +np.float32,0xbf4b22b0,0xbf3a42e2,2 +np.float32,0x63e5ae,0x63e5ae,2 +np.float32,0xbe18facc,0xbe186a90,2 +np.float32,0x7e137351,0x42aef4bd,2 +np.float32,0x80518ffd,0x80518ffd,2 +np.float32,0xbf0a8ffc,0xbf048f0d,2 +np.float32,0x841d,0x841d,2 +np.float32,0x7edfdc9e,0x42b12d69,2 +np.float32,0xfd1092b0,0xc2ac24de,2 +np.float32,0x7e2c9bdf,0x42af4566,2 +np.float32,0x7f7fffff,0x42b2d4fc,2 +np.float32,0x3f4954a6,0x3f38d853,2 +np.float32,0xbe83efd2,0xbe8284c3,2 +np.float32,0x800e8e02,0x800e8e02,2 +np.float32,0x78ad39,0x78ad39,2 +np.float32,0x7eb0f967,0x42b0b514,2 +np.float32,0xbe39aa94,0xbe38a9ee,2 +np.float32,0x80194e7b,0x80194e7b,2 +np.float32,0x3cf3a340,0x3cf39a0f,2 +np.float32,0x3ed3117a,0x3ecd8173,2 +np.float32,0x7f530b11,0x42b2721c,2 +np.float32,0xff756ba2,0xc2b2bf60,2 +np.float32,0x15ea25,0x15ea25,2 +np.float32,0x803cbb64,0x803cbb64,2 +np.float32,0x3f34722d,0x3f281a2c,2 +np.float32,0x3ddd88e0,0x3ddd1adb,2 +np.float32,0x3f54244c,0x3f41418b,2 +np.float32,0x3e0adb98,0x3e0a6f8b,2 +np.float32,0x80800000,0x80800000,2 +np.float32,0x58902b,0x58902b,2 +np.float32,0xfe3b50b8,0xc2af6f43,2 +np.float32,0xfe0846d0,0xc2aecc64,2 +np.float32,0xbe0299d0,0xbe023fd4,2 +np.float32,0x18dde6,0x18dde6,2 +np.float32,0x8039fe8b,0x8039fe8b,2 +np.float32,0x8015d179,0x8015d179,2 +np.float32,0x3f551322,0x3f41f947,2 +np.float32,0x2ab387,0x2ab387,2 +np.float32,0xbf7e311e,0xbf6059d0,2 +np.float32,0xbdba58a8,0xbdba1713,2 +np.float32,0xbf1d008a,0xbf148724,2 +np.float32,0xbf6b9c97,0xbf52ec98,2 +np.float32,0x802acf04,0x802acf04,2 +np.float32,0x1,0x1,2 +np.float32,0xbe9e16d6,0xbe9bade3,2 +np.float32,0xbf048a14,0xbefe78c7,2 +np.float32,0x7e432ad3,0x42af8449,2 +np.float32,0xbdcc7fe0,0xbdcc2944,2 +np.float32,0x6dfc27,0x6dfc27,2 +np.float32,0xfef6eed8,0xc2b15fa1,2 +np.float32,0xbeeff6e8,0xbee7f2e4,2 +np.float32,0x7e3a6ca8,0x42af6cd2,2 +np.float32,0xff2c82e8,0xc2b20ae4,2 +np.float32,0x3e9f8d74,0x3e9d13b0,2 +np.float32,0x7ea36191,0x42b08c29,2 +np.float32,0x7f734bed,0x42b2baed,2 +np.float32,0x7f2df96d,0x42b20f37,2 +np.float32,0x5036fd,0x5036fd,2 +np.float32,0x806eab38,0x806eab38,2 +np.float32,0xbe9db90e,0xbe9b5446,2 +np.float32,0xfeef6fac,0xc2b14fd9,2 +np.float32,0xc2bf7,0xc2bf7,2 +np.float32,0xff53ec3d,0xc2b2743d,2 +np.float32,0x7e837637,0x42b01cde,2 +np.float32,0xbefb5934,0xbef23662,2 +np.float32,0x3f6cec80,0x3f53e371,2 +np.float32,0x3e86e7de,0x3e85643f,2 +np.float32,0x3f09cb42,0x3f03e1ef,2 +np.float32,0xbec3d236,0xbebf5620,2 +np.float32,0xfedef246,0xc2b12b50,2 +np.float32,0xbf08d6a8,0xbf030a62,2 +np.float32,0x8036cbf9,0x8036cbf9,2 +np.float32,0x3f74d3e3,0x3f59a512,2 +np.float32,0x6a600c,0x6a600c,2 +np.float32,0xfd1295b0,0xc2ac2bf1,2 +np.float32,0xbeb61142,0xbeb26efa,2 +np.float32,0x80216556,0x80216556,2 +np.float32,0xbf1fa0f6,0xbf16c30a,2 +np.float32,0x3e0af8e1,0x3e0a8c90,2 +np.float32,0x80434709,0x80434709,2 +np.float32,0x49efd9,0x49efd9,2 +np.float32,0x7f7cce6c,0x42b2ce8f,2 +np.float32,0x6e5450,0x6e5450,2 +np.float32,0x7f0fc115,0x42b1ad86,2 +np.float32,0x632db0,0x632db0,2 +np.float32,0x3f6f4c2a,0x3f55a064,2 +np.float32,0x7ec4f273,0x42b0ebd3,2 +np.float32,0x61ae1e,0x61ae1e,2 +np.float32,0x5f47c4,0x5f47c4,2 +np.float32,0xbf3c8f62,0xbf2eaf54,2 +np.float32,0xfca38900,0xc2ab0113,2 +np.float32,0x3ec89d52,0x3ec3ce78,2 +np.float32,0xbe0e3f70,0xbe0dcb53,2 +np.float32,0x805d3156,0x805d3156,2 +np.float32,0x3eee33f8,0x3ee65a4e,2 +np.float32,0xbeda7e9a,0xbed45a90,2 +np.float32,0x7e2fac7b,0x42af4e69,2 +np.float32,0x7efd0e28,0x42b16c2c,2 +np.float32,0x3f0c7b17,0x3f063e46,2 +np.float32,0xbf395bec,0xbf2c198f,2 +np.float32,0xfdf1c3f8,0xc2ae8f05,2 +np.float32,0xbe11f4e4,0xbe117783,2 +np.float32,0x7eddc901,0x42b128a3,2 +np.float32,0x3f4bad09,0x3f3aaf33,2 +np.float32,0xfefb5d76,0xc2b168bd,2 +np.float32,0x3ed3a4cf,0x3ece09a3,2 +np.float32,0x7ec582e4,0x42b0ed4a,2 +np.float32,0x3dc2268a,0x3dc1dc64,2 +np.float32,0x3ef9b17c,0x3ef0b9c9,2 +np.float32,0x2748ac,0x2748ac,2 +np.float32,0xfed6a602,0xc2b117e4,2 +np.float32,0xbefc9c36,0xbef35832,2 +np.float32,0x7e0476,0x7e0476,2 +np.float32,0x804be1a0,0x804be1a0,2 +np.float32,0xbefbc1c2,0xbef2943a,2 +np.float32,0xbd4698f0,0xbd46850a,2 +np.float32,0x688627,0x688627,2 +np.float32,0x3f7f7685,0x3f61406f,2 +np.float32,0x827fb,0x827fb,2 +np.float32,0x3f503264,0x3f3e34fd,2 +np.float32,0x7f5458d1,0x42b27543,2 +np.float32,0x800ac01f,0x800ac01f,2 +np.float32,0x6188dd,0x6188dd,2 +np.float32,0x806ac0ba,0x806ac0ba,2 +np.float32,0xbe14493c,0xbe13c5cc,2 +np.float32,0x3f77542c,0x3f5b72ae,2 +np.float32,0xfeaacab6,0xc2b0a2df,2 +np.float32,0x7f2893d5,0x42b1ff15,2 +np.float32,0x66b528,0x66b528,2 +np.float32,0xbf653e24,0xbf4e3573,2 +np.float32,0x801a2853,0x801a2853,2 +np.float32,0x3f3d8c98,0x3f2f7b04,2 +np.float32,0xfdffbad8,0xc2aeabc5,2 +np.float32,0x3dd50f,0x3dd50f,2 +np.float32,0x3f325a4c,0x3f266353,2 +np.float32,0xfcc48ec0,0xc2ab5f3f,2 +np.float32,0x3e6f5b9a,0x3e6d3ae5,2 +np.float32,0x3dbcd62b,0x3dbc91ee,2 +np.float32,0xbf7458d9,0xbf594c1c,2 +np.float32,0xff5adb24,0xc2b284b9,2 +np.float32,0x807b246d,0x807b246d,2 +np.float32,0x3f800000,0x3f61a1b3,2 +np.float32,0x231a28,0x231a28,2 +np.float32,0xbdc66258,0xbdc61341,2 +np.float32,0x3c84b4b4,0x3c84b338,2 +np.float32,0xbf215894,0xbf183783,2 +np.float32,0xff4ee298,0xc2b267ec,2 +np.float32,0x801ef52e,0x801ef52e,2 +np.float32,0x1040b0,0x1040b0,2 +np.float32,0xff545582,0xc2b2753b,2 +np.float32,0x3f3b9dda,0x3f2decaf,2 +np.float32,0x730f99,0x730f99,2 +np.float32,0xff7fffff,0xc2b2d4fc,2 +np.float32,0xff24cc5e,0xc2b1f379,2 +np.float32,0xbe9b456a,0xbe98fc0b,2 +np.float32,0x188fb,0x188fb,2 +np.float32,0x3f5c7ce2,0x3f47a18a,2 +np.float32,0x7fc00000,0x7fc00000,2 +np.float32,0x806ea4da,0x806ea4da,2 +np.float32,0xfe810570,0xc2b01345,2 +np.float32,0x8036af89,0x8036af89,2 +np.float32,0x8043cec6,0x8043cec6,2 +np.float32,0x80342bb3,0x80342bb3,2 +np.float32,0x1a2bd4,0x1a2bd4,2 +np.float32,0x3f6248c2,0x3f4bff9a,2 +np.float32,0x8024eb35,0x8024eb35,2 +np.float32,0x7ea55872,0x42b09247,2 +np.float32,0x806d6e56,0x806d6e56,2 +np.float32,0x25c21a,0x25c21a,2 +np.float32,0x3f4e95f3,0x3f3cf483,2 +np.float32,0x15ca38,0x15ca38,2 +np.float32,0x803f01b2,0x803f01b2,2 +np.float32,0xbe731634,0xbe70dc10,2 +np.float32,0x3e80cee4,0x3e7ef933,2 +np.float32,0x3ef6dda5,0x3eee2e7b,2 +np.float32,0x3f3dfdc2,0x3f2fd5ed,2 +np.float32,0xff0492a7,0xc2b18411,2 +np.float32,0xbf1d0adf,0xbf148ff3,2 +np.float32,0xfcf75460,0xc2abd4e3,2 +np.float32,0x3f46fca6,0x3f36ffa6,2 +np.float32,0xbe63b5c0,0xbe61dfb3,2 +np.float32,0xff019bec,0xc2b1787d,2 +np.float32,0x801f14a9,0x801f14a9,2 +np.float32,0x3f176cfa,0x3f0fc051,2 +np.float32,0x3f69d976,0x3f51a015,2 +np.float32,0x3f4917cb,0x3f38a87a,2 +np.float32,0x3b2a0bea,0x3b2a0bdd,2 +np.float32,0xbf41d857,0xbf32eb50,2 +np.float32,0xbf08841a,0xbf02c18f,2 +np.float32,0x7ec86f14,0x42b0f4d0,2 +np.float32,0xbf7d15d1,0xbf5f9090,2 +np.float32,0xbd080550,0xbd07feea,2 +np.float32,0xbf6f1bef,0xbf557d26,2 +np.float32,0xfebc282c,0xc2b0d473,2 +np.float32,0x3e68d2f5,0x3e66dd03,2 +np.float32,0x3f3ed8fe,0x3f3085d5,2 +np.float32,0xff2f78ae,0xc2b2139a,2 +np.float32,0xff647a70,0xc2b29ac1,2 +np.float32,0xfd0859a0,0xc2ac06e2,2 +np.float32,0x3ea578a8,0x3ea2b7e1,2 +np.float32,0x6c58c6,0x6c58c6,2 +np.float32,0xff23f26a,0xc2b1f0d2,2 +np.float32,0x800902a4,0x800902a4,2 +np.float32,0xfe8ba64e,0xc2b03bcd,2 +np.float32,0x3f091143,0x3f033e0f,2 +np.float32,0x8017c4bd,0x8017c4bd,2 +np.float32,0xbf708fd4,0xbf568c8c,2 +np.float32,0x3be1d8,0x3be1d8,2 +np.float32,0x80091f07,0x80091f07,2 +np.float32,0x68eabe,0x68eabe,2 +np.float32,0xfe9ab2c8,0xc2b07033,2 +np.float32,0x3eabe752,0x3ea8d3d7,2 +np.float32,0xbf7adcb2,0xbf5dfaf5,2 +np.float32,0x801ecc01,0x801ecc01,2 +np.float32,0xbf5570a9,0xbf424123,2 +np.float32,0x3e89eecd,0x3e88510e,2 +np.float32,0xfeb2feee,0xc2b0bae4,2 +np.float32,0xbeb25ec2,0xbeaef22b,2 +np.float32,0x201e49,0x201e49,2 +np.float32,0x800a35f6,0x800a35f6,2 +np.float32,0xbf02d449,0xbefb6e2a,2 +np.float32,0x3f062bea,0x3f00aef6,2 +np.float32,0x7f5219ff,0x42b26fd2,2 +np.float32,0xbd4561d0,0xbd454e47,2 +np.float32,0x3f6c4789,0x3f536a4b,2 +np.float32,0x7f58b06d,0x42b27fa1,2 +np.float32,0x7f132f39,0x42b1b999,2 +np.float32,0x3e05dcb4,0x3e057bd8,2 +np.float32,0x7f526045,0x42b2707d,2 +np.float32,0x3f6117d0,0x3f4b1adb,2 +np.float32,0xbf21f47d,0xbf18bb57,2 +np.float32,0x1a26d6,0x1a26d6,2 +np.float32,0x46b114,0x46b114,2 +np.float32,0x3eb24518,0x3eaed9ef,2 +np.float32,0xfe2139c8,0xc2af2278,2 +np.float32,0xbf7c36fb,0xbf5ef1f6,2 +np.float32,0x3f193834,0x3f114af7,2 +np.float32,0xff3ea650,0xc2b23e14,2 +np.float32,0xfeeb3bca,0xc2b146c7,2 +np.float32,0x7e8b8ca0,0x42b03b6f,2 +np.float32,0x3eed903d,0x3ee5c5d2,2 +np.float32,0xbdc73740,0xbdc6e72a,2 +np.float32,0x7e500307,0x42afa4ec,2 +np.float32,0xe003c,0xe003c,2 +np.float32,0x3e612bb4,0x3e5f64fd,2 +np.float32,0xfd81e248,0xc2ad50e6,2 +np.float32,0x766a4f,0x766a4f,2 +np.float32,0x3e8708c9,0x3e858414,2 +np.float32,0xbf206c58,0xbf176f7f,2 +np.float32,0x7e93aeb0,0x42b0586f,2 +np.float32,0xfd9d36b8,0xc2adb2ad,2 +np.float32,0xff1f4e0e,0xc2b1e21d,2 +np.float32,0x3f22bd5a,0x3f1964f8,2 +np.float32,0x7f6a517a,0x42b2a7ad,2 +np.float32,0xff6ca773,0xc2b2acc1,2 +np.float32,0x7f6bf453,0x42b2ab3d,2 +np.float32,0x3edfdd64,0x3ed9489f,2 +np.float32,0xbeafc5ba,0xbeac7daa,2 +np.float32,0x7d862039,0x42ad615b,2 +np.float32,0xbe9d2002,0xbe9ac1fc,2 +np.float32,0xbdcc54c0,0xbdcbfe5b,2 +np.float32,0xbf1bc0aa,0xbf13762a,2 +np.float32,0xbf4679ce,0xbf36984b,2 +np.float32,0x3ef45696,0x3eebe713,2 +np.float32,0xff6eb999,0xc2b2b137,2 +np.float32,0xbe4b2e4c,0xbe49dee8,2 +np.float32,0x3f498951,0x3f3901b7,2 +np.float32,0xbe9692f4,0xbe947be1,2 +np.float32,0xbf44ce26,0xbf3545c8,2 +np.float32,0x805787a8,0x805787a8,2 +np.float32,0xbf342650,0xbf27dc26,2 +np.float32,0x3edafbf0,0x3ed4cdd2,2 +np.float32,0x3f6fb858,0x3f55ef63,2 +np.float32,0xff227d0a,0xc2b1ec3f,2 +np.float32,0xfeb9a202,0xc2b0cd89,2 +np.float32,0x7f5b12c1,0x42b2853b,2 +np.float32,0x584578,0x584578,2 +np.float32,0x7ec0b76f,0x42b0e0b5,2 +np.float32,0x3f57f54b,0x3f442f10,2 +np.float32,0x7eef3620,0x42b14f5d,2 +np.float32,0x4525b5,0x4525b5,2 +np.float32,0x801bd407,0x801bd407,2 +np.float32,0xbed1f166,0xbecc7703,2 +np.float32,0x3f57e732,0x3f442449,2 +np.float32,0x80767cd5,0x80767cd5,2 +np.float32,0xbef1a7d2,0xbee97aa3,2 +np.float32,0x3dd5b1af,0x3dd54ee6,2 +np.float32,0x960c,0x960c,2 +np.float32,0x7c392d41,0x42a9ddd1,2 +np.float32,0x3f5c9a34,0x3f47b7c1,2 +np.float32,0x3f5cecee,0x3f47f667,2 +np.float32,0xbee482ce,0xbedd8899,2 +np.float32,0x8066ba7e,0x8066ba7e,2 +np.float32,0x7ed76127,0x42b119a2,2 +np.float32,0x805ca40b,0x805ca40b,2 +np.float32,0x7f5ed5d1,0x42b28df3,2 +np.float32,0xfe9e1b1e,0xc2b07b5b,2 +np.float32,0x3f0201a2,0x3ef9f6c4,2 +np.float32,0xbf2e6430,0xbf232039,2 +np.float32,0x80326b4d,0x80326b4d,2 +np.float32,0x3f11dc7c,0x3f0af06e,2 +np.float32,0xbe89c42e,0xbe8827e6,2 +np.float32,0x3f3c69f8,0x3f2e9133,2 +np.float32,0x806326a9,0x806326a9,2 +np.float32,0x3f1c5286,0x3f13f2b6,2 +np.float32,0xff5c0ead,0xc2b28786,2 +np.float32,0xff32b952,0xc2b21d01,2 +np.float32,0x7dd27c4e,0x42ae4815,2 +np.float32,0xbf7a6816,0xbf5da7a2,2 +np.float32,0xfeac72f8,0xc2b0a7d1,2 +np.float32,0x335ad7,0x335ad7,2 +np.float32,0xbe682da4,0xbe663bcc,2 +np.float32,0x3f2df244,0x3f22c208,2 +np.float32,0x80686e8e,0x80686e8e,2 +np.float32,0x7f50120f,0x42b26ad9,2 +np.float32,0x3dbc596a,0x3dbc15b3,2 +np.float32,0xbf4f2868,0xbf3d666d,2 +np.float32,0x80000001,0x80000001,2 +np.float32,0xff66c059,0xc2b29fd2,2 +np.float32,0xfe8bbcaa,0xc2b03c1f,2 +np.float32,0x3ece6a51,0x3ec93271,2 +np.float32,0x7f06cd26,0x42b18c9a,2 +np.float32,0x7e41e6dc,0x42af80f5,2 +np.float32,0x7d878334,0x42ad669f,2 +np.float32,0xfe8c5c4c,0xc2b03e67,2 +np.float32,0x337a05,0x337a05,2 +np.float32,0x3e63801d,0x3e61ab58,2 +np.float32,0x62c315,0x62c315,2 +np.float32,0x802aa888,0x802aa888,2 +np.float32,0x80038b43,0x80038b43,2 +np.float32,0xff5c1271,0xc2b2878f,2 +np.float32,0xff4184a5,0xc2b245b9,2 +np.float32,0x7ef58f4b,0x42b15cc6,2 +np.float32,0x7f42d8ac,0x42b2493a,2 +np.float32,0x806609f2,0x806609f2,2 +np.float32,0x801e763b,0x801e763b,2 +np.float32,0x7f2bc073,0x42b208a2,2 +np.float32,0x801d7d7f,0x801d7d7f,2 +np.float32,0x7d415dc1,0x42acb9c2,2 +np.float32,0xbf624ff9,0xbf4c0502,2 +np.float32,0xbf603afd,0xbf4a74e2,2 +np.float32,0x8007fe42,0x8007fe42,2 +np.float32,0x800456db,0x800456db,2 +np.float32,0x620871,0x620871,2 +np.float32,0x3e9c6c1e,0x3e9a15fa,2 +np.float32,0x4245d,0x4245d,2 +np.float32,0x8035bde9,0x8035bde9,2 +np.float32,0xbf597418,0xbf45533c,2 +np.float32,0x3c730f80,0x3c730d38,2 +np.float32,0x3f7cd8ed,0x3f5f6540,2 +np.float32,0x807e49c3,0x807e49c3,2 +np.float32,0x3d6584c0,0x3d65660c,2 +np.float32,0xff42a744,0xc2b248b8,2 +np.float32,0xfedc6f56,0xc2b12583,2 +np.float32,0x806263a4,0x806263a4,2 +np.float32,0x175a17,0x175a17,2 +np.float32,0x3f1e8537,0x3f15d208,2 +np.float32,0x4055b5,0x4055b5,2 +np.float32,0x438aa6,0x438aa6,2 +np.float32,0x8038507f,0x8038507f,2 +np.float32,0xbed75348,0xbed16f85,2 +np.float32,0x7f07b7d6,0x42b19012,2 +np.float32,0xfe8b9d30,0xc2b03bac,2 +np.float32,0x805c501c,0x805c501c,2 +np.float32,0x3ef22b1d,0x3ee9f159,2 +np.float32,0x802b6759,0x802b6759,2 +np.float32,0x45281a,0x45281a,2 +np.float32,0xbf7e9970,0xbf60a3cf,2 +np.float32,0xbf14d152,0xbf0d8062,2 +np.float32,0x3d9ff950,0x3d9fcfc8,2 +np.float32,0x7865d9,0x7865d9,2 +np.float32,0xbee67fa4,0xbedf58eb,2 +np.float32,0x7dc822d1,0x42ae2e44,2 +np.float32,0x3f3af0fe,0x3f2d612c,2 +np.float32,0xbefea106,0xbef5274e,2 +np.float32,0xbf758a3f,0xbf5a28c5,2 +np.float32,0xbf331bdd,0xbf270209,2 +np.float32,0x7f51c901,0x42b26f0d,2 +np.float32,0x3f67c33b,0x3f5014d8,2 +np.float32,0xbbc9d980,0xbbc9d92c,2 +np.float32,0xbc407540,0xbc40741e,2 +np.float32,0x7eed9a3c,0x42b14be9,2 +np.float32,0x1be0fe,0x1be0fe,2 +np.float32,0xbf6b4913,0xbf52af1f,2 +np.float32,0xbda8eba8,0xbda8bac6,2 +np.float32,0x8004bcea,0x8004bcea,2 +np.float32,0xff6f6afe,0xc2b2b2b3,2 +np.float32,0xbf205810,0xbf175e50,2 +np.float32,0x80651944,0x80651944,2 +np.float32,0xbec73016,0xbec27a3f,2 +np.float32,0x5701b9,0x5701b9,2 +np.float32,0xbf1062ce,0xbf09a7df,2 +np.float32,0x3e0306ae,0x3e02abd1,2 +np.float32,0x7bfc62,0x7bfc62,2 +np.float32,0xbf48dd3c,0xbf387a6b,2 +np.float32,0x8009573e,0x8009573e,2 +np.float32,0x660a2c,0x660a2c,2 +np.float32,0xff2280da,0xc2b1ec4b,2 +np.float32,0xbf7034fe,0xbf564a54,2 +np.float32,0xbeeb448e,0xbee3b045,2 +np.float32,0xff4e949c,0xc2b2672b,2 +np.float32,0xbf3c4486,0xbf2e7309,2 +np.float32,0x7eb086d8,0x42b0b3c8,2 +np.float32,0x7eac8aca,0x42b0a817,2 +np.float32,0xfd3d2d60,0xc2acae8b,2 +np.float32,0xbf363226,0xbf2987bd,2 +np.float32,0x7f02e524,0x42b17d8c,2 +np.float32,0x8049a148,0x8049a148,2 +np.float32,0x147202,0x147202,2 +np.float32,0x8031d3f6,0x8031d3f6,2 +np.float32,0xfe78bf68,0xc2b0007d,2 +np.float32,0x7ebd16d0,0x42b0d6fb,2 +np.float32,0xbdaed2e8,0xbdae9cbb,2 +np.float32,0x802833ae,0x802833ae,2 +np.float32,0x7f62adf6,0x42b296b5,2 +np.float32,0xff2841c0,0xc2b1fe1b,2 +np.float32,0xbeb2c47e,0xbeaf523b,2 +np.float32,0x7e42a36e,0x42af82e6,2 +np.float32,0x41ea29,0x41ea29,2 +np.float32,0xbcaaa800,0xbcaaa4d7,2 +np.float64,0x3fed71f27ebae3e5,0x3fea5c6095012ca6,1 +np.float64,0x224dc392449b9,0x224dc392449b9,1 +np.float64,0x3fdf897a7d3f12f5,0x3fde620339360992,1 +np.float64,0xbfe1f99a5123f334,0xbfe124a57cfaf556,1 +np.float64,0xbfd9725c3bb2e4b8,0xbfd8d1e3f75110c7,1 +np.float64,0x3fe38977546712ee,0x3fe27d9d37f4b91f,1 +np.float64,0xbfc36c29e526d854,0xbfc3594743ee45c4,1 +np.float64,0xbfe5cbec332b97d8,0xbfe4638802316849,1 +np.float64,0x2ff35efe5fe6d,0x2ff35efe5fe6d,1 +np.float64,0x7fd3f828e227f051,0x40862a7d4a40b1e0,1 +np.float64,0xffd06fc11620df82,0xc08628ee8f1bf6c8,1 +np.float64,0x3fe5321bf4aa6438,0x3fe3e3d9fa453199,1 +np.float64,0xffd07a323ca0f464,0xc08628f3a2930f8c,1 +np.float64,0x3fdf7abe7abef57c,0x3fde54cb193d49cb,1 +np.float64,0x40941f1881285,0x40941f1881285,1 +np.float64,0xffef18defc7e31bd,0xc0863393f2c9f061,1 +np.float64,0xbfe379f871e6f3f1,0xbfe270620cb68347,1 +np.float64,0xffec829848f90530,0xc08632e210edaa2b,1 +np.float64,0x80070c00574e1801,0x80070c00574e1801,1 +np.float64,0xffce7654b23ceca8,0xc086285291e89975,1 +np.float64,0x7fc9932daa33265a,0x408626ec6cc2b807,1 +np.float64,0x355ee98c6abde,0x355ee98c6abde,1 +np.float64,0x3fac54962c38a920,0x3fac50e40b6c19f2,1 +np.float64,0x800857984af0af31,0x800857984af0af31,1 +np.float64,0x7fea6a3d55f4d47a,0x40863245bf39f179,1 +np.float64,0x3fdb8fab33371f56,0x3fdac5ffc9e1c347,1 +np.float64,0x800a887a7bf510f5,0x800a887a7bf510f5,1 +np.float64,0xbfbdbda3c63b7b48,0xbfbdac9dd5a2d3e8,1 +np.float64,0xbfd4a2457b29448a,0xbfd44acb3b316d6d,1 +np.float64,0x7fd5329a502a6534,0x40862af789b528b5,1 +np.float64,0x3fd96a7bceb2d4f8,0x3fd8ca92104d6cd6,1 +np.float64,0x3fde6a0cd6bcd41a,0x3fdd5f4b85abf749,1 +np.float64,0xbfc7faaff32ff560,0xbfc7d7560b8c4a52,1 +np.float64,0x7fec381b2f787035,0x408632cd0e9c095c,1 +np.float64,0x1fc2eb543f85e,0x1fc2eb543f85e,1 +np.float64,0x7ac6000af58c1,0x7ac6000af58c1,1 +np.float64,0xffe060a87920c150,0xc0862e72c37d5a4e,1 +np.float64,0xbfb7d8c89e2fb190,0xbfb7cffd3c3f8e3a,1 +np.float64,0x3fd91033deb22068,0x3fd87695b067aa1e,1 +np.float64,0x3fec1aff01b835fe,0x3fe95d5cbd729af7,1 +np.float64,0x7fb97f69ec32fed3,0x4086215aaae5c697,1 +np.float64,0x7feaf1e4e5f5e3c9,0x4086326e6ca6a2bb,1 +np.float64,0x800537e44d0a6fc9,0x800537e44d0a6fc9,1 +np.float64,0x800b2a0d0d36541a,0x800b2a0d0d36541a,1 +np.float64,0x3fe2193846e43270,0x3fe140308550138e,1 +np.float64,0x5e2a0a32bc542,0x5e2a0a32bc542,1 +np.float64,0xffe5888b09eb1116,0xc08630a348783aa3,1 +np.float64,0xbfceb9b5033d736c,0xbfce701049c10435,1 +np.float64,0x7fe5d68589abad0a,0x408630c00ce63f23,1 +np.float64,0x8009b5457ff36a8b,0x8009b5457ff36a8b,1 +np.float64,0xbfb5518c2e2aa318,0xbfb54b42638ca718,1 +np.float64,0x3f9c58469838b080,0x3f9c575974fbcd7b,1 +np.float64,0x3fe8db4b4731b697,0x3fe6dc9231587966,1 +np.float64,0x8007d0f77f4fa1f0,0x8007d0f77f4fa1f0,1 +np.float64,0x7fe79eef542f3dde,0x40863160c673c67f,1 +np.float64,0xffbdc0b6163b8170,0xc0862296be4bf032,1 +np.float64,0x3fbb8d3312371a66,0x3fbb7fa76fb4cf8d,1 +np.float64,0xffd8a0eedbb141de,0xc0862c2ac6e512f0,1 +np.float64,0x7fee99d8d87d33b1,0x4086337301c4c8df,1 +np.float64,0xffe7479b552e8f36,0xc0863142fba0f0ec,1 +np.float64,0xffedf8ef4abbf1de,0xc08633488068fe69,1 +np.float64,0x895c4d9f12b8a,0x895c4d9f12b8a,1 +np.float64,0x29b4caf05369a,0x29b4caf05369a,1 +np.float64,0xbfefb90d657f721b,0xbfec01efa2425b35,1 +np.float64,0xde07c3bdbc0f9,0xde07c3bdbc0f9,1 +np.float64,0x7feae9fd02f5d3f9,0x4086326c1368ed5a,1 +np.float64,0x3feab792da756f26,0x3fe84f6e15338ed7,1 +np.float64,0xbfeff8ed72fff1db,0xbfec2f35da06daaf,1 +np.float64,0x8004b2c132896583,0x8004b2c132896583,1 +np.float64,0xbf9fcb00103f9600,0xbf9fc9b1751c569e,1 +np.float64,0x4182b72e83058,0x4182b72e83058,1 +np.float64,0x90820d812105,0x90820d812105,1 +np.float64,0xbfdec9a0ba3d9342,0xbfddb585df607ce1,1 +np.float64,0x7fdc0a69a03814d2,0x40862d347f201b63,1 +np.float64,0xbfef0708937e0e11,0xbfeb82d27f8ea97f,1 +np.float64,0xffda57e4ddb4afca,0xc0862cb49e2e0c4c,1 +np.float64,0xbfa30b9af4261730,0xbfa30a7b4a633060,1 +np.float64,0x7feb57fcc4b6aff9,0x4086328c83957a0b,1 +np.float64,0x7fe6759153eceb22,0x408630f980433963,1 +np.float64,0x7fdd3278c8ba64f1,0x40862d87445243e9,1 +np.float64,0xd3b8e6b9a771d,0xd3b8e6b9a771d,1 +np.float64,0x6267dc88c4cfc,0x6267dc88c4cfc,1 +np.float64,0x7fedd3cf00bba79d,0x4086333e91712ff5,1 +np.float64,0xffbe512ce03ca258,0xc08622bd39314cea,1 +np.float64,0xbfe71742ca6e2e86,0xbfe572ccbf2d010d,1 +np.float64,0x8002fb048c65f60a,0x8002fb048c65f60a,1 +np.float64,0x800d9d9ddf7b3b3c,0x800d9d9ddf7b3b3c,1 +np.float64,0xbfeaf6230df5ec46,0xbfe87f5d751ec3d5,1 +np.float64,0xbfe69973a42d32e8,0xbfe50c680f7002fe,1 +np.float64,0x3fe309cf87e613a0,0x3fe21048714ce1ac,1 +np.float64,0x800435d17a286ba4,0x800435d17a286ba4,1 +np.float64,0x7fefffffffffffff,0x408633ce8fb9f87e,1 +np.float64,0x3fe36ade1766d5bc,0x3fe26379fb285dde,1 +np.float64,0x3f98d8d94831b1c0,0x3f98d839885dc527,1 +np.float64,0xbfd08f7ae5211ef6,0xbfd0618ab5293e1e,1 +np.float64,0xbfcf630bd53ec618,0xbfcf14a0cd20704d,1 +np.float64,0xbfe58f0ca6eb1e1a,0xbfe4312225df8e28,1 +np.float64,0xffef4f6406be9ec7,0xc08633a1ed1d27e5,1 +np.float64,0x7fe10120b3e20240,0x40862ebfaf94e6e8,1 +np.float64,0xffe96c52fbb2d8a5,0xc08631f75d9a59a0,1 +np.float64,0xbfe448a333e89146,0xbfe31fee44c3ec43,1 +np.float64,0x80045ff4e788bfeb,0x80045ff4e788bfeb,1 +np.float64,0x7fefaa2f823f545e,0x408633b8fea29524,1 +np.float64,0xffea6b8bf234d717,0xc0863246248e5960,1 +np.float64,0xbfdb085d80b610bc,0xbfda498b15b43eec,1 +np.float64,0xbfd5e12da3abc25c,0xbfd57970e2b8aecc,1 +np.float64,0x3fcc84928a390925,0x3fcc497c417a89f3,1 +np.float64,0xbfdcb713bf396e28,0xbfdbd46c5e731fd9,1 +np.float64,0xffdf50c0453ea180,0xc0862e16b5562f25,1 +np.float64,0x800342c2f7268587,0x800342c2f7268587,1 +np.float64,0x7feb8b6d743716da,0x4086329b8248de2c,1 +np.float64,0x800a9b18b4953632,0x800a9b18b4953632,1 +np.float64,0xffedaf0d12fb5e19,0xc0863334af82de1a,1 +np.float64,0x800aebda4ab5d7b5,0x800aebda4ab5d7b5,1 +np.float64,0xbfa9f5848433eb10,0xbfa9f2ac7ac065d4,1 +np.float64,0x3fea375928f46eb2,0x3fe7ec9f10eeac7d,1 +np.float64,0x3fd6c213fead8428,0x3fd64dcc1eff5f1b,1 +np.float64,0xbfa0476f44208ee0,0xbfa046bb986007ac,1 +np.float64,0x6c8e18aed91c4,0x6c8e18aed91c4,1 +np.float64,0x8000000000000001,0x8000000000000001,1 +np.float64,0x7fea86b5ba350d6a,0x4086324e59f13027,1 +np.float64,0x2316c3b0462d9,0x2316c3b0462d9,1 +np.float64,0x3fec4e3281389c65,0x3fe983c5c9d65940,1 +np.float64,0x3fbb87c47f772,0x3fbb87c47f772,1 +np.float64,0x8004af00fdc95e03,0x8004af00fdc95e03,1 +np.float64,0xbfd316db9ba62db8,0xbfd2d12765b9d155,1 +np.float64,0x3fec1a7a99f834f6,0x3fe95cf941889b3d,1 +np.float64,0x3feff7e1477fefc3,0x3fec2e782392d4b9,1 +np.float64,0xbfc683ea042d07d4,0xbfc66698cfa5026e,1 +np.float64,0x3fdbc8aaa9b79154,0x3fdafa50e6fc3fff,1 +np.float64,0xfb3b630ff676d,0xfb3b630ff676d,1 +np.float64,0x7fe715ef8eae2bde,0x40863131d794b41f,1 +np.float64,0x7fefa06c11bf40d7,0x408633b686c7996a,1 +np.float64,0x80002a40f5205483,0x80002a40f5205483,1 +np.float64,0x7fe95f3c74b2be78,0x408631f33e37bf76,1 +np.float64,0x3fb2977b32252ef0,0x3fb2934eaf5a4be8,1 +np.float64,0x3fc0f3dbc821e7b8,0x3fc0e745288c84c3,1 +np.float64,0x3fda98da56b531b5,0x3fd9e2b19447dacc,1 +np.float64,0x3f95b9d5202b73aa,0x3f95b96a53282949,1 +np.float64,0x3fdc1ace7738359d,0x3fdb4597d31df7ff,1 +np.float64,0xffeac5bb2e358b76,0xc0863261452ab66c,1 +np.float64,0xbfefb1b78f7f636f,0xbfebfcb9be100ced,1 +np.float64,0xf5c9e191eb93c,0xf5c9e191eb93c,1 +np.float64,0x3fe83a977630752f,0x3fe65d0df90ff6ef,1 +np.float64,0x3fc317515d262ea0,0x3fc3056072b719f0,1 +np.float64,0x7fe2dcfab225b9f4,0x40862f94257c28a2,1 +np.float64,0xca2b115794562,0xca2b115794562,1 +np.float64,0x3fd495301aa92a60,0x3fd43e57108761d5,1 +np.float64,0x800ccc4293199885,0x800ccc4293199885,1 +np.float64,0xc8d3173d91a63,0xc8d3173d91a63,1 +np.float64,0xbf2541bb7e4a8,0xbf2541bb7e4a8,1 +np.float64,0xbfe9a330df334662,0xbfe779816573f5be,1 +np.float64,0xffd5e4c8252bc990,0xc0862b39b3ca5d72,1 +np.float64,0x3fe90f3a53721e75,0x3fe70585ae09531d,1 +np.float64,0xbfe2b5ddc7a56bbc,0xbfe1c7fa91a675ed,1 +np.float64,0xbf981a0360303400,0xbf9819719345073a,1 +np.float64,0x19174b0e322ea,0x19174b0e322ea,1 +np.float64,0xbfd2f71a1725ee34,0xbfd2b2b6f7cd10b1,1 +np.float64,0x80056e83236add07,0x80056e83236add07,1 +np.float64,0x7fe4bc41d9697883,0x40863055f20ce0cb,1 +np.float64,0xffe76e06c46edc0d,0xc086315024b25559,1 +np.float64,0x3fe3c4f0f96789e2,0x3fe2b04b584609bf,1 +np.float64,0x3fe6cfc533ed9f8a,0x3fe538b4d784d5ee,1 +np.float64,0x7fd234a640a4694c,0x408629bfead4f0b2,1 +np.float64,0x3fdbc49c9ab78939,0x3fdaf698a83d08e2,1 +np.float64,0x3fe4c5336ee98a66,0x3fe388c6ddb60e0a,1 +np.float64,0xf4b9497be9729,0xf4b9497be9729,1 +np.float64,0x3fb312be12262580,0x3fb30e3c847c1d16,1 +np.float64,0x3fe9554218f2aa84,0x3fe73c8b311c7a98,1 +np.float64,0xff899816a0333040,0xc08610bfb2cd8559,1 +np.float64,0x8006008ad52c0116,0x8006008ad52c0116,1 +np.float64,0x3fd7d47be4afa8f8,0x3fd74fa71ec17fd0,1 +np.float64,0x8010000000000000,0x8010000000000000,1 +np.float64,0xdf2a9943be553,0xdf2a9943be553,1 +np.float64,0xbfeb86bf1eb70d7e,0xbfe8ed797580ba5c,1 +np.float64,0x800e2c0c28bc5818,0x800e2c0c28bc5818,1 +np.float64,0xbfe2be65d4657ccc,0xbfe1cf578dec2323,1 +np.float64,0xbfedea3a5afbd475,0xbfeab490bf05e585,1 +np.float64,0xbfe04b1583a0962b,0xbfdf523dfd7be25c,1 +np.float64,0x75929bb4eb254,0x75929bb4eb254,1 +np.float64,0x3fd7b4968caf692d,0x3fd731c0938ff97c,1 +np.float64,0x60bd8fd2c17b3,0x60bd8fd2c17b3,1 +np.float64,0xbfdaf15e70b5e2bc,0xbfda345a95ce18fe,1 +np.float64,0x7fdd7c35c2baf86b,0x40862d9b5f40c6b2,1 +np.float64,0x7feeb4d2ab7d69a4,0x4086337a0c0dffaf,1 +np.float64,0xffe65b5a1decb6b4,0xc08630f024420efb,1 +np.float64,0x7feb272b30764e55,0x4086327e2e553aa2,1 +np.float64,0x3fd27513e8a4ea28,0x3fd235ea49670f6a,1 +np.float64,0x3fe6541a6aeca834,0x3fe4d3a5b69fd1b6,1 +np.float64,0xbfe0c6ca0f618d94,0xbfe017058259efdb,1 +np.float64,0x7fc1bf07b7237e0e,0x4086240000fa5a52,1 +np.float64,0x7fe96af9c0f2d5f3,0x408631f6f0f4faa2,1 +np.float64,0x3fe0728be7a0e518,0x3fdf9881a5869de9,1 +np.float64,0xffe8ea4441b1d488,0xc08631ce0685ae7e,1 +np.float64,0xffd0b973f02172e8,0xc08629121e7fdf85,1 +np.float64,0xffe37b907a26f720,0xc0862fd6529401a0,1 +np.float64,0x3fe0ee826461dd05,0x3fe03a2a424a1b40,1 +np.float64,0xbfe8073c92300e79,0xbfe6340cbd179ac1,1 +np.float64,0x800768383f8ed071,0x800768383f8ed071,1 +np.float64,0x8002e467c7c5c8d0,0x8002e467c7c5c8d0,1 +np.float64,0xbfd8d53ea5b1aa7e,0xbfd83fa7243289d7,1 +np.float64,0xffebefce2bb7df9c,0xc08632b874f4f8dc,1 +np.float64,0xffe3be9eb9277d3d,0xc0862ff1ac70ad0b,1 +np.float64,0xffe2f8a82e65f150,0xc0862f9fd9e77d86,1 +np.float64,0xbfa01d151c203a30,0xbfa01c66dc13a70a,1 +np.float64,0x800877062d30ee0d,0x800877062d30ee0d,1 +np.float64,0xaade16a755bc3,0xaade16a755bc3,1 +np.float64,0xbfeb1abc70363579,0xbfe89b52c3b003aa,1 +np.float64,0x80097d0b2ad2fa17,0x80097d0b2ad2fa17,1 +np.float64,0x8001499907429333,0x8001499907429333,1 +np.float64,0x3fe8db2aaf71b656,0x3fe6dc7873f1b235,1 +np.float64,0x5cfeadc4b9fd6,0x5cfeadc4b9fd6,1 +np.float64,0xff3f77d1fe7ef,0xff3f77d1fe7ef,1 +np.float64,0xffeecd56f9bd9aad,0xc08633806cb1163d,1 +np.float64,0xbf96f3ca582de7a0,0xbf96f34c6b8e1c85,1 +np.float64,0x7ed6b44afdad7,0x7ed6b44afdad7,1 +np.float64,0x80071808da4e3012,0x80071808da4e3012,1 +np.float64,0x3feb8aee2bf715dc,0x3fe8f0a55516615c,1 +np.float64,0x800038f62e2071ed,0x800038f62e2071ed,1 +np.float64,0x3fb13f9af2227f30,0x3fb13c456ced8e08,1 +np.float64,0xffd584d1812b09a4,0xc0862b165558ec0c,1 +np.float64,0x800b20c30fb64186,0x800b20c30fb64186,1 +np.float64,0x80024f9646e49f2d,0x80024f9646e49f2d,1 +np.float64,0xffefffffffffffff,0xc08633ce8fb9f87e,1 +np.float64,0x3fdddbcb5bbbb797,0x3fdcde981111f650,1 +np.float64,0xffed14077f3a280e,0xc086330a795ad634,1 +np.float64,0x800fec2da7ffd85b,0x800fec2da7ffd85b,1 +np.float64,0x3fe8205ffc7040c0,0x3fe6482318d217f9,1 +np.float64,0x3013e5226027d,0x3013e5226027d,1 +np.float64,0xffe4e5aad469cb55,0xc0863065dc2fb4e3,1 +np.float64,0x5cb0f7b2b9620,0x5cb0f7b2b9620,1 +np.float64,0xbfeb4537d2768a70,0xbfe8bbb2c1d3bff9,1 +np.float64,0xbfd859e297b0b3c6,0xbfd7cc807948bf9d,1 +np.float64,0x71f00b8ce3e02,0x71f00b8ce3e02,1 +np.float64,0xf5c1b875eb837,0xf5c1b875eb837,1 +np.float64,0xa0f35c8141e8,0xa0f35c8141e8,1 +np.float64,0xffe24860b42490c1,0xc0862f54222f616e,1 +np.float64,0xffcd9ae8583b35d0,0xc08628181e643a42,1 +np.float64,0x7fe9b710c7736e21,0x4086320ec033490f,1 +np.float64,0x3fd2b9ca1d257394,0x3fd277e631f0c0b3,1 +np.float64,0x23559bfc46ab4,0x23559bfc46ab4,1 +np.float64,0x8002adf75e455bef,0x8002adf75e455bef,1 +np.float64,0xbfefa4d75cbf49af,0xbfebf392e51d6a1a,1 +np.float64,0xffcfef263e3fde4c,0xc08628b336adb611,1 +np.float64,0x80061acaa8ec3596,0x80061acaa8ec3596,1 +np.float64,0x7fc1b33be0236677,0x408623faaddcc17e,1 +np.float64,0x7fe3a84083675080,0x40862fe8972e41e1,1 +np.float64,0xbfe756c1276ead82,0xbfe5a6318b061e1b,1 +np.float64,0xbfae4b71b43c96e0,0xbfae46ed0b6203a4,1 +np.float64,0x800421c6d0a8438e,0x800421c6d0a8438e,1 +np.float64,0x8009ad56fe335aae,0x8009ad56fe335aae,1 +np.float64,0xbfe71afc976e35f9,0xbfe575d21f3d7193,1 +np.float64,0x7fec0bbe4c38177c,0x408632c0710f1d8a,1 +np.float64,0x750e1daeea1c4,0x750e1daeea1c4,1 +np.float64,0x800501d4240a03a9,0x800501d4240a03a9,1 +np.float64,0x800794955cef292b,0x800794955cef292b,1 +np.float64,0x3fdf8a87f5bf1510,0x3fde62f4f00cfa19,1 +np.float64,0xbfebebdbc7f7d7b8,0xbfe939e51ba1340c,1 +np.float64,0xbfe3a16217a742c4,0xbfe292039dd08a71,1 +np.float64,0x3fed6cd04c3ad9a1,0x3fea58995973f74b,1 +np.float64,0xffcad8787335b0f0,0xc086274fbb35dd37,1 +np.float64,0x3fcb178e3d362f1c,0x3fcae4c9f3e6dddc,1 +np.float64,0xbfcadc669435b8cc,0xbfcaaae7cf075420,1 +np.float64,0x7fe0e3906321c720,0x40862eb1bacc5c43,1 +np.float64,0xff8ad5edb035abc0,0xc0861120b6404d0b,1 +np.float64,0x3fe175a21562eb44,0x3fe0b13120a46549,1 +np.float64,0xbfeb4c4a5f769895,0xbfe8c1147f1c9d8f,1 +np.float64,0x7fca22f4e63445e9,0x40862718e9b4094e,1 +np.float64,0x3fe4269d0c684d3a,0x3fe3032aa2015c53,1 +np.float64,0x3fef551c09beaa38,0x3febbabe03f49c83,1 +np.float64,0xffd843df9fb087c0,0xc0862c0c52d5e5d9,1 +np.float64,0x7fc497e2ca292fc5,0x40862530bbd9fcc7,1 +np.float64,0x3fee02919efc0523,0x3feac655588a4acd,1 +np.float64,0x7fed1e52c0fa3ca5,0x4086330d4ddd8a2c,1 +np.float64,0xba04d4ef7409b,0xba04d4ef7409b,1 +np.float64,0x3fee22d0937c45a2,0x3feaddd4ca66b447,1 +np.float64,0xffeb2558cf764ab1,0xc086327da4e84053,1 +np.float64,0xbfe103d987e207b3,0xbfe04d04818ad1ff,1 +np.float64,0x3f9fd7fed03faffe,0x3f9fd6ae9a45be84,1 +np.float64,0x800a53ec4c34a7d9,0x800a53ec4c34a7d9,1 +np.float64,0xbfe2feb17f65fd63,0xbfe206b9d33a78a2,1 +np.float64,0x989bdd613139,0x989bdd613139,1 +np.float64,0xbfdd0ad3fb3a15a8,0xbfdc20c32a530741,1 +np.float64,0xbfc4222163284444,0xbfc40d1c612784b5,1 +np.float64,0xc30cf5c78619f,0xc30cf5c78619f,1 +np.float64,0x3fe913bd6732277b,0x3fe70912f76bad71,1 +np.float64,0x98f175f531e2f,0x98f175f531e2f,1 +np.float64,0x3fed8c1f717b183f,0x3fea6f9fb3af3423,1 +np.float64,0x7fee46b085bc8d60,0x4086335d269eb7e9,1 +np.float64,0x8007480f564e901f,0x8007480f564e901f,1 +np.float64,0xc9b96e179372e,0xc9b96e179372e,1 +np.float64,0x3fe44deac4289bd6,0x3fe32463a74a69e7,1 +np.float64,0x80021d6c5c243ad9,0x80021d6c5c243ad9,1 +np.float64,0xbfebc805a6f7900b,0xbfe91edcf65a1c19,1 +np.float64,0x80044748adc88e92,0x80044748adc88e92,1 +np.float64,0x4007ee44800fe,0x4007ee44800fe,1 +np.float64,0xbfe24307a4648610,0xbfe1648ad5c47b6f,1 +np.float64,0xbfee6d3a93fcda75,0xbfeb13e1a3196e78,1 +np.float64,0x3fe49a287f293451,0x3fe364a11b9f0068,1 +np.float64,0x80052b37ceaa5670,0x80052b37ceaa5670,1 +np.float64,0xbfd42be893a857d2,0xbfd3da05dac7c286,1 +np.float64,0xffb4bbe4ac2977c8,0xc0861fb31bda6956,1 +np.float64,0xbfc732a4142e6548,0xbfc7129a4eafa399,1 +np.float64,0x7fd0696791a0d2ce,0x408628eb7756cb9c,1 +np.float64,0x3fe46c8f8d68d91f,0x3fe33e3df16187c1,1 +np.float64,0x3fe3a28f1ce7451e,0x3fe293043238d08c,1 +np.float64,0xffedc4eb723b89d6,0xc086333a92258c15,1 +np.float64,0x8000d15b4c41a2b7,0x8000d15b4c41a2b7,1 +np.float64,0xffeb73450236e689,0xc08632947b0148ab,1 +np.float64,0xffe68cf4722d19e8,0xc0863101d08d77bd,1 +np.float64,0x800c70eb4698e1d7,0x800c70eb4698e1d7,1 +np.float64,0xffa94387ff529,0xffa94387ff529,1 +np.float64,0x7fe3835d996706ba,0x40862fd985ff8e7d,1 +np.float64,0x3fe55e476feabc8e,0x3fe408a15594ec52,1 +np.float64,0xffc69672222d2ce4,0xc08625ee0c4c0f6a,1 +np.float64,0xbf9d900b883b2020,0xbf9d8efe811d36df,1 +np.float64,0xbfdb9b9755b7372e,0xbfdad0f2aa2cb110,1 +np.float64,0xffeade6073b5bcc0,0xc08632689f17a25d,1 +np.float64,0xffd1d6a6baa3ad4e,0xc086299630a93a7b,1 +np.float64,0x7fd05ba25620b744,0x408628e4be1ef845,1 +np.float64,0xbfc7d422d52fa844,0xbfc7b170a61531bf,1 +np.float64,0x3fd5196797aa32d0,0x3fd4bc0f0e7d8e1d,1 +np.float64,0x617594a4c2eb3,0x617594a4c2eb3,1 +np.float64,0x7fd779bc4caef378,0x40862bc89271b882,1 +np.float64,0xffd2fb262ba5f64c,0xc0862a15561e9524,1 +np.float64,0x72fd661ae5fad,0x72fd661ae5fad,1 +np.float64,0x3fecf441f339e884,0x3fe9ff880d584f64,1 +np.float64,0x7fc3a8968827512c,0x408624d198b05c61,1 +np.float64,0x3fe7a25c56ef44b9,0x3fe5e32509a7c32d,1 +np.float64,0x7fd117d514222fa9,0x4086293ec640d5f2,1 +np.float64,0x3fe37dfe5ee6fbfc,0x3fe273d1bcaa1ef0,1 +np.float64,0xbfed4cd19d7a99a3,0xbfea41064cba4c8b,1 +np.float64,0x8003ff12aaa7fe26,0x8003ff12aaa7fe26,1 +np.float64,0x3fcbc3d1193787a2,0x3fcb8d39e3e88264,1 +np.float64,0xe9ba1a91d3744,0xe9ba1a91d3744,1 +np.float64,0x8002ab71998556e4,0x8002ab71998556e4,1 +np.float64,0x800110057922200c,0x800110057922200c,1 +np.float64,0xbfe3b7af19a76f5e,0xbfe2a502fc0a2882,1 +np.float64,0x7fd9de9d5e33bd3a,0x40862c8f73cccabf,1 +np.float64,0xbfba0f0a86341e18,0xbfba0392f44c2771,1 +np.float64,0x8000000000000000,0x8000000000000000,1 +np.float64,0x7fe5d162e96ba2c5,0x408630be2b15e01b,1 +np.float64,0x800b7f0eac76fe1e,0x800b7f0eac76fe1e,1 +np.float64,0xff98bed150317da0,0xc086160633164f5f,1 +np.float64,0x3fef91fd70ff23fb,0x3febe629709d0ae7,1 +np.float64,0x7fe5bea7f16b7d4f,0x408630b749f445e9,1 +np.float64,0xbfe3dc428467b885,0xbfe2c41ea93fab07,1 +np.float64,0xbfeba1fbfcf743f8,0xbfe9021b52851bb9,1 +np.float64,0x7fd2fb2108a5f641,0x40862a1553f45830,1 +np.float64,0x7feb8199a4370332,0x40863298a7169dad,1 +np.float64,0x800f97ff8d7f2fff,0x800f97ff8d7f2fff,1 +np.float64,0x3fd5e20b6b2bc417,0x3fd57a42bd1c0993,1 +np.float64,0x8006b4072dad680f,0x8006b4072dad680f,1 +np.float64,0x605dccf2c0bba,0x605dccf2c0bba,1 +np.float64,0x3fc705ed142e0bda,0x3fc6e69971d86f73,1 +np.float64,0xffd2ba1aad257436,0xc08629f9bc918f8b,1 +np.float64,0x8002954e23c52a9d,0x8002954e23c52a9d,1 +np.float64,0xbfecc65da7798cbb,0xbfe9dd745be18562,1 +np.float64,0x7fc66110482cc220,0x408625db0db57ef8,1 +np.float64,0x3fcd09446d3a1289,0x3fcccaf2dd0a41ea,1 +np.float64,0x3febe7095437ce13,0x3fe93642d1e73b2a,1 +np.float64,0x8004773c7da8ee7a,0x8004773c7da8ee7a,1 +np.float64,0x8001833241230665,0x8001833241230665,1 +np.float64,0x3fe6a262db6d44c6,0x3fe513b3dab5adce,1 +np.float64,0xe6282cc1cc506,0xe6282cc1cc506,1 +np.float64,0x800b9d8553973b0b,0x800b9d8553973b0b,1 +np.float64,0x3fdfbe0c7b3f7c19,0x3fde912375d867a8,1 +np.float64,0x7fd5ac11ebab5823,0x40862b24dfc6d08e,1 +np.float64,0x800e4b7cb1fc96f9,0x800e4b7cb1fc96f9,1 +np.float64,0x3fe14706da628e0e,0x3fe0883aec2a917a,1 +np.float64,0x7fc963f97532c7f2,0x408626dd9b0cafe1,1 +np.float64,0xbfe9c250b5b384a2,0xbfe791c5eabcb05d,1 +np.float64,0x3fe8d16e6c71a2dd,0x3fe6d4c7a33a0bf4,1 +np.float64,0x3fe474ae4628e95d,0x3fe34515c93f4733,1 +np.float64,0x3fbf3257ee3e64b0,0x3fbf1eb530e126ea,1 +np.float64,0x8005f089b3abe114,0x8005f089b3abe114,1 +np.float64,0x3fece07bccf9c0f8,0x3fe9f0dc228124d5,1 +np.float64,0xbfc52521632a4a44,0xbfc50ccebdf59c2c,1 +np.float64,0x7fdf53beb13ea77c,0x40862e177918195e,1 +np.float64,0x8003d9f6ad07b3ee,0x8003d9f6ad07b3ee,1 +np.float64,0xffeacf96bbb59f2d,0xc086326436b38b1a,1 +np.float64,0xdccaea29b995e,0xdccaea29b995e,1 +np.float64,0x5948d21eb291b,0x5948d21eb291b,1 +np.float64,0x10000000000000,0x10000000000000,1 +np.float64,0x7fef6d2c543eda58,0x408633a98593cdf5,1 +np.float64,0x7feda454f47b48a9,0x40863331cb6dc9f7,1 +np.float64,0x3fdd377cecba6ef8,0x3fdc4968f74a9c83,1 +np.float64,0x800644096d4c8814,0x800644096d4c8814,1 +np.float64,0xbfe33ca15ae67942,0xbfe23be5de832bd8,1 +np.float64,0xffce9582bd3d2b04,0xc086285abdf9bf9d,1 +np.float64,0x3fe6621e86acc43d,0x3fe4df231bfa93e1,1 +np.float64,0xee7d19e9dcfa3,0xee7d19e9dcfa3,1 +np.float64,0x800be5997277cb33,0x800be5997277cb33,1 +np.float64,0x82069041040e,0x82069041040e,1 +np.float64,0x800d6efdc19addfc,0x800d6efdc19addfc,1 +np.float64,0x7fb27770ee24eee1,0x40861ec5ed91b839,1 +np.float64,0x3fd506064caa0c0d,0x3fd4a9a66353fefd,1 +np.float64,0xbfeca9b36bf95367,0xbfe9c81f03ba37b8,1 +np.float64,0xffeab1b7bab5636f,0xc086325b47f61f2b,1 +np.float64,0xffc99f5b2e333eb8,0xc08626f03b08b412,1 +np.float64,0x3fbf1a71bc3e34e3,0x3fbf06fbcaa5de58,1 +np.float64,0x3fe75015736ea02b,0x3fe5a0cd8d763d8d,1 +np.float64,0xffe6a7442fad4e88,0xc086310b20addba4,1 +np.float64,0x3fe5d62ff86bac60,0x3fe46c033195bf28,1 +np.float64,0x7fd0b1f0362163df,0x4086290e857dc1be,1 +np.float64,0xbe0353737c06b,0xbe0353737c06b,1 +np.float64,0x7fec912d8739225a,0x408632e627704635,1 +np.float64,0xded8ba2fbdb18,0xded8ba2fbdb18,1 +np.float64,0x7fec0b53fdf816a7,0x408632c052bc1bd2,1 +np.float64,0x7fe9640d12b2c819,0x408631f4c2ba54d8,1 +np.float64,0x800be714eeb7ce2a,0x800be714eeb7ce2a,1 +np.float64,0xbfcf444a793e8894,0xbfcef6c126b54853,1 +np.float64,0xffeb20cf1bf6419e,0xc086327c4e6ffe80,1 +np.float64,0xc07de22180fd,0xc07de22180fd,1 +np.float64,0xffed129d387a253a,0xc086330a15ad0adb,1 +np.float64,0x3fd9e94fedb3d2a0,0x3fd94049924706a8,1 +np.float64,0x7fe6ba488c2d7490,0x40863111d51e7861,1 +np.float64,0xbfebbdf25db77be5,0xbfe91740ad7ba521,1 +np.float64,0x7fbc6c3c4838d878,0x40862239160cb613,1 +np.float64,0xbfefa82ecebf505e,0xbfebf5f31957dffd,1 +np.float64,0x800bebeb7ad7d7d7,0x800bebeb7ad7d7d7,1 +np.float64,0x7fecccc6f8f9998d,0x408632f6c6da8aac,1 +np.float64,0xcbe4926197ca,0xcbe4926197ca,1 +np.float64,0x2c5d9fd858bb5,0x2c5d9fd858bb5,1 +np.float64,0xbfe9fb021073f604,0xbfe7bddc61f1151a,1 +np.float64,0xbfebb18572f7630b,0xbfe90ddc5002313f,1 +np.float64,0x13bb0d3227763,0x13bb0d3227763,1 +np.float64,0x3feefa5e5cbdf4bd,0x3feb79b9e8ce16bf,1 +np.float64,0x3fc97f086132fe10,0x3fc9549fc8e15ecb,1 +np.float64,0xffe70887c06e110f,0xc086312d30fd31cf,1 +np.float64,0xa00c113540182,0xa00c113540182,1 +np.float64,0x800950984772a131,0x800950984772a131,1 +np.float64,0x1,0x1,1 +np.float64,0x3fd83b4026b07680,0x3fd7afdc659d9a34,1 +np.float64,0xbfe32348fbe64692,0xbfe226292a706a1a,1 +np.float64,0x800b894dcc77129c,0x800b894dcc77129c,1 +np.float64,0xeb2ca419d6595,0xeb2ca419d6595,1 +np.float64,0xbff0000000000000,0xbfec34366179d427,1 +np.float64,0x3feb269e99f64d3d,0x3fe8a4634b927a21,1 +np.float64,0xbfe83149d7706294,0xbfe655a2b245254e,1 +np.float64,0xbfe6eef3ca6ddde8,0xbfe5521310e24d16,1 +np.float64,0x3fea89a4b7b51349,0x3fe82c1fc69edcec,1 +np.float64,0x800f2a8bf17e5518,0x800f2a8bf17e5518,1 +np.float64,0x800f71fac29ee3f6,0x800f71fac29ee3f6,1 +np.float64,0xe7cb31f1cf966,0xe7cb31f1cf966,1 +np.float64,0x3b0f8752761f2,0x3b0f8752761f2,1 +np.float64,0x3fea27dea3744fbd,0x3fe7e0a4705476b2,1 +np.float64,0xbfa97c019c32f800,0xbfa97950c1257b92,1 +np.float64,0xffeff13647ffe26c,0xc08633cadc7105ed,1 +np.float64,0x3feee162353dc2c4,0x3feb67c2da0fbce8,1 +np.float64,0x80088c0807911810,0x80088c0807911810,1 +np.float64,0x3fe936ab1db26d56,0x3fe72489bc69719d,1 +np.float64,0xa2f84bd545f0a,0xa2f84bd545f0a,1 +np.float64,0xbfed445ed27a88be,0xbfea3acac0aaf482,1 +np.float64,0x800faf3e69df5e7d,0x800faf3e69df5e7d,1 +np.float64,0x3fc145a330228b46,0x3fc13853f11b1c90,1 +np.float64,0xbfe25ec5abe4bd8c,0xbfe17c9e9b486f07,1 +np.float64,0x3fe119b160e23363,0x3fe0604b10178966,1 +np.float64,0x7fe0cbf2836197e4,0x40862ea6831e5f4a,1 +np.float64,0x3fe75dd3b4eebba8,0x3fe5abe80fd628fb,1 +np.float64,0x3f7c391000387220,0x3f7c39015d8f3a36,1 +np.float64,0x899d9cad133b4,0x899d9cad133b4,1 +np.float64,0x3fe5f0e34febe1c6,0x3fe4820cefe138fc,1 +np.float64,0x7fe060dfdba0c1bf,0x40862e72de8afcd0,1 +np.float64,0xbfae42f7103c85f0,0xbfae3e7630819c60,1 +np.float64,0x35f1f2c06be5,0x35f1f2c06be5,1 +np.float64,0xffc5194d362a329c,0xc086256266c8b7ad,1 +np.float64,0xbfda034f1b34069e,0xbfd95860a44c43ad,1 +np.float64,0x32bcebca6579e,0x32bcebca6579e,1 +np.float64,0xbfd1751ebca2ea3e,0xbfd13f79f45bf75c,1 +np.float64,0x3fee4fa1e5bc9f44,0x3feafe69e0d6c1c7,1 +np.float64,0x7f9c03cd5038079a,0x4086170459172900,1 +np.float64,0x7fc5fb6d6d2bf6da,0x408625b6651cfc73,1 +np.float64,0x7ff8000000000000,0x7ff8000000000000,1 +np.float64,0xffd1a8162ca3502c,0xc0862981333931ad,1 +np.float64,0x7fc415c198282b82,0x408624fd8c155d1b,1 +np.float64,0xffda37fbe7b46ff8,0xc0862caae7865c43,1 +np.float64,0xbfef4312257e8624,0xbfebadd89f3ee31c,1 +np.float64,0xbfec45e1fd788bc4,0xbfe97d8b14db6274,1 +np.float64,0xbfe6fdcfd26dfba0,0xbfe55e25b770d00a,1 +np.float64,0x7feb66d424f6cda7,0x40863290d9ff7ea2,1 +np.float64,0x8b08a29916115,0x8b08a29916115,1 +np.float64,0xffe12ca25c625944,0xc0862ed40d769f72,1 +np.float64,0x7ff4000000000000,0x7ffc000000000000,1 +np.float64,0x804925e100925,0x804925e100925,1 +np.float64,0xcebf3e019d9,0xcebf3e019d9,1 +np.float64,0xbfd5d75d4aabaeba,0xbfd57027671dedf7,1 +np.float64,0x800b829ecd37053e,0x800b829ecd37053e,1 +np.float64,0x800b1205daf6240c,0x800b1205daf6240c,1 +np.float64,0x3fdf7e9889befd31,0x3fde583fdff406c3,1 +np.float64,0x7ff0000000000000,0x7ff0000000000000,1 +np.float64,0x3fdc09760d3812ec,0x3fdb35b55c8090c6,1 +np.float64,0x800c4d99e4f89b34,0x800c4d99e4f89b34,1 +np.float64,0xffbaa6772e354cf0,0xc08621b535badb2f,1 +np.float64,0xbfc91188fd322310,0xbfc8e933b5d25ea7,1 +np.float64,0xffc1b947f4237290,0xc08623fd69164251,1 +np.float64,0x3fc6ab3b252d5678,0x3fc68d50bbac106d,1 +np.float64,0xffac8eb968391d70,0xc0861cb734833355,1 +np.float64,0xffe29a35c365346b,0xc0862f77a1aed6d8,1 +np.float64,0x3fde14b9543c2973,0x3fdd122697779015,1 +np.float64,0xbf10f5400021e000,0xbf10f53fffef1383,1 +np.float64,0xffe0831aa3e10635,0xc0862e838553d0ca,1 +np.float64,0x3fccbadbcf3975b8,0x3fcc7e768d0154ec,1 +np.float64,0x3fe092ef66e125df,0x3fdfd212a7116c9b,1 +np.float64,0xbfd727f039ae4fe0,0xbfd6adad040b2334,1 +np.float64,0xbfe4223b93a84477,0xbfe2ff7587364db4,1 +np.float64,0x3f4e5c3a003cb874,0x3f4e5c39b75c70f7,1 +np.float64,0x800e76b1a87ced63,0x800e76b1a87ced63,1 +np.float64,0x3fed2b7368fa56e7,0x3fea2863b9131b8c,1 +np.float64,0xffadb76ec43b6ee0,0xc0861d08ae79f20c,1 +np.float64,0x800b6a0cd1f6d41a,0x800b6a0cd1f6d41a,1 +np.float64,0xffee6aa943fcd552,0xc0863366a24250d5,1 +np.float64,0xbfe68cbc4e6d1978,0xbfe502040591aa5b,1 +np.float64,0xff859a38002b3480,0xc0860f64726235cc,1 +np.float64,0x3474d13e68e9b,0x3474d13e68e9b,1 +np.float64,0xffc11d49f6223a94,0xc08623b5c2df9712,1 +np.float64,0x800d82d019bb05a0,0x800d82d019bb05a0,1 +np.float64,0xbfe2af0192255e03,0xbfe1c20e38106388,1 +np.float64,0x3fe97d13c032fa28,0x3fe75bba11a65f86,1 +np.float64,0x7fcd457e133a8afb,0x40862800e80f5863,1 +np.float64,0x9d7254cf3ae4b,0x9d7254cf3ae4b,1 +np.float64,0x8003047675a608ee,0x8003047675a608ee,1 +np.float64,0x3fead6cd7d75ad9a,0x3fe8676138e5ff93,1 +np.float64,0x3fea6ee3b0f4ddc7,0x3fe817838a2bcbe3,1 +np.float64,0x3feed0edea7da1dc,0x3feb5bea3cb12fe2,1 +np.float64,0x88003fe510008,0x88003fe510008,1 +np.float64,0x3fe64cadc56c995c,0x3fe4cd8ead87fc79,1 +np.float64,0xaae30c5955c62,0xaae30c5955c62,1 +np.float64,0x7fc8c97cae3192f8,0x408626ac579f4fc5,1 +np.float64,0xbfc2bc0e8b25781c,0xbfc2ab188fdab7dc,1 +np.float64,0xc8f8e5e791f1d,0xc8f8e5e791f1d,1 +np.float64,0x3fecfaa5d6f9f54c,0x3fea0444dabe5a15,1 +np.float64,0xbfeb93740ff726e8,0xbfe8f71a9ab13baf,1 +np.float64,0xffd951236c32a246,0xc0862c633a4661eb,1 +np.float64,0x3fddbc5fcd3b78c0,0x3fdcc21c1a0a9246,1 +np.float64,0xbfd242443da48488,0xbfd20512d91f7924,1 +np.float64,0x2a3689b2546d2,0x2a3689b2546d2,1 +np.float64,0xffe24c67382498ce,0xc0862f55e4ea6283,1 +np.float64,0x800cbfce22197f9c,0x800cbfce22197f9c,1 +np.float64,0x8002269428044d29,0x8002269428044d29,1 +np.float64,0x7fd44babbd289756,0x40862a9e79b51c3b,1 +np.float64,0x3feea056a27d40ad,0x3feb38dcddb682f0,1 +np.float64,0xffeca8174b39502e,0xc08632ec8f88a5b2,1 +np.float64,0x7fbe0853a03c10a6,0x408622a9e8d53a9e,1 +np.float64,0xbfa9704b2432e090,0xbfa96d9dfc8c0cc2,1 +np.float64,0x800bda28fab7b452,0x800bda28fab7b452,1 +np.float64,0xbfb0ffa2f621ff48,0xbfb0fc71f405e82a,1 +np.float64,0xbfe66c04216cd808,0xbfe4e73ea3b58cf6,1 +np.float64,0x3fe336ea5d266dd5,0x3fe236ffcf078c62,1 +np.float64,0xbfe7729ae6aee536,0xbfe5bcad4b8ac62d,1 +np.float64,0x558cfc96ab1a0,0x558cfc96ab1a0,1 +np.float64,0xbfe7d792aaefaf26,0xbfe60de1b8f0279d,1 +np.float64,0xffd19ef6bda33dee,0xc086297d0ffee3c7,1 +np.float64,0x666b3ab4ccd68,0x666b3ab4ccd68,1 +np.float64,0xffa3d89e3c27b140,0xc08619cdeb2c1e49,1 +np.float64,0xbfb1728f7f62f,0xbfb1728f7f62f,1 +np.float64,0x3fc76319f32ec634,0x3fc74247bd005e20,1 +np.float64,0xbfbf1caee23e3960,0xbfbf0934c13d70e2,1 +np.float64,0x7fe79626f32f2c4d,0x4086315dcc68a5cb,1 +np.float64,0xffee78c4603cf188,0xc086336a572c05c2,1 +np.float64,0x3fce546eda3ca8de,0x3fce0d8d737fd31d,1 +np.float64,0xa223644d4446d,0xa223644d4446d,1 +np.float64,0x3fecea878b79d510,0x3fe9f850d50973f6,1 +np.float64,0x3fc20e0ea1241c1d,0x3fc1fedda87c5e75,1 +np.float64,0xffd1c5a99ca38b54,0xc086298e8e94cd47,1 +np.float64,0x7feb2c299d765852,0x4086327fa6db2808,1 +np.float64,0xcaf9d09595f3a,0xcaf9d09595f3a,1 +np.float64,0xbfe293bf21e5277e,0xbfe1aa7f6ac274ef,1 +np.float64,0xbfbaa3c8ce354790,0xbfba97891df19c01,1 +np.float64,0x3faf5784543eaf09,0x3faf5283acc7d71d,1 +np.float64,0x7fc014f8f62029f1,0x40862336531c662d,1 +np.float64,0xbfe0d9ac2d61b358,0xbfe027bce36699ca,1 +np.float64,0x8003e112ff27c227,0x8003e112ff27c227,1 +np.float64,0xffec0d4151381a82,0xc08632c0df718dd0,1 +np.float64,0x7fa2156fb0242ade,0x4086190f7587d708,1 +np.float64,0xd698358dad307,0xd698358dad307,1 +np.float64,0xbfed8d1b0efb1a36,0xbfea70588ef9ba18,1 +np.float64,0xbfd2cae6a92595ce,0xbfd28851e2185dee,1 +np.float64,0xffe7a36764ef46ce,0xc086316249c9287a,1 +np.float64,0xbfdb8ad8e5b715b2,0xbfdac19213c14315,1 +np.float64,0x3b5dba6076bc,0x3b5dba6076bc,1 +np.float64,0x800e6e8347bcdd07,0x800e6e8347bcdd07,1 +np.float64,0x800bea9f3fb7d53f,0x800bea9f3fb7d53f,1 +np.float64,0x7fb6d0e5fc2da1cb,0x4086207714c4ab85,1 +np.float64,0x0,0x0,1 +np.float64,0xbfe2aa1e1465543c,0xbfe1bdd550ef2966,1 +np.float64,0x7fd3f6a47fa7ed48,0x40862a7caea33055,1 +np.float64,0x800094e292c129c6,0x800094e292c129c6,1 +np.float64,0x800e1500ecbc2a02,0x800e1500ecbc2a02,1 +np.float64,0xbfd8ff6f97b1fee0,0xbfd866f84346ecdc,1 +np.float64,0x681457d0d028c,0x681457d0d028c,1 +np.float64,0x3feed0b5987da16b,0x3feb5bc1ab424984,1 +np.float64,0x3fdbcb34cdb79668,0x3fdafca540f32c06,1 +np.float64,0xbfdc9eacdcb93d5a,0xbfdbbe274aa8aeb0,1 +np.float64,0xffe6e35d526dc6ba,0xc08631203df38ed2,1 +np.float64,0x3fcac1cc65358398,0x3fca90de41889613,1 +np.float64,0xbfebf07a55b7e0f5,0xbfe93d6007db0c67,1 +np.float64,0xbfd7a7b1e7af4f64,0xbfd725a9081c22cb,1 +np.float64,0x800232bd7de4657c,0x800232bd7de4657c,1 +np.float64,0x7fb1dae43c23b5c7,0x40861e80f5c0a64e,1 +np.float64,0x8013ded70027c,0x8013ded70027c,1 +np.float64,0x7fc4373a59286e74,0x4086250ad60575d0,1 +np.float64,0xbfe9980fd6733020,0xbfe770d1352d0ed3,1 +np.float64,0x8008a66b8dd14cd7,0x8008a66b8dd14cd7,1 +np.float64,0xbfaebc67f83d78d0,0xbfaeb7b015848478,1 +np.float64,0xffd0c52762218a4e,0xc0862917b564afc6,1 +np.float64,0xbfd503860aaa070c,0xbfd4a74618441561,1 +np.float64,0x5bdacabcb7b5a,0x5bdacabcb7b5a,1 +np.float64,0xf3623cffe6c48,0xf3623cffe6c48,1 +np.float64,0x7fe16c6c7ea2d8d8,0x40862ef18d90201f,1 +np.float64,0x3ff0000000000000,0x3fec34366179d427,1 +np.float64,0x7fe19cbc84233978,0x40862f079dcbc169,1 +np.float64,0x3fcfd3d6933fa7ad,0x3fcf822187907f6b,1 +np.float64,0x8007d65d672facbc,0x8007d65d672facbc,1 +np.float64,0xffca6115aa34c22c,0xc086272bd7728750,1 +np.float64,0xbfe77ab1556ef562,0xbfe5c332fb55b66e,1 +np.float64,0x8001ed797c23daf4,0x8001ed797c23daf4,1 +np.float64,0x7fdd3d16cb3a7a2d,0x40862d8a2c869281,1 +np.float64,0x75f36beaebe6e,0x75f36beaebe6e,1 +np.float64,0xffda3c2798b47850,0xc0862cac2d3435df,1 +np.float64,0xbfa37cc3c426f980,0xbfa37b8f9d3ec4b7,1 +np.float64,0x80030ea8bd061d52,0x80030ea8bd061d52,1 +np.float64,0xffe41f7617683eec,0xc08630188a3e135e,1 +np.float64,0x800e40590dfc80b2,0x800e40590dfc80b2,1 +np.float64,0x3fea950d80f52a1c,0x3fe834e74481e66f,1 +np.float64,0xffec95e39a792bc6,0xc08632e779150084,1 +np.float64,0xbfd54310ecaa8622,0xbfd4e39c4d767002,1 +np.float64,0xffd40c9971a81932,0xc0862a85764eb2f4,1 +np.float64,0xb0a2230761445,0xb0a2230761445,1 +np.float64,0x80092973661252e7,0x80092973661252e7,1 +np.float64,0x7fb13b030a227605,0x40861e380aeb5549,1 +np.float64,0x3fbd5d8db23abb1b,0x3fbd4d2a0b94af36,1 +np.float64,0xbfd6cb8567ad970a,0xbfd656b19ab8fa61,1 +np.float64,0xbfe7c0fd346f81fa,0xbfe5fbc28807c794,1 +np.float64,0xffd586579eab0cb0,0xc0862b16e65c0754,1 +np.float64,0x8000e52da461ca5c,0x8000e52da461ca5c,1 +np.float64,0x3fc69d17112d3a2e,0x3fc67f63fe1fea1c,1 +np.float64,0x3fd36ba892a6d750,0x3fd3225be1fa87af,1 +np.float64,0x7fe2850598e50a0a,0x40862f6e7fcd6c1a,1 +np.float64,0x80074a4dacce949c,0x80074a4dacce949c,1 +np.float64,0x3fe25eea4d64bdd5,0x3fe17cbe5fefbd4e,1 +np.float64,0xbfe250c08be4a181,0xbfe17074c520e5de,1 +np.float64,0x8000f5665481eacd,0x8000f5665481eacd,1 +np.float64,0x7fdb3172f83662e5,0x40862cf5a46764f1,1 +np.float64,0x7fd8ed82d631db05,0x40862c4380658afa,1 +np.float64,0xffec5163feb8a2c7,0xc08632d4366aab06,1 +np.float64,0x800ff14ac6ffe296,0x800ff14ac6ffe296,1 +np.float64,0xbfc7cc7aea2f98f4,0xbfc7a9e9cb38f023,1 +np.float64,0xbfd50cdfc32a19c0,0xbfd4b0282b452fb2,1 +np.float64,0xbfec256d75b84adb,0xbfe965328c1860b2,1 +np.float64,0xffe860c4cdb0c189,0xc08631a164b7059a,1 +np.float64,0xbfe23de164247bc3,0xbfe16011bffa4651,1 +np.float64,0xcc96b39d992d7,0xcc96b39d992d7,1 +np.float64,0xbfec43acf938875a,0xbfe97be3a13b50c3,1 +np.float64,0xc4f587bb89eb1,0xc4f587bb89eb1,1 +np.float64,0xbfcd971d9a3b2e3c,0xbfcd5537ad15dab4,1 +np.float64,0xffcaf00d8035e01c,0xc0862756bf2cdf8f,1 +np.float64,0x8008c26f93f184e0,0x8008c26f93f184e0,1 +np.float64,0xfff0000000000000,0xfff0000000000000,1 +np.float64,0xbfd13552c3a26aa6,0xbfd101e5e252eb7b,1 +np.float64,0x7fe497235e292e46,0x4086304792fb423a,1 +np.float64,0x7fd6dc0192adb802,0x40862b921a5e935d,1 +np.float64,0xf16d49a1e2da9,0xf16d49a1e2da9,1 +np.float64,0xffef6b1b71bed636,0xc08633a8feed0178,1 +np.float64,0x7fe15ec62f62bd8b,0x40862eeb46b193dc,1 +np.float64,0x3fef4369ec7e86d4,0x3febae1768be52cc,1 +np.float64,0x4f84e8e89f09e,0x4f84e8e89f09e,1 +np.float64,0xbfe19e71ade33ce4,0xbfe0d4fad05e0ebc,1 +np.float64,0xbfe7e1df1defc3be,0xbfe616233e15b3d0,1 +np.float64,0x7fe9349afdb26935,0x408631e5c1c5c6cd,1 +np.float64,0xff90c35ac82186c0,0xc08612e896a06467,1 +np.float64,0xbfe88bf8807117f1,0xbfe69dc786464422,1 +np.float64,0x3feaf9ff6475f3fe,0x3fe8825132410d18,1 +np.float64,0x9ff487a33fe91,0x9ff487a33fe91,1 +np.float64,0x7fedb30159bb6602,0x40863335c0419322,1 +np.float64,0x800bddf6ed77bbee,0x800bddf6ed77bbee,1 +np.float64,0x3fd919df133233be,0x3fd87f963b9584ce,1 +np.float64,0x7fd64da3b52c9b46,0x40862b5fa9dd3b6d,1 +np.float64,0xbfce288db43c511c,0xbfcde2d953407ae8,1 +np.float64,0x3fe88bc72771178e,0x3fe69da05e9e9b4e,1 +np.float64,0x800feafe259fd5fc,0x800feafe259fd5fc,1 +np.float64,0x3febbbff4a7777ff,0x3fe915c78f6a280f,1 +np.float64,0xbfefbde4417f7bc9,0xbfec055f4fb2cd21,1 +np.float64,0xf13ca103e2794,0xf13ca103e2794,1 +np.float64,0x3fe6423884ec8471,0x3fe4c4f97eaa876a,1 +np.float64,0x800ca01c8cb94039,0x800ca01c8cb94039,1 +np.float64,0x3fbc5073f638a0e0,0x3fbc41c163ac0001,1 +np.float64,0xbfda0d83cfb41b08,0xbfd961d4cacc82cf,1 +np.float64,0x800f37b8f17e6f72,0x800f37b8f17e6f72,1 +np.float64,0x7fe0b08cd7216119,0x40862e996becb771,1 +np.float64,0xffd4222a40a84454,0xc0862a8e0c984917,1 +np.float64,0x7feb3df98ff67bf2,0x40863284e3a86ee6,1 +np.float64,0x8001d5d291e3aba6,0x8001d5d291e3aba6,1 +np.float64,0xbfd3c21629a7842c,0xbfd3750095a5894a,1 +np.float64,0xbfd069eb48a0d3d6,0xbfd03d2b1c2ae9db,1 +np.float64,0xffeb1be2973637c4,0xc086327ada954662,1 +np.float64,0x3fc659f97e2cb3f3,0x3fc63d497a451f10,1 +np.float64,0xbfeb624bc776c498,0xbfe8d1cf7c0626ca,1 +np.float64,0xffeedf26e23dbe4d,0xc08633850baab425,1 +np.float64,0xffe70da48a6e1b48,0xc086312ef75d5036,1 +np.float64,0x2b4f4830569ea,0x2b4f4830569ea,1 +np.float64,0xffe82e7fcfb05cff,0xc0863190d4771f75,1 +np.float64,0x3fcc2c1fd5385840,0x3fcbf3211ddc5123,1 +np.float64,0x7fe22ced5a6459da,0x40862f481629ee6a,1 +np.float64,0x7fe13d2895e27a50,0x40862edbbc411899,1 +np.float64,0x3fd54c4280aa9884,0x3fd4ec55a946c5d7,1 +np.float64,0xffd75b8e01aeb71c,0xc0862bbe42d76e5e,1 +np.float64,0x7f1d5376fe3ab,0x7f1d5376fe3ab,1 +np.float64,0x3fe6ec6c902dd8d9,0x3fe55004f35192bd,1 +np.float64,0x5634504aac68b,0x5634504aac68b,1 +np.float64,0x3feedb0d83bdb61b,0x3feb633467467ce6,1 +np.float64,0x3fddb1c0dcbb6380,0x3fdcb87a02daf1fa,1 +np.float64,0xbfa832da443065b0,0xbfa8308c70257209,1 +np.float64,0x87a9836b0f531,0x87a9836b0f531,1 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-arctan.csv b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-arctan.csv new file mode 100644 index 0000000000000000000000000000000000000000..c03e144a994afceb4b43622381bd07134dec1e9e --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-arctan.csv @@ -0,0 +1,1429 @@ +dtype,input,output,ulperrortol +np.float32,0x3f338252,0x3f1c8d9c,3 +np.float32,0x7e569df2,0x3fc90fdb,3 +np.float32,0xbf347e25,0xbf1d361f,3 +np.float32,0xbf0a654e,0xbefdbfd2,3 +np.float32,0x8070968e,0x8070968e,3 +np.float32,0x803cfb27,0x803cfb27,3 +np.float32,0x8024362e,0x8024362e,3 +np.float32,0xfd55dca0,0xbfc90fdb,3 +np.float32,0x592b82,0x592b82,3 +np.float32,0x802eb8e1,0x802eb8e1,3 +np.float32,0xbc5fef40,0xbc5febae,3 +np.float32,0x3f1f6ce8,0x3f0e967c,3 +np.float32,0x20bedc,0x20bedc,3 +np.float32,0xbf058860,0xbef629c7,3 +np.float32,0x311504,0x311504,3 +np.float32,0xbd23f560,0xbd23defa,3 +np.float32,0x800ff4e8,0x800ff4e8,3 +np.float32,0x355009,0x355009,3 +np.float32,0x3f7be42e,0x3f46fdb3,3 +np.float32,0xbf225f7c,0xbf10b364,3 +np.float32,0x8074fa9e,0x8074fa9e,3 +np.float32,0xbea4b418,0xbe9f59ce,3 +np.float32,0xbe909c14,0xbe8cf045,3 +np.float32,0x80026bee,0x80026bee,3 +np.float32,0x3d789c20,0x3d784e25,3 +np.float32,0x7f56a4ba,0x3fc90fdb,3 +np.float32,0xbf70d141,0xbf413db7,3 +np.float32,0xbf2c4886,0xbf17a505,3 +np.float32,0x7e2993bf,0x3fc90fdb,3 +np.float32,0xbe2c8a30,0xbe2aef28,3 +np.float32,0x803f82d9,0x803f82d9,3 +np.float32,0x3f062fbc,0x3ef730a1,3 +np.float32,0x3f349ee0,0x3f1d4bfa,3 +np.float32,0x3eccfb69,0x3ec2f9e8,3 +np.float32,0x7e8a85dd,0x3fc90fdb,3 +np.float32,0x25331,0x25331,3 +np.float32,0x464f19,0x464f19,3 +np.float32,0x8035c818,0x8035c818,3 +np.float32,0x802e5799,0x802e5799,3 +np.float32,0x64e1c0,0x64e1c0,3 +np.float32,0x701cc2,0x701cc2,3 +np.float32,0x265c57,0x265c57,3 +np.float32,0x807a053f,0x807a053f,3 +np.float32,0x3bd2c412,0x3bd2c354,3 +np.float32,0xff28f1c8,0xbfc90fdb,3 +np.float32,0x7f08f08b,0x3fc90fdb,3 +np.float32,0x800c50e4,0x800c50e4,3 +np.float32,0x369674,0x369674,3 +np.float32,0xbf5b7db3,0xbf3571bf,3 +np.float32,0x7edcf5e2,0x3fc90fdb,3 +np.float32,0x800e5d4b,0x800e5d4b,3 +np.float32,0x80722554,0x80722554,3 +np.float32,0x693f33,0x693f33,3 +np.float32,0x800844e4,0x800844e4,3 +np.float32,0xbf111b82,0xbf0402ec,3 +np.float32,0x7df9c9ac,0x3fc90fdb,3 +np.float32,0xbf6619a6,0xbf3b6f57,3 +np.float32,0x8002fafe,0x8002fafe,3 +np.float32,0xfe1e67f8,0xbfc90fdb,3 +np.float32,0x3f7f4bf8,0x3f48b5b7,3 +np.float32,0x7f017b20,0x3fc90fdb,3 +np.float32,0x2d9b07,0x2d9b07,3 +np.float32,0x803aa174,0x803aa174,3 +np.float32,0x7d530336,0x3fc90fdb,3 +np.float32,0x80662195,0x80662195,3 +np.float32,0xfd5ebcf0,0xbfc90fdb,3 +np.float32,0xbe7b8dcc,0xbe76ab59,3 +np.float32,0x7f2bacaf,0x3fc90fdb,3 +np.float32,0x3f194fc4,0x3f0a229e,3 +np.float32,0x7ee21cdf,0x3fc90fdb,3 +np.float32,0x3f5a17fc,0x3f34a307,3 +np.float32,0x7f100c58,0x3fc90fdb,3 +np.float32,0x7e9128f5,0x3fc90fdb,3 +np.float32,0xbf2107c6,0xbf0fbdb4,3 +np.float32,0xbd29c800,0xbd29af22,3 +np.float32,0xbf5af499,0xbf3522a6,3 +np.float32,0x801bde44,0x801bde44,3 +np.float32,0xfeb4761a,0xbfc90fdb,3 +np.float32,0x3d88aa1b,0x3d887650,3 +np.float32,0x7eba5e0b,0x3fc90fdb,3 +np.float32,0x803906bd,0x803906bd,3 +np.float32,0x80101512,0x80101512,3 +np.float32,0x7e898f83,0x3fc90fdb,3 +np.float32,0x806406d3,0x806406d3,3 +np.float32,0x7ed20fc0,0x3fc90fdb,3 +np.float32,0x20827d,0x20827d,3 +np.float32,0x3f361359,0x3f1e43fe,3 +np.float32,0xfe4ef8d8,0xbfc90fdb,3 +np.float32,0x805e7d2d,0x805e7d2d,3 +np.float32,0xbe4316b0,0xbe40c745,3 +np.float32,0xbf0a1c06,0xbefd4e5a,3 +np.float32,0x3e202860,0x3e1edee1,3 +np.float32,0xbeb32a2c,0xbeac5899,3 +np.float32,0xfe528838,0xbfc90fdb,3 +np.float32,0x2f73e2,0x2f73e2,3 +np.float32,0xbe16e010,0xbe15cc27,3 +np.float32,0x3f50d6c5,0x3f2f2d75,3 +np.float32,0xbe88a6a2,0xbe8589c7,3 +np.float32,0x3ee36060,0x3ed5fb36,3 +np.float32,0x6c978b,0x6c978b,3 +np.float32,0x7f1b735f,0x3fc90fdb,3 +np.float32,0x3dad8256,0x3dad1885,3 +np.float32,0x807f5094,0x807f5094,3 +np.float32,0x65c358,0x65c358,3 +np.float32,0xff315ce4,0xbfc90fdb,3 +np.float32,0x7411a6,0x7411a6,3 +np.float32,0x80757b04,0x80757b04,3 +np.float32,0x3eec73a6,0x3edd82f4,3 +np.float32,0xfe9f69e8,0xbfc90fdb,3 +np.float32,0x801f4fa8,0x801f4fa8,3 +np.float32,0xbf6f2fae,0xbf405f79,3 +np.float32,0xfea206b6,0xbfc90fdb,3 +np.float32,0x3f257301,0x3f12e1ee,3 +np.float32,0x7ea6a506,0x3fc90fdb,3 +np.float32,0x80800000,0x80800000,3 +np.float32,0xff735c2d,0xbfc90fdb,3 +np.float32,0x80197f95,0x80197f95,3 +np.float32,0x7f4a354f,0x3fc90fdb,3 +np.float32,0xff320c00,0xbfc90fdb,3 +np.float32,0x3f2659de,0x3f138484,3 +np.float32,0xbe5451bc,0xbe515a52,3 +np.float32,0x3f6e228c,0x3f3fcf7c,3 +np.float32,0x66855a,0x66855a,3 +np.float32,0x8034b3a3,0x8034b3a3,3 +np.float32,0xbe21a2fc,0xbe20505d,3 +np.float32,0x7f79e2dc,0x3fc90fdb,3 +np.float32,0xbe19a8e0,0xbe18858c,3 +np.float32,0x10802c,0x10802c,3 +np.float32,0xfeee579e,0xbfc90fdb,3 +np.float32,0x3f3292c8,0x3f1becc0,3 +np.float32,0xbf595a71,0xbf34350a,3 +np.float32,0xbf7c3373,0xbf4725f4,3 +np.float32,0xbdd30938,0xbdd24b36,3 +np.float32,0x153a17,0x153a17,3 +np.float32,0x807282a0,0x807282a0,3 +np.float32,0xfe817322,0xbfc90fdb,3 +np.float32,0x3f1b3628,0x3f0b8771,3 +np.float32,0x41be8f,0x41be8f,3 +np.float32,0x7f4a8343,0x3fc90fdb,3 +np.float32,0x3dc4ea2b,0x3dc44fae,3 +np.float32,0x802aac25,0x802aac25,3 +np.float32,0xbf20e1d7,0xbf0fa284,3 +np.float32,0xfd91a1b0,0xbfc90fdb,3 +np.float32,0x3f0d5476,0x3f012265,3 +np.float32,0x21c916,0x21c916,3 +np.float32,0x807df399,0x807df399,3 +np.float32,0x7e207b4c,0x3fc90fdb,3 +np.float32,0x8055f8ff,0x8055f8ff,3 +np.float32,0x7edf3b01,0x3fc90fdb,3 +np.float32,0x803a8df3,0x803a8df3,3 +np.float32,0x3ce3b002,0x3ce3a101,3 +np.float32,0x3f62dd54,0x3f39a248,3 +np.float32,0xff33ae10,0xbfc90fdb,3 +np.float32,0x7e3de69d,0x3fc90fdb,3 +np.float32,0x8024581e,0x8024581e,3 +np.float32,0xbf4ac99d,0xbf2b807a,3 +np.float32,0x3f157d19,0x3f074d8c,3 +np.float32,0xfed383f4,0xbfc90fdb,3 +np.float32,0xbf5a39fa,0xbf34b6b8,3 +np.float32,0x800d757d,0x800d757d,3 +np.float32,0x807d606b,0x807d606b,3 +np.float32,0x3e828f89,0x3e7fac2d,3 +np.float32,0x7a6604,0x7a6604,3 +np.float32,0x7dc7e72b,0x3fc90fdb,3 +np.float32,0x80144146,0x80144146,3 +np.float32,0x7c2eed69,0x3fc90fdb,3 +np.float32,0x3f5b4d8c,0x3f3555fc,3 +np.float32,0xfd8b7778,0xbfc90fdb,3 +np.float32,0xfc9d9140,0xbfc90fdb,3 +np.float32,0xbea265d4,0xbe9d4232,3 +np.float32,0xbe9344d0,0xbe8f65da,3 +np.float32,0x3f71f19a,0x3f41d65b,3 +np.float32,0x804a3f59,0x804a3f59,3 +np.float32,0x3e596290,0x3e563476,3 +np.float32,0x3e994ee4,0x3e94f546,3 +np.float32,0xbc103e00,0xbc103d0c,3 +np.float32,0xbf1cd896,0xbf0cb889,3 +np.float32,0x7f52b080,0x3fc90fdb,3 +np.float32,0xff584452,0xbfc90fdb,3 +np.float32,0x58b26b,0x58b26b,3 +np.float32,0x3f23cd4c,0x3f11b799,3 +np.float32,0x707d7,0x707d7,3 +np.float32,0xff732cff,0xbfc90fdb,3 +np.float32,0x3e41c2a6,0x3e3f7f0f,3 +np.float32,0xbf7058e9,0xbf40fdcf,3 +np.float32,0x7dca9857,0x3fc90fdb,3 +np.float32,0x7f0eb44b,0x3fc90fdb,3 +np.float32,0x8000405c,0x8000405c,3 +np.float32,0x4916ab,0x4916ab,3 +np.float32,0x4811a8,0x4811a8,3 +np.float32,0x3d69bf,0x3d69bf,3 +np.float32,0xfeadcf1e,0xbfc90fdb,3 +np.float32,0x3e08dbbf,0x3e080d58,3 +np.float32,0xff031f88,0xbfc90fdb,3 +np.float32,0xbe09cab8,0xbe08f818,3 +np.float32,0x21d7cd,0x21d7cd,3 +np.float32,0x3f23230d,0x3f113ea9,3 +np.float32,0x7e8a48d4,0x3fc90fdb,3 +np.float32,0x413869,0x413869,3 +np.float32,0x7e832990,0x3fc90fdb,3 +np.float32,0x800f5c09,0x800f5c09,3 +np.float32,0x7f5893b6,0x3fc90fdb,3 +np.float32,0x7f06b5b1,0x3fc90fdb,3 +np.float32,0xbe1cbee8,0xbe1b89d6,3 +np.float32,0xbf279f14,0xbf1468a8,3 +np.float32,0xfea86060,0xbfc90fdb,3 +np.float32,0x3e828174,0x3e7f91bb,3 +np.float32,0xff682c82,0xbfc90fdb,3 +np.float32,0x4e20f3,0x4e20f3,3 +np.float32,0x7f17d7e9,0x3fc90fdb,3 +np.float32,0x80671f92,0x80671f92,3 +np.float32,0x7f6dd100,0x3fc90fdb,3 +np.float32,0x3f219a4d,0x3f102695,3 +np.float32,0x803c9808,0x803c9808,3 +np.float32,0x3c432ada,0x3c43287d,3 +np.float32,0xbd3db450,0xbd3d91a2,3 +np.float32,0x3baac135,0x3baac0d0,3 +np.float32,0xff7fffe1,0xbfc90fdb,3 +np.float32,0xfe38a6f4,0xbfc90fdb,3 +np.float32,0x3dfb0a04,0x3df9cb04,3 +np.float32,0x800b05c2,0x800b05c2,3 +np.float32,0x644163,0x644163,3 +np.float32,0xff03a025,0xbfc90fdb,3 +np.float32,0x3f7d506c,0x3f47b641,3 +np.float32,0xff0e682a,0xbfc90fdb,3 +np.float32,0x3e09b7b0,0x3e08e567,3 +np.float32,0x7f72a216,0x3fc90fdb,3 +np.float32,0x7f800000,0x3fc90fdb,3 +np.float32,0x8050a281,0x8050a281,3 +np.float32,0x7edafa2f,0x3fc90fdb,3 +np.float32,0x3f4e0df6,0x3f2d7f2f,3 +np.float32,0xbf6728e0,0xbf3c050f,3 +np.float32,0x3e904ce4,0x3e8ca6eb,3 +np.float32,0x0,0x0,3 +np.float32,0xfd215070,0xbfc90fdb,3 +np.float32,0x7e406b15,0x3fc90fdb,3 +np.float32,0xbf2803c9,0xbf14af18,3 +np.float32,0x5950c8,0x5950c8,3 +np.float32,0xbeddcec8,0xbed14faa,3 +np.float32,0xbec6457e,0xbebd2aa5,3 +np.float32,0xbf42843c,0xbf2656db,3 +np.float32,0x3ee9cba8,0x3edb5163,3 +np.float32,0xbe30c954,0xbe2f0f90,3 +np.float32,0xbeee6b44,0xbedf216f,3 +np.float32,0xbe35d818,0xbe33f7cd,3 +np.float32,0xbe47c630,0xbe454bc6,3 +np.float32,0x801b146f,0x801b146f,3 +np.float32,0x7f6788da,0x3fc90fdb,3 +np.float32,0x3eaef088,0x3ea8927d,3 +np.float32,0x3eb5983e,0x3eae81fc,3 +np.float32,0x40b51d,0x40b51d,3 +np.float32,0xfebddd04,0xbfc90fdb,3 +np.float32,0x3e591aee,0x3e55efea,3 +np.float32,0xbe2b6b48,0xbe29d81f,3 +np.float32,0xff4a8826,0xbfc90fdb,3 +np.float32,0x3e791df0,0x3e745eac,3 +np.float32,0x7c8f681f,0x3fc90fdb,3 +np.float32,0xfe7a15c4,0xbfc90fdb,3 +np.float32,0x3c8963,0x3c8963,3 +np.float32,0x3f0afa0a,0x3efea5cc,3 +np.float32,0xbf0d2680,0xbf00ff29,3 +np.float32,0x3dc306b0,0x3dc27096,3 +np.float32,0x7f4cf105,0x3fc90fdb,3 +np.float32,0xbe196060,0xbe183ea4,3 +np.float32,0x5caf1c,0x5caf1c,3 +np.float32,0x801f2852,0x801f2852,3 +np.float32,0xbe01aa0c,0xbe00fa53,3 +np.float32,0x3f0cfd32,0x3f00df7a,3 +np.float32,0x7d82038e,0x3fc90fdb,3 +np.float32,0x7f7b927f,0x3fc90fdb,3 +np.float32,0xbe93b2e4,0xbe8fcb7f,3 +np.float32,0x1ffe8c,0x1ffe8c,3 +np.float32,0x3faaf6,0x3faaf6,3 +np.float32,0x3e32b1b8,0x3e30e9ab,3 +np.float32,0x802953c0,0x802953c0,3 +np.float32,0xfe5d9844,0xbfc90fdb,3 +np.float32,0x3e1a59d0,0x3e193292,3 +np.float32,0x801c6edc,0x801c6edc,3 +np.float32,0x1ecf41,0x1ecf41,3 +np.float32,0xfe56b09c,0xbfc90fdb,3 +np.float32,0x7e878351,0x3fc90fdb,3 +np.float32,0x3f401e2c,0x3f24cfcb,3 +np.float32,0xbf204a40,0xbf0f35bb,3 +np.float32,0x3e155a98,0x3e144ee1,3 +np.float32,0xbf34f929,0xbf1d8838,3 +np.float32,0x801bbf70,0x801bbf70,3 +np.float32,0x7e7c9730,0x3fc90fdb,3 +np.float32,0x7cc23432,0x3fc90fdb,3 +np.float32,0xbf351638,0xbf1d9b97,3 +np.float32,0x80152094,0x80152094,3 +np.float32,0x3f2d731c,0x3f187219,3 +np.float32,0x804ab0b7,0x804ab0b7,3 +np.float32,0x37d6db,0x37d6db,3 +np.float32,0xbf3ccc56,0xbf22acbf,3 +np.float32,0x3e546f8c,0x3e5176e7,3 +np.float32,0xbe90e87e,0xbe8d3707,3 +np.float32,0x48256c,0x48256c,3 +np.float32,0x7e2468d0,0x3fc90fdb,3 +np.float32,0x807af47e,0x807af47e,3 +np.float32,0x3ed4b221,0x3ec996f0,3 +np.float32,0x3d3b1956,0x3d3af811,3 +np.float32,0xbe69d93c,0xbe65e7f0,3 +np.float32,0xff03ff14,0xbfc90fdb,3 +np.float32,0x801e79dc,0x801e79dc,3 +np.float32,0x3f467c53,0x3f28d63d,3 +np.float32,0x3eab6baa,0x3ea56a1c,3 +np.float32,0xbf15519c,0xbf072d1c,3 +np.float32,0x7f0bd8e8,0x3fc90fdb,3 +np.float32,0xbe1e0d1c,0xbe1cd053,3 +np.float32,0x8016edab,0x8016edab,3 +np.float32,0x7ecaa09b,0x3fc90fdb,3 +np.float32,0x3f72e6d9,0x3f4257a8,3 +np.float32,0xbefe787e,0xbeec29a4,3 +np.float32,0xbee989e8,0xbedb1af9,3 +np.float32,0xbe662db0,0xbe626a45,3 +np.float32,0x495bf7,0x495bf7,3 +np.float32,0x26c379,0x26c379,3 +np.float32,0x7f54d41a,0x3fc90fdb,3 +np.float32,0x801e7dd9,0x801e7dd9,3 +np.float32,0x80000000,0x80000000,3 +np.float32,0xfa3d3000,0xbfc90fdb,3 +np.float32,0xfa3cb800,0xbfc90fdb,3 +np.float32,0x264894,0x264894,3 +np.float32,0xff6de011,0xbfc90fdb,3 +np.float32,0x7e9045b2,0x3fc90fdb,3 +np.float32,0x3f2253a8,0x3f10aaf4,3 +np.float32,0xbd462bf0,0xbd460469,3 +np.float32,0x7f1796af,0x3fc90fdb,3 +np.float32,0x3e718858,0x3e6d3279,3 +np.float32,0xff437d7e,0xbfc90fdb,3 +np.float32,0x805ae7cb,0x805ae7cb,3 +np.float32,0x807e32e9,0x807e32e9,3 +np.float32,0x3ee0bafc,0x3ed3c453,3 +np.float32,0xbf721dee,0xbf41edc3,3 +np.float32,0xfec9f792,0xbfc90fdb,3 +np.float32,0x7f050720,0x3fc90fdb,3 +np.float32,0x182261,0x182261,3 +np.float32,0x3e39e678,0x3e37e5be,3 +np.float32,0x7e096e4b,0x3fc90fdb,3 +np.float32,0x103715,0x103715,3 +np.float32,0x3f7e7741,0x3f484ae4,3 +np.float32,0x3e29aea5,0x3e28277c,3 +np.float32,0x58c183,0x58c183,3 +np.float32,0xff72fdb2,0xbfc90fdb,3 +np.float32,0xbd9a9420,0xbd9a493c,3 +np.float32,0x7f1e07e7,0x3fc90fdb,3 +np.float32,0xff79f522,0xbfc90fdb,3 +np.float32,0x7c7d0e96,0x3fc90fdb,3 +np.float32,0xbeba9e8e,0xbeb2f504,3 +np.float32,0xfd880a80,0xbfc90fdb,3 +np.float32,0xff7f2a33,0xbfc90fdb,3 +np.float32,0x3e861ae0,0x3e83289c,3 +np.float32,0x7f0161c1,0x3fc90fdb,3 +np.float32,0xfe844ff8,0xbfc90fdb,3 +np.float32,0xbebf4b98,0xbeb7128e,3 +np.float32,0x652bee,0x652bee,3 +np.float32,0xff188a4b,0xbfc90fdb,3 +np.float32,0xbf800000,0xbf490fdb,3 +np.float32,0x80418711,0x80418711,3 +np.float32,0xbeb712d4,0xbeafd1f6,3 +np.float32,0xbf7cee28,0xbf478491,3 +np.float32,0xfe66c59c,0xbfc90fdb,3 +np.float32,0x4166a2,0x4166a2,3 +np.float32,0x3dfa1a2c,0x3df8deb5,3 +np.float32,0xbdbfbcb8,0xbdbf2e0f,3 +np.float32,0xfe60ef70,0xbfc90fdb,3 +np.float32,0xfe009444,0xbfc90fdb,3 +np.float32,0xfeb27aa0,0xbfc90fdb,3 +np.float32,0xbe99f7bc,0xbe95902b,3 +np.float32,0x8043d28d,0x8043d28d,3 +np.float32,0xfe5328c4,0xbfc90fdb,3 +np.float32,0x8017b27e,0x8017b27e,3 +np.float32,0x3ef1d2cf,0x3ee1ebd7,3 +np.float32,0x805ddd90,0x805ddd90,3 +np.float32,0xbf424263,0xbf262d17,3 +np.float32,0xfc99dde0,0xbfc90fdb,3 +np.float32,0xbf7ec13b,0xbf487015,3 +np.float32,0xbef727ea,0xbee64377,3 +np.float32,0xff15ce95,0xbfc90fdb,3 +np.float32,0x1fbba4,0x1fbba4,3 +np.float32,0x3f3b2368,0x3f2198a9,3 +np.float32,0xfefda26e,0xbfc90fdb,3 +np.float32,0x801519ad,0x801519ad,3 +np.float32,0x80473fa2,0x80473fa2,3 +np.float32,0x7e7a8bc1,0x3fc90fdb,3 +np.float32,0x3e8a9289,0x3e87548a,3 +np.float32,0x3ed68987,0x3ecb2872,3 +np.float32,0x805bca66,0x805bca66,3 +np.float32,0x8079c4e3,0x8079c4e3,3 +np.float32,0x3a2510,0x3a2510,3 +np.float32,0x7eedc598,0x3fc90fdb,3 +np.float32,0x80681956,0x80681956,3 +np.float32,0xff64c778,0xbfc90fdb,3 +np.float32,0x806bbc46,0x806bbc46,3 +np.float32,0x433643,0x433643,3 +np.float32,0x705b92,0x705b92,3 +np.float32,0xff359392,0xbfc90fdb,3 +np.float32,0xbee78672,0xbed96fa7,3 +np.float32,0x3e21717b,0x3e202010,3 +np.float32,0xfea13c34,0xbfc90fdb,3 +np.float32,0x2c8895,0x2c8895,3 +np.float32,0x3ed33290,0x3ec84f7c,3 +np.float32,0x3e63031e,0x3e5f662e,3 +np.float32,0x7e30907b,0x3fc90fdb,3 +np.float32,0xbe293708,0xbe27b310,3 +np.float32,0x3ed93738,0x3ecd6ea3,3 +np.float32,0x9db7e,0x9db7e,3 +np.float32,0x3f7cd1b8,0x3f47762c,3 +np.float32,0x3eb5143c,0x3eae0cb0,3 +np.float32,0xbe69b234,0xbe65c2d7,3 +np.float32,0x3f6e74de,0x3f3ffb97,3 +np.float32,0x5d0559,0x5d0559,3 +np.float32,0x3e1e8c30,0x3e1d4c70,3 +np.float32,0xbf2d1878,0xbf1833ef,3 +np.float32,0xff2adf82,0xbfc90fdb,3 +np.float32,0x8012e2c1,0x8012e2c1,3 +np.float32,0x7f031be3,0x3fc90fdb,3 +np.float32,0x805ff94e,0x805ff94e,3 +np.float32,0x3e9d5b27,0x3e98aa31,3 +np.float32,0x3f56d5cf,0x3f32bc9e,3 +np.float32,0x3eaa0412,0x3ea4267f,3 +np.float32,0xbe899ea4,0xbe86712f,3 +np.float32,0x800f2f48,0x800f2f48,3 +np.float32,0x3f1c2269,0x3f0c33ea,3 +np.float32,0x3f4a5f64,0x3f2b3f28,3 +np.float32,0x80739318,0x80739318,3 +np.float32,0x806e9b47,0x806e9b47,3 +np.float32,0x3c8cd300,0x3c8ccf73,3 +np.float32,0x7f39a39d,0x3fc90fdb,3 +np.float32,0x3ec95d61,0x3ebfd9dc,3 +np.float32,0xff351ff8,0xbfc90fdb,3 +np.float32,0xff3a8f58,0xbfc90fdb,3 +np.float32,0x7f313ec0,0x3fc90fdb,3 +np.float32,0x803aed13,0x803aed13,3 +np.float32,0x7f771d9b,0x3fc90fdb,3 +np.float32,0x8045a6d6,0x8045a6d6,3 +np.float32,0xbc85f280,0xbc85ef72,3 +np.float32,0x7e9c68f5,0x3fc90fdb,3 +np.float32,0xbf0f9379,0xbf02d975,3 +np.float32,0x7e97bcb1,0x3fc90fdb,3 +np.float32,0x804a07d5,0x804a07d5,3 +np.float32,0x802e6117,0x802e6117,3 +np.float32,0x7ed5e388,0x3fc90fdb,3 +np.float32,0x80750455,0x80750455,3 +np.float32,0xff4a8325,0xbfc90fdb,3 +np.float32,0xbedb6866,0xbecf497c,3 +np.float32,0x52ea3b,0x52ea3b,3 +np.float32,0xff773172,0xbfc90fdb,3 +np.float32,0xbeaa8ff0,0xbea4a46e,3 +np.float32,0x7eef2058,0x3fc90fdb,3 +np.float32,0x3f712472,0x3f4169d3,3 +np.float32,0xff6c8608,0xbfc90fdb,3 +np.float32,0xbf6eaa41,0xbf40182a,3 +np.float32,0x3eb03c24,0x3ea9bb34,3 +np.float32,0xfe118cd4,0xbfc90fdb,3 +np.float32,0x3e5b03b0,0x3e57c378,3 +np.float32,0x7f34d92d,0x3fc90fdb,3 +np.float32,0x806c3418,0x806c3418,3 +np.float32,0x7f3074e3,0x3fc90fdb,3 +np.float32,0x8002df02,0x8002df02,3 +np.float32,0x3f6df63a,0x3f3fb7b7,3 +np.float32,0xfd2b4100,0xbfc90fdb,3 +np.float32,0x80363d5c,0x80363d5c,3 +np.float32,0xbeac1f98,0xbea60bd6,3 +np.float32,0xff7fffff,0xbfc90fdb,3 +np.float32,0x80045097,0x80045097,3 +np.float32,0xfe011100,0xbfc90fdb,3 +np.float32,0x80739ef5,0x80739ef5,3 +np.float32,0xff3976ed,0xbfc90fdb,3 +np.float32,0xbe18e3a0,0xbe17c49e,3 +np.float32,0xbe289294,0xbe2712f6,3 +np.float32,0x3f1d41e7,0x3f0d050e,3 +np.float32,0x39364a,0x39364a,3 +np.float32,0x8072b77e,0x8072b77e,3 +np.float32,0x3f7cfec0,0x3f478cf6,3 +np.float32,0x2f68f6,0x2f68f6,3 +np.float32,0xbf031fb8,0xbef25c84,3 +np.float32,0xbf0b842c,0xbeff7afc,3 +np.float32,0x3f081e7e,0x3efa3676,3 +np.float32,0x7f7fffff,0x3fc90fdb,3 +np.float32,0xff15da0e,0xbfc90fdb,3 +np.float32,0x3d2001b2,0x3d1fece1,3 +np.float32,0x7f76efef,0x3fc90fdb,3 +np.float32,0x3f2405dd,0x3f11dfb7,3 +np.float32,0xa0319,0xa0319,3 +np.float32,0x3e23d2bd,0x3e227255,3 +np.float32,0xbd4d4c50,0xbd4d205e,3 +np.float32,0x382344,0x382344,3 +np.float32,0x21bbf,0x21bbf,3 +np.float32,0xbf209e82,0xbf0f7239,3 +np.float32,0xff03bf9f,0xbfc90fdb,3 +np.float32,0x7b1789,0x7b1789,3 +np.float32,0xff314944,0xbfc90fdb,3 +np.float32,0x1a63eb,0x1a63eb,3 +np.float32,0x803dc983,0x803dc983,3 +np.float32,0x3f0ff558,0x3f0323dc,3 +np.float32,0x3f544f2c,0x3f313f58,3 +np.float32,0xff032948,0xbfc90fdb,3 +np.float32,0x7f4933cc,0x3fc90fdb,3 +np.float32,0x7f14c5ed,0x3fc90fdb,3 +np.float32,0x803aeebf,0x803aeebf,3 +np.float32,0xbf0d4c0f,0xbf011bf5,3 +np.float32,0xbeaf8de2,0xbea91f57,3 +np.float32,0xff3ae030,0xbfc90fdb,3 +np.float32,0xbb362d00,0xbb362ce1,3 +np.float32,0x3d1f79e0,0x3d1f6544,3 +np.float32,0x3f56e9d9,0x3f32c860,3 +np.float32,0x3f723e5e,0x3f41fee2,3 +np.float32,0x4c0179,0x4c0179,3 +np.float32,0xfee36132,0xbfc90fdb,3 +np.float32,0x619ae6,0x619ae6,3 +np.float32,0xfde5d670,0xbfc90fdb,3 +np.float32,0xff079ac5,0xbfc90fdb,3 +np.float32,0x3e974fbd,0x3e931fae,3 +np.float32,0x8020ae6b,0x8020ae6b,3 +np.float32,0x6b5af1,0x6b5af1,3 +np.float32,0xbeb57cd6,0xbeae69a3,3 +np.float32,0x806e7eb2,0x806e7eb2,3 +np.float32,0x7e666edb,0x3fc90fdb,3 +np.float32,0xbf458c18,0xbf283ff0,3 +np.float32,0x3e50518e,0x3e4d8399,3 +np.float32,0x3e9ce224,0x3e983b98,3 +np.float32,0x3e6bc067,0x3e67b6c6,3 +np.float32,0x13783d,0x13783d,3 +np.float32,0xff3d518c,0xbfc90fdb,3 +np.float32,0xfeba5968,0xbfc90fdb,3 +np.float32,0xbf0b9f76,0xbeffa50f,3 +np.float32,0xfe174900,0xbfc90fdb,3 +np.float32,0x3f38bb0a,0x3f200527,3 +np.float32,0x7e94a77d,0x3fc90fdb,3 +np.float32,0x29d776,0x29d776,3 +np.float32,0xbf4e058d,0xbf2d7a15,3 +np.float32,0xbd94abc8,0xbd946923,3 +np.float32,0xbee62db0,0xbed85124,3 +np.float32,0x800000,0x800000,3 +np.float32,0xbef1df7e,0xbee1f636,3 +np.float32,0xbcf3cd20,0xbcf3bab5,3 +np.float32,0x80007b05,0x80007b05,3 +np.float32,0x3d9b3f2e,0x3d9af351,3 +np.float32,0xbf714a68,0xbf417dee,3 +np.float32,0xbf2a2d37,0xbf163069,3 +np.float32,0x8055104f,0x8055104f,3 +np.float32,0x7f5c40d7,0x3fc90fdb,3 +np.float32,0x1,0x1,3 +np.float32,0xff35f3a6,0xbfc90fdb,3 +np.float32,0xd9c7c,0xd9c7c,3 +np.float32,0xbf440cfc,0xbf274f22,3 +np.float32,0x8050ac43,0x8050ac43,3 +np.float32,0x63ee16,0x63ee16,3 +np.float32,0x7d90419b,0x3fc90fdb,3 +np.float32,0xfee22198,0xbfc90fdb,3 +np.float32,0xc2ead,0xc2ead,3 +np.float32,0x7f5cd6a6,0x3fc90fdb,3 +np.float32,0x3f6fab7e,0x3f40a184,3 +np.float32,0x3ecf998c,0x3ec53a73,3 +np.float32,0x7e5271f0,0x3fc90fdb,3 +np.float32,0x67c016,0x67c016,3 +np.float32,0x2189c8,0x2189c8,3 +np.float32,0x27d892,0x27d892,3 +np.float32,0x3f0d02c4,0x3f00e3c0,3 +np.float32,0xbf69ebca,0xbf3d8862,3 +np.float32,0x3e60c0d6,0x3e5d3ebb,3 +np.float32,0x3f45206c,0x3f27fc66,3 +np.float32,0xbf6b47dc,0xbf3e4592,3 +np.float32,0xfe9be2e2,0xbfc90fdb,3 +np.float32,0x7fa00000,0x7fe00000,3 +np.float32,0xff271562,0xbfc90fdb,3 +np.float32,0x3e2e5270,0x3e2caaaf,3 +np.float32,0x80222934,0x80222934,3 +np.float32,0xbd01d220,0xbd01c701,3 +np.float32,0x223aa0,0x223aa0,3 +np.float32,0x3f4b5a7e,0x3f2bd967,3 +np.float32,0x3f217d85,0x3f101200,3 +np.float32,0xbf57663a,0xbf331144,3 +np.float32,0x3f219862,0x3f102536,3 +np.float32,0x28a28c,0x28a28c,3 +np.float32,0xbf3f55f4,0xbf244f86,3 +np.float32,0xbf3de287,0xbf236092,3 +np.float32,0xbf1c1ce2,0xbf0c2fe3,3 +np.float32,0x80000001,0x80000001,3 +np.float32,0x3db695d0,0x3db61a90,3 +np.float32,0x6c39bf,0x6c39bf,3 +np.float32,0x7e33a12f,0x3fc90fdb,3 +np.float32,0x67623a,0x67623a,3 +np.float32,0x3e45dc54,0x3e4373b6,3 +np.float32,0x7f62fa68,0x3fc90fdb,3 +np.float32,0x3f0e1d01,0x3f01bbe5,3 +np.float32,0x3f13dc69,0x3f0615f5,3 +np.float32,0x246703,0x246703,3 +np.float32,0xbf1055b5,0xbf036d07,3 +np.float32,0x7f46d3d0,0x3fc90fdb,3 +np.float32,0x3d2b8086,0x3d2b66e5,3 +np.float32,0xbf03be44,0xbef35776,3 +np.float32,0x3f800000,0x3f490fdb,3 +np.float32,0xbec8d226,0xbebf613d,3 +np.float32,0x3d8faf00,0x3d8f72d4,3 +np.float32,0x170c4e,0x170c4e,3 +np.float32,0xff14c0f0,0xbfc90fdb,3 +np.float32,0xff16245d,0xbfc90fdb,3 +np.float32,0x7f44ce6d,0x3fc90fdb,3 +np.float32,0xbe8175d8,0xbe7d9aeb,3 +np.float32,0x3df7a4a1,0x3df67254,3 +np.float32,0xfe2cc46c,0xbfc90fdb,3 +np.float32,0x3f284e63,0x3f14e335,3 +np.float32,0x7e46e5d6,0x3fc90fdb,3 +np.float32,0x397be4,0x397be4,3 +np.float32,0xbf2560bc,0xbf12d50b,3 +np.float32,0x3ed9b8c1,0x3ecddc60,3 +np.float32,0xfec18c5a,0xbfc90fdb,3 +np.float32,0x64894d,0x64894d,3 +np.float32,0x36a65d,0x36a65d,3 +np.float32,0x804ffcd7,0x804ffcd7,3 +np.float32,0x800f79e4,0x800f79e4,3 +np.float32,0x5d45ac,0x5d45ac,3 +np.float32,0x6cdda0,0x6cdda0,3 +np.float32,0xbf7f2077,0xbf489fe5,3 +np.float32,0xbf152f78,0xbf0713a1,3 +np.float32,0x807bf344,0x807bf344,3 +np.float32,0x3f775023,0x3f44a4d8,3 +np.float32,0xbf3edf67,0xbf240365,3 +np.float32,0x7eed729c,0x3fc90fdb,3 +np.float32,0x14cc29,0x14cc29,3 +np.float32,0x7edd7b6b,0x3fc90fdb,3 +np.float32,0xbf3c6e2c,0xbf226fb7,3 +np.float32,0x51b9ad,0x51b9ad,3 +np.float32,0x3f617ee8,0x3f38dd7c,3 +np.float32,0xff800000,0xbfc90fdb,3 +np.float32,0x7f440ea0,0x3fc90fdb,3 +np.float32,0x3e639893,0x3e5ff49e,3 +np.float32,0xbd791bb0,0xbd78cd3c,3 +np.float32,0x8059fcbc,0x8059fcbc,3 +np.float32,0xbf7d1214,0xbf4796bd,3 +np.float32,0x3ef368fa,0x3ee33788,3 +np.float32,0xbecec0f4,0xbec48055,3 +np.float32,0xbc83d940,0xbc83d656,3 +np.float32,0xbce01220,0xbce003d4,3 +np.float32,0x803192a5,0x803192a5,3 +np.float32,0xbe40e0c0,0xbe3ea4f0,3 +np.float32,0xfb692600,0xbfc90fdb,3 +np.float32,0x3f1bec65,0x3f0c0c88,3 +np.float32,0x7f042798,0x3fc90fdb,3 +np.float32,0xbe047374,0xbe03b83b,3 +np.float32,0x7f7c6630,0x3fc90fdb,3 +np.float32,0x7f58dae3,0x3fc90fdb,3 +np.float32,0x80691c92,0x80691c92,3 +np.float32,0x7dbe76,0x7dbe76,3 +np.float32,0xbf231384,0xbf11339d,3 +np.float32,0xbef4acf8,0xbee43f8b,3 +np.float32,0x3ee9f9d0,0x3edb7793,3 +np.float32,0x3f0064f6,0x3eee04a8,3 +np.float32,0x313732,0x313732,3 +np.float32,0xfd58cf80,0xbfc90fdb,3 +np.float32,0x3f7a2bc9,0x3f461d30,3 +np.float32,0x7f7681af,0x3fc90fdb,3 +np.float32,0x7f504211,0x3fc90fdb,3 +np.float32,0xfeae0c00,0xbfc90fdb,3 +np.float32,0xbee14396,0xbed436d1,3 +np.float32,0x7fc00000,0x7fc00000,3 +np.float32,0x693406,0x693406,3 +np.float32,0x3eb4a679,0x3eadab1b,3 +np.float32,0x550505,0x550505,3 +np.float32,0xfd493d10,0xbfc90fdb,3 +np.float32,0x3f4fc907,0x3f2e8b2c,3 +np.float32,0x80799aa4,0x80799aa4,3 +np.float32,0xff1ea89b,0xbfc90fdb,3 +np.float32,0xff424510,0xbfc90fdb,3 +np.float32,0x7f68d026,0x3fc90fdb,3 +np.float32,0xbea230ca,0xbe9d1200,3 +np.float32,0x7ea585da,0x3fc90fdb,3 +np.float32,0x3f3db211,0x3f23414c,3 +np.float32,0xfea4d964,0xbfc90fdb,3 +np.float32,0xbf17fe18,0xbf092984,3 +np.float32,0x7cc8a2,0x7cc8a2,3 +np.float32,0xff0330ba,0xbfc90fdb,3 +np.float32,0x3f769835,0x3f444592,3 +np.float32,0xeb0ac,0xeb0ac,3 +np.float32,0x7f7e45de,0x3fc90fdb,3 +np.float32,0xbdb510a8,0xbdb49873,3 +np.float32,0x3ebf900b,0x3eb74e9c,3 +np.float32,0xbf21bbce,0xbf103e89,3 +np.float32,0xbf3f4682,0xbf24459d,3 +np.float32,0x7eb6e9c8,0x3fc90fdb,3 +np.float32,0xbf42532d,0xbf2637be,3 +np.float32,0xbd3b2600,0xbd3b04b4,3 +np.float32,0x3f1fa9aa,0x3f0ec23e,3 +np.float32,0x7ed6a0f1,0x3fc90fdb,3 +np.float32,0xff4759a1,0xbfc90fdb,3 +np.float32,0x6d26e3,0x6d26e3,3 +np.float32,0xfe1108e0,0xbfc90fdb,3 +np.float32,0xfdf76900,0xbfc90fdb,3 +np.float32,0xfec66f22,0xbfc90fdb,3 +np.float32,0xbf3d097f,0xbf22d458,3 +np.float32,0x3d85be25,0x3d858d99,3 +np.float32,0x7f36739f,0x3fc90fdb,3 +np.float32,0x7bc0a304,0x3fc90fdb,3 +np.float32,0xff48dd90,0xbfc90fdb,3 +np.float32,0x48cab0,0x48cab0,3 +np.float32,0x3ed3943c,0x3ec8a2ef,3 +np.float32,0xbf61488e,0xbf38bede,3 +np.float32,0x3f543df5,0x3f313525,3 +np.float32,0x5cf2ca,0x5cf2ca,3 +np.float32,0x572686,0x572686,3 +np.float32,0x80369c7c,0x80369c7c,3 +np.float32,0xbd2c1d20,0xbd2c0338,3 +np.float32,0x3e255428,0x3e23ea0b,3 +np.float32,0xbeba9ee0,0xbeb2f54c,3 +np.float32,0x8015c165,0x8015c165,3 +np.float32,0x3d31f488,0x3d31d7e6,3 +np.float32,0x3f68591c,0x3f3cac43,3 +np.float32,0xf5ed5,0xf5ed5,3 +np.float32,0xbf3b1d34,0xbf21949e,3 +np.float32,0x1f0343,0x1f0343,3 +np.float32,0x3f0e52b5,0x3f01e4ef,3 +np.float32,0x7f57c596,0x3fc90fdb,3 +np.float64,0x7fd8e333ddb1c667,0x3ff921fb54442d18,1 +np.float64,0x800bcc9cdad7993a,0x800bcc9cdad7993a,1 +np.float64,0x3fcd6f81df3adf00,0x3fcceebbafc5d55e,1 +np.float64,0x3fed7338a57ae671,0x3fe7ce3e5811fc0a,1 +np.float64,0x7fe64994fcac9329,0x3ff921fb54442d18,1 +np.float64,0xfa5a6345f4b4d,0xfa5a6345f4b4d,1 +np.float64,0xe9dcd865d3b9b,0xe9dcd865d3b9b,1 +np.float64,0x7fea6cffabf4d9fe,0x3ff921fb54442d18,1 +np.float64,0xa9e1de6153c3c,0xa9e1de6153c3c,1 +np.float64,0xab6bdc5356d7c,0xab6bdc5356d7c,1 +np.float64,0x80062864a02c50ca,0x80062864a02c50ca,1 +np.float64,0xbfdac03aa7b58076,0xbfd9569f3230128d,1 +np.float64,0xbfe61b77752c36ef,0xbfe3588f51b8be8f,1 +np.float64,0x800bc854c8d790aa,0x800bc854c8d790aa,1 +np.float64,0x3feed1a2da3da346,0x3fe887f9b8ea031f,1 +np.float64,0x3fe910d3697221a7,0x3fe54365a53d840e,1 +np.float64,0x7fe7ab4944ef5692,0x3ff921fb54442d18,1 +np.float64,0x3fa462f1a028c5e3,0x3fa460303a6a4e69,1 +np.float64,0x800794f1a3af29e4,0x800794f1a3af29e4,1 +np.float64,0x3fee6fe7fafcdfd0,0x3fe854f863816d55,1 +np.float64,0x8000000000000000,0x8000000000000000,1 +np.float64,0x7f336472fe66d,0x7f336472fe66d,1 +np.float64,0xffb1623ac822c478,0xbff921fb54442d18,1 +np.float64,0x3fbacd68ce359ad2,0x3fbab480b3638846,1 +np.float64,0xffd5c02706ab804e,0xbff921fb54442d18,1 +np.float64,0xbfd4daf03d29b5e0,0xbfd42928f069c062,1 +np.float64,0x800c6e85dbd8dd0c,0x800c6e85dbd8dd0c,1 +np.float64,0x800e3599c5bc6b34,0x800e3599c5bc6b34,1 +np.float64,0x2c0d654c581ad,0x2c0d654c581ad,1 +np.float64,0xbfdd3eb13fba7d62,0xbfdb6e8143302de7,1 +np.float64,0x800b60cb8776c197,0x800b60cb8776c197,1 +np.float64,0x80089819ad113034,0x80089819ad113034,1 +np.float64,0x29fe721453fcf,0x29fe721453fcf,1 +np.float64,0x3fe8722f4df0e45f,0x3fe4e026d9eadb4d,1 +np.float64,0xffd1fbcd01a3f79a,0xbff921fb54442d18,1 +np.float64,0x7fc74e1e982e9c3c,0x3ff921fb54442d18,1 +np.float64,0x800c09d3d15813a8,0x800c09d3d15813a8,1 +np.float64,0xbfeee4578b3dc8af,0xbfe891ab3d6c3ce4,1 +np.float64,0xffdd01a6f33a034e,0xbff921fb54442d18,1 +np.float64,0x7fcc130480382608,0x3ff921fb54442d18,1 +np.float64,0xffcbb6bd1d376d7c,0xbff921fb54442d18,1 +np.float64,0xc068a53780d15,0xc068a53780d15,1 +np.float64,0xbfc974f15532e9e4,0xbfc92100b355f3e7,1 +np.float64,0x3fe6da79442db4f3,0x3fe3d87393b082e7,1 +np.float64,0xd9d9be4db3b38,0xd9d9be4db3b38,1 +np.float64,0x5ea50a20bd4a2,0x5ea50a20bd4a2,1 +np.float64,0xbfe5597f7d2ab2ff,0xbfe2d3ccc544b52b,1 +np.float64,0x80019364e4e326cb,0x80019364e4e326cb,1 +np.float64,0x3fed2902c3fa5206,0x3fe7a5e1df07e5c1,1 +np.float64,0xbfa7b72b5c2f6e50,0xbfa7b2d545b3cc1f,1 +np.float64,0xffdb60dd43b6c1ba,0xbff921fb54442d18,1 +np.float64,0x81a65d8b034cc,0x81a65d8b034cc,1 +np.float64,0x8000c30385818608,0x8000c30385818608,1 +np.float64,0x6022f5f4c045f,0x6022f5f4c045f,1 +np.float64,0x8007a2bb810f4578,0x8007a2bb810f4578,1 +np.float64,0x7fdc68893238d111,0x3ff921fb54442d18,1 +np.float64,0x7fd443454ea8868a,0x3ff921fb54442d18,1 +np.float64,0xffe6b04209ed6084,0xbff921fb54442d18,1 +np.float64,0x7fcd9733d13b2e67,0x3ff921fb54442d18,1 +np.float64,0xf5ee80a9ebdd0,0xf5ee80a9ebdd0,1 +np.float64,0x3fe3788e8de6f11e,0x3fe17dec7e6843a0,1 +np.float64,0x3fee36f62f7c6dec,0x3fe836f832515b43,1 +np.float64,0xf6cb49aded969,0xf6cb49aded969,1 +np.float64,0x3fd2b15ea4a562bc,0x3fd22fdc09920e67,1 +np.float64,0x7fccf6aef139ed5d,0x3ff921fb54442d18,1 +np.float64,0x3fd396b8ce272d72,0x3fd3026118857bd4,1 +np.float64,0x7fe53d3c80ea7a78,0x3ff921fb54442d18,1 +np.float64,0x3feae88fc4f5d120,0x3fe65fb04b18ef7a,1 +np.float64,0x3fedc643747b8c86,0x3fe7fafa6c20e25a,1 +np.float64,0xffdb2dc0df365b82,0xbff921fb54442d18,1 +np.float64,0xbfa2af3658255e70,0xbfa2ad17348f4253,1 +np.float64,0x3f8aa77b30354ef6,0x3f8aa71892336a69,1 +np.float64,0xbfdd1b1efbba363e,0xbfdb510dcd186820,1 +np.float64,0x800f50d99c5ea1b3,0x800f50d99c5ea1b3,1 +np.float64,0xff6ed602403dac00,0xbff921fb54442d18,1 +np.float64,0x800477d71aa8efaf,0x800477d71aa8efaf,1 +np.float64,0xbfe729a9e86e5354,0xbfe40ca78d9eefcf,1 +np.float64,0x3fd81ab2d4303566,0x3fd70d7e3937ea22,1 +np.float64,0xb617cbab6c2fa,0xb617cbab6c2fa,1 +np.float64,0x7fefffffffffffff,0x3ff921fb54442d18,1 +np.float64,0xffa40933ac281260,0xbff921fb54442d18,1 +np.float64,0xbfe1ede621e3dbcc,0xbfe057bb2b341ced,1 +np.float64,0xbfec700f03b8e01e,0xbfe73fb190bc722e,1 +np.float64,0x6e28af02dc517,0x6e28af02dc517,1 +np.float64,0x3fe37ad37ae6f5a7,0x3fe17f94674818a9,1 +np.float64,0x8000cbdeeae197bf,0x8000cbdeeae197bf,1 +np.float64,0x3fe8fd1f01f1fa3e,0x3fe5372bbec5d72c,1 +np.float64,0x3f8f9229103f2452,0x3f8f918531894256,1 +np.float64,0x800536858e0a6d0c,0x800536858e0a6d0c,1 +np.float64,0x7fe82bb4f9f05769,0x3ff921fb54442d18,1 +np.float64,0xffc1c2fb592385f8,0xbff921fb54442d18,1 +np.float64,0x7f924ddfc0249bbf,0x3ff921fb54442d18,1 +np.float64,0xffd5e125c52bc24c,0xbff921fb54442d18,1 +np.float64,0xbfef0d8738be1b0e,0xbfe8a6ef17b16c10,1 +np.float64,0x3fc9c8875233910f,0x3fc9715e708503cb,1 +np.float64,0xbfe2d926f4e5b24e,0xbfe108956e61cbb3,1 +np.float64,0x7fd61c496dac3892,0x3ff921fb54442d18,1 +np.float64,0x7fed545c6b7aa8b8,0x3ff921fb54442d18,1 +np.float64,0x8003746fea86e8e1,0x8003746fea86e8e1,1 +np.float64,0x3fdf515e75bea2bd,0x3fdd201a5585caa3,1 +np.float64,0xffda87c8ee350f92,0xbff921fb54442d18,1 +np.float64,0xffc675d8e22cebb0,0xbff921fb54442d18,1 +np.float64,0xffcdc173433b82e8,0xbff921fb54442d18,1 +np.float64,0xffed9df1517b3be2,0xbff921fb54442d18,1 +np.float64,0x3fd6a2eec72d45de,0x3fd5c1f1d7dcddcf,1 +np.float64,0xffec116a66f822d4,0xbff921fb54442d18,1 +np.float64,0x8007c2a2458f8545,0x8007c2a2458f8545,1 +np.float64,0x3fe4ee80d969dd02,0x3fe2895076094668,1 +np.float64,0x3fe3cae7116795ce,0x3fe1b9c07e0d03a7,1 +np.float64,0xbfd81bf8d8b037f2,0xbfd70e9bbbb4ca57,1 +np.float64,0x800c88ccd1f9119a,0x800c88ccd1f9119a,1 +np.float64,0xffdab2aee2b5655e,0xbff921fb54442d18,1 +np.float64,0x3fe743d227ee87a4,0x3fe41dcaef186d96,1 +np.float64,0x3fb060fd0220c1fa,0x3fb05b47f56ebbb4,1 +np.float64,0xbfd3f03772a7e06e,0xbfd3541522377291,1 +np.float64,0x190a5ae03216,0x190a5ae03216,1 +np.float64,0x3fe48c71916918e4,0x3fe24442f45b3183,1 +np.float64,0x800862470590c48e,0x800862470590c48e,1 +np.float64,0x7fd3ced89d279db0,0x3ff921fb54442d18,1 +np.float64,0x3feb3d9b4ab67b37,0x3fe69140cf2623f7,1 +np.float64,0xbc3f296b787e5,0xbc3f296b787e5,1 +np.float64,0xbfed6b905dfad721,0xbfe7ca1881a8c0fd,1 +np.float64,0xbfe621c2aaac4386,0xbfe35cd1969a82db,1 +np.float64,0x8009e7b17593cf63,0x8009e7b17593cf63,1 +np.float64,0x80045f580ca8beb1,0x80045f580ca8beb1,1 +np.float64,0xbfea2e177e745c2f,0xbfe5f13971633339,1 +np.float64,0x3fee655787fccab0,0x3fe84f6b98b6de26,1 +np.float64,0x3fc9cde92f339bd0,0x3fc9768a88b2c97c,1 +np.float64,0x3fc819c3b3303388,0x3fc7d25e1526e731,1 +np.float64,0x3fd3e848d2a7d090,0x3fd34cd9e6af558f,1 +np.float64,0x3fe19dacac633b5a,0x3fe01a6b4d27adc2,1 +np.float64,0x800b190da316321c,0x800b190da316321c,1 +np.float64,0xd5c69711ab8d3,0xd5c69711ab8d3,1 +np.float64,0xbfdc31bed7b8637e,0xbfda8ea3c1309d6d,1 +np.float64,0xbfd02ba007a05740,0xbfcfad86f0d756dc,1 +np.float64,0x3fe874473d70e88e,0x3fe4e1793cd82123,1 +np.float64,0xffb465585c28cab0,0xbff921fb54442d18,1 +np.float64,0xbfb5d8e13e2bb1c0,0xbfb5cb5c7807fc4d,1 +np.float64,0xffe80f933bf01f26,0xbff921fb54442d18,1 +np.float64,0x7feea783f5fd4f07,0x3ff921fb54442d18,1 +np.float64,0xbfae6665f43cccd0,0xbfae5d45b0a6f90a,1 +np.float64,0x800bd6ef5a77addf,0x800bd6ef5a77addf,1 +np.float64,0x800d145babda28b8,0x800d145babda28b8,1 +np.float64,0x39de155473bc3,0x39de155473bc3,1 +np.float64,0x3fefbd6bb1ff7ad8,0x3fe9008e73a3296e,1 +np.float64,0x3fc40bca3d281798,0x3fc3e2710e167007,1 +np.float64,0x3fcae0918335c120,0x3fca7e09e704a678,1 +np.float64,0x51287fbea2511,0x51287fbea2511,1 +np.float64,0x7fa6bc33a82d7866,0x3ff921fb54442d18,1 +np.float64,0xe72a2bebce546,0xe72a2bebce546,1 +np.float64,0x3fe1c8fd686391fa,0x3fe03b9622aeb4e3,1 +np.float64,0x3fe2a73ac3654e76,0x3fe0e36bc1ee4ac4,1 +np.float64,0x59895218b312b,0x59895218b312b,1 +np.float64,0xc6dc25c78db85,0xc6dc25c78db85,1 +np.float64,0xbfc06cfac520d9f4,0xbfc0561f85d2c907,1 +np.float64,0xbfea912dc4f5225c,0xbfe62c3b1c01c793,1 +np.float64,0x3fb78ce89a2f19d0,0x3fb77bfcb65a67d3,1 +np.float64,0xbfece5cdea39cb9c,0xbfe78103d24099e5,1 +np.float64,0x30d3054e61a61,0x30d3054e61a61,1 +np.float64,0xbfd3fe26fba7fc4e,0xbfd360c8447c4f7a,1 +np.float64,0x800956072a92ac0f,0x800956072a92ac0f,1 +np.float64,0x7fe639b3b6ec7366,0x3ff921fb54442d18,1 +np.float64,0x800ee30240bdc605,0x800ee30240bdc605,1 +np.float64,0x7fef6af0d2bed5e1,0x3ff921fb54442d18,1 +np.float64,0xffefce8725ff9d0d,0xbff921fb54442d18,1 +np.float64,0x3fe2e311da65c624,0x3fe10ff1623089dc,1 +np.float64,0xbfe7e5cbe56fcb98,0xbfe486c3daeda67c,1 +np.float64,0x80095bc14472b783,0x80095bc14472b783,1 +np.float64,0xffef0cb4553e1968,0xbff921fb54442d18,1 +np.float64,0xe3e60567c7cc1,0xe3e60567c7cc1,1 +np.float64,0xffde919f06bd233e,0xbff921fb54442d18,1 +np.float64,0x3fe3f9632e27f2c6,0x3fe1db49ebd21c4e,1 +np.float64,0x9dee9a233bdd4,0x9dee9a233bdd4,1 +np.float64,0xbfe3bb0602e7760c,0xbfe1ae41b6d4c488,1 +np.float64,0x3fc46945a128d288,0x3fc43da54c6c6a2a,1 +np.float64,0x7fdef149ac3de292,0x3ff921fb54442d18,1 +np.float64,0x800a96c76d752d8f,0x800a96c76d752d8f,1 +np.float64,0x3f971a32382e3464,0x3f9719316b9e9baf,1 +np.float64,0x7fe97bcf15b2f79d,0x3ff921fb54442d18,1 +np.float64,0x7fea894558f5128a,0x3ff921fb54442d18,1 +np.float64,0x3fc9e3be1933c780,0x3fc98b847c3923eb,1 +np.float64,0x3f7accac40359959,0x3f7acc9330741b64,1 +np.float64,0xa80c136950183,0xa80c136950183,1 +np.float64,0x3fe408732b2810e6,0x3fe1e61e7cbc8824,1 +np.float64,0xffa775bc042eeb80,0xbff921fb54442d18,1 +np.float64,0x3fbf04bd223e0980,0x3fbede37b8fc697e,1 +np.float64,0x7fd999b34c333366,0x3ff921fb54442d18,1 +np.float64,0xe72146dfce429,0xe72146dfce429,1 +np.float64,0x4f511ee49ea24,0x4f511ee49ea24,1 +np.float64,0xffb3e6e58827cdc8,0xbff921fb54442d18,1 +np.float64,0x3fd1f180cfa3e300,0x3fd17e85b2871de2,1 +np.float64,0x97c8e45b2f91d,0x97c8e45b2f91d,1 +np.float64,0xbfeeb20e88fd641d,0xbfe8778f878440bf,1 +np.float64,0xbfe1fc6dee23f8dc,0xbfe062c815a93cde,1 +np.float64,0xab4bf71f5697f,0xab4bf71f5697f,1 +np.float64,0xa9675a2952cec,0xa9675a2952cec,1 +np.float64,0xbfef3ea4a33e7d49,0xbfe8c02743ebc1b6,1 +np.float64,0x3fe22a2eafa4545d,0x3fe08577afca52a9,1 +np.float64,0x3fe8a08daaf1411c,0x3fe4fd5a34f05305,1 +np.float64,0xbfc6cda77b2d9b50,0xbfc6910bcfa0cf4f,1 +np.float64,0x3fec398394387307,0x3fe7211dd5276500,1 +np.float64,0x3fe36c95c626d92c,0x3fe1752e5aa2357b,1 +np.float64,0xffd8b9e7073173ce,0xbff921fb54442d18,1 +np.float64,0xffe19f043ae33e08,0xbff921fb54442d18,1 +np.float64,0x800e3640709c6c81,0x800e3640709c6c81,1 +np.float64,0x3fe7d6c20aafad84,0x3fe47d1a3307d9c8,1 +np.float64,0x80093fd63b727fad,0x80093fd63b727fad,1 +np.float64,0xffe1a671a4634ce3,0xbff921fb54442d18,1 +np.float64,0xbfe53a6b386a74d6,0xbfe2be41859cb10d,1 +np.float64,0xbfed149a097a2934,0xbfe79ab7e3e93c1c,1 +np.float64,0x7fc2769a5724ed34,0x3ff921fb54442d18,1 +np.float64,0xffd01e4e99a03c9e,0xbff921fb54442d18,1 +np.float64,0xa61f38434c3e7,0xa61f38434c3e7,1 +np.float64,0x800ad4ac5195a959,0x800ad4ac5195a959,1 +np.float64,0x7ff8000000000000,0x7ff8000000000000,1 +np.float64,0x80034a45b6c6948c,0x80034a45b6c6948c,1 +np.float64,0x6350b218c6a17,0x6350b218c6a17,1 +np.float64,0xfff0000000000000,0xbff921fb54442d18,1 +np.float64,0x3fe363e759e6c7cf,0x3fe16ed58d80f9ce,1 +np.float64,0xffe3b98e59e7731c,0xbff921fb54442d18,1 +np.float64,0x3fdbf7b40337ef68,0x3fda5df7ad3c80f9,1 +np.float64,0xbfe9cdf784739bef,0xbfe5b74f346ef93d,1 +np.float64,0xbfc321bea326437c,0xbfc2fdc0d4ff7561,1 +np.float64,0xbfe40f77d2a81ef0,0xbfe1eb28c4ae4dde,1 +np.float64,0x7fe071806960e300,0x3ff921fb54442d18,1 +np.float64,0x7fd269006ea4d200,0x3ff921fb54442d18,1 +np.float64,0x80017a56e0e2f4af,0x80017a56e0e2f4af,1 +np.float64,0x8004b4ea09a969d5,0x8004b4ea09a969d5,1 +np.float64,0xbfedbb01e63b7604,0xbfe7f4f0e84297df,1 +np.float64,0x3fe44454826888a9,0x3fe210ff6d005706,1 +np.float64,0xbfe0e77e6ea1cefd,0xbfdf1a977da33402,1 +np.float64,0xbfed6d4c8c3ada99,0xbfe7cb0932093f60,1 +np.float64,0x1d74cb9e3ae9a,0x1d74cb9e3ae9a,1 +np.float64,0x80082a785d1054f1,0x80082a785d1054f1,1 +np.float64,0x3fe58393266b0726,0x3fe2f0d8e91d4887,1 +np.float64,0xffe4028899680510,0xbff921fb54442d18,1 +np.float64,0x783a2e5af0746,0x783a2e5af0746,1 +np.float64,0x7fcdce88e73b9d11,0x3ff921fb54442d18,1 +np.float64,0x3fc58672a72b0ce5,0x3fc5535e090e56e2,1 +np.float64,0x800889c839b11391,0x800889c839b11391,1 +np.float64,0xffe5e05c466bc0b8,0xbff921fb54442d18,1 +np.float64,0xbfcbef6ebe37dedc,0xbfcb810752468f49,1 +np.float64,0xffe9408563b2810a,0xbff921fb54442d18,1 +np.float64,0xbfee4738367c8e70,0xbfe83f8e5dd7602f,1 +np.float64,0xbfe4aeb587295d6b,0xbfe25c7a0c76a454,1 +np.float64,0xffc9aea0a7335d40,0xbff921fb54442d18,1 +np.float64,0xe1e02199c3c04,0xe1e02199c3c04,1 +np.float64,0xbfbd9400783b2800,0xbfbd729345d1d14f,1 +np.float64,0x7a5418bcf4a84,0x7a5418bcf4a84,1 +np.float64,0x3fdc1c2fa5b83860,0x3fda7c935965ae72,1 +np.float64,0x80076a9f58ced53f,0x80076a9f58ced53f,1 +np.float64,0x3fedc4bf957b897f,0x3fe7fa2a83148f1c,1 +np.float64,0x800981b8a9d30372,0x800981b8a9d30372,1 +np.float64,0xffe1082311621046,0xbff921fb54442d18,1 +np.float64,0xe0091f89c0124,0xe0091f89c0124,1 +np.float64,0xbfce8d674f3d1ad0,0xbfcdfdbf2ddaa0ca,1 +np.float64,0x800516e72eaa2dcf,0x800516e72eaa2dcf,1 +np.float64,0xffe61ee64c6c3dcc,0xbff921fb54442d18,1 +np.float64,0x7fed2683cafa4d07,0x3ff921fb54442d18,1 +np.float64,0xffd4faf27729f5e4,0xbff921fb54442d18,1 +np.float64,0x7fe308fa842611f4,0x3ff921fb54442d18,1 +np.float64,0x3fc612a62b2c2550,0x3fc5db9ddbd4e159,1 +np.float64,0xbfe5b01e766b603d,0xbfe30f72a875e988,1 +np.float64,0x3fc2dd8b9a25bb17,0x3fc2bb06246b9f78,1 +np.float64,0x8170908102e12,0x8170908102e12,1 +np.float64,0x800c1c8a8a583915,0x800c1c8a8a583915,1 +np.float64,0xffe5d91e8b6bb23c,0xbff921fb54442d18,1 +np.float64,0xffd140adee22815c,0xbff921fb54442d18,1 +np.float64,0xbfe2f1f5f8e5e3ec,0xbfe11afa5d749952,1 +np.float64,0xbfed6d1d587ada3b,0xbfe7caef9ecf7651,1 +np.float64,0x3fe9b85e67f370bd,0x3fe5aa3474768982,1 +np.float64,0x7fdc8932edb91265,0x3ff921fb54442d18,1 +np.float64,0x7fd136bc54a26d78,0x3ff921fb54442d18,1 +np.float64,0x800a1ea12a343d43,0x800a1ea12a343d43,1 +np.float64,0x3fec6a5c1b78d4b8,0x3fe73c82235c3f8f,1 +np.float64,0x800fbf6a00df7ed4,0x800fbf6a00df7ed4,1 +np.float64,0xbfd0e6e0cda1cdc2,0xbfd0864bf8cad294,1 +np.float64,0x3fc716df482e2dbf,0x3fc6d7fbfd4a8470,1 +np.float64,0xbfe75990936eb321,0xbfe42bffec3fa0d7,1 +np.float64,0x3fd58e54a02b1ca9,0x3fd4cace1107a5cc,1 +np.float64,0xbfc9c04136338084,0xbfc9696ad2591d54,1 +np.float64,0xdd1f0147ba3e0,0xdd1f0147ba3e0,1 +np.float64,0x5c86a940b90e,0x5c86a940b90e,1 +np.float64,0xbfecae3b8e795c77,0xbfe7624d4988c612,1 +np.float64,0xffd0370595206e0c,0xbff921fb54442d18,1 +np.float64,0xbfdc26d443384da8,0xbfda857ecd33ba9f,1 +np.float64,0xbfd1c849d9a39094,0xbfd15849449cc378,1 +np.float64,0xffee04acdb3c0959,0xbff921fb54442d18,1 +np.float64,0xbfded1056dbda20a,0xbfdcb83b30e1528c,1 +np.float64,0x7fb7b826622f704c,0x3ff921fb54442d18,1 +np.float64,0xbfee4df8ae7c9bf1,0xbfe8431df9dfd05d,1 +np.float64,0x7fe7f3670e2fe6cd,0x3ff921fb54442d18,1 +np.float64,0x8008ac9ae0d15936,0x8008ac9ae0d15936,1 +np.float64,0x800dce9f3b3b9d3f,0x800dce9f3b3b9d3f,1 +np.float64,0x7fbb19db203633b5,0x3ff921fb54442d18,1 +np.float64,0x3fe56c7f302ad8fe,0x3fe2e0eec3ad45fd,1 +np.float64,0x7fe82c05c570580b,0x3ff921fb54442d18,1 +np.float64,0xc0552b7780aa6,0xc0552b7780aa6,1 +np.float64,0x39d40e3073a83,0x39d40e3073a83,1 +np.float64,0x3fd8db54d731b6aa,0x3fd7b589b3ee9b20,1 +np.float64,0xffcdd355233ba6ac,0xbff921fb54442d18,1 +np.float64,0x3fbe97b3a43d2f67,0x3fbe72bca9be0348,1 +np.float64,0xbff0000000000000,0xbfe921fb54442d18,1 +np.float64,0xbfb4f55e6229eac0,0xbfb4e96df18a75a7,1 +np.float64,0xbfc66399ba2cc734,0xbfc62a3298bd96fc,1 +np.float64,0x3fd00988bb201311,0x3fcf6d67a9374c38,1 +np.float64,0x7fe471867d28e30c,0x3ff921fb54442d18,1 +np.float64,0xbfe38e0e64271c1d,0xbfe18d9888b7523b,1 +np.float64,0x8009dc127573b825,0x8009dc127573b825,1 +np.float64,0x800047bde4608f7d,0x800047bde4608f7d,1 +np.float64,0xffeede42c77dbc85,0xbff921fb54442d18,1 +np.float64,0xd8cf6d13b19ee,0xd8cf6d13b19ee,1 +np.float64,0xbfd08fb302a11f66,0xbfd034b1f8235e23,1 +np.float64,0x7fdb404c0b368097,0x3ff921fb54442d18,1 +np.float64,0xbfd6ba0438ad7408,0xbfd5d673e3276ec1,1 +np.float64,0xffd9568027b2ad00,0xbff921fb54442d18,1 +np.float64,0xbfb313b73e262770,0xbfb30ab4acb4fa67,1 +np.float64,0xbfe2dc1a15e5b834,0xbfe10ac5f8f3acd3,1 +np.float64,0xbfee426bf4bc84d8,0xbfe83d061df91edd,1 +np.float64,0xd9142c2fb2286,0xd9142c2fb2286,1 +np.float64,0x7feb0d11dff61a23,0x3ff921fb54442d18,1 +np.float64,0x800fea5b509fd4b7,0x800fea5b509fd4b7,1 +np.float64,0x3fe1a8818da35103,0x3fe022ba1bdf366e,1 +np.float64,0x8010000000000000,0x8010000000000000,1 +np.float64,0xbfd8fc6de6b1f8dc,0xbfd7d24726ed8dcc,1 +np.float64,0xf4b3dc2de967c,0xf4b3dc2de967c,1 +np.float64,0x8af0409b15e08,0x8af0409b15e08,1 +np.float64,0x3fb21e6934243cd2,0x3fb216b065f8709a,1 +np.float64,0x3fc53069392a60d2,0x3fc4ffa931211fb9,1 +np.float64,0xffc955812c32ab04,0xbff921fb54442d18,1 +np.float64,0xbfe3de42b1a7bc86,0xbfe1c7bd1324de75,1 +np.float64,0x1dc149a03b82a,0x1dc149a03b82a,1 +np.float64,0x8001bc5a24a378b5,0x8001bc5a24a378b5,1 +np.float64,0x3da14c407b44,0x3da14c407b44,1 +np.float64,0x80025e8da924bd1c,0x80025e8da924bd1c,1 +np.float64,0xbfcb0141c9360284,0xbfca9d572ea5e1f3,1 +np.float64,0xc90036fd92007,0xc90036fd92007,1 +np.float64,0x138312c427063,0x138312c427063,1 +np.float64,0x800dda3a963bb475,0x800dda3a963bb475,1 +np.float64,0x3fe9339934f26732,0x3fe558e723291f78,1 +np.float64,0xbfea8357027506ae,0xbfe6240826faaf48,1 +np.float64,0x7fe04735cae08e6b,0x3ff921fb54442d18,1 +np.float64,0x3fe29aca3c653594,0x3fe0da214c8bc6a4,1 +np.float64,0x3fbe1f09a03c3e13,0x3fbdfbbefef0155b,1 +np.float64,0x816ee4ad02ddd,0x816ee4ad02ddd,1 +np.float64,0xffddd1b31d3ba366,0xbff921fb54442d18,1 +np.float64,0x3fe2e01e0625c03c,0x3fe10dc0bd6677c2,1 +np.float64,0x3fec6bcf1978d79e,0x3fe73d518cddeb7c,1 +np.float64,0x7fe01aaaf8603555,0x3ff921fb54442d18,1 +np.float64,0xdf300cc5be602,0xdf300cc5be602,1 +np.float64,0xbfe71c01a36e3804,0xbfe403af80ce47b8,1 +np.float64,0xffa5be00ac2b7c00,0xbff921fb54442d18,1 +np.float64,0xbfda9ba711b5374e,0xbfd93775e3ac6bda,1 +np.float64,0xbfe56d8a27eadb14,0xbfe2e1a7185e8e6d,1 +np.float64,0x800f1bc937be3792,0x800f1bc937be3792,1 +np.float64,0x800a61d93c74c3b3,0x800a61d93c74c3b3,1 +np.float64,0x7fe71a52fcae34a5,0x3ff921fb54442d18,1 +np.float64,0x7fb4aef256295de4,0x3ff921fb54442d18,1 +np.float64,0x3fe6c1e861ed83d1,0x3fe3c828f281a7ef,1 +np.float64,0x3fba128402342508,0x3fb9fb94cf141860,1 +np.float64,0x3fee55a7ecfcab50,0x3fe8472a9af893ee,1 +np.float64,0x3fe586f31b2b0de6,0x3fe2f32bce9e91bc,1 +np.float64,0xbfbb1d1442363a28,0xbfbb034c7729d5f2,1 +np.float64,0xc78b4d3f8f16a,0xc78b4d3f8f16a,1 +np.float64,0x7fdbc277d4b784ef,0x3ff921fb54442d18,1 +np.float64,0xbfa728ca2c2e5190,0xbfa724c04e73ccbd,1 +np.float64,0x7fefc7b2143f8f63,0x3ff921fb54442d18,1 +np.float64,0x3fd153a3dda2a748,0x3fd0ebccd33a4dca,1 +np.float64,0xbfe18a6eace314de,0xbfe00ba32ec89d30,1 +np.float64,0x7feef518537dea30,0x3ff921fb54442d18,1 +np.float64,0x8005f007cd4be010,0x8005f007cd4be010,1 +np.float64,0x7fd890b840b12170,0x3ff921fb54442d18,1 +np.float64,0x7feed0582ebda0af,0x3ff921fb54442d18,1 +np.float64,0x1013f53220280,0x1013f53220280,1 +np.float64,0xbfe77273986ee4e7,0xbfe43c375a8bf6de,1 +np.float64,0x7fe3ab8918675711,0x3ff921fb54442d18,1 +np.float64,0xbfc6ad515b2d5aa4,0xbfc671b2f7f86624,1 +np.float64,0x7fcd86231d3b0c45,0x3ff921fb54442d18,1 +np.float64,0xffe2523299a4a464,0xbff921fb54442d18,1 +np.float64,0x7fcadc5a1b35b8b3,0x3ff921fb54442d18,1 +np.float64,0x3fe5e020c4ebc042,0x3fe330418eec75bd,1 +np.float64,0x7fe332a9dc266553,0x3ff921fb54442d18,1 +np.float64,0xfa11dc21f425,0xfa11dc21f425,1 +np.float64,0xbec800177d900,0xbec800177d900,1 +np.float64,0x3fcadd057835ba0b,0x3fca7aa42face8bc,1 +np.float64,0xbfe6b9a206ad7344,0xbfe3c2a9719803de,1 +np.float64,0x3fbb4250b63684a0,0x3fbb281e9cefc519,1 +np.float64,0x7fef8787517f0f0e,0x3ff921fb54442d18,1 +np.float64,0x8001315c2d6262b9,0x8001315c2d6262b9,1 +np.float64,0xbfd94e3cf2b29c7a,0xbfd819257d36f56c,1 +np.float64,0xf1f325abe3e65,0xf1f325abe3e65,1 +np.float64,0x7fd6c07079ad80e0,0x3ff921fb54442d18,1 +np.float64,0x7fe328b075a65160,0x3ff921fb54442d18,1 +np.float64,0x7fe7998f812f331e,0x3ff921fb54442d18,1 +np.float64,0xffe026bb65604d76,0xbff921fb54442d18,1 +np.float64,0xffd6c06de8ad80dc,0xbff921fb54442d18,1 +np.float64,0x3fcd5a37bf3ab46f,0x3fccda82935d98ce,1 +np.float64,0xffc3e5a45227cb48,0xbff921fb54442d18,1 +np.float64,0x3febf7dd8177efbc,0x3fe6fc0bb999883e,1 +np.float64,0x7fd7047ea92e08fc,0x3ff921fb54442d18,1 +np.float64,0x35b3fc406b680,0x35b3fc406b680,1 +np.float64,0x7fd52e97632a5d2e,0x3ff921fb54442d18,1 +np.float64,0x3fd464d401a8c9a8,0x3fd3be2967fc97c3,1 +np.float64,0x800e815b2ebd02b6,0x800e815b2ebd02b6,1 +np.float64,0x3fca8428af350850,0x3fca257b466b8970,1 +np.float64,0x8007b7526f6f6ea6,0x8007b7526f6f6ea6,1 +np.float64,0x82f60a8f05ec2,0x82f60a8f05ec2,1 +np.float64,0x3fb71a5d0a2e34c0,0x3fb70a629ef8e2a2,1 +np.float64,0x7fc8570c7d30ae18,0x3ff921fb54442d18,1 +np.float64,0x7fe5528e77eaa51c,0x3ff921fb54442d18,1 +np.float64,0xffc20dbbf1241b78,0xbff921fb54442d18,1 +np.float64,0xeb13368fd6267,0xeb13368fd6267,1 +np.float64,0x7fe7d529056faa51,0x3ff921fb54442d18,1 +np.float64,0x3fecd02eabf9a05d,0x3fe77516f0ba1ac4,1 +np.float64,0x800fcba6a09f974d,0x800fcba6a09f974d,1 +np.float64,0x7fe7e8e015afd1bf,0x3ff921fb54442d18,1 +np.float64,0xbfd271a382a4e348,0xbfd1f513a191c595,1 +np.float64,0x9f1014013e21,0x9f1014013e21,1 +np.float64,0x3fc05da47f20bb49,0x3fc04708a13a3a47,1 +np.float64,0x3fe0f427dda1e850,0x3fdf2e60ba8678b9,1 +np.float64,0xbfecb29fa539653f,0xbfe764bc791c45dd,1 +np.float64,0x45881ec68b104,0x45881ec68b104,1 +np.float64,0x8000000000000001,0x8000000000000001,1 +np.float64,0x3fe9c67ee1338cfe,0x3fe5b2c7b3df6ce8,1 +np.float64,0x7fedb8fef6bb71fd,0x3ff921fb54442d18,1 +np.float64,0x3fe54f6aaaea9ed6,0x3fe2ccd1df2abaa9,1 +np.float64,0x7feff58a1bbfeb13,0x3ff921fb54442d18,1 +np.float64,0x7fe3b62827276c4f,0x3ff921fb54442d18,1 +np.float64,0x3fe5feb682ebfd6d,0x3fe345105bc6d980,1 +np.float64,0x3fe49f38d9693e72,0x3fe2518b2824757f,1 +np.float64,0x8006bfd27c6d7fa6,0x8006bfd27c6d7fa6,1 +np.float64,0x3fc13409e2226814,0x3fc119ce0c01a5a2,1 +np.float64,0x95f8c7212bf19,0x95f8c7212bf19,1 +np.float64,0x3fd9f0fa6133e1f5,0x3fd8a567515edecf,1 +np.float64,0x3fef95cbe5ff2b98,0x3fe8ec88c768ba0b,1 +np.float64,0x3fbed28bba3da510,0x3fbeacbf136e51c2,1 +np.float64,0xbfd3987aeca730f6,0xbfd303fca58e3e60,1 +np.float64,0xbfed0f90cbfa1f22,0xbfe797f59249410d,1 +np.float64,0xffe55d8cbf2abb19,0xbff921fb54442d18,1 +np.float64,0x3feb4d9fc6769b40,0x3fe69a88131a1f1f,1 +np.float64,0x80085569acd0aad4,0x80085569acd0aad4,1 +np.float64,0x20557a6e40ab0,0x20557a6e40ab0,1 +np.float64,0x3fead2fd5df5a5fb,0x3fe653091f33b27f,1 +np.float64,0x3fe7b9983eaf7330,0x3fe46a50c4b5235e,1 +np.float64,0xffdad237ffb5a470,0xbff921fb54442d18,1 +np.float64,0xbfe5cc39a4eb9874,0xbfe322ad3a903f93,1 +np.float64,0x800ad6eecb35adde,0x800ad6eecb35adde,1 +np.float64,0xffec620f6438c41e,0xbff921fb54442d18,1 +np.float64,0xbfe5ef29122bde52,0xbfe33a7dfcc255e2,1 +np.float64,0x3fd451e7d0a8a3d0,0x3fd3acfa4939af10,1 +np.float64,0x8003ea93c127d528,0x8003ea93c127d528,1 +np.float64,0x800b48d37c9691a7,0x800b48d37c9691a7,1 +np.float64,0x3fe7e202acafc405,0x3fe484558246069b,1 +np.float64,0x80070c9b686e1938,0x80070c9b686e1938,1 +np.float64,0xbfda90bbc6352178,0xbfd92e25fcd12288,1 +np.float64,0x800e1ffebb1c3ffe,0x800e1ffebb1c3ffe,1 +np.float64,0x3ff0000000000000,0x3fe921fb54442d18,1 +np.float64,0xffd8cfdd46319fba,0xbff921fb54442d18,1 +np.float64,0x7fd8cd4182319a82,0x3ff921fb54442d18,1 +np.float64,0x3fed8bb778bb176f,0x3fe7db7c77c4c694,1 +np.float64,0x3fc74a70302e94e0,0x3fc709e95d6defec,1 +np.float64,0x3fe87269d070e4d4,0x3fe4e04bcc4a2137,1 +np.float64,0x7fb48223f6290447,0x3ff921fb54442d18,1 +np.float64,0xffe8ec444b71d888,0xbff921fb54442d18,1 +np.float64,0x7fde17d280bc2fa4,0x3ff921fb54442d18,1 +np.float64,0x3fd1cbde01a397bc,0x3fd15b9bb7b3147b,1 +np.float64,0x800883a64451074d,0x800883a64451074d,1 +np.float64,0x7fe3160a3f262c13,0x3ff921fb54442d18,1 +np.float64,0xbfe051d4d9a0a3aa,0xbfde2ecf14dc75fb,1 +np.float64,0xbfd89de689b13bce,0xbfd780176d1a28a3,1 +np.float64,0x3fecde2bf779bc58,0x3fe77ccf10bdd8e2,1 +np.float64,0xffe75774dc6eaee9,0xbff921fb54442d18,1 +np.float64,0x7fe834414d706882,0x3ff921fb54442d18,1 +np.float64,0x1,0x1,1 +np.float64,0xbfea5e4e4a74bc9c,0xbfe60e0601711835,1 +np.float64,0xffec248d4cb8491a,0xbff921fb54442d18,1 +np.float64,0xffd9942c2c332858,0xbff921fb54442d18,1 +np.float64,0xa9db36a553b67,0xa9db36a553b67,1 +np.float64,0x7fec630718b8c60d,0x3ff921fb54442d18,1 +np.float64,0xbfd062188f20c432,0xbfd009ecd652be89,1 +np.float64,0x8001b84e3023709d,0x8001b84e3023709d,1 +np.float64,0xbfe9e26d7cb3c4db,0xbfe5c3b157ecf668,1 +np.float64,0xbfef66ddf33ecdbc,0xbfe8d4b1f6410a24,1 +np.float64,0x3fd8d7109431ae21,0x3fd7b1d4860719a2,1 +np.float64,0xffee0f53107c1ea5,0xbff921fb54442d18,1 +np.float64,0x80000b4fd60016a0,0x80000b4fd60016a0,1 +np.float64,0xbfd99ff6e5333fee,0xbfd85fb3cbdaa049,1 +np.float64,0xbfe9cfd268339fa5,0xbfe5b86ef021a1b1,1 +np.float64,0xe32eace1c65d6,0xe32eace1c65d6,1 +np.float64,0xffc81f6627303ecc,0xbff921fb54442d18,1 +np.float64,0x7fe98dadde331b5b,0x3ff921fb54442d18,1 +np.float64,0xbfbcebd11e39d7a0,0xbfbccc8ec47883c7,1 +np.float64,0x7fe164880f22c90f,0x3ff921fb54442d18,1 +np.float64,0x800467c0cae8cf82,0x800467c0cae8cf82,1 +np.float64,0x800071e4b140e3ca,0x800071e4b140e3ca,1 +np.float64,0xbfc87a7eae30f4fc,0xbfc82fbc55bb0f24,1 +np.float64,0xffb2e0e23225c1c8,0xbff921fb54442d18,1 +np.float64,0x20ef338041df,0x20ef338041df,1 +np.float64,0x7fe6de71ca6dbce3,0x3ff921fb54442d18,1 +np.float64,0x5d1fa026ba3f5,0x5d1fa026ba3f5,1 +np.float64,0xffd112a9ce222554,0xbff921fb54442d18,1 +np.float64,0x3fb351f66626a3ed,0x3fb3489ab578c452,1 +np.float64,0x7fef7b2bd3bef657,0x3ff921fb54442d18,1 +np.float64,0xffe144f5d4e289eb,0xbff921fb54442d18,1 +np.float64,0xffd63a6750ac74ce,0xbff921fb54442d18,1 +np.float64,0x7fd2d8bb25a5b175,0x3ff921fb54442d18,1 +np.float64,0x3fec5920a078b242,0x3fe732dcffcf6521,1 +np.float64,0x80009a8b7f813518,0x80009a8b7f813518,1 +np.float64,0x3fdea220893d4441,0x3fdc921edf6bf3d8,1 +np.float64,0x8006cee2208d9dc5,0x8006cee2208d9dc5,1 +np.float64,0xdd0b0081ba17,0xdd0b0081ba17,1 +np.float64,0x7ff4000000000000,0x7ffc000000000000,1 +np.float64,0xbfdac33955358672,0xbfd9592bce7daf1f,1 +np.float64,0x7fe8301d7170603a,0x3ff921fb54442d18,1 +np.float64,0xbfc1d34d8523a69c,0xbfc1b62449af9684,1 +np.float64,0x800c62239458c447,0x800c62239458c447,1 +np.float64,0xffd398c009a73180,0xbff921fb54442d18,1 +np.float64,0xbfe0c6d9ee218db4,0xbfdee777557f4401,1 +np.float64,0x3feccdd373799ba7,0x3fe773c9c2263f89,1 +np.float64,0xbfd21898bda43132,0xbfd1a2be8545fcc5,1 +np.float64,0x3fd77019b62ee033,0x3fd67793cabdf267,1 +np.float64,0x7fa609cad42c1395,0x3ff921fb54442d18,1 +np.float64,0x7fb4eaea5a29d5d4,0x3ff921fb54442d18,1 +np.float64,0x3fc570dc9a2ae1b9,0x3fc53e5f6218a799,1 +np.float64,0x800344ae8466895e,0x800344ae8466895e,1 +np.float64,0xbfc7c985252f930c,0xbfc784d60fa27bac,1 +np.float64,0xffaa2929fc345250,0xbff921fb54442d18,1 +np.float64,0xffe63e5ee9ac7cbe,0xbff921fb54442d18,1 +np.float64,0x73f0280ce7e06,0x73f0280ce7e06,1 +np.float64,0xffc525f8822a4bf0,0xbff921fb54442d18,1 +np.float64,0x7fd744d00aae899f,0x3ff921fb54442d18,1 +np.float64,0xbfe0fe590761fcb2,0xbfdf3e493e8b1f32,1 +np.float64,0xfae04ae7f5c0a,0xfae04ae7f5c0a,1 +np.float64,0xef821939df043,0xef821939df043,1 +np.float64,0x7fef6135843ec26a,0x3ff921fb54442d18,1 +np.float64,0xbfebf34dcbf7e69c,0xbfe6f97588a8f911,1 +np.float64,0xbfeec0b498fd8169,0xbfe87f2eceeead12,1 +np.float64,0x7fb67161b42ce2c2,0x3ff921fb54442d18,1 +np.float64,0x3fdcfd998639fb33,0x3fdb38934927c096,1 +np.float64,0xffda5960bc34b2c2,0xbff921fb54442d18,1 +np.float64,0xbfe11f8c71223f19,0xbfdf71fe770c96ab,1 +np.float64,0x3fe4ac1bab695838,0x3fe25aa4517b8322,1 +np.float64,0x3f730458a02608b1,0x3f73044fabb5e999,1 +np.float64,0x3fdb14ffcdb62a00,0x3fd99ea6c241a3ed,1 +np.float64,0xbfc93208cd326410,0xbfc8e09d78b6d4db,1 +np.float64,0x19e734dc33ce8,0x19e734dc33ce8,1 +np.float64,0x3fe5e98428abd308,0x3fe336a6a085eb55,1 +np.float64,0x7fec672a1378ce53,0x3ff921fb54442d18,1 +np.float64,0x800f8bd8d4ff17b2,0x800f8bd8d4ff17b2,1 +np.float64,0xbfe5a12e4e6b425c,0xbfe30533f99d5d06,1 +np.float64,0x75a34cb0eb46a,0x75a34cb0eb46a,1 +np.float64,0x7fe1d21d16a3a439,0x3ff921fb54442d18,1 +np.float64,0x7ff0000000000000,0x3ff921fb54442d18,1 +np.float64,0xffe0f50db261ea1b,0xbff921fb54442d18,1 +np.float64,0xbfd9dc22feb3b846,0xbfd8937ec965a501,1 +np.float64,0x8009d68e48d3ad1d,0x8009d68e48d3ad1d,1 +np.float64,0xbfe2eba620e5d74c,0xbfe1164d7d273c60,1 +np.float64,0x992efa09325e0,0x992efa09325e0,1 +np.float64,0x3fdab640ea356c82,0x3fd94e20cab88db2,1 +np.float64,0x69a6f04ad34df,0x69a6f04ad34df,1 +np.float64,0x3fe397df25272fbe,0x3fe194bd1a3a6192,1 +np.float64,0xebcce9fdd799d,0xebcce9fdd799d,1 +np.float64,0x3fbb49490c369292,0x3fbb2f02eccc497d,1 +np.float64,0xffd871f980b0e3f4,0xbff921fb54442d18,1 +np.float64,0x800348f6966691ee,0x800348f6966691ee,1 +np.float64,0xbfebc270a7f784e1,0xbfe6dda8d0d80f26,1 +np.float64,0xffd6d559b1adaab4,0xbff921fb54442d18,1 +np.float64,0x3fec3635c0b86c6c,0x3fe71f420256e43e,1 +np.float64,0x7fbc82ad7039055a,0x3ff921fb54442d18,1 +np.float64,0x7f873050602e60a0,0x3ff921fb54442d18,1 +np.float64,0x3fca44b8c3348970,0x3fc9e8a1a1a2d96e,1 +np.float64,0x3fe0fc308fe1f861,0x3fdf3aeb469ea225,1 +np.float64,0x7fefc27de8bf84fb,0x3ff921fb54442d18,1 +np.float64,0x8005f3f3916be7e8,0x8005f3f3916be7e8,1 +np.float64,0xbfd4278c7c284f18,0xbfd38678988873b6,1 +np.float64,0x435eafc486bd7,0x435eafc486bd7,1 +np.float64,0xbfd01f5199203ea4,0xbfcf96631f2108a3,1 +np.float64,0xffd5ee9185abdd24,0xbff921fb54442d18,1 +np.float64,0xffedb363257b66c5,0xbff921fb54442d18,1 +np.float64,0x800d68e6e11ad1ce,0x800d68e6e11ad1ce,1 +np.float64,0xbfcf687f8e3ed100,0xbfceccb771b0d39a,1 +np.float64,0x7feb3b9ef2f6773d,0x3ff921fb54442d18,1 +np.float64,0x3fe15ec5ca62bd8c,0x3fdfd3fab9d96f81,1 +np.float64,0x10000000000000,0x10000000000000,1 +np.float64,0xd2386f81a470e,0xd2386f81a470e,1 +np.float64,0xb9feed4573fde,0xb9feed4573fde,1 +np.float64,0x3fe7ed25c9efda4c,0x3fe48b7b72db4014,1 +np.float64,0xbfe01478726028f1,0xbfddcd1f5a2efc59,1 +np.float64,0x9946d02f328da,0x9946d02f328da,1 +np.float64,0xbfe3bb67f06776d0,0xbfe1ae88aa81c5a6,1 +np.float64,0xbfd3fd8a4c27fb14,0xbfd3603982e3b78d,1 +np.float64,0xffd5c3ab912b8758,0xbff921fb54442d18,1 +np.float64,0xffd5f502b12bea06,0xbff921fb54442d18,1 +np.float64,0xbfc64981ec2c9304,0xbfc610e0382b1fa6,1 +np.float64,0xffec42e3413885c6,0xbff921fb54442d18,1 +np.float64,0x80084eb4ed109d6a,0x80084eb4ed109d6a,1 +np.float64,0xbfd17cac9fa2f95a,0xbfd112020588a4b3,1 +np.float64,0xbfd06c1359a0d826,0xbfd0134a28aa9a66,1 +np.float64,0x7fdc3d7c03b87af7,0x3ff921fb54442d18,1 +np.float64,0x7bdf5aaaf7bec,0x7bdf5aaaf7bec,1 +np.float64,0xbfee3cd966fc79b3,0xbfe83a14bc07ac3b,1 +np.float64,0x7fec910da3f9221a,0x3ff921fb54442d18,1 +np.float64,0xffb4ea667029d4d0,0xbff921fb54442d18,1 +np.float64,0x800103d7cce207b0,0x800103d7cce207b0,1 +np.float64,0x7fbb229a6c364534,0x3ff921fb54442d18,1 +np.float64,0x0,0x0,1 +np.float64,0xffd8fccd0331f99a,0xbff921fb54442d18,1 +np.float64,0xbfd0784ae1a0f096,0xbfd01ebff62e39ad,1 +np.float64,0xbfed2ec9b3ba5d93,0xbfe7a9099410bc76,1 +np.float64,0x800690b8d16d2172,0x800690b8d16d2172,1 +np.float64,0x7fc061b26520c364,0x3ff921fb54442d18,1 +np.float64,0x8007ec47054fd88f,0x8007ec47054fd88f,1 +np.float64,0x775546b6eeaa9,0x775546b6eeaa9,1 +np.float64,0x8005e00fb56bc020,0x8005e00fb56bc020,1 +np.float64,0xbfe510f8d0ea21f2,0xbfe2a16862b5a37f,1 +np.float64,0xffd87a6bf3b0f4d8,0xbff921fb54442d18,1 +np.float64,0x800906e3d0520dc8,0x800906e3d0520dc8,1 +np.float64,0x2296f000452f,0x2296f000452f,1 +np.float64,0xbfe3189fa2e63140,0xbfe1378c0e005be4,1 +np.float64,0xb4d2447f69a49,0xb4d2447f69a49,1 +np.float64,0xffd056a24a20ad44,0xbff921fb54442d18,1 +np.float64,0xbfe3b23fe4e76480,0xbfe1a7e5840fcbeb,1 +np.float64,0x80018ee270831dc6,0x80018ee270831dc6,1 +np.float64,0x800df89f245bf13e,0x800df89f245bf13e,1 +np.float64,0x3fee1409d7bc2814,0x3fe824779d133232,1 +np.float64,0xbfef8d81667f1b03,0xbfe8e85523620368,1 +np.float64,0xffd8a6519b314ca4,0xbff921fb54442d18,1 +np.float64,0x7fc7bc86f32f790d,0x3ff921fb54442d18,1 +np.float64,0xffea6159e674c2b3,0xbff921fb54442d18,1 +np.float64,0x3fe153c3fba2a788,0x3fdfc2f74769d300,1 +np.float64,0xffc4261ef3284c3c,0xbff921fb54442d18,1 +np.float64,0x7fe8a8961ff1512b,0x3ff921fb54442d18,1 +np.float64,0xbfe3fb1fd167f640,0xbfe1dc89dcb7ecdf,1 +np.float64,0x3fd88577c2b10af0,0x3fd76acc09660704,1 +np.float64,0x3fe128ec27e251d8,0x3fdf808fc7ebcd8f,1 +np.float64,0xbfed6ca7c4fad950,0xbfe7caafe9a3e213,1 +np.float64,0xbf9a3912b8347220,0xbf9a379b3349352e,1 +np.float64,0xbfd724d7bcae49b0,0xbfd6351efa2a5fc5,1 +np.float64,0xbfed59700a7ab2e0,0xbfe7c043014c694c,1 +np.float64,0x8002ad435bc55a87,0x8002ad435bc55a87,1 +np.float64,0xffe46ed345a8dda6,0xbff921fb54442d18,1 +np.float64,0x7fd2f1d1d825e3a3,0x3ff921fb54442d18,1 +np.float64,0xbfea0265e23404cc,0xbfe5d6fb3fd30464,1 +np.float64,0xbfd17e049122fc0a,0xbfd113421078bbae,1 +np.float64,0xffea03b986b40772,0xbff921fb54442d18,1 +np.float64,0x800b55331a16aa67,0x800b55331a16aa67,1 +np.float64,0xbfc6fcafbf2df960,0xbfc6be9ecd0ebc1f,1 +np.float64,0xd6a36017ad46c,0xd6a36017ad46c,1 +np.float64,0xbfe9ba86dfb3750e,0xbfe5ab840cb0ef86,1 +np.float64,0x75c4a108eb895,0x75c4a108eb895,1 +np.float64,0x8008d6bc8051ad79,0x8008d6bc8051ad79,1 +np.float64,0xbfd3dc5984a7b8b4,0xbfd341f78e0528ec,1 +np.float64,0xffe1cbb01aa39760,0xbff921fb54442d18,1 +np.float64,0x3fc7e292f52fc526,0x3fc79d0ce9365767,1 +np.float64,0xbfcbeae2bd37d5c4,0xbfcb7cb034f82467,1 +np.float64,0x8000f0c62e21e18d,0x8000f0c62e21e18d,1 +np.float64,0xbfe23d8bc6247b18,0xbfe09418ee35c3c7,1 +np.float64,0x717394bae2e73,0x717394bae2e73,1 +np.float64,0xffa2ef1cc425de40,0xbff921fb54442d18,1 +np.float64,0x3fd938c229b27184,0x3fd806900735c99d,1 +np.float64,0x800bf3ec8a77e7d9,0x800bf3ec8a77e7d9,1 +np.float64,0xffeef41dd57de83b,0xbff921fb54442d18,1 +np.float64,0x8008df97e5b1bf30,0x8008df97e5b1bf30,1 +np.float64,0xffe9ab9d0db35739,0xbff921fb54442d18,1 +np.float64,0x99ff391333fe7,0x99ff391333fe7,1 +np.float64,0x3fb864b4a630c969,0x3fb851e883ea2cf9,1 +np.float64,0x22c1230a45825,0x22c1230a45825,1 +np.float64,0xff2336fbfe467,0xff2336fbfe467,1 +np.float64,0xbfd488f4cea911ea,0xbfd3def0490f5414,1 +np.float64,0x3fa379c78426f38f,0x3fa377607370800b,1 +np.float64,0xbfb0873302210e68,0xbfb08155b78dfd53,1 +np.float64,0xbfdf9ff7c2bf3ff0,0xbfdd5f658e357ad2,1 +np.float64,0x800978719192f0e4,0x800978719192f0e4,1 +np.float64,0xbfba8759ea350eb0,0xbfba6f325013b9e5,1 +np.float64,0xbfdd3e6b06ba7cd6,0xbfdb6e472b6091b0,1 +np.float64,0x7fe0c334a7a18668,0x3ff921fb54442d18,1 +np.float64,0xbfeb971feb772e40,0xbfe6c4e0f61404d1,1 +np.float64,0x3fe2a50968e54a13,0x3fe0e1c8b8d96e85,1 +np.float64,0x800fa9c5515f538b,0x800fa9c5515f538b,1 +np.float64,0x800f8532fbbf0a66,0x800f8532fbbf0a66,1 +np.float64,0x167d6f1e2cfaf,0x167d6f1e2cfaf,1 +np.float64,0xffee88e769fd11ce,0xbff921fb54442d18,1 +np.float64,0xbfeecc8529fd990a,0xbfe885520cdad8ea,1 +np.float64,0xffefffffffffffff,0xbff921fb54442d18,1 +np.float64,0xbfef6a566afed4ad,0xbfe8d6767b4c4235,1 +np.float64,0xffec12415af82482,0xbff921fb54442d18,1 +np.float64,0x3678a20a6cf15,0x3678a20a6cf15,1 +np.float64,0xffe468d54ee8d1aa,0xbff921fb54442d18,1 +np.float64,0x800ad6006795ac01,0x800ad6006795ac01,1 +np.float64,0x8001d5b61063ab6d,0x8001d5b61063ab6d,1 +np.float64,0x800dfcd1863bf9a3,0x800dfcd1863bf9a3,1 +np.float64,0xc9fbff6f93f80,0xc9fbff6f93f80,1 +np.float64,0xffe55c20f9eab842,0xbff921fb54442d18,1 +np.float64,0xbfcb596b6536b2d8,0xbfcaf1b339c5c615,1 +np.float64,0xbfe092689ea124d1,0xbfde94fa58946e51,1 +np.float64,0x3fe9ec733af3d8e6,0x3fe5c9bf5dee2623,1 +np.float64,0x3fe30f3d83261e7b,0x3fe1309fd6620e03,1 +np.float64,0xffd31d7f84263b00,0xbff921fb54442d18,1 +np.float64,0xbfe88d2d3e711a5a,0xbfe4f12b5a136178,1 +np.float64,0xffc81e4ce1303c98,0xbff921fb54442d18,1 +np.float64,0xffe5b96ebfab72dd,0xbff921fb54442d18,1 +np.float64,0x512f0502a25e1,0x512f0502a25e1,1 +np.float64,0x7fa3a376982746ec,0x3ff921fb54442d18,1 +np.float64,0x80005b5f2f60b6bf,0x80005b5f2f60b6bf,1 +np.float64,0xc337cc69866fa,0xc337cc69866fa,1 +np.float64,0x3fe7719c4caee339,0x3fe43bab42b19e64,1 +np.float64,0x7fde7ec1d93cfd83,0x3ff921fb54442d18,1 +np.float64,0x3fd2f38f3825e71e,0x3fd26cc7b1dd0acb,1 +np.float64,0x7fce298b993c5316,0x3ff921fb54442d18,1 +np.float64,0x56ae3b2cad5c8,0x56ae3b2cad5c8,1 +np.float64,0x3fe9299f2bf2533e,0x3fe552bddd999e72,1 +np.float64,0x7feff3a4823fe748,0x3ff921fb54442d18,1 +np.float64,0xbfd05c670aa0b8ce,0xbfd00494d78e9e97,1 +np.float64,0xffe745323eae8a64,0xbff921fb54442d18,1 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-arctanh.csv b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-arctanh.csv new file mode 100644 index 0000000000000000000000000000000000000000..68ecaab37f92c81e3edf5b8a16224a7f92429051 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-arctanh.csv @@ -0,0 +1,1429 @@ +dtype,input,output,ulperrortol +np.float32,0x3ee82930,0x3efa60fd,2 +np.float32,0x3f0aa640,0x3f1b3e13,2 +np.float32,0x3ec1a21c,0x3ecbbf8d,2 +np.float32,0x3cdb1740,0x3cdb24a1,2 +np.float32,0xbf28b6f3,0xbf4a86ac,2 +np.float32,0xbe490dcc,0xbe4bb2eb,2 +np.float32,0x80000001,0x80000001,2 +np.float32,0xbf44f9dd,0xbf826ce1,2 +np.float32,0xbf1d66c4,0xbf37786b,2 +np.float32,0x3f0ad26a,0x3f1b7c9b,2 +np.float32,0x3f7b6c54,0x4016aab0,2 +np.float32,0xbf715bb8,0xbfe1a0bc,2 +np.float32,0xbee8a562,0xbefafd6a,2 +np.float32,0x3db94d00,0x3db9cf16,2 +np.float32,0x3ee2970c,0x3ef368b3,2 +np.float32,0x3f3f8614,0x3f77fdca,2 +np.float32,0xbf1fb5f0,0xbf3b3789,2 +np.float32,0x3f798dc0,0x400b96bb,2 +np.float32,0x3e975d64,0x3e9c0573,2 +np.float32,0xbe3f1908,0xbe415d1f,2 +np.float32,0x3f2cea38,0x3f52192e,2 +np.float32,0x3e82f1ac,0x3e85eaa1,2 +np.float32,0x3eab6b30,0x3eb24acd,2 +np.float32,0xbe9bb90c,0xbea0cf5f,2 +np.float32,0xbf43e847,0xbf81202f,2 +np.float32,0xbd232fa0,0xbd2345c0,2 +np.float32,0xbbabbc00,0xbbabbc67,2 +np.float32,0xbf0b2975,0xbf1bf808,2 +np.float32,0xbef5ab0a,0xbf05d305,2 +np.float32,0x3f2cad16,0x3f51a8e2,2 +np.float32,0xbef75940,0xbf06eb08,2 +np.float32,0xbf0c1216,0xbf1d4325,2 +np.float32,0x3e7bdc08,0x3e8090c2,2 +np.float32,0x3da14e10,0x3da1a3c5,2 +np.float32,0x3f627412,0x3fb2bf21,2 +np.float32,0xbd6d08c0,0xbd6d4ca0,2 +np.float32,0x3f3e2368,0x3f74df8b,2 +np.float32,0xbe0df104,0xbe0edc77,2 +np.float32,0x3e8a265c,0x3e8da833,2 +np.float32,0xbdccdbb0,0xbdcd8ba8,2 +np.float32,0x3eb080c4,0x3eb80a44,2 +np.float32,0x3e627800,0x3e6645fe,2 +np.float32,0xbd8be0b0,0xbd8c1886,2 +np.float32,0xbf3282ac,0xbf5cae8c,2 +np.float32,0xbe515910,0xbe545707,2 +np.float32,0xbf2e64ac,0xbf54d637,2 +np.float32,0x3e0fc230,0x3e10b6de,2 +np.float32,0x3eb13ca0,0x3eb8df94,2 +np.float32,0x3f07a3ca,0x3f170572,2 +np.float32,0x3f2c7026,0x3f513935,2 +np.float32,0x3f3c4ec8,0x3f70d67c,2 +np.float32,0xbee9cce8,0xbefc724f,2 +np.float32,0xbe53ca60,0xbe56e3f3,2 +np.float32,0x3dd9e9a0,0x3ddabd98,2 +np.float32,0x3f38b8d4,0x3f69319b,2 +np.float32,0xbe176dc4,0xbe188c1d,2 +np.float32,0xbf322f2e,0xbf5c0c51,2 +np.float32,0xbe9b8676,0xbea097a2,2 +np.float32,0xbca44280,0xbca44823,2 +np.float32,0xbe2b0248,0xbe2ca036,2 +np.float32,0x3d101e80,0x3d102dbd,2 +np.float32,0xbf4eb610,0xbf8f526d,2 +np.float32,0xbec32a50,0xbecd89d1,2 +np.float32,0x3d549100,0x3d54c1ee,2 +np.float32,0x3f78e55e,0x40087025,2 +np.float32,0x3e592798,0x3e5c802d,2 +np.float32,0x3de045d0,0x3de12cfb,2 +np.float32,0xbdad28e0,0xbdad92f7,2 +np.float32,0x3e9a69e0,0x3e9f5e59,2 +np.float32,0x3e809778,0x3e836716,2 +np.float32,0xbf3278d9,0xbf5c9b6d,2 +np.float32,0x3f39fa00,0x3f6bd4a5,2 +np.float32,0xbec8143c,0xbed34ffa,2 +np.float32,0x3ddb7f40,0x3ddc57e6,2 +np.float32,0x3f0e8342,0x3f20c634,2 +np.float32,0x3f353dda,0x3f6213a4,2 +np.float32,0xbe96b400,0xbe9b4bea,2 +np.float32,0x3e626580,0x3e66328a,2 +np.float32,0xbde091c8,0xbde179df,2 +np.float32,0x3eb47b5c,0x3ebc91ca,2 +np.float32,0xbf282182,0xbf497f2f,2 +np.float32,0x3ea9f64c,0x3eb0a748,2 +np.float32,0x3f28dd4e,0x3f4aca86,2 +np.float32,0xbf71de18,0xbfe3f587,2 +np.float32,0x7fa00000,0x7fe00000,2 +np.float32,0xbf6696a6,0xbfbcf11a,2 +np.float32,0xbc853ae0,0xbc853de2,2 +np.float32,0xbeced246,0xbedb51b8,2 +np.float32,0x3f3472a4,0x3f607e00,2 +np.float32,0xbee90124,0xbefb7117,2 +np.float32,0x3eb45b90,0x3ebc6d7c,2 +np.float32,0xbe53ead0,0xbe5705d6,2 +np.float32,0x3f630c80,0x3fb420e2,2 +np.float32,0xbf408cd0,0xbf7a56a2,2 +np.float32,0x3dda4ed0,0x3ddb23f1,2 +np.float32,0xbf37ae88,0xbf67096b,2 +np.float32,0xbdd48c28,0xbdd550c9,2 +np.float32,0xbf5745b0,0xbf9cb4a4,2 +np.float32,0xbf44e6fc,0xbf8255c1,2 +np.float32,0x3f5c8e6a,0x3fa65020,2 +np.float32,0xbea45fe8,0xbeaa6630,2 +np.float32,0x3f08bdee,0x3f188ef5,2 +np.float32,0x3ec77e74,0x3ed29f4b,2 +np.float32,0xbf1a1d3c,0xbf324029,2 +np.float32,0x3cad7340,0x3cad79e3,2 +np.float32,0xbf4fac2e,0xbf90b72a,2 +np.float32,0x3f58516e,0x3f9e8330,2 +np.float32,0x3f442008,0x3f816391,2 +np.float32,0xbf6e0c6c,0xbfd42854,2 +np.float32,0xbf266f7a,0xbf4689b2,2 +np.float32,0x3eb7e2f0,0x3ec077ba,2 +np.float32,0xbf320fd0,0xbf5bcf83,2 +np.float32,0xbf6a76b9,0xbfc80a11,2 +np.float32,0xbf2a91b4,0xbf4dd526,2 +np.float32,0x3f176e30,0x3f2e150e,2 +np.float32,0xbdcccad0,0xbdcd7a9c,2 +np.float32,0x3f60a8a4,0x3faebbf7,2 +np.float32,0x3d9706f0,0x3d974d40,2 +np.float32,0x3ef3cd34,0x3f049d58,2 +np.float32,0xbf73c615,0xbfed79fe,2 +np.float32,0x3df1b170,0x3df2d31b,2 +np.float32,0x3f632a46,0x3fb466c7,2 +np.float32,0xbf3ea18e,0xbf75f9ce,2 +np.float32,0xbf3ea05c,0xbf75f71f,2 +np.float32,0xbdd76750,0xbdd83403,2 +np.float32,0xbca830c0,0xbca836cd,2 +np.float32,0x3f1d4162,0x3f373c59,2 +np.float32,0x3c115700,0x3c1157fa,2 +np.float32,0x3dae8ab0,0x3daef758,2 +np.float32,0xbcad5020,0xbcad56bf,2 +np.float32,0x3ee299c4,0x3ef36c15,2 +np.float32,0xbf7f566c,0xc054c3bd,2 +np.float32,0x3f0cc698,0x3f1e4557,2 +np.float32,0xbe75c648,0xbe7aaa04,2 +np.float32,0x3ea29238,0x3ea86417,2 +np.float32,0x3f09d9c0,0x3f1a1d61,2 +np.float32,0x3f67275c,0x3fbe74b3,2 +np.float32,0x3e1a4e18,0x3e1b7d3a,2 +np.float32,0xbef6e3fc,0xbf069e98,2 +np.float32,0xbf6038ac,0xbfadc9fd,2 +np.float32,0xbe46bdd4,0xbe494b7f,2 +np.float32,0xbf4df1f4,0xbf8e3a98,2 +np.float32,0x3d094dc0,0x3d095aed,2 +np.float32,0x3f44c7d2,0x3f822fa3,2 +np.float32,0xbea30816,0xbea8e737,2 +np.float32,0xbe3c27c4,0xbe3e511b,2 +np.float32,0x3f3bb47c,0x3f6f8789,2 +np.float32,0xbe423760,0xbe4498c3,2 +np.float32,0x3ece1a74,0x3eda7634,2 +np.float32,0x3f14d1f6,0x3f2a1a89,2 +np.float32,0xbf4d9e8f,0xbf8dc4c1,2 +np.float32,0xbe92968e,0xbe96cd7f,2 +np.float32,0x3e99e6c0,0x3e9ece26,2 +np.float32,0xbf397361,0xbf6ab878,2 +np.float32,0xbf4fcea4,0xbf90e99f,2 +np.float32,0x3de37640,0x3de46779,2 +np.float32,0x3eb1b604,0x3eb9698c,2 +np.float32,0xbf52d0a2,0xbf957361,2 +np.float32,0xbe20435c,0xbe21975a,2 +np.float32,0x3f437a58,0x3f809bf1,2 +np.float32,0x3f27d1cc,0x3f48f335,2 +np.float32,0x3f7d4ff2,0x4027d1e2,2 +np.float32,0xbef732e4,0xbf06d205,2 +np.float32,0x3f4a0ae6,0x3f88e18e,2 +np.float32,0x3f800000,0x7f800000,2 +np.float32,0x3e3e56a0,0x3e4093ba,2 +np.float32,0xbed2fcfa,0xbee0517d,2 +np.float32,0xbe0e0114,0xbe0eecd7,2 +np.float32,0xbe808574,0xbe8353db,2 +np.float32,0x3f572e2a,0x3f9c8c86,2 +np.float32,0x80800000,0x80800000,2 +np.float32,0x3f3f3c82,0x3f775703,2 +np.float32,0xbf6e2482,0xbfd4818b,2 +np.float32,0xbf3943b0,0xbf6a5439,2 +np.float32,0x3f6e42ac,0x3fd4f1ea,2 +np.float32,0x3eb676c4,0x3ebed619,2 +np.float32,0xbe5e56c4,0xbe61ef6c,2 +np.float32,0x3eea200c,0x3efcdb65,2 +np.float32,0x3e3d2c78,0x3e3f5ef8,2 +np.float32,0xbdfd8fb0,0xbdfede71,2 +np.float32,0xbee69c8a,0xbef86e89,2 +np.float32,0x3e9efca0,0x3ea46a1c,2 +np.float32,0x3e4c2498,0x3e4ee9ee,2 +np.float32,0xbf3cc93c,0xbf71e21d,2 +np.float32,0x3ee0d77c,0x3ef13d2b,2 +np.float32,0xbefbcd2a,0xbf09d6a3,2 +np.float32,0x3f6dbe5c,0x3fd30a3e,2 +np.float32,0x3dae63e0,0x3daed03f,2 +np.float32,0xbd5001e0,0xbd502fb9,2 +np.float32,0x3f59632a,0x3fa067c8,2 +np.float32,0x3f0d355a,0x3f1ee452,2 +np.float32,0x3f2cbe5c,0x3f51c896,2 +np.float32,0x3c5e6e80,0x3c5e7200,2 +np.float32,0xbe8ac49c,0xbe8e52f0,2 +np.float32,0x3f54e576,0x3f98c0e6,2 +np.float32,0xbeaa0762,0xbeb0ba7c,2 +np.float32,0x3ec81e88,0x3ed35c21,2 +np.float32,0x3f5a6738,0x3fa23fb6,2 +np.float32,0xbf24a682,0xbf43784a,2 +np.float32,0x1,0x1,2 +np.float32,0x3ee6bc24,0x3ef89630,2 +np.float32,0x3f19444a,0x3f30ecf5,2 +np.float32,0x3ec1fc70,0x3ecc28fc,2 +np.float32,0xbf706e14,0xbfdd92fb,2 +np.float32,0x3eccb630,0x3ed8cd98,2 +np.float32,0xbcdf7aa0,0xbcdf88d3,2 +np.float32,0xbe450da8,0xbe478a8e,2 +np.float32,0x3ec9c210,0x3ed54c0b,2 +np.float32,0xbf3b86ca,0xbf6f24d1,2 +np.float32,0x3edcc7a0,0x3eec3a5c,2 +np.float32,0x3f075d5c,0x3f16a39a,2 +np.float32,0xbf5719ce,0xbf9c69de,2 +np.float32,0x3f62cb22,0x3fb3885a,2 +np.float32,0x3f639216,0x3fb55c93,2 +np.float32,0xbf473ee7,0xbf85413a,2 +np.float32,0xbf01b66c,0xbf0eea86,2 +np.float32,0x3e872d80,0x3e8a74f8,2 +np.float32,0xbf60957e,0xbfae925c,2 +np.float32,0xbf6847b2,0xbfc1929b,2 +np.float32,0x3f78bb94,0x4007b363,2 +np.float32,0xbf47efdb,0xbf8622db,2 +np.float32,0xbe1f2308,0xbe206fd6,2 +np.float32,0xbf414926,0xbf7c0a7e,2 +np.float32,0x3eecc268,0x3f00194d,2 +np.float32,0x3eb086d0,0x3eb81120,2 +np.float32,0xbef1af80,0xbf033ff5,2 +np.float32,0xbf454e56,0xbf82d4aa,2 +np.float32,0x3e622560,0x3e65ef20,2 +np.float32,0x3f50d2b2,0x3f926a83,2 +np.float32,0x3eb2c45c,0x3eba9d2c,2 +np.float32,0x3e42d1a0,0x3e4538c9,2 +np.float32,0xbf24cc5c,0xbf43b8e3,2 +np.float32,0x3e8c6464,0x3e90141a,2 +np.float32,0xbf3abff2,0xbf6d79c5,2 +np.float32,0xbec8f2e6,0xbed456fa,2 +np.float32,0xbf787b38,0xc00698b4,2 +np.float32,0xbf58d5cd,0xbf9f6c03,2 +np.float32,0x3df4ee20,0x3df61ba8,2 +np.float32,0xbf34581e,0xbf604951,2 +np.float32,0xbeba5cf4,0xbec35119,2 +np.float32,0xbf76c22d,0xbfffc51c,2 +np.float32,0x3ef63b2c,0x3f0630b4,2 +np.float32,0x3eeadb64,0x3efdc877,2 +np.float32,0x3dfd8c70,0x3dfedb24,2 +np.float32,0x3f441600,0x3f81576d,2 +np.float32,0x3f23a0d8,0x3f41bbf6,2 +np.float32,0x3cb84d40,0x3cb85536,2 +np.float32,0xbf25cb5c,0xbf456e38,2 +np.float32,0xbc108540,0xbc108636,2 +np.float32,0xbc5b9140,0xbc5b949e,2 +np.float32,0xbf62ff40,0xbfb401dd,2 +np.float32,0x3e8e0710,0x3e91d93e,2 +np.float32,0x3f1b6ae0,0x3f344dfd,2 +np.float32,0xbf4dbbbe,0xbf8dedea,2 +np.float32,0x3f1a5fb2,0x3f32a880,2 +np.float32,0xbe56bd00,0xbe59f8cb,2 +np.float32,0xbf490a5c,0xbf87902d,2 +np.float32,0xbf513072,0xbf92f717,2 +np.float32,0x3e73ee28,0x3e78b542,2 +np.float32,0x3f0a4c7a,0x3f1abf2c,2 +np.float32,0x3e10d5c8,0x3e11d00b,2 +np.float32,0xbf771aac,0xc001207e,2 +np.float32,0x3efe2f54,0x3f0b6a46,2 +np.float32,0xbea5f3ea,0xbeac291f,2 +np.float32,0xbf1a73e8,0xbf32c845,2 +np.float32,0x3ebcc82c,0x3ec61c4f,2 +np.float32,0xbf24f492,0xbf43fd9a,2 +np.float32,0x3ecbd908,0x3ed7c691,2 +np.float32,0x3f461c5e,0x3f83d3f0,2 +np.float32,0x3eed0524,0x3f0043c1,2 +np.float32,0x3d06e840,0x3d06f4bf,2 +np.float32,0x3eb6c974,0x3ebf34d7,2 +np.float32,0xbf1c85e1,0xbf36100f,2 +np.float32,0x3ed697d0,0x3ee4ad04,2 +np.float32,0x3eab0484,0x3eb1d733,2 +np.float32,0xbf3b02f2,0xbf6e0935,2 +np.float32,0xbeeab154,0xbefd9334,2 +np.float32,0xbf695372,0xbfc49881,2 +np.float32,0x3e8aaa7c,0x3e8e36be,2 +np.float32,0xbf208754,0xbf3c8f7b,2 +np.float32,0xbe0dbf28,0xbe0ea9a1,2 +np.float32,0x3ca780c0,0x3ca786ba,2 +np.float32,0xbeb320b4,0xbebb065e,2 +np.float32,0x3f13c698,0x3f288821,2 +np.float32,0xbe8cbbec,0xbe9072c4,2 +np.float32,0x3f1ed534,0x3f39c8df,2 +np.float32,0x3e1ca450,0x3e1de190,2 +np.float32,0x3f54be1c,0x3f988134,2 +np.float32,0x3f34e4ee,0x3f6161b4,2 +np.float32,0xbf7e6913,0xc038b246,2 +np.float32,0x3d3c3f20,0x3d3c6119,2 +np.float32,0x3ca9dc80,0x3ca9e2bc,2 +np.float32,0xbf577ea2,0xbf9d161a,2 +np.float32,0xbedb22c8,0xbeea3644,2 +np.float32,0x3f22a044,0x3f400bfa,2 +np.float32,0xbe214b8c,0xbe22a637,2 +np.float32,0x3e8cd300,0x3e908bbc,2 +np.float32,0xbec4d214,0xbecf7a58,2 +np.float32,0x3e9399a4,0x3e97e7e4,2 +np.float32,0xbee6a1a2,0xbef874ed,2 +np.float32,0xbf323742,0xbf5c1bfd,2 +np.float32,0x3f48b882,0x3f8725ac,2 +np.float32,0xbf4d4dba,0xbf8d532e,2 +np.float32,0xbf59640a,0xbfa0695a,2 +np.float32,0xbf2ad562,0xbf4e4f03,2 +np.float32,0x3e317d98,0x3e334d03,2 +np.float32,0xbf6a5b71,0xbfc7b5a2,2 +np.float32,0x3e87b434,0x3e8b05cf,2 +np.float32,0xbf1c344c,0xbf358dee,2 +np.float32,0x3e449428,0x3e470c65,2 +np.float32,0xbf2c0f2f,0xbf508808,2 +np.float32,0xbec5b5ac,0xbed0859c,2 +np.float32,0xbf4aa956,0xbf89b4b1,2 +np.float32,0x3f6dd374,0x3fd35717,2 +np.float32,0x3f45f76c,0x3f83a5ef,2 +np.float32,0xbed1fba8,0xbedf1bd5,2 +np.float32,0xbd26b2d0,0xbd26ca66,2 +np.float32,0xbe9817c2,0xbe9cd1c3,2 +np.float32,0x3e725988,0x3e770875,2 +np.float32,0xbf1a8ded,0xbf32f132,2 +np.float32,0xbe695860,0xbe6d83d3,2 +np.float32,0x3d8cecd0,0x3d8d25ea,2 +np.float32,0x3f574706,0x3f9cb6ec,2 +np.float32,0xbf5c5a1f,0xbfa5eaf3,2 +np.float32,0x3e7a7c88,0x3e7fab83,2 +np.float32,0xff800000,0xffc00000,2 +np.float32,0x3f66396a,0x3fbbfbb0,2 +np.float32,0x3ed6e588,0x3ee50b53,2 +np.float32,0xbb56d500,0xbb56d532,2 +np.float32,0x3ebd23fc,0x3ec6869a,2 +np.float32,0xbf70d490,0xbfdf4af5,2 +np.float32,0x3e514f88,0x3e544d15,2 +np.float32,0x3e660f98,0x3e6a0dac,2 +np.float32,0xbf034da1,0xbf1110bb,2 +np.float32,0xbf60d9be,0xbfaf2714,2 +np.float32,0x3df67b10,0x3df7ae64,2 +np.float32,0xbeeedc0a,0xbf017010,2 +np.float32,0xbe149224,0xbe15a072,2 +np.float32,0x3f455084,0x3f82d759,2 +np.float32,0x3f210f9e,0x3f3d7093,2 +np.float32,0xbeaea3e0,0xbeb5edd3,2 +np.float32,0x3e0724b0,0x3e07efad,2 +np.float32,0x3f09a784,0x3f19d6ac,2 +np.float32,0xbf044340,0xbf125ee8,2 +np.float32,0xbf71adc9,0xbfe315fe,2 +np.float32,0x3efd3870,0x3f0ac6a8,2 +np.float32,0xbf53c7a6,0xbf96f6df,2 +np.float32,0xbf3cf784,0xbf7247af,2 +np.float32,0x3e0ce9e0,0x3e0dd035,2 +np.float32,0xbd3051a0,0xbd306d89,2 +np.float32,0x3ecab804,0x3ed66f77,2 +np.float32,0x3e984350,0x3e9d0189,2 +np.float32,0x3edd1c00,0x3eeca20b,2 +np.float32,0xbe8e22a0,0xbe91f71b,2 +np.float32,0x3ebebc18,0x3ec85fd6,2 +np.float32,0xba275c00,0xba275c01,2 +np.float32,0x3f1d8190,0x3f37a385,2 +np.float32,0x3f17343e,0x3f2dbbfe,2 +np.float32,0x3caa8000,0x3caa864e,2 +np.float32,0x3e7a7308,0x3e7fa168,2 +np.float32,0x3f7359a6,0x3feb3e1a,2 +np.float32,0xbf7ad15a,0xc012a743,2 +np.float32,0xbf122efb,0xbf262812,2 +np.float32,0xbf03ba04,0xbf11a3fa,2 +np.float32,0x3ed7a90c,0x3ee5f8d4,2 +np.float32,0xbe23e318,0xbe254eed,2 +np.float32,0xbe2866f4,0xbe29f20a,2 +np.float32,0xbeaedff2,0xbeb631d0,2 +np.float32,0x0,0x0,2 +np.float32,0x3ef2a034,0x3f03dafd,2 +np.float32,0x3f35806c,0x3f62994e,2 +np.float32,0xbf655e19,0xbfb9c718,2 +np.float32,0x3f5d54ce,0x3fa7d4f4,2 +np.float32,0x3f33e64a,0x3f5f67e3,2 +np.float32,0x3ebf4010,0x3ec8f923,2 +np.float32,0xbe050dc8,0xbe05cf70,2 +np.float32,0x3f61693e,0x3fb063b0,2 +np.float32,0xbd94ac00,0xbd94ef12,2 +np.float32,0x3e9de008,0x3ea32f61,2 +np.float32,0xbe3d042c,0xbe3f3540,2 +np.float32,0x3e8fdfc0,0x3e93d9e4,2 +np.float32,0x3f28bc48,0x3f4a9019,2 +np.float32,0x3edea928,0x3eee8b09,2 +np.float32,0xbf05f673,0xbf14b362,2 +np.float32,0xbf360730,0xbf63a914,2 +np.float32,0xbe3fb454,0xbe41fe0a,2 +np.float32,0x3f6d99a8,0x3fd28552,2 +np.float32,0xbf3ae866,0xbf6dd052,2 +np.float32,0x3f5b1164,0x3fa37aec,2 +np.float32,0xbf64a451,0xbfb7f61b,2 +np.float32,0xbdd79bd0,0xbdd86919,2 +np.float32,0x3e89fc00,0x3e8d7a85,2 +np.float32,0x3f4bf690,0x3f8b77ea,2 +np.float32,0x3cbdf280,0x3cbdfb38,2 +np.float32,0x3f138f98,0x3f2835b4,2 +np.float32,0xbe33967c,0xbe3576bc,2 +np.float32,0xbf298164,0xbf4bedda,2 +np.float32,0x3e9955cc,0x3e9e2edb,2 +np.float32,0xbf79b383,0xc00c56c0,2 +np.float32,0x3ea0834c,0x3ea61aea,2 +np.float32,0xbf511184,0xbf92c89a,2 +np.float32,0x3f4d9fba,0x3f8dc666,2 +np.float32,0x3f3387c2,0x3f5ead80,2 +np.float32,0x3e3f7360,0x3e41babb,2 +np.float32,0xbf3cc4d6,0xbf71d879,2 +np.float32,0x3f2e4402,0x3f54994e,2 +np.float32,0x3e6a7118,0x3e6eabff,2 +np.float32,0xbf05d83e,0xbf1489cc,2 +np.float32,0xbdce4fd8,0xbdcf039a,2 +np.float32,0xbf03e2f4,0xbf11dbaf,2 +np.float32,0x3f1ea0a0,0x3f397375,2 +np.float32,0x3f7aff54,0x4013cb1b,2 +np.float32,0x3f5ef158,0x3fab1801,2 +np.float32,0xbe33bcc8,0xbe359e40,2 +np.float32,0xbf04dd0e,0xbf133111,2 +np.float32,0xbf14f887,0xbf2a54d1,2 +np.float32,0x3f75c37a,0x3ff9196e,2 +np.float32,0x3f35c3c8,0x3f6320f2,2 +np.float32,0x3f53bb94,0x3f96e3c3,2 +np.float32,0x3f4d473e,0x3f8d4a19,2 +np.float32,0xbdfe19e0,0xbdff6ac9,2 +np.float32,0xbf7f0cc4,0xc049342d,2 +np.float32,0xbdbfc778,0xbdc057bb,2 +np.float32,0xbf7575b7,0xbff73067,2 +np.float32,0xbe9df488,0xbea34609,2 +np.float32,0xbefbd3c6,0xbf09daff,2 +np.float32,0x3f19962c,0x3f316cbd,2 +np.float32,0x3f7acec6,0x40129732,2 +np.float32,0xbf5db7de,0xbfa89a21,2 +np.float32,0x3f62f444,0x3fb3e830,2 +np.float32,0xbf522adb,0xbf94737f,2 +np.float32,0xbef6ceb2,0xbf0690ba,2 +np.float32,0xbf57c41e,0xbf9d8db0,2 +np.float32,0x3eb3360c,0x3ebb1eb0,2 +np.float32,0x3f29327e,0x3f4b618e,2 +np.float32,0xbf08d099,0xbf18a916,2 +np.float32,0x3ea21014,0x3ea7d369,2 +np.float32,0x3f39e516,0x3f6ba861,2 +np.float32,0x3e7c4f28,0x3e80ce08,2 +np.float32,0xbec5a7f8,0xbed07582,2 +np.float32,0xbf0b1b46,0xbf1be3e7,2 +np.float32,0xbef0e0ec,0xbf02bb2e,2 +np.float32,0x3d835a30,0x3d838869,2 +np.float32,0x3f08aa40,0x3f18736e,2 +np.float32,0x3eb0e4c8,0x3eb87bcd,2 +np.float32,0x3eb3821c,0x3ebb7564,2 +np.float32,0xbe3a7320,0xbe3c8d5a,2 +np.float32,0x3e43f8c0,0x3e466b10,2 +np.float32,0x3e914288,0x3e955b69,2 +np.float32,0x3ec7d800,0x3ed308e7,2 +np.float32,0x3e603df8,0x3e63eef2,2 +np.float32,0x3f225cac,0x3f3f9ac6,2 +np.float32,0x3e3db8f0,0x3e3ff06b,2 +np.float32,0x3f358d78,0x3f62b38c,2 +np.float32,0xbed9bd64,0xbee88158,2 +np.float32,0x800000,0x800000,2 +np.float32,0x3f1adfce,0x3f337230,2 +np.float32,0xbefdc346,0xbf0b229d,2 +np.float32,0xbf091018,0xbf190208,2 +np.float32,0xbf800000,0xff800000,2 +np.float32,0x3f27c2c4,0x3f48d8db,2 +np.float32,0x3ef59c80,0x3f05c993,2 +np.float32,0x3e18a340,0x3e19c893,2 +np.float32,0x3f209610,0x3f3ca7c5,2 +np.float32,0x3f69cc22,0x3fc60087,2 +np.float32,0xbf66cf07,0xbfbd8721,2 +np.float32,0xbf768098,0xbffdfcc4,2 +np.float32,0x3df27a40,0x3df39ec4,2 +np.float32,0x3daf5bd0,0x3dafca02,2 +np.float32,0x3f53f2be,0x3f973b41,2 +np.float32,0xbf7edcbc,0xc0436ce3,2 +np.float32,0xbdf61db8,0xbdf74fae,2 +np.float32,0x3e2c9328,0x3e2e3cb2,2 +np.float32,0x3f1a4570,0x3f327f41,2 +np.float32,0xbf766306,0xbffd32f1,2 +np.float32,0xbf468b9d,0xbf845f0f,2 +np.float32,0x3e398970,0x3e3b9bb1,2 +np.float32,0xbbefa900,0xbbefaa18,2 +np.float32,0xbf54c989,0xbf9893ad,2 +np.float32,0x3f262cf6,0x3f46169d,2 +np.float32,0x3f638a8a,0x3fb54a98,2 +np.float32,0xbeb36c78,0xbebb5cb8,2 +np.float32,0xbeac4d42,0xbeb34993,2 +np.float32,0x3f1d1942,0x3f36fbf2,2 +np.float32,0xbf5d49ba,0xbfa7bf07,2 +np.float32,0xbf182b5c,0xbf2f38d0,2 +np.float32,0x3f41a742,0x3f7ce5ef,2 +np.float32,0x3f0b9a6c,0x3f1c9898,2 +np.float32,0x3e847494,0x3e8788f3,2 +np.float32,0xbde41608,0xbde50941,2 +np.float32,0x3f693944,0x3fc44b5a,2 +np.float32,0x3f0386b2,0x3f115e37,2 +np.float32,0x3f3a08b0,0x3f6bf3c1,2 +np.float32,0xbf78ee64,0xc0089977,2 +np.float32,0xbf013a11,0xbf0e436e,2 +np.float32,0x3f00668e,0x3f0d2836,2 +np.float32,0x3e6d9850,0x3e720081,2 +np.float32,0x3eacf578,0x3eb4075d,2 +np.float32,0x3f18aef8,0x3f3004b4,2 +np.float32,0x3de342f0,0x3de43385,2 +np.float32,0x3e56cee8,0x3e5a0b85,2 +np.float32,0xbf287912,0xbf4a1966,2 +np.float32,0x3e92c948,0x3e9704c2,2 +np.float32,0x3c07d080,0x3c07d14c,2 +np.float32,0xbe90f6a0,0xbe9508e0,2 +np.float32,0x3e8b4f28,0x3e8ee884,2 +np.float32,0xbf35b56c,0xbf6303ff,2 +np.float32,0xbef512b8,0xbf057027,2 +np.float32,0x3e36c630,0x3e38c0cd,2 +np.float32,0x3f0b3ca8,0x3f1c134a,2 +np.float32,0x3e4cd610,0x3e4fa2c5,2 +np.float32,0xbf5a8372,0xbfa273a3,2 +np.float32,0xbecaad3c,0xbed662ae,2 +np.float32,0xbec372d2,0xbecddeac,2 +np.float32,0x3f6fb2b2,0x3fda8a22,2 +np.float32,0x3f365f28,0x3f645b5a,2 +np.float32,0xbecd00fa,0xbed926a4,2 +np.float32,0xbebafa32,0xbec40672,2 +np.float32,0xbf235b73,0xbf4146c4,2 +np.float32,0x3f7a4658,0x400f6e2c,2 +np.float32,0x3f35e824,0x3f636a54,2 +np.float32,0x3cb87640,0x3cb87e3c,2 +np.float32,0xbf296288,0xbf4bb6ee,2 +np.float32,0x7f800000,0xffc00000,2 +np.float32,0xbf4de86e,0xbf8e2d1a,2 +np.float32,0xbf4ace12,0xbf89e5f3,2 +np.float32,0x3d65a300,0x3d65e0b5,2 +np.float32,0xbe10c534,0xbe11bf21,2 +np.float32,0xbeba3c1c,0xbec32b3e,2 +np.float32,0x3e87eaf8,0x3e8b40b8,2 +np.float32,0x3d5c3bc0,0x3d5c722d,2 +np.float32,0x3e8c14b8,0x3e8fbdf8,2 +np.float32,0xbf06c6f0,0xbf15d327,2 +np.float32,0xbe0f1e30,0xbe100f96,2 +np.float32,0xbee244b0,0xbef30251,2 +np.float32,0x3f2a21b0,0x3f4d0c1d,2 +np.float32,0xbf5f7f81,0xbfac408e,2 +np.float32,0xbe3dba2c,0xbe3ff1b2,2 +np.float32,0x3f3ffc22,0x3f790abf,2 +np.float32,0x3edc3dac,0x3eeb90fd,2 +np.float32,0x7f7fffff,0xffc00000,2 +np.float32,0x3ecfaaac,0x3edc5485,2 +np.float32,0x3f0affbe,0x3f1bbcd9,2 +np.float32,0x3f5f2264,0x3fab7dca,2 +np.float32,0x3f37394c,0x3f66186c,2 +np.float32,0xbe6b2f6c,0xbe6f74e3,2 +np.float32,0x3f284772,0x3f49c1f1,2 +np.float32,0xbdf27bc8,0xbdf3a051,2 +np.float32,0xbc8b14e0,0xbc8b184c,2 +np.float32,0x3f6a867c,0x3fc83b07,2 +np.float32,0x3f1ec876,0x3f39b429,2 +np.float32,0x3f6fd9a8,0x3fdb28d6,2 +np.float32,0xbf473cca,0xbf853e8c,2 +np.float32,0x3e23eff8,0x3e255c23,2 +np.float32,0x3ebefdfc,0x3ec8ac5d,2 +np.float32,0x3f6c8c22,0x3fced2b1,2 +np.float32,0x3f168388,0x3f2cad44,2 +np.float32,0xbece2410,0xbeda81ac,2 +np.float32,0x3f5532f0,0x3f993eea,2 +np.float32,0x3ef1938c,0x3f032dfa,2 +np.float32,0xbef05268,0xbf025fba,2 +np.float32,0x3f552e4a,0x3f993754,2 +np.float32,0x3e9ed068,0x3ea4392d,2 +np.float32,0xbe1a0c24,0xbe1b39be,2 +np.float32,0xbf2623aa,0xbf46068c,2 +np.float32,0xbe1cc300,0xbe1e00fc,2 +np.float32,0xbe9c0576,0xbea12397,2 +np.float32,0xbd827338,0xbd82a07e,2 +np.float32,0x3f0fc31a,0x3f229786,2 +np.float32,0x3e577810,0x3e5abc7d,2 +np.float32,0x3e0e1cb8,0x3e0f0906,2 +np.float32,0x3e84d344,0x3e87ee73,2 +np.float32,0xbf39c45e,0xbf6b6337,2 +np.float32,0x3edfb25c,0x3eefd273,2 +np.float32,0x3e016398,0x3e021596,2 +np.float32,0xbefeb1be,0xbf0bc0de,2 +np.float32,0x3f37e104,0x3f677196,2 +np.float32,0x3f545316,0x3f97d500,2 +np.float32,0xbefc165a,0xbf0a06ed,2 +np.float32,0xbf0923e6,0xbf191dcd,2 +np.float32,0xbf386508,0xbf68831f,2 +np.float32,0xbf3d4630,0xbf72f4e1,2 +np.float32,0x3f3dbe82,0x3f73ff13,2 +np.float32,0xbf703de4,0xbfdcc7e2,2 +np.float32,0xbf531482,0xbf95dd1a,2 +np.float32,0xbf0af1b6,0xbf1ba8f4,2 +np.float32,0xbec8fd9c,0xbed463a4,2 +np.float32,0xbe230320,0xbe24691a,2 +np.float32,0xbf7de541,0xc02faf38,2 +np.float32,0x3efd2360,0x3f0ab8b7,2 +np.float32,0x3db7f350,0x3db87291,2 +np.float32,0x3e74c510,0x3e799924,2 +np.float32,0x3da549c0,0x3da5a5fc,2 +np.float32,0x3e8a3bc4,0x3e8dbf4a,2 +np.float32,0xbf69f086,0xbfc66e84,2 +np.float32,0x3f323f8e,0x3f5c2c17,2 +np.float32,0x3ec0ae3c,0x3ecaa334,2 +np.float32,0xbebe8966,0xbec824fc,2 +np.float32,0x3f34691e,0x3f606b13,2 +np.float32,0x3f13790e,0x3f2813f5,2 +np.float32,0xbf61c027,0xbfb12618,2 +np.float32,0x3e90c690,0x3e94d4a1,2 +np.float32,0xbefce8f0,0xbf0a920e,2 +np.float32,0xbf5c0e8a,0xbfa559a7,2 +np.float32,0x3f374f60,0x3f6645b6,2 +np.float32,0x3f25f6fa,0x3f45b967,2 +np.float32,0x3f2421aa,0x3f42963a,2 +np.float32,0x3ebfa328,0x3ec96c57,2 +np.float32,0x3e3bef28,0x3e3e1685,2 +np.float32,0x3ea3fa3c,0x3ea9f4dd,2 +np.float32,0x3f362b8e,0x3f63f2b2,2 +np.float32,0xbedcef18,0xbeec6ada,2 +np.float32,0xbdd29c88,0xbdd35bd0,2 +np.float32,0x3f261aea,0x3f45f76f,2 +np.float32,0xbe62c470,0xbe66965e,2 +np.float32,0x7fc00000,0x7fc00000,2 +np.float32,0xbee991aa,0xbefc277b,2 +np.float32,0xbf571960,0xbf9c6923,2 +np.float32,0xbe6fb410,0xbe743b41,2 +np.float32,0x3eb1bed0,0x3eb9738d,2 +np.float32,0x80000000,0x80000000,2 +np.float32,0x3eddcbe4,0x3eed7a69,2 +np.float32,0xbf2a81ba,0xbf4db86d,2 +np.float32,0x3f74da54,0x3ff38737,2 +np.float32,0xbeb6bff4,0xbebf29f4,2 +np.float32,0x3f445752,0x3f81a698,2 +np.float32,0x3ed081b4,0x3edd5618,2 +np.float32,0xbee73802,0xbef931b4,2 +np.float32,0xbd13f2a0,0xbd14031c,2 +np.float32,0xbb4d1200,0xbb4d122c,2 +np.float32,0xbee8777a,0xbefac393,2 +np.float32,0x3f42047c,0x3f7dc06c,2 +np.float32,0xbd089270,0xbd089f67,2 +np.float32,0xbf628c16,0xbfb2f66b,2 +np.float32,0x3e72e098,0x3e77978d,2 +np.float32,0x3ed967cc,0x3ee818e4,2 +np.float32,0x3e284c80,0x3e29d6d9,2 +np.float32,0x3f74e8ba,0x3ff3dbef,2 +np.float32,0x3f013e86,0x3f0e4969,2 +np.float32,0xbf610d4f,0xbfaf983c,2 +np.float32,0xbf3c8d36,0xbf715eba,2 +np.float32,0xbedbc756,0xbeeaffdb,2 +np.float32,0x3e143ec8,0x3e154b4c,2 +np.float32,0xbe1c9808,0xbe1dd4fc,2 +np.float32,0xbe887a1e,0xbe8bdac5,2 +np.float32,0xbe85c4bc,0xbe88f17a,2 +np.float32,0x3f35967e,0x3f62c5b4,2 +np.float32,0x3ea2c4a4,0x3ea89c2d,2 +np.float32,0xbc8703c0,0xbc8706e1,2 +np.float32,0xbf13d52c,0xbf289dff,2 +np.float32,0xbf63bb56,0xbfb5bf29,2 +np.float32,0xbf61c5ef,0xbfb13319,2 +np.float32,0xbf128410,0xbf26a675,2 +np.float32,0x3f03fcf2,0x3f11ff13,2 +np.float32,0xbe49c924,0xbe4c75cd,2 +np.float32,0xbf211a9c,0xbf3d82c5,2 +np.float32,0x3f7e9d52,0x403d1b42,2 +np.float32,0x3edfefd4,0x3ef01e71,2 +np.float32,0x3ebc5bd8,0x3ec59efb,2 +np.float32,0x3d7b02e0,0x3d7b537f,2 +np.float32,0xbf1163ba,0xbf24fb43,2 +np.float32,0x3f5072f2,0x3f91dbf1,2 +np.float32,0xbee700ce,0xbef8ec60,2 +np.float32,0x3f534168,0x3f962359,2 +np.float32,0x3e6d6c40,0x3e71d1ef,2 +np.float32,0x3def9d70,0x3df0b7a8,2 +np.float32,0x3e89cf80,0x3e8d4a8a,2 +np.float32,0xbf687ca7,0xbfc2290f,2 +np.float32,0x3f35e134,0x3f635c51,2 +np.float32,0x3e59eef8,0x3e5d50fa,2 +np.float32,0xbf65c9e1,0xbfbada61,2 +np.float32,0xbf759292,0xbff7e43d,2 +np.float32,0x3f4635a0,0x3f83f372,2 +np.float32,0x3f29baaa,0x3f4c53f1,2 +np.float32,0x3f6b15a6,0x3fc9fe04,2 +np.float32,0x3edabc88,0x3ee9b922,2 +np.float32,0x3ef382e0,0x3f046d4d,2 +np.float32,0xbe351310,0xbe36ff7f,2 +np.float32,0xbf05c935,0xbf14751c,2 +np.float32,0xbf0e7c50,0xbf20bc24,2 +np.float32,0xbf69bc94,0xbfc5d1b8,2 +np.float32,0xbed41aca,0xbee1aa23,2 +np.float32,0x3f518c08,0x3f938162,2 +np.float32,0xbf3d7974,0xbf73661a,2 +np.float32,0x3f1951a6,0x3f3101c9,2 +np.float32,0xbeb3f436,0xbebbf787,2 +np.float32,0xbf77a190,0xc0031d43,2 +np.float32,0x3eb5b3cc,0x3ebdf6e7,2 +np.float32,0xbed534b4,0xbee2fed2,2 +np.float32,0xbe53e1b8,0xbe56fc56,2 +np.float32,0x3f679e20,0x3fbfb91c,2 +np.float32,0xff7fffff,0xffc00000,2 +np.float32,0xbf7b9bcb,0xc0180073,2 +np.float32,0xbf5635e8,0xbf9aea15,2 +np.float32,0xbe5a3318,0xbe5d9856,2 +np.float32,0xbe003284,0xbe00df9a,2 +np.float32,0x3eb119a4,0x3eb8b7d6,2 +np.float32,0xbf3bccf8,0xbf6fbc84,2 +np.float32,0x3f36f600,0x3f658ea8,2 +np.float32,0x3f1ea834,0x3f397fc2,2 +np.float32,0xbe7cfb54,0xbe8129b3,2 +np.float32,0xbe9b3746,0xbea0406a,2 +np.float32,0x3edc0f90,0x3eeb586c,2 +np.float32,0x3e1842e8,0x3e19660c,2 +np.float32,0xbd8f10b0,0xbd8f4c70,2 +np.float32,0xbf064aca,0xbf1527a2,2 +np.float32,0x3e632e58,0x3e6705be,2 +np.float32,0xbef28ba4,0xbf03cdbb,2 +np.float32,0x3f27b21e,0x3f48bbaf,2 +np.float32,0xbe6f30d4,0xbe73b06e,2 +np.float32,0x3f3e6cb0,0x3f75834b,2 +np.float32,0xbf264aa5,0xbf4649f0,2 +np.float32,0xbf690775,0xbfc3b978,2 +np.float32,0xbf3e4a38,0xbf753632,2 +np.float64,0x3fe12bbe8c62577e,0x3fe32de8e5f961b0,1 +np.float64,0x3fc9b8909b337120,0x3fca1366da00efff,1 +np.float64,0x3feaee4245f5dc84,0x3ff3a011ea0432f3,1 +np.float64,0xbfe892c000f12580,0xbff03e5adaed6f0c,1 +np.float64,0xbf9be8de4837d1c0,0xbf9beaa367756bd1,1 +np.float64,0x3fe632e58fec65cc,0x3feb5ccc5114ca38,1 +np.float64,0x3fe78a0ef7ef141e,0x3fee1b4521d8eb6c,1 +np.float64,0x3feec27a65fd84f4,0x3fff643c8318e81e,1 +np.float64,0x3fbed6efce3dade0,0x3fbefd76cff00111,1 +np.float64,0xbfe3a05fab6740c0,0xbfe6db078aeeb0ca,1 +np.float64,0x3fdca11a56b94234,0x3fdece9e6eacff1b,1 +np.float64,0x3fe0fb15aae1f62c,0x3fe2e9e095ec2089,1 +np.float64,0x3fede12abf7bc256,0x3ffafd0ff4142807,1 +np.float64,0x3feb919edcf7233e,0x3ff4c9aa0bc2432f,1 +np.float64,0x3fd39633b5a72c68,0x3fd43c2e6d5f441c,1 +np.float64,0x3fd9efcbfeb3df98,0x3fdb83f03e58f91c,1 +np.float64,0x3fe2867a36650cf4,0x3fe525858c8ce72e,1 +np.float64,0x3fdacbb8f3b59770,0x3fdc8cd431b6e3ff,1 +np.float64,0x3fcc120503382408,0x3fcc88a8fa43e1c6,1 +np.float64,0xbfd99ff4eab33fea,0xbfdb24a20ae3687d,1 +np.float64,0xbfe8caf0157195e0,0xbff083b8dd0941d3,1 +np.float64,0x3fddc9bf92bb9380,0x3fe022aac0f761d5,1 +np.float64,0x3fe2dbb66e65b76c,0x3fe5a6e7caf3f1f2,1 +np.float64,0x3fe95f5c4a72beb8,0x3ff1444697e96138,1 +np.float64,0xbfc6b163d92d62c8,0xbfc6ef6e006658a1,1 +np.float64,0x3fdf1b2616be364c,0x3fe0fcbd2848c9e8,1 +np.float64,0xbfdca1ccf7b9439a,0xbfdecf7dc0eaa663,1 +np.float64,0x3fe078d6a260f1ae,0x3fe236a7c66ef6c2,1 +np.float64,0x3fdf471bb9be8e38,0x3fe11990ec74e704,1 +np.float64,0xbfe417626be82ec5,0xbfe79c9aa5ed2e2f,1 +np.float64,0xbfeb9cf5677739eb,0xbff4dfc24c012c90,1 +np.float64,0x3f8d9142b03b2280,0x3f8d91c9559d4779,1 +np.float64,0x3fb052c67220a590,0x3fb05873c90d1cd6,1 +np.float64,0x3fd742e2c7ae85c4,0x3fd860128947d15d,1 +np.float64,0x3fec2e2a2bf85c54,0x3ff60eb554bb8d71,1 +np.float64,0xbfeb2b8bc8f65718,0xbff40b734679497a,1 +np.float64,0x3fe25f8e0d64bf1c,0x3fe4eb381d077803,1 +np.float64,0x3fe56426256ac84c,0x3fe9dafbe79370f0,1 +np.float64,0x3feecc1e5d7d983c,0x3fffa49bedc7aa25,1 +np.float64,0xbfc88ce94b3119d4,0xbfc8dbba0fdee2d2,1 +np.float64,0xbfabcf51ac379ea0,0xbfabd6552aa63da3,1 +np.float64,0xbfccc8b849399170,0xbfcd48d6ff057a4d,1 +np.float64,0x3fd2f831e8a5f064,0x3fd38e67b0dda905,1 +np.float64,0x3fcafdcd6135fb98,0x3fcb670ae2ef4d36,1 +np.float64,0x3feda6042efb4c08,0x3ffa219442ac4ea5,1 +np.float64,0x3fed382b157a7056,0x3ff8bc01bc6d10bc,1 +np.float64,0x3fed858a50fb0b14,0x3ff9b1c05cb6cc0f,1 +np.float64,0x3fcc3960653872c0,0x3fccb2045373a3d1,1 +np.float64,0xbfec5177e478a2f0,0xbff65eb4557d94eb,1 +np.float64,0x3feafe0d5e75fc1a,0x3ff3bb4a260a0dcb,1 +np.float64,0x3fe08bc87ee11790,0x3fe25078aac99d31,1 +np.float64,0xffefffffffffffff,0xfff8000000000000,1 +np.float64,0x3f79985ce0333100,0x3f799872b591d1cb,1 +np.float64,0xbfd4001cf9a8003a,0xbfd4b14b9035b94f,1 +np.float64,0x3fe54a17e6ea9430,0x3fe9ac0f18682343,1 +np.float64,0xbfb4e07fea29c100,0xbfb4ec6520dd0689,1 +np.float64,0xbfed2b6659fa56cd,0xbff895ed57dc1450,1 +np.float64,0xbfe81fc8b5f03f92,0xbfef6b95e72a7a7c,1 +np.float64,0xbfe6aced16ed59da,0xbfec4ce131ee3704,1 +np.float64,0xbfe599f30ceb33e6,0xbfea3d07c1cd78e2,1 +np.float64,0xbfe0ff278b61fe4f,0xbfe2ef8b5efa89ed,1 +np.float64,0xbfe3e9406467d281,0xbfe750e43e841736,1 +np.float64,0x3fcc6b52cf38d6a8,0x3fcce688f4fb2cf1,1 +np.float64,0xbfc890e8133121d0,0xbfc8dfdfee72d258,1 +np.float64,0x3fe46e81dbe8dd04,0x3fe82e09783811a8,1 +np.float64,0x3fd94455e5b288ac,0x3fdab7cef2de0b1f,1 +np.float64,0xbfe82151fff042a4,0xbfef6f254c9696ca,1 +np.float64,0x3fcee1ac1d3dc358,0x3fcf80a6ed07070a,1 +np.float64,0x3fcce8f90939d1f0,0x3fcd6ad18d34f8b5,1 +np.float64,0x3fd6afe56fad5fcc,0x3fd7b7567526b1fb,1 +np.float64,0x3fb1a77092234ee0,0x3fb1ae9fe0d176fc,1 +np.float64,0xbfeb758b0d76eb16,0xbff493d105652edc,1 +np.float64,0xbfb857c24e30af88,0xbfb86aa4da3be53f,1 +np.float64,0x3fe89064eff120ca,0x3ff03b7c5b3339a8,1 +np.float64,0xbfc1bd2fef237a60,0xbfc1da99893473ed,1 +np.float64,0xbfe5ad6e2eeb5adc,0xbfea60ed181b5c05,1 +np.float64,0x3fd5a66358ab4cc8,0x3fd6899e640aeb1f,1 +np.float64,0xbfe198e832e331d0,0xbfe3c8c9496d0de5,1 +np.float64,0xbfdaa5c0d7b54b82,0xbfdc5ed7d3c5ce49,1 +np.float64,0x3fcceccb6939d998,0x3fcd6ed88c2dd3a5,1 +np.float64,0xbfe44413eae88828,0xbfe7e6cd32b34046,1 +np.float64,0xbfc7cbeccf2f97d8,0xbfc8139a2626edae,1 +np.float64,0x3fbf31e4fa3e63d0,0x3fbf59c6e863255e,1 +np.float64,0x3fdf03fa05be07f4,0x3fe0ed953f7989ad,1 +np.float64,0x3fe7f4eaceefe9d6,0x3fef092ca7e2ac39,1 +np.float64,0xbfc084e9d92109d4,0xbfc09ca10fd6aaea,1 +np.float64,0xbf88cfbf70319f80,0xbf88d00effa6d897,1 +np.float64,0x7ff4000000000000,0x7ffc000000000000,1 +np.float64,0xbfa0176e9c202ee0,0xbfa018ca0a6ceef3,1 +np.float64,0xbfd88d0815b11a10,0xbfd9dfc6c6bcbe4e,1 +np.float64,0x3fe89f7730713eee,0x3ff04de52fb536f3,1 +np.float64,0xbfedc9707bfb92e1,0xbffaa25fcf9dd6da,1 +np.float64,0x3fe936d1a6726da4,0x3ff10e40c2d94bc9,1 +np.float64,0x3fdb64aec7b6c95c,0x3fdd473177317b3f,1 +np.float64,0xbfee4f9aaefc9f35,0xbffcdd212667003c,1 +np.float64,0x3fe3730067e6e600,0x3fe692b0a0babf5f,1 +np.float64,0xbfc257e58924afcc,0xbfc27871f8c218d7,1 +np.float64,0x3fe62db12dec5b62,0x3feb52c61b97d9f6,1 +np.float64,0xbfe3ff491367fe92,0xbfe774f1b3a96fd6,1 +np.float64,0x3fea43255274864a,0x3ff28b0c4b7b8d21,1 +np.float64,0xbfea37923c746f24,0xbff27962159f2072,1 +np.float64,0x3fcd0ac3c73a1588,0x3fcd8e6f8de41755,1 +np.float64,0xbfdccafde6b995fc,0xbfdf030fea8a0630,1 +np.float64,0x3fdba35268b746a4,0x3fdd94094f6f50c1,1 +np.float64,0x3fc68ea1d92d1d40,0x3fc6cb8d07cbb0e4,1 +np.float64,0xbfb88b1f6e311640,0xbfb89e7af4e58778,1 +np.float64,0xbfedc7cadffb8f96,0xbffa9c3766227956,1 +np.float64,0x3fe7928d3eef251a,0x3fee2dcf2ac7961b,1 +np.float64,0xbfeff42ede7fe85e,0xc00cef6b0f1e8323,1 +np.float64,0xbfebf07fa477e0ff,0xbff5893f99e15236,1 +np.float64,0x3fe3002ab9660056,0x3fe5defba550c583,1 +np.float64,0x3feb8f4307f71e86,0x3ff4c517ec8d6de9,1 +np.float64,0x3fd3c16f49a782e0,0x3fd46becaacf74da,1 +np.float64,0x3fc7613df12ec278,0x3fc7a52b2a3c3368,1 +np.float64,0xbfe33af560e675eb,0xbfe63a6528ff1587,1 +np.float64,0xbfde86495abd0c92,0xbfe09bd7ba05b461,1 +np.float64,0x3fe1e7fb4ee3cff6,0x3fe43b04311c0ab6,1 +np.float64,0xbfc528b6bd2a516c,0xbfc55ae0a0c184c8,1 +np.float64,0xbfd81025beb0204c,0xbfd94dd72d804613,1 +np.float64,0x10000000000000,0x10000000000000,1 +np.float64,0x3fc1151c47222a38,0x3fc12f5aad80a6bf,1 +np.float64,0x3feafa136775f426,0x3ff3b46854da0b3a,1 +np.float64,0x3fed2da0747a5b40,0x3ff89c85b658459e,1 +np.float64,0x3fda2a4b51b45498,0x3fdbca0d908ddbbd,1 +np.float64,0xbfd04cf518a099ea,0xbfd0aae0033b9e4c,1 +np.float64,0xbfb9065586320ca8,0xbfb91adb7e31f322,1 +np.float64,0xbfd830b428b06168,0xbfd973ca3c484d8d,1 +np.float64,0x3fc952f7ed32a5f0,0x3fc9a9994561fc1a,1 +np.float64,0xbfeb06c83c760d90,0xbff3ca77b326df20,1 +np.float64,0xbfeb1c98ac763931,0xbff3f0d0900f6149,1 +np.float64,0x3fdf061dbebe0c3c,0x3fe0eefb32b48d17,1 +np.float64,0xbf9acbaf28359760,0xbf9acd4024be9fec,1 +np.float64,0x3fec0adde2f815bc,0x3ff5c1628423794d,1 +np.float64,0xbfc4bc750d2978ec,0xbfc4eba43f590b94,1 +np.float64,0x3fdbe47878b7c8f0,0x3fdde44a2b500d73,1 +np.float64,0x3fe160d18162c1a4,0x3fe378cff08f18f0,1 +np.float64,0x3fc3b58dfd276b18,0x3fc3de01d3802de9,1 +np.float64,0x3fa860343430c060,0x3fa864ecd07ec962,1 +np.float64,0x3fcaebfb4b35d7f8,0x3fcb546512d1b4c7,1 +np.float64,0x3fe3fda558e7fb4a,0x3fe772412e5776de,1 +np.float64,0xbfe8169f2c702d3e,0xbfef5666c9a10f6d,1 +np.float64,0x3feda78e9efb4f1e,0x3ffa270712ded769,1 +np.float64,0xbfda483161b49062,0xbfdbedfbf2e850ba,1 +np.float64,0x3fd7407cf3ae80f8,0x3fd85d4f52622743,1 +np.float64,0xbfd63de4d4ac7bca,0xbfd73550a33e3c32,1 +np.float64,0xbfd9c30b90b38618,0xbfdb4e7695c856f3,1 +np.float64,0x3fcd70c00b3ae180,0x3fcdfa0969e0a119,1 +np.float64,0x3feb4f127f769e24,0x3ff44bf42514e0f4,1 +np.float64,0xbfec1db44af83b69,0xbff5ea54aed1f8e9,1 +np.float64,0x3fd68ff051ad1fe0,0x3fd792d0ed6d6122,1 +np.float64,0x3fe0a048a5614092,0x3fe26c80a826b2a2,1 +np.float64,0x3fd59f3742ab3e70,0x3fd6818563fcaf80,1 +np.float64,0x3fca26ecf9344dd8,0x3fca867ceb5d7ba8,1 +np.float64,0x3fdc1d547ab83aa8,0x3fde2a9cea866484,1 +np.float64,0xbfc78df6312f1bec,0xbfc7d3719b698a39,1 +np.float64,0x3fe754e72b6ea9ce,0x3feda89ea844a2e5,1 +np.float64,0x3fe740c1a4ee8184,0x3fed7dc56ec0c425,1 +np.float64,0x3fe77566a9eeeace,0x3fedee6f408df6de,1 +np.float64,0xbfbbf5bf8e37eb80,0xbfbc126a223781b4,1 +np.float64,0xbfe0acb297615965,0xbfe27d86681ca2b5,1 +np.float64,0xbfc20a0487241408,0xbfc228f5f7d52ce8,1 +np.float64,0xfff0000000000000,0xfff8000000000000,1 +np.float64,0x3fef98a4dbff314a,0x40043cfb60bd46fa,1 +np.float64,0x3fd059102ca0b220,0x3fd0b7d2be6d7822,1 +np.float64,0x3fe89f18a1f13e32,0x3ff04d714bbbf400,1 +np.float64,0x3fd45b6275a8b6c4,0x3fd516a44a276a4b,1 +np.float64,0xbfe04463e86088c8,0xbfe1ef9dfc9f9a53,1 +np.float64,0xbfe086e279610dc5,0xbfe249c9c1040a13,1 +np.float64,0x3f89c9b110339380,0x3f89ca0a641454b5,1 +np.float64,0xbfb5f5b4322beb68,0xbfb6038dc3fd1516,1 +np.float64,0x3fe6eae76f6dd5ce,0x3feccabae04d5c14,1 +np.float64,0x3fa9ef6c9c33dee0,0x3fa9f51c9a8c8a2f,1 +np.float64,0xbfe171b45f62e368,0xbfe390ccc4c01bf6,1 +np.float64,0x3fb2999442253330,0x3fb2a1fc006804b5,1 +np.float64,0x3fd124bf04a24980,0x3fd1927abb92472d,1 +np.float64,0xbfe6e05938edc0b2,0xbfecb519ba78114f,1 +np.float64,0x3fed466ee6fa8cde,0x3ff8e75405b50490,1 +np.float64,0xbfb999aa92333358,0xbfb9afa4f19f80a2,1 +np.float64,0xbfe98969ed7312d4,0xbff17d887b0303e7,1 +np.float64,0x3fe782843e6f0508,0x3fee0adbeebe3486,1 +np.float64,0xbfe232fcc26465fa,0xbfe4a90a68d46040,1 +np.float64,0x3fd190a90fa32154,0x3fd206f56ffcdca2,1 +np.float64,0xbfc4f8b75929f170,0xbfc5298b2d4e7740,1 +np.float64,0xbfba3a63d63474c8,0xbfba520835c2fdc2,1 +np.float64,0xbfb7708eea2ee120,0xbfb781695ec17846,1 +np.float64,0x3fed9fb7a5fb3f70,0x3ffa0b717bcd1609,1 +np.float64,0xbfc1b158cd2362b0,0xbfc1ce87345f3473,1 +np.float64,0x3f963478082c6900,0x3f96355c3000953b,1 +np.float64,0x3fc5050e532a0a20,0x3fc536397f38f616,1 +np.float64,0x3fe239f9eee473f4,0x3fe4b360da3b2faa,1 +np.float64,0xbfd66bd80eacd7b0,0xbfd769a29fd784c0,1 +np.float64,0x3fc57cdad52af9b8,0x3fc5b16b937f5f72,1 +np.float64,0xbfd3c36a0aa786d4,0xbfd46e1cd0b4eddc,1 +np.float64,0x3feff433487fe866,0x400cf0ea1def3161,1 +np.float64,0xbfed5577807aaaef,0xbff915e8f6bfdf22,1 +np.float64,0xbfca0dd3eb341ba8,0xbfca6c4d11836cb6,1 +np.float64,0x7ff8000000000000,0x7ff8000000000000,1 +np.float64,0xbf974deaa82e9be0,0xbf974ef26a3130d1,1 +np.float64,0xbfe7f425e1efe84c,0xbfef076cb00d649d,1 +np.float64,0xbfe4413605e8826c,0xbfe7e20448b8a4b1,1 +np.float64,0xbfdfad202cbf5a40,0xbfe15cd9eb2be707,1 +np.float64,0xbfe43261ee6864c4,0xbfe7c952c951fe33,1 +np.float64,0xbfec141225782824,0xbff5d54d33861d98,1 +np.float64,0x3fd0f47abaa1e8f4,0x3fd15e8691a7f1c2,1 +np.float64,0x3fd378f0baa6f1e0,0x3fd41bea4a599081,1 +np.float64,0xbfb52523462a4a48,0xbfb5317fa7f436e2,1 +np.float64,0x3fcb30797d3660f0,0x3fcb9c174ea401ff,1 +np.float64,0xbfd48480dea90902,0xbfd5446e02c8b329,1 +np.float64,0xbfee4ae3ab7c95c7,0xbffcc650340ba274,1 +np.float64,0xbfeab086d075610e,0xbff3387f4e83ae26,1 +np.float64,0x3fa17cddf422f9c0,0x3fa17e9bf1b25736,1 +np.float64,0xbfe3064536e60c8a,0xbfe5e86aa5244319,1 +np.float64,0x3feb2882c5765106,0x3ff40604c7d97d44,1 +np.float64,0xbfa6923ff42d2480,0xbfa695ff57b2fc3f,1 +np.float64,0xbfa8bdbdcc317b80,0xbfa8c2ada0d94aa7,1 +np.float64,0x3fe7f16b8e6fe2d8,0x3fef013948c391a6,1 +np.float64,0x3fe4e7169f69ce2e,0x3fe8fceef835050a,1 +np.float64,0x3fed877638fb0eec,0x3ff9b83694127959,1 +np.float64,0xbfe0cc9ecf61993e,0xbfe2a978234cbde5,1 +np.float64,0xbfe977e79672efcf,0xbff16589ea494a38,1 +np.float64,0xbfe240130ae48026,0xbfe4bc69113e0d7f,1 +np.float64,0x3feb1e9b70763d36,0x3ff3f4615938a491,1 +np.float64,0xbfdf197dfcbe32fc,0xbfe0fba78a0fc816,1 +np.float64,0xbfee0f8543fc1f0a,0xbffbb9d9a4ee5387,1 +np.float64,0x3fe88d2191f11a44,0x3ff037843b5b6313,1 +np.float64,0xbfd11bb850a23770,0xbfd188c1cef40007,1 +np.float64,0xbfa1b36e9c2366e0,0xbfa1b53d1d8a8bc4,1 +np.float64,0xbfea2d70d9f45ae2,0xbff26a0629e36b3e,1 +np.float64,0xbfd9188703b2310e,0xbfda83f9ddc18348,1 +np.float64,0xbfee194894fc3291,0xbffbe3c83b61e7cb,1 +np.float64,0xbfe093b4a9e1276a,0xbfe25b4ad6f8f83d,1 +np.float64,0x3fea031489f4062a,0x3ff22accc000082e,1 +np.float64,0xbfc6c0827b2d8104,0xbfc6ff0a94326381,1 +np.float64,0x3fef5cd340feb9a6,0x4002659c5a1b34af,1 +np.float64,0x8010000000000000,0x8010000000000000,1 +np.float64,0x3fd97cb533b2f96c,0x3fdafab28aaae8e3,1 +np.float64,0x3fe2123334642466,0x3fe478bd83a8ce02,1 +np.float64,0xbfd9a69637b34d2c,0xbfdb2c87c6b6fb8c,1 +np.float64,0x3fc58def7f2b1be0,0x3fc5c2ff724a9f61,1 +np.float64,0xbfedd5da1f7babb4,0xbffad15949b7fb22,1 +np.float64,0x3fe90e92a0721d26,0x3ff0d9b64323efb8,1 +np.float64,0x3fd34b9442a69728,0x3fd3e9f8fe80654e,1 +np.float64,0xbfc5f509ab2bea14,0xbfc62d2ad325c59f,1 +np.float64,0x3feb245634f648ac,0x3ff3fe91a46acbe1,1 +np.float64,0x3fd101e539a203cc,0x3fd16cf52ae6d203,1 +np.float64,0xbfc51e9ba72a3d38,0xbfc5507d00521ba3,1 +np.float64,0x3fe5fe1683ebfc2e,0x3feaf7dd8b1f92b0,1 +np.float64,0x3fc362e59126c5c8,0x3fc389601814170b,1 +np.float64,0x3fea34dbd77469b8,0x3ff27542eb721e7e,1 +np.float64,0xbfc13ed241227da4,0xbfc159d42c0a35a9,1 +np.float64,0xbfe6df118cedbe23,0xbfecb27bb5d3f784,1 +np.float64,0x3fd92895f6b2512c,0x3fda96f5f94b625e,1 +np.float64,0xbfe7ea3aa76fd476,0xbfeef0e93939086e,1 +np.float64,0xbfc855498330aa94,0xbfc8a1ff690c9533,1 +np.float64,0x3fd9f27b3ab3e4f8,0x3fdb8726979afc3b,1 +np.float64,0x3fc65d52232cbaa8,0x3fc698ac4367afba,1 +np.float64,0x3fd1271dd0a24e3c,0x3fd195087649d54e,1 +np.float64,0xbfe983445df30689,0xbff175158b773b90,1 +np.float64,0xbfe0d9b13261b362,0xbfe2bb8908fc9e6e,1 +np.float64,0x3fd7671f2aaece40,0x3fd889dccbf21629,1 +np.float64,0x3fe748aebfee915e,0x3fed8e970d94c17d,1 +np.float64,0x3fea756e4e74eadc,0x3ff2d947ef3a54f4,1 +np.float64,0x3fde22311cbc4464,0x3fe05b4ce9df1fdd,1 +np.float64,0x3fe2b55ec1e56abe,0x3fe56c6849e3985a,1 +np.float64,0x3fed7b47437af68e,0x3ff98f8e82de99a0,1 +np.float64,0x3fec8184b179030a,0x3ff6d03aaf0135ba,1 +np.float64,0x3fc9ea825533d508,0x3fca4776d7190e71,1 +np.float64,0xbfe8ddd58b71bbab,0xbff09b770ed7bc9a,1 +np.float64,0xbfed41741bfa82e8,0xbff8d81c2a9fc615,1 +np.float64,0x3fe0a73888e14e72,0x3fe27602ad9a3726,1 +np.float64,0xbfe9d0a565f3a14b,0xbff1e1897b628f66,1 +np.float64,0x3fda12b381b42568,0x3fdbadbec22fbd5a,1 +np.float64,0x3fef0081187e0102,0x4000949eff8313c2,1 +np.float64,0x3fef6942b67ed286,0x4002b7913eb1ee76,1 +np.float64,0x3fda10f882b421f0,0x3fdbababa2d6659d,1 +np.float64,0x3fe5828971eb0512,0x3fea122b5088315a,1 +np.float64,0x3fe9d4b53ff3a96a,0x3ff1e75c148bda01,1 +np.float64,0x3fe95d246bf2ba48,0x3ff1414a61a136ec,1 +np.float64,0x3f9e575eb83caec0,0x3f9e59a4f17179e3,1 +np.float64,0x3fdb0a20b5b61440,0x3fdcd8a56178a17f,1 +np.float64,0xbfdef425e3bde84c,0xbfe0e33eeacf3861,1 +np.float64,0x3fd6afcf6bad5fa0,0x3fd7b73d47288347,1 +np.float64,0x3fe89256367124ac,0x3ff03dd9f36ce40e,1 +np.float64,0x3fe7e560fcefcac2,0x3feee5ef8688b60b,1 +np.float64,0x3fedef55e1fbdeac,0x3ffb350ee1df986b,1 +np.float64,0xbfe44b926de89725,0xbfe7f3539910c41f,1 +np.float64,0x3fc58310f32b0620,0x3fc5b7cfdba15bd0,1 +np.float64,0x3f736d256026da00,0x3f736d2eebe91a90,1 +np.float64,0x3feb012d2076025a,0x3ff3c0b5d21a7259,1 +np.float64,0xbfe466a6c468cd4e,0xbfe820c9c197601f,1 +np.float64,0x3fe1aba8aa635752,0x3fe3e3b73920f64c,1 +np.float64,0x3fe5597c336ab2f8,0x3fe9c7bc4b765b15,1 +np.float64,0x3fe1004ac5e20096,0x3fe2f12116e99821,1 +np.float64,0x3fecbc67477978ce,0x3ff76377434dbdad,1 +np.float64,0x3fe0e64515e1cc8a,0x3fe2ccf5447c1579,1 +np.float64,0x3febcfa874f79f50,0x3ff54528f0822144,1 +np.float64,0x3fc36915ed26d228,0x3fc38fb5b28d3f72,1 +np.float64,0xbfe01213e5e02428,0xbfe1ac0e1e7418f1,1 +np.float64,0x3fcd97875b3b2f10,0x3fce22fe3fc98702,1 +np.float64,0xbfe30383c5e60708,0xbfe5e427e62cc957,1 +np.float64,0xbfde339bf9bc6738,0xbfe0667f337924f5,1 +np.float64,0xbfda7c1c49b4f838,0xbfdc2c8801ce654a,1 +np.float64,0x3fb6b3489e2d6690,0x3fb6c29650387b92,1 +np.float64,0xbfe1fd4d76e3fa9b,0xbfe45a1f60077678,1 +np.float64,0xbf67c5e0402f8c00,0xbf67c5e49fce115a,1 +np.float64,0xbfd4f9aa2da9f354,0xbfd5c759603d0b9b,1 +np.float64,0x3fe83c227bf07844,0x3fefada9f1bd7fa9,1 +np.float64,0xbf97f717982fee20,0xbf97f836701a8cd5,1 +np.float64,0x3fe9688a2472d114,0x3ff150aa575e7d51,1 +np.float64,0xbfc5a9779d2b52f0,0xbfc5df56509c48b1,1 +np.float64,0xbfe958d5f472b1ac,0xbff13b813f9bee20,1 +np.float64,0xbfd7b3b944af6772,0xbfd8e276c2b2920f,1 +np.float64,0x3fed10198e7a2034,0x3ff8469c817572f0,1 +np.float64,0xbfeeecc4517dd989,0xc000472b1f858be3,1 +np.float64,0xbfdbcce47eb799c8,0xbfddc734aa67812b,1 +np.float64,0xbfd013ee24a027dc,0xbfd06df3089384ca,1 +np.float64,0xbfd215f2bfa42be6,0xbfd29774ffe26a74,1 +np.float64,0x3fdfd0ae67bfa15c,0x3fe1746e3a963a9f,1 +np.float64,0xbfc84aa10b309544,0xbfc896f0d25b723a,1 +np.float64,0xbfcd0c627d3a18c4,0xbfcd9024c73747a9,1 +np.float64,0x3fd87df6dbb0fbec,0x3fd9ce1dde757f31,1 +np.float64,0xbfdad85e05b5b0bc,0xbfdc9c2addb6ce47,1 +np.float64,0xbfee4f8977fc9f13,0xbffcdccd68e514b3,1 +np.float64,0x3fa5c290542b8520,0x3fa5c5ebdf09ca70,1 +np.float64,0xbfd7e401d2afc804,0xbfd91a7e4eb5a026,1 +np.float64,0xbfe33ff73b667fee,0xbfe6423cc6eb07d7,1 +np.float64,0x3fdfb7d6c4bf6fac,0x3fe163f2e8175177,1 +np.float64,0xbfd515d69eaa2bae,0xbfd5e6eedd6a1598,1 +np.float64,0x3fb322232e264440,0x3fb32b49d91c3cbe,1 +np.float64,0xbfe20ac39e641587,0xbfe46dd4b3803f19,1 +np.float64,0x3fe282dc18e505b8,0x3fe520152120c297,1 +np.float64,0xbfc905a4cd320b48,0xbfc95929b74865fb,1 +np.float64,0x3fe0ae3b83615c78,0x3fe27fa1dafc825b,1 +np.float64,0xbfc1bfed0f237fdc,0xbfc1dd6466225cdf,1 +np.float64,0xbfeca4d47d7949a9,0xbff72761a34fb682,1 +np.float64,0xbfe8cf8c48f19f18,0xbff0897ebc003626,1 +np.float64,0xbfe1aaf0a36355e2,0xbfe3e2ae7b17a286,1 +np.float64,0x3fe2ca442e659488,0x3fe58c3a2fb4f14a,1 +np.float64,0xbfda3c2deeb4785c,0xbfdbdf89fe96a243,1 +np.float64,0xbfdc12bfecb82580,0xbfde1d81dea3c221,1 +np.float64,0xbfe2d6d877e5adb1,0xbfe59f73e22c1fc7,1 +np.float64,0x3fe5f930636bf260,0x3feaee96a462e4de,1 +np.float64,0x3fcf3c0ea53e7820,0x3fcfe0b0f92be7e9,1 +np.float64,0xbfa5bb90f42b7720,0xbfa5bee9424004cc,1 +np.float64,0xbfe2fb3a3265f674,0xbfe5d75b988bb279,1 +np.float64,0x3fcaec7aab35d8f8,0x3fcb54ea582fff6f,1 +np.float64,0xbfd8d3228db1a646,0xbfda322297747fbc,1 +np.float64,0x3fedd2e0ad7ba5c2,0x3ffac6002b65c424,1 +np.float64,0xbfd9edeca2b3dbda,0xbfdb81b2b7785e33,1 +np.float64,0xbfef5febb17ebfd7,0xc002796b15950960,1 +np.float64,0x3fde22f787bc45f0,0x3fe05bcc624b9ba2,1 +np.float64,0xbfc716a4ab2e2d48,0xbfc758073839dd44,1 +np.float64,0xbf9bed852837db00,0xbf9bef4b2a3f3bdc,1 +np.float64,0x3fef8f88507f1f10,0x4003e5e566444571,1 +np.float64,0xbfdc1bbed6b8377e,0xbfde28a64e174e60,1 +np.float64,0x3fe02d30eae05a62,0x3fe1d064ec027cd3,1 +np.float64,0x3fd9dbb500b3b76c,0x3fdb6bea40162279,1 +np.float64,0x3fe353ff1d66a7fe,0x3fe661b3358c925e,1 +np.float64,0x3fac3ebfb4387d80,0x3fac4618effff2b0,1 +np.float64,0x3fe63cf0ba6c79e2,0x3feb7030cff5f434,1 +np.float64,0x3fd0e915f8a1d22c,0x3fd152464597b510,1 +np.float64,0xbfd36987cda6d310,0xbfd40af049d7621e,1 +np.float64,0xbfdc5b4dc7b8b69c,0xbfde7790a35da2bc,1 +np.float64,0x3feee7ff4a7dcffe,0x40003545989e07c7,1 +np.float64,0xbfeb2c8308765906,0xbff40d2e6469249e,1 +np.float64,0x3fe535a894ea6b52,0x3fe98781648550d0,1 +np.float64,0xbfef168eb9fe2d1d,0xc000f274ed3cd312,1 +np.float64,0x3fc3e2d98927c5b0,0x3fc40c6991b8900c,1 +np.float64,0xbfcd8fe3e73b1fc8,0xbfce1aec7f9b7f7d,1 +np.float64,0xbfd55d8c3aaabb18,0xbfd6378132ee4892,1 +np.float64,0xbfe424a66168494d,0xbfe7b289d72c98b3,1 +np.float64,0x3fd81af13eb035e4,0x3fd95a6a9696ab45,1 +np.float64,0xbfe3016722e602ce,0xbfe5e0e46db228cd,1 +np.float64,0x3fe9a20beff34418,0x3ff19faca17fc468,1 +np.float64,0xbfe2124bc7e42498,0xbfe478e19927e723,1 +np.float64,0x3fd96f8622b2df0c,0x3fdaeb08da6b08ae,1 +np.float64,0x3fecd6796579acf2,0x3ff7a7d02159e181,1 +np.float64,0x3fe60015df6c002c,0x3feafba6f2682a61,1 +np.float64,0x3fc7181cf72e3038,0x3fc7598c2cc3c3b4,1 +np.float64,0xbfce6e2e0b3cdc5c,0xbfcf0621b3e37115,1 +np.float64,0xbfe52a829e6a5505,0xbfe973a785980af9,1 +np.float64,0x3fed4bbac37a9776,0x3ff8f7a0e68a2bbe,1 +np.float64,0x3fabdfaacc37bf60,0x3fabe6bab42bd246,1 +np.float64,0xbfcd9598cb3b2b30,0xbfce20f3c4c2c261,1 +np.float64,0x3fd717d859ae2fb0,0x3fd82e88eca09ab1,1 +np.float64,0x3fe28ccb18e51996,0x3fe52f071d2694fd,1 +np.float64,0xbfe43f064ae87e0c,0xbfe7de5eab36b5b9,1 +np.float64,0x7fefffffffffffff,0xfff8000000000000,1 +np.float64,0xbfb39b045a273608,0xbfb3a4dd3395fdd5,1 +np.float64,0xbfb3358bae266b18,0xbfb33ece5e95970a,1 +np.float64,0xbfeeafb6717d5f6d,0xbffeec3f9695b575,1 +np.float64,0xbfe7a321afef4644,0xbfee522dd80f41f4,1 +np.float64,0x3fe3a17e5be742fc,0x3fe6dcd32af51e92,1 +np.float64,0xbfc61694bd2c2d28,0xbfc64fbbd835f6e7,1 +np.float64,0xbfd795906faf2b20,0xbfd8bf89b370655c,1 +np.float64,0xbfe4b39b59e96736,0xbfe8a3c5c645b6e3,1 +np.float64,0x3fd310af3ba62160,0x3fd3a9442e825e1c,1 +np.float64,0xbfd45198a6a8a332,0xbfd50bc10311a0a3,1 +np.float64,0x3fd0017eaaa002fc,0x3fd05a472a837999,1 +np.float64,0xbfea974d98752e9b,0xbff30f67f1835183,1 +np.float64,0xbf978f60582f1ec0,0xbf979070e1c2b59d,1 +np.float64,0x3fe1c715d4e38e2c,0x3fe40b479e1241a2,1 +np.float64,0xbfccb965cd3972cc,0xbfcd38b40c4a352d,1 +np.float64,0xbfd9897048b312e0,0xbfdb09d55624c2a3,1 +np.float64,0x3fe7f5de4befebbc,0x3fef0b56be259f9c,1 +np.float64,0x3fcc6c6d4338d8d8,0x3fcce7b20ed68a78,1 +np.float64,0xbfe63884046c7108,0xbfeb67a3b945c3ee,1 +np.float64,0xbfce64e2ad3cc9c4,0xbfcefc47fae2e81f,1 +np.float64,0x3fefeb57b27fd6b0,0x400ab2eac6321cfb,1 +np.float64,0x3fe679627e6cf2c4,0x3febe6451b6ee0c4,1 +np.float64,0x3fc5f710172bee20,0x3fc62f40f85cb040,1 +np.float64,0x3fc34975e52692e8,0x3fc36f58588c7fa2,1 +np.float64,0x3fe8a3784cf146f0,0x3ff052ced9bb9406,1 +np.float64,0x3fd11a607ca234c0,0x3fd1874f876233fe,1 +np.float64,0x3fb2d653f625aca0,0x3fb2df0f4c9633f3,1 +np.float64,0x3fe555f39eeaabe8,0x3fe9c15ee962a28c,1 +np.float64,0xbfea297e3bf452fc,0xbff264107117f709,1 +np.float64,0x3fe1581cdde2b03a,0x3fe36c79acedf99c,1 +np.float64,0x3fd4567063a8ace0,0x3fd51123dbd9106f,1 +np.float64,0x3fa3883aec271080,0x3fa38aa86ec71218,1 +np.float64,0x3fe40e5d7de81cba,0x3fe78dbb9b568850,1 +np.float64,0xbfe9a2f7347345ee,0xbff1a0f4faa05041,1 +np.float64,0x3f9eef03a83dde00,0x3f9ef16caa0c1478,1 +np.float64,0xbfcb4641d1368c84,0xbfcbb2e7ff8c266d,1 +np.float64,0xbfa8403b2c308070,0xbfa844e148b735b7,1 +np.float64,0xbfe1875cd6e30eba,0xbfe3afadc08369f5,1 +np.float64,0xbfdd3c3d26ba787a,0xbfdf919b3e296766,1 +np.float64,0x3fcd6c4c853ad898,0x3fcdf55647b518b8,1 +np.float64,0xbfe360a173e6c143,0xbfe6759eb3a08cf2,1 +np.float64,0x3fe5a13147eb4262,0x3fea4a5a060f5adb,1 +np.float64,0x3feb3cdd7af679ba,0x3ff42aae0cf61234,1 +np.float64,0x3fe5205128ea40a2,0x3fe9618f3d0c54af,1 +np.float64,0x3fce35343f3c6a68,0x3fcec9c4e612b050,1 +np.float64,0xbfc345724d268ae4,0xbfc36b3ce6338e6a,1 +np.float64,0x3fedc4fc0e7b89f8,0x3ffa91c1d775c1f7,1 +np.float64,0x3fe41fbf21683f7e,0x3fe7aa6c174a0e65,1 +np.float64,0xbfc7a1a5d32f434c,0xbfc7e7d27a4c5241,1 +np.float64,0x3fd3e33eaca7c67c,0x3fd4915264441e2f,1 +np.float64,0x3feb3f02f6f67e06,0x3ff42e942249e596,1 +np.float64,0x3fdb75fcb0b6ebf8,0x3fdd5c63f98b6275,1 +np.float64,0x3fd6476603ac8ecc,0x3fd74020b164cf38,1 +np.float64,0x3fed535372faa6a6,0x3ff90f3791821841,1 +np.float64,0x3fe8648ead70c91e,0x3ff006a62befd7ed,1 +np.float64,0x3fd0f90760a1f210,0x3fd1636b39bb1525,1 +np.float64,0xbfca052443340a48,0xbfca633d6e777ae0,1 +np.float64,0xbfa6a5e3342d4bc0,0xbfa6a9ac6a488f5f,1 +np.float64,0x3fd5598038aab300,0x3fd632f35c0c3d52,1 +np.float64,0xbfdf66218fbecc44,0xbfe12df83b19f300,1 +np.float64,0x3fe78e15b56f1c2c,0x3fee240d12489cd1,1 +np.float64,0x3fe3d6a7b3e7ad50,0x3fe7329dcf7401e2,1 +np.float64,0xbfddb8e97bbb71d2,0xbfe017ed6d55a673,1 +np.float64,0xbfd57afd55aaf5fa,0xbfd658a9607c3370,1 +np.float64,0xbfdba4c9abb74994,0xbfdd95d69e5e8814,1 +np.float64,0xbfe71d8090ee3b01,0xbfed3390be6d2eef,1 +np.float64,0xbfc738ac0f2e7158,0xbfc77b3553b7c026,1 +np.float64,0x3f873656302e6c80,0x3f873697556ae011,1 +np.float64,0x3fe559491d6ab292,0x3fe9c7603b12c608,1 +np.float64,0xbfe262776864c4ef,0xbfe4ef905dda8599,1 +np.float64,0x3fe59d8917eb3b12,0x3fea439f44b7573f,1 +np.float64,0xbfd4b5afb5a96b60,0xbfd57b4e3df4dbc8,1 +np.float64,0x3fe81158447022b0,0x3fef4a3cea3eb6a9,1 +np.float64,0xbfeb023441f60468,0xbff3c27f0fc1a4dc,1 +np.float64,0x3fefb212eaff6426,0x40055fc6d949cf44,1 +np.float64,0xbfe1300ac1e26016,0xbfe333f297a1260e,1 +np.float64,0xbfeae0a2f575c146,0xbff388d58c380b8c,1 +np.float64,0xbfeddd8e55fbbb1d,0xbffaef045b2e21d9,1 +np.float64,0x3fec7c6c1d78f8d8,0x3ff6c3ebb019a8e5,1 +np.float64,0xbfe27e071f64fc0e,0xbfe518d2ff630f33,1 +np.float64,0x8000000000000001,0x8000000000000001,1 +np.float64,0x3fc5872abf2b0e58,0x3fc5bc083105db76,1 +np.float64,0x3fe65114baeca22a,0x3feb9745b82ef15a,1 +np.float64,0xbfc783abe52f0758,0xbfc7c8cb23f93e79,1 +np.float64,0x3fe4b7a5dd696f4c,0x3fe8aab9d492f0ca,1 +np.float64,0xbf91a8e8a82351e0,0xbf91a95b6ae806f1,1 +np.float64,0xbfee482eb77c905d,0xbffcb952830e715a,1 +np.float64,0x3fba0eee2a341de0,0x3fba261d495e3a1b,1 +np.float64,0xbfeb8876ae7710ed,0xbff4b7f7f4343506,1 +np.float64,0xbfe4d29e46e9a53c,0xbfe8d9547a601ba7,1 +np.float64,0xbfe12413b8e24828,0xbfe3232656541d10,1 +np.float64,0x3fc0bd8f61217b20,0x3fc0d63f937f0aa4,1 +np.float64,0xbfd3debafda7bd76,0xbfd48c534e5329e4,1 +np.float64,0x3fc0f92de921f258,0x3fc112eb7d47349b,1 +np.float64,0xbfe576b95f6aed72,0xbfe9fca859239b3c,1 +np.float64,0x3fd10e520da21ca4,0x3fd17a546e4152f7,1 +np.float64,0x3fcef917eb3df230,0x3fcf998677a8fa8f,1 +np.float64,0x3fdfcf863abf9f0c,0x3fe173a98af1cb13,1 +np.float64,0x3fc28c4b4f251898,0x3fc2adf43792e917,1 +np.float64,0x3fceb837ad3d7070,0x3fcf54a63b7d8c5c,1 +np.float64,0x3fc0140a05202818,0x3fc029e4f75330cb,1 +np.float64,0xbfd76c3362aed866,0xbfd88fb9e790b4e8,1 +np.float64,0xbfe475300868ea60,0xbfe8395334623e1f,1 +np.float64,0x3fea70b9b4f4e174,0x3ff2d1dad92173ba,1 +np.float64,0xbfe2edbd4965db7a,0xbfe5c29449a9365d,1 +np.float64,0xbfddf86f66bbf0de,0xbfe0408439cada9b,1 +np.float64,0xbfb443cdfa288798,0xbfb44eae796ad3ea,1 +np.float64,0xbf96a8a0482d5140,0xbf96a992b6ef073b,1 +np.float64,0xbfd279db2fa4f3b6,0xbfd3043db6acbd9e,1 +np.float64,0x3fe5d99088ebb322,0x3feab30be14e1605,1 +np.float64,0xbfe1a917abe35230,0xbfe3e0063d0f5f63,1 +np.float64,0x3fc77272f52ee4e8,0x3fc7b6f8ab6f4591,1 +np.float64,0x3fd6b62146ad6c44,0x3fd7be77eef8390a,1 +np.float64,0xbfe39fd9bc673fb4,0xbfe6da30dc4eadde,1 +np.float64,0x3fe35545c066aa8c,0x3fe663b5873e4d4b,1 +np.float64,0xbfcbbeffb3377e00,0xbfcc317edf7f6992,1 +np.float64,0xbfe28a58366514b0,0xbfe52b5734579ffa,1 +np.float64,0xbfbf0c87023e1910,0xbfbf33d970a0dfa5,1 +np.float64,0xbfd31144cba6228a,0xbfd3a9e84f9168f9,1 +np.float64,0xbfe5c044056b8088,0xbfea83d607c1a88a,1 +np.float64,0x3fdaabdf18b557c0,0x3fdc663ee8eddc83,1 +np.float64,0xbfeb883006f71060,0xbff4b76feff615be,1 +np.float64,0xbfebaef41d775de8,0xbff5034111440754,1 +np.float64,0x3fd9b6eb3bb36dd8,0x3fdb3fff5071dacf,1 +np.float64,0x3fe4e33c45e9c678,0x3fe8f637779ddedf,1 +np.float64,0x3fe52213a06a4428,0x3fe964adeff5c14e,1 +np.float64,0x3fe799254cef324a,0x3fee3c3ecfd3cdc5,1 +np.float64,0x3fd0533f35a0a680,0x3fd0b19a003469d3,1 +np.float64,0x3fec7ef5c7f8fdec,0x3ff6ca0abe055048,1 +np.float64,0xbfd1b5da82a36bb6,0xbfd22f357acbee79,1 +np.float64,0xbfd8f9c652b1f38c,0xbfda5faacbce9cf9,1 +np.float64,0x3fc8fc818b31f900,0x3fc94fa9a6aa53c8,1 +np.float64,0x3fcf42cc613e8598,0x3fcfe7dc128f33f2,1 +np.float64,0x3fd393a995a72754,0x3fd4396127b19305,1 +np.float64,0x3fec7b7df9f8f6fc,0x3ff6c1ae51753ef2,1 +np.float64,0x3fc07f175b20fe30,0x3fc096b55c11568c,1 +np.float64,0xbf979170082f22e0,0xbf979280d9555f44,1 +np.float64,0xbfb9d110c633a220,0xbfb9e79ba19b3c4a,1 +np.float64,0x3fedcd7d417b9afa,0x3ffab19734e86d58,1 +np.float64,0xbfec116f27f822de,0xbff5cf9425cb415b,1 +np.float64,0xbfec4fa0bef89f42,0xbff65a771982c920,1 +np.float64,0x3f94d4452829a880,0x3f94d501789ad11c,1 +np.float64,0xbfefe5ede27fcbdc,0xc009c440d3c2a4ce,1 +np.float64,0xbfe7e5f7b5efcbf0,0xbfeee74449aee1db,1 +np.float64,0xbfeb71dc8976e3b9,0xbff48cd84ea54ed2,1 +np.float64,0xbfe4cdb65f699b6c,0xbfe8d0d3bce901ef,1 +np.float64,0x3fb78ef1ee2f1de0,0x3fb7a00e7d183c48,1 +np.float64,0x3fb681864a2d0310,0x3fb6906fe64b4cd7,1 +np.float64,0xbfd2ad3b31a55a76,0xbfd33c57b5985399,1 +np.float64,0x3fdcdaaa95b9b554,0x3fdf16b99628db1e,1 +np.float64,0x3fa4780b7428f020,0x3fa47ad6ce9b8081,1 +np.float64,0x3fc546b0ad2a8d60,0x3fc579b361b3b18f,1 +np.float64,0x3feaf98dd6f5f31c,0x3ff3b38189c3539c,1 +np.float64,0x3feb0b2eca76165e,0x3ff3d22797083f9a,1 +np.float64,0xbfdc02ae3ab8055c,0xbfde099ecb5dbacf,1 +np.float64,0x3fd248bf17a49180,0x3fd2ceb77b346d1d,1 +np.float64,0x3fe349d666e693ac,0x3fe651b9933a8853,1 +np.float64,0xbfca526fc534a4e0,0xbfcab3e83f0d9b93,1 +np.float64,0x3fc156421722ac88,0x3fc171b38826563b,1 +np.float64,0xbfe4244569e8488b,0xbfe7b1e93e7d4f92,1 +np.float64,0x3fe010faabe021f6,0x3fe1aa961338886d,1 +np.float64,0xbfc52dacb72a5b58,0xbfc55ffa50eba380,1 +np.float64,0x8000000000000000,0x8000000000000000,1 +np.float64,0x3fea1d4865f43a90,0x3ff251b839eb4817,1 +np.float64,0xbfa0f65c8421ecc0,0xbfa0f7f37c91be01,1 +np.float64,0x3fcab29c0b356538,0x3fcb1863edbee184,1 +np.float64,0x3fe7949162ef2922,0x3fee323821958b88,1 +np.float64,0x3fdaf9288ab5f250,0x3fdcc400190a4839,1 +np.float64,0xbfe13ece6be27d9d,0xbfe348ba07553179,1 +np.float64,0x3f8a0c4fd0341880,0x3f8a0cabdf710185,1 +np.float64,0x3fdd0442a2ba0884,0x3fdf4b016c4da452,1 +np.float64,0xbfaf06d2343e0da0,0xbfaf1090b1600422,1 +np.float64,0xbfd3b65225a76ca4,0xbfd45fa49ae76cca,1 +np.float64,0x3fef5d75fefebaec,0x400269a5e7c11891,1 +np.float64,0xbfe048e35ce091c6,0xbfe1f5af45dd64f8,1 +np.float64,0xbfe27d4599e4fa8b,0xbfe517b07843d04c,1 +np.float64,0xbfe6f2a637ede54c,0xbfecdaa730462576,1 +np.float64,0x3fc63fbb752c7f78,0x3fc67a2854974109,1 +np.float64,0x3fedda6bfbfbb4d8,0x3ffae2e6131f3475,1 +np.float64,0x3fe7a6f5286f4dea,0x3fee5a9b1ef46016,1 +np.float64,0xbfd4ea8bcea9d518,0xbfd5b66ab7e5cf00,1 +np.float64,0x3fdc116568b822cc,0x3fde1bd4d0d9fd6c,1 +np.float64,0x3fdc45cb1bb88b98,0x3fde5cd1d2751032,1 +np.float64,0x3feabd932f757b26,0x3ff34e06e56a62a1,1 +np.float64,0xbfae5dbe0c3cbb80,0xbfae66e062ac0d65,1 +np.float64,0xbfdb385a00b670b4,0xbfdd10fedf3a58a7,1 +np.float64,0xbfebb14755f7628f,0xbff507e123a2b47c,1 +np.float64,0x3fe6de2fdfedbc60,0x3fecb0ae6e131da2,1 +np.float64,0xbfd86de640b0dbcc,0xbfd9bb4dbf0bf6af,1 +np.float64,0x3fe39e86d9e73d0e,0x3fe6d811c858d5d9,1 +np.float64,0x7ff0000000000000,0xfff8000000000000,1 +np.float64,0x3fa8101684302020,0x3fa814a12176e937,1 +np.float64,0x3fefdd5ad37fbab6,0x4008a08c0b76fbb5,1 +np.float64,0x3fe645c727ec8b8e,0x3feb814ebc470940,1 +np.float64,0x3fe3ba79dce774f4,0x3fe70500db564cb6,1 +np.float64,0xbfe0e5a254e1cb44,0xbfe2cc13940c6d9a,1 +np.float64,0x3fe2cac62465958c,0x3fe58d008c5e31f8,1 +np.float64,0xbfd3ffb531a7ff6a,0xbfd4b0d88cff2040,1 +np.float64,0x3fe0929104612522,0x3fe259bc42dce788,1 +np.float64,0x1,0x1,1 +np.float64,0xbfe7db77e6efb6f0,0xbfeecf93e8a61cb3,1 +np.float64,0xbfe37e9559e6fd2a,0xbfe6a514e29cb7aa,1 +np.float64,0xbfc53a843f2a7508,0xbfc56d2e9ad8b716,1 +np.float64,0xbfedb04485fb6089,0xbffa4615d4334ec3,1 +np.float64,0xbfc44349b1288694,0xbfc46f484b6f1cd6,1 +np.float64,0xbfe265188264ca31,0xbfe4f37d61cd9e17,1 +np.float64,0xbfd030351da0606a,0xbfd08c2537287ee1,1 +np.float64,0x3fd8fb131db1f628,0x3fda613363ca601e,1 +np.float64,0xbff0000000000000,0xfff0000000000000,1 +np.float64,0xbfe48d9a60691b35,0xbfe862c02d8fec1e,1 +np.float64,0x3fd185e050a30bc0,0x3fd1fb4c614ddb07,1 +np.float64,0xbfe4a5807e694b01,0xbfe88b8ff2d6caa7,1 +np.float64,0xbfc934d7ad3269b0,0xbfc98a405d25a666,1 +np.float64,0xbfea0e3c62741c79,0xbff23b4bd3a7b15d,1 +np.float64,0x3fe7244071ee4880,0x3fed41b27ba6bb22,1 +np.float64,0xbfd419f81ba833f0,0xbfd4cdf71b4533a3,1 +np.float64,0xbfe1e73a34e3ce74,0xbfe439eb15fa6baf,1 +np.float64,0x3fcdd9a63f3bb350,0x3fce68e1c401eff0,1 +np.float64,0x3fd1b5960ba36b2c,0x3fd22eeb566f1976,1 +np.float64,0x3fe9ad18e0735a32,0x3ff1af23c534260d,1 +np.float64,0xbfd537918aaa6f24,0xbfd60ccc8df0962b,1 +np.float64,0x3fcba3d3c73747a8,0x3fcc14fd5e5c49ad,1 +np.float64,0x3fd367e3c0a6cfc8,0x3fd40921b14e288e,1 +np.float64,0x3fe94303c6f28608,0x3ff11e62db2db6ac,1 +np.float64,0xbfcc5f77fd38bef0,0xbfccda110c087519,1 +np.float64,0xbfd63b74d7ac76ea,0xbfd7328af9f37402,1 +np.float64,0xbfe5321289ea6425,0xbfe9811ce96609ad,1 +np.float64,0xbfde910879bd2210,0xbfe0a2cd0ed1d368,1 +np.float64,0xbfcc9d9bad393b38,0xbfcd1b722a0b1371,1 +np.float64,0xbfe6dd39e16dba74,0xbfecaeb7c8c069f6,1 +np.float64,0xbfe98316eff3062e,0xbff174d7347d48bf,1 +np.float64,0xbfda88f8d1b511f2,0xbfdc3c0e75dad903,1 +np.float64,0x3fd400d8c2a801b0,0x3fd4b21bacff1f5d,1 +np.float64,0xbfe1ed335863da66,0xbfe4429e45e99779,1 +np.float64,0xbf3423a200284800,0xbf3423a20acb0342,1 +np.float64,0xbfe97bc59672f78b,0xbff16ad1adc44a33,1 +np.float64,0xbfeeca60d7fd94c2,0xbfff98d7f18f7728,1 +np.float64,0x3fd1eb13b2a3d628,0x3fd268e6ff4d56ce,1 +np.float64,0xbfa5594c242ab2a0,0xbfa55c77d6740a39,1 +np.float64,0x3fe72662006e4cc4,0x3fed462a9dedbfee,1 +np.float64,0x3fef4bb221fe9764,0x4001fe4f4cdfedb2,1 +np.float64,0xbfe938d417f271a8,0xbff110e78724ca2b,1 +np.float64,0xbfcc29ab2f385358,0xbfcca182140ef541,1 +np.float64,0x3fe18cd42c6319a8,0x3fe3b77e018165e7,1 +np.float64,0xbfec6c5cae78d8b9,0xbff69d8e01309b48,1 +np.float64,0xbfd5723da7aae47c,0xbfd64ecde17da471,1 +np.float64,0xbfe3096722e612ce,0xbfe5ed43634f37ff,1 +np.float64,0xbfdacaceb1b5959e,0xbfdc8bb826bbed39,1 +np.float64,0x3fc59a57cb2b34b0,0x3fc5cfc4a7c9bac8,1 +np.float64,0x3f84adce10295b80,0x3f84adfc1f1f6e97,1 +np.float64,0x3fdd5b28bbbab650,0x3fdfb8b906d77df4,1 +np.float64,0x3fdebf94c6bd7f28,0x3fe0c10188e1bc7c,1 +np.float64,0x3fdb30c612b6618c,0x3fdd07bf18597821,1 +np.float64,0x3fe7eeb3176fdd66,0x3feefb0be694b855,1 +np.float64,0x0,0x0,1 +np.float64,0xbfe10057e9e200b0,0xbfe2f13365e5b1c9,1 +np.float64,0xbfeb61a82376c350,0xbff46e665d3a60f5,1 +np.float64,0xbfe7f54aec6fea96,0xbfef0a0759f726dc,1 +np.float64,0xbfe4f6da3de9edb4,0xbfe9187d85bd1ab5,1 +np.float64,0xbfeb8be1b3f717c4,0xbff4be8efaab2e75,1 +np.float64,0x3fed40bc31fa8178,0x3ff8d5ec4a7f3e9b,1 +np.float64,0xbfe40f8711681f0e,0xbfe78fa5c62b191b,1 +np.float64,0x3fd1034d94a2069c,0x3fd16e78e9efb85b,1 +np.float64,0x3fc74db15b2e9b60,0x3fc790f26e894098,1 +np.float64,0x3fd912a88cb22550,0x3fda7d0ab3b21308,1 +np.float64,0x3fd8948a3bb12914,0x3fd9e8950c7874c8,1 +np.float64,0xbfa7ada5242f5b50,0xbfa7b1f8db50c104,1 +np.float64,0x3feeb2e1c27d65c4,0x3fff000b7d09c9b7,1 +np.float64,0x3fe9d46cbbf3a8da,0x3ff1e6f405265a6e,1 +np.float64,0xbfe2480b77e49017,0xbfe4c83b9b37bf0c,1 +np.float64,0x3fe950ea9372a1d6,0x3ff130e62468bf2c,1 +np.float64,0x3fefa7272a7f4e4e,0x4004d8c9bf31ab58,1 +np.float64,0xbfe7309209ee6124,0xbfed5b94acef917a,1 +np.float64,0x3fd05e8c64a0bd18,0x3fd0bdb11e0903c6,1 +np.float64,0x3fd9236043b246c0,0x3fda90ccbe4bab1e,1 +np.float64,0xbfdc3d6805b87ad0,0xbfde5266e17154c3,1 +np.float64,0x3fe5e6bad76bcd76,0x3feacbc306c63445,1 +np.float64,0x3ff0000000000000,0x7ff0000000000000,1 +np.float64,0xbfde3d7390bc7ae8,0xbfe06cd480bd0196,1 +np.float64,0xbfd3e2e3c0a7c5c8,0xbfd490edc0a45e26,1 +np.float64,0x3fe39871d76730e4,0x3fe6ce54d1719953,1 +np.float64,0x3fdff00ebcbfe01c,0x3fe1894b6655a6d0,1 +np.float64,0x3f91b7ad58236f40,0x3f91b8213bcb8b0b,1 +np.float64,0xbfd99f48f7b33e92,0xbfdb23d544f62591,1 +np.float64,0x3fae3512cc3c6a20,0x3fae3e10939fd7b5,1 +np.float64,0x3fcc4cf3db3899e8,0x3fccc698a15176d6,1 +np.float64,0xbfd0927e39a124fc,0xbfd0f5522e2bc030,1 +np.float64,0x3fcee859633dd0b0,0x3fcf87bdef7a1e82,1 +np.float64,0xbfe2a8b69565516d,0xbfe5593437b6659a,1 +np.float64,0x3fecf61e20f9ec3c,0x3ff7fda16b0209d4,1 +np.float64,0xbfbf37571e3e6eb0,0xbfbf5f4e1379a64c,1 +np.float64,0xbfd54e1b75aa9c36,0xbfd626223b68971a,1 +np.float64,0x3fe1035a56e206b4,0x3fe2f5651ca0f4b0,1 +np.float64,0x3fe4992989e93254,0x3fe876751afa70dc,1 +np.float64,0x3fc8c313d3318628,0x3fc913faf15d1562,1 +np.float64,0x3f99f6ba8833ed80,0x3f99f8274fb94828,1 +np.float64,0xbfd4a58af0a94b16,0xbfd56947c276e04f,1 +np.float64,0x3fc66f8c872cdf18,0x3fc6ab7a14372a73,1 +np.float64,0x3fc41eee0d283de0,0x3fc449ff1ff0e7a6,1 +np.float64,0x3fefd04d287fa09a,0x4007585010cfa9b0,1 +np.float64,0x3fce9e746f3d3ce8,0x3fcf39514bbe5070,1 +np.float64,0xbfe8056f72700adf,0xbfef2ee2c13e67ba,1 +np.float64,0x3fdd6b1ec0bad63c,0x3fdfccf2ba144fa8,1 +np.float64,0x3fd92ee432b25dc8,0x3fda9e6b96b2b142,1 +np.float64,0xbfc4d18f9529a320,0xbfc50150fb4de0cc,1 +np.float64,0xbfe09939a7613274,0xbfe262d703c317af,1 +np.float64,0xbfd130b132a26162,0xbfd19f5a00ae29c4,1 +np.float64,0x3fa06e21d420dc40,0x3fa06f93aba415fb,1 +np.float64,0x3fc5c48fbd2b8920,0x3fc5fb3bfad3bf55,1 +np.float64,0xbfdfa2bacbbf4576,0xbfe155f839825308,1 +np.float64,0x3fe3e1fa0f67c3f4,0x3fe745081dd4fd03,1 +np.float64,0x3fdae58289b5cb04,0x3fdcac1f6789130a,1 +np.float64,0xbf8ed3ba103da780,0xbf8ed452a9cc1442,1 +np.float64,0xbfec06b46f780d69,0xbff5b86f30d70908,1 +np.float64,0xbfe990c13b732182,0xbff187a90ae611f8,1 +np.float64,0xbfdd46c738ba8d8e,0xbfdf9eee0a113230,1 +np.float64,0x3fe08b83f3611708,0x3fe2501b1c77035c,1 +np.float64,0xbfd501b65baa036c,0xbfd5d05de3fceac8,1 +np.float64,0xbfcf4fa21f3e9f44,0xbfcff5829582c0b6,1 +np.float64,0xbfefbc0bfbff7818,0xc005eca1a2c56b38,1 +np.float64,0xbfe1ba6959e374d2,0xbfe3f8f88d128ce5,1 +np.float64,0xbfd4e74ee3a9ce9e,0xbfd5b2cabeb45e6c,1 +np.float64,0xbfe77c38eaeef872,0xbfedfd332d6f1c75,1 +np.float64,0x3fa9b5e4fc336bc0,0x3fa9bb6f6b80b4af,1 +np.float64,0xbfecba63917974c7,0xbff75e44df7f8e81,1 +np.float64,0x3fd6cf17b2ad9e30,0x3fd7db0b93b7f2b5,1 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-cos.csv b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-cos.csv new file mode 100644 index 0000000000000000000000000000000000000000..258ae48ce0209f6873101426fce439c233078c89 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-cos.csv @@ -0,0 +1,1375 @@ +dtype,input,output,ulperrortol +## +ve denormals ## +np.float32,0x004b4716,0x3f800000,2 +np.float32,0x007b2490,0x3f800000,2 +np.float32,0x007c99fa,0x3f800000,2 +np.float32,0x00734a0c,0x3f800000,2 +np.float32,0x0070de24,0x3f800000,2 +np.float32,0x007fffff,0x3f800000,2 +np.float32,0x00000001,0x3f800000,2 +## -ve denormals ## +np.float32,0x80495d65,0x3f800000,2 +np.float32,0x806894f6,0x3f800000,2 +np.float32,0x80555a76,0x3f800000,2 +np.float32,0x804e1fb8,0x3f800000,2 +np.float32,0x80687de9,0x3f800000,2 +np.float32,0x807fffff,0x3f800000,2 +np.float32,0x80000001,0x3f800000,2 +## +/-0.0f, +/-FLT_MIN +/-FLT_MAX ## +np.float32,0x00000000,0x3f800000,2 +np.float32,0x80000000,0x3f800000,2 +np.float32,0x00800000,0x3f800000,2 +np.float32,0x80800000,0x3f800000,2 +## 1.00f + 0x00000001 ## +np.float32,0x3f800000,0x3f0a5140,2 +np.float32,0x3f800001,0x3f0a513f,2 +np.float32,0x3f800002,0x3f0a513d,2 +np.float32,0xc090a8b0,0xbe4332ce,2 +np.float32,0x41ce3184,0x3f4d1de1,2 +np.float32,0xc1d85848,0xbeaa8980,2 +np.float32,0x402b8820,0xbf653aa3,2 +np.float32,0x42b4e454,0xbf4a338b,2 +np.float32,0x42a67a60,0x3c58202e,2 +np.float32,0x41d92388,0xbed987c7,2 +np.float32,0x422dd66c,0x3f5dcab3,2 +np.float32,0xc28f5be6,0xbf5688d8,2 +np.float32,0x41ab2674,0xbf53aa3b,2 +np.float32,0x3f490fdb,0x3f3504f3,2 +np.float32,0xbf490fdb,0x3f3504f3,2 +np.float32,0x3fc90fdb,0xb33bbd2e,2 +np.float32,0xbfc90fdb,0xb33bbd2e,2 +np.float32,0x40490fdb,0xbf800000,2 +np.float32,0xc0490fdb,0xbf800000,2 +np.float32,0x3fc90fdb,0xb33bbd2e,2 +np.float32,0xbfc90fdb,0xb33bbd2e,2 +np.float32,0x40490fdb,0xbf800000,2 +np.float32,0xc0490fdb,0xbf800000,2 +np.float32,0x40c90fdb,0x3f800000,2 +np.float32,0xc0c90fdb,0x3f800000,2 +np.float32,0x4016cbe4,0xbf3504f3,2 +np.float32,0xc016cbe4,0xbf3504f3,2 +np.float32,0x4096cbe4,0x324cde2e,2 +np.float32,0xc096cbe4,0x324cde2e,2 +np.float32,0x4116cbe4,0xbf800000,2 +np.float32,0xc116cbe4,0xbf800000,2 +np.float32,0x40490fdb,0xbf800000,2 +np.float32,0xc0490fdb,0xbf800000,2 +np.float32,0x40c90fdb,0x3f800000,2 +np.float32,0xc0c90fdb,0x3f800000,2 +np.float32,0x41490fdb,0x3f800000,2 +np.float32,0xc1490fdb,0x3f800000,2 +np.float32,0x407b53d2,0xbf3504f1,2 +np.float32,0xc07b53d2,0xbf3504f1,2 +np.float32,0x40fb53d2,0xb4b5563d,2 +np.float32,0xc0fb53d2,0xb4b5563d,2 +np.float32,0x417b53d2,0xbf800000,2 +np.float32,0xc17b53d2,0xbf800000,2 +np.float32,0x4096cbe4,0x324cde2e,2 +np.float32,0xc096cbe4,0x324cde2e,2 +np.float32,0x4116cbe4,0xbf800000,2 +np.float32,0xc116cbe4,0xbf800000,2 +np.float32,0x4196cbe4,0x3f800000,2 +np.float32,0xc196cbe4,0x3f800000,2 +np.float32,0x40afede0,0x3f3504f7,2 +np.float32,0xc0afede0,0x3f3504f7,2 +np.float32,0x412fede0,0x353222c4,2 +np.float32,0xc12fede0,0x353222c4,2 +np.float32,0x41afede0,0xbf800000,2 +np.float32,0xc1afede0,0xbf800000,2 +np.float32,0x40c90fdb,0x3f800000,2 +np.float32,0xc0c90fdb,0x3f800000,2 +np.float32,0x41490fdb,0x3f800000,2 +np.float32,0xc1490fdb,0x3f800000,2 +np.float32,0x41c90fdb,0x3f800000,2 +np.float32,0xc1c90fdb,0x3f800000,2 +np.float32,0x40e231d6,0x3f3504f3,2 +np.float32,0xc0e231d6,0x3f3504f3,2 +np.float32,0x416231d6,0xb319a6a2,2 +np.float32,0xc16231d6,0xb319a6a2,2 +np.float32,0x41e231d6,0xbf800000,2 +np.float32,0xc1e231d6,0xbf800000,2 +np.float32,0x40fb53d2,0xb4b5563d,2 +np.float32,0xc0fb53d2,0xb4b5563d,2 +np.float32,0x417b53d2,0xbf800000,2 +np.float32,0xc17b53d2,0xbf800000,2 +np.float32,0x41fb53d2,0x3f800000,2 +np.float32,0xc1fb53d2,0x3f800000,2 +np.float32,0x410a3ae7,0xbf3504fb,2 +np.float32,0xc10a3ae7,0xbf3504fb,2 +np.float32,0x418a3ae7,0x35b08908,2 +np.float32,0xc18a3ae7,0x35b08908,2 +np.float32,0x420a3ae7,0xbf800000,2 +np.float32,0xc20a3ae7,0xbf800000,2 +np.float32,0x4116cbe4,0xbf800000,2 +np.float32,0xc116cbe4,0xbf800000,2 +np.float32,0x4196cbe4,0x3f800000,2 +np.float32,0xc196cbe4,0x3f800000,2 +np.float32,0x4216cbe4,0x3f800000,2 +np.float32,0xc216cbe4,0x3f800000,2 +np.float32,0x41235ce2,0xbf3504ef,2 +np.float32,0xc1235ce2,0xbf3504ef,2 +np.float32,0x41a35ce2,0xb53889b6,2 +np.float32,0xc1a35ce2,0xb53889b6,2 +np.float32,0x42235ce2,0xbf800000,2 +np.float32,0xc2235ce2,0xbf800000,2 +np.float32,0x412fede0,0x353222c4,2 +np.float32,0xc12fede0,0x353222c4,2 +np.float32,0x41afede0,0xbf800000,2 +np.float32,0xc1afede0,0xbf800000,2 +np.float32,0x422fede0,0x3f800000,2 +np.float32,0xc22fede0,0x3f800000,2 +np.float32,0x413c7edd,0x3f3504f4,2 +np.float32,0xc13c7edd,0x3f3504f4,2 +np.float32,0x41bc7edd,0x33800add,2 +np.float32,0xc1bc7edd,0x33800add,2 +np.float32,0x423c7edd,0xbf800000,2 +np.float32,0xc23c7edd,0xbf800000,2 +np.float32,0x41490fdb,0x3f800000,2 +np.float32,0xc1490fdb,0x3f800000,2 +np.float32,0x41c90fdb,0x3f800000,2 +np.float32,0xc1c90fdb,0x3f800000,2 +np.float32,0x42490fdb,0x3f800000,2 +np.float32,0xc2490fdb,0x3f800000,2 +np.float32,0x4155a0d9,0x3f3504eb,2 +np.float32,0xc155a0d9,0x3f3504eb,2 +np.float32,0x41d5a0d9,0xb5b3bc81,2 +np.float32,0xc1d5a0d9,0xb5b3bc81,2 +np.float32,0x4255a0d9,0xbf800000,2 +np.float32,0xc255a0d9,0xbf800000,2 +np.float32,0x416231d6,0xb319a6a2,2 +np.float32,0xc16231d6,0xb319a6a2,2 +np.float32,0x41e231d6,0xbf800000,2 +np.float32,0xc1e231d6,0xbf800000,2 +np.float32,0x426231d6,0x3f800000,2 +np.float32,0xc26231d6,0x3f800000,2 +np.float32,0x416ec2d4,0xbf3504f7,2 +np.float32,0xc16ec2d4,0xbf3504f7,2 +np.float32,0x41eec2d4,0x353ef0a7,2 +np.float32,0xc1eec2d4,0x353ef0a7,2 +np.float32,0x426ec2d4,0xbf800000,2 +np.float32,0xc26ec2d4,0xbf800000,2 +np.float32,0x417b53d2,0xbf800000,2 +np.float32,0xc17b53d2,0xbf800000,2 +np.float32,0x41fb53d2,0x3f800000,2 +np.float32,0xc1fb53d2,0x3f800000,2 +np.float32,0x427b53d2,0x3f800000,2 +np.float32,0xc27b53d2,0x3f800000,2 +np.float32,0x4183f268,0xbf3504e7,2 +np.float32,0xc183f268,0xbf3504e7,2 +np.float32,0x4203f268,0xb6059a13,2 +np.float32,0xc203f268,0xb6059a13,2 +np.float32,0x4283f268,0xbf800000,2 +np.float32,0xc283f268,0xbf800000,2 +np.float32,0x418a3ae7,0x35b08908,2 +np.float32,0xc18a3ae7,0x35b08908,2 +np.float32,0x420a3ae7,0xbf800000,2 +np.float32,0xc20a3ae7,0xbf800000,2 +np.float32,0x428a3ae7,0x3f800000,2 +np.float32,0xc28a3ae7,0x3f800000,2 +np.float32,0x41908365,0x3f3504f0,2 +np.float32,0xc1908365,0x3f3504f0,2 +np.float32,0x42108365,0xb512200d,2 +np.float32,0xc2108365,0xb512200d,2 +np.float32,0x42908365,0xbf800000,2 +np.float32,0xc2908365,0xbf800000,2 +np.float32,0x4196cbe4,0x3f800000,2 +np.float32,0xc196cbe4,0x3f800000,2 +np.float32,0x4216cbe4,0x3f800000,2 +np.float32,0xc216cbe4,0x3f800000,2 +np.float32,0x4296cbe4,0x3f800000,2 +np.float32,0xc296cbe4,0x3f800000,2 +np.float32,0x419d1463,0x3f3504ef,2 +np.float32,0xc19d1463,0x3f3504ef,2 +np.float32,0x421d1463,0xb5455799,2 +np.float32,0xc21d1463,0xb5455799,2 +np.float32,0x429d1463,0xbf800000,2 +np.float32,0xc29d1463,0xbf800000,2 +np.float32,0x41a35ce2,0xb53889b6,2 +np.float32,0xc1a35ce2,0xb53889b6,2 +np.float32,0x42235ce2,0xbf800000,2 +np.float32,0xc2235ce2,0xbf800000,2 +np.float32,0x42a35ce2,0x3f800000,2 +np.float32,0xc2a35ce2,0x3f800000,2 +np.float32,0x41a9a561,0xbf3504ff,2 +np.float32,0xc1a9a561,0xbf3504ff,2 +np.float32,0x4229a561,0x360733d0,2 +np.float32,0xc229a561,0x360733d0,2 +np.float32,0x42a9a561,0xbf800000,2 +np.float32,0xc2a9a561,0xbf800000,2 +np.float32,0x41afede0,0xbf800000,2 +np.float32,0xc1afede0,0xbf800000,2 +np.float32,0x422fede0,0x3f800000,2 +np.float32,0xc22fede0,0x3f800000,2 +np.float32,0x42afede0,0x3f800000,2 +np.float32,0xc2afede0,0x3f800000,2 +np.float32,0x41b6365e,0xbf3504f6,2 +np.float32,0xc1b6365e,0xbf3504f6,2 +np.float32,0x4236365e,0x350bb91c,2 +np.float32,0xc236365e,0x350bb91c,2 +np.float32,0x42b6365e,0xbf800000,2 +np.float32,0xc2b6365e,0xbf800000,2 +np.float32,0x41bc7edd,0x33800add,2 +np.float32,0xc1bc7edd,0x33800add,2 +np.float32,0x423c7edd,0xbf800000,2 +np.float32,0xc23c7edd,0xbf800000,2 +np.float32,0x42bc7edd,0x3f800000,2 +np.float32,0xc2bc7edd,0x3f800000,2 +np.float32,0x41c2c75c,0x3f3504f8,2 +np.float32,0xc1c2c75c,0x3f3504f8,2 +np.float32,0x4242c75c,0x354bbe8a,2 +np.float32,0xc242c75c,0x354bbe8a,2 +np.float32,0x42c2c75c,0xbf800000,2 +np.float32,0xc2c2c75c,0xbf800000,2 +np.float32,0x41c90fdb,0x3f800000,2 +np.float32,0xc1c90fdb,0x3f800000,2 +np.float32,0x42490fdb,0x3f800000,2 +np.float32,0xc2490fdb,0x3f800000,2 +np.float32,0x42c90fdb,0x3f800000,2 +np.float32,0xc2c90fdb,0x3f800000,2 +np.float32,0x41cf585a,0x3f3504e7,2 +np.float32,0xc1cf585a,0x3f3504e7,2 +np.float32,0x424f585a,0xb608cd8c,2 +np.float32,0xc24f585a,0xb608cd8c,2 +np.float32,0x42cf585a,0xbf800000,2 +np.float32,0xc2cf585a,0xbf800000,2 +np.float32,0x41d5a0d9,0xb5b3bc81,2 +np.float32,0xc1d5a0d9,0xb5b3bc81,2 +np.float32,0x4255a0d9,0xbf800000,2 +np.float32,0xc255a0d9,0xbf800000,2 +np.float32,0x42d5a0d9,0x3f800000,2 +np.float32,0xc2d5a0d9,0x3f800000,2 +np.float32,0x41dbe958,0xbf350507,2 +np.float32,0xc1dbe958,0xbf350507,2 +np.float32,0x425be958,0x365eab75,2 +np.float32,0xc25be958,0x365eab75,2 +np.float32,0x42dbe958,0xbf800000,2 +np.float32,0xc2dbe958,0xbf800000,2 +np.float32,0x41e231d6,0xbf800000,2 +np.float32,0xc1e231d6,0xbf800000,2 +np.float32,0x426231d6,0x3f800000,2 +np.float32,0xc26231d6,0x3f800000,2 +np.float32,0x42e231d6,0x3f800000,2 +np.float32,0xc2e231d6,0x3f800000,2 +np.float32,0x41e87a55,0xbf3504ef,2 +np.float32,0xc1e87a55,0xbf3504ef,2 +np.float32,0x42687a55,0xb552257b,2 +np.float32,0xc2687a55,0xb552257b,2 +np.float32,0x42e87a55,0xbf800000,2 +np.float32,0xc2e87a55,0xbf800000,2 +np.float32,0x41eec2d4,0x353ef0a7,2 +np.float32,0xc1eec2d4,0x353ef0a7,2 +np.float32,0x426ec2d4,0xbf800000,2 +np.float32,0xc26ec2d4,0xbf800000,2 +np.float32,0x42eec2d4,0x3f800000,2 +np.float32,0xc2eec2d4,0x3f800000,2 +np.float32,0x41f50b53,0x3f3504ff,2 +np.float32,0xc1f50b53,0x3f3504ff,2 +np.float32,0x42750b53,0x360a6748,2 +np.float32,0xc2750b53,0x360a6748,2 +np.float32,0x42f50b53,0xbf800000,2 +np.float32,0xc2f50b53,0xbf800000,2 +np.float32,0x41fb53d2,0x3f800000,2 +np.float32,0xc1fb53d2,0x3f800000,2 +np.float32,0x427b53d2,0x3f800000,2 +np.float32,0xc27b53d2,0x3f800000,2 +np.float32,0x42fb53d2,0x3f800000,2 +np.float32,0xc2fb53d2,0x3f800000,2 +np.float32,0x4200ce28,0x3f3504f6,2 +np.float32,0xc200ce28,0x3f3504f6,2 +np.float32,0x4280ce28,0x34fdd672,2 +np.float32,0xc280ce28,0x34fdd672,2 +np.float32,0x4300ce28,0xbf800000,2 +np.float32,0xc300ce28,0xbf800000,2 +np.float32,0x4203f268,0xb6059a13,2 +np.float32,0xc203f268,0xb6059a13,2 +np.float32,0x4283f268,0xbf800000,2 +np.float32,0xc283f268,0xbf800000,2 +np.float32,0x4303f268,0x3f800000,2 +np.float32,0xc303f268,0x3f800000,2 +np.float32,0x420716a7,0xbf3504f8,2 +np.float32,0xc20716a7,0xbf3504f8,2 +np.float32,0x428716a7,0x35588c6d,2 +np.float32,0xc28716a7,0x35588c6d,2 +np.float32,0x430716a7,0xbf800000,2 +np.float32,0xc30716a7,0xbf800000,2 +np.float32,0x420a3ae7,0xbf800000,2 +np.float32,0xc20a3ae7,0xbf800000,2 +np.float32,0x428a3ae7,0x3f800000,2 +np.float32,0xc28a3ae7,0x3f800000,2 +np.float32,0x430a3ae7,0x3f800000,2 +np.float32,0xc30a3ae7,0x3f800000,2 +np.float32,0x420d5f26,0xbf3504e7,2 +np.float32,0xc20d5f26,0xbf3504e7,2 +np.float32,0x428d5f26,0xb60c0105,2 +np.float32,0xc28d5f26,0xb60c0105,2 +np.float32,0x430d5f26,0xbf800000,2 +np.float32,0xc30d5f26,0xbf800000,2 +np.float32,0x42108365,0xb512200d,2 +np.float32,0xc2108365,0xb512200d,2 +np.float32,0x42908365,0xbf800000,2 +np.float32,0xc2908365,0xbf800000,2 +np.float32,0x43108365,0x3f800000,2 +np.float32,0xc3108365,0x3f800000,2 +np.float32,0x4213a7a5,0x3f350507,2 +np.float32,0xc213a7a5,0x3f350507,2 +np.float32,0x4293a7a5,0x3661deee,2 +np.float32,0xc293a7a5,0x3661deee,2 +np.float32,0x4313a7a5,0xbf800000,2 +np.float32,0xc313a7a5,0xbf800000,2 +np.float32,0x4216cbe4,0x3f800000,2 +np.float32,0xc216cbe4,0x3f800000,2 +np.float32,0x4296cbe4,0x3f800000,2 +np.float32,0xc296cbe4,0x3f800000,2 +np.float32,0x4316cbe4,0x3f800000,2 +np.float32,0xc316cbe4,0x3f800000,2 +np.float32,0x4219f024,0x3f3504d8,2 +np.float32,0xc219f024,0x3f3504d8,2 +np.float32,0x4299f024,0xb69bde6c,2 +np.float32,0xc299f024,0xb69bde6c,2 +np.float32,0x4319f024,0xbf800000,2 +np.float32,0xc319f024,0xbf800000,2 +np.float32,0x421d1463,0xb5455799,2 +np.float32,0xc21d1463,0xb5455799,2 +np.float32,0x429d1463,0xbf800000,2 +np.float32,0xc29d1463,0xbf800000,2 +np.float32,0x431d1463,0x3f800000,2 +np.float32,0xc31d1463,0x3f800000,2 +np.float32,0x422038a3,0xbf350516,2 +np.float32,0xc22038a3,0xbf350516,2 +np.float32,0x42a038a3,0x36c6cd61,2 +np.float32,0xc2a038a3,0x36c6cd61,2 +np.float32,0x432038a3,0xbf800000,2 +np.float32,0xc32038a3,0xbf800000,2 +np.float32,0x42235ce2,0xbf800000,2 +np.float32,0xc2235ce2,0xbf800000,2 +np.float32,0x42a35ce2,0x3f800000,2 +np.float32,0xc2a35ce2,0x3f800000,2 +np.float32,0x43235ce2,0x3f800000,2 +np.float32,0xc3235ce2,0x3f800000,2 +np.float32,0x42268121,0xbf3504f6,2 +np.float32,0xc2268121,0xbf3504f6,2 +np.float32,0x42a68121,0x34e43aac,2 +np.float32,0xc2a68121,0x34e43aac,2 +np.float32,0x43268121,0xbf800000,2 +np.float32,0xc3268121,0xbf800000,2 +np.float32,0x4229a561,0x360733d0,2 +np.float32,0xc229a561,0x360733d0,2 +np.float32,0x42a9a561,0xbf800000,2 +np.float32,0xc2a9a561,0xbf800000,2 +np.float32,0x4329a561,0x3f800000,2 +np.float32,0xc329a561,0x3f800000,2 +np.float32,0x422cc9a0,0x3f3504f8,2 +np.float32,0xc22cc9a0,0x3f3504f8,2 +np.float32,0x42acc9a0,0x35655a50,2 +np.float32,0xc2acc9a0,0x35655a50,2 +np.float32,0x432cc9a0,0xbf800000,2 +np.float32,0xc32cc9a0,0xbf800000,2 +np.float32,0x422fede0,0x3f800000,2 +np.float32,0xc22fede0,0x3f800000,2 +np.float32,0x42afede0,0x3f800000,2 +np.float32,0xc2afede0,0x3f800000,2 +np.float32,0x432fede0,0x3f800000,2 +np.float32,0xc32fede0,0x3f800000,2 +np.float32,0x4233121f,0x3f3504e7,2 +np.float32,0xc233121f,0x3f3504e7,2 +np.float32,0x42b3121f,0xb60f347d,2 +np.float32,0xc2b3121f,0xb60f347d,2 +np.float32,0x4333121f,0xbf800000,2 +np.float32,0xc333121f,0xbf800000,2 +np.float32,0x4236365e,0x350bb91c,2 +np.float32,0xc236365e,0x350bb91c,2 +np.float32,0x42b6365e,0xbf800000,2 +np.float32,0xc2b6365e,0xbf800000,2 +np.float32,0x4336365e,0x3f800000,2 +np.float32,0xc336365e,0x3f800000,2 +np.float32,0x42395a9e,0xbf350507,2 +np.float32,0xc2395a9e,0xbf350507,2 +np.float32,0x42b95a9e,0x36651267,2 +np.float32,0xc2b95a9e,0x36651267,2 +np.float32,0x43395a9e,0xbf800000,2 +np.float32,0xc3395a9e,0xbf800000,2 +np.float32,0x423c7edd,0xbf800000,2 +np.float32,0xc23c7edd,0xbf800000,2 +np.float32,0x42bc7edd,0x3f800000,2 +np.float32,0xc2bc7edd,0x3f800000,2 +np.float32,0x433c7edd,0x3f800000,2 +np.float32,0xc33c7edd,0x3f800000,2 +np.float32,0x423fa31d,0xbf3504d7,2 +np.float32,0xc23fa31d,0xbf3504d7,2 +np.float32,0x42bfa31d,0xb69d7828,2 +np.float32,0xc2bfa31d,0xb69d7828,2 +np.float32,0x433fa31d,0xbf800000,2 +np.float32,0xc33fa31d,0xbf800000,2 +np.float32,0x4242c75c,0x354bbe8a,2 +np.float32,0xc242c75c,0x354bbe8a,2 +np.float32,0x42c2c75c,0xbf800000,2 +np.float32,0xc2c2c75c,0xbf800000,2 +np.float32,0x4342c75c,0x3f800000,2 +np.float32,0xc342c75c,0x3f800000,2 +np.float32,0x4245eb9c,0x3f350517,2 +np.float32,0xc245eb9c,0x3f350517,2 +np.float32,0x42c5eb9c,0x36c8671d,2 +np.float32,0xc2c5eb9c,0x36c8671d,2 +np.float32,0x4345eb9c,0xbf800000,2 +np.float32,0xc345eb9c,0xbf800000,2 +np.float32,0x42490fdb,0x3f800000,2 +np.float32,0xc2490fdb,0x3f800000,2 +np.float32,0x42c90fdb,0x3f800000,2 +np.float32,0xc2c90fdb,0x3f800000,2 +np.float32,0x43490fdb,0x3f800000,2 +np.float32,0xc3490fdb,0x3f800000,2 +np.float32,0x424c341a,0x3f3504f5,2 +np.float32,0xc24c341a,0x3f3504f5,2 +np.float32,0x42cc341a,0x34ca9ee6,2 +np.float32,0xc2cc341a,0x34ca9ee6,2 +np.float32,0x434c341a,0xbf800000,2 +np.float32,0xc34c341a,0xbf800000,2 +np.float32,0x424f585a,0xb608cd8c,2 +np.float32,0xc24f585a,0xb608cd8c,2 +np.float32,0x42cf585a,0xbf800000,2 +np.float32,0xc2cf585a,0xbf800000,2 +np.float32,0x434f585a,0x3f800000,2 +np.float32,0xc34f585a,0x3f800000,2 +np.float32,0x42527c99,0xbf3504f9,2 +np.float32,0xc2527c99,0xbf3504f9,2 +np.float32,0x42d27c99,0x35722833,2 +np.float32,0xc2d27c99,0x35722833,2 +np.float32,0x43527c99,0xbf800000,2 +np.float32,0xc3527c99,0xbf800000,2 +np.float32,0x4255a0d9,0xbf800000,2 +np.float32,0xc255a0d9,0xbf800000,2 +np.float32,0x42d5a0d9,0x3f800000,2 +np.float32,0xc2d5a0d9,0x3f800000,2 +np.float32,0x4355a0d9,0x3f800000,2 +np.float32,0xc355a0d9,0x3f800000,2 +np.float32,0x4258c518,0xbf3504e6,2 +np.float32,0xc258c518,0xbf3504e6,2 +np.float32,0x42d8c518,0xb61267f6,2 +np.float32,0xc2d8c518,0xb61267f6,2 +np.float32,0x4358c518,0xbf800000,2 +np.float32,0xc358c518,0xbf800000,2 +np.float32,0x425be958,0x365eab75,2 +np.float32,0xc25be958,0x365eab75,2 +np.float32,0x42dbe958,0xbf800000,2 +np.float32,0xc2dbe958,0xbf800000,2 +np.float32,0x435be958,0x3f800000,2 +np.float32,0xc35be958,0x3f800000,2 +np.float32,0x425f0d97,0x3f350508,2 +np.float32,0xc25f0d97,0x3f350508,2 +np.float32,0x42df0d97,0x366845e0,2 +np.float32,0xc2df0d97,0x366845e0,2 +np.float32,0x435f0d97,0xbf800000,2 +np.float32,0xc35f0d97,0xbf800000,2 +np.float32,0x426231d6,0x3f800000,2 +np.float32,0xc26231d6,0x3f800000,2 +np.float32,0x42e231d6,0x3f800000,2 +np.float32,0xc2e231d6,0x3f800000,2 +np.float32,0x436231d6,0x3f800000,2 +np.float32,0xc36231d6,0x3f800000,2 +np.float32,0x42655616,0x3f3504d7,2 +np.float32,0xc2655616,0x3f3504d7,2 +np.float32,0x42e55616,0xb69f11e5,2 +np.float32,0xc2e55616,0xb69f11e5,2 +np.float32,0x43655616,0xbf800000,2 +np.float32,0xc3655616,0xbf800000,2 +np.float32,0x42687a55,0xb552257b,2 +np.float32,0xc2687a55,0xb552257b,2 +np.float32,0x42e87a55,0xbf800000,2 +np.float32,0xc2e87a55,0xbf800000,2 +np.float32,0x43687a55,0x3f800000,2 +np.float32,0xc3687a55,0x3f800000,2 +np.float32,0x426b9e95,0xbf350517,2 +np.float32,0xc26b9e95,0xbf350517,2 +np.float32,0x42eb9e95,0x36ca00d9,2 +np.float32,0xc2eb9e95,0x36ca00d9,2 +np.float32,0x436b9e95,0xbf800000,2 +np.float32,0xc36b9e95,0xbf800000,2 +np.float32,0x426ec2d4,0xbf800000,2 +np.float32,0xc26ec2d4,0xbf800000,2 +np.float32,0x42eec2d4,0x3f800000,2 +np.float32,0xc2eec2d4,0x3f800000,2 +np.float32,0x436ec2d4,0x3f800000,2 +np.float32,0xc36ec2d4,0x3f800000,2 +np.float32,0x4271e713,0xbf3504f5,2 +np.float32,0xc271e713,0xbf3504f5,2 +np.float32,0x42f1e713,0x34b10321,2 +np.float32,0xc2f1e713,0x34b10321,2 +np.float32,0x4371e713,0xbf800000,2 +np.float32,0xc371e713,0xbf800000,2 +np.float32,0x42750b53,0x360a6748,2 +np.float32,0xc2750b53,0x360a6748,2 +np.float32,0x42f50b53,0xbf800000,2 +np.float32,0xc2f50b53,0xbf800000,2 +np.float32,0x43750b53,0x3f800000,2 +np.float32,0xc3750b53,0x3f800000,2 +np.float32,0x42782f92,0x3f3504f9,2 +np.float32,0xc2782f92,0x3f3504f9,2 +np.float32,0x42f82f92,0x357ef616,2 +np.float32,0xc2f82f92,0x357ef616,2 +np.float32,0x43782f92,0xbf800000,2 +np.float32,0xc3782f92,0xbf800000,2 +np.float32,0x427b53d2,0x3f800000,2 +np.float32,0xc27b53d2,0x3f800000,2 +np.float32,0x42fb53d2,0x3f800000,2 +np.float32,0xc2fb53d2,0x3f800000,2 +np.float32,0x437b53d2,0x3f800000,2 +np.float32,0xc37b53d2,0x3f800000,2 +np.float32,0x427e7811,0x3f3504e6,2 +np.float32,0xc27e7811,0x3f3504e6,2 +np.float32,0x42fe7811,0xb6159b6f,2 +np.float32,0xc2fe7811,0xb6159b6f,2 +np.float32,0x437e7811,0xbf800000,2 +np.float32,0xc37e7811,0xbf800000,2 +np.float32,0x4280ce28,0x34fdd672,2 +np.float32,0xc280ce28,0x34fdd672,2 +np.float32,0x4300ce28,0xbf800000,2 +np.float32,0xc300ce28,0xbf800000,2 +np.float32,0x4380ce28,0x3f800000,2 +np.float32,0xc380ce28,0x3f800000,2 +np.float32,0x42826048,0xbf350508,2 +np.float32,0xc2826048,0xbf350508,2 +np.float32,0x43026048,0x366b7958,2 +np.float32,0xc3026048,0x366b7958,2 +np.float32,0x43826048,0xbf800000,2 +np.float32,0xc3826048,0xbf800000,2 +np.float32,0x4283f268,0xbf800000,2 +np.float32,0xc283f268,0xbf800000,2 +np.float32,0x4303f268,0x3f800000,2 +np.float32,0xc303f268,0x3f800000,2 +np.float32,0x4383f268,0x3f800000,2 +np.float32,0xc383f268,0x3f800000,2 +np.float32,0x42858487,0xbf350504,2 +np.float32,0xc2858487,0xbf350504,2 +np.float32,0x43058487,0x363ea8be,2 +np.float32,0xc3058487,0x363ea8be,2 +np.float32,0x43858487,0xbf800000,2 +np.float32,0xc3858487,0xbf800000,2 +np.float32,0x428716a7,0x35588c6d,2 +np.float32,0xc28716a7,0x35588c6d,2 +np.float32,0x430716a7,0xbf800000,2 +np.float32,0xc30716a7,0xbf800000,2 +np.float32,0x438716a7,0x3f800000,2 +np.float32,0xc38716a7,0x3f800000,2 +np.float32,0x4288a8c7,0x3f350517,2 +np.float32,0xc288a8c7,0x3f350517,2 +np.float32,0x4308a8c7,0x36cb9a96,2 +np.float32,0xc308a8c7,0x36cb9a96,2 +np.float32,0x4388a8c7,0xbf800000,2 +np.float32,0xc388a8c7,0xbf800000,2 +np.float32,0x428a3ae7,0x3f800000,2 +np.float32,0xc28a3ae7,0x3f800000,2 +np.float32,0x430a3ae7,0x3f800000,2 +np.float32,0xc30a3ae7,0x3f800000,2 +np.float32,0x438a3ae7,0x3f800000,2 +np.float32,0xc38a3ae7,0x3f800000,2 +np.float32,0x428bcd06,0x3f3504f5,2 +np.float32,0xc28bcd06,0x3f3504f5,2 +np.float32,0x430bcd06,0x3497675b,2 +np.float32,0xc30bcd06,0x3497675b,2 +np.float32,0x438bcd06,0xbf800000,2 +np.float32,0xc38bcd06,0xbf800000,2 +np.float32,0x428d5f26,0xb60c0105,2 +np.float32,0xc28d5f26,0xb60c0105,2 +np.float32,0x430d5f26,0xbf800000,2 +np.float32,0xc30d5f26,0xbf800000,2 +np.float32,0x438d5f26,0x3f800000,2 +np.float32,0xc38d5f26,0x3f800000,2 +np.float32,0x428ef146,0xbf350526,2 +np.float32,0xc28ef146,0xbf350526,2 +np.float32,0x430ef146,0x3710bc40,2 +np.float32,0xc30ef146,0x3710bc40,2 +np.float32,0x438ef146,0xbf800000,2 +np.float32,0xc38ef146,0xbf800000,2 +np.float32,0x42908365,0xbf800000,2 +np.float32,0xc2908365,0xbf800000,2 +np.float32,0x43108365,0x3f800000,2 +np.float32,0xc3108365,0x3f800000,2 +np.float32,0x43908365,0x3f800000,2 +np.float32,0xc3908365,0x3f800000,2 +np.float32,0x42921585,0xbf3504e6,2 +np.float32,0xc2921585,0xbf3504e6,2 +np.float32,0x43121585,0xb618cee8,2 +np.float32,0xc3121585,0xb618cee8,2 +np.float32,0x43921585,0xbf800000,2 +np.float32,0xc3921585,0xbf800000,2 +np.float32,0x4293a7a5,0x3661deee,2 +np.float32,0xc293a7a5,0x3661deee,2 +np.float32,0x4313a7a5,0xbf800000,2 +np.float32,0xc313a7a5,0xbf800000,2 +np.float32,0x4393a7a5,0x3f800000,2 +np.float32,0xc393a7a5,0x3f800000,2 +np.float32,0x429539c5,0x3f350536,2 +np.float32,0xc29539c5,0x3f350536,2 +np.float32,0x431539c5,0x373bab34,2 +np.float32,0xc31539c5,0x373bab34,2 +np.float32,0x439539c5,0xbf800000,2 +np.float32,0xc39539c5,0xbf800000,2 +np.float32,0x4296cbe4,0x3f800000,2 +np.float32,0xc296cbe4,0x3f800000,2 +np.float32,0x4316cbe4,0x3f800000,2 +np.float32,0xc316cbe4,0x3f800000,2 +np.float32,0x4396cbe4,0x3f800000,2 +np.float32,0xc396cbe4,0x3f800000,2 +np.float32,0x42985e04,0x3f3504d7,2 +np.float32,0xc2985e04,0x3f3504d7,2 +np.float32,0x43185e04,0xb6a2455d,2 +np.float32,0xc3185e04,0xb6a2455d,2 +np.float32,0x43985e04,0xbf800000,2 +np.float32,0xc3985e04,0xbf800000,2 +np.float32,0x4299f024,0xb69bde6c,2 +np.float32,0xc299f024,0xb69bde6c,2 +np.float32,0x4319f024,0xbf800000,2 +np.float32,0xc319f024,0xbf800000,2 +np.float32,0x4399f024,0x3f800000,2 +np.float32,0xc399f024,0x3f800000,2 +np.float32,0x429b8243,0xbf3504ea,2 +np.float32,0xc29b8243,0xbf3504ea,2 +np.float32,0x431b8243,0xb5cb2eb8,2 +np.float32,0xc31b8243,0xb5cb2eb8,2 +np.float32,0x439b8243,0xbf800000,2 +np.float32,0xc39b8243,0xbf800000,2 +np.float32,0x435b2047,0x3f3504c1,2 +np.float32,0x42a038a2,0xb5e4ca7e,2 +np.float32,0x432038a2,0xbf800000,2 +np.float32,0x4345eb9b,0xbf800000,2 +np.float32,0x42c5eb9b,0xb5de638c,2 +np.float32,0x42eb9e94,0xb5d7fc9b,2 +np.float32,0x4350ea79,0x3631dadb,2 +np.float32,0x42dbe957,0xbf800000,2 +np.float32,0x425be957,0xb505522a,2 +np.float32,0x435be957,0x3f800000,2 +np.float32,0x46027eb2,0x3e7d94c9,2 +np.float32,0x4477baed,0xbe7f1824,2 +np.float32,0x454b8024,0x3e7f5268,2 +np.float32,0x455d2c09,0x3e7f40cb,2 +np.float32,0x4768d3de,0xba14b4af,2 +np.float32,0x46c1e7cd,0x3e7fb102,2 +np.float32,0x44a52949,0xbe7dc9d5,2 +np.float32,0x4454633a,0x3e7dbc7d,2 +np.float32,0x4689810b,0x3e7eb02b,2 +np.float32,0x473473cd,0xbe7eef6f,2 +np.float32,0x44a5193f,0x3e7e1b1f,2 +np.float32,0x46004b36,0x3e7dac59,2 +np.float32,0x467f604b,0x3d7ffd3a,2 +np.float32,0x45ea1805,0x3dffd2e0,2 +np.float32,0x457b6af3,0x3dff7831,2 +np.float32,0x44996159,0xbe7d85f4,2 +np.float32,0x47883553,0xbb80584e,2 +np.float32,0x44e19f0c,0xbdffcfe6,2 +np.float32,0x472b3bf6,0xbe7f7a82,2 +np.float32,0x4600bb4e,0x3a135e33,2 +np.float32,0x449f4556,0x3e7e42e5,2 +np.float32,0x474e9420,0x3dff77b2,2 +np.float32,0x45cbdb23,0x3dff7240,2 +np.float32,0x44222747,0x3dffb039,2 +np.float32,0x4772e419,0xbdff74b8,2 +np.float64,0x1,0x3ff0000000000000,1 +np.float64,0x8000000000000001,0x3ff0000000000000,1 +np.float64,0x10000000000000,0x3ff0000000000000,1 +np.float64,0x8010000000000000,0x3ff0000000000000,1 +np.float64,0x7fefffffffffffff,0xbfefffe62ecfab75,1 +np.float64,0xffefffffffffffff,0xbfefffe62ecfab75,1 +np.float64,0x7ff0000000000000,0xfff8000000000000,1 +np.float64,0xfff0000000000000,0xfff8000000000000,1 +np.float64,0x7ff8000000000000,0x7ff8000000000000,1 +np.float64,0x7ff4000000000000,0x7ffc000000000000,1 +np.float64,0xbfc28bd9dd2517b4,0x3fefaa28ba13a702,1 +np.float64,0x3fb673c62e2ce790,0x3fefe083847a717f,1 +np.float64,0xbfe3e1dac7e7c3b6,0x3fea0500ba099f3a,1 +np.float64,0xbfbe462caa3c8c58,0x3fefc6c8b9c1c87c,1 +np.float64,0xbfb9353576326a68,0x3fefd8513e50e6b1,1 +np.float64,0xbfc05e798520bcf4,0x3fefbd1ad81cf089,1 +np.float64,0xbfe3ca3be2e79478,0x3fea12b995ea6574,1 +np.float64,0xbfde875d46bd0eba,0x3fec6d888662a824,1 +np.float64,0x3fafc4e02c3f89c0,0x3feff03c34bffd69,1 +np.float64,0xbf98855848310ac0,0x3feffda6c1588bdb,1 +np.float64,0x3fe66c51186cd8a2,0x3fe875c61c630ecb,1 +np.float64,0xbfedff1c3b7bfe38,0x3fe2f0c8c9e8fa39,1 +np.float64,0x3fd6082267ac1044,0x3fee1f6023695050,1 +np.float64,0xbfe78449b06f0894,0x3fe7bda2b223850e,1 +np.float64,0x3feedb8e63fdb71c,0x3fe23d5dfd2dd33f,1 +np.float64,0xbfc0a9de3d2153bc,0x3fefbaadf5e5285e,1 +np.float64,0x3fc04c67432098d0,0x3fefbdae07b7de8d,1 +np.float64,0xbfeeef84c4fddf0a,0x3fe22cf37f309d88,1 +np.float64,0x3fc04bb025209760,0x3fefbdb3d7d34ecf,1 +np.float64,0x3fd6b84d48ad709c,0x3fee013403da6e2a,1 +np.float64,0x3fec1ae25d7835c4,0x3fe46e62195cf274,1 +np.float64,0xbfdc6fdf9bb8dfc0,0x3fece48dc78bbb2e,1 +np.float64,0x3fb4db2c9229b660,0x3fefe4d42f79bf49,1 +np.float64,0xbfc0ed698521dad4,0x3fefb8785ea658c9,1 +np.float64,0xbfee82772b7d04ee,0x3fe2864a80efe8e9,1 +np.float64,0x3fd575b664aaeb6c,0x3fee37c669a12879,1 +np.float64,0x3fe4afb1c5e95f64,0x3fe98b177194439c,1 +np.float64,0x3fd93962f9b272c4,0x3fed8bef61876294,1 +np.float64,0x3fd97ae025b2f5c0,0x3fed7f4cfbf4d300,1 +np.float64,0xbfd9afdb1bb35fb6,0x3fed74fdc44dabb1,1 +np.float64,0x3f8ae65e3035cc80,0x3fefff4b1a0ea62b,1 +np.float64,0xbfe7e58664efcb0d,0x3fe77c02a1cbb670,1 +np.float64,0x3fe5f68b37ebed16,0x3fe8c10f849a5d4d,1 +np.float64,0x3fd9137d61b226fc,0x3fed9330eb4815a1,1 +np.float64,0x3fc146d019228da0,0x3fefb57e2d4d52f8,1 +np.float64,0xbfda6036edb4c06e,0x3fed521b2b578679,1 +np.float64,0xbfe78ddfb0ef1bc0,0x3fe7b734319a77e4,1 +np.float64,0x3fe0877823610ef0,0x3febd33a993dd786,1 +np.float64,0x3fbc61af2e38c360,0x3fefcdb4f889756d,1 +np.float64,0x3fd4dcdca4a9b9b8,0x3fee50962ffea5ae,1 +np.float64,0xbfe03cb29f607965,0x3febf7dbf640a75a,1 +np.float64,0xbfc81de407303bc8,0x3fef6f066cef64bc,1 +np.float64,0x3fd8dea42db1bd48,0x3fed9d3e00dbe0b3,1 +np.float64,0x3feac75e94f58ebe,0x3fe56f1f47f97896,1 +np.float64,0x3fb3a1ea6e2743d0,0x3fefe7ec1247cdaa,1 +np.float64,0x3fd695c0f4ad2b80,0x3fee0730bd40883d,1 +np.float64,0xbfd2c631f5a58c64,0x3feea20cbd1105d7,1 +np.float64,0xbfe978a8e1f2f152,0x3fe663014d40ad7a,1 +np.float64,0x3fd8b6b76ab16d70,0x3feda4c879aacc19,1 +np.float64,0x3feaafd30e755fa6,0x3fe5809514c28453,1 +np.float64,0x3fe1e37dc263c6fc,0x3feb20f9ad1f3f5c,1 +np.float64,0x3fd0ec7c24a1d8f8,0x3feee34048f43b75,1 +np.float64,0xbfe3881cbf67103a,0x3fea38d7886e6f53,1 +np.float64,0xbfd7023957ae0472,0x3fedf4471c765a1c,1 +np.float64,0xbfebc51c4ef78a38,0x3fe4b01c424e297b,1 +np.float64,0xbfe20a93eae41528,0x3feb0c2aa321d2e0,1 +np.float64,0x3fef39be867e737e,0x3fe1efaba9164d27,1 +np.float64,0x3fe8ea9576f1d52a,0x3fe6c7a8826ce1be,1 +np.float64,0x3fea921d91f5243c,0x3fe5968c6cf78963,1 +np.float64,0x3fd7ee5d31afdcbc,0x3fedc9f19d43fe61,1 +np.float64,0xbfe3ed581767dab0,0x3fe9fe4ee2f2b1cd,1 +np.float64,0xbfc40923d5281248,0x3fef9bd8ee9f6e68,1 +np.float64,0x3fe411a834682350,0x3fe9e9103854f057,1 +np.float64,0xbfedf6ccdf7bed9a,0x3fe2f77ad6543246,1 +np.float64,0xbfe8788a44f0f114,0x3fe7172f3aa0c742,1 +np.float64,0xbfce728f173ce520,0x3fef1954083bea04,1 +np.float64,0xbfd64dd0acac9ba2,0x3fee138c3293c246,1 +np.float64,0xbfe00669f5600cd4,0x3fec121443945350,1 +np.float64,0xbfe7152ba2ee2a58,0x3fe8079465d09846,1 +np.float64,0x3fe8654d8f70ca9c,0x3fe7247c94f09596,1 +np.float64,0x3fea68045cf4d008,0x3fe5b58cfe81a243,1 +np.float64,0xbfcd4779073a8ef4,0x3fef2a9d78153fa5,1 +np.float64,0xbfdb4456e5b688ae,0x3fed23b11614203f,1 +np.float64,0x3fcb5d59cd36bab0,0x3fef45818216a515,1 +np.float64,0xbfd914ff5ab229fe,0x3fed92e73746fea8,1 +np.float64,0x3fe4d211db69a424,0x3fe97653f433d15f,1 +np.float64,0xbfdbbb9224b77724,0x3fed0adb593dde80,1 +np.float64,0x3fd424ceafa8499c,0x3fee6d9124795d33,1 +np.float64,0x3feb5968f976b2d2,0x3fe501d116efbf54,1 +np.float64,0x3fee7d92a2fcfb26,0x3fe28a479b6a9dcf,1 +np.float64,0x3fc308e9972611d0,0x3fefa595f4df0c89,1 +np.float64,0x3fda79cd77b4f39c,0x3fed4cf8e69ba1f8,1 +np.float64,0x3fcbcf42d5379e88,0x3fef3f6a6a77c187,1 +np.float64,0x3fe13a1da662743c,0x3feb79504faea888,1 +np.float64,0xbfee4435f07c886c,0x3fe2b8ea98d2fc29,1 +np.float64,0x3fd65d68ccacbad0,0x3fee10e1ac7ada89,1 +np.float64,0x3fef2f89bb7e5f14,0x3fe1f81e882cc3f4,1 +np.float64,0xbfef0a7769fe14ef,0x3fe216bf384fc646,1 +np.float64,0x3fc065277320ca50,0x3fefbce44835c193,1 +np.float64,0x3fe9c1a74d73834e,0x3fe62e9ee0c2f2bf,1 +np.float64,0x3fd9d96e5db3b2dc,0x3fed6cd88eb51f6a,1 +np.float64,0x3fe02bf1c56057e4,0x3febfffc24b5a7ba,1 +np.float64,0xbfd6814350ad0286,0x3fee0ab9ad318b84,1 +np.float64,0x3f9fcbec583f97c0,0x3feffc0d0f1d8e75,1 +np.float64,0x3fe23524e5e46a4a,0x3feaf55372949a06,1 +np.float64,0xbfbdc95f6a3b92c0,0x3fefc89c21d44995,1 +np.float64,0x3fe961bb9cf2c378,0x3fe6735d6e1cca58,1 +np.float64,0xbfe8f1c370f1e387,0x3fe6c29d1be8bee9,1 +np.float64,0x3fd880d43ab101a8,0x3fedaee3c7ccfc96,1 +np.float64,0xbfedb37005fb66e0,0x3fe32d91ef2e3bd3,1 +np.float64,0xfdce287bfb9c5,0x3ff0000000000000,1 +np.float64,0x9aa1b9e735437,0x3ff0000000000000,1 +np.float64,0x6beac6e0d7d59,0x3ff0000000000000,1 +np.float64,0x47457aae8e8b0,0x3ff0000000000000,1 +np.float64,0x35ff13b46bfe3,0x3ff0000000000000,1 +np.float64,0xb9c0c82b73819,0x3ff0000000000000,1 +np.float64,0x1a8dc21a351b9,0x3ff0000000000000,1 +np.float64,0x7e87ef6afd0ff,0x3ff0000000000000,1 +np.float64,0x620a6588c414d,0x3ff0000000000000,1 +np.float64,0x7f366000fe6e,0x3ff0000000000000,1 +np.float64,0x787e39f4f0fc8,0x3ff0000000000000,1 +np.float64,0xf5134f1fea26a,0x3ff0000000000000,1 +np.float64,0xbce700ef79ce0,0x3ff0000000000000,1 +np.float64,0x144d7cc8289b1,0x3ff0000000000000,1 +np.float64,0xb9fbc5b973f79,0x3ff0000000000000,1 +np.float64,0xc3d6292d87ac5,0x3ff0000000000000,1 +np.float64,0xc1084e618210a,0x3ff0000000000000,1 +np.float64,0xb6b9eca56d73e,0x3ff0000000000000,1 +np.float64,0xc7ac4b858f58a,0x3ff0000000000000,1 +np.float64,0x516d75d2a2daf,0x3ff0000000000000,1 +np.float64,0x9dc089d93b811,0x3ff0000000000000,1 +np.float64,0x7b5f2840f6be6,0x3ff0000000000000,1 +np.float64,0x121d3ce8243a9,0x3ff0000000000000,1 +np.float64,0xf0be0337e17c1,0x3ff0000000000000,1 +np.float64,0xff58a5cbfeb15,0x3ff0000000000000,1 +np.float64,0xdaf1d07fb5e3a,0x3ff0000000000000,1 +np.float64,0x61d95382c3b2b,0x3ff0000000000000,1 +np.float64,0xe4df943fc9bf3,0x3ff0000000000000,1 +np.float64,0xf72ac2bdee559,0x3ff0000000000000,1 +np.float64,0x12dafbf625b60,0x3ff0000000000000,1 +np.float64,0xee11d427dc23b,0x3ff0000000000000,1 +np.float64,0xf4f8eb37e9f1e,0x3ff0000000000000,1 +np.float64,0xad7cb5df5af97,0x3ff0000000000000,1 +np.float64,0x59fc9b06b3f94,0x3ff0000000000000,1 +np.float64,0x3c3e65e4787ce,0x3ff0000000000000,1 +np.float64,0xe37bc993c6f79,0x3ff0000000000000,1 +np.float64,0x13bd6330277ad,0x3ff0000000000000,1 +np.float64,0x56cc2800ad986,0x3ff0000000000000,1 +np.float64,0x6203b8fcc4078,0x3ff0000000000000,1 +np.float64,0x75c7c8b8eb8fa,0x3ff0000000000000,1 +np.float64,0x5ebf8e00bd7f2,0x3ff0000000000000,1 +np.float64,0xda81f2f1b503f,0x3ff0000000000000,1 +np.float64,0x6adb17d6d5b64,0x3ff0000000000000,1 +np.float64,0x1ba68eee374d3,0x3ff0000000000000,1 +np.float64,0xeecf6fbbdd9ee,0x3ff0000000000000,1 +np.float64,0x24d6dd8e49add,0x3ff0000000000000,1 +np.float64,0xdf7cb81bbef97,0x3ff0000000000000,1 +np.float64,0xafd7be1b5faf8,0x3ff0000000000000,1 +np.float64,0xdb90ca35b721a,0x3ff0000000000000,1 +np.float64,0xa72903a14e521,0x3ff0000000000000,1 +np.float64,0x14533ee028a7,0x3ff0000000000000,1 +np.float64,0x7951540cf2a2b,0x3ff0000000000000,1 +np.float64,0x22882be045106,0x3ff0000000000000,1 +np.float64,0x136270d626c4f,0x3ff0000000000000,1 +np.float64,0x6a0f5744d41ec,0x3ff0000000000000,1 +np.float64,0x21e0d1aa43c1b,0x3ff0000000000000,1 +np.float64,0xee544155dca88,0x3ff0000000000000,1 +np.float64,0xcbe8aac797d16,0x3ff0000000000000,1 +np.float64,0x6c065e80d80e,0x3ff0000000000000,1 +np.float64,0xe57f0411cafe1,0x3ff0000000000000,1 +np.float64,0xdec3a6bdbd875,0x3ff0000000000000,1 +np.float64,0xf4d23a0fe9a48,0x3ff0000000000000,1 +np.float64,0xda77ef47b4efe,0x3ff0000000000000,1 +np.float64,0x8c405c9b1880c,0x3ff0000000000000,1 +np.float64,0x4eced5149d9db,0x3ff0000000000000,1 +np.float64,0x16b6552c2d6cc,0x3ff0000000000000,1 +np.float64,0x6fbc262cdf785,0x3ff0000000000000,1 +np.float64,0x628c3844c5188,0x3ff0000000000000,1 +np.float64,0x6d827d2cdb050,0x3ff0000000000000,1 +np.float64,0xd1bfdf29a37fc,0x3ff0000000000000,1 +np.float64,0xd85400fdb0a80,0x3ff0000000000000,1 +np.float64,0xcc420b2d98842,0x3ff0000000000000,1 +np.float64,0xac41d21b5883b,0x3ff0000000000000,1 +np.float64,0x432f18d4865e4,0x3ff0000000000000,1 +np.float64,0xe7e89a1bcfd14,0x3ff0000000000000,1 +np.float64,0x9b1141d536228,0x3ff0000000000000,1 +np.float64,0x6805f662d00bf,0x3ff0000000000000,1 +np.float64,0xc76552358ecab,0x3ff0000000000000,1 +np.float64,0x4ae8ffee95d21,0x3ff0000000000000,1 +np.float64,0x4396c096872d9,0x3ff0000000000000,1 +np.float64,0x6e8e55d4dd1cb,0x3ff0000000000000,1 +np.float64,0x4c2e33dc985c7,0x3ff0000000000000,1 +np.float64,0xbce814a579d03,0x3ff0000000000000,1 +np.float64,0x911681b5222d0,0x3ff0000000000000,1 +np.float64,0x5f90a4b2bf215,0x3ff0000000000000,1 +np.float64,0x26f76be84deee,0x3ff0000000000000,1 +np.float64,0xb2f7536165eeb,0x3ff0000000000000,1 +np.float64,0x4de4e6089bc9d,0x3ff0000000000000,1 +np.float64,0xf2e016afe5c03,0x3ff0000000000000,1 +np.float64,0xb9b7b949736f7,0x3ff0000000000000,1 +np.float64,0x3363ea1866c7e,0x3ff0000000000000,1 +np.float64,0xd1a3bd6ba3478,0x3ff0000000000000,1 +np.float64,0xae89f3595d13f,0x3ff0000000000000,1 +np.float64,0xddbd9601bb7c,0x3ff0000000000000,1 +np.float64,0x5de41a06bbc84,0x3ff0000000000000,1 +np.float64,0xfd58c86dfab19,0x3ff0000000000000,1 +np.float64,0x24922e8c49247,0x3ff0000000000000,1 +np.float64,0xcda040339b408,0x3ff0000000000000,1 +np.float64,0x5fe500b2bfca1,0x3ff0000000000000,1 +np.float64,0x9214abb924296,0x3ff0000000000000,1 +np.float64,0x800609fe0a2c13fd,0x3ff0000000000000,1 +np.float64,0x800c7c6fe518f8e0,0x3ff0000000000000,1 +np.float64,0x800a1a9491b4352a,0x3ff0000000000000,1 +np.float64,0x800b45e0e8968bc2,0x3ff0000000000000,1 +np.float64,0x8008497e57d092fd,0x3ff0000000000000,1 +np.float64,0x800b9c0af0173816,0x3ff0000000000000,1 +np.float64,0x800194cccb43299a,0x3ff0000000000000,1 +np.float64,0x8001c91ef183923f,0x3ff0000000000000,1 +np.float64,0x800f25b5ccde4b6c,0x3ff0000000000000,1 +np.float64,0x800ce63ccc79cc7a,0x3ff0000000000000,1 +np.float64,0x800d8fb2e83b1f66,0x3ff0000000000000,1 +np.float64,0x80083cd06f7079a1,0x3ff0000000000000,1 +np.float64,0x800823598e9046b3,0x3ff0000000000000,1 +np.float64,0x8001c1319de38264,0x3ff0000000000000,1 +np.float64,0x800f2b68543e56d1,0x3ff0000000000000,1 +np.float64,0x80022a4f4364549f,0x3ff0000000000000,1 +np.float64,0x800f51badf7ea376,0x3ff0000000000000,1 +np.float64,0x8003fbf31e27f7e7,0x3ff0000000000000,1 +np.float64,0x800d4c00e2fa9802,0x3ff0000000000000,1 +np.float64,0x800023b974804774,0x3ff0000000000000,1 +np.float64,0x800860778990c0ef,0x3ff0000000000000,1 +np.float64,0x800a15c241542b85,0x3ff0000000000000,1 +np.float64,0x8003097d9dc612fc,0x3ff0000000000000,1 +np.float64,0x800d77d8541aefb1,0x3ff0000000000000,1 +np.float64,0x80093804ab52700a,0x3ff0000000000000,1 +np.float64,0x800d2b3bfd7a5678,0x3ff0000000000000,1 +np.float64,0x800da24bcd5b4498,0x3ff0000000000000,1 +np.float64,0x8006eee1c28dddc4,0x3ff0000000000000,1 +np.float64,0x80005137fa40a271,0x3ff0000000000000,1 +np.float64,0x8007a3fbc22f47f8,0x3ff0000000000000,1 +np.float64,0x800dcd97071b9b2e,0x3ff0000000000000,1 +np.float64,0x80065b36048cb66d,0x3ff0000000000000,1 +np.float64,0x8004206ba72840d8,0x3ff0000000000000,1 +np.float64,0x8007e82b98cfd058,0x3ff0000000000000,1 +np.float64,0x8001a116ed23422f,0x3ff0000000000000,1 +np.float64,0x800c69e9ff18d3d4,0x3ff0000000000000,1 +np.float64,0x8003843688e7086e,0x3ff0000000000000,1 +np.float64,0x800335e3b8866bc8,0x3ff0000000000000,1 +np.float64,0x800e3308f0bc6612,0x3ff0000000000000,1 +np.float64,0x8002a9ec55c553d9,0x3ff0000000000000,1 +np.float64,0x80001c2084e03842,0x3ff0000000000000,1 +np.float64,0x800bc2bbd8d78578,0x3ff0000000000000,1 +np.float64,0x800ae6bcc555cd7a,0x3ff0000000000000,1 +np.float64,0x80083f7a13907ef5,0x3ff0000000000000,1 +np.float64,0x800d83ed76db07db,0x3ff0000000000000,1 +np.float64,0x800a12251974244b,0x3ff0000000000000,1 +np.float64,0x800a69c95714d393,0x3ff0000000000000,1 +np.float64,0x800cd5a85639ab51,0x3ff0000000000000,1 +np.float64,0x800e0e1837bc1c31,0x3ff0000000000000,1 +np.float64,0x8007b5ca39ef6b95,0x3ff0000000000000,1 +np.float64,0x800cf961cad9f2c4,0x3ff0000000000000,1 +np.float64,0x80066e8fc14cdd20,0x3ff0000000000000,1 +np.float64,0x8001cb8c7b43971a,0x3ff0000000000000,1 +np.float64,0x800002df68a005c0,0x3ff0000000000000,1 +np.float64,0x8003e6681567ccd1,0x3ff0000000000000,1 +np.float64,0x800b039126b60723,0x3ff0000000000000,1 +np.float64,0x800d2e1b663a5c37,0x3ff0000000000000,1 +np.float64,0x800188b3e2a31169,0x3ff0000000000000,1 +np.float64,0x8001f272e943e4e7,0x3ff0000000000000,1 +np.float64,0x800d7f53607afea7,0x3ff0000000000000,1 +np.float64,0x80092cafa4f25960,0x3ff0000000000000,1 +np.float64,0x800fc009f07f8014,0x3ff0000000000000,1 +np.float64,0x8003da896507b514,0x3ff0000000000000,1 +np.float64,0x800d4d1b4c3a9a37,0x3ff0000000000000,1 +np.float64,0x8007a835894f506c,0x3ff0000000000000,1 +np.float64,0x80057ba0522af741,0x3ff0000000000000,1 +np.float64,0x8009b7054b336e0b,0x3ff0000000000000,1 +np.float64,0x800b2c6c125658d9,0x3ff0000000000000,1 +np.float64,0x8008b1840ad16308,0x3ff0000000000000,1 +np.float64,0x8007ea0e3befd41d,0x3ff0000000000000,1 +np.float64,0x800dd658683bacb1,0x3ff0000000000000,1 +np.float64,0x8008cda48fd19b49,0x3ff0000000000000,1 +np.float64,0x8003acca14c75995,0x3ff0000000000000,1 +np.float64,0x8008bd152d717a2b,0x3ff0000000000000,1 +np.float64,0x80010d1ea3621a3e,0x3ff0000000000000,1 +np.float64,0x800130b78b826170,0x3ff0000000000000,1 +np.float64,0x8002cf3a46e59e75,0x3ff0000000000000,1 +np.float64,0x800b76e7fa76edd0,0x3ff0000000000000,1 +np.float64,0x800e065fe1dc0cc0,0x3ff0000000000000,1 +np.float64,0x8000dd527ea1baa6,0x3ff0000000000000,1 +np.float64,0x80032cb234665965,0x3ff0000000000000,1 +np.float64,0x800affc1acb5ff84,0x3ff0000000000000,1 +np.float64,0x80074be23fee97c5,0x3ff0000000000000,1 +np.float64,0x8004f83eafc9f07e,0x3ff0000000000000,1 +np.float64,0x800b02a115560543,0x3ff0000000000000,1 +np.float64,0x800b324a55766495,0x3ff0000000000000,1 +np.float64,0x800ffbcfd69ff7a0,0x3ff0000000000000,1 +np.float64,0x800830bc7b906179,0x3ff0000000000000,1 +np.float64,0x800cbafe383975fd,0x3ff0000000000000,1 +np.float64,0x8001ee42bfe3dc86,0x3ff0000000000000,1 +np.float64,0x8005b00fdc0b6020,0x3ff0000000000000,1 +np.float64,0x8005e7addd0bcf5c,0x3ff0000000000000,1 +np.float64,0x8001ae4cb0635c9a,0x3ff0000000000000,1 +np.float64,0x80098a9941131533,0x3ff0000000000000,1 +np.float64,0x800334c929466993,0x3ff0000000000000,1 +np.float64,0x8009568239d2ad05,0x3ff0000000000000,1 +np.float64,0x800f0639935e0c73,0x3ff0000000000000,1 +np.float64,0x800cebce7499d79d,0x3ff0000000000000,1 +np.float64,0x800482ee4c2905dd,0x3ff0000000000000,1 +np.float64,0x8007b7bd9e2f6f7c,0x3ff0000000000000,1 +np.float64,0x3fe654469f2ca88d,0x3fe8853f6c01ffb3,1 +np.float64,0x3feb4d7297369ae5,0x3fe50ad5bb621408,1 +np.float64,0x3feef53ba43dea77,0x3fe2283f356f8658,1 +np.float64,0x3fddf564eabbeaca,0x3fec8ec0e0dead9c,1 +np.float64,0x3fd3a69078274d21,0x3fee80e05c320000,1 +np.float64,0x3fecdafe5d39b5fd,0x3fe3d91a5d440fd9,1 +np.float64,0x3fd93286bc32650d,0x3fed8d40696cd10e,1 +np.float64,0x3fc0d34eb821a69d,0x3fefb954023d4284,1 +np.float64,0x3fc7b4b9a02f6973,0x3fef73e8739787ce,1 +np.float64,0x3fe08c839a611907,0x3febd0bc6f5641cd,1 +np.float64,0x3fb3d1758627a2eb,0x3fefe776f6183f96,1 +np.float64,0x3fef93c9ff3f2794,0x3fe1a4d2f622627d,1 +np.float64,0x3fea8d0041351a01,0x3fe59a52a1c78c9e,1 +np.float64,0x3fe3e26a30e7c4d4,0x3fea04ad3e0bbf8d,1 +np.float64,0x3fe5a34c9f6b4699,0x3fe8f57c5ccd1eab,1 +np.float64,0x3fc21ef859243df1,0x3fefae0b68a3a2e7,1 +np.float64,0x3fed7dd585fafbab,0x3fe35860041e5b0d,1 +np.float64,0x3fe5abacf22b575a,0x3fe8f03d8b6ef0f2,1 +np.float64,0x3fe426451f284c8a,0x3fe9dcf21f13205b,1 +np.float64,0x3fc01f6456203ec9,0x3fefbf19e2a8e522,1 +np.float64,0x3fe1cf2772239e4f,0x3feb2bbd645c7697,1 +np.float64,0x3fd18c4ace231896,0x3feecdfdd086c110,1 +np.float64,0x3fe8387d5b7070fb,0x3fe74358f2ec4910,1 +np.float64,0x3fdce51c2239ca38,0x3feccb2ae5459632,1 +np.float64,0x3fe5b0f2e4eb61e6,0x3fe8ecef4dbe4277,1 +np.float64,0x3fe1ceeb08a39dd6,0x3feb2bdd4dcfb3df,1 +np.float64,0x3febc5899d778b13,0x3fe4afc8dd8ad228,1 +np.float64,0x3fe7a47fbe2f48ff,0x3fe7a7fd9b352ea5,1 +np.float64,0x3fe7f74e1fafee9c,0x3fe76feb2755b247,1 +np.float64,0x3fe2bfad04e57f5a,0x3feaa9b46adddaeb,1 +np.float64,0x3fd06a090320d412,0x3feef40c334f8fba,1 +np.float64,0x3fdc97297d392e53,0x3fecdc16a3e22fcb,1 +np.float64,0x3fdc1a3f3838347e,0x3fecf6db2769d404,1 +np.float64,0x3fcca90096395201,0x3fef338156fcd218,1 +np.float64,0x3fed464733fa8c8e,0x3fe38483f0465d91,1 +np.float64,0x3fe7e067d82fc0d0,0x3fe77f7c8c9de896,1 +np.float64,0x3fc014fa0b2029f4,0x3fefbf6d84c933f8,1 +np.float64,0x3fd3bf1524277e2a,0x3fee7d2997b74dec,1 +np.float64,0x3fec153b86782a77,0x3fe472bb5497bb2a,1 +np.float64,0x3fd3e4d9d5a7c9b4,0x3fee776842691902,1 +np.float64,0x3fea6c0e2c74d81c,0x3fe5b2954cb458d9,1 +np.float64,0x3fee8f6a373d1ed4,0x3fe27bb9e348125b,1 +np.float64,0x3fd30c6dd42618dc,0x3fee97d2cab2b0bc,1 +np.float64,0x3fe4f90e6d69f21d,0x3fe95ea3dd4007f2,1 +np.float64,0x3fe271d467e4e3a9,0x3fead470d6d4008b,1 +np.float64,0x3fef2983897e5307,0x3fe1fd1a4debe33b,1 +np.float64,0x3fe980cc83b30199,0x3fe65d2fb8a0eb46,1 +np.float64,0x3fdfdf53db3fbea8,0x3fec1cf95b2a1cc7,1 +np.float64,0x3fe4d5307ba9aa61,0x3fe974701b4156cb,1 +np.float64,0x3fdb4e2345b69c47,0x3fed21aa6c146512,1 +np.float64,0x3fe3f7830327ef06,0x3fe9f85f6c88c2a8,1 +np.float64,0x3fca915fb63522bf,0x3fef502b73a52ecf,1 +np.float64,0x3fe66d3709ecda6e,0x3fe87531d7372d7a,1 +np.float64,0x3fd86000bcb0c001,0x3fedb5018dd684ca,1 +np.float64,0x3fe516e5feea2dcc,0x3fe94c68b111404e,1 +np.float64,0x3fd83c53dd3078a8,0x3fedbb9e5dd9e165,1 +np.float64,0x3fedfeeb673bfdd7,0x3fe2f0f0253c5d5d,1 +np.float64,0x3fe0dc6f9c21b8df,0x3feba8e2452410c2,1 +np.float64,0x3fbe154d643c2a9b,0x3fefc780a9357457,1 +np.float64,0x3fe5f63986abec73,0x3fe8c1434951a40a,1 +np.float64,0x3fbce0e50839c1ca,0x3fefcbeeaa27de75,1 +np.float64,0x3fd7ef5c5c2fdeb9,0x3fedc9c3022495b3,1 +np.float64,0x3fc1073914220e72,0x3fefb79de80fc0fd,1 +np.float64,0x3fe1a93c3d235278,0x3feb3fb21f86ac67,1 +np.float64,0x3fe321ee53e643dd,0x3fea72e2999f1e22,1 +np.float64,0x3fa881578c3102af,0x3feff69e6e51e0d6,1 +np.float64,0x3fd313482a262690,0x3fee96d161199495,1 +np.float64,0x3fe7272cd6ae4e5a,0x3fe7fbacbd0d8f43,1 +np.float64,0x3fd6cf4015ad9e80,0x3fedfd3513d544b8,1 +np.float64,0x3fc67b7e6d2cf6fd,0x3fef81f5c16923a4,1 +np.float64,0x3fa1999c14233338,0x3feffb2913a14184,1 +np.float64,0x3fc74eb8dd2e9d72,0x3fef78909a138e3c,1 +np.float64,0x3fc0b9274921724f,0x3fefba2ebd5f3e1c,1 +np.float64,0x3fd53fa156aa7f43,0x3fee40a18e952e88,1 +np.float64,0x3feaccbca4b59979,0x3fe56b22b33eb713,1 +np.float64,0x3fe6a01e3a2d403c,0x3fe8543fbd820ecc,1 +np.float64,0x3fd392a869a72551,0x3fee83e0ffe0e8de,1 +np.float64,0x3fe44d8928689b12,0x3fe9c5bf3c8fffdb,1 +np.float64,0x3fca3f209f347e41,0x3fef5461b6fa0924,1 +np.float64,0x3fee9e84b07d3d09,0x3fe26f638f733549,1 +np.float64,0x3faf49acb03e9359,0x3feff0b583cd8c48,1 +np.float64,0x3fea874b2af50e96,0x3fe59e882fa6febf,1 +np.float64,0x3fc50b72772a16e5,0x3fef918777dc41be,1 +np.float64,0x3fe861d1d4f0c3a4,0x3fe726e44d9d42c2,1 +np.float64,0x3fcadd2e2535ba5c,0x3fef4c3e2b56da38,1 +np.float64,0x3fea59c29cb4b385,0x3fe5c0043e586439,1 +np.float64,0x3fc1ffef0d23ffde,0x3fefaf22be452d13,1 +np.float64,0x3fc2d8dbc125b1b8,0x3fefa75b646d8e4e,1 +np.float64,0x3fd66c6471acd8c9,0x3fee0e5038b895c0,1 +np.float64,0x3fd0854adfa10a96,0x3feef0945bcc5c99,1 +np.float64,0x3feaac7076f558e1,0x3fe58316c23a82ad,1 +np.float64,0x3fdda49db3bb493b,0x3feca0e347c0ad6f,1 +np.float64,0x3fe43a539de874a7,0x3fe9d11d722d4822,1 +np.float64,0x3feeee3ebbfddc7d,0x3fe22dffd251e9af,1 +np.float64,0x3f8ee2c5b03dc58b,0x3fefff11855a7b6c,1 +np.float64,0x3fcd7107c63ae210,0x3fef2840bb55ca52,1 +np.float64,0x3f8d950d203b2a1a,0x3fefff253a08e40e,1 +np.float64,0x3fd40a5e57a814bd,0x3fee71a633c761fc,1 +np.float64,0x3fee836ec83d06de,0x3fe28580975be2fd,1 +np.float64,0x3fd7bbe87f2f77d1,0x3fedd31f661890cc,1 +np.float64,0xbfe05bf138a0b7e2,0x3febe8a000d96e47,1 +np.float64,0xbf88bddd90317bc0,0x3fefff66f6e2ff26,1 +np.float64,0xbfdc9cbb12393976,0x3fecdae2982335db,1 +np.float64,0xbfd85b4eccb0b69e,0x3fedb5e0dd87f702,1 +np.float64,0xbfe5c326cb2b864e,0x3fe8e180f525fa12,1 +np.float64,0xbfe381a0e4a70342,0x3fea3c8e5e3ab78e,1 +np.float64,0xbfe58d892c2b1b12,0x3fe9031551617aed,1 +np.float64,0xbfd7f3a52cafe74a,0x3fedc8fa97edd080,1 +np.float64,0xbfef3417bc7e682f,0x3fe1f45989f6a009,1 +np.float64,0xbfddfb8208bbf704,0x3fec8d5fa9970773,1 +np.float64,0xbfdab69bcc356d38,0x3fed40b2f6c347c6,1 +np.float64,0xbfed3f7cf17a7efa,0x3fe389e4ff4d9235,1 +np.float64,0xbfe47675d9a8ecec,0x3fe9ad6829a69e94,1 +np.float64,0xbfd030e2902061c6,0x3feefb3f811e024f,1 +np.float64,0xbfc376ac7226ed58,0x3fefa1798712b37e,1 +np.float64,0xbfdb7e54a0b6fcaa,0x3fed17a974c4bc28,1 +np.float64,0xbfdb7d5d5736faba,0x3fed17dcf31a8d84,1 +np.float64,0xbf876bd6502ed7c0,0x3fefff76dce6232c,1 +np.float64,0xbfd211e6c02423ce,0x3feebba41f0a1764,1 +np.float64,0xbfb443e3962887c8,0x3fefe658953629d4,1 +np.float64,0xbfe81b09e9b03614,0x3fe757882e4fdbae,1 +np.float64,0xbfdcb905d2b9720c,0x3fecd4c22cfe84e5,1 +np.float64,0xbfe3b62d99276c5b,0x3fea1e5520b3098d,1 +np.float64,0xbfbf05b25c3e0b68,0x3fefc3ecc04bca8e,1 +np.float64,0xbfdedc885b3db910,0x3fec59e22feb49f3,1 +np.float64,0xbfe33aa282667545,0x3fea64f2d55ec471,1 +np.float64,0xbfec84745a3908e9,0x3fe41cb3214e7044,1 +np.float64,0xbfddefdff1bbdfc0,0x3fec8fff88d4d0ec,1 +np.float64,0xbfd26ae6aca4d5ce,0x3feeaf208c7fedf6,1 +np.float64,0xbfee010591fc020b,0x3fe2ef3e57211a5e,1 +np.float64,0xbfb8cfddca319fb8,0x3fefd98d8f7918ed,1 +np.float64,0xbfe991648f3322c9,0x3fe6514e54670bae,1 +np.float64,0xbfee63fd087cc7fa,0x3fe29f1bfa3297cc,1 +np.float64,0xbfe1685942a2d0b2,0x3feb617f5f839eee,1 +np.float64,0xbfc6fc2fd62df860,0x3fef7c4698fd58cf,1 +np.float64,0xbfe42723d3a84e48,0x3fe9dc6ef7243e90,1 +np.float64,0xbfc3a7e89d274fd0,0x3fef9f99e3314e77,1 +np.float64,0xbfeb4c9521f6992a,0x3fe50b7c919bc6d8,1 +np.float64,0xbf707b34e020f680,0x3fefffef05e30264,1 +np.float64,0xbfc078478e20f090,0x3fefbc479305d5aa,1 +np.float64,0xbfd494ac4ca92958,0x3fee5c11f1cd8269,1 +np.float64,0xbfdaf888a035f112,0x3fed3346ae600469,1 +np.float64,0xbfa5d8ed502bb1e0,0x3feff88b0f262609,1 +np.float64,0xbfeec0cbfffd8198,0x3fe253543b2371cb,1 +np.float64,0xbfe594b5986b296b,0x3fe8fe9b39fb3940,1 +np.float64,0xbfc8ece7c631d9d0,0x3fef652bd0611ac7,1 +np.float64,0xbfd8ffeca0b1ffda,0x3fed96ebdf9b65cb,1 +np.float64,0xbfba9b221e353648,0x3fefd3cc21e2f15c,1 +np.float64,0xbfca63a52c34c74c,0x3fef52848eb9ed3b,1 +np.float64,0xbfe588e9b06b11d4,0x3fe905f7403e8881,1 +np.float64,0xbfc76f82db2edf04,0x3fef77138fe9bbc2,1 +np.float64,0xbfeeb3f334bd67e6,0x3fe25ddadb1096d6,1 +np.float64,0xbfbf2b64ce3e56c8,0x3fefc35a9555f6df,1 +np.float64,0xbfe9920e4ff3241c,0x3fe650d4ab8f5c42,1 +np.float64,0xbfb4a54c02294a98,0x3fefe55fc85ae5e9,1 +np.float64,0xbfe353b0c766a762,0x3fea56c02d17e4b7,1 +np.float64,0xbfd99961a4b332c4,0x3fed795fcd00dbf9,1 +np.float64,0xbfef191ddabe323c,0x3fe20aa79524f636,1 +np.float64,0xbfb25d060224ba10,0x3fefeaeee5cc8c0b,1 +np.float64,0xbfe6022428ec0448,0x3fe8b9b46e776194,1 +np.float64,0xbfed1a236cba3447,0x3fe3a76bee0d9861,1 +np.float64,0xbfc59671e72b2ce4,0x3fef8bc4daef6f14,1 +np.float64,0xbfdf2711703e4e22,0x3fec4886a8c9ceb5,1 +np.float64,0xbfeb7e207536fc41,0x3fe4e610c783f168,1 +np.float64,0xbfe6cdf5bcad9bec,0x3fe8365f8a59bc81,1 +np.float64,0xbfe55294adaaa52a,0x3fe927b0af5ccd09,1 +np.float64,0xbfdf4a88913e9512,0x3fec4036df58ba74,1 +np.float64,0xbfebb7efe4376fe0,0x3fe4ba276006992d,1 +np.float64,0xbfe09f29cfa13e54,0x3febc77f4f9c95e7,1 +np.float64,0xbfdf8c75653f18ea,0x3fec30ac924e4f46,1 +np.float64,0xbfefd601c7ffac04,0x3fe16d6f21bcb9c1,1 +np.float64,0xbfeae97ff5f5d300,0x3fe555bb5b87efe9,1 +np.float64,0xbfed427f02fa84fe,0x3fe387830db093bc,1 +np.float64,0xbfa33909cc267210,0x3feffa3a1bcb50dd,1 +np.float64,0xbfe9aa4bf5f35498,0x3fe63f6e98f6aa0f,1 +np.float64,0xbfe2d7349b25ae69,0x3fea9caa7c331e7e,1 +np.float64,0xbfcdbb2a3a3b7654,0x3fef2401c9659e4b,1 +np.float64,0xbfc8a90919315214,0x3fef686fe7fc0513,1 +np.float64,0xbfe62a98df2c5532,0x3fe89ff22a02cc6b,1 +np.float64,0xbfdc0f67b3b81ed0,0x3fecf928b637798f,1 +np.float64,0xbfebb32bf6f76658,0x3fe4bdc893c09698,1 +np.float64,0xbfec067996380cf3,0x3fe47e132741db97,1 +np.float64,0xbfd9774e1d32ee9c,0x3fed7ffe1e87c434,1 +np.float64,0xbfef989890bf3131,0x3fe1a0d025c80cf4,1 +np.float64,0xbfe59887e62b3110,0x3fe8fc382a3d4197,1 +np.float64,0xbfdea0a11e3d4142,0x3fec67b987e236ec,1 +np.float64,0xbfe2ec495825d892,0x3fea90efb231602d,1 +np.float64,0xbfb329c5c2265388,0x3fefe90f1b8209c3,1 +np.float64,0xbfdcd2dcd339a5ba,0x3feccf24c60b1478,1 +np.float64,0xbfe537ea18aa6fd4,0x3fe938237e217fe0,1 +np.float64,0xbfe8675ce170ceba,0x3fe723105925ce3a,1 +np.float64,0xbfd70723acae0e48,0x3fedf369ac070e65,1 +np.float64,0xbfea9d8692b53b0d,0x3fe58e1ee42e3fdb,1 +np.float64,0xbfcfeb96653fd72c,0x3fef029770033bdc,1 +np.float64,0xbfcc06c92d380d94,0x3fef3c69797d9b0a,1 +np.float64,0xbfe16b7c4f62d6f8,0x3feb5fdf9f0a9a07,1 +np.float64,0xbfed4d7a473a9af4,0x3fe37ecee27b1eb7,1 +np.float64,0xbfe6a6f6942d4ded,0x3fe84fccdf762b19,1 +np.float64,0xbfda46d867348db0,0x3fed572d928fa657,1 +np.float64,0xbfdbd9482db7b290,0x3fed049b5f907b52,1 +np.float64,0x7fe992ceb933259c,0xbfeb15af92aad70e,1 +np.float64,0x7fe3069204a60d23,0xbfe5eeff454240e9,1 +np.float64,0x7fe729dbf32e53b7,0xbfefe0528a330e4c,1 +np.float64,0x7fec504fb638a09e,0x3fd288e95dbedf65,1 +np.float64,0x7fe1d30167a3a602,0xbfeffc41f946fd02,1 +np.float64,0x7fed7f8ffd3aff1f,0x3fefe68ec604a19d,1 +np.float64,0x7fd2f23635a5e46b,0x3fea63032efbb447,1 +np.float64,0x7fd4c86db1a990da,0x3fdf6b9f7888db5d,1 +np.float64,0x7fe7554db6eeaa9a,0x3fe1b41476861bb0,1 +np.float64,0x7fe34e823ba69d03,0x3fefc435532e6294,1 +np.float64,0x7fec5c82fef8b905,0x3fef8f0c6473034f,1 +np.float64,0x7feba221bff74442,0xbfea95b81eb19b47,1 +np.float64,0x7fe74808a5ae9010,0xbfd3aa322917c3e5,1 +np.float64,0x7fdf41b7e0be836f,0x3fd14283c7147282,1 +np.float64,0x7fec09892f381311,0x3fe5240376ae484b,1 +np.float64,0x7faaf80bf435f017,0x3fe20227fa811423,1 +np.float64,0x7f8422d8402845b0,0x3fe911714593b8a0,1 +np.float64,0x7fd23a7fada474fe,0x3feff9f40aa37e9c,1 +np.float64,0x7fef4a4806fe948f,0x3fec6eca89cb4a62,1 +np.float64,0x7fe1e71cf763ce39,0xbfea6ac63f9ba457,1 +np.float64,0x7fe3e555be27caaa,0xbfe75b305d0dbbfd,1 +np.float64,0x7fcb8bac96371758,0xbfe8b126077f9d4c,1 +np.float64,0x7fc98e2c84331c58,0x3fef9092eb0bc85a,1 +np.float64,0x7fe947cf2b728f9d,0xbfebfff2c5b7d198,1 +np.float64,0x7feee8058c3dd00a,0xbfef21ebaae2eb17,1 +np.float64,0x7fef61d8d5bec3b1,0xbfdf1a032fb1c864,1 +np.float64,0x7fcf714b6f3ee296,0x3fe6fc89a8084098,1 +np.float64,0x7fa9a8b44c335168,0xbfeb16c149cea943,1 +np.float64,0x7fd175c482a2eb88,0xbfef64d341e73f88,1 +np.float64,0x7feab8e6a87571cc,0x3feb10069c397464,1 +np.float64,0x7fe3ade72de75bcd,0x3fd1753e333d5790,1 +np.float64,0x7fb26d87d224db0f,0xbfe753d36b18f4ca,1 +np.float64,0x7fdb7ef159b6fde2,0x3fe5c0a6044d3607,1 +np.float64,0x7fd5af86422b5f0c,0x3fe77193c95f6484,1 +np.float64,0x7fee9e00b07d3c00,0x3fe864d494596845,1 +np.float64,0x7fef927a147f24f3,0xbfe673b14715693d,1 +np.float64,0x7fd0aea63c215d4b,0xbfeff435f119fce9,1 +np.float64,0x7fd02e3796a05c6e,0x3fe4f7e3706e9a3d,1 +np.float64,0x7fd3ed61da27dac3,0xbfefef2f057f168c,1 +np.float64,0x7fefaca0d4ff5941,0x3fd3e8ad205cd4ab,1 +np.float64,0x7feb659e06f6cb3b,0x3fd64d803203e027,1 +np.float64,0x7fc94ccfaf32999e,0x3fee04922209369a,1 +np.float64,0x7feb4ec294f69d84,0xbfd102763a056c89,1 +np.float64,0x7fe2ada6ac655b4c,0x3fef4f6792aa6093,1 +np.float64,0x7fe5f40fdc2be81f,0xbfb4a6327186eee8,1 +np.float64,0x7fe7584bc3eeb097,0xbfd685b8ff94651d,1 +np.float64,0x7fe45d276be8ba4e,0x3fee53b13f7e442f,1 +np.float64,0x7fe6449b3d6c8935,0xbfe7e08bafa75251,1 +np.float64,0x7f8d62e6b03ac5cc,0x3fe73d30762f38fd,1 +np.float64,0x7fe3a76f72a74ede,0xbfeb48a28bc60968,1 +np.float64,0x7fd057706920aee0,0x3fdece8fa06f626c,1 +np.float64,0x7fe45ae158e8b5c2,0x3fe7a70f47b4d349,1 +np.float64,0x7fea8a5a983514b4,0x3fefb053d5f9ddd7,1 +np.float64,0x7fdd1e86ab3a3d0c,0x3fe3cded1b93816b,1 +np.float64,0x7fdb456108b68ac1,0xbfe37574c0b9bf8f,1 +np.float64,0x7fe972602432e4bf,0x3fef9a26e65ec01c,1 +np.float64,0x7fdbe2385637c470,0x3fed541df57969e1,1 +np.float64,0x7fe57f03602afe06,0x3fbd90f595cbbd94,1 +np.float64,0x7feb0ceb68f619d6,0xbfeae9cb8ee5261f,1 +np.float64,0x7fe6abfe6c6d57fc,0xbfef40a6edaca26f,1 +np.float64,0x7fe037ea08606fd3,0xbfda817d75858597,1 +np.float64,0x7fdd75a52dbaeb49,0x3feef2a0d91d6aa1,1 +np.float64,0x7fe8f9af66b1f35e,0xbfedfceef2a3bfc9,1 +np.float64,0x7fedf762b53beec4,0x3fd8b4f21ef69ee3,1 +np.float64,0x7fe99295b7f3252a,0x3feffc24d970383e,1 +np.float64,0x7fe797b0172f2f5f,0x3fee089aa56f7ce8,1 +np.float64,0x7fed89dcc97b13b9,0xbfcfa2bb0c3ea41f,1 +np.float64,0x7fae9e8d5c3d3d1a,0xbfe512ffe16c6b08,1 +np.float64,0x7fefaecbe27f5d97,0x3fbfc718a5e972f1,1 +np.float64,0x7fce0236d93c046d,0xbfa9b7cd790db256,1 +np.float64,0x7fa9689aac32d134,0x3feced501946628a,1 +np.float64,0x7feb1469e93628d3,0x3fef2a988e7673ed,1 +np.float64,0x7fdba78344b74f06,0xbfe092e78965b30c,1 +np.float64,0x7fece54c3fb9ca97,0x3fd3cfd184bed2e6,1 +np.float64,0x7fdb84212b370841,0xbfe25ebf2db6ee55,1 +np.float64,0x7fbe3e8bf23c7d17,0x3fe2ee72df573345,1 +np.float64,0x7fe43d9803687b2f,0xbfed2eff6a9e66a0,1 +np.float64,0x7fb0f9c00a21f37f,0x3feff70f3276fdb7,1 +np.float64,0x7fea0c6cbbb418d8,0xbfefa612494798b2,1 +np.float64,0x7fe4b3239e296646,0xbfe74dd959af8cdc,1 +np.float64,0x7fe5c6a773eb8d4e,0xbfd06944048f8d2b,1 +np.float64,0x7fb1c1278223824e,0xbfeb533a34655bde,1 +np.float64,0x7fd21c09ee243813,0xbfe921ccbc9255c3,1 +np.float64,0x7fe051020c20a203,0x3fbd519d700c1f2f,1 +np.float64,0x7fe0c76845e18ed0,0x3fefb9595191a31b,1 +np.float64,0x7fe6b0b57b6d616a,0xbf8c59a8ba5fcd9a,1 +np.float64,0x7fd386c460270d88,0x3fe8ffea5d1a5c46,1 +np.float64,0x7feeb884713d7108,0x3fee9b2247ef6c0d,1 +np.float64,0x7fd85f71b6b0bee2,0xbfefc30ec3e28f07,1 +np.float64,0x7fc341366426826c,0x3fd4234d35386d3b,1 +np.float64,0x7fe56482dd6ac905,0x3fe7189de6a50668,1 +np.float64,0x7fec67a2e3f8cf45,0xbfef86d0b940f37f,1 +np.float64,0x7fe38b202fe7163f,0x3feb90b75caa2030,1 +np.float64,0x7fdcbc64883978c8,0x3fed4f758fbf64d4,1 +np.float64,0x7fea5f0598f4be0a,0x3fdd503a417b3d4d,1 +np.float64,0x7fda3b6bcf3476d7,0x3fea6e9af3f7f9f5,1 +np.float64,0x7fc7d7896c2faf12,0x3fda2bebc36a2363,1 +np.float64,0x7fe7e8e2626fd1c4,0xbfe7d5e390c4cc3f,1 +np.float64,0x7fde0f3d7abc1e7a,0xbfede7a0ecfa3606,1 +np.float64,0x7fc692b8f52d2571,0x3feff0cd7ab6f61b,1 +np.float64,0xff92d1fce825a400,0xbfc921c36fc014fa,1 +np.float64,0xffdec3af2fbd875e,0xbfed6a77e6a0364e,1 +np.float64,0xffef46e7d9be8dcf,0xbfed7d39476f7e27,1 +np.float64,0xffe2c2ce4525859c,0x3fe1757261316bc9,1 +np.float64,0xffe27c8b5864f916,0xbfefe017c0d43457,1 +np.float64,0xffe184d7442309ae,0x3fa1fb8c49dba596,1 +np.float64,0xffddf5f98d3bebf4,0x3fee4f8eaa5f847e,1 +np.float64,0xffee3ef354fc7de6,0xbfebfd60fa51b2ba,1 +np.float64,0xffdecb3e85bd967e,0x3fbfad2667a8b468,1 +np.float64,0xffe4ee900b29dd20,0xbfdc02dc626f91cd,1 +np.float64,0xffd3179f6da62f3e,0xbfe2cfe442511776,1 +np.float64,0xffe99ef7cef33def,0x3f50994542a7f303,1 +np.float64,0xffe2b66b1ae56cd6,0xbfefe3e066eb6329,1 +np.float64,0xff8f72aff03ee540,0x3fe9c46224cf5003,1 +np.float64,0xffd29beb85a537d8,0x3fefcb0b6166be71,1 +np.float64,0xffaef02d4c3de060,0xbfef5fb71028fc72,1 +np.float64,0xffd39a2a89273456,0x3fe6d4b183205dca,1 +np.float64,0xffef8a9392ff1526,0x3fedb99fbf402468,1 +np.float64,0xffb9b3f31e3367e8,0x3fee1005270fcf80,1 +np.float64,0xffed9d5c693b3ab8,0x3fd110f4b02365d5,1 +np.float64,0xffeaba45f9f5748b,0x3fe499e0a6f4afb2,1 +np.float64,0xffdba3f70d3747ee,0xbfca0c30493ae519,1 +np.float64,0xffa35b985426b730,0xbfdb625df56bcf45,1 +np.float64,0xffccbc9728397930,0x3fc53cbc59020704,1 +np.float64,0xffef73c942bee792,0xbfdc647a7a5e08be,1 +np.float64,0xffcb5acfb236b5a0,0x3feeb4ec038c39fc,1 +np.float64,0xffea116fe2b422df,0x3fefe03b6ae0b435,1 +np.float64,0xffe97de6e7b2fbcd,0xbfd2025698fab9eb,1 +np.float64,0xffdddba314bbb746,0x3fd31f0fdb8f93be,1 +np.float64,0xffd613a24a2c2744,0xbfebbb1efae884b3,1 +np.float64,0xffe3d938aa67b271,0xbfc2099cead3d3be,1 +np.float64,0xffdf08c2e33e1186,0xbfefd236839b900d,1 +np.float64,0xffea6ba8bd34d751,0x3fe8dfc032114719,1 +np.float64,0xffe3202083e64040,0x3fed513b81432a22,1 +np.float64,0xffb2397db62472f8,0xbfee7d7fe1c3f76c,1 +np.float64,0xffd9d0682ab3a0d0,0x3fe0bcf9e531ad79,1 +np.float64,0xffc293df202527c0,0xbfe58d0bdece5e64,1 +np.float64,0xffe1422c7da28458,0xbf81bd72595f2341,1 +np.float64,0xffd64e4ed4ac9c9e,0x3fa4334cc011c703,1 +np.float64,0xffe40a970ae8152e,0x3fead3d258b55b7d,1 +np.float64,0xffc8c2f2223185e4,0xbfef685f07c8b9fd,1 +np.float64,0xffe4b2f7216965ee,0x3fe3861d3d896a83,1 +np.float64,0xffdb531db3b6a63c,0x3fe18cb8332dd59d,1 +np.float64,0xffe8e727a3b1ce4e,0xbfe57b15abb677b9,1 +np.float64,0xffe530c1e12a6184,0xbfb973ea5535e48f,1 +np.float64,0xffe6f7849cedef08,0x3fd39a37ec5af4b6,1 +np.float64,0xffead62a78b5ac54,0x3fe69b3f6c7aa24b,1 +np.float64,0xffeefdd725fdfbad,0xbfc08a456111fdd5,1 +np.float64,0xffe682182fed0430,0x3fecc7c1292761d2,1 +np.float64,0xffee0ca8dcbc1951,0x3fef6cc361ef2c19,1 +np.float64,0xffec9b338f393666,0x3fefa9ab8e0471b5,1 +np.float64,0xffe13c5e29a278bc,0xbfef8da74ad83398,1 +np.float64,0xffd7bd48c62f7a92,0x3fe3468cd4ac9d34,1 +np.float64,0xffedd0ed14bba1d9,0xbfd563a83477077b,1 +np.float64,0xffe86b83f3f0d707,0x3fe9eb3c658e4b2d,1 +np.float64,0xffd6a4db4bad49b6,0xbfc7e11276166e17,1 +np.float64,0xffc29e8404253d08,0x3fd35971961c789f,1 +np.float64,0xffe27cf3d664f9e7,0xbfeca0f73c72f810,1 +np.float64,0xffc34152352682a4,0x3fef384e564c002c,1 +np.float64,0xffe395728ba72ae4,0x3f8fe18c2de86eba,1 +np.float64,0xffed86c4fbbb0d89,0x3fef709db881c672,1 +np.float64,0xffe8a98d37f1531a,0x3fd4879c8f73c3dc,1 +np.float64,0xffb8ce9fea319d40,0xbfb853c8fe46b08d,1 +np.float64,0xffe7f26db8efe4db,0xbfec1cfd3e5c2ac1,1 +np.float64,0xffd7935b77af26b6,0x3fb7368c89b2a460,1 +np.float64,0xffc5840ed02b081c,0x3fd92220b56631f3,1 +np.float64,0xffc36a873926d510,0x3fa84d61baf61811,1 +np.float64,0xffe06ea583e0dd4a,0x3feb647e348b9e39,1 +np.float64,0xffe6a33031ed4660,0xbfe096b851dc1a0a,1 +np.float64,0xffe001c938e00392,0x3fe4eece77623e7a,1 +np.float64,0xffc1e4f23b23c9e4,0xbfdb9bb1f83f6ac4,1 +np.float64,0xffecd3ecbab9a7d9,0x3fbafb1f800f177d,1 +np.float64,0xffc2d3016825a604,0xbfef650e8b0d6afb,1 +np.float64,0xffe222cb68e44596,0x3fde3690e44de5bd,1 +np.float64,0xffe5bb145e2b7628,0x3fedbb98e23c9dc1,1 +np.float64,0xffe9e5823b73cb04,0xbfee41661016c03c,1 +np.float64,0xffd234a00ba46940,0x3fda0312cda580c2,1 +np.float64,0xffe0913ed6e1227d,0xbfed508bb529bd23,1 +np.float64,0xffe8e3596171c6b2,0xbfdc33e1c1d0310e,1 +np.float64,0xffef9c6835ff38cf,0x3fea8ce6d27dfba3,1 +np.float64,0xffdd3bcf66ba779e,0x3fe50523d2b6470e,1 +np.float64,0xffe57e8cf06afd1a,0xbfee600933347247,1 +np.float64,0xffe0d8c65fa1b18c,0x3fe75091f93d5e4c,1 +np.float64,0xffea7c8c16b4f918,0x3fee681724795198,1 +np.float64,0xffe34f7a05269ef4,0xbfe3c3e179676f13,1 +np.float64,0xffd28894a6a5112a,0xbfe5d1027aee615d,1 +np.float64,0xffc73be6f22e77cc,0x3fe469bbc08b472a,1 +np.float64,0xffe7f71b066fee36,0x3fe7ed136c8fdfaa,1 +np.float64,0xffebc13e29f7827c,0x3fefcdc6e677d314,1 +np.float64,0xffd53e9c942a7d3a,0x3fea5a02c7341749,1 +np.float64,0xffd7191b23ae3236,0x3fea419b66023443,1 +np.float64,0xffe9480325b29006,0xbfefeaff5fa38cd5,1 +np.float64,0xffba46dc0e348db8,0xbfefa54f4de28eba,1 +np.float64,0xffdd4cc31eba9986,0x3fe60bb41fe1c4da,1 +np.float64,0xffe13a70dea274e1,0xbfaa9192f7bd6c9b,1 +np.float64,0xffde25127bbc4a24,0x3f7c75f45e29be7d,1 +np.float64,0xffe4076543a80eca,0x3fea5aad50d2f687,1 +np.float64,0xffe61512acec2a25,0xbfefffeb67401649,1 +np.float64,0xffef812ec1ff025d,0xbfe919c7c073c766,1 +np.float64,0xffd5552aeaaaaa56,0x3fc89d38ab047396,1 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-cosh.csv b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-cosh.csv new file mode 100644 index 0000000000000000000000000000000000000000..af14d8475fe69c8a973caa598a34eccc610c7e90 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-cosh.csv @@ -0,0 +1,1429 @@ +dtype,input,output,ulperrortol +np.float32,0xfe0ac238,0x7f800000,3 +np.float32,0xbf553b86,0x3faf079b,3 +np.float32,0xff4457da,0x7f800000,3 +np.float32,0xff7253f3,0x7f800000,3 +np.float32,0x5a5802,0x3f800000,3 +np.float32,0x3db03413,0x3f80795b,3 +np.float32,0x7f6795c9,0x7f800000,3 +np.float32,0x805b9142,0x3f800000,3 +np.float32,0xfeea581a,0x7f800000,3 +np.float32,0x3f7e2dba,0x3fc472f6,3 +np.float32,0x3d9c4d74,0x3f805f7a,3 +np.float32,0x7f18c665,0x7f800000,3 +np.float32,0x7f003e23,0x7f800000,3 +np.float32,0x3d936fa0,0x3f8054f3,3 +np.float32,0x3f32034f,0x3fa0368e,3 +np.float32,0xff087604,0x7f800000,3 +np.float32,0x380a5,0x3f800000,3 +np.float32,0x3f59694e,0x3fb10077,3 +np.float32,0x3e63e648,0x3f832ee4,3 +np.float32,0x80712f42,0x3f800000,3 +np.float32,0x3e169908,0x3f816302,3 +np.float32,0x3f2d766e,0x3f9e8692,3 +np.float32,0x3d6412e0,0x3f8032d0,3 +np.float32,0xbde689e8,0x3f80cfd4,3 +np.float32,0x483e2e,0x3f800000,3 +np.float32,0xff1ba2d0,0x7f800000,3 +np.float32,0x80136bff,0x3f800000,3 +np.float32,0x3f72534c,0x3fbdc1d4,3 +np.float32,0x3e9eb381,0x3f8632c6,3 +np.float32,0x3e142892,0x3f815795,3 +np.float32,0x0,0x3f800000,3 +np.float32,0x2f2528,0x3f800000,3 +np.float32,0x7f38be13,0x7f800000,3 +np.float32,0xfeee6896,0x7f800000,3 +np.float32,0x7f09095d,0x7f800000,3 +np.float32,0xbe94d,0x3f800000,3 +np.float32,0xbedcf8d4,0x3f8c1b74,3 +np.float32,0xbf694c02,0x3fb8ef07,3 +np.float32,0x3e2261f8,0x3f819cde,3 +np.float32,0xbf01d3ce,0x3f90d0e0,3 +np.float32,0xbeb7b3a2,0x3f8853de,3 +np.float32,0x8046de7b,0x3f800000,3 +np.float32,0xbcb45ea0,0x3f8007f1,3 +np.float32,0x3eef14af,0x3f8e35dd,3 +np.float32,0xbf047316,0x3f91846e,3 +np.float32,0x801cef45,0x3f800000,3 +np.float32,0x3e9ad891,0x3f85e609,3 +np.float32,0xff20e9cf,0x7f800000,3 +np.float32,0x80068434,0x3f800000,3 +np.float32,0xbe253020,0x3f81ab49,3 +np.float32,0x3f13f4b8,0x3f95fac9,3 +np.float32,0x804accd1,0x3f800000,3 +np.float32,0x3dee3e10,0x3f80ddf7,3 +np.float32,0xbe6c4690,0x3f836c29,3 +np.float32,0xff30d431,0x7f800000,3 +np.float32,0xbec82416,0x3f89e791,3 +np.float32,0x3f30bbcb,0x3f9fbbcc,3 +np.float32,0x3f5620a2,0x3faf72b8,3 +np.float32,0x807a8130,0x3f800000,3 +np.float32,0x3e3cb02d,0x3f822de0,3 +np.float32,0xff4839ac,0x7f800000,3 +np.float32,0x800a3e9c,0x3f800000,3 +np.float32,0x3dffd65b,0x3f810002,3 +np.float32,0xbf2b1492,0x3f9da987,3 +np.float32,0xbf21602c,0x3f9a48fe,3 +np.float32,0x512531,0x3f800000,3 +np.float32,0x24b99a,0x3f800000,3 +np.float32,0xbf53e345,0x3fae67b1,3 +np.float32,0xff2126ec,0x7f800000,3 +np.float32,0x7e79b49d,0x7f800000,3 +np.float32,0x3ea3cf04,0x3f869b6f,3 +np.float32,0x7f270059,0x7f800000,3 +np.float32,0x3f625b2f,0x3fb561e1,3 +np.float32,0xbf59947e,0x3fb11519,3 +np.float32,0xfe0d1c64,0x7f800000,3 +np.float32,0xbf3f3eae,0x3fa568e2,3 +np.float32,0x7c04d1,0x3f800000,3 +np.float32,0x7e66bd,0x3f800000,3 +np.float32,0x8011880d,0x3f800000,3 +np.float32,0x3f302f07,0x3f9f8759,3 +np.float32,0x4e3375,0x3f800000,3 +np.float32,0xfe67a134,0x7f800000,3 +np.float32,0xff670249,0x7f800000,3 +np.float32,0x7e19f27d,0x7f800000,3 +np.float32,0xbf36ce12,0x3fa20b81,3 +np.float32,0xbe6bcfc4,0x3f8368b5,3 +np.float32,0x76fcba,0x3f800000,3 +np.float32,0x7f30abaf,0x7f800000,3 +np.float32,0x3f4c1f6d,0x3faae43c,3 +np.float32,0x7f61f44a,0x7f800000,3 +np.float32,0xbf4bb3c9,0x3faab4af,3 +np.float32,0xbda15ee0,0x3f8065c6,3 +np.float32,0xfbb4e800,0x7f800000,3 +np.float32,0x7fa00000,0x7fe00000,3 +np.float32,0x80568501,0x3f800000,3 +np.float32,0xfeb285e4,0x7f800000,3 +np.float32,0x804423a7,0x3f800000,3 +np.float32,0x7e6c0f21,0x7f800000,3 +np.float32,0x7f136b3c,0x7f800000,3 +np.float32,0x3f2d08e6,0x3f9e5e9c,3 +np.float32,0xbf6b454e,0x3fb9f7e6,3 +np.float32,0x3e6bceb0,0x3f8368ad,3 +np.float32,0xff1ad16a,0x7f800000,3 +np.float32,0x7cce1a04,0x7f800000,3 +np.float32,0xff7bcf95,0x7f800000,3 +np.float32,0x8049788d,0x3f800000,3 +np.float32,0x7ec45918,0x7f800000,3 +np.float32,0xff7fffff,0x7f800000,3 +np.float32,0x8039a1a0,0x3f800000,3 +np.float32,0x7e90cd72,0x7f800000,3 +np.float32,0xbf7dfd53,0x3fc456cc,3 +np.float32,0x3eeeb664,0x3f8e2a76,3 +np.float32,0x8055ef9b,0x3f800000,3 +np.float32,0x7ee06ddd,0x7f800000,3 +np.float32,0xba2cc000,0x3f800002,3 +np.float32,0x806da632,0x3f800000,3 +np.float32,0x7ecfaaf5,0x7f800000,3 +np.float32,0x3ddd12e6,0x3f80bf19,3 +np.float32,0xbf754394,0x3fbf60b1,3 +np.float32,0x6f3f19,0x3f800000,3 +np.float32,0x800a9af0,0x3f800000,3 +np.float32,0xfeef13ea,0x7f800000,3 +np.float32,0x7f74841f,0x7f800000,3 +np.float32,0xbeb9a2f0,0x3f888181,3 +np.float32,0x77cbb,0x3f800000,3 +np.float32,0xbf587f84,0x3fb0911b,3 +np.float32,0x210ba5,0x3f800000,3 +np.float32,0x3ee60a28,0x3f8d2367,3 +np.float32,0xbe3731ac,0x3f820dc7,3 +np.float32,0xbee8cfee,0x3f8d765e,3 +np.float32,0x7b2ef179,0x7f800000,3 +np.float32,0xfe81377c,0x7f800000,3 +np.float32,0x6ac98c,0x3f800000,3 +np.float32,0x3f51f144,0x3fad8288,3 +np.float32,0x80785750,0x3f800000,3 +np.float32,0x3f46615a,0x3fa864ff,3 +np.float32,0xbf35ac9e,0x3fa19b8e,3 +np.float32,0x7f0982ac,0x7f800000,3 +np.float32,0x1b2610,0x3f800000,3 +np.float32,0x3ed8bb25,0x3f8ba3df,3 +np.float32,0xbeb41bac,0x3f88006d,3 +np.float32,0xff48e89d,0x7f800000,3 +np.float32,0x3ed0ab8c,0x3f8ac755,3 +np.float32,0xbe64671c,0x3f833282,3 +np.float32,0x64bce4,0x3f800000,3 +np.float32,0x284f79,0x3f800000,3 +np.float32,0x7e09faa7,0x7f800000,3 +np.float32,0x4376c1,0x3f800000,3 +np.float32,0x805ca8c0,0x3f800000,3 +np.float32,0xff0859d5,0x7f800000,3 +np.float32,0xbed2f3b2,0x3f8b04dd,3 +np.float32,0x8045bd0c,0x3f800000,3 +np.float32,0x3f0e6216,0x3f94503f,3 +np.float32,0x3f41e3ae,0x3fa68035,3 +np.float32,0x80088ccc,0x3f800000,3 +np.float32,0x3f37fc19,0x3fa2812f,3 +np.float32,0x71c87d,0x3f800000,3 +np.float32,0x8024f4b2,0x3f800000,3 +np.float32,0xff78dd88,0x7f800000,3 +np.float32,0xbda66c90,0x3f806c40,3 +np.float32,0x7f33ef0d,0x7f800000,3 +np.float32,0x46a343,0x3f800000,3 +np.float32,0xff1dce38,0x7f800000,3 +np.float32,0x1b935d,0x3f800000,3 +np.float32,0x3ebec598,0x3f88fd0e,3 +np.float32,0xff115530,0x7f800000,3 +np.float32,0x803916aa,0x3f800000,3 +np.float32,0xff60a3e2,0x7f800000,3 +np.float32,0x3b8ddd48,0x3f80004f,3 +np.float32,0x3f761b6e,0x3fbfd8ea,3 +np.float32,0xbdf55b88,0x3f80eb70,3 +np.float32,0x37374,0x3f800000,3 +np.float32,0x3de150e0,0x3f80c682,3 +np.float32,0x3f343278,0x3fa10a83,3 +np.float32,0xbe9baefa,0x3f85f68b,3 +np.float32,0x3d8d43,0x3f800000,3 +np.float32,0x3e80994b,0x3f840f0c,3 +np.float32,0xbe573c6c,0x3f82d685,3 +np.float32,0x805b83b4,0x3f800000,3 +np.float32,0x683d88,0x3f800000,3 +np.float32,0x692465,0x3f800000,3 +np.float32,0xbdc345f8,0x3f809511,3 +np.float32,0x3f7c1c5a,0x3fc3406f,3 +np.float32,0xbf40bef3,0x3fa606df,3 +np.float32,0xff1e25b9,0x7f800000,3 +np.float32,0x3e4481e0,0x3f825d37,3 +np.float32,0x75d188,0x3f800000,3 +np.float32,0x3ea53cec,0x3f86b956,3 +np.float32,0xff105a54,0x7f800000,3 +np.float32,0x7f800000,0x7f800000,3 +np.float32,0x7f11f0b0,0x7f800000,3 +np.float32,0xbf58a57d,0x3fb0a328,3 +np.float32,0xbdd11e38,0x3f80aaf8,3 +np.float32,0xbea94adc,0x3f870fa0,3 +np.float32,0x3e9dd780,0x3f862180,3 +np.float32,0xff1786b9,0x7f800000,3 +np.float32,0xfec46aa2,0x7f800000,3 +np.float32,0x7f4300c1,0x7f800000,3 +np.float32,0x29ba2b,0x3f800000,3 +np.float32,0x3f4112e2,0x3fa62993,3 +np.float32,0xbe6c9224,0x3f836e5d,3 +np.float32,0x7f0e42a3,0x7f800000,3 +np.float32,0xff6390ad,0x7f800000,3 +np.float32,0x3f54e374,0x3faede94,3 +np.float32,0x7f2642a2,0x7f800000,3 +np.float32,0x7f46b2be,0x7f800000,3 +np.float32,0xfe59095c,0x7f800000,3 +np.float32,0x7146a0,0x3f800000,3 +np.float32,0x3f07763d,0x3f925786,3 +np.float32,0x3d172780,0x3f801651,3 +np.float32,0xff66f1c5,0x7f800000,3 +np.float32,0xff025349,0x7f800000,3 +np.float32,0x6ce99d,0x3f800000,3 +np.float32,0xbf7e4f50,0x3fc48685,3 +np.float32,0xbeff8ca2,0x3f904708,3 +np.float32,0x3e6c8,0x3f800000,3 +np.float32,0x7f7153dc,0x7f800000,3 +np.float32,0xbedcf612,0x3f8c1b26,3 +np.float32,0xbbc2f180,0x3f800094,3 +np.float32,0xbf397399,0x3fa314b8,3 +np.float32,0x6c6e35,0x3f800000,3 +np.float32,0x7f50a88b,0x7f800000,3 +np.float32,0xfe84093e,0x7f800000,3 +np.float32,0x3f737b9d,0x3fbe6478,3 +np.float32,0x7f6a5340,0x7f800000,3 +np.float32,0xbde83c20,0x3f80d2e7,3 +np.float32,0xff769ce9,0x7f800000,3 +np.float32,0xfdd33c30,0x7f800000,3 +np.float32,0xbc95cb60,0x3f80057a,3 +np.float32,0x8007a40d,0x3f800000,3 +np.float32,0x3f55d90c,0x3faf5132,3 +np.float32,0x80282082,0x3f800000,3 +np.float32,0xbf43b1f2,0x3fa7418c,3 +np.float32,0x3f1dc7cb,0x3f991731,3 +np.float32,0xbd4346a0,0x3f80253f,3 +np.float32,0xbf5aa82a,0x3fb19946,3 +np.float32,0x3f4b8c22,0x3faaa333,3 +np.float32,0x3d13468c,0x3f80152f,3 +np.float32,0x7db77097,0x7f800000,3 +np.float32,0x4a00df,0x3f800000,3 +np.float32,0xbedea5e0,0x3f8c4b64,3 +np.float32,0x80482543,0x3f800000,3 +np.float32,0xbef344fe,0x3f8eb8dd,3 +np.float32,0x7ebd4044,0x7f800000,3 +np.float32,0xbf512c0e,0x3fad287e,3 +np.float32,0x3db28cce,0x3f807c9c,3 +np.float32,0xbd0f5ae0,0x3f801412,3 +np.float32,0xfe7ed9ac,0x7f800000,3 +np.float32,0x3eb1aa82,0x3f87c8b4,3 +np.float32,0xfef1679e,0x7f800000,3 +np.float32,0xff3629f2,0x7f800000,3 +np.float32,0xff3562b4,0x7f800000,3 +np.float32,0x3dcafe1d,0x3f80a118,3 +np.float32,0xfedf242a,0x7f800000,3 +np.float32,0xbf43102a,0x3fa6fda4,3 +np.float32,0x8028834e,0x3f800000,3 +np.float32,0x805c8513,0x3f800000,3 +np.float32,0x3f59306a,0x3fb0e550,3 +np.float32,0x3eda2c9c,0x3f8bcc4a,3 +np.float32,0x80023524,0x3f800000,3 +np.float32,0x7ef72879,0x7f800000,3 +np.float32,0x661c8a,0x3f800000,3 +np.float32,0xfec3ba6c,0x7f800000,3 +np.float32,0x805aaca6,0x3f800000,3 +np.float32,0xff5c1f13,0x7f800000,3 +np.float32,0x3f6ab3f4,0x3fb9ab6b,3 +np.float32,0x3f014896,0x3f90ac20,3 +np.float32,0x3f030584,0x3f91222a,3 +np.float32,0xbf74853d,0x3fbef71d,3 +np.float32,0xbf534ee0,0x3fae2323,3 +np.float32,0x2c90c3,0x3f800000,3 +np.float32,0x7f62ad25,0x7f800000,3 +np.float32,0x1c8847,0x3f800000,3 +np.float32,0x7e2a8d43,0x7f800000,3 +np.float32,0x807a09cd,0x3f800000,3 +np.float32,0x413871,0x3f800000,3 +np.float32,0x80063692,0x3f800000,3 +np.float32,0x3edaf29b,0x3f8be211,3 +np.float32,0xbf64a7ab,0x3fb68b2d,3 +np.float32,0xfe56a720,0x7f800000,3 +np.float32,0xbf54a8d4,0x3faec350,3 +np.float32,0x3ecbaef7,0x3f8a4350,3 +np.float32,0x3f413714,0x3fa63890,3 +np.float32,0x7d3aa8,0x3f800000,3 +np.float32,0xbea9a13c,0x3f8716e7,3 +np.float32,0x7ef7553e,0x7f800000,3 +np.float32,0x8056f29f,0x3f800000,3 +np.float32,0xff1f7ffe,0x7f800000,3 +np.float32,0x3f41953b,0x3fa65f9c,3 +np.float32,0x3daa2f,0x3f800000,3 +np.float32,0xff0893e4,0x7f800000,3 +np.float32,0xbefc7ec6,0x3f8fe207,3 +np.float32,0xbb026800,0x3f800011,3 +np.float32,0x341e4f,0x3f800000,3 +np.float32,0x3e7b708a,0x3f83e0d1,3 +np.float32,0xa18cb,0x3f800000,3 +np.float32,0x7e290239,0x7f800000,3 +np.float32,0xbf4254f2,0x3fa6af62,3 +np.float32,0x80000000,0x3f800000,3 +np.float32,0x3f0a6c,0x3f800000,3 +np.float32,0xbec44d28,0x3f898609,3 +np.float32,0xf841f,0x3f800000,3 +np.float32,0x7f01a693,0x7f800000,3 +np.float32,0x8053340b,0x3f800000,3 +np.float32,0xfd4e7990,0x7f800000,3 +np.float32,0xbf782f1f,0x3fc10356,3 +np.float32,0xbe962118,0x3f858acc,3 +np.float32,0xfe8cd702,0x7f800000,3 +np.float32,0x7ecd986f,0x7f800000,3 +np.float32,0x3ebe775f,0x3f88f59b,3 +np.float32,0x8065524f,0x3f800000,3 +np.float32,0x3ede7fc4,0x3f8c471e,3 +np.float32,0x7f5e15ea,0x7f800000,3 +np.float32,0xbe871ada,0x3f847b78,3 +np.float32,0x3f21958b,0x3f9a5af7,3 +np.float32,0x3f64d480,0x3fb6a1fa,3 +np.float32,0xff18b0e9,0x7f800000,3 +np.float32,0xbf0840dd,0x3f928fd9,3 +np.float32,0x80104f5d,0x3f800000,3 +np.float32,0x643b94,0x3f800000,3 +np.float32,0xbc560a80,0x3f8002cc,3 +np.float32,0x3f5c75d6,0x3fb2786e,3 +np.float32,0x7f365fc9,0x7f800000,3 +np.float32,0x54e965,0x3f800000,3 +np.float32,0x6dcd4d,0x3f800000,3 +np.float32,0x3f2057a0,0x3f99f04d,3 +np.float32,0x272fa3,0x3f800000,3 +np.float32,0xff423dc9,0x7f800000,3 +np.float32,0x80273463,0x3f800000,3 +np.float32,0xfe21cc78,0x7f800000,3 +np.float32,0x7fc00000,0x7fc00000,3 +np.float32,0x802feb65,0x3f800000,3 +np.float32,0x3dc733d0,0x3f809b21,3 +np.float32,0x65d56b,0x3f800000,3 +np.float32,0x80351d8e,0x3f800000,3 +np.float32,0xbf244247,0x3f9b43dd,3 +np.float32,0x7f328e7e,0x7f800000,3 +np.float32,0x7f4d9712,0x7f800000,3 +np.float32,0x2c505d,0x3f800000,3 +np.float32,0xbf232ebe,0x3f9ae5a0,3 +np.float32,0x804a363a,0x3f800000,3 +np.float32,0x80417102,0x3f800000,3 +np.float32,0xbf48b170,0x3fa963d4,3 +np.float32,0x7ea3e3b6,0x7f800000,3 +np.float32,0xbf41415b,0x3fa63cd2,3 +np.float32,0xfe3af7c8,0x7f800000,3 +np.float32,0x7f478010,0x7f800000,3 +np.float32,0x80143113,0x3f800000,3 +np.float32,0x3f7626a7,0x3fbfdf2e,3 +np.float32,0xfea20b0a,0x7f800000,3 +np.float32,0x80144d64,0x3f800000,3 +np.float32,0x7db9ba47,0x7f800000,3 +np.float32,0x7f7fffff,0x7f800000,3 +np.float32,0xbe410834,0x3f8247ef,3 +np.float32,0x14a7af,0x3f800000,3 +np.float32,0x7eaebf9e,0x7f800000,3 +np.float32,0xff800000,0x7f800000,3 +np.float32,0x3f0a7d8e,0x3f9330fd,3 +np.float32,0x3ef780,0x3f800000,3 +np.float32,0x3f62253e,0x3fb546d1,3 +np.float32,0x3f4cbeac,0x3fab2acc,3 +np.float32,0x25db1,0x3f800000,3 +np.float32,0x65c54a,0x3f800000,3 +np.float32,0x800f0645,0x3f800000,3 +np.float32,0x3ed28c78,0x3f8af9f0,3 +np.float32,0x8040c6ce,0x3f800000,3 +np.float32,0x5e4e9a,0x3f800000,3 +np.float32,0xbd3fd2b0,0x3f8023f1,3 +np.float32,0xbf5d2d3f,0x3fb2d1b6,3 +np.float32,0x7ead999f,0x7f800000,3 +np.float32,0xbf30dc86,0x3f9fc805,3 +np.float32,0xff2b0a62,0x7f800000,3 +np.float32,0x3d5180e9,0x3f802adf,3 +np.float32,0x3f62716f,0x3fb56d0d,3 +np.float32,0x7e82ae9c,0x7f800000,3 +np.float32,0xfe2d4bdc,0x7f800000,3 +np.float32,0x805cc7d4,0x3f800000,3 +np.float32,0xfb50f700,0x7f800000,3 +np.float32,0xff57b684,0x7f800000,3 +np.float32,0x80344f01,0x3f800000,3 +np.float32,0x7f2af372,0x7f800000,3 +np.float32,0xfeab6204,0x7f800000,3 +np.float32,0x30b251,0x3f800000,3 +np.float32,0x3eed8cc4,0x3f8e0698,3 +np.float32,0x7eeb1c6a,0x7f800000,3 +np.float32,0x3f17ece6,0x3f9735b0,3 +np.float32,0x21e985,0x3f800000,3 +np.float32,0x3f3a7df3,0x3fa37e34,3 +np.float32,0x802a14a2,0x3f800000,3 +np.float32,0x807d4d5b,0x3f800000,3 +np.float32,0x7f6093ce,0x7f800000,3 +np.float32,0x3f800000,0x3fc583ab,3 +np.float32,0x3da2c26e,0x3f806789,3 +np.float32,0xfe05f278,0x7f800000,3 +np.float32,0x800000,0x3f800000,3 +np.float32,0xbee63342,0x3f8d282e,3 +np.float32,0xbf225586,0x3f9a9bd4,3 +np.float32,0xbed60e86,0x3f8b59ba,3 +np.float32,0xbec99484,0x3f8a0ca3,3 +np.float32,0x3e967c71,0x3f859199,3 +np.float32,0x7f26ab62,0x7f800000,3 +np.float32,0xca7f4,0x3f800000,3 +np.float32,0xbf543790,0x3fae8ebc,3 +np.float32,0x3e4c1ed9,0x3f828d2d,3 +np.float32,0xbdf37f88,0x3f80e7e1,3 +np.float32,0xff0cc44e,0x7f800000,3 +np.float32,0x5dea48,0x3f800000,3 +np.float32,0x31023c,0x3f800000,3 +np.float32,0x3ea10733,0x3f866208,3 +np.float32,0x3e11e6f2,0x3f814d2e,3 +np.float32,0x80641960,0x3f800000,3 +np.float32,0x3ef779a8,0x3f8f3edb,3 +np.float32,0x3f2a5062,0x3f9d632a,3 +np.float32,0x2b7d34,0x3f800000,3 +np.float32,0x3eeb95c5,0x3f8dca67,3 +np.float32,0x805c1357,0x3f800000,3 +np.float32,0x3db3a79d,0x3f807e29,3 +np.float32,0xfded1900,0x7f800000,3 +np.float32,0x45f362,0x3f800000,3 +np.float32,0x451f38,0x3f800000,3 +np.float32,0x801d3ae5,0x3f800000,3 +np.float32,0x458d45,0x3f800000,3 +np.float32,0xfda9d298,0x7f800000,3 +np.float32,0x467439,0x3f800000,3 +np.float32,0x7f66554a,0x7f800000,3 +np.float32,0xfef2375a,0x7f800000,3 +np.float32,0xbf33fc47,0x3fa0f5d7,3 +np.float32,0x3f75ba69,0x3fbfa2d0,3 +np.float32,0xfeb625b2,0x7f800000,3 +np.float32,0x8066b371,0x3f800000,3 +np.float32,0x3f5cb4e9,0x3fb29718,3 +np.float32,0x7f3b6a58,0x7f800000,3 +np.float32,0x7f6b35ea,0x7f800000,3 +np.float32,0xbf6ee555,0x3fbbe5be,3 +np.float32,0x3d836e21,0x3f804380,3 +np.float32,0xff43cd0c,0x7f800000,3 +np.float32,0xff55c1fa,0x7f800000,3 +np.float32,0xbf0dfccc,0x3f9432a6,3 +np.float32,0x3ed92121,0x3f8baf00,3 +np.float32,0x80068cc1,0x3f800000,3 +np.float32,0xff0103f9,0x7f800000,3 +np.float32,0x7e51b175,0x7f800000,3 +np.float32,0x8012f214,0x3f800000,3 +np.float32,0x62d298,0x3f800000,3 +np.float32,0xbf3e1525,0x3fa4ef8d,3 +np.float32,0x806b4882,0x3f800000,3 +np.float32,0xbf38c146,0x3fa2ce7c,3 +np.float32,0xbed59c30,0x3f8b4d70,3 +np.float32,0x3d1910c0,0x3f8016e2,3 +np.float32,0x7f33d55b,0x7f800000,3 +np.float32,0x7f5800e3,0x7f800000,3 +np.float32,0x5b2c5d,0x3f800000,3 +np.float32,0x807be750,0x3f800000,3 +np.float32,0x7eb297c1,0x7f800000,3 +np.float32,0x7dafee62,0x7f800000,3 +np.float32,0x7d9e23f0,0x7f800000,3 +np.float32,0x3e580537,0x3f82dbd8,3 +np.float32,0xbf800000,0x3fc583ab,3 +np.float32,0x7f40f880,0x7f800000,3 +np.float32,0x775ad3,0x3f800000,3 +np.float32,0xbedacd36,0x3f8bddf3,3 +np.float32,0x2138f6,0x3f800000,3 +np.float32,0x52c3b7,0x3f800000,3 +np.float32,0x8041cfdd,0x3f800000,3 +np.float32,0x7bf16791,0x7f800000,3 +np.float32,0xbe95869c,0x3f857f55,3 +np.float32,0xbf199796,0x3f97bcaf,3 +np.float32,0x3ef8da38,0x3f8f6b45,3 +np.float32,0x803f3648,0x3f800000,3 +np.float32,0x80026fd2,0x3f800000,3 +np.float32,0x7eb3ac26,0x7f800000,3 +np.float32,0x3e49921b,0x3f827ce8,3 +np.float32,0xbf689aed,0x3fb892de,3 +np.float32,0x3f253509,0x3f9b9779,3 +np.float32,0xff17894a,0x7f800000,3 +np.float32,0x3cd12639,0x3f800aae,3 +np.float32,0x1db14b,0x3f800000,3 +np.float32,0x39a0bf,0x3f800000,3 +np.float32,0xfdfe1d08,0x7f800000,3 +np.float32,0xff416cd2,0x7f800000,3 +np.float32,0x8070d818,0x3f800000,3 +np.float32,0x3e516e12,0x3f82afb8,3 +np.float32,0x80536651,0x3f800000,3 +np.float32,0xbf2903d2,0x3f9cecb7,3 +np.float32,0x3e896ae4,0x3f84a353,3 +np.float32,0xbd6ba2c0,0x3f80363d,3 +np.float32,0x80126d3e,0x3f800000,3 +np.float32,0xfd9d43d0,0x7f800000,3 +np.float32,0x7b56b6,0x3f800000,3 +np.float32,0xff04718e,0x7f800000,3 +np.float32,0x31440f,0x3f800000,3 +np.float32,0xbf7a1313,0x3fc215c9,3 +np.float32,0x7f43d6a0,0x7f800000,3 +np.float32,0x3f566503,0x3faf92cc,3 +np.float32,0xbf39eb0e,0x3fa343f1,3 +np.float32,0xbe35fd70,0x3f8206df,3 +np.float32,0x800c36ac,0x3f800000,3 +np.float32,0x60d061,0x3f800000,3 +np.float32,0x80453e12,0x3f800000,3 +np.float32,0xfe17c36c,0x7f800000,3 +np.float32,0x3d8c72,0x3f800000,3 +np.float32,0xfe8e9134,0x7f800000,3 +np.float32,0xff5d89de,0x7f800000,3 +np.float32,0x7f45020e,0x7f800000,3 +np.float32,0x3f28225e,0x3f9c9d01,3 +np.float32,0xbf3b6900,0x3fa3dbdd,3 +np.float32,0x80349023,0x3f800000,3 +np.float32,0xbf14d780,0x3f964042,3 +np.float32,0x3f56b5d2,0x3fafb8c3,3 +np.float32,0x800c639c,0x3f800000,3 +np.float32,0x7f7a19c8,0x7f800000,3 +np.float32,0xbf7a0815,0x3fc20f86,3 +np.float32,0xbec55926,0x3f89a06e,3 +np.float32,0x4b2cd2,0x3f800000,3 +np.float32,0xbf271eb2,0x3f9c41c8,3 +np.float32,0xff26e168,0x7f800000,3 +np.float32,0x800166b2,0x3f800000,3 +np.float32,0xbde97e38,0x3f80d532,3 +np.float32,0xbf1f93ec,0x3f99af1a,3 +np.float32,0x7f2896ed,0x7f800000,3 +np.float32,0x3da7d96d,0x3f806e1d,3 +np.float32,0x802b7237,0x3f800000,3 +np.float32,0xfdca6bc0,0x7f800000,3 +np.float32,0xbed2e300,0x3f8b0318,3 +np.float32,0x8079d9e8,0x3f800000,3 +np.float32,0x3f388c81,0x3fa2b9c2,3 +np.float32,0x3ed2607c,0x3f8af54a,3 +np.float32,0xff287de6,0x7f800000,3 +np.float32,0x3f55ed89,0x3faf5ac9,3 +np.float32,0x7f5b6af7,0x7f800000,3 +np.float32,0xbeb24730,0x3f87d698,3 +np.float32,0x1,0x3f800000,3 +np.float32,0x3f3a2350,0x3fa35a3b,3 +np.float32,0x8013b422,0x3f800000,3 +np.float32,0x3e9a6560,0x3f85dd35,3 +np.float32,0x80510631,0x3f800000,3 +np.float32,0xfeae39d6,0x7f800000,3 +np.float32,0x7eb437ad,0x7f800000,3 +np.float32,0x8047545b,0x3f800000,3 +np.float32,0x806a1c71,0x3f800000,3 +np.float32,0xbe5543f0,0x3f82c93b,3 +np.float32,0x40e8d,0x3f800000,3 +np.float32,0x63d18b,0x3f800000,3 +np.float32,0x1fa1ea,0x3f800000,3 +np.float32,0x801944e0,0x3f800000,3 +np.float32,0xbf4c7ac6,0x3fab0cae,3 +np.float32,0x7f2679d4,0x7f800000,3 +np.float32,0x3f0102fc,0x3f9099d0,3 +np.float32,0x7e44bdc1,0x7f800000,3 +np.float32,0xbf2072f6,0x3f99f970,3 +np.float32,0x5c7d38,0x3f800000,3 +np.float32,0x30a2e6,0x3f800000,3 +np.float32,0x805b9ca3,0x3f800000,3 +np.float32,0x7cc24ad5,0x7f800000,3 +np.float32,0x3f4f7920,0x3fac6357,3 +np.float32,0x111d62,0x3f800000,3 +np.float32,0xbf4de40a,0x3fabad77,3 +np.float32,0x805d0354,0x3f800000,3 +np.float32,0xbb3d2b00,0x3f800023,3 +np.float32,0x3ef229e7,0x3f8e960b,3 +np.float32,0x3f15754e,0x3f9670e0,3 +np.float32,0xbf689c6b,0x3fb893a5,3 +np.float32,0xbf3796c6,0x3fa2599b,3 +np.float32,0xbe95303c,0x3f8578f2,3 +np.float32,0xfee330de,0x7f800000,3 +np.float32,0xff0d9705,0x7f800000,3 +np.float32,0xbeb0ebd0,0x3f87b7dd,3 +np.float32,0xbf4d5a13,0x3fab6fe7,3 +np.float32,0x80142f5a,0x3f800000,3 +np.float32,0x7e01a87b,0x7f800000,3 +np.float32,0xbe45e5ec,0x3f8265d7,3 +np.float32,0x7f4ac255,0x7f800000,3 +np.float32,0x3ebf6a60,0x3f890ccb,3 +np.float32,0x7f771e16,0x7f800000,3 +np.float32,0x3f41834e,0x3fa6582b,3 +np.float32,0x3f7f6f98,0x3fc52ef0,3 +np.float32,0x7e4ad775,0x7f800000,3 +np.float32,0x3eb39991,0x3f87f4c4,3 +np.float32,0x1e3f4,0x3f800000,3 +np.float32,0x7e84ba19,0x7f800000,3 +np.float32,0x80640be4,0x3f800000,3 +np.float32,0x3f459fc8,0x3fa81272,3 +np.float32,0x3f554ed0,0x3faf109b,3 +np.float32,0x3c6617,0x3f800000,3 +np.float32,0x7f441158,0x7f800000,3 +np.float32,0x7f66e6d8,0x7f800000,3 +np.float32,0x7f565152,0x7f800000,3 +np.float32,0x7f16d550,0x7f800000,3 +np.float32,0xbd4f1950,0x3f8029e5,3 +np.float32,0xcf722,0x3f800000,3 +np.float32,0x3f37d6fd,0x3fa272ad,3 +np.float32,0xff7324ea,0x7f800000,3 +np.float32,0x804bc246,0x3f800000,3 +np.float32,0x7f099ef8,0x7f800000,3 +np.float32,0x5f838b,0x3f800000,3 +np.float32,0x80523534,0x3f800000,3 +np.float32,0x3f595e84,0x3fb0fb50,3 +np.float32,0xfdef8ac8,0x7f800000,3 +np.float32,0x3d9a07,0x3f800000,3 +np.float32,0x410f61,0x3f800000,3 +np.float32,0xbf715dbb,0x3fbd3bcb,3 +np.float32,0xbedd4734,0x3f8c242f,3 +np.float32,0x7e86739a,0x7f800000,3 +np.float32,0x3e81f144,0x3f8424fe,3 +np.float32,0x7f6342d1,0x7f800000,3 +np.float32,0xff6919a3,0x7f800000,3 +np.float32,0xff051878,0x7f800000,3 +np.float32,0x800ba28f,0x3f800000,3 +np.float32,0xfefab3d8,0x7f800000,3 +np.float32,0xff612a84,0x7f800000,3 +np.float32,0x800cd5ab,0x3f800000,3 +np.float32,0x802a07ae,0x3f800000,3 +np.float32,0xfef6ee3a,0x7f800000,3 +np.float32,0x8037e896,0x3f800000,3 +np.float32,0x3ef2d86f,0x3f8eab7d,3 +np.float32,0x3eafe53d,0x3f87a0cb,3 +np.float32,0xba591c00,0x3f800003,3 +np.float32,0x3e9ed028,0x3f863508,3 +np.float32,0x4a12a8,0x3f800000,3 +np.float32,0xbee55c84,0x3f8d0f45,3 +np.float32,0x8038a8d3,0x3f800000,3 +np.float32,0xff055243,0x7f800000,3 +np.float32,0xbf659067,0x3fb701ca,3 +np.float32,0xbee36a86,0x3f8cd5e0,3 +np.float32,0x7f1d74c1,0x7f800000,3 +np.float32,0xbf7657df,0x3fbffaad,3 +np.float32,0x7e37ee34,0x7f800000,3 +np.float32,0xff04bc74,0x7f800000,3 +np.float32,0x806d194e,0x3f800000,3 +np.float32,0x7f5596c3,0x7f800000,3 +np.float32,0xbe09d268,0x3f81293e,3 +np.float32,0x79ff75,0x3f800000,3 +np.float32,0xbf55479c,0x3faf0d3e,3 +np.float32,0xbe5428ec,0x3f82c1d4,3 +np.float32,0x3f624134,0x3fb554d7,3 +np.float32,0x2ccb8a,0x3f800000,3 +np.float32,0xfc082040,0x7f800000,3 +np.float32,0xff315467,0x7f800000,3 +np.float32,0x3e6ea2d2,0x3f837dd5,3 +np.float32,0x8020fdd1,0x3f800000,3 +np.float32,0x7f0416a1,0x7f800000,3 +np.float32,0x710a1b,0x3f800000,3 +np.float32,0x3dfcd050,0x3f80f9fc,3 +np.float32,0xfe995e96,0x7f800000,3 +np.float32,0x3f020d00,0x3f90e006,3 +np.float32,0x8064263e,0x3f800000,3 +np.float32,0xfcee4160,0x7f800000,3 +np.float32,0x801b3a18,0x3f800000,3 +np.float32,0x3f62c984,0x3fb59955,3 +np.float32,0x806e8355,0x3f800000,3 +np.float32,0x7e94f65d,0x7f800000,3 +np.float32,0x1173de,0x3f800000,3 +np.float32,0x3e3ff3b7,0x3f824166,3 +np.float32,0x803b4aea,0x3f800000,3 +np.float32,0x804c5bcc,0x3f800000,3 +np.float32,0x509fe5,0x3f800000,3 +np.float32,0xbf33b5ee,0x3fa0db0b,3 +np.float32,0x3f2ac15c,0x3f9d8ba4,3 +np.float32,0x7f2c54f8,0x7f800000,3 +np.float32,0x7f33d933,0x7f800000,3 +np.float32,0xbf09b2b4,0x3f92f795,3 +np.float32,0x805db8d6,0x3f800000,3 +np.float32,0x6d6e66,0x3f800000,3 +np.float32,0x3ddfea92,0x3f80c40c,3 +np.float32,0xfda719b8,0x7f800000,3 +np.float32,0x5d657f,0x3f800000,3 +np.float32,0xbf005ba3,0x3f906df6,3 +np.float32,0xbf45e606,0x3fa8305c,3 +np.float32,0x5e9fd1,0x3f800000,3 +np.float32,0x8079dc45,0x3f800000,3 +np.float32,0x7e9c40e3,0x7f800000,3 +np.float32,0x6bd5f6,0x3f800000,3 +np.float32,0xbea14a0e,0x3f866761,3 +np.float32,0x7e7323f3,0x7f800000,3 +np.float32,0x7f0c0a79,0x7f800000,3 +np.float32,0xbf7d7aeb,0x3fc40b0f,3 +np.float32,0x437588,0x3f800000,3 +np.float32,0xbf356376,0x3fa17f63,3 +np.float32,0x7f129921,0x7f800000,3 +np.float32,0x7f47a52e,0x7f800000,3 +np.float32,0xba8cb400,0x3f800005,3 +np.float32,0x802284e0,0x3f800000,3 +np.float32,0xbe820f56,0x3f8426ec,3 +np.float32,0x7f2ef6cf,0x7f800000,3 +np.float32,0xbf70a090,0x3fbcd501,3 +np.float32,0xbf173fea,0x3f96ff6d,3 +np.float32,0x3e19c489,0x3f817224,3 +np.float32,0x7f429b30,0x7f800000,3 +np.float32,0xbdae4118,0x3f8076af,3 +np.float32,0x3e70ad30,0x3f838d41,3 +np.float32,0x335fed,0x3f800000,3 +np.float32,0xff5359cf,0x7f800000,3 +np.float32,0xbf17e42b,0x3f9732f1,3 +np.float32,0xff3a950b,0x7f800000,3 +np.float32,0xbcca70c0,0x3f800a02,3 +np.float32,0x3f2cda62,0x3f9e4dad,3 +np.float32,0x3f50c185,0x3facf805,3 +np.float32,0x80000001,0x3f800000,3 +np.float32,0x807b86d2,0x3f800000,3 +np.float32,0x8010c2cf,0x3f800000,3 +np.float32,0x3f130fb8,0x3f95b519,3 +np.float32,0x807dc546,0x3f800000,3 +np.float32,0xbee20740,0x3f8cad3f,3 +np.float32,0x80800000,0x3f800000,3 +np.float32,0x3cbd90c0,0x3f8008c6,3 +np.float32,0x3e693488,0x3f835571,3 +np.float32,0xbe70cd44,0x3f838e35,3 +np.float32,0xbe348dc8,0x3f81feb1,3 +np.float32,0x3f31ea90,0x3fa02d3f,3 +np.float32,0xfcd7e180,0x7f800000,3 +np.float32,0xbe30a75c,0x3f81e8d0,3 +np.float32,0x3e552c5a,0x3f82c89d,3 +np.float32,0xff513f74,0x7f800000,3 +np.float32,0xbdb16248,0x3f807afd,3 +np.float64,0x7fbbf954e437f2a9,0x7ff0000000000000,1 +np.float64,0x581bbf0cb0379,0x3ff0000000000000,1 +np.float64,0x7ff8000000000000,0x7ff8000000000000,1 +np.float64,0xffb959a2a632b348,0x7ff0000000000000,1 +np.float64,0xbfdbd6baebb7ad76,0x3ff189a5ca25a6e1,1 +np.float64,0xbfd094ec9aa129da,0x3ff08a3f6b918065,1 +np.float64,0x3fe236753f646cea,0x3ff2a982660b8b43,1 +np.float64,0xbfe537fadfaa6ff6,0x3ff3a5f1c49c31bf,1 +np.float64,0xbfe31fa7dc663f50,0x3ff2f175374aef0e,1 +np.float64,0x3fc4b6569f296cb0,0x3ff035bde801bb53,1 +np.float64,0x800ce3c00f99c780,0x3ff0000000000000,1 +np.float64,0xbfebcde33e779bc6,0x3ff66de82cd30fc5,1 +np.float64,0x800dc09d3b7b813b,0x3ff0000000000000,1 +np.float64,0x80067d4c450cfa99,0x3ff0000000000000,1 +np.float64,0x1f6ade203ed7,0x3ff0000000000000,1 +np.float64,0xbfd4e311eca9c624,0x3ff0dc1383d6c3db,1 +np.float64,0x800649b3a54c9368,0x3ff0000000000000,1 +np.float64,0xcc14d1ab9829a,0x3ff0000000000000,1 +np.float64,0x3fc290c5bb25218b,0x3ff02b290f46dd6d,1 +np.float64,0x3fe78eb8376f1d70,0x3ff488f3bc259537,1 +np.float64,0xffc60f58e82c1eb0,0x7ff0000000000000,1 +np.float64,0x3fd35666ad26accd,0x3ff0bc6573da6bcd,1 +np.float64,0x7fc20257a62404ae,0x7ff0000000000000,1 +np.float64,0x80076d842e0edb09,0x3ff0000000000000,1 +np.float64,0x3fd8e44b08b1c898,0x3ff139b9a1f8428e,1 +np.float64,0x7fd6f6fc7a2dedf8,0x7ff0000000000000,1 +np.float64,0x3fa01b9f0820373e,0x3ff00206f8ad0f1b,1 +np.float64,0x69ed190ed3da4,0x3ff0000000000000,1 +np.float64,0xbfd997eb34b32fd6,0x3ff14be65a5db4a0,1 +np.float64,0x7feada2d0935b459,0x7ff0000000000000,1 +np.float64,0xbf80987120213100,0x3ff000226d29a9fc,1 +np.float64,0xbfef203e37fe407c,0x3ff82f51f04e8821,1 +np.float64,0xffe3dcf91fa7b9f2,0x7ff0000000000000,1 +np.float64,0x9a367283346cf,0x3ff0000000000000,1 +np.float64,0x800feb09f7bfd614,0x3ff0000000000000,1 +np.float64,0xbfe0319f9520633f,0x3ff217c5205c403f,1 +np.float64,0xbfa91eabd4323d50,0x3ff004ee4347f627,1 +np.float64,0x3fd19cbf7d23397f,0x3ff09c13e8e43571,1 +np.float64,0xffeb8945f0b7128b,0x7ff0000000000000,1 +np.float64,0x800a0eb4f2141d6a,0x3ff0000000000000,1 +np.float64,0xffe83e7312f07ce6,0x7ff0000000000000,1 +np.float64,0xffca53fee834a7fc,0x7ff0000000000000,1 +np.float64,0x800881cbf1710398,0x3ff0000000000000,1 +np.float64,0x80003e6abbe07cd6,0x3ff0000000000000,1 +np.float64,0xbfef6a998afed533,0x3ff859b7852d1b4d,1 +np.float64,0x3fd4eb7577a9d6eb,0x3ff0dcc601261aab,1 +np.float64,0xbfc9c12811338250,0x3ff05331268b05c8,1 +np.float64,0x7fddf84e5e3bf09c,0x7ff0000000000000,1 +np.float64,0xbfd4d6fbbc29adf8,0x3ff0db12db19d187,1 +np.float64,0x80077892bfaef126,0x3ff0000000000000,1 +np.float64,0xffae9d49543d3a90,0x7ff0000000000000,1 +np.float64,0xbfd8bef219317de4,0x3ff136034e5d2f1b,1 +np.float64,0xffe89c74ddb138e9,0x7ff0000000000000,1 +np.float64,0x8003b6bbb7e76d78,0x3ff0000000000000,1 +np.float64,0x315a4e8462b4b,0x3ff0000000000000,1 +np.float64,0x800ee616edddcc2e,0x3ff0000000000000,1 +np.float64,0xdfb27f97bf650,0x3ff0000000000000,1 +np.float64,0x8004723dc328e47c,0x3ff0000000000000,1 +np.float64,0xbfe529500daa52a0,0x3ff3a0b9b33fc84c,1 +np.float64,0xbfe4e46a7ce9c8d5,0x3ff3886ce0f92612,1 +np.float64,0xbf52003680240000,0x3ff00000a203d61a,1 +np.float64,0xffd3400458268008,0x7ff0000000000000,1 +np.float64,0x80076deb444edbd7,0x3ff0000000000000,1 +np.float64,0xa612f6c14c27,0x3ff0000000000000,1 +np.float64,0xbfd41c74c9a838ea,0x3ff0cbe61e16aecf,1 +np.float64,0x43f464a887e8d,0x3ff0000000000000,1 +np.float64,0x800976e748b2edcf,0x3ff0000000000000,1 +np.float64,0xffc79d6ba12f3ad8,0x7ff0000000000000,1 +np.float64,0xffd6dbcb022db796,0x7ff0000000000000,1 +np.float64,0xffd6a9672a2d52ce,0x7ff0000000000000,1 +np.float64,0x3fe95dcfa632bb9f,0x3ff54bbad2ee919e,1 +np.float64,0x3febadd2e1375ba6,0x3ff65e336c47c018,1 +np.float64,0x7fd47c37d828f86f,0x7ff0000000000000,1 +np.float64,0xbfd4ea59e0a9d4b4,0x3ff0dcae6af3e443,1 +np.float64,0x2c112afc58226,0x3ff0000000000000,1 +np.float64,0x8008122bced02458,0x3ff0000000000000,1 +np.float64,0x7fe7105ab3ee20b4,0x7ff0000000000000,1 +np.float64,0x80089634df312c6a,0x3ff0000000000000,1 +np.float64,0x68e9fbc8d1d40,0x3ff0000000000000,1 +np.float64,0xbfec1e1032f83c20,0x3ff69590b9f18ea8,1 +np.float64,0xbfedf181623be303,0x3ff787ef48935dc6,1 +np.float64,0xffe8600457f0c008,0x7ff0000000000000,1 +np.float64,0x7a841ec6f5084,0x3ff0000000000000,1 +np.float64,0x459a572e8b34c,0x3ff0000000000000,1 +np.float64,0x3fe8a232bef14465,0x3ff4fac1780f731e,1 +np.float64,0x3fcb37597d366eb3,0x3ff05cf08ab14ebd,1 +np.float64,0xbfb0261d00204c38,0x3ff00826fb86ca8a,1 +np.float64,0x3fc6e7a6dd2dcf4e,0x3ff041c1222ffa79,1 +np.float64,0xee65dd03dccbc,0x3ff0000000000000,1 +np.float64,0xffe26fdc23e4dfb8,0x7ff0000000000000,1 +np.float64,0x7fe8d6c8cab1ad91,0x7ff0000000000000,1 +np.float64,0xbfeb64bf2676c97e,0x3ff63abb8607828c,1 +np.float64,0x3fd28417b425082f,0x3ff0ac9eb22a732b,1 +np.float64,0xbfd26835b3a4d06c,0x3ff0aa94c48fb6d2,1 +np.float64,0xffec617a01b8c2f3,0x7ff0000000000000,1 +np.float64,0xe1bfff01c3800,0x3ff0000000000000,1 +np.float64,0x3fd4def913a9bdf4,0x3ff0dbbc7271046f,1 +np.float64,0x94f4c17129e98,0x3ff0000000000000,1 +np.float64,0x8009b2eaa33365d6,0x3ff0000000000000,1 +np.float64,0x3fd9633b41b2c678,0x3ff1468388bdfb65,1 +np.float64,0xffe0ae5c80e15cb8,0x7ff0000000000000,1 +np.float64,0x7fdfc35996bf86b2,0x7ff0000000000000,1 +np.float64,0x3fcfc5bdc23f8b7c,0x3ff07ed5caa4545c,1 +np.float64,0xd48b4907a9169,0x3ff0000000000000,1 +np.float64,0xbfe0a2cc52614598,0x3ff2361665895d95,1 +np.float64,0xbfe9068f90720d1f,0x3ff525b82491a1a5,1 +np.float64,0x4238b9208472,0x3ff0000000000000,1 +np.float64,0x800e6b2bf69cd658,0x3ff0000000000000,1 +np.float64,0x7fb638b6ae2c716c,0x7ff0000000000000,1 +np.float64,0x7fe267641764cec7,0x7ff0000000000000,1 +np.float64,0xffc0933d3521267c,0x7ff0000000000000,1 +np.float64,0x7fddfdfb533bfbf6,0x7ff0000000000000,1 +np.float64,0xced2a8e99da55,0x3ff0000000000000,1 +np.float64,0x2a80d5165501b,0x3ff0000000000000,1 +np.float64,0xbfeead2ab63d5a55,0x3ff7eeb5cbcfdcab,1 +np.float64,0x80097f6f92f2fee0,0x3ff0000000000000,1 +np.float64,0x3fee1f29b77c3e54,0x3ff7a0a58c13df62,1 +np.float64,0x3f9d06b8383a0d70,0x3ff001a54a2d8cf8,1 +np.float64,0xbfc8b41d3f31683c,0x3ff04c85379dd6b0,1 +np.float64,0xffd2a04c1e254098,0x7ff0000000000000,1 +np.float64,0xbfb71c01e02e3800,0x3ff010b34220e838,1 +np.float64,0xbfe69249ef6d2494,0x3ff425e48d1e938b,1 +np.float64,0xffefffffffffffff,0x7ff0000000000000,1 +np.float64,0x3feb1d52fbf63aa6,0x3ff618813ae922d7,1 +np.float64,0x7fb8d1a77e31a34e,0x7ff0000000000000,1 +np.float64,0xffc3cfc4ed279f88,0x7ff0000000000000,1 +np.float64,0x2164b9fc42c98,0x3ff0000000000000,1 +np.float64,0x3fbb868cee370d1a,0x3ff017b31b0d4d27,1 +np.float64,0x3fcd6dea583adbd5,0x3ff06cbd16bf44a0,1 +np.float64,0xbfecd041d479a084,0x3ff6efb25f61012d,1 +np.float64,0xbfb0552e6e20aa60,0x3ff00856ca83834a,1 +np.float64,0xe6293cbfcc528,0x3ff0000000000000,1 +np.float64,0x7fba58394034b072,0x7ff0000000000000,1 +np.float64,0x33bc96d467794,0x3ff0000000000000,1 +np.float64,0xffe90ea86bf21d50,0x7ff0000000000000,1 +np.float64,0xbfc626ea6d2c4dd4,0x3ff03d7e01ec3849,1 +np.float64,0x65b56fe4cb6af,0x3ff0000000000000,1 +np.float64,0x3fea409fb7f4813f,0x3ff5b171deab0ebd,1 +np.float64,0x3fe849c1df709384,0x3ff4d59063ff98c4,1 +np.float64,0x169073082d20f,0x3ff0000000000000,1 +np.float64,0xcc8b6add9916e,0x3ff0000000000000,1 +np.float64,0xbfef3d78d5fe7af2,0x3ff83fecc26abeea,1 +np.float64,0x3fe8c65a4a718cb4,0x3ff50a23bfeac7df,1 +np.float64,0x3fde9fa5c8bd3f4c,0x3ff1ddeb12b9d623,1 +np.float64,0xffe2af536da55ea6,0x7ff0000000000000,1 +np.float64,0x800186d0b0c30da2,0x3ff0000000000000,1 +np.float64,0x3fe9ba3c1d737478,0x3ff574ab2bf3a560,1 +np.float64,0xbfe1489c46a29138,0x3ff2641d36b30e21,1 +np.float64,0xbfe4b6b7c0e96d70,0x3ff37880ac8b0540,1 +np.float64,0x800e66ad82fccd5b,0x3ff0000000000000,1 +np.float64,0x7ff0000000000000,0x7ff0000000000000,1 +np.float64,0x7febb0fd477761fa,0x7ff0000000000000,1 +np.float64,0xbfdc433f2eb8867e,0x3ff195ec2a6cce27,1 +np.float64,0x3fe12c5a172258b4,0x3ff25c225b8a34bb,1 +np.float64,0xbfef6f116c3ede23,0x3ff85c47eaed49a0,1 +np.float64,0x800af6f60f35edec,0x3ff0000000000000,1 +np.float64,0xffe567999a2acf32,0x7ff0000000000000,1 +np.float64,0xbfc5ac5ae72b58b4,0x3ff03adb50ec04f3,1 +np.float64,0x3fea1b57e23436b0,0x3ff5a06f98541767,1 +np.float64,0x7fcc3e36fb387c6d,0x7ff0000000000000,1 +np.float64,0x8000c8dc698191ba,0x3ff0000000000000,1 +np.float64,0x3fee5085ed7ca10c,0x3ff7bb92f61245b8,1 +np.float64,0x7fbb9f803a373eff,0x7ff0000000000000,1 +np.float64,0xbfe1e5e806e3cbd0,0x3ff2918f2d773007,1 +np.float64,0x8008f8c3f3b1f188,0x3ff0000000000000,1 +np.float64,0x7fe53df515ea7be9,0x7ff0000000000000,1 +np.float64,0x7fdbb87fb3b770fe,0x7ff0000000000000,1 +np.float64,0x3fefcc0f50ff981f,0x3ff89210a6a04e6b,1 +np.float64,0x3fe33f87d0267f10,0x3ff2fb989ea4f2bc,1 +np.float64,0x1173992022e8,0x3ff0000000000000,1 +np.float64,0x3fef534632bea68c,0x3ff84c5ca9713ff9,1 +np.float64,0x3fc5991d552b3238,0x3ff03a72bfdb6e5f,1 +np.float64,0x3fdad90dc1b5b21c,0x3ff16db868180034,1 +np.float64,0xffe20b8078e41700,0x7ff0000000000000,1 +np.float64,0x7fdf409a82be8134,0x7ff0000000000000,1 +np.float64,0x3fccb7e691396fcd,0x3ff06786b6ccdbcb,1 +np.float64,0xffe416e0b7282dc1,0x7ff0000000000000,1 +np.float64,0xffe3a8a981275152,0x7ff0000000000000,1 +np.float64,0x3fd9c8bd31b3917c,0x3ff150ee6f5f692f,1 +np.float64,0xffeab6fef6356dfd,0x7ff0000000000000,1 +np.float64,0x3fe9c5e3faf38bc8,0x3ff579e18c9bd548,1 +np.float64,0x800b173e44762e7d,0x3ff0000000000000,1 +np.float64,0xffe2719db764e33b,0x7ff0000000000000,1 +np.float64,0x3fd1fcf31223f9e6,0x3ff0a2da7ad99856,1 +np.float64,0x80082c4afcd05896,0x3ff0000000000000,1 +np.float64,0xa56e5e4b4adcc,0x3ff0000000000000,1 +np.float64,0xffbbbddab2377bb8,0x7ff0000000000000,1 +np.float64,0x3b3927c076726,0x3ff0000000000000,1 +np.float64,0x3fec03fd58f807fb,0x3ff6889b8a774728,1 +np.float64,0xbfaa891fb4351240,0x3ff00580987bd914,1 +np.float64,0x7fb4800c4a290018,0x7ff0000000000000,1 +np.float64,0xffbb5d2b6036ba58,0x7ff0000000000000,1 +np.float64,0x7fd6608076acc100,0x7ff0000000000000,1 +np.float64,0x31267e4c624d1,0x3ff0000000000000,1 +np.float64,0x33272266664e5,0x3ff0000000000000,1 +np.float64,0x47bb37f28f768,0x3ff0000000000000,1 +np.float64,0x3fe134bb4ee26977,0x3ff25e7ea647a928,1 +np.float64,0xbfe2b5f42ba56be8,0x3ff2d05cbdc7344b,1 +np.float64,0xbfe0e013fd61c028,0x3ff246dfce572914,1 +np.float64,0x7fecedcda4f9db9a,0x7ff0000000000000,1 +np.float64,0x8001816c2da302d9,0x3ff0000000000000,1 +np.float64,0xffced8b65b3db16c,0x7ff0000000000000,1 +np.float64,0xffdc1d4a0b383a94,0x7ff0000000000000,1 +np.float64,0x7fe94e7339f29ce5,0x7ff0000000000000,1 +np.float64,0x33fb846667f71,0x3ff0000000000000,1 +np.float64,0x800a1380e9542702,0x3ff0000000000000,1 +np.float64,0x800b74eaa776e9d6,0x3ff0000000000000,1 +np.float64,0x5681784aad030,0x3ff0000000000000,1 +np.float64,0xbfee0eb7917c1d6f,0x3ff797b949f7f6b4,1 +np.float64,0xffe4ec5fd2a9d8bf,0x7ff0000000000000,1 +np.float64,0xbfcd7401dd3ae804,0x3ff06cea52c792c0,1 +np.float64,0x800587563beb0ead,0x3ff0000000000000,1 +np.float64,0x3fc15c6f3322b8de,0x3ff025bbd030166d,1 +np.float64,0x7feb6b4caf76d698,0x7ff0000000000000,1 +np.float64,0x7fe136ef82a26dde,0x7ff0000000000000,1 +np.float64,0xf592dac3eb25c,0x3ff0000000000000,1 +np.float64,0x7fd300baf6a60175,0x7ff0000000000000,1 +np.float64,0x7fc880de9e3101bc,0x7ff0000000000000,1 +np.float64,0x7fe7a1aa5caf4354,0x7ff0000000000000,1 +np.float64,0x2f9b8e0e5f373,0x3ff0000000000000,1 +np.float64,0xffcc9071993920e4,0x7ff0000000000000,1 +np.float64,0x8009e151b313c2a4,0x3ff0000000000000,1 +np.float64,0xbfd46e2d18a8dc5a,0x3ff0d27a7b37c1ae,1 +np.float64,0x3fe65c7961acb8f3,0x3ff4116946062a4c,1 +np.float64,0x7fd31b371626366d,0x7ff0000000000000,1 +np.float64,0x98dc924d31b93,0x3ff0000000000000,1 +np.float64,0x268bef364d17f,0x3ff0000000000000,1 +np.float64,0x7fd883ba56310774,0x7ff0000000000000,1 +np.float64,0x3fc53f01a32a7e03,0x3ff0388dea9cd63e,1 +np.float64,0xffe1ea8c0563d518,0x7ff0000000000000,1 +np.float64,0x3fd0bf0e63a17e1d,0x3ff08d0577f5ffa6,1 +np.float64,0x7fef42418f7e8482,0x7ff0000000000000,1 +np.float64,0x8000bccd38c1799b,0x3ff0000000000000,1 +np.float64,0xbfe6c48766ed890f,0x3ff43936fa4048c8,1 +np.float64,0xbfb2a38f3a254720,0x3ff00adc7f7b2822,1 +np.float64,0x3fd5262b2eaa4c56,0x3ff0e1af492c08f5,1 +np.float64,0x80065b4691ecb68e,0x3ff0000000000000,1 +np.float64,0xfb6b9e9ff6d74,0x3ff0000000000000,1 +np.float64,0x8006c71e6ecd8e3e,0x3ff0000000000000,1 +np.float64,0x3fd0a3e43ca147c8,0x3ff08b3ad7b42485,1 +np.float64,0xbfc82d8607305b0c,0x3ff04949d6733ef6,1 +np.float64,0xde048c61bc092,0x3ff0000000000000,1 +np.float64,0xffcf73e0fa3ee7c0,0x7ff0000000000000,1 +np.float64,0xbfe8639d7830c73b,0x3ff4e05f97948376,1 +np.float64,0x8010000000000000,0x3ff0000000000000,1 +np.float64,0x67f01a2acfe04,0x3ff0000000000000,1 +np.float64,0x3fe222e803e445d0,0x3ff2a3a75e5f29d8,1 +np.float64,0xffef84c6387f098b,0x7ff0000000000000,1 +np.float64,0x3fe5969c1e6b2d38,0x3ff3c80130462bb2,1 +np.float64,0x8009f56953d3ead3,0x3ff0000000000000,1 +np.float64,0x3fe05c9b6360b937,0x3ff2232e1cba5617,1 +np.float64,0x3fd8888d63b1111b,0x3ff130a5b788d52f,1 +np.float64,0xffe3a9e6f26753ce,0x7ff0000000000000,1 +np.float64,0x800e2aaa287c5554,0x3ff0000000000000,1 +np.float64,0x3fea8d6c82351ad9,0x3ff5d4d8cde9a11d,1 +np.float64,0x7feef700723dee00,0x7ff0000000000000,1 +np.float64,0x3fa5cb77242b96e0,0x3ff003b62b3e50f1,1 +np.float64,0x7fb68f0a862d1e14,0x7ff0000000000000,1 +np.float64,0x7fb97ee83432fdcf,0x7ff0000000000000,1 +np.float64,0x7fd74a78632e94f0,0x7ff0000000000000,1 +np.float64,0x7fcfe577713fcaee,0x7ff0000000000000,1 +np.float64,0xffe192ee5ea325dc,0x7ff0000000000000,1 +np.float64,0x477d6ae48efae,0x3ff0000000000000,1 +np.float64,0xffe34d5237669aa4,0x7ff0000000000000,1 +np.float64,0x7fe3ce8395a79d06,0x7ff0000000000000,1 +np.float64,0x80019c01ffa33805,0x3ff0000000000000,1 +np.float64,0x74b5b56ce96b7,0x3ff0000000000000,1 +np.float64,0x7fe05ecdeda0bd9b,0x7ff0000000000000,1 +np.float64,0xffe9693eb232d27d,0x7ff0000000000000,1 +np.float64,0xffd2be2c7da57c58,0x7ff0000000000000,1 +np.float64,0x800dbd5cbc1b7aba,0x3ff0000000000000,1 +np.float64,0xbfa36105d426c210,0x3ff002ef2e3a87f7,1 +np.float64,0x800b2d69fb765ad4,0x3ff0000000000000,1 +np.float64,0xbfdb81c9a9370394,0x3ff1802d409cbf7a,1 +np.float64,0x7fd481d014a9039f,0x7ff0000000000000,1 +np.float64,0xffe66c3c1fecd878,0x7ff0000000000000,1 +np.float64,0x3fc55865192ab0c8,0x3ff03915b51e8839,1 +np.float64,0xd6a78987ad4f1,0x3ff0000000000000,1 +np.float64,0x800c6cc80d58d990,0x3ff0000000000000,1 +np.float64,0x979435a12f29,0x3ff0000000000000,1 +np.float64,0xbfbd971e7a3b2e40,0x3ff01b647e45f5a6,1 +np.float64,0x80067565bfeceacc,0x3ff0000000000000,1 +np.float64,0x8001ad689ce35ad2,0x3ff0000000000000,1 +np.float64,0x7fa43253dc2864a7,0x7ff0000000000000,1 +np.float64,0xbfe3dda307e7bb46,0x3ff32ef99a2efe1d,1 +np.float64,0x3fe5d7b395ebaf68,0x3ff3dfd33cdc8ef4,1 +np.float64,0xd94cc9c3b2999,0x3ff0000000000000,1 +np.float64,0x3fee5a513fbcb4a2,0x3ff7c0f17b876ce5,1 +np.float64,0xffe27761fa64eec4,0x7ff0000000000000,1 +np.float64,0x3feb788119b6f102,0x3ff64446f67f4efa,1 +np.float64,0xbfed6e10dffadc22,0x3ff741d5ef610ca0,1 +np.float64,0x7fe73cf98b2e79f2,0x7ff0000000000000,1 +np.float64,0x7847d09af08fb,0x3ff0000000000000,1 +np.float64,0x29ded2da53bdb,0x3ff0000000000000,1 +np.float64,0xbfe51c1ec1aa383e,0x3ff39c0b7cf832e2,1 +np.float64,0xbfeafd5e65f5fabd,0x3ff609548a787f57,1 +np.float64,0x3fd872a26fb0e545,0x3ff12e7fbd95505c,1 +np.float64,0x7fed6b7c1b7ad6f7,0x7ff0000000000000,1 +np.float64,0xffe7ba9ec16f753d,0x7ff0000000000000,1 +np.float64,0x7f89b322f0336645,0x7ff0000000000000,1 +np.float64,0xbfad1677383a2cf0,0x3ff0069ca67e7baa,1 +np.float64,0x3fe0906d04a120da,0x3ff2311b04b7bfef,1 +np.float64,0xffe4b3c9d4296793,0x7ff0000000000000,1 +np.float64,0xbfe476bb0ce8ed76,0x3ff36277d2921a74,1 +np.float64,0x7fc35655cf26acab,0x7ff0000000000000,1 +np.float64,0x7fe9980f0373301d,0x7ff0000000000000,1 +np.float64,0x9e6e04cb3cdc1,0x3ff0000000000000,1 +np.float64,0x800b89e0afb713c2,0x3ff0000000000000,1 +np.float64,0x800bd951a3f7b2a4,0x3ff0000000000000,1 +np.float64,0x29644a9e52c8a,0x3ff0000000000000,1 +np.float64,0x3fe1be2843637c51,0x3ff285e90d8387e4,1 +np.float64,0x7fa233cce4246799,0x7ff0000000000000,1 +np.float64,0xbfcfb7bc2d3f6f78,0x3ff07e657de3e2ed,1 +np.float64,0xffd7c953e7af92a8,0x7ff0000000000000,1 +np.float64,0xbfc5bbaf772b7760,0x3ff03b2ee4febb1e,1 +np.float64,0x8007b7315a6f6e63,0x3ff0000000000000,1 +np.float64,0xbfe906d902320db2,0x3ff525d7e16acfe0,1 +np.float64,0x3fde33d8553c67b1,0x3ff1d09faa19aa53,1 +np.float64,0x61fe76a0c3fcf,0x3ff0000000000000,1 +np.float64,0xa75e355b4ebc7,0x3ff0000000000000,1 +np.float64,0x3fc9e6d86033cdb1,0x3ff05426299c7064,1 +np.float64,0x7fd83f489eb07e90,0x7ff0000000000000,1 +np.float64,0x8000000000000001,0x3ff0000000000000,1 +np.float64,0x80014434ae62886a,0x3ff0000000000000,1 +np.float64,0xbfe21af9686435f3,0x3ff2a149338bdefe,1 +np.float64,0x9354e6cd26a9d,0x3ff0000000000000,1 +np.float64,0xb42b95f768573,0x3ff0000000000000,1 +np.float64,0xbfecb4481bb96890,0x3ff6e15d269dd651,1 +np.float64,0x3f97842ae82f0840,0x3ff0011485156f28,1 +np.float64,0xffdef63d90bdec7c,0x7ff0000000000000,1 +np.float64,0x7fe511a8d36a2351,0x7ff0000000000000,1 +np.float64,0xbf8cb638a0396c80,0x3ff000670c318fb6,1 +np.float64,0x3fe467e1f668cfc4,0x3ff35d65f93ccac6,1 +np.float64,0xbfce7d88f03cfb10,0x3ff074c22475fe5b,1 +np.float64,0x6d0a4994da14a,0x3ff0000000000000,1 +np.float64,0xbfb3072580260e48,0x3ff00b51d3913e9f,1 +np.float64,0x8008fcde36b1f9bd,0x3ff0000000000000,1 +np.float64,0x3fd984df66b309c0,0x3ff149f29125eca4,1 +np.float64,0xffee2a10fe7c5421,0x7ff0000000000000,1 +np.float64,0x80039168ace722d2,0x3ff0000000000000,1 +np.float64,0xffda604379b4c086,0x7ff0000000000000,1 +np.float64,0xffdc6a405bb8d480,0x7ff0000000000000,1 +np.float64,0x3fe62888b26c5111,0x3ff3fdda754c4372,1 +np.float64,0x8008b452cb5168a6,0x3ff0000000000000,1 +np.float64,0x6165d540c2cbb,0x3ff0000000000000,1 +np.float64,0xbfee0c04d17c180a,0x3ff796431c64bcbe,1 +np.float64,0x800609b8448c1371,0x3ff0000000000000,1 +np.float64,0x800fc3fca59f87f9,0x3ff0000000000000,1 +np.float64,0x77f64848efeca,0x3ff0000000000000,1 +np.float64,0x8007cf522d8f9ea5,0x3ff0000000000000,1 +np.float64,0xbfe9fb0b93f3f617,0x3ff591cb0052e22c,1 +np.float64,0x7fd569d5f0aad3ab,0x7ff0000000000000,1 +np.float64,0x7fe5cf489d6b9e90,0x7ff0000000000000,1 +np.float64,0x7fd6e193e92dc327,0x7ff0000000000000,1 +np.float64,0xf78988a5ef131,0x3ff0000000000000,1 +np.float64,0x3fe8f97562b1f2eb,0x3ff5201080fbc12d,1 +np.float64,0x7febfd69d7b7fad3,0x7ff0000000000000,1 +np.float64,0xffc07b5c1720f6b8,0x7ff0000000000000,1 +np.float64,0xbfd966926832cd24,0x3ff146da9adf492e,1 +np.float64,0x7fef5bd9edfeb7b3,0x7ff0000000000000,1 +np.float64,0xbfd2afbc96255f7a,0x3ff0afd601febf44,1 +np.float64,0x7fdd4ea6293a9d4b,0x7ff0000000000000,1 +np.float64,0xbfe8a1e916b143d2,0x3ff4faa23c2793e5,1 +np.float64,0x800188fcd8c311fa,0x3ff0000000000000,1 +np.float64,0xbfe30803f1661008,0x3ff2e9fc729baaee,1 +np.float64,0x7fefffffffffffff,0x7ff0000000000000,1 +np.float64,0x3fd287bec3250f7e,0x3ff0ace34d3102f6,1 +np.float64,0x1f0ee9443e1de,0x3ff0000000000000,1 +np.float64,0xbfd92f73da325ee8,0x3ff14143e4fa2c5a,1 +np.float64,0x3fed7c9bdffaf938,0x3ff74984168734d3,1 +np.float64,0x8002c4d1696589a4,0x3ff0000000000000,1 +np.float64,0xfe03011bfc060,0x3ff0000000000000,1 +np.float64,0x7f7a391e6034723c,0x7ff0000000000000,1 +np.float64,0xffd6fd46f82dfa8e,0x7ff0000000000000,1 +np.float64,0xbfd7520a742ea414,0x3ff112f1ba5d4f91,1 +np.float64,0x8009389d8812713b,0x3ff0000000000000,1 +np.float64,0x7fefb846aaff708c,0x7ff0000000000000,1 +np.float64,0x3fd98a0983331413,0x3ff14a79efb8adbf,1 +np.float64,0xbfd897158db12e2c,0x3ff132137902cf3e,1 +np.float64,0xffc4048d5928091c,0x7ff0000000000000,1 +np.float64,0x80036ae46046d5ca,0x3ff0000000000000,1 +np.float64,0x7faba7ed3c374fd9,0x7ff0000000000000,1 +np.float64,0xbfec4265e1f884cc,0x3ff6a7b8602422c9,1 +np.float64,0xaa195e0b5432c,0x3ff0000000000000,1 +np.float64,0x3feac15d317582ba,0x3ff5ed115758145f,1 +np.float64,0x6c13a5bcd8275,0x3ff0000000000000,1 +np.float64,0xbfed20b8883a4171,0x3ff7194dbd0dc988,1 +np.float64,0x800cde65c899bccc,0x3ff0000000000000,1 +np.float64,0x7c72912af8e53,0x3ff0000000000000,1 +np.float64,0x3fe49d2bb4e93a57,0x3ff36fab3aba15d4,1 +np.float64,0xbfd598fa02ab31f4,0x3ff0eb72fc472025,1 +np.float64,0x8007a191712f4324,0x3ff0000000000000,1 +np.float64,0xbfdeb14872bd6290,0x3ff1e01ca83f35fd,1 +np.float64,0xbfe1da46b3e3b48e,0x3ff28e23ad2f5615,1 +np.float64,0x800a2f348e745e69,0x3ff0000000000000,1 +np.float64,0xbfee66928afccd25,0x3ff7c7ac7dbb3273,1 +np.float64,0xffd78a0a2b2f1414,0x7ff0000000000000,1 +np.float64,0x7fc5fa80b82bf500,0x7ff0000000000000,1 +np.float64,0x800e6d7260dcdae5,0x3ff0000000000000,1 +np.float64,0xbfd6cff2aaad9fe6,0x3ff106f78ee61642,1 +np.float64,0x7fe1041d1d220839,0x7ff0000000000000,1 +np.float64,0xbfdf75586cbeeab0,0x3ff1f8dbaa7e57f0,1 +np.float64,0xffdcaae410b955c8,0x7ff0000000000000,1 +np.float64,0x800fe5e0d1ffcbc2,0x3ff0000000000000,1 +np.float64,0x800d7999527af333,0x3ff0000000000000,1 +np.float64,0xbfe62c233bac5846,0x3ff3ff34220a204c,1 +np.float64,0x7fe99bbff8f3377f,0x7ff0000000000000,1 +np.float64,0x7feeaf471d3d5e8d,0x7ff0000000000000,1 +np.float64,0xd5904ff5ab20a,0x3ff0000000000000,1 +np.float64,0x3fd07aae3320f55c,0x3ff08888c227c968,1 +np.float64,0x7fea82b8dff50571,0x7ff0000000000000,1 +np.float64,0xffef2db9057e5b71,0x7ff0000000000000,1 +np.float64,0xbfe2077fef640f00,0x3ff29b7dd0d39d36,1 +np.float64,0xbfe09a4d7c61349b,0x3ff233c7e88881f4,1 +np.float64,0x3fda50c4cbb4a188,0x3ff15f28a71deee7,1 +np.float64,0x7fe7d9ee6b2fb3dc,0x7ff0000000000000,1 +np.float64,0x3febbf6faeb77edf,0x3ff666d13682ea93,1 +np.float64,0xc401a32988035,0x3ff0000000000000,1 +np.float64,0xbfeab30aa8f56615,0x3ff5e65dcc6603f8,1 +np.float64,0x92c8cea32591a,0x3ff0000000000000,1 +np.float64,0xbff0000000000000,0x3ff8b07551d9f550,1 +np.float64,0xbfbddfb4dc3bbf68,0x3ff01bebaec38faa,1 +np.float64,0xbfd8de3e2a31bc7c,0x3ff1391f4830d20b,1 +np.float64,0xffc83a8f8a307520,0x7ff0000000000000,1 +np.float64,0x3fee026ef53c04de,0x3ff7911337085827,1 +np.float64,0x7fbaf380b235e700,0x7ff0000000000000,1 +np.float64,0xffe5b89fa62b713f,0x7ff0000000000000,1 +np.float64,0xbfdc1ff54ab83fea,0x3ff191e8c0b60bb2,1 +np.float64,0x6ae3534cd5c6b,0x3ff0000000000000,1 +np.float64,0xbfea87e558750fcb,0x3ff5d24846013794,1 +np.float64,0xffe0f467bee1e8cf,0x7ff0000000000000,1 +np.float64,0x7fee3b0dc7bc761b,0x7ff0000000000000,1 +np.float64,0x3fed87521afb0ea4,0x3ff74f2f5cd36a5c,1 +np.float64,0x7b3c9882f6794,0x3ff0000000000000,1 +np.float64,0x7fdd1a62243a34c3,0x7ff0000000000000,1 +np.float64,0x800f1dc88d3e3b91,0x3ff0000000000000,1 +np.float64,0x7fc3213cfa264279,0x7ff0000000000000,1 +np.float64,0x3fe40e0f3d681c1e,0x3ff33f135e9d5ded,1 +np.float64,0x7febf14e51f7e29c,0x7ff0000000000000,1 +np.float64,0xffe96c630c72d8c5,0x7ff0000000000000,1 +np.float64,0x7fdd82fbe7bb05f7,0x7ff0000000000000,1 +np.float64,0xbf9a6a0b1034d420,0x3ff0015ce009f7d8,1 +np.float64,0xbfceb4f8153d69f0,0x3ff0766e3ecc77df,1 +np.float64,0x3fd9de31e633bc64,0x3ff15327b794a16e,1 +np.float64,0x3faa902a30352054,0x3ff00583848d1969,1 +np.float64,0x0,0x3ff0000000000000,1 +np.float64,0x3fbe3459c43c68b4,0x3ff01c8af6710ef6,1 +np.float64,0xbfa8df010031be00,0x3ff004d5632dc9f5,1 +np.float64,0x7fbcf6cf2a39ed9d,0x7ff0000000000000,1 +np.float64,0xffe4236202a846c4,0x7ff0000000000000,1 +np.float64,0x3fd35ed52e26bdaa,0x3ff0bd0b231f11f7,1 +np.float64,0x7fe7a2df532f45be,0x7ff0000000000000,1 +np.float64,0xffe32f8315665f06,0x7ff0000000000000,1 +np.float64,0x7fe1a69f03e34d3d,0x7ff0000000000000,1 +np.float64,0x7fa5542b742aa856,0x7ff0000000000000,1 +np.float64,0x3fe84e9f8ef09d3f,0x3ff4d79816359765,1 +np.float64,0x29076fe6520ef,0x3ff0000000000000,1 +np.float64,0xffd70894f7ae112a,0x7ff0000000000000,1 +np.float64,0x800188edcbe311dc,0x3ff0000000000000,1 +np.float64,0x3fe2c7acda258f5a,0x3ff2d5dad4617703,1 +np.float64,0x3f775d41a02ebb00,0x3ff000110f212445,1 +np.float64,0x7fe8a084d1714109,0x7ff0000000000000,1 +np.float64,0x3fe31562d8a62ac6,0x3ff2ee35055741cd,1 +np.float64,0xbfd195d4d1a32baa,0x3ff09b98a50c151b,1 +np.float64,0xffaae9ff0c35d400,0x7ff0000000000000,1 +np.float64,0xff819866502330c0,0x7ff0000000000000,1 +np.float64,0x7fddc64815bb8c8f,0x7ff0000000000000,1 +np.float64,0xbfd442b428288568,0x3ff0cef70aa73ae6,1 +np.float64,0x8002e7625aa5cec5,0x3ff0000000000000,1 +np.float64,0x7fe8d4f70e71a9ed,0x7ff0000000000000,1 +np.float64,0xbfc3bd015f277a04,0x3ff030cbf16f29d9,1 +np.float64,0x3fd315d5baa62bab,0x3ff0b77a551a5335,1 +np.float64,0x7fa638b4642c7168,0x7ff0000000000000,1 +np.float64,0x3fdea8b795bd516f,0x3ff1df0bb70cdb79,1 +np.float64,0xbfd78754762f0ea8,0x3ff117ee0f29abed,1 +np.float64,0x8009f6a37633ed47,0x3ff0000000000000,1 +np.float64,0x3fea1daf75343b5f,0x3ff5a1804789bf13,1 +np.float64,0x3fd044b6c0a0896e,0x3ff0850b7297d02f,1 +np.float64,0x8003547a9c86a8f6,0x3ff0000000000000,1 +np.float64,0x3fa6c2cd782d859b,0x3ff0040c4ac8f44a,1 +np.float64,0x3fe225baaae44b76,0x3ff2a47f5e1f5e85,1 +np.float64,0x8000000000000000,0x3ff0000000000000,1 +np.float64,0x3fcb53da8736a7b8,0x3ff05db45af470ac,1 +np.float64,0x80079f8f140f3f1f,0x3ff0000000000000,1 +np.float64,0xbfcd1d7e2b3a3afc,0x3ff06a6b6845d05f,1 +np.float64,0x96df93672dbf3,0x3ff0000000000000,1 +np.float64,0xdef86e43bdf0e,0x3ff0000000000000,1 +np.float64,0xbfec05a09db80b41,0x3ff6896b768eea08,1 +np.float64,0x7fe3ff91d267ff23,0x7ff0000000000000,1 +np.float64,0xffea3eaa07347d53,0x7ff0000000000000,1 +np.float64,0xbfebde1cc1f7bc3a,0x3ff675e34ac2afc2,1 +np.float64,0x629bcde8c537a,0x3ff0000000000000,1 +np.float64,0xbfdde4fcff3bc9fa,0x3ff1c7061d21f0fe,1 +np.float64,0x3fee60fd003cc1fa,0x3ff7c49af3878a51,1 +np.float64,0x3fe5c92ac32b9256,0x3ff3da7a7929588b,1 +np.float64,0xbfe249c78f64938f,0x3ff2af52a06f1a50,1 +np.float64,0xbfc6de9dbe2dbd3c,0x3ff0418d284ee29f,1 +np.float64,0xffc8ef094631de14,0x7ff0000000000000,1 +np.float64,0x3fdef05f423de0bf,0x3ff1e800caba8ab5,1 +np.float64,0xffc1090731221210,0x7ff0000000000000,1 +np.float64,0xbfedec9b5fbbd937,0x3ff7854b6792a24a,1 +np.float64,0xbfb873507630e6a0,0x3ff012b23b3b7a67,1 +np.float64,0xbfe3cd6692679acd,0x3ff3299d6936ec4b,1 +np.float64,0xbfb107c890220f90,0x3ff0091122162472,1 +np.float64,0xbfe4e6ee48e9cddc,0x3ff3894e5a5e70a6,1 +np.float64,0xffe6fa3413edf468,0x7ff0000000000000,1 +np.float64,0x3fe2faf79b65f5ef,0x3ff2e5e11fae8b54,1 +np.float64,0xbfdfeb8df9bfd71c,0x3ff208189691b15f,1 +np.float64,0x75d2d03ceba5b,0x3ff0000000000000,1 +np.float64,0x3feb48c182b69183,0x3ff62d4462eba6cb,1 +np.float64,0xffcda9f7ff3b53f0,0x7ff0000000000000,1 +np.float64,0x7fcafbdcbd35f7b8,0x7ff0000000000000,1 +np.float64,0xbfd1895523a312aa,0x3ff09aba642a78d9,1 +np.float64,0x3fe3129c3f662538,0x3ff2ed546bbfafcf,1 +np.float64,0x3fb444dee02889be,0x3ff00cd86273b964,1 +np.float64,0xbf73b32d7ee77,0x3ff0000000000000,1 +np.float64,0x3fae19904c3c3321,0x3ff00714865c498a,1 +np.float64,0x7fefbfaef5bf7f5d,0x7ff0000000000000,1 +np.float64,0x8000dc3816e1b871,0x3ff0000000000000,1 +np.float64,0x8003f957ba47f2b0,0x3ff0000000000000,1 +np.float64,0xbfe3563c7ea6ac79,0x3ff302dcebc92856,1 +np.float64,0xbfdc80fbae3901f8,0x3ff19cfe73e58092,1 +np.float64,0x8009223b04524476,0x3ff0000000000000,1 +np.float64,0x3fd95f431c32be86,0x3ff1461c21cb03f0,1 +np.float64,0x7ff4000000000000,0x7ffc000000000000,1 +np.float64,0xbfe7c12ed3ef825e,0x3ff49d59c265efcd,1 +np.float64,0x10000000000000,0x3ff0000000000000,1 +np.float64,0x7fc5e2632f2bc4c5,0x7ff0000000000000,1 +np.float64,0xffd8f6b4c7b1ed6a,0x7ff0000000000000,1 +np.float64,0x80034b93d4069728,0x3ff0000000000000,1 +np.float64,0xffdf5d4c1dbeba98,0x7ff0000000000000,1 +np.float64,0x800bc63d70178c7b,0x3ff0000000000000,1 +np.float64,0xbfeba31ea0f7463d,0x3ff658fa27073d2b,1 +np.float64,0xbfeebeede97d7ddc,0x3ff7f89a8e80dec4,1 +np.float64,0x7feb0f1f91361e3e,0x7ff0000000000000,1 +np.float64,0xffec3158d0b862b1,0x7ff0000000000000,1 +np.float64,0x3fde51cbfbbca398,0x3ff1d44c2ff15b3d,1 +np.float64,0xd58fb2b3ab1f7,0x3ff0000000000000,1 +np.float64,0x80028b9e32e5173d,0x3ff0000000000000,1 +np.float64,0x7fea77a56c74ef4a,0x7ff0000000000000,1 +np.float64,0x3fdaabbd4a35577b,0x3ff168d82edf2fe0,1 +np.float64,0xbfe69c39cc2d3874,0x3ff429b2f4cdb362,1 +np.float64,0x3b78f5d876f20,0x3ff0000000000000,1 +np.float64,0x7fa47d116428fa22,0x7ff0000000000000,1 +np.float64,0xbfe4118b0ce82316,0x3ff3403d989f780f,1 +np.float64,0x800482e793c905d0,0x3ff0000000000000,1 +np.float64,0xbfe48e5728e91cae,0x3ff36a9020bf9d20,1 +np.float64,0x7fe078ba8860f174,0x7ff0000000000000,1 +np.float64,0x3fd80843e5b01088,0x3ff1242f401e67da,1 +np.float64,0x3feb1f6965f63ed3,0x3ff6197fc590e143,1 +np.float64,0xffa41946d8283290,0x7ff0000000000000,1 +np.float64,0xffe30de129661bc2,0x7ff0000000000000,1 +np.float64,0x3fec9c8e1ab9391c,0x3ff6d542ea2f49b4,1 +np.float64,0x3fdc3e4490387c89,0x3ff1955ae18cac37,1 +np.float64,0xffef49d9c77e93b3,0x7ff0000000000000,1 +np.float64,0xfff0000000000000,0x7ff0000000000000,1 +np.float64,0x3fe0442455608849,0x3ff21cab90067d5c,1 +np.float64,0xbfed86aebd3b0d5e,0x3ff74ed8d4b75f50,1 +np.float64,0xffe4600d2b28c01a,0x7ff0000000000000,1 +np.float64,0x7fc1e8ccff23d199,0x7ff0000000000000,1 +np.float64,0x8008d49b0091a936,0x3ff0000000000000,1 +np.float64,0xbfe4139df028273c,0x3ff340ef3c86227c,1 +np.float64,0xbfe9ab4542b3568a,0x3ff56dfe32061247,1 +np.float64,0xbfd76dd365aedba6,0x3ff11589bab5fe71,1 +np.float64,0x3fd42cf829a859f0,0x3ff0cd3844bb0e11,1 +np.float64,0x7fd077cf2e20ef9d,0x7ff0000000000000,1 +np.float64,0x3fd7505760aea0b0,0x3ff112c937b3f088,1 +np.float64,0x1f93341a3f267,0x3ff0000000000000,1 +np.float64,0x7fe3c3c1b0678782,0x7ff0000000000000,1 +np.float64,0x800f85cec97f0b9e,0x3ff0000000000000,1 +np.float64,0xd93ab121b2756,0x3ff0000000000000,1 +np.float64,0xbfef8066fd7f00ce,0x3ff8663ed7d15189,1 +np.float64,0xffe31dd4af663ba9,0x7ff0000000000000,1 +np.float64,0xbfd7ff05a6affe0c,0x3ff1234c09bb686d,1 +np.float64,0xbfe718c31fee3186,0x3ff45a0c2d0ef7b0,1 +np.float64,0x800484bf33e9097f,0x3ff0000000000000,1 +np.float64,0xffd409dad02813b6,0x7ff0000000000000,1 +np.float64,0x3fe59679896b2cf4,0x3ff3c7f49e4fbbd3,1 +np.float64,0xbfd830c54d30618a,0x3ff1281729861390,1 +np.float64,0x1d4fc81c3a9fa,0x3ff0000000000000,1 +np.float64,0x3fd334e4272669c8,0x3ff0b9d5d82894f0,1 +np.float64,0xffc827e65c304fcc,0x7ff0000000000000,1 +np.float64,0xffe2d1814aa5a302,0x7ff0000000000000,1 +np.float64,0xffd7b5b8d32f6b72,0x7ff0000000000000,1 +np.float64,0xbfdbc9f077b793e0,0x3ff18836b9106ad0,1 +np.float64,0x7fc724c2082e4983,0x7ff0000000000000,1 +np.float64,0x3fa39ed72c273da0,0x3ff00302051ce17e,1 +np.float64,0xbfe3c4c209678984,0x3ff326c4fd16b5cd,1 +np.float64,0x7fe91f6d00f23ed9,0x7ff0000000000000,1 +np.float64,0x8004ee93fea9dd29,0x3ff0000000000000,1 +np.float64,0xbfe7c32d0eaf865a,0x3ff49e290ed2ca0e,1 +np.float64,0x800ea996b29d532d,0x3ff0000000000000,1 +np.float64,0x2df9ec1c5bf3e,0x3ff0000000000000,1 +np.float64,0xabb175df5762f,0x3ff0000000000000,1 +np.float64,0xffe3fc9c8e27f938,0x7ff0000000000000,1 +np.float64,0x7fb358a62826b14b,0x7ff0000000000000,1 +np.float64,0x800aedcccaf5db9a,0x3ff0000000000000,1 +np.float64,0xffca530c5234a618,0x7ff0000000000000,1 +np.float64,0x40f91e9681f24,0x3ff0000000000000,1 +np.float64,0x80098f4572f31e8b,0x3ff0000000000000,1 +np.float64,0xbfdc58c21fb8b184,0x3ff1986115f8fe92,1 +np.float64,0xbfebeafd40b7d5fa,0x3ff67c3cf34036e3,1 +np.float64,0x7fd108861a22110b,0x7ff0000000000000,1 +np.float64,0xff8e499ae03c9340,0x7ff0000000000000,1 +np.float64,0xbfd2f58caa25eb1a,0x3ff0b50b1bffafdf,1 +np.float64,0x3fa040c9bc208193,0x3ff002105e95aefa,1 +np.float64,0xbfd2ebc0a5a5d782,0x3ff0b44ed5a11584,1 +np.float64,0xffe237bc93a46f78,0x7ff0000000000000,1 +np.float64,0x3fd557c5eeaaaf8c,0x3ff0e5e0a575e1ba,1 +np.float64,0x7abb419ef5769,0x3ff0000000000000,1 +np.float64,0xffefa1fe353f43fb,0x7ff0000000000000,1 +np.float64,0x3fa6f80ba02df017,0x3ff0041f51fa0d76,1 +np.float64,0xbfdce79488b9cf2a,0x3ff1a8e32877beb4,1 +np.float64,0x2285f3e4450bf,0x3ff0000000000000,1 +np.float64,0x3bf7eb7277efe,0x3ff0000000000000,1 +np.float64,0xbfd5925fd3ab24c0,0x3ff0eae1c2ac2e78,1 +np.float64,0xbfed6325227ac64a,0x3ff73c14a2ad5bfe,1 +np.float64,0x8000429c02408539,0x3ff0000000000000,1 +np.float64,0xb67c21e76cf84,0x3ff0000000000000,1 +np.float64,0x3fec3d3462f87a69,0x3ff6a51e4c027eb7,1 +np.float64,0x3feae69cbcf5cd3a,0x3ff5fe9387314afd,1 +np.float64,0x7fd0c9a0ec219341,0x7ff0000000000000,1 +np.float64,0x8004adb7f6295b71,0x3ff0000000000000,1 +np.float64,0xffd61fe8bb2c3fd2,0x7ff0000000000000,1 +np.float64,0xffe7fb3834aff670,0x7ff0000000000000,1 +np.float64,0x7fd1eef163a3dde2,0x7ff0000000000000,1 +np.float64,0x2e84547a5d08b,0x3ff0000000000000,1 +np.float64,0x8002d8875ee5b10f,0x3ff0000000000000,1 +np.float64,0x3fe1d1c5f763a38c,0x3ff28ba524fb6de8,1 +np.float64,0x8001dea0bc43bd42,0x3ff0000000000000,1 +np.float64,0xfecfad91fd9f6,0x3ff0000000000000,1 +np.float64,0xffed7965fa3af2cb,0x7ff0000000000000,1 +np.float64,0xbfe6102ccc2c205a,0x3ff3f4c082506686,1 +np.float64,0x3feff75b777feeb6,0x3ff8ab6222578e0c,1 +np.float64,0x3fb8a97bd43152f8,0x3ff013057f0a9d89,1 +np.float64,0xffe234b5e964696c,0x7ff0000000000000,1 +np.float64,0x984d9137309b2,0x3ff0000000000000,1 +np.float64,0xbfe42e9230e85d24,0x3ff349fb7d1a7560,1 +np.float64,0xbfecc8b249f99165,0x3ff6ebd0fea0ea72,1 +np.float64,0x8000840910410813,0x3ff0000000000000,1 +np.float64,0xbfd81db9e7303b74,0x3ff126402d3539ec,1 +np.float64,0x800548eb7fea91d8,0x3ff0000000000000,1 +np.float64,0xbfe4679ad0e8cf36,0x3ff35d4db89296a3,1 +np.float64,0x3fd4c55b5a298ab7,0x3ff0d99da31081f9,1 +np.float64,0xbfa8f5b38c31eb60,0x3ff004de3a23b32d,1 +np.float64,0x80005d348e80ba6a,0x3ff0000000000000,1 +np.float64,0x800c348d6118691b,0x3ff0000000000000,1 +np.float64,0xffd6b88f84ad7120,0x7ff0000000000000,1 +np.float64,0x3fc1aaaa82235555,0x3ff027136afd08e0,1 +np.float64,0x7fca7d081b34fa0f,0x7ff0000000000000,1 +np.float64,0x1,0x3ff0000000000000,1 +np.float64,0xbfdc810d1139021a,0x3ff19d007408cfe3,1 +np.float64,0xbfe5dce05f2bb9c0,0x3ff3e1bb9234617b,1 +np.float64,0xffecfe2c32b9fc58,0x7ff0000000000000,1 +np.float64,0x95b2891b2b651,0x3ff0000000000000,1 +np.float64,0x8000b60c6c616c1a,0x3ff0000000000000,1 +np.float64,0x4944f0889289f,0x3ff0000000000000,1 +np.float64,0x3fe6e508696dca10,0x3ff445d1b94863e9,1 +np.float64,0xbfe63355d0ec66ac,0x3ff401e74f16d16f,1 +np.float64,0xbfe9b9595af372b3,0x3ff57445e1b4d670,1 +np.float64,0x800e16f7313c2dee,0x3ff0000000000000,1 +np.float64,0xffe898f5f0b131eb,0x7ff0000000000000,1 +np.float64,0x3fe91ac651f2358d,0x3ff52e787c21c004,1 +np.float64,0x7fbfaac6783f558c,0x7ff0000000000000,1 +np.float64,0xd8ef3dfbb1de8,0x3ff0000000000000,1 +np.float64,0xbfc58c13a52b1828,0x3ff03a2c19d65019,1 +np.float64,0xbfbde55e8a3bcac0,0x3ff01bf648a3e0a7,1 +np.float64,0xffc3034930260694,0x7ff0000000000000,1 +np.float64,0xea77a64dd4ef5,0x3ff0000000000000,1 +np.float64,0x800cfe7e7739fcfd,0x3ff0000000000000,1 +np.float64,0x4960f31a92c1f,0x3ff0000000000000,1 +np.float64,0x3fd9552c94b2aa58,0x3ff14515a29add09,1 +np.float64,0xffe8b3244c316648,0x7ff0000000000000,1 +np.float64,0x3fe8201e6a70403d,0x3ff4c444fa679cce,1 +np.float64,0xffe9ab7c20f356f8,0x7ff0000000000000,1 +np.float64,0x3fed8bba5f7b1774,0x3ff751853c4c95c5,1 +np.float64,0x8007639cb76ec73a,0x3ff0000000000000,1 +np.float64,0xbfe396db89672db7,0x3ff317bfd1d6fa8c,1 +np.float64,0xbfeb42f888f685f1,0x3ff62a7e0eee56b1,1 +np.float64,0x3fe894827c712904,0x3ff4f4f561d9ea13,1 +np.float64,0xb66b3caf6cd68,0x3ff0000000000000,1 +np.float64,0x800f8907fdbf1210,0x3ff0000000000000,1 +np.float64,0x7fe9b0cddb73619b,0x7ff0000000000000,1 +np.float64,0xbfda70c0e634e182,0x3ff1628c6fdffc53,1 +np.float64,0x3fe0b5f534a16bea,0x3ff23b4ed4c2b48e,1 +np.float64,0xbfe8eee93671ddd2,0x3ff51b85b3c50ae4,1 +np.float64,0xbfe8c22627f1844c,0x3ff50858787a3bfe,1 +np.float64,0x37bb83c86f771,0x3ff0000000000000,1 +np.float64,0xffb7827ffe2f0500,0x7ff0000000000000,1 +np.float64,0x64317940c864,0x3ff0000000000000,1 +np.float64,0x800430ecee6861db,0x3ff0000000000000,1 +np.float64,0x3fa4291fbc285240,0x3ff0032d0204f6dd,1 +np.float64,0xffec69f76af8d3ee,0x7ff0000000000000,1 +np.float64,0x3ff0000000000000,0x3ff8b07551d9f550,1 +np.float64,0x3fc4cf3c42299e79,0x3ff0363fb1d3c254,1 +np.float64,0x7fe0223a77e04474,0x7ff0000000000000,1 +np.float64,0x800a3d4fa4347aa0,0x3ff0000000000000,1 +np.float64,0x3fdd273f94ba4e7f,0x3ff1b05b686e6879,1 +np.float64,0x3feca79052f94f20,0x3ff6dadedfa283aa,1 +np.float64,0x5e7f6f80bcfef,0x3ff0000000000000,1 +np.float64,0xbfef035892fe06b1,0x3ff81efb39cbeba2,1 +np.float64,0x3fee6c08e07cd812,0x3ff7caad952860a1,1 +np.float64,0xffeda715877b4e2a,0x7ff0000000000000,1 +np.float64,0x800580286b0b0052,0x3ff0000000000000,1 +np.float64,0x800703a73fee074f,0x3ff0000000000000,1 +np.float64,0xbfccf96a6639f2d4,0x3ff0696330a60832,1 +np.float64,0x7feb408442368108,0x7ff0000000000000,1 +np.float64,0x3fedc87a46fb90f5,0x3ff771e3635649a9,1 +np.float64,0x3fd8297b773052f7,0x3ff12762bc0cea76,1 +np.float64,0x3fee41bb03fc8376,0x3ff7b37b2da48ab4,1 +np.float64,0xbfe2b05a226560b4,0x3ff2cea17ae7c528,1 +np.float64,0xbfd2e92cf2a5d25a,0x3ff0b41d605ced61,1 +np.float64,0x4817f03a902ff,0x3ff0000000000000,1 +np.float64,0x8c9d4f0d193aa,0x3ff0000000000000,1 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-exp.csv b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-exp.csv new file mode 100644 index 0000000000000000000000000000000000000000..7c5ef3b334fbd8a63bb1a0e6ad72577f767dbaf6 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-exp.csv @@ -0,0 +1,412 @@ +dtype,input,output,ulperrortol +## +ve denormals ## +np.float32,0x004b4716,0x3f800000,3 +np.float32,0x007b2490,0x3f800000,3 +np.float32,0x007c99fa,0x3f800000,3 +np.float32,0x00734a0c,0x3f800000,3 +np.float32,0x0070de24,0x3f800000,3 +np.float32,0x00495d65,0x3f800000,3 +np.float32,0x006894f6,0x3f800000,3 +np.float32,0x00555a76,0x3f800000,3 +np.float32,0x004e1fb8,0x3f800000,3 +np.float32,0x00687de9,0x3f800000,3 +## -ve denormals ## +np.float32,0x805b59af,0x3f800000,3 +np.float32,0x807ed8ed,0x3f800000,3 +np.float32,0x807142ad,0x3f800000,3 +np.float32,0x80772002,0x3f800000,3 +np.float32,0x8062abcb,0x3f800000,3 +np.float32,0x8045e31c,0x3f800000,3 +np.float32,0x805f01c2,0x3f800000,3 +np.float32,0x80506432,0x3f800000,3 +np.float32,0x8060089d,0x3f800000,3 +np.float32,0x8071292f,0x3f800000,3 +## floats that output a denormal ## +np.float32,0xc2cf3fc1,0x00000001,3 +np.float32,0xc2c79726,0x00000021,3 +np.float32,0xc2cb295d,0x00000005,3 +np.float32,0xc2b49e6b,0x00068c4c,3 +np.float32,0xc2ca8116,0x00000008,3 +np.float32,0xc2c23f82,0x000001d7,3 +np.float32,0xc2cb69c0,0x00000005,3 +np.float32,0xc2cc1f4d,0x00000003,3 +np.float32,0xc2ae094e,0x00affc4c,3 +np.float32,0xc2c86c44,0x00000015,3 +## random floats between -87.0f and 88.0f ## +np.float32,0x4030d7e0,0x417d9a05,3 +np.float32,0x426f60e8,0x6aa1be2c,3 +np.float32,0x41a1b220,0x4e0efc11,3 +np.float32,0xc20cc722,0x26159da7,3 +np.float32,0x41c492bc,0x512ec79d,3 +np.float32,0x40980210,0x42e73a0e,3 +np.float32,0xbf1f7b80,0x3f094de3,3 +np.float32,0x42a678a4,0x7b87a383,3 +np.float32,0xc20f3cfd,0x25a1c304,3 +np.float32,0x423ff34c,0x6216467f,3 +np.float32,0x00000000,0x3f800000,3 +## floats that cause an overflow ## +np.float32,0x7f06d8c1,0x7f800000,3 +np.float32,0x7f451912,0x7f800000,3 +np.float32,0x7ecceac3,0x7f800000,3 +np.float32,0x7f643b45,0x7f800000,3 +np.float32,0x7e910ea0,0x7f800000,3 +np.float32,0x7eb4756b,0x7f800000,3 +np.float32,0x7f4ec708,0x7f800000,3 +np.float32,0x7f6b4551,0x7f800000,3 +np.float32,0x7d8edbda,0x7f800000,3 +np.float32,0x7f730718,0x7f800000,3 +np.float32,0x42b17217,0x7f7fff84,3 +np.float32,0x42b17218,0x7f800000,3 +np.float32,0x42b17219,0x7f800000,3 +np.float32,0xfef2b0bc,0x00000000,3 +np.float32,0xff69f83e,0x00000000,3 +np.float32,0xff4ecb12,0x00000000,3 +np.float32,0xfeac6d86,0x00000000,3 +np.float32,0xfde0cdb8,0x00000000,3 +np.float32,0xff26aef4,0x00000000,3 +np.float32,0xff6f9277,0x00000000,3 +np.float32,0xff7adfc4,0x00000000,3 +np.float32,0xff0ad40e,0x00000000,3 +np.float32,0xff6fd8f3,0x00000000,3 +np.float32,0xc2cff1b4,0x00000001,3 +np.float32,0xc2cff1b5,0x00000000,3 +np.float32,0xc2cff1b6,0x00000000,3 +np.float32,0x7f800000,0x7f800000,3 +np.float32,0xff800000,0x00000000,3 +np.float32,0x4292f27c,0x7480000a,3 +np.float32,0x42a920be,0x7c7fff94,3 +np.float32,0x41c214c9,0x50ffffd9,3 +np.float32,0x41abe686,0x4effffd9,3 +np.float32,0x4287db5a,0x707fffd3,3 +np.float32,0x41902cbb,0x4c800078,3 +np.float32,0x42609466,0x67ffffeb,3 +np.float32,0x41a65af5,0x4e7fffd1,3 +np.float32,0x417f13ff,0x4affffc9,3 +np.float32,0x426d0e6c,0x6a3504f2,3 +np.float32,0x41bc8934,0x507fff51,3 +np.float32,0x42a7bdde,0x7c0000d6,3 +np.float32,0x4120cf66,0x46b504f6,3 +np.float32,0x4244da8f,0x62ffff1a,3 +np.float32,0x41a0cf69,0x4e000034,3 +np.float32,0x41cd2bec,0x52000005,3 +np.float32,0x42893e41,0x7100009e,3 +np.float32,0x41b437e1,0x4fb50502,3 +np.float32,0x41d8430f,0x5300001d,3 +np.float32,0x4244da92,0x62ffffda,3 +np.float32,0x41a0cf63,0x4dffffa9,3 +np.float32,0x3eb17218,0x3fb504f3,3 +np.float32,0x428729e8,0x703504dc,3 +np.float32,0x41a0cf67,0x4e000014,3 +np.float32,0x4252b77d,0x65800011,3 +np.float32,0x41902cb9,0x4c800058,3 +np.float32,0x42a0cf67,0x79800052,3 +np.float32,0x4152b77b,0x48ffffe9,3 +np.float32,0x41265af3,0x46ffffc8,3 +np.float32,0x42187e0b,0x5affff9a,3 +np.float32,0xc0d2b77c,0x3ab504f6,3 +np.float32,0xc283b2ac,0x10000072,3 +np.float32,0xc1cff1b4,0x2cb504f5,3 +np.float32,0xc05dce9e,0x3d000000,3 +np.float32,0xc28ec9d2,0x0bfffea5,3 +np.float32,0xc23c893a,0x1d7fffde,3 +np.float32,0xc2a920c0,0x027fff6c,3 +np.float32,0xc1f9886f,0x2900002b,3 +np.float32,0xc2c42920,0x000000b5,3 +np.float32,0xc2893e41,0x0dfffec5,3 +np.float32,0xc2c4da93,0x00000080,3 +np.float32,0xc17f1401,0x3400000c,3 +np.float32,0xc1902cb6,0x327fffaf,3 +np.float32,0xc27c4e3b,0x11ffffc5,3 +np.float32,0xc268e5c5,0x157ffe9d,3 +np.float32,0xc2b4e953,0x0005a826,3 +np.float32,0xc287db5a,0x0e800016,3 +np.float32,0xc207db5a,0x2700000b,3 +np.float32,0xc2b2d4fe,0x000ffff1,3 +np.float32,0xc268e5c0,0x157fffdd,3 +np.float32,0xc22920bd,0x2100003b,3 +np.float32,0xc2902caf,0x0b80011e,3 +np.float32,0xc1902cba,0x327fff2f,3 +np.float32,0xc2ca6625,0x00000008,3 +np.float32,0xc280ece8,0x10fffeb5,3 +np.float32,0xc2918f94,0x0b0000ea,3 +np.float32,0xc29b43d5,0x077ffffc,3 +np.float32,0xc1e61ff7,0x2ab504f5,3 +np.float32,0xc2867878,0x0effff15,3 +np.float32,0xc2a2324a,0x04fffff4,3 +#float64 +## near zero ## +np.float64,0x8000000000000000,0x3ff0000000000000,1 +np.float64,0x8010000000000000,0x3ff0000000000000,1 +np.float64,0x8000000000000001,0x3ff0000000000000,1 +np.float64,0x8360000000000000,0x3ff0000000000000,1 +np.float64,0x9a70000000000000,0x3ff0000000000000,1 +np.float64,0xb9b0000000000000,0x3ff0000000000000,1 +np.float64,0xb810000000000000,0x3ff0000000000000,1 +np.float64,0xbc30000000000000,0x3ff0000000000000,1 +np.float64,0xb6a0000000000000,0x3ff0000000000000,1 +np.float64,0x0000000000000000,0x3ff0000000000000,1 +np.float64,0x0010000000000000,0x3ff0000000000000,1 +np.float64,0x0000000000000001,0x3ff0000000000000,1 +np.float64,0x0360000000000000,0x3ff0000000000000,1 +np.float64,0x1a70000000000000,0x3ff0000000000000,1 +np.float64,0x3c30000000000000,0x3ff0000000000000,1 +np.float64,0x36a0000000000000,0x3ff0000000000000,1 +np.float64,0x39b0000000000000,0x3ff0000000000000,1 +np.float64,0x3810000000000000,0x3ff0000000000000,1 +## underflow ## +np.float64,0xc0c6276800000000,0x0000000000000000,1 +np.float64,0xc0c62d918ce2421d,0x0000000000000000,1 +np.float64,0xc0c62d918ce2421e,0x0000000000000000,1 +np.float64,0xc0c62d91a0000000,0x0000000000000000,1 +np.float64,0xc0c62d9180000000,0x0000000000000000,1 +np.float64,0xc0c62dea45ee3e06,0x0000000000000000,1 +np.float64,0xc0c62dea45ee3e07,0x0000000000000000,1 +np.float64,0xc0c62dea40000000,0x0000000000000000,1 +np.float64,0xc0c62dea60000000,0x0000000000000000,1 +np.float64,0xc0875f1120000000,0x0000000000000000,1 +np.float64,0xc0875f113c30b1c8,0x0000000000000000,1 +np.float64,0xc0875f1140000000,0x0000000000000000,1 +np.float64,0xc093480000000000,0x0000000000000000,1 +np.float64,0xffefffffffffffff,0x0000000000000000,1 +np.float64,0xc7efffffe0000000,0x0000000000000000,1 +## overflow ## +np.float64,0x40862e52fefa39ef,0x7ff0000000000000,1 +np.float64,0x40872e42fefa39ef,0x7ff0000000000000,1 +## +/- INF, +/- NAN ## +np.float64,0x7ff0000000000000,0x7ff0000000000000,1 +np.float64,0xfff0000000000000,0x0000000000000000,1 +np.float64,0x7ff8000000000000,0x7ff8000000000000,1 +np.float64,0xfff8000000000000,0xfff8000000000000,1 +## output denormal ## +np.float64,0xc087438520000000,0x0000000000000001,1 +np.float64,0xc08743853f2f4461,0x0000000000000001,1 +np.float64,0xc08743853f2f4460,0x0000000000000001,1 +np.float64,0xc087438540000000,0x0000000000000001,1 +## between -745.13321910 and 709.78271289 ## +np.float64,0xbff760cd14774bd9,0x3fcdb14ced00ceb6,1 +np.float64,0xbff760cd20000000,0x3fcdb14cd7993879,1 +np.float64,0xbff760cd00000000,0x3fcdb14d12fbd264,1 +np.float64,0xc07f1cf360000000,0x130c1b369af14fda,1 +np.float64,0xbeb0000000000000,0x3feffffe00001000,1 +np.float64,0xbd70000000000000,0x3fefffffffffe000,1 +np.float64,0xc084fd46e5c84952,0x0360000000000139,1 +np.float64,0xc084fd46e5c84953,0x035ffffffffffe71,1 +np.float64,0xc084fd46e0000000,0x0360000b9096d32c,1 +np.float64,0xc084fd4700000000,0x035fff9721d12104,1 +np.float64,0xc086232bc0000000,0x0010003af5e64635,1 +np.float64,0xc086232bdd7abcd2,0x001000000000007c,1 +np.float64,0xc086232bdd7abcd3,0x000ffffffffffe7c,1 +np.float64,0xc086232be0000000,0x000ffffaf57a6fc9,1 +np.float64,0xc086233920000000,0x000fe590e3b45eb0,1 +np.float64,0xc086233938000000,0x000fe56133493c57,1 +np.float64,0xc086233940000000,0x000fe5514deffbbc,1 +np.float64,0xc086234c98000000,0x000fbf1024c32ccb,1 +np.float64,0xc086234ca0000000,0x000fbf0065bae78d,1 +np.float64,0xc086234c80000000,0x000fbf3f623a7724,1 +np.float64,0xc086234ec0000000,0x000fbad237c846f9,1 +np.float64,0xc086234ec8000000,0x000fbac27cfdec97,1 +np.float64,0xc086234ee0000000,0x000fba934cfd3dc2,1 +np.float64,0xc086234ef0000000,0x000fba73d7f618d9,1 +np.float64,0xc086234f00000000,0x000fba54632dddc0,1 +np.float64,0xc0862356e0000000,0x000faae0945b761a,1 +np.float64,0xc0862356f0000000,0x000faac13eb9a310,1 +np.float64,0xc086235700000000,0x000faaa1e9567b0a,1 +np.float64,0xc086236020000000,0x000f98cd75c11ed7,1 +np.float64,0xc086236ca0000000,0x000f8081b4d93f89,1 +np.float64,0xc086236cb0000000,0x000f8062b3f4d6c5,1 +np.float64,0xc086236cc0000000,0x000f8043b34e6f8c,1 +np.float64,0xc086238d98000000,0x000f41220d9b0d2c,1 +np.float64,0xc086238da0000000,0x000f4112cc80a01f,1 +np.float64,0xc086238d80000000,0x000f414fd145db5b,1 +np.float64,0xc08624fd00000000,0x000cbfce8ea1e6c4,1 +np.float64,0xc086256080000000,0x000c250747fcd46e,1 +np.float64,0xc08626c480000000,0x000a34f4bd975193,1 +np.float64,0xbf50000000000000,0x3feff800ffeaac00,1 +np.float64,0xbe10000000000000,0x3fefffffff800000,1 +np.float64,0xbcd0000000000000,0x3feffffffffffff8,1 +np.float64,0xc055d589e0000000,0x38100004bf94f63e,1 +np.float64,0xc055d58a00000000,0x380ffff97f292ce8,1 +np.float64,0xbfd962d900000000,0x3fe585a4b00110e1,1 +np.float64,0x3ff4bed280000000,0x400d411e7a58a303,1 +np.float64,0x3fff0b3620000000,0x401bd7737ffffcf3,1 +np.float64,0x3ff0000000000000,0x4005bf0a8b145769,1 +np.float64,0x3eb0000000000000,0x3ff0000100000800,1 +np.float64,0x3d70000000000000,0x3ff0000000001000,1 +np.float64,0x40862e42e0000000,0x7fefff841808287f,1 +np.float64,0x40862e42fefa39ef,0x7fefffffffffff2a,1 +np.float64,0x40862e0000000000,0x7feef85a11e73f2d,1 +np.float64,0x4000000000000000,0x401d8e64b8d4ddae,1 +np.float64,0x4009242920000000,0x40372a52c383a488,1 +np.float64,0x4049000000000000,0x44719103e4080b45,1 +np.float64,0x4008000000000000,0x403415e5bf6fb106,1 +np.float64,0x3f50000000000000,0x3ff00400800aab55,1 +np.float64,0x3e10000000000000,0x3ff0000000400000,1 +np.float64,0x3cd0000000000000,0x3ff0000000000004,1 +np.float64,0x40562e40a0000000,0x47effed088821c3f,1 +np.float64,0x40562e42e0000000,0x47effff082e6c7ff,1 +np.float64,0x40562e4300000000,0x47f00000417184b8,1 +np.float64,0x3fe8000000000000,0x4000ef9db467dcf8,1 +np.float64,0x402b12e8d4f33589,0x412718f68c71a6fe,1 +np.float64,0x402b12e8d4f3358a,0x412718f68c71a70a,1 +np.float64,0x402b12e8c0000000,0x412718f59a7f472e,1 +np.float64,0x402b12e8e0000000,0x412718f70c0eac62,1 +##use 1th entry +np.float64,0x40631659AE147CB4,0x4db3a95025a4890f,1 +np.float64,0xC061B87D2E85A4E2,0x332640c8e2de2c51,1 +np.float64,0x405A4A50BE243AF4,0x496a45e4b7f0339a,1 +np.float64,0xC0839898B98EC5C6,0x0764027828830df4,1 +#use 2th entry +np.float64,0xC072428C44B6537C,0x2596ade838b96f3e,1 +np.float64,0xC053057C5E1AE9BF,0x3912c8fad18fdadf,1 +np.float64,0x407E89C78328BAA3,0x6bfe35d5b9a1a194,1 +np.float64,0x4083501B6DD87112,0x77a855503a38924e,1 +#use 3th entry +np.float64,0x40832C6195F24540,0x7741e73c80e5eb2f,1 +np.float64,0xC083D4CD557C2EC9,0x06b61727c2d2508e,1 +np.float64,0x400C48F5F67C99BD,0x404128820f02b92e,1 +np.float64,0x4056E36D9B2DF26A,0x4830f52ff34a8242,1 +#use 4th entry +np.float64,0x4080FF700D8CBD06,0x70fa70df9bc30f20,1 +np.float64,0x406C276D39E53328,0x543eb8e20a8f4741,1 +np.float64,0xC070D6159BBD8716,0x27a4a0548c904a75,1 +np.float64,0xC052EBCF8ED61F83,0x391c0e92368d15e4,1 +#use 5th entry +np.float64,0xC061F892A8AC5FBE,0x32f807a89efd3869,1 +np.float64,0x4021D885D2DBA085,0x40bd4dc86d3e3270,1 +np.float64,0x40767AEEEE7D4FCF,0x605e22851ee2afb7,1 +np.float64,0xC0757C5D75D08C80,0x20f0751599b992a2,1 +#use 6th entry +np.float64,0x405ACF7A284C4CE3,0x499a4e0b7a27027c,1 +np.float64,0xC085A6C9E80D7AF5,0x0175914009d62ec2,1 +np.float64,0xC07E4C02F86F1DAE,0x1439269b29a9231e,1 +np.float64,0x4080D80F9691CC87,0x7088a6cdafb041de,1 +#use 7th entry +np.float64,0x407FDFD84FBA0AC1,0x6deb1ae6f9bc4767,1 +np.float64,0x40630C06A1A2213D,0x4dac7a9d51a838b7,1 +np.float64,0x40685FDB30BB8B4F,0x5183f5cc2cac9e79,1 +np.float64,0x408045A2208F77F4,0x6ee299e08e2aa2f0,1 +#use 8th entry +np.float64,0xC08104E391F5078B,0x0ed397b7cbfbd230,1 +np.float64,0xC031501CAEFAE395,0x3e6040fd1ea35085,1 +np.float64,0xC079229124F6247C,0x1babf4f923306b1e,1 +np.float64,0x407FB65F44600435,0x6db03beaf2512b8a,1 +#use 9th entry +np.float64,0xC07EDEE8E8E8A5AC,0x136536cec9cbef48,1 +np.float64,0x4072BB4086099A14,0x5af4d3c3008b56cc,1 +np.float64,0x4050442A2EC42CB4,0x45cd393bd8fad357,1 +np.float64,0xC06AC28FB3D419B4,0x2ca1b9d3437df85f,1 +#use 10th entry +np.float64,0x40567FC6F0A68076,0x480c977fd5f3122e,1 +np.float64,0x40620A2F7EDA59BB,0x4cf278e96f4ce4d7,1 +np.float64,0xC085044707CD557C,0x034aad6c968a045a,1 +np.float64,0xC07374EA5AC516AA,0x23dd6afdc03e83d5,1 +#use 11th entry +np.float64,0x4073CC95332619C1,0x5c804b1498bbaa54,1 +np.float64,0xC0799FEBBE257F31,0x1af6a954c43b87d2,1 +np.float64,0x408159F19EA424F6,0x7200858efcbfc84d,1 +np.float64,0x404A81F6F24C0792,0x44b664a07ce5bbfa,1 +#use 12th entry +np.float64,0x40295FF1EFB9A741,0x4113c0e74c52d7b0,1 +np.float64,0x4073975F4CC411DA,0x5c32be40b4fec2c1,1 +np.float64,0x406E9DE52E82A77E,0x56049c9a3f1ae089,1 +np.float64,0x40748C2F52560ED9,0x5d93bc14fd4cd23b,1 +#use 13th entry +np.float64,0x4062A553CDC4D04C,0x4d6266bfde301318,1 +np.float64,0xC079EC1D63598AB7,0x1a88cb184dab224c,1 +np.float64,0xC0725C1CB3167427,0x25725b46f8a081f6,1 +np.float64,0x407888771D9B45F9,0x6353b1ec6bd7ce80,1 +#use 14th entry +np.float64,0xC082CBA03AA89807,0x09b383723831ce56,1 +np.float64,0xC083A8961BB67DD7,0x0735b118d5275552,1 +np.float64,0xC076BC6ECA12E7E3,0x1f2222679eaef615,1 +np.float64,0xC072752503AA1A5B,0x254eb832242c77e1,1 +#use 15th entry +np.float64,0xC058800792125DEC,0x371882372a0b48d4,1 +np.float64,0x4082909FD863E81C,0x7580d5f386920142,1 +np.float64,0xC071616F8FB534F9,0x26dbe20ef64a412b,1 +np.float64,0x406D1AB571CAA747,0x54ee0d55cb38ac20,1 +#use 16th entry +np.float64,0x406956428B7DAD09,0x52358682c271237f,1 +np.float64,0xC07EFC2D9D17B621,0x133b3e77c27a4d45,1 +np.float64,0xC08469BAC5BA3CCA,0x050863e5f42cc52f,1 +np.float64,0x407189D9626386A5,0x593cb1c0b3b5c1d3,1 +#use 17th entry +np.float64,0x4077E652E3DEB8C6,0x6269a10dcbd3c752,1 +np.float64,0x407674C97DB06878,0x605485dcc2426ec2,1 +np.float64,0xC07CE9969CF4268D,0x16386cf8996669f2,1 +np.float64,0x40780EE32D5847C4,0x62a436bd1abe108d,1 +#use 18th entry +np.float64,0x4076C3AA5E1E8DA1,0x60c62f56a5e72e24,1 +np.float64,0xC0730AFC7239B9BE,0x24758ead095cec1e,1 +np.float64,0xC085CC2B9C420DDB,0x0109cdaa2e5694c1,1 +np.float64,0x406D0765CB6D7AA4,0x54e06f8dd91bd945,1 +#use 19th entry +np.float64,0xC082D011F3B495E7,0x09a6647661d279c2,1 +np.float64,0xC072826AF8F6AFBC,0x253acd3cd224507e,1 +np.float64,0x404EB9C4810CEA09,0x457933dbf07e8133,1 +np.float64,0x408284FBC97C58CE,0x755f6eb234aa4b98,1 +#use 20th entry +np.float64,0x40856008CF6EDC63,0x7d9c0b3c03f4f73c,1 +np.float64,0xC077CB2E9F013B17,0x1d9b3d3a166a55db,1 +np.float64,0xC0479CA3C20AD057,0x3bad40e081555b99,1 +np.float64,0x40844CD31107332A,0x7a821d70aea478e2,1 +#use 21th entry +np.float64,0xC07C8FCC0BFCC844,0x16ba1cc8c539d19b,1 +np.float64,0xC085C4E9A3ABA488,0x011ff675ba1a2217,1 +np.float64,0x4074D538B32966E5,0x5dfd9d78043c6ad9,1 +np.float64,0xC0630CA16902AD46,0x3231a446074cede6,1 +#use 22th entry +np.float64,0xC06C826733D7D0B7,0x2b5f1078314d41e1,1 +np.float64,0xC0520DF55B2B907F,0x396c13a6ce8e833e,1 +np.float64,0xC080712072B0F437,0x107eae02d11d98ea,1 +np.float64,0x40528A6150E19EFB,0x469fdabda02228c5,1 +#use 23th entry +np.float64,0xC07B1D74B6586451,0x18d1253883ae3b48,1 +np.float64,0x4045AFD7867DAEC0,0x43d7d634fc4c5d98,1 +np.float64,0xC07A08B91F9ED3E2,0x1a60973e6397fc37,1 +np.float64,0x407B3ECF0AE21C8C,0x673e03e9d98d7235,1 +#use 24th entry +np.float64,0xC078AEB6F30CEABF,0x1c530b93ab54a1b3,1 +np.float64,0x4084495006A41672,0x7a775b6dc7e63064,1 +np.float64,0x40830B1C0EBF95DD,0x76e1e6eed77cfb89,1 +np.float64,0x407D93E8F33D8470,0x6a9adbc9e1e4f1e5,1 +#use 25th entry +np.float64,0x4066B11A09EFD9E8,0x504dd528065c28a7,1 +np.float64,0x408545823723AEEB,0x7d504a9b1844f594,1 +np.float64,0xC068C711F2CA3362,0x2e104f3496ea118e,1 +np.float64,0x407F317FCC3CA873,0x6cf0732c9948ebf4,1 +#use 26th entry +np.float64,0x407AFB3EBA2ED50F,0x66dc28a129c868d5,1 +np.float64,0xC075377037708ADE,0x21531a329f3d793e,1 +np.float64,0xC07C30066A1F3246,0x174448baa16ded2b,1 +np.float64,0xC06689A75DE2ABD3,0x2fad70662fae230b,1 +#use 27th entry +np.float64,0x4081514E9FCCF1E0,0x71e673b9efd15f44,1 +np.float64,0xC0762C710AF68460,0x1ff1ed7d8947fe43,1 +np.float64,0xC0468102FF70D9C4,0x3be0c3a8ff3419a3,1 +np.float64,0xC07EA4CEEF02A83E,0x13b908f085102c61,1 +#use 28th entry +np.float64,0xC06290B04AE823C4,0x328a83da3c2e3351,1 +np.float64,0xC0770EB1D1C395FB,0x1eab281c1f1db5fe,1 +np.float64,0xC06F5D4D838A5BAE,0x29500ea32fb474ea,1 +np.float64,0x40723B3133B54C5D,0x5a3c82c7c3a2b848,1 +#use 29th entry +np.float64,0x4085E6454CE3B4AA,0x7f20319b9638d06a,1 +np.float64,0x408389F2A0585D4B,0x7850667c58aab3d0,1 +np.float64,0xC0382798F9C8AE69,0x3dc1c79fe8739d6d,1 +np.float64,0xC08299D827608418,0x0a4335f76cdbaeb5,1 +#use 30th entry +np.float64,0xC06F3DED43301BF1,0x2965670ae46750a8,1 +np.float64,0xC070CAF6BDD577D9,0x27b4aa4ffdd29981,1 +np.float64,0x4078529AD4B2D9F2,0x6305c12755d5e0a6,1 +np.float64,0xC055B14E75A31B96,0x381c2eda6d111e5d,1 +#use 31th entry +np.float64,0x407B13EE414FA931,0x6700772c7544564d,1 +np.float64,0x407EAFDE9DE3EC54,0x6c346a0e49724a3c,1 +np.float64,0xC08362F398B9530D,0x07ffeddbadf980cb,1 +np.float64,0x407E865CDD9EEB86,0x6bf866cac5e0d126,1 +#use 32th entry +np.float64,0x407FB62DBC794C86,0x6db009f708ac62cb,1 +np.float64,0xC063D0BAA68CDDDE,0x31a3b2a51ce50430,1 +np.float64,0xC05E7706A2231394,0x34f24bead6fab5c9,1 +np.float64,0x4083E3A06FDE444E,0x79527b7a386d1937,1 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-log.csv b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-log.csv new file mode 100644 index 0000000000000000000000000000000000000000..b8f6b08757d5bddbec06328ac2c3f6b694d85fd3 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-log.csv @@ -0,0 +1,271 @@ +dtype,input,output,ulperrortol +## +ve denormals ## +np.float32,0x004b4716,0xc2afbc1b,4 +np.float32,0x007b2490,0xc2aec01e,4 +np.float32,0x007c99fa,0xc2aeba17,4 +np.float32,0x00734a0c,0xc2aee1dc,4 +np.float32,0x0070de24,0xc2aeecba,4 +np.float32,0x007fffff,0xc2aeac50,4 +np.float32,0x00000001,0xc2ce8ed0,4 +## -ve denormals ## +np.float32,0x80495d65,0xffc00000,4 +np.float32,0x806894f6,0xffc00000,4 +np.float32,0x80555a76,0xffc00000,4 +np.float32,0x804e1fb8,0xffc00000,4 +np.float32,0x80687de9,0xffc00000,4 +np.float32,0x807fffff,0xffc00000,4 +np.float32,0x80000001,0xffc00000,4 +## +/-0.0f, +/-FLT_MIN +/-FLT_MAX ## +np.float32,0x00000000,0xff800000,4 +np.float32,0x80000000,0xff800000,4 +np.float32,0x7f7fffff,0x42b17218,4 +np.float32,0x80800000,0xffc00000,4 +np.float32,0xff7fffff,0xffc00000,4 +## 1.00f + 0x00000001 ## +np.float32,0x3f800000,0x00000000,4 +np.float32,0x3f800001,0x33ffffff,4 +np.float32,0x3f800002,0x347ffffe,4 +np.float32,0x3f7fffff,0xb3800000,4 +np.float32,0x3f7ffffe,0xb4000000,4 +np.float32,0x3f7ffffd,0xb4400001,4 +np.float32,0x402df853,0x3f7ffffe,4 +np.float32,0x402df854,0x3f7fffff,4 +np.float32,0x402df855,0x3f800000,4 +np.float32,0x402df856,0x3f800001,4 +np.float32,0x3ebc5ab0,0xbf800001,4 +np.float32,0x3ebc5ab1,0xbf800000,4 +np.float32,0x3ebc5ab2,0xbf800000,4 +np.float32,0x3ebc5ab3,0xbf7ffffe,4 +np.float32,0x423ef575,0x407768ab,4 +np.float32,0x427b8c61,0x408485dd,4 +np.float32,0x4211e9ee,0x406630b0,4 +np.float32,0x424d5c41,0x407c0fed,4 +np.float32,0x42be722a,0x4091cc91,4 +np.float32,0x42b73d30,0x4090908b,4 +np.float32,0x427e48e2,0x4084de7f,4 +np.float32,0x428f759b,0x4088bba3,4 +np.float32,0x41629069,0x4029a0cc,4 +np.float32,0x4272c99d,0x40836379,4 +np.float32,0x4d1b7458,0x4197463d,4 +np.float32,0x4f10c594,0x41ace2b2,4 +np.float32,0x4ea397c2,0x41a85171,4 +np.float32,0x4fefa9d1,0x41b6769c,4 +np.float32,0x4ebac6ab,0x41a960dc,4 +np.float32,0x4f6efb42,0x41b0e535,4 +np.float32,0x4e9ab8e7,0x41a7df44,4 +np.float32,0x4e81b5d1,0x41a67625,4 +np.float32,0x5014d9f2,0x41b832bd,4 +np.float32,0x4f02175c,0x41ac07b8,4 +np.float32,0x7f034f89,0x42b01c47,4 +np.float32,0x7f56d00e,0x42b11849,4 +np.float32,0x7f1cd5f6,0x42b0773a,4 +np.float32,0x7e979174,0x42af02d7,4 +np.float32,0x7f23369f,0x42b08ba2,4 +np.float32,0x7f0637ae,0x42b0277d,4 +np.float32,0x7efcb6e8,0x42b00897,4 +np.float32,0x7f7907c8,0x42b163f6,4 +np.float32,0x7e95c4c2,0x42aefcba,4 +np.float32,0x7f4577b2,0x42b0ed2d,4 +np.float32,0x3f49c92e,0xbe73ae84,4 +np.float32,0x3f4a23d1,0xbe71e2f8,4 +np.float32,0x3f4abb67,0xbe6ee430,4 +np.float32,0x3f48169a,0xbe7c5532,4 +np.float32,0x3f47f5fa,0xbe7cfc37,4 +np.float32,0x3f488309,0xbe7a2ad8,4 +np.float32,0x3f479df4,0xbe7ebf5f,4 +np.float32,0x3f47cfff,0xbe7dbec9,4 +np.float32,0x3f496704,0xbe75a125,4 +np.float32,0x3f478ee8,0xbe7f0c92,4 +np.float32,0x3f4a763b,0xbe7041ce,4 +np.float32,0x3f47a108,0xbe7eaf94,4 +np.float32,0x3f48136c,0xbe7c6578,4 +np.float32,0x3f481c17,0xbe7c391c,4 +np.float32,0x3f47cd28,0xbe7dcd56,4 +np.float32,0x3f478be8,0xbe7f1bf7,4 +np.float32,0x3f4c1f8e,0xbe67e367,4 +np.float32,0x3f489b0c,0xbe79b03f,4 +np.float32,0x3f4934cf,0xbe76a08a,4 +np.float32,0x3f4954df,0xbe75fd6a,4 +np.float32,0x3f47a3f5,0xbe7ea093,4 +np.float32,0x3f4ba4fc,0xbe6a4b02,4 +np.float32,0x3f47a0e1,0xbe7eb05c,4 +np.float32,0x3f48c30a,0xbe78e42f,4 +np.float32,0x3f48cab8,0xbe78bd05,4 +np.float32,0x3f4b0569,0xbe6d6ea4,4 +np.float32,0x3f47de32,0xbe7d7607,4 +np.float32,0x3f477328,0xbe7f9b00,4 +np.float32,0x3f496dab,0xbe757f52,4 +np.float32,0x3f47662c,0xbe7fddac,4 +np.float32,0x3f48ddd8,0xbe785b80,4 +np.float32,0x3f481866,0xbe7c4bff,4 +np.float32,0x3f48b119,0xbe793fb6,4 +np.float32,0x3f48c7e8,0xbe78cb5c,4 +np.float32,0x3f4985f6,0xbe7503da,4 +np.float32,0x3f483fdf,0xbe7b8212,4 +np.float32,0x3f4b1c76,0xbe6cfa67,4 +np.float32,0x3f480b2e,0xbe7c8fa8,4 +np.float32,0x3f48745f,0xbe7a75bf,4 +np.float32,0x3f485bda,0xbe7af308,4 +np.float32,0x3f47a660,0xbe7e942c,4 +np.float32,0x3f47d4d5,0xbe7da600,4 +np.float32,0x3f4b0a26,0xbe6d56be,4 +np.float32,0x3f4a4883,0xbe712924,4 +np.float32,0x3f4769e7,0xbe7fca84,4 +np.float32,0x3f499702,0xbe74ad3f,4 +np.float32,0x3f494ab1,0xbe763131,4 +np.float32,0x3f476b69,0xbe7fc2c6,4 +np.float32,0x3f4884e8,0xbe7a214a,4 +np.float32,0x3f486945,0xbe7aae76,4 +#float64 +## +ve denormal ## +np.float64,0x0000000000000001,0xc0874385446d71c3,1 +np.float64,0x0001000000000000,0xc086395a2079b70c,1 +np.float64,0x000fffffffffffff,0xc086232bdd7abcd2,1 +np.float64,0x0007ad63e2168cb6,0xc086290bc0b2980f,1 +## -ve denormal ## +np.float64,0x8000000000000001,0xfff8000000000001,1 +np.float64,0x8001000000000000,0xfff8000000000001,1 +np.float64,0x800fffffffffffff,0xfff8000000000001,1 +np.float64,0x8007ad63e2168cb6,0xfff8000000000001,1 +## +/-0.0f, MAX, MIN## +np.float64,0x0000000000000000,0xfff0000000000000,1 +np.float64,0x8000000000000000,0xfff0000000000000,1 +np.float64,0x7fefffffffffffff,0x40862e42fefa39ef,1 +np.float64,0xffefffffffffffff,0xfff8000000000001,1 +## near 1.0f ## +np.float64,0x3ff0000000000000,0x0000000000000000,1 +np.float64,0x3fe8000000000000,0xbfd269621134db92,1 +np.float64,0x3ff0000000000001,0x3cafffffffffffff,1 +np.float64,0x3ff0000020000000,0x3e7fffffe000002b,1 +np.float64,0x3ff0000000000001,0x3cafffffffffffff,1 +np.float64,0x3fefffffe0000000,0xbe70000008000005,1 +np.float64,0x3fefffffffffffff,0xbca0000000000000,1 +## random numbers ## +np.float64,0x02500186f3d9da56,0xc0855b8abf135773,1 +np.float64,0x09200815a3951173,0xc082ff1ad7131bdc,1 +np.float64,0x0da029623b0243d4,0xc0816fc994695bb5,1 +np.float64,0x48703b8ac483a382,0x40579213a313490b,1 +np.float64,0x09207b74c87c9860,0xc082fee20ff349ef,1 +np.float64,0x62c077698e8df947,0x407821c996d110f0,1 +np.float64,0x2350b45e87c3cfb0,0xc073d6b16b51d072,1 +np.float64,0x3990a23f9ff2b623,0xc051aa60eadd8c61,1 +np.float64,0x0d011386a116c348,0xc081a6cc7ea3b8fb,1 +np.float64,0x1fe0f0303ebe273a,0xc0763870b78a81ca,1 +np.float64,0x0cd1260121d387da,0xc081b7668d61a9d1,1 +np.float64,0x1e6135a8f581d422,0xc077425ac10f08c2,1 +np.float64,0x622168db5fe52d30,0x4077b3c669b9fadb,1 +np.float64,0x69f188e1ec6d1718,0x407d1e2f18c63889,1 +np.float64,0x3aa1bf1d9c4dd1a3,0xc04d682e24bde479,1 +np.float64,0x6c81c4011ce4f683,0x407ee5190e8a8e6a,1 +np.float64,0x2191fa55aa5a5095,0xc0750c0c318b5e2d,1 +np.float64,0x32a1f602a32bf360,0xc06270caa493fc17,1 +np.float64,0x16023c90ba93249b,0xc07d0f88e0801638,1 +np.float64,0x1c525fe6d71fa9ff,0xc078af49c66a5d63,1 +np.float64,0x1a927675815d65b7,0xc079e5bdd7fe376e,1 +np.float64,0x41227b8fe70da028,0x402aa0c9f9a84c71,1 +np.float64,0x4962bb6e853fe87d,0x405a34aa04c83747,1 +np.float64,0x23d2cda00b26b5a4,0xc0737c13a06d00ea,1 +np.float64,0x2d13083fd62987fa,0xc06a25055aeb474e,1 +np.float64,0x10e31e4c9b4579a1,0xc0804e181929418e,1 +np.float64,0x26d3247d556a86a9,0xc0716774171da7e8,1 +np.float64,0x6603379398d0d4ac,0x407a64f51f8a887b,1 +np.float64,0x02d38af17d9442ba,0xc0852d955ac9dd68,1 +np.float64,0x6a2382b4818dd967,0x407d4129d688e5d4,1 +np.float64,0x2ee3c403c79b3934,0xc067a091fefaf8b6,1 +np.float64,0x6493a699acdbf1a4,0x4079663c8602bfc5,1 +np.float64,0x1c8413c4f0de3100,0xc0788c99697059b6,1 +np.float64,0x4573f1ed350d9622,0x404e9bd1e4c08920,1 +np.float64,0x2f34265c9200b69c,0xc067310cfea4e986,1 +np.float64,0x19b43e65fa22029b,0xc07a7f8877de22d6,1 +np.float64,0x0af48ab7925ed6bc,0xc0825c4fbc0e5ade,1 +np.float64,0x4fa49699cad82542,0x4065c76d2a318235,1 +np.float64,0x7204a15e56ade492,0x40815bb87484dffb,1 +np.float64,0x4734aa08a230982d,0x40542a4bf7a361a9,1 +np.float64,0x1ae4ed296c2fd749,0xc079ac4921f20abb,1 +np.float64,0x472514ea4370289c,0x4053ff372bd8f18f,1 +np.float64,0x53a54b3f73820430,0x406b5411fc5f2e33,1 +np.float64,0x64754de5a15684fa,0x407951592e99a5ab,1 +np.float64,0x69358e279868a7c3,0x407c9c671a882c31,1 +np.float64,0x284579ec61215945,0xc0706688e55f0927,1 +np.float64,0x68b5c58806447adc,0x407c43d6f4eff760,1 +np.float64,0x1945a83f98b0e65d,0xc07acc15eeb032cc,1 +np.float64,0x0fc5eb98a16578bf,0xc080b0d02eddca0e,1 +np.float64,0x6a75e208f5784250,0x407d7a7383bf8f05,1 +np.float64,0x0fe63a029c47645d,0xc080a59ca1e98866,1 +np.float64,0x37963ac53f065510,0xc057236281f7bdb6,1 +np.float64,0x135661bb07067ff7,0xc07ee924930c21e4,1 +np.float64,0x4b4699469d458422,0x405f73843756e887,1 +np.float64,0x1a66d73e4bf4881b,0xc07a039ba1c63adf,1 +np.float64,0x12a6b9b119a7da59,0xc07f62e49c6431f3,1 +np.float64,0x24c719aa8fd1bdb5,0xc072d26da4bf84d3,1 +np.float64,0x0fa6ff524ffef314,0xc080bb8514662e77,1 +np.float64,0x1db751d66fdd4a9a,0xc077b77cb50d7c92,1 +np.float64,0x4947374c516da82c,0x4059e9acfc7105bf,1 +np.float64,0x1b1771ab98f3afc8,0xc07989326b8e1f66,1 +np.float64,0x25e78805baac8070,0xc0720a818e6ef080,1 +np.float64,0x4bd7a148225d3687,0x406082d004ea3ee7,1 +np.float64,0x53d7d6b2bbbda00a,0x406b9a398967cbd5,1 +np.float64,0x6997fb9f4e1c685f,0x407ce0a703413eba,1 +np.float64,0x069802c2ff71b951,0xc083df39bf7acddc,1 +np.float64,0x4d683ac9890f66d8,0x4062ae21d8c2acf0,1 +np.float64,0x5a2825863ec14f4c,0x40722d718d549552,1 +np.float64,0x0398799a88f4db80,0xc084e93dab8e2158,1 +np.float64,0x5ed87a8b77e135a5,0x40756d7051777b33,1 +np.float64,0x5828cd6d79b9bede,0x4070cafb22fc6ca1,1 +np.float64,0x7b18ba2a5ec6f068,0x408481386b3ed6fe,1 +np.float64,0x4938fd60922198fe,0x4059c206b762ea7e,1 +np.float64,0x31b8f44fcdd1a46e,0xc063b2faa8b6434e,1 +np.float64,0x5729341c0d918464,0x407019cac0c4a7d7,1 +np.float64,0x13595e9228ee878e,0xc07ee7235a7d8088,1 +np.float64,0x17698b0dc9dd4135,0xc07c1627e3a5ad5f,1 +np.float64,0x63b977c283abb0cc,0x4078cf1ec6ed65be,1 +np.float64,0x7349cc0d4dc16943,0x4081cc697ce4cb53,1 +np.float64,0x4e49a80b732fb28d,0x4063e67e3c5cbe90,1 +np.float64,0x07ba14b848a8ae02,0xc0837ac032a094e0,1 +np.float64,0x3da9f17b691bfddc,0xc03929c25366acda,1 +np.float64,0x02ea39aa6c3ac007,0xc08525af6f21e1c4,1 +np.float64,0x3a6a42f04ed9563d,0xc04e98e825dca46b,1 +np.float64,0x1afa877cd7900be7,0xc0799d6648cb34a9,1 +np.float64,0x58ea986649e052c6,0x4071512e939ad790,1 +np.float64,0x691abbc04647f536,0x407c89aaae0fcb83,1 +np.float64,0x43aabc5063e6f284,0x4044b45d18106fd2,1 +np.float64,0x488b003c893e0bea,0x4057df012a2dafbe,1 +np.float64,0x77eb076ed67caee5,0x40836720de94769e,1 +np.float64,0x5c1b46974aba46f4,0x40738731ba256007,1 +np.float64,0x1a5b29ecb5d3c261,0xc07a0becc77040d6,1 +np.float64,0x5d8b6ccf868c6032,0x4074865c1865e2db,1 +np.float64,0x4cfb6690b4aaf5af,0x406216cd8c7e8ddb,1 +np.float64,0x76cbd8eb5c5fc39e,0x4083038dc66d682b,1 +np.float64,0x28bbd1fec5012814,0xc07014c2dd1b9711,1 +np.float64,0x33dc1b3a4fd6bf7a,0xc060bd0756e07d8a,1 +np.float64,0x52bbe89b37de99f3,0x406a10041aa7d343,1 +np.float64,0x07bc479d15eb2dd3,0xc0837a1a6e3a3b61,1 +np.float64,0x18fc5275711a901d,0xc07aff3e9d62bc93,1 +np.float64,0x114c9758e247dc71,0xc080299a7cf15b05,1 +np.float64,0x25ac8f6d60755148,0xc07233c4c0c511d4,1 +np.float64,0x260cae2bb9e9fd7e,0xc071f128c7e82eac,1 +np.float64,0x572ccdfe0241de82,0x40701bedc84bb504,1 +np.float64,0x0ddcef6c8d41f5ee,0xc0815a7e16d07084,1 +np.float64,0x6dad1d59c988af68,0x407fb4a0bc0142b1,1 +np.float64,0x025d200580d8b6d1,0xc08556c0bc32b1b2,1 +np.float64,0x7aad344b6aa74c18,0x40845bbc453f22be,1 +np.float64,0x5b5d9d6ad9d14429,0x4073036d2d21f382,1 +np.float64,0x49cd8d8dcdf19954,0x405b5c034f5c7353,1 +np.float64,0x63edb9483335c1e6,0x4078f2dd21378786,1 +np.float64,0x7b1dd64c9d2c26bd,0x408482b922017bc9,1 +np.float64,0x782e13e0b574be5f,0x40837e2a0090a5ad,1 +np.float64,0x592dfe18b9d6db2f,0x40717f777fbcb1ec,1 +np.float64,0x654e3232ac60d72c,0x4079e71a95a70446,1 +np.float64,0x7b8e42ad22091456,0x4084a9a6f1e61722,1 +np.float64,0x570e88dfd5860ae6,0x407006ae6c0d137a,1 +np.float64,0x294e98346cb98ef1,0xc06f5edaac12bd44,1 +np.float64,0x1adeaa4ab792e642,0xc079b1431d5e2633,1 +np.float64,0x7b6ead3377529ac8,0x40849eabc8c7683c,1 +np.float64,0x2b8eedae8a9b2928,0xc06c400054deef11,1 +np.float64,0x65defb45b2dcf660,0x407a4b53f181c05a,1 +np.float64,0x1baf582d475e7701,0xc07920bcad4a502c,1 +np.float64,0x461f39cf05a0f15a,0x405126368f984fa1,1 +np.float64,0x7e5f6f5dcfff005b,0x4085a37d610439b4,1 +np.float64,0x136f66e4d09bd662,0xc07ed8a2719f2511,1 +np.float64,0x65afd8983fb6ca1f,0x407a2a7f48bf7fc1,1 +np.float64,0x572fa7f95ed22319,0x40701d706cf82e6f,1 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-log10.csv b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-log10.csv new file mode 100644 index 0000000000000000000000000000000000000000..c7657773e00f4599180ed9fe9dca413ee7b98ea9 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-log10.csv @@ -0,0 +1,1629 @@ +dtype,input,output,ulperrortol +np.float32,0x3f6fd5c8,0xbce80e8e,4 +np.float32,0x3ea4ab17,0xbefc3deb,4 +np.float32,0x3e87a133,0xbf13b0b7,4 +np.float32,0x3f0d9069,0xbe83bb19,4 +np.float32,0x3f7b9269,0xbbf84f47,4 +np.float32,0x3f7a9ffa,0xbc16fd97,4 +np.float32,0x7f535d34,0x4219cb66,4 +np.float32,0x3e79ad7c,0xbf1ce857,4 +np.float32,0x7e8bfd3b,0x4217dfe9,4 +np.float32,0x3f2d2ee9,0xbe2dcec6,4 +np.float32,0x572e04,0xc21862e4,4 +np.float32,0x7f36f8,0xc217bad5,4 +np.float32,0x3f7982fb,0xbc36aaed,4 +np.float32,0x45b019,0xc218c67c,4 +np.float32,0x3f521c46,0xbdafb3e3,4 +np.float32,0x80000001,0x7fc00000,4 +np.float32,0x3f336c81,0xbe1e107f,4 +np.float32,0x3eac92d7,0xbef1d0bb,4 +np.float32,0x47bdfc,0xc218b990,4 +np.float32,0x7f2d94c8,0x421973d1,4 +np.float32,0x7d53ff8d,0x4214fbb6,4 +np.float32,0x3f581e4e,0xbd96a079,4 +np.float32,0x7ddaf20d,0x42163e4e,4 +np.float32,0x3f341d3c,0xbe1c5b4c,4 +np.float32,0x7ef04ba9,0x4218d032,4 +np.float32,0x620ed2,0xc2182e99,4 +np.float32,0x507850,0xc2188682,4 +np.float32,0x7d08f9,0xc217c284,4 +np.float32,0x7f0cf2aa,0x42191734,4 +np.float32,0x3f109a17,0xbe7e04fe,4 +np.float32,0x7f426152,0x4219a625,4 +np.float32,0x7f32d5a3,0x42198113,4 +np.float32,0x2e14b2,0xc2197e6f,4 +np.float32,0x3a5acd,0xc219156a,4 +np.float32,0x50a565,0xc2188589,4 +np.float32,0x5b751c,0xc2184d97,4 +np.float32,0x7e4149f6,0x42173b22,4 +np.float32,0x3dc34bf9,0xbf82a42a,4 +np.float32,0x3d12bc28,0xbfb910d6,4 +np.float32,0x7ebd2584,0x421865c1,4 +np.float32,0x7f6b3375,0x4219faeb,4 +np.float32,0x7fa00000,0x7fe00000,4 +np.float32,0x3f35fe7d,0xbe17bd33,4 +np.float32,0x7db45c87,0x4215e818,4 +np.float32,0x3efff366,0xbe9a2b8d,4 +np.float32,0x3eb331d0,0xbee971a3,4 +np.float32,0x3f259d5f,0xbe41ae2e,4 +np.float32,0x3eab85ec,0xbef32c4a,4 +np.float32,0x7f194b8a,0x42193c8c,4 +np.float32,0x3f11a614,0xbe7acfc7,4 +np.float32,0x5b17,0xc221f16b,4 +np.float32,0x3f33dadc,0xbe1cff4d,4 +np.float32,0x3cda1506,0xbfc9920f,4 +np.float32,0x3f6856f1,0xbd2c8290,4 +np.float32,0x7f3357fb,0x42198257,4 +np.float32,0x7f56f329,0x4219d2e1,4 +np.float32,0x3ef84108,0xbea0f595,4 +np.float32,0x3f72340f,0xbcc51916,4 +np.float32,0x3daf28,0xc218fcbd,4 +np.float32,0x131035,0xc21b06f4,4 +np.float32,0x3f275c3b,0xbe3d0487,4 +np.float32,0x3ef06130,0xbea82069,4 +np.float32,0x3f57f3b0,0xbd974fef,4 +np.float32,0x7f6c4a78,0x4219fcfa,4 +np.float32,0x7e8421d0,0x4217c639,4 +np.float32,0x3f17a479,0xbe68e08e,4 +np.float32,0x7f03774e,0x4218f83b,4 +np.float32,0x441a33,0xc218d0b8,4 +np.float32,0x539158,0xc21875b6,4 +np.float32,0x3e8fcc75,0xbf0d3018,4 +np.float32,0x7ef74130,0x4218dce4,4 +np.float32,0x3ea6f4fa,0xbef92c38,4 +np.float32,0x7f3948ab,0x421990d5,4 +np.float32,0x7db6f8f5,0x4215ee7c,4 +np.float32,0x3ee44a2f,0xbeb399e5,4 +np.float32,0x156c59,0xc21ad30d,4 +np.float32,0x3f21ee53,0xbe4baf16,4 +np.float32,0x3f2c08f4,0xbe30c424,4 +np.float32,0x3f49885c,0xbdd4c6a9,4 +np.float32,0x3eae0b9c,0xbeefed54,4 +np.float32,0x1b5c1f,0xc21a6646,4 +np.float32,0x3e7330e2,0xbf1fd592,4 +np.float32,0x3ebbeb4c,0xbededf82,4 +np.float32,0x427154,0xc218dbb1,4 +np.float32,0x3f6b8b4b,0xbd142498,4 +np.float32,0x8e769,0xc21c5981,4 +np.float32,0x3e9db557,0xbf02ec1c,4 +np.float32,0x3f001bef,0xbe99f019,4 +np.float32,0x3e58b48c,0xbf2ca77a,4 +np.float32,0x3d46c16b,0xbfa8327c,4 +np.float32,0x7eeeb305,0x4218cd3b,4 +np.float32,0x3e3f163d,0xbf3aa446,4 +np.float32,0x3f66c872,0xbd3877d9,4 +np.float32,0x7f7162f8,0x421a0677,4 +np.float32,0x3edca3bc,0xbebb2e28,4 +np.float32,0x3dc1055b,0xbf834afa,4 +np.float32,0x12b16f,0xc21b0fad,4 +np.float32,0x3f733898,0xbcb62e16,4 +np.float32,0x3e617af8,0xbf283db0,4 +np.float32,0x7e86577a,0x4217cd99,4 +np.float32,0x3f0ba3c7,0xbe86c633,4 +np.float32,0x3f4cad25,0xbdc70247,4 +np.float32,0xb6cdf,0xc21bea9f,4 +np.float32,0x3f42971a,0xbdf3f49e,4 +np.float32,0x3e6ccad2,0xbf22cc78,4 +np.float32,0x7f2121b2,0x421952b8,4 +np.float32,0x3f6d3f55,0xbd075366,4 +np.float32,0x3f524f,0xc218f117,4 +np.float32,0x3e95b5d9,0xbf08b56a,4 +np.float32,0x7f6ae47d,0x4219fa56,4 +np.float32,0x267539,0xc219ceda,4 +np.float32,0x3ef72f6d,0xbea1eb2e,4 +np.float32,0x2100b2,0xc21a12e2,4 +np.float32,0x3d9777d1,0xbf90c4e7,4 +np.float32,0x44c6f5,0xc218cc56,4 +np.float32,0x7f2a613d,0x42196b8a,4 +np.float32,0x390a25,0xc2191f8d,4 +np.float32,0x3f1de5ad,0xbe56e703,4 +np.float32,0x2f59ce,0xc2197258,4 +np.float32,0x7f3b12a1,0x4219951b,4 +np.float32,0x3ecb66d4,0xbecd44ca,4 +np.float32,0x7e74ff,0xc217bd7d,4 +np.float32,0x7ed83f78,0x4218a14d,4 +np.float32,0x685994,0xc21812f1,4 +np.float32,0xbf800000,0x7fc00000,4 +np.float32,0x736f47,0xc217e60b,4 +np.float32,0x7f09c371,0x42190d0a,4 +np.float32,0x3f7ca51d,0xbbbbbce0,4 +np.float32,0x7f4b4d3b,0x4219ba1a,4 +np.float32,0x3f6c4471,0xbd0eb076,4 +np.float32,0xd944e,0xc21b9dcf,4 +np.float32,0x7cb06ffc,0x421375cd,4 +np.float32,0x586187,0xc2185cce,4 +np.float32,0x3f3cbf5b,0xbe078911,4 +np.float32,0x3f30b504,0xbe24d983,4 +np.float32,0x3f0a16ba,0xbe8941fd,4 +np.float32,0x5c43b0,0xc21849af,4 +np.float32,0x3dad74f6,0xbf893bd5,4 +np.float32,0x3c586958,0xbff087a6,4 +np.float32,0x3e8307a8,0xbf1786ba,4 +np.float32,0x7dcd1776,0x4216213d,4 +np.float32,0x3f44d107,0xbde9d662,4 +np.float32,0x3e2e6823,0xbf44cbec,4 +np.float32,0x3d87ea27,0xbf96caca,4 +np.float32,0x3e0c715b,0xbf5ce07e,4 +np.float32,0x7ec9cd5a,0x4218828e,4 +np.float32,0x3e26c0b4,0xbf49c93e,4 +np.float32,0x75b94e,0xc217dd50,4 +np.float32,0x3df7b9f5,0xbf6ad7f4,4 +np.float32,0x0,0xff800000,4 +np.float32,0x3f284795,0xbe3a94da,4 +np.float32,0x7ee49092,0x4218b9f0,4 +np.float32,0x7f4c20e0,0x4219bbe8,4 +np.float32,0x3efbbce8,0xbe9ddc4b,4 +np.float32,0x12274a,0xc21b1cb4,4 +np.float32,0x5fa1b1,0xc21839be,4 +np.float32,0x7f0b210e,0x4219116d,4 +np.float32,0x3f67092a,0xbd368545,4 +np.float32,0x3d572721,0xbfa3ca5b,4 +np.float32,0x3f7913ce,0xbc431028,4 +np.float32,0x3b0613,0xc2191059,4 +np.float32,0x3e1d16c0,0xbf506c6f,4 +np.float32,0xab130,0xc21c081d,4 +np.float32,0x3e23ac97,0xbf4bdb9d,4 +np.float32,0x7ef52368,0x4218d911,4 +np.float32,0x7f38e686,0x42198fe9,4 +np.float32,0x3f106a21,0xbe7e9897,4 +np.float32,0x3ecef8d5,0xbec96644,4 +np.float32,0x3ec37e02,0xbed61683,4 +np.float32,0x3efbd063,0xbe9dcb17,4 +np.float32,0x3f318fe3,0xbe22b402,4 +np.float32,0x7e5e5228,0x4217795d,4 +np.float32,0x72a046,0xc217e92c,4 +np.float32,0x7f6f970b,0x421a0324,4 +np.float32,0x3ed871b4,0xbebf72fb,4 +np.float32,0x7a2eaa,0xc217ccc8,4 +np.float32,0x3e819655,0xbf18c1d7,4 +np.float32,0x80800000,0x7fc00000,4 +np.float32,0x7eab0719,0x421838f9,4 +np.float32,0x7f0763cb,0x4219054f,4 +np.float32,0x3f191672,0xbe64a8af,4 +np.float32,0x7d4327,0xc217c1b6,4 +np.float32,0x3f724ba6,0xbcc3bea3,4 +np.float32,0x60fe06,0xc2183375,4 +np.float32,0x48cd59,0xc218b30b,4 +np.float32,0x3f7fec2b,0xb909d3f3,4 +np.float32,0x1c7bb9,0xc21a5460,4 +np.float32,0x24d8a8,0xc219e1e4,4 +np.float32,0x3e727c52,0xbf20283c,4 +np.float32,0x4bc460,0xc218a14a,4 +np.float32,0x63e313,0xc2182661,4 +np.float32,0x7f625581,0x4219e9d4,4 +np.float32,0x3eeb3e77,0xbeacedc0,4 +np.float32,0x7ef27a47,0x4218d437,4 +np.float32,0x27105a,0xc219c7e6,4 +np.float32,0x22a10b,0xc219fd7d,4 +np.float32,0x3f41e907,0xbdf711ab,4 +np.float32,0x7c1fbf95,0x4212155b,4 +np.float32,0x7e5acceb,0x42177244,4 +np.float32,0x3e0892fa,0xbf5ffb83,4 +np.float32,0x3ea0e51d,0xbf00b2c0,4 +np.float32,0x3e56fc29,0xbf2d8a51,4 +np.float32,0x7ee724ed,0x4218beed,4 +np.float32,0x7ebf142b,0x42186a46,4 +np.float32,0x7f6cf35c,0x4219fe37,4 +np.float32,0x3f11abf7,0xbe7abdcd,4 +np.float32,0x588d7a,0xc2185bf1,4 +np.float32,0x3f6e81d2,0xbcfbcf97,4 +np.float32,0x3f1b6be8,0xbe5dee2b,4 +np.float32,0x7f3815e0,0x42198df2,4 +np.float32,0x3f5bfc88,0xbd86d93d,4 +np.float32,0x3f3775d0,0xbe142bbc,4 +np.float32,0x78a958,0xc217d25a,4 +np.float32,0x2ff7c3,0xc2196c96,4 +np.float32,0x4b9c0,0xc21d733c,4 +np.float32,0x3ec025af,0xbed9ecf3,4 +np.float32,0x6443f0,0xc21824b3,4 +np.float32,0x3f754e28,0xbc97d299,4 +np.float32,0x3eaa91d3,0xbef4699d,4 +np.float32,0x3e5f2837,0xbf296478,4 +np.float32,0xe5676,0xc21b85a4,4 +np.float32,0x3f6859f2,0xbd2c6b90,4 +np.float32,0x3f68686b,0xbd2bfcc6,4 +np.float32,0x4b39b8,0xc218a47b,4 +np.float32,0x630ac4,0xc2182a28,4 +np.float32,0x160980,0xc21ac67d,4 +np.float32,0x3ed91c4d,0xbebec3fd,4 +np.float32,0x7ec27b0d,0x4218721f,4 +np.float32,0x3f3c0a5f,0xbe09344b,4 +np.float32,0x3dbff9c1,0xbf839841,4 +np.float32,0x7f0e8ea7,0x42191c40,4 +np.float32,0x3f36b162,0xbe1608e4,4 +np.float32,0x228bb3,0xc219fe90,4 +np.float32,0x2fdd30,0xc2196d8c,4 +np.float32,0x3e8fce8e,0xbf0d2e79,4 +np.float32,0x3f36acc7,0xbe16141a,4 +np.float32,0x7f44b51c,0x4219ab70,4 +np.float32,0x3ec3371c,0xbed66736,4 +np.float32,0x4388a2,0xc218d473,4 +np.float32,0x3f5aa6c3,0xbd8c4344,4 +np.float32,0x7f09fce4,0x42190dc3,4 +np.float32,0x7ed7854a,0x42189fce,4 +np.float32,0x7f4da83a,0x4219bf3a,4 +np.float32,0x3db8da28,0xbf85b25a,4 +np.float32,0x7f449686,0x4219ab2b,4 +np.float32,0x2eb25,0xc21e498c,4 +np.float32,0x3f2bcc08,0xbe3161bd,4 +np.float32,0x36c923,0xc219317b,4 +np.float32,0x3d52a866,0xbfa4f6d2,4 +np.float32,0x3f7d6688,0xbb913e4e,4 +np.float32,0x3f5a6ba4,0xbd8d33e3,4 +np.float32,0x719740,0xc217ed35,4 +np.float32,0x78a472,0xc217d26c,4 +np.float32,0x7ee33d0c,0x4218b759,4 +np.float32,0x7f668c1d,0x4219f208,4 +np.float32,0x3e29c600,0xbf47ca46,4 +np.float32,0x3f3cefc3,0xbe071712,4 +np.float32,0x3e224ebd,0xbf4cca41,4 +np.float32,0x7f1417be,0x42192d31,4 +np.float32,0x7f29d7d5,0x42196a23,4 +np.float32,0x3338ce,0xc2194f65,4 +np.float32,0x2a7897,0xc219a2b6,4 +np.float32,0x3d6bc3d8,0xbf9eb468,4 +np.float32,0x3f6bd7bf,0xbd11e392,4 +np.float32,0x7f6d26bf,0x4219fe98,4 +np.float32,0x3f52d378,0xbdacadb5,4 +np.float32,0x3efac453,0xbe9eb84a,4 +np.float32,0x3f692eb7,0xbd261184,4 +np.float32,0x3f6a0bb5,0xbd1f7ec1,4 +np.float32,0x3f037a49,0xbe942aa8,4 +np.float32,0x3f465bd4,0xbde2e530,4 +np.float32,0x7ef0f47b,0x4218d16a,4 +np.float32,0x637127,0xc218285e,4 +np.float32,0x3f41e511,0xbdf723d7,4 +np.float32,0x7f800000,0x7f800000,4 +np.float32,0x3f3342d5,0xbe1e77d5,4 +np.float32,0x7f57cfe6,0x4219d4a9,4 +np.float32,0x3e4358ed,0xbf3830a7,4 +np.float32,0x3ce25f15,0xbfc77f2b,4 +np.float32,0x7ed057e7,0x421890be,4 +np.float32,0x7ce154d9,0x4213e295,4 +np.float32,0x3ee91984,0xbeaef703,4 +np.float32,0x7e4e919c,0x421758af,4 +np.float32,0x6830e7,0xc218139e,4 +np.float32,0x3f12f08e,0xbe76e328,4 +np.float32,0x7f0a7a32,0x42190f56,4 +np.float32,0x7f38e,0xc21c8bd3,4 +np.float32,0x3e01def9,0xbf6593e3,4 +np.float32,0x3f5c8c6d,0xbd849432,4 +np.float32,0x3eed8747,0xbeaac7a3,4 +np.float32,0x3cadaa0e,0xbfd63b21,4 +np.float32,0x3f7532a9,0xbc996178,4 +np.float32,0x31f3ac,0xc2195a8f,4 +np.float32,0x3f0e0f97,0xbe82f3af,4 +np.float32,0x3f2a1f35,0xbe35bd3f,4 +np.float32,0x3f4547b2,0xbde7bebd,4 +np.float32,0x3f7988a6,0xbc36094c,4 +np.float32,0x74464c,0xc217e2d2,4 +np.float32,0x7f7518be,0x421a0d3f,4 +np.float32,0x7e97fa0a,0x42180473,4 +np.float32,0x584e3a,0xc2185d2f,4 +np.float32,0x3e7291f3,0xbf201e52,4 +np.float32,0xc0a05,0xc21bd359,4 +np.float32,0x3a3177,0xc21916a6,4 +np.float32,0x4f417f,0xc2188d45,4 +np.float32,0x263fce,0xc219d145,4 +np.float32,0x7e1d58,0xc217beb1,4 +np.float32,0x7f056af3,0x4218fec9,4 +np.float32,0x3f21c181,0xbe4c2a3f,4 +np.float32,0x7eca4956,0x4218839f,4 +np.float32,0x3e58afa8,0xbf2ca9fd,4 +np.float32,0x3f40d583,0xbdfc04ef,4 +np.float32,0x7f432fbb,0x4219a7fc,4 +np.float32,0x43aaa4,0xc218d393,4 +np.float32,0x7f2c9b62,0x42197150,4 +np.float32,0x5c3876,0xc21849e5,4 +np.float32,0x7f2034e8,0x42195029,4 +np.float32,0x7e5be772,0x42177481,4 +np.float32,0x80000000,0xff800000,4 +np.float32,0x3f5be03b,0xbd874bb0,4 +np.float32,0x3e32494f,0xbf4259be,4 +np.float32,0x3e1f4671,0xbf4ee30b,4 +np.float32,0x4606cc,0xc218c454,4 +np.float32,0x425cbc,0xc218dc3b,4 +np.float32,0x7dd9b8bf,0x42163bd0,4 +np.float32,0x3f0465d0,0xbe929db7,4 +np.float32,0x3f735077,0xbcb4d0fa,4 +np.float32,0x4d6a43,0xc21897b8,4 +np.float32,0x3e27d600,0xbf4910f5,4 +np.float32,0x3f06e0cc,0xbe8e7d24,4 +np.float32,0x3f3fd064,0xbe005e45,4 +np.float32,0x176f1,0xc21f7c2d,4 +np.float32,0x3eb64e6f,0xbee59d9c,4 +np.float32,0x7f0f075d,0x42191db8,4 +np.float32,0x3f718cbe,0xbcceb621,4 +np.float32,0x3ead7bda,0xbef0a54a,4 +np.float32,0x7f77c1a8,0x421a120c,4 +np.float32,0x3f6a79c5,0xbd1c3afd,4 +np.float32,0x3e992d1f,0xbf062a02,4 +np.float32,0x3e6f6335,0xbf219639,4 +np.float32,0x7f6d9a3e,0x4219ff70,4 +np.float32,0x557ed1,0xc2186b91,4 +np.float32,0x3f13a456,0xbe74c457,4 +np.float32,0x15c2dc,0xc21acc17,4 +np.float32,0x71f36f,0xc217ebcc,4 +np.float32,0x748dea,0xc217e1c1,4 +np.float32,0x7f0f32e0,0x42191e3f,4 +np.float32,0x5b1da8,0xc2184f41,4 +np.float32,0x3d865d3a,0xbf976e11,4 +np.float32,0x3f800000,0x0,4 +np.float32,0x7f67b56d,0x4219f444,4 +np.float32,0x6266a1,0xc2182d0c,4 +np.float32,0x3ec9c5e4,0xbecf0e6b,4 +np.float32,0x6a6a0e,0xc2180a3b,4 +np.float32,0x7e9db6fd,0x421814ef,4 +np.float32,0x3e7458f7,0xbf1f4e88,4 +np.float32,0x3ead8016,0xbef09fdc,4 +np.float32,0x3e263d1c,0xbf4a211e,4 +np.float32,0x7f6b3329,0x4219faeb,4 +np.float32,0x800000,0xc217b818,4 +np.float32,0x3f0654c7,0xbe8f6471,4 +np.float32,0x3f281b71,0xbe3b0990,4 +np.float32,0x7c4c8e,0xc217c524,4 +np.float32,0x7d113a87,0x4214537d,4 +np.float32,0x734b5f,0xc217e696,4 +np.float32,0x7f079d05,0x4219060b,4 +np.float32,0x3ee830b1,0xbeafd58b,4 +np.float32,0x3f1c3b8b,0xbe5b9d96,4 +np.float32,0x3f2bf0c6,0xbe3102aa,4 +np.float32,0x7ddffe22,0x42164871,4 +np.float32,0x3f1e58b4,0xbe55a37f,4 +np.float32,0x5f3edf,0xc2183b8a,4 +np.float32,0x7f1fb6ec,0x42194eca,4 +np.float32,0x3f78718e,0xbc55311e,4 +np.float32,0x3e574b7d,0xbf2d6152,4 +np.float32,0x7eab27c6,0x4218394e,4 +np.float32,0x7f34603c,0x421984e5,4 +np.float32,0x3f3a8b57,0xbe0cc1ca,4 +np.float32,0x3f744181,0xbca7134e,4 +np.float32,0x3f7e3bc4,0xbb45156b,4 +np.float32,0x93ab4,0xc21c498b,4 +np.float32,0x7ed5541e,0x42189b42,4 +np.float32,0x6bf8ec,0xc21803c4,4 +np.float32,0x757395,0xc217de58,4 +np.float32,0x7f177214,0x42193726,4 +np.float32,0x59935f,0xc21856d6,4 +np.float32,0x2cd9ba,0xc2198a78,4 +np.float32,0x3ef6fd5c,0xbea2183c,4 +np.float32,0x3ebb6c63,0xbedf75e0,4 +np.float32,0x7f43272c,0x4219a7e9,4 +np.float32,0x7f42e67d,0x4219a755,4 +np.float32,0x3f3f744f,0xbe0133f6,4 +np.float32,0x7f5fddaa,0x4219e4f4,4 +np.float32,0x3dc9874f,0xbf80e529,4 +np.float32,0x3f2efe64,0xbe292ec8,4 +np.float32,0x3e0406a6,0xbf63bf7c,4 +np.float32,0x3cdbb0aa,0xbfc92984,4 +np.float32,0x3e6597e7,0xbf263b30,4 +np.float32,0x3f0c1153,0xbe861807,4 +np.float32,0x7fce16,0xc217b8c6,4 +np.float32,0x3f5f4e5f,0xbd730dc6,4 +np.float32,0x3ed41ffa,0xbec3ee69,4 +np.float32,0x3f216c78,0xbe4d1446,4 +np.float32,0x3f123ed7,0xbe78fe4b,4 +np.float32,0x7f7e0ca9,0x421a1d34,4 +np.float32,0x7e318af4,0x42171558,4 +np.float32,0x7f1e1659,0x42194a3d,4 +np.float32,0x34d12a,0xc21941c2,4 +np.float32,0x3d9566ad,0xbf918870,4 +np.float32,0x3e799a47,0xbf1cf0e5,4 +np.float32,0x3e89dd6f,0xbf11df76,4 +np.float32,0x32f0d3,0xc21951d8,4 +np.float32,0x7e89d17e,0x4217d8f6,4 +np.float32,0x1f3b38,0xc21a2b6b,4 +np.float32,0x7ee9e060,0x4218c427,4 +np.float32,0x31a673,0xc2195d41,4 +np.float32,0x5180f1,0xc21880d5,4 +np.float32,0x3cd36f,0xc21902f8,4 +np.float32,0x3bb63004,0xc01050cb,4 +np.float32,0x3e8ee9d1,0xbf0ddfde,4 +np.float32,0x3d2a7da3,0xbfb0b970,4 +np.float32,0x3ea58107,0xbefb1dc3,4 +np.float32,0x7f6760b0,0x4219f3a2,4 +np.float32,0x7f7f9e08,0x421a1ff0,4 +np.float32,0x37e7f1,0xc219287b,4 +np.float32,0x3ef7eb53,0xbea14267,4 +np.float32,0x3e2eb581,0xbf449aa5,4 +np.float32,0x3da7671c,0xbf8b3568,4 +np.float32,0x7af36f7b,0x420f33ee,4 +np.float32,0x3eb3602c,0xbee93823,4 +np.float32,0x3f68bcff,0xbd2975de,4 +np.float32,0x3ea7cefb,0xbef80a9d,4 +np.float32,0x3f329689,0xbe202414,4 +np.float32,0x7f0c7c80,0x421915be,4 +np.float32,0x7f4739b8,0x4219b118,4 +np.float32,0x73af58,0xc217e515,4 +np.float32,0x7f13eb2a,0x42192cab,4 +np.float32,0x30f2d9,0xc2196395,4 +np.float32,0x7ea7066c,0x42182e71,4 +np.float32,0x669fec,0xc2181a5b,4 +np.float32,0x3f7d6876,0xbb90d1ef,4 +np.float32,0x3f08a4ef,0xbe8b9897,4 +np.float32,0x7f2a906c,0x42196c05,4 +np.float32,0x3ed3ca42,0xbec44856,4 +np.float32,0x9d27,0xc220fee2,4 +np.float32,0x3e4508a1,0xbf373c03,4 +np.float32,0x3e41f8de,0xbf38f9bb,4 +np.float32,0x3e912714,0xbf0c255b,4 +np.float32,0xff800000,0x7fc00000,4 +np.float32,0x7eefd13d,0x4218cf4f,4 +np.float32,0x3f491674,0xbdd6bded,4 +np.float32,0x3ef49512,0xbea445c9,4 +np.float32,0x3f045b79,0xbe92af15,4 +np.float32,0x3ef6c412,0xbea24bd5,4 +np.float32,0x3e6f3c28,0xbf21a85d,4 +np.float32,0x3ef71839,0xbea2000e,4 +np.float32,0x1,0xc23369f4,4 +np.float32,0x3e3fcfe4,0xbf3a3876,4 +np.float32,0x3e9d7a65,0xbf0315b2,4 +np.float32,0x20b7c4,0xc21a16bd,4 +np.float32,0x7f707b10,0x421a04cb,4 +np.float32,0x7fc00000,0x7fc00000,4 +np.float32,0x3f285ebd,0xbe3a57ac,4 +np.float32,0x74c9ea,0xc217e0dc,4 +np.float32,0x3f6501f2,0xbd4634ab,4 +np.float32,0x3f248959,0xbe4495cc,4 +np.float32,0x7e915ff0,0x4217f0b3,4 +np.float32,0x7edbb910,0x4218a864,4 +np.float32,0x3f7042dd,0xbce1bddb,4 +np.float32,0x6f08c9,0xc217f754,4 +np.float32,0x7f423993,0x4219a5ca,4 +np.float32,0x3f125704,0xbe78b4cd,4 +np.float32,0x7ef7f5ae,0x4218de28,4 +np.float32,0x3f2dd940,0xbe2c1a33,4 +np.float32,0x3f1ca78e,0xbe5a6a8b,4 +np.float32,0x244863,0xc219e8be,4 +np.float32,0x3f2614fe,0xbe406d6b,4 +np.float32,0x3e75e7a3,0xbf1e99b5,4 +np.float32,0x2bdd6e,0xc2199459,4 +np.float32,0x7e49e279,0x42174e7b,4 +np.float32,0x3e3bb09a,0xbf3ca2cd,4 +np.float32,0x649f06,0xc2182320,4 +np.float32,0x7f4a44e1,0x4219b7d6,4 +np.float32,0x400473,0xc218ec3a,4 +np.float32,0x3edb19ad,0xbebcbcad,4 +np.float32,0x3d8ee956,0xbf94006c,4 +np.float32,0x7e91c603,0x4217f1eb,4 +np.float32,0x221384,0xc21a04a6,4 +np.float32,0x7f7dd660,0x421a1cd5,4 +np.float32,0x7ef34609,0x4218d5ac,4 +np.float32,0x7f5ed529,0x4219e2e5,4 +np.float32,0x7f1bf685,0x42194438,4 +np.float32,0x3cdd094a,0xbfc8d294,4 +np.float32,0x7e87fc8e,0x4217d303,4 +np.float32,0x7f53d971,0x4219cc6b,4 +np.float32,0xabc8b,0xc21c0646,4 +np.float32,0x7f5011e6,0x4219c46a,4 +np.float32,0x7e460638,0x421745e5,4 +np.float32,0xa8126,0xc21c0ffd,4 +np.float32,0x3eec2a66,0xbeac0f2d,4 +np.float32,0x3f3a1213,0xbe0de340,4 +np.float32,0x7f5908db,0x4219d72c,4 +np.float32,0x7e0ad3c5,0x4216a7f3,4 +np.float32,0x3f2de40e,0xbe2bfe90,4 +np.float32,0x3d0463c5,0xbfbec8e4,4 +np.float32,0x7c7cde0b,0x4212e19a,4 +np.float32,0x74c24f,0xc217e0f9,4 +np.float32,0x3f14b4cb,0xbe71929b,4 +np.float32,0x3e94e192,0xbf09537f,4 +np.float32,0x3eebde71,0xbeac56bd,4 +np.float32,0x3f65e413,0xbd3f5b8a,4 +np.float32,0x7e109199,0x4216b9f9,4 +np.float32,0x3f22f5d0,0xbe48ddc0,4 +np.float32,0x3e22d3bc,0xbf4c6f4d,4 +np.float32,0x3f7a812f,0xbc1a680b,4 +np.float32,0x3f67f361,0xbd2f7d7c,4 +np.float32,0x3f1caa63,0xbe5a6281,4 +np.float32,0x3f306fde,0xbe2587ab,4 +np.float32,0x3e8df9d3,0xbf0e9b2f,4 +np.float32,0x3eaaccc4,0xbef41cd4,4 +np.float32,0x7f3f65ec,0x42199f45,4 +np.float32,0x3dc706e0,0xbf8196ec,4 +np.float32,0x3e14eaba,0xbf565cf6,4 +np.float32,0xcc60,0xc2208a09,4 +np.float32,0x358447,0xc2193be7,4 +np.float32,0x3dcecade,0xbf7eec70,4 +np.float32,0x3f20b4f8,0xbe4f0ef0,4 +np.float32,0x7e7c979f,0x4217b222,4 +np.float32,0x7f2387b9,0x4219594a,4 +np.float32,0x3f6f6e5c,0xbcee0e05,4 +np.float32,0x7f19ad81,0x42193da8,4 +np.float32,0x5635e1,0xc21867dd,4 +np.float32,0x4c5e97,0xc2189dc4,4 +np.float32,0x7f35f97f,0x421988d1,4 +np.float32,0x7f685224,0x4219f571,4 +np.float32,0x3eca0616,0xbecec7b8,4 +np.float32,0x3f436d0d,0xbdf024ca,4 +np.float32,0x12a97d,0xc21b106a,4 +np.float32,0x7f0fdc93,0x4219204d,4 +np.float32,0x3debfb42,0xbf703e65,4 +np.float32,0x3c6c54d2,0xbfeba291,4 +np.float32,0x7e5d7491,0x421777a1,4 +np.float32,0x3f4bd2f0,0xbdcab87d,4 +np.float32,0x3f7517f4,0xbc9ae510,4 +np.float32,0x3f71a59a,0xbccd480d,4 +np.float32,0x3f514653,0xbdb33f61,4 +np.float32,0x3f4e6ea4,0xbdbf694b,4 +np.float32,0x3eadadec,0xbef06526,4 +np.float32,0x3f3b41c1,0xbe0b0fbf,4 +np.float32,0xc35a,0xc2209e1e,4 +np.float32,0x384982,0xc2192575,4 +np.float32,0x3464c3,0xc2194556,4 +np.float32,0x7f5e20d9,0x4219e17d,4 +np.float32,0x3ea18b62,0xbf004016,4 +np.float32,0x63a02b,0xc218278c,4 +np.float32,0x7ef547ba,0x4218d953,4 +np.float32,0x3f2496fb,0xbe4470f4,4 +np.float32,0x7ea0c8c6,0x42181d81,4 +np.float32,0x3f42ba60,0xbdf35372,4 +np.float32,0x7e40d9,0xc217be34,4 +np.float32,0x3e95883b,0xbf08d750,4 +np.float32,0x3e0cddf3,0xbf5c8aa8,4 +np.float32,0x3f2305d5,0xbe48b20a,4 +np.float32,0x7f0d0941,0x4219177b,4 +np.float32,0x3f7b98d3,0xbbf6e477,4 +np.float32,0x3f687cdc,0xbd2b6057,4 +np.float32,0x3f42ce91,0xbdf2f73d,4 +np.float32,0x3ee00fc0,0xbeb7c217,4 +np.float32,0x7f3d483a,0x42199a53,4 +np.float32,0x3e1e08eb,0xbf4fc18d,4 +np.float32,0x7e202ff5,0x4216e798,4 +np.float32,0x582898,0xc2185ded,4 +np.float32,0x3e3552b1,0xbf40790c,4 +np.float32,0x3d3f7c87,0xbfaa44b6,4 +np.float32,0x669d8e,0xc2181a65,4 +np.float32,0x3f0e21b4,0xbe82d757,4 +np.float32,0x686f95,0xc2181293,4 +np.float32,0x3f48367f,0xbdda9ead,4 +np.float32,0x3dc27802,0xbf82e0a0,4 +np.float32,0x3f6ac40c,0xbd1a07d4,4 +np.float32,0x3bba6d,0xc2190b12,4 +np.float32,0x3ec7b6b0,0xbed15665,4 +np.float32,0x3f1f9ca4,0xbe521955,4 +np.float32,0x3ef2f147,0xbea5c4b8,4 +np.float32,0x7c65f769,0x4212b762,4 +np.float32,0x7e98e162,0x42180716,4 +np.float32,0x3f0f0c09,0xbe8169ea,4 +np.float32,0x3d67f03b,0xbf9f9d48,4 +np.float32,0x7f3751e4,0x42198c18,4 +np.float32,0x7f1fac61,0x42194ead,4 +np.float32,0x3e9b698b,0xbf048d89,4 +np.float32,0x7e66507b,0x42178913,4 +np.float32,0x7f5cb680,0x4219dea5,4 +np.float32,0x234700,0xc219f53e,4 +np.float32,0x3d9984ad,0xbf900591,4 +np.float32,0x3f33a3f2,0xbe1d872a,4 +np.float32,0x3eaf52b6,0xbeee4cf4,4 +np.float32,0x7f078930,0x421905ca,4 +np.float32,0x3f083b39,0xbe8c44df,4 +np.float32,0x3e3823f8,0xbf3ec231,4 +np.float32,0x3eef6f5d,0xbea9008c,4 +np.float32,0x6145e1,0xc218322c,4 +np.float32,0x16d9ae,0xc21ab65f,4 +np.float32,0x7e543376,0x421764a5,4 +np.float32,0x3ef77ccb,0xbea1a5a0,4 +np.float32,0x3f4a443f,0xbdd18af5,4 +np.float32,0x8f209,0xc21c5770,4 +np.float32,0x3ecac126,0xbecdfa33,4 +np.float32,0x3e8662f9,0xbf14b6c7,4 +np.float32,0x23759a,0xc219f2f4,4 +np.float32,0xf256d,0xc21b6d3f,4 +np.float32,0x3f579f93,0xbd98aaa2,4 +np.float32,0x3ed4cc8e,0xbec339cb,4 +np.float32,0x3ed25400,0xbec5d2a1,4 +np.float32,0x3ed6f8ba,0xbec0f795,4 +np.float32,0x7f36efd9,0x42198b2a,4 +np.float32,0x7f5169dd,0x4219c746,4 +np.float32,0x7de18a20,0x42164b80,4 +np.float32,0x3e8de526,0xbf0eab61,4 +np.float32,0x3de0cbcd,0xbf75a47e,4 +np.float32,0xe265f,0xc21b8b82,4 +np.float32,0x3df3cdbd,0xbf6c9e40,4 +np.float32,0x3f38a25a,0xbe115589,4 +np.float32,0x7f01f2c0,0x4218f311,4 +np.float32,0x3da7d5f4,0xbf8b10a5,4 +np.float32,0x4d4fe8,0xc2189850,4 +np.float32,0x3cc96d9d,0xbfcdfc8d,4 +np.float32,0x259a88,0xc219d8d7,4 +np.float32,0x7f1d5102,0x42194810,4 +np.float32,0x7e17ca91,0x4216cfa7,4 +np.float32,0x3f73d110,0xbcad7a8f,4 +np.float32,0x3f009383,0xbe9920ed,4 +np.float32,0x7e22af,0xc217be9f,4 +np.float32,0x3f7de2ce,0xbb6c0394,4 +np.float32,0x3edd0cd2,0xbebac45a,4 +np.float32,0x3ec9b5c1,0xbecf2035,4 +np.float32,0x3168c5,0xc2195f6b,4 +np.float32,0x3e935522,0xbf0a7d18,4 +np.float32,0x3e494077,0xbf34e120,4 +np.float32,0x3f52ed06,0xbdac41ec,4 +np.float32,0x3f73d51e,0xbcad3f65,4 +np.float32,0x3f03d453,0xbe939295,4 +np.float32,0x7ef4ee68,0x4218d8b1,4 +np.float32,0x3ed0e2,0xc218f4a7,4 +np.float32,0x4efab8,0xc2188ed3,4 +np.float32,0x3dbd5632,0xbf845d3b,4 +np.float32,0x7eecad4f,0x4218c972,4 +np.float32,0x9d636,0xc21c2d32,4 +np.float32,0x3e5f3b6b,0xbf295ae7,4 +np.float32,0x7f4932df,0x4219b57a,4 +np.float32,0x4b59b5,0xc218a3be,4 +np.float32,0x3e5de97f,0xbf2a03b4,4 +np.float32,0x3f1c479d,0xbe5b7b3c,4 +np.float32,0x3f42e7e4,0xbdf283a5,4 +np.float32,0x2445,0xc2238af2,4 +np.float32,0x7aa71b43,0x420e8c9e,4 +np.float32,0x3ede6e4e,0xbeb961e1,4 +np.float32,0x7f05dd3b,0x42190045,4 +np.float32,0x3ef5b55c,0xbea3404b,4 +np.float32,0x7f738624,0x421a0a62,4 +np.float32,0x3e7d50a1,0xbf1b4cb4,4 +np.float32,0x3f44cc4a,0xbde9ebcc,4 +np.float32,0x7e1a7b0b,0x4216d777,4 +np.float32,0x3f1d9868,0xbe57c0da,4 +np.float32,0x1ebee2,0xc21a3263,4 +np.float32,0x31685f,0xc2195f6e,4 +np.float32,0x368a8e,0xc2193379,4 +np.float32,0xa9847,0xc21c0c2e,4 +np.float32,0x3bd3b3,0xc2190a56,4 +np.float32,0x3961e4,0xc2191ce3,4 +np.float32,0x7e13a243,0x4216c34e,4 +np.float32,0x7f7b1790,0x421a17ff,4 +np.float32,0x3e55f020,0xbf2e1545,4 +np.float32,0x3f513861,0xbdb37aa8,4 +np.float32,0x3dd9e754,0xbf791ad2,4 +np.float32,0x5e8d86,0xc2183ec9,4 +np.float32,0x26b796,0xc219cbdd,4 +np.float32,0x429daa,0xc218da89,4 +np.float32,0x3f477caa,0xbdddd9ba,4 +np.float32,0x3f0e5114,0xbe828d45,4 +np.float32,0x3f54f362,0xbda3c286,4 +np.float32,0x6eac1c,0xc217f8c8,4 +np.float32,0x3f04c479,0xbe91fef5,4 +np.float32,0x3e993765,0xbf06228e,4 +np.float32,0x3eafd99f,0xbeeda21b,4 +np.float32,0x3f2a759e,0xbe34db96,4 +np.float32,0x3f05adfb,0xbe907937,4 +np.float32,0x3f6e2dfc,0xbd005980,4 +np.float32,0x3f2f2daa,0xbe28b6b5,4 +np.float32,0x15e746,0xc21ac931,4 +np.float32,0x7d34ca26,0x4214b4e5,4 +np.float32,0x7ebd175c,0x4218659f,4 +np.float32,0x7f1ed26b,0x42194c4c,4 +np.float32,0x2588b,0xc21eaab0,4 +np.float32,0x3f0065e3,0xbe996fe2,4 +np.float32,0x3f610376,0xbd658122,4 +np.float32,0x451995,0xc218ca41,4 +np.float32,0x70e083,0xc217f002,4 +np.float32,0x7e19821a,0x4216d4a8,4 +np.float32,0x3e7cd9a0,0xbf1b80fb,4 +np.float32,0x7f1a8f18,0x42194033,4 +np.float32,0x3f008fee,0xbe99271f,4 +np.float32,0xff7fffff,0x7fc00000,4 +np.float32,0x7f31d826,0x42197e9b,4 +np.float32,0x3f18cf12,0xbe657838,4 +np.float32,0x3e5c1bc7,0xbf2aebf9,4 +np.float32,0x3e3d3993,0xbf3bbaf8,4 +np.float32,0x68457a,0xc2181347,4 +np.float32,0x7ddf7561,0x42164761,4 +np.float32,0x7f47341b,0x4219b10c,4 +np.float32,0x4d3ecd,0xc21898b2,4 +np.float32,0x7f43dee8,0x4219a98b,4 +np.float32,0x3f0def7c,0xbe8325f5,4 +np.float32,0x3d5a551f,0xbfa2f994,4 +np.float32,0x7ed26602,0x4218951b,4 +np.float32,0x3ee7fa5b,0xbeb0099a,4 +np.float32,0x7ef74ea8,0x4218dcfc,4 +np.float32,0x6a3bb2,0xc2180afd,4 +np.float32,0x7f4c1e6e,0x4219bbe3,4 +np.float32,0x3e26f625,0xbf49a5a2,4 +np.float32,0xb8482,0xc21be70b,4 +np.float32,0x3f32f077,0xbe1f445b,4 +np.float32,0x7dd694b6,0x4216355a,4 +np.float32,0x7f3d62fd,0x42199a92,4 +np.float32,0x3f48e41a,0xbdd79cbf,4 +np.float32,0x338fc3,0xc2194c75,4 +np.float32,0x3e8355f0,0xbf174462,4 +np.float32,0x7f487e83,0x4219b3eb,4 +np.float32,0x2227f7,0xc21a039b,4 +np.float32,0x7e4383dd,0x4217403a,4 +np.float32,0x52d28b,0xc21879b2,4 +np.float32,0x12472c,0xc21b19a9,4 +np.float32,0x353530,0xc2193e7b,4 +np.float32,0x3f4e4728,0xbdc0137a,4 +np.float32,0x3bf169,0xc2190979,4 +np.float32,0x3eb3ee2e,0xbee8885f,4 +np.float32,0x3f03e3c0,0xbe937892,4 +np.float32,0x3c9f8408,0xbfdaf47f,4 +np.float32,0x40e792,0xc218e61b,4 +np.float32,0x5a6b29,0xc21852ab,4 +np.float32,0x7f268b83,0x4219616a,4 +np.float32,0x3ee25997,0xbeb57fa7,4 +np.float32,0x3f175324,0xbe69cf53,4 +np.float32,0x3f781d91,0xbc5e9827,4 +np.float32,0x7dba5210,0x4215f68c,4 +np.float32,0x7f1e66,0xc217bb2b,4 +np.float32,0x7f7fffff,0x421a209b,4 +np.float32,0x3f646202,0xbd4b10b8,4 +np.float32,0x575248,0xc218622b,4 +np.float32,0x7c67faa1,0x4212bb42,4 +np.float32,0x7f1683f2,0x42193469,4 +np.float32,0x1a3864,0xc21a7931,4 +np.float32,0x7f30ad75,0x42197bae,4 +np.float32,0x7f1c9d05,0x42194612,4 +np.float32,0x3e791795,0xbf1d2b2c,4 +np.float32,0x7e9ebc19,0x421817cd,4 +np.float32,0x4999b7,0xc218ae31,4 +np.float32,0x3d130e2c,0xbfb8f1cc,4 +np.float32,0x3f7e436f,0xbb41bb07,4 +np.float32,0x3ee00241,0xbeb7cf7d,4 +np.float32,0x7e496181,0x42174d5f,4 +np.float32,0x7efe58be,0x4218e978,4 +np.float32,0x3f5e5b0c,0xbd7aa43f,4 +np.float32,0x7ee4c6ab,0x4218ba59,4 +np.float32,0x3f6da8c6,0xbd043d7e,4 +np.float32,0x3e3e6e0f,0xbf3b064b,4 +np.float32,0x3f0143b3,0xbe97f10a,4 +np.float32,0x79170f,0xc217d0c6,4 +np.float32,0x517645,0xc218810f,4 +np.float32,0x3f1f9960,0xbe52226e,4 +np.float32,0x2a8df9,0xc219a1d6,4 +np.float32,0x2300a6,0xc219f8b8,4 +np.float32,0x3ee31355,0xbeb4c97a,4 +np.float32,0x3f20b05f,0xbe4f1ba9,4 +np.float32,0x3ee64249,0xbeb1b0ff,4 +np.float32,0x3a94b7,0xc21913b2,4 +np.float32,0x7ef7ef43,0x4218de1d,4 +np.float32,0x3f1abb5d,0xbe5fe872,4 +np.float32,0x7f65360b,0x4219ef72,4 +np.float32,0x3d315d,0xc219004c,4 +np.float32,0x3f26bbc4,0xbe3eafb9,4 +np.float32,0x3ee8c6e9,0xbeaf45de,4 +np.float32,0x7e5f1452,0x42177ae1,4 +np.float32,0x3f32e777,0xbe1f5aba,4 +np.float32,0x4d39a1,0xc21898d0,4 +np.float32,0x3e59ad15,0xbf2c2841,4 +np.float32,0x3f4be746,0xbdca5fc4,4 +np.float32,0x72e4fd,0xc217e821,4 +np.float32,0x1af0b8,0xc21a6d25,4 +np.float32,0x3f311147,0xbe23f18d,4 +np.float32,0x3f1ecebb,0xbe545880,4 +np.float32,0x7e90d293,0x4217ef02,4 +np.float32,0x3e3b366a,0xbf3ceb46,4 +np.float32,0x3f133239,0xbe761c96,4 +np.float32,0x7541ab,0xc217df15,4 +np.float32,0x3d8c8275,0xbf94f1a1,4 +np.float32,0x483b92,0xc218b689,4 +np.float32,0x3eb0dbed,0xbeec5c6b,4 +np.float32,0x3f00c676,0xbe98c8e2,4 +np.float32,0x3f445ac2,0xbdebed7c,4 +np.float32,0x3d2af4,0xc219007a,4 +np.float32,0x7f196ee1,0x42193cf2,4 +np.float32,0x290c94,0xc219b1db,4 +np.float32,0x3f5dbdc9,0xbd7f9019,4 +np.float32,0x3e80c62e,0xbf1974fc,4 +np.float32,0x3ec9ed2c,0xbecee326,4 +np.float32,0x7f469d60,0x4219afbb,4 +np.float32,0x3f698413,0xbd2386ce,4 +np.float32,0x42163f,0xc218de14,4 +np.float32,0x67a554,0xc21815f4,4 +np.float32,0x3f4bff74,0xbdc9f651,4 +np.float32,0x16a743,0xc21aba39,4 +np.float32,0x2eb8b0,0xc219784b,4 +np.float32,0x3eed9be1,0xbeaab45b,4 +np.float64,0x7fe0d76873e1aed0,0x40733f9d783bad7a,1 +np.float64,0x3fe22626bb244c4d,0xbfcf86a59864eea2,1 +np.float64,0x7f874113d02e8227,0x407324f54c4015b8,1 +np.float64,0x3fe40a46a9e8148d,0xbfca0411f533fcb9,1 +np.float64,0x3fd03932eea07266,0xbfe312bc9cf5649e,1 +np.float64,0x7fee5d2a1b3cba53,0x407343b5f56367a0,1 +np.float64,0x3feb7bda4a76f7b5,0xbfb0ea2c6edc784a,1 +np.float64,0x3fd6cd831a2d9b06,0xbfdcaf2e1a5faf51,1 +np.float64,0x98324e273064a,0xc0733e0e4c6d11c6,1 +np.float64,0x7fe1dd63b363bac6,0x4073400667c405c3,1 +np.float64,0x3fec5971f178b2e4,0xbfaaef32a7d94563,1 +np.float64,0x17abc07e2f579,0xc0734afca4da721e,1 +np.float64,0x3feec6ab5cfd8d57,0xbf9157f3545a8235,1 +np.float64,0x3fe3ae9622a75d2c,0xbfcb04b5ad254581,1 +np.float64,0x7fea73d854b4e7b0,0x407342c0a548f4c5,1 +np.float64,0x7fe29babf4653757,0x4073404eeb5fe714,1 +np.float64,0x7fd3a55d85a74aba,0x40733bde72e86c27,1 +np.float64,0x3fe83ce305f079c6,0xbfbee3511e85e0f1,1 +np.float64,0x3fd72087ea2e4110,0xbfdc4ab30802d7c2,1 +np.float64,0x7feb54ddab76a9ba,0x407342facb6f3ede,1 +np.float64,0xc57e34a18afd,0xc0734f82ec815baa,1 +np.float64,0x7a8cb97ef5198,0xc0733f8fb3777a67,1 +np.float64,0x7fe801032c300205,0x40734213dbe4eda9,1 +np.float64,0x3aefb1f475df7,0xc07344a5f08a0584,1 +np.float64,0x7fee85f1dd3d0be3,0x407343bf4441c2a7,1 +np.float64,0x3fdc7f1055b8fe21,0xbfd67d300630e893,1 +np.float64,0xe8ecddb3d1d9c,0xc0733b194f18f466,1 +np.float64,0x3fdf2b23c73e5648,0xbfd3ff6872c1f887,1 +np.float64,0x3fdba4aef2b7495e,0xbfd7557205e18b7b,1 +np.float64,0x3fe2ac34c6e5586a,0xbfcdf1dac69bfa08,1 +np.float64,0x3fc9852628330a4c,0xbfe66914f0fb9b0a,1 +np.float64,0x7fda211acf344235,0x40733dd9c2177aeb,1 +np.float64,0x3fe9420eb432841d,0xbfba4dd969a32575,1 +np.float64,0xb2f9d1ed65f3a,0xc0733cedfb6527ff,1 +np.float64,0x3fe9768a68f2ed15,0xbfb967c39c35c435,1 +np.float64,0x7fe8268462b04d08,0x4073421eaed32734,1 +np.float64,0x3fcf331f063e663e,0xbfe39e2f4b427ca9,1 +np.float64,0x7fd4eb9e2b29d73b,0x40733c4e4141418d,1 +np.float64,0x7fd2bba658a5774c,0x40733b89cd53d5b1,1 +np.float64,0x3fdfdf04913fbe09,0xbfd360c7fd9d251b,1 +np.float64,0x3fca5bfd0534b7fa,0xbfe5f5f844b2b20c,1 +np.float64,0x3feacd5032f59aa0,0xbfb3b5234ba8bf7b,1 +np.float64,0x7fe9241cec724839,0x4073426631362cec,1 +np.float64,0x3fe57aca20eaf594,0xbfc628e3ac2c6387,1 +np.float64,0x3fec6553ca38caa8,0xbfaa921368d3b222,1 +np.float64,0x3fe1e9676563d2cf,0xbfd020f866ba9b24,1 +np.float64,0x3fd5590667aab20d,0xbfde8458af5a4fd6,1 +np.float64,0x3fdf7528f43eea52,0xbfd3bdb438d6ba5e,1 +np.float64,0xb8dddc5571bbc,0xc0733cb4601e5bb2,1 +np.float64,0xe6d4e1fbcda9c,0xc0733b295ef4a4ba,1 +np.float64,0x3fe7019d962e033b,0xbfc257c0a6e8de16,1 +np.float64,0x3f94ef585029deb1,0xbffb07e5dfb0e936,1 +np.float64,0x7fc863b08030c760,0x4073388e28d7b354,1 +np.float64,0xf684443bed089,0xc0733ab46cfbff9a,1 +np.float64,0x7fe00e901d201d1f,0x40733f489c05a0f0,1 +np.float64,0x9e5c0a273cb82,0xc0733dc7af797e19,1 +np.float64,0x7fe49734f0692e69,0x4073410303680df0,1 +np.float64,0x7fb7b584442f6b08,0x4073338acff72502,1 +np.float64,0x3f99984c30333098,0xbff9a2642a6ed8cc,1 +np.float64,0x7fea2fcda8745f9a,0x407342aeae7f5e64,1 +np.float64,0xe580caadcb01a,0xc0733b33a3639217,1 +np.float64,0x1899ab3831336,0xc0734ab823729417,1 +np.float64,0x39bd4c76737aa,0xc07344ca6fac6d21,1 +np.float64,0xd755b2dbaeab7,0xc0733ba4fe19f2cc,1 +np.float64,0x3f952bebf82a57d8,0xbffaf3e7749c2512,1 +np.float64,0x3fe62ee5d72c5dcc,0xbfc45e3cb5baad08,1 +np.float64,0xb1264a7d624ca,0xc0733d003a1d0a66,1 +np.float64,0x3fc4bd1bcd297a38,0xbfe94b3058345c46,1 +np.float64,0x7fc5758bb32aeb16,0x407337aa7805497f,1 +np.float64,0x3fb0edcaf421db96,0xbff2dfb09c405294,1 +np.float64,0x3fd240fceaa481fa,0xbfe16f356bb36134,1 +np.float64,0x38c0c62a7181a,0xc07344e916d1e9b7,1 +np.float64,0x3fe98f2b3bf31e56,0xbfb8fc6eb622a820,1 +np.float64,0x3fe2bdf99c257bf3,0xbfcdbd0dbbae4d0b,1 +np.float64,0xce4b390d9c967,0xc0733bf14ada3134,1 +np.float64,0x3fd2ad607ba55ac1,0xbfe11da15167b37b,1 +np.float64,0x3fd8154f11b02a9e,0xbfdb2a6fabb9a026,1 +np.float64,0xf37849fde6f09,0xc0733aca8c64344c,1 +np.float64,0x3fcbae43b2375c87,0xbfe547f267c8e570,1 +np.float64,0x3fcd46fd7d3a8dfb,0xbfe48070f7232929,1 +np.float64,0x7fcdd245273ba489,0x407339f3d907b101,1 +np.float64,0x3fac75cd0838eb9a,0xbff4149d177b057b,1 +np.float64,0x7fe8ff3fd7f1fe7f,0x4073425bf968ba6f,1 +np.float64,0x7febadaa4df75b54,0x407343113a91f0e9,1 +np.float64,0x7fd5e4649c2bc8c8,0x40733c9f0620b065,1 +np.float64,0x903429812069,0xc07351b255e27887,1 +np.float64,0x3fe1d8c51c63b18a,0xbfd03ad448c1f1ee,1 +np.float64,0x3fe573ea646ae7d5,0xbfc63ab0bfd0e601,1 +np.float64,0x3f83b3f3c02767e8,0xc00022677e310649,1 +np.float64,0x7fd15d1582a2ba2a,0x40733b02c469c1d6,1 +np.float64,0x3fe63d3dabec7a7b,0xbfc43a56ee97b27e,1 +np.float64,0x7fe3a452fb2748a5,0x407340af1973c228,1 +np.float64,0x3fafac6b303f58d6,0xbff35651703ae9f2,1 +np.float64,0x513ddd24a27bc,0xc073426af96aaebb,1 +np.float64,0x3fef152246be2a45,0xbf89df79d7719282,1 +np.float64,0x3fe8c923e9f19248,0xbfbc67228e8db5f6,1 +np.float64,0x3fd6e2325fadc465,0xbfdc9602fb0b950f,1 +np.float64,0x3fe9616815f2c2d0,0xbfb9c4311a3b415b,1 +np.float64,0x2fe4e4005fc9d,0xc0734616fe294395,1 +np.float64,0x3fbceb02dc39d606,0xbfee4e68f1c7886f,1 +np.float64,0x7fe35e843d66bd07,0x407340963b066ad6,1 +np.float64,0x7fecd6c648f9ad8c,0x4073435a4c176e94,1 +np.float64,0x7fcbd72bf437ae57,0x4073397994b85665,1 +np.float64,0x3feff6443b3fec88,0xbf40eb380d5318ae,1 +np.float64,0x7fb9373cf6326e79,0x407333f869edef08,1 +np.float64,0x63790d9cc6f22,0xc0734102d4793cda,1 +np.float64,0x3f9de6efe83bcde0,0xbff88db6f0a6b56e,1 +np.float64,0xe00f2dc1c01f,0xc0734ea26ab84ff2,1 +np.float64,0xd7a9aa8baf536,0xc0733ba248fa33ab,1 +np.float64,0x3fee0089ea7c0114,0xbf9cab936ac31c4b,1 +np.float64,0x3fdec0d51cbd81aa,0xbfd45ed8878c5860,1 +np.float64,0x7fe91bf5e9f237eb,0x40734263f005081d,1 +np.float64,0x34ea7d1e69d50,0xc07345659dde7444,1 +np.float64,0x7fe67321a3ace642,0x4073419cc8130d95,1 +np.float64,0x9d1aeb2f3a35e,0xc0733dd5d506425c,1 +np.float64,0x7fbb01df003603bd,0x4073347282f1391d,1 +np.float64,0x42b945b285729,0xc07343c92d1bbef9,1 +np.float64,0x7fc92799b8324f32,0x407338c51e3f0733,1 +np.float64,0x3fe119c19b223383,0xbfd16ab707f65686,1 +np.float64,0x3fc9f9ac5333f359,0xbfe62a2f91ec0dff,1 +np.float64,0x3fd820d5a8b041ab,0xbfdb1d2586fe7b18,1 +np.float64,0x10000000000000,0xc0733a7146f72a42,1 +np.float64,0x3fe7e1543eafc2a8,0xbfc045362889592d,1 +np.float64,0xcbc0e1819783,0xc0734f4b68e05b1c,1 +np.float64,0xeb57e411d6afd,0xc0733b06efec001a,1 +np.float64,0xa9b74b47536ea,0xc0733d4c7bd06ddc,1 +np.float64,0x3fe56d4022eada80,0xbfc64bf8c7e3dd59,1 +np.float64,0x3fd445ca27288b94,0xbfdff40aecd0f882,1 +np.float64,0x3fe5af1cf5ab5e3a,0xbfc5a21d83699a04,1 +np.float64,0x7fed3431eb7a6863,0x40734370aa6131e1,1 +np.float64,0x3fd878dea1b0f1bd,0xbfdab8730dc00517,1 +np.float64,0x7ff8000000000000,0x7ff8000000000000,1 +np.float64,0x3feba9fcc1f753fa,0xbfb03027dcecbf65,1 +np.float64,0x7fca4feed6349fdd,0x4073391526327eb0,1 +np.float64,0x3fe7748ddbaee91c,0xbfc144b438218065,1 +np.float64,0x3fb5fbd94c2bf7b3,0xbff10ee6342c21a0,1 +np.float64,0x3feb603b97f6c077,0xbfb15a1f99d6d25e,1 +np.float64,0x3fe2e6fc8ce5cdf9,0xbfcd43edd7f3b4e6,1 +np.float64,0x7feb2b31f7765663,0x407342f02b306688,1 +np.float64,0x3fe290e2282521c4,0xbfce436deb8dbcf3,1 +np.float64,0x3fe3d5adf9e7ab5c,0xbfca96b8aa55d942,1 +np.float64,0x691899f2d2314,0xc07340a1026897c8,1 +np.float64,0x7fe468b008e8d15f,0x407340f33eadc628,1 +np.float64,0x3fb3a4c416274988,0xbff1d71da539a56e,1 +np.float64,0x3fe2442b29e48856,0xbfcf2b0037322661,1 +np.float64,0x3f376fbc7e6ef,0xc073442939a84643,1 +np.float64,0x3fe7c78d65ef8f1b,0xbfc08157cff411de,1 +np.float64,0xd4f27acba9e50,0xc0733bb8d38daa50,1 +np.float64,0x5198919ea3313,0xc07342633ba7cbea,1 +np.float64,0x7fd09f66f0a13ecd,0x40733ab5310b4385,1 +np.float64,0x3fdfe5531dbfcaa6,0xbfd35b487c7e739f,1 +np.float64,0x3fc4b0fecc2961fe,0xbfe95350c38c1640,1 +np.float64,0x7fd5ae21962b5c42,0x40733c8db78b7250,1 +np.float64,0x3fa4a8fcd42951fa,0xbff64e62fe602b72,1 +np.float64,0x7fc8e0e25831c1c4,0x407338b179b91223,1 +np.float64,0x7fdde1df6f3bc3be,0x40733ec87f9f027e,1 +np.float64,0x3fd8b9ad86b1735b,0xbfda6f385532c41b,1 +np.float64,0x3fd9f20ee933e41e,0xbfd91872fd858597,1 +np.float64,0x7feb35332df66a65,0x407342f2b9c715f0,1 +np.float64,0x7fe783dc7eaf07b8,0x407341ef41873706,1 +np.float64,0x7fceee929f3ddd24,0x40733a34e3c660fd,1 +np.float64,0x985b58d730b6b,0xc0733e0c6cfbb6f8,1 +np.float64,0x3fef4bb55cfe976b,0xbf83cb246c6f2a78,1 +np.float64,0x3fe218014f243003,0xbfcfb20ac683e1f6,1 +np.float64,0x7fe43b9fbea8773e,0x407340e3d5d5d29e,1 +np.float64,0x7fe148c74c62918e,0x40733fcba4367b8b,1 +np.float64,0x3feea4ad083d495a,0xbf93443917f3c991,1 +np.float64,0x8bcf6311179ed,0xc0733ea54d59dd31,1 +np.float64,0xf4b7a2dbe96f5,0xc0733ac175182401,1 +np.float64,0x543338baa8668,0xc073422b59165fe4,1 +np.float64,0x3fdb467317368ce6,0xbfd7b4d515929635,1 +np.float64,0x7fe3bbbc89e77778,0x407340b75cdf3de7,1 +np.float64,0x7fe693377aad266e,0x407341a6af60a0f1,1 +np.float64,0x3fc66210502cc421,0xbfe83bb940610a24,1 +np.float64,0x7fa75638982eac70,0x40732e9da476b816,1 +np.float64,0x3fe0d72a4761ae55,0xbfd1d7c82c479fab,1 +np.float64,0x97dec0dd2fbd8,0xc0733e121e072804,1 +np.float64,0x3fef33ec8c7e67d9,0xbf86701be6be8df1,1 +np.float64,0x7fcfca9b423f9536,0x40733a65a51efb94,1 +np.float64,0x9f2215633e443,0xc0733dbf043de9ed,1 +np.float64,0x2469373e48d28,0xc07347fe9e904b77,1 +np.float64,0x7fecc2e18cb985c2,0x407343557f58dfa2,1 +np.float64,0x3fde4acbfdbc9598,0xbfd4ca559e575e74,1 +np.float64,0x3fd6b11cf1ad623a,0xbfdcd1e17ef36114,1 +np.float64,0x3fc19ec494233d89,0xbfeb8ef228e8826a,1 +np.float64,0x4c89ee389913e,0xc07342d50c904f61,1 +np.float64,0x88c2046f11841,0xc0733ecc91369431,1 +np.float64,0x7fc88c13fd311827,0x40733899a125b392,1 +np.float64,0x3fcebd893a3d7b12,0xbfe3d2f35ab93765,1 +np.float64,0x3feb582a1476b054,0xbfb17ae8ec6a0465,1 +np.float64,0x7fd4369e5da86d3c,0x40733c1118b8cd67,1 +np.float64,0x3fda013fc1340280,0xbfd90831b85e98b2,1 +np.float64,0x7fed33d73fba67ad,0x4073437094ce1bd9,1 +np.float64,0x3fed3191053a6322,0xbfa468cc26a8f685,1 +np.float64,0x3fc04ed51c209daa,0xbfeca24a6f093bca,1 +np.float64,0x3fee4ac8763c9591,0xbf986458abbb90b5,1 +np.float64,0xa2d39dd145a74,0xc0733d9633651fbc,1 +np.float64,0x3fe7d9f86f2fb3f1,0xbfc0565a0b059f1c,1 +np.float64,0x3fe3250144e64a03,0xbfcc8eb2b9ae494b,1 +np.float64,0x7fe2b29507a56529,0x4073405774492075,1 +np.float64,0x7fdcdfcbe2b9bf97,0x40733e8b736b1bd8,1 +np.float64,0x3fc832730f3064e6,0xbfe7267ac9b2e7c3,1 +np.float64,0x3fc7e912e52fd226,0xbfe750dfc0aeae57,1 +np.float64,0x7fc960472f32c08d,0x407338d4b4cb3957,1 +np.float64,0x3fbdf182ea3be306,0xbfedd27150283ffb,1 +np.float64,0x3fd1e9359823d26b,0xbfe1b2ac7fd25f8d,1 +np.float64,0x7fbcf75f6039eebe,0x407334ef13eb16f8,1 +np.float64,0x3fe5a3c910eb4792,0xbfc5bf2f57c5d643,1 +np.float64,0x3fcf4f2a6e3e9e55,0xbfe391b6f065c4b8,1 +np.float64,0x3fee067873fc0cf1,0xbf9c53af0373fc0e,1 +np.float64,0xd3f08b85a7e12,0xc0733bc14357e686,1 +np.float64,0x7ff0000000000000,0x7ff0000000000000,1 +np.float64,0x3fc8635f6430c6bf,0xbfe70a7dc77749a7,1 +np.float64,0x3fe3ff5c52a7feb9,0xbfca22617c6636d5,1 +np.float64,0x3fbbae91fa375d24,0xbfeee9d4c300543f,1 +np.float64,0xe3f71b59c7ee4,0xc0733b3f99187375,1 +np.float64,0x7fca93d3be3527a6,0x40733926fd48ecd6,1 +np.float64,0x3fcd29f7223a53ee,0xbfe48e3edf32fe57,1 +np.float64,0x7fdc4ef6f8389ded,0x40733e68401cf2a6,1 +np.float64,0xe009bc81c014,0xc0734ea295ee3e5b,1 +np.float64,0x61f56c78c3eae,0xc073411e1dbd7c54,1 +np.float64,0x3fde131928bc2632,0xbfd4fda024f6927c,1 +np.float64,0x3fb21ee530243dca,0xbff266aaf0358129,1 +np.float64,0x7feaac82a4f55904,0x407342cf7809d9f9,1 +np.float64,0x3fe66ab177ecd563,0xbfc3c92d4d522819,1 +np.float64,0xfe9f9c2bfd3f4,0xc0733a7ade3a88a7,1 +np.float64,0x7fd0c5217c218a42,0x40733ac4e4c6dfa5,1 +np.float64,0x430f4ae6861ea,0xc07343c03d8a9442,1 +np.float64,0x494bff2a92981,0xc073432209d2fd16,1 +np.float64,0x3f8860e9d030c1d4,0xbffeca059ebf5e89,1 +np.float64,0x3fe43732dc286e66,0xbfc98800388bad2e,1 +np.float64,0x6443b60ec8877,0xc07340f4bab11827,1 +np.float64,0x3feda9be6d7b537d,0xbfa0dcb9a6914069,1 +np.float64,0x3fc5ceb6772b9d6d,0xbfe89868c881db70,1 +np.float64,0x3fbdf153023be2a6,0xbfedd2878c3b4949,1 +np.float64,0x7fe8f6b8e8f1ed71,0x407342599a30b273,1 +np.float64,0x3fea6fbdb8b4df7b,0xbfb53bf66f71ee96,1 +np.float64,0xc7ac3dbb8f588,0xc0733c2b525b7963,1 +np.float64,0x3fef3a91f77e7524,0xbf85b2bd3adbbe31,1 +np.float64,0x3f887cb97030f973,0xbffec21ccbb5d22a,1 +np.float64,0x8b2f1c9f165e4,0xc0733ead49300951,1 +np.float64,0x2c1cb32058397,0xc07346a951bd8d2b,1 +np.float64,0x3fe057edd620afdc,0xbfd2acf1881b7e99,1 +np.float64,0x7f82e9530025d2a5,0x4073238591dd52ce,1 +np.float64,0x3fe4e03dff69c07c,0xbfc7be96c5c006fc,1 +np.float64,0x52727b4aa4e50,0xc0734250c58ebbc1,1 +np.float64,0x3f99a62160334c43,0xbff99ea3ca09d8f9,1 +np.float64,0x3fd5314b4faa6297,0xbfdeb843daf01e03,1 +np.float64,0x3fefde89e13fbd14,0xbf5d1facb7a1e9de,1 +np.float64,0x7fb460f1a228c1e2,0x4073327d8cbc5f86,1 +np.float64,0xeb93efb3d727e,0xc0733b052a4990e4,1 +np.float64,0x3fe884baecf10976,0xbfbd9ba9cfe23713,1 +np.float64,0x7fefffffffffffff,0x40734413509f79ff,1 +np.float64,0x149dc7c6293ba,0xc0734bf26b1df025,1 +np.float64,0x64188f88c8313,0xc07340f7b8e6f4b5,1 +np.float64,0x3fdfac314abf5863,0xbfd38d3e9dba1b0e,1 +np.float64,0x3fd72052a42e40a5,0xbfdc4af30ee0b245,1 +np.float64,0x7fdd951f743b2a3e,0x40733eb68fafa838,1 +np.float64,0x65a2dd5acb45c,0xc07340dc8ed625e1,1 +np.float64,0x7fe89a79997134f2,0x4073423fbceb1cbe,1 +np.float64,0x3fe70a000d6e1400,0xbfc24381e09d02f7,1 +np.float64,0x3fe2cec160259d83,0xbfcd8b5e92354129,1 +np.float64,0x3feb9ef77a773def,0xbfb05c7b2ee6f388,1 +np.float64,0xe0d66689c1acd,0xc0733b582c779620,1 +np.float64,0x3fee86bd0ffd0d7a,0xbf94f7870502c325,1 +np.float64,0x186afc6230d60,0xc0734ac55fb66d5d,1 +np.float64,0xc0631f4b80c64,0xc0733c6d7149d373,1 +np.float64,0x3fdad1b87735a371,0xbfd82cca73ec663b,1 +np.float64,0x7fe7f6d313efeda5,0x40734210e84576ab,1 +np.float64,0x7fd7b7fce6af6ff9,0x40733d2d92ffdaaf,1 +np.float64,0x3fe6f35a28ade6b4,0xbfc27a4239b540c3,1 +np.float64,0x7fdb0b834eb61706,0x40733e17073a61f3,1 +np.float64,0x82f4661105e8d,0xc0733f19b34adeed,1 +np.float64,0x3fc77230112ee460,0xbfe796a7603c0d16,1 +np.float64,0x8000000000000000,0xfff0000000000000,1 +np.float64,0x7fb8317bc63062f7,0x407333aec761a739,1 +np.float64,0x7fd165609a22cac0,0x40733b061541ff15,1 +np.float64,0x3fed394768fa728f,0xbfa42e1596e1faf6,1 +np.float64,0x7febab693d7756d1,0x40734310a9ac828e,1 +np.float64,0x7fe809a69230134c,0x407342165b9acb69,1 +np.float64,0x3fc091d38f2123a7,0xbfec69a70fc23548,1 +np.float64,0x3fb2a8f5dc2551ec,0xbff2327f2641dd0d,1 +np.float64,0x7fc60b6fe02c16df,0x407337da5adc342c,1 +np.float64,0x3fefa53c3bbf4a78,0xbf73d1be15b73b00,1 +np.float64,0x7fee09c1717c1382,0x407343a2c479e1cb,1 +np.float64,0x8000000000000001,0x7ff8000000000000,1 +np.float64,0x3fede0b2733bc165,0xbf9e848ac2ecf604,1 +np.float64,0x3fee2ac331bc5586,0xbf9a3b699b721c9a,1 +np.float64,0x3fd4db12d829b626,0xbfdf2a413d1e453a,1 +np.float64,0x7fe605230dec0a45,0x4073417a67db06be,1 +np.float64,0x3fe378b2bf26f165,0xbfcb9dbb2b6d6832,1 +np.float64,0xc1d4c1ab83a98,0xc0733c60244cadbf,1 +np.float64,0x3feb15500e762aa0,0xbfb28c071d5efc22,1 +np.float64,0x3fe36225a626c44b,0xbfcbde4259e9047e,1 +np.float64,0x3fe7c586a72f8b0d,0xbfc08614b13ed4b2,1 +np.float64,0x7fb0f2d8cc21e5b1,0x40733135b2c7dd99,1 +np.float64,0x5957f3feb2aff,0xc07341c1df75638c,1 +np.float64,0x3fca4851bd3490a3,0xbfe6005ae5279485,1 +np.float64,0x824217d904843,0xc0733f232fd58f0f,1 +np.float64,0x4f9332269f267,0xc073428fd8e9cb32,1 +np.float64,0x3fea6f087374de11,0xbfb53ef0d03918b2,1 +np.float64,0x3fd9409ab4328135,0xbfd9d9231381e2b8,1 +np.float64,0x3fdba03b00374076,0xbfd759ec94a7ab5b,1 +np.float64,0x3fe0ce3766619c6f,0xbfd1e6912582ccf0,1 +np.float64,0x3fabd45ddc37a8bc,0xbff43c78d3188423,1 +np.float64,0x3fc3cadd592795bb,0xbfe9f1576c9b2c79,1 +np.float64,0x3fe10df049621be1,0xbfd17df2f2c28022,1 +np.float64,0x945b5d1328b6c,0xc0733e3bc06f1e75,1 +np.float64,0x7fc1c3742b2386e7,0x4073365a403d1051,1 +np.float64,0x7fdc957138b92ae1,0x40733e7977717586,1 +np.float64,0x7f943fa1a0287f42,0x407328d01de143f5,1 +np.float64,0x3fec9631c4392c64,0xbfa914b176d8f9d2,1 +np.float64,0x3fd8e7c008b1cf80,0xbfda3b9d9b6da8f4,1 +np.float64,0x7222f9fee4460,0xc073400e371516cc,1 +np.float64,0x3fe890e43eb121c8,0xbfbd64921462e823,1 +np.float64,0x3fcfd7fe2a3faffc,0xbfe3557e2f207800,1 +np.float64,0x3fed5dd1c1babba4,0xbfa318bb20db64e6,1 +np.float64,0x3fe6aa34c66d546a,0xbfc32c8a8991c11e,1 +np.float64,0x8ca79801196,0xc0736522bd5adf6a,1 +np.float64,0x3feb274079364e81,0xbfb2427b24b0ca20,1 +np.float64,0x7fe04927e4a0924f,0x40733f61c96f7f89,1 +np.float64,0x7c05f656f80bf,0xc0733f7a70555b4e,1 +np.float64,0x7fe97819eff2f033,0x4073427d4169b0f8,1 +np.float64,0x9def86e33bdf1,0xc0733dcc740b7175,1 +np.float64,0x7fedd1ef3f3ba3dd,0x40734395ceab8238,1 +np.float64,0x77bed86cef7dc,0xc0733fb8e0e9bf73,1 +np.float64,0x9274b41b24e97,0xc0733e52b16dff71,1 +np.float64,0x8010000000000000,0x7ff8000000000000,1 +np.float64,0x9c977855392ef,0xc0733ddba7d421d9,1 +np.float64,0xfb4560a3f68ac,0xc0733a9271e6a118,1 +np.float64,0xa67d9f394cfb4,0xc0733d6e9d58cc94,1 +np.float64,0x3fbfa766b03f4ecd,0xbfed0cccfecfc900,1 +np.float64,0x3fe177417522ee83,0xbfd0d45803bff01a,1 +np.float64,0x7fe85e077bb0bc0e,0x4073422e957a4aa3,1 +np.float64,0x7feeb0a6883d614c,0x407343c8f6568f7c,1 +np.float64,0xbab82edb75706,0xc0733ca2a2b20094,1 +np.float64,0xfadb44bdf5b69,0xc0733a9561b7ec04,1 +np.float64,0x3fefb9b82b3f7370,0xbf6ea776b2dcc3a9,1 +np.float64,0x7fe080ba8a610174,0x40733f795779b220,1 +np.float64,0x3f87faa1c02ff544,0xbffee76acafc92b7,1 +np.float64,0x7fed474108fa8e81,0x4073437531d4313e,1 +np.float64,0x3fdb7b229336f645,0xbfd77f583a4a067f,1 +np.float64,0x256dbf0c4adb9,0xc07347cd94e6fa81,1 +np.float64,0x3fd034ae25a0695c,0xbfe3169c15decdac,1 +np.float64,0x3a72177274e44,0xc07344b4cf7d68cd,1 +np.float64,0x7fa2522d5c24a45a,0x40732cef2f793470,1 +np.float64,0x3fb052bdde20a57c,0xbff3207fd413c848,1 +np.float64,0x3fdccfecbbb99fd9,0xbfd62ec04a1a687a,1 +np.float64,0x3fd403ac53280759,0xbfe027a31df2c8cc,1 +np.float64,0x3fab708e4036e11d,0xbff45591df4f2e8b,1 +np.float64,0x7fcfc001993f8002,0x40733a63539acf9d,1 +np.float64,0x3fd2b295dfa5652c,0xbfe119c1b476c536,1 +np.float64,0x7fe8061262b00c24,0x4073421552ae4538,1 +np.float64,0xffefffffffffffff,0x7ff8000000000000,1 +np.float64,0x7fed52093ffaa411,0x40734377c072a7e8,1 +np.float64,0xf3df902fe7bf2,0xc0733ac79a75ff7a,1 +np.float64,0x7fe13d382e227a6f,0x40733fc6fd0486bd,1 +np.float64,0x3621d5086c43b,0xc073453d31effbcd,1 +np.float64,0x3ff0000000000000,0x0,1 +np.float64,0x3fdaffea27b5ffd4,0xbfd7fd139dc1c2c5,1 +np.float64,0x7fea6536dc34ca6d,0x407342bccc564fdd,1 +np.float64,0x7fd478f00c28f1df,0x40733c27c0072fde,1 +np.float64,0x7fa72ef0502e5de0,0x40732e91e83db75c,1 +np.float64,0x7fd302970626052d,0x40733ba3ec6775f6,1 +np.float64,0x7fbb57ab0036af55,0x407334887348e613,1 +np.float64,0x3fda0ff722b41fee,0xbfd8f87b77930330,1 +np.float64,0x1e983ce23d309,0xc073493438f57e61,1 +np.float64,0x7fc90de97c321bd2,0x407338be01ffd4bd,1 +np.float64,0x7fe074b09c20e960,0x40733f7443f0dbe1,1 +np.float64,0x3fed5dec9fbabbd9,0xbfa317efb1fe8a95,1 +np.float64,0x7fdb877632b70eeb,0x40733e3697c88ba8,1 +np.float64,0x7fe4fb0067e9f600,0x40734124604b99e8,1 +np.float64,0x7fd447dc96288fb8,0x40733c1703ab2cce,1 +np.float64,0x3feb2d1e64f65a3d,0xbfb22a781df61c05,1 +np.float64,0xb6c8e6676d91d,0xc0733cc8859a0b91,1 +np.float64,0x3fdc3c2418387848,0xbfd6bec3a3c3cdb5,1 +np.float64,0x3fdecb9ccdbd973a,0xbfd4551c05721a8e,1 +np.float64,0x3feb1100e7762202,0xbfb29db911fe6768,1 +np.float64,0x3fe0444bc2a08898,0xbfd2ce69582e78c1,1 +np.float64,0x7fda403218b48063,0x40733de201d8340c,1 +np.float64,0x3fdc70421238e084,0xbfd68ba4bd48322b,1 +np.float64,0x3fe06e747c60dce9,0xbfd286bcac34a981,1 +np.float64,0x7fc1931d9623263a,0x407336473da54de4,1 +np.float64,0x229914da45323,0xc073485979ff141c,1 +np.float64,0x3fe142f92da285f2,0xbfd1280909992cb6,1 +np.float64,0xf1d02fa9e3a06,0xc0733ad6b19d71a0,1 +np.float64,0x3fb1fe9b0023fd36,0xbff27317d8252c16,1 +np.float64,0x3fa544b9242a8972,0xbff61ac38569bcfc,1 +np.float64,0x3feeb129d4fd6254,0xbf928f23ad20c1ee,1 +np.float64,0xa2510b7f44a22,0xc0733d9bc81ea0a1,1 +np.float64,0x3fca75694d34ead3,0xbfe5e8975b3646c2,1 +np.float64,0x7fece10621b9c20b,0x4073435cc3dd9a1b,1 +np.float64,0x7fe98a57d3b314af,0x4073428239b6a135,1 +np.float64,0x3fe259c62a64b38c,0xbfcee96682a0f355,1 +np.float64,0x3feaaa9b9d755537,0xbfb445779f3359af,1 +np.float64,0xdaadecfdb55be,0xc0733b899338432a,1 +np.float64,0x3fed00eae4fa01d6,0xbfa5dc8d77be5991,1 +np.float64,0x7fcc96c773392d8e,0x407339a8c5cd786e,1 +np.float64,0x3fef7b8b203ef716,0xbf7cff655ecb6424,1 +np.float64,0x7fd4008113a80101,0x40733bfe6552acb7,1 +np.float64,0x7fe99ff035b33fdf,0x407342881753ee2e,1 +np.float64,0x3ee031e87dc07,0xc0734432d736e492,1 +np.float64,0x3fddfe390f3bfc72,0xbfd510f1d9ec3e36,1 +np.float64,0x3fd9ddce74b3bb9d,0xbfd92e2d75a061bb,1 +np.float64,0x7fe5f742edebee85,0x40734176058e3a77,1 +np.float64,0x3fdb04185b360831,0xbfd7f8c63aa5e1c4,1 +np.float64,0xea2b0f43d4562,0xc0733b0fd77c8118,1 +np.float64,0x7fc3f4973527e92d,0x407337293bbb22c4,1 +np.float64,0x3fb9adfb38335bf6,0xbfeff4f3ea85821a,1 +np.float64,0x87fb98750ff73,0xc0733ed6ad83c269,1 +np.float64,0x3fe005721a200ae4,0xbfd33a9f1ebfb0ac,1 +np.float64,0xd9e04fe7b3c0a,0xc0733b901ee257f3,1 +np.float64,0x2c39102658723,0xc07346a4db63bf55,1 +np.float64,0x3f7dc28e003b851c,0xc0011c1d1233d948,1 +np.float64,0x3430fd3868620,0xc073457e24e0b70d,1 +np.float64,0xbff0000000000000,0x7ff8000000000000,1 +np.float64,0x3fd23e45e0247c8c,0xbfe17146bcf87b57,1 +np.float64,0x6599df3ecb33d,0xc07340dd2c41644c,1 +np.float64,0x3fdf074f31be0e9e,0xbfd41f6e9dbb68a5,1 +np.float64,0x7fdd6233f3bac467,0x40733eaa8f674b72,1 +np.float64,0x7fe03e8481607d08,0x40733f5d3df3b087,1 +np.float64,0x3fcc3b79f13876f4,0xbfe501bf3b379b77,1 +np.float64,0xe5d97ae3cbb30,0xc0733b30f47cbd12,1 +np.float64,0x8acbc4a115979,0xc0733eb240a4d2c6,1 +np.float64,0x3fedbdbc48bb7b79,0xbfa0470fd70c4359,1 +np.float64,0x3fde1611103c2c22,0xbfd4fae1fa8e7e5e,1 +np.float64,0x3fe09478bd2128f1,0xbfd246b7e85711dc,1 +np.float64,0x3fd6dfe8f3adbfd2,0xbfdc98ca2f32c1ad,1 +np.float64,0x72ccf274e599f,0xc0734003e5b0da63,1 +np.float64,0xe27c7265c4f8f,0xc0733b4b2d808566,1 +np.float64,0x7fee3161703c62c2,0x407343abe90f5649,1 +np.float64,0xf54fb5c1eaa0,0xc0734e01384fcf78,1 +np.float64,0xcde5924d9bcb3,0xc0733bf4b83c66c2,1 +np.float64,0x3fc46fdbe528dfb8,0xbfe97f55ef5e9683,1 +np.float64,0x7fe513528a2a26a4,0x4073412c69baceca,1 +np.float64,0x3fd29eca4aa53d95,0xbfe128801cd33ed0,1 +np.float64,0x7febb21718b7642d,0x4073431256def857,1 +np.float64,0x3fcab536c0356a6e,0xbfe5c73c59f41578,1 +np.float64,0x7fc7e9f0d82fd3e1,0x4073386b213e5dfe,1 +np.float64,0xb5b121276b624,0xc0733cd33083941c,1 +np.float64,0x7e0dd9bcfc1bc,0xc0733f5d8bf35050,1 +np.float64,0x3fd1c75106238ea2,0xbfe1cd11cccda0f4,1 +np.float64,0x9f060e673e0c2,0xc0733dc03da71909,1 +np.float64,0x7fd915a2f3322b45,0x40733d912af07189,1 +np.float64,0x3fd8cbae4431975d,0xbfda5b02ca661139,1 +np.float64,0x3fde8b411f3d1682,0xbfd48f6f710a53b6,1 +np.float64,0x3fc17a780622f4f0,0xbfebabb10c55255f,1 +np.float64,0x3fde5cbe5f3cb97d,0xbfd4b9e2e0101fb1,1 +np.float64,0x7fd859036530b206,0x40733d5c2252ff81,1 +np.float64,0xb0f5040f61ea1,0xc0733d02292f527b,1 +np.float64,0x3fde5c49ae3cb893,0xbfd4ba4db3ce2cf3,1 +np.float64,0x3fecc4518df988a3,0xbfa7af0bfc98bc65,1 +np.float64,0x3feffee03cbffdc0,0xbf0f3ede6ca7d695,1 +np.float64,0xbc5eac9b78bd6,0xc0733c92fb51c8ae,1 +np.float64,0x3fe2bb4ef765769e,0xbfcdc4f70a65dadc,1 +np.float64,0x5089443ca1129,0xc073427a7d0cde4a,1 +np.float64,0x3fd0d6e29121adc5,0xbfe28e28ece1db86,1 +np.float64,0xbe171e397c2e4,0xc0733c82cede5d02,1 +np.float64,0x4ede27be9dbc6,0xc073429fba1a4af1,1 +np.float64,0x3fe2aff3af655fe7,0xbfcde6b52a8ed3c1,1 +np.float64,0x7fd85ca295b0b944,0x40733d5d2adcccf1,1 +np.float64,0x24919bba49234,0xc07347f6ed704a6f,1 +np.float64,0x7fd74bc1eeae9783,0x40733d0d94a89011,1 +np.float64,0x3fc1cd12cb239a26,0xbfeb6a9c25c2a11d,1 +np.float64,0x3fdafbc0ac35f781,0xbfd8015ccf1f1b51,1 +np.float64,0x3fee01327c3c0265,0xbf9ca1d0d762dc18,1 +np.float64,0x3fe65bd7702cb7af,0xbfc3ee0de5c36b8d,1 +np.float64,0x7349c82ee693a,0xc0733ffc5b6eccf2,1 +np.float64,0x3fdc5906f738b20e,0xbfd6a26288eb5933,1 +np.float64,0x1,0xc07434e6420f4374,1 +np.float64,0x3fb966128a32cc25,0xbff00e0aa7273838,1 +np.float64,0x3fd501ff9a2a03ff,0xbfdef69133482121,1 +np.float64,0x194d4f3c329ab,0xc0734a861b44cfbe,1 +np.float64,0x3fec5d34f8f8ba6a,0xbfaad1b31510e70b,1 +np.float64,0x1635e4c22c6be,0xc0734b6dec650943,1 +np.float64,0x3fead2f8edb5a5f2,0xbfb39dac30a962cf,1 +np.float64,0x3f7dfa4ce03bf49a,0xc00115a112141aa7,1 +np.float64,0x3fef6827223ed04e,0xbf80a42c9edebfe9,1 +np.float64,0xe771f303cee3f,0xc0733b24a6269fe4,1 +np.float64,0x1160ccc622c1b,0xc0734d22604eacb9,1 +np.float64,0x3fc485cd08290b9a,0xbfe970723008c8c9,1 +np.float64,0x7fef99c518bf3389,0x407343fcf9ed202f,1 +np.float64,0x7fd8c1447a318288,0x40733d79a440b44d,1 +np.float64,0xaf219f955e434,0xc0733d149c13f440,1 +np.float64,0xcf45f6239e8bf,0xc0733be8ddda045d,1 +np.float64,0x7599394aeb328,0xc0733fd90fdbb0ea,1 +np.float64,0xc7f6390f8fec7,0xc0733c28bfbc66a3,1 +np.float64,0x3fd39ae96c2735d3,0xbfe0712274a8742b,1 +np.float64,0xa4d6c18f49ad8,0xc0733d805a0528f7,1 +np.float64,0x7fd9ea78d7b3d4f1,0x40733dcb2b74802a,1 +np.float64,0x3fecd251cb39a4a4,0xbfa742ed41d4ae57,1 +np.float64,0x7fed7a07cd7af40f,0x407343813476027e,1 +np.float64,0x3fd328ae7f26515d,0xbfe0c30b56a83c64,1 +np.float64,0x7fc937ff7a326ffe,0x407338c9a45b9140,1 +np.float64,0x3fcf1d31143e3a62,0xbfe3a7f760fbd6a8,1 +np.float64,0x7fb911dcbc3223b8,0x407333ee158cccc7,1 +np.float64,0x3fd352fc83a6a5f9,0xbfe0a47d2f74d283,1 +np.float64,0x7fd310753fa620e9,0x40733ba8fc4300dd,1 +np.float64,0x3febd64b4577ac97,0xbfaefd4a79f95c4b,1 +np.float64,0x6a6961a4d4d2d,0xc073408ae1687943,1 +np.float64,0x3fe4ba73d16974e8,0xbfc8239341b9e457,1 +np.float64,0x3fed8e7cac3b1cf9,0xbfa1a96a0cc5fcdc,1 +np.float64,0x7fd505ec04aa0bd7,0x40733c56f86e3531,1 +np.float64,0x3fdf166e9abe2cdd,0xbfd411e5f8569d70,1 +np.float64,0x7fe1bc6434e378c7,0x40733ff9861bdabb,1 +np.float64,0x3fd3b0b175a76163,0xbfe061ba5703f3c8,1 +np.float64,0x7fed75d7ffbaebaf,0x4073438037ba6f19,1 +np.float64,0x5a9e109cb53c3,0xc07341a8b04819c8,1 +np.float64,0x3fe14786b4e28f0d,0xbfd120b541bb880e,1 +np.float64,0x3fed4948573a9291,0xbfa3b471ff91614b,1 +np.float64,0x66aac5d8cd559,0xc07340ca9b18af46,1 +np.float64,0x3fdb48efd23691e0,0xbfd7b24c5694838b,1 +np.float64,0x7fe6da7d1eadb4f9,0x407341bc7d1fae43,1 +np.float64,0x7feb702cf336e059,0x40734301b96cc3c0,1 +np.float64,0x3fd1e60987a3cc13,0xbfe1b522cfcc3d0e,1 +np.float64,0x3feca57f50794aff,0xbfa89dc90625d39c,1 +np.float64,0x7fdc46dc56b88db8,0x40733e664294a0f9,1 +np.float64,0x8dc8fd811b920,0xc0733e8c5955df06,1 +np.float64,0xf01634abe02c7,0xc0733ae370a76d0c,1 +np.float64,0x3fc6f8d8ab2df1b1,0xbfe7df5093829464,1 +np.float64,0xda3d7597b47af,0xc0733b8d2702727a,1 +np.float64,0x7feefd53227dfaa5,0x407343da3d04db28,1 +np.float64,0x3fe2fbca3525f794,0xbfcd06e134417c08,1 +np.float64,0x7fd36d3ce226da79,0x40733bca7c322df1,1 +np.float64,0x7fec37e00b786fbf,0x4073433397b48a5b,1 +np.float64,0x3fbf133f163e267e,0xbfed4e72f1362a77,1 +np.float64,0x3fc11efbb9223df7,0xbfebf53002a561fe,1 +np.float64,0x3fc89c0e5431381d,0xbfe6ea562364bf81,1 +np.float64,0x3f9cd45da839a8bb,0xbff8ceb14669ee4b,1 +np.float64,0x23dc8fa647b93,0xc0734819aaa9b0ee,1 +np.float64,0x3fe829110d305222,0xbfbf3e60c45e2399,1 +np.float64,0x7fed8144e57b0289,0x40734382e917a02a,1 +np.float64,0x7fe033fbf7a067f7,0x40733f58bb00b20f,1 +np.float64,0xe3807f45c7010,0xc0733b43379415d1,1 +np.float64,0x3fd708fb342e11f6,0xbfdc670ef9793782,1 +np.float64,0x3fe88c924b311925,0xbfbd78210d9e7164,1 +np.float64,0x3fe0a2a7c7614550,0xbfd22efaf0472c4a,1 +np.float64,0x7fe3a37501a746e9,0x407340aecaeade41,1 +np.float64,0x3fd05077ec20a0f0,0xbfe2fedbf07a5302,1 +np.float64,0x7fd33bf61da677eb,0x40733bb8c58912aa,1 +np.float64,0x3feb29bdae76537b,0xbfb2384a8f61b5f9,1 +np.float64,0x3fec0fc14ff81f83,0xbfad3423e7ade174,1 +np.float64,0x3fd0f8b1a1a1f163,0xbfe2725dd4ccea8b,1 +np.float64,0x3fe382d26a6705a5,0xbfcb80dba4218bdf,1 +np.float64,0x3fa873f2cc30e7e6,0xbff522911cb34279,1 +np.float64,0x7fed7fd7377affad,0x4073438292f6829b,1 +np.float64,0x3feeacd8067d59b0,0xbf92cdbeda94b35e,1 +np.float64,0x7fe464d62228c9ab,0x407340f1eee19aa9,1 +np.float64,0xe997648bd32ed,0xc0733b143aa0fad3,1 +np.float64,0x7fea4869f13490d3,0x407342b5333b54f7,1 +np.float64,0x935b871926b71,0xc0733e47c6683319,1 +np.float64,0x28a9d0c05155,0xc0735a7e3532af83,1 +np.float64,0x79026548f204d,0xc0733fa6339ffa2f,1 +np.float64,0x3fdb1daaabb63b55,0xbfd7de839c240ace,1 +np.float64,0x3fc0db73b421b6e7,0xbfec2c6e36c4f416,1 +np.float64,0xb8b50ac1716b,0xc0734ff9fc60ebce,1 +np.float64,0x7fdf13e0c6be27c1,0x40733f0e44f69437,1 +np.float64,0x3fcd0cb97b3a1973,0xbfe49c34ff531273,1 +np.float64,0x3fcbac034b375807,0xbfe54913d73f180d,1 +np.float64,0x3fe091d2a2e123a5,0xbfd24b290a9218de,1 +np.float64,0xede43627dbc87,0xc0733af3c7c7f716,1 +np.float64,0x7fc037e7ed206fcf,0x407335b85fb0fedb,1 +np.float64,0x3fce7ae4c63cf5ca,0xbfe3f1350fe03f28,1 +np.float64,0x7fcdd862263bb0c3,0x407339f5458bb20e,1 +np.float64,0x4d7adf709af5d,0xc07342bf4edfadb2,1 +np.float64,0xdc6c03f3b8d81,0xc0733b7b74d6a635,1 +np.float64,0x3fe72ae0a4ee55c1,0xbfc1f4665608b21f,1 +np.float64,0xcd62f19d9ac5e,0xc0733bf92235e4d8,1 +np.float64,0xe3a7b8fdc74f7,0xc0733b4204f8e166,1 +np.float64,0x3fdafd35adb5fa6b,0xbfd7ffdca0753b36,1 +np.float64,0x3fa023e8702047d1,0xbff8059150ea1464,1 +np.float64,0x99ff336933fe7,0xc0733df961197517,1 +np.float64,0x7feeb365b9bd66ca,0x407343c995864091,1 +np.float64,0x7fe449b49f689368,0x407340e8aa3369e3,1 +np.float64,0x7faf5843043eb085,0x407330aa700136ca,1 +np.float64,0x3fd47b2922a8f652,0xbfdfab3de86f09ee,1 +np.float64,0x7fd9fc3248b3f864,0x40733dcfea6f9b3e,1 +np.float64,0xe20b0d8dc4162,0xc0733b4ea8fe7b3f,1 +np.float64,0x7feff8e0e23ff1c1,0x40734411c490ed70,1 +np.float64,0x7fa58382d02b0705,0x40732e0cf28e14fe,1 +np.float64,0xb8ad9a1b715b4,0xc0733cb630b8f2d4,1 +np.float64,0xe90abcf1d2158,0xc0733b186b04eeee,1 +np.float64,0x7fd6aa6f32ad54dd,0x40733cdccc636604,1 +np.float64,0x3fd8f84eedb1f09e,0xbfda292909a5298a,1 +np.float64,0x7fecd6b1d9f9ad63,0x4073435a472b05b5,1 +np.float64,0x3fd9f47604b3e8ec,0xbfd915e028cbf4a6,1 +np.float64,0x3fd20d9398241b27,0xbfe19691363dd508,1 +np.float64,0x3fe5ed09bbabda13,0xbfc5043dfc9c8081,1 +np.float64,0x7fbe5265363ca4c9,0x407335406f8e4fac,1 +np.float64,0xac2878af5850f,0xc0733d3311be9786,1 +np.float64,0xac2074555840f,0xc0733d3364970018,1 +np.float64,0x3fcd49b96b3a9373,0xbfe47f24c8181d9c,1 +np.float64,0x3fd10caca6a21959,0xbfe2620ae5594f9a,1 +np.float64,0xec5b87e9d8b71,0xc0733aff499e72ca,1 +np.float64,0x9d5e9fad3abd4,0xc0733dd2d70eeb4a,1 +np.float64,0x7fe3d3a24227a744,0x407340bfc2072fdb,1 +np.float64,0x3fc5f7a77c2bef4f,0xbfe87e69d502d784,1 +np.float64,0x33161a66662c4,0xc07345a436308244,1 +np.float64,0xa27acdc744f5a,0xc0733d99feb3d8ea,1 +np.float64,0x3fe2d9301565b260,0xbfcd6c914e204437,1 +np.float64,0x7fd5d111e12ba223,0x40733c98e14a6fd0,1 +np.float64,0x6c3387bed8672,0xc073406d3648171a,1 +np.float64,0x24d89fe849b15,0xc07347e97bec008c,1 +np.float64,0x3fefd763677faec7,0xbf61ae69caa9cad9,1 +np.float64,0x7fe0a4684ba148d0,0x40733f884d32c464,1 +np.float64,0x3fd5c3c939ab8792,0xbfddfaaefc1c7fca,1 +np.float64,0x3fec9b87a6b9370f,0xbfa8eb34efcc6b9b,1 +np.float64,0x3feb062431f60c48,0xbfb2ca6036698877,1 +np.float64,0x3fef97f6633f2fed,0xbf76bc742860a340,1 +np.float64,0x74477490e88ef,0xc0733fed220986bc,1 +np.float64,0x3fe4bea67ce97d4d,0xbfc818525292b0f6,1 +np.float64,0x3fc6add3a92d5ba7,0xbfe80cfdc9a90bda,1 +np.float64,0x847c9ce308f94,0xc0733f05026f5965,1 +np.float64,0x7fea53fd2eb4a7f9,0x407342b841fc4723,1 +np.float64,0x3fc55a16fc2ab42e,0xbfe8e3849130da34,1 +np.float64,0x3fbdf7d07c3befa1,0xbfedcf84b9c6c161,1 +np.float64,0x3fe5fb25aa6bf64b,0xbfc4e083ff96b116,1 +np.float64,0x61c776a8c38ef,0xc0734121611d84d7,1 +np.float64,0x3fec413164f88263,0xbfabadbd05131546,1 +np.float64,0x9bf06fe137e0e,0xc0733de315469ee0,1 +np.float64,0x2075eefc40ebf,0xc07348cae84de924,1 +np.float64,0x3fdd42e0143a85c0,0xbfd5c0b6f60b3cea,1 +np.float64,0xdbb1ab45b7636,0xc0733b8157329daf,1 +np.float64,0x3feac6d56bf58dab,0xbfb3d00771b28621,1 +np.float64,0x7fb2dc825025b904,0x407331f3e950751a,1 +np.float64,0x3fecea6efd79d4de,0xbfa689309cc0e3fe,1 +np.float64,0x3fd83abec7b0757e,0xbfdaff5c674a9c59,1 +np.float64,0x3fd396f7c0272df0,0xbfe073ee75c414ba,1 +np.float64,0x3fe10036c162006e,0xbfd1945a38342ae1,1 +np.float64,0x3fd5bbded52b77be,0xbfde04cca40d4156,1 +np.float64,0x3fe870945ab0e129,0xbfbdf72f0e6206fa,1 +np.float64,0x3fef72fddcbee5fc,0xbf7ee2dba88b1bad,1 +np.float64,0x4e111aa09c224,0xc07342b1e2b29643,1 +np.float64,0x3fd926d8b5b24db1,0xbfd9f58b78d6b061,1 +np.float64,0x3fc55679172aacf2,0xbfe8e5df687842e2,1 +np.float64,0x7f5f1749803e2e92,0x40731886e16cfc4d,1 +np.float64,0x7fea082b53b41056,0x407342a42227700e,1 +np.float64,0x3fece1d1d039c3a4,0xbfa6cb780988a469,1 +np.float64,0x3b2721d8764e5,0xc073449f6a5a4832,1 +np.float64,0x365cb7006cba,0xc0735879ba5f0b6e,1 +np.float64,0x7ff4000000000000,0x7ffc000000000000,1 +np.float64,0x7fe606ce92ac0d9c,0x4073417aeebe97e8,1 +np.float64,0x3fe237b544a46f6b,0xbfcf50f8f76d7df9,1 +np.float64,0x3fe7265e5eee4cbd,0xbfc1ff39089ec8d0,1 +np.float64,0x7fe2bb3c5ea57678,0x4073405aaad81cf2,1 +np.float64,0x3fd811df84b023bf,0xbfdb2e670ea8d8de,1 +np.float64,0x3f6a0efd00341dfa,0xc003fac1ae831241,1 +np.float64,0x3fd0d214afa1a429,0xbfe2922080a91c72,1 +np.float64,0x3feca6a350b94d47,0xbfa894eea3a96809,1 +np.float64,0x7fe23e5c76247cb8,0x4073402bbaaf71c7,1 +np.float64,0x3fe739a1fdae7344,0xbfc1d109f66efb5d,1 +np.float64,0x3fdf4b8e283e971c,0xbfd3e28f46169cc5,1 +np.float64,0x38f2535271e4b,0xc07344e3085219fa,1 +np.float64,0x7fd263a0f9a4c741,0x40733b68d945dae0,1 +np.float64,0x7fdd941863bb2830,0x40733eb651e3dca9,1 +np.float64,0xace7279159ce5,0xc0733d2b63b5947e,1 +np.float64,0x7fe34670b2268ce0,0x4073408d92770cb5,1 +np.float64,0x7fd11fa6dfa23f4d,0x40733aea02e76ea3,1 +np.float64,0x3fe6d9cbca6db398,0xbfc2b84b5c8c7eab,1 +np.float64,0x3fd69a0274ad3405,0xbfdcee3c7e52c463,1 +np.float64,0x3feb5af671f6b5ed,0xbfb16f88d739477f,1 +np.float64,0x3feea400163d4800,0xbf934e071c64fd0b,1 +np.float64,0x3fefd6bcf17fad7a,0xbf61f711c392b119,1 +np.float64,0x3fe148d43da291a8,0xbfd11e9cd3f91cd3,1 +np.float64,0x7fedf1308b7be260,0x4073439d135656da,1 +np.float64,0x3fe614c99c6c2993,0xbfc49fd1984dfd6d,1 +np.float64,0xd6e8d4e5add1b,0xc0733ba88256026e,1 +np.float64,0xfff0000000000000,0x7ff8000000000000,1 +np.float64,0x3fb530b5562a616b,0xbff1504bcc5c8f73,1 +np.float64,0xb7da68396fb4d,0xc0733cbe2790f52e,1 +np.float64,0x7fad78e26c3af1c4,0x4073303cdbfb0a15,1 +np.float64,0x7fee5698447cad30,0x407343b474573a8b,1 +np.float64,0x3fd488325c291065,0xbfdf999296d901e7,1 +np.float64,0x2669283a4cd26,0xc073479f823109a4,1 +np.float64,0x7fef3b090afe7611,0x407343e805a3b264,1 +np.float64,0x7fe8b96ae0f172d5,0x4073424874a342ab,1 +np.float64,0x7fef409f56fe813e,0x407343e943c3cd44,1 +np.float64,0x3fed28073dfa500e,0xbfa4b17e4cd31a3a,1 +np.float64,0x7f87ecc4802fd988,0x40732527e027b24b,1 +np.float64,0x3fdda24da0bb449b,0xbfd566a43ac035af,1 +np.float64,0x179fc9e62f3fa,0xc0734b0028c80fc1,1 +np.float64,0x3fef85b0927f0b61,0xbf7ac27565d5ab4f,1 +np.float64,0x5631501aac62b,0xc0734201be12c5d4,1 +np.float64,0x3fd782e424af05c8,0xbfdbd57544f8a7c3,1 +np.float64,0x3fe603a9a6ac0753,0xbfc4caff04dc3caf,1 +np.float64,0x7fbd5225163aa449,0x40733504b88f0a56,1 +np.float64,0x3fecd27506b9a4ea,0xbfa741dd70e6b08c,1 +np.float64,0x9c99603b3932c,0xc0733ddb922dc5db,1 +np.float64,0x3fbeb57f1a3d6afe,0xbfed789ff217aa08,1 +np.float64,0x3fef9c0f85bf381f,0xbf75d5c3d6cb281a,1 +np.float64,0x3fde4afb613c95f7,0xbfd4ca2a231c9005,1 +np.float64,0x396233d472c47,0xc07344d56ee70631,1 +np.float64,0x3fb31ea1c6263d44,0xbff207356152138d,1 +np.float64,0x3fe50bdf78aa17bf,0xbfc74ae0cbffb735,1 +np.float64,0xef74c701dee99,0xc0733ae81e4bb443,1 +np.float64,0x9a3e13a1347c3,0xc0733df68b60afc7,1 +np.float64,0x33ba4f886774b,0xc073458e03f0c13e,1 +np.float64,0x3fe8ba0e9931741d,0xbfbcaadf974e8f64,1 +np.float64,0x3fe090a4cd61214a,0xbfd24d236cf365d6,1 +np.float64,0x7fd87d992930fb31,0x40733d668b73b820,1 +np.float64,0x3fe6422b296c8456,0xbfc42e070b695d01,1 +np.float64,0x3febe9334677d267,0xbfae667864606cfe,1 +np.float64,0x771a3ce4ee348,0xc0733fc274d12c97,1 +np.float64,0x3fe0413542e0826b,0xbfd2d3b08fb5b8a6,1 +np.float64,0x3fd00870ea2010e2,0xbfe33cc04cbd42e0,1 +np.float64,0x3fe74fb817ae9f70,0xbfc19c45dbf919e1,1 +np.float64,0x40382fa08071,0xc07357514ced5577,1 +np.float64,0xa14968474292d,0xc0733da71a990f3a,1 +np.float64,0x5487c740a90fa,0xc0734224622d5801,1 +np.float64,0x3fed7d8d14fafb1a,0xbfa228f7ecc2ac03,1 +np.float64,0x3fe39bb485e73769,0xbfcb3a235a722960,1 +np.float64,0x3fd01090b2202121,0xbfe335b752589a22,1 +np.float64,0x3fd21a3e7da4347d,0xbfe18cd435a7c582,1 +np.float64,0x3fe7fa855a2ff50b,0xbfc00ab0665709fe,1 +np.float64,0x3fedc0d4577b81a9,0xbfa02fef3ff553fc,1 +np.float64,0x3fe99d4906333a92,0xbfb8bf18220e5e8e,1 +np.float64,0x3fd944ee3c3289dc,0xbfd9d46071675e73,1 +np.float64,0x3fe3ed8d52e7db1b,0xbfca53f8d4aef484,1 +np.float64,0x7fe748623a6e90c3,0x407341dd97c9dd79,1 +np.float64,0x3fea1b4b98343697,0xbfb6a1560a56927f,1 +np.float64,0xe1215715c242b,0xc0733b55dbf1f0a8,1 +np.float64,0x3fd0d5bccca1ab7a,0xbfe28f1b66d7a470,1 +np.float64,0x881a962710353,0xc0733ed51848a30d,1 +np.float64,0x3fcf022afe3e0456,0xbfe3b40eabf24501,1 +np.float64,0x3fdf1ac6bbbe358d,0xbfd40e03e888288d,1 +np.float64,0x3fa51a5eac2a34bd,0xbff628a7c34d51b3,1 +np.float64,0x3fdbaf408d375e81,0xbfd74ad39d97c92a,1 +np.float64,0x3fcd2418ea3a4832,0xbfe4910b009d8b11,1 +np.float64,0x3fc7b3062a2f660c,0xbfe7706dc47993e1,1 +np.float64,0x7fb8232218304643,0x407333aaa7041a9f,1 +np.float64,0x7fd5f186362be30b,0x40733ca32fdf9cc6,1 +np.float64,0x3fe57ef1d6aafde4,0xbfc61e23d00210c7,1 +np.float64,0x7c6830baf8d07,0xc0733f74f19e9dad,1 +np.float64,0xcacbfd5595980,0xc0733c0fb49edca7,1 +np.float64,0x3fdfdeac873fbd59,0xbfd36114c56bed03,1 +np.float64,0x3fd31f0889263e11,0xbfe0ca0cc1250169,1 +np.float64,0x3fe839fbe47073f8,0xbfbef0a2abc3d63f,1 +np.float64,0x3fc36af57e26d5eb,0xbfea3553f38770b7,1 +np.float64,0x3fe73dbc44ee7b79,0xbfc1c738f8fa6b3d,1 +np.float64,0x3fd3760e4da6ec1d,0xbfe08b5b609d11e5,1 +np.float64,0x3fee1cfa297c39f4,0xbf9b06d081bc9d5b,1 +np.float64,0xdfb01561bf61,0xc0734ea55e559888,1 +np.float64,0x687bd01cd0f7b,0xc07340ab67fe1816,1 +np.float64,0x3fefc88f4cbf911f,0xbf6828c359cf19dc,1 +np.float64,0x8ad34adb15a6a,0xc0733eb1e03811e5,1 +np.float64,0x3fe2b49c12e56938,0xbfcdd8dbdbc0ce59,1 +np.float64,0x6e05037adc0a1,0xc073404f91261635,1 +np.float64,0x3fe2fd737fe5fae7,0xbfcd020407ef4d78,1 +np.float64,0x3fd0f3c0dc21e782,0xbfe2766a1ab02eae,1 +np.float64,0x28564d9850acb,0xc073474875f87c5e,1 +np.float64,0x3fe4758015a8eb00,0xbfc8ddb45134a1bd,1 +np.float64,0x7fe7f19306efe325,0x4073420f626141a7,1 +np.float64,0x7fd27f34c0a4fe69,0x40733b733d2a5b50,1 +np.float64,0x92c2366325847,0xc0733e4f04f8195a,1 +np.float64,0x3fc21f8441243f09,0xbfeb2ad23bc1ab0b,1 +np.float64,0x3fc721d3e42e43a8,0xbfe7c69bb47b40c2,1 +np.float64,0x3fe2f11a1625e234,0xbfcd26363b9c36c3,1 +np.float64,0x3fdcb585acb96b0b,0xbfd648446237cb55,1 +np.float64,0x3fd4060bf2280c18,0xbfe025fd4c8a658b,1 +np.float64,0x7fb8ae2750315c4e,0x407333d23b025d08,1 +np.float64,0x3fe3a03119a74062,0xbfcb2d6c91b38552,1 +np.float64,0x7fdd2af92bba55f1,0x40733e9d737e16e6,1 +np.float64,0x3fe50b05862a160b,0xbfc74d20815fe36b,1 +np.float64,0x164409f82c882,0xc0734b6980e19c03,1 +np.float64,0x3fe4093712a8126e,0xbfca070367fda5e3,1 +np.float64,0xae3049935c609,0xc0733d1e3608797b,1 +np.float64,0x3fd71df4b4ae3be9,0xbfdc4dcb7637600d,1 +np.float64,0x7fca01e8023403cf,0x407339006c521c49,1 +np.float64,0x3fb0c5c43e218b88,0xbff2f03211c63f25,1 +np.float64,0x3fee757af83ceaf6,0xbf95f33a6e56b454,1 +np.float64,0x3f865f1f402cbe3f,0xbfff62d9c9072bd7,1 +np.float64,0x89864e95130ca,0xc0733ec29f1e32c6,1 +np.float64,0x3fe51482bcea2905,0xbfc73414ddc8f1b7,1 +np.float64,0x7fd802f8fa3005f1,0x40733d43684e460a,1 +np.float64,0x3fbeb86ca63d70d9,0xbfed774ccca9b8f5,1 +np.float64,0x3fb355dcc826abba,0xbff1f33f9339e7a3,1 +np.float64,0x3fe506c61eaa0d8c,0xbfc7585a3f7565a6,1 +np.float64,0x7fe393f25ba727e4,0x407340a94bcea73b,1 +np.float64,0xf66f532decdeb,0xc0733ab5041feb0f,1 +np.float64,0x3fe26e872be4dd0e,0xbfceaaab466f32e0,1 +np.float64,0x3fefd9e290bfb3c5,0xbf60977d24496295,1 +np.float64,0x7fe19c5f692338be,0x40733fecef53ad95,1 +np.float64,0x3fe80365ab3006cb,0xbfbfec4090ef76ec,1 +np.float64,0x3fe88ab39eb11567,0xbfbd8099388d054d,1 +np.float64,0x3fe68fb09fad1f61,0xbfc36db9de38c2c0,1 +np.float64,0x3fe9051883b20a31,0xbfbb5b75b8cb8f24,1 +np.float64,0x3fd4708683a8e10d,0xbfdfb9b085dd8a83,1 +np.float64,0x3fe00ac11a601582,0xbfd3316af3e43500,1 +np.float64,0xd16af30ba2d5f,0xc0733bd68e8252f9,1 +np.float64,0x3fb97d654632facb,0xbff007ac1257f575,1 +np.float64,0x7fd637c10fac6f81,0x40733cb949d76546,1 +np.float64,0x7fed2cab6dba5956,0x4073436edfc3764e,1 +np.float64,0x3fed04afbbba095f,0xbfa5bfaa5074b7f4,1 +np.float64,0x0,0xfff0000000000000,1 +np.float64,0x389a1dc671345,0xc07344edd4206338,1 +np.float64,0x3fbc9ba25a393745,0xbfee74c34f49b921,1 +np.float64,0x3feee749947dce93,0xbf8f032d9cf6b5ae,1 +np.float64,0xedc4cf89db89a,0xc0733af4b2a57920,1 +np.float64,0x3fe41629eba82c54,0xbfc9e321faf79e1c,1 +np.float64,0x3feb0bcbf7b61798,0xbfb2b31e5d952869,1 +np.float64,0xad60654b5ac0d,0xc0733d26860df676,1 +np.float64,0x3fe154e1ff22a9c4,0xbfd10b416e58c867,1 +np.float64,0x7fb20e9c8a241d38,0x407331a66453b8bc,1 +np.float64,0x7fcbbaaf7d37755e,0x4073397274f28008,1 +np.float64,0x187d0fbc30fa3,0xc0734ac03cc98cc9,1 +np.float64,0x7fd153afeaa2a75f,0x40733aff00b4311d,1 +np.float64,0x3fe05310a5e0a621,0xbfd2b5386aeecaac,1 +np.float64,0x7fea863b2b750c75,0x407342c57807f700,1 +np.float64,0x3fed5f0c633abe19,0xbfa30f6cfbc4bf94,1 +np.float64,0xf227c8b3e44f9,0xc0733ad42daaec9f,1 +np.float64,0x3fe956524772aca5,0xbfb9f4cabed7081d,1 +np.float64,0xefd11af7dfa24,0xc0733ae570ed2552,1 +np.float64,0x1690fff02d221,0xc0734b51a56c2980,1 +np.float64,0x7fd2e547a825ca8e,0x40733b992d6d9635,1 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-log1p.csv b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-log1p.csv new file mode 100644 index 0000000000000000000000000000000000000000..094e052a5070637a264cbb2f4a722f9fbf2e4906 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-log1p.csv @@ -0,0 +1,1429 @@ +dtype,input,output,ulperrortol +np.float32,0x3e10aca8,0x3e075347,2 +np.float32,0x3f776e66,0x3f2d2003,2 +np.float32,0xbf34e8ce,0xbf9cfd5c,2 +np.float32,0xbf0260ee,0xbf363f69,2 +np.float32,0x3ed285e8,0x3eb05870,2 +np.float32,0x262b88,0x262b88,2 +np.float32,0x3eeffd6c,0x3ec4cfdb,2 +np.float32,0x3ee86808,0x3ebf9f54,2 +np.float32,0x3f36eba8,0x3f0a0524,2 +np.float32,0xbf1c047a,0xbf70afc7,2 +np.float32,0x3ead2916,0x3e952902,2 +np.float32,0x61c9c9,0x61c9c9,2 +np.float32,0xff7fffff,0xffc00000,2 +np.float32,0x7f64ee52,0x42b138e0,2 +np.float32,0x7ed00b1e,0x42afa4ff,2 +np.float32,0x3db53340,0x3dada0b2,2 +np.float32,0x3e6b0a4a,0x3e5397a4,2 +np.float32,0x7ed5d64f,0x42afb310,2 +np.float32,0xbf12bc5f,0xbf59f5ee,2 +np.float32,0xbda12710,0xbda7d8b5,2 +np.float32,0xbe2e89d8,0xbe3f5a9f,2 +np.float32,0x3f5bee75,0x3f1ebea4,2 +np.float32,0x9317a,0x9317a,2 +np.float32,0x7ee00130,0x42afcad8,2 +np.float32,0x7ef0d16d,0x42afefe7,2 +np.float32,0xbec7463a,0xbefc6a44,2 +np.float32,0xbf760ecc,0xc04fe59c,2 +np.float32,0xbecacb3c,0xbf011ae3,2 +np.float32,0x3ead92be,0x3e9577f0,2 +np.float32,0xbf41510d,0xbfb41b3a,2 +np.float32,0x7f71d489,0x42b154f1,2 +np.float32,0x8023bcd5,0x8023bcd5,2 +np.float32,0x801d33d8,0x801d33d8,2 +np.float32,0x3f3f545d,0x3f0ee0d4,2 +np.float32,0xbf700682,0xc0318c25,2 +np.float32,0xbe54e990,0xbe6eb0a3,2 +np.float32,0x7f0289bf,0x42b01941,2 +np.float32,0xbd61ac90,0xbd682113,2 +np.float32,0xbf2ff310,0xbf94cd6f,2 +np.float32,0x7f10064a,0x42b04b98,2 +np.float32,0x804d0d6d,0x804d0d6d,2 +np.float32,0x80317b0a,0x80317b0a,2 +np.float32,0xbddfef18,0xbded2640,2 +np.float32,0x3f00c9ab,0x3ed0a5bd,2 +np.float32,0x7f04b905,0x42b021c1,2 +np.float32,0x7fc00000,0x7fc00000,2 +np.float32,0x6524c4,0x6524c4,2 +np.float32,0x3da08ae0,0x3d9a8f88,2 +np.float32,0x293ea9,0x293ea9,2 +np.float32,0x71499e,0x71499e,2 +np.float32,0xbf14f54d,0xbf5f38a5,2 +np.float32,0x806e60f5,0x806e60f5,2 +np.float32,0x3f5f34bb,0x3f207fff,2 +np.float32,0x80513427,0x80513427,2 +np.float32,0x7f379670,0x42b0c7dc,2 +np.float32,0x3efba888,0x3eccb20b,2 +np.float32,0x3eeadd1b,0x3ec14f4b,2 +np.float32,0x7ec5a27f,0x42af8ab8,2 +np.float32,0x3f2afe4e,0x3f02f7a2,2 +np.float32,0x5591c8,0x5591c8,2 +np.float32,0x3dbb7240,0x3db35bab,2 +np.float32,0x805b911b,0x805b911b,2 +np.float32,0x800000,0x800000,2 +np.float32,0x7e784c04,0x42ae9cab,2 +np.float32,0x7ebaae14,0x42af6d86,2 +np.float32,0xbec84f7a,0xbefe1d42,2 +np.float32,0x7cea8281,0x42aa56bf,2 +np.float32,0xbf542cf6,0xbfe1eb1b,2 +np.float32,0xbf6bfb13,0xc0231a5b,2 +np.float32,0x7d6eeaef,0x42abc32c,2 +np.float32,0xbf062f6b,0xbf3e2000,2 +np.float32,0x8073d8e9,0x8073d8e9,2 +np.float32,0xbea4db14,0xbec6f485,2 +np.float32,0x7d7e8d62,0x42abe3a0,2 +np.float32,0x7e8fc34e,0x42aee7c6,2 +np.float32,0x7dcbb0c3,0x42acd464,2 +np.float32,0x7e123c,0x7e123c,2 +np.float32,0x3d77af62,0x3d707c34,2 +np.float32,0x498cc8,0x498cc8,2 +np.float32,0x7f4e2206,0x42b1032a,2 +np.float32,0x3f734e0a,0x3f2b04a1,2 +np.float32,0x8053a9d0,0x8053a9d0,2 +np.float32,0xbe8a67e0,0xbea15be9,2 +np.float32,0xbf78e0ea,0xc065409e,2 +np.float32,0x352bdd,0x352bdd,2 +np.float32,0x3ee42be7,0x3ebcb38a,2 +np.float32,0x7f482d10,0x42b0f427,2 +np.float32,0xbf23155e,0xbf81b993,2 +np.float32,0x594920,0x594920,2 +np.float32,0x63f53f,0x63f53f,2 +np.float32,0x363592,0x363592,2 +np.float32,0x7dafbb78,0x42ac88cc,2 +np.float32,0x7f69516c,0x42b14298,2 +np.float32,0x3e1d5be2,0x3e126131,2 +np.float32,0x410c23,0x410c23,2 +np.float32,0x7ec9563c,0x42af9439,2 +np.float32,0xbedd3a0e,0xbf10d705,2 +np.float32,0x7f7c4f1f,0x42b16aa8,2 +np.float32,0xbe99b34e,0xbeb6c2d3,2 +np.float32,0x6cdc84,0x6cdc84,2 +np.float32,0x5b3bbe,0x5b3bbe,2 +np.float32,0x252178,0x252178,2 +np.float32,0x7d531865,0x42ab83c8,2 +np.float32,0xbf565b44,0xbfe873bf,2 +np.float32,0x5977ce,0x5977ce,2 +np.float32,0x588a58,0x588a58,2 +np.float32,0x3eae7054,0x3e961d51,2 +np.float32,0x725049,0x725049,2 +np.float32,0x7f2b9386,0x42b0a538,2 +np.float32,0xbe674714,0xbe831245,2 +np.float32,0x8044f0d8,0x8044f0d8,2 +np.float32,0x800a3c21,0x800a3c21,2 +np.float32,0x807b275b,0x807b275b,2 +np.float32,0xbf2463b6,0xbf83896e,2 +np.float32,0x801cca42,0x801cca42,2 +np.float32,0xbf28f2d0,0xbf8a121a,2 +np.float32,0x3f4168c2,0x3f1010ce,2 +np.float32,0x6f91a1,0x6f91a1,2 +np.float32,0xbf2b9eeb,0xbf8e0fc5,2 +np.float32,0xbea4c858,0xbec6d8e4,2 +np.float32,0xbf7abba0,0xc0788e88,2 +np.float32,0x802f18f7,0x802f18f7,2 +np.float32,0xbf7f6c75,0xc0c3145c,2 +np.float32,0xbe988210,0xbeb50f5e,2 +np.float32,0xbf219b7e,0xbf7f6a3b,2 +np.float32,0x7f800000,0x7f800000,2 +np.float32,0x7f7fffff,0x42b17218,2 +np.float32,0xbdca8d90,0xbdd5487e,2 +np.float32,0xbef683b0,0xbf2821b0,2 +np.float32,0x8043e648,0x8043e648,2 +np.float32,0xbf4319a4,0xbfb7cd1b,2 +np.float32,0x62c2b2,0x62c2b2,2 +np.float32,0xbf479ccd,0xbfc1a7b1,2 +np.float32,0x806c8a32,0x806c8a32,2 +np.float32,0x7f004447,0x42b01045,2 +np.float32,0x3f737d36,0x3f2b1ccf,2 +np.float32,0x3ee71f24,0x3ebebced,2 +np.float32,0x3ea0b6b4,0x3e8bc606,2 +np.float32,0x358fd7,0x358fd7,2 +np.float32,0xbe69780c,0xbe847d17,2 +np.float32,0x7f6bed18,0x42b14849,2 +np.float32,0xbf6a5113,0xc01dfe1d,2 +np.float32,0xbf255693,0xbf84de88,2 +np.float32,0x7f34acac,0x42b0bfac,2 +np.float32,0xbe8a3b6a,0xbea11efe,2 +np.float32,0x3f470d84,0x3f1342ab,2 +np.float32,0xbf2cbde3,0xbf8fc602,2 +np.float32,0x47c103,0x47c103,2 +np.float32,0xe3c94,0xe3c94,2 +np.float32,0xbec07afa,0xbef1693a,2 +np.float32,0x6a9cfe,0x6a9cfe,2 +np.float32,0xbe4339e0,0xbe5899da,2 +np.float32,0x7ea9bf1e,0x42af3cd6,2 +np.float32,0x3f6378b4,0x3f22c4c4,2 +np.float32,0xbd989ff0,0xbd9e9c77,2 +np.float32,0xbe6f2f50,0xbe88343d,2 +np.float32,0x3f7f2ac5,0x3f310764,2 +np.float32,0x3f256704,0x3eff2fb2,2 +np.float32,0x80786aca,0x80786aca,2 +np.float32,0x65d02f,0x65d02f,2 +np.float32,0x50d1c3,0x50d1c3,2 +np.float32,0x3f4a9d76,0x3f1541b4,2 +np.float32,0x802cf491,0x802cf491,2 +np.float32,0x3e935cec,0x3e81829b,2 +np.float32,0x3e2ad478,0x3e1dfd81,2 +np.float32,0xbf107cbd,0xbf54bef2,2 +np.float32,0xbf58c02e,0xbff007fe,2 +np.float32,0x80090808,0x80090808,2 +np.float32,0x805d1f66,0x805d1f66,2 +np.float32,0x6aec95,0x6aec95,2 +np.float32,0xbee3fc6e,0xbf16dc73,2 +np.float32,0x7f63314b,0x42b134f9,2 +np.float32,0x550443,0x550443,2 +np.float32,0xbefa8174,0xbf2c026e,2 +np.float32,0x3f7fb380,0x3f314bd5,2 +np.float32,0x80171f2c,0x80171f2c,2 +np.float32,0x3f2f56ae,0x3f058f2d,2 +np.float32,0x3eacaecb,0x3e94cd97,2 +np.float32,0xbe0c4f0c,0xbe16e69d,2 +np.float32,0x3f48e4cb,0x3f144b42,2 +np.float32,0x7f03efe2,0x42b01eb7,2 +np.float32,0xbf1019ac,0xbf53dbe9,2 +np.float32,0x3e958524,0x3e832eb5,2 +np.float32,0xbf1b23c6,0xbf6e72f2,2 +np.float32,0x12c554,0x12c554,2 +np.float32,0x7dee588c,0x42ad24d6,2 +np.float32,0xbe8c216c,0xbea3ba70,2 +np.float32,0x804553cb,0x804553cb,2 +np.float32,0xbe446324,0xbe5a0966,2 +np.float32,0xbef7150a,0xbf28adff,2 +np.float32,0xbf087282,0xbf42ec6e,2 +np.float32,0x3eeef15c,0x3ec41937,2 +np.float32,0x61bbd2,0x61bbd2,2 +np.float32,0x3e51b28d,0x3e3ec538,2 +np.float32,0x57e869,0x57e869,2 +np.float32,0x7e5e7711,0x42ae646c,2 +np.float32,0x8050b173,0x8050b173,2 +np.float32,0xbf63c90c,0xc00d2438,2 +np.float32,0xbeba774c,0xbee7dcf8,2 +np.float32,0x8016faac,0x8016faac,2 +np.float32,0xbe8b448c,0xbea28aaf,2 +np.float32,0x3e8cd448,0x3e78d29e,2 +np.float32,0x80484e02,0x80484e02,2 +np.float32,0x3f63ba68,0x3f22e78c,2 +np.float32,0x2e87bb,0x2e87bb,2 +np.float32,0x230496,0x230496,2 +np.float32,0x1327b2,0x1327b2,2 +np.float32,0xbf046c56,0xbf3a72d2,2 +np.float32,0x3ecefe60,0x3eadd69a,2 +np.float32,0x49c56e,0x49c56e,2 +np.float32,0x3df22d60,0x3de4e550,2 +np.float32,0x3f67c19d,0x3f250707,2 +np.float32,0x3f20eb9c,0x3ef9b624,2 +np.float32,0x3f05ca75,0x3ed742fa,2 +np.float32,0xbe8514f8,0xbe9a1d45,2 +np.float32,0x8070a003,0x8070a003,2 +np.float32,0x7e49650e,0x42ae317a,2 +np.float32,0x3de16ce9,0x3dd5dc3e,2 +np.float32,0xbf4ae952,0xbfc95f1f,2 +np.float32,0xbe44dd84,0xbe5aa0db,2 +np.float32,0x803c3bc0,0x803c3bc0,2 +np.float32,0x3eebb9e8,0x3ec1e692,2 +np.float32,0x80588275,0x80588275,2 +np.float32,0xbea1e69a,0xbec29d86,2 +np.float32,0x3f7b4bf8,0x3f2f154c,2 +np.float32,0x7eb47ecc,0x42af5c46,2 +np.float32,0x3d441e00,0x3d3f911a,2 +np.float32,0x7f54d40e,0x42b11388,2 +np.float32,0xbf47f17e,0xbfc26882,2 +np.float32,0x3ea7da57,0x3e912db4,2 +np.float32,0x3f59cc7b,0x3f1d984e,2 +np.float32,0x570e08,0x570e08,2 +np.float32,0x3e99560c,0x3e8620a2,2 +np.float32,0x3ecfbd14,0x3eae5e55,2 +np.float32,0x7e86be08,0x42aec698,2 +np.float32,0x3f10f28a,0x3ee5b5d3,2 +np.float32,0x7f228722,0x42b0897a,2 +np.float32,0x3f4b979b,0x3f15cd30,2 +np.float32,0xbf134283,0xbf5b30f9,2 +np.float32,0x3f2ae16a,0x3f02e64f,2 +np.float32,0x3e98e158,0x3e85c6cc,2 +np.float32,0x7ec39f27,0x42af857a,2 +np.float32,0x3effedb0,0x3ecf8cea,2 +np.float32,0xbd545620,0xbd5a09c1,2 +np.float32,0x503a28,0x503a28,2 +np.float32,0x3f712744,0x3f29e9a1,2 +np.float32,0x3edc6194,0x3eb748b1,2 +np.float32,0xbf4ec1e5,0xbfd2ff5f,2 +np.float32,0x3f46669e,0x3f12e4b5,2 +np.float32,0xabad3,0xabad3,2 +np.float32,0x80000000,0x80000000,2 +np.float32,0x803f2e6d,0x803f2e6d,2 +np.float32,0xbf431542,0xbfb7c3e6,2 +np.float32,0x3f6f2d53,0x3f28e496,2 +np.float32,0x546bd8,0x546bd8,2 +np.float32,0x25c80a,0x25c80a,2 +np.float32,0x3e50883c,0x3e3dcd7e,2 +np.float32,0xbf5fa2ba,0xc0045c14,2 +np.float32,0x80271c07,0x80271c07,2 +np.float32,0x8043755d,0x8043755d,2 +np.float32,0xbf3c5cea,0xbfaa5ee9,2 +np.float32,0x3f2fea38,0x3f05e6af,2 +np.float32,0x6da3dc,0x6da3dc,2 +np.float32,0xbf095945,0xbf44dc70,2 +np.float32,0xbe33d584,0xbe45c1f5,2 +np.float32,0x7eb41b2e,0x42af5b2b,2 +np.float32,0xbf0feb74,0xbf537242,2 +np.float32,0xbe96225a,0xbeb1b0b1,2 +np.float32,0x3f63b95f,0x3f22e700,2 +np.float32,0x0,0x0,2 +np.float32,0x3e20b0cc,0x3e154374,2 +np.float32,0xbf79880c,0xc06b6801,2 +np.float32,0xbea690b6,0xbec97b93,2 +np.float32,0xbf3e11ca,0xbfada449,2 +np.float32,0x7e7e6292,0x42aea912,2 +np.float32,0x3e793350,0x3e5f0b7b,2 +np.float32,0x802e7183,0x802e7183,2 +np.float32,0x3f1b3695,0x3ef2a788,2 +np.float32,0x801efa20,0x801efa20,2 +np.float32,0x3f1ec43a,0x3ef70f42,2 +np.float32,0xbf12c5ed,0xbf5a0c52,2 +np.float32,0x8005e99c,0x8005e99c,2 +np.float32,0xbf79f5e7,0xc06fcca5,2 +np.float32,0x3ecbaf50,0x3eab7a03,2 +np.float32,0x46b0fd,0x46b0fd,2 +np.float32,0x3edb9023,0x3eb6b631,2 +np.float32,0x7f24bc41,0x42b09063,2 +np.float32,0xbd8d9328,0xbd92b4c6,2 +np.float32,0x3f2c5d7f,0x3f03c9d9,2 +np.float32,0x807bebc9,0x807bebc9,2 +np.float32,0x7f797a99,0x42b164e2,2 +np.float32,0x756e3c,0x756e3c,2 +np.float32,0x80416f8a,0x80416f8a,2 +np.float32,0x3e0d512a,0x3e04611a,2 +np.float32,0x3f7be3e6,0x3f2f61ec,2 +np.float32,0x80075c41,0x80075c41,2 +np.float32,0xbe850294,0xbe9a046c,2 +np.float32,0x684679,0x684679,2 +np.float32,0x3eb393c4,0x3e99eed2,2 +np.float32,0x3f4177c6,0x3f10195b,2 +np.float32,0x3dd1f402,0x3dc7dfe5,2 +np.float32,0x3ef484d4,0x3ec7e2e1,2 +np.float32,0x53eb8f,0x53eb8f,2 +np.float32,0x7f072cb6,0x42b02b20,2 +np.float32,0xbf1b6b55,0xbf6f28d4,2 +np.float32,0xbd8a98d8,0xbd8f827d,2 +np.float32,0x3eafb418,0x3e970e96,2 +np.float32,0x6555af,0x6555af,2 +np.float32,0x7dd5118e,0x42aceb6f,2 +np.float32,0x800a13f7,0x800a13f7,2 +np.float32,0x331a9d,0x331a9d,2 +np.float32,0x8063773f,0x8063773f,2 +np.float32,0x3e95e068,0x3e837553,2 +np.float32,0x80654b32,0x80654b32,2 +np.float32,0x3dabe0e0,0x3da50bb3,2 +np.float32,0xbf6283c3,0xc00a5280,2 +np.float32,0x80751cc5,0x80751cc5,2 +np.float32,0x3f668eb6,0x3f2465c0,2 +np.float32,0x3e13c058,0x3e0a048c,2 +np.float32,0x77780c,0x77780c,2 +np.float32,0x3f7d6e48,0x3f302868,2 +np.float32,0x7e31f9e3,0x42adf22f,2 +np.float32,0x246c7b,0x246c7b,2 +np.float32,0xbe915bf0,0xbeaafa6c,2 +np.float32,0xbf800000,0xff800000,2 +np.float32,0x3f698f42,0x3f25f8e0,2 +np.float32,0x7e698885,0x42ae7d48,2 +np.float32,0x3f5bbd42,0x3f1ea42c,2 +np.float32,0x5b8444,0x5b8444,2 +np.float32,0xbf6065f6,0xc005e2c6,2 +np.float32,0xbeb95036,0xbee60dad,2 +np.float32,0xbf44f846,0xbfbbcade,2 +np.float32,0xc96e5,0xc96e5,2 +np.float32,0xbf213e90,0xbf7e6eae,2 +np.float32,0xbeb309cc,0xbedc4fe6,2 +np.float32,0xbe781cf4,0xbe8e0fe6,2 +np.float32,0x7f0cf0db,0x42b04083,2 +np.float32,0xbf7b6143,0xc08078f9,2 +np.float32,0x80526fc6,0x80526fc6,2 +np.float32,0x3f092bf3,0x3edbaeec,2 +np.float32,0x3ecdf154,0x3ead16df,2 +np.float32,0x2fe85b,0x2fe85b,2 +np.float32,0xbf5100a0,0xbfd8f871,2 +np.float32,0xbec09d40,0xbef1a028,2 +np.float32,0x5e6a85,0x5e6a85,2 +np.float32,0xbec0e2a0,0xbef20f6b,2 +np.float32,0x3f72e788,0x3f2ad00d,2 +np.float32,0x880a6,0x880a6,2 +np.float32,0x3d9e90bf,0x3d98b9fc,2 +np.float32,0x15cf25,0x15cf25,2 +np.float32,0x10171b,0x10171b,2 +np.float32,0x805cf1aa,0x805cf1aa,2 +np.float32,0x3f19bd36,0x3ef0d0d2,2 +np.float32,0x3ebe2bda,0x3ea1b774,2 +np.float32,0xbecd8192,0xbf035c49,2 +np.float32,0x3e2ce508,0x3e1fc21b,2 +np.float32,0x290f,0x290f,2 +np.float32,0x803b679f,0x803b679f,2 +np.float32,0x1,0x1,2 +np.float32,0x807a9c76,0x807a9c76,2 +np.float32,0xbf65fced,0xc01257f8,2 +np.float32,0x3f783414,0x3f2d8475,2 +np.float32,0x3f2d9d92,0x3f0488da,2 +np.float32,0xbddb5798,0xbde80018,2 +np.float32,0x3e91afb8,0x3e8034e7,2 +np.float32,0xbf1b775a,0xbf6f476d,2 +np.float32,0xbf73a32c,0xc041f3ba,2 +np.float32,0xbea39364,0xbec5121b,2 +np.float32,0x80375b94,0x80375b94,2 +np.float32,0x3f331252,0x3f07c3e9,2 +np.float32,0xbf285774,0xbf892e74,2 +np.float32,0x3e699bb8,0x3e526d55,2 +np.float32,0x3f08208a,0x3eda523a,2 +np.float32,0xbf42fb4a,0xbfb78d60,2 +np.float32,0x8029c894,0x8029c894,2 +np.float32,0x3e926c0c,0x3e80c76e,2 +np.float32,0x801e4715,0x801e4715,2 +np.float32,0x3e4b36d8,0x3e395ffd,2 +np.float32,0x8041556b,0x8041556b,2 +np.float32,0xbf2d99ba,0xbf9119bd,2 +np.float32,0x3ed83ea8,0x3eb46250,2 +np.float32,0xbe94a280,0xbeaf92b4,2 +np.float32,0x7f4c7a64,0x42b0ff0a,2 +np.float32,0x806d4022,0x806d4022,2 +np.float32,0xbed382f8,0xbf086d26,2 +np.float32,0x1846fe,0x1846fe,2 +np.float32,0xbe702558,0xbe88d4d8,2 +np.float32,0xbe650ee0,0xbe81a3cc,2 +np.float32,0x3ee9d088,0x3ec0970c,2 +np.float32,0x7f6d4498,0x42b14b30,2 +np.float32,0xbef9f9e6,0xbf2b7ddb,2 +np.float32,0xbf70c384,0xc0349370,2 +np.float32,0xbeff9e9e,0xbf3110c8,2 +np.float32,0xbef06372,0xbf224aa9,2 +np.float32,0xbf15a692,0xbf60e1fa,2 +np.float32,0x8058c117,0x8058c117,2 +np.float32,0xbd9f74b8,0xbda6017b,2 +np.float32,0x801bf130,0x801bf130,2 +np.float32,0x805da84c,0x805da84c,2 +np.float32,0xff800000,0xffc00000,2 +np.float32,0xbeb01de2,0xbed7d6d6,2 +np.float32,0x8077de08,0x8077de08,2 +np.float32,0x3e327668,0x3e2482c1,2 +np.float32,0xbe7add88,0xbe8fe1ab,2 +np.float32,0x805a3c2e,0x805a3c2e,2 +np.float32,0x80326a73,0x80326a73,2 +np.float32,0x800b8a34,0x800b8a34,2 +np.float32,0x8048c83a,0x8048c83a,2 +np.float32,0xbf3799d6,0xbfa1a975,2 +np.float32,0x807649c7,0x807649c7,2 +np.float32,0x3dfdbf90,0x3def3798,2 +np.float32,0xbf1b538a,0xbf6eec4c,2 +np.float32,0xbf1e5989,0xbf76baa0,2 +np.float32,0xc7a80,0xc7a80,2 +np.float32,0x8001be54,0x8001be54,2 +np.float32,0x3f435bbc,0x3f112c6d,2 +np.float32,0xbeabcff8,0xbed151d1,2 +np.float32,0x7de20c78,0x42ad09b7,2 +np.float32,0x3f0e6d2e,0x3ee27b1e,2 +np.float32,0xbf0cb352,0xbf4c3267,2 +np.float32,0x7f6ec06f,0x42b14e61,2 +np.float32,0x7f6fa8ef,0x42b15053,2 +np.float32,0xbf3d2a6a,0xbfabe623,2 +np.float32,0x7f077a4c,0x42b02c46,2 +np.float32,0xbf2a68dc,0xbf8c3cc4,2 +np.float32,0x802a5dbe,0x802a5dbe,2 +np.float32,0x807f631c,0x807f631c,2 +np.float32,0x3dc9b8,0x3dc9b8,2 +np.float32,0x3ebdc1b7,0x3ea16a0a,2 +np.float32,0x7ef29dab,0x42aff3b5,2 +np.float32,0x3e8ab1cc,0x3e757806,2 +np.float32,0x3f27e88e,0x3f011c6d,2 +np.float32,0x3cfd1455,0x3cf93fb5,2 +np.float32,0x7f7eebf5,0x42b16fef,2 +np.float32,0x3c9b2140,0x3c99ade9,2 +np.float32,0x7e928601,0x42aef183,2 +np.float32,0xbd7d2db0,0xbd82abae,2 +np.float32,0x3e6f0df3,0x3e56da20,2 +np.float32,0x7d36a2fc,0x42ab39a3,2 +np.float32,0xbf49d3a2,0xbfc6c859,2 +np.float32,0x7ee541d3,0x42afd6b6,2 +np.float32,0x80753dc0,0x80753dc0,2 +np.float32,0x3f4ce486,0x3f16865d,2 +np.float32,0x39e701,0x39e701,2 +np.float32,0x3f3d9ede,0x3f0de5fa,2 +np.float32,0x7fafb2,0x7fafb2,2 +np.float32,0x3e013fdc,0x3df37090,2 +np.float32,0x807b6a2c,0x807b6a2c,2 +np.float32,0xbe86800a,0xbe9c08c7,2 +np.float32,0x7f40f080,0x42b0e14d,2 +np.float32,0x7eef5afe,0x42afecc8,2 +np.float32,0x7ec30052,0x42af83da,2 +np.float32,0x3eacf768,0x3e9503e1,2 +np.float32,0x7f13ef0e,0x42b0594e,2 +np.float32,0x80419f4a,0x80419f4a,2 +np.float32,0xbf485932,0xbfc3562a,2 +np.float32,0xbe8a24d6,0xbea10011,2 +np.float32,0xbda791c0,0xbdaed2bc,2 +np.float32,0x3e9b5169,0x3e87a67d,2 +np.float32,0x807dd882,0x807dd882,2 +np.float32,0x7f40170e,0x42b0df0a,2 +np.float32,0x7f02f7f9,0x42b01af1,2 +np.float32,0x3ea38bf9,0x3e8decde,2 +np.float32,0x3e2e7ce8,0x3e211ed4,2 +np.float32,0x70a7a6,0x70a7a6,2 +np.float32,0x7d978592,0x42ac3ce7,2 +np.float32,0x804d12d0,0x804d12d0,2 +np.float32,0x80165dc8,0x80165dc8,2 +np.float32,0x80000001,0x80000001,2 +np.float32,0x3e325da0,0x3e246da6,2 +np.float32,0xbe063bb8,0xbe0fe281,2 +np.float32,0x160b8,0x160b8,2 +np.float32,0xbe5687a4,0xbe70bbef,2 +np.float32,0x7f11ab34,0x42b05168,2 +np.float32,0xc955c,0xc955c,2 +np.float32,0xbea0003a,0xbebfd826,2 +np.float32,0x3f7fbdd9,0x3f315102,2 +np.float32,0xbe61aefc,0xbe7ef121,2 +np.float32,0xbf1b9873,0xbf6f9bc3,2 +np.float32,0x3a6d14,0x3a6d14,2 +np.float32,0xbf1ad3b4,0xbf6da808,2 +np.float32,0x3ed2dd24,0x3eb0963d,2 +np.float32,0xbe81a4ca,0xbe957d52,2 +np.float32,0x7f1be3e9,0x42b07421,2 +np.float32,0x7f5ce943,0x42b1269e,2 +np.float32,0x7eebcbdf,0x42afe51d,2 +np.float32,0x807181b5,0x807181b5,2 +np.float32,0xbecb03ba,0xbf0149ad,2 +np.float32,0x42edb8,0x42edb8,2 +np.float32,0xbf3aeec8,0xbfa7b13f,2 +np.float32,0xbd0c4f00,0xbd0ec4a0,2 +np.float32,0x3e48d260,0x3e376070,2 +np.float32,0x1a9731,0x1a9731,2 +np.float32,0x7f323be4,0x42b0b8b5,2 +np.float32,0x1a327f,0x1a327f,2 +np.float32,0x17f1fc,0x17f1fc,2 +np.float32,0xbf2f4f9b,0xbf93c91a,2 +np.float32,0x3ede8934,0x3eb8c9c3,2 +np.float32,0xbf56aaac,0xbfe968bb,2 +np.float32,0x3e22cb5a,0x3e17148c,2 +np.float32,0x7d9def,0x7d9def,2 +np.float32,0x8045b963,0x8045b963,2 +np.float32,0x77404f,0x77404f,2 +np.float32,0x7e2c9efb,0x42ade28b,2 +np.float32,0x8058ad89,0x8058ad89,2 +np.float32,0x7f4139,0x7f4139,2 +np.float32,0x8020e12a,0x8020e12a,2 +np.float32,0x800c9daa,0x800c9daa,2 +np.float32,0x7f2c5ac5,0x42b0a789,2 +np.float32,0x3f04a47b,0x3ed5c043,2 +np.float32,0x804692d5,0x804692d5,2 +np.float32,0xbf6e7fa4,0xc02bb493,2 +np.float32,0x80330756,0x80330756,2 +np.float32,0x7f3e29ad,0x42b0d9e1,2 +np.float32,0xbebf689a,0xbeefb24d,2 +np.float32,0x3f29a86c,0x3f022a56,2 +np.float32,0x3e3bd1c0,0x3e2c72b3,2 +np.float32,0x3f78f2e8,0x3f2de546,2 +np.float32,0x3f3709be,0x3f0a16af,2 +np.float32,0x3e11f150,0x3e086f97,2 +np.float32,0xbf5867ad,0xbfeee8a0,2 +np.float32,0xbebfb328,0xbef0296c,2 +np.float32,0x2f7f15,0x2f7f15,2 +np.float32,0x805cfe84,0x805cfe84,2 +np.float32,0xbf504e01,0xbfd71589,2 +np.float32,0x3ee0903c,0x3eba330c,2 +np.float32,0xbd838990,0xbd87f399,2 +np.float32,0x3f14444e,0x3ee9ee7d,2 +np.float32,0x7e352583,0x42adfb3a,2 +np.float32,0x7e76f824,0x42ae99ec,2 +np.float32,0x3f772d00,0x3f2cfebf,2 +np.float32,0x801f7763,0x801f7763,2 +np.float32,0x3f760bf5,0x3f2c6b87,2 +np.float32,0xbf0bb696,0xbf4a03a5,2 +np.float32,0x3f175d2c,0x3eedd6d2,2 +np.float32,0xbf5723f8,0xbfeae288,2 +np.float32,0x24de0a,0x24de0a,2 +np.float32,0x3cd73f80,0x3cd47801,2 +np.float32,0x7f013305,0x42b013fa,2 +np.float32,0x3e3ad425,0x3e2b9c50,2 +np.float32,0x7d3d16,0x7d3d16,2 +np.float32,0x3ef49738,0x3ec7ef54,2 +np.float32,0x3f5b8612,0x3f1e8678,2 +np.float32,0x7f0eeb5c,0x42b047a7,2 +np.float32,0x7e9d7cb0,0x42af1675,2 +np.float32,0xbdd1cfb0,0xbddd5aa0,2 +np.float32,0xbf645dba,0xc00e78fe,2 +np.float32,0x3f511174,0x3f18d56c,2 +np.float32,0x3d91ad00,0x3d8cba62,2 +np.float32,0x805298da,0x805298da,2 +np.float32,0xbedb6af4,0xbf0f4090,2 +np.float32,0x3d23b1ba,0x3d208205,2 +np.float32,0xbea5783e,0xbec7dc87,2 +np.float32,0x79d191,0x79d191,2 +np.float32,0x3e894413,0x3e7337da,2 +np.float32,0x80800000,0x80800000,2 +np.float32,0xbf34a8d3,0xbf9c907b,2 +np.float32,0x3bae779a,0x3bae011f,2 +np.float32,0x8049284d,0x8049284d,2 +np.float32,0x3eb42cc4,0x3e9a600b,2 +np.float32,0x3da1e2d0,0x3d9bce5f,2 +np.float32,0x3f364b8a,0x3f09a7af,2 +np.float32,0x3d930b10,0x3d8e0118,2 +np.float32,0x8061f8d7,0x8061f8d7,2 +np.float32,0x3f473213,0x3f13573b,2 +np.float32,0x3f1e2a38,0x3ef65102,2 +np.float32,0x8068f7d9,0x8068f7d9,2 +np.float32,0x3f181ef8,0x3eeeca2c,2 +np.float32,0x3eeb6168,0x3ec1a9f5,2 +np.float32,0xc2db6,0xc2db6,2 +np.float32,0x3ef7b578,0x3eca0a69,2 +np.float32,0xbf5b5a84,0xbff8d075,2 +np.float32,0x7f479d5f,0x42b0f2b7,2 +np.float32,0x3e6f3c24,0x3e56ff92,2 +np.float32,0x3f45543a,0x3f1249f0,2 +np.float32,0xbea7c1fa,0xbecb40d2,2 +np.float32,0x7de082,0x7de082,2 +np.float32,0x383729,0x383729,2 +np.float32,0xbd91cb90,0xbd973eb3,2 +np.float32,0x7f320218,0x42b0b80f,2 +np.float32,0x5547f2,0x5547f2,2 +np.float32,0x291fe4,0x291fe4,2 +np.float32,0xbe078ba0,0xbe11655f,2 +np.float32,0x7e0c0658,0x42ad7764,2 +np.float32,0x7e129a2b,0x42ad8ee5,2 +np.float32,0x3f7c96d4,0x3f2fbc0c,2 +np.float32,0x3f800000,0x3f317218,2 +np.float32,0x7f131754,0x42b05662,2 +np.float32,0x15f833,0x15f833,2 +np.float32,0x80392ced,0x80392ced,2 +np.float32,0x3f7c141a,0x3f2f7a36,2 +np.float32,0xbf71c03f,0xc038dcfd,2 +np.float32,0xbe14fb2c,0xbe20fff3,2 +np.float32,0xbee0bac6,0xbf13f14c,2 +np.float32,0x801a32dd,0x801a32dd,2 +np.float32,0x8e12d,0x8e12d,2 +np.float32,0x3f48c606,0x3f143a04,2 +np.float32,0x7f418af5,0x42b0e2e6,2 +np.float32,0x3f1f2918,0x3ef78bb7,2 +np.float32,0x11141b,0x11141b,2 +np.float32,0x3e9fc9e8,0x3e8b11ad,2 +np.float32,0xbea5447a,0xbec79010,2 +np.float32,0xbe31d904,0xbe4359db,2 +np.float32,0x80184667,0x80184667,2 +np.float32,0xbf00503c,0xbf3212c2,2 +np.float32,0x3e0328cf,0x3df6d425,2 +np.float32,0x7ee8e1b7,0x42afdebe,2 +np.float32,0xbef95e24,0xbf2ae5db,2 +np.float32,0x7f3e4eed,0x42b0da45,2 +np.float32,0x3f43ee85,0x3f117fa0,2 +np.float32,0xbcfa2ac0,0xbcfe10fe,2 +np.float32,0x80162774,0x80162774,2 +np.float32,0x372e8b,0x372e8b,2 +np.float32,0x3f263802,0x3f0016b0,2 +np.float32,0x8008725f,0x8008725f,2 +np.float32,0x800beb40,0x800beb40,2 +np.float32,0xbe93308e,0xbead8a77,2 +np.float32,0x3d8a4240,0x3d85cab8,2 +np.float32,0x80179de0,0x80179de0,2 +np.float32,0x7f4a98f2,0x42b0fa4f,2 +np.float32,0x3f0d214e,0x3ee0cff1,2 +np.float32,0x80536c2c,0x80536c2c,2 +np.float32,0x7e7038ed,0x42ae8bbe,2 +np.float32,0x7f345af9,0x42b0bec4,2 +np.float32,0xbf243219,0xbf83442f,2 +np.float32,0x7e0d5555,0x42ad7c27,2 +np.float32,0x762e95,0x762e95,2 +np.float32,0x7ebf4548,0x42af79f6,2 +np.float32,0x8079639e,0x8079639e,2 +np.float32,0x3ef925c0,0x3ecb0260,2 +np.float32,0x3f708695,0x3f2996d6,2 +np.float32,0xfca9f,0xfca9f,2 +np.float32,0x8060dbf4,0x8060dbf4,2 +np.float32,0x4c8840,0x4c8840,2 +np.float32,0xbea922ee,0xbecd4ed5,2 +np.float32,0xbf4f28a9,0xbfd40b98,2 +np.float32,0xbe25ad48,0xbe34ba1b,2 +np.float32,0x3f2fb254,0x3f05c58c,2 +np.float32,0x3f73bcc2,0x3f2b3d5f,2 +np.float32,0xbf479a07,0xbfc1a165,2 +np.float32,0xbeb9a808,0xbee69763,2 +np.float32,0x7eb16a65,0x42af5376,2 +np.float32,0xbeb3e442,0xbedda042,2 +np.float32,0x3d8f439c,0x3d8a79ac,2 +np.float32,0x80347516,0x80347516,2 +np.float32,0x3e8a0c5d,0x3e74738c,2 +np.float32,0xbf0383a4,0xbf389289,2 +np.float32,0x806be8f5,0x806be8f5,2 +np.float32,0x8023f0c5,0x8023f0c5,2 +np.float32,0x2060e9,0x2060e9,2 +np.float32,0xbf759eba,0xc04d239f,2 +np.float32,0x3d84cc5a,0x3d80ab96,2 +np.float32,0xbf57746b,0xbfebdf87,2 +np.float32,0x3e418417,0x3e31401f,2 +np.float32,0xaecce,0xaecce,2 +np.float32,0x3cd1766f,0x3cced45c,2 +np.float32,0x53724a,0x53724a,2 +np.float32,0x3f773710,0x3f2d03de,2 +np.float32,0x8013d040,0x8013d040,2 +np.float32,0x4d0eb2,0x4d0eb2,2 +np.float32,0x8014364a,0x8014364a,2 +np.float32,0x7f3c56c9,0x42b0d4f2,2 +np.float32,0x3eee1e1c,0x3ec3891a,2 +np.float32,0xbdda3eb8,0xbde6c5a0,2 +np.float32,0x26ef4a,0x26ef4a,2 +np.float32,0x7ed3370c,0x42afacbf,2 +np.float32,0xbf06e31b,0xbf3f9ab7,2 +np.float32,0xbe3185f0,0xbe42f556,2 +np.float32,0x3dcf9abe,0x3dc5be41,2 +np.float32,0xbf3696d9,0xbf9fe2bd,2 +np.float32,0x3e68ee50,0x3e51e01a,2 +np.float32,0x3f3d4cc2,0x3f0db6ca,2 +np.float32,0x7fa00000,0x7fe00000,2 +np.float32,0xbf03070c,0xbf3792d0,2 +np.float32,0x3ea79e6c,0x3e910092,2 +np.float32,0xbf1a393a,0xbf6c2251,2 +np.float32,0x3f41eb0e,0x3f105afc,2 +np.float32,0x3ceadb2f,0x3ce78d79,2 +np.float32,0xbf5dc105,0xc000be2c,2 +np.float32,0x7ebb5a0e,0x42af6f5c,2 +np.float32,0xbf7c44eb,0xc0875058,2 +np.float32,0x6aaaf4,0x6aaaf4,2 +np.float32,0x807d8f23,0x807d8f23,2 +np.float32,0xbee6b142,0xbf194fef,2 +np.float32,0xbe83f256,0xbe989526,2 +np.float32,0x7d588e,0x7d588e,2 +np.float32,0x7cc80131,0x42aa0542,2 +np.float32,0x3e0ab198,0x3e02124f,2 +np.float32,0xbf6e64db,0xc02b52eb,2 +np.float32,0x3d238b56,0x3d205d1b,2 +np.float32,0xbeb408e2,0xbeddd8bc,2 +np.float32,0x3f78340d,0x3f2d8471,2 +np.float32,0x806162a3,0x806162a3,2 +np.float32,0x804e484f,0x804e484f,2 +np.float32,0xbeb8c576,0xbee53466,2 +np.float32,0x807aab15,0x807aab15,2 +np.float32,0x3f523e20,0x3f197ab8,2 +np.float32,0xbf009190,0xbf3295de,2 +np.float32,0x3df43da5,0x3de6bd82,2 +np.float32,0x7f639aea,0x42b135e6,2 +np.float32,0x3f1e638a,0x3ef697da,2 +np.float32,0xbf4884de,0xbfc3bac3,2 +np.float32,0xbe9336b6,0xbead931b,2 +np.float32,0x6daf7f,0x6daf7f,2 +np.float32,0xbf1fc152,0xbf7a70b1,2 +np.float32,0x3f103720,0x3ee4c649,2 +np.float32,0x3eeaa227,0x3ec126df,2 +np.float32,0x7f7ea945,0x42b16f69,2 +np.float32,0x3d3cd800,0x3d389ead,2 +np.float32,0x3f3d7268,0x3f0dcc6e,2 +np.float32,0xbf3c1b41,0xbfa9e2e3,2 +np.float32,0x3ecf3818,0x3eadffb2,2 +np.float32,0x3f1af312,0x3ef25372,2 +np.float32,0x48fae4,0x48fae4,2 +np.float64,0x7fedaa1ee4fb543d,0x40862da7ca7c308e,1 +np.float64,0x8007d2d810efa5b1,0x8007d2d810efa5b1,1 +np.float64,0x3fc385e069270bc0,0x3fc22b8884cf2c3b,1 +np.float64,0x68ed4130d1da9,0x68ed4130d1da9,1 +np.float64,0x8008e93e58d1d27d,0x8008e93e58d1d27d,1 +np.float64,0xbfd3d62852a7ac50,0xbfd7be3a7ad1af02,1 +np.float64,0xbfc1fa0ba923f418,0xbfc35f0f19447df7,1 +np.float64,0xbfe01b8cec20371a,0xbfe6658c7e6c8e50,1 +np.float64,0xbfeda81a147b5034,0xc004e9c94f2b91c1,1 +np.float64,0xbfe1c36a97e386d5,0xbfe9ead4d6beaa92,1 +np.float64,0x3fe50be51f2a17ca,0x3fe02c8067d9e5c5,1 +np.float64,0x3febed4d3337da9a,0x3fe413956466134f,1 +np.float64,0x80068ea59ced1d4c,0x80068ea59ced1d4c,1 +np.float64,0x3febe77d5877cefb,0x3fe4107ac088bc71,1 +np.float64,0x800ae77617d5ceed,0x800ae77617d5ceed,1 +np.float64,0x3fd0546b60a0a8d7,0x3fcd16c2e995ab23,1 +np.float64,0xbfe33e1476667c29,0xbfed6d7faec4db2f,1 +np.float64,0x3fe9d2fd51b3a5fb,0x3fe2eef834310219,1 +np.float64,0x8004249878284932,0x8004249878284932,1 +np.float64,0xbfd5b485c72b690c,0xbfda828ccc6a7a5c,1 +np.float64,0x7fcd6e6b6b3adcd6,0x408622807f04768e,1 +np.float64,0x3fd7f9c32caff386,0x3fd45d024514b8da,1 +np.float64,0x7f87eb9d702fd73a,0x40860aa99fcff27f,1 +np.float64,0xbfc5d1f6fb2ba3ec,0xbfc7ec367cb3fecc,1 +np.float64,0x8008316a44d062d5,0x8008316a44d062d5,1 +np.float64,0xbfd54e4358aa9c86,0xbfd9e889d2998a4a,1 +np.float64,0xda65facdb4cc0,0xda65facdb4cc0,1 +np.float64,0x3fc5b4f6f32b69f0,0x3fc40d13aa8e248b,1 +np.float64,0x3fd825a5d5b04b4c,0x3fd47ce73e04d3ff,1 +np.float64,0x7ac9d56ef593b,0x7ac9d56ef593b,1 +np.float64,0xbfd0a51977214a32,0xbfd34702071428be,1 +np.float64,0x3fd21f620b243ec4,0x3fcfea0c02193640,1 +np.float64,0x3fe6fb3f1b2df67e,0x3fe151ffb18c983b,1 +np.float64,0x700de022e01bd,0x700de022e01bd,1 +np.float64,0xbfbb76b81236ed70,0xbfbd0d31deea1ec7,1 +np.float64,0x3fecfc3856f9f870,0x3fe4a2fcadf221e0,1 +np.float64,0x3fede286517bc50c,0x3fe51af2fbd6ef63,1 +np.float64,0x7fdc8da96c391b52,0x408627ce09cfef2b,1 +np.float64,0x8000edfcfb81dbfb,0x8000edfcfb81dbfb,1 +np.float64,0x8009ebc42af3d789,0x8009ebc42af3d789,1 +np.float64,0x7fd658aaf8acb155,0x408625d80cd1ccc9,1 +np.float64,0x3feea584a37d4b09,0x3fe57f29a73729cd,1 +np.float64,0x4cfe494699fca,0x4cfe494699fca,1 +np.float64,0xbfe9d96460b3b2c9,0xbffa62ecfa026c77,1 +np.float64,0x7fdb3852c3b670a5,0x4086276c191dc9b1,1 +np.float64,0xbfe4d1fc9ee9a3f9,0xbff0d37ce37cf479,1 +np.float64,0xffefffffffffffff,0xfff8000000000000,1 +np.float64,0xbfd1c43d7fa3887a,0xbfd4cfbefb5f2c43,1 +np.float64,0x3fec4a8e0d78951c,0x3fe4453a82ca2570,1 +np.float64,0x7fafed74583fdae8,0x4086181017b8dac9,1 +np.float64,0x80076c4ebcced89e,0x80076c4ebcced89e,1 +np.float64,0x8001a9aa7b235356,0x8001a9aa7b235356,1 +np.float64,0x121260fe2424d,0x121260fe2424d,1 +np.float64,0x3fddd028e3bba052,0x3fd87998c4c43c5b,1 +np.float64,0x800ed1cf4a9da39f,0x800ed1cf4a9da39f,1 +np.float64,0xbfef2e63d7fe5cc8,0xc00d53480b16971b,1 +np.float64,0xbfedde3309fbbc66,0xc005ab55b7a7c127,1 +np.float64,0x3fda3e1e85b47c3d,0x3fd5fddafd8d6729,1 +np.float64,0x8007c6443c6f8c89,0x8007c6443c6f8c89,1 +np.float64,0xbfe101705f2202e0,0xbfe8420817665121,1 +np.float64,0x7fe0bff3c1e17fe7,0x4086291539c56d80,1 +np.float64,0x7fe6001dab6c003a,0x40862b43aa7cb060,1 +np.float64,0x7fbdecf7de3bd9ef,0x40861d170b1c51a5,1 +np.float64,0xbfc0fd508c21faa0,0xbfc23a5876e99fa3,1 +np.float64,0xbfcf6eb14f3edd64,0xbfd208cbf742c8ea,1 +np.float64,0x3f6d40ea403a81d5,0x3f6d33934ab8e799,1 +np.float64,0x7fc32600b6264c00,0x40861f10302357e0,1 +np.float64,0x3fd05870baa0b0e0,0x3fcd1d2af420fac7,1 +np.float64,0x80051d5120aa3aa3,0x80051d5120aa3aa3,1 +np.float64,0x3fdb783fcfb6f080,0x3fd6db229658c083,1 +np.float64,0x3fe0b61199e16c24,0x3fdae41e277be2eb,1 +np.float64,0x3daf62167b5ed,0x3daf62167b5ed,1 +np.float64,0xbfec3c53b6f878a7,0xc0011f0ce7a78a2a,1 +np.float64,0x800fc905161f920a,0x800fc905161f920a,1 +np.float64,0x3fdc7b9cc138f73a,0x3fd78f9c2360e661,1 +np.float64,0x7fe4079e97a80f3c,0x40862a83795f2443,1 +np.float64,0x8010000000000000,0x8010000000000000,1 +np.float64,0x7fe6da5345adb4a6,0x40862b9183c1e4b0,1 +np.float64,0xbfd0a76667214ecc,0xbfd34a1e0c1f6186,1 +np.float64,0x37fb0b906ff62,0x37fb0b906ff62,1 +np.float64,0x7fe170e59fa2e1ca,0x408629680a55e5c5,1 +np.float64,0x3fea900c77752019,0x3fe356eec75aa345,1 +np.float64,0x3fc575c63a2aeb8c,0x3fc3d701167d76b5,1 +np.float64,0x3fe8b45da87168bc,0x3fe24ecbb778fd44,1 +np.float64,0xbfcb990ab5373214,0xbfcf1596c076813c,1 +np.float64,0xf146fdfbe28e0,0xf146fdfbe28e0,1 +np.float64,0x8001fcd474c3f9aa,0x8001fcd474c3f9aa,1 +np.float64,0xbfe9b555eeb36aac,0xbffa0630c3bb485b,1 +np.float64,0x800f950be83f2a18,0x800f950be83f2a18,1 +np.float64,0x7feb0e03ab761c06,0x40862ceb30e36887,1 +np.float64,0x7fca51bd4a34a37a,0x4086219b9dfd35c9,1 +np.float64,0xbfdc27c34cb84f86,0xbfe28ccde8d6bc08,1 +np.float64,0x80009ce1714139c4,0x80009ce1714139c4,1 +np.float64,0x8005290fb1ea5220,0x8005290fb1ea5220,1 +np.float64,0xbfee81e6473d03cd,0xc00885972ca1699b,1 +np.float64,0x7fcfb11a373f6233,0x408623180b8f75d9,1 +np.float64,0xbfcb9c4bfd373898,0xbfcf19bd25881928,1 +np.float64,0x7feaec5885f5d8b0,0x40862ce136050e6c,1 +np.float64,0x8009e17a4a53c2f5,0x8009e17a4a53c2f5,1 +np.float64,0xbfe1cceb9e6399d7,0xbfea0038bd3def20,1 +np.float64,0x8009170bd7122e18,0x8009170bd7122e18,1 +np.float64,0xb2b6f7f1656df,0xb2b6f7f1656df,1 +np.float64,0x3fc75bfd1f2eb7f8,0x3fc574c858332265,1 +np.float64,0x3fa24c06ec249800,0x3fa1fa462ffcb8ec,1 +np.float64,0xaa9a4d2d5534a,0xaa9a4d2d5534a,1 +np.float64,0xbfd7b76208af6ec4,0xbfdda0c3200dcc9f,1 +np.float64,0x7f8cbab73039756d,0x40860c20cba57a94,1 +np.float64,0x3fdbcf9f48b79f3f,0x3fd71827a60e8b6d,1 +np.float64,0xbfdd60f71a3ac1ee,0xbfe3a94bc8cf134d,1 +np.float64,0xb9253589724a7,0xb9253589724a7,1 +np.float64,0xbfcf28e37e3e51c8,0xbfd1da9977b741e3,1 +np.float64,0x80011457f7e228b1,0x80011457f7e228b1,1 +np.float64,0x7fec33df737867be,0x40862d404a897122,1 +np.float64,0xae55f8f95cabf,0xae55f8f95cabf,1 +np.float64,0xbfc1ab9397235728,0xbfc303e5533d4a5f,1 +np.float64,0x7fef0f84b3be1f08,0x40862e05f9ba7118,1 +np.float64,0x7fdc94f328b929e5,0x408627d01449d825,1 +np.float64,0x3fee1b598c7c36b3,0x3fe53847be166834,1 +np.float64,0x3fee8326f37d064e,0x3fe56d96f3fbcf43,1 +np.float64,0x3fe7b18a83ef6316,0x3fe1bb6a6d48c675,1 +np.float64,0x3fe5db969c6bb72e,0x3fe0a8d7d151996c,1 +np.float64,0x3e3391d27c673,0x3e3391d27c673,1 +np.float64,0x3fe79a46d76f348e,0x3fe1ae09a96ea628,1 +np.float64,0x7ff4000000000000,0x7ffc000000000000,1 +np.float64,0x7fe57d6505aafac9,0x40862b13925547f1,1 +np.float64,0x3fc433371d28666e,0x3fc2c196a764c47b,1 +np.float64,0x8008dbf69cd1b7ee,0x8008dbf69cd1b7ee,1 +np.float64,0xbfe744f459ee89e8,0xbff4c847ad3ee152,1 +np.float64,0x80098aa245331545,0x80098aa245331545,1 +np.float64,0x6747112ece8e3,0x6747112ece8e3,1 +np.float64,0x5d342a40ba69,0x5d342a40ba69,1 +np.float64,0xf7a17739ef42f,0xf7a17739ef42f,1 +np.float64,0x3fe1b34a9d236695,0x3fdc2d7c4e2c347a,1 +np.float64,0x7fb53bf5ec2a77eb,0x40861a585ec8f7ff,1 +np.float64,0xbfe6256f1cec4ade,0xbff2d89a36be65ae,1 +np.float64,0xb783bc9b6f078,0xb783bc9b6f078,1 +np.float64,0xbfedf74a3bfbee94,0xc0060bb6f2bc11ef,1 +np.float64,0x3fda2a5eccb454be,0x3fd5efd7f18b8e81,1 +np.float64,0xbfb3838ab2270718,0xbfb44c337fbca3c3,1 +np.float64,0x3fb4ac6dc22958e0,0x3fb3e194ca01a502,1 +np.float64,0x76c11aaaed824,0x76c11aaaed824,1 +np.float64,0x80025bb1af04b764,0x80025bb1af04b764,1 +np.float64,0x3fdc02740ab804e8,0x3fd73b8cd6f95f19,1 +np.float64,0x3fe71856f5ee30ae,0x3fe162e9fafb4428,1 +np.float64,0x800236f332646de7,0x800236f332646de7,1 +np.float64,0x7fe13fd9d2e27fb3,0x408629516b42a317,1 +np.float64,0x7fdf6bbd34bed779,0x40862892069d805c,1 +np.float64,0x3fd4727beba8e4f8,0x3fd1be5b48d9e282,1 +np.float64,0x800e0fac9e5c1f59,0x800e0fac9e5c1f59,1 +np.float64,0xfb54423ff6a89,0xfb54423ff6a89,1 +np.float64,0x800fbf7ed47f7efe,0x800fbf7ed47f7efe,1 +np.float64,0x3fe9d41fa2f3a840,0x3fe2ef98dc1fd463,1 +np.float64,0x800d733e805ae67d,0x800d733e805ae67d,1 +np.float64,0x3feebe4c46fd7c98,0x3fe58bcf7f47264e,1 +np.float64,0x7fe1ab77b5e356ee,0x40862982bb3dce34,1 +np.float64,0xbfdddac05abbb580,0xbfe41aa45f72d5a2,1 +np.float64,0x3fe14219dee28434,0x3fdb9b137d1f1220,1 +np.float64,0x3fe25d3d5a24ba7b,0x3fdd06e1cf32d35a,1 +np.float64,0x8000fa4fbe81f4a0,0x8000fa4fbe81f4a0,1 +np.float64,0x3fe303e23e6607c4,0x3fddd94982efa9f1,1 +np.float64,0x3fe89cf5d83139ec,0x3fe24193a2e12f75,1 +np.float64,0x3fe9b36ef87366de,0x3fe2dd7cdc25a4a5,1 +np.float64,0xbfdb8b38f8371672,0xbfe2023ba7e002bb,1 +np.float64,0xafc354955f86b,0xafc354955f86b,1 +np.float64,0xbfe2f3d49e65e7a9,0xbfecb557a94123d3,1 +np.float64,0x800496617c092cc4,0x800496617c092cc4,1 +np.float64,0x32db0cfa65b62,0x32db0cfa65b62,1 +np.float64,0xbfd893bfa2b12780,0xbfdf02a8c1e545aa,1 +np.float64,0x7fd5ac927d2b5924,0x408625997e7c1f9b,1 +np.float64,0x3fde9defb8bd3be0,0x3fd9056190986349,1 +np.float64,0x80030cfeb54619fe,0x80030cfeb54619fe,1 +np.float64,0x3fcba85b273750b8,0x3fc90a5ca976594f,1 +np.float64,0x3fe98f6f5cf31edf,0x3fe2c97fcb4eca25,1 +np.float64,0x3fe33dbf90667b80,0x3fde21b83321b993,1 +np.float64,0x3fe4686636e8d0cc,0x3fdf928cdca751b3,1 +np.float64,0x80018ade6ce315be,0x80018ade6ce315be,1 +np.float64,0x7fa9af70c8335ee1,0x408616528cd5a906,1 +np.float64,0x3fbeb460aa3d68c0,0x3fbcff96b00a2193,1 +np.float64,0x7fa82c869830590c,0x408615d6598d9368,1 +np.float64,0xd08c0e6fa1182,0xd08c0e6fa1182,1 +np.float64,0x3fef4eb750fe9d6f,0x3fe5d522fd4e7f64,1 +np.float64,0xbfc586f5492b0dec,0xbfc791eaae92aad1,1 +np.float64,0x7fede64ac7bbcc95,0x40862db7f444fa7b,1 +np.float64,0x3fe540003d6a8000,0x3fe04bdfc2916a0b,1 +np.float64,0x8009417fe6f28300,0x8009417fe6f28300,1 +np.float64,0x3fe6959cf16d2b3a,0x3fe116a1ce01887b,1 +np.float64,0x3fb0a40036214800,0x3fb01f447778219a,1 +np.float64,0x3feff26e91ffe4dd,0x3fe627798fc859a7,1 +np.float64,0x7fed8e46cd7b1c8d,0x40862da044a1d102,1 +np.float64,0x7fec4eb774f89d6e,0x40862d47e43edb53,1 +np.float64,0x3fe800e5e07001cc,0x3fe1e8e2b9105fc2,1 +np.float64,0x800f4eb2f9be9d66,0x800f4eb2f9be9d66,1 +np.float64,0x800611659bcc22cc,0x800611659bcc22cc,1 +np.float64,0x3fd66e65d2acdccc,0x3fd33ad63a5e1000,1 +np.float64,0x800a9085b7f5210c,0x800a9085b7f5210c,1 +np.float64,0x7fdf933a3fbf2673,0x4086289c0e292f2b,1 +np.float64,0x1cd1ba7a39a38,0x1cd1ba7a39a38,1 +np.float64,0xbfefd0b10fffa162,0xc0149ded900ed851,1 +np.float64,0xbfe8c63485b18c69,0xbff7cf3078b1574f,1 +np.float64,0x3fecde56ca79bcae,0x3fe4934afbd7dda9,1 +np.float64,0x8006cd6888cd9ad2,0x8006cd6888cd9ad2,1 +np.float64,0x3fd7a391c2af4724,0x3fd41e2f74df2329,1 +np.float64,0x3fe6a8ad58ed515a,0x3fe121ccfb28e6f5,1 +np.float64,0x7fe18a80dd631501,0x40862973c09086b9,1 +np.float64,0xbf74fd6d8029fb00,0xbf750b3e368ebe6b,1 +np.float64,0x3fdd35e93dba6bd4,0x3fd810071faaffad,1 +np.float64,0x3feb0d8f57361b1f,0x3fe39b3abdef8b7a,1 +np.float64,0xbfd5ec7288abd8e6,0xbfdad764df0d2ca1,1 +np.float64,0x7fdc848272b90904,0x408627cb78f3fb9e,1 +np.float64,0x800ed3eda91da7db,0x800ed3eda91da7db,1 +np.float64,0x3fefac64857f58c9,0x3fe60459dbaad1ba,1 +np.float64,0x3fd1df7a5ba3bef4,0x3fcf864a39b926ff,1 +np.float64,0xfe26ca4bfc4da,0xfe26ca4bfc4da,1 +np.float64,0xbfd1099f8da21340,0xbfd3cf6e6efe934b,1 +np.float64,0xbfe15de9a7a2bbd4,0xbfe909cc895f8795,1 +np.float64,0x3fe89714ed712e2a,0x3fe23e40d31242a4,1 +np.float64,0x800387113e470e23,0x800387113e470e23,1 +np.float64,0x3fe4f80730e9f00e,0x3fe0208219314cf1,1 +np.float64,0x2f95a97c5f2b6,0x2f95a97c5f2b6,1 +np.float64,0x800ea7cdd87d4f9c,0x800ea7cdd87d4f9c,1 +np.float64,0xbf64b967c0297300,0xbf64c020a145b7a5,1 +np.float64,0xbfc5a91a342b5234,0xbfc7bafd77a61d81,1 +np.float64,0xbfe2226fe76444e0,0xbfeac33eb1d1b398,1 +np.float64,0x3fc6aaa8d42d5552,0x3fc4de79f5c68cd4,1 +np.float64,0x3fe54fd4c1ea9faa,0x3fe05561a9a5922b,1 +np.float64,0x80029c1f75653840,0x80029c1f75653840,1 +np.float64,0xbfcb4a84a2369508,0xbfceb1a23bac3995,1 +np.float64,0x80010abeff02157f,0x80010abeff02157f,1 +np.float64,0x7f92d12cf825a259,0x40860e49bde3a5b6,1 +np.float64,0x800933e7027267ce,0x800933e7027267ce,1 +np.float64,0x3fc022b12e204562,0x3fbe64acc53ed887,1 +np.float64,0xbfe35f938de6bf27,0xbfedc1f3e443c016,1 +np.float64,0x1f8d9bae3f1b4,0x1f8d9bae3f1b4,1 +np.float64,0x3fe552f22ceaa5e4,0x3fe057404072350f,1 +np.float64,0xbfa73753442e6ea0,0xbfa7c24a100190f1,1 +np.float64,0x7fb3e2982827c52f,0x408619d1efa676b6,1 +np.float64,0xbfd80cb7a5301970,0xbfde28e65f344f33,1 +np.float64,0xbfcde835973bd06c,0xbfd10806fba46c8f,1 +np.float64,0xbfd4e3c749a9c78e,0xbfd949aff65de39c,1 +np.float64,0x3fcb4b9d6f36973b,0x3fc8be02ad6dc0d3,1 +np.float64,0x1a63000034c7,0x1a63000034c7,1 +np.float64,0x7fdc9c751e3938e9,0x408627d22df71959,1 +np.float64,0x3fd74f3f712e9e7f,0x3fd3e07df0c37ec1,1 +np.float64,0xbfceab74d33d56e8,0xbfd187e99bf82903,1 +np.float64,0x7ff0000000000000,0x7ff0000000000000,1 +np.float64,0xbfb2cca466259948,0xbfb3868208e8de30,1 +np.float64,0x800204688b8408d2,0x800204688b8408d2,1 +np.float64,0x3e4547407c8aa,0x3e4547407c8aa,1 +np.float64,0xbfe4668846e8cd10,0xbff03c85189f3818,1 +np.float64,0x800dd350245ba6a0,0x800dd350245ba6a0,1 +np.float64,0xbfbc13c160382780,0xbfbdbd56ce996d16,1 +np.float64,0x7fe25a628a24b4c4,0x408629d06eb2d64d,1 +np.float64,0x3fd19dabbc233b57,0x3fcf1f3ed1d34c8c,1 +np.float64,0x547e20faa8fc5,0x547e20faa8fc5,1 +np.float64,0xbfe19392c6232726,0xbfe97ffe4f303335,1 +np.float64,0x3f87f9f6702ff400,0x3f87d64fb471bb04,1 +np.float64,0x9dfc52db3bf8b,0x9dfc52db3bf8b,1 +np.float64,0x800e1f5a9adc3eb5,0x800e1f5a9adc3eb5,1 +np.float64,0xbfddbd09c8bb7a14,0xbfe3fed7d7cffc70,1 +np.float64,0xbfeda71af87b4e36,0xc004e6631c514544,1 +np.float64,0xbfdbfcfe1bb7f9fc,0xbfe266b5d4a56265,1 +np.float64,0x3fe4ee78cd69dcf2,0x3fe01abba4e81fc9,1 +np.float64,0x800f13b820de2770,0x800f13b820de2770,1 +np.float64,0x3f861e09702c3c00,0x3f85ffae83b02c4f,1 +np.float64,0xbfc0972479212e48,0xbfc1c4bf70b30cbc,1 +np.float64,0x7fef057ef57e0afd,0x40862e036479f6a9,1 +np.float64,0x8bdbabe517b76,0x8bdbabe517b76,1 +np.float64,0xbfec495417f892a8,0xc0013ade88746d18,1 +np.float64,0x3fec680ab3f8d015,0x3fe454dd304b560d,1 +np.float64,0xbfae7ce60c3cf9d0,0xbfaf6eef15bbe56b,1 +np.float64,0x3fec314124786282,0x3fe437ca06294f5a,1 +np.float64,0x7fd5ed05b82bda0a,0x408625b125518e58,1 +np.float64,0x3feac9f02f3593e0,0x3fe3768104dd5cb7,1 +np.float64,0x0,0x0,1 +np.float64,0xbfddd2abd5bba558,0xbfe41312b8ea20de,1 +np.float64,0xbfedf9558c7bf2ab,0xc00613c53e0bb33a,1 +np.float64,0x3fef245ffefe48c0,0x3fe5bfb4dfe3b7a5,1 +np.float64,0x7fe178604922f0c0,0x4086296b77d5eaef,1 +np.float64,0x10000000000000,0x10000000000000,1 +np.float64,0x7fed026766ba04ce,0x40862d7a0dc45643,1 +np.float64,0xbfde27d8c3bc4fb2,0xbfe46336b6447697,1 +np.float64,0x3fe9485d9cb290bb,0x3fe2a1e4b6419423,1 +np.float64,0xbfe27b8a7464f715,0xbfeb9382f5b16f65,1 +np.float64,0x5c34d274b869b,0x5c34d274b869b,1 +np.float64,0xbfeee0b7453dc16f,0xc00acdb46459b6e6,1 +np.float64,0x7fe3dfb4d4e7bf69,0x40862a73785fdf12,1 +np.float64,0xb4635eef68c6c,0xb4635eef68c6c,1 +np.float64,0xbfe522a2c82a4546,0xbff148912a59a1d6,1 +np.float64,0x8009ba38a9737472,0x8009ba38a9737472,1 +np.float64,0xbfc056ff3820ae00,0xbfc17b2205fa180d,1 +np.float64,0x7fe1c8b8a0239170,0x4086298feeee6133,1 +np.float64,0x3fe2d2c6b9e5a58e,0x3fdd9b907471031b,1 +np.float64,0x3fa0a161bc2142c0,0x3fa05db36f6a073b,1 +np.float64,0x3fdef4268ebde84c,0x3fd93f980794d1e7,1 +np.float64,0x800ecd9fe2fd9b40,0x800ecd9fe2fd9b40,1 +np.float64,0xbfc9fbd45e33f7a8,0xbfcd0afc47c340f6,1 +np.float64,0x3fe8c3035b718606,0x3fe2570eb65551a1,1 +np.float64,0xbfe78c4ad2ef1896,0xbff54d25b3328742,1 +np.float64,0x8006f5dcf8adebbb,0x8006f5dcf8adebbb,1 +np.float64,0x800301dca2a603ba,0x800301dca2a603ba,1 +np.float64,0xad4289e55a851,0xad4289e55a851,1 +np.float64,0x80037764f9e6eecb,0x80037764f9e6eecb,1 +np.float64,0xbfe73575b26e6aec,0xbff4abfb5e985c62,1 +np.float64,0xbfc6cb91652d9724,0xbfc91a8001b33ec2,1 +np.float64,0xbfe3a918ffe75232,0xbfee7e6e4fd34c53,1 +np.float64,0x9bc84e2b3790a,0x9bc84e2b3790a,1 +np.float64,0x7fdeec303cbdd85f,0x408628714a49d996,1 +np.float64,0x3fe1d1dcb763a3ba,0x3fdc54ce060dc7f4,1 +np.float64,0x8008ae6432b15cc9,0x8008ae6432b15cc9,1 +np.float64,0x3fd8022fa2b00460,0x3fd46322bf02a609,1 +np.float64,0xbfc55b64472ab6c8,0xbfc75d9568f462e0,1 +np.float64,0xbfe8b165437162ca,0xbff7a15e2ead645f,1 +np.float64,0x7f759330feeb3,0x7f759330feeb3,1 +np.float64,0xbfd504f68eaa09ee,0xbfd97b06c01d7473,1 +np.float64,0x54702d5aa8e06,0x54702d5aa8e06,1 +np.float64,0xbfed1779337a2ef2,0xc0032f7109ef5a51,1 +np.float64,0xe248bd4dc4918,0xe248bd4dc4918,1 +np.float64,0xbfd8c59150318b22,0xbfdf53bca6ca8b1e,1 +np.float64,0xbfe3b9d942e773b2,0xbfeea9fcad277ba7,1 +np.float64,0x800934ec127269d9,0x800934ec127269d9,1 +np.float64,0xbfbb7f535a36fea8,0xbfbd16d61b6c52b8,1 +np.float64,0xccb185a199631,0xccb185a199631,1 +np.float64,0x3fe3dda76fe7bb4e,0x3fdee83bc6094301,1 +np.float64,0xbfe0c902f5e19206,0xbfe7ca7c0e888006,1 +np.float64,0xbfefeed08cbfdda1,0xc018aadc483c8724,1 +np.float64,0x7fd0c05c52a180b8,0x40862389daf64aac,1 +np.float64,0xbfd28e3323a51c66,0xbfd5e9ba278fb685,1 +np.float64,0xbef4103b7de82,0xbef4103b7de82,1 +np.float64,0x3fe7661fd12ecc40,0x3fe18ff7dfb696e2,1 +np.float64,0x3fddd5f2f0bbabe4,0x3fd87d8bb6719c3b,1 +np.float64,0x800b3914cfd6722a,0x800b3914cfd6722a,1 +np.float64,0xf3f09a97e7e14,0xf3f09a97e7e14,1 +np.float64,0x7f97092b502e1256,0x40860fe8054cf54e,1 +np.float64,0xbfdbec7917b7d8f2,0xbfe2580b4b792c79,1 +np.float64,0x7fe7ff215aaffe42,0x40862bf5887fa062,1 +np.float64,0x80080186e570030e,0x80080186e570030e,1 +np.float64,0xbfc27f05e624fe0c,0xbfc3fa214be4adc4,1 +np.float64,0x3fe4481be1689038,0x3fdf6b11e9c4ca72,1 +np.float64,0x3fd642cc9cac8598,0x3fd31a857fe70227,1 +np.float64,0xbef8782d7df0f,0xbef8782d7df0f,1 +np.float64,0x8003077dc2e60efc,0x8003077dc2e60efc,1 +np.float64,0x80083eb5a2507d6c,0x80083eb5a2507d6c,1 +np.float64,0x800e8d1eb77d1a3e,0x800e8d1eb77d1a3e,1 +np.float64,0xbfc7737cd22ee6f8,0xbfc9e7716f03f1fc,1 +np.float64,0xbfe9a2b4ddf3456a,0xbff9d71664a8fc78,1 +np.float64,0x7fe67c7d322cf8f9,0x40862b7066465194,1 +np.float64,0x3fec080ce2b8101a,0x3fe421dac225be46,1 +np.float64,0xbfe6d27beb6da4f8,0xbff3fbb1add521f7,1 +np.float64,0x3fdd4f96ceba9f2e,0x3fd821a638986dbe,1 +np.float64,0x3fbd89f1303b13e2,0x3fbbf49223a9d002,1 +np.float64,0xbfe94e2b9d329c57,0xbff907e549c534f5,1 +np.float64,0x3fe2f2cc51e5e599,0x3fddc3d6b4a834a1,1 +np.float64,0xfdcb5b49fb96c,0xfdcb5b49fb96c,1 +np.float64,0xbfea7108fa74e212,0xbffc01b392f4897b,1 +np.float64,0x3fd38baef7a7175c,0x3fd10e7fd3b958dd,1 +np.float64,0x3fa75bf9cc2eb800,0x3fa6d792ecdedb8e,1 +np.float64,0x7fd19fd20aa33fa3,0x408623f1e2cd04c3,1 +np.float64,0x3fd62c708dac58e0,0x3fd309ec7818d16e,1 +np.float64,0x3fdf489047be9120,0x3fd978640617c758,1 +np.float64,0x1,0x1,1 +np.float64,0xbfe21e7c3ea43cf8,0xbfeaba21320697d3,1 +np.float64,0xbfd3649047a6c920,0xbfd71a6f14223744,1 +np.float64,0xbfd68ca68c2d194e,0xbfdbcce6784e5d44,1 +np.float64,0x3fdb26b0ea364d62,0x3fd6a1f86f64ff74,1 +np.float64,0xbfd843821cb08704,0xbfde80e90805ab3f,1 +np.float64,0x3fd508a27aaa1144,0x3fd22fc203a7b9d8,1 +np.float64,0xbfdb951c7eb72a38,0xbfe20aeaec13699b,1 +np.float64,0x3fef556ba57eaad7,0x3fe5d8865cce0a6d,1 +np.float64,0x3fd0d224b3a1a448,0x3fcdde7be5d7e21e,1 +np.float64,0x8007ff272baffe4f,0x8007ff272baffe4f,1 +np.float64,0x3fe1c7bddf638f7c,0x3fdc47cc6cf2f5cd,1 +np.float64,0x7ff8000000000000,0x7ff8000000000000,1 +np.float64,0x2016d560402f,0x2016d560402f,1 +np.float64,0xbfcca10be9394218,0xbfd033f36b94fc54,1 +np.float64,0xbfdb833628b7066c,0xbfe1fb344b840c70,1 +np.float64,0x3fd8529cb3b0a539,0x3fd49d847fe77218,1 +np.float64,0xbfc0b0ebab2161d8,0xbfc1e260c60ffd1b,1 +np.float64,0xbfea8b9a79f51735,0xbffc4ee6be8a0fa2,1 +np.float64,0x7feca8fab7f951f4,0x40862d613e454646,1 +np.float64,0x7fd8c52d82318a5a,0x408626aaf37423a3,1 +np.float64,0xbfe364ad4526c95a,0xbfedcee39bc93ff5,1 +np.float64,0x800b78161256f02d,0x800b78161256f02d,1 +np.float64,0xbfd55f0153aabe02,0xbfda01a78f72d494,1 +np.float64,0x800315a5f0662b4d,0x800315a5f0662b4d,1 +np.float64,0x7fe4c0dca02981b8,0x40862acc27e4819f,1 +np.float64,0x8009825c703304b9,0x8009825c703304b9,1 +np.float64,0x3fe6e94e1cadd29c,0x3fe1478ccc634f49,1 +np.float64,0x7fe622d8586c45b0,0x40862b504177827e,1 +np.float64,0x3fe4458600688b0c,0x3fdf67e79a84b953,1 +np.float64,0xbfdd75d8a1baebb2,0xbfe3bc9e6ca1bbb5,1 +np.float64,0x3fde789c6bbcf138,0x3fd8ec1d435531b3,1 +np.float64,0x3fe7052b94ee0a58,0x3fe157c5c4418dc1,1 +np.float64,0x7fef31652abe62c9,0x40862e0eaeabcfc0,1 +np.float64,0x3fe279691ee4f2d2,0x3fdd2aa41eb43cd4,1 +np.float64,0xbfd533fa95aa67f6,0xbfd9c12f516d29d7,1 +np.float64,0x3fe6d057f96da0b0,0x3fe138fd96693a6a,1 +np.float64,0x800bad984f775b31,0x800bad984f775b31,1 +np.float64,0x7fdd6fdba4badfb6,0x4086280c73d8ef97,1 +np.float64,0x7fe9b5c0eef36b81,0x40862c82c6f57a53,1 +np.float64,0x8000bc02ece17807,0x8000bc02ece17807,1 +np.float64,0xbff0000000000000,0xfff0000000000000,1 +np.float64,0xbfed430be3fa8618,0xc003aaf338c75b3c,1 +np.float64,0x3fee17b759fc2f6f,0x3fe53668696bf48b,1 +np.float64,0x3f8d4cf9d03a9a00,0x3f8d17d2f532afdc,1 +np.float64,0x8005d6257b8bac4c,0x8005d6257b8bac4c,1 +np.float64,0xbfd17a6df9a2f4dc,0xbfd469e3848adc6e,1 +np.float64,0xb28a293965145,0xb28a293965145,1 +np.float64,0xbfe7d011e42fa024,0xbff5cf818998c8ec,1 +np.float64,0xbfe74f0f136e9e1e,0xbff4dad6ebb0443c,1 +np.float64,0x800f249fc9be4940,0x800f249fc9be4940,1 +np.float64,0x2542f8fe4a860,0x2542f8fe4a860,1 +np.float64,0xc48d40cd891a8,0xc48d40cd891a8,1 +np.float64,0x3fe4e64bc8e9cc98,0x3fe015c9eb3caa53,1 +np.float64,0x3fd33881eca67104,0x3fd0cea886be2457,1 +np.float64,0xbfd01748fba02e92,0xbfd28875959e6901,1 +np.float64,0x7fb7ab01f22f5603,0x40861b369927bf53,1 +np.float64,0xbfe340274ce6804e,0xbfed72b39f0ebb24,1 +np.float64,0x7fc16c0c3422d817,0x40861e4eaf1a286c,1 +np.float64,0x3fc26944a324d288,0x3fc133a77b356ac4,1 +np.float64,0xa149d7134293b,0xa149d7134293b,1 +np.float64,0x800837382d106e71,0x800837382d106e71,1 +np.float64,0x797d1740f2fa4,0x797d1740f2fa4,1 +np.float64,0xc3f15b7787e2c,0xc3f15b7787e2c,1 +np.float64,0x80cad1b90195a,0x80cad1b90195a,1 +np.float64,0x3fdd8f1142bb1e23,0x3fd84d21490d1ce6,1 +np.float64,0xbfbde6c9123bcd90,0xbfbfcc030a86836a,1 +np.float64,0x8007f77e032feefd,0x8007f77e032feefd,1 +np.float64,0x3fe74fed1c6e9fda,0x3fe18322cf19cb61,1 +np.float64,0xbfd8a40bbcb14818,0xbfdf1d23520ba74b,1 +np.float64,0xbfeb7a0e6076f41d,0xbfff4ddfb926efa5,1 +np.float64,0xbfcb8c5f663718c0,0xbfcf0570f702bda9,1 +np.float64,0xf668cd97ecd1a,0xf668cd97ecd1a,1 +np.float64,0xbfe92accf572559a,0xbff8b4393878ffdb,1 +np.float64,0xbfeaa955567552ab,0xbffca70c7d73eee5,1 +np.float64,0xbfe083a14f610742,0xbfe739d84bc35077,1 +np.float64,0x78290568f0521,0x78290568f0521,1 +np.float64,0x3fe94bae2372975c,0x3fe2a3beac5c9858,1 +np.float64,0x3fca4fbab9349f78,0x3fc7edbca2492acb,1 +np.float64,0x8000000000000000,0x8000000000000000,1 +np.float64,0x7fb9eb505433d6a0,0x40861bf0adedb74d,1 +np.float64,0x7fdc66f72a38cded,0x408627c32aeecf0f,1 +np.float64,0x2e8e6f445d1cf,0x2e8e6f445d1cf,1 +np.float64,0xbfec43195af88633,0xc0012d7e3f91b7e8,1 +np.float64,0x7fcdb971e93b72e3,0x40862294c9e3a7bc,1 +np.float64,0x800cabc461195789,0x800cabc461195789,1 +np.float64,0x2c79709c58f2f,0x2c79709c58f2f,1 +np.float64,0x8005d772d3cbaee6,0x8005d772d3cbaee6,1 +np.float64,0x3fe84d8c03709b18,0x3fe21490ce3673dd,1 +np.float64,0x7fe5578adc2aaf15,0x40862b056e8437d4,1 +np.float64,0xbf91298c58225320,0xbf914ec86c32d11f,1 +np.float64,0xc7ed2b6d8fda6,0xc7ed2b6d8fda6,1 +np.float64,0x2761404c4ec29,0x2761404c4ec29,1 +np.float64,0x3fbad3c48835a789,0x3fb9833c02385305,1 +np.float64,0x3fa46fee5428dfe0,0x3fa40a357fb24c23,1 +np.float64,0xbfe3900c6fe72019,0xbfee3dba29dd9d43,1 +np.float64,0x3fe7a9e41a6f53c8,0x3fe1b704dfb9884b,1 +np.float64,0xbfe74a7a1eee94f4,0xbff4d269cacb1f29,1 +np.float64,0xbfee609c72fcc139,0xc007da8499d34123,1 +np.float64,0x3fef2d5fc23e5ac0,0x3fe5c44414e59cb4,1 +np.float64,0xbfd7bdc0402f7b80,0xbfddaae1e7bb78fb,1 +np.float64,0xd71ee01dae3dc,0xd71ee01dae3dc,1 +np.float64,0x3fe98cbcdef3197a,0x3fe2c7ffe33c4541,1 +np.float64,0x8000f8dbb3a1f1b8,0x8000f8dbb3a1f1b8,1 +np.float64,0x3fe3e98ad567d316,0x3fdef6e58058313f,1 +np.float64,0x41ad0bfc835a2,0x41ad0bfc835a2,1 +np.float64,0x7fdcc2dc0d3985b7,0x408627dce39f77af,1 +np.float64,0xbfe47b980de8f730,0xbff059acdccd6e2b,1 +np.float64,0xbfef49b6577e936d,0xc00e714f46b2ccc1,1 +np.float64,0x3fac31816c386300,0x3fab71cb92b0db8f,1 +np.float64,0x3fe59097e76b2130,0x3fe07c299fd1127c,1 +np.float64,0xbfecf0df5cf9e1bf,0xc002c7ebdd65039c,1 +np.float64,0x3fd2b7d0b6a56fa1,0x3fd06b638990ae02,1 +np.float64,0xbfeb68deecf6d1be,0xbfff1187e042d3e4,1 +np.float64,0x3fd44a9771a8952f,0x3fd1a01867c5e302,1 +np.float64,0xf79a9dedef354,0xf79a9dedef354,1 +np.float64,0x800c25a170d84b43,0x800c25a170d84b43,1 +np.float64,0x3ff0000000000000,0x3fe62e42fefa39ef,1 +np.float64,0x3fbff4f7623fe9f0,0x3fbe1d3878f4c417,1 +np.float64,0xd284c845a5099,0xd284c845a5099,1 +np.float64,0xbfe3c7815f678f02,0xbfeecdab5ca2e651,1 +np.float64,0x3fc19c934e233927,0x3fc08036104b1f23,1 +np.float64,0x800b6096de16c12e,0x800b6096de16c12e,1 +np.float64,0xbfe962a67e32c54d,0xbff9392313a112a1,1 +np.float64,0x2b9d0116573a1,0x2b9d0116573a1,1 +np.float64,0x3fcab269ed3564d4,0x3fc83f7e1c3095b7,1 +np.float64,0x3fc8c78d86318f1b,0x3fc6a6cde5696f99,1 +np.float64,0xd5b1e9b5ab63d,0xd5b1e9b5ab63d,1 +np.float64,0xbfed802a47fb0054,0xc00465cad3b5b0ef,1 +np.float64,0xbfd73aaf08ae755e,0xbfdcdbd62b8af271,1 +np.float64,0xbfd4f13c0229e278,0xbfd95dacff79e570,1 +np.float64,0xbfe9622808f2c450,0xbff937f13c397e8d,1 +np.float64,0xbfeddfa62efbbf4c,0xc005b0c835eed829,1 +np.float64,0x3fd65663d4acacc8,0x3fd3290cd0e675dc,1 +np.float64,0x8005e890f1abd123,0x8005e890f1abd123,1 +np.float64,0xbfe924919fb24923,0xbff8a5a827a28756,1 +np.float64,0x3fe8cdf490719be9,0x3fe25d39535e8366,1 +np.float64,0x7fc229e6ff2453cd,0x40861ea40ef87a5a,1 +np.float64,0x3fe5cf53ceeb9ea8,0x3fe0a18e0b65f27e,1 +np.float64,0xa79cf6fb4f39f,0xa79cf6fb4f39f,1 +np.float64,0x7fddbb3c0f3b7677,0x40862820d5edf310,1 +np.float64,0x3e1011de7c203,0x3e1011de7c203,1 +np.float64,0x3fc0b59a83216b38,0x3fbf6916510ff411,1 +np.float64,0x8647f98d0c8ff,0x8647f98d0c8ff,1 +np.float64,0x8005dad33ecbb5a7,0x8005dad33ecbb5a7,1 +np.float64,0x8a80d0631501a,0x8a80d0631501a,1 +np.float64,0xbfe18f7d6ee31efb,0xbfe976f06713afc1,1 +np.float64,0xbfe06eaed560dd5e,0xbfe70eac696933e6,1 +np.float64,0xbfed8ef93c7b1df2,0xc00495bfa3195b53,1 +np.float64,0x3febe9c24677d385,0x3fe411b10db16c42,1 +np.float64,0x7fd5d80c1fabb017,0x408625a97a7787ba,1 +np.float64,0x3fca79b59334f368,0x3fc8108a521341dc,1 +np.float64,0xbfccf8db4339f1b8,0xbfd06c9a5424aadb,1 +np.float64,0xbfea5ac5a574b58b,0xbffbc21d1405d840,1 +np.float64,0x800ce2bf4b19c57f,0x800ce2bf4b19c57f,1 +np.float64,0xbfe8df896d31bf13,0xbff807ab38ac41ab,1 +np.float64,0x3feab83da9f5707c,0x3fe36cdd827c0eff,1 +np.float64,0x3fee717683bce2ed,0x3fe564879171719b,1 +np.float64,0x80025e5577c4bcac,0x80025e5577c4bcac,1 +np.float64,0x3fe3e5378e67ca70,0x3fdef1902c5d1efd,1 +np.float64,0x3fa014bb7c202980,0x3f9faacf9238d499,1 +np.float64,0x3fddbf5e16bb7ebc,0x3fd86e2311cb0f6d,1 +np.float64,0x3fd24e50e6a49ca0,0x3fd0198f04f82186,1 +np.float64,0x656b5214cad6b,0x656b5214cad6b,1 +np.float64,0x8b0a4bfd1614a,0x8b0a4bfd1614a,1 +np.float64,0xbfeeb6bd9e7d6d7b,0xc009b669285e319e,1 +np.float64,0x8000000000000001,0x8000000000000001,1 +np.float64,0xbfe719feceee33fe,0xbff47a4c8cbf0cca,1 +np.float64,0xbfd14fa8c8a29f52,0xbfd42f27b1aced39,1 +np.float64,0x7fec9dcb80f93b96,0x40862d5e1e70bbb9,1 +np.float64,0x7fecacb826f9596f,0x40862d6249746915,1 +np.float64,0x973459f52e68b,0x973459f52e68b,1 +np.float64,0x7f40a59e00214b3b,0x4085f194f45f82b1,1 +np.float64,0x7fc5dbaec32bb75d,0x4086201f3e7065d9,1 +np.float64,0x82d0801305a10,0x82d0801305a10,1 +np.float64,0x7fec81c0f4790381,0x40862d5643c0fc85,1 +np.float64,0xbfe2d81e9ee5b03d,0xbfec71a8e864ea40,1 +np.float64,0x6c545c9ad8a8c,0x6c545c9ad8a8c,1 +np.float64,0x3f9be95a5037d2b5,0x3f9b89b48ac8f5d8,1 +np.float64,0x8000cae9702195d4,0x8000cae9702195d4,1 +np.float64,0xbfd375f45126ebe8,0xbfd733677e54a80d,1 +np.float64,0x3fd29a5b81a534b7,0x3fd05494bf200278,1 +np.float64,0xfff0000000000000,0xfff8000000000000,1 +np.float64,0x7fca8fc195351f82,0x408621ae61aa6c13,1 +np.float64,0x1b28e2ae3651d,0x1b28e2ae3651d,1 +np.float64,0x3fe7fdbd14effb7a,0x3fe1e714884b46a8,1 +np.float64,0x3fdf1ce068be39c0,0x3fd95b054e0fad3d,1 +np.float64,0x3fe79f9a636f3f34,0x3fe1b11a40c00b3e,1 +np.float64,0x3fe60eb7036c1d6e,0x3fe0c72a02176874,1 +np.float64,0x229da17e453b5,0x229da17e453b5,1 +np.float64,0x3fc1a921b5235240,0x3fc08b3f35e47fb1,1 +np.float64,0xbb92d2af7725b,0xbb92d2af7725b,1 +np.float64,0x3fe4110cb1e8221a,0x3fdf2787de6c73f7,1 +np.float64,0xbfbc87771a390ef0,0xbfbe3f6e95622363,1 +np.float64,0xbfe74025dfee804c,0xbff4bf7b1895e697,1 +np.float64,0x964eb6592c9d7,0x964eb6592c9d7,1 +np.float64,0x3f951689b82a2d00,0x3f94dfb38d746fdf,1 +np.float64,0x800356271be6ac4f,0x800356271be6ac4f,1 +np.float64,0x7fefffffffffffff,0x40862e42fefa39ef,1 +np.float64,0xbfed5ce250fab9c5,0xc003f7ddfeb94345,1 +np.float64,0x3fec3d5dc1387abc,0x3fe43e39c02d86f4,1 +np.float64,0x3999897e73332,0x3999897e73332,1 +np.float64,0xbfdcb57744b96aee,0xbfe30c4b98f3d088,1 +np.float64,0x7f961fb0b82c3f60,0x40860f9549c3a380,1 +np.float64,0x67d6efcacfadf,0x67d6efcacfadf,1 +np.float64,0x8002c9498f859294,0x8002c9498f859294,1 +np.float64,0xbfa3033800260670,0xbfa35fe3bf43e188,1 +np.float64,0xbfeab2fc157565f8,0xbffcc413c486b4eb,1 +np.float64,0x3fe25e62f364bcc6,0x3fdd0856e19e3430,1 +np.float64,0x7fb2f42dda25e85b,0x4086196fb34a65fd,1 +np.float64,0x3fe0f1a5af61e34c,0x3fdb3235a1786efb,1 +np.float64,0x800a340ca1f4681a,0x800a340ca1f4681a,1 +np.float64,0x7c20b9def8418,0x7c20b9def8418,1 +np.float64,0xdf0842a1be109,0xdf0842a1be109,1 +np.float64,0x3fe9f22cc2f3e45a,0x3fe300359b842bf0,1 +np.float64,0x3fe389ed73e713da,0x3fde809780fe4432,1 +np.float64,0x9500fb932a020,0x9500fb932a020,1 +np.float64,0x3fd8a21ffdb14440,0x3fd4d70862345d86,1 +np.float64,0x800d99c15cbb3383,0x800d99c15cbb3383,1 +np.float64,0x3fd96c98c932d932,0x3fd568959c9b028f,1 +np.float64,0x7fc228483a24508f,0x40861ea358420976,1 +np.float64,0x7fc6737bef2ce6f7,0x408620560ffc6a98,1 +np.float64,0xbfb2c27cee2584f8,0xbfb37b8cc7774b5f,1 +np.float64,0xbfd18409f9230814,0xbfd4771d1a9a24fb,1 +np.float64,0x3fb53cb3f42a7968,0x3fb466f06f88044b,1 +np.float64,0x3fef61d0187ec3a0,0x3fe5dec8a9d13dd9,1 +np.float64,0x3fe59a6ffd2b34e0,0x3fe0820a99c6143d,1 +np.float64,0x3fce18aff43c3160,0x3fcb07c7b523f0d1,1 +np.float64,0xbfb1319a62226338,0xbfb1cc62f31b2b40,1 +np.float64,0xa00cce6d4019a,0xa00cce6d4019a,1 +np.float64,0x80068ae8e0ed15d3,0x80068ae8e0ed15d3,1 +np.float64,0x3fecef353239de6a,0x3fe49c280adc607b,1 +np.float64,0x3fdf1a7fb0be34ff,0x3fd9596bafe2d766,1 +np.float64,0x3feb5e12eeb6bc26,0x3fe3c6be3ede8d07,1 +np.float64,0x3fdeff5cd43dfeba,0x3fd947262ec96b05,1 +np.float64,0x3f995e75e832bd00,0x3f990f511f4c7f1c,1 +np.float64,0xbfeb5b3ed0b6b67e,0xbffee24fc0fc2881,1 +np.float64,0x7fb82aad0a305559,0x40861b614d901182,1 +np.float64,0xbfe5c3a4926b8749,0xbff23cd0ad144fe6,1 +np.float64,0x3fef47da373e8fb4,0x3fe5d1aaa4031993,1 +np.float64,0x7fc6a8c3872d5186,0x40862068f5ca84be,1 +np.float64,0x7fc0c2276221844e,0x40861dff2566d001,1 +np.float64,0x7fc9ce7d28339cf9,0x40862173541f84d1,1 +np.float64,0x3fce2c34933c5869,0x3fcb179428ad241d,1 +np.float64,0xbfcf864c293f0c98,0xbfd21872c4821cfc,1 +np.float64,0x3fc51fd1f82a3fa4,0x3fc38d4f1685c166,1 +np.float64,0xbfe2707b70a4e0f7,0xbfeb795fbd5bb444,1 +np.float64,0x46629b568cc54,0x46629b568cc54,1 +np.float64,0x7fe5f821f32bf043,0x40862b40c2cdea3f,1 +np.float64,0x3fedd2c9457ba592,0x3fe512ce92394526,1 +np.float64,0x7fe6dcb8ceadb971,0x40862b925a7dc05d,1 +np.float64,0x3fd1b983b4a37307,0x3fcf4ae2545cf64e,1 +np.float64,0xbfe1c93104639262,0xbfe9f7d28e4c0c82,1 +np.float64,0x995ebc2932bd8,0x995ebc2932bd8,1 +np.float64,0x800a4c3ee614987e,0x800a4c3ee614987e,1 +np.float64,0x3fbb58766e36b0f0,0x3fb9fb3b9810ec16,1 +np.float64,0xbfe36d636666dac7,0xbfede5080f69053c,1 +np.float64,0x3f4feee1003fddc2,0x3f4feae5f05443d1,1 +np.float64,0x3fed0b772ffa16ee,0x3fe4aafb924903c6,1 +np.float64,0x800bb3faef3767f6,0x800bb3faef3767f6,1 +np.float64,0x3fe285cda5e50b9c,0x3fdd3a58df06c427,1 +np.float64,0x7feb9d560bb73aab,0x40862d152362bb94,1 +np.float64,0x3fecd1f447f9a3e9,0x3fe48cc78288cb3f,1 +np.float64,0x3fca927b0c3524f6,0x3fc8250f49ba28df,1 +np.float64,0x7fcc19944e383328,0x40862221b02fcf43,1 +np.float64,0xbfd8ddf41db1bbe8,0xbfdf7b92073ff2fd,1 +np.float64,0x80006fe736e0dfcf,0x80006fe736e0dfcf,1 +np.float64,0x800bbeb66d577d6d,0x800bbeb66d577d6d,1 +np.float64,0xbfe4329353e86526,0xbfefeaf19ab92b42,1 +np.float64,0x2fad72805f5af,0x2fad72805f5af,1 +np.float64,0x3fe1b827aa637050,0x3fdc33bf46012c0d,1 +np.float64,0x3fc3f3f8e227e7f2,0x3fc28aeb86d65278,1 +np.float64,0x3fec018933780312,0x3fe41e619aa4285c,1 +np.float64,0xbfd92428e0b24852,0xbfdfeecb08d154df,1 +np.float64,0x2d7046845ae0a,0x2d7046845ae0a,1 +np.float64,0x7fde7fd2233cffa3,0x408628550f8a948f,1 +np.float64,0x8000a32cd241465a,0x8000a32cd241465a,1 +np.float64,0x8004267a45084cf5,0x8004267a45084cf5,1 +np.float64,0xbfe6b422556d6844,0xbff3c71f67661e6e,1 +np.float64,0x3fe3a37d922746fb,0x3fdea04e04d6195c,1 +np.float64,0xbfddcc54b53b98aa,0xbfe40d2389cdb848,1 +np.float64,0x3fe18b4b92a31697,0x3fdbf9e68cbf5794,1 +np.float64,0x7fc9c5b2ee338b65,0x408621709a17a47a,1 +np.float64,0x1ebd1ce03d7b,0x1ebd1ce03d7b,1 +np.float64,0x8008a6fc39d14df9,0x8008a6fc39d14df9,1 +np.float64,0x3fec11384c782270,0x3fe426bdaedd2965,1 +np.float64,0x3fefc28344ff8507,0x3fe60f75d34fc3d2,1 +np.float64,0xc35f379786be7,0xc35f379786be7,1 +np.float64,0x3feef51f4a7dea3e,0x3fe5a7b95d7786b5,1 +np.float64,0x3fec9b9f0379373e,0x3fe4702477abbb63,1 +np.float64,0x3fde94f8cdbd29f0,0x3fd8ff50f7df0a6f,1 +np.float64,0xbfed32d1cdfa65a4,0xc0037c1470f6f979,1 +np.float64,0x800d3ba44f5a7749,0x800d3ba44f5a7749,1 +np.float64,0x3fe3c56c8fe78ad9,0x3fdeca4eb9bb8918,1 +np.float64,0xbfe7c97242ef92e4,0xbff5c2950dfd6f69,1 +np.float64,0xbd9440057b288,0xbd9440057b288,1 +np.float64,0x7feb2fc111f65f81,0x40862cf524bd2001,1 +np.float64,0x800a431e2df4863d,0x800a431e2df4863d,1 +np.float64,0x80038a3b79e71478,0x80038a3b79e71478,1 +np.float64,0x80000c93d4601928,0x80000c93d4601928,1 +np.float64,0x7fe9fec022f3fd7f,0x40862c995db8ada0,1 +np.float64,0x3fead0129c35a025,0x3fe379d7a92c8f79,1 +np.float64,0x3fdd8cbaf7bb1974,0x3fd84b87ff0c26c7,1 +np.float64,0x3fe8fb7c60b1f6f9,0x3fe276d5339e7135,1 +np.float64,0x85a255e10b44b,0x85a255e10b44b,1 +np.float64,0xbfe507c23fea0f84,0xbff1212d2260022a,1 +np.float64,0x3fc5487c7b2a90f9,0x3fc3b03222d3d148,1 +np.float64,0x7fec0bdcb8f817b8,0x40862d34e8fd11e7,1 +np.float64,0xbfc5f34b4f2be698,0xbfc8146a899c7a0c,1 +np.float64,0xbfa2a49c14254940,0xbfa2fdab2eae3826,1 +np.float64,0x800ec52f15dd8a5e,0x800ec52f15dd8a5e,1 +np.float64,0xbfe3ba4b12a77496,0xbfeeab256b3e9422,1 +np.float64,0x80034d6c7ba69ada,0x80034d6c7ba69ada,1 +np.float64,0x7fd394d4202729a7,0x408624c98a216742,1 +np.float64,0xbfd4493a38289274,0xbfd865d67af2de91,1 +np.float64,0xe47d6203c8fad,0xe47d6203c8fad,1 +np.float64,0x98eb4e4b31d6a,0x98eb4e4b31d6a,1 +np.float64,0x4507fb128a100,0x4507fb128a100,1 +np.float64,0xbfc77032e42ee064,0xbfc9e36ab747a14d,1 +np.float64,0xa1f8a03b43f14,0xa1f8a03b43f14,1 +np.float64,0xbfc3d4da8527a9b4,0xbfc58c27af2476b0,1 +np.float64,0x3fc0eb7d6921d6fb,0x3fbfc858a077ed61,1 +np.float64,0x7fddb2e9403b65d2,0x4086281e98443709,1 +np.float64,0xbfa7ea62942fd4c0,0xbfa87dfd06b05d2a,1 +np.float64,0xbfe7d5c5426fab8a,0xbff5daa969c6d9e5,1 +np.float64,0x3fbf7cba0c3ef974,0x3fbdb23cd8fe875b,1 +np.float64,0x7fe92021eb324043,0x40862c53aee8b154,1 +np.float64,0x7fefbaa1827f7542,0x40862e3194737072,1 +np.float64,0x3fc6f82c402df059,0x3fc520432cbc533f,1 +np.float64,0x7fb37679a826ecf2,0x408619a5f857e27f,1 +np.float64,0x79ec1528f3d83,0x79ec1528f3d83,1 +np.float64,0x3fbefe1d0c3dfc3a,0x3fbd41650ba2c893,1 +np.float64,0x3fc3e5e11827cbc2,0x3fc27eb9b47c9c42,1 +np.float64,0x16aed1922d5db,0x16aed1922d5db,1 +np.float64,0x800124f7e58249f1,0x800124f7e58249f1,1 +np.float64,0x8004f7d12489efa3,0x8004f7d12489efa3,1 +np.float64,0x3fef80b8e27f0172,0x3fe5ee5fd43322c6,1 +np.float64,0xbfe7740c88eee819,0xbff51f823c8da14d,1 +np.float64,0xbfe6e1f1f6edc3e4,0xbff416bcb1302e7c,1 +np.float64,0x8001a2c4a7e3458a,0x8001a2c4a7e3458a,1 +np.float64,0x3fe861e155f0c3c2,0x3fe2201d3000c329,1 +np.float64,0x3fd00a101a201420,0x3fcca01087dbd728,1 +np.float64,0x7fdf0eb1133e1d61,0x4086287a327839b8,1 +np.float64,0x95e3ffdb2bc80,0x95e3ffdb2bc80,1 +np.float64,0x3fd87a1e8230f43d,0x3fd4ba1eb9be1270,1 +np.float64,0x3fedc4792afb88f2,0x3fe50b6529080f73,1 +np.float64,0x7fc9e81fa833d03e,0x4086217b428cc6ff,1 +np.float64,0xbfd21f1ba5a43e38,0xbfd54e048b988e09,1 +np.float64,0xbfbf52af5a3ea560,0xbfc0b4ab3b81fafc,1 +np.float64,0x7fe475f8e268ebf1,0x40862aaf14fee029,1 +np.float64,0x3fcf56899f3ead10,0x3fcc081de28ae9cf,1 +np.float64,0x917d407122fa8,0x917d407122fa8,1 +np.float64,0x22e23e3245c49,0x22e23e3245c49,1 +np.float64,0xbfeec2814f3d8503,0xc00a00ecca27b426,1 +np.float64,0xbfd97fee1c32ffdc,0xbfe04351dfe306ec,1 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-log2.csv b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-log2.csv new file mode 100644 index 0000000000000000000000000000000000000000..26921ef1df12ca02f7a4c68e899685c38f0b54d0 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-log2.csv @@ -0,0 +1,1629 @@ +dtype,input,output,ulperrortol +np.float32,0x80000000,0xff800000,3 +np.float32,0x7f12870a,0x42fe63db,3 +np.float32,0x3ef29cf5,0xbf89eb12,3 +np.float32,0x3d6ba8fb,0xc083d26c,3 +np.float32,0x3d9907e8,0xc06f8230,3 +np.float32,0x4ee592,0xc2fd656e,3 +np.float32,0x58d8b1,0xc2fd0db3,3 +np.float32,0x7ba103,0xc2fc19aa,3 +np.float32,0x7f52e90e,0x42ff70e4,3 +np.float32,0x7fcb15,0xc2fc0132,3 +np.float32,0x7cb7129f,0x42f50855,3 +np.float32,0x9faba,0xc301ae59,3 +np.float32,0x7f300a,0xc2fc04b4,3 +np.float32,0x3f0bf047,0xbf5f10cb,3 +np.float32,0x2fb1fb,0xc2fed934,3 +np.float32,0x3eedb0d1,0xbf8db417,3 +np.float32,0x3d7a0b40,0xc0811638,3 +np.float32,0x2e0bac,0xc2fef334,3 +np.float32,0x6278c1,0xc2fcc1b9,3 +np.float32,0x7f61ab2e,0x42ffa2d9,3 +np.float32,0x8fe7c,0xc301d4be,3 +np.float32,0x3f25e6ee,0xbf203536,3 +np.float32,0x7efc78f0,0x42fdf5c0,3 +np.float32,0x6d7304,0xc2fc73a7,3 +np.float32,0x7f1a472a,0x42fe89ed,3 +np.float32,0x7dd029a6,0x42f96734,3 +np.float32,0x3e9b9327,0xbfdbf8f7,3 +np.float32,0x3f4eefc1,0xbe9d2942,3 +np.float32,0x7f5b9b64,0x42ff8ebc,3 +np.float32,0x3e458ee1,0xc017ed6e,3 +np.float32,0x3f7b766b,0xbcd35acf,3 +np.float32,0x3e616070,0xc00bc378,3 +np.float32,0x7f20e633,0x42fea8f8,3 +np.float32,0x3ee3b461,0xbf95a126,3 +np.float32,0x7e7722ba,0x42fbe5f8,3 +np.float32,0x3f0873d7,0xbf6861fa,3 +np.float32,0x7b4cb2,0xc2fc1ba3,3 +np.float32,0x3f0b6b02,0xbf60712e,3 +np.float32,0x9bff4,0xc301b6f2,3 +np.float32,0x3f07be25,0xbf6a4f0c,3 +np.float32,0x3ef10e57,0xbf8b1b75,3 +np.float32,0x46ad75,0xc2fdb6b1,3 +np.float32,0x3f7bc542,0xbcc4e3a9,3 +np.float32,0x3f6673d4,0xbe1b509c,3 +np.float32,0x7f19fe59,0x42fe8890,3 +np.float32,0x7f800000,0x7f800000,3 +np.float32,0x7f2fe696,0x42feead0,3 +np.float32,0x3dc9432d,0xc0563655,3 +np.float32,0x3ee47623,0xbf950446,3 +np.float32,0x3f1f8817,0xbf2eab51,3 +np.float32,0x7f220ec5,0x42feae44,3 +np.float32,0x2325e3,0xc2ffbab1,3 +np.float32,0x29dfc8,0xc2ff395a,3 +np.float32,0x7f524950,0x42ff6eb3,3 +np.float32,0x3e2234e0,0xc02a21c8,3 +np.float32,0x7f1c6f5a,0x42fe942f,3 +np.float32,0x3b6a61,0xc2fe36e7,3 +np.float32,0x3f1df90e,0xbf324ba9,3 +np.float32,0xb57f0,0xc3017f07,3 +np.float32,0x7d0eba,0xc2fc112e,3 +np.float32,0x403aa9,0xc2fdfd5c,3 +np.float32,0x3e74ecc7,0xc004155f,3 +np.float32,0x17509c,0xc30074f2,3 +np.float32,0x7f62196b,0x42ffa442,3 +np.float32,0x3ecef9a9,0xbfa7417a,3 +np.float32,0x7f14b158,0x42fe6eb1,3 +np.float32,0x3ede12be,0xbf9a40fe,3 +np.float32,0x42cfaa,0xc2fde03f,3 +np.float32,0x3f407b0f,0xbed2a6f5,3 +np.float32,0x7f7fffff,0x43000000,3 +np.float32,0x5467c6,0xc2fd3394,3 +np.float32,0x7ea6b80f,0x42fcc336,3 +np.float32,0x3f21e7b2,0xbf293704,3 +np.float32,0x3dc7e9eb,0xc056d542,3 +np.float32,0x7f3e6e67,0x42ff2571,3 +np.float32,0x3e3e809d,0xc01b4911,3 +np.float32,0x3f800000,0x0,3 +np.float32,0x3d8fd238,0xc0753d52,3 +np.float32,0x3f74aa65,0xbd85cd0e,3 +np.float32,0x7ec30305,0x42fd36ff,3 +np.float32,0x3e97bb93,0xbfe0971d,3 +np.float32,0x3e109d9c,0xc034bb1b,3 +np.float32,0x3f4a0b67,0xbeaed537,3 +np.float32,0x3f25a7aa,0xbf20c228,3 +np.float32,0x3ebc05eb,0xbfb8fd6b,3 +np.float32,0x3eebe749,0xbf8f18e5,3 +np.float32,0x3e9dc479,0xbfd96356,3 +np.float32,0x7f245200,0x42feb882,3 +np.float32,0x1573a8,0xc30093b5,3 +np.float32,0x3e66c4b9,0xc00994a6,3 +np.float32,0x3e73bffc,0xc0048709,3 +np.float32,0x3dfef8e5,0xc0405f16,3 +np.float32,0x403750,0xc2fdfd83,3 +np.float32,0x3ebedf17,0xbfb636a4,3 +np.float32,0x15cae6,0xc3008de2,3 +np.float32,0x3edf4d4e,0xbf993c24,3 +np.float32,0x3f7cc41e,0xbc963fb3,3 +np.float32,0x3e9e12a4,0xbfd907ee,3 +np.float32,0x7ded7b59,0x42f9c889,3 +np.float32,0x7f034878,0x42fe12b5,3 +np.float32,0x7ddce43f,0x42f9930b,3 +np.float32,0x3d82b257,0xc07e1333,3 +np.float32,0x3dae89c1,0xc0635dd4,3 +np.float32,0x6b1d00,0xc2fc8396,3 +np.float32,0x449a5a,0xc2fdccb3,3 +np.float32,0x4e89d2,0xc2fd68cb,3 +np.float32,0x7e1ae83f,0x42fa8cef,3 +np.float32,0x7e4bb22c,0x42fb572e,3 +np.float32,0x3de308ea,0xc04b1634,3 +np.float32,0x7f238c7a,0x42feb508,3 +np.float32,0x3f6c62a3,0xbdeb86f3,3 +np.float32,0x3e58cba6,0xc00f5908,3 +np.float32,0x7f7dd91f,0x42fff9c4,3 +np.float32,0x3d989376,0xc06fc88d,3 +np.float32,0x3dd013c5,0xc0532339,3 +np.float32,0x4b17e6,0xc2fd89ed,3 +np.float32,0x7f67f287,0x42ffb71e,3 +np.float32,0x3f69365e,0xbe09ba3c,3 +np.float32,0x3e4b8b21,0xc0152bf1,3 +np.float32,0x3a75b,0xc3032171,3 +np.float32,0x7f303676,0x42feec1f,3 +np.float32,0x7f6570e5,0x42ffaf18,3 +np.float32,0x3f5ed61e,0xbe4cf676,3 +np.float32,0x3e9b22f9,0xbfdc7e4f,3 +np.float32,0x2c095e,0xc2ff1428,3 +np.float32,0x3f1b17c1,0xbf391754,3 +np.float32,0x422dc6,0xc2fde746,3 +np.float32,0x3f677c8d,0xbe14b365,3 +np.float32,0x3ef85d0c,0xbf8597a9,3 +np.float32,0x3ecaaa6b,0xbfab2430,3 +np.float32,0x3f0607d1,0xbf6eff3d,3 +np.float32,0x3f011fdb,0xbf7cc50d,3 +np.float32,0x6ed7c1,0xc2fc6a4e,3 +np.float32,0x7ec2d1a2,0x42fd3644,3 +np.float32,0x3f75b7fe,0xbd7238a2,3 +np.float32,0x3ef2d146,0xbf89c344,3 +np.float32,0x7ec2cd27,0x42fd3633,3 +np.float32,0x7ee1e55a,0x42fda397,3 +np.float32,0x7f464d6a,0x42ff435c,3 +np.float32,0x7f469a93,0x42ff447b,3 +np.float32,0x7ece752f,0x42fd6121,3 +np.float32,0x2ed878,0xc2fee67b,3 +np.float32,0x75b23,0xc3021eff,3 +np.float32,0x3e0f4be4,0xc03593b8,3 +np.float32,0x2778e1,0xc2ff64fc,3 +np.float32,0x5fe2b7,0xc2fcd561,3 +np.float32,0x19b8a9,0xc30050ab,3 +np.float32,0x7df303e5,0x42f9d98d,3 +np.float32,0x608b8d,0xc2fcd051,3 +np.float32,0x588f46,0xc2fd1017,3 +np.float32,0x3eec6a11,0xbf8eb2a1,3 +np.float32,0x3f714121,0xbdaf4906,3 +np.float32,0x7f4f7b9e,0x42ff64c9,3 +np.float32,0x3c271606,0xc0d3b29c,3 +np.float32,0x3f002fe0,0xbf7f75f6,3 +np.float32,0x7efa4798,0x42fdef4f,3 +np.float32,0x3f61a865,0xbe3a601a,3 +np.float32,0x7e8087aa,0x42fc030d,3 +np.float32,0x3f70f0c7,0xbdb321ba,3 +np.float32,0x5db898,0xc2fce63f,3 +np.float32,0x7a965f,0xc2fc1fea,3 +np.float32,0x7f68b112,0x42ffb97c,3 +np.float32,0x7ef0ed3d,0x42fdd32d,3 +np.float32,0x7f3156a1,0x42fef0d3,3 +np.float32,0x3f1d405f,0xbf33fc6e,3 +np.float32,0x3e3494cf,0xc0203945,3 +np.float32,0x6018de,0xc2fcd3c1,3 +np.float32,0x623e49,0xc2fcc370,3 +np.float32,0x3ea29f0f,0xbfd3cad4,3 +np.float32,0xa514,0xc305a20c,3 +np.float32,0x3e1b2ab1,0xc02e3a8f,3 +np.float32,0x3f450b6f,0xbec1578f,3 +np.float32,0x7eb12908,0x42fcf015,3 +np.float32,0x3f10b720,0xbf52ab48,3 +np.float32,0x3e0a93,0xc2fe16f6,3 +np.float32,0x93845,0xc301cb96,3 +np.float32,0x7f4e9ce3,0x42ff61af,3 +np.float32,0x3f6d4296,0xbde09ceb,3 +np.float32,0x6ddede,0xc2fc70d0,3 +np.float32,0x3f4fb6fd,0xbe9a636d,3 +np.float32,0x3f6d08de,0xbde36c0b,3 +np.float32,0x3f56f057,0xbe8122ad,3 +np.float32,0x334e95,0xc2fea349,3 +np.float32,0x7efadbcd,0x42fdf104,3 +np.float32,0x3db02e88,0xc0628046,3 +np.float32,0x3f3309d1,0xbf041066,3 +np.float32,0x2d8722,0xc2fefb8f,3 +np.float32,0x7e926cac,0x42fc6356,3 +np.float32,0x3e3674ab,0xc01f452e,3 +np.float32,0x1b46ce,0xc3003afc,3 +np.float32,0x3f06a338,0xbf6d53fc,3 +np.float32,0x1b1ba7,0xc3003d46,3 +np.float32,0x319dfb,0xc2febc06,3 +np.float32,0x3e2f126a,0xc02315a5,3 +np.float32,0x3f40fe65,0xbed0af9e,3 +np.float32,0x3f1d842f,0xbf335d4b,3 +np.float32,0x3d044e4f,0xc09e78f8,3 +np.float32,0x7f272674,0x42fec51f,3 +np.float32,0x3cda6d8f,0xc0a753db,3 +np.float32,0x3eb92f12,0xbfbbccbb,3 +np.float32,0x7e4318f4,0x42fb3752,3 +np.float32,0x3c5890,0xc2fe2b6d,3 +np.float32,0x3d1993c9,0xc09796f8,3 +np.float32,0x7f18ef24,0x42fe8377,3 +np.float32,0x3e30c3a0,0xc0223244,3 +np.float32,0x3f27cd27,0xbf1c00ef,3 +np.float32,0x3f150957,0xbf47cd6c,3 +np.float32,0x7e7178a3,0x42fbd4d8,3 +np.float32,0x3f298db8,0xbf182ac3,3 +np.float32,0x7cb3be,0xc2fc1348,3 +np.float32,0x3ef64266,0xbf8729de,3 +np.float32,0x3eeb06ce,0xbf8fc8f2,3 +np.float32,0x3f406e36,0xbed2d845,3 +np.float32,0x7f1e1bd3,0x42fe9c0b,3 +np.float32,0x478dcc,0xc2fdad97,3 +np.float32,0x7f7937b5,0x42ffec2b,3 +np.float32,0x3f20f350,0xbf2b6624,3 +np.float32,0x7f13661a,0x42fe683c,3 +np.float32,0x208177,0xc2fff46b,3 +np.float32,0x263cfb,0xc2ff7c72,3 +np.float32,0x7f0bd28c,0x42fe4141,3 +np.float32,0x7230d8,0xc2fc5453,3 +np.float32,0x3f261bbf,0xbf1fbfb4,3 +np.float32,0x737b56,0xc2fc4c05,3 +np.float32,0x3ef88f33,0xbf857263,3 +np.float32,0x7e036464,0x42fa1352,3 +np.float32,0x4b5c4f,0xc2fd874d,3 +np.float32,0x3f77984d,0xbd454596,3 +np.float32,0x3f674202,0xbe162932,3 +np.float32,0x3e7157d9,0xc0057197,3 +np.float32,0x3f3f21da,0xbed7d861,3 +np.float32,0x7f1fb40f,0x42fea375,3 +np.float32,0x7ef0157f,0x42fdd096,3 +np.float32,0x3f71e88d,0xbda74962,3 +np.float32,0x3f174855,0xbf424728,3 +np.float32,0x3f3fdd2c,0xbed505d5,3 +np.float32,0x7b95d1,0xc2fc19ed,3 +np.float32,0x7f23f4e5,0x42feb6df,3 +np.float32,0x7d741925,0x42f7dcd6,3 +np.float32,0x60f81d,0xc2fccd14,3 +np.float32,0x3f17d267,0xbf40f6ae,3 +np.float32,0x3f036fc8,0xbf7636f8,3 +np.float32,0x167653,0xc30082b5,3 +np.float32,0x256d05,0xc2ff8c4f,3 +np.float32,0x3eccc63d,0xbfa93adb,3 +np.float32,0x7f6c91ea,0x42ffc5b2,3 +np.float32,0x2ee52a,0xc2fee5b3,3 +np.float32,0x3dc3579e,0xc058f80d,3 +np.float32,0x4c7170,0xc2fd7cc4,3 +np.float32,0x7f737f20,0x42ffdb03,3 +np.float32,0x3f2f9dbf,0xbf0b3119,3 +np.float32,0x3f4d0c54,0xbea3eec5,3 +np.float32,0x7e380862,0x42fb0c32,3 +np.float32,0x5d637f,0xc2fce8df,3 +np.float32,0x3f0aa623,0xbf627c27,3 +np.float32,0x3e4d5896,0xc0145b88,3 +np.float32,0x3f6cacdc,0xbde7e7ca,3 +np.float32,0x63a2c3,0xc2fcb90a,3 +np.float32,0x6c138c,0xc2fc7cfa,3 +np.float32,0x2063c,0xc303fb88,3 +np.float32,0x7e9e5a3e,0x42fc9d2f,3 +np.float32,0x56ec64,0xc2fd1ddd,3 +np.float32,0x7f1d6a35,0x42fe98cc,3 +np.float32,0x73dc96,0xc2fc4998,3 +np.float32,0x3e5d74e5,0xc00d6238,3 +np.float32,0x7f033cbb,0x42fe1273,3 +np.float32,0x3f5143fc,0xbe94e4e7,3 +np.float32,0x1d56d9,0xc3002010,3 +np.float32,0x2bf3e4,0xc2ff1591,3 +np.float32,0x3f2a6ef1,0xbf164170,3 +np.float32,0x3f33238b,0xbf03db58,3 +np.float32,0x22780e,0xc2ffc91a,3 +np.float32,0x7f00b873,0x42fe0425,3 +np.float32,0x3f7f6145,0xbb654706,3 +np.float32,0x7fc00000,0x7fc00000,3 +np.float32,0x63895a,0xc2fcb9c7,3 +np.float32,0x18a1b2,0xc30060a8,3 +np.float32,0x7e43c6a6,0x42fb39e3,3 +np.float32,0x78676e,0xc2fc2d30,3 +np.float32,0x3f16d839,0xbf435940,3 +np.float32,0x7eff78ba,0x42fdfe79,3 +np.float32,0x3f2e152c,0xbf0e6e54,3 +np.float32,0x3db20ced,0xc06186e1,3 +np.float32,0x3f0cd1d8,0xbf5cbf57,3 +np.float32,0x3fd7a8,0xc2fe01d2,3 +np.float32,0x3ebb075e,0xbfb9f816,3 +np.float32,0x7f94ef,0xc2fc026b,3 +np.float32,0x3d80ba0e,0xc07f7a2b,3 +np.float32,0x7f227e15,0x42feb03f,3 +np.float32,0x792264bf,0x42e6afcc,3 +np.float32,0x7f501576,0x42ff66ec,3 +np.float32,0x223629,0xc2ffcea3,3 +np.float32,0x40a79e,0xc2fdf87b,3 +np.float32,0x449483,0xc2fdccf2,3 +np.float32,0x3f4fa978,0xbe9a9382,3 +np.float32,0x7f148c53,0x42fe6df9,3 +np.float32,0x3ec98b3c,0xbfac2a98,3 +np.float32,0x3e4da320,0xc0143a0a,3 +np.float32,0x3d1d94bb,0xc09666d0,3 +np.float32,0x3c8e624e,0xc0bb155b,3 +np.float32,0x66a9af,0xc2fca2ef,3 +np.float32,0x3ec76ed7,0xbfae1c57,3 +np.float32,0x3f4b52f3,0xbeaa2b81,3 +np.float32,0x7e99bbb5,0x42fc8750,3 +np.float32,0x3f69a46b,0xbe0701be,3 +np.float32,0x3f775400,0xbd4ba495,3 +np.float32,0x131e56,0xc300be3c,3 +np.float32,0x3f30abb4,0xbf08fb10,3 +np.float32,0x7f7e528c,0x42fffb25,3 +np.float32,0x3eb89515,0xbfbc668a,3 +np.float32,0x7e9191b6,0x42fc5f02,3 +np.float32,0x7e80c7e9,0x42fc047e,3 +np.float32,0x3f77ef58,0xbd3d2995,3 +np.float32,0x7ddb1f8a,0x42f98d1b,3 +np.float32,0x7ebc6c4f,0x42fd1d9c,3 +np.float32,0x3f6638e0,0xbe1ccab8,3 +np.float32,0x7f4c45,0xc2fc0410,3 +np.float32,0x3e7d8aad,0xc000e414,3 +np.float32,0x3f4d148b,0xbea3d12e,3 +np.float32,0x3e98c45c,0xbfdf55f4,3 +np.float32,0x3d754c78,0xc081f8a9,3 +np.float32,0x17e4cf,0xc3006be3,3 +np.float32,0x7eb65814,0x42fd0563,3 +np.float32,0x3f65e0d8,0xbe1f0008,3 +np.float32,0x3e99541f,0xbfdea87e,3 +np.float32,0x3f3cb80e,0xbee13b27,3 +np.float32,0x3e99f0c0,0xbfddec3b,3 +np.float32,0x3f43903e,0xbec6ea66,3 +np.float32,0x7e211cd4,0x42faa9f2,3 +np.float32,0x824af,0xc301f971,3 +np.float32,0x3e16a56e,0xc030f56c,3 +np.float32,0x542b3b,0xc2fd35a6,3 +np.float32,0x3eeea2d1,0xbf8cf873,3 +np.float32,0x232e93,0xc2ffb9fa,3 +np.float32,0x3e8c52b9,0xbfef06aa,3 +np.float32,0x7f69c7e3,0x42ffbcef,3 +np.float32,0x3f573e43,0xbe801714,3 +np.float32,0x43b009,0xc2fdd69f,3 +np.float32,0x3ee571ab,0xbf943966,3 +np.float32,0x3ee3d5d8,0xbf958604,3 +np.float32,0x338b12,0xc2fe9fe4,3 +np.float32,0x29cb1f,0xc2ff3ac6,3 +np.float32,0x3f0892b4,0xbf680e7a,3 +np.float32,0x3e8c4f7f,0xbfef0ae9,3 +np.float32,0x7c9d3963,0x42f497e6,3 +np.float32,0x3f26ba84,0xbf1e5f59,3 +np.float32,0x3dd0acc0,0xc052df6f,3 +np.float32,0x3e43fbda,0xc018aa8c,3 +np.float32,0x3ec4fd0f,0xbfb0635d,3 +np.float32,0x3f52c8c6,0xbe8f8d85,3 +np.float32,0x3f5fdc5d,0xbe462fdb,3 +np.float32,0x3f461920,0xbebd6743,3 +np.float32,0x6161ff,0xc2fcc9ef,3 +np.float32,0x7f7ed306,0x42fffc9a,3 +np.float32,0x3d212263,0xc0955f46,3 +np.float32,0x3eca5826,0xbfab6f36,3 +np.float32,0x7d6317ac,0x42f7a77e,3 +np.float32,0x3eb02063,0xbfc50f60,3 +np.float32,0x7f71a6f8,0x42ffd565,3 +np.float32,0x1a3efe,0xc3004935,3 +np.float32,0x3dc599c9,0xc057e856,3 +np.float32,0x3f3e1301,0xbedbf205,3 +np.float32,0xf17d4,0xc301158d,3 +np.float32,0x3f615f84,0xbe3c3d85,3 +np.float32,0x3de63be1,0xc049cb77,3 +np.float32,0x3e8d2f51,0xbfede541,3 +np.float32,0x3a5cdd,0xc2fe441c,3 +np.float32,0x3f443ec0,0xbec4586a,3 +np.float32,0x3eacbd00,0xbfc8a5ad,3 +np.float32,0x3f600f6a,0xbe44df1b,3 +np.float32,0x5f77a6,0xc2fcd89c,3 +np.float32,0x476706,0xc2fdaf28,3 +np.float32,0x2f469,0xc3036fde,3 +np.float32,0x7dc4ba24,0x42f93d77,3 +np.float32,0x3e2d6080,0xc023fb9b,3 +np.float32,0x7e8d7135,0x42fc49c3,3 +np.float32,0x3f589065,0xbe77247b,3 +np.float32,0x3f59e210,0xbe6e2c05,3 +np.float32,0x7f51d388,0x42ff6d15,3 +np.float32,0x7d9a5fda,0x42f88a63,3 +np.float32,0x3e67d5bc,0xc00927ab,3 +np.float32,0x61d72c,0xc2fcc679,3 +np.float32,0x3ef3351d,0xbf897766,3 +np.float32,0x1,0xc3150000,3 +np.float32,0x7f653429,0x42ffae54,3 +np.float32,0x7e1ad3e5,0x42fa8c8e,3 +np.float32,0x3f4ca01d,0xbea57500,3 +np.float32,0x3f7606db,0xbd6ad13e,3 +np.float32,0x7ec4a27d,0x42fd3d1f,3 +np.float32,0x3efe4fd5,0xbf8138c7,3 +np.float32,0x77c2f1,0xc2fc3124,3 +np.float32,0x7e4d3251,0x42fb5c9a,3 +np.float32,0x3f543ac7,0xbe8a8154,3 +np.float32,0x7c3dbe29,0x42f322c4,3 +np.float32,0x408e01,0xc2fdf9a0,3 +np.float32,0x45069b,0xc2fdc829,3 +np.float32,0x3d7ecab7,0xc08037e8,3 +np.float32,0xf8c22,0xc3010a99,3 +np.float32,0x7f69af63,0x42ffbca2,3 +np.float32,0x7ec7d228,0x42fd48fe,3 +np.float32,0xff800000,0xffc00000,3 +np.float32,0xdd7c5,0xc301357c,3 +np.float32,0x143f38,0xc300a90e,3 +np.float32,0x7e65c176,0x42fbb01b,3 +np.float32,0x2c1a9e,0xc2ff1307,3 +np.float32,0x7f6e9224,0x42ffcbeb,3 +np.float32,0x3d32ab39,0xc0909a77,3 +np.float32,0x3e150b42,0xc031f22b,3 +np.float32,0x1f84b4,0xc300059a,3 +np.float32,0x3f71ce21,0xbda88c2a,3 +np.float32,0x2625c4,0xc2ff7e33,3 +np.float32,0x3dd0b293,0xc052dcdc,3 +np.float32,0x625c11,0xc2fcc290,3 +np.float32,0x3f610297,0xbe3e9f24,3 +np.float32,0x7ebdd5e5,0x42fd2320,3 +np.float32,0x3e883458,0xbff486ff,3 +np.float32,0x782313,0xc2fc2ed4,3 +np.float32,0x7f39c843,0x42ff132f,3 +np.float32,0x7f326aa7,0x42fef54d,3 +np.float32,0x4d2c71,0xc2fd75be,3 +np.float32,0x3f55747c,0xbe86409e,3 +np.float32,0x7f7f0867,0x42fffd34,3 +np.float32,0x321316,0xc2feb53f,3 +np.float32,0x3e1b37ed,0xc02e32b0,3 +np.float32,0x80edf,0xc301fd54,3 +np.float32,0x3f0b08ad,0xbf617607,3 +np.float32,0x7f3f4174,0x42ff28a2,3 +np.float32,0x3d79306d,0xc0813eb0,3 +np.float32,0x3f5f657a,0xbe49413d,3 +np.float32,0x3f56c63a,0xbe81b376,3 +np.float32,0x7f667123,0x42ffb24f,3 +np.float32,0x3f71021b,0xbdb24d43,3 +np.float32,0x7f434ab1,0x42ff380f,3 +np.float32,0x3dcae496,0xc055779c,3 +np.float32,0x3f5a7d88,0xbe6a0f5b,3 +np.float32,0x3cdf5c32,0xc0a64bf5,3 +np.float32,0x3e56222c,0xc0107d11,3 +np.float32,0x561a3a,0xc2fd24df,3 +np.float32,0x7ddd953c,0x42f9955a,3 +np.float32,0x7e35d839,0x42fb035c,3 +np.float32,0x3ec1816c,0xbfb3aeb2,3 +np.float32,0x7c87cfcd,0x42f42bc2,3 +np.float32,0xd9cd,0xc3053baf,3 +np.float32,0x3f388234,0xbef1e5b7,3 +np.float32,0x3edfcaca,0xbf98d47b,3 +np.float32,0x3ef28852,0xbf89fac8,3 +np.float32,0x7f7525df,0x42ffe001,3 +np.float32,0x7f6c33ef,0x42ffc48c,3 +np.float32,0x3ea4a881,0xbfd17e61,3 +np.float32,0x3f3e379f,0xbedb63c6,3 +np.float32,0x3f0524c1,0xbf717301,3 +np.float32,0x3db3e7f0,0xc06091d3,3 +np.float32,0x800000,0xc2fc0000,3 +np.float32,0x3f2f2897,0xbf0c27ce,3 +np.float32,0x7eb1776d,0x42fcf15c,3 +np.float32,0x3f039018,0xbf75dc37,3 +np.float32,0x3c4055,0xc2fe2c96,3 +np.float32,0x3f603653,0xbe43dea5,3 +np.float32,0x7f700d24,0x42ffd07c,3 +np.float32,0x3f4741a3,0xbeb918dc,3 +np.float32,0x3f5fe959,0xbe45da2d,3 +np.float32,0x3f3e4401,0xbedb33b1,3 +np.float32,0x7f0705ff,0x42fe2775,3 +np.float32,0x3ea85662,0xbfcd69b0,3 +np.float32,0x3f15f49f,0xbf458829,3 +np.float32,0x3f17c50e,0xbf411728,3 +np.float32,0x3e483f60,0xc016add2,3 +np.float32,0x3f1ab9e5,0xbf39f71b,3 +np.float32,0x3de0b6fb,0xc04c08fe,3 +np.float32,0x7e671225,0x42fbb452,3 +np.float32,0x80800000,0xffc00000,3 +np.float32,0xe2df3,0xc3012c9d,3 +np.float32,0x3ede1e3c,0xbf9a3770,3 +np.float32,0x3df2ffde,0xc044cfec,3 +np.float32,0x3eed8da5,0xbf8dcf6c,3 +np.float32,0x3ead15c3,0xbfc846e1,3 +np.float32,0x7ef3750a,0x42fddae4,3 +np.float32,0x7e6ab7c0,0x42fbbfe4,3 +np.float32,0x7ea4bbe5,0x42fcba5d,3 +np.float32,0x3f227706,0xbf27f0a1,3 +np.float32,0x3ef39bfd,0xbf89295a,3 +np.float32,0x3f289a20,0xbf1a3edd,3 +np.float32,0x7f225f82,0x42feafb4,3 +np.float32,0x768963,0xc2fc38bc,3 +np.float32,0x3f493c00,0xbeb1ccfc,3 +np.float32,0x3f4e7249,0xbe9ee9a7,3 +np.float32,0x1d0c3a,0xc30023c0,3 +np.float32,0x7f3c5f78,0x42ff1d6a,3 +np.float32,0xff7fffff,0xffc00000,3 +np.float32,0x3ee7896a,0xbf928c2a,3 +np.float32,0x3e788479,0xc002bd2e,3 +np.float32,0x3ee4df17,0xbf94af84,3 +np.float32,0x5e06d7,0xc2fce3d7,3 +np.float32,0x3d7b2776,0xc080e1dc,3 +np.float32,0x3e3d39d3,0xc01be7fd,3 +np.float32,0x7c81dece,0x42f40ab7,3 +np.float32,0x3f7d2085,0xbc856255,3 +np.float32,0x7f7f6627,0x42fffe44,3 +np.float32,0x7f5f2e94,0x42ff9aaa,3 +np.float32,0x7f5835f2,0x42ff8339,3 +np.float32,0x3f6a0e32,0xbe046580,3 +np.float32,0x7e16f586,0x42fa79dd,3 +np.float32,0x3f04a2f2,0xbf72dbc5,3 +np.float32,0x3f35e334,0xbefc7740,3 +np.float32,0x3f0d056e,0xbf5c3824,3 +np.float32,0x7ebeb95e,0x42fd2693,3 +np.float32,0x3c6192,0xc2fe2aff,3 +np.float32,0x3e892b4f,0xbff33958,3 +np.float32,0x3f61d694,0xbe3931df,3 +np.float32,0x29d183,0xc2ff3a56,3 +np.float32,0x7f0b0598,0x42fe3d04,3 +np.float32,0x7f743b28,0x42ffdd3d,3 +np.float32,0x3a2ed6,0xc2fe4663,3 +np.float32,0x3e27403a,0xc0274de8,3 +np.float32,0x3f58ee78,0xbe74a349,3 +np.float32,0x3eaa4b,0xc2fe0f92,3 +np.float32,0x3ecb613b,0xbfaa7de8,3 +np.float32,0x7f637d81,0x42ffa8c9,3 +np.float32,0x3f026e96,0xbf790c73,3 +np.float32,0x386cdf,0xc2fe5d0c,3 +np.float32,0x35abd1,0xc2fe8202,3 +np.float32,0x3eac3cd1,0xbfc92ee8,3 +np.float32,0x3f567869,0xbe82bf47,3 +np.float32,0x3f65c643,0xbe1faae6,3 +np.float32,0x7f5422b9,0x42ff752b,3 +np.float32,0x7c26e9,0xc2fc168c,3 +np.float32,0x7eff5cfd,0x42fdfe29,3 +np.float32,0x3f728e7f,0xbd9f6142,3 +np.float32,0x3f10fd43,0xbf51f874,3 +np.float32,0x7e7ada08,0x42fbf0fe,3 +np.float32,0x3e82a611,0xbffc37be,3 +np.float32,0xbf800000,0xffc00000,3 +np.float32,0x3dbe2e12,0xc05b711c,3 +np.float32,0x7e768fa9,0x42fbe440,3 +np.float32,0x5e44e8,0xc2fce1f0,3 +np.float32,0x7f25071a,0x42febbae,3 +np.float32,0x3f54db5e,0xbe885339,3 +np.float32,0x3f0f2c26,0xbf56a0b8,3 +np.float32,0x22f9a7,0xc2ffbe55,3 +np.float32,0x7ed63dcb,0x42fd7c77,3 +np.float32,0x7ea4fae2,0x42fcbb78,3 +np.float32,0x3f1d7766,0xbf337b47,3 +np.float32,0x7f16d59f,0x42fe7941,3 +np.float32,0x3f3a1bb6,0xbeeb855c,3 +np.float32,0x3ef57128,0xbf87c709,3 +np.float32,0xb24ff,0xc3018591,3 +np.float32,0x3ef99e27,0xbf84a983,3 +np.float32,0x3eac2ccf,0xbfc94013,3 +np.float32,0x3e9d3e1e,0xbfda00dc,3 +np.float32,0x718213,0xc2fc58c1,3 +np.float32,0x7edbf509,0x42fd8fea,3 +np.float32,0x70c7f1,0xc2fc5d80,3 +np.float32,0x3f7012f5,0xbdbdc6cd,3 +np.float32,0x12cba,0xc304c487,3 +np.float32,0x7f5d445d,0x42ff944c,3 +np.float32,0x7f3e30bd,0x42ff2481,3 +np.float32,0x63b110,0xc2fcb8a0,3 +np.float32,0x3f39f728,0xbeec1680,3 +np.float32,0x3f5bea58,0xbe6074b1,3 +np.float32,0x3f350749,0xbefff679,3 +np.float32,0x3e91ab2c,0xbfe81f3e,3 +np.float32,0x7ec53fe0,0x42fd3f6d,3 +np.float32,0x3f6cbbdc,0xbde72c8e,3 +np.float32,0x3f4df49f,0xbea0abcf,3 +np.float32,0x3e9c9638,0xbfdac674,3 +np.float32,0x7f3b82ec,0x42ff1a07,3 +np.float32,0x7f612a09,0x42ffa132,3 +np.float32,0x7ea26650,0x42fcafd3,3 +np.float32,0x3a615138,0xc122f26d,3 +np.float32,0x3f1108bd,0xbf51db39,3 +np.float32,0x6f80f6,0xc2fc65ea,3 +np.float32,0x3f7cb578,0xbc98ecb1,3 +np.float32,0x7f54d31a,0x42ff7790,3 +np.float32,0x196868,0xc3005532,3 +np.float32,0x3f01ee0a,0xbf7a7925,3 +np.float32,0x3e184013,0xc02ffb11,3 +np.float32,0xadde3,0xc3018ee3,3 +np.float32,0x252a91,0xc2ff9173,3 +np.float32,0x3f0382c2,0xbf7601a9,3 +np.float32,0x6d818c,0xc2fc7345,3 +np.float32,0x3bfbfd,0xc2fe2fdd,3 +np.float32,0x7f3cad19,0x42ff1e9a,3 +np.float32,0x4169a7,0xc2fdefdf,3 +np.float32,0x3f615d96,0xbe3c4a2b,3 +np.float32,0x3f036480,0xbf7656ac,3 +np.float32,0x7f5fbda3,0x42ff9c83,3 +np.float32,0x3d202d,0xc2fe21f1,3 +np.float32,0x3d0f5e5d,0xc09ac3e9,3 +np.float32,0x3f0fff6e,0xbf548142,3 +np.float32,0x7f11ed32,0x42fe60d2,3 +np.float32,0x3e6f856b,0xc00624b6,3 +np.float32,0x7f7c4dd7,0x42fff542,3 +np.float32,0x3e76fb86,0xc0034fa0,3 +np.float32,0x3e8a0d6e,0xbff209e7,3 +np.float32,0x3eacad19,0xbfc8b6ad,3 +np.float32,0xa7776,0xc3019cbe,3 +np.float32,0x3dc84d74,0xc056a754,3 +np.float32,0x3efb8052,0xbf834626,3 +np.float32,0x3f0e55fc,0xbf58cacc,3 +np.float32,0x7e0e71e3,0x42fa4efb,3 +np.float32,0x3ed5a800,0xbfa1639c,3 +np.float32,0x3f33335b,0xbf03babf,3 +np.float32,0x38cad7,0xc2fe5842,3 +np.float32,0x3bc21256,0xc0ecc927,3 +np.float32,0x3f09522d,0xbf660a19,3 +np.float32,0xcbd5d,0xc3015428,3 +np.float32,0x492752,0xc2fd9d42,3 +np.float32,0x3f2b9b32,0xbf13b904,3 +np.float32,0x6544ac,0xc2fcad09,3 +np.float32,0x52eb12,0xc2fd40b5,3 +np.float32,0x3f66a7c0,0xbe1a03e8,3 +np.float32,0x7ab289,0xc2fc1f41,3 +np.float32,0x62af5e,0xc2fcc020,3 +np.float32,0x7f73e9cf,0x42ffdc46,3 +np.float32,0x3e5eca,0xc2fe130e,3 +np.float32,0x3e3a10f4,0xc01d7602,3 +np.float32,0x3f04db46,0xbf723f0d,3 +np.float32,0x18fc4a,0xc3005b63,3 +np.float32,0x525bcb,0xc2fd45b6,3 +np.float32,0x3f6b9108,0xbdf5c769,3 +np.float32,0x3e992e8c,0xbfded5c5,3 +np.float32,0x7efea647,0x42fdfc18,3 +np.float32,0x7e8371db,0x42fc139e,3 +np.float32,0x3f397cfb,0xbeedfc69,3 +np.float32,0x7e46d233,0x42fb454a,3 +np.float32,0x7d5281ad,0x42f76f79,3 +np.float32,0x7f4c1878,0x42ff58a1,3 +np.float32,0x3e96ca5e,0xbfe1bd97,3 +np.float32,0x6a2743,0xc2fc8a3d,3 +np.float32,0x7f688781,0x42ffb8f8,3 +np.float32,0x7814b7,0xc2fc2f2d,3 +np.float32,0x3f2ffdc9,0xbf0a6756,3 +np.float32,0x3f766fa8,0xbd60fe24,3 +np.float32,0x4dc64e,0xc2fd7003,3 +np.float32,0x3a296f,0xc2fe46a8,3 +np.float32,0x3f2af942,0xbf15162e,3 +np.float32,0x7f702c32,0x42ffd0dc,3 +np.float32,0x7e61e318,0x42fba390,3 +np.float32,0x7f7d3bdb,0x42fff7fa,3 +np.float32,0x3ee87f3f,0xbf91c881,3 +np.float32,0x2bbc28,0xc2ff193c,3 +np.float32,0x3e01f918,0xc03e966e,3 +np.float32,0x7f0b39f4,0x42fe3e1a,3 +np.float32,0x3eaa4d64,0xbfcb4516,3 +np.float32,0x3e53901e,0xc0119a88,3 +np.float32,0x603cb,0xc3026957,3 +np.float32,0x7e81f926,0x42fc0b4d,3 +np.float32,0x5dab7c,0xc2fce6a6,3 +np.float32,0x3f46fefd,0xbeba1018,3 +np.float32,0x648448,0xc2fcb28a,3 +np.float32,0x3ec49470,0xbfb0c58b,3 +np.float32,0x3e8a5393,0xbff1ac2b,3 +np.float32,0x3f27ccfc,0xbf1c014e,3 +np.float32,0x3ed886e6,0xbf9eeca8,3 +np.float32,0x7cfbe06e,0x42f5f401,3 +np.float32,0x3f5aa7ba,0xbe68f229,3 +np.float32,0x9500d,0xc301c7e3,3 +np.float32,0x3f4861,0xc2fe0853,3 +np.float32,0x3e5ae104,0xc00e76f5,3 +np.float32,0x71253a,0xc2fc5b1e,3 +np.float32,0xcf7b8,0xc3014d9c,3 +np.float32,0x7f7edd2d,0x42fffcb7,3 +np.float32,0x3e9039ee,0xbfe9f5ab,3 +np.float32,0x2fd54e,0xc2fed712,3 +np.float32,0x3f600752,0xbe45147a,3 +np.float32,0x3f4da8f6,0xbea1bb5c,3 +np.float32,0x3f2d34a9,0xbf104bd9,3 +np.float32,0x3e1e66dd,0xc02c52d2,3 +np.float32,0x798276,0xc2fc2670,3 +np.float32,0xd55e2,0xc3014347,3 +np.float32,0x80000001,0xffc00000,3 +np.float32,0x3e7a5ead,0xc0020da6,3 +np.float32,0x7ec4c744,0x42fd3da9,3 +np.float32,0x597e00,0xc2fd085a,3 +np.float32,0x3dff6bf4,0xc0403575,3 +np.float32,0x5d6f1a,0xc2fce883,3 +np.float32,0x7e21faff,0x42faadea,3 +np.float32,0x3e570fea,0xc01016c6,3 +np.float32,0x28e6b6,0xc2ff4ab7,3 +np.float32,0x7e77062d,0x42fbe5a3,3 +np.float32,0x74cac4,0xc2fc43b0,3 +np.float32,0x3f707273,0xbdb93078,3 +np.float32,0x228e96,0xc2ffc737,3 +np.float32,0x686ac1,0xc2fc966b,3 +np.float32,0x3d76400d,0xc081cae8,3 +np.float32,0x3e9f502f,0xbfd7966b,3 +np.float32,0x3f6bc656,0xbdf32b1f,3 +np.float32,0x3edb828b,0xbf9c65d4,3 +np.float32,0x6c6e56,0xc2fc7a8e,3 +np.float32,0x3f04552e,0xbf73b48f,3 +np.float32,0x3f39cb69,0xbeecc457,3 +np.float32,0x7f681c44,0x42ffb7a3,3 +np.float32,0x7f5b44ee,0x42ff8d99,3 +np.float32,0x3e71430a,0xc005798d,3 +np.float32,0x3edcfde3,0xbf9b27c6,3 +np.float32,0x3f616a5a,0xbe3bf67f,3 +np.float32,0x3f523936,0xbe918548,3 +np.float32,0x3f39ce3a,0xbeecb925,3 +np.float32,0x3eac589a,0xbfc91120,3 +np.float32,0x7efc8d3d,0x42fdf5fc,3 +np.float32,0x5704b0,0xc2fd1d0f,3 +np.float32,0x7e7972e9,0x42fbecda,3 +np.float32,0x3eb0811c,0xbfc4aa13,3 +np.float32,0x7f1efcbb,0x42fea023,3 +np.float32,0x3e0b9e32,0xc037fa6b,3 +np.float32,0x7eef6a48,0x42fdce87,3 +np.float32,0x3cc0a373,0xc0ad20c0,3 +np.float32,0x3f2a75bb,0xbf1632ba,3 +np.float32,0x0,0xff800000,3 +np.float32,0x7ecdb6f4,0x42fd5e77,3 +np.float32,0x7f2e2dfd,0x42fee38d,3 +np.float32,0x3ee17f6e,0xbf976d8c,3 +np.float32,0x3f51e7ee,0xbe92a319,3 +np.float32,0x3f06942f,0xbf6d7d3c,3 +np.float32,0x3f7ba528,0xbccac6f1,3 +np.float32,0x3f413787,0xbecfd513,3 +np.float32,0x3e085e48,0xc03a2716,3 +np.float32,0x7e4c5e0e,0x42fb599c,3 +np.float32,0x306f76,0xc2fecdd4,3 +np.float32,0x7f5c2203,0x42ff9081,3 +np.float32,0x3d5355b4,0xc088da05,3 +np.float32,0x9a2a,0xc305bb4f,3 +np.float32,0x3db93a1f,0xc05de0db,3 +np.float32,0x4e50c6,0xc2fd6ae4,3 +np.float32,0x7ec4afed,0x42fd3d51,3 +np.float32,0x3a8f27,0xc2fe41a0,3 +np.float32,0x7f213caf,0x42feaa84,3 +np.float32,0x7e7b5f00,0x42fbf286,3 +np.float32,0x7e367194,0x42fb05ca,3 +np.float32,0x7f56e6de,0x42ff7ebd,3 +np.float32,0x3ed7383e,0xbfa00aef,3 +np.float32,0x7e844752,0x42fc184a,3 +np.float32,0x15157,0xc3049a19,3 +np.float32,0x3f78cd92,0xbd28824a,3 +np.float32,0x7ecddb16,0x42fd5ef9,3 +np.float32,0x3e479f16,0xc016f7d8,3 +np.float32,0x3f5cb418,0xbe5b2bd3,3 +np.float32,0x7c0934cb,0x42f2334e,3 +np.float32,0x3ebe5505,0xbfb6bc69,3 +np.float32,0x3eb1335a,0xbfc3eff5,3 +np.float32,0x3f2488a3,0xbf234444,3 +np.float32,0x642906,0xc2fcb52a,3 +np.float32,0x3da635fa,0xc067e15a,3 +np.float32,0x7e0d80db,0x42fa4a15,3 +np.float32,0x4f0b9d,0xc2fd640a,3 +np.float32,0x7e083806,0x42fa2df8,3 +np.float32,0x7f77f8c6,0x42ffe877,3 +np.float32,0x3e7bb46a,0xc0018ff5,3 +np.float32,0x3f06eb2e,0xbf6c8eca,3 +np.float32,0x7eae8f7c,0x42fce52a,3 +np.float32,0x3de481a0,0xc04a7d7f,3 +np.float32,0x3eed4311,0xbf8e096f,3 +np.float32,0x3f7b0300,0xbce8903d,3 +np.float32,0x3811b,0xc30330dd,3 +np.float32,0x3eb6f8e1,0xbfbe04bc,3 +np.float32,0x3ec35210,0xbfb1f55a,3 +np.float32,0x3d386916,0xc08f24a5,3 +np.float32,0x3f1fa197,0xbf2e704d,3 +np.float32,0x7f2020a5,0x42fea56a,3 +np.float32,0x7e1ea53f,0x42fa9e8c,3 +np.float32,0x3f148903,0xbf490bf9,3 +np.float32,0x3f2f56a0,0xbf0bc6c9,3 +np.float32,0x7da9fc,0xc2fc0d9b,3 +np.float32,0x3d802134,0xc07fe810,3 +np.float32,0x3f6cb927,0xbde74e57,3 +np.float32,0x7e05b125,0x42fa2023,3 +np.float32,0x3f3307f9,0xbf041433,3 +np.float32,0x5666bf,0xc2fd2250,3 +np.float32,0x3f51c93b,0xbe930f28,3 +np.float32,0x3eb5dcfe,0xbfbf241e,3 +np.float32,0xb2773,0xc301853f,3 +np.float32,0x7f4dee96,0x42ff5f3f,3 +np.float32,0x3e3f5c33,0xc01adee1,3 +np.float32,0x3f2ed29a,0xbf0cdd4a,3 +np.float32,0x3e3c01ef,0xc01c80ab,3 +np.float32,0x3ec2236e,0xbfb31458,3 +np.float32,0x7e841dc4,0x42fc1761,3 +np.float32,0x3df2cd8e,0xc044e30c,3 +np.float32,0x3f010901,0xbf7d0670,3 +np.float32,0x3c05ceaa,0xc0ddf39b,3 +np.float32,0x3f517226,0xbe944206,3 +np.float32,0x3f23c83d,0xbf24f522,3 +np.float32,0x7fc9da,0xc2fc0139,3 +np.float32,0x7f1bde53,0x42fe9181,3 +np.float32,0x3ea3786c,0xbfd2d4a5,3 +np.float32,0x3e83a71b,0xbffacdd2,3 +np.float32,0x3f6f0d4f,0xbdca61d5,3 +np.float32,0x7f5ab613,0x42ff8bb7,3 +np.float32,0x3ab1ec,0xc2fe3fea,3 +np.float32,0x4fbf58,0xc2fd5d82,3 +np.float32,0x3dea141b,0xc0484403,3 +np.float32,0x7d86ad3b,0x42f8258f,3 +np.float32,0x7f345315,0x42fefd29,3 +np.float32,0x3f3752fe,0xbef6a780,3 +np.float32,0x64830d,0xc2fcb293,3 +np.float32,0x3d9dc1eb,0xc06cb32a,3 +np.float32,0x3f2f935a,0xbf0b46f6,3 +np.float32,0xb90a4,0xc30177e3,3 +np.float32,0x4111dd,0xc2fdf3c1,3 +np.float32,0x3d4cd078,0xc08a4c68,3 +np.float32,0x3e95c3f1,0xbfe30011,3 +np.float32,0x3ec9f356,0xbfabcb4e,3 +np.float32,0x1b90d5,0xc3003717,3 +np.float32,0xee70f,0xc3011a3e,3 +np.float32,0x7fa00000,0x7fe00000,3 +np.float32,0x3f74cdb6,0xbd8422af,3 +np.float32,0x3d9b56fe,0xc06e2037,3 +np.float32,0x3f1853df,0xbf3fbc40,3 +np.float32,0x7d86a011,0x42f82547,3 +np.float32,0x3dff9629,0xc0402634,3 +np.float32,0x46f8c9,0xc2fdb39f,3 +np.float32,0x3e9b410b,0xbfdc5a87,3 +np.float32,0x3f5aed42,0xbe671cac,3 +np.float32,0x3b739886,0xc101257f,3 +np.float64,0x3fe2f58d6565eb1b,0xbfe82a641138e19a,1 +np.float64,0x3fee7f0642fcfe0d,0xbfb1c702f6974932,1 +np.float64,0x25b71f244b6e5,0xc090030d3b3c5d2b,1 +np.float64,0x8c9cc8e1193b,0xc0900b752a678fa8,1 +np.float64,0x3fd329b5d326536c,0xbffbd607f6db945c,1 +np.float64,0x3fb5109b3a2a2136,0xc00cd36bd15dfb18,1 +np.float64,0x3fd5393ae12a7276,0xbff97a7e4a157154,1 +np.float64,0x3fd374d1b926e9a3,0xbffb7c3e1a3a7ed3,1 +np.float64,0x3fe2c7f4e2658fea,0xbfe899f15ca78fcb,1 +np.float64,0x7fe3d6b81ee7ad6f,0x408ffa7b63d407ee,1 +np.float64,0x3fe086d097e10da1,0xbfee81456ce8dd03,1 +np.float64,0x7fd374a64ca6e94c,0x408ff241c7306d39,1 +np.float64,0x3fc0709a5b20e135,0xc007afdede31b29c,1 +np.float64,0x3fd4218f4b28431f,0xbffab2c696966e2d,1 +np.float64,0x143134c828628,0xc09006a8372c4d8a,1 +np.float64,0x3f8bd0aa0037a154,0xc018cf0e8b9c3107,1 +np.float64,0x7fe0ce905ee19d20,0x408ff8915e71bd67,1 +np.float64,0x3fda0f5f32b41ebe,0xbff4bd5e0869e820,1 +np.float64,0x7fe9ae63d0b35cc7,0x408ffd760ca4f292,1 +np.float64,0x3fe75abd9eeeb57b,0xbfdd1476fc8b3089,1 +np.float64,0x786c3110f0d87,0xc08ff8b44cedbeea,1 +np.float64,0x22c5fe80458d,0xc09013853591c2f2,1 +np.float64,0x3fdc250797384a0f,0xbff2f6a02c961f0b,1 +np.float64,0x3fa2b367b02566cf,0xc013199238485054,1 +np.float64,0x3fd26a910ca4d522,0xbffcc0e2089b1c0c,1 +np.float64,0x8068d3b300d1b,0xc08ff7f690210aac,1 +np.float64,0x3fe663bfa9ecc77f,0xbfe07cd95a43a5ce,1 +np.float64,0x3fd0ddb07321bb61,0xbffec886665e895e,1 +np.float64,0x3f91c730b0238e61,0xc0176452badc8d22,1 +np.float64,0x4dd10d309ba22,0xc08ffdbe738b1d8d,1 +np.float64,0x7fe322afa4a6455e,0x408ffa10c038f9de,1 +np.float64,0x7fdf7f7c42befef8,0x408ff7d147ddaad5,1 +np.float64,0x7fd673f386ace7e6,0x408ff3e920d00eef,1 +np.float64,0x3feaebfcadb5d7f9,0xbfcfe8ec27083478,1 +np.float64,0x3fdc6dc23738db84,0xbff2bb46794f07b8,1 +np.float64,0xcd8819599b103,0xc08ff288c5b2cf0f,1 +np.float64,0xfda00e77fb402,0xc08ff01b895d2236,1 +np.float64,0x840b02ff08161,0xc08ff7a41e41114c,1 +np.float64,0x3fbdce3a383b9c74,0xc008d1e61903a289,1 +np.float64,0x3fd24ed3c4a49da8,0xbffce3c12136b6d3,1 +np.float64,0x3fe8d0834131a107,0xbfd77b194e7051d4,1 +np.float64,0x3fdd0cb11aba1962,0xbff23b9dbd554455,1 +np.float64,0x1a32d97e3465c,0xc090052781a37271,1 +np.float64,0x3fdb09d2b1b613a5,0xbff3e396b862bd83,1 +np.float64,0x3fe04c848aa09909,0xbfef2540dd90103a,1 +np.float64,0x3fce0c48613c1891,0xc000b9f76877d744,1 +np.float64,0x3fc37109a226e213,0xc005c05d8b2b9a2f,1 +np.float64,0x81cf3837039e7,0xc08ff7d686517dff,1 +np.float64,0xd9342c29b2686,0xc08ff1e591c9a895,1 +np.float64,0x7fec731b0638e635,0x408ffea4884550a9,1 +np.float64,0x3fba0fc138341f82,0xc00a5e839b085f64,1 +np.float64,0x7fdda893b03b5126,0x408ff71f7c5a2797,1 +np.float64,0xd2a4bb03a5498,0xc08ff2402f7a907c,1 +np.float64,0x3fea61fb0d34c3f6,0xbfd1d293fbe76183,1 +np.float64,0x3fed5cf486fab9e9,0xbfbfc2e01a7ffff1,1 +np.float64,0x3fcbabc2bf375785,0xc001ad7750c9dbdf,1 +np.float64,0x3fdb5fff53b6bfff,0xbff39a7973a0c6a5,1 +np.float64,0x7feef05a00bde0b3,0x408fff9c5cbc8651,1 +np.float64,0xb1cf24f1639e5,0xc08ff434de10fffb,1 +np.float64,0x3fa583989c2b0731,0xc0124a8a3bbf18ce,1 +np.float64,0x7feae90bf9f5d217,0x408ffe002e7bbbea,1 +np.float64,0x3fe9ef41c4b3de84,0xbfd367878ae4528e,1 +np.float64,0x9be24ce337c4a,0xc08ff5b9b1c31cf9,1 +np.float64,0x3fe916894cb22d13,0xbfd677f915d58503,1 +np.float64,0x3fec1bab20f83756,0xbfc7f2777aabe8ee,1 +np.float64,0x3feaabf2873557e5,0xbfd0d11f28341233,1 +np.float64,0x3fd4d3c3b529a787,0xbff9e9e47acc8ca9,1 +np.float64,0x3fe4cfe96c699fd3,0xbfe3dc53fa739169,1 +np.float64,0xccfdb97399fb7,0xc08ff2908d893400,1 +np.float64,0x3fec7598be78eb31,0xbfc5a750f8f3441a,1 +np.float64,0x355be5fc6ab7e,0xc090010ca315b50b,1 +np.float64,0x3fba9f9074353f21,0xc00a1f80eaf5e581,1 +np.float64,0x7fdcaff189395fe2,0x408ff6bd1c5b90d9,1 +np.float64,0x3fd94d3b64b29a77,0xbff56be1b43d25f3,1 +np.float64,0x4e5f29949cbe6,0xc08ffda972da1d73,1 +np.float64,0x3fe654e2d9aca9c6,0xbfe09b88dcd8f15d,1 +np.float64,0x7fdc130190b82602,0x408ff67d496c1a27,1 +np.float64,0x3fbcd4701e39a8e0,0xc009343e36627e80,1 +np.float64,0x7fdaa4d38f3549a6,0x408ff5e2c6d8678f,1 +np.float64,0x3febe95e5237d2bd,0xbfc93e16d453fe3a,1 +np.float64,0x9ef5ca553deba,0xc08ff57ff4f7883d,1 +np.float64,0x7fe878e91170f1d1,0x408ffce795868fc8,1 +np.float64,0x3fe63dff466c7bff,0xbfe0caf2b79c9e5f,1 +np.float64,0x6561446ccac29,0xc08ffab0e383834c,1 +np.float64,0x30c6c2ae618d9,0xc09001914b30381b,1 +np.float64,0x7ff0000000000000,0x7ff0000000000000,1 +np.float64,0x3fe5c9daf1ab93b6,0xbfe1be81baf4dbdb,1 +np.float64,0x3fe0a03e24a1407c,0xbfee3a73c4c0e8f8,1 +np.float64,0xff2a2cf3fe546,0xc08ff009a7e6e782,1 +np.float64,0x7fcf0332213e0663,0x408fefa36235e210,1 +np.float64,0x3fb612affc2c2560,0xc00c494be9c8c33b,1 +np.float64,0x3fd2b259702564b3,0xbffc67967f077e75,1 +np.float64,0x7fcb63685d36c6d0,0x408fee343343f913,1 +np.float64,0x3fe369f1d5a6d3e4,0xbfe71251139939ad,1 +np.float64,0x3fdd17c618ba2f8c,0xbff232d11c986251,1 +np.float64,0x3f92cc8040259901,0xc01711d8e06b52ee,1 +np.float64,0x69a81dc2d3504,0xc08ffa36cdaf1141,1 +np.float64,0x3fea0fad99b41f5b,0xbfd2f4625a652645,1 +np.float64,0xd1cd5799a39ab,0xc08ff24c02b90d26,1 +np.float64,0x324e59ce649cc,0xc0900163ad091c76,1 +np.float64,0x3fc3d460a227a8c1,0xc00585f903dc7a7f,1 +np.float64,0xa7185ec74e30c,0xc08ff4ec7d65ccd9,1 +np.float64,0x3fa254eaac24a9d5,0xc01337053963321a,1 +np.float64,0x3feaeb112435d622,0xbfcfef3be17f81f6,1 +np.float64,0x60144c3ac028a,0xc08ffb4f8eb94595,1 +np.float64,0x7fa4d2ec6829a5d8,0x408fdb0a9670ab83,1 +np.float64,0x3fed1372f97a26e6,0xbfc1b1fe50d48a55,1 +np.float64,0x3fd5ade5972b5bcb,0xbff8fcf28f525031,1 +np.float64,0x7fe72e335bee5c66,0x408ffc4759236437,1 +np.float64,0x7fdfafab143f5f55,0x408ff7e2e22a8129,1 +np.float64,0x3fe90d0db9321a1b,0xbfd69ae5fe10eb9e,1 +np.float64,0x7fe20a59072414b1,0x408ff962a2492484,1 +np.float64,0x3fed853690bb0a6d,0xbfbdc9dc5f199d2b,1 +np.float64,0x3fd709d469ae13a9,0xbff795a218deb700,1 +np.float64,0x3fe21c35f5e4386c,0xbfea47d71789329b,1 +np.float64,0x9ea5ec053d4be,0xc08ff585c2f6b7a3,1 +np.float64,0x3fc0580f9e20b01f,0xc007c1268f49d037,1 +np.float64,0xd99127abb3225,0xc08ff1e0a1ff339d,1 +np.float64,0x3fdc8c9bbfb91937,0xbff2a2478354effb,1 +np.float64,0x3fe15fc6b162bf8d,0xbfec323ac358e008,1 +np.float64,0xffefffffffffffff,0x7ff8000000000000,1 +np.float64,0x3fee341afb3c6836,0xbfb556b6faee9a84,1 +np.float64,0x3fe4b64c56296c99,0xbfe4154835ad2afe,1 +np.float64,0x85de22810bbc5,0xc08ff77b914fe5b5,1 +np.float64,0x3fd22c72e3a458e6,0xbffd0f4269d20bb9,1 +np.float64,0xc090e5218123,0xc09009a4a65a8a8f,1 +np.float64,0x7fd9641692b2c82c,0x408ff5547782bdfc,1 +np.float64,0x3fd9b9cb28b37396,0xbff509a8fb59a9f1,1 +np.float64,0x3fcd2726f93a4e4e,0xc001135059a22117,1 +np.float64,0x3fa4b493d4296928,0xc0128323c7a55f4a,1 +np.float64,0x47455e788e8ac,0xc08ffec2101c1e82,1 +np.float64,0x3fe0d7e2e261afc6,0xbfeda0f1e2d0f4bd,1 +np.float64,0x3fe860fc5b70c1f9,0xbfd91dc42eaf72c2,1 +np.float64,0xa5d7805b4baf0,0xc08ff502bc819ff6,1 +np.float64,0xd83395b1b0673,0xc08ff1f33c3f94c2,1 +np.float64,0x3f865972e02cb2e6,0xc01a1243651565c8,1 +np.float64,0x52fc6952a5f8e,0xc08ffd006b158179,1 +np.float64,0x7fecac6c793958d8,0x408ffebbb1c09a70,1 +np.float64,0x7fe621ff606c43fe,0x408ffbbeb2b1473a,1 +np.float64,0x3fdb9f3f9db73e7f,0xbff365610c52bda7,1 +np.float64,0x7feab92992757252,0x408ffdeb92a04813,1 +np.float64,0xcc46c79f988d9,0xc08ff29adf03fb7c,1 +np.float64,0x3fe3156a03262ad4,0xbfe7dd0f598781c7,1 +np.float64,0x3fc00e3a61201c75,0xc007f5c121a87302,1 +np.float64,0x3fdce8e9f739d1d4,0xbff2581d41ef50ef,1 +np.float64,0x0,0xfff0000000000000,1 +np.float64,0x7d373ac4fa6e8,0xc08ff840fa8beaec,1 +np.float64,0x3fee41e0653c83c1,0xbfb4ae786f2a0d54,1 +np.float64,0x3ff0000000000000,0x0,1 +np.float64,0x7feca6fff9794dff,0x408ffeb982a70556,1 +np.float64,0x7fc532716d2a64e2,0x408feb3f0f6c095b,1 +np.float64,0x3fe4ec2954a9d853,0xbfe39dd44aa5a040,1 +np.float64,0x7fd3321d52a6643a,0x408ff21a0ab9cd85,1 +np.float64,0x7fd8f1b2dfb1e365,0x408ff52001fa7922,1 +np.float64,0x3fee5e58cabcbcb2,0xbfb3539734a24d8b,1 +np.float64,0x3feebf6e7dfd7edd,0xbfad7c648f025102,1 +np.float64,0x6008026ec0101,0xc08ffb5108b54a93,1 +np.float64,0x3fea06f5e2340dec,0xbfd3134a48283360,1 +np.float64,0x41cad13c8395b,0xc08fffae654b2426,1 +np.float64,0x7fedb5c9353b6b91,0x408fff249f1f32b6,1 +np.float64,0xe00c5af9c018c,0xc08ff189e68c655f,1 +np.float64,0x7feac398ddf58731,0x408ffdf01374de9f,1 +np.float64,0x3fed21127c7a4225,0xbfc15b8cf55628fa,1 +np.float64,0x3fd3446711a688ce,0xbffbb5f7252a9fa3,1 +np.float64,0x7fe75fa07a6ebf40,0x408ffc5fdb096018,1 +np.float64,0x3feeb1618cbd62c3,0xbfaece3bd0863070,1 +np.float64,0x7f5226e180244dc2,0x408fb174d506e52f,1 +np.float64,0x3fcd67deca3acfbe,0xc000f9cd7a490749,1 +np.float64,0xdc6f30efb8de6,0xc08ff1b9f2a22d2e,1 +np.float64,0x9c14931338293,0xc08ff5b5f975ec5d,1 +np.float64,0x7fe93e802df27cff,0x408ffd4354eba0e0,1 +np.float64,0x3feb92ae5077255d,0xbfcb7f2084e44dbb,1 +np.float64,0xd78dbfddaf1b8,0xc08ff1fc19fa5a13,1 +np.float64,0x7fe14c301fa2985f,0x408ff8e666cb6592,1 +np.float64,0xbda3d8b77b47b,0xc08ff37689f4b2e5,1 +np.float64,0x8a42953b14853,0xc08ff71c2db3b8cf,1 +np.float64,0x7fe4ca7e186994fb,0x408ffb05e94254a7,1 +np.float64,0x7fe92ffc5e325ff8,0x408ffd3cb0265b12,1 +np.float64,0x91b262912364d,0xc08ff681619be214,1 +np.float64,0x33fe2b0667fc6,0xc0900132f3fab55e,1 +np.float64,0x3fde10e9183c21d2,0xbff17060fb4416c7,1 +np.float64,0xb6b811cb6d702,0xc08ff3e46303b541,1 +np.float64,0x3fe4a7bda0a94f7b,0xbfe435c6481cd0e3,1 +np.float64,0x7fd9fe6057b3fcc0,0x408ff599c79a822c,1 +np.float64,0x3fef44bf917e897f,0xbfa11484e351a6e9,1 +np.float64,0x3fe57d701daafae0,0xbfe2618ab40fc01b,1 +np.float64,0x7fe52d2adbaa5a55,0x408ffb3c2fb1c99d,1 +np.float64,0xb432f66d6865f,0xc08ff40d6b4084fe,1 +np.float64,0xbff0000000000000,0x7ff8000000000000,1 +np.float64,0x7fecd2292bf9a451,0x408ffecad860de6f,1 +np.float64,0x3fddd2ae153ba55c,0xbff1a059adaca33e,1 +np.float64,0x3fee55d6e5bcabae,0xbfb3bb1c6179d820,1 +np.float64,0x7fc1d0085623a010,0x408fe93d16ada7a7,1 +np.float64,0x829b000105360,0xc08ff7c47629a68f,1 +np.float64,0x7fe1e0257523c04a,0x408ff94782cf0717,1 +np.float64,0x7fd652f9ad2ca5f2,0x408ff3d820ec892e,1 +np.float64,0x3fef2246203e448c,0xbfa444ab6209d8cd,1 +np.float64,0x3fec6c0ae178d816,0xbfc5e559ebd4e790,1 +np.float64,0x3fe6ddfee92dbbfe,0xbfdf06dd7d3fa7a8,1 +np.float64,0x3fb7fbcbea2ff798,0xc00b5404d859d148,1 +np.float64,0x7feb9a154d37342a,0x408ffe4b26c29e55,1 +np.float64,0x3fe4db717aa9b6e3,0xbfe3c2c6b3ef13bc,1 +np.float64,0x3fbae17dda35c2fc,0xc00a030f7f4b37e7,1 +np.float64,0x7fd632b9082c6571,0x408ff3c76826ef19,1 +np.float64,0x7fc4184a15283093,0x408feaa14adf00be,1 +np.float64,0x3fe052d19920a5a3,0xbfef136b5df81a3e,1 +np.float64,0x7fe38b872b67170d,0x408ffa4f51aafc86,1 +np.float64,0x3fef9842d03f3086,0xbf92d3d2a21d4be2,1 +np.float64,0x9cea662139d4d,0xc08ff5a634810daa,1 +np.float64,0x3fe35f0855e6be11,0xbfe72c4b564e62aa,1 +np.float64,0x3fecee3d3779dc7a,0xbfc29ee942f8729e,1 +np.float64,0x3fe7903fd72f2080,0xbfdc41db9b5f4048,1 +np.float64,0xb958889572b11,0xc08ff3ba366cf84b,1 +np.float64,0x3fcb3a67c53674d0,0xc001dd21081ad1ea,1 +np.float64,0xe3b1b53fc7637,0xc08ff15a3505e1ce,1 +np.float64,0xe5954ae9cb2aa,0xc08ff141cbbf0ae4,1 +np.float64,0x3fe394af74e7295f,0xbfe6ad1d13f206e8,1 +np.float64,0x7fe21dd704643bad,0x408ff96f13f80c1a,1 +np.float64,0x3fd23a7cf02474fa,0xbffcfd7454117a05,1 +np.float64,0x7fe257515e24aea2,0x408ff99378764d52,1 +np.float64,0x7fe4c5d0a6e98ba0,0x408ffb03503cf939,1 +np.float64,0x3fadc2c1603b8583,0xc0106b2c17550e3a,1 +np.float64,0x3fc0f7f02421efe0,0xc007525ac446864c,1 +np.float64,0x3feaf0b27275e165,0xbfcfc8a03eaa32ad,1 +np.float64,0x5ce7503cb9ceb,0xc08ffbb2de365fa8,1 +np.float64,0x2a0014f654003,0xc090026e41761a0d,1 +np.float64,0x7fe2c848a8e59090,0x408ff9d9b723ee89,1 +np.float64,0x7f66f54bc02dea97,0x408fbc2ae0ec5623,1 +np.float64,0xa35a890146b6,0xc0900a97b358ddbd,1 +np.float64,0x7fee267ded7c4cfb,0x408fff501560c9f5,1 +np.float64,0x3fe07c328520f865,0xbfee9ef7c3435b58,1 +np.float64,0x3fe67122cf6ce246,0xbfe06147001932ba,1 +np.float64,0x3fdacc8925359912,0xbff41824cece219e,1 +np.float64,0xffa3047fff461,0xc08ff00431ec9be3,1 +np.float64,0x3e1af43e7c35f,0xc090002c6573d29b,1 +np.float64,0x86fa94590df53,0xc08ff7632525ed92,1 +np.float64,0x7fec4c76227898eb,0x408ffe94d032c657,1 +np.float64,0x7fe2274ce1e44e99,0x408ff975194cfdff,1 +np.float64,0x7fe670e1b4ace1c2,0x408ffbe78cc451de,1 +np.float64,0x7fe853871db0a70d,0x408ffcd5e6a6ff47,1 +np.float64,0x3fcbf265db37e4cc,0xc0019026336e1176,1 +np.float64,0x3fef033cef3e067a,0xbfa726712eaae7f0,1 +np.float64,0x5d74973abae94,0xc08ffba15e6bb992,1 +np.float64,0x7fdd9c99b6bb3932,0x408ff71ad24a7ae0,1 +np.float64,0xbdc8e09b7b91c,0xc08ff3744939e9a3,1 +np.float64,0xdbfcff71b7fa0,0xc08ff1bfeecc9dfb,1 +np.float64,0xf9b38cf5f3672,0xc08ff0499af34a43,1 +np.float64,0x3fea820aa6b50415,0xbfd162a38e1927b1,1 +np.float64,0x3fe67f59a12cfeb3,0xbfe04412adca49dc,1 +np.float64,0x3feb301d9c76603b,0xbfce17e6edeb92d5,1 +np.float64,0x828ce00b0519c,0xc08ff7c5b5c57cde,1 +np.float64,0x4f935e229f26c,0xc08ffd7c67c1c54f,1 +np.float64,0x7fcd139e023a273b,0x408feee4f12ff11e,1 +np.float64,0x666a9944ccd54,0xc08ffa92d5e5cd64,1 +np.float64,0x3fe792f0fa6f25e2,0xbfdc374fda28f470,1 +np.float64,0xe996029bd32c1,0xc08ff10eb9b47a11,1 +np.float64,0x3fe7b0dd1eef61ba,0xbfdbc2676dc77db0,1 +np.float64,0x7fd3ec0127a7d801,0x408ff287bf47e27d,1 +np.float64,0x3fe793a8ea6f2752,0xbfdc347f7717e48d,1 +np.float64,0x7fdb89d15e3713a2,0x408ff64457a13ea2,1 +np.float64,0x3fe35b3cbbe6b679,0xbfe73557c8321b70,1 +np.float64,0x66573c94ccae8,0xc08ffa9504af7eb5,1 +np.float64,0x3fc620a2302c4144,0xc00442036b944a67,1 +np.float64,0x49b2fe0693660,0xc08ffe5f131c3c7e,1 +np.float64,0x7fda936cdfb526d9,0x408ff5db3ab3f701,1 +np.float64,0xc774ceef8ee9a,0xc08ff2e16d082fa1,1 +np.float64,0x4da9f8a09b55,0xc0900ee2206d0c88,1 +np.float64,0x3fe2ca5d5ae594bb,0xbfe89406611a5f1a,1 +np.float64,0x7fe0832497e10648,0x408ff85d1de6056e,1 +np.float64,0x3fe6a9e3222d53c6,0xbfdfda35a9bc2de1,1 +np.float64,0x3fed3d92c8ba7b26,0xbfc0a73620db8b98,1 +np.float64,0x3fdd2ec093ba5d81,0xbff2209cf78ce3f1,1 +np.float64,0x62fcb968c5f98,0xc08ffaf775a593c7,1 +np.float64,0xfcfb019ff9f60,0xc08ff0230e95bd16,1 +np.float64,0x3fd7a63e8f2f4c7d,0xbff6faf4fff7dbe0,1 +np.float64,0x3fef23b0ec3e4762,0xbfa4230cb176f917,1 +np.float64,0x340d1e6a681a5,0xc09001314b68a0a2,1 +np.float64,0x7fc0b85ba02170b6,0x408fe8821487b802,1 +np.float64,0x7fe9976e84f32edc,0x408ffd6bb6aaf467,1 +np.float64,0x329a0e9e65343,0xc090015b044e3270,1 +np.float64,0x3fea4928d3f49252,0xbfd2299b05546eab,1 +np.float64,0x3f188c70003118e0,0xc02ac3ce23bc5d5a,1 +np.float64,0x3fecce5020b99ca0,0xbfc36b23153d5f50,1 +np.float64,0x3fe203873e24070e,0xbfea86edb3690830,1 +np.float64,0x3fe02d9eaa205b3d,0xbfef7d18c54a76d2,1 +np.float64,0xef7537ebdeea7,0xc08ff0c55e9d89e7,1 +np.float64,0x3fedf7572efbeeae,0xbfb840af357cf07c,1 +np.float64,0xd1a97a61a354,0xc0900926fdfb96cc,1 +np.float64,0x7fe6a0daeced41b5,0x408ffc001edf1407,1 +np.float64,0x3fe5063625aa0c6c,0xbfe3647cfb949d62,1 +np.float64,0x7fe9b28d31736519,0x408ffd77eb4a922b,1 +np.float64,0x7feea90d033d5219,0x408fff81a4bbff62,1 +np.float64,0x3fe9494d17f2929a,0xbfd5bde02eb5287a,1 +np.float64,0x7feee17a8cbdc2f4,0x408fff96cf0dc16a,1 +np.float64,0xb2ad18ef655a3,0xc08ff4267eda8af8,1 +np.float64,0x3fad3b52683a76a5,0xc01085ab75b797ce,1 +np.float64,0x2300a65846016,0xc090037b81ce9500,1 +np.float64,0x3feb1041f9b62084,0xbfcef0c87d8b3249,1 +np.float64,0x3fdd887d3e3b10fa,0xbff1da0e1ede6db2,1 +np.float64,0x3fd3e410eb27c822,0xbffaf9b5fc9cc8cc,1 +np.float64,0x3fe0aa53e3e154a8,0xbfee1e7b5c486578,1 +np.float64,0x7fe33e389aa67c70,0x408ffa214fe50961,1 +np.float64,0x3fd27e3a43a4fc75,0xbffca84a79e8adeb,1 +np.float64,0x3fb309e0082613c0,0xc00dfe407b77a508,1 +np.float64,0x7feaf2ed8cf5e5da,0x408ffe046a9d1ba9,1 +np.float64,0x1e76167a3cec4,0xc0900448cd35ec67,1 +np.float64,0x3fe0a18e1721431c,0xbfee36cf1165a0d4,1 +np.float64,0x3fa73b78c02e76f2,0xc011d9069823b172,1 +np.float64,0x3fef6d48287eda90,0xbf9ab2d08722c101,1 +np.float64,0x8fdf0da31fbe2,0xc08ff6a6a2accaa1,1 +np.float64,0x3fc3638db826c71b,0xc005c86191688826,1 +np.float64,0xaa9c09c555381,0xc08ff4aefe1d9473,1 +np.float64,0x7fccb0f4523961e8,0x408feebd84773f23,1 +np.float64,0xede75dcfdbcec,0xc08ff0d89ba887d1,1 +np.float64,0x7f8a051520340a29,0x408fcd9cc17f0d95,1 +np.float64,0x3fef5ca2babeb945,0xbf9dc221f3618e6a,1 +np.float64,0x7fea0ff4bcf41fe8,0x408ffda193359f22,1 +np.float64,0x7fe05c53fd20b8a7,0x408ff841dc7123e8,1 +np.float64,0x3fc625664b2c4acd,0xc0043f8749b9a1d8,1 +np.float64,0x7fed58f98f7ab1f2,0x408fff00585f48c2,1 +np.float64,0x3fb3e5e51427cbca,0xc00d7bcb6528cafe,1 +np.float64,0x3fe728bd3d6e517a,0xbfdddafa72bd0f60,1 +np.float64,0x3fe3f005dd27e00c,0xbfe5d7b3ec93bca0,1 +np.float64,0x3fd74fbd1a2e9f7a,0xbff750001b63ce81,1 +np.float64,0x3fd3af6d85a75edb,0xbffb371d678d11b4,1 +np.float64,0x7fa690ad8c2d215a,0x408fdbf7db9c7640,1 +np.float64,0x3fbdfd38e23bfa72,0xc008bfc1c5c9b89e,1 +np.float64,0x3fe2374684a46e8d,0xbfea030c4595dfba,1 +np.float64,0x7fc0806c372100d7,0x408fe85b36fee334,1 +np.float64,0x3fef3ac47b7e7589,0xbfa2007195c5213f,1 +np.float64,0x3fb55473922aa8e7,0xc00cae7af8230e0c,1 +np.float64,0x7fe018dc152031b7,0x408ff811e0d712fa,1 +np.float64,0x3fe3b3fca56767f9,0xbfe6638ae2c99c62,1 +np.float64,0x7fac79818c38f302,0x408fdea720b39c3c,1 +np.float64,0x7fefffffffffffff,0x4090000000000000,1 +np.float64,0xd2b290cba5652,0xc08ff23f6d7152a6,1 +np.float64,0x7fc5848eb52b091c,0x408feb6b6f8b77d0,1 +np.float64,0xf399f62de733f,0xc08ff092ae319ad8,1 +np.float64,0x7fdec56c12bd8ad7,0x408ff78c4ddbc667,1 +np.float64,0x3fca640f1e34c81e,0xc0023969c5cbfa4c,1 +np.float64,0x3fd55225db2aa44c,0xbff95f7442a2189e,1 +np.float64,0x7fefa009a97f4012,0x408fffdd2f42ef9f,1 +np.float64,0x4a3b70609478,0xc0900f24e449bc3d,1 +np.float64,0x7fe3738b1ba6e715,0x408ffa411f2cb5e7,1 +np.float64,0x7fe5e53f0b6bca7d,0x408ffb9ed8d95cea,1 +np.float64,0x3fe274dd24a4e9ba,0xbfe967fb114b2a83,1 +np.float64,0x3fcbc58b8c378b17,0xc001a2bb1e158bcc,1 +np.float64,0x3fefc2c0043f8580,0xbf862c9b464dcf38,1 +np.float64,0xc2c4fafd858a0,0xc08ff327aecc409b,1 +np.float64,0x3fd8bc39a9b17873,0xbff5f1ad46e5a51c,1 +np.float64,0x3fdf341656be682d,0xbff094f41e7cb4c4,1 +np.float64,0x3fef8495c13f092c,0xbf966cf6313bae4c,1 +np.float64,0x3fe14e0f05229c1e,0xbfec6166f26b7161,1 +np.float64,0x3fed42d3b2ba85a7,0xbfc0860b773d35d8,1 +np.float64,0x7fd92bbac5b25775,0x408ff53abcb3fe0c,1 +np.float64,0xb1635b6f62c6c,0xc08ff43bdf47accf,1 +np.float64,0x4a3a2dbc94746,0xc08ffe49fabddb36,1 +np.float64,0x87d831290fb06,0xc08ff750419dc6fb,1 +np.float64,0x3fec4713f7f88e28,0xbfc6d6217c9f5cf9,1 +np.float64,0x7fed43ba2d3a8773,0x408ffef7fa2fc303,1 +np.float64,0x7fd1ec5b56a3d8b6,0x408ff14f62615f1e,1 +np.float64,0x3fee534b6c7ca697,0xbfb3da1951aa3e68,1 +np.float64,0x3febb564c2b76aca,0xbfca9737062e55e7,1 +np.float64,0x943e6b0f287ce,0xc08ff64e2d09335c,1 +np.float64,0xf177d957e2efb,0xc08ff0acab2999fa,1 +np.float64,0x7fb5b881a82b7102,0x408fe3872b4fde5e,1 +np.float64,0x3fdb2b4a97b65695,0xbff3c715c91359bc,1 +np.float64,0x3fac0a17e4381430,0xc010c330967309fb,1 +np.float64,0x7fd8057990b00af2,0x408ff4b0a287a348,1 +np.float64,0x1f9026a23f206,0xc09004144f3a19dd,1 +np.float64,0x3fdb2977243652ee,0xbff3c8a2fd05803d,1 +np.float64,0x3fe0f6e74b21edcf,0xbfed4c3bb956bae0,1 +np.float64,0xde9cc3bbbd399,0xc08ff19ce5c1e762,1 +np.float64,0x3fe72ce106ae59c2,0xbfddca7ab14ceba2,1 +np.float64,0x3fa8ee14e031dc2a,0xc01170d54ca88e86,1 +np.float64,0x3fe0b09bbb216137,0xbfee0d189a95b877,1 +np.float64,0x7fdfdcb157bfb962,0x408ff7f33cf2afea,1 +np.float64,0x3fef84d5f53f09ac,0xbf966134e2a154f4,1 +np.float64,0x3fea0e0b1bb41c16,0xbfd2fa2d36637d19,1 +np.float64,0x1ab76fd6356ef,0xc090050a9616ffbd,1 +np.float64,0x7fd0ccf79a2199ee,0x408ff09045af2dee,1 +np.float64,0x7fea929345f52526,0x408ffddadc322b07,1 +np.float64,0x3fe9ef629cf3dec5,0xbfd367129c166838,1 +np.float64,0x3feedf0ea2fdbe1d,0xbfaa862afca44c00,1 +np.float64,0x7fce725f723ce4be,0x408fef6cfd2769a8,1 +np.float64,0x7fe4313b3ca86275,0x408ffaaf9557ef8c,1 +np.float64,0xe2d46463c5a8d,0xc08ff165725c6b08,1 +np.float64,0x7fbacb4ace359695,0x408fe5f3647bd0d5,1 +np.float64,0x3fbafd009635fa01,0xc009f745a7a5c5d5,1 +np.float64,0x3fe3cea66ce79d4d,0xbfe6253b895e2838,1 +np.float64,0x7feaa71484354e28,0x408ffde3c0bad2a6,1 +np.float64,0x3fd755b8b42eab71,0xbff74a1444c6e654,1 +np.float64,0x3fc313e2172627c4,0xc005f830e77940c3,1 +np.float64,0x12d699a225ad4,0xc090070ec00f2338,1 +np.float64,0x3fa975fe8432ebfd,0xc01151b3da48b3f9,1 +np.float64,0x7fdce3103b39c61f,0x408ff6d19b3326fa,1 +np.float64,0x7fd341cbba268396,0x408ff2237490fdca,1 +np.float64,0x3fd8405885b080b1,0xbff6666d8802a7d5,1 +np.float64,0x3fe0f0cca3a1e199,0xbfed5cdb3e600791,1 +np.float64,0x7fbd56680c3aaccf,0x408fe6ff55bf378d,1 +np.float64,0x3f939c4f3027389e,0xc016d364dd6313fb,1 +np.float64,0x3fe9e87fac73d0ff,0xbfd37f9a2be4fe38,1 +np.float64,0x7fc93c6a883278d4,0x408fed4260e614f1,1 +np.float64,0x7fa88c0ff031181f,0x408fdcf09a46bd3a,1 +np.float64,0xd5487f99aa910,0xc08ff21b6390ab3b,1 +np.float64,0x3fe34acc96e69599,0xbfe75c9d290428fb,1 +np.float64,0x3fd17f5964a2feb3,0xbffdef50b524137b,1 +np.float64,0xe23dec0dc47be,0xc08ff16d1ce61dcb,1 +np.float64,0x3fec8bd64fb917ad,0xbfc5173941614b8f,1 +np.float64,0x3fc81d97d7303b30,0xc00343ccb791401d,1 +np.float64,0x7fe79ad18e2f35a2,0x408ffc7cf0ab0f2a,1 +np.float64,0x3f96306b402c60d7,0xc0161ce54754cac1,1 +np.float64,0xfb09fc97f6140,0xc08ff039d1d30123,1 +np.float64,0x3fec9c4afa793896,0xbfc4ace43ee46079,1 +np.float64,0x3f9262dac824c5b6,0xc01732a3a7eeb598,1 +np.float64,0x3fa5cd33f42b9a68,0xc01236ed4d315a3a,1 +np.float64,0x3fe7bb336caf7667,0xbfdb9a268a82e267,1 +np.float64,0xc6c338f98d867,0xc08ff2ebb8475bbc,1 +np.float64,0x3fd50714482a0e29,0xbff9b14a9f84f2c2,1 +np.float64,0xfff0000000000000,0x7ff8000000000000,1 +np.float64,0x3fde2cd0f93c59a2,0xbff15afe35a43a37,1 +np.float64,0xf1719cb9e2e34,0xc08ff0acf77b06d3,1 +np.float64,0xfd3caaf9fa796,0xc08ff020101771bd,1 +np.float64,0x7f750d63a02a1ac6,0x408fc32ad0caa362,1 +np.float64,0x7fcc50f4e238a1e9,0x408fee96a5622f1a,1 +np.float64,0x421d1da0843a4,0xc08fff9ffe62d869,1 +np.float64,0x3fd9e17023b3c2e0,0xbff4e631d687ee8e,1 +np.float64,0x3fe4999a09693334,0xbfe4556b3734c215,1 +np.float64,0xd619ef03ac33e,0xc08ff21013c85529,1 +np.float64,0x3fc4da522229b4a4,0xc004f150b2c573aa,1 +np.float64,0x3feb04b053b60961,0xbfcf3fc9e00ebc40,1 +np.float64,0x3fbedec5ea3dbd8c,0xc0086a33dc22fab5,1 +np.float64,0x7fec3b217ab87642,0x408ffe8dbc8ca041,1 +np.float64,0xdb257d33b64b0,0xc08ff1cb42d3c182,1 +np.float64,0x7fa2d92ec025b25d,0x408fd9e414d11cb0,1 +np.float64,0x3fa425c550284b8b,0xc012ab7cbf83be12,1 +np.float64,0x10b4869021692,0xc09007c0487d648a,1 +np.float64,0x7f97918c902f2318,0x408fd47867806574,1 +np.float64,0x3fe4f91238e9f224,0xbfe38160b4e99919,1 +np.float64,0x3fc2b1af6125635f,0xc00634343bc58461,1 +np.float64,0x3fc2a98071255301,0xc0063942bc8301be,1 +np.float64,0x3fe4cfc585299f8b,0xbfe3dca39f114f34,1 +np.float64,0x3fd1ea75b3a3d4eb,0xbffd63acd02c5406,1 +np.float64,0x3fd6bf48492d7e91,0xbff7e0cd249f80f9,1 +np.float64,0x76643d36ecc88,0xc08ff8e68f13b38c,1 +np.float64,0x7feeabab3e7d5755,0x408fff82a0fd4501,1 +np.float64,0x46c0d4a68d81b,0xc08ffed79abaddc9,1 +np.float64,0x3fd088d57ca111ab,0xbfff3dd0ed7128ea,1 +np.float64,0x3fed25887cba4b11,0xbfc13f47639bd645,1 +np.float64,0x7fd90984b4b21308,0x408ff52b022c7fb4,1 +np.float64,0x3fe6ef31daadde64,0xbfdec185760cbf21,1 +np.float64,0x3fe48dbe83291b7d,0xbfe47005b99920bd,1 +np.float64,0x3fdce8422f39d084,0xbff258a33a96cc8e,1 +np.float64,0xb8ecdef771d9c,0xc08ff3c0eca61b10,1 +np.float64,0x3fe9bbf9a03377f3,0xbfd41ecfdcc336b9,1 +np.float64,0x7fe2565339a4aca5,0x408ff992d8851eaf,1 +np.float64,0x3fe1693e3822d27c,0xbfec1919da2ca697,1 +np.float64,0x3fd3680488a6d009,0xbffb8b7330275947,1 +np.float64,0x7fbe4f3d2c3c9e79,0x408fe75fa3f4e600,1 +np.float64,0x7fd4cfef3ca99fdd,0x408ff308ee3ab50f,1 +np.float64,0x3fd9c9a51cb3934a,0xbff4fb7440055ce6,1 +np.float64,0x3fe08a9640a1152d,0xbfee76bd1bfbf5c2,1 +np.float64,0x3fef012c41fe0259,0xbfa757a2da7f9707,1 +np.float64,0x3fee653fe2fcca80,0xbfb2ffae0c95025c,1 +np.float64,0x7fd0776933a0eed1,0x408ff054e7b43d41,1 +np.float64,0x4c94e5c09929d,0xc08ffdedb7f49e5e,1 +np.float64,0xca3e3d17947c8,0xc08ff2b86dce2f7a,1 +np.float64,0x3fb528e1342a51c2,0xc00cc626c8e2d9ba,1 +np.float64,0xd774df81aee9c,0xc08ff1fd6f0a7548,1 +np.float64,0x3fc47a9b6128f537,0xc00526c577b80849,1 +np.float64,0x3fe29a6f6a6534df,0xbfe90a5f83644911,1 +np.float64,0x3fecda4f59f9b49f,0xbfc31e4a80c4cbb6,1 +np.float64,0x7fe51d44f5aa3a89,0x408ffb3382437426,1 +np.float64,0x3fd677fc412ceff9,0xbff82999086977e7,1 +np.float64,0x3fe2a3c7e7254790,0xbfe8f33415cdba9d,1 +np.float64,0x3fe6d8d1dc6db1a4,0xbfdf1bc61bc24dff,1 +np.float64,0x7febb32d8ef7665a,0x408ffe55a043ded1,1 +np.float64,0x60677860c0d0,0xc0900da2caa7d571,1 +np.float64,0x7390c2e0e7219,0xc08ff92df18bb5d2,1 +np.float64,0x3fca53711b34a6e2,0xc00240b07a9b529b,1 +np.float64,0x7fe7ce6dd8ef9cdb,0x408ffc961164ead9,1 +np.float64,0x7fc0c9de0d2193bb,0x408fe88e245767f6,1 +np.float64,0xc0ee217981dc4,0xc08ff343b77ea770,1 +np.float64,0x72bd4668e57a9,0xc08ff94323fd74fc,1 +np.float64,0x7fd6970e252d2e1b,0x408ff3fb1e2fead2,1 +np.float64,0x7fdcb61040396c20,0x408ff6bf926bc98f,1 +np.float64,0xda4faa25b49f6,0xc08ff1d68b3877f0,1 +np.float64,0x3feb344749f6688f,0xbfcdfba2d66c72c5,1 +np.float64,0x3fe2aa4284e55485,0xbfe8e32ae0683f57,1 +np.float64,0x3f8e8fcfd03d1fa0,0xc01843efb2129908,1 +np.float64,0x8000000000000000,0xfff0000000000000,1 +np.float64,0x3fd8e01155b1c023,0xbff5d0529dae9515,1 +np.float64,0x3fe8033f3370067e,0xbfda837c80b87e7c,1 +np.float64,0x7fc5bf831e2b7f05,0x408feb8ae3b039a0,1 +np.float64,0x3fd8dcdf5331b9bf,0xbff5d349e1ed422a,1 +np.float64,0x3fe58b4e302b169c,0xbfe243c9cbccde44,1 +np.float64,0x3fea8a2e47b5145d,0xbfd1464e37221894,1 +np.float64,0x75cd1e88eb9a4,0xc08ff8f553ef0475,1 +np.float64,0x7fcfc876e23f90ed,0x408fefebe6cc95e6,1 +np.float64,0x7f51aceb002359d5,0x408fb1263f9003fb,1 +np.float64,0x7fc2a1b877254370,0x408fe9c1ec52f8b9,1 +np.float64,0x7fd495810e292b01,0x408ff2e859414d31,1 +np.float64,0x7fd72048632e4090,0x408ff440690cebdb,1 +np.float64,0x7fd7aafaffaf6,0xc08ff803a390779f,1 +np.float64,0x7fe18067d4a300cf,0x408ff9090a02693f,1 +np.float64,0x3fdc1080f8b82102,0xbff3077bf44a89bd,1 +np.float64,0x3fc34a462f26948c,0xc005d777b3cdf139,1 +np.float64,0x3fe21e4a1fe43c94,0xbfea428acfbc6ea9,1 +np.float64,0x1f0d79083e1b0,0xc090042c65a7abf2,1 +np.float64,0x3fe8d0d15931a1a3,0xbfd779f6bbd4db78,1 +np.float64,0x3fe74578022e8af0,0xbfdd68b6c15e9f5e,1 +np.float64,0x50995dd0a132c,0xc08ffd56a5c8accf,1 +np.float64,0x3f9a6342b034c685,0xc0151ce1973c62bd,1 +np.float64,0x3f30856a00210ad4,0xc027e852f4d1fcbc,1 +np.float64,0x3febcf7646b79eed,0xbfc9e9cc9d12425c,1 +np.float64,0x8010000000000000,0x7ff8000000000000,1 +np.float64,0x3fdf520c02bea418,0xbff07ed5013f3062,1 +np.float64,0x3fe5433ecbea867e,0xbfe2df38968b6d14,1 +np.float64,0x3fb933a84e326751,0xc00ac1a144ad26c5,1 +np.float64,0x7b6d72c2f6daf,0xc08ff86b7a67f962,1 +np.float64,0xaef5dae75debc,0xc08ff46496bb2932,1 +np.float64,0x522d869aa45b1,0xc08ffd1d55281e98,1 +np.float64,0xa2462b05448c6,0xc08ff542fe0ac5fd,1 +np.float64,0x3fe2b71dd6e56e3c,0xbfe8c3690cf15415,1 +np.float64,0x3fe5778231aaef04,0xbfe26e495d09b783,1 +np.float64,0x3fe9b8d564f371ab,0xbfd42a161132970d,1 +np.float64,0x3f89ebc34033d787,0xc019373f90bfc7f1,1 +np.float64,0x3fe438ddc6e871bc,0xbfe53039341b0a93,1 +np.float64,0x873c75250e78f,0xc08ff75d8478dccd,1 +np.float64,0x807134cb00e27,0xc08ff7f5cf59c57a,1 +np.float64,0x3fac459878388b31,0xc010b6fe803bcdc2,1 +np.float64,0xca9dc7eb953b9,0xc08ff2b2fb480784,1 +np.float64,0x7feb38587bb670b0,0x408ffe21ff6d521e,1 +np.float64,0x7fd70e9b782e1d36,0x408ff437936b393a,1 +np.float64,0x3fa4037bbc2806f7,0xc012b55744c65ab2,1 +np.float64,0x3fd3d4637427a8c7,0xbffb0beebf4311ef,1 +np.float64,0x7fdabbda5db577b4,0x408ff5ecbc0d4428,1 +np.float64,0x7fda9be0a2b537c0,0x408ff5dee5d03d5a,1 +np.float64,0x7fe9c74396338e86,0x408ffd813506a18a,1 +np.float64,0x3fd058243e20b048,0xbfff822ffd8a7f21,1 +np.float64,0x3fe6aa6ca9ed54d9,0xbfdfd805629ff49e,1 +np.float64,0x3fd91431d5322864,0xbff5a025eea8c78b,1 +np.float64,0x7fe4d7f02329afdf,0x408ffb0d5d9b7878,1 +np.float64,0x3fe2954a12252a94,0xbfe917266e3e22d5,1 +np.float64,0x3fb25f7c8224bef9,0xc00e6764c81b3718,1 +np.float64,0x3fda4bddeeb497bc,0xbff4880638908c81,1 +np.float64,0x55dfd12eabbfb,0xc08ffc9b54ff4002,1 +np.float64,0x3fe8f399e031e734,0xbfd6f8e5c4dcd93f,1 +np.float64,0x3fd954a24832a945,0xbff56521f4707a06,1 +np.float64,0x3fdea911f2bd5224,0xbff0fcb2d0c2b2e2,1 +np.float64,0x3fe6b4ff8a2d69ff,0xbfdfacfc85cafeab,1 +np.float64,0x3fc7fa02042ff404,0xc00354e13b0767ad,1 +np.float64,0x3fe955088c72aa11,0xbfd593130f29949e,1 +np.float64,0xd7e74ec1afcea,0xc08ff1f74f61721c,1 +np.float64,0x3fe9d69c1ab3ad38,0xbfd3bf710a337e06,1 +np.float64,0x3fd85669a2b0acd3,0xbff65176143ccc1e,1 +np.float64,0x3fea99b285353365,0xbfd11062744783f2,1 +np.float64,0x3fe2c79f80a58f3f,0xbfe89ac33f990289,1 +np.float64,0x3f8332ba30266574,0xc01af2cb7b635783,1 +np.float64,0x30d0150061a1,0xc090119030f74c5d,1 +np.float64,0x3fdbf4cb06b7e996,0xbff31e5207aaa754,1 +np.float64,0x3fe6b56c216d6ad8,0xbfdfab42fb2941c5,1 +np.float64,0x7fc4dc239829b846,0x408feb0fb0e13fbe,1 +np.float64,0x3fd0ab85ef21570c,0xbfff0d95d6c7a35c,1 +np.float64,0x7fe13d75e5e27aeb,0x408ff8dc8efa476b,1 +np.float64,0x3fece3b832f9c770,0xbfc2e21b165d583f,1 +np.float64,0x3fe3a279c4e744f4,0xbfe68ca4fbb55dbf,1 +np.float64,0x3feb64659ef6c8cb,0xbfccb6204b6bf724,1 +np.float64,0x2279a6bc44f36,0xc0900391eeeb3e7c,1 +np.float64,0xb88046d571009,0xc08ff3c7b5b45300,1 +np.float64,0x7ff4000000000000,0x7ffc000000000000,1 +np.float64,0x3fe49af059a935e1,0xbfe4526c294f248f,1 +np.float64,0xa3e5508147cc,0xc0900a92ce5924b1,1 +np.float64,0x7fc56def3d2adbdd,0x408feb5f46c360e8,1 +np.float64,0x7fd99f3574333e6a,0x408ff56f3807987c,1 +np.float64,0x3fdc38d56fb871ab,0xbff2e667cad8f36a,1 +np.float64,0xd0b03507a1607,0xc08ff25bbcf8aa9d,1 +np.float64,0xc493f9078927f,0xc08ff30c5fa4e759,1 +np.float64,0x3fc86ddbcb30dbb8,0xc0031da1fcb56d75,1 +np.float64,0x7fe75dc395aebb86,0x408ffc5eef841491,1 +np.float64,0x1647618a2c8ed,0xc0900616ef9479c1,1 +np.float64,0xdf144763be289,0xc08ff196b527f3c9,1 +np.float64,0x3fe0b29da6a1653b,0xbfee078b5f4d7744,1 +np.float64,0x3feb055852b60ab1,0xbfcf3b4db5779a7a,1 +np.float64,0x3fe8bc1625f1782c,0xbfd7c739ade904bc,1 +np.float64,0x7fd19bfb8ea337f6,0x408ff11b2b55699c,1 +np.float64,0x3fed1d80d1ba3b02,0xbfc1722e8d3ce094,1 +np.float64,0x2d9c65925b38e,0xc09001f46bcd3bc5,1 +np.float64,0x7fed6f4d857ade9a,0x408fff091cf6a3b4,1 +np.float64,0x3fd070cd6ba0e19b,0xbfff5f7609ca29e8,1 +np.float64,0x7fea3508b8f46a10,0x408ffdb1f30bd6be,1 +np.float64,0x508b897ca1172,0xc08ffd58a0eb3583,1 +np.float64,0x7feba367b07746ce,0x408ffe4f0bf4bd4e,1 +np.float64,0x3fefebd5c4bfd7ac,0xbf6d20b4fcf21b69,1 +np.float64,0x3fd8ef07b8b1de0f,0xbff5c2745c0795a5,1 +np.float64,0x3fd38ed518271daa,0xbffb5d75f00f6900,1 +np.float64,0x6de0fecedbc20,0xc08ff9c307bbc647,1 +np.float64,0xafc0ffc35f820,0xc08ff45737e5d6b4,1 +np.float64,0x7fd282097ca50412,0x408ff1ae3b27bf3b,1 +np.float64,0x3fe2f2d50b65e5aa,0xbfe831042e6a1e99,1 +np.float64,0x3faa437bac3486f7,0xc01123d8d962205a,1 +np.float64,0x3feea54434fd4a88,0xbfaff202cc456647,1 +np.float64,0x3fc9e65b8633ccb7,0xc00270e77ffd19da,1 +np.float64,0x7fee15af61fc2b5e,0x408fff49a49154a3,1 +np.float64,0x7fefe670a73fcce0,0x408ffff6c44c1005,1 +np.float64,0x3fc0832d0f21065a,0xc007a2dc2f25384a,1 +np.float64,0x3fecfc96bcb9f92d,0xbfc24367c3912620,1 +np.float64,0x3feb705682b6e0ad,0xbfcc65b1bb16f9c5,1 +np.float64,0x3fe185c4f9630b8a,0xbfebcdb401af67a4,1 +np.float64,0x3fb0a5a9f6214b54,0xc00f8ada2566a047,1 +np.float64,0x7fe2908cdda52119,0x408ff9b744861fb1,1 +np.float64,0x7fee776e183ceedb,0x408fff6ee7c2f86e,1 +np.float64,0x3fce1d608f3c3ac1,0xc000b3685d006474,1 +np.float64,0x7fecf92aa339f254,0x408ffeda6c998267,1 +np.float64,0xce13cb519c27a,0xc08ff280f02882a9,1 +np.float64,0x1,0xc090c80000000000,1 +np.float64,0x3fe485a8afa90b51,0xbfe4823265d5a50a,1 +np.float64,0x3feea60908bd4c12,0xbfafdf7ad7fe203f,1 +np.float64,0x3fd2253033a44a60,0xbffd187d0ec8d5b9,1 +np.float64,0x435338fc86a68,0xc08fff6a591059dd,1 +np.float64,0x7fce8763a73d0ec6,0x408fef74f1e715ff,1 +np.float64,0x3fbe5ddb783cbbb7,0xc0089acc5afa794b,1 +np.float64,0x7fe4cf19ada99e32,0x408ffb0877ca302b,1 +np.float64,0x3fe94c9ea1b2993d,0xbfd5b1c2e867b911,1 +np.float64,0x3fe75541c72eaa84,0xbfdd2a27aa117699,1 +np.float64,0x8000000000000001,0x7ff8000000000000,1 +np.float64,0x7fdbec7f2c37d8fd,0x408ff66d69a7f818,1 +np.float64,0x8ef10d091de22,0xc08ff6b9ca5094f8,1 +np.float64,0x3fea69025b74d205,0xbfd1b9fe2c252c70,1 +np.float64,0x562376d0ac46f,0xc08ffc924111cd31,1 +np.float64,0x8e8097ab1d013,0xc08ff6c2e2706f67,1 +np.float64,0x3fca6803ed34d008,0xc00237aef808825b,1 +np.float64,0x7fe8fe9067b1fd20,0x408ffd25f459a7d1,1 +np.float64,0x3f918e8c7f233,0xc0900009fe011d54,1 +np.float64,0x3fdfe773833fcee7,0xbff011bc1af87bb9,1 +np.float64,0xefffef6fdfffe,0xc08ff0beb0f09eb0,1 +np.float64,0x7fe64610282c8c1f,0x408ffbd17209db18,1 +np.float64,0xe66be8c1ccd7d,0xc08ff13706c056e1,1 +np.float64,0x2837e570506fd,0xc09002ae4dae0c1a,1 +np.float64,0x3febe3a081f7c741,0xbfc964171f2a5a47,1 +np.float64,0x3fe21ed09a243da1,0xbfea41342d29c3ff,1 +np.float64,0x3fe1596c8162b2d9,0xbfec431eee30823a,1 +np.float64,0x8f2b9a131e574,0xc08ff6b51104ed4e,1 +np.float64,0x3fe88ed179711da3,0xbfd870d08a4a4b0c,1 +np.float64,0x34159bc2682b4,0xc09001305a885f94,1 +np.float64,0x1ed31e543da65,0xc0900437481577f8,1 +np.float64,0x3feafbe9de75f7d4,0xbfcf7bcdbacf1c61,1 +np.float64,0xfb16fb27f62e0,0xc08ff03938e682a2,1 +np.float64,0x3fe5cd5ba7eb9ab7,0xbfe1b7165771af3c,1 +np.float64,0x7fe72905e76e520b,0x408ffc44c4e7e80c,1 +np.float64,0x7fb7136e2e2e26db,0x408fe439fd383fb7,1 +np.float64,0x8fa585e11f4c,0xc0900b55a08a486b,1 +np.float64,0x7fed985ce47b30b9,0x408fff192b596821,1 +np.float64,0x3feaaf0869755e11,0xbfd0c671571b3764,1 +np.float64,0x3fa40fd4ec281faa,0xc012b1c8dc0b9e5f,1 +np.float64,0x7fda2a70993454e0,0x408ff5ad47b0c68a,1 +np.float64,0x3fe5f7e931abefd2,0xbfe15d52b3605abf,1 +np.float64,0x3fe9fc6d3533f8da,0xbfd338b06a790994,1 +np.float64,0x3fe060649420c0c9,0xbfeeed1756111891,1 +np.float64,0x3fce8435e33d086c,0xc0008c41cea9ed40,1 +np.float64,0x7ff8000000000000,0x7ff8000000000000,1 +np.float64,0x617820aec2f05,0xc08ffb251e9af0f0,1 +np.float64,0x7fcc4ab6ee38956d,0x408fee9419c8f77d,1 +np.float64,0x7fdefda2fc3dfb45,0x408ff7a15063bc05,1 +np.float64,0x7fe5138ccaaa2719,0x408ffb2e30f3a46e,1 +np.float64,0x3fe3817a836702f5,0xbfe6da7c2b25e35a,1 +np.float64,0x3fb8a7dafa314fb6,0xc00b025bc0784ebe,1 +np.float64,0x349dc420693d,0xc09011215825d2c8,1 +np.float64,0x6b0e504ad61cb,0xc08ffa0fee9c5cd6,1 +np.float64,0x273987644e732,0xc09002d34294ed79,1 +np.float64,0x3fc0bd8a6e217b15,0xc0077a5828b4d2f5,1 +np.float64,0x758b48c4eb16a,0xc08ff8fbc8fbe46a,1 +np.float64,0x3fc8a9a52631534a,0xc00301854ec0ef81,1 +np.float64,0x7fe79d29a76f3a52,0x408ffc7e1607a4c1,1 +np.float64,0x3fd7d3ebce2fa7d8,0xbff6ce8a94aebcda,1 +np.float64,0x7fd1cb68a52396d0,0x408ff13a17533b2b,1 +np.float64,0x7fda514a5d34a294,0x408ff5be5e081578,1 +np.float64,0x3fc40b4382281687,0xc0056632c8067228,1 +np.float64,0x7feff1208c3fe240,0x408ffffaa180fa0d,1 +np.float64,0x8f58739f1eb0f,0xc08ff6b17402689d,1 +np.float64,0x1fdbe9a23fb7e,0xc090040685b2d24f,1 +np.float64,0xcb1d0e87963a2,0xc08ff2abbd903b82,1 +np.float64,0x3fc45a6a1a28b4d4,0xc00538f86c4aeaee,1 +np.float64,0x3fe61885b1ac310b,0xbfe118fd2251d2ec,1 +np.float64,0x3fedf584c8fbeb0a,0xbfb8572433ff67a9,1 +np.float64,0x7fb0bddd1a217bb9,0x408fe085e0d621db,1 +np.float64,0x72d8d3e0e5b3,0xc0900ca02f68c7a1,1 +np.float64,0x5cca6ff6b994f,0xc08ffbb6751fda01,1 +np.float64,0x7fe3197839a632ef,0x408ffa0b2fccfb68,1 +np.float64,0x3fcce4d9c139c9b4,0xc0012dae05baa91b,1 +np.float64,0x3fe76d00f62eda02,0xbfdccc5f12799be1,1 +np.float64,0x3fc53c22f72a7846,0xc004bbaa9cbc7958,1 +np.float64,0x7fdda02f1ebb405d,0x408ff71c37c71659,1 +np.float64,0x3fe0844eaba1089d,0xbfee884722762583,1 +np.float64,0x3febb438dc776872,0xbfca9f05e1c691f1,1 +np.float64,0x3fdf4170cdbe82e2,0xbff08b1561c8d848,1 +np.float64,0x3fce1b8d6f3c371b,0xc000b41b69507671,1 +np.float64,0x8370e60706e1d,0xc08ff7b19ea0b4ca,1 +np.float64,0x7fa5bf92382b7f23,0x408fdb8aebb3df87,1 +np.float64,0x7fe4a59979a94b32,0x408ffaf15c1358cd,1 +np.float64,0x3faa66086034cc11,0xc0111c466b7835d6,1 +np.float64,0x7fb7a958262f52af,0x408fe48408b1e093,1 +np.float64,0x3fdaacc5f635598c,0xbff43390d06b5614,1 +np.float64,0x3fd2825b9e2504b7,0xbffca3234264f109,1 +np.float64,0x3fcede160a3dbc2c,0xc0006a759e29060c,1 +np.float64,0x7fd3b19603a7632b,0x408ff265b528371c,1 +np.float64,0x7fcf8a86ea3f150d,0x408fefd552e7f3b2,1 +np.float64,0xedbcc0f7db798,0xc08ff0daad12096b,1 +np.float64,0xf1e1683de3c2d,0xc08ff0a7a0a37e00,1 +np.float64,0xb6ebd9bf6dd7b,0xc08ff3e11e28378d,1 +np.float64,0x3fec8090d6f90122,0xbfc56031b72194cc,1 +np.float64,0x3fd3e10e37a7c21c,0xbffafd34a3ebc933,1 +np.float64,0x7fbb1c96aa36392c,0x408fe616347b3342,1 +np.float64,0x3fe2f3996f25e733,0xbfe82f25bc5d1bbd,1 +np.float64,0x7fe8709da870e13a,0x408ffce3ab6ce59a,1 +np.float64,0x7fea3233d1b46467,0x408ffdb0b3bbc6de,1 +np.float64,0x65fa4112cbf49,0xc08ffa9f85eb72b9,1 +np.float64,0x3fca2cae9f34595d,0xc00251bb275afb87,1 +np.float64,0x8135fd9f026c0,0xc08ff7e42e14dce7,1 +np.float64,0x7fe0a6f057e14de0,0x408ff876081a4bfe,1 +np.float64,0x10000000000000,0xc08ff00000000000,1 +np.float64,0x3fe1fd506263faa1,0xbfea96dd8c543b72,1 +np.float64,0xa5532c554aa66,0xc08ff50bf5bfc66d,1 +np.float64,0xc239d00b8473a,0xc08ff32ff0ea3f92,1 +np.float64,0x7fdb5314e336a629,0x408ff62d4ff60d82,1 +np.float64,0x3fe5f506e2abea0e,0xbfe16362a4682120,1 +np.float64,0x3fa20c60202418c0,0xc0134e08d82608b6,1 +np.float64,0x7fe03864b22070c8,0x408ff82866d65e9a,1 +np.float64,0x3fe72cf5656e59eb,0xbfddca298969effa,1 +np.float64,0x5c295386b852b,0xc08ffbca90b136c9,1 +np.float64,0x7fd71e5020ae3c9f,0x408ff43f6d58eb7c,1 +np.float64,0x3fd1905a842320b5,0xbffdd8ecd288159c,1 +np.float64,0x3fe6bddb256d7bb6,0xbfdf88fee1a820bb,1 +np.float64,0xe061b967c0c37,0xc08ff18581951561,1 +np.float64,0x3fe534f65cea69ed,0xbfe2fe45fe7d3040,1 +np.float64,0xdc7dae07b8fb6,0xc08ff1b93074ea76,1 +np.float64,0x3fd0425082a084a1,0xbfffa11838b21633,1 +np.float64,0xba723fc974e48,0xc08ff3a8b8d01c58,1 +np.float64,0x3fce42ffc73c8600,0xc000a5062678406e,1 +np.float64,0x3f2e6d3c7e5ce,0xc090001304cfd1c7,1 +np.float64,0x3fd4b2e5f7a965cc,0xbffa0e6e6bae0a68,1 +np.float64,0x3fe6db1d18edb63a,0xbfdf128158ee92d9,1 +np.float64,0x7fe4e5792f29caf1,0x408ffb14d9dbf133,1 +np.float64,0x3fc11cdf992239bf,0xc00739569619cd77,1 +np.float64,0x3fc05ea11220bd42,0xc007bc841b48a890,1 +np.float64,0x4bd592d497ab3,0xc08ffe0ab1c962e2,1 +np.float64,0x280068fc5000e,0xc09002b64955e865,1 +np.float64,0x7fe2f2637065e4c6,0x408ff9f379c1253a,1 +np.float64,0x3fefc38467ff8709,0xbf85e53e64b9a424,1 +np.float64,0x2d78ec5a5af1e,0xc09001f8ea8601e0,1 +np.float64,0x7feeef2b957dde56,0x408fff9bebe995f7,1 +np.float64,0x2639baf44c738,0xc09002f9618d623b,1 +np.float64,0x3fc562964d2ac52d,0xc004a6d76959ef78,1 +np.float64,0x3fe21b071fe4360e,0xbfea4adb2cd96ade,1 +np.float64,0x7fe56aa6802ad54c,0x408ffb5d81d1a898,1 +np.float64,0x4296b452852d7,0xc08fff8ad7fbcbe1,1 +np.float64,0x7fe3fac4ff27f589,0x408ffa9049eec479,1 +np.float64,0x7fe7a83e6caf507c,0x408ffc837f436604,1 +np.float64,0x3fc4ac5b872958b7,0xc0050add72381ac3,1 +np.float64,0x3fd6d697c02dad30,0xbff7c931a3eefb01,1 +np.float64,0x3f61e391c023c724,0xc021ad91e754f94b,1 +np.float64,0x10817f9c21031,0xc09007d20434d7bc,1 +np.float64,0x3fdb9c4c4cb73899,0xbff367d8615c5ece,1 +np.float64,0x3fe26ead6b64dd5b,0xbfe977771def5989,1 +np.float64,0x3fc43ea5c3287d4c,0xc00548c2163ae631,1 +np.float64,0x3fe05bd8bba0b7b1,0xbfeef9ea0db91abc,1 +np.float64,0x3feac78369358f07,0xbfd071e2b0aeab39,1 +np.float64,0x7fe254922ca4a923,0x408ff991bdd4e5d3,1 +np.float64,0x3fe5a2f5842b45eb,0xbfe21135c9a71666,1 +np.float64,0x3fd5daf98c2bb5f3,0xbff8cd24f7c07003,1 +np.float64,0x3fcb2a1384365427,0xc001e40f0d04299a,1 +np.float64,0x3fe073974360e72f,0xbfeeb7183a9930b7,1 +np.float64,0xcf3440819e688,0xc08ff270d3a71001,1 +np.float64,0x3fd35656cda6acae,0xbffba083fba4939d,1 +np.float64,0x7fe6c59b4ded8b36,0x408ffc12ce725425,1 +np.float64,0x3fba896f943512df,0xc00a291cb6947701,1 +np.float64,0x7fe54917e86a922f,0x408ffb4b5e0fb848,1 +np.float64,0x7fed2a3f51ba547e,0x408ffeede945a948,1 +np.float64,0x3fdc72bd5038e57b,0xbff2b73b7e93e209,1 +np.float64,0x7fefdb3f9f3fb67e,0x408ffff2b702a768,1 +np.float64,0x3fb0184430203088,0xc00fee8c1351763c,1 +np.float64,0x7d6c3668fad87,0xc08ff83c195f2cca,1 +np.float64,0x3fd5aa254aab544b,0xbff900f16365991b,1 +np.float64,0x3f963daab02c7b55,0xc0161974495b1b71,1 +np.float64,0x3fa7a9c5982f538b,0xc011bde0f6052a89,1 +np.float64,0xb3a5a74b674b5,0xc08ff4167bc97c81,1 +np.float64,0x7fad0c14503a1828,0x408fdee1f2d56cd7,1 +np.float64,0x43e0e9d887c1e,0xc08fff522837b13b,1 +np.float64,0x3fe513b20aea2764,0xbfe346ea994100e6,1 +np.float64,0x7fe4e10393e9c206,0x408ffb12630f6a06,1 +np.float64,0x68b286e2d1651,0xc08ffa51c0d795d4,1 +np.float64,0x7fe8de453331bc89,0x408ffd17012b75ac,1 +np.float64,0x1b3d77d4367b0,0xc09004edea60aa36,1 +np.float64,0x3fd351cbc326a398,0xbffba5f0f4d5fdba,1 +np.float64,0x3fd264951b24c92a,0xbffcc8636788b9bf,1 +np.float64,0xd2465761a48cb,0xc08ff2455c9c53e5,1 +np.float64,0x7fe46a0ef028d41d,0x408ffacfe32c6f5d,1 +np.float64,0x3fafd8ac4c3fb159,0xc010071bf33195d0,1 +np.float64,0x902aec5d2055e,0xc08ff6a08e28aabc,1 +np.float64,0x3fcea61bb03d4c37,0xc0007f76e509b657,1 +np.float64,0x7fe8d90f9571b21e,0x408ffd1495f952e7,1 +np.float64,0x7fa650c9442ca192,0x408fdbd6ff22fdd8,1 +np.float64,0x3fe8ecfdf171d9fc,0xbfd7115df40e8580,1 +np.float64,0x7fd4e6fe7f29cdfc,0x408ff315b0dae183,1 +np.float64,0x77df4c52efbea,0xc08ff8c1d5c1df33,1 +np.float64,0xe200b0cfc4016,0xc08ff1703cfb8e79,1 +np.float64,0x3fe230ea7e2461d5,0xbfea132d2385160e,1 +np.float64,0x7fd1f7ced723ef9d,0x408ff156bfbf92a4,1 +np.float64,0x3fea762818f4ec50,0xbfd18c12a88e5f79,1 +np.float64,0x7feea4ba7c7d4974,0x408fff8004164054,1 +np.float64,0x833ec605067d9,0xc08ff7b606383841,1 +np.float64,0x7fd0c2d7fea185af,0x408ff0894f3a0cf4,1 +np.float64,0x3fe1d7d61d23afac,0xbfeaf76fee875d3e,1 +np.float64,0x65adecb0cb5be,0xc08ffaa82cb09d68,1 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-sinh.csv b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-sinh.csv new file mode 100644 index 0000000000000000000000000000000000000000..1ef7b6e76bfb49b093d8bf6c468bae208ec988e4 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-sinh.csv @@ -0,0 +1,1429 @@ +dtype,input,output,ulperrortol +np.float32,0xfee27582,0xff800000,2 +np.float32,0xff19f092,0xff800000,2 +np.float32,0xbf393576,0xbf49cb31,2 +np.float32,0x8020fdea,0x8020fdea,2 +np.float32,0x455f4e,0x455f4e,2 +np.float32,0xff718c35,0xff800000,2 +np.float32,0x3f3215e3,0x3f40cce5,2 +np.float32,0x19e833,0x19e833,2 +np.float32,0xff2dcd49,0xff800000,2 +np.float32,0x7e8f6c95,0x7f800000,2 +np.float32,0xbf159dac,0xbf1e47a5,2 +np.float32,0x100d3d,0x100d3d,2 +np.float32,0xff673441,0xff800000,2 +np.float32,0x80275355,0x80275355,2 +np.float32,0x4812d0,0x4812d0,2 +np.float32,0x8072b956,0x8072b956,2 +np.float32,0xff3bb918,0xff800000,2 +np.float32,0x0,0x0,2 +np.float32,0xfe327798,0xff800000,2 +np.float32,0x41d4e2,0x41d4e2,2 +np.float32,0xfe34b1b8,0xff800000,2 +np.float32,0x80199f72,0x80199f72,2 +np.float32,0x807242ce,0x807242ce,2 +np.float32,0x3ef4202d,0x3efd7b48,2 +np.float32,0x763529,0x763529,2 +np.float32,0x4f6662,0x4f6662,2 +np.float32,0x3f18efe9,0x3f2232b5,2 +np.float32,0x80701846,0x80701846,2 +np.float32,0x3f599948,0x3f74c393,2 +np.float32,0x5a3d69,0x5a3d69,2 +np.float32,0xbf4a7e65,0xbf6047a3,2 +np.float32,0xff0d4c82,0xff800000,2 +np.float32,0x7a74db,0x7a74db,2 +np.float32,0x803388e6,0x803388e6,2 +np.float32,0x7f4430bb,0x7f800000,2 +np.float32,0x14c5b1,0x14c5b1,2 +np.float32,0xfa113400,0xff800000,2 +np.float32,0x7f4b3209,0x7f800000,2 +np.float32,0x8038d88c,0x8038d88c,2 +np.float32,0xbef2f9de,0xbefc330b,2 +np.float32,0xbe147b38,0xbe15008f,2 +np.float32,0x2b61e6,0x2b61e6,2 +np.float32,0x80000001,0x80000001,2 +np.float32,0x8060456c,0x8060456c,2 +np.float32,0x3f30fa82,0x3f3f6a99,2 +np.float32,0xfd1f0220,0xff800000,2 +np.float32,0xbf2b7555,0xbf389151,2 +np.float32,0xff100b7a,0xff800000,2 +np.float32,0x70d3cd,0x70d3cd,2 +np.float32,0x2a8d4a,0x2a8d4a,2 +np.float32,0xbf7b733f,0xbf92f05f,2 +np.float32,0x3f7106dc,0x3f8b1fc6,2 +np.float32,0x3f39da7a,0x3f4a9d79,2 +np.float32,0x3f5dd73f,0x3f7aaab5,2 +np.float32,0xbe8c8754,0xbe8e4cba,2 +np.float32,0xbf6c74c9,0xbf87c556,2 +np.float32,0x800efbbb,0x800efbbb,2 +np.float32,0xff054ab5,0xff800000,2 +np.float32,0x800b4b46,0x800b4b46,2 +np.float32,0xff77fd74,0xff800000,2 +np.float32,0x257d0,0x257d0,2 +np.float32,0x7caa0c,0x7caa0c,2 +np.float32,0x8025d24d,0x8025d24d,2 +np.float32,0x3d9f1b60,0x3d9f445c,2 +np.float32,0xbe3bf6e8,0xbe3d0595,2 +np.float32,0x54bb93,0x54bb93,2 +np.float32,0xbf3e6a45,0xbf507716,2 +np.float32,0x3f4bb26e,0x3f61e1cd,2 +np.float32,0x3f698edc,0x3f85aac5,2 +np.float32,0xff7bd0ef,0xff800000,2 +np.float32,0xbed07b68,0xbed64a8e,2 +np.float32,0xbf237c72,0xbf2ed3d2,2 +np.float32,0x27b0fa,0x27b0fa,2 +np.float32,0x3f7606d1,0x3f8ed7d6,2 +np.float32,0x790dc0,0x790dc0,2 +np.float32,0x7f68f3ac,0x7f800000,2 +np.float32,0xbed39288,0xbed9a52f,2 +np.float32,0x3f6f8266,0x3f8a0187,2 +np.float32,0x3fbdca,0x3fbdca,2 +np.float32,0xbf7c3e5d,0xbf938b2c,2 +np.float32,0x802321a8,0x802321a8,2 +np.float32,0x3eecab66,0x3ef53031,2 +np.float32,0x62b324,0x62b324,2 +np.float32,0x3f13afac,0x3f1c03fe,2 +np.float32,0xff315ad7,0xff800000,2 +np.float32,0xbf1fac0d,0xbf2a3a63,2 +np.float32,0xbf543984,0xbf6d61d6,2 +np.float32,0x71a212,0x71a212,2 +np.float32,0x114fbe,0x114fbe,2 +np.float32,0x3f5b6ff2,0x3f77505f,2 +np.float32,0xff6ff89e,0xff800000,2 +np.float32,0xff4527a1,0xff800000,2 +np.float32,0x22cb3,0x22cb3,2 +np.float32,0x7f53bb6b,0x7f800000,2 +np.float32,0xff3d2dea,0xff800000,2 +np.float32,0xfd21dac0,0xff800000,2 +np.float32,0xfc486140,0xff800000,2 +np.float32,0x7e2b693a,0x7f800000,2 +np.float32,0x8022a9fb,0x8022a9fb,2 +np.float32,0x80765de0,0x80765de0,2 +np.float32,0x13d299,0x13d299,2 +np.float32,0x7ee53713,0x7f800000,2 +np.float32,0xbde1c770,0xbde23c96,2 +np.float32,0xbd473fc0,0xbd4753de,2 +np.float32,0x3f1cb455,0x3f26acf3,2 +np.float32,0x683e49,0x683e49,2 +np.float32,0x3ed5a9fc,0x3edbeb79,2 +np.float32,0x3f4fe3f6,0x3f67814f,2 +np.float32,0x802a2bce,0x802a2bce,2 +np.float32,0x7e951b4c,0x7f800000,2 +np.float32,0xbe6eb260,0xbe70dd44,2 +np.float32,0xbe3daca8,0xbe3ec2cb,2 +np.float32,0xbe9c38b2,0xbe9ea822,2 +np.float32,0xff2e29dc,0xff800000,2 +np.float32,0x7f62c7cc,0x7f800000,2 +np.float32,0xbf6799a4,0xbf84416c,2 +np.float32,0xbe30a7f0,0xbe318898,2 +np.float32,0xc83d9,0xc83d9,2 +np.float32,0x3f05abf4,0x3f0bd447,2 +np.float32,0x7e9b018a,0x7f800000,2 +np.float32,0xbf0ed72e,0xbf165e5b,2 +np.float32,0x8011ac8c,0x8011ac8c,2 +np.float32,0xbeb7c706,0xbebbbfcb,2 +np.float32,0x803637f9,0x803637f9,2 +np.float32,0xfe787cc8,0xff800000,2 +np.float32,0x3f533d4b,0x3f6c0a50,2 +np.float32,0x3f5c0f1c,0x3f782dde,2 +np.float32,0x3f301f36,0x3f3e590d,2 +np.float32,0x2dc929,0x2dc929,2 +np.float32,0xff15018a,0xff800000,2 +np.float32,0x3f4d0c56,0x3f63afeb,2 +np.float32,0xbf7a2ae3,0xbf91f6e4,2 +np.float32,0xbe771b84,0xbe798346,2 +np.float32,0x80800000,0x80800000,2 +np.float32,0x7f5689ba,0x7f800000,2 +np.float32,0x3f1c3177,0x3f2610df,2 +np.float32,0x3f1b9664,0x3f255825,2 +np.float32,0x3f7e5066,0x3f9520d4,2 +np.float32,0xbf1935f8,0xbf2285ab,2 +np.float32,0x3f096cc7,0x3f101ef9,2 +np.float32,0x8030c180,0x8030c180,2 +np.float32,0x6627ed,0x6627ed,2 +np.float32,0x454595,0x454595,2 +np.float32,0x7de66a33,0x7f800000,2 +np.float32,0xbf800000,0xbf966cfe,2 +np.float32,0xbf35c0a8,0xbf456939,2 +np.float32,0x3f6a6266,0x3f8643e0,2 +np.float32,0x3f0cbcee,0x3f13ef6a,2 +np.float32,0x7efd1e58,0x7f800000,2 +np.float32,0xfe9a74c6,0xff800000,2 +np.float32,0x807ebe6c,0x807ebe6c,2 +np.float32,0x80656736,0x80656736,2 +np.float32,0x800e0608,0x800e0608,2 +np.float32,0xbf30e39a,0xbf3f4e00,2 +np.float32,0x802015fd,0x802015fd,2 +np.float32,0x3e3ce26d,0x3e3df519,2 +np.float32,0x7ec142ac,0x7f800000,2 +np.float32,0xbf68c9ce,0xbf851c78,2 +np.float32,0xfede8356,0xff800000,2 +np.float32,0xbf1507ce,0xbf1d978d,2 +np.float32,0x3e53914c,0x3e551374,2 +np.float32,0x7f3e1c14,0x7f800000,2 +np.float32,0x8070d2ba,0x8070d2ba,2 +np.float32,0xbf4eb793,0xbf65ecee,2 +np.float32,0x7365a6,0x7365a6,2 +np.float32,0x8045cba2,0x8045cba2,2 +np.float32,0x7e4af521,0x7f800000,2 +np.float32,0xbf228625,0xbf2da9e1,2 +np.float32,0x7ee0536c,0x7f800000,2 +np.float32,0x3e126607,0x3e12e5d5,2 +np.float32,0x80311d92,0x80311d92,2 +np.float32,0xbf386b8b,0xbf48ca54,2 +np.float32,0x7f800000,0x7f800000,2 +np.float32,0x8049ec7a,0x8049ec7a,2 +np.float32,0xbf1dfde4,0xbf2836be,2 +np.float32,0x7e719a8c,0x7f800000,2 +np.float32,0x3eb9c856,0x3ebde2e6,2 +np.float32,0xfe3efda8,0xff800000,2 +np.float32,0xbe89d60c,0xbe8b81d1,2 +np.float32,0x3eaad338,0x3eae0317,2 +np.float32,0x7f4e5217,0x7f800000,2 +np.float32,0x3e9d0f40,0x3e9f88ce,2 +np.float32,0xbe026708,0xbe02c155,2 +np.float32,0x5fc22f,0x5fc22f,2 +np.float32,0x1c4572,0x1c4572,2 +np.float32,0xbed89d96,0xbedf22c5,2 +np.float32,0xbf3debee,0xbf4fd441,2 +np.float32,0xbf465520,0xbf5ac6e5,2 +np.float32,0x3f797081,0x3f9169b3,2 +np.float32,0xbf250734,0xbf30b2aa,2 +np.float32,0x7f5068e9,0x7f800000,2 +np.float32,0x3f1b814e,0x3f253f0c,2 +np.float32,0xbf27c5d3,0xbf340b05,2 +np.float32,0x3f1b78ae,0x3f2534c8,2 +np.float32,0x8059b51a,0x8059b51a,2 +np.float32,0x8059f182,0x8059f182,2 +np.float32,0xbf1bb36e,0xbf257ab8,2 +np.float32,0x41ac35,0x41ac35,2 +np.float32,0x68f41f,0x68f41f,2 +np.float32,0xbea504dc,0xbea7e40f,2 +np.float32,0x1,0x1,2 +np.float32,0x3e96b5b0,0x3e98e542,2 +np.float32,0x7f7fffff,0x7f800000,2 +np.float32,0x3c557a80,0x3c557c0c,2 +np.float32,0x800ca3ec,0x800ca3ec,2 +np.float32,0x8077d4aa,0x8077d4aa,2 +np.float32,0x3f000af0,0x3f0572d6,2 +np.float32,0x3e0434dd,0x3e0492f8,2 +np.float32,0x7d1a710a,0x7f800000,2 +np.float32,0x3f70f996,0x3f8b15f8,2 +np.float32,0x8033391d,0x8033391d,2 +np.float32,0x11927c,0x11927c,2 +np.float32,0x7f7784be,0x7f800000,2 +np.float32,0x7acb22af,0x7f800000,2 +np.float32,0x7e8b153c,0x7f800000,2 +np.float32,0x66d402,0x66d402,2 +np.float32,0xfed6e7b0,0xff800000,2 +np.float32,0x7f6872d3,0x7f800000,2 +np.float32,0x1bd49c,0x1bd49c,2 +np.float32,0xfdc4f1b8,0xff800000,2 +np.float32,0xbed8a466,0xbedf2a33,2 +np.float32,0x7ee789,0x7ee789,2 +np.float32,0xbece94b4,0xbed43b52,2 +np.float32,0x3cf3f734,0x3cf4006f,2 +np.float32,0x7e44aa00,0x7f800000,2 +np.float32,0x7f19e99c,0x7f800000,2 +np.float32,0x806ff1bc,0x806ff1bc,2 +np.float32,0x80296934,0x80296934,2 +np.float32,0x7f463363,0x7f800000,2 +np.float32,0xbf212ac3,0xbf2c06bb,2 +np.float32,0x3dc63778,0x3dc686ba,2 +np.float32,0x7f1b4328,0x7f800000,2 +np.float32,0x6311f6,0x6311f6,2 +np.float32,0xbf6b6fb6,0xbf870751,2 +np.float32,0xbf2c44cf,0xbf399155,2 +np.float32,0x3e7a67bc,0x3e7ce887,2 +np.float32,0x7f57c5f7,0x7f800000,2 +np.float32,0x7f2bb4ff,0x7f800000,2 +np.float32,0xbe9d448e,0xbe9fc0a4,2 +np.float32,0xbf4840f0,0xbf5d4f6b,2 +np.float32,0x7f1e1176,0x7f800000,2 +np.float32,0xff76638e,0xff800000,2 +np.float32,0xff055555,0xff800000,2 +np.float32,0x3f32b82b,0x3f419834,2 +np.float32,0xff363aa8,0xff800000,2 +np.float32,0x7f737fd0,0x7f800000,2 +np.float32,0x3da5d798,0x3da60602,2 +np.float32,0x3f1cc126,0x3f26bc3e,2 +np.float32,0x7eb07541,0x7f800000,2 +np.float32,0x3f7b2ff2,0x3f92bd2a,2 +np.float32,0x474f7,0x474f7,2 +np.float32,0x7fc00000,0x7fc00000,2 +np.float32,0xff2b0a4e,0xff800000,2 +np.float32,0xfeb24f16,0xff800000,2 +np.float32,0x2cb9fc,0x2cb9fc,2 +np.float32,0x67189d,0x67189d,2 +np.float32,0x8033d854,0x8033d854,2 +np.float32,0xbe85e94c,0xbe87717a,2 +np.float32,0x80767c6c,0x80767c6c,2 +np.float32,0x7ea84d65,0x7f800000,2 +np.float32,0x3f024bc7,0x3f07fead,2 +np.float32,0xbdcb0100,0xbdcb5625,2 +np.float32,0x3f160a9e,0x3f1ec7c9,2 +np.float32,0xff1734c8,0xff800000,2 +np.float32,0x7f424d5e,0x7f800000,2 +np.float32,0xbf75b215,0xbf8e9862,2 +np.float32,0x3f262a42,0x3f3214c4,2 +np.float32,0xbf4cfb53,0xbf639927,2 +np.float32,0x3f4ac8b8,0x3f60aa7c,2 +np.float32,0x3e90e593,0x3e92d6b3,2 +np.float32,0xbf66bccf,0xbf83a2d8,2 +np.float32,0x7d3d851a,0x7f800000,2 +np.float32,0x7bac783c,0x7f800000,2 +np.float32,0x8001c626,0x8001c626,2 +np.float32,0xbdffd480,0xbe003f7b,2 +np.float32,0x7f6680bf,0x7f800000,2 +np.float32,0xbecf448e,0xbed4f9bb,2 +np.float32,0x584c7,0x584c7,2 +np.float32,0x3f3e8ea0,0x3f50a5fb,2 +np.float32,0xbf5a5f04,0xbf75d56e,2 +np.float32,0x8065ae47,0x8065ae47,2 +np.float32,0xbf48dce3,0xbf5e1dba,2 +np.float32,0xbe8dae2e,0xbe8f7ed8,2 +np.float32,0x3f7ca6ab,0x3f93dace,2 +np.float32,0x4c3e81,0x4c3e81,2 +np.float32,0x80000000,0x80000000,2 +np.float32,0x3ee1f7d9,0x3ee96033,2 +np.float32,0x80588c6f,0x80588c6f,2 +np.float32,0x5ba34e,0x5ba34e,2 +np.float32,0x80095d28,0x80095d28,2 +np.float32,0xbe7ba198,0xbe7e2bdd,2 +np.float32,0xbe0bdcb4,0xbe0c4c22,2 +np.float32,0x1776f7,0x1776f7,2 +np.float32,0x80328b2a,0x80328b2a,2 +np.float32,0x3e978d37,0x3e99c63e,2 +np.float32,0x7ed50906,0x7f800000,2 +np.float32,0x3f776a54,0x3f8fe2bd,2 +np.float32,0xbed624c4,0xbedc7120,2 +np.float32,0x7f0b6a31,0x7f800000,2 +np.float32,0x7eb13913,0x7f800000,2 +np.float32,0xbe733684,0xbe758190,2 +np.float32,0x80016474,0x80016474,2 +np.float32,0x7a51ee,0x7a51ee,2 +np.float32,0x3f6cb91e,0x3f87f729,2 +np.float32,0xbd99b050,0xbd99d540,2 +np.float32,0x7c6e3cba,0x7f800000,2 +np.float32,0xbf00179a,0xbf05811e,2 +np.float32,0x3e609b29,0x3e626954,2 +np.float32,0xff3fd71a,0xff800000,2 +np.float32,0x5d8c2,0x5d8c2,2 +np.float32,0x7ee93662,0x7f800000,2 +np.float32,0x4b0b31,0x4b0b31,2 +np.float32,0x3ec243b7,0x3ec6f594,2 +np.float32,0x804d60f1,0x804d60f1,2 +np.float32,0xbf0cb784,0xbf13e929,2 +np.float32,0x3f13b74d,0x3f1c0cee,2 +np.float32,0xfe37cb64,0xff800000,2 +np.float32,0x1a88,0x1a88,2 +np.float32,0x3e22a472,0x3e2353ba,2 +np.float32,0x7f07d6a0,0x7f800000,2 +np.float32,0x3f78f435,0x3f910bb5,2 +np.float32,0x555a4a,0x555a4a,2 +np.float32,0x3e306c1f,0x3e314be3,2 +np.float32,0x8005877c,0x8005877c,2 +np.float32,0x4df389,0x4df389,2 +np.float32,0x8069ffc7,0x8069ffc7,2 +np.float32,0x3f328f24,0x3f4164c6,2 +np.float32,0x53a31b,0x53a31b,2 +np.float32,0xbe4d6768,0xbe4ec8be,2 +np.float32,0x7fa00000,0x7fe00000,2 +np.float32,0x3f484c1b,0x3f5d5e2f,2 +np.float32,0x8038be05,0x8038be05,2 +np.float32,0x58ac0f,0x58ac0f,2 +np.float32,0x7ed7fb72,0x7f800000,2 +np.float32,0x5a22e1,0x5a22e1,2 +np.float32,0xbebb7394,0xbebfaad6,2 +np.float32,0xbda98160,0xbda9b2ef,2 +np.float32,0x7f3e5c42,0x7f800000,2 +np.float32,0xfed204ae,0xff800000,2 +np.float32,0xbf5ef782,0xbf7c3ec5,2 +np.float32,0xbef7a0a8,0xbf00b292,2 +np.float32,0xfee6e176,0xff800000,2 +np.float32,0xfe121140,0xff800000,2 +np.float32,0xfe9e13be,0xff800000,2 +np.float32,0xbf3c98b1,0xbf4e2003,2 +np.float32,0x77520d,0x77520d,2 +np.float32,0xf17b2,0xf17b2,2 +np.float32,0x724d2f,0x724d2f,2 +np.float32,0x7eb326f5,0x7f800000,2 +np.float32,0x3edd6bf2,0x3ee4636e,2 +np.float32,0x350f57,0x350f57,2 +np.float32,0xff7d4435,0xff800000,2 +np.float32,0x802b2b9d,0x802b2b9d,2 +np.float32,0xbf7fbeee,0xbf963acf,2 +np.float32,0x804f3100,0x804f3100,2 +np.float32,0x7c594a71,0x7f800000,2 +np.float32,0x3ef49340,0x3efdfbb6,2 +np.float32,0x2e0659,0x2e0659,2 +np.float32,0x8006d5fe,0x8006d5fe,2 +np.float32,0xfd2a00b0,0xff800000,2 +np.float32,0xbee1c016,0xbee922ed,2 +np.float32,0x3e3b7de8,0x3e3c8a8b,2 +np.float32,0x805e6bba,0x805e6bba,2 +np.float32,0x1a7da2,0x1a7da2,2 +np.float32,0x6caba4,0x6caba4,2 +np.float32,0x802f7eab,0x802f7eab,2 +np.float32,0xff68b16b,0xff800000,2 +np.float32,0x8064f5e5,0x8064f5e5,2 +np.float32,0x2e39b4,0x2e39b4,2 +np.float32,0x800000,0x800000,2 +np.float32,0xfd0334c0,0xff800000,2 +np.float32,0x3e952fc4,0x3e974e7e,2 +np.float32,0x80057d33,0x80057d33,2 +np.float32,0x3ed3ddc4,0x3ed9f6f1,2 +np.float32,0x3f74ce18,0x3f8dedf4,2 +np.float32,0xff6bb7c0,0xff800000,2 +np.float32,0xff43bc21,0xff800000,2 +np.float32,0x80207570,0x80207570,2 +np.float32,0x7e1dda75,0x7f800000,2 +np.float32,0x3efe335c,0x3f0462ff,2 +np.float32,0xbf252c0c,0xbf30df70,2 +np.float32,0x3ef4b8e3,0x3efe25ba,2 +np.float32,0x7c33938d,0x7f800000,2 +np.float32,0x3eb1593c,0x3eb4ea95,2 +np.float32,0xfe1d0068,0xff800000,2 +np.float32,0xbf10da9b,0xbf18b551,2 +np.float32,0xfeb65748,0xff800000,2 +np.float32,0xfe8c6014,0xff800000,2 +np.float32,0x3f0503e2,0x3f0b14e3,2 +np.float32,0xfe5e5248,0xff800000,2 +np.float32,0xbd10afa0,0xbd10b754,2 +np.float32,0xff64b609,0xff800000,2 +np.float32,0xbf674a96,0xbf84089c,2 +np.float32,0x7f5d200d,0x7f800000,2 +np.float32,0x3cf44900,0x3cf45245,2 +np.float32,0x8044445a,0x8044445a,2 +np.float32,0xff35b676,0xff800000,2 +np.float32,0x806452cd,0x806452cd,2 +np.float32,0xbf2930fb,0xbf35c7b4,2 +np.float32,0x7e500617,0x7f800000,2 +np.float32,0x543719,0x543719,2 +np.float32,0x3ed11068,0x3ed6ec1d,2 +np.float32,0xbd8db068,0xbd8dcd59,2 +np.float32,0x3ede62c8,0x3ee571d0,2 +np.float32,0xbf00a410,0xbf061f9c,2 +np.float32,0xbf44fa39,0xbf58ff5b,2 +np.float32,0x3f1c3114,0x3f261069,2 +np.float32,0xbdea6210,0xbdeae521,2 +np.float32,0x80059f6d,0x80059f6d,2 +np.float32,0xbdba15f8,0xbdba578c,2 +np.float32,0x6d8a61,0x6d8a61,2 +np.float32,0x6f5428,0x6f5428,2 +np.float32,0x18d0e,0x18d0e,2 +np.float32,0x50e131,0x50e131,2 +np.float32,0x3f2f52be,0x3f3d5a7e,2 +np.float32,0x7399d8,0x7399d8,2 +np.float32,0x106524,0x106524,2 +np.float32,0x7ebf1c53,0x7f800000,2 +np.float32,0x80276458,0x80276458,2 +np.float32,0x3ebbde67,0x3ec01ceb,2 +np.float32,0x80144d9d,0x80144d9d,2 +np.float32,0x8017ea6b,0x8017ea6b,2 +np.float32,0xff38f201,0xff800000,2 +np.float32,0x7f2daa82,0x7f800000,2 +np.float32,0x3f3cb7c7,0x3f4e47ed,2 +np.float32,0x7f08c779,0x7f800000,2 +np.float32,0xbecc907a,0xbed20cec,2 +np.float32,0x7d440002,0x7f800000,2 +np.float32,0xbd410d80,0xbd411fcd,2 +np.float32,0x3d63ae07,0x3d63cc0c,2 +np.float32,0x805a9c13,0x805a9c13,2 +np.float32,0x803bdcdc,0x803bdcdc,2 +np.float32,0xbe88b354,0xbe8a5497,2 +np.float32,0x3f4eaf43,0x3f65e1c2,2 +np.float32,0x3f15e5b8,0x3f1e9c60,2 +np.float32,0x3e8a870c,0x3e8c394e,2 +np.float32,0x7e113de9,0x7f800000,2 +np.float32,0x7ee5ba41,0x7f800000,2 +np.float32,0xbe73d178,0xbe7620eb,2 +np.float32,0xfe972e6a,0xff800000,2 +np.float32,0xbf65567d,0xbf82a25a,2 +np.float32,0x3f38247e,0x3f487010,2 +np.float32,0xbece1c62,0xbed3b918,2 +np.float32,0x442c8d,0x442c8d,2 +np.float32,0x2dc52,0x2dc52,2 +np.float32,0x802ed923,0x802ed923,2 +np.float32,0x788cf8,0x788cf8,2 +np.float32,0x8024888e,0x8024888e,2 +np.float32,0x3f789bde,0x3f90c8fc,2 +np.float32,0x3f5de620,0x3f7abf88,2 +np.float32,0x3f0ffc45,0x3f17b2a7,2 +np.float32,0xbf709678,0xbf8accd4,2 +np.float32,0x12181f,0x12181f,2 +np.float32,0xfe54bbe4,0xff800000,2 +np.float32,0x7f1daba0,0x7f800000,2 +np.float32,0xbf6226df,0xbf805e3c,2 +np.float32,0xbd120610,0xbd120dfb,2 +np.float32,0x7f75e951,0x7f800000,2 +np.float32,0x80068048,0x80068048,2 +np.float32,0x45f04a,0x45f04a,2 +np.float32,0xff4c4f58,0xff800000,2 +np.float32,0x311604,0x311604,2 +np.float32,0x805e809c,0x805e809c,2 +np.float32,0x3d1d62c0,0x3d1d6caa,2 +np.float32,0x7f14ccf9,0x7f800000,2 +np.float32,0xff10017c,0xff800000,2 +np.float32,0xbf43ec48,0xbf579df4,2 +np.float32,0xff64da57,0xff800000,2 +np.float32,0x7f0622c5,0x7f800000,2 +np.float32,0x7f5460cd,0x7f800000,2 +np.float32,0xff0ef1c6,0xff800000,2 +np.float32,0xbece1146,0xbed3ad13,2 +np.float32,0x3f4d457f,0x3f63fc70,2 +np.float32,0xbdc1da28,0xbdc2244b,2 +np.float32,0xbe46d3f4,0xbe481463,2 +np.float32,0xff36b3d6,0xff800000,2 +np.float32,0xbec2e76c,0xbec7a540,2 +np.float32,0x8078fb81,0x8078fb81,2 +np.float32,0x7ec819cb,0x7f800000,2 +np.float32,0x39c4d,0x39c4d,2 +np.float32,0xbe8cddc2,0xbe8ea670,2 +np.float32,0xbf36dffb,0xbf46d48b,2 +np.float32,0xbf2302a3,0xbf2e4065,2 +np.float32,0x3e7b34a2,0x3e7dbb9a,2 +np.float32,0x3e3d87e1,0x3e3e9d62,2 +np.float32,0x7f3c94b1,0x7f800000,2 +np.float32,0x80455a85,0x80455a85,2 +np.float32,0xfd875568,0xff800000,2 +np.float32,0xbf618103,0xbf7fd1c8,2 +np.float32,0xbe332e3c,0xbe3418ac,2 +np.float32,0x80736b79,0x80736b79,2 +np.float32,0x3f705d9a,0x3f8aa2e6,2 +np.float32,0xbf3a36d2,0xbf4b134b,2 +np.float32,0xfddc55c0,0xff800000,2 +np.float32,0x805606fd,0x805606fd,2 +np.float32,0x3f4f0bc4,0x3f665e25,2 +np.float32,0xfebe7494,0xff800000,2 +np.float32,0xff0c541b,0xff800000,2 +np.float32,0xff0b8e7f,0xff800000,2 +np.float32,0xbcc51640,0xbcc51b1e,2 +np.float32,0x7ec1c4d0,0x7f800000,2 +np.float32,0xfc5c8e00,0xff800000,2 +np.float32,0x7f48d682,0x7f800000,2 +np.float32,0x7d5c7d8d,0x7f800000,2 +np.float32,0x8052ed03,0x8052ed03,2 +np.float32,0x7d4db058,0x7f800000,2 +np.float32,0xff3a65ee,0xff800000,2 +np.float32,0x806eeb93,0x806eeb93,2 +np.float32,0x803f9733,0x803f9733,2 +np.float32,0xbf2d1388,0xbf3a90e3,2 +np.float32,0x68e260,0x68e260,2 +np.float32,0x3e47a69f,0x3e48eb0e,2 +np.float32,0x3f0c4623,0x3f136646,2 +np.float32,0x3f37a831,0x3f47d249,2 +np.float32,0xff153a0c,0xff800000,2 +np.float32,0x2e8086,0x2e8086,2 +np.float32,0xc3f5e,0xc3f5e,2 +np.float32,0x7f31dc14,0x7f800000,2 +np.float32,0xfee37d68,0xff800000,2 +np.float32,0x711d4,0x711d4,2 +np.float32,0x7ede2ce4,0x7f800000,2 +np.float32,0xbf5d76d0,0xbf7a23d0,2 +np.float32,0xbe2b9eb4,0xbe2c6cac,2 +np.float32,0x2b14d7,0x2b14d7,2 +np.float32,0x3ea1db72,0x3ea4910e,2 +np.float32,0x7f3f03f7,0x7f800000,2 +np.float32,0x92de5,0x92de5,2 +np.float32,0x80322e1b,0x80322e1b,2 +np.float32,0xbf5eb214,0xbf7bdd55,2 +np.float32,0xbf21bf87,0xbf2cba14,2 +np.float32,0xbf5d4b78,0xbf79e73a,2 +np.float32,0xbc302840,0xbc30291e,2 +np.float32,0xfee567c6,0xff800000,2 +np.float32,0x7f70ee14,0x7f800000,2 +np.float32,0x7e5c4b33,0x7f800000,2 +np.float32,0x3f1e7b64,0x3f28ccfd,2 +np.float32,0xbf6309f7,0xbf80ff3e,2 +np.float32,0x1c2fe3,0x1c2fe3,2 +np.float32,0x8e78d,0x8e78d,2 +np.float32,0x7f2fce73,0x7f800000,2 +np.float32,0x7f25f690,0x7f800000,2 +np.float32,0x8074cba5,0x8074cba5,2 +np.float32,0x16975f,0x16975f,2 +np.float32,0x8012cf5c,0x8012cf5c,2 +np.float32,0x7da72138,0x7f800000,2 +np.float32,0xbf563f35,0xbf7025be,2 +np.float32,0x3f69d3f5,0x3f85dcbe,2 +np.float32,0xbf15c148,0xbf1e7184,2 +np.float32,0xbe7a077c,0xbe7c8564,2 +np.float32,0x3ebb6ef1,0x3ebfa5e3,2 +np.float32,0xbe41fde4,0xbe43277b,2 +np.float32,0x7f10b479,0x7f800000,2 +np.float32,0x3e021ace,0x3e02747d,2 +np.float32,0x3e93d984,0x3e95e9be,2 +np.float32,0xfe17e924,0xff800000,2 +np.float32,0xfe21a7cc,0xff800000,2 +np.float32,0x8019b660,0x8019b660,2 +np.float32,0x7e954631,0x7f800000,2 +np.float32,0x7e7330d1,0x7f800000,2 +np.float32,0xbe007d98,0xbe00d3fb,2 +np.float32,0x3ef3870e,0x3efcd077,2 +np.float32,0x7f5bbde8,0x7f800000,2 +np.float32,0x14a5b3,0x14a5b3,2 +np.float32,0x3e84d23f,0x3e8650e8,2 +np.float32,0x80763017,0x80763017,2 +np.float32,0xfe871f36,0xff800000,2 +np.float32,0x7ed43150,0x7f800000,2 +np.float32,0x3cc44547,0x3cc44a16,2 +np.float32,0x3ef0c0fa,0x3ef9b97d,2 +np.float32,0xbede9944,0xbee5ad86,2 +np.float32,0xbf10f0b2,0xbf18cf0a,2 +np.float32,0x3ecdaa78,0x3ed33dd9,2 +np.float32,0x3f7cc058,0x3f93ee6b,2 +np.float32,0x2d952f,0x2d952f,2 +np.float32,0x3f2cf2de,0x3f3a687a,2 +np.float32,0x8029b33c,0x8029b33c,2 +np.float32,0xbf22c737,0xbf2df888,2 +np.float32,0xff53c84a,0xff800000,2 +np.float32,0x40a509,0x40a509,2 +np.float32,0x56abce,0x56abce,2 +np.float32,0xff7fffff,0xff800000,2 +np.float32,0xbf3e67f6,0xbf50741c,2 +np.float32,0xfde67580,0xff800000,2 +np.float32,0x3f103e9b,0x3f17ffc7,2 +np.float32,0x3f3f7232,0x3f51cbe2,2 +np.float32,0x803e6d78,0x803e6d78,2 +np.float32,0x3a61da,0x3a61da,2 +np.float32,0xbc04de80,0xbc04dedf,2 +np.float32,0x7f1e7c52,0x7f800000,2 +np.float32,0x8058ee88,0x8058ee88,2 +np.float32,0x806dd660,0x806dd660,2 +np.float32,0x7e4af9,0x7e4af9,2 +np.float32,0x80702d27,0x80702d27,2 +np.float32,0x802cdad1,0x802cdad1,2 +np.float32,0x3e9b5c23,0x3e9dc149,2 +np.float32,0x7f076e89,0x7f800000,2 +np.float32,0x7f129d68,0x7f800000,2 +np.float32,0x7f6f0b0a,0x7f800000,2 +np.float32,0x7eafafb5,0x7f800000,2 +np.float32,0xbf2ef2ca,0xbf3ce332,2 +np.float32,0xff34c000,0xff800000,2 +np.float32,0x7f559274,0x7f800000,2 +np.float32,0xfed08556,0xff800000,2 +np.float32,0xbf014621,0xbf06d6ad,2 +np.float32,0xff23086a,0xff800000,2 +np.float32,0x6cb33f,0x6cb33f,2 +np.float32,0xfe6e3ffc,0xff800000,2 +np.float32,0x3e6bbec0,0x3e6dd546,2 +np.float32,0x8036afa6,0x8036afa6,2 +np.float32,0xff800000,0xff800000,2 +np.float32,0x3e0ed05c,0x3e0f46ff,2 +np.float32,0x3ec9215c,0x3ece57e6,2 +np.float32,0xbf449fa4,0xbf5888aa,2 +np.float32,0xff2c6640,0xff800000,2 +np.float32,0x7f08f4a7,0x7f800000,2 +np.float32,0xbf4f63e5,0xbf66d4c1,2 +np.float32,0x3f800000,0x3f966cfe,2 +np.float32,0xfe86c7d2,0xff800000,2 +np.float32,0x3f63f969,0x3f81a970,2 +np.float32,0xbd7022d0,0xbd704609,2 +np.float32,0xbead906c,0xbeb0e853,2 +np.float32,0x7ef149ee,0x7f800000,2 +np.float32,0xff0b9ff7,0xff800000,2 +np.float32,0x3f38380d,0x3f4888e7,2 +np.float32,0x3ef3a3e2,0x3efcf09e,2 +np.float32,0xff616477,0xff800000,2 +np.float32,0x3f3f83e4,0x3f51e2c3,2 +np.float32,0xbf79963c,0xbf918642,2 +np.float32,0x801416f4,0x801416f4,2 +np.float32,0xff75ce6d,0xff800000,2 +np.float32,0xbdbf3588,0xbdbf7cad,2 +np.float32,0xbe6ea938,0xbe70d3dc,2 +np.float32,0x8066f977,0x8066f977,2 +np.float32,0x3f5b5362,0x3f7728aa,2 +np.float32,0xbf72052c,0xbf8bdbd8,2 +np.float32,0xbe21ed74,0xbe229a6f,2 +np.float32,0x8062d19c,0x8062d19c,2 +np.float32,0x3ed8d01f,0x3edf59e6,2 +np.float32,0x803ed42b,0x803ed42b,2 +np.float32,0xbe099a64,0xbe0a0481,2 +np.float32,0xbe173eb4,0xbe17cba2,2 +np.float32,0xbebdcf02,0xbec22faf,2 +np.float32,0x7e3ff29e,0x7f800000,2 +np.float32,0x367c92,0x367c92,2 +np.float32,0xbf5c9db8,0xbf78f4a4,2 +np.float32,0xff0b49ea,0xff800000,2 +np.float32,0x3f4f9bc4,0x3f672001,2 +np.float32,0x85d4a,0x85d4a,2 +np.float32,0x80643e33,0x80643e33,2 +np.float32,0x8013aabd,0x8013aabd,2 +np.float32,0xff6997c3,0xff800000,2 +np.float32,0x3f4dd43c,0x3f64bbb6,2 +np.float32,0xff13bbb9,0xff800000,2 +np.float32,0x3f34efa2,0x3f446187,2 +np.float32,0x3e4b2f10,0x3e4c850d,2 +np.float32,0xfef695c6,0xff800000,2 +np.float32,0x7f7e0057,0x7f800000,2 +np.float32,0x3f6e1b9c,0x3f88fa40,2 +np.float32,0x806e46cf,0x806e46cf,2 +np.float32,0x3f15a88a,0x3f1e546c,2 +np.float32,0xbd2de7d0,0xbd2df530,2 +np.float32,0xbf63cae0,0xbf818854,2 +np.float32,0xbdc3e1a0,0xbdc42e1e,2 +np.float32,0xbf11a038,0xbf199b98,2 +np.float32,0xbec13706,0xbec5d56b,2 +np.float32,0x3f1c5f54,0x3f26478d,2 +np.float32,0x3e9ea97e,0x3ea136b4,2 +np.float32,0xfeb5a508,0xff800000,2 +np.float32,0x7f4698f4,0x7f800000,2 +np.float32,0xff51ee2c,0xff800000,2 +np.float32,0xff5994df,0xff800000,2 +np.float32,0x4b9fb9,0x4b9fb9,2 +np.float32,0xfda10d98,0xff800000,2 +np.float32,0x525555,0x525555,2 +np.float32,0x7ed571ef,0x7f800000,2 +np.float32,0xbf600d18,0xbf7dc50c,2 +np.float32,0x3ec674ca,0x3ecb768b,2 +np.float32,0x3cb69115,0x3cb694f3,2 +np.float32,0x7eac75f2,0x7f800000,2 +np.float32,0x804d4d75,0x804d4d75,2 +np.float32,0xfed5292e,0xff800000,2 +np.float32,0x800ed06a,0x800ed06a,2 +np.float32,0xfec37584,0xff800000,2 +np.float32,0x3ef96ac7,0x3f01b326,2 +np.float32,0x42f743,0x42f743,2 +np.float32,0x3f56f442,0x3f711e39,2 +np.float32,0xbf7ea726,0xbf956375,2 +np.float32,0x806c7202,0x806c7202,2 +np.float32,0xbd8ee980,0xbd8f0733,2 +np.float32,0xbdf2e930,0xbdf37b18,2 +np.float32,0x3f103910,0x3f17f955,2 +np.float32,0xff123e8f,0xff800000,2 +np.float32,0x806e4b5d,0x806e4b5d,2 +np.float32,0xbf4f3bfc,0xbf669f07,2 +np.float32,0xbf070c16,0xbf0d6609,2 +np.float32,0xff00e0ba,0xff800000,2 +np.float32,0xff49d828,0xff800000,2 +np.float32,0x7e47f04a,0x7f800000,2 +np.float32,0x7e984dac,0x7f800000,2 +np.float32,0x3f77473c,0x3f8fc858,2 +np.float32,0x3f017439,0x3f070ac8,2 +np.float32,0x118417,0x118417,2 +np.float32,0xbcf7a2c0,0xbcf7ac68,2 +np.float32,0xfee46fee,0xff800000,2 +np.float32,0x3e42a648,0x3e43d2e9,2 +np.float32,0x80131916,0x80131916,2 +np.float32,0x806209d3,0x806209d3,2 +np.float32,0x807c1f12,0x807c1f12,2 +np.float32,0x2f3696,0x2f3696,2 +np.float32,0xff28722b,0xff800000,2 +np.float32,0x7f1416a1,0x7f800000,2 +np.float32,0x8054e7a1,0x8054e7a1,2 +np.float32,0xbddc39a0,0xbddca656,2 +np.float32,0x7dc60175,0x7f800000,2 +np.float64,0x7fd0ae584da15cb0,0x7ff0000000000000,1 +np.float64,0x7fd41d68e5283ad1,0x7ff0000000000000,1 +np.float64,0x7fe93073bb7260e6,0x7ff0000000000000,1 +np.float64,0x3fb4fd19d229fa34,0x3fb5031f57dbac0f,1 +np.float64,0x85609ce10ac2,0x85609ce10ac2,1 +np.float64,0xbfd7aa12ccaf5426,0xbfd8351003a320e2,1 +np.float64,0x8004487c9b4890fa,0x8004487c9b4890fa,1 +np.float64,0x7fe7584cfd2eb099,0x7ff0000000000000,1 +np.float64,0x800ea8edc6dd51dc,0x800ea8edc6dd51dc,1 +np.float64,0x3fe0924aa5a12495,0x3fe15276e271c6dc,1 +np.float64,0x3feb1abf6d36357f,0x3fee76b4d3d06964,1 +np.float64,0x3fa8c14534318280,0x3fa8c3bd5ce5923c,1 +np.float64,0x800b9f5915d73eb3,0x800b9f5915d73eb3,1 +np.float64,0xffc05aaa7820b554,0xfff0000000000000,1 +np.float64,0x800157eda8c2afdc,0x800157eda8c2afdc,1 +np.float64,0xffe8d90042b1b200,0xfff0000000000000,1 +np.float64,0x3feda02ea93b405d,0x3ff1057e61d08d59,1 +np.float64,0xffd03b7361a076e6,0xfff0000000000000,1 +np.float64,0x3fe1a8ecd7e351da,0x3fe291eda9080847,1 +np.float64,0xffc5bfdff82b7fc0,0xfff0000000000000,1 +np.float64,0xbfe6fb3d386df67a,0xbfe9022c05df0565,1 +np.float64,0x7fefffffffffffff,0x7ff0000000000000,1 +np.float64,0x7fa10c340c221867,0x7ff0000000000000,1 +np.float64,0x3fe55cbf1daab97e,0x3fe6fc1648258b75,1 +np.float64,0xbfddeb5f60bbd6be,0xbfdf056d4fb5825f,1 +np.float64,0xffddb1a8213b6350,0xfff0000000000000,1 +np.float64,0xbfb20545e4240a88,0xbfb2091579375176,1 +np.float64,0x3f735ded2026bbda,0x3f735df1dad4ee3a,1 +np.float64,0xbfd1eb91efa3d724,0xbfd227c044dead61,1 +np.float64,0xffd737c588ae6f8c,0xfff0000000000000,1 +np.float64,0x3fc46818ec28d032,0x3fc47e416c4237a6,1 +np.float64,0x0,0x0,1 +np.float64,0xffb632097a2c6410,0xfff0000000000000,1 +np.float64,0xbfcb5ae84b36b5d0,0xbfcb905613af55b8,1 +np.float64,0xbfe7b926402f724c,0xbfe9f4f0be6aacc3,1 +np.float64,0x80081840b3f03082,0x80081840b3f03082,1 +np.float64,0x3fe767a656eecf4d,0x3fe98c53b4779de7,1 +np.float64,0x8005834c088b0699,0x8005834c088b0699,1 +np.float64,0x80074e92658e9d26,0x80074e92658e9d26,1 +np.float64,0x80045d60c268bac2,0x80045d60c268bac2,1 +np.float64,0xffb9aecfe8335da0,0xfff0000000000000,1 +np.float64,0x7fcad3e1cd35a7c3,0x7ff0000000000000,1 +np.float64,0xbf881853d03030c0,0xbf8818783e28fc87,1 +np.float64,0xe18c6d23c318e,0xe18c6d23c318e,1 +np.float64,0x7fcb367b8f366cf6,0x7ff0000000000000,1 +np.float64,0x5c13436cb8269,0x5c13436cb8269,1 +np.float64,0xffe5399938aa7332,0xfff0000000000000,1 +np.float64,0xbfdc45dbc3b88bb8,0xbfdd33958222c27e,1 +np.float64,0xbfd714691bae28d2,0xbfd7954edbef810b,1 +np.float64,0xbfdf18b02b3e3160,0xbfe02ad13634c651,1 +np.float64,0x8003e6f276e7cde6,0x8003e6f276e7cde6,1 +np.float64,0x3febb6b412776d68,0x3fef4f753def31f9,1 +np.float64,0x7fe016a3b4a02d46,0x7ff0000000000000,1 +np.float64,0x3fdc899ac7b91336,0x3fdd7e1cee1cdfc8,1 +np.float64,0x800219271e24324f,0x800219271e24324f,1 +np.float64,0x1529d93e2a53c,0x1529d93e2a53c,1 +np.float64,0x800d5bc827fab790,0x800d5bc827fab790,1 +np.float64,0x3e1495107c293,0x3e1495107c293,1 +np.float64,0x3fe89da0f2b13b42,0x3feb1dc1f3015ad7,1 +np.float64,0x800ba8c17b975183,0x800ba8c17b975183,1 +np.float64,0x8002dacf0265b59f,0x8002dacf0265b59f,1 +np.float64,0xffe6d0a4cc2da149,0xfff0000000000000,1 +np.float64,0x3fdf23fe82be47fc,0x3fe03126d8e2b309,1 +np.float64,0xffe41b1f1c28363e,0xfff0000000000000,1 +np.float64,0xbfd635c634ac6b8c,0xbfd6a8966da6adaa,1 +np.float64,0x800755bc08eeab79,0x800755bc08eeab79,1 +np.float64,0x800ba4c47c374989,0x800ba4c47c374989,1 +np.float64,0x7fec9f7649793eec,0x7ff0000000000000,1 +np.float64,0x7fdbf45738b7e8ad,0x7ff0000000000000,1 +np.float64,0x3f5597f07eab4,0x3f5597f07eab4,1 +np.float64,0xbfbf4599183e8b30,0xbfbf5985d8c65097,1 +np.float64,0xbf5b200580364000,0xbf5b2006501b21ae,1 +np.float64,0x7f91868370230d06,0x7ff0000000000000,1 +np.float64,0x3838e2a67071d,0x3838e2a67071d,1 +np.float64,0xffefe3ff5d3fc7fe,0xfff0000000000000,1 +np.float64,0xffe66b26d06cd64d,0xfff0000000000000,1 +np.float64,0xbfd830a571b0614a,0xbfd8c526927c742c,1 +np.float64,0x7fe8442122f08841,0x7ff0000000000000,1 +np.float64,0x800efa8c637df519,0x800efa8c637df519,1 +np.float64,0xf0026835e004d,0xf0026835e004d,1 +np.float64,0xffb11beefe2237e0,0xfff0000000000000,1 +np.float64,0x3fef9bbb327f3776,0x3ff2809f10641c32,1 +np.float64,0x350595306a0b3,0x350595306a0b3,1 +np.float64,0xf7f6538befecb,0xf7f6538befecb,1 +np.float64,0xffe36379c4a6c6f3,0xfff0000000000000,1 +np.float64,0x28b1d82e5163c,0x28b1d82e5163c,1 +np.float64,0x70a3d804e147c,0x70a3d804e147c,1 +np.float64,0xffd96c1bc9b2d838,0xfff0000000000000,1 +np.float64,0xffce8e00893d1c00,0xfff0000000000000,1 +np.float64,0x800f2bdcb25e57b9,0x800f2bdcb25e57b9,1 +np.float64,0xbfe0d9c63361b38c,0xbfe1a3eb02192b76,1 +np.float64,0xbfdc7b8711b8f70e,0xbfdd6e9db3a01e51,1 +np.float64,0x99e22ec133c46,0x99e22ec133c46,1 +np.float64,0xffeaef6ddab5dedb,0xfff0000000000000,1 +np.float64,0x7fe89c22c0f13845,0x7ff0000000000000,1 +np.float64,0x8002d5207de5aa42,0x8002d5207de5aa42,1 +np.float64,0x3fd1b13353236267,0x3fd1eb1b9345dfca,1 +np.float64,0x800ccae0a41995c1,0x800ccae0a41995c1,1 +np.float64,0x3fdbdaba38b7b574,0x3fdcbdfcbca37ce6,1 +np.float64,0x5b06d12cb60db,0x5b06d12cb60db,1 +np.float64,0xffd52262752a44c4,0xfff0000000000000,1 +np.float64,0x5a17f050b42ff,0x5a17f050b42ff,1 +np.float64,0x3d24205e7a485,0x3d24205e7a485,1 +np.float64,0x7fbed4dec63da9bd,0x7ff0000000000000,1 +np.float64,0xbfe56e9776aadd2f,0xbfe71212863c284f,1 +np.float64,0x7fea0bc952341792,0x7ff0000000000000,1 +np.float64,0x800f692d139ed25a,0x800f692d139ed25a,1 +np.float64,0xffdb63feab36c7fe,0xfff0000000000000,1 +np.float64,0x3fe1c2297fe38452,0x3fe2af21293c9571,1 +np.float64,0x7fede384747bc708,0x7ff0000000000000,1 +np.float64,0x800440169288802e,0x800440169288802e,1 +np.float64,0xffe3241eeb26483e,0xfff0000000000000,1 +np.float64,0xffe28f3879651e70,0xfff0000000000000,1 +np.float64,0xa435cbc1486d,0xa435cbc1486d,1 +np.float64,0x7fe55e08db6abc11,0x7ff0000000000000,1 +np.float64,0x1405e624280be,0x1405e624280be,1 +np.float64,0x3fd861bdf0b0c37c,0x3fd8f9d2e33e45e5,1 +np.float64,0x3feeb67cdc3d6cfa,0x3ff1d337d81d1c14,1 +np.float64,0x3fd159a10e22b342,0x3fd1903be7c2ea0c,1 +np.float64,0x3fd84626bc308c4d,0x3fd8dc373645e65b,1 +np.float64,0xffd3da81d9a7b504,0xfff0000000000000,1 +np.float64,0xbfd4a768b8294ed2,0xbfd503aa7c240051,1 +np.float64,0x3fe3059f2a660b3e,0x3fe42983e0c6bb2e,1 +np.float64,0x3fe3b8353827706a,0x3fe4fdd635c7269b,1 +np.float64,0xbfe4af0399695e07,0xbfe6277d9002b46c,1 +np.float64,0xbfd7e18a92afc316,0xbfd87066b54c4fe6,1 +np.float64,0x800432bcab48657a,0x800432bcab48657a,1 +np.float64,0x80033d609d267ac2,0x80033d609d267ac2,1 +np.float64,0x7fef5f758e7ebeea,0x7ff0000000000000,1 +np.float64,0xbfed7833dbfaf068,0xbff0e85bf45a5ebc,1 +np.float64,0x3fe2283985a45073,0x3fe325b0a9099c74,1 +np.float64,0xe820b4b3d0417,0xe820b4b3d0417,1 +np.float64,0x8003ecb72aa7d96f,0x8003ecb72aa7d96f,1 +np.float64,0xbfeab2c755b5658f,0xbfede7c83e92a625,1 +np.float64,0xbfc7b287f72f6510,0xbfc7d53ef2ffe9dc,1 +np.float64,0xffd9a41d0f33483a,0xfff0000000000000,1 +np.float64,0x3fd3a5b6e3a74b6c,0x3fd3f516f39a4725,1 +np.float64,0x800bc72091578e42,0x800bc72091578e42,1 +np.float64,0x800ff405ce9fe80c,0x800ff405ce9fe80c,1 +np.float64,0x57918600af24,0x57918600af24,1 +np.float64,0x2a5be7fa54b7e,0x2a5be7fa54b7e,1 +np.float64,0xbfdca7886bb94f10,0xbfdd9f142b5b43e4,1 +np.float64,0xbfe216993ee42d32,0xbfe3112936590995,1 +np.float64,0xbfe06bd9cf20d7b4,0xbfe126cd353ab42f,1 +np.float64,0x8003e6c31827cd87,0x8003e6c31827cd87,1 +np.float64,0x8005f37d810be6fc,0x8005f37d810be6fc,1 +np.float64,0x800715b081ae2b62,0x800715b081ae2b62,1 +np.float64,0x3fef94c35bff2986,0x3ff27b4bed2f4051,1 +np.float64,0x6f5798e0deb0,0x6f5798e0deb0,1 +np.float64,0x3fcef1f05c3de3e1,0x3fcf3f557550598f,1 +np.float64,0xbf9a91c400352380,0xbf9a92876273b85c,1 +np.float64,0x3fc9143f7f322880,0x3fc93d678c05d26b,1 +np.float64,0x78ad847af15b1,0x78ad847af15b1,1 +np.float64,0x8000fdc088c1fb82,0x8000fdc088c1fb82,1 +np.float64,0x800200fd304401fb,0x800200fd304401fb,1 +np.float64,0x7fb8ab09dc315613,0x7ff0000000000000,1 +np.float64,0x3fe949771b7292ee,0x3fec00891c3fc5a2,1 +np.float64,0xbfc54cae0e2a995c,0xbfc565e0f3d0e3af,1 +np.float64,0xffd546161e2a8c2c,0xfff0000000000000,1 +np.float64,0x800fe1d1279fc3a2,0x800fe1d1279fc3a2,1 +np.float64,0x3fd9c45301b388a8,0x3fda77fa1f4c79bf,1 +np.float64,0x7fe10ff238221fe3,0x7ff0000000000000,1 +np.float64,0xbfbc2181ae384300,0xbfbc3002229155c4,1 +np.float64,0xbfe7bbfae4ef77f6,0xbfe9f895e91f468d,1 +np.float64,0x800d3d994f7a7b33,0x800d3d994f7a7b33,1 +np.float64,0xffe6e15a896dc2b4,0xfff0000000000000,1 +np.float64,0x800e6b6c8abcd6d9,0x800e6b6c8abcd6d9,1 +np.float64,0xbfd862c938b0c592,0xbfd8faf1cdcb09db,1 +np.float64,0xffe2411f8464823e,0xfff0000000000000,1 +np.float64,0xffd0b32efaa1665e,0xfff0000000000000,1 +np.float64,0x3ac4ace475896,0x3ac4ace475896,1 +np.float64,0xf9c3a7ebf3875,0xf9c3a7ebf3875,1 +np.float64,0xdb998ba5b7332,0xdb998ba5b7332,1 +np.float64,0xbfe438a14fe87142,0xbfe5981751e4c5cd,1 +np.float64,0xbfbcf48cbc39e918,0xbfbd045d60e65d3a,1 +np.float64,0x7fde499615bc932b,0x7ff0000000000000,1 +np.float64,0x800bba269057744e,0x800bba269057744e,1 +np.float64,0x3fc9bb1ba3337638,0x3fc9e78fdb6799c1,1 +np.float64,0xffd9f974fbb3f2ea,0xfff0000000000000,1 +np.float64,0x7fcf1ad1693e35a2,0x7ff0000000000000,1 +np.float64,0x7fe5dcedd32bb9db,0x7ff0000000000000,1 +np.float64,0xeb06500bd60ca,0xeb06500bd60ca,1 +np.float64,0x7fd73e7b592e7cf6,0x7ff0000000000000,1 +np.float64,0xbfe9d91ae873b236,0xbfecc08482849bcd,1 +np.float64,0xffc85338b730a670,0xfff0000000000000,1 +np.float64,0x7fbba41eee37483d,0x7ff0000000000000,1 +np.float64,0x3fed5624fb7aac4a,0x3ff0cf9f0de1fd54,1 +np.float64,0xffe566d80d6acdb0,0xfff0000000000000,1 +np.float64,0x3fd4477884a88ef1,0x3fd49ec7acdd25a0,1 +np.float64,0x3fcb98c5fd37318c,0x3fcbcfa20e2c2712,1 +np.float64,0xffdeba71d5bd74e4,0xfff0000000000000,1 +np.float64,0x8001edc59dc3db8c,0x8001edc59dc3db8c,1 +np.float64,0x3fe6b09e896d613e,0x3fe8a3bb541ec0e3,1 +np.float64,0x3fe8694b4970d296,0x3fead94d271d05cf,1 +np.float64,0xb52c27bf6a585,0xb52c27bf6a585,1 +np.float64,0x7fcb0a21d9361443,0x7ff0000000000000,1 +np.float64,0xbfd9efc68cb3df8e,0xbfdaa7058c0ccbd1,1 +np.float64,0x8007cd170fef9a2f,0x8007cd170fef9a2f,1 +np.float64,0x3fe83325e770664c,0x3fea92c55c9d567e,1 +np.float64,0x800bd0085537a011,0x800bd0085537a011,1 +np.float64,0xffe05b9e7820b73c,0xfff0000000000000,1 +np.float64,0x3fea4ce4347499c8,0x3fed5cea9fdc541b,1 +np.float64,0x7fe08aae1921155b,0x7ff0000000000000,1 +np.float64,0x3fe7a5e7deef4bd0,0x3fe9dc2e20cfb61c,1 +np.float64,0xbfe0ccc8e6e19992,0xbfe195175f32ee3f,1 +np.float64,0xbfe8649717f0c92e,0xbfead3298974dcf0,1 +np.float64,0x7fed6c5308bad8a5,0x7ff0000000000000,1 +np.float64,0xffdbd8c7af37b190,0xfff0000000000000,1 +np.float64,0xbfb2bc4d06257898,0xbfb2c09569912839,1 +np.float64,0x3fc62eca512c5d95,0x3fc64b4251bce8f9,1 +np.float64,0xbfcae2ddbd35c5bc,0xbfcb15971fc61312,1 +np.float64,0x18d26ce831a4f,0x18d26ce831a4f,1 +np.float64,0x7fe38b279267164e,0x7ff0000000000000,1 +np.float64,0x97e1d9ab2fc3b,0x97e1d9ab2fc3b,1 +np.float64,0xbfee8e4785fd1c8f,0xbff1b52d16807627,1 +np.float64,0xbfb189b4a6231368,0xbfb18d37e83860ee,1 +np.float64,0xffd435761ea86aec,0xfff0000000000000,1 +np.float64,0x3fe6c48ebced891e,0x3fe8bcea189c3867,1 +np.float64,0x7fdadd3678b5ba6c,0x7ff0000000000000,1 +np.float64,0x7fea8f15b7b51e2a,0x7ff0000000000000,1 +np.float64,0xbff0000000000000,0xbff2cd9fc44eb982,1 +np.float64,0x80004c071120980f,0x80004c071120980f,1 +np.float64,0x8005367adfea6cf6,0x8005367adfea6cf6,1 +np.float64,0x3fbdc9139a3b9220,0x3fbdda4aba667ce5,1 +np.float64,0x7fed5ee3ad7abdc6,0x7ff0000000000000,1 +np.float64,0x51563fb2a2ac9,0x51563fb2a2ac9,1 +np.float64,0xbfba7d26ce34fa50,0xbfba894229c50ea1,1 +np.float64,0x6c10db36d821c,0x6c10db36d821c,1 +np.float64,0xbfbdaec0d03b5d80,0xbfbdbfca6ede64f4,1 +np.float64,0x800a1cbe7414397d,0x800a1cbe7414397d,1 +np.float64,0x800ae6e7f2d5cdd0,0x800ae6e7f2d5cdd0,1 +np.float64,0x3fea63d3fef4c7a8,0x3fed7c1356688ddc,1 +np.float64,0xbfde1e3a88bc3c76,0xbfdf3dfb09cc2260,1 +np.float64,0xbfd082d75a2105ae,0xbfd0b1e28c84877b,1 +np.float64,0x7fea1e5e85f43cbc,0x7ff0000000000000,1 +np.float64,0xffe2237a1a6446f4,0xfff0000000000000,1 +np.float64,0x3fd1e2be8523c57d,0x3fd21e93dfd1bbc4,1 +np.float64,0x3fd1acd428a359a8,0x3fd1e6916a42bc3a,1 +np.float64,0x61a152f0c342b,0x61a152f0c342b,1 +np.float64,0xbfc61a6b902c34d8,0xbfc6369557690ba0,1 +np.float64,0x7fd1a84b1f235095,0x7ff0000000000000,1 +np.float64,0x1c5cc7e638b9a,0x1c5cc7e638b9a,1 +np.float64,0x8008039755f0072f,0x8008039755f0072f,1 +np.float64,0x80097532d6f2ea66,0x80097532d6f2ea66,1 +np.float64,0xbfc6d979a12db2f4,0xbfc6f89777c53f8f,1 +np.float64,0x8004293ab1085276,0x8004293ab1085276,1 +np.float64,0x3fc2af5c21255eb8,0x3fc2c05dc0652554,1 +np.float64,0xbfd9a5ab87b34b58,0xbfda56d1076abc98,1 +np.float64,0xbfebd360ba77a6c2,0xbfef779fd6595f9b,1 +np.float64,0xffd5313c43aa6278,0xfff0000000000000,1 +np.float64,0xbfe994a262b32945,0xbfec64b969852ed5,1 +np.float64,0x3fce01a52e3c034a,0x3fce48324eb29c31,1 +np.float64,0x56bd74b2ad7af,0x56bd74b2ad7af,1 +np.float64,0xb84093ff70813,0xb84093ff70813,1 +np.float64,0x7fe776df946eedbe,0x7ff0000000000000,1 +np.float64,0xbfe294ac2e652958,0xbfe3a480938afa26,1 +np.float64,0x7fe741b4d0ee8369,0x7ff0000000000000,1 +np.float64,0x800b7e8a1056fd15,0x800b7e8a1056fd15,1 +np.float64,0x7fd28f1269251e24,0x7ff0000000000000,1 +np.float64,0x8009d4492e73a893,0x8009d4492e73a893,1 +np.float64,0x3fe3f27fca67e500,0x3fe543aff825e244,1 +np.float64,0x3fd12447e5a24890,0x3fd158efe43c0452,1 +np.float64,0xbfd58df0f2ab1be2,0xbfd5f6d908e3ebce,1 +np.float64,0xffc0a8e4642151c8,0xfff0000000000000,1 +np.float64,0xbfedb197787b632f,0xbff112367ec9d3e7,1 +np.float64,0xffdde07a7f3bc0f4,0xfff0000000000000,1 +np.float64,0x3fe91f3e5b723e7d,0x3febc886a1d48364,1 +np.float64,0x3fe50415236a082a,0x3fe68f43a5468d8c,1 +np.float64,0xd9a0c875b3419,0xd9a0c875b3419,1 +np.float64,0xbfee04ccf4bc099a,0xbff14f4740a114cf,1 +np.float64,0xbfd2bcc6a125798e,0xbfd30198b1e7d7ed,1 +np.float64,0xbfeb3c16f8f6782e,0xbfeea4ce47d09f58,1 +np.float64,0xffd3ba19e4a77434,0xfff0000000000000,1 +np.float64,0x8010000000000000,0x8010000000000000,1 +np.float64,0x3fdef0a642bde14d,0x3fe0146677b3a488,1 +np.float64,0x3fdc3dd0a2b87ba0,0x3fdd2abe65651487,1 +np.float64,0x3fdbb1fd47b763fb,0x3fdc915a2fd19f4b,1 +np.float64,0x7fbaa375e63546eb,0x7ff0000000000000,1 +np.float64,0x433ef8ee867e0,0x433ef8ee867e0,1 +np.float64,0xf5345475ea68b,0xf5345475ea68b,1 +np.float64,0xa126419b424c8,0xa126419b424c8,1 +np.float64,0x3fe0057248200ae5,0x3fe0b2f488339709,1 +np.float64,0xffc5e3b82f2bc770,0xfff0000000000000,1 +np.float64,0xffb215c910242b90,0xfff0000000000000,1 +np.float64,0xbfeba4ae0837495c,0xbfef3642e4b54aac,1 +np.float64,0xffbb187ebe363100,0xfff0000000000000,1 +np.float64,0x3fe4c6a496a98d49,0x3fe64440cdf06aab,1 +np.float64,0x800767a28f6ecf46,0x800767a28f6ecf46,1 +np.float64,0x3fdbed63b1b7dac8,0x3fdcd27318c0b683,1 +np.float64,0x80006d8339e0db07,0x80006d8339e0db07,1 +np.float64,0x8000b504f0416a0b,0x8000b504f0416a0b,1 +np.float64,0xbfe88055bfb100ac,0xbfeaf767bd2767b9,1 +np.float64,0x3fefe503317fca06,0x3ff2b8d4057240c8,1 +np.float64,0x7fe307538b660ea6,0x7ff0000000000000,1 +np.float64,0x944963c12892d,0x944963c12892d,1 +np.float64,0xbfd2c20b38a58416,0xbfd30717900f8233,1 +np.float64,0x7feed04e3e3da09b,0x7ff0000000000000,1 +np.float64,0x3fe639619cac72c3,0x3fe80de7b8560a8d,1 +np.float64,0x3fde066c66bc0cd9,0x3fdf237fb759a652,1 +np.float64,0xbfc56b22b52ad644,0xbfc584c267a47ebd,1 +np.float64,0x3fc710d5b12e21ab,0x3fc730d817ba0d0c,1 +np.float64,0x3fee1dfc347c3bf8,0x3ff161d9c3e15f68,1 +np.float64,0x3fde400954bc8013,0x3fdf639e5cc9e7a9,1 +np.float64,0x56e701f8adce1,0x56e701f8adce1,1 +np.float64,0xbfe33bbc89e67779,0xbfe46996b39381fe,1 +np.float64,0x7fec89e2f87913c5,0x7ff0000000000000,1 +np.float64,0xbfdad58b40b5ab16,0xbfdba098cc0ad5d3,1 +np.float64,0x3fe99c76a13338ed,0x3fec6f31bae613e7,1 +np.float64,0x3fe4242a29a84854,0x3fe57f6b45e5c0ef,1 +np.float64,0xbfe79d3199ef3a63,0xbfe9d0fb96c846ba,1 +np.float64,0x7ff8000000000000,0x7ff8000000000000,1 +np.float64,0xbfeb35a6cf766b4e,0xbfee9be4e7e943f7,1 +np.float64,0x3e047f267c091,0x3e047f267c091,1 +np.float64,0x4bf1376a97e28,0x4bf1376a97e28,1 +np.float64,0x800ef419685de833,0x800ef419685de833,1 +np.float64,0x3fe0efa61a21df4c,0x3fe1bce98baf2f0f,1 +np.float64,0x3fcc13c4d738278a,0x3fcc4d8c778bcaf7,1 +np.float64,0x800f1d291afe3a52,0x800f1d291afe3a52,1 +np.float64,0x3fd3f10e6da7e21d,0x3fd444106761ea1d,1 +np.float64,0x800706d6d76e0dae,0x800706d6d76e0dae,1 +np.float64,0xffa1ffbc9023ff80,0xfff0000000000000,1 +np.float64,0xbfe098f26d6131e5,0xbfe15a08a5f3eac0,1 +np.float64,0x3fe984f9cc7309f4,0x3fec4fcdbdb1cb9b,1 +np.float64,0x7fd7c2f1eaaf85e3,0x7ff0000000000000,1 +np.float64,0x800a8adb64f515b7,0x800a8adb64f515b7,1 +np.float64,0x80060d3ffc8c1a81,0x80060d3ffc8c1a81,1 +np.float64,0xbfec37e4aef86fc9,0xbff0029a6a1d61e2,1 +np.float64,0x800b21bcfcf6437a,0x800b21bcfcf6437a,1 +np.float64,0xbfc08facc1211f58,0xbfc09b8380ea8032,1 +np.float64,0xffebb4b52577696a,0xfff0000000000000,1 +np.float64,0x800b08096df61013,0x800b08096df61013,1 +np.float64,0x8000000000000000,0x8000000000000000,1 +np.float64,0xffd2f0c9c8a5e194,0xfff0000000000000,1 +np.float64,0xffe78b2299af1644,0xfff0000000000000,1 +np.float64,0x7fd0444794a0888e,0x7ff0000000000000,1 +np.float64,0x307c47b460f8a,0x307c47b460f8a,1 +np.float64,0xffe6b4c851ad6990,0xfff0000000000000,1 +np.float64,0xffe1877224a30ee4,0xfff0000000000000,1 +np.float64,0x48d7b5c091af7,0x48d7b5c091af7,1 +np.float64,0xbfa1dc6b1c23b8d0,0xbfa1dd5889e1b7da,1 +np.float64,0x3fe5004737ea008e,0x3fe68a9c310b08c1,1 +np.float64,0x7fec5f0742b8be0e,0x7ff0000000000000,1 +np.float64,0x3fd0a86285a150c5,0x3fd0d8b238d557fa,1 +np.float64,0x7fed60380efac06f,0x7ff0000000000000,1 +np.float64,0xeeca74dfdd94f,0xeeca74dfdd94f,1 +np.float64,0x3fda05aaa8b40b54,0x3fdabebdbf405e84,1 +np.float64,0x800e530ceb1ca61a,0x800e530ceb1ca61a,1 +np.float64,0x800b3866379670cd,0x800b3866379670cd,1 +np.float64,0xffedb3e7fa3b67cf,0xfff0000000000000,1 +np.float64,0xffdfa4c0713f4980,0xfff0000000000000,1 +np.float64,0x7fe4679e0728cf3b,0x7ff0000000000000,1 +np.float64,0xffe978611ef2f0c2,0xfff0000000000000,1 +np.float64,0x7fc9f4601f33e8bf,0x7ff0000000000000,1 +np.float64,0x3fd4942de6a9285c,0x3fd4ef6e089357dd,1 +np.float64,0x3faafe064435fc00,0x3fab0139cd6564dc,1 +np.float64,0x800d145a519a28b5,0x800d145a519a28b5,1 +np.float64,0xbfd82636f2304c6e,0xbfd8b9f75ddd2f02,1 +np.float64,0xbfdf2e975e3e5d2e,0xbfe037174280788c,1 +np.float64,0x7fd7051d7c2e0a3a,0x7ff0000000000000,1 +np.float64,0x8007933d452f267b,0x8007933d452f267b,1 +np.float64,0xb2043beb64088,0xb2043beb64088,1 +np.float64,0x3febfd9708f7fb2e,0x3fefb2ef090f18d2,1 +np.float64,0xffd9bc6bc83378d8,0xfff0000000000000,1 +np.float64,0xc10f9fd3821f4,0xc10f9fd3821f4,1 +np.float64,0x3fe3c83413a79068,0x3fe510fa1dd8edf7,1 +np.float64,0x3fbe26ccda3c4da0,0x3fbe38a892279975,1 +np.float64,0x3fcc1873103830e6,0x3fcc5257a6ae168d,1 +np.float64,0xe7e000e9cfc00,0xe7e000e9cfc00,1 +np.float64,0xffda73852bb4e70a,0xfff0000000000000,1 +np.float64,0xbfe831be19f0637c,0xbfea90f1b34da3e5,1 +np.float64,0xbfeb568f3076ad1e,0xbfeec97eebfde862,1 +np.float64,0x510a6ad0a214e,0x510a6ad0a214e,1 +np.float64,0x3fe6ba7e35ed74fc,0x3fe8b032a9a28c6a,1 +np.float64,0xffeb5cdcff76b9b9,0xfff0000000000000,1 +np.float64,0x4f0a23e89e145,0x4f0a23e89e145,1 +np.float64,0x446ec20288dd9,0x446ec20288dd9,1 +np.float64,0x7fe2521b02e4a435,0x7ff0000000000000,1 +np.float64,0x8001cd2969e39a54,0x8001cd2969e39a54,1 +np.float64,0x3fdfe90600bfd20c,0x3fe09fdcca10001c,1 +np.float64,0x7fd660c5762cc18a,0x7ff0000000000000,1 +np.float64,0xbfb11b23aa223648,0xbfb11e661949b377,1 +np.float64,0x800e025285fc04a5,0x800e025285fc04a5,1 +np.float64,0xffb180bb18230178,0xfff0000000000000,1 +np.float64,0xaaf590df55eb2,0xaaf590df55eb2,1 +np.float64,0xbfe8637d9df0c6fb,0xbfead1ba429462ec,1 +np.float64,0x7fd2577866a4aef0,0x7ff0000000000000,1 +np.float64,0xbfcfb2ab5a3f6558,0xbfd002ee87f272b9,1 +np.float64,0x7fdd64ae2f3ac95b,0x7ff0000000000000,1 +np.float64,0xffd1a502c9234a06,0xfff0000000000000,1 +np.float64,0x7fc4be4b60297c96,0x7ff0000000000000,1 +np.float64,0xbfb46b712a28d6e0,0xbfb470fca9919172,1 +np.float64,0xffdef913033df226,0xfff0000000000000,1 +np.float64,0x3fd94a3545b2946b,0x3fd9f40431ce9f9c,1 +np.float64,0x7fef88a0b6ff1140,0x7ff0000000000000,1 +np.float64,0xbfbcc81876399030,0xbfbcd7a0ab6cb388,1 +np.float64,0x800a4acfdd9495a0,0x800a4acfdd9495a0,1 +np.float64,0xffe270b3d5e4e167,0xfff0000000000000,1 +np.float64,0xbfd23f601e247ec0,0xbfd27eeca50a49eb,1 +np.float64,0x7fec6e796a78dcf2,0x7ff0000000000000,1 +np.float64,0x3fb85e0c9630bc19,0x3fb867791ccd6c72,1 +np.float64,0x7fe49fc424a93f87,0x7ff0000000000000,1 +np.float64,0xbfe75a99fbaeb534,0xbfe97ba37663de4c,1 +np.float64,0xffe85011b630a023,0xfff0000000000000,1 +np.float64,0xffe5962e492b2c5c,0xfff0000000000000,1 +np.float64,0x6f36ed4cde6de,0x6f36ed4cde6de,1 +np.float64,0x3feb72170af6e42e,0x3feeefbe6f1a2084,1 +np.float64,0x80014d8d60629b1c,0x80014d8d60629b1c,1 +np.float64,0xbfe0eb40d321d682,0xbfe1b7e31f252bf1,1 +np.float64,0x31fe305663fc7,0x31fe305663fc7,1 +np.float64,0x3fd2cd6381a59ac7,0x3fd312edc9868a4d,1 +np.float64,0xffcf0720793e0e40,0xfff0000000000000,1 +np.float64,0xbfeef1ef133de3de,0xbff1ffd5e1a3b648,1 +np.float64,0xbfd01c787aa038f0,0xbfd0482be3158a01,1 +np.float64,0x3fda3607c5b46c10,0x3fdaf3301e217301,1 +np.float64,0xffda9a9911b53532,0xfff0000000000000,1 +np.float64,0x3fc0b37c392166f8,0x3fc0bfa076f3c43e,1 +np.float64,0xbfe06591c760cb24,0xbfe11fad179ea12c,1 +np.float64,0x8006e369c20dc6d4,0x8006e369c20dc6d4,1 +np.float64,0x3fdf2912a8be5224,0x3fe033ff74b92f4d,1 +np.float64,0xffc0feb07821fd60,0xfff0000000000000,1 +np.float64,0xa4b938c949727,0xa4b938c949727,1 +np.float64,0x8008fe676571fccf,0x8008fe676571fccf,1 +np.float64,0xbfdda68459bb4d08,0xbfdeb8faab34fcbc,1 +np.float64,0xbfda18b419343168,0xbfdad360ca52ec7c,1 +np.float64,0x3febcbae35b7975c,0x3fef6cd51c9ebc15,1 +np.float64,0x3fbec615f63d8c30,0x3fbed912ba729926,1 +np.float64,0x7f99a831c8335063,0x7ff0000000000000,1 +np.float64,0x3fe663e8826cc7d1,0x3fe84330bd9aada8,1 +np.float64,0x70a9f9e6e1540,0x70a9f9e6e1540,1 +np.float64,0x8a13a5db14275,0x8a13a5db14275,1 +np.float64,0x7fc4330a3b286613,0x7ff0000000000000,1 +np.float64,0xbfe580c6136b018c,0xbfe728806cc7a99a,1 +np.float64,0x8000000000000001,0x8000000000000001,1 +np.float64,0xffec079d5df80f3a,0xfff0000000000000,1 +np.float64,0x8e1173c31c22f,0x8e1173c31c22f,1 +np.float64,0x3fe088456d21108b,0x3fe14712ca414103,1 +np.float64,0x3fe1b76f73636edf,0x3fe2a2b658557112,1 +np.float64,0xbfd4a1dd162943ba,0xbfd4fdd45cae8fb8,1 +np.float64,0x7fd60b46c8ac168d,0x7ff0000000000000,1 +np.float64,0xffe36cc3b166d987,0xfff0000000000000,1 +np.float64,0x3fdc2ae0cfb855c0,0x3fdd15f026773151,1 +np.float64,0xbfc41aa203283544,0xbfc42fd1b145fdd5,1 +np.float64,0xffed90c55fbb218a,0xfff0000000000000,1 +np.float64,0x3fe67e3a9aecfc75,0x3fe86440db65b4f6,1 +np.float64,0x7fd12dbeaba25b7c,0x7ff0000000000000,1 +np.float64,0xbfe1267c0de24cf8,0xbfe1fbb611bdf1e9,1 +np.float64,0x22e5619645cad,0x22e5619645cad,1 +np.float64,0x7fe327c72ea64f8d,0x7ff0000000000000,1 +np.float64,0x7fd2c3f545a587ea,0x7ff0000000000000,1 +np.float64,0x7fc7b689372f6d11,0x7ff0000000000000,1 +np.float64,0xc5e140bd8bc28,0xc5e140bd8bc28,1 +np.float64,0x3fccb3627a3966c5,0x3fccf11b44fa4102,1 +np.float64,0xbfd2cf725c259ee4,0xbfd315138d0e5dca,1 +np.float64,0x10000000000000,0x10000000000000,1 +np.float64,0xbfd3dfa8b627bf52,0xbfd431d17b235477,1 +np.float64,0xbfb82124e6304248,0xbfb82a4b6d9c2663,1 +np.float64,0x3fdcd590d9b9ab22,0x3fddd1d548806347,1 +np.float64,0x7fdee0cd1b3dc199,0x7ff0000000000000,1 +np.float64,0x8004ebfc60a9d7fa,0x8004ebfc60a9d7fa,1 +np.float64,0x3fe8eb818b71d704,0x3feb842679806108,1 +np.float64,0xffdd5e8fe63abd20,0xfff0000000000000,1 +np.float64,0xbfe3efcbd9e7df98,0xbfe54071436645ee,1 +np.float64,0x3fd5102557aa204b,0x3fd57203d31a05b8,1 +np.float64,0x3fe6318af7ec6316,0x3fe8041a177cbf96,1 +np.float64,0x3fdf3cecdabe79da,0x3fe03f2084ffbc78,1 +np.float64,0x7fe0ab6673a156cc,0x7ff0000000000000,1 +np.float64,0x800037d5c6c06fac,0x800037d5c6c06fac,1 +np.float64,0xffce58b86a3cb170,0xfff0000000000000,1 +np.float64,0xbfe3455d6ce68abb,0xbfe475034cecb2b8,1 +np.float64,0x991b663d3236d,0x991b663d3236d,1 +np.float64,0x3fda82d37c3505a7,0x3fdb46973da05c12,1 +np.float64,0x3f9b736fa036e6df,0x3f9b74471c234411,1 +np.float64,0x8001c96525e392cb,0x8001c96525e392cb,1 +np.float64,0x7ff0000000000000,0x7ff0000000000000,1 +np.float64,0xbfaf59122c3eb220,0xbfaf5e15f8b272b0,1 +np.float64,0xbf9aa7d288354fa0,0xbf9aa897d2a40cb5,1 +np.float64,0x8004a43428694869,0x8004a43428694869,1 +np.float64,0x7feead476dbd5a8e,0x7ff0000000000000,1 +np.float64,0xffca150f81342a20,0xfff0000000000000,1 +np.float64,0x80047ec3bc88fd88,0x80047ec3bc88fd88,1 +np.float64,0xbfee3e5b123c7cb6,0xbff179c8b8334278,1 +np.float64,0x3fd172359f22e46b,0x3fd1a9ba6b1420a1,1 +np.float64,0x3fe8e5e242f1cbc5,0x3feb7cbcaefc4d5c,1 +np.float64,0x8007fb059a6ff60c,0x8007fb059a6ff60c,1 +np.float64,0xe3899e71c7134,0xe3899e71c7134,1 +np.float64,0x7fe3b98326a77305,0x7ff0000000000000,1 +np.float64,0x7fec4e206cb89c40,0x7ff0000000000000,1 +np.float64,0xbfa3b012c4276020,0xbfa3b150c13b3cc5,1 +np.float64,0xffefffffffffffff,0xfff0000000000000,1 +np.float64,0xffe28a5b9aa514b6,0xfff0000000000000,1 +np.float64,0xbfd76a6cc2aed4da,0xbfd7f10f4d04e7f6,1 +np.float64,0xbc2b1c0178564,0xbc2b1c0178564,1 +np.float64,0x6d9d444adb3a9,0x6d9d444adb3a9,1 +np.float64,0xbfdcadd368395ba6,0xbfdda6037b5c429c,1 +np.float64,0x3fe11891fde23124,0x3fe1ebc1c204b14b,1 +np.float64,0x3fdd66c3eebacd88,0x3fde72526b5304c4,1 +np.float64,0xbfe79d85612f3b0b,0xbfe9d1673bd1f6d6,1 +np.float64,0x3fed60abdabac158,0x3ff0d7426b3800a2,1 +np.float64,0xbfb0ffa54021ff48,0xbfb102d81073a9f0,1 +np.float64,0xd2452af5a48a6,0xd2452af5a48a6,1 +np.float64,0xf4b835c1e971,0xf4b835c1e971,1 +np.float64,0x7e269cdafc4d4,0x7e269cdafc4d4,1 +np.float64,0x800097a21d812f45,0x800097a21d812f45,1 +np.float64,0x3fdfcc85e8bf990c,0x3fe08fcf770fd456,1 +np.float64,0xd8d53155b1aa6,0xd8d53155b1aa6,1 +np.float64,0x7fb8ed658831daca,0x7ff0000000000000,1 +np.float64,0xbfec865415b90ca8,0xbff03a4584d719f9,1 +np.float64,0xffd8cda62a319b4c,0xfff0000000000000,1 +np.float64,0x273598d84e6b4,0x273598d84e6b4,1 +np.float64,0x7fd566b5c32acd6b,0x7ff0000000000000,1 +np.float64,0xff61d9d48023b400,0xfff0000000000000,1 +np.float64,0xbfec5c3bf4f8b878,0xbff01c594243337c,1 +np.float64,0x7fd1be0561a37c0a,0x7ff0000000000000,1 +np.float64,0xffeaee3271b5dc64,0xfff0000000000000,1 +np.float64,0x800c0e1931b81c33,0x800c0e1931b81c33,1 +np.float64,0xbfad1171583a22e0,0xbfad1570e5c466d2,1 +np.float64,0x7fd783b0fe2f0761,0x7ff0000000000000,1 +np.float64,0x7fc39903e6273207,0x7ff0000000000000,1 +np.float64,0xffe00003c5600007,0xfff0000000000000,1 +np.float64,0x35a7b9c06b50,0x35a7b9c06b50,1 +np.float64,0x7fee441a22bc8833,0x7ff0000000000000,1 +np.float64,0xff6e47fbc03c9000,0xfff0000000000000,1 +np.float64,0xbfd3c3c9c8a78794,0xbfd41499b1912534,1 +np.float64,0x82c9c87f05939,0x82c9c87f05939,1 +np.float64,0xbfedeb0fe4fbd620,0xbff13c573ce9d3d0,1 +np.float64,0x2b79298656f26,0x2b79298656f26,1 +np.float64,0xbf5ee44f003dc800,0xbf5ee4503353c0ba,1 +np.float64,0xbfe1dd264e63ba4c,0xbfe2ce68116c7bf6,1 +np.float64,0x3fece10b7579c217,0x3ff07b21b11799c6,1 +np.float64,0x3fba47143a348e28,0x3fba52e601adf24c,1 +np.float64,0xffe9816e7a7302dc,0xfff0000000000000,1 +np.float64,0x8009a8047fd35009,0x8009a8047fd35009,1 +np.float64,0x800ac28e4e95851d,0x800ac28e4e95851d,1 +np.float64,0x80093facf4f27f5a,0x80093facf4f27f5a,1 +np.float64,0x3ff0000000000000,0x3ff2cd9fc44eb982,1 +np.float64,0x3fe76a9857eed530,0x3fe99018a5895a4f,1 +np.float64,0xbfd13c59a3a278b4,0xbfd171e133df0b16,1 +np.float64,0x7feb43bc83368778,0x7ff0000000000000,1 +np.float64,0xbfe2970c5fa52e18,0xbfe3a74a434c6efe,1 +np.float64,0xffd091c380212388,0xfff0000000000000,1 +np.float64,0x3febb3b9d2f76774,0x3fef4b4af2bd8580,1 +np.float64,0x7fec66787ef8ccf0,0x7ff0000000000000,1 +np.float64,0xbf935e185826bc40,0xbf935e640557a354,1 +np.float64,0x979df1552f3be,0x979df1552f3be,1 +np.float64,0x7fc096ee73212ddc,0x7ff0000000000000,1 +np.float64,0xbfe9de88faf3bd12,0xbfecc7d1ae691d1b,1 +np.float64,0x7fdc733f06b8e67d,0x7ff0000000000000,1 +np.float64,0xffd71be1a0ae37c4,0xfff0000000000000,1 +np.float64,0xb50dabd36a1b6,0xb50dabd36a1b6,1 +np.float64,0x7fce3d94d63c7b29,0x7ff0000000000000,1 +np.float64,0x7fbaf95e4435f2bc,0x7ff0000000000000,1 +np.float64,0x81a32a6f03466,0x81a32a6f03466,1 +np.float64,0xa99b5b4d5336c,0xa99b5b4d5336c,1 +np.float64,0x7f97c1eeb82f83dc,0x7ff0000000000000,1 +np.float64,0x3fe761636d6ec2c6,0x3fe98451160d2ffb,1 +np.float64,0xbfe3224ef5e6449e,0xbfe44b73eeadac52,1 +np.float64,0x7fde6feb0dbcdfd5,0x7ff0000000000000,1 +np.float64,0xbfee87f9ca7d0ff4,0xbff1b079e9d7f706,1 +np.float64,0x3fe46f4c9828de99,0x3fe5da2ab9609ea5,1 +np.float64,0xffb92fe882325fd0,0xfff0000000000000,1 +np.float64,0x80054bc63cea978d,0x80054bc63cea978d,1 +np.float64,0x3d988bea7b312,0x3d988bea7b312,1 +np.float64,0x3fe6468e1d6c8d1c,0x3fe81e64d37d39a8,1 +np.float64,0x3fd68eefc22d1de0,0x3fd7074264faeead,1 +np.float64,0xffb218a074243140,0xfff0000000000000,1 +np.float64,0x3fdbcb3b6cb79678,0x3fdcad011de40b7d,1 +np.float64,0x7fe3c161772782c2,0x7ff0000000000000,1 +np.float64,0x25575c904aaec,0x25575c904aaec,1 +np.float64,0x800fa43a8f5f4875,0x800fa43a8f5f4875,1 +np.float64,0x3fe41fc9e1e83f94,0x3fe57a25dd1a37f1,1 +np.float64,0x3fd895f4a7b12be9,0x3fd931e7b721a08a,1 +np.float64,0xce31469f9c629,0xce31469f9c629,1 +np.float64,0xffea0f55ca341eab,0xfff0000000000000,1 +np.float64,0xffe831c9ba306393,0xfff0000000000000,1 +np.float64,0x7fe2056f03a40add,0x7ff0000000000000,1 +np.float64,0x7fd6b075e02d60eb,0x7ff0000000000000,1 +np.float64,0x3fdfbef4273f7de8,0x3fe0882c1f59efc0,1 +np.float64,0x8005b9e094ab73c2,0x8005b9e094ab73c2,1 +np.float64,0x3fea881ac6351036,0x3fedad7a319b887c,1 +np.float64,0xbfe2c61c7ee58c39,0xbfe3de9a99d8a9c6,1 +np.float64,0x30b0d3786161b,0x30b0d3786161b,1 +np.float64,0x3fa51d56a02a3aad,0x3fa51edee2d2ecef,1 +np.float64,0x79745732f2e8c,0x79745732f2e8c,1 +np.float64,0x800d55b4907aab69,0x800d55b4907aab69,1 +np.float64,0xbfbe8fcf0a3d1fa0,0xbfbea267fbb5bfdf,1 +np.float64,0xbfd04e2756a09c4e,0xbfd07b74d079f9a2,1 +np.float64,0x3fc65170552ca2e1,0x3fc66e6eb00c82ed,1 +np.float64,0xbfb0674b8020ce98,0xbfb06a2b4771b64c,1 +np.float64,0x2059975840b34,0x2059975840b34,1 +np.float64,0x33d1385467a28,0x33d1385467a28,1 +np.float64,0x3fea41b74ff4836f,0x3fed4dc1a09e53cc,1 +np.float64,0xbfe8e08c9d71c119,0xbfeb75b4c59a6bec,1 +np.float64,0x7fdbbf14d6377e29,0x7ff0000000000000,1 +np.float64,0x3fcd8b71513b16e0,0x3fcdcec80174f9ad,1 +np.float64,0x5c50bc94b8a18,0x5c50bc94b8a18,1 +np.float64,0x969a18f52d343,0x969a18f52d343,1 +np.float64,0x3fd7ae44462f5c89,0x3fd8398bc34e395c,1 +np.float64,0xffdd0f8617ba1f0c,0xfff0000000000000,1 +np.float64,0xfff0000000000000,0xfff0000000000000,1 +np.float64,0xbfe2f9badb65f376,0xbfe41b771320ece8,1 +np.float64,0x3fd140bc7fa29,0x3fd140bc7fa29,1 +np.float64,0xbfe14523b5628a48,0xbfe21ee850972043,1 +np.float64,0x3feedd0336bdba06,0x3ff1f01afc1f3a06,1 +np.float64,0x800de423ad7bc848,0x800de423ad7bc848,1 +np.float64,0x4cef857c99df1,0x4cef857c99df1,1 +np.float64,0xbfea55e0e374abc2,0xbfed691e41d648dd,1 +np.float64,0x3fe70d7a18ae1af4,0x3fe91955a34d8094,1 +np.float64,0xbfc62fc3032c5f88,0xbfc64c3ec25decb8,1 +np.float64,0x3fc915abb5322b58,0x3fc93edac5cc73fe,1 +np.float64,0x69aaff66d3561,0x69aaff66d3561,1 +np.float64,0x5c6a90f2b8d53,0x5c6a90f2b8d53,1 +np.float64,0x3fefe30dc1bfc61c,0x3ff2b752257bdacd,1 +np.float64,0x3fef15db15fe2bb6,0x3ff21aea05601396,1 +np.float64,0xbfe353e5ac66a7cc,0xbfe48644e6553d1a,1 +np.float64,0x3fe6d30cffada61a,0x3fe8cf3e4c61ddac,1 +np.float64,0x7fb7857eb62f0afc,0x7ff0000000000000,1 +np.float64,0xbfdd9b53d23b36a8,0xbfdeac91a7af1340,1 +np.float64,0x3fd1456357228ac7,0x3fd17b3f7d39b27a,1 +np.float64,0x3fb57d10ae2afa21,0x3fb5838702b806f4,1 +np.float64,0x800c59c96c98b393,0x800c59c96c98b393,1 +np.float64,0x7fc1f2413823e481,0x7ff0000000000000,1 +np.float64,0xbfa3983624273070,0xbfa3996fa26c419a,1 +np.float64,0x7fb28874ae2510e8,0x7ff0000000000000,1 +np.float64,0x3fe826d02a304da0,0x3fea82bec50bc0b6,1 +np.float64,0x8008d6f0d3d1ade2,0x8008d6f0d3d1ade2,1 +np.float64,0xffe7c970ca2f92e1,0xfff0000000000000,1 +np.float64,0x7fcf42bcaa3e8578,0x7ff0000000000000,1 +np.float64,0x7fda1ab517343569,0x7ff0000000000000,1 +np.float64,0xbfe7926a65ef24d5,0xbfe9c323dd890d5b,1 +np.float64,0xbfcaf6282d35ec50,0xbfcb294f36a0a33d,1 +np.float64,0x800ca49df8d9493c,0x800ca49df8d9493c,1 +np.float64,0xffea18d26af431a4,0xfff0000000000000,1 +np.float64,0x3fb72f276e2e5e50,0x3fb7374539fd1221,1 +np.float64,0xffa6b613842d6c20,0xfff0000000000000,1 +np.float64,0xbfeb3c7263f678e5,0xbfeea54cdb60b54c,1 +np.float64,0x3fc976d2ba32eda5,0x3fc9a1e83a058de4,1 +np.float64,0xbfe4acd4b0e959aa,0xbfe624d5d4f9b9a6,1 +np.float64,0x7fca410a0f348213,0x7ff0000000000000,1 +np.float64,0xbfde368f77bc6d1e,0xbfdf5910c8c8bcb0,1 +np.float64,0xbfed7412937ae825,0xbff0e55afc428453,1 +np.float64,0xffef6b7b607ed6f6,0xfff0000000000000,1 +np.float64,0xbfb936f17e326de0,0xbfb941629a53c694,1 +np.float64,0x800dbb0c469b7619,0x800dbb0c469b7619,1 +np.float64,0x800f68b0581ed161,0x800f68b0581ed161,1 +np.float64,0x3fe25b2aad64b656,0x3fe361266fa9c5eb,1 +np.float64,0xbfb87e445a30fc88,0xbfb887d676910c3f,1 +np.float64,0x6e6ba9b6dcd76,0x6e6ba9b6dcd76,1 +np.float64,0x3fad27ce583a4f9d,0x3fad2bd72782ffdb,1 +np.float64,0xbfec0bc5d638178c,0xbfefc6e8c8f9095f,1 +np.float64,0x7fcba4a296374944,0x7ff0000000000000,1 +np.float64,0x8004ca237cc99448,0x8004ca237cc99448,1 +np.float64,0xffe85b8c3270b718,0xfff0000000000000,1 +np.float64,0x7fe7ee3eddafdc7d,0x7ff0000000000000,1 +np.float64,0xffd275967ca4eb2c,0xfff0000000000000,1 +np.float64,0xbfa95bc3a032b780,0xbfa95e6b288ecf43,1 +np.float64,0x3fc9e3214b33c643,0x3fca10667e7e7ff4,1 +np.float64,0x8001b89c5d837139,0x8001b89c5d837139,1 +np.float64,0xbf8807dfc0300fc0,0xbf880803e3badfbd,1 +np.float64,0x800aca94b895952a,0x800aca94b895952a,1 +np.float64,0x7fd79534a02f2a68,0x7ff0000000000000,1 +np.float64,0x3fe1b81179e37023,0x3fe2a371d8cc26f0,1 +np.float64,0x800699539d6d32a8,0x800699539d6d32a8,1 +np.float64,0xffe51dfbb3aa3bf7,0xfff0000000000000,1 +np.float64,0xbfdfb775abbf6eec,0xbfe083f48be2f98f,1 +np.float64,0x3fe87979d7b0f2f4,0x3feaee701d959079,1 +np.float64,0x3fd8e4e6a731c9cd,0x3fd986d29f25f982,1 +np.float64,0x3fe3dadaaf67b5b6,0x3fe527520fb02920,1 +np.float64,0x8003c2262bc7844d,0x8003c2262bc7844d,1 +np.float64,0x800c930add392616,0x800c930add392616,1 +np.float64,0xffb7a152a22f42a8,0xfff0000000000000,1 +np.float64,0x80028fe03dc51fc1,0x80028fe03dc51fc1,1 +np.float64,0xffe32ae60c6655cc,0xfff0000000000000,1 +np.float64,0x3fea3527e4746a50,0x3fed3cbbf47f18eb,1 +np.float64,0x800a53059e14a60c,0x800a53059e14a60c,1 +np.float64,0xbfd79e3b202f3c76,0xbfd828672381207b,1 +np.float64,0xffeed7e2eb7dafc5,0xfff0000000000000,1 +np.float64,0x3fec51ed6778a3db,0x3ff01509e34df61d,1 +np.float64,0xbfd84bc577b0978a,0xbfd8e23ec55e42e8,1 +np.float64,0x2483aff849077,0x2483aff849077,1 +np.float64,0x6f57883adeaf2,0x6f57883adeaf2,1 +np.float64,0xffd3fd74d927faea,0xfff0000000000000,1 +np.float64,0x7fca49ec773493d8,0x7ff0000000000000,1 +np.float64,0x7fd08fe2e8211fc5,0x7ff0000000000000,1 +np.float64,0x800852086db0a411,0x800852086db0a411,1 +np.float64,0x3fe5b1f2c9eb63e6,0x3fe7654f511bafc6,1 +np.float64,0xbfe01e2a58e03c54,0xbfe0cedb68f021e6,1 +np.float64,0x800988421d331085,0x800988421d331085,1 +np.float64,0xffd5038b18aa0716,0xfff0000000000000,1 +np.float64,0x8002c9264c85924d,0x8002c9264c85924d,1 +np.float64,0x3fd21ca302243946,0x3fd25ac653a71aab,1 +np.float64,0xbfea60d6e6f4c1ae,0xbfed78031d9dfa2b,1 +np.float64,0xffef97b6263f2f6b,0xfff0000000000000,1 +np.float64,0xbfd524732faa48e6,0xbfd5876ecc415dcc,1 +np.float64,0x660387e8cc072,0x660387e8cc072,1 +np.float64,0x7fcfc108a33f8210,0x7ff0000000000000,1 +np.float64,0x7febe5b0f877cb61,0x7ff0000000000000,1 +np.float64,0xbfa55fdfac2abfc0,0xbfa56176991851a8,1 +np.float64,0x25250f4c4a4a3,0x25250f4c4a4a3,1 +np.float64,0xffe2f6a2f2a5ed46,0xfff0000000000000,1 +np.float64,0x7fa754fcc02ea9f9,0x7ff0000000000000,1 +np.float64,0x3febd19dea37a33c,0x3fef75279f75d3b8,1 +np.float64,0xc5ed55218bdab,0xc5ed55218bdab,1 +np.float64,0x3fe72ff6b3ee5fed,0x3fe945388b979882,1 +np.float64,0xbfe16b854e22d70a,0xbfe24b10fc0dff14,1 +np.float64,0xffb22cbe10245980,0xfff0000000000000,1 +np.float64,0xa54246b54a849,0xa54246b54a849,1 +np.float64,0x3fe7f4cda76fe99c,0x3fea41edc74888b6,1 +np.float64,0x1,0x1,1 +np.float64,0x800d84acce9b095a,0x800d84acce9b095a,1 +np.float64,0xb0eef04761dde,0xb0eef04761dde,1 +np.float64,0x7ff4000000000000,0x7ffc000000000000,1 +np.float64,0xffecaf1dbb795e3b,0xfff0000000000000,1 +np.float64,0x90dbab8d21b76,0x90dbab8d21b76,1 +np.float64,0x3fe79584a9ef2b09,0x3fe9c71fa9e40eb5,1 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-tan.csv b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-tan.csv new file mode 100644 index 0000000000000000000000000000000000000000..ac97624ec5652bca842e28c3f7d726b073315f0f --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/data/umath-validation-set-tan.csv @@ -0,0 +1,1429 @@ +dtype,input,output,ulperrortol +np.float32,0xfd97ece0,0xc11186e9,4 +np.float32,0x8013bb34,0x8013bb34,4 +np.float32,0x316389,0x316389,4 +np.float32,0x7f7fffff,0xbf1c9eca,4 +np.float32,0x3f7674bb,0x3fb7e450,4 +np.float32,0x80800000,0x80800000,4 +np.float32,0x7f5995e8,0xbf94106c,4 +np.float32,0x74527,0x74527,4 +np.float32,0x7f08caea,0xbeceddb6,4 +np.float32,0x2d49b2,0x2d49b2,4 +np.float32,0x3f74e5e4,0x3fb58695,4 +np.float32,0x3f3fcd51,0x3f6e1e81,4 +np.float32,0xbf4f3608,0xbf864d3d,4 +np.float32,0xbed974a0,0xbee78c70,4 +np.float32,0xff5f483c,0x3ecf3cb2,4 +np.float32,0x7f4532f4,0xc0b96f7b,4 +np.float32,0x3f0a4f7c,0x3f198cc0,4 +np.float32,0x210193,0x210193,4 +np.float32,0xfeebad7a,0xbf92eba8,4 +np.float32,0xfed29f74,0xc134cab6,4 +np.float32,0x803433a0,0x803433a0,4 +np.float32,0x64eb46,0x64eb46,4 +np.float32,0xbf54ef22,0xbf8c757b,4 +np.float32,0x3f3d5fdd,0x3f69a17b,4 +np.float32,0x80000001,0x80000001,4 +np.float32,0x800a837a,0x800a837a,4 +np.float32,0x6ff0be,0x6ff0be,4 +np.float32,0xfe8f1186,0x3f518820,4 +np.float32,0x804963e5,0x804963e5,4 +np.float32,0xfebaa59a,0x3fa1dbb0,4 +np.float32,0x637970,0x637970,4 +np.float32,0x3e722a6b,0x3e76c89a,4 +np.float32,0xff2b0478,0xbddccb5f,4 +np.float32,0xbf7bd85b,0xbfc06821,4 +np.float32,0x3ec33600,0x3ecd4126,4 +np.float32,0x3e0a43b9,0x3e0b1c69,4 +np.float32,0x7f7511b6,0xbe427083,4 +np.float32,0x3f28c114,0x3f465a73,4 +np.float32,0x3f179e1c,0x3f2c3e7c,4 +np.float32,0x7b2963,0x7b2963,4 +np.float32,0x3f423d06,0x3f72b442,4 +np.float32,0x3f5a24c6,0x3f925508,4 +np.float32,0xff18c834,0xbf79b5c8,4 +np.float32,0x3f401ece,0x3f6eb6ac,4 +np.float32,0x7b8a3013,0xbffab968,4 +np.float32,0x80091ff0,0x80091ff0,4 +np.float32,0x3f389c51,0x3f610b47,4 +np.float32,0x5ea174,0x5ea174,4 +np.float32,0x807a9eb2,0x807a9eb2,4 +np.float32,0x806ce61e,0x806ce61e,4 +np.float32,0xbe956acc,0xbe99cefc,4 +np.float32,0x7e60e247,0xbf5e64a5,4 +np.float32,0x7f398e24,0x404d12ed,4 +np.float32,0x3d9049f8,0x3d908735,4 +np.float32,0x7db17ffc,0xbf5b3d87,4 +np.float32,0xff453f78,0xc0239c9f,4 +np.float32,0x3f024aac,0x3f0ed802,4 +np.float32,0xbe781c30,0xbe7d1508,4 +np.float32,0x3f77962a,0x3fb9a28e,4 +np.float32,0xff7fffff,0x3f1c9eca,4 +np.float32,0x3f7152e3,0x3fb03f9d,4 +np.float32,0xff7cb167,0x3f9ce831,4 +np.float32,0x3e763e30,0x3e7b1a10,4 +np.float32,0xbf126527,0xbf24c253,4 +np.float32,0x803f6660,0x803f6660,4 +np.float32,0xbf79de38,0xbfbd38b1,4 +np.float32,0x8046c2f0,0x8046c2f0,4 +np.float32,0x6dc74e,0x6dc74e,4 +np.float32,0xbec9c45e,0xbed4e768,4 +np.float32,0x3f0eedb6,0x3f1fe610,4 +np.float32,0x7e031999,0xbcc13026,4 +np.float32,0x7efc2fd7,0x41e4b284,4 +np.float32,0xbeab7454,0xbeb22a1b,4 +np.float32,0x805ee67b,0x805ee67b,4 +np.float32,0x7f76e58e,0xc2436659,4 +np.float32,0xbe62b024,0xbe667718,4 +np.float32,0x3eea0808,0x3efbd182,4 +np.float32,0xbf7fd00c,0xbfc70719,4 +np.float32,0x7f27b640,0xbf0d97e0,4 +np.float32,0x3f1b58a4,0x3f31b6f4,4 +np.float32,0x252a9f,0x252a9f,4 +np.float32,0x7f65f95a,0xbead5de3,4 +np.float32,0xfc6ea780,0x42d15801,4 +np.float32,0x7eac4c52,0xc0682424,4 +np.float32,0xbe8a3f5a,0xbe8db54d,4 +np.float32,0xbf1644e2,0xbf2a4abd,4 +np.float32,0x3fc96a,0x3fc96a,4 +np.float32,0x7f38c0e4,0x3cc04af8,4 +np.float32,0x3f623d75,0x3f9c065d,4 +np.float32,0x3ee6a51a,0x3ef7a058,4 +np.float32,0x3dd11020,0x3dd1cacf,4 +np.float32,0xb6918,0xb6918,4 +np.float32,0xfdd7a540,0x3f22f081,4 +np.float32,0x80798563,0x80798563,4 +np.float32,0x3e9a8b7a,0x3e9f6a7e,4 +np.float32,0xbea515d4,0xbeab0df5,4 +np.float32,0xbea9b9f4,0xbeb03abe,4 +np.float32,0xbf11a5fa,0xbf23b478,4 +np.float32,0xfd6cadf0,0xbfa2a878,4 +np.float32,0xbf6edd07,0xbfacbb78,4 +np.float32,0xff5c5328,0x3e2d1552,4 +np.float32,0xbea2f788,0xbea8b3f5,4 +np.float32,0x802efaeb,0x802efaeb,4 +np.float32,0xff1c85e5,0x41f8560e,4 +np.float32,0x3f53b123,0x3f8b18e1,4 +np.float32,0xff798c4a,0x4092e66f,4 +np.float32,0x7f2e6fe7,0xbdcbd58f,4 +np.float32,0xfe8a8196,0x3fd7fc56,4 +np.float32,0x5e7ad4,0x5e7ad4,4 +np.float32,0xbf23a02d,0xbf3e4533,4 +np.float32,0x3f31c55c,0x3f5531bf,4 +np.float32,0x80331be3,0x80331be3,4 +np.float32,0x8056960a,0x8056960a,4 +np.float32,0xff1c06ae,0xbfd26992,4 +np.float32,0xbe0cc4b0,0xbe0da96c,4 +np.float32,0x7e925ad5,0xbf8dba54,4 +np.float32,0x2c8cec,0x2c8cec,4 +np.float32,0x8011951e,0x8011951e,4 +np.float32,0x3f2caf84,0x3f4cb89f,4 +np.float32,0xbd32c220,0xbd32df33,4 +np.float32,0xbec358d6,0xbecd6996,4 +np.float32,0x3f6e4930,0x3fabeb92,4 +np.float32,0xbf6a3afd,0xbfa65a3a,4 +np.float32,0x80067764,0x80067764,4 +np.float32,0x3d8df1,0x3d8df1,4 +np.float32,0x7ee51cf2,0x409e4061,4 +np.float32,0x435f5d,0x435f5d,4 +np.float32,0xbf5b17f7,0xbf936ebe,4 +np.float32,0x3ecaacb5,0x3ed5f81f,4 +np.float32,0x807b0aa5,0x807b0aa5,4 +np.float32,0x52b40b,0x52b40b,4 +np.float32,0x146a97,0x146a97,4 +np.float32,0x7f42b952,0xbfdcb413,4 +np.float32,0xbf1a1af2,0xbf2fe1bb,4 +np.float32,0x3f312034,0x3f541aa2,4 +np.float32,0x3f281d60,0x3f4554f9,4 +np.float32,0x50e451,0x50e451,4 +np.float32,0xbe45838c,0xbe480016,4 +np.float32,0xff7d0aeb,0x3eb0746e,4 +np.float32,0x7f32a489,0xbf96af6d,4 +np.float32,0xbf1b4e27,0xbf31a769,4 +np.float32,0x3f242936,0x3f3f1a44,4 +np.float32,0xbf7482ff,0xbfb4f201,4 +np.float32,0x4bda38,0x4bda38,4 +np.float32,0xbf022208,0xbf0ea2bb,4 +np.float32,0x7d08ca95,0xbe904602,4 +np.float32,0x7ed2f356,0xc02b55ad,4 +np.float32,0xbf131204,0xbf25b734,4 +np.float32,0xff3464b4,0x3fb23706,4 +np.float32,0x5a97cf,0x5a97cf,4 +np.float32,0xbe52db70,0xbe55e388,4 +np.float32,0x3f52934f,0x3f89e2aa,4 +np.float32,0xfeea866a,0x40a2b33f,4 +np.float32,0x80333925,0x80333925,4 +np.float32,0xfef5d13e,0xc00139ec,4 +np.float32,0x3f4750ab,0x3f7c87ad,4 +np.float32,0x3e41bfdd,0x3e44185a,4 +np.float32,0xbf5b0572,0xbf935935,4 +np.float32,0xbe93c9da,0xbe9808d8,4 +np.float32,0x7f501f33,0xc0f9973c,4 +np.float32,0x800af035,0x800af035,4 +np.float32,0x3f29faf8,0x3f4852a8,4 +np.float32,0xbe1e4c20,0xbe1f920c,4 +np.float32,0xbf7e8616,0xbfc4d79d,4 +np.float32,0x43ffbf,0x43ffbf,4 +np.float32,0x7f28e8a9,0xbfa1ac24,4 +np.float32,0xbf1f9f92,0xbf3820bc,4 +np.float32,0x3f07e004,0x3f1641c4,4 +np.float32,0x3ef7ea7f,0x3f06a64a,4 +np.float32,0x7e013101,0x3f6080e6,4 +np.float32,0x7f122a4f,0xbf0a796f,4 +np.float32,0xfe096960,0x3ed7273a,4 +np.float32,0x3f06abf1,0x3f14a4b2,4 +np.float32,0x3e50ded3,0x3e53d0f1,4 +np.float32,0x7f50b346,0x3eabb536,4 +np.float32,0xff5adb0f,0xbd441972,4 +np.float32,0xbecefe46,0xbedb0f66,4 +np.float32,0x7da70bd4,0xbec66273,4 +np.float32,0x169811,0x169811,4 +np.float32,0xbee4dfee,0xbef5721a,4 +np.float32,0x3efbeae3,0x3f0936e6,4 +np.float32,0x8031bd61,0x8031bd61,4 +np.float32,0x8048e443,0x8048e443,4 +np.float32,0xff209aa6,0xbeb364cb,4 +np.float32,0xff477499,0x3c1b0041,4 +np.float32,0x803fe929,0x803fe929,4 +np.float32,0x3f70158b,0x3fae7725,4 +np.float32,0x7f795723,0x3e8e850a,4 +np.float32,0x3cba99,0x3cba99,4 +np.float32,0x80588d2a,0x80588d2a,4 +np.float32,0x805d1f05,0x805d1f05,4 +np.float32,0xff4ac09a,0xbefe614d,4 +np.float32,0x804af084,0x804af084,4 +np.float32,0x7c64ae63,0xc1a8b563,4 +np.float32,0x8078d793,0x8078d793,4 +np.float32,0x7f3e2436,0xbf8bf9d3,4 +np.float32,0x7ccec1,0x7ccec1,4 +np.float32,0xbf6462c7,0xbf9eb830,4 +np.float32,0x3f1002ca,0x3f216843,4 +np.float32,0xfe878ca6,0x409e73a5,4 +np.float32,0x3bd841d9,0x3bd842a7,4 +np.float32,0x7d406f41,0xbd9dcfa3,4 +np.float32,0x7c6d6,0x7c6d6,4 +np.float32,0x3f4ef360,0x3f86074b,4 +np.float32,0x805f534a,0x805f534a,4 +np.float32,0x1,0x1,4 +np.float32,0x3f739ee2,0x3fb39db2,4 +np.float32,0x3d0c2352,0x3d0c3153,4 +np.float32,0xfe8a4f2c,0x3edd8add,4 +np.float32,0x3e52eaa0,0x3e55f362,4 +np.float32,0x7bde9758,0xbf5ba5cf,4 +np.float32,0xff422654,0xbf41e487,4 +np.float32,0x385e5b,0x385e5b,4 +np.float32,0x5751dd,0x5751dd,4 +np.float32,0xff6c671c,0xc03e2d6d,4 +np.float32,0x1458be,0x1458be,4 +np.float32,0x80153d4d,0x80153d4d,4 +np.float32,0x7efd2adb,0x3e25458f,4 +np.float32,0xbe161880,0xbe172e12,4 +np.float32,0x7ecea1aa,0x40a66d79,4 +np.float32,0xbf5b02a2,0xbf9355f0,4 +np.float32,0x15d9ab,0x15d9ab,4 +np.float32,0x2dc7c7,0x2dc7c7,4 +np.float32,0xfebbf81a,0x4193f6e6,4 +np.float32,0xfe8e3594,0xc00a6695,4 +np.float32,0x185aa8,0x185aa8,4 +np.float32,0x3daea156,0x3daf0e00,4 +np.float32,0x3e071688,0x3e07e08e,4 +np.float32,0x802db9e6,0x802db9e6,4 +np.float32,0x7f7be2c4,0x3f1363dd,4 +np.float32,0x7eba3f5e,0xc13eb497,4 +np.float32,0x3de04a00,0x3de130a9,4 +np.float32,0xbf1022bc,0xbf2194eb,4 +np.float32,0xbf5b547e,0xbf93b53b,4 +np.float32,0x3e867bd6,0x3e89aa10,4 +np.float32,0xbea5eb5c,0xbeabfb73,4 +np.float32,0x7f1efae9,0x3ffca038,4 +np.float32,0xff5d0344,0xbe55dbbb,4 +np.float32,0x805167e7,0x805167e7,4 +np.float32,0xbdb3a020,0xbdb41667,4 +np.float32,0xbedea6b4,0xbeedd5fd,4 +np.float32,0x8053b45c,0x8053b45c,4 +np.float32,0x7ed370e9,0x3d90eba5,4 +np.float32,0xbefcd7da,0xbf09cf91,4 +np.float32,0x78b9ac,0x78b9ac,4 +np.float32,0xbf2f6dc0,0xbf5141ef,4 +np.float32,0x802d3a7b,0x802d3a7b,4 +np.float32,0xfd45d120,0x3fec31cc,4 +np.float32,0xbf7e7020,0xbfc4b2af,4 +np.float32,0xf04da,0xf04da,4 +np.float32,0xbe9819d4,0xbe9cbd35,4 +np.float32,0x8075ab35,0x8075ab35,4 +np.float32,0xbf052fdc,0xbf12aa2c,4 +np.float32,0x3f1530d0,0x3f28bd9f,4 +np.float32,0x80791881,0x80791881,4 +np.float32,0x67f309,0x67f309,4 +np.float32,0x3f12f16a,0x3f2588f5,4 +np.float32,0x3ecdac47,0x3ed97ff8,4 +np.float32,0xbf297fb7,0xbf478c39,4 +np.float32,0x8069fa80,0x8069fa80,4 +np.float32,0x807f940e,0x807f940e,4 +np.float32,0xbf648dc8,0xbf9eeecb,4 +np.float32,0x3de873b0,0x3de9748d,4 +np.float32,0x3f1aa645,0x3f30af1f,4 +np.float32,0xff227a62,0x3d8283cc,4 +np.float32,0xbf37187d,0xbf5e5f4c,4 +np.float32,0x803b1b1f,0x803b1b1f,4 +np.float32,0x3f58142a,0x3f8ff8da,4 +np.float32,0x8004339e,0x8004339e,4 +np.float32,0xbf0f5654,0xbf2077a4,4 +np.float32,0x3f17e509,0x3f2ca598,4 +np.float32,0x3f800000,0x3fc75923,4 +np.float32,0xfdf79980,0x42f13047,4 +np.float32,0x7f111381,0x3f13c4c9,4 +np.float32,0xbea40c70,0xbea9e724,4 +np.float32,0x110520,0x110520,4 +np.float32,0x60490d,0x60490d,4 +np.float32,0x3f6703ec,0x3fa21951,4 +np.float32,0xbf098256,0xbf187652,4 +np.float32,0x658951,0x658951,4 +np.float32,0x3f53bf16,0x3f8b2818,4 +np.float32,0xff451811,0xc0026068,4 +np.float32,0x80777ee0,0x80777ee0,4 +np.float32,0x3e4fcc19,0x3e52b286,4 +np.float32,0x7f387ee0,0x3ce93eb6,4 +np.float32,0xff51181f,0xbfca3ee4,4 +np.float32,0xbf5655ae,0xbf8e0304,4 +np.float32,0xff2f1dcd,0x40025471,4 +np.float32,0x7f6e58e5,0xbe9930d5,4 +np.float32,0x7adf11,0x7adf11,4 +np.float32,0xbe9a2bc2,0xbe9f0185,4 +np.float32,0x8065d3a0,0x8065d3a0,4 +np.float32,0x3ed6e826,0x3ee47c45,4 +np.float32,0x80598ea0,0x80598ea0,4 +np.float32,0x7f10b90a,0x40437bd0,4 +np.float32,0x27b447,0x27b447,4 +np.float32,0x7ecd861c,0x3fce250f,4 +np.float32,0x0,0x0,4 +np.float32,0xbeba82d6,0xbec3394c,4 +np.float32,0xbf4958b0,0xbf8048ea,4 +np.float32,0x7c643e,0x7c643e,4 +np.float32,0x580770,0x580770,4 +np.float32,0x805bf54a,0x805bf54a,4 +np.float32,0x7f1f3cee,0xbe1a54d6,4 +np.float32,0xfefefdea,0x3fa84576,4 +np.float32,0x7f007b7a,0x3e8a6d25,4 +np.float32,0xbf177959,0xbf2c0919,4 +np.float32,0xbf30fda0,0xbf53e058,4 +np.float32,0x3f0576be,0x3f130861,4 +np.float32,0x3f49380e,0x3f80283a,4 +np.float32,0xebc56,0xebc56,4 +np.float32,0x654e3b,0x654e3b,4 +np.float32,0x14a4d8,0x14a4d8,4 +np.float32,0xff69b3cb,0xbf822a88,4 +np.float32,0xbe9b6c1c,0xbea06109,4 +np.float32,0xbefddd7e,0xbf0a787b,4 +np.float32,0x4c4ebb,0x4c4ebb,4 +np.float32,0x7d0a74,0x7d0a74,4 +np.float32,0xbebb5f80,0xbec43635,4 +np.float32,0x7ee79723,0xc1c7f3f3,4 +np.float32,0x7f2be4c7,0xbfa6c693,4 +np.float32,0x805bc7d5,0x805bc7d5,4 +np.float32,0x8042f12c,0x8042f12c,4 +np.float32,0x3ef91be8,0x3f07697b,4 +np.float32,0x3cf37ac0,0x3cf38d1c,4 +np.float32,0x800000,0x800000,4 +np.float32,0xbe1ebf4c,0xbe200806,4 +np.float32,0x7f380862,0xbeb512e8,4 +np.float32,0xbe320064,0xbe33d0fc,4 +np.float32,0xff300b0c,0xbfadb805,4 +np.float32,0x308a06,0x308a06,4 +np.float32,0xbf084f6e,0xbf16d7b6,4 +np.float32,0xff47cab6,0x3f892b65,4 +np.float32,0xbed99f4a,0xbee7bfd5,4 +np.float32,0xff7d74c0,0x3ee88c9a,4 +np.float32,0x3c3d23,0x3c3d23,4 +np.float32,0x8074bde8,0x8074bde8,4 +np.float32,0x80042164,0x80042164,4 +np.float32,0x3e97c92a,0x3e9c6500,4 +np.float32,0x3b80e0,0x3b80e0,4 +np.float32,0xbf16646a,0xbf2a783d,4 +np.float32,0x7f3b4cb1,0xc01339be,4 +np.float32,0xbf31f36e,0xbf557fd0,4 +np.float32,0x7f540618,0xbe5f6fc1,4 +np.float32,0x7eee47d0,0x40a27e94,4 +np.float32,0x7f12f389,0xbebed654,4 +np.float32,0x56cff5,0x56cff5,4 +np.float32,0x8056032b,0x8056032b,4 +np.float32,0x3ed34e40,0x3ee02e38,4 +np.float32,0x7d51a908,0xbf19a90e,4 +np.float32,0x80000000,0x80000000,4 +np.float32,0xfdf73fd0,0xbf0f8cad,4 +np.float32,0x7ee4fe6d,0xbf1ea7e4,4 +np.float32,0x1f15ba,0x1f15ba,4 +np.float32,0xd18c3,0xd18c3,4 +np.float32,0x80797705,0x80797705,4 +np.float32,0x7ef07091,0x3f2f3b9a,4 +np.float32,0x7f552f41,0x3faf608c,4 +np.float32,0x3f779977,0x3fb9a7ad,4 +np.float32,0xfe1a7a50,0xbdadc4d1,4 +np.float32,0xbf449cf0,0xbf7740db,4 +np.float32,0xbe44e620,0xbe475cad,4 +np.float32,0x3f63a098,0x3f9dc2b5,4 +np.float32,0xfed40a12,0x4164533a,4 +np.float32,0x7a2bbb,0x7a2bbb,4 +np.float32,0xff7f7b9e,0xbeee8740,4 +np.float32,0x7ee27f8b,0x4233f53b,4 +np.float32,0xbf044c06,0xbf117c28,4 +np.float32,0xbeffde54,0xbf0bc49f,4 +np.float32,0xfeaef2e8,0x3ff258fe,4 +np.float32,0x527451,0x527451,4 +np.float32,0xbcef8d00,0xbcef9e7c,4 +np.float32,0xbf0e20c0,0xbf1ec9b2,4 +np.float32,0x8024afda,0x8024afda,4 +np.float32,0x7ef6cb3e,0x422cad0b,4 +np.float32,0x3c120,0x3c120,4 +np.float32,0xbf125c8f,0xbf24b62c,4 +np.float32,0x7e770a93,0x402c9d86,4 +np.float32,0xbd30a4e0,0xbd30c0ee,4 +np.float32,0xbf4d3388,0xbf843530,4 +np.float32,0x3f529072,0x3f89df92,4 +np.float32,0xff0270b1,0xbf81be9a,4 +np.float32,0x5e07e7,0x5e07e7,4 +np.float32,0x7bec32,0x7bec32,4 +np.float32,0x7fc00000,0x7fc00000,4 +np.float32,0x3e3ba5e0,0x3e3dc6e9,4 +np.float32,0x3ecb62d4,0x3ed6ce2c,4 +np.float32,0x3eb3dde8,0x3ebba68f,4 +np.float32,0x8063f952,0x8063f952,4 +np.float32,0x7f204aeb,0x3e88614e,4 +np.float32,0xbeae1ddc,0xbeb5278e,4 +np.float32,0x6829e9,0x6829e9,4 +np.float32,0xbf361a99,0xbf5ca354,4 +np.float32,0xbf24fbe6,0xbf406326,4 +np.float32,0x3f329d41,0x3f56a061,4 +np.float32,0xfed6d666,0x3e8f71a5,4 +np.float32,0x337f92,0x337f92,4 +np.float32,0xbe1c4970,0xbe1d8305,4 +np.float32,0xbe6b7e18,0xbe6fbbde,4 +np.float32,0x3f2267b9,0x3f3c61da,4 +np.float32,0xbee1ee94,0xbef1d628,4 +np.float32,0x7ecffc1a,0x3f02987e,4 +np.float32,0xbe9b1306,0xbe9fff3b,4 +np.float32,0xbeffacae,0xbf0ba468,4 +np.float32,0x7f800000,0xffc00000,4 +np.float32,0xfefc9aa8,0xc19de2a3,4 +np.float32,0x7d7185bb,0xbf9090ec,4 +np.float32,0x7edfbafd,0x3fe9352f,4 +np.float32,0x4ef2ec,0x4ef2ec,4 +np.float32,0x7f4cab2e,0xbff4e5dd,4 +np.float32,0xff3b1788,0x3e3c22e9,4 +np.float32,0x4e15ee,0x4e15ee,4 +np.float32,0xbf5451e6,0xbf8bc8a7,4 +np.float32,0x3f7f6d2e,0x3fc65e8b,4 +np.float32,0xbf1d9184,0xbf35071b,4 +np.float32,0xbf3a81cf,0xbf646d9b,4 +np.float32,0xbe71acc4,0xbe7643ab,4 +np.float32,0x528b7d,0x528b7d,4 +np.float32,0x2cb1d0,0x2cb1d0,4 +np.float32,0x3f324bf8,0x3f56161a,4 +np.float32,0x80709a21,0x80709a21,4 +np.float32,0x4bc448,0x4bc448,4 +np.float32,0x3e8bd600,0x3e8f6b7a,4 +np.float32,0xbeb97d30,0xbec20dd6,4 +np.float32,0x2a5669,0x2a5669,4 +np.float32,0x805f2689,0x805f2689,4 +np.float32,0xfe569f50,0x3fc51952,4 +np.float32,0x1de44c,0x1de44c,4 +np.float32,0x3ec7036c,0x3ed1ae67,4 +np.float32,0x8052b8e5,0x8052b8e5,4 +np.float32,0xff740a6b,0x3f4981a8,4 +np.float32,0xfee9bb70,0xc05e23be,4 +np.float32,0xff4e12c9,0x4002b4ad,4 +np.float32,0x803de0c2,0x803de0c2,4 +np.float32,0xbf433a07,0xbf74966f,4 +np.float32,0x803e60ca,0x803e60ca,4 +np.float32,0xbf19ee98,0xbf2fa07a,4 +np.float32,0x92929,0x92929,4 +np.float32,0x7f709c27,0x4257ba2d,4 +np.float32,0x803167c6,0x803167c6,4 +np.float32,0xbf095ead,0xbf184607,4 +np.float32,0x617060,0x617060,4 +np.float32,0x2d85b3,0x2d85b3,4 +np.float32,0x53d20b,0x53d20b,4 +np.float32,0x3e046838,0x3e052666,4 +np.float32,0xbe7c5fdc,0xbe80ce4b,4 +np.float32,0x3d18d060,0x3d18e289,4 +np.float32,0x804dc031,0x804dc031,4 +np.float32,0x3f224166,0x3f3c26cd,4 +np.float32,0x7d683e3c,0xbea24f25,4 +np.float32,0xbf3a92aa,0xbf648be4,4 +np.float32,0x8072670b,0x8072670b,4 +np.float32,0xbe281aec,0xbe29a1bc,4 +np.float32,0x7f09d918,0xc0942490,4 +np.float32,0x7ca9fd07,0x4018b990,4 +np.float32,0x7d36ac5d,0x3cf57184,4 +np.float32,0x8039b62f,0x8039b62f,4 +np.float32,0x6cad7b,0x6cad7b,4 +np.float32,0x3c0fd9ab,0x3c0fda9d,4 +np.float32,0x80299883,0x80299883,4 +np.float32,0x3c2d0e3e,0x3c2d0fe4,4 +np.float32,0x8002cf62,0x8002cf62,4 +np.float32,0x801dde97,0x801dde97,4 +np.float32,0x80411856,0x80411856,4 +np.float32,0x6ebce8,0x6ebce8,4 +np.float32,0x7b7d1a,0x7b7d1a,4 +np.float32,0x8031d3de,0x8031d3de,4 +np.float32,0x8005c4ab,0x8005c4ab,4 +np.float32,0xbf7dd803,0xbfc3b3ef,4 +np.float32,0x8017ae60,0x8017ae60,4 +np.float32,0xfe9316ce,0xbfe0544a,4 +np.float32,0x3f136bfe,0x3f2636ff,4 +np.float32,0x3df87b80,0x3df9b57d,4 +np.float32,0xff44c356,0xbf11c7ad,4 +np.float32,0x4914ae,0x4914ae,4 +np.float32,0x80524c21,0x80524c21,4 +np.float32,0x805c7dc8,0x805c7dc8,4 +np.float32,0xfed3c0aa,0xbff0c0ab,4 +np.float32,0x7eb2bfbb,0xbf4600bc,4 +np.float32,0xfec8df84,0x3f5bd350,4 +np.float32,0x3e5431a4,0x3e5748c3,4 +np.float32,0xbee6a3a0,0xbef79e86,4 +np.float32,0xbf6cc9b2,0xbfa9d61a,4 +np.float32,0x3f132bd5,0x3f25dbd9,4 +np.float32,0x7e6d2e48,0x3f9d025b,4 +np.float32,0x3edf430c,0x3eee942d,4 +np.float32,0x3f0d1b8a,0x3f1d60e1,4 +np.float32,0xbdf2f688,0xbdf41bfb,4 +np.float32,0xbe47a284,0xbe4a33ff,4 +np.float32,0x3eaa9fbc,0x3eb13be7,4 +np.float32,0xfe98d45e,0x3eb84517,4 +np.float32,0x7efc23b3,0x3dcc1c99,4 +np.float32,0x3ca36242,0x3ca367ce,4 +np.float32,0x3f76a944,0x3fb834e3,4 +np.float32,0xbf45207c,0xbf783f9b,4 +np.float32,0x3e7c1220,0x3e80a4f8,4 +np.float32,0x3f018200,0x3f0dd14e,4 +np.float32,0x3f53cdde,0x3f8b3839,4 +np.float32,0xbdbacb58,0xbdbb5063,4 +np.float32,0x804af68d,0x804af68d,4 +np.float32,0x3e2c12fc,0x3e2db65b,4 +np.float32,0x3f039433,0x3f10895a,4 +np.float32,0x7ef5193d,0x3f4115f7,4 +np.float32,0x8030afbe,0x8030afbe,4 +np.float32,0x3f06fa2a,0x3f150d5d,4 +np.float32,0x3f124442,0x3f2493d2,4 +np.float32,0xbeb5b792,0xbebdc090,4 +np.float32,0xbedc90a4,0xbeeb4de9,4 +np.float32,0x3f3ff8,0x3f3ff8,4 +np.float32,0x3ee75bc5,0x3ef881e4,4 +np.float32,0xfe80e3de,0xbf5cd535,4 +np.float32,0xf52eb,0xf52eb,4 +np.float32,0x80660ee8,0x80660ee8,4 +np.float32,0x3e173a58,0x3e185648,4 +np.float32,0xfe49520c,0xbf728d7c,4 +np.float32,0xbecbb8ec,0xbed73373,4 +np.float32,0xbf027ae0,0xbf0f173e,4 +np.float32,0xbcab6740,0xbcab6da8,4 +np.float32,0xbf2a15e2,0xbf487e11,4 +np.float32,0x3b781b,0x3b781b,4 +np.float32,0x44f559,0x44f559,4 +np.float32,0xff6a0ca6,0xc174d7c3,4 +np.float32,0x6460ef,0x6460ef,4 +np.float32,0xfe58009c,0x3ee2bb30,4 +np.float32,0xfec3c038,0x3e30d617,4 +np.float32,0x7f0687c0,0xbf62c820,4 +np.float32,0xbf44655e,0xbf76d589,4 +np.float32,0xbf42968c,0xbf735e78,4 +np.float32,0x80385503,0x80385503,4 +np.float32,0xbea7e3a2,0xbeae2d59,4 +np.float32,0x3dd0b770,0x3dd17131,4 +np.float32,0xbf4bc185,0xbf82b907,4 +np.float32,0xfefd7d64,0xbee05650,4 +np.float32,0xfaac3c00,0xbff23bc9,4 +np.float32,0xbf562f0d,0xbf8dd7f4,4 +np.float32,0x7fa00000,0x7fe00000,4 +np.float32,0x3e01bdb8,0x3e027098,4 +np.float32,0x3e2868ab,0x3e29f19e,4 +np.float32,0xfec55f2e,0x3f39f304,4 +np.float32,0xed4e,0xed4e,4 +np.float32,0x3e2b7330,0x3e2d11fa,4 +np.float32,0x7f738542,0x40cbbe16,4 +np.float32,0x3f123521,0x3f247e71,4 +np.float32,0x73572c,0x73572c,4 +np.float32,0x804936c8,0x804936c8,4 +np.float32,0x803b80d8,0x803b80d8,4 +np.float32,0x7f566c57,0xbee2855a,4 +np.float32,0xff0e3bd8,0xbff0543f,4 +np.float32,0x7d2b2fe7,0xbf94ba4c,4 +np.float32,0xbf0da470,0xbf1e1dc2,4 +np.float32,0xbd276500,0xbd277ce0,4 +np.float32,0xfcd15dc0,0x403ccc2a,4 +np.float32,0x80071e59,0x80071e59,4 +np.float32,0xbe9b0c34,0xbe9ff7be,4 +np.float32,0x3f4f9069,0x3f86ac50,4 +np.float32,0x80042a95,0x80042a95,4 +np.float32,0x7de28e39,0x3bc9b7f4,4 +np.float32,0xbf641935,0xbf9e5af8,4 +np.float32,0x8034f068,0x8034f068,4 +np.float32,0xff33a3d2,0xbf408e75,4 +np.float32,0xbcc51540,0xbcc51efc,4 +np.float32,0xff6d1ddf,0x3ef58f0e,4 +np.float32,0xbf64dfc4,0xbf9f5725,4 +np.float32,0xff068a06,0x3eea8987,4 +np.float32,0xff01c0af,0x3f24cdfe,4 +np.float32,0x3f4def7e,0x3f84f802,4 +np.float32,0xbf1b4ae7,0xbf31a299,4 +np.float32,0x8077df2d,0x8077df2d,4 +np.float32,0x3f0155c5,0x3f0d9785,4 +np.float32,0x5a54b2,0x5a54b2,4 +np.float32,0x7f271f9e,0x3efb2ef3,4 +np.float32,0xbf0ff2ec,0xbf215217,4 +np.float32,0x7f500130,0xbf8a7fdd,4 +np.float32,0xfed9891c,0xbf65c872,4 +np.float32,0xfecbfaae,0x403bdbc2,4 +np.float32,0x3f3a5aba,0x3f642772,4 +np.float32,0x7ebc681e,0xbd8df059,4 +np.float32,0xfe05e400,0xbfe35d74,4 +np.float32,0xbf295ace,0xbf4750ea,4 +np.float32,0x7ea055b2,0x3f62d6be,4 +np.float32,0xbd00b520,0xbd00bff9,4 +np.float32,0xbf7677aa,0xbfb7e8cf,4 +np.float32,0x3e83f788,0x3e86f816,4 +np.float32,0x801f6710,0x801f6710,4 +np.float32,0x801133cc,0x801133cc,4 +np.float32,0x41da2a,0x41da2a,4 +np.float32,0xff1622fd,0x3f023650,4 +np.float32,0x806c7a72,0x806c7a72,4 +np.float32,0x3f10779c,0x3f220bb4,4 +np.float32,0xbf08cf94,0xbf17848d,4 +np.float32,0xbecb55b4,0xbed6bebd,4 +np.float32,0xbf0a1528,0xbf193d7b,4 +np.float32,0x806a16bd,0x806a16bd,4 +np.float32,0xc222a,0xc222a,4 +np.float32,0x3930de,0x3930de,4 +np.float32,0x3f5c3588,0x3f94bca2,4 +np.float32,0x1215ad,0x1215ad,4 +np.float32,0x3ed15030,0x3eddcf67,4 +np.float32,0x7da83b2e,0x3fce0d39,4 +np.float32,0x32b0a8,0x32b0a8,4 +np.float32,0x805aed6b,0x805aed6b,4 +np.float32,0x3ef8e02f,0x3f074346,4 +np.float32,0xbdeb6780,0xbdec7250,4 +np.float32,0x3f6e3cec,0x3fabda61,4 +np.float32,0xfefd467a,0x3ef7821a,4 +np.float32,0xfef090fe,0x3bb752a2,4 +np.float32,0x8019c538,0x8019c538,4 +np.float32,0x3e8cf284,0x3e909e81,4 +np.float32,0xbe6c6618,0xbe70b0a2,4 +np.float32,0x7f50a539,0x3f367be1,4 +np.float32,0x8019fe2f,0x8019fe2f,4 +np.float32,0x800c3f48,0x800c3f48,4 +np.float32,0xfd054cc0,0xc0f52802,4 +np.float32,0x3d0cca20,0x3d0cd853,4 +np.float32,0xbf4a7c44,0xbf816e74,4 +np.float32,0x3f46fc40,0x3f7be153,4 +np.float32,0x807c5849,0x807c5849,4 +np.float32,0xd7e41,0xd7e41,4 +np.float32,0x70589b,0x70589b,4 +np.float32,0x80357b95,0x80357b95,4 +np.float32,0x3de239f0,0x3de326a5,4 +np.float32,0x800b08e3,0x800b08e3,4 +np.float32,0x807ec946,0x807ec946,4 +np.float32,0x3e2e4b83,0x3e2fff76,4 +np.float32,0x3f198e0f,0x3f2f12a6,4 +np.float32,0xbecb1aca,0xbed67979,4 +np.float32,0x80134082,0x80134082,4 +np.float32,0x3f3a269f,0x3f63ca05,4 +np.float32,0x3f1381e4,0x3f265622,4 +np.float32,0xff293080,0xbf10be6f,4 +np.float32,0xff800000,0xffc00000,4 +np.float32,0x37d196,0x37d196,4 +np.float32,0x7e57eea7,0x3e7d8138,4 +np.float32,0x804b1dae,0x804b1dae,4 +np.float32,0x7d9508f9,0xc1075b35,4 +np.float32,0x3f7bf468,0x3fc095e0,4 +np.float32,0x55472c,0x55472c,4 +np.float32,0x3ecdcd86,0x3ed9a738,4 +np.float32,0x3ed9be0f,0x3ee7e4e9,4 +np.float32,0x3e7e0ddb,0x3e81b2fe,4 +np.float32,0x7ee6c1d3,0x3f850634,4 +np.float32,0x800f6fad,0x800f6fad,4 +np.float32,0xfefb3bd6,0xbff68ecc,4 +np.float32,0x8013d6e2,0x8013d6e2,4 +np.float32,0x3f3a2cb6,0x3f63d4ee,4 +np.float32,0xff383c84,0x3e7854bb,4 +np.float32,0x3f21946e,0x3f3b1cea,4 +np.float32,0xff322ea2,0x3fb22f31,4 +np.float32,0x8065a024,0x8065a024,4 +np.float32,0x7f395e30,0xbefe0de1,4 +np.float32,0x5b52db,0x5b52db,4 +np.float32,0x7f7caea7,0x3dac8ded,4 +np.float32,0xbf0431f8,0xbf1159b2,4 +np.float32,0x7f15b25b,0xc02a3833,4 +np.float32,0x80131abc,0x80131abc,4 +np.float32,0x7e829d81,0xbeb2e93d,4 +np.float32,0x3f2c64d7,0x3f4c3e4d,4 +np.float32,0x7f228d48,0xc1518c74,4 +np.float32,0xfc3c6f40,0xbf00d585,4 +np.float32,0x7f754f0f,0x3e2152f5,4 +np.float32,0xff65d32b,0xbe8bd56c,4 +np.float32,0xfea6b8c0,0x41608655,4 +np.float32,0x3f7d4b05,0x3fc2c96a,4 +np.float32,0x3f463230,0x3f7a54da,4 +np.float32,0x805117bb,0x805117bb,4 +np.float32,0xbf2ad4f7,0xbf49b30e,4 +np.float32,0x3eaa01ff,0x3eb08b56,4 +np.float32,0xff7a02bb,0x3f095f73,4 +np.float32,0x759176,0x759176,4 +np.float32,0x803c18d5,0x803c18d5,4 +np.float32,0xbe0722d8,0xbe07ed16,4 +np.float32,0x3f4b4a99,0x3f823fc6,4 +np.float32,0x3f7d0451,0x3fc25463,4 +np.float32,0xfee31e40,0xbfb41091,4 +np.float32,0xbf733d2c,0xbfb30cf1,4 +np.float32,0x7ed81015,0x417c380c,4 +np.float32,0x7daafc3e,0xbe2a37ed,4 +np.float32,0x3e44f82b,0x3e476f67,4 +np.float32,0x7c8d99,0x7c8d99,4 +np.float32,0x3f7aec5a,0x3fbee991,4 +np.float32,0xff09fd55,0x3e0709d3,4 +np.float32,0xff4ba4df,0x4173c01f,4 +np.float32,0x3f43d944,0x3f75c7bd,4 +np.float32,0xff6a9106,0x40a10eff,4 +np.float32,0x3bc8341c,0x3bc834bf,4 +np.float32,0x3eea82,0x3eea82,4 +np.float32,0xfea36a3c,0x435729b2,4 +np.float32,0x7dcc1fb0,0x3e330053,4 +np.float32,0x3f616ae6,0x3f9b01ae,4 +np.float32,0x8030963f,0x8030963f,4 +np.float32,0x10d1e2,0x10d1e2,4 +np.float32,0xfeb9a8a6,0x40e6daac,4 +np.float32,0xbe1aba00,0xbe1bea3a,4 +np.float32,0x3cb6b4ea,0x3cb6bcac,4 +np.float32,0x3d8b0b64,0x3d8b422f,4 +np.float32,0x7b6894,0x7b6894,4 +np.float32,0x3e89dcde,0x3e8d4b4b,4 +np.float32,0x3f12b952,0x3f253974,4 +np.float32,0x1c316c,0x1c316c,4 +np.float32,0x7e2da535,0x3f95fe6b,4 +np.float32,0x3ae9a494,0x3ae9a4a4,4 +np.float32,0xbc5f5500,0xbc5f588b,4 +np.float32,0x3e7850fc,0x3e7d4d0e,4 +np.float32,0xbf800000,0xbfc75923,4 +np.float32,0x3e652d69,0x3e691502,4 +np.float32,0xbf6bdd26,0xbfa89129,4 +np.float32,0x3f441cfc,0x3f764a02,4 +np.float32,0x7f5445ff,0xc0906191,4 +np.float32,0x807b2ee3,0x807b2ee3,4 +np.float32,0xbeb6cab8,0xbebef9c0,4 +np.float32,0xff737277,0xbf327011,4 +np.float32,0xfc832aa0,0x402fd52e,4 +np.float32,0xbf0c7538,0xbf1c7c0f,4 +np.float32,0x7e1301c7,0xbf0ee63e,4 +np.float64,0xbfe0ef7df7a1defc,0xbfe2b76a8d8aeb35,1 +np.float64,0x7fdd9c2eae3b385c,0xbfc00d6885485039,1 +np.float64,0xbfb484c710290990,0xbfb4900e0a527555,1 +np.float64,0x7fe73e5d6cee7cba,0x3fefbf70a56b60d3,1 +np.float64,0x800a110aa8d42216,0x800a110aa8d42216,1 +np.float64,0xffedd4f3f3bba9e7,0xbff076f8c4124919,1 +np.float64,0x800093407f812682,0x800093407f812682,1 +np.float64,0x800a23150e54462a,0x800a23150e54462a,1 +np.float64,0xbfb1076864220ed0,0xbfb10dd95a74b733,1 +np.float64,0x3fed1f8b37fa3f16,0x3ff496100985211f,1 +np.float64,0x3fdf762f84beec5f,0x3fe1223eb04a17e0,1 +np.float64,0x53fd4e0aa7faa,0x53fd4e0aa7faa,1 +np.float64,0x3fdbd283bdb7a507,0x3fddb7ec9856a546,1 +np.float64,0xbfe43f449d687e89,0xbfe77724a0d3072b,1 +np.float64,0x618b73bcc316f,0x618b73bcc316f,1 +np.float64,0x67759424ceeb3,0x67759424ceeb3,1 +np.float64,0xbfe4b6f7d9a96df0,0xbfe831371f3bd7a8,1 +np.float64,0x800a531b8b74a637,0x800a531b8b74a637,1 +np.float64,0xffeeffd5c37dffab,0x3fea140cbc2c3726,1 +np.float64,0x3fe648e2002c91c4,0x3feac1b8816f972a,1 +np.float64,0x800f16242a1e2c48,0x800f16242a1e2c48,1 +np.float64,0xffeeff8e1dbdff1b,0xc000b555f117dce7,1 +np.float64,0x3fdf1cf73fbe39f0,0x3fe0e9032401135b,1 +np.float64,0x7fe19c388b633870,0x3fd5271b69317d5b,1 +np.float64,0x918f226d231e5,0x918f226d231e5,1 +np.float64,0x4cc19ab499834,0x4cc19ab499834,1 +np.float64,0xbd3121d57a624,0xbd3121d57a624,1 +np.float64,0xbfd145d334a28ba6,0xbfd1b468866124d6,1 +np.float64,0x8bdbf41517b7f,0x8bdbf41517b7f,1 +np.float64,0x3fd1b8cb3ea37198,0x3fd2306b13396cae,1 +np.float64,0xbfd632a959ac6552,0xbfd7220fcfb5ef78,1 +np.float64,0x1cdaafc639b57,0x1cdaafc639b57,1 +np.float64,0x3febdcce1577b99c,0x3ff2fe076195a2bc,1 +np.float64,0x7fca6e945934dd28,0x3ff43040df7024e8,1 +np.float64,0x3fbe08e78e3c11cf,0x3fbe2c60e6b48f75,1 +np.float64,0x7fc1ed0d0523da19,0x3ff55f8dcad9440f,1 +np.float64,0xbfdc729b8cb8e538,0xbfde7b6e15dd60c4,1 +np.float64,0x3fd219404f243281,0x3fd298d7b3546531,1 +np.float64,0x3fe715c3f56e2b88,0x3fec255b5a59456e,1 +np.float64,0x7fe8b88e74b1711c,0x3ff60efd2c81d13d,1 +np.float64,0xa1d2b9fd43a57,0xa1d2b9fd43a57,1 +np.float64,0xffc1818223230304,0xbfb85c6c1e8018e7,1 +np.float64,0x3fde38ac8b3c7159,0x3fe0580c7e228576,1 +np.float64,0x8008faf7b491f5f0,0x8008faf7b491f5f0,1 +np.float64,0xffe7a1d751af43ae,0xbf7114cd7bbcd981,1 +np.float64,0xffec2db1b4b85b62,0xbff5cae759667f83,1 +np.float64,0x7fefce1ae27f9c35,0x3ff4b8b88f4876cf,1 +np.float64,0x7fd1ff56a523feac,0xbff342ce192f14dd,1 +np.float64,0x80026b3e3f84d67d,0x80026b3e3f84d67d,1 +np.float64,0xffedee5879bbdcb0,0xc02fae11508b2be0,1 +np.float64,0x8003c0dc822781ba,0x8003c0dc822781ba,1 +np.float64,0xffe38a79eca714f4,0xc008aa23b7a63980,1 +np.float64,0xbfda70411eb4e082,0xbfdc0d7e29c89010,1 +np.float64,0x800a5e34f574bc6a,0x800a5e34f574bc6a,1 +np.float64,0x3fc19fac6e233f59,0x3fc1bc66ac0d73d4,1 +np.float64,0x3a8a61ea7514d,0x3a8a61ea7514d,1 +np.float64,0x3fb57b536e2af6a0,0x3fb588451f72f44c,1 +np.float64,0x7fd68c6d082d18d9,0xc032ac926b665c9a,1 +np.float64,0xd5b87cfdab710,0xd5b87cfdab710,1 +np.float64,0xfe80b20bfd017,0xfe80b20bfd017,1 +np.float64,0x3fef8781e37f0f04,0x3ff8215fe2c1315a,1 +np.float64,0xffedddbb9c3bbb76,0x3fd959b82258a32a,1 +np.float64,0x3fc7d41f382fa83e,0x3fc81b94c3a091ba,1 +np.float64,0xffc3275dcf264ebc,0x3fb2b3d4985c6078,1 +np.float64,0x7fe34d2b7ba69a56,0x40001f3618e3c7c9,1 +np.float64,0x3fd64ae35fac95c7,0x3fd73d77e0b730f8,1 +np.float64,0x800e53bf6b3ca77f,0x800e53bf6b3ca77f,1 +np.float64,0xbfddf7c9083bef92,0xbfe02f392744d2d1,1 +np.float64,0x1c237cc038471,0x1c237cc038471,1 +np.float64,0x3fe4172beea82e58,0x3fe739b4bf16bc7e,1 +np.float64,0xfa950523f52a1,0xfa950523f52a1,1 +np.float64,0xffc839a2c5307344,0xbff70ff8a3c9247f,1 +np.float64,0x264f828c4c9f1,0x264f828c4c9f1,1 +np.float64,0x148a650a2914e,0x148a650a2914e,1 +np.float64,0x3fe8d255c0b1a4ac,0x3fef623c3ea8d6e3,1 +np.float64,0x800f4fbb28be9f76,0x800f4fbb28be9f76,1 +np.float64,0x7fdca57bcfb94af7,0x3ff51207563fb6cb,1 +np.float64,0x3fe4944107692882,0x3fe7fad593235364,1 +np.float64,0x800119b4f1a2336b,0x800119b4f1a2336b,1 +np.float64,0xbfe734075e6e680e,0xbfec5b35381069f2,1 +np.float64,0xffeb3c00db767801,0xbfbbd7d22df7b4b3,1 +np.float64,0xbfe95c658cb2b8cb,0xbff03ad5e0bc888a,1 +np.float64,0xffeefeb58fbdfd6a,0xbfd5c9264deb0e11,1 +np.float64,0x7fccc80fde39901f,0xc012c60f914f3ca2,1 +np.float64,0x3fe5da289c2bb451,0x3fea07ad00a0ca63,1 +np.float64,0x800e364b0a5c6c96,0x800e364b0a5c6c96,1 +np.float64,0x3fcf9ea7d23f3d50,0x3fd023b72e8c9dcf,1 +np.float64,0x800a475cfc948eba,0x800a475cfc948eba,1 +np.float64,0xffd4e0d757a9c1ae,0xbfa89d573352e011,1 +np.float64,0xbfd4dbec8229b7da,0xbfd5a165f12c7c40,1 +np.float64,0xffe307ab51260f56,0x3fe6b1639da58c3f,1 +np.float64,0xbfe6955a546d2ab4,0xbfeb44ae2183fee9,1 +np.float64,0xbfca1f18f5343e30,0xbfca7d804ccccdf4,1 +np.float64,0xe9f4dfebd3e9c,0xe9f4dfebd3e9c,1 +np.float64,0xfff0000000000000,0xfff8000000000000,1 +np.float64,0x8008e69c0fb1cd38,0x8008e69c0fb1cd38,1 +np.float64,0xbfead1ccf975a39a,0xbff1c84b3db8ca93,1 +np.float64,0x25a982424b531,0x25a982424b531,1 +np.float64,0x8010000000000000,0x8010000000000000,1 +np.float64,0x80056204ea0ac40b,0x80056204ea0ac40b,1 +np.float64,0x800d1442d07a2886,0x800d1442d07a2886,1 +np.float64,0xbfaef3dadc3de7b0,0xbfaefd85ae6205f0,1 +np.float64,0x7fe969ce4b32d39c,0xbff3c4364fc6778f,1 +np.float64,0x7fe418bac0a83175,0x402167d16b1efe0b,1 +np.float64,0x3fd7c82a25af9054,0x3fd8f0c701315672,1 +np.float64,0x80013782a7826f06,0x80013782a7826f06,1 +np.float64,0x7fc031c7ee20638f,0x400747ab705e6904,1 +np.float64,0x3fe8cf327ff19e65,0x3fef5c14f8aafa89,1 +np.float64,0xbfe331a416a66348,0xbfe5e2290a098dd4,1 +np.float64,0x800607b2116c0f65,0x800607b2116c0f65,1 +np.float64,0x7fb40448f0280891,0xbfd43d4f0ffa1d64,1 +np.float64,0x7fefffffffffffff,0xbf74530cfe729484,1 +np.float64,0x3fe39b5444a736a9,0x3fe67eaa0b6acf27,1 +np.float64,0x3fee4733c4fc8e68,0x3ff631eabeef9696,1 +np.float64,0xbfec840f3b79081e,0xbff3cc8563ab2e74,1 +np.float64,0xbfc8f6854c31ed0c,0xbfc948caacb3bba0,1 +np.float64,0xffbcf754a639eea8,0xbfc88d17cad3992b,1 +np.float64,0x8000bd3163417a64,0x8000bd3163417a64,1 +np.float64,0x3fe766d0eaeecda2,0x3fecb660882f7024,1 +np.float64,0xb6cc30156d986,0xb6cc30156d986,1 +np.float64,0xffc0161f9f202c40,0x3fe19bdefe5cf8b1,1 +np.float64,0xffe1e462caa3c8c5,0x3fe392c47feea17b,1 +np.float64,0x30a36a566146e,0x30a36a566146e,1 +np.float64,0x3fa996f580332deb,0x3fa99c6b4f2abebe,1 +np.float64,0x3fba71716e34e2e0,0x3fba899f35edba1d,1 +np.float64,0xbfe8f7e5e971efcc,0xbfefac431a0e3d55,1 +np.float64,0xf48f1803e91e3,0xf48f1803e91e3,1 +np.float64,0x7fe3edc0a127db80,0xc03d1a579a5d74a8,1 +np.float64,0xffeba82056375040,0x3fdfd701308700db,1 +np.float64,0xbfeb5a924cf6b524,0xbff2640de7cd107f,1 +np.float64,0xfa4cd1a9f499a,0xfa4cd1a9f499a,1 +np.float64,0x800de1be7b9bc37d,0x800de1be7b9bc37d,1 +np.float64,0xffd44e56ad289cae,0x3fdf4b8085db9b67,1 +np.float64,0xbfe4fb3aea69f676,0xbfe89d2cc46fcc50,1 +np.float64,0xbfe596495d6b2c92,0xbfe997a589a1f632,1 +np.float64,0x6f55a2b8deab5,0x6f55a2b8deab5,1 +np.float64,0x7fe72dc4712e5b88,0x4039c4586b28c2bc,1 +np.float64,0x89348bd712692,0x89348bd712692,1 +np.float64,0xffe062156120c42a,0x4005f0580973bc77,1 +np.float64,0xbfeabc714d7578e2,0xbff1b07e2fa57dc0,1 +np.float64,0x8003a56b3e874ad7,0x8003a56b3e874ad7,1 +np.float64,0x800eeadfb85dd5c0,0x800eeadfb85dd5c0,1 +np.float64,0x46d77a4c8daf0,0x46d77a4c8daf0,1 +np.float64,0x8000c06e7dc180de,0x8000c06e7dc180de,1 +np.float64,0x3fe428d211e851a4,0x3fe754b1c00a89bc,1 +np.float64,0xc5be11818b7c2,0xc5be11818b7c2,1 +np.float64,0x7fefc244893f8488,0x401133dc54f52de5,1 +np.float64,0x3fde30eee93c61de,0x3fe0532b827543a6,1 +np.float64,0xbfd447f48b288fea,0xbfd4fd0654f90718,1 +np.float64,0xbfde98dc7b3d31b8,0xbfe094df12f84a06,1 +np.float64,0x3fed2c1a1dfa5834,0x3ff4a6c4f3470a65,1 +np.float64,0xbfe992165073242d,0xbff071ab039c9177,1 +np.float64,0x3fd0145d1b2028ba,0x3fd06d3867b703dc,1 +np.float64,0x3fe179457362f28b,0x3fe3722f1d045fda,1 +np.float64,0x800e28964fbc512d,0x800e28964fbc512d,1 +np.float64,0x8004a5d785294bb0,0x8004a5d785294bb0,1 +np.float64,0xbfd652f2272ca5e4,0xbfd7469713125120,1 +np.float64,0x7fe61f49036c3e91,0xbf9b6ccdf2d87e70,1 +np.float64,0xffb7d47dd02fa8f8,0xc004449a82320b13,1 +np.float64,0x3feb82f996b705f3,0x3ff29336c738a4c5,1 +np.float64,0x3fbb7fceea36ffa0,0x3fbb9b02c8ad7f93,1 +np.float64,0x80004519fb208a35,0x80004519fb208a35,1 +np.float64,0xbfe0539114e0a722,0xbfe1e86dc5aa039c,1 +np.float64,0x0,0x0,1 +np.float64,0xbfe99d1125f33a22,0xbff07cf8ec04300f,1 +np.float64,0xffd4fbeecc29f7de,0x3ffab76775a8455f,1 +np.float64,0xbfbf1c618e3e38c0,0xbfbf43d2764a8333,1 +np.float64,0x800cae02a9d95c06,0x800cae02a9d95c06,1 +np.float64,0x3febc47d3bf788fa,0x3ff2e0d7cf8ef509,1 +np.float64,0x3fef838f767f071f,0x3ff81aeac309bca0,1 +np.float64,0xbfd5e70716abce0e,0xbfd6ccb033ef7a35,1 +np.float64,0x3f9116fa60222df5,0x3f9117625f008e0b,1 +np.float64,0xffe02b1e5f20563c,0xbfe6b2ec293520b7,1 +np.float64,0xbf9b5aec3036b5e0,0xbf9b5c96c4c7f951,1 +np.float64,0xfdb0169bfb603,0xfdb0169bfb603,1 +np.float64,0x7fcdd1d51c3ba3a9,0x401f0e12fa0b7570,1 +np.float64,0xbfd088103fa11020,0xbfd0e8c4a333ffb2,1 +np.float64,0x3fe22df82ee45bf0,0x3fe46d03a7c14de2,1 +np.float64,0xbfd57b0c28aaf618,0xbfd65349a6191de5,1 +np.float64,0x3fe0a42f50a1485f,0x3fe252e26775d9a4,1 +np.float64,0x800fab4e363f569c,0x800fab4e363f569c,1 +np.float64,0xffe9f0ed63f3e1da,0xbfe278c341b171d5,1 +np.float64,0x7fe26c244664d848,0xbfb325269dad1996,1 +np.float64,0xffe830410bf06081,0xc00181a39f606e96,1 +np.float64,0x800c548a0c78a914,0x800c548a0c78a914,1 +np.float64,0x800f94761ebf28ec,0x800f94761ebf28ec,1 +np.float64,0x3fe5984845eb3091,0x3fe99aeb653c666d,1 +np.float64,0x7fe93e5bf8f27cb7,0xc010d159fa27396a,1 +np.float64,0xffefffffffffffff,0x3f74530cfe729484,1 +np.float64,0x4c83f1269907f,0x4c83f1269907f,1 +np.float64,0x3fde0065a8bc00cc,0x3fe034a1cdf026d4,1 +np.float64,0x800743810d6e8703,0x800743810d6e8703,1 +np.float64,0x80040662d5280cc6,0x80040662d5280cc6,1 +np.float64,0x3fed20b2c5ba4166,0x3ff497988519d7aa,1 +np.float64,0xffe8fa15e5f1f42b,0x3fff82ca76d797b4,1 +np.float64,0xbb72e22f76e5d,0xbb72e22f76e5d,1 +np.float64,0x7fc18ffa7c231ff4,0xbff4b8b4c3315026,1 +np.float64,0xbfe8d1ac44f1a358,0xbfef60efc4f821e3,1 +np.float64,0x3fd38c1fe8271840,0x3fd42dc37ff7262b,1 +np.float64,0xe577bee5caef8,0xe577bee5caef8,1 +np.float64,0xbff0000000000000,0xbff8eb245cbee3a6,1 +np.float64,0xffcb3a9dd436753c,0x3fcd1a3aff1c3fc7,1 +np.float64,0x7fe44bf2172897e3,0x3ff60bfe82a379f4,1 +np.float64,0x8009203823924071,0x8009203823924071,1 +np.float64,0x7fef8e0abc7f1c14,0x3fe90e4962d47ce5,1 +np.float64,0xffda50004434a000,0x3fb50dee03e1418b,1 +np.float64,0x7fe2ff276ea5fe4e,0xc0355b7d2a0a8d9d,1 +np.float64,0x3fd0711ba5a0e238,0x3fd0d03823d2d259,1 +np.float64,0xe7625b03cec4c,0xe7625b03cec4c,1 +np.float64,0xbfd492c8d7a92592,0xbfd55006cde8d300,1 +np.float64,0x8001fee99f23fdd4,0x8001fee99f23fdd4,1 +np.float64,0x7ff4000000000000,0x7ffc000000000000,1 +np.float64,0xfa15df97f42bc,0xfa15df97f42bc,1 +np.float64,0xbfec3fdca9787fb9,0xbff377164b13c7a9,1 +np.float64,0xbcec10e579d82,0xbcec10e579d82,1 +np.float64,0xbfc3b4e2132769c4,0xbfc3dd1fcc7150a6,1 +np.float64,0x80045b149ee8b62a,0x80045b149ee8b62a,1 +np.float64,0xffe044554c2088aa,0xbff741436d558785,1 +np.float64,0xffcc65f09f38cbe0,0xc0172b4adc2d317d,1 +np.float64,0xf68b2d3bed166,0xf68b2d3bed166,1 +np.float64,0x7fc7f44c572fe898,0x3fec69f3b1eca790,1 +np.float64,0x3fac51f61438a3ec,0x3fac595d34156002,1 +np.float64,0xbfeaa9f256f553e5,0xbff19bfdf5984326,1 +np.float64,0x800e4742149c8e84,0x800e4742149c8e84,1 +np.float64,0xbfc493df132927c0,0xbfc4c1ba4268ead9,1 +np.float64,0xbfbf0c56383e18b0,0xbfbf3389fcf50c72,1 +np.float64,0xbf978a0e082f1420,0xbf978b1dd1da3d3c,1 +np.float64,0xbfe04375356086ea,0xbfe1d34c57314dd1,1 +np.float64,0x3feaeeb29b75dd65,0x3ff1e8b772374979,1 +np.float64,0xbfe15e42c3a2bc86,0xbfe34d45d56c5c15,1 +np.float64,0x3fe507429a6a0e85,0x3fe8b058176b3225,1 +np.float64,0x3feee2b26c3dc565,0x3ff71b73203de921,1 +np.float64,0xbfd496577aa92cae,0xbfd553fa7fe15a5f,1 +np.float64,0x7fe2c10953e58212,0x3fc8ead6a0d14bbf,1 +np.float64,0x800035b77aa06b70,0x800035b77aa06b70,1 +np.float64,0x2329201e46525,0x2329201e46525,1 +np.float64,0xbfe6225c9a6c44b9,0xbfea80861590fa02,1 +np.float64,0xbfd6925030ad24a0,0xbfd78e70b1c2215d,1 +np.float64,0xbfd82225c4b0444c,0xbfd958a60f845b39,1 +np.float64,0xbb03d8a17609,0xbb03d8a17609,1 +np.float64,0x7fc33967b12672ce,0x40001e00c9af4002,1 +np.float64,0xff9373c6d026e780,0xbff308654a459d3d,1 +np.float64,0x3feab1f9c5f563f4,0x3ff1a4e0fd2f093d,1 +np.float64,0xbf993ef768327de0,0xbf994046b64e308b,1 +np.float64,0xffb87382fc30e708,0xbfde0accb83c891b,1 +np.float64,0x800bb3a118176743,0x800bb3a118176743,1 +np.float64,0x800c810250d90205,0x800c810250d90205,1 +np.float64,0xbfd2c4eb9ba589d8,0xbfd3539508b4a4a8,1 +np.float64,0xbee1f5437dc3f,0xbee1f5437dc3f,1 +np.float64,0x3fc07aeab520f5d8,0x3fc0926272f9d8e2,1 +np.float64,0xbfe23747a3246e90,0xbfe47a20a6e98687,1 +np.float64,0x3fde1296debc252c,0x3fe0401143ff6b5c,1 +np.float64,0xbfcec8c2f73d9184,0xbfcf644e25ed3b74,1 +np.float64,0xff9314f2c82629e0,0x40559a0f9099dfd1,1 +np.float64,0xbfe27487afa4e910,0xbfe4d0e01200bde6,1 +np.float64,0xffb3d6637627acc8,0x3fe326d4b1e1834f,1 +np.float64,0xffe6f84d642df09a,0x3fc73fa9f57c3acb,1 +np.float64,0xffe67cf76fecf9ee,0xc01cf48c97937ef9,1 +np.float64,0x7fdc73fc12b8e7f7,0xbfcfcecde9331104,1 +np.float64,0xffdcf8789239f0f2,0x3fe345e3b8e28776,1 +np.float64,0x800a70af5314e15f,0x800a70af5314e15f,1 +np.float64,0xffc862300730c460,0x3fc4e9ea813beca7,1 +np.float64,0xbfcc6961bd38d2c4,0xbfcce33bfa6c6bd1,1 +np.float64,0xbfc9b76bbf336ed8,0xbfca117456ac37e5,1 +np.float64,0x7fb86e829430dd04,0x400a5bd7a18e302d,1 +np.float64,0x7fb9813ef833027d,0xbfe5a6494f143625,1 +np.float64,0x8005085e2c2a10bd,0x8005085e2c2a10bd,1 +np.float64,0xffe5af099d6b5e12,0x40369bbe31e03e06,1 +np.float64,0xffde03b1fd3c0764,0x3ff061120aa1f52a,1 +np.float64,0x7fa4eb6cdc29d6d9,0x3fe9defbe9010322,1 +np.float64,0x800803f4b11007ea,0x800803f4b11007ea,1 +np.float64,0x7febd50f6df7aa1e,0xbffcf540ccf220dd,1 +np.float64,0x7fed454f08fa8a9d,0xbffc2a8b81079403,1 +np.float64,0xbfed7e8c69bafd19,0xbff5161e51ba6634,1 +np.float64,0xffef92e78eff25ce,0xbffefeecddae0ad3,1 +np.float64,0x7fe5b9b413ab7367,0xbfc681ba29704176,1 +np.float64,0x29284e805252,0x29284e805252,1 +np.float64,0xffed3955bcfa72ab,0xbfc695acb5f468de,1 +np.float64,0x3fe464ee1ca8c9dc,0x3fe7b140ce50fdca,1 +np.float64,0xffe522ae4bea455c,0x3feb957c146e66ef,1 +np.float64,0x8000000000000000,0x8000000000000000,1 +np.float64,0x3fd0c353a2a186a8,0x3fd1283aaa43a411,1 +np.float64,0x3fdb30a749b6614f,0x3fdcf40df006ed10,1 +np.float64,0x800109213cc21243,0x800109213cc21243,1 +np.float64,0xbfe72aa0c5ee5542,0xbfec4a713f513bc5,1 +np.float64,0x800865344ad0ca69,0x800865344ad0ca69,1 +np.float64,0x7feb7df60eb6fbeb,0x3fb1df06a67aa22f,1 +np.float64,0x3fe83a5dd93074bc,0x3fee3d63cda72636,1 +np.float64,0xbfde70e548bce1ca,0xbfe07b8e19c9dac6,1 +np.float64,0xbfeea38d537d471b,0xbff6bb18c230c0be,1 +np.float64,0x3fefeebbc47fdd78,0x3ff8cdaa53b7c7b4,1 +np.float64,0x7fe6512e20eca25b,0xbff623cee44a22b5,1 +np.float64,0xf8fa5ca3f1f4c,0xf8fa5ca3f1f4c,1 +np.float64,0x7fd12d00ed225a01,0xbfe90d518ea61faf,1 +np.float64,0x80027db43504fb69,0x80027db43504fb69,1 +np.float64,0xffc10a01aa221404,0x3fcc2065b3d0157b,1 +np.float64,0xbfef8286e87f050e,0xbff8193a54449b59,1 +np.float64,0xbfc73178092e62f0,0xbfc7735072ba4593,1 +np.float64,0x3fc859d70630b3ae,0x3fc8a626522af1c0,1 +np.float64,0x3fe4654c4268ca99,0x3fe7b1d2913eda1a,1 +np.float64,0xbfce93cd843d279c,0xbfcf2c2ef16a0957,1 +np.float64,0xffbcaa16d4395430,0xbfd511ced032d784,1 +np.float64,0xbfe91f980e723f30,0xbfeffb39cf8c7746,1 +np.float64,0x800556fb6f0aadf8,0x800556fb6f0aadf8,1 +np.float64,0xffd009cde520139c,0x3fe4fa83b1e93d28,1 +np.float64,0x7febc0675e3780ce,0x3feb53930c004dae,1 +np.float64,0xbfe7f975bdeff2ec,0xbfedc36e6729b010,1 +np.float64,0x45aff57c8b5ff,0x45aff57c8b5ff,1 +np.float64,0xbfec7ebd0138fd7a,0xbff3c5cab680aae0,1 +np.float64,0x8009448003b28900,0x8009448003b28900,1 +np.float64,0x3fca4b992d349732,0x3fcaabebcc86aa9c,1 +np.float64,0x3fca069161340d20,0x3fca63ecc742ff3a,1 +np.float64,0x80063bc80bec7791,0x80063bc80bec7791,1 +np.float64,0xbfe1764bffe2ec98,0xbfe36e1cb30cec94,1 +np.float64,0xffd0dba72f21b74e,0x3fb1834964d57ef6,1 +np.float64,0xbfe31848fc263092,0xbfe5bd066445cbc3,1 +np.float64,0xbfd1fb227323f644,0xbfd278334e27f02d,1 +np.float64,0xffdc59069fb8b20e,0xbfdfc363f559ea2c,1 +np.float64,0x3fdea52a52bd4a55,0x3fe09cada4e5344c,1 +np.float64,0x3f715e55a022bd00,0x3f715e5c72a2809e,1 +np.float64,0x1d1ac6023a35a,0x1d1ac6023a35a,1 +np.float64,0x7feacc71627598e2,0x400486b82121da19,1 +np.float64,0xa0287fa340510,0xa0287fa340510,1 +np.float64,0xffe352c5abe6a58b,0xc002623346060543,1 +np.float64,0x7fed577a23baaef3,0x3fda19bc8fa3b21f,1 +np.float64,0x3fde8dd5263d1baa,0x3fe08de0fedf7029,1 +np.float64,0x3feddd3be2bbba78,0x3ff599b2f3e018cc,1 +np.float64,0xc7a009f58f401,0xc7a009f58f401,1 +np.float64,0xbfef03d5a4fe07ab,0xbff74ee08681f47b,1 +np.float64,0x7fe2cf60eea59ec1,0x3fe905fb44f8cc60,1 +np.float64,0xbfe498fcab6931fa,0xbfe8023a6ff8becf,1 +np.float64,0xbfef7142acfee285,0xbff7fd196133a595,1 +np.float64,0xd214ffdba42a0,0xd214ffdba42a0,1 +np.float64,0x8006de7d78cdbcfc,0x8006de7d78cdbcfc,1 +np.float64,0xb247d34f648fb,0xb247d34f648fb,1 +np.float64,0xbfdd5bece6bab7da,0xbfdf9ba63ca2c5b2,1 +np.float64,0x7fe874650af0e8c9,0x3fe74204e122c10f,1 +np.float64,0x800768c49baed18a,0x800768c49baed18a,1 +np.float64,0x3fb4c0a192298140,0x3fb4cc4c8aa43300,1 +np.float64,0xbfa740531c2e80a0,0xbfa7446b7c74ae8e,1 +np.float64,0x7fe10d6edf221add,0x3fedbcd2eae26657,1 +np.float64,0xbfe9175d0f722eba,0xbfefeaca7f32c6e3,1 +np.float64,0x953e11d32a7c2,0x953e11d32a7c2,1 +np.float64,0x80032df90c465bf3,0x80032df90c465bf3,1 +np.float64,0xffec5b799638b6f2,0xbfe95cd2c69be12c,1 +np.float64,0xffe0c3cfa9a1879f,0x3fe20b99b0c108ce,1 +np.float64,0x3fb610d8e22c21b2,0x3fb61ee0d6c16df8,1 +np.float64,0xffe16bb39962d766,0xc016d370381b6b42,1 +np.float64,0xbfdc72edb238e5dc,0xbfde7bd2de10717a,1 +np.float64,0xffed52dee3baa5bd,0xc01994c08899129a,1 +np.float64,0xffa92aab08325550,0xbff2b881ce363cbd,1 +np.float64,0x7fe028282de0504f,0xc0157ff96c69a9c7,1 +np.float64,0xbfdb2151bf3642a4,0xbfdce196fcc35857,1 +np.float64,0x3fcffbd13c3ff7a2,0x3fd0554b5f0371ac,1 +np.float64,0x800d206bff1a40d8,0x800d206bff1a40d8,1 +np.float64,0x458f818c8b1f1,0x458f818c8b1f1,1 +np.float64,0x800a7b56a234f6ae,0x800a7b56a234f6ae,1 +np.float64,0xffe3d86161e7b0c2,0xbff58d0dbde9f188,1 +np.float64,0xe8ed82e3d1db1,0xe8ed82e3d1db1,1 +np.float64,0x3fe234e0176469c0,0x3fe476bd36b96a75,1 +np.float64,0xbfc7cb9c132f9738,0xbfc812c46e185e0b,1 +np.float64,0xbfeba116c1f7422e,0xbff2b6b7563ad854,1 +np.float64,0x7fe7041de62e083b,0x3f5d2b42aca47274,1 +np.float64,0xbfcf60f4ff3ec1e8,0xbfd002eb83406436,1 +np.float64,0xbfc06067a520c0d0,0xbfc0776e5839ecda,1 +np.float64,0x4384965a87093,0x4384965a87093,1 +np.float64,0xd2ed9d01a5db4,0xd2ed9d01a5db4,1 +np.float64,0x3fbea88cb63d5119,0x3fbece49cc34a379,1 +np.float64,0x3fe7e982ebefd306,0x3feda5bd4c435d43,1 +np.float64,0xffdb60a3e036c148,0xbfcb7ed21e7a8f49,1 +np.float64,0x7fdba9231eb75245,0xbfd750cab1536398,1 +np.float64,0x800d593534dab26b,0x800d593534dab26b,1 +np.float64,0xffdf15fb683e2bf6,0x3fb3aaea23357f06,1 +np.float64,0xbfd6f8a2e5adf146,0xbfd802e509d67c67,1 +np.float64,0x3feeaa31513d5463,0x3ff6c52147dc053c,1 +np.float64,0xf2f6dfd3e5edc,0xf2f6dfd3e5edc,1 +np.float64,0x7fd58d8279ab1b04,0x403243f23d02af2a,1 +np.float64,0x8000000000000001,0x8000000000000001,1 +np.float64,0x3fdffb8e0ebff71c,0x3fe1786cb0a6b0f3,1 +np.float64,0xc999826b93331,0xc999826b93331,1 +np.float64,0xffc4966f19292ce0,0x3ff0836c75c56cc7,1 +np.float64,0x7fef95a4b2ff2b48,0xbfbbe2c27c78154f,1 +np.float64,0xb8f1307f71e26,0xb8f1307f71e26,1 +np.float64,0x3fe807bc7eb00f79,0x3fedde19f2d3c42d,1 +np.float64,0x5e4b6580bc98,0x5e4b6580bc98,1 +np.float64,0xffe19353576326a6,0xc0278c51fee07d36,1 +np.float64,0xbfb0ca6f3e2194e0,0xbfb0d09be673fa72,1 +np.float64,0x3fea724211b4e484,0x3ff15ee06f0a0a13,1 +np.float64,0xbfda21e1c4b443c4,0xbfdbb041f3c86832,1 +np.float64,0x8008082b24901057,0x8008082b24901057,1 +np.float64,0xbfd031aa4ea06354,0xbfd08c77729634bb,1 +np.float64,0xbfc407e153280fc4,0xbfc432275711df5f,1 +np.float64,0xbb4fa4b5769f5,0xbb4fa4b5769f5,1 +np.float64,0x7fed6d1daffada3a,0xc037a14bc7b41fab,1 +np.float64,0xffeee589943dcb12,0x3ff2abfe47037778,1 +np.float64,0x301379d260270,0x301379d260270,1 +np.float64,0xbfec2fefc2b85fe0,0xbff36362c0363e06,1 +np.float64,0xbfe0b1c82e216390,0xbfe264f503f7c22c,1 +np.float64,0xbfea2bce78f4579d,0xbff112d6f07935ea,1 +np.float64,0x18508ef230a13,0x18508ef230a13,1 +np.float64,0x800667a74d6ccf4f,0x800667a74d6ccf4f,1 +np.float64,0x79ce5c8cf39cc,0x79ce5c8cf39cc,1 +np.float64,0x3feda61c8efb4c39,0x3ff54c9ade076f54,1 +np.float64,0x3fe27e06b0e4fc0d,0x3fe4de665c1dc3ca,1 +np.float64,0xbfd15fea2722bfd4,0xbfd1d081c55813b0,1 +np.float64,0xbfe5222c4cea4458,0xbfe8db62deb7d2ad,1 +np.float64,0xbfe8a16c33b142d8,0xbfef02d5831592a8,1 +np.float64,0x3fdb60e7c4b6c1d0,0x3fdd2e4265c4c3b6,1 +np.float64,0x800076d62b60edad,0x800076d62b60edad,1 +np.float64,0xbfec8f1527791e2a,0xbff3da7ed3641e8d,1 +np.float64,0x2af03bfe55e08,0x2af03bfe55e08,1 +np.float64,0xa862ee0950c5e,0xa862ee0950c5e,1 +np.float64,0x7fea5a7c1eb4b4f7,0xbffa6f07d28ef211,1 +np.float64,0x90e118fb21c23,0x90e118fb21c23,1 +np.float64,0xbfead0721bf5a0e4,0xbff1c6c7a771a128,1 +np.float64,0x3f63f4a4c027e94a,0x3f63f4a75665da67,1 +np.float64,0x3fece0efa579c1e0,0x3ff443bec52f021e,1 +np.float64,0xbfdbe743b737ce88,0xbfddd129bff89c15,1 +np.float64,0x3fd48c9b8fa91938,0x3fd5492a630a8cb5,1 +np.float64,0x3ff0000000000000,0x3ff8eb245cbee3a6,1 +np.float64,0xbfd51ea33baa3d46,0xbfd5ebd5dc710204,1 +np.float64,0x3fcfbab0183f7560,0x3fd032a054580b00,1 +np.float64,0x8007abce13cf579d,0x8007abce13cf579d,1 +np.float64,0xbfef0f4723be1e8e,0xbff760c7008e8913,1 +np.float64,0x8006340f524c681f,0x8006340f524c681f,1 +np.float64,0x87b7d7010f71,0x87b7d7010f71,1 +np.float64,0x3fe9422da9b2845b,0x3ff02052e6148c45,1 +np.float64,0x7fddd259b93ba4b2,0xc000731aa33d84b6,1 +np.float64,0x3fe0156d12202ada,0x3fe1972ba309cb29,1 +np.float64,0x8004f1264b89e24d,0x8004f1264b89e24d,1 +np.float64,0x3fececdcacb9d9b9,0x3ff4534d5861f731,1 +np.float64,0x3fd1790ab822f215,0x3fd1eb97b1bb6fb4,1 +np.float64,0xffce5d11863cba24,0xbfcb4f38c17210da,1 +np.float64,0x800a30c32a546187,0x800a30c32a546187,1 +np.float64,0x3fa58cc61c2b198c,0x3fa59008add7233e,1 +np.float64,0xbfe0ac77d62158f0,0xbfe25de3dba0bc4a,1 +np.float64,0xeb8c5753d718b,0xeb8c5753d718b,1 +np.float64,0x3fee5438dafca872,0x3ff644fef7e7adb5,1 +np.float64,0x3faad1eb2c35a3e0,0x3faad83499f94057,1 +np.float64,0x3fe39152c46722a6,0x3fe66fba0b96ab6e,1 +np.float64,0xffd6fd17712dfa2e,0xc010d697d1ab8731,1 +np.float64,0x5214a888a4296,0x5214a888a4296,1 +np.float64,0x8000127a5da024f5,0x8000127a5da024f5,1 +np.float64,0x7feb3a366cb6746c,0x3fbe49bd8d5f213a,1 +np.float64,0xca479501948f3,0xca479501948f3,1 +np.float64,0x7fe7c799ce6f8f33,0xbfd796cd98dc620c,1 +np.float64,0xffe20bcf30a4179e,0xbff8ca5453fa088f,1 +np.float64,0x3fe624638a6c48c7,0x3fea83f123832c3c,1 +np.float64,0xbfe5f1377c6be26f,0xbfea2e143a2d522c,1 +np.float64,0x7fd193f9f8a327f3,0xbfb04ee2602574d4,1 +np.float64,0xbfe7419d2fee833a,0xbfec737f140d363d,1 +np.float64,0x1,0x1,1 +np.float64,0x7fe2ac246c655848,0x3fd14fee3237727a,1 +np.float64,0xa459b42948b37,0xa459b42948b37,1 +np.float64,0x3fb26155ae24c2ab,0x3fb2696fc446d4c6,1 +np.float64,0xbfdd7b332e3af666,0xbfdfc296c21f1aa8,1 +np.float64,0xbfe00dbda4a01b7c,0xbfe18d2b060f0506,1 +np.float64,0x8003bb22d3e77646,0x8003bb22d3e77646,1 +np.float64,0x3fee21b0a57c4361,0x3ff5fb6a21dc911c,1 +np.float64,0x80ca69270194d,0x80ca69270194d,1 +np.float64,0xbfd6d80350adb006,0xbfd7ddb501edbde0,1 +np.float64,0xd2f8b801a5f2,0xd2f8b801a5f2,1 +np.float64,0xbfe856b3f170ad68,0xbfee7334fdc49296,1 +np.float64,0x3fed5c1b20bab836,0x3ff4e73ee5d5c7f3,1 +np.float64,0xbfd58085a5ab010c,0xbfd6596ddc381ffa,1 +np.float64,0x3fe4f0134b29e027,0x3fe88b70602fbd21,1 +np.float64,0xffc9098fdc321320,0x4011c334a74a92cf,1 +np.float64,0x794749bef28ea,0x794749bef28ea,1 +np.float64,0xbfc86b547f30d6a8,0xbfc8b84a4fafe0af,1 +np.float64,0x7fe1356b9da26ad6,0x3fd270bca208d899,1 +np.float64,0x7fca0ef1aa341de2,0xbff851044c0734fa,1 +np.float64,0x80064cb8b62c9972,0x80064cb8b62c9972,1 +np.float64,0xffd3a09a83a74136,0x3ffb66dae0accdf5,1 +np.float64,0x800e301aa15c6035,0x800e301aa15c6035,1 +np.float64,0x800e51f323bca3e6,0x800e51f323bca3e6,1 +np.float64,0x7ff0000000000000,0xfff8000000000000,1 +np.float64,0x800c4278c87884f2,0x800c4278c87884f2,1 +np.float64,0xbfe8481649f0902c,0xbfee576772695096,1 +np.float64,0xffe2344e3fa4689c,0x3fb10442ec0888de,1 +np.float64,0xbfeada313d75b462,0xbff1d1aee3fab3a9,1 +np.float64,0x8009ddfb1333bbf7,0x8009ddfb1333bbf7,1 +np.float64,0x7fed3314c93a6629,0x3ff7a9b12dc1cd37,1 +np.float64,0x3fd55c26da2ab84e,0x3fd630a7b8aac78a,1 +np.float64,0x800cdb5203f9b6a4,0x800cdb5203f9b6a4,1 +np.float64,0xffd04a875da0950e,0x4009a13810ab121d,1 +np.float64,0x800f1acb527e3597,0x800f1acb527e3597,1 +np.float64,0xbf9519bf282a3380,0xbf951a82e9b955ff,1 +np.float64,0x3fcd7a42fa3af486,0x3fce028f3c51072d,1 +np.float64,0xbfdd3e21b73a7c44,0xbfdf769f2ff2480b,1 +np.float64,0xffd4361e2aa86c3c,0xbfc211ce8e9f792c,1 +np.float64,0x7fccf97f6939f2fe,0xbff8464bad830f06,1 +np.float64,0x800ce47fb939c900,0x800ce47fb939c900,1 +np.float64,0xffe9e51df173ca3b,0xbfceaf990d652c4e,1 +np.float64,0x3fe05bba5b20b775,0x3fe1f326e4455442,1 +np.float64,0x800a29b4b134536a,0x800a29b4b134536a,1 +np.float64,0xe6f794b7cdef3,0xe6f794b7cdef3,1 +np.float64,0xffb5b688ce2b6d10,0x3ff924bb97ae2f6d,1 +np.float64,0x7fa74105d82e820b,0x3fd49643aaa9eee4,1 +np.float64,0x80020d15f7a41a2d,0x80020d15f7a41a2d,1 +np.float64,0x3fd6a983d5ad5308,0x3fd7a8cc8835b5b8,1 +np.float64,0x7fcd9798f03b2f31,0x3fc534c2f7bf4721,1 +np.float64,0xffdd31873a3a630e,0xbfe3171fcdffb3f7,1 +np.float64,0x80075183234ea307,0x80075183234ea307,1 +np.float64,0x82f3132505e63,0x82f3132505e63,1 +np.float64,0x3febfd9cb837fb39,0x3ff325bbf812515d,1 +np.float64,0xbfb4630fda28c620,0xbfb46e1f802ec278,1 +np.float64,0x3feeed7c89fddafa,0x3ff72c20ce5a9ee4,1 +np.float64,0x7fd3dcb3c127b967,0x40123d27ec9ec31d,1 +np.float64,0xbfe923450c72468a,0xbff00149c5742725,1 +np.float64,0x7fdef7f91abdeff1,0xbfe02ceb21f7923d,1 +np.float64,0x7fdd70d28fbae1a4,0xbfefcc5c9d10cdfd,1 +np.float64,0x800ca445a8d9488c,0x800ca445a8d9488c,1 +np.float64,0x7fec2754e1f84ea9,0x40173f6c1c97f825,1 +np.float64,0x7fcbca31f7379463,0x401e26bd2667075b,1 +np.float64,0x8003fa1d0847f43b,0x8003fa1d0847f43b,1 +np.float64,0xffe95cf85932b9f0,0xc01308e60278aa11,1 +np.float64,0x8009c53948f38a73,0x8009c53948f38a73,1 +np.float64,0x3fdcca9226b99524,0x3fdee7a008f75d41,1 +np.float64,0xbfe9ee241f33dc48,0xbff0d16bfff6c8e9,1 +np.float64,0xbfb3365058266ca0,0xbfb33f9176ebb51d,1 +np.float64,0x7fa98e10f4331c21,0x3fdee04ffd31314e,1 +np.float64,0xbfe1a11aea634236,0xbfe3a8e3d84fda38,1 +np.float64,0xbfd8df051131be0a,0xbfda342805d1948b,1 +np.float64,0x3d49a2407a935,0x3d49a2407a935,1 +np.float64,0xfc51eefff8a3e,0xfc51eefff8a3e,1 +np.float64,0xda63950bb4c73,0xda63950bb4c73,1 +np.float64,0x80050f3d4fea1e7b,0x80050f3d4fea1e7b,1 +np.float64,0x3fcdbd6e453b7ae0,0x3fce497478c28e77,1 +np.float64,0x7ebd4932fd7aa,0x7ebd4932fd7aa,1 +np.float64,0x7fa3904eac27209c,0xc0015f3125efc151,1 +np.float64,0x7fc59f956b2b3f2a,0xc00c012e7a2c281f,1 +np.float64,0xbfd436d716a86dae,0xbfd4ea13533a942b,1 +np.float64,0x9347ae3d268f6,0x9347ae3d268f6,1 +np.float64,0xffd001764d2002ec,0xbffab3462e515623,1 +np.float64,0x3fe6f406662de80d,0x3febe9bac3954999,1 +np.float64,0x3f943ecaf8287d96,0x3f943f77dee5e77f,1 +np.float64,0x3fd6250efcac4a1c,0x3fd712afa947d56f,1 +np.float64,0xbfe849ff777093ff,0xbfee5b089d03391f,1 +np.float64,0xffd3b8ef8f2771e0,0x4000463ff7f29214,1 +np.float64,0xbfc3bae9252775d4,0xbfc3e34c133f1933,1 +np.float64,0xbfea93943df52728,0xbff18355e4fc341d,1 +np.float64,0x3fc4d922ad29b245,0x3fc508d66869ef29,1 +np.float64,0x4329694a8652e,0x4329694a8652e,1 +np.float64,0x8834f1a71069e,0x8834f1a71069e,1 +np.float64,0xe0e5be8dc1cb8,0xe0e5be8dc1cb8,1 +np.float64,0x7fef4d103afe9a1f,0xc0047b88b94554fe,1 +np.float64,0x3fe9b57af4f36af6,0x3ff0963831d51c3f,1 +np.float64,0x3fe081e2fa6103c6,0x3fe22572e41be655,1 +np.float64,0x3fd78cf7b42f19ef,0x3fd8acafa1ad776a,1 +np.float64,0x7fbffd58d43ffab1,0x3fb16092c7de6036,1 +np.float64,0xbfe1e8bfae23d180,0xbfe40c1c6277dd52,1 +np.float64,0x800a9f59fb153eb4,0x800a9f59fb153eb4,1 +np.float64,0xffebe14e33b7c29c,0x3fe0ec532f4deedd,1 +np.float64,0xffc36ca00426d940,0xc000806a712d6e83,1 +np.float64,0xbfcc2be82d3857d0,0xbfcca2a7d372ec64,1 +np.float64,0x800c03b908780772,0x800c03b908780772,1 +np.float64,0xf315a64be62b5,0xf315a64be62b5,1 +np.float64,0xbfe644043cec8808,0xbfeab974d3dc6d80,1 +np.float64,0x3fedb7de3cbb6fbc,0x3ff56549a5acd324,1 +np.float64,0xbfb1a875522350e8,0xbfb1afa41dee338d,1 +np.float64,0xffee8d4a407d1a94,0x3fead1749a636ff6,1 +np.float64,0x8004061c13080c39,0x8004061c13080c39,1 +np.float64,0x3fe650ae7feca15c,0x3feacefb8bc25f64,1 +np.float64,0x3fda8340e6b50682,0x3fdc24275cab1df8,1 +np.float64,0x8009084344321087,0x8009084344321087,1 +np.float64,0x7fdd19cb823a3396,0xbfd1d8fb35d89e3f,1 +np.float64,0xbfe893172571262e,0xbfeee716b592b93c,1 +np.float64,0x8ff5acc11fec,0x8ff5acc11fec,1 +np.float64,0xbfdca0c57cb9418a,0xbfdeb42465a1b59e,1 +np.float64,0xffd77bd2a3aef7a6,0x4012cd69e85b82d8,1 +np.float64,0xbfe6ea78982dd4f1,0xbfebd8ec61fb9e1f,1 +np.float64,0x7fe14b1d80a2963a,0xc02241642102cf71,1 +np.float64,0x3fe712bf286e257e,0x3fec20012329a7fb,1 +np.float64,0x7fcb6fa4d636df49,0x400b899d14a886b3,1 +np.float64,0x3fb82cb39a305960,0x3fb83f29c5f0822e,1 +np.float64,0x7fed694c8b3ad298,0xbfe2724373c69808,1 +np.float64,0xbfcd21229f3a4244,0xbfcda497fc3e1245,1 +np.float64,0x564d3770ac9a8,0x564d3770ac9a8,1 +np.float64,0xf4409e13e8814,0xf4409e13e8814,1 +np.float64,0x80068dca9a8d1b96,0x80068dca9a8d1b96,1 +np.float64,0xbfe13f82afe27f06,0xbfe3236ddded353f,1 +np.float64,0x80023f8114647f03,0x80023f8114647f03,1 +np.float64,0xeafba7dfd5f75,0xeafba7dfd5f75,1 +np.float64,0x3feca74ddeb94e9c,0x3ff3f95dcce5a227,1 +np.float64,0x10000000000000,0x10000000000000,1 +np.float64,0xbfebdb4141f7b682,0xbff2fc29823ac64a,1 +np.float64,0xbfcd75ee2f3aebdc,0xbfcdfdfd87cc6a29,1 +np.float64,0x7fc010cda420219a,0x3fae4ca2cf1f2657,1 +np.float64,0x1a90209e35205,0x1a90209e35205,1 +np.float64,0x8008057d01900afa,0x8008057d01900afa,1 +np.float64,0x3f9cb5f280396be5,0x3f9cb7dfb4e4be4e,1 +np.float64,0xffe1bbb60b63776c,0xc00011b1ffcb2561,1 +np.float64,0xffda883f6fb5107e,0x4044238ef4e2a198,1 +np.float64,0x3fc07c0b4a20f817,0x3fc09387de9eebcf,1 +np.float64,0x8003a9ebc0c753d8,0x8003a9ebc0c753d8,1 +np.float64,0x1d7fd5923affc,0x1d7fd5923affc,1 +np.float64,0xbfe9cd8cf9b39b1a,0xbff0af43e567ba4a,1 +np.float64,0x11285cb42250c,0x11285cb42250c,1 +np.float64,0xffe81ae1ccb035c3,0xbfe038be7eb563a6,1 +np.float64,0xbfe56473b1eac8e8,0xbfe94654d8ab9e75,1 +np.float64,0x3fee904619fd208c,0x3ff69e198152fe17,1 +np.float64,0xbfeeb9a2cbfd7346,0xbff6dc8d96da78cd,1 +np.float64,0x8006cdfa59ed9bf5,0x8006cdfa59ed9bf5,1 +np.float64,0x8008f2366d31e46d,0x8008f2366d31e46d,1 +np.float64,0x8008d5f91e31abf3,0x8008d5f91e31abf3,1 +np.float64,0x3fe85886f8b0b10e,0x3fee76af16f5a126,1 +np.float64,0x3fefb9b2b73f7365,0x3ff8745128fa3e3b,1 +np.float64,0x7fdf3e721f3e7ce3,0xbfb19381541ca2a8,1 +np.float64,0x3fd2768c41a4ed18,0x3fd2fe2f85a3f3a6,1 +np.float64,0xbfcabe3c6a357c78,0xbfcb239fb88bc260,1 +np.float64,0xffdffb6a3dbff6d4,0xbff7af4759fd557c,1 +np.float64,0x800817f75f302fef,0x800817f75f302fef,1 +np.float64,0xbfe6a1d1762d43a3,0xbfeb5a399a095ef3,1 +np.float64,0x7fd6f32f912de65e,0x40016dedc51aabd0,1 +np.float64,0x3fc6cb26652d964d,0x3fc7099f047d924a,1 +np.float64,0x3fe8b975d67172ec,0x3fef31946123c0e7,1 +np.float64,0xffe44a09d1e89413,0x3fdee9e5eac6e540,1 +np.float64,0xbfece76d4cb9cedb,0xbff44c34849d07ba,1 +np.float64,0x7feb76027036ec04,0x3fe08595a5e263ac,1 +np.float64,0xffe194f591a329ea,0x3fbe5bd626400a70,1 +np.float64,0xbfc170698122e0d4,0xbfc18c3de8b63565,1 +np.float64,0x3fc82b2c0f305658,0x3fc875c3b5fbcd08,1 +np.float64,0x3fd5015634aa02ac,0x3fd5cb1df07213c3,1 +np.float64,0x7fe640884b6c8110,0xbff66255a420abb5,1 +np.float64,0x5a245206b448b,0x5a245206b448b,1 +np.float64,0xffe9d9fa2f73b3f4,0xc0272b0dd34ab9bf,1 +np.float64,0x3fd990e8aab321d0,0x3fdb04cd3a29bcc3,1 +np.float64,0xde9dda8bbd3bc,0xde9dda8bbd3bc,1 +np.float64,0xbfe81b32b4703666,0xbfee029937fa9f5a,1 +np.float64,0xbfe68116886d022d,0xbfeb21c62081cb73,1 +np.float64,0x3fb8da191231b432,0x3fb8ee28c71507d3,1 +np.float64,0x3fb111395a222273,0x3fb117b57de3dea4,1 +np.float64,0xffbafadc6a35f5b8,0x3ffcc6d2370297b9,1 +np.float64,0x8002ca475b05948f,0x8002ca475b05948f,1 +np.float64,0xbfeafef57875fdeb,0xbff1fb1315676f24,1 +np.float64,0x7fcda427d73b484f,0xbff9f70212694d17,1 +np.float64,0xffe2517b3ba4a2f6,0xc029ca6707305bf4,1 +np.float64,0x7fc5ee156b2bdc2a,0xbff8384b59e9056e,1 +np.float64,0xbfec22af3278455e,0xbff3530fe25816b4,1 +np.float64,0x6b5a8c2cd6b52,0x6b5a8c2cd6b52,1 +np.float64,0xffdaf6c4b935ed8a,0x4002f00ce58affcf,1 +np.float64,0x800a41813c748303,0x800a41813c748303,1 +np.float64,0xbfd09a1269213424,0xbfd0fc0a0c5de8eb,1 +np.float64,0x7fa2cb74d42596e9,0x3fc3d40e000fa69d,1 +np.float64,0x7ff8000000000000,0x7ff8000000000000,1 +np.float64,0x3fbfbf8ed63f7f1e,0x3fbfe97bcad9f53a,1 +np.float64,0x7fe0ebba65a1d774,0x401b0f17b28618df,1 +np.float64,0x3fd02c3a25a05874,0x3fd086aa55b19c9c,1 +np.float64,0xec628f95d8c52,0xec628f95d8c52,1 +np.float64,0x3fd319329fa63264,0x3fd3afb04e0dec63,1 +np.float64,0x180e0ade301c2,0x180e0ade301c2,1 +np.float64,0xbfe8d78324f1af06,0xbfef6c66153064ee,1 +np.float64,0xffb89fa200313f48,0xbfeb96ff2d9358dc,1 +np.float64,0x7fe6abcf86ed579e,0xc0269f4de86365ec,1 +np.float64,0x7fdff8cd65bff19a,0xbfd0f7c6b9052c9a,1 +np.float64,0xbfd2e3a53d25c74a,0xbfd37520cda5f6b2,1 +np.float64,0x7fe844b096708960,0x3ff696a6182e5a7a,1 +np.float64,0x7fdce0c7a3b9c18e,0x3fd42875d69ed379,1 +np.float64,0xffba5a91cc34b520,0x4001b571e8991951,1 +np.float64,0xffe78fe4a6ef1fc9,0x3ff4507b31f5b3bc,1 +np.float64,0xbfd7047493ae08ea,0xbfd810618a53fffb,1 +np.float64,0xc6559def8cab4,0xc6559def8cab4,1 +np.float64,0x3fe75d67a76ebacf,0x3feca56817de65e4,1 +np.float64,0xffd24adbd6a495b8,0xc012c491addf2df5,1 +np.float64,0x7fed35e28dba6bc4,0x403a0fa555ff7ec6,1 +np.float64,0x80078c4afa0f1897,0x80078c4afa0f1897,1 +np.float64,0xa6ec39114dd87,0xa6ec39114dd87,1 +np.float64,0x7fb1bd33ba237a66,0x4010092bb6810fd4,1 +np.float64,0x800ecf215edd9e43,0x800ecf215edd9e43,1 +np.float64,0x3fb7c169242f82d2,0x3fb7d2ed30c462e6,1 +np.float64,0xbf71b46d60236900,0xbf71b4749a10c112,1 +np.float64,0x800d7851787af0a3,0x800d7851787af0a3,1 +np.float64,0x3fcb4a45e7369488,0x3fcbb61701a1bcec,1 +np.float64,0x3fd4e3682429c6d0,0x3fd5a9bcb916eb94,1 +np.float64,0x800497564c292ead,0x800497564c292ead,1 +np.float64,0xbfca3737a1346e70,0xbfca96a86ae5d687,1 +np.float64,0x19aa87e03356,0x19aa87e03356,1 +np.float64,0xffb2593fe624b280,0xc05fedb99b467ced,1 +np.float64,0xbfdd8748fbbb0e92,0xbfdfd1a7df17252c,1 +np.float64,0x8004c7afc7098f60,0x8004c7afc7098f60,1 +np.float64,0x7fde48b2bf3c9164,0xbfe36ef1158ed420,1 +np.float64,0xbfec8e0eb0f91c1d,0xbff3d9319705a602,1 +np.float64,0xffea1be204f437c3,0xc0144f67298c3e6f,1 +np.float64,0x7fdb906b593720d6,0xbfce99233396eda7,1 +np.float64,0x3fef0f114ffe1e22,0x3ff76072a258a51b,1 +np.float64,0x3fe3e284c8e7c50a,0x3fe6e9b05e17c999,1 +np.float64,0xbfbda9eef23b53e0,0xbfbdcc1abb443597,1 +np.float64,0x3feb6454d4f6c8aa,0x3ff26f65a85baba4,1 +np.float64,0x3fea317439f462e8,0x3ff118e2187ef33f,1 +np.float64,0x376ad0646ed5b,0x376ad0646ed5b,1 +np.float64,0x7fdd461a1c3a8c33,0x3f7ba20fb79e785f,1 +np.float64,0xebc520a3d78a4,0xebc520a3d78a4,1 +np.float64,0x3fca90fe53352200,0x3fcaf45c7fae234d,1 +np.float64,0xbfe80dd1de701ba4,0xbfede97e12cde9de,1 +np.float64,0x3fd242b00ea48560,0x3fd2c5cf9bf69a31,1 +np.float64,0x7fe46c057828d80a,0xbfe2f76837488f94,1 +np.float64,0x3fc162bea322c580,0x3fc17e517c958867,1 +np.float64,0xffebf0452ff7e08a,0x3ffc3fd95c257b54,1 +np.float64,0xffd88043c6310088,0x4008b05598d0d95f,1 +np.float64,0x800d8c49da5b1894,0x800d8c49da5b1894,1 +np.float64,0xbfed33b487ba6769,0xbff4b0ea941f8a6a,1 +np.float64,0x16b881e22d711,0x16b881e22d711,1 +np.float64,0x288bae0051177,0x288bae0051177,1 +np.float64,0xffc83a0fe8307420,0x4006eff03da17f86,1 +np.float64,0x3fc7868b252f0d18,0x3fc7cb4954290324,1 +np.float64,0xbfe195514b232aa2,0xbfe398aae6c8ed76,1 +np.float64,0x800c001ae7f80036,0x800c001ae7f80036,1 +np.float64,0x7feb82abe7370557,0xbff1e13fe6fad23c,1 +np.float64,0xffecf609cdf9ec13,0xc0112aa1805ae59e,1 +np.float64,0xffddd654f63bacaa,0x3fe46cce899f710d,1 +np.float64,0x3fe2163138642c62,0x3fe44b9c760acd4c,1 +np.float64,0x4e570dc09cae2,0x4e570dc09cae2,1 +np.float64,0x7fe9e8d091f3d1a0,0xc000fe20f8e9a4b5,1 +np.float64,0x7fe60042952c0084,0x3fd0aa740f394c2a,1 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/examples/cython/checks.pyx b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/examples/cython/checks.pyx new file mode 100644 index 0000000000000000000000000000000000000000..a2637951908c5288cb21f6e1d7c8be6de20c5ae2 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/examples/cython/checks.pyx @@ -0,0 +1,381 @@ +#cython: language_level=3 + +""" +Functions in this module give python-space wrappers for cython functions +exposed in numpy/__init__.pxd, so they can be tested in test_cython.py +""" +import numpy as np +cimport numpy as cnp +cnp.import_array() + + +def is_td64(obj): + return cnp.is_timedelta64_object(obj) + + +def is_dt64(obj): + return cnp.is_datetime64_object(obj) + + +def get_dt64_value(obj): + return cnp.get_datetime64_value(obj) + + +def get_td64_value(obj): + return cnp.get_timedelta64_value(obj) + + +def get_dt64_unit(obj): + return cnp.get_datetime64_unit(obj) + + +def is_integer(obj): + return isinstance(obj, (cnp.integer, int)) + + +def get_datetime_iso_8601_strlen(): + return cnp.get_datetime_iso_8601_strlen(0, cnp.NPY_FR_ns) + + +def convert_datetime64_to_datetimestruct(): + cdef: + cnp.npy_datetimestruct dts + cnp.PyArray_DatetimeMetaData meta + cnp.int64_t value = 1647374515260292 + # i.e. (time.time() * 10**6) at 2022-03-15 20:01:55.260292 UTC + + meta.base = cnp.NPY_FR_us + meta.num = 1 + cnp.convert_datetime64_to_datetimestruct(&meta, value, &dts) + return dts + + +def make_iso_8601_datetime(dt: "datetime"): + cdef: + cnp.npy_datetimestruct dts + char result[36] # 36 corresponds to NPY_FR_s passed below + int local = 0 + int utc = 0 + int tzoffset = 0 + + dts.year = dt.year + dts.month = dt.month + dts.day = dt.day + dts.hour = dt.hour + dts.min = dt.minute + dts.sec = dt.second + dts.us = dt.microsecond + dts.ps = dts.as = 0 + + cnp.make_iso_8601_datetime( + &dts, + result, + sizeof(result), + local, + utc, + cnp.NPY_FR_s, + tzoffset, + cnp.NPY_NO_CASTING, + ) + return result + + +cdef cnp.broadcast multiiter_from_broadcast_obj(object bcast): + cdef dict iter_map = { + 1: cnp.PyArray_MultiIterNew1, + 2: cnp.PyArray_MultiIterNew2, + 3: cnp.PyArray_MultiIterNew3, + 4: cnp.PyArray_MultiIterNew4, + 5: cnp.PyArray_MultiIterNew5, + } + arrays = [x.base for x in bcast.iters] + cdef cnp.broadcast result = iter_map[len(arrays)](*arrays) + return result + + +def get_multiiter_size(bcast: "broadcast"): + cdef cnp.broadcast multi = multiiter_from_broadcast_obj(bcast) + return multi.size + + +def get_multiiter_number_of_dims(bcast: "broadcast"): + cdef cnp.broadcast multi = multiiter_from_broadcast_obj(bcast) + return multi.nd + + +def get_multiiter_current_index(bcast: "broadcast"): + cdef cnp.broadcast multi = multiiter_from_broadcast_obj(bcast) + return multi.index + + +def get_multiiter_num_of_iterators(bcast: "broadcast"): + cdef cnp.broadcast multi = multiiter_from_broadcast_obj(bcast) + return multi.numiter + + +def get_multiiter_shape(bcast: "broadcast"): + cdef cnp.broadcast multi = multiiter_from_broadcast_obj(bcast) + return tuple([multi.dimensions[i] for i in range(bcast.nd)]) + + +def get_multiiter_iters(bcast: "broadcast"): + cdef cnp.broadcast multi = multiiter_from_broadcast_obj(bcast) + return tuple([multi.iters[i] for i in range(bcast.numiter)]) + + +def get_default_integer(): + if cnp.NPY_DEFAULT_INT == cnp.NPY_LONG: + return cnp.dtype("long") + if cnp.NPY_DEFAULT_INT == cnp.NPY_INTP: + return cnp.dtype("intp") + return None + +def get_ravel_axis(): + return cnp.NPY_RAVEL_AXIS + + +def conv_intp(cnp.intp_t val): + return val + + +def get_dtype_flags(cnp.dtype dtype): + return dtype.flags + + +cdef cnp.NpyIter* npyiter_from_nditer_obj(object it): + """A function to create a NpyIter struct from a nditer object. + + This function is only meant for testing purposes and only extracts the + necessary info from nditer to test the functionality of NpyIter methods + """ + cdef: + cnp.NpyIter* cit + cnp.PyArray_Descr* op_dtypes[3] + cnp.npy_uint32 op_flags[3] + cnp.PyArrayObject* ops[3] + cnp.npy_uint32 flags = 0 + + if it.has_index: + flags |= cnp.NPY_ITER_C_INDEX + if it.has_delayed_bufalloc: + flags |= cnp.NPY_ITER_BUFFERED | cnp.NPY_ITER_DELAY_BUFALLOC + if it.has_multi_index: + flags |= cnp.NPY_ITER_MULTI_INDEX + + # one of READWRITE, READONLY and WRTIEONLY at the minimum must be specified for op_flags + for i in range(it.nop): + op_flags[i] = cnp.NPY_ITER_READONLY + + for i in range(it.nop): + op_dtypes[i] = cnp.PyArray_DESCR(it.operands[i]) + ops[i] = it.operands[i] + + cit = cnp.NpyIter_MultiNew(it.nop, &ops[0], flags, cnp.NPY_KEEPORDER, + cnp.NPY_NO_CASTING, &op_flags[0], + NULL) + return cit + + +def get_npyiter_size(it: "nditer"): + cdef cnp.NpyIter* cit = npyiter_from_nditer_obj(it) + result = cnp.NpyIter_GetIterSize(cit) + cnp.NpyIter_Deallocate(cit) + return result + + +def get_npyiter_ndim(it: "nditer"): + cdef cnp.NpyIter* cit = npyiter_from_nditer_obj(it) + result = cnp.NpyIter_GetNDim(cit) + cnp.NpyIter_Deallocate(cit) + return result + + +def get_npyiter_nop(it: "nditer"): + cdef cnp.NpyIter* cit = npyiter_from_nditer_obj(it) + result = cnp.NpyIter_GetNOp(cit) + cnp.NpyIter_Deallocate(cit) + return result + + +def get_npyiter_operands(it: "nditer"): + cdef cnp.NpyIter* cit = npyiter_from_nditer_obj(it) + try: + arr = cnp.NpyIter_GetOperandArray(cit) + return tuple([arr[i] for i in range(it.nop)]) + finally: + cnp.NpyIter_Deallocate(cit) + + +def get_npyiter_itviews(it: "nditer"): + cdef cnp.NpyIter* cit = npyiter_from_nditer_obj(it) + result = tuple([cnp.NpyIter_GetIterView(cit, i) for i in range(it.nop)]) + cnp.NpyIter_Deallocate(cit) + return result + + +def get_npyiter_dtypes(it: "nditer"): + cdef cnp.NpyIter* cit = npyiter_from_nditer_obj(it) + try: + arr = cnp.NpyIter_GetDescrArray(cit) + return tuple([arr[i] for i in range(it.nop)]) + finally: + cnp.NpyIter_Deallocate(cit) + + +def npyiter_has_delayed_bufalloc(it: "nditer"): + cdef cnp.NpyIter* cit = npyiter_from_nditer_obj(it) + result = cnp.NpyIter_HasDelayedBufAlloc(cit) + cnp.NpyIter_Deallocate(cit) + return result + + +def npyiter_has_index(it: "nditer"): + cdef cnp.NpyIter* cit = npyiter_from_nditer_obj(it) + result = cnp.NpyIter_HasIndex(cit) + cnp.NpyIter_Deallocate(cit) + return result + + +def npyiter_has_multi_index(it: "nditer"): + cdef cnp.NpyIter* cit = npyiter_from_nditer_obj(it) + result = cnp.NpyIter_HasMultiIndex(cit) + cnp.NpyIter_Deallocate(cit) + return result + + +def test_get_multi_index_iter_next(it: "nditer", cnp.ndarray[cnp.float64_t, ndim=2] arr): + cdef cnp.NpyIter* cit = npyiter_from_nditer_obj(it) + cdef cnp.NpyIter_GetMultiIndexFunc _get_multi_index = \ + cnp.NpyIter_GetGetMultiIndex(cit, NULL) + cdef cnp.NpyIter_IterNextFunc _iternext = \ + cnp.NpyIter_GetIterNext(cit, NULL) + cnp.NpyIter_Deallocate(cit) + return 1 + + +def npyiter_has_finished(it: "nditer"): + cdef cnp.NpyIter* cit + try: + cit = npyiter_from_nditer_obj(it) + cnp.NpyIter_GotoIterIndex(cit, it.index) + return not (cnp.NpyIter_GetIterIndex(cit) < cnp.NpyIter_GetIterSize(cit)) + finally: + cnp.NpyIter_Deallocate(cit) + +def compile_fillwithbyte(): + # Regression test for gh-25878, mostly checks it compiles. + cdef cnp.npy_intp dims[2] + dims = (1, 2) + pos = cnp.PyArray_ZEROS(2, dims, cnp.NPY_UINT8, 0) + cnp.PyArray_FILLWBYTE(pos, 1) + return pos + +def inc2_cfloat_struct(cnp.ndarray[cnp.cfloat_t] arr): + # This works since we compile in C mode, it will fail in cpp mode + arr[1].real += 1 + arr[1].imag += 1 + # This works in both modes + arr[1].real = arr[1].real + 1 + arr[1].imag = arr[1].imag + 1 + + +def npystring_pack(arr): + cdef char *string = "Hello world" + cdef size_t size = 11 + + allocator = cnp.NpyString_acquire_allocator( + cnp.PyArray_DESCR(arr) + ) + + # copy string->packed_string, the pointer to the underlying array buffer + ret = cnp.NpyString_pack( + allocator, cnp.PyArray_DATA(arr), string, size, + ) + + cnp.NpyString_release_allocator(allocator) + return ret + + +def npystring_load(arr): + allocator = cnp.NpyString_acquire_allocator( + cnp.PyArray_DESCR(arr) + ) + + cdef cnp.npy_static_string sdata + sdata.size = 0 + sdata.buf = NULL + + cdef cnp.npy_packed_static_string *packed_string = cnp.PyArray_DATA(arr) + cdef int is_null = cnp.NpyString_load(allocator, packed_string, &sdata) + cnp.NpyString_release_allocator(allocator) + if is_null == -1: + raise ValueError("String unpacking failed.") + elif is_null == 1: + # String in the array buffer is the null string + return "" + else: + # Cython syntax for copying a c string to python bytestring: + # slice the char * by the length of the string + return sdata.buf[:sdata.size].decode('utf-8') + + +def npystring_pack_multiple(arr1, arr2): + cdef cnp.npy_string_allocator *allocators[2] + cdef cnp.PyArray_Descr *descrs[2] + descrs[0] = cnp.PyArray_DESCR(arr1) + descrs[1] = cnp.PyArray_DESCR(arr2) + + cnp.NpyString_acquire_allocators(2, descrs, allocators) + + # Write into the first element of each array + cdef int ret1 = cnp.NpyString_pack( + allocators[0], cnp.PyArray_DATA(arr1), "Hello world", 11, + ) + cdef int ret2 = cnp.NpyString_pack( + allocators[1], cnp.PyArray_DATA(arr2), "test this", 9, + ) + + # Write a null string into the last element + cdef cnp.npy_intp elsize = cnp.PyArray_ITEMSIZE(arr1) + cdef int ret3 = cnp.NpyString_pack_null( + allocators[0], + (cnp.PyArray_DATA(arr1) + 2*elsize), + ) + + cnp.NpyString_release_allocators(2, allocators) + if ret1 == -1 or ret2 == -1 or ret3 == -1: + return -1 + + return 0 + + +def npystring_allocators_other_types(arr1, arr2): + cdef cnp.npy_string_allocator *allocators[2] + cdef cnp.PyArray_Descr *descrs[2] + descrs[0] = cnp.PyArray_DESCR(arr1) + descrs[1] = cnp.PyArray_DESCR(arr2) + + cnp.NpyString_acquire_allocators(2, descrs, allocators) + + # None of the dtypes here are StringDType, so every allocator + # should be NULL upon acquisition. + cdef int ret = 0 + for allocator in allocators: + if allocator != NULL: + ret = -1 + break + + cnp.NpyString_release_allocators(2, allocators) + return ret + + +def check_npy_uintp_type_enum(): + # Regression test for gh-27890: cnp.NPY_UINTP was not defined. + # Cython would fail to compile this before gh-27890 was fixed. + return cnp.NPY_UINTP > 0 + + +def resize_refcheck_test(): + # see gh-30991 + a = np.array([[0, 1], [2, 3]], order='C') + a.resize((2, 1)) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/examples/cython/meson.build b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/examples/cython/meson.build new file mode 100644 index 0000000000000000000000000000000000000000..8362c339ae7372ece36b97d57e2e03509f4e4c0b --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/examples/cython/meson.build @@ -0,0 +1,43 @@ +project('checks', 'c', 'cython') + +py = import('python').find_installation(pure: false) + +cc = meson.get_compiler('c') +cy = meson.get_compiler('cython') + +# Keep synced with pyproject.toml +if not cy.version().version_compare('>=3.0.6') + error('tests requires Cython >= 3.0.6') +endif + +cython_args = [] +if cy.version().version_compare('>=3.1.0') + cython_args += ['-Xfreethreading_compatible=True'] +endif + +npy_include_path = run_command(py, [ + '-c', + 'import os; os.chdir(".."); import numpy; print(os.path.abspath(numpy.get_include()))' + ], check: true).stdout().strip() + +npy_path = run_command(py, [ + '-c', + 'import os; os.chdir(".."); import numpy; print(os.path.dirname(numpy.__file__).removesuffix("numpy"))' + ], check: true).stdout().strip() + +# TODO: This is a hack due to gh-25135, where cython may not find the right +# __init__.pyd file. +add_project_arguments('-I', npy_path, language : 'cython') + +py.extension_module( + 'checks', + 'checks.pyx', + install: false, + c_args: [ + '-DNPY_NO_DEPRECATED_API=0', # Cython still uses old NumPy C API + # Require 1.25+ to test datetime additions + '-DNPY_TARGET_VERSION=NPY_2_0_API_VERSION', + ], + include_directories: [npy_include_path], + cython_args: cython_args, +) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/test_nep50_promotions.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/test_nep50_promotions.py new file mode 100644 index 0000000000000000000000000000000000000000..8d9d9e63ce38107a1ba3e6eac9d5feec34dc49a1 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/test_nep50_promotions.py @@ -0,0 +1,287 @@ +""" +This file adds basic tests to test the NEP 50 style promotion compatibility +mode. Most of these test are likely to be simply deleted again once NEP 50 +is adopted in the main test suite. A few may be moved elsewhere. +""" + +import operator + +import hypothesis +import pytest +from hypothesis import strategies + +import numpy as np +from numpy.testing import IS_WASM, assert_array_equal + + +@pytest.mark.skipif(IS_WASM, reason="wasm doesn't have support for fp errors") +def test_nep50_examples(): + res = np.uint8(1) + 2 + assert res.dtype == np.uint8 + + res = np.array([1], np.uint8) + np.int64(1) + assert res.dtype == np.int64 + + res = np.array([1], np.uint8) + np.array(1, dtype=np.int64) + assert res.dtype == np.int64 + + with pytest.warns(RuntimeWarning, match="overflow"): + res = np.uint8(100) + 200 + assert res.dtype == np.uint8 + + with pytest.warns(RuntimeWarning, match="overflow"): + res = np.float32(1) + 3e100 + + assert np.isinf(res) + assert res.dtype == np.float32 + + res = np.array([0.1], np.float32) == np.float64(0.1) + assert res[0] == False + + res = np.array([0.1], np.float32) + np.float64(0.1) + assert res.dtype == np.float64 + + res = np.array([1.], np.float32) + np.int64(3) + assert res.dtype == np.float64 + + +@pytest.mark.parametrize("dtype", np.typecodes["AllInteger"]) +def test_nep50_weak_integers(dtype): + # Avoids warning (different code path for scalars) + scalar_type = np.dtype(dtype).type + + maxint = int(np.iinfo(dtype).max) + + with np.errstate(over="warn"): + with pytest.warns(RuntimeWarning): + res = scalar_type(100) + maxint + assert res.dtype == dtype + + # Array operations are not expected to warn, but should give the same + # result dtype. + res = np.array(100, dtype=dtype) + maxint + assert res.dtype == dtype + + +@pytest.mark.parametrize("dtype", np.typecodes["AllFloat"]) +def test_nep50_weak_integers_with_inexact(dtype): + # Avoids warning (different code path for scalars) + scalar_type = np.dtype(dtype).type + + too_big_int = int(np.finfo(dtype).max) * 2 + + if dtype in "dDG": + # These dtypes currently convert to Python float internally, which + # raises an OverflowError, while the other dtypes overflow to inf. + # NOTE: It may make sense to normalize the behavior! + with pytest.raises(OverflowError): + scalar_type(1) + too_big_int + + with pytest.raises(OverflowError): + np.array(1, dtype=dtype) + too_big_int + else: + # NumPy uses (or used) `int -> string -> longdouble` for the + # conversion. But Python may refuse `str(int)` for huge ints. + # In that case, RuntimeWarning would be correct, but conversion + # fails earlier (seems to happen on 32bit linux, possibly only debug). + if dtype in "gG": + try: + str(too_big_int) + except ValueError: + pytest.skip("`huge_int -> string -> longdouble` failed") + + # Otherwise, we overflow to infinity: + with pytest.warns(RuntimeWarning): + res = scalar_type(1) + too_big_int + assert res.dtype == dtype + assert res == np.inf + + with pytest.warns(RuntimeWarning): + # We force the dtype here, since windows may otherwise pick the + # double instead of the longdouble loop. That leads to slightly + # different results (conversion of the int fails as above). + res = np.add(np.array(1, dtype=dtype), too_big_int, dtype=dtype) + assert res.dtype == dtype + assert res == np.inf + + +@pytest.mark.parametrize("op", [operator.add, operator.pow]) +def test_weak_promotion_scalar_path(op): + # Some additional paths exercising the weak scalars. + + # Integer path: + res = op(np.uint8(3), 5) + assert res == op(3, 5) + assert res.dtype == np.uint8 or res.dtype == bool # noqa: PLR1714 + + with pytest.raises(OverflowError): + op(np.uint8(3), 1000) + + # Float path: + res = op(np.float32(3), 5.) + assert res == op(3., 5.) + assert res.dtype == np.float32 or res.dtype == bool # noqa: PLR1714 + + +def test_nep50_complex_promotion(): + with pytest.warns(RuntimeWarning, match=".*overflow"): + res = np.complex64(3) + complex(2**300) + + assert type(res) == np.complex64 + + +def test_nep50_integer_conversion_errors(): + # Implementation for error paths is mostly missing (as of writing) + with pytest.raises(OverflowError, match=".*uint8"): + np.array([1], np.uint8) + 300 + + with pytest.raises(OverflowError, match=".*uint8"): + np.uint8(1) + 300 + + # Error message depends on platform (maybe unsigned int or unsigned long) + with pytest.raises(OverflowError, + match="Python integer -1 out of bounds for uint8"): + np.uint8(1) + -1 + + +def test_nep50_with_axisconcatenator(): + # Concatenate/r_ does not promote, so this has to error: + with pytest.raises(OverflowError): + np.r_[np.arange(5, dtype=np.int8), 255] + + +@pytest.mark.parametrize("ufunc", [np.add, np.power]) +def test_nep50_huge_integers(ufunc): + # Very large integers are complicated, because they go to uint64 or + # object dtype. This tests covers a few possible paths. + with pytest.raises(OverflowError): + ufunc(np.int64(0), 2**63) # 2**63 too large for int64 + + with pytest.raises(OverflowError): + ufunc(np.uint64(0), 2**64) # 2**64 cannot be represented by uint64 + + # However, 2**63 can be represented by the uint64 (and that is used): + res = ufunc(np.uint64(1), 2**63) + + assert res.dtype == np.uint64 + assert res == ufunc(1, 2**63, dtype=object) + + # The following paths fail to warn correctly about the change: + with pytest.raises(OverflowError): + ufunc(np.int64(1), 2**63) # np.array(2**63) would go to uint + + with pytest.raises(OverflowError): + ufunc(np.int64(1), 2**100) # np.array(2**100) would go to object + + # This would go to object and thus a Python float, not a NumPy one: + res = ufunc(1.0, 2**100) + assert isinstance(res, np.float64) + + +def test_nep50_in_concat_and_choose(): + res = np.concatenate([np.float32(1), 1.], axis=None) + assert res.dtype == "float32" + + res = np.choose(1, [np.float32(1), 1.]) + assert res.dtype == "float32" + + +@pytest.mark.parametrize("expected,dtypes,optional_dtypes", [ + (np.float32, [np.float32], + [np.float16, 0.0, np.uint16, np.int16, np.int8, 0]), + (np.complex64, [np.float32, 0j], + [np.float16, 0.0, np.uint16, np.int16, np.int8, 0]), + (np.float32, [np.int16, np.uint16, np.float16], + [np.int8, np.uint8, np.float32, 0., 0]), + (np.int32, [np.int16, np.uint16], + [np.int8, np.uint8, 0, np.bool]), + ]) +@hypothesis.given(data=strategies.data()) +def test_expected_promotion(expected, dtypes, optional_dtypes, data): + # Sample randomly while ensuring "dtypes" is always present: + optional = data.draw(strategies.lists( + strategies.sampled_from(dtypes + optional_dtypes))) + all_dtypes = dtypes + optional + dtypes_sample = data.draw(strategies.permutations(all_dtypes)) + + res = np.result_type(*dtypes_sample) + assert res == expected + + +@pytest.mark.parametrize("sctype", + [np.int8, np.int16, np.int32, np.int64, + np.uint8, np.uint16, np.uint32, np.uint64]) +@pytest.mark.parametrize("other_val", + [-2 * 100, -1, 0, 9, 10, 11, 2**63, 2 * 100]) +@pytest.mark.parametrize("comp", + [operator.eq, operator.ne, operator.le, operator.lt, + operator.ge, operator.gt]) +def test_integer_comparison(sctype, other_val, comp): + # Test that comparisons with integers (especially out-of-bound) ones + # works correctly. + val_obj = 10 + val = sctype(val_obj) + # Check that the scalar behaves the same as the python int: + assert comp(10, other_val) == comp(val, other_val) + assert comp(val, other_val) == comp(10, other_val) + # Except for the result type: + assert type(comp(val, other_val)) is np.bool + + # Check that the integer array and object array behave the same: + val_obj = np.array([10, 10], dtype=object) + val = val_obj.astype(sctype) + assert_array_equal(comp(val_obj, other_val), comp(val, other_val)) + assert_array_equal(comp(other_val, val_obj), comp(other_val, val)) + + +@pytest.mark.parametrize("arr", [ + np.ones((100, 100), dtype=np.uint8)[::2], # not trivially iterable + np.ones(20000, dtype=">u4"), # cast and >buffersize + np.ones(100, dtype=">u4"), # fast path compatible with cast +]) +def test_integer_comparison_with_cast(arr): + # Similar to above, but mainly test a few cases that cover the slow path + # the test is limited to unsigned ints and -1 for simplicity. + res = arr >= -1 + assert_array_equal(res, np.ones_like(arr, dtype=bool)) + res = arr < -1 + assert_array_equal(res, np.zeros_like(arr, dtype=bool)) + + +@pytest.mark.parametrize("comp", + [np.equal, np.not_equal, np.less_equal, np.less, + np.greater_equal, np.greater]) +def test_integer_integer_comparison(comp): + # Test that the NumPy comparison ufuncs work with large Python integers + assert comp(2**200, -2**200) == comp(2**200, -2**200, dtype=object) + + +def create_with_scalar(sctype, value): + return sctype(value) + + +def create_with_array(sctype, value): + return np.array([value], dtype=sctype) + + +@pytest.mark.parametrize("sctype", + [np.int8, np.int16, np.int32, np.int64, + np.uint8, np.uint16, np.uint32, np.uint64]) +@pytest.mark.parametrize("create", [create_with_scalar, create_with_array]) +def test_oob_creation(sctype, create): + iinfo = np.iinfo(sctype) + + with pytest.raises(OverflowError): + create(sctype, iinfo.min - 1) + + with pytest.raises(OverflowError): + create(sctype, iinfo.max + 1) + + with pytest.raises(OverflowError): + create(sctype, str(iinfo.min - 1)) + + with pytest.raises(OverflowError): + create(sctype, str(iinfo.max + 1)) + + assert create(sctype, iinfo.min) == iinfo.min + assert create(sctype, iinfo.max) == iinfo.max diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/test_stringdtype.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/test_stringdtype.py new file mode 100644 index 0000000000000000000000000000000000000000..492894087aa91b55ca75b3c5e33ab92bd42e54e9 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_core/tests/test_stringdtype.py @@ -0,0 +1,1855 @@ +import copy +import itertools +import os +import pickle +import string +import sys +import tempfile + +import pytest + +import numpy as np +from numpy._core.tests._natype import pd_NA +from numpy.dtypes import StringDType +from numpy.testing import IS_PYPY, assert_array_equal + + +def random_unicode_string_list(): + """Returns an array of 10 100-character strings containing random text""" + chars = list(string.ascii_letters + string.digits) + chars = np.array(chars, dtype="U1") + ret = np.random.choice(chars, size=100 * 10, replace=True) + return ret.view("U100") + + +def get_dtype(na_object, coerce=True): + """Helper to work around pd_NA boolean behavior""" + # explicit is check for pd_NA because != with pd_NA returns pd_NA + if na_object is pd_NA or na_object != "unset": + return np.dtypes.StringDType(na_object=na_object, coerce=coerce) + else: + return np.dtypes.StringDType(coerce=coerce) + + +@pytest.fixture(params=[True, False]) +def coerce(request): + """Coerce input to strings or raise an error for non-string input""" + return request.param + + +@pytest.fixture( + params=["unset", None, pd_NA, np.nan, float("nan"), "__nan__"], + ids=["unset", "None", "pandas.NA", "np.nan", "float('nan')", "string nan"], +) +def na_object(request): + """Possible values for the missing data sentinel""" + return request.param + + +@pytest.fixture() +def dtype(na_object, coerce): + """Cartesian project of missing data sentinel and string coercion options""" + return get_dtype(na_object, coerce) + +@pytest.fixture +def string_list(): + """Mix of short and long strings, some with unicode, some without""" + return ["abc", "def", "ghi" * 10, "A¢☃€ 😊" * 100, "Abc" * 1000, "DEF"] + + +@pytest.fixture(params=[True, False]) +def coerce2(request): + """Second copy of the coerce fixture for tests that need two instances""" + return request.param + + +@pytest.fixture( + params=["unset", None, pd_NA, np.nan, float("nan"), "__nan__"], + ids=["unset", "None", "pandas.NA", "np.nan", "float('nan')", "string nan"], +) +def na_object2(request): + """Second copy of the na_object fixture for tests that need two instances""" + return request.param + + +@pytest.fixture() +def dtype2(na_object2, coerce2): + """Second copy of the dtype fixture for tests that need two instances""" + # explicit is check for pd_NA because != with pd_NA returns pd_NA + if na_object2 is pd_NA or na_object2 != "unset": + return StringDType(na_object=na_object2, coerce=coerce2) + else: + return StringDType(coerce=coerce2) + + +def test_dtype_creation(): + hashes = set() + dt = StringDType() + assert not hasattr(dt, "na_object") and dt.coerce is True + hashes.add(hash(dt)) + + dt = StringDType(na_object=None) + assert dt.na_object is None and dt.coerce is True + hashes.add(hash(dt)) + + dt = StringDType(coerce=False) + assert not hasattr(dt, "na_object") and dt.coerce is False + hashes.add(hash(dt)) + + dt = StringDType(na_object=None, coerce=False) + assert dt.na_object is None and dt.coerce is False + hashes.add(hash(dt)) + + assert len(hashes) == 4 + + dt = np.dtype("T") + assert dt == StringDType() + assert dt.kind == "T" + assert dt.char == "T" + + hashes.add(hash(dt)) + assert len(hashes) == 4 + + +def test_dtype_equality(dtype): + assert dtype == dtype + for ch in "SU": + assert dtype != np.dtype(ch) + assert dtype != np.dtype(f"{ch}8") + + +def test_dtype_repr(dtype): + if not hasattr(dtype, "na_object") and dtype.coerce: + assert repr(dtype) == "StringDType()" + elif dtype.coerce: + assert repr(dtype) == f"StringDType(na_object={dtype.na_object!r})" + elif not hasattr(dtype, "na_object"): + assert repr(dtype) == "StringDType(coerce=False)" + else: + assert ( + repr(dtype) + == f"StringDType(na_object={dtype.na_object!r}, coerce=False)" + ) + + +def test_create_with_na(dtype): + if not hasattr(dtype, "na_object"): + pytest.skip("does not have an na object") + na_val = dtype.na_object + string_list = ["hello", na_val, "world"] + arr = np.array(string_list, dtype=dtype) + assert str(arr) == "[" + " ".join([repr(s) for s in string_list]) + "]" + assert arr[1] is dtype.na_object + + +@pytest.mark.parametrize("i", list(range(5))) +def test_set_replace_na(i): + # Test strings of various lengths can be set to NaN and then replaced. + s_empty = "" + s_short = "0123456789" + s_medium = "abcdefghijklmnopqrstuvwxyz" + s_long = "-=+" * 100 + strings = [s_medium, s_empty, s_short, s_medium, s_long] + a = np.array(strings, StringDType(na_object=np.nan)) + for s in [a[i], s_medium + s_short, s_short, s_empty, s_long]: + a[i] = np.nan + assert np.isnan(a[i]) + a[i] = s + assert a[i] == s + assert_array_equal(a, strings[:i] + [s] + strings[i + 1:]) + + +def test_null_roundtripping(): + data = ["hello\0world", "ABC\0DEF\0\0"] + arr = np.array(data, dtype="T") + assert data[0] == arr[0] + assert data[1] == arr[1] + + +def test_string_too_large_error(): + arr = np.array(["a", "b", "c"], dtype=StringDType()) + with pytest.raises(OverflowError): + arr * (sys.maxsize + 1) + + +@pytest.mark.parametrize( + "data", + [ + ["abc", "def", "ghi"], + ["🤣", "📵", "😰"], + ["🚜", "🙃", "😾"], + ["😹", "🚠", "🚌"], + ], +) +def test_array_creation_utf8(dtype, data): + arr = np.array(data, dtype=dtype) + assert str(arr) == "[" + " ".join(["'" + str(d) + "'" for d in data]) + "]" + assert arr.dtype == dtype + + +@pytest.mark.parametrize( + "data", + [ + [1, 2, 3], + [b"abc", b"def", b"ghi"], + [object, object, object], + ], +) +def test_scalars_string_conversion(data, dtype): + try: + str_vals = [str(d.decode('utf-8')) for d in data] + except AttributeError: + str_vals = [str(d) for d in data] + if dtype.coerce: + assert_array_equal( + np.array(data, dtype=dtype), + np.array(str_vals, dtype=dtype), + ) + else: + with pytest.raises(ValueError): + np.array(data, dtype=dtype) + + +@pytest.mark.parametrize( + ("strings"), + [ + ["this", "is", "an", "array"], + ["€", "", "😊"], + ["A¢☃€ 😊", " A☃€¢😊", "☃€😊 A¢", "😊☃A¢ €"], + ], +) +def test_self_casts(dtype, dtype2, strings): + if hasattr(dtype, "na_object"): + strings = strings + [dtype.na_object] + elif hasattr(dtype2, "na_object"): + strings = strings + [""] + arr = np.array(strings, dtype=dtype) + newarr = arr.astype(dtype2) + + if hasattr(dtype, "na_object") and not hasattr(dtype2, "na_object"): + assert newarr[-1] == str(dtype.na_object) + with pytest.raises(TypeError): + arr.astype(dtype2, casting="safe") + elif hasattr(dtype, "na_object") and hasattr(dtype2, "na_object"): + assert newarr[-1] is dtype2.na_object + arr.astype(dtype2, casting="safe") + elif hasattr(dtype2, "na_object"): + assert newarr[-1] == "" + arr.astype(dtype2, casting="safe") + else: + arr.astype(dtype2, casting="safe") + + if hasattr(dtype, "na_object") and hasattr(dtype2, "na_object"): + na1 = dtype.na_object + na2 = dtype2.na_object + if (na1 is not na2 and + # check for pd_NA first because bool(pd_NA) is an error + ((na1 is pd_NA or na2 is pd_NA) or + # the second check is a NaN check, spelled this way + # to avoid errors from math.isnan and np.isnan + (na1 != na2 and not (na1 != na1 and na2 != na2)))): + with pytest.raises(TypeError): + arr[:-1] == newarr[:-1] + return + assert_array_equal(arr[:-1], newarr[:-1]) + + +@pytest.mark.parametrize( + ("strings"), + [ + ["this", "is", "an", "array"], + ["€", "", "😊"], + ["A¢☃€ 😊", " A☃€¢😊", "☃€😊 A¢", "😊☃A¢ €"], + ], +) +class TestStringLikeCasts: + def test_unicode_casts(self, dtype, strings): + arr = np.array(strings, dtype=np.str_).astype(dtype) + expected = np.array(strings, dtype=dtype) + assert_array_equal(arr, expected) + + arr_as_U8 = expected.astype("U8") + assert_array_equal(arr_as_U8, np.array(strings, dtype="U8")) + assert_array_equal(arr_as_U8.astype(dtype), arr) + arr_as_U3 = expected.astype("U3") + assert_array_equal(arr_as_U3, np.array(strings, dtype="U3")) + assert_array_equal( + arr_as_U3.astype(dtype), + np.array([s[:3] for s in strings], dtype=dtype), + ) + + def test_void_casts(self, dtype, strings): + sarr = np.array(strings, dtype=dtype) + utf8_bytes = [s.encode("utf-8") for s in strings] + void_dtype = f"V{max(len(s) for s in utf8_bytes)}" + varr = np.array(utf8_bytes, dtype=void_dtype) + assert_array_equal(varr, sarr.astype(void_dtype)) + assert_array_equal(varr.astype(dtype), sarr) + + def test_bytes_casts(self, dtype, strings): + sarr = np.array(strings, dtype=dtype) + try: + utf8_bytes = [s.encode("ascii") for s in strings] + bytes_dtype = f"S{max(len(s) for s in utf8_bytes)}" + barr = np.array(utf8_bytes, dtype=bytes_dtype) + assert_array_equal(barr, sarr.astype(bytes_dtype)) + assert_array_equal(barr.astype(dtype), sarr) + if dtype.coerce: + barr = np.array(utf8_bytes, dtype=dtype) + assert_array_equal(barr, sarr) + barr = np.array(utf8_bytes, dtype="O") + assert_array_equal(barr.astype(dtype), sarr) + else: + with pytest.raises(ValueError): + np.array(utf8_bytes, dtype=dtype) + except UnicodeEncodeError: + with pytest.raises(UnicodeEncodeError): + sarr.astype("S20") + + +def test_additional_unicode_cast(dtype): + string_list = random_unicode_string_list() + arr = np.array(string_list, dtype=dtype) + # test that this short-circuits correctly + assert_array_equal(arr, arr.astype(arr.dtype)) + # tests the casts via the comparison promoter + assert_array_equal(arr, arr.astype(string_list.dtype)) + + +def test_insert_scalar(dtype, string_list): + """Test that inserting a scalar works.""" + arr = np.array(string_list, dtype=dtype) + scalar_instance = "what" + arr[1] = scalar_instance + assert_array_equal( + arr, + np.array(string_list[:1] + ["what"] + string_list[2:], dtype=dtype), + ) + + +comparison_operators = [ + np.equal, + np.not_equal, + np.greater, + np.greater_equal, + np.less, + np.less_equal, +] + + +@pytest.mark.parametrize("op", comparison_operators) +@pytest.mark.parametrize("o_dtype", [np.str_, object, StringDType()]) +def test_comparisons(string_list, dtype, op, o_dtype): + sarr = np.array(string_list, dtype=dtype) + oarr = np.array(string_list, dtype=o_dtype) + + # test that comparison operators work + res = op(sarr, sarr) + ores = op(oarr, oarr) + # test that promotion works as well + orres = op(sarr, oarr) + olres = op(oarr, sarr) + + assert_array_equal(res, ores) + assert_array_equal(res, orres) + assert_array_equal(res, olres) + + # test we get the correct answer for unequal length strings + sarr2 = np.array([s + "2" for s in string_list], dtype=dtype) + oarr2 = np.array([s + "2" for s in string_list], dtype=o_dtype) + + res = op(sarr, sarr2) + ores = op(oarr, oarr2) + olres = op(oarr, sarr2) + orres = op(sarr, oarr2) + + assert_array_equal(res, ores) + assert_array_equal(res, olres) + assert_array_equal(res, orres) + + res = op(sarr2, sarr) + ores = op(oarr2, oarr) + olres = op(oarr2, sarr) + orres = op(sarr2, oarr) + + assert_array_equal(res, ores) + assert_array_equal(res, olres) + assert_array_equal(res, orres) + + +def test_isnan(dtype, string_list): + if not hasattr(dtype, "na_object"): + pytest.skip("no na support") + sarr = np.array(string_list + [dtype.na_object], dtype=dtype) + is_nan = isinstance(dtype.na_object, float) and np.isnan(dtype.na_object) + bool_errors = 0 + try: + bool(dtype.na_object) + except TypeError: + bool_errors = 1 + if is_nan or bool_errors: + # isnan is only true when na_object is a NaN + assert_array_equal( + np.isnan(sarr), + np.array([0] * len(string_list) + [1], dtype=np.bool), + ) + else: + assert not np.any(np.isnan(sarr)) + + +def test_pickle(dtype, string_list): + arr = np.array(string_list, dtype=dtype) + + with tempfile.NamedTemporaryFile("wb", delete=False) as f: + pickle.dump([arr, dtype], f) + + with open(f.name, "rb") as f: + res = pickle.load(f) + + assert_array_equal(res[0], arr) + assert res[1] == dtype + + os.remove(f.name) + + +def test_stdlib_copy(dtype, string_list): + arr = np.array(string_list, dtype=dtype) + + assert_array_equal(copy.copy(arr), arr) + assert_array_equal(copy.deepcopy(arr), arr) + + +@pytest.mark.parametrize( + "strings", + [ + ["left", "right", "leftovers", "righty", "up", "down"], + [ + "left" * 10, + "right" * 10, + "leftovers" * 10, + "righty" * 10, + "up" * 10, + ], + ["🤣🤣", "🤣", "📵", "😰"], + ["🚜", "🙃", "😾"], + ["😹", "🚠", "🚌"], + ["A¢☃€ 😊", " A☃€¢😊", "☃€😊 A¢", "😊☃A¢ €"], + ], +) +def test_sort(dtype, strings): + """Test that sorting matches python's internal sorting.""" + + def test_sort(strings, arr_sorted): + arr = np.array(strings, dtype=dtype) + na_object = getattr(arr.dtype, "na_object", "") + if na_object is None and None in strings: + with pytest.raises( + ValueError, + match="Cannot compare null that is not a nan-like value", + ): + np.argsort(arr) + argsorted = None + elif na_object is pd_NA or na_object != '': + argsorted = None + else: + argsorted = np.argsort(arr) + np.random.default_rng().shuffle(arr) + if na_object is None and None in strings: + with pytest.raises( + ValueError, + match="Cannot compare null that is not a nan-like value", + ): + arr.sort() + else: + arr.sort() + assert np.array_equal(arr, arr_sorted, equal_nan=True) + if argsorted is not None: + assert np.array_equal(argsorted, np.argsort(strings)) + + # make a copy so we don't mutate the lists in the fixture + strings = strings.copy() + arr_sorted = np.array(sorted(strings), dtype=dtype) + test_sort(strings, arr_sorted) + + if not hasattr(dtype, "na_object"): + return + + # make sure NAs get sorted to the end of the array and string NAs get + # sorted like normal strings + strings.insert(0, dtype.na_object) + strings.insert(2, dtype.na_object) + # can't use append because doing that with NA converts + # the result to object dtype + if not isinstance(dtype.na_object, str): + arr_sorted = np.array( + arr_sorted.tolist() + [dtype.na_object, dtype.na_object], + dtype=dtype, + ) + else: + arr_sorted = np.array(sorted(strings), dtype=dtype) + + test_sort(strings, arr_sorted) + + +@pytest.mark.parametrize( + "strings", + [ + ["A¢☃€ 😊", " A☃€¢😊", "☃€😊 A¢", "😊☃A¢ €"], + ["A¢☃€ 😊", "", " ", " "], + ["", "a", "😸", "ááðfáíóåéë"], + ], +) +def test_nonzero(strings, na_object): + dtype = get_dtype(na_object) + arr = np.array(strings, dtype=dtype) + is_nonzero = np.array( + [i for i, item in enumerate(strings) if len(item) != 0]) + assert_array_equal(arr.nonzero()[0], is_nonzero) + + if na_object is not pd_NA and na_object == 'unset': + return + + strings_with_na = np.array(strings + [na_object], dtype=dtype) + is_nan = np.isnan(np.array([dtype.na_object], dtype=dtype))[0] + + if is_nan: + assert strings_with_na.nonzero()[0][-1] == 4 + else: + assert strings_with_na.nonzero()[0][-1] == 3 + + # check that the casting to bool and nonzero give consistent results + assert_array_equal(strings_with_na[strings_with_na.nonzero()], + strings_with_na[strings_with_na.astype(bool)]) + + +def test_where(string_list, na_object): + dtype = get_dtype(na_object) + a = np.array(string_list, dtype=dtype) + b = a[::-1] + res = np.where([True, False, True, False, True, False], a, b) + assert_array_equal(res, [a[0], b[1], a[2], b[3], a[4], b[5]]) + + +def test_fancy_indexing(string_list): + sarr = np.array(string_list, dtype="T") + assert_array_equal(sarr, sarr[np.arange(sarr.shape[0])]) + + inds = [ + [True, True], + [0, 1], + ..., + np.array([0, 1], dtype='uint8'), + ] + + lops = [ + ['a' * 25, 'b' * 25], + ['', ''], + ['hello', 'world'], + ['hello', 'world' * 25], + ] + + # see gh-27003 and gh-27053 + for ind in inds: + for lop in lops: + a = np.array(lop, dtype="T") + assert_array_equal(a[ind], a) + rop = ['d' * 25, 'e' * 25] + for b in [rop, np.array(rop, dtype="T")]: + a[ind] = b + assert_array_equal(a, b) + assert a[0] == 'd' * 25 + + # see gh-29279 + data = [ + ["AAAAAAAAAAAAAAAAA"], + ["BBBBBBBBBBBBBBBBBBBBBBBBBBBBB"], + ["CCCCCCCCCCCCCCCCC"], + ["DDDDDDDDDDDDDDDDD"], + ] + sarr = np.array(data, dtype=np.dtypes.StringDType()) + uarr = np.array(data, dtype="U30") + for ind in [[0], [1], [2], [3], [[0, 0]], [[1, 1, 3]], [[1, 1]]]: + assert_array_equal(sarr[ind], uarr[ind]) + + +def test_flatiter_indexing(): + # see gh-29659 + arr = np.array(['hello', 'world'], dtype='T') + arr.flat[:] = 9223372036854775 + assert_array_equal(arr, np.array([9223372036854775] * 2, dtype='T')) + + +def test_creation_functions(): + assert_array_equal(np.zeros(3, dtype="T"), ["", "", ""]) + assert_array_equal(np.empty(3, dtype="T"), ["", "", ""]) + + assert np.zeros(3, dtype="T")[0] == "" + assert np.empty(3, dtype="T")[0] == "" + + +def test_concatenate(string_list): + sarr = np.array(string_list, dtype="T") + sarr_cat = np.array(string_list + string_list, dtype="T") + + assert_array_equal(np.concatenate([sarr], axis=0), sarr) + + +def test_resize_method(string_list): + sarr = np.array(string_list, dtype="T") + if IS_PYPY: + sarr.resize(len(string_list) + 3, refcheck=False) + else: + sarr.resize(len(string_list) + 3) + assert_array_equal(sarr, np.array(string_list + [''] * 3, dtype="T")) + + +def test_create_with_copy_none(string_list): + arr = np.array(string_list, dtype=StringDType()) + # create another stringdtype array with an arena that has a different + # in-memory layout than the first array + arr_rev = np.array(string_list[::-1], dtype=StringDType()) + + # this should create a copy and the resulting array + # shouldn't share an allocator or arena with arr_rev, despite + # explicitly passing arr_rev.dtype + arr_copy = np.array(arr, copy=None, dtype=arr_rev.dtype) + np.testing.assert_array_equal(arr, arr_copy) + assert arr_copy.base is None + + with pytest.raises(ValueError, match="Unable to avoid copy"): + np.array(arr, copy=False, dtype=arr_rev.dtype) + + # because we're using arr's dtype instance, the view is safe + arr_view = np.array(arr, copy=None, dtype=arr.dtype) + np.testing.assert_array_equal(arr, arr) + np.testing.assert_array_equal(arr_view[::-1], arr_rev) + assert arr_view is arr + + +def test_astype_copy_false(): + orig_dt = StringDType() + arr = np.array(["hello", "world"], dtype=StringDType()) + assert not arr.astype(StringDType(coerce=False), copy=False).dtype.coerce + + assert arr.astype(orig_dt, copy=False).dtype is orig_dt + +@pytest.mark.parametrize( + "strings", + [ + ["left", "right", "leftovers", "righty", "up", "down"], + ["🤣🤣", "🤣", "📵", "😰"], + ["🚜", "🙃", "😾"], + ["😹", "🚠", "🚌"], + ["A¢☃€ 😊", " A☃€¢😊", "☃€😊 A¢", "😊☃A¢ €"], + ], +) +def test_argmax(strings): + """Test that argmax/argmin matches what python calculates.""" + arr = np.array(strings, dtype="T") + assert np.argmax(arr) == strings.index(max(strings)) + assert np.argmin(arr) == strings.index(min(strings)) + + +@pytest.mark.parametrize( + "arrfunc,expected", + [ + [np.sort, None], + [np.nonzero, (np.array([], dtype=np.int_),)], + [np.argmax, 0], + [np.argmin, 0], + ], +) +def test_arrfuncs_zeros(arrfunc, expected): + arr = np.zeros(10, dtype="T") + result = arrfunc(arr) + if expected is None: + expected = arr + assert_array_equal(result, expected, strict=True) + + +@pytest.mark.parametrize( + ("strings", "cast_answer", "any_answer", "all_answer"), + [ + [["hello", "world"], [True, True], True, True], + [["", ""], [False, False], False, False], + [["hello", ""], [True, False], True, False], + [["", "world"], [False, True], True, False], + ], +) +def test_cast_to_bool(strings, cast_answer, any_answer, all_answer): + sarr = np.array(strings, dtype="T") + assert_array_equal(sarr.astype("bool"), cast_answer) + + assert np.any(sarr) == any_answer + assert np.all(sarr) == all_answer + + +@pytest.mark.parametrize( + ("strings", "cast_answer"), + [ + [[True, True], ["True", "True"]], + [[False, False], ["False", "False"]], + [[True, False], ["True", "False"]], + [[False, True], ["False", "True"]], + ], +) +def test_cast_from_bool(strings, cast_answer): + barr = np.array(strings, dtype=bool) + assert_array_equal(barr.astype("T"), np.array(cast_answer, dtype="T")) + + +@pytest.mark.parametrize("bitsize", [8, 16, 32, 64]) +@pytest.mark.parametrize("signed", [True, False]) +def test_sized_integer_casts(bitsize, signed): + idtype = f"int{bitsize}" + if signed: + inp = [-(2**p - 1) for p in reversed(range(bitsize - 1))] + inp += [2**p - 1 for p in range(1, bitsize - 1)] + else: + idtype = "u" + idtype + inp = [2**p - 1 for p in range(bitsize)] + ainp = np.array(inp, dtype=idtype) + assert_array_equal(ainp, ainp.astype("T").astype(idtype)) + + # safe casting works + ainp.astype("T", casting="safe") + + with pytest.raises(TypeError): + ainp.astype("T").astype(idtype, casting="safe") + + oob = [str(2**bitsize), str(-(2**bitsize))] + with pytest.raises(OverflowError): + np.array(oob, dtype="T").astype(idtype) + + with pytest.raises(ValueError): + np.array(["1", np.nan, "3"], + dtype=StringDType(na_object=np.nan)).astype(idtype) + + +@pytest.mark.parametrize("typename", ["byte", "short", "int", "longlong"]) +@pytest.mark.parametrize("signed", ["", "u"]) +def test_unsized_integer_casts(typename, signed): + idtype = f"{signed}{typename}" + + inp = [1, 2, 3, 4] + ainp = np.array(inp, dtype=idtype) + assert_array_equal(ainp, ainp.astype("T").astype(idtype)) + + +@pytest.mark.parametrize( + "typename", + [ + pytest.param( + "longdouble", + marks=pytest.mark.xfail( + np.dtypes.LongDoubleDType() != np.dtypes.Float64DType(), + reason="numpy lacks an ld2a implementation", + strict=True, + ), + ), + "float64", + "float32", + "float16", + ], +) +def test_float_casts(typename): + inp = [1.1, 2.8, -3.2, 2.7e4] + ainp = np.array(inp, dtype=typename) + assert_array_equal(ainp, ainp.astype("T").astype(typename)) + + inp = [0.1] + sres = np.array(inp, dtype=typename).astype("T") + res = sres.astype(typename) + assert_array_equal(np.array(inp, dtype=typename), res) + assert sres[0] == "0.1" + + if typename == "longdouble": + # let's not worry about platform-dependent rounding of longdouble + return + + fi = np.finfo(typename) + + inp = [1e-324, fi.smallest_subnormal, -1e-324, -fi.smallest_subnormal] + eres = [0, fi.smallest_subnormal, -0, -fi.smallest_subnormal] + res = np.array(inp, dtype=typename).astype("T").astype(typename) + assert_array_equal(eres, res) + + inp = [2e308, fi.max, -2e308, fi.min] + eres = [np.inf, fi.max, -np.inf, fi.min] + res = np.array(inp, dtype=typename).astype("T").astype(typename) + assert_array_equal(eres, res) + + +def test_float_nan_cast_na_object(): + # gh-28157 + dt = np.dtypes.StringDType(na_object=np.nan) + arr1 = np.full((1,), fill_value=np.nan, dtype=dt) + arr2 = np.full_like(arr1, fill_value=np.nan) + + assert arr1.item() is np.nan + assert arr2.item() is np.nan + + inp = [1.2, 2.3, np.nan] + arr = np.array(inp).astype(dt) + assert arr[2] is np.nan + assert arr[0] == '1.2' + + +@pytest.mark.parametrize( + "typename", + [ + "csingle", + "cdouble", + pytest.param( + "clongdouble", + marks=pytest.mark.xfail( + np.dtypes.CLongDoubleDType() != np.dtypes.Complex128DType(), + reason="numpy lacks an ld2a implementation", + strict=True, + ), + ), + ], +) +def test_cfloat_casts(typename): + inp = [1.1 + 1.1j, 2.8 + 2.8j, -3.2 - 3.2j, 2.7e4 + 2.7e4j] + ainp = np.array(inp, dtype=typename) + assert_array_equal(ainp, ainp.astype("T").astype(typename)) + + inp = [0.1 + 0.1j] + sres = np.array(inp, dtype=typename).astype("T") + res = sres.astype(typename) + assert_array_equal(np.array(inp, dtype=typename), res) + assert sres[0] == "(0.1+0.1j)" + + +def test_take(string_list): + sarr = np.array(string_list, dtype="T") + res = sarr.take(np.arange(len(string_list))) + assert_array_equal(sarr, res) + + # make sure it also works for out + out = np.empty(len(string_list), dtype="T") + out[0] = "hello" + res = sarr.take(np.arange(len(string_list)), out=out) + assert res is out + assert_array_equal(sarr, res) + + +@pytest.mark.parametrize("use_out", [True, False]) +@pytest.mark.parametrize( + "ufunc_name,func", + [ + ("min", min), + ("max", max), + ], +) +def test_ufuncs_minmax(string_list, ufunc_name, func, use_out): + """Test that the min/max ufuncs match Python builtin min/max behavior.""" + arr = np.array(string_list, dtype="T") + uarr = np.array(string_list, dtype=str) + res = np.array(func(string_list), dtype="T") + assert_array_equal(getattr(arr, ufunc_name)(), res) + + ufunc = getattr(np, ufunc_name + "imum") + + if use_out: + res = ufunc(arr, arr, out=arr) + else: + res = ufunc(arr, arr) + + assert_array_equal(uarr, res) + assert_array_equal(getattr(arr, ufunc_name)(), func(string_list)) + + +def test_max_regression(): + arr = np.array(['y', 'y', 'z'], dtype="T") + assert arr.max() == 'z' + + +@pytest.mark.parametrize("use_out", [True, False]) +@pytest.mark.parametrize( + "other_strings", + [ + ["abc", "def" * 500, "ghi" * 16, "🤣" * 100, "📵", "😰"], + ["🚜", "🙃", "😾", "😹", "🚠", "🚌"], + ["🥦", "¨", "⨯", "∰ ", "⨌ ", "⎶ "], + ], +) +def test_ufunc_add(dtype, string_list, other_strings, use_out): + arr1 = np.array(string_list, dtype=dtype) + arr2 = np.array(other_strings, dtype=dtype) + result = np.array([a + b for a, b in zip(arr1, arr2)], dtype=dtype) + + if use_out: + res = np.add(arr1, arr2, out=arr1) + else: + res = np.add(arr1, arr2) + + assert_array_equal(res, result) + + if not hasattr(dtype, "na_object"): + return + + is_nan = isinstance(dtype.na_object, float) and np.isnan(dtype.na_object) + is_str = isinstance(dtype.na_object, str) + bool_errors = 0 + try: + bool(dtype.na_object) + except TypeError: + bool_errors = 1 + + arr1 = np.array([dtype.na_object] + string_list, dtype=dtype) + arr2 = np.array(other_strings + [dtype.na_object], dtype=dtype) + + if is_nan or bool_errors or is_str: + res = np.add(arr1, arr2) + assert_array_equal(res[1:-1], arr1[1:-1] + arr2[1:-1]) + if not is_str: + assert res[0] is dtype.na_object and res[-1] is dtype.na_object + else: + assert res[0] == dtype.na_object + arr2[0] + assert res[-1] == arr1[-1] + dtype.na_object + else: + with pytest.raises(ValueError): + np.add(arr1, arr2) + + +def test_ufunc_add_reduce(dtype): + values = ["a", "this is a long string", "c"] + arr = np.array(values, dtype=dtype) + out = np.empty((), dtype=dtype) + + expected = np.array("".join(values), dtype=dtype) + assert_array_equal(np.add.reduce(arr), expected) + + np.add.reduce(arr, out=out) + assert_array_equal(out, expected) + + +def test_add_promoter(string_list): + arr = np.array(string_list, dtype=StringDType()) + lresult = np.array(["hello" + s for s in string_list], dtype=StringDType()) + rresult = np.array([s + "hello" for s in string_list], dtype=StringDType()) + + for op in ["hello", np.str_("hello"), np.array(["hello"])]: + assert_array_equal(op + arr, lresult) + assert_array_equal(arr + op, rresult) + + # The promoter should be able to handle things if users pass `dtype=` + res = np.add("hello", string_list, dtype=StringDType) + assert res.dtype == StringDType() + + # The promoter should not kick in if users override the input, + # which means arr is cast, this fails because of the unknown length. + with pytest.raises(TypeError, match="cannot cast dtype"): + np.add(arr, "add", signature=("U", "U", None), casting="unsafe") + + # But it must simply reject the following: + with pytest.raises(TypeError, match=".*did not contain a loop"): + np.add(arr, "add", signature=(None, "U", None)) + + with pytest.raises(TypeError, match=".*did not contain a loop"): + np.add("a", "b", signature=("U", "U", StringDType)) + + +def test_add_no_legacy_promote_with_signature(): + # Possibly misplaced, but useful to test with string DType. We check that + # if there is clearly no loop found, a stray `dtype=` doesn't break things + # Regression test for the bad error in gh-26735 + # (If legacy promotion is gone, this can be deleted...) + with pytest.raises(TypeError, match=".*did not contain a loop"): + np.add("3", 6, dtype=StringDType) + + +def test_add_promoter_reduce(): + # Exact TypeError could change, but ensure StringDtype doesn't match + with pytest.raises(TypeError, match="the resolved dtypes are not"): + np.add.reduce(np.array(["a", "b"], dtype="U")) + + # On the other hand, using `dtype=T` in the *ufunc* should work. + np.add.reduce(np.array(["a", "b"], dtype="U"), dtype=np.dtypes.StringDType) + + +def test_multiply_reduce(): + # At the time of writing (NumPy 2.0) this is very limited (and rather + # ridiculous anyway). But it works and actually makes some sense... + # (NumPy does not allow non-scalar initial values) + repeats = np.array([2, 3, 4]) + val = "school-🚌" + res = np.multiply.reduce(repeats, initial=val, dtype=np.dtypes.StringDType) + assert res == val * np.prod(repeats) + + +def test_multiply_two_string_raises(): + arr = np.array(["hello", "world"], dtype="T") + with pytest.raises(np._core._exceptions._UFuncNoLoopError): + np.multiply(arr, arr) + + +@pytest.mark.parametrize("use_out", [True, False]) +@pytest.mark.parametrize("other", [2, [2, 1, 3, 4, 1, 3]]) +@pytest.mark.parametrize( + "other_dtype", + [ + None, + "int8", + "int16", + "int32", + "int64", + "uint8", + "uint16", + "uint32", + "uint64", + "short", + "int", + "intp", + "long", + "longlong", + "ushort", + "uint", + "uintp", + "ulong", + "ulonglong", + ], +) +def test_ufunc_multiply(dtype, string_list, other, other_dtype, use_out): + """Test the two-argument ufuncs match python builtin behavior.""" + arr = np.array(string_list, dtype=dtype) + if other_dtype is not None: + other_dtype = np.dtype(other_dtype) + try: + len(other) + result = [s * o for s, o in zip(string_list, other)] + other = np.array(other) + if other_dtype is not None: + other = other.astype(other_dtype) + except TypeError: + if other_dtype is not None: + other = other_dtype.type(other) + result = [s * other for s in string_list] + + if use_out: + arr_cache = arr.copy() + lres = np.multiply(arr, other, out=arr) + assert_array_equal(lres, result) + arr[:] = arr_cache + assert lres is arr + arr *= other + assert_array_equal(arr, result) + arr[:] = arr_cache + rres = np.multiply(other, arr, out=arr) + assert rres is arr + assert_array_equal(rres, result) + else: + lres = arr * other + assert_array_equal(lres, result) + rres = other * arr + assert_array_equal(rres, result) + + if not hasattr(dtype, "na_object"): + return + + is_nan = np.isnan(np.array([dtype.na_object], dtype=dtype))[0] + is_str = isinstance(dtype.na_object, str) + bool_errors = 0 + try: + bool(dtype.na_object) + except TypeError: + bool_errors = 1 + + arr = np.array(string_list + [dtype.na_object], dtype=dtype) + + try: + len(other) + other = np.append(other, 3) + if other_dtype is not None: + other = other.astype(other_dtype) + except TypeError: + pass + + if is_nan or bool_errors or is_str: + for res in [arr * other, other * arr]: + assert_array_equal(res[:-1], result) + if not is_str: + assert res[-1] is dtype.na_object + else: + try: + assert res[-1] == dtype.na_object * other[-1] + except (IndexError, TypeError): + assert res[-1] == dtype.na_object * other + else: + with pytest.raises(TypeError): + arr * other + with pytest.raises(TypeError): + other * arr + + +def test_findlike_promoters(): + r = "Wally" + l = "Where's Wally?" + s = np.int32(3) + e = np.int8(13) + for dtypes in [("T", "U"), ("U", "T")]: + for function, answer in [ + (np.strings.index, 8), + (np.strings.endswith, True), + ]: + assert answer == function( + np.array(l, dtype=dtypes[0]), np.array(r, dtype=dtypes[1]), s, e + ) + + +def test_strip_promoter(): + arg = ["Hello!!!!", "Hello??!!"] + strip_char = "!" + answer = ["Hello", "Hello??"] + for dtypes in [("T", "U"), ("U", "T")]: + result = np.strings.strip( + np.array(arg, dtype=dtypes[0]), + np.array(strip_char, dtype=dtypes[1]) + ) + assert_array_equal(result, answer) + assert result.dtype.char == "T" + + +def test_replace_promoter(): + arg = ["Hello, planet!", "planet, Hello!"] + old = "planet" + new = "world" + answer = ["Hello, world!", "world, Hello!"] + for dtypes in itertools.product("TU", repeat=3): + if dtypes == ("U", "U", "U"): + continue + answer_arr = np.strings.replace( + np.array(arg, dtype=dtypes[0]), + np.array(old, dtype=dtypes[1]), + np.array(new, dtype=dtypes[2]), + ) + assert_array_equal(answer_arr, answer) + assert answer_arr.dtype.char == "T" + + +def test_center_promoter(): + arg = ["Hello", "planet!"] + fillchar = "/" + for dtypes in [("T", "U"), ("U", "T")]: + answer = np.strings.center( + np.array(arg, dtype=dtypes[0]), 9, np.array(fillchar, dtype=dtypes[1]) + ) + assert_array_equal(answer, ["//Hello//", "/planet!/"]) + assert answer.dtype.char == "T" + + +DATETIME_INPUT = [ + np.datetime64("1923-04-14T12:43:12"), + np.datetime64("1994-06-21T14:43:15"), + np.datetime64("2001-10-15T04:10:32"), + np.datetime64("NaT"), + np.datetime64("1995-11-25T16:02:16"), + np.datetime64("2005-01-04T03:14:12"), + np.datetime64("2041-12-03T14:05:03"), +] + + +TIMEDELTA_INPUT = [ + np.timedelta64(12358, "s"), + np.timedelta64(23, "s"), + np.timedelta64(74, "s"), + np.timedelta64("NaT"), + np.timedelta64(23, "s"), + np.timedelta64(73, "s"), + np.timedelta64(7, "s"), +] + + +@pytest.mark.parametrize( + "input_data, input_dtype", + [ + (DATETIME_INPUT, "M8[s]"), + (TIMEDELTA_INPUT, "m8[s]") + ] +) +def test_datetime_timedelta_cast(dtype, input_data, input_dtype): + + a = np.array(input_data, dtype=input_dtype) + + has_na = hasattr(dtype, "na_object") + is_str = isinstance(getattr(dtype, "na_object", None), str) + + if not has_na or is_str: + a = np.delete(a, 3) + + sa = a.astype(dtype) + ra = sa.astype(a.dtype) + + if has_na and not is_str: + assert sa[3] is dtype.na_object + assert np.isnat(ra[3]) + + assert_array_equal(a, ra) + + if has_na and not is_str: + # don't worry about comparing how NaT is converted + sa = np.delete(sa, 3) + a = np.delete(a, 3) + + if input_dtype.startswith("M"): + assert_array_equal(sa, a.astype("U")) + else: + # The timedelta to unicode cast produces strings + # that aren't round-trippable and we don't want to + # reproduce that behavior in stringdtype + assert_array_equal(sa, a.astype("int64").astype("U")) + + +def test_nat_casts(): + s = 'nat' + all_nats = itertools.product(*zip(s.upper(), s.lower())) + all_nats = list(map(''.join, all_nats)) + NaT_dt = np.datetime64('NaT') + NaT_td = np.timedelta64('NaT') + for na_object in [np._NoValue, None, np.nan, 'nat', '']: + # numpy treats empty string and all case combinations of 'nat' as NaT + dtype = StringDType(na_object=na_object) + arr = np.array([''] + all_nats, dtype=dtype) + dt_array = arr.astype('M8[s]') + td_array = arr.astype('m8[s]') + assert_array_equal(dt_array, NaT_dt) + assert_array_equal(td_array, NaT_td) + + if na_object is np._NoValue: + output_object = 'NaT' + else: + output_object = na_object + + for arr in [dt_array, td_array]: + assert_array_equal( + arr.astype(dtype), + np.array([output_object] * arr.size, dtype=dtype)) + + +def test_nat_conversion(): + for nat in [np.datetime64("NaT", "s"), np.timedelta64("NaT", "s")]: + with pytest.raises(ValueError, match="string coercion is disabled"): + np.array(["a", nat], dtype=StringDType(coerce=False)) + + +def test_growing_strings(dtype): + # growing a string leads to a heap allocation, this tests to make sure + # we do that bookkeeping correctly for all possible starting cases + data = [ + "hello", # a short string + "abcdefghijklmnopqestuvwxyz", # a medium heap-allocated string + "hello" * 200, # a long heap-allocated string + ] + + arr = np.array(data, dtype=dtype) + uarr = np.array(data, dtype=str) + + for _ in range(5): + arr = arr + arr + uarr = uarr + uarr + + assert_array_equal(arr, uarr) + + +def test_assign_medium_strings(): + # see gh-29261 + N = 9 + src = np.array( + ( + ['0' * 256] * 3 + ['0' * 255] + ['0' * 256] + ['0' * 255] + + ['0' * 256] * 2 + ['0' * 255] + ), dtype='T') + dst = np.array( + ( + ['0' * 255] + ['0' * 256] * 2 + ['0' * 255] + ['0' * 256] + + ['0' * 255] + [''] * 5 + ), dtype='T') + + dst[1:N + 1] = src + assert_array_equal(dst[1:N + 1], src) + + +UFUNC_TEST_DATA = [ + "hello" * 10, + "Ae¢☃€ 😊" * 20, + "entry\nwith\nnewlines", + "entry\twith\ttabs", +] + + +@pytest.fixture +def string_array(dtype): + return np.array(UFUNC_TEST_DATA, dtype=dtype) + + +@pytest.fixture +def unicode_array(): + return np.array(UFUNC_TEST_DATA, dtype=np.str_) + + +NAN_PRESERVING_FUNCTIONS = [ + "capitalize", + "expandtabs", + "lower", + "lstrip", + "rstrip", + "splitlines", + "strip", + "swapcase", + "title", + "upper", +] + +BOOL_OUTPUT_FUNCTIONS = [ + "isalnum", + "isalpha", + "isdigit", + "islower", + "isspace", + "istitle", + "isupper", + "isnumeric", + "isdecimal", +] + +UNARY_FUNCTIONS = [ + "str_len", + "capitalize", + "expandtabs", + "isalnum", + "isalpha", + "isdigit", + "islower", + "isspace", + "istitle", + "isupper", + "lower", + "lstrip", + "rstrip", + "splitlines", + "strip", + "swapcase", + "title", + "upper", + "isnumeric", + "isdecimal", + "isalnum", + "islower", + "istitle", + "isupper", +] + +UNIMPLEMENTED_VEC_STRING_FUNCTIONS = [ + "capitalize", + "expandtabs", + "lower", + "splitlines", + "swapcase", + "title", + "upper", +] + +ONLY_IN_NP_CHAR = [ + "join", + "split", + "rsplit", + "splitlines" +] + + +@pytest.mark.parametrize("function_name", UNARY_FUNCTIONS) +def test_unary(string_array, unicode_array, function_name): + if function_name in ONLY_IN_NP_CHAR: + func = getattr(np.char, function_name) + else: + func = getattr(np.strings, function_name) + dtype = string_array.dtype + sres = func(string_array) + ures = func(unicode_array) + if sres.dtype == StringDType(): + ures = ures.astype(StringDType()) + assert_array_equal(sres, ures) + + if not hasattr(dtype, "na_object"): + return + + is_nan = np.isnan(np.array([dtype.na_object], dtype=dtype))[0] + is_str = isinstance(dtype.na_object, str) + na_arr = np.insert(string_array, 0, dtype.na_object) + + if function_name in UNIMPLEMENTED_VEC_STRING_FUNCTIONS: + if not is_str: + # to avoid these errors we'd need to add NA support to _vec_string + with pytest.raises((ValueError, TypeError)): + func(na_arr) + elif function_name == "splitlines": + assert func(na_arr)[0] == func(dtype.na_object)[()] + else: + assert func(na_arr)[0] == func(dtype.na_object) + return + if function_name == "str_len" and not is_str: + # str_len always errors for any non-string null, even NA ones because + # it has an integer result + with pytest.raises(ValueError): + func(na_arr) + return + if function_name in BOOL_OUTPUT_FUNCTIONS: + if is_nan: + assert func(na_arr)[0] is np.False_ + elif is_str: + assert func(na_arr)[0] == func(dtype.na_object) + else: + with pytest.raises(ValueError): + func(na_arr) + return + if not (is_nan or is_str): + with pytest.raises(ValueError): + func(na_arr) + return + res = func(na_arr) + if is_nan and function_name in NAN_PRESERVING_FUNCTIONS: + assert res[0] is dtype.na_object + elif is_str: + assert res[0] == func(dtype.na_object) + + +unicode_bug_fail = pytest.mark.xfail( + reason="unicode output width is buggy", strict=True +) + +# None means that the argument is a string array +BINARY_FUNCTIONS = [ + ("add", (None, None)), + ("multiply", (None, 2)), + ("mod", ("format: %s", None)), + ("center", (None, 25)), + ("count", (None, "A")), + ("encode", (None, "UTF-8")), + ("endswith", (None, "lo")), + ("find", (None, "A")), + ("index", (None, "e")), + ("join", ("-", None)), + ("ljust", (None, 12)), + ("lstrip", (None, "A")), + ("partition", (None, "A")), + ("replace", (None, "A", "B")), + ("rfind", (None, "A")), + ("rindex", (None, "e")), + ("rjust", (None, 12)), + ("rsplit", (None, "A")), + ("rstrip", (None, "A")), + ("rpartition", (None, "A")), + ("split", (None, "A")), + ("strip", (None, "A")), + ("startswith", (None, "A")), + ("zfill", (None, 12)), +] + +PASSES_THROUGH_NAN_NULLS = [ + "add", + "center", + "ljust", + "multiply", + "replace", + "rjust", + "strip", + "lstrip", + "rstrip", + "replace" + "zfill", +] + +NULLS_ARE_FALSEY = [ + "startswith", + "endswith", +] + +NULLS_ALWAYS_ERROR = [ + "count", + "find", + "rfind", +] + +SUPPORTS_NULLS = ( + PASSES_THROUGH_NAN_NULLS + + NULLS_ARE_FALSEY + + NULLS_ALWAYS_ERROR +) + + +def call_func(func, args, array, sanitize=True): + if args == (None, None): + return func(array, array) + if args[0] is None: + if sanitize: + san_args = tuple( + np.array(arg, dtype=array.dtype) if isinstance(arg, str) else + arg for arg in args[1:] + ) + else: + san_args = args[1:] + return func(array, *san_args) + if args[1] is None: + return func(args[0], array) + # shouldn't ever happen + assert 0 + + +@pytest.mark.parametrize("function_name, args", BINARY_FUNCTIONS) +def test_binary(string_array, unicode_array, function_name, args): + if function_name in ONLY_IN_NP_CHAR: + func = getattr(np.char, function_name) + else: + func = getattr(np.strings, function_name) + sres = call_func(func, args, string_array) + ures = call_func(func, args, unicode_array, sanitize=False) + if not isinstance(sres, tuple) and sres.dtype == StringDType(): + ures = ures.astype(StringDType()) + assert_array_equal(sres, ures) + + dtype = string_array.dtype + if function_name not in SUPPORTS_NULLS or not hasattr(dtype, "na_object"): + return + + na_arr = np.insert(string_array, 0, dtype.na_object) + is_nan = np.isnan(np.array([dtype.na_object], dtype=dtype))[0] + is_str = isinstance(dtype.na_object, str) + should_error = not (is_nan or is_str) + + if ( + (function_name in NULLS_ALWAYS_ERROR and not is_str) + or (function_name in PASSES_THROUGH_NAN_NULLS and should_error) + or (function_name in NULLS_ARE_FALSEY and should_error) + ): + with pytest.raises((ValueError, TypeError)): + call_func(func, args, na_arr) + return + + res = call_func(func, args, na_arr) + + if is_str: + assert res[0] == call_func(func, args, na_arr[:1]) + elif function_name in NULLS_ARE_FALSEY: + assert res[0] is np.False_ + elif function_name in PASSES_THROUGH_NAN_NULLS: + assert res[0] is dtype.na_object + else: + # shouldn't ever get here + assert 0 + + +@pytest.mark.parametrize("function, expected", [ + (np.strings.find, [[2, -1], [1, -1]]), + (np.strings.startswith, [[False, False], [True, False]])]) +@pytest.mark.parametrize("start, stop", [ + (1, 4), + (np.int8(1), np.int8(4)), + (np.array([1, 1], dtype='u2'), np.array([4, 4], dtype='u2'))]) +def test_non_default_start_stop(function, start, stop, expected): + a = np.array([["--🐍--", "--🦜--"], + ["-🐍---", "-🦜---"]], "T") + indx = function(a, "🐍", start, stop) + assert_array_equal(indx, expected) + + +@pytest.mark.parametrize("count", [2, np.int8(2), np.array([2, 2], 'u2')]) +def test_replace_non_default_repeat(count): + a = np.array(["🐍--", "🦜-🦜-"], "T") + result = np.strings.replace(a, "🦜-", "🦜†", count) + assert_array_equal(result, np.array(["🐍--", "🦜†🦜†"], "T")) + + +def test_strip_ljust_rjust_consistency(string_array, unicode_array): + rjs = np.char.rjust(string_array, 1000) + rju = np.char.rjust(unicode_array, 1000) + + ljs = np.char.ljust(string_array, 1000) + lju = np.char.ljust(unicode_array, 1000) + + assert_array_equal( + np.char.lstrip(rjs), + np.char.lstrip(rju).astype(StringDType()), + ) + + assert_array_equal( + np.char.rstrip(ljs), + np.char.rstrip(lju).astype(StringDType()), + ) + + assert_array_equal( + np.char.strip(ljs), + np.char.strip(lju).astype(StringDType()), + ) + + assert_array_equal( + np.char.strip(rjs), + np.char.strip(rju).astype(StringDType()), + ) + + +def test_unset_na_coercion(): + # a dtype instance with an unset na object is compatible + # with a dtype that has one set + + # this test uses the "add" and "equal" ufunc but all ufuncs that + # accept more than one string argument and produce a string should + # behave this way + # TODO: generalize to more ufuncs + inp = ["hello", "world"] + arr = np.array(inp, dtype=StringDType(na_object=None)) + for op_dtype in [None, StringDType(), StringDType(coerce=False), + StringDType(na_object=None)]: + if op_dtype is None: + op = "2" + else: + op = np.array("2", dtype=op_dtype) + res = arr + op + assert_array_equal(res, ["hello2", "world2"]) + + # dtype instances with distinct explicitly set NA objects are incompatible + for op_dtype in [StringDType(na_object=pd_NA), StringDType(na_object="")]: + op = np.array("2", dtype=op_dtype) + with pytest.raises(TypeError): + arr + op + + # comparisons only consider the na_object + for op_dtype in [None, StringDType(), StringDType(coerce=True), + StringDType(na_object=None)]: + if op_dtype is None: + op = inp + else: + op = np.array(inp, dtype=op_dtype) + assert_array_equal(arr, op) + + for op_dtype in [StringDType(na_object=pd_NA), + StringDType(na_object=np.nan)]: + op = np.array(inp, dtype=op_dtype) + with pytest.raises(TypeError): + arr == op + + +def test_repeat(string_array): + res = string_array.repeat(1000) + # Create an empty array with expanded dimension, and fill it. Then, + # reshape it to the expected result. + expected = np.empty_like(string_array, shape=string_array.shape + (1000,)) + expected[...] = string_array[:, np.newaxis] + expected = expected.reshape(-1) + + assert_array_equal(res, expected, strict=True) + + +@pytest.mark.parametrize("tile", [1, 6, (2, 5)]) +def test_accumulation(string_array, tile): + """Accumulation is odd for StringDType but tests dtypes with references. + """ + # Fill with mostly empty strings to not create absurdly big strings + arr = np.zeros_like(string_array, shape=(100,)) + arr[:len(string_array)] = string_array + arr[-len(string_array):] = string_array + + # Bloat size a bit (get above thresholds and test >1 ndim). + arr = np.tile(string_array, tile) + + res = np.add.accumulate(arr, axis=0) + res_obj = np.add.accumulate(arr.astype(object), axis=0) + assert_array_equal(res, res_obj.astype(arr.dtype), strict=True) + + if arr.ndim > 1: + res = np.add.accumulate(arr, axis=-1) + res_obj = np.add.accumulate(arr.astype(object), axis=-1) + + assert_array_equal(res, res_obj.astype(arr.dtype), strict=True) + + +class TestImplementation: + """Check that strings are stored in the arena when possible. + + This tests implementation details, so should be adjusted if + the implementation changes. + """ + + @classmethod + def setup_class(cls): + cls.MISSING = 0x80 + cls.INITIALIZED = 0x40 + cls.OUTSIDE_ARENA = 0x20 + cls.LONG = 0x10 + cls.dtype = StringDType(na_object=np.nan) + cls.sizeofstr = cls.dtype.itemsize + sp = cls.dtype.itemsize // 2 # pointer size = sizeof(size_t) + # Below, size is not strictly correct, since it really uses + # 7 (or 3) bytes, but good enough for the tests here. + cls.view_dtype = np.dtype([ + ('offset', f'u{sp}'), + ('size', f'u{sp // 2}'), + ('xsiz', f'V{sp // 2 - 1}'), + ('size_and_flags', 'u1'), + ] if sys.byteorder == 'little' else [ + ('size_and_flags', 'u1'), + ('xsiz', f'V{sp // 2 - 1}'), + ('size', f'u{sp // 2}'), + ('offset', f'u{sp}'), + ]) + cls.s_empty = "" + cls.s_short = "01234" + cls.s_medium = "abcdefghijklmnopqrstuvwxyz" + cls.s_long = "-=+" * 100 + cls.a = np.array( + [cls.s_empty, cls.s_short, cls.s_medium, cls.s_long], + cls.dtype) + + def get_view(self, a): + # Cannot view a StringDType as anything else directly, since + # it has references. So, we use a stride trick hack. + from numpy.lib._stride_tricks_impl import DummyArray + interface = dict(a.__array_interface__) + interface['descr'] = self.view_dtype.descr + interface['typestr'] = self.view_dtype.str + return np.asarray(DummyArray(interface, base=a)) + + def get_flags(self, a): + return self.get_view(a)['size_and_flags'] & 0xf0 + + def is_short(self, a): + return self.get_flags(a) == self.INITIALIZED | self.OUTSIDE_ARENA + + def is_on_heap(self, a): + return self.get_flags(a) == (self.INITIALIZED + | self.OUTSIDE_ARENA + | self.LONG) + + def is_missing(self, a): + return self.get_flags(a) & self.MISSING == self.MISSING + + def in_arena(self, a): + return (self.get_flags(a) & (self.INITIALIZED | self.OUTSIDE_ARENA) + == self.INITIALIZED) + + def test_setup(self): + is_short = self.is_short(self.a) + length = np.strings.str_len(self.a) + assert_array_equal(is_short, (length > 0) & (length <= 15)) + assert_array_equal(self.in_arena(self.a), [False, False, True, True]) + assert_array_equal(self.is_on_heap(self.a), False) + assert_array_equal(self.is_missing(self.a), False) + view = self.get_view(self.a) + sizes = np.where(is_short, view['size_and_flags'] & 0xf, + view['size']) + assert_array_equal(sizes, np.strings.str_len(self.a)) + assert_array_equal(view['xsiz'][2:], + np.void(b'\x00' * (self.sizeofstr // 4 - 1))) + # Check that the medium string uses only 1 byte for its length + # in the arena, while the long string takes 8 (or 4). + offsets = view['offset'] + assert offsets[2] == 1 + assert offsets[3] == 1 + len(self.s_medium) + self.sizeofstr // 2 + + def test_empty(self): + e = np.empty((3,), self.dtype) + assert_array_equal(self.get_flags(e), 0) + assert_array_equal(e, "") + + def test_zeros(self): + z = np.zeros((2,), self.dtype) + assert_array_equal(self.get_flags(z), 0) + assert_array_equal(z, "") + + def test_copy(self): + for c in [self.a.copy(), copy.copy(self.a), copy.deepcopy(self.a)]: + assert_array_equal(self.get_flags(c), self.get_flags(self.a)) + assert_array_equal(c, self.a) + offsets = self.get_view(c)['offset'] + assert offsets[2] == 1 + assert offsets[3] == 1 + len(self.s_medium) + self.sizeofstr // 2 + + def test_arena_use_with_setting(self): + c = np.zeros_like(self.a) + assert_array_equal(self.get_flags(c), 0) + c[:] = self.a + assert_array_equal(self.get_flags(c), self.get_flags(self.a)) + assert_array_equal(c, self.a) + + def test_arena_reuse_with_setting(self): + c = self.a.copy() + c[:] = self.a + assert_array_equal(self.get_flags(c), self.get_flags(self.a)) + assert_array_equal(c, self.a) + + def test_arena_reuse_after_missing(self): + c = self.a.copy() + c[:] = np.nan + assert np.all(self.is_missing(c)) + # Replacing with the original strings, the arena should be reused. + c[:] = self.a + assert_array_equal(self.get_flags(c), self.get_flags(self.a)) + assert_array_equal(c, self.a) + + def test_arena_reuse_after_empty(self): + c = self.a.copy() + c[:] = "" + assert_array_equal(c, "") + # Replacing with the original strings, the arena should be reused. + c[:] = self.a + assert_array_equal(self.get_flags(c), self.get_flags(self.a)) + assert_array_equal(c, self.a) + + def test_arena_reuse_for_shorter(self): + c = self.a.copy() + # A string slightly shorter than the shortest in the arena + # should be used for all strings in the arena. + c[:] = self.s_medium[:-1] + assert_array_equal(c, self.s_medium[:-1]) + # first empty string in original was never initialized, so + # filling it in now leaves it initialized inside the arena. + # second string started as a short string so it can never live + # in the arena. + in_arena = np.array([True, False, True, True]) + assert_array_equal(self.in_arena(c), in_arena) + # But when a short string is replaced, it will go on the heap. + assert_array_equal(self.is_short(c), False) + assert_array_equal(self.is_on_heap(c), ~in_arena) + # We can put the originals back, and they'll still fit, + # and short strings are back as short strings + c[:] = self.a + assert_array_equal(c, self.a) + assert_array_equal(self.in_arena(c), in_arena) + assert_array_equal(self.is_short(c), self.is_short(self.a)) + assert_array_equal(self.is_on_heap(c), False) + + def test_arena_reuse_if_possible(self): + c = self.a.copy() + # A slightly longer string will not fit in the arena for + # the medium string, but will fit for the longer one. + c[:] = self.s_medium + "±" + assert_array_equal(c, self.s_medium + "±") + in_arena_exp = np.strings.str_len(self.a) >= len(self.s_medium) + 1 + # first entry started uninitialized and empty, so filling it leaves + # it in the arena + in_arena_exp[0] = True + assert not np.all(in_arena_exp == self.in_arena(self.a)) + assert_array_equal(self.in_arena(c), in_arena_exp) + assert_array_equal(self.is_short(c), False) + assert_array_equal(self.is_on_heap(c), ~in_arena_exp) + # And once outside arena, it stays outside, since offset is lost. + # But short strings are used again. + c[:] = self.a + is_short_exp = self.is_short(self.a) + assert_array_equal(c, self.a) + assert_array_equal(self.in_arena(c), in_arena_exp) + assert_array_equal(self.is_short(c), is_short_exp) + assert_array_equal(self.is_on_heap(c), ~in_arena_exp & ~is_short_exp) + + def test_arena_no_reuse_after_short(self): + c = self.a.copy() + # If we replace a string with a short string, it cannot + # go into the arena after because the offset is lost. + c[:] = self.s_short + assert_array_equal(c, self.s_short) + assert_array_equal(self.in_arena(c), False) + c[:] = self.a + assert_array_equal(c, self.a) + assert_array_equal(self.in_arena(c), False) + assert_array_equal(self.is_on_heap(c), self.in_arena(self.a)) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_utils/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_utils/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..84ee99db1be8a4c1654be2a9069b03e4e7c10738 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_utils/__init__.py @@ -0,0 +1,95 @@ +""" +This is a module for defining private helpers which do not depend on the +rest of NumPy. + +Everything in here must be self-contained so that it can be +imported anywhere else without creating circular imports. +If a utility requires the import of NumPy, it probably belongs +in ``numpy._core``. +""" + +import functools +import warnings + +from ._convertions import asbytes, asunicode + + +def set_module(module): + """Private decorator for overriding __module__ on a function or class. + + Example usage:: + + @set_module('numpy') + def example(): + pass + + assert example.__module__ == 'numpy' + """ + def decorator(func): + if module is not None: + if isinstance(func, type): + try: + func._module_source = func.__module__ + except (AttributeError): + pass + + func.__module__ = module + return func + return decorator + + +def _rename_parameter(old_names, new_names, dep_version=None): + """ + Generate decorator for backward-compatible keyword renaming. + + Apply the decorator generated by `_rename_parameter` to functions with a + renamed parameter to maintain backward-compatibility. + + After decoration, the function behaves as follows: + If only the new parameter is passed into the function, behave as usual. + If only the old parameter is passed into the function (as a keyword), raise + a DeprecationWarning if `dep_version` is provided, and behave as usual + otherwise. + If both old and new parameters are passed into the function, raise a + DeprecationWarning if `dep_version` is provided, and raise the appropriate + TypeError (function got multiple values for argument). + + Parameters + ---------- + old_names : list of str + Old names of parameters + new_name : list of str + New names of parameters + dep_version : str, optional + Version of NumPy in which old parameter was deprecated in the format + 'X.Y.Z'. If supplied, the deprecation message will indicate that + support for the old parameter will be removed in version 'X.Y+2.Z' + + Notes + ----- + Untested with functions that accept *args. Probably won't work as written. + + """ + def decorator(fun): + @functools.wraps(fun) + def wrapper(*args, **kwargs): + __tracebackhide__ = True # Hide traceback for py.test + for old_name, new_name in zip(old_names, new_names): + if old_name in kwargs: + if dep_version: + end_version = dep_version.split('.') + end_version[1] = str(int(end_version[1]) + 2) + end_version = '.'.join(end_version) + msg = (f"Use of keyword argument `{old_name}` is " + f"deprecated and replaced by `{new_name}`. " + f"Support for `{old_name}` will be removed " + f"in NumPy {end_version}.") + warnings.warn(msg, DeprecationWarning, stacklevel=2) + if new_name in kwargs: + msg = (f"{fun.__name__}() got multiple values for " + f"argument now known as `{new_name}`") + raise TypeError(msg) + kwargs[new_name] = kwargs.pop(old_name) + return fun(*args, **kwargs) + return wrapper + return decorator diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_utils/__init__.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_utils/__init__.pyi new file mode 100644 index 0000000000000000000000000000000000000000..b630777ced9984fd8e297b76a37aafe7d12ea61d --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_utils/__init__.pyi @@ -0,0 +1,28 @@ +from _typeshed import IdentityFunction +from collections.abc import Callable, Iterable +from typing import Protocol, TypeVar, overload, type_check_only + +from ._convertions import asbytes as asbytes, asunicode as asunicode + +### + +_T = TypeVar("_T") +_HasModuleT = TypeVar("_HasModuleT", bound=_HasModule) + +@type_check_only +class _HasModule(Protocol): + __module__: str + +### + +@overload +def set_module(module: None) -> IdentityFunction: ... +@overload +def set_module(module: str) -> Callable[[_HasModuleT], _HasModuleT]: ... + +# +def _rename_parameter( + old_names: Iterable[str], + new_names: Iterable[str], + dep_version: str | None = None, +) -> Callable[[Callable[..., _T]], Callable[..., _T]]: ... diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_utils/_convertions.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_utils/_convertions.py new file mode 100644 index 0000000000000000000000000000000000000000..ab15a8ba019f1b6a40ac3f562897fa4581323efc --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_utils/_convertions.py @@ -0,0 +1,18 @@ +""" +A set of methods retained from np.compat module that +are still used across codebase. +""" + +__all__ = ["asunicode", "asbytes"] + + +def asunicode(s): + if isinstance(s, bytes): + return s.decode('latin1') + return str(s) + + +def asbytes(s): + if isinstance(s, bytes): + return s + return str(s).encode('latin1') diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_utils/_convertions.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_utils/_convertions.pyi new file mode 100644 index 0000000000000000000000000000000000000000..6cc599acc94f97f026c3f81a538c3d1766d450d3 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_utils/_convertions.pyi @@ -0,0 +1,4 @@ +__all__ = ["asbytes", "asunicode"] + +def asunicode(s: bytes | str) -> str: ... +def asbytes(s: bytes | str) -> str: ... diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_utils/_inspect.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_utils/_inspect.py new file mode 100644 index 0000000000000000000000000000000000000000..b499f5837b08a35bd7b158be87364222e8de0e03 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_utils/_inspect.py @@ -0,0 +1,192 @@ +"""Subset of inspect module from upstream python + +We use this instead of upstream because upstream inspect is slow to import, and +significantly contributes to numpy import times. Importing this copy has almost +no overhead. + +""" +import types + +__all__ = ['getargspec', 'formatargspec'] + +# ----------------------------------------------------------- type-checking +def ismethod(object): + """Return true if the object is an instance method. + + Instance method objects provide these attributes: + __doc__ documentation string + __name__ name with which this method was defined + im_class class object in which this method belongs + im_func function object containing implementation of method + im_self instance to which this method is bound, or None + + """ + return isinstance(object, types.MethodType) + +def isfunction(object): + """Return true if the object is a user-defined function. + + Function objects provide these attributes: + __doc__ documentation string + __name__ name with which this function was defined + func_code code object containing compiled function bytecode + func_defaults tuple of any default values for arguments + func_doc (same as __doc__) + func_globals global namespace in which this function was defined + func_name (same as __name__) + + """ + return isinstance(object, types.FunctionType) + +def iscode(object): + """Return true if the object is a code object. + + Code objects provide these attributes: + co_argcount number of arguments (not including * or ** args) + co_code string of raw compiled bytecode + co_consts tuple of constants used in the bytecode + co_filename name of file in which this code object was created + co_firstlineno number of first line in Python source code + co_flags bitmap: 1=optimized | 2=newlocals | 4=*arg | 8=**arg + co_lnotab encoded mapping of line numbers to bytecode indices + co_name name with which this code object was defined + co_names tuple of names of local variables + co_nlocals number of local variables + co_stacksize virtual machine stack space required + co_varnames tuple of names of arguments and local variables + + """ + return isinstance(object, types.CodeType) + + +# ------------------------------------------------ argument list extraction +# These constants are from Python's compile.h. +CO_OPTIMIZED, CO_NEWLOCALS, CO_VARARGS, CO_VARKEYWORDS = 1, 2, 4, 8 + +def getargs(co): + """Get information about the arguments accepted by a code object. + + Three things are returned: (args, varargs, varkw), where 'args' is + a list of argument names (possibly containing nested lists), and + 'varargs' and 'varkw' are the names of the * and ** arguments or None. + + """ + + if not iscode(co): + raise TypeError('arg is not a code object') + + nargs = co.co_argcount + names = co.co_varnames + args = list(names[:nargs]) + + # The following acrobatics are for anonymous (tuple) arguments. + # Which we do not need to support, so remove to avoid importing + # the dis module. + for i in range(nargs): + if args[i][:1] in ['', '.']: + raise TypeError("tuple function arguments are not supported") + varargs = None + if co.co_flags & CO_VARARGS: + varargs = co.co_varnames[nargs] + nargs = nargs + 1 + varkw = None + if co.co_flags & CO_VARKEYWORDS: + varkw = co.co_varnames[nargs] + return args, varargs, varkw + +def getargspec(func): + """Get the names and default values of a function's arguments. + + A tuple of four things is returned: (args, varargs, varkw, defaults). + 'args' is a list of the argument names (it may contain nested lists). + 'varargs' and 'varkw' are the names of the * and ** arguments or None. + 'defaults' is an n-tuple of the default values of the last n arguments. + + """ + + if ismethod(func): + func = func.__func__ + if not isfunction(func): + raise TypeError('arg is not a Python function') + args, varargs, varkw = getargs(func.__code__) + return args, varargs, varkw, func.__defaults__ + +def getargvalues(frame): + """Get information about arguments passed into a particular frame. + + A tuple of four things is returned: (args, varargs, varkw, locals). + 'args' is a list of the argument names (it may contain nested lists). + 'varargs' and 'varkw' are the names of the * and ** arguments or None. + 'locals' is the locals dictionary of the given frame. + + """ + args, varargs, varkw = getargs(frame.f_code) + return args, varargs, varkw, frame.f_locals + +def joinseq(seq): + if len(seq) == 1: + return '(' + seq[0] + ',)' + else: + return '(' + ', '.join(seq) + ')' + +def strseq(object, convert, join=joinseq): + """Recursively walk a sequence, stringifying each element. + + """ + if type(object) in [list, tuple]: + return join([strseq(_o, convert, join) for _o in object]) + else: + return convert(object) + +def formatargspec(args, varargs=None, varkw=None, defaults=None, + formatarg=str, + formatvarargs=lambda name: '*' + name, + formatvarkw=lambda name: '**' + name, + formatvalue=lambda value: '=' + repr(value), + join=joinseq): + """Format an argument spec from the 4 values returned by getargspec. + + The first four arguments are (args, varargs, varkw, defaults). The + other four arguments are the corresponding optional formatting functions + that are called to turn names and values into strings. The ninth + argument is an optional function to format the sequence of arguments. + + """ + specs = [] + if defaults: + firstdefault = len(args) - len(defaults) + for i in range(len(args)): + spec = strseq(args[i], formatarg, join) + if defaults and i >= firstdefault: + spec = spec + formatvalue(defaults[i - firstdefault]) + specs.append(spec) + if varargs is not None: + specs.append(formatvarargs(varargs)) + if varkw is not None: + specs.append(formatvarkw(varkw)) + return '(' + ', '.join(specs) + ')' + +def formatargvalues(args, varargs, varkw, locals, + formatarg=str, + formatvarargs=lambda name: '*' + name, + formatvarkw=lambda name: '**' + name, + formatvalue=lambda value: '=' + repr(value), + join=joinseq): + """Format an argument spec from the 4 values returned by getargvalues. + + The first four arguments are (args, varargs, varkw, locals). The + next four arguments are the corresponding optional formatting functions + that are called to turn names and values into strings. The ninth + argument is an optional function to format the sequence of arguments. + + """ + def convert(name, locals=locals, + formatarg=formatarg, formatvalue=formatvalue): + return formatarg(name) + formatvalue(locals[name]) + specs = [strseq(arg, convert, join) for arg in args] + + if varargs: + specs.append(formatvarargs(varargs) + formatvalue(locals[varargs])) + if varkw: + specs.append(formatvarkw(varkw) + formatvalue(locals[varkw])) + return '(' + ', '.join(specs) + ')' diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_utils/_inspect.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_utils/_inspect.pyi new file mode 100644 index 0000000000000000000000000000000000000000..40546d2f449791f64b944b19fc53c4771d5d26bc --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_utils/_inspect.pyi @@ -0,0 +1,70 @@ +import types +from _typeshed import SupportsLenAndGetItem +from collections.abc import Callable, Mapping +from typing import Any, Final, TypeAlias, TypeVar, overload +from typing_extensions import TypeIs + +__all__ = ["formatargspec", "getargspec"] + +### + +_T = TypeVar("_T") +_RT = TypeVar("_RT") + +_StrSeq: TypeAlias = SupportsLenAndGetItem[str] +_NestedSeq: TypeAlias = list[_T | _NestedSeq[_T]] | tuple[_T | _NestedSeq[_T], ...] + +_JoinFunc: TypeAlias = Callable[[list[_T]], _T] +_FormatFunc: TypeAlias = Callable[[_T], str] + +### + +CO_OPTIMIZED: Final = 1 +CO_NEWLOCALS: Final = 2 +CO_VARARGS: Final = 4 +CO_VARKEYWORDS: Final = 8 + +### + +def ismethod(object: object) -> TypeIs[types.MethodType]: ... +def isfunction(object: object) -> TypeIs[types.FunctionType]: ... +def iscode(object: object) -> TypeIs[types.CodeType]: ... + +### + +def getargs(co: types.CodeType) -> tuple[list[str], str | None, str | None]: ... +def getargspec(func: types.MethodType | types.FunctionType) -> tuple[list[str], str | None, str | None, tuple[Any, ...]]: ... +def getargvalues(frame: types.FrameType) -> tuple[list[str], str | None, str | None, dict[str, Any]]: ... + +# +def joinseq(seq: _StrSeq) -> str: ... + +# +@overload +def strseq(object: _NestedSeq[str], convert: Callable[[Any], Any], join: _JoinFunc[str] = ...) -> str: ... +@overload +def strseq(object: _NestedSeq[_T], convert: Callable[[_T], _RT], join: _JoinFunc[_RT]) -> _RT: ... + +# +def formatargspec( + args: _StrSeq, + varargs: str | None = None, + varkw: str | None = None, + defaults: SupportsLenAndGetItem[object] | None = None, + formatarg: _FormatFunc[str] = ..., # str + formatvarargs: _FormatFunc[str] = ..., # "*{}".format + formatvarkw: _FormatFunc[str] = ..., # "**{}".format + formatvalue: _FormatFunc[object] = ..., # "={!r}".format + join: _JoinFunc[str] = ..., # joinseq +) -> str: ... +def formatargvalues( + args: _StrSeq, + varargs: str | None, + varkw: str | None, + locals: Mapping[str, object] | None, + formatarg: _FormatFunc[str] = ..., # str + formatvarargs: _FormatFunc[str] = ..., # "*{}".format + formatvarkw: _FormatFunc[str] = ..., # "**{}".format + formatvalue: _FormatFunc[object] = ..., # "={!r}".format + join: _JoinFunc[str] = ..., # joinseq +) -> str: ... diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_utils/_pep440.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_utils/_pep440.py new file mode 100644 index 0000000000000000000000000000000000000000..035a0695e5ee8ceab6f3c6fde0a0232afb9b8bd7 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_utils/_pep440.py @@ -0,0 +1,486 @@ +"""Utility to compare pep440 compatible version strings. + +The LooseVersion and StrictVersion classes that distutils provides don't +work; they don't recognize anything like alpha/beta/rc/dev versions. +""" + +# Copyright (c) Donald Stufft and individual contributors. +# All rights reserved. + +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: + +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. + +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. + +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +import collections +import itertools +import re + +__all__ = [ + "parse", "Version", "LegacyVersion", "InvalidVersion", "VERSION_PATTERN", +] + + +# BEGIN packaging/_structures.py + + +class Infinity: + def __repr__(self): + return "Infinity" + + def __hash__(self): + return hash(repr(self)) + + def __lt__(self, other): + return False + + def __le__(self, other): + return False + + def __eq__(self, other): + return isinstance(other, self.__class__) + + def __ne__(self, other): + return not isinstance(other, self.__class__) + + def __gt__(self, other): + return True + + def __ge__(self, other): + return True + + def __neg__(self): + return NegativeInfinity + + +Infinity = Infinity() + + +class NegativeInfinity: + def __repr__(self): + return "-Infinity" + + def __hash__(self): + return hash(repr(self)) + + def __lt__(self, other): + return True + + def __le__(self, other): + return True + + def __eq__(self, other): + return isinstance(other, self.__class__) + + def __ne__(self, other): + return not isinstance(other, self.__class__) + + def __gt__(self, other): + return False + + def __ge__(self, other): + return False + + def __neg__(self): + return Infinity + + +# BEGIN packaging/version.py + + +NegativeInfinity = NegativeInfinity() + +_Version = collections.namedtuple( + "_Version", + ["epoch", "release", "dev", "pre", "post", "local"], +) + + +def parse(version): + """ + Parse the given version string and return either a :class:`Version` object + or a :class:`LegacyVersion` object depending on if the given version is + a valid PEP 440 version or a legacy version. + """ + try: + return Version(version) + except InvalidVersion: + return LegacyVersion(version) + + +class InvalidVersion(ValueError): + """ + An invalid version was found, users should refer to PEP 440. + """ + + +class _BaseVersion: + + def __hash__(self): + return hash(self._key) + + def __lt__(self, other): + return self._compare(other, lambda s, o: s < o) + + def __le__(self, other): + return self._compare(other, lambda s, o: s <= o) + + def __eq__(self, other): + return self._compare(other, lambda s, o: s == o) + + def __ge__(self, other): + return self._compare(other, lambda s, o: s >= o) + + def __gt__(self, other): + return self._compare(other, lambda s, o: s > o) + + def __ne__(self, other): + return self._compare(other, lambda s, o: s != o) + + def _compare(self, other, method): + if not isinstance(other, _BaseVersion): + return NotImplemented + + return method(self._key, other._key) + + +class LegacyVersion(_BaseVersion): + + def __init__(self, version): + self._version = str(version) + self._key = _legacy_cmpkey(self._version) + + def __str__(self): + return self._version + + def __repr__(self): + return f"" + + @property + def public(self): + return self._version + + @property + def base_version(self): + return self._version + + @property + def local(self): + return None + + @property + def is_prerelease(self): + return False + + @property + def is_postrelease(self): + return False + + +_legacy_version_component_re = re.compile( + r"(\d+ | [a-z]+ | \.| -)", re.VERBOSE, +) + +_legacy_version_replacement_map = { + "pre": "c", "preview": "c", "-": "final-", "rc": "c", "dev": "@", +} + + +def _parse_version_parts(s): + for part in _legacy_version_component_re.split(s): + part = _legacy_version_replacement_map.get(part, part) + + if not part or part == ".": + continue + + if part[:1] in "0123456789": + # pad for numeric comparison + yield part.zfill(8) + else: + yield "*" + part + + # ensure that alpha/beta/candidate are before final + yield "*final" + + +def _legacy_cmpkey(version): + # We hardcode an epoch of -1 here. A PEP 440 version can only have an epoch + # greater than or equal to 0. This will effectively put the LegacyVersion, + # which uses the defacto standard originally implemented by setuptools, + # as before all PEP 440 versions. + epoch = -1 + + # This scheme is taken from pkg_resources.parse_version setuptools prior to + # its adoption of the packaging library. + parts = [] + for part in _parse_version_parts(version.lower()): + if part.startswith("*"): + # remove "-" before a prerelease tag + if part < "*final": + while parts and parts[-1] == "*final-": + parts.pop() + + # remove trailing zeros from each series of numeric parts + while parts and parts[-1] == "00000000": + parts.pop() + + parts.append(part) + parts = tuple(parts) + + return epoch, parts + + +# Deliberately not anchored to the start and end of the string, to make it +# easier for 3rd party code to reuse +VERSION_PATTERN = r""" + v? + (?: + (?:(?P[0-9]+)!)? # epoch + (?P[0-9]+(?:\.[0-9]+)*) # release segment + (?P
                                              # pre-release
    +            [-_\.]?
    +            (?P(a|b|c|rc|alpha|beta|pre|preview))
    +            [-_\.]?
    +            (?P[0-9]+)?
    +        )?
    +        (?P                                         # post release
    +            (?:-(?P[0-9]+))
    +            |
    +            (?:
    +                [-_\.]?
    +                (?Ppost|rev|r)
    +                [-_\.]?
    +                (?P[0-9]+)?
    +            )
    +        )?
    +        (?P                                          # dev release
    +            [-_\.]?
    +            (?Pdev)
    +            [-_\.]?
    +            (?P[0-9]+)?
    +        )?
    +    )
    +    (?:\+(?P[a-z0-9]+(?:[-_\.][a-z0-9]+)*))?       # local version
    +"""
    +
    +
    +class Version(_BaseVersion):
    +
    +    _regex = re.compile(
    +        r"^\s*" + VERSION_PATTERN + r"\s*$",
    +        re.VERBOSE | re.IGNORECASE,
    +    )
    +
    +    def __init__(self, version):
    +        # Validate the version and parse it into pieces
    +        match = self._regex.search(version)
    +        if not match:
    +            raise InvalidVersion(f"Invalid version: '{version}'")
    +
    +        # Store the parsed out pieces of the version
    +        self._version = _Version(
    +            epoch=int(match.group("epoch")) if match.group("epoch") else 0,
    +            release=tuple(int(i) for i in match.group("release").split(".")),
    +            pre=_parse_letter_version(
    +                match.group("pre_l"),
    +                match.group("pre_n"),
    +            ),
    +            post=_parse_letter_version(
    +                match.group("post_l"),
    +                match.group("post_n1") or match.group("post_n2"),
    +            ),
    +            dev=_parse_letter_version(
    +                match.group("dev_l"),
    +                match.group("dev_n"),
    +            ),
    +            local=_parse_local_version(match.group("local")),
    +        )
    +
    +        # Generate a key which will be used for sorting
    +        self._key = _cmpkey(
    +            self._version.epoch,
    +            self._version.release,
    +            self._version.pre,
    +            self._version.post,
    +            self._version.dev,
    +            self._version.local,
    +        )
    +
    +    def __repr__(self):
    +        return f""
    +
    +    def __str__(self):
    +        parts = []
    +
    +        # Epoch
    +        if self._version.epoch != 0:
    +            parts.append(f"{self._version.epoch}!")
    +
    +        # Release segment
    +        parts.append(".".join(str(x) for x in self._version.release))
    +
    +        # Pre-release
    +        if self._version.pre is not None:
    +            parts.append("".join(str(x) for x in self._version.pre))
    +
    +        # Post-release
    +        if self._version.post is not None:
    +            parts.append(f".post{self._version.post[1]}")
    +
    +        # Development release
    +        if self._version.dev is not None:
    +            parts.append(f".dev{self._version.dev[1]}")
    +
    +        # Local version segment
    +        if self._version.local is not None:
    +            parts.append(
    +                f"+{'.'.join(str(x) for x in self._version.local)}"
    +            )
    +
    +        return "".join(parts)
    +
    +    @property
    +    def public(self):
    +        return str(self).split("+", 1)[0]
    +
    +    @property
    +    def base_version(self):
    +        parts = []
    +
    +        # Epoch
    +        if self._version.epoch != 0:
    +            parts.append(f"{self._version.epoch}!")
    +
    +        # Release segment
    +        parts.append(".".join(str(x) for x in self._version.release))
    +
    +        return "".join(parts)
    +
    +    @property
    +    def local(self):
    +        version_string = str(self)
    +        if "+" in version_string:
    +            return version_string.split("+", 1)[1]
    +
    +    @property
    +    def is_prerelease(self):
    +        return bool(self._version.dev or self._version.pre)
    +
    +    @property
    +    def is_postrelease(self):
    +        return bool(self._version.post)
    +
    +
    +def _parse_letter_version(letter, number):
    +    if letter:
    +        # We assume there is an implicit 0 in a pre-release if there is
    +        # no numeral associated with it.
    +        if number is None:
    +            number = 0
    +
    +        # We normalize any letters to their lower-case form
    +        letter = letter.lower()
    +
    +        # We consider some words to be alternate spellings of other words and
    +        # in those cases we want to normalize the spellings to our preferred
    +        # spelling.
    +        if letter == "alpha":
    +            letter = "a"
    +        elif letter == "beta":
    +            letter = "b"
    +        elif letter in ["c", "pre", "preview"]:
    +            letter = "rc"
    +        elif letter in ["rev", "r"]:
    +            letter = "post"
    +
    +        return letter, int(number)
    +    if not letter and number:
    +        # We assume that if we are given a number but not given a letter,
    +        # then this is using the implicit post release syntax (e.g., 1.0-1)
    +        letter = "post"
    +
    +        return letter, int(number)
    +
    +
    +_local_version_seperators = re.compile(r"[\._-]")
    +
    +
    +def _parse_local_version(local):
    +    """
    +    Takes a string like abc.1.twelve and turns it into ("abc", 1, "twelve").
    +    """
    +    if local is not None:
    +        return tuple(
    +            part.lower() if not part.isdigit() else int(part)
    +            for part in _local_version_seperators.split(local)
    +        )
    +
    +
    +def _cmpkey(epoch, release, pre, post, dev, local):
    +    # When we compare a release version, we want to compare it with all of the
    +    # trailing zeros removed. So we'll use a reverse the list, drop all the now
    +    # leading zeros until we come to something non-zero, then take the rest,
    +    # re-reverse it back into the correct order, and make it a tuple and use
    +    # that for our sorting key.
    +    release = tuple(
    +        reversed(list(
    +            itertools.dropwhile(
    +                lambda x: x == 0,
    +                reversed(release),
    +            )
    +        ))
    +    )
    +
    +    # We need to "trick" the sorting algorithm to put 1.0.dev0 before 1.0a0.
    +    # We'll do this by abusing the pre-segment, but we _only_ want to do this
    +    # if there is no pre- or a post-segment. If we have one of those, then
    +    # the normal sorting rules will handle this case correctly.
    +    if pre is None and post is None and dev is not None:
    +        pre = -Infinity
    +    # Versions without a pre-release (except as noted above) should sort after
    +    # those with one.
    +    elif pre is None:
    +        pre = Infinity
    +
    +    # Versions without a post-segment should sort before those with one.
    +    if post is None:
    +        post = -Infinity
    +
    +    # Versions without a development segment should sort after those with one.
    +    if dev is None:
    +        dev = Infinity
    +
    +    if local is None:
    +        # Versions without a local segment should sort before those with one.
    +        local = -Infinity
    +    else:
    +        # Versions with a local segment need that segment parsed to implement
    +        # the sorting rules in PEP440.
    +        # - Alphanumeric segments sort before numeric segments
    +        # - Alphanumeric segments sort lexicographically
    +        # - Numeric segments sort numerically
    +        # - Shorter versions sort before longer versions when the prefixes
    +        #   match exactly
    +        local = tuple(
    +            (i, "") if isinstance(i, int) else (-Infinity, i)
    +            for i in local
    +        )
    +
    +    return epoch, release, pre, post, dev, local
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_utils/_pep440.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_utils/_pep440.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..11ae02e57a59cbe4c37db043941b1a449f75ef4d
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/_utils/_pep440.pyi
    @@ -0,0 +1,118 @@
    +import re
    +from collections.abc import Callable
    +from typing import (
    +    Any,
    +    ClassVar,
    +    Final,
    +    Generic,
    +    Literal as L,
    +    NamedTuple,
    +    TypeVar,
    +    final,
    +    type_check_only,
    +)
    +from typing_extensions import TypeIs
    +
    +__all__ = ["VERSION_PATTERN", "InvalidVersion", "LegacyVersion", "Version", "parse"]
    +
    +###
    +
    +_CmpKeyT = TypeVar("_CmpKeyT", bound=tuple[object, ...])
    +_CmpKeyT_co = TypeVar("_CmpKeyT_co", bound=tuple[object, ...], default=tuple[Any, ...], covariant=True)
    +
    +###
    +
    +VERSION_PATTERN: Final[str] = ...
    +
    +class InvalidVersion(ValueError): ...
    +
    +@type_check_only
    +@final
    +class _InfinityType:
    +    def __hash__(self) -> int: ...
    +    def __eq__(self, other: object, /) -> TypeIs[_InfinityType]: ...
    +    def __ne__(self, other: object, /) -> bool: ...
    +    def __lt__(self, other: object, /) -> L[False]: ...
    +    def __le__(self, other: object, /) -> L[False]: ...
    +    def __gt__(self, other: object, /) -> L[True]: ...
    +    def __ge__(self, other: object, /) -> L[True]: ...
    +    def __neg__(self) -> _NegativeInfinityType: ...
    +
    +Infinity: Final[_InfinityType] = ...
    +
    +@type_check_only
    +@final
    +class _NegativeInfinityType:
    +    def __hash__(self) -> int: ...
    +    def __eq__(self, other: object, /) -> TypeIs[_NegativeInfinityType]: ...
    +    def __ne__(self, other: object, /) -> bool: ...
    +    def __lt__(self, other: object, /) -> L[True]: ...
    +    def __le__(self, other: object, /) -> L[True]: ...
    +    def __gt__(self, other: object, /) -> L[False]: ...
    +    def __ge__(self, other: object, /) -> L[False]: ...
    +    def __neg__(self) -> _InfinityType: ...
    +
    +NegativeInfinity: Final[_NegativeInfinityType] = ...
    +
    +class _Version(NamedTuple):
    +    epoch: int
    +    release: tuple[int, ...]
    +    dev: tuple[str, int] | None
    +    pre: tuple[str, int] | None
    +    post: tuple[str, int] | None
    +    local: tuple[str | int, ...] | None
    +
    +class _BaseVersion(Generic[_CmpKeyT_co]):
    +    _key: _CmpKeyT_co
    +    def __hash__(self) -> int: ...
    +    def __eq__(self, other: _BaseVersion, /) -> bool: ...  # type: ignore[override]  # pyright: ignore[reportIncompatibleMethodOverride]
    +    def __ne__(self, other: _BaseVersion, /) -> bool: ...  # type: ignore[override]  # pyright: ignore[reportIncompatibleMethodOverride]
    +    def __lt__(self, other: _BaseVersion, /) -> bool: ...
    +    def __le__(self, other: _BaseVersion, /) -> bool: ...
    +    def __ge__(self, other: _BaseVersion, /) -> bool: ...
    +    def __gt__(self, other: _BaseVersion, /) -> bool: ...
    +    def _compare(self, /, other: _BaseVersion[_CmpKeyT], method: Callable[[_CmpKeyT_co, _CmpKeyT], bool]) -> bool: ...
    +
    +class LegacyVersion(_BaseVersion[tuple[L[-1], tuple[str, ...]]]):
    +    _version: Final[str]
    +    def __init__(self, /, version: str) -> None: ...
    +    @property
    +    def public(self) -> str: ...
    +    @property
    +    def base_version(self) -> str: ...
    +    @property
    +    def local(self) -> None: ...
    +    @property
    +    def is_prerelease(self) -> L[False]: ...
    +    @property
    +    def is_postrelease(self) -> L[False]: ...
    +
    +class Version(
    +    _BaseVersion[
    +        tuple[
    +            int,  # epoch
    +            tuple[int, ...],  # release
    +            tuple[str, int] | _InfinityType | _NegativeInfinityType,  # pre
    +            tuple[str, int] | _NegativeInfinityType,  # post
    +            tuple[str, int] | _InfinityType,  # dev
    +            tuple[tuple[int, L[""]] | tuple[_NegativeInfinityType, str], ...] | _NegativeInfinityType,  # local
    +        ],
    +    ],
    +):
    +    _regex: ClassVar[re.Pattern[str]] = ...
    +    _version: Final[str]
    +
    +    def __init__(self, /, version: str) -> None: ...
    +    @property
    +    def public(self) -> str: ...
    +    @property
    +    def base_version(self) -> str: ...
    +    @property
    +    def local(self) -> str | None: ...
    +    @property
    +    def is_prerelease(self) -> bool: ...
    +    @property
    +    def is_postrelease(self) -> bool: ...
    +
    +#
    +def parse(version: str) -> Version | LegacyVersion: ...
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/linalg/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/linalg/__init__.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..cc482cfc9579e2bd60c658822004e4b0698f18cb
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/linalg/__init__.py
    @@ -0,0 +1,95 @@
    +"""
    +``numpy.linalg``
    +================
    +
    +The NumPy linear algebra functions rely on BLAS and LAPACK to provide efficient
    +low level implementations of standard linear algebra algorithms. Those
    +libraries may be provided by NumPy itself using C versions of a subset of their
    +reference implementations but, when possible, highly optimized libraries that
    +take advantage of specialized processor functionality are preferred. Examples
    +of such libraries are OpenBLAS, MKL (TM), and ATLAS. Because those libraries
    +are multithreaded and processor dependent, environmental variables and external
    +packages such as threadpoolctl may be needed to control the number of threads
    +or specify the processor architecture.
    +
    +- OpenBLAS: https://www.openblas.net/
    +- threadpoolctl: https://github.com/joblib/threadpoolctl
    +
    +Please note that the most-used linear algebra functions in NumPy are present in
    +the main ``numpy`` namespace rather than in ``numpy.linalg``.  There are:
    +``dot``, ``vdot``, ``inner``, ``outer``, ``matmul``, ``tensordot``, ``einsum``,
    +``einsum_path`` and ``kron``.
    +
    +Functions present in numpy.linalg are listed below.
    +
    +
    +Matrix and vector products
    +--------------------------
    +
    +   cross
    +   multi_dot
    +   matrix_power
    +   tensordot
    +   matmul
    +
    +Decompositions
    +--------------
    +
    +   cholesky
    +   outer
    +   qr
    +   svd
    +   svdvals
    +
    +Matrix eigenvalues
    +------------------
    +
    +   eig
    +   eigh
    +   eigvals
    +   eigvalsh
    +
    +Norms and other numbers
    +-----------------------
    +
    +   norm
    +   matrix_norm
    +   vector_norm
    +   cond
    +   det
    +   matrix_rank
    +   slogdet
    +   trace (Array API compatible)
    +
    +Solving equations and inverting matrices
    +----------------------------------------
    +
    +   solve
    +   tensorsolve
    +   lstsq
    +   inv
    +   pinv
    +   tensorinv
    +
    +Other matrix operations
    +-----------------------
    +
    +   diagonal (Array API compatible)
    +   matrix_transpose (Array API compatible)
    +
    +Exceptions
    +----------
    +
    +   LinAlgError
    +
    +"""
    +# To get sub-modules
    +from . import _linalg
    +from ._linalg import *
    +
    +__all__ = _linalg.__all__.copy()  # noqa: PLE0605
    +
    +from numpy._pytesttester import PytestTester
    +
    +test = PytestTester(__name__)
    +del PytestTester
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/linalg/__init__.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/linalg/__init__.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..4d6aca76063721f176f178a532b564ef1cc19774
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/linalg/__init__.pyi
    @@ -0,0 +1,71 @@
    +from . import _linalg as _linalg, _umath_linalg as _umath_linalg
    +from ._linalg import (
    +    cholesky,
    +    cond,
    +    cross,
    +    det,
    +    diagonal,
    +    eig,
    +    eigh,
    +    eigvals,
    +    eigvalsh,
    +    inv,
    +    lstsq,
    +    matmul,
    +    matrix_norm,
    +    matrix_power,
    +    matrix_rank,
    +    matrix_transpose,
    +    multi_dot,
    +    norm,
    +    outer,
    +    pinv,
    +    qr,
    +    slogdet,
    +    solve,
    +    svd,
    +    svdvals,
    +    tensordot,
    +    tensorinv,
    +    tensorsolve,
    +    trace,
    +    vecdot,
    +    vector_norm,
    +)
    +
    +__all__ = [
    +    "LinAlgError",
    +    "cholesky",
    +    "cond",
    +    "cross",
    +    "det",
    +    "diagonal",
    +    "eig",
    +    "eigh",
    +    "eigvals",
    +    "eigvalsh",
    +    "inv",
    +    "lstsq",
    +    "matmul",
    +    "matrix_norm",
    +    "matrix_power",
    +    "matrix_rank",
    +    "matrix_transpose",
    +    "multi_dot",
    +    "norm",
    +    "outer",
    +    "pinv",
    +    "qr",
    +    "slogdet",
    +    "solve",
    +    "svd",
    +    "svdvals",
    +    "tensordot",
    +    "tensorinv",
    +    "tensorsolve",
    +    "trace",
    +    "vecdot",
    +    "vector_norm",
    +]
    +
    +class LinAlgError(ValueError): ...
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/linalg/_linalg.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/linalg/_linalg.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..6884c9b7ef8d88eae60f3008bb97584061d562a9
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/linalg/_linalg.py
    @@ -0,0 +1,3657 @@
    +"""Lite version of scipy.linalg.
    +
    +Notes
    +-----
    +This module is a lite version of the linalg.py module in SciPy which
    +contains high-level Python interface to the LAPACK library.  The lite
    +version only accesses the following LAPACK functions: dgesv, zgesv,
    +dgeev, zgeev, dgesdd, zgesdd, dgelsd, zgelsd, dsyevd, zheevd, dgetrf,
    +zgetrf, dpotrf, zpotrf, dgeqrf, zgeqrf, zungqr, dorgqr.
    +"""
    +
    +__all__ = ['matrix_power', 'solve', 'tensorsolve', 'tensorinv', 'inv',
    +           'cholesky', 'eigvals', 'eigvalsh', 'pinv', 'slogdet', 'det',
    +           'svd', 'svdvals', 'eig', 'eigh', 'lstsq', 'norm', 'qr', 'cond',
    +           'matrix_rank', 'LinAlgError', 'multi_dot', 'trace', 'diagonal',
    +           'cross', 'outer', 'tensordot', 'matmul', 'matrix_transpose',
    +           'matrix_norm', 'vector_norm', 'vecdot']
    +
    +import functools
    +import operator
    +import warnings
    +from typing import Any, NamedTuple
    +
    +from numpy._core import (
    +    abs,
    +    add,
    +    all,
    +    amax,
    +    amin,
    +    argsort,
    +    array,
    +    asanyarray,
    +    asarray,
    +    atleast_2d,
    +    cdouble,
    +    complexfloating,
    +    count_nonzero,
    +    cross as _core_cross,
    +    csingle,
    +    diagonal as _core_diagonal,
    +    divide,
    +    dot,
    +    double,
    +    empty,
    +    empty_like,
    +    errstate,
    +    finfo,
    +    inexact,
    +    inf,
    +    intc,
    +    intp,
    +    isfinite,
    +    isnan,
    +    matmul as _core_matmul,
    +    matrix_transpose as _core_matrix_transpose,
    +    moveaxis,
    +    multiply,
    +    newaxis,
    +    object_,
    +    outer as _core_outer,
    +    overrides,
    +    prod,
    +    reciprocal,
    +    sign,
    +    single,
    +    sort,
    +    sqrt,
    +    sum,
    +    swapaxes,
    +    tensordot as _core_tensordot,
    +    trace as _core_trace,
    +    transpose as _core_transpose,
    +    vecdot as _core_vecdot,
    +    zeros,
    +)
    +from numpy._globals import _NoValue
    +from numpy._typing import NDArray
    +from numpy._utils import set_module
    +from numpy.lib._twodim_base_impl import eye, triu
    +from numpy.lib.array_utils import normalize_axis_index, normalize_axis_tuple
    +from numpy.linalg import _umath_linalg
    +
    +
    +class EigResult(NamedTuple):
    +    eigenvalues: NDArray[Any]
    +    eigenvectors: NDArray[Any]
    +
    +class EighResult(NamedTuple):
    +    eigenvalues: NDArray[Any]
    +    eigenvectors: NDArray[Any]
    +
    +class QRResult(NamedTuple):
    +    Q: NDArray[Any]
    +    R: NDArray[Any]
    +
    +class SlogdetResult(NamedTuple):
    +    sign: NDArray[Any]
    +    logabsdet: NDArray[Any]
    +
    +class SVDResult(NamedTuple):
    +    U: NDArray[Any]
    +    S: NDArray[Any]
    +    Vh: NDArray[Any]
    +
    +
    +array_function_dispatch = functools.partial(
    +    overrides.array_function_dispatch, module='numpy.linalg'
    +)
    +
    +
    +fortran_int = intc
    +
    +
    +@set_module('numpy.linalg')
    +class LinAlgError(ValueError):
    +    """
    +    Generic Python-exception-derived object raised by linalg functions.
    +
    +    General purpose exception class, derived from Python's ValueError
    +    class, programmatically raised in linalg functions when a Linear
    +    Algebra-related condition would prevent further correct execution of the
    +    function.
    +
    +    Parameters
    +    ----------
    +    None
    +
    +    Examples
    +    --------
    +    >>> from numpy import linalg as LA
    +    >>> LA.inv(np.zeros((2,2)))
    +    Traceback (most recent call last):
    +      File "", line 1, in 
    +      File "...linalg.py", line 350,
    +        in inv return wrap(solve(a, identity(a.shape[0], dtype=a.dtype)))
    +      File "...linalg.py", line 249,
    +        in solve
    +        raise LinAlgError('Singular matrix')
    +    numpy.linalg.LinAlgError: Singular matrix
    +
    +    """
    +
    +
    +def _raise_linalgerror_singular(err, flag):
    +    raise LinAlgError("Singular matrix")
    +
    +def _raise_linalgerror_nonposdef(err, flag):
    +    raise LinAlgError("Matrix is not positive definite")
    +
    +def _raise_linalgerror_eigenvalues_nonconvergence(err, flag):
    +    raise LinAlgError("Eigenvalues did not converge")
    +
    +def _raise_linalgerror_svd_nonconvergence(err, flag):
    +    raise LinAlgError("SVD did not converge")
    +
    +def _raise_linalgerror_lstsq(err, flag):
    +    raise LinAlgError("SVD did not converge in Linear Least Squares")
    +
    +def _raise_linalgerror_qr(err, flag):
    +    raise LinAlgError("Incorrect argument found while performing "
    +                      "QR factorization")
    +
    +
    +def _makearray(a):
    +    new = asarray(a)
    +    wrap = getattr(a, "__array_wrap__", new.__array_wrap__)
    +    return new, wrap
    +
    +def isComplexType(t):
    +    return issubclass(t, complexfloating)
    +
    +
    +_real_types_map = {single: single,
    +                   double: double,
    +                   csingle: single,
    +                   cdouble: double}
    +
    +_complex_types_map = {single: csingle,
    +                      double: cdouble,
    +                      csingle: csingle,
    +                      cdouble: cdouble}
    +
    +def _realType(t, default=double):
    +    return _real_types_map.get(t, default)
    +
    +def _complexType(t, default=cdouble):
    +    return _complex_types_map.get(t, default)
    +
    +def _commonType(*arrays):
    +    # in lite version, use higher precision (always double or cdouble)
    +    result_type = single
    +    is_complex = False
    +    for a in arrays:
    +        type_ = a.dtype.type
    +        if issubclass(type_, inexact):
    +            if isComplexType(type_):
    +                is_complex = True
    +            rt = _realType(type_, default=None)
    +            if rt is double:
    +                result_type = double
    +            elif rt is None:
    +                # unsupported inexact scalar
    +                raise TypeError(f"array type {a.dtype.name} is unsupported in linalg")
    +        else:
    +            result_type = double
    +    if is_complex:
    +        result_type = _complex_types_map[result_type]
    +        return cdouble, result_type
    +    else:
    +        return double, result_type
    +
    +
    +def _to_native_byte_order(*arrays):
    +    ret = []
    +    for arr in arrays:
    +        if arr.dtype.byteorder not in ('=', '|'):
    +            ret.append(asarray(arr, dtype=arr.dtype.newbyteorder('=')))
    +        else:
    +            ret.append(arr)
    +    if len(ret) == 1:
    +        return ret[0]
    +    else:
    +        return ret
    +
    +
    +def _assert_2d(*arrays):
    +    for a in arrays:
    +        if a.ndim != 2:
    +            raise LinAlgError('%d-dimensional array given. Array must be '
    +                    'two-dimensional' % a.ndim)
    +
    +def _assert_stacked_2d(*arrays):
    +    for a in arrays:
    +        if a.ndim < 2:
    +            raise LinAlgError('%d-dimensional array given. Array must be '
    +                    'at least two-dimensional' % a.ndim)
    +
    +def _assert_stacked_square(*arrays):
    +    for a in arrays:
    +        try:
    +            m, n = a.shape[-2:]
    +        except ValueError:
    +            raise LinAlgError('%d-dimensional array given. Array must be '
    +                    'at least two-dimensional' % a.ndim)
    +        if m != n:
    +            raise LinAlgError('Last 2 dimensions of the array must be square')
    +
    +def _assert_finite(*arrays):
    +    for a in arrays:
    +        if not isfinite(a).all():
    +            raise LinAlgError("Array must not contain infs or NaNs")
    +
    +def _is_empty_2d(arr):
    +    # check size first for efficiency
    +    return arr.size == 0 and prod(arr.shape[-2:]) == 0
    +
    +
    +def transpose(a):
    +    """
    +    Transpose each matrix in a stack of matrices.
    +
    +    Unlike np.transpose, this only swaps the last two axes, rather than all of
    +    them
    +
    +    Parameters
    +    ----------
    +    a : (...,M,N) array_like
    +
    +    Returns
    +    -------
    +    aT : (...,N,M) ndarray
    +    """
    +    return swapaxes(a, -1, -2)
    +
    +# Linear equations
    +
    +def _tensorsolve_dispatcher(a, b, axes=None):
    +    return (a, b)
    +
    +
    +@array_function_dispatch(_tensorsolve_dispatcher)
    +def tensorsolve(a, b, axes=None):
    +    """
    +    Solve the tensor equation ``a x = b`` for x.
    +
    +    It is assumed that all indices of `x` are summed over in the product,
    +    together with the rightmost indices of `a`, as is done in, for example,
    +    ``tensordot(a, x, axes=x.ndim)``.
    +
    +    Parameters
    +    ----------
    +    a : array_like
    +        Coefficient tensor, of shape ``b.shape + Q``. `Q`, a tuple, equals
    +        the shape of that sub-tensor of `a` consisting of the appropriate
    +        number of its rightmost indices, and must be such that
    +        ``prod(Q) == prod(b.shape)`` (in which sense `a` is said to be
    +        'square').
    +    b : array_like
    +        Right-hand tensor, which can be of any shape.
    +    axes : tuple of ints, optional
    +        Axes in `a` to reorder to the right, before inversion.
    +        If None (default), no reordering is done.
    +
    +    Returns
    +    -------
    +    x : ndarray, shape Q
    +
    +    Raises
    +    ------
    +    LinAlgError
    +        If `a` is singular or not 'square' (in the above sense).
    +
    +    See Also
    +    --------
    +    numpy.tensordot, tensorinv, numpy.einsum
    +
    +    Examples
    +    --------
    +    >>> import numpy as np
    +    >>> a = np.eye(2*3*4).reshape((2*3, 4, 2, 3, 4))
    +    >>> rng = np.random.default_rng()
    +    >>> b = rng.normal(size=(2*3, 4))
    +    >>> x = np.linalg.tensorsolve(a, b)
    +    >>> x.shape
    +    (2, 3, 4)
    +    >>> np.allclose(np.tensordot(a, x, axes=3), b)
    +    True
    +
    +    """
    +    a, wrap = _makearray(a)
    +    b = asarray(b)
    +    an = a.ndim
    +
    +    if axes is not None:
    +        allaxes = list(range(an))
    +        for k in axes:
    +            allaxes.remove(k)
    +            allaxes.insert(an, k)
    +        a = a.transpose(allaxes)
    +
    +    oldshape = a.shape[-(an - b.ndim):]
    +    prod = 1
    +    for k in oldshape:
    +        prod *= k
    +
    +    if a.size != prod ** 2:
    +        raise LinAlgError(
    +            "Input arrays must satisfy the requirement \
    +            prod(a.shape[b.ndim:]) == prod(a.shape[:b.ndim])"
    +        )
    +
    +    a = a.reshape(prod, prod)
    +    b = b.ravel()
    +    res = wrap(solve(a, b))
    +    res.shape = oldshape
    +    return res
    +
    +
    +def _solve_dispatcher(a, b):
    +    return (a, b)
    +
    +
    +@array_function_dispatch(_solve_dispatcher)
    +def solve(a, b):
    +    """
    +    Solve a linear matrix equation, or system of linear scalar equations.
    +
    +    Computes the "exact" solution, `x`, of the well-determined, i.e., full
    +    rank, linear matrix equation `ax = b`.
    +
    +    Parameters
    +    ----------
    +    a : (..., M, M) array_like
    +        Coefficient matrix.
    +    b : {(M,), (..., M, K)}, array_like
    +        Ordinate or "dependent variable" values.
    +
    +    Returns
    +    -------
    +    x : {(..., M,), (..., M, K)} ndarray
    +        Solution to the system a x = b.  Returned shape is (..., M) if b is
    +        shape (M,) and (..., M, K) if b is (..., M, K), where the "..." part is
    +        broadcasted between a and b.
    +
    +    Raises
    +    ------
    +    LinAlgError
    +        If `a` is singular or not square.
    +
    +    See Also
    +    --------
    +    scipy.linalg.solve : Similar function in SciPy.
    +
    +    Notes
    +    -----
    +    Broadcasting rules apply, see the `numpy.linalg` documentation for
    +    details.
    +
    +    The solutions are computed using LAPACK routine ``_gesv``.
    +
    +    `a` must be square and of full-rank, i.e., all rows (or, equivalently,
    +    columns) must be linearly independent; if either is not true, use
    +    `lstsq` for the least-squares best "solution" of the
    +    system/equation.
    +
    +    .. versionchanged:: 2.0
    +
    +       The b array is only treated as a shape (M,) column vector if it is
    +       exactly 1-dimensional. In all other instances it is treated as a stack
    +       of (M, K) matrices. Previously b would be treated as a stack of (M,)
    +       vectors if b.ndim was equal to a.ndim - 1.
    +
    +    References
    +    ----------
    +    .. [1] G. Strang, *Linear Algebra and Its Applications*, 2nd Ed., Orlando,
    +           FL, Academic Press, Inc., 1980, pg. 22.
    +
    +    Examples
    +    --------
    +    Solve the system of equations:
    +    ``x0 + 2 * x1 = 1`` and
    +    ``3 * x0 + 5 * x1 = 2``:
    +
    +    >>> import numpy as np
    +    >>> a = np.array([[1, 2], [3, 5]])
    +    >>> b = np.array([1, 2])
    +    >>> x = np.linalg.solve(a, b)
    +    >>> x
    +    array([-1.,  1.])
    +
    +    Check that the solution is correct:
    +
    +    >>> np.allclose(np.dot(a, x), b)
    +    True
    +
    +    """
    +    a, _ = _makearray(a)
    +    _assert_stacked_square(a)
    +    b, wrap = _makearray(b)
    +    t, result_t = _commonType(a, b)
    +
    +    # We use the b = (..., M,) logic, only if the number of extra dimensions
    +    # match exactly
    +    if b.ndim == 1:
    +        gufunc = _umath_linalg.solve1
    +    else:
    +        gufunc = _umath_linalg.solve
    +
    +    signature = 'DD->D' if isComplexType(t) else 'dd->d'
    +    with errstate(call=_raise_linalgerror_singular, invalid='call',
    +                  over='ignore', divide='ignore', under='ignore'):
    +        r = gufunc(a, b, signature=signature)
    +
    +    return wrap(r.astype(result_t, copy=False))
    +
    +
    +def _tensorinv_dispatcher(a, ind=None):
    +    return (a,)
    +
    +
    +@array_function_dispatch(_tensorinv_dispatcher)
    +def tensorinv(a, ind=2):
    +    """
    +    Compute the 'inverse' of an N-dimensional array.
    +
    +    The result is an inverse for `a` relative to the tensordot operation
    +    ``tensordot(a, b, ind)``, i. e., up to floating-point accuracy,
    +    ``tensordot(tensorinv(a), a, ind)`` is the "identity" tensor for the
    +    tensordot operation.
    +
    +    Parameters
    +    ----------
    +    a : array_like
    +        Tensor to 'invert'. Its shape must be 'square', i. e.,
    +        ``prod(a.shape[:ind]) == prod(a.shape[ind:])``.
    +    ind : int, optional
    +        Number of first indices that are involved in the inverse sum.
    +        Must be a positive integer, default is 2.
    +
    +    Returns
    +    -------
    +    b : ndarray
    +        `a`'s tensordot inverse, shape ``a.shape[ind:] + a.shape[:ind]``.
    +
    +    Raises
    +    ------
    +    LinAlgError
    +        If `a` is singular or not 'square' (in the above sense).
    +
    +    See Also
    +    --------
    +    numpy.tensordot, tensorsolve
    +
    +    Examples
    +    --------
    +    >>> import numpy as np
    +    >>> a = np.eye(4*6).reshape((4, 6, 8, 3))
    +    >>> ainv = np.linalg.tensorinv(a, ind=2)
    +    >>> ainv.shape
    +    (8, 3, 4, 6)
    +    >>> rng = np.random.default_rng()
    +    >>> b = rng.normal(size=(4, 6))
    +    >>> np.allclose(np.tensordot(ainv, b), np.linalg.tensorsolve(a, b))
    +    True
    +
    +    >>> a = np.eye(4*6).reshape((24, 8, 3))
    +    >>> ainv = np.linalg.tensorinv(a, ind=1)
    +    >>> ainv.shape
    +    (8, 3, 24)
    +    >>> rng = np.random.default_rng()
    +    >>> b = rng.normal(size=24)
    +    >>> np.allclose(np.tensordot(ainv, b, 1), np.linalg.tensorsolve(a, b))
    +    True
    +
    +    """
    +    a = asarray(a)
    +    oldshape = a.shape
    +    prod = 1
    +    if ind > 0:
    +        invshape = oldshape[ind:] + oldshape[:ind]
    +        for k in oldshape[ind:]:
    +            prod *= k
    +    else:
    +        raise ValueError("Invalid ind argument.")
    +    a = a.reshape(prod, -1)
    +    ia = inv(a)
    +    return ia.reshape(*invshape)
    +
    +
    +# Matrix inversion
    +
    +def _unary_dispatcher(a):
    +    return (a,)
    +
    +
    +@array_function_dispatch(_unary_dispatcher)
    +def inv(a):
    +    """
    +    Compute the inverse of a matrix.
    +
    +    Given a square matrix `a`, return the matrix `ainv` satisfying
    +    ``a @ ainv = ainv @ a = eye(a.shape[0])``.
    +
    +    Parameters
    +    ----------
    +    a : (..., M, M) array_like
    +        Matrix to be inverted.
    +
    +    Returns
    +    -------
    +    ainv : (..., M, M) ndarray or matrix
    +        Inverse of the matrix `a`.
    +
    +    Raises
    +    ------
    +    LinAlgError
    +        If `a` is not square or inversion fails.
    +
    +    See Also
    +    --------
    +    scipy.linalg.inv : Similar function in SciPy.
    +    numpy.linalg.cond : Compute the condition number of a matrix.
    +    numpy.linalg.svd : Compute the singular value decomposition of a matrix.
    +
    +    Notes
    +    -----
    +    Broadcasting rules apply, see the `numpy.linalg` documentation for
    +    details.
    +
    +    If `a` is detected to be singular, a `LinAlgError` is raised. If `a` is
    +    ill-conditioned, a `LinAlgError` may or may not be raised, and results may
    +    be inaccurate due to floating-point errors.
    +
    +    References
    +    ----------
    +    .. [1] Wikipedia, "Condition number",
    +           https://en.wikipedia.org/wiki/Condition_number
    +
    +    Examples
    +    --------
    +    >>> import numpy as np
    +    >>> from numpy.linalg import inv
    +    >>> a = np.array([[1., 2.], [3., 4.]])
    +    >>> ainv = inv(a)
    +    >>> np.allclose(a @ ainv, np.eye(2))
    +    True
    +    >>> np.allclose(ainv @ a, np.eye(2))
    +    True
    +
    +    If a is a matrix object, then the return value is a matrix as well:
    +
    +    >>> ainv = inv(np.matrix(a))
    +    >>> ainv
    +    matrix([[-2. ,  1. ],
    +            [ 1.5, -0.5]])
    +
    +    Inverses of several matrices can be computed at once:
    +
    +    >>> a = np.array([[[1., 2.], [3., 4.]], [[1, 3], [3, 5]]])
    +    >>> inv(a)
    +    array([[[-2.  ,  1.  ],
    +            [ 1.5 , -0.5 ]],
    +           [[-1.25,  0.75],
    +            [ 0.75, -0.25]]])
    +
    +    If a matrix is close to singular, the computed inverse may not satisfy
    +    ``a @ ainv = ainv @ a = eye(a.shape[0])`` even if a `LinAlgError`
    +    is not raised:
    +
    +    >>> a = np.array([[2,4,6],[2,0,2],[6,8,14]])
    +    >>> inv(a)  # No errors raised
    +    array([[-1.12589991e+15, -5.62949953e+14,  5.62949953e+14],
    +       [-1.12589991e+15, -5.62949953e+14,  5.62949953e+14],
    +       [ 1.12589991e+15,  5.62949953e+14, -5.62949953e+14]])
    +    >>> a @ inv(a)
    +    array([[ 0.   , -0.5  ,  0.   ],  # may vary
    +           [-0.5  ,  0.625,  0.25 ],
    +           [ 0.   ,  0.   ,  1.   ]])
    +
    +    To detect ill-conditioned matrices, you can use `numpy.linalg.cond` to
    +    compute its *condition number* [1]_. The larger the condition number, the
    +    more ill-conditioned the matrix is. As a rule of thumb, if the condition
    +    number ``cond(a) = 10**k``, then you may lose up to ``k`` digits of
    +    accuracy on top of what would be lost to the numerical method due to loss
    +    of precision from arithmetic methods.
    +
    +    >>> from numpy.linalg import cond
    +    >>> cond(a)
    +    np.float64(8.659885634118668e+17)  # may vary
    +
    +    It is also possible to detect ill-conditioning by inspecting the matrix's
    +    singular values directly. The ratio between the largest and the smallest
    +    singular value is the condition number:
    +
    +    >>> from numpy.linalg import svd
    +    >>> sigma = svd(a, compute_uv=False)  # Do not compute singular vectors
    +    >>> sigma.max()/sigma.min()
    +    8.659885634118668e+17  # may vary
    +
    +    """
    +    a, wrap = _makearray(a)
    +    _assert_stacked_square(a)
    +    t, result_t = _commonType(a)
    +
    +    signature = 'D->D' if isComplexType(t) else 'd->d'
    +    with errstate(call=_raise_linalgerror_singular, invalid='call',
    +                  over='ignore', divide='ignore', under='ignore'):
    +        ainv = _umath_linalg.inv(a, signature=signature)
    +    return wrap(ainv.astype(result_t, copy=False))
    +
    +
    +def _matrix_power_dispatcher(a, n):
    +    return (a,)
    +
    +
    +@array_function_dispatch(_matrix_power_dispatcher)
    +def matrix_power(a, n):
    +    """
    +    Raise a square matrix to the (integer) power `n`.
    +
    +    For positive integers `n`, the power is computed by repeated matrix
    +    squarings and matrix multiplications. If ``n == 0``, the identity matrix
    +    of the same shape as M is returned. If ``n < 0``, the inverse
    +    is computed and then raised to the ``abs(n)``.
    +
    +    .. note:: Stacks of object matrices are not currently supported.
    +
    +    Parameters
    +    ----------
    +    a : (..., M, M) array_like
    +        Matrix to be "powered".
    +    n : int
    +        The exponent can be any integer or long integer, positive,
    +        negative, or zero.
    +
    +    Returns
    +    -------
    +    a**n : (..., M, M) ndarray or matrix object
    +        The return value is the same shape and type as `M`;
    +        if the exponent is positive or zero then the type of the
    +        elements is the same as those of `M`. If the exponent is
    +        negative the elements are floating-point.
    +
    +    Raises
    +    ------
    +    LinAlgError
    +        For matrices that are not square or that (for negative powers) cannot
    +        be inverted numerically.
    +
    +    Examples
    +    --------
    +    >>> import numpy as np
    +    >>> from numpy.linalg import matrix_power
    +    >>> i = np.array([[0, 1], [-1, 0]]) # matrix equiv. of the imaginary unit
    +    >>> matrix_power(i, 3) # should = -i
    +    array([[ 0, -1],
    +           [ 1,  0]])
    +    >>> matrix_power(i, 0)
    +    array([[1, 0],
    +           [0, 1]])
    +    >>> matrix_power(i, -3) # should = 1/(-i) = i, but w/ f.p. elements
    +    array([[ 0.,  1.],
    +           [-1.,  0.]])
    +
    +    Somewhat more sophisticated example
    +
    +    >>> q = np.zeros((4, 4))
    +    >>> q[0:2, 0:2] = -i
    +    >>> q[2:4, 2:4] = i
    +    >>> q # one of the three quaternion units not equal to 1
    +    array([[ 0., -1.,  0.,  0.],
    +           [ 1.,  0.,  0.,  0.],
    +           [ 0.,  0.,  0.,  1.],
    +           [ 0.,  0., -1.,  0.]])
    +    >>> matrix_power(q, 2) # = -np.eye(4)
    +    array([[-1.,  0.,  0.,  0.],
    +           [ 0., -1.,  0.,  0.],
    +           [ 0.,  0., -1.,  0.],
    +           [ 0.,  0.,  0., -1.]])
    +
    +    """
    +    a = asanyarray(a)
    +    _assert_stacked_square(a)
    +
    +    try:
    +        n = operator.index(n)
    +    except TypeError as e:
    +        raise TypeError("exponent must be an integer") from e
    +
    +    # Fall back on dot for object arrays. Object arrays are not supported by
    +    # the current implementation of matmul using einsum
    +    if a.dtype != object:
    +        fmatmul = matmul
    +    elif a.ndim == 2:
    +        fmatmul = dot
    +    else:
    +        raise NotImplementedError(
    +            "matrix_power not supported for stacks of object arrays")
    +
    +    if n == 0:
    +        a = empty_like(a)
    +        a[...] = eye(a.shape[-2], dtype=a.dtype)
    +        return a
    +
    +    elif n < 0:
    +        a = inv(a)
    +        n = abs(n)
    +
    +    # short-cuts.
    +    if n == 1:
    +        return a
    +
    +    elif n == 2:
    +        return fmatmul(a, a)
    +
    +    elif n == 3:
    +        return fmatmul(fmatmul(a, a), a)
    +
    +    # Use binary decomposition to reduce the number of matrix multiplications.
    +    # Here, we iterate over the bits of n, from LSB to MSB, raise `a` to
    +    # increasing powers of 2, and multiply into the result as needed.
    +    z = result = None
    +    while n > 0:
    +        z = a if z is None else fmatmul(z, z)
    +        n, bit = divmod(n, 2)
    +        if bit:
    +            result = z if result is None else fmatmul(result, z)
    +
    +    return result
    +
    +
    +# Cholesky decomposition
    +
    +def _cholesky_dispatcher(a, /, *, upper=None):
    +    return (a,)
    +
    +
    +@array_function_dispatch(_cholesky_dispatcher)
    +def cholesky(a, /, *, upper=False):
    +    """
    +    Cholesky decomposition.
    +
    +    Return the lower or upper Cholesky decomposition, ``L * L.H`` or
    +    ``U.H * U``, of the square matrix ``a``, where ``L`` is lower-triangular,
    +    ``U`` is upper-triangular, and ``.H`` is the conjugate transpose operator
    +    (which is the ordinary transpose if ``a`` is real-valued). ``a`` must be
    +    Hermitian (symmetric if real-valued) and positive-definite. No checking is
    +    performed to verify whether ``a`` is Hermitian or not. In addition, only
    +    the lower or upper-triangular and diagonal elements of ``a`` are used.
    +    Only ``L`` or ``U`` is actually returned.
    +
    +    Parameters
    +    ----------
    +    a : (..., M, M) array_like
    +        Hermitian (symmetric if all elements are real), positive-definite
    +        input matrix.
    +    upper : bool
    +        If ``True``, the result must be the upper-triangular Cholesky factor.
    +        If ``False``, the result must be the lower-triangular Cholesky factor.
    +        Default: ``False``.
    +
    +    Returns
    +    -------
    +    L : (..., M, M) array_like
    +        Lower or upper-triangular Cholesky factor of `a`. Returns a matrix
    +        object if `a` is a matrix object.
    +
    +    Raises
    +    ------
    +    LinAlgError
    +       If the decomposition fails, for example, if `a` is not
    +       positive-definite.
    +
    +    See Also
    +    --------
    +    scipy.linalg.cholesky : Similar function in SciPy.
    +    scipy.linalg.cholesky_banded : Cholesky decompose a banded Hermitian
    +                                   positive-definite matrix.
    +    scipy.linalg.cho_factor : Cholesky decomposition of a matrix, to use in
    +                              `scipy.linalg.cho_solve`.
    +
    +    Notes
    +    -----
    +    Broadcasting rules apply, see the `numpy.linalg` documentation for
    +    details.
    +
    +    The Cholesky decomposition is often used as a fast way of solving
    +
    +    .. math:: A \\mathbf{x} = \\mathbf{b}
    +
    +    (when `A` is both Hermitian/symmetric and positive-definite).
    +
    +    First, we solve for :math:`\\mathbf{y}` in
    +
    +    .. math:: L \\mathbf{y} = \\mathbf{b},
    +
    +    and then for :math:`\\mathbf{x}` in
    +
    +    .. math:: L^{H} \\mathbf{x} = \\mathbf{y}.
    +
    +    Examples
    +    --------
    +    >>> import numpy as np
    +    >>> A = np.array([[1,-2j],[2j,5]])
    +    >>> A
    +    array([[ 1.+0.j, -0.-2.j],
    +           [ 0.+2.j,  5.+0.j]])
    +    >>> L = np.linalg.cholesky(A)
    +    >>> L
    +    array([[1.+0.j, 0.+0.j],
    +           [0.+2.j, 1.+0.j]])
    +    >>> np.dot(L, L.T.conj()) # verify that L * L.H = A
    +    array([[1.+0.j, 0.-2.j],
    +           [0.+2.j, 5.+0.j]])
    +    >>> A = [[1,-2j],[2j,5]] # what happens if A is only array_like?
    +    >>> np.linalg.cholesky(A) # an ndarray object is returned
    +    array([[1.+0.j, 0.+0.j],
    +           [0.+2.j, 1.+0.j]])
    +    >>> # But a matrix object is returned if A is a matrix object
    +    >>> np.linalg.cholesky(np.matrix(A))
    +    matrix([[ 1.+0.j,  0.+0.j],
    +            [ 0.+2.j,  1.+0.j]])
    +    >>> # The upper-triangular Cholesky factor can also be obtained.
    +    >>> np.linalg.cholesky(A, upper=True)
    +    array([[1.-0.j, 0.-2.j],
    +           [0.-0.j, 1.-0.j]])
    +
    +    """
    +    gufunc = _umath_linalg.cholesky_up if upper else _umath_linalg.cholesky_lo
    +    a, wrap = _makearray(a)
    +    _assert_stacked_square(a)
    +    t, result_t = _commonType(a)
    +    signature = 'D->D' if isComplexType(t) else 'd->d'
    +    with errstate(call=_raise_linalgerror_nonposdef, invalid='call',
    +                  over='ignore', divide='ignore', under='ignore'):
    +        r = gufunc(a, signature=signature)
    +    return wrap(r.astype(result_t, copy=False))
    +
    +
    +# outer product
    +
    +
    +def _outer_dispatcher(x1, x2):
    +    return (x1, x2)
    +
    +
    +@array_function_dispatch(_outer_dispatcher)
    +def outer(x1, x2, /):
    +    """
    +    Compute the outer product of two vectors.
    +
    +    This function is Array API compatible. Compared to ``np.outer``
    +    it accepts 1-dimensional inputs only.
    +
    +    Parameters
    +    ----------
    +    x1 : (M,) array_like
    +        One-dimensional input array of size ``N``.
    +        Must have a numeric data type.
    +    x2 : (N,) array_like
    +        One-dimensional input array of size ``M``.
    +        Must have a numeric data type.
    +
    +    Returns
    +    -------
    +    out : (M, N) ndarray
    +        ``out[i, j] = a[i] * b[j]``
    +
    +    See also
    +    --------
    +    outer
    +
    +    Examples
    +    --------
    +    Make a (*very* coarse) grid for computing a Mandelbrot set:
    +
    +    >>> rl = np.linalg.outer(np.ones((5,)), np.linspace(-2, 2, 5))
    +    >>> rl
    +    array([[-2., -1.,  0.,  1.,  2.],
    +           [-2., -1.,  0.,  1.,  2.],
    +           [-2., -1.,  0.,  1.,  2.],
    +           [-2., -1.,  0.,  1.,  2.],
    +           [-2., -1.,  0.,  1.,  2.]])
    +    >>> im = np.linalg.outer(1j*np.linspace(2, -2, 5), np.ones((5,)))
    +    >>> im
    +    array([[0.+2.j, 0.+2.j, 0.+2.j, 0.+2.j, 0.+2.j],
    +           [0.+1.j, 0.+1.j, 0.+1.j, 0.+1.j, 0.+1.j],
    +           [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
    +           [0.-1.j, 0.-1.j, 0.-1.j, 0.-1.j, 0.-1.j],
    +           [0.-2.j, 0.-2.j, 0.-2.j, 0.-2.j, 0.-2.j]])
    +    >>> grid = rl + im
    +    >>> grid
    +    array([[-2.+2.j, -1.+2.j,  0.+2.j,  1.+2.j,  2.+2.j],
    +           [-2.+1.j, -1.+1.j,  0.+1.j,  1.+1.j,  2.+1.j],
    +           [-2.+0.j, -1.+0.j,  0.+0.j,  1.+0.j,  2.+0.j],
    +           [-2.-1.j, -1.-1.j,  0.-1.j,  1.-1.j,  2.-1.j],
    +           [-2.-2.j, -1.-2.j,  0.-2.j,  1.-2.j,  2.-2.j]])
    +
    +    An example using a "vector" of letters:
    +
    +    >>> x = np.array(['a', 'b', 'c'], dtype=object)
    +    >>> np.linalg.outer(x, [1, 2, 3])
    +    array([['a', 'aa', 'aaa'],
    +           ['b', 'bb', 'bbb'],
    +           ['c', 'cc', 'ccc']], dtype=object)
    +
    +    """
    +    x1 = asanyarray(x1)
    +    x2 = asanyarray(x2)
    +    if x1.ndim != 1 or x2.ndim != 1:
    +        raise ValueError(
    +            "Input arrays must be one-dimensional, but they are "
    +            f"{x1.ndim=} and {x2.ndim=}."
    +        )
    +    return _core_outer(x1, x2, out=None)
    +
    +
    +# QR decomposition
    +
    +
    +def _qr_dispatcher(a, mode=None):
    +    return (a,)
    +
    +
    +@array_function_dispatch(_qr_dispatcher)
    +def qr(a, mode='reduced'):
    +    """
    +    Compute the qr factorization of a matrix.
    +
    +    Factor the matrix `a` as *qr*, where `q` is orthonormal and `r` is
    +    upper-triangular.
    +
    +    Parameters
    +    ----------
    +    a : array_like, shape (..., M, N)
    +        An array-like object with the dimensionality of at least 2.
    +    mode : {'reduced', 'complete', 'r', 'raw'}, optional, default: 'reduced'
    +        If K = min(M, N), then
    +
    +        * 'reduced'  : returns Q, R with dimensions (..., M, K), (..., K, N)
    +        * 'complete' : returns Q, R with dimensions (..., M, M), (..., M, N)
    +        * 'r'        : returns R only with dimensions (..., K, N)
    +        * 'raw'      : returns h, tau with dimensions (..., N, M), (..., K,)
    +
    +        The options 'reduced', 'complete, and 'raw' are new in numpy 1.8,
    +        see the notes for more information. The default is 'reduced', and to
    +        maintain backward compatibility with earlier versions of numpy both
    +        it and the old default 'full' can be omitted. Note that array h
    +        returned in 'raw' mode is transposed for calling Fortran. The
    +        'economic' mode is deprecated.  The modes 'full' and 'economic' may
    +        be passed using only the first letter for backwards compatibility,
    +        but all others must be spelled out. See the Notes for more
    +        explanation.
    +
    +
    +    Returns
    +    -------
    +    Q : ndarray of float or complex, optional
    +        A matrix with orthonormal columns. When mode = 'complete' the
    +        result is an orthogonal/unitary matrix depending on whether or not
    +        a is real/complex. The determinant may be either +/- 1 in that
    +        case. In case the number of dimensions in the input array is
    +        greater than 2 then a stack of the matrices with above properties
    +        is returned.
    +    R : ndarray of float or complex, optional
    +        The upper-triangular matrix or a stack of upper-triangular
    +        matrices if the number of dimensions in the input array is greater
    +        than 2.
    +    (h, tau) : ndarrays of np.double or np.cdouble, optional
    +        The array h contains the Householder reflectors that generate q
    +        along with r. The tau array contains scaling factors for the
    +        reflectors. In the deprecated  'economic' mode only h is returned.
    +
    +    Raises
    +    ------
    +    LinAlgError
    +        If factoring fails.
    +
    +    See Also
    +    --------
    +    scipy.linalg.qr : Similar function in SciPy.
    +    scipy.linalg.rq : Compute RQ decomposition of a matrix.
    +
    +    Notes
    +    -----
    +    When mode is 'reduced' or 'complete', the result will be a namedtuple with
    +    the attributes ``Q`` and ``R``.
    +
    +    This is an interface to the LAPACK routines ``dgeqrf``, ``zgeqrf``,
    +    ``dorgqr``, and ``zungqr``.
    +
    +    For more information on the qr factorization, see for example:
    +    https://en.wikipedia.org/wiki/QR_factorization
    +
    +    Subclasses of `ndarray` are preserved except for the 'raw' mode. So if
    +    `a` is of type `matrix`, all the return values will be matrices too.
    +
    +    New 'reduced', 'complete', and 'raw' options for mode were added in
    +    NumPy 1.8.0 and the old option 'full' was made an alias of 'reduced'.  In
    +    addition the options 'full' and 'economic' were deprecated.  Because
    +    'full' was the previous default and 'reduced' is the new default,
    +    backward compatibility can be maintained by letting `mode` default.
    +    The 'raw' option was added so that LAPACK routines that can multiply
    +    arrays by q using the Householder reflectors can be used. Note that in
    +    this case the returned arrays are of type np.double or np.cdouble and
    +    the h array is transposed to be FORTRAN compatible.  No routines using
    +    the 'raw' return are currently exposed by numpy, but some are available
    +    in lapack_lite and just await the necessary work.
    +
    +    Examples
    +    --------
    +    >>> import numpy as np
    +    >>> rng = np.random.default_rng()
    +    >>> a = rng.normal(size=(9, 6))
    +    >>> Q, R = np.linalg.qr(a)
    +    >>> np.allclose(a, np.dot(Q, R))  # a does equal QR
    +    True
    +    >>> R2 = np.linalg.qr(a, mode='r')
    +    >>> np.allclose(R, R2)  # mode='r' returns the same R as mode='full'
    +    True
    +    >>> a = np.random.normal(size=(3, 2, 2)) # Stack of 2 x 2 matrices as input
    +    >>> Q, R = np.linalg.qr(a)
    +    >>> Q.shape
    +    (3, 2, 2)
    +    >>> R.shape
    +    (3, 2, 2)
    +    >>> np.allclose(a, np.matmul(Q, R))
    +    True
    +
    +    Example illustrating a common use of `qr`: solving of least squares
    +    problems
    +
    +    What are the least-squares-best `m` and `y0` in ``y = y0 + mx`` for
    +    the following data: {(0,1), (1,0), (1,2), (2,1)}. (Graph the points
    +    and you'll see that it should be y0 = 0, m = 1.)  The answer is provided
    +    by solving the over-determined matrix equation ``Ax = b``, where::
    +
    +      A = array([[0, 1], [1, 1], [1, 1], [2, 1]])
    +      x = array([[y0], [m]])
    +      b = array([[1], [0], [2], [1]])
    +
    +    If A = QR such that Q is orthonormal (which is always possible via
    +    Gram-Schmidt), then ``x = inv(R) * (Q.T) * b``.  (In numpy practice,
    +    however, we simply use `lstsq`.)
    +
    +    >>> A = np.array([[0, 1], [1, 1], [1, 1], [2, 1]])
    +    >>> A
    +    array([[0, 1],
    +           [1, 1],
    +           [1, 1],
    +           [2, 1]])
    +    >>> b = np.array([1, 2, 2, 3])
    +    >>> Q, R = np.linalg.qr(A)
    +    >>> p = np.dot(Q.T, b)
    +    >>> np.dot(np.linalg.inv(R), p)
    +    array([  1.,   1.])
    +
    +    """
    +    if mode not in ('reduced', 'complete', 'r', 'raw'):
    +        if mode in ('f', 'full'):
    +            # 2013-04-01, 1.8
    +            msg = (
    +                "The 'full' option is deprecated in favor of 'reduced'.\n"
    +                "For backward compatibility let mode default."
    +            )
    +            warnings.warn(msg, DeprecationWarning, stacklevel=2)
    +            mode = 'reduced'
    +        elif mode in ('e', 'economic'):
    +            # 2013-04-01, 1.8
    +            msg = "The 'economic' option is deprecated."
    +            warnings.warn(msg, DeprecationWarning, stacklevel=2)
    +            mode = 'economic'
    +        else:
    +            raise ValueError(f"Unrecognized mode '{mode}'")
    +
    +    a, wrap = _makearray(a)
    +    _assert_stacked_2d(a)
    +    m, n = a.shape[-2:]
    +    t, result_t = _commonType(a)
    +    a = a.astype(t, copy=True)
    +    a = _to_native_byte_order(a)
    +    mn = min(m, n)
    +
    +    signature = 'D->D' if isComplexType(t) else 'd->d'
    +    with errstate(call=_raise_linalgerror_qr, invalid='call',
    +                  over='ignore', divide='ignore', under='ignore'):
    +        tau = _umath_linalg.qr_r_raw(a, signature=signature)
    +
    +    # handle modes that don't return q
    +    if mode == 'r':
    +        r = triu(a[..., :mn, :])
    +        r = r.astype(result_t, copy=False)
    +        return wrap(r)
    +
    +    if mode == 'raw':
    +        q = transpose(a)
    +        q = q.astype(result_t, copy=False)
    +        tau = tau.astype(result_t, copy=False)
    +        return wrap(q), tau
    +
    +    if mode == 'economic':
    +        a = a.astype(result_t, copy=False)
    +        return wrap(a)
    +
    +    # mc is the number of columns in the resulting q
    +    # matrix. If the mode is complete then it is
    +    # same as number of rows, and if the mode is reduced,
    +    # then it is the minimum of number of rows and columns.
    +    if mode == 'complete' and m > n:
    +        mc = m
    +        gufunc = _umath_linalg.qr_complete
    +    else:
    +        mc = mn
    +        gufunc = _umath_linalg.qr_reduced
    +
    +    signature = 'DD->D' if isComplexType(t) else 'dd->d'
    +    with errstate(call=_raise_linalgerror_qr, invalid='call',
    +                  over='ignore', divide='ignore', under='ignore'):
    +        q = gufunc(a, tau, signature=signature)
    +    r = triu(a[..., :mc, :])
    +
    +    q = q.astype(result_t, copy=False)
    +    r = r.astype(result_t, copy=False)
    +
    +    return QRResult(wrap(q), wrap(r))
    +
    +# Eigenvalues
    +
    +
    +@array_function_dispatch(_unary_dispatcher)
    +def eigvals(a):
    +    """
    +    Compute the eigenvalues of a general matrix.
    +
    +    Main difference between `eigvals` and `eig`: the eigenvectors aren't
    +    returned.
    +
    +    Parameters
    +    ----------
    +    a : (..., M, M) array_like
    +        A complex- or real-valued matrix whose eigenvalues will be computed.
    +
    +    Returns
    +    -------
    +    w : (..., M,) ndarray
    +        The eigenvalues, each repeated according to its multiplicity.
    +        They are not necessarily ordered, nor are they necessarily
    +        real for real matrices.
    +
    +    Raises
    +    ------
    +    LinAlgError
    +        If the eigenvalue computation does not converge.
    +
    +    See Also
    +    --------
    +    eig : eigenvalues and right eigenvectors of general arrays
    +    eigvalsh : eigenvalues of real symmetric or complex Hermitian
    +               (conjugate symmetric) arrays.
    +    eigh : eigenvalues and eigenvectors of real symmetric or complex
    +           Hermitian (conjugate symmetric) arrays.
    +    scipy.linalg.eigvals : Similar function in SciPy.
    +
    +    Notes
    +    -----
    +    Broadcasting rules apply, see the `numpy.linalg` documentation for
    +    details.
    +
    +    This is implemented using the ``_geev`` LAPACK routines which compute
    +    the eigenvalues and eigenvectors of general square arrays.
    +
    +    Examples
    +    --------
    +    Illustration, using the fact that the eigenvalues of a diagonal matrix
    +    are its diagonal elements, that multiplying a matrix on the left
    +    by an orthogonal matrix, `Q`, and on the right by `Q.T` (the transpose
    +    of `Q`), preserves the eigenvalues of the "middle" matrix. In other words,
    +    if `Q` is orthogonal, then ``Q * A * Q.T`` has the same eigenvalues as
    +    ``A``:
    +
    +    >>> import numpy as np
    +    >>> from numpy import linalg as LA
    +    >>> x = np.random.random()
    +    >>> Q = np.array([[np.cos(x), -np.sin(x)], [np.sin(x), np.cos(x)]])
    +    >>> LA.norm(Q[0, :]), LA.norm(Q[1, :]), np.dot(Q[0, :],Q[1, :])
    +    (1.0, 1.0, 0.0)
    +
    +    Now multiply a diagonal matrix by ``Q`` on one side and
    +    by ``Q.T`` on the other:
    +
    +    >>> D = np.diag((-1,1))
    +    >>> LA.eigvals(D)
    +    array([-1.,  1.])
    +    >>> A = np.dot(Q, D)
    +    >>> A = np.dot(A, Q.T)
    +    >>> LA.eigvals(A)
    +    array([ 1., -1.]) # random
    +
    +    """
    +    a, wrap = _makearray(a)
    +    _assert_stacked_square(a)
    +    _assert_finite(a)
    +    t, result_t = _commonType(a)
    +
    +    signature = 'D->D' if isComplexType(t) else 'd->D'
    +    with errstate(call=_raise_linalgerror_eigenvalues_nonconvergence,
    +                  invalid='call', over='ignore', divide='ignore',
    +                  under='ignore'):
    +        w = _umath_linalg.eigvals(a, signature=signature)
    +
    +    if not isComplexType(t):
    +        if all(w.imag == 0):
    +            w = w.real
    +            result_t = _realType(result_t)
    +        else:
    +            result_t = _complexType(result_t)
    +
    +    return w.astype(result_t, copy=False)
    +
    +
    +def _eigvalsh_dispatcher(a, UPLO=None):
    +    return (a,)
    +
    +
    +@array_function_dispatch(_eigvalsh_dispatcher)
    +def eigvalsh(a, UPLO='L'):
    +    """
    +    Compute the eigenvalues of a complex Hermitian or real symmetric matrix.
    +
    +    Main difference from eigh: the eigenvectors are not computed.
    +
    +    Parameters
    +    ----------
    +    a : (..., M, M) array_like
    +        A complex- or real-valued matrix whose eigenvalues are to be
    +        computed.
    +    UPLO : {'L', 'U'}, optional
    +        Specifies whether the calculation is done with the lower triangular
    +        part of `a` ('L', default) or the upper triangular part ('U').
    +        Irrespective of this value only the real parts of the diagonal will
    +        be considered in the computation to preserve the notion of a Hermitian
    +        matrix. It therefore follows that the imaginary part of the diagonal
    +        will always be treated as zero.
    +
    +    Returns
    +    -------
    +    w : (..., M,) ndarray
    +        The eigenvalues in ascending order, each repeated according to
    +        its multiplicity.
    +
    +    Raises
    +    ------
    +    LinAlgError
    +        If the eigenvalue computation does not converge.
    +
    +    See Also
    +    --------
    +    eigh : eigenvalues and eigenvectors of real symmetric or complex Hermitian
    +           (conjugate symmetric) arrays.
    +    eigvals : eigenvalues of general real or complex arrays.
    +    eig : eigenvalues and right eigenvectors of general real or complex
    +          arrays.
    +    scipy.linalg.eigvalsh : Similar function in SciPy.
    +
    +    Notes
    +    -----
    +    Broadcasting rules apply, see the `numpy.linalg` documentation for
    +    details.
    +
    +    The eigenvalues are computed using LAPACK routines ``_syevd``, ``_heevd``.
    +
    +    Examples
    +    --------
    +    >>> import numpy as np
    +    >>> from numpy import linalg as LA
    +    >>> a = np.array([[1, -2j], [2j, 5]])
    +    >>> LA.eigvalsh(a)
    +    array([ 0.17157288,  5.82842712]) # may vary
    +
    +    >>> # demonstrate the treatment of the imaginary part of the diagonal
    +    >>> a = np.array([[5+2j, 9-2j], [0+2j, 2-1j]])
    +    >>> a
    +    array([[5.+2.j, 9.-2.j],
    +           [0.+2.j, 2.-1.j]])
    +    >>> # with UPLO='L' this is numerically equivalent to using LA.eigvals()
    +    >>> # with:
    +    >>> b = np.array([[5.+0.j, 0.-2.j], [0.+2.j, 2.-0.j]])
    +    >>> b
    +    array([[5.+0.j, 0.-2.j],
    +           [0.+2.j, 2.+0.j]])
    +    >>> wa = LA.eigvalsh(a)
    +    >>> wb = LA.eigvals(b)
    +    >>> wa
    +    array([1., 6.])
    +    >>> wb
    +    array([6.+0.j, 1.+0.j])
    +
    +    """
    +    UPLO = UPLO.upper()
    +    if UPLO not in ('L', 'U'):
    +        raise ValueError("UPLO argument must be 'L' or 'U'")
    +
    +    if UPLO == 'L':
    +        gufunc = _umath_linalg.eigvalsh_lo
    +    else:
    +        gufunc = _umath_linalg.eigvalsh_up
    +
    +    a, wrap = _makearray(a)
    +    _assert_stacked_square(a)
    +    t, result_t = _commonType(a)
    +    signature = 'D->d' if isComplexType(t) else 'd->d'
    +    with errstate(call=_raise_linalgerror_eigenvalues_nonconvergence,
    +                  invalid='call', over='ignore', divide='ignore',
    +                  under='ignore'):
    +        w = gufunc(a, signature=signature)
    +    return w.astype(_realType(result_t), copy=False)
    +
    +
    +# Eigenvectors
    +
    +
    +@array_function_dispatch(_unary_dispatcher)
    +def eig(a):
    +    """
    +    Compute the eigenvalues and right eigenvectors of a square array.
    +
    +    Parameters
    +    ----------
    +    a : (..., M, M) array
    +        Matrices for which the eigenvalues and right eigenvectors will
    +        be computed
    +
    +    Returns
    +    -------
    +    A namedtuple with the following attributes:
    +
    +    eigenvalues : (..., M) array
    +        The eigenvalues, each repeated according to its multiplicity.
    +        The eigenvalues are not necessarily ordered. The resulting
    +        array will be of complex type, unless the imaginary part is
    +        zero in which case it will be cast to a real type. When `a`
    +        is real the resulting eigenvalues will be real (0 imaginary
    +        part) or occur in conjugate pairs
    +
    +    eigenvectors : (..., M, M) array
    +        The normalized (unit "length") eigenvectors, such that the
    +        column ``eigenvectors[:,i]`` is the eigenvector corresponding to the
    +        eigenvalue ``eigenvalues[i]``.
    +
    +    Raises
    +    ------
    +    LinAlgError
    +        If the eigenvalue computation does not converge.
    +
    +    See Also
    +    --------
    +    eigvals : eigenvalues of a non-symmetric array.
    +    eigh : eigenvalues and eigenvectors of a real symmetric or complex
    +           Hermitian (conjugate symmetric) array.
    +    eigvalsh : eigenvalues of a real symmetric or complex Hermitian
    +               (conjugate symmetric) array.
    +    scipy.linalg.eig : Similar function in SciPy that also solves the
    +                       generalized eigenvalue problem.
    +    scipy.linalg.schur : Best choice for unitary and other non-Hermitian
    +                         normal matrices.
    +
    +    Notes
    +    -----
    +    Broadcasting rules apply, see the `numpy.linalg` documentation for
    +    details.
    +
    +    This is implemented using the ``_geev`` LAPACK routines which compute
    +    the eigenvalues and eigenvectors of general square arrays.
    +
    +    The number `w` is an eigenvalue of `a` if there exists a vector `v` such
    +    that ``a @ v = w * v``. Thus, the arrays `a`, `eigenvalues`, and
    +    `eigenvectors` satisfy the equations ``a @ eigenvectors[:,i] =
    +    eigenvalues[i] * eigenvectors[:,i]`` for :math:`i \\in \\{0,...,M-1\\}`.
    +
    +    The array `eigenvectors` may not be of maximum rank, that is, some of the
    +    columns may be linearly dependent, although round-off error may obscure
    +    that fact. If the eigenvalues are all different, then theoretically the
    +    eigenvectors are linearly independent and `a` can be diagonalized by a
    +    similarity transformation using `eigenvectors`, i.e, ``inv(eigenvectors) @
    +    a @ eigenvectors`` is diagonal.
    +
    +    For non-Hermitian normal matrices the SciPy function `scipy.linalg.schur`
    +    is preferred because the matrix `eigenvectors` is guaranteed to be
    +    unitary, which is not the case when using `eig`. The Schur factorization
    +    produces an upper triangular matrix rather than a diagonal matrix, but for
    +    normal matrices only the diagonal of the upper triangular matrix is
    +    needed, the rest is roundoff error.
    +
    +    Finally, it is emphasized that `eigenvectors` consists of the *right* (as
    +    in right-hand side) eigenvectors of `a`. A vector `y` satisfying ``y.T @ a
    +    = z * y.T`` for some number `z` is called a *left* eigenvector of `a`,
    +    and, in general, the left and right eigenvectors of a matrix are not
    +    necessarily the (perhaps conjugate) transposes of each other.
    +
    +    References
    +    ----------
    +    G. Strang, *Linear Algebra and Its Applications*, 2nd Ed., Orlando, FL,
    +    Academic Press, Inc., 1980, Various pp.
    +
    +    Examples
    +    --------
    +    >>> import numpy as np
    +    >>> from numpy import linalg as LA
    +
    +    (Almost) trivial example with real eigenvalues and eigenvectors.
    +
    +    >>> eigenvalues, eigenvectors = LA.eig(np.diag((1, 2, 3)))
    +    >>> eigenvalues
    +    array([1., 2., 3.])
    +    >>> eigenvectors
    +    array([[1., 0., 0.],
    +           [0., 1., 0.],
    +           [0., 0., 1.]])
    +
    +    Real matrix possessing complex eigenvalues and eigenvectors;
    +    note that the eigenvalues are complex conjugates of each other.
    +
    +    >>> eigenvalues, eigenvectors = LA.eig(np.array([[1, -1], [1, 1]]))
    +    >>> eigenvalues
    +    array([1.+1.j, 1.-1.j])
    +    >>> eigenvectors
    +    array([[0.70710678+0.j        , 0.70710678-0.j        ],
    +           [0.        -0.70710678j, 0.        +0.70710678j]])
    +
    +    Complex-valued matrix with real eigenvalues (but complex-valued
    +    eigenvectors); note that ``a.conj().T == a``, i.e., `a` is Hermitian.
    +
    +    >>> a = np.array([[1, 1j], [-1j, 1]])
    +    >>> eigenvalues, eigenvectors = LA.eig(a)
    +    >>> eigenvalues
    +    array([2.+0.j, 0.+0.j])
    +    >>> eigenvectors
    +    array([[ 0.        +0.70710678j,  0.70710678+0.j        ], # may vary
    +           [ 0.70710678+0.j        , -0.        +0.70710678j]])
    +
    +    Be careful about round-off error!
    +
    +    >>> a = np.array([[1 + 1e-9, 0], [0, 1 - 1e-9]])
    +    >>> # Theor. eigenvalues are 1 +/- 1e-9
    +    >>> eigenvalues, eigenvectors = LA.eig(a)
    +    >>> eigenvalues
    +    array([1., 1.])
    +    >>> eigenvectors
    +    array([[1., 0.],
    +           [0., 1.]])
    +
    +    """
    +    a, wrap = _makearray(a)
    +    _assert_stacked_square(a)
    +    _assert_finite(a)
    +    t, result_t = _commonType(a)
    +
    +    signature = 'D->DD' if isComplexType(t) else 'd->DD'
    +    with errstate(call=_raise_linalgerror_eigenvalues_nonconvergence,
    +                  invalid='call', over='ignore', divide='ignore',
    +                  under='ignore'):
    +        w, vt = _umath_linalg.eig(a, signature=signature)
    +
    +    if not isComplexType(t) and all(w.imag == 0.0):
    +        w = w.real
    +        vt = vt.real
    +        result_t = _realType(result_t)
    +    else:
    +        result_t = _complexType(result_t)
    +
    +    vt = vt.astype(result_t, copy=False)
    +    return EigResult(w.astype(result_t, copy=False), wrap(vt))
    +
    +
    +@array_function_dispatch(_eigvalsh_dispatcher)
    +def eigh(a, UPLO='L'):
    +    """
    +    Return the eigenvalues and eigenvectors of a complex Hermitian
    +    (conjugate symmetric) or a real symmetric matrix.
    +
    +    Returns two objects, a 1-D array containing the eigenvalues of `a`, and
    +    a 2-D square array or matrix (depending on the input type) of the
    +    corresponding eigenvectors (in columns).
    +
    +    Parameters
    +    ----------
    +    a : (..., M, M) array
    +        Hermitian or real symmetric matrices whose eigenvalues and
    +        eigenvectors are to be computed.
    +    UPLO : {'L', 'U'}, optional
    +        Specifies whether the calculation is done with the lower triangular
    +        part of `a` ('L', default) or the upper triangular part ('U').
    +        Irrespective of this value only the real parts of the diagonal will
    +        be considered in the computation to preserve the notion of a Hermitian
    +        matrix. It therefore follows that the imaginary part of the diagonal
    +        will always be treated as zero.
    +
    +    Returns
    +    -------
    +    A namedtuple with the following attributes:
    +
    +    eigenvalues : (..., M) ndarray
    +        The eigenvalues in ascending order, each repeated according to
    +        its multiplicity.
    +    eigenvectors : {(..., M, M) ndarray, (..., M, M) matrix}
    +        The column ``eigenvectors[:, i]`` is the normalized eigenvector
    +        corresponding to the eigenvalue ``eigenvalues[i]``.  Will return a
    +        matrix object if `a` is a matrix object.
    +
    +    Raises
    +    ------
    +    LinAlgError
    +        If the eigenvalue computation does not converge.
    +
    +    See Also
    +    --------
    +    eigvalsh : eigenvalues of real symmetric or complex Hermitian
    +               (conjugate symmetric) arrays.
    +    eig : eigenvalues and right eigenvectors for non-symmetric arrays.
    +    eigvals : eigenvalues of non-symmetric arrays.
    +    scipy.linalg.eigh : Similar function in SciPy (but also solves the
    +                        generalized eigenvalue problem).
    +
    +    Notes
    +    -----
    +    Broadcasting rules apply, see the `numpy.linalg` documentation for
    +    details.
    +
    +    The eigenvalues/eigenvectors are computed using LAPACK routines ``_syevd``,
    +    ``_heevd``.
    +
    +    The eigenvalues of real symmetric or complex Hermitian matrices are always
    +    real. [1]_ The array `eigenvalues` of (column) eigenvectors is unitary and
    +    `a`, `eigenvalues`, and `eigenvectors` satisfy the equations ``dot(a,
    +    eigenvectors[:, i]) = eigenvalues[i] * eigenvectors[:, i]``.
    +
    +    References
    +    ----------
    +    .. [1] G. Strang, *Linear Algebra and Its Applications*, 2nd Ed., Orlando,
    +           FL, Academic Press, Inc., 1980, pg. 222.
    +
    +    Examples
    +    --------
    +    >>> import numpy as np
    +    >>> from numpy import linalg as LA
    +    >>> a = np.array([[1, -2j], [2j, 5]])
    +    >>> a
    +    array([[ 1.+0.j, -0.-2.j],
    +           [ 0.+2.j,  5.+0.j]])
    +    >>> eigenvalues, eigenvectors = LA.eigh(a)
    +    >>> eigenvalues
    +    array([0.17157288, 5.82842712])
    +    >>> eigenvectors
    +    array([[-0.92387953+0.j        , -0.38268343+0.j        ], # may vary
    +           [ 0.        +0.38268343j,  0.        -0.92387953j]])
    +
    +    >>> (np.dot(a, eigenvectors[:, 0]) -
    +    ... eigenvalues[0] * eigenvectors[:, 0])  # verify 1st eigenval/vec pair
    +    array([5.55111512e-17+0.0000000e+00j, 0.00000000e+00+1.2490009e-16j])
    +    >>> (np.dot(a, eigenvectors[:, 1]) -
    +    ... eigenvalues[1] * eigenvectors[:, 1])  # verify 2nd eigenval/vec pair
    +    array([0.+0.j, 0.+0.j])
    +
    +    >>> A = np.matrix(a) # what happens if input is a matrix object
    +    >>> A
    +    matrix([[ 1.+0.j, -0.-2.j],
    +            [ 0.+2.j,  5.+0.j]])
    +    >>> eigenvalues, eigenvectors = LA.eigh(A)
    +    >>> eigenvalues
    +    array([0.17157288, 5.82842712])
    +    >>> eigenvectors
    +    matrix([[-0.92387953+0.j        , -0.38268343+0.j        ], # may vary
    +            [ 0.        +0.38268343j,  0.        -0.92387953j]])
    +
    +    >>> # demonstrate the treatment of the imaginary part of the diagonal
    +    >>> a = np.array([[5+2j, 9-2j], [0+2j, 2-1j]])
    +    >>> a
    +    array([[5.+2.j, 9.-2.j],
    +           [0.+2.j, 2.-1.j]])
    +    >>> # with UPLO='L' this is numerically equivalent to using LA.eig() with:
    +    >>> b = np.array([[5.+0.j, 0.-2.j], [0.+2.j, 2.-0.j]])
    +    >>> b
    +    array([[5.+0.j, 0.-2.j],
    +           [0.+2.j, 2.+0.j]])
    +    >>> wa, va = LA.eigh(a)
    +    >>> wb, vb = LA.eig(b)
    +    >>> wa
    +    array([1., 6.])
    +    >>> wb
    +    array([6.+0.j, 1.+0.j])
    +    >>> va
    +    array([[-0.4472136 +0.j        , -0.89442719+0.j        ], # may vary
    +           [ 0.        +0.89442719j,  0.        -0.4472136j ]])
    +    >>> vb
    +    array([[ 0.89442719+0.j       , -0.        +0.4472136j],
    +           [-0.        +0.4472136j,  0.89442719+0.j       ]])
    +
    +    """
    +    UPLO = UPLO.upper()
    +    if UPLO not in ('L', 'U'):
    +        raise ValueError("UPLO argument must be 'L' or 'U'")
    +
    +    a, wrap = _makearray(a)
    +    _assert_stacked_square(a)
    +    t, result_t = _commonType(a)
    +
    +    if UPLO == 'L':
    +        gufunc = _umath_linalg.eigh_lo
    +    else:
    +        gufunc = _umath_linalg.eigh_up
    +
    +    signature = 'D->dD' if isComplexType(t) else 'd->dd'
    +    with errstate(call=_raise_linalgerror_eigenvalues_nonconvergence,
    +                  invalid='call', over='ignore', divide='ignore',
    +                  under='ignore'):
    +        w, vt = gufunc(a, signature=signature)
    +    w = w.astype(_realType(result_t), copy=False)
    +    vt = vt.astype(result_t, copy=False)
    +    return EighResult(w, wrap(vt))
    +
    +
    +# Singular value decomposition
    +
    +def _svd_dispatcher(a, full_matrices=None, compute_uv=None, hermitian=None):
    +    return (a,)
    +
    +
    +@array_function_dispatch(_svd_dispatcher)
    +def svd(a, full_matrices=True, compute_uv=True, hermitian=False):
    +    """
    +    Singular Value Decomposition.
    +
    +    When `a` is a 2D array, and ``full_matrices=False``, then it is
    +    factorized as ``u @ np.diag(s) @ vh = (u * s) @ vh``, where
    +    `u` and the Hermitian transpose of `vh` are 2D arrays with
    +    orthonormal columns and `s` is a 1D array of `a`'s singular
    +    values. When `a` is higher-dimensional, SVD is applied in
    +    stacked mode as explained below.
    +
    +    Parameters
    +    ----------
    +    a : (..., M, N) array_like
    +        A real or complex array with ``a.ndim >= 2``.
    +    full_matrices : bool, optional
    +        If True (default), `u` and `vh` have the shapes ``(..., M, M)`` and
    +        ``(..., N, N)``, respectively.  Otherwise, the shapes are
    +        ``(..., M, K)`` and ``(..., K, N)``, respectively, where
    +        ``K = min(M, N)``.
    +    compute_uv : bool, optional
    +        Whether or not to compute `u` and `vh` in addition to `s`.  True
    +        by default.
    +    hermitian : bool, optional
    +        If True, `a` is assumed to be Hermitian (symmetric if real-valued),
    +        enabling a more efficient method for finding singular values.
    +        Defaults to False.
    +
    +    Returns
    +    -------
    +    U : { (..., M, M), (..., M, K) } array
    +        Unitary array(s). The first ``a.ndim - 2`` dimensions have the same
    +        size as those of the input `a`. The size of the last two dimensions
    +        depends on the value of `full_matrices`. Only returned when
    +        `compute_uv` is True.
    +    S : (..., K) array
    +        Vector(s) with the singular values, within each vector sorted in
    +        descending order. The first ``a.ndim - 2`` dimensions have the same
    +        size as those of the input `a`.
    +    Vh : { (..., N, N), (..., K, N) } array
    +        Unitary array(s). The first ``a.ndim - 2`` dimensions have the same
    +        size as those of the input `a`. The size of the last two dimensions
    +        depends on the value of `full_matrices`. Only returned when
    +        `compute_uv` is True.
    +
    +    Raises
    +    ------
    +    LinAlgError
    +        If SVD computation does not converge.
    +
    +    See Also
    +    --------
    +    scipy.linalg.svd : Similar function in SciPy.
    +    scipy.linalg.svdvals : Compute singular values of a matrix.
    +
    +    Notes
    +    -----
    +    When `compute_uv` is True, the result is a namedtuple with the following
    +    attribute names: `U`, `S`, and `Vh`.
    +
    +    The decomposition is performed using LAPACK routine ``_gesdd``.
    +
    +    SVD is usually described for the factorization of a 2D matrix :math:`A`.
    +    The higher-dimensional case will be discussed below. In the 2D case, SVD is
    +    written as :math:`A = U S V^H`, where :math:`A = a`, :math:`U= u`,
    +    :math:`S= \\mathtt{np.diag}(s)` and :math:`V^H = vh`. The 1D array `s`
    +    contains the singular values of `a` and `u` and `vh` are unitary. The rows
    +    of `vh` are the eigenvectors of :math:`A^H A` and the columns of `u` are
    +    the eigenvectors of :math:`A A^H`. In both cases the corresponding
    +    (possibly non-zero) eigenvalues are given by ``s**2``.
    +
    +    If `a` has more than two dimensions, then broadcasting rules apply, as
    +    explained in :ref:`routines.linalg-broadcasting`. This means that SVD is
    +    working in "stacked" mode: it iterates over all indices of the first
    +    ``a.ndim - 2`` dimensions and for each combination SVD is applied to the
    +    last two indices. The matrix `a` can be reconstructed from the
    +    decomposition with either ``(u * s[..., None, :]) @ vh`` or
    +    ``u @ (s[..., None] * vh)``. (The ``@`` operator can be replaced by the
    +    function ``np.matmul`` for python versions below 3.5.)
    +
    +    If `a` is a ``matrix`` object (as opposed to an ``ndarray``), then so are
    +    all the return values.
    +
    +    Examples
    +    --------
    +    >>> import numpy as np
    +    >>> rng = np.random.default_rng()
    +    >>> a = rng.normal(size=(9, 6)) + 1j*rng.normal(size=(9, 6))
    +    >>> b = rng.normal(size=(2, 7, 8, 3)) + 1j*rng.normal(size=(2, 7, 8, 3))
    +
    +
    +    Reconstruction based on full SVD, 2D case:
    +
    +    >>> U, S, Vh = np.linalg.svd(a, full_matrices=True)
    +    >>> U.shape, S.shape, Vh.shape
    +    ((9, 9), (6,), (6, 6))
    +    >>> np.allclose(a, np.dot(U[:, :6] * S, Vh))
    +    True
    +    >>> smat = np.zeros((9, 6), dtype=complex)
    +    >>> smat[:6, :6] = np.diag(S)
    +    >>> np.allclose(a, np.dot(U, np.dot(smat, Vh)))
    +    True
    +
    +    Reconstruction based on reduced SVD, 2D case:
    +
    +    >>> U, S, Vh = np.linalg.svd(a, full_matrices=False)
    +    >>> U.shape, S.shape, Vh.shape
    +    ((9, 6), (6,), (6, 6))
    +    >>> np.allclose(a, np.dot(U * S, Vh))
    +    True
    +    >>> smat = np.diag(S)
    +    >>> np.allclose(a, np.dot(U, np.dot(smat, Vh)))
    +    True
    +
    +    Reconstruction based on full SVD, 4D case:
    +
    +    >>> U, S, Vh = np.linalg.svd(b, full_matrices=True)
    +    >>> U.shape, S.shape, Vh.shape
    +    ((2, 7, 8, 8), (2, 7, 3), (2, 7, 3, 3))
    +    >>> np.allclose(b, np.matmul(U[..., :3] * S[..., None, :], Vh))
    +    True
    +    >>> np.allclose(b, np.matmul(U[..., :3], S[..., None] * Vh))
    +    True
    +
    +    Reconstruction based on reduced SVD, 4D case:
    +
    +    >>> U, S, Vh = np.linalg.svd(b, full_matrices=False)
    +    >>> U.shape, S.shape, Vh.shape
    +    ((2, 7, 8, 3), (2, 7, 3), (2, 7, 3, 3))
    +    >>> np.allclose(b, np.matmul(U * S[..., None, :], Vh))
    +    True
    +    >>> np.allclose(b, np.matmul(U, S[..., None] * Vh))
    +    True
    +
    +    """
    +    import numpy as np
    +    a, wrap = _makearray(a)
    +
    +    if hermitian:
    +        # note: lapack svd returns eigenvalues with s ** 2 sorted descending,
    +        # but eig returns s sorted ascending, so we re-order the eigenvalues
    +        # and related arrays to have the correct order
    +        if compute_uv:
    +            s, u = eigh(a)
    +            sgn = sign(s)
    +            s = abs(s)
    +            sidx = argsort(s)[..., ::-1]
    +            sgn = np.take_along_axis(sgn, sidx, axis=-1)
    +            s = np.take_along_axis(s, sidx, axis=-1)
    +            u = np.take_along_axis(u, sidx[..., None, :], axis=-1)
    +            # singular values are unsigned, move the sign into v
    +            vt = transpose(u * sgn[..., None, :]).conjugate()
    +            return SVDResult(wrap(u), s, wrap(vt))
    +        else:
    +            s = eigvalsh(a)
    +            s = abs(s)
    +            return sort(s)[..., ::-1]
    +
    +    _assert_stacked_2d(a)
    +    t, result_t = _commonType(a)
    +
    +    m, n = a.shape[-2:]
    +    if compute_uv:
    +        if full_matrices:
    +            gufunc = _umath_linalg.svd_f
    +        else:
    +            gufunc = _umath_linalg.svd_s
    +
    +        signature = 'D->DdD' if isComplexType(t) else 'd->ddd'
    +        with errstate(call=_raise_linalgerror_svd_nonconvergence,
    +                      invalid='call', over='ignore', divide='ignore',
    +                      under='ignore'):
    +            u, s, vh = gufunc(a, signature=signature)
    +        u = u.astype(result_t, copy=False)
    +        s = s.astype(_realType(result_t), copy=False)
    +        vh = vh.astype(result_t, copy=False)
    +        return SVDResult(wrap(u), s, wrap(vh))
    +    else:
    +        signature = 'D->d' if isComplexType(t) else 'd->d'
    +        with errstate(call=_raise_linalgerror_svd_nonconvergence,
    +                      invalid='call', over='ignore', divide='ignore',
    +                      under='ignore'):
    +            s = _umath_linalg.svd(a, signature=signature)
    +        s = s.astype(_realType(result_t), copy=False)
    +        return s
    +
    +
    +def _svdvals_dispatcher(x):
    +    return (x,)
    +
    +
    +@array_function_dispatch(_svdvals_dispatcher)
    +def svdvals(x, /):
    +    """
    +    Returns the singular values of a matrix (or a stack of matrices) ``x``.
    +    When x is a stack of matrices, the function will compute the singular
    +    values for each matrix in the stack.
    +
    +    This function is Array API compatible.
    +
    +    Calling ``np.svdvals(x)`` to get singular values is the same as
    +    ``np.svd(x, compute_uv=False, hermitian=False)``.
    +
    +    Parameters
    +    ----------
    +    x : (..., M, N) array_like
    +        Input array having shape (..., M, N) and whose last two
    +        dimensions form matrices on which to perform singular value
    +        decomposition. Should have a floating-point data type.
    +
    +    Returns
    +    -------
    +    out : ndarray
    +        An array with shape (..., K) that contains the vector(s)
    +        of singular values of length K, where K = min(M, N).
    +
    +    See Also
    +    --------
    +    scipy.linalg.svdvals : Compute singular values of a matrix.
    +
    +    Examples
    +    --------
    +
    +    >>> np.linalg.svdvals([[1, 2, 3, 4, 5],
    +    ...                    [1, 4, 9, 16, 25],
    +    ...                    [1, 8, 27, 64, 125]])
    +    array([146.68862757,   5.57510612,   0.60393245])
    +
    +    Determine the rank of a matrix using singular values:
    +
    +    >>> s = np.linalg.svdvals([[1, 2, 3],
    +    ...                        [2, 4, 6],
    +    ...                        [-1, 1, -1]]); s
    +    array([8.38434191e+00, 1.64402274e+00, 2.31534378e-16])
    +    >>> np.count_nonzero(s > 1e-10)  # Matrix of rank 2
    +    2
    +
    +    """
    +    return svd(x, compute_uv=False, hermitian=False)
    +
    +
    +def _cond_dispatcher(x, p=None):
    +    return (x,)
    +
    +
    +@array_function_dispatch(_cond_dispatcher)
    +def cond(x, p=None):
    +    """
    +    Compute the condition number of a matrix.
    +
    +    This function is capable of returning the condition number using
    +    one of seven different norms, depending on the value of `p` (see
    +    Parameters below).
    +
    +    Parameters
    +    ----------
    +    x : (..., M, N) array_like
    +        The matrix whose condition number is sought.
    +    p : {None, 1, -1, 2, -2, inf, -inf, 'fro'}, optional
    +        Order of the norm used in the condition number computation:
    +
    +        =====  ============================
    +        p      norm for matrices
    +        =====  ============================
    +        None   2-norm, computed directly using the ``SVD``
    +        'fro'  Frobenius norm
    +        inf    max(sum(abs(x), axis=1))
    +        -inf   min(sum(abs(x), axis=1))
    +        1      max(sum(abs(x), axis=0))
    +        -1     min(sum(abs(x), axis=0))
    +        2      2-norm (largest sing. value)
    +        -2     smallest singular value
    +        =====  ============================
    +
    +        inf means the `numpy.inf` object, and the Frobenius norm is
    +        the root-of-sum-of-squares norm.
    +
    +    Returns
    +    -------
    +    c : {float, inf}
    +        The condition number of the matrix. May be infinite.
    +
    +    See Also
    +    --------
    +    numpy.linalg.norm
    +
    +    Notes
    +    -----
    +    The condition number of `x` is defined as the norm of `x` times the
    +    norm of the inverse of `x` [1]_; the norm can be the usual L2-norm
    +    (root-of-sum-of-squares) or one of a number of other matrix norms.
    +
    +    References
    +    ----------
    +    .. [1] G. Strang, *Linear Algebra and Its Applications*, Orlando, FL,
    +           Academic Press, Inc., 1980, pg. 285.
    +
    +    Examples
    +    --------
    +    >>> import numpy as np
    +    >>> from numpy import linalg as LA
    +    >>> a = np.array([[1, 0, -1], [0, 1, 0], [1, 0, 1]])
    +    >>> a
    +    array([[ 1,  0, -1],
    +           [ 0,  1,  0],
    +           [ 1,  0,  1]])
    +    >>> LA.cond(a)
    +    1.4142135623730951
    +    >>> LA.cond(a, 'fro')
    +    3.1622776601683795
    +    >>> LA.cond(a, np.inf)
    +    2.0
    +    >>> LA.cond(a, -np.inf)
    +    1.0
    +    >>> LA.cond(a, 1)
    +    2.0
    +    >>> LA.cond(a, -1)
    +    1.0
    +    >>> LA.cond(a, 2)
    +    1.4142135623730951
    +    >>> LA.cond(a, -2)
    +    0.70710678118654746 # may vary
    +    >>> (min(LA.svd(a, compute_uv=False)) *
    +    ... min(LA.svd(LA.inv(a), compute_uv=False)))
    +    0.70710678118654746 # may vary
    +
    +    """
    +    x = asarray(x)  # in case we have a matrix
    +    if _is_empty_2d(x):
    +        raise LinAlgError("cond is not defined on empty arrays")
    +    if p is None or p in {2, -2}:
    +        s = svd(x, compute_uv=False)
    +        with errstate(all='ignore'):
    +            if p == -2:
    +                r = s[..., -1] / s[..., 0]
    +            else:
    +                r = s[..., 0] / s[..., -1]
    +    else:
    +        # Call inv(x) ignoring errors. The result array will
    +        # contain nans in the entries where inversion failed.
    +        _assert_stacked_square(x)
    +        t, result_t = _commonType(x)
    +        result_t = _realType(result_t)  # condition number is always real
    +        signature = 'D->D' if isComplexType(t) else 'd->d'
    +        with errstate(all='ignore'):
    +            invx = _umath_linalg.inv(x, signature=signature)
    +            r = norm(x, p, axis=(-2, -1)) * norm(invx, p, axis=(-2, -1))
    +        r = r.astype(result_t, copy=False)
    +
    +    # Convert nans to infs unless the original array had nan entries
    +    nan_mask = isnan(r)
    +    if nan_mask.any():
    +        nan_mask &= ~isnan(x).any(axis=(-2, -1))
    +        if r.ndim > 0:
    +            r[nan_mask] = inf
    +        elif nan_mask:
    +            # Convention is to return scalars instead of 0d arrays.
    +            r = r.dtype.type(inf)
    +
    +    return r
    +
    +
    +def _matrix_rank_dispatcher(A, tol=None, hermitian=None, *, rtol=None):
    +    return (A,)
    +
    +
    +@array_function_dispatch(_matrix_rank_dispatcher)
    +def matrix_rank(A, tol=None, hermitian=False, *, rtol=None):
    +    """
    +    Return matrix rank of array using SVD method
    +
    +    Rank of the array is the number of singular values of the array that are
    +    greater than `tol`.
    +
    +    Parameters
    +    ----------
    +    A : {(M,), (..., M, N)} array_like
    +        Input vector or stack of matrices.
    +    tol : (...) array_like, float, optional
    +        Threshold below which SVD values are considered zero. If `tol` is
    +        None, and ``S`` is an array with singular values for `M`, and
    +        ``eps`` is the epsilon value for datatype of ``S``, then `tol` is
    +        set to ``S.max() * max(M, N) * eps``.
    +    hermitian : bool, optional
    +        If True, `A` is assumed to be Hermitian (symmetric if real-valued),
    +        enabling a more efficient method for finding singular values.
    +        Defaults to False.
    +    rtol : (...) array_like, float, optional
    +        Parameter for the relative tolerance component. Only ``tol`` or
    +        ``rtol`` can be set at a time. Defaults to ``max(M, N) * eps``.
    +
    +        .. versionadded:: 2.0.0
    +
    +    Returns
    +    -------
    +    rank : (...) array_like
    +        Rank of A.
    +
    +    Notes
    +    -----
    +    The default threshold to detect rank deficiency is a test on the magnitude
    +    of the singular values of `A`.  By default, we identify singular values
    +    less than ``S.max() * max(M, N) * eps`` as indicating rank deficiency
    +    (with the symbols defined above). This is the algorithm MATLAB uses [1]_.
    +    It also appears in *Numerical recipes* in the discussion of SVD solutions
    +    for linear least squares [2]_.
    +
    +    This default threshold is designed to detect rank deficiency accounting
    +    for the numerical errors of the SVD computation. Imagine that there
    +    is a column in `A` that is an exact (in floating point) linear combination
    +    of other columns in `A`. Computing the SVD on `A` will not produce
    +    a singular value exactly equal to 0 in general: any difference of
    +    the smallest SVD value from 0 will be caused by numerical imprecision
    +    in the calculation of the SVD. Our threshold for small SVD values takes
    +    this numerical imprecision into account, and the default threshold will
    +    detect such numerical rank deficiency. The threshold may declare a matrix
    +    `A` rank deficient even if the linear combination of some columns of `A`
    +    is not exactly equal to another column of `A` but only numerically very
    +    close to another column of `A`.
    +
    +    We chose our default threshold because it is in wide use. Other thresholds
    +    are possible.  For example, elsewhere in the 2007 edition of *Numerical
    +    recipes* there is an alternative threshold of ``S.max() *
    +    np.finfo(A.dtype).eps / 2. * np.sqrt(m + n + 1.)``. The authors describe
    +    this threshold as being based on "expected roundoff error" (p 71).
    +
    +    The thresholds above deal with floating point roundoff error in the
    +    calculation of the SVD.  However, you may have more information about
    +    the sources of error in `A` that would make you consider other tolerance
    +    values to detect *effective* rank deficiency. The most useful measure
    +    of the tolerance depends on the operations you intend to use on your
    +    matrix. For example, if your data come from uncertain measurements with
    +    uncertainties greater than floating point epsilon, choosing a tolerance
    +    near that uncertainty may be preferable. The tolerance may be absolute
    +    if the uncertainties are absolute rather than relative.
    +
    +    References
    +    ----------
    +    .. [1] MATLAB reference documentation, "Rank"
    +           https://www.mathworks.com/help/techdoc/ref/rank.html
    +    .. [2] W. H. Press, S. A. Teukolsky, W. T. Vetterling and B. P. Flannery,
    +           "Numerical Recipes (3rd edition)", Cambridge University Press, 2007,
    +           page 795.
    +
    +    Examples
    +    --------
    +    >>> import numpy as np
    +    >>> from numpy.linalg import matrix_rank
    +    >>> matrix_rank(np.eye(4)) # Full rank matrix
    +    4
    +    >>> I=np.eye(4); I[-1,-1] = 0. # rank deficient matrix
    +    >>> matrix_rank(I)
    +    3
    +    >>> matrix_rank(np.ones((4,))) # 1 dimension - rank 1 unless all 0
    +    1
    +    >>> matrix_rank(np.zeros((4,)))
    +    0
    +    """
    +    if rtol is not None and tol is not None:
    +        raise ValueError("`tol` and `rtol` can't be both set.")
    +
    +    A = asarray(A)
    +    if A.ndim < 2:
    +        return int(not all(A == 0))
    +    S = svd(A, compute_uv=False, hermitian=hermitian)
    +
    +    if tol is None:
    +        if rtol is None:
    +            rtol = max(A.shape[-2:]) * finfo(S.dtype).eps
    +        else:
    +            rtol = asarray(rtol)[..., newaxis]
    +        tol = S.max(axis=-1, keepdims=True) * rtol
    +    else:
    +        tol = asarray(tol)[..., newaxis]
    +
    +    return count_nonzero(S > tol, axis=-1)
    +
    +
    +# Generalized inverse
    +
    +def _pinv_dispatcher(a, rcond=None, hermitian=None, *, rtol=None):
    +    return (a,)
    +
    +
    +@array_function_dispatch(_pinv_dispatcher)
    +def pinv(a, rcond=None, hermitian=False, *, rtol=_NoValue):
    +    """
    +    Compute the (Moore-Penrose) pseudo-inverse of a matrix.
    +
    +    Calculate the generalized inverse of a matrix using its
    +    singular-value decomposition (SVD) and including all
    +    *large* singular values.
    +
    +    Parameters
    +    ----------
    +    a : (..., M, N) array_like
    +        Matrix or stack of matrices to be pseudo-inverted.
    +    rcond : (...) array_like of float, optional
    +        Cutoff for small singular values.
    +        Singular values less than or equal to
    +        ``rcond * largest_singular_value`` are set to zero.
    +        Broadcasts against the stack of matrices. Default: ``1e-15``.
    +    hermitian : bool, optional
    +        If True, `a` is assumed to be Hermitian (symmetric if real-valued),
    +        enabling a more efficient method for finding singular values.
    +        Defaults to False.
    +    rtol : (...) array_like of float, optional
    +        Same as `rcond`, but it's an Array API compatible parameter name.
    +        Only `rcond` or `rtol` can be set at a time. If none of them are
    +        provided then NumPy's ``1e-15`` default is used. If ``rtol=None``
    +        is passed then the API standard default is used.
    +
    +        .. versionadded:: 2.0.0
    +
    +    Returns
    +    -------
    +    B : (..., N, M) ndarray
    +        The pseudo-inverse of `a`. If `a` is a `matrix` instance, then so
    +        is `B`.
    +
    +    Raises
    +    ------
    +    LinAlgError
    +        If the SVD computation does not converge.
    +
    +    See Also
    +    --------
    +    scipy.linalg.pinv : Similar function in SciPy.
    +    scipy.linalg.pinvh : Compute the (Moore-Penrose) pseudo-inverse of a
    +                         Hermitian matrix.
    +
    +    Notes
    +    -----
    +    The pseudo-inverse of a matrix A, denoted :math:`A^+`, is
    +    defined as: "the matrix that 'solves' [the least-squares problem]
    +    :math:`Ax = b`," i.e., if :math:`\\bar{x}` is said solution, then
    +    :math:`A^+` is that matrix such that :math:`\\bar{x} = A^+b`.
    +
    +    It can be shown that if :math:`Q_1 \\Sigma Q_2^T = A` is the singular
    +    value decomposition of A, then
    +    :math:`A^+ = Q_2 \\Sigma^+ Q_1^T`, where :math:`Q_{1,2}` are
    +    orthogonal matrices, :math:`\\Sigma` is a diagonal matrix consisting
    +    of A's so-called singular values, (followed, typically, by
    +    zeros), and then :math:`\\Sigma^+` is simply the diagonal matrix
    +    consisting of the reciprocals of A's singular values
    +    (again, followed by zeros). [1]_
    +
    +    References
    +    ----------
    +    .. [1] G. Strang, *Linear Algebra and Its Applications*, 2nd Ed., Orlando,
    +           FL, Academic Press, Inc., 1980, pp. 139-142.
    +
    +    Examples
    +    --------
    +    The following example checks that ``a * a+ * a == a`` and
    +    ``a+ * a * a+ == a+``:
    +
    +    >>> import numpy as np
    +    >>> rng = np.random.default_rng()
    +    >>> a = rng.normal(size=(9, 6))
    +    >>> B = np.linalg.pinv(a)
    +    >>> np.allclose(a, np.dot(a, np.dot(B, a)))
    +    True
    +    >>> np.allclose(B, np.dot(B, np.dot(a, B)))
    +    True
    +
    +    """
    +    a, wrap = _makearray(a)
    +    if rcond is None:
    +        if rtol is _NoValue:
    +            rcond = 1e-15
    +        elif rtol is None:
    +            rcond = max(a.shape[-2:]) * finfo(a.dtype).eps
    +        else:
    +            rcond = rtol
    +    elif rtol is not _NoValue:
    +        raise ValueError("`rtol` and `rcond` can't be both set.")
    +    else:
    +        # NOTE: Deprecate `rcond` in a few versions.
    +        pass
    +
    +    rcond = asarray(rcond)
    +    if _is_empty_2d(a):
    +        m, n = a.shape[-2:]
    +        res = empty(a.shape[:-2] + (n, m), dtype=a.dtype)
    +        return wrap(res)
    +    a = a.conjugate()
    +    u, s, vt = svd(a, full_matrices=False, hermitian=hermitian)
    +
    +    # discard small singular values
    +    cutoff = rcond[..., newaxis] * amax(s, axis=-1, keepdims=True)
    +    large = s > cutoff
    +    s = divide(1, s, where=large, out=s)
    +    s[~large] = 0
    +
    +    res = matmul(transpose(vt), multiply(s[..., newaxis], transpose(u)))
    +    return wrap(res)
    +
    +
    +# Determinant
    +
    +
    +@array_function_dispatch(_unary_dispatcher)
    +def slogdet(a):
    +    """
    +    Compute the sign and (natural) logarithm of the determinant of an array.
    +
    +    If an array has a very small or very large determinant, then a call to
    +    `det` may overflow or underflow. This routine is more robust against such
    +    issues, because it computes the logarithm of the determinant rather than
    +    the determinant itself.
    +
    +    Parameters
    +    ----------
    +    a : (..., M, M) array_like
    +        Input array, has to be a square 2-D array.
    +
    +    Returns
    +    -------
    +    A namedtuple with the following attributes:
    +
    +    sign : (...) array_like
    +        A number representing the sign of the determinant. For a real matrix,
    +        this is 1, 0, or -1. For a complex matrix, this is a complex number
    +        with absolute value 1 (i.e., it is on the unit circle), or else 0.
    +    logabsdet : (...) array_like
    +        The natural log of the absolute value of the determinant.
    +
    +    If the determinant is zero, then `sign` will be 0 and `logabsdet`
    +    will be -inf. In all cases, the determinant is equal to
    +    ``sign * np.exp(logabsdet)``.
    +
    +    See Also
    +    --------
    +    det
    +
    +    Notes
    +    -----
    +    Broadcasting rules apply, see the `numpy.linalg` documentation for
    +    details.
    +
    +    The determinant is computed via LU factorization using the LAPACK
    +    routine ``z/dgetrf``.
    +
    +    Examples
    +    --------
    +    The determinant of a 2-D array ``[[a, b], [c, d]]`` is ``ad - bc``:
    +
    +    >>> import numpy as np
    +    >>> a = np.array([[1, 2], [3, 4]])
    +    >>> (sign, logabsdet) = np.linalg.slogdet(a)
    +    >>> (sign, logabsdet)
    +    (-1, 0.69314718055994529) # may vary
    +    >>> sign * np.exp(logabsdet)
    +    -2.0
    +
    +    Computing log-determinants for a stack of matrices:
    +
    +    >>> a = np.array([ [[1, 2], [3, 4]], [[1, 2], [2, 1]], [[1, 3], [3, 1]] ])
    +    >>> a.shape
    +    (3, 2, 2)
    +    >>> sign, logabsdet = np.linalg.slogdet(a)
    +    >>> (sign, logabsdet)
    +    (array([-1., -1., -1.]), array([ 0.69314718,  1.09861229,  2.07944154]))
    +    >>> sign * np.exp(logabsdet)
    +    array([-2., -3., -8.])
    +
    +    This routine succeeds where ordinary `det` does not:
    +
    +    >>> np.linalg.det(np.eye(500) * 0.1)
    +    0.0
    +    >>> np.linalg.slogdet(np.eye(500) * 0.1)
    +    (1, -1151.2925464970228)
    +
    +    """
    +    a = asarray(a)
    +    _assert_stacked_square(a)
    +    t, result_t = _commonType(a)
    +    real_t = _realType(result_t)
    +    signature = 'D->Dd' if isComplexType(t) else 'd->dd'
    +    sign, logdet = _umath_linalg.slogdet(a, signature=signature)
    +    sign = sign.astype(result_t, copy=False)
    +    logdet = logdet.astype(real_t, copy=False)
    +    return SlogdetResult(sign, logdet)
    +
    +
    +@array_function_dispatch(_unary_dispatcher)
    +def det(a):
    +    """
    +    Compute the determinant of an array.
    +
    +    Parameters
    +    ----------
    +    a : (..., M, M) array_like
    +        Input array to compute determinants for.
    +
    +    Returns
    +    -------
    +    det : (...) array_like
    +        Determinant of `a`.
    +
    +    See Also
    +    --------
    +    slogdet : Another way to represent the determinant, more suitable
    +      for large matrices where underflow/overflow may occur.
    +    scipy.linalg.det : Similar function in SciPy.
    +
    +    Notes
    +    -----
    +    Broadcasting rules apply, see the `numpy.linalg` documentation for
    +    details.
    +
    +    The determinant is computed via LU factorization using the LAPACK
    +    routine ``z/dgetrf``.
    +
    +    Examples
    +    --------
    +    The determinant of a 2-D array [[a, b], [c, d]] is ad - bc:
    +
    +    >>> import numpy as np
    +    >>> a = np.array([[1, 2], [3, 4]])
    +    >>> np.linalg.det(a)
    +    -2.0 # may vary
    +
    +    Computing determinants for a stack of matrices:
    +
    +    >>> a = np.array([ [[1, 2], [3, 4]], [[1, 2], [2, 1]], [[1, 3], [3, 1]] ])
    +    >>> a.shape
    +    (3, 2, 2)
    +    >>> np.linalg.det(a)
    +    array([-2., -3., -8.])
    +
    +    """
    +    a = asarray(a)
    +    _assert_stacked_square(a)
    +    t, result_t = _commonType(a)
    +    signature = 'D->D' if isComplexType(t) else 'd->d'
    +    r = _umath_linalg.det(a, signature=signature)
    +    r = r.astype(result_t, copy=False)
    +    return r
    +
    +
    +# Linear Least Squares
    +
    +def _lstsq_dispatcher(a, b, rcond=None):
    +    return (a, b)
    +
    +
    +@array_function_dispatch(_lstsq_dispatcher)
    +def lstsq(a, b, rcond=None):
    +    r"""
    +    Return the least-squares solution to a linear matrix equation.
    +
    +    Computes the vector `x` that approximately solves the equation
    +    ``a @ x = b``. The equation may be under-, well-, or over-determined
    +    (i.e., the number of linearly independent rows of `a` can be less than,
    +    equal to, or greater than its number of linearly independent columns).
    +    If `a` is square and of full rank, then `x` (but for round-off error)
    +    is the "exact" solution of the equation. Else, `x` minimizes the
    +    Euclidean 2-norm :math:`||b - ax||`. If there are multiple minimizing
    +    solutions, the one with the smallest 2-norm :math:`||x||` is returned.
    +
    +    Parameters
    +    ----------
    +    a : (M, N) array_like
    +        "Coefficient" matrix.
    +    b : {(M,), (M, K)} array_like
    +        Ordinate or "dependent variable" values. If `b` is two-dimensional,
    +        the least-squares solution is calculated for each of the `K` columns
    +        of `b`.
    +    rcond : float, optional
    +        Cut-off ratio for small singular values of `a`.
    +        For the purposes of rank determination, singular values are treated
    +        as zero if they are smaller than `rcond` times the largest singular
    +        value of `a`.
    +        The default uses the machine precision times ``max(M, N)``.  Passing
    +        ``-1`` will use machine precision.
    +
    +        .. versionchanged:: 2.0
    +            Previously, the default was ``-1``, but a warning was given that
    +            this would change.
    +
    +    Returns
    +    -------
    +    x : {(N,), (N, K)} ndarray
    +        Least-squares solution. If `b` is two-dimensional,
    +        the solutions are in the `K` columns of `x`.
    +    residuals : {(1,), (K,), (0,)} ndarray
    +        Sums of squared residuals: Squared Euclidean 2-norm for each column in
    +        ``b - a @ x``.
    +        If the rank of `a` is < N or M <= N, this is an empty array.
    +        If `b` is 1-dimensional, this is a (1,) shape array.
    +        Otherwise the shape is (K,).
    +    rank : int
    +        Rank of matrix `a`.
    +    s : (min(M, N),) ndarray
    +        Singular values of `a`.
    +
    +    Raises
    +    ------
    +    LinAlgError
    +        If computation does not converge.
    +
    +    See Also
    +    --------
    +    scipy.linalg.lstsq : Similar function in SciPy.
    +
    +    Notes
    +    -----
    +    If `b` is a matrix, then all array results are returned as matrices.
    +
    +    Examples
    +    --------
    +    Fit a line, ``y = mx + c``, through some noisy data-points:
    +
    +    >>> import numpy as np
    +    >>> x = np.array([0, 1, 2, 3])
    +    >>> y = np.array([-1, 0.2, 0.9, 2.1])
    +
    +    By examining the coefficients, we see that the line should have a
    +    gradient of roughly 1 and cut the y-axis at, more or less, -1.
    +
    +    We can rewrite the line equation as ``y = Ap``, where ``A = [[x 1]]``
    +    and ``p = [[m], [c]]``.  Now use `lstsq` to solve for `p`:
    +
    +    >>> A = np.vstack([x, np.ones(len(x))]).T
    +    >>> A
    +    array([[ 0.,  1.],
    +           [ 1.,  1.],
    +           [ 2.,  1.],
    +           [ 3.,  1.]])
    +
    +    >>> m, c = np.linalg.lstsq(A, y)[0]
    +    >>> m, c
    +    (1.0 -0.95) # may vary
    +
    +    Plot the data along with the fitted line:
    +
    +    >>> import matplotlib.pyplot as plt
    +    >>> _ = plt.plot(x, y, 'o', label='Original data', markersize=10)
    +    >>> _ = plt.plot(x, m*x + c, 'r', label='Fitted line')
    +    >>> _ = plt.legend()
    +    >>> plt.show()
    +
    +    """
    +    a, _ = _makearray(a)
    +    b, wrap = _makearray(b)
    +    is_1d = b.ndim == 1
    +    if is_1d:
    +        b = b[:, newaxis]
    +    _assert_2d(a, b)
    +    m, n = a.shape[-2:]
    +    m2, n_rhs = b.shape[-2:]
    +    if m != m2:
    +        raise LinAlgError('Incompatible dimensions')
    +
    +    t, result_t = _commonType(a, b)
    +    result_real_t = _realType(result_t)
    +
    +    if rcond is None:
    +        rcond = finfo(t).eps * max(n, m)
    +
    +    signature = 'DDd->Ddid' if isComplexType(t) else 'ddd->ddid'
    +    if n_rhs == 0:
    +        # lapack can't handle n_rhs = 0 - so allocate
    +        # the array one larger in that axis
    +        b = zeros(b.shape[:-2] + (m, n_rhs + 1), dtype=b.dtype)
    +
    +    with errstate(call=_raise_linalgerror_lstsq, invalid='call',
    +                  over='ignore', divide='ignore', under='ignore'):
    +        x, resids, rank, s = _umath_linalg.lstsq(a, b, rcond,
    +                                                 signature=signature)
    +    if m == 0:
    +        x[...] = 0
    +    if n_rhs == 0:
    +        # remove the item we added
    +        x = x[..., :n_rhs]
    +        resids = resids[..., :n_rhs]
    +
    +    # remove the axis we added
    +    if is_1d:
    +        x = x.squeeze(axis=-1)
    +        # we probably should squeeze resids too, but we can't
    +        # without breaking compatibility.
    +
    +    # as documented
    +    if rank != n or m <= n:
    +        resids = array([], result_real_t)
    +
    +    # coerce output arrays
    +    s = s.astype(result_real_t, copy=False)
    +    resids = resids.astype(result_real_t, copy=False)
    +    # Copying lets the memory in r_parts be freed
    +    x = x.astype(result_t, copy=True)
    +    return wrap(x), wrap(resids), rank, s
    +
    +
    +def _multi_svd_norm(x, row_axis, col_axis, op, initial=None):
    +    """Compute a function of the singular values of the 2-D matrices in `x`.
    +
    +    This is a private utility function used by `numpy.linalg.norm()`.
    +
    +    Parameters
    +    ----------
    +    x : ndarray
    +    row_axis, col_axis : int
    +        The axes of `x` that hold the 2-D matrices.
    +    op : callable
    +        This should be either numpy.amin or `numpy.amax` or `numpy.sum`.
    +
    +    Returns
    +    -------
    +    result : float or ndarray
    +        If `x` is 2-D, the return values is a float.
    +        Otherwise, it is an array with ``x.ndim - 2`` dimensions.
    +        The return values are either the minimum or maximum or sum of the
    +        singular values of the matrices, depending on whether `op`
    +        is `numpy.amin` or `numpy.amax` or `numpy.sum`.
    +
    +    """
    +    y = moveaxis(x, (row_axis, col_axis), (-2, -1))
    +    result = op(svd(y, compute_uv=False), axis=-1, initial=initial)
    +    return result
    +
    +
    +def _norm_dispatcher(x, ord=None, axis=None, keepdims=None):
    +    return (x,)
    +
    +
    +@array_function_dispatch(_norm_dispatcher)
    +def norm(x, ord=None, axis=None, keepdims=False):
    +    """
    +    Matrix or vector norm.
    +
    +    This function is able to return one of eight different matrix norms,
    +    or one of an infinite number of vector norms (described below), depending
    +    on the value of the ``ord`` parameter.
    +
    +    Parameters
    +    ----------
    +    x : array_like
    +        Input array.  If `axis` is None, `x` must be 1-D or 2-D, unless `ord`
    +        is None. If both `axis` and `ord` are None, the 2-norm of
    +        ``x.ravel`` will be returned.
    +    ord : {int, float, inf, -inf, 'fro', 'nuc'}, optional
    +        Order of the norm (see table under ``Notes`` for what values are
    +        supported for matrices and vectors respectively). inf means numpy's
    +        `inf` object. The default is None.
    +    axis : {None, int, 2-tuple of ints}, optional.
    +        If `axis` is an integer, it specifies the axis of `x` along which to
    +        compute the vector norms.  If `axis` is a 2-tuple, it specifies the
    +        axes that hold 2-D matrices, and the matrix norms of these matrices
    +        are computed.  If `axis` is None then either a vector norm (when `x`
    +        is 1-D) or a matrix norm (when `x` is 2-D) is returned. The default
    +        is None.
    +
    +    keepdims : bool, optional
    +        If this is set to True, the axes which are normed over are left in the
    +        result as dimensions with size one.  With this option the result will
    +        broadcast correctly against the original `x`.
    +
    +    Returns
    +    -------
    +    n : float or ndarray
    +        Norm of the matrix or vector(s).
    +
    +    See Also
    +    --------
    +    scipy.linalg.norm : Similar function in SciPy.
    +
    +    Notes
    +    -----
    +    For values of ``ord < 1``, the result is, strictly speaking, not a
    +    mathematical 'norm', but it may still be useful for various numerical
    +    purposes.
    +
    +    The following norms can be calculated:
    +
    +    =====  ============================  ==========================
    +    ord    norm for matrices             norm for vectors
    +    =====  ============================  ==========================
    +    None   Frobenius norm                2-norm
    +    'fro'  Frobenius norm                --
    +    'nuc'  nuclear norm                  --
    +    inf    max(sum(abs(x), axis=1))      max(abs(x))
    +    -inf   min(sum(abs(x), axis=1))      min(abs(x))
    +    0      --                            sum(x != 0)
    +    1      max(sum(abs(x), axis=0))      as below
    +    -1     min(sum(abs(x), axis=0))      as below
    +    2      2-norm (largest sing. value)  as below
    +    -2     smallest singular value       as below
    +    other  --                            sum(abs(x)**ord)**(1./ord)
    +    =====  ============================  ==========================
    +
    +    The Frobenius norm is given by [1]_:
    +
    +    :math:`||A||_F = [\\sum_{i,j} abs(a_{i,j})^2]^{1/2}`
    +
    +    The nuclear norm is the sum of the singular values.
    +
    +    Both the Frobenius and nuclear norm orders are only defined for
    +    matrices and raise a ValueError when ``x.ndim != 2``.
    +
    +    References
    +    ----------
    +    .. [1] G. H. Golub and C. F. Van Loan, *Matrix Computations*,
    +           Baltimore, MD, Johns Hopkins University Press, 1985, pg. 15
    +
    +    Examples
    +    --------
    +
    +    >>> import numpy as np
    +    >>> from numpy import linalg as LA
    +    >>> a = np.arange(9) - 4
    +    >>> a
    +    array([-4, -3, -2, ...,  2,  3,  4])
    +    >>> b = a.reshape((3, 3))
    +    >>> b
    +    array([[-4, -3, -2],
    +           [-1,  0,  1],
    +           [ 2,  3,  4]])
    +
    +    >>> LA.norm(a)
    +    7.745966692414834
    +    >>> LA.norm(b)
    +    7.745966692414834
    +    >>> LA.norm(b, 'fro')
    +    7.745966692414834
    +    >>> LA.norm(a, np.inf)
    +    4.0
    +    >>> LA.norm(b, np.inf)
    +    9.0
    +    >>> LA.norm(a, -np.inf)
    +    0.0
    +    >>> LA.norm(b, -np.inf)
    +    2.0
    +
    +    >>> LA.norm(a, 1)
    +    20.0
    +    >>> LA.norm(b, 1)
    +    7.0
    +    >>> LA.norm(a, -1)
    +    -4.6566128774142013e-010
    +    >>> LA.norm(b, -1)
    +    6.0
    +    >>> LA.norm(a, 2)
    +    7.745966692414834
    +    >>> LA.norm(b, 2)
    +    7.3484692283495345
    +
    +    >>> LA.norm(a, -2)
    +    0.0
    +    >>> LA.norm(b, -2)
    +    1.8570331885190563e-016 # may vary
    +    >>> LA.norm(a, 3)
    +    5.8480354764257312 # may vary
    +    >>> LA.norm(a, -3)
    +    0.0
    +
    +    Using the `axis` argument to compute vector norms:
    +
    +    >>> c = np.array([[ 1, 2, 3],
    +    ...               [-1, 1, 4]])
    +    >>> LA.norm(c, axis=0)
    +    array([ 1.41421356,  2.23606798,  5.        ])
    +    >>> LA.norm(c, axis=1)
    +    array([ 3.74165739,  4.24264069])
    +    >>> LA.norm(c, ord=1, axis=1)
    +    array([ 6.,  6.])
    +
    +    Using the `axis` argument to compute matrix norms:
    +
    +    >>> m = np.arange(8).reshape(2,2,2)
    +    >>> LA.norm(m, axis=(1,2))
    +    array([  3.74165739,  11.22497216])
    +    >>> LA.norm(m[0, :, :]), LA.norm(m[1, :, :])
    +    (3.7416573867739413, 11.224972160321824)
    +
    +    """
    +    x = asarray(x)
    +
    +    if not issubclass(x.dtype.type, (inexact, object_)):
    +        x = x.astype(float)
    +
    +    # Immediately handle some default, simple, fast, and common cases.
    +    if axis is None:
    +        ndim = x.ndim
    +        if (
    +            (ord is None) or
    +            (ord in ('f', 'fro') and ndim == 2) or
    +            (ord == 2 and ndim == 1)
    +        ):
    +            x = x.ravel(order='K')
    +            if isComplexType(x.dtype.type):
    +                x_real = x.real
    +                x_imag = x.imag
    +                sqnorm = x_real.dot(x_real) + x_imag.dot(x_imag)
    +            else:
    +                sqnorm = x.dot(x)
    +            ret = sqrt(sqnorm)
    +            if keepdims:
    +                ret = ret.reshape(ndim * [1])
    +            return ret
    +
    +    # Normalize the `axis` argument to a tuple.
    +    nd = x.ndim
    +    if axis is None:
    +        axis = tuple(range(nd))
    +    elif not isinstance(axis, tuple):
    +        try:
    +            axis = int(axis)
    +        except Exception as e:
    +            raise TypeError(
    +                "'axis' must be None, an integer or a tuple of integers"
    +            ) from e
    +        axis = (axis,)
    +
    +    if len(axis) == 1:
    +        if ord == inf:
    +            return abs(x).max(axis=axis, keepdims=keepdims, initial=0)
    +        elif ord == -inf:
    +            return abs(x).min(axis=axis, keepdims=keepdims)
    +        elif ord == 0:
    +            # Zero norm
    +            return (
    +                (x != 0)
    +                .astype(x.real.dtype)
    +                .sum(axis=axis, keepdims=keepdims)
    +            )
    +        elif ord == 1:
    +            # special case for speedup
    +            return add.reduce(abs(x), axis=axis, keepdims=keepdims)
    +        elif ord is None or ord == 2:
    +            # special case for speedup
    +            s = (x.conj() * x).real
    +            return sqrt(add.reduce(s, axis=axis, keepdims=keepdims))
    +        # None of the str-type keywords for ord ('fro', 'nuc')
    +        # are valid for vectors
    +        elif isinstance(ord, str):
    +            raise ValueError(f"Invalid norm order '{ord}' for vectors")
    +        else:
    +            absx = abs(x)
    +            absx **= ord
    +            ret = add.reduce(absx, axis=axis, keepdims=keepdims)
    +            ret **= reciprocal(ord, dtype=ret.dtype)
    +            return ret
    +    elif len(axis) == 2:
    +        row_axis, col_axis = axis
    +        row_axis = normalize_axis_index(row_axis, nd)
    +        col_axis = normalize_axis_index(col_axis, nd)
    +        if row_axis == col_axis:
    +            raise ValueError('Duplicate axes given.')
    +        if ord == 2:
    +            ret = _multi_svd_norm(x, row_axis, col_axis, amax, 0)
    +        elif ord == -2:
    +            ret = _multi_svd_norm(x, row_axis, col_axis, amin)
    +        elif ord == 1:
    +            if col_axis > row_axis:
    +                col_axis -= 1
    +            ret = add.reduce(abs(x), axis=row_axis).max(axis=col_axis, initial=0)
    +        elif ord == inf:
    +            if row_axis > col_axis:
    +                row_axis -= 1
    +            ret = add.reduce(abs(x), axis=col_axis).max(axis=row_axis, initial=0)
    +        elif ord == -1:
    +            if col_axis > row_axis:
    +                col_axis -= 1
    +            ret = add.reduce(abs(x), axis=row_axis).min(axis=col_axis)
    +        elif ord == -inf:
    +            if row_axis > col_axis:
    +                row_axis -= 1
    +            ret = add.reduce(abs(x), axis=col_axis).min(axis=row_axis)
    +        elif ord in [None, 'fro', 'f']:
    +            ret = sqrt(add.reduce((x.conj() * x).real, axis=axis))
    +        elif ord == 'nuc':
    +            ret = _multi_svd_norm(x, row_axis, col_axis, sum, 0)
    +        else:
    +            raise ValueError("Invalid norm order for matrices.")
    +        if keepdims:
    +            ret_shape = list(x.shape)
    +            ret_shape[axis[0]] = 1
    +            ret_shape[axis[1]] = 1
    +            ret = ret.reshape(ret_shape)
    +        return ret
    +    else:
    +        raise ValueError("Improper number of dimensions to norm.")
    +
    +
    +# multi_dot
    +
    +def _multidot_dispatcher(arrays, *, out=None):
    +    yield from arrays
    +    yield out
    +
    +
    +@array_function_dispatch(_multidot_dispatcher)
    +def multi_dot(arrays, *, out=None):
    +    """
    +    Compute the dot product of two or more arrays in a single function call,
    +    while automatically selecting the fastest evaluation order.
    +
    +    `multi_dot` chains `numpy.dot` and uses optimal parenthesization
    +    of the matrices [1]_ [2]_. Depending on the shapes of the matrices,
    +    this can speed up the multiplication a lot.
    +
    +    If the first argument is 1-D it is treated as a row vector.
    +    If the last argument is 1-D it is treated as a column vector.
    +    The other arguments must be 2-D.
    +
    +    Think of `multi_dot` as::
    +
    +        def multi_dot(arrays): return functools.reduce(np.dot, arrays)
    +
    +
    +    Parameters
    +    ----------
    +    arrays : sequence of array_like
    +        If the first argument is 1-D it is treated as row vector.
    +        If the last argument is 1-D it is treated as column vector.
    +        The other arguments must be 2-D.
    +    out : ndarray, optional
    +        Output argument. This must have the exact kind that would be returned
    +        if it was not used. In particular, it must have the right type, must be
    +        C-contiguous, and its dtype must be the dtype that would be returned
    +        for `dot(a, b)`. This is a performance feature. Therefore, if these
    +        conditions are not met, an exception is raised, instead of attempting
    +        to be flexible.
    +
    +    Returns
    +    -------
    +    output : ndarray
    +        Returns the dot product of the supplied arrays.
    +
    +    See Also
    +    --------
    +    numpy.dot : dot multiplication with two arguments.
    +
    +    References
    +    ----------
    +
    +    .. [1] Cormen, "Introduction to Algorithms", Chapter 15.2, p. 370-378
    +    .. [2] https://en.wikipedia.org/wiki/Matrix_chain_multiplication
    +
    +    Examples
    +    --------
    +    `multi_dot` allows you to write::
    +
    +    >>> import numpy as np
    +    >>> from numpy.linalg import multi_dot
    +    >>> # Prepare some data
    +    >>> A = np.random.random((10000, 100))
    +    >>> B = np.random.random((100, 1000))
    +    >>> C = np.random.random((1000, 5))
    +    >>> D = np.random.random((5, 333))
    +    >>> # the actual dot multiplication
    +    >>> _ = multi_dot([A, B, C, D])
    +
    +    instead of::
    +
    +    >>> _ = np.dot(np.dot(np.dot(A, B), C), D)
    +    >>> # or
    +    >>> _ = A.dot(B).dot(C).dot(D)
    +
    +    Notes
    +    -----
    +    The cost for a matrix multiplication can be calculated with the
    +    following function::
    +
    +        def cost(A, B):
    +            return A.shape[0] * A.shape[1] * B.shape[1]
    +
    +    Assume we have three matrices
    +    :math:`A_{10 \\times 100}, B_{100 \\times 5}, C_{5 \\times 50}`.
    +
    +    The costs for the two different parenthesizations are as follows::
    +
    +        cost((AB)C) = 10*100*5 + 10*5*50   = 5000 + 2500   = 7500
    +        cost(A(BC)) = 10*100*50 + 100*5*50 = 50000 + 25000 = 75000
    +
    +    """
    +    n = len(arrays)
    +    # optimization only makes sense for len(arrays) > 2
    +    if n < 2:
    +        raise ValueError("Expecting at least two arrays.")
    +    elif n == 2:
    +        return dot(arrays[0], arrays[1], out=out)
    +
    +    arrays = [asanyarray(a) for a in arrays]
    +
    +    # save original ndim to reshape the result array into the proper form later
    +    ndim_first, ndim_last = arrays[0].ndim, arrays[-1].ndim
    +    # Explicitly convert vectors to 2D arrays to keep the logic of the internal
    +    # _multi_dot_* functions as simple as possible.
    +    if arrays[0].ndim == 1:
    +        arrays[0] = atleast_2d(arrays[0])
    +    if arrays[-1].ndim == 1:
    +        arrays[-1] = atleast_2d(arrays[-1]).T
    +    _assert_2d(*arrays)
    +
    +    # _multi_dot_three is much faster than _multi_dot_matrix_chain_order
    +    if n == 3:
    +        result = _multi_dot_three(arrays[0], arrays[1], arrays[2], out=out)
    +    else:
    +        order = _multi_dot_matrix_chain_order(arrays)
    +        result = _multi_dot(arrays, order, 0, n - 1, out=out)
    +
    +    # return proper shape
    +    if ndim_first == 1 and ndim_last == 1:
    +        return result[0, 0]  # scalar
    +    elif ndim_first == 1 or ndim_last == 1:
    +        return result.ravel()  # 1-D
    +    else:
    +        return result
    +
    +
    +def _multi_dot_three(A, B, C, out=None):
    +    """
    +    Find the best order for three arrays and do the multiplication.
    +
    +    For three arguments `_multi_dot_three` is approximately 15 times faster
    +    than `_multi_dot_matrix_chain_order`
    +
    +    """
    +    a0, a1b0 = A.shape
    +    b1c0, c1 = C.shape
    +    # cost1 = cost((AB)C) = a0*a1b0*b1c0 + a0*b1c0*c1
    +    cost1 = a0 * b1c0 * (a1b0 + c1)
    +    # cost2 = cost(A(BC)) = a1b0*b1c0*c1 + a0*a1b0*c1
    +    cost2 = a1b0 * c1 * (a0 + b1c0)
    +
    +    if cost1 < cost2:
    +        return dot(dot(A, B), C, out=out)
    +    else:
    +        return dot(A, dot(B, C), out=out)
    +
    +
    +def _multi_dot_matrix_chain_order(arrays, return_costs=False):
    +    """
    +    Return a np.array that encodes the optimal order of multiplications.
    +
    +    The optimal order array is then used by `_multi_dot()` to do the
    +    multiplication.
    +
    +    Also return the cost matrix if `return_costs` is `True`
    +
    +    The implementation CLOSELY follows Cormen, "Introduction to Algorithms",
    +    Chapter 15.2, p. 370-378.  Note that Cormen uses 1-based indices.
    +
    +        cost[i, j] = min([
    +            cost[prefix] + cost[suffix] + cost_mult(prefix, suffix)
    +            for k in range(i, j)])
    +
    +    """
    +    n = len(arrays)
    +    # p stores the dimensions of the matrices
    +    # Example for p: A_{10x100}, B_{100x5}, C_{5x50} --> p = [10, 100, 5, 50]
    +    p = [a.shape[0] for a in arrays] + [arrays[-1].shape[1]]
    +    # m is a matrix of costs of the subproblems
    +    # m[i,j]: min number of scalar multiplications needed to compute A_{i..j}
    +    m = zeros((n, n), dtype=double)
    +    # s is the actual ordering
    +    # s[i, j] is the value of k at which we split the product A_i..A_j
    +    s = empty((n, n), dtype=intp)
    +
    +    for l in range(1, n):
    +        for i in range(n - l):
    +            j = i + l
    +            m[i, j] = inf
    +            for k in range(i, j):
    +                q = m[i, k] + m[k + 1, j] + p[i] * p[k + 1] * p[j + 1]
    +                if q < m[i, j]:
    +                    m[i, j] = q
    +                    s[i, j] = k  # Note that Cormen uses 1-based index
    +
    +    return (s, m) if return_costs else s
    +
    +
    +def _multi_dot(arrays, order, i, j, out=None):
    +    """Actually do the multiplication with the given order."""
    +    if i == j:
    +        # the initial call with non-None out should never get here
    +        assert out is None
    +
    +        return arrays[i]
    +    else:
    +        return dot(_multi_dot(arrays, order, i, order[i, j]),
    +                   _multi_dot(arrays, order, order[i, j] + 1, j),
    +                   out=out)
    +
    +
    +# diagonal
    +
    +def _diagonal_dispatcher(x, /, *, offset=None):
    +    return (x,)
    +
    +
    +@array_function_dispatch(_diagonal_dispatcher)
    +def diagonal(x, /, *, offset=0):
    +    """
    +    Returns specified diagonals of a matrix (or a stack of matrices) ``x``.
    +
    +    This function is Array API compatible, contrary to
    +    :py:func:`numpy.diagonal`, the matrix is assumed
    +    to be defined by the last two dimensions.
    +
    +    Parameters
    +    ----------
    +    x : (...,M,N) array_like
    +        Input array having shape (..., M, N) and whose innermost two
    +        dimensions form MxN matrices.
    +    offset : int, optional
    +        Offset specifying the off-diagonal relative to the main diagonal,
    +        where::
    +
    +            * offset = 0: the main diagonal.
    +            * offset > 0: off-diagonal above the main diagonal.
    +            * offset < 0: off-diagonal below the main diagonal.
    +
    +    Returns
    +    -------
    +    out : (...,min(N,M)) ndarray
    +        An array containing the diagonals and whose shape is determined by
    +        removing the last two dimensions and appending a dimension equal to
    +        the size of the resulting diagonals. The returned array must have
    +        the same data type as ``x``.
    +
    +    See Also
    +    --------
    +    numpy.diagonal
    +
    +    Examples
    +    --------
    +    >>> a = np.arange(4).reshape(2, 2); a
    +    array([[0, 1],
    +           [2, 3]])
    +    >>> np.linalg.diagonal(a)
    +    array([0, 3])
    +
    +    A 3-D example:
    +
    +    >>> a = np.arange(8).reshape(2, 2, 2); a
    +    array([[[0, 1],
    +            [2, 3]],
    +           [[4, 5],
    +            [6, 7]]])
    +    >>> np.linalg.diagonal(a)
    +    array([[0, 3],
    +           [4, 7]])
    +
    +    Diagonals adjacent to the main diagonal can be obtained by using the
    +    `offset` argument:
    +
    +    >>> a = np.arange(9).reshape(3, 3)
    +    >>> a
    +    array([[0, 1, 2],
    +           [3, 4, 5],
    +           [6, 7, 8]])
    +    >>> np.linalg.diagonal(a, offset=1)  # First superdiagonal
    +    array([1, 5])
    +    >>> np.linalg.diagonal(a, offset=2)  # Second superdiagonal
    +    array([2])
    +    >>> np.linalg.diagonal(a, offset=-1)  # First subdiagonal
    +    array([3, 7])
    +    >>> np.linalg.diagonal(a, offset=-2)  # Second subdiagonal
    +    array([6])
    +
    +    The anti-diagonal can be obtained by reversing the order of elements
    +    using either `numpy.flipud` or `numpy.fliplr`.
    +
    +    >>> a = np.arange(9).reshape(3, 3)
    +    >>> a
    +    array([[0, 1, 2],
    +           [3, 4, 5],
    +           [6, 7, 8]])
    +    >>> np.linalg.diagonal(np.fliplr(a))  # Horizontal flip
    +    array([2, 4, 6])
    +    >>> np.linalg.diagonal(np.flipud(a))  # Vertical flip
    +    array([6, 4, 2])
    +
    +    Note that the order in which the diagonal is retrieved varies depending
    +    on the flip function.
    +
    +    """
    +    return _core_diagonal(x, offset, axis1=-2, axis2=-1)
    +
    +
    +# trace
    +
    +def _trace_dispatcher(x, /, *, offset=None, dtype=None):
    +    return (x,)
    +
    +
    +@array_function_dispatch(_trace_dispatcher)
    +def trace(x, /, *, offset=0, dtype=None):
    +    """
    +    Returns the sum along the specified diagonals of a matrix
    +    (or a stack of matrices) ``x``.
    +
    +    This function is Array API compatible, contrary to
    +    :py:func:`numpy.trace`.
    +
    +    Parameters
    +    ----------
    +    x : (...,M,N) array_like
    +        Input array having shape (..., M, N) and whose innermost two
    +        dimensions form MxN matrices.
    +    offset : int, optional
    +        Offset specifying the off-diagonal relative to the main diagonal,
    +        where::
    +
    +            * offset = 0: the main diagonal.
    +            * offset > 0: off-diagonal above the main diagonal.
    +            * offset < 0: off-diagonal below the main diagonal.
    +
    +    dtype : dtype, optional
    +        Data type of the returned array.
    +
    +    Returns
    +    -------
    +    out : ndarray
    +        An array containing the traces and whose shape is determined by
    +        removing the last two dimensions and storing the traces in the last
    +        array dimension. For example, if x has rank k and shape:
    +        (I, J, K, ..., L, M, N), then an output array has rank k-2 and shape:
    +        (I, J, K, ..., L) where::
    +
    +            out[i, j, k, ..., l] = trace(a[i, j, k, ..., l, :, :])
    +
    +        The returned array must have a data type as described by the dtype
    +        parameter above.
    +
    +    See Also
    +    --------
    +    numpy.trace
    +
    +    Examples
    +    --------
    +    >>> np.linalg.trace(np.eye(3))
    +    3.0
    +    >>> a = np.arange(8).reshape((2, 2, 2))
    +    >>> np.linalg.trace(a)
    +    array([3, 11])
    +
    +    Trace is computed with the last two axes as the 2-d sub-arrays.
    +    This behavior differs from :py:func:`numpy.trace` which uses the first two
    +    axes by default.
    +
    +    >>> a = np.arange(24).reshape((3, 2, 2, 2))
    +    >>> np.linalg.trace(a).shape
    +    (3, 2)
    +
    +    Traces adjacent to the main diagonal can be obtained by using the
    +    `offset` argument:
    +
    +    >>> a = np.arange(9).reshape((3, 3)); a
    +    array([[0, 1, 2],
    +           [3, 4, 5],
    +           [6, 7, 8]])
    +    >>> np.linalg.trace(a, offset=1)  # First superdiagonal
    +    6
    +    >>> np.linalg.trace(a, offset=2)  # Second superdiagonal
    +    2
    +    >>> np.linalg.trace(a, offset=-1)  # First subdiagonal
    +    10
    +    >>> np.linalg.trace(a, offset=-2)  # Second subdiagonal
    +    6
    +
    +    """
    +    return _core_trace(x, offset, axis1=-2, axis2=-1, dtype=dtype)
    +
    +
    +# cross
    +
    +def _cross_dispatcher(x1, x2, /, *, axis=None):
    +    return (x1, x2,)
    +
    +
    +@array_function_dispatch(_cross_dispatcher)
    +def cross(x1, x2, /, *, axis=-1):
    +    """
    +    Returns the cross product of 3-element vectors.
    +
    +    If ``x1`` and/or ``x2`` are multi-dimensional arrays, then
    +    the cross-product of each pair of corresponding 3-element vectors
    +    is independently computed.
    +
    +    This function is Array API compatible, contrary to
    +    :func:`numpy.cross`.
    +
    +    Parameters
    +    ----------
    +    x1 : array_like
    +        The first input array.
    +    x2 : array_like
    +        The second input array. Must be compatible with ``x1`` for all
    +        non-compute axes. The size of the axis over which to compute
    +        the cross-product must be the same size as the respective axis
    +        in ``x1``.
    +    axis : int, optional
    +        The axis (dimension) of ``x1`` and ``x2`` containing the vectors for
    +        which to compute the cross-product. Default: ``-1``.
    +
    +    Returns
    +    -------
    +    out : ndarray
    +        An array containing the cross products.
    +
    +    See Also
    +    --------
    +    numpy.cross
    +
    +    Examples
    +    --------
    +    Vector cross-product.
    +
    +    >>> x = np.array([1, 2, 3])
    +    >>> y = np.array([4, 5, 6])
    +    >>> np.linalg.cross(x, y)
    +    array([-3,  6, -3])
    +
    +    Multiple vector cross-products. Note that the direction of the cross
    +    product vector is defined by the *right-hand rule*.
    +
    +    >>> x = np.array([[1,2,3], [4,5,6]])
    +    >>> y = np.array([[4,5,6], [1,2,3]])
    +    >>> np.linalg.cross(x, y)
    +    array([[-3,  6, -3],
    +           [ 3, -6,  3]])
    +
    +    >>> x = np.array([[1, 2], [3, 4], [5, 6]])
    +    >>> y = np.array([[4, 5], [6, 1], [2, 3]])
    +    >>> np.linalg.cross(x, y, axis=0)
    +    array([[-24,  6],
    +           [ 18, 24],
    +           [-6,  -18]])
    +
    +    """
    +    x1 = asanyarray(x1)
    +    x2 = asanyarray(x2)
    +
    +    if x1.shape[axis] != 3 or x2.shape[axis] != 3:
    +        raise ValueError(
    +            "Both input arrays must be (arrays of) 3-dimensional vectors, "
    +            f"but they are {x1.shape[axis]} and {x2.shape[axis]} "
    +            "dimensional instead."
    +        )
    +
    +    return _core_cross(x1, x2, axis=axis)
    +
    +
    +# matmul
    +
    +def _matmul_dispatcher(x1, x2, /):
    +    return (x1, x2)
    +
    +
    +@array_function_dispatch(_matmul_dispatcher)
    +def matmul(x1, x2, /):
    +    """
    +    Computes the matrix product.
    +
    +    This function is Array API compatible, contrary to
    +    :func:`numpy.matmul`.
    +
    +    Parameters
    +    ----------
    +    x1 : array_like
    +        The first input array.
    +    x2 : array_like
    +        The second input array.
    +
    +    Returns
    +    -------
    +    out : ndarray
    +        The matrix product of the inputs.
    +        This is a scalar only when both ``x1``, ``x2`` are 1-d vectors.
    +
    +    Raises
    +    ------
    +    ValueError
    +        If the last dimension of ``x1`` is not the same size as
    +        the second-to-last dimension of ``x2``.
    +
    +        If a scalar value is passed in.
    +
    +    See Also
    +    --------
    +    numpy.matmul
    +
    +    Examples
    +    --------
    +    For 2-D arrays it is the matrix product:
    +
    +    >>> a = np.array([[1, 0],
    +    ...               [0, 1]])
    +    >>> b = np.array([[4, 1],
    +    ...               [2, 2]])
    +    >>> np.linalg.matmul(a, b)
    +    array([[4, 1],
    +           [2, 2]])
    +
    +    For 2-D mixed with 1-D, the result is the usual.
    +
    +    >>> a = np.array([[1, 0],
    +    ...               [0, 1]])
    +    >>> b = np.array([1, 2])
    +    >>> np.linalg.matmul(a, b)
    +    array([1, 2])
    +    >>> np.linalg.matmul(b, a)
    +    array([1, 2])
    +
    +
    +    Broadcasting is conventional for stacks of arrays
    +
    +    >>> a = np.arange(2 * 2 * 4).reshape((2, 2, 4))
    +    >>> b = np.arange(2 * 2 * 4).reshape((2, 4, 2))
    +    >>> np.linalg.matmul(a,b).shape
    +    (2, 2, 2)
    +    >>> np.linalg.matmul(a, b)[0, 1, 1]
    +    98
    +    >>> sum(a[0, 1, :] * b[0 , :, 1])
    +    98
    +
    +    Vector, vector returns the scalar inner product, but neither argument
    +    is complex-conjugated:
    +
    +    >>> np.linalg.matmul([2j, 3j], [2j, 3j])
    +    (-13+0j)
    +
    +    Scalar multiplication raises an error.
    +
    +    >>> np.linalg.matmul([1,2], 3)
    +    Traceback (most recent call last):
    +    ...
    +    ValueError: matmul: Input operand 1 does not have enough dimensions ...
    +
    +    """
    +    return _core_matmul(x1, x2)
    +
    +
    +# tensordot
    +
    +def _tensordot_dispatcher(x1, x2, /, *, axes=None):
    +    return (x1, x2)
    +
    +
    +@array_function_dispatch(_tensordot_dispatcher)
    +def tensordot(x1, x2, /, *, axes=2):
    +    return _core_tensordot(x1, x2, axes=axes)
    +
    +
    +tensordot.__doc__ = _core_tensordot.__doc__
    +
    +
    +# matrix_transpose
    +
    +def _matrix_transpose_dispatcher(x):
    +    return (x,)
    +
    +@array_function_dispatch(_matrix_transpose_dispatcher)
    +def matrix_transpose(x, /):
    +    return _core_matrix_transpose(x)
    +
    +
    +matrix_transpose.__doc__ = f"""{_core_matrix_transpose.__doc__}
    +
    +    Notes
    +    -----
    +    This function is an alias of `numpy.matrix_transpose`.
    +"""
    +
    +
    +# matrix_norm
    +
    +def _matrix_norm_dispatcher(x, /, *, keepdims=None, ord=None):
    +    return (x,)
    +
    +@array_function_dispatch(_matrix_norm_dispatcher)
    +def matrix_norm(x, /, *, keepdims=False, ord="fro"):
    +    """
    +    Computes the matrix norm of a matrix (or a stack of matrices) ``x``.
    +
    +    This function is Array API compatible.
    +
    +    Parameters
    +    ----------
    +    x : array_like
    +        Input array having shape (..., M, N) and whose two innermost
    +        dimensions form ``MxN`` matrices.
    +    keepdims : bool, optional
    +        If this is set to True, the axes which are normed over are left in
    +        the result as dimensions with size one. Default: False.
    +    ord : {1, -1, 2, -2, inf, -inf, 'fro', 'nuc'}, optional
    +        The order of the norm. For details see the table under ``Notes``
    +        in `numpy.linalg.norm`.
    +
    +    See Also
    +    --------
    +    numpy.linalg.norm : Generic norm function
    +
    +    Examples
    +    --------
    +    >>> from numpy import linalg as LA
    +    >>> a = np.arange(9) - 4
    +    >>> a
    +    array([-4, -3, -2, ...,  2,  3,  4])
    +    >>> b = a.reshape((3, 3))
    +    >>> b
    +    array([[-4, -3, -2],
    +           [-1,  0,  1],
    +           [ 2,  3,  4]])
    +
    +    >>> LA.matrix_norm(b)
    +    7.745966692414834
    +    >>> LA.matrix_norm(b, ord='fro')
    +    7.745966692414834
    +    >>> LA.matrix_norm(b, ord=np.inf)
    +    9.0
    +    >>> LA.matrix_norm(b, ord=-np.inf)
    +    2.0
    +
    +    >>> LA.matrix_norm(b, ord=1)
    +    7.0
    +    >>> LA.matrix_norm(b, ord=-1)
    +    6.0
    +    >>> LA.matrix_norm(b, ord=2)
    +    7.3484692283495345
    +    >>> LA.matrix_norm(b, ord=-2)
    +    1.8570331885190563e-016 # may vary
    +
    +    """
    +    x = asanyarray(x)
    +    return norm(x, axis=(-2, -1), keepdims=keepdims, ord=ord)
    +
    +
    +# vector_norm
    +
    +def _vector_norm_dispatcher(x, /, *, axis=None, keepdims=None, ord=None):
    +    return (x,)
    +
    +@array_function_dispatch(_vector_norm_dispatcher)
    +def vector_norm(x, /, *, axis=None, keepdims=False, ord=2):
    +    """
    +    Computes the vector norm of a vector (or batch of vectors) ``x``.
    +
    +    This function is Array API compatible.
    +
    +    Parameters
    +    ----------
    +    x : array_like
    +        Input array.
    +    axis : {None, int, 2-tuple of ints}, optional
    +        If an integer, ``axis`` specifies the axis (dimension) along which
    +        to compute vector norms. If an n-tuple, ``axis`` specifies the axes
    +        (dimensions) along which to compute batched vector norms. If ``None``,
    +        the vector norm must be computed over all array values (i.e.,
    +        equivalent to computing the vector norm of a flattened array).
    +        Default: ``None``.
    +    keepdims : bool, optional
    +        If this is set to True, the axes which are normed over are left in
    +        the result as dimensions with size one. Default: False.
    +    ord : {int, float, inf, -inf}, optional
    +        The order of the norm. For details see the table under ``Notes``
    +        in `numpy.linalg.norm`.
    +
    +    See Also
    +    --------
    +    numpy.linalg.norm : Generic norm function
    +
    +    Examples
    +    --------
    +    >>> from numpy import linalg as LA
    +    >>> a = np.arange(9) + 1
    +    >>> a
    +    array([1, 2, 3, 4, 5, 6, 7, 8, 9])
    +    >>> b = a.reshape((3, 3))
    +    >>> b
    +    array([[1, 2, 3],
    +           [4, 5, 6],
    +           [7, 8, 9]])
    +
    +    >>> LA.vector_norm(b)
    +    16.881943016134134
    +    >>> LA.vector_norm(b, ord=np.inf)
    +    9.0
    +    >>> LA.vector_norm(b, ord=-np.inf)
    +    1.0
    +
    +    >>> LA.vector_norm(b, ord=0)
    +    9.0
    +    >>> LA.vector_norm(b, ord=1)
    +    45.0
    +    >>> LA.vector_norm(b, ord=-1)
    +    0.3534857623790153
    +    >>> LA.vector_norm(b, ord=2)
    +    16.881943016134134
    +    >>> LA.vector_norm(b, ord=-2)
    +    0.8058837395885292
    +
    +    """
    +    x = asanyarray(x)
    +    shape = list(x.shape)
    +    if axis is None:
    +        # Note: np.linalg.norm() doesn't handle 0-D arrays
    +        x = x.ravel()
    +        _axis = 0
    +    elif isinstance(axis, tuple):
    +        # Note: The axis argument supports any number of axes, whereas
    +        # np.linalg.norm() only supports a single axis for vector norm.
    +        normalized_axis = normalize_axis_tuple(axis, x.ndim)
    +        rest = tuple(i for i in range(x.ndim) if i not in normalized_axis)
    +        newshape = axis + rest
    +        x = _core_transpose(x, newshape).reshape(
    +            (
    +                prod([x.shape[i] for i in axis], dtype=int),
    +                *[x.shape[i] for i in rest]
    +            )
    +        )
    +        _axis = 0
    +    else:
    +        _axis = axis
    +
    +    res = norm(x, axis=_axis, ord=ord)
    +
    +    if keepdims:
    +        # We can't reuse np.linalg.norm(keepdims) because of the reshape hacks
    +        # above to avoid matrix norm logic.
    +        _axis = normalize_axis_tuple(
    +            range(len(shape)) if axis is None else axis, len(shape)
    +        )
    +        for i in _axis:
    +            shape[i] = 1
    +        res = res.reshape(tuple(shape))
    +
    +    return res
    +
    +
    +# vecdot
    +
    +def _vecdot_dispatcher(x1, x2, /, *, axis=None):
    +    return (x1, x2)
    +
    +@array_function_dispatch(_vecdot_dispatcher)
    +def vecdot(x1, x2, /, *, axis=-1):
    +    """
    +    Computes the vector dot product.
    +
    +    This function is restricted to arguments compatible with the Array API,
    +    contrary to :func:`numpy.vecdot`.
    +
    +    Let :math:`\\mathbf{a}` be a vector in ``x1`` and :math:`\\mathbf{b}` be
    +    a corresponding vector in ``x2``. The dot product is defined as:
    +
    +    .. math::
    +       \\mathbf{a} \\cdot \\mathbf{b} = \\sum_{i=0}^{n-1} \\overline{a_i}b_i
    +
    +    over the dimension specified by ``axis`` and where :math:`\\overline{a_i}`
    +    denotes the complex conjugate if :math:`a_i` is complex and the identity
    +    otherwise.
    +
    +    Parameters
    +    ----------
    +    x1 : array_like
    +        First input array.
    +    x2 : array_like
    +        Second input array.
    +    axis : int, optional
    +        Axis over which to compute the dot product. Default: ``-1``.
    +
    +    Returns
    +    -------
    +    output : ndarray
    +        The vector dot product of the input.
    +
    +    See Also
    +    --------
    +    numpy.vecdot
    +
    +    Examples
    +    --------
    +    Get the projected size along a given normal for an array of vectors.
    +
    +    >>> v = np.array([[0., 5., 0.], [0., 0., 10.], [0., 6., 8.]])
    +    >>> n = np.array([0., 0.6, 0.8])
    +    >>> np.linalg.vecdot(v, n)
    +    array([ 3.,  8., 10.])
    +
    +    """
    +    return _core_vecdot(x1, x2, axis=axis)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/linalg/_linalg.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/linalg/_linalg.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..60320b021c71436da4bb651cdf1d0b791a10f8b4
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/linalg/_linalg.pyi
    @@ -0,0 +1,548 @@
    +from collections.abc import Iterable
    +from typing import (
    +    Any,
    +    Literal as L,
    +    NamedTuple,
    +    Never,
    +    SupportsIndex,
    +    SupportsInt,
    +    TypeAlias,
    +    TypeVar,
    +    overload,
    +)
    +
    +import numpy as np
    +from numpy import (
    +    complex128,
    +    complexfloating,
    +    float64,
    +    floating,
    +    int32,
    +    object_,
    +    signedinteger,
    +    timedelta64,
    +    unsignedinteger,
    +    vecdot,
    +)
    +from numpy._core.fromnumeric import matrix_transpose
    +from numpy._globals import _NoValueType
    +from numpy._typing import (
    +    ArrayLike,
    +    DTypeLike,
    +    NDArray,
    +    _ArrayLike,
    +    _ArrayLikeBool_co,
    +    _ArrayLikeComplex_co,
    +    _ArrayLikeFloat_co,
    +    _ArrayLikeInt_co,
    +    _ArrayLikeNumber_co,
    +    _ArrayLikeObject_co,
    +    _ArrayLikeTD64_co,
    +    _ArrayLikeUInt_co,
    +    _NestedSequence,
    +    _ShapeLike,
    +)
    +from numpy.linalg import LinAlgError
    +
    +__all__ = [
    +    "matrix_power",
    +    "solve",
    +    "tensorsolve",
    +    "tensorinv",
    +    "inv",
    +    "cholesky",
    +    "eigvals",
    +    "eigvalsh",
    +    "pinv",
    +    "slogdet",
    +    "det",
    +    "svd",
    +    "svdvals",
    +    "eig",
    +    "eigh",
    +    "lstsq",
    +    "norm",
    +    "qr",
    +    "cond",
    +    "matrix_rank",
    +    "LinAlgError",
    +    "multi_dot",
    +    "trace",
    +    "diagonal",
    +    "cross",
    +    "outer",
    +    "tensordot",
    +    "matmul",
    +    "matrix_transpose",
    +    "matrix_norm",
    +    "vector_norm",
    +    "vecdot",
    +]
    +
    +_NumberT = TypeVar("_NumberT", bound=np.number)
    +_NumericScalarT = TypeVar("_NumericScalarT", bound=np.number | np.timedelta64 | np.object_)
    +
    +_ModeKind: TypeAlias = L["reduced", "complete", "r", "raw"]
    +
    +###
    +
    +fortran_int = np.intc
    +
    +class EigResult(NamedTuple):
    +    eigenvalues: NDArray[Any]
    +    eigenvectors: NDArray[Any]
    +
    +class EighResult(NamedTuple):
    +    eigenvalues: NDArray[Any]
    +    eigenvectors: NDArray[Any]
    +
    +class QRResult(NamedTuple):
    +    Q: NDArray[Any]
    +    R: NDArray[Any]
    +
    +class SlogdetResult(NamedTuple):
    +    # TODO: `sign` and `logabsdet` are scalars for input 2D arrays and
    +    # a `(x.ndim - 2)`` dimensionl arrays otherwise
    +    sign: Any
    +    logabsdet: Any
    +
    +class SVDResult(NamedTuple):
    +    U: NDArray[Any]
    +    S: NDArray[Any]
    +    Vh: NDArray[Any]
    +
    +@overload
    +def tensorsolve(
    +    a: _ArrayLikeInt_co,
    +    b: _ArrayLikeInt_co,
    +    axes: Iterable[int] | None = None,
    +) -> NDArray[float64]: ...
    +@overload
    +def tensorsolve(
    +    a: _ArrayLikeFloat_co,
    +    b: _ArrayLikeFloat_co,
    +    axes: Iterable[int] | None = None,
    +) -> NDArray[floating]: ...
    +@overload
    +def tensorsolve(
    +    a: _ArrayLikeComplex_co,
    +    b: _ArrayLikeComplex_co,
    +    axes: Iterable[int] | None = None,
    +) -> NDArray[complexfloating]: ...
    +
    +@overload
    +def solve(
    +    a: _ArrayLikeInt_co,
    +    b: _ArrayLikeInt_co,
    +) -> NDArray[float64]: ...
    +@overload
    +def solve(
    +    a: _ArrayLikeFloat_co,
    +    b: _ArrayLikeFloat_co,
    +) -> NDArray[floating]: ...
    +@overload
    +def solve(
    +    a: _ArrayLikeComplex_co,
    +    b: _ArrayLikeComplex_co,
    +) -> NDArray[complexfloating]: ...
    +
    +@overload
    +def tensorinv(
    +    a: _ArrayLikeInt_co,
    +    ind: int = 2,
    +) -> NDArray[float64]: ...
    +@overload
    +def tensorinv(
    +    a: _ArrayLikeFloat_co,
    +    ind: int = 2,
    +) -> NDArray[floating]: ...
    +@overload
    +def tensorinv(
    +    a: _ArrayLikeComplex_co,
    +    ind: int = 2,
    +) -> NDArray[complexfloating]: ...
    +
    +@overload
    +def inv(a: _ArrayLikeInt_co) -> NDArray[float64]: ...
    +@overload
    +def inv(a: _ArrayLikeFloat_co) -> NDArray[floating]: ...
    +@overload
    +def inv(a: _ArrayLikeComplex_co) -> NDArray[complexfloating]: ...
    +
    +# TODO: The supported input and output dtypes are dependent on the value of `n`.
    +# For example: `n < 0` always casts integer types to float64
    +def matrix_power(
    +    a: _ArrayLikeComplex_co | _ArrayLikeObject_co,
    +    n: SupportsIndex,
    +) -> NDArray[Any]: ...
    +
    +@overload
    +def cholesky(a: _ArrayLikeInt_co, /, *, upper: bool = False) -> NDArray[float64]: ...
    +@overload
    +def cholesky(a: _ArrayLikeFloat_co, /, *, upper: bool = False) -> NDArray[floating]: ...
    +@overload
    +def cholesky(a: _ArrayLikeComplex_co, /, *, upper: bool = False) -> NDArray[complexfloating]: ...
    +
    +@overload
    +def outer(x1: _ArrayLike[Never], x2: _ArrayLike[Never], /) -> NDArray[Any]: ...
    +@overload
    +def outer(x1: _ArrayLikeBool_co, x2: _ArrayLikeBool_co, /) -> NDArray[np.bool]: ...
    +@overload
    +def outer(x1: _ArrayLike[_NumberT], x2: _ArrayLike[_NumberT], /) -> NDArray[_NumberT]: ...
    +@overload
    +def outer(x1: _ArrayLikeUInt_co, x2: _ArrayLikeUInt_co, /) -> NDArray[unsignedinteger]: ...
    +@overload
    +def outer(x1: _ArrayLikeInt_co, x2: _ArrayLikeInt_co, /) -> NDArray[signedinteger]: ...
    +@overload
    +def outer(x1: _ArrayLikeFloat_co, x2: _ArrayLikeFloat_co, /) -> NDArray[floating]: ...
    +@overload
    +def outer(x1: _ArrayLikeComplex_co, x2: _ArrayLikeComplex_co, /) -> NDArray[complexfloating]: ...
    +@overload
    +def outer(x1: _ArrayLikeTD64_co, x2: _ArrayLikeTD64_co, /) -> NDArray[timedelta64]: ...
    +@overload
    +def outer(x1: _ArrayLikeObject_co, x2: _ArrayLikeObject_co, /) -> NDArray[object_]: ...
    +@overload
    +def outer(
    +    x1: _ArrayLikeComplex_co | _ArrayLikeTD64_co | _ArrayLikeObject_co,
    +    x2: _ArrayLikeComplex_co | _ArrayLikeTD64_co | _ArrayLikeObject_co,
    +    /,
    +) -> NDArray[Any]: ...
    +
    +@overload
    +def qr(a: _ArrayLikeInt_co, mode: _ModeKind = "reduced") -> QRResult: ...
    +@overload
    +def qr(a: _ArrayLikeFloat_co, mode: _ModeKind = "reduced") -> QRResult: ...
    +@overload
    +def qr(a: _ArrayLikeComplex_co, mode: _ModeKind = "reduced") -> QRResult: ...
    +
    +@overload
    +def eigvals(a: _ArrayLikeInt_co) -> NDArray[float64] | NDArray[complex128]: ...
    +@overload
    +def eigvals(a: _ArrayLikeFloat_co) -> NDArray[floating] | NDArray[complexfloating]: ...
    +@overload
    +def eigvals(a: _ArrayLikeComplex_co) -> NDArray[complexfloating]: ...
    +
    +@overload
    +def eigvalsh(a: _ArrayLikeInt_co, UPLO: L["L", "U", "l", "u"] = "L") -> NDArray[float64]: ...
    +@overload
    +def eigvalsh(a: _ArrayLikeComplex_co, UPLO: L["L", "U", "l", "u"] = "L") -> NDArray[floating]: ...
    +
    +@overload
    +def eig(a: _ArrayLikeInt_co) -> EigResult: ...
    +@overload
    +def eig(a: _ArrayLikeFloat_co) -> EigResult: ...
    +@overload
    +def eig(a: _ArrayLikeComplex_co) -> EigResult: ...
    +
    +@overload
    +def eigh(
    +    a: _ArrayLikeInt_co,
    +    UPLO: L["L", "U", "l", "u"] = "L",
    +) -> EighResult: ...
    +@overload
    +def eigh(
    +    a: _ArrayLikeFloat_co,
    +    UPLO: L["L", "U", "l", "u"] = "L",
    +) -> EighResult: ...
    +@overload
    +def eigh(
    +    a: _ArrayLikeComplex_co,
    +    UPLO: L["L", "U", "l", "u"] = "L",
    +) -> EighResult: ...
    +
    +@overload
    +def svd(
    +    a: _ArrayLikeInt_co,
    +    full_matrices: bool = True,
    +    compute_uv: L[True] = True,
    +    hermitian: bool = False,
    +) -> SVDResult: ...
    +@overload
    +def svd(
    +    a: _ArrayLikeFloat_co,
    +    full_matrices: bool = True,
    +    compute_uv: L[True] = True,
    +    hermitian: bool = False,
    +) -> SVDResult: ...
    +@overload
    +def svd(
    +    a: _ArrayLikeComplex_co,
    +    full_matrices: bool = True,
    +    compute_uv: L[True] = True,
    +    hermitian: bool = False,
    +) -> SVDResult: ...
    +@overload
    +def svd(
    +    a: _ArrayLikeInt_co,
    +    full_matrices: bool = True,
    +    *,
    +    compute_uv: L[False],
    +    hermitian: bool = False,
    +) -> NDArray[float64]: ...
    +@overload
    +def svd(
    +    a: _ArrayLikeInt_co,
    +    full_matrices: bool,
    +    compute_uv: L[False],
    +    hermitian: bool = False,
    +) -> NDArray[float64]: ...
    +@overload
    +def svd(
    +    a: _ArrayLikeComplex_co,
    +    full_matrices: bool = True,
    +    *,
    +    compute_uv: L[False],
    +    hermitian: bool = False,
    +) -> NDArray[floating]: ...
    +@overload
    +def svd(
    +    a: _ArrayLikeComplex_co,
    +    full_matrices: bool,
    +    compute_uv: L[False],
    +    hermitian: bool = False,
    +) -> NDArray[floating]: ...
    +
    +# the ignored `overload-overlap` mypy error below is a false-positive
    +@overload
    +def svdvals(  # type: ignore[overload-overlap]
    +    x: _ArrayLike[np.float64 | np.complex128 | np.integer | np.bool] | _NestedSequence[complex], /
    +) -> NDArray[np.float64]: ...
    +@overload
    +def svdvals(x: _ArrayLike[np.float32 | np.complex64], /) -> NDArray[np.float32]: ...
    +@overload
    +def svdvals(x: _ArrayLikeNumber_co, /) -> NDArray[floating]: ...
    +
    +# TODO: Returns a scalar for 2D arrays and
    +# a `(x.ndim - 2)`` dimensionl array otherwise
    +def cond(x: _ArrayLikeComplex_co, p: float | L["fro", "nuc"] | None = None) -> Any: ...
    +
    +# TODO: Returns `int` for <2D arrays and `intp` otherwise
    +def matrix_rank(
    +    A: _ArrayLikeComplex_co,
    +    tol: _ArrayLikeFloat_co | None = None,
    +    hermitian: bool = False,
    +    *,
    +    rtol: _ArrayLikeFloat_co | None = None,
    +) -> Any: ...
    +
    +@overload
    +def pinv(
    +    a: _ArrayLikeInt_co,
    +    rcond: _ArrayLikeFloat_co | None = None,
    +    hermitian: bool = False,
    +    *,
    +    rtol: _ArrayLikeFloat_co | _NoValueType = ...,
    +) -> NDArray[float64]: ...
    +@overload
    +def pinv(
    +    a: _ArrayLikeFloat_co,
    +    rcond: _ArrayLikeFloat_co | None = None,
    +    hermitian: bool = False,
    +    *,
    +    rtol: _ArrayLikeFloat_co | _NoValueType = ...,
    +) -> NDArray[floating]: ...
    +@overload
    +def pinv(
    +    a: _ArrayLikeComplex_co,
    +    rcond: _ArrayLikeFloat_co | None = None,
    +    hermitian: bool = False,
    +    *,
    +    rtol: _ArrayLikeFloat_co | _NoValueType = ...,
    +) -> NDArray[complexfloating]: ...
    +
    +# TODO: Returns a 2-tuple of scalars for 2D arrays and
    +# a 2-tuple of `(a.ndim - 2)`` dimensionl arrays otherwise
    +def slogdet(a: _ArrayLikeComplex_co) -> SlogdetResult: ...
    +
    +# TODO: Returns a 2-tuple of scalars for 2D arrays and
    +# a 2-tuple of `(a.ndim - 2)`` dimensionl arrays otherwise
    +def det(a: _ArrayLikeComplex_co) -> Any: ...
    +
    +@overload
    +def lstsq(a: _ArrayLikeInt_co, b: _ArrayLikeInt_co, rcond: float | None = None) -> tuple[
    +    NDArray[float64],
    +    NDArray[float64],
    +    int32,
    +    NDArray[float64],
    +]: ...
    +@overload
    +def lstsq(a: _ArrayLikeFloat_co, b: _ArrayLikeFloat_co, rcond: float | None = None) -> tuple[
    +    NDArray[floating],
    +    NDArray[floating],
    +    int32,
    +    NDArray[floating],
    +]: ...
    +@overload
    +def lstsq(a: _ArrayLikeComplex_co, b: _ArrayLikeComplex_co, rcond: float | None = None) -> tuple[
    +    NDArray[complexfloating],
    +    NDArray[floating],
    +    int32,
    +    NDArray[floating],
    +]: ...
    +
    +@overload
    +def norm(
    +    x: ArrayLike,
    +    ord: float | L["fro", "nuc"] | None = None,
    +    axis: None = None,
    +    keepdims: L[False] = False,
    +) -> floating: ...
    +@overload
    +def norm(
    +    x: ArrayLike,
    +    ord: float | L["fro", "nuc"] | None,
    +    axis: SupportsInt | SupportsIndex | tuple[int, ...] | None,
    +    keepdims: bool = False,
    +) -> Any: ...
    +@overload
    +def norm(
    +    x: ArrayLike,
    +    ord: float | L["fro", "nuc"] | None = None,
    +    *,
    +    axis: SupportsInt | SupportsIndex | tuple[int, ...] | None,
    +    keepdims: bool = False,
    +) -> Any: ...
    +
    +@overload
    +def matrix_norm(
    +    x: ArrayLike,
    +    /,
    +    *,
    +    ord: float | L["fro", "nuc"] | None = "fro",
    +    keepdims: L[False] = False,
    +) -> floating: ...
    +@overload
    +def matrix_norm(
    +    x: ArrayLike,
    +    /,
    +    *,
    +    ord: float | L["fro", "nuc"] | None = "fro",
    +    keepdims: bool = False,
    +) -> Any: ...
    +
    +@overload
    +def vector_norm(
    +    x: ArrayLike,
    +    /,
    +    *,
    +    axis: None = None,
    +    ord: float | None = 2,
    +    keepdims: L[False] = False,
    +) -> floating: ...
    +@overload
    +def vector_norm(
    +    x: ArrayLike,
    +    /,
    +    *,
    +    axis: SupportsInt | SupportsIndex | tuple[int, ...],
    +    ord: float | None = 2,
    +    keepdims: bool = False,
    +) -> Any: ...
    +
    +# keep in sync with numpy._core.numeric.tensordot (ignoring `/, *`)
    +@overload
    +def tensordot(
    +    a: _ArrayLike[_NumericScalarT],
    +    b: _ArrayLike[_NumericScalarT],
    +    /,
    +    *,
    +    axes: int | tuple[_ShapeLike, _ShapeLike] = 2,
    +) -> NDArray[_NumericScalarT]: ...
    +@overload
    +def tensordot(
    +    a: _ArrayLikeBool_co,
    +    b: _ArrayLikeBool_co,
    +    /,
    +    *,
    +    axes: int | tuple[_ShapeLike, _ShapeLike] = 2,
    +) -> NDArray[np.bool_]: ...
    +@overload
    +def tensordot(
    +    a: _ArrayLikeInt_co,
    +    b: _ArrayLikeInt_co,
    +    /,
    +    *,
    +    axes: int | tuple[_ShapeLike, _ShapeLike] = 2,
    +) -> NDArray[np.int_ | Any]: ...
    +@overload
    +def tensordot(
    +    a: _ArrayLikeFloat_co,
    +    b: _ArrayLikeFloat_co,
    +    /,
    +    *,
    +    axes: int | tuple[_ShapeLike, _ShapeLike] = 2,
    +) -> NDArray[np.float64 | Any]: ...
    +@overload
    +def tensordot(
    +    a: _ArrayLikeComplex_co,
    +    b: _ArrayLikeComplex_co,
    +    /,
    +    *,
    +    axes: int | tuple[_ShapeLike, _ShapeLike] = 2,
    +) -> NDArray[np.complex128 | Any]: ...
    +
    +# TODO: Returns a scalar or array
    +def multi_dot(
    +    arrays: Iterable[_ArrayLikeComplex_co | _ArrayLikeObject_co | _ArrayLikeTD64_co],
    +    *,
    +    out: NDArray[Any] | None = None,
    +) -> Any: ...
    +
    +def diagonal(
    +    x: ArrayLike,  # >= 2D array
    +    /,
    +    *,
    +    offset: SupportsIndex = 0,
    +) -> NDArray[Any]: ...
    +
    +def trace(
    +    x: ArrayLike,  # >= 2D array
    +    /,
    +    *,
    +    offset: SupportsIndex = 0,
    +    dtype: DTypeLike | None = None,
    +) -> Any: ...
    +
    +@overload
    +def cross(
    +    x1: _ArrayLikeUInt_co,
    +    x2: _ArrayLikeUInt_co,
    +    /,
    +    *,
    +    axis: int = -1,
    +) -> NDArray[unsignedinteger]: ...
    +@overload
    +def cross(
    +    x1: _ArrayLikeInt_co,
    +    x2: _ArrayLikeInt_co,
    +    /,
    +    *,
    +    axis: int = -1,
    +) -> NDArray[signedinteger]: ...
    +@overload
    +def cross(
    +    x1: _ArrayLikeFloat_co,
    +    x2: _ArrayLikeFloat_co,
    +    /,
    +    *,
    +    axis: int = -1,
    +) -> NDArray[floating]: ...
    +@overload
    +def cross(
    +    x1: _ArrayLikeComplex_co,
    +    x2: _ArrayLikeComplex_co,
    +    /,
    +    *,
    +    axis: int = -1,
    +) -> NDArray[complexfloating]: ...
    +
    +@overload
    +def matmul(x1: _ArrayLike[_NumberT], x2: _ArrayLike[_NumberT], /) -> NDArray[_NumberT]: ...
    +@overload
    +def matmul(x1: _ArrayLikeUInt_co, x2: _ArrayLikeUInt_co, /) -> NDArray[unsignedinteger]: ...
    +@overload
    +def matmul(x1: _ArrayLikeInt_co, x2: _ArrayLikeInt_co, /) -> NDArray[signedinteger]: ...
    +@overload
    +def matmul(x1: _ArrayLikeFloat_co, x2: _ArrayLikeFloat_co, /) -> NDArray[floating]: ...
    +@overload
    +def matmul(x1: _ArrayLikeComplex_co, x2: _ArrayLikeComplex_co, /) -> NDArray[complexfloating]: ...
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/linalg/_umath_linalg.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/linalg/_umath_linalg.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..f90706a7b159b0e57d8eec8782111257089477fc
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/linalg/_umath_linalg.pyi
    @@ -0,0 +1,60 @@
    +from typing import Final, Literal as L
    +
    +import numpy as np
    +from numpy._typing._ufunc import _GUFunc_Nin2_Nout1
    +
    +__version__: Final[str] = ...
    +_ilp64: Final[bool] = ...
    +
    +###
    +# 1 -> 1
    +
    +# (m,m) -> ()
    +det: Final[np.ufunc] = ...
    +# (m,m) -> (m)
    +cholesky_lo: Final[np.ufunc] = ...
    +cholesky_up: Final[np.ufunc] = ...
    +eigvals: Final[np.ufunc] = ...
    +eigvalsh_lo: Final[np.ufunc] = ...
    +eigvalsh_up: Final[np.ufunc] = ...
    +# (m,m) -> (m,m)
    +inv: Final[np.ufunc] = ...
    +# (m,n) -> (p)
    +qr_r_raw: Final[np.ufunc] = ...
    +svd: Final[np.ufunc] = ...
    +
    +###
    +# 1 -> 2
    +
    +# (m,m) -> (), ()
    +slogdet: Final[np.ufunc] = ...
    +# (m,m) -> (m), (m,m)
    +eig: Final[np.ufunc] = ...
    +eigh_lo: Final[np.ufunc] = ...
    +eigh_up: Final[np.ufunc] = ...
    +
    +###
    +# 2 -> 1
    +
    +# (m,n), (n) -> (m,m)
    +qr_complete: Final[_GUFunc_Nin2_Nout1[L["qr_complete"], L[2], None, L["(m,n),(n)->(m,m)"]]] = ...
    +# (m,n), (k) -> (m,k)
    +qr_reduced: Final[_GUFunc_Nin2_Nout1[L["qr_reduced"], L[2], None, L["(m,n),(k)->(m,k)"]]] = ...
    +# (m,m), (m,n) -> (m,n)
    +solve: Final[_GUFunc_Nin2_Nout1[L["solve"], L[4], None, L["(m,m),(m,n)->(m,n)"]]] = ...
    +# (m,m), (m) -> (m)
    +solve1: Final[_GUFunc_Nin2_Nout1[L["solve1"], L[4], None, L["(m,m),(m)->(m)"]]] = ...
    +
    +###
    +# 1 -> 3
    +
    +# (m,n) -> (m,m), (p), (n,n)
    +svd_f: Final[np.ufunc] = ...
    +# (m,n) -> (m,p), (p), (p,n)
    +svd_s: Final[np.ufunc] = ...
    +
    +###
    +# 3 -> 4
    +
    +# (m,n), (m,k), () -> (n,k), (k), (), (p)
    +lstsq: Final[np.ufunc] = ...
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/linalg/lapack_lite.cpython-314-x86_64-linux-gnu.so b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/linalg/lapack_lite.cpython-314-x86_64-linux-gnu.so
    new file mode 100644
    index 0000000000000000000000000000000000000000..9b11dcb13990319eaeba8388bddaf3da5f130472
    Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/linalg/lapack_lite.cpython-314-x86_64-linux-gnu.so differ
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/linalg/lapack_lite.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/linalg/lapack_lite.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..3ec3919bfa3b25acf3a04b29783ea5835fca02de
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/linalg/lapack_lite.pyi
    @@ -0,0 +1,143 @@
    +from typing import Final, TypedDict, type_check_only
    +
    +import numpy as np
    +from numpy._typing import NDArray
    +
    +from ._linalg import fortran_int
    +
    +###
    +
    +@type_check_only
    +class _GELSD(TypedDict):
    +    m: int
    +    n: int
    +    nrhs: int
    +    lda: int
    +    ldb: int
    +    rank: int
    +    lwork: int
    +    info: int
    +
    +@type_check_only
    +class _DGELSD(_GELSD):
    +    dgelsd_: int
    +    rcond: float
    +
    +@type_check_only
    +class _ZGELSD(_GELSD):
    +    zgelsd_: int
    +
    +@type_check_only
    +class _GEQRF(TypedDict):
    +    m: int
    +    n: int
    +    lda: int
    +    lwork: int
    +    info: int
    +
    +@type_check_only
    +class _DGEQRF(_GEQRF):
    +    dgeqrf_: int
    +
    +@type_check_only
    +class _ZGEQRF(_GEQRF):
    +    zgeqrf_: int
    +
    +@type_check_only
    +class _DORGQR(TypedDict):
    +    dorgqr_: int
    +    info: int
    +
    +@type_check_only
    +class _ZUNGQR(TypedDict):
    +    zungqr_: int
    +    info: int
    +
    +###
    +
    +_ilp64: Final[bool] = ...
    +
    +class LapackError(Exception): ...
    +
    +def dgelsd(
    +    m: int,
    +    n: int,
    +    nrhs: int,
    +    a: NDArray[np.float64],
    +    lda: int,
    +    b: NDArray[np.float64],
    +    ldb: int,
    +    s: NDArray[np.float64],
    +    rcond: float,
    +    rank: int,
    +    work: NDArray[np.float64],
    +    lwork: int,
    +    iwork: NDArray[fortran_int],
    +    info: int,
    +) -> _DGELSD: ...
    +def zgelsd(
    +    m: int,
    +    n: int,
    +    nrhs: int,
    +    a: NDArray[np.complex128],
    +    lda: int,
    +    b: NDArray[np.complex128],
    +    ldb: int,
    +    s: NDArray[np.float64],
    +    rcond: float,
    +    rank: int,
    +    work: NDArray[np.complex128],
    +    lwork: int,
    +    rwork: NDArray[np.float64],
    +    iwork: NDArray[fortran_int],
    +    info: int,
    +) -> _ZGELSD: ...
    +
    +#
    +def dgeqrf(
    +    m: int,
    +    n: int,
    +    a: NDArray[np.float64],  # in/out, shape: (lda, n)
    +    lda: int,
    +    tau: NDArray[np.float64],  # out, shape: (min(m, n),)
    +    work: NDArray[np.float64],  # out, shape: (max(1, lwork),)
    +    lwork: int,
    +    info: int,  # out
    +) -> _DGEQRF: ...
    +def zgeqrf(
    +    m: int,
    +    n: int,
    +    a: NDArray[np.complex128],  # in/out, shape: (lda, n)
    +    lda: int,
    +    tau: NDArray[np.complex128],  # out, shape: (min(m, n),)
    +    work: NDArray[np.complex128],  # out, shape: (max(1, lwork),)
    +    lwork: int,
    +    info: int,  # out
    +) -> _ZGEQRF: ...
    +
    +#
    +def dorgqr(
    +    m: int,  # >=0
    +    n: int,  # m >= n >= 0
    +    k: int,  # n >= k >= 0
    +    a: NDArray[np.float64],  # in/out, shape: (lda, n)
    +    lda: int,  # >= max(1, m)
    +    tau: NDArray[np.float64],  # in, shape: (k,)
    +    work: NDArray[np.float64],  # out, shape: (max(1, lwork),)
    +    lwork: int,
    +    info: int,  # out
    +) -> _DORGQR: ...
    +def zungqr(
    +    m: int,
    +    n: int,
    +    k: int,
    +    a: NDArray[np.complex128],
    +    lda: int,
    +    tau: NDArray[np.complex128],
    +    work: NDArray[np.complex128],
    +    lwork: int,
    +    info: int,
    +) -> _ZUNGQR: ...
    +
    +#
    +def xerbla(srname: object, info: int) -> None: ...
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/linalg/tests/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/linalg/tests/__init__.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/linalg/tests/test_deprecations.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/linalg/tests/test_deprecations.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..7fb5008f1ff88be426e3a1ad9e172306f0f57bde
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/linalg/tests/test_deprecations.py
    @@ -0,0 +1,21 @@
    +"""Test deprecation and future warnings.
    +
    +"""
    +import pytest
    +
    +import numpy as np
    +
    +
    +def test_qr_mode_full_future_warning():
    +    """Check mode='full' FutureWarning.
    +
    +    In numpy 1.8 the mode options 'full' and 'economic' in linalg.qr were
    +    deprecated. The release date will probably be sometime in the summer
    +    of 2013.
    +
    +    """
    +    a = np.eye(2)
    +    pytest.warns(DeprecationWarning, np.linalg.qr, a, mode='full')
    +    pytest.warns(DeprecationWarning, np.linalg.qr, a, mode='f')
    +    pytest.warns(DeprecationWarning, np.linalg.qr, a, mode='economic')
    +    pytest.warns(DeprecationWarning, np.linalg.qr, a, mode='e')
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/linalg/tests/test_linalg.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/linalg/tests/test_linalg.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..b3744024fd88e97838dc1a0fa68fe737db161396
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/linalg/tests/test_linalg.py
    @@ -0,0 +1,2442 @@
    +""" Test functions for linalg module
    +
    +"""
    +import itertools
    +import os
    +import subprocess
    +import sys
    +import textwrap
    +import threading
    +import traceback
    +import warnings
    +
    +import pytest
    +
    +import numpy as np
    +from numpy import (
    +    array,
    +    asarray,
    +    atleast_2d,
    +    cdouble,
    +    csingle,
    +    dot,
    +    double,
    +    identity,
    +    inf,
    +    linalg,
    +    matmul,
    +    multiply,
    +    single,
    +)
    +from numpy._core import swapaxes
    +from numpy.exceptions import AxisError
    +from numpy.linalg import LinAlgError, matrix_power, matrix_rank, multi_dot, norm
    +from numpy.linalg._linalg import _multi_dot_matrix_chain_order
    +from numpy.testing import (
    +    HAS_LAPACK64,
    +    IS_WASM,
    +    NOGIL_BUILD,
    +    assert_,
    +    assert_allclose,
    +    assert_almost_equal,
    +    assert_array_equal,
    +    assert_equal,
    +    assert_raises,
    +    assert_raises_regex,
    +)
    +
    +try:
    +    import numpy.linalg.lapack_lite
    +except ImportError:
    +    # May be broken when numpy was built without BLAS/LAPACK present
    +    # If so, ensure we don't break the whole test suite - the `lapack_lite`
    +    # submodule should be removed, it's only used in two tests in this file.
    +    pass
    +
    +
    +def consistent_subclass(out, in_):
    +    # For ndarray subclass input, our output should have the same subclass
    +    # (non-ndarray input gets converted to ndarray).
    +    return type(out) is (type(in_) if isinstance(in_, np.ndarray)
    +                         else np.ndarray)
    +
    +
    +old_assert_almost_equal = assert_almost_equal
    +
    +
    +def assert_almost_equal(a, b, single_decimal=6, double_decimal=12, **kw):
    +    if asarray(a).dtype.type in (single, csingle):
    +        decimal = single_decimal
    +    else:
    +        decimal = double_decimal
    +    old_assert_almost_equal(a, b, decimal=decimal, **kw)
    +
    +
    +def get_real_dtype(dtype):
    +    return {single: single, double: double,
    +            csingle: single, cdouble: double}[dtype]
    +
    +
    +def get_complex_dtype(dtype):
    +    return {single: csingle, double: cdouble,
    +            csingle: csingle, cdouble: cdouble}[dtype]
    +
    +
    +def get_rtol(dtype):
    +    # Choose a safe rtol
    +    if dtype in (single, csingle):
    +        return 1e-5
    +    else:
    +        return 1e-11
    +
    +
    +# used to categorize tests
    +all_tags = {
    +  'square', 'nonsquare', 'hermitian',  # mutually exclusive
    +  'generalized', 'size-0', 'strided'  # optional additions
    +}
    +
    +
    +class LinalgCase:
    +    def __init__(self, name, a, b, tags=set()):
    +        """
    +        A bundle of arguments to be passed to a test case, with an identifying
    +        name, the operands a and b, and a set of tags to filter the tests
    +        """
    +        assert_(isinstance(name, str))
    +        self.name = name
    +        self.a = a
    +        self.b = b
    +        self.tags = frozenset(tags)  # prevent shared tags
    +
    +    def check(self, do):
    +        """
    +        Run the function `do` on this test case, expanding arguments
    +        """
    +        do(self.a, self.b, tags=self.tags)
    +
    +    def __repr__(self):
    +        return f''
    +
    +
    +def apply_tag(tag, cases):
    +    """
    +    Add the given tag (a string) to each of the cases (a list of LinalgCase
    +    objects)
    +    """
    +    assert tag in all_tags, "Invalid tag"
    +    for case in cases:
    +        case.tags = case.tags | {tag}
    +    return cases
    +
    +
    +#
    +# Base test cases
    +#
    +
    +np.random.seed(1234)
    +
    +CASES = []
    +
    +# square test cases
    +CASES += apply_tag('square', [
    +    LinalgCase("single",
    +               array([[1., 2.], [3., 4.]], dtype=single),
    +               array([2., 1.], dtype=single)),
    +    LinalgCase("double",
    +               array([[1., 2.], [3., 4.]], dtype=double),
    +               array([2., 1.], dtype=double)),
    +    LinalgCase("double_2",
    +               array([[1., 2.], [3., 4.]], dtype=double),
    +               array([[2., 1., 4.], [3., 4., 6.]], dtype=double)),
    +    LinalgCase("csingle",
    +               array([[1. + 2j, 2 + 3j], [3 + 4j, 4 + 5j]], dtype=csingle),
    +               array([2. + 1j, 1. + 2j], dtype=csingle)),
    +    LinalgCase("cdouble",
    +               array([[1. + 2j, 2 + 3j], [3 + 4j, 4 + 5j]], dtype=cdouble),
    +               array([2. + 1j, 1. + 2j], dtype=cdouble)),
    +    LinalgCase("cdouble_2",
    +               array([[1. + 2j, 2 + 3j], [3 + 4j, 4 + 5j]], dtype=cdouble),
    +               array([[2. + 1j, 1. + 2j, 1 + 3j], [1 - 2j, 1 - 3j, 1 - 6j]], dtype=cdouble)),
    +    LinalgCase("0x0",
    +               np.empty((0, 0), dtype=double),
    +               np.empty((0,), dtype=double),
    +               tags={'size-0'}),
    +    LinalgCase("8x8",
    +               np.random.rand(8, 8),
    +               np.random.rand(8)),
    +    LinalgCase("1x1",
    +               np.random.rand(1, 1),
    +               np.random.rand(1)),
    +    LinalgCase("nonarray",
    +               [[1, 2], [3, 4]],
    +               [2, 1]),
    +])
    +
    +# non-square test-cases
    +CASES += apply_tag('nonsquare', [
    +    LinalgCase("single_nsq_1",
    +               array([[1., 2., 3.], [3., 4., 6.]], dtype=single),
    +               array([2., 1.], dtype=single)),
    +    LinalgCase("single_nsq_2",
    +               array([[1., 2.], [3., 4.], [5., 6.]], dtype=single),
    +               array([2., 1., 3.], dtype=single)),
    +    LinalgCase("double_nsq_1",
    +               array([[1., 2., 3.], [3., 4., 6.]], dtype=double),
    +               array([2., 1.], dtype=double)),
    +    LinalgCase("double_nsq_2",
    +               array([[1., 2.], [3., 4.], [5., 6.]], dtype=double),
    +               array([2., 1., 3.], dtype=double)),
    +    LinalgCase("csingle_nsq_1",
    +               array(
    +                   [[1. + 1j, 2. + 2j, 3. - 3j], [3. - 5j, 4. + 9j, 6. + 2j]], dtype=csingle),
    +               array([2. + 1j, 1. + 2j], dtype=csingle)),
    +    LinalgCase("csingle_nsq_2",
    +               array(
    +                   [[1. + 1j, 2. + 2j], [3. - 3j, 4. - 9j], [5. - 4j, 6. + 8j]], dtype=csingle),
    +               array([2. + 1j, 1. + 2j, 3. - 3j], dtype=csingle)),
    +    LinalgCase("cdouble_nsq_1",
    +               array(
    +                   [[1. + 1j, 2. + 2j, 3. - 3j], [3. - 5j, 4. + 9j, 6. + 2j]], dtype=cdouble),
    +               array([2. + 1j, 1. + 2j], dtype=cdouble)),
    +    LinalgCase("cdouble_nsq_2",
    +               array(
    +                   [[1. + 1j, 2. + 2j], [3. - 3j, 4. - 9j], [5. - 4j, 6. + 8j]], dtype=cdouble),
    +               array([2. + 1j, 1. + 2j, 3. - 3j], dtype=cdouble)),
    +    LinalgCase("cdouble_nsq_1_2",
    +               array(
    +                   [[1. + 1j, 2. + 2j, 3. - 3j], [3. - 5j, 4. + 9j, 6. + 2j]], dtype=cdouble),
    +               array([[2. + 1j, 1. + 2j], [1 - 1j, 2 - 2j]], dtype=cdouble)),
    +    LinalgCase("cdouble_nsq_2_2",
    +               array(
    +                   [[1. + 1j, 2. + 2j], [3. - 3j, 4. - 9j], [5. - 4j, 6. + 8j]], dtype=cdouble),
    +               array([[2. + 1j, 1. + 2j], [1 - 1j, 2 - 2j], [1 - 1j, 2 - 2j]], dtype=cdouble)),
    +    LinalgCase("8x11",
    +               np.random.rand(8, 11),
    +               np.random.rand(8)),
    +    LinalgCase("1x5",
    +               np.random.rand(1, 5),
    +               np.random.rand(1)),
    +    LinalgCase("5x1",
    +               np.random.rand(5, 1),
    +               np.random.rand(5)),
    +    LinalgCase("0x4",
    +               np.random.rand(0, 4),
    +               np.random.rand(0),
    +               tags={'size-0'}),
    +    LinalgCase("4x0",
    +               np.random.rand(4, 0),
    +               np.random.rand(4),
    +               tags={'size-0'}),
    +])
    +
    +# hermitian test-cases
    +CASES += apply_tag('hermitian', [
    +    LinalgCase("hsingle",
    +               array([[1., 2.], [2., 1.]], dtype=single),
    +               None),
    +    LinalgCase("hdouble",
    +               array([[1., 2.], [2., 1.]], dtype=double),
    +               None),
    +    LinalgCase("hcsingle",
    +               array([[1., 2 + 3j], [2 - 3j, 1]], dtype=csingle),
    +               None),
    +    LinalgCase("hcdouble",
    +               array([[1., 2 + 3j], [2 - 3j, 1]], dtype=cdouble),
    +               None),
    +    LinalgCase("hempty",
    +               np.empty((0, 0), dtype=double),
    +               None,
    +               tags={'size-0'}),
    +    LinalgCase("hnonarray",
    +               [[1, 2], [2, 1]],
    +               None),
    +    LinalgCase("matrix_b_only",
    +               array([[1., 2.], [2., 1.]]),
    +               None),
    +    LinalgCase("hmatrix_1x1",
    +               np.random.rand(1, 1),
    +               None),
    +])
    +
    +
    +#
    +# Gufunc test cases
    +#
    +def _make_generalized_cases():
    +    new_cases = []
    +
    +    for case in CASES:
    +        if not isinstance(case.a, np.ndarray):
    +            continue
    +
    +        a = np.array([case.a, 2 * case.a, 3 * case.a])
    +        if case.b is None:
    +            b = None
    +        elif case.b.ndim == 1:
    +            b = case.b
    +        else:
    +            b = np.array([case.b, 7 * case.b, 6 * case.b])
    +        new_case = LinalgCase(case.name + "_tile3", a, b,
    +                              tags=case.tags | {'generalized'})
    +        new_cases.append(new_case)
    +
    +        a = np.array([case.a] * 2 * 3).reshape((3, 2) + case.a.shape)
    +        if case.b is None:
    +            b = None
    +        elif case.b.ndim == 1:
    +            b = np.array([case.b] * 2 * 3 * a.shape[-1])\
    +                  .reshape((3, 2) + case.a.shape[-2:])
    +        else:
    +            b = np.array([case.b] * 2 * 3).reshape((3, 2) + case.b.shape)
    +        new_case = LinalgCase(case.name + "_tile213", a, b,
    +                              tags=case.tags | {'generalized'})
    +        new_cases.append(new_case)
    +
    +    return new_cases
    +
    +
    +CASES += _make_generalized_cases()
    +
    +
    +#
    +# Generate stride combination variations of the above
    +#
    +def _stride_comb_iter(x):
    +    """
    +    Generate cartesian product of strides for all axes
    +    """
    +
    +    if not isinstance(x, np.ndarray):
    +        yield x, "nop"
    +        return
    +
    +    stride_set = [(1,)] * x.ndim
    +    stride_set[-1] = (1, 3, -4)
    +    if x.ndim > 1:
    +        stride_set[-2] = (1, 3, -4)
    +    if x.ndim > 2:
    +        stride_set[-3] = (1, -4)
    +
    +    for repeats in itertools.product(*tuple(stride_set)):
    +        new_shape = [abs(a * b) for a, b in zip(x.shape, repeats)]
    +        slices = tuple(slice(None, None, repeat) for repeat in repeats)
    +
    +        # new array with different strides, but same data
    +        xi = np.empty(new_shape, dtype=x.dtype)
    +        xi.view(np.uint32).fill(0xdeadbeef)
    +        xi = xi[slices]
    +        xi[...] = x
    +        xi = xi.view(x.__class__)
    +        assert_(np.all(xi == x))
    +        yield xi, "stride_" + "_".join(["%+d" % j for j in repeats])
    +
    +        # generate also zero strides if possible
    +        if x.ndim >= 1 and x.shape[-1] == 1:
    +            s = list(x.strides)
    +            s[-1] = 0
    +            xi = np.lib.stride_tricks.as_strided(x, strides=s)
    +            yield xi, "stride_xxx_0"
    +        if x.ndim >= 2 and x.shape[-2] == 1:
    +            s = list(x.strides)
    +            s[-2] = 0
    +            xi = np.lib.stride_tricks.as_strided(x, strides=s)
    +            yield xi, "stride_xxx_0_x"
    +        if x.ndim >= 2 and x.shape[:-2] == (1, 1):
    +            s = list(x.strides)
    +            s[-1] = 0
    +            s[-2] = 0
    +            xi = np.lib.stride_tricks.as_strided(x, strides=s)
    +            yield xi, "stride_xxx_0_0"
    +
    +
    +def _make_strided_cases():
    +    new_cases = []
    +    for case in CASES:
    +        for a, a_label in _stride_comb_iter(case.a):
    +            for b, b_label in _stride_comb_iter(case.b):
    +                new_case = LinalgCase(case.name + "_" + a_label + "_" + b_label, a, b,
    +                                      tags=case.tags | {'strided'})
    +                new_cases.append(new_case)
    +    return new_cases
    +
    +
    +CASES += _make_strided_cases()
    +
    +
    +#
    +# Test different routines against the above cases
    +#
    +class LinalgTestCase:
    +    TEST_CASES = CASES
    +
    +    def check_cases(self, require=set(), exclude=set()):
    +        """
    +        Run func on each of the cases with all of the tags in require, and none
    +        of the tags in exclude
    +        """
    +        for case in self.TEST_CASES:
    +            # filter by require and exclude
    +            if case.tags & require != require:
    +                continue
    +            if case.tags & exclude:
    +                continue
    +
    +            try:
    +                case.check(self.do)
    +            except Exception as e:
    +                msg = f'In test case: {case!r}\n\n'
    +                msg += traceback.format_exc()
    +                raise AssertionError(msg) from e
    +
    +
    +class LinalgSquareTestCase(LinalgTestCase):
    +
    +    def test_sq_cases(self):
    +        self.check_cases(require={'square'},
    +                         exclude={'generalized', 'size-0'})
    +
    +    def test_empty_sq_cases(self):
    +        self.check_cases(require={'square', 'size-0'},
    +                         exclude={'generalized'})
    +
    +
    +class LinalgNonsquareTestCase(LinalgTestCase):
    +
    +    def test_nonsq_cases(self):
    +        self.check_cases(require={'nonsquare'},
    +                         exclude={'generalized', 'size-0'})
    +
    +    def test_empty_nonsq_cases(self):
    +        self.check_cases(require={'nonsquare', 'size-0'},
    +                         exclude={'generalized'})
    +
    +
    +class HermitianTestCase(LinalgTestCase):
    +
    +    def test_herm_cases(self):
    +        self.check_cases(require={'hermitian'},
    +                         exclude={'generalized', 'size-0'})
    +
    +    def test_empty_herm_cases(self):
    +        self.check_cases(require={'hermitian', 'size-0'},
    +                         exclude={'generalized'})
    +
    +
    +class LinalgGeneralizedSquareTestCase(LinalgTestCase):
    +
    +    @pytest.mark.slow
    +    def test_generalized_sq_cases(self):
    +        self.check_cases(require={'generalized', 'square'},
    +                         exclude={'size-0'})
    +
    +    @pytest.mark.slow
    +    def test_generalized_empty_sq_cases(self):
    +        self.check_cases(require={'generalized', 'square', 'size-0'})
    +
    +
    +class LinalgGeneralizedNonsquareTestCase(LinalgTestCase):
    +
    +    @pytest.mark.slow
    +    def test_generalized_nonsq_cases(self):
    +        self.check_cases(require={'generalized', 'nonsquare'},
    +                         exclude={'size-0'})
    +
    +    @pytest.mark.slow
    +    def test_generalized_empty_nonsq_cases(self):
    +        self.check_cases(require={'generalized', 'nonsquare', 'size-0'})
    +
    +
    +class HermitianGeneralizedTestCase(LinalgTestCase):
    +
    +    @pytest.mark.slow
    +    def test_generalized_herm_cases(self):
    +        self.check_cases(require={'generalized', 'hermitian'},
    +                         exclude={'size-0'})
    +
    +    @pytest.mark.slow
    +    def test_generalized_empty_herm_cases(self):
    +        self.check_cases(require={'generalized', 'hermitian', 'size-0'},
    +                         exclude={'none'})
    +
    +
    +def identity_like_generalized(a):
    +    a = asarray(a)
    +    if a.ndim >= 3:
    +        r = np.empty(a.shape, dtype=a.dtype)
    +        r[...] = identity(a.shape[-2])
    +        return r
    +    else:
    +        return identity(a.shape[0])
    +
    +
    +class SolveCases(LinalgSquareTestCase, LinalgGeneralizedSquareTestCase):
    +    # kept apart from TestSolve for use for testing with matrices.
    +    def do(self, a, b, tags):
    +        x = linalg.solve(a, b)
    +        if np.array(b).ndim == 1:
    +            # When a is (..., M, M) and b is (M,), it is the same as when b is
    +            # (M, 1), except the result has shape (..., M)
    +            adotx = matmul(a, x[..., None])[..., 0]
    +            assert_almost_equal(np.broadcast_to(b, adotx.shape), adotx)
    +        else:
    +            adotx = matmul(a, x)
    +            assert_almost_equal(b, adotx)
    +        assert_(consistent_subclass(x, b))
    +
    +
    +class TestSolve(SolveCases):
    +    @pytest.mark.parametrize('dtype', [single, double, csingle, cdouble])
    +    def test_types(self, dtype):
    +        x = np.array([[1, 0.5], [0.5, 1]], dtype=dtype)
    +        assert_equal(linalg.solve(x, x).dtype, dtype)
    +
    +    def test_1_d(self):
    +        class ArraySubclass(np.ndarray):
    +            pass
    +        a = np.arange(8).reshape(2, 2, 2)
    +        b = np.arange(2).view(ArraySubclass)
    +        result = linalg.solve(a, b)
    +        assert result.shape == (2, 2)
    +
    +        # If b is anything other than 1-D it should be treated as a stack of
    +        # matrices
    +        b = np.arange(4).reshape(2, 2).view(ArraySubclass)
    +        result = linalg.solve(a, b)
    +        assert result.shape == (2, 2, 2)
    +
    +        b = np.arange(2).reshape(1, 2).view(ArraySubclass)
    +        assert_raises(ValueError, linalg.solve, a, b)
    +
    +    def test_0_size(self):
    +        class ArraySubclass(np.ndarray):
    +            pass
    +        # Test system of 0x0 matrices
    +        a = np.arange(8).reshape(2, 2, 2)
    +        b = np.arange(6).reshape(1, 2, 3).view(ArraySubclass)
    +
    +        expected = linalg.solve(a, b)[:, 0:0, :]
    +        result = linalg.solve(a[:, 0:0, 0:0], b[:, 0:0, :])
    +        assert_array_equal(result, expected)
    +        assert_(isinstance(result, ArraySubclass))
    +
    +        # Test errors for non-square and only b's dimension being 0
    +        assert_raises(linalg.LinAlgError, linalg.solve, a[:, 0:0, 0:1], b)
    +        assert_raises(ValueError, linalg.solve, a, b[:, 0:0, :])
    +
    +        # Test broadcasting error
    +        b = np.arange(6).reshape(1, 3, 2)  # broadcasting error
    +        assert_raises(ValueError, linalg.solve, a, b)
    +        assert_raises(ValueError, linalg.solve, a[0:0], b[0:0])
    +
    +        # Test zero "single equations" with 0x0 matrices.
    +        b = np.arange(2).view(ArraySubclass)
    +        expected = linalg.solve(a, b)[:, 0:0]
    +        result = linalg.solve(a[:, 0:0, 0:0], b[0:0])
    +        assert_array_equal(result, expected)
    +        assert_(isinstance(result, ArraySubclass))
    +
    +        b = np.arange(3).reshape(1, 3)
    +        assert_raises(ValueError, linalg.solve, a, b)
    +        assert_raises(ValueError, linalg.solve, a[0:0], b[0:0])
    +        assert_raises(ValueError, linalg.solve, a[:, 0:0, 0:0], b)
    +
    +    def test_0_size_k(self):
    +        # test zero multiple equation (K=0) case.
    +        class ArraySubclass(np.ndarray):
    +            pass
    +        a = np.arange(4).reshape(1, 2, 2)
    +        b = np.arange(6).reshape(3, 2, 1).view(ArraySubclass)
    +
    +        expected = linalg.solve(a, b)[:, :, 0:0]
    +        result = linalg.solve(a, b[:, :, 0:0])
    +        assert_array_equal(result, expected)
    +        assert_(isinstance(result, ArraySubclass))
    +
    +        # test both zero.
    +        expected = linalg.solve(a, b)[:, 0:0, 0:0]
    +        result = linalg.solve(a[:, 0:0, 0:0], b[:, 0:0, 0:0])
    +        assert_array_equal(result, expected)
    +        assert_(isinstance(result, ArraySubclass))
    +
    +
    +class InvCases(LinalgSquareTestCase, LinalgGeneralizedSquareTestCase):
    +
    +    def do(self, a, b, tags):
    +        a_inv = linalg.inv(a)
    +        assert_almost_equal(matmul(a, a_inv),
    +                            identity_like_generalized(a))
    +        assert_(consistent_subclass(a_inv, a))
    +
    +
    +class TestInv(InvCases):
    +    @pytest.mark.parametrize('dtype', [single, double, csingle, cdouble])
    +    def test_types(self, dtype):
    +        x = np.array([[1, 0.5], [0.5, 1]], dtype=dtype)
    +        assert_equal(linalg.inv(x).dtype, dtype)
    +
    +    def test_0_size(self):
    +        # Check that all kinds of 0-sized arrays work
    +        class ArraySubclass(np.ndarray):
    +            pass
    +        a = np.zeros((0, 1, 1), dtype=np.int_).view(ArraySubclass)
    +        res = linalg.inv(a)
    +        assert_(res.dtype.type is np.float64)
    +        assert_equal(a.shape, res.shape)
    +        assert_(isinstance(res, ArraySubclass))
    +
    +        a = np.zeros((0, 0), dtype=np.complex64).view(ArraySubclass)
    +        res = linalg.inv(a)
    +        assert_(res.dtype.type is np.complex64)
    +        assert_equal(a.shape, res.shape)
    +        assert_(isinstance(res, ArraySubclass))
    +
    +
    +class EigvalsCases(LinalgSquareTestCase, LinalgGeneralizedSquareTestCase):
    +
    +    def do(self, a, b, tags):
    +        ev = linalg.eigvals(a)
    +        evalues, evectors = linalg.eig(a)
    +        assert_almost_equal(ev, evalues)
    +
    +
    +class TestEigvals(EigvalsCases):
    +    @pytest.mark.parametrize('dtype', [single, double, csingle, cdouble])
    +    def test_types(self, dtype):
    +        x = np.array([[1, 0.5], [0.5, 1]], dtype=dtype)
    +        assert_equal(linalg.eigvals(x).dtype, dtype)
    +        x = np.array([[1, 0.5], [-1, 1]], dtype=dtype)
    +        assert_equal(linalg.eigvals(x).dtype, get_complex_dtype(dtype))
    +
    +    def test_0_size(self):
    +        # Check that all kinds of 0-sized arrays work
    +        class ArraySubclass(np.ndarray):
    +            pass
    +        a = np.zeros((0, 1, 1), dtype=np.int_).view(ArraySubclass)
    +        res = linalg.eigvals(a)
    +        assert_(res.dtype.type is np.float64)
    +        assert_equal((0, 1), res.shape)
    +        # This is just for documentation, it might make sense to change:
    +        assert_(isinstance(res, np.ndarray))
    +
    +        a = np.zeros((0, 0), dtype=np.complex64).view(ArraySubclass)
    +        res = linalg.eigvals(a)
    +        assert_(res.dtype.type is np.complex64)
    +        assert_equal((0,), res.shape)
    +        # This is just for documentation, it might make sense to change:
    +        assert_(isinstance(res, np.ndarray))
    +
    +
    +class EigCases(LinalgSquareTestCase, LinalgGeneralizedSquareTestCase):
    +
    +    def do(self, a, b, tags):
    +        res = linalg.eig(a)
    +        eigenvalues, eigenvectors = res.eigenvalues, res.eigenvectors
    +        assert_allclose(matmul(a, eigenvectors),
    +                        np.asarray(eigenvectors) * np.asarray(eigenvalues)[..., None, :],
    +                        rtol=get_rtol(eigenvalues.dtype))
    +        assert_(consistent_subclass(eigenvectors, a))
    +
    +
    +class TestEig(EigCases):
    +    @pytest.mark.parametrize('dtype', [single, double, csingle, cdouble])
    +    def test_types(self, dtype):
    +        x = np.array([[1, 0.5], [0.5, 1]], dtype=dtype)
    +        w, v = np.linalg.eig(x)
    +        assert_equal(w.dtype, dtype)
    +        assert_equal(v.dtype, dtype)
    +
    +        x = np.array([[1, 0.5], [-1, 1]], dtype=dtype)
    +        w, v = np.linalg.eig(x)
    +        assert_equal(w.dtype, get_complex_dtype(dtype))
    +        assert_equal(v.dtype, get_complex_dtype(dtype))
    +
    +    def test_0_size(self):
    +        # Check that all kinds of 0-sized arrays work
    +        class ArraySubclass(np.ndarray):
    +            pass
    +        a = np.zeros((0, 1, 1), dtype=np.int_).view(ArraySubclass)
    +        res, res_v = linalg.eig(a)
    +        assert_(res_v.dtype.type is np.float64)
    +        assert_(res.dtype.type is np.float64)
    +        assert_equal(a.shape, res_v.shape)
    +        assert_equal((0, 1), res.shape)
    +        # This is just for documentation, it might make sense to change:
    +        assert_(isinstance(a, np.ndarray))
    +
    +        a = np.zeros((0, 0), dtype=np.complex64).view(ArraySubclass)
    +        res, res_v = linalg.eig(a)
    +        assert_(res_v.dtype.type is np.complex64)
    +        assert_(res.dtype.type is np.complex64)
    +        assert_equal(a.shape, res_v.shape)
    +        assert_equal((0,), res.shape)
    +        # This is just for documentation, it might make sense to change:
    +        assert_(isinstance(a, np.ndarray))
    +
    +
    +class SVDBaseTests:
    +    hermitian = False
    +
    +    @pytest.mark.parametrize('dtype', [single, double, csingle, cdouble])
    +    def test_types(self, dtype):
    +        x = np.array([[1, 0.5], [0.5, 1]], dtype=dtype)
    +        res = linalg.svd(x)
    +        U, S, Vh = res.U, res.S, res.Vh
    +        assert_equal(U.dtype, dtype)
    +        assert_equal(S.dtype, get_real_dtype(dtype))
    +        assert_equal(Vh.dtype, dtype)
    +        s = linalg.svd(x, compute_uv=False, hermitian=self.hermitian)
    +        assert_equal(s.dtype, get_real_dtype(dtype))
    +
    +
    +class SVDCases(LinalgSquareTestCase, LinalgGeneralizedSquareTestCase):
    +
    +    def do(self, a, b, tags):
    +        u, s, vt = linalg.svd(a, False)
    +        assert_allclose(a, matmul(np.asarray(u) * np.asarray(s)[..., None, :],
    +                                           np.asarray(vt)),
    +                        rtol=get_rtol(u.dtype))
    +        assert_(consistent_subclass(u, a))
    +        assert_(consistent_subclass(vt, a))
    +
    +
    +class TestSVD(SVDCases, SVDBaseTests):
    +    def test_empty_identity(self):
    +        """ Empty input should put an identity matrix in u or vh """
    +        x = np.empty((4, 0))
    +        u, s, vh = linalg.svd(x, compute_uv=True, hermitian=self.hermitian)
    +        assert_equal(u.shape, (4, 4))
    +        assert_equal(vh.shape, (0, 0))
    +        assert_equal(u, np.eye(4))
    +
    +        x = np.empty((0, 4))
    +        u, s, vh = linalg.svd(x, compute_uv=True, hermitian=self.hermitian)
    +        assert_equal(u.shape, (0, 0))
    +        assert_equal(vh.shape, (4, 4))
    +        assert_equal(vh, np.eye(4))
    +
    +    def test_svdvals(self):
    +        x = np.array([[1, 0.5], [0.5, 1]])
    +        s_from_svd = linalg.svd(x, compute_uv=False, hermitian=self.hermitian)
    +        s_from_svdvals = linalg.svdvals(x)
    +        assert_almost_equal(s_from_svd, s_from_svdvals)
    +
    +
    +class SVDHermitianCases(HermitianTestCase, HermitianGeneralizedTestCase):
    +
    +    def do(self, a, b, tags):
    +        u, s, vt = linalg.svd(a, False, hermitian=True)
    +        assert_allclose(a, matmul(np.asarray(u) * np.asarray(s)[..., None, :],
    +                                           np.asarray(vt)),
    +                        rtol=get_rtol(u.dtype))
    +
    +        def hermitian(mat):
    +            axes = list(range(mat.ndim))
    +            axes[-1], axes[-2] = axes[-2], axes[-1]
    +            return np.conj(np.transpose(mat, axes=axes))
    +
    +        assert_almost_equal(np.matmul(u, hermitian(u)), np.broadcast_to(np.eye(u.shape[-1]), u.shape))
    +        assert_almost_equal(np.matmul(vt, hermitian(vt)), np.broadcast_to(np.eye(vt.shape[-1]), vt.shape))
    +        assert_equal(np.sort(s)[..., ::-1], s)
    +        assert_(consistent_subclass(u, a))
    +        assert_(consistent_subclass(vt, a))
    +
    +
    +class TestSVDHermitian(SVDHermitianCases, SVDBaseTests):
    +    hermitian = True
    +
    +
    +class CondCases(LinalgSquareTestCase, LinalgGeneralizedSquareTestCase):
    +    # cond(x, p) for p in (None, 2, -2)
    +
    +    def do(self, a, b, tags):
    +        c = asarray(a)  # a might be a matrix
    +        if 'size-0' in tags:
    +            assert_raises(LinAlgError, linalg.cond, c)
    +            return
    +
    +        # +-2 norms
    +        s = linalg.svd(c, compute_uv=False)
    +        assert_almost_equal(
    +            linalg.cond(a), s[..., 0] / s[..., -1],
    +            single_decimal=5, double_decimal=11)
    +        assert_almost_equal(
    +            linalg.cond(a, 2), s[..., 0] / s[..., -1],
    +            single_decimal=5, double_decimal=11)
    +        assert_almost_equal(
    +            linalg.cond(a, -2), s[..., -1] / s[..., 0],
    +            single_decimal=5, double_decimal=11)
    +
    +        # Other norms
    +        cinv = np.linalg.inv(c)
    +        assert_almost_equal(
    +            linalg.cond(a, 1),
    +            abs(c).sum(-2).max(-1) * abs(cinv).sum(-2).max(-1),
    +            single_decimal=5, double_decimal=11)
    +        assert_almost_equal(
    +            linalg.cond(a, -1),
    +            abs(c).sum(-2).min(-1) * abs(cinv).sum(-2).min(-1),
    +            single_decimal=5, double_decimal=11)
    +        assert_almost_equal(
    +            linalg.cond(a, np.inf),
    +            abs(c).sum(-1).max(-1) * abs(cinv).sum(-1).max(-1),
    +            single_decimal=5, double_decimal=11)
    +        assert_almost_equal(
    +            linalg.cond(a, -np.inf),
    +            abs(c).sum(-1).min(-1) * abs(cinv).sum(-1).min(-1),
    +            single_decimal=5, double_decimal=11)
    +        assert_almost_equal(
    +            linalg.cond(a, 'fro'),
    +            np.sqrt((abs(c)**2).sum(-1).sum(-1)
    +                    * (abs(cinv)**2).sum(-1).sum(-1)),
    +            single_decimal=5, double_decimal=11)
    +
    +
    +class TestCond(CondCases):
    +    @pytest.mark.parametrize('is_complex', [False, True])
    +    def test_basic_nonsvd(self, is_complex):
    +        # Smoketest the non-svd norms
    +        A = array([[1., 0, 1], [0, -2., 0], [0, 0, 3.]])
    +        if is_complex:
    +            # Since A is linearly scaled, the condition number should not change
    +            A = A * (1 + 1j)
    +        assert_almost_equal(linalg.cond(A, inf), 4)
    +        assert_almost_equal(linalg.cond(A, -inf), 2 / 3)
    +        assert_almost_equal(linalg.cond(A, 1), 4)
    +        assert_almost_equal(linalg.cond(A, -1), 0.5)
    +        assert_almost_equal(linalg.cond(A, 'fro'), np.sqrt(265 / 12))
    +
    +    @pytest.mark.parametrize('dtype', [single, double, csingle, cdouble])
    +    @pytest.mark.parametrize('norm_ord', [1, -1, 2, -2, 'fro', np.inf, -np.inf])
    +    def test_cond_dtypes(self, dtype, norm_ord):
    +        # Check that the condition number is computed in the same dtype
    +        # as the input matrix
    +        A = array([[1., 0, 1], [0, -2., 0], [0, 0, 3.]], dtype=dtype)
    +        out_type = get_real_dtype(dtype)
    +        assert_equal(linalg.cond(A, p=norm_ord).dtype, out_type)
    +
    +    def test_singular(self):
    +        # Singular matrices have infinite condition number for
    +        # positive norms, and negative norms shouldn't raise
    +        # exceptions
    +        As = [np.zeros((2, 2)), np.ones((2, 2))]
    +        p_pos = [None, 1, 2, 'fro']
    +        p_neg = [-1, -2]
    +        for A, p in itertools.product(As, p_pos):
    +            # Inversion may not hit exact infinity, so just check the
    +            # number is large
    +            assert_(linalg.cond(A, p) > 1e15)
    +        for A, p in itertools.product(As, p_neg):
    +            linalg.cond(A, p)
    +
    +    @pytest.mark.xfail(True, run=False,
    +                       reason="Platform/LAPACK-dependent failure, "
    +                              "see gh-18914")
    +    def test_nan(self):
    +        # nans should be passed through, not converted to infs
    +        ps = [None, 1, -1, 2, -2, 'fro']
    +        p_pos = [None, 1, 2, 'fro']
    +
    +        A = np.ones((2, 2))
    +        A[0, 1] = np.nan
    +        for p in ps:
    +            c = linalg.cond(A, p)
    +            assert_(isinstance(c, np.float64))
    +            assert_(np.isnan(c))
    +
    +        A = np.ones((3, 2, 2))
    +        A[1, 0, 1] = np.nan
    +        for p in ps:
    +            c = linalg.cond(A, p)
    +            assert_(np.isnan(c[1]))
    +            if p in p_pos:
    +                assert_(c[0] > 1e15)
    +                assert_(c[2] > 1e15)
    +            else:
    +                assert_(not np.isnan(c[0]))
    +                assert_(not np.isnan(c[2]))
    +
    +    def test_stacked_singular(self):
    +        # Check behavior when only some of the stacked matrices are
    +        # singular
    +        np.random.seed(1234)
    +        A = np.random.rand(2, 2, 2, 2)
    +        A[0, 0] = 0
    +        A[1, 1] = 0
    +
    +        for p in (None, 1, 2, 'fro', -1, -2):
    +            c = linalg.cond(A, p)
    +            assert_equal(c[0, 0], np.inf)
    +            assert_equal(c[1, 1], np.inf)
    +            assert_(np.isfinite(c[0, 1]))
    +            assert_(np.isfinite(c[1, 0]))
    +
    +
    +class PinvCases(LinalgSquareTestCase,
    +                LinalgNonsquareTestCase,
    +                LinalgGeneralizedSquareTestCase,
    +                LinalgGeneralizedNonsquareTestCase):
    +
    +    def do(self, a, b, tags):
    +        a_ginv = linalg.pinv(a)
    +        # `a @ a_ginv == I` does not hold if a is singular
    +        dot = matmul
    +        assert_almost_equal(dot(dot(a, a_ginv), a), a, single_decimal=5, double_decimal=11)
    +        assert_(consistent_subclass(a_ginv, a))
    +
    +
    +class TestPinv(PinvCases):
    +    pass
    +
    +
    +class PinvHermitianCases(HermitianTestCase, HermitianGeneralizedTestCase):
    +
    +    def do(self, a, b, tags):
    +        a_ginv = linalg.pinv(a, hermitian=True)
    +        # `a @ a_ginv == I` does not hold if a is singular
    +        dot = matmul
    +        assert_almost_equal(dot(dot(a, a_ginv), a), a, single_decimal=5, double_decimal=11)
    +        assert_(consistent_subclass(a_ginv, a))
    +
    +
    +class TestPinvHermitian(PinvHermitianCases):
    +    pass
    +
    +
    +def test_pinv_rtol_arg():
    +    a = np.array([[1, 2, 3], [4, 1, 1], [2, 3, 1]])
    +
    +    assert_almost_equal(
    +        np.linalg.pinv(a, rcond=0.5),
    +        np.linalg.pinv(a, rtol=0.5),
    +    )
    +
    +    with pytest.raises(
    +        ValueError, match=r"`rtol` and `rcond` can't be both set."
    +    ):
    +        np.linalg.pinv(a, rcond=0.5, rtol=0.5)
    +
    +
    +class DetCases(LinalgSquareTestCase, LinalgGeneralizedSquareTestCase):
    +
    +    def do(self, a, b, tags):
    +        d = linalg.det(a)
    +        res = linalg.slogdet(a)
    +        s, ld = res.sign, res.logabsdet
    +        if asarray(a).dtype.type in (single, double):
    +            ad = asarray(a).astype(double)
    +        else:
    +            ad = asarray(a).astype(cdouble)
    +        ev = linalg.eigvals(ad)
    +        assert_almost_equal(d, multiply.reduce(ev, axis=-1))
    +        assert_almost_equal(s * np.exp(ld), multiply.reduce(ev, axis=-1))
    +
    +        s = np.atleast_1d(s)
    +        ld = np.atleast_1d(ld)
    +        m = (s != 0)
    +        assert_almost_equal(np.abs(s[m]), 1)
    +        assert_equal(ld[~m], -inf)
    +
    +
    +class TestDet(DetCases):
    +    def test_zero(self):
    +        assert_equal(linalg.det([[0.0]]), 0.0)
    +        assert_equal(type(linalg.det([[0.0]])), double)
    +        assert_equal(linalg.det([[0.0j]]), 0.0)
    +        assert_equal(type(linalg.det([[0.0j]])), cdouble)
    +
    +        assert_equal(linalg.slogdet([[0.0]]), (0.0, -inf))
    +        assert_equal(type(linalg.slogdet([[0.0]])[0]), double)
    +        assert_equal(type(linalg.slogdet([[0.0]])[1]), double)
    +        assert_equal(linalg.slogdet([[0.0j]]), (0.0j, -inf))
    +        assert_equal(type(linalg.slogdet([[0.0j]])[0]), cdouble)
    +        assert_equal(type(linalg.slogdet([[0.0j]])[1]), double)
    +
    +    @pytest.mark.parametrize('dtype', [single, double, csingle, cdouble])
    +    def test_types(self, dtype):
    +        x = np.array([[1, 0.5], [0.5, 1]], dtype=dtype)
    +        assert_equal(np.linalg.det(x).dtype, dtype)
    +        ph, s = np.linalg.slogdet(x)
    +        assert_equal(s.dtype, get_real_dtype(dtype))
    +        assert_equal(ph.dtype, dtype)
    +
    +    def test_0_size(self):
    +        a = np.zeros((0, 0), dtype=np.complex64)
    +        res = linalg.det(a)
    +        assert_equal(res, 1.)
    +        assert_(res.dtype.type is np.complex64)
    +        res = linalg.slogdet(a)
    +        assert_equal(res, (1, 0))
    +        assert_(res[0].dtype.type is np.complex64)
    +        assert_(res[1].dtype.type is np.float32)
    +
    +        a = np.zeros((0, 0), dtype=np.float64)
    +        res = linalg.det(a)
    +        assert_equal(res, 1.)
    +        assert_(res.dtype.type is np.float64)
    +        res = linalg.slogdet(a)
    +        assert_equal(res, (1, 0))
    +        assert_(res[0].dtype.type is np.float64)
    +        assert_(res[1].dtype.type is np.float64)
    +
    +
    +class LstsqCases(LinalgSquareTestCase, LinalgNonsquareTestCase):
    +
    +    def do(self, a, b, tags):
    +        arr = np.asarray(a)
    +        m, n = arr.shape
    +        u, s, vt = linalg.svd(a, False)
    +        x, residuals, rank, sv = linalg.lstsq(a, b, rcond=-1)
    +        if m == 0:
    +            assert_((x == 0).all())
    +        if m <= n:
    +            assert_almost_equal(b, dot(a, x))
    +            assert_equal(rank, m)
    +        else:
    +            assert_equal(rank, n)
    +        assert_almost_equal(sv, sv.__array_wrap__(s))
    +        if rank == n and m > n:
    +            expect_resids = (
    +                np.asarray(abs(np.dot(a, x) - b)) ** 2).sum(axis=0)
    +            expect_resids = np.asarray(expect_resids)
    +            if np.asarray(b).ndim == 1:
    +                expect_resids.shape = (1,)
    +                assert_equal(residuals.shape, expect_resids.shape)
    +        else:
    +            expect_resids = np.array([]).view(type(x))
    +        assert_almost_equal(residuals, expect_resids)
    +        assert_(np.issubdtype(residuals.dtype, np.floating))
    +        assert_(consistent_subclass(x, b))
    +        assert_(consistent_subclass(residuals, b))
    +
    +
    +class TestLstsq(LstsqCases):
    +    def test_rcond(self):
    +        a = np.array([[0., 1.,  0.,  1.,  2.,  0.],
    +                      [0., 2.,  0.,  0.,  1.,  0.],
    +                      [1., 0.,  1.,  0.,  0.,  4.],
    +                      [0., 0.,  0.,  2.,  3.,  0.]]).T
    +
    +        b = np.array([1, 0, 0, 0, 0, 0])
    +
    +        x, residuals, rank, s = linalg.lstsq(a, b, rcond=-1)
    +        assert_(rank == 4)
    +        x, residuals, rank, s = linalg.lstsq(a, b)
    +        assert_(rank == 3)
    +        x, residuals, rank, s = linalg.lstsq(a, b, rcond=None)
    +        assert_(rank == 3)
    +
    +    @pytest.mark.parametrize(["m", "n", "n_rhs"], [
    +        (4, 2, 2),
    +        (0, 4, 1),
    +        (0, 4, 2),
    +        (4, 0, 1),
    +        (4, 0, 2),
    +        (4, 2, 0),
    +        (0, 0, 0)
    +    ])
    +    def test_empty_a_b(self, m, n, n_rhs):
    +        a = np.arange(m * n).reshape(m, n)
    +        b = np.ones((m, n_rhs))
    +        x, residuals, rank, s = linalg.lstsq(a, b, rcond=None)
    +        if m == 0:
    +            assert_((x == 0).all())
    +        assert_equal(x.shape, (n, n_rhs))
    +        assert_equal(residuals.shape, ((n_rhs,) if m > n else (0,)))
    +        if m > n and n_rhs > 0:
    +            # residuals are exactly the squared norms of b's columns
    +            r = b - np.dot(a, x)
    +            assert_almost_equal(residuals, (r * r).sum(axis=-2))
    +        assert_equal(rank, min(m, n))
    +        assert_equal(s.shape, (min(m, n),))
    +
    +    def test_incompatible_dims(self):
    +        # use modified version of docstring example
    +        x = np.array([0, 1, 2, 3])
    +        y = np.array([-1, 0.2, 0.9, 2.1, 3.3])
    +        A = np.vstack([x, np.ones(len(x))]).T
    +        with assert_raises_regex(LinAlgError, "Incompatible dimensions"):
    +            linalg.lstsq(A, y, rcond=None)
    +
    +
    +@pytest.mark.parametrize('dt', [np.dtype(c) for c in '?bBhHiIqQefdgFDGO'])
    +class TestMatrixPower:
    +
    +    rshft_0 = np.eye(4)
    +    rshft_1 = rshft_0[[3, 0, 1, 2]]
    +    rshft_2 = rshft_0[[2, 3, 0, 1]]
    +    rshft_3 = rshft_0[[1, 2, 3, 0]]
    +    rshft_all = [rshft_0, rshft_1, rshft_2, rshft_3]
    +    noninv = array([[1, 0], [0, 0]])
    +    stacked = np.block([[[rshft_0]]] * 2)
    +    # FIXME the 'e' dtype might work in future
    +    dtnoinv = [object, np.dtype('e'), np.dtype('g'), np.dtype('G')]
    +
    +    def test_large_power(self, dt):
    +        rshft = self.rshft_1.astype(dt)
    +        assert_equal(
    +            matrix_power(rshft, 2**100 + 2**10 + 2**5 + 0), self.rshft_0)
    +        assert_equal(
    +            matrix_power(rshft, 2**100 + 2**10 + 2**5 + 1), self.rshft_1)
    +        assert_equal(
    +            matrix_power(rshft, 2**100 + 2**10 + 2**5 + 2), self.rshft_2)
    +        assert_equal(
    +            matrix_power(rshft, 2**100 + 2**10 + 2**5 + 3), self.rshft_3)
    +
    +    def test_power_is_zero(self, dt):
    +        def tz(M):
    +            mz = matrix_power(M, 0)
    +            assert_equal(mz, identity_like_generalized(M))
    +            assert_equal(mz.dtype, M.dtype)
    +
    +        for mat in self.rshft_all:
    +            tz(mat.astype(dt))
    +            if dt != object:
    +                tz(self.stacked.astype(dt))
    +
    +    def test_power_is_one(self, dt):
    +        def tz(mat):
    +            mz = matrix_power(mat, 1)
    +            assert_equal(mz, mat)
    +            assert_equal(mz.dtype, mat.dtype)
    +
    +        for mat in self.rshft_all:
    +            tz(mat.astype(dt))
    +            if dt != object:
    +                tz(self.stacked.astype(dt))
    +
    +    def test_power_is_two(self, dt):
    +        def tz(mat):
    +            mz = matrix_power(mat, 2)
    +            mmul = matmul if mat.dtype != object else dot
    +            assert_equal(mz, mmul(mat, mat))
    +            assert_equal(mz.dtype, mat.dtype)
    +
    +        for mat in self.rshft_all:
    +            tz(mat.astype(dt))
    +            if dt != object:
    +                tz(self.stacked.astype(dt))
    +
    +    def test_power_is_minus_one(self, dt):
    +        def tz(mat):
    +            invmat = matrix_power(mat, -1)
    +            mmul = matmul if mat.dtype != object else dot
    +            assert_almost_equal(
    +                mmul(invmat, mat), identity_like_generalized(mat))
    +
    +        for mat in self.rshft_all:
    +            if dt not in self.dtnoinv:
    +                tz(mat.astype(dt))
    +
    +    def test_exceptions_bad_power(self, dt):
    +        mat = self.rshft_0.astype(dt)
    +        assert_raises(TypeError, matrix_power, mat, 1.5)
    +        assert_raises(TypeError, matrix_power, mat, [1])
    +
    +    def test_exceptions_non_square(self, dt):
    +        assert_raises(LinAlgError, matrix_power, np.array([1], dt), 1)
    +        assert_raises(LinAlgError, matrix_power, np.array([[1], [2]], dt), 1)
    +        assert_raises(LinAlgError, matrix_power, np.ones((4, 3, 2), dt), 1)
    +
    +    @pytest.mark.skipif(IS_WASM, reason="fp errors don't work in wasm")
    +    def test_exceptions_not_invertible(self, dt):
    +        if dt in self.dtnoinv:
    +            return
    +        mat = self.noninv.astype(dt)
    +        assert_raises(LinAlgError, matrix_power, mat, -1)
    +
    +
    +class TestEigvalshCases(HermitianTestCase, HermitianGeneralizedTestCase):
    +
    +    def do(self, a, b, tags):
    +        # note that eigenvalue arrays returned by eig must be sorted since
    +        # their order isn't guaranteed.
    +        ev = linalg.eigvalsh(a, 'L')
    +        evalues, evectors = linalg.eig(a)
    +        evalues.sort(axis=-1)
    +        assert_allclose(ev, evalues, rtol=get_rtol(ev.dtype))
    +
    +        ev2 = linalg.eigvalsh(a, 'U')
    +        assert_allclose(ev2, evalues, rtol=get_rtol(ev.dtype))
    +
    +
    +class TestEigvalsh:
    +    @pytest.mark.parametrize('dtype', [single, double, csingle, cdouble])
    +    def test_types(self, dtype):
    +        x = np.array([[1, 0.5], [0.5, 1]], dtype=dtype)
    +        w = np.linalg.eigvalsh(x)
    +        assert_equal(w.dtype, get_real_dtype(dtype))
    +
    +    def test_invalid(self):
    +        x = np.array([[1, 0.5], [0.5, 1]], dtype=np.float32)
    +        assert_raises(ValueError, np.linalg.eigvalsh, x, UPLO="lrong")
    +        assert_raises(ValueError, np.linalg.eigvalsh, x, "lower")
    +        assert_raises(ValueError, np.linalg.eigvalsh, x, "upper")
    +
    +    def test_UPLO(self):
    +        Klo = np.array([[0, 0], [1, 0]], dtype=np.double)
    +        Kup = np.array([[0, 1], [0, 0]], dtype=np.double)
    +        tgt = np.array([-1, 1], dtype=np.double)
    +        rtol = get_rtol(np.double)
    +
    +        # Check default is 'L'
    +        w = np.linalg.eigvalsh(Klo)
    +        assert_allclose(w, tgt, rtol=rtol)
    +        # Check 'L'
    +        w = np.linalg.eigvalsh(Klo, UPLO='L')
    +        assert_allclose(w, tgt, rtol=rtol)
    +        # Check 'l'
    +        w = np.linalg.eigvalsh(Klo, UPLO='l')
    +        assert_allclose(w, tgt, rtol=rtol)
    +        # Check 'U'
    +        w = np.linalg.eigvalsh(Kup, UPLO='U')
    +        assert_allclose(w, tgt, rtol=rtol)
    +        # Check 'u'
    +        w = np.linalg.eigvalsh(Kup, UPLO='u')
    +        assert_allclose(w, tgt, rtol=rtol)
    +
    +    def test_0_size(self):
    +        # Check that all kinds of 0-sized arrays work
    +        class ArraySubclass(np.ndarray):
    +            pass
    +        a = np.zeros((0, 1, 1), dtype=np.int_).view(ArraySubclass)
    +        res = linalg.eigvalsh(a)
    +        assert_(res.dtype.type is np.float64)
    +        assert_equal((0, 1), res.shape)
    +        # This is just for documentation, it might make sense to change:
    +        assert_(isinstance(res, np.ndarray))
    +
    +        a = np.zeros((0, 0), dtype=np.complex64).view(ArraySubclass)
    +        res = linalg.eigvalsh(a)
    +        assert_(res.dtype.type is np.float32)
    +        assert_equal((0,), res.shape)
    +        # This is just for documentation, it might make sense to change:
    +        assert_(isinstance(res, np.ndarray))
    +
    +
    +class TestEighCases(HermitianTestCase, HermitianGeneralizedTestCase):
    +
    +    def do(self, a, b, tags):
    +        # note that eigenvalue arrays returned by eig must be sorted since
    +        # their order isn't guaranteed.
    +        res = linalg.eigh(a)
    +        ev, evc = res.eigenvalues, res.eigenvectors
    +        evalues, evectors = linalg.eig(a)
    +        evalues.sort(axis=-1)
    +        assert_almost_equal(ev, evalues)
    +
    +        assert_allclose(matmul(a, evc),
    +                        np.asarray(ev)[..., None, :] * np.asarray(evc),
    +                        rtol=get_rtol(ev.dtype))
    +
    +        ev2, evc2 = linalg.eigh(a, 'U')
    +        assert_almost_equal(ev2, evalues)
    +
    +        assert_allclose(matmul(a, evc2),
    +                        np.asarray(ev2)[..., None, :] * np.asarray(evc2),
    +                        rtol=get_rtol(ev.dtype), err_msg=repr(a))
    +
    +
    +class TestEigh:
    +    @pytest.mark.parametrize('dtype', [single, double, csingle, cdouble])
    +    def test_types(self, dtype):
    +        x = np.array([[1, 0.5], [0.5, 1]], dtype=dtype)
    +        w, v = np.linalg.eigh(x)
    +        assert_equal(w.dtype, get_real_dtype(dtype))
    +        assert_equal(v.dtype, dtype)
    +
    +    def test_invalid(self):
    +        x = np.array([[1, 0.5], [0.5, 1]], dtype=np.float32)
    +        assert_raises(ValueError, np.linalg.eigh, x, UPLO="lrong")
    +        assert_raises(ValueError, np.linalg.eigh, x, "lower")
    +        assert_raises(ValueError, np.linalg.eigh, x, "upper")
    +
    +    def test_UPLO(self):
    +        Klo = np.array([[0, 0], [1, 0]], dtype=np.double)
    +        Kup = np.array([[0, 1], [0, 0]], dtype=np.double)
    +        tgt = np.array([-1, 1], dtype=np.double)
    +        rtol = get_rtol(np.double)
    +
    +        # Check default is 'L'
    +        w, v = np.linalg.eigh(Klo)
    +        assert_allclose(w, tgt, rtol=rtol)
    +        # Check 'L'
    +        w, v = np.linalg.eigh(Klo, UPLO='L')
    +        assert_allclose(w, tgt, rtol=rtol)
    +        # Check 'l'
    +        w, v = np.linalg.eigh(Klo, UPLO='l')
    +        assert_allclose(w, tgt, rtol=rtol)
    +        # Check 'U'
    +        w, v = np.linalg.eigh(Kup, UPLO='U')
    +        assert_allclose(w, tgt, rtol=rtol)
    +        # Check 'u'
    +        w, v = np.linalg.eigh(Kup, UPLO='u')
    +        assert_allclose(w, tgt, rtol=rtol)
    +
    +    def test_0_size(self):
    +        # Check that all kinds of 0-sized arrays work
    +        class ArraySubclass(np.ndarray):
    +            pass
    +        a = np.zeros((0, 1, 1), dtype=np.int_).view(ArraySubclass)
    +        res, res_v = linalg.eigh(a)
    +        assert_(res_v.dtype.type is np.float64)
    +        assert_(res.dtype.type is np.float64)
    +        assert_equal(a.shape, res_v.shape)
    +        assert_equal((0, 1), res.shape)
    +        # This is just for documentation, it might make sense to change:
    +        assert_(isinstance(a, np.ndarray))
    +
    +        a = np.zeros((0, 0), dtype=np.complex64).view(ArraySubclass)
    +        res, res_v = linalg.eigh(a)
    +        assert_(res_v.dtype.type is np.complex64)
    +        assert_(res.dtype.type is np.float32)
    +        assert_equal(a.shape, res_v.shape)
    +        assert_equal((0,), res.shape)
    +        # This is just for documentation, it might make sense to change:
    +        assert_(isinstance(a, np.ndarray))
    +
    +
    +class _TestNormBase:
    +    dt = None
    +    dec = None
    +
    +    @staticmethod
    +    def check_dtype(x, res):
    +        if issubclass(x.dtype.type, np.inexact):
    +            assert_equal(res.dtype, x.real.dtype)
    +        else:
    +            # For integer input, don't have to test float precision of output.
    +            assert_(issubclass(res.dtype.type, np.floating))
    +
    +
    +class _TestNormGeneral(_TestNormBase):
    +
    +    def test_empty(self):
    +        assert_equal(norm([]), 0.0)
    +        assert_equal(norm(array([], dtype=self.dt)), 0.0)
    +        assert_equal(norm(atleast_2d(array([], dtype=self.dt))), 0.0)
    +
    +    def test_vector_return_type(self):
    +        a = np.array([1, 0, 1])
    +
    +        exact_types = np.typecodes['AllInteger']
    +        inexact_types = np.typecodes['AllFloat']
    +
    +        all_types = exact_types + inexact_types
    +
    +        for each_type in all_types:
    +            at = a.astype(each_type)
    +
    +            an = norm(at, -np.inf)
    +            self.check_dtype(at, an)
    +            assert_almost_equal(an, 0.0)
    +
    +            with warnings.catch_warnings():
    +                warnings.filterwarnings(
    +                    'ignore', "divide by zero encountered", RuntimeWarning)
    +                an = norm(at, -1)
    +                self.check_dtype(at, an)
    +                assert_almost_equal(an, 0.0)
    +
    +            an = norm(at, 0)
    +            self.check_dtype(at, an)
    +            assert_almost_equal(an, 2)
    +
    +            an = norm(at, 1)
    +            self.check_dtype(at, an)
    +            assert_almost_equal(an, 2.0)
    +
    +            an = norm(at, 2)
    +            self.check_dtype(at, an)
    +            assert_almost_equal(an, an.dtype.type(2.0)**an.dtype.type(1.0 / 2.0))
    +
    +            an = norm(at, 4)
    +            self.check_dtype(at, an)
    +            assert_almost_equal(an, an.dtype.type(2.0)**an.dtype.type(1.0 / 4.0))
    +
    +            an = norm(at, np.inf)
    +            self.check_dtype(at, an)
    +            assert_almost_equal(an, 1.0)
    +
    +    def test_vector(self):
    +        a = [1, 2, 3, 4]
    +        b = [-1, -2, -3, -4]
    +        c = [-1, 2, -3, 4]
    +
    +        def _test(v):
    +            np.testing.assert_almost_equal(norm(v), 30 ** 0.5,
    +                                           decimal=self.dec)
    +            np.testing.assert_almost_equal(norm(v, inf), 4.0,
    +                                           decimal=self.dec)
    +            np.testing.assert_almost_equal(norm(v, -inf), 1.0,
    +                                           decimal=self.dec)
    +            np.testing.assert_almost_equal(norm(v, 1), 10.0,
    +                                           decimal=self.dec)
    +            np.testing.assert_almost_equal(norm(v, -1), 12.0 / 25,
    +                                           decimal=self.dec)
    +            np.testing.assert_almost_equal(norm(v, 2), 30 ** 0.5,
    +                                           decimal=self.dec)
    +            np.testing.assert_almost_equal(norm(v, -2), ((205. / 144) ** -0.5),
    +                                           decimal=self.dec)
    +            np.testing.assert_almost_equal(norm(v, 0), 4,
    +                                           decimal=self.dec)
    +
    +        for v in (a, b, c,):
    +            _test(v)
    +
    +        for v in (array(a, dtype=self.dt), array(b, dtype=self.dt),
    +                  array(c, dtype=self.dt)):
    +            _test(v)
    +
    +    def test_axis(self):
    +        # Vector norms.
    +        # Compare the use of `axis` with computing the norm of each row
    +        # or column separately.
    +        A = array([[1, 2, 3], [4, 5, 6]], dtype=self.dt)
    +        for order in [None, -1, 0, 1, 2, 3, np.inf, -np.inf]:
    +            expected0 = [norm(A[:, k], ord=order) for k in range(A.shape[1])]
    +            assert_almost_equal(norm(A, ord=order, axis=0), expected0)
    +            expected1 = [norm(A[k, :], ord=order) for k in range(A.shape[0])]
    +            assert_almost_equal(norm(A, ord=order, axis=1), expected1)
    +
    +        # Matrix norms.
    +        B = np.arange(1, 25, dtype=self.dt).reshape(2, 3, 4)
    +        nd = B.ndim
    +        for order in [None, -2, 2, -1, 1, np.inf, -np.inf, 'fro']:
    +            for axis in itertools.combinations(range(-nd, nd), 2):
    +                row_axis, col_axis = axis
    +                if row_axis < 0:
    +                    row_axis += nd
    +                if col_axis < 0:
    +                    col_axis += nd
    +                if row_axis == col_axis:
    +                    assert_raises(ValueError, norm, B, ord=order, axis=axis)
    +                else:
    +                    n = norm(B, ord=order, axis=axis)
    +
    +                    # The logic using k_index only works for nd = 3.
    +                    # This has to be changed if nd is increased.
    +                    k_index = nd - (row_axis + col_axis)
    +                    if row_axis < col_axis:
    +                        expected = [norm(B[:].take(k, axis=k_index), ord=order)
    +                                    for k in range(B.shape[k_index])]
    +                    else:
    +                        expected = [norm(B[:].take(k, axis=k_index).T, ord=order)
    +                                    for k in range(B.shape[k_index])]
    +                    assert_almost_equal(n, expected)
    +
    +    def test_keepdims(self):
    +        A = np.arange(1, 25, dtype=self.dt).reshape(2, 3, 4)
    +
    +        allclose_err = 'order {0}, axis = {1}'
    +        shape_err = 'Shape mismatch found {0}, expected {1}, order={2}, axis={3}'
    +
    +        # check the order=None, axis=None case
    +        expected = norm(A, ord=None, axis=None)
    +        found = norm(A, ord=None, axis=None, keepdims=True)
    +        assert_allclose(np.squeeze(found), expected,
    +                        err_msg=allclose_err.format(None, None))
    +        expected_shape = (1, 1, 1)
    +        assert_(found.shape == expected_shape,
    +                shape_err.format(found.shape, expected_shape, None, None))
    +
    +        # Vector norms.
    +        for order in [None, -1, 0, 1, 2, 3, np.inf, -np.inf]:
    +            for k in range(A.ndim):
    +                expected = norm(A, ord=order, axis=k)
    +                found = norm(A, ord=order, axis=k, keepdims=True)
    +                assert_allclose(np.squeeze(found), expected,
    +                                err_msg=allclose_err.format(order, k))
    +                expected_shape = list(A.shape)
    +                expected_shape[k] = 1
    +                expected_shape = tuple(expected_shape)
    +                assert_(found.shape == expected_shape,
    +                        shape_err.format(found.shape, expected_shape, order, k))
    +
    +        # Matrix norms.
    +        for order in [None, -2, 2, -1, 1, np.inf, -np.inf, 'fro', 'nuc']:
    +            for k in itertools.permutations(range(A.ndim), 2):
    +                expected = norm(A, ord=order, axis=k)
    +                found = norm(A, ord=order, axis=k, keepdims=True)
    +                assert_allclose(np.squeeze(found), expected,
    +                                err_msg=allclose_err.format(order, k))
    +                expected_shape = list(A.shape)
    +                expected_shape[k[0]] = 1
    +                expected_shape[k[1]] = 1
    +                expected_shape = tuple(expected_shape)
    +                assert_(found.shape == expected_shape,
    +                        shape_err.format(found.shape, expected_shape, order, k))
    +
    +
    +class _TestNorm2D(_TestNormBase):
    +    # Define the part for 2d arrays separately, so we can subclass this
    +    # and run the tests using np.matrix in matrixlib.tests.test_matrix_linalg.
    +    array = np.array
    +
    +    def test_matrix_empty(self):
    +        assert_equal(norm(self.array([[]], dtype=self.dt)), 0.0)
    +
    +    def test_matrix_return_type(self):
    +        a = self.array([[1, 0, 1], [0, 1, 1]])
    +
    +        exact_types = np.typecodes['AllInteger']
    +
    +        # float32, complex64, float64, complex128 types are the only types
    +        # allowed by `linalg`, which performs the matrix operations used
    +        # within `norm`.
    +        inexact_types = 'fdFD'
    +
    +        all_types = exact_types + inexact_types
    +
    +        for each_type in all_types:
    +            at = a.astype(each_type)
    +
    +            an = norm(at, -np.inf)
    +            self.check_dtype(at, an)
    +            assert_almost_equal(an, 2.0)
    +
    +            with warnings.catch_warnings():
    +                warnings.filterwarnings(
    +                    'ignore', "divide by zero encountered", RuntimeWarning)
    +                an = norm(at, -1)
    +                self.check_dtype(at, an)
    +                assert_almost_equal(an, 1.0)
    +
    +            an = norm(at, 1)
    +            self.check_dtype(at, an)
    +            assert_almost_equal(an, 2.0)
    +
    +            an = norm(at, 2)
    +            self.check_dtype(at, an)
    +            assert_almost_equal(an, 3.0**(1.0 / 2.0))
    +
    +            an = norm(at, -2)
    +            self.check_dtype(at, an)
    +            assert_almost_equal(an, 1.0)
    +
    +            an = norm(at, np.inf)
    +            self.check_dtype(at, an)
    +            assert_almost_equal(an, 2.0)
    +
    +            an = norm(at, 'fro')
    +            self.check_dtype(at, an)
    +            assert_almost_equal(an, 2.0)
    +
    +            an = norm(at, 'nuc')
    +            self.check_dtype(at, an)
    +            # Lower bar needed to support low precision floats.
    +            # They end up being off by 1 in the 7th place.
    +            np.testing.assert_almost_equal(an, 2.7320508075688772, decimal=6)
    +
    +    def test_matrix_2x2(self):
    +        A = self.array([[1, 3], [5, 7]], dtype=self.dt)
    +        assert_almost_equal(norm(A), 84 ** 0.5)
    +        assert_almost_equal(norm(A, 'fro'), 84 ** 0.5)
    +        assert_almost_equal(norm(A, 'nuc'), 10.0)
    +        assert_almost_equal(norm(A, inf), 12.0)
    +        assert_almost_equal(norm(A, -inf), 4.0)
    +        assert_almost_equal(norm(A, 1), 10.0)
    +        assert_almost_equal(norm(A, -1), 6.0)
    +        assert_almost_equal(norm(A, 2), 9.1231056256176615)
    +        assert_almost_equal(norm(A, -2), 0.87689437438234041)
    +
    +        assert_raises(ValueError, norm, A, 'nofro')
    +        assert_raises(ValueError, norm, A, -3)
    +        assert_raises(ValueError, norm, A, 0)
    +
    +    def test_matrix_3x3(self):
    +        # This test has been added because the 2x2 example
    +        # happened to have equal nuclear norm and induced 1-norm.
    +        # The 1/10 scaling factor accommodates the absolute tolerance
    +        # used in assert_almost_equal.
    +        A = (1 / 10) * \
    +            self.array([[1, 2, 3], [6, 0, 5], [3, 2, 1]], dtype=self.dt)
    +        assert_almost_equal(norm(A), (1 / 10) * 89 ** 0.5)
    +        assert_almost_equal(norm(A, 'fro'), (1 / 10) * 89 ** 0.5)
    +        assert_almost_equal(norm(A, 'nuc'), 1.3366836911774836)
    +        assert_almost_equal(norm(A, inf), 1.1)
    +        assert_almost_equal(norm(A, -inf), 0.6)
    +        assert_almost_equal(norm(A, 1), 1.0)
    +        assert_almost_equal(norm(A, -1), 0.4)
    +        assert_almost_equal(norm(A, 2), 0.88722940323461277)
    +        assert_almost_equal(norm(A, -2), 0.19456584790481812)
    +
    +    def test_bad_args(self):
    +        # Check that bad arguments raise the appropriate exceptions.
    +
    +        A = self.array([[1, 2, 3], [4, 5, 6]], dtype=self.dt)
    +        B = np.arange(1, 25, dtype=self.dt).reshape(2, 3, 4)
    +
    +        # Using `axis=` or passing in a 1-D array implies vector
    +        # norms are being computed, so also using `ord='fro'`
    +        # or `ord='nuc'` or any other string raises a ValueError.
    +        assert_raises(ValueError, norm, A, 'fro', 0)
    +        assert_raises(ValueError, norm, A, 'nuc', 0)
    +        assert_raises(ValueError, norm, [3, 4], 'fro', None)
    +        assert_raises(ValueError, norm, [3, 4], 'nuc', None)
    +        assert_raises(ValueError, norm, [3, 4], 'test', None)
    +
    +        # Similarly, norm should raise an exception when ord is any finite
    +        # number other than 1, 2, -1 or -2 when computing matrix norms.
    +        for order in [0, 3]:
    +            assert_raises(ValueError, norm, A, order, None)
    +            assert_raises(ValueError, norm, A, order, (0, 1))
    +            assert_raises(ValueError, norm, B, order, (1, 2))
    +
    +        # Invalid axis
    +        assert_raises(AxisError, norm, B, None, 3)
    +        assert_raises(AxisError, norm, B, None, (2, 3))
    +        assert_raises(ValueError, norm, B, None, (0, 1, 2))
    +
    +
    +class _TestNorm(_TestNorm2D, _TestNormGeneral):
    +    pass
    +
    +
    +class TestNorm_NonSystematic:
    +
    +    def test_longdouble_norm(self):
    +        # Non-regression test: p-norm of longdouble would previously raise
    +        # UnboundLocalError.
    +        x = np.arange(10, dtype=np.longdouble)
    +        old_assert_almost_equal(norm(x, ord=3), 12.65, decimal=2)
    +
    +    def test_intmin(self):
    +        # Non-regression test: p-norm of signed integer would previously do
    +        # float cast and abs in the wrong order.
    +        x = np.array([-2 ** 31], dtype=np.int32)
    +        old_assert_almost_equal(norm(x, ord=3), 2 ** 31, decimal=5)
    +
    +    def test_complex_high_ord(self):
    +        # gh-4156
    +        d = np.empty((2,), dtype=np.clongdouble)
    +        d[0] = 6 + 7j
    +        d[1] = -6 + 7j
    +        res = 11.615898132184
    +        old_assert_almost_equal(np.linalg.norm(d, ord=3), res, decimal=10)
    +        d = d.astype(np.complex128)
    +        old_assert_almost_equal(np.linalg.norm(d, ord=3), res, decimal=9)
    +        d = d.astype(np.complex64)
    +        old_assert_almost_equal(np.linalg.norm(d, ord=3), res, decimal=5)
    +
    +
    +# Separate definitions so we can use them for matrix tests.
    +class _TestNormDoubleBase(_TestNormBase):
    +    dt = np.double
    +    dec = 12
    +
    +
    +class _TestNormSingleBase(_TestNormBase):
    +    dt = np.float32
    +    dec = 6
    +
    +
    +class _TestNormInt64Base(_TestNormBase):
    +    dt = np.int64
    +    dec = 12
    +
    +
    +class TestNormDouble(_TestNorm, _TestNormDoubleBase):
    +    pass
    +
    +
    +class TestNormSingle(_TestNorm, _TestNormSingleBase):
    +    pass
    +
    +
    +class TestNormInt64(_TestNorm, _TestNormInt64Base):
    +    pass
    +
    +
    +class TestMatrixRank:
    +
    +    def test_matrix_rank(self):
    +        # Full rank matrix
    +        assert_equal(4, matrix_rank(np.eye(4)))
    +        # rank deficient matrix
    +        I = np.eye(4)
    +        I[-1, -1] = 0.
    +        assert_equal(matrix_rank(I), 3)
    +        # All zeros - zero rank
    +        assert_equal(matrix_rank(np.zeros((4, 4))), 0)
    +        # 1 dimension - rank 1 unless all 0
    +        assert_equal(matrix_rank([1, 0, 0, 0]), 1)
    +        assert_equal(matrix_rank(np.zeros((4,))), 0)
    +        # accepts array-like
    +        assert_equal(matrix_rank([1]), 1)
    +        # greater than 2 dimensions treated as stacked matrices
    +        ms = np.array([I, np.eye(4), np.zeros((4, 4))])
    +        assert_equal(matrix_rank(ms), np.array([3, 4, 0]))
    +        # works on scalar
    +        assert_equal(matrix_rank(1), 1)
    +
    +        with assert_raises_regex(
    +            ValueError, "`tol` and `rtol` can\'t be both set."
    +        ):
    +            matrix_rank(I, tol=0.01, rtol=0.01)
    +
    +    def test_symmetric_rank(self):
    +        assert_equal(4, matrix_rank(np.eye(4), hermitian=True))
    +        assert_equal(1, matrix_rank(np.ones((4, 4)), hermitian=True))
    +        assert_equal(0, matrix_rank(np.zeros((4, 4)), hermitian=True))
    +        # rank deficient matrix
    +        I = np.eye(4)
    +        I[-1, -1] = 0.
    +        assert_equal(3, matrix_rank(I, hermitian=True))
    +        # manually supplied tolerance
    +        I[-1, -1] = 1e-8
    +        assert_equal(4, matrix_rank(I, hermitian=True, tol=0.99e-8))
    +        assert_equal(3, matrix_rank(I, hermitian=True, tol=1.01e-8))
    +
    +
    +def test_reduced_rank():
    +    # Test matrices with reduced rank
    +    rng = np.random.RandomState(20120714)
    +    for i in range(100):
    +        # Make a rank deficient matrix
    +        X = rng.normal(size=(40, 10))
    +        X[:, 0] = X[:, 1] + X[:, 2]
    +        # Assert that matrix_rank detected deficiency
    +        assert_equal(matrix_rank(X), 9)
    +        X[:, 3] = X[:, 4] + X[:, 5]
    +        assert_equal(matrix_rank(X), 8)
    +
    +
    +class TestQR:
    +    # Define the array class here, so run this on matrices elsewhere.
    +    array = np.array
    +
    +    def check_qr(self, a):
    +        # This test expects the argument `a` to be an ndarray or
    +        # a subclass of an ndarray of inexact type.
    +        a_type = type(a)
    +        a_dtype = a.dtype
    +        m, n = a.shape
    +        k = min(m, n)
    +
    +        # mode == 'complete'
    +        res = linalg.qr(a, mode='complete')
    +        Q, R = res.Q, res.R
    +        assert_(Q.dtype == a_dtype)
    +        assert_(R.dtype == a_dtype)
    +        assert_(isinstance(Q, a_type))
    +        assert_(isinstance(R, a_type))
    +        assert_(Q.shape == (m, m))
    +        assert_(R.shape == (m, n))
    +        assert_almost_equal(dot(Q, R), a)
    +        assert_almost_equal(dot(Q.T.conj(), Q), np.eye(m))
    +        assert_almost_equal(np.triu(R), R)
    +
    +        # mode == 'reduced'
    +        q1, r1 = linalg.qr(a, mode='reduced')
    +        assert_(q1.dtype == a_dtype)
    +        assert_(r1.dtype == a_dtype)
    +        assert_(isinstance(q1, a_type))
    +        assert_(isinstance(r1, a_type))
    +        assert_(q1.shape == (m, k))
    +        assert_(r1.shape == (k, n))
    +        assert_almost_equal(dot(q1, r1), a)
    +        assert_almost_equal(dot(q1.T.conj(), q1), np.eye(k))
    +        assert_almost_equal(np.triu(r1), r1)
    +
    +        # mode == 'r'
    +        r2 = linalg.qr(a, mode='r')
    +        assert_(r2.dtype == a_dtype)
    +        assert_(isinstance(r2, a_type))
    +        assert_almost_equal(r2, r1)
    +
    +    @pytest.mark.parametrize(["m", "n"], [
    +        (3, 0),
    +        (0, 3),
    +        (0, 0)
    +    ])
    +    def test_qr_empty(self, m, n):
    +        k = min(m, n)
    +        a = np.empty((m, n))
    +
    +        self.check_qr(a)
    +
    +        h, tau = np.linalg.qr(a, mode='raw')
    +        assert_equal(h.dtype, np.double)
    +        assert_equal(tau.dtype, np.double)
    +        assert_equal(h.shape, (n, m))
    +        assert_equal(tau.shape, (k,))
    +
    +    def test_mode_raw(self):
    +        # The factorization is not unique and varies between libraries,
    +        # so it is not possible to check against known values. Functional
    +        # testing is a possibility, but awaits the exposure of more
    +        # of the functions in lapack_lite. Consequently, this test is
    +        # very limited in scope. Note that the results are in FORTRAN
    +        # order, hence the h arrays are transposed.
    +        a = self.array([[1, 2], [3, 4], [5, 6]], dtype=np.double)
    +
    +        # Test double
    +        h, tau = linalg.qr(a, mode='raw')
    +        assert_(h.dtype == np.double)
    +        assert_(tau.dtype == np.double)
    +        assert_(h.shape == (2, 3))
    +        assert_(tau.shape == (2,))
    +
    +        h, tau = linalg.qr(a.T, mode='raw')
    +        assert_(h.dtype == np.double)
    +        assert_(tau.dtype == np.double)
    +        assert_(h.shape == (3, 2))
    +        assert_(tau.shape == (2,))
    +
    +    def test_mode_all_but_economic(self):
    +        a = self.array([[1, 2], [3, 4]])
    +        b = self.array([[1, 2], [3, 4], [5, 6]])
    +        for dt in "fd":
    +            m1 = a.astype(dt)
    +            m2 = b.astype(dt)
    +            self.check_qr(m1)
    +            self.check_qr(m2)
    +            self.check_qr(m2.T)
    +
    +        for dt in "fd":
    +            m1 = 1 + 1j * a.astype(dt)
    +            m2 = 1 + 1j * b.astype(dt)
    +            self.check_qr(m1)
    +            self.check_qr(m2)
    +            self.check_qr(m2.T)
    +
    +    def check_qr_stacked(self, a):
    +        # This test expects the argument `a` to be an ndarray or
    +        # a subclass of an ndarray of inexact type.
    +        a_type = type(a)
    +        a_dtype = a.dtype
    +        m, n = a.shape[-2:]
    +        k = min(m, n)
    +
    +        # mode == 'complete'
    +        q, r = linalg.qr(a, mode='complete')
    +        assert_(q.dtype == a_dtype)
    +        assert_(r.dtype == a_dtype)
    +        assert_(isinstance(q, a_type))
    +        assert_(isinstance(r, a_type))
    +        assert_(q.shape[-2:] == (m, m))
    +        assert_(r.shape[-2:] == (m, n))
    +        assert_almost_equal(matmul(q, r), a)
    +        I_mat = np.identity(q.shape[-1])
    +        stack_I_mat = np.broadcast_to(I_mat,
    +                        q.shape[:-2] + (q.shape[-1],) * 2)
    +        assert_almost_equal(matmul(swapaxes(q, -1, -2).conj(), q), stack_I_mat)
    +        assert_almost_equal(np.triu(r[..., :, :]), r)
    +
    +        # mode == 'reduced'
    +        q1, r1 = linalg.qr(a, mode='reduced')
    +        assert_(q1.dtype == a_dtype)
    +        assert_(r1.dtype == a_dtype)
    +        assert_(isinstance(q1, a_type))
    +        assert_(isinstance(r1, a_type))
    +        assert_(q1.shape[-2:] == (m, k))
    +        assert_(r1.shape[-2:] == (k, n))
    +        assert_almost_equal(matmul(q1, r1), a)
    +        I_mat = np.identity(q1.shape[-1])
    +        stack_I_mat = np.broadcast_to(I_mat,
    +                        q1.shape[:-2] + (q1.shape[-1],) * 2)
    +        assert_almost_equal(matmul(swapaxes(q1, -1, -2).conj(), q1),
    +                            stack_I_mat)
    +        assert_almost_equal(np.triu(r1[..., :, :]), r1)
    +
    +        # mode == 'r'
    +        r2 = linalg.qr(a, mode='r')
    +        assert_(r2.dtype == a_dtype)
    +        assert_(isinstance(r2, a_type))
    +        assert_almost_equal(r2, r1)
    +
    +    @pytest.mark.parametrize("size", [
    +        (3, 4), (4, 3), (4, 4),
    +        (3, 0), (0, 3)])
    +    @pytest.mark.parametrize("outer_size", [
    +        (2, 2), (2,), (2, 3, 4)])
    +    @pytest.mark.parametrize("dt", [
    +        np.single, np.double,
    +        np.csingle, np.cdouble])
    +    def test_stacked_inputs(self, outer_size, size, dt):
    +
    +        rng = np.random.default_rng(123)
    +        A = rng.normal(size=outer_size + size).astype(dt)
    +        B = rng.normal(size=outer_size + size).astype(dt)
    +        self.check_qr_stacked(A)
    +        self.check_qr_stacked(A + 1.j * B)
    +
    +
    +class TestCholesky:
    +
    +    @pytest.mark.parametrize(
    +        'shape', [(1, 1), (2, 2), (3, 3), (50, 50), (3, 10, 10)]
    +    )
    +    @pytest.mark.parametrize(
    +        'dtype', (np.float32, np.float64, np.complex64, np.complex128)
    +    )
    +    @pytest.mark.parametrize(
    +        'upper', [False, True])
    +    def test_basic_property(self, shape, dtype, upper):
    +        np.random.seed(1)
    +        a = np.random.randn(*shape)
    +        if np.issubdtype(dtype, np.complexfloating):
    +            a = a + 1j * np.random.randn(*shape)
    +
    +        t = list(range(len(shape)))
    +        t[-2:] = -1, -2
    +
    +        a = np.matmul(a.transpose(t).conj(), a)
    +        a = np.asarray(a, dtype=dtype)
    +
    +        c = np.linalg.cholesky(a, upper=upper)
    +
    +        # Check A = L L^H or A = U^H U
    +        if upper:
    +            b = np.matmul(c.transpose(t).conj(), c)
    +        else:
    +            b = np.matmul(c, c.transpose(t).conj())
    +
    +        atol = 500 * a.shape[0] * np.finfo(dtype).eps
    +        assert_allclose(b, a, atol=atol, err_msg=f'{shape} {dtype}\n{a}\n{c}')
    +
    +        # Check diag(L or U) is real and positive
    +        d = np.diagonal(c, axis1=-2, axis2=-1)
    +        assert_(np.all(np.isreal(d)))
    +        assert_(np.all(d >= 0))
    +
    +    def test_0_size(self):
    +        class ArraySubclass(np.ndarray):
    +            pass
    +        a = np.zeros((0, 1, 1), dtype=np.int_).view(ArraySubclass)
    +        res = linalg.cholesky(a)
    +        assert_equal(a.shape, res.shape)
    +        assert_(res.dtype.type is np.float64)
    +        # for documentation purpose:
    +        assert_(isinstance(res, np.ndarray))
    +
    +        a = np.zeros((1, 0, 0), dtype=np.complex64).view(ArraySubclass)
    +        res = linalg.cholesky(a)
    +        assert_equal(a.shape, res.shape)
    +        assert_(res.dtype.type is np.complex64)
    +        assert_(isinstance(res, np.ndarray))
    +
    +    def test_upper_lower_arg(self):
    +        # Explicit test of upper argument that also checks the default.
    +        a = np.array([[1 + 0j, 0 - 2j], [0 + 2j, 5 + 0j]])
    +
    +        assert_equal(linalg.cholesky(a), linalg.cholesky(a, upper=False))
    +
    +        assert_equal(
    +            linalg.cholesky(a, upper=True),
    +            linalg.cholesky(a).T.conj()
    +        )
    +
    +
    +class TestOuter:
    +    arr1 = np.arange(3)
    +    arr2 = np.arange(3)
    +    expected = np.array(
    +        [[0, 0, 0],
    +         [0, 1, 2],
    +         [0, 2, 4]]
    +    )
    +
    +    assert_array_equal(np.linalg.outer(arr1, arr2), expected)
    +
    +    with assert_raises_regex(
    +        ValueError, "Input arrays must be one-dimensional"
    +    ):
    +        np.linalg.outer(arr1[:, np.newaxis], arr2)
    +
    +
    +def test_byteorder_check():
    +    # Byte order check should pass for native order
    +    if sys.byteorder == 'little':
    +        native = '<'
    +    else:
    +        native = '>'
    +
    +    for dtt in (np.float32, np.float64):
    +        arr = np.eye(4, dtype=dtt)
    +        n_arr = arr.view(arr.dtype.newbyteorder(native))
    +        sw_arr = arr.view(arr.dtype.newbyteorder("S")).byteswap()
    +        assert_equal(arr.dtype.byteorder, '=')
    +        for routine in (linalg.inv, linalg.det, linalg.pinv):
    +            # Normal call
    +            res = routine(arr)
    +            # Native but not '='
    +            assert_array_equal(res, routine(n_arr))
    +            # Swapped
    +            assert_array_equal(res, routine(sw_arr))
    +
    +
    +@pytest.mark.skipif(IS_WASM, reason="fp errors don't work in wasm")
    +def test_generalized_raise_multiloop():
    +    # It should raise an error even if the error doesn't occur in the
    +    # last iteration of the ufunc inner loop
    +
    +    invertible = np.array([[1, 2], [3, 4]])
    +    non_invertible = np.array([[1, 1], [1, 1]])
    +
    +    x = np.zeros([4, 4, 2, 2])[1::2]
    +    x[...] = invertible
    +    x[0, 0] = non_invertible
    +
    +    assert_raises(np.linalg.LinAlgError, np.linalg.inv, x)
    +
    +
    +@pytest.mark.skipif(
    +    threading.active_count() > 1,
    +    reason="skipping test that uses fork because there are multiple threads")
    +@pytest.mark.skipif(
    +    NOGIL_BUILD,
    +    reason="Cannot safely use fork in tests on the free-threaded build")
    +def test_xerbla_override():
    +    # Check that our xerbla has been successfully linked in. If it is not,
    +    # the default xerbla routine is called, which prints a message to stdout
    +    # and may, or may not, abort the process depending on the LAPACK package.
    +
    +    XERBLA_OK = 255
    +
    +    try:
    +        pid = os.fork()
    +    except (OSError, AttributeError):
    +        # fork failed, or not running on POSIX
    +        pytest.skip("Not POSIX or fork failed.")
    +
    +    if pid == 0:
    +        # child; close i/o file handles
    +        os.close(1)
    +        os.close(0)
    +        # Avoid producing core files.
    +        import resource
    +        resource.setrlimit(resource.RLIMIT_CORE, (0, 0))
    +        # These calls may abort.
    +        try:
    +            np.linalg.lapack_lite.xerbla()
    +        except ValueError:
    +            pass
    +        except Exception:
    +            os._exit(os.EX_CONFIG)
    +
    +        try:
    +            a = np.array([[1.]])
    +            np.linalg.lapack_lite.dorgqr(
    +                1, 1, 1, a,
    +                0,  # <- invalid value
    +                a, a, 0, 0)
    +        except ValueError as e:
    +            if "DORGQR parameter number 5" in str(e):
    +                # success, reuse error code to mark success as
    +                # FORTRAN STOP returns as success.
    +                os._exit(XERBLA_OK)
    +
    +        # Did not abort, but our xerbla was not linked in.
    +        os._exit(os.EX_CONFIG)
    +    else:
    +        # parent
    +        pid, status = os.wait()
    +        if os.WEXITSTATUS(status) != XERBLA_OK:
    +            pytest.skip('Numpy xerbla not linked in.')
    +
    +
    +@pytest.mark.skipif(IS_WASM, reason="Cannot start subprocess")
    +@pytest.mark.slow
    +def test_sdot_bug_8577():
    +    # Regression test that loading certain other libraries does not
    +    # result to wrong results in float32 linear algebra.
    +    #
    +    # There's a bug gh-8577 on OSX that can trigger this, and perhaps
    +    # there are also other situations in which it occurs.
    +    #
    +    # Do the check in a separate process.
    +
    +    bad_libs = ['PyQt5.QtWidgets', 'IPython']
    +
    +    template = textwrap.dedent("""
    +    import sys
    +    {before}
    +    try:
    +        import {bad_lib}
    +    except ImportError:
    +        sys.exit(0)
    +    {after}
    +    x = np.ones(2, dtype=np.float32)
    +    sys.exit(0 if np.allclose(x.dot(x), 2.0) else 1)
    +    """)
    +
    +    for bad_lib in bad_libs:
    +        code = template.format(before="import numpy as np", after="",
    +                               bad_lib=bad_lib)
    +        subprocess.check_call([sys.executable, "-c", code])
    +
    +        # Swapped import order
    +        code = template.format(after="import numpy as np", before="",
    +                               bad_lib=bad_lib)
    +        subprocess.check_call([sys.executable, "-c", code])
    +
    +
    +class TestMultiDot:
    +
    +    def test_basic_function_with_three_arguments(self):
    +        # multi_dot with three arguments uses a fast hand coded algorithm to
    +        # determine the optimal order. Therefore test it separately.
    +        A = np.random.random((6, 2))
    +        B = np.random.random((2, 6))
    +        C = np.random.random((6, 2))
    +
    +        assert_almost_equal(multi_dot([A, B, C]), A.dot(B).dot(C))
    +        assert_almost_equal(multi_dot([A, B, C]), np.dot(A, np.dot(B, C)))
    +
    +    def test_basic_function_with_two_arguments(self):
    +        # separate code path with two arguments
    +        A = np.random.random((6, 2))
    +        B = np.random.random((2, 6))
    +
    +        assert_almost_equal(multi_dot([A, B]), A.dot(B))
    +        assert_almost_equal(multi_dot([A, B]), np.dot(A, B))
    +
    +    def test_basic_function_with_dynamic_programming_optimization(self):
    +        # multi_dot with four or more arguments uses the dynamic programming
    +        # optimization and therefore deserve a separate
    +        A = np.random.random((6, 2))
    +        B = np.random.random((2, 6))
    +        C = np.random.random((6, 2))
    +        D = np.random.random((2, 1))
    +        assert_almost_equal(multi_dot([A, B, C, D]), A.dot(B).dot(C).dot(D))
    +
    +    def test_vector_as_first_argument(self):
    +        # The first argument can be 1-D
    +        A1d = np.random.random(2)  # 1-D
    +        B = np.random.random((2, 6))
    +        C = np.random.random((6, 2))
    +        D = np.random.random((2, 2))
    +
    +        # the result should be 1-D
    +        assert_equal(multi_dot([A1d, B, C, D]).shape, (2,))
    +
    +    def test_vector_as_last_argument(self):
    +        # The last argument can be 1-D
    +        A = np.random.random((6, 2))
    +        B = np.random.random((2, 6))
    +        C = np.random.random((6, 2))
    +        D1d = np.random.random(2)  # 1-D
    +
    +        # the result should be 1-D
    +        assert_equal(multi_dot([A, B, C, D1d]).shape, (6,))
    +
    +    def test_vector_as_first_and_last_argument(self):
    +        # The first and last arguments can be 1-D
    +        A1d = np.random.random(2)  # 1-D
    +        B = np.random.random((2, 6))
    +        C = np.random.random((6, 2))
    +        D1d = np.random.random(2)  # 1-D
    +
    +        # the result should be a scalar
    +        assert_equal(multi_dot([A1d, B, C, D1d]).shape, ())
    +
    +    def test_three_arguments_and_out(self):
    +        # multi_dot with three arguments uses a fast hand coded algorithm to
    +        # determine the optimal order. Therefore test it separately.
    +        A = np.random.random((6, 2))
    +        B = np.random.random((2, 6))
    +        C = np.random.random((6, 2))
    +
    +        out = np.zeros((6, 2))
    +        ret = multi_dot([A, B, C], out=out)
    +        assert out is ret
    +        assert_almost_equal(out, A.dot(B).dot(C))
    +        assert_almost_equal(out, np.dot(A, np.dot(B, C)))
    +
    +    def test_two_arguments_and_out(self):
    +        # separate code path with two arguments
    +        A = np.random.random((6, 2))
    +        B = np.random.random((2, 6))
    +        out = np.zeros((6, 6))
    +        ret = multi_dot([A, B], out=out)
    +        assert out is ret
    +        assert_almost_equal(out, A.dot(B))
    +        assert_almost_equal(out, np.dot(A, B))
    +
    +    def test_dynamic_programming_optimization_and_out(self):
    +        # multi_dot with four or more arguments uses the dynamic programming
    +        # optimization and therefore deserve a separate test
    +        A = np.random.random((6, 2))
    +        B = np.random.random((2, 6))
    +        C = np.random.random((6, 2))
    +        D = np.random.random((2, 1))
    +        out = np.zeros((6, 1))
    +        ret = multi_dot([A, B, C, D], out=out)
    +        assert out is ret
    +        assert_almost_equal(out, A.dot(B).dot(C).dot(D))
    +
    +    def test_dynamic_programming_logic(self):
    +        # Test for the dynamic programming part
    +        # This test is directly taken from Cormen page 376.
    +        arrays = [np.random.random((30, 35)),
    +                  np.random.random((35, 15)),
    +                  np.random.random((15, 5)),
    +                  np.random.random((5, 10)),
    +                  np.random.random((10, 20)),
    +                  np.random.random((20, 25))]
    +        m_expected = np.array([[0., 15750., 7875., 9375., 11875., 15125.],
    +                               [0.,     0., 2625., 4375.,  7125., 10500.],
    +                               [0.,     0.,    0.,  750.,  2500.,  5375.],
    +                               [0.,     0.,    0.,    0.,  1000.,  3500.],
    +                               [0.,     0.,    0.,    0.,     0.,  5000.],
    +                               [0.,     0.,    0.,    0.,     0.,     0.]])
    +        s_expected = np.array([[0,  1,  1,  3,  3,  3],
    +                               [0,  0,  2,  3,  3,  3],
    +                               [0,  0,  0,  3,  3,  3],
    +                               [0,  0,  0,  0,  4,  5],
    +                               [0,  0,  0,  0,  0,  5],
    +                               [0,  0,  0,  0,  0,  0]], dtype=int)
    +        s_expected -= 1  # Cormen uses 1-based index, python does not.
    +
    +        s, m = _multi_dot_matrix_chain_order(arrays, return_costs=True)
    +
    +        # Only the upper triangular part (without the diagonal) is interesting.
    +        assert_almost_equal(np.triu(s[:-1, 1:]),
    +                            np.triu(s_expected[:-1, 1:]))
    +        assert_almost_equal(np.triu(m), np.triu(m_expected))
    +
    +    def test_too_few_input_arrays(self):
    +        assert_raises(ValueError, multi_dot, [])
    +        assert_raises(ValueError, multi_dot, [np.random.random((3, 3))])
    +
    +
    +class TestTensorinv:
    +
    +    @pytest.mark.parametrize("arr, ind", [
    +        (np.ones((4, 6, 8, 2)), 2),
    +        (np.ones((3, 3, 2)), 1),
    +        ])
    +    def test_non_square_handling(self, arr, ind):
    +        with assert_raises(LinAlgError):
    +            linalg.tensorinv(arr, ind=ind)
    +
    +    @pytest.mark.parametrize("shape, ind", [
    +        # examples from docstring
    +        ((4, 6, 8, 3), 2),
    +        ((24, 8, 3), 1),
    +        ])
    +    def test_tensorinv_shape(self, shape, ind):
    +        a = np.eye(24).reshape(shape)
    +        ainv = linalg.tensorinv(a=a, ind=ind)
    +        expected = a.shape[ind:] + a.shape[:ind]
    +        actual = ainv.shape
    +        assert_equal(actual, expected)
    +
    +    @pytest.mark.parametrize("ind", [
    +        0, -2,
    +        ])
    +    def test_tensorinv_ind_limit(self, ind):
    +        a = np.eye(24).reshape((4, 6, 8, 3))
    +        with assert_raises(ValueError):
    +            linalg.tensorinv(a=a, ind=ind)
    +
    +    def test_tensorinv_result(self):
    +        # mimic a docstring example
    +        a = np.eye(24).reshape((24, 8, 3))
    +        ainv = linalg.tensorinv(a, ind=1)
    +        b = np.ones(24)
    +        assert_allclose(np.tensordot(ainv, b, 1), np.linalg.tensorsolve(a, b))
    +
    +
    +class TestTensorsolve:
    +
    +    @pytest.mark.parametrize("a, axes", [
    +        (np.ones((4, 6, 8, 2)), None),
    +        (np.ones((3, 3, 2)), (0, 2)),
    +        ])
    +    def test_non_square_handling(self, a, axes):
    +        with assert_raises(LinAlgError):
    +            b = np.ones(a.shape[:2])
    +            linalg.tensorsolve(a, b, axes=axes)
    +
    +    @pytest.mark.parametrize("shape",
    +        [(2, 3, 6), (3, 4, 4, 3), (0, 3, 3, 0)],
    +    )
    +    def test_tensorsolve_result(self, shape):
    +        a = np.random.randn(*shape)
    +        b = np.ones(a.shape[:2])
    +        x = np.linalg.tensorsolve(a, b)
    +        assert_allclose(np.tensordot(a, x, axes=len(x.shape)), b)
    +
    +
    +def test_unsupported_commontype():
    +    # linalg gracefully handles unsupported type
    +    arr = np.array([[1, -2], [2, 5]], dtype='float16')
    +    with assert_raises_regex(TypeError, "unsupported in linalg"):
    +        linalg.cholesky(arr)
    +
    +
    +#@pytest.mark.slow
    +#@pytest.mark.xfail(not HAS_LAPACK64, run=False,
    +#                   reason="Numpy not compiled with 64-bit BLAS/LAPACK")
    +#@requires_memory(free_bytes=16e9)
    +@pytest.mark.skip(reason="Bad memory reports lead to OOM in ci testing")
    +def test_blas64_dot():
    +    n = 2**32
    +    a = np.zeros([1, n], dtype=np.float32)
    +    b = np.ones([1, 1], dtype=np.float32)
    +    a[0, -1] = 1
    +    c = np.dot(b, a)
    +    assert_equal(c[0, -1], 1)
    +
    +
    +@pytest.mark.xfail(not HAS_LAPACK64,
    +                   reason="Numpy not compiled with 64-bit BLAS/LAPACK")
    +def test_blas64_geqrf_lwork_smoketest():
    +    # Smoke test LAPACK geqrf lwork call with 64-bit integers
    +    dtype = np.float64
    +    lapack_routine = np.linalg.lapack_lite.dgeqrf
    +
    +    m = 2**32 + 1
    +    n = 2**32 + 1
    +    lda = m
    +
    +    # Dummy arrays, not referenced by the lapack routine, so don't
    +    # need to be of the right size
    +    a = np.zeros([1, 1], dtype=dtype)
    +    work = np.zeros([1], dtype=dtype)
    +    tau = np.zeros([1], dtype=dtype)
    +
    +    # Size query
    +    results = lapack_routine(m, n, a, lda, tau, work, -1, 0)
    +    assert_equal(results['info'], 0)
    +    assert_equal(results['m'], m)
    +    assert_equal(results['n'], m)
    +
    +    # Should result to an integer of a reasonable size
    +    lwork = int(work.item())
    +    assert_(2**32 < lwork < 2**42)
    +
    +
    +def test_diagonal():
    +    # Here we only test if selected axes are compatible
    +    # with Array API (last two). Core implementation
    +    # of `diagonal` is tested in `test_multiarray.py`.
    +    x = np.arange(60).reshape((3, 4, 5))
    +    actual = np.linalg.diagonal(x)
    +    expected = np.array(
    +        [
    +            [0,  6, 12, 18],
    +            [20, 26, 32, 38],
    +            [40, 46, 52, 58],
    +        ]
    +    )
    +    assert_equal(actual, expected)
    +
    +
    +def test_trace():
    +    # Here we only test if selected axes are compatible
    +    # with Array API (last two). Core implementation
    +    # of `trace` is tested in `test_multiarray.py`.
    +    x = np.arange(60).reshape((3, 4, 5))
    +    actual = np.linalg.trace(x)
    +    expected = np.array([36, 116, 196])
    +
    +    assert_equal(actual, expected)
    +
    +
    +def test_cross():
    +    x = np.arange(9).reshape((3, 3))
    +    actual = np.linalg.cross(x, x + 1)
    +    expected = np.array([
    +        [-1, 2, -1],
    +        [-1, 2, -1],
    +        [-1, 2, -1],
    +    ])
    +
    +    assert_equal(actual, expected)
    +
    +    # We test that lists are converted to arrays.
    +    u = [1, 2, 3]
    +    v = [4, 5, 6]
    +    actual = np.linalg.cross(u, v)
    +    expected = array([-3,  6, -3])
    +
    +    assert_equal(actual, expected)
    +
    +    with assert_raises_regex(
    +        ValueError,
    +        r"input arrays must be \(arrays of\) 3-dimensional vectors"
    +    ):
    +        x_2dim = x[:, 1:]
    +        np.linalg.cross(x_2dim, x_2dim)
    +
    +
    +def test_tensordot():
    +    # np.linalg.tensordot is just an alias for np.tensordot
    +    x = np.arange(6).reshape((2, 3))
    +
    +    assert np.linalg.tensordot(x, x) == 55
    +    assert np.linalg.tensordot(x, x, axes=[(0, 1), (0, 1)]) == 55
    +
    +
    +def test_matmul():
    +    # np.linalg.matmul and np.matmul only differs in the number
    +    # of arguments in the signature
    +    x = np.arange(6).reshape((2, 3))
    +    actual = np.linalg.matmul(x, x.T)
    +    expected = np.array([[5, 14], [14, 50]])
    +
    +    assert_equal(actual, expected)
    +
    +
    +def test_matrix_transpose():
    +    x = np.arange(6).reshape((2, 3))
    +    actual = np.linalg.matrix_transpose(x)
    +    expected = x.T
    +
    +    assert_equal(actual, expected)
    +
    +    with assert_raises_regex(
    +        ValueError, "array must be at least 2-dimensional"
    +    ):
    +        np.linalg.matrix_transpose(x[:, 0])
    +
    +
    +def test_matrix_norm():
    +    x = np.arange(9).reshape((3, 3))
    +    actual = np.linalg.matrix_norm(x)
    +
    +    assert_almost_equal(actual, np.float64(14.2828), double_decimal=3)
    +
    +    actual = np.linalg.matrix_norm(x, keepdims=True)
    +
    +    assert_almost_equal(actual, np.array([[14.2828]]), double_decimal=3)
    +
    +
    +def test_matrix_norm_empty():
    +    for shape in [(0, 2), (2, 0), (0, 0)]:
    +        for dtype in [np.float64, np.float32, np.int32]:
    +            x = np.zeros(shape, dtype)
    +            assert_equal(np.linalg.matrix_norm(x, ord="fro"), 0)
    +            assert_equal(np.linalg.matrix_norm(x, ord="nuc"), 0)
    +            assert_equal(np.linalg.matrix_norm(x, ord=1), 0)
    +            assert_equal(np.linalg.matrix_norm(x, ord=2), 0)
    +            assert_equal(np.linalg.matrix_norm(x, ord=np.inf), 0)
    +
    +def test_vector_norm():
    +    x = np.arange(9).reshape((3, 3))
    +    actual = np.linalg.vector_norm(x)
    +
    +    assert_almost_equal(actual, np.float64(14.2828), double_decimal=3)
    +
    +    actual = np.linalg.vector_norm(x, axis=0)
    +
    +    assert_almost_equal(
    +        actual, np.array([6.7082, 8.124, 9.6436]), double_decimal=3
    +    )
    +
    +    actual = np.linalg.vector_norm(x, keepdims=True)
    +    expected = np.full((1, 1), 14.2828, dtype='float64')
    +    assert_equal(actual.shape, expected.shape)
    +    assert_almost_equal(actual, expected, double_decimal=3)
    +
    +
    +def test_vector_norm_empty():
    +    for dtype in [np.float64, np.float32, np.int32]:
    +        x = np.zeros(0, dtype)
    +        assert_equal(np.linalg.vector_norm(x, ord=1), 0)
    +        assert_equal(np.linalg.vector_norm(x, ord=2), 0)
    +        assert_equal(np.linalg.vector_norm(x, ord=np.inf), 0)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/linalg/tests/test_regression.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/linalg/tests/test_regression.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..12d10bc7eeccf625aa90b0f25a7425fb14aa6b10
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/linalg/tests/test_regression.py
    @@ -0,0 +1,190 @@
    +""" Test functions for linalg module
    +"""
    +
    +import pytest
    +
    +import numpy as np
    +from numpy import arange, array, dot, float64, linalg, transpose
    +from numpy.testing import (
    +    assert_,
    +    assert_almost_equal,
    +    assert_array_almost_equal,
    +    assert_array_equal,
    +    assert_array_less,
    +    assert_equal,
    +    assert_raises,
    +)
    +
    +
    +class TestRegression:
    +
    +    def test_eig_build(self):
    +        # Ticket #652
    +        rva = array([1.03221168e+02 + 0.j,
    +                     -1.91843603e+01 + 0.j,
    +                     -6.04004526e-01 + 15.84422474j,
    +                     -6.04004526e-01 - 15.84422474j,
    +                     -1.13692929e+01 + 0.j,
    +                     -6.57612485e-01 + 10.41755503j,
    +                     -6.57612485e-01 - 10.41755503j,
    +                     1.82126812e+01 + 0.j,
    +                     1.06011014e+01 + 0.j,
    +                     7.80732773e+00 + 0.j,
    +                     -7.65390898e-01 + 0.j,
    +                     1.51971555e-15 + 0.j,
    +                     -1.51308713e-15 + 0.j])
    +        a = arange(13 * 13, dtype=float64)
    +        a = a.reshape((13, 13))
    +        a = a % 17
    +        va, ve = linalg.eig(a)
    +        va.sort()
    +        rva.sort()
    +        assert_array_almost_equal(va, rva)
    +
    +    def test_eigh_build(self):
    +        # Ticket 662.
    +        rvals = [68.60568999, 89.57756725, 106.67185574]
    +
    +        cov = array([[77.70273908,  3.51489954, 15.64602427],
    +                     [ 3.51489954, 88.97013878, -1.07431931],
    +                     [15.64602427, -1.07431931, 98.18223512]])
    +
    +        vals, vecs = linalg.eigh(cov)
    +        assert_array_almost_equal(vals, rvals)
    +
    +    def test_svd_build(self):
    +        # Ticket 627.
    +        a = array([[0., 1.], [1., 1.], [2., 1.], [3., 1.]])
    +        m, n = a.shape
    +        u, s, vh = linalg.svd(a)
    +
    +        b = dot(transpose(u[:, n:]), a)
    +
    +        assert_array_almost_equal(b, np.zeros((2, 2)))
    +
    +    def test_norm_vector_badarg(self):
    +        # Regression for #786: Frobenius norm for vectors raises
    +        # ValueError.
    +        assert_raises(ValueError, linalg.norm, array([1., 2., 3.]), 'fro')
    +
    +    def test_lapack_endian(self):
    +        # For bug #1482
    +        a = array([[ 5.7998084, -2.1825367],
    +                   [-2.1825367,  9.85910595]], dtype='>f8')
    +        b = array(a, dtype=' 0.5)
    +                assert_equal(c, 1)
    +                assert_equal(np.linalg.matrix_rank(a), 1)
    +                assert_array_less(1, np.linalg.norm(a, ord=2))
    +
    +                w_svdvals = linalg.svdvals(a)
    +                assert_array_almost_equal(w, w_svdvals)
    +
    +    def test_norm_object_array(self):
    +        # gh-7575
    +        testvector = np.array([np.array([0, 1]), 0, 0], dtype=object)
    +
    +        norm = linalg.norm(testvector)
    +        assert_array_equal(norm, [0, 1])
    +        assert_(norm.dtype == np.dtype('float64'))
    +
    +        norm = linalg.norm(testvector, ord=1)
    +        assert_array_equal(norm, [0, 1])
    +        assert_(norm.dtype != np.dtype('float64'))
    +
    +        norm = linalg.norm(testvector, ord=2)
    +        assert_array_equal(norm, [0, 1])
    +        assert_(norm.dtype == np.dtype('float64'))
    +
    +        assert_raises(ValueError, linalg.norm, testvector, ord='fro')
    +        assert_raises(ValueError, linalg.norm, testvector, ord='nuc')
    +        assert_raises(ValueError, linalg.norm, testvector, ord=np.inf)
    +        assert_raises(ValueError, linalg.norm, testvector, ord=-np.inf)
    +        assert_raises(ValueError, linalg.norm, testvector, ord=0)
    +        assert_raises(ValueError, linalg.norm, testvector, ord=-1)
    +        assert_raises(ValueError, linalg.norm, testvector, ord=-2)
    +
    +        testmatrix = np.array([[np.array([0, 1]), 0, 0],
    +                               [0,                0, 0]], dtype=object)
    +
    +        norm = linalg.norm(testmatrix)
    +        assert_array_equal(norm, [0, 1])
    +        assert_(norm.dtype == np.dtype('float64'))
    +
    +        norm = linalg.norm(testmatrix, ord='fro')
    +        assert_array_equal(norm, [0, 1])
    +        assert_(norm.dtype == np.dtype('float64'))
    +
    +        assert_raises(TypeError, linalg.norm, testmatrix, ord='nuc')
    +        assert_raises(ValueError, linalg.norm, testmatrix, ord=np.inf)
    +        assert_raises(ValueError, linalg.norm, testmatrix, ord=-np.inf)
    +        assert_raises(ValueError, linalg.norm, testmatrix, ord=0)
    +        assert_raises(ValueError, linalg.norm, testmatrix, ord=1)
    +        assert_raises(ValueError, linalg.norm, testmatrix, ord=-1)
    +        assert_raises(TypeError, linalg.norm, testmatrix, ord=2)
    +        assert_raises(TypeError, linalg.norm, testmatrix, ord=-2)
    +        assert_raises(ValueError, linalg.norm, testmatrix, ord=3)
    +
    +    def test_lstsq_complex_larger_rhs(self):
    +        # gh-9891
    +        size = 20
    +        n_rhs = 70
    +        G = np.random.randn(size, size) + 1j * np.random.randn(size, size)
    +        u = np.random.randn(size, n_rhs) + 1j * np.random.randn(size, n_rhs)
    +        b = G.dot(u)
    +        # This should work without segmentation fault.
    +        u_lstsq, res, rank, sv = linalg.lstsq(G, b, rcond=None)
    +        # check results just in case
    +        assert_array_almost_equal(u_lstsq, u)
    +
    +    @pytest.mark.parametrize("upper", [True, False])
    +    def test_cholesky_empty_array(self, upper):
    +        # gh-25840 - upper=True hung before.
    +        res = np.linalg.cholesky(np.zeros((0, 0)), upper=upper)
    +        assert res.size == 0
    +
    +    @pytest.mark.parametrize("rtol", [0.0, [0.0] * 4, np.zeros((4,))])
    +    def test_matrix_rank_rtol_argument(self, rtol):
    +        # gh-25877
    +        x = np.zeros((4, 3, 2))
    +        res = np.linalg.matrix_rank(x, rtol=rtol)
    +        assert res.shape == (4,)
    +
    +    @pytest.mark.thread_unsafe(reason="test is already testing threads with openblas")
    +    def test_openblas_threading(self):
    +        # gh-27036
    +        # Test whether matrix multiplication involving a large matrix always
    +        # gives the same (correct) answer
    +        x = np.arange(500000, dtype=np.float64)
    +        src = np.vstack((x, -10 * x)).T
    +        matrix = np.array([[0, 1], [1, 0]])
    +        expected = np.vstack((-10 * x, x)).T  # src @ matrix
    +        for i in range(200):
    +            result = src @ matrix
    +            mismatches = (~np.isclose(result, expected)).sum()
    +            if mismatches != 0:
    +                assert False, ("unexpected result from matmul, "
    +                    "probably due to OpenBLAS threading issues")
    +
    +    def test_norm_linux_arm(self):
    +        # gh-30816
    +        a = np.arange(20000) / 50000
    +        b = a + 1j * np.roll(np.flip(a), 12345)
    +        norm = np.linalg.norm(b)
    +        assert_almost_equal(norm, 46.18628948075393)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/ma/LICENSE b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/ma/LICENSE
    new file mode 100644
    index 0000000000000000000000000000000000000000..b41aae0c89a0f2486843d395f972db759c73c4b8
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/ma/LICENSE
    @@ -0,0 +1,24 @@
    +* Copyright (c) 2006, University of Georgia and Pierre G.F. Gerard-Marchant
    +* All rights reserved.
    +* Redistribution and use in source and binary forms, with or without
    +* modification, are permitted provided that the following conditions are met:
    +*
    +*     * Redistributions of source code must retain the above copyright
    +*       notice, this list of conditions and the following disclaimer.
    +*     * Redistributions in binary form must reproduce the above copyright
    +*       notice, this list of conditions and the following disclaimer in the
    +*       documentation and/or other materials provided with the distribution.
    +*     * Neither the name of the University of Georgia nor the
    +*       names of its contributors may be used to endorse or promote products
    +*       derived from this software without specific prior written permission.
    +*
    +* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
    +* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    +* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
    +* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    +* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
    +* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
    +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
    +* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    \ No newline at end of file
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/ma/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/ma/__init__.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..e2a742e9b64ac83d17d934f929a0749ccfbb5c08
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/ma/__init__.py
    @@ -0,0 +1,53 @@
    +"""
    +=============
    +Masked Arrays
    +=============
    +
    +Arrays sometimes contain invalid or missing data.  When doing operations
    +on such arrays, we wish to suppress invalid values, which is the purpose masked
    +arrays fulfill (an example of typical use is given below).
    +
    +For example, examine the following array:
    +
    +>>> x = np.array([2, 1, 3, np.nan, 5, 2, 3, np.nan])
    +
    +When we try to calculate the mean of the data, the result is undetermined:
    +
    +>>> np.mean(x)
    +nan
    +
    +The mean is calculated using roughly ``np.sum(x)/len(x)``, but since
    +any number added to ``NaN`` [1]_ produces ``NaN``, this doesn't work.  Enter
    +masked arrays:
    +
    +>>> m = np.ma.masked_array(x, np.isnan(x))
    +>>> m
    +masked_array(data=[2.0, 1.0, 3.0, --, 5.0, 2.0, 3.0, --],
    +             mask=[False, False, False, True, False, False, False, True],
    +      fill_value=1e+20)
    +
    +Here, we construct a masked array that suppress all ``NaN`` values.  We
    +may now proceed to calculate the mean of the other values:
    +
    +>>> np.mean(m)
    +2.6666666666666665
    +
    +.. [1] Not-a-Number, a floating point value that is the result of an
    +       invalid operation.
    +
    +.. moduleauthor:: Pierre Gerard-Marchant
    +.. moduleauthor:: Jarrod Millman
    +
    +"""
    +from . import core, extras
    +from .core import *
    +from .extras import *
    +
    +__all__ = ['core', 'extras']
    +__all__ += core.__all__
    +__all__ += extras.__all__
    +
    +from numpy._pytesttester import PytestTester
    +
    +test = PytestTester(__name__)
    +del PytestTester
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/ma/mrecords.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/ma/mrecords.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..737a34ebdb70c6610cae6bf7cb3c498bad1aeb62
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/ma/mrecords.pyi
    @@ -0,0 +1,96 @@
    +from typing import Any, Generic
    +from typing_extensions import TypeVar
    +
    +import numpy as np
    +from numpy._typing import _AnyShape
    +
    +from .core import MaskedArray
    +
    +__all__ = [
    +    "MaskedRecords",
    +    "mrecarray",
    +    "fromarrays",
    +    "fromrecords",
    +    "fromtextfile",
    +    "addfield",
    +]
    +
    +_ShapeT_co = TypeVar("_ShapeT_co", bound=tuple[int, ...], default=_AnyShape, covariant=True)
    +_DTypeT_co = TypeVar("_DTypeT_co", bound=np.dtype, default=np.dtype, covariant=True)
    +
    +class MaskedRecords(MaskedArray[_ShapeT_co, _DTypeT_co], Generic[_ShapeT_co, _DTypeT_co]):
    +    def __new__(
    +        cls,
    +        shape,
    +        dtype=...,
    +        buf=...,
    +        offset=...,
    +        strides=...,
    +        formats=...,
    +        names=...,
    +        titles=...,
    +        byteorder=...,
    +        aligned=...,
    +        mask=...,
    +        hard_mask=...,
    +        fill_value=...,
    +        keep_mask=...,
    +        copy=...,
    +        **options,
    +    ): ...
    +    _mask: Any
    +    _fill_value: Any
    +    @property
    +    def _data(self): ...
    +    @property
    +    def _fieldmask(self): ...
    +    def __array_finalize__(self, obj): ...
    +    def __len__(self): ...
    +    def __getattribute__(self, attr): ...
    +    def __setattr__(self, attr, val): ...
    +    def __getitem__(self, indx): ...
    +    def __setitem__(self, indx, value): ...
    +    def view(self, dtype=None, type=None): ...
    +    def harden_mask(self): ...
    +    def soften_mask(self): ...
    +    def copy(self): ...
    +    def tolist(self, fill_value=None): ...
    +    def __reduce__(self): ...
    +
    +mrecarray = MaskedRecords
    +
    +def fromarrays(
    +    arraylist,
    +    dtype=None,
    +    shape=None,
    +    formats=None,
    +    names=None,
    +    titles=None,
    +    aligned=False,
    +    byteorder=None,
    +    fill_value=None,
    +): ...
    +
    +def fromrecords(
    +    reclist,
    +    dtype=None,
    +    shape=None,
    +    formats=None,
    +    names=None,
    +    titles=None,
    +    aligned=False,
    +    byteorder=None,
    +    fill_value=None,
    +    mask=...,
    +): ...
    +
    +def fromtextfile(
    +    fname,
    +    delimiter=None,
    +    commentchar="#",
    +    missingchar="",
    +    varnames=None,
    +    vartypes=None,
    +): ...
    +
    +def addfield(mrecord, newfield, newfieldname=None): ...
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/_examples/cffi/extending.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/_examples/cffi/extending.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..ad4c9acbdcebfd20455de53dc265eda785ef505e
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/_examples/cffi/extending.py
    @@ -0,0 +1,44 @@
    +"""
    +Use cffi to access any of the underlying C functions from distributions.h
    +"""
    +import os
    +
    +import cffi
    +
    +import numpy as np
    +
    +from .parse import parse_distributions_h
    +
    +ffi = cffi.FFI()
    +
    +inc_dir = os.path.join(np.get_include(), 'numpy')
    +
    +# Basic numpy types
    +ffi.cdef('''
    +    typedef intptr_t npy_intp;
    +    typedef unsigned char npy_bool;
    +
    +''')
    +
    +parse_distributions_h(ffi, inc_dir)
    +
    +lib = ffi.dlopen(np.random._generator.__file__)
    +
    +# Compare the distributions.h random_standard_normal_fill to
    +# Generator.standard_random
    +bit_gen = np.random.PCG64()
    +rng = np.random.Generator(bit_gen)
    +state = bit_gen.state
    +
    +interface = rng.bit_generator.cffi
    +n = 100
    +vals_cffi = ffi.new('double[%d]' % n)
    +lib.random_standard_normal_fill(interface.bit_generator, n, vals_cffi)
    +
    +# reset the state
    +bit_gen.state = state
    +
    +vals = rng.standard_normal(n)
    +
    +for i in range(n):
    +    assert vals[i] == vals_cffi[i]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/_examples/cffi/parse.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/_examples/cffi/parse.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..0f80adb352500ff08c0d650334600bafcd1afaaa
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/_examples/cffi/parse.py
    @@ -0,0 +1,53 @@
    +import os
    +
    +
    +def parse_distributions_h(ffi, inc_dir):
    +    """
    +    Parse distributions.h located in inc_dir for CFFI, filling in the ffi.cdef
    +
    +    Read the function declarations without the "#define ..." macros that will
    +    be filled in when loading the library.
    +    """
    +
    +    with open(os.path.join(inc_dir, 'random', 'bitgen.h')) as fid:
    +        s = []
    +        for line in fid:
    +            # massage the include file
    +            if line.strip().startswith('#'):
    +                continue
    +            s.append(line)
    +        ffi.cdef('\n'.join(s))
    +
    +    with open(os.path.join(inc_dir, 'random', 'distributions.h')) as fid:
    +        s = []
    +        in_skip = 0
    +        ignoring = False
    +        for line in fid:
    +            # check for and remove extern "C" guards
    +            if ignoring:
    +                if line.strip().startswith('#endif'):
    +                    ignoring = False
    +                continue
    +            if line.strip().startswith('#ifdef __cplusplus'):
    +                ignoring = True
    +
    +            # massage the include file
    +            if line.strip().startswith('#'):
    +                continue
    +
    +            # skip any inlined function definition
    +            # which starts with 'static inline xxx(...) {'
    +            # and ends with a closing '}'
    +            if line.strip().startswith('static inline'):
    +                in_skip += line.count('{')
    +                continue
    +            elif in_skip > 0:
    +                in_skip += line.count('{')
    +                in_skip -= line.count('}')
    +                continue
    +
    +            # replace defines with their value or remove them
    +            line = line.replace('DECLDIR', '')
    +            line = line.replace('RAND_INT_TYPE', 'int64_t')
    +            s.append(line)
    +        ffi.cdef('\n'.join(s))
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/_examples/cython/extending.pyx b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/_examples/cython/extending.pyx
    new file mode 100644
    index 0000000000000000000000000000000000000000..6a0f45e1be9e6f32ac9ac39952cd01597b93a2e9
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/_examples/cython/extending.pyx
    @@ -0,0 +1,77 @@
    +#cython: language_level=3
    +
    +from libc.stdint cimport uint32_t
    +from cpython.pycapsule cimport PyCapsule_IsValid, PyCapsule_GetPointer
    +
    +import numpy as np
    +cimport numpy as np
    +cimport cython
    +
    +from numpy.random cimport bitgen_t
    +from numpy.random import PCG64
    +
    +np.import_array()
    +
    +
    +@cython.boundscheck(False)
    +@cython.wraparound(False)
    +def uniform_mean(Py_ssize_t n):
    +    cdef Py_ssize_t i
    +    cdef bitgen_t *rng
    +    cdef const char *capsule_name = "BitGenerator"
    +    cdef double[::1] random_values
    +    cdef np.ndarray randoms
    +
    +    x = PCG64()
    +    capsule = x.capsule
    +    if not PyCapsule_IsValid(capsule, capsule_name):
    +        raise ValueError("Invalid pointer to anon_func_state")
    +    rng =  PyCapsule_GetPointer(capsule, capsule_name)
    +    random_values = np.empty(n)
    +    # Best practice is to acquire the lock whenever generating random values.
    +    # This prevents other threads from modifying the state. Acquiring the lock
    +    # is only necessary if the GIL is also released, as in this example.
    +    with x.lock, nogil:
    +        for i in range(n):
    +            random_values[i] = rng.next_double(rng.state)
    +    randoms = np.asarray(random_values)
    +    return randoms.mean()
    +
    +
    +# This function is declared nogil so it can be used without the GIL below
    +cdef uint32_t bounded_uint(uint32_t lb, uint32_t ub, bitgen_t *rng) nogil:
    +    cdef uint32_t mask, delta, val
    +    mask = delta = ub - lb
    +    mask |= mask >> 1
    +    mask |= mask >> 2
    +    mask |= mask >> 4
    +    mask |= mask >> 8
    +    mask |= mask >> 16
    +
    +    val = rng.next_uint32(rng.state) & mask
    +    while val > delta:
    +        val = rng.next_uint32(rng.state) & mask
    +
    +    return lb + val
    +
    +
    +@cython.boundscheck(False)
    +@cython.wraparound(False)
    +def bounded_uints(uint32_t lb, uint32_t ub, Py_ssize_t n):
    +    cdef Py_ssize_t i
    +    cdef bitgen_t *rng
    +    cdef uint32_t[::1] out
    +    cdef const char *capsule_name = "BitGenerator"
    +
    +    x = PCG64()
    +    out = np.empty(n, dtype=np.uint32)
    +    capsule = x.capsule
    +
    +    if not PyCapsule_IsValid(capsule, capsule_name):
    +        raise ValueError("Invalid pointer to anon_func_state")
    +    rng = PyCapsule_GetPointer(capsule, capsule_name)
    +
    +    with x.lock, nogil:
    +        for i in range(n):
    +            out[i] = bounded_uint(lb, ub, rng)
    +    return np.asarray(out)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/_examples/cython/extending_distributions.pyx b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/_examples/cython/extending_distributions.pyx
    new file mode 100644
    index 0000000000000000000000000000000000000000..8de722686304bd46f997aa35c2f7d15582626e01
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/_examples/cython/extending_distributions.pyx
    @@ -0,0 +1,117 @@
    +#cython: language_level=3
    +"""
    +This file shows how the to use a BitGenerator to create a distribution.
    +"""
    +import numpy as np
    +cimport numpy as np
    +cimport cython
    +from cpython.pycapsule cimport PyCapsule_IsValid, PyCapsule_GetPointer
    +from libc.stdint cimport uint16_t, uint64_t
    +from numpy.random cimport bitgen_t
    +from numpy.random import PCG64
    +from numpy.random.c_distributions cimport (
    +      random_standard_uniform_fill, random_standard_uniform_fill_f)
    +
    +np.import_array()
    +
    +
    +@cython.boundscheck(False)
    +@cython.wraparound(False)
    +def uniforms(Py_ssize_t n):
    +    """
    +    Create an array of `n` uniformly distributed doubles.
    +    A 'real' distribution would want to process the values into
    +    some non-uniform distribution
    +    """
    +    cdef Py_ssize_t i
    +    cdef bitgen_t *rng
    +    cdef const char *capsule_name = "BitGenerator"
    +    cdef double[::1] random_values
    +
    +    x = PCG64()
    +    capsule = x.capsule
    +    # Optional check that the capsule if from a BitGenerator
    +    if not PyCapsule_IsValid(capsule, capsule_name):
    +        raise ValueError("Invalid pointer to anon_func_state")
    +    # Cast the pointer
    +    rng =  PyCapsule_GetPointer(capsule, capsule_name)
    +    random_values = np.empty(n, dtype='float64')
    +    with x.lock, nogil:
    +        for i in range(n):
    +            # Call the function
    +            random_values[i] = rng.next_double(rng.state)
    +    randoms = np.asarray(random_values)
    +
    +    return randoms
    +
    +# cython example 2
    +@cython.boundscheck(False)
    +@cython.wraparound(False)
    +def uint10_uniforms(Py_ssize_t n):
    +    """Uniform 10 bit integers stored as 16-bit unsigned integers"""
    +    cdef Py_ssize_t i
    +    cdef bitgen_t *rng
    +    cdef const char *capsule_name = "BitGenerator"
    +    cdef uint16_t[::1] random_values
    +    cdef int bits_remaining
    +    cdef int width = 10
    +    cdef uint64_t buff, mask = 0x3FF
    +
    +    x = PCG64()
    +    capsule = x.capsule
    +    if not PyCapsule_IsValid(capsule, capsule_name):
    +        raise ValueError("Invalid pointer to anon_func_state")
    +    rng =  PyCapsule_GetPointer(capsule, capsule_name)
    +    random_values = np.empty(n, dtype='uint16')
    +    # Best practice is to release GIL and acquire the lock
    +    bits_remaining = 0
    +    with x.lock, nogil:
    +        for i in range(n):
    +            if bits_remaining < width:
    +                buff = rng.next_uint64(rng.state)
    +            random_values[i] = buff & mask
    +            buff >>= width
    +
    +    randoms = np.asarray(random_values)
    +    return randoms
    +
    +# cython example 3
    +def uniforms_ex(bit_generator, Py_ssize_t n, dtype=np.float64):
    +    """
    +    Create an array of `n` uniformly distributed doubles via a "fill" function.
    +
    +    A 'real' distribution would want to process the values into
    +    some non-uniform distribution
    +
    +    Parameters
    +    ----------
    +    bit_generator: BitGenerator instance
    +    n: int
    +        Output vector length
    +    dtype: {str, dtype}, optional
    +        Desired dtype, either 'd' (or 'float64') or 'f' (or 'float32'). The
    +        default dtype value is 'd'
    +    """
    +    cdef bitgen_t *rng
    +    cdef const char *capsule_name = "BitGenerator"
    +    cdef np.ndarray randoms
    +
    +    capsule = bit_generator.capsule
    +    # Optional check that the capsule if from a BitGenerator
    +    if not PyCapsule_IsValid(capsule, capsule_name):
    +        raise ValueError("Invalid pointer to anon_func_state")
    +    # Cast the pointer
    +    rng =  PyCapsule_GetPointer(capsule, capsule_name)
    +
    +    _dtype = np.dtype(dtype)
    +    randoms = np.empty(n, dtype=_dtype)
    +    if _dtype == np.float32:
    +        with bit_generator.lock:
    +            random_standard_uniform_fill_f(rng, n, np.PyArray_DATA(randoms))
    +    elif _dtype == np.float64:
    +        with bit_generator.lock:
    +            random_standard_uniform_fill(rng, n, np.PyArray_DATA(randoms))
    +    else:
    +        raise TypeError('Unsupported dtype %r for random' % _dtype)
    +    return randoms
    +
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/_examples/cython/meson.build b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/_examples/cython/meson.build
    new file mode 100644
    index 0000000000000000000000000000000000000000..7aa367d13787c4f7ad5c2910bb044670b07eb012
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/_examples/cython/meson.build
    @@ -0,0 +1,53 @@
    +project('random-build-examples', 'c', 'cpp', 'cython')
    +
    +py_mod = import('python')
    +py3 = py_mod.find_installation(pure: false)
    +
    +cc = meson.get_compiler('c')
    +cy = meson.get_compiler('cython')
    +
    +# Keep synced with pyproject.toml
    +if not cy.version().version_compare('>=3.0.6')
    +  error('tests requires Cython >= 3.0.6')
    +endif
    +
    +base_cython_args = []
    +if cy.version().version_compare('>=3.1.0')
    +  base_cython_args += ['-Xfreethreading_compatible=True']
    +endif
    +
    +_numpy_abs = run_command(py3, ['-c',
    +               'import os; os.chdir(".."); import numpy; print(os.path.abspath(numpy.get_include() + "../../.."))'],
    +                         check: true).stdout().strip()
    +
    +npymath_path = _numpy_abs / '_core' / 'lib'
    +npy_include_path = _numpy_abs / '_core' / 'include'
    +npyrandom_path = _numpy_abs / 'random' / 'lib'
    +npymath_lib = cc.find_library('npymath', dirs: npymath_path)
    +npyrandom_lib = cc.find_library('npyrandom', dirs: npyrandom_path)
    +
    +py3.extension_module(
    +    'extending_distributions',
    +    'extending_distributions.pyx',
    +    install: false,
    +    include_directories: [npy_include_path],
    +    dependencies: [npyrandom_lib, npymath_lib],
    +    cython_args: base_cython_args,
    +)
    +py3.extension_module(
    +    'extending',
    +    'extending.pyx',
    +    install: false,
    +    include_directories: [npy_include_path],
    +    dependencies: [npyrandom_lib, npymath_lib],
    +    cython_args: base_cython_args,
    +)
    +py3.extension_module(
    +    'extending_cpp',
    +    'extending_distributions.pyx',
    +    install: false,
    +    override_options : ['cython_language=cpp'],
    +    cython_args: base_cython_args + ['--module-name', 'extending_cpp'],
    +    include_directories: [npy_include_path],
    +    dependencies: [npyrandom_lib, npymath_lib],
    +)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/_examples/numba/extending.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/_examples/numba/extending.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..c1d0f4fbd3e38247a129ef879f46d22950b412e6
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/_examples/numba/extending.py
    @@ -0,0 +1,86 @@
    +from timeit import timeit
    +
    +import numba as nb
    +
    +import numpy as np
    +from numpy.random import PCG64
    +
    +bit_gen = PCG64()
    +next_d = bit_gen.cffi.next_double
    +state_addr = bit_gen.cffi.state_address
    +
    +def normals(n, state):
    +    out = np.empty(n)
    +    for i in range((n + 1) // 2):
    +        x1 = 2.0 * next_d(state) - 1.0
    +        x2 = 2.0 * next_d(state) - 1.0
    +        r2 = x1 * x1 + x2 * x2
    +        while r2 >= 1.0 or r2 == 0.0:
    +            x1 = 2.0 * next_d(state) - 1.0
    +            x2 = 2.0 * next_d(state) - 1.0
    +            r2 = x1 * x1 + x2 * x2
    +        f = np.sqrt(-2.0 * np.log(r2) / r2)
    +        out[2 * i] = f * x1
    +        if 2 * i + 1 < n:
    +            out[2 * i + 1] = f * x2
    +    return out
    +
    +
    +# Compile using Numba
    +normalsj = nb.jit(normals, nopython=True)
    +# Must use state address not state with numba
    +n = 10000
    +
    +def numbacall():
    +    return normalsj(n, state_addr)
    +
    +
    +rg = np.random.Generator(PCG64())
    +
    +def numpycall():
    +    return rg.normal(size=n)
    +
    +
    +# Check that the functions work
    +r1 = numbacall()
    +r2 = numpycall()
    +assert r1.shape == (n,)
    +assert r1.shape == r2.shape
    +
    +t1 = timeit(numbacall, number=1000)
    +print(f'{t1:.2f} secs for {n} PCG64 (Numba/PCG64) gaussian randoms')
    +t2 = timeit(numpycall, number=1000)
    +print(f'{t2:.2f} secs for {n} PCG64 (NumPy/PCG64) gaussian randoms')
    +
    +# example 2
    +
    +next_u32 = bit_gen.ctypes.next_uint32
    +ctypes_state = bit_gen.ctypes.state
    +
    +@nb.jit(nopython=True)
    +def bounded_uint(lb, ub, state):
    +    mask = delta = ub - lb
    +    mask |= mask >> 1
    +    mask |= mask >> 2
    +    mask |= mask >> 4
    +    mask |= mask >> 8
    +    mask |= mask >> 16
    +
    +    val = next_u32(state) & mask
    +    while val > delta:
    +        val = next_u32(state) & mask
    +
    +    return lb + val
    +
    +
    +print(bounded_uint(323, 2394691, ctypes_state.value))
    +
    +
    +@nb.jit(nopython=True)
    +def bounded_uints(lb, ub, n, state):
    +    out = np.empty(n, dtype=np.uint32)
    +    for i in range(n):
    +        out[i] = bounded_uint(lb, ub, state)
    +
    +
    +bounded_uints(323, 2394691, 10000000, ctypes_state.value)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/_examples/numba/extending_distributions.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/_examples/numba/extending_distributions.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..d0462e73ee0bec4df2727af8a56a2f2edf86486e
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/_examples/numba/extending_distributions.py
    @@ -0,0 +1,67 @@
    +r"""
    +Building the required library in this example requires a source distribution
    +of NumPy or clone of the NumPy git repository since distributions.c is not
    +included in binary distributions.
    +
    +On *nix, execute in numpy/random/src/distributions
    +
    +export ${PYTHON_VERSION}=3.8 # Python version
    +export PYTHON_INCLUDE=#path to Python's include folder, usually \
    +    ${PYTHON_HOME}/include/python${PYTHON_VERSION}m
    +export NUMPY_INCLUDE=#path to numpy's include folder, usually \
    +    ${PYTHON_HOME}/lib/python${PYTHON_VERSION}/site-packages/numpy/_core/include
    +gcc -shared -o libdistributions.so -fPIC distributions.c \
    +    -I${NUMPY_INCLUDE} -I${PYTHON_INCLUDE}
    +mv libdistributions.so ../../_examples/numba/
    +
    +On Windows
    +
    +rem PYTHON_HOME and PYTHON_VERSION are setup dependent, this is an example
    +set PYTHON_HOME=c:\Anaconda
    +set PYTHON_VERSION=38
    +cl.exe /LD .\distributions.c -DDLL_EXPORT \
    +    -I%PYTHON_HOME%\lib\site-packages\numpy\_core\include \
    +    -I%PYTHON_HOME%\include %PYTHON_HOME%\libs\python%PYTHON_VERSION%.lib
    +move distributions.dll ../../_examples/numba/
    +"""
    +import os
    +
    +import numba as nb
    +from cffi import FFI
    +
    +import numpy as np
    +from numpy.random import PCG64
    +
    +ffi = FFI()
    +if os.path.exists('./distributions.dll'):
    +    lib = ffi.dlopen('./distributions.dll')
    +elif os.path.exists('./libdistributions.so'):
    +    lib = ffi.dlopen('./libdistributions.so')
    +else:
    +    raise RuntimeError('Required DLL/so file was not found.')
    +
    +ffi.cdef("""
    +double random_standard_normal(void *bitgen_state);
    +""")
    +x = PCG64()
    +xffi = x.cffi
    +bit_generator = xffi.bit_generator
    +
    +random_standard_normal = lib.random_standard_normal
    +
    +
    +def normals(n, bit_generator):
    +    out = np.empty(n)
    +    for i in range(n):
    +        out[i] = random_standard_normal(bit_generator)
    +    return out
    +
    +
    +normalsj = nb.jit(normals, nopython=True)
    +
    +# Numba requires a memory address for void *
    +# Can also get address from x.ctypes.bit_generator.value
    +bit_generator_address = int(ffi.cast('uintptr_t', bit_generator))
    +
    +norm = normalsj(1000, bit_generator_address)
    +print(norm[:12])
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/c_distributions.pxd b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/c_distributions.pxd
    new file mode 100644
    index 0000000000000000000000000000000000000000..da790ca499df2aadb503d6a98182575fb0de67ed
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/c_distributions.pxd
    @@ -0,0 +1,119 @@
    +#cython: wraparound=False, nonecheck=False, boundscheck=False, cdivision=True, language_level=3
    +from numpy cimport npy_intp
    +
    +from libc.stdint cimport (uint64_t, int32_t, int64_t)
    +from numpy.random cimport bitgen_t
    +
    +cdef extern from "numpy/random/distributions.h":
    +
    +    struct s_binomial_t:
    +        int has_binomial
    +        double psave
    +        int64_t nsave
    +        double r
    +        double q
    +        double fm
    +        int64_t m
    +        double p1
    +        double xm
    +        double xl
    +        double xr
    +        double c
    +        double laml
    +        double lamr
    +        double p2
    +        double p3
    +        double p4
    +
    +    ctypedef s_binomial_t binomial_t
    +
    +    float random_standard_uniform_f(bitgen_t *bitgen_state) nogil
    +    double random_standard_uniform(bitgen_t *bitgen_state) nogil
    +    void random_standard_uniform_fill(bitgen_t* bitgen_state, npy_intp cnt, double *out) nogil
    +    void random_standard_uniform_fill_f(bitgen_t *bitgen_state, npy_intp cnt, float *out) nogil
    +    
    +    double random_standard_exponential(bitgen_t *bitgen_state) nogil
    +    float random_standard_exponential_f(bitgen_t *bitgen_state) nogil
    +    void random_standard_exponential_fill(bitgen_t *bitgen_state, npy_intp cnt, double *out) nogil
    +    void random_standard_exponential_fill_f(bitgen_t *bitgen_state, npy_intp cnt, float *out) nogil
    +    void random_standard_exponential_inv_fill(bitgen_t *bitgen_state, npy_intp cnt, double *out) nogil
    +    void random_standard_exponential_inv_fill_f(bitgen_t *bitgen_state, npy_intp cnt, float *out) nogil
    +    
    +    double random_standard_normal(bitgen_t* bitgen_state) nogil
    +    float random_standard_normal_f(bitgen_t *bitgen_state) nogil
    +    void random_standard_normal_fill(bitgen_t *bitgen_state, npy_intp count, double *out) nogil
    +    void random_standard_normal_fill_f(bitgen_t *bitgen_state, npy_intp count, float *out) nogil
    +    double random_standard_gamma(bitgen_t *bitgen_state, double shape) nogil
    +    float random_standard_gamma_f(bitgen_t *bitgen_state, float shape) nogil
    +
    +    float random_standard_uniform_f(bitgen_t *bitgen_state) nogil
    +    void random_standard_uniform_fill_f(bitgen_t* bitgen_state, npy_intp cnt, float *out) nogil
    +    float random_standard_normal_f(bitgen_t* bitgen_state) nogil
    +    float random_standard_gamma_f(bitgen_t *bitgen_state, float shape) nogil
    +
    +    int64_t random_positive_int64(bitgen_t *bitgen_state) nogil
    +    int32_t random_positive_int32(bitgen_t *bitgen_state) nogil
    +    int64_t random_positive_int(bitgen_t *bitgen_state) nogil
    +    uint64_t random_uint(bitgen_t *bitgen_state) nogil
    +
    +    double random_normal(bitgen_t *bitgen_state, double loc, double scale) nogil
    +
    +    double random_gamma(bitgen_t *bitgen_state, double shape, double scale) nogil
    +    float random_gamma_f(bitgen_t *bitgen_state, float shape, float scale) nogil
    +
    +    double random_exponential(bitgen_t *bitgen_state, double scale) nogil
    +    double random_uniform(bitgen_t *bitgen_state, double lower, double range) nogil
    +    double random_beta(bitgen_t *bitgen_state, double a, double b) nogil
    +    double random_chisquare(bitgen_t *bitgen_state, double df) nogil
    +    double random_f(bitgen_t *bitgen_state, double dfnum, double dfden) nogil
    +    double random_standard_cauchy(bitgen_t *bitgen_state) nogil
    +    double random_pareto(bitgen_t *bitgen_state, double a) nogil
    +    double random_weibull(bitgen_t *bitgen_state, double a) nogil
    +    double random_power(bitgen_t *bitgen_state, double a) nogil
    +    double random_laplace(bitgen_t *bitgen_state, double loc, double scale) nogil
    +    double random_gumbel(bitgen_t *bitgen_state, double loc, double scale) nogil
    +    double random_logistic(bitgen_t *bitgen_state, double loc, double scale) nogil
    +    double random_lognormal(bitgen_t *bitgen_state, double mean, double sigma) nogil
    +    double random_rayleigh(bitgen_t *bitgen_state, double mode) nogil
    +    double random_standard_t(bitgen_t *bitgen_state, double df) nogil
    +    double random_noncentral_chisquare(bitgen_t *bitgen_state, double df,
    +                                       double nonc) nogil
    +    double random_noncentral_f(bitgen_t *bitgen_state, double dfnum,
    +                               double dfden, double nonc) nogil
    +    double random_wald(bitgen_t *bitgen_state, double mean, double scale) nogil
    +    double random_vonmises(bitgen_t *bitgen_state, double mu, double kappa) nogil
    +    double random_triangular(bitgen_t *bitgen_state, double left, double mode,
    +                             double right) nogil
    +
    +    int64_t random_poisson(bitgen_t *bitgen_state, double lam) nogil
    +    int64_t random_negative_binomial(bitgen_t *bitgen_state, double n, double p) nogil
    +    int64_t random_binomial(bitgen_t *bitgen_state, double p, int64_t n, binomial_t *binomial) nogil
    +    int64_t random_logseries(bitgen_t *bitgen_state, double p) nogil
    +    int64_t random_geometric_search(bitgen_t *bitgen_state, double p) nogil
    +    int64_t random_geometric_inversion(bitgen_t *bitgen_state, double p) nogil
    +    int64_t random_geometric(bitgen_t *bitgen_state, double p) nogil
    +    int64_t random_zipf(bitgen_t *bitgen_state, double a) nogil
    +    int64_t random_hypergeometric(bitgen_t *bitgen_state, int64_t good, int64_t bad,
    +                                    int64_t sample) nogil
    +
    +    uint64_t random_interval(bitgen_t *bitgen_state, uint64_t max) nogil
    +
    +    # Generate random uint64 numbers in closed interval [off, off + rng].
    +    uint64_t random_bounded_uint64(bitgen_t *bitgen_state,
    +                                   uint64_t off, uint64_t rng,
    +                                   uint64_t mask, bint use_masked) nogil
    +
    +    void random_multinomial(bitgen_t *bitgen_state, int64_t n, int64_t *mnix,
    +                            double *pix, npy_intp d, binomial_t *binomial) nogil
    +
    +    int random_multivariate_hypergeometric_count(bitgen_t *bitgen_state,
    +                          int64_t total,
    +                          size_t num_colors, int64_t *colors,
    +                          int64_t nsample,
    +                          size_t num_variates, int64_t *variates) nogil
    +    void random_multivariate_hypergeometric_marginals(bitgen_t *bitgen_state,
    +                               int64_t total,
    +                               size_t num_colors, int64_t *colors,
    +                               int64_t nsample,
    +                               size_t num_variates, int64_t *variates) nogil
    +
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/lib/libnpyrandom.a b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/lib/libnpyrandom.a
    new file mode 100644
    index 0000000000000000000000000000000000000000..93e6efeafaea3fa14750c1169455594d72aca796
    Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/lib/libnpyrandom.a differ
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/data/mt19937-testset-1.csv b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/data/mt19937-testset-1.csv
    new file mode 100644
    index 0000000000000000000000000000000000000000..b97bfa66f72f1be31cdb32317905d805fe90f4d5
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/data/mt19937-testset-1.csv
    @@ -0,0 +1,1001 @@
    +seed, 0xdeadbeaf
    +0, 0xc816921f
    +1, 0xb3623c6d
    +2, 0x5fa391bb
    +3, 0x40178d9
    +4, 0x7dcc9811
    +5, 0x548eb8e6
    +6, 0x92ba3125
    +7, 0x65fde68d
    +8, 0x2f81ec95
    +9, 0xbd94f7a2
    +10, 0xdc4d9bcc
    +11, 0xa672bf13
    +12, 0xb41113e
    +13, 0xec7e0066
    +14, 0x50239372
    +15, 0xd9d66b1d
    +16, 0xab72a161
    +17, 0xddc2e29f
    +18, 0x7ea29ab4
    +19, 0x80d141ba
    +20, 0xb1c7edf1
    +21, 0x44d29203
    +22, 0xe224d98
    +23, 0x5b3e9d26
    +24, 0x14fd567c
    +25, 0x27d98c96
    +26, 0x838779fc
    +27, 0x92a138a
    +28, 0x5d08965b
    +29, 0x531e0ad6
    +30, 0x984ee8f4
    +31, 0x1ed78539
    +32, 0x32bd6d8d
    +33, 0xc37c8516
    +34, 0x9aef5c6b
    +35, 0x3aacd139
    +36, 0xd96ed154
    +37, 0x489cd1ed
    +38, 0x2cba4b3b
    +39, 0x76c6ae72
    +40, 0x2dae02b9
    +41, 0x52ac5fd6
    +42, 0xc2b5e265
    +43, 0x630e6a28
    +44, 0x3f560d5d
    +45, 0x9315bdf3
    +46, 0xf1055aba
    +47, 0x840e42c6
    +48, 0xf2099c6b
    +49, 0x15ff7696
    +50, 0x7948d146
    +51, 0x97342961
    +52, 0x7a7a21c
    +53, 0xc66f4fb1
    +54, 0x23c4103e
    +55, 0xd7321f98
    +56, 0xeb7efb75
    +57, 0xe02490b5
    +58, 0x2aa02de
    +59, 0x8bee0bf7
    +60, 0xfc2da059
    +61, 0xae835034
    +62, 0x678f2075
    +63, 0x6d03094b
    +64, 0x56455e05
    +65, 0x18b32373
    +66, 0x8ff0356b
    +67, 0x1fe442fb
    +68, 0x3f1ab6c3
    +69, 0xb6fd21b
    +70, 0xfc310eb2
    +71, 0xb19e9a4d
    +72, 0x17ddee72
    +73, 0xfd534251
    +74, 0x9e500564
    +75, 0x9013a036
    +76, 0xcf08f118
    +77, 0x6b6d5969
    +78, 0x3ccf1977
    +79, 0x7cc11497
    +80, 0x651c6ac9
    +81, 0x4d6b104b
    +82, 0x9a28314e
    +83, 0x14c237be
    +84, 0x9cfc8d52
    +85, 0x2947fad5
    +86, 0xd71eff49
    +87, 0x5188730e
    +88, 0x4b894614
    +89, 0xf4fa2a34
    +90, 0x42f7cc69
    +91, 0x4089c9e8
    +92, 0xbf0bbfe4
    +93, 0x3cea65c
    +94, 0xc6221207
    +95, 0x1bb71a8f
    +96, 0x54843fe7
    +97, 0xbc59de4c
    +98, 0x79c6ee64
    +99, 0x14e57a26
    +100, 0x68d88fe
    +101, 0x2b86ef64
    +102, 0x8ffff3c1
    +103, 0x5bdd573f
    +104, 0x85671813
    +105, 0xefe32ca2
    +106, 0x105ded1e
    +107, 0x90ca2769
    +108, 0xb33963ac
    +109, 0x363fbbc3
    +110, 0x3b3763ae
    +111, 0x1d50ab88
    +112, 0xc9ec01eb
    +113, 0xc8bbeada
    +114, 0x5d704692
    +115, 0x5fd9e40
    +116, 0xe61c125
    +117, 0x2fe05792
    +118, 0xda8afb72
    +119, 0x4cbaa653
    +120, 0xdd2243df
    +121, 0x896fd3f5
    +122, 0x5bc23db
    +123, 0xa1c4e807
    +124, 0x57d1a24d
    +125, 0x66503ddc
    +126, 0xcf7c0838
    +127, 0x19e034fc
    +128, 0x66807450
    +129, 0xfc219b3b
    +130, 0xe8a843e7
    +131, 0x9ce61f08
    +132, 0x92b950d6
    +133, 0xce955ec4
    +134, 0xda0d1f0d
    +135, 0x960c6250
    +136, 0x39552432
    +137, 0xde845e84
    +138, 0xff3b4b11
    +139, 0x5d918e6f
    +140, 0xbb930df2
    +141, 0x7cfb0993
    +142, 0x5400e1e9
    +143, 0x3bfa0954
    +144, 0x7e2605fb
    +145, 0x11941591
    +146, 0x887e6994
    +147, 0xdc8bed45
    +148, 0x45b3fb50
    +149, 0xfbdf8358
    +150, 0x41507468
    +151, 0x34c87166
    +152, 0x17f64d77
    +153, 0x3bbaf4f8
    +154, 0x4f26f37e
    +155, 0x4a56ebf2
    +156, 0x81100f1
    +157, 0x96d94eae
    +158, 0xca88fda5
    +159, 0x2eef3a60
    +160, 0x952afbd3
    +161, 0x2bec88c7
    +162, 0x52335c4b
    +163, 0x8296db8e
    +164, 0x4da7d00a
    +165, 0xc00ac899
    +166, 0xadff8c72
    +167, 0xbecf26cf
    +168, 0x8835c83c
    +169, 0x1d13c804
    +170, 0xaa940ddc
    +171, 0x68222cfe
    +172, 0x4569c0e1
    +173, 0x29077976
    +174, 0x32d4a5af
    +175, 0xd31fcdef
    +176, 0xdc60682b
    +177, 0x7c95c368
    +178, 0x75a70213
    +179, 0x43021751
    +180, 0x5e52e0a6
    +181, 0xf7e190b5
    +182, 0xee3e4bb
    +183, 0x2fe3b150
    +184, 0xcf419c07
    +185, 0x478a4570
    +186, 0xe5c3ea50
    +187, 0x417f30a8
    +188, 0xf0cfdaa0
    +189, 0xd1f7f738
    +190, 0x2c70fc23
    +191, 0x54fc89f9
    +192, 0x444dcf01
    +193, 0xec2a002d
    +194, 0xef0c3a88
    +195, 0xde21be9
    +196, 0x88ab3296
    +197, 0x3028897c
    +198, 0x264b200b
    +199, 0xd8ae0706
    +200, 0x9eef901a
    +201, 0xbd1b96e0
    +202, 0xea71366c
    +203, 0x1465b694
    +204, 0x5a794650
    +205, 0x83df52d4
    +206, 0x8262413d
    +207, 0x5bc148c0
    +208, 0xe0ecd80c
    +209, 0x40649571
    +210, 0xb4d2ee5f
    +211, 0xedfd7d09
    +212, 0xa082e25f
    +213, 0xc62992d1
    +214, 0xbc7e65ee
    +215, 0x5499cf8a
    +216, 0xac28f775
    +217, 0x649840fb
    +218, 0xd4c54805
    +219, 0x1d166ba6
    +220, 0xbeb1171f
    +221, 0x45b66703
    +222, 0x78c03349
    +223, 0x38d2a6ff
    +224, 0x935cae8b
    +225, 0x1d07dc3f
    +226, 0x6c1ed365
    +227, 0x579fc585
    +228, 0x1320c0ec
    +229, 0x632757eb
    +230, 0xd265a397
    +231, 0x70e9b6c2
    +232, 0xc81e322c
    +233, 0xa27153cf
    +234, 0x2118ba19
    +235, 0x514ec400
    +236, 0x2bd0ecd6
    +237, 0xc3e7dae3
    +238, 0xfa39355e
    +239, 0x48f23cc1
    +240, 0xbcf75948
    +241, 0x53ccc70c
    +242, 0x75346423
    +243, 0x951181e0
    +244, 0x348e90df
    +245, 0x14365d7f
    +246, 0xfbc95d7a
    +247, 0xdc98a9e6
    +248, 0xed202df7
    +249, 0xa59ec913
    +250, 0x6b6e9ae2
    +251, 0x1697f265
    +252, 0x15d322d0
    +253, 0xa2e7ee0a
    +254, 0x88860b7e
    +255, 0x455d8b9d
    +256, 0x2f5c59cb
    +257, 0xac49c9f1
    +258, 0xa6a6a039
    +259, 0xc057f56b
    +260, 0xf1ff1208
    +261, 0x5eb8dc9d
    +262, 0xe6702509
    +263, 0xe238b0ed
    +264, 0x5ae32e3d
    +265, 0xa88ebbdf
    +266, 0xef885ae7
    +267, 0xafa6d49b
    +268, 0xc94499e0
    +269, 0x1a196325
    +270, 0x88938da3
    +271, 0x14f4345
    +272, 0xd8e33637
    +273, 0xa3551bd5
    +274, 0x73fe35c7
    +275, 0x9561e94b
    +276, 0xd673bf68
    +277, 0x16134872
    +278, 0x68c42f9f
    +279, 0xdf7574c8
    +280, 0x8809bab9
    +281, 0x1432cf69
    +282, 0xafb66bf1
    +283, 0xc184aa7b
    +284, 0xedbf2007
    +285, 0xbd420ce1
    +286, 0x761033a0
    +287, 0xff7e351f
    +288, 0xd6c3780e
    +289, 0x5844416f
    +290, 0xc6c0ee1c
    +291, 0xd2e147db
    +292, 0x92ac601a
    +293, 0x393e846b
    +294, 0x18196cca
    +295, 0x54a22be
    +296, 0x32bab1c4
    +297, 0x60365183
    +298, 0x64fa342
    +299, 0xca24a493
    +300, 0xd8cc8b83
    +301, 0x3faf102b
    +302, 0x6e09bb58
    +303, 0x812f0ea
    +304, 0x592c95d8
    +305, 0xe45ea4c5
    +306, 0x23aebf83
    +307, 0xbd9691d4
    +308, 0xf47b4baa
    +309, 0x4ac7b487
    +310, 0xcce18803
    +311, 0x3377556e
    +312, 0x3ff8e6b6
    +313, 0x99d22063
    +314, 0x23250bec
    +315, 0x4e1f9861
    +316, 0x8554249b
    +317, 0x8635c2fc
    +318, 0xe8426e8a
    +319, 0x966c29d8
    +320, 0x270b6082
    +321, 0x3180a8a1
    +322, 0xe7e1668b
    +323, 0x7f868dc
    +324, 0xcf4c17cf
    +325, 0xe31de4d1
    +326, 0xc8c8aff4
    +327, 0xae8db704
    +328, 0x3c928cc2
    +329, 0xe12cd48
    +330, 0xb33ecd04
    +331, 0xb93d7cbe
    +332, 0x49c69d6a
    +333, 0x7d3bce64
    +334, 0x86bc219
    +335, 0x8408233b
    +336, 0x44dc7479
    +337, 0xdf80d538
    +338, 0xf3db02c3
    +339, 0xbbbd31d7
    +340, 0x121281f
    +341, 0x7521e9a3
    +342, 0x8859675a
    +343, 0x75aa6502
    +344, 0x430ed15b
    +345, 0xecf0a28d
    +346, 0x659774fd
    +347, 0xd58a2311
    +348, 0x512389a9
    +349, 0xff65e1ff
    +350, 0xb6ddf222
    +351, 0xe3458895
    +352, 0x8b13cd6e
    +353, 0xd4a22870
    +354, 0xe604c50c
    +355, 0x27f54f26
    +356, 0x8f7f422f
    +357, 0x9735b4cf
    +358, 0x414072b0
    +359, 0x76a1c6d5
    +360, 0xa2208c06
    +361, 0x83cd0f61
    +362, 0x6c4f7ead
    +363, 0x6553cf76
    +364, 0xeffcf44
    +365, 0x7f434a3f
    +366, 0x9dc364bd
    +367, 0x3cdf52ed
    +368, 0xad597594
    +369, 0x9c3e211b
    +370, 0x6c04a33f
    +371, 0x885dafa6
    +372, 0xbbdaca71
    +373, 0x7ae5dd5c
    +374, 0x37675644
    +375, 0x251853c6
    +376, 0x130b086b
    +377, 0x143fa54b
    +378, 0x54cdc282
    +379, 0x9faff5b3
    +380, 0x502a5c8b
    +381, 0xd9524550
    +382, 0xae221aa6
    +383, 0x55cf759b
    +384, 0x24782da4
    +385, 0xd715d815
    +386, 0x250ea09a
    +387, 0x4e0744ac
    +388, 0x11e15814
    +389, 0xabe5f9df
    +390, 0xc8146350
    +391, 0xfba67d9b
    +392, 0x2b82e42f
    +393, 0xd4ea96fc
    +394, 0x5ffc179e
    +395, 0x1598bafe
    +396, 0x7fb6d662
    +397, 0x1a12a0db
    +398, 0x450cee4a
    +399, 0x85f8e12
    +400, 0xce71b594
    +401, 0xd4bb1d19
    +402, 0x968f379d
    +403, 0x54cc1d52
    +404, 0x467e6066
    +405, 0x7da5f9a9
    +406, 0x70977034
    +407, 0x49e65c4b
    +408, 0xd08570d1
    +409, 0x7acdf60b
    +410, 0xdffa038b
    +411, 0x9ce14e4c
    +412, 0x107cbbf8
    +413, 0xdd746ca0
    +414, 0xc6370a46
    +415, 0xe7f83312
    +416, 0x373fa9ce
    +417, 0xd822a2c6
    +418, 0x1d4efea6
    +419, 0xc53dcadb
    +420, 0x9b4e898f
    +421, 0x71daa6bf
    +422, 0x7a0bc78b
    +423, 0xd7b86f50
    +424, 0x1b8b3286
    +425, 0xcf9425dd
    +426, 0xd5263220
    +427, 0x4ea0b647
    +428, 0xc767fe64
    +429, 0xcfc5e67
    +430, 0xcc6a2942
    +431, 0xa51eff00
    +432, 0x76092e1b
    +433, 0xf606e80f
    +434, 0x824b5e20
    +435, 0xebb55e14
    +436, 0x783d96a6
    +437, 0x10696512
    +438, 0x17ee510a
    +439, 0x3ab70a1f
    +440, 0xcce6b210
    +441, 0x8f72f0fb
    +442, 0xf0610b41
    +443, 0x83d01fb5
    +444, 0x6b3de36
    +445, 0xe4c2e84f
    +446, 0x9c43bb15
    +447, 0xddf2905
    +448, 0x7dd63556
    +449, 0x3662ca09
    +450, 0xfb81f35b
    +451, 0xc2c8a72a
    +452, 0x8e93c37
    +453, 0xa93da2d4
    +454, 0xa03af8f1
    +455, 0x8d75159a
    +456, 0x15f010b0
    +457, 0xa296ab06
    +458, 0xe55962ba
    +459, 0xeae700a9
    +460, 0xe388964a
    +461, 0x917f2bec
    +462, 0x1c203fea
    +463, 0x792a01ba
    +464, 0xa93a80ac
    +465, 0x9eb8a197
    +466, 0x56c0bc73
    +467, 0xb8f05799
    +468, 0xf429a8c8
    +469, 0xb92cee42
    +470, 0xf8864ec
    +471, 0x62f2518a
    +472, 0x3a7bfa3e
    +473, 0x12e56e6d
    +474, 0xd7a18313
    +475, 0x41fa3899
    +476, 0xa09c4956
    +477, 0xebcfd94a
    +478, 0xc485f90b
    +479, 0x4391ce40
    +480, 0x742a3333
    +481, 0xc932f9e5
    +482, 0x75c6c263
    +483, 0x80937f0
    +484, 0xcf21833c
    +485, 0x16027520
    +486, 0xd42e669f
    +487, 0xb0f01fb7
    +488, 0xb35896f1
    +489, 0x763737a9
    +490, 0x1bb20209
    +491, 0x3551f189
    +492, 0x56bc2602
    +493, 0xb6eacf4
    +494, 0x42ec4d11
    +495, 0x245cc68
    +496, 0xc27ac43b
    +497, 0x9d903466
    +498, 0xce3f0c05
    +499, 0xb708c31c
    +500, 0xc0fd37eb
    +501, 0x95938b2c
    +502, 0xf20175a7
    +503, 0x4a86ee9b
    +504, 0xbe039a58
    +505, 0xd41cabe7
    +506, 0x83bc99ba
    +507, 0x761d60e1
    +508, 0x7737cc2e
    +509, 0x2b82fc4b
    +510, 0x375aa401
    +511, 0xfe9597a0
    +512, 0x5543806a
    +513, 0x44f31238
    +514, 0x7df31538
    +515, 0x74cfa770
    +516, 0x8755d881
    +517, 0x1fde665a
    +518, 0xda8bf315
    +519, 0x973d8e95
    +520, 0x72205228
    +521, 0x8fe59717
    +522, 0x7bb90b34
    +523, 0xef6ed945
    +524, 0x16fd4a38
    +525, 0x5db44de1
    +526, 0xf09f93b3
    +527, 0xe84824cc
    +528, 0x945bb50e
    +529, 0xd0be4aa5
    +530, 0x47c277c2
    +531, 0xd3800c28
    +532, 0xac1c33ec
    +533, 0xd3dacce
    +534, 0x811c8387
    +535, 0x6761b36
    +536, 0x70d3882f
    +537, 0xd6e62e3a
    +538, 0xea25daa2
    +539, 0xb07f39d1
    +540, 0x391d89d7
    +541, 0x84b6fb5e
    +542, 0x3dda3fca
    +543, 0x229e80a4
    +544, 0x3d94a4b7
    +545, 0x5d3d576a
    +546, 0xad7818a0
    +547, 0xce23b03a
    +548, 0x7aa2079c
    +549, 0x9a6be555
    +550, 0x83f3b34a
    +551, 0x1848f9d9
    +552, 0xd8fefc1c
    +553, 0x48e6ce48
    +554, 0x52e55750
    +555, 0xf41a71cf
    +556, 0xba08e259
    +557, 0xfaf06a15
    +558, 0xeaaac0fb
    +559, 0x34f90098
    +560, 0xb1dfffbb
    +561, 0x718daec2
    +562, 0xab4dda21
    +563, 0xd27cc1ee
    +564, 0x4aafbc4c
    +565, 0x356dfb4f
    +566, 0x83fcdfd6
    +567, 0x8f0bcde0
    +568, 0x4363f844
    +569, 0xadc0f4d5
    +570, 0x3bde994e
    +571, 0x3884d452
    +572, 0x21876b4a
    +573, 0x9c985398
    +574, 0xca55a226
    +575, 0x3a88c583
    +576, 0x916dc33c
    +577, 0x8f67d1d7
    +578, 0x3b26a667
    +579, 0xe4ddeb4b
    +580, 0x1a9d8c33
    +581, 0x81c9b74f
    +582, 0x9ed1e9df
    +583, 0x6e61aecf
    +584, 0x95e95a5d
    +585, 0x68864ff5
    +586, 0xb8fa5b9
    +587, 0x72b1b3de
    +588, 0x5e18a86b
    +589, 0xd7f2337d
    +590, 0xd70e0925
    +591, 0xb573a4c1
    +592, 0xc77b3f8a
    +593, 0x389b20de
    +594, 0x16cf6afb
    +595, 0xa39bd275
    +596, 0xf491cf01
    +597, 0x6f88a802
    +598, 0x8510af05
    +599, 0xe7cd549a
    +600, 0x8603179a
    +601, 0xef43f191
    +602, 0xf9b64c60
    +603, 0xb00254a7
    +604, 0xd7c06a2d
    +605, 0x17e9380b
    +606, 0x529e727b
    +607, 0xaaa8fe0a
    +608, 0xfb64ff4c
    +609, 0xcd75af26
    +610, 0xfb717c87
    +611, 0xa0789899
    +612, 0x10391ec9
    +613, 0x7e9b40b3
    +614, 0x18536554
    +615, 0x728c05f7
    +616, 0x787dca98
    +617, 0xad948d1
    +618, 0x44c18def
    +619, 0x3303f2ec
    +620, 0xa15acb5
    +621, 0xb58d38f4
    +622, 0xfe041ef8
    +623, 0xd151a956
    +624, 0x7b9168e8
    +625, 0x5ebeca06
    +626, 0x90fe95df
    +627, 0xf76875aa
    +628, 0xb2e0d664
    +629, 0x2e3253b7
    +630, 0x68e34469
    +631, 0x1f0c2d89
    +632, 0x13a34ac2
    +633, 0x5ffeb841
    +634, 0xe381e91c
    +635, 0xb8549a92
    +636, 0x3f35cf1
    +637, 0xda0f9dcb
    +638, 0xdd9828a6
    +639, 0xe1428f29
    +640, 0xf4db80b5
    +641, 0xdac30af5
    +642, 0x1af1dd17
    +643, 0x9a540254
    +644, 0xcab68a38
    +645, 0x33560361
    +646, 0x2fbf3886
    +647, 0xbc785923
    +648, 0xe081cd10
    +649, 0x8e473356
    +650, 0xd102c357
    +651, 0xeea4fe48
    +652, 0x248d3453
    +653, 0x1da79ac
    +654, 0x815a65ff
    +655, 0x27693e76
    +656, 0xb7d5af40
    +657, 0x6d245d30
    +658, 0x9e06fa8f
    +659, 0xb0570dcb
    +660, 0x469f0005
    +661, 0x3e0ca132
    +662, 0xd89bbf3
    +663, 0xd61ccd47
    +664, 0x6383878
    +665, 0x62b5956
    +666, 0x4dc83675
    +667, 0x93fd8492
    +668, 0x5a0091f5
    +669, 0xc9f9bc3
    +670, 0xa26e7778
    +671, 0xeabf2d01
    +672, 0xe612dc06
    +673, 0x85d89ff9
    +674, 0xd1763179
    +675, 0xcb88947b
    +676, 0x9e8757a5
    +677, 0xe100e85c
    +678, 0x904166eb
    +679, 0x4996243d
    +680, 0x4038e1cb
    +681, 0x2be2c63d
    +682, 0x77017e81
    +683, 0x3b1f556b
    +684, 0x1c785c77
    +685, 0x6869b8bd
    +686, 0xe1217ed4
    +687, 0x4012ab2f
    +688, 0xc06c0d8e
    +689, 0x2122eb68
    +690, 0xad1783fd
    +691, 0x5f0c80e3
    +692, 0x828f7efa
    +693, 0x29328399
    +694, 0xeadf1087
    +695, 0x85dc0037
    +696, 0x9691ef26
    +697, 0xc0947a53
    +698, 0x2a178d2a
    +699, 0x2a2c7e8f
    +700, 0x90378380
    +701, 0xaad8d326
    +702, 0x9cf1c3c8
    +703, 0x84eccd44
    +704, 0x79e61808
    +705, 0x8b3f454e
    +706, 0x209e6e1
    +707, 0x51f88378
    +708, 0xc210226f
    +709, 0xd982adb5
    +710, 0x55d44a31
    +711, 0x9817d443
    +712, 0xa328c626
    +713, 0x13455966
    +714, 0xb8f681d3
    +715, 0x2a3c713b
    +716, 0xc186959b
    +717, 0x814a74b0
    +718, 0xed7bc90
    +719, 0xa88d3d6d
    +720, 0x88a9f561
    +721, 0x73aa1c0a
    +722, 0xdfeff404
    +723, 0xec037e4b
    +724, 0xa5c209f0
    +725, 0xb3a223b4
    +726, 0x24ce3709
    +727, 0x3184c790
    +728, 0xa1398c62
    +729, 0x2f92034e
    +730, 0xbb37a79a
    +731, 0x605287b4
    +732, 0x8faa772c
    +733, 0x6ce56c1d
    +734, 0xc035fb4c
    +735, 0x7cf5b316
    +736, 0x6502645
    +737, 0xa283d810
    +738, 0x778bc2f1
    +739, 0xfdf99313
    +740, 0x1f513265
    +741, 0xbd3837e2
    +742, 0x9b84a9a
    +743, 0x2139ce91
    +744, 0x61a8e890
    +745, 0xf9ff12db
    +746, 0xb43d2ea7
    +747, 0x88532e61
    +748, 0x175a6655
    +749, 0x7a6c4f72
    +750, 0x6dafc1b7
    +751, 0x449b1459
    +752, 0x514f654f
    +753, 0x9a6731e2
    +754, 0x8632da43
    +755, 0xc81b0422
    +756, 0x81fe9005
    +757, 0x15b79618
    +758, 0xb5fa629f
    +759, 0x987a474f
    +760, 0x1c74f54e
    +761, 0xf9743232
    +762, 0xec4b55f
    +763, 0x87d761e5
    +764, 0xd1ad78b7
    +765, 0x453d9350
    +766, 0xc7a7d85
    +767, 0xb2576ff5
    +768, 0xcdde49b7
    +769, 0x8e1f763e
    +770, 0x1338583e
    +771, 0xfd65b9dc
    +772, 0x4f19c4f4
    +773, 0x3a52d73d
    +774, 0xd3509c4c
    +775, 0xda24fe31
    +776, 0xe2de56ba
    +777, 0x2db5e540
    +778, 0x23172734
    +779, 0x4db572f
    +780, 0xeb941718
    +781, 0x84c2649a
    +782, 0x3b1e5b6a
    +783, 0x4c9c61b9
    +784, 0x3bccd11
    +785, 0xb4d7b78e
    +786, 0x48580ae5
    +787, 0xd273ab68
    +788, 0x25c11615
    +789, 0x470b53f6
    +790, 0x329c2068
    +791, 0x1693721b
    +792, 0xf8c9aacf
    +793, 0x4c3d5693
    +794, 0xd778284e
    +795, 0xae1cb24f
    +796, 0x3c11d1b3
    +797, 0xddd2b0c0
    +798, 0x90269fa7
    +799, 0x5666e0a2
    +800, 0xf9f195a4
    +801, 0x61d78eb2
    +802, 0xada5a7c0
    +803, 0xaa272fbe
    +804, 0xba3bae2f
    +805, 0xd0b70fc2
    +806, 0x529f32b
    +807, 0xda7a3e21
    +808, 0x9a776a20
    +809, 0xb21f9635
    +810, 0xb3acc14e
    +811, 0xac55f56
    +812, 0x29dccf41
    +813, 0x32dabdb3
    +814, 0xaa032f58
    +815, 0xfa406af4
    +816, 0xce3c415d
    +817, 0xb44fb4d9
    +818, 0x32248d1c
    +819, 0x680c6440
    +820, 0xae2337b
    +821, 0x294cb597
    +822, 0x5bca48fe
    +823, 0xaef19f40
    +824, 0xad60406
    +825, 0x4781f090
    +826, 0xfd691ffc
    +827, 0xb6568268
    +828, 0xa56c72cb
    +829, 0xf8a9e0fc
    +830, 0x9af4fd02
    +831, 0x2cd30932
    +832, 0x776cefd7
    +833, 0xe31f476e
    +834, 0x6d94a437
    +835, 0xb3cab598
    +836, 0xf582d13f
    +837, 0x3bf8759d
    +838, 0xc3777dc
    +839, 0x5e425ea8
    +840, 0x1c7ff4ed
    +841, 0x1c2e97d1
    +842, 0xc062d2b4
    +843, 0x46dc80e0
    +844, 0xbcdb47e6
    +845, 0x32282fe0
    +846, 0xaba89063
    +847, 0x5e94e9bb
    +848, 0x3e667f78
    +849, 0xea6eb21a
    +850, 0xe56e54e8
    +851, 0xa0383510
    +852, 0x6768fe2b
    +853, 0xb53ac3e0
    +854, 0x779569a0
    +855, 0xeca83c6a
    +856, 0x24db4d2d
    +857, 0x4585f696
    +858, 0xf84748b2
    +859, 0xf6a4dd5b
    +860, 0x31fb524d
    +861, 0x67ab39fe
    +862, 0x5882a899
    +863, 0x9a05fcf6
    +864, 0x712b5674
    +865, 0xe8c6958f
    +866, 0x4b448bb3
    +867, 0x530b9abf
    +868, 0xb491f491
    +869, 0x98352c62
    +870, 0x2d0a50e3
    +871, 0xeb4384da
    +872, 0x36246f07
    +873, 0xcbc5c1a
    +874, 0xae24031d
    +875, 0x44d11ed6
    +876, 0xf07f1608
    +877, 0xf296aadd
    +878, 0x3bcfe3be
    +879, 0x8fa1e7df
    +880, 0xfd317a6e
    +881, 0xe4975c44
    +882, 0x15205892
    +883, 0xa762d4df
    +884, 0xf1167365
    +885, 0x6811cc00
    +886, 0x8315f23
    +887, 0xe045b4b1
    +888, 0xa8496414
    +889, 0xbed313ae
    +890, 0xcdae3ddb
    +891, 0xa9c22c9
    +892, 0x275fab1a
    +893, 0xedd65fa
    +894, 0x4c188229
    +895, 0x63a83e58
    +896, 0x18aa9207
    +897, 0xa41f2e78
    +898, 0xd9f63653
    +899, 0xbe2be73b
    +900, 0xa3364d39
    +901, 0x896d5428
    +902, 0xc737539e
    +903, 0x745a78c6
    +904, 0xf0b2b042
    +905, 0x510773b4
    +906, 0x92ad8e37
    +907, 0x27f2f8c4
    +908, 0x23704cc8
    +909, 0x3d95a77f
    +910, 0xf08587a4
    +911, 0xbd696a25
    +912, 0x948924f3
    +913, 0x8cddb634
    +914, 0xcd2a4910
    +915, 0x8e0e300e
    +916, 0x83815a9b
    +917, 0x67383510
    +918, 0x3c18f0d0
    +919, 0xc7a7bccc
    +920, 0x7cc2d3a2
    +921, 0x52eb2eeb
    +922, 0xe4a257e5
    +923, 0xec76160e
    +924, 0x63f9ad68
    +925, 0x36d0bbbf
    +926, 0x957bc4e4
    +927, 0xc9ed90ff
    +928, 0x4cb6059d
    +929, 0x2f86eca1
    +930, 0x3e3665a3
    +931, 0x9b7eb6f4
    +932, 0x492e7e18
    +933, 0xa098aa51
    +934, 0x7eb568b2
    +935, 0x3fd639ba
    +936, 0x7bebcf1
    +937, 0x99c844ad
    +938, 0x43cb5ec7
    +939, 0x8dfbbef5
    +940, 0x5be413ff
    +941, 0xd93b976d
    +942, 0xc1c7a86d
    +943, 0x1f0e93d0
    +944, 0x498204a2
    +945, 0xe8fe832a
    +946, 0x2236bd7
    +947, 0x89953769
    +948, 0x2acc3491
    +949, 0x2c4f22c6
    +950, 0xd7996277
    +951, 0x3bcdc349
    +952, 0xfc286630
    +953, 0x5f8909fd
    +954, 0x242677c0
    +955, 0x4cb34104
    +956, 0xa6ff8100
    +957, 0x39ea47ec
    +958, 0x9bd54140
    +959, 0x7502ffe8
    +960, 0x7ebef8ae
    +961, 0x1ed8abe4
    +962, 0xfaba8450
    +963, 0xc197b65f
    +964, 0x19431455
    +965, 0xe229c176
    +966, 0xeb2967da
    +967, 0xe0c5dc05
    +968, 0xa84e3227
    +969, 0x10dd9e0f
    +970, 0xbdb70b02
    +971, 0xce24808a
    +972, 0x423edab8
    +973, 0x194caf71
    +974, 0x144f150d
    +975, 0xf811c2d2
    +976, 0xc224ee85
    +977, 0x2b217a5b
    +978, 0xf78a5a79
    +979, 0x6554a4b1
    +980, 0x769582df
    +981, 0xf4b2cf93
    +982, 0x89648483
    +983, 0xb3283a3e
    +984, 0x82b895db
    +985, 0x79388ef0
    +986, 0x54bc42a6
    +987, 0xc4dd39d9
    +988, 0x45b33b7d
    +989, 0x8703b2c1
    +990, 0x1cc94806
    +991, 0xe0f43e49
    +992, 0xcaa7b6bc
    +993, 0x4f88e9af
    +994, 0x1477cce5
    +995, 0x347dd115
    +996, 0x36e335fa
    +997, 0xb93c9a31
    +998, 0xaac3a175
    +999, 0x68a19647
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/data/mt19937-testset-2.csv b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/data/mt19937-testset-2.csv
    new file mode 100644
    index 0000000000000000000000000000000000000000..cdb8e4794ccdb30fa4fea3045b30a188f6ff5357
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/data/mt19937-testset-2.csv
    @@ -0,0 +1,1001 @@
    +seed, 0x0
    +0, 0x7ab4ea94
    +1, 0x9b561119
    +2, 0x4957d02e
    +3, 0x7dd3fdc2
    +4, 0x5affe54
    +5, 0x5a01741c
    +6, 0x8b9e8c1f
    +7, 0xda5bf11a
    +8, 0x509226
    +9, 0x64e2ea17
    +10, 0x82c6dab5
    +11, 0xe4302515
    +12, 0x8198b873
    +13, 0xc3ec9a82
    +14, 0x829dff28
    +15, 0x5278e44f
    +16, 0x994a7d2c
    +17, 0xf1c89398
    +18, 0xaf2fddec
    +19, 0x22abc6ee
    +20, 0x963dbd43
    +21, 0xc29edffb
    +22, 0x41c1ce07
    +23, 0x9c90034d
    +24, 0x1f17a796
    +25, 0x3833caa8
    +26, 0xb8795528
    +27, 0xebc595a2
    +28, 0xf8f5b5dd
    +29, 0xc2881f72
    +30, 0x18e5d3f0
    +31, 0x9b19ac7a
    +32, 0xb9992436
    +33, 0xc00052b3
    +34, 0xb63f4475
    +35, 0x962642d9
    +36, 0x63506c10
    +37, 0x2be6b127
    +38, 0x569bdbc6
    +39, 0x7f185e01
    +40, 0xebb55f53
    +41, 0x1c30198c
    +42, 0x7c8d75c6
    +43, 0xd3f2186b
    +44, 0xaca5b9b1
    +45, 0xbc49ff45
    +46, 0xc4a802af
    +47, 0x2cecd86f
    +48, 0x8e0da529
    +49, 0x1f22b00e
    +50, 0x4559ea80
    +51, 0x60f587d8
    +52, 0x7c7460e9
    +53, 0x67be0a4a
    +54, 0x987a0183
    +55, 0x7bd30f1
    +56, 0xab18c4ac
    +57, 0xffdbfb64
    +58, 0x9ea917f9
    +59, 0x1239dab7
    +60, 0x38efabeb
    +61, 0x5da91888
    +62, 0x8f49ed62
    +63, 0x83f60b1e
    +64, 0x5950a3fc
    +65, 0xd8911104
    +66, 0x19e8859e
    +67, 0x1a4d89ec
    +68, 0x968ca180
    +69, 0x9e1b6da3
    +70, 0x3d99c2c
    +71, 0x55f76289
    +72, 0x8fa28b9e
    +73, 0x9fe01d33
    +74, 0xdade4e38
    +75, 0x1ea04290
    +76, 0xa7263313
    +77, 0xaafc762e
    +78, 0x460476d6
    +79, 0x31226e12
    +80, 0x451d3f05
    +81, 0xd0d2764b
    +82, 0xd06e1ab3
    +83, 0x1394e3f4
    +84, 0x2fc04ea3
    +85, 0x5b8401c
    +86, 0xebd6c929
    +87, 0xe881687c
    +88, 0x94bdd66a
    +89, 0xabf85983
    +90, 0x223ad12d
    +91, 0x2aaeeaa3
    +92, 0x1f704934
    +93, 0x2db2efb6
    +94, 0xf49b8dfb
    +95, 0x5bdbbb9d
    +96, 0xba0cd0db
    +97, 0x4ec4674e
    +98, 0xad0129e
    +99, 0x7a66129b
    +100, 0x50d12c5e
    +101, 0x85b1d335
    +102, 0x3efda58a
    +103, 0xecd886fb
    +104, 0x8ecadd3d
    +105, 0x60ebac0f
    +106, 0x5e10fe79
    +107, 0xa84f7e5d
    +108, 0x43931288
    +109, 0xfacf448
    +110, 0x4ee01997
    +111, 0xcdc0a651
    +112, 0x33c87037
    +113, 0x8b50fc03
    +114, 0xf52aad34
    +115, 0xda6cd856
    +116, 0x7585bea0
    +117, 0xe947c762
    +118, 0x4ddff5d8
    +119, 0xe0e79b3b
    +120, 0xb804cf09
    +121, 0x84765c44
    +122, 0x3ff666b4
    +123, 0xe31621ad
    +124, 0x816f2236
    +125, 0x228176bc
    +126, 0xfdc14904
    +127, 0x635f5077
    +128, 0x6981a817
    +129, 0xfd9a0300
    +130, 0xd3fa8a24
    +131, 0xd67c1a77
    +132, 0x903fe97a
    +133, 0xf7c4a4d5
    +134, 0x109f2058
    +135, 0x48ab87fe
    +136, 0xfd6f1928
    +137, 0x707e9452
    +138, 0xf327db9e
    +139, 0x7b80d76d
    +140, 0xfb6ba193
    +141, 0x454a1ad0
    +142, 0xe20b51e
    +143, 0xb774d085
    +144, 0x6b1ed574
    +145, 0xb1e77de4
    +146, 0xe2a83b37
    +147, 0x33d3176f
    +148, 0x2f0ca0fc
    +149, 0x17f51e2
    +150, 0x7c1fbf55
    +151, 0xf09e9cd0
    +152, 0xe3d9bacd
    +153, 0x4244db0a
    +154, 0x876c09fc
    +155, 0x9db4fc2f
    +156, 0xd3771d60
    +157, 0x25fc6a75
    +158, 0xb309915c
    +159, 0xc50ee027
    +160, 0xaa5b7b38
    +161, 0x4c650ded
    +162, 0x1acb2879
    +163, 0x50db5887
    +164, 0x90054847
    +165, 0xfef23e5b
    +166, 0x2dd7b7d5
    +167, 0x990b8c2e
    +168, 0x6001a601
    +169, 0xb5d314c4
    +170, 0xfbfb7bf9
    +171, 0x1aba997d
    +172, 0x814e7304
    +173, 0x989d956a
    +174, 0x86d5a29c
    +175, 0x70a9fa08
    +176, 0xc4ccba87
    +177, 0x7e9cb366
    +178, 0xee18eb0a
    +179, 0x44f5be58
    +180, 0x91d4af2d
    +181, 0x5ab6e593
    +182, 0x9fd6bb4d
    +183, 0x85894ce
    +184, 0x728a2401
    +185, 0xf006f6d4
    +186, 0xd782741e
    +187, 0x842cd5bd
    +188, 0xfb5883aa
    +189, 0x7e5a471
    +190, 0x83ff6965
    +191, 0xc9675c6b
    +192, 0xb6ced3c7
    +193, 0x3de6425b
    +194, 0x25e14db4
    +195, 0x69ca3dec
    +196, 0x81342d13
    +197, 0xd7cd8417
    +198, 0x88d15e69
    +199, 0xefba17c9
    +200, 0x43d595e6
    +201, 0x89d4cf25
    +202, 0x7cae9b9b
    +203, 0x2242c621
    +204, 0x27fc3598
    +205, 0x467b1d84
    +206, 0xe84d4622
    +207, 0xa26bf980
    +208, 0x80411010
    +209, 0xe2c2bfea
    +210, 0xbc6ca25a
    +211, 0x3ddb592a
    +212, 0xdd46eb9e
    +213, 0xdfe8f657
    +214, 0x2cedc974
    +215, 0xf0dc546b
    +216, 0xd46be68f
    +217, 0x26d8a5aa
    +218, 0x76e96ba3
    +219, 0x7d5b5353
    +220, 0xf532237c
    +221, 0x6478b79
    +222, 0x9b81a5e5
    +223, 0x5fc68e5c
    +224, 0x68436e70
    +225, 0x2a0043f9
    +226, 0x108d523c
    +227, 0x7a4c32a3
    +228, 0x9c84c742
    +229, 0x6f813dae
    +230, 0xfcc5bbcc
    +231, 0x215b6f3a
    +232, 0x84cb321d
    +233, 0x7913a248
    +234, 0xb1e6b585
    +235, 0x49376b31
    +236, 0x1dc896b0
    +237, 0x347051ad
    +238, 0x5524c042
    +239, 0xda0eef9d
    +240, 0xf2e73342
    +241, 0xbeee2f9d
    +242, 0x7c702874
    +243, 0x9eb3bd34
    +244, 0x97b09700
    +245, 0xcdbab1d4
    +246, 0x4a2f6ed1
    +247, 0x2047bda5
    +248, 0x3ecc7005
    +249, 0x8d0d5e67
    +250, 0x40876fb5
    +251, 0xb5fd2187
    +252, 0xe915d8af
    +253, 0x9a2351c7
    +254, 0xccc658ae
    +255, 0xebb1eddc
    +256, 0xc4a83671
    +257, 0xffb2548f
    +258, 0xe4fe387a
    +259, 0x477aaab4
    +260, 0x8475a4e4
    +261, 0xf8823e46
    +262, 0xe4130f71
    +263, 0xbdb54482
    +264, 0x98fe0462
    +265, 0xf36b27b8
    +266, 0xed7733da
    +267, 0x5f428afc
    +268, 0x43a3a21a
    +269, 0xf8370b55
    +270, 0xfade1de1
    +271, 0xd9a038ea
    +272, 0x3c69af23
    +273, 0x24df7dd0
    +274, 0xf66d9353
    +275, 0x71d811be
    +276, 0xcc4d024b
    +277, 0xb8c30bf0
    +278, 0x4198509d
    +279, 0x8b37ba36
    +280, 0xa41ae29a
    +281, 0x8cf7799e
    +282, 0x5cd0136a
    +283, 0xa11324ef
    +284, 0x2f8b6d4b
    +285, 0x3657cf17
    +286, 0x35b6873f
    +287, 0xee6e5bd7
    +288, 0xbeeaa98
    +289, 0x9ad3c581
    +290, 0xe2376c3f
    +291, 0x738027cc
    +292, 0x536ac839
    +293, 0xf066227
    +294, 0x6c9cb0f9
    +295, 0x84082ae6
    +296, 0xab38ae9d
    +297, 0x493eade9
    +298, 0xcb630b3a
    +299, 0x64d44250
    +300, 0xe5efb557
    +301, 0xea2424d9
    +302, 0x11a690ba
    +303, 0x30a48ae4
    +304, 0x58987e53
    +305, 0x94ec6076
    +306, 0x5d3308fa
    +307, 0xf1635ebb
    +308, 0x56a5ab90
    +309, 0x2b2f2ee4
    +310, 0x6f9e6483
    +311, 0x8b93e327
    +312, 0xa7ce140b
    +313, 0x4c8aa42
    +314, 0x7657bb3f
    +315, 0xf250fd75
    +316, 0x1edfcb0f
    +317, 0xdb42ace3
    +318, 0xf8147e16
    +319, 0xd1992bd
    +320, 0x64bb14d1
    +321, 0x423e724d
    +322, 0x7b172f7c
    +323, 0x17171696
    +324, 0x4acaf83b
    +325, 0x7a83527e
    +326, 0xfc980c60
    +327, 0xc8b56bb
    +328, 0x2453f77f
    +329, 0x85ad1bf9
    +330, 0x62a85dfe
    +331, 0x48238c4d
    +332, 0xbb3ec1eb
    +333, 0x4c1c039c
    +334, 0x1f37f571
    +335, 0x98aecb63
    +336, 0xc3b3ddd6
    +337, 0xd22dad4
    +338, 0xe49671a3
    +339, 0xe3baf945
    +340, 0xb9e21680
    +341, 0xda562856
    +342, 0xe8b88ce4
    +343, 0x86f88de2
    +344, 0x986faf76
    +345, 0x6f0025c3
    +346, 0x3fe21234
    +347, 0xd8d3f729
    +348, 0xc2d11c6f
    +349, 0xd4f9e8f
    +350, 0xf61a0aa
    +351, 0xc48bb313
    +352, 0xe944e940
    +353, 0xf1801b2e
    +354, 0x253590be
    +355, 0x981f069d
    +356, 0x891454d8
    +357, 0xa4f824ad
    +358, 0x6dd2cc48
    +359, 0x3018827e
    +360, 0x3fb329e6
    +361, 0x65276517
    +362, 0x8d2c0dd2
    +363, 0xc965b48e
    +364, 0x85d14d90
    +365, 0x5a51623c
    +366, 0xa9573d6a
    +367, 0x82d00edf
    +368, 0x5ed7ce07
    +369, 0x1d946abc
    +370, 0x24fa567b
    +371, 0x83ef5ecc
    +372, 0x9001724a
    +373, 0xc4fe48f3
    +374, 0x1e07c25c
    +375, 0xf4d5e65e
    +376, 0xb734f6e9
    +377, 0x327a2df8
    +378, 0x766d59b7
    +379, 0x625e6b61
    +380, 0xe82f32d7
    +381, 0x1566c638
    +382, 0x2e815871
    +383, 0x606514aa
    +384, 0x36b7386e
    +385, 0xcaa8ce08
    +386, 0xb453fe9c
    +387, 0x48574e23
    +388, 0x71f0da06
    +389, 0xa8a79463
    +390, 0x6b590210
    +391, 0x86e989db
    +392, 0x42899f4f
    +393, 0x7a654ef9
    +394, 0x4c4fe932
    +395, 0x77b2fd10
    +396, 0xb6b4565c
    +397, 0xa2e537a3
    +398, 0xef5a3dca
    +399, 0x41235ea8
    +400, 0x95c90541
    +401, 0x50ad32c4
    +402, 0xc1b8e0a4
    +403, 0x498e9aab
    +404, 0xffc965f1
    +405, 0x72633485
    +406, 0x3a731aef
    +407, 0x7cfddd0b
    +408, 0xb04d4129
    +409, 0x184fc28e
    +410, 0x424369b0
    +411, 0xf9ae13a1
    +412, 0xaf357c8d
    +413, 0x7a19228e
    +414, 0xb46de2a8
    +415, 0xeff2ac76
    +416, 0xa6c9357b
    +417, 0x614f19c1
    +418, 0x8ee1a53f
    +419, 0xbe1257b1
    +420, 0xf72651fe
    +421, 0xd347c298
    +422, 0x96dd2f23
    +423, 0x5bb1d63e
    +424, 0x32e10887
    +425, 0x36a144da
    +426, 0x9d70e791
    +427, 0x5e535a25
    +428, 0x214253da
    +429, 0x2e43dd40
    +430, 0xfc0413f4
    +431, 0x1f5ea409
    +432, 0x1754c126
    +433, 0xcdbeebbe
    +434, 0x1fb44a14
    +435, 0xaec7926
    +436, 0xb9d9a1e
    +437, 0x9e4a6577
    +438, 0x8b1f04c5
    +439, 0x19854e8a
    +440, 0x531080cd
    +441, 0xc0cbd73
    +442, 0x20399d77
    +443, 0x7d8e9ed5
    +444, 0x66177598
    +445, 0x4d18a5c2
    +446, 0xe08ebf58
    +447, 0xb1f9c87b
    +448, 0x66bedb10
    +449, 0x26670d21
    +450, 0x7a7892da
    +451, 0x69b69d86
    +452, 0xd04f1d1c
    +453, 0xaf469625
    +454, 0x7946b813
    +455, 0x1ee596bd
    +456, 0x7f365d85
    +457, 0x795b662b
    +458, 0x194ad02d
    +459, 0x5a9649b5
    +460, 0x6085e278
    +461, 0x2cf54550
    +462, 0x9c77ea0b
    +463, 0x3c6ff8b
    +464, 0x2141cd34
    +465, 0xb90bc671
    +466, 0x35037c4b
    +467, 0xd04c0d76
    +468, 0xc75bff8
    +469, 0x8f52003b
    +470, 0xfad3d031
    +471, 0x667024bc
    +472, 0xcb04ea36
    +473, 0x3e03d587
    +474, 0x2644d3a0
    +475, 0xa8fe99ba
    +476, 0x2b9a55fc
    +477, 0x45c4d44a
    +478, 0xd059881
    +479, 0xe07fcd20
    +480, 0x4e22046c
    +481, 0x7c2cbf81
    +482, 0xbf7f23de
    +483, 0x69d924c3
    +484, 0xe53cd01
    +485, 0x3879017c
    +486, 0xa590e558
    +487, 0x263bc076
    +488, 0x245465b1
    +489, 0x449212c6
    +490, 0x249dcb29
    +491, 0x703d42d7
    +492, 0x140eb9ec
    +493, 0xc86c5741
    +494, 0x7992aa5b
    +495, 0xb8b76a91
    +496, 0x771dac3d
    +497, 0x4ecd81e3
    +498, 0xe5ac30b3
    +499, 0xf4d7a5a6
    +500, 0xac24b97
    +501, 0x63494d78
    +502, 0x627ffa89
    +503, 0xfa4f330
    +504, 0x8098a1aa
    +505, 0xcc0c61dc
    +506, 0x34749fa0
    +507, 0x7f217822
    +508, 0x418d6f15
    +509, 0xa4b6e51e
    +510, 0x1036de68
    +511, 0x1436986e
    +512, 0x44df961d
    +513, 0x368e4651
    +514, 0x6a9e5d8c
    +515, 0x27d1597e
    +516, 0xa1926c62
    +517, 0x8d1f2b55
    +518, 0x5797eb42
    +519, 0xa90f9e81
    +520, 0x57547b10
    +521, 0xdbbcca8e
    +522, 0x9edd2d86
    +523, 0xbb0a7527
    +524, 0x7662380c
    +525, 0xe7c98590
    +526, 0x950fbf3f
    +527, 0xdc2b76b3
    +528, 0x8a945102
    +529, 0x3f0a1a85
    +530, 0xeb215834
    +531, 0xc59f2802
    +532, 0xe2a4610
    +533, 0x8b5a8665
    +534, 0x8b2d9933
    +535, 0x40a4f0bc
    +536, 0xaab5bc67
    +537, 0x1442a69e
    +538, 0xdf531193
    +539, 0x698d3db4
    +540, 0x2d40324e
    +541, 0x1a25feb2
    +542, 0xe8cc898f
    +543, 0xf12e98f5
    +544, 0xc03ad34c
    +545, 0xf62fceff
    +546, 0xdd827e1e
    +547, 0x7d8ccb3b
    +548, 0xab2d6bc1
    +549, 0xc323a124
    +550, 0x8184a19a
    +551, 0xc3c4e934
    +552, 0x5487424d
    +553, 0xd6a81a44
    +554, 0x90a8689d
    +555, 0xe69c4c67
    +556, 0xbdae02dd
    +557, 0x72a18a79
    +558, 0x2a88e907
    +559, 0x31cf4b5d
    +560, 0xb157772f
    +561, 0x206ba601
    +562, 0x18529232
    +563, 0x7dac90d8
    +564, 0x3a5f8a09
    +565, 0x9f4b64a3
    +566, 0xae373af9
    +567, 0x1d79447c
    +568, 0x2a23684b
    +569, 0x41fb7ba4
    +570, 0x55e4bb9e
    +571, 0xd7619d3e
    +572, 0xc04e4dd8
    +573, 0x8418d516
    +574, 0x2b2ca585
    +575, 0xfa8eedf
    +576, 0x5bafd977
    +577, 0x31974fb0
    +578, 0x9eb6697b
    +579, 0xc8be22f5
    +580, 0x173b126a
    +581, 0x8809becf
    +582, 0x3e41efe1
    +583, 0x3d6cbbb8
    +584, 0x278c81d8
    +585, 0xa6f08434
    +586, 0xa0e6601d
    +587, 0x2fccd88d
    +588, 0x3cbc8beb
    +589, 0x5f65d864
    +590, 0xa1ff8ddf
    +591, 0x609dcb7c
    +592, 0x4a4e1663
    +593, 0xeae5531
    +594, 0x962a7c85
    +595, 0x1e110607
    +596, 0x8c5db5d0
    +597, 0xc7f2337e
    +598, 0xc94fcc9c
    +599, 0xe7f62629
    +600, 0x6c9aa9f8
    +601, 0x2e27fe0e
    +602, 0x4d0dae12
    +603, 0x9eecf588
    +604, 0x977ba3f2
    +605, 0xed0a51af
    +606, 0x3f3ec633
    +607, 0xc174b2ec
    +608, 0x590be8a9
    +609, 0x4f630d18
    +610, 0xf579e989
    +611, 0xe2a55584
    +612, 0xee11edcd
    +613, 0x150a4833
    +614, 0xc0a0535c
    +615, 0xb5e00993
    +616, 0xb6435700
    +617, 0xa98dbff
    +618, 0x315716af
    +619, 0x94395776
    +620, 0x6cbd48d9
    +621, 0xab17f8fc
    +622, 0xa794ffb7
    +623, 0x6b55e231
    +624, 0x89ff5783
    +625, 0x431dcb26
    +626, 0x270f9bf8
    +627, 0x2af1b8d0
    +628, 0x881745ed
    +629, 0x17e1be4e
    +630, 0x132a0ec4
    +631, 0x5712df17
    +632, 0x2dfb3334
    +633, 0xf5a35519
    +634, 0xcafbdac6
    +635, 0x73b6189d
    +636, 0x10107cac
    +637, 0x18c1045e
    +638, 0xbc19bbad
    +639, 0x8b4f05ac
    +640, 0x5830d038
    +641, 0x468cd98a
    +642, 0x5b83a201
    +643, 0xf0ccdd9c
    +644, 0xcb20c4bd
    +645, 0x1ff186c9
    +646, 0xcdddb47f
    +647, 0x5c65ce6
    +648, 0xb748c580
    +649, 0x23b6f262
    +650, 0xe2ba8e5c
    +651, 0x9a164a03
    +652, 0x62d3322e
    +653, 0x918d8b43
    +654, 0x45c8b49d
    +655, 0xce172c6e
    +656, 0x23febc6
    +657, 0x84fdc5b7
    +658, 0xe7d1fd82
    +659, 0xf0ddf3a6
    +660, 0x87050436
    +661, 0x13d46375
    +662, 0x5b191c78
    +663, 0x2cbd99c0
    +664, 0x7686c7f
    +665, 0xcff56c84
    +666, 0x7f9b4486
    +667, 0xefc997fe
    +668, 0x984d4588
    +669, 0xfa44f36a
    +670, 0x7a5276c1
    +671, 0xcfde6176
    +672, 0xcacf7b1d
    +673, 0xcffae9a7
    +674, 0xe98848d5
    +675, 0xd4346001
    +676, 0xa2196cac
    +677, 0x217f07dc
    +678, 0x42d5bef
    +679, 0x6f2e8838
    +680, 0x4677a24
    +681, 0x4ad9cd54
    +682, 0x43df42af
    +683, 0x2dde417
    +684, 0xaef5acb1
    +685, 0xf377f4b3
    +686, 0x7d870d40
    +687, 0xe53df1c2
    +688, 0xaeb5be50
    +689, 0x7c92eac0
    +690, 0x4f00838c
    +691, 0x91e05e84
    +692, 0x23856c80
    +693, 0xc4266fa6
    +694, 0x912fddb
    +695, 0x34d42d22
    +696, 0x6c02ffa
    +697, 0xe47d093
    +698, 0x183c55b3
    +699, 0xc161d142
    +700, 0x3d43ff5f
    +701, 0xc944a36
    +702, 0x27bb9fc6
    +703, 0x75c91080
    +704, 0x2460d0dc
    +705, 0xd2174558
    +706, 0x68062dbf
    +707, 0x778e5c6e
    +708, 0xa4dc9a
    +709, 0x7a191e69
    +710, 0xc084b2ba
    +711, 0xbb391d2
    +712, 0x88849be
    +713, 0x69c02714
    +714, 0x69d4a389
    +715, 0x8f51854d
    +716, 0xaf10bb82
    +717, 0x4d5d1c77
    +718, 0x53b53109
    +719, 0xa0a92aa0
    +720, 0x83ecb757
    +721, 0x5325752a
    +722, 0x114e466e
    +723, 0x4b3f2780
    +724, 0xa7a6a39c
    +725, 0x5e723357
    +726, 0xa6b8be9b
    +727, 0x157c32ff
    +728, 0x8b898012
    +729, 0xd7ff2b1e
    +730, 0x69cd8444
    +731, 0x6ad8030c
    +732, 0xa08a49ec
    +733, 0xfbc055d3
    +734, 0xedf17e46
    +735, 0xc9526200
    +736, 0x3849b88a
    +737, 0x2746860b
    +738, 0xae13d0c1
    +739, 0x4f15154f
    +740, 0xd65c3975
    +741, 0x6a377278
    +742, 0x54d501f7
    +743, 0x81a054ea
    +744, 0x143592ba
    +745, 0x97714ad6
    +746, 0x4f9926d9
    +747, 0x4f7ac56d
    +748, 0xe87ca939
    +749, 0x58b76f6f
    +750, 0x60901ad8
    +751, 0x3e401bb6
    +752, 0xa058468e
    +753, 0xc0bb14f6
    +754, 0x2cb8f02a
    +755, 0x7c2cf756
    +756, 0x34c31de5
    +757, 0x9b243e83
    +758, 0xa5c85ab4
    +759, 0x2741e3b3
    +760, 0x1249000e
    +761, 0x3fc4e72b
    +762, 0xa3e038a2
    +763, 0x952dd92c
    +764, 0x2b821966
    +765, 0xfa81b365
    +766, 0x530919b9
    +767, 0x4486d66f
    +768, 0xccf4f3c1
    +769, 0xa8bddd1d
    +770, 0xcc295eb9
    +771, 0xfccbe42f
    +772, 0x38bacd8d
    +773, 0x2261854f
    +774, 0x56068c62
    +775, 0x9bdaeb8
    +776, 0x555fa5b6
    +777, 0x20fe615e
    +778, 0x49fb23d3
    +779, 0xd093bad6
    +780, 0x54919e86
    +781, 0x7373eb24
    +782, 0xfbaa7a98
    +783, 0x5f62fb39
    +784, 0xe03bc9ec
    +785, 0xa5074d41
    +786, 0xa1cefb1
    +787, 0x13912d74
    +788, 0xf6421b8
    +789, 0xfcb48812
    +790, 0x8f1db50b
    +791, 0xc1654b87
    +792, 0x948b43c2
    +793, 0xf503ef77
    +794, 0x117d891d
    +795, 0x5493ffa
    +796, 0x171313b1
    +797, 0xa4b62e1e
    +798, 0x77454ea6
    +799, 0xbea0aff0
    +800, 0x13c36389
    +801, 0xe3b60bac
    +802, 0xa176bed3
    +803, 0x2863d428
    +804, 0xe2314f46
    +805, 0xa85cd3d4
    +806, 0x7866e57
    +807, 0x8f03f5bc
    +808, 0x239ae
    +809, 0x46f279fb
    +810, 0xcca00559
    +811, 0xaa07a104
    +812, 0x89123d08
    +813, 0x2e6856ba
    +814, 0x43a9780d
    +815, 0x676cff25
    +816, 0x6744b87d
    +817, 0xee260d4f
    +818, 0xb98d8b77
    +819, 0x9b0ca455
    +820, 0x659f6fe
    +821, 0x28d20d1c
    +822, 0x601f2657
    +823, 0xdec3073e
    +824, 0x61263863
    +825, 0x1a13435a
    +826, 0x27497d1e
    +827, 0x17a8458e
    +828, 0xdddc407d
    +829, 0x4bb2e8ac
    +830, 0x16b2aedb
    +831, 0x77ccd696
    +832, 0x9d108fcd
    +833, 0x25ad233e
    +834, 0xaa9bc370
    +835, 0xa873ab50
    +836, 0xaf19c9d9
    +837, 0x696e1e6b
    +838, 0x1fdc4bf4
    +839, 0x4c2ebc81
    +840, 0xde4929ed
    +841, 0xf4d0c10c
    +842, 0xb6595b76
    +843, 0x75cbb1b3
    +844, 0xbcb6de49
    +845, 0xe23157fd
    +846, 0x5e596078
    +847, 0xa69b0d29
    +848, 0x2118a41
    +849, 0x7088c16
    +850, 0xc75e1e1
    +851, 0x6a4af2d6
    +852, 0xf19c6521
    +853, 0xaff7b3b1
    +854, 0x615295c7
    +855, 0xbda3a8d7
    +856, 0x5b5ca72e
    +857, 0xdad9d80f
    +858, 0xfa81c084
    +859, 0xf4703fa
    +860, 0x3ca54540
    +861, 0xa8961d51
    +862, 0x53d1ecc2
    +863, 0x808d83b6
    +864, 0x68e8c48e
    +865, 0x89be2039
    +866, 0x9088ea11
    +867, 0xb8665d12
    +868, 0x91272f9
    +869, 0x53dddff2
    +870, 0xb7a54ab
    +871, 0xd2b645ca
    +872, 0x99fb8590
    +873, 0x5315c8e
    +874, 0x2a913806
    +875, 0x7f15eb2b
    +876, 0xa7f1cc5d
    +877, 0xbb2ee836
    +878, 0xd9fafd60
    +879, 0x17448d6f
    +880, 0x999ec436
    +881, 0x482ec606
    +882, 0x9b403c0e
    +883, 0x569eb51b
    +884, 0xb275d1a6
    +885, 0xadd29c31
    +886, 0xb7ebdb15
    +887, 0xdfef3662
    +888, 0x51aba6db
    +889, 0x6d41946d
    +890, 0x77bf8896
    +891, 0xcafa6fab
    +892, 0x976ab40f
    +893, 0x49a6d86b
    +894, 0x56639e55
    +895, 0x9945b996
    +896, 0x81459b50
    +897, 0xbce97542
    +898, 0xe397c9c9
    +899, 0x247a5955
    +900, 0xb72b1573
    +901, 0x86306f86
    +902, 0x34f65dc5
    +903, 0x909360c0
    +904, 0xf3f696ef
    +905, 0xcb9faae5
    +906, 0x93daecd9
    +907, 0xde1af7af
    +908, 0x43a1f2d
    +909, 0x6d75cde5
    +910, 0x9e412b6
    +911, 0x5673fed
    +912, 0x16bb511a
    +913, 0x35ef4cca
    +914, 0x4e615aca
    +915, 0x5cdaf47a
    +916, 0x26676047
    +917, 0x8c199325
    +918, 0x2adf0cb9
    +919, 0x84f2e6fd
    +920, 0x5e627f64
    +921, 0xb7cee354
    +922, 0x542ab4a6
    +923, 0xe59cd83b
    +924, 0x89cc3f10
    +925, 0x92b0f5f
    +926, 0xc1328370
    +927, 0x8208d9f7
    +928, 0x68eb00cf
    +929, 0xfadd4ac4
    +930, 0x2517784f
    +931, 0x4042b99
    +932, 0x75ce0230
    +933, 0x97c5a1b4
    +934, 0x1a97f709
    +935, 0x4c62781e
    +936, 0xf530a83
    +937, 0x75776413
    +938, 0x321c7240
    +939, 0x6afe4e36
    +940, 0xad00a2b4
    +941, 0xbc05477d
    +942, 0xb0911e80
    +943, 0x9935b87d
    +944, 0xd535eec5
    +945, 0x149af45e
    +946, 0x786934b0
    +947, 0xbc13cdac
    +948, 0x208bfa2e
    +949, 0xcf4b39cc
    +950, 0x6ac6c172
    +951, 0xbfa9a37
    +952, 0x42d28db6
    +953, 0x2bf1ea63
    +954, 0xbed6e677
    +955, 0x50325d27
    +956, 0xa79d3b8b
    +957, 0x52448bb1
    +958, 0xefaad1bd
    +959, 0x833a2e54
    +960, 0xd9de549a
    +961, 0x9f59672f
    +962, 0x9d5f5f16
    +963, 0x1c914489
    +964, 0xc08fa058
    +965, 0xb188698b
    +966, 0xdc4672b5
    +967, 0x594f720e
    +968, 0x56ed428f
    +969, 0x9b0898af
    +970, 0x8a64d3d5
    +971, 0x773308d6
    +972, 0x84d62098
    +973, 0x46da7cf9
    +974, 0x1114eae7
    +975, 0xf9f2a092
    +976, 0x5363a28
    +977, 0xf2db7b3a
    +978, 0x102c71a9
    +979, 0xe8e76aaf
    +980, 0x77a97b3b
    +981, 0x77b090d
    +982, 0x1099620e
    +983, 0xa6daaae6
    +984, 0x86ff4713
    +985, 0xc0ef85b8
    +986, 0xf621d409
    +987, 0xfd1561e2
    +988, 0x4bcc687d
    +989, 0x596f760
    +990, 0x7c8819f9
    +991, 0x8cb865b8
    +992, 0xadea115a
    +993, 0x56609348
    +994, 0xb321ac14
    +995, 0x1bac7db2
    +996, 0x5fe6ee2
    +997, 0xe9bfe072
    +998, 0x15549e74
    +999, 0xad8c191b
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/data/pcg64-testset-1.csv b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/data/pcg64-testset-1.csv
    new file mode 100644
    index 0000000000000000000000000000000000000000..0c8271fab6dff757e0b94443be8bc0e8b5f1e047
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/data/pcg64-testset-1.csv
    @@ -0,0 +1,1001 @@
    +seed, 0xdeadbeaf
    +0, 0x60d24054e17a0698
    +1, 0xd5e79d89856e4f12
    +2, 0xd254972fe64bd782
    +3, 0xf1e3072a53c72571
    +4, 0xd7c1d7393d4115c9
    +5, 0x77b75928b763e1e2
    +6, 0xee6dee05190f7909
    +7, 0x15f7b1c51d7fa319
    +8, 0x27e44105f26ac2d7
    +9, 0xcc0d88b29e5b415
    +10, 0xe07b1a90c685e361
    +11, 0xd2e430240de95e38
    +12, 0x3260bca9a24ca9da
    +13, 0x9b3cf2e92385adb7
    +14, 0x30b5514548271976
    +15, 0xa3a1fa16c124faf9
    +16, 0xf53e17e918e45bb6
    +17, 0x26f19faaeb833bfc
    +18, 0x95e1d605730cce1b
    +19, 0xa7b520c5c093c1aa
    +20, 0x4b68c010c9b106a3
    +21, 0x25e19fe91df703f0
    +22, 0x898364bb0bf593cb
    +23, 0x5bd6ab7dbaa125db
    +24, 0xd1fe47f25152045c
    +25, 0x3bb11919addf2409
    +26, 0x26a8cb7b3f54af8
    +27, 0xe6a27ee11200aa24
    +28, 0x7cb585ab01e22000
    +29, 0x78e60028676d2ef3
    +30, 0x5c32535e5a899528
    +31, 0x83e8b6f8c4a46fb3
    +32, 0xe56ef7668a161246
    +33, 0x36dcbc15aeb73055
    +34, 0x5ea247f0bd188acb
    +35, 0x438b547b84601a80
    +36, 0x8acda2a1273e9e3d
    +37, 0x2b05e30a4b40c24c
    +38, 0xfd87236bd13af032
    +39, 0x471df211d8d985ef
    +40, 0x18e8a5609a793292
    +41, 0x46f0951fab6dc4e3
    +42, 0x6c199c4e700f6795
    +43, 0xf04aa16bfb7d22cb
    +44, 0xd763d269fbaffc89
    +45, 0x9991930cefbe5c2b
    +46, 0xb2a11b953f824c96
    +47, 0x63fd9f52172c44b0
    +48, 0x183bdad907b1d848
    +49, 0xe17953cddb931c52
    +50, 0x515cf16726ec205a
    +51, 0x88c327605150711a
    +52, 0xc7090dd79cbc8dc3
    +53, 0xcb487cedeb00a350
    +54, 0xc8abf254d87b657
    +55, 0xd43cc4cbfb493d1a
    +56, 0x8705452e5d9ed1e
    +57, 0xcecd11446769cf43
    +58, 0xde72156c8d65bc69
    +59, 0x796a8f0f47d52ee8
    +60, 0xb4c0da443917d6c3
    +61, 0xe07ad7568a8e3dc3
    +62, 0xc24a8da39ce6dc21
    +63, 0x92b21ea80a8556eb
    +64, 0x572f21e531edf3af
    +65, 0x9b917ed56bbed198
    +66, 0xe65fd8ddc5ab3d7d
    +67, 0xf55a80a8ec84fa18
    +68, 0x18fc22e1a5227b61
    +69, 0x72305dc7eeaa79d3
    +70, 0x47ce58a36e7592cf
    +71, 0x14c6374340c0f7cc
    +72, 0x6f98273d4eb5a2c
    +73, 0x59a8702c46fe8f8a
    +74, 0xb67cbd8113cfe57f
    +75, 0xaa03c5db5f5b7690
    +76, 0x3fb0f77ea4568013
    +77, 0x756530990398b26e
    +78, 0x4c1952b2a3a6a343
    +79, 0x1da15c5383074582
    +80, 0xb405b21c81c274f7
    +81, 0xbe664677a16788b
    +82, 0x9d2e37550bcee656
    +83, 0x8b4589f0d9defe02
    +84, 0x2935f018ee06a59
    +85, 0x3834bf88be97ed11
    +86, 0xa610d049cea79b6d
    +87, 0xd49ffc0d09a59ea9
    +88, 0x4073365b76567adf
    +89, 0x499eefb9bb7513e2
    +90, 0x74a743ee6b0138a9
    +91, 0x3bf0880f2d947594
    +92, 0x555d1c0498600a99
    +93, 0x923b32a88ef2ffa4
    +94, 0x7325411065fbedea
    +95, 0x9f4129ff8b79d300
    +96, 0xab2b0a9b8a3785dc
    +97, 0x11734bdfba3a1713
    +98, 0xc8333398841ba585
    +99, 0xee2409cc234e6742
    +100, 0xf6638e700872ecd2
    +101, 0x10875300c13cd284
    +102, 0x27a9bbed7c15b2d3
    +103, 0x3c87f8fef31ce9bd
    +104, 0x92be263cd0914a95
    +105, 0xa7b0f11bc742307e
    +106, 0x4a56f788cc1c1a3c
    +107, 0x4a130fa32257a48b
    +108, 0x5d4d9eda16e90286
    +109, 0x7cc2af564844bedc
    +110, 0x2532867bfe7cda1a
    +111, 0xb1c504676611fd17
    +112, 0xce8e86cfb4189aee
    +113, 0x99685898980d1970
    +114, 0x8c3b67db23bcf1e
    +115, 0x73e14c93905b135f
    +116, 0xf0271b64ac2bd4d3
    +117, 0xf4beba82f3ec1b2d
    +118, 0x1cdbf3ee9f210af
    +119, 0x2e938557c09c3ea6
    +120, 0x2d314ccfa6ffd81d
    +121, 0x31ad47079950ade4
    +122, 0x342b27547b900872
    +123, 0x171b0e20b9ef1a76
    +124, 0xdf10ce6318b03654
    +125, 0x1d625df4aa718897
    +126, 0x8712715a9f6e02ec
    +127, 0xb4a072da725bca3b
    +128, 0x19d346cb7734bd42
    +129, 0xfd4281d311cb2958
    +130, 0x58274c9519fc8789
    +131, 0x4cacf29d885fd544
    +132, 0x784b14d1c2523b80
    +133, 0x2d25242131bb2373
    +134, 0xcd2a5e43a7d9abf9
    +135, 0x15eda3806e650ecb
    +136, 0xdaac5e277d764d96
    +137, 0xdc5a5dd59aaa94e0
    +138, 0x40d00237a46d5999
    +139, 0x6205dd35a692743f
    +140, 0xbbd8236740361f09
    +141, 0x1625c9f4e7288bf9
    +142, 0xb74f12df1479e3ce
    +143, 0xb2d72a51b43d7131
    +144, 0xf006a324b3707c83
    +145, 0x28e8ab4abe7655b8
    +146, 0xfb480093ad7ab55
    +147, 0x3f8abd0d6ff8d272
    +148, 0xc81a94177ac26bb7
    +149, 0x3cdc178307751b14
    +150, 0x9de84cc2b10ba025
    +151, 0x3f8ab5aefcd046e2
    +152, 0x43bdb894e1ee83b2
    +153, 0xe288a40f3f06ac9d
    +154, 0xdab62a7d04b4f30f
    +155, 0x49f4e20295e1a805
    +156, 0x3643764805e0edef
    +157, 0x9449954618b6b
    +158, 0x6c87e0d4508e0ce0
    +159, 0x3a334be688a9dd7b
    +160, 0xb35c39228776e499
    +161, 0xc4118bfff938490e
    +162, 0x88cbde3dcbb034b2
    +163, 0xf91b287793c417c3
    +164, 0x42b15f731a59f5b3
    +165, 0xffa27104bbe4814d
    +166, 0x1b6789d138beccde
    +167, 0x542c2c1440d0ceb9
    +168, 0x367294504d18fa0d
    +169, 0xf918b60e804a1b58
    +170, 0xd390964e33a9d0e3
    +171, 0x23bb1be7c4030fe8
    +172, 0x9731054d039a8afb
    +173, 0x1a6205026b9d139b
    +174, 0x2fa13b318254a07e
    +175, 0x69571de7d8520626
    +176, 0x641a13d7c03332b7
    +177, 0x76a6237818f7a441
    +178, 0x4e77860d0c660d81
    +179, 0x4441448a1c1cbdb2
    +180, 0xccd7783a042046e5
    +181, 0xf620d8e0805e3200
    +182, 0x7de02971367fdd0c
    +183, 0x539c263c5914cab1
    +184, 0x9c3b9ba1a87bbf08
    +185, 0x6d95baa34cda215f
    +186, 0x2db3f83ace0bac5f
    +187, 0x7f5af1da2dc670a4
    +188, 0xfcc098d16c891bfb
    +189, 0x81a33df1d7a5ab12
    +190, 0x767b0f863c8e9882
    +191, 0x7a92983830de483d
    +192, 0xfa7598c37a79ac25
    +193, 0xb89b3ca42ce03053
    +194, 0x457a542b8efed4f7
    +195, 0x571b7737fd0eeda7
    +196, 0xa0f59e524485c0a
    +197, 0x82dca766b7901efd
    +198, 0xa68243caf6a3bd5d
    +199, 0x1bac981c6c740e5e
    +200, 0xbcd51bedf9103e44
    +201, 0x4e197efd3ae5a7bf
    +202, 0x523568efd782268b
    +203, 0x5ec4ef1191fef09
    +204, 0xed751ed5e31c9ab
    +205, 0x44eac24de03e1b29
    +206, 0x9237d57c011d3fb3
    +207, 0xa8c6da0f7692f235
    +208, 0x9f9eb6bc15d6cac7
    +209, 0x34bb8e0c93427aad
    +210, 0x115febd738eaac4a
    +211, 0xa439991ed139d27a
    +212, 0x45c7c2633d8710a2
    +213, 0x48b7475f3405a3ce
    +214, 0x80158497c77bd00b
    +215, 0x935c316a5b1657cb
    +216, 0x59c5d54440e9695e
    +217, 0x337c78c5b3d0ede2
    +218, 0x8c46bb956b93790d
    +219, 0xbf1dd03e471d71c5
    +220, 0x2d375e90a4bef583
    +221, 0xd0365428331b3790
    +222, 0xfcd3969ac827ecd4
    +223, 0x392fb6c580498410
    +224, 0x6d6db4ceab5ea6c0
    +225, 0x9bf84f1972e24786
    +226, 0x798dfd820959dcc5
    +227, 0x2e425095e65e8bfb
    +228, 0x8c1aa11536b1c9c3
    +229, 0xd28e2ef9b12f6f74
    +230, 0x86583bc98c8f78d2
    +231, 0x489877530e3f93e7
    +232, 0xb1d9430631104a15
    +233, 0x1814f6098e6263bd
    +234, 0x8e2658a4e0d4cd53
    +235, 0x5afe20e2531cdb2a
    +236, 0x30d02f7c4755c9bf
    +237, 0xe1e217cda16ed2d2
    +238, 0xccb4913a42e3b791
    +239, 0xfff21363ac183226
    +240, 0xe788690bbda147a7
    +241, 0x76905cf5917bfc6a
    +242, 0x2a8fa58f7916f52c
    +243, 0xf903c0cc0357815a
    +244, 0x15d20f243a4998d2
    +245, 0x5b7decee5a86ea44
    +246, 0x114f7fc421211185
    +247, 0x328eb21715764c50
    +248, 0xaffaa3f45c0678fd
    +249, 0x2579e6ef50378393
    +250, 0x7610ab7743c19795
    +251, 0xf9923d2bd101b197
    +252, 0x57e42e7a62ba7e53
    +253, 0x9f1dc217b4f02901
    +254, 0x88a9ebd86509b234
    +255, 0x867fc926aecc8591
    +256, 0xaf22c1bfef04c718
    +257, 0x39f701f0313f4288
    +258, 0x6171ad397e6faab2
    +259, 0x239bb5b9abdec4fc
    +260, 0xd9a591e25dd01c6e
    +261, 0x826dc4a75b628e49
    +262, 0xf112b152c408f47
    +263, 0x6843a06110f86c0
    +264, 0x965e56a7185c1332
    +265, 0x8d84492edbc71710
    +266, 0xeee8ec111cfd1319
    +267, 0xf2858e94ad98e458
    +268, 0xbc9589fdf5f3a97e
    +269, 0xaf0ceef3bc375130
    +270, 0x48f4aaf13fa75c1e
    +271, 0x111e9db47bee758f
    +272, 0xea3171df130164ba
    +273, 0x2a7bbe30bf827ab6
    +274, 0xc516c3fdbf758c35
    +275, 0xec55097754b04be5
    +276, 0x374a997d52b6d3e6
    +277, 0x487df5456085ffbc
    +278, 0x528883b84df8eafe
    +279, 0x805f77ab5ba26f86
    +280, 0x8eb81477dc04f213
    +281, 0x471ea08ec6794d72
    +282, 0x69d3667ecc4d2176
    +283, 0x98b7b6e295548a66
    +284, 0x3877713c173f8f2
    +285, 0xa00542570d0e8de3
    +286, 0xf534b1bfa4033e50
    +287, 0x7e1fedeac8bf6b26
    +288, 0x8043f37c89628af4
    +289, 0x1dd7039ec295e86d
    +290, 0xce9c05b763a40cc4
    +291, 0x246926481e61028f
    +292, 0xb7cb0f1babf5893b
    +293, 0xefe6b777f37fc63e
    +294, 0xebbcabb4cb35cdcb
    +295, 0x39fa63cd711eeea9
    +296, 0xad5d3ba7aaf30c8d
    +297, 0x8e9e78fe46021990
    +298, 0xc7eaef6e7d5a3c62
    +299, 0xefccdd5495d3f386
    +300, 0x2179557ee8cfc76a
    +301, 0x88a77f621f0885ce
    +302, 0xafda62674543d90c
    +303, 0xb8e6fbe2e13e56c0
    +304, 0x8bfbbe26a14f9b1a
    +305, 0x1404f59f5851f8c3
    +306, 0x1140c53a0489566d
    +307, 0x3edf2d138b5c3f1d
    +308, 0x75d6bb275d817dc
    +309, 0x8e660ae27107664e
    +310, 0x7a8021038ee303e1
    +311, 0x2042ef5eefa9079f
    +312, 0xe3e7b90bbf6d457a
    +313, 0xf3f819d2bb9405b
    +314, 0x522e42155cae0c10
    +315, 0xf5bfbb975b40e233
    +316, 0x2cf82b614dd95cfa
    +317, 0x183ef4a96bc40e55
    +318, 0x9f6e351c5ba4e752
    +319, 0x37c1110683c90846
    +320, 0x1d89b7a996d8a977
    +321, 0x18a444f77c7cb4d9
    +322, 0xd0a8a971b78dc893
    +323, 0x860232fb9e6543f1
    +324, 0x60b6097f51002555
    +325, 0xca1e5214123e3894
    +326, 0xe03fe695c95f99bb
    +327, 0x2c7c6779d5f03622
    +328, 0xafeeee42f63055d1
    +329, 0x670dde905515936a
    +330, 0x9a922f42b59fb094
    +331, 0xddb5ff49af5a651a
    +332, 0xe61b04c9e58ebbf8
    +333, 0x4e459dcf272e7fc4
    +334, 0xd549e92c16adceeb
    +335, 0x7a17dba1299d4a9c
    +336, 0x825d756109f2b585
    +337, 0xba142e61a9cb203e
    +338, 0xc2a19f00e9c04a30
    +339, 0x2d0f8140d23d0652
    +340, 0x8b866d4d4d6caaf4
    +341, 0x4f11d90dd91f8217
    +342, 0xf6efc37373b9e0d
    +343, 0x248493d6cd6a4736
    +344, 0xd12b6ae74a951a3e
    +345, 0x56e34722070b70a7
    +346, 0x22d3f201cc9fa0eb
    +347, 0xbfdcc320008291b7
    +348, 0x1a7a6922e9204fbd
    +349, 0x831421e0c4945ae4
    +350, 0x66316feddddf0e11
    +351, 0xa8c86a1517456554
    +352, 0x14a9049ad989e335
    +353, 0x837022259f141ecd
    +354, 0xcb71793a06c261f7
    +355, 0x4aeefc07ebe09a79
    +356, 0x8982f15aa3b6594b
    +357, 0x67bccfa7ed9b0d5b
    +358, 0xb377463b523e9dec
    +359, 0x53d3d594870fecb7
    +360, 0xa5274b1caec5a60a
    +361, 0xd6316d0cb643db39
    +362, 0xabc1a9b536de88ce
    +363, 0xed2fdb1383d2a077
    +364, 0x12319c6feb97221b
    +365, 0x7e0f6cd40ef47403
    +366, 0x86135c84fe26dbf8
    +367, 0xc96622d3fbbee19b
    +368, 0xe3989d8d8511573f
    +369, 0x42cc365554d1fdc7
    +370, 0x4c1a1eb8bbce8b4f
    +371, 0xfc4e30e7ef2034c1
    +372, 0xc490444317a91e76
    +373, 0x7ccdf469ff5dc81c
    +374, 0xf5a0da4110cc09d7
    +375, 0x505227baf34c0fb5
    +376, 0xbe58737e8a35cc88
    +377, 0xd449bee91b3e8c41
    +378, 0x3e590e23299d0e6
    +379, 0x291a7d9e0a64caf7
    +380, 0xdc6fafbdfebd2293
    +381, 0x8223f1e259fe8a65
    +382, 0x6186fbc9efd9e3df
    +383, 0xfda39b07e4007ffb
    +384, 0xfc19aea98574dc02
    +385, 0xd0e10d354fcacd8c
    +386, 0xc9619916544a55a5
    +387, 0xd454d50a8c8558cd
    +388, 0xcd94a246712d91e
    +389, 0x76a771f5d1231cce
    +390, 0xdd20cb2b7b370ee5
    +391, 0xa6f4f50feca57c49
    +392, 0x78c8fb431f17ab9c
    +393, 0x1b692b79a59b43cc
    +394, 0x4c45045d287da7e6
    +395, 0x522132e18bf43928
    +396, 0x25c458983138b41c
    +397, 0x2a1fb426ef229796
    +398, 0x74dc324c74e5dd3d
    +399, 0x6df75e3eb6eb5374
    +400, 0xb63f2f4f9ca25b61
    +401, 0xac72286112ee54d6
    +402, 0x5a966f3d0a6863c4
    +403, 0x8d7046bc64a46fc2
    +404, 0xa7b740fd6e3087eb
    +405, 0xcdbcbe0340cfcdf5
    +406, 0xcb632613bf312b65
    +407, 0xa91b3f2c2aac238b
    +408, 0xa06deb3f5ae555a3
    +409, 0x29d72e1f8db69
    +410, 0x2d004bae09728ea6
    +411, 0xc6eee5dce0736cc1
    +412, 0xa7493145500ff60f
    +413, 0xc4d68c4aa18ab93c
    +414, 0x8210c29e79d48d7f
    +415, 0xd0999d7889ecbef6
    +416, 0x6e3bd61e66e93566
    +417, 0xe6cc13d47d7d7b1f
    +418, 0x3d6f181f42e03979
    +419, 0xbed4e14fd867604a
    +420, 0xbe511c84067bd86d
    +421, 0x49a876d89e697d38
    +422, 0xc04c3dde8f889c98
    +423, 0xaf293eeab0f53e3f
    +424, 0x9f6291dd65732cd6
    +425, 0xd7811ac01de78c01
    +426, 0xe385cf0261d50ec2
    +427, 0x5a64134b3542bbf
    +428, 0xf9d1302bc6f13a68
    +429, 0x5d2aabbea37d8c31
    +430, 0xd9842e99a5192970
    +431, 0x713eadc4cd30e837
    +432, 0xb7b002fc72abb413
    +433, 0x276cfeea526af1cf
    +434, 0x8519fe79b633a0ce
    +435, 0x2f0e87363705a3e2
    +436, 0x9adbac0be3c371e7
    +437, 0xf3f44ba899a6173c
    +438, 0x782d6c29618fde2b
    +439, 0x7f61062acec408f
    +440, 0x6e79cd836359258f
    +441, 0x5c8e9b138df5785a
    +442, 0xa54359c9f39a9a84
    +443, 0xeec3f033135084b0
    +444, 0x883ee717787a535c
    +445, 0x9a2422b513a73b00
    +446, 0x2dd4beddcdd64a58
    +447, 0x90c8a13202239c7b
    +448, 0x85b352ab759646d9
    +449, 0x139f5cb2e46c53aa
    +450, 0xe1d3ba6c721c66d1
    +451, 0xaa66e0edc4b60a98
    +452, 0x3521275c75be29b6
    +453, 0x490a5190b3edfa5d
    +454, 0xd2abcdd2ccb2f14e
    +455, 0x9d9be8bef4a5857d
    +456, 0xde19676f13ef7755
    +457, 0xdac2fee2e42615f3
    +458, 0xf4239801cb02f2ab
    +459, 0xaa8bf923ed91875c
    +460, 0x61d18a1940e4c7c0
    +461, 0x1eb6aa3d5f077a6d
    +462, 0xee7374c063bf29d8
    +463, 0x2f0a59e34d76268d
    +464, 0xc92e80e17d1eb3e9
    +465, 0xafd05b3ec3d2ca72
    +466, 0x28a61ad8d6c497b8
    +467, 0xa7094d6834ad7d47
    +468, 0x57d80ea9eccbb4f
    +469, 0xb047e0fee6cdaf16
    +470, 0x44f41b5eb48c00bb
    +471, 0xd6dc8e1eb9c8c9ba
    +472, 0x47adfd2c638c7849
    +473, 0x365d63db7d526c68
    +474, 0xc21cda439016135d
    +475, 0x14d10c3f0f98863c
    +476, 0xa93e56f74e037602
    +477, 0x3b4e9c8915bdc9
    +478, 0xb46f5ae155e54aa2
    +479, 0x8e470d21ce1943e1
    +480, 0x60b96301b5ba2e8d
    +481, 0x1b473a41d381f9ff
    +482, 0xabcf5a8e3269e73f
    +483, 0xd410f6e94fb21fa1
    +484, 0x65d1a47eebf87e5e
    +485, 0x48eaa201c61cb843
    +486, 0x212c1abc2499bfc5
    +487, 0x4255ad8377d2d8d
    +488, 0x44caeef472010612
    +489, 0xffae764524f572f2
    +490, 0x78d374d20c9ee550
    +491, 0x6e003206c0511cee
    +492, 0x7998a159145bfb82
    +493, 0x921239650bda1d4d
    +494, 0xae05025509bcfdc5
    +495, 0xc6430c980be407b4
    +496, 0x78524f1744b153f1
    +497, 0x84089e6f468181fe
    +498, 0x8d0d21d7dfb6c254
    +499, 0x90bad90502a33603
    +500, 0x3072a403cbd16315
    +501, 0xdfadddf3f1c040c2
    +502, 0x22f0b0639d9ff975
    +503, 0xb49e48a4cad0765b
    +504, 0x95a0a04f8239709d
    +505, 0x56e147a24a4c481f
    +506, 0xacf16ef61dea4c7e
    +507, 0x424040afd2700de6
    +508, 0xc67e8096a3c717a9
    +509, 0x39f164181dd0a399
    +510, 0x2449cedc1d62198c
    +511, 0x7a53df11a1f1a61c
    +512, 0x5596f1d4a3badae3
    +513, 0x38ed4c822072b3d0
    +514, 0xf07ef346b3fd730a
    +515, 0xfd349c35c3ed51fd
    +516, 0x2f15c9c7890f8f32
    +517, 0x3b470df52b173c29
    +518, 0xd31bfc8981281af7
    +519, 0xbbcc9bdf561215bb
    +520, 0x5782fffea326574f
    +521, 0xb0ebdcfcc5e03290
    +522, 0x7fd89d93d2b3fbef
    +523, 0x280ea1865d9ba2
    +524, 0xe726959845b2c100
    +525, 0xd0361f032cd7dbb1
    +526, 0x3c65ec2028b81a22
    +527, 0x5221e9b2188920bf
    +528, 0xeb5ab27c4125ec20
    +529, 0x80a32dd48b54f0a4
    +530, 0x369b5ced1012bebb
    +531, 0x582d35d76530bc6f
    +532, 0x7b50dc9b48e1e37d
    +533, 0x37fdfe8bbacf8dad
    +534, 0x7a0cb7e6e93840ea
    +535, 0xa1132c870be0b2ce
    +536, 0x9d8ac2c68267cd1a
    +537, 0x470969b647fa7df4
    +538, 0xabcb7d8adf7e2d24
    +539, 0xacdebec9bdf9eb1c
    +540, 0xe30f4cbf7eb6a59
    +541, 0x746673836c4df41d
    +542, 0x75120a6b647bb326
    +543, 0x2f4eab556c3f6878
    +544, 0xd84651ab05405b7a
    +545, 0x9e695808b9622284
    +546, 0xc93b71e56aa6e1a5
    +547, 0x2be7f3be4a7b7050
    +548, 0x6497e910b6733241
    +549, 0xcf7050dfd08076fc
    +550, 0x4e3cc156eca183f7
    +551, 0xf801a33d9326c265
    +552, 0x6aa293c8a47d40e6
    +553, 0x28c429755faa6230
    +554, 0x82b818651f54e7bb
    +555, 0xa84d726d7acdbead
    +556, 0x5cfa535d5774965d
    +557, 0x4a34b7b1cb48d53
    +558, 0x86a7b5bce426de84
    +559, 0xfcd2307cecdb7318
    +560, 0x16dbaaa71181a038
    +561, 0x88e7e8cd261c2547
    +562, 0x3c09ba6d1d5ea913
    +563, 0x5dd3d643734ee5b6
    +564, 0x326d725fe8cbb33
    +565, 0x7bcca9ca2da8e784
    +566, 0x482dcf6b11d7f9a4
    +567, 0x1291b605b4cd3e04
    +568, 0x6988181b50e2f4a8
    +569, 0x649e3c37131fc292
    +570, 0x4eeb67b9e21eba54
    +571, 0xc051d39073dec45f
    +572, 0xc99c52e110270d67
    +573, 0xcb813d5d77868add
    +574, 0x423a5f13573e7ac0
    +575, 0x231ac4cc4fe73616
    +576, 0x4c22b888a6e600ea
    +577, 0x8059a6dc7c9e25c6
    +578, 0x49f498a5b8ad22de
    +579, 0xf1e812cc6d1826c8
    +580, 0xbbaf60abe8b11e00
    +581, 0x1d31d7f4d8be9a6a
    +582, 0xfeadce70a9a10c14
    +583, 0xb47c635bc136996a
    +584, 0xd88e694c8da030cb
    +585, 0xc41bbe132aff1364
    +586, 0x34249ab18a4b0800
    +587, 0xf14b5c825aa736cc
    +588, 0x2710be6b08df78e
    +589, 0x2ab56bcc9bf9e740
    +590, 0x9b7f6e591b5f648
    +591, 0xfb665c3772f34135
    +592, 0x628a0a5d2db5d8d5
    +593, 0xb3e3f251e61b5259
    +594, 0x82310ae33faf1b23
    +595, 0x24af8723a65cbd0b
    +596, 0x671c93282fc4ad97
    +597, 0x6cabeaac77270cad
    +598, 0xef4643fe38b02b7f
    +599, 0x7b011549d1ac6653
    +600, 0xe2af87b9fccfe89
    +601, 0x36b71ad67197ac8a
    +602, 0xdbba55d06f2fd93b
    +603, 0xf571dbd764b7f7e5
    +604, 0x38ea402501cdbd45
    +605, 0xb8ab5b5b1bab2913
    +606, 0xfab973c4d45f32bd
    +607, 0x9364f1717c2636b9
    +608, 0xfad00f4d983e00fe
    +609, 0xc90c532a11aef75a
    +610, 0x64a6eda96e44783c
    +611, 0x35891f2eb84520be
    +612, 0x28d216080caed43
    +613, 0x129629cc5bd206f6
    +614, 0x22c3d39822cbb4b3
    +615, 0xf1efbf4cce1eaa2b
    +616, 0x7070cba12524ed08
    +617, 0xa7ed0be9deabf20d
    +618, 0x8ddb4cd6b454f76b
    +619, 0xb82814b1db37b63
    +620, 0x418e83b36de01876
    +621, 0x9a538c7f39c6413
    +622, 0xee0cd7abf8a2ecb9
    +623, 0xa9222b07e95590f3
    +624, 0x6296a415d68341e6
    +625, 0x981e0a5a8f811929
    +626, 0x4bb372d3b0de283d
    +627, 0xa9805b5971866e16
    +628, 0xaf3b5f5183497657
    +629, 0x2152b0fd23c3d9f
    +630, 0xb730c325b7173180
    +631, 0x1e3439d231608c19
    +632, 0x1c5ba6031379823c
    +633, 0x87f5d12d6d365cbc
    +634, 0xd3bc7f29614bc594
    +635, 0x63102214bb391268
    +636, 0x482bbd5bba648a44
    +637, 0x6a23604690759dc4
    +638, 0x4091d41408d3a39e
    +639, 0x7cd017f922101b15
    +640, 0x7ce9004ac5f9231
    +641, 0x978bc3d8ec7f7fdf
    +642, 0x5bd0c4d780580c11
    +643, 0x4313c068bb040153
    +644, 0x3ab7dab7bc38bf80
    +645, 0x3aaf9c187728deea
    +646, 0x6633a4ce8efb88d9
    +647, 0x7263b089878f00fc
    +648, 0xd0d767e96fe00eb8
    +649, 0x184a7c0c01908028
    +650, 0x1ebdf41e6f76e186
    +651, 0xeb740ee1d0402083
    +652, 0xfccf4974edb1c339
    +653, 0x16e2707aa28306d
    +654, 0x1684f0bdb018c3a5
    +655, 0x887b6b67b88aa862
    +656, 0x923d7810a2bea33a
    +657, 0x56b3560babef5d6b
    +658, 0xb39a14614c54b8c6
    +659, 0x33e4dc545a509fc8
    +660, 0x26e21f84142da9b
    +661, 0xdd07598125756855
    +662, 0x572d49a071d7ae0a
    +663, 0xba3c7e3baea28760
    +664, 0x7ecdb2d714db4b61
    +665, 0x1c62b4920e1b2fe2
    +666, 0x71bfafb70092834a
    +667, 0xd710a4228f60d56a
    +668, 0xeb16277d4ce4e95b
    +669, 0x968168c90b16d3a1
    +670, 0xac3439dfe8ad0062
    +671, 0x5a8226f9dd5876ad
    +672, 0xb843affe917291b0
    +673, 0xd76d1e67051f8259
    +674, 0xb73a6638cce8ccde
    +675, 0xa0e6afd3c7295f9
    +676, 0xff8857b4bbb5f4c6
    +677, 0x99becf78938f0426
    +678, 0xfcd17edc1e70f004
    +679, 0x6223b8b23f2f50
    +680, 0xca875f3e84587b4c
    +681, 0x7d1e81e589f87fb9
    +682, 0x9eb621586aa826fc
    +683, 0xf46fb9ef5b9c2086
    +684, 0x2882c9b7092725f3
    +685, 0x5493f099bbedcd02
    +686, 0x90c1ec979ffa811d
    +687, 0x963f765025bcc53
    +688, 0x56194e3ec3d9d4e9
    +689, 0x7ec4720954cac1f0
    +690, 0xfab3145171af7f90
    +691, 0x52a0b4e41a13b593
    +692, 0x740e2d4d5909d126
    +693, 0x98f5339c09c94a28
    +694, 0x1700e462fe8dec76
    +695, 0x3dbffc2aa4695ac3
    +696, 0x5763edacabdfe2a1
    +697, 0x7b5b623ce49ef21d
    +698, 0x30addc66f49860df
    +699, 0xcc7511a6c31bceda
    +700, 0x1b25b61ca75db43b
    +701, 0x416bc4c298e59046
    +702, 0x4cd11fe2d74e4649
    +703, 0xb54458a9229fc978
    +704, 0x8c21a27882b6ca35
    +705, 0x57887c8b5e01639b
    +706, 0xf4e893da996680bb
    +707, 0x8d601297702c9c0d
    +708, 0x2a27904a30aa53af
    +709, 0x497800f6917ea8d0
    +710, 0xe96db3340ada9c00
    +711, 0xcc23166f14c010ee
    +712, 0x782690d78fa65ec9
    +713, 0xf3e00d74a0878eda
    +714, 0xa7cbb683decca0a3
    +715, 0xdd2e038e683a94aa
    +716, 0xe2096ff8da896ca5
    +717, 0xf7c83400afdabe11
    +718, 0x395b8c6f6a4086a4
    +719, 0x4a164ec05bee71d4
    +720, 0xe87aa5d1ca0462fe
    +721, 0x8dbc5aed6dff9ceb
    +722, 0x12120d1e9552707b
    +723, 0x877dca6889b3e6cd
    +724, 0xbd65605c01e900fb
    +725, 0xbd6b82c4157c3115
    +726, 0x8b60282732caf78a
    +727, 0x279fcf5e5de9e57f
    +728, 0x34b34ebfb6a37eae
    +729, 0xd258cc1a14e03b7b
    +730, 0x9a528ba3db4a13fb
    +731, 0xffa0aea59d057746
    +732, 0x27fa7f456cd37c4e
    +733, 0xe1117a57a6fdce63
    +734, 0xdc8fc903970a1551
    +735, 0x492dd104f30faf29
    +736, 0x110def0959e5652b
    +737, 0x7f8d1997636fdd15
    +738, 0xfb77b05e538a9b59
    +739, 0x2e41fa35b4b01fc6
    +740, 0xbc35ae69a3374085
    +741, 0x192c2a681c2d9b4b
    +742, 0x12566b8866c189d6
    +743, 0x9d88ea785c5185c8
    +744, 0x30a621ad5f983c4
    +745, 0x8b875efe1206f587
    +746, 0x224d25c3af6e3423
    +747, 0x7503e976a1ac7bcc
    +748, 0x3c98aa869e823859
    +749, 0x3d8835304b646892
    +750, 0xf6353330ff970bc2
    +751, 0x8a673f5e2edb8acb
    +752, 0xf2fdcc53493838b9
    +753, 0x85ddcd526236af16
    +754, 0x60afb99814c676c5
    +755, 0x32a1c2749e281ca8
    +756, 0x2367a92ae3bee9ca
    +757, 0x219fe082703743cc
    +758, 0x34d8b74dc85182a9
    +759, 0xdd04164c72db23f
    +760, 0xe293ac28fe2671a9
    +761, 0x9ca7d169cbda6f45
    +762, 0x705c47972b4240ed
    +763, 0xc10eda9eeb536209
    +764, 0xc36ddacd0c94e85d
    +765, 0x8eb592c27e8cd0d2
    +766, 0x3e815991c76e7cc4
    +767, 0xac9cfce31acf7580
    +768, 0xbf7a4cb31c7aee94
    +769, 0x663077444aceecf6
    +770, 0xe7f614ff386eb568
    +771, 0x79d7a229c66912c0
    +772, 0x161ed4311f63e1f3
    +773, 0x308a5faeb9982ede
    +774, 0x7b38ddb9b7efd10
    +775, 0x1e103a2589b27ecf
    +776, 0x67b02baf4259f27e
    +777, 0x868921c115ea2eee
    +778, 0x959791912200f71e
    +779, 0x4dd55f36dec10557
    +780, 0xe3464d90080cb99d
    +781, 0xfb2d4f6accce652f
    +782, 0x109900a9257d77ba
    +783, 0x3c4bda8e2c83684c
    +784, 0xc9ae040fb7f868c6
    +785, 0x78098ffe994f4905
    +786, 0x7a94c33eca77f0b4
    +787, 0xbe6a2a95e9b5c0e8
    +788, 0x797d39cf963f4837
    +789, 0x8d2e249e4425d06d
    +790, 0x6ae2c30cd5da06f4
    +791, 0x904489de762b179f
    +792, 0x84713e2dfb591e3b
    +793, 0x6405a40da3f6f51b
    +794, 0x976b560d663a2df1
    +795, 0xed1c544784ba1e22
    +796, 0xca658e995ed9344c
    +797, 0x2b1c6b8e4db49025
    +798, 0x52b1513da528bad
    +799, 0x3c63406d256d9968
    +800, 0x63a31ca3d423f85e
    +801, 0xb05a81f55789a720
    +802, 0xd04412992c476c8e
    +803, 0x828ec2f77a150a3d
    +804, 0xee50926671bb60c6
    +805, 0x5aa70f93e2df61b4
    +806, 0x94d60fa2e8655858
    +807, 0x3f5e5b770703cc7d
    +808, 0xc62dfb2688ca7784
    +809, 0xaaf02e1e8ba89fe4
    +810, 0x4ab74e0d8c047405
    +811, 0x31ee04fbac6fcead
    +812, 0x1203b78b8228f5af
    +813, 0x412a70836f9aa71a
    +814, 0xab51cf98c03f1819
    +815, 0x783a3ce9ce137f65
    +816, 0x8897085b0a072cf2
    +817, 0x685dd9bde8798cb
    +818, 0x9a1fac7b1705e2c1
    +819, 0xf3e9ff98de48e9cb
    +820, 0x5c2d3eb1a1fbe917
    +821, 0x3bda718b6b54d82e
    +822, 0x29f2dd18f22f0821
    +823, 0xb992da1572ac3597
    +824, 0xacb69e7aa14b34f7
    +825, 0xcd36e3ad14f088d1
    +826, 0x6aaacc96a1ec55e8
    +827, 0xf8ac593f154fe68f
    +828, 0x18fc9cbff012339f
    +829, 0x2f3368ccbbb99899
    +830, 0x7cec7d17f37031f7
    +831, 0x96e86bfaadcb8fc2
    +832, 0x74f9e7ee3d42a752
    +833, 0xbd52f6c7d9b0733
    +834, 0xa48e6d96bb6ce1c9
    +835, 0xaefa058254b82133
    +836, 0xb7a19edfd0929107
    +837, 0x6160ce9125b26e26
    +838, 0x6537dbbde1d2aed
    +839, 0xc567f9a6bec52dde
    +840, 0xca29fd3f22443342
    +841, 0x7732aa6db6a1c476
    +842, 0x8f5a4d7df6b11b3
    +843, 0x76649262aa7e31e1
    +844, 0x60a13eb125fbc829
    +845, 0xc81e4d123dd21ac1
    +846, 0x643cbb09bb72f86b
    +847, 0xf971a98fb25555a6
    +848, 0xffa2774c66692d56
    +849, 0xcb33c16c50b13ea9
    +850, 0xfabf388dffda0e9b
    +851, 0x55d41ec12ca24b9f
    +852, 0x91cf693a3467e807
    +853, 0x6be2c00b2c31d6dd
    +854, 0xc5cf513b5251ae28
    +855, 0xffc4384212403dec
    +856, 0x45d4e1865255a69d
    +857, 0xfb1dcf956972086a
    +858, 0xcae946a55c4c55b8
    +859, 0x7351ac7720e385c1
    +860, 0x19aa8ffd86240254
    +861, 0x8f515ae78f4040da
    +862, 0x1e1ed2058de50fce
    +863, 0x22d006dcdb374243
    +864, 0x6e0f0ede7c95b441
    +865, 0x70e8aa81b53b4d25
    +866, 0x998f309ea41e3814
    +867, 0x89ed6598fb66f390
    +868, 0xb5997dc3278060df
    +869, 0xb2a021eac4f7e046
    +870, 0x3705b60aa2fd0768
    +871, 0xfc415079ab9200e
    +872, 0xf2871ac4cf45ecc9
    +873, 0x24bf758d2246175f
    +874, 0xac503dd6f8141b3
    +875, 0x4e879d12d9f03b3
    +876, 0x82034af8cf93b644
    +877, 0x59899dd7e478a6c7
    +878, 0xae90addb6eb11507
    +879, 0x1524ddf76730cdef
    +880, 0x6fd4afd5456b1c9d
    +881, 0xcddb9221ea001cbc
    +882, 0x64ff400bbf2e8604
    +883, 0x6dda10549b06ed9b
    +884, 0xed2c85104c261527
    +885, 0xc7e09217d29929a8
    +886, 0x56284df611a428b1
    +887, 0x1a7608289c0a61
    +888, 0x7cb63db15166ff66
    +889, 0xc6013c76fcdcdc72
    +890, 0x8e5dd566c7a5a676
    +891, 0x5a8e8565f40d133b
    +892, 0xe465973455848c44
    +893, 0xf92eecbfe0f3c2c0
    +894, 0x7d64155d4dcc5cac
    +895, 0xf17595706f988dad
    +896, 0xd590a001a6a19c5c
    +897, 0x82a164475758db3d
    +898, 0x6b144993ea1bbe32
    +899, 0x22a81a7a6e453779
    +900, 0x8e8c298df1a68a73
    +901, 0x78056afd6d936b4c
    +902, 0xaaceef0325faaf62
    +903, 0xe78bb7699f82266f
    +904, 0x523a2d283c5a5166
    +905, 0x7076d87088f6c6db
    +906, 0x6087dd54cff5aeb2
    +907, 0x7ef82e62cb851680
    +908, 0x4e8bcc8ed84d03d8
    +909, 0xd12fa0361df3cfd3
    +910, 0xefb89c79f8127297
    +911, 0xa9af4e2fbce0b1f8
    +912, 0x462136685b70331e
    +913, 0xe9e74c93da699b77
    +914, 0x9ec69215fb11d0c3
    +915, 0xc10f229939e3e111
    +916, 0x3f67fa79e41d2374
    +917, 0xd5e7c1a9a7185162
    +918, 0xa1dcce9ec91492fe
    +919, 0xd4e61f0727b5d21b
    +920, 0xdf6cdce46551800a
    +921, 0xa3f256ce906982d3
    +922, 0x209742a6b9ffc27
    +923, 0x4006c96958526a57
    +924, 0x9606aebc75a1967e
    +925, 0x91b9f42fb64189df
    +926, 0xb27119defcb938bc
    +927, 0x128cc7a84ba05597
    +928, 0x6c3df613c62d0d30
    +929, 0x3adf69d48b629ec7
    +930, 0xda42ee493837b128
    +931, 0xb8e770480e760bb5
    +932, 0x9feb55d57c99c626
    +933, 0x29812d80afdae3ed
    +934, 0xae4222a64276a8c7
    +935, 0xe3897212a5b4ed53
    +936, 0x98bedfd13886e669
    +937, 0xca858675d7fc0d0e
    +938, 0x28a359f665354234
    +939, 0xfac2ccabe4128b35
    +940, 0x61373cc5d11ca180
    +941, 0x7007605a4512a87a
    +942, 0xe71f8eade7b30b3d
    +943, 0x3a9e77f9b99bd04d
    +944, 0x70d3e42488098866
    +945, 0xd30fc159c7cd4d99
    +946, 0xe4d3f6600d2e2d6f
    +947, 0x1088324dfa955c25
    +948, 0x516437acd4764623
    +949, 0x38a31abe50d0aa03
    +950, 0x72e1054e9dc02ba
    +951, 0xe6971dd664d1a2e2
    +952, 0xf6698cb095d3b702
    +953, 0xad995a5a8c19bd92
    +954, 0x34e53c6936f656e6
    +955, 0x10de240bc07c757a
    +956, 0x3e3b9a6861c2bd1c
    +957, 0x9c0b0b97d3712ec9
    +958, 0xabf1505a75043aed
    +959, 0xbdf93d3de3274179
    +960, 0x28fa5904d3f62c28
    +961, 0xc3b97b39ef6c5133
    +962, 0xf2b2219225b8679d
    +963, 0x8be4ec0f930c0aaa
    +964, 0x47de5a56aa590643
    +965, 0xb6f871b304129856
    +966, 0x80a61c06233ab0f9
    +967, 0x3ce6c3af8101b055
    +968, 0x85b911708274e7d1
    +969, 0x4cab65d093a488b7
    +970, 0xaabc4b10661fe28e
    +971, 0x35b16dea64474a68
    +972, 0x1d6eb5b093361223
    +973, 0xc39107b92f0fe1fb
    +974, 0x1d09e048073c4841
    +975, 0xc6a02f43aca8cb2f
    +976, 0xaf6613dbc7da909c
    +977, 0x5ac2a40c230aa756
    +978, 0x33afb5e7c01c39a5
    +979, 0xc7b0b20ea8b7d0ef
    +980, 0xdf7306c8ccb1bbea
    +981, 0x9710efc0c188b2a0
    +982, 0xd6303eadb72c873e
    +983, 0xa38ca609b118f35a
    +984, 0x8390613065c6e535
    +985, 0xdf9a0106757e431f
    +986, 0x8bcf77039788e143
    +987, 0x6026806a986b378e
    +988, 0x482ff3b1394cb1dc
    +989, 0x2a27d0ccac9ede9c
    +990, 0x53c77f26e271b3ab
    +991, 0x1ba004cf276cf3f
    +992, 0xc135b0517dc81f7c
    +993, 0x5d137838db75e442
    +994, 0x3fe505f93d1dbdd7
    +995, 0x351654ae7d598294
    +996, 0x173f8d182af9d84d
    +997, 0xf97dfcd164fe11c5
    +998, 0xcda423e5ad43b290
    +999, 0xa5cb380b8de10d10
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/data/pcg64-testset-2.csv b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/data/pcg64-testset-2.csv
    new file mode 100644
    index 0000000000000000000000000000000000000000..7c13e3172d0e7798200f58cde9362ad0bd4a22e1
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/data/pcg64-testset-2.csv
    @@ -0,0 +1,1001 @@
    +seed, 0x0
    +0, 0xa30febcfd9c2825f
    +1, 0x4510bdf882d9d721
    +2, 0xa7d3da94ecde8b8
    +3, 0x43b27b61342f01d
    +4, 0xd0327a782cde513b
    +5, 0xe9aa5979a6401c4e
    +6, 0x9b4c7b7180edb27f
    +7, 0xbac0495ff8829a45
    +8, 0x8b2b01e7a1dc7fbf
    +9, 0xef60e8078f56bfed
    +10, 0xd0dbc74d4700374c
    +11, 0xb37868abbe90b0
    +12, 0xdb7ed8bf64e6f5f0
    +13, 0x89910738de7951f
    +14, 0xbacab307c3cfd379
    +15, 0x2cf7c449d8b927a6
    +16, 0xdcf94b3a16db7f0e
    +17, 0x8a9d33d905a8792e
    +18, 0x4cb9eb2014951238
    +19, 0x6c353acf7b26d6f1
    +20, 0x73ff53d673aa30c
    +21, 0x1fd10760015eca68
    +22, 0xabae0aa9021eeba8
    +23, 0xa5ae363a868ee2bb
    +24, 0x9d89e0f041de6631
    +25, 0x6238b133c3991a65
    +26, 0xff49267d75fef51a
    +27, 0xfb180656ce13c53f
    +28, 0xaf7fadf36128712d
    +29, 0xa6847fc6f339c63e
    +30, 0xb03e0b80d71ea5bc
    +31, 0x63905abcb43969af
    +32, 0x2295af3ee00a3bba
    +33, 0xb8b375b994330415
    +34, 0x867d9ef1d8716a3b
    +35, 0x4f6c02f5601b4e18
    +36, 0x7c5fb4c16c470d18
    +37, 0xe3b57986b804b343
    +38, 0xef1d79d212aca692
    +39, 0x5b98774c8806209c
    +40, 0x924fc76bac38a5d1
    +41, 0x5266084c412ddeed
    +42, 0x98240bf9b831d6a3
    +43, 0x5681599e81219442
    +44, 0x6441248fc2ba92bc
    +45, 0xe3e9051a540349ea
    +46, 0x3a2700034390baa3
    +47, 0x9f893155b6d402bc
    +48, 0x158207910c6d8aef
    +49, 0xd5282ab7608c2cbc
    +50, 0xc97f4651669dee4f
    +51, 0x3d4750d95103ed60
    +52, 0xe0614542caac1f04
    +53, 0xefe5092144cfc6c
    +54, 0x560bc486abd7e9ae
    +55, 0x2678b71392daa4b8
    +56, 0x734970d3dc2ba416
    +57, 0xcbdbe849e51e4aaf
    +58, 0x3b0b5e28b491556c
    +59, 0xd51449ac45abd88
    +60, 0x6790b59991f1b7ab
    +61, 0x32d1c039ff2415bc
    +62, 0x173b9772f24f72e0
    +63, 0x9490a9ca9f883b1b
    +64, 0x4c775989e6214222
    +65, 0xac07db37e6ee6114
    +66, 0x331371b2e3f10aee
    +67, 0xf12e5326c21c28e4
    +68, 0x5d77dc280c70d614
    +69, 0x1b01bd17a2f281ec
    +70, 0xa10d3b5882938487
    +71, 0xed5a0033c394ae8f
    +72, 0x70bc8ea568ea44b4
    +73, 0xf4600ae77965e730
    +74, 0x7ff92c0b321ce233
    +75, 0x6cdbc87d0cc1d670
    +76, 0x9ec64f0cf2000eb1
    +77, 0xfebea50259800f68
    +78, 0xf2edf9019a8fd343
    +79, 0x75c584ac042e5468
    +80, 0xc1fa8481d5bf9a1d
    +81, 0x7f57180168514ac2
    +82, 0x878100716b94f81e
    +83, 0xc929406e3af17fd2
    +84, 0x6a26e2c013e4bf4d
    +85, 0xbc071d8848280955
    +86, 0xb60d75abbfd1bdac
    +87, 0xee9b76afeca9fa69
    +88, 0x1d6c399d2f452810
    +89, 0xbaa0bc1621e25c83
    +90, 0xed6ba792f8671ba5
    +91, 0xf7ca02c2ab11d8d7
    +92, 0x3c3cadadf0b21e3
    +93, 0xdd1784571e864e9c
    +94, 0xfb2f992015157509
    +95, 0xf50bb9f0d3ced743
    +96, 0x261565f75c3e185f
    +97, 0xf8fe33b284513e60
    +98, 0xe3d2d10b5e024664
    +99, 0xd28717566242cf35
    +100, 0x7ae07d133ac5b789
    +101, 0x3b7ccaaa53ac338e
    +102, 0xcd480bace4871650
    +103, 0xec6c78f923c080e9
    +104, 0x44211d0ff8919d59
    +105, 0x89f79af76d2a45fe
    +106, 0x71583fd8a837548b
    +107, 0xee57269c261511f5
    +108, 0xa5ee8f3b128c5d1
    +109, 0xbb64c20ed0765a17
    +110, 0x9d4790ab2eeaf7e4
    +111, 0x742f3db806d9e98
    +112, 0xb81ec97aed6a0d1b
    +113, 0x41808b34f6a8a23
    +114, 0xc20913af175dfd4d
    +115, 0x834427db263b22bb
    +116, 0xedd9c632e611828a
    +117, 0x10eac8524496f571
    +118, 0xd76091b97eb00ab7
    +119, 0x111298ae9fe95666
    +120, 0x5824b2e2a6719c43
    +121, 0x6e280ec539e934ed
    +122, 0xf74fd832df90083e
    +123, 0x8fee6d0f241c2e97
    +124, 0x4244f331c2f19c3c
    +125, 0x3dde75a845cce97f
    +126, 0xe35bb8e635a9915b
    +127, 0x39d2943037f7932e
    +128, 0x1fe2d134201d0970
    +129, 0x49d00b63c749b804
    +130, 0x960c2942cd4e4e04
    +131, 0x8dd8e009dbc0435f
    +132, 0xcf493495c3a055cd
    +133, 0x8f7b5a1c0f9fe9cd
    +134, 0x49d5f90374641a25
    +135, 0x69b3932073d3524c
    +136, 0xd170603e7de84ee2
    +137, 0xa062ba3ed3539948
    +138, 0xf5861cc5b5d56c82
    +139, 0x5e914998a30c7e76
    +140, 0x8d77f2ad1503c0f1
    +141, 0x980b6a9e3b4181fb
    +142, 0xd9299cd50694c084
    +143, 0x253dc0f8f1cec4c5
    +144, 0x68110fb9d1b3e695
    +145, 0xe8f3120d0aabc461
    +146, 0xb066e7df0dfb042
    +147, 0xd29ce0f797e6b60b
    +148, 0x6a569bb7ca33bd42
    +149, 0xd46e08b2dc2385f8
    +150, 0x28c61d11d055767
    +151, 0x5d73aa3d1a2bb725
    +152, 0x1421191e1c14829a
    +153, 0xa711bfb6423df35e
    +154, 0x461af97a86308006
    +155, 0xb3e1018ff3519367
    +156, 0xf19cf866a268ef2b
    +157, 0x207715eac9199d1d
    +158, 0xdd621c410975b78c
    +159, 0xf390aea68683610
    +160, 0x617a2d107a0047d9
    +161, 0x6e05ac416e5bebf0
    +162, 0x7d253e70506c1bed
    +163, 0xf9f96f4a7dd53810
    +164, 0xc693b29cb1573f73
    +165, 0x4f1146b0020ea544
    +166, 0x45140608fbd40579
    +167, 0xdcf57219828ce6be
    +168, 0xe19d58cca37b5b32
    +169, 0x82bda95b2a161235
    +170, 0x5823c3d8a2b6c9ba
    +171, 0xfeb2e74092fdf89a
    +172, 0x50e1ad1abc8f869d
    +173, 0x2ec63d0c105eb8da
    +174, 0xe14e1c4845a3264a
    +175, 0xcff53670455eb6aa
    +176, 0xaafaccd24619fa3e
    +177, 0xf55a988486e2422a
    +178, 0xecfba16a90ff4d04
    +179, 0xbf8d36c2f644757a
    +180, 0xdc56ed75a0dd6249
    +181, 0x3f45023eff17c3bb
    +182, 0x2428bbfe90023fab
    +183, 0xab892c611adcb70c
    +184, 0xb6f13d8c0c2b9d74
    +185, 0x2ac3fb11d224f2a8
    +186, 0x65433dcfae2d9351
    +187, 0xe906859ae4b45f82
    +188, 0x8fb7f5f093d76a3b
    +189, 0x940dd290b5e88d1a
    +190, 0x31b27d21bef116e7
    +191, 0x86a964e2c83b5296
    +192, 0x85ffd17bc079a9e8
    +193, 0x16c47c724e7ab7f1
    +194, 0xfb6098a9867e7d7f
    +195, 0x9246fb69092c6cb2
    +196, 0x1a4033572760f32
    +197, 0xc5cc568a8b273b84
    +198, 0xfa6f9f2fbdd44abc
    +199, 0x9701b8e087718ba3
    +200, 0x51d6a7dcf73f8f3a
    +201, 0x30008172cc6a972d
    +202, 0xac2ab49a5ca6ac81
    +203, 0x31f28ef79461e54c
    +204, 0x93e35a8da8cc6132
    +205, 0x9a2c58beeba3d5b9
    +206, 0xf6615c1de266ac39
    +207, 0x127ff9f8166b766b
    +208, 0x7ffe380e80a69556
    +209, 0xbe7d2c228e1542f7
    +210, 0x2d5ebb4e50ba1746
    +211, 0x63585761ae1bf684
    +212, 0x1019eb5cee022fea
    +213, 0xb9d3540ab58da30d
    +214, 0x1677f4cb45620eb9
    +215, 0x6524baee51783822
    +216, 0xdf9f2ddcfabb0adc
    +217, 0x78e8acc43b287935
    +218, 0xe9a1974e999222b5
    +219, 0xc41324ec2291e780
    +220, 0xea52abc9ecdcbc9f
    +221, 0x209d7bcd46ec6b04
    +222, 0x12d504c09803db2e
    +223, 0x1200e6bf21475d81
    +224, 0xde6d3c2b35fd2cfc
    +225, 0xa2526900ac33bd3c
    +226, 0x7f1f5290fc432bc5
    +227, 0x29ddfb380a3d69c8
    +228, 0xac79cb6942a2909d
    +229, 0x516996685b67a92a
    +230, 0xb5fc39041cb828bb
    +231, 0x75d9d8ca0644a276
    +232, 0x81e98b76be92a3e9
    +233, 0xca27888fafe12179
    +234, 0x17be2ae039925765
    +235, 0x9429846c0e6d0342
    +236, 0x327dfd50439815e9
    +237, 0xcee20cd7bc254aeb
    +238, 0x7d250389f453f29e
    +239, 0xfd1b232a85c95569
    +240, 0x2ed55fac80f3e9e9
    +241, 0xf6886c20417a1be7
    +242, 0xcd08e61f0b0fdfde
    +243, 0x7b33e34da5c27bff
    +244, 0xd043c4b7d5603dd5
    +245, 0x9a544e4c70a3b686
    +246, 0xa7b60398c381f771
    +247, 0xe9e7a3487c4bd4f2
    +248, 0x10b58fdfe1ff112c
    +249, 0xd5c1c9748c0f4ceb
    +250, 0x61be9d09159d54ff
    +251, 0x5356f51e8239f510
    +252, 0xfe7889d9b202ecef
    +253, 0xc7fc19ca5d263d5d
    +254, 0x7c4c07e61dfd9f69
    +255, 0x6c315fe5015f300a
    +256, 0xe0a5bc00039747b4
    +257, 0x16397fdcf829ee80
    +258, 0xb55aee80d16a5169
    +259, 0xca0609944d007eea
    +260, 0xcc982249f65a02ce
    +261, 0x528161feb149c148
    +262, 0xcbf08ba49b41c006
    +263, 0x39af1ff0b6f14138
    +264, 0x5cc036be69799aec
    +265, 0x6adde125b1db21c5
    +266, 0x8a99d83d6b613b67
    +267, 0x1cd43fca9451f74c
    +268, 0x682dbb26ecc96365
    +269, 0x13b4be2ceb43e3
    +270, 0xbe8fbc3b6f4f581e
    +271, 0xda148a2f4bda5719
    +272, 0x239106ca3319f393
    +273, 0xb42b4dde641f0dd5
    +274, 0xd233cfdf4cb0af74
    +275, 0xfb5919d905589afc
    +276, 0xd802a8860c10b66a
    +277, 0x6c923e1d00e7b5bc
    +278, 0xfacce1134f383b89
    +279, 0xf9570abda7a6d553
    +280, 0x80f0f9796a208f18
    +281, 0xc0e1df5280951c57
    +282, 0xe9f143f08257bbe0
    +283, 0x79e4c6463123d588
    +284, 0xdd2118583f2b1684
    +285, 0xb399ff5f2329fa18
    +286, 0x4b3e9ebae96f813c
    +287, 0xc484dbf247787384
    +288, 0x921865eb97603f2c
    +289, 0x18063c68e257d300
    +290, 0x643181f345e7fc26
    +291, 0x12e0b0e8eadf9fa7
    +292, 0x79e613fe73dfa354
    +293, 0x6db4c59203b7217a
    +294, 0x6c7a0e9ba6139eaf
    +295, 0x9617c7ac4e3f6d97
    +296, 0x1f68a7b4fb1b4b75
    +297, 0xef0b7ab24944f466
    +298, 0xaf1dee1f4be1bc89
    +299, 0xd2e355c959f5fd8d
    +300, 0xe594c3fb95d96efc
    +301, 0x9554766ca3342906
    +302, 0xa4bbdc77d12842c
    +303, 0xb62400211ee489a8
    +304, 0x91abadaaa3bbe67c
    +305, 0xd371eeb91deb42bb
    +306, 0x883bab35cbd2b6e5
    +307, 0xd030c3d9411a9041
    +308, 0xff3c110a858ff000
    +309, 0x59bdf5ca47d0bde7
    +310, 0x2bc80fa3cdba1853
    +311, 0x6444ccb652662cb8
    +312, 0xc0c7e256b9e90339
    +313, 0x70714ea9c9d72302
    +314, 0x96a0142f9d897d27
    +315, 0x209a9097c5a91ef7
    +316, 0xb9e33afc5171e009
    +317, 0x47b37af433a58d40
    +318, 0x30cc4ffbfa831d26
    +319, 0xdcea4a85ff815466
    +320, 0x907d5bd027f2e5cc
    +321, 0x7c081f6852e04a4b
    +322, 0xe61950749c1d502b
    +323, 0x1604e937ee69834a
    +324, 0xb2372d952dd25309
    +325, 0x53f6a5b834c72577
    +326, 0x2ce7a74395e0b694
    +327, 0xacbf9ab4fe91f225
    +328, 0x5ce1e63d3a2bb90f
    +329, 0x54740da3a5ed139b
    +330, 0xf194ddb39f29880b
    +331, 0x3305374f5d8ec08b
    +332, 0x831dd0164927ff4a
    +333, 0x625baa78e4458cf
    +334, 0x29d27dc0a4a71152
    +335, 0xe227bae9a1401034
    +336, 0xca0c209831846b2b
    +337, 0x8e8cc54b08b5a411
    +338, 0x38f2b4acaac27db6
    +339, 0x8ec88baac814e86b
    +340, 0x31c08e46b007bde
    +341, 0xb686c02722794c09
    +342, 0xb77cf8fc682e3907
    +343, 0xa56334e7f606f4b2
    +344, 0x9c80b127bddd5f4f
    +345, 0x12df14834cd858bf
    +346, 0x3f14762a9cf5fb9f
    +347, 0x930a70941ef5779e
    +348, 0x64e96c849c30c080
    +349, 0xfdf53bfba1300484
    +350, 0xec7a9363c21bc616
    +351, 0x26e9fd6a115ecb47
    +352, 0x9707a84b5bc77fbb
    +353, 0xb23b2737b20d5903
    +354, 0x22f4825ae80f6501
    +355, 0x500644b12be6a01b
    +356, 0xb746645b2af082db
    +357, 0xe6af051f697892f8
    +358, 0x577c724248a1cfc6
    +359, 0x3d2b6a434c84eed3
    +360, 0xd260f5efd7328314
    +361, 0x95c16cc84bb3f55c
    +362, 0x7a01b2e4e0e80ca7
    +363, 0x41930c3ce70a0935
    +364, 0x1299bccf39d4e110
    +365, 0x494883ba1a8a87f
    +366, 0x9478ecfe2d918e60
    +367, 0x30ec9a5670cda8af
    +368, 0xf9bc877e833e2b99
    +369, 0x1b83a0acfbb4a8db
    +370, 0x73bc1740c0d18880
    +371, 0x65086ca9773cb3e1
    +372, 0x3b78c3ccd63cff2e
    +373, 0xbfae748795acfb31
    +374, 0xa4c9d5d56a15ba20
    +375, 0xb9cb41721e52b71e
    +376, 0x1532f15d4dc47748
    +377, 0x5a4d647a4b9ee632
    +378, 0x8513c7c5a50898d9
    +379, 0x6d3d98ccd5461b2e
    +380, 0xa65e99be2fe98d6
    +381, 0x31abc8855334a0e5
    +382, 0xf1ed22a661dca5b8
    +383, 0x299e2b63229e03be
    +384, 0xda201a06687bce48
    +385, 0xd27794b302142c55
    +386, 0x642bd3e1c7898a9d
    +387, 0x777f1ff00afa1a87
    +388, 0xd2f1c84fb3877baa
    +389, 0xae417583289191fd
    +390, 0xd641f1d88e0e2d55
    +391, 0xc1f1d98fb5d18ebf
    +392, 0xb0f72aecdadce97b
    +393, 0xe9b8abc764f6018a
    +394, 0xd2a37cff8e890594
    +395, 0x2dd70d631a528771
    +396, 0xbf8ba0478c18e336
    +397, 0x1630bf47f372ce0a
    +398, 0x6d04ea20dc3f46b8
    +399, 0x6591881bf34337f2
    +400, 0x33c149c7eb5b4103
    +401, 0xf01a8c9857c86748
    +402, 0x184348cdfc16d215
    +403, 0x141168b253d2ed7
    +404, 0x52aaf012ef50a6f1
    +405, 0xfda1722387e16f4c
    +406, 0x43c30f57d6c038fa
    +407, 0xd4a8611f5f96d214
    +408, 0x2c512ce17e987f2c
    +409, 0x961ce450f0fa2822
    +410, 0xf55a506ec6cea9cd
    +411, 0xb76d694d9c7f5ef6
    +412, 0xfb029216dbd8e988
    +413, 0x93162501896a0081
    +414, 0xfbbbd2c5ab300f5c
    +415, 0xd648b6da7387d491
    +416, 0xc73b4697471d9d98
    +417, 0xe37412bf1c93ee76
    +418, 0xa1a96d96570e6637
    +419, 0x5b3ab4f82428f65c
    +420, 0x873d849b188aa36f
    +421, 0x39fbee0ffc9fa9ff
    +422, 0xc70d21b744d677fe
    +423, 0x2b8a43c23043d209
    +424, 0x93c33eaa37370d16
    +425, 0x8930ac1880f2b0ef
    +426, 0xac01d27707036af0
    +427, 0xc2af3fee504343a0
    +428, 0x1c1dae2ad5535d97
    +429, 0x9ffc21804b76a480
    +430, 0x69f903412cc13563
    +431, 0x9d3c4e2759a0c47d
    +432, 0xb1a8f894be6302b9
    +433, 0x95e1fd7951479506
    +434, 0xbb9e6c03cd4ae8e3
    +435, 0x85206010c9b737cf
    +436, 0x767e813694d6238c
    +437, 0x4969af329ccbb30a
    +438, 0x3aa9af1075aaea5c
    +439, 0xb1ff519e8118a993
    +440, 0xb21a23a3c91180fe
    +441, 0x320b24582ca3fd88
    +442, 0xf8ca56415fb4e453
    +443, 0xabd0899c07205e77
    +444, 0x87fdc7a44b4ad50f
    +445, 0xd75744911641a278
    +446, 0x7c8c9a65df6fcb95
    +447, 0x79d785e3c7a5b695
    +448, 0x421e4565ba1f592f
    +449, 0x27f87eb2517835cf
    +450, 0xb62cc4297441c83e
    +451, 0xd817a80ac815ca6d
    +452, 0xad84388130df2aa8
    +453, 0x5e6b1640452d6ac8
    +454, 0x936285e15edce2a3
    +455, 0x903bccc4969768e8
    +456, 0xefc2cb7b109d3140
    +457, 0x633e9dfdda2d903a
    +458, 0x2a2f3225925678a1
    +459, 0xe07eac91a27f8547
    +460, 0xe50ced40eda78cb3
    +461, 0xc5b22500e1c7441
    +462, 0x32becf61bca3aa72
    +463, 0xa2e37c4b30671344
    +464, 0xc9f1c1910f45d544
    +465, 0x9b50333b2dcdf730
    +466, 0x310bfd53a1684b94
    +467, 0x1e1dc21e66ac6455
    +468, 0x81876c2bfb1ed5a1
    +469, 0xd0c54a3e25eadc7b
    +470, 0x3791b6fbbd5c7ba0
    +471, 0x133be57356c599fc
    +472, 0x8d1148eb8e83fdea
    +473, 0x311aedba0d8b42cc
    +474, 0x1142ae52745f94bb
    +475, 0xc5f4ab2fbde8c4a3
    +476, 0xd23be827b5b24f6d
    +477, 0x65f95194cd122715
    +478, 0x4b48969d73125922
    +479, 0x46f165052b8ff988
    +480, 0x5c689f94b9275ff4
    +481, 0x93b03823ff2d536b
    +482, 0x871f3775aa4e3523
    +483, 0x5af829f7cc0f66a5
    +484, 0xa32e05739cbeac8c
    +485, 0xacff1856ddace0fe
    +486, 0x8eeb5e7f991a5322
    +487, 0x6325c2720e0dbdea
    +488, 0x9fb817bc4fdf5200
    +489, 0x9786f0d850e43d78
    +490, 0x571f76dd7f9fb77a
    +491, 0x4d9e94e181cbc63f
    +492, 0x8bb632d3376c547a
    +493, 0x9cc26d9efd1c88b9
    +494, 0x9c5d49579df52b0b
    +495, 0x6201abf7e1cda07b
    +496, 0x90d68f0c6c884963
    +497, 0xfc5b66188ef7f561
    +498, 0x6d9303cf2e0e0f95
    +499, 0xd7cfcff535f5ed07
    +500, 0x14d1a1228daa4ac6
    +501, 0xe00ef5762f66ae50
    +502, 0xf113a79471582978
    +503, 0x430985281785dc7a
    +504, 0x31914108c206ed5
    +505, 0x7ba6707b6419971c
    +506, 0x2ec63b033ce112e5
    +507, 0xf8bcd36ced3b41e3
    +508, 0xe5cf908c8010414b
    +509, 0xf5ee224b7c703e30
    +510, 0x9a9733af0b12338b
    +511, 0x83e18cc00ace34f8
    +512, 0xd52cff39e23008b8
    +513, 0xa700578136b9c0c5
    +514, 0x3fa179d32ac51f99
    +515, 0xef2d5eab6d4ad380
    +516, 0x709024a5abd032df
    +517, 0xc607c7ee349ede87
    +518, 0x803d784e9731eb5f
    +519, 0x2ef06f4ba769282d
    +520, 0x4bc1dca1e9f07eb9
    +521, 0x930c958a7a72f94d
    +522, 0x249bc8db2cc7a3bf
    +523, 0x3845305798f9a5d
    +524, 0x6f137eca9ab6f948
    +525, 0xc31f5a963d31bd67
    +526, 0x9d39693d5383626f
    +527, 0x52fb41c335a8b98e
    +528, 0xb79d1a29a06006ec
    +529, 0x7c0926a7a3eda2cc
    +530, 0xffdf5214406fd53e
    +531, 0xc6aa02a7e94282b9
    +532, 0xd4a4431b4aa301ee
    +533, 0x4271cc0f9420d3ab
    +534, 0x26fccd7cc7fc2485
    +535, 0x330594bb945b8d5a
    +536, 0x6ea8eaad12e5cb8c
    +537, 0x831c3467726bede3
    +538, 0x31d1eb10017eaa61
    +539, 0xc7aa75e41508f5cb
    +540, 0xde51810f0cadd0b5
    +541, 0x50e5b3e73692f80b
    +542, 0x82107ec55636e188
    +543, 0x9828ef175d843ab4
    +544, 0xb8edc6a860dd421e
    +545, 0x25c0c138fd537ac3
    +546, 0x47e72a771e8eb563
    +547, 0xbb0f8c5333f4a2cc
    +548, 0x91750d2fb9b2d479
    +549, 0xe662d8f6fe38df36
    +550, 0x72a6d879fb5619f0
    +551, 0x6817c7878dcbf077
    +552, 0x4e7741cb484661e8
    +553, 0x3b3b3ba0be5711bf
    +554, 0xa6989f5d25868765
    +555, 0x43c276398997e4e0
    +556, 0xdcbe16a94da28870
    +557, 0x454936980a699c99
    +558, 0xac614bfa8f0266c6
    +559, 0x9174841392e213d5
    +560, 0xa0e2acffc5fc9d1f
    +561, 0xe53a08a7a0e6521a
    +562, 0x2b845cf7c24172e0
    +563, 0x265a4fc5f7adec0d
    +564, 0x1f34fbe5f1e49420
    +565, 0x139181f6fb647f20
    +566, 0x88c35d46e2fcd05e
    +567, 0x2a6d5b55903c0459
    +568, 0xcea28eb621ad7bf1
    +569, 0x5c9cdc13e7aaa30
    +570, 0x5fe63e14746e7103
    +571, 0x7923e53d73835db9
    +572, 0x376e661210bf1b06
    +573, 0x5b1cab85450efdd5
    +574, 0x3908dc096c70b452
    +575, 0x4825e303cd1f396f
    +576, 0xed476bfd702957c3
    +577, 0x6acc013aff5db743
    +578, 0x62c80b776343d488
    +579, 0x9c75edcd5b012697
    +580, 0xaa053362a3b9770a
    +581, 0xa907e236c7c07e94
    +582, 0x15b2c380451692c0
    +583, 0x94f79142697bd61f
    +584, 0xbc657d31ea98d44f
    +585, 0xcbaa5e52517a1f5e
    +586, 0x96aa2e44a7c4a03f
    +587, 0x216d3c66db2b515d
    +588, 0x157001807e3ca88a
    +589, 0x52b3a596bdd3859a
    +590, 0xed747e7fc5e3adac
    +591, 0x78fd765ddb2c448d
    +592, 0xe53dc7299ed8614e
    +593, 0x75ad41fb1d7a790a
    +594, 0xc14f6b944b0e6cb1
    +595, 0x7c314b69fce3df1c
    +596, 0xb56d82eb740d7abc
    +597, 0x5132a93c41251fdb
    +598, 0xe3ce35bd2a82f958
    +599, 0x440571a981c722f2
    +600, 0x194cdfd9f186bc9
    +601, 0xb89e522a5db00939
    +602, 0xad35f339f68df3c8
    +603, 0xa82ab18420322293
    +604, 0xaffa6df9b72b27c4
    +605, 0x9615694d23beaa2c
    +606, 0x1d82ebe563abad91
    +607, 0xab50ef65fbd94385
    +608, 0x1b070dbd70a9a14
    +609, 0x2ececa796abbadf0
    +610, 0x6bbeafe9e81ab2a2
    +611, 0x60dcd0d2a9b76914
    +612, 0x1e748039ef05c33f
    +613, 0x6d4d17f2213ccdff
    +614, 0x9fa56132957bc987
    +615, 0x60a17185de2428eb
    +616, 0xb56038ddf306479c
    +617, 0x3b1db5df92d06d8b
    +618, 0x24d1bba8bdedf580
    +619, 0xbfb7e6740ebaa4d9
    +620, 0xab31c4473e46f61d
    +621, 0x6deb3cdd8fd5869f
    +622, 0x23032e47746d72d6
    +623, 0xa9e72d734e10f2e8
    +624, 0xbffd199b6157bc23
    +625, 0x29f8254df273fb62
    +626, 0xb076142130ee55ec
    +627, 0x5b0b08374126c309
    +628, 0xea4536aae979521f
    +629, 0xc064e7abec91a174
    +630, 0x46133ef80c59d935
    +631, 0xf0227e2da1b14160
    +632, 0x675a76641e1af5a
    +633, 0x2f50a069b33d198c
    +634, 0x3ded5a65e1d657eb
    +635, 0xbb6999b020694f6b
    +636, 0x86b2f2b33487aed7
    +637, 0x76e14e85f8bfb4cf
    +638, 0x38f7f1e44bd4e0db
    +639, 0xc1a7d41b7e80d4ae
    +640, 0x1dfaaf80bbceb42e
    +641, 0x3f51c11497720c2b
    +642, 0xce6da1415ddb8b80
    +643, 0x7377d8bcd359b5f3
    +644, 0xe077208f3f810aca
    +645, 0x9a06a8a2dacbffce
    +646, 0xca1f99156b09b735
    +647, 0x2ff9a93064d91451
    +648, 0x50f3ea93f351a7ef
    +649, 0x606fceccb07054de
    +650, 0x7e83d6d2f8f6685d
    +651, 0x78f3995291c5d407
    +652, 0xd28d2460e22d0228
    +653, 0x2c5636f68a0054dd
    +654, 0xd9fafb1c56c8f6cb
    +655, 0xe39889b5f9d74464
    +656, 0x1355372bf5db2cc1
    +657, 0x26768426b9ac323
    +658, 0x4af1dbdc1111fd89
    +659, 0x66973587943b927f
    +660, 0xf86f5f50684dfb1d
    +661, 0x1247d574ff79b534
    +662, 0xc8039f3259210fe2
    +663, 0x79b573235c92a9f5
    +664, 0x213f642d8450e2f0
    +665, 0x5db7706973376566
    +666, 0x6182c12e69b373d7
    +667, 0x3e5ac47300aec07f
    +668, 0x4b5b6c57b1574376
    +669, 0x6b7fcceefd56b17c
    +670, 0xf656c3455cb9d4b8
    +671, 0x7577e2e13329721f
    +672, 0xf33c0c53ce956e8d
    +673, 0x7d0f328ee356174
    +674, 0x10ec9a168088686e
    +675, 0x71ef1776d062dfa
    +676, 0xaa7b590a488a6bc4
    +677, 0x38612b6dd8049a1c
    +678, 0x939045e36874f731
    +679, 0xcb9d1d74c56d5ac9
    +680, 0x54f1c1c8fef1d8ff
    +681, 0x3ee4b85c8c7e939e
    +682, 0xb9b4608e019f352c
    +683, 0x79d4701275d12e6a
    +684, 0x2632a2d9835c7f19
    +685, 0x1662cd9fba293692
    +686, 0xbcb70265115ee944
    +687, 0xdc43fb9761468604
    +688, 0xe3eec4e7d3871352
    +689, 0x829531753226989d
    +690, 0x2748cc67f540e074
    +691, 0x39c4af25d607837d
    +692, 0x741a243f4cb5df99
    +693, 0xda1353287e18b49a
    +694, 0xa6735689d751ea74
    +695, 0x46326d587340ce0b
    +696, 0xc18531df4550012b
    +697, 0x6f7901e05dd4b818
    +698, 0xfb966afc4c001d63
    +699, 0x6dc10fca67a9cfdb
    +700, 0xd6527ffadf0feaae
    +701, 0x3b900172045e25d
    +702, 0xb7dd594cdded6a46
    +703, 0x6602aee7ec1599fc
    +704, 0x7fbf12f23747546a
    +705, 0x32e63f662bd2de0d
    +706, 0xedf47770b67ed641
    +707, 0x331bef83481c5c2a
    +708, 0x8fc4256fdf05158c
    +709, 0x98eba48dabccf5e0
    +710, 0xdbc2f2cdb7b1c154
    +711, 0x7777755616517ad3
    +712, 0xd473c147d2628ac1
    +713, 0x861e15d1d760b5a7
    +714, 0xf4d25926405ecb07
    +715, 0xb7739c69effff86e
    +716, 0xe97fbafa6f96830c
    +717, 0xf13e8a334e8bede1
    +718, 0xcd60010cba4ee4f9
    +719, 0x1f537ac2b82e6008
    +720, 0x1fda8d781a89140a
    +721, 0x9dc204f3f4a463f0
    +722, 0x456dcd18eb56a1ab
    +723, 0x629957bc87bd16a1
    +724, 0x2c8000ddb8c75253
    +725, 0xc31dae9ec8449284
    +726, 0xdac05c8baa2b691a
    +727, 0x21ff7be9ffa3e7ac
    +728, 0x844f4b5ed4ee08d0
    +729, 0x651f913fd636c994
    +730, 0xca3e71a2110b2d49
    +731, 0x7709bc42253ed09d
    +732, 0xbb164d45b6569d43
    +733, 0x90ec2f040c20a112
    +734, 0xfa6e77e9166f5be4
    +735, 0x6b6d12c1842d587d
    +736, 0xfcd7ff8466e25e2a
    +737, 0x6a5a2ed8bd971297
    +738, 0x2ec35f6bba5adcbc
    +739, 0xc83676e16651249a
    +740, 0x458f6064cefe10ba
    +741, 0x90d54d527e6cd028
    +742, 0xa5613e88db27c388
    +743, 0x331e0c7d85aa1abc
    +744, 0x8cee4977e210358
    +745, 0xfcae379aa6cbff8e
    +746, 0xd1407afc97a57e86
    +747, 0x1fab25c864f094ae
    +748, 0xd914864a63004552
    +749, 0x4214d226a20f1384
    +750, 0x3f4e0d80c488b715
    +751, 0xc5ca2f654024b7c8
    +752, 0xc1e27a124e7c821c
    +753, 0xd890a915ffc7918c
    +754, 0x22fba040ce51a9f8
    +755, 0xbf61cebd8891617a
    +756, 0x7846609ee228e319
    +757, 0x536d1854375509b8
    +758, 0xbbfb45fc6e666f50
    +759, 0xd85b4c0527f9d7d6
    +760, 0x528cc9c7fa2a84c8
    +761, 0x27a1baece647f2cb
    +762, 0xfddf0cb92fe09dc3
    +763, 0xeb5008fe965d8d96
    +764, 0x4a3307937eb2e5c8
    +765, 0xd07d74c240c6c363
    +766, 0x16f62290179d1bbf
    +767, 0xe99c9bcc9cb1ece7
    +768, 0xc64f9be03c8a93be
    +769, 0x32659effaf666c1f
    +770, 0x4bb228cfb30b6672
    +771, 0x98764870842068a5
    +772, 0x5b12ef2d2cd8bdcc
    +773, 0xbc79d1c1b41f28b8
    +774, 0x97a517cf3279fc9a
    +775, 0x34ffd46c1d4d6025
    +776, 0x9c302307ee25c8f0
    +777, 0x399604eed1f18a8
    +778, 0x1c9b813c2043142a
    +779, 0x2944ea5e55267fe9
    +780, 0x5a8a9f5e728ea667
    +781, 0x30c8440adb804a0
    +782, 0xee0e6b627099a937
    +783, 0x3d50757ada3c52da
    +784, 0x4548916b32c813ab
    +785, 0x602a186fe5bf109b
    +786, 0xf0d440a2227ba304
    +787, 0x5a10d4e0ca9ea32b
    +788, 0x6e5eb90da13ba64c
    +789, 0x4c6af8fd04241ab2
    +790, 0xf9eb31d26e093006
    +791, 0x5d674878839fe3ea
    +792, 0x1562b55b2484e47c
    +793, 0xa87188c099c1cb61
    +794, 0xb7736b8aa02a3392
    +795, 0x5f4b301125abb20f
    +796, 0x361d566984637f44
    +797, 0x68c4b3feac8bd0c3
    +798, 0x7066c634dd2503c1
    +799, 0xfecbf7c9441eb6ea
    +800, 0xdbc26ae0fc81436b
    +801, 0x9ef3e2b48252e7a4
    +802, 0x31a49b4c339b37c7
    +803, 0xb01b2a83cf346cf4
    +804, 0xc24dc2347f82fbe3
    +805, 0x134cad272dcd410f
    +806, 0x61260742823ba59c
    +807, 0x53ac4c193a97c730
    +808, 0x9207c9833af34b52
    +809, 0xa72e7ee77078d1f5
    +810, 0x2e6f6e1b05936885
    +811, 0x783b99ce5dbf9464
    +812, 0xfdfeb6f0d027bb44
    +813, 0x40eeb27096f92b0
    +814, 0x5ef96ff5d4a4521f
    +815, 0x5595806ae873718a
    +816, 0x67d449eecf4ca1c3
    +817, 0xde837ab611364f3f
    +818, 0x7034c24d2b139be9
    +819, 0xe21166603e0a9c86
    +820, 0x935694435c1f0d51
    +821, 0x6cb3bec90c126088
    +822, 0x4096ef662b7a9f89
    +823, 0xd2d85b8d238d8c15
    +824, 0xa4ea533ce3ec59b2
    +825, 0x3654729d80a2db29
    +826, 0x214c4cc3906d29d4
    +827, 0x201c447e7588e373
    +828, 0xe8b8f0ae25f683eb
    +829, 0x6744aaf5754e38af
    +830, 0xd1ffb10d6f27a061
    +831, 0xe536733a7b3a6c30
    +832, 0x39f0f66e47cbf2c9
    +833, 0x856a9593526fde2
    +834, 0x2e2a817a0098ea4b
    +835, 0xc5e1eeb551a0e3d3
    +836, 0x3f21e2f5e2d50b2
    +837, 0x906af56c66dd9f8c
    +838, 0x30f6dbd70329fac8
    +839, 0xc443dfddf3c01a60
    +840, 0x7ab85d9aa9675470
    +841, 0x8c9080bd39717bfc
    +842, 0x4b1ccdb3c3597f6f
    +843, 0x74e2542d70ab5d67
    +844, 0xbb3d236aad00f74
    +845, 0xcf3cadf9a2804774
    +846, 0xe851d9750e42bd07
    +847, 0xc0ad82029b1c371f
    +848, 0x7ee119eb552d6c07
    +849, 0xd8024049bd1d784a
    +850, 0xfa67a899760363
    +851, 0xaa7c2f438b178197
    +852, 0xc473674a47ffe064
    +853, 0x539fbe3fc674c270
    +854, 0xdb48484748a76f3b
    +855, 0xc73b2b092060d
    +856, 0xa1d2a15345016f5d
    +857, 0x4d0fe8599f9bba47
    +858, 0xa0edc275e6f8f1d1
    +859, 0x40590a8655bc8d72
    +860, 0x35b4223161f05f75
    +861, 0xa04c0c0f616752dc
    +862, 0x7f371ed2ca45432d
    +863, 0x2ff1a08f75ac6438
    +864, 0xe2dc5c3682282f48
    +865, 0xe1e4179fa98d9013
    +866, 0x8cb083d6843a73d5
    +867, 0xb4c2b5921b706854
    +868, 0x738e14c0e7352445
    +869, 0xcd2b646f91afd8c7
    +870, 0xd5779a5b57a264fd
    +871, 0xc39ff855586c7d07
    +872, 0x3e3f0098c631a859
    +873, 0x644e02fae032110
    +874, 0xa8834613c0a45278
    +875, 0x69482f2c08e10657
    +876, 0xe4ee475bdb87e69a
    +877, 0xdc1ef7b25c0d0019
    +878, 0x88a3fa2be18d8744
    +879, 0x60a02e0b21c5bec7
    +880, 0xb6867b88aa19bc1a
    +881, 0xb599409affcf10eb
    +882, 0xaeaa1778a5e59daa
    +883, 0xd7a91a52c16663e3
    +884, 0x93cb269affe07b1c
    +885, 0x841b6ced3a4ba815
    +886, 0x84541768e1540a5c
    +887, 0xe3943c84f83b3020
    +888, 0x5de366fbd7b45258
    +889, 0xd787cc3bde91a661
    +890, 0x814071446edecb57
    +891, 0x15d8c602a1141514
    +892, 0x72f07bc8002d1d0d
    +893, 0x4a8bd8dc9a1f0f3e
    +894, 0x8723796ae0f20d35
    +895, 0xda7283c2051f73b2
    +896, 0x2df0cc247f90bd3b
    +897, 0x79a8522b968f990a
    +898, 0x951ede190c8b9d02
    +899, 0xc512f1a5b14b018a
    +900, 0xf0e3ddc03b9a4259
    +901, 0x8cf4a35ad312e15f
    +902, 0xebef28926b11094b
    +903, 0x5628ba687325921c
    +904, 0xc3aa75e57edc49c3
    +905, 0xc38382fa98e762ba
    +906, 0x8d209e896285848e
    +907, 0x2c7d6adf592b4a3e
    +908, 0x62de48e36f8338f3
    +909, 0x4a752741e00de30e
    +910, 0xf7855b70f1f6ec2b
    +911, 0xa505fa4428199e43
    +912, 0xe8b6b423b826bbac
    +913, 0x4bd1206cf8786d05
    +914, 0x6dcf040391fe3bf4
    +915, 0x913f500f87e1bba3
    +916, 0x5acf775aa180a5d5
    +917, 0x74dd28d9432ce739
    +918, 0x996c2ff2f0dc2495
    +919, 0x73dbfe6c56effe4
    +920, 0x56fddd25196f5e40
    +921, 0xe87810158f5b7
    +922, 0x7b8795e996383f1f
    +923, 0x9ba5ee7c777c4c82
    +924, 0x17ce3908d270fe1c
    +925, 0x3df9e613c1aedfae
    +926, 0xcdd26871b32fc8e1
    +927, 0xd71cb13afc633979
    +928, 0x63427c8ea9b1c79e
    +929, 0xd070f7664d3b405d
    +930, 0x46f2a9e32d9fb769
    +931, 0xb4c3822a45e9fe9b
    +932, 0x8ba30b97fe6f5ec7
    +933, 0x70aa554ee2fc11f9
    +934, 0xa80c99dbe0cfcfaf
    +935, 0x36d9250cb2d68ed
    +936, 0x2995e4b9e1cd1db4
    +937, 0x4b3803ba57fc570f
    +938, 0xae3959e7d740eaa5
    +939, 0xb4cbd6662adbae08
    +940, 0xae46576446e8dbc4
    +941, 0xc4828e008a9a8a54
    +942, 0x145d7db8e6554b2f
    +943, 0x1b1b8916a730c371
    +944, 0xdaf84b2bebe31963
    +945, 0x5b59b80ef23a2403
    +946, 0x9180c7e89cab6fd3
    +947, 0x80e58f5411babf34
    +948, 0xa06cf55185b9b005
    +949, 0x13b2c798424173ad
    +950, 0xc510f8e706311d49
    +951, 0x1f974b83b6046d3a
    +952, 0xae6e8e85e822d1c3
    +953, 0x66f2c8dc3274a31a
    +954, 0x7e04dbcbf65bd377
    +955, 0xabf41ede01ec20a4
    +956, 0x5efa0948f6bbb2ea
    +957, 0xbc91c99d8592255
    +958, 0xf6d6917911d86d75
    +959, 0x85ce273d54e9097a
    +960, 0xbdfd30f2420fff92
    +961, 0x8802f02f610b537c
    +962, 0xd1d70037ed543229
    +963, 0x908aaf97f9693a46
    +964, 0x1f6cfeaa0834d53a
    +965, 0xa453fd1648ce04d2
    +966, 0x2c38bb85ebc64af9
    +967, 0xd2daff551c90c4f8
    +968, 0xae5a0d949797d784
    +969, 0xf0974c8552ac9593
    +970, 0xa10b70499f65c693
    +971, 0x39a449ebd594ddff
    +972, 0x8ea090f2b17b9b49
    +973, 0xc592de318090fd83
    +974, 0xb63e4fbc467b6912
    +975, 0x57a0c1c5ce0e4dcc
    +976, 0xa7c517cf3d436b35
    +977, 0xef6dcb0f3fad038b
    +978, 0xaf4fb60315b91287
    +979, 0x5e0776f67304f331
    +980, 0xe927753b8e6f7932
    +981, 0xd3df2dd92559e304
    +982, 0xdaed52aa6af44413
    +983, 0x1b59f4dac1e181f8
    +984, 0x4a73c2293877ef39
    +985, 0xca45d0d015fe44de
    +986, 0x4659c8b7853735a8
    +987, 0x12de6466bdf8adeb
    +988, 0xaeea857a09bfec15
    +989, 0xcc9cf4b3c0b88a23
    +990, 0xa44ae52396a5e1bf
    +991, 0x5847a724305d137f
    +992, 0x8f4d4de223956182
    +993, 0x58254dfada867a8
    +994, 0x900a98222c2f339e
    +995, 0xdb575260935d51d5
    +996, 0x13fb4bfbbc0d7b53
    +997, 0x62213850186bb92b
    +998, 0x2a34823312c00388
    +999, 0x6148329042f743b0
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/data/pcg64dxsm-testset-1.csv b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/data/pcg64dxsm-testset-1.csv
    new file mode 100644
    index 0000000000000000000000000000000000000000..39cef057f4490a70d7041bcfde4d3fec844732bb
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/data/pcg64dxsm-testset-1.csv
    @@ -0,0 +1,1001 @@
    +seed, 0xdeadbeaf
    +0, 0xdf1ddcf1e22521fe
    +1, 0xc71b2f9c706cf151
    +2, 0x6922a8cc24ad96b2
    +3, 0x82738c549beccc30
    +4, 0x5e8415cdb1f17580
    +5, 0x64c54ad0c09cb43
    +6, 0x361a17a607dce278
    +7, 0x4346f6afb7acad68
    +8, 0x6e9f14d4f6398d6b
    +9, 0xf818d4343f8ed822
    +10, 0x6327647daf508ed6
    +11, 0xe1d1dbe5496a262a
    +12, 0xfc081e619076b2e0
    +13, 0x37126563a956ab1
    +14, 0x8bb46e155db16b9
    +15, 0x56449f006c9f3fb4
    +16, 0x34a9273550941803
    +17, 0x5b4df62660f99462
    +18, 0xb8665cad532e3018
    +19, 0x72fc3e5f7f84216a
    +20, 0x71d3c47f6fd59939
    +21, 0xfd4218afa1de463b
    +22, 0xc84054c78e0a9a71
    +23, 0xae59034726be61a8
    +24, 0xa6a5f21de983654d
    +25, 0x3b633acf572009da
    +26, 0x6a0884f347ab54c8
    +27, 0x7a907ebe9adcab50
    +28, 0xbe779be53d7b8d4a
    +29, 0xf5976e8c69b9dcd1
    +30, 0x1d8302f114699e11
    +31, 0x7d37e43042c038a0
    +32, 0x2cc1d4edc2a40f35
    +33, 0x83e3347bb2d581f1
    +34, 0x253f8698651a844d
    +35, 0x4312dea0dd4e32f6
    +36, 0x10f106439964ea3a
    +37, 0x810eb374844868cc
    +38, 0x366342a54b1978cc
    +39, 0x9fb39b13aaddfb5e
    +40, 0xdb91fd0d9482bed7
    +41, 0x89f6ea4ca9c68204
    +42, 0x146b31ccca461792
    +43, 0x203fd9724deb2486
    +44, 0x58a84f23748e25cb
    +45, 0x2f20eb6aeb94e88
    +46, 0x14d3581460e473c
    +47, 0xad5bd0d25f37d047
    +48, 0x1cf88fa16de258b2
    +49, 0x3bcab6485b7a341
    +50, 0xb2433b37f227d90c
    +51, 0x2cffd7e0a8360cc8
    +52, 0x5d2eeff7c9ebc847
    +53, 0x6fd7c7ae23f9f64b
    +54, 0x381650b2d00f175d
    +55, 0x9d93edcedc873cae
    +56, 0x56e369a033d4cb49
    +57, 0x7547997116a3bac
    +58, 0x11debaa897fd4665
    +59, 0xdf799d2b73bd6fb8
    +60, 0x3747d299c66624d
    +61, 0xac9346701afd0cfa
    +62, 0xac90e150fa13c7bf
    +63, 0x85c56ad2248c2871
    +64, 0xdea66bf35c45f195
    +65, 0x59cf910ea079fb74
    +66, 0x2f841bb782274586
    +67, 0x9814df4384d92bd9
    +68, 0x15bc70824be09925
    +69, 0x16d4d0524c0503a3
    +70, 0xf04ea249135c0cc7
    +71, 0xa707ab509b7e3032
    +72, 0x465459efa869e372
    +73, 0x64cbf70a783fab67
    +74, 0x36b3541a14ca8ed7
    +75, 0x9a4dfae8f4c596bf
    +76, 0x11d9a04224281be3
    +77, 0xe09bbe6d5e98ec32
    +78, 0xa6c60d908973aa0d
    +79, 0x7c524c57dd5915c8
    +80, 0xa810c170b27f1fdc
    +81, 0xce5d409819621583
    +82, 0xfe2ee3d5332a3525
    +83, 0x162fb7c8b32045eb
    +84, 0x4a3327156b0b2d83
    +85, 0x808d0282f971064
    +86, 0x2e6f04cf5ed27e60
    +87, 0xaf6800699cca67a9
    +88, 0xc7590aae7244c3bf
    +89, 0x7824345f4713f5f9
    +90, 0x8f713505f8fd059b
    +91, 0x3d5b5b9bb6b1e80e
    +92, 0x8674f45e5dc40d79
    +93, 0xcb1e36846aa14773
    +94, 0xe0ae45b2b9b778c1
    +95, 0xd7254ce931eefcfb
    +96, 0xef34e15e4f55ac0a
    +97, 0xf17cc0ba15a99bc4
    +98, 0x77bb0f7ffe7b31f1
    +99, 0x6ee86438d2e71d38
    +100, 0x584890f86829a455
    +101, 0x7baf0d8d30ba70fe
    +102, 0xb1ac8f326b8403ae
    +103, 0xcc1963435c874ba7
    +104, 0x9c483b953d1334ce
    +105, 0xc0924bcbf3e10941
    +106, 0x21bcc581558717b1
    +107, 0x2c5ad1623f8d292b
    +108, 0xa8ea110f6124557e
    +109, 0x15f24a6c5c4c591
    +110, 0x40fe0d9cd7629126
    +111, 0xcfe8f2b3b081484d
    +112, 0x891383f4b4cac284
    +113, 0x76f2fcdef7fa845
    +114, 0x4edd12133aed0584
    +115, 0xd53c06d12308873d
    +116, 0xf7f22882c17f86bf
    +117, 0xfbaa4aad72f35e10
    +118, 0x627610da2e3c0cc3
    +119, 0x582b16a143634d9a
    +120, 0x9b4a7f69ed38f4a0
    +121, 0x2df694974d1e1cbe
    +122, 0xe5be6eaafed5d4b
    +123, 0xc48e2a288ad6605e
    +124, 0xbcb088149ce27c2b
    +125, 0x3cb6a7fb06ceecbe
    +126, 0x516735fff3b9e3ac
    +127, 0x5cbafc551ee5008d
    +128, 0xee27d1ab855c5fd5
    +129, 0xc99fb341f6baf846
    +130, 0x7ad8891b92058e6d
    +131, 0xf50310d03c1ac6c7
    +132, 0x947e281d998cbd3e
    +133, 0x1d4d94a93824fe80
    +134, 0x5568b77289e7ee73
    +135, 0x7d82d1b2b41e3c8b
    +136, 0x1af462c7abc787b
    +137, 0xcfd8dfe80bfae1ef
    +138, 0xd314caeb723a63ea
    +139, 0x1c63ddcfc1145429
    +140, 0x3801b7cc6cbf2437
    +141, 0xc327d5b9fdafddd3
    +142, 0xe140278430ca3c78
    +143, 0x4d0345a685cb6ef8
    +144, 0x47640dc86e261ff9
    +145, 0xab817f158523ebf4
    +146, 0x37c51e35fbe65a6b
    +147, 0xab090f475d30a178
    +148, 0x4d3ec225bf599fc1
    +149, 0xefd517b0041679b1
    +150, 0x20ad50bca4da32c5
    +151, 0x75e1f7cd07fad86d
    +152, 0x348cf781ee655f4b
    +153, 0x9375f0e5ffc2d2ec
    +154, 0x7689082fd5f7279c
    +155, 0x633e56f763561e77
    +156, 0x9d1752d70861f9fd
    +157, 0xa3c994b4e70b0b0f
    +158, 0xabf7276a58701b88
    +159, 0xbfa18d1a0540d000
    +160, 0xc6a28a2475646d26
    +161, 0x7cdf108583f65085
    +162, 0x82dcefb9f32104be
    +163, 0xc6baadd0adc6b446
    +164, 0x7a63cff01075b1b4
    +165, 0x67ac62e575c89919
    +166, 0x96fa4320a0942035
    +167, 0xc4658859385b325f
    +168, 0xde22c17ff47808f6
    +169, 0xbb952c4d89e2f2ec
    +170, 0x638251fbc55bdc37
    +171, 0x38918b307a03b3ea
    +172, 0xccb60f2cedbb570b
    +173, 0x3c06f4086a28f012
    +174, 0x4e8d238388986e33
    +175, 0x1760b7793514a143
    +176, 0xa3f924efe49ee7d6
    +177, 0xaf6be2dbaebc0bdf
    +178, 0x6782682090dffe09
    +179, 0xb63a4d90d848e8ef
    +180, 0x5f649c7eaf4c54c5
    +181, 0xbe57582426a085ba
    +182, 0xb5dd825aa52fb76d
    +183, 0x74cb4e6ca4039617
    +184, 0x382e578bf0a49588
    +185, 0xc043e8ea6e1dcdae
    +186, 0xf902addd5c04fa7c
    +187, 0xf3337994612528db
    +188, 0x4e8fd48d6d15b4e6
    +189, 0x7190a509927c07ab
    +190, 0x864c2dee5b7108ae
    +191, 0xbb9972ddc196f467
    +192, 0x1ea02ab3ca10a448
    +193, 0xe50a8ffde35ddef9
    +194, 0x7bd2f59a67183541
    +195, 0x5a940b30d8fcd27a
    +196, 0x82b4cea62623d4d3
    +197, 0x6fbda76d4afef445
    +198, 0x8b1f6880f418328e
    +199, 0x8b69a025c72c54b7
    +200, 0xb71e0f3986a3835f
    +201, 0xa4a7ddb8b9816825
    +202, 0x945dcda28228b1d8
    +203, 0xb471abf2f8044d72
    +204, 0xf07d4af64742b1ba
    +205, 0xfca5190bc4dd6a2a
    +206, 0xd681497262e11bc5
    +207, 0xbe95d5f00c577028
    +208, 0x56313439fd8bde19
    +209, 0x3f3d9ac9b5ee6522
    +210, 0x7b8d457dd2b49bbe
    +211, 0xe76b5747885d214b
    +212, 0xa8a695b3deb493ea
    +213, 0x5292446548c95d71
    +214, 0xbf5cdf0d436412df
    +215, 0x7936abaed779d28d
    +216, 0x659c6e8073b3a06d
    +217, 0x86c9ff28f5543b71
    +218, 0x6faa748445a99146
    +219, 0xdcc1e6ab57904fd7
    +220, 0x770bd61233addc5f
    +221, 0x16963e041e46d94f
    +222, 0x158e6cb2934157ac
    +223, 0xb65088a8fd246441
    +224, 0x2b12ced6ce8a68c3
    +225, 0x59a18d02cd6082b3
    +226, 0x4ddbc318cb5488ee
    +227, 0x3d4cf520b3ed20a1
    +228, 0x7028b3a92e2b292d
    +229, 0xf141da264a250e4d
    +230, 0x9788d53e86041c37
    +231, 0x1bb91238a7c97dbf
    +232, 0x81953d0ddb634309
    +233, 0xfa39ccfe14d2d46
    +234, 0xf7c7861c9b7e8399
    +235, 0x18d27ca50d9dc249
    +236, 0x258dfdf38510d0d9
    +237, 0x9e72d8af910ea76f
    +238, 0x4f8ef24b96de50ad
    +239, 0xb9d9c12297e03dc9
    +240, 0x91994e41b4a1929c
    +241, 0x8defa79b2ccc83b9
    +242, 0x948566748706dac5
    +243, 0x7b0454946e70e4cf
    +244, 0x340b7cb298c70ed7
    +245, 0x6602005330cebd95
    +246, 0xf71cb803aa61f722
    +247, 0x4683fb07fc70ae8a
    +248, 0xc6db9f0c4de3ed88
    +249, 0x3e8dfae2a593cef9
    +250, 0x615f7c38e3862b33
    +251, 0x676c7996550d857
    +252, 0xc6d520d54a5c266a
    +253, 0x202b1e8eef14aa2e
    +254, 0xa3a84891a27a582
    +255, 0x84dbee451658d47f
    +256, 0x254c7cd97e777e3a
    +257, 0xf50b6e977f0eba50
    +258, 0x2898b1d3062a4798
    +259, 0x4096f7cbbb019773
    +260, 0x9fb8e75548062c50
    +261, 0x4647071e5ca318ec
    +262, 0x2b4750bdb3b3b01
    +263, 0x88ac41cc69a39786
    +264, 0x705e25476ef46fa3
    +265, 0xc0c1db19884a48a6
    +266, 0x1364c0afdbb465e5
    +267, 0x58e98534701272a6
    +268, 0x746a5ea9701517c0
    +269, 0x523a70bc6b300b67
    +270, 0x9b1c098eda8564ad
    +271, 0xfbaeb28d3637067f
    +272, 0xddd9a13551fdba65
    +273, 0x56461a670559e832
    +274, 0xab4fd79be85570ad
    +275, 0xd4b691ecaff8ca55
    +276, 0x11a4495939e7f004
    +277, 0x40d069d19477eb47
    +278, 0xe790783d285cd81e
    +279, 0xde8218b16d935bc7
    +280, 0x2635e8c65cd4182d
    +281, 0xeae402623e3454
    +282, 0x9f99c833184e0279
    +283, 0x3d0f79a0d52d84e7
    +284, 0xc1f8edb10c625b90
    +285, 0x9b4546363d1f0489
    +286, 0x98d86d0b1212a282
    +287, 0x386b53863161200d
    +288, 0xbe1165c7fe48a135
    +289, 0xb9658b04dbbfdc8c
    +290, 0xcea14eddfe84d71a
    +291, 0x55d03298be74abe7
    +292, 0x5be3b50d961ffd7e
    +293, 0xc76b1045dc4b78e1
    +294, 0x7830e3ff3f6c3d4c
    +295, 0xb617adb36ca3729
    +296, 0x4a51bdb194f14aa9
    +297, 0x246024e54e6b682a
    +298, 0x33d42fc9c6d33083
    +299, 0xadccba149f31e1d
    +300, 0x5183e66b9002f8b
    +301, 0x70eb2416404d51b7
    +302, 0x26c25eb225535351
    +303, 0xbc2d5b0d23076561
    +304, 0x5823019ddead1da
    +305, 0x85cfa109fca69f62
    +306, 0x26017933e7e1efd9
    +307, 0x3ec7be9a32212753
    +308, 0x697e8a0697cd6f60
    +309, 0x44735f6cca03920f
    +310, 0x8cc655eb94ee212e
    +311, 0x8b8b74eba84929a0
    +312, 0x7708ccedd0c98c80
    +313, 0x1b6f21f19777cbe1
    +314, 0x363e564bd5fadedb
    +315, 0x5921543a641591fe
    +316, 0xc390786d68ea8a1b
    +317, 0x9b293138dc033fca
    +318, 0x45447ca8dc843345
    +319, 0xee6ef6755bc49c5e
    +320, 0x70a3a1f5163c3be5
    +321, 0xf05e25448b6343b0
    +322, 0x4739f4f8717b7e69
    +323, 0xb006141975bf957
    +324, 0x31874a91b707f452
    +325, 0x3a07f2c90bae2869
    +326, 0xb73dae5499a55c5e
    +327, 0x489070893bb51575
    +328, 0x7129acf423940575
    +329, 0x38c41f4b90130972
    +330, 0xc5260ca65f5a84a1
    +331, 0x6e76194f39563932
    +332, 0x62ca1f9ca3de3ca6
    +333, 0xb4a97874e640853f
    +334, 0x38ed0f71e311cc02
    +335, 0xde183b81099e8f47
    +336, 0x9bb8bf8e6694346
    +337, 0xd15497b6bf81e0f2
    +338, 0xaaae52536c00111
    +339, 0x4e4e60d1435aaafd
    +340, 0x5a15512e5d6ea721
    +341, 0xff0f1ffabfc6664f
    +342, 0xba3ffcedc5f97fec
    +343, 0xef87f391c0c6bfb6
    +344, 0x4a888c5d31eb0f98
    +345, 0x559a3fbfd7946e95
    +346, 0xe45b44a0db5a9bad
    +347, 0x9457898964190af1
    +348, 0xd9357dfaab76cd9e
    +349, 0xa60e907178d965a1
    +350, 0x76b2dc3032dc2f4a
    +351, 0x13549b9c2802120
    +352, 0x8656b965a66a1800
    +353, 0x16802e6e22456a23
    +354, 0x23b62edc60efaa9
    +355, 0x6832a366e1e4ea3b
    +356, 0x46b1b41093ff2b1e
    +357, 0x55c857128143f219
    +358, 0x7fc35ddf5e138200
    +359, 0x790abe78be67467e
    +360, 0xa4446fc08babd466
    +361, 0xc23d70327999b855
    +362, 0x2e019d1597148196
    +363, 0xfefd98e560403ab8
    +364, 0xbe5f0a33da330d58
    +365, 0x3078a4e9d43ca395
    +366, 0x511bfedd6f12f2b3
    +367, 0x8bc138e335be987c
    +368, 0x24640f803465716d
    +369, 0xf6530b04d0bd618f
    +370, 0x9b7833e5aa782716
    +371, 0x778cd35aea5841b1
    +372, 0xecea3c458cefbc60
    +373, 0x5107ae83fc527f46
    +374, 0x278ad83d44bd2d1a
    +375, 0x7014a382295aeb16
    +376, 0xf326dd762048743f
    +377, 0x858633d56279e553
    +378, 0x76408154085f01bc
    +379, 0x3e77d3364d02e746
    +380, 0x2f26cea26cadd50b
    +381, 0x6d6846a4ecb84273
    +382, 0x4847e96f2df5f76
    +383, 0x5a8610f46e13ff61
    +384, 0x4e7a7cac403e10dd
    +385, 0x754bdf2e20c7bc90
    +386, 0x8bdd80e6c51bd0be
    +387, 0x61c655fae2b4bc52
    +388, 0x60873ef48e3d2f03
    +389, 0x9d7d8d3698a0b4a4
    +390, 0xdf48e9c355cd5d4b
    +391, 0x69ecf03e20be99ac
    +392, 0xc1a0c5a339bd1815
    +393, 0x2e3263a6a3adccb
    +394, 0x23557459719adbdc
    +395, 0xd1b709a3b330e5a
    +396, 0xade5ab00a5d88b9d
    +397, 0x69a6bd644120cfad
    +398, 0x40187ecceee92342
    +399, 0x1c41964ba1ac78da
    +400, 0x9ac5c51cbecabe67
    +401, 0xbdc075781cf36d55
    +402, 0xeaf5a32246ded56
    +403, 0xcda0b67e39c0fb71
    +404, 0x4839ee456ef7cc95
    +405, 0xf17092fdd41d5658
    +406, 0x2b5d422e60ae3253
    +407, 0x3effe71102008551
    +408, 0x20a47108e83934b7
    +409, 0xd02da65fe768a88f
    +410, 0xeb046bd56afa4026
    +411, 0x70c0509c08e0fbe0
    +412, 0x1d35c38d4f8bac6c
    +413, 0x9aa8eb6466f392e0
    +414, 0x587bd4a430740f30
    +415, 0x82978fe4bad4195
    +416, 0xdc4ebc4c0feb50ab
    +417, 0xd3b7164d0240c06f
    +418, 0x6e2ad6e5a5003a63
    +419, 0xa24b430e2ee6b59c
    +420, 0x2905f49fd5073094
    +421, 0x5f209e4de03aa941
    +422, 0x57b7da3e0bedb1dc
    +423, 0x5e054018875b01f5
    +424, 0xb2f2da6145658db3
    +425, 0xbd9c94a69a8eb651
    +426, 0x9c5f9a07cd6ac749
    +427, 0x2296c4af4d529c38
    +428, 0x522ed800fafdefab
    +429, 0xe2a447ced0c66791
    +430, 0x937f10d45e455fef
    +431, 0xc882987d9e29a24
    +432, 0x4610bfd6a247ee1a
    +433, 0x562ba3e50870059
    +434, 0x59d8d58793602189
    +435, 0xfe9a606e3e34abe
    +436, 0x6825f7932a5e9282
    +437, 0xe77f7061bab476ad
    +438, 0xbf42001da340ace3
    +439, 0x9c3e9230f5e47960
    +440, 0x2c0f700d96d5ad58
    +441, 0x330048b7cd18f1f9
    +442, 0xffc08785eca5cca9
    +443, 0xb5879046915f07a5
    +444, 0xef51fe26f83c988e
    +445, 0xfa4c2968e7881a9a
    +446, 0xc0a9744455a4aad
    +447, 0xbd2ad686d6313928
    +448, 0x6b9f0984c127682a
    +449, 0xc9aaa00a5da59ed8
    +450, 0x762a0c4b98980dbf
    +451, 0x52d1a2393d3ca2d1
    +452, 0x1e9308f2861db15c
    +453, 0xe7b3c74fe4b4a844
    +454, 0x485e15704a7fc594
    +455, 0x9e7f67ea44c221f6
    +456, 0xbab9ad47fde916e0
    +457, 0x50e383912b7fc1f4
    +458, 0xaad63db8abcef62d
    +459, 0xc2f0c5699f47f013
    +460, 0xee15b36ada826812
    +461, 0x2a1b1cf1e1777142
    +462, 0x8adb03ede79e937d
    +463, 0xf14105ef65643bf3
    +464, 0x752bbaefc374a3c7
    +465, 0xa4980a08a5a21d23
    +466, 0x418a1c05194b2db7
    +467, 0xdd6ff32efe1c3cd6
    +468, 0x272473ed1f0d3aa2
    +469, 0x1e7fdebadabe6c06
    +470, 0xd1baa90c17b3842f
    +471, 0xd3d3a778e9c8404a
    +472, 0x781ae7fda49fa1a0
    +473, 0x61c44fdbdacc672d
    +474, 0x6d447d0a1404f257
    +475, 0x9303e8bdfbfb894d
    +476, 0x3b3482cdec016244
    +477, 0xb149bf245d062e7b
    +478, 0x96f8d54b14cf992d
    +479, 0x4741549a01f8c3d0
    +480, 0x48270811b2992af
    +481, 0x7b58f175cd25d147
    +482, 0x8f19a840b56f4be9
    +483, 0x84a77f43c0951a93
    +484, 0x34e1a69381f0c374
    +485, 0xb158383c9b4040f
    +486, 0x372f1abc7cf3a9fa
    +487, 0x5439819a84571763
    +488, 0xabf8515e9084e2fa
    +489, 0xb02312b9387ff99
    +490, 0x238a85bb47a68b12
    +491, 0x2068cb83857c49bb
    +492, 0xc6170e743083664c
    +493, 0x745cf8470bcb8467
    +494, 0xe3a759a301670300
    +495, 0x292c7686ad3e67da
    +496, 0x359efedaff192a45
    +497, 0x511f2c31a2d8c475
    +498, 0x97fd041bf21c20b3
    +499, 0x25ef1fe841b7b3f6
    +500, 0xbb71739e656f262d
    +501, 0x2729b0e989b6b7b8
    +502, 0xd2142702ec7dbabf
    +503, 0x7008decd2488ee3f
    +504, 0x69daa95e303298d7
    +505, 0xc35eca4efb8baa5a
    +506, 0xf3f16d261cec3b6c
    +507, 0x22371c1d75396bd3
    +508, 0x7aefa08eccae857e
    +509, 0x255b493c5e3c2a2f
    +510, 0x779474a077d34241
    +511, 0x5199c42686bea241
    +512, 0x16c83931e293b8d3
    +513, 0xa57fe8db8c0302c7
    +514, 0xd7ace619e5312eb1
    +515, 0x8740f013306d217c
    +516, 0xb6a1ad5e29f4d453
    +517, 0x31abf7c964688597
    +518, 0xbc3d791daed71e7
    +519, 0x31ee4ca67b7056ed
    +520, 0x1ab5416bfe290ea3
    +521, 0x93db416f6d3b843a
    +522, 0xed83bbe5b1dd2fed
    +523, 0xece38271470d9b6d
    +524, 0x3a620f42663cd8ae
    +525, 0x50c87e02acafee5d
    +526, 0xcabeb8bedbc6dab5
    +527, 0x2880a6d09970c729
    +528, 0x4aba5dd3bfc81bc
    +529, 0xaba54edf41080cec
    +530, 0xb86bb916fc85a169
    +531, 0x4c41de87bc79d8ca
    +532, 0xcce2a202622945fe
    +533, 0x513f086fad94c107
    +534, 0x18b3960c11f8cc96
    +535, 0x2f0d1cfd1896e236
    +536, 0x1702ae3880d79b15
    +537, 0x88923749029ae81
    +538, 0x84810d4bdec668eb
    +539, 0xf85b0a123f4fc68d
    +540, 0x93efd68974b6e4d1
    +541, 0x5d16d6d993a071c9
    +542, 0x94436858f94ca43b
    +543, 0xb3dbb9ed0cb180b6
    +544, 0x6447030a010b8c99
    +545, 0xd7224897c62925d8
    +546, 0xb0c13c1d50605d3a
    +547, 0xdff02c7cb9d45f30
    +548, 0xe8103179f983570d
    +549, 0xbc552037d6d0a24e
    +550, 0x775e500b01486b0d
    +551, 0x2050ac632c694dd6
    +552, 0x218910387c4d7ae7
    +553, 0xf83e8b68ff885d5d
    +554, 0xe3374ec25fca51a3
    +555, 0xfa750ffa3a60f3af
    +556, 0x29ee40ba6df5592e
    +557, 0x70e21a68f48260d2
    +558, 0x3805ca72cd40886e
    +559, 0x2f23e73f8eabf062
    +560, 0x2296f80cdf6531ae
    +561, 0x903099ed968db43a
    +562, 0xf044445cf9f2929f
    +563, 0xcd47fdc2de1b7a1
    +564, 0xaab1cbd4f849da99
    +565, 0x5fc990688da01acb
    +566, 0xa9cee52ea7dab392
    +567, 0xecefc3a4349283a8
    +568, 0xdd6b572972e3fafc
    +569, 0xc1f0b1a2ffb155da
    +570, 0xc30d53fc17bd25c8
    +571, 0x8afa89c77834db28
    +572, 0x5569a596fb32896c
    +573, 0x36f207fc8df3e3d4
    +574, 0x57c2bd58517d81db
    +575, 0xb524693e73d0061c
    +576, 0xb69f6eb233f5c48b
    +577, 0x4f0fb23cab8dc695
    +578, 0x492c1ad0a48df8df
    +579, 0xf6dcc348ec8dec1f
    +580, 0xa4d8708d6eb2e262
    +581, 0x4c2072c2c9766ff1
    +582, 0xa9bf27c4304875f0
    +583, 0xfc8fb8066d4f9ae2
    +584, 0x188095f6235fec3c
    +585, 0x1d8227a2938c2864
    +586, 0x89ea50c599010378
    +587, 0xcac86df0a7c6d56d
    +588, 0x47a8c5df84c7d78
    +589, 0xe607ae24ea228bfa
    +590, 0x36624a7996efe104
    +591, 0x5d72881c1227d810
    +592, 0x78694a6750374c8
    +593, 0x7b9a217d4ab5ff45
    +594, 0xd53e5d6f7504becc
    +595, 0x197a72d3f4889a0e
    +596, 0xfdc70c4755a8df36
    +597, 0xd0fda83748c77f74
    +598, 0x7ddc919ac9d6dcc9
    +599, 0x785c810a6a2dc08b
    +600, 0xba4be83e7e36896c
    +601, 0x379d6fe80cf2bffe
    +602, 0x74cae2dabc429206
    +603, 0x1efac32d5d34c917
    +604, 0x3cb64e2f98d36e70
    +605, 0xc0a7c3cdc3c60aa7
    +606, 0x699dfadd38790ebe
    +607, 0x4861e61b3ecfbeac
    +608, 0x531744826c345baa
    +609, 0x5ec26427ad450cba
    +610, 0xf2c1741479abdcae
    +611, 0xe9328a78b2595458
    +612, 0x30cd1bdf087acd7f
    +613, 0x7491ced4e009adbe
    +614, 0xdcd942df1e2e7023
    +615, 0xfe63f01689fee35
    +616, 0x80282dfe5eaedc42
    +617, 0x6ecdea86495f8427
    +618, 0xe0adfdd5e9ed31c3
    +619, 0xf32bd2a7418127e
    +620, 0x8aabba078db6ee2
    +621, 0xa8a8e60499145aca
    +622, 0xf76b086ac4e8a0f2
    +623, 0x6e55b3c452ff27f8
    +624, 0xe18fa7cd025a71bf
    +625, 0xeed7b685fde0fa25
    +626, 0xba9b6c95867fa721
    +627, 0x4c2603bc69de2df2
    +628, 0xaac87eee1b58cd66
    +629, 0x3c9af6656e01282c
    +630, 0x2dfa05ce8ff476b6
    +631, 0xeae9143fcf92f23d
    +632, 0x3f0699f631be3bc8
    +633, 0xa0f5f79f2492bd67
    +634, 0x59c47722388131ed
    +635, 0x5f6e9d2941cef1de
    +636, 0xe9ad915c09788b7b
    +637, 0x92c6d37e4f9482f5
    +638, 0x57d301b7fdadd911
    +639, 0x7e952d23d2a8443
    +640, 0xbb2fa5e0704b3871
    +641, 0xe5642199be36e2d5
    +642, 0x5020b60d54358291
    +643, 0xa0b6317ec3f60343
    +644, 0xb57b08b99540bc5c
    +645, 0x21f1890adc997a88
    +646, 0xfcf824200dd9da2d
    +647, 0x8146293d83d425d1
    +648, 0xdadfbf5fbb99d420
    +649, 0x1eb9bbc5e6482b7d
    +650, 0xd40ff44f1bbd0f1c
    +651, 0xa9f948ba2d08afa5
    +652, 0x638cc07c5301e601
    +653, 0x1f984baa606e14e8
    +654, 0x44e153671081f398
    +655, 0xb17882eeb1d77a5d
    +656, 0x5fd8dbee995f14c
    +657, 0xff3533e87f81b7fe
    +658, 0x2f44124293c49795
    +659, 0x3bf6b51e9360248
    +660, 0x72d615edf1436371
    +661, 0x8fc5cf4a38adab9d
    +662, 0xfa517e9022078374
    +663, 0xf356733f3e26f4d8
    +664, 0x20ea099cdc6aad40
    +665, 0xe15b977deb37637d
    +666, 0xcc85601b89dae88d
    +667, 0x5768c62f8dd4905c
    +668, 0xa43cc632b4e56ea
    +669, 0xc4240cf980e82458
    +670, 0xb194e8ffb4b3eeb6
    +671, 0xee753cf2219c5fa1
    +672, 0xfe2500192181d44d
    +673, 0x2d03d7d6493dd821
    +674, 0xff0e787bb98e7f9b
    +675, 0xa05cf8d3bd810ce7
    +676, 0x718d5d6dcbbdcd65
    +677, 0x8d0b5343a06931c
    +678, 0xae3a00a932e7eaf9
    +679, 0x7ed3d8f18f983e18
    +680, 0x3bb778ee466dc143
    +681, 0x711c685c4e9062c0
    +682, 0x104c3af5d7ac9834
    +683, 0x17bdbb671fb5d5cf
    +684, 0xabf26caead4d2292
    +685, 0xa45f02866467c005
    +686, 0xf3769a32dc945d2d
    +687, 0xe78d0007f6aabb66
    +688, 0x34b60be4acbd8d4b
    +689, 0x58c0b04b69359084
    +690, 0x3a8bb354c212b1
    +691, 0x6b82a8f3d70058d5
    +692, 0x405bdef80a276a4a
    +693, 0xe20ca40ee9195cad
    +694, 0xf5dd96ba2446fefd
    +695, 0xc1e180c55fe55e3c
    +696, 0xa329caf6daa952b3
    +697, 0xb4809dd0c84a6b0a
    +698, 0xd27f82661070cee7
    +699, 0xa7121f15ee2b0d8a
    +700, 0x4bdaea70d6b34583
    +701, 0xe821dc2f310f7a49
    +702, 0x4c00a5a68e76f647
    +703, 0x331065b064a2d5ea
    +704, 0xac0c2ce3dc04fa37
    +705, 0x56b32b37b8229008
    +706, 0xe757cdb51534fcfa
    +707, 0xd3ff183576b2fad7
    +708, 0x179e1f4190f197a7
    +709, 0xf874c626a7c9aae5
    +710, 0xd58514ffc37c80e4
    +711, 0xc65de31d33fa7fd3
    +712, 0x6f6637052025769b
    +713, 0xca1c6bdadb519cc0
    +714, 0xd1f3534cde37828a
    +715, 0xc858c339eee4830a
    +716, 0x2371eacc215e02f4
    +717, 0x84e5022db85bbbe9
    +718, 0x5f71c50bba48610e
    +719, 0xe420192dad9c323f
    +720, 0x2889342721fca003
    +721, 0x83e64f63334f501d
    +722, 0xac2617172953f2c
    +723, 0xfa1f78d8433938ff
    +724, 0x5578382760051462
    +725, 0x375d7a2e3b90af16
    +726, 0xb93ff44e6c07552d
    +727, 0xded1d5ad811e818c
    +728, 0x7cf256b3b29e3a8c
    +729, 0x78d581b8e7bf95e8
    +730, 0x5b69192f2caa6ad3
    +731, 0xa9e25855a52de3ce
    +732, 0x69d8e8fc45cc188d
    +733, 0x5dd012c139ad347d
    +734, 0xfcb01c07b77db606
    +735, 0x56253e36ab3d1cce
    +736, 0x1181edbb3ea2192
    +737, 0x325bef47ff19a08d
    +738, 0xd3e231ceb27e5f7
    +739, 0x8e819dd2de7956d2
    +740, 0x34a9689fe6f84a51
    +741, 0x3e4eeb719a9c2927
    +742, 0x5c3b3440581d0aaf
    +743, 0x57caf51897d7c920
    +744, 0xec6a458130464b40
    +745, 0xe98f044e0da40e9b
    +746, 0xbe38662020eeb8e7
    +747, 0x7b8c407c632724ae
    +748, 0x16c7cfa97b33a544
    +749, 0xd23359e2e978ae5a
    +750, 0x4fdba458250933dd
    +751, 0x3c9e0713cfe616ba
    +752, 0x6f0df87b13163b42
    +753, 0xc460902cb852cc97
    +754, 0x289df8fefd6b0bce
    +755, 0x4ac2a2a1c3fb8029
    +756, 0x2fc3e24d8b68eef7
    +757, 0x34564386a59aab9a
    +758, 0x31047391ebd67ce4
    +759, 0x6c23d070a0564d41
    +760, 0xba6387b2b72545f7
    +761, 0xcdcf1008058387af
    +762, 0xc9308fa98db05192
    +763, 0xdbdbb5abd01a9d84
    +764, 0x937088275c7804ab
    +765, 0x6f6accfefe34ee81
    +766, 0x5c33c74c49cfdb2c
    +767, 0x5e1a771edfb92bd3
    +768, 0x6e89b009069ecae7
    +769, 0x34d64e17ec0e8968
    +770, 0x841203d0cde0c330
    +771, 0x7642cc9d7eb9e9cb
    +772, 0xca01d2e8c128b97e
    +773, 0x5b8390617b3304ab
    +774, 0x52ec4ed10de1eb2d
    +775, 0xb90f288b9616f237
    +776, 0x5bd43cd49617b2e2
    +777, 0x1a53e21d25230596
    +778, 0x36ccd15207a21cd6
    +779, 0xc8263d780618fd3c
    +780, 0x6eb520598c6ce1cb
    +781, 0x493c99a3b341564f
    +782, 0xab999e9c5aa8764f
    +783, 0xab2fa4ceaba84b
    +784, 0xbbd2f17e5cb2331b
    +785, 0xc8b4d377c0cc4e81
    +786, 0x31f71a6e165c4b1e
    +787, 0xd1011e55fb3addaa
    +788, 0x5f7ec34728dfa59
    +789, 0x2aef59e60a84eb0f
    +790, 0x5dde6f09aec9ad5f
    +791, 0x968c6cdbc0ef0438
    +792, 0x1957133afa15b13a
    +793, 0xbaf28f27573a64c2
    +794, 0xc6f6ddd543ebf862
    +795, 0xdd7534315ec9ae1e
    +796, 0xd2b80cd2758dd3b
    +797, 0xa38c3da00cc81538
    +798, 0x15c95b82d3f9b0f9
    +799, 0x6704930287ce2571
    +800, 0x9c40cc2f6f4ecb0c
    +801, 0xc8de91f50b22e94e
    +802, 0x39272e8fddbfdf0a
    +803, 0x879e0aa810a117d
    +804, 0xa312fff4e9e5f3bd
    +805, 0x10dd747f2835dfec
    +806, 0xeb8466db7171cdae
    +807, 0xaa808d87b9ad040a
    +808, 0xab4d2229a329243a
    +809, 0x7c622f70d46f789c
    +810, 0x5d41cef5965b2a8e
    +811, 0xce97ec4702410d99
    +812, 0x5beba2812c91211b
    +813, 0xf134b46c93a3fec7
    +814, 0x76401d5630127226
    +815, 0xc55fc9d9eacd4ec1
    +816, 0xaec8cefaa12f813f
    +817, 0x2f845dcfd7b00722
    +818, 0x3380ab4c20885921
    +819, 0xdb68ad2597691b74
    +820, 0x8a7e4951455f563f
    +821, 0x2372d007ed761c53
    +822, 0xcab691907714c4f1
    +823, 0x16bc31d6f3abec1a
    +824, 0x7dff639fbcf1824
    +825, 0x6666985fbcff543d
    +826, 0xb618948e3d8e6d0c
    +827, 0x77b87837c794e068
    +828, 0xcd48288d54fcb5a8
    +829, 0x47a773ed6ae30dc3
    +830, 0xba85ae44e203c942
    +831, 0xa7a7b21791a25b2d
    +832, 0x4029dd92e63f19e0
    +833, 0xc2ad66ab85e7d5aa
    +834, 0xa0f237c96fdab0db
    +835, 0xffefb0ab1ca18ed
    +836, 0x90cb4500785fd7d5
    +837, 0xa7dd3120f4876435
    +838, 0x53f7872624694300
    +839, 0xea111326ff0040d9
    +840, 0x5f83cb4cce40c83b
    +841, 0x918e04936c3b504d
    +842, 0x87a8db4c0e15e87c
    +843, 0x7cff39da6a0dedd0
    +844, 0x36f7de2037f85381
    +845, 0xd1d8d94022a1e9a7
    +846, 0x2c9930127dc33ec9
    +847, 0x6cb4719dcd0101c6
    +848, 0xc01868cde76935f7
    +849, 0x6b86f2ec1ab50143
    +850, 0x68af607d8d94ae61
    +851, 0xe216c5b95feedf34
    +852, 0x4b866bd91efe2e4b
    +853, 0x4bff79df08f92c99
    +854, 0x6ff664ea806acfd1
    +855, 0x7fce0b3f9ece39bc
    +856, 0x29bc90b59cb3db97
    +857, 0x833c4b419198607d
    +858, 0xf3573e36ca4d4768
    +859, 0x50d71c0a3c2a3fa8
    +860, 0xd754591aea2017e7
    +861, 0x3f9126f1ee1ebf3
    +862, 0xe775d7f4b1e43de8
    +863, 0xe93d51628c263060
    +864, 0x83e77f6fb32d6d82
    +865, 0x43dd7eef823408e4
    +866, 0x1c843c2c90180662
    +867, 0xe924dafb9a16066b
    +868, 0x6af3ee96e7b7fbd9
    +869, 0x94d5c4f37befcd1f
    +870, 0x40ffb04bedef4236
    +871, 0x71c17bbc20e553e
    +872, 0x101f7a0a6208729f
    +873, 0x5ca34570cf923548
    +874, 0x8e3139db2e96e814
    +875, 0x3ab96d96263d048d
    +876, 0x97f3c0bbc6755c3c
    +877, 0x31fc72daedaef3dc
    +878, 0x71f8d7855d10789b
    +879, 0xce6dc97b4662333b
    +880, 0xfddc2aabd342bc61
    +881, 0xefbd4007ff8c7d2e
    +882, 0xf72cd6c689ef8758
    +883, 0x932c8b0c0e755137
    +884, 0x94cc4dedd58ff69
    +885, 0xde4dfd6890535979
    +886, 0xdb00dcd2dcb4a50a
    +887, 0xb0466240b4548107
    +888, 0x9cb9264c7b90d1a3
    +889, 0x357e378e9be5766b
    +890, 0x6e0316ef03367bbf
    +891, 0x201ea18839544ca
    +892, 0x803ff3406be5f338
    +893, 0xf9d5e82fd4144bb2
    +894, 0x1b6b88ca701e9f47
    +895, 0xd1fe5ab8e1f89cc0
    +896, 0x14171fe176c4bece
    +897, 0x887948bdef78beaa
    +898, 0x80449ddc3eb9b977
    +899, 0x5f4e1f900fb4bcf3
    +900, 0xbe30f8701909f8e2
    +901, 0xd1f2a2fb5503306d
    +902, 0x6b1c77238dc23803
    +903, 0x102156a6c9860f66
    +904, 0x4cd446e099edf4c1
    +905, 0xc79ac6cbc911f33b
    +906, 0x3ee096ffe3384f1c
    +907, 0xb58f83b18a306dc7
    +908, 0x9f76582141de56b2
    +909, 0x9ddfa85e02c13866
    +910, 0x4d9a19d4ce90a543
    +911, 0xbf81ab39fd17d376
    +912, 0x5327e5054c6a74f1
    +913, 0xd5062dd31db1a9b7
    +914, 0x645853735527edc
    +915, 0x485393967f91af08
    +916, 0xeff9667dcf77ca68
    +917, 0xd012313f5fbec464
    +918, 0xbeae35bdfae55144
    +919, 0x302c41ebac8444a0
    +920, 0x9ccdb6c2fe58fba8
    +921, 0x567753af68ed23f8
    +922, 0xff90f790e43efec3
    +923, 0x970cc756fb799696
    +924, 0xe59239d1c44915
    +925, 0x4d2d189fb3941f05
    +926, 0x96f23085db165a9c
    +927, 0xa1202dec7a37b1a5
    +928, 0xc0c1ee74bcd7dc1a
    +929, 0x9edcf2048b30333a
    +930, 0xd848588ba7e865fb
    +931, 0x8d9f0897317cab40
    +932, 0x67b96f15e25924fb
    +933, 0xefc8d8536619ee42
    +934, 0xf3f621d22bdde0c2
    +935, 0x68610a0de862ae32
    +936, 0xa22ca5142de24cbd
    +937, 0x8815452f4e6b4801
    +938, 0x4e9c1b607b2750e5
    +939, 0x19b3c09ba6fc9b25
    +940, 0x9b2543c8836780ac
    +941, 0xe702b8f950e56431
    +942, 0xb357cc329cac3917
    +943, 0x387bf86a17a31e08
    +944, 0x9940b983d331b163
    +945, 0xf5d89d7fe9095e18
    +946, 0x4362682329e5c4d1
    +947, 0xd2132573f6ae7b42
    +948, 0xc0a5849e23a61606
    +949, 0xdadbddf47265bc02
    +950, 0x1b96f00339a705f7
    +951, 0x94e6642329288913
    +952, 0x825ab3f10e6d330b
    +953, 0x1a1c31ac9d883ea0
    +954, 0xb49076b7155c6f47
    +955, 0x920cf3085dfe3ccb
    +956, 0x9743407c9f28e825
    +957, 0x6ce8a28622402719
    +958, 0xce2fe67e06baf8a6
    +959, 0x3a16b34784ecf5e6
    +960, 0x140467cc1d162a0c
    +961, 0x32d4772692ab625
    +962, 0xa4f4b28562f43336
    +963, 0x885b4335457bd84a
    +964, 0x499d3ed26c87ad8a
    +965, 0xc7328bcedb9a545e
    +966, 0xc6dd76a6cbf5d2b2
    +967, 0xba9c22be404ee1aa
    +968, 0x70e6aee45f23521d
    +969, 0x61e03a798593c177
    +970, 0x171671f809c68213
    +971, 0x28d54872fc1d914c
    +972, 0x43c2fcd9bd098b53
    +973, 0x172ad4c4a98b9d37
    +974, 0x330860c9460f2516
    +975, 0x49547f472df984f4
    +976, 0x873b2436d3f0e114
    +977, 0x6f99accf4ea050b6
    +978, 0x5968ac874ed51613
    +979, 0x4939d70d29a3c611
    +980, 0x11f381ed28738d3d
    +981, 0xa97430d36ab3a869
    +982, 0xe6fa880801129e22
    +983, 0xf84decbd8f48c913
    +984, 0x4425c0ed1e9a82a5
    +985, 0x7a1f9485e9929d5a
    +986, 0xc7c51f155dfce1c6
    +987, 0x9619a39501d74f2b
    +988, 0x7c7035955dbf4c1b
    +989, 0xc61ee569cf57c2c9
    +990, 0x3eaf7c5b0df734e1
    +991, 0xe71cb4064d1ede05
    +992, 0x356e3cec80e418b2
    +993, 0xca04306243a15be6
    +994, 0x941cf3881fa18896
    +995, 0x30dbb0e819d644e0
    +996, 0xaae22c0bef02859a
    +997, 0x7bd30917bbaa8a94
    +998, 0x2672547bc8d7d329
    +999, 0x4955c92aaa231578
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/data/pcg64dxsm-testset-2.csv b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/data/pcg64dxsm-testset-2.csv
    new file mode 100644
    index 0000000000000000000000000000000000000000..878c5ea7c3a5173467a4f945947174149f3913e6
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/data/pcg64dxsm-testset-2.csv
    @@ -0,0 +1,1001 @@
    +seed, 0x0
    +0, 0xd97e4a147f788a70
    +1, 0x8dfa7bce56e3a253
    +2, 0x13556ed9f53d3c10
    +3, 0x55dbf1c241341e98
    +4, 0xa2cd98f722eb0e0a
    +5, 0x83dfc407203ade8
    +6, 0xeaa083df518f030d
    +7, 0x44968c87e432852b
    +8, 0x573107b9cb8d9ecc
    +9, 0x9eedd1da50b9daca
    +10, 0xb33a6735ca451e3c
    +11, 0x72830d2b39677262
    +12, 0x9da8c512fd0207e8
    +13, 0x1fc5c91954a2672b
    +14, 0xd33479437116e08
    +15, 0x9ccdd9390cee46f3
    +16, 0x1fd39bb01acd9e76
    +17, 0xedc1869a42ff7fe5
    +18, 0xbd68ca0b42a6e7e9
    +19, 0x620b67df09621b1f
    +20, 0xfa11d51bd6950221
    +21, 0xc8c45b36e7d28d08
    +22, 0xe9c91272fbaad777
    +23, 0x2dc87a143f220e90
    +24, 0x6376a7c82361f49d
    +25, 0x552c5e434232fe75
    +26, 0x468f7f872ac195bc
    +27, 0x32bed6858125cf89
    +28, 0xe4f06111494d09d3
    +29, 0xa5c166ffea248b80
    +30, 0x4e26605b97064a3f
    +31, 0xceafd9f6fc5569d
    +32, 0xb772f2f9eed9e106
    +33, 0x672c65e6a93534e2
    +34, 0xcdc5e1a28d1bd6a0
    +35, 0x1ed9c96daeebd3e3
    +36, 0x4d189dcfc0c93c3f
    +37, 0x50df5a95c62f4b43
    +38, 0xcccf4949fa65bbb8
    +39, 0x19b8073d53cdc984
    +40, 0x6fb40bba35483703
    +41, 0xb02de4aef86b515a
    +42, 0x4d90c63655350310
    +43, 0xea44e4089825b16c
    +44, 0x8d676958b1f9da2b
    +45, 0x6d313940917ae195
    +46, 0x1b1d35a4c1dd19f4
    +47, 0x117720f8397337ef
    +48, 0xcc073cf3ac11eeaa
    +49, 0x8331ec58a9ff8acb
    +50, 0xf3dc2a308b6b866f
    +51, 0x7eba1202663382b6
    +52, 0x8269839debeb4e5a
    +53, 0x87fd3dc0f9181a8e
    +54, 0xabe62ddd3c925f03
    +55, 0x7f56f146944fe8d4
    +56, 0xc535972150852068
    +57, 0x60b252d453bd3a68
    +58, 0x4251f0134634490a
    +59, 0x338950da210dfeb2
    +60, 0xcadfe932971c9471
    +61, 0xfb7049457fab470e
    +62, 0x9bfb8145a4459dff
    +63, 0x4a89dda3898f9d8a
    +64, 0x88cc560151483929
    +65, 0x277dc820f4b6796e
    +66, 0x3524bd07ea0afb88
    +67, 0x92eb6ffb2bf14311
    +68, 0xf6559be0783f3fe9
    +69, 0xf0844f9af54af00d
    +70, 0xdd5e0b59adcef8a
    +71, 0x4ff7e4f2ab18554c
    +72, 0x3fa22c8a02634587
    +73, 0x1db8e1a9442fe300
    +74, 0x40cf15953ad3d3e7
    +75, 0x92af15fe1a9f6f0a
    +76, 0xab4a0e466fb0cfd
    +77, 0x944f1555a06cca82
    +78, 0x10cf48412f1f6066
    +79, 0x7f51f9a455f9e8e1
    +80, 0x47ee93530f024c7e
    +81, 0x36cf2f0413e0f6f2
    +82, 0xa315e23731969407
    +83, 0xd8e2796327cf5f87
    +84, 0xa86072696a555c34
    +85, 0xee3f0b8804feaab7
    +86, 0x41e80dc858f8360b
    +87, 0x31ec2e9b78f5b29
    +88, 0xd397fb9b8561344c
    +89, 0x28081e724e649b74
    +90, 0x5c135fc3fc672348
    +91, 0x9a276ca70ce9caa0
    +92, 0x9216da059229050a
    +93, 0xcf7d375ed68007b0
    +94, 0xa68ad1963724a770
    +95, 0xd4350de8d3b6787c
    +96, 0xee7d2c2cc275b6d2
    +97, 0x71645ec738749735
    +98, 0x45abdf8c68d33dbb
    +99, 0xe71cadb692c705ea
    +100, 0x60af6f061fd90622
    +101, 0x1eabe2072632c99d
    +102, 0x947dda995a402cb6
    +103, 0xbb19f49a3454f3b
    +104, 0xe6e43e907407758c
    +105, 0xfe2b67016bd6873a
    +106, 0x7fdb4dd8ab30a722
    +107, 0x39d3265b0ff1a45b
    +108, 0xed24c0e4fce8d0c2
    +109, 0xf6e074f86faf669d
    +110, 0x9142040df8dc2a79
    +111, 0x9682ab16bc939a9c
    +112, 0x6a4e80c378d971c8
    +113, 0x31309c2c7fc2d3d6
    +114, 0xb7237ec682993339
    +115, 0x6a30c06bb83dccd9
    +116, 0x21c8e9b6d8e7c382
    +117, 0x258a24ae6f086a19
    +118, 0xb76edb5be7df5c35
    +119, 0x3c11d7d5c16e7175
    +120, 0xbdfc34c31eff66e1
    +121, 0x8af66e44be8bf3a2
    +122, 0x3053292e193dec28
    +123, 0xd0cc44545b454995
    +124, 0x408ac01a9289d56
    +125, 0x4e02d34318ec2e85
    +126, 0x9413ff3777c6eb6b
    +127, 0xa3a301f8e37eb3df
    +128, 0x14e6306bd8d8f9f9
    +129, 0xd3ea06ce16c4a653
    +130, 0x170abe5429122982
    +131, 0x7f9e6fddc6cacb85
    +132, 0xa41b93e10a10a4c8
    +133, 0x239216f9d5b6d0b5
    +134, 0x985fcb6cb4190d98
    +135, 0xb45e3e7c68f480c6
    +136, 0xc1b2fc2e0446211c
    +137, 0x4596adb28858c498
    +138, 0x2dd706f3458ddc75
    +139, 0x29c988c86f75464
    +140, 0xac33a65aa679a60
    +141, 0xa28fef762d39d938
    +142, 0x541e6fa48647f53
    +143, 0x27838d56b2649735
    +144, 0x8e143d318a796212
    +145, 0xaea6097745f586b8
    +146, 0x636143330f8ee2e6
    +147, 0xc2d05fd8b945b172
    +148, 0x6e355f9eb4353055
    +149, 0xeb64ca42e8bf282e
    +150, 0xe8202dfd9da0fe5
    +151, 0x7305689c9d790cba
    +152, 0xf122f8b1bef32970
    +153, 0x9562887e38c32ba5
    +154, 0xf9cd9be121b738d
    +155, 0x6238e0c398307913
    +156, 0x5f2e79bb07c30f47
    +157, 0x8ce8e45c465006e
    +158, 0x39281fe1e99e2441
    +159, 0xafb10c2ca2874fea
    +160, 0x6e52f91633f83cf
    +161, 0x8ff12c1ac73c4494
    +162, 0xe48608a09365af59
    +163, 0xefd9bbc7e76e6a33
    +164, 0xbe16a39d5c38ec92
    +165, 0x6a6ffbcaf5a2330f
    +166, 0xdd5d6ac7d998d43d
    +167, 0x207bf978226d4f11
    +168, 0xf8eec56bd2a0f62e
    +169, 0xa5bccf05dce0d975
    +170, 0x93cf3ec1afe457a6
    +171, 0x38651466d201f736
    +172, 0x3ad21473985c9184
    +173, 0xc6407a3bd38c92a6
    +174, 0xb1ec42c7afa90a25
    +175, 0xbdeca984df8b7dd3
    +176, 0xb6926b1d00aa6c55
    +177, 0x86141d0022352d49
    +178, 0x169316256135ee09
    +179, 0xffb1c7767af02a5c
    +180, 0x502af38ad19f5c91
    +181, 0xfbf6cbc080086658
    +182, 0x33cf9b219edae501
    +183, 0x46e69bebd77b8862
    +184, 0xf11e0cc91125d041
    +185, 0xb4cd1649f85e078f
    +186, 0xb49be408db4e952
    +187, 0xb0b8db46140cce3c
    +188, 0xba647f2174012be7
    +189, 0x4f0a09e406970ac9
    +190, 0xf868c7aec9890a5c
    +191, 0xde4c8fa7498ea090
    +192, 0x872ceb197978c1d4
    +193, 0x1eb5cd9c3269b258
    +194, 0x3ea189f91724f014
    +195, 0x41379656f7746f2c
    +196, 0x7bd18493aca60e51
    +197, 0x5380c23b0cbbf15e
    +198, 0x920b72835f88246b
    +199, 0x24d7f734a4548b8e
    +200, 0x9944edb57e5aa145
    +201, 0x4628e136ebb8afe1
    +202, 0xb4ee6a776356e2a7
    +203, 0x481cbe9744ccf7d7
    +204, 0x7e8d67e8b0b995d9
    +205, 0xeeacde100af7b47e
    +206, 0x103da08f2487dab7
    +207, 0x6b9890a91d831459
    +208, 0xd0c5beae37b572c7
    +209, 0xfdccc371ee73fcc
    +210, 0x65438f0a367a2003
    +211, 0x5d23b2c818a7e943
    +212, 0x9a8ed45ac04b58b3
    +213, 0xdaf3c3f1695dce10
    +214, 0x5960eec706fa2bc0
    +215, 0x98ca652facb80d40
    +216, 0x72970ae5e2194143
    +217, 0x18c6374d878c5c94
    +218, 0x20fa51f997381900
    +219, 0x3af253dba26d6e1d
    +220, 0x1b23d65db15c7f78
    +221, 0x9f53ae976259b0e3
    +222, 0x9a6addb28dc92d49
    +223, 0x1e085c4accd0a7d7
    +224, 0xe9d3f4cc9bad6ce5
    +225, 0xe018fad78b5b1059
    +226, 0x5ef7682232b4b95
    +227, 0xb2242aa649f5de80
    +228, 0x8f3e6d8dd99b9e4e
    +229, 0xb9be6cc22949d62a
    +230, 0xecbdc7beaa5ff1fe
    +231, 0xd388db43a855bdf0
    +232, 0xd71ee3238852568d
    +233, 0x85ab3056304c04b5
    +234, 0x2ed7ae7ad3cfc3cb
    +235, 0x781d1b03d40b6c48
    +236, 0x7d3c740886657e6d
    +237, 0x982cfa6828daa6b0
    +238, 0x278579599c529464
    +239, 0x773adecfae9f0e08
    +240, 0x63a243ea4b85c5d7
    +241, 0x59940074fc3709e1
    +242, 0xc914a2eed58a6363
    +243, 0x2602b04274dd724c
    +244, 0xdf636eb7636c2c42
    +245, 0x891a334d0d26c547
    +246, 0xde8cd586d499e22d
    +247, 0x3ea1aa4d9b7035b6
    +248, 0xd085cff6f9501523
    +249, 0xe82a872f374959e
    +250, 0x55cb495bbd42cc53
    +251, 0x5f42b3226e56ca97
    +252, 0xea463f6f203493a3
    +253, 0xeef3718e57731737
    +254, 0x1bd4f9d62b7f9f3c
    +255, 0x19284f5e74817511
    +256, 0xaf6e842c7450ca87
    +257, 0x1d27d2b08a6b3600
    +258, 0xfb4b912b396a52e3
    +259, 0x30804d4c5c710121
    +260, 0x4907e82564e36338
    +261, 0x6441cf3b2900ddb7
    +262, 0xd76de6f51988dc66
    +263, 0x4f298ef96fd5e6d2
    +264, 0x65432960c009f83d
    +265, 0x65ebed07e1d2e3df
    +266, 0xf83ee8078febca20
    +267, 0x7bb18e9d74fc5b29
    +268, 0x597b5fbc2261d91
    +269, 0xea4f8ed0732b15b2
    +270, 0xba2267f74f458268
    +271, 0x3f304acabd746bbb
    +272, 0x7bd187af85659a82
    +273, 0x88e20dbdb7a08ea3
    +274, 0x2a2dc948c772fcb4
    +275, 0x87784fec2993c867
    +276, 0x89163933cd362d4e
    +277, 0xfd7b24f04302f957
    +278, 0x9bdd544405dfb153
    +279, 0xddee0fac58ffc611
    +280, 0xa8e8993417e71ec1
    +281, 0x55e0ab46ff7757af
    +282, 0x53e7645f08d3d7df
    +283, 0xbf78e563bc656ba2
    +284, 0x1d162253b45ee2de
    +285, 0x15e2bfefedf29eb4
    +286, 0x4e2a4584aa394702
    +287, 0xa89fb12b01525897
    +288, 0x825bd98f0544e4df
    +289, 0xfc6c50da6750700
    +290, 0xc24aaabde7d28423
    +291, 0x79d6f4660fcb19e5
    +292, 0xee7d4fb40c8d659f
    +293, 0x70bc281b462e811d
    +294, 0x23ed4dc9636519a7
    +295, 0xcb7c3f5a5711b935
    +296, 0xe73090e0508c5d9d
    +297, 0xb25a331f375952a6
    +298, 0xa64c86e0c04740f6
    +299, 0xb8f3ffc8d56ac124
    +300, 0x2479266fc5ee6b15
    +301, 0x8d5792d27f5ffbcb
    +302, 0xb064298be946cd52
    +303, 0xf0934a98912ffe26
    +304, 0xbe805682c6634d98
    +305, 0xe0e6e2c010012b4f
    +306, 0x58c47d475f75976
    +307, 0x358c9a6e646b2b4a
    +308, 0x7e7c4ffca5b17ba7
    +309, 0x43585c8c9a24a04c
    +310, 0x5154ddbcd68d5c2c
    +311, 0x4a2b062d3742a5e
    +312, 0xca5691191da2b946
    +313, 0x696a542109457466
    +314, 0x9eb5d658a5022ba5
    +315, 0x8158cf6b599ab8dc
    +316, 0x1b95391eaa4af4a6
    +317, 0x9953e79bd0fc3107
    +318, 0x8639690086748123
    +319, 0x2d35781c287c6842
    +320, 0x393ef0001cd7bc8f
    +321, 0xe3a61be8c5f2c22a
    +322, 0x5e4ff21b847cc29b
    +323, 0x4c9c9389a370eb84
    +324, 0xd43a25a8fc3635fa
    +325, 0xf6790e4a85385508
    +326, 0x37edf0c81cb95e1d
    +327, 0x52db00d6e6e79af8
    +328, 0x3b202bceeb7f096
    +329, 0x2a164a1c776136bb
    +330, 0x73e03ee3fd80fd1b
    +331, 0xd2c58c0746b8d858
    +332, 0x2ed2cb0038153d22
    +333, 0x98996d0fc8ceeacc
    +334, 0xa4ed0589936b37f
    +335, 0x5f61cf41a6d2c172
    +336, 0xa6d4afb538c110d7
    +337, 0xe85834541baadf1a
    +338, 0x4c8967107fd49212
    +339, 0x49bafb762ab1a8c1
    +340, 0x45d540e2a834bf17
    +341, 0x1c0ec8b4ed671dac
    +342, 0x3d503ce2c83fe883
    +343, 0x437bfffd95f42022
    +344, 0xc82d1e3d5c2bc8d2
    +345, 0x7a0a9cbfcb0d3f24
    +346, 0xc0a4f00251b7a3be
    +347, 0xb5be24e74bb6a1c6
    +348, 0xa3104b94b57545b1
    +349, 0x86de7d0c4b97b361
    +350, 0x879c1483f26538a6
    +351, 0xd74c87557f6accfb
    +352, 0x2f9be40dbf0fe8a1
    +353, 0x445a93398f608d89
    +354, 0x7b3cb8a7211d7fdc
    +355, 0xe86cc51290d031e7
    +356, 0x33ef3594052ad79f
    +357, 0xc61911d241dbb590
    +358, 0x37cccb0c0e3de461
    +359, 0xb75259124080b48b
    +360, 0xd81e8961beb4abe5
    +361, 0xf4542deb84a754e
    +362, 0x6ea036d00385f02e
    +363, 0xa7b60b0ac3b88681
    +364, 0x108a6c36ca30baf5
    +365, 0x4a2adc5bbfe2bf07
    +366, 0x4079501f892a5342
    +367, 0x55e113963c5448f0
    +368, 0x8019ff4903b37242
    +369, 0x109c6dcdb7ec6618
    +370, 0x1239ac50944da450
    +371, 0xe1399c7f94c651c1
    +372, 0x5a6bbbae388d365a
    +373, 0x4d72be57b8810929
    +374, 0x3f067df24384e1fb
    +375, 0x4f8b9e0f7f6c7be
    +376, 0x202492c342a3b08
    +377, 0x250753192af93a3
    +378, 0xfba1159d9de2cb8e
    +379, 0xba964497ab05505c
    +380, 0x1329ec5d8a709dca
    +381, 0x32927cacb6cd22bb
    +382, 0x6b4d7db904187d56
    +383, 0xe76adccf8e841e02
    +384, 0x8c4bf4b6a788202
    +385, 0x3013a3b409831651
    +386, 0x7427d125c475412f
    +387, 0x84dcc4bb2bf43202
    +388, 0x117526f1101372a5
    +389, 0xfe95d64b8984bd72
    +390, 0x524e129934cc55c1
    +391, 0xc3db4b0418c36d30
    +392, 0xe1cb2047e9c19f7a
    +393, 0xea43d6c8d8982795
    +394, 0xe80ac8a37df89ed
    +395, 0xfecc2104329ed306
    +396, 0xa5c38aac9c1d51ea
    +397, 0x3abe5d1c01e4fe17
    +398, 0x717a805d97fcc7ac
    +399, 0x94441f8207a1fb78
    +400, 0x22d7869c5f002607
    +401, 0x349e899f28c3a1b9
    +402, 0x5639950cdea92b75
    +403, 0x7e08450497c375b
    +404, 0x94bf898b475d211d
    +405, 0x75c761a402375104
    +406, 0x1930920ec9d2a1e7
    +407, 0xb774ba1bc6f6e4e2
    +408, 0xf715602412e5d900
    +409, 0x87bb995f4a13f0ba
    +410, 0xa3c787868dfa9c8d
    +411, 0xa17fd42a5a4f0987
    +412, 0x4a9f7d435242b86
    +413, 0x240364aff88f8aef
    +414, 0xe7cd4cf4bf39f144
    +415, 0xd030f313ca4c2692
    +416, 0xc46696f4e03ec1e9
    +417, 0x22c60f1ec21060b3
    +418, 0x16c88058fd68986f
    +419, 0x69ca448e8e6bde3f
    +420, 0x3466c2cdec218abd
    +421, 0x837ac4d05e6b117d
    +422, 0x911210e154690191
    +423, 0x9ece851d6fa358b7
    +424, 0x42f79cb0c45e7897
    +425, 0xbf7583babd7c499b
    +426, 0x2059fe8031c6e0b9
    +427, 0xabbec8fc00f7e51d
    +428, 0x88809d86a3a256e1
    +429, 0xd36056df829fdcb5
    +430, 0x515632b6cb914c64
    +431, 0xba76d06c2558874
    +432, 0x632c54ca4214d253
    +433, 0xadec487adf2cb215
    +434, 0x521e663e1940513d
    +435, 0xb1b638b548806694
    +436, 0xbe2d5bfbe57d2c72
    +437, 0x8b89e7719db02f7
    +438, 0x90ba5281c1d56e63
    +439, 0x899e1b92fceea102
    +440, 0xf90d918e15182fa6
    +441, 0x94a489ce96c948c4
    +442, 0xad34db453517fcd4
    +443, 0xc5264eb2de15930f
    +444, 0x101b4e6603a21cee
    +445, 0xef9b6258d6e85fff
    +446, 0x6075c7d6c048bd7a
    +447, 0x6f03232c64e438aa
    +448, 0x18c983d7105ee469
    +449, 0x3ffc23f5c1375879
    +450, 0xbc1b4a00afb1f9f
    +451, 0x5afa6b2bb8c6b46e
    +452, 0xe7fce4af2f2c152a
    +453, 0x5b00ab5c4b3982c7
    +454, 0x2d4b0c9c0eb4bd0c
    +455, 0x61d926270642f1f2
    +456, 0x7219c485c23a2377
    +457, 0x7e471c752fecd895
    +458, 0x23c4d30a4d17ba1f
    +459, 0x65cb277fe565ca22
    +460, 0xcbb56ed9c701363b
    +461, 0xfd04ab3a6eba8282
    +462, 0x19c9e5c8bab38500
    +463, 0xea4c15227676b65b
    +464, 0x20f3412606c8da6f
    +465, 0xb06782d3bf61a239
    +466, 0xf96e02d5276a9a31
    +467, 0x835d256b42aa52a6
    +468, 0x25b09151747f39c1
    +469, 0x64507386e1103eda
    +470, 0x51cbc05716ef88e4
    +471, 0x998cd9b7989e81cc
    +472, 0x9d7115416bec28d1
    +473, 0xc992ca39de97906b
    +474, 0xd571e6f7ca598214
    +475, 0xafc7fb6ccd9abbf8
    +476, 0x88ef456febff7bf4
    +477, 0xdbe87ccc55b157d2
    +478, 0xaab95e405f8a4f6d
    +479, 0xad586a385e74af4f
    +480, 0x23cd15225c8485aa
    +481, 0x370940bf47900ac7
    +482, 0xefd6afda1a4b0ead
    +483, 0x9cb1a4c90993dd7a
    +484, 0xff7893e8b2f70b11
    +485, 0xb09e1807c0638e8e
    +486, 0xb10915dcb4978f74
    +487, 0x88212ab0051a85eb
    +488, 0x7af41b76e1ec793f
    +489, 0x2e5c486406d3fefd
    +490, 0xebe54eff67f513cc
    +491, 0xab6c90d0876a79b8
    +492, 0x224df82f93fe9089
    +493, 0xc51c1ce053dc9cd2
    +494, 0x5ef35a4d8a633ee7
    +495, 0x4aca033459c2585f
    +496, 0xd066932c6eefb23d
    +497, 0x5309768aab9a7591
    +498, 0xa2a3e33823df37f9
    +499, 0xcec77ff6a359ee9
    +500, 0x784dc62d999d3483
    +501, 0x84e789fb8acc985d
    +502, 0xd590237e86aa60f
    +503, 0x737e2ffe1c8ad600
    +504, 0xc019c3a39a99eab8
    +505, 0x6a39e9836964c516
    +506, 0xe0fe43129535d9da
    +507, 0xdfc5f603d639d4de
    +508, 0x7b9a7d048a9c03b6
    +509, 0xbb5aa520faa27fdd
    +510, 0x2a09b4200f398fa2
    +511, 0x38cc88107904064e
    +512, 0xa9a90d0b2d92bb25
    +513, 0x9419762f87e987e3
    +514, 0x1a52c525153dedcd
    +515, 0xc26d9973dd65ae99
    +516, 0x8e89bd9d0dc6e6a1
    +517, 0x2f30868dc01bfb53
    +518, 0x20f09d99b46501c4
    +519, 0x78b468a563b8f1e9
    +520, 0xcccf34b0b6c380c7
    +521, 0xf554e7dc815297e6
    +522, 0x332a585cfb4a50ef
    +523, 0xa9fb64a2b6da41d7
    +524, 0xdcd2a5a337391ce0
    +525, 0x8a9bd3e324c6463d
    +526, 0x9f4487d725503bdd
    +527, 0xf72282d82f1d0ff
    +528, 0x308f4160abb72d42
    +529, 0x648de1db3a601b08
    +530, 0x36cab5192e7ebd39
    +531, 0x7975fbe4ab6a1c66
    +532, 0xd515b4d72243864e
    +533, 0x43a568f8b915e895
    +534, 0x15fa9f2057bdb91d
    +535, 0x7a43858ef7a222dc
    +536, 0x17b4a9175ac074fe
    +537, 0xa932c833b8d0f8f8
    +538, 0x1d2db93a9a587678
    +539, 0x98abd1d146124d27
    +540, 0xf0ab0431671740aa
    +541, 0xa9d182467540ad33
    +542, 0x41c8a6cfc331b7fc
    +543, 0xa52c6bd0fcd1d228
    +544, 0x2773c29a34dc6fa3
    +545, 0x3098230746fc1f37
    +546, 0xd63311bb4f23fabe
    +547, 0x6712bf530cd2faec
    +548, 0x342e8f342e42c4dd
    +549, 0xfbd83331851cdcad
    +550, 0xe903be1361bbc34d
    +551, 0xd94372e5077e3ef9
    +552, 0x95aaa234f194bd8
    +553, 0x20c0c8fb11e27538
    +554, 0xfaf47dc90462b30b
    +555, 0x8ddc6d144147682a
    +556, 0xf626833fd926af55
    +557, 0x5df93c34290d1793
    +558, 0xb06a903e6e9fca5e
    +559, 0x10c792dc851d77ca
    +560, 0xd9b1b817b18e56cb
    +561, 0x3a81730c408eb408
    +562, 0x65052c04a8d4b63c
    +563, 0x3328546598e33742
    +564, 0xeca44a13f62d156d
    +565, 0x69f83d1d86b20170
    +566, 0x937764200412027d
    +567, 0xc57eb1b58df0f191
    +568, 0xa1c7d67dce81bc41
    +569, 0x8e709c59a6a579ce
    +570, 0x776a2f5155d46c70
    +571, 0xd92906fbbc373aa5
    +572, 0xe97ad478a2a98bf6
    +573, 0xc296c8819ac815f
    +574, 0x613ede67ba70e93e
    +575, 0xe145222498f99cde
    +576, 0xafcdfa7a3c1cf9bf
    +577, 0x1c89252176db670d
    +578, 0xad245eda5c0865ff
    +579, 0x249463d3053eb917
    +580, 0xc9be16d337517c0b
    +581, 0xefcc82bf67b8f731
    +582, 0x1e01577d029e0d00
    +583, 0xad9c24b2a4f3d418
    +584, 0xed2cceb510db4d0f
    +585, 0xbddadcdb92400c70
    +586, 0x67d6b0476ef82186
    +587, 0xbc7662ff7bf19f73
    +588, 0x9d94452a729e6e92
    +589, 0x6b278d8594f55428
    +590, 0x6c4b31cceb1b2109
    +591, 0xccc6c3a726701e9
    +592, 0x6bc28ece07df8925
    +593, 0xc0422b7bf150ccc4
    +594, 0xab7158f044e73479
    +595, 0xdf3347546d9ed83f
    +596, 0x3b3235a02c70dff4
    +597, 0x2551c49c14ea8d77
    +598, 0xee2f7f5bb3cc228e
    +599, 0x39b87bfe8c882d39
    +600, 0x7dd420fad380b51c
    +601, 0xffe64976af093f96
    +602, 0x4a4f48dc6e7eaa5f
    +603, 0x85f2514d32fdc8cc
    +604, 0x1ab1215fd7f94801
    +605, 0x4cd1200fc795b774
    +606, 0xcf8af463a38942ee
    +607, 0x319caa7ce3022721
    +608, 0x8cd9798a76d1aea4
    +609, 0x2bd3933ac7afd34e
    +610, 0x85d4c323403cf811
    +611, 0xd7b956d3064efa30
    +612, 0x67a078dbf1f13068
    +613, 0x665fa6c83e87c290
    +614, 0x9333ac2416d2469b
    +615, 0xdfb1fd21a0094977
    +616, 0xa1962a6e2c25f8ff
    +617, 0x1f3b10a7ed5287cf
    +618, 0x70641efb3d362713
    +619, 0xe527a2cf85d00918
    +620, 0x9741e45d3f9890a3
    +621, 0x6cb74b5d4d36db4b
    +622, 0xf24734d622bd2209
    +623, 0xadd6d94f78e9d378
    +624, 0xc3bbdb59225cca7f
    +625, 0x5ad36614275b30cd
    +626, 0x495568dd74eea434
    +627, 0xf35de47e0ffe1f2d
    +628, 0xefa209dca719ab18
    +629, 0x844ddcaeb5b99ae8
    +630, 0x37449670a1dc7b19
    +631, 0x5a4612c166f845c1
    +632, 0xe70f7782f2087947
    +633, 0x98d484deac365721
    +634, 0x705302198cf52457
    +635, 0x7135ae0f5b77df41
    +636, 0x342ac6e44a9b6fc3
    +637, 0x2713fd2a59af5826
    +638, 0x6e1a3f90f84efa75
    +639, 0x9fb3b4dd446ca040
    +640, 0x530044ae91e6bd49
    +641, 0xe984c4183974dc3e
    +642, 0x40c1fa961997d066
    +643, 0xb7868250d8c21559
    +644, 0x8bc929fa085fd1de
    +645, 0x7bdb63288dc8733e
    +646, 0xac4faad24326a468
    +647, 0x1c6e799833aea0b1
    +648, 0xcc8a749e94f20f36
    +649, 0x4e7abfd0443547c5
    +650, 0xb661c73bb8caa358
    +651, 0x4a800f5728ff2351
    +652, 0x8c15e15189b9f7ed
    +653, 0xab367846b811362c
    +654, 0x4ba7508f0851ca2a
    +655, 0xe9af891acbafc356
    +656, 0xbdebe183989601f8
    +657, 0x4c665ea496afc061
    +658, 0x3ca1d14a5f2ed7c
    +659, 0xfbdff10a1027dd21
    +660, 0xdfd28f77c8cff968
    +661, 0xc4fbaadf8a3e9c77
    +662, 0xdac7e448b218c589
    +663, 0xb26390b5befd19e2
    +664, 0xd2ef14916c66dba9
    +665, 0xfab600284b0ff86b
    +666, 0xf04a1c229b58dabb
    +667, 0xc21c45637e452476
    +668, 0xd1435966f75e0791
    +669, 0xc1f28522eda4a2d0
    +670, 0x52332ae8f1222185
    +671, 0x81c6c0790c0bf47e
    +672, 0xfebd215e7d8ffb86
    +673, 0x68c5dce55dbe962b
    +674, 0x231d09cb0d2531d1
    +675, 0x3218fba199dbbc6b
    +676, 0x8f23c535f8ea0bf6
    +677, 0x6c228963e1df8bd9
    +678, 0x9843c7722ed153e3
    +679, 0xd032d99e419bddec
    +680, 0xe2dca88aa7814cab
    +681, 0x4d53fb8c6a59cdc2
    +682, 0x8fb3abc46157b68b
    +683, 0xa3e733087e09b8e
    +684, 0x6bdc1aee029d6b96
    +685, 0x4089667a8906d65b
    +686, 0x8f3026a52d39dd03
    +687, 0x6d2e0ccb567bae84
    +688, 0x74bad450199e464
    +689, 0xf114fb68a8f300d5
    +690, 0xc7a5cc7b374c7d10
    +691, 0xf0e93da639b279d1
    +692, 0xb9943841ad493166
    +693, 0x77a69290455a3664
    +694, 0x41530da2ebea054b
    +695, 0xe8f9fab03ea24abf
    +696, 0xaa931f0c9f55a57a
    +697, 0xb4d68a75d56f97ae
    +698, 0x3d58ff898b6ba297
    +699, 0x49d81e08faf5a3f5
    +700, 0xfc5207b9f3697f3b
    +701, 0xa25911abb3cf19b7
    +702, 0x6b8908eb67c3a41
    +703, 0xd63ef402e2e3fa33
    +704, 0x728e75d3f33b14c5
    +705, 0x248cb1b8bc6f379a
    +706, 0x3aa3d6d2b8c72996
    +707, 0x49cc50bd2d3d2860
    +708, 0xb4e1387647c72075
    +709, 0x435a1630a4a81ed3
    +710, 0xa5ea13005d2460cf
    +711, 0xc7a613df37d159ec
    +712, 0x95721ccc218b857e
    +713, 0xd4b70d8c86b124d3
    +714, 0x2b82bcc4b612d494
    +715, 0xaf13062885276050
    +716, 0xcbd8fcf571a33d9c
    +717, 0x3f7f67ca1125fc15
    +718, 0xddf4bb45aac81b4c
    +719, 0x23606da62de9c040
    +720, 0xa3a172375666b636
    +721, 0x292f87387a6c6c3c
    +722, 0xd1d10d00c5496fe1
    +723, 0x86b0411ce8a25550
    +724, 0x38e0487872e33976
    +725, 0x363e49f88ddfd42c
    +726, 0x45bdf1e9f6b66b0a
    +727, 0x8a6fff3de394f9b5
    +728, 0x8502158bb03f6209
    +729, 0x22e24d16dba42907
    +730, 0x3fe3ba427cc2b779
    +731, 0x77144793f66b3d7e
    +732, 0xcf8912ccb29b8af9
    +733, 0xdc856caff2abd670
    +734, 0xe6d3ae0b0d9d4c8b
    +735, 0xb8f5d40e454c539f
    +736, 0x79ca953114fbc6b7
    +737, 0x478d6f4bbfa38837
    +738, 0x9babae1a3ffdc340
    +739, 0x40edd56802bae613
    +740, 0x97a56c2dcccf0641
    +741, 0xafc250257f027f8e
    +742, 0x8da41ef1edf69125
    +743, 0x6574b0280ff9d309
    +744, 0x197c776151b8f820
    +745, 0x6b03e077c9dac3b6
    +746, 0x24a40ebbc5c341c5
    +747, 0x50e585169a6a1c4b
    +748, 0x37783a5a6a3e4e02
    +749, 0xb3de81ee6fbad647
    +750, 0xf4f292f57ca4591e
    +751, 0x6214e9e7d44d30a
    +752, 0x5920190c56d21c12
    +753, 0x9ac163419b5e0c9b
    +754, 0xfc2328761ae8ed93
    +755, 0xc68f945b545508c6
    +756, 0x687c49a17ce0a5e2
    +757, 0x276d8f53d30d4ab4
    +758, 0x8201804970343ce1
    +759, 0x1b5d323cc2e7fb7e
    +760, 0x6f351ef04fd904b
    +761, 0x6c793a7d455d5198
    +762, 0x46f5d108430ae91f
    +763, 0xac16a15b2a0cf77f
    +764, 0xa0d479d9e4122b9d
    +765, 0x3afd94604307f19
    +766, 0x2573ed6d39d38dbf
    +767, 0xa58e14ba60b4294b
    +768, 0xe69c1aed5840d156
    +769, 0x4cf6fda7f04855c2
    +770, 0x2fb65a56ef5f22da
    +771, 0xf95819434d5dc220
    +772, 0x29c65133623dafba
    +773, 0x8e997bd018467523
    +774, 0xfd08ba9d498461a7
    +775, 0xdd52243bc78a5592
    +776, 0x39c30108f6db88b3
    +777, 0x38af8e1894f259b9
    +778, 0x97eedf3b4ae5f6de
    +779, 0x757825add80c5ece
    +780, 0xf0fdd90ac14edb14
    +781, 0xbbb19d4cc8cac6d4
    +782, 0x9a82234edfae05e3
    +783, 0x704401c61d1edf1c
    +784, 0x8b0eb481fb3a1fb2
    +785, 0xef6f36e7cc06c002
    +786, 0x7a208b17e04b8cd7
    +787, 0xf20e33d498838fe9
    +788, 0xc2bdb22117058326
    +789, 0x6ec31939eb4ca543
    +790, 0x6f1654838f507a21
    +791, 0xc65ab81a955d2b93
    +792, 0x40b1420fdd9531b8
    +793, 0xe31f221cab9f4f40
    +794, 0x798cdd414c1deb7a
    +795, 0x9c84e9c7d41cd983
    +796, 0x63d6b1ae3b60b7fa
    +797, 0xb42bfdd1a2f78ffa
    +798, 0x37e431eaccaaa8e9
    +799, 0x7508142a0f73eac9
    +800, 0x91662a023df5893a
    +801, 0x59782070e2fe3031
    +802, 0xb2acd589a8ce7961
    +803, 0xa224743fa877b292
    +804, 0xaa5362aa27e6ed9e
    +805, 0xa394a4e520c0c1c7
    +806, 0xe49b16d2018ffb6f
    +807, 0xb8074b9f2f1e762b
    +808, 0xcf5f86143d5c23a7
    +809, 0xfd838785db987087
    +810, 0x31b1889df389aff8
    +811, 0x30aaca876a4383b
    +812, 0x1731bb71c4c38d4f
    +813, 0x9a83a65395e05458
    +814, 0x99cd0c8d67c8f4fc
    +815, 0xfbd9fdc849b761a5
    +816, 0x82c04834fc466889
    +817, 0xdeef9d6e715e8c97
    +818, 0x549c281c16da6078
    +819, 0x2d70661254ad599d
    +820, 0x57995793a72acac
    +821, 0xf1727005116183ba
    +822, 0xa22bb38945285de3
    +823, 0x4f2d687fe45131ff
    +824, 0x5666c87ddbbc981f
    +825, 0xbcb4b2d4e7a517d0
    +826, 0x5e794dd2e20b785d
    +827, 0x449ad020149e093c
    +828, 0x7704ee0412d106f5
    +829, 0x83cbdf257b072ac1
    +830, 0xae5c4fc9f638b0da
    +831, 0x7b9e5a64e372ed47
    +832, 0x7eddbbb22c2cdf57
    +833, 0x3f19ebfa155b08e
    +834, 0x91d991154dfd7177
    +835, 0x611ae74b952d387f
    +836, 0x3fdf7a335bda36ee
    +837, 0xdf182433fc7a7c05
    +838, 0x62c78598d1f8db0a
    +839, 0xc3750c69d2c5c1f0
    +840, 0xf1318024709efdee
    +841, 0xaa3fd360d224dc29
    +842, 0x62af53b2f307c19
    +843, 0xdf527683c58120c2
    +844, 0x3281deecc496f93d
    +845, 0x4f704ad31527ef08
    +846, 0x127a14a5e07cfdfc
    +847, 0x90d0b1f549255c92
    +848, 0xbc3406b212c5e1fc
    +849, 0x4e89f39379dba91d
    +850, 0x1290ef43c4998e6e
    +851, 0xecfeb1a1cb1c6e1b
    +852, 0x2067e90403003bf1
    +853, 0x38ae04be30bdbeba
    +854, 0x8a3537f298baedda
    +855, 0xd07f3b825cdb2936
    +856, 0xea020b5aebae8b45
    +857, 0xfcd614ab031132b0
    +858, 0x5fb682a4ff2268f5
    +859, 0xd1c4662ce65596f4
    +860, 0x7026b8270dd0b8dc
    +861, 0x8101ec4b4beae45a
    +862, 0xa0e9dc87940610a6
    +863, 0x83ec33679d83165b
    +864, 0x981847ca82e86d41
    +865, 0xda84c188a304a0b7
    +866, 0x3c37529c5a5bbbb8
    +867, 0x34a8491ce3e19a5a
    +868, 0xd36ad716a2fa6cb8
    +869, 0xfd1d1d6a5189a15c
    +870, 0x9716eb47851e8d8d
    +871, 0x7dfb13ea3b15c5aa
    +872, 0xbdf6e707f45113a5
    +873, 0xb8118261b04bd097
    +874, 0x6191f9895881bec6
    +875, 0x7aac257ae11acf9b
    +876, 0x35a491e1537ff120
    +877, 0xe078943432efa71c
    +878, 0xb3338485dd3dc2b9
    +879, 0x456060975d2bb3b5
    +880, 0xaddc4c451bdfc44c
    +881, 0x18bfa7beacf96430
    +882, 0x8802ebcaf0f67498
    +883, 0xad922a5a825bd780
    +884, 0x9fb4587d748f4efa
    +885, 0xdb2a445136cd5e7
    +886, 0xb98b3676ea8e96ac
    +887, 0xb02d8d244d784878
    +888, 0xa1a8442b18860abb
    +889, 0x6a3029ba1361e5d1
    +890, 0xf426d5fac161eb1
    +891, 0xfa5ac2b87acecb23
    +892, 0xaa659896e50535df
    +893, 0xf40dd7a3d3c5c8ed
    +894, 0x3f8367abecb705bc
    +895, 0x2d60e7525873358f
    +896, 0xc4a9d3948a0c3937
    +897, 0x5ecc04fef6003909
    +898, 0x7a865004918cba2
    +899, 0x47ae110a678ec10b
    +900, 0xa0f02f629d91aa67
    +901, 0x4848b99e7fac9347
    +902, 0xaa858346d63b80ac
    +903, 0xeb5bf42ee161eeef
    +904, 0x4d35d723d3c6ba37
    +905, 0xdf22ca6ca93b64a7
    +906, 0x9d198520f97b25b1
    +907, 0x3068415350778efe
    +908, 0xf3709f2e8793c2fe
    +909, 0xd1517bac8dd9f16f
    +910, 0xfb99bccaa15861dc
    +911, 0xa9ad607d796a2521
    +912, 0x55d3793d36bd22e4
    +913, 0xf99270d891ff7401
    +914, 0x401750a5c4aa8238
    +915, 0xd84b3003e6f28309
    +916, 0x8a23798b5fa7c98b
    +917, 0xadd58bbc8f43e399
    +918, 0xbd8c741ada62c6a8
    +919, 0xbdc6937bc55b49fa
    +920, 0x4aefa82201b8502
    +921, 0x17adf29a717b303
    +922, 0xa6ed2197be168f6c
    +923, 0x1ba47543f4359a95
    +924, 0xe34299949ac01ae9
    +925, 0x711c76cffc9b62f3
    +926, 0xbac259895508a4b7
    +927, 0x3c8b3b3626b0d900
    +928, 0x1a8d23fbe2ae71bf
    +929, 0xca984fa3b5a5c3a1
    +930, 0xb1986ab7521a9c93
    +931, 0xd6b5b2c8d47a75b5
    +932, 0xc7f1c4a88afb4957
    +933, 0xdeb58033a3acd6cc
    +934, 0xabe49ddfe1167e67
    +935, 0x8d559c10205c06e3
    +936, 0xea07a1a7de67a651
    +937, 0xcbef60db15b6fef8
    +938, 0xbfca142cff280e7
    +939, 0x362693eba0732221
    +940, 0x7463237e134db103
    +941, 0x45574ddb5035e17a
    +942, 0xfc65e0cb9b94a1aa
    +943, 0x3154c55f1d86b36d
    +944, 0x2d93a96dd6ab2d8b
    +945, 0xbe3bc1d1f2542a25
    +946, 0xdd4b541f7385bdaa
    +947, 0x3b56b919d914e3f8
    +948, 0x82fd51468a21895f
    +949, 0x8988cf120731b916
    +950, 0xa06a61db5fb93e32
    +951, 0x6ed66c1b36f68623
    +952, 0x875ae844d2f01c59
    +953, 0x17ccd7ac912e5925
    +954, 0x12fe2a66b8e40cb1
    +955, 0xf843e5e3923ad791
    +956, 0xa17560f2fd4ef48
    +957, 0x27a2968191a8ee07
    +958, 0xa9aab4d22ff44a3c
    +959, 0x63cd0dcc3bb083ae
    +960, 0x7a30b48c6160bf85
    +961, 0x956160fb572503b3
    +962, 0xc47f6b7546640257
    +963, 0xaf4b625f7f49153
    +964, 0x2f5c86a790e0c7e8
    +965, 0xb52e0610ae07f0b8
    +966, 0x38a589292c3d849e
    +967, 0xc3e9ef655d30b4ef
    +968, 0xb5695f765cda998a
    +969, 0xde5d5e692a028e91
    +970, 0x839476721555f72e
    +971, 0x48b20679b17d9ebf
    +972, 0xe3d4c6b2c26fb0df
    +973, 0xce5a9834f0b4e71f
    +974, 0x533abb253d5d420e
    +975, 0x9eac5ad9aed34627
    +976, 0xc0f2a01ab3c90dbb
    +977, 0x6528eda93f6a066c
    +978, 0xc16a1b625e467ade
    +979, 0x1a4a320fb5e8b098
    +980, 0x8819cccd8b4ab32f
    +981, 0x42daa88531fd0bfd
    +982, 0xcf732226409be17c
    +983, 0xfddcdb25ccbf378c
    +984, 0x9b15b603bf589fc1
    +985, 0x2436066b95d366fe
    +986, 0x8d42eff2e9cbda90
    +987, 0x694b2fc8a4e8303c
    +988, 0x8e207f98aaea3ccd
    +989, 0x4730d7a620f822d9
    +990, 0x468dc9ca30fe2fd4
    +991, 0x74b36d8a1c0f031b
    +992, 0x3c1aac1c488c1a94
    +993, 0x19d0101042444585
    +994, 0x8ec50c56d0c8adf4
    +995, 0x721ec629e4d66394
    +996, 0x3ca5ad93abeac4a4
    +997, 0xaaebc76e71592623
    +998, 0x969cc319e3ed6058
    +999, 0xc0a277e3b2bfc3de
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/data/philox-testset-1.csv b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/data/philox-testset-1.csv
    new file mode 100644
    index 0000000000000000000000000000000000000000..e448cbf73cc0774eca74df0d3d8866d8bd35bab3
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/data/philox-testset-1.csv
    @@ -0,0 +1,1001 @@
    +seed, 0xdeadbeaf
    +0, 0xedc95200e2bd66a5
    +1, 0x581d4e43b7682352
    +2, 0x4be7278f5e373eab
    +3, 0xee47f17991a9e7ea
    +4, 0x38a7d2ae422f2e2c
    +5, 0xe2a6730a3b4a8a15
    +6, 0x1588b7a841486442
    +7, 0x13ad777246700504
    +8, 0x14d157e0f5e18204
    +9, 0xd87c22a7ee8c13f1
    +10, 0x30cc389ce3542ba1
    +11, 0xb8a53348955bb2e9
    +12, 0xc08802e3c454f74f
    +13, 0xb444f627671a5780
    +14, 0x4b6dd42b29cbf567
    +15, 0x6109c7dc0bc5f7d5
    +16, 0x85c954715d6b5b1e
    +17, 0x646178d3d9a3a5d5
    +18, 0xebbde42b1cd83465
    +19, 0x3d015102f6bc9c1a
    +20, 0x720fe2ec3798d5fd
    +21, 0x93120961289ceb2e
    +22, 0xc9207e960a56fae2
    +23, 0xa7f042f31d991b98
    +24, 0x5fac117415fae74b
    +25, 0xd0a970ba8dddc287
    +26, 0x84b4e7e51b43106
    +27, 0x6ad02bf525ea265f
    +28, 0xcdc7e5992b36ef8f
    +29, 0x44d4985209261d60
    +30, 0x628c02d50f4b902e
    +31, 0xc7b1914922d1e76d
    +32, 0xfde99ff895cba51d
    +33, 0x175a0be050fa985f
    +34, 0x47297d3699e03228
    +35, 0xccf1e9aeaa3339cd
    +36, 0x9fdd18ebeeaf15b1
    +37, 0x7c94c9ab68747011
    +38, 0x612d8ef22c1fa80f
    +39, 0x13f52b860de89ab5
    +40, 0x81f264b8c139c43b
    +41, 0x8d017ba4ef1e85ba
    +42, 0x6d0556f46219951e
    +43, 0x8ee7b85663cf67b6
    +44, 0x2432fc707645fe67
    +45, 0xaf814046051e5941
    +46, 0x4d432a83739ac76f
    +47, 0x59e5060d0983ccdd
    +48, 0xdd20e828b83d9b53
    +49, 0x1b891800d7385f4c
    +50, 0x10e86a026c52ff5e
    +51, 0xb932f11723f7b90c
    +52, 0xb2413d0a1f3582d0
    +53, 0xe7cd4edda65fc6b5
    +54, 0x6d3808848d56593b
    +55, 0x192a727c3c7f47d9
    +56, 0x9659d8aea5db8c16
    +57, 0x4242c79fe2c77c16
    +58, 0x605f90c913827cea
    +59, 0x53e153c8bfc2138a
    +60, 0xed2158fbdef5910e
    +61, 0xae9e6e29d4cb5060
    +62, 0x7dd51afaad3b11ce
    +63, 0x2b9ba533d01a5453
    +64, 0x7e0e9cf2b6c72c8
    +65, 0x1cc8b3c7747ed147
    +66, 0x9b102651e2e11b48
    +67, 0x30b0b53cbaac33ea
    +68, 0x70c28aec39b99b85
    +69, 0x5f1417ff536fdb75
    +70, 0x3a1d91abd53acf58
    +71, 0xba116a1772168259
    +72, 0xf5369bc9bd284151
    +73, 0x67bf11373bf183ca
    +74, 0xef0b2d44dbd33dc7
    +75, 0xbfd567ee1a2953ed
    +76, 0x7d373f2579b5e5c6
    +77, 0x756eeae7bcdd99be
    +78, 0x75f16eb9faa56f3b
    +79, 0x96d55ded2b54b9a5
    +80, 0x94495191db692c24
    +81, 0x32358bdd56bab38c
    +82, 0x3f6b64078576579
    +83, 0x7177e7948bc064c9
    +84, 0x2cbf23f09ba9bc91
    +85, 0x9b97cc31c26645f5
    +86, 0x5af2d239ff9028b1
    +87, 0x316fa920e0332abe
    +88, 0x46535b7d1cae10a0
    +89, 0x21f0a6869298022c
    +90, 0xf395c623b12deb14
    +91, 0x8573995180675aa7
    +92, 0xc3076509f4dc42d5
    +93, 0x15e11e49760c6066
    +94, 0xe8a6d311e67a021d
    +95, 0x7482f389c883339b
    +96, 0xda6f881573cba403
    +97, 0xb110ffb847e42f07
    +98, 0x2c3393140605ccf9
    +99, 0xba1c8ba37d8bdc33
    +100, 0x59adf43db7a86fe0
    +101, 0xb4fcbf6aa585ca85
    +102, 0xd794a93c18033fa6
    +103, 0x6e839c01985f9d4
    +104, 0x64065bf28222b2c7
    +105, 0x6a6359b293fa0640
    +106, 0x5ff610969e383e44
    +107, 0xa8172c263f05c7f7
    +108, 0x62a0172e8bd75d07
    +109, 0x7be66e3c453b65ac
    +110, 0x6a3b8d5a14014292
    +111, 0xa2583e6087450020
    +112, 0xd5d3ecc480c627d2
    +113, 0xa24e83f1eec8a27c
    +114, 0xa23febd2a99ee75a
    +115, 0x9a5fbf91c7310366
    +116, 0x5b63156932e039b
    +117, 0x942af3c569908505
    +118, 0x89a850f71ab6a912
    +119, 0xfeadc803ac132fe9
    +120, 0x67bf60e758250f3
    +121, 0x533c25103466a697
    +122, 0xb7deede3482f9769
    +123, 0x325e043b53bba915
    +124, 0x9e8d9e7fde132006
    +125, 0x6bacc6860bbc436e
    +126, 0xb3ea0534c42b1c53
    +127, 0xb2389334db583172
    +128, 0xa74b1bfbf5242ee4
    +129, 0x53a487e2dc51d15c
    +130, 0xe5a3b538d2c7a82e
    +131, 0x7b6c70bb0c4cadaf
    +132, 0xae20791b2081df1
    +133, 0xc685c12e3c61d32c
    +134, 0x60110e6b0286e882
    +135, 0x49682119c774045c
    +136, 0x53dc11a3bbd072e
    +137, 0xbdc87c6e732d9c2d
    +138, 0xcc4620861ebac8fd
    +139, 0x7e9c3558759350cc
    +140, 0x157408dee34891ba
    +141, 0x9bcad1855b80651b
    +142, 0xd81b29141d636908
    +143, 0x1ed041a9f319c69d
    +144, 0x805b2f541208b490
    +145, 0x484ef3bba2eb7c66
    +146, 0xb6b5e37d50a99691
    +147, 0xabc26a7d9e97e85f
    +148, 0xcba2a3cce0417c2f
    +149, 0xa030dfffd701993c
    +150, 0x2bf2dc50582ebf33
    +151, 0xd9df13dd3eb9993e
    +152, 0x31ca28b757232ae5
    +153, 0x614562a0ccf37263
    +154, 0x44d635b01725afbb
    +155, 0x5ae230bc9ca9cd
    +156, 0xb23a124eb98705c6
    +157, 0x6395675444981b11
    +158, 0xd97314c34119f9ca
    +159, 0x9de61048327dd980
    +160, 0x16bac6bded819707
    +161, 0xcea3700e3e84b8c7
    +162, 0xaa96955e2ee9c408
    +163, 0x95361dcc93b5bc99
    +164, 0x306921aed3713287
    +165, 0x4df87f3130cd302a
    +166, 0x37c451daeb6a4af5
    +167, 0x8dbbe35f911d5cc1
    +168, 0x518157ce61cb10f9
    +169, 0x669f577aebc7b35b
    +170, 0x4b0a5824a8786040
    +171, 0x519bc3528de379f5
    +172, 0x6128012516b54e02
    +173, 0x98e4f165e5e6a6dd
    +174, 0x6404d03618a9b882
    +175, 0x15b6aeb3d9cd8dc5
    +176, 0x87ed2c1bae83c35b
    +177, 0x8377fc0252d41278
    +178, 0x843f89d257a9ba02
    +179, 0xcdda696ea95d0180
    +180, 0xcfc4b23a50a89def
    +181, 0xf37fd270d5e29902
    +182, 0xafe14418f76b7efa
    +183, 0xf984b81577076842
    +184, 0xe8c60649ccb5458d
    +185, 0x3b7be8e50f8ff27b
    +186, 0xaa7506f25cef1464
    +187, 0x5e513da59f106688
    +188, 0x3c585e1f21a90d91
    +189, 0x1df0e2075af292a
    +190, 0x29fdd36d4f72795f
    +191, 0xb162fe6c24cb4741
    +192, 0x45073a8c02bd12c4
    +193, 0xcbaaa395c2106f34
    +194, 0x5db3c4c6011bc21c
    +195, 0x1b02aac4f752e377
    +196, 0xa2dfb583eb7bec5
    +197, 0xfe1d728805d34bb1
    +198, 0xf647fb78bb4601ec
    +199, 0xd17be06f0d1f51ef
    +200, 0x39ec97c26e3d18a0
    +201, 0xb7117c6037e142c8
    +202, 0xe3a6ce6e6c71a028
    +203, 0xe70a265e5db90bb2
    +204, 0x24da4480530def1e
    +205, 0xfd82b28ce11d9a90
    +206, 0x5bf61ead55074a1d
    +207, 0xbe9899c61dec480d
    +208, 0xae7d66d21e51ec9e
    +209, 0x384ee62c26a08419
    +210, 0x6648dccb7c2f4abf
    +211, 0xc72aa0c2c708bdc9
    +212, 0x205c5946b2b5ba71
    +213, 0xd4d8d0b01890a812
    +214, 0x56f185493625378d
    +215, 0x92f8072c81d39bd0
    +216, 0xa60b3ceecb3e4979
    +217, 0xfcf41d88b63b5896
    +218, 0xf5a49aa845c14003
    +219, 0xffcc7e99eee1e705
    +220, 0xdd98312a7a43b32d
    +221, 0xa6339bd7730b004
    +222, 0xdac7874ba7e30386
    +223, 0xadf6f0b0d321c8
    +224, 0x126a173ae4ffa39f
    +225, 0x5c854b137385c1e7
    +226, 0x8173d471b1e69c00
    +227, 0x23fa34de43581e27
    +228, 0x343b373aef4507b1
    +229, 0xa482d262b4ea919c
    +230, 0xf7fbef1b6f7fbba
    +231, 0xd8ce559487976613
    +232, 0xbf3c8dd1e6ebc654
    +233, 0xda41ed375451e988
    +234, 0xf54906371fd4b9b3
    +235, 0x5b6bb41231a04230
    +236, 0x866d816482b29c17
    +237, 0x11315b96941f27dc
    +238, 0xff95c79205c47d50
    +239, 0x19c4fff96fbdac98
    +240, 0xbfb1ae6e4131d0f4
    +241, 0x9d20923f3cdb82c9
    +242, 0x282175507c865dff
    +243, 0xdfd5e58a40fe29be
    +244, 0xedbd906ff40c8e4f
    +245, 0x11b04fc82614ccb3
    +246, 0xeceb8afda76ae49f
    +247, 0xa4856913847c2cdf
    +248, 0x6f1425f15a627f2a
    +249, 0xdf144ffedf60349e
    +250, 0x392d7ecfd77cc65f
    +251, 0x72b8e2531049b2c6
    +252, 0x5a7eb2bdb0ec9529
    +253, 0xdcfd4306443e78c1
    +254, 0x89ad67ed86cd7583
    +255, 0x276b06c0779a6c8f
    +256, 0xb2dbb723196a0ac3
    +257, 0x66c86a3b65906016
    +258, 0x938348768a730b47
    +259, 0x5f5282de938d1a96
    +260, 0xa4d4588c4b473b1f
    +261, 0x8daed5962be4796f
    +262, 0x9dde8d796985a56e
    +263, 0x46be06dbd9ed9543
    +264, 0xdf98286ceb9c5955
    +265, 0xa1da1f52d7a7ca2b
    +266, 0x5a7f1449f24bbd62
    +267, 0x3aedc4e324e525fd
    +268, 0xced62464cd0154e1
    +269, 0x148fc035e7d88ce3
    +270, 0x82f8878948f40d4c
    +271, 0x4c04d9cdd6135c17
    +272, 0xdf046948d86b3b93
    +273, 0x2f0dec84f403fe40
    +274, 0xa61954fb71e63c0d
    +275, 0x616d8496f00382e8
    +276, 0x162c622472746e27
    +277, 0x43bcfe48731d2ceb
    +278, 0xff22432f9ff16d85
    +279, 0xc033ed32bb0ad5a4
    +280, 0x5d3717cc91c0ce09
    +281, 0x7a39a4852d251075
    +282, 0x61cd73d71d6e6a6
    +283, 0xe37e2ea4783ab1a5
    +284, 0x60e1882162579ea8
    +285, 0x9258ec33f1a88e00
    +286, 0x24b32acf029f0407
    +287, 0x1410fc9aea6d3fac
    +288, 0x6054cf2a3c71d8f7
    +289, 0x82f7605157a66183
    +290, 0x3b34c1c0dff9eac5
    +291, 0xfebe01b6d5c61819
    +292, 0x7372187c68b777f2
    +293, 0xc6923812cda479f0
    +294, 0x386613be41b45156
    +295, 0x92cfebe8cc4014b
    +296, 0x8e13c4595849828b
    +297, 0x90e47390d412291f
    +298, 0x6b21a1d93d285138
    +299, 0xbf5b1f5922f04b12
    +300, 0x21e65d1643b3cb69
    +301, 0xf7683b131948ac3c
    +302, 0xe5d99fc926196ed2
    +303, 0x7b138debbec90116
    +304, 0x8a2650a75c2c2a5c
    +305, 0x20689a768f9b347b
    +306, 0xdfa2900cfb72dc6e
    +307, 0x98959c3855611cc2
    +308, 0x5fdb71b89596cc7c
    +309, 0x1c14ac5c49568c7b
    +310, 0x958c4293016091fe
    +311, 0x7484522eb0087243
    +312, 0xc4018dfb34fc190f
    +313, 0xca638567e9888860
    +314, 0x102cd4805f0c0e89
    +315, 0xcc3bc438e04548f8
    +316, 0xb808944bb56ea5be
    +317, 0xffd4778dbf945c57
    +318, 0xfe42617784c0233b
    +319, 0x3eccbfeae9b42d3c
    +320, 0xd9f1b585fd0bfa60
    +321, 0x5c063d1b2705d5dd
    +322, 0x8e8bec3519941b64
    +323, 0x9e94c36cbec2a42
    +324, 0x1cd19f5b64ffd3ad
    +325, 0x9632e3aebfc68e66
    +326, 0x98960c2d9da4ae45
    +327, 0xb76994b1f2bbfc1f
    +328, 0xca184a737d3971cc
    +329, 0x964d31b07183adfb
    +330, 0xe9e0ff351cd276d4
    +331, 0xb5747c860b05bbe4
    +332, 0x5549ddc3bd3862e2
    +333, 0x495496677b27873b
    +334, 0x53910baa26e3ea18
    +335, 0xaa07a07ad0a688d3
    +336, 0xbb43bd1f09ecdb1e
    +337, 0xe2ebc105699dd84
    +338, 0x6e815a2729584035
    +339, 0x2caab1713b17948a
    +340, 0x43d39d209fa41c90
    +341, 0xfe3e71089d5d1c3a
    +342, 0xa778646c32f81177
    +343, 0x8d42bfb86e6e92d5
    +344, 0x175571f70b4fcfbe
    +345, 0x2a66a6fe10dc3b5b
    +346, 0xd9545e85235ca709
    +347, 0x5642781c77ced48a
    +348, 0x24facc40b72ccd09
    +349, 0xa800fbacce33f6f8
    +350, 0x675f58a0ff19fba
    +351, 0x35aedf57bb5cde1b
    +352, 0xe5535a6b63f6d068
    +353, 0x84dffd0102aaa85d
    +354, 0x621faad65467aaa7
    +355, 0x596ad85b556b112f
    +356, 0x837545fff8894c7a
    +357, 0x3d9a4ae1356bc6a6
    +358, 0xcd8b7153205d4ad0
    +359, 0x98afdd40f1ed09a6
    +360, 0xa38b2dc55a5cf87f
    +361, 0x484aecce2b6838bc
    +362, 0x6af05c26bdab18d9
    +363, 0xf418b7399dcf2e4b
    +364, 0x1cfa38789b0d2445
    +365, 0xfbed23c34166ee67
    +366, 0x38e6820039e4912a
    +367, 0x1fe94911e963591e
    +368, 0x1291c79aee29ad70
    +369, 0x65eccfc89506f963
    +370, 0x7d14de3b2f55b1f6
    +371, 0x82eb79c36cd2a739
    +372, 0x41ffe3b75ea0def5
    +373, 0x9eba9156470a51d9
    +374, 0xd17c00b981db37d1
    +375, 0xf688769a75601aa7
    +376, 0xbcf738e9e03d571e
    +377, 0x14712e56df8f919b
    +378, 0xab14e227d156e310
    +379, 0xf53d193e993e351e
    +380, 0x857fae46bd312141
    +381, 0xc2dd71e41b639966
    +382, 0x74f8b987a3d00ad1
    +383, 0x5bce8526dc527981
    +384, 0x94910926c172a379
    +385, 0x503c45557688a9d5
    +386, 0x244d03834e05807f
    +387, 0x6e014cbab9c7a31f
    +388, 0xae544c638530facf
    +389, 0x9b853aaaf9cbc22d
    +390, 0xfb42ab7024d060ed
    +391, 0x74cc3fba0dfd7ff2
    +392, 0x24ec9e8f62144ad5
    +393, 0x72f082954307bbe7
    +394, 0x36feda21bbf67577
    +395, 0x3222191611b832f1
    +396, 0xd0584e81bcac8b0b
    +397, 0xdce8d793ef75e771
    +398, 0x978824c6c2578fc
    +399, 0x6e8f77503b3c2ee4
    +400, 0xc85d2d86fecf5d03
    +401, 0x3d35b4a5d4d723c4
    +402, 0xd3987dfd4727fff3
    +403, 0xd3cde63fb6a31add
    +404, 0xf6699e86165bdaeb
    +405, 0x9d60ba158ec364c4
    +406, 0x920c3c18b346bfc9
    +407, 0x770fd1fdfbc236ca
    +408, 0x45998cfc5fc12ddd
    +409, 0xd74a3454e888834b
    +410, 0xbf2aa68081a4a28f
    +411, 0xea41b26a6f1da1b3
    +412, 0x5560a2d24b9d5903
    +413, 0xe3791f652a228d8b
    +414, 0x365116d3b5a8520c
    +415, 0xb1b2bd46528f8969
    +416, 0xfcfe14943ef16ae7
    +417, 0xf4d43425e8a535dc
    +418, 0xe6cf10a78782a7e0
    +419, 0x9c7ac0de46556e3e
    +420, 0xc667ae0856eed9ef
    +421, 0x47dbb532e16f9c7e
    +422, 0xdf4785a5d89ee82e
    +423, 0xbd014925ce79dbcf
    +424, 0xea0d663fb58fa5be
    +425, 0x51af07d5cc3821fb
    +426, 0x27a1bdcdc4159a9d
    +427, 0x520c986c59b1e140
    +428, 0x50b73fd9bacd5b39
    +429, 0xae5240641f51e4f3
    +430, 0x71faecc164ed9681
    +431, 0xda95aa35529a7ee
    +432, 0xe25ba29b853c1c6d
    +433, 0x9871a925cda53735
    +434, 0xde481ad8540e114d
    +435, 0xa2997f540e8abca0
    +436, 0xc9683c5035e28185
    +437, 0x1082471b57182bac
    +438, 0xbd3ecf0f0b788988
    +439, 0xf479760776fbb342
    +440, 0x3730929200d91f44
    +441, 0xc1762d79ae72809c
    +442, 0xfaa0a4c7b1686cb3
    +443, 0xd581e6d55afdafcd
    +444, 0x6cf57bdfba2dcf6d
    +445, 0xdef79d9fe6a5bcef
    +446, 0x13ed376e18132bd3
    +447, 0xbe67efd72defa2a
    +448, 0x5acc176c468966ea
    +449, 0x8b35b626af139187
    +450, 0x446de3fac0d973ac
    +451, 0xe1d49e06dc890317
    +452, 0x817bc3fd21fc09b7
    +453, 0xb71c3958a13d5579
    +454, 0x8746e010f73d7148
    +455, 0x1b61c06009922e83
    +456, 0xba17e62e6b092316
    +457, 0x1375fa23c4db8290
    +458, 0x3f071230f51245a6
    +459, 0x51c99a086a61cd13
    +460, 0x5f0f2ae78589e1fd
    +461, 0x604834e114bbbc27
    +462, 0x5eb2a7a34814e9a9
    +463, 0x77a6907f386bf11e
    +464, 0x99525de2bd407eeb
    +465, 0xb818348c57b3b98f
    +466, 0x25f5f9e702fbe78d
    +467, 0x8f66669e6f884473
    +468, 0x1e47d46e2af4f919
    +469, 0xf6a19df846476833
    +470, 0xff00c67bcd06621f
    +471, 0xe3dfe069795d72d8
    +472, 0x8affc88b2fea4d73
    +473, 0x66df747e5f827168
    +474, 0xf368ec338d898a0e
    +475, 0x9e1f1a739c5984a2
    +476, 0x46a1c90e1ca32cbc
    +477, 0xc261bc305ed8d762
    +478, 0x754d7949f7da9e72
    +479, 0x4c8fbbb14ef47b17
    +480, 0xccbdc67a3848d80d
    +481, 0x3c25e6f58bae751d
    +482, 0x7078b163b936d9b6
    +483, 0x440e27463c134ecf
    +484, 0x6c83ee39f324db0f
    +485, 0x27cf901b22aea535
    +486, 0x57262dec79a3f366
    +487, 0x91db09f1dbb524fb
    +488, 0xd7436eefba865df2
    +489, 0x16c86b0a275a3f43
    +490, 0x689493e6681deaa9
    +491, 0x7e1dc536c1a9ac42
    +492, 0x1145beac3ac7f5cc
    +493, 0x3d05e211a104b2b0
    +494, 0x4f9e77ced3c52f44
    +495, 0x53de1369354add72
    +496, 0x1fb60f835f47cdeb
    +497, 0x6ab36f089e40c106
    +498, 0xaabffcb0d3d04c7
    +499, 0xaa399686d921bd25
    +500, 0x2bf8dd8b6d6fa7f0
    +501, 0x1ddbf4e124329613
    +502, 0x466a740241466a72
    +503, 0x98d7381eb68a761
    +504, 0x817691510bc4857a
    +505, 0x8837622c0171fe33
    +506, 0xcba078873179ee16
    +507, 0x13adad1ab7b75af4
    +508, 0x3bac3f502428840c
    +509, 0xbeb3cce138de9a91
    +510, 0x30ef556e40b5f0b4
    +511, 0x19c22abdf3bbb108
    +512, 0x977e66ea4ddc7cf
    +513, 0x9f4a505f223d3bf3
    +514, 0x6bc3f42ac79ec87b
    +515, 0x31e77712158d6c23
    +516, 0x6d8de4295a28af0d
    +517, 0xee1807dbda72adb7
    +518, 0xda54140179cd038f
    +519, 0x715aa5cdac38e062
    +520, 0x5a7e55e99a22fa16
    +521, 0xf190c36aa8edbe4f
    +522, 0xccadd93a82c1d044
    +523, 0x7070e6d5012c3f15
    +524, 0x50a83341a26c1ba5
    +525, 0x11bca7cc634142e5
    +526, 0x623a0d27867d8b04
    +527, 0x75c18acff54fbf6e
    +528, 0x455ae7d933497a6f
    +529, 0xf624cf27d030c3d3
    +530, 0x7a852716f8758bac
    +531, 0xe7a497ac1fa2b5b4
    +532, 0xf84f097498f57562
    +533, 0xc4bb392f87f65943
    +534, 0x618e79a5d499fbfb
    +535, 0xb3c0b61d82b48b8
    +536, 0x4750a10815c78ea7
    +537, 0x9cf09cca3ddece69
    +538, 0x2a69f1c94cc901a2
    +539, 0x347a0e446e1ce86d
    +540, 0xb06f3a5a5ab37bb1
    +541, 0x8035bd0713d591db
    +542, 0x539c9637042c3a1f
    +543, 0xd7ba4dc6b273cbd7
    +544, 0x12f3f99933444f85
    +545, 0x4a9517b9783fb9a4
    +546, 0x6422b2ea95093bc5
    +547, 0x3a5ecff0f996c2a6
    +548, 0x31de504efc76a723
    +549, 0x7ccb7c5233c21a9f
    +550, 0xc687d9e6ce4186e8
    +551, 0x6e40769d6940376a
    +552, 0xf51207314f1f7528
    +553, 0x67ee3acb190865e3
    +554, 0xe08d586270588761
    +555, 0xe387fa489af1a75c
    +556, 0x73414a52d29d8375
    +557, 0x671a38191cf2a357
    +558, 0xe00fb25b1aa54008
    +559, 0x11a0610e22cf549b
    +560, 0xc90cc865d57c75be
    +561, 0x90d0863cc15f2b79
    +562, 0x8b3e60d32ebcb856
    +563, 0xb28cc55af621e04a
    +564, 0xcf60bd3cb2a5ab1d
    +565, 0x212cb5d421948f86
    +566, 0xee297b96e0a3363f
    +567, 0x4e9392ff998760d1
    +568, 0x61940c8d0105ba3e
    +569, 0x14ebcbae72a59a16
    +570, 0xdf0f39a3d10c02af
    +571, 0xfc047b2b3c1c549d
    +572, 0x91718b5b98e3b286
    +573, 0x9ea9539b1547d326
    +574, 0x7a5a624a89a165e6
    +575, 0x145b37dcaa8c4166
    +576, 0x63814bbb90e5616c
    +577, 0xc4bc3ca6c38bb739
    +578, 0x853c3a61ddc6626c
    +579, 0xa7ce8481c433829a
    +580, 0x8aff426941cc07b
    +581, 0x2dc3347ca68d8b95
    +582, 0xce69f44f349e9917
    +583, 0x2fa5cb8aca009b11
    +584, 0xf26bb012115d9aca
    +585, 0xafa01c2f2d27235a
    +586, 0xabcba21f1b40305e
    +587, 0xfec20c896c0c1128
    +588, 0xc5f7a71ebacadfa0
    +589, 0xc8479ad14bab4eef
    +590, 0xad86ec9a3e7d3dc
    +591, 0xbbecd65292b915c5
    +592, 0xb1f9e28149e67446
    +593, 0x708d081c03dad352
    +594, 0xaa8a84dbd1de916c
    +595, 0x9aa3efb29ba9480b
    +596, 0xd3c63969ff11443e
    +597, 0x1e9e9ac861315919
    +598, 0x4fe227f91e66b41d
    +599, 0xefc0212d43d253ab
    +600, 0x98341437727c42d1
    +601, 0x5ea85c0fe9008adc
    +602, 0x7891b15faa808613
    +603, 0x32db2d63989aacfd
    +604, 0xc92f7f28e88fd7bc
    +605, 0x3513545eb6549475
    +606, 0x49abe0082906fbf8
    +607, 0xcee1e1a6551e729c
    +608, 0x38556672b592a28e
    +609, 0xc3e61409c4ec2d45
    +610, 0x96c67ce2995a0fd4
    +611, 0x9b9b0cada870293
    +612, 0x82d6dd5dada48037
    +613, 0xeea4f415299f1706
    +614, 0x371107895f152ab3
    +615, 0x2f6686159f4396bb
    +616, 0x61005a2ff3680089
    +617, 0x9d2f2cafb595e6b6
    +618, 0x4a812a920f011672
    +619, 0x317554d3a77385d7
    +620, 0x24c01086727eb74b
    +621, 0xa15ff76d618a3a9e
    +622, 0x2121bfd983859940
    +623, 0x384d11577eea8114
    +624, 0xab0f4299f3c44d88
    +625, 0x136fd4b07cfa14d9
    +626, 0x665fe45cbfaa972a
    +627, 0x76c5a23398a314e9
    +628, 0x5507036357ccda98
    +629, 0xd9b8c5ac9dce632b
    +630, 0x366bc71781da6e27
    +631, 0xdd2b2ba1d6be6d15
    +632, 0xf33ed0d50ea6f1a6
    +633, 0xf05a9b1900174c18
    +634, 0x3947e1419e2787cf
    +635, 0x6c742b1e029637d0
    +636, 0x32aba12196a0d2e8
    +637, 0x1b94aab2e82e7df
    +638, 0x68b617db19229d6
    +639, 0x6c88a95ac0a33f98
    +640, 0xdc9b95fd60c2d23e
    +641, 0x999e6971d3afc8b3
    +642, 0x7071fc6ad8b60129
    +643, 0x41a8184ef62485f6
    +644, 0xb68e0605c7d5e713
    +645, 0x272b961a1d1bbee
    +646, 0x23f04e76446187b0
    +647, 0x999a7a8f6d33f260
    +648, 0xdbd6318df4f168d
    +649, 0x8f5e74c84c40711e
    +650, 0x8ccc6b04393a19d6
    +651, 0xadcd24b782dd8d3d
    +652, 0x1a966b4f80ef9499
    +653, 0xcb6d4f9ff5a280f0
    +654, 0x8095ff2b8484018a
    +655, 0xbfd3389611b8e771
    +656, 0x278eb670b7d12d51
    +657, 0x31df54ca8d65c20f
    +658, 0x121c7fb38af6985e
    +659, 0x84fb94f38fe1d0a
    +660, 0x15ae8af1a6d48f02
    +661, 0x8d51e4a62cba1a28
    +662, 0x58e6b6b3ae0f9e42
    +663, 0x9365a0a85669cc99
    +664, 0xe56e92f65a2106df
    +665, 0x68fa299c66b428fc
    +666, 0x55e51bb0b0a832c6
    +667, 0x48b565293f9bc494
    +668, 0x73d8132b1cbabb57
    +669, 0x9178ac3926c36cbc
    +670, 0xe2f22c7b28ea5e0f
    +671, 0x6af45322a99afb12
    +672, 0x59072fcb486a46f4
    +673, 0x166b717b08d3d8e
    +674, 0xd4e627a2dfacc4ab
    +675, 0x33dad6f2921dedaa
    +676, 0x4b13b806834a6704
    +677, 0xe5f7971b398ed54d
    +678, 0x20bfae65e3e6899b
    +679, 0x881dab45d2b4fc98
    +680, 0x6f248126b5b885be
    +681, 0x7aeb39e986f9deee
    +682, 0xf819f9574b8c3a03
    +683, 0xff3d93ed6bd9781a
    +684, 0x3a31e2e24a2f6385
    +685, 0x7888a88f8944a5e
    +686, 0x4faee12f5de95537
    +687, 0x7f3e4efccdb2ed67
    +688, 0x91e0f2fc12593af5
    +689, 0xb5be8a4b886a40d3
    +690, 0x998e8288ac3a9b1b
    +691, 0x85c48fc8b1349e7b
    +692, 0xf03af25222d8fae5
    +693, 0x45467e805b242c2e
    +694, 0xa2350db793dbebdc
    +695, 0xfebe5b61d2174553
    +696, 0xa9a331f02c54ad0b
    +697, 0xe94e49a0f905aef3
    +698, 0xe54b4c812b55e3da
    +699, 0xdc454114c6bc0278
    +700, 0x99c7765ab476baa2
    +701, 0xccd9590e47fdff7c
    +702, 0xfa2bcae7afd6cb71
    +703, 0x2c1bf1a433a6f0f7
    +704, 0x53882c62ff0aab28
    +705, 0x80ac900f844dacc
    +706, 0x27ba8eb5c4a44d54
    +707, 0x78f3dfb072a46004
    +708, 0x34e00e6ec629edce
    +709, 0x5b88d19b552d1fbd
    +710, 0xe4df375dc79df432
    +711, 0x37446312ff79c3b4
    +712, 0xb72256900a95fa6d
    +713, 0x89f3171fbdff0bfc
    +714, 0xd37885b048687eba
    +715, 0xbb033213b283b60e
    +716, 0xcf10b523ee769030
    +717, 0xbf8070b6cfd7bafb
    +718, 0xb7194da81fd1763b
    +719, 0xbfc303de88e68d24
    +720, 0xb949c7a5aea8a072
    +721, 0x844216e7bae90455
    +722, 0xf1e7f20840049a33
    +723, 0x96e3263ad0cae794
    +724, 0x10772d51f6e9ba49
    +725, 0xcea24fccae9d23b3
    +726, 0xefd378add9dde040
    +727, 0xba0c7c5275805976
    +728, 0x2e2a04608f64fa8c
    +729, 0xafb42ec43aa0fa7
    +730, 0x30444b84241ac465
    +731, 0x19ef384bac4493ab
    +732, 0xfd1ac615d3ba5ab9
    +733, 0x6cc781ba38643aff
    +734, 0x30ff27ebed875cfd
    +735, 0xee1a261aca97ae62
    +736, 0xc5a92715202bc940
    +737, 0x9e6ec76f93c657ff
    +738, 0x9b9fd55f55191ca5
    +739, 0x654b13af008d8f03
    +740, 0x1b7f030d9bd0719f
    +741, 0x6d622e277550cb7f
    +742, 0x3f8ee6b8830d0538
    +743, 0x475462bcd0de190f
    +744, 0x21380e8a513bdbcd
    +745, 0x629bf3771b1bd7a4
    +746, 0x3b5fd0b62c353709
    +747, 0xf95634006ec3867e
    +748, 0x1be8bb584a6653c2
    +749, 0x2e2d3cfa85320ce8
    +750, 0x5b904b692252d11d
    +751, 0x4bfd76631d527990
    +752, 0xc019571ca2bec4a0
    +753, 0xf2eb730cea4cd751
    +754, 0xd4571d709530191a
    +755, 0x3b5bd947061f5a7d
    +756, 0x56e2322cd2d1d1c0
    +757, 0xa8830a5f62019f83
    +758, 0x901d130c1b873cf3
    +759, 0xb5dd29b363c61299
    +760, 0xbb710bec3a17b26d
    +761, 0xc0c464daca0f2328
    +762, 0x4dc8055df02650f5
    +763, 0x3d3cd9bbe8b957af
    +764, 0xdb79612c2635b828
    +765, 0xe25b3a8ad8fa3040
    +766, 0xd5875c563cbf236b
    +767, 0x46861c1c3849c9bc
    +768, 0xf84bf1a2814dff43
    +769, 0x6d8103902e0ad5e6
    +770, 0x99f51c9be8af79e5
    +771, 0xb0bfa8540ff94a96
    +772, 0xaf45109a4e06f7d0
    +773, 0x281df3e55aea9bfc
    +774, 0x6a1155ca8aa40e60
    +775, 0x754d32c5de1f5da
    +776, 0xce1eafb1c6ca916f
    +777, 0xc4f2185fa8577bd1
    +778, 0x4a188e9bdb5501d9
    +779, 0xbb14107e99bd5550
    +780, 0xf0381d8425ec2962
    +781, 0x213dbfffc16ec4f6
    +782, 0x7a999c5a28ea65bc
    +783, 0x23758c2aba7709ff
    +784, 0xea7e4bb205e93b44
    +785, 0x9c5a31e53911c658
    +786, 0x7f04d0bbdc689ddc
    +787, 0xe3ed89ab8d78dcb3
    +788, 0x73c38bfb43986210
    +789, 0x740c7d787eb8e158
    +790, 0x5284fafdfb3fb9ec
    +791, 0x2e91a58ac1fb1409
    +792, 0xb94a600bf0a09af3
    +793, 0x533ea4dbe07d81dd
    +794, 0x48c3f1a736b3c5fd
    +795, 0x56ae3499fa8720ce
    +796, 0x526f2def663ca818
    +797, 0x2f085759c65665c4
    +798, 0xf715f042c69e0db4
    +799, 0x110889c399231e60
    +800, 0x64584a244866f3a0
    +801, 0xf02ec101a39405d3
    +802, 0xe73cd5e9a7f17283
    +803, 0xfea64869e7028234
    +804, 0x97559974ad877891
    +805, 0xc8695aba1dc9f2e5
    +806, 0x7b62b76ffc2264ec
    +807, 0xf5e1df172ec5ccd
    +808, 0xafaeb68765e443bd
    +809, 0xd3870eb2e8337623
    +810, 0x4f944d684138fb39
    +811, 0x6977c575038916ad
    +812, 0x8ada1a225df95a56
    +813, 0xe4044c6c58d15e54
    +814, 0x4e5121366681cf2
    +815, 0xcf8640b079357b0d
    +816, 0xcd5b157d44106fa3
    +817, 0x9d7a5481279e25a1
    +818, 0xe10e9db41fb4b34f
    +819, 0x1052607be1eadff9
    +820, 0x3403d67232fe2265
    +821, 0xac9358f498c34afc
    +822, 0x820172da0dc39c9
    +823, 0xe186e91a3b826b6a
    +824, 0x1a838e2a40284445
    +825, 0x1870b617ebd7bce6
    +826, 0xcb7cba4424be1ed7
    +827, 0x6a2e56e40fdf9041
    +828, 0xace93bbe108f97ee
    +829, 0xfeb9bc74ac41ca08
    +830, 0x8cb2d05b0f6a1f51
    +831, 0x73792309f3fac0a9
    +832, 0x2507343d431308ca
    +833, 0xd0ea1197be615412
    +834, 0xb1870812f1d2fa94
    +835, 0x6d067b6935dcd23e
    +836, 0xaf161014e5492c31
    +837, 0xd4be0dce97064be4
    +838, 0xf8edfe3fc75c20f1
    +839, 0x894751dc442d2d9c
    +840, 0xb4a95f6a6663456c
    +841, 0x74e93162e2d805db
    +842, 0x784bc5f3a7a2f645
    +843, 0xd234d7c5b0582ea9
    +844, 0x491f28d0ab6cb97c
    +845, 0xa79419e5cf4336c3
    +846, 0x66b00141978c849
    +847, 0xa7ddbd64698d563f
    +848, 0xefc33a4a5d97d4b2
    +849, 0x95075514a65aebdc
    +850, 0x40eca5b3e28cd25e
    +851, 0x90ec7d00e9c9e35d
    +852, 0x63e84104d5af417a
    +853, 0xdaca0ea32df5744
    +854, 0x7ed54f2587795881
    +855, 0x5a73931760af4ee0
    +856, 0x857d1a185a3081ec
    +857, 0x6eac2aabe67fb463
    +858, 0xd1f86155d8bfc55f
    +859, 0x6d56398f3e7877ef
    +860, 0x7642f61dfc62bc17
    +861, 0x1d76b12843246ffa
    +862, 0xde7817809b8a31d0
    +863, 0xbcca9cd091198f9d
    +864, 0xf71ca566dddcdfd4
    +865, 0xea4386ee8b61d082
    +866, 0xe351729d6010bac4
    +867, 0xfd685d8a49910dd6
    +868, 0xa7a20ea6c686bd3
    +869, 0x1cdaf82f4dbd5536
    +870, 0xa3da1d1e77dda3e0
    +871, 0x4f723b3818ff8b2a
    +872, 0x1290669eca152469
    +873, 0xb54158b52d30651b
    +874, 0xc06b74f2c7f0fee
    +875, 0x7d5840bcbf702379
    +876, 0x19fa4c1254a82ed
    +877, 0xcf5ce090ad0b38ea
    +878, 0xd4edd6ac9437e16d
    +879, 0xc6ebf25eb623b426
    +880, 0xd2b6dbdf00d8fea2
    +881, 0x949cf98391cc59e1
    +882, 0x380a0c7d0356f7b3
    +883, 0x8ffefe32465473bf
    +884, 0x637b6542d27c861e
    +885, 0x347d12ffc664ecd9
    +886, 0xea66e3a0c75a6b37
    +887, 0xc3aff6f34fb537a1
    +888, 0x67bdf3579959bf49
    +889, 0xa17a348e3a74b723
    +890, 0x93c9ef26ddadd569
    +891, 0x483909059a5ac0b2
    +892, 0x26ec9074b56d5a0d
    +893, 0x6216000d9a48403a
    +894, 0x79b43909eab1ec05
    +895, 0xe4a8e8d03649e0de
    +896, 0x1435d666f3ccdc08
    +897, 0xb9e22ba902650a0e
    +898, 0x44dffcccc68b41f8
    +899, 0x23e60dcc7a559a17
    +900, 0x6fd1735eacd81266
    +901, 0xf6bda0745ea20c8e
    +902, 0x85efcaefe271e07c
    +903, 0x9be996ee931cef42
    +904, 0xe78b41c158611d64
    +905, 0xd6201df605839830
    +906, 0x702e8e47d2769fd3
    +907, 0xb8dcf70e18cf14c
    +908, 0xac2690bab1bf5c17
    +909, 0x92b166b71205d696
    +910, 0xb0e73c795fc6df28
    +911, 0x4bf2322c8b6b6f0d
    +912, 0xa842fbe67918cea0
    +913, 0xb01a8675d9294e54
    +914, 0xfbe3c94f03ca5af2
    +915, 0x51a5c089600c441f
    +916, 0x60f0fd7512d85ded
    +917, 0xef3113d3bc2cadb0
    +918, 0xe1ea128ade300d60
    +919, 0xde413b7f8d92d746
    +920, 0xfc32c6d43f47c5d8
    +921, 0x69d551d8c2b54c68
    +922, 0xb9bc68c175777943
    +923, 0xb9c79c687f0dae90
    +924, 0xd799421ef883c06e
    +925, 0xbff553ca95a29a3e
    +926, 0xfc9ffac46bd0aca1
    +927, 0x4f6c3a30c80c3e5a
    +928, 0x8b7245bc6dc4a0a
    +929, 0xaf4e191a4575ff60
    +930, 0x41218c4a76b90f0b
    +931, 0x986052aa51b8e89b
    +932, 0x284b464ed5622f9
    +933, 0xba6bded912626b40
    +934, 0x43cad3ed7443cb5c
    +935, 0x21641fa95725f328
    +936, 0x6d99d6d09d755822
    +937, 0x8246dfa2d4838492
    +938, 0xd2ee70b9056f4726
    +939, 0x87db515a786fbb8b
    +940, 0x7c63e4c1d7786e7d
    +941, 0xd1a9d548f10b3e88
    +942, 0xa00856475f3b74c9
    +943, 0x7f1964ce67148bf4
    +944, 0x446650ec71e6018c
    +945, 0xb1805ca07d1b6345
    +946, 0x869c0a1625b7271b
    +947, 0x79d6da06ce2ecfe2
    +948, 0xec7b3cafc5e3c85f
    +949, 0x1745ce21e39f2c3d
    +950, 0xd9a0a7af6ee97825
    +951, 0x680e0e52a6e11d5c
    +952, 0xd86b3f344ff7f4cd
    +953, 0xab56af117c840b9c
    +954, 0x5c5404c7e333a10e
    +955, 0x4f1eb462f35d990d
    +956, 0xf857605a5644458e
    +957, 0x3bb87cdf09262f86
    +958, 0xd57295baf6da64b
    +959, 0xb5993f48472f2894
    +960, 0x7d1a501608c060b2
    +961, 0x45fabe2d0e54adf0
    +962, 0xbb41c3806afb4efe
    +963, 0xbfbc506049424c8
    +964, 0xb7dd6b67f2203344
    +965, 0x389ce52eff883b81
    +966, 0xe259c55c0cf6d000
    +967, 0x70fb3e3824f7d213
    +968, 0x9f36d5599ed55f4b
    +969, 0xd14cf6f12f83c4f7
    +970, 0x570a09d56aaa0b66
    +971, 0x8accafd527f4598
    +972, 0xa42d64c62175adfd
    +973, 0xddb9c6a87b6e1558
    +974, 0xd80b6c69fa1cde2a
    +975, 0x44ebaac10082207b
    +976, 0xf99be8889552fa1a
    +977, 0x38253cd4b38b5dc5
    +978, 0x85356c8b02675791
    +979, 0xbf91677b2ecdcf55
    +980, 0x2316cb85e93f366e
    +981, 0x9abf35954db6b053
    +982, 0xf49f7425e086b45a
    +983, 0x8f5b625e074afde2
    +984, 0xe0d614559791b080
    +985, 0xbf7b866afab2a525
    +986, 0xde89d7e1641a6412
    +987, 0x1d10687d8ae5b86f
    +988, 0x1f034caa0e904cbd
    +989, 0x2086357aec8a7a2c
    +990, 0x22dc476b80c56e1e
    +991, 0xbef5a73cc0e3a493
    +992, 0xddfa3829b26ed797
    +993, 0x8917a87ec3d4dc78
    +994, 0xfeabe390628c365e
    +995, 0x581b0c4f6fb2d642
    +996, 0x1ef8c590adbf5b9a
    +997, 0x4d8e13aac0cce879
    +998, 0xfe38f71e5977fad0
    +999, 0x1f83a32d4adfd2ed
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/data/philox-testset-2.csv b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/data/philox-testset-2.csv
    new file mode 100644
    index 0000000000000000000000000000000000000000..69d24c38c28959361283624e02f8891fa69b6c5f
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/data/philox-testset-2.csv
    @@ -0,0 +1,1001 @@
    +seed, 0x0
    +0, 0x399e5b222b82fa9
    +1, 0x41fd08c1f00f3bc5
    +2, 0x78b8824162ee4d04
    +3, 0x176747919e02739d
    +4, 0xfaa88f002a8d3596
    +5, 0x418eb6f592e6c227
    +6, 0xef83020b8344dd45
    +7, 0x30a74a1a6eaa064b
    +8, 0x93d43bf97a490c3
    +9, 0xe4ba28b442194cc
    +10, 0xc829083a168a8656
    +11, 0x73f45d50f8e22849
    +12, 0xf912db57352824cc
    +13, 0xf524216927b12ada
    +14, 0x22b7697473b1dfda
    +15, 0x311e2a936414b39f
    +16, 0xb905abfdcc425be6
    +17, 0x4b14630d031eac9c
    +18, 0x1cf0c4ae01222bc8
    +19, 0xa6c33efc6e82ef3
    +20, 0x43b3576937ba0948
    +21, 0x1e483d17cdde108a
    +22, 0x6722784cac11ac88
    +23, 0xee87569a48fc45d7
    +24, 0xb821dcbe74d18661
    +25, 0xa5d1876ef3da1a81
    +26, 0xe4121c2af72a483
    +27, 0x2d747e355a52cf43
    +28, 0x609059957bd03725
    +29, 0xc3327244b49e16c5
    +30, 0xb5ae6cb000dde769
    +31, 0x774315003209017
    +32, 0xa2013397ba8db605
    +33, 0x73b228945dbcd957
    +34, 0x801af7190375d3c0
    +35, 0xae6dca29f24c9c67
    +36, 0xd1cc0bcb1ca26249
    +37, 0x1defa62a5bd853be
    +38, 0x67c2f5557fa89462
    +39, 0xf1729b58122fab02
    +40, 0xb67eb71949ec6c42
    +41, 0x5456366ec1f8f7d7
    +42, 0x44492b32eb7966f5
    +43, 0xa801804159f175f1
    +44, 0x5a416f23cac70d84
    +45, 0x186f55293302303d
    +46, 0x7339d5d7b6a43639
    +47, 0xfc6df38d6a566121
    +48, 0xed2fe018f150b39e
    +49, 0x508e0b04a781fa1b
    +50, 0x8bee9d50f32eaf50
    +51, 0x9870015d37e63cc
    +52, 0x93c6b12309c14f2d
    +53, 0xb571cf798abe93ff
    +54, 0x85c35a297a88ae6e
    +55, 0x9b1b79afe497a2ae
    +56, 0x1ca02e5b95d96b8d
    +57, 0x5bb695a666c0a94a
    +58, 0x4e3caf9bbab0b208
    +59, 0x44a44be1a89f2dc1
    +60, 0x4ff37c33445758d1
    +61, 0xd0e02875322f35da
    +62, 0xfd449a91fb92646b
    +63, 0xbe0b49096b95db4d
    +64, 0xffa3647cad13ef5d
    +65, 0x75c127a61acd10c8
    +66, 0xd65f697756f5f98e
    +67, 0x3ced84be93d94434
    +68, 0x4da3095c2fc46d68
    +69, 0x67564e2a771ee9ac
    +70, 0x36944775180644a9
    +71, 0xf458db1c177cdb60
    +72, 0x5b58406dcd034c8
    +73, 0x793301a3fdab2a73
    +74, 0x1c2a1a16d6db6128
    +75, 0xc2dacd4ddddbe56c
    +76, 0x2e7d15be2301a111
    +77, 0xd4f4a6341b3bcd18
    +78, 0x3622996bbe6a9e3b
    +79, 0xaf29aa9a7d6d47da
    +80, 0x6d7dbb74a4cd68ae
    +81, 0xc260a17e0f39f841
    +82, 0xdee0170f2af66f0d
    +83, 0xf84ae780d7b5a06e
    +84, 0x8326247b73f43c3a
    +85, 0xd44eef44b4f98b84
    +86, 0x3d10aee62ec895e3
    +87, 0x4f23fef01bf703b3
    +88, 0xf8e50aa57d888df6
    +89, 0x7da67411e3bef261
    +90, 0x1d00f2769b2f96d7
    +91, 0x7ef9a15b7444b84e
    +92, 0xcfa16436cc2b7e21
    +93, 0x29ab8cfac00460ff
    +94, 0x23613de8608b0e70
    +95, 0xb1aa0980625798a8
    +96, 0xb9256fd29db7df99
    +97, 0xdacf311bf3e7fa18
    +98, 0xa013c8f9fada20d8
    +99, 0xaf5fd4fe8230fe3e
    +100, 0xd3d59ca55102bc5c
    +101, 0x9d08e2aa5242767f
    +102, 0x40278fe131e83b53
    +103, 0x56397d03c7c14c98
    +104, 0xe874b77b119359b3
    +105, 0x926a1ba4304ab19f
    +106, 0x1e115d5aa695a91d
    +107, 0xc6a459df441f2fe3
    +108, 0x2ca842bc1b0b3c6a
    +109, 0x24c804cf8e5eed16
    +110, 0x7ca00fc4a4c3ebd3
    +111, 0x546af7cecc4a4ba6
    +112, 0x8faae1fa18fd6e3
    +113, 0x40420b0089641a6a
    +114, 0x88175a35d9abcb83
    +115, 0xf7d746d1b8b1357c
    +116, 0x7dae771a651be970
    +117, 0x2f6485247ee4df84
    +118, 0x6883702fab2d8ec5
    +119, 0xeb7eea829a67f9a6
    +120, 0x60d5880b485562ed
    +121, 0x7d4ca3d7e41a4e7e
    +122, 0xbb7fef961ab8de18
    +123, 0x3b92452fb810c164
    +124, 0x5f4b4755348b338
    +125, 0xca45a715a7539806
    +126, 0xc33efd9da5399dd
    +127, 0x593d665a51d4aedd
    +128, 0x75d6b8636563036b
    +129, 0x7b57caa55e262082
    +130, 0x4ede7427969e0dd5
    +131, 0xc3f19b6f78ea00b
    +132, 0xeea7bab9be2181ea
    +133, 0x652c45fe9c420c04
    +134, 0x14ba9e3d175670ee
    +135, 0xd2ad156ba6490474
    +136, 0x4d65ae41065f614
    +137, 0x6ff911c8afa28eb1
    +138, 0xedc2b33588f3cb68
    +139, 0x437c8bc324666a2f
    +140, 0x828cee25457a3f0
    +141, 0x530c986091f31b9b
    +142, 0x2f34671e8326ade7
    +143, 0x4f686a8f4d77f6da
    +144, 0xa4c1987083498895
    +145, 0xbce5a88b672b0fb1
    +146, 0x8476115a9e6a00cc
    +147, 0x16de18a55dd2c238
    +148, 0xdf38cf4c416232bc
    +149, 0x2cb837924e7559f3
    +150, 0xfad4727484e982ed
    +151, 0x32a55d4b7801e4f
    +152, 0x8b9ef96804bd10a5
    +153, 0xa1fd422c9b5cf2a9
    +154, 0xf46ddb122eb7e442
    +155, 0x6e3842547afa3b33
    +156, 0x863dee1c34afe5c4
    +157, 0x6a43a1935b6db171
    +158, 0x1060a5c2f8145821
    +159, 0xf783ec9ed34c4607
    +160, 0x1da4a86bf5f8c0b0
    +161, 0x4c7714041ba12af8
    +162, 0x580da7010be2f192
    +163, 0xad682fe795a7ea7a
    +164, 0x6687b6cb88a9ed2c
    +165, 0x3c8d4b175517cd18
    +166, 0xe9247c3a524a6b6b
    +167, 0x337ca9cfaa02658
    +168, 0xed95399481c6feec
    +169, 0x58726a088e606062
    +170, 0xfe7588a5b4ee342a
    +171, 0xee434c7ed146fdee
    +172, 0xe2ade8b60fdc4ba5
    +173, 0xd57e4c155de4eaab
    +174, 0xdefeae12de1137cb
    +175, 0xb7a276a241316ac1
    +176, 0xeb838b1b1df4ca15
    +177, 0x6f78965edea32f6f
    +178, 0x18bebd264d7a5d53
    +179, 0x3641c691d77005ec
    +180, 0xbe70ed7efea8c24c
    +181, 0x33047fa8d03ca560
    +182, 0x3bed0d2221ff0f87
    +183, 0x23083a6ffbcf38a2
    +184, 0xc23eb827073d3fa5
    +185, 0xc873bb3415e9fb9b
    +186, 0xa4645179e54147fe
    +187, 0x2c72fb443f66e207
    +188, 0x98084915dd89d8f4
    +189, 0x88baa2de12c99037
    +190, 0x85c74ab238cb795f
    +191, 0xe122186469ea3a26
    +192, 0x4c3bba99b3249292
    +193, 0x85d6845d9a015234
    +194, 0x147ddd69c13e6a31
    +195, 0x255f4d678c9a570b
    +196, 0x2d7c0c410bf962b4
    +197, 0x58eb7649e0aa16ca
    +198, 0x9d240bf662fe0783
    +199, 0x5f74f6fa32d293cc
    +200, 0x4928e52f0f79d9b9
    +201, 0xe61c2b87146b706d
    +202, 0xcfcd90d100cf5431
    +203, 0xf15ea8138e6aa178
    +204, 0x6ab8287024f9a819
    +205, 0xed8942593db74e01
    +206, 0xefc00e4ec2ae36dd
    +207, 0xc21429fb9387f334
    +208, 0xf9a3389e285a9bce
    +209, 0xacdee8c43aae49b3
    +210, 0xefc382f02ad55c25
    +211, 0x1153b50e8d406b72
    +212, 0xb00d39ebcc2f89d8
    +213, 0xde62f0b9831c8850
    +214, 0xc076994662eef6c7
    +215, 0x66f08f4752f1e3ef
    +216, 0x283b90619796249a
    +217, 0x4e4869bc4227499e
    +218, 0xb45ad78a49efd7ed
    +219, 0xffe19aa77abf5f4b
    +220, 0xfce11a0daf913aef
    +221, 0x7e4e64450d5cdceb
    +222, 0xe9621997cfd62762
    +223, 0x4d2c9e156868081
    +224, 0x4e2d96eb7cc9a08
    +225, 0xda74849bba6e3bd3
    +226, 0x6f4621da935e7fde
    +227, 0xb94b914aa0497259
    +228, 0xd50d03e8b8db1563
    +229, 0x1a45c1ce5dca422e
    +230, 0xc8d30d33276f843f
    +231, 0xb57245774e4176b4
    +232, 0x8d36342c05abbbb1
    +233, 0x3591ad893ecf9e78
    +234, 0x62f4717239ee0ac8
    +235, 0x9b71148a1a1d4200
    +236, 0x65f8e0f56dd94463
    +237, 0x453b1fcfd4fac8c2
    +238, 0x4c25e48e54a55865
    +239, 0xa866baa05112ace2
    +240, 0x7741d3c69c6e79c5
    +241, 0x7deb375e8f4f7a8a
    +242, 0xc242087ede42abd8
    +243, 0x2fa9d1d488750c4b
    +244, 0xe8940137a935d3d3
    +245, 0x1dab4918ca24b2f2
    +246, 0xe2368c782168fe3e
    +247, 0x6e8b2d1d73695909
    +248, 0x70455ebea268b33e
    +249, 0x656a919202e28da1
    +250, 0x5a5a8935647da999
    +251, 0x428c6f77e118c13c
    +252, 0xa87aee2b675bb083
    +253, 0x3873a6412b239969
    +254, 0x5f72c1e91cb8a2ee
    +255, 0xa25af80a1beb5679
    +256, 0x1af65d27c7b4abc3
    +257, 0x133437060670e067
    +258, 0xb1990fa39a97d32e
    +259, 0x724adc89ae10ed17
    +260, 0x3f682a3f2363a240
    +261, 0x29198f8dbd343499
    +262, 0xdfaeeaa42bc51105
    +263, 0x5baff3901b9480c2
    +264, 0x3f760a67043e77f5
    +265, 0x610fa7aa355a43ba
    +266, 0x394856ac09c4f7a7
    +267, 0x1d9229d058aee82e
    +268, 0x19c674804c41aeec
    +269, 0x74cf12372012f4aa
    +270, 0xa5d89b353fa2f6ca
    +271, 0x697e4f672ac363dd
    +272, 0xde6f55ba73df5af9
    +273, 0x679cf537510bd68f
    +274, 0x3dc916114ae9ef7e
    +275, 0xd7e31a66ec2ee7ba
    +276, 0xc21bebb968728495
    +277, 0xc5e0781414e2adfd
    +278, 0x71147b5412ddd4bd
    +279, 0x3b864b410625cca9
    +280, 0x433d67c0036cdc6
    +281, 0x48083afa0ae20b1b
    +282, 0x2d80beecd64ac4e8
    +283, 0x2a753c27c3a3ee3e
    +284, 0xb2c5e6afd1fe051a
    +285, 0xea677930cd66c46b
    +286, 0x4c3960932f92810a
    +287, 0xf1b367a9e527eaba
    +288, 0xb7d92a8a9a69a98e
    +289, 0x9f9ad3210bd6b453
    +290, 0x817f2889db2dcbd8
    +291, 0x4270a665ac15813c
    +292, 0x90b85353bd2be4dd
    +293, 0x10c0460f7b2d68d
    +294, 0x11cef32b94f947f5
    +295, 0x3cf29ed8e7d477e8
    +296, 0x793aaa9bd50599ef
    +297, 0xbac15d1190014aad
    +298, 0x987944ae80b5cb13
    +299, 0x460aa51f8d57c484
    +300, 0xc77df0385f97c2d3
    +301, 0x92e743b7293a3822
    +302, 0xbc3458bcfbcbb8c0
    +303, 0xe277bcf3d04b4ed7
    +304, 0xa537ae5cf1c9a31c
    +305, 0x95eb00d30bd8cfb2
    +306, 0x6376361c24e4f2dd
    +307, 0x374477fe87b9ea8e
    +308, 0x8210f1a9a039902e
    +309, 0xe7628f7031321f68
    +310, 0x8b8e9c0888fc1d3d
    +311, 0x306be461fdc9e0ed
    +312, 0x510009372f9b56f5
    +313, 0xa6e6fa486b7a027a
    +314, 0x9d3f002025203b5a
    +315, 0x7a46e0e81ecbef86
    +316, 0x41e280c611d04df0
    +317, 0xedcec10418a99e8a
    +318, 0x5c27b6327e0b9dbd
    +319, 0xa81ed2035b509f07
    +320, 0x3581e855983a4cc4
    +321, 0x4744594b25e9809d
    +322, 0xc737ac7c27fbd0ed
    +323, 0x1b523a307045433a
    +324, 0x8b4ce9171076f1d9
    +325, 0x2db02d817cd5eec0
    +326, 0x24a1f1229af50288
    +327, 0x5550c0dcf583ff16
    +328, 0x3587baaa122ec422
    +329, 0xf9d3dc894229e510
    +330, 0xf3100430d5cf8e87
    +331, 0xc31af79862f8e2fb
    +332, 0xd20582063b9f3537
    +333, 0xac5e90ac95fcc7ad
    +334, 0x107c4c704d5109d4
    +335, 0xebc8628906dbfd70
    +336, 0x215242776da8c531
    +337, 0xa98002f1dcf08b51
    +338, 0xbc3bdc07f3b09718
    +339, 0x238677062495b512
    +340, 0x53b4796f2a3c49e8
    +341, 0x6424286467e22f0e
    +342, 0x14d0952a11a71bac
    +343, 0x2f97098149b82514
    +344, 0x3777f2fdc425ad2
    +345, 0xa32f2382938876d4
    +346, 0xda8a39a021f20ae3
    +347, 0x364361ef0a6ac32c
    +348, 0x4413eede008ff05a
    +349, 0x8dda8ace851aa327
    +350, 0x4303cabbdcecd1ee
    +351, 0x2e69f06d74aa549f
    +352, 0x4797079cd4d9275c
    +353, 0xc7b1890917e98307
    +354, 0x34031b0e822a4b4c
    +355, 0xfc79f76b566303ea
    +356, 0x77014adbe255a930
    +357, 0xab6c43dd162f3be5
    +358, 0xa430041f3463f6b9
    +359, 0x5c191a32ada3f84a
    +360, 0xe8674a0781645a31
    +361, 0x3a11cb667b8d0916
    +362, 0xaedc73e80c39fd8a
    +363, 0xfde12c1b42328765
    +364, 0x97abb7dcccdc1a0b
    +365, 0x52475c14d2167bc8
    +366, 0x540e8811196d5aff
    +367, 0xa867e4ccdb2b4b77
    +368, 0x2be04af61e5bcfb9
    +369, 0x81b645102bfc5dfd
    +370, 0x96a52c9a66c6450f
    +371, 0x632ec2d136889234
    +372, 0x4ed530c0b36a6c25
    +373, 0x6f4851225546b75
    +374, 0x2c065d6ba46a1144
    +375, 0xf8a3613ff416551d
    +376, 0xb5f0fd60e9c971a9
    +377, 0x339011a03bb4be65
    +378, 0x9439f72b6995ded6
    +379, 0xc1b03f3ef3b2292d
    +380, 0xad12fd221daab3ae
    +381, 0xf615b770f2cf996f
    +382, 0x269d0fdcb764172
    +383, 0x67837025e8039256
    +384, 0x6402831fc823fafa
    +385, 0x22854146a4abb964
    +386, 0x7b5ad9b5a1bad7a8
    +387, 0x67170e7beb6ac935
    +388, 0xfc2d1e8e24adfaaa
    +389, 0x7ded4395345ff40d
    +390, 0x418981760a80dd07
    +391, 0xc03bef38022c1d2
    +392, 0x3a11850b26eade29
    +393, 0xaa56d02c7175c5f4
    +394, 0xd83b7917b9bfbff5
    +395, 0x3c1df2f8fa6fced3
    +396, 0xf3d6e2999c0bb760
    +397, 0xc66d683a59a950e3
    +398, 0x8e3972a9d73ffabf
    +399, 0x97720a0443edffd9
    +400, 0xa85f5d2fe198444a
    +401, 0xfc5f0458e1b0de5e
    +402, 0xe3973f03df632b87
    +403, 0xe151073c84c594b3
    +404, 0x68eb4e22e7ff8ecf
    +405, 0x274f36eaed7cae27
    +406, 0x3b87b1eb60896b13
    +407, 0xbe0b2f831442d70a
    +408, 0x2782ed7a48a1b328
    +409, 0xb3619d890310f704
    +410, 0xb03926b11b55921a
    +411, 0xdb46fc44aa6a0ce4
    +412, 0x4b063e2ef2e9453a
    +413, 0xe1584f1aeec60fb5
    +414, 0x7092bd6a879c5a49
    +415, 0xb84e1e7c7d52b0e6
    +416, 0x29d09ca48db64dfb
    +417, 0x8f6c4a402066e905
    +418, 0x77390795eabc36b
    +419, 0xcc2dc2e4141cc69f
    +420, 0x2727f83beb9e3c7c
    +421, 0x1b29868619331de0
    +422, 0xd38c571e192c246f
    +423, 0x535327479fe37b6f
    +424, 0xaff9ce5758617eb3
    +425, 0x5658539e9288a4e4
    +426, 0x8df91d87126c4c6d
    +427, 0xe931cf8fdba6e255
    +428, 0x815dfdf25fbee9e8
    +429, 0x5c61f4c7cba91697
    +430, 0xdd5f5512fe2313a1
    +431, 0x499dd918a92a53cd
    +432, 0xa7e969d007c97dfd
    +433, 0xb8d39c6fc81ac0bb
    +434, 0x1d646983def5746c
    +435, 0x44d4b3b17432a60c
    +436, 0x65664232a14db1e3
    +437, 0xda8fae6433e7500b
    +438, 0xbe51b94ff2a3fe94
    +439, 0xe9b1bd9a9098ef9f
    +440, 0xfe47d54176297ef5
    +441, 0xb8ab99bc03bb7135
    +442, 0xcfad97f608565b38
    +443, 0xf05da71f6760d9c1
    +444, 0xef8da40a7c70e7b
    +445, 0xe0465d58dbd5d138
    +446, 0xb54a2d70eb1a938
    +447, 0xfdd50c905958f2d8
    +448, 0x3c41933c90a57d43
    +449, 0x678f6d894c6ad0bb
    +450, 0x403e8f4582274e8
    +451, 0x5cbbe975668df6b0
    +452, 0x297e6520a7902f03
    +453, 0x8f6dded33cd1efd7
    +454, 0x8e903c97be8d783b
    +455, 0x10bd015577e30f77
    +456, 0x3fcd69d1c36eab0c
    +457, 0xb45989f3ca198d3
    +458, 0x507655ce02b491a9
    +459, 0xa92cf99bb78602ce
    +460, 0xebfb82055fbc2f0f
    +461, 0x3334256279289b7a
    +462, 0xc19d2a0f740ee0ac
    +463, 0x8bb070dea3934905
    +464, 0xa4ab57d3a8d1b3eb
    +465, 0xfee1b09bcacf7ff4
    +466, 0xccc7fb41ceec41fa
    +467, 0xd4da49094eb5a74d
    +468, 0xed5c693770af02ed
    +469, 0x369dabc9bbfaa8e4
    +470, 0x7eab9f360d054199
    +471, 0xe36dbebf5ee94076
    +472, 0xd30840e499b23d7
    +473, 0x8678e6cb545015ff
    +474, 0x3a47932ca0b336e
    +475, 0xeb7c742b6e93d6fe
    +476, 0x1404ea51fe5a62a9
    +477, 0xa72cd49db978e288
    +478, 0xfd7bada020173dcf
    +479, 0xc9e74fc7abe50054
    +480, 0x93197847bb66808d
    +481, 0x25fd5f053dce5698
    +482, 0xe198a9b18cc21f4
    +483, 0x5cc27b1689452d5d
    +484, 0x8b3657af955a98dc
    +485, 0xc17f7584f54aa1c0
    +486, 0xe821b088246b1427
    +487, 0x32b5a9f6b45b6fa0
    +488, 0x2aef7c315c2bae0c
    +489, 0xe1af8129846b705a
    +490, 0x4123b4c091b34614
    +491, 0x6999d61ec341c073
    +492, 0x14b9a8fcf86831ea
    +493, 0xfd4cff6548f46c9f
    +494, 0x350c3b7e6cc8d7d6
    +495, 0x202a5047fecafcd5
    +496, 0xa82509fe496bb57d
    +497, 0x835e4b2608b575fe
    +498, 0xf3abe3da919f54ec
    +499, 0x8705a21e2c9b8796
    +500, 0xfd02d1427005c314
    +501, 0xa38458faa637f49b
    +502, 0x61622f2360e7622a
    +503, 0xe89335a773c2963b
    +504, 0x481264b659b0e0d0
    +505, 0x1e82ae94ebf62f15
    +506, 0x8ea7812de49209d4
    +507, 0xff963d764680584
    +508, 0x418a68bef717f4af
    +509, 0x581f0e7621a8ab91
    +510, 0x840337e9a0ec4150
    +511, 0x951ef61b344be505
    +512, 0xc8b1b899feb61ec2
    +513, 0x8b78ca13c56f6ed9
    +514, 0x3d2fd793715a946f
    +515, 0xf1c04fabcd0f4084
    +516, 0x92b602614a9a9fcc
    +517, 0x7991bd7a94a65be7
    +518, 0x5dead10b06cad2d7
    +519, 0xda7719b33f722f06
    +520, 0x9d87a722b7bff71e
    +521, 0xb038e479071409e9
    +522, 0xf4e8bbec48054775
    +523, 0x4fec2cd7a28a88ea
    +524, 0x839e28526aad3e56
    +525, 0xd37ec57852a98bf0
    +526, 0xdef2cbbe00f3a02d
    +527, 0x1aecfe01a9e4d801
    +528, 0x59018d3c8beaf067
    +529, 0x892753e6ac8bf3cd
    +530, 0xefdd3437023d2d1c
    +531, 0x447bfbd148c8cb88
    +532, 0x282380221bd442b8
    +533, 0xfce8658d1347384a
    +534, 0x60b211a7ec6bfa8
    +535, 0xd21729cfcc692974
    +536, 0x162087ecd5038a47
    +537, 0x2b17000c4bce39d2
    +538, 0x3a1f75ff6adcdce0
    +539, 0x721a411d312f1a2c
    +540, 0x9c13b6133f66934d
    +541, 0xaa975d14978980e5
    +542, 0x9403dbd4754203fa
    +543, 0x588c15762fdd643
    +544, 0xdd1290f8d0ada73a
    +545, 0xd9b77380936103f4
    +546, 0xb2e2047a356eb829
    +547, 0x7019e5e7f76f7a47
    +548, 0x3c29a461f62b001d
    +549, 0xa07dc6cfab59c116
    +550, 0x9b97e278433f8eb
    +551, 0x6affc714e7236588
    +552, 0x36170aeb32911a73
    +553, 0x4a665104d364a789
    +554, 0x4be01464ec276c9c
    +555, 0x71bb10271a8b4ecf
    +556, 0xbf62e1d068bc018
    +557, 0xc9ada5db2cbbb413
    +558, 0x2bded75e726650e5
    +559, 0x33d5a7af2f34385d
    +560, 0x8179c46661d85657
    +561, 0x324ebcfd29267359
    +562, 0xac4c9311dc9f9110
    +563, 0xc14bb6a52f9f9c0
    +564, 0xc430abe15e7fb9db
    +565, 0xf1cce5c14df91c38
    +566, 0x651e3efa2c0750d3
    +567, 0x38a33604a8be5c75
    +568, 0x7aaf77fe7ff56a49
    +569, 0xc0d1cc56bbf27706
    +570, 0x887aa47324e156c6
    +571, 0x12547c004b085e8d
    +572, 0xd86a8d6fbbbfd011
    +573, 0x57c860188c92d7b4
    +574, 0xcd5d3843d361b8ca
    +575, 0x8f586ef05a9cb3ef
    +576, 0x174456e1ba6267d5
    +577, 0xf5dc302c62fe583c
    +578, 0xa349442fabcdb71
    +579, 0xe5123c1a8b6fd08e
    +580, 0x80681552aa318593
    +581, 0xb295396deaef1e31
    +582, 0xabb626e0b900e32b
    +583, 0xf024db8d3f19c15e
    +584, 0x1d04bb9548e2fb6c
    +585, 0xd8ed2b2214936c2b
    +586, 0x618ca1e430a52bc9
    +587, 0xccbca44a6088136b
    +588, 0xd0481855c8b9ccbe
    +589, 0x3c92a2fade28bdf7
    +590, 0x855e9fefc38c0816
    +591, 0x1269bbfe55a7b27c
    +592, 0x1d6c853d83726d43
    +593, 0xc8655511cc7fcafc
    +594, 0x301503eb125a9b0e
    +595, 0xb3108e4532016b11
    +596, 0xbb7ab6245da9cb3d
    +597, 0x18004c49116d85eb
    +598, 0x3480849c20f61129
    +599, 0xe28f45157463937b
    +600, 0x8e85e61060f2ce1
    +601, 0x1673da4ec589ba5e
    +602, 0x74b9a6bd1b194712
    +603, 0xed39e147fa8b7601
    +604, 0x28ce54019102ca77
    +605, 0x42e0347f6d7a2f30
    +606, 0xb6a908d1c4814731
    +607, 0x16c3435e4e9a126d
    +608, 0x8880190514c1ad54
    +609, 0xfffd86229a6f773c
    +610, 0x4f2420cdb0aa1a93
    +611, 0xf8e1acb4120fc1fa
    +612, 0x63a8c553ab36a2f2
    +613, 0x86b88cf3c0a6a190
    +614, 0x44d8b2801622c792
    +615, 0xf6eae14e93082ff1
    +616, 0xd9ed4f5d1b8fac61
    +617, 0x1808ce17f4e1f70
    +618, 0x446e83ea336f262f
    +619, 0xc7c802b04c0917b7
    +620, 0x626f45fd64968b73
    +621, 0x9ffa540edc9b2c5c
    +622, 0xa96a1e219e486af8
    +623, 0x2bb8963884e887a1
    +624, 0xba7f68a5d029e3c4
    +625, 0xefc45f44392d9ca0
    +626, 0x98d77762503c5eab
    +627, 0xd89bcf62f2da627c
    +628, 0xa3cab8347f833151
    +629, 0xa095b7595907d5c7
    +630, 0x3b3041274286181
    +631, 0xb518db8919eb71fa
    +632, 0x187036c14fdc9a36
    +633, 0xd06e28301e696f5d
    +634, 0xdbc71184e0c56492
    +635, 0xfe51e9cae6125bfd
    +636, 0x3b12d17cd014df24
    +637, 0x3b95e4e2c986ac1a
    +638, 0x29c1cce59fb2dea2
    +639, 0x58c05793182a49d6
    +640, 0xc016477e330d8c00
    +641, 0x79ef335133ada5d
    +642, 0x168e2cad941203f3
    +643, 0xf99d0f219d702ef0
    +644, 0x655628068f8f135b
    +645, 0xdcdea51910ae3f92
    +646, 0x8e4505039c567892
    +647, 0x91a9ec7e947c89ae
    +648, 0x8717172530f93949
    +649, 0x1c80aba9a440171a
    +650, 0x9c8f83f6ebe7441e
    +651, 0x6c05e1efea4aa7f9
    +652, 0x10af696b777c01b
    +653, 0x5892e9d9a92fc309
    +654, 0xd2ba7da71e709432
    +655, 0x46378c7c3269a466
    +656, 0x942c63dfe18e772c
    +657, 0x6245cf02ef2476f
    +658, 0x6f265b2759ea2aea
    +659, 0x5aa757f17d17f4a6
    +660, 0x1ad6a3c44fa09be6
    +661, 0xe861af14e7015fb8
    +662, 0x86be2e7db388c77
    +663, 0x5c7bba32b519e9a0
    +664, 0x3feb314850c4437b
    +665, 0x97955add60cfb45b
    +666, 0xfdb536230a540bdc
    +667, 0xdac9d7bf6e58512e
    +668, 0x4894c00e474e8120
    +669, 0xa1918a37739da366
    +670, 0xa8097f2096532807
    +671, 0x592afe50e6c5e643
    +672, 0xd69050ee6dcb33dc
    +673, 0xa6956b262dd3c561
    +674, 0x1a55c815555e63f7
    +675, 0x2ec7fd37516de2bb
    +676, 0x8ec251d9c70e76ba
    +677, 0x9b76e4abafd2689
    +678, 0x9ce3f5c751a57df1
    +679, 0x915c4818bf287bc7
    +680, 0x2293a0d1fe07c735
    +681, 0x7627dcd5d5a66d3d
    +682, 0xb5e4f92cc49c7138
    +683, 0x6fc51298731d268c
    +684, 0xd19800aa95441f87
    +685, 0x14f70f31162fa115
    +686, 0x41a3da3752936f59
    +687, 0xbec0652be95652ee
    +688, 0x7aa4bdb1020a290f
    +689, 0x4382d0d9bee899ef
    +690, 0xe6d988ae4277d6ff
    +691, 0xe618088ccb2a32d1
    +692, 0x411669dfaa899e90
    +693, 0x234e2bf4ba76d9f
    +694, 0xe109fe4cb7828687
    +695, 0x1fb96b5022b0b360
    +696, 0x6b24ad76c061a716
    +697, 0x7e1781d4d7ecee15
    +698, 0xf20c2dbe82ba38ba
    +699, 0xeda8e8ae1d943655
    +700, 0xa58d196e2a77eaec
    +701, 0x44564765a5995a0b
    +702, 0x11902fe871ecae21
    +703, 0x2ea60279900e675d
    +704, 0x38427227c18a9a96
    +705, 0xe0af01490a1b1b48
    +706, 0x826f91997e057824
    +707, 0x1e57308e6e50451
    +708, 0xb42d469bbbfdc350
    +709, 0xb9734cff1109c49b
    +710, 0x98967559bb9d364f
    +711, 0xd6be360041907c12
    +712, 0xa86a1279122a1e21
    +713, 0x26f99a8527bfc698
    +714, 0xfa8b85758f28f5d6
    +715, 0xe3057429940806ae
    +716, 0x4bee2d7e84f93b2b
    +717, 0x948350a76ea506f4
    +718, 0xa139154488045e74
    +719, 0x8893579ba5e78085
    +720, 0x5f21c215c6a9e397
    +721, 0x456134f3a59641dc
    +722, 0x92c0273f8e97a9c6
    +723, 0xd2936c9c3f0c6936
    +724, 0xcfa4221e752c4735
    +725, 0x28cd5a7457355dca
    +726, 0xecdfdde23d90999f
    +727, 0x60631b2d494d032b
    +728, 0xf67289df269a827f
    +729, 0xcbe8011ef0f5b7ef
    +730, 0x20eea973c70a84f5
    +731, 0xbe1fd200398557ce
    +732, 0xd2279ee030191bba
    +733, 0xf2bd4291dedaf819
    +734, 0xfc6d167dbe8c402
    +735, 0x39ac298da5d0044b
    +736, 0xceac026f5f561ce
    +737, 0x10a5b0bdd8ad60e6
    +738, 0xdeb3c626df6d4bcb
    +739, 0x3c128962e77ff6ca
    +740, 0xc786262e9c67a0e5
    +741, 0x4332855b3febcdc0
    +742, 0x7bda9724d1c0e020
    +743, 0x6a8c93399bc4df22
    +744, 0xa9b20100ac707396
    +745, 0xa11a3458502c4eb5
    +746, 0xb185461c60478941
    +747, 0x13131d56195b7ff6
    +748, 0x8d55875ddbd4aa1c
    +749, 0xc09b67425f469aa5
    +750, 0x39e33786cc7594c4
    +751, 0x75e96db8e4b08b93
    +752, 0xda01cd12a3275d1e
    +753, 0x2c49e7822344fab5
    +754, 0x9bd5f10612514ca7
    +755, 0x1c801a5c828e7332
    +756, 0x29797d3f4f6c7b4c
    +757, 0xac992715e21e4e53
    +758, 0xe40e89ee887ddb37
    +759, 0x15189a2b265a783b
    +760, 0xa854159a52af5c5
    +761, 0xb9d8a5a81c12bead
    +762, 0x3240cdc9d59e2a58
    +763, 0x1d0b872234cf8e23
    +764, 0xc01224cf6ce12cff
    +765, 0x2601e9f3905c8663
    +766, 0xd4ecf9890168d6b4
    +767, 0xa45db796d89bfdd5
    +768, 0x9f389406dad64ab4
    +769, 0xa5a851adce43ffe3
    +770, 0xd0962c41c26e5aa9
    +771, 0x8a671679e48510a4
    +772, 0xc196dc0924a6bfeb
    +773, 0x3ead661043b549cb
    +774, 0x51af4ca737d405ac
    +775, 0xf4425b5c62275fb6
    +776, 0x71e69d1f818c10f5
    +777, 0xacaf4af2d3c70162
    +778, 0x2e1f1d4fd7524244
    +779, 0xe54fdd8f388890e8
    +780, 0xfda0d33e84eb2b83
    +781, 0x53965c5e392b81da
    +782, 0x5c92288267263097
    +783, 0xcac1b431c878c66c
    +784, 0x36c0e1cf417241c6
    +785, 0x5cc4d9cd1a36bf2c
    +786, 0x32e4257bb5d3e470
    +787, 0x4aecff904adb44fb
    +788, 0x4d91a8e0d1d60cac
    +789, 0xa3b478388385b038
    +790, 0x48d955f24eba70be
    +791, 0x310e4deb07f24f68
    +792, 0x8853e73b1f30a5a
    +793, 0x278aee45c2a65c5
    +794, 0xf6932eedbd62fb0b
    +795, 0xafb95958c82fafad
    +796, 0x78e807c18616c16c
    +797, 0xd7abadda7488ed9f
    +798, 0x2dd72e2572aa2ae6
    +799, 0x6ec3791982c2be09
    +800, 0x6865bb314fac478f
    +801, 0xa14dc0ce09000d1a
    +802, 0xb8081ad134da10f2
    +803, 0xc4ac1534aa825ef5
    +804, 0xd83aeb48ae2d538f
    +805, 0x38052027e3074be4
    +806, 0xa9833e06ef136582
    +807, 0x4f02d790ec9fd78
    +808, 0xec2f60bc711c5bdc
    +809, 0x9253b0d12268e561
    +810, 0xa8ac607fdd62c206
    +811, 0x895e28ebc920289f
    +812, 0xe2fd42b154243ac7
    +813, 0xc69cac2f776eee19
    +814, 0xf4d4ac11db56d0dc
    +815, 0xa8d37049b9f39833
    +816, 0x75abbf8a196c337c
    +817, 0xb115bb76750d27b8
    +818, 0x39426d187839154
    +819, 0xd488423e7f38bf83
    +820, 0xbb92e0c76ecb6a62
    +821, 0x3055a018ce39f4e3
    +822, 0xc93fe0e907729bfb
    +823, 0x65985d17c5863340
    +824, 0x2088ae081b2028e1
    +825, 0x6e628de873314057
    +826, 0x864377cccf573f0e
    +827, 0xae03f4c9aa63d132
    +828, 0xb1db766d6404c66d
    +829, 0xdce5a22414a374b
    +830, 0x622155b777819997
    +831, 0x69fe96e620371f3c
    +832, 0xa9c67dbc326d94fc
    +833, 0x932a84ae5dd43bab
    +834, 0xe2301a20f6c48c3f
    +835, 0x795d2e79c6477300
    +836, 0xd8e3e631289521e7
    +837, 0xae2684979002dfd6
    +838, 0xc9c2392377550f89
    +839, 0xa1b0c99d508ef7ec
    +840, 0x593aef3c5a5272ec
    +841, 0xe32e511a4b7162cd
    +842, 0xab3b81655f5a2857
    +843, 0x1b535e1a0aaf053e
    +844, 0x5b33f56c1b6a07e2
    +845, 0x782dc8cfcac4ef36
    +846, 0xb3d4f256eecfd202
    +847, 0xf73a6598f58c4f7e
    +848, 0xd5722189524870ae
    +849, 0x707878de6b995fc0
    +850, 0xc3eb6ba73e3d7e8a
    +851, 0xca75c017655b75a7
    +852, 0x1b29369ea3541e5f
    +853, 0x352e98858bdb58a3
    +854, 0x1e4412d184b6b27d
    +855, 0x2d375ba0304b2d17
    +856, 0x56c30fce69a5d08e
    +857, 0x6b8c2b0c06584bda
    +858, 0xde4dfff228c8c91f
    +859, 0xb7c9edd574e6287f
    +860, 0xf6078281c9fca2b2
    +861, 0xb9b9a51de02a2f1e
    +862, 0xa411bef31c0103b0
    +863, 0xc5facd8fc5e1d7a3
    +864, 0x54e631c05ddf7359
    +865, 0x815b42b3fd06c474
    +866, 0xc9ac07566fda18ec
    +867, 0xd84ea62957bd8e15
    +868, 0x5575f74b5cfd8803
    +869, 0x5779a8d460c2e304
    +870, 0xfd6e87e264a85587
    +871, 0xa1d674daa320b26d
    +872, 0x2c3c3ec64b35afc4
    +873, 0x393a274ff03e6935
    +874, 0x1f40ecbac52c50ea
    +875, 0xc3de64fa324ffc0c
    +876, 0x56ae828b7f9deb04
    +877, 0xe7c1a77b5c1f2cb3
    +878, 0xa4c4aab19ea921cc
    +879, 0xec164c238825822c
    +880, 0xa6a3304770c03b03
    +881, 0x3a63641d5b1e8123
    +882, 0x42677be3a54617ef
    +883, 0xa2680423e3a200c0
    +884, 0x8b17cf75f3f37277
    +885, 0xe7ce65a49242be3d
    +886, 0x7f85934271323e4b
    +887, 0xcfb0f431f79a4fab
    +888, 0x392e4041a8505b65
    +889, 0xd3e5daf0d8b25ea6
    +890, 0x9447eff675d80f53
    +891, 0xea27a9d53cfaeea8
    +892, 0xe3f2335945a83ba
    +893, 0x8875a43ce216413b
    +894, 0xe49941f9eabce33e
    +895, 0x9357c1296683a5b1
    +896, 0xf0f16439e81ee701
    +897, 0x3181515295ffd79a
    +898, 0x9d7150fffd169ed8
    +899, 0x2d6a1d281e255a72
    +900, 0x81bf1286fb3a92b6
    +901, 0x566d3079b499e279
    +902, 0xc7939ca8f047341
    +903, 0xb1f8050e7c2d59f6
    +904, 0x605701045e7be192
    +905, 0x51b73360e8e31a1c
    +906, 0x9f4ad54483ba9fe0
    +907, 0xd3085b8fcf69d1c8
    +908, 0xc3e7475026dc5f0b
    +909, 0x5800f8554b157354
    +910, 0x37dfdf858cfcd963
    +911, 0x3a1fce05ce385072
    +912, 0xf495c062645c20c3
    +913, 0xdcbeec2c3492c773
    +914, 0xc38f427589d1d0b4
    +915, 0x681ead60216a8184
    +916, 0x4bd569c40cc88c41
    +917, 0x49b0d442e130b7a2
    +918, 0xee349156b7d1fa3f
    +919, 0x2bde2d2db055135b
    +920, 0xc6a460d2fbcb2378
    +921, 0xd0f170494ff3dbb
    +922, 0xb294422492528a23
    +923, 0xfc95873c854e7b86
    +924, 0x6c9c3ad1797bb19c
    +925, 0xe0c06f2aab65062d
    +926, 0x58e32ce0f11e3a81
    +927, 0xa745fcd729ff5036
    +928, 0x599b249b2fc2cdb2
    +929, 0x78f23b5b0dd5b082
    +930, 0x6de3e957f549ecfc
    +931, 0x9d0712fa6d878756
    +932, 0x9076e8554e4a413a
    +933, 0xf3185818c0294de8
    +934, 0x5de7cdf4b455b9b6
    +935, 0xb15f6908ed703f7d
    +936, 0x98c654dfedc6818
    +937, 0x120502ab0e93ae42
    +938, 0x67966a98a58dc120
    +939, 0x1caa0fc628989482
    +940, 0xd8b2c3cd480a8625
    +941, 0x85c70071b3aed671
    +942, 0xff385f8473714662
    +943, 0xe2868e4bf3773b63
    +944, 0x96cf8019b279298e
    +945, 0x8511cc930bd74800
    +946, 0x5312e48fdd55f5ab
    +947, 0xfcdae564b52df78d
    +948, 0x9eee48373e652176
    +949, 0x953788f6bcbc56b0
    +950, 0xd1a3855dbd2f6b37
    +951, 0x3ad32acf77f4d1e9
    +952, 0x917c7be81b003e30
    +953, 0x9ce817da1e2e9dfb
    +954, 0x2968983db162d44d
    +955, 0x1e005decef5828ad
    +956, 0xc38fe59d1aa4f3d5
    +957, 0xf357f1710dc02f1d
    +958, 0x2613912a4c83ec67
    +959, 0x832a11470b9a17cb
    +960, 0x5e85508a611f0dad
    +961, 0x2781131677f59d56
    +962, 0xa82358d7d4b0237f
    +963, 0xfbf8b3cc030c3af6
    +964, 0x68b2f68ac8a55adb
    +965, 0x3b6fcf353add0ada
    +966, 0xd1956049bcd15bd5
    +967, 0x95b76f31c7f98b6d
    +968, 0x814b6690df971a84
    +969, 0xdcf7959cddd819e4
    +970, 0xcf8c72c5d804fc88
    +971, 0x56883769c8945a22
    +972, 0x1f034652f658cf46
    +973, 0x41df1324cda235a1
    +974, 0xeccd32524504a054
    +975, 0x974e0910a04ec02c
    +976, 0x72104507b821f6db
    +977, 0x791f8d089f273044
    +978, 0xe0f79a4f567f73c3
    +979, 0x52fe5bea3997f024
    +980, 0x5f8b9b446494f78
    +981, 0xfd9f511947059190
    +982, 0x3aea9dac6063bce3
    +983, 0xbfdae4dfc24aee60
    +984, 0xa82cdbbf0a280318
    +985, 0xf460aae18d70aa9d
    +986, 0x997367cb204a57c4
    +987, 0x616e21ab95ba05ef
    +988, 0x9bfc93bec116769f
    +989, 0x2b2ee27c37a3fa5b
    +990, 0xb25c6ed54006ee38
    +991, 0xab04d4a5c69e69a5
    +992, 0x6d2f6b45f2d8438f
    +993, 0x4ad2f32afc82f092
    +994, 0x513d718908f709c0
    +995, 0x5272aadc4fffca51
    +996, 0xeb3f87e66156ef5d
    +997, 0xf8a3d5a46a86ba85
    +998, 0xdb4548a86f27abfd
    +999, 0x57c05f47ff62380d
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/data/sfc64-testset-1.csv b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/data/sfc64-testset-1.csv
    new file mode 100644
    index 0000000000000000000000000000000000000000..4fffe69591fea3f399f35034057096089d4017fb
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/data/sfc64-testset-1.csv
    @@ -0,0 +1,1001 @@
    +seed, 0xdeadbeaf
    +0, 0xa475f55fbb6bc638
    +1, 0xb2d594b6c29d971c
    +2, 0x275bc4ece4484fb1
    +3, 0x569be72d9b3492fb
    +4, 0x89a5bb9b206a670c
    +5, 0xd951bfa06afdc3f9
    +6, 0x7ee2e1029d52a265
    +7, 0x12ef1d4de0cb4d4c
    +8, 0x41658ba8f0ef0280
    +9, 0x5b650c82e4fe09c5
    +10, 0x638a9f3e30ec4e94
    +11, 0x147487fb2ba9233e
    +12, 0x89ef035603d2d1fb
    +13, 0xe66ca57a190e6cbe
    +14, 0x330f673740dd61fc
    +15, 0xc71d3dce2f8bb34e
    +16, 0x3c07c39ff150b185
    +17, 0x5df952b6cae8f099
    +18, 0x9f09f2b1f0ceac80
    +19, 0x19598eee2d0c4c67
    +20, 0x64e06483702e0ebd
    +21, 0xda04d1fdb545f7fa
    +22, 0xf2cf53b61a0c4f9b
    +23, 0xf0bb724ce196f66e
    +24, 0x71cefde55d9cf0f
    +25, 0x6323f62824a20048
    +26, 0x1e93604680f14b4e
    +27, 0xd9d8fad1d4654025
    +28, 0xf4ee25af2e76ca08
    +29, 0x6af3325896befa98
    +30, 0xad9e43abf5e04053
    +31, 0xbf930e318ce09de3
    +32, 0x61f9583b4f9ffe76
    +33, 0x9b69d0b3d5ec8958
    +34, 0xa608f250f9b2ca41
    +35, 0x6fdba7073dc2bb5d
    +36, 0xa9d57601efea6d26
    +37, 0xc24a88a994954105
    +38, 0xc728b1f78d88fe5b
    +39, 0x88da88c2b083b3b2
    +40, 0xa9e27f7303c76cfd
    +41, 0xc4c24608c29176eb
    +42, 0x5420b58466b972fd
    +43, 0xd2018a661b6756c8
    +44, 0x7caed83d9573fc7
    +45, 0x562a3d81b849a06a
    +46, 0x16588af120c21f2c
    +47, 0x658109a7e0eb4837
    +48, 0x877aabb14d3822e1
    +49, 0x95704c342c3745fe
    +50, 0xeeb8a0dc81603616
    +51, 0x431bf94889290419
    +52, 0xe4a9410ab92a5863
    +53, 0xbc6be64ea60f12ba
    +54, 0x328a2da920015063
    +55, 0x40f6b3bf8271ae07
    +56, 0x4068ff00a0e854f8
    +57, 0x1b287572ca13fa78
    +58, 0xa11624a600490b99
    +59, 0x4a04ef29eb7150fa
    +60, 0xcc9469ab5ffb739
    +61, 0x99a6a9f8d95e782
    +62, 0x8e90356573e7a070
    +63, 0xa740b8fb415c81c4
    +64, 0x47eccef67447f3da
    +65, 0x2c720afe3a62a49b
    +66, 0xe2a747f0a43eacf4
    +67, 0xba063a87ab165576
    +68, 0xbc1c78ed27feb5a3
    +69, 0x285a19fa3974f9d
    +70, 0x489c61e704f5f0e3
    +71, 0xf5ab04f6b03f238b
    +72, 0x7e25f88138a110dd
    +73, 0xc3d1cef3d7c1f1d1
    +74, 0xc3de6ec64d0d8e00
    +75, 0x73682a15b6cc5088
    +76, 0x6fecbeb319163dc5
    +77, 0x7e100d5defe570a1
    +78, 0xad2af9af076dce57
    +79, 0x3c65100e23cd3a9a
    +80, 0x4b442cc6cfe521bb
    +81, 0xe89dc50f8ab1ef75
    +82, 0x8b3c6fdc2496566
    +83, 0xdfc50042bc2c308c
    +84, 0xe39c5f158b33d2b2
    +85, 0x92f6adefdfeb0ac
    +86, 0xdf5808a949c85b3e
    +87, 0x437384021c9dace9
    +88, 0xa7b5ed0d3d67d8f
    +89, 0xe1408f8b21da3c34
    +90, 0xa1bba125c1e80522
    +91, 0x7611dc4710385264
    +92, 0xb00a46ea84082917
    +93, 0x51bf8002ffa87cef
    +94, 0x9bb81013e9810adc
    +95, 0xd28f6600013541cd
    +96, 0xc2ca3b1fa7791c1f
    +97, 0x47f9ad58f099c82c
    +98, 0x4d1bb9458469caf9
    +99, 0xca0b165b2844257
    +100, 0xc3b2e667d075dc66
    +101, 0xde22f71136a3dbb1
    +102, 0x23b4e3b6f219e4c3
    +103, 0x327e0db4c9782f66
    +104, 0x9365506a6c7a1807
    +105, 0x3e868382dedd3be7
    +106, 0xff04fa6534bcaa99
    +107, 0x96621a8862995305
    +108, 0x81bf39cb5f8e1df7
    +109, 0x79b684bb8c37af7a
    +110, 0xae3bc073c3cde33c
    +111, 0x7805674112c899ac
    +112, 0xd95a27995abb20f2
    +113, 0x71a503c57b105c40
    +114, 0x5ff00d6a73ec8acc
    +115, 0x12f96391d91e47c2
    +116, 0xd55ca097b3bd4947
    +117, 0x794d79d20468b04
    +118, 0x35d814efb0d7a07d
    +119, 0xfa9ac9bd0aae76d3
    +120, 0xa77b8a3711e175cd
    +121, 0xe6694fbf421f9489
    +122, 0xd8f1756525a1a0aa
    +123, 0xe38dfa8426277433
    +124, 0x16b640c269bbcd44
    +125, 0x2a7a5a67ca24cfeb
    +126, 0x669039c28d5344b4
    +127, 0x2a445ee81fd596bb
    +128, 0x600df94cf25607e0
    +129, 0x9358561a7579abff
    +130, 0xee1d52ea179fc274
    +131, 0x21a8b325e89d31be
    +132, 0x36fc0917486eec0a
    +133, 0x3d99f40717a6be9f
    +134, 0x39ac140051ca55ff
    +135, 0xcef7447c26711575
    +136, 0xf22666870eff441d
    +137, 0x4a53c6134e1c7268
    +138, 0xd26de518ad6bdb1b
    +139, 0x1a736bf75b8b0e55
    +140, 0xef1523f4e6bd0219
    +141, 0xb287b32fd615ad92
    +142, 0x2583d6af5e841dd5
    +143, 0x4b9294aae7ca670c
    +144, 0xf5aa4a84174f3ca9
    +145, 0x886300f9e0dc6376
    +146, 0x3611401e475ef130
    +147, 0x69b56432b367e1ac
    +148, 0x30c330e9ab36b7c4
    +149, 0x1e0e73079a85b8d5
    +150, 0x40fdfc7a5bfaecf
    +151, 0xd7760f3e8e75a085
    +152, 0x1cc1891f7f625313
    +153, 0xeece1fe6165b4272
    +154, 0xe61111b0c166a3c1
    +155, 0x2f1201563312f185
    +156, 0xfd10e8ecdd2a57cb
    +157, 0x51cdc8c9dd3a89bf
    +158, 0xed13cc93938b5496
    +159, 0x843816129750526b
    +160, 0xd09995cd6819ada
    +161, 0x4601e778d40607df
    +162, 0xef9df06bd66c2ea0
    +163, 0xae0bdecd3db65d69
    +164, 0xbb921a3c65a4ae9a
    +165, 0xd66698ce8e9361be
    +166, 0xacdc91647b6068f4
    +167, 0xe505ef68f2a5c1c0
    +168, 0xd6e62fd27c6ab137
    +169, 0x6a2ba2c6a4641d86
    +170, 0x9c89143715c3b81
    +171, 0xe408c4e00362601a
    +172, 0x986155cbf5d4bd9d
    +173, 0xb9e6831728c893a7
    +174, 0xb985497c3bf88d8c
    +175, 0xd0d729214b727bec
    +176, 0x4e557f75fece38a
    +177, 0x6572067fdfd623ca
    +178, 0x178d49bb4d5cd794
    +179, 0xe6baf59f60445d82
    +180, 0x5607d53518e3a8d2
    +181, 0xba7931adb6ebbd61
    +182, 0xe853576172611329
    +183, 0xe945daff96000c44
    +184, 0x565b9ba3d952a176
    +185, 0xcdb54d4f88c584c8
    +186, 0x482a7499bee9b5e5
    +187, 0x76560dd0affe825b
    +188, 0x2a56221faa5ca22c
    +189, 0x7729be5b361f5a25
    +190, 0xd6f2195795764876
    +191, 0x59ef7f8f423f18c5
    +192, 0x7ebefed6d02adde1
    +193, 0xcfec7265329c73e5
    +194, 0x4fd8606a5e59881c
    +195, 0x95860982ae370b73
    +196, 0xdecfa33b1f902acc
    +197, 0xf9b8a57400b7c0a6
    +198, 0xd20b822672ec857b
    +199, 0x4eb81084096c7364
    +200, 0xe535c29a44d9b6ad
    +201, 0xdef8b48ebacb2e29
    +202, 0x1063bc2b8ba0e915
    +203, 0xe4e837fb53d76d02
    +204, 0x4df935db53579fb8
    +205, 0xa30a0c8053869a89
    +206, 0xe891ee58a388a7b5
    +207, 0x17931a0c64b8a985
    +208, 0xaf2d350b494ce1b3
    +209, 0x2ab9345ffbcfed82
    +210, 0x7de3fe628a2592f0
    +211, 0x85cf54fab8b7e79d
    +212, 0x42d221520edab71b
    +213, 0x17b695b3af36c233
    +214, 0xa4ffe50fe53eb485
    +215, 0x1102d242db800e4d
    +216, 0xc8dc01f0233b3b6
    +217, 0x984a030321053d36
    +218, 0x27fa8dc7b7112c0e
    +219, 0xba634dd8294e177f
    +220, 0xe67ce34b36332eb
    +221, 0x8f1351e1894fb41a
    +222, 0xb522a3048761fd30
    +223, 0xc350ad9bc6729edc
    +224, 0xe0ed105bd3c805e1
    +225, 0xa14043d2b0825aa7
    +226, 0xee7779ce7fc11fdf
    +227, 0xc0fa8ba23a60ab25
    +228, 0xb596d1ce259afbad
    +229, 0xaa9b8445537fdf62
    +230, 0x770ab2c700762e13
    +231, 0xe812f1183e40cc1
    +232, 0x44bc898e57aefbbd
    +233, 0xdd8a871df785c996
    +234, 0x88836a5e371eb36b
    +235, 0xb6081c9152623f27
    +236, 0x895acbcd6528ca96
    +237, 0xfb67e33ddfbed435
    +238, 0xaf7af47d323ce26
    +239, 0xe354a510c3c39b2d
    +240, 0x5cacdedda0672ba3
    +241, 0xa440d9a2c6c22b09
    +242, 0x6395099f48d64304
    +243, 0xc11cf04c75f655b5
    +244, 0x1c4e054d144ddb30
    +245, 0x3e0c2db89d336636
    +246, 0x127ecf18a5b0b9a7
    +247, 0x3b50551a88ea7a73
    +248, 0xbd27003e47f1f684
    +249, 0xf32d657782baac9b
    +250, 0x727f5cabf020bc9
    +251, 0x39c1c1c226197dc7
    +252, 0x5552c87b35deeb69
    +253, 0x64d54067b5ce493f
    +254, 0x3494b091fe28dda0
    +255, 0xdf0278bc85ee2965
    +256, 0xdef16fec25efbd66
    +257, 0xe2be09f578c4ce28
    +258, 0xd27a9271979d3019
    +259, 0x427f6fcd71845e3
    +260, 0x26b52c5f81ec142b
    +261, 0x98267efc3986ad46
    +262, 0x7bf4165ddb7e4374
    +263, 0xd05f7996d7941010
    +264, 0x3b3991de97b45f14
    +265, 0x9068217fb4f27a30
    +266, 0xd8fe295160afc7f3
    +267, 0x8a159fab4c3bc06f
    +268, 0x57855506d19080b6
    +269, 0x7636df6b3f2367a4
    +270, 0x2844ee3abd1d5ec9
    +271, 0xe5788de061f51c16
    +272, 0x69e78cc9132a164
    +273, 0xacd53cde6d8cd421
    +274, 0xb23f3100068e91da
    +275, 0x4140070a47f53891
    +276, 0xe4a422225a96e53a
    +277, 0xb82a8925a272a2ac
    +278, 0x7c2f9573590fe3b7
    +279, 0xbaf80764db170575
    +280, 0x955abffa54358368
    +281, 0x355ce7460614a869
    +282, 0x3700ede779a4afbf
    +283, 0x10a6ec01d92d68cd
    +284, 0x3308f5a0a4c0afef
    +285, 0x97b892d7601136c9
    +286, 0x4955c3b941b8552e
    +287, 0xca85aa67e941961d
    +288, 0xb1859ae5db28e9d2
    +289, 0x305d072ac1521fbd
    +290, 0xed52a868996085bb
    +291, 0x723bfa6a76358852
    +292, 0x78d946ecd97c5fb3
    +293, 0x39205b30a8e23e79
    +294, 0xb927e3d086baadbe
    +295, 0xa18d6946136e1ff5
    +296, 0xdab6f0b51c1eb5ff
    +297, 0xf0a640bf7a1af60c
    +298, 0xf0e81db09004d0d4
    +299, 0xfe76cebdbe5a4dde
    +300, 0x2dafe9cc3decc376
    +301, 0x4c871fdf1af34205
    +302, 0xe79617d0c8fa893b
    +303, 0xee658aaad3a141f7
    +304, 0xfd91aa74863e19f1
    +305, 0x841b8f55c103cc22
    +306, 0x22766ed65444ad5d
    +307, 0x56d03d1beca6c17a
    +308, 0x5fd4c112c92036ae
    +309, 0x75466ae58a5616dc
    +310, 0xfbf98b1081e802a9
    +311, 0xdc325e957bf6d8f5
    +312, 0xb08da7015ebd19b7
    +313, 0xf25a9c0944f0c073
    +314, 0xf4625bafa0ced718
    +315, 0x4349c9e093a9e692
    +316, 0x75a9ccd4dd8935cb
    +317, 0x7e6cf9e539361e91
    +318, 0x20fdd22fb6edd475
    +319, 0x5973021b57c2311f
    +320, 0x75392403667edc15
    +321, 0xed9b2156ea70d9f1
    +322, 0xf40c114db50b64a0
    +323, 0xe26bb2c9eef20c62
    +324, 0x409c1e3037869f03
    +325, 0xcdfd71fdda3b7f91
    +326, 0xa0dfae46816777d6
    +327, 0xde060a8f61a8deb8
    +328, 0x890e082a8b0ca4fc
    +329, 0xb9f2958eddf2d0db
    +330, 0xd17c148020d20e30
    +331, 0xffdc9cc176fe7201
    +332, 0xffb83d925b764c1
    +333, 0x817ea639e313da8d
    +334, 0xa4dd335dd891ca91
    +335, 0x1342d25a5e81f488
    +336, 0xfa7eb9c3cf466b03
    +337, 0xfe0a423d44b185d0
    +338, 0x101cfd430ab96049
    +339, 0x7b5d3eda9c4504b
    +340, 0xe20ccc006e0193f1
    +341, 0xf54ccddedebc5df0
    +342, 0xc0edd142bd58f1db
    +343, 0x3831f40d378d2430
    +344, 0x80132353f0a88289
    +345, 0x688f23c419d03ef8
    +346, 0x4c6837e697884066
    +347, 0x699387bb2e9a3a8f
    +348, 0x8996f860342448d8
    +349, 0xb0f80dff99bfa5cc
    +350, 0x3e927a7f9ea12c8e
    +351, 0xd7e498d1e5f9dff3
    +352, 0x78ecb97bb3f864cc
    +353, 0x3c4ffd069a014d38
    +354, 0xf8d5073a1e09b4d4
    +355, 0x8717e854f9faef23
    +356, 0xfbcc5478d8d0ad7
    +357, 0xd3cd8b233ca274ff
    +358, 0x8bd8f11f79beb265
    +359, 0xf64498a832d8fd0e
    +360, 0xb01bba75112131ec
    +361, 0x55572445a7869781
    +362, 0x7b56622f18cb3d7a
    +363, 0x7f192c9e075bdb83
    +364, 0xd9a112f836b83ff3
    +365, 0x68673b37269653dc
    +366, 0xe46a9433fb6a0879
    +367, 0x127d756ca4779001
    +368, 0xc1378e8b1e8eab94
    +369, 0x1006edb0f51d078c
    +370, 0xc6dd53961232d926
    +371, 0x9a4aeef44038256d
    +372, 0xd357f4fa652d4f5f
    +373, 0x59f3d2cc3378598
    +374, 0xe76e6207a824a7fc
    +375, 0x5fc5e33712ceffef
    +376, 0x77d24aeb0ccb1adc
    +377, 0x5be4b2826805659e
    +378, 0x257c69d787e64634
    +379, 0x58dd52ca6bc727b1
    +380, 0x3ab997767235ea33
    +381, 0x986a2a7a966fad14
    +382, 0xc900f8b27761dcc4
    +383, 0x44991bdb13795700
    +384, 0xe5c145a4fe733b2
    +385, 0x56f041b56bffe0d3
    +386, 0x5779c4fef8067996
    +387, 0xa0fe8748e829532d
    +388, 0x840c1277d78d9dd4
    +389, 0x37ebcb315432acbc
    +390, 0xf4bc8738433ba3be
    +391, 0x8b122993f2e10062
    +392, 0xe1fe8481f2681ed5
    +393, 0x8e23f1630d9f494a
    +394, 0xda24661a01b7d0b3
    +395, 0x7a02942a179cee36
    +396, 0xf1e08a3c09b71ac
    +397, 0x3dec2cc7ee0bd8fd
    +398, 0x1f3e480113d805d4
    +399, 0xc061b973ad4e3f2c
    +400, 0x6bea750f17a66836
    +401, 0xbc2add72eac84c25
    +402, 0xcff058d3f97934ca
    +403, 0x54ccc30987778ec2
    +404, 0x93449ec1e1469558
    +405, 0xe2ff369eb0c6836
    +406, 0x41c2df2d63bf8e55
    +407, 0xf9302629b6c71be2
    +408, 0xdd30376b8e5ab29a
    +409, 0x12db9e04f911d754
    +410, 0x8d03d6cd359f1b97
    +411, 0xe15956511abf1cee
    +412, 0x9b68e10e2c2fd940
    +413, 0x2e28de6491c1ce53
    +414, 0x52b329b72d0c109d
    +415, 0xc2c0b115f9da2a60
    +416, 0x6ca084105271bbff
    +417, 0x49b92b8676058c1e
    +418, 0x767fc92a70f7e5a3
    +419, 0x87ba4ed4b65a6aa0
    +420, 0xf70b052e0a3975e9
    +421, 0x3e925c3306db9eec
    +422, 0x43253f1d96ac9513
    +423, 0xe3e04f1a1ea454c4
    +424, 0x763e3f4cc81ba0c8
    +425, 0x2a2721ac69265705
    +426, 0xdf3b0ac6416ea214
    +427, 0xa6a6b57450f3e000
    +428, 0xc3d3b1ac7dbfe6ac
    +429, 0xb66e5e6f7d2e4ec0
    +430, 0x43c65296f98f0f04
    +431, 0xdb0f6e3ff974d842
    +432, 0x3d6b48e02ebb203b
    +433, 0xd74674ebf09d8f27
    +434, 0xbe65243c58fc1200
    +435, 0x55eb210a68d42625
    +436, 0x87badab097dbe883
    +437, 0xada3fda85a53824f
    +438, 0xef2791e8f48cd37a
    +439, 0x3fe7fceb927a641a
    +440, 0xd3bffd3ff031ac78
    +441, 0xb94efe03da4d18fb
    +442, 0x162a0ad8da65ea68
    +443, 0x300f234ef5b7e4a6
    +444, 0xa2a8b4c77024e4fb
    +445, 0x5950f095ddd7b109
    +446, 0xded66dd2b1bb02ba
    +447, 0x8ec24b7fa509bcb6
    +448, 0x9bede53d924bdad6
    +449, 0xa9c3f46423be1930
    +450, 0x6dfc90597f8de8b4
    +451, 0xb7419ebc65b434f0
    +452, 0xa6596949238f58b9
    +453, 0x966cbade640829b8
    +454, 0x58c74877bdcbf65e
    +455, 0xaa103b8f89b0c453
    +456, 0x219f0a86e41179a4
    +457, 0x90f534fc06ddc57f
    +458, 0x8db7cdd644f1affa
    +459, 0x38f91de0167127ac
    +460, 0xdcd2a65e4df43daa
    +461, 0x3e04f34a7e01f834
    +462, 0x5b237eea68007768
    +463, 0x7ff4d2b015921768
    +464, 0xf786b286549d3d51
    +465, 0xaefa053fc2c3884c
    +466, 0x8e6a8ff381515d36
    +467, 0x35b94f3d0a1fce3c
    +468, 0x165266d19e9abb64
    +469, 0x1deb5caa5f9d8076
    +470, 0x13ab91290c7cfe9d
    +471, 0x3651ca9856be3e05
    +472, 0xe7b705f6e9cccc19
    +473, 0xd6e7f79668c127ed
    +474, 0xa9faf37154896f92
    +475, 0x89fbf190603e0ab1
    +476, 0xb34d155a86f942d0
    +477, 0xb2d4400a78bfdd76
    +478, 0x7c0946aca8cfb3f0
    +479, 0x7492771591c9d0e8
    +480, 0xd084d95c5ca2eb28
    +481, 0xb18d12bd3a6023e
    +482, 0xea217ed7b864d80b
    +483, 0xe52f69a755dd5c6f
    +484, 0x127133993d81c4aa
    +485, 0xe07188fcf1670bfb
    +486, 0x178fbfe668e4661d
    +487, 0x1c9ee14bb0cda154
    +488, 0x8d043b96b6668f98
    +489, 0xbc858986ec96ca2b
    +490, 0x7660f779d528b6b7
    +491, 0xd448c6a1f74ae1d3
    +492, 0x178e122cfc2a6862
    +493, 0x236f000abaf2d23b
    +494, 0x171b27f3f0921915
    +495, 0x4c3ff07652f50a70
    +496, 0x18663e5e7d3a66ca
    +497, 0xb38c97946c750cc9
    +498, 0xc5031aae6f78f909
    +499, 0x4d1514e2925e95c1
    +500, 0x4c2184a741dabfbb
    +501, 0xfd410364edf77182
    +502, 0xc228157f863ee873
    +503, 0x9856fdc735cc09fc
    +504, 0x660496cd1e41d60e
    +505, 0x2edf1d7e01954c32
    +506, 0xd32e94639bdd98cf
    +507, 0x8e153f48709a77d
    +508, 0x89357f332d2d6561
    +509, 0x1840d512c97085e6
    +510, 0x2f18d035c9e26a85
    +511, 0x77b88b1448b26d5b
    +512, 0xc1ca6ef4cdae0799
    +513, 0xcc203f9e4508165f
    +514, 0xeaf762fbc9e0cbbe
    +515, 0xc070c687f3c4a290
    +516, 0xd49ed321068d5c15
    +517, 0x84a55eec17ee64ee
    +518, 0x4d8ee685298a8871
    +519, 0x9ff5f17d7e029793
    +520, 0x791d7d0d62e46302
    +521, 0xab218b9114e22bc6
    +522, 0x4902b7ab3f7119a7
    +523, 0x694930f2e29b049e
    +524, 0x1a3c90650848999f
    +525, 0x79f1b9d8499c932b
    +526, 0xfacb6d3d55e3c92f
    +527, 0x8fd8b4f25a5da9f5
    +528, 0xd037dcc3a7e62ae7
    +529, 0xfecf57300d8f84f4
    +530, 0x32079b1e1dc12d48
    +531, 0xe5f8f1e62b288f54
    +532, 0x97feba3a9c108894
    +533, 0xd279a51e1899a9a0
    +534, 0xd68eea8e8e363fa8
    +535, 0x7394cf2deeca9386
    +536, 0x5f70b0c80f1dbf10
    +537, 0x8d646916ed40462
    +538, 0xd253bb1c8a12bbb6
    +539, 0x38f399a821fbd73e
    +540, 0x947523a26333ac90
    +541, 0xb52e90affbc52a37
    +542, 0xcf899cd964654da4
    +543, 0xdf66ae9cca8d99e7
    +544, 0x6051478e57c21b6a
    +545, 0xffa7dc975af3c1da
    +546, 0x195c7bff2d1a8f5
    +547, 0x64f12b6575cf984d
    +548, 0x536034cb842cf9e1
    +549, 0x180f247ce5bbfad
    +550, 0x8ced45081b134867
    +551, 0x532bbfdf426710f3
    +552, 0x4747933e74c4f54d
    +553, 0x197a890dc4793401
    +554, 0x76c7cc2bd42fae2
    +555, 0xdabfd67f69675dd0
    +556, 0x85c690a68cdb3197
    +557, 0xe482cec89ce8f92
    +558, 0x20bc9fb7797011b1
    +559, 0x76dc85a2185782ad
    +560, 0x3df37c164422117a
    +561, 0x99211f5d231e0ab0
    +562, 0xef7fd794a0a91f4
    +563, 0x419577151915f5fe
    +564, 0x3ce14a0a7135dae3
    +565, 0x389b57598a075d6a
    +566, 0x8cc2a9d51b5af9aa
    +567, 0xe80a9beffbd13f13
    +568, 0x65e96b22ea8a54d8
    +569, 0x79f38c4164138ede
    +570, 0xd1955846cba03d81
    +571, 0x60359fe58e4f26d6
    +572, 0x4ea724f585f8d13e
    +573, 0x316dfdbadc801a3c
    +574, 0x20aa29b7c6dd66fe
    +575, 0x65eaf83a6a008caa
    +576, 0x407000aff1b9e8cb
    +577, 0xb4d49bfb2b268c40
    +578, 0xd4e6fe8a7a0f14a9
    +579, 0xe34afef924e8f58e
    +580, 0xe377b0c891844824
    +581, 0x29c2e20c112d30c8
    +582, 0x906aad1fe0c18a95
    +583, 0x308385f0efbb6474
    +584, 0xf23900481bf70445
    +585, 0xfdfe3ade7f937a55
    +586, 0xf37aae71c33c4f97
    +587, 0x1c81e3775a8bed85
    +588, 0x7eb5013882ce35ea
    +589, 0x37a1c1692495818d
    +590, 0x3f90ae118622a0ba
    +591, 0x58e4fe6fea29b037
    +592, 0xd10ff1d269808825
    +593, 0xbce30edb60c21bba
    +594, 0x123732329afd6fee
    +595, 0x429b4059f797d840
    +596, 0x421166568a8c4be1
    +597, 0x88f895c424c1bd7f
    +598, 0x2adaf7a7b9f781cb
    +599, 0xa425644b26cb698
    +600, 0x8cc44d2486cc5743
    +601, 0xdb9f357a33abf6ba
    +602, 0x1a57c4ea77a4d70c
    +603, 0x1dea29be75239e44
    +604, 0x463141a137121a06
    +605, 0x8fecfbbe0b8a9517
    +606, 0x92c83984b3566123
    +607, 0x3b1c69180ed28665
    +608, 0x14a6073425ea8717
    +609, 0x71f4c2b3283238d7
    +610, 0xb3d491e3152f19f
    +611, 0x3a0ba3a11ebac5d2
    +612, 0xddb4d1dd4c0f54ac
    +613, 0xdb8f36fe02414035
    +614, 0x1cf5df5031b1902c
    +615, 0x23a20ed12ef95870
    +616, 0xf113e573b2dedcbb
    +617, 0x308e2395cde0a9fa
    +618, 0xd377a22581c3a7da
    +619, 0xe0ced97a947a66fb
    +620, 0xe44f4de9cd754b00
    +621, 0x2344943337d9d1bf
    +622, 0x4b5ae5e2ea6e749c
    +623, 0x9b8d2e3ef41d1c01
    +624, 0x59a5a53ebbd24c6b
    +625, 0x4f7611bf9e8a06fb
    +626, 0xea38c7b61361cd06
    +627, 0xf125a2bfdd2c0c7
    +628, 0x2df8dcb5926b9ebb
    +629, 0x233e18720cc56988
    +630, 0x974c61379b4aa95e
    +631, 0xc7fe24c1c868910b
    +632, 0x818fd1affc82a842
    +633, 0xcee92a952a26d38e
    +634, 0x8962f575ebcbf43
    +635, 0x7770687e3678c460
    +636, 0xdfb1db4ed1298117
    +637, 0xb9db54cb03d434d3
    +638, 0x34aebbf2244257ad
    +639, 0xd836db0cb210c490
    +640, 0x935daed7138957cd
    +641, 0x3cd914b14e7948fd
    +642, 0xd0472e9ed0a0f7f0
    +643, 0xa9df33dca697f75e
    +644, 0x15e9ea259398721a
    +645, 0x23eeba0f970abd60
    +646, 0x2217fdf8bbe99a12
    +647, 0x5ea490a95717b198
    +648, 0xf4e2bfc28280b639
    +649, 0x9d19916072d6f05c
    +650, 0x5e0387cab1734c6a
    +651, 0x93c2c8ac26e5f01e
    +652, 0xb0d934354d957eb1
    +653, 0xee5099a1eef3188c
    +654, 0x8be0abca8edc1115
    +655, 0x989a60845dbf5aa3
    +656, 0x181c7ed964eee892
    +657, 0x49838ea07481288d
    +658, 0x17dbc75d66116b2e
    +659, 0xa4cafb7a87c0117e
    +660, 0xab2d0ae44cdc2e6e
    +661, 0xdf802f2457e7da6
    +662, 0x4b966c4b9187e124
    +663, 0x62de9db6f4811e1a
    +664, 0x1e20485968bc62
    +665, 0xe9ac288265caca94
    +666, 0xc5c694d349aa8c1a
    +667, 0x3d67f2083d9bdf10
    +668, 0x9a2468e503085486
    +669, 0x9d6acd3dc152d1a3
    +670, 0xca951e2aeee8df77
    +671, 0x2707371af9cdd7b0
    +672, 0x2347ae6a4eb5ecbd
    +673, 0x16abe5582cb426f
    +674, 0x523af4ff980bbccb
    +675, 0xb07a0f043e3694aa
    +676, 0x14d7c3da81b2de7
    +677, 0xf471f1b8ac22305b
    +678, 0xdb087ffff9e18520
    +679, 0x1a352db3574359e8
    +680, 0x48d5431502cc7476
    +681, 0x7c9b7e7003dfd1bf
    +682, 0x4f43a48aae987169
    +683, 0x9a5d3eb66dedb3e9
    +684, 0xa7b331af76a9f817
    +685, 0xba440154b118ab2d
    +686, 0x64d22344ce24c9c6
    +687, 0xa22377bd52bd043
    +688, 0x9dfa1bb18ca6c5f7
    +689, 0xdccf44a92f644c8b
    +690, 0xf623d0a49fd18145
    +691, 0x556d5c37978e28b3
    +692, 0xad96e32ce9d2bb8b
    +693, 0x2e479c120be52798
    +694, 0x7501cf871af7b2f7
    +695, 0xd02536a5d026a5b8
    +696, 0x4b37ff53e76ab5a4
    +697, 0xdb3a4039caaeab13
    +698, 0x6cbd65e3b700c7be
    +699, 0x7367abd98761a147
    +700, 0xf4f9ba216a35aa77
    +701, 0xf88ca25ce921eb86
    +702, 0xb211de082ec2cbf2
    +703, 0xdd94aa46ec57e12e
    +704, 0xa967d74ad8210240
    +705, 0xdaa1fada8cfa887
    +706, 0x85901d081c4488ee
    +707, 0xcf67f79a699ef06
    +708, 0x7f2f1f0de921ee14
    +709, 0x28bc61e9d3f2328b
    +710, 0x3332f2963faf18e5
    +711, 0x4167ac71fcf43a6
    +712, 0x843c1746b0160b74
    +713, 0xd9be80070c578a5e
    +714, 0xbd7250c9af1473e7
    +715, 0x43f78afaa3647899
    +716, 0x91c6b5dd715a75a5
    +717, 0x29cc66c8a07bfef3
    +718, 0x3f5c667311dc22be
    +719, 0x4f49cd47958260cd
    +720, 0xbef8be43d920b64e
    +721, 0x7a892a5f13061d8b
    +722, 0x9532f40125c819b1
    +723, 0x924fca3045f8a564
    +724, 0x9b2c6442453b0c20
    +725, 0x7e21009085b8e793
    +726, 0x9b98c17e17af59d2
    +727, 0xba61acb73e3ae89a
    +728, 0xb9d61a710555c138
    +729, 0xc2a425d80978974b
    +730, 0xa275e13592da7d67
    +731, 0xe962103202d9ad0f
    +732, 0xbdf8367a4d6f33fd
    +733, 0xe59beb2f8648bdc8
    +734, 0xb4c387d8fbc4ac1c
    +735, 0x5e3f276b63054b75
    +736, 0xf27e616aa54d8464
    +737, 0x3f271661d1cd7426
    +738, 0x43a69dbee7502c78
    +739, 0x8066fcea6df059a1
    +740, 0x3c10f19409bdc993
    +741, 0x6ba6f43fb21f23e0
    +742, 0x9e182d70a5bccf09
    +743, 0x1520783d2a63a199
    +744, 0xba1dcc0c70b9cace
    +745, 0x1009e1e9b1032d8
    +746, 0xf632f6a95fb0315
    +747, 0x48e711c7114cbfff
    +748, 0xef281dcec67debf7
    +749, 0x33789894d6abf59b
    +750, 0x6c8e541fffbe7f9c
    +751, 0x85417f13b08e0a88
    +752, 0x9a581e36d589608f
    +753, 0x461dca50b1befd35
    +754, 0x5a3231680dde6462
    +755, 0xcc57acf729780b97
    +756, 0x50301efef62e1054
    +757, 0x675d042cd4f6bbc9
    +758, 0x1652fdd3794384c9
    +759, 0x1c93bbeeb763cd4d
    +760, 0x44b7240c4b105242
    +761, 0x4c6af2a1b606ccfb
    +762, 0x18fc43ece2ec1a40
    +763, 0x859a5511aeae8acb
    +764, 0x2f56826f1996ad2f
    +765, 0xa8e95ce8bb363bdf
    +766, 0xf4da396054e50e4b
    +767, 0x5493865e9895883c
    +768, 0x768e4c8b332ac0e3
    +769, 0x32195d2aa583fca5
    +770, 0xf2f353f21266bc15
    +771, 0x43cddf1d021307d
    +772, 0x6031e3aa30300e4a
    +773, 0x4f1298469ac6088f
    +774, 0x4b4d450bafac574e
    +775, 0x23e1cf9c0582a22b
    +776, 0x2e9036980db49cd0
    +777, 0xe4e228b113c411b2
    +778, 0x8bddcdb82b51706
    +779, 0xd2a7ea8288593629
    +780, 0x67fe90e98fdda61
    +781, 0x7b63494dba95717b
    +782, 0x105625904510d782
    +783, 0xdf4aa2242454e50a
    +784, 0x32541d6cd7d6c7e3
    +785, 0x5661fb432591cf3b
    +786, 0xce920a5ed047bce7
    +787, 0xed4178a3c96eea8f
    +788, 0xe378cd996e39863b
    +789, 0x169e1fdc8e2b05e1
    +790, 0xaee1812ef7149a96
    +791, 0x648571c7453d12c5
    +792, 0xb7b6bc9328573c43
    +793, 0xe7fb969078e270d7
    +794, 0xdfc2b1b8985f6e6f
    +795, 0x862b6527ee39a1aa
    +796, 0x1ee329aea91d7882
    +797, 0x20d25324f2fe704
    +798, 0xbfcc47401fc3bbfd
    +799, 0x1515cdc8d48b2904
    +800, 0xbd6eefe86284261c
    +801, 0x9b1f28e3b35f22ee
    +802, 0x842a29d35e5aecda
    +803, 0xf2346109ad370765
    +804, 0x24d68add5a71afd9
    +805, 0x4a691421613d91e2
    +806, 0x60e3058b3c244051
    +807, 0x79194905cdaa5de8
    +808, 0xe0e2df35c01e8987
    +809, 0xe29b78beffbb5e4a
    +810, 0xcdcdbc020218c19e
    +811, 0x5ae0af8c16feae43
    +812, 0x8109292feeaf14fa
    +813, 0x34113f7508dfa521
    +814, 0xc062ac163f56730a
    +815, 0xf1660e66ec6d4c4c
    +816, 0x5966c55f60151c80
    +817, 0x3865ae8ec934b17
    +818, 0x472a7314afb055ec
    +819, 0x7a24277309a44a44
    +820, 0x556e02dd35d38baa
    +821, 0x9849611a1bc96ec1
    +822, 0xd176f5d5a8eb0843
    +823, 0x44db12ec60510030
    +824, 0x272e3a06a0030078
    +825, 0x7c4764dbefc075ea
    +826, 0x910712f3735c1183
    +827, 0xd49a2da74ae7aff6
    +828, 0xcf9b3e6e8f776d71
    +829, 0x27789fe3ec481a02
    +830, 0x86659f82c6b5912b
    +831, 0xe044b3dbf339158c
    +832, 0x99d81f6bb62a37b0
    +833, 0x5f5830c246fada9a
    +834, 0xe68abab1eeb432cb
    +835, 0x49c5c5ace04e104
    +836, 0x1ac3871b3fc6771b
    +837, 0x773b39f32d070652
    +838, 0x9c4138c2ae58b1f3
    +839, 0xac41c63d7452ac60
    +840, 0x9248826b245359e1
    +841, 0x99bba1c7a64f1670
    +842, 0xe0dc99ff4ebb92f2
    +843, 0x113638652740f87c
    +844, 0xebf51e94da88cfc
    +845, 0x5441c344b81b2585
    +846, 0xe1e69e0bc2de652a
    +847, 0xe9ab6d64ae42ed1e
    +848, 0x879af8730e305f31
    +849, 0x36b9ad912c7e00d6
    +850, 0x83ef5e9fca853886
    +851, 0xda54d48bb20ea974
    +852, 0x32c6d93aefa92aa2
    +853, 0x4e887b2c3391847d
    +854, 0x50966e815f42b1b8
    +855, 0x53411ac087832837
    +856, 0x46f64fef79df4f29
    +857, 0xb34aae3924cd272c
    +858, 0xf5ad455869a0adbe
    +859, 0x8351ded7144edac8
    +860, 0xeb558af089677494
    +861, 0x36ed71d69293a8d6
    +862, 0x659f90bf5431b254
    +863, 0x53349102b7519949
    +864, 0x3db83e20b1713610
    +865, 0x6d63f96090556254
    +866, 0x4cc0467e8f45c645
    +867, 0xb8840c4bd5cd4091
    +868, 0xbd381463cc93d584
    +869, 0x203410d878c2066d
    +870, 0x2ebea06213cf71c8
    +871, 0x598e8fb75e3fceb4
    +872, 0xdcca41ceba0fce02
    +873, 0x61bf69212b56aae5
    +874, 0x97eed7f70c9114fa
    +875, 0xf46f37a8b7a063f9
    +876, 0x66c8f4ffe5bd6efa
    +877, 0xe43fd6efda2d4e32
    +878, 0x12d6c799e5ad01de
    +879, 0x9ac83e7f8b709360
    +880, 0xbbb7bb3c1957513d
    +881, 0x7f87c08d4b3796b0
    +882, 0x9a7d1d74b6aa4a5c
    +883, 0xa4314530ff741b6f
    +884, 0x99a80c6b6f15fca8
    +885, 0xd2fec81d6d5fc3ce
    +886, 0x15a98be1cc40cea
    +887, 0x98693eb7719366f3
    +888, 0x36ccdc2a9e9d4de8
    +889, 0x3c8208f63d77df25
    +890, 0xca2e376e2343df6
    +891, 0xcc9b17cbb54420c6
    +892, 0x8724c44a64d7dcb8
    +893, 0x9d00c6949ff33869
    +894, 0xf4f8e584d2699372
    +895, 0x88f4748cdd5a2d53
    +896, 0xe215072a1205bc6d
    +897, 0x190934fe6d740442
    +898, 0x7fac5c0ab2af106d
    +899, 0x1b86633a0bd84fa1
    +900, 0x1293e54318492dfb
    +901, 0x433324fd390f34b9
    +902, 0x4c5eb2c67a44643b
    +903, 0x59a6e281c388b0dd
    +904, 0xe78e03f9c44623b7
    +905, 0x91307a93c768fc3d
    +906, 0xde8867b004d8e3ff
    +907, 0xdf52c3f57b7c5862
    +908, 0x993f3e1d10358a92
    +909, 0x9ccb10bc3e18662d
    +910, 0x45093ce48a114c73
    +911, 0xd59d05979d26330a
    +912, 0x417c0e03300119a9
    +913, 0x1c336500f90cde81
    +914, 0x1c8ccd29ead9b85b
    +915, 0xb76baf3e55d4d950
    +916, 0x133ad6196c75fd7e
    +917, 0x34200b0cde7ed560
    +918, 0x9c7c3dacb213c8d9
    +919, 0xd97563c4fd9bf1b6
    +920, 0x5d910e871835b6cb
    +921, 0x7d46c4733a16bdf9
    +922, 0xe41d73194ddc87b2
    +923, 0x7d3d8a0855a465a9
    +924, 0x70c2a8b5d3f90c0f
    +925, 0x9e7565ca5dccfe12
    +926, 0x2c0acb4577aa51b1
    +927, 0x3d2cd211145b79c7
    +928, 0x15a7b17aa6da7732
    +929, 0xab44a3730c27d780
    +930, 0xf008bd6c802bde3a
    +931, 0x82ed86ddf3619f77
    +932, 0xaabe982ab15c49f9
    +933, 0x9bcad8fa6d8e58a4
    +934, 0x8f39ed8243718aa1
    +935, 0xe9489340e03e3cb6
    +936, 0xc722314f5eefb8d0
    +937, 0x870e8869a436df59
    +938, 0x4dae75b8087a8204
    +939, 0xe1d790f6ec6e425b
    +940, 0xafd39ea1b1d0ed09
    +941, 0xdf2c99e464ddf08f
    +942, 0x74936d859ab9644d
    +943, 0x3871302164250e73
    +944, 0x764b68921e911886
    +945, 0x2a1d024b26bb9d66
    +946, 0x797fba43918e75b4
    +947, 0x62ec6d24ccca335b
    +948, 0xf4bd8b951762b520
    +949, 0x9d450dede9119397
    +950, 0x5393a26d10f8c124
    +951, 0x6b74769392896b57
    +952, 0x7f61dbcc0e328581
    +953, 0x64e1df3884d0d94
    +954, 0xba77dcdf23738c37
    +955, 0xf8e288bc0a177475
    +956, 0x4a8abfd1702ecb7d
    +957, 0x53f22886694736a7
    +958, 0x8fc982597ced3e3
    +959, 0x1bc46090f820fff7
    +960, 0x8bd31f965d02229f
    +961, 0x65cd0cb29996ee53
    +962, 0x702e0f4fcf8c2e9f
    +963, 0x293b77bff307a9a0
    +964, 0x125a986b8b305788
    +965, 0x416b0eea428ebf3c
    +966, 0xeac85421ab0e8469
    +967, 0x7f5496095019aa68
    +968, 0x1a96d7afbc708e0
    +969, 0xb91262e6766e01e1
    +970, 0xd0a549cc4ccc6954
    +971, 0x75a9a073f50c8a0d
    +972, 0xae275d2c1c6cd23c
    +973, 0xcf159b5ec5d28fd4
    +974, 0x75d0838ce9b92b
    +975, 0xd4eddcee6dc4677f
    +976, 0x6a0a8ad5df6b75b8
    +977, 0x6f3fd0ef0f13ecc4
    +978, 0xb75a5826c1a8f8a8
    +979, 0xd47098bbc7943766
    +980, 0x3d4ddd62d5f23dd1
    +981, 0x760a904e4583841c
    +982, 0x2afeb5022b4cf1f
    +983, 0x66d5f653729f0a13
    +984, 0x9a6a5ab62980d30f
    +985, 0xc332f5643bbf8d5b
    +986, 0x848fb702e4056a90
    +987, 0xa057beaf3f9e8c5f
    +988, 0x6cc603e4560a6c6a
    +989, 0xec761811a7b23211
    +990, 0xb14aa4090a82aaa5
    +991, 0xe29d9d028a5b2dbb
    +992, 0x5564e53738d68f97
    +993, 0xfabca36542eaaf3b
    +994, 0xb9912fcb782020a2
    +995, 0xe865e01b349284fd
    +996, 0x540b5ff11c5f9274
    +997, 0x3463f64e1e7451dc
    +998, 0xe15d3e2f33b735f8
    +999, 0xf5433336eadef6e
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/data/sfc64-testset-2.csv b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/data/sfc64-testset-2.csv
    new file mode 100644
    index 0000000000000000000000000000000000000000..70aebd5d539256e0abd453bb45828b691c6abdf0
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/data/sfc64-testset-2.csv
    @@ -0,0 +1,1001 @@
    +seed, 0x0
    +0, 0x91959e5fb96a6332
    +1, 0x3c1dd8a25a7e9f21
    +2, 0x657bdffc99798d9e
    +3, 0x1a04de320b19e022
    +4, 0x65b92af0e5f3c61c
    +5, 0x9c84070ce8f743c0
    +6, 0xbb10e573693cdb25
    +7, 0xd65ea9e76b37fb6b
    +8, 0x503efd0e76c8ae66
    +9, 0xd711dcd04c26d0f
    +10, 0x12f53f435814ac8c
    +11, 0xb392cd402cfc82bd
    +12, 0x461764550e06c889
    +13, 0x716a48b3514e6979
    +14, 0xdd0a322213c18ad7
    +15, 0x6673a8ca0a05c4d7
    +16, 0x2992ef333437f844
    +17, 0xc4aaf7e8240b2aad
    +18, 0x6ab0a1af1f41474f
    +19, 0xb0bae400c226941d
    +20, 0xe5f80c2eeeab48c6
    +21, 0x3832c6a93a4024bf
    +22, 0x280bd824fabe8368
    +23, 0x66b626228321e5ff
    +24, 0xe0bdfba5325a307e
    +25, 0x3a5f65c6ef254e05
    +26, 0x99ea12503cb02f94
    +27, 0x5d01fd2db77d420b
    +28, 0x6959bf5f36b2368d
    +29, 0xd856e30c62b5f5be
    +30, 0xe33233e1d8140e66
    +31, 0xb78be619d415fa8d
    +32, 0x4f943bb2cc63d3b
    +33, 0x9b1460b290952d81
    +34, 0x19205d794826740e
    +35, 0x64617bd9d7a6a1ff
    +36, 0x30442124b55ea76a
    +37, 0xebbbc3b29d0333fc
    +38, 0x39235a0fe359751c
    +39, 0xf9629768891121aa
    +40, 0x32052f53f366e05a
    +41, 0x60cc5b412c925bc8
    +42, 0xf8b7ecda1c0e5a9
    +43, 0x195f036e170a2568
    +44, 0xfe06d0381a9ca782
    +45, 0x919d89e8b88eebbf
    +46, 0xa47fb30148cf0d43
    +47, 0x5c983e99d5f9fd56
    +48, 0xe7492cdb6a1d42cd
    +49, 0xf9cfe5c865b0cfd8
    +50, 0x35b653367bbc3b99
    +51, 0xb1d92f6f4d4e440b
    +52, 0x737e1d5bd87ed9c0
    +53, 0x7a880ca1498f8e17
    +54, 0x687dae8494f9a3f7
    +55, 0x6bae1989f441d5d7
    +56, 0x71ad3fa5a9195c2e
    +57, 0x16b3969779f5d03
    +58, 0xd1bce2ac973f15b3
    +59, 0xa114b1ee2ce0dcdd
    +60, 0x270d75c11eb1b8d5
    +61, 0xc48ffa087c0a7bc
    +62, 0xaaf9dc48cda9848d
    +63, 0x8111cf10ef6e584d
    +64, 0x6736df6af40ee6f4
    +65, 0x1a1a111682fbf98d
    +66, 0xeb217658e1cb3b5d
    +67, 0xcaf58a8b79de9dec
    +68, 0x25d0ffd63c88d7a1
    +69, 0x4c498cd871b7f176
    +70, 0x4069a6156eb0cf3c
    +71, 0xdf012f12edcdd867
    +72, 0x7734c0ac8edb1689
    +73, 0xed6960ac53dbc245
    +74, 0x305e20da8868c661
    +75, 0x5f0c7a3719956f95
    +76, 0x66842bbe3b28895
    +77, 0xb608bc9a31eac410
    +78, 0xfcb17d5529503abd
    +79, 0x829ae5cbc29b92ee
    +80, 0x17f2f0027bc24f3a
    +81, 0x435926c33d8f44cc
    +82, 0x3ab899327098dbec
    +83, 0xaf78573b27f8ead8
    +84, 0xa8b334fabcf8dc60
    +85, 0xcdf3b366a6a303db
    +86, 0x8da9379dd62b34c8
    +87, 0xb0ba511955f264a7
    +88, 0x9d72e21a644f961d
    +89, 0xfac28382e2e7e710
    +90, 0xd457065f048410aa
    +91, 0x1cae57d952563969
    +92, 0x5a160a6223253e03
    +93, 0x2c45df736d73c8bd
    +94, 0x7f651ebc6ad9cec5
    +95, 0x77a6be96c7d2e7e7
    +96, 0x1721fb1dbfd6546a
    +97, 0xf73f433ecff3c997
    +98, 0xed1e80f680965bfe
    +99, 0x6705ad67a3003b30
    +100, 0xac21134efcadb9f7
    +101, 0x4d2ba0a91d456ac
    +102, 0x59da7b59434eb52b
    +103, 0x26c1d070fd414b5f
    +104, 0xed7079ddfce83d9a
    +105, 0x9277d21f88e0fb7a
    +106, 0xfae16b9a8d53d282
    +107, 0xb08a0e2e405fdf7d
    +108, 0x2ea20df44229d6ec
    +109, 0x80e4634cd3612825
    +110, 0xbe62e8aeba8f8a1a
    +111, 0x4981209769c190fb
    +112, 0xcec96ef14c7e1f65
    +113, 0x73fe4457b47e7b53
    +114, 0x1d66300677315c31
    +115, 0xe26821290498c4cc
    +116, 0xf6110248fd8fb1c5
    +117, 0x30fd7fe32dbd8be3
    +118, 0x534ec9b910a2bd72
    +119, 0x8f9bfe878bbf7382
    +120, 0x4f4eb5295c0c2193
    +121, 0xdeb22f03a913be9e
    +122, 0x40f716f8e2a8886c
    +123, 0xc65007d0e386cdb1
    +124, 0x9bdd26d92b143a14
    +125, 0xf644b0b77ea44625
    +126, 0x75f5a53f6b01993a
    +127, 0xfe803e347bf41010
    +128, 0x594bff5fa17bc360
    +129, 0x3551edfb450373c7
    +130, 0x898f9dad433615db
    +131, 0x923d2406daa26d49
    +132, 0x99e07faccbc33426
    +133, 0x7389f9ff4470f807
    +134, 0xdc2a25957c6df90b
    +135, 0x33c6d8965ef3053f
    +136, 0x51a8f07e838f1ab
    +137, 0x91c5db369380274f
    +138, 0xc37de65ac56b207e
    +139, 0xfcc6d2375dde7f14
    +140, 0xa4e6418bff505958
    +141, 0x4b8b9f78e46953c4
    +142, 0x255ab2e0f93cf278
    +143, 0xdf650717af3d96ef
    +144, 0x2caa21cba3aae2b2
    +145, 0xce7e46c6f393daa4
    +146, 0x1d5b3573f9997ac7
    +147, 0x5280c556e850847d
    +148, 0x32edc31bef920ad7
    +149, 0xefaa6b0b08cf2c6
    +150, 0x5151c99d97b111c5
    +151, 0x35ccf4bf53d17590
    +152, 0xa210d7bd8697b385
    +153, 0xa9419f95738fbe61
    +154, 0xdeccf93a1a4fdc90
    +155, 0xd0ea3365b18e7a05
    +156, 0x84122df6dcd31b9a
    +157, 0x33040a2125cea5f5
    +158, 0xfe18306a862f6d86
    +159, 0xdb97c8392e5c4457
    +160, 0xc3e0fa735e80e422
    +161, 0x7d106ff36467a0c1
    +162, 0xb9825eecc720a76d
    +163, 0x7fefc6f771647081
    +164, 0xf5df3f5b3977bf13
    +165, 0x18fb22736d36f1e0
    +166, 0xadc4637b4953abfc
    +167, 0x174e66d3e17974bd
    +168, 0xf1614c51df4db5db
    +169, 0x6664ecde5717b293
    +170, 0xd5bc5b6839265c26
    +171, 0xf6ca9ce1af3f1832
    +172, 0xca696789a9d506ea
    +173, 0x7399c246c8f9d53
    +174, 0xadf49049626417e2
    +175, 0xbcd84af37d09ab91
    +176, 0xbb41c177f3a3fa45
    +177, 0x592becc814d55302
    +178, 0xa88b4e65f6cfe5f7
    +179, 0xa0a55e34ff879426
    +180, 0x3c2ea6aa725b42b7
    +181, 0x65ac4a407b1f9521
    +182, 0xde63d53f7e88b556
    +183, 0x18bc76696d015f40
    +184, 0xd1363f2cd4c116a8
    +185, 0x2fe859be19a48e4a
    +186, 0x83d6099b1415e656
    +187, 0x43f2cbc1a4ee6410
    +188, 0xb2eca3d3421c533d
    +189, 0xc52b98ea3f031f5d
    +190, 0xfe57eb01da07e9d1
    +191, 0xf9377883537a6031
    +192, 0x364030c05dac7add
    +193, 0x6815cb06b35d4404
    +194, 0xceae2d4ce31894be
    +195, 0xc602bcdf6062bf6a
    +196, 0xc8e4bd8dcc6062e3
    +197, 0x9c29e87b92a1a791
    +198, 0x41e626b871ca9651
    +199, 0x325c3d1fb8efbcd8
    +200, 0x7dbbacf8e3419fb3
    +201, 0x3602e72516bb7319
    +202, 0x537a008ebd94d24b
    +203, 0xda7714fc9d4d161d
    +204, 0x1c8c73700e1b621b
    +205, 0x2749b80937d6c939
    +206, 0x76ee6abac5b14d33
    +207, 0xf18d1e92cb6a8b5c
    +208, 0x6ce9579d9291c721
    +209, 0x60523c745a40e58
    +210, 0x637f837fcc901757
    +211, 0x2ff71b19661dc5b3
    +212, 0x393ab586326ad16f
    +213, 0xa0970ea30fe742b7
    +214, 0x570222d7f27fe5ae
    +215, 0x3b5806d43fd38629
    +216, 0x129a0ad7420180c5
    +217, 0x1c4726355778d52c
    +218, 0x7c1459cf77656499
    +219, 0xfe038a0932132069
    +220, 0x4c4cc317a937483a
    +221, 0xa333d24067e926ba
    +222, 0x401d9b6ab37f6ef2
    +223, 0x87ad0e491ebe4a2a
    +224, 0xfc02f312e72d121d
    +225, 0xfde715b3b99767b2
    +226, 0xd111c342ba521c92
    +227, 0x83b221b10879c617
    +228, 0x6a1bf5c01fdf4277
    +229, 0x166bfc0c3f5892ee
    +230, 0x4608d556d7c57856
    +231, 0x8d786857c95ece49
    +232, 0x2d357445a1aca4ac
    +233, 0x79620dae28ecd796
    +234, 0x90e715dc0f2201c4
    +235, 0x173b68b4c9f4b665
    +236, 0x4e14d040ebac4eef
    +237, 0xbd25960b4b892e
    +238, 0x911a199db6f1989d
    +239, 0xfe822d7c601fd2e0
    +240, 0x9b4c1d58d8223a69
    +241, 0x907c1891283843b0
    +242, 0xf4868bf54061c4b2
    +243, 0x17f8cd1fc24efd85
    +244, 0xd44253f9af14c3aa
    +245, 0x16d0da0cb911d43c
    +246, 0x3c6a46615828e79a
    +247, 0x498591c1138e11a5
    +248, 0xcc0f26336d0d6141
    +249, 0x4d3ebc873212309a
    +250, 0x16bad7792d5c2c6a
    +251, 0x474215a80b2bbd11
    +252, 0x7159848abd8492fc
    +253, 0x359341c50973685f
    +254, 0x27512ee7bf784a4a
    +255, 0x45228ea080f70447
    +256, 0x880cab616500d50e
    +257, 0x12fae93f9830d56e
    +258, 0x6744ee64348d9acd
    +259, 0x484dada28cd2a828
    +260, 0x98491d0729e41863
    +261, 0x2f15aac43c2863b0
    +262, 0x5727a34d77a1da0f
    +263, 0xa435cebef6a62eed
    +264, 0xd211697d57b053b0
    +265, 0x65aa757b68bd557
    +266, 0xe3a1b7a2d8a3e06a
    +267, 0x2adf64e67252a7a9
    +268, 0xadadcb75cadee276
    +269, 0x7934bc57ac8d97bf
    +270, 0xccff0d0f412e0606
    +271, 0x101a82aa3e8f3db9
    +272, 0xb0f2498094b4575c
    +273, 0xba2561d9ef26ed8a
    +274, 0xfbcd1268fc3febe1
    +275, 0x9aa10bb19eb152e0
    +276, 0xf496217a601a6d72
    +277, 0xe4be1e4f2fa91363
    +278, 0x473a602bf3dd68eb
    +279, 0xfe8ed2a48c26f4b5
    +280, 0x20e94b1a00159476
    +281, 0x93e1cb1c6af86ec7
    +282, 0x4fcba3898f7442ba
    +283, 0x5150c3a3d94891df
    +284, 0x91cfce6c85b033ea
    +285, 0x625e8a832a806491
    +286, 0x28c97ba72e3ec0b2
    +287, 0x8e172de217c71ea1
    +288, 0x926b80216c732639
    +289, 0x28b19431a649ae3d
    +290, 0x57c039a6e95a3795
    +291, 0xfbc354182fe52718
    +292, 0x819dfd7c7d534cef
    +293, 0xabb4093a619ed44f
    +294, 0xe785b7ac6f656745
    +295, 0xb647b4588b2f942f
    +296, 0x64cf870a14c72d27
    +297, 0x6d4a4a2a0ba9b37e
    +298, 0x78bfb0427d7ce6b0
    +299, 0x8dcc72b8bfc79ac6
    +300, 0x1c14d915d5e76c99
    +301, 0xaf48ddea6f096d79
    +302, 0x51b39b67aa130d8
    +303, 0x1aeeb39d4def06de
    +304, 0xd678092ffedfdd27
    +305, 0x8f54787f325111d3
    +306, 0xf2ca2e827beaa6bc
    +307, 0x339d134099e98545
    +308, 0x1f6a8a7b33942e43
    +309, 0x952c8065dbef669a
    +310, 0xe066aeb6690147f7
    +311, 0xed25aa92cf58ebb6
    +312, 0x7601edce215ef521
    +313, 0xed1c5b396abd9434
    +314, 0x4fd1e407535de9d5
    +315, 0xccc8315a0d4d1441
    +316, 0x85753e250bb86976
    +317, 0xf232e469378761c3
    +318, 0x81d691b8e9aef3c6
    +319, 0x224a2f9cab0ad0e
    +320, 0x978f3d3e50007f4e
    +321, 0xd3713e6a6c0cbe60
    +322, 0xcce8f1eadd41f80d
    +323, 0x34bda028a97d469
    +324, 0x90e242fdf0f59183
    +325, 0x4d749754fbc5f092
    +326, 0x4399f5b7851cc87b
    +327, 0xcb921a5f25f6c5d7
    +328, 0x120bf5d0162101
    +329, 0x1304cc2aa352735a
    +330, 0xf7236c5d0d5d417b
    +331, 0xc31b320fc1654306
    +332, 0xb468c6b23f3fb4e7
    +333, 0xb5985b5bfaca4166
    +334, 0x898285a1cd2f8375
    +335, 0xa13493da372aa7c9
    +336, 0x15c80c09c12634e7
    +337, 0x9b765c5cc9d438bd
    +338, 0xee7da816a9201dcb
    +339, 0x92e269f73b5a248e
    +340, 0xa8086c5de81400ce
    +341, 0xe0053901853d42be
    +342, 0x821df32c012f433e
    +343, 0x17a6d69ca37387c7
    +344, 0x2b10044bfba3501f
    +345, 0x8dfd262afc2e8515
    +346, 0xd68c2c7b60226371
    +347, 0xe81ac114e4416774
    +348, 0x5896d60061ebc471
    +349, 0xa996e3147811dbd1
    +350, 0xa819c7b80ecb3661
    +351, 0x982ad71b38afbc01
    +352, 0xab152b65aa17b7fe
    +353, 0x4582bc282ef187ef
    +354, 0xab5a17fe8d9bc669
    +355, 0x83664fa9cb0284b7
    +356, 0x234c4b0091968f52
    +357, 0x8ab5f51805688d37
    +358, 0xe9e11186e0c53eda
    +359, 0x10df37ef1de2eccf
    +360, 0x780f1b0d52db968f
    +361, 0x50bd4ff292872cd5
    +362, 0x51e681c265f5ad0
    +363, 0x842c49660a527566
    +364, 0x6e56ee026e9eda87
    +365, 0x4cf39e40d8c80393
    +366, 0x13e466df371f7e1f
    +367, 0xf2ce1799f38e028e
    +368, 0x833c8db7adc6ff0e
    +369, 0xc6e189abc2ec98f
    +370, 0xafebb3721283fec5
    +371, 0xb49bc1eb5cc17bdc
    +372, 0xf1d02e818f5e4488
    +373, 0xe5e9d5b41a1dd815
    +374, 0xce8aca6573b1bfe5
    +375, 0x9b0a5d70e268b1d5
    +376, 0xf3c0503a8358f4de
    +377, 0x2681605dd755669d
    +378, 0xea265ca7601efc70
    +379, 0xa93747f0a159439f
    +380, 0x62a86ede78a23e50
    +381, 0xac8a18935c3d063c
    +382, 0x729c0a298f5059f5
    +383, 0xbbf195e5b54399f4
    +384, 0x38aa9d551f968900
    +385, 0x3b3e700c58778caa
    +386, 0x68e6e33c4443957a
    +387, 0x7c56fc13eb269815
    +388, 0xaf7daca39711804a
    +389, 0x50fde6d10f9544b3
    +390, 0xf3d37159f6f6c03d
    +391, 0x82d298f5c1a71685
    +392, 0x478661ac54c5002c
    +393, 0x6053768e1a324ae0
    +394, 0xde8fb4a7e56707ea
    +395, 0xaa2809301faa8cf4
    +396, 0x690a8d49fedd0722
    +397, 0xe17c481b9c217de9
    +398, 0x60d1d8a2b57288e3
    +399, 0x149adfaadc6b0886
    +400, 0xa3c18b6eb79cd5fa
    +401, 0x5774e3a091af5f58
    +402, 0x2acca57ff30e5712
    +403, 0x94454d67367c4b0c
    +404, 0x581b2985ac2df5ca
    +405, 0x71618e50744f3e70
    +406, 0x270a7f3bd9a94ae6
    +407, 0x3ef81af9bb36cd7b
    +408, 0x8a4a2592875254aa
    +409, 0x704ac6086fbb414a
    +410, 0xda774d5d3f57414d
    +411, 0xe20d3358b918ae9e
    +412, 0x934a6b9f7b91e247
    +413, 0xf91649cde87ec42c
    +414, 0x248cec5f9b6ced30
    +415, 0x56791809fd8d64ba
    +416, 0xf502b2765c1395f
    +417, 0x6b04ec973d75aa7f
    +418, 0xb0339f2794bb26f
    +419, 0x4c524636efbaea49
    +420, 0x6bbf3876e9738748
    +421, 0xf686524e754e9e24
    +422, 0x8dafa05a42d19cd3
    +423, 0xc5f069ab2434008e
    +424, 0x4fd64cc713cba76
    +425, 0xdbf93450c881ed5f
    +426, 0x492e278ebabb59a2
    +427, 0x993fddfde4542642
    +428, 0xecde68a72c8d4e52
    +429, 0xe0760b3074c311fd
    +430, 0x68dc0e7e06528707
    +431, 0x52b50edf49c0fdc7
    +432, 0xb2bd4185c138f412
    +433, 0x431496d7e1d86f3
    +434, 0xa4e605b037e26c44
    +435, 0x58236ae1f0aca2b5
    +436, 0x26c72c420fc314d8
    +437, 0x20134e982ab99a2b
    +438, 0x544b59b8b211374b
    +439, 0x1301c42f3a14d993
    +440, 0x52a6ea740f763b0f
    +441, 0xf209d70c2bebf119
    +442, 0xac66a4ebc2aa1be
    +443, 0x683713ed35878788
    +444, 0x2b5578acec06b80c
    +445, 0x86428efa11c45b36
    +446, 0xb49010adb17d291e
    +447, 0x73b686bd8664b6be
    +448, 0x6d28ebf57b6884cc
    +449, 0x9712091230ff58d9
    +450, 0xc9c91f74c38b286
    +451, 0x776310ac41dc008e
    +452, 0x2f3739df0bf6a88e
    +453, 0x5792dc62b94db675
    +454, 0x5715910d024b06af
    +455, 0xeb1dd745458da08
    +456, 0xfce7b07ccfa851a7
    +457, 0xc305f1e983ac368
    +458, 0x485aa9519ac00bb0
    +459, 0xa5354f6589fb0ea0
    +460, 0x32fee02dfdbf4454
    +461, 0x4d1ddc304bbefaaa
    +462, 0x789a270a1737e57e
    +463, 0x9f3072f4b1ed8156
    +464, 0x4de3c00e89058120
    +465, 0xb00a02529e0a86fa
    +466, 0x539f6f0edd845d9a
    +467, 0x85e578fe15a8c001
    +468, 0xa12c8e1a72cce7d8
    +469, 0xc6908abbc2b1828
    +470, 0xcf70090774cbb38c
    +471, 0x3b636a6977b45d4a
    +472, 0xf0a731b220680b57
    +473, 0x18973929f51443a8
    +474, 0xe93e1fbe7eadabe
    +475, 0x8233730f0a6dfa02
    +476, 0x66e50b6919b0ab74
    +477, 0xb1aba87c97fd08a2
    +478, 0xd4dffc1fbc117ad6
    +479, 0x6f7fa65724b96e6a
    +480, 0x4bd5800dee92e0fa
    +481, 0xe18a959db6256da
    +482, 0xe53a291bc66df487
    +483, 0xb7ec306a08651806
    +484, 0x1847a6b80d2821e1
    +485, 0xda50391283b14d39
    +486, 0xacc4d3cd7cceb97a
    +487, 0x57f70185165b7bc6
    +488, 0x302b6d597c3aaba7
    +489, 0xa47f32d037eab51e
    +490, 0xe1509b4408abc559
    +491, 0x4f30a1d7c2934157
    +492, 0x2ad03e6c60b650b2
    +493, 0x334d9c337b0a9064
    +494, 0xc7f442821e7aac12
    +495, 0xbcdeb09298694cdd
    +496, 0xe42402389f8f0fb4
    +497, 0xe5de56af539df727
    +498, 0x7017f9b2101ee240
    +499, 0x1ee5e68d5b10001d
    +500, 0x436229051836387a
    +501, 0xcd532d6d6ec38fb7
    +502, 0x30a66606fdf38272
    +503, 0xfdaa2ab9cf798496
    +504, 0x4277b4adec70e7df
    +505, 0x72cfc30256e0eaef
    +506, 0x3c3359fd9bd34917
    +507, 0xb7aa89598856efb0
    +508, 0xf72226f8bf299ef5
    +509, 0x258c499275a4356f
    +510, 0x999a56bfc7f20d76
    +511, 0x2b3e7432e20c18b
    +512, 0x2d1251332f760cb5
    +513, 0x7420e0eea62157c5
    +514, 0xe85c895aa27cec3d
    +515, 0x27a0545c7020d57c
    +516, 0xc68638a65b4fff0d
    +517, 0xfda473983a4ea747
    +518, 0xd19fe65fb4c06062
    +519, 0x6b1374e050ee15e4
    +520, 0x80065ecd49bc4bef
    +521, 0x4ee655954bc838de
    +522, 0xe8fb777504a72299
    +523, 0x86b652ea70f4bdde
    +524, 0xcdc9e0fbde7e4f33
    +525, 0x352c0a50cd3ac56
    +526, 0x4b8605d368be75dc
    +527, 0x1ac9ea8129efbc37
    +528, 0x470325faa99f39c5
    +529, 0x25dd7ef9adccf7a1
    +530, 0x5ae2c7a03e965816
    +531, 0xf733d2df59dacc7d
    +532, 0xa05bbf0a8a1a7a70
    +533, 0xe8aa3f102846ef5f
    +534, 0xc9b85ec49ae71789
    +535, 0xb904c14ed1cb1936
    +536, 0x5ae618230b5f0444
    +537, 0x97987fe47b5d7467
    +538, 0xabb3aca8865ca761
    +539, 0x38bfdf29d4508228
    +540, 0x353654f408353330
    +541, 0xeb7e92930ae4ef0d
    +542, 0xec50f1a7ca526b96
    +543, 0xd5e2dc08b5697544
    +544, 0x24c7fd69d5ec32df
    +545, 0x6f7e1095568b8620
    +546, 0x6ed9c16ca13b3c8
    +547, 0xe676ef460002130f
    +548, 0xa3a01a3992c4b430
    +549, 0xe2130406c3b1f202
    +550, 0xa8f7263e2aedcd20
    +551, 0xc45d71ef2e35f507
    +552, 0x37155594021da7ba
    +553, 0x22dc94f19de73159
    +554, 0x7969fc6bffc5443f
    +555, 0x97def7e44faa6bfe
    +556, 0x8b940f5e8931d71f
    +557, 0xd95b1dd3f1a3fdd5
    +558, 0x1c83bfdca615701a
    +559, 0xb7fcb56279ceca6b
    +560, 0xd84f8950f20dcd0
    +561, 0xb03343698de3cbe0
    +562, 0xf64565d448d71f71
    +563, 0xda52b4676e0ae662
    +564, 0xda39c2c05b4ffb91
    +565, 0xb35e2560421f6a85
    +566, 0x1a7b108d48ac3646
    +567, 0xc4e264dc390d79ed
    +568, 0xa10727dfd9813256
    +569, 0x40d23154e720e4f7
    +570, 0xd9fa7cd7e313e119
    +571, 0xcbf29107859e6013
    +572, 0xc357338553d940b7
    +573, 0x2641b7ab0bdfcbaa
    +574, 0xd12f2b6060533ae7
    +575, 0xd0435aa626411c56
    +576, 0x44af4a488a9cec72
    +577, 0xb934232ea8fa5696
    +578, 0x760a8b12072b572d
    +579, 0xfab18f9942cfa9b3
    +580, 0x5676834c1fe84d16
    +581, 0x9c54e4fddb353236
    +582, 0xab49edfc9551f293
    +583, 0x567f1fb45a871d
    +584, 0x32a967c873998834
    +585, 0x99240aad380ef8d1
    +586, 0x7f66cbd432859a64
    +587, 0x4cdc8a4658166822
    +588, 0x984e3984a5766492
    +589, 0xa3b2d0a3d64d3d94
    +590, 0x177f667172f2affc
    +591, 0xb1a90607a73a303f
    +592, 0xe600b6c36427f878
    +593, 0xf758f9834cb7f466
    +594, 0x8ee9fce4a3f36449
    +595, 0xcb8f11533e7da347
    +596, 0xe7cf647794dabd7c
    +597, 0xc9d92cfe6110806
    +598, 0xea1335fa9145a1ec
    +599, 0xbc6c29821d094552
    +600, 0x37b9d6a858cc8bc3
    +601, 0xf24e4c694929893e
    +602, 0x55d025ce2d7d0004
    +603, 0xccdc69acccf4267b
    +604, 0xc491c04340c222eb
    +605, 0xba50f75ecec9befb
    +606, 0x1ec7bd85b8fe3bb9
    +607, 0xe4de66498c59ae8a
    +608, 0x38aa9e912712c889
    +609, 0xcee0e43c5cc31566
    +610, 0x72b69aa708fc7ed
    +611, 0xdff70b7f6fa96679
    +612, 0xd6d71d82112aadc3
    +613, 0x365177892cb78531
    +614, 0xa54852b39de4f72c
    +615, 0x11dd5832bf16dd59
    +616, 0x248a0f3369c97097
    +617, 0xa14cec0260e26792
    +618, 0x3517616ff142bed1
    +619, 0x9b693ad39dab7636
    +620, 0x739dff825e994434
    +621, 0x67711e7356098c9
    +622, 0xa81f8515d2fdf458
    +623, 0xdac2908113fe568e
    +624, 0xe99944ebc6e2806a
    +625, 0x671728ca5b030975
    +626, 0xfdad20edb2b4a789
    +627, 0xedc6e466bd0369d2
    +628, 0x88b5d469821f7e1b
    +629, 0x2eabf94049a522a5
    +630, 0x247794b7a2f5a8e3
    +631, 0x278942bdbe02c649
    +632, 0xbe5a9a9196ab99c1
    +633, 0x75955060866da1b5
    +634, 0xdedcfa149273c0b5
    +635, 0xdbeb7a57758f3867
    +636, 0x7b9053347a2c8d5a
    +637, 0xa059b3f2eed338a5
    +638, 0x59401a46ded3b79f
    +639, 0x38044ba56a6d19fb
    +640, 0x72c7221b4e77e779
    +641, 0x526df3491a3a34da
    +642, 0xc3b31184ba16c0c2
    +643, 0xd94c7144488624af
    +644, 0xcf966ee4dc373f91
    +645, 0x62049e65dd416266
    +646, 0x7c2adccb925bf8f
    +647, 0xd5fa5c22ed4ef8e1
    +648, 0xd00134ebd11f2cd1
    +649, 0xfbdf81767bed3634
    +650, 0x62e8cc8ff66b6e26
    +651, 0x3a72d6bcd4f2dcf7
    +652, 0xf1cd45b1b46a86ed
    +653, 0x1271f98e0938bb9a
    +654, 0x82e6927e83dc31fa
    +655, 0x7b9b0e0acb67b92d
    +656, 0x6df503e397b2e701
    +657, 0x93888f6fb561e0c3
    +658, 0x393fb6069a40291
    +659, 0x967a7d894cc0754d
    +660, 0x6e298996ad866333
    +661, 0x5ff3cf5559d6ab46
    +662, 0xd0d70508c40349f5
    +663, 0xc64c66c0dd426b33
    +664, 0x8fea340ee35c64dd
    +665, 0xf9cd381eb3060005
    +666, 0xfcc37c2799fc0b11
    +667, 0x6a37c91d65b489fa
    +668, 0x57231000fa0a0c9d
    +669, 0x55f6e292c6703f9a
    +670, 0xd0508ffbfa55a7a6
    +671, 0x885db543276bdac8
    +672, 0xc26dbe6a26b0e704
    +673, 0x21f884874ebd709e
    +674, 0x711f0b6c8f732220
    +675, 0x354d0a361eaee195
    +676, 0x721344d8d30b006a
    +677, 0xa0e090a0d3a56f07
    +678, 0x16b3d5d823a4952b
    +679, 0x59d7874bc9eae7b6
    +680, 0x9bbb32710076455f
    +681, 0xd4fb22242ffabafd
    +682, 0xe1d4ac6770be1d89
    +683, 0xb259cedebc73dc8a
    +684, 0x35faaa3b4246ab69
    +685, 0x5d26addefdaee89
    +686, 0x8e7ec350da0f3545
    +687, 0xd0f316eed9f8fc79
    +688, 0x98b2a52c9bf291b2
    +689, 0xe4d294a8aca6a314
    +690, 0x25bd554e6aa7673c
    +691, 0xcfde5dcba5be2a6c
    +692, 0xb5e01fb48d2d2107
    +693, 0xe1caf28948028536
    +694, 0xd434aa0a26f3ee9b
    +695, 0xd17723381641b8f6
    +696, 0xfe73bd1f3f3768a2
    +697, 0x1cc6b1abd08d67e9
    +698, 0x247e328371a28de0
    +699, 0x502e7942e5a9104a
    +700, 0x6a030fd242eb4502
    +701, 0xa2ffe02744014ce8
    +702, 0x59290763b18fe04e
    +703, 0xcf14241564271436
    +704, 0xb0fb73c3c1503aff
    +705, 0x94e27c622f82137a
    +706, 0x747a5b406ac3e1f0
    +707, 0x9a914e96a732031d
    +708, 0x59f68c6c8f078835
    +709, 0x809d012c73eb4724
    +710, 0x5b3c3b73e1b37d74
    +711, 0xdde60ef3ba49cdf7
    +712, 0x87a14e1f9c761986
    +713, 0x4109b960604522af
    +714, 0x122d0e1ed0eb6bb9
    +715, 0xadc0d29e80bfe33
    +716, 0xa25b1b44f5fc8e4e
    +717, 0xbab85d8a9b793f20
    +718, 0x825f4cbced0e7d1e
    +719, 0x2d6ae8807acb37ea
    +720, 0x8234420adce2e39
    +721, 0x4a8ad4da6b804807
    +722, 0x1e19f9bc215e5245
    +723, 0x1d6f4848a916dd5e
    +724, 0x9ac40dfcdc2d39cc
    +725, 0x9f3524e3086155ec
    +726, 0x861fffc43124b2ef
    +727, 0xe640e3b756396372
    +728, 0x41cb0f0c5e149669
    +729, 0xe0bd37e1192e4205
    +730, 0x62917d3858f4ce47
    +731, 0xa36e7eb4d855820a
    +732, 0x204b90255a3bf724
    +733, 0x66ee83a0175535bc
    +734, 0x2c14ce7c6b0c1423
    +735, 0x85d9495fa514f70d
    +736, 0x5a4fe45ead874dbc
    +737, 0xe72248dcb8cfc863
    +738, 0xfc21ff2932ed98cd
    +739, 0xcbba1edd735b5cad
    +740, 0x91ddc32809679bf5
    +741, 0x192cdf2c7631ea1f
    +742, 0xbbc451ddf2ea286f
    +743, 0xad9e80cae2397a64
    +744, 0x6918f0119b95d0e5
    +745, 0xa40379017a27d70a
    +746, 0x1aaeddb600e61e1
    +747, 0x15afd93cbd7adda9
    +748, 0x156719bc2b757ff4
    +749, 0x13d9a59e2b2df49d
    +750, 0x9a490986eaddf0a
    +751, 0xef9a350f0b3eb6b4
    +752, 0x5de7f6295ba4fa4d
    +753, 0x7f37fd087c3fdb49
    +754, 0xa9fe3749d6f3f209
    +755, 0x50912ac036d9bfb
    +756, 0x982cb4d726a441f8
    +757, 0x8ca8d8af59b872d0
    +758, 0x7f8adfb0ceeade8a
    +759, 0xdad390ec742be44
    +760, 0xa637944d0045be5b
    +761, 0x3569a3b3af807061
    +762, 0x9599da8eae14511d
    +763, 0xc333e8d19589b01a
    +764, 0xfb9b524a20b571e1
    +765, 0xbd9dc8b37ce5c3e1
    +766, 0x142333005fa389ac
    +767, 0x1368bc37cd5bcce1
    +768, 0x16094907ad6ecf73
    +769, 0xb32c90dbba4c1130
    +770, 0x82761d97c1747dd0
    +771, 0x599f9f267ae3444d
    +772, 0x79ad3382994852e1
    +773, 0x2511f06d9ef06e54
    +774, 0xb35e6ab7d5bbddae
    +775, 0xfca9fa83a2988732
    +776, 0x7d4350f0394ac3ba
    +777, 0xa52a9527bb176ea3
    +778, 0xb49fa0ceb2aa8353
    +779, 0x1f62e504d1468cc0
    +780, 0xe1a77bfccce6efc3
    +781, 0x776cdff4dc0d6797
    +782, 0x56612e39b652c1f2
    +783, 0x5f096a29294eda04
    +784, 0x7978abc3aabd8b23
    +785, 0x79dd875e0485b979
    +786, 0x8a98aa4d5735d778
    +787, 0xcca43940f69d2388
    +788, 0xb2d4b156f144f93a
    +789, 0xbd528a676e9a862
    +790, 0x2a394939c8e7ec5e
    +791, 0xb1da900c6efe4abc
    +792, 0x9869af479de4c034
    +793, 0x78dbdfb88ac7c1db
    +794, 0x18cb169143088041
    +795, 0xe69e5461c51a3e13
    +796, 0x5389fa16ea98183c
    +797, 0xed7c80d1be1ea520
    +798, 0x87246fc359758ced
    +799, 0xab323eba95fae4ed
    +800, 0xbc4c0dde7f8a1828
    +801, 0xdb739f7955610b1a
    +802, 0xecd8c68c3434cc
    +803, 0x138c2eb88c477f44
    +804, 0x28a65f96727aae41
    +805, 0xdee879f2cf5629d
    +806, 0x684f0c90ef20070f
    +807, 0xa24a819ef5621800
    +808, 0x8d0054f870e4fdcb
    +809, 0x99e8c6e695b600b
    +810, 0x50b705245891f7c3
    +811, 0xc02eed3a6e58e51a
    +812, 0x443d64e95443606c
    +813, 0xca24959cfbd2d120
    +814, 0xe072609ea48815bc
    +815, 0xbcc715026590315b
    +816, 0x3e76df24d7aa5938
    +817, 0xd8ff04940d9b79ae
    +818, 0x54474ce790059bcd
    +819, 0x278390dd6aa70e81
    +820, 0xf4df619fe35414e4
    +821, 0x757d71270264e615
    +822, 0x1e8a373699c11b23
    +823, 0xef68c82046e67dd6
    +824, 0xe280006599972620
    +825, 0x234e095183b0f4d6
    +826, 0xe3b7560ed9839749
    +827, 0xcd5ec4086572332e
    +828, 0xc41c0d4aaa279108
    +829, 0x4b9cd6126bc16a6d
    +830, 0x4a7252734f3e3dd0
    +831, 0xb3132df156cc103a
    +832, 0xf9e4abbf7b64464a
    +833, 0xf936df27fb3c47b7
    +834, 0x9142960873f6d71a
    +835, 0x4ba6aa3235cdb10d
    +836, 0x3237a2e765ba7766
    +837, 0xd62f0b94c8e99e54
    +838, 0x26b682f90a3ae41b
    +839, 0x40ad5e82072b6f81
    +840, 0xd0198101f5484000
    +841, 0xe4fac60ba11c332
    +842, 0x472d0b0a95ef9d38
    +843, 0x8512557aec5a3d8f
    +844, 0xef83169d3efd4de9
    +845, 0x53fe89283e7a7676
    +846, 0x2f50933053d69fc4
    +847, 0x76f5e4362e2e53a2
    +848, 0x8676fdccce28874a
    +849, 0x2737764c1fb1f821
    +850, 0x4a6f70afc066ab55
    +851, 0x27f8e151e310fca4
    +852, 0xd606960ccbe85161
    +853, 0xcce51d7ddd270a32
    +854, 0xb4235999794875c2
    +855, 0x580084e358e884
    +856, 0x2159d5e6dc8586d7
    +857, 0x87bd54d8599b3ba4
    +858, 0x3e9ade6a2181664
    +859, 0x5e6e140406d97623
    +860, 0x511545d5aa0080a2
    +861, 0xf49d78ed219aac57
    +862, 0xbece1f9c90b8ea87
    +863, 0x1c741cac36a2c514
    +864, 0x7453c141047db967
    +865, 0xd751832a5037eba2
    +866, 0x71370a3f30ada1f7
    +867, 0x7c01cf2dcb408631
    +868, 0x1052a4fbdccc0fa1
    +869, 0x13d525c9df3fb6c
    +870, 0xa3aa8dbfee760c55
    +871, 0xc0288d200f5155cf
    +872, 0x79f4bcd12af567c3
    +873, 0x8160d163bb548755
    +874, 0x5cf2995fb69fd2df
    +875, 0xcc98ed01396639df
    +876, 0xad95f1d9cfc8256e
    +877, 0xa3df27d9fbdbfb9d
    +878, 0x83e5f5dda4d52929
    +879, 0x9adc05043009f55b
    +880, 0xdfe8329dfde1c001
    +881, 0x9980ccdd5298e6a2
    +882, 0x636a7bd134f6ef56
    +883, 0xef5ff780c4be6ba4
    +884, 0x290d71dc77a56d16
    +885, 0x6d65db9ff58de1e6
    +886, 0x944b063b3805a696
    +887, 0xce468ca2cce33008
    +888, 0x5ba1ccb840f80f48
    +889, 0x28ddce36fc9ad268
    +890, 0x4f77ef254d507a21
    +891, 0xce9b4057fadf3ab
    +892, 0xb518bc68298730e6
    +893, 0xd2eb5b8e2ec665b0
    +894, 0xe1583303a4f87344
    +895, 0x9d5a0df4fbe1bed5
    +896, 0x2ba9bc03ec8cfd07
    +897, 0x479ed880a96ca669
    +898, 0xcedf96338324771a
    +899, 0x312f4fc2da41ffaa
    +900, 0xa0eb9cf23b5e1ed8
    +901, 0xf8f88f975dc3f539
    +902, 0x4a37e185d0e96e0f
    +903, 0xf829654a5c0b46f9
    +904, 0x3909cca7a7f8c7fb
    +905, 0x4c2e1d66ceb45105
    +906, 0xaffaa19e1db8af87
    +907, 0x9ec498246bd18c76
    +908, 0x21d51558edc089da
    +909, 0xe8984112cd1b1561
    +910, 0x7de1d2cf54b0c0e1
    +911, 0xa06729aed50bfb9d
    +912, 0xcf19f733e5db19e1
    +913, 0x70edf2624ab777cd
    +914, 0x46685becad10e078
    +915, 0x825e0f6add46785
    +916, 0x66d4af3b15f70de4
    +917, 0xc676614b0666b21
    +918, 0x282a916c864f5cb7
    +919, 0x2707283a3f512167
    +920, 0x37ff3afda7461623
    +921, 0xc767eb1205e4ca86
    +922, 0x46b359aecc4ea25b
    +923, 0x67fbbb797a16dbb1
    +924, 0x64fd4ba57122290e
    +925, 0x8acc2a8ae59d8fac
    +926, 0x64a49298599acc67
    +927, 0xedf00de67177ce30
    +928, 0x1ea9d8d7e76d2d2c
    +929, 0x363fcac323f70eb2
    +930, 0x19e6e3ec8a9712eb
    +931, 0xca541e96b0961f09
    +932, 0x4d8fd34c2822ec46
    +933, 0x2fdd56a50b32f705
    +934, 0xaac2fcf251e3fd3
    +935, 0xb0c600299e57045c
    +936, 0xd951ec589e909e38
    +937, 0x4dc8414390cae508
    +938, 0x537ef9d5e2321344
    +939, 0xa57bc21fd31aa2dc
    +940, 0xa3a60df564183750
    +941, 0xbe69a5ce2e369fb6
    +942, 0x7744601f4c053ec8
    +943, 0x3838452af42f2612
    +944, 0xd4f0dad7115a54e9
    +945, 0x629cf68d8009a624
    +946, 0x2211c8fa34cb98cb
    +947, 0x8040b19e2213db83
    +948, 0xb2a86d3ba2384fd
    +949, 0x4b85cec4f93f0dab
    +950, 0xc8d212d21ea6845d
    +951, 0x5b271a03a4fe2be0
    +952, 0xff4f671319ad8434
    +953, 0x8e615a919d5afa96
    +954, 0xea7f47c53161160a
    +955, 0x33273930b13c6efc
    +956, 0x98eedda27fb59c3c
    +957, 0x188dc5e92e939677
    +958, 0x9dbd0fa0911430f1
    +959, 0x5b3dcf3fa75dfd2b
    +960, 0x3f03846febdb275d
    +961, 0x20cc24faea9e9cf6
    +962, 0x854f3ac66199ff5d
    +963, 0x31169ac99d341e6f
    +964, 0xa85daed3c0bc1bbe
    +965, 0x64633711e71ba5dd
    +966, 0x530e79978dc73334
    +967, 0x636f2ee6e20aef13
    +968, 0xf6220f8b6d9a58fb
    +969, 0x425db8fa32141a7b
    +970, 0xac7c210f4b02be95
    +971, 0x5fe8cfbe197a7754
    +972, 0xfff7d40c79420ea
    +973, 0x5f8bab9ef4697b77
    +974, 0xaf6fe54e45b23fe8
    +975, 0xce79456ccc70bbce
    +976, 0x645ef680f48f1c00
    +977, 0xa4dfac46e2028595
    +978, 0x6bece4c41effc5df
    +979, 0xd316df886442641f
    +980, 0xa4f6ff994edd2a6
    +981, 0x30281ae3cc49abe4
    +982, 0x39acb7b663dea974
    +983, 0x5e8829b01a7c06fb
    +984, 0x87bdb08cf027f13e
    +985, 0xdfa5ede784e802f6
    +986, 0x46d03d55711c38cc
    +987, 0xa55a961fc9788306
    +988, 0xbf09ded495a2e57a
    +989, 0xcd601b29a639cc16
    +990, 0x2193ce026bfd1085
    +991, 0x25ba27f3f225be13
    +992, 0x6f685be82f64f2fe
    +993, 0xec8454108229c450
    +994, 0x6e79d8d205447a44
    +995, 0x9ed7b6a96b9ccd68
    +996, 0xae7134b3b7f8ee37
    +997, 0x66963de0e5ebcc02
    +998, 0x29c8dcd0d17c423f
    +999, 0xfb8482c827eb90bc
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/test_direct.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/test_direct.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..9916f8ad3440f6815397cb019c06405353903706
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/test_direct.py
    @@ -0,0 +1,595 @@
    +import os
    +import sys
    +from os.path import join
    +
    +import pytest
    +
    +import numpy as np
    +from numpy.random import (
    +    MT19937,
    +    PCG64,
    +    PCG64DXSM,
    +    SFC64,
    +    Generator,
    +    Philox,
    +    RandomState,
    +    SeedSequence,
    +    default_rng,
    +)
    +from numpy.random._common import interface
    +from numpy.testing import (
    +    assert_allclose,
    +    assert_array_equal,
    +    assert_equal,
    +    assert_raises,
    +)
    +
    +try:
    +    import cffi  # noqa: F401
    +
    +    MISSING_CFFI = False
    +except ImportError:
    +    MISSING_CFFI = True
    +
    +try:
    +    import ctypes  # noqa: F401
    +
    +    MISSING_CTYPES = False
    +except ImportError:
    +    MISSING_CTYPES = False
    +
    +if sys.flags.optimize > 1:
    +    # no docstrings present to inspect when PYTHONOPTIMIZE/Py_OptimizeFlag > 1
    +    # cffi cannot succeed
    +    MISSING_CFFI = True
    +
    +
    +pwd = os.path.dirname(os.path.abspath(__file__))
    +
    +
    +def assert_state_equal(actual, target):
    +    for key in actual:
    +        if isinstance(actual[key], dict):
    +            assert_state_equal(actual[key], target[key])
    +        elif isinstance(actual[key], np.ndarray):
    +            assert_array_equal(actual[key], target[key])
    +        else:
    +            assert actual[key] == target[key]
    +
    +
    +def uint32_to_float32(u):
    +    return ((u >> np.uint32(8)) * (1.0 / 2**24)).astype(np.float32)
    +
    +
    +def uniform32_from_uint64(x):
    +    x = np.uint64(x)
    +    upper = np.array(x >> np.uint64(32), dtype=np.uint32)
    +    lower = np.uint64(0xffffffff)
    +    lower = np.array(x & lower, dtype=np.uint32)
    +    joined = np.column_stack([lower, upper]).ravel()
    +    return uint32_to_float32(joined)
    +
    +
    +def uniform32_from_uint53(x):
    +    x = np.uint64(x) >> np.uint64(16)
    +    x = np.uint32(x & np.uint64(0xffffffff))
    +    return uint32_to_float32(x)
    +
    +
    +def uniform32_from_uint32(x):
    +    return uint32_to_float32(x)
    +
    +
    +def uniform32_from_uint(x, bits):
    +    if bits == 64:
    +        return uniform32_from_uint64(x)
    +    elif bits == 53:
    +        return uniform32_from_uint53(x)
    +    elif bits == 32:
    +        return uniform32_from_uint32(x)
    +    else:
    +        raise NotImplementedError
    +
    +
    +def uniform_from_uint(x, bits):
    +    if bits in (64, 63, 53):
    +        return uniform_from_uint64(x)
    +    elif bits == 32:
    +        return uniform_from_uint32(x)
    +
    +
    +def uniform_from_uint64(x):
    +    return (x >> np.uint64(11)) * (1.0 / 9007199254740992.0)
    +
    +
    +def uniform_from_uint32(x):
    +    out = np.empty(len(x) // 2)
    +    for i in range(0, len(x), 2):
    +        a = x[i] >> 5
    +        b = x[i + 1] >> 6
    +        out[i // 2] = (a * 67108864.0 + b) / 9007199254740992.0
    +    return out
    +
    +
    +def uniform_from_dsfmt(x):
    +    return x.view(np.double) - 1.0
    +
    +
    +def gauss_from_uint(x, n, bits):
    +    if bits in (64, 63):
    +        doubles = uniform_from_uint64(x)
    +    elif bits == 32:
    +        doubles = uniform_from_uint32(x)
    +    else:  # bits == 'dsfmt'
    +        doubles = uniform_from_dsfmt(x)
    +    gauss = []
    +    loc = 0
    +    x1 = x2 = 0.0
    +    while len(gauss) < n:
    +        r2 = 2
    +        while r2 >= 1.0 or r2 == 0.0:
    +            x1 = 2.0 * doubles[loc] - 1.0
    +            x2 = 2.0 * doubles[loc + 1] - 1.0
    +            r2 = x1 * x1 + x2 * x2
    +            loc += 2
    +
    +        f = np.sqrt(-2.0 * np.log(r2) / r2)
    +        gauss.append(f * x2)
    +        gauss.append(f * x1)
    +
    +    return gauss[:n]
    +
    +
    +def test_seedsequence():
    +    from numpy.random.bit_generator import (
    +        ISeedSequence,
    +        ISpawnableSeedSequence,
    +        SeedlessSeedSequence,
    +    )
    +
    +    s1 = SeedSequence(range(10), spawn_key=(1, 2), pool_size=6)
    +    s1.spawn(10)
    +    s2 = SeedSequence(**s1.state)
    +    assert_equal(s1.state, s2.state)
    +    assert_equal(s1.n_children_spawned, s2.n_children_spawned)
    +
    +    # The interfaces cannot be instantiated themselves.
    +    assert_raises(TypeError, ISeedSequence)
    +    assert_raises(TypeError, ISpawnableSeedSequence)
    +    dummy = SeedlessSeedSequence()
    +    assert_raises(NotImplementedError, dummy.generate_state, 10)
    +    assert len(dummy.spawn(10)) == 10
    +
    +
    +def test_generator_spawning():
    +    """ Test spawning new generators and bit_generators directly.
    +    """
    +    rng = np.random.default_rng()
    +    seq = rng.bit_generator.seed_seq
    +    new_ss = seq.spawn(5)
    +    expected_keys = [seq.spawn_key + (i,) for i in range(5)]
    +    assert [c.spawn_key for c in new_ss] == expected_keys
    +
    +    new_bgs = rng.bit_generator.spawn(5)
    +    expected_keys = [seq.spawn_key + (i,) for i in range(5, 10)]
    +    assert [bg.seed_seq.spawn_key for bg in new_bgs] == expected_keys
    +
    +    new_rngs = rng.spawn(5)
    +    expected_keys = [seq.spawn_key + (i,) for i in range(10, 15)]
    +    found_keys = [rng.bit_generator.seed_seq.spawn_key for rng in new_rngs]
    +    assert found_keys == expected_keys
    +
    +    # Sanity check that streams are actually different:
    +    assert new_rngs[0].uniform() != new_rngs[1].uniform()
    +
    +
    +def test_non_spawnable():
    +    from numpy.random.bit_generator import ISeedSequence
    +
    +    class FakeSeedSequence:
    +        def generate_state(self, n_words, dtype=np.uint32):
    +            return np.zeros(n_words, dtype=dtype)
    +
    +    ISeedSequence.register(FakeSeedSequence)
    +
    +    rng = np.random.default_rng(FakeSeedSequence())
    +
    +    with pytest.raises(TypeError, match="The underlying SeedSequence"):
    +        rng.spawn(5)
    +
    +    with pytest.raises(TypeError, match="The underlying SeedSequence"):
    +        rng.bit_generator.spawn(5)
    +
    +
    +class Base:
    +    dtype = np.uint64
    +    data2 = data1 = {}
    +
    +    @classmethod
    +    def setup_class(cls):
    +        cls.bit_generator = PCG64
    +        cls.bits = 64
    +        cls.dtype = np.uint64
    +        cls.seed_error_type = TypeError
    +        cls.invalid_init_types = []
    +        cls.invalid_init_values = []
    +
    +    @classmethod
    +    def _read_csv(cls, filename):
    +        with open(filename) as csv:
    +            seed = csv.readline()
    +            seed = seed.split(',')
    +            seed = [int(s.strip(), 0) for s in seed[1:]]
    +            data = []
    +            for line in csv:
    +                data.append(int(line.split(',')[-1].strip(), 0))
    +            return {'seed': seed, 'data': np.array(data, dtype=cls.dtype)}
    +
    +    def test_raw(self):
    +        bit_generator = self.bit_generator(*self.data1['seed'])
    +        uints = bit_generator.random_raw(1000)
    +        assert_equal(uints, self.data1['data'])
    +
    +        bit_generator = self.bit_generator(*self.data1['seed'])
    +        uints = bit_generator.random_raw()
    +        assert_equal(uints, self.data1['data'][0])
    +
    +        bit_generator = self.bit_generator(*self.data2['seed'])
    +        uints = bit_generator.random_raw(1000)
    +        assert_equal(uints, self.data2['data'])
    +
    +    def test_random_raw(self):
    +        bit_generator = self.bit_generator(*self.data1['seed'])
    +        uints = bit_generator.random_raw(output=False)
    +        assert uints is None
    +        uints = bit_generator.random_raw(1000, output=False)
    +        assert uints is None
    +
    +    def test_gauss_inv(self):
    +        n = 25
    +        rs = RandomState(self.bit_generator(*self.data1['seed']))
    +        gauss = rs.standard_normal(n)
    +        assert_allclose(gauss,
    +                        gauss_from_uint(self.data1['data'], n, self.bits))
    +
    +        rs = RandomState(self.bit_generator(*self.data2['seed']))
    +        gauss = rs.standard_normal(25)
    +        assert_allclose(gauss,
    +                        gauss_from_uint(self.data2['data'], n, self.bits))
    +
    +    def test_uniform_double(self):
    +        rs = Generator(self.bit_generator(*self.data1['seed']))
    +        vals = uniform_from_uint(self.data1['data'], self.bits)
    +        uniforms = rs.random(len(vals))
    +        assert_allclose(uniforms, vals)
    +        assert_equal(uniforms.dtype, np.float64)
    +
    +        rs = Generator(self.bit_generator(*self.data2['seed']))
    +        vals = uniform_from_uint(self.data2['data'], self.bits)
    +        uniforms = rs.random(len(vals))
    +        assert_allclose(uniforms, vals)
    +        assert_equal(uniforms.dtype, np.float64)
    +
    +    def test_uniform_float(self):
    +        rs = Generator(self.bit_generator(*self.data1['seed']))
    +        vals = uniform32_from_uint(self.data1['data'], self.bits)
    +        uniforms = rs.random(len(vals), dtype=np.float32)
    +        assert_allclose(uniforms, vals)
    +        assert_equal(uniforms.dtype, np.float32)
    +
    +        rs = Generator(self.bit_generator(*self.data2['seed']))
    +        vals = uniform32_from_uint(self.data2['data'], self.bits)
    +        uniforms = rs.random(len(vals), dtype=np.float32)
    +        assert_allclose(uniforms, vals)
    +        assert_equal(uniforms.dtype, np.float32)
    +
    +    def test_repr(self):
    +        rs = Generator(self.bit_generator(*self.data1['seed']))
    +        assert 'Generator' in repr(rs)
    +        assert f'{id(rs):#x}'.upper().replace('X', 'x') in repr(rs)
    +
    +    def test_str(self):
    +        rs = Generator(self.bit_generator(*self.data1['seed']))
    +        assert 'Generator' in str(rs)
    +        assert str(self.bit_generator.__name__) in str(rs)
    +        assert f'{id(rs):#x}'.upper().replace('X', 'x') not in str(rs)
    +
    +    def test_pickle(self):
    +        import pickle
    +
    +        bit_generator = self.bit_generator(*self.data1['seed'])
    +        state = bit_generator.state
    +        bitgen_pkl = pickle.dumps(bit_generator)
    +        reloaded = pickle.loads(bitgen_pkl)
    +        reloaded_state = reloaded.state
    +        assert_array_equal(Generator(bit_generator).standard_normal(1000),
    +                           Generator(reloaded).standard_normal(1000))
    +        assert bit_generator is not reloaded
    +        assert_state_equal(reloaded_state, state)
    +
    +        ss = SeedSequence(100)
    +        aa = pickle.loads(pickle.dumps(ss))
    +        assert_equal(ss.state, aa.state)
    +
    +    def test_pickle_preserves_seed_sequence(self):
    +        # GH 26234
    +        # Add explicit test that bit generators preserve seed sequences
    +        import pickle
    +
    +        bit_generator = self.bit_generator(*self.data1['seed'])
    +        ss = bit_generator.seed_seq
    +        bg_plk = pickle.loads(pickle.dumps(bit_generator))
    +        ss_plk = bg_plk.seed_seq
    +        assert_equal(ss.state, ss_plk.state)
    +        assert_equal(ss.pool, ss_plk.pool)
    +
    +        bit_generator.seed_seq.spawn(10)
    +        bg_plk = pickle.loads(pickle.dumps(bit_generator))
    +        ss_plk = bg_plk.seed_seq
    +        assert_equal(ss.state, ss_plk.state)
    +        assert_equal(ss.n_children_spawned, ss_plk.n_children_spawned)
    +
    +    def test_invalid_state_type(self):
    +        bit_generator = self.bit_generator(*self.data1['seed'])
    +        with pytest.raises(TypeError):
    +            bit_generator.state = {'1'}
    +
    +    def test_invalid_state_value(self):
    +        bit_generator = self.bit_generator(*self.data1['seed'])
    +        state = bit_generator.state
    +        state['bit_generator'] = 'otherBitGenerator'
    +        with pytest.raises(ValueError):
    +            bit_generator.state = state
    +
    +    def test_invalid_init_type(self):
    +        bit_generator = self.bit_generator
    +        for st in self.invalid_init_types:
    +            with pytest.raises(TypeError):
    +                bit_generator(*st)
    +
    +    def test_invalid_init_values(self):
    +        bit_generator = self.bit_generator
    +        for st in self.invalid_init_values:
    +            with pytest.raises((ValueError, OverflowError)):
    +                bit_generator(*st)
    +
    +    def test_benchmark(self):
    +        bit_generator = self.bit_generator(*self.data1['seed'])
    +        bit_generator._benchmark(1)
    +        bit_generator._benchmark(1, 'double')
    +        with pytest.raises(ValueError):
    +            bit_generator._benchmark(1, 'int32')
    +
    +    @pytest.mark.skipif(MISSING_CFFI, reason='cffi not available')
    +    def test_cffi(self):
    +        bit_generator = self.bit_generator(*self.data1['seed'])
    +        cffi_interface = bit_generator.cffi
    +        assert isinstance(cffi_interface, interface)
    +        other_cffi_interface = bit_generator.cffi
    +        assert other_cffi_interface is cffi_interface
    +
    +    @pytest.mark.skipif(MISSING_CTYPES, reason='ctypes not available')
    +    def test_ctypes(self):
    +        bit_generator = self.bit_generator(*self.data1['seed'])
    +        ctypes_interface = bit_generator.ctypes
    +        assert isinstance(ctypes_interface, interface)
    +        other_ctypes_interface = bit_generator.ctypes
    +        assert other_ctypes_interface is ctypes_interface
    +
    +    def test_getstate(self):
    +        bit_generator = self.bit_generator(*self.data1['seed'])
    +        state = bit_generator.state
    +        alt_state = bit_generator.__getstate__()
    +        assert isinstance(alt_state, tuple)
    +        assert_state_equal(state, alt_state[0])
    +        assert isinstance(alt_state[1], SeedSequence)
    +
    +class TestPhilox(Base):
    +    @classmethod
    +    def setup_class(cls):
    +        cls.bit_generator = Philox
    +        cls.bits = 64
    +        cls.dtype = np.uint64
    +        cls.data1 = cls._read_csv(
    +            join(pwd, './data/philox-testset-1.csv'))
    +        cls.data2 = cls._read_csv(
    +            join(pwd, './data/philox-testset-2.csv'))
    +        cls.seed_error_type = TypeError
    +        cls.invalid_init_types = []
    +        cls.invalid_init_values = [(1, None, 1), (-1,), (None, None, 2 ** 257 + 1)]
    +
    +    def test_set_key(self):
    +        bit_generator = self.bit_generator(*self.data1['seed'])
    +        state = bit_generator.state
    +        keyed = self.bit_generator(counter=state['state']['counter'],
    +                                   key=state['state']['key'])
    +        assert_state_equal(bit_generator.state, keyed.state)
    +
    +
    +class TestPCG64(Base):
    +    @classmethod
    +    def setup_class(cls):
    +        cls.bit_generator = PCG64
    +        cls.bits = 64
    +        cls.dtype = np.uint64
    +        cls.data1 = cls._read_csv(join(pwd, './data/pcg64-testset-1.csv'))
    +        cls.data2 = cls._read_csv(join(pwd, './data/pcg64-testset-2.csv'))
    +        cls.seed_error_type = (ValueError, TypeError)
    +        cls.invalid_init_types = [(3.2,), ([None],), (1, None)]
    +        cls.invalid_init_values = [(-1,)]
    +
    +    def test_advance_symmetry(self):
    +        rs = Generator(self.bit_generator(*self.data1['seed']))
    +        state = rs.bit_generator.state
    +        step = -0x9e3779b97f4a7c150000000000000000
    +        rs.bit_generator.advance(step)
    +        val_neg = rs.integers(10)
    +        rs.bit_generator.state = state
    +        rs.bit_generator.advance(2**128 + step)
    +        val_pos = rs.integers(10)
    +        rs.bit_generator.state = state
    +        rs.bit_generator.advance(10 * 2**128 + step)
    +        val_big = rs.integers(10)
    +        assert val_neg == val_pos
    +        assert val_big == val_pos
    +
    +    def test_advange_large(self):
    +        rs = Generator(self.bit_generator(38219308213743))
    +        pcg = rs.bit_generator
    +        state = pcg.state["state"]
    +        initial_state = 287608843259529770491897792873167516365
    +        assert state["state"] == initial_state
    +        pcg.advance(sum(2**i for i in (96, 64, 32, 16, 8, 4, 2, 1)))
    +        state = pcg.state["state"]
    +        advanced_state = 135275564607035429730177404003164635391
    +        assert state["state"] == advanced_state
    +
    +
    +class TestPCG64DXSM(Base):
    +    @classmethod
    +    def setup_class(cls):
    +        cls.bit_generator = PCG64DXSM
    +        cls.bits = 64
    +        cls.dtype = np.uint64
    +        cls.data1 = cls._read_csv(join(pwd, './data/pcg64dxsm-testset-1.csv'))
    +        cls.data2 = cls._read_csv(join(pwd, './data/pcg64dxsm-testset-2.csv'))
    +        cls.seed_error_type = (ValueError, TypeError)
    +        cls.invalid_init_types = [(3.2,), ([None],), (1, None)]
    +        cls.invalid_init_values = [(-1,)]
    +
    +    def test_advance_symmetry(self):
    +        rs = Generator(self.bit_generator(*self.data1['seed']))
    +        state = rs.bit_generator.state
    +        step = -0x9e3779b97f4a7c150000000000000000
    +        rs.bit_generator.advance(step)
    +        val_neg = rs.integers(10)
    +        rs.bit_generator.state = state
    +        rs.bit_generator.advance(2**128 + step)
    +        val_pos = rs.integers(10)
    +        rs.bit_generator.state = state
    +        rs.bit_generator.advance(10 * 2**128 + step)
    +        val_big = rs.integers(10)
    +        assert val_neg == val_pos
    +        assert val_big == val_pos
    +
    +    def test_advange_large(self):
    +        rs = Generator(self.bit_generator(38219308213743))
    +        pcg = rs.bit_generator
    +        state = pcg.state
    +        initial_state = 287608843259529770491897792873167516365
    +        assert state["state"]["state"] == initial_state
    +        pcg.advance(sum(2**i for i in (96, 64, 32, 16, 8, 4, 2, 1)))
    +        state = pcg.state["state"]
    +        advanced_state = 277778083536782149546677086420637664879
    +        assert state["state"] == advanced_state
    +
    +
    +class TestMT19937(Base):
    +    @classmethod
    +    def setup_class(cls):
    +        cls.bit_generator = MT19937
    +        cls.bits = 32
    +        cls.dtype = np.uint32
    +        cls.data1 = cls._read_csv(join(pwd, './data/mt19937-testset-1.csv'))
    +        cls.data2 = cls._read_csv(join(pwd, './data/mt19937-testset-2.csv'))
    +        cls.seed_error_type = ValueError
    +        cls.invalid_init_types = []
    +        cls.invalid_init_values = [(-1,)]
    +
    +    def test_seed_float_array(self):
    +        assert_raises(TypeError, self.bit_generator, np.array([np.pi]))
    +        assert_raises(TypeError, self.bit_generator, np.array([-np.pi]))
    +        assert_raises(TypeError, self.bit_generator, np.array([np.pi, -np.pi]))
    +        assert_raises(TypeError, self.bit_generator, np.array([0, np.pi]))
    +        assert_raises(TypeError, self.bit_generator, [np.pi])
    +        assert_raises(TypeError, self.bit_generator, [0, np.pi])
    +
    +    def test_state_tuple(self):
    +        rs = Generator(self.bit_generator(*self.data1['seed']))
    +        bit_generator = rs.bit_generator
    +        state = bit_generator.state
    +        desired = rs.integers(2 ** 16)
    +        tup = (state['bit_generator'], state['state']['key'],
    +               state['state']['pos'])
    +        bit_generator.state = tup
    +        actual = rs.integers(2 ** 16)
    +        assert_equal(actual, desired)
    +        tup = tup + (0, 0.0)
    +        bit_generator.state = tup
    +        actual = rs.integers(2 ** 16)
    +        assert_equal(actual, desired)
    +
    +
    +class TestSFC64(Base):
    +    @classmethod
    +    def setup_class(cls):
    +        cls.bit_generator = SFC64
    +        cls.bits = 64
    +        cls.dtype = np.uint64
    +        cls.data1 = cls._read_csv(
    +            join(pwd, './data/sfc64-testset-1.csv'))
    +        cls.data2 = cls._read_csv(
    +            join(pwd, './data/sfc64-testset-2.csv'))
    +        cls.seed_error_type = (ValueError, TypeError)
    +        cls.invalid_init_types = [(3.2,), ([None],), (1, None)]
    +        cls.invalid_init_values = [(-1,)]
    +
    +    def test_legacy_pickle(self):
    +        # Pickling format was changed in 2.0.x
    +        import gzip
    +        import pickle
    +
    +        expected_state = np.array(
    +            [
    +                9957867060933711493,
    +                532597980065565856,
    +                14769588338631205282,
    +                13
    +            ],
    +            dtype=np.uint64
    +        )
    +
    +        base_path = os.path.split(os.path.abspath(__file__))[0]
    +        pkl_file = os.path.join(base_path, "data", "sfc64_np126.pkl.gz")
    +        with gzip.open(pkl_file) as gz:
    +            sfc = pickle.load(gz)
    +
    +        assert isinstance(sfc, SFC64)
    +        assert_equal(sfc.state["state"]["state"], expected_state)
    +
    +
    +class TestDefaultRNG:
    +    def test_seed(self):
    +        for args in [(), (None,), (1234,), ([1234, 5678],)]:
    +            rg = default_rng(*args)
    +            assert isinstance(rg.bit_generator, PCG64)
    +
    +    def test_passthrough(self):
    +        bg = Philox()
    +        rg = default_rng(bg)
    +        assert rg.bit_generator is bg
    +        rg2 = default_rng(rg)
    +        assert rg2 is rg
    +        assert rg2.bit_generator is bg
    +
    +    @pytest.mark.thread_unsafe(
    +        reason="np.random.set_bit_generator affects global state"
    +    )
    +    def test_coercion_RandomState_Generator(self):
    +        # use default_rng to coerce RandomState to Generator
    +        rs = RandomState(1234)
    +        rg = default_rng(rs)
    +        assert isinstance(rg.bit_generator, MT19937)
    +        assert rg.bit_generator is rs._bit_generator
    +
    +        # RandomState with a non MT19937 bit generator
    +        _original = np.random.get_bit_generator()
    +        bg = PCG64(12342298)
    +        np.random.set_bit_generator(bg)
    +        rs = np.random.mtrand._rand
    +        rg = default_rng(rs)
    +        assert rg.bit_generator is bg
    +
    +        # vital to get global state back to original, otherwise
    +        # other tests start to fail.
    +        np.random.set_bit_generator(_original)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/test_extending.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/test_extending.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..a1e64ecbe343d88bc9678d16387dc13493672009
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/test_extending.py
    @@ -0,0 +1,131 @@
    +import os
    +import shutil
    +import subprocess
    +import sys
    +import sysconfig
    +import warnings
    +from importlib.util import module_from_spec, spec_from_file_location
    +
    +import pytest
    +
    +import numpy as np
    +from numpy.testing import IS_EDITABLE, IS_WASM
    +
    +try:
    +    import cffi
    +except ImportError:
    +    cffi = None
    +
    +if sys.flags.optimize > 1:
    +    # no docstrings present to inspect when PYTHONOPTIMIZE/Py_OptimizeFlag > 1
    +    # cffi cannot succeed
    +    cffi = None
    +
    +try:
    +    with warnings.catch_warnings(record=True) as w:
    +        # numba issue gh-4733
    +        warnings.filterwarnings('always', '', DeprecationWarning)
    +        import numba
    +except (ImportError, SystemError):
    +    # Certain numpy/numba versions trigger a SystemError due to a numba bug
    +    numba = None
    +
    +try:
    +    import cython
    +    from Cython.Compiler.Version import version as cython_version
    +except ImportError:
    +    cython = None
    +else:
    +    from numpy._utils import _pep440
    +    # Note: keep in sync with the one in pyproject.toml
    +    required_version = '3.0.6'
    +    if _pep440.parse(cython_version) < _pep440.Version(required_version):
    +        # too old or wrong cython, skip the test
    +        cython = None
    +
    +
    +@pytest.mark.skipif(
    +    IS_EDITABLE,
    +    reason='Editable install cannot find .pxd headers'
    +)
    +@pytest.mark.skipif(
    +        sys.platform == "win32" and sys.maxsize < 2**32,
    +        reason="Failing in 32-bit Windows wheel build job, skip for now"
    +)
    +@pytest.mark.skipif(IS_WASM, reason="Can't start subprocess")
    +@pytest.mark.skipif(cython is None, reason="requires cython")
    +@pytest.mark.skipif(sysconfig.get_platform() == 'win-arm64',
    +                    reason='Meson unable to find MSVC linker on win-arm64')
    +@pytest.mark.slow
    +@pytest.mark.thread_unsafe(
    +    reason="building cython code in a subprocess doesn't make sense to do in many "
    +           "threads and sometimes crashes"
    +)
    +def test_cython(tmp_path):
    +    import glob
    +    # build the examples in a temporary directory
    +    srcdir = os.path.join(os.path.dirname(__file__), '..')
    +    shutil.copytree(srcdir, tmp_path / 'random')
    +    build_dir = tmp_path / 'random' / '_examples' / 'cython'
    +    target_dir = build_dir / "build"
    +    os.makedirs(target_dir, exist_ok=True)
    +    # Ensure we use the correct Python interpreter even when `meson` is
    +    # installed in a different Python environment (see gh-24956)
    +    native_file = str(build_dir / 'interpreter-native-file.ini')
    +    with open(native_file, 'w') as f:
    +        f.write("[binaries]\n")
    +        f.write(f"python = '{sys.executable}'\n")
    +        f.write(f"python3 = '{sys.executable}'")
    +    if sys.platform == "win32":
    +        subprocess.check_call(["meson", "setup",
    +                               "--buildtype=release",
    +                               "--vsenv", "--native-file", native_file,
    +                               str(build_dir)],
    +                              cwd=target_dir,
    +                              )
    +    else:
    +        subprocess.check_call(["meson", "setup",
    +                               "--native-file", native_file, str(build_dir)],
    +                              cwd=target_dir
    +                              )
    +    subprocess.check_call(["meson", "compile", "-vv"], cwd=target_dir)
    +
    +    # gh-16162: make sure numpy's __init__.pxd was used for cython
    +    # not really part of this test, but it is a convenient place to check
    +
    +    g = glob.glob(str(target_dir / "*" / "extending.pyx.c"))
    +    with open(g[0]) as fid:
    +        txt_to_find = 'NumPy API declarations from "numpy/__init__'
    +        for line in fid:
    +            if txt_to_find in line:
    +                break
    +        else:
    +            assert False, f"Could not find '{txt_to_find}' in C file, wrong pxd used"
    +    # import without adding the directory to sys.path
    +    suffix = sysconfig.get_config_var('EXT_SUFFIX')
    +
    +    def load(modname):
    +        so = (target_dir / modname).with_suffix(suffix)
    +        spec = spec_from_file_location(modname, so)
    +        mod = module_from_spec(spec)
    +        spec.loader.exec_module(mod)
    +        return mod
    +
    +    # test that the module can be imported
    +    load("extending")
    +    load("extending_cpp")
    +    # actually test the cython c-extension
    +    extending_distributions = load("extending_distributions")
    +    from numpy.random import PCG64
    +    values = extending_distributions.uniforms_ex(PCG64(0), 10, 'd')
    +    assert values.shape == (10,)
    +    assert values.dtype == np.float64
    +
    +@pytest.mark.skipif(numba is None or cffi is None,
    +                    reason="requires numba and cffi")
    +def test_numba():
    +    from numpy.random._examples.numba import extending  # noqa: F401
    +
    +@pytest.mark.skipif(cffi is None, reason="requires cffi")
    +def test_cffi():
    +    from numpy.random._examples.cffi import extending  # noqa: F401
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/test_generator_mt19937.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/test_generator_mt19937.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..7d13c49149b36cfa81bb099137ff72ad89dc461f
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/test_generator_mt19937.py
    @@ -0,0 +1,2825 @@
    +import hashlib
    +import os.path
    +import sys
    +import warnings
    +
    +import pytest
    +
    +import numpy as np
    +from numpy.exceptions import AxisError
    +from numpy.linalg import LinAlgError
    +from numpy.random import MT19937, Generator, RandomState, SeedSequence
    +from numpy.testing import (
    +    IS_WASM,
    +    assert_,
    +    assert_allclose,
    +    assert_array_almost_equal,
    +    assert_array_equal,
    +    assert_equal,
    +    assert_no_warnings,
    +    assert_raises,
    +)
    +
    +random = Generator(MT19937())
    +
    +JUMP_TEST_DATA = [
    +    {
    +        "seed": 0,
    +        "steps": 10,
    +        "initial": {"key_sha256": "bb1636883c2707b51c5b7fc26c6927af4430f2e0785a8c7bc886337f919f9edf", "pos": 9},    # noqa: E501
    +        "jumped":  {"key_sha256": "ff682ac12bb140f2d72fba8d3506cf4e46817a0db27aae1683867629031d8d55", "pos": 598},  # noqa: E501
    +    },
    +    {
    +        "seed": 384908324,
    +        "steps": 312,
    +        "initial": {"key_sha256": "16b791a1e04886ccbbb4d448d6ff791267dc458ae599475d08d5cced29d11614", "pos": 311},  # noqa: E501
    +        "jumped":  {"key_sha256": "a0110a2cf23b56be0feaed8f787a7fc84bef0cb5623003d75b26bdfa1c18002c", "pos": 276},  # noqa: E501
    +    },
    +    {
    +        "seed": [839438204, 980239840, 859048019, 821],
    +        "steps": 511,
    +        "initial": {"key_sha256": "d306cf01314d51bd37892d874308200951a35265ede54d200f1e065004c3e9ea", "pos": 510},  # noqa: E501
    +        "jumped":  {"key_sha256": "0e00ab449f01a5195a83b4aee0dfbc2ce8d46466a640b92e33977d2e42f777f8", "pos": 475},  # noqa: E501
    +    },
    +]
    +
    +
    +@pytest.fixture(scope='module', params=[True, False])
    +def endpoint(request):
    +    return request.param
    +
    +
    +class TestSeed:
    +    def test_scalar(self):
    +        s = Generator(MT19937(0))
    +        assert_equal(s.integers(1000), 479)
    +        s = Generator(MT19937(4294967295))
    +        assert_equal(s.integers(1000), 324)
    +
    +    def test_array(self):
    +        s = Generator(MT19937(range(10)))
    +        assert_equal(s.integers(1000), 465)
    +        s = Generator(MT19937(np.arange(10)))
    +        assert_equal(s.integers(1000), 465)
    +        s = Generator(MT19937([0]))
    +        assert_equal(s.integers(1000), 479)
    +        s = Generator(MT19937([4294967295]))
    +        assert_equal(s.integers(1000), 324)
    +
    +    def test_seedsequence(self):
    +        s = MT19937(SeedSequence(0))
    +        assert_equal(s.random_raw(1), 2058676884)
    +
    +    def test_invalid_scalar(self):
    +        # seed must be an unsigned 32 bit integer
    +        assert_raises(TypeError, MT19937, -0.5)
    +        assert_raises(ValueError, MT19937, -1)
    +
    +    def test_invalid_array(self):
    +        # seed must be an unsigned integer
    +        assert_raises(TypeError, MT19937, [-0.5])
    +        assert_raises(ValueError, MT19937, [-1])
    +        assert_raises(ValueError, MT19937, [1, -2, 4294967296])
    +
    +    def test_noninstantized_bitgen(self):
    +        assert_raises(ValueError, Generator, MT19937)
    +
    +
    +class TestBinomial:
    +    def test_n_zero(self):
    +        # Tests the corner case of n == 0 for the binomial distribution.
    +        # binomial(0, p) should be zero for any p in [0, 1].
    +        # This test addresses issue #3480.
    +        zeros = np.zeros(2, dtype='int')
    +        for p in [0, .5, 1]:
    +            assert_(random.binomial(0, p) == 0)
    +            assert_array_equal(random.binomial(zeros, p), zeros)
    +
    +    def test_p_is_nan(self):
    +        # Issue #4571.
    +        assert_raises(ValueError, random.binomial, 1, np.nan)
    +
    +    def test_p_extremely_small(self):
    +        n = 50000000000
    +        p = 5e-17
    +        sample_size = 20000000
    +        x = random.binomial(n, p, size=sample_size)
    +        sample_mean = x.mean()
    +        expected_mean = n * p
    +        sigma = np.sqrt(n * p * (1 - p) / sample_size)
    +        # Note: the parameters were chosen so that expected_mean - 6*sigma
    +        # is a positive value.  The first `assert` below validates that
    +        # assumption (in case someone edits the parameters in the future).
    +        # The second `assert` is the actual test.
    +        low_bound = expected_mean - 6 * sigma
    +        assert low_bound > 0, "bad test params: 6-sigma lower bound is negative"
    +        test_msg = (f"sample mean {sample_mean} deviates from the expected mean "
    +                    f"{expected_mean} by more than 6*sigma")
    +        assert abs(expected_mean - sample_mean) < 6 * sigma, test_msg
    +
    +
    +class TestMultinomial:
    +    def test_basic(self):
    +        random.multinomial(100, [0.2, 0.8])
    +
    +    def test_zero_probability(self):
    +        random.multinomial(100, [0.2, 0.8, 0.0, 0.0, 0.0])
    +
    +    def test_int_negative_interval(self):
    +        assert_(-5 <= random.integers(-5, -1) < -1)
    +        x = random.integers(-5, -1, 5)
    +        assert_(np.all(-5 <= x))
    +        assert_(np.all(x < -1))
    +
    +    def test_size(self):
    +        # gh-3173
    +        p = [0.5, 0.5]
    +        assert_equal(random.multinomial(1, p, np.uint32(1)).shape, (1, 2))
    +        assert_equal(random.multinomial(1, p, np.uint32(1)).shape, (1, 2))
    +        assert_equal(random.multinomial(1, p, np.uint32(1)).shape, (1, 2))
    +        assert_equal(random.multinomial(1, p, [2, 2]).shape, (2, 2, 2))
    +        assert_equal(random.multinomial(1, p, (2, 2)).shape, (2, 2, 2))
    +        assert_equal(random.multinomial(1, p, np.array((2, 2))).shape,
    +                     (2, 2, 2))
    +
    +        assert_raises(TypeError, random.multinomial, 1, p,
    +                      float(1))
    +
    +    def test_invalid_prob(self):
    +        assert_raises(ValueError, random.multinomial, 100, [1.1, 0.2])
    +        assert_raises(ValueError, random.multinomial, 100, [-.1, 0.9])
    +
    +    def test_invalid_n(self):
    +        assert_raises(ValueError, random.multinomial, -1, [0.8, 0.2])
    +        assert_raises(ValueError, random.multinomial, [-1] * 10, [0.8, 0.2])
    +
    +    def test_p_non_contiguous(self):
    +        p = np.arange(15.)
    +        p /= np.sum(p[1::3])
    +        pvals = p[1::3]
    +        random = Generator(MT19937(1432985819))
    +        non_contig = random.multinomial(100, pvals=pvals)
    +        random = Generator(MT19937(1432985819))
    +        contig = random.multinomial(100, pvals=np.ascontiguousarray(pvals))
    +        assert_array_equal(non_contig, contig)
    +
    +    def test_multinomial_pvals_float32(self):
    +        x = np.array([9.9e-01, 9.9e-01, 1.0e-09, 1.0e-09, 1.0e-09, 1.0e-09,
    +                      1.0e-09, 1.0e-09, 1.0e-09, 1.0e-09], dtype=np.float32)
    +        pvals = x / x.sum()
    +        random = Generator(MT19937(1432985819))
    +        match = r"[\w\s]*pvals array is cast to 64-bit floating"
    +        with pytest.raises(ValueError, match=match):
    +            random.multinomial(1, pvals)
    +
    +
    +class TestMultivariateHypergeometric:
    +
    +    seed = 8675309
    +
    +    def test_argument_validation(self):
    +        # Error cases...
    +
    +        # `colors` must be a 1-d sequence
    +        assert_raises(ValueError, random.multivariate_hypergeometric,
    +                      10, 4)
    +
    +        # Negative nsample
    +        assert_raises(ValueError, random.multivariate_hypergeometric,
    +                      [2, 3, 4], -1)
    +
    +        # Negative color
    +        assert_raises(ValueError, random.multivariate_hypergeometric,
    +                      [-1, 2, 3], 2)
    +
    +        # nsample exceeds sum(colors)
    +        assert_raises(ValueError, random.multivariate_hypergeometric,
    +                      [2, 3, 4], 10)
    +
    +        # nsample exceeds sum(colors) (edge case of empty colors)
    +        assert_raises(ValueError, random.multivariate_hypergeometric,
    +                      [], 1)
    +
    +        # Validation errors associated with very large values in colors.
    +        assert_raises(ValueError, random.multivariate_hypergeometric,
    +                      [999999999, 101], 5, 1, 'marginals')
    +
    +        int64_info = np.iinfo(np.int64)
    +        max_int64 = int64_info.max
    +        max_int64_index = max_int64 // int64_info.dtype.itemsize
    +        assert_raises(ValueError, random.multivariate_hypergeometric,
    +                      [max_int64_index - 100, 101], 5, 1, 'count')
    +
    +    @pytest.mark.parametrize('method', ['count', 'marginals'])
    +    def test_edge_cases(self, method):
    +        # Set the seed, but in fact, all the results in this test are
    +        # deterministic, so we don't really need this.
    +        random = Generator(MT19937(self.seed))
    +
    +        x = random.multivariate_hypergeometric([0, 0, 0], 0, method=method)
    +        assert_array_equal(x, [0, 0, 0])
    +
    +        x = random.multivariate_hypergeometric([], 0, method=method)
    +        assert_array_equal(x, [])
    +
    +        x = random.multivariate_hypergeometric([], 0, size=1, method=method)
    +        assert_array_equal(x, np.empty((1, 0), dtype=np.int64))
    +
    +        x = random.multivariate_hypergeometric([1, 2, 3], 0, method=method)
    +        assert_array_equal(x, [0, 0, 0])
    +
    +        x = random.multivariate_hypergeometric([9, 0, 0], 3, method=method)
    +        assert_array_equal(x, [3, 0, 0])
    +
    +        colors = [1, 1, 0, 1, 1]
    +        x = random.multivariate_hypergeometric(colors, sum(colors),
    +                                               method=method)
    +        assert_array_equal(x, colors)
    +
    +        x = random.multivariate_hypergeometric([3, 4, 5], 12, size=3,
    +                                               method=method)
    +        assert_array_equal(x, [[3, 4, 5]] * 3)
    +
    +    # Cases for nsample:
    +    #     nsample < 10
    +    #     10 <= nsample < colors.sum()/2
    +    #     colors.sum()/2 < nsample < colors.sum() - 10
    +    #     colors.sum() - 10 < nsample < colors.sum()
    +    @pytest.mark.parametrize('nsample', [8, 25, 45, 55])
    +    @pytest.mark.parametrize('method', ['count', 'marginals'])
    +    @pytest.mark.parametrize('size', [5, (2, 3), 150000])
    +    def test_typical_cases(self, nsample, method, size):
    +        random = Generator(MT19937(self.seed))
    +
    +        colors = np.array([10, 5, 20, 25])
    +        sample = random.multivariate_hypergeometric(colors, nsample, size,
    +                                                    method=method)
    +        if isinstance(size, int):
    +            expected_shape = (size,) + colors.shape
    +        else:
    +            expected_shape = size + colors.shape
    +        assert_equal(sample.shape, expected_shape)
    +        assert_((sample >= 0).all())
    +        assert_((sample <= colors).all())
    +        assert_array_equal(sample.sum(axis=-1),
    +                           np.full(size, fill_value=nsample, dtype=int))
    +        if isinstance(size, int) and size >= 100000:
    +            # This sample is large enough to compare its mean to
    +            # the expected values.
    +            assert_allclose(sample.mean(axis=0),
    +                            nsample * colors / colors.sum(),
    +                            rtol=1e-3, atol=0.005)
    +
    +    def test_repeatability1(self):
    +        random = Generator(MT19937(self.seed))
    +        sample = random.multivariate_hypergeometric([3, 4, 5], 5, size=5,
    +                                                    method='count')
    +        expected = np.array([[2, 1, 2],
    +                             [2, 1, 2],
    +                             [1, 1, 3],
    +                             [2, 0, 3],
    +                             [2, 1, 2]])
    +        assert_array_equal(sample, expected)
    +
    +    def test_repeatability2(self):
    +        random = Generator(MT19937(self.seed))
    +        sample = random.multivariate_hypergeometric([20, 30, 50], 50,
    +                                                    size=5,
    +                                                    method='marginals')
    +        expected = np.array([[ 9, 17, 24],
    +                             [ 7, 13, 30],
    +                             [ 9, 15, 26],
    +                             [ 9, 17, 24],
    +                             [12, 14, 24]])
    +        assert_array_equal(sample, expected)
    +
    +    def test_repeatability3(self):
    +        random = Generator(MT19937(self.seed))
    +        sample = random.multivariate_hypergeometric([20, 30, 50], 12,
    +                                                    size=5,
    +                                                    method='marginals')
    +        expected = np.array([[2, 3, 7],
    +                             [5, 3, 4],
    +                             [2, 5, 5],
    +                             [5, 3, 4],
    +                             [1, 5, 6]])
    +        assert_array_equal(sample, expected)
    +
    +
    +class TestSetState:
    +    def _create_rng(self):
    +        seed = 1234567890
    +        rg = Generator(MT19937(seed))
    +        bit_generator = rg.bit_generator
    +        state = bit_generator.state
    +        legacy_state = (state['bit_generator'],
    +                        state['state']['key'],
    +                        state['state']['pos'])
    +        return rg, bit_generator, state
    +
    +    def test_gaussian_reset(self):
    +        # Make sure the cached every-other-Gaussian is reset.
    +        rg, bit_generator, state = self._create_rng()
    +        old = rg.standard_normal(size=3)
    +        bit_generator.state = state
    +        new = rg.standard_normal(size=3)
    +        assert_(np.all(old == new))
    +
    +    def test_gaussian_reset_in_media_res(self):
    +        # When the state is saved with a cached Gaussian, make sure the
    +        # cached Gaussian is restored.
    +        rg, bit_generator, state = self._create_rng()
    +        rg.standard_normal()
    +        state = bit_generator.state
    +        old = rg.standard_normal(size=3)
    +        bit_generator.state = state
    +        new = rg.standard_normal(size=3)
    +        assert_(np.all(old == new))
    +
    +    def test_negative_binomial(self):
    +        # Ensure that the negative binomial results take floating point
    +        # arguments without truncation.
    +        rg, _, _ = self._create_rng()
    +        rg.negative_binomial(0.5, 0.5)
    +
    +
    +class TestIntegers:
    +    rfunc = random.integers
    +
    +    # valid integer/boolean types
    +    itype = [bool, np.int8, np.uint8, np.int16, np.uint16,
    +             np.int32, np.uint32, np.int64, np.uint64]
    +
    +    def test_unsupported_type(self, endpoint):
    +        assert_raises(TypeError, self.rfunc, 1, endpoint=endpoint, dtype=float)
    +
    +    def test_bounds_checking(self, endpoint):
    +        for dt in self.itype:
    +            lbnd = 0 if dt is bool else np.iinfo(dt).min
    +            ubnd = 2 if dt is bool else np.iinfo(dt).max + 1
    +            ubnd = ubnd - 1 if endpoint else ubnd
    +            assert_raises(ValueError, self.rfunc, lbnd - 1, ubnd,
    +                          endpoint=endpoint, dtype=dt)
    +            assert_raises(ValueError, self.rfunc, lbnd, ubnd + 1,
    +                          endpoint=endpoint, dtype=dt)
    +            assert_raises(ValueError, self.rfunc, ubnd, lbnd,
    +                          endpoint=endpoint, dtype=dt)
    +            assert_raises(ValueError, self.rfunc, 1, 0, endpoint=endpoint,
    +                          dtype=dt)
    +
    +            assert_raises(ValueError, self.rfunc, [lbnd - 1], ubnd,
    +                          endpoint=endpoint, dtype=dt)
    +            assert_raises(ValueError, self.rfunc, [lbnd], [ubnd + 1],
    +                          endpoint=endpoint, dtype=dt)
    +            assert_raises(ValueError, self.rfunc, [ubnd], [lbnd],
    +                          endpoint=endpoint, dtype=dt)
    +            assert_raises(ValueError, self.rfunc, 1, [0],
    +                          endpoint=endpoint, dtype=dt)
    +            assert_raises(ValueError, self.rfunc, [ubnd + 1], [ubnd],
    +                          endpoint=endpoint, dtype=dt)
    +
    +    def test_bounds_checking_array(self, endpoint):
    +        for dt in self.itype:
    +            lbnd = 0 if dt is bool else np.iinfo(dt).min
    +            ubnd = 2 if dt is bool else np.iinfo(dt).max + (not endpoint)
    +
    +            assert_raises(ValueError, self.rfunc, [lbnd - 1] * 2, [ubnd] * 2,
    +                          endpoint=endpoint, dtype=dt)
    +            assert_raises(ValueError, self.rfunc, [lbnd] * 2,
    +                          [ubnd + 1] * 2, endpoint=endpoint, dtype=dt)
    +            assert_raises(ValueError, self.rfunc, ubnd, [lbnd] * 2,
    +                          endpoint=endpoint, dtype=dt)
    +            assert_raises(ValueError, self.rfunc, [1] * 2, 0,
    +                          endpoint=endpoint, dtype=dt)
    +
    +    def test_rng_zero_and_extremes(self, endpoint):
    +        for dt in self.itype:
    +            lbnd = 0 if dt is bool else np.iinfo(dt).min
    +            ubnd = 2 if dt is bool else np.iinfo(dt).max + 1
    +            ubnd = ubnd - 1 if endpoint else ubnd
    +            is_open = not endpoint
    +
    +            tgt = ubnd - 1
    +            assert_equal(self.rfunc(tgt, tgt + is_open, size=1000,
    +                                    endpoint=endpoint, dtype=dt), tgt)
    +            assert_equal(self.rfunc([tgt], tgt + is_open, size=1000,
    +                                    endpoint=endpoint, dtype=dt), tgt)
    +
    +            tgt = lbnd
    +            assert_equal(self.rfunc(tgt, tgt + is_open, size=1000,
    +                                    endpoint=endpoint, dtype=dt), tgt)
    +            assert_equal(self.rfunc(tgt, [tgt + is_open], size=1000,
    +                                    endpoint=endpoint, dtype=dt), tgt)
    +
    +            tgt = (lbnd + ubnd) // 2
    +            assert_equal(self.rfunc(tgt, tgt + is_open, size=1000,
    +                                    endpoint=endpoint, dtype=dt), tgt)
    +            assert_equal(self.rfunc([tgt], [tgt + is_open],
    +                                    size=1000, endpoint=endpoint, dtype=dt),
    +                         tgt)
    +
    +    def test_rng_zero_and_extremes_array(self, endpoint):
    +        size = 1000
    +        for dt in self.itype:
    +            lbnd = 0 if dt is bool else np.iinfo(dt).min
    +            ubnd = 2 if dt is bool else np.iinfo(dt).max + 1
    +            ubnd = ubnd - 1 if endpoint else ubnd
    +
    +            tgt = ubnd - 1
    +            assert_equal(self.rfunc([tgt], [tgt + 1],
    +                                    size=size, dtype=dt), tgt)
    +            assert_equal(self.rfunc(
    +                [tgt] * size, [tgt + 1] * size, dtype=dt), tgt)
    +            assert_equal(self.rfunc(
    +                [tgt] * size, [tgt + 1] * size, size=size, dtype=dt), tgt)
    +
    +            tgt = lbnd
    +            assert_equal(self.rfunc([tgt], [tgt + 1],
    +                                    size=size, dtype=dt), tgt)
    +            assert_equal(self.rfunc(
    +                [tgt] * size, [tgt + 1] * size, dtype=dt), tgt)
    +            assert_equal(self.rfunc(
    +                [tgt] * size, [tgt + 1] * size, size=size, dtype=dt), tgt)
    +
    +            tgt = (lbnd + ubnd) // 2
    +            assert_equal(self.rfunc([tgt], [tgt + 1],
    +                                    size=size, dtype=dt), tgt)
    +            assert_equal(self.rfunc(
    +                [tgt] * size, [tgt + 1] * size, dtype=dt), tgt)
    +            assert_equal(self.rfunc(
    +                [tgt] * size, [tgt + 1] * size, size=size, dtype=dt), tgt)
    +
    +    def test_full_range(self, endpoint):
    +        # Test for ticket #1690
    +
    +        for dt in self.itype:
    +            lbnd = 0 if dt is bool else np.iinfo(dt).min
    +            ubnd = 2 if dt is bool else np.iinfo(dt).max + 1
    +            ubnd = ubnd - 1 if endpoint else ubnd
    +
    +            try:
    +                self.rfunc(lbnd, ubnd, endpoint=endpoint, dtype=dt)
    +            except Exception as e:
    +                raise AssertionError("No error should have been raised, "
    +                                     "but one was with the following "
    +                                     "message:\n\n%s" % str(e))
    +
    +    def test_full_range_array(self, endpoint):
    +        # Test for ticket #1690
    +
    +        for dt in self.itype:
    +            lbnd = 0 if dt is bool else np.iinfo(dt).min
    +            ubnd = 2 if dt is bool else np.iinfo(dt).max + 1
    +            ubnd = ubnd - 1 if endpoint else ubnd
    +
    +            try:
    +                self.rfunc([lbnd] * 2, [ubnd], endpoint=endpoint, dtype=dt)
    +            except Exception as e:
    +                raise AssertionError("No error should have been raised, "
    +                                     "but one was with the following "
    +                                     "message:\n\n%s" % str(e))
    +
    +    def test_in_bounds_fuzz(self, endpoint):
    +        # Don't use fixed seed
    +        random = Generator(MT19937())
    +
    +        for dt in self.itype[1:]:
    +            for ubnd in [4, 8, 16]:
    +                vals = self.rfunc(2, ubnd - endpoint, size=2 ** 16,
    +                                  endpoint=endpoint, dtype=dt)
    +                assert_(vals.max() < ubnd)
    +                assert_(vals.min() >= 2)
    +
    +        vals = self.rfunc(0, 2 - endpoint, size=2 ** 16, endpoint=endpoint,
    +                          dtype=bool)
    +        assert_(vals.max() < 2)
    +        assert_(vals.min() >= 0)
    +
    +    def test_scalar_array_equiv(self, endpoint):
    +        for dt in self.itype:
    +            lbnd = 0 if dt is bool else np.iinfo(dt).min
    +            ubnd = 2 if dt is bool else np.iinfo(dt).max + 1
    +            ubnd = ubnd - 1 if endpoint else ubnd
    +
    +            size = 1000
    +            random = Generator(MT19937(1234))
    +            scalar = random.integers(lbnd, ubnd, size=size, endpoint=endpoint,
    +                                dtype=dt)
    +
    +            random = Generator(MT19937(1234))
    +            scalar_array = random.integers([lbnd], [ubnd], size=size,
    +                                      endpoint=endpoint, dtype=dt)
    +
    +            random = Generator(MT19937(1234))
    +            array = random.integers([lbnd] * size, [ubnd] *
    +                               size, size=size, endpoint=endpoint, dtype=dt)
    +            assert_array_equal(scalar, scalar_array)
    +            assert_array_equal(scalar, array)
    +
    +    def test_repeatability(self, endpoint):
    +        # We use a sha256 hash of generated sequences of 1000 samples
    +        # in the range [0, 6) for all but bool, where the range
    +        # is [0, 2). Hashes are for little endian numbers.
    +        tgt = {'bool':   '053594a9b82d656f967c54869bc6970aa0358cf94ad469c81478459c6a90eee3',  # noqa: E501
    +               'int16':  '54de9072b6ee9ff7f20b58329556a46a447a8a29d67db51201bf88baa6e4e5d4',  # noqa: E501
    +               'int32':  'd3a0d5efb04542b25ac712e50d21f39ac30f312a5052e9bbb1ad3baa791ac84b',  # noqa: E501
    +               'int64':  '14e224389ac4580bfbdccb5697d6190b496f91227cf67df60989de3d546389b1',  # noqa: E501
    +               'int8':   '0e203226ff3fbbd1580f15da4621e5f7164d0d8d6b51696dd42d004ece2cbec1',  # noqa: E501
    +               'uint16': '54de9072b6ee9ff7f20b58329556a46a447a8a29d67db51201bf88baa6e4e5d4',  # noqa: E501
    +               'uint32': 'd3a0d5efb04542b25ac712e50d21f39ac30f312a5052e9bbb1ad3baa791ac84b',  # noqa: E501
    +               'uint64': '14e224389ac4580bfbdccb5697d6190b496f91227cf67df60989de3d546389b1',  # noqa: E501
    +               'uint8':  '0e203226ff3fbbd1580f15da4621e5f7164d0d8d6b51696dd42d004ece2cbec1'}  # noqa: E501
    +
    +        for dt in self.itype[1:]:
    +            random = Generator(MT19937(1234))
    +
    +            # view as little endian for hash
    +            if sys.byteorder == 'little':
    +                val = random.integers(0, 6 - endpoint, size=1000, endpoint=endpoint,
    +                                 dtype=dt)
    +            else:
    +                val = random.integers(0, 6 - endpoint, size=1000, endpoint=endpoint,
    +                                 dtype=dt).byteswap()
    +
    +            res = hashlib.sha256(val).hexdigest()
    +            assert_(tgt[np.dtype(dt).name] == res)
    +
    +        # bools do not depend on endianness
    +        random = Generator(MT19937(1234))
    +        val = random.integers(0, 2 - endpoint, size=1000, endpoint=endpoint,
    +                         dtype=bool).view(np.int8)
    +        res = hashlib.sha256(val).hexdigest()
    +        assert_(tgt[np.dtype(bool).name] == res)
    +
    +    def test_repeatability_broadcasting(self, endpoint):
    +        for dt in self.itype:
    +            lbnd = 0 if dt in (bool, np.bool) else np.iinfo(dt).min
    +            ubnd = 2 if dt in (bool, np.bool) else np.iinfo(dt).max + 1
    +            ubnd = ubnd - 1 if endpoint else ubnd
    +
    +            # view as little endian for hash
    +            random = Generator(MT19937(1234))
    +            val = random.integers(lbnd, ubnd, size=1000, endpoint=endpoint,
    +                             dtype=dt)
    +
    +            random = Generator(MT19937(1234))
    +            val_bc = random.integers([lbnd] * 1000, ubnd, endpoint=endpoint,
    +                                dtype=dt)
    +
    +            assert_array_equal(val, val_bc)
    +
    +            random = Generator(MT19937(1234))
    +            val_bc = random.integers([lbnd] * 1000, [ubnd] * 1000,
    +                                endpoint=endpoint, dtype=dt)
    +
    +            assert_array_equal(val, val_bc)
    +
    +    @pytest.mark.parametrize(
    +        'bound, expected',
    +        [(2**32 - 1, np.array([517043486, 1364798665, 1733884389, 1353720612,
    +                               3769704066, 1170797179, 4108474671])),
    +         (2**32, np.array([517043487, 1364798666, 1733884390, 1353720613,
    +                           3769704067, 1170797180, 4108474672])),
    +         (2**32 + 1, np.array([517043487, 1733884390, 3769704068, 4108474673,
    +                               1831631863, 1215661561, 3869512430]))]
    +    )
    +    def test_repeatability_32bit_boundary(self, bound, expected):
    +        for size in [None, len(expected)]:
    +            random = Generator(MT19937(1234))
    +            x = random.integers(bound, size=size)
    +            assert_equal(x, expected if size is not None else expected[0])
    +
    +    def test_repeatability_32bit_boundary_broadcasting(self):
    +        desired = np.array([[[1622936284, 3620788691, 1659384060],
    +                             [1417365545,  760222891, 1909653332],
    +                             [3788118662,  660249498, 4092002593]],
    +                            [[3625610153, 2979601262, 3844162757],
    +                             [ 685800658,  120261497, 2694012896],
    +                             [1207779440, 1586594375, 3854335050]],
    +                            [[3004074748, 2310761796, 3012642217],
    +                             [2067714190, 2786677879, 1363865881],
    +                             [ 791663441, 1867303284, 2169727960]],
    +                            [[1939603804, 1250951100,  298950036],
    +                             [1040128489, 3791912209, 3317053765],
    +                             [3155528714,   61360675, 2305155588]],
    +                            [[ 817688762, 1335621943, 3288952434],
    +                             [1770890872, 1102951817, 1957607470],
    +                             [3099996017,  798043451,   48334215]]])
    +        for size in [None, (5, 3, 3)]:
    +            random = Generator(MT19937(12345))
    +            x = random.integers([[-1], [0], [1]],
    +                                [2**32 - 1, 2**32, 2**32 + 1],
    +                                size=size)
    +            assert_array_equal(x, desired if size is not None else desired[0])
    +
    +    def test_int64_uint64_broadcast_exceptions(self, endpoint):
    +        configs = {np.uint64: ((0, 2**65), (-1, 2**62), (10, 9), (0, 0)),
    +                   np.int64: ((0, 2**64), (-(2**64), 2**62), (10, 9), (0, 0),
    +                              (-2**63 - 1, -2**63 - 1))}
    +        for dtype in configs:
    +            for config in configs[dtype]:
    +                low, high = config
    +                high = high - endpoint
    +                low_a = np.array([[low] * 10])
    +                high_a = np.array([high] * 10)
    +                assert_raises(ValueError, random.integers, low, high,
    +                              endpoint=endpoint, dtype=dtype)
    +                assert_raises(ValueError, random.integers, low_a, high,
    +                              endpoint=endpoint, dtype=dtype)
    +                assert_raises(ValueError, random.integers, low, high_a,
    +                              endpoint=endpoint, dtype=dtype)
    +                assert_raises(ValueError, random.integers, low_a, high_a,
    +                              endpoint=endpoint, dtype=dtype)
    +
    +                low_o = np.array([[low] * 10], dtype=object)
    +                high_o = np.array([high] * 10, dtype=object)
    +                assert_raises(ValueError, random.integers, low_o, high,
    +                              endpoint=endpoint, dtype=dtype)
    +                assert_raises(ValueError, random.integers, low, high_o,
    +                              endpoint=endpoint, dtype=dtype)
    +                assert_raises(ValueError, random.integers, low_o, high_o,
    +                              endpoint=endpoint, dtype=dtype)
    +
    +    def test_int64_uint64_corner_case(self, endpoint):
    +        # When stored in Numpy arrays, `lbnd` is casted
    +        # as np.int64, and `ubnd` is casted as np.uint64.
    +        # Checking whether `lbnd` >= `ubnd` used to be
    +        # done solely via direct comparison, which is incorrect
    +        # because when Numpy tries to compare both numbers,
    +        # it casts both to np.float64 because there is
    +        # no integer superset of np.int64 and np.uint64. However,
    +        # `ubnd` is too large to be represented in np.float64,
    +        # causing it be round down to np.iinfo(np.int64).max,
    +        # leading to a ValueError because `lbnd` now equals
    +        # the new `ubnd`.
    +
    +        dt = np.int64
    +        tgt = np.iinfo(np.int64).max
    +        lbnd = np.int64(np.iinfo(np.int64).max)
    +        ubnd = np.uint64(np.iinfo(np.int64).max + 1 - endpoint)
    +
    +        # None of these function calls should
    +        # generate a ValueError now.
    +        actual = random.integers(lbnd, ubnd, endpoint=endpoint, dtype=dt)
    +        assert_equal(actual, tgt)
    +
    +    def test_respect_dtype_singleton(self, endpoint):
    +        # See gh-7203
    +        for dt in self.itype:
    +            lbnd = 0 if dt is bool else np.iinfo(dt).min
    +            ubnd = 2 if dt is bool else np.iinfo(dt).max + 1
    +            ubnd = ubnd - 1 if endpoint else ubnd
    +            dt = np.bool if dt is bool else dt
    +
    +            sample = self.rfunc(lbnd, ubnd, endpoint=endpoint, dtype=dt)
    +            assert_equal(sample.dtype, dt)
    +
    +        for dt in (bool, int):
    +            lbnd = 0 if dt is bool else np.iinfo(dt).min
    +            ubnd = 2 if dt is bool else np.iinfo(dt).max + 1
    +            ubnd = ubnd - 1 if endpoint else ubnd
    +
    +            # gh-7284: Ensure that we get Python data types
    +            sample = self.rfunc(lbnd, ubnd, endpoint=endpoint, dtype=dt)
    +            assert not hasattr(sample, 'dtype')
    +            assert_equal(type(sample), dt)
    +
    +    def test_respect_dtype_array(self, endpoint):
    +        # See gh-7203
    +        for dt in self.itype:
    +            lbnd = 0 if dt is bool else np.iinfo(dt).min
    +            ubnd = 2 if dt is bool else np.iinfo(dt).max + 1
    +            ubnd = ubnd - 1 if endpoint else ubnd
    +            dt = np.bool if dt is bool else dt
    +
    +            sample = self.rfunc([lbnd], [ubnd], endpoint=endpoint, dtype=dt)
    +            assert_equal(sample.dtype, dt)
    +            sample = self.rfunc([lbnd] * 2, [ubnd] * 2, endpoint=endpoint,
    +                                dtype=dt)
    +            assert_equal(sample.dtype, dt)
    +
    +    def test_zero_size(self, endpoint):
    +        # See gh-7203
    +        for dt in self.itype:
    +            sample = self.rfunc(0, 0, (3, 0, 4), endpoint=endpoint, dtype=dt)
    +            assert sample.shape == (3, 0, 4)
    +            assert sample.dtype == dt
    +            assert self.rfunc(0, -10, 0, endpoint=endpoint,
    +                              dtype=dt).shape == (0,)
    +            assert_equal(random.integers(0, 0, size=(3, 0, 4)).shape,
    +                         (3, 0, 4))
    +            assert_equal(random.integers(0, -10, size=0).shape, (0,))
    +            assert_equal(random.integers(10, 10, size=0).shape, (0,))
    +
    +    def test_error_byteorder(self):
    +        other_byteord_dt = 'i4'
    +        with pytest.raises(ValueError):
    +            random.integers(0, 200, size=10, dtype=other_byteord_dt)
    +
    +    # chi2max is the maximum acceptable chi-squared value.
    +    @pytest.mark.slow
    +    @pytest.mark.parametrize('sample_size,high,dtype,chi2max',
    +        [(5000000, 5, np.int8, 125.0),          # p-value ~4.6e-25
    +         (5000000, 7, np.uint8, 150.0),         # p-value ~7.7e-30
    +         (10000000, 2500, np.int16, 3300.0),    # p-value ~3.0e-25
    +         (50000000, 5000, np.uint16, 6500.0),   # p-value ~3.5e-25
    +        ])
    +    def test_integers_small_dtype_chisquared(self, sample_size, high,
    +                                             dtype, chi2max):
    +        # Regression test for gh-14774.
    +        samples = random.integers(high, size=sample_size, dtype=dtype)
    +
    +        values, counts = np.unique(samples, return_counts=True)
    +        expected = sample_size / high
    +        chi2 = ((counts - expected)**2 / expected).sum()
    +        assert chi2 < chi2max
    +
    +
    +class TestRandomDist:
    +    # Make sure the random distribution returns the correct value for a
    +    # given seed
    +    seed = 1234567890
    +
    +    def test_integers(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.integers(-99, 99, size=(3, 2))
    +        desired = np.array([[-80, -56], [41, 37], [-83, -16]])
    +        assert_array_equal(actual, desired)
    +
    +    def test_integers_masked(self):
    +        # Test masked rejection sampling algorithm to generate array of
    +        # uint32 in an interval.
    +        random = Generator(MT19937(self.seed))
    +        actual = random.integers(0, 99, size=(3, 2), dtype=np.uint32)
    +        desired = np.array([[9, 21], [70, 68], [8, 41]], dtype=np.uint32)
    +        assert_array_equal(actual, desired)
    +
    +    def test_integers_closed(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.integers(-99, 99, size=(3, 2), endpoint=True)
    +        desired = np.array([[-80, -56], [41, 38], [-83, -15]])
    +        assert_array_equal(actual, desired)
    +
    +    def test_integers_max_int(self):
    +        # Tests whether integers with closed=True can generate the
    +        # maximum allowed Python int that can be converted
    +        # into a C long. Previous implementations of this
    +        # method have thrown an OverflowError when attempting
    +        # to generate this integer.
    +        actual = random.integers(np.iinfo('l').max, np.iinfo('l').max,
    +                                 endpoint=True)
    +
    +        desired = np.iinfo('l').max
    +        assert_equal(actual, desired)
    +
    +    def test_random(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.random((3, 2))
    +        desired = np.array([[0.096999199829214, 0.707517457682192],
    +                            [0.084364834598269, 0.767731206553125],
    +                            [0.665069021359413, 0.715487190596693]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +        random = Generator(MT19937(self.seed))
    +        actual = random.random()
    +        assert_array_almost_equal(actual, desired[0, 0], decimal=15)
    +
    +    def test_random_float(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.random((3, 2))
    +        desired = np.array([[0.0969992 , 0.70751746],  # noqa: E203
    +                            [0.08436483, 0.76773121],
    +                            [0.66506902, 0.71548719]])
    +        assert_array_almost_equal(actual, desired, decimal=7)
    +
    +    def test_random_float_scalar(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.random(dtype=np.float32)
    +        desired = 0.0969992
    +        assert_array_almost_equal(actual, desired, decimal=7)
    +
    +    @pytest.mark.parametrize('dtype, uint_view_type',
    +                             [(np.float32, np.uint32),
    +                              (np.float64, np.uint64)])
    +    def test_random_distribution_of_lsb(self, dtype, uint_view_type):
    +        random = Generator(MT19937(self.seed))
    +        sample = random.random(100000, dtype=dtype)
    +        num_ones_in_lsb = np.count_nonzero(sample.view(uint_view_type) & 1)
    +        # The probability of a 1 in the least significant bit is 0.25.
    +        # With a sample size of 100000, the probability that num_ones_in_lsb
    +        # is outside the following range is less than 5e-11.
    +        assert 24100 < num_ones_in_lsb < 25900
    +
    +    def test_random_unsupported_type(self):
    +        assert_raises(TypeError, random.random, dtype='int32')
    +
    +    def test_choice_uniform_replace(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.choice(4, 4)
    +        desired = np.array([0, 0, 2, 2], dtype=np.int64)
    +        assert_array_equal(actual, desired)
    +
    +    def test_choice_nonuniform_replace(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.choice(4, 4, p=[0.4, 0.4, 0.1, 0.1])
    +        desired = np.array([0, 1, 0, 1], dtype=np.int64)
    +        assert_array_equal(actual, desired)
    +
    +    def test_choice_uniform_noreplace(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.choice(4, 3, replace=False)
    +        desired = np.array([2, 0, 3], dtype=np.int64)
    +        assert_array_equal(actual, desired)
    +        actual = random.choice(4, 4, replace=False, shuffle=False)
    +        desired = np.arange(4, dtype=np.int64)
    +        assert_array_equal(actual, desired)
    +
    +    def test_choice_nonuniform_noreplace(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.choice(4, 3, replace=False, p=[0.1, 0.3, 0.5, 0.1])
    +        desired = np.array([0, 2, 3], dtype=np.int64)
    +        assert_array_equal(actual, desired)
    +
    +    def test_choice_noninteger(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.choice(['a', 'b', 'c', 'd'], 4)
    +        desired = np.array(['a', 'a', 'c', 'c'])
    +        assert_array_equal(actual, desired)
    +
    +    def test_choice_multidimensional_default_axis(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.choice([[0, 1], [2, 3], [4, 5], [6, 7]], 3)
    +        desired = np.array([[0, 1], [0, 1], [4, 5]])
    +        assert_array_equal(actual, desired)
    +
    +    def test_choice_multidimensional_custom_axis(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.choice([[0, 1], [2, 3], [4, 5], [6, 7]], 1, axis=1)
    +        desired = np.array([[0], [2], [4], [6]])
    +        assert_array_equal(actual, desired)
    +
    +    def test_choice_exceptions(self):
    +        sample = random.choice
    +        assert_raises(ValueError, sample, -1, 3)
    +        assert_raises(ValueError, sample, 3., 3)
    +        assert_raises(ValueError, sample, [], 3)
    +        assert_raises(ValueError, sample, [1, 2, 3, 4], 3,
    +                      p=[[0.25, 0.25], [0.25, 0.25]])
    +        assert_raises(ValueError, sample, [1, 2], 3, p=[0.4, 0.4, 0.2])
    +        assert_raises(ValueError, sample, [1, 2], 3, p=[1.1, -0.1])
    +        assert_raises(ValueError, sample, [1, 2], 3, p=[0.4, 0.4])
    +        assert_raises(ValueError, sample, [1, 2, 3], 4, replace=False)
    +        # gh-13087
    +        assert_raises(ValueError, sample, [1, 2, 3], -2, replace=False)
    +        assert_raises(ValueError, sample, [1, 2, 3], (-1,), replace=False)
    +        assert_raises(ValueError, sample, [1, 2, 3], (-1, 1), replace=False)
    +        assert_raises(ValueError, sample, [1, 2, 3], 2,
    +                      replace=False, p=[1, 0, 0])
    +
    +    def test_choice_return_shape(self):
    +        p = [0.1, 0.9]
    +        # Check scalar
    +        assert_(np.isscalar(random.choice(2, replace=True)))
    +        assert_(np.isscalar(random.choice(2, replace=False)))
    +        assert_(np.isscalar(random.choice(2, replace=True, p=p)))
    +        assert_(np.isscalar(random.choice(2, replace=False, p=p)))
    +        assert_(np.isscalar(random.choice([1, 2], replace=True)))
    +        assert_(random.choice([None], replace=True) is None)
    +        a = np.array([1, 2])
    +        arr = np.empty(1, dtype=object)
    +        arr[0] = a
    +        assert_(random.choice(arr, replace=True) is a)
    +
    +        # Check 0-d array
    +        s = ()
    +        assert_(not np.isscalar(random.choice(2, s, replace=True)))
    +        assert_(not np.isscalar(random.choice(2, s, replace=False)))
    +        assert_(not np.isscalar(random.choice(2, s, replace=True, p=p)))
    +        assert_(not np.isscalar(random.choice(2, s, replace=False, p=p)))
    +        assert_(not np.isscalar(random.choice([1, 2], s, replace=True)))
    +        assert_(random.choice([None], s, replace=True).ndim == 0)
    +        a = np.array([1, 2])
    +        arr = np.empty(1, dtype=object)
    +        arr[0] = a
    +        assert_(random.choice(arr, s, replace=True).item() is a)
    +
    +        # Check multi dimensional array
    +        s = (2, 3)
    +        p = [0.1, 0.1, 0.1, 0.1, 0.4, 0.2]
    +        assert_equal(random.choice(6, s, replace=True).shape, s)
    +        assert_equal(random.choice(6, s, replace=False).shape, s)
    +        assert_equal(random.choice(6, s, replace=True, p=p).shape, s)
    +        assert_equal(random.choice(6, s, replace=False, p=p).shape, s)
    +        assert_equal(random.choice(np.arange(6), s, replace=True).shape, s)
    +
    +        # Check zero-size
    +        assert_equal(random.integers(0, 0, size=(3, 0, 4)).shape, (3, 0, 4))
    +        assert_equal(random.integers(0, -10, size=0).shape, (0,))
    +        assert_equal(random.integers(10, 10, size=0).shape, (0,))
    +        assert_equal(random.choice(0, size=0).shape, (0,))
    +        assert_equal(random.choice([], size=(0,)).shape, (0,))
    +        assert_equal(random.choice(['a', 'b'], size=(3, 0, 4)).shape,
    +                     (3, 0, 4))
    +        assert_raises(ValueError, random.choice, [], 10)
    +
    +    def test_choice_nan_probabilities(self):
    +        a = np.array([42, 1, 2])
    +        p = [None, None, None]
    +        assert_raises(ValueError, random.choice, a, p=p)
    +
    +    def test_choice_p_non_contiguous(self):
    +        p = np.ones(10) / 5
    +        p[1::2] = 3.0
    +        random = Generator(MT19937(self.seed))
    +        non_contig = random.choice(5, 3, p=p[::2])
    +        random = Generator(MT19937(self.seed))
    +        contig = random.choice(5, 3, p=np.ascontiguousarray(p[::2]))
    +        assert_array_equal(non_contig, contig)
    +
    +    def test_choice_return_type(self):
    +        # gh 9867
    +        p = np.ones(4) / 4.
    +        actual = random.choice(4, 2)
    +        assert actual.dtype == np.int64
    +        actual = random.choice(4, 2, replace=False)
    +        assert actual.dtype == np.int64
    +        actual = random.choice(4, 2, p=p)
    +        assert actual.dtype == np.int64
    +        actual = random.choice(4, 2, p=p, replace=False)
    +        assert actual.dtype == np.int64
    +
    +    def test_choice_large_sample(self):
    +        choice_hash = '4266599d12bfcfb815213303432341c06b4349f5455890446578877bb322e222'
    +        random = Generator(MT19937(self.seed))
    +        actual = random.choice(10000, 5000, replace=False)
    +        if sys.byteorder != 'little':
    +            actual = actual.byteswap()
    +        res = hashlib.sha256(actual.view(np.int8)).hexdigest()
    +        assert_(choice_hash == res)
    +
    +    def test_choice_array_size_empty_tuple(self):
    +        random = Generator(MT19937(self.seed))
    +        assert_array_equal(random.choice([1, 2, 3], size=()), np.array(1),
    +                           strict=True)
    +        assert_array_equal(random.choice([[1, 2, 3]], size=()), [1, 2, 3])
    +        assert_array_equal(random.choice([[1]], size=()), [1], strict=True)
    +        assert_array_equal(random.choice([[1]], size=(), axis=1), [1],
    +                           strict=True)
    +
    +    def test_bytes(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.bytes(10)
    +        desired = b'\x86\xf0\xd4\x18\xe1\x81\t8%\xdd'
    +        assert_equal(actual, desired)
    +
    +    def test_shuffle(self):
    +        # Test lists, arrays (of various dtypes), and multidimensional versions
    +        # of both, c-contiguous or not:
    +        for conv in [lambda x: np.array([]),
    +                     lambda x: x,
    +                     lambda x: np.asarray(x).astype(np.int8),
    +                     lambda x: np.asarray(x).astype(np.float32),
    +                     lambda x: np.asarray(x).astype(np.complex64),
    +                     lambda x: np.asarray(x).astype(object),
    +                     lambda x: [(i, i) for i in x],
    +                     lambda x: np.asarray([[i, i] for i in x]),
    +                     lambda x: np.vstack([x, x]).T,
    +                     # gh-11442
    +                     lambda x: (np.asarray([(i, i) for i in x],
    +                                           [("a", int), ("b", int)])
    +                                .view(np.recarray)),
    +                     # gh-4270
    +                     lambda x: np.asarray([(i, i) for i in x],
    +                                          [("a", object, (1,)),
    +                                           ("b", np.int32, (1,))])]:
    +            random = Generator(MT19937(self.seed))
    +            alist = conv([1, 2, 3, 4, 5, 6, 7, 8, 9, 0])
    +            random.shuffle(alist)
    +            actual = alist
    +            desired = conv([4, 1, 9, 8, 0, 5, 3, 6, 2, 7])
    +            assert_array_equal(actual, desired)
    +
    +    def test_shuffle_custom_axis(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = np.arange(16).reshape((4, 4))
    +        random.shuffle(actual, axis=1)
    +        desired = np.array([[ 0,  3,  1,  2],
    +                            [ 4,  7,  5,  6],
    +                            [ 8, 11,  9, 10],
    +                            [12, 15, 13, 14]])
    +        assert_array_equal(actual, desired)
    +        random = Generator(MT19937(self.seed))
    +        actual = np.arange(16).reshape((4, 4))
    +        random.shuffle(actual, axis=-1)
    +        assert_array_equal(actual, desired)
    +
    +    def test_shuffle_custom_axis_empty(self):
    +        random = Generator(MT19937(self.seed))
    +        desired = np.array([]).reshape((0, 6))
    +        for axis in (0, 1):
    +            actual = np.array([]).reshape((0, 6))
    +            random.shuffle(actual, axis=axis)
    +            assert_array_equal(actual, desired)
    +
    +    def test_shuffle_axis_nonsquare(self):
    +        y1 = np.arange(20).reshape(2, 10)
    +        y2 = y1.copy()
    +        random = Generator(MT19937(self.seed))
    +        random.shuffle(y1, axis=1)
    +        random = Generator(MT19937(self.seed))
    +        random.shuffle(y2.T)
    +        assert_array_equal(y1, y2)
    +
    +    def test_shuffle_masked(self):
    +        # gh-3263
    +        a = np.ma.masked_values(np.reshape(range(20), (5, 4)) % 3 - 1, -1)
    +        b = np.ma.masked_values(np.arange(20) % 3 - 1, -1)
    +        a_orig = a.copy()
    +        b_orig = b.copy()
    +        for i in range(50):
    +            random.shuffle(a)
    +            assert_equal(
    +                sorted(a.data[~a.mask]), sorted(a_orig.data[~a_orig.mask]))
    +            random.shuffle(b)
    +            assert_equal(
    +                sorted(b.data[~b.mask]), sorted(b_orig.data[~b_orig.mask]))
    +
    +    def test_shuffle_exceptions(self):
    +        random = Generator(MT19937(self.seed))
    +        arr = np.arange(10)
    +        assert_raises(AxisError, random.shuffle, arr, 1)
    +        arr = np.arange(9).reshape((3, 3))
    +        assert_raises(AxisError, random.shuffle, arr, 3)
    +        assert_raises(TypeError, random.shuffle, arr, slice(1, 2, None))
    +        arr = [[1, 2, 3], [4, 5, 6]]
    +        assert_raises(NotImplementedError, random.shuffle, arr, 1)
    +
    +        arr = np.array(3)
    +        assert_raises(TypeError, random.shuffle, arr)
    +        arr = np.ones((3, 2))
    +        assert_raises(AxisError, random.shuffle, arr, 2)
    +
    +    def test_shuffle_not_writeable(self):
    +        random = Generator(MT19937(self.seed))
    +        a = np.zeros(5)
    +        a.flags.writeable = False
    +        with pytest.raises(ValueError, match='read-only'):
    +            random.shuffle(a)
    +
    +    def test_permutation(self):
    +        random = Generator(MT19937(self.seed))
    +        alist = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
    +        actual = random.permutation(alist)
    +        desired = [4, 1, 9, 8, 0, 5, 3, 6, 2, 7]
    +        assert_array_equal(actual, desired)
    +
    +        random = Generator(MT19937(self.seed))
    +        arr_2d = np.atleast_2d([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]).T
    +        actual = random.permutation(arr_2d)
    +        assert_array_equal(actual, np.atleast_2d(desired).T)
    +
    +        bad_x_str = "abcd"
    +        assert_raises(AxisError, random.permutation, bad_x_str)
    +
    +        bad_x_float = 1.2
    +        assert_raises(AxisError, random.permutation, bad_x_float)
    +
    +        random = Generator(MT19937(self.seed))
    +        integer_val = 10
    +        desired = [3, 0, 8, 7, 9, 4, 2, 5, 1, 6]
    +
    +        actual = random.permutation(integer_val)
    +        assert_array_equal(actual, desired)
    +
    +    def test_permutation_custom_axis(self):
    +        a = np.arange(16).reshape((4, 4))
    +        desired = np.array([[ 0,  3,  1,  2],
    +                            [ 4,  7,  5,  6],
    +                            [ 8, 11,  9, 10],
    +                            [12, 15, 13, 14]])
    +        random = Generator(MT19937(self.seed))
    +        actual = random.permutation(a, axis=1)
    +        assert_array_equal(actual, desired)
    +        random = Generator(MT19937(self.seed))
    +        actual = random.permutation(a, axis=-1)
    +        assert_array_equal(actual, desired)
    +
    +    def test_permutation_exceptions(self):
    +        random = Generator(MT19937(self.seed))
    +        arr = np.arange(10)
    +        assert_raises(AxisError, random.permutation, arr, 1)
    +        arr = np.arange(9).reshape((3, 3))
    +        assert_raises(AxisError, random.permutation, arr, 3)
    +        assert_raises(TypeError, random.permutation, arr, slice(1, 2, None))
    +
    +    @pytest.mark.parametrize("dtype", [int, object])
    +    @pytest.mark.parametrize("axis, expected",
    +                             [(None, np.array([[3, 7, 0, 9, 10, 11],
    +                                               [8, 4, 2, 5,  1,  6]])),
    +                              (0, np.array([[6, 1, 2, 9, 10, 11],
    +                                            [0, 7, 8, 3,  4,  5]])),
    +                              (1, np.array([[ 5, 3,  4, 0, 2, 1],
    +                                            [11, 9, 10, 6, 8, 7]]))])
    +    def test_permuted(self, dtype, axis, expected):
    +        random = Generator(MT19937(self.seed))
    +        x = np.arange(12).reshape(2, 6).astype(dtype)
    +        random.permuted(x, axis=axis, out=x)
    +        assert_array_equal(x, expected)
    +
    +        random = Generator(MT19937(self.seed))
    +        x = np.arange(12).reshape(2, 6).astype(dtype)
    +        y = random.permuted(x, axis=axis)
    +        assert y.dtype == dtype
    +        assert_array_equal(y, expected)
    +
    +    def test_permuted_with_strides(self):
    +        random = Generator(MT19937(self.seed))
    +        x0 = np.arange(22).reshape(2, 11)
    +        x1 = x0.copy()
    +        x = x0[:, ::3]
    +        y = random.permuted(x, axis=1, out=x)
    +        expected = np.array([[0, 9, 3, 6],
    +                             [14, 20, 11, 17]])
    +        assert_array_equal(y, expected)
    +        x1[:, ::3] = expected
    +        # Verify that the original x0 was modified in-place as expected.
    +        assert_array_equal(x1, x0)
    +
    +    def test_permuted_empty(self):
    +        y = random.permuted([])
    +        assert_array_equal(y, [])
    +
    +    @pytest.mark.parametrize('outshape', [(2, 3), 5])
    +    def test_permuted_out_with_wrong_shape(self, outshape):
    +        a = np.array([1, 2, 3])
    +        out = np.zeros(outshape, dtype=a.dtype)
    +        with pytest.raises(ValueError, match='same shape'):
    +            random.permuted(a, out=out)
    +
    +    def test_permuted_out_with_wrong_type(self):
    +        out = np.zeros((3, 5), dtype=np.int32)
    +        x = np.ones((3, 5))
    +        with pytest.raises(TypeError, match='Cannot cast'):
    +            random.permuted(x, axis=1, out=out)
    +
    +    def test_permuted_not_writeable(self):
    +        x = np.zeros((2, 5))
    +        x.flags.writeable = False
    +        with pytest.raises(ValueError, match='read-only'):
    +            random.permuted(x, axis=1, out=x)
    +
    +    def test_beta(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.beta(.1, .9, size=(3, 2))
    +        desired = np.array(
    +            [[1.083029353267698e-10, 2.449965303168024e-11],
    +             [2.397085162969853e-02, 3.590779671820755e-08],
    +             [2.830254190078299e-04, 1.744709918330393e-01]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_binomial(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.binomial(100.123, .456, size=(3, 2))
    +        desired = np.array([[42, 41],
    +                            [42, 48],
    +                            [44, 50]])
    +        assert_array_equal(actual, desired)
    +
    +        random = Generator(MT19937(self.seed))
    +        actual = random.binomial(100.123, .456)
    +        desired = 42
    +        assert_array_equal(actual, desired)
    +
    +    def test_chisquare(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.chisquare(50, size=(3, 2))
    +        desired = np.array([[32.9850547060149, 39.0219480493301],
    +                            [56.2006134779419, 57.3474165711485],
    +                            [55.4243733880198, 55.4209797925213]])
    +        assert_array_almost_equal(actual, desired, decimal=13)
    +
    +    def test_dirichlet(self):
    +        random = Generator(MT19937(self.seed))
    +        alpha = np.array([51.72840233779265162, 39.74494232180943953])
    +        actual = random.dirichlet(alpha, size=(3, 2))
    +        desired = np.array([[[0.5439892869558927,  0.45601071304410745],
    +                             [0.5588917345860708,  0.4411082654139292 ]],  # noqa: E202
    +                            [[0.5632074165063435,  0.43679258349365657],
    +                             [0.54862581112627,    0.45137418887373015]],
    +                            [[0.49961831357047226, 0.5003816864295278 ],  # noqa: E202
    +                             [0.52374806183482,    0.47625193816517997]]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +        bad_alpha = np.array([5.4e-01, -1.0e-16])
    +        assert_raises(ValueError, random.dirichlet, bad_alpha)
    +
    +        random = Generator(MT19937(self.seed))
    +        alpha = np.array([51.72840233779265162, 39.74494232180943953])
    +        actual = random.dirichlet(alpha)
    +        assert_array_almost_equal(actual, desired[0, 0], decimal=15)
    +
    +    def test_dirichlet_size(self):
    +        # gh-3173
    +        p = np.array([51.72840233779265162, 39.74494232180943953])
    +        assert_equal(random.dirichlet(p, np.uint32(1)).shape, (1, 2))
    +        assert_equal(random.dirichlet(p, np.uint32(1)).shape, (1, 2))
    +        assert_equal(random.dirichlet(p, np.uint32(1)).shape, (1, 2))
    +        assert_equal(random.dirichlet(p, [2, 2]).shape, (2, 2, 2))
    +        assert_equal(random.dirichlet(p, (2, 2)).shape, (2, 2, 2))
    +        assert_equal(random.dirichlet(p, np.array((2, 2))).shape, (2, 2, 2))
    +
    +        assert_raises(TypeError, random.dirichlet, p, float(1))
    +
    +    def test_dirichlet_bad_alpha(self):
    +        # gh-2089
    +        alpha = np.array([5.4e-01, -1.0e-16])
    +        assert_raises(ValueError, random.dirichlet, alpha)
    +
    +        # gh-15876
    +        assert_raises(ValueError, random.dirichlet, [[5, 1]])
    +        assert_raises(ValueError, random.dirichlet, [[5], [1]])
    +        assert_raises(ValueError, random.dirichlet, [[[5], [1]], [[1], [5]]])
    +        assert_raises(ValueError, random.dirichlet, np.array([[5, 1], [1, 5]]))
    +
    +    def test_dirichlet_alpha_non_contiguous(self):
    +        a = np.array([51.72840233779265162, -1.0, 39.74494232180943953])
    +        alpha = a[::2]
    +        random = Generator(MT19937(self.seed))
    +        non_contig = random.dirichlet(alpha, size=(3, 2))
    +        random = Generator(MT19937(self.seed))
    +        contig = random.dirichlet(np.ascontiguousarray(alpha),
    +                                  size=(3, 2))
    +        assert_array_almost_equal(non_contig, contig)
    +
    +    def test_dirichlet_small_alpha(self):
    +        eps = 1.0e-9  # 1.0e-10 -> runtime x 10; 1e-11 -> runtime x 200, etc.
    +        alpha = eps * np.array([1., 1.0e-3])
    +        random = Generator(MT19937(self.seed))
    +        actual = random.dirichlet(alpha, size=(3, 2))
    +        expected = np.array([
    +            [[1., 0.],
    +             [1., 0.]],
    +            [[1., 0.],
    +             [1., 0.]],
    +            [[1., 0.],
    +             [1., 0.]]
    +        ])
    +        assert_array_almost_equal(actual, expected, decimal=15)
    +
    +    @pytest.mark.slow
    +    @pytest.mark.thread_unsafe(reason="crashes with low memory")
    +    def test_dirichlet_moderately_small_alpha(self):
    +        # Use alpha.max() < 0.1 to trigger stick breaking code path
    +        alpha = np.array([0.02, 0.04, 0.03])
    +        exact_mean = alpha / alpha.sum()
    +        random = Generator(MT19937(self.seed))
    +        sample = random.dirichlet(alpha, size=20000000)
    +        sample_mean = sample.mean(axis=0)
    +        assert_allclose(sample_mean, exact_mean, rtol=1e-3)
    +
    +    # This set of parameters includes inputs with alpha.max() >= 0.1 and
    +    # alpha.max() < 0.1 to exercise both generation methods within the
    +    # dirichlet code.
    +    @pytest.mark.parametrize(
    +        'alpha',
    +        [[5, 9, 0, 8],
    +         [0.5, 0, 0, 0],
    +         [1, 5, 0, 0, 1.5, 0, 0, 0],
    +         [0.01, 0.03, 0, 0.005],
    +         [1e-5, 0, 0, 0],
    +         [0.002, 0.015, 0, 0, 0.04, 0, 0, 0],
    +         [0.0],
    +         [0, 0, 0]],
    +    )
    +    def test_dirichlet_multiple_zeros_in_alpha(self, alpha):
    +        alpha = np.array(alpha)
    +        y = random.dirichlet(alpha)
    +        assert_equal(y[alpha == 0], 0.0)
    +
    +    def test_exponential(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.exponential(1.1234, size=(3, 2))
    +        desired = np.array([[0.098845481066258, 1.560752510746964],
    +                            [0.075730916041636, 1.769098974710777],
    +                            [1.488602544592235, 2.49684815275751 ]])  # noqa: E202
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_exponential_0(self):
    +        assert_equal(random.exponential(scale=0), 0)
    +        assert_raises(ValueError, random.exponential, scale=-0.)
    +
    +    def test_f(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.f(12, 77, size=(3, 2))
    +        desired = np.array([[0.461720027077085, 1.100441958872451],
    +                            [1.100337455217484, 0.91421736740018 ],  # noqa: E202
    +                            [0.500811891303113, 0.826802454552058]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_gamma(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.gamma(5, 3, size=(3, 2))
    +        desired = np.array([[ 5.03850858902096,  7.9228656732049 ],  # noqa: E202
    +                            [18.73983605132985, 19.57961681699238],
    +                            [18.17897755150825, 18.17653912505234]])
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +
    +    def test_gamma_0(self):
    +        assert_equal(random.gamma(shape=0, scale=0), 0)
    +        assert_raises(ValueError, random.gamma, shape=-0., scale=-0.)
    +
    +    def test_geometric(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.geometric(.123456789, size=(3, 2))
    +        desired = np.array([[1, 11],
    +                            [1, 12],
    +                            [11, 17]])
    +        assert_array_equal(actual, desired)
    +
    +    def test_geometric_exceptions(self):
    +        assert_raises(ValueError, random.geometric, 1.1)
    +        assert_raises(ValueError, random.geometric, [1.1] * 10)
    +        assert_raises(ValueError, random.geometric, -0.1)
    +        assert_raises(ValueError, random.geometric, [-0.1] * 10)
    +        with np.errstate(invalid='ignore'):
    +            assert_raises(ValueError, random.geometric, np.nan)
    +            assert_raises(ValueError, random.geometric, [np.nan] * 10)
    +
    +    def test_gumbel(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.gumbel(loc=.123456789, scale=2.0, size=(3, 2))
    +        desired = np.array([[ 4.688397515056245, -0.289514845417841],
    +                            [ 4.981176042584683, -0.633224272589149],
    +                            [-0.055915275687488, -0.333962478257953]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_gumbel_0(self):
    +        assert_equal(random.gumbel(scale=0), 0)
    +        assert_raises(ValueError, random.gumbel, scale=-0.)
    +
    +    def test_hypergeometric(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.hypergeometric(10.1, 5.5, 14, size=(3, 2))
    +        desired = np.array([[ 9, 9],
    +                            [ 9, 9],
    +                            [10, 9]])
    +        assert_array_equal(actual, desired)
    +
    +        # Test nbad = 0
    +        actual = random.hypergeometric(5, 0, 3, size=4)
    +        desired = np.array([3, 3, 3, 3])
    +        assert_array_equal(actual, desired)
    +
    +        actual = random.hypergeometric(15, 0, 12, size=4)
    +        desired = np.array([12, 12, 12, 12])
    +        assert_array_equal(actual, desired)
    +
    +        # Test ngood = 0
    +        actual = random.hypergeometric(0, 5, 3, size=4)
    +        desired = np.array([0, 0, 0, 0])
    +        assert_array_equal(actual, desired)
    +
    +        actual = random.hypergeometric(0, 15, 12, size=4)
    +        desired = np.array([0, 0, 0, 0])
    +        assert_array_equal(actual, desired)
    +
    +    def test_laplace(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.laplace(loc=.123456789, scale=2.0, size=(3, 2))
    +        desired = np.array([[-3.156353949272393,  1.195863024830054],
    +                            [-3.435458081645966,  1.656882398925444],
    +                            [ 0.924824032467446,  1.251116432209336]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_laplace_0(self):
    +        assert_equal(random.laplace(scale=0), 0)
    +        assert_raises(ValueError, random.laplace, scale=-0.)
    +
    +    def test_logistic(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.logistic(loc=.123456789, scale=2.0, size=(3, 2))
    +        desired = np.array([[-4.338584631510999,  1.890171436749954],
    +                            [-4.64547787337966 ,  2.514545562919217],  # noqa: E203
    +                            [ 1.495389489198666,  1.967827627577474]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_lognormal(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.lognormal(mean=.123456789, sigma=2.0, size=(3, 2))
    +        desired = np.array([[ 0.0268252166335, 13.9534486483053],
    +                            [ 0.1204014788936,  2.2422077497792],
    +                            [ 4.2484199496128, 12.0093343977523]])
    +        assert_array_almost_equal(actual, desired, decimal=13)
    +
    +    def test_lognormal_0(self):
    +        assert_equal(random.lognormal(sigma=0), 1)
    +        assert_raises(ValueError, random.lognormal, sigma=-0.)
    +
    +    def test_logseries(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.logseries(p=.923456789, size=(3, 2))
    +        desired = np.array([[14, 17],
    +                            [3, 18],
    +                            [5, 1]])
    +        assert_array_equal(actual, desired)
    +
    +    def test_logseries_zero(self):
    +        random = Generator(MT19937(self.seed))
    +        assert random.logseries(0) == 1
    +
    +    @pytest.mark.parametrize("value", [np.nextafter(0., -1), 1., np.nan, 5.])
    +    def test_logseries_exceptions(self, value):
    +        random = Generator(MT19937(self.seed))
    +        with np.errstate(invalid="ignore"):
    +            with pytest.raises(ValueError):
    +                random.logseries(value)
    +            with pytest.raises(ValueError):
    +                # contiguous path:
    +                random.logseries(np.array([value] * 10))
    +            with pytest.raises(ValueError):
    +                # non-contiguous path:
    +                random.logseries(np.array([value] * 10)[::2])
    +
    +    def test_multinomial(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.multinomial(20, [1 / 6.] * 6, size=(3, 2))
    +        desired = np.array([[[1, 5, 1, 6, 4, 3],
    +                             [4, 2, 6, 2, 4, 2]],
    +                            [[5, 3, 2, 6, 3, 1],
    +                             [4, 4, 0, 2, 3, 7]],
    +                            [[6, 3, 1, 5, 3, 2],
    +                             [5, 5, 3, 1, 2, 4]]])
    +        assert_array_equal(actual, desired)
    +
    +    @pytest.mark.skipif(IS_WASM, reason="fp errors don't work in wasm")
    +    @pytest.mark.parametrize("method", ["svd", "eigh", "cholesky"])
    +    def test_multivariate_normal(self, method):
    +        random = Generator(MT19937(self.seed))
    +        mean = (.123456789, 10)
    +        cov = [[1, 0], [0, 1]]
    +        size = (3, 2)
    +        actual = random.multivariate_normal(mean, cov, size, method=method)
    +        desired = np.array([[[-1.747478062846581,  11.25613495182354 ],  # noqa: E202
    +                             [-0.9967333370066214, 10.342002097029821]],
    +                            [[ 0.7850019631242964, 11.181113712443013],
    +                             [ 0.8901349653255224,  8.873825399642492]],
    +                            [[ 0.7130260107430003,  9.551628690083056],
    +                             [ 0.7127098726541128, 11.991709234143173]]])
    +
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +        # Check for default size, was raising deprecation warning
    +        actual = random.multivariate_normal(mean, cov, method=method)
    +        desired = np.array([0.233278563284287, 9.424140804347195])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +        # Check that non symmetric covariance input raises exception when
    +        # check_valid='raises' if using default svd method.
    +        mean = [0, 0]
    +        cov = [[1, 2], [1, 2]]
    +        assert_raises(ValueError, random.multivariate_normal, mean, cov,
    +                      check_valid='raise')
    +
    +        # Check that non positive-semidefinite covariance warns with
    +        # RuntimeWarning
    +        cov = [[1, 2], [2, 1]]
    +        pytest.warns(RuntimeWarning, random.multivariate_normal, mean, cov)
    +        pytest.warns(RuntimeWarning, random.multivariate_normal, mean, cov,
    +                     method='eigh')
    +        assert_raises(LinAlgError, random.multivariate_normal, mean, cov,
    +                      method='cholesky')
    +
    +        # and that it doesn't warn with RuntimeWarning check_valid='ignore'
    +        assert_no_warnings(random.multivariate_normal, mean, cov,
    +                           check_valid='ignore')
    +
    +        # and that it raises with RuntimeWarning check_valid='raises'
    +        assert_raises(ValueError, random.multivariate_normal, mean, cov,
    +                      check_valid='raise')
    +        assert_raises(ValueError, random.multivariate_normal, mean, cov,
    +                      check_valid='raise', method='eigh')
    +
    +        # check degenerate samples from singular covariance matrix
    +        cov = [[1, 1], [1, 1]]
    +        if method in ('svd', 'eigh'):
    +            samples = random.multivariate_normal(mean, cov, size=(3, 2),
    +                                                 method=method)
    +            assert_array_almost_equal(samples[..., 0], samples[..., 1],
    +                                      decimal=6)
    +        else:
    +            assert_raises(LinAlgError, random.multivariate_normal, mean, cov,
    +                          method='cholesky')
    +
    +        cov = np.array([[1, 0.1], [0.1, 1]], dtype=np.float32)
    +        with warnings.catch_warnings():
    +            warnings.simplefilter("error")
    +            random.multivariate_normal(mean, cov, method=method)
    +
    +        mu = np.zeros(2)
    +        cov = np.eye(2)
    +        assert_raises(ValueError, random.multivariate_normal, mean, cov,
    +                      check_valid='other')
    +        assert_raises(ValueError, random.multivariate_normal,
    +                      np.zeros((2, 1, 1)), cov)
    +        assert_raises(ValueError, random.multivariate_normal,
    +                      mu, np.empty((3, 2)))
    +        assert_raises(ValueError, random.multivariate_normal,
    +                      mu, np.eye(3))
    +
    +    @pytest.mark.parametrize('mean, cov', [([0], [[1 + 1j]]), ([0j], [[1]])])
    +    def test_multivariate_normal_disallow_complex(self, mean, cov):
    +        random = Generator(MT19937(self.seed))
    +        with pytest.raises(TypeError, match="must not be complex"):
    +            random.multivariate_normal(mean, cov)
    +
    +    @pytest.mark.parametrize("method", ["svd", "eigh", "cholesky"])
    +    def test_multivariate_normal_basic_stats(self, method):
    +        random = Generator(MT19937(self.seed))
    +        n_s = 1000
    +        mean = np.array([1, 2])
    +        cov = np.array([[2, 1], [1, 2]])
    +        s = random.multivariate_normal(mean, cov, size=(n_s,), method=method)
    +        s_center = s - mean
    +        cov_emp = (s_center.T @ s_center) / (n_s - 1)
    +        # these are pretty loose and are only designed to detect major errors
    +        assert np.all(np.abs(s_center.mean(-2)) < 0.1)
    +        assert np.all(np.abs(cov_emp - cov) < 0.2)
    +
    +    def test_negative_binomial(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.negative_binomial(n=100, p=.12345, size=(3, 2))
    +        desired = np.array([[543, 727],
    +                            [775, 760],
    +                            [600, 674]])
    +        assert_array_equal(actual, desired)
    +
    +    def test_negative_binomial_exceptions(self):
    +        with np.errstate(invalid='ignore'):
    +            assert_raises(ValueError, random.negative_binomial, 100, np.nan)
    +            assert_raises(ValueError, random.negative_binomial, 100,
    +                          [np.nan] * 10)
    +
    +    def test_negative_binomial_p0_exception(self):
    +        # Verify that p=0 raises an exception.
    +        with assert_raises(ValueError):
    +            x = random.negative_binomial(1, 0)
    +
    +    def test_negative_binomial_invalid_p_n_combination(self):
    +        # Verify that values of p and n that would result in an overflow
    +        # or infinite loop raise an exception.
    +        with np.errstate(invalid='ignore'):
    +            assert_raises(ValueError, random.negative_binomial, 2**62, 0.1)
    +            assert_raises(ValueError, random.negative_binomial, [2**62], [0.1])
    +
    +    def test_noncentral_chisquare(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.noncentral_chisquare(df=5, nonc=5, size=(3, 2))
    +        desired = np.array([[ 1.70561552362133, 15.97378184942111],
    +                            [13.71483425173724, 20.17859633310629],
    +                            [11.3615477156643 ,  3.67891108738029]])  # noqa: E203
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +
    +        actual = random.noncentral_chisquare(df=.5, nonc=.2, size=(3, 2))
    +        desired = np.array([[9.41427665607629e-04, 1.70473157518850e-04],
    +                            [1.14554372041263e+00, 1.38187755933435e-03],
    +                            [1.90659181905387e+00, 1.21772577941822e+00]])
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +
    +        random = Generator(MT19937(self.seed))
    +        actual = random.noncentral_chisquare(df=5, nonc=0, size=(3, 2))
    +        desired = np.array([[0.82947954590419, 1.80139670767078],
    +                            [6.58720057417794, 7.00491463609814],
    +                            [6.31101879073157, 6.30982307753005]])
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +
    +    def test_noncentral_f(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.noncentral_f(dfnum=5, dfden=2, nonc=1,
    +                                     size=(3, 2))
    +        desired = np.array([[0.060310671139  , 0.23866058175939],  # noqa: E203
    +                            [0.86860246709073, 0.2668510459738 ],  # noqa: E202
    +                            [0.23375780078364, 1.88922102885943]])
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +
    +    def test_noncentral_f_nan(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.noncentral_f(dfnum=5, dfden=2, nonc=np.nan)
    +        assert np.isnan(actual)
    +
    +    def test_normal(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.normal(loc=.123456789, scale=2.0, size=(3, 2))
    +        desired = np.array([[-3.618412914693162,  2.635726692647081],
    +                            [-2.116923463013243,  0.807460983059643],
    +                            [ 1.446547137248593,  2.485684213886024]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_normal_0(self):
    +        assert_equal(random.normal(scale=0), 0)
    +        assert_raises(ValueError, random.normal, scale=-0.)
    +
    +    def test_pareto(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.pareto(a=.123456789, size=(3, 2))
    +        desired = np.array([[1.0394926776069018e+00, 7.7142534343505773e+04],
    +                            [7.2640150889064703e-01, 3.4650454783825594e+05],
    +                            [4.5852344481994740e+04, 6.5851383009539105e+07]])
    +        # For some reason on 32-bit x86 Ubuntu 12.10 the [1, 0] entry in this
    +        # matrix differs by 24 nulps. Discussion:
    +        #   https://mail.python.org/pipermail/numpy-discussion/2012-September/063801.html
    +        # Consensus is that this is probably some gcc quirk that affects
    +        # rounding but not in any important way, so we just use a looser
    +        # tolerance on this test:
    +        np.testing.assert_array_almost_equal_nulp(actual, desired, nulp=30)
    +
    +    def test_poisson(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.poisson(lam=.123456789, size=(3, 2))
    +        desired = np.array([[0, 0],
    +                            [0, 0],
    +                            [0, 0]])
    +        assert_array_equal(actual, desired)
    +
    +    def test_poisson_exceptions(self):
    +        lambig = np.iinfo('int64').max
    +        lamneg = -1
    +        assert_raises(ValueError, random.poisson, lamneg)
    +        assert_raises(ValueError, random.poisson, [lamneg] * 10)
    +        assert_raises(ValueError, random.poisson, lambig)
    +        assert_raises(ValueError, random.poisson, [lambig] * 10)
    +        with np.errstate(invalid='ignore'):
    +            assert_raises(ValueError, random.poisson, np.nan)
    +            assert_raises(ValueError, random.poisson, [np.nan] * 10)
    +
    +    def test_power(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.power(a=.123456789, size=(3, 2))
    +        desired = np.array([[1.977857368842754e-09, 9.806792196620341e-02],
    +                            [2.482442984543471e-10, 1.527108843266079e-01],
    +                            [8.188283434244285e-02, 3.950547209346948e-01]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_rayleigh(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.rayleigh(scale=10, size=(3, 2))
    +        desired = np.array([[4.19494429102666, 16.66920198906598],
    +                            [3.67184544902662, 17.74695521962917],
    +                            [16.27935397855501, 21.08355560691792]])
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +
    +    def test_rayleigh_0(self):
    +        assert_equal(random.rayleigh(scale=0), 0)
    +        assert_raises(ValueError, random.rayleigh, scale=-0.)
    +
    +    def test_standard_cauchy(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.standard_cauchy(size=(3, 2))
    +        desired = np.array([[-1.489437778266206, -3.275389641569784],
    +                            [ 0.560102864910406, -0.680780916282552],
    +                            [-1.314912905226277,  0.295852965660225]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_standard_exponential(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.standard_exponential(size=(3, 2), method='inv')
    +        desired = np.array([[0.102031839440643, 1.229350298474972],
    +                            [0.088137284693098, 1.459859985522667],
    +                            [1.093830802293668, 1.256977002164613]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_standard_expoential_type_error(self):
    +        assert_raises(TypeError, random.standard_exponential, dtype=np.int32)
    +
    +    def test_standard_gamma(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.standard_gamma(shape=3, size=(3, 2))
    +        desired = np.array([[0.62970724056362, 1.22379851271008],
    +                            [3.899412530884  , 4.12479964250139],  # noqa: E203
    +                            [3.74994102464584, 3.74929307690815]])
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +
    +    def test_standard_gammma_scalar_float(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.standard_gamma(3, dtype=np.float32)
    +        desired = 2.9242148399353027
    +        assert_array_almost_equal(actual, desired, decimal=6)
    +
    +    def test_standard_gamma_float(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.standard_gamma(shape=3, size=(3, 2))
    +        desired = np.array([[0.62971, 1.2238],
    +                            [3.89941, 4.1248],
    +                            [3.74994, 3.74929]])
    +        assert_array_almost_equal(actual, desired, decimal=5)
    +
    +    def test_standard_gammma_float_out(self):
    +        actual = np.zeros((3, 2), dtype=np.float32)
    +        random = Generator(MT19937(self.seed))
    +        random.standard_gamma(10.0, out=actual, dtype=np.float32)
    +        desired = np.array([[10.14987,  7.87012],
    +                             [ 9.46284, 12.56832],
    +                             [13.82495,  7.81533]], dtype=np.float32)
    +        assert_array_almost_equal(actual, desired, decimal=5)
    +
    +        random = Generator(MT19937(self.seed))
    +        random.standard_gamma(10.0, out=actual, size=(3, 2), dtype=np.float32)
    +        assert_array_almost_equal(actual, desired, decimal=5)
    +
    +    def test_standard_gamma_unknown_type(self):
    +        assert_raises(TypeError, random.standard_gamma, 1.,
    +                      dtype='int32')
    +
    +    def test_out_size_mismatch(self):
    +        out = np.zeros(10)
    +        assert_raises(ValueError, random.standard_gamma, 10.0, size=20,
    +                      out=out)
    +        assert_raises(ValueError, random.standard_gamma, 10.0, size=(10, 1),
    +                      out=out)
    +
    +    def test_standard_gamma_0(self):
    +        assert_equal(random.standard_gamma(shape=0), 0)
    +        assert_raises(ValueError, random.standard_gamma, shape=-0.)
    +
    +    def test_standard_normal(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.standard_normal(size=(3, 2))
    +        desired = np.array([[-1.870934851846581,  1.25613495182354 ],  # noqa: E202
    +                            [-1.120190126006621,  0.342002097029821],
    +                            [ 0.661545174124296,  1.181113712443012]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_standard_normal_unsupported_type(self):
    +        assert_raises(TypeError, random.standard_normal, dtype=np.int32)
    +
    +    def test_standard_t(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.standard_t(df=10, size=(3, 2))
    +        desired = np.array([[-1.484666193042647,  0.30597891831161],
    +                            [ 1.056684299648085, -0.407312602088507],
    +                            [ 0.130704414281157, -2.038053410490321]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_triangular(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.triangular(left=5.12, mode=10.23, right=20.34,
    +                                   size=(3, 2))
    +        desired = np.array([[ 7.86664070590917, 13.6313848513185 ],  # noqa: E202
    +                            [ 7.68152445215983, 14.36169131136546],
    +                            [13.16105603911429, 13.72341621856971]])
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +
    +    def test_uniform(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.uniform(low=1.23, high=10.54, size=(3, 2))
    +        desired = np.array([[2.13306255040998 , 7.816987531021207],  # noqa: E203
    +                            [2.015436610109887, 8.377577533009589],
    +                            [7.421792588856135, 7.891185744455209]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_uniform_range_bounds(self):
    +        fmin = np.finfo('float').min
    +        fmax = np.finfo('float').max
    +
    +        func = random.uniform
    +        assert_raises(OverflowError, func, -np.inf, 0)
    +        assert_raises(OverflowError, func, 0, np.inf)
    +        assert_raises(OverflowError, func, fmin, fmax)
    +        assert_raises(OverflowError, func, [-np.inf], [0])
    +        assert_raises(OverflowError, func, [0], [np.inf])
    +
    +        # (fmax / 1e17) - fmin is within range, so this should not throw
    +        # account for i386 extended precision DBL_MAX / 1e17 + DBL_MAX >
    +        # DBL_MAX by increasing fmin a bit
    +        random.uniform(low=np.nextafter(fmin, 1), high=fmax / 1e17)
    +
    +    def test_uniform_zero_range(self):
    +        func = random.uniform
    +        result = func(1.5, 1.5)
    +        assert_allclose(result, 1.5)
    +        result = func([0.0, np.pi], [0.0, np.pi])
    +        assert_allclose(result, [0.0, np.pi])
    +        result = func([[2145.12], [2145.12]], [2145.12, 2145.12])
    +        assert_allclose(result, 2145.12 + np.zeros((2, 2)))
    +
    +    def test_uniform_neg_range(self):
    +        func = random.uniform
    +        assert_raises(ValueError, func, 2, 1)
    +        assert_raises(ValueError, func,  [1, 2], [1, 1])
    +        assert_raises(ValueError, func,  [[0, 1], [2, 3]], 2)
    +
    +    def test_scalar_exception_propagation(self):
    +        # Tests that exceptions are correctly propagated in distributions
    +        # when called with objects that throw exceptions when converted to
    +        # scalars.
    +        #
    +        # Regression test for gh: 8865
    +
    +        class ThrowingFloat(np.ndarray):
    +            def __float__(self):
    +                raise TypeError
    +
    +        throwing_float = np.array(1.0).view(ThrowingFloat)
    +        assert_raises(TypeError, random.uniform, throwing_float,
    +                      throwing_float)
    +
    +        class ThrowingInteger(np.ndarray):
    +            def __int__(self):
    +                raise TypeError
    +
    +        throwing_int = np.array(1).view(ThrowingInteger)
    +        assert_raises(TypeError, random.hypergeometric, throwing_int, 1, 1)
    +
    +    def test_vonmises(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.vonmises(mu=1.23, kappa=1.54, size=(3, 2))
    +        desired = np.array([[ 1.107972248690106,  2.841536476232361],
    +                            [ 1.832602376042457,  1.945511926976032],
    +                            [-0.260147475776542,  2.058047492231698]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_vonmises_small(self):
    +        # check infinite loop, gh-4720
    +        random = Generator(MT19937(self.seed))
    +        r = random.vonmises(mu=0., kappa=1.1e-8, size=10**6)
    +        assert_(np.isfinite(r).all())
    +
    +    def test_vonmises_nan(self):
    +        random = Generator(MT19937(self.seed))
    +        r = random.vonmises(mu=0., kappa=np.nan)
    +        assert_(np.isnan(r))
    +
    +    @pytest.mark.parametrize("kappa", [1e4, 1e15])
    +    def test_vonmises_large_kappa(self, kappa):
    +        random = Generator(MT19937(self.seed))
    +        rs = RandomState(random.bit_generator)
    +        state = random.bit_generator.state
    +
    +        random_state_vals = rs.vonmises(0, kappa, size=10)
    +        random.bit_generator.state = state
    +        gen_vals = random.vonmises(0, kappa, size=10)
    +        if kappa < 1e6:
    +            assert_allclose(random_state_vals, gen_vals)
    +        else:
    +            assert np.all(random_state_vals != gen_vals)
    +
    +    @pytest.mark.parametrize("mu", [-7., -np.pi, -3.1, np.pi, 3.2])
    +    @pytest.mark.parametrize("kappa", [1e-9, 1e-6, 1, 1e3, 1e15])
    +    def test_vonmises_large_kappa_range(self, mu, kappa):
    +        random = Generator(MT19937(self.seed))
    +        r = random.vonmises(mu, kappa, 50)
    +        assert_(np.all(r > -np.pi) and np.all(r <= np.pi))
    +
    +    def test_wald(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.wald(mean=1.23, scale=1.54, size=(3, 2))
    +        desired = np.array([[0.26871721804551, 3.2233942732115 ],  # noqa: E202
    +                            [2.20328374987066, 2.40958405189353],
    +                            [2.07093587449261, 0.73073890064369]])
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +
    +    def test_wald_nonnegative(self):
    +        random = Generator(MT19937(self.seed))
    +        samples = random.wald(mean=1e9, scale=2.25, size=1000)
    +        assert_(np.all(samples >= 0.0))
    +
    +    def test_weibull(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.weibull(a=1.23, size=(3, 2))
    +        desired = np.array([[0.138613914769468, 1.306463419753191],
    +                            [0.111623365934763, 1.446570494646721],
    +                            [1.257145775276011, 1.914247725027957]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_weibull_0(self):
    +        random = Generator(MT19937(self.seed))
    +        assert_equal(random.weibull(a=0, size=12), np.zeros(12))
    +        assert_raises(ValueError, random.weibull, a=-0.)
    +
    +    def test_zipf(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.zipf(a=1.23, size=(3, 2))
    +        desired = np.array([[  1,   1],
    +                            [ 10, 867],
    +                            [354,   2]])
    +        assert_array_equal(actual, desired)
    +
    +
    +class TestBroadcast:
    +    # tests that functions that broadcast behave
    +    # correctly when presented with non-scalar arguments
    +    seed = 123456789
    +
    +    def test_uniform(self):
    +        random = Generator(MT19937(self.seed))
    +        low = [0]
    +        high = [1]
    +        uniform = random.uniform
    +        desired = np.array([0.16693771389729, 0.19635129550675, 0.75563050964095])
    +
    +        random = Generator(MT19937(self.seed))
    +        actual = random.uniform(low * 3, high)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +
    +        random = Generator(MT19937(self.seed))
    +        actual = random.uniform(low, high * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +
    +    def test_normal(self):
    +        loc = [0]
    +        scale = [1]
    +        bad_scale = [-1]
    +        random = Generator(MT19937(self.seed))
    +        desired = np.array([-0.38736406738527, 0.79594375042255, 0.0197076236097])
    +
    +        random = Generator(MT19937(self.seed))
    +        actual = random.normal(loc * 3, scale)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, random.normal, loc * 3, bad_scale)
    +
    +        random = Generator(MT19937(self.seed))
    +        normal = random.normal
    +        actual = normal(loc, scale * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, normal, loc, bad_scale * 3)
    +
    +    def test_beta(self):
    +        a = [1]
    +        b = [2]
    +        bad_a = [-1]
    +        bad_b = [-2]
    +        desired = np.array([0.18719338682602, 0.73234824491364, 0.17928615186455])
    +
    +        random = Generator(MT19937(self.seed))
    +        beta = random.beta
    +        actual = beta(a * 3, b)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, beta, bad_a * 3, b)
    +        assert_raises(ValueError, beta, a * 3, bad_b)
    +
    +        random = Generator(MT19937(self.seed))
    +        actual = random.beta(a, b * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +
    +    def test_exponential(self):
    +        scale = [1]
    +        bad_scale = [-1]
    +        desired = np.array([0.67245993212806, 0.21380495318094, 0.7177848928629])
    +
    +        random = Generator(MT19937(self.seed))
    +        actual = random.exponential(scale * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, random.exponential, bad_scale * 3)
    +
    +    def test_standard_gamma(self):
    +        shape = [1]
    +        bad_shape = [-1]
    +        desired = np.array([0.67245993212806, 0.21380495318094, 0.7177848928629])
    +
    +        random = Generator(MT19937(self.seed))
    +        std_gamma = random.standard_gamma
    +        actual = std_gamma(shape * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, std_gamma, bad_shape * 3)
    +
    +    def test_gamma(self):
    +        shape = [1]
    +        scale = [2]
    +        bad_shape = [-1]
    +        bad_scale = [-2]
    +        desired = np.array([1.34491986425611, 0.42760990636187, 1.4355697857258])
    +
    +        random = Generator(MT19937(self.seed))
    +        gamma = random.gamma
    +        actual = gamma(shape * 3, scale)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, gamma, bad_shape * 3, scale)
    +        assert_raises(ValueError, gamma, shape * 3, bad_scale)
    +
    +        random = Generator(MT19937(self.seed))
    +        gamma = random.gamma
    +        actual = gamma(shape, scale * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, gamma, bad_shape, scale * 3)
    +        assert_raises(ValueError, gamma, shape, bad_scale * 3)
    +
    +    def test_f(self):
    +        dfnum = [1]
    +        dfden = [2]
    +        bad_dfnum = [-1]
    +        bad_dfden = [-2]
    +        desired = np.array([0.07765056244107, 7.72951397913186, 0.05786093891763])
    +
    +        random = Generator(MT19937(self.seed))
    +        f = random.f
    +        actual = f(dfnum * 3, dfden)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, f, bad_dfnum * 3, dfden)
    +        assert_raises(ValueError, f, dfnum * 3, bad_dfden)
    +
    +        random = Generator(MT19937(self.seed))
    +        f = random.f
    +        actual = f(dfnum, dfden * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, f, bad_dfnum, dfden * 3)
    +        assert_raises(ValueError, f, dfnum, bad_dfden * 3)
    +
    +    def test_noncentral_f(self):
    +        dfnum = [2]
    +        dfden = [3]
    +        nonc = [4]
    +        bad_dfnum = [0]
    +        bad_dfden = [-1]
    +        bad_nonc = [-2]
    +        desired = np.array([2.02434240411421, 12.91838601070124, 1.24395160354629])
    +
    +        random = Generator(MT19937(self.seed))
    +        nonc_f = random.noncentral_f
    +        actual = nonc_f(dfnum * 3, dfden, nonc)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert np.all(np.isnan(nonc_f(dfnum, dfden, [np.nan] * 3)))
    +
    +        assert_raises(ValueError, nonc_f, bad_dfnum * 3, dfden, nonc)
    +        assert_raises(ValueError, nonc_f, dfnum * 3, bad_dfden, nonc)
    +        assert_raises(ValueError, nonc_f, dfnum * 3, dfden, bad_nonc)
    +
    +        random = Generator(MT19937(self.seed))
    +        nonc_f = random.noncentral_f
    +        actual = nonc_f(dfnum, dfden * 3, nonc)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, nonc_f, bad_dfnum, dfden * 3, nonc)
    +        assert_raises(ValueError, nonc_f, dfnum, bad_dfden * 3, nonc)
    +        assert_raises(ValueError, nonc_f, dfnum, dfden * 3, bad_nonc)
    +
    +        random = Generator(MT19937(self.seed))
    +        nonc_f = random.noncentral_f
    +        actual = nonc_f(dfnum, dfden, nonc * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, nonc_f, bad_dfnum, dfden, nonc * 3)
    +        assert_raises(ValueError, nonc_f, dfnum, bad_dfden, nonc * 3)
    +        assert_raises(ValueError, nonc_f, dfnum, dfden, bad_nonc * 3)
    +
    +    def test_noncentral_f_small_df(self):
    +        random = Generator(MT19937(self.seed))
    +        desired = np.array([0.04714867120827, 0.1239390327694])
    +        actual = random.noncentral_f(0.9, 0.9, 2, size=2)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +
    +    def test_chisquare(self):
    +        df = [1]
    +        bad_df = [-1]
    +        desired = np.array([0.05573640064251, 1.47220224353539, 2.9469379318589])
    +
    +        random = Generator(MT19937(self.seed))
    +        actual = random.chisquare(df * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, random.chisquare, bad_df * 3)
    +
    +    def test_noncentral_chisquare(self):
    +        df = [1]
    +        nonc = [2]
    +        bad_df = [-1]
    +        bad_nonc = [-2]
    +        desired = np.array([0.07710766249436, 5.27829115110304, 0.630732147399])
    +
    +        random = Generator(MT19937(self.seed))
    +        nonc_chi = random.noncentral_chisquare
    +        actual = nonc_chi(df * 3, nonc)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, nonc_chi, bad_df * 3, nonc)
    +        assert_raises(ValueError, nonc_chi, df * 3, bad_nonc)
    +
    +        random = Generator(MT19937(self.seed))
    +        nonc_chi = random.noncentral_chisquare
    +        actual = nonc_chi(df, nonc * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, nonc_chi, bad_df, nonc * 3)
    +        assert_raises(ValueError, nonc_chi, df, bad_nonc * 3)
    +
    +    def test_standard_t(self):
    +        df = [1]
    +        bad_df = [-1]
    +        desired = np.array([-1.39498829447098, -1.23058658835223, 0.17207021065983])
    +
    +        random = Generator(MT19937(self.seed))
    +        actual = random.standard_t(df * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, random.standard_t, bad_df * 3)
    +
    +    def test_vonmises(self):
    +        mu = [2]
    +        kappa = [1]
    +        bad_kappa = [-1]
    +        desired = np.array([2.25935584988528, 2.23326261461399, -2.84152146503326])
    +
    +        random = Generator(MT19937(self.seed))
    +        actual = random.vonmises(mu * 3, kappa)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, random.vonmises, mu * 3, bad_kappa)
    +
    +        random = Generator(MT19937(self.seed))
    +        actual = random.vonmises(mu, kappa * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, random.vonmises, mu, bad_kappa * 3)
    +
    +    def test_pareto(self):
    +        a = [1]
    +        bad_a = [-1]
    +        desired = np.array([0.95905052946317, 0.2383810889437, 1.04988745750013])
    +
    +        random = Generator(MT19937(self.seed))
    +        actual = random.pareto(a * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, random.pareto, bad_a * 3)
    +
    +    def test_weibull(self):
    +        a = [1]
    +        bad_a = [-1]
    +        desired = np.array([0.67245993212806, 0.21380495318094, 0.7177848928629])
    +
    +        random = Generator(MT19937(self.seed))
    +        actual = random.weibull(a * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, random.weibull, bad_a * 3)
    +
    +    def test_power(self):
    +        a = [1]
    +        bad_a = [-1]
    +        desired = np.array([0.48954864361052, 0.19249412888486, 0.51216834058807])
    +
    +        random = Generator(MT19937(self.seed))
    +        actual = random.power(a * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, random.power, bad_a * 3)
    +
    +    def test_laplace(self):
    +        loc = [0]
    +        scale = [1]
    +        bad_scale = [-1]
    +        desired = np.array([-1.09698732625119, -0.93470271947368, 0.71592671378202])
    +
    +        random = Generator(MT19937(self.seed))
    +        laplace = random.laplace
    +        actual = laplace(loc * 3, scale)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, laplace, loc * 3, bad_scale)
    +
    +        random = Generator(MT19937(self.seed))
    +        laplace = random.laplace
    +        actual = laplace(loc, scale * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, laplace, loc, bad_scale * 3)
    +
    +    def test_gumbel(self):
    +        loc = [0]
    +        scale = [1]
    +        bad_scale = [-1]
    +        desired = np.array([1.70020068231762, 1.52054354273631, -0.34293267607081])
    +
    +        random = Generator(MT19937(self.seed))
    +        gumbel = random.gumbel
    +        actual = gumbel(loc * 3, scale)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, gumbel, loc * 3, bad_scale)
    +
    +        random = Generator(MT19937(self.seed))
    +        gumbel = random.gumbel
    +        actual = gumbel(loc, scale * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, gumbel, loc, bad_scale * 3)
    +
    +    def test_logistic(self):
    +        loc = [0]
    +        scale = [1]
    +        bad_scale = [-1]
    +        desired = np.array([-1.607487640433, -1.40925686003678, 1.12887112820397])
    +
    +        random = Generator(MT19937(self.seed))
    +        actual = random.logistic(loc * 3, scale)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, random.logistic, loc * 3, bad_scale)
    +
    +        random = Generator(MT19937(self.seed))
    +        actual = random.logistic(loc, scale * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, random.logistic, loc, bad_scale * 3)
    +        assert_equal(random.logistic(1.0, 0.0), 1.0)
    +
    +    def test_lognormal(self):
    +        mean = [0]
    +        sigma = [1]
    +        bad_sigma = [-1]
    +        desired = np.array([0.67884390500697, 2.21653186290321, 1.01990310084276])
    +
    +        random = Generator(MT19937(self.seed))
    +        lognormal = random.lognormal
    +        actual = lognormal(mean * 3, sigma)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, lognormal, mean * 3, bad_sigma)
    +
    +        random = Generator(MT19937(self.seed))
    +        actual = random.lognormal(mean, sigma * 3)
    +        assert_raises(ValueError, random.lognormal, mean, bad_sigma * 3)
    +
    +    def test_rayleigh(self):
    +        scale = [1]
    +        bad_scale = [-1]
    +        desired = np.array(
    +            [1.1597068009872629,
    +             0.6539188836253857,
    +             1.1981526554349398]
    +        )
    +
    +        random = Generator(MT19937(self.seed))
    +        actual = random.rayleigh(scale * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, random.rayleigh, bad_scale * 3)
    +
    +    def test_wald(self):
    +        mean = [0.5]
    +        scale = [1]
    +        bad_mean = [0]
    +        bad_scale = [-2]
    +        desired = np.array([0.38052407392905, 0.50701641508592, 0.484935249864])
    +
    +        random = Generator(MT19937(self.seed))
    +        actual = random.wald(mean * 3, scale)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, random.wald, bad_mean * 3, scale)
    +        assert_raises(ValueError, random.wald, mean * 3, bad_scale)
    +
    +        random = Generator(MT19937(self.seed))
    +        actual = random.wald(mean, scale * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, random.wald, bad_mean, scale * 3)
    +        assert_raises(ValueError, random.wald, mean, bad_scale * 3)
    +
    +    def test_triangular(self):
    +        left = [1]
    +        right = [3]
    +        mode = [2]
    +        bad_left_one = [3]
    +        bad_mode_one = [4]
    +        bad_left_two, bad_mode_two = right * 2
    +        desired = np.array([1.57781954604754, 1.62665986867957, 2.30090130831326])
    +
    +        random = Generator(MT19937(self.seed))
    +        triangular = random.triangular
    +        actual = triangular(left * 3, mode, right)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, triangular, bad_left_one * 3, mode, right)
    +        assert_raises(ValueError, triangular, left * 3, bad_mode_one, right)
    +        assert_raises(ValueError, triangular, bad_left_two * 3, bad_mode_two,
    +                      right)
    +
    +        random = Generator(MT19937(self.seed))
    +        triangular = random.triangular
    +        actual = triangular(left, mode * 3, right)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, triangular, bad_left_one, mode * 3, right)
    +        assert_raises(ValueError, triangular, left, bad_mode_one * 3, right)
    +        assert_raises(ValueError, triangular, bad_left_two, bad_mode_two * 3,
    +                      right)
    +
    +        random = Generator(MT19937(self.seed))
    +        triangular = random.triangular
    +        actual = triangular(left, mode, right * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, triangular, bad_left_one, mode, right * 3)
    +        assert_raises(ValueError, triangular, left, bad_mode_one, right * 3)
    +        assert_raises(ValueError, triangular, bad_left_two, bad_mode_two,
    +                      right * 3)
    +
    +        assert_raises(ValueError, triangular, 10., 0., 20.)
    +        assert_raises(ValueError, triangular, 10., 25., 20.)
    +        assert_raises(ValueError, triangular, 10., 10., 10.)
    +
    +    def test_binomial(self):
    +        n = [1]
    +        p = [0.5]
    +        bad_n = [-1]
    +        bad_p_one = [-1]
    +        bad_p_two = [1.5]
    +        desired = np.array([0, 0, 1])
    +
    +        random = Generator(MT19937(self.seed))
    +        binom = random.binomial
    +        actual = binom(n * 3, p)
    +        assert_array_equal(actual, desired)
    +        assert_raises(ValueError, binom, bad_n * 3, p)
    +        assert_raises(ValueError, binom, n * 3, bad_p_one)
    +        assert_raises(ValueError, binom, n * 3, bad_p_two)
    +
    +        random = Generator(MT19937(self.seed))
    +        actual = random.binomial(n, p * 3)
    +        assert_array_equal(actual, desired)
    +        assert_raises(ValueError, binom, bad_n, p * 3)
    +        assert_raises(ValueError, binom, n, bad_p_one * 3)
    +        assert_raises(ValueError, binom, n, bad_p_two * 3)
    +
    +    def test_negative_binomial(self):
    +        n = [1]
    +        p = [0.5]
    +        bad_n = [-1]
    +        bad_p_one = [-1]
    +        bad_p_two = [1.5]
    +        desired = np.array([0, 2, 1], dtype=np.int64)
    +
    +        random = Generator(MT19937(self.seed))
    +        neg_binom = random.negative_binomial
    +        actual = neg_binom(n * 3, p)
    +        assert_array_equal(actual, desired)
    +        assert_raises(ValueError, neg_binom, bad_n * 3, p)
    +        assert_raises(ValueError, neg_binom, n * 3, bad_p_one)
    +        assert_raises(ValueError, neg_binom, n * 3, bad_p_two)
    +
    +        random = Generator(MT19937(self.seed))
    +        neg_binom = random.negative_binomial
    +        actual = neg_binom(n, p * 3)
    +        assert_array_equal(actual, desired)
    +        assert_raises(ValueError, neg_binom, bad_n, p * 3)
    +        assert_raises(ValueError, neg_binom, n, bad_p_one * 3)
    +        assert_raises(ValueError, neg_binom, n, bad_p_two * 3)
    +
    +    def test_poisson(self):
    +
    +        lam = [1]
    +        bad_lam_one = [-1]
    +        desired = np.array([0, 0, 3])
    +
    +        random = Generator(MT19937(self.seed))
    +        max_lam = random._poisson_lam_max
    +        bad_lam_two = [max_lam * 2]
    +        poisson = random.poisson
    +        actual = poisson(lam * 3)
    +        assert_array_equal(actual, desired)
    +        assert_raises(ValueError, poisson, bad_lam_one * 3)
    +        assert_raises(ValueError, poisson, bad_lam_two * 3)
    +
    +    def test_zipf(self):
    +        a = [2]
    +        bad_a = [0]
    +        desired = np.array([1, 8, 1])
    +
    +        random = Generator(MT19937(self.seed))
    +        zipf = random.zipf
    +        actual = zipf(a * 3)
    +        assert_array_equal(actual, desired)
    +        assert_raises(ValueError, zipf, bad_a * 3)
    +        with np.errstate(invalid='ignore'):
    +            assert_raises(ValueError, zipf, np.nan)
    +            assert_raises(ValueError, zipf, [0, 0, np.nan])
    +
    +    def test_geometric(self):
    +        p = [0.5]
    +        bad_p_one = [-1]
    +        bad_p_two = [1.5]
    +        desired = np.array([1, 1, 3])
    +
    +        random = Generator(MT19937(self.seed))
    +        geometric = random.geometric
    +        actual = geometric(p * 3)
    +        assert_array_equal(actual, desired)
    +        assert_raises(ValueError, geometric, bad_p_one * 3)
    +        assert_raises(ValueError, geometric, bad_p_two * 3)
    +
    +    def test_hypergeometric(self):
    +        ngood = [1]
    +        nbad = [2]
    +        nsample = [2]
    +        bad_ngood = [-1]
    +        bad_nbad = [-2]
    +        bad_nsample_one = [-1]
    +        bad_nsample_two = [4]
    +        desired = np.array([0, 0, 1])
    +
    +        random = Generator(MT19937(self.seed))
    +        actual = random.hypergeometric(ngood * 3, nbad, nsample)
    +        assert_array_equal(actual, desired)
    +        assert_raises(ValueError, random.hypergeometric, bad_ngood * 3, nbad, nsample)
    +        assert_raises(ValueError, random.hypergeometric, ngood * 3, bad_nbad, nsample)
    +        assert_raises(ValueError, random.hypergeometric, ngood * 3, nbad, bad_nsample_one)  # noqa: E501
    +        assert_raises(ValueError, random.hypergeometric, ngood * 3, nbad, bad_nsample_two)  # noqa: E501
    +
    +        random = Generator(MT19937(self.seed))
    +        actual = random.hypergeometric(ngood, nbad * 3, nsample)
    +        assert_array_equal(actual, desired)
    +        assert_raises(ValueError, random.hypergeometric, bad_ngood, nbad * 3, nsample)
    +        assert_raises(ValueError, random.hypergeometric, ngood, bad_nbad * 3, nsample)
    +        assert_raises(ValueError, random.hypergeometric, ngood, nbad * 3, bad_nsample_one)  # noqa: E501
    +        assert_raises(ValueError, random.hypergeometric, ngood, nbad * 3, bad_nsample_two)  # noqa: E501
    +
    +        random = Generator(MT19937(self.seed))
    +        hypergeom = random.hypergeometric
    +        actual = hypergeom(ngood, nbad, nsample * 3)
    +        assert_array_equal(actual, desired)
    +        assert_raises(ValueError, hypergeom, bad_ngood, nbad, nsample * 3)
    +        assert_raises(ValueError, hypergeom, ngood, bad_nbad, nsample * 3)
    +        assert_raises(ValueError, hypergeom, ngood, nbad, bad_nsample_one * 3)
    +        assert_raises(ValueError, hypergeom, ngood, nbad, bad_nsample_two * 3)
    +
    +        assert_raises(ValueError, hypergeom, -1, 10, 20)
    +        assert_raises(ValueError, hypergeom, 10, -1, 20)
    +        assert_raises(ValueError, hypergeom, 10, 10, -1)
    +        assert_raises(ValueError, hypergeom, 10, 10, 25)
    +
    +        # ValueError for arguments that are too big.
    +        assert_raises(ValueError, hypergeom, 2**30, 10, 20)
    +        assert_raises(ValueError, hypergeom, 999, 2**31, 50)
    +        assert_raises(ValueError, hypergeom, 999, [2**29, 2**30], 1000)
    +
    +    def test_logseries(self):
    +        p = [0.5]
    +        bad_p_one = [2]
    +        bad_p_two = [-1]
    +        desired = np.array([1, 1, 1])
    +
    +        random = Generator(MT19937(self.seed))
    +        logseries = random.logseries
    +        actual = logseries(p * 3)
    +        assert_array_equal(actual, desired)
    +        assert_raises(ValueError, logseries, bad_p_one * 3)
    +        assert_raises(ValueError, logseries, bad_p_two * 3)
    +
    +    def test_multinomial(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.multinomial([5, 20], [1 / 6.] * 6, size=(3, 2))
    +        desired = np.array([[[0, 0, 2, 1, 2, 0],
    +                             [2, 3, 6, 4, 2, 3]],
    +                            [[1, 0, 1, 0, 2, 1],
    +                             [7, 2, 2, 1, 4, 4]],
    +                            [[0, 2, 0, 1, 2, 0],
    +                             [3, 2, 3, 3, 4, 5]]], dtype=np.int64)
    +        assert_array_equal(actual, desired)
    +
    +        random = Generator(MT19937(self.seed))
    +        actual = random.multinomial([5, 20], [1 / 6.] * 6)
    +        desired = np.array([[0, 0, 2, 1, 2, 0],
    +                            [2, 3, 6, 4, 2, 3]], dtype=np.int64)
    +        assert_array_equal(actual, desired)
    +
    +        random = Generator(MT19937(self.seed))
    +        actual = random.multinomial([5, 20], [[1 / 6.] * 6] * 2)
    +        desired = np.array([[0, 0, 2, 1, 2, 0],
    +                            [2, 3, 6, 4, 2, 3]], dtype=np.int64)
    +        assert_array_equal(actual, desired)
    +
    +        random = Generator(MT19937(self.seed))
    +        actual = random.multinomial([[5], [20]], [[1 / 6.] * 6] * 2)
    +        desired = np.array([[[0, 0, 2, 1, 2, 0],
    +                             [0, 0, 2, 1, 1, 1]],
    +                            [[4, 2, 3, 3, 5, 3],
    +                             [7, 2, 2, 1, 4, 4]]], dtype=np.int64)
    +        assert_array_equal(actual, desired)
    +
    +    @pytest.mark.parametrize("n", [10,
    +                                   np.array([10, 10]),
    +                                   np.array([[[10]], [[10]]])
    +                                   ]
    +                             )
    +    def test_multinomial_pval_broadcast(self, n):
    +        random = Generator(MT19937(self.seed))
    +        pvals = np.array([1 / 4] * 4)
    +        actual = random.multinomial(n, pvals)
    +        n_shape = () if isinstance(n, int) else n.shape
    +        expected_shape = n_shape + (4,)
    +        assert actual.shape == expected_shape
    +        pvals = np.vstack([pvals, pvals])
    +        actual = random.multinomial(n, pvals)
    +        expected_shape = np.broadcast_shapes(n_shape, pvals.shape[:-1]) + (4,)
    +        assert actual.shape == expected_shape
    +
    +        pvals = np.vstack([[pvals], [pvals]])
    +        actual = random.multinomial(n, pvals)
    +        expected_shape = np.broadcast_shapes(n_shape, pvals.shape[:-1])
    +        assert actual.shape == expected_shape + (4,)
    +        actual = random.multinomial(n, pvals, size=(3, 2) + expected_shape)
    +        assert actual.shape == (3, 2) + expected_shape + (4,)
    +
    +        with pytest.raises(ValueError):
    +            # Ensure that size is not broadcast
    +            actual = random.multinomial(n, pvals, size=(1,) * 6)
    +
    +    def test_invalid_pvals_broadcast(self):
    +        random = Generator(MT19937(self.seed))
    +        pvals = [[1 / 6] * 6, [1 / 4] * 6]
    +        assert_raises(ValueError, random.multinomial, 1, pvals)
    +        assert_raises(ValueError, random.multinomial, 6, 0.5)
    +
    +    def test_empty_outputs(self):
    +        random = Generator(MT19937(self.seed))
    +        actual = random.multinomial(np.empty((10, 0, 6), "i8"), [1 / 6] * 6)
    +        assert actual.shape == (10, 0, 6, 6)
    +        actual = random.multinomial(12, np.empty((10, 0, 10)))
    +        assert actual.shape == (10, 0, 10)
    +        actual = random.multinomial(np.empty((3, 0, 7), "i8"),
    +                                    np.empty((3, 0, 7, 4)))
    +        assert actual.shape == (3, 0, 7, 4)
    +
    +
    +@pytest.mark.skipif(IS_WASM, reason="can't start thread")
    +class TestThread:
    +    # make sure each state produces the same sequence even in threads
    +    seeds = range(4)
    +
    +    def check_function(self, function, sz):
    +        from threading import Thread
    +
    +        out1 = np.empty((len(self.seeds),) + sz)
    +        out2 = np.empty((len(self.seeds),) + sz)
    +
    +        # threaded generation
    +        t = [Thread(target=function, args=(Generator(MT19937(s)), o))
    +             for s, o in zip(self.seeds, out1)]
    +        [x.start() for x in t]
    +        [x.join() for x in t]
    +
    +        # the same serial
    +        for s, o in zip(self.seeds, out2):
    +            function(Generator(MT19937(s)), o)
    +
    +        # these platforms change x87 fpu precision mode in threads
    +        if np.intp().dtype.itemsize == 4 and sys.platform == "win32":
    +            assert_array_almost_equal(out1, out2)
    +        else:
    +            assert_array_equal(out1, out2)
    +
    +    def test_normal(self):
    +        def gen_random(state, out):
    +            out[...] = state.normal(size=10000)
    +
    +        self.check_function(gen_random, sz=(10000,))
    +
    +    def test_exp(self):
    +        def gen_random(state, out):
    +            out[...] = state.exponential(scale=np.ones((100, 1000)))
    +
    +        self.check_function(gen_random, sz=(100, 1000))
    +
    +    def test_multinomial(self):
    +        def gen_random(state, out):
    +            out[...] = state.multinomial(10, [1 / 6.] * 6, size=10000)
    +
    +        self.check_function(gen_random, sz=(10000, 6))
    +
    +
    +# See Issue #4263
    +class TestSingleEltArrayInput:
    +    def _create_arrays(self):
    +        return np.array([2]), np.array([3]), np.array([4]), (1,)
    +
    +    def test_one_arg_funcs(self):
    +        argOne, _, _, tgtShape = self._create_arrays()
    +        funcs = (random.exponential, random.standard_gamma,
    +                 random.chisquare, random.standard_t,
    +                 random.pareto, random.weibull,
    +                 random.power, random.rayleigh,
    +                 random.poisson, random.zipf,
    +                 random.geometric, random.logseries)
    +
    +        probfuncs = (random.geometric, random.logseries)
    +
    +        for func in funcs:
    +            if func in probfuncs:  # p < 1.0
    +                out = func(np.array([0.5]))
    +
    +            else:
    +                out = func(argOne)
    +
    +            assert_equal(out.shape, tgtShape)
    +
    +    def test_two_arg_funcs(self):
    +        argOne, argTwo, _, tgtShape = self._create_arrays()
    +        funcs = (random.uniform, random.normal,
    +                 random.beta, random.gamma,
    +                 random.f, random.noncentral_chisquare,
    +                 random.vonmises, random.laplace,
    +                 random.gumbel, random.logistic,
    +                 random.lognormal, random.wald,
    +                 random.binomial, random.negative_binomial)
    +
    +        probfuncs = (random.binomial, random.negative_binomial)
    +
    +        for func in funcs:
    +            if func in probfuncs:  # p <= 1
    +                argTwo = np.array([0.5])
    +
    +            else:
    +                argTwo = argTwo
    +
    +            out = func(argOne, argTwo)
    +            assert_equal(out.shape, tgtShape)
    +
    +            out = func(argOne[0], argTwo)
    +            assert_equal(out.shape, tgtShape)
    +
    +            out = func(argOne, argTwo[0])
    +            assert_equal(out.shape, tgtShape)
    +
    +    def test_integers(self, endpoint):
    +        _, _, _, tgtShape = self._create_arrays()
    +        itype = [np.bool, np.int8, np.uint8, np.int16, np.uint16,
    +                 np.int32, np.uint32, np.int64, np.uint64]
    +        func = random.integers
    +        high = np.array([1])
    +        low = np.array([0])
    +
    +        for dt in itype:
    +            out = func(low, high, endpoint=endpoint, dtype=dt)
    +            assert_equal(out.shape, tgtShape)
    +
    +            out = func(low[0], high, endpoint=endpoint, dtype=dt)
    +            assert_equal(out.shape, tgtShape)
    +
    +            out = func(low, high[0], endpoint=endpoint, dtype=dt)
    +            assert_equal(out.shape, tgtShape)
    +
    +    def test_three_arg_funcs(self):
    +        argOne, argTwo, argThree, tgtShape = self._create_arrays()
    +        funcs = [random.noncentral_f, random.triangular,
    +                 random.hypergeometric]
    +
    +        for func in funcs:
    +            out = func(argOne, argTwo, argThree)
    +            assert_equal(out.shape, tgtShape)
    +
    +            out = func(argOne[0], argTwo, argThree)
    +            assert_equal(out.shape, tgtShape)
    +
    +            out = func(argOne, argTwo[0], argThree)
    +            assert_equal(out.shape, tgtShape)
    +
    +
    +@pytest.mark.parametrize("config", JUMP_TEST_DATA)
    +def test_jumped(config):
    +    # Each config contains the initial seed, a number of raw steps
    +    # the sha256 hashes of the initial and the final states' keys and
    +    # the position of the initial and the final state.
    +    # These were produced using the original C implementation.
    +    seed = config["seed"]
    +    steps = config["steps"]
    +
    +    mt19937 = MT19937(seed)
    +    # Burn step
    +    mt19937.random_raw(steps)
    +    key = mt19937.state["state"]["key"]
    +    if sys.byteorder == 'big':
    +        key = key.byteswap()
    +    sha256 = hashlib.sha256(key)
    +    assert mt19937.state["state"]["pos"] == config["initial"]["pos"]
    +    assert sha256.hexdigest() == config["initial"]["key_sha256"]
    +
    +    jumped = mt19937.jumped()
    +    key = jumped.state["state"]["key"]
    +    if sys.byteorder == 'big':
    +        key = key.byteswap()
    +    sha256 = hashlib.sha256(key)
    +    assert jumped.state["state"]["pos"] == config["jumped"]["pos"]
    +    assert sha256.hexdigest() == config["jumped"]["key_sha256"]
    +
    +
    +def test_broadcast_size_error():
    +    mu = np.ones(3)
    +    sigma = np.ones((4, 3))
    +    size = (10, 4, 2)
    +    assert random.normal(mu, sigma, size=(5, 4, 3)).shape == (5, 4, 3)
    +    with pytest.raises(ValueError):
    +        random.normal(mu, sigma, size=size)
    +    with pytest.raises(ValueError):
    +        random.normal(mu, sigma, size=(1, 3))
    +    with pytest.raises(ValueError):
    +        random.normal(mu, sigma, size=(4, 1, 1))
    +    # 1 arg
    +    shape = np.ones((4, 3))
    +    with pytest.raises(ValueError):
    +        random.standard_gamma(shape, size=size)
    +    with pytest.raises(ValueError):
    +        random.standard_gamma(shape, size=(3,))
    +    with pytest.raises(ValueError):
    +        random.standard_gamma(shape, size=3)
    +    # Check out
    +    out = np.empty(size)
    +    with pytest.raises(ValueError):
    +        random.standard_gamma(shape, out=out)
    +
    +    # 2 arg
    +    with pytest.raises(ValueError):
    +        random.binomial(1, [0.3, 0.7], size=(2, 1))
    +    with pytest.raises(ValueError):
    +        random.binomial([1, 2], 0.3, size=(2, 1))
    +    with pytest.raises(ValueError):
    +        random.binomial([1, 2], [0.3, 0.7], size=(2, 1))
    +    with pytest.raises(ValueError):
    +        random.multinomial([2, 2], [.3, .7], size=(2, 1))
    +
    +    # 3 arg
    +    a = random.chisquare(5, size=3)
    +    b = random.chisquare(5, size=(4, 3))
    +    c = random.chisquare(5, size=(5, 4, 3))
    +    assert random.noncentral_f(a, b, c).shape == (5, 4, 3)
    +    with pytest.raises(ValueError, match=r"Output size \(6, 5, 1, 1\) is"):
    +        random.noncentral_f(a, b, c, size=(6, 5, 1, 1))
    +
    +
    +def test_broadcast_size_scalar():
    +    mu = np.ones(3)
    +    sigma = np.ones(3)
    +    random.normal(mu, sigma, size=3)
    +    with pytest.raises(ValueError):
    +        random.normal(mu, sigma, size=2)
    +
    +
    +def test_ragged_shuffle():
    +    # GH 18142
    +    seq = [[], [], 1]
    +    gen = Generator(MT19937(0))
    +    assert_no_warnings(gen.shuffle, seq)
    +    assert seq == [1, [], []]
    +
    +
    +@pytest.mark.parametrize("high", [-2, [-2]])
    +@pytest.mark.parametrize("endpoint", [True, False])
    +def test_single_arg_integer_exception(high, endpoint):
    +    # GH 14333
    +    gen = Generator(MT19937(0))
    +    msg = 'high < 0' if endpoint else 'high <= 0'
    +    with pytest.raises(ValueError, match=msg):
    +        gen.integers(high, endpoint=endpoint)
    +    msg = 'low > high' if endpoint else 'low >= high'
    +    with pytest.raises(ValueError, match=msg):
    +        gen.integers(-1, high, endpoint=endpoint)
    +    with pytest.raises(ValueError, match=msg):
    +        gen.integers([-1], high, endpoint=endpoint)
    +
    +
    +@pytest.mark.parametrize("dtype", ["f4", "f8"])
    +def test_c_contig_req_out(dtype):
    +    # GH 18704
    +    out = np.empty((2, 3), order="F", dtype=dtype)
    +    shape = [1, 2, 3]
    +    with pytest.raises(ValueError, match="Supplied output array"):
    +        random.standard_gamma(shape, out=out, dtype=dtype)
    +    with pytest.raises(ValueError, match="Supplied output array"):
    +        random.standard_gamma(shape, out=out, size=out.shape, dtype=dtype)
    +
    +
    +@pytest.mark.parametrize("dtype", ["f4", "f8"])
    +@pytest.mark.parametrize("order", ["F", "C"])
    +@pytest.mark.parametrize("dist", [random.standard_normal, random.random])
    +def test_contig_req_out(dist, order, dtype):
    +    # GH 18704
    +    out = np.empty((2, 3), dtype=dtype, order=order)
    +    variates = dist(out=out, dtype=dtype)
    +    assert variates is out
    +    variates = dist(out=out, dtype=dtype, size=out.shape)
    +    assert variates is out
    +
    +
    +def test_generator_ctor_old_style_pickle():
    +    rg = np.random.Generator(np.random.PCG64DXSM(0))
    +    rg.standard_normal(1)
    +    # Directly call reduce which is used in pickling
    +    ctor, (bit_gen, ), _ = rg.__reduce__()
    +    # Simulate unpickling an old pickle that only has the name
    +    assert bit_gen.__class__.__name__ == "PCG64DXSM"
    +    print(ctor)
    +    b = ctor(*("PCG64DXSM",))
    +    print(b)
    +    b.bit_generator.state = bit_gen.state
    +    state_b = b.bit_generator.state
    +    assert bit_gen.state == state_b
    +
    +
    +def test_pickle_preserves_seed_sequence():
    +    # GH 26234
    +    # Add explicit test that bit generators preserve seed sequences
    +    import pickle
    +
    +    rg = np.random.Generator(np.random.PCG64DXSM(20240411))
    +    ss = rg.bit_generator.seed_seq
    +    rg_plk = pickle.loads(pickle.dumps(rg))
    +    ss_plk = rg_plk.bit_generator.seed_seq
    +    assert_equal(ss.state, ss_plk.state)
    +    assert_equal(ss.pool, ss_plk.pool)
    +
    +    rg.bit_generator.seed_seq.spawn(10)
    +    rg_plk = pickle.loads(pickle.dumps(rg))
    +    ss_plk = rg_plk.bit_generator.seed_seq
    +    assert_equal(ss.state, ss_plk.state)
    +
    +
    +@pytest.mark.parametrize("version", [121, 126])
    +def test_legacy_pickle(version):
    +    # Pickling format was changes in 1.22.x and in 2.0.x
    +    import gzip
    +    import pickle
    +
    +    base_path = os.path.split(os.path.abspath(__file__))[0]
    +    pkl_file = os.path.join(
    +        base_path, "data", f"generator_pcg64_np{version}.pkl.gz"
    +    )
    +    with gzip.open(pkl_file) as gz:
    +        rg = pickle.load(gz)
    +    state = rg.bit_generator.state['state']
    +
    +    assert isinstance(rg, Generator)
    +    assert isinstance(rg.bit_generator, np.random.PCG64)
    +    assert state['state'] == 35399562948360463058890781895381311971
    +    assert state['inc'] == 87136372517582989555478159403783844777
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/test_generator_mt19937_regressions.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/test_generator_mt19937_regressions.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..21093ef73eb6a1632389b7286c0798e5ec7f43de
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/test_generator_mt19937_regressions.py
    @@ -0,0 +1,221 @@
    +import pytest
    +
    +import numpy as np
    +from numpy.random import MT19937, Generator
    +from numpy.testing import assert_, assert_array_equal
    +
    +
    +class TestRegression:
    +    def _create_generator(self):
    +        return Generator(MT19937(121263137472525314065))
    +
    +    def test_vonmises_range(self):
    +        # Make sure generated random variables are in [-pi, pi].
    +        # Regression test for ticket #986.
    +        mt19937 = self._create_generator()
    +        for mu in np.linspace(-7., 7., 5):
    +            r = mt19937.vonmises(mu, 1, 50)
    +            assert_(np.all(r > -np.pi) and np.all(r <= np.pi))
    +
    +    def test_hypergeometric_range(self):
    +        # Test for ticket #921
    +        mt19937 = self._create_generator()
    +        assert_(np.all(mt19937.hypergeometric(3, 18, 11, size=10) < 4))
    +        assert_(np.all(mt19937.hypergeometric(18, 3, 11, size=10) > 0))
    +
    +        # Test for ticket #5623
    +        args = (2**20 - 2, 2**20 - 2, 2**20 - 2)  # Check for 32-bit systems
    +        assert_(mt19937.hypergeometric(*args) > 0)
    +
    +    def test_logseries_convergence(self):
    +        # Test for ticket #923
    +        mt19937 = self._create_generator()
    +        N = 1000
    +        rvsn = mt19937.logseries(0.8, size=N)
    +        # these two frequency counts should be close to theoretical
    +        # numbers with this large sample
    +        # theoretical large N result is 0.49706795
    +        freq = np.sum(rvsn == 1) / N
    +        msg = f'Frequency was {freq:f}, should be > 0.45'
    +        assert_(freq > 0.45, msg)
    +        # theoretical large N result is 0.19882718
    +        freq = np.sum(rvsn == 2) / N
    +        msg = f'Frequency was {freq:f}, should be < 0.23'
    +        assert_(freq < 0.23, msg)
    +
    +    def test_shuffle_mixed_dimension(self):
    +        # Test for trac ticket #2074
    +        for t in [[1, 2, 3, None],
    +                  [(1, 1), (2, 2), (3, 3), None],
    +                  [1, (2, 2), (3, 3), None],
    +                  [(1, 1), 2, 3, None]]:
    +            mt19937 = Generator(MT19937(12345))
    +            shuffled = np.array(t, dtype=object)
    +            mt19937.shuffle(shuffled)
    +            expected = np.array([t[2], t[0], t[3], t[1]], dtype=object)
    +            assert_array_equal(np.array(shuffled, dtype=object), expected)
    +
    +    def test_call_within_randomstate(self):
    +        # Check that custom BitGenerator does not call into global state
    +        res = np.array([1, 8, 0, 1, 5, 3, 3, 8, 1, 4])
    +        for i in range(3):
    +            mt19937 = Generator(MT19937(i))
    +            m = Generator(MT19937(4321))
    +            # If m.state is not honored, the result will change
    +            assert_array_equal(m.choice(10, size=10, p=np.ones(10) / 10.), res)
    +
    +    def test_multivariate_normal_size_types(self):
    +        # Test for multivariate_normal issue with 'size' argument.
    +        # Check that the multivariate_normal size argument can be a
    +        # numpy integer.
    +        mt19937 = self._create_generator()
    +        mt19937.multivariate_normal([0], [[0]], size=1)
    +        mt19937.multivariate_normal([0], [[0]], size=np.int_(1))
    +        mt19937.multivariate_normal([0], [[0]], size=np.int64(1))
    +
    +    def test_beta_small_parameters(self):
    +        # Test that beta with small a and b parameters does not produce
    +        # NaNs due to roundoff errors causing 0 / 0, gh-5851
    +        mt19937 = self._create_generator()
    +        x = mt19937.beta(0.0001, 0.0001, size=100)
    +        assert_(not np.any(np.isnan(x)), 'Nans in mt19937.beta')
    +
    +    def test_beta_very_small_parameters(self):
    +        # gh-24203: beta would hang with very small parameters.
    +        mt19937 = self._create_generator()
    +        mt19937.beta(1e-49, 1e-40)
    +
    +    def test_beta_ridiculously_small_parameters(self):
    +        # gh-24266: beta would generate nan when the parameters
    +        # were subnormal or a small multiple of the smallest normal.
    +        mt19937 = self._create_generator()
    +        tiny = np.finfo(1.0).tiny
    +        x = mt19937.beta(tiny / 32, tiny / 40, size=50)
    +        assert not np.any(np.isnan(x))
    +
    +    def test_beta_expected_zero_frequency(self):
    +        # gh-24475: For small a and b (e.g. a=0.0025, b=0.0025), beta
    +        # would generate too many zeros.
    +        mt19937 = self._create_generator()
    +        a = 0.0025
    +        b = 0.0025
    +        n = 1000000
    +        x = mt19937.beta(a, b, size=n)
    +        nzeros = np.count_nonzero(x == 0)
    +        # beta CDF at x = np.finfo(np.double).smallest_subnormal/2
    +        # is p = 0.0776169083131899, e.g,
    +        #
    +        #    import numpy as np
    +        #    from mpmath import mp
    +        #    mp.dps = 160
    +        #    x = mp.mpf(np.finfo(np.float64).smallest_subnormal)/2
    +        #    # CDF of the beta distribution at x:
    +        #    p = mp.betainc(a, b, x1=0, x2=x, regularized=True)
    +        #    n = 1000000
    +        #    exprected_freq = float(n*p)
    +        #
    +        expected_freq = 77616.90831318991
    +        assert 0.95 * expected_freq < nzeros < 1.05 * expected_freq
    +
    +    def test_choice_sum_of_probs_tolerance(self):
    +        # The sum of probs should be 1.0 with some tolerance.
    +        # For low precision dtypes the tolerance was too tight.
    +        # See numpy github issue 6123.
    +        mt19937 = self._create_generator()
    +        a = [1, 2, 3]
    +        counts = [4, 4, 2]
    +        for dt in np.float16, np.float32, np.float64:
    +            probs = np.array(counts, dtype=dt) / sum(counts)
    +            c = mt19937.choice(a, p=probs)
    +            assert_(c in a)
    +            with pytest.raises(ValueError):
    +                mt19937.choice(a, p=probs * 0.9)
    +
    +    def test_shuffle_of_array_of_different_length_strings(self):
    +        # Test that permuting an array of different length strings
    +        # will not cause a segfault on garbage collection
    +        # Tests gh-7710
    +        mt19937 = self._create_generator()
    +
    +        a = np.array(['a', 'a' * 1000])
    +
    +        for _ in range(100):
    +            mt19937.shuffle(a)
    +
    +        # Force Garbage Collection - should not segfault.
    +        import gc
    +        gc.collect()
    +
    +    def test_shuffle_of_array_of_objects(self):
    +        # Test that permuting an array of objects will not cause
    +        # a segfault on garbage collection.
    +        # See gh-7719
    +        mt19937 = self._create_generator()
    +        a = np.array([np.arange(1), np.arange(4)], dtype=object)
    +
    +        for _ in range(1000):
    +            mt19937.shuffle(a)
    +
    +        # Force Garbage Collection - should not segfault.
    +        import gc
    +        gc.collect()
    +
    +    def test_permutation_subclass(self):
    +
    +        class N(np.ndarray):
    +            pass
    +
    +        mt19937 = Generator(MT19937(1))
    +        orig = np.arange(3).view(N)
    +        perm = mt19937.permutation(orig)
    +        assert_array_equal(perm, np.array([2, 0, 1]))
    +        assert_array_equal(orig, np.arange(3).view(N))
    +
    +        class M:
    +            a = np.arange(5)
    +
    +            def __array__(self, dtype=None, copy=None):
    +                return self.a
    +
    +        mt19937 = Generator(MT19937(1))
    +        m = M()
    +        perm = mt19937.permutation(m)
    +        assert_array_equal(perm, np.array([4, 1, 3, 0, 2]))
    +        assert_array_equal(m.__array__(), np.arange(5))
    +
    +    def test_gamma_0(self):
    +        mt19937 = self._create_generator()
    +        assert mt19937.standard_gamma(0.0) == 0.0
    +        assert_array_equal(mt19937.standard_gamma([0.0]), 0.0)
    +
    +        actual = mt19937.standard_gamma([0.0], dtype='float')
    +        expected = np.array([0.], dtype=np.float32)
    +        assert_array_equal(actual, expected)
    +
    +    def test_geometric_tiny_prob(self):
    +        # Regression test for gh-17007.
    +        # When p = 1e-30, the probability that a sample will exceed 2**63-1
    +        # is 0.9999999999907766, so we expect the result to be all 2**63-1.
    +        mt19937 = self._create_generator()
    +        assert_array_equal(mt19937.geometric(p=1e-30, size=3),
    +                           np.iinfo(np.int64).max)
    +
    +    def test_zipf_large_parameter(self):
    +        # Regression test for part of gh-9829: a call such as rng.zipf(10000)
    +        # would hang.
    +        mt19937 = self._create_generator()
    +        n = 8
    +        sample = mt19937.zipf(10000, size=n)
    +        assert_array_equal(sample, np.ones(n, dtype=np.int64))
    +
    +    def test_zipf_a_near_1(self):
    +        # Regression test for gh-9829: a call such as rng.zipf(1.0000000000001)
    +        # would hang.
    +        mt19937 = self._create_generator()
    +        n = 100000
    +        sample = mt19937.zipf(1.0000000000001, size=n)
    +        # Not much of a test, but let's do something more than verify that
    +        # it doesn't hang.  Certainly for a monotonically decreasing
    +        # discrete distribution truncated to signed 64 bit integers, more
    +        # than half should be less than 2**62.
    +        assert np.count_nonzero(sample < 2**62) > n / 2
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/test_random.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/test_random.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..f110aa892b31ba5a614a4ba0b3b664bfcbc09094
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/test_random.py
    @@ -0,0 +1,1724 @@
    +import sys
    +import warnings
    +
    +import pytest
    +
    +import numpy as np
    +from numpy import random
    +from numpy.testing import (
    +    IS_WASM,
    +    assert_,
    +    assert_array_almost_equal,
    +    assert_array_equal,
    +    assert_equal,
    +    assert_no_warnings,
    +    assert_raises,
    +)
    +
    +
    +class TestSeed:
    +    def test_scalar(self):
    +        s = np.random.RandomState(0)
    +        assert_equal(s.randint(1000), 684)
    +        s = np.random.RandomState(4294967295)
    +        assert_equal(s.randint(1000), 419)
    +
    +    def test_array(self):
    +        s = np.random.RandomState(range(10))
    +        assert_equal(s.randint(1000), 468)
    +        s = np.random.RandomState(np.arange(10))
    +        assert_equal(s.randint(1000), 468)
    +        s = np.random.RandomState([0])
    +        assert_equal(s.randint(1000), 973)
    +        s = np.random.RandomState([4294967295])
    +        assert_equal(s.randint(1000), 265)
    +
    +    def test_invalid_scalar(self):
    +        # seed must be an unsigned 32 bit integer
    +        assert_raises(TypeError, np.random.RandomState, -0.5)
    +        assert_raises(ValueError, np.random.RandomState, -1)
    +
    +    def test_invalid_array(self):
    +        # seed must be an unsigned 32 bit integer
    +        assert_raises(TypeError, np.random.RandomState, [-0.5])
    +        assert_raises(ValueError, np.random.RandomState, [-1])
    +        assert_raises(ValueError, np.random.RandomState, [4294967296])
    +        assert_raises(ValueError, np.random.RandomState, [1, 2, 4294967296])
    +        assert_raises(ValueError, np.random.RandomState, [1, -2, 4294967296])
    +
    +    def test_invalid_array_shape(self):
    +        # gh-9832
    +        assert_raises(ValueError, np.random.RandomState,
    +                      np.array([], dtype=np.int64))
    +        assert_raises(ValueError, np.random.RandomState, [[1, 2, 3]])
    +        assert_raises(ValueError, np.random.RandomState, [[1, 2, 3],
    +                                                          [4, 5, 6]])
    +
    +
    +class TestBinomial:
    +    def test_n_zero(self):
    +        # Tests the corner case of n == 0 for the binomial distribution.
    +        # binomial(0, p) should be zero for any p in [0, 1].
    +        # This test addresses issue #3480.
    +        zeros = np.zeros(2, dtype='int')
    +        for p in [0, .5, 1]:
    +            assert_(random.binomial(0, p) == 0)
    +            assert_array_equal(random.binomial(zeros, p), zeros)
    +
    +    def test_p_is_nan(self):
    +        # Issue #4571.
    +        assert_raises(ValueError, random.binomial, 1, np.nan)
    +
    +
    +class TestMultinomial:
    +    def test_basic(self):
    +        random.multinomial(100, [0.2, 0.8])
    +
    +    def test_zero_probability(self):
    +        random.multinomial(100, [0.2, 0.8, 0.0, 0.0, 0.0])
    +
    +    def test_int_negative_interval(self):
    +        assert_(-5 <= random.randint(-5, -1) < -1)
    +        x = random.randint(-5, -1, 5)
    +        assert_(np.all(-5 <= x))
    +        assert_(np.all(x < -1))
    +
    +    def test_size(self):
    +        # gh-3173
    +        p = [0.5, 0.5]
    +        assert_equal(np.random.multinomial(1, p, np.uint32(1)).shape, (1, 2))
    +        assert_equal(np.random.multinomial(1, p, np.uint32(1)).shape, (1, 2))
    +        assert_equal(np.random.multinomial(1, p, np.uint32(1)).shape, (1, 2))
    +        assert_equal(np.random.multinomial(1, p, [2, 2]).shape, (2, 2, 2))
    +        assert_equal(np.random.multinomial(1, p, (2, 2)).shape, (2, 2, 2))
    +        assert_equal(np.random.multinomial(1, p, np.array((2, 2))).shape,
    +                     (2, 2, 2))
    +
    +        assert_raises(TypeError, np.random.multinomial, 1, p,
    +                      float(1))
    +
    +    def test_multidimensional_pvals(self):
    +        assert_raises(ValueError, np.random.multinomial, 10, [[0, 1]])
    +        assert_raises(ValueError, np.random.multinomial, 10, [[0], [1]])
    +        assert_raises(ValueError, np.random.multinomial, 10, [[[0], [1]], [[1], [0]]])
    +        assert_raises(ValueError, np.random.multinomial, 10, np.array([[0, 1], [1, 0]]))
    +
    +
    +class TestSetState:
    +    def _create_rng(self):
    +        seed = 1234567890
    +        prng = random.RandomState(seed)
    +        state = prng.get_state()
    +        return prng, state
    +
    +    def test_basic(self):
    +        prng, state = self._create_rng()
    +        old = prng.tomaxint(16)
    +        prng.set_state(state)
    +        new = prng.tomaxint(16)
    +        assert_(np.all(old == new))
    +
    +    def test_gaussian_reset(self):
    +        # Make sure the cached every-other-Gaussian is reset.
    +        prng, state = self._create_rng()
    +        old = prng.standard_normal(size=3)
    +        prng.set_state(state)
    +        new = prng.standard_normal(size=3)
    +        assert_(np.all(old == new))
    +
    +    def test_gaussian_reset_in_media_res(self):
    +        # When the state is saved with a cached Gaussian, make sure the
    +        # cached Gaussian is restored.
    +        prng, state = self._create_rng()
    +        prng.standard_normal()
    +        state = prng.get_state()
    +        old = prng.standard_normal(size=3)
    +        prng.set_state(state)
    +        new = prng.standard_normal(size=3)
    +        assert_(np.all(old == new))
    +
    +    def test_backwards_compatibility(self):
    +        # Make sure we can accept old state tuples that do not have the
    +        # cached Gaussian value.
    +        prng, state = self._create_rng()
    +        old_state = state[:-2]
    +        x1 = prng.standard_normal(size=16)
    +        prng.set_state(old_state)
    +        x2 = prng.standard_normal(size=16)
    +        prng.set_state(state)
    +        x3 = prng.standard_normal(size=16)
    +        assert_(np.all(x1 == x2))
    +        assert_(np.all(x1 == x3))
    +
    +    def test_negative_binomial(self):
    +        # Ensure that the negative binomial results take floating point
    +        # arguments without truncation.
    +        prng, _ = self._create_rng()
    +        prng.negative_binomial(0.5, 0.5)
    +
    +    def test_set_invalid_state(self):
    +        # gh-25402
    +        prng, _ = self._create_rng()
    +        with pytest.raises(IndexError):
    +            prng.set_state(())
    +
    +
    +class TestRandint:
    +
    +    # valid integer/boolean types
    +    itype = [np.bool, np.int8, np.uint8, np.int16, np.uint16,
    +             np.int32, np.uint32, np.int64, np.uint64]
    +
    +    def test_unsupported_type(self):
    +        rng = random.RandomState()
    +        assert_raises(TypeError, rng.randint, 1, dtype=float)
    +
    +    def test_bounds_checking(self):
    +        rng = random.RandomState()
    +        for dt in self.itype:
    +            lbnd = 0 if dt is np.bool else np.iinfo(dt).min
    +            ubnd = 2 if dt is np.bool else np.iinfo(dt).max + 1
    +            assert_raises(ValueError, rng.randint, lbnd - 1, ubnd, dtype=dt)
    +            assert_raises(ValueError, rng.randint, lbnd, ubnd + 1, dtype=dt)
    +            assert_raises(ValueError, rng.randint, ubnd, lbnd, dtype=dt)
    +            assert_raises(ValueError, rng.randint, 1, 0, dtype=dt)
    +
    +    def test_rng_zero_and_extremes(self):
    +        rng = random.RandomState()
    +        for dt in self.itype:
    +            lbnd = 0 if dt is np.bool else np.iinfo(dt).min
    +            ubnd = 2 if dt is np.bool else np.iinfo(dt).max + 1
    +
    +            tgt = ubnd - 1
    +            assert_equal(rng.randint(tgt, tgt + 1, size=1000, dtype=dt), tgt)
    +
    +            tgt = lbnd
    +            assert_equal(rng.randint(tgt, tgt + 1, size=1000, dtype=dt), tgt)
    +
    +            tgt = (lbnd + ubnd) // 2
    +            assert_equal(rng.randint(tgt, tgt + 1, size=1000, dtype=dt), tgt)
    +
    +    def test_full_range(self):
    +        # Test for ticket #1690
    +        rng = random.RandomState()
    +
    +        for dt in self.itype:
    +            lbnd = 0 if dt is np.bool else np.iinfo(dt).min
    +            ubnd = 2 if dt is np.bool else np.iinfo(dt).max + 1
    +
    +            try:
    +                rng.randint(lbnd, ubnd, dtype=dt)
    +            except Exception as e:
    +                raise AssertionError("No error should have been raised, "
    +                                     "but one was with the following "
    +                                     "message:\n\n%s" % str(e))
    +
    +    def test_in_bounds_fuzz(self):
    +        # Don't use fixed seed
    +        rng = random.RandomState()
    +
    +        for dt in self.itype[1:]:
    +            for ubnd in [4, 8, 16]:
    +                vals = rng.randint(2, ubnd, size=2**16, dtype=dt)
    +                assert_(vals.max() < ubnd)
    +                assert_(vals.min() >= 2)
    +
    +        vals = rng.randint(0, 2, size=2**16, dtype=np.bool)
    +
    +        assert_(vals.max() < 2)
    +        assert_(vals.min() >= 0)
    +
    +    def test_repeatability(self):
    +        import hashlib
    +        # We use a sha256 hash of generated sequences of 1000 samples
    +        # in the range [0, 6) for all but bool, where the range
    +        # is [0, 2). Hashes are for little endian numbers.
    +        tgt = {'bool':   '509aea74d792fb931784c4b0135392c65aec64beee12b0cc167548a2c3d31e71',  # noqa: E501
    +               'int16':  '7b07f1a920e46f6d0fe02314155a2330bcfd7635e708da50e536c5ebb631a7d4',  # noqa: E501
    +               'int32':  'e577bfed6c935de944424667e3da285012e741892dcb7051a8f1ce68ab05c92f',  # noqa: E501
    +               'int64':  '0fbead0b06759df2cfb55e43148822d4a1ff953c7eb19a5b08445a63bb64fa9e',  # noqa: E501
    +               'int8':   '001aac3a5acb935a9b186cbe14a1ca064b8bb2dd0b045d48abeacf74d0203404',  # noqa: E501
    +               'uint16': '7b07f1a920e46f6d0fe02314155a2330bcfd7635e708da50e536c5ebb631a7d4',  # noqa: E501
    +               'uint32': 'e577bfed6c935de944424667e3da285012e741892dcb7051a8f1ce68ab05c92f',  # noqa: E501
    +               'uint64': '0fbead0b06759df2cfb55e43148822d4a1ff953c7eb19a5b08445a63bb64fa9e',  # noqa: E501
    +               'uint8':  '001aac3a5acb935a9b186cbe14a1ca064b8bb2dd0b045d48abeacf74d0203404'}  # noqa: E501
    +
    +        for dt in self.itype[1:]:
    +            rng = random.RandomState(1234)
    +
    +            # view as little endian for hash
    +            if sys.byteorder == 'little':
    +                val = rng.randint(0, 6, size=1000, dtype=dt)
    +            else:
    +                val = rng.randint(0, 6, size=1000, dtype=dt).byteswap()
    +
    +            res = hashlib.sha256(val.view(np.int8)).hexdigest()
    +            assert_(tgt[np.dtype(dt).name] == res)
    +
    +        # bools do not depend on endianness
    +        rng = random.RandomState(1234)
    +        val = rng.randint(0, 2, size=1000, dtype=bool).view(np.int8)
    +        res = hashlib.sha256(val).hexdigest()
    +        assert_(tgt[np.dtype(bool).name] == res)
    +
    +    def test_int64_uint64_corner_case(self):
    +        # When stored in Numpy arrays, `lbnd` is casted
    +        # as np.int64, and `ubnd` is casted as np.uint64.
    +        # Checking whether `lbnd` >= `ubnd` used to be
    +        # done solely via direct comparison, which is incorrect
    +        # because when Numpy tries to compare both numbers,
    +        # it casts both to np.float64 because there is
    +        # no integer superset of np.int64 and np.uint64. However,
    +        # `ubnd` is too large to be represented in np.float64,
    +        # causing it be round down to np.iinfo(np.int64).max,
    +        # leading to a ValueError because `lbnd` now equals
    +        # the new `ubnd`.
    +
    +        dt = np.int64
    +        tgt = np.iinfo(np.int64).max
    +        lbnd = np.int64(np.iinfo(np.int64).max)
    +        ubnd = np.uint64(np.iinfo(np.int64).max + 1)
    +
    +        # None of these function calls should
    +        # generate a ValueError now.
    +        actual = np.random.randint(lbnd, ubnd, dtype=dt)
    +        assert_equal(actual, tgt)
    +
    +    def test_respect_dtype_singleton(self):
    +        # See gh-7203
    +        rng = random.RandomState()
    +        for dt in self.itype:
    +            lbnd = 0 if dt is np.bool else np.iinfo(dt).min
    +            ubnd = 2 if dt is np.bool else np.iinfo(dt).max + 1
    +
    +            sample = rng.randint(lbnd, ubnd, dtype=dt)
    +            assert_equal(sample.dtype, np.dtype(dt))
    +
    +        for dt in (bool, int):
    +            # The legacy rng uses "long" as the default integer:
    +            lbnd = 0 if dt is bool else np.iinfo("long").min
    +            ubnd = 2 if dt is bool else np.iinfo("long").max + 1
    +
    +            # gh-7284: Ensure that we get Python data types
    +            sample = rng.randint(lbnd, ubnd, dtype=dt)
    +            assert_(not hasattr(sample, 'dtype'))
    +            assert_equal(type(sample), dt)
    +
    +
    +class TestRandomDist:
    +    # Make sure the random distribution returns the correct value for a
    +    # given seed
    +    seed = 1234567890
    +
    +    def test_rand(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.rand(3, 2)
    +        desired = np.array([[0.61879477158567997, 0.59162362775974664],
    +                            [0.88868358904449662, 0.89165480011560816],
    +                            [0.4575674820298663, 0.7781880808593471]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_randn(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.randn(3, 2)
    +        desired = np.array([[1.34016345771863121, 1.73759122771936081],
    +                           [1.498988344300628, -0.2286433324536169],
    +                           [2.031033998682787, 2.17032494605655257]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_randint(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.randint(-99, 99, size=(3, 2))
    +        desired = np.array([[31, 3],
    +                            [-52, 41],
    +                            [-48, -66]])
    +        assert_array_equal(actual, desired)
    +
    +    def test_random_integers(self):
    +        rng = random.RandomState(self.seed)
    +        with pytest.warns(DeprecationWarning):
    +            actual = rng.random_integers(-99, 99, size=(3, 2))
    +        desired = np.array([[31, 3],
    +                            [-52, 41],
    +                            [-48, -66]])
    +        assert_array_equal(actual, desired)
    +
    +    def test_random_integers_max_int(self):
    +        # Tests whether random_integers can generate the
    +        # maximum allowed Python int that can be converted
    +        # into a C long. Previous implementations of this
    +        # method have thrown an OverflowError when attempting
    +        # to generate this integer.
    +        with pytest.warns(DeprecationWarning):
    +            actual = np.random.random_integers(np.iinfo('l').max,
    +                                               np.iinfo('l').max)
    +
    +        desired = np.iinfo('l').max
    +        assert_equal(actual, desired)
    +
    +    def test_random_integers_deprecated(self):
    +        with warnings.catch_warnings():
    +            warnings.simplefilter("error", DeprecationWarning)
    +
    +            # DeprecationWarning raised with high == None
    +            assert_raises(DeprecationWarning,
    +                          np.random.random_integers,
    +                          np.iinfo('l').max)
    +
    +            # DeprecationWarning raised with high != None
    +            assert_raises(DeprecationWarning,
    +                          np.random.random_integers,
    +                          np.iinfo('l').max, np.iinfo('l').max)
    +
    +    def test_random(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.random((3, 2))
    +        desired = np.array([[0.61879477158567997, 0.59162362775974664],
    +                            [0.88868358904449662, 0.89165480011560816],
    +                            [0.4575674820298663, 0.7781880808593471]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_choice_uniform_replace(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.choice(4, 4)
    +        desired = np.array([2, 3, 2, 3])
    +        assert_array_equal(actual, desired)
    +
    +    def test_choice_nonuniform_replace(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.choice(4, 4, p=[0.4, 0.4, 0.1, 0.1])
    +        desired = np.array([1, 1, 2, 2])
    +        assert_array_equal(actual, desired)
    +
    +    def test_choice_uniform_noreplace(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.choice(4, 3, replace=False)
    +        desired = np.array([0, 1, 3])
    +        assert_array_equal(actual, desired)
    +
    +    def test_choice_nonuniform_noreplace(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.choice(4, 3, replace=False,
    +                                  p=[0.1, 0.3, 0.5, 0.1])
    +        desired = np.array([2, 3, 1])
    +        assert_array_equal(actual, desired)
    +
    +    def test_choice_noninteger(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.choice(['a', 'b', 'c', 'd'], 4)
    +        desired = np.array(['c', 'd', 'c', 'd'])
    +        assert_array_equal(actual, desired)
    +
    +    def test_choice_exceptions(self):
    +        sample = np.random.choice
    +        assert_raises(ValueError, sample, -1, 3)
    +        assert_raises(ValueError, sample, 3., 3)
    +        assert_raises(ValueError, sample, [[1, 2], [3, 4]], 3)
    +        assert_raises(ValueError, sample, [], 3)
    +        assert_raises(ValueError, sample, [1, 2, 3, 4], 3,
    +                      p=[[0.25, 0.25], [0.25, 0.25]])
    +        assert_raises(ValueError, sample, [1, 2], 3, p=[0.4, 0.4, 0.2])
    +        assert_raises(ValueError, sample, [1, 2], 3, p=[1.1, -0.1])
    +        assert_raises(ValueError, sample, [1, 2], 3, p=[0.4, 0.4])
    +        assert_raises(ValueError, sample, [1, 2, 3], 4, replace=False)
    +        # gh-13087
    +        assert_raises(ValueError, sample, [1, 2, 3], -2, replace=False)
    +        assert_raises(ValueError, sample, [1, 2, 3], (-1,), replace=False)
    +        assert_raises(ValueError, sample, [1, 2, 3], (-1, 1), replace=False)
    +        assert_raises(ValueError, sample, [1, 2, 3], 2,
    +                      replace=False, p=[1, 0, 0])
    +
    +    def test_choice_return_shape(self):
    +        p = [0.1, 0.9]
    +        # Check scalar
    +        assert_(np.isscalar(np.random.choice(2, replace=True)))
    +        assert_(np.isscalar(np.random.choice(2, replace=False)))
    +        assert_(np.isscalar(np.random.choice(2, replace=True, p=p)))
    +        assert_(np.isscalar(np.random.choice(2, replace=False, p=p)))
    +        assert_(np.isscalar(np.random.choice([1, 2], replace=True)))
    +        assert_(np.random.choice([None], replace=True) is None)
    +        a = np.array([1, 2])
    +        arr = np.empty(1, dtype=object)
    +        arr[0] = a
    +        assert_(np.random.choice(arr, replace=True) is a)
    +
    +        # Check 0-d array
    +        s = ()
    +        assert_(not np.isscalar(np.random.choice(2, s, replace=True)))
    +        assert_(not np.isscalar(np.random.choice(2, s, replace=False)))
    +        assert_(not np.isscalar(np.random.choice(2, s, replace=True, p=p)))
    +        assert_(not np.isscalar(np.random.choice(2, s, replace=False, p=p)))
    +        assert_(not np.isscalar(np.random.choice([1, 2], s, replace=True)))
    +        assert_(np.random.choice([None], s, replace=True).ndim == 0)
    +        a = np.array([1, 2])
    +        arr = np.empty(1, dtype=object)
    +        arr[0] = a
    +        assert_(np.random.choice(arr, s, replace=True).item() is a)
    +
    +        # Check multi dimensional array
    +        s = (2, 3)
    +        p = [0.1, 0.1, 0.1, 0.1, 0.4, 0.2]
    +        assert_equal(np.random.choice(6, s, replace=True).shape, s)
    +        assert_equal(np.random.choice(6, s, replace=False).shape, s)
    +        assert_equal(np.random.choice(6, s, replace=True, p=p).shape, s)
    +        assert_equal(np.random.choice(6, s, replace=False, p=p).shape, s)
    +        assert_equal(np.random.choice(np.arange(6), s, replace=True).shape, s)
    +
    +        # Check zero-size
    +        assert_equal(np.random.randint(0, 0, size=(3, 0, 4)).shape, (3, 0, 4))
    +        assert_equal(np.random.randint(0, -10, size=0).shape, (0,))
    +        assert_equal(np.random.randint(10, 10, size=0).shape, (0,))
    +        assert_equal(np.random.choice(0, size=0).shape, (0,))
    +        assert_equal(np.random.choice([], size=(0,)).shape, (0,))
    +        assert_equal(np.random.choice(['a', 'b'], size=(3, 0, 4)).shape,
    +                     (3, 0, 4))
    +        assert_raises(ValueError, np.random.choice, [], 10)
    +
    +    def test_choice_nan_probabilities(self):
    +        a = np.array([42, 1, 2])
    +        p = [None, None, None]
    +        assert_raises(ValueError, np.random.choice, a, p=p)
    +
    +    def test_bytes(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.bytes(10)
    +        desired = b'\x82Ui\x9e\xff\x97+Wf\xa5'
    +        assert_equal(actual, desired)
    +
    +    def test_shuffle(self):
    +        # Test lists, arrays (of various dtypes), and multidimensional versions
    +        # of both, c-contiguous or not:
    +        for conv in [lambda x: np.array([]),
    +                     lambda x: x,
    +                     lambda x: np.asarray(x).astype(np.int8),
    +                     lambda x: np.asarray(x).astype(np.float32),
    +                     lambda x: np.asarray(x).astype(np.complex64),
    +                     lambda x: np.asarray(x).astype(object),
    +                     lambda x: [(i, i) for i in x],
    +                     lambda x: np.asarray([[i, i] for i in x]),
    +                     lambda x: np.vstack([x, x]).T,
    +                     # gh-11442
    +                     lambda x: (np.asarray([(i, i) for i in x],
    +                                           [("a", int), ("b", int)])
    +                                .view(np.recarray)),
    +                     # gh-4270
    +                     lambda x: np.asarray([(i, i) for i in x],
    +                                          [("a", object), ("b", np.int32)])]:
    +            rng = random.RandomState(self.seed)
    +            alist = conv([1, 2, 3, 4, 5, 6, 7, 8, 9, 0])
    +            rng.shuffle(alist)
    +            actual = alist
    +            desired = conv([0, 1, 9, 6, 2, 4, 5, 8, 7, 3])
    +            assert_array_equal(actual, desired)
    +
    +    def test_shuffle_masked(self):
    +        # gh-3263
    +        a = np.ma.masked_values(np.reshape(range(20), (5, 4)) % 3 - 1, -1)
    +        b = np.ma.masked_values(np.arange(20) % 3 - 1, -1)
    +        a_orig = a.copy()
    +        b_orig = b.copy()
    +        for i in range(50):
    +            np.random.shuffle(a)
    +            assert_equal(
    +                sorted(a.data[~a.mask]), sorted(a_orig.data[~a_orig.mask]))
    +            np.random.shuffle(b)
    +            assert_equal(
    +                sorted(b.data[~b.mask]), sorted(b_orig.data[~b_orig.mask]))
    +
    +    @pytest.mark.parametrize("random",
    +            [np.random, np.random.RandomState(), np.random.default_rng()])
    +    def test_shuffle_untyped_warning(self, random):
    +        # Create a dict works like a sequence but isn't one
    +        values = {0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6}
    +        with pytest.warns(UserWarning,
    +                match="you are shuffling a 'dict' object") as rec:
    +            random.shuffle(values)
    +        assert "test_random" in rec[0].filename
    +
    +    @pytest.mark.parametrize("random",
    +        [np.random, np.random.RandomState(), np.random.default_rng()])
    +    @pytest.mark.parametrize("use_array_like", [True, False])
    +    def test_shuffle_no_object_unpacking(self, random, use_array_like):
    +        class MyArr(np.ndarray):
    +            pass
    +
    +        items = [
    +            None, np.array([3]), np.float64(3), np.array(10), np.float64(7)
    +        ]
    +        arr = np.array(items, dtype=object)
    +        item_ids = {id(i) for i in items}
    +        if use_array_like:
    +            arr = arr.view(MyArr)
    +
    +        # The array was created fine, and did not modify any objects:
    +        assert all(id(i) in item_ids for i in arr)
    +
    +        if use_array_like and not isinstance(random, np.random.Generator):
    +            # The old API gives incorrect results, but warns about it.
    +            with pytest.warns(UserWarning,
    +                    match="Shuffling a one dimensional array.*"):
    +                random.shuffle(arr)
    +        else:
    +            random.shuffle(arr)
    +            assert all(id(i) in item_ids for i in arr)
    +
    +    def test_shuffle_memoryview(self):
    +        # gh-18273
    +        # allow graceful handling of memoryviews
    +        # (treat the same as arrays)
    +        rng = random.RandomState(self.seed)
    +        a = np.arange(5).data
    +        rng.shuffle(a)
    +        assert_equal(np.asarray(a), [0, 1, 4, 3, 2])
    +        rng = random.RandomState(self.seed)
    +        rng.shuffle(a)
    +        assert_equal(np.asarray(a), [0, 1, 2, 3, 4])
    +        rng = np.random.default_rng(self.seed)
    +        rng.shuffle(a)
    +        assert_equal(np.asarray(a), [4, 1, 0, 3, 2])
    +
    +    def test_shuffle_not_writeable(self):
    +        a = np.zeros(3)
    +        a.flags.writeable = False
    +        with pytest.raises(ValueError, match='read-only'):
    +            np.random.shuffle(a)
    +
    +    def test_beta(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.beta(.1, .9, size=(3, 2))
    +        desired = np.array(
    +                [[1.45341850513746058e-02, 5.31297615662868145e-04],
    +                 [1.85366619058432324e-06, 4.19214516800110563e-03],
    +                 [1.58405155108498093e-04, 1.26252891949397652e-04]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_binomial(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.binomial(100, .456, size=(3, 2))
    +        desired = np.array([[37, 43],
    +                            [42, 48],
    +                            [46, 45]])
    +        assert_array_equal(actual, desired)
    +
    +    def test_chisquare(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.chisquare(50, size=(3, 2))
    +        desired = np.array([[63.87858175501090585, 68.68407748911370447],
    +                            [65.77116116901505904, 47.09686762438974483],
    +                            [72.3828403199695174, 74.18408615260374006]])
    +        assert_array_almost_equal(actual, desired, decimal=13)
    +
    +    def test_dirichlet(self):
    +        rng = random.RandomState(self.seed)
    +        alpha = np.array([51.72840233779265162, 39.74494232180943953])
    +        actual = rng.dirichlet(alpha, size=(3, 2))
    +        desired = np.array([[[0.54539444573611562, 0.45460555426388438],
    +                             [0.62345816822039413, 0.37654183177960598]],
    +                            [[0.55206000085785778, 0.44793999914214233],
    +                             [0.58964023305154301, 0.41035976694845688]],
    +                            [[0.59266909280647828, 0.40733090719352177],
    +                             [0.56974431743975207, 0.43025568256024799]]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_dirichlet_size(self):
    +        # gh-3173
    +        p = np.array([51.72840233779265162, 39.74494232180943953])
    +        assert_equal(np.random.dirichlet(p, np.uint32(1)).shape, (1, 2))
    +        assert_equal(np.random.dirichlet(p, np.uint32(1)).shape, (1, 2))
    +        assert_equal(np.random.dirichlet(p, np.uint32(1)).shape, (1, 2))
    +        assert_equal(np.random.dirichlet(p, [2, 2]).shape, (2, 2, 2))
    +        assert_equal(np.random.dirichlet(p, (2, 2)).shape, (2, 2, 2))
    +        assert_equal(np.random.dirichlet(p, np.array((2, 2))).shape, (2, 2, 2))
    +
    +        assert_raises(TypeError, np.random.dirichlet, p, float(1))
    +
    +    def test_dirichlet_bad_alpha(self):
    +        # gh-2089
    +        alpha = np.array([5.4e-01, -1.0e-16])
    +        assert_raises(ValueError, np.random.mtrand.dirichlet, alpha)
    +
    +        # gh-15876
    +        assert_raises(ValueError, random.dirichlet, [[5, 1]])
    +        assert_raises(ValueError, random.dirichlet, [[5], [1]])
    +        assert_raises(ValueError, random.dirichlet, [[[5], [1]], [[1], [5]]])
    +        assert_raises(ValueError, random.dirichlet, np.array([[5, 1], [1, 5]]))
    +
    +    def test_exponential(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.exponential(1.1234, size=(3, 2))
    +        desired = np.array([[1.08342649775011624, 1.00607889924557314],
    +                            [2.46628830085216721, 2.49668106809923884],
    +                            [0.68717433461363442, 1.69175666993575979]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_exponential_0(self):
    +        assert_equal(np.random.exponential(scale=0), 0)
    +        assert_raises(ValueError, np.random.exponential, scale=-0.)
    +
    +    def test_f(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.f(12, 77, size=(3, 2))
    +        desired = np.array([[1.21975394418575878, 1.75135759791559775],
    +                            [1.44803115017146489, 1.22108959480396262],
    +                            [1.02176975757740629, 1.34431827623300415]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_gamma(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.gamma(5, 3, size=(3, 2))
    +        desired = np.array([[24.60509188649287182, 28.54993563207210627],
    +                            [26.13476110204064184, 12.56988482927716078],
    +                            [31.71863275789960568, 33.30143302795922011]])
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +
    +    def test_gamma_0(self):
    +        assert_equal(np.random.gamma(shape=0, scale=0), 0)
    +        assert_raises(ValueError, np.random.gamma, shape=-0., scale=-0.)
    +
    +    def test_geometric(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.geometric(.123456789, size=(3, 2))
    +        desired = np.array([[8, 7],
    +                            [17, 17],
    +                            [5, 12]])
    +        assert_array_equal(actual, desired)
    +
    +    def test_gumbel(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.gumbel(loc=.123456789, scale=2.0, size=(3, 2))
    +        desired = np.array([[0.19591898743416816, 0.34405539668096674],
    +                            [-1.4492522252274278, -1.47374816298446865],
    +                            [1.10651090478803416, -0.69535848626236174]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_gumbel_0(self):
    +        assert_equal(np.random.gumbel(scale=0), 0)
    +        assert_raises(ValueError, np.random.gumbel, scale=-0.)
    +
    +    def test_hypergeometric(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.hypergeometric(10, 5, 14, size=(3, 2))
    +        desired = np.array([[10, 10],
    +                            [10, 10],
    +                            [9, 9]])
    +        assert_array_equal(actual, desired)
    +
    +        # Test nbad = 0
    +        actual = rng.hypergeometric(5, 0, 3, size=4)
    +        desired = np.array([3, 3, 3, 3])
    +        assert_array_equal(actual, desired)
    +
    +        actual = rng.hypergeometric(15, 0, 12, size=4)
    +        desired = np.array([12, 12, 12, 12])
    +        assert_array_equal(actual, desired)
    +
    +        # Test ngood = 0
    +        actual = rng.hypergeometric(0, 5, 3, size=4)
    +        desired = np.array([0, 0, 0, 0])
    +        assert_array_equal(actual, desired)
    +
    +        actual = rng.hypergeometric(0, 15, 12, size=4)
    +        desired = np.array([0, 0, 0, 0])
    +        assert_array_equal(actual, desired)
    +
    +    def test_laplace(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.laplace(loc=.123456789, scale=2.0, size=(3, 2))
    +        desired = np.array([[0.66599721112760157, 0.52829452552221945],
    +                            [3.12791959514407125, 3.18202813572992005],
    +                            [-0.05391065675859356, 1.74901336242837324]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_laplace_0(self):
    +        assert_equal(np.random.laplace(scale=0), 0)
    +        assert_raises(ValueError, np.random.laplace, scale=-0.)
    +
    +    def test_logistic(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.logistic(loc=.123456789, scale=2.0, size=(3, 2))
    +        desired = np.array([[1.09232835305011444, 0.8648196662399954],
    +                            [4.27818590694950185, 4.33897006346929714],
    +                            [-0.21682183359214885, 2.63373365386060332]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_lognormal(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.lognormal(mean=.123456789, sigma=2.0, size=(3, 2))
    +        desired = np.array([[16.50698631688883822, 36.54846706092654784],
    +                            [22.67886599981281748, 0.71617561058995771],
    +                            [65.72798501792723869, 86.84341601437161273]])
    +        assert_array_almost_equal(actual, desired, decimal=13)
    +
    +    def test_lognormal_0(self):
    +        assert_equal(np.random.lognormal(sigma=0), 1)
    +        assert_raises(ValueError, np.random.lognormal, sigma=-0.)
    +
    +    def test_logseries(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.logseries(p=.923456789, size=(3, 2))
    +        desired = np.array([[2, 2],
    +                            [6, 17],
    +                            [3, 6]])
    +        assert_array_equal(actual, desired)
    +
    +    def test_multinomial(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.multinomial(20, [1 / 6.] * 6, size=(3, 2))
    +        desired = np.array([[[4, 3, 5, 4, 2, 2],
    +                             [5, 2, 8, 2, 2, 1]],
    +                            [[3, 4, 3, 6, 0, 4],
    +                             [2, 1, 4, 3, 6, 4]],
    +                            [[4, 4, 2, 5, 2, 3],
    +                             [4, 3, 4, 2, 3, 4]]])
    +        assert_array_equal(actual, desired)
    +
    +    def test_multivariate_normal(self):
    +        rng = random.RandomState(self.seed)
    +        mean = (.123456789, 10)
    +        cov = [[1, 0], [0, 1]]
    +        size = (3, 2)
    +        actual = rng.multivariate_normal(mean, cov, size)
    +        desired = np.array([[[1.463620246718631, 11.73759122771936],
    +                             [1.622445133300628, 9.771356667546383]],
    +                            [[2.154490787682787, 12.170324946056553],
    +                             [1.719909438201865, 9.230548443648306]],
    +                            [[0.689515026297799, 9.880729819607714],
    +                             [-0.023054015651998, 9.201096623542879]]])
    +
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +        # Check for default size, was raising deprecation warning
    +        actual = rng.multivariate_normal(mean, cov)
    +        desired = np.array([0.895289569463708, 9.17180864067987])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +        # Check that non positive-semidefinite covariance warns with
    +        # RuntimeWarning
    +        mean = [0, 0]
    +        cov = [[1, 2], [2, 1]]
    +        pytest.warns(RuntimeWarning, rng.multivariate_normal, mean, cov)
    +
    +        # and that it doesn't warn with RuntimeWarning check_valid='ignore'
    +        assert_no_warnings(rng.multivariate_normal, mean, cov,
    +                           check_valid='ignore')
    +
    +        # and that it raises with RuntimeWarning check_valid='raises'
    +        assert_raises(ValueError, rng.multivariate_normal, mean, cov,
    +                      check_valid='raise')
    +
    +        cov = np.array([[1, 0.1], [0.1, 1]], dtype=np.float32)
    +        with warnings.catch_warnings():
    +            warnings.simplefilter('error')
    +            rng.multivariate_normal(mean, cov)
    +
    +    def test_negative_binomial(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.negative_binomial(n=100, p=.12345, size=(3, 2))
    +        desired = np.array([[848, 841],
    +                            [892, 611],
    +                            [779, 647]])
    +        assert_array_equal(actual, desired)
    +
    +    def test_noncentral_chisquare(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.noncentral_chisquare(df=5, nonc=5, size=(3, 2))
    +        desired = np.array([[23.91905354498517511, 13.35324692733826346],
    +                            [31.22452661329736401, 16.60047399466177254],
    +                            [5.03461598262724586, 17.94973089023519464]])
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +
    +        actual = rng.noncentral_chisquare(df=.5, nonc=.2, size=(3, 2))
    +        desired = np.array([[1.47145377828516666,  0.15052899268012659],
    +                            [0.00943803056963588,  1.02647251615666169],
    +                            [0.332334982684171,  0.15451287602753125]])
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.noncentral_chisquare(df=5, nonc=0, size=(3, 2))
    +        desired = np.array([[9.597154162763948, 11.725484450296079],
    +                            [10.413711048138335, 3.694475922923986],
    +                            [13.484222138963087, 14.377255424602957]])
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +
    +    def test_noncentral_f(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.noncentral_f(dfnum=5, dfden=2, nonc=1,
    +                                        size=(3, 2))
    +        desired = np.array([[1.40598099674926669, 0.34207973179285761],
    +                            [3.57715069265772545, 7.92632662577829805],
    +                            [0.43741599463544162, 1.1774208752428319]])
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +
    +    def test_normal(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.normal(loc=.123456789, scale=2.0, size=(3, 2))
    +        desired = np.array([[2.80378370443726244, 3.59863924443872163],
    +                            [3.121433477601256, -0.33382987590723379],
    +                            [4.18552478636557357, 4.46410668111310471]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_normal_0(self):
    +        assert_equal(np.random.normal(scale=0), 0)
    +        assert_raises(ValueError, np.random.normal, scale=-0.)
    +
    +    def test_pareto(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.pareto(a=.123456789, size=(3, 2))
    +        desired = np.array(
    +                [[2.46852460439034849e+03, 1.41286880810518346e+03],
    +                 [5.28287797029485181e+07, 6.57720981047328785e+07],
    +                 [1.40840323350391515e+02, 1.98390255135251704e+05]])
    +        # For some reason on 32-bit x86 Ubuntu 12.10 the [1, 0] entry in this
    +        # matrix differs by 24 nulps. Discussion:
    +        #   https://mail.python.org/pipermail/numpy-discussion/2012-September/063801.html
    +        # Consensus is that this is probably some gcc quirk that affects
    +        # rounding but not in any important way, so we just use a looser
    +        # tolerance on this test:
    +        np.testing.assert_array_almost_equal_nulp(actual, desired, nulp=30)
    +
    +    def test_poisson(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.poisson(lam=.123456789, size=(3, 2))
    +        desired = np.array([[0, 0],
    +                            [1, 0],
    +                            [0, 0]])
    +        assert_array_equal(actual, desired)
    +
    +    def test_poisson_exceptions(self):
    +        lambig = np.iinfo('l').max
    +        lamneg = -1
    +        assert_raises(ValueError, np.random.poisson, lamneg)
    +        assert_raises(ValueError, np.random.poisson, [lamneg] * 10)
    +        assert_raises(ValueError, np.random.poisson, lambig)
    +        assert_raises(ValueError, np.random.poisson, [lambig] * 10)
    +
    +    def test_power(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.power(a=.123456789, size=(3, 2))
    +        desired = np.array([[0.02048932883240791, 0.01424192241128213],
    +                            [0.38446073748535298, 0.39499689943484395],
    +                            [0.00177699707563439, 0.13115505880863756]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_rayleigh(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.rayleigh(scale=10, size=(3, 2))
    +        desired = np.array([[13.8882496494248393, 13.383318339044731],
    +                            [20.95413364294492098, 21.08285015800712614],
    +                            [11.06066537006854311, 17.35468505778271009]])
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +
    +    def test_rayleigh_0(self):
    +        assert_equal(np.random.rayleigh(scale=0), 0)
    +        assert_raises(ValueError, np.random.rayleigh, scale=-0.)
    +
    +    def test_standard_cauchy(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.standard_cauchy(size=(3, 2))
    +        desired = np.array([[0.77127660196445336, -6.55601161955910605],
    +                            [0.93582023391158309, -2.07479293013759447],
    +                            [-4.74601644297011926, 0.18338989290760804]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_standard_exponential(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.standard_exponential(size=(3, 2))
    +        desired = np.array([[0.96441739162374596, 0.89556604882105506],
    +                            [2.1953785836319808, 2.22243285392490542],
    +                            [0.6116915921431676, 1.50592546727413201]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_standard_gamma(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.standard_gamma(shape=3, size=(3, 2))
    +        desired = np.array([[5.50841531318455058, 6.62953470301903103],
    +                            [5.93988484943779227, 2.31044849402133989],
    +                            [7.54838614231317084, 8.012756093271868]])
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +
    +    def test_standard_gamma_0(self):
    +        assert_equal(np.random.standard_gamma(shape=0), 0)
    +        assert_raises(ValueError, np.random.standard_gamma, shape=-0.)
    +
    +    def test_standard_normal(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.standard_normal(size=(3, 2))
    +        desired = np.array([[1.34016345771863121, 1.73759122771936081],
    +                            [1.498988344300628, -0.2286433324536169],
    +                            [2.031033998682787, 2.17032494605655257]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_standard_t(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.standard_t(df=10, size=(3, 2))
    +        desired = np.array([[0.97140611862659965, -0.08830486548450577],
    +                            [1.36311143689505321, -0.55317463909867071],
    +                            [-0.18473749069684214, 0.61181537341755321]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_triangular(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.triangular(left=5.12, mode=10.23, right=20.34,
    +                                      size=(3, 2))
    +        desired = np.array([[12.68117178949215784, 12.4129206149193152],
    +                            [16.20131377335158263, 16.25692138747600524],
    +                            [11.20400690911820263, 14.4978144835829923]])
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +
    +    def test_uniform(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.uniform(low=1.23, high=10.54, size=(3, 2))
    +        desired = np.array([[6.99097932346268003, 6.73801597444323974],
    +                            [9.50364421400426274, 9.53130618907631089],
    +                            [5.48995325769805476, 8.47493103280052118]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_uniform_range_bounds(self):
    +        fmin = np.finfo('float').min
    +        fmax = np.finfo('float').max
    +
    +        func = np.random.uniform
    +        assert_raises(OverflowError, func, -np.inf, 0)
    +        assert_raises(OverflowError, func,  0,      np.inf)
    +        assert_raises(OverflowError, func,  fmin,   fmax)
    +        assert_raises(OverflowError, func, [-np.inf], [0])
    +        assert_raises(OverflowError, func, [0], [np.inf])
    +
    +        # (fmax / 1e17) - fmin is within range, so this should not throw
    +        # account for i386 extended precision DBL_MAX / 1e17 + DBL_MAX >
    +        # DBL_MAX by increasing fmin a bit
    +        np.random.uniform(low=np.nextafter(fmin, 1), high=fmax / 1e17)
    +
    +    def test_scalar_exception_propagation(self):
    +        # Tests that exceptions are correctly propagated in distributions
    +        # when called with objects that throw exceptions when converted to
    +        # scalars.
    +        #
    +        # Regression test for gh: 8865
    +
    +        class ThrowingFloat(np.ndarray):
    +            def __float__(self):
    +                raise TypeError
    +
    +        throwing_float = np.array(1.0).view(ThrowingFloat)
    +        assert_raises(TypeError, np.random.uniform, throwing_float,
    +                      throwing_float)
    +
    +        class ThrowingInteger(np.ndarray):
    +            def __int__(self):
    +                raise TypeError
    +
    +            __index__ = __int__
    +
    +        throwing_int = np.array(1).view(ThrowingInteger)
    +        assert_raises(TypeError, np.random.hypergeometric, throwing_int, 1, 1)
    +
    +    def test_vonmises(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.vonmises(mu=1.23, kappa=1.54, size=(3, 2))
    +        desired = np.array([[2.28567572673902042, 2.89163838442285037],
    +                            [0.38198375564286025, 2.57638023113890746],
    +                            [1.19153771588353052, 1.83509849681825354]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_vonmises_small(self):
    +        # check infinite loop, gh-4720
    +        np.random.seed(self.seed)
    +        r = np.random.vonmises(mu=0., kappa=1.1e-8, size=10**6)
    +        np.testing.assert_(np.isfinite(r).all())
    +
    +    def test_wald(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.wald(mean=1.23, scale=1.54, size=(3, 2))
    +        desired = np.array([[3.82935265715889983, 5.13125249184285526],
    +                            [0.35045403618358717, 1.50832396872003538],
    +                            [0.24124319895843183, 0.22031101461955038]])
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +
    +    def test_weibull(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.weibull(a=1.23, size=(3, 2))
    +        desired = np.array([[0.97097342648766727, 0.91422896443565516],
    +                            [1.89517770034962929, 1.91414357960479564],
    +                            [0.67057783752390987, 1.39494046635066793]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_weibull_0(self):
    +        np.random.seed(self.seed)
    +        assert_equal(np.random.weibull(a=0, size=12), np.zeros(12))
    +        assert_raises(ValueError, np.random.weibull, a=-0.)
    +
    +    def test_zipf(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.zipf(a=1.23, size=(3, 2))
    +        desired = np.array([[66, 29],
    +                            [1, 1],
    +                            [3, 13]])
    +        assert_array_equal(actual, desired)
    +
    +
    +class TestBroadcast:
    +    # tests that functions that broadcast behave
    +    # correctly when presented with non-scalar arguments
    +    seed = 123456789
    +
    +    # TODO: Include test for randint once it can broadcast
    +    # Can steal the test written in PR #6938
    +
    +    def test_uniform(self):
    +        low = [0]
    +        high = [1]
    +        desired = np.array([0.53283302478975902,
    +                            0.53413660089041659,
    +                            0.50955303552646702])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.uniform(low * 3, high)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.uniform(low, high * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +
    +    def test_normal(self):
    +        loc = [0]
    +        scale = [1]
    +        bad_scale = [-1]
    +        desired = np.array([2.2129019979039612,
    +                            2.1283977976520019,
    +                            1.8417114045748335])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.normal(loc * 3, scale)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.normal, loc * 3, bad_scale)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.normal(loc, scale * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.normal, loc, bad_scale * 3)
    +
    +    def test_beta(self):
    +        a = [1]
    +        b = [2]
    +        bad_a = [-1]
    +        bad_b = [-2]
    +        desired = np.array([0.19843558305989056,
    +                            0.075230336409423643,
    +                            0.24976865978980844])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.beta(a * 3, b)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.beta, bad_a * 3, b)
    +        assert_raises(ValueError, rng.beta, a * 3, bad_b)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.beta(a, b * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.beta, bad_a, b * 3)
    +        assert_raises(ValueError, rng.beta, a, bad_b * 3)
    +
    +    def test_exponential(self):
    +        scale = [1]
    +        bad_scale = [-1]
    +        desired = np.array([0.76106853658845242,
    +                            0.76386282278691653,
    +                            0.71243813125891797])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.exponential(scale * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.exponential, bad_scale * 3)
    +
    +    def test_standard_gamma(self):
    +        shape = [1]
    +        bad_shape = [-1]
    +        desired = np.array([0.76106853658845242,
    +                            0.76386282278691653,
    +                            0.71243813125891797])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.standard_gamma(shape * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.standard_gamma, bad_shape * 3)
    +
    +    def test_gamma(self):
    +        shape = [1]
    +        scale = [2]
    +        bad_shape = [-1]
    +        bad_scale = [-2]
    +        desired = np.array([1.5221370731769048,
    +                            1.5277256455738331,
    +                            1.4248762625178359])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.gamma(shape * 3, scale)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.gamma, bad_shape * 3, scale)
    +        assert_raises(ValueError, rng.gamma, shape * 3, bad_scale)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.gamma(shape, scale * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.gamma, bad_shape, scale * 3)
    +        assert_raises(ValueError, rng.gamma, shape, bad_scale * 3)
    +
    +    def test_f(self):
    +        dfnum = [1]
    +        dfden = [2]
    +        bad_dfnum = [-1]
    +        bad_dfden = [-2]
    +        desired = np.array([0.80038951638264799,
    +                            0.86768719635363512,
    +                            2.7251095168386801])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.f(dfnum * 3, dfden)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.f, bad_dfnum * 3, dfden)
    +        assert_raises(ValueError, rng.f, dfnum * 3, bad_dfden)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.f(dfnum, dfden * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.f, bad_dfnum, dfden * 3)
    +        assert_raises(ValueError, rng.f, dfnum, bad_dfden * 3)
    +
    +    def test_noncentral_f(self):
    +        dfnum = [2]
    +        dfden = [3]
    +        nonc = [4]
    +        bad_dfnum = [0]
    +        bad_dfden = [-1]
    +        bad_nonc = [-2]
    +        desired = np.array([9.1393943263705211,
    +                            13.025456344595602,
    +                            8.8018098359100545])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.noncentral_f(dfnum * 3, dfden, nonc)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.noncentral_f, bad_dfnum * 3, dfden, nonc)
    +        assert_raises(ValueError, rng.noncentral_f, dfnum * 3, bad_dfden, nonc)
    +        assert_raises(ValueError, rng.noncentral_f, dfnum * 3, dfden, bad_nonc)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.noncentral_f(dfnum, dfden * 3, nonc)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.noncentral_f, bad_dfnum, dfden * 3, nonc)
    +        assert_raises(ValueError, rng.noncentral_f, dfnum, bad_dfden * 3, nonc)
    +        assert_raises(ValueError, rng.noncentral_f, dfnum, dfden * 3, bad_nonc)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.noncentral_f(dfnum, dfden, nonc * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.noncentral_f, bad_dfnum, dfden, nonc * 3)
    +        assert_raises(ValueError, rng.noncentral_f, dfnum, bad_dfden, nonc * 3)
    +        assert_raises(ValueError, rng.noncentral_f, dfnum, dfden, bad_nonc * 3)
    +
    +    def test_noncentral_f_small_df(self):
    +        rng = random.RandomState(self.seed)
    +        desired = np.array([6.869638627492048, 0.785880199263955])
    +        actual = rng.noncentral_f(0.9, 0.9, 2, size=2)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +
    +    def test_chisquare(self):
    +        df = [1]
    +        bad_df = [-1]
    +        desired = np.array([0.57022801133088286,
    +                            0.51947702108840776,
    +                            0.1320969254923558])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.chisquare(df * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.chisquare, bad_df * 3)
    +
    +    def test_noncentral_chisquare(self):
    +        df = [1]
    +        nonc = [2]
    +        bad_df = [-1]
    +        bad_nonc = [-2]
    +        desired = np.array([9.0015599467913763,
    +                            4.5804135049718742,
    +                            6.0872302432834564])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.noncentral_chisquare(df * 3, nonc)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.noncentral_chisquare, bad_df * 3, nonc)
    +        assert_raises(ValueError, rng.noncentral_chisquare, df * 3, bad_nonc)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.noncentral_chisquare(df, nonc * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.noncentral_chisquare, bad_df, nonc * 3)
    +        assert_raises(ValueError, rng.noncentral_chisquare, df, bad_nonc * 3)
    +
    +    def test_standard_t(self):
    +        df = [1]
    +        bad_df = [-1]
    +        desired = np.array([3.0702872575217643,
    +                            5.8560725167361607,
    +                            1.0274791436474273])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.standard_t(df * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.standard_t, bad_df * 3)
    +
    +    def test_vonmises(self):
    +        mu = [2]
    +        kappa = [1]
    +        bad_kappa = [-1]
    +        desired = np.array([2.9883443664201312,
    +                            -2.7064099483995943,
    +                            -1.8672476700665914])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.vonmises(mu * 3, kappa)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.vonmises, mu * 3, bad_kappa)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.vonmises(mu, kappa * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.vonmises, mu, bad_kappa * 3)
    +
    +    def test_pareto(self):
    +        a = [1]
    +        bad_a = [-1]
    +        desired = np.array([1.1405622680198362,
    +                            1.1465519762044529,
    +                            1.0389564467453547])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.pareto(a * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.pareto, bad_a * 3)
    +
    +    def test_weibull(self):
    +        a = [1]
    +        bad_a = [-1]
    +        desired = np.array([0.76106853658845242,
    +                            0.76386282278691653,
    +                            0.71243813125891797])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.weibull(a * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.weibull, bad_a * 3)
    +
    +    def test_power(self):
    +        a = [1]
    +        bad_a = [-1]
    +        desired = np.array([0.53283302478975902,
    +                            0.53413660089041659,
    +                            0.50955303552646702])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.power(a * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.power, bad_a * 3)
    +
    +    def test_laplace(self):
    +        loc = [0]
    +        scale = [1]
    +        bad_scale = [-1]
    +        desired = np.array([0.067921356028507157,
    +                            0.070715642226971326,
    +                            0.019290950698972624])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.laplace(loc * 3, scale)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.laplace, loc * 3, bad_scale)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.laplace(loc, scale * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.laplace, loc, bad_scale * 3)
    +
    +    def test_gumbel(self):
    +        loc = [0]
    +        scale = [1]
    +        bad_scale = [-1]
    +        desired = np.array([0.2730318639556768,
    +                            0.26936705726291116,
    +                            0.33906220393037939])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.gumbel(loc * 3, scale)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.gumbel, loc * 3, bad_scale)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.gumbel(loc, scale * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.gumbel, loc, bad_scale * 3)
    +
    +    def test_logistic(self):
    +        loc = [0]
    +        scale = [1]
    +        bad_scale = [-1]
    +        desired = np.array([0.13152135837586171,
    +                            0.13675915696285773,
    +                            0.038216792802833396])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.logistic(loc * 3, scale)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.logistic, loc * 3, bad_scale)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.logistic(loc, scale * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.logistic, loc, bad_scale * 3)
    +
    +    def test_lognormal(self):
    +        mean = [0]
    +        sigma = [1]
    +        bad_sigma = [-1]
    +        desired = np.array([9.1422086044848427,
    +                            8.4013952870126261,
    +                            6.3073234116578671])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.lognormal(mean * 3, sigma)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.lognormal, mean * 3, bad_sigma)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.lognormal(mean, sigma * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.lognormal, mean, bad_sigma * 3)
    +
    +    def test_rayleigh(self):
    +        scale = [1]
    +        bad_scale = [-1]
    +        desired = np.array([1.2337491937897689,
    +                            1.2360119924878694,
    +                            1.1936818095781789])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.rayleigh(scale * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.rayleigh, bad_scale * 3)
    +
    +    def test_wald(self):
    +        mean = [0.5]
    +        scale = [1]
    +        bad_mean = [0]
    +        bad_scale = [-2]
    +        desired = np.array([0.11873681120271318,
    +                            0.12450084820795027,
    +                            0.9096122728408238])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.wald(mean * 3, scale)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.wald, bad_mean * 3, scale)
    +        assert_raises(ValueError, rng.wald, mean * 3, bad_scale)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.wald(mean, scale * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.wald, bad_mean, scale * 3)
    +        assert_raises(ValueError, rng.wald, mean, bad_scale * 3)
    +        assert_raises(ValueError, rng.wald, 0.0, 1)
    +        assert_raises(ValueError, rng.wald, 0.5, 0.0)
    +
    +    def test_triangular(self):
    +        left = [1]
    +        right = [3]
    +        mode = [2]
    +        bad_left_one = [3]
    +        bad_mode_one = [4]
    +        bad_left_two, bad_mode_two = right * 2
    +        desired = np.array([2.03339048710429,
    +                            2.0347400359389356,
    +                            2.0095991069536208])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.triangular(left * 3, mode, right)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.triangular, bad_left_one * 3, mode, right)
    +        assert_raises(ValueError, rng.triangular, left * 3, bad_mode_one, right)
    +        assert_raises(ValueError, rng.triangular, bad_left_two * 3, bad_mode_two,
    +                      right)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.triangular(left, mode * 3, right)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.triangular, bad_left_one, mode * 3, right)
    +        assert_raises(ValueError, rng.triangular, left, bad_mode_one * 3, right)
    +        assert_raises(ValueError, rng.triangular, bad_left_two, bad_mode_two * 3,
    +                      right)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.triangular(left, mode, right * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.triangular, bad_left_one, mode, right * 3)
    +        assert_raises(ValueError, rng.triangular, left, bad_mode_one, right * 3)
    +        assert_raises(ValueError, rng.triangular, bad_left_two, bad_mode_two,
    +                      right * 3)
    +
    +    def test_binomial(self):
    +        n = [1]
    +        p = [0.5]
    +        bad_n = [-1]
    +        bad_p_one = [-1]
    +        bad_p_two = [1.5]
    +        desired = np.array([1, 1, 1])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.binomial(n * 3, p)
    +        assert_array_equal(actual, desired)
    +        assert_raises(ValueError, rng.binomial, bad_n * 3, p)
    +        assert_raises(ValueError, rng.binomial, n * 3, bad_p_one)
    +        assert_raises(ValueError, rng.binomial, n * 3, bad_p_two)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.binomial(n, p * 3)
    +        assert_array_equal(actual, desired)
    +        assert_raises(ValueError, rng.binomial, bad_n, p * 3)
    +        assert_raises(ValueError, rng.binomial, n, bad_p_one * 3)
    +        assert_raises(ValueError, rng.binomial, n, bad_p_two * 3)
    +
    +    def test_negative_binomial(self):
    +        n = [1]
    +        p = [0.5]
    +        bad_n = [-1]
    +        bad_p_one = [-1]
    +        bad_p_two = [1.5]
    +        desired = np.array([1, 0, 1])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.negative_binomial(n * 3, p)
    +        assert_array_equal(actual, desired)
    +        assert_raises(ValueError, rng.negative_binomial, bad_n * 3, p)
    +        assert_raises(ValueError, rng.negative_binomial, n * 3, bad_p_one)
    +        assert_raises(ValueError, rng.negative_binomial, n * 3, bad_p_two)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.negative_binomial(n, p * 3)
    +        assert_array_equal(actual, desired)
    +        assert_raises(ValueError, rng.negative_binomial, bad_n, p * 3)
    +        assert_raises(ValueError, rng.negative_binomial, n, bad_p_one * 3)
    +        assert_raises(ValueError, rng.negative_binomial, n, bad_p_two * 3)
    +
    +    def test_poisson(self):
    +        max_lam = np.random.RandomState()._poisson_lam_max
    +
    +        lam = [1]
    +        bad_lam_one = [-1]
    +        bad_lam_two = [max_lam * 2]
    +        desired = np.array([1, 1, 0])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.poisson(lam * 3)
    +        assert_array_equal(actual, desired)
    +        assert_raises(ValueError, rng.poisson, bad_lam_one * 3)
    +        assert_raises(ValueError, rng.poisson, bad_lam_two * 3)
    +
    +    def test_zipf(self):
    +        a = [2]
    +        bad_a = [0]
    +        desired = np.array([2, 2, 1])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.zipf(a * 3)
    +        assert_array_equal(actual, desired)
    +        assert_raises(ValueError, rng.zipf, bad_a * 3)
    +        with np.errstate(invalid='ignore'):
    +            assert_raises(ValueError, rng.zipf, np.nan)
    +            assert_raises(ValueError, rng.zipf, [0, 0, np.nan])
    +
    +    def test_geometric(self):
    +        p = [0.5]
    +        bad_p_one = [-1]
    +        bad_p_two = [1.5]
    +        desired = np.array([2, 2, 2])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.geometric(p * 3)
    +        assert_array_equal(actual, desired)
    +        assert_raises(ValueError, rng.geometric, bad_p_one * 3)
    +        assert_raises(ValueError, rng.geometric, bad_p_two * 3)
    +
    +    def test_hypergeometric(self):
    +        ngood = [1]
    +        nbad = [2]
    +        nsample = [2]
    +        bad_ngood = [-1]
    +        bad_nbad = [-2]
    +        bad_nsample_one = [0]
    +        bad_nsample_two = [4]
    +        desired = np.array([1, 1, 1])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.hypergeometric(ngood * 3, nbad, nsample)
    +        assert_array_equal(actual, desired)
    +        assert_raises(ValueError, rng.hypergeometric, bad_ngood * 3, nbad, nsample)
    +        assert_raises(ValueError, rng.hypergeometric, ngood * 3, bad_nbad, nsample)
    +        assert_raises(ValueError, rng.hypergeometric, ngood * 3, nbad, bad_nsample_one)
    +        assert_raises(ValueError, rng.hypergeometric, ngood * 3, nbad, bad_nsample_two)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.hypergeometric(ngood, nbad * 3, nsample)
    +        assert_array_equal(actual, desired)
    +        assert_raises(ValueError, rng.hypergeometric, bad_ngood, nbad * 3, nsample)
    +        assert_raises(ValueError, rng.hypergeometric, ngood, bad_nbad * 3, nsample)
    +        assert_raises(ValueError, rng.hypergeometric, ngood, nbad * 3, bad_nsample_one)
    +        assert_raises(ValueError, rng.hypergeometric, ngood, nbad * 3, bad_nsample_two)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.hypergeometric(ngood, nbad, nsample * 3)
    +        assert_array_equal(actual, desired)
    +        assert_raises(ValueError, rng.hypergeometric, bad_ngood, nbad, nsample * 3)
    +        assert_raises(ValueError, rng.hypergeometric, ngood, bad_nbad, nsample * 3)
    +        assert_raises(ValueError, rng.hypergeometric, ngood, nbad, bad_nsample_one * 3)
    +        assert_raises(ValueError, rng.hypergeometric, ngood, nbad, bad_nsample_two * 3)
    +
    +    def test_logseries(self):
    +        p = [0.5]
    +        bad_p_one = [2]
    +        bad_p_two = [-1]
    +        desired = np.array([1, 1, 1])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.logseries(p * 3)
    +        assert_array_equal(actual, desired)
    +        assert_raises(ValueError, rng.logseries, bad_p_one * 3)
    +        assert_raises(ValueError, rng.logseries, bad_p_two * 3)
    +
    +
    +@pytest.mark.skipif(IS_WASM, reason="can't start thread")
    +class TestThread:
    +    # make sure each state produces the same sequence even in threads
    +    seeds = range(4)
    +
    +    def check_function(self, function, sz):
    +        from threading import Thread
    +
    +        out1 = np.empty((len(self.seeds),) + sz)
    +        out2 = np.empty((len(self.seeds),) + sz)
    +
    +        # threaded generation
    +        t = [Thread(target=function, args=(np.random.RandomState(s), o))
    +             for s, o in zip(self.seeds, out1)]
    +        [x.start() for x in t]
    +        [x.join() for x in t]
    +
    +        # the same serial
    +        for s, o in zip(self.seeds, out2):
    +            function(np.random.RandomState(s), o)
    +
    +        # these platforms change x87 fpu precision mode in threads
    +        if np.intp().dtype.itemsize == 4 and sys.platform == "win32":
    +            assert_array_almost_equal(out1, out2)
    +        else:
    +            assert_array_equal(out1, out2)
    +
    +    def test_normal(self):
    +        def gen_random(state, out):
    +            out[...] = state.normal(size=10000)
    +        self.check_function(gen_random, sz=(10000,))
    +
    +    def test_exp(self):
    +        def gen_random(state, out):
    +            out[...] = state.exponential(scale=np.ones((100, 1000)))
    +        self.check_function(gen_random, sz=(100, 1000))
    +
    +    def test_multinomial(self):
    +        def gen_random(state, out):
    +            out[...] = state.multinomial(10, [1 / 6.] * 6, size=10000)
    +        self.check_function(gen_random, sz=(10000, 6))
    +
    +
    +# See Issue #4263
    +class TestSingleEltArrayInput:
    +    def _create_arrays(self):
    +        return np.array([2]), np.array([3]), np.array([4]), (1,)
    +
    +    def test_one_arg_funcs(self):
    +        argOne, _, _, tgtShape = self._create_arrays()
    +        funcs = (np.random.exponential, np.random.standard_gamma,
    +                 np.random.chisquare, np.random.standard_t,
    +                 np.random.pareto, np.random.weibull,
    +                 np.random.power, np.random.rayleigh,
    +                 np.random.poisson, np.random.zipf,
    +                 np.random.geometric, np.random.logseries)
    +
    +        probfuncs = (np.random.geometric, np.random.logseries)
    +
    +        for func in funcs:
    +            if func in probfuncs:  # p < 1.0
    +                out = func(np.array([0.5]))
    +
    +            else:
    +                out = func(argOne)
    +
    +            assert_equal(out.shape, tgtShape)
    +
    +    def test_two_arg_funcs(self):
    +        argOne, argTwo, _, tgtShape = self._create_arrays()
    +        funcs = (np.random.uniform, np.random.normal,
    +                 np.random.beta, np.random.gamma,
    +                 np.random.f, np.random.noncentral_chisquare,
    +                 np.random.vonmises, np.random.laplace,
    +                 np.random.gumbel, np.random.logistic,
    +                 np.random.lognormal, np.random.wald,
    +                 np.random.binomial, np.random.negative_binomial)
    +
    +        probfuncs = (np.random.binomial, np.random.negative_binomial)
    +
    +        for func in funcs:
    +            if func in probfuncs:  # p <= 1
    +                argTwo = np.array([0.5])
    +
    +            else:
    +                argTwo = argTwo
    +
    +            out = func(argOne, argTwo)
    +            assert_equal(out.shape, tgtShape)
    +
    +            out = func(argOne[0], argTwo)
    +            assert_equal(out.shape, tgtShape)
    +
    +            out = func(argOne, argTwo[0])
    +            assert_equal(out.shape, tgtShape)
    +
    +    def test_randint(self):
    +        _, _, _, tgtShape = self._create_arrays()
    +        itype = [bool, np.int8, np.uint8, np.int16, np.uint16,
    +                 np.int32, np.uint32, np.int64, np.uint64]
    +        func = np.random.randint
    +        high = np.array([1])
    +        low = np.array([0])
    +
    +        for dt in itype:
    +            out = func(low, high, dtype=dt)
    +            assert_equal(out.shape, tgtShape)
    +
    +            out = func(low[0], high, dtype=dt)
    +            assert_equal(out.shape, tgtShape)
    +
    +            out = func(low, high[0], dtype=dt)
    +            assert_equal(out.shape, tgtShape)
    +
    +    def test_three_arg_funcs(self):
    +        argOne, argTwo, argThree, tgtShape = self._create_arrays()
    +        funcs = [np.random.noncentral_f, np.random.triangular,
    +                 np.random.hypergeometric]
    +
    +        for func in funcs:
    +            out = func(argOne, argTwo, argThree)
    +            assert_equal(out.shape, tgtShape)
    +
    +            out = func(argOne[0], argTwo, argThree)
    +            assert_equal(out.shape, tgtShape)
    +
    +            out = func(argOne, argTwo[0], argThree)
    +            assert_equal(out.shape, tgtShape)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/test_randomstate.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/test_randomstate.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..63ffb5a86389b3d0d7783dac49a7eb63c25b07ef
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/test_randomstate.py
    @@ -0,0 +1,2099 @@
    +import hashlib
    +import pickle
    +import sys
    +import warnings
    +
    +import pytest
    +
    +import numpy as np
    +from numpy import random
    +from numpy.random import MT19937, PCG64
    +from numpy.testing import (
    +    IS_WASM,
    +    assert_,
    +    assert_array_almost_equal,
    +    assert_array_equal,
    +    assert_equal,
    +    assert_no_warnings,
    +    assert_raises,
    +)
    +
    +INT_FUNCS = {'binomial': (100.0, 0.6),
    +             'geometric': (.5,),
    +             'hypergeometric': (20, 20, 10),
    +             'logseries': (.5,),
    +             'multinomial': (20, np.ones(6) / 6.0),
    +             'negative_binomial': (100, .5),
    +             'poisson': (10.0,),
    +             'zipf': (2,),
    +             }
    +
    +if np.iinfo(np.long).max < 2**32:
    +    # Windows and some 32-bit platforms, e.g., ARM
    +    INT_FUNC_HASHES = {'binomial':          '2fbead005fc63942decb5326d36a1f32fe2c9d32c904ee61e46866b88447c263',  # noqa: E501
    +                       'logseries':         '23ead5dcde35d4cfd4ef2c105e4c3d43304b45dc1b1444b7823b9ee4fa144ebb',  # noqa: E501
    +                       'geometric':         '0d764db64f5c3bad48c8c33551c13b4d07a1e7b470f77629bef6c985cac76fcf',  # noqa: E501
    +                       'hypergeometric':    '7b59bf2f1691626c5815cdcd9a49e1dd68697251d4521575219e4d2a1b8b2c67',  # noqa: E501
    +                       'multinomial':       'd754fa5b92943a38ec07630de92362dd2e02c43577fc147417dc5b9db94ccdd3',  # noqa: E501
    +                       'negative_binomial': '8eb216f7cb2a63cf55605422845caaff002fddc64a7dc8b2d45acd477a49e824',  # noqa: E501
    +                       'poisson':           '70c891d76104013ebd6f6bcf30d403a9074b886ff62e4e6b8eb605bf1a4673b7',  # noqa: E501
    +                       'zipf':              '01f074f97517cd5d21747148ac6ca4074dde7fcb7acbaec0a936606fecacd93f',  # noqa: E501
    +                       }
    +else:
    +    INT_FUNC_HASHES = {'binomial':          '8626dd9d052cb608e93d8868de0a7b347258b199493871a1dc56e2a26cacb112',  # noqa: E501
    +                       'geometric':         '8edd53d272e49c4fc8fbbe6c7d08d563d62e482921f3131d0a0e068af30f0db9',  # noqa: E501
    +                       'hypergeometric':    '83496cc4281c77b786c9b7ad88b74d42e01603a55c60577ebab81c3ba8d45657',  # noqa: E501
    +                       'logseries':         '65878a38747c176bc00e930ebafebb69d4e1e16cd3a704e264ea8f5e24f548db',  # noqa: E501
    +                       'multinomial':       '7a984ae6dca26fd25374479e118b22f55db0aedccd5a0f2584ceada33db98605',  # noqa: E501
    +                       'negative_binomial': 'd636d968e6a24ae92ab52fe11c46ac45b0897e98714426764e820a7d77602a61',  # noqa: E501
    +                       'poisson':           '956552176f77e7c9cb20d0118fc9cf690be488d790ed4b4c4747b965e61b0bb4',  # noqa: E501
    +                       'zipf':              'f84ba7feffda41e606e20b28dfc0f1ea9964a74574513d4a4cbc98433a8bfa45',  # noqa: E501
    +                       }
    +
    +
    +@pytest.fixture(scope='module', params=INT_FUNCS)
    +def int_func(request):
    +    return (request.param, INT_FUNCS[request.param],
    +            INT_FUNC_HASHES[request.param])
    +
    +
    +@pytest.fixture
    +def restore_singleton_bitgen():
    +    """Ensures that the singleton bitgen is restored after a test"""
    +    orig_bitgen = np.random.get_bit_generator()
    +    yield
    +    np.random.set_bit_generator(orig_bitgen)
    +
    +
    +def assert_mt19937_state_equal(a, b):
    +    assert_equal(a['bit_generator'], b['bit_generator'])
    +    assert_array_equal(a['state']['key'], b['state']['key'])
    +    assert_array_equal(a['state']['pos'], b['state']['pos'])
    +    assert_equal(a['has_gauss'], b['has_gauss'])
    +    assert_equal(a['gauss'], b['gauss'])
    +
    +
    +class TestSeed:
    +    def test_scalar(self):
    +        s = random.RandomState(0)
    +        assert_equal(s.randint(1000), 684)
    +        s = random.RandomState(4294967295)
    +        assert_equal(s.randint(1000), 419)
    +
    +    def test_array(self):
    +        s = random.RandomState(range(10))
    +        assert_equal(s.randint(1000), 468)
    +        s = random.RandomState(np.arange(10))
    +        assert_equal(s.randint(1000), 468)
    +        s = random.RandomState([0])
    +        assert_equal(s.randint(1000), 973)
    +        s = random.RandomState([4294967295])
    +        assert_equal(s.randint(1000), 265)
    +
    +    def test_invalid_scalar(self):
    +        # seed must be an unsigned 32 bit integer
    +        assert_raises(TypeError, random.RandomState, -0.5)
    +        assert_raises(ValueError, random.RandomState, -1)
    +
    +    def test_invalid_array(self):
    +        # seed must be an unsigned 32 bit integer
    +        assert_raises(TypeError, random.RandomState, [-0.5])
    +        assert_raises(ValueError, random.RandomState, [-1])
    +        assert_raises(ValueError, random.RandomState, [4294967296])
    +        assert_raises(ValueError, random.RandomState, [1, 2, 4294967296])
    +        assert_raises(ValueError, random.RandomState, [1, -2, 4294967296])
    +
    +    def test_invalid_array_shape(self):
    +        # gh-9832
    +        assert_raises(ValueError, random.RandomState, np.array([],
    +                                                               dtype=np.int64))
    +        assert_raises(ValueError, random.RandomState, [[1, 2, 3]])
    +        assert_raises(ValueError, random.RandomState, [[1, 2, 3],
    +                                                       [4, 5, 6]])
    +
    +    def test_cannot_seed(self):
    +        rs = random.RandomState(PCG64(0))
    +        with assert_raises(TypeError):
    +            rs.seed(1234)
    +
    +    def test_invalid_initialization(self):
    +        assert_raises(ValueError, random.RandomState, MT19937)
    +
    +
    +class TestBinomial:
    +    def test_n_zero(self):
    +        # Tests the corner case of n == 0 for the binomial distribution.
    +        # binomial(0, p) should be zero for any p in [0, 1].
    +        # This test addresses issue #3480.
    +        zeros = np.zeros(2, dtype='int')
    +        for p in [0, .5, 1]:
    +            assert_(random.binomial(0, p) == 0)
    +            assert_array_equal(random.binomial(zeros, p), zeros)
    +
    +    def test_p_is_nan(self):
    +        # Issue #4571.
    +        assert_raises(ValueError, random.binomial, 1, np.nan)
    +
    +
    +class TestMultinomial:
    +    def test_basic(self):
    +        random.multinomial(100, [0.2, 0.8])
    +
    +    def test_zero_probability(self):
    +        random.multinomial(100, [0.2, 0.8, 0.0, 0.0, 0.0])
    +
    +    def test_int_negative_interval(self):
    +        assert_(-5 <= random.randint(-5, -1) < -1)
    +        x = random.randint(-5, -1, 5)
    +        assert_(np.all(-5 <= x))
    +        assert_(np.all(x < -1))
    +
    +    def test_size(self):
    +        # gh-3173
    +        p = [0.5, 0.5]
    +        assert_equal(random.multinomial(1, p, np.uint32(1)).shape, (1, 2))
    +        assert_equal(random.multinomial(1, p, np.uint32(1)).shape, (1, 2))
    +        assert_equal(random.multinomial(1, p, np.uint32(1)).shape, (1, 2))
    +        assert_equal(random.multinomial(1, p, [2, 2]).shape, (2, 2, 2))
    +        assert_equal(random.multinomial(1, p, (2, 2)).shape, (2, 2, 2))
    +        assert_equal(random.multinomial(1, p, np.array((2, 2))).shape,
    +                     (2, 2, 2))
    +
    +        assert_raises(TypeError, random.multinomial, 1, p,
    +                      float(1))
    +
    +    def test_invalid_prob(self):
    +        assert_raises(ValueError, random.multinomial, 100, [1.1, 0.2])
    +        assert_raises(ValueError, random.multinomial, 100, [-.1, 0.9])
    +
    +    def test_invalid_n(self):
    +        assert_raises(ValueError, random.multinomial, -1, [0.8, 0.2])
    +
    +    def test_p_non_contiguous(self):
    +        p = np.arange(15.)
    +        p /= np.sum(p[1::3])
    +        pvals = p[1::3]
    +        rng = random.RandomState(1432985819)
    +        non_contig = rng.multinomial(100, pvals=pvals)
    +        rng = random.RandomState(1432985819)
    +        contig = rng.multinomial(100, pvals=np.ascontiguousarray(pvals))
    +        assert_array_equal(non_contig, contig)
    +
    +    def test_multinomial_pvals_float32(self):
    +        x = np.array([9.9e-01, 9.9e-01, 1.0e-09, 1.0e-09, 1.0e-09, 1.0e-09,
    +                      1.0e-09, 1.0e-09, 1.0e-09, 1.0e-09], dtype=np.float32)
    +        pvals = x / x.sum()
    +        match = r"[\w\s]*pvals array is cast to 64-bit floating"
    +        with pytest.raises(ValueError, match=match):
    +            random.multinomial(1, pvals)
    +
    +    def test_multinomial_n_float(self):
    +        # Non-index integer types should gracefully truncate floats
    +        random.multinomial(100.5, [0.2, 0.8])
    +
    +
    +class TestSetState:
    +    def _create_state(self):
    +        seed = 1234567890
    +        random_state = random.RandomState(seed)
    +        state = random_state.get_state()
    +        return random_state, state
    +
    +    def test_basic(self):
    +        random_state, state = self._create_state()
    +        old = random_state.tomaxint(16)
    +        random_state.set_state(state)
    +        new = random_state.tomaxint(16)
    +        assert_(np.all(old == new))
    +
    +    def test_gaussian_reset(self):
    +        # Make sure the cached every-other-Gaussian is reset.
    +        random_state, state = self._create_state()
    +        old = random_state.standard_normal(size=3)
    +        random_state.set_state(state)
    +        new = random_state.standard_normal(size=3)
    +        assert_(np.all(old == new))
    +
    +    def test_gaussian_reset_in_media_res(self):
    +        # When the state is saved with a cached Gaussian, make sure the
    +        # cached Gaussian is restored.
    +        random_state, state = self._create_state()
    +        random_state.standard_normal()
    +        state = random_state.get_state()
    +        old = random_state.standard_normal(size=3)
    +        random_state.set_state(state)
    +        new = random_state.standard_normal(size=3)
    +        assert_(np.all(old == new))
    +
    +    def test_backwards_compatibility(self):
    +        # Make sure we can accept old state tuples that do not have the
    +        # cached Gaussian value.
    +        random_state, state = self._create_state()
    +        old_state = state[:-2]
    +        x1 = random_state.standard_normal(size=16)
    +        random_state.set_state(old_state)
    +        x2 = random_state.standard_normal(size=16)
    +        random_state.set_state(state)
    +        x3 = random_state.standard_normal(size=16)
    +        assert_(np.all(x1 == x2))
    +        assert_(np.all(x1 == x3))
    +
    +    def test_negative_binomial(self):
    +        # Ensure that the negative binomial results take floating point
    +        # arguments without truncation.
    +        random_state, _ = self._create_state()
    +        random_state.negative_binomial(0.5, 0.5)
    +
    +    def test_get_state_warning(self):
    +        rs = random.RandomState(PCG64())
    +        with pytest.warns(RuntimeWarning):
    +            state = rs.get_state()
    +        assert isinstance(state, dict)
    +        assert state['bit_generator'] == 'PCG64'
    +
    +    def test_invalid_legacy_state_setting(self):
    +        random_state, state = self._create_state()
    +        state = random_state.get_state()
    +        new_state = ('Unknown', ) + state[1:]
    +        assert_raises(ValueError, random_state.set_state, new_state)
    +        assert_raises(TypeError, random_state.set_state,
    +                      np.array(new_state, dtype=object))
    +        state = random_state.get_state(legacy=False)
    +        del state['bit_generator']
    +        assert_raises(ValueError, random_state.set_state, state)
    +
    +    def test_pickle(self):
    +        random_state, _ = self._create_state()
    +        random_state.seed(0)
    +        random_state.random_sample(100)
    +        random_state.standard_normal()
    +        pickled = random_state.get_state(legacy=False)
    +        assert_equal(pickled['has_gauss'], 1)
    +        rs_unpick = pickle.loads(pickle.dumps(random_state))
    +        unpickled = rs_unpick.get_state(legacy=False)
    +        assert_mt19937_state_equal(pickled, unpickled)
    +
    +    def test_state_setting(self):
    +        random_state, state = self._create_state()
    +        attr_state = random_state.__getstate__()
    +        random_state.standard_normal()
    +        random_state.__setstate__(attr_state)
    +        state = random_state.get_state(legacy=False)
    +        assert_mt19937_state_equal(attr_state, state)
    +
    +    def test_repr(self):
    +        random_state, _ = self._create_state()
    +        assert repr(random_state).startswith('RandomState(MT19937)')
    +
    +
    +class TestRandint:
    +
    +    # valid integer/boolean types
    +    itype = [np.bool, np.int8, np.uint8, np.int16, np.uint16,
    +             np.int32, np.uint32, np.int64, np.uint64]
    +
    +    def test_unsupported_type(self):
    +        rng = np.random.RandomState()
    +        assert_raises(TypeError, rng.randint, 1, dtype=float)
    +
    +    def test_bounds_checking(self):
    +        rng = np.random.RandomState()
    +        for dt in self.itype:
    +            lbnd = 0 if dt is np.bool else np.iinfo(dt).min
    +            ubnd = 2 if dt is np.bool else np.iinfo(dt).max + 1
    +            assert_raises(ValueError, rng.randint, lbnd - 1, ubnd, dtype=dt)
    +            assert_raises(ValueError, rng.randint, lbnd, ubnd + 1, dtype=dt)
    +            assert_raises(ValueError, rng.randint, ubnd, lbnd, dtype=dt)
    +            assert_raises(ValueError, rng.randint, 1, 0, dtype=dt)
    +
    +    def test_rng_zero_and_extremes(self):
    +        rng = np.random.RandomState()
    +        for dt in self.itype:
    +            lbnd = 0 if dt is np.bool else np.iinfo(dt).min
    +            ubnd = 2 if dt is np.bool else np.iinfo(dt).max + 1
    +
    +            tgt = ubnd - 1
    +            assert_equal(rng.randint(tgt, tgt + 1, size=1000, dtype=dt), tgt)
    +
    +            tgt = lbnd
    +            assert_equal(rng.randint(tgt, tgt + 1, size=1000, dtype=dt), tgt)
    +
    +            tgt = (lbnd + ubnd) // 2
    +            assert_equal(rng.randint(tgt, tgt + 1, size=1000, dtype=dt), tgt)
    +
    +    def test_full_range(self):
    +        # Test for ticket #1690
    +        rng = np.random.RandomState()
    +
    +        for dt in self.itype:
    +            lbnd = 0 if dt is np.bool else np.iinfo(dt).min
    +            ubnd = 2 if dt is np.bool else np.iinfo(dt).max + 1
    +
    +            try:
    +                rng.randint(lbnd, ubnd, dtype=dt)
    +            except Exception as e:
    +                raise AssertionError("No error should have been raised, "
    +                                     "but one was with the following "
    +                                     "message:\n\n%s" % str(e))
    +
    +    def test_in_bounds_fuzz(self):
    +        # Don't use fixed seed
    +        rng = np.random.RandomState()
    +
    +        for dt in self.itype[1:]:
    +            for ubnd in [4, 8, 16]:
    +                vals = rng.randint(2, ubnd, size=2**16, dtype=dt)
    +                assert_(vals.max() < ubnd)
    +                assert_(vals.min() >= 2)
    +
    +        vals = rng.randint(0, 2, size=2**16, dtype=np.bool)
    +
    +        assert_(vals.max() < 2)
    +        assert_(vals.min() >= 0)
    +
    +    def test_repeatability(self):
    +        # We use a sha256 hash of generated sequences of 1000 samples
    +        # in the range [0, 6) for all but bool, where the range
    +        # is [0, 2). Hashes are for little endian numbers.
    +        tgt = {'bool':   '509aea74d792fb931784c4b0135392c65aec64beee12b0cc167548a2c3d31e71',  # noqa: E501
    +               'int16':  '7b07f1a920e46f6d0fe02314155a2330bcfd7635e708da50e536c5ebb631a7d4',  # noqa: E501
    +               'int32':  'e577bfed6c935de944424667e3da285012e741892dcb7051a8f1ce68ab05c92f',  # noqa: E501
    +               'int64':  '0fbead0b06759df2cfb55e43148822d4a1ff953c7eb19a5b08445a63bb64fa9e',  # noqa: E501
    +               'int8':   '001aac3a5acb935a9b186cbe14a1ca064b8bb2dd0b045d48abeacf74d0203404',  # noqa: E501
    +               'uint16': '7b07f1a920e46f6d0fe02314155a2330bcfd7635e708da50e536c5ebb631a7d4',  # noqa: E501
    +               'uint32': 'e577bfed6c935de944424667e3da285012e741892dcb7051a8f1ce68ab05c92f',  # noqa: E501
    +               'uint64': '0fbead0b06759df2cfb55e43148822d4a1ff953c7eb19a5b08445a63bb64fa9e',  # noqa: E501
    +               'uint8':  '001aac3a5acb935a9b186cbe14a1ca064b8bb2dd0b045d48abeacf74d0203404'}  # noqa: E501
    +
    +        for dt in self.itype[1:]:
    +            rng = random.RandomState(1234)
    +
    +            # view as little endian for hash
    +            if sys.byteorder == 'little':
    +                val = rng.randint(0, 6, size=1000, dtype=dt)
    +            else:
    +                val = rng.randint(0, 6, size=1000, dtype=dt).byteswap()
    +
    +            res = hashlib.sha256(val.view(np.int8)).hexdigest()
    +            assert_(tgt[np.dtype(dt).name] == res)
    +
    +        # bools do not depend on endianness
    +        rng = random.RandomState(1234)
    +        val = rng.randint(0, 2, size=1000, dtype=bool).view(np.int8)
    +        res = hashlib.sha256(val).hexdigest()
    +        assert_(tgt[np.dtype(bool).name] == res)
    +
    +    @pytest.mark.skipif(np.iinfo('l').max < 2**32,
    +                        reason='Cannot test with 32-bit C long')
    +    def test_repeatability_32bit_boundary_broadcasting(self):
    +        desired = np.array([[[3992670689, 2438360420, 2557845020],
    +                             [4107320065, 4142558326, 3216529513],
    +                             [1605979228, 2807061240,  665605495]],
    +                            [[3211410639, 4128781000,  457175120],
    +                             [1712592594, 1282922662, 3081439808],
    +                             [3997822960, 2008322436, 1563495165]],
    +                            [[1398375547, 4269260146,  115316740],
    +                             [3414372578, 3437564012, 2112038651],
    +                             [3572980305, 2260248732, 3908238631]],
    +                            [[2561372503,  223155946, 3127879445],
    +                             [ 441282060, 3514786552, 2148440361],
    +                             [1629275283, 3479737011, 3003195987]],
    +                            [[ 412181688,  940383289, 3047321305],
    +                             [2978368172,  764731833, 2282559898],
    +                             [ 105711276,  720447391, 3596512484]]])
    +        for size in [None, (5, 3, 3)]:
    +            rng = random.RandomState(12345)
    +            x = rng.randint([[-1], [0], [1]], [2**32 - 1, 2**32, 2**32 + 1],
    +                           size=size)
    +            assert_array_equal(x, desired if size is not None else desired[0])
    +
    +    def test_int64_uint64_corner_case(self):
    +        # When stored in Numpy arrays, `lbnd` is casted
    +        # as np.int64, and `ubnd` is casted as np.uint64.
    +        # Checking whether `lbnd` >= `ubnd` used to be
    +        # done solely via direct comparison, which is incorrect
    +        # because when Numpy tries to compare both numbers,
    +        # it casts both to np.float64 because there is
    +        # no integer superset of np.int64 and np.uint64. However,
    +        # `ubnd` is too large to be represented in np.float64,
    +        # causing it be round down to np.iinfo(np.int64).max,
    +        # leading to a ValueError because `lbnd` now equals
    +        # the new `ubnd`.
    +
    +        dt = np.int64
    +        tgt = np.iinfo(np.int64).max
    +        lbnd = np.int64(np.iinfo(np.int64).max)
    +        ubnd = np.uint64(np.iinfo(np.int64).max + 1)
    +
    +        # None of these function calls should
    +        # generate a ValueError now.
    +        actual = random.randint(lbnd, ubnd, dtype=dt)
    +        assert_equal(actual, tgt)
    +
    +    def test_respect_dtype_singleton(self):
    +        # See gh-7203
    +        rng = np.random.RandomState()
    +
    +        for dt in self.itype:
    +            lbnd = 0 if dt is np.bool else np.iinfo(dt).min
    +            ubnd = 2 if dt is np.bool else np.iinfo(dt).max + 1
    +
    +            sample = rng.randint(lbnd, ubnd, dtype=dt)
    +            assert_equal(sample.dtype, np.dtype(dt))
    +
    +        for dt in (bool, int):
    +            # The legacy random generation forces the use of "long" on this
    +            # branch even when the input is `int` and the default dtype
    +            # for int changed (dtype=int is also the functions default)
    +            op_dtype = "long" if dt is int else "bool"
    +            lbnd = 0 if dt is bool else np.iinfo(op_dtype).min
    +            ubnd = 2 if dt is bool else np.iinfo(op_dtype).max + 1
    +
    +            sample = rng.randint(lbnd, ubnd, dtype=dt)
    +            assert_(not hasattr(sample, 'dtype'))
    +            assert_equal(type(sample), dt)
    +
    +
    +class TestRandomDist:
    +    # Make sure the random distribution returns the correct value for a
    +    # given seed
    +    seed = 1234567890
    +
    +    def test_rand(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.rand(3, 2)
    +        desired = np.array([[0.61879477158567997, 0.59162362775974664],
    +                            [0.88868358904449662, 0.89165480011560816],
    +                            [0.4575674820298663, 0.7781880808593471]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_rand_singleton(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.rand()
    +        desired = 0.61879477158567997
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_randn(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.randn(3, 2)
    +        desired = np.array([[1.34016345771863121, 1.73759122771936081],
    +                           [1.498988344300628, -0.2286433324536169],
    +                           [2.031033998682787, 2.17032494605655257]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.randn()
    +        assert_array_almost_equal(actual, desired[0, 0], decimal=15)
    +
    +    def test_randint(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.randint(-99, 99, size=(3, 2))
    +        desired = np.array([[31, 3],
    +                            [-52, 41],
    +                            [-48, -66]])
    +        assert_array_equal(actual, desired)
    +
    +    def test_random_integers(self):
    +        rng = random.RandomState(self.seed)
    +        with pytest.warns(DeprecationWarning):
    +            actual = rng.random_integers(-99, 99, size=(3, 2))
    +        desired = np.array([[31, 3],
    +                            [-52, 41],
    +                            [-48, -66]])
    +        assert_array_equal(actual, desired)
    +
    +        rng = random.RandomState(self.seed)
    +        with pytest.warns(DeprecationWarning):
    +            actual = rng.random_integers(198, size=(3, 2))
    +        assert_array_equal(actual, desired + 100)
    +
    +    def test_tomaxint(self):
    +        rs = random.RandomState(self.seed)
    +        actual = rs.tomaxint(size=(3, 2))
    +        if np.iinfo(np.long).max == 2147483647:
    +            desired = np.array([[1328851649,  731237375],
    +                                [1270502067,  320041495],
    +                                [1908433478,  499156889]], dtype=np.int64)
    +        else:
    +            desired = np.array([[5707374374421908479, 5456764827585442327],
    +                                [8196659375100692377, 8224063923314595285],
    +                                [4220315081820346526, 7177518203184491332]],
    +                               dtype=np.int64)
    +
    +        assert_equal(actual, desired)
    +
    +        rs.seed(self.seed)
    +        actual = rs.tomaxint()
    +        assert_equal(actual, desired[0, 0])
    +
    +    def test_random_integers_max_int(self):
    +        # Tests whether random_integers can generate the
    +        # maximum allowed Python int that can be converted
    +        # into a C long. Previous implementations of this
    +        # method have thrown an OverflowError when attempting
    +        # to generate this integer.
    +        with pytest.warns(DeprecationWarning):
    +            actual = random.random_integers(np.iinfo('l').max,
    +                                            np.iinfo('l').max)
    +
    +        desired = np.iinfo('l').max
    +        assert_equal(actual, desired)
    +        with pytest.warns(DeprecationWarning):
    +            typer = np.dtype('l').type
    +            actual = random.random_integers(typer(np.iinfo('l').max),
    +                                            typer(np.iinfo('l').max))
    +        assert_equal(actual, desired)
    +
    +    def test_random_integers_deprecated(self):
    +        with warnings.catch_warnings():
    +            warnings.simplefilter("error", DeprecationWarning)
    +
    +            # DeprecationWarning raised with high == None
    +            assert_raises(DeprecationWarning,
    +                          random.random_integers,
    +                          np.iinfo('l').max)
    +
    +            # DeprecationWarning raised with high != None
    +            assert_raises(DeprecationWarning,
    +                          random.random_integers,
    +                          np.iinfo('l').max, np.iinfo('l').max)
    +
    +    def test_random_sample(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.random_sample((3, 2))
    +        desired = np.array([[0.61879477158567997, 0.59162362775974664],
    +                            [0.88868358904449662, 0.89165480011560816],
    +                            [0.4575674820298663, 0.7781880808593471]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.random_sample()
    +        assert_array_almost_equal(actual, desired[0, 0], decimal=15)
    +
    +    def test_choice_uniform_replace(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.choice(4, 4)
    +        desired = np.array([2, 3, 2, 3])
    +        assert_array_equal(actual, desired)
    +
    +    def test_choice_nonuniform_replace(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.choice(4, 4, p=[0.4, 0.4, 0.1, 0.1])
    +        desired = np.array([1, 1, 2, 2])
    +        assert_array_equal(actual, desired)
    +
    +    def test_choice_uniform_noreplace(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.choice(4, 3, replace=False)
    +        desired = np.array([0, 1, 3])
    +        assert_array_equal(actual, desired)
    +
    +    def test_choice_nonuniform_noreplace(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.choice(4, 3, replace=False, p=[0.1, 0.3, 0.5, 0.1])
    +        desired = np.array([2, 3, 1])
    +        assert_array_equal(actual, desired)
    +
    +    def test_choice_noninteger(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.choice(['a', 'b', 'c', 'd'], 4)
    +        desired = np.array(['c', 'd', 'c', 'd'])
    +        assert_array_equal(actual, desired)
    +
    +    def test_choice_exceptions(self):
    +        sample = random.choice
    +        assert_raises(ValueError, sample, -1, 3)
    +        assert_raises(ValueError, sample, 3., 3)
    +        assert_raises(ValueError, sample, [[1, 2], [3, 4]], 3)
    +        assert_raises(ValueError, sample, [], 3)
    +        assert_raises(ValueError, sample, [1, 2, 3, 4], 3,
    +                      p=[[0.25, 0.25], [0.25, 0.25]])
    +        assert_raises(ValueError, sample, [1, 2], 3, p=[0.4, 0.4, 0.2])
    +        assert_raises(ValueError, sample, [1, 2], 3, p=[1.1, -0.1])
    +        assert_raises(ValueError, sample, [1, 2], 3, p=[0.4, 0.4])
    +        assert_raises(ValueError, sample, [1, 2, 3], 4, replace=False)
    +        # gh-13087
    +        assert_raises(ValueError, sample, [1, 2, 3], -2, replace=False)
    +        assert_raises(ValueError, sample, [1, 2, 3], (-1,), replace=False)
    +        assert_raises(ValueError, sample, [1, 2, 3], (-1, 1), replace=False)
    +        assert_raises(ValueError, sample, [1, 2, 3], 2,
    +                      replace=False, p=[1, 0, 0])
    +
    +    def test_choice_return_shape(self):
    +        p = [0.1, 0.9]
    +        # Check scalar
    +        assert_(np.isscalar(random.choice(2, replace=True)))
    +        assert_(np.isscalar(random.choice(2, replace=False)))
    +        assert_(np.isscalar(random.choice(2, replace=True, p=p)))
    +        assert_(np.isscalar(random.choice(2, replace=False, p=p)))
    +        assert_(np.isscalar(random.choice([1, 2], replace=True)))
    +        assert_(random.choice([None], replace=True) is None)
    +        a = np.array([1, 2])
    +        arr = np.empty(1, dtype=object)
    +        arr[0] = a
    +        assert_(random.choice(arr, replace=True) is a)
    +
    +        # Check 0-d array
    +        s = ()
    +        assert_(not np.isscalar(random.choice(2, s, replace=True)))
    +        assert_(not np.isscalar(random.choice(2, s, replace=False)))
    +        assert_(not np.isscalar(random.choice(2, s, replace=True, p=p)))
    +        assert_(not np.isscalar(random.choice(2, s, replace=False, p=p)))
    +        assert_(not np.isscalar(random.choice([1, 2], s, replace=True)))
    +        assert_(random.choice([None], s, replace=True).ndim == 0)
    +        a = np.array([1, 2])
    +        arr = np.empty(1, dtype=object)
    +        arr[0] = a
    +        assert_(random.choice(arr, s, replace=True).item() is a)
    +
    +        # Check multi dimensional array
    +        s = (2, 3)
    +        p = [0.1, 0.1, 0.1, 0.1, 0.4, 0.2]
    +        assert_equal(random.choice(6, s, replace=True).shape, s)
    +        assert_equal(random.choice(6, s, replace=False).shape, s)
    +        assert_equal(random.choice(6, s, replace=True, p=p).shape, s)
    +        assert_equal(random.choice(6, s, replace=False, p=p).shape, s)
    +        assert_equal(random.choice(np.arange(6), s, replace=True).shape, s)
    +
    +        # Check zero-size
    +        assert_equal(random.randint(0, 0, size=(3, 0, 4)).shape, (3, 0, 4))
    +        assert_equal(random.randint(0, -10, size=0).shape, (0,))
    +        assert_equal(random.randint(10, 10, size=0).shape, (0,))
    +        assert_equal(random.choice(0, size=0).shape, (0,))
    +        assert_equal(random.choice([], size=(0,)).shape, (0,))
    +        assert_equal(random.choice(['a', 'b'], size=(3, 0, 4)).shape,
    +                     (3, 0, 4))
    +        assert_raises(ValueError, random.choice, [], 10)
    +
    +    def test_choice_nan_probabilities(self):
    +        a = np.array([42, 1, 2])
    +        p = [None, None, None]
    +        assert_raises(ValueError, random.choice, a, p=p)
    +
    +    def test_choice_p_non_contiguous(self):
    +        p = np.ones(10) / 5
    +        p[1::2] = 3.0
    +        rng = random.RandomState(self.seed)
    +        non_contig = rng.choice(5, 3, p=p[::2])
    +        rng = random.RandomState(self.seed)
    +        contig = rng.choice(5, 3, p=np.ascontiguousarray(p[::2]))
    +        assert_array_equal(non_contig, contig)
    +
    +    def test_bytes(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.bytes(10)
    +        desired = b'\x82Ui\x9e\xff\x97+Wf\xa5'
    +        assert_equal(actual, desired)
    +
    +    def test_shuffle(self):
    +        # Test lists, arrays (of various dtypes), and multidimensional versions
    +        # of both, c-contiguous or not:
    +        for conv in [lambda x: np.array([]),
    +                     lambda x: x,
    +                     lambda x: np.asarray(x).astype(np.int8),
    +                     lambda x: np.asarray(x).astype(np.float32),
    +                     lambda x: np.asarray(x).astype(np.complex64),
    +                     lambda x: np.asarray(x).astype(object),
    +                     lambda x: [(i, i) for i in x],
    +                     lambda x: np.asarray([[i, i] for i in x]),
    +                     lambda x: np.vstack([x, x]).T,
    +                     # gh-11442
    +                     lambda x: (np.asarray([(i, i) for i in x],
    +                                           [("a", int), ("b", int)])
    +                                .view(np.recarray)),
    +                     # gh-4270
    +                     lambda x: np.asarray([(i, i) for i in x],
    +                                          [("a", object, (1,)),
    +                                           ("b", np.int32, (1,))])]:
    +            rng = random.RandomState(self.seed)
    +            alist = conv([1, 2, 3, 4, 5, 6, 7, 8, 9, 0])
    +            rng.shuffle(alist)
    +            actual = alist
    +            desired = conv([0, 1, 9, 6, 2, 4, 5, 8, 7, 3])
    +            assert_array_equal(actual, desired)
    +
    +    def test_shuffle_masked(self):
    +        # gh-3263
    +        a = np.ma.masked_values(np.reshape(range(20), (5, 4)) % 3 - 1, -1)
    +        b = np.ma.masked_values(np.arange(20) % 3 - 1, -1)
    +        a_orig = a.copy()
    +        b_orig = b.copy()
    +        for i in range(50):
    +            random.shuffle(a)
    +            assert_equal(
    +                sorted(a.data[~a.mask]), sorted(a_orig.data[~a_orig.mask]))
    +            random.shuffle(b)
    +            assert_equal(
    +                sorted(b.data[~b.mask]), sorted(b_orig.data[~b_orig.mask]))
    +
    +        def test_shuffle_invalid_objects(self):
    +            x = np.array(3)
    +            assert_raises(TypeError, random.shuffle, x)
    +
    +    def test_permutation(self):
    +        rng = random.RandomState(self.seed)
    +        alist = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
    +        actual = rng.permutation(alist)
    +        desired = [0, 1, 9, 6, 2, 4, 5, 8, 7, 3]
    +        assert_array_equal(actual, desired)
    +
    +        rng = random.RandomState(self.seed)
    +        arr_2d = np.atleast_2d([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]).T
    +        actual = rng.permutation(arr_2d)
    +        assert_array_equal(actual, np.atleast_2d(desired).T)
    +
    +        rng = random.RandomState(self.seed)
    +        bad_x_str = "abcd"
    +        assert_raises(IndexError, random.permutation, bad_x_str)
    +
    +        rng = random.RandomState(self.seed)
    +        bad_x_float = 1.2
    +        assert_raises(IndexError, random.permutation, bad_x_float)
    +
    +        integer_val = 10
    +        desired = [9, 0, 8, 5, 1, 3, 4, 7, 6, 2]
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.permutation(integer_val)
    +        assert_array_equal(actual, desired)
    +
    +    def test_beta(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.beta(.1, .9, size=(3, 2))
    +        desired = np.array(
    +                [[1.45341850513746058e-02, 5.31297615662868145e-04],
    +                 [1.85366619058432324e-06, 4.19214516800110563e-03],
    +                 [1.58405155108498093e-04, 1.26252891949397652e-04]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_binomial(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.binomial(100.123, .456, size=(3, 2))
    +        desired = np.array([[37, 43],
    +                            [42, 48],
    +                            [46, 45]])
    +        assert_array_equal(actual, desired)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.binomial(100.123, .456)
    +        desired = 37
    +        assert_array_equal(actual, desired)
    +
    +    def test_chisquare(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.chisquare(50, size=(3, 2))
    +        desired = np.array([[63.87858175501090585, 68.68407748911370447],
    +                            [65.77116116901505904, 47.09686762438974483],
    +                            [72.3828403199695174, 74.18408615260374006]])
    +        assert_array_almost_equal(actual, desired, decimal=13)
    +
    +    def test_dirichlet(self):
    +        rng = random.RandomState(self.seed)
    +        alpha = np.array([51.72840233779265162, 39.74494232180943953])
    +        actual = rng.dirichlet(alpha, size=(3, 2))
    +        desired = np.array([[[0.54539444573611562, 0.45460555426388438],
    +                             [0.62345816822039413, 0.37654183177960598]],
    +                            [[0.55206000085785778, 0.44793999914214233],
    +                             [0.58964023305154301, 0.41035976694845688]],
    +                            [[0.59266909280647828, 0.40733090719352177],
    +                             [0.56974431743975207, 0.43025568256024799]]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +        bad_alpha = np.array([5.4e-01, -1.0e-16])
    +        assert_raises(ValueError, random.dirichlet, bad_alpha)
    +
    +        rng = random.RandomState(self.seed)
    +        alpha = np.array([51.72840233779265162, 39.74494232180943953])
    +        actual = rng.dirichlet(alpha)
    +        assert_array_almost_equal(actual, desired[0, 0], decimal=15)
    +
    +    def test_dirichlet_size(self):
    +        # gh-3173
    +        p = np.array([51.72840233779265162, 39.74494232180943953])
    +        assert_equal(random.dirichlet(p, np.uint32(1)).shape, (1, 2))
    +        assert_equal(random.dirichlet(p, np.uint32(1)).shape, (1, 2))
    +        assert_equal(random.dirichlet(p, np.uint32(1)).shape, (1, 2))
    +        assert_equal(random.dirichlet(p, [2, 2]).shape, (2, 2, 2))
    +        assert_equal(random.dirichlet(p, (2, 2)).shape, (2, 2, 2))
    +        assert_equal(random.dirichlet(p, np.array((2, 2))).shape, (2, 2, 2))
    +
    +        assert_raises(TypeError, random.dirichlet, p, float(1))
    +
    +    def test_dirichlet_bad_alpha(self):
    +        # gh-2089
    +        alpha = np.array([5.4e-01, -1.0e-16])
    +        assert_raises(ValueError, random.dirichlet, alpha)
    +
    +    def test_dirichlet_alpha_non_contiguous(self):
    +        a = np.array([51.72840233779265162, -1.0, 39.74494232180943953])
    +        alpha = a[::2]
    +        rng = random.RandomState(self.seed)
    +        non_contig = rng.dirichlet(alpha, size=(3, 2))
    +        rng = random.RandomState(self.seed)
    +        contig = rng.dirichlet(np.ascontiguousarray(alpha),
    +                                  size=(3, 2))
    +        assert_array_almost_equal(non_contig, contig)
    +
    +    def test_exponential(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.exponential(1.1234, size=(3, 2))
    +        desired = np.array([[1.08342649775011624, 1.00607889924557314],
    +                            [2.46628830085216721, 2.49668106809923884],
    +                            [0.68717433461363442, 1.69175666993575979]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_exponential_0(self):
    +        assert_equal(random.exponential(scale=0), 0)
    +        assert_raises(ValueError, random.exponential, scale=-0.)
    +
    +    def test_f(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.f(12, 77, size=(3, 2))
    +        desired = np.array([[1.21975394418575878, 1.75135759791559775],
    +                            [1.44803115017146489, 1.22108959480396262],
    +                            [1.02176975757740629, 1.34431827623300415]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_gamma(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.gamma(5, 3, size=(3, 2))
    +        desired = np.array([[24.60509188649287182, 28.54993563207210627],
    +                            [26.13476110204064184, 12.56988482927716078],
    +                            [31.71863275789960568, 33.30143302795922011]])
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +
    +    def test_gamma_0(self):
    +        assert_equal(random.gamma(shape=0, scale=0), 0)
    +        assert_raises(ValueError, random.gamma, shape=-0., scale=-0.)
    +
    +    def test_geometric(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.geometric(.123456789, size=(3, 2))
    +        desired = np.array([[8, 7],
    +                            [17, 17],
    +                            [5, 12]])
    +        assert_array_equal(actual, desired)
    +
    +    def test_geometric_exceptions(self):
    +        assert_raises(ValueError, random.geometric, 1.1)
    +        assert_raises(ValueError, random.geometric, [1.1] * 10)
    +        assert_raises(ValueError, random.geometric, -0.1)
    +        assert_raises(ValueError, random.geometric, [-0.1] * 10)
    +        with warnings.catch_warnings():
    +            warnings.simplefilter('ignore', RuntimeWarning)
    +            assert_raises(ValueError, random.geometric, np.nan)
    +            assert_raises(ValueError, random.geometric, [np.nan] * 10)
    +
    +    def test_gumbel(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.gumbel(loc=.123456789, scale=2.0, size=(3, 2))
    +        desired = np.array([[0.19591898743416816, 0.34405539668096674],
    +                            [-1.4492522252274278, -1.47374816298446865],
    +                            [1.10651090478803416, -0.69535848626236174]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_gumbel_0(self):
    +        assert_equal(random.gumbel(scale=0), 0)
    +        assert_raises(ValueError, random.gumbel, scale=-0.)
    +
    +    def test_hypergeometric(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.hypergeometric(10.1, 5.5, 14, size=(3, 2))
    +        desired = np.array([[10, 10],
    +                            [10, 10],
    +                            [9, 9]])
    +        assert_array_equal(actual, desired)
    +
    +        # Test nbad = 0
    +        actual = rng.hypergeometric(5, 0, 3, size=4)
    +        desired = np.array([3, 3, 3, 3])
    +        assert_array_equal(actual, desired)
    +
    +        actual = rng.hypergeometric(15, 0, 12, size=4)
    +        desired = np.array([12, 12, 12, 12])
    +        assert_array_equal(actual, desired)
    +
    +        # Test ngood = 0
    +        actual = rng.hypergeometric(0, 5, 3, size=4)
    +        desired = np.array([0, 0, 0, 0])
    +        assert_array_equal(actual, desired)
    +
    +        actual = rng.hypergeometric(0, 15, 12, size=4)
    +        desired = np.array([0, 0, 0, 0])
    +        assert_array_equal(actual, desired)
    +
    +    def test_laplace(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.laplace(loc=.123456789, scale=2.0, size=(3, 2))
    +        desired = np.array([[0.66599721112760157, 0.52829452552221945],
    +                            [3.12791959514407125, 3.18202813572992005],
    +                            [-0.05391065675859356, 1.74901336242837324]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_laplace_0(self):
    +        assert_equal(random.laplace(scale=0), 0)
    +        assert_raises(ValueError, random.laplace, scale=-0.)
    +
    +    def test_logistic(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.logistic(loc=.123456789, scale=2.0, size=(3, 2))
    +        desired = np.array([[1.09232835305011444, 0.8648196662399954],
    +                            [4.27818590694950185, 4.33897006346929714],
    +                            [-0.21682183359214885, 2.63373365386060332]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_lognormal(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.lognormal(mean=.123456789, sigma=2.0, size=(3, 2))
    +        desired = np.array([[16.50698631688883822, 36.54846706092654784],
    +                            [22.67886599981281748, 0.71617561058995771],
    +                            [65.72798501792723869, 86.84341601437161273]])
    +        assert_array_almost_equal(actual, desired, decimal=13)
    +
    +    def test_lognormal_0(self):
    +        assert_equal(random.lognormal(sigma=0), 1)
    +        assert_raises(ValueError, random.lognormal, sigma=-0.)
    +
    +    def test_logseries(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.logseries(p=.923456789, size=(3, 2))
    +        desired = np.array([[2, 2],
    +                            [6, 17],
    +                            [3, 6]])
    +        assert_array_equal(actual, desired)
    +
    +    def test_logseries_zero(self):
    +        assert random.logseries(0) == 1
    +
    +    @pytest.mark.parametrize("value", [np.nextafter(0., -1), 1., np.nan, 5.])
    +    def test_logseries_exceptions(self, value):
    +        with np.errstate(invalid="ignore"):
    +            with pytest.raises(ValueError):
    +                random.logseries(value)
    +            with pytest.raises(ValueError):
    +                # contiguous path:
    +                random.logseries(np.array([value] * 10))
    +            with pytest.raises(ValueError):
    +                # non-contiguous path:
    +                random.logseries(np.array([value] * 10)[::2])
    +
    +    def test_multinomial(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.multinomial(20, [1 / 6.] * 6, size=(3, 2))
    +        desired = np.array([[[4, 3, 5, 4, 2, 2],
    +                             [5, 2, 8, 2, 2, 1]],
    +                            [[3, 4, 3, 6, 0, 4],
    +                             [2, 1, 4, 3, 6, 4]],
    +                            [[4, 4, 2, 5, 2, 3],
    +                             [4, 3, 4, 2, 3, 4]]])
    +        assert_array_equal(actual, desired)
    +
    +    def test_multivariate_normal(self):
    +        rng = random.RandomState(self.seed)
    +        mean = (.123456789, 10)
    +        cov = [[1, 0], [0, 1]]
    +        size = (3, 2)
    +        actual = rng.multivariate_normal(mean, cov, size)
    +        desired = np.array([[[1.463620246718631, 11.73759122771936],
    +                             [1.622445133300628, 9.771356667546383]],
    +                            [[2.154490787682787, 12.170324946056553],
    +                             [1.719909438201865, 9.230548443648306]],
    +                            [[0.689515026297799, 9.880729819607714],
    +                             [-0.023054015651998, 9.201096623542879]]])
    +
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +        # Check for default size, was raising deprecation warning
    +        actual = rng.multivariate_normal(mean, cov)
    +        desired = np.array([0.895289569463708, 9.17180864067987])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +        # Check that non positive-semidefinite covariance warns with
    +        # RuntimeWarning
    +        mean = [0, 0]
    +        cov = [[1, 2], [2, 1]]
    +        pytest.warns(RuntimeWarning, rng.multivariate_normal, mean, cov)
    +
    +        # and that it doesn't warn with RuntimeWarning check_valid='ignore'
    +        assert_no_warnings(rng.multivariate_normal, mean, cov,
    +                           check_valid='ignore')
    +
    +        # and that it raises with RuntimeWarning check_valid='raises'
    +        assert_raises(ValueError, rng.multivariate_normal, mean, cov,
    +                      check_valid='raise')
    +
    +        cov = np.array([[1, 0.1], [0.1, 1]], dtype=np.float32)
    +        with warnings.catch_warnings():
    +            warnings.simplefilter('error', RuntimeWarning)
    +            rng.multivariate_normal(mean, cov)
    +
    +        mu = np.zeros(2)
    +        cov = np.eye(2)
    +        assert_raises(ValueError, rng.multivariate_normal, mean, cov,
    +                      check_valid='other')
    +        assert_raises(ValueError, rng.multivariate_normal,
    +                      np.zeros((2, 1, 1)), cov)
    +        assert_raises(ValueError, rng.multivariate_normal,
    +                      mu, np.empty((3, 2)))
    +        assert_raises(ValueError, rng.multivariate_normal,
    +                      mu, np.eye(3))
    +
    +    def test_negative_binomial(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.negative_binomial(n=100, p=.12345, size=(3, 2))
    +        desired = np.array([[848, 841],
    +                            [892, 611],
    +                            [779, 647]])
    +        assert_array_equal(actual, desired)
    +
    +    def test_negative_binomial_exceptions(self):
    +        with warnings.catch_warnings():
    +            warnings.simplefilter('ignore', RuntimeWarning)
    +            assert_raises(ValueError, random.negative_binomial, 100, np.nan)
    +            assert_raises(ValueError, random.negative_binomial, 100,
    +                          [np.nan] * 10)
    +
    +    def test_noncentral_chisquare(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.noncentral_chisquare(df=5, nonc=5, size=(3, 2))
    +        desired = np.array([[23.91905354498517511, 13.35324692733826346],
    +                            [31.22452661329736401, 16.60047399466177254],
    +                            [5.03461598262724586, 17.94973089023519464]])
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +
    +        actual = rng.noncentral_chisquare(df=.5, nonc=.2, size=(3, 2))
    +        desired = np.array([[1.47145377828516666,  0.15052899268012659],
    +                            [0.00943803056963588,  1.02647251615666169],
    +                            [0.332334982684171,  0.15451287602753125]])
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.noncentral_chisquare(df=5, nonc=0, size=(3, 2))
    +        desired = np.array([[9.597154162763948, 11.725484450296079],
    +                            [10.413711048138335, 3.694475922923986],
    +                            [13.484222138963087, 14.377255424602957]])
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +
    +    def test_noncentral_f(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.noncentral_f(dfnum=5, dfden=2, nonc=1,
    +                                     size=(3, 2))
    +        desired = np.array([[1.40598099674926669, 0.34207973179285761],
    +                            [3.57715069265772545, 7.92632662577829805],
    +                            [0.43741599463544162, 1.1774208752428319]])
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +
    +    def test_noncentral_f_nan(self):
    +        random.seed(self.seed)
    +        actual = random.noncentral_f(dfnum=5, dfden=2, nonc=np.nan)
    +        assert np.isnan(actual)
    +
    +    def test_normal(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.normal(loc=.123456789, scale=2.0, size=(3, 2))
    +        desired = np.array([[2.80378370443726244, 3.59863924443872163],
    +                            [3.121433477601256, -0.33382987590723379],
    +                            [4.18552478636557357, 4.46410668111310471]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_normal_0(self):
    +        assert_equal(random.normal(scale=0), 0)
    +        assert_raises(ValueError, random.normal, scale=-0.)
    +
    +    def test_pareto(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.pareto(a=.123456789, size=(3, 2))
    +        desired = np.array(
    +                [[2.46852460439034849e+03, 1.41286880810518346e+03],
    +                 [5.28287797029485181e+07, 6.57720981047328785e+07],
    +                 [1.40840323350391515e+02, 1.98390255135251704e+05]])
    +        # For some reason on 32-bit x86 Ubuntu 12.10 the [1, 0] entry in this
    +        # matrix differs by 24 nulps. Discussion:
    +        #   https://mail.python.org/pipermail/numpy-discussion/2012-September/063801.html
    +        # Consensus is that this is probably some gcc quirk that affects
    +        # rounding but not in any important way, so we just use a looser
    +        # tolerance on this test:
    +        np.testing.assert_array_almost_equal_nulp(actual, desired, nulp=30)
    +
    +    def test_poisson(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.poisson(lam=.123456789, size=(3, 2))
    +        desired = np.array([[0, 0],
    +                            [1, 0],
    +                            [0, 0]])
    +        assert_array_equal(actual, desired)
    +
    +    def test_poisson_exceptions(self):
    +        lambig = np.iinfo('l').max
    +        lamneg = -1
    +        assert_raises(ValueError, random.poisson, lamneg)
    +        assert_raises(ValueError, random.poisson, [lamneg] * 10)
    +        assert_raises(ValueError, random.poisson, lambig)
    +        assert_raises(ValueError, random.poisson, [lambig] * 10)
    +        with warnings.catch_warnings():
    +            warnings.simplefilter('ignore', RuntimeWarning)
    +            assert_raises(ValueError, random.poisson, np.nan)
    +            assert_raises(ValueError, random.poisson, [np.nan] * 10)
    +
    +    def test_power(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.power(a=.123456789, size=(3, 2))
    +        desired = np.array([[0.02048932883240791, 0.01424192241128213],
    +                            [0.38446073748535298, 0.39499689943484395],
    +                            [0.00177699707563439, 0.13115505880863756]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_rayleigh(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.rayleigh(scale=10, size=(3, 2))
    +        desired = np.array([[13.8882496494248393, 13.383318339044731],
    +                            [20.95413364294492098, 21.08285015800712614],
    +                            [11.06066537006854311, 17.35468505778271009]])
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +
    +    def test_rayleigh_0(self):
    +        assert_equal(random.rayleigh(scale=0), 0)
    +        assert_raises(ValueError, random.rayleigh, scale=-0.)
    +
    +    def test_standard_cauchy(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.standard_cauchy(size=(3, 2))
    +        desired = np.array([[0.77127660196445336, -6.55601161955910605],
    +                            [0.93582023391158309, -2.07479293013759447],
    +                            [-4.74601644297011926, 0.18338989290760804]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_standard_exponential(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.standard_exponential(size=(3, 2))
    +        desired = np.array([[0.96441739162374596, 0.89556604882105506],
    +                            [2.1953785836319808, 2.22243285392490542],
    +                            [0.6116915921431676, 1.50592546727413201]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_standard_gamma(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.standard_gamma(shape=3, size=(3, 2))
    +        desired = np.array([[5.50841531318455058, 6.62953470301903103],
    +                            [5.93988484943779227, 2.31044849402133989],
    +                            [7.54838614231317084, 8.012756093271868]])
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +
    +    def test_standard_gamma_0(self):
    +        assert_equal(random.standard_gamma(shape=0), 0)
    +        assert_raises(ValueError, random.standard_gamma, shape=-0.)
    +
    +    def test_standard_normal(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.standard_normal(size=(3, 2))
    +        desired = np.array([[1.34016345771863121, 1.73759122771936081],
    +                            [1.498988344300628, -0.2286433324536169],
    +                            [2.031033998682787, 2.17032494605655257]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_randn_singleton(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.randn()
    +        desired = np.array(1.34016345771863121)
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_standard_t(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.standard_t(df=10, size=(3, 2))
    +        desired = np.array([[0.97140611862659965, -0.08830486548450577],
    +                            [1.36311143689505321, -0.55317463909867071],
    +                            [-0.18473749069684214, 0.61181537341755321]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_triangular(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.triangular(left=5.12, mode=10.23, right=20.34,
    +                                   size=(3, 2))
    +        desired = np.array([[12.68117178949215784, 12.4129206149193152],
    +                            [16.20131377335158263, 16.25692138747600524],
    +                            [11.20400690911820263, 14.4978144835829923]])
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +
    +    def test_uniform(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.uniform(low=1.23, high=10.54, size=(3, 2))
    +        desired = np.array([[6.99097932346268003, 6.73801597444323974],
    +                            [9.50364421400426274, 9.53130618907631089],
    +                            [5.48995325769805476, 8.47493103280052118]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_uniform_range_bounds(self):
    +        fmin = np.finfo('float').min
    +        fmax = np.finfo('float').max
    +
    +        func = random.uniform
    +        assert_raises(OverflowError, func, -np.inf, 0)
    +        assert_raises(OverflowError, func, 0, np.inf)
    +        assert_raises(OverflowError, func, fmin, fmax)
    +        assert_raises(OverflowError, func, [-np.inf], [0])
    +        assert_raises(OverflowError, func, [0], [np.inf])
    +
    +        # (fmax / 1e17) - fmin is within range, so this should not throw
    +        # account for i386 extended precision DBL_MAX / 1e17 + DBL_MAX >
    +        # DBL_MAX by increasing fmin a bit
    +        random.uniform(low=np.nextafter(fmin, 1), high=fmax / 1e17)
    +
    +    def test_scalar_exception_propagation(self):
    +        # Tests that exceptions are correctly propagated in distributions
    +        # when called with objects that throw exceptions when converted to
    +        # scalars.
    +        #
    +        # Regression test for gh: 8865
    +
    +        class ThrowingFloat(np.ndarray):
    +            def __float__(self):
    +                raise TypeError
    +
    +        throwing_float = np.array(1.0).view(ThrowingFloat)
    +        assert_raises(TypeError, random.uniform, throwing_float,
    +                      throwing_float)
    +
    +        class ThrowingInteger(np.ndarray):
    +            def __int__(self):
    +                raise TypeError
    +
    +        throwing_int = np.array(1).view(ThrowingInteger)
    +        assert_raises(TypeError, random.hypergeometric, throwing_int, 1, 1)
    +
    +    def test_vonmises(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.vonmises(mu=1.23, kappa=1.54, size=(3, 2))
    +        desired = np.array([[2.28567572673902042, 2.89163838442285037],
    +                            [0.38198375564286025, 2.57638023113890746],
    +                            [1.19153771588353052, 1.83509849681825354]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_vonmises_small(self):
    +        # check infinite loop, gh-4720
    +        random.seed(self.seed)
    +        r = random.vonmises(mu=0., kappa=1.1e-8, size=10**6)
    +        assert_(np.isfinite(r).all())
    +
    +    def test_vonmises_large(self):
    +        # guard against changes in RandomState when Generator is fixed
    +        rng = random.RandomState(self.seed)
    +        actual = rng.vonmises(mu=0., kappa=1e7, size=3)
    +        desired = np.array([4.634253748521111e-04,
    +                            3.558873596114509e-04,
    +                            -2.337119622577433e-04])
    +        assert_array_almost_equal(actual, desired, decimal=8)
    +
    +    def test_vonmises_nan(self):
    +        random.seed(self.seed)
    +        r = random.vonmises(mu=0., kappa=np.nan)
    +        assert_(np.isnan(r))
    +
    +    def test_wald(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.wald(mean=1.23, scale=1.54, size=(3, 2))
    +        desired = np.array([[3.82935265715889983, 5.13125249184285526],
    +                            [0.35045403618358717, 1.50832396872003538],
    +                            [0.24124319895843183, 0.22031101461955038]])
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +
    +    def test_weibull(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.weibull(a=1.23, size=(3, 2))
    +        desired = np.array([[0.97097342648766727, 0.91422896443565516],
    +                            [1.89517770034962929, 1.91414357960479564],
    +                            [0.67057783752390987, 1.39494046635066793]])
    +        assert_array_almost_equal(actual, desired, decimal=15)
    +
    +    def test_weibull_0(self):
    +        random.seed(self.seed)
    +        assert_equal(random.weibull(a=0, size=12), np.zeros(12))
    +        assert_raises(ValueError, random.weibull, a=-0.)
    +
    +    def test_zipf(self):
    +        rng = random.RandomState(self.seed)
    +        actual = rng.zipf(a=1.23, size=(3, 2))
    +        desired = np.array([[66, 29],
    +                            [1, 1],
    +                            [3, 13]])
    +        assert_array_equal(actual, desired)
    +
    +
    +class TestBroadcast:
    +    # tests that functions that broadcast behave
    +    # correctly when presented with non-scalar arguments
    +    seed = 123456789
    +
    +    def test_uniform(self):
    +        low = [0]
    +        high = [1]
    +        desired = np.array([0.53283302478975902,
    +                            0.53413660089041659,
    +                            0.50955303552646702])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.uniform(low * 3, high)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.uniform(low, high * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +
    +    def test_normal(self):
    +        loc = [0]
    +        scale = [1]
    +        bad_scale = [-1]
    +        desired = np.array([2.2129019979039612,
    +                            2.1283977976520019,
    +                            1.8417114045748335])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.normal(loc * 3, scale)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.normal, loc * 3, bad_scale)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.normal(loc, scale * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.normal, loc, bad_scale * 3)
    +
    +    def test_beta(self):
    +        a = [1]
    +        b = [2]
    +        bad_a = [-1]
    +        bad_b = [-2]
    +        desired = np.array([0.19843558305989056,
    +                            0.075230336409423643,
    +                            0.24976865978980844])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.beta(a * 3, b)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.beta, bad_a * 3, b)
    +        assert_raises(ValueError, rng.beta, a * 3, bad_b)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.beta(a, b * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.beta, bad_a, b * 3)
    +        assert_raises(ValueError, rng.beta, a, bad_b * 3)
    +
    +    def test_exponential(self):
    +        scale = [1]
    +        bad_scale = [-1]
    +        desired = np.array([0.76106853658845242,
    +                            0.76386282278691653,
    +                            0.71243813125891797])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.exponential(scale * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.exponential, bad_scale * 3)
    +
    +    def test_standard_gamma(self):
    +        shape = [1]
    +        bad_shape = [-1]
    +        desired = np.array([0.76106853658845242,
    +                            0.76386282278691653,
    +                            0.71243813125891797])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.standard_gamma(shape * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.standard_gamma, bad_shape * 3)
    +
    +    def test_gamma(self):
    +        shape = [1]
    +        scale = [2]
    +        bad_shape = [-1]
    +        bad_scale = [-2]
    +        desired = np.array([1.5221370731769048,
    +                            1.5277256455738331,
    +                            1.4248762625178359])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.gamma(shape * 3, scale)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.gamma, bad_shape * 3, scale)
    +        assert_raises(ValueError, rng.gamma, shape * 3, bad_scale)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.gamma(shape, scale * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.gamma, bad_shape, scale * 3)
    +        assert_raises(ValueError, rng.gamma, shape, bad_scale * 3)
    +
    +    def test_f(self):
    +        dfnum = [1]
    +        dfden = [2]
    +        bad_dfnum = [-1]
    +        bad_dfden = [-2]
    +        desired = np.array([0.80038951638264799,
    +                            0.86768719635363512,
    +                            2.7251095168386801])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.f(dfnum * 3, dfden)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.f, bad_dfnum * 3, dfden)
    +        assert_raises(ValueError, rng.f, dfnum * 3, bad_dfden)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.f(dfnum, dfden * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.f, bad_dfnum, dfden * 3)
    +        assert_raises(ValueError, rng.f, dfnum, bad_dfden * 3)
    +
    +    def test_noncentral_f(self):
    +        dfnum = [2]
    +        dfden = [3]
    +        nonc = [4]
    +        bad_dfnum = [0]
    +        bad_dfden = [-1]
    +        bad_nonc = [-2]
    +        desired = np.array([9.1393943263705211,
    +                            13.025456344595602,
    +                            8.8018098359100545])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.noncentral_f(dfnum * 3, dfden, nonc)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert np.all(np.isnan(rng.noncentral_f(dfnum, dfden, [np.nan] * 3)))
    +
    +        assert_raises(ValueError, rng.noncentral_f, bad_dfnum * 3, dfden, nonc)
    +        assert_raises(ValueError, rng.noncentral_f, dfnum * 3, bad_dfden, nonc)
    +        assert_raises(ValueError, rng.noncentral_f, dfnum * 3, dfden, bad_nonc)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.noncentral_f(dfnum, dfden * 3, nonc)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.noncentral_f, bad_dfnum, dfden * 3, nonc)
    +        assert_raises(ValueError, rng.noncentral_f, dfnum, bad_dfden * 3, nonc)
    +        assert_raises(ValueError, rng.noncentral_f, dfnum, dfden * 3, bad_nonc)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.noncentral_f(dfnum, dfden, nonc * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.noncentral_f, bad_dfnum, dfden, nonc * 3)
    +        assert_raises(ValueError, rng.noncentral_f, dfnum, bad_dfden, nonc * 3)
    +        assert_raises(ValueError, rng.noncentral_f, dfnum, dfden, bad_nonc * 3)
    +
    +    def test_noncentral_f_small_df(self):
    +        rng = random.RandomState(self.seed)
    +        desired = np.array([6.869638627492048, 0.785880199263955])
    +        actual = rng.noncentral_f(0.9, 0.9, 2, size=2)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +
    +    def test_chisquare(self):
    +        df = [1]
    +        bad_df = [-1]
    +        desired = np.array([0.57022801133088286,
    +                            0.51947702108840776,
    +                            0.1320969254923558])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.chisquare(df * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.chisquare, bad_df * 3)
    +
    +    def test_noncentral_chisquare(self):
    +        df = [1]
    +        nonc = [2]
    +        bad_df = [-1]
    +        bad_nonc = [-2]
    +        desired = np.array([9.0015599467913763,
    +                            4.5804135049718742,
    +                            6.0872302432834564])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.noncentral_chisquare(df * 3, nonc)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.noncentral_chisquare, bad_df * 3, nonc)
    +        assert_raises(ValueError, rng.noncentral_chisquare, df * 3, bad_nonc)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.noncentral_chisquare(df, nonc * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.noncentral_chisquare, bad_df, nonc * 3)
    +        assert_raises(ValueError, rng.noncentral_chisquare, df, bad_nonc * 3)
    +
    +    def test_standard_t(self):
    +        df = [1]
    +        bad_df = [-1]
    +        desired = np.array([3.0702872575217643,
    +                            5.8560725167361607,
    +                            1.0274791436474273])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.standard_t(df * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.standard_t, bad_df * 3)
    +        assert_raises(ValueError, random.standard_t, bad_df * 3)
    +
    +    def test_vonmises(self):
    +        mu = [2]
    +        kappa = [1]
    +        bad_kappa = [-1]
    +        desired = np.array([2.9883443664201312,
    +                            -2.7064099483995943,
    +                            -1.8672476700665914])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.vonmises(mu * 3, kappa)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.vonmises, mu * 3, bad_kappa)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.vonmises(mu, kappa * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.vonmises, mu, bad_kappa * 3)
    +
    +    def test_pareto(self):
    +        a = [1]
    +        bad_a = [-1]
    +        desired = np.array([1.1405622680198362,
    +                            1.1465519762044529,
    +                            1.0389564467453547])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.pareto(a * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.pareto, bad_a * 3)
    +        assert_raises(ValueError, random.pareto, bad_a * 3)
    +
    +    def test_weibull(self):
    +        a = [1]
    +        bad_a = [-1]
    +        desired = np.array([0.76106853658845242,
    +                            0.76386282278691653,
    +                            0.71243813125891797])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.weibull(a * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.weibull, bad_a * 3)
    +        assert_raises(ValueError, random.weibull, bad_a * 3)
    +
    +    def test_power(self):
    +        a = [1]
    +        bad_a = [-1]
    +        desired = np.array([0.53283302478975902,
    +                            0.53413660089041659,
    +                            0.50955303552646702])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.power(a * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.power, bad_a * 3)
    +        assert_raises(ValueError, random.power, bad_a * 3)
    +
    +    def test_laplace(self):
    +        loc = [0]
    +        scale = [1]
    +        bad_scale = [-1]
    +        desired = np.array([0.067921356028507157,
    +                            0.070715642226971326,
    +                            0.019290950698972624])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.laplace(loc * 3, scale)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.laplace, loc * 3, bad_scale)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.laplace(loc, scale * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.laplace, loc, bad_scale * 3)
    +
    +    def test_gumbel(self):
    +        loc = [0]
    +        scale = [1]
    +        bad_scale = [-1]
    +        desired = np.array([0.2730318639556768,
    +                            0.26936705726291116,
    +                            0.33906220393037939])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.gumbel(loc * 3, scale)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.gumbel, loc * 3, bad_scale)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.gumbel(loc, scale * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.gumbel, loc, bad_scale * 3)
    +
    +    def test_logistic(self):
    +        loc = [0]
    +        scale = [1]
    +        bad_scale = [-1]
    +        desired = np.array([0.13152135837586171,
    +                            0.13675915696285773,
    +                            0.038216792802833396])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.logistic(loc * 3, scale)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.logistic, loc * 3, bad_scale)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.logistic(loc, scale * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.logistic, loc, bad_scale * 3)
    +        assert_equal(rng.logistic(1.0, 0.0), 1.0)
    +
    +    def test_lognormal(self):
    +        mean = [0]
    +        sigma = [1]
    +        bad_sigma = [-1]
    +        desired = np.array([9.1422086044848427,
    +                            8.4013952870126261,
    +                            6.3073234116578671])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.lognormal(mean * 3, sigma)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.lognormal, mean * 3, bad_sigma)
    +        assert_raises(ValueError, random.lognormal, mean * 3, bad_sigma)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.lognormal(mean, sigma * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.lognormal, mean, bad_sigma * 3)
    +        assert_raises(ValueError, random.lognormal, mean, bad_sigma * 3)
    +
    +    def test_rayleigh(self):
    +        scale = [1]
    +        bad_scale = [-1]
    +        desired = np.array([1.2337491937897689,
    +                            1.2360119924878694,
    +                            1.1936818095781789])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.rayleigh(scale * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.rayleigh, bad_scale * 3)
    +
    +    def test_wald(self):
    +        mean = [0.5]
    +        scale = [1]
    +        bad_mean = [0]
    +        bad_scale = [-2]
    +        desired = np.array([0.11873681120271318,
    +                            0.12450084820795027,
    +                            0.9096122728408238])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.wald(mean * 3, scale)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.wald, bad_mean * 3, scale)
    +        assert_raises(ValueError, rng.wald, mean * 3, bad_scale)
    +        assert_raises(ValueError, random.wald, bad_mean * 3, scale)
    +        assert_raises(ValueError, random.wald, mean * 3, bad_scale)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.wald(mean, scale * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.wald, bad_mean, scale * 3)
    +        assert_raises(ValueError, rng.wald, mean, bad_scale * 3)
    +        assert_raises(ValueError, rng.wald, 0.0, 1)
    +        assert_raises(ValueError, rng.wald, 0.5, 0.0)
    +
    +    def test_triangular(self):
    +        left = [1]
    +        right = [3]
    +        mode = [2]
    +        bad_left_one = [3]
    +        bad_mode_one = [4]
    +        bad_left_two, bad_mode_two = right * 2
    +        desired = np.array([2.03339048710429,
    +                            2.0347400359389356,
    +                            2.0095991069536208])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.triangular(left * 3, mode, right)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.triangular, bad_left_one * 3, mode, right)
    +        assert_raises(ValueError, rng.triangular, left * 3, bad_mode_one, right)
    +        assert_raises(ValueError, rng.triangular, bad_left_two * 3, bad_mode_two,
    +                      right)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.triangular(left, mode * 3, right)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.triangular, bad_left_one, mode * 3, right)
    +        assert_raises(ValueError, rng.triangular, left, bad_mode_one * 3, right)
    +        assert_raises(ValueError, rng.triangular, bad_left_two, bad_mode_two * 3,
    +                      right)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.triangular(left, mode, right * 3)
    +        assert_array_almost_equal(actual, desired, decimal=14)
    +        assert_raises(ValueError, rng.triangular, bad_left_one, mode, right * 3)
    +        assert_raises(ValueError, rng.triangular, left, bad_mode_one, right * 3)
    +        assert_raises(ValueError, rng.triangular, bad_left_two, bad_mode_two,
    +                      right * 3)
    +
    +        assert_raises(ValueError, rng.triangular, 10., 0., 20.)
    +        assert_raises(ValueError, rng.triangular, 10., 25., 20.)
    +        assert_raises(ValueError, rng.triangular, 10., 10., 10.)
    +
    +    def test_binomial(self):
    +        n = [1]
    +        p = [0.5]
    +        bad_n = [-1]
    +        bad_p_one = [-1]
    +        bad_p_two = [1.5]
    +        desired = np.array([1, 1, 1])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.binomial(n * 3, p)
    +        assert_array_equal(actual, desired)
    +        assert_raises(ValueError, rng.binomial, bad_n * 3, p)
    +        assert_raises(ValueError, rng.binomial, n * 3, bad_p_one)
    +        assert_raises(ValueError, rng.binomial, n * 3, bad_p_two)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.binomial(n, p * 3)
    +        assert_array_equal(actual, desired)
    +        assert_raises(ValueError, rng.binomial, bad_n, p * 3)
    +        assert_raises(ValueError, rng.binomial, n, bad_p_one * 3)
    +        assert_raises(ValueError, rng.binomial, n, bad_p_two * 3)
    +
    +    def test_negative_binomial(self):
    +        n = [1]
    +        p = [0.5]
    +        bad_n = [-1]
    +        bad_p_one = [-1]
    +        bad_p_two = [1.5]
    +        desired = np.array([1, 0, 1])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.negative_binomial(n * 3, p)
    +        assert_array_equal(actual, desired)
    +        assert_raises(ValueError, rng.negative_binomial, bad_n * 3, p)
    +        assert_raises(ValueError, rng.negative_binomial, n * 3, bad_p_one)
    +        assert_raises(ValueError, rng.negative_binomial, n * 3, bad_p_two)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.negative_binomial(n, p * 3)
    +        assert_array_equal(actual, desired)
    +        assert_raises(ValueError, rng.negative_binomial, bad_n, p * 3)
    +        assert_raises(ValueError, rng.negative_binomial, n, bad_p_one * 3)
    +        assert_raises(ValueError, rng.negative_binomial, n, bad_p_two * 3)
    +
    +    def test_poisson(self):
    +        max_lam = random.RandomState()._poisson_lam_max
    +
    +        lam = [1]
    +        bad_lam_one = [-1]
    +        bad_lam_two = [max_lam * 2]
    +        desired = np.array([1, 1, 0])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.poisson(lam * 3)
    +        assert_array_equal(actual, desired)
    +        assert_raises(ValueError, rng.poisson, bad_lam_one * 3)
    +        assert_raises(ValueError, rng.poisson, bad_lam_two * 3)
    +
    +    def test_zipf(self):
    +        a = [2]
    +        bad_a = [0]
    +        desired = np.array([2, 2, 1])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.zipf(a * 3)
    +        assert_array_equal(actual, desired)
    +        assert_raises(ValueError, rng.zipf, bad_a * 3)
    +        with np.errstate(invalid='ignore'):
    +            assert_raises(ValueError, rng.zipf, np.nan)
    +            assert_raises(ValueError, rng.zipf, [0, 0, np.nan])
    +
    +    def test_geometric(self):
    +        p = [0.5]
    +        bad_p_one = [-1]
    +        bad_p_two = [1.5]
    +        desired = np.array([2, 2, 2])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.geometric(p * 3)
    +        assert_array_equal(actual, desired)
    +        assert_raises(ValueError, rng.geometric, bad_p_one * 3)
    +        assert_raises(ValueError, rng.geometric, bad_p_two * 3)
    +
    +    def test_hypergeometric(self):
    +        ngood = [1]
    +        nbad = [2]
    +        nsample = [2]
    +        bad_ngood = [-1]
    +        bad_nbad = [-2]
    +        bad_nsample_one = [0]
    +        bad_nsample_two = [4]
    +        desired = np.array([1, 1, 1])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.hypergeometric(ngood * 3, nbad, nsample)
    +        assert_array_equal(actual, desired)
    +        assert_raises(ValueError, rng.hypergeometric, bad_ngood * 3, nbad, nsample)
    +        assert_raises(ValueError, rng.hypergeometric, ngood * 3, bad_nbad, nsample)
    +        assert_raises(ValueError, rng.hypergeometric, ngood * 3, nbad, bad_nsample_one)
    +        assert_raises(ValueError, rng.hypergeometric, ngood * 3, nbad, bad_nsample_two)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.hypergeometric(ngood, nbad * 3, nsample)
    +        assert_array_equal(actual, desired)
    +        assert_raises(ValueError, rng.hypergeometric, bad_ngood, nbad * 3, nsample)
    +        assert_raises(ValueError, rng.hypergeometric, ngood, bad_nbad * 3, nsample)
    +        assert_raises(ValueError, rng.hypergeometric, ngood, nbad * 3, bad_nsample_one)
    +        assert_raises(ValueError, rng.hypergeometric, ngood, nbad * 3, bad_nsample_two)
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.hypergeometric(ngood, nbad, nsample * 3)
    +        assert_array_equal(actual, desired)
    +        assert_raises(ValueError, rng.hypergeometric, bad_ngood, nbad, nsample * 3)
    +        assert_raises(ValueError, rng.hypergeometric, ngood, bad_nbad, nsample * 3)
    +        assert_raises(ValueError, rng.hypergeometric, ngood, nbad, bad_nsample_one * 3)
    +        assert_raises(ValueError, rng.hypergeometric, ngood, nbad, bad_nsample_two * 3)
    +
    +        assert_raises(ValueError, rng.hypergeometric, -1, 10, 20)
    +        assert_raises(ValueError, rng.hypergeometric, 10, -1, 20)
    +        assert_raises(ValueError, rng.hypergeometric, 10, 10, 0)
    +        assert_raises(ValueError, rng.hypergeometric, 10, 10, 25)
    +
    +    def test_logseries(self):
    +        p = [0.5]
    +        bad_p_one = [2]
    +        bad_p_two = [-1]
    +        desired = np.array([1, 1, 1])
    +
    +        rng = random.RandomState(self.seed)
    +        actual = rng.logseries(p * 3)
    +        assert_array_equal(actual, desired)
    +        assert_raises(ValueError, rng.logseries, bad_p_one * 3)
    +        assert_raises(ValueError, rng.logseries, bad_p_two * 3)
    +
    +
    +@pytest.mark.skipif(IS_WASM, reason="can't start thread")
    +class TestThread:
    +    # make sure each state produces the same sequence even in threads
    +    seeds = range(4)
    +
    +    def check_function(self, function, sz):
    +        from threading import Thread
    +
    +        out1 = np.empty((len(self.seeds),) + sz)
    +        out2 = np.empty((len(self.seeds),) + sz)
    +
    +        # threaded generation
    +        t = [Thread(target=function, args=(random.RandomState(s), o))
    +             for s, o in zip(self.seeds, out1)]
    +        [x.start() for x in t]
    +        [x.join() for x in t]
    +
    +        # the same serial
    +        for s, o in zip(self.seeds, out2):
    +            function(random.RandomState(s), o)
    +
    +        # these platforms change x87 fpu precision mode in threads
    +        if np.intp().dtype.itemsize == 4 and sys.platform == "win32":
    +            assert_array_almost_equal(out1, out2)
    +        else:
    +            assert_array_equal(out1, out2)
    +
    +    def test_normal(self):
    +        def gen_random(state, out):
    +            out[...] = state.normal(size=10000)
    +
    +        self.check_function(gen_random, sz=(10000,))
    +
    +    def test_exp(self):
    +        def gen_random(state, out):
    +            out[...] = state.exponential(scale=np.ones((100, 1000)))
    +
    +        self.check_function(gen_random, sz=(100, 1000))
    +
    +    def test_multinomial(self):
    +        def gen_random(state, out):
    +            out[...] = state.multinomial(10, [1 / 6.] * 6, size=10000)
    +
    +        self.check_function(gen_random, sz=(10000, 6))
    +
    +
    +# See Issue #4263
    +class TestSingleEltArrayInput:
    +    def _create_arrays(self):
    +        return np.array([2]), np.array([3]), np.array([4]), (1,)
    +
    +    def test_one_arg_funcs(self):
    +        argOne, _, _, tgtShape = self._create_arrays()
    +        funcs = (random.exponential, random.standard_gamma,
    +                 random.chisquare, random.standard_t,
    +                 random.pareto, random.weibull,
    +                 random.power, random.rayleigh,
    +                 random.poisson, random.zipf,
    +                 random.geometric, random.logseries)
    +
    +        probfuncs = (random.geometric, random.logseries)
    +
    +        for func in funcs:
    +            if func in probfuncs:  # p < 1.0
    +                out = func(np.array([0.5]))
    +
    +            else:
    +                out = func(argOne)
    +
    +            assert_equal(out.shape, tgtShape)
    +
    +    def test_two_arg_funcs(self):
    +        argOne, argTwo, _, tgtShape = self._create_arrays()
    +        funcs = (random.uniform, random.normal,
    +                 random.beta, random.gamma,
    +                 random.f, random.noncentral_chisquare,
    +                 random.vonmises, random.laplace,
    +                 random.gumbel, random.logistic,
    +                 random.lognormal, random.wald,
    +                 random.binomial, random.negative_binomial)
    +
    +        probfuncs = (random.binomial, random.negative_binomial)
    +
    +        for func in funcs:
    +            if func in probfuncs:  # p <= 1
    +                argTwo = np.array([0.5])
    +
    +            else:
    +                argTwo = argTwo
    +
    +            out = func(argOne, argTwo)
    +            assert_equal(out.shape, tgtShape)
    +
    +            out = func(argOne[0], argTwo)
    +            assert_equal(out.shape, tgtShape)
    +
    +            out = func(argOne, argTwo[0])
    +            assert_equal(out.shape, tgtShape)
    +
    +    def test_three_arg_funcs(self):
    +        argOne, argTwo, argThree, tgtShape = self._create_arrays()
    +        funcs = [random.noncentral_f, random.triangular,
    +                 random.hypergeometric]
    +
    +        for func in funcs:
    +            out = func(argOne, argTwo, argThree)
    +            assert_equal(out.shape, tgtShape)
    +
    +            out = func(argOne[0], argTwo, argThree)
    +            assert_equal(out.shape, tgtShape)
    +
    +            out = func(argOne, argTwo[0], argThree)
    +            assert_equal(out.shape, tgtShape)
    +
    +
    +# Ensure returned array dtype is correct for platform
    +def test_integer_dtype(int_func):
    +    random.seed(123456789)
    +    fname, args, sha256 = int_func
    +    f = getattr(random, fname)
    +    actual = f(*args, size=2)
    +    assert_(actual.dtype == np.dtype('l'))
    +
    +
    +def test_integer_repeat(int_func):
    +    rng = random.RandomState(123456789)
    +    fname, args, sha256 = int_func
    +    f = getattr(rng, fname)
    +    val = f(*args, size=1000000)
    +    if sys.byteorder != 'little':
    +        val = val.byteswap()
    +    res = hashlib.sha256(val.view(np.int8)).hexdigest()
    +    assert_(res == sha256)
    +
    +
    +def test_broadcast_size_error():
    +    # GH-16833
    +    with pytest.raises(ValueError):
    +        random.binomial(1, [0.3, 0.7], size=(2, 1))
    +    with pytest.raises(ValueError):
    +        random.binomial([1, 2], 0.3, size=(2, 1))
    +    with pytest.raises(ValueError):
    +        random.binomial([1, 2], [0.3, 0.7], size=(2, 1))
    +
    +
    +def test_randomstate_ctor_old_style_pickle():
    +    rs = np.random.RandomState(MT19937(0))
    +    rs.standard_normal(1)
    +    # Directly call reduce which is used in pickling
    +    ctor, args, state_a = rs.__reduce__()
    +    # Simulate unpickling an old pickle that only has the name
    +    assert args[0].__class__.__name__ == "MT19937"
    +    b = ctor(*("MT19937",))
    +    b.set_state(state_a)
    +    state_b = b.get_state(legacy=False)
    +
    +    assert_equal(state_a['bit_generator'], state_b['bit_generator'])
    +    assert_array_equal(state_a['state']['key'], state_b['state']['key'])
    +    assert_array_equal(state_a['state']['pos'], state_b['state']['pos'])
    +    assert_equal(state_a['has_gauss'], state_b['has_gauss'])
    +    assert_equal(state_a['gauss'], state_b['gauss'])
    +
    +
    +@pytest.mark.thread_unsafe(reason="np.random.set_bit_generator affects global state")
    +def test_hot_swap(restore_singleton_bitgen):
    +    # GH 21808
    +    def_bg = np.random.default_rng(0)
    +    bg = def_bg.bit_generator
    +    np.random.set_bit_generator(bg)
    +    assert isinstance(np.random.mtrand._rand._bit_generator, type(bg))
    +
    +    second_bg = np.random.get_bit_generator()
    +    assert bg is second_bg
    +
    +
    +@pytest.mark.thread_unsafe(reason="np.random.set_bit_generator affects global state")
    +def test_seed_alt_bit_gen(restore_singleton_bitgen):
    +    # GH 21808
    +    bg = PCG64(0)
    +    np.random.set_bit_generator(bg)
    +    state = np.random.get_state(legacy=False)
    +    np.random.seed(1)
    +    new_state = np.random.get_state(legacy=False)
    +    print(state)
    +    print(new_state)
    +    assert state["bit_generator"] == "PCG64"
    +    assert state["state"]["state"] != new_state["state"]["state"]
    +    assert state["state"]["inc"] != new_state["state"]["inc"]
    +
    +
    +@pytest.mark.thread_unsafe(reason="np.random.set_bit_generator affects global state")
    +def test_state_error_alt_bit_gen(restore_singleton_bitgen):
    +    # GH 21808
    +    state = np.random.get_state()
    +    bg = PCG64(0)
    +    np.random.set_bit_generator(bg)
    +    with pytest.raises(ValueError, match="state must be for a PCG64"):
    +        np.random.set_state(state)
    +
    +
    +@pytest.mark.thread_unsafe(reason="np.random.set_bit_generator affects global state")
    +def test_swap_worked(restore_singleton_bitgen):
    +    # GH 21808
    +    np.random.seed(98765)
    +    vals = np.random.randint(0, 2 ** 30, 10)
    +    bg = PCG64(0)
    +    state = bg.state
    +    np.random.set_bit_generator(bg)
    +    state_direct = np.random.get_state(legacy=False)
    +    for field in state:
    +        assert state[field] == state_direct[field]
    +    np.random.seed(98765)
    +    pcg_vals = np.random.randint(0, 2 ** 30, 10)
    +    assert not np.all(vals == pcg_vals)
    +    new_state = bg.state
    +    assert new_state["state"]["state"] != state["state"]["state"]
    +    assert new_state["state"]["inc"] == new_state["state"]["inc"]
    +
    +
    +@pytest.mark.thread_unsafe(reason="np.random.set_bit_generator affects global state")
    +def test_swapped_singleton_against_direct(restore_singleton_bitgen):
    +    np.random.set_bit_generator(PCG64(98765))
    +    singleton_vals = np.random.randint(0, 2 ** 30, 10)
    +    rg = np.random.RandomState(PCG64(98765))
    +    non_singleton_vals = rg.randint(0, 2 ** 30, 10)
    +    assert_equal(non_singleton_vals, singleton_vals)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/test_randomstate_regression.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/test_randomstate_regression.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..1c8882d1b67268dfe00bd56c302f96c68d78b192
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/random/tests/test_randomstate_regression.py
    @@ -0,0 +1,213 @@
    +import sys
    +
    +import pytest
    +
    +import numpy as np
    +from numpy import random
    +from numpy.testing import assert_, assert_array_equal, assert_raises
    +
    +
    +class TestRegression:
    +
    +    def test_VonMises_range(self):
    +        # Make sure generated random variables are in [-pi, pi].
    +        # Regression test for ticket #986.
    +        for mu in np.linspace(-7., 7., 5):
    +            r = random.vonmises(mu, 1, 50)
    +            assert_(np.all(r > -np.pi) and np.all(r <= np.pi))
    +
    +    def test_hypergeometric_range(self):
    +        # Test for ticket #921
    +        assert_(np.all(random.hypergeometric(3, 18, 11, size=10) < 4))
    +        assert_(np.all(random.hypergeometric(18, 3, 11, size=10) > 0))
    +
    +        # Test for ticket #5623
    +        args = [
    +            (2**20 - 2, 2**20 - 2, 2**20 - 2),  # Check for 32-bit systems
    +        ]
    +        is_64bits = sys.maxsize > 2**32
    +        if is_64bits and sys.platform != 'win32':
    +            # Check for 64-bit systems
    +            args.append((2**40 - 2, 2**40 - 2, 2**40 - 2))
    +        for arg in args:
    +            assert_(random.hypergeometric(*arg) > 0)
    +
    +    def test_logseries_convergence(self):
    +        # Test for ticket #923
    +        N = 1000
    +        random.seed(0)
    +        rvsn = random.logseries(0.8, size=N)
    +        # these two frequency counts should be close to theoretical
    +        # numbers with this large sample
    +        # theoretical large N result is 0.49706795
    +        freq = np.sum(rvsn == 1) / N
    +        msg = f'Frequency was {freq:f}, should be > 0.45'
    +        assert_(freq > 0.45, msg)
    +        # theoretical large N result is 0.19882718
    +        freq = np.sum(rvsn == 2) / N
    +        msg = f'Frequency was {freq:f}, should be < 0.23'
    +        assert_(freq < 0.23, msg)
    +
    +    def test_shuffle_mixed_dimension(self):
    +        # Test for trac ticket #2074
    +        for t in [[1, 2, 3, None],
    +                  [(1, 1), (2, 2), (3, 3), None],
    +                  [1, (2, 2), (3, 3), None],
    +                  [(1, 1), 2, 3, None]]:
    +            rng = random.RandomState(12345)
    +            shuffled = list(t)
    +            rng.shuffle(shuffled)
    +            expected = np.array([t[0], t[3], t[1], t[2]], dtype=object)
    +            assert_array_equal(np.array(shuffled, dtype=object), expected)
    +
    +    def test_call_within_randomstate(self):
    +        # Check that custom RandomState does not call into global state
    +        m = random.RandomState()
    +        res = np.array([0, 8, 7, 2, 1, 9, 4, 7, 0, 3])
    +        for i in range(3):
    +            random.seed(i)
    +            m.seed(4321)
    +            # If m.state is not honored, the result will change
    +            assert_array_equal(m.choice(10, size=10, p=np.ones(10) / 10.), res)
    +
    +    def test_multivariate_normal_size_types(self):
    +        # Test for multivariate_normal issue with 'size' argument.
    +        # Check that the multivariate_normal size argument can be a
    +        # numpy integer.
    +        random.multivariate_normal([0], [[0]], size=1)
    +        random.multivariate_normal([0], [[0]], size=np.int_(1))
    +        random.multivariate_normal([0], [[0]], size=np.int64(1))
    +
    +    def test_beta_small_parameters(self):
    +        # Test that beta with small a and b parameters does not produce
    +        # NaNs due to roundoff errors causing 0 / 0, gh-5851
    +        random.seed(1234567890)
    +        x = random.beta(0.0001, 0.0001, size=100)
    +        assert_(not np.any(np.isnan(x)), 'Nans in random.beta')
    +
    +    def test_choice_sum_of_probs_tolerance(self):
    +        # The sum of probs should be 1.0 with some tolerance.
    +        # For low precision dtypes the tolerance was too tight.
    +        # See numpy github issue 6123.
    +        random.seed(1234)
    +        a = [1, 2, 3]
    +        counts = [4, 4, 2]
    +        for dt in np.float16, np.float32, np.float64:
    +            probs = np.array(counts, dtype=dt) / sum(counts)
    +            c = random.choice(a, p=probs)
    +            assert_(c in a)
    +            assert_raises(ValueError, random.choice, a, p=probs * 0.9)
    +
    +    def test_shuffle_of_array_of_different_length_strings(self):
    +        # Test that permuting an array of different length strings
    +        # will not cause a segfault on garbage collection
    +        # Tests gh-7710
    +        random.seed(1234)
    +
    +        a = np.array(['a', 'a' * 1000])
    +
    +        for _ in range(100):
    +            random.shuffle(a)
    +
    +        # Force Garbage Collection - should not segfault.
    +        import gc
    +        gc.collect()
    +
    +    def test_shuffle_of_array_of_objects(self):
    +        # Test that permuting an array of objects will not cause
    +        # a segfault on garbage collection.
    +        # See gh-7719
    +        random.seed(1234)
    +        a = np.array([np.arange(1), np.arange(4)], dtype=object)
    +
    +        for _ in range(1000):
    +            random.shuffle(a)
    +
    +        # Force Garbage Collection - should not segfault.
    +        import gc
    +        gc.collect()
    +
    +    def test_permutation_subclass(self):
    +        class N(np.ndarray):
    +            pass
    +
    +        rng = random.RandomState(1)
    +        orig = np.arange(3).view(N)
    +        perm = rng.permutation(orig)
    +        assert_array_equal(perm, np.array([0, 2, 1]))
    +        assert_array_equal(orig, np.arange(3).view(N))
    +
    +        class M:
    +            a = np.arange(5)
    +
    +            def __array__(self, dtype=None, copy=None):
    +                return self.a
    +
    +        rng = random.RandomState(1)
    +        m = M()
    +        perm = rng.permutation(m)
    +        assert_array_equal(perm, np.array([2, 1, 4, 0, 3]))
    +        assert_array_equal(m.__array__(), np.arange(5))
    +
    +    def test_warns_byteorder(self):
    +        # GH 13159
    +        other_byteord_dt = 'i4'
    +        with pytest.deprecated_call(match='non-native byteorder is not'):
    +            random.randint(0, 200, size=10, dtype=other_byteord_dt)
    +
    +    def test_named_argument_initialization(self):
    +        # GH 13669
    +        rs1 = np.random.RandomState(123456789)
    +        rs2 = np.random.RandomState(seed=123456789)
    +        assert rs1.randint(0, 100) == rs2.randint(0, 100)
    +
    +    def test_choice_retun_dtype(self):
    +        # GH 9867, now long since the NumPy default changed.
    +        c = np.random.choice(10, p=[.1] * 10, size=2)
    +        assert c.dtype == np.dtype(np.long)
    +        c = np.random.choice(10, p=[.1] * 10, replace=False, size=2)
    +        assert c.dtype == np.dtype(np.long)
    +        c = np.random.choice(10, size=2)
    +        assert c.dtype == np.dtype(np.long)
    +        c = np.random.choice(10, replace=False, size=2)
    +        assert c.dtype == np.dtype(np.long)
    +
    +    @pytest.mark.skipif(np.iinfo('l').max < 2**32,
    +                        reason='Cannot test with 32-bit C long')
    +    def test_randint_117(self):
    +        # GH 14189
    +        rng = random.RandomState(0)
    +        expected = np.array([2357136044, 2546248239, 3071714933, 3626093760,
    +                             2588848963, 3684848379, 2340255427, 3638918503,
    +                             1819583497, 2678185683], dtype='int64')
    +        actual = rng.randint(2**32, size=10)
    +        assert_array_equal(actual, expected)
    +
    +    def test_p_zero_stream(self):
    +        # Regression test for gh-14522.  Ensure that future versions
    +        # generate the same variates as version 1.16.
    +        rng = random.RandomState(12345)
    +        assert_array_equal(rng.binomial(1, [0, 0.25, 0.5, 0.75, 1]),
    +                           [0, 0, 0, 1, 1])
    +
    +    def test_n_zero_stream(self):
    +        # Regression test for gh-14522.  Ensure that future versions
    +        # generate the same variates as version 1.16.
    +        rng = random.RandomState(8675309)
    +        expected = np.array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
    +                             [3, 4, 2, 3, 3, 1, 5, 3, 1, 3]])
    +        assert_array_equal(rng.binomial([[0], [10]], 0.25, size=(2, 10)),
    +                           expected)
    +
    +
    +def test_multinomial_empty():
    +    # gh-20483
    +    # Ensure that empty p-vals are correctly handled
    +    assert random.multinomial(10, []).shape == (0,)
    +    assert random.multinomial(3, [], size=(7, 5, 3)).shape == (7, 5, 3, 0)
    +
    +
    +def test_multinomial_1d_pval():
    +    # gh-20483
    +    with pytest.raises(TypeError, match="pvals must be a 1-d"):
    +        random.multinomial(10, 0.3)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/strings/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/strings/__init__.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..561dadcf37d090ce201f9b9bec2dbb6e86a3dc06
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/strings/__init__.py
    @@ -0,0 +1,2 @@
    +from numpy._core.strings import *
    +from numpy._core.strings import __all__, __doc__
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/strings/__init__.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/strings/__init__.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..b2fb363531d4eba972ba9866a08fb6f047ec7eb1
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/strings/__init__.pyi
    @@ -0,0 +1,97 @@
    +from numpy._core.strings import (
    +    add,
    +    capitalize,
    +    center,
    +    count,
    +    decode,
    +    encode,
    +    endswith,
    +    equal,
    +    expandtabs,
    +    find,
    +    greater,
    +    greater_equal,
    +    index,
    +    isalnum,
    +    isalpha,
    +    isdecimal,
    +    isdigit,
    +    islower,
    +    isnumeric,
    +    isspace,
    +    istitle,
    +    isupper,
    +    less,
    +    less_equal,
    +    ljust,
    +    lower,
    +    lstrip,
    +    mod,
    +    multiply,
    +    not_equal,
    +    partition,
    +    replace,
    +    rfind,
    +    rindex,
    +    rjust,
    +    rpartition,
    +    rstrip,
    +    slice,
    +    startswith,
    +    str_len,
    +    strip,
    +    swapcase,
    +    title,
    +    translate,
    +    upper,
    +    zfill,
    +)
    +
    +__all__ = [
    +    "equal",
    +    "not_equal",
    +    "less",
    +    "less_equal",
    +    "greater",
    +    "greater_equal",
    +    "add",
    +    "multiply",
    +    "isalpha",
    +    "isdigit",
    +    "isspace",
    +    "isalnum",
    +    "islower",
    +    "isupper",
    +    "istitle",
    +    "isdecimal",
    +    "isnumeric",
    +    "str_len",
    +    "find",
    +    "rfind",
    +    "index",
    +    "rindex",
    +    "count",
    +    "startswith",
    +    "endswith",
    +    "lstrip",
    +    "rstrip",
    +    "strip",
    +    "replace",
    +    "expandtabs",
    +    "center",
    +    "ljust",
    +    "rjust",
    +    "zfill",
    +    "partition",
    +    "rpartition",
    +    "upper",
    +    "lower",
    +    "swapcase",
    +    "capitalize",
    +    "title",
    +    "mod",
    +    "decode",
    +    "encode",
    +    "translate",
    +    "slice",
    +]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/__init__.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..ef4c0885257b296570ba275f95d4b453a6aff127
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/__init__.py
    @@ -0,0 +1,233 @@
    +"""
    +============================
    +Typing (:mod:`numpy.typing`)
    +============================
    +
    +.. versionadded:: 1.20
    +
    +Large parts of the NumPy API have :pep:`484`-style type annotations. In
    +addition a number of type aliases are available to users, most prominently
    +the two below:
    +
    +- `ArrayLike`: objects that can be converted to arrays
    +- `DTypeLike`: objects that can be converted to dtypes
    +
    +.. _typing-extensions: https://pypi.org/project/typing-extensions/
    +
    +Mypy plugin
    +-----------
    +
    +.. versionadded:: 1.21
    +
    +.. automodule:: numpy.typing.mypy_plugin
    +
    +.. currentmodule:: numpy.typing
    +
    +Differences from the runtime NumPy API
    +--------------------------------------
    +
    +NumPy is very flexible. Trying to describe the full range of
    +possibilities statically would result in types that are not very
    +helpful. For that reason, the typed NumPy API is often stricter than
    +the runtime NumPy API. This section describes some notable
    +differences.
    +
    +ArrayLike
    +~~~~~~~~~
    +
    +The `ArrayLike` type tries to avoid creating object arrays. For
    +example,
    +
    +.. code-block:: python
    +
    +    >>> np.array(x**2 for x in range(10))
    +    array( at ...>, dtype=object)
    +
    +is valid NumPy code which will create a 0-dimensional object
    +array. Type checkers will complain about the above example when using
    +the NumPy types however. If you really intended to do the above, then
    +you can either use a ``# type: ignore`` comment:
    +
    +.. code-block:: python
    +
    +    >>> np.array(x**2 for x in range(10))  # type: ignore
    +
    +or explicitly type the array like object as `~typing.Any`:
    +
    +.. code-block:: python
    +
    +    >>> from typing import Any
    +    >>> array_like: Any = (x**2 for x in range(10))
    +    >>> np.array(array_like)
    +    array( at ...>, dtype=object)
    +
    +ndarray
    +~~~~~~~
    +
    +It's possible to mutate the dtype of an array at runtime. For example,
    +the following code is valid:
    +
    +.. code-block:: python
    +
    +    >>> x = np.array([1, 2])
    +    >>> x.dtype = np.bool
    +
    +This sort of mutation is not allowed by the types. Users who want to
    +write statically typed code should instead use the `numpy.ndarray.view`
    +method to create a view of the array with a different dtype.
    +
    +DTypeLike
    +~~~~~~~~~
    +
    +The `DTypeLike` type tries to avoid creation of dtype objects using
    +dictionary of fields like below:
    +
    +.. code-block:: python
    +
    +    >>> x = np.dtype({"field1": (float, 1), "field2": (int, 3)})
    +
    +Although this is valid NumPy code, the type checker will complain about it,
    +since its usage is discouraged.
    +Please see : :ref:`Data type objects `
    +
    +Number precision
    +~~~~~~~~~~~~~~~~
    +
    +The precision of `numpy.number` subclasses is treated as a invariant generic
    +parameter (see :class:`~NBitBase`), simplifying the annotating of processes
    +involving precision-based casting.
    +
    +.. code-block:: python
    +
    +    >>> from typing import TypeVar
    +    >>> import numpy as np
    +    >>> import numpy.typing as npt
    +
    +    >>> T = TypeVar("T", bound=npt.NBitBase)
    +    >>> def func(a: np.floating[T], b: np.floating[T]) -> np.floating[T]:
    +    ...     ...
    +
    +Consequently, the likes of `~numpy.float16`, `~numpy.float32` and
    +`~numpy.float64` are still sub-types of `~numpy.floating`, but, contrary to
    +runtime, they're not necessarily considered as sub-classes.
    +
    +.. deprecated:: 2.3
    +    The :class:`~numpy.typing.NBitBase` helper is deprecated and will be
    +    removed in a future release. Prefer expressing precision relationships via
    +    ``typing.overload`` or ``TypeVar`` definitions bounded by concrete scalar
    +    classes. For example:
    +
    +    .. code-block:: python
    +
    +        from typing import TypeVar
    +        import numpy as np
    +
    +        S = TypeVar("S", bound=np.floating)
    +
    +        def func(a: S, b: S) -> S:
    +            ...
    +
    +    or in the case of different input types mapping to different output types:
    +
    +   .. code-block:: python
    +
    +        from typing import overload
    +        import numpy as np
    +
    +        @overload
    +        def phase(x: np.complex64) -> np.float32: ...
    +        @overload
    +        def phase(x: np.complex128) -> np.float64: ...
    +        @overload
    +        def phase(x: np.clongdouble) -> np.longdouble: ...
    +        def phase(x: np.complexfloating) -> np.floating:
    +            ...
    +
    +Timedelta64
    +~~~~~~~~~~~
    +
    +The `~numpy.timedelta64` class is not considered a subclass of
    +`~numpy.signedinteger`, the former only inheriting from `~numpy.generic`
    +while static type checking.
    +
    +0D arrays
    +~~~~~~~~~
    +
    +During runtime numpy aggressively casts any passed 0D arrays into their
    +corresponding `~numpy.generic` instance. Until the introduction of shape
    +typing (see :pep:`646`) it is unfortunately not possible to make the
    +necessary distinction between 0D and >0D arrays. While thus not strictly
    +correct, all operations that can potentially perform a 0D-array -> scalar
    +cast are currently annotated as exclusively returning an `~numpy.ndarray`.
    +
    +If it is known in advance that an operation *will* perform a
    +0D-array -> scalar cast, then one can consider manually remedying the
    +situation with either `typing.cast` or a ``# type: ignore`` comment.
    +
    +Record array dtypes
    +~~~~~~~~~~~~~~~~~~~
    +
    +The dtype of `numpy.recarray`, and the :ref:`routines.array-creation.rec`
    +functions in general, can be specified in one of two ways:
    +
    +* Directly via the ``dtype`` argument.
    +* With up to five helper arguments that operate via `numpy.rec.format_parser`:
    +  ``formats``, ``names``, ``titles``, ``aligned`` and ``byteorder``.
    +
    +These two approaches are currently typed as being mutually exclusive,
    +*i.e.* if ``dtype`` is specified than one may not specify ``formats``.
    +While this mutual exclusivity is not (strictly) enforced during runtime,
    +combining both dtype specifiers can lead to unexpected or even downright
    +buggy behavior.
    +
    +API
    +---
    +
    +"""
    +# NOTE: The API section will be appended with additional entries
    +# further down in this file
    +
    +# pyright: reportDeprecated=false
    +
    +from numpy._typing import ArrayLike, DTypeLike, NBitBase, NDArray
    +
    +__all__ = ["ArrayLike", "DTypeLike", "NBitBase", "NDArray"]
    +
    +
    +__DIR = __all__ + [k for k in globals() if k.startswith("__") and k.endswith("__")]
    +__DIR_SET = frozenset(__DIR)
    +
    +
    +def __dir__() -> list[str]:
    +    return __DIR
    +
    +def __getattr__(name: str) -> object:
    +    if name == "NBitBase":
    +        import warnings
    +
    +        # Deprecated in NumPy 2.3, 2025-05-01
    +        warnings.warn(
    +            "`NBitBase` is deprecated and will be removed from numpy.typing in the "
    +            "future. Use `@typing.overload` or a `TypeVar` with a scalar-type as upper "
    +            "bound, instead. (deprecated in NumPy 2.3)",
    +            DeprecationWarning,
    +            stacklevel=2,
    +        )
    +        return NBitBase
    +
    +    if name in __DIR_SET:
    +        return globals()[name]
    +
    +    raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
    +
    +
    +if __doc__ is not None:
    +    from numpy._typing._add_docstring import _docstrings
    +    __doc__ += _docstrings
    +    __doc__ += '\n.. autoclass:: numpy.typing.NBitBase\n'
    +    del _docstrings
    +
    +from numpy._pytesttester import PytestTester
    +
    +test = PytestTester(__name__)
    +del PytestTester
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/__init__.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/__init__.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..7a4c7b41079c19bfcae69cd1fdbc813bc8eb3f07
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/__init__.pyi
    @@ -0,0 +1,3 @@
    +from numpy._typing import ArrayLike, DTypeLike, NBitBase, NDArray
    +
    +__all__ = ["ArrayLike", "DTypeLike", "NBitBase", "NDArray"]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/mypy_plugin.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/mypy_plugin.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..fb78eb077c44da20c17523e1fbe12c3541646162
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/mypy_plugin.py
    @@ -0,0 +1,200 @@
    +"""A mypy_ plugin for managing a number of platform-specific annotations.
    +Its functionality can be split into three distinct parts:
    +
    +* Assigning the (platform-dependent) precisions of certain `~numpy.number`
    +  subclasses, including the likes of `~numpy.int_`, `~numpy.intp` and
    +  `~numpy.longlong`. See the documentation on
    +  :ref:`scalar types ` for a comprehensive overview
    +  of the affected classes. Without the plugin the precision of all relevant
    +  classes will be inferred as `~typing.Any`.
    +* Removing all extended-precision `~numpy.number` subclasses that are
    +  unavailable for the platform in question. Most notably this includes the
    +  likes of `~numpy.float128` and `~numpy.complex256`. Without the plugin *all*
    +  extended-precision types will, as far as mypy is concerned, be available
    +  to all platforms.
    +* Assigning the (platform-dependent) precision of `~numpy.ctypeslib.c_intp`.
    +  Without the plugin the type will default to `ctypes.c_int64`.
    +
    +  .. versionadded:: 1.22
    +
    +.. deprecated:: 2.3
    +    The :mod:`numpy.typing.mypy_plugin` entry-point is deprecated in favor of
    +    platform-agnostic static type inference. Remove
    +    ``numpy.typing.mypy_plugin`` from the ``plugins`` section of your mypy
    +    configuration; if that surfaces new errors, please open an issue with a
    +    minimal reproducer.
    +
    +Examples
    +--------
    +To enable the plugin, one must add it to their mypy `configuration file`_:
    +
    +.. code-block:: ini
    +
    +    [mypy]
    +    plugins = numpy.typing.mypy_plugin
    +
    +.. _mypy: https://mypy-lang.org/
    +.. _configuration file: https://mypy.readthedocs.io/en/stable/config_file.html
    +
    +"""
    +
    +from collections.abc import Callable, Iterable
    +from typing import TYPE_CHECKING, Final, TypeAlias, cast
    +
    +import numpy as np
    +
    +__all__: list[str] = []
    +
    +
    +def _get_precision_dict() -> dict[str, str]:
    +    names = [
    +        ("_NBitByte", np.byte),
    +        ("_NBitShort", np.short),
    +        ("_NBitIntC", np.intc),
    +        ("_NBitIntP", np.intp),
    +        ("_NBitInt", np.int_),
    +        ("_NBitLong", np.long),
    +        ("_NBitLongLong", np.longlong),
    +
    +        ("_NBitHalf", np.half),
    +        ("_NBitSingle", np.single),
    +        ("_NBitDouble", np.double),
    +        ("_NBitLongDouble", np.longdouble),
    +    ]
    +    ret: dict[str, str] = {}
    +    for name, typ in names:
    +        n = 8 * np.dtype(typ).itemsize
    +        ret[f"{_MODULE}._nbit.{name}"] = f"{_MODULE}._nbit_base._{n}Bit"
    +    return ret
    +
    +
    +def _get_extended_precision_list() -> list[str]:
    +    extended_names = [
    +        "float96",
    +        "float128",
    +        "complex192",
    +        "complex256",
    +    ]
    +    return [i for i in extended_names if hasattr(np, i)]
    +
    +def _get_c_intp_name() -> str:
    +    # Adapted from `np.core._internal._getintp_ctype`
    +    return {
    +        "i": "c_int",
    +        "l": "c_long",
    +        "q": "c_longlong",
    +    }.get(np.dtype("n").char, "c_long")
    +
    +
    +_MODULE: Final = "numpy._typing"
    +
    +#: A dictionary mapping type-aliases in `numpy._typing._nbit` to
    +#: concrete `numpy.typing.NBitBase` subclasses.
    +_PRECISION_DICT: Final = _get_precision_dict()
    +
    +#: A list with the names of all extended precision `np.number` subclasses.
    +_EXTENDED_PRECISION_LIST: Final = _get_extended_precision_list()
    +
    +#: The name of the ctypes equivalent of `np.intp`
    +_C_INTP: Final = _get_c_intp_name()
    +
    +
    +try:
    +    if TYPE_CHECKING:
    +        from mypy.typeanal import TypeAnalyser
    +
    +    import mypy.types
    +    from mypy.build import PRI_MED
    +    from mypy.nodes import ImportFrom, MypyFile, Statement
    +    from mypy.plugin import AnalyzeTypeContext, Plugin
    +
    +except ModuleNotFoundError as e:
    +
    +    def plugin(version: str) -> type:
    +        raise e
    +
    +else:
    +
    +    _HookFunc: TypeAlias = Callable[[AnalyzeTypeContext], mypy.types.Type]
    +
    +    def _hook(ctx: AnalyzeTypeContext) -> mypy.types.Type:
    +        """Replace a type-alias with a concrete ``NBitBase`` subclass."""
    +        typ, _, api = ctx
    +        name = typ.name.split(".")[-1]
    +        name_new = _PRECISION_DICT[f"{_MODULE}._nbit.{name}"]
    +        return cast("TypeAnalyser", api).named_type(name_new)
    +
    +    def _index(iterable: Iterable[Statement], id: str) -> int:
    +        """Identify the first ``ImportFrom`` instance the specified `id`."""
    +        for i, value in enumerate(iterable):
    +            if getattr(value, "id", None) == id:
    +                return i
    +        raise ValueError("Failed to identify a `ImportFrom` instance "
    +                         f"with the following id: {id!r}")
    +
    +    def _override_imports(
    +        file: MypyFile,
    +        module: str,
    +        imports: list[tuple[str, str | None]],
    +    ) -> None:
    +        """Override the first `module`-based import with new `imports`."""
    +        # Construct a new `from module import y` statement
    +        import_obj = ImportFrom(module, 0, names=imports)
    +        import_obj.is_top_level = True
    +
    +        # Replace the first `module`-based import statement with `import_obj`
    +        for lst in [file.defs, cast("list[Statement]", file.imports)]:
    +            i = _index(lst, module)
    +            lst[i] = import_obj
    +
    +    class _NumpyPlugin(Plugin):
    +        """A mypy plugin for handling versus numpy-specific typing tasks."""
    +
    +        def get_type_analyze_hook(self, fullname: str) -> _HookFunc | None:
    +            """Set the precision of platform-specific `numpy.number`
    +            subclasses.
    +
    +            For example: `numpy.int_`, `numpy.longlong` and `numpy.longdouble`.
    +            """
    +            if fullname in _PRECISION_DICT:
    +                return _hook
    +            return None
    +
    +        def get_additional_deps(
    +            self, file: MypyFile
    +        ) -> list[tuple[int, str, int]]:
    +            """Handle all import-based overrides.
    +
    +            * Import platform-specific extended-precision `numpy.number`
    +              subclasses (*e.g.* `numpy.float96` and `numpy.float128`).
    +            * Import the appropriate `ctypes` equivalent to `numpy.intp`.
    +
    +            """
    +            fullname = file.fullname
    +            if fullname == "numpy":
    +                _override_imports(
    +                    file,
    +                    f"{_MODULE}._extended_precision",
    +                    imports=[(v, v) for v in _EXTENDED_PRECISION_LIST],
    +                )
    +            elif fullname == "numpy.ctypeslib":
    +                _override_imports(
    +                    file,
    +                    "ctypes",
    +                    imports=[(_C_INTP, "_c_intp")],
    +                )
    +            return [(PRI_MED, fullname, -1)]
    +
    +    def plugin(version: str) -> type:
    +        import warnings
    +
    +        plugin = "numpy.typing.mypy_plugin"
    +        # Deprecated 2025-01-10, NumPy 2.3
    +        warn_msg = (
    +            f"`{plugin}` is deprecated, and will be removed in a future "
    +            f"release. Please remove `plugins = {plugin}` in your mypy config."
    +            f"(deprecated in NumPy 2.3)"
    +        )
    +        warnings.warn(warn_msg, DeprecationWarning, stacklevel=3)
    +
    +        return _NumpyPlugin
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/__init__.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/arithmetic.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/arithmetic.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..a68df2ea53c3a8b26082e1fe7591dedbe8fd7aa7
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/arithmetic.pyi
    @@ -0,0 +1,126 @@
    +from typing import Any
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +b_ = np.bool()
    +dt = np.datetime64(0, "D")
    +td = np.timedelta64(0, "D")
    +
    +AR_b: npt.NDArray[np.bool]
    +AR_u: npt.NDArray[np.uint32]
    +AR_i: npt.NDArray[np.int64]
    +AR_f: npt.NDArray[np.longdouble]
    +AR_c: npt.NDArray[np.complex128]
    +AR_m: npt.NDArray[np.timedelta64]
    +AR_M: npt.NDArray[np.datetime64]
    +
    +ANY: Any
    +
    +AR_LIKE_b: list[bool]
    +AR_LIKE_u: list[np.uint32]
    +AR_LIKE_i: list[int]
    +AR_LIKE_f: list[float]
    +AR_LIKE_c: list[complex]
    +AR_LIKE_m: list[np.timedelta64]
    +AR_LIKE_M: list[np.datetime64]
    +
    +# Array subtraction
    +
    +# NOTE: mypys `NoReturn` errors are, unfortunately, not that great
    +_1 = AR_b - AR_LIKE_b  # type: ignore[var-annotated]
    +_2 = AR_LIKE_b - AR_b  # type: ignore[var-annotated]
    +AR_i - b""  # type: ignore[operator]
    +
    +AR_f - AR_LIKE_m  # type: ignore[operator]
    +AR_f - AR_LIKE_M  # type: ignore[operator]
    +AR_c - AR_LIKE_m  # type: ignore[operator]
    +AR_c - AR_LIKE_M  # type: ignore[operator]
    +
    +AR_m - AR_LIKE_f  # type: ignore[operator]
    +AR_M - AR_LIKE_f  # type: ignore[operator]
    +AR_m - AR_LIKE_c  # type: ignore[operator]
    +AR_M - AR_LIKE_c  # type: ignore[operator]
    +
    +AR_m - AR_LIKE_M  # type: ignore[operator]
    +AR_LIKE_m - AR_M  # type: ignore[operator]
    +
    +# array floor division
    +
    +AR_M // AR_LIKE_b  # type: ignore[operator]
    +AR_M // AR_LIKE_u  # type: ignore[operator]
    +AR_M // AR_LIKE_i  # type: ignore[operator]
    +AR_M // AR_LIKE_f  # type: ignore[operator]
    +AR_M // AR_LIKE_c  # type: ignore[operator]
    +AR_M // AR_LIKE_m  # type: ignore[operator]
    +AR_M // AR_LIKE_M  # type: ignore[operator]
    +
    +AR_b // AR_LIKE_M  # type: ignore[operator]
    +AR_u // AR_LIKE_M  # type: ignore[operator]
    +AR_i // AR_LIKE_M  # type: ignore[operator]
    +AR_f // AR_LIKE_M  # type: ignore[operator]
    +AR_c // AR_LIKE_M  # type: ignore[operator]
    +AR_m // AR_LIKE_M  # type: ignore[operator]
    +AR_M // AR_LIKE_M  # type: ignore[operator]
    +
    +_3 = AR_m // AR_LIKE_b  # type: ignore[var-annotated]
    +AR_m // AR_LIKE_c  # type: ignore[operator]
    +
    +AR_b // AR_LIKE_m  # type: ignore[operator]
    +AR_u // AR_LIKE_m  # type: ignore[operator]
    +AR_i // AR_LIKE_m  # type: ignore[operator]
    +AR_f // AR_LIKE_m  # type: ignore[operator]
    +AR_c // AR_LIKE_m  # type: ignore[operator]
    +
    +# regression tests for https://github.com/numpy/numpy/issues/28957
    +AR_c // 2  # type: ignore[operator]
    +AR_c // AR_i  # type: ignore[operator]
    +AR_c // AR_c  # type: ignore[operator]
    +
    +# Array multiplication
    +
    +AR_b *= AR_LIKE_u  # type: ignore[arg-type]
    +AR_b *= AR_LIKE_i  # type: ignore[arg-type]
    +AR_b *= AR_LIKE_f  # type: ignore[arg-type]
    +AR_b *= AR_LIKE_c  # type: ignore[arg-type]
    +AR_b *= AR_LIKE_m  # type: ignore[arg-type]
    +
    +AR_u *= AR_LIKE_f  # type: ignore[arg-type]
    +AR_u *= AR_LIKE_c  # type: ignore[arg-type]
    +AR_u *= AR_LIKE_m  # type: ignore[arg-type]
    +
    +AR_i *= AR_LIKE_f  # type: ignore[arg-type]
    +AR_i *= AR_LIKE_c  # type: ignore[arg-type]
    +AR_i *= AR_LIKE_m  # type: ignore[arg-type]
    +
    +AR_f *= AR_LIKE_c  # type: ignore[arg-type]
    +AR_f *= AR_LIKE_m  # type: ignore[arg-type]
    +
    +# Array power
    +
    +AR_b **= AR_LIKE_b  # type: ignore[misc]
    +AR_b **= AR_LIKE_u  # type: ignore[misc]
    +AR_b **= AR_LIKE_i  # type: ignore[misc]
    +AR_b **= AR_LIKE_f  # type: ignore[misc]
    +AR_b **= AR_LIKE_c  # type: ignore[misc]
    +
    +AR_u **= AR_LIKE_f  # type: ignore[arg-type]
    +AR_u **= AR_LIKE_c  # type: ignore[arg-type]
    +
    +AR_i **= AR_LIKE_f  # type: ignore[arg-type]
    +AR_i **= AR_LIKE_c  # type: ignore[arg-type]
    +
    +AR_f **= AR_LIKE_c  # type: ignore[arg-type]
    +
    +# Scalars
    +
    +b_ - b_  # type: ignore[operator]
    +
    +dt + dt  # type: ignore[operator]
    +td - dt  # type: ignore[operator]
    +td % 1  # type: ignore[operator]
    +td / dt  # type: ignore[operator]
    +td % dt  # type: ignore[operator]
    +
    +-b_  # type: ignore[operator]
    ++b_  # type: ignore[operator]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/array_constructors.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/array_constructors.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..6ed619958c1c797710e610b3521ca04ba8d04dd1
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/array_constructors.pyi
    @@ -0,0 +1,34 @@
    +import numpy as np
    +import numpy.typing as npt
    +
    +a: npt.NDArray[np.float64]
    +generator = (i for i in range(10))
    +
    +np.require(a, requirements=1)  # type: ignore[call-overload]
    +np.require(a, requirements="TEST")  # type: ignore[arg-type]
    +
    +np.zeros("test")  # type: ignore[arg-type]
    +np.zeros()  # type: ignore[call-overload]
    +
    +np.ones("test")  # type: ignore[arg-type]
    +np.ones()  # type: ignore[call-overload]
    +
    +np.array(0, float, True)  # type: ignore[call-overload]
    +
    +np.linspace(None, "bob")  # type: ignore[call-overload]
    +np.linspace(0, 2, num=10.0)  # type: ignore[call-overload]
    +np.linspace(0, 2, endpoint="True")  # type: ignore[call-overload]
    +np.linspace(0, 2, retstep=b"False")  # type: ignore[call-overload]
    +np.linspace(0, 2, dtype=0)  # type: ignore[call-overload]
    +np.linspace(0, 2, axis=None)  # type: ignore[call-overload]
    +
    +np.logspace(None, "bob")  # type: ignore[call-overload]
    +np.logspace(0, 2, base=None)  # type: ignore[call-overload]
    +
    +np.geomspace(None, "bob")  # type: ignore[call-overload]
    +
    +np.stack(generator)  # type: ignore[call-overload]
    +np.hstack({1, 2})  # type: ignore[call-overload]
    +np.vstack(1)  # type: ignore[call-overload]
    +
    +np.array([1], like=1)  # type: ignore[call-overload]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/array_like.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/array_like.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..4e37354e8ebaffbeb281c5f4b13fb4ba96d55527
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/array_like.pyi
    @@ -0,0 +1,15 @@
    +import numpy as np
    +from numpy._typing import ArrayLike
    +
    +class A: ...
    +
    +x1: ArrayLike = (i for i in range(10))  # type: ignore[assignment]
    +x2: ArrayLike = A()  # type: ignore[assignment]
    +x3: ArrayLike = {1: "foo", 2: "bar"}  # type: ignore[assignment]
    +
    +scalar = np.int64(1)
    +scalar.__array__(dtype=np.float64)  # type: ignore[call-overload]
    +array = np.array([1])
    +array.__array__(dtype=np.float64)  # type: ignore[call-overload]
    +
    +array.setfield(np.eye(1), np.int32, (0, 1))  # type: ignore[arg-type]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/array_pad.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/array_pad.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..42e61c8d70d652836c6d07146cacadf00fd15536
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/array_pad.pyi
    @@ -0,0 +1,6 @@
    +import numpy as np
    +import numpy.typing as npt
    +
    +AR_i8: npt.NDArray[np.int64]
    +
    +np.pad(AR_i8, 2, mode="bob")  # type: ignore[call-overload]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/arrayprint.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/arrayprint.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..3c9dc9330a2bccfa4bae7cc83a7d159cedb1877a
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/arrayprint.pyi
    @@ -0,0 +1,15 @@
    +from collections.abc import Callable
    +from typing import Any
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +AR: npt.NDArray[np.float64]
    +func1: Callable[[Any], str]
    +func2: Callable[[np.integer], str]
    +
    +np.array2string(AR, legacy="1.14")  # type: ignore[arg-type]
    +np.array2string(AR, sign="*")  # type: ignore[arg-type]
    +np.array2string(AR, floatmode="default")  # type: ignore[arg-type]
    +np.array2string(AR, formatter={"A": func1})  # type: ignore[arg-type]
    +np.array2string(AR, formatter={"float": func2})  # type: ignore[arg-type]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/arrayterator.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/arrayterator.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..8d2295a5859ffe06e84dfdcf66cb8f712f72943c
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/arrayterator.pyi
    @@ -0,0 +1,14 @@
    +import numpy as np
    +import numpy.typing as npt
    +
    +AR_i8: npt.NDArray[np.int64]
    +ar_iter = np.lib.Arrayterator(AR_i8)
    +
    +np.lib.Arrayterator(np.int64())  # type: ignore[arg-type]
    +ar_iter.shape = (10, 5)  # type: ignore[misc]
    +ar_iter[None]  # type: ignore[index]
    +ar_iter[None, 1]  # type: ignore[index]
    +ar_iter[np.intp()]  # type: ignore[index]
    +ar_iter[np.intp(), ...]  # type: ignore[index]
    +ar_iter[AR_i8]  # type: ignore[index]
    +ar_iter[AR_i8, :]  # type: ignore[index]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/bitwise_ops.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/bitwise_ops.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..f4de2928ff54ec63bcbff72b4c0bcfde9bcfad73
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/bitwise_ops.pyi
    @@ -0,0 +1,17 @@
    +import numpy as np
    +
    +i8 = np.int64()
    +i4 = np.int32()
    +u8 = np.uint64()
    +b_ = np.bool()
    +i = 0
    +
    +f8 = np.float64()
    +
    +b_ >> f8  # type: ignore[operator]
    +i8 << f8  # type: ignore[operator]
    +i | f8  # type: ignore[operator]
    +i8 ^ f8  # type: ignore[operator]
    +u8 & f8  # type: ignore[operator]
    +~f8  # type: ignore[operator]
    +# TODO: Certain mixes like i4 << u8 go to float and thus should fail
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/char.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/char.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..3dbe5eda296e9dc85ed73d99e440c7da73600f02
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/char.pyi
    @@ -0,0 +1,63 @@
    +import numpy as np
    +import numpy.typing as npt
    +
    +AR_U: npt.NDArray[np.str_]
    +AR_S: npt.NDArray[np.bytes_]
    +
    +np.char.equal(AR_U, AR_S)  # type: ignore[arg-type]
    +np.char.not_equal(AR_U, AR_S)  # type: ignore[arg-type]
    +
    +np.char.greater_equal(AR_U, AR_S)  # type: ignore[arg-type]
    +np.char.less_equal(AR_U, AR_S)  # type: ignore[arg-type]
    +np.char.greater(AR_U, AR_S)  # type: ignore[arg-type]
    +np.char.less(AR_U, AR_S)  # type: ignore[arg-type]
    +
    +np.char.encode(AR_S)  # type: ignore[arg-type]
    +np.char.decode(AR_U)  # type: ignore[arg-type]
    +
    +np.char.join(AR_U, b"_")  # type: ignore[arg-type]
    +np.char.join(AR_S, "_")  # type: ignore[arg-type]
    +
    +np.char.ljust(AR_U, 5, fillchar=b"a")  # type: ignore[arg-type]
    +np.char.rjust(AR_U, 5, fillchar=b"a")  # type: ignore[arg-type]
    +
    +np.char.lstrip(AR_U, chars=b"a")  # type: ignore[arg-type]
    +np.char.lstrip(AR_S, chars="a")  # type: ignore[arg-type]
    +np.char.strip(AR_U, chars=b"a")  # type: ignore[arg-type]
    +np.char.strip(AR_S, chars="a")  # type: ignore[arg-type]
    +np.char.rstrip(AR_U, chars=b"a")  # type: ignore[arg-type]
    +np.char.rstrip(AR_S, chars="a")  # type: ignore[arg-type]
    +
    +np.char.partition(AR_U, b"a")  # type: ignore[arg-type]
    +np.char.partition(AR_S, "a")  # type: ignore[arg-type]
    +np.char.rpartition(AR_U, b"a")  # type: ignore[arg-type]
    +np.char.rpartition(AR_S, "a")  # type: ignore[arg-type]
    +
    +np.char.replace(AR_U, b"_", b"-")  # type: ignore[arg-type]
    +np.char.replace(AR_S, "_", "-")  # type: ignore[arg-type]
    +
    +np.char.split(AR_U, b"_")  # type: ignore[arg-type]
    +np.char.split(AR_S, "_")  # type: ignore[arg-type]
    +np.char.rsplit(AR_U, b"_")  # type: ignore[arg-type]
    +np.char.rsplit(AR_S, "_")  # type: ignore[arg-type]
    +
    +np.char.count(AR_U, b"a", start=[1, 2, 3])  # type: ignore[arg-type]
    +np.char.count(AR_S, "a", end=9)  # type: ignore[arg-type]
    +
    +np.char.endswith(AR_U, b"a", start=[1, 2, 3])  # type: ignore[arg-type]
    +np.char.endswith(AR_S, "a", end=9)  # type: ignore[arg-type]
    +np.char.startswith(AR_U, b"a", start=[1, 2, 3])  # type: ignore[arg-type]
    +np.char.startswith(AR_S, "a", end=9)  # type: ignore[arg-type]
    +
    +np.char.find(AR_U, b"a", start=[1, 2, 3])  # type: ignore[arg-type]
    +np.char.find(AR_S, "a", end=9)  # type: ignore[arg-type]
    +np.char.rfind(AR_U, b"a", start=[1, 2, 3])  # type: ignore[arg-type]
    +np.char.rfind(AR_S, "a", end=9)  # type: ignore[arg-type]
    +
    +np.char.index(AR_U, b"a", start=[1, 2, 3])  # type: ignore[arg-type]
    +np.char.index(AR_S, "a", end=9)  # type: ignore[arg-type]
    +np.char.rindex(AR_U, b"a", start=[1, 2, 3])  # type: ignore[arg-type]
    +np.char.rindex(AR_S, "a", end=9)  # type: ignore[arg-type]
    +
    +np.char.isdecimal(AR_S)  # type: ignore[arg-type]
    +np.char.isnumeric(AR_S)  # type: ignore[arg-type]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/chararray.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/chararray.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..589895510227e5e8a236d685bf02cc4c65a0dcee
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/chararray.pyi
    @@ -0,0 +1,61 @@
    +from typing import Any
    +
    +import numpy as np
    +
    +AR_U: np.char.chararray[tuple[Any, ...], np.dtype[np.str_]]
    +AR_S: np.char.chararray[tuple[Any, ...], np.dtype[np.bytes_]]
    +
    +AR_S.encode()  # type: ignore[misc]
    +AR_U.decode()  # type: ignore[misc]
    +
    +AR_U.join(b"_")  # type: ignore[arg-type]
    +AR_S.join("_")  # type: ignore[arg-type]
    +
    +AR_U.ljust(5, fillchar=b"a")  # type: ignore[arg-type]
    +AR_U.rjust(5, fillchar=b"a")  # type: ignore[arg-type]
    +
    +AR_U.lstrip(chars=b"a")  # type: ignore[arg-type]
    +AR_S.lstrip(chars="a")  # type: ignore[arg-type]
    +AR_U.strip(chars=b"a")  # type: ignore[arg-type]
    +AR_S.strip(chars="a")  # type: ignore[arg-type]
    +AR_U.rstrip(chars=b"a")  # type: ignore[arg-type]
    +AR_S.rstrip(chars="a")  # type: ignore[arg-type]
    +
    +AR_U.partition(b"a")  # type: ignore[arg-type]
    +AR_S.partition("a")  # type: ignore[arg-type]
    +AR_U.rpartition(b"a")  # type: ignore[arg-type]
    +AR_S.rpartition("a")  # type: ignore[arg-type]
    +
    +AR_U.replace(b"_", b"-")  # type: ignore[arg-type]
    +AR_S.replace("_", "-")  # type: ignore[arg-type]
    +
    +AR_U.split(b"_")  # type: ignore[arg-type]
    +AR_S.split("_")  # type: ignore[arg-type]
    +AR_S.split(1)  # type: ignore[arg-type]
    +AR_U.rsplit(b"_")  # type: ignore[arg-type]
    +AR_S.rsplit("_")  # type: ignore[arg-type]
    +
    +AR_U.count(b"a", start=[1, 2, 3])  # type: ignore[arg-type]
    +AR_S.count("a", end=9)  # type: ignore[arg-type]
    +
    +AR_U.endswith(b"a", start=[1, 2, 3])  # type: ignore[arg-type]
    +AR_S.endswith("a", end=9)  # type: ignore[arg-type]
    +AR_U.startswith(b"a", start=[1, 2, 3])  # type: ignore[arg-type]
    +AR_S.startswith("a", end=9)  # type: ignore[arg-type]
    +
    +AR_U.find(b"a", start=[1, 2, 3])  # type: ignore[arg-type]
    +AR_S.find("a", end=9)  # type: ignore[arg-type]
    +AR_U.rfind(b"a", start=[1, 2, 3])  # type: ignore[arg-type]
    +AR_S.rfind("a", end=9)  # type: ignore[arg-type]
    +
    +AR_U.index(b"a", start=[1, 2, 3])  # type: ignore[arg-type]
    +AR_S.index("a", end=9)  # type: ignore[arg-type]
    +AR_U.rindex(b"a", start=[1, 2, 3])  # type: ignore[arg-type]
    +AR_S.rindex("a", end=9)  # type: ignore[arg-type]
    +
    +AR_U == AR_S  # type: ignore[operator]
    +AR_U != AR_S  # type: ignore[operator]
    +AR_U >= AR_S  # type: ignore[operator]
    +AR_U <= AR_S  # type: ignore[operator]
    +AR_U > AR_S  # type: ignore[operator]
    +AR_U < AR_S  # type: ignore[operator]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/comparisons.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/comparisons.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..d2965b5c1a918fc4f0170f38ff22fd6779d9e3db
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/comparisons.pyi
    @@ -0,0 +1,27 @@
    +import numpy as np
    +import numpy.typing as npt
    +
    +AR_i: npt.NDArray[np.int64]
    +AR_f: npt.NDArray[np.float64]
    +AR_c: npt.NDArray[np.complex128]
    +AR_m: npt.NDArray[np.timedelta64]
    +AR_M: npt.NDArray[np.datetime64]
    +
    +AR_f > AR_m  # type: ignore[operator]
    +AR_c > AR_m  # type: ignore[operator]
    +
    +AR_m > AR_f  # type: ignore[operator]
    +AR_m > AR_c  # type: ignore[operator]
    +
    +AR_i > AR_M  # type: ignore[operator]
    +AR_f > AR_M  # type: ignore[operator]
    +AR_m > AR_M  # type: ignore[operator]
    +
    +AR_M > AR_i  # type: ignore[operator]
    +AR_M > AR_f  # type: ignore[operator]
    +AR_M > AR_m  # type: ignore[operator]
    +
    +AR_i > ""  # type: ignore[operator]
    +AR_i > b""  # type: ignore[operator]
    +"" > AR_M  # type: ignore[operator]
    +b"" > AR_M  # type: ignore[operator]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/constants.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/constants.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..10717f664e0a679fdd30e232001948dac6da063d
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/constants.pyi
    @@ -0,0 +1,3 @@
    +import numpy as np
    +
    +np.little_endian = np.little_endian  # type: ignore[misc]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/datasource.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/datasource.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..4c603cf693a15b801dd564bbcdf9ce1b1b1a2c73
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/datasource.pyi
    @@ -0,0 +1,16 @@
    +from pathlib import Path
    +
    +import numpy as np
    +
    +path: Path
    +d1: np.lib.npyio.DataSource
    +
    +d1.abspath(path)  # type: ignore[arg-type]
    +d1.abspath(b"...")  # type: ignore[arg-type]
    +
    +d1.exists(path)  # type: ignore[arg-type]
    +d1.exists(b"...")  # type: ignore[arg-type]
    +
    +d1.open(path, "r")  # type: ignore[arg-type]
    +d1.open(b"...", encoding="utf8")  # type: ignore[arg-type]
    +d1.open(None, newline="/n")  # type: ignore[arg-type]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/dtype.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/dtype.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..64a7c3f775e1a14080aafda8ba3ff293232e9c47
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/dtype.pyi
    @@ -0,0 +1,17 @@
    +import numpy as np
    +
    +class Test1:
    +    not_dtype = np.dtype(float)
    +
    +class Test2:
    +    dtype = float
    +
    +np.dtype(Test1())  # type: ignore[call-overload]
    +np.dtype(Test2())  # type: ignore[arg-type]
    +
    +np.dtype(  # type: ignore[call-overload]
    +    {
    +        "field1": (float, 1),
    +        "field2": (int, 3),
    +    }
    +)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/einsumfunc.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/einsumfunc.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..982ad986297f6695126c5831ff9d6b66e1b60da8
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/einsumfunc.pyi
    @@ -0,0 +1,12 @@
    +import numpy as np
    +import numpy.typing as npt
    +
    +AR_i: npt.NDArray[np.int64]
    +AR_f: npt.NDArray[np.float64]
    +AR_m: npt.NDArray[np.timedelta64]
    +AR_U: npt.NDArray[np.str_]
    +
    +np.einsum("i,i->i", AR_i, AR_m)  # type: ignore[arg-type]
    +np.einsum("i,i->i", AR_f, AR_f, dtype=np.int32)  # type: ignore[arg-type]
    +np.einsum("i,i->i", AR_i, AR_i, out=AR_U)  # type: ignore[type-var]
    +np.einsum("i,i->i", AR_i, AR_i, out=AR_U, casting="unsafe")  # type: ignore[call-overload]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/flatiter.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/flatiter.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..2c6e912bd3185b30956af18f6ce329638165ff8c
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/flatiter.pyi
    @@ -0,0 +1,38 @@
    +from typing import Any
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +class _Index:
    +    def __index__(self) -> int: ...
    +
    +class _MyArray:
    +    def __array__(self) -> np.ndarray[tuple[int], np.dtypes.Float64DType]: ...
    +
    +_index: _Index
    +_my_array: _MyArray
    +_something: Any
    +_dtype: np.dtype[np.int8]
    +
    +_a_nd: np.flatiter[npt.NDArray[np.float64]]
    +
    +###
    +
    +_a_nd.base = _something  # type: ignore[misc]
    +_a_nd.coords = _something  # type: ignore[misc]
    +_a_nd.index = _something  # type: ignore[misc]
    +
    +_a_nd.copy("C")  # type: ignore[call-arg]
    +_a_nd.copy(order="C")  # type: ignore[call-arg]
    +
    +# NOTE: Contrary to `ndarray.__getitem__` its counterpart in `flatiter`
    +# does not accept objects with the `__array__` or `__index__` protocols;
    +# boolean indexing is just plain broken (gh-17175)
    +_a_nd[np.True_]  # type: ignore[call-overload]
    +_a_nd[_index]  # type: ignore[call-overload]
    +_a_nd[_my_array]  # type: ignore[call-overload]
    +
    +# `dtype` and `copy` are no-ops in `flatiter.__array__`
    +_a_nd.__array__(_dtype)  # type: ignore[arg-type]
    +_a_nd.__array__(dtype=_dtype)  # type: ignore[call-arg]
    +_a_nd.__array__(copy=True)  # type: ignore[arg-type]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/fromnumeric.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/fromnumeric.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..92b0cb3662073011885434b8fa7279528c1cf708
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/fromnumeric.pyi
    @@ -0,0 +1,148 @@
    +"""Tests for :mod:`numpy._core.fromnumeric`."""
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +A = np.array(True, ndmin=2, dtype=bool)
    +A.setflags(write=False)
    +AR_U: npt.NDArray[np.str_]
    +AR_M: npt.NDArray[np.datetime64]
    +AR_f4: npt.NDArray[np.float32]
    +
    +a = np.bool(True)
    +
    +np.take(a, None)  # type: ignore[call-overload]
    +np.take(a, axis=1.0)  # type: ignore[call-overload]
    +np.take(A, out=1)  # type: ignore[call-overload]
    +np.take(A, mode="bob")  # type: ignore[call-overload]
    +
    +np.reshape(a, None)  # type: ignore[call-overload]
    +np.reshape(A, 1, order="bob")  # type: ignore[call-overload]
    +
    +np.choose(a, None)  # type: ignore[call-overload]
    +np.choose(a, out=1.0)  # type: ignore[call-overload]
    +np.choose(A, mode="bob")  # type: ignore[call-overload]
    +
    +np.repeat(a, None)  # type: ignore[call-overload]
    +np.repeat(A, 1, axis=1.0)  # type: ignore[call-overload]
    +
    +np.swapaxes(A, None, 1)  # type: ignore[call-overload]
    +np.swapaxes(A, 1, [0])  # type: ignore[call-overload]
    +
    +np.transpose(A, axes=1.0)  # type: ignore[call-overload]
    +
    +np.partition(a, None)  # type: ignore[call-overload]
    +np.partition(a, 0, axis="bob")  # type: ignore[call-overload]
    +np.partition(A, 0, kind="bob")  # type: ignore[call-overload]
    +np.partition(A, 0, order=range(5))  # type: ignore[arg-type]
    +
    +np.argpartition(a, None)  # type: ignore[arg-type]
    +np.argpartition(a, 0, axis="bob")  # type: ignore[arg-type]
    +np.argpartition(A, 0, kind="bob")  # type: ignore[arg-type]
    +np.argpartition(A, 0, order=range(5))  # type: ignore[arg-type]
    +
    +np.sort(A, axis="bob")  # type: ignore[call-overload]
    +np.sort(A, kind="bob")  # type: ignore[call-overload]
    +np.sort(A, order=range(5))  # type: ignore[arg-type]
    +
    +np.argsort(A, axis="bob")  # type: ignore[arg-type]
    +np.argsort(A, kind="bob")  # type: ignore[arg-type]
    +np.argsort(A, order=range(5))  # type: ignore[arg-type]
    +
    +np.argmax(A, axis="bob")  # type: ignore[call-overload]
    +np.argmax(A, kind="bob")  # type: ignore[call-overload]
    +np.argmax(A, out=AR_f4)  # type: ignore[type-var]
    +
    +np.argmin(A, axis="bob")  # type: ignore[call-overload]
    +np.argmin(A, kind="bob")  # type: ignore[call-overload]
    +np.argmin(A, out=AR_f4)  # type: ignore[type-var]
    +
    +np.searchsorted(A[0], 0, side="bob")  # type: ignore[call-overload]
    +np.searchsorted(A[0], 0, sorter=1.0)  # type: ignore[call-overload]
    +
    +np.resize(A, 1.0)  # type: ignore[call-overload]
    +
    +np.squeeze(A, 1.0)  # type: ignore[call-overload]
    +
    +np.diagonal(A, offset=None)  # type: ignore[call-overload]
    +np.diagonal(A, axis1="bob")  # type: ignore[call-overload]
    +np.diagonal(A, axis2=[])  # type: ignore[call-overload]
    +
    +np.trace(A, offset=None)  # type: ignore[call-overload]
    +np.trace(A, axis1="bob")  # type: ignore[call-overload]
    +np.trace(A, axis2=[])  # type: ignore[call-overload]
    +
    +np.ravel(a, order="bob")  # type: ignore[call-overload]
    +
    +np.nonzero(0)  # type: ignore[arg-type]
    +
    +np.compress([True], A, axis=1.0)  # type: ignore[call-overload]
    +
    +np.clip(a, 1, 2, out=1)  # type: ignore[call-overload]
    +
    +np.sum(a, axis=1.0)  # type: ignore[call-overload]
    +np.sum(a, keepdims=1.0)  # type: ignore[call-overload]
    +np.sum(a, initial=[1])  # type: ignore[call-overload]
    +
    +np.all(a, axis=1.0)  # type: ignore[call-overload]
    +np.all(a, keepdims=1.0)  # type: ignore[call-overload]
    +np.all(a, out=1.0)  # type: ignore[call-overload]
    +
    +np.any(a, axis=1.0)  # type: ignore[call-overload]
    +np.any(a, keepdims=1.0)  # type: ignore[call-overload]
    +np.any(a, out=1.0)  # type: ignore[call-overload]
    +
    +np.cumsum(a, axis=1.0)  # type: ignore[call-overload]
    +np.cumsum(a, dtype=1.0)  # type: ignore[call-overload]
    +np.cumsum(a, out=1.0)  # type: ignore[call-overload]
    +
    +np.ptp(a, axis=1.0)  # type: ignore[call-overload]
    +np.ptp(a, keepdims=1.0)  # type: ignore[call-overload]
    +np.ptp(a, out=1.0)  # type: ignore[call-overload]
    +
    +np.amax(a, axis=1.0)  # type: ignore[call-overload]
    +np.amax(a, keepdims=1.0)  # type: ignore[call-overload]
    +np.amax(a, out=1.0)  # type: ignore[call-overload]
    +np.amax(a, initial=[1.0])  # type: ignore[call-overload]
    +np.amax(a, where=[1.0])  # type: ignore[arg-type]
    +
    +np.amin(a, axis=1.0)  # type: ignore[call-overload]
    +np.amin(a, keepdims=1.0)  # type: ignore[call-overload]
    +np.amin(a, out=1.0)  # type: ignore[call-overload]
    +np.amin(a, initial=[1.0])  # type: ignore[call-overload]
    +np.amin(a, where=[1.0])  # type: ignore[arg-type]
    +
    +np.prod(a, axis=1.0)  # type: ignore[call-overload]
    +np.prod(a, out=False)  # type: ignore[call-overload]
    +np.prod(a, keepdims=1.0)  # type: ignore[call-overload]
    +np.prod(a, initial=int)  # type: ignore[call-overload]
    +np.prod(a, where=1.0)  # type: ignore[call-overload]
    +np.prod(AR_U)  # type: ignore[arg-type]
    +
    +np.cumprod(a, axis=1.0)  # type: ignore[call-overload]
    +np.cumprod(a, out=False)  # type: ignore[call-overload]
    +np.cumprod(AR_U)  # type: ignore[arg-type]
    +
    +np.size(a, axis=1.0)  # type: ignore[arg-type]
    +
    +np.around(a, decimals=1.0)  # type: ignore[call-overload]
    +np.around(a, out=type)  # type: ignore[call-overload]
    +np.around(AR_U)  # type: ignore[arg-type]
    +
    +np.mean(a, axis=1.0)  # type: ignore[call-overload]
    +np.mean(a, out=False)  # type: ignore[call-overload]
    +np.mean(a, keepdims=1.0)  # type: ignore[call-overload]
    +np.mean(AR_U)  # type: ignore[arg-type]
    +np.mean(AR_M)  # type: ignore[arg-type]
    +
    +np.std(a, axis=1.0)  # type: ignore[call-overload]
    +np.std(a, out=False)  # type: ignore[call-overload]
    +np.std(a, ddof="test")  # type: ignore[call-overload]
    +np.std(a, keepdims=1.0)  # type: ignore[call-overload]
    +np.std(AR_U)  # type: ignore[arg-type]
    +
    +np.var(a, axis=1.0)  # type: ignore[call-overload]
    +np.var(a, out=False)  # type: ignore[call-overload]
    +np.var(a, ddof="test")  # type: ignore[call-overload]
    +np.var(a, keepdims=1.0)  # type: ignore[call-overload]
    +np.var(AR_U)  # type: ignore[arg-type]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/histograms.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/histograms.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..5f7892719eb4bf0c225e6ff6ddaf52c42de9c9c9
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/histograms.pyi
    @@ -0,0 +1,12 @@
    +import numpy as np
    +import numpy.typing as npt
    +
    +AR_i8: npt.NDArray[np.int64]
    +AR_f8: npt.NDArray[np.float64]
    +
    +np.histogram_bin_edges(AR_i8, range=(0, 1, 2))  # type: ignore[arg-type]
    +
    +np.histogram(AR_i8, range=(0, 1, 2))  # type: ignore[arg-type]
    +
    +np.histogramdd(AR_i8, range=(0, 1))  # type: ignore[arg-type]
    +np.histogramdd(AR_i8, range=[(0, 1, 2)])  # type: ignore[list-item]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/index_tricks.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/index_tricks.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..8b7b1ae2b5bfc0705bbbfbdd244c06b90d0fdd4d
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/index_tricks.pyi
    @@ -0,0 +1,14 @@
    +import numpy as np
    +
    +AR_LIKE_i: list[int]
    +AR_LIKE_f: list[float]
    +
    +np.ndindex([1, 2, 3])  # type: ignore[call-overload]
    +np.unravel_index(AR_LIKE_f, (1, 2, 3))  # type: ignore[arg-type]
    +np.ravel_multi_index(AR_LIKE_i, (1, 2, 3), mode="bob")  # type: ignore[call-overload]
    +np.mgrid[1]  # type: ignore[index]
    +np.mgrid[...]  # type: ignore[index]
    +np.ogrid[1]  # type: ignore[index]
    +np.ogrid[...]  # type: ignore[index]
    +np.fill_diagonal(AR_LIKE_f, 2)  # type: ignore[arg-type]
    +np.diag_indices(1.0)  # type: ignore[arg-type]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/lib_function_base.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/lib_function_base.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..d7be993bf04c96555ef8dae357aacca337c3d478
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/lib_function_base.pyi
    @@ -0,0 +1,60 @@
    +from typing import Any
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +AR_f8: npt.NDArray[np.float64]
    +AR_c16: npt.NDArray[np.complex128]
    +AR_m: npt.NDArray[np.timedelta64]
    +AR_M: npt.NDArray[np.datetime64]
    +AR_O: npt.NDArray[np.object_]
    +AR_b_list: list[npt.NDArray[np.bool]]
    +
    +def fn_none_i(a: None, /) -> npt.NDArray[Any]: ...
    +def fn_ar_i(a: npt.NDArray[np.float64], posarg: int, /) -> npt.NDArray[Any]: ...
    +
    +np.average(AR_m)  # type: ignore[type-var]
    +np.select(1, [AR_f8])  # type: ignore[call-overload]
    +np.angle(AR_m)  # type: ignore[type-var]
    +np.unwrap(AR_m)  # type: ignore[type-var]
    +np.unwrap(AR_c16)  # type: ignore[type-var]
    +np.trim_zeros(1)  # type: ignore[arg-type]
    +np.place(1, [True], 1.5)  # type: ignore[arg-type]
    +np.vectorize(1)  # type: ignore[arg-type]
    +np.place(AR_f8, slice(None), 5)  # type: ignore[arg-type]
    +
    +np.piecewise(AR_f8, True, [fn_ar_i], "wrong")  # type: ignore[call-overload]
    +np.piecewise(AR_f8, AR_b_list, [fn_none_i])  # type: ignore[call-overload]
    +np.piecewise(AR_f8, AR_b_list, [fn_ar_i])  # type: ignore[call-overload]
    +np.piecewise(AR_f8, AR_b_list, [fn_ar_i], 3.14)  # type: ignore[call-overload]
    +np.piecewise(AR_f8, AR_b_list, [fn_ar_i], 42, None)  # type: ignore[call-overload]
    +np.piecewise(AR_f8, AR_b_list, [fn_ar_i], 42, _=None)  # type: ignore[list-item]
    +
    +np.interp(AR_f8, AR_c16, AR_f8)  # type: ignore[arg-type]
    +np.interp(AR_c16, AR_f8, AR_f8)  # type: ignore[arg-type]
    +np.interp(AR_f8, AR_f8, AR_f8, period=AR_c16)  # type: ignore[call-overload]
    +np.interp(AR_f8, AR_f8, AR_O)  # type: ignore[arg-type]
    +
    +np.cov(AR_m)  # type: ignore[type-var]
    +np.cov(AR_O)  # type: ignore[type-var]
    +np.corrcoef(AR_m)  # type: ignore[type-var]
    +np.corrcoef(AR_O)  # type: ignore[type-var]
    +np.corrcoef(AR_f8, bias=True)  # type: ignore[call-overload]
    +np.corrcoef(AR_f8, ddof=2)  # type: ignore[call-overload]
    +np.blackman(1j)  # type: ignore[arg-type]
    +np.bartlett(1j)  # type: ignore[arg-type]
    +np.hanning(1j)  # type: ignore[arg-type]
    +np.hamming(1j)  # type: ignore[arg-type]
    +np.hamming(AR_c16)  # type: ignore[arg-type]
    +np.kaiser(1j, 1)  # type: ignore[arg-type]
    +np.sinc(AR_O)  # type: ignore[type-var]
    +np.median(AR_M)  # type: ignore[type-var]
    +
    +np.percentile(AR_f8, 50j)  # type: ignore[call-overload]
    +np.percentile(AR_f8, 50, interpolation="bob")  # type: ignore[call-overload]
    +np.quantile(AR_f8, 0.5j)  # type: ignore[call-overload]
    +np.quantile(AR_f8, 0.5, interpolation="bob")  # type: ignore[call-overload]
    +np.meshgrid(AR_f8, AR_f8, indexing="bob")  # type: ignore[call-overload]
    +np.delete(AR_f8, AR_f8)  # type: ignore[arg-type]
    +np.insert(AR_f8, AR_f8, 1.5)  # type: ignore[arg-type]
    +np.digitize(AR_f8, 1j)  # type: ignore[call-overload]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/lib_polynomial.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/lib_polynomial.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..727eb7f4b2b121f4b51c61e8df467d2ba7bb6d85
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/lib_polynomial.pyi
    @@ -0,0 +1,29 @@
    +import numpy as np
    +import numpy.typing as npt
    +
    +AR_f8: npt.NDArray[np.float64]
    +AR_c16: npt.NDArray[np.complex128]
    +AR_O: npt.NDArray[np.object_]
    +AR_U: npt.NDArray[np.str_]
    +
    +poly_obj: np.poly1d
    +
    +np.polymul(AR_f8, AR_U)  # type: ignore[arg-type]
    +np.polydiv(AR_f8, AR_U)  # type: ignore[arg-type]
    +
    +5**poly_obj  # type: ignore[operator]
    +
    +np.polyint(AR_U)  # type: ignore[arg-type]
    +np.polyint(AR_f8, m=1j)  # type: ignore[call-overload]
    +
    +np.polyder(AR_U)  # type: ignore[arg-type]
    +np.polyder(AR_f8, m=1j)  # type: ignore[call-overload]
    +
    +np.polyfit(AR_O, AR_f8, 1)  # type: ignore[arg-type]
    +np.polyfit(AR_f8, AR_f8, 1, rcond=1j)  # type: ignore[call-overload]
    +np.polyfit(AR_f8, AR_f8, 1, w=AR_c16)  # type: ignore[arg-type]
    +np.polyfit(AR_f8, AR_f8, 1, cov="bob")  # type: ignore[call-overload]
    +
    +np.polyval(AR_f8, AR_U)  # type: ignore[arg-type]
    +np.polyadd(AR_f8, AR_U)  # type: ignore[arg-type]
    +np.polysub(AR_f8, AR_U)  # type: ignore[arg-type]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/lib_utils.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/lib_utils.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..25af32b432977e311d8612c74725340d926cf590
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/lib_utils.pyi
    @@ -0,0 +1,3 @@
    +import numpy.lib.array_utils as array_utils
    +
    +array_utils.byte_bounds(1)  # type: ignore[arg-type]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/lib_version.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/lib_version.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..62011a848cc15a1c2f1454a80aba21d814855a8c
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/lib_version.pyi
    @@ -0,0 +1,6 @@
    +from numpy.lib import NumpyVersion
    +
    +version: NumpyVersion
    +
    +NumpyVersion(b"1.8.0")  # type: ignore[arg-type]
    +version >= b"1.8.0"  # type: ignore[operator]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/linalg.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/linalg.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..78aceb235f8da5a57f6b4c52fc7b3e5e510404bc
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/linalg.pyi
    @@ -0,0 +1,52 @@
    +import numpy as np
    +import numpy.typing as npt
    +
    +AR_f8: npt.NDArray[np.float64]
    +AR_O: npt.NDArray[np.object_]
    +AR_M: npt.NDArray[np.datetime64]
    +
    +np.linalg.tensorsolve(AR_O, AR_O)  # type: ignore[arg-type]
    +
    +np.linalg.solve(AR_O, AR_O)  # type: ignore[arg-type]
    +
    +np.linalg.tensorinv(AR_O)  # type: ignore[arg-type]
    +
    +np.linalg.inv(AR_O)  # type: ignore[arg-type]
    +
    +np.linalg.matrix_power(AR_M, 5)  # type: ignore[arg-type]
    +
    +np.linalg.cholesky(AR_O)  # type: ignore[arg-type]
    +
    +np.linalg.qr(AR_O)  # type: ignore[arg-type]
    +np.linalg.qr(AR_f8, mode="bob")  # type: ignore[call-overload]
    +
    +np.linalg.eigvals(AR_O)  # type: ignore[arg-type]
    +
    +np.linalg.eigvalsh(AR_O)  # type: ignore[arg-type]
    +np.linalg.eigvalsh(AR_O, UPLO="bob")  # type: ignore[call-overload]
    +
    +np.linalg.eig(AR_O)  # type: ignore[arg-type]
    +
    +np.linalg.eigh(AR_O)  # type: ignore[arg-type]
    +np.linalg.eigh(AR_O, UPLO="bob")  # type: ignore[call-overload]
    +
    +np.linalg.svd(AR_O)  # type: ignore[arg-type]
    +
    +np.linalg.svdvals(AR_O)  # type: ignore[arg-type]
    +np.linalg.svdvals(AR_M)  # type: ignore[arg-type]
    +np.linalg.svdvals(x=AR_f8)  # type: ignore[call-overload]
    +
    +np.linalg.cond(AR_O)  # type: ignore[arg-type]
    +np.linalg.cond(AR_f8, p="bob")  # type: ignore[arg-type]
    +
    +np.linalg.matrix_rank(AR_O)  # type: ignore[arg-type]
    +
    +np.linalg.pinv(AR_O)  # type: ignore[arg-type]
    +
    +np.linalg.slogdet(AR_O)  # type: ignore[arg-type]
    +
    +np.linalg.det(AR_O)  # type: ignore[arg-type]
    +
    +np.linalg.norm(AR_f8, ord="bob")  # type: ignore[call-overload]
    +
    +np.linalg.multi_dot([AR_M])  # type: ignore[list-item]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/ma.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/ma.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..084ae971bdd059ce6c5f03d5eeb561f6ec5c3e84
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/ma.pyi
    @@ -0,0 +1,155 @@
    +from typing import TypeAlias, TypeVar
    +
    +import numpy as np
    +import numpy.typing as npt
    +from numpy._typing import _AnyShape
    +
    +_ScalarT = TypeVar("_ScalarT", bound=np.generic)
    +MaskedArray: TypeAlias = np.ma.MaskedArray[_AnyShape, np.dtype[_ScalarT]]
    +
    +MAR_1d_f8: np.ma.MaskedArray[tuple[int], np.dtype[np.float64]]
    +MAR_b: MaskedArray[np.bool]
    +MAR_c: MaskedArray[np.complex128]
    +MAR_td64: MaskedArray[np.timedelta64]
    +
    +AR_b: npt.NDArray[np.bool]
    +
    +MAR_1d_f8.shape = (3, 1)  # type: ignore[assignment]
    +MAR_1d_f8.dtype = np.bool  # type: ignore[assignment]
    +
    +def invalid_recordmask_setter() -> None:
    +    # We make an inner function for this one to avoid the
    +    # `NoReturn` causing an early exit for type checkers.
    +    MAR_1d_f8.recordmask = [True]  # type: ignore[assignment]
    +
    +np.ma.min(MAR_1d_f8, axis=1.0)  # type: ignore[call-overload]
    +np.ma.min(MAR_1d_f8, keepdims=1.0)  # type: ignore[call-overload]
    +np.ma.min(MAR_1d_f8, out=1.0)  # type: ignore[call-overload]
    +np.ma.min(MAR_1d_f8, fill_value=lambda x: 27)  # type: ignore[call-overload]
    +
    +MAR_1d_f8.min(axis=1.0)  # type: ignore[call-overload]
    +MAR_1d_f8.min(keepdims=1.0)  # type: ignore[call-overload]
    +MAR_1d_f8.min(out=1.0)  # type: ignore[call-overload]
    +MAR_1d_f8.min(fill_value=lambda x: 27)  # type: ignore[call-overload]
    +
    +np.ma.max(MAR_1d_f8, axis=1.0)  # type: ignore[call-overload]
    +np.ma.max(MAR_1d_f8, keepdims=1.0)  # type: ignore[call-overload]
    +np.ma.max(MAR_1d_f8, out=1.0)  # type: ignore[call-overload]
    +np.ma.max(MAR_1d_f8, fill_value=lambda x: 27)  # type: ignore[call-overload]
    +
    +MAR_1d_f8.max(axis=1.0)  # type: ignore[call-overload]
    +MAR_1d_f8.max(keepdims=1.0)  # type: ignore[call-overload]
    +MAR_1d_f8.max(out=1.0)  # type: ignore[call-overload]
    +MAR_1d_f8.max(fill_value=lambda x: 27)  # type: ignore[call-overload]
    +
    +np.ma.ptp(MAR_1d_f8, axis=1.0)  # type: ignore[call-overload]
    +np.ma.ptp(MAR_1d_f8, keepdims=1.0)  # type: ignore[call-overload]
    +np.ma.ptp(MAR_1d_f8, out=1.0)  # type: ignore[call-overload]
    +np.ma.ptp(MAR_1d_f8, fill_value=lambda x: 27)  # type: ignore[call-overload]
    +
    +MAR_1d_f8.ptp(axis=1.0)  # type: ignore[call-overload]
    +MAR_1d_f8.ptp(keepdims=1.0)  # type: ignore[call-overload]
    +MAR_1d_f8.ptp(out=1.0)  # type: ignore[call-overload]
    +MAR_1d_f8.ptp(fill_value=lambda x: 27)  # type: ignore[call-overload]
    +
    +MAR_1d_f8.argmin(axis=1.0)  # type: ignore[call-overload]
    +MAR_1d_f8.argmin(keepdims=1.0)  # type: ignore[call-overload]
    +MAR_1d_f8.argmin(out=1.0)  # type: ignore[call-overload]
    +MAR_1d_f8.argmin(fill_value=lambda x: 27)  # type: ignore[call-overload]
    +
    +np.ma.argmin(MAR_1d_f8, axis=1.0)  # type: ignore[call-overload]
    +np.ma.argmin(MAR_1d_f8, axis=(1,))  # type: ignore[call-overload]
    +np.ma.argmin(MAR_1d_f8, keepdims=1.0)  # type: ignore[call-overload]
    +np.ma.argmin(MAR_1d_f8, out=1.0)  # type: ignore[call-overload]
    +np.ma.argmin(MAR_1d_f8, fill_value=lambda x: 27)  # type: ignore[call-overload]
    +
    +MAR_1d_f8.argmax(axis=1.0)  # type: ignore[call-overload]
    +MAR_1d_f8.argmax(keepdims=1.0)  # type: ignore[call-overload]
    +MAR_1d_f8.argmax(out=1.0)  # type: ignore[call-overload]
    +MAR_1d_f8.argmax(fill_value=lambda x: 27)  # type: ignore[call-overload]
    +
    +np.ma.argmax(MAR_1d_f8, axis=1.0)  # type: ignore[call-overload]
    +np.ma.argmax(MAR_1d_f8, axis=(0,))  # type: ignore[call-overload]
    +np.ma.argmax(MAR_1d_f8, keepdims=1.0)  # type: ignore[call-overload]
    +np.ma.argmax(MAR_1d_f8, out=1.0)  # type: ignore[call-overload]
    +np.ma.argmax(MAR_1d_f8, fill_value=lambda x: 27)  # type: ignore[call-overload]
    +
    +MAR_1d_f8.all(axis=1.0)  # type: ignore[call-overload]
    +MAR_1d_f8.all(keepdims=1.0)  # type: ignore[call-overload]
    +MAR_1d_f8.all(out=1.0)  # type: ignore[call-overload]
    +
    +MAR_1d_f8.any(axis=1.0)  # type: ignore[call-overload]
    +MAR_1d_f8.any(keepdims=1.0)  # type: ignore[call-overload]
    +MAR_1d_f8.any(out=1.0)  # type: ignore[call-overload]
    +
    +MAR_1d_f8.sort(axis=(0, 1))  # type: ignore[arg-type]
    +MAR_1d_f8.sort(axis=None)  # type: ignore[arg-type]
    +MAR_1d_f8.sort(kind="cabbage")  # type: ignore[arg-type]
    +MAR_1d_f8.sort(order=lambda: "cabbage")  # type: ignore[arg-type]
    +MAR_1d_f8.sort(endwith="cabbage")  # type: ignore[arg-type]
    +MAR_1d_f8.sort(fill_value=lambda: "cabbage")  # type: ignore[arg-type]
    +MAR_1d_f8.sort(stable="cabbage")  # type: ignore[arg-type]
    +MAR_1d_f8.sort(stable=True)  # type: ignore[arg-type]
    +
    +MAR_1d_f8.take(axis=1.0)  # type: ignore[call-overload]
    +MAR_1d_f8.take(out=1)  # type: ignore[call-overload]
    +MAR_1d_f8.take(mode="bob")  # type: ignore[call-overload]
    +
    +np.ma.take(None)  # type: ignore[call-overload]
    +np.ma.take(axis=1.0)  # type: ignore[call-overload]
    +np.ma.take(out=1)  # type: ignore[call-overload]
    +np.ma.take(mode="bob")  # type: ignore[call-overload]
    +
    +MAR_1d_f8.partition(["cabbage"])  # type: ignore[arg-type]
    +MAR_1d_f8.partition(axis=(0, 1))  # type: ignore[arg-type, call-arg]
    +MAR_1d_f8.partition(kind="cabbage")  # type: ignore[arg-type, call-arg]
    +MAR_1d_f8.partition(order=lambda: "cabbage")  # type: ignore[arg-type, call-arg]
    +MAR_1d_f8.partition(AR_b)  # type: ignore[arg-type]
    +
    +MAR_1d_f8.argpartition(["cabbage"])  # type: ignore[arg-type]
    +MAR_1d_f8.argpartition(axis=(0, 1))  # type: ignore[arg-type, call-arg]
    +MAR_1d_f8.argpartition(kind="cabbage")  # type: ignore[arg-type, call-arg]
    +MAR_1d_f8.argpartition(order=lambda: "cabbage")  # type: ignore[arg-type, call-arg]
    +MAR_1d_f8.argpartition(AR_b)  # type: ignore[arg-type]
    +
    +np.ma.ndim(lambda: "lambda")  # type: ignore[arg-type]
    +
    +np.ma.size(AR_b, axis="0")  # type: ignore[arg-type]
    +
    +MAR_1d_f8 >= (lambda x: "mango")  # type: ignore[operator]
    +MAR_1d_f8 > (lambda x: "mango")  # type: ignore[operator]
    +MAR_1d_f8 <= (lambda x: "mango")  # type: ignore[operator]
    +MAR_1d_f8 < (lambda x: "mango")  # type: ignore[operator]
    +
    +MAR_1d_f8.count(axis=0.)  # type: ignore[call-overload]
    +
    +np.ma.count(MAR_1d_f8, axis=0.)  # type: ignore[call-overload]
    +
    +MAR_1d_f8.put(4, 999, mode="flip")  # type: ignore[arg-type]
    +
    +np.ma.put(MAR_1d_f8, 4, 999, mode="flip")  # type: ignore[arg-type]
    +
    +np.ma.put([1, 1, 3], 0, 999)  # type: ignore[arg-type]
    +
    +np.ma.compressed(lambda: "compress me")  # type: ignore[call-overload]
    +
    +np.ma.allequal(MAR_1d_f8, [1, 2, 3], fill_value=1.5)  # type: ignore[arg-type]
    +
    +np.ma.allclose(MAR_1d_f8, [1, 2, 3], masked_equal=4.5)  # type: ignore[arg-type]
    +np.ma.allclose(MAR_1d_f8, [1, 2, 3], rtol=".4")  # type: ignore[arg-type]
    +np.ma.allclose(MAR_1d_f8, [1, 2, 3], atol=".5")  # type: ignore[arg-type]
    +
    +MAR_1d_f8.__setmask__("mask")  # type: ignore[arg-type]
    +
    +MAR_b *= 2  # type: ignore[arg-type]
    +MAR_c //= 2  # type: ignore[misc]
    +MAR_td64 **= 2  # type: ignore[misc]
    +
    +MAR_1d_f8.swapaxes(axis1=1, axis2=0)  # type: ignore[call-arg]
    +
    +MAR_1d_f8.argsort(axis=(1, 0))  # type: ignore[arg-type]
    +
    +np.ma.MaskedArray(np.array([1, 2, 3]), keep_mask="yes")  # type: ignore[call-overload]
    +np.ma.MaskedArray(np.array([1, 2, 3]), subok=None)  # type: ignore[call-overload]
    +np.ma.MaskedArray(np.array([1, 2, 3]), ndim=None)  # type: ignore[call-overload]
    +np.ma.MaskedArray(np.array([1, 2, 3]), order="Corinthian")  # type: ignore[call-overload]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/memmap.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/memmap.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..3a4fc7df068961736ee8cf05412d5bcea5ad547a
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/memmap.pyi
    @@ -0,0 +1,5 @@
    +import numpy as np
    +
    +with open("file.txt", "r") as f:
    +    np.memmap(f)  # type: ignore[call-overload]
    +np.memmap("test.txt", shape=[10, 5])  # type: ignore[call-overload]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/modules.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/modules.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..c12a182807d395fd9d8c94882b826c23de703932
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/modules.pyi
    @@ -0,0 +1,17 @@
    +import numpy as np
    +
    +np.testing.bob  # type: ignore[attr-defined]
    +np.bob  # type: ignore[attr-defined]
    +
    +# Stdlib modules in the namespace by accident
    +np.warnings  # type: ignore[attr-defined]
    +np.sys  # type: ignore[attr-defined]
    +np.os  # type: ignore[attr-defined]
    +np.math  # type: ignore[attr-defined]
    +
    +# Public sub-modules that are not imported to their parent module by default;
    +# e.g. one must first execute `import numpy.lib.recfunctions`
    +np.lib.recfunctions  # type: ignore[attr-defined]
    +
    +np.__deprecated_attrs__  # type: ignore[attr-defined]
    +np.__expired_functions__  # type: ignore[attr-defined]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/multiarray.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/multiarray.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..51128dfbf6f785a96931cb8ee5d4df6a7574802d
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/multiarray.pyi
    @@ -0,0 +1,52 @@
    +import numpy as np
    +import numpy.typing as npt
    +
    +i8: np.int64
    +
    +AR_b: npt.NDArray[np.bool]
    +AR_u1: npt.NDArray[np.uint8]
    +AR_i8: npt.NDArray[np.int64]
    +AR_f8: npt.NDArray[np.float64]
    +AR_M: npt.NDArray[np.datetime64]
    +
    +M: np.datetime64
    +
    +AR_LIKE_f: list[float]
    +
    +def func(a: int) -> None: ...
    +
    +np.where(AR_b, 1)  # type: ignore[call-overload]
    +
    +np.can_cast(AR_f8, 1)  # type: ignore[arg-type]
    +
    +np.vdot(AR_M, AR_M)  # type: ignore[arg-type]
    +
    +np.copyto(AR_LIKE_f, AR_f8)  # type: ignore[arg-type]
    +
    +np.putmask(AR_LIKE_f, [True, True, False], 1.5)  # type: ignore[arg-type]
    +
    +np.packbits(AR_f8)  # type: ignore[arg-type]
    +np.packbits(AR_u1, bitorder=">")  # type: ignore[call-overload]
    +
    +np.unpackbits(AR_i8)  # type: ignore[arg-type]
    +np.unpackbits(AR_u1, bitorder=">")  # type: ignore[call-overload]
    +
    +np.shares_memory(1, 1, max_work=i8)  # type: ignore[arg-type]
    +np.may_share_memory(1, 1, max_work=i8)  # type: ignore[arg-type]
    +
    +np.arange(stop=10)  # type: ignore[call-overload]
    +
    +np.datetime_data(int)  # type: ignore[arg-type]
    +
    +np.busday_offset("2012", 10)  # type: ignore[call-overload]
    +
    +np.datetime_as_string("2012")  # type: ignore[call-overload]
    +
    +np.char.compare_chararrays("a", b"a", "==", False)  # type: ignore[call-overload]
    +
    +np.nested_iters([AR_i8, AR_i8])  # type: ignore[call-arg]
    +np.nested_iters([AR_i8, AR_i8], 0)  # type: ignore[arg-type]
    +np.nested_iters([AR_i8, AR_i8], [0])  # type: ignore[list-item]
    +np.nested_iters([AR_i8, AR_i8], [[0], [1]], flags=["test"])  # type: ignore[list-item]
    +np.nested_iters([AR_i8, AR_i8], [[0], [1]], op_flags=[["test"]])  # type: ignore[list-item]
    +np.nested_iters([AR_i8, AR_i8], [[0], [1]], buffersize=1.0)  # type: ignore[arg-type]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/ndarray.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/ndarray.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..2aeec0883e3ff1463c36d64ec233af91d83220bc
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/ndarray.pyi
    @@ -0,0 +1,11 @@
    +import numpy as np
    +
    +# Ban setting dtype since mutating the type of the array in place
    +# makes having ndarray be generic over dtype impossible. Generally
    +# users should use `ndarray.view` in this situation anyway. See
    +#
    +# https://github.com/numpy/numpy-stubs/issues/7
    +#
    +# for more context.
    +float_array = np.array([1.0])
    +float_array.dtype = np.bool  # type: ignore[assignment, misc]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/ndarray_misc.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/ndarray_misc.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..29418930061c3753882b36457d9491ea130cd25c
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/ndarray_misc.pyi
    @@ -0,0 +1,49 @@
    +"""
    +Tests for miscellaneous (non-magic) ``np.ndarray``/``np.generic`` methods.
    +
    +More extensive tests are performed for the methods'
    +function-based counterpart in `../from_numeric.py`.
    +
    +"""
    +from typing import Never
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +f8: np.float64
    +AR_f8: npt.NDArray[np.float64]
    +AR_M: npt.NDArray[np.datetime64]
    +AR_b: npt.NDArray[np.bool]
    +
    +ctypes_obj = AR_f8.ctypes
    +
    +f8.argpartition(0)  # type: ignore[attr-defined]
    +f8.partition(0)  # type: ignore[attr-defined]
    +f8.dot(1)  # type: ignore[attr-defined]
    +
    +# NOTE: The following functions retur `Never`, causing mypy to stop analysis at that
    +# point, which we circumvent by wrapping them in a function.
    +
    +def f8_diagonal(x: np.float64) -> Never:
    +    return x.diagonal()  # type: ignore[misc]
    +
    +def f8_nonzero(x: np.float64) -> Never:
    +    return x.nonzero()  # type: ignore[misc]
    +
    +def f8_setfield(x: np.float64) -> Never:
    +    return x.setfield(2, np.float64)  # type: ignore[misc]
    +
    +def f8_sort(x: np.float64) -> Never:
    +    return x.sort()  # type: ignore[misc]
    +
    +def f8_trace(x: np.float64) -> Never:
    +    return x.trace()  # type: ignore[misc]
    +
    +AR_M.__complex__()  # type: ignore[misc]
    +AR_b.__index__()  # type: ignore[misc]
    +
    +AR_f8[1.5]  # type: ignore[call-overload]
    +AR_f8["field_a"]  # type: ignore[call-overload]
    +AR_f8[["field_a", "field_b"]]  # type: ignore[index]
    +
    +AR_f8.__array_finalize__(object())  # type: ignore[arg-type]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/nditer.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/nditer.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..fae728da454e76a5459012fd6af66345ffaa5422
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/nditer.pyi
    @@ -0,0 +1,8 @@
    +import numpy as np
    +
    +class Test(np.nditer): ...  # type: ignore[misc]
    +
    +np.nditer([0, 1], flags=["test"])  # type: ignore[list-item]
    +np.nditer([0, 1], op_flags=[["test"]])  # type: ignore[list-item]
    +np.nditer([0, 1], itershape=(1.0,))  # type: ignore[arg-type]
    +np.nditer([0, 1], buffersize=1.0)  # type: ignore[call-overload]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/nested_sequence.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/nested_sequence.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..1004a36accc7958835c2bb8fa2dae9d8e0135016
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/nested_sequence.pyi
    @@ -0,0 +1,17 @@
    +from collections.abc import Sequence
    +
    +from numpy._typing import _NestedSequence
    +
    +a: Sequence[float]
    +b: list[complex]
    +c: tuple[str, ...]
    +d: int
    +e: str
    +
    +def func(a: _NestedSequence[int]) -> None: ...
    +
    +reveal_type(func(a))  # type: ignore[arg-type, misc]
    +reveal_type(func(b))  # type: ignore[arg-type, misc]
    +reveal_type(func(c))  # type: ignore[arg-type, misc]
    +reveal_type(func(d))  # type: ignore[arg-type, misc]
    +reveal_type(func(e))  # type: ignore[arg-type, misc]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/npyio.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/npyio.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..e20be3a2a2470ae8d233bb5a099c0336236c9ec1
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/npyio.pyi
    @@ -0,0 +1,24 @@
    +import pathlib
    +from typing import IO
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +str_path: str
    +bytes_path: bytes
    +pathlib_path: pathlib.Path
    +str_file: IO[str]
    +AR_i8: npt.NDArray[np.int64]
    +
    +np.load(str_file)  # type: ignore[arg-type]
    +
    +np.save(bytes_path, AR_i8)  # type: ignore[arg-type]
    +np.save(str_path, AR_i8, fix_imports=True)  # type: ignore[call-arg]
    +
    +np.savez(bytes_path, AR_i8)  # type: ignore[arg-type]
    +
    +np.savez_compressed(bytes_path, AR_i8)  # type: ignore[arg-type]
    +
    +np.loadtxt(bytes_path)  # type: ignore[arg-type]
    +
    +np.fromregex(bytes_path, ".", np.int64)  # type: ignore[call-overload]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/numerictypes.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/numerictypes.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..a1fd47a6f479039639bf188399c8cd001f45d6c0
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/numerictypes.pyi
    @@ -0,0 +1,5 @@
    +import numpy as np
    +
    +np.isdtype(1, np.int64)  # type: ignore[arg-type]
    +
    +np.issubdtype(1, np.int64)  # type: ignore[arg-type]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/random.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/random.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..1abf4b77653c5f49fa095e0785949aef16a34ced
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/random.pyi
    @@ -0,0 +1,62 @@
    +import numpy as np
    +import numpy.typing as npt
    +
    +SEED_FLOAT: float = 457.3
    +SEED_ARR_FLOAT: npt.NDArray[np.float64] = np.array([1.0, 2, 3, 4])
    +SEED_ARRLIKE_FLOAT: list[float] = [1.0, 2.0, 3.0, 4.0]
    +SEED_SEED_SEQ: np.random.SeedSequence = np.random.SeedSequence(0)
    +SEED_STR: str = "String seeding not allowed"
    +
    +# default rng
    +np.random.default_rng(SEED_FLOAT)  # type: ignore[arg-type]
    +np.random.default_rng(SEED_ARR_FLOAT)  # type: ignore[arg-type]
    +np.random.default_rng(SEED_ARRLIKE_FLOAT)  # type: ignore[arg-type]
    +np.random.default_rng(SEED_STR)  # type: ignore[arg-type]
    +
    +# Seed Sequence
    +np.random.SeedSequence(SEED_FLOAT)  # type: ignore[arg-type]
    +np.random.SeedSequence(SEED_ARR_FLOAT)  # type: ignore[arg-type]
    +np.random.SeedSequence(SEED_ARRLIKE_FLOAT)  # type: ignore[arg-type]
    +np.random.SeedSequence(SEED_SEED_SEQ)  # type: ignore[arg-type]
    +np.random.SeedSequence(SEED_STR)  # type: ignore[arg-type]
    +
    +seed_seq: np.random.bit_generator.SeedSequence = np.random.SeedSequence()
    +seed_seq.spawn(11.5)  # type: ignore[arg-type]
    +seed_seq.generate_state(3.14)  # type: ignore[arg-type]
    +seed_seq.generate_state(3, np.uint8)  # type: ignore[arg-type]
    +seed_seq.generate_state(3, "uint8")  # type: ignore[arg-type]
    +seed_seq.generate_state(3, "u1")  # type: ignore[arg-type]
    +seed_seq.generate_state(3, np.uint16)  # type: ignore[arg-type]
    +seed_seq.generate_state(3, "uint16")  # type: ignore[arg-type]
    +seed_seq.generate_state(3, "u2")  # type: ignore[arg-type]
    +seed_seq.generate_state(3, np.int32)  # type: ignore[arg-type]
    +seed_seq.generate_state(3, "int32")  # type: ignore[arg-type]
    +seed_seq.generate_state(3, "i4")  # type: ignore[arg-type]
    +
    +# Bit Generators
    +np.random.MT19937(SEED_FLOAT)  # type: ignore[arg-type]
    +np.random.MT19937(SEED_ARR_FLOAT)  # type: ignore[arg-type]
    +np.random.MT19937(SEED_ARRLIKE_FLOAT)  # type: ignore[arg-type]
    +np.random.MT19937(SEED_STR)  # type: ignore[arg-type]
    +
    +np.random.PCG64(SEED_FLOAT)  # type: ignore[arg-type]
    +np.random.PCG64(SEED_ARR_FLOAT)  # type: ignore[arg-type]
    +np.random.PCG64(SEED_ARRLIKE_FLOAT)  # type: ignore[arg-type]
    +np.random.PCG64(SEED_STR)  # type: ignore[arg-type]
    +
    +np.random.Philox(SEED_FLOAT)  # type: ignore[arg-type]
    +np.random.Philox(SEED_ARR_FLOAT)  # type: ignore[arg-type]
    +np.random.Philox(SEED_ARRLIKE_FLOAT)  # type: ignore[arg-type]
    +np.random.Philox(SEED_STR)  # type: ignore[arg-type]
    +
    +np.random.SFC64(SEED_FLOAT)  # type: ignore[arg-type]
    +np.random.SFC64(SEED_ARR_FLOAT)  # type: ignore[arg-type]
    +np.random.SFC64(SEED_ARRLIKE_FLOAT)  # type: ignore[arg-type]
    +np.random.SFC64(SEED_STR)  # type: ignore[arg-type]
    +
    +# Generator
    +np.random.Generator(None)  # type: ignore[arg-type]
    +np.random.Generator(12333283902830213)  # type: ignore[arg-type]
    +np.random.Generator("OxFEEDF00D")  # type: ignore[arg-type]
    +np.random.Generator([123, 234])  # type: ignore[arg-type]
    +np.random.Generator(np.array([123, 234], dtype="u4"))  # type: ignore[arg-type]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/rec.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/rec.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..c9d43dd2ff1fc48cfb99630d27481de929100077
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/rec.pyi
    @@ -0,0 +1,17 @@
    +import numpy as np
    +import numpy.typing as npt
    +
    +AR_i8: npt.NDArray[np.int64]
    +
    +np.rec.fromarrays(1)  # type: ignore[call-overload]
    +np.rec.fromarrays([1, 2, 3], dtype=[("f8", "f8")], formats=["f8", "f8"])  # type: ignore[call-overload]
    +
    +np.rec.fromrecords(AR_i8)  # type: ignore[arg-type]
    +np.rec.fromrecords([(1.5,)], dtype=[("f8", "f8")], formats=["f8", "f8"])  # type: ignore[call-overload]
    +
    +np.rec.fromstring("string", dtype=[("f8", "f8")])  # type: ignore[call-overload]
    +np.rec.fromstring(b"bytes")  # type: ignore[call-overload]
    +np.rec.fromstring(b"(1.5,)", dtype=[("f8", "f8")], formats=["f8", "f8"])  # type: ignore[call-overload]
    +
    +with open("test", "r") as f:
    +    np.rec.fromfile(f, dtype=[("f8", "f8")])  # type: ignore[call-overload]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/scalars.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/scalars.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..018a88e652ae7c5d39119fa18fbe525b9556d373
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/scalars.pyi
    @@ -0,0 +1,86 @@
    +import numpy as np
    +
    +f2: np.float16
    +f8: np.float64
    +c8: np.complex64
    +
    +# Construction
    +
    +np.float32(3j)  # type: ignore[arg-type]
    +
    +# Technically the following examples are valid NumPy code. But they
    +# are not considered a best practice, and people who wish to use the
    +# stubs should instead do
    +#
    +# np.array([1.0, 0.0, 0.0], dtype=np.float32)
    +# np.array([], dtype=np.complex64)
    +#
    +# See e.g. the discussion on the mailing list
    +#
    +# https://mail.python.org/pipermail/numpy-discussion/2020-April/080566.html
    +#
    +# and the issue
    +#
    +# https://github.com/numpy/numpy-stubs/issues/41
    +#
    +# for more context.
    +np.float32([1.0, 0.0, 0.0])  # type: ignore[arg-type]
    +np.complex64([])  # type: ignore[call-overload]
    +
    +# TODO: protocols (can't check for non-existent protocols w/ __getattr__)
    +
    +np.datetime64(0)  # type: ignore[call-overload]
    +
    +class A:
    +    def __float__(self) -> float: ...
    +
    +np.int8(A())  # type: ignore[arg-type]
    +np.int16(A())  # type: ignore[arg-type]
    +np.int32(A())  # type: ignore[arg-type]
    +np.int64(A())  # type: ignore[arg-type]
    +np.uint8(A())  # type: ignore[arg-type]
    +np.uint16(A())  # type: ignore[arg-type]
    +np.uint32(A())  # type: ignore[arg-type]
    +np.uint64(A())  # type: ignore[arg-type]
    +
    +np.void("test")  # type: ignore[call-overload]
    +np.void("test", dtype=None)  # type: ignore[call-overload]
    +
    +np.generic(1)  # type: ignore[abstract]
    +np.number(1)  # type: ignore[abstract]
    +np.integer(1)  # type: ignore[abstract]
    +np.inexact(1)  # type: ignore[abstract]
    +np.character("test")  # type: ignore[abstract]
    +np.flexible(b"test")  # type: ignore[abstract]
    +
    +np.float64(value=0.0)  # type: ignore[call-arg]
    +np.int64(value=0)  # type: ignore[call-arg]
    +np.uint64(value=0)  # type: ignore[call-arg]
    +np.complex128(value=0.0j)  # type: ignore[call-overload]
    +np.str_(value="bob")  # type: ignore[call-overload]
    +np.bytes_(value=b"test")  # type: ignore[call-overload]
    +np.void(value=b"test")  # type: ignore[call-overload]
    +np.bool(value=True)  # type: ignore[call-overload]
    +np.datetime64(value="2019")  # type: ignore[call-overload]
    +np.timedelta64(value=0)  # type: ignore[call-overload]
    +
    +np.bytes_(b"hello", encoding="utf-8")  # type: ignore[call-overload]
    +np.str_("hello", encoding="utf-8")  # type: ignore[call-overload]
    +
    +f8.item(1)  # type: ignore[call-overload]
    +f8.item((0, 1))  # type: ignore[arg-type]
    +f8.squeeze(axis=1)  # type: ignore[arg-type]
    +f8.squeeze(axis=(0, 1))  # type: ignore[arg-type]
    +f8.transpose(1)  # type: ignore[arg-type]
    +
    +def func(a: np.float32) -> None: ...
    +
    +func(f2)  # type: ignore[arg-type]
    +func(f8)  # type: ignore[arg-type]
    +
    +c8.__getnewargs__()  # type: ignore[attr-defined]
    +f2.__getnewargs__()  # type: ignore[attr-defined]
    +f2.hex()  # type: ignore[attr-defined]
    +np.float16.fromhex("0x0.0p+0")  # type: ignore[attr-defined]
    +f2.__trunc__()  # type: ignore[attr-defined]
    +f2.__getformat__("float")  # type: ignore[attr-defined]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/shape.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/shape.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..a024d7bda27367395ebf028b65a75b0b0bbfd7b6
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/shape.pyi
    @@ -0,0 +1,7 @@
    +from typing import Any
    +
    +import numpy as np
    +
    +# test bounds of _ShapeT_co
    +
    +np.ndarray[tuple[str, str], Any]  # type: ignore[type-var]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/shape_base.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/shape_base.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..652b24ba311e35411fd0fc11f3e79c5d90a6dac4
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/shape_base.pyi
    @@ -0,0 +1,8 @@
    +import numpy as np
    +
    +class DTypeLike:
    +    dtype: np.dtype[np.int_]
    +
    +dtype_like: DTypeLike
    +
    +np.expand_dims(dtype_like, (5, 10))  # type: ignore[call-overload]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/stride_tricks.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/stride_tricks.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..7f9a26b9692438eef717972fcc0024d2ab4dbcb9
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/stride_tricks.pyi
    @@ -0,0 +1,9 @@
    +import numpy as np
    +import numpy.typing as npt
    +
    +AR_f8: npt.NDArray[np.float64]
    +
    +np.lib.stride_tricks.as_strided(AR_f8, shape=8)  # type: ignore[call-overload]
    +np.lib.stride_tricks.as_strided(AR_f8, strides=8)  # type: ignore[call-overload]
    +
    +np.lib.stride_tricks.sliding_window_view(AR_f8, axis=(1,))  # type: ignore[call-overload]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/strings.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/strings.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..328a521ae67957086e9d490975634cda448e5497
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/strings.pyi
    @@ -0,0 +1,52 @@
    +import numpy as np
    +import numpy.typing as npt
    +
    +AR_U: npt.NDArray[np.str_]
    +AR_S: npt.NDArray[np.bytes_]
    +
    +np.strings.equal(AR_U, AR_S)  # type: ignore[arg-type]
    +np.strings.not_equal(AR_U, AR_S)  # type: ignore[arg-type]
    +
    +np.strings.greater_equal(AR_U, AR_S)  # type: ignore[arg-type]
    +np.strings.less_equal(AR_U, AR_S)  # type: ignore[arg-type]
    +np.strings.greater(AR_U, AR_S)  # type: ignore[arg-type]
    +np.strings.less(AR_U, AR_S)  # type: ignore[arg-type]
    +
    +np.strings.encode(AR_S)  # type: ignore[arg-type]
    +np.strings.decode(AR_U)  # type: ignore[arg-type]
    +
    +np.strings.lstrip(AR_U, b"a")  # type: ignore[arg-type]
    +np.strings.lstrip(AR_S, "a")  # type: ignore[arg-type]
    +np.strings.strip(AR_U, b"a")  # type: ignore[arg-type]
    +np.strings.strip(AR_S, "a")  # type: ignore[arg-type]
    +np.strings.rstrip(AR_U, b"a")  # type: ignore[arg-type]
    +np.strings.rstrip(AR_S, "a")  # type: ignore[arg-type]
    +
    +np.strings.partition(AR_U, b"a")  # type: ignore[arg-type]
    +np.strings.partition(AR_S, "a")  # type: ignore[arg-type]
    +np.strings.rpartition(AR_U, b"a")  # type: ignore[arg-type]
    +np.strings.rpartition(AR_S, "a")  # type: ignore[arg-type]
    +
    +np.strings.count(AR_U, b"a", [1, 2, 3], [1, 2, 3])  # type: ignore[arg-type]
    +np.strings.count(AR_S, "a", 0, 9)  # type: ignore[arg-type]
    +
    +np.strings.endswith(AR_U, b"a", [1, 2, 3], [1, 2, 3])  # type: ignore[arg-type]
    +np.strings.endswith(AR_S, "a", 0, 9)  # type: ignore[arg-type]
    +np.strings.startswith(AR_U, b"a", [1, 2, 3], [1, 2, 3])  # type: ignore[arg-type]
    +np.strings.startswith(AR_S, "a", 0, 9)  # type: ignore[arg-type]
    +
    +np.strings.find(AR_U, b"a", [1, 2, 3], [1, 2, 3])  # type: ignore[arg-type]
    +np.strings.find(AR_S, "a", 0, 9)  # type: ignore[arg-type]
    +np.strings.rfind(AR_U, b"a", [1, 2, 3], [1, 2, 3])  # type: ignore[arg-type]
    +np.strings.rfind(AR_S, "a", 0, 9)  # type: ignore[arg-type]
    +
    +np.strings.index(AR_U, b"a", start=[1, 2, 3])  # type: ignore[arg-type]
    +np.strings.index(AR_S, "a", end=9)  # type: ignore[arg-type]
    +np.strings.rindex(AR_U, b"a", start=[1, 2, 3])  # type: ignore[arg-type]
    +np.strings.rindex(AR_S, "a", end=9)  # type: ignore[arg-type]
    +
    +np.strings.isdecimal(AR_S)  # type: ignore[arg-type]
    +np.strings.isnumeric(AR_S)  # type: ignore[arg-type]
    +
    +np.strings.replace(AR_U, b"_", b"-", 10)  # type: ignore[arg-type]
    +np.strings.replace(AR_S, "_", "-", 1)  # type: ignore[arg-type]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/testing.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/testing.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..517062c4c95261dbde10cf52e299ffc1d0cdd8b3
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/testing.pyi
    @@ -0,0 +1,28 @@
    +import numpy as np
    +import numpy.typing as npt
    +
    +AR_U: npt.NDArray[np.str_]
    +
    +def func(x: object) -> bool: ...
    +
    +np.testing.assert_(True, msg=1)  # type: ignore[arg-type]
    +np.testing.build_err_msg(1, "test")  # type: ignore[arg-type]
    +np.testing.assert_almost_equal(AR_U, AR_U)  # type: ignore[arg-type]
    +np.testing.assert_approx_equal([1, 2, 3], [1, 2, 3])  # type: ignore[arg-type]
    +np.testing.assert_array_almost_equal(AR_U, AR_U)  # type: ignore[arg-type]
    +np.testing.assert_array_less(AR_U, AR_U)  # type: ignore[arg-type]
    +np.testing.assert_string_equal(b"a", b"a")  # type: ignore[arg-type]
    +
    +np.testing.assert_raises(expected_exception=TypeError, callable=func)  # type: ignore[call-overload]
    +np.testing.assert_raises_regex(expected_exception=TypeError, expected_regex="T", callable=func)  # type: ignore[call-overload]
    +
    +np.testing.assert_allclose(AR_U, AR_U)  # type: ignore[arg-type]
    +np.testing.assert_array_almost_equal_nulp(AR_U, AR_U)  # type: ignore[arg-type]
    +np.testing.assert_array_max_ulp(AR_U, AR_U)  # type: ignore[arg-type]
    +
    +np.testing.assert_warns(RuntimeWarning, func)  # type: ignore[call-overload]
    +np.testing.assert_no_warnings(func=func)  # type: ignore[call-overload]
    +np.testing.assert_no_warnings(func)  # type: ignore[call-overload]
    +np.testing.assert_no_warnings(func, y=None)  # type: ignore[call-overload]
    +
    +np.testing.assert_no_gc_cycles(func=func)  # type: ignore[call-overload]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/twodim_base.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/twodim_base.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..473419cecef1bd101d5e98db0fe52ddf06403f10
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/twodim_base.pyi
    @@ -0,0 +1,39 @@
    +from typing import type_check_only
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +_0d_bool: np.bool
    +_nd_bool: npt.NDArray[np.bool]
    +_nd_td64: npt.NDArray[np.timedelta64]
    +_to_2d_bool: list[list[bool]]
    +
    +@type_check_only
    +def func1(ar: np.ndarray, a: int) -> npt.NDArray[np.str_]: ...
    +@type_check_only
    +def func2(ar: np.ndarray, a: float) -> float: ...
    +
    +###
    +
    +np.eye(10, M=20.0)  # type: ignore[call-overload]
    +np.eye(10, k=2.5, dtype=int)  # type: ignore[call-overload]
    +
    +np.diag(_nd_bool, k=0.5)  # type: ignore[call-overload]
    +np.diagflat(_nd_bool, k=0.5)  # type: ignore[call-overload]
    +
    +np.tri(10, M=20.0)  # type: ignore[call-overload]
    +np.tri(10, k=2.5, dtype=int)  # type: ignore[call-overload]
    +
    +np.tril(_nd_bool, k=0.5)  # type: ignore[call-overload]
    +np.triu(_nd_bool, k=0.5)  # type: ignore[call-overload]
    +
    +np.vander(_nd_td64)  # type: ignore[type-var]
    +
    +np.histogram2d(_nd_td64)  # type: ignore[call-overload]
    +
    +np.mask_indices(10, func1)  # type: ignore[arg-type]
    +np.mask_indices(10, func2, 10.5)  # type: ignore[arg-type]
    +
    +np.tril_indices(3.14)  # type: ignore[arg-type]
    +
    +np.tril_indices_from(_to_2d_bool)  # type: ignore[arg-type]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/type_check.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/type_check.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..8b68e996304ccda7359b1a9756865eca754aa27a
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/type_check.pyi
    @@ -0,0 +1,12 @@
    +import numpy as np
    +
    +DTYPE_i8: np.dtype[np.int64]
    +
    +np.mintypecode(DTYPE_i8)  # type: ignore[arg-type]
    +np.iscomplexobj(DTYPE_i8)  # type: ignore[arg-type]
    +np.isrealobj(DTYPE_i8)  # type: ignore[arg-type]
    +
    +np.typename(DTYPE_i8)  # type: ignore[call-overload]
    +np.typename("invalid")  # type: ignore[call-overload]
    +
    +np.common_type(np.timedelta64())  # type: ignore[arg-type]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/ufunc_config.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/ufunc_config.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..c67b6a3acf989cda443dc1b82826cd9086202190
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/ufunc_config.pyi
    @@ -0,0 +1,21 @@
    +"""Typing tests for `numpy._core._ufunc_config`."""
    +
    +import numpy as np
    +
    +def func1(a: str, b: int, c: float) -> None: ...
    +def func2(a: str, *, b: int) -> None: ...
    +
    +class Write1:
    +    def write1(self, a: str) -> None: ...
    +
    +class Write2:
    +    def write(self, a: str, b: str) -> None: ...
    +
    +class Write3:
    +    def write(self, *, a: str) -> None: ...
    +
    +np.seterrcall(func1)  # type: ignore[arg-type]
    +np.seterrcall(func2)  # type: ignore[arg-type]
    +np.seterrcall(Write1())  # type: ignore[arg-type]
    +np.seterrcall(Write2())  # type: ignore[arg-type]
    +np.seterrcall(Write3())  # type: ignore[arg-type]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/ufunclike.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/ufunclike.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..e556e409ebbc90f1e57c096d53f656850c041b0f
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/ufunclike.pyi
    @@ -0,0 +1,21 @@
    +import numpy as np
    +import numpy.typing as npt
    +
    +AR_c: npt.NDArray[np.complex128]
    +AR_m: npt.NDArray[np.timedelta64]
    +AR_M: npt.NDArray[np.datetime64]
    +AR_O: npt.NDArray[np.object_]
    +
    +np.fix(AR_c)  # type: ignore[arg-type]
    +np.fix(AR_m)  # type: ignore[arg-type]
    +np.fix(AR_M)  # type: ignore[arg-type]
    +
    +np.isposinf(AR_c)  # type: ignore[arg-type]
    +np.isposinf(AR_m)  # type: ignore[arg-type]
    +np.isposinf(AR_M)  # type: ignore[arg-type]
    +np.isposinf(AR_O)  # type: ignore[arg-type]
    +
    +np.isneginf(AR_c)  # type: ignore[arg-type]
    +np.isneginf(AR_m)  # type: ignore[arg-type]
    +np.isneginf(AR_M)  # type: ignore[arg-type]
    +np.isneginf(AR_O)  # type: ignore[arg-type]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/ufuncs.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/ufuncs.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..1b1628d7da4431c5fc30c051a413052cbe6969ac
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/ufuncs.pyi
    @@ -0,0 +1,17 @@
    +import numpy as np
    +import numpy.typing as npt
    +
    +AR_f8: npt.NDArray[np.float64]
    +
    +np.sin.nin + "foo"  # type: ignore[operator]
    +np.sin(1, foo="bar")  # type: ignore[call-overload]
    +
    +np.abs(None)  # type: ignore[call-overload]
    +
    +np.add(1, 1, 1)  # type: ignore[call-overload]
    +np.add(1, 1, axis=0)  # type: ignore[call-overload]
    +
    +np.matmul(AR_f8, AR_f8, where=True)  # type: ignore[call-overload]
    +
    +np.frexp(AR_f8, out=None)  # type: ignore[call-overload]
    +np.frexp(AR_f8, out=AR_f8)  # type: ignore[call-overload]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/warnings_and_errors.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/warnings_and_errors.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..8ba34f6dfa3ec31d2d363f88cd5a87e4b8ad69e8
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/fail/warnings_and_errors.pyi
    @@ -0,0 +1,5 @@
    +import numpy.exceptions as ex
    +
    +ex.AxisError(1.0)  # type: ignore[call-overload]
    +ex.AxisError(1, ndim=2.0)  # type: ignore[call-overload]
    +ex.AxisError(2, msg_prefix=404)  # type: ignore[call-overload]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/misc/extended_precision.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/misc/extended_precision.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..7978faf4d5bd1b1fba35fc84146ee42e25c8405a
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/misc/extended_precision.pyi
    @@ -0,0 +1,9 @@
    +from typing import assert_type
    +
    +import numpy as np
    +from numpy._typing import _96Bit, _128Bit
    +
    +assert_type(np.float96(), np.floating[_96Bit])
    +assert_type(np.float128(), np.floating[_128Bit])
    +assert_type(np.complex192(), np.complexfloating[_96Bit, _96Bit])
    +assert_type(np.complex256(), np.complexfloating[_128Bit, _128Bit])
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/mypy.ini b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/mypy.ini
    new file mode 100644
    index 0000000000000000000000000000000000000000..4aa465ae087bc2be5013d282a979b9a48fb9a485
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/mypy.ini
    @@ -0,0 +1,8 @@
    +[mypy]
    +strict = True
    +enable_error_code = deprecated, ignore-without-code, truthy-bool
    +disallow_any_unimported = True
    +allow_redefinition_new = True
    +local_partial_types = True
    +show_absolute_path = True
    +pretty = True
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/arithmetic.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/arithmetic.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..e347ec096e2142cfa16c8a8d31ca63c87bd612e0
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/arithmetic.py
    @@ -0,0 +1,614 @@
    +from __future__ import annotations
    +
    +from typing import Any, cast
    +
    +import pytest
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +c16 = np.complex128(1)
    +f8 = np.float64(1)
    +i8 = np.int64(1)
    +u8 = np.uint64(1)
    +
    +c8 = np.complex64(1)
    +f4 = np.float32(1)
    +i4 = np.int32(1)
    +u4 = np.uint32(1)
    +
    +dt = np.datetime64(1, "D")
    +td = np.timedelta64(1, "D")
    +
    +b_ = np.bool(1)
    +
    +b = bool(1)
    +c = complex(1)
    +f = float(1)
    +i = 1
    +
    +
    +class Object:
    +    def __array__(self, dtype: np.typing.DTypeLike | None = None,
    +                  copy: bool | None = None) -> np.ndarray[Any, np.dtype[np.object_]]:
    +        ret = np.empty((), dtype=object)
    +        ret[()] = self
    +        return ret
    +
    +    def __sub__(self, value: Any) -> Object:
    +        return self
    +
    +    def __rsub__(self, value: Any) -> Object:
    +        return self
    +
    +    def __floordiv__(self, value: Any) -> Object:
    +        return self
    +
    +    def __rfloordiv__(self, value: Any) -> Object:
    +        return self
    +
    +    def __mul__(self, value: Any) -> Object:
    +        return self
    +
    +    def __rmul__(self, value: Any) -> Object:
    +        return self
    +
    +    def __pow__(self, value: Any) -> Object:
    +        return self
    +
    +    def __rpow__(self, value: Any) -> Object:
    +        return self
    +
    +
    +AR_b: npt.NDArray[np.bool] = np.array([True])
    +AR_u: npt.NDArray[np.uint32] = np.array([1], dtype=np.uint32)
    +AR_i: npt.NDArray[np.int64] = np.array([1])
    +AR_integer: npt.NDArray[np.integer] = cast(npt.NDArray[np.integer], AR_i)
    +AR_f: npt.NDArray[np.float64] = np.array([1.0])
    +AR_c: npt.NDArray[np.complex128] = np.array([1j])
    +AR_m: npt.NDArray[np.timedelta64] = np.array([np.timedelta64(1, "D")])
    +AR_M: npt.NDArray[np.datetime64] = np.array([np.datetime64(1, "D")])
    +AR_O: npt.NDArray[np.object_] = np.array([Object()])
    +
    +AR_LIKE_b = [True]
    +AR_LIKE_u = [np.uint32(1)]
    +AR_LIKE_i = [1]
    +AR_LIKE_f = [1.0]
    +AR_LIKE_c = [1j]
    +AR_LIKE_m = [np.timedelta64(1, "D")]
    +AR_LIKE_M = [np.datetime64(1, "D")]
    +AR_LIKE_O = [Object()]
    +
    +# Array subtractions
    +
    +AR_b - AR_LIKE_u
    +AR_b - AR_LIKE_i
    +AR_b - AR_LIKE_f
    +AR_b - AR_LIKE_c
    +AR_b - AR_LIKE_m
    +AR_b - AR_LIKE_O
    +
    +AR_LIKE_u - AR_b
    +AR_LIKE_i - AR_b
    +AR_LIKE_f - AR_b
    +AR_LIKE_c - AR_b
    +AR_LIKE_m - AR_b
    +AR_LIKE_M - AR_b
    +AR_LIKE_O - AR_b
    +
    +AR_u - AR_LIKE_b
    +AR_u - AR_LIKE_u
    +AR_u - AR_LIKE_i
    +AR_u - AR_LIKE_f
    +AR_u - AR_LIKE_c
    +AR_u - AR_LIKE_m
    +AR_u - AR_LIKE_O
    +
    +AR_LIKE_b - AR_u
    +AR_LIKE_u - AR_u
    +AR_LIKE_i - AR_u
    +AR_LIKE_f - AR_u
    +AR_LIKE_c - AR_u
    +AR_LIKE_m - AR_u
    +AR_LIKE_M - AR_u
    +AR_LIKE_O - AR_u
    +
    +AR_i - AR_LIKE_b
    +AR_i - AR_LIKE_u
    +AR_i - AR_LIKE_i
    +AR_i - AR_LIKE_f
    +AR_i - AR_LIKE_c
    +AR_i - AR_LIKE_m
    +AR_i - AR_LIKE_O
    +
    +AR_LIKE_b - AR_i
    +AR_LIKE_u - AR_i
    +AR_LIKE_i - AR_i
    +AR_LIKE_f - AR_i
    +AR_LIKE_c - AR_i
    +AR_LIKE_m - AR_i
    +AR_LIKE_M - AR_i
    +AR_LIKE_O - AR_i
    +
    +AR_f - AR_LIKE_b
    +AR_f - AR_LIKE_u
    +AR_f - AR_LIKE_i
    +AR_f - AR_LIKE_f
    +AR_f - AR_LIKE_c
    +AR_f - AR_LIKE_O
    +
    +AR_LIKE_b - AR_f
    +AR_LIKE_u - AR_f
    +AR_LIKE_i - AR_f
    +AR_LIKE_f - AR_f
    +AR_LIKE_c - AR_f
    +AR_LIKE_O - AR_f
    +
    +AR_c - AR_LIKE_b
    +AR_c - AR_LIKE_u
    +AR_c - AR_LIKE_i
    +AR_c - AR_LIKE_f
    +AR_c - AR_LIKE_c
    +AR_c - AR_LIKE_O
    +
    +AR_LIKE_b - AR_c
    +AR_LIKE_u - AR_c
    +AR_LIKE_i - AR_c
    +AR_LIKE_f - AR_c
    +AR_LIKE_c - AR_c
    +AR_LIKE_O - AR_c
    +
    +AR_m - AR_LIKE_b
    +AR_m - AR_LIKE_u
    +AR_m - AR_LIKE_i
    +AR_m - AR_LIKE_m
    +
    +AR_LIKE_b - AR_m
    +AR_LIKE_u - AR_m
    +AR_LIKE_i - AR_m
    +AR_LIKE_m - AR_m
    +AR_LIKE_M - AR_m
    +
    +AR_M - AR_LIKE_b
    +AR_M - AR_LIKE_u
    +AR_M - AR_LIKE_i
    +AR_M - AR_LIKE_m
    +AR_M - AR_LIKE_M
    +
    +AR_LIKE_M - AR_M
    +
    +AR_O - AR_LIKE_b
    +AR_O - AR_LIKE_u
    +AR_O - AR_LIKE_i
    +AR_O - AR_LIKE_f
    +AR_O - AR_LIKE_c
    +AR_O - AR_LIKE_O
    +
    +AR_LIKE_b - AR_O
    +AR_LIKE_u - AR_O
    +AR_LIKE_i - AR_O
    +AR_LIKE_f - AR_O
    +AR_LIKE_c - AR_O
    +AR_LIKE_O - AR_O
    +
    +AR_u += AR_b
    +AR_u += AR_u
    +AR_u += 1  # Allowed during runtime as long as the object is 0D and >=0
    +
    +# Array floor division
    +
    +AR_b // AR_LIKE_b
    +AR_b // AR_LIKE_u
    +AR_b // AR_LIKE_i
    +AR_b // AR_LIKE_f
    +AR_b // AR_LIKE_O
    +
    +AR_LIKE_b // AR_b
    +AR_LIKE_u // AR_b
    +AR_LIKE_i // AR_b
    +AR_LIKE_f // AR_b
    +AR_LIKE_O // AR_b
    +
    +AR_u // AR_LIKE_b
    +AR_u // AR_LIKE_u
    +AR_u // AR_LIKE_i
    +AR_u // AR_LIKE_f
    +AR_u // AR_LIKE_O
    +
    +AR_LIKE_b // AR_u
    +AR_LIKE_u // AR_u
    +AR_LIKE_i // AR_u
    +AR_LIKE_f // AR_u
    +AR_LIKE_m // AR_u
    +AR_LIKE_O // AR_u
    +
    +AR_i // AR_LIKE_b
    +AR_i // AR_LIKE_u
    +AR_i // AR_LIKE_i
    +AR_i // AR_LIKE_f
    +AR_i // AR_LIKE_O
    +
    +AR_LIKE_b // AR_i
    +AR_LIKE_u // AR_i
    +AR_LIKE_i // AR_i
    +AR_LIKE_f // AR_i
    +AR_LIKE_m // AR_i
    +AR_LIKE_O // AR_i
    +
    +AR_f // AR_LIKE_b
    +AR_f // AR_LIKE_u
    +AR_f // AR_LIKE_i
    +AR_f // AR_LIKE_f
    +AR_f // AR_LIKE_O
    +
    +AR_LIKE_b // AR_f
    +AR_LIKE_u // AR_f
    +AR_LIKE_i // AR_f
    +AR_LIKE_f // AR_f
    +AR_LIKE_m // AR_f
    +AR_LIKE_O // AR_f
    +
    +AR_m // AR_LIKE_u
    +AR_m // AR_LIKE_i
    +AR_m // AR_LIKE_f
    +AR_m // AR_LIKE_m
    +
    +AR_LIKE_m // AR_m
    +
    +AR_m /= f
    +AR_m //= f
    +AR_m /= AR_f
    +AR_m /= AR_LIKE_f
    +AR_m //= AR_f
    +AR_m //= AR_LIKE_f
    +
    +AR_O // AR_LIKE_b
    +AR_O // AR_LIKE_u
    +AR_O // AR_LIKE_i
    +AR_O // AR_LIKE_f
    +AR_O // AR_LIKE_O
    +
    +AR_LIKE_b // AR_O
    +AR_LIKE_u // AR_O
    +AR_LIKE_i // AR_O
    +AR_LIKE_f // AR_O
    +AR_LIKE_O // AR_O
    +
    +# Inplace multiplication
    +
    +AR_b *= AR_LIKE_b
    +
    +AR_u *= AR_LIKE_b
    +AR_u *= AR_LIKE_u
    +
    +AR_i *= AR_LIKE_b
    +AR_i *= AR_LIKE_u
    +AR_i *= AR_LIKE_i
    +
    +AR_integer *= AR_LIKE_b
    +AR_integer *= AR_LIKE_u
    +AR_integer *= AR_LIKE_i
    +
    +AR_f *= AR_LIKE_b
    +AR_f *= AR_LIKE_u
    +AR_f *= AR_LIKE_i
    +AR_f *= AR_LIKE_f
    +
    +AR_c *= AR_LIKE_b
    +AR_c *= AR_LIKE_u
    +AR_c *= AR_LIKE_i
    +AR_c *= AR_LIKE_f
    +AR_c *= AR_LIKE_c
    +
    +AR_m *= AR_LIKE_b
    +AR_m *= AR_LIKE_u
    +AR_m *= AR_LIKE_i
    +AR_m *= AR_LIKE_f
    +
    +AR_O *= AR_LIKE_b
    +AR_O *= AR_LIKE_u
    +AR_O *= AR_LIKE_i
    +AR_O *= AR_LIKE_f
    +AR_O *= AR_LIKE_c
    +AR_O *= AR_LIKE_O
    +
    +# Inplace power
    +
    +AR_u **= AR_LIKE_b
    +AR_u **= AR_LIKE_u
    +
    +AR_i **= AR_LIKE_b
    +AR_i **= AR_LIKE_u
    +AR_i **= AR_LIKE_i
    +
    +AR_integer **= AR_LIKE_b
    +AR_integer **= AR_LIKE_u
    +AR_integer **= AR_LIKE_i
    +
    +AR_f **= AR_LIKE_b
    +AR_f **= AR_LIKE_u
    +AR_f **= AR_LIKE_i
    +AR_f **= AR_LIKE_f
    +
    +AR_c **= AR_LIKE_b
    +AR_c **= AR_LIKE_u
    +AR_c **= AR_LIKE_i
    +AR_c **= AR_LIKE_f
    +AR_c **= AR_LIKE_c
    +
    +AR_O **= AR_LIKE_b
    +AR_O **= AR_LIKE_u
    +AR_O **= AR_LIKE_i
    +AR_O **= AR_LIKE_f
    +AR_O **= AR_LIKE_c
    +AR_O **= AR_LIKE_O
    +
    +# unary ops
    +
    +-c16
    +-c8
    +-f8
    +-f4
    +-i8
    +-i4
    +with pytest.warns(RuntimeWarning):
    +    -u8
    +    -u4
    +-td
    +-AR_f
    +
    ++c16
    ++c8
    ++f8
    ++f4
    ++i8
    ++i4
    ++u8
    ++u4
    ++td
    ++AR_f
    +
    +abs(c16)
    +abs(c8)
    +abs(f8)
    +abs(f4)
    +abs(i8)
    +abs(i4)
    +abs(u8)
    +abs(u4)
    +abs(td)
    +abs(b_)
    +abs(AR_f)
    +
    +# Time structures
    +
    +dt + td
    +dt + i
    +dt + i4
    +dt + i8
    +dt - dt
    +dt - i
    +dt - i4
    +dt - i8
    +
    +td + td
    +td + i
    +td + i4
    +td + i8
    +td - td
    +td - i
    +td - i4
    +td - i8
    +td / f
    +td / f4
    +td / f8
    +td / td
    +td // td
    +td % td
    +
    +
    +# boolean
    +
    +b_ / b
    +b_ / b_
    +b_ / i
    +b_ / i8
    +b_ / i4
    +b_ / u8
    +b_ / u4
    +b_ / f
    +b_ / f8
    +b_ / f4
    +b_ / c
    +b_ / c16
    +b_ / c8
    +
    +b / b_
    +b_ / b_
    +i / b_
    +i8 / b_
    +i4 / b_
    +u8 / b_
    +u4 / b_
    +f / b_
    +f8 / b_
    +f4 / b_
    +c / b_
    +c16 / b_
    +c8 / b_
    +
    +# Complex
    +
    +c16 + c16
    +c16 + f8
    +c16 + i8
    +c16 + c8
    +c16 + f4
    +c16 + i4
    +c16 + b_
    +c16 + b
    +c16 + c
    +c16 + f
    +c16 + i
    +c16 + AR_f
    +
    +c16 + c16
    +f8 + c16
    +i8 + c16
    +c8 + c16
    +f4 + c16
    +i4 + c16
    +b_ + c16
    +b + c16
    +c + c16
    +f + c16
    +i + c16
    +AR_f + c16
    +
    +c8 + c16
    +c8 + f8
    +c8 + i8
    +c8 + c8
    +c8 + f4
    +c8 + i4
    +c8 + b_
    +c8 + b
    +c8 + c
    +c8 + f
    +c8 + i
    +c8 + AR_f
    +
    +c16 + c8
    +f8 + c8
    +i8 + c8
    +c8 + c8
    +f4 + c8
    +i4 + c8
    +b_ + c8
    +b + c8
    +c + c8
    +f + c8
    +i + c8
    +AR_f + c8
    +
    +# Float
    +
    +f8 + f8
    +f8 + i8
    +f8 + f4
    +f8 + i4
    +f8 + b_
    +f8 + b
    +f8 + c
    +f8 + f
    +f8 + i
    +f8 + AR_f
    +
    +f8 + f8
    +i8 + f8
    +f4 + f8
    +i4 + f8
    +b_ + f8
    +b + f8
    +c + f8
    +f + f8
    +i + f8
    +AR_f + f8
    +
    +f4 + f8
    +f4 + i8
    +f4 + f4
    +f4 + i4
    +f4 + b_
    +f4 + b
    +f4 + c
    +f4 + f
    +f4 + i
    +f4 + AR_f
    +
    +f8 + f4
    +i8 + f4
    +f4 + f4
    +i4 + f4
    +b_ + f4
    +b + f4
    +c + f4
    +f + f4
    +i + f4
    +AR_f + f4
    +
    +# Int
    +
    +i8 + i8
    +i8 + u8
    +i8 + i4
    +i8 + u4
    +i8 + b_
    +i8 + b
    +i8 + c
    +i8 + f
    +i8 + i
    +i8 + AR_f
    +
    +u8 + u8
    +u8 + i4
    +u8 + u4
    +u8 + b_
    +u8 + b
    +u8 + c
    +u8 + f
    +u8 + i
    +u8 + AR_f
    +
    +i8 + i8
    +u8 + i8
    +i4 + i8
    +u4 + i8
    +b_ + i8
    +b + i8
    +c + i8
    +f + i8
    +i + i8
    +AR_f + i8
    +
    +u8 + u8
    +i4 + u8
    +u4 + u8
    +b_ + u8
    +b + u8
    +c + u8
    +f + u8
    +i + u8
    +AR_f + u8
    +
    +i4 + i8
    +i4 + i4
    +i4 + i
    +i4 + b_
    +i4 + b
    +i4 + AR_f
    +
    +u4 + i8
    +u4 + i4
    +u4 + u8
    +u4 + u4
    +u4 + i
    +u4 + b_
    +u4 + b
    +u4 + AR_f
    +
    +i8 + i4
    +i4 + i4
    +i + i4
    +b_ + i4
    +b + i4
    +AR_f + i4
    +
    +i8 + u4
    +i4 + u4
    +u8 + u4
    +u4 + u4
    +b_ + u4
    +b + u4
    +i + u4
    +AR_f + u4
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/array_constructors.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/array_constructors.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..d91d257cb17cf907326e066b184e6265656abdb2
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/array_constructors.py
    @@ -0,0 +1,138 @@
    +from typing import Any
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +
    +class Index:
    +    def __index__(self) -> int:
    +        return 0
    +
    +
    +class SubClass(npt.NDArray[np.float64]):
    +    pass
    +
    +
    +def func(i: int, j: int, **kwargs: Any) -> SubClass:
    +    return B
    +
    +
    +i8 = np.int64(1)
    +
    +A = np.array([1])
    +B = A.view(SubClass).copy()
    +B_stack = np.array([[1], [1]]).view(SubClass)
    +C = [1]
    +
    +np.ndarray(Index())
    +np.ndarray([Index()])
    +
    +np.array(1, dtype=float)
    +np.array(1, copy=None)
    +np.array(1, order='F')
    +np.array(1, order=None)
    +np.array(1, subok=True)
    +np.array(1, ndmin=3)
    +np.array(1, str, copy=True, order='C', subok=False, ndmin=2)
    +
    +np.asarray(A)
    +np.asarray(B)
    +np.asarray(C)
    +
    +np.asanyarray(A)
    +np.asanyarray(B)
    +np.asanyarray(B, dtype=int)
    +np.asanyarray(C)
    +
    +np.ascontiguousarray(A)
    +np.ascontiguousarray(B)
    +np.ascontiguousarray(C)
    +
    +np.asfortranarray(A)
    +np.asfortranarray(B)
    +np.asfortranarray(C)
    +
    +np.require(A)
    +np.require(B)
    +np.require(B, dtype=int)
    +np.require(B, requirements=None)
    +np.require(B, requirements="E")
    +np.require(B, requirements=["ENSUREARRAY"])
    +np.require(B, requirements={"F", "E"})
    +np.require(B, requirements=["C", "OWNDATA"])
    +np.require(B, requirements="W")
    +np.require(B, requirements="A")
    +np.require(C)
    +
    +np.linspace(0, 2)
    +np.linspace(0.5, [0, 1, 2])
    +np.linspace([0, 1, 2], 3)
    +np.linspace(0j, 2)
    +np.linspace(0, 2, num=10)
    +np.linspace(0, 2, endpoint=True)
    +np.linspace(0, 2, retstep=True)
    +np.linspace(0j, 2j, retstep=True)
    +np.linspace(0, 2, dtype=bool)
    +np.linspace([0, 1], [2, 3], axis=Index())
    +
    +np.logspace(0, 2, base=2)
    +np.logspace(0, 2, base=2)
    +np.logspace(0, 2, base=[1j, 2j], num=2)
    +
    +np.geomspace(1, 2)
    +
    +np.zeros_like(A)
    +np.zeros_like(C)
    +np.zeros_like(B)
    +np.zeros_like(B, dtype=np.int64)
    +
    +np.ones_like(A)
    +np.ones_like(C)
    +np.ones_like(B)
    +np.ones_like(B, dtype=np.int64)
    +
    +np.empty_like(A)
    +np.empty_like(C)
    +np.empty_like(B)
    +np.empty_like(B, dtype=np.int64)
    +
    +np.full_like(A, i8)
    +np.full_like(C, i8)
    +np.full_like(B, i8)
    +np.full_like(B, i8, dtype=np.int64)
    +
    +np.ones(1)
    +np.ones([1, 1, 1])
    +
    +np.full(1, i8)
    +np.full([1, 1, 1], i8)
    +
    +np.indices([1, 2, 3])
    +np.indices([1, 2, 3], sparse=True)
    +
    +np.fromfunction(func, (3, 5))
    +
    +np.identity(10)
    +
    +np.atleast_1d(C)
    +np.atleast_1d(A)
    +np.atleast_1d(C, C)
    +np.atleast_1d(C, A)
    +np.atleast_1d(A, A)
    +
    +np.atleast_2d(C)
    +
    +np.atleast_3d(C)
    +
    +np.vstack([C, C])
    +np.vstack([C, A])
    +np.vstack([A, A])
    +
    +np.hstack([C, C])
    +
    +np.stack([C, C])
    +np.stack([C, C], axis=0)
    +np.stack([C, C], out=B_stack)
    +
    +np.block([[C, C], [C, C]])
    +np.block(A)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/array_like.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/array_like.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..f922beae34ce87bfbd57cc8d0e6f5778cc4d96eb
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/array_like.py
    @@ -0,0 +1,43 @@
    +from __future__ import annotations
    +
    +from typing import TYPE_CHECKING
    +
    +import numpy as np
    +
    +if TYPE_CHECKING:
    +    from numpy._typing import ArrayLike, NDArray, _SupportsArray
    +
    +x1: ArrayLike = True
    +x2: ArrayLike = 5
    +x3: ArrayLike = 1.0
    +x4: ArrayLike = 1 + 1j
    +x5: ArrayLike = np.int8(1)
    +x6: ArrayLike = np.float64(1)
    +x7: ArrayLike = np.complex128(1)
    +x8: ArrayLike = np.array([1, 2, 3])
    +x9: ArrayLike = [1, 2, 3]
    +x10: ArrayLike = (1, 2, 3)
    +x11: ArrayLike = "foo"
    +x12: ArrayLike = memoryview(b'foo')
    +
    +
    +class A:
    +    def __array__(self, dtype: np.dtype | None = None) -> NDArray[np.float64]:
    +        return np.array([1.0, 2.0, 3.0])
    +
    +
    +x13: ArrayLike = A()
    +
    +scalar: _SupportsArray[np.dtype[np.int64]] = np.int64(1)
    +scalar.__array__()
    +array: _SupportsArray[np.dtype[np.int_]] = np.array(1)
    +array.__array__()
    +
    +a: _SupportsArray[np.dtype[np.float64]] = A()
    +a.__array__()
    +a.__array__()
    +
    +# Escape hatch for when you mean to make something like an object
    +# array.
    +object_array_scalar: object = (i for i in range(10))
    +np.array(object_array_scalar)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/arrayprint.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/arrayprint.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..6c704c755570d1508424af92a0eb5aa1353666a0
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/arrayprint.py
    @@ -0,0 +1,37 @@
    +import numpy as np
    +
    +AR = np.arange(10)
    +AR.setflags(write=False)
    +
    +with np.printoptions():
    +    np.set_printoptions(
    +        precision=1,
    +        threshold=2,
    +        edgeitems=3,
    +        linewidth=4,
    +        suppress=False,
    +        nanstr="Bob",
    +        infstr="Bill",
    +        formatter={},
    +        sign="+",
    +        floatmode="unique",
    +    )
    +    np.get_printoptions()
    +    str(AR)
    +
    +    np.array2string(
    +        AR,
    +        max_line_width=5,
    +        precision=2,
    +        suppress_small=True,
    +        separator=";",
    +        prefix="test",
    +        threshold=5,
    +        floatmode="fixed",
    +        suffix="?",
    +        legacy="1.13",
    +    )
    +    np.format_float_scientific(1, precision=5)
    +    np.format_float_positional(1, trim="k")
    +    np.array_repr(AR)
    +    np.array_str(AR)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/arrayterator.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/arrayterator.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..a99c09a252312fde45504c58824d1c07693c15d4
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/arrayterator.py
    @@ -0,0 +1,28 @@
    +
    +from __future__ import annotations
    +
    +from typing import Any
    +
    +import numpy as np
    +
    +AR_i8: np.ndarray[Any, np.dtype[np.int_]] = np.arange(10)
    +ar_iter = np.lib.Arrayterator(AR_i8)
    +
    +ar_iter.var
    +ar_iter.buf_size
    +ar_iter.start
    +ar_iter.stop
    +ar_iter.step
    +ar_iter.shape
    +ar_iter.flat
    +
    +ar_iter.__array__()
    +
    +for i in ar_iter:
    +    pass
    +
    +ar_iter[0]
    +ar_iter[...]
    +ar_iter[:]
    +ar_iter[0, 0, 0]
    +ar_iter[..., 0, :]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/bitwise_ops.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/bitwise_ops.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..2d4815b0d94089fa24b7924828b826ad35d75cde
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/bitwise_ops.py
    @@ -0,0 +1,131 @@
    +import numpy as np
    +
    +i8 = np.int64(1)
    +u8 = np.uint64(1)
    +
    +i4 = np.int32(1)
    +u4 = np.uint32(1)
    +
    +b_ = np.bool(1)
    +
    +b = bool(1)
    +i = 1
    +
    +AR = np.array([0, 1, 2], dtype=np.int32)
    +AR.setflags(write=False)
    +
    +
    +i8 << i8
    +i8 >> i8
    +i8 | i8
    +i8 ^ i8
    +i8 & i8
    +
    +i << AR
    +i >> AR
    +i | AR
    +i ^ AR
    +i & AR
    +
    +i8 << AR
    +i8 >> AR
    +i8 | AR
    +i8 ^ AR
    +i8 & AR
    +
    +i4 << i4
    +i4 >> i4
    +i4 | i4
    +i4 ^ i4
    +i4 & i4
    +
    +i8 << i4
    +i8 >> i4
    +i8 | i4
    +i8 ^ i4
    +i8 & i4
    +
    +i8 << i
    +i8 >> i
    +i8 | i
    +i8 ^ i
    +i8 & i
    +
    +i8 << b_
    +i8 >> b_
    +i8 | b_
    +i8 ^ b_
    +i8 & b_
    +
    +i8 << b
    +i8 >> b
    +i8 | b
    +i8 ^ b
    +i8 & b
    +
    +u8 << u8
    +u8 >> u8
    +u8 | u8
    +u8 ^ u8
    +u8 & u8
    +
    +u4 << u4
    +u4 >> u4
    +u4 | u4
    +u4 ^ u4
    +u4 & u4
    +
    +u4 << i4
    +u4 >> i4
    +u4 | i4
    +u4 ^ i4
    +u4 & i4
    +
    +u4 << i
    +u4 >> i
    +u4 | i
    +u4 ^ i
    +u4 & i
    +
    +u8 << b_
    +u8 >> b_
    +u8 | b_
    +u8 ^ b_
    +u8 & b_
    +
    +u8 << b
    +u8 >> b
    +u8 | b
    +u8 ^ b
    +u8 & b
    +
    +b_ << b_
    +b_ >> b_
    +b_ | b_
    +b_ ^ b_
    +b_ & b_
    +
    +b_ << AR
    +b_ >> AR
    +b_ | AR
    +b_ ^ AR
    +b_ & AR
    +
    +b_ << b
    +b_ >> b
    +b_ | b
    +b_ ^ b
    +b_ & b
    +
    +b_ << i
    +b_ >> i
    +b_ | i
    +b_ ^ i
    +b_ & i
    +
    +~i8
    +~i4
    +~u8
    +~u4
    +~b_
    +~AR
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/comparisons.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/comparisons.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..b2e52762c7a8e61fbe7d6e526df4dc4ffc6676df
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/comparisons.py
    @@ -0,0 +1,316 @@
    +from __future__ import annotations
    +
    +from typing import Any, cast
    +
    +import numpy as np
    +
    +c16 = np.complex128()
    +f8 = np.float64()
    +i8 = np.int64()
    +u8 = np.uint64()
    +
    +c8 = np.complex64()
    +f4 = np.float32()
    +i4 = np.int32()
    +u4 = np.uint32()
    +
    +dt = np.datetime64(0, "D")
    +td = np.timedelta64(0, "D")
    +
    +b_ = np.bool()
    +
    +b = False
    +c = complex()
    +f = 0.0
    +i = 0
    +
    +SEQ = (0, 1, 2, 3, 4)
    +
    +AR_b: np.ndarray[Any, np.dtype[np.bool]] = np.array([True])
    +AR_u: np.ndarray[Any, np.dtype[np.uint32]] = np.array([1], dtype=np.uint32)
    +AR_i: np.ndarray[Any, np.dtype[np.int_]] = np.array([1])
    +AR_f: np.ndarray[Any, np.dtype[np.float64]] = np.array([1.0])
    +AR_c: np.ndarray[Any, np.dtype[np.complex128]] = np.array([1.0j])
    +AR_S: np.ndarray[Any, np.dtype[np.bytes_]] = np.array([b"a"], "S")
    +AR_T = cast(np.ndarray[Any, np.dtypes.StringDType], np.array(["a"], "T"))
    +AR_U: np.ndarray[Any, np.dtype[np.str_]] = np.array(["a"], "U")
    +AR_m: np.ndarray[Any, np.dtype[np.timedelta64]] = np.array([np.timedelta64("1")])
    +AR_M: np.ndarray[Any, np.dtype[np.datetime64]] = np.array([np.datetime64("1")])
    +AR_O: np.ndarray[Any, np.dtype[np.object_]] = np.array([1], dtype=object)
    +
    +# Arrays
    +
    +AR_b > AR_b
    +AR_b > AR_u
    +AR_b > AR_i
    +AR_b > AR_f
    +AR_b > AR_c
    +
    +AR_u > AR_b
    +AR_u > AR_u
    +AR_u > AR_i
    +AR_u > AR_f
    +AR_u > AR_c
    +
    +AR_i > AR_b
    +AR_i > AR_u
    +AR_i > AR_i
    +AR_i > AR_f
    +AR_i > AR_c
    +
    +AR_f > AR_b
    +AR_f > AR_u
    +AR_f > AR_i
    +AR_f > AR_f
    +AR_f > AR_c
    +
    +AR_c > AR_b
    +AR_c > AR_u
    +AR_c > AR_i
    +AR_c > AR_f
    +AR_c > AR_c
    +
    +AR_S > AR_S
    +AR_S > b""
    +
    +AR_T > AR_T
    +AR_T > AR_U
    +AR_T > ""
    +
    +AR_U > AR_U
    +AR_U > AR_T
    +AR_U > ""
    +
    +AR_m > AR_b
    +AR_m > AR_u
    +AR_m > AR_i
    +AR_b > AR_m
    +AR_u > AR_m
    +AR_i > AR_m
    +
    +AR_M > AR_M
    +
    +AR_O > AR_O
    +1 > AR_O
    +AR_O > 1
    +
    +# Time structures
    +
    +dt > dt
    +
    +td > td
    +td > i
    +td > i4
    +td > i8
    +td > AR_i
    +td > SEQ
    +
    +# boolean
    +
    +b_ > b
    +b_ > b_
    +b_ > i
    +b_ > i8
    +b_ > i4
    +b_ > u8
    +b_ > u4
    +b_ > f
    +b_ > f8
    +b_ > f4
    +b_ > c
    +b_ > c16
    +b_ > c8
    +b_ > AR_i
    +b_ > SEQ
    +
    +# Complex
    +
    +c16 > c16
    +c16 > f8
    +c16 > i8
    +c16 > c8
    +c16 > f4
    +c16 > i4
    +c16 > b_
    +c16 > b
    +c16 > c
    +c16 > f
    +c16 > i
    +c16 > AR_i
    +c16 > SEQ
    +
    +c16 > c16
    +f8 > c16
    +i8 > c16
    +c8 > c16
    +f4 > c16
    +i4 > c16
    +b_ > c16
    +b > c16
    +c > c16
    +f > c16
    +i > c16
    +AR_i > c16
    +SEQ > c16
    +
    +c8 > c16
    +c8 > f8
    +c8 > i8
    +c8 > c8
    +c8 > f4
    +c8 > i4
    +c8 > b_
    +c8 > b
    +c8 > c
    +c8 > f
    +c8 > i
    +c8 > AR_i
    +c8 > SEQ
    +
    +c16 > c8
    +f8 > c8
    +i8 > c8
    +c8 > c8
    +f4 > c8
    +i4 > c8
    +b_ > c8
    +b > c8
    +c > c8
    +f > c8
    +i > c8
    +AR_i > c8
    +SEQ > c8
    +
    +# Float
    +
    +f8 > f8
    +f8 > i8
    +f8 > f4
    +f8 > i4
    +f8 > b_
    +f8 > b
    +f8 > c
    +f8 > f
    +f8 > i
    +f8 > AR_i
    +f8 > SEQ
    +
    +f8 > f8
    +i8 > f8
    +f4 > f8
    +i4 > f8
    +b_ > f8
    +b > f8
    +c > f8
    +f > f8
    +i > f8
    +AR_i > f8
    +SEQ > f8
    +
    +f4 > f8
    +f4 > i8
    +f4 > f4
    +f4 > i4
    +f4 > b_
    +f4 > b
    +f4 > c
    +f4 > f
    +f4 > i
    +f4 > AR_i
    +f4 > SEQ
    +
    +f8 > f4
    +i8 > f4
    +f4 > f4
    +i4 > f4
    +b_ > f4
    +b > f4
    +c > f4
    +f > f4
    +i > f4
    +AR_i > f4
    +SEQ > f4
    +
    +# Int
    +
    +i8 > i8
    +i8 > u8
    +i8 > i4
    +i8 > u4
    +i8 > b_
    +i8 > b
    +i8 > c
    +i8 > f
    +i8 > i
    +i8 > AR_i
    +i8 > SEQ
    +
    +u8 > u8
    +u8 > i4
    +u8 > u4
    +u8 > b_
    +u8 > b
    +u8 > c
    +u8 > f
    +u8 > i
    +u8 > AR_i
    +u8 > SEQ
    +
    +i8 > i8
    +u8 > i8
    +i4 > i8
    +u4 > i8
    +b_ > i8
    +b > i8
    +c > i8
    +f > i8
    +i > i8
    +AR_i > i8
    +SEQ > i8
    +
    +u8 > u8
    +i4 > u8
    +u4 > u8
    +b_ > u8
    +b > u8
    +c > u8
    +f > u8
    +i > u8
    +AR_i > u8
    +SEQ > u8
    +
    +i4 > i8
    +i4 > i4
    +i4 > i
    +i4 > b_
    +i4 > b
    +i4 > AR_i
    +i4 > SEQ
    +
    +u4 > i8
    +u4 > i4
    +u4 > u8
    +u4 > u4
    +u4 > i
    +u4 > b_
    +u4 > b
    +u4 > AR_i
    +u4 > SEQ
    +
    +i8 > i4
    +i4 > i4
    +i > i4
    +b_ > i4
    +b > i4
    +AR_i > i4
    +SEQ > i4
    +
    +i8 > u4
    +i4 > u4
    +u8 > u4
    +u4 > u4
    +b_ > u4
    +b > u4
    +i > u4
    +AR_i > u4
    +SEQ > u4
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/dtype.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/dtype.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..9f11518276c842f2601e3f1420020f00e3b775fb
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/dtype.py
    @@ -0,0 +1,57 @@
    +import numpy as np
    +
    +dtype_obj = np.dtype(np.str_)
    +void_dtype_obj = np.dtype([("f0", np.float64), ("f1", np.float32)])
    +
    +np.dtype(dtype=np.int64)
    +np.dtype(int)
    +np.dtype("int")
    +np.dtype(None)
    +
    +np.dtype((int, 2))
    +np.dtype((int, (1,)))
    +
    +np.dtype({"names": ["a", "b"], "formats": [int, float]})
    +np.dtype({"names": ["a"], "formats": [int], "titles": [object]})
    +np.dtype({"names": ["a"], "formats": [int], "titles": [object()]})
    +
    +np.dtype([("name", np.str_, 16), ("grades", np.float64, (2,)), ("age", "int32")])
    +
    +np.dtype(
    +    {
    +        "names": ["a", "b"],
    +        "formats": [int, float],
    +        "itemsize": 9,
    +        "aligned": False,
    +        "titles": ["x", "y"],
    +        "offsets": [0, 1],
    +    }
    +)
    +
    +np.dtype((np.float64, float))
    +
    +
    +class Test:
    +    dtype = np.dtype(float)
    +
    +
    +np.dtype(Test())
    +
    +# Methods and attributes
    +dtype_obj.base
    +dtype_obj.subdtype
    +dtype_obj.newbyteorder()
    +dtype_obj.type
    +dtype_obj.name
    +dtype_obj.names
    +
    +dtype_obj * 0
    +dtype_obj * 2
    +
    +0 * dtype_obj
    +2 * dtype_obj
    +
    +void_dtype_obj["f0"]
    +void_dtype_obj[0]
    +void_dtype_obj[["f0", "f1"]]
    +void_dtype_obj[["f0"]]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/einsumfunc.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/einsumfunc.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..429764e67eccc7855d363da20d432fdb45e66971
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/einsumfunc.py
    @@ -0,0 +1,36 @@
    +from __future__ import annotations
    +
    +from typing import Any
    +
    +import numpy as np
    +
    +AR_LIKE_b = [True, True, True]
    +AR_LIKE_u = [np.uint32(1), np.uint32(2), np.uint32(3)]
    +AR_LIKE_i = [1, 2, 3]
    +AR_LIKE_f = [1.0, 2.0, 3.0]
    +AR_LIKE_c = [1j, 2j, 3j]
    +AR_LIKE_U = ["1", "2", "3"]
    +
    +OUT_f: np.ndarray[Any, np.dtype[np.float64]] = np.empty(3, dtype=np.float64)
    +OUT_c: np.ndarray[Any, np.dtype[np.complex128]] = np.empty(3, dtype=np.complex128)
    +
    +np.einsum("i,i->i", AR_LIKE_b, AR_LIKE_b)
    +np.einsum("i,i->i", AR_LIKE_u, AR_LIKE_u)
    +np.einsum("i,i->i", AR_LIKE_i, AR_LIKE_i)
    +np.einsum("i,i->i", AR_LIKE_f, AR_LIKE_f)
    +np.einsum("i,i->i", AR_LIKE_c, AR_LIKE_c)
    +np.einsum("i,i->i", AR_LIKE_b, AR_LIKE_i)
    +np.einsum("i,i,i,i->i", AR_LIKE_b, AR_LIKE_u, AR_LIKE_i, AR_LIKE_c)
    +
    +np.einsum("i,i->i", AR_LIKE_f, AR_LIKE_f, dtype="c16")
    +np.einsum("i,i->i", AR_LIKE_U, AR_LIKE_U, dtype=bool, casting="unsafe")
    +np.einsum("i,i->i", AR_LIKE_f, AR_LIKE_f, out=OUT_c)
    +np.einsum("i,i->i", AR_LIKE_U, AR_LIKE_U, dtype=int, casting="unsafe", out=OUT_f)
    +
    +np.einsum_path("i,i->i", AR_LIKE_b, AR_LIKE_b)
    +np.einsum_path("i,i->i", AR_LIKE_u, AR_LIKE_u)
    +np.einsum_path("i,i->i", AR_LIKE_i, AR_LIKE_i)
    +np.einsum_path("i,i->i", AR_LIKE_f, AR_LIKE_f)
    +np.einsum_path("i,i->i", AR_LIKE_c, AR_LIKE_c)
    +np.einsum_path("i,i->i", AR_LIKE_b, AR_LIKE_i)
    +np.einsum_path("i,i,i,i->i", AR_LIKE_b, AR_LIKE_u, AR_LIKE_i, AR_LIKE_c)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/flatiter.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/flatiter.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..70de3a67917dd8e9f32f4c0dc4f38d1693d96764
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/flatiter.py
    @@ -0,0 +1,26 @@
    +import numpy as np
    +
    +a = np.empty((2, 2)).flat
    +
    +a.base
    +a.copy()
    +a.coords
    +a.index
    +iter(a)
    +next(a)
    +a[0]
    +a[...]
    +a[:]
    +a.__array__()
    +
    +b = np.array([1]).flat
    +a[b]
    +
    +a[0] = "1"
    +a[:] = "2"
    +a[...] = "3"
    +a[[]] = "4"
    +a[[0]] = "5"
    +a[[[0]]] = "6"
    +a[[[[[0]]]]] = "7"
    +a[b] = "8"
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/fromnumeric.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/fromnumeric.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..7cc2bcfd8b50f235081e737c3420e1db34191637
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/fromnumeric.py
    @@ -0,0 +1,272 @@
    +"""Tests for :mod:`numpy._core.fromnumeric`."""
    +
    +import numpy as np
    +
    +A = np.array(True, ndmin=2, dtype=bool)
    +B = np.array(1.0, ndmin=2, dtype=np.float32)
    +A.setflags(write=False)
    +B.setflags(write=False)
    +
    +a = np.bool(True)
    +b = np.float32(1.0)
    +c = 1.0
    +d = np.array(1.0, dtype=np.float32)  # writeable
    +
    +np.take(a, 0)
    +np.take(b, 0)
    +np.take(c, 0)
    +np.take(A, 0)
    +np.take(B, 0)
    +np.take(A, [0])
    +np.take(B, [0])
    +
    +np.reshape(a, 1)
    +np.reshape(b, 1)
    +np.reshape(c, 1)
    +np.reshape(A, 1)
    +np.reshape(B, 1)
    +
    +np.choose(a, [True, True])
    +np.choose(A, [1.0, 1.0])
    +
    +np.repeat(a, 1)
    +np.repeat(b, 1)
    +np.repeat(c, 1)
    +np.repeat(A, 1)
    +np.repeat(B, 1)
    +
    +np.swapaxes(A, 0, 0)
    +np.swapaxes(B, 0, 0)
    +
    +np.transpose(a)
    +np.transpose(b)
    +np.transpose(c)
    +np.transpose(A)
    +np.transpose(B)
    +
    +np.partition(a, 0, axis=None)
    +np.partition(b, 0, axis=None)
    +np.partition(c, 0, axis=None)
    +np.partition(A, 0)
    +np.partition(B, 0)
    +
    +np.argpartition(a, 0)
    +np.argpartition(b, 0)
    +np.argpartition(c, 0)
    +np.argpartition(A, 0)
    +np.argpartition(B, 0)
    +
    +np.sort(A, 0)
    +np.sort(B, 0)
    +
    +np.argsort(A, 0)
    +np.argsort(B, 0)
    +
    +np.argmax(A)
    +np.argmax(B)
    +np.argmax(A, axis=0)
    +np.argmax(B, axis=0)
    +
    +np.argmin(A)
    +np.argmin(B)
    +np.argmin(A, axis=0)
    +np.argmin(B, axis=0)
    +
    +np.searchsorted(A[0], 0)
    +np.searchsorted(B[0], 0)
    +np.searchsorted(A[0], [0])
    +np.searchsorted(B[0], [0])
    +
    +np.resize(a, (5, 5))
    +np.resize(b, (5, 5))
    +np.resize(c, (5, 5))
    +np.resize(A, (5, 5))
    +np.resize(B, (5, 5))
    +
    +np.squeeze(a)
    +np.squeeze(b)
    +np.squeeze(c)
    +np.squeeze(A)
    +np.squeeze(B)
    +
    +np.diagonal(A)
    +np.diagonal(B)
    +
    +np.trace(A)
    +np.trace(B)
    +
    +np.ravel(a)
    +np.ravel(b)
    +np.ravel(c)
    +np.ravel(A)
    +np.ravel(B)
    +
    +np.nonzero(A)
    +np.nonzero(B)
    +
    +np.shape(a)
    +np.shape(b)
    +np.shape(c)
    +np.shape(A)
    +np.shape(B)
    +
    +np.compress([True], a)
    +np.compress([True], b)
    +np.compress([True], c)
    +np.compress([True], A)
    +np.compress([True], B)
    +
    +np.clip(a, 0, 1.0)
    +np.clip(b, -1, 1)
    +np.clip(a, 0, None)
    +np.clip(b, None, 1)
    +np.clip(c, 0, 1)
    +np.clip(A, 0, 1)
    +np.clip(B, 0, 1)
    +np.clip(B, [0, 1], [1, 2])
    +
    +np.sum(a)
    +np.sum(b)
    +np.sum(c)
    +np.sum(A)
    +np.sum(B)
    +np.sum(A, axis=0)
    +np.sum(B, axis=0)
    +
    +np.all(a)
    +np.all(b)
    +np.all(c)
    +np.all(A)
    +np.all(B)
    +np.all(A, axis=0)
    +np.all(B, axis=0)
    +np.all(A, keepdims=True)
    +np.all(B, keepdims=True)
    +
    +np.any(a)
    +np.any(b)
    +np.any(c)
    +np.any(A)
    +np.any(B)
    +np.any(A, axis=0)
    +np.any(B, axis=0)
    +np.any(A, keepdims=True)
    +np.any(B, keepdims=True)
    +
    +np.cumsum(a)
    +np.cumsum(b)
    +np.cumsum(c)
    +np.cumsum(A)
    +np.cumsum(B)
    +
    +np.cumulative_sum(a)
    +np.cumulative_sum(b)
    +np.cumulative_sum(c)
    +np.cumulative_sum(A, axis=0)
    +np.cumulative_sum(B, axis=0)
    +
    +np.ptp(b)
    +np.ptp(c)
    +np.ptp(B)
    +np.ptp(B, axis=0)
    +np.ptp(B, keepdims=True)
    +
    +np.amax(a)
    +np.amax(b)
    +np.amax(c)
    +np.amax(A)
    +np.amax(B)
    +np.amax(A, axis=0)
    +np.amax(B, axis=0)
    +np.amax(A, keepdims=True)
    +np.amax(B, keepdims=True)
    +
    +np.amin(a)
    +np.amin(b)
    +np.amin(c)
    +np.amin(A)
    +np.amin(B)
    +np.amin(A, axis=0)
    +np.amin(B, axis=0)
    +np.amin(A, keepdims=True)
    +np.amin(B, keepdims=True)
    +
    +np.prod(a)
    +np.prod(b)
    +np.prod(c)
    +np.prod(A)
    +np.prod(B)
    +np.prod(a, dtype=None)
    +np.prod(A, dtype=None)
    +np.prod(A, axis=0)
    +np.prod(B, axis=0)
    +np.prod(A, keepdims=True)
    +np.prod(B, keepdims=True)
    +np.prod(b, out=d)
    +np.prod(B, out=d)
    +
    +np.cumprod(a)
    +np.cumprod(b)
    +np.cumprod(c)
    +np.cumprod(A)
    +np.cumprod(B)
    +
    +np.cumulative_prod(a)
    +np.cumulative_prod(b)
    +np.cumulative_prod(c)
    +np.cumulative_prod(A, axis=0)
    +np.cumulative_prod(B, axis=0)
    +
    +np.ndim(a)
    +np.ndim(b)
    +np.ndim(c)
    +np.ndim(A)
    +np.ndim(B)
    +
    +np.size(a)
    +np.size(b)
    +np.size(c)
    +np.size(A)
    +np.size(B)
    +
    +np.around(a)
    +np.around(b)
    +np.around(c)
    +np.around(A)
    +np.around(B)
    +
    +np.mean(a)
    +np.mean(b)
    +np.mean(c)
    +np.mean(A)
    +np.mean(B)
    +np.mean(A, axis=0)
    +np.mean(B, axis=0)
    +np.mean(A, keepdims=True)
    +np.mean(B, keepdims=True)
    +np.mean(b, out=d)
    +np.mean(B, out=d)
    +
    +np.std(a)
    +np.std(b)
    +np.std(c)
    +np.std(A)
    +np.std(B)
    +np.std(A, axis=0)
    +np.std(B, axis=0)
    +np.std(A, keepdims=True)
    +np.std(B, keepdims=True)
    +np.std(b, out=d)
    +np.std(B, out=d)
    +
    +np.var(a)
    +np.var(b)
    +np.var(c)
    +np.var(A)
    +np.var(B)
    +np.var(A, axis=0)
    +np.var(B, axis=0)
    +np.var(A, keepdims=True)
    +np.var(B, keepdims=True)
    +np.var(b, out=d)
    +np.var(B, out=d)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/index_tricks.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/index_tricks.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..ea98156a8f2eacf86f0e4dff14b95588455b8d96
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/index_tricks.py
    @@ -0,0 +1,62 @@
    +from __future__ import annotations
    +
    +from typing import Any
    +
    +import numpy as np
    +
    +AR_LIKE_b = [[True, True], [True, True]]
    +AR_LIKE_i = [[1, 2], [3, 4]]
    +AR_LIKE_f = [[1.0, 2.0], [3.0, 4.0]]
    +AR_LIKE_U = [["1", "2"], ["3", "4"]]
    +
    +AR_i8: np.ndarray[Any, np.dtype[np.int64]] = np.array(AR_LIKE_i, dtype=np.int64)
    +
    +np.ndenumerate(AR_i8)
    +np.ndenumerate(AR_LIKE_f)
    +np.ndenumerate(AR_LIKE_U)
    +
    +next(np.ndenumerate(AR_i8))
    +next(np.ndenumerate(AR_LIKE_f))
    +next(np.ndenumerate(AR_LIKE_U))
    +
    +iter(np.ndenumerate(AR_i8))
    +iter(np.ndenumerate(AR_LIKE_f))
    +iter(np.ndenumerate(AR_LIKE_U))
    +
    +iter(np.ndindex(1, 2, 3))
    +next(np.ndindex(1, 2, 3))
    +
    +np.unravel_index([22, 41, 37], (7, 6))
    +np.unravel_index([31, 41, 13], (7, 6), order='F')
    +np.unravel_index(1621, (6, 7, 8, 9))
    +
    +np.ravel_multi_index(AR_LIKE_i, (7, 6))
    +np.ravel_multi_index(AR_LIKE_i, (7, 6), order='F')
    +np.ravel_multi_index(AR_LIKE_i, (4, 6), mode='clip')
    +np.ravel_multi_index(AR_LIKE_i, (4, 4), mode=('clip', 'wrap'))
    +np.ravel_multi_index((3, 1, 4, 1), (6, 7, 8, 9))
    +
    +np.mgrid[1:1:2]
    +np.mgrid[1:1:2, None:10]
    +
    +np.ogrid[1:1:2]
    +np.ogrid[1:1:2, None:10]
    +
    +np.index_exp[0:1]
    +np.index_exp[0:1, None:3]
    +np.index_exp[0, 0:1, ..., [0, 1, 3]]
    +
    +np.s_[0:1]
    +np.s_[0:1, None:3]
    +np.s_[0, 0:1, ..., [0, 1, 3]]
    +
    +np.ix_(AR_LIKE_b[0])
    +np.ix_(AR_LIKE_i[0], AR_LIKE_f[0])
    +np.ix_(AR_i8[0])
    +
    +np.fill_diagonal(AR_i8, 5)
    +
    +np.diag_indices(4)
    +np.diag_indices(2, 3)
    +
    +np.diag_indices_from(AR_i8)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/lib_user_array.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/lib_user_array.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..f79dc38af508b43c5d385c67072e31acca418def
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/lib_user_array.py
    @@ -0,0 +1,22 @@
    +"""Based on the `if __name__ == "__main__"` test code in `lib/_user_array_impl.py`."""
    +
    +from __future__ import annotations
    +
    +import numpy as np
    +from numpy.lib.user_array import container  # type: ignore[deprecated]
    +
    +N = 10_000
    +W = H = int(N**0.5)
    +
    +a: np.ndarray[tuple[int, int], np.dtype[np.int32]]
    +ua: container[tuple[int, int], np.dtype[np.int32]]
    +
    +a = np.arange(N, dtype=np.int32).reshape(W, H)
    +ua = container(a)
    +
    +ua_small: container[tuple[int, int], np.dtype[np.int32]] = ua[:3, :5]
    +ua_small[0, 0] = 10
    +
    +ua_bool: container[tuple[int, int], np.dtype[np.bool]] = ua_small > 1
    +
    +# shape: tuple[int, int] = np.shape(ua)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/lib_utils.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/lib_utils.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..f9b3381e13d26adf44130fc62aa8bfaacfe6a658
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/lib_utils.py
    @@ -0,0 +1,19 @@
    +from __future__ import annotations
    +
    +from io import StringIO
    +
    +import numpy as np
    +import numpy.lib.array_utils as array_utils
    +
    +FILE = StringIO()
    +AR = np.arange(10, dtype=np.float64)
    +
    +
    +def func(a: int) -> bool:
    +    return True
    +
    +
    +array_utils.byte_bounds(AR)
    +array_utils.byte_bounds(np.float64())
    +
    +np.info(1, output=FILE)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/lib_version.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/lib_version.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..f3825eca524795f4d9742873a773cd7749636e2a
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/lib_version.py
    @@ -0,0 +1,18 @@
    +from numpy.lib import NumpyVersion
    +
    +version = NumpyVersion("1.8.0")
    +
    +version.vstring
    +version.version
    +version.major
    +version.minor
    +version.bugfix
    +version.pre_release
    +version.is_devversion
    +
    +version == version
    +version != version
    +version < "1.8.0"
    +version <= version
    +version > version
    +version >= "1.8.0"
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/literal.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/literal.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..f1e0cb2a69d3e60d1afabeab970b156cce1dde58
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/literal.py
    @@ -0,0 +1,52 @@
    +from __future__ import annotations
    +
    +from functools import partial
    +from typing import TYPE_CHECKING, Any
    +
    +import pytest
    +
    +import numpy as np
    +
    +if TYPE_CHECKING:
    +    from collections.abc import Callable
    +
    +AR = np.array(0)
    +AR.setflags(write=False)
    +
    +KACF = frozenset({None, "K", "A", "C", "F"})
    +ACF = frozenset({None, "A", "C", "F"})
    +CF = frozenset({None, "C", "F"})
    +
    +order_list: list[tuple[frozenset[str | None], Callable[..., Any]]] = [
    +    (KACF, AR.tobytes),
    +    (KACF, partial(AR.astype, int)),
    +    (KACF, AR.copy),
    +    (ACF, partial(AR.reshape, 1)),
    +    (KACF, AR.flatten),
    +    (KACF, AR.ravel),
    +    (KACF, partial(np.array, 1)),
    +    # NOTE: __call__ is needed due to mypy bugs (#17620, #17631)
    +    (KACF, partial(np.ndarray.__call__, 1)),
    +    (CF, partial(np.zeros.__call__, 1)),
    +    (CF, partial(np.ones.__call__, 1)),
    +    (CF, partial(np.empty.__call__, 1)),
    +    (CF, partial(np.full, 1, 1)),
    +    (KACF, partial(np.zeros_like, AR)),
    +    (KACF, partial(np.ones_like, AR)),
    +    (KACF, partial(np.empty_like, AR)),
    +    (KACF, partial(np.full_like, AR, 1)),
    +    (KACF, partial(np.add.__call__, 1, 1)),  # i.e. np.ufunc.__call__
    +    (ACF, partial(np.reshape, AR, 1)),
    +    (KACF, partial(np.ravel, AR)),
    +    (KACF, partial(np.asarray, 1)),
    +    (KACF, partial(np.asanyarray, 1)),
    +]
    +
    +for order_set, func in order_list:
    +    for order in order_set:
    +        func(order=order)
    +
    +    invalid_orders = KACF - order_set
    +    for order in invalid_orders:
    +        with pytest.raises(ValueError):
    +            func(order=order)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/ma.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/ma.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..3ccea66861ebd1816dca659456ca246a3536f8d8
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/ma.py
    @@ -0,0 +1,199 @@
    +import datetime as dt
    +from typing import Any, TypeAlias, TypeVar, cast
    +
    +import numpy as np
    +import numpy.typing as npt
    +from numpy._typing import _Shape
    +
    +_ScalarT = TypeVar("_ScalarT", bound=np.generic)
    +MaskedArray: TypeAlias = np.ma.MaskedArray[_Shape, np.dtype[_ScalarT]]
    +
    +# mypy: disable-error-code=no-untyped-call
    +
    +MAR_b: MaskedArray[np.bool] = np.ma.MaskedArray([True])
    +MAR_u: MaskedArray[np.uint32] = np.ma.MaskedArray([1], dtype=np.uint32)
    +MAR_i: MaskedArray[np.int64] = np.ma.MaskedArray([1])
    +MAR_f: MaskedArray[np.float64] = np.ma.MaskedArray([1.0])
    +MAR_c: MaskedArray[np.complex128] = np.ma.MaskedArray([1j])
    +MAR_td64: MaskedArray[np.timedelta64] = np.ma.MaskedArray([np.timedelta64(1, "D")])
    +MAR_dt64: MaskedArray[np.datetime64] = np.ma.MaskedArray([np.datetime64(1, "D")])
    +MAR_S: MaskedArray[np.bytes_] = np.ma.MaskedArray([b'foo'], dtype=np.bytes_)
    +MAR_U: MaskedArray[np.str_] = np.ma.MaskedArray(['foo'], dtype=np.str_)
    +MAR_T = cast(np.ma.MaskedArray[Any, np.dtypes.StringDType],
    +             np.ma.MaskedArray(["a"], dtype="T"))
    +MAR_V: MaskedArray[np.void] = np.ma.MaskedArray(
    +    [(1, 1)],
    +    mask=[(False, False)],
    +    dtype=[('a', int), ('b', int)]
    +)
    +
    +AR_b: npt.NDArray[np.bool] = np.array([True, False, True])
    +
    +AR_LIKE_b = [True]
    +AR_LIKE_u = [np.uint32(1)]
    +AR_LIKE_i = [1]
    +AR_LIKE_f = [1.0]
    +AR_LIKE_c = [1j]
    +AR_LIKE_m = [np.timedelta64(1, "D")]
    +AR_LIKE_M = [np.datetime64(1, "D")]
    +
    +MAR_f.mask = AR_b
    +MAR_f.mask = np.False_
    +
    +MAR_i.fill_value = 0
    +
    +MAR_b.flat[MAR_i > 0] = False
    +MAR_i.flat[:] = 1
    +MAR_f.flat[[0]] = AR_LIKE_f
    +MAR_c.flat[[0, 0]] = [3, 4 + 3j]
    +MAR_td64.flat[0] = dt.timedelta(1)
    +MAR_dt64.flat[0] = dt.datetime(2020, 1, 1)
    +
    +MAR_b[MAR_i > 0] = False
    +MAR_i[:] = 1
    +MAR_f[[0]] = AR_LIKE_f
    +MAR_c[[0, 0]] = [3, 4 + 3j]
    +MAR_td64[0] = dt.timedelta(1)
    +MAR_dt64[0] = dt.datetime(2020, 1, 1)
    +MAR_V['a'] = [2]
    +
    +# Inplace addition
    +
    +MAR_b += AR_LIKE_b
    +
    +MAR_u += AR_LIKE_b
    +MAR_u += AR_LIKE_u
    +
    +MAR_i += AR_LIKE_b
    +MAR_i += 2
    +MAR_i += AR_LIKE_i
    +
    +MAR_f += AR_LIKE_b
    +MAR_f += 2
    +MAR_f += AR_LIKE_u
    +MAR_f += AR_LIKE_i
    +MAR_f += AR_LIKE_f
    +
    +MAR_c += AR_LIKE_b
    +MAR_c += AR_LIKE_u
    +MAR_c += AR_LIKE_i
    +MAR_c += AR_LIKE_f
    +MAR_c += AR_LIKE_c
    +
    +MAR_td64 += AR_LIKE_b
    +MAR_td64 += AR_LIKE_u
    +MAR_td64 += AR_LIKE_i
    +MAR_td64 += AR_LIKE_m
    +MAR_dt64 += AR_LIKE_b
    +MAR_dt64 += AR_LIKE_u
    +MAR_dt64 += AR_LIKE_i
    +MAR_dt64 += AR_LIKE_m
    +
    +MAR_S += b'snakes'
    +MAR_U += 'snakes'
    +MAR_T += 'snakes'
    +
    +# Inplace subtraction
    +
    +MAR_u -= AR_LIKE_b
    +MAR_u -= AR_LIKE_u
    +
    +MAR_i -= AR_LIKE_b
    +MAR_i -= AR_LIKE_i
    +
    +MAR_f -= AR_LIKE_b
    +MAR_f -= AR_LIKE_u
    +MAR_f -= AR_LIKE_i
    +MAR_f -= AR_LIKE_f
    +
    +MAR_c -= AR_LIKE_b
    +MAR_c -= AR_LIKE_u
    +MAR_c -= AR_LIKE_i
    +MAR_c -= AR_LIKE_f
    +MAR_c -= AR_LIKE_c
    +
    +MAR_td64 -= AR_LIKE_b
    +MAR_td64 -= AR_LIKE_u
    +MAR_td64 -= AR_LIKE_i
    +MAR_td64 -= AR_LIKE_m
    +MAR_dt64 -= AR_LIKE_b
    +MAR_dt64 -= AR_LIKE_u
    +MAR_dt64 -= AR_LIKE_i
    +MAR_dt64 -= AR_LIKE_m
    +
    +# Inplace floor division
    +
    +MAR_f //= AR_LIKE_b
    +MAR_f //= 2
    +MAR_f //= AR_LIKE_u
    +MAR_f //= AR_LIKE_i
    +MAR_f //= AR_LIKE_f
    +
    +MAR_td64 //= AR_LIKE_i
    +
    +# Inplace true division
    +
    +MAR_f /= AR_LIKE_b
    +MAR_f /= 2
    +MAR_f /= AR_LIKE_u
    +MAR_f /= AR_LIKE_i
    +MAR_f /= AR_LIKE_f
    +
    +MAR_c /= AR_LIKE_b
    +MAR_c /= AR_LIKE_u
    +MAR_c /= AR_LIKE_i
    +MAR_c /= AR_LIKE_f
    +MAR_c /= AR_LIKE_c
    +
    +MAR_td64 /= AR_LIKE_i
    +
    +# Inplace multiplication
    +
    +MAR_b *= AR_LIKE_b
    +
    +MAR_u *= AR_LIKE_b
    +MAR_u *= AR_LIKE_u
    +
    +MAR_i *= AR_LIKE_b
    +MAR_i *= 2
    +MAR_i *= AR_LIKE_i
    +
    +MAR_f *= AR_LIKE_b
    +MAR_f *= 2
    +MAR_f *= AR_LIKE_u
    +MAR_f *= AR_LIKE_i
    +MAR_f *= AR_LIKE_f
    +
    +MAR_c *= AR_LIKE_b
    +MAR_c *= AR_LIKE_u
    +MAR_c *= AR_LIKE_i
    +MAR_c *= AR_LIKE_f
    +MAR_c *= AR_LIKE_c
    +
    +MAR_td64 *= AR_LIKE_b
    +MAR_td64 *= AR_LIKE_u
    +MAR_td64 *= AR_LIKE_i
    +MAR_td64 *= AR_LIKE_f
    +
    +MAR_S *= 2
    +MAR_U *= 2
    +MAR_T *= 2
    +
    +# Inplace power
    +
    +MAR_u **= AR_LIKE_b
    +MAR_u **= AR_LIKE_u
    +
    +MAR_i **= AR_LIKE_b
    +MAR_i **= AR_LIKE_i
    +
    +MAR_f **= AR_LIKE_b
    +MAR_f **= AR_LIKE_u
    +MAR_f **= AR_LIKE_i
    +MAR_f **= AR_LIKE_f
    +
    +MAR_c **= AR_LIKE_b
    +MAR_c **= AR_LIKE_u
    +MAR_c **= AR_LIKE_i
    +MAR_c **= AR_LIKE_f
    +MAR_c **= AR_LIKE_c
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/mod.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/mod.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..464326486fa296374e722e3fc77242d67f7a8dac
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/mod.py
    @@ -0,0 +1,149 @@
    +import numpy as np
    +
    +f8 = np.float64(1)
    +i8 = np.int64(1)
    +u8 = np.uint64(1)
    +
    +f4 = np.float32(1)
    +i4 = np.int32(1)
    +u4 = np.uint32(1)
    +
    +td = np.timedelta64(1, "D")
    +b_ = np.bool(1)
    +
    +b = bool(1)
    +f = float(1)
    +i = 1
    +
    +AR = np.array([1], dtype=np.bool)
    +AR.setflags(write=False)
    +
    +AR2 = np.array([1], dtype=np.timedelta64)
    +AR2.setflags(write=False)
    +
    +# Time structures
    +
    +td % td
    +td % AR2
    +AR2 % td
    +
    +divmod(td, td)
    +divmod(td, AR2)
    +divmod(AR2, td)
    +
    +# Bool
    +
    +b_ % b
    +b_ % i
    +b_ % f
    +b_ % b_
    +b_ % i8
    +b_ % u8
    +b_ % f8
    +b_ % AR
    +
    +divmod(b_, b)
    +divmod(b_, i)
    +divmod(b_, f)
    +divmod(b_, b_)
    +divmod(b_, i8)
    +divmod(b_, u8)
    +divmod(b_, f8)
    +divmod(b_, AR)
    +
    +b % b_
    +i % b_
    +f % b_
    +b_ % b_
    +i8 % b_
    +u8 % b_
    +f8 % b_
    +AR % b_
    +
    +divmod(b, b_)
    +divmod(i, b_)
    +divmod(f, b_)
    +divmod(b_, b_)
    +divmod(i8, b_)
    +divmod(u8, b_)
    +divmod(f8, b_)
    +divmod(AR, b_)
    +
    +# int
    +
    +i8 % b
    +i8 % i
    +i8 % f
    +i8 % i8
    +i8 % f8
    +i4 % i8
    +i4 % f8
    +i4 % i4
    +i4 % f4
    +i8 % AR
    +
    +divmod(i8, b)
    +divmod(i8, i)
    +divmod(i8, f)
    +divmod(i8, i8)
    +divmod(i8, f8)
    +divmod(i8, i4)
    +divmod(i8, f4)
    +divmod(i4, i4)
    +divmod(i4, f4)
    +divmod(i8, AR)
    +
    +b % i8
    +i % i8
    +f % i8
    +i8 % i8
    +f8 % i8
    +i8 % i4
    +f8 % i4
    +i4 % i4
    +f4 % i4
    +AR % i8
    +
    +divmod(b, i8)
    +divmod(i, i8)
    +divmod(f, i8)
    +divmod(i8, i8)
    +divmod(f8, i8)
    +divmod(i4, i8)
    +divmod(f4, i8)
    +divmod(i4, i4)
    +divmod(f4, i4)
    +divmod(AR, i8)
    +
    +# float
    +
    +f8 % b
    +f8 % i
    +f8 % f
    +i8 % f4
    +f4 % f4
    +f8 % AR
    +
    +divmod(f8, b)
    +divmod(f8, i)
    +divmod(f8, f)
    +divmod(f8, f8)
    +divmod(f8, f4)
    +divmod(f4, f4)
    +divmod(f8, AR)
    +
    +b % f8
    +i % f8
    +f % f8
    +f8 % f8
    +f8 % f8
    +f4 % f4
    +AR % f8
    +
    +divmod(b, f8)
    +divmod(i, f8)
    +divmod(f, f8)
    +divmod(f8, f8)
    +divmod(f4, f8)
    +divmod(f4, f4)
    +divmod(AR, f8)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/modules.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/modules.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..0c2fd4b7e7c3a2d0ed1be39928edf5b2d591e16c
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/modules.py
    @@ -0,0 +1,45 @@
    +import numpy as np
    +from numpy import f2py
    +
    +np.char
    +np.ctypeslib
    +np.emath
    +np.fft
    +np.lib
    +np.linalg
    +np.ma
    +np.matrixlib
    +np.polynomial
    +np.random
    +np.rec
    +np.strings
    +np.testing
    +np.version
    +
    +np.lib.format
    +np.lib.mixins
    +np.lib.scimath
    +np.lib.stride_tricks
    +np.lib.array_utils
    +np.ma.extras
    +np.polynomial.chebyshev
    +np.polynomial.hermite
    +np.polynomial.hermite_e
    +np.polynomial.laguerre
    +np.polynomial.legendre
    +np.polynomial.polynomial
    +
    +np.__path__
    +np.__version__
    +
    +np.__all__
    +np.char.__all__
    +np.ctypeslib.__all__
    +np.emath.__all__
    +np.lib.__all__
    +np.ma.__all__
    +np.random.__all__
    +np.rec.__all__
    +np.strings.__all__
    +np.testing.__all__
    +f2py.__all__
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/multiarray.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/multiarray.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..3a505590b5d3818da89aecba3fdf10eecd45004d
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/multiarray.py
    @@ -0,0 +1,77 @@
    +import numpy as np
    +import numpy.typing as npt
    +
    +AR_f8: npt.NDArray[np.float64] = np.array([1.0])
    +AR_i4 = np.array([1], dtype=np.int32)
    +AR_u1 = np.array([1], dtype=np.uint8)
    +
    +AR_LIKE_f = [1.5]
    +AR_LIKE_i = [1]
    +
    +b_f8 = np.broadcast(AR_f8)
    +b_i4_f8_f8 = np.broadcast(AR_i4, AR_f8, AR_f8)
    +
    +next(b_f8)
    +b_f8.reset()
    +b_f8.index
    +b_f8.iters
    +b_f8.nd
    +b_f8.ndim
    +b_f8.numiter
    +b_f8.shape
    +b_f8.size
    +
    +next(b_i4_f8_f8)
    +b_i4_f8_f8.reset()
    +b_i4_f8_f8.ndim
    +b_i4_f8_f8.index
    +b_i4_f8_f8.iters
    +b_i4_f8_f8.nd
    +b_i4_f8_f8.numiter
    +b_i4_f8_f8.shape
    +b_i4_f8_f8.size
    +
    +np.inner(AR_f8, AR_i4)
    +
    +np.where([True, True, False])
    +np.where([True, True, False], 1, 0)
    +
    +np.lexsort([0, 1, 2])
    +
    +np.can_cast(np.dtype("i8"), int)
    +np.can_cast(AR_f8, "f8")
    +np.can_cast(AR_f8, np.complex128, casting="unsafe")
    +
    +np.min_scalar_type([1])
    +np.min_scalar_type(AR_f8)
    +
    +np.result_type(int, AR_i4)
    +np.result_type(AR_f8, AR_u1)
    +np.result_type(AR_f8, np.complex128)
    +
    +np.dot(AR_LIKE_f, AR_i4)
    +np.dot(AR_u1, 1)
    +np.dot(1.5j, 1)
    +np.dot(AR_u1, 1, out=AR_f8)
    +
    +np.vdot(AR_LIKE_f, AR_i4)
    +np.vdot(AR_u1, 1)
    +np.vdot(1.5j, 1)
    +
    +np.bincount(AR_i4)
    +
    +np.copyto(AR_f8, [1.6])
    +
    +np.putmask(AR_f8, [True], 1.5)
    +
    +np.packbits(AR_i4)
    +np.packbits(AR_u1)
    +
    +np.unpackbits(AR_u1)
    +
    +np.shares_memory(1, 2)
    +np.shares_memory(AR_f8, AR_f8, max_work=-1)
    +
    +np.may_share_memory(1, 2)
    +np.may_share_memory(AR_f8, AR_f8, max_work=0)
    +np.may_share_memory(AR_f8, AR_f8, max_work=-1)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/ndarray_conversion.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/ndarray_conversion.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..a4e0bcf34bdd41bc936ee5b38ff58807e471533f
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/ndarray_conversion.py
    @@ -0,0 +1,81 @@
    +import os
    +import tempfile
    +
    +import numpy as np
    +
    +nd = np.array([[1, 2], [3, 4]])
    +scalar_array = np.array(1)
    +
    +# item
    +scalar_array.item()
    +nd.item(1)
    +nd.item(0, 1)
    +nd.item((0, 1))
    +
    +# tobytes
    +nd.tobytes()
    +nd.tobytes("C")
    +nd.tobytes(None)
    +
    +# tofile
    +if os.name != "nt":
    +    with tempfile.NamedTemporaryFile(suffix=".txt") as tmp:
    +        nd.tofile(tmp.name)
    +        nd.tofile(tmp.name, "")
    +        nd.tofile(tmp.name, sep="")
    +
    +        nd.tofile(tmp.name, "", "%s")
    +        nd.tofile(tmp.name, format="%s")
    +
    +        nd.tofile(tmp)
    +
    +# dump is pretty simple
    +# dumps is pretty simple
    +
    +# astype
    +nd.astype("float")
    +nd.astype(float)
    +
    +nd.astype(float, "K")
    +nd.astype(float, order="K")
    +
    +nd.astype(float, "K", "unsafe")
    +nd.astype(float, casting="unsafe")
    +
    +nd.astype(float, "K", "unsafe", True)
    +nd.astype(float, subok=True)
    +
    +nd.astype(float, "K", "unsafe", True, True)
    +nd.astype(float, copy=True)
    +
    +# byteswap
    +nd.byteswap()
    +nd.byteswap(True)
    +
    +# copy
    +nd.copy()
    +nd.copy("C")
    +
    +# view
    +nd.view()
    +nd.view(np.int64)
    +nd.view(dtype=np.int64)
    +nd.view(np.int64, np.matrix)
    +nd.view(type=np.matrix)
    +
    +# getfield
    +complex_array = np.array([[1 + 1j, 0], [0, 1 - 1j]], dtype=np.complex128)
    +
    +complex_array.getfield("float")
    +complex_array.getfield(float)
    +
    +complex_array.getfield("float", 8)
    +complex_array.getfield(float, offset=8)
    +
    +# setflags
    +nd.setflags()
    +nd.setflags(write=True)
    +nd.setflags(write=True, align=True)
    +nd.setflags(write=True, align=True, uic=False)
    +
    +# fill is pretty simple
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/ndarray_misc.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/ndarray_misc.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..3437338ec7d1dafafed79278cffb642cdb1c2c2f
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/ndarray_misc.py
    @@ -0,0 +1,199 @@
    +"""
    +Tests for miscellaneous (non-magic) ``np.ndarray``/``np.generic`` methods.
    +
    +More extensive tests are performed for the methods'
    +function-based counterpart in `../from_numeric.py`.
    +
    +"""
    +
    +from __future__ import annotations
    +
    +import operator
    +from collections.abc import Hashable
    +from typing import Any, cast
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +
    +class SubClass(npt.NDArray[np.float64]): ...
    +class IntSubClass(npt.NDArray[np.intp]): ...
    +
    +
    +i4 = np.int32(1)
    +A: np.ndarray[Any, np.dtype[np.int32]] = np.array([[1]], dtype=np.int32)
    +B0 = np.empty((), dtype=np.int32).view(SubClass)
    +B1 = np.empty((1,), dtype=np.int32).view(SubClass)
    +B2 = np.empty((1, 1), dtype=np.int32).view(SubClass)
    +B_int0: IntSubClass = np.empty((), dtype=np.intp).view(IntSubClass)
    +C: np.ndarray[Any, np.dtype[np.int32]] = np.array([0, 1, 2], dtype=np.int32)
    +D = np.ones(3).view(SubClass)
    +
    +ctypes_obj = A.ctypes
    +
    +i4.all()
    +A.all()
    +A.all(axis=0)
    +A.all(keepdims=True)
    +A.all(out=B0)
    +
    +i4.any()
    +A.any()
    +A.any(axis=0)
    +A.any(keepdims=True)
    +A.any(out=B0)
    +
    +i4.argmax()
    +A.argmax()
    +A.argmax(axis=0)
    +A.argmax(out=B_int0)
    +
    +i4.argmin()
    +A.argmin()
    +A.argmin(axis=0)
    +A.argmin(out=B_int0)
    +
    +i4.argsort()
    +i4.argsort(stable=True)
    +A.argsort()
    +A.argsort(stable=True)
    +
    +A.sort()
    +A.sort(stable=True)
    +
    +i4.choose([()])
    +_choices = np.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]], dtype=np.int32)
    +C.choose(_choices)
    +C.choose(_choices, out=D)
    +
    +i4.clip(1)
    +A.clip(1)
    +A.clip(None, 1)
    +A.clip(1, out=B2)
    +A.clip(None, 1, out=B2)
    +
    +i4.compress([1])
    +A.compress([1])
    +A.compress([1], out=B1)
    +
    +i4.conj()
    +A.conj()
    +B0.conj()
    +
    +i4.conjugate()
    +A.conjugate()
    +B0.conjugate()
    +
    +i4.cumprod()
    +A.cumprod()
    +A.cumprod(out=B1)
    +
    +i4.cumsum()
    +A.cumsum()
    +A.cumsum(out=B1)
    +
    +i4.max()
    +A.max()
    +A.max(axis=0)
    +A.max(keepdims=True)
    +A.max(out=B0)
    +
    +i4.mean()
    +A.mean()
    +A.mean(axis=0)
    +A.mean(keepdims=True)
    +A.mean(out=B0)
    +
    +i4.min()
    +A.min()
    +A.min(axis=0)
    +A.min(keepdims=True)
    +A.min(out=B0)
    +
    +i4.prod()
    +A.prod()
    +A.prod(axis=0)
    +A.prod(keepdims=True)
    +A.prod(out=B0)
    +
    +i4.round()
    +A.round()
    +A.round(out=B2)
    +
    +i4.repeat(1)
    +A.repeat(1)
    +B0.repeat(1)
    +
    +i4.std()
    +A.std()
    +A.std(axis=0)
    +A.std(keepdims=True, mean=0.)
    +A.std(out=B0.astype(np.float64))
    +
    +i4.sum()
    +A.sum()
    +A.sum(axis=0)
    +A.sum(keepdims=True)
    +A.sum(out=B0)
    +
    +i4.take(0)
    +A.take(0)
    +A.take([0])
    +A.take(0, out=B0)
    +A.take([0], out=B1)
    +
    +i4.var()
    +A.var()
    +A.var(axis=0)
    +A.var(keepdims=True, mean=0.)
    +A.var(out=B0)
    +
    +A.argpartition([0])
    +
    +A.diagonal()
    +
    +A.dot(1)
    +A.dot(1, out=B2)
    +
    +A.nonzero()
    +
    +C.searchsorted(1)
    +
    +A.trace()
    +A.trace(out=B0)
    +
    +void = cast(np.void, np.array(1, dtype=[("f", np.float64)]).take(0))
    +void.setfield(10, np.float64)
    +
    +A.item(0)
    +C.item(0)
    +
    +A.ravel()
    +C.ravel()
    +
    +A.flatten()
    +C.flatten()
    +
    +A.reshape(1)
    +C.reshape(3)
    +
    +int(np.array(1.0, dtype=np.float64))
    +int(np.array("1", dtype=np.str_))
    +
    +float(np.array(1.0, dtype=np.float64))
    +float(np.array("1", dtype=np.str_))
    +
    +complex(np.array(1.0, dtype=np.float64))
    +
    +operator.index(np.array(1, dtype=np.int64))
    +
    +# this fails on numpy 2.2.1
    +# https://github.com/scipy/scipy/blob/a755ee77ec47a64849abe42c349936475a6c2f24/scipy/io/arff/tests/test_arffread.py#L41-L44
    +A_float = np.array([[1, 5], [2, 4], [np.nan, np.nan]])
    +A_void: npt.NDArray[np.void] = np.empty(3, [("yop", float), ("yap", float)])
    +A_void["yop"] = A_float[:, 0]
    +A_void["yap"] = A_float[:, 1]
    +
    +# regression test for https://github.com/numpy/numpy/issues/30445
    +def f(x: np.generic) -> Hashable:
    +    return x
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/ndarray_shape_manipulation.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/ndarray_shape_manipulation.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..0ca3dff392e12a4122740ade2ceb71b3d6e7bb08
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/ndarray_shape_manipulation.py
    @@ -0,0 +1,47 @@
    +import numpy as np
    +
    +nd1 = np.array([[1, 2], [3, 4]])
    +
    +# reshape
    +nd1.reshape(4)
    +nd1.reshape(2, 2)
    +nd1.reshape((2, 2))
    +
    +nd1.reshape((2, 2), order="C")
    +nd1.reshape(4, order="C")
    +
    +# resize
    +nd1.resize()
    +nd1.resize(4)
    +nd1.resize(2, 2)
    +nd1.resize((2, 2))
    +
    +nd1.resize((2, 2), refcheck=True)
    +nd1.resize(4, refcheck=True)
    +
    +nd2 = np.array([[1, 2], [3, 4]])
    +
    +# transpose
    +nd2.transpose()
    +nd2.transpose(1, 0)
    +nd2.transpose((1, 0))
    +
    +# swapaxes
    +nd2.swapaxes(0, 1)
    +
    +# flatten
    +nd2.flatten()
    +nd2.flatten("C")
    +
    +# ravel
    +nd2.ravel()
    +nd2.ravel("C")
    +
    +# squeeze
    +nd2.squeeze()
    +
    +nd3 = np.array([[1, 2]])
    +nd3.squeeze(0)
    +
    +nd4 = np.array([[[1, 2]]])
    +nd4.squeeze((0, 1))
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/nditer.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/nditer.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..25a5b44d7aecf6486be76ae0dca0e3e8ef60699b
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/nditer.py
    @@ -0,0 +1,4 @@
    +import numpy as np
    +
    +arr = np.array([1])
    +np.nditer([arr, None])
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/numeric.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/numeric.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..825a6dd74f34e33f0a29f7e42b9f48abf6d2d9a7
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/numeric.py
    @@ -0,0 +1,90 @@
    +"""
    +Tests for :mod:`numpy._core.numeric`.
    +
    +Does not include tests which fall under ``array_constructors``.
    +"""
    +
    +from typing import Any
    +
    +import numpy as np
    +
    +
    +class SubClass(np.ndarray[tuple[Any, ...], np.dtype[np.float64]]): ...
    +
    +
    +i8 = np.int64(1)
    +
    +A = np.arange(27).reshape(3, 3, 3)
    +B = A.tolist()
    +C = np.empty((27, 27)).view(SubClass)
    +
    +np.count_nonzero(i8)
    +np.count_nonzero(A)
    +np.count_nonzero(B)
    +np.count_nonzero(A, keepdims=True)
    +np.count_nonzero(A, axis=0)
    +
    +np.isfortran(i8)
    +np.isfortran(A)
    +
    +np.argwhere(i8)
    +np.argwhere(A)
    +
    +np.flatnonzero(i8)
    +np.flatnonzero(A)
    +
    +np.correlate(B[0][0], A.ravel(), mode="valid")
    +np.correlate(A.ravel(), A.ravel(), mode="same")
    +
    +np.convolve(B[0][0], A.ravel(), mode="valid")
    +np.convolve(A.ravel(), A.ravel(), mode="same")
    +
    +np.outer(i8, A)
    +np.outer(B, A)
    +np.outer(A, A)
    +np.outer(A, A, out=C)
    +
    +np.tensordot(B, A)
    +np.tensordot(A, A)
    +np.tensordot(A, A, axes=0)
    +np.tensordot(A, A, axes=(0, 1))
    +
    +np.isscalar(i8)
    +np.isscalar(A)
    +np.isscalar(B)
    +
    +np.roll(A, 1)
    +np.roll(A, (1, 2))
    +np.roll(B, 1)
    +
    +np.rollaxis(A, 0, 1)
    +
    +np.moveaxis(A, 0, 1)
    +np.moveaxis(A, (0, 1), (1, 2))
    +
    +np.cross(B, A)
    +np.cross(A, A)
    +
    +np.indices([0, 1, 2])
    +np.indices([0, 1, 2], sparse=False)
    +np.indices([0, 1, 2], sparse=True)
    +
    +np.binary_repr(1)
    +
    +np.base_repr(1)
    +
    +np.allclose(i8, A)
    +np.allclose(B, A)
    +np.allclose(A, A)
    +
    +np.isclose(i8, A)
    +np.isclose(B, A)
    +np.isclose(A, A)
    +
    +np.array_equal(i8, A)
    +np.array_equal(B, A)
    +np.array_equal(A, A)
    +
    +np.array_equiv(i8, A)
    +np.array_equiv(B, A)
    +np.array_equiv(A, A)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/numerictypes.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/numerictypes.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..24e1a9986d88ab320bdfab54a050ed27aec4d8b5
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/numerictypes.py
    @@ -0,0 +1,17 @@
    +import numpy as np
    +
    +np.isdtype(np.float64, (np.int64, np.float64))
    +np.isdtype(np.int64, "signed integer")
    +
    +np.issubdtype("S1", np.bytes_)
    +np.issubdtype(np.float64, np.float32)
    +
    +np.ScalarType
    +np.ScalarType[0]
    +np.ScalarType[3]
    +np.ScalarType[8]
    +np.ScalarType[10]
    +
    +np.typecodes["Character"]
    +np.typecodes["Complex"]
    +np.typecodes["All"]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/random.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/random.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..fd07e378e553015d0432fbec3db43f5342e5915e
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/random.py
    @@ -0,0 +1,1498 @@
    +from __future__ import annotations
    +
    +from typing import Any
    +
    +import numpy as np
    +
    +SEED_NONE = None
    +SEED_INT = 4579435749574957634658964293569
    +SEED_ARR: np.ndarray[Any, np.dtype[np.int64]] = np.array([1, 2, 3, 4], dtype=np.int64)
    +SEED_ARRLIKE: list[int] = [1, 2, 3, 4]
    +SEED_SEED_SEQ: np.random.SeedSequence = np.random.SeedSequence(0)
    +SEED_MT19937: np.random.MT19937 = np.random.MT19937(0)
    +SEED_PCG64: np.random.PCG64 = np.random.PCG64(0)
    +SEED_PHILOX: np.random.Philox = np.random.Philox(0)
    +SEED_SFC64: np.random.SFC64 = np.random.SFC64(0)
    +
    +# default rng
    +np.random.default_rng()
    +np.random.default_rng(SEED_NONE)
    +np.random.default_rng(SEED_INT)
    +np.random.default_rng(SEED_ARR)
    +np.random.default_rng(SEED_ARRLIKE)
    +np.random.default_rng(SEED_SEED_SEQ)
    +np.random.default_rng(SEED_MT19937)
    +np.random.default_rng(SEED_PCG64)
    +np.random.default_rng(SEED_PHILOX)
    +np.random.default_rng(SEED_SFC64)
    +
    +# Seed Sequence
    +np.random.SeedSequence(SEED_NONE)
    +np.random.SeedSequence(SEED_INT)
    +np.random.SeedSequence(SEED_ARR)
    +np.random.SeedSequence(SEED_ARRLIKE)
    +
    +# Bit Generators
    +np.random.MT19937(SEED_NONE)
    +np.random.MT19937(SEED_INT)
    +np.random.MT19937(SEED_ARR)
    +np.random.MT19937(SEED_ARRLIKE)
    +np.random.MT19937(SEED_SEED_SEQ)
    +
    +np.random.PCG64(SEED_NONE)
    +np.random.PCG64(SEED_INT)
    +np.random.PCG64(SEED_ARR)
    +np.random.PCG64(SEED_ARRLIKE)
    +np.random.PCG64(SEED_SEED_SEQ)
    +
    +np.random.Philox(SEED_NONE)
    +np.random.Philox(SEED_INT)
    +np.random.Philox(SEED_ARR)
    +np.random.Philox(SEED_ARRLIKE)
    +np.random.Philox(SEED_SEED_SEQ)
    +
    +np.random.SFC64(SEED_NONE)
    +np.random.SFC64(SEED_INT)
    +np.random.SFC64(SEED_ARR)
    +np.random.SFC64(SEED_ARRLIKE)
    +np.random.SFC64(SEED_SEED_SEQ)
    +
    +seed_seq: np.random.bit_generator.SeedSequence = np.random.SeedSequence(SEED_NONE)
    +seed_seq.spawn(10)
    +seed_seq.generate_state(3)
    +seed_seq.generate_state(3, "u4")
    +seed_seq.generate_state(3, "uint32")
    +seed_seq.generate_state(3, "u8")
    +seed_seq.generate_state(3, "uint64")
    +seed_seq.generate_state(3, np.uint32)
    +seed_seq.generate_state(3, np.uint64)
    +
    +
    +def_gen: np.random.Generator = np.random.default_rng()
    +
    +D_arr_0p1: np.ndarray[Any, np.dtype[np.float64]] = np.array([0.1])
    +D_arr_0p5: np.ndarray[Any, np.dtype[np.float64]] = np.array([0.5])
    +D_arr_0p9: np.ndarray[Any, np.dtype[np.float64]] = np.array([0.9])
    +D_arr_1p5: np.ndarray[Any, np.dtype[np.float64]] = np.array([1.5])
    +I_arr_10: np.ndarray[Any, np.dtype[np.int_]] = np.array([10], dtype=np.int_)
    +I_arr_20: np.ndarray[Any, np.dtype[np.int_]] = np.array([20], dtype=np.int_)
    +D_arr_like_0p1: list[float] = [0.1]
    +D_arr_like_0p5: list[float] = [0.5]
    +D_arr_like_0p9: list[float] = [0.9]
    +D_arr_like_1p5: list[float] = [1.5]
    +I_arr_like_10: list[int] = [10]
    +I_arr_like_20: list[int] = [20]
    +D_2D_like: list[list[float]] = [[1, 2], [2, 3], [3, 4], [4, 5.1]]
    +D_2D: np.ndarray[Any, np.dtype[np.float64]] = np.array(D_2D_like)
    +
    +S_out: np.ndarray[Any, np.dtype[np.float32]] = np.empty(1, dtype=np.float32)
    +D_out: np.ndarray[Any, np.dtype[np.float64]] = np.empty(1)
    +
    +def_gen.standard_normal()
    +def_gen.standard_normal(dtype=np.float32)
    +def_gen.standard_normal(dtype="float32")
    +def_gen.standard_normal(dtype="double")
    +def_gen.standard_normal(dtype=np.float64)
    +def_gen.standard_normal(size=None)
    +def_gen.standard_normal(size=1)
    +def_gen.standard_normal(size=1, dtype=np.float32)
    +def_gen.standard_normal(size=1, dtype="f4")
    +def_gen.standard_normal(size=1, dtype="float32", out=S_out)
    +def_gen.standard_normal(dtype=np.float32, out=S_out)
    +def_gen.standard_normal(size=1, dtype=np.float64)
    +def_gen.standard_normal(size=1, dtype="float64")
    +def_gen.standard_normal(size=1, dtype="f8")
    +def_gen.standard_normal(out=D_out)
    +def_gen.standard_normal(size=1, dtype="float64")
    +def_gen.standard_normal(size=1, dtype="float64", out=D_out)
    +
    +def_gen.random()
    +def_gen.random(dtype=np.float32)
    +def_gen.random(dtype="float32")
    +def_gen.random(dtype="double")
    +def_gen.random(dtype=np.float64)
    +def_gen.random(size=None)
    +def_gen.random(size=1)
    +def_gen.random(size=1, dtype=np.float32)
    +def_gen.random(size=1, dtype="f4")
    +def_gen.random(size=1, dtype="float32", out=S_out)
    +def_gen.random(dtype=np.float32, out=S_out)
    +def_gen.random(size=1, dtype=np.float64)
    +def_gen.random(size=1, dtype="float64")
    +def_gen.random(size=1, dtype="f8")
    +def_gen.random(out=D_out)
    +def_gen.random(size=1, dtype="float64")
    +def_gen.random(size=1, dtype="float64", out=D_out)
    +
    +def_gen.standard_cauchy()
    +def_gen.standard_cauchy(size=None)
    +def_gen.standard_cauchy(size=1)
    +
    +def_gen.standard_exponential()
    +def_gen.standard_exponential(method="inv")
    +def_gen.standard_exponential(dtype=np.float32)
    +def_gen.standard_exponential(dtype="float32")
    +def_gen.standard_exponential(dtype="double")
    +def_gen.standard_exponential(dtype=np.float64)
    +def_gen.standard_exponential(size=None)
    +def_gen.standard_exponential(size=None, method="inv")
    +def_gen.standard_exponential(size=1, method="inv")
    +def_gen.standard_exponential(size=1, dtype=np.float32)
    +def_gen.standard_exponential(size=1, dtype="f4", method="inv")
    +def_gen.standard_exponential(size=1, dtype="float32", out=S_out)
    +def_gen.standard_exponential(dtype=np.float32, out=S_out)
    +def_gen.standard_exponential(size=1, dtype=np.float64, method="inv")
    +def_gen.standard_exponential(size=1, dtype="float64")
    +def_gen.standard_exponential(size=1, dtype="f8")
    +def_gen.standard_exponential(out=D_out)
    +def_gen.standard_exponential(size=1, dtype="float64")
    +def_gen.standard_exponential(size=1, dtype="float64", out=D_out)
    +
    +def_gen.zipf(1.5)
    +def_gen.zipf(1.5, size=None)
    +def_gen.zipf(1.5, size=1)
    +def_gen.zipf(D_arr_1p5)
    +def_gen.zipf(D_arr_1p5, size=1)
    +def_gen.zipf(D_arr_like_1p5)
    +def_gen.zipf(D_arr_like_1p5, size=1)
    +
    +def_gen.weibull(0.5)
    +def_gen.weibull(0.5, size=None)
    +def_gen.weibull(0.5, size=1)
    +def_gen.weibull(D_arr_0p5)
    +def_gen.weibull(D_arr_0p5, size=1)
    +def_gen.weibull(D_arr_like_0p5)
    +def_gen.weibull(D_arr_like_0p5, size=1)
    +
    +def_gen.standard_t(0.5)
    +def_gen.standard_t(0.5, size=None)
    +def_gen.standard_t(0.5, size=1)
    +def_gen.standard_t(D_arr_0p5)
    +def_gen.standard_t(D_arr_0p5, size=1)
    +def_gen.standard_t(D_arr_like_0p5)
    +def_gen.standard_t(D_arr_like_0p5, size=1)
    +
    +def_gen.poisson(0.5)
    +def_gen.poisson(0.5, size=None)
    +def_gen.poisson(0.5, size=1)
    +def_gen.poisson(D_arr_0p5)
    +def_gen.poisson(D_arr_0p5, size=1)
    +def_gen.poisson(D_arr_like_0p5)
    +def_gen.poisson(D_arr_like_0p5, size=1)
    +
    +def_gen.power(0.5)
    +def_gen.power(0.5, size=None)
    +def_gen.power(0.5, size=1)
    +def_gen.power(D_arr_0p5)
    +def_gen.power(D_arr_0p5, size=1)
    +def_gen.power(D_arr_like_0p5)
    +def_gen.power(D_arr_like_0p5, size=1)
    +
    +def_gen.pareto(0.5)
    +def_gen.pareto(0.5, size=None)
    +def_gen.pareto(0.5, size=1)
    +def_gen.pareto(D_arr_0p5)
    +def_gen.pareto(D_arr_0p5, size=1)
    +def_gen.pareto(D_arr_like_0p5)
    +def_gen.pareto(D_arr_like_0p5, size=1)
    +
    +def_gen.chisquare(0.5)
    +def_gen.chisquare(0.5, size=None)
    +def_gen.chisquare(0.5, size=1)
    +def_gen.chisquare(D_arr_0p5)
    +def_gen.chisquare(D_arr_0p5, size=1)
    +def_gen.chisquare(D_arr_like_0p5)
    +def_gen.chisquare(D_arr_like_0p5, size=1)
    +
    +def_gen.exponential(0.5)
    +def_gen.exponential(0.5, size=None)
    +def_gen.exponential(0.5, size=1)
    +def_gen.exponential(D_arr_0p5)
    +def_gen.exponential(D_arr_0p5, size=1)
    +def_gen.exponential(D_arr_like_0p5)
    +def_gen.exponential(D_arr_like_0p5, size=1)
    +
    +def_gen.geometric(0.5)
    +def_gen.geometric(0.5, size=None)
    +def_gen.geometric(0.5, size=1)
    +def_gen.geometric(D_arr_0p5)
    +def_gen.geometric(D_arr_0p5, size=1)
    +def_gen.geometric(D_arr_like_0p5)
    +def_gen.geometric(D_arr_like_0p5, size=1)
    +
    +def_gen.logseries(0.5)
    +def_gen.logseries(0.5, size=None)
    +def_gen.logseries(0.5, size=1)
    +def_gen.logseries(D_arr_0p5)
    +def_gen.logseries(D_arr_0p5, size=1)
    +def_gen.logseries(D_arr_like_0p5)
    +def_gen.logseries(D_arr_like_0p5, size=1)
    +
    +def_gen.rayleigh(0.5)
    +def_gen.rayleigh(0.5, size=None)
    +def_gen.rayleigh(0.5, size=1)
    +def_gen.rayleigh(D_arr_0p5)
    +def_gen.rayleigh(D_arr_0p5, size=1)
    +def_gen.rayleigh(D_arr_like_0p5)
    +def_gen.rayleigh(D_arr_like_0p5, size=1)
    +
    +def_gen.standard_gamma(0.5)
    +def_gen.standard_gamma(0.5, size=None)
    +def_gen.standard_gamma(0.5, dtype="float32")
    +def_gen.standard_gamma(0.5, size=None, dtype="float32")
    +def_gen.standard_gamma(0.5, size=1)
    +def_gen.standard_gamma(D_arr_0p5)
    +def_gen.standard_gamma(D_arr_0p5, dtype="f4")
    +def_gen.standard_gamma(0.5, size=1, dtype="float32", out=S_out)
    +def_gen.standard_gamma(D_arr_0p5, dtype=np.float32, out=S_out)
    +def_gen.standard_gamma(D_arr_0p5, size=1)
    +def_gen.standard_gamma(D_arr_like_0p5)
    +def_gen.standard_gamma(D_arr_like_0p5, size=1)
    +def_gen.standard_gamma(0.5, out=D_out)
    +def_gen.standard_gamma(D_arr_like_0p5, out=D_out)
    +def_gen.standard_gamma(D_arr_like_0p5, size=1)
    +def_gen.standard_gamma(D_arr_like_0p5, size=1, out=D_out, dtype=np.float64)
    +
    +def_gen.vonmises(0.5, 0.5)
    +def_gen.vonmises(0.5, 0.5, size=None)
    +def_gen.vonmises(0.5, 0.5, size=1)
    +def_gen.vonmises(D_arr_0p5, 0.5)
    +def_gen.vonmises(0.5, D_arr_0p5)
    +def_gen.vonmises(D_arr_0p5, 0.5, size=1)
    +def_gen.vonmises(0.5, D_arr_0p5, size=1)
    +def_gen.vonmises(D_arr_like_0p5, 0.5)
    +def_gen.vonmises(0.5, D_arr_like_0p5)
    +def_gen.vonmises(D_arr_0p5, D_arr_0p5)
    +def_gen.vonmises(D_arr_like_0p5, D_arr_like_0p5)
    +def_gen.vonmises(D_arr_0p5, D_arr_0p5, size=1)
    +def_gen.vonmises(D_arr_like_0p5, D_arr_like_0p5, size=1)
    +
    +def_gen.wald(0.5, 0.5)
    +def_gen.wald(0.5, 0.5, size=None)
    +def_gen.wald(0.5, 0.5, size=1)
    +def_gen.wald(D_arr_0p5, 0.5)
    +def_gen.wald(0.5, D_arr_0p5)
    +def_gen.wald(D_arr_0p5, 0.5, size=1)
    +def_gen.wald(0.5, D_arr_0p5, size=1)
    +def_gen.wald(D_arr_like_0p5, 0.5)
    +def_gen.wald(0.5, D_arr_like_0p5)
    +def_gen.wald(D_arr_0p5, D_arr_0p5)
    +def_gen.wald(D_arr_like_0p5, D_arr_like_0p5)
    +def_gen.wald(D_arr_0p5, D_arr_0p5, size=1)
    +def_gen.wald(D_arr_like_0p5, D_arr_like_0p5, size=1)
    +
    +def_gen.uniform(0.5, 0.5)
    +def_gen.uniform(0.5, 0.5, size=None)
    +def_gen.uniform(0.5, 0.5, size=1)
    +def_gen.uniform(D_arr_0p5, 0.5)
    +def_gen.uniform(0.5, D_arr_0p5)
    +def_gen.uniform(D_arr_0p5, 0.5, size=1)
    +def_gen.uniform(0.5, D_arr_0p5, size=1)
    +def_gen.uniform(D_arr_like_0p5, 0.5)
    +def_gen.uniform(0.5, D_arr_like_0p5)
    +def_gen.uniform(D_arr_0p5, D_arr_0p5)
    +def_gen.uniform(D_arr_like_0p5, D_arr_like_0p5)
    +def_gen.uniform(D_arr_0p5, D_arr_0p5, size=1)
    +def_gen.uniform(D_arr_like_0p5, D_arr_like_0p5, size=1)
    +
    +def_gen.beta(0.5, 0.5)
    +def_gen.beta(0.5, 0.5, size=None)
    +def_gen.beta(0.5, 0.5, size=1)
    +def_gen.beta(D_arr_0p5, 0.5)
    +def_gen.beta(0.5, D_arr_0p5)
    +def_gen.beta(D_arr_0p5, 0.5, size=1)
    +def_gen.beta(0.5, D_arr_0p5, size=1)
    +def_gen.beta(D_arr_like_0p5, 0.5)
    +def_gen.beta(0.5, D_arr_like_0p5)
    +def_gen.beta(D_arr_0p5, D_arr_0p5)
    +def_gen.beta(D_arr_like_0p5, D_arr_like_0p5)
    +def_gen.beta(D_arr_0p5, D_arr_0p5, size=1)
    +def_gen.beta(D_arr_like_0p5, D_arr_like_0p5, size=1)
    +
    +def_gen.f(0.5, 0.5)
    +def_gen.f(0.5, 0.5, size=None)
    +def_gen.f(0.5, 0.5, size=1)
    +def_gen.f(D_arr_0p5, 0.5)
    +def_gen.f(0.5, D_arr_0p5)
    +def_gen.f(D_arr_0p5, 0.5, size=1)
    +def_gen.f(0.5, D_arr_0p5, size=1)
    +def_gen.f(D_arr_like_0p5, 0.5)
    +def_gen.f(0.5, D_arr_like_0p5)
    +def_gen.f(D_arr_0p5, D_arr_0p5)
    +def_gen.f(D_arr_like_0p5, D_arr_like_0p5)
    +def_gen.f(D_arr_0p5, D_arr_0p5, size=1)
    +def_gen.f(D_arr_like_0p5, D_arr_like_0p5, size=1)
    +
    +def_gen.gamma(0.5, 0.5)
    +def_gen.gamma(0.5, 0.5, size=None)
    +def_gen.gamma(0.5, 0.5, size=1)
    +def_gen.gamma(D_arr_0p5, 0.5)
    +def_gen.gamma(0.5, D_arr_0p5)
    +def_gen.gamma(D_arr_0p5, 0.5, size=1)
    +def_gen.gamma(0.5, D_arr_0p5, size=1)
    +def_gen.gamma(D_arr_like_0p5, 0.5)
    +def_gen.gamma(0.5, D_arr_like_0p5)
    +def_gen.gamma(D_arr_0p5, D_arr_0p5)
    +def_gen.gamma(D_arr_like_0p5, D_arr_like_0p5)
    +def_gen.gamma(D_arr_0p5, D_arr_0p5, size=1)
    +def_gen.gamma(D_arr_like_0p5, D_arr_like_0p5, size=1)
    +
    +def_gen.gumbel(0.5, 0.5)
    +def_gen.gumbel(0.5, 0.5, size=None)
    +def_gen.gumbel(0.5, 0.5, size=1)
    +def_gen.gumbel(D_arr_0p5, 0.5)
    +def_gen.gumbel(0.5, D_arr_0p5)
    +def_gen.gumbel(D_arr_0p5, 0.5, size=1)
    +def_gen.gumbel(0.5, D_arr_0p5, size=1)
    +def_gen.gumbel(D_arr_like_0p5, 0.5)
    +def_gen.gumbel(0.5, D_arr_like_0p5)
    +def_gen.gumbel(D_arr_0p5, D_arr_0p5)
    +def_gen.gumbel(D_arr_like_0p5, D_arr_like_0p5)
    +def_gen.gumbel(D_arr_0p5, D_arr_0p5, size=1)
    +def_gen.gumbel(D_arr_like_0p5, D_arr_like_0p5, size=1)
    +
    +def_gen.laplace(0.5, 0.5)
    +def_gen.laplace(0.5, 0.5, size=None)
    +def_gen.laplace(0.5, 0.5, size=1)
    +def_gen.laplace(D_arr_0p5, 0.5)
    +def_gen.laplace(0.5, D_arr_0p5)
    +def_gen.laplace(D_arr_0p5, 0.5, size=1)
    +def_gen.laplace(0.5, D_arr_0p5, size=1)
    +def_gen.laplace(D_arr_like_0p5, 0.5)
    +def_gen.laplace(0.5, D_arr_like_0p5)
    +def_gen.laplace(D_arr_0p5, D_arr_0p5)
    +def_gen.laplace(D_arr_like_0p5, D_arr_like_0p5)
    +def_gen.laplace(D_arr_0p5, D_arr_0p5, size=1)
    +def_gen.laplace(D_arr_like_0p5, D_arr_like_0p5, size=1)
    +
    +def_gen.logistic(0.5, 0.5)
    +def_gen.logistic(0.5, 0.5, size=None)
    +def_gen.logistic(0.5, 0.5, size=1)
    +def_gen.logistic(D_arr_0p5, 0.5)
    +def_gen.logistic(0.5, D_arr_0p5)
    +def_gen.logistic(D_arr_0p5, 0.5, size=1)
    +def_gen.logistic(0.5, D_arr_0p5, size=1)
    +def_gen.logistic(D_arr_like_0p5, 0.5)
    +def_gen.logistic(0.5, D_arr_like_0p5)
    +def_gen.logistic(D_arr_0p5, D_arr_0p5)
    +def_gen.logistic(D_arr_like_0p5, D_arr_like_0p5)
    +def_gen.logistic(D_arr_0p5, D_arr_0p5, size=1)
    +def_gen.logistic(D_arr_like_0p5, D_arr_like_0p5, size=1)
    +
    +def_gen.lognormal(0.5, 0.5)
    +def_gen.lognormal(0.5, 0.5, size=None)
    +def_gen.lognormal(0.5, 0.5, size=1)
    +def_gen.lognormal(D_arr_0p5, 0.5)
    +def_gen.lognormal(0.5, D_arr_0p5)
    +def_gen.lognormal(D_arr_0p5, 0.5, size=1)
    +def_gen.lognormal(0.5, D_arr_0p5, size=1)
    +def_gen.lognormal(D_arr_like_0p5, 0.5)
    +def_gen.lognormal(0.5, D_arr_like_0p5)
    +def_gen.lognormal(D_arr_0p5, D_arr_0p5)
    +def_gen.lognormal(D_arr_like_0p5, D_arr_like_0p5)
    +def_gen.lognormal(D_arr_0p5, D_arr_0p5, size=1)
    +def_gen.lognormal(D_arr_like_0p5, D_arr_like_0p5, size=1)
    +
    +def_gen.noncentral_chisquare(0.5, 0.5)
    +def_gen.noncentral_chisquare(0.5, 0.5, size=None)
    +def_gen.noncentral_chisquare(0.5, 0.5, size=1)
    +def_gen.noncentral_chisquare(D_arr_0p5, 0.5)
    +def_gen.noncentral_chisquare(0.5, D_arr_0p5)
    +def_gen.noncentral_chisquare(D_arr_0p5, 0.5, size=1)
    +def_gen.noncentral_chisquare(0.5, D_arr_0p5, size=1)
    +def_gen.noncentral_chisquare(D_arr_like_0p5, 0.5)
    +def_gen.noncentral_chisquare(0.5, D_arr_like_0p5)
    +def_gen.noncentral_chisquare(D_arr_0p5, D_arr_0p5)
    +def_gen.noncentral_chisquare(D_arr_like_0p5, D_arr_like_0p5)
    +def_gen.noncentral_chisquare(D_arr_0p5, D_arr_0p5, size=1)
    +def_gen.noncentral_chisquare(D_arr_like_0p5, D_arr_like_0p5, size=1)
    +
    +def_gen.normal(0.5, 0.5)
    +def_gen.normal(0.5, 0.5, size=None)
    +def_gen.normal(0.5, 0.5, size=1)
    +def_gen.normal(D_arr_0p5, 0.5)
    +def_gen.normal(0.5, D_arr_0p5)
    +def_gen.normal(D_arr_0p5, 0.5, size=1)
    +def_gen.normal(0.5, D_arr_0p5, size=1)
    +def_gen.normal(D_arr_like_0p5, 0.5)
    +def_gen.normal(0.5, D_arr_like_0p5)
    +def_gen.normal(D_arr_0p5, D_arr_0p5)
    +def_gen.normal(D_arr_like_0p5, D_arr_like_0p5)
    +def_gen.normal(D_arr_0p5, D_arr_0p5, size=1)
    +def_gen.normal(D_arr_like_0p5, D_arr_like_0p5, size=1)
    +
    +def_gen.triangular(0.1, 0.5, 0.9)
    +def_gen.triangular(0.1, 0.5, 0.9, size=None)
    +def_gen.triangular(0.1, 0.5, 0.9, size=1)
    +def_gen.triangular(D_arr_0p1, 0.5, 0.9)
    +def_gen.triangular(0.1, D_arr_0p5, 0.9)
    +def_gen.triangular(D_arr_0p1, 0.5, D_arr_like_0p9, size=1)
    +def_gen.triangular(0.1, D_arr_0p5, 0.9, size=1)
    +def_gen.triangular(D_arr_like_0p1, 0.5, D_arr_0p9)
    +def_gen.triangular(0.5, D_arr_like_0p5, 0.9)
    +def_gen.triangular(D_arr_0p1, D_arr_0p5, 0.9)
    +def_gen.triangular(D_arr_like_0p1, D_arr_like_0p5, 0.9)
    +def_gen.triangular(D_arr_0p1, D_arr_0p5, D_arr_0p9, size=1)
    +def_gen.triangular(D_arr_like_0p1, D_arr_like_0p5, D_arr_like_0p9, size=1)
    +
    +def_gen.noncentral_f(0.1, 0.5, 0.9)
    +def_gen.noncentral_f(0.1, 0.5, 0.9, size=None)
    +def_gen.noncentral_f(0.1, 0.5, 0.9, size=1)
    +def_gen.noncentral_f(D_arr_0p1, 0.5, 0.9)
    +def_gen.noncentral_f(0.1, D_arr_0p5, 0.9)
    +def_gen.noncentral_f(D_arr_0p1, 0.5, D_arr_like_0p9, size=1)
    +def_gen.noncentral_f(0.1, D_arr_0p5, 0.9, size=1)
    +def_gen.noncentral_f(D_arr_like_0p1, 0.5, D_arr_0p9)
    +def_gen.noncentral_f(0.5, D_arr_like_0p5, 0.9)
    +def_gen.noncentral_f(D_arr_0p1, D_arr_0p5, 0.9)
    +def_gen.noncentral_f(D_arr_like_0p1, D_arr_like_0p5, 0.9)
    +def_gen.noncentral_f(D_arr_0p1, D_arr_0p5, D_arr_0p9, size=1)
    +def_gen.noncentral_f(D_arr_like_0p1, D_arr_like_0p5, D_arr_like_0p9, size=1)
    +
    +def_gen.binomial(10, 0.5)
    +def_gen.binomial(10, 0.5, size=None)
    +def_gen.binomial(10, 0.5, size=1)
    +def_gen.binomial(I_arr_10, 0.5)
    +def_gen.binomial(10, D_arr_0p5)
    +def_gen.binomial(I_arr_10, 0.5, size=1)
    +def_gen.binomial(10, D_arr_0p5, size=1)
    +def_gen.binomial(I_arr_like_10, 0.5)
    +def_gen.binomial(10, D_arr_like_0p5)
    +def_gen.binomial(I_arr_10, D_arr_0p5)
    +def_gen.binomial(I_arr_like_10, D_arr_like_0p5)
    +def_gen.binomial(I_arr_10, D_arr_0p5, size=1)
    +def_gen.binomial(I_arr_like_10, D_arr_like_0p5, size=1)
    +
    +def_gen.negative_binomial(10, 0.5)
    +def_gen.negative_binomial(10, 0.5, size=None)
    +def_gen.negative_binomial(10, 0.5, size=1)
    +def_gen.negative_binomial(I_arr_10, 0.5)
    +def_gen.negative_binomial(10, D_arr_0p5)
    +def_gen.negative_binomial(I_arr_10, 0.5, size=1)
    +def_gen.negative_binomial(10, D_arr_0p5, size=1)
    +def_gen.negative_binomial(I_arr_like_10, 0.5)
    +def_gen.negative_binomial(10, D_arr_like_0p5)
    +def_gen.negative_binomial(I_arr_10, D_arr_0p5)
    +def_gen.negative_binomial(I_arr_like_10, D_arr_like_0p5)
    +def_gen.negative_binomial(I_arr_10, D_arr_0p5, size=1)
    +def_gen.negative_binomial(I_arr_like_10, D_arr_like_0p5, size=1)
    +
    +def_gen.hypergeometric(20, 20, 10)
    +def_gen.hypergeometric(20, 20, 10, size=None)
    +def_gen.hypergeometric(20, 20, 10, size=1)
    +def_gen.hypergeometric(I_arr_20, 20, 10)
    +def_gen.hypergeometric(20, I_arr_20, 10)
    +def_gen.hypergeometric(I_arr_20, 20, I_arr_like_10, size=1)
    +def_gen.hypergeometric(20, I_arr_20, 10, size=1)
    +def_gen.hypergeometric(I_arr_like_20, 20, I_arr_10)
    +def_gen.hypergeometric(20, I_arr_like_20, 10)
    +def_gen.hypergeometric(I_arr_20, I_arr_20, 10)
    +def_gen.hypergeometric(I_arr_like_20, I_arr_like_20, 10)
    +def_gen.hypergeometric(I_arr_20, I_arr_20, I_arr_10, size=1)
    +def_gen.hypergeometric(I_arr_like_20, I_arr_like_20, I_arr_like_10, size=1)
    +
    +I_int64_100: np.ndarray[Any, np.dtype[np.int64]] = np.array([100], dtype=np.int64)
    +
    +def_gen.integers(0, 100)
    +def_gen.integers(100)
    +def_gen.integers([100])
    +def_gen.integers(0, [100])
    +
    +I_bool_low: np.ndarray[Any, np.dtype[np.bool]] = np.array([0], dtype=np.bool)
    +I_bool_low_like: list[int] = [0]
    +I_bool_high_open: np.ndarray[Any, np.dtype[np.bool]] = np.array([1], dtype=np.bool)
    +I_bool_high_closed: np.ndarray[Any, np.dtype[np.bool]] = np.array([1], dtype=np.bool)
    +
    +def_gen.integers(2, dtype=bool)
    +def_gen.integers(0, 2, dtype=bool)
    +def_gen.integers(1, dtype=bool, endpoint=True)
    +def_gen.integers(0, 1, dtype=bool, endpoint=True)
    +def_gen.integers(I_bool_low_like, 1, dtype=bool, endpoint=True)
    +def_gen.integers(I_bool_high_open, dtype=bool)
    +def_gen.integers(I_bool_low, I_bool_high_open, dtype=bool)
    +def_gen.integers(0, I_bool_high_open, dtype=bool)
    +def_gen.integers(I_bool_high_closed, dtype=bool, endpoint=True)
    +def_gen.integers(I_bool_low, I_bool_high_closed, dtype=bool, endpoint=True)
    +def_gen.integers(0, I_bool_high_closed, dtype=bool, endpoint=True)
    +
    +def_gen.integers(2, dtype=np.bool)
    +def_gen.integers(0, 2, dtype=np.bool)
    +def_gen.integers(1, dtype=np.bool, endpoint=True)
    +def_gen.integers(0, 1, dtype=np.bool, endpoint=True)
    +def_gen.integers(I_bool_low_like, 1, dtype=np.bool, endpoint=True)
    +def_gen.integers(I_bool_high_open, dtype=np.bool)
    +def_gen.integers(I_bool_low, I_bool_high_open, dtype=np.bool)
    +def_gen.integers(0, I_bool_high_open, dtype=np.bool)
    +def_gen.integers(I_bool_high_closed, dtype=np.bool, endpoint=True)
    +def_gen.integers(I_bool_low, I_bool_high_closed, dtype=np.bool, endpoint=True)
    +def_gen.integers(0, I_bool_high_closed, dtype=np.bool, endpoint=True)
    +
    +I_u1_low: np.ndarray[Any, np.dtype[np.uint8]] = np.array([0], dtype=np.uint8)
    +I_u1_low_like: list[int] = [0]
    +I_u1_high_open: np.ndarray[Any, np.dtype[np.uint8]] = np.array([255], dtype=np.uint8)
    +I_u1_high_closed: np.ndarray[Any, np.dtype[np.uint8]] = np.array([255], dtype=np.uint8)
    +
    +def_gen.integers(256, dtype="u1")
    +def_gen.integers(0, 256, dtype="u1")
    +def_gen.integers(255, dtype="u1", endpoint=True)
    +def_gen.integers(0, 255, dtype="u1", endpoint=True)
    +def_gen.integers(I_u1_low_like, 255, dtype="u1", endpoint=True)
    +def_gen.integers(I_u1_high_open, dtype="u1")
    +def_gen.integers(I_u1_low, I_u1_high_open, dtype="u1")
    +def_gen.integers(0, I_u1_high_open, dtype="u1")
    +def_gen.integers(I_u1_high_closed, dtype="u1", endpoint=True)
    +def_gen.integers(I_u1_low, I_u1_high_closed, dtype="u1", endpoint=True)
    +def_gen.integers(0, I_u1_high_closed, dtype="u1", endpoint=True)
    +
    +def_gen.integers(256, dtype="uint8")
    +def_gen.integers(0, 256, dtype="uint8")
    +def_gen.integers(255, dtype="uint8", endpoint=True)
    +def_gen.integers(0, 255, dtype="uint8", endpoint=True)
    +def_gen.integers(I_u1_low_like, 255, dtype="uint8", endpoint=True)
    +def_gen.integers(I_u1_high_open, dtype="uint8")
    +def_gen.integers(I_u1_low, I_u1_high_open, dtype="uint8")
    +def_gen.integers(0, I_u1_high_open, dtype="uint8")
    +def_gen.integers(I_u1_high_closed, dtype="uint8", endpoint=True)
    +def_gen.integers(I_u1_low, I_u1_high_closed, dtype="uint8", endpoint=True)
    +def_gen.integers(0, I_u1_high_closed, dtype="uint8", endpoint=True)
    +
    +def_gen.integers(256, dtype=np.uint8)
    +def_gen.integers(0, 256, dtype=np.uint8)
    +def_gen.integers(255, dtype=np.uint8, endpoint=True)
    +def_gen.integers(0, 255, dtype=np.uint8, endpoint=True)
    +def_gen.integers(I_u1_low_like, 255, dtype=np.uint8, endpoint=True)
    +def_gen.integers(I_u1_high_open, dtype=np.uint8)
    +def_gen.integers(I_u1_low, I_u1_high_open, dtype=np.uint8)
    +def_gen.integers(0, I_u1_high_open, dtype=np.uint8)
    +def_gen.integers(I_u1_high_closed, dtype=np.uint8, endpoint=True)
    +def_gen.integers(I_u1_low, I_u1_high_closed, dtype=np.uint8, endpoint=True)
    +def_gen.integers(0, I_u1_high_closed, dtype=np.uint8, endpoint=True)
    +
    +I_u2_low: np.ndarray[Any, np.dtype[np.uint16]] = np.array([0], dtype=np.uint16)
    +I_u2_low_like: list[int] = [0]
    +I_u2_high_open: np.ndarray[Any, np.dtype[np.uint16]] = np.array([65535], dtype=np.uint16)
    +I_u2_high_closed: np.ndarray[Any, np.dtype[np.uint16]] = np.array([65535], dtype=np.uint16)
    +
    +def_gen.integers(65536, dtype="u2")
    +def_gen.integers(0, 65536, dtype="u2")
    +def_gen.integers(65535, dtype="u2", endpoint=True)
    +def_gen.integers(0, 65535, dtype="u2", endpoint=True)
    +def_gen.integers(I_u2_low_like, 65535, dtype="u2", endpoint=True)
    +def_gen.integers(I_u2_high_open, dtype="u2")
    +def_gen.integers(I_u2_low, I_u2_high_open, dtype="u2")
    +def_gen.integers(0, I_u2_high_open, dtype="u2")
    +def_gen.integers(I_u2_high_closed, dtype="u2", endpoint=True)
    +def_gen.integers(I_u2_low, I_u2_high_closed, dtype="u2", endpoint=True)
    +def_gen.integers(0, I_u2_high_closed, dtype="u2", endpoint=True)
    +
    +def_gen.integers(65536, dtype="uint16")
    +def_gen.integers(0, 65536, dtype="uint16")
    +def_gen.integers(65535, dtype="uint16", endpoint=True)
    +def_gen.integers(0, 65535, dtype="uint16", endpoint=True)
    +def_gen.integers(I_u2_low_like, 65535, dtype="uint16", endpoint=True)
    +def_gen.integers(I_u2_high_open, dtype="uint16")
    +def_gen.integers(I_u2_low, I_u2_high_open, dtype="uint16")
    +def_gen.integers(0, I_u2_high_open, dtype="uint16")
    +def_gen.integers(I_u2_high_closed, dtype="uint16", endpoint=True)
    +def_gen.integers(I_u2_low, I_u2_high_closed, dtype="uint16", endpoint=True)
    +def_gen.integers(0, I_u2_high_closed, dtype="uint16", endpoint=True)
    +
    +def_gen.integers(65536, dtype=np.uint16)
    +def_gen.integers(0, 65536, dtype=np.uint16)
    +def_gen.integers(65535, dtype=np.uint16, endpoint=True)
    +def_gen.integers(0, 65535, dtype=np.uint16, endpoint=True)
    +def_gen.integers(I_u2_low_like, 65535, dtype=np.uint16, endpoint=True)
    +def_gen.integers(I_u2_high_open, dtype=np.uint16)
    +def_gen.integers(I_u2_low, I_u2_high_open, dtype=np.uint16)
    +def_gen.integers(0, I_u2_high_open, dtype=np.uint16)
    +def_gen.integers(I_u2_high_closed, dtype=np.uint16, endpoint=True)
    +def_gen.integers(I_u2_low, I_u2_high_closed, dtype=np.uint16, endpoint=True)
    +def_gen.integers(0, I_u2_high_closed, dtype=np.uint16, endpoint=True)
    +
    +I_u4_low: np.ndarray[Any, np.dtype[np.uint32]] = np.array([0], dtype=np.uint32)
    +I_u4_low_like: list[int] = [0]
    +I_u4_high_open: np.ndarray[Any, np.dtype[np.uint32]] = np.array([4294967295], dtype=np.uint32)
    +I_u4_high_closed: np.ndarray[Any, np.dtype[np.uint32]] = np.array([4294967295], dtype=np.uint32)
    +
    +def_gen.integers(4294967296, dtype="u4")
    +def_gen.integers(0, 4294967296, dtype="u4")
    +def_gen.integers(4294967295, dtype="u4", endpoint=True)
    +def_gen.integers(0, 4294967295, dtype="u4", endpoint=True)
    +def_gen.integers(I_u4_low_like, 4294967295, dtype="u4", endpoint=True)
    +def_gen.integers(I_u4_high_open, dtype="u4")
    +def_gen.integers(I_u4_low, I_u4_high_open, dtype="u4")
    +def_gen.integers(0, I_u4_high_open, dtype="u4")
    +def_gen.integers(I_u4_high_closed, dtype="u4", endpoint=True)
    +def_gen.integers(I_u4_low, I_u4_high_closed, dtype="u4", endpoint=True)
    +def_gen.integers(0, I_u4_high_closed, dtype="u4", endpoint=True)
    +
    +def_gen.integers(4294967296, dtype="uint32")
    +def_gen.integers(0, 4294967296, dtype="uint32")
    +def_gen.integers(4294967295, dtype="uint32", endpoint=True)
    +def_gen.integers(0, 4294967295, dtype="uint32", endpoint=True)
    +def_gen.integers(I_u4_low_like, 4294967295, dtype="uint32", endpoint=True)
    +def_gen.integers(I_u4_high_open, dtype="uint32")
    +def_gen.integers(I_u4_low, I_u4_high_open, dtype="uint32")
    +def_gen.integers(0, I_u4_high_open, dtype="uint32")
    +def_gen.integers(I_u4_high_closed, dtype="uint32", endpoint=True)
    +def_gen.integers(I_u4_low, I_u4_high_closed, dtype="uint32", endpoint=True)
    +def_gen.integers(0, I_u4_high_closed, dtype="uint32", endpoint=True)
    +
    +def_gen.integers(4294967296, dtype=np.uint32)
    +def_gen.integers(0, 4294967296, dtype=np.uint32)
    +def_gen.integers(4294967295, dtype=np.uint32, endpoint=True)
    +def_gen.integers(0, 4294967295, dtype=np.uint32, endpoint=True)
    +def_gen.integers(I_u4_low_like, 4294967295, dtype=np.uint32, endpoint=True)
    +def_gen.integers(I_u4_high_open, dtype=np.uint32)
    +def_gen.integers(I_u4_low, I_u4_high_open, dtype=np.uint32)
    +def_gen.integers(0, I_u4_high_open, dtype=np.uint32)
    +def_gen.integers(I_u4_high_closed, dtype=np.uint32, endpoint=True)
    +def_gen.integers(I_u4_low, I_u4_high_closed, dtype=np.uint32, endpoint=True)
    +def_gen.integers(0, I_u4_high_closed, dtype=np.uint32, endpoint=True)
    +
    +I_u8_low: np.ndarray[Any, np.dtype[np.uint64]] = np.array([0], dtype=np.uint64)
    +I_u8_low_like: list[int] = [0]
    +I_u8_high_open: np.ndarray[Any, np.dtype[np.uint64]] = np.array([18446744073709551615], dtype=np.uint64)
    +I_u8_high_closed: np.ndarray[Any, np.dtype[np.uint64]] = np.array([18446744073709551615], dtype=np.uint64)
    +
    +def_gen.integers(18446744073709551616, dtype="u8")
    +def_gen.integers(0, 18446744073709551616, dtype="u8")
    +def_gen.integers(18446744073709551615, dtype="u8", endpoint=True)
    +def_gen.integers(0, 18446744073709551615, dtype="u8", endpoint=True)
    +def_gen.integers(I_u8_low_like, 18446744073709551615, dtype="u8", endpoint=True)
    +def_gen.integers(I_u8_high_open, dtype="u8")
    +def_gen.integers(I_u8_low, I_u8_high_open, dtype="u8")
    +def_gen.integers(0, I_u8_high_open, dtype="u8")
    +def_gen.integers(I_u8_high_closed, dtype="u8", endpoint=True)
    +def_gen.integers(I_u8_low, I_u8_high_closed, dtype="u8", endpoint=True)
    +def_gen.integers(0, I_u8_high_closed, dtype="u8", endpoint=True)
    +
    +def_gen.integers(18446744073709551616, dtype="uint64")
    +def_gen.integers(0, 18446744073709551616, dtype="uint64")
    +def_gen.integers(18446744073709551615, dtype="uint64", endpoint=True)
    +def_gen.integers(0, 18446744073709551615, dtype="uint64", endpoint=True)
    +def_gen.integers(I_u8_low_like, 18446744073709551615, dtype="uint64", endpoint=True)
    +def_gen.integers(I_u8_high_open, dtype="uint64")
    +def_gen.integers(I_u8_low, I_u8_high_open, dtype="uint64")
    +def_gen.integers(0, I_u8_high_open, dtype="uint64")
    +def_gen.integers(I_u8_high_closed, dtype="uint64", endpoint=True)
    +def_gen.integers(I_u8_low, I_u8_high_closed, dtype="uint64", endpoint=True)
    +def_gen.integers(0, I_u8_high_closed, dtype="uint64", endpoint=True)
    +
    +def_gen.integers(18446744073709551616, dtype=np.uint64)
    +def_gen.integers(0, 18446744073709551616, dtype=np.uint64)
    +def_gen.integers(18446744073709551615, dtype=np.uint64, endpoint=True)
    +def_gen.integers(0, 18446744073709551615, dtype=np.uint64, endpoint=True)
    +def_gen.integers(I_u8_low_like, 18446744073709551615, dtype=np.uint64, endpoint=True)
    +def_gen.integers(I_u8_high_open, dtype=np.uint64)
    +def_gen.integers(I_u8_low, I_u8_high_open, dtype=np.uint64)
    +def_gen.integers(0, I_u8_high_open, dtype=np.uint64)
    +def_gen.integers(I_u8_high_closed, dtype=np.uint64, endpoint=True)
    +def_gen.integers(I_u8_low, I_u8_high_closed, dtype=np.uint64, endpoint=True)
    +def_gen.integers(0, I_u8_high_closed, dtype=np.uint64, endpoint=True)
    +
    +I_i1_low: np.ndarray[Any, np.dtype[np.int8]] = np.array([-128], dtype=np.int8)
    +I_i1_low_like: list[int] = [-128]
    +I_i1_high_open: np.ndarray[Any, np.dtype[np.int8]] = np.array([127], dtype=np.int8)
    +I_i1_high_closed: np.ndarray[Any, np.dtype[np.int8]] = np.array([127], dtype=np.int8)
    +
    +def_gen.integers(128, dtype="i1")
    +def_gen.integers(-128, 128, dtype="i1")
    +def_gen.integers(127, dtype="i1", endpoint=True)
    +def_gen.integers(-128, 127, dtype="i1", endpoint=True)
    +def_gen.integers(I_i1_low_like, 127, dtype="i1", endpoint=True)
    +def_gen.integers(I_i1_high_open, dtype="i1")
    +def_gen.integers(I_i1_low, I_i1_high_open, dtype="i1")
    +def_gen.integers(-128, I_i1_high_open, dtype="i1")
    +def_gen.integers(I_i1_high_closed, dtype="i1", endpoint=True)
    +def_gen.integers(I_i1_low, I_i1_high_closed, dtype="i1", endpoint=True)
    +def_gen.integers(-128, I_i1_high_closed, dtype="i1", endpoint=True)
    +
    +def_gen.integers(128, dtype="int8")
    +def_gen.integers(-128, 128, dtype="int8")
    +def_gen.integers(127, dtype="int8", endpoint=True)
    +def_gen.integers(-128, 127, dtype="int8", endpoint=True)
    +def_gen.integers(I_i1_low_like, 127, dtype="int8", endpoint=True)
    +def_gen.integers(I_i1_high_open, dtype="int8")
    +def_gen.integers(I_i1_low, I_i1_high_open, dtype="int8")
    +def_gen.integers(-128, I_i1_high_open, dtype="int8")
    +def_gen.integers(I_i1_high_closed, dtype="int8", endpoint=True)
    +def_gen.integers(I_i1_low, I_i1_high_closed, dtype="int8", endpoint=True)
    +def_gen.integers(-128, I_i1_high_closed, dtype="int8", endpoint=True)
    +
    +def_gen.integers(128, dtype=np.int8)
    +def_gen.integers(-128, 128, dtype=np.int8)
    +def_gen.integers(127, dtype=np.int8, endpoint=True)
    +def_gen.integers(-128, 127, dtype=np.int8, endpoint=True)
    +def_gen.integers(I_i1_low_like, 127, dtype=np.int8, endpoint=True)
    +def_gen.integers(I_i1_high_open, dtype=np.int8)
    +def_gen.integers(I_i1_low, I_i1_high_open, dtype=np.int8)
    +def_gen.integers(-128, I_i1_high_open, dtype=np.int8)
    +def_gen.integers(I_i1_high_closed, dtype=np.int8, endpoint=True)
    +def_gen.integers(I_i1_low, I_i1_high_closed, dtype=np.int8, endpoint=True)
    +def_gen.integers(-128, I_i1_high_closed, dtype=np.int8, endpoint=True)
    +
    +I_i2_low: np.ndarray[Any, np.dtype[np.int16]] = np.array([-32768], dtype=np.int16)
    +I_i2_low_like: list[int] = [-32768]
    +I_i2_high_open: np.ndarray[Any, np.dtype[np.int16]] = np.array([32767], dtype=np.int16)
    +I_i2_high_closed: np.ndarray[Any, np.dtype[np.int16]] = np.array([32767], dtype=np.int16)
    +
    +def_gen.integers(32768, dtype="i2")
    +def_gen.integers(-32768, 32768, dtype="i2")
    +def_gen.integers(32767, dtype="i2", endpoint=True)
    +def_gen.integers(-32768, 32767, dtype="i2", endpoint=True)
    +def_gen.integers(I_i2_low_like, 32767, dtype="i2", endpoint=True)
    +def_gen.integers(I_i2_high_open, dtype="i2")
    +def_gen.integers(I_i2_low, I_i2_high_open, dtype="i2")
    +def_gen.integers(-32768, I_i2_high_open, dtype="i2")
    +def_gen.integers(I_i2_high_closed, dtype="i2", endpoint=True)
    +def_gen.integers(I_i2_low, I_i2_high_closed, dtype="i2", endpoint=True)
    +def_gen.integers(-32768, I_i2_high_closed, dtype="i2", endpoint=True)
    +
    +def_gen.integers(32768, dtype="int16")
    +def_gen.integers(-32768, 32768, dtype="int16")
    +def_gen.integers(32767, dtype="int16", endpoint=True)
    +def_gen.integers(-32768, 32767, dtype="int16", endpoint=True)
    +def_gen.integers(I_i2_low_like, 32767, dtype="int16", endpoint=True)
    +def_gen.integers(I_i2_high_open, dtype="int16")
    +def_gen.integers(I_i2_low, I_i2_high_open, dtype="int16")
    +def_gen.integers(-32768, I_i2_high_open, dtype="int16")
    +def_gen.integers(I_i2_high_closed, dtype="int16", endpoint=True)
    +def_gen.integers(I_i2_low, I_i2_high_closed, dtype="int16", endpoint=True)
    +def_gen.integers(-32768, I_i2_high_closed, dtype="int16", endpoint=True)
    +
    +def_gen.integers(32768, dtype=np.int16)
    +def_gen.integers(-32768, 32768, dtype=np.int16)
    +def_gen.integers(32767, dtype=np.int16, endpoint=True)
    +def_gen.integers(-32768, 32767, dtype=np.int16, endpoint=True)
    +def_gen.integers(I_i2_low_like, 32767, dtype=np.int16, endpoint=True)
    +def_gen.integers(I_i2_high_open, dtype=np.int16)
    +def_gen.integers(I_i2_low, I_i2_high_open, dtype=np.int16)
    +def_gen.integers(-32768, I_i2_high_open, dtype=np.int16)
    +def_gen.integers(I_i2_high_closed, dtype=np.int16, endpoint=True)
    +def_gen.integers(I_i2_low, I_i2_high_closed, dtype=np.int16, endpoint=True)
    +def_gen.integers(-32768, I_i2_high_closed, dtype=np.int16, endpoint=True)
    +
    +I_i4_low: np.ndarray[Any, np.dtype[np.int32]] = np.array([-2147483648], dtype=np.int32)
    +I_i4_low_like: list[int] = [-2147483648]
    +I_i4_high_open: np.ndarray[Any, np.dtype[np.int32]] = np.array([2147483647], dtype=np.int32)
    +I_i4_high_closed: np.ndarray[Any, np.dtype[np.int32]] = np.array([2147483647], dtype=np.int32)
    +
    +def_gen.integers(2147483648, dtype="i4")
    +def_gen.integers(-2147483648, 2147483648, dtype="i4")
    +def_gen.integers(2147483647, dtype="i4", endpoint=True)
    +def_gen.integers(-2147483648, 2147483647, dtype="i4", endpoint=True)
    +def_gen.integers(I_i4_low_like, 2147483647, dtype="i4", endpoint=True)
    +def_gen.integers(I_i4_high_open, dtype="i4")
    +def_gen.integers(I_i4_low, I_i4_high_open, dtype="i4")
    +def_gen.integers(-2147483648, I_i4_high_open, dtype="i4")
    +def_gen.integers(I_i4_high_closed, dtype="i4", endpoint=True)
    +def_gen.integers(I_i4_low, I_i4_high_closed, dtype="i4", endpoint=True)
    +def_gen.integers(-2147483648, I_i4_high_closed, dtype="i4", endpoint=True)
    +
    +def_gen.integers(2147483648, dtype="int32")
    +def_gen.integers(-2147483648, 2147483648, dtype="int32")
    +def_gen.integers(2147483647, dtype="int32", endpoint=True)
    +def_gen.integers(-2147483648, 2147483647, dtype="int32", endpoint=True)
    +def_gen.integers(I_i4_low_like, 2147483647, dtype="int32", endpoint=True)
    +def_gen.integers(I_i4_high_open, dtype="int32")
    +def_gen.integers(I_i4_low, I_i4_high_open, dtype="int32")
    +def_gen.integers(-2147483648, I_i4_high_open, dtype="int32")
    +def_gen.integers(I_i4_high_closed, dtype="int32", endpoint=True)
    +def_gen.integers(I_i4_low, I_i4_high_closed, dtype="int32", endpoint=True)
    +def_gen.integers(-2147483648, I_i4_high_closed, dtype="int32", endpoint=True)
    +
    +def_gen.integers(2147483648, dtype=np.int32)
    +def_gen.integers(-2147483648, 2147483648, dtype=np.int32)
    +def_gen.integers(2147483647, dtype=np.int32, endpoint=True)
    +def_gen.integers(-2147483648, 2147483647, dtype=np.int32, endpoint=True)
    +def_gen.integers(I_i4_low_like, 2147483647, dtype=np.int32, endpoint=True)
    +def_gen.integers(I_i4_high_open, dtype=np.int32)
    +def_gen.integers(I_i4_low, I_i4_high_open, dtype=np.int32)
    +def_gen.integers(-2147483648, I_i4_high_open, dtype=np.int32)
    +def_gen.integers(I_i4_high_closed, dtype=np.int32, endpoint=True)
    +def_gen.integers(I_i4_low, I_i4_high_closed, dtype=np.int32, endpoint=True)
    +def_gen.integers(-2147483648, I_i4_high_closed, dtype=np.int32, endpoint=True)
    +
    +I_i8_low: np.ndarray[Any, np.dtype[np.int64]] = np.array([-9223372036854775808], dtype=np.int64)
    +I_i8_low_like: list[int] = [-9223372036854775808]
    +I_i8_high_open: np.ndarray[Any, np.dtype[np.int64]] = np.array([9223372036854775807], dtype=np.int64)
    +I_i8_high_closed: np.ndarray[Any, np.dtype[np.int64]] = np.array([9223372036854775807], dtype=np.int64)
    +
    +def_gen.integers(9223372036854775808, dtype="i8")
    +def_gen.integers(-9223372036854775808, 9223372036854775808, dtype="i8")
    +def_gen.integers(9223372036854775807, dtype="i8", endpoint=True)
    +def_gen.integers(-9223372036854775808, 9223372036854775807, dtype="i8", endpoint=True)
    +def_gen.integers(I_i8_low_like, 9223372036854775807, dtype="i8", endpoint=True)
    +def_gen.integers(I_i8_high_open, dtype="i8")
    +def_gen.integers(I_i8_low, I_i8_high_open, dtype="i8")
    +def_gen.integers(-9223372036854775808, I_i8_high_open, dtype="i8")
    +def_gen.integers(I_i8_high_closed, dtype="i8", endpoint=True)
    +def_gen.integers(I_i8_low, I_i8_high_closed, dtype="i8", endpoint=True)
    +def_gen.integers(-9223372036854775808, I_i8_high_closed, dtype="i8", endpoint=True)
    +
    +def_gen.integers(9223372036854775808, dtype="int64")
    +def_gen.integers(-9223372036854775808, 9223372036854775808, dtype="int64")
    +def_gen.integers(9223372036854775807, dtype="int64", endpoint=True)
    +def_gen.integers(-9223372036854775808, 9223372036854775807, dtype="int64", endpoint=True)
    +def_gen.integers(I_i8_low_like, 9223372036854775807, dtype="int64", endpoint=True)
    +def_gen.integers(I_i8_high_open, dtype="int64")
    +def_gen.integers(I_i8_low, I_i8_high_open, dtype="int64")
    +def_gen.integers(-9223372036854775808, I_i8_high_open, dtype="int64")
    +def_gen.integers(I_i8_high_closed, dtype="int64", endpoint=True)
    +def_gen.integers(I_i8_low, I_i8_high_closed, dtype="int64", endpoint=True)
    +def_gen.integers(-9223372036854775808, I_i8_high_closed, dtype="int64", endpoint=True)
    +
    +def_gen.integers(9223372036854775808, dtype=np.int64)
    +def_gen.integers(-9223372036854775808, 9223372036854775808, dtype=np.int64)
    +def_gen.integers(9223372036854775807, dtype=np.int64, endpoint=True)
    +def_gen.integers(-9223372036854775808, 9223372036854775807, dtype=np.int64, endpoint=True)
    +def_gen.integers(I_i8_low_like, 9223372036854775807, dtype=np.int64, endpoint=True)
    +def_gen.integers(I_i8_high_open, dtype=np.int64)
    +def_gen.integers(I_i8_low, I_i8_high_open, dtype=np.int64)
    +def_gen.integers(-9223372036854775808, I_i8_high_open, dtype=np.int64)
    +def_gen.integers(I_i8_high_closed, dtype=np.int64, endpoint=True)
    +def_gen.integers(I_i8_low, I_i8_high_closed, dtype=np.int64, endpoint=True)
    +def_gen.integers(-9223372036854775808, I_i8_high_closed, dtype=np.int64, endpoint=True)
    +
    +
    +def_gen.bit_generator
    +
    +def_gen.bytes(2)
    +
    +def_gen.choice(5)
    +def_gen.choice(5, 3)
    +def_gen.choice(5, 3, replace=True)
    +def_gen.choice(5, 3, p=[1 / 5] * 5)
    +def_gen.choice(5, 3, p=[1 / 5] * 5, replace=False)
    +
    +def_gen.choice(["pooh", "rabbit", "piglet", "Christopher"])
    +def_gen.choice(["pooh", "rabbit", "piglet", "Christopher"], 3)
    +def_gen.choice(["pooh", "rabbit", "piglet", "Christopher"], 3, p=[1 / 4] * 4)
    +def_gen.choice(["pooh", "rabbit", "piglet", "Christopher"], 3, replace=True)
    +def_gen.choice(["pooh", "rabbit", "piglet", "Christopher"], 3, replace=False, p=np.array([1 / 8, 1 / 8, 1 / 2, 1 / 4]))
    +
    +def_gen.dirichlet([0.5, 0.5])
    +def_gen.dirichlet(np.array([0.5, 0.5]))
    +def_gen.dirichlet(np.array([0.5, 0.5]), size=3)
    +
    +def_gen.multinomial(20, [1 / 6.0] * 6)
    +def_gen.multinomial(20, np.array([0.5, 0.5]))
    +def_gen.multinomial(20, [1 / 6.0] * 6, size=2)
    +def_gen.multinomial([[10], [20]], [1 / 6.0] * 6, size=(2, 2))
    +def_gen.multinomial(np.array([[10], [20]]), np.array([0.5, 0.5]), size=(2, 2))
    +
    +def_gen.multivariate_hypergeometric([3, 5, 7], 2)
    +def_gen.multivariate_hypergeometric(np.array([3, 5, 7]), 2)
    +def_gen.multivariate_hypergeometric(np.array([3, 5, 7]), 2, size=4)
    +def_gen.multivariate_hypergeometric(np.array([3, 5, 7]), 2, size=(4, 7))
    +def_gen.multivariate_hypergeometric([3, 5, 7], 2, method="count")
    +def_gen.multivariate_hypergeometric(np.array([3, 5, 7]), 2, method="marginals")
    +
    +def_gen.multivariate_normal([0.0], [[1.0]])
    +def_gen.multivariate_normal([0.0], np.array([[1.0]]))
    +def_gen.multivariate_normal(np.array([0.0]), [[1.0]])
    +def_gen.multivariate_normal([0.0], np.array([[1.0]]))
    +
    +def_gen.permutation(10)
    +def_gen.permutation([1, 2, 3, 4])
    +def_gen.permutation(np.array([1, 2, 3, 4]))
    +def_gen.permutation(D_2D, axis=1)
    +def_gen.permuted(D_2D)
    +def_gen.permuted(D_2D_like)
    +def_gen.permuted(D_2D, axis=1)
    +def_gen.permuted(D_2D, out=D_2D)
    +def_gen.permuted(D_2D_like, out=D_2D)
    +def_gen.permuted(D_2D_like, out=D_2D)
    +def_gen.permuted(D_2D, axis=1, out=D_2D)
    +
    +def_gen.shuffle(np.arange(10))
    +def_gen.shuffle([1, 2, 3, 4, 5])
    +def_gen.shuffle(D_2D, axis=1)
    +
    +def_gen.__str__()
    +def_gen.__repr__()
    +def_gen.__setstate__(dict(def_gen.bit_generator.state))
    +
    +# RandomState
    +random_st: np.random.RandomState = np.random.RandomState()
    +
    +random_st.standard_normal()
    +random_st.standard_normal(size=None)
    +random_st.standard_normal(size=1)
    +
    +random_st.random()
    +random_st.random(size=None)
    +random_st.random(size=1)
    +
    +random_st.standard_cauchy()
    +random_st.standard_cauchy(size=None)
    +random_st.standard_cauchy(size=1)
    +
    +random_st.standard_exponential()
    +random_st.standard_exponential(size=None)
    +random_st.standard_exponential(size=1)
    +
    +random_st.zipf(1.5)
    +random_st.zipf(1.5, size=None)
    +random_st.zipf(1.5, size=1)
    +random_st.zipf(D_arr_1p5)
    +random_st.zipf(D_arr_1p5, size=1)
    +random_st.zipf(D_arr_like_1p5)
    +random_st.zipf(D_arr_like_1p5, size=1)
    +
    +random_st.weibull(0.5)
    +random_st.weibull(0.5, size=None)
    +random_st.weibull(0.5, size=1)
    +random_st.weibull(D_arr_0p5)
    +random_st.weibull(D_arr_0p5, size=1)
    +random_st.weibull(D_arr_like_0p5)
    +random_st.weibull(D_arr_like_0p5, size=1)
    +
    +random_st.standard_t(0.5)
    +random_st.standard_t(0.5, size=None)
    +random_st.standard_t(0.5, size=1)
    +random_st.standard_t(D_arr_0p5)
    +random_st.standard_t(D_arr_0p5, size=1)
    +random_st.standard_t(D_arr_like_0p5)
    +random_st.standard_t(D_arr_like_0p5, size=1)
    +
    +random_st.poisson(0.5)
    +random_st.poisson(0.5, size=None)
    +random_st.poisson(0.5, size=1)
    +random_st.poisson(D_arr_0p5)
    +random_st.poisson(D_arr_0p5, size=1)
    +random_st.poisson(D_arr_like_0p5)
    +random_st.poisson(D_arr_like_0p5, size=1)
    +
    +random_st.power(0.5)
    +random_st.power(0.5, size=None)
    +random_st.power(0.5, size=1)
    +random_st.power(D_arr_0p5)
    +random_st.power(D_arr_0p5, size=1)
    +random_st.power(D_arr_like_0p5)
    +random_st.power(D_arr_like_0p5, size=1)
    +
    +random_st.pareto(0.5)
    +random_st.pareto(0.5, size=None)
    +random_st.pareto(0.5, size=1)
    +random_st.pareto(D_arr_0p5)
    +random_st.pareto(D_arr_0p5, size=1)
    +random_st.pareto(D_arr_like_0p5)
    +random_st.pareto(D_arr_like_0p5, size=1)
    +
    +random_st.chisquare(0.5)
    +random_st.chisquare(0.5, size=None)
    +random_st.chisquare(0.5, size=1)
    +random_st.chisquare(D_arr_0p5)
    +random_st.chisquare(D_arr_0p5, size=1)
    +random_st.chisquare(D_arr_like_0p5)
    +random_st.chisquare(D_arr_like_0p5, size=1)
    +
    +random_st.exponential(0.5)
    +random_st.exponential(0.5, size=None)
    +random_st.exponential(0.5, size=1)
    +random_st.exponential(D_arr_0p5)
    +random_st.exponential(D_arr_0p5, size=1)
    +random_st.exponential(D_arr_like_0p5)
    +random_st.exponential(D_arr_like_0p5, size=1)
    +
    +random_st.geometric(0.5)
    +random_st.geometric(0.5, size=None)
    +random_st.geometric(0.5, size=1)
    +random_st.geometric(D_arr_0p5)
    +random_st.geometric(D_arr_0p5, size=1)
    +random_st.geometric(D_arr_like_0p5)
    +random_st.geometric(D_arr_like_0p5, size=1)
    +
    +random_st.logseries(0.5)
    +random_st.logseries(0.5, size=None)
    +random_st.logseries(0.5, size=1)
    +random_st.logseries(D_arr_0p5)
    +random_st.logseries(D_arr_0p5, size=1)
    +random_st.logseries(D_arr_like_0p5)
    +random_st.logseries(D_arr_like_0p5, size=1)
    +
    +random_st.rayleigh(0.5)
    +random_st.rayleigh(0.5, size=None)
    +random_st.rayleigh(0.5, size=1)
    +random_st.rayleigh(D_arr_0p5)
    +random_st.rayleigh(D_arr_0p5, size=1)
    +random_st.rayleigh(D_arr_like_0p5)
    +random_st.rayleigh(D_arr_like_0p5, size=1)
    +
    +random_st.standard_gamma(0.5)
    +random_st.standard_gamma(0.5, size=None)
    +random_st.standard_gamma(0.5, size=1)
    +random_st.standard_gamma(D_arr_0p5)
    +random_st.standard_gamma(D_arr_0p5, size=1)
    +random_st.standard_gamma(D_arr_like_0p5)
    +random_st.standard_gamma(D_arr_like_0p5, size=1)
    +random_st.standard_gamma(D_arr_like_0p5, size=1)
    +
    +random_st.vonmises(0.5, 0.5)
    +random_st.vonmises(0.5, 0.5, size=None)
    +random_st.vonmises(0.5, 0.5, size=1)
    +random_st.vonmises(D_arr_0p5, 0.5)
    +random_st.vonmises(0.5, D_arr_0p5)
    +random_st.vonmises(D_arr_0p5, 0.5, size=1)
    +random_st.vonmises(0.5, D_arr_0p5, size=1)
    +random_st.vonmises(D_arr_like_0p5, 0.5)
    +random_st.vonmises(0.5, D_arr_like_0p5)
    +random_st.vonmises(D_arr_0p5, D_arr_0p5)
    +random_st.vonmises(D_arr_like_0p5, D_arr_like_0p5)
    +random_st.vonmises(D_arr_0p5, D_arr_0p5, size=1)
    +random_st.vonmises(D_arr_like_0p5, D_arr_like_0p5, size=1)
    +
    +random_st.wald(0.5, 0.5)
    +random_st.wald(0.5, 0.5, size=None)
    +random_st.wald(0.5, 0.5, size=1)
    +random_st.wald(D_arr_0p5, 0.5)
    +random_st.wald(0.5, D_arr_0p5)
    +random_st.wald(D_arr_0p5, 0.5, size=1)
    +random_st.wald(0.5, D_arr_0p5, size=1)
    +random_st.wald(D_arr_like_0p5, 0.5)
    +random_st.wald(0.5, D_arr_like_0p5)
    +random_st.wald(D_arr_0p5, D_arr_0p5)
    +random_st.wald(D_arr_like_0p5, D_arr_like_0p5)
    +random_st.wald(D_arr_0p5, D_arr_0p5, size=1)
    +random_st.wald(D_arr_like_0p5, D_arr_like_0p5, size=1)
    +
    +random_st.uniform(0.5, 0.5)
    +random_st.uniform(0.5, 0.5, size=None)
    +random_st.uniform(0.5, 0.5, size=1)
    +random_st.uniform(D_arr_0p5, 0.5)
    +random_st.uniform(0.5, D_arr_0p5)
    +random_st.uniform(D_arr_0p5, 0.5, size=1)
    +random_st.uniform(0.5, D_arr_0p5, size=1)
    +random_st.uniform(D_arr_like_0p5, 0.5)
    +random_st.uniform(0.5, D_arr_like_0p5)
    +random_st.uniform(D_arr_0p5, D_arr_0p5)
    +random_st.uniform(D_arr_like_0p5, D_arr_like_0p5)
    +random_st.uniform(D_arr_0p5, D_arr_0p5, size=1)
    +random_st.uniform(D_arr_like_0p5, D_arr_like_0p5, size=1)
    +
    +random_st.beta(0.5, 0.5)
    +random_st.beta(0.5, 0.5, size=None)
    +random_st.beta(0.5, 0.5, size=1)
    +random_st.beta(D_arr_0p5, 0.5)
    +random_st.beta(0.5, D_arr_0p5)
    +random_st.beta(D_arr_0p5, 0.5, size=1)
    +random_st.beta(0.5, D_arr_0p5, size=1)
    +random_st.beta(D_arr_like_0p5, 0.5)
    +random_st.beta(0.5, D_arr_like_0p5)
    +random_st.beta(D_arr_0p5, D_arr_0p5)
    +random_st.beta(D_arr_like_0p5, D_arr_like_0p5)
    +random_st.beta(D_arr_0p5, D_arr_0p5, size=1)
    +random_st.beta(D_arr_like_0p5, D_arr_like_0p5, size=1)
    +
    +random_st.f(0.5, 0.5)
    +random_st.f(0.5, 0.5, size=None)
    +random_st.f(0.5, 0.5, size=1)
    +random_st.f(D_arr_0p5, 0.5)
    +random_st.f(0.5, D_arr_0p5)
    +random_st.f(D_arr_0p5, 0.5, size=1)
    +random_st.f(0.5, D_arr_0p5, size=1)
    +random_st.f(D_arr_like_0p5, 0.5)
    +random_st.f(0.5, D_arr_like_0p5)
    +random_st.f(D_arr_0p5, D_arr_0p5)
    +random_st.f(D_arr_like_0p5, D_arr_like_0p5)
    +random_st.f(D_arr_0p5, D_arr_0p5, size=1)
    +random_st.f(D_arr_like_0p5, D_arr_like_0p5, size=1)
    +
    +random_st.gamma(0.5, 0.5)
    +random_st.gamma(0.5, 0.5, size=None)
    +random_st.gamma(0.5, 0.5, size=1)
    +random_st.gamma(D_arr_0p5, 0.5)
    +random_st.gamma(0.5, D_arr_0p5)
    +random_st.gamma(D_arr_0p5, 0.5, size=1)
    +random_st.gamma(0.5, D_arr_0p5, size=1)
    +random_st.gamma(D_arr_like_0p5, 0.5)
    +random_st.gamma(0.5, D_arr_like_0p5)
    +random_st.gamma(D_arr_0p5, D_arr_0p5)
    +random_st.gamma(D_arr_like_0p5, D_arr_like_0p5)
    +random_st.gamma(D_arr_0p5, D_arr_0p5, size=1)
    +random_st.gamma(D_arr_like_0p5, D_arr_like_0p5, size=1)
    +
    +random_st.gumbel(0.5, 0.5)
    +random_st.gumbel(0.5, 0.5, size=None)
    +random_st.gumbel(0.5, 0.5, size=1)
    +random_st.gumbel(D_arr_0p5, 0.5)
    +random_st.gumbel(0.5, D_arr_0p5)
    +random_st.gumbel(D_arr_0p5, 0.5, size=1)
    +random_st.gumbel(0.5, D_arr_0p5, size=1)
    +random_st.gumbel(D_arr_like_0p5, 0.5)
    +random_st.gumbel(0.5, D_arr_like_0p5)
    +random_st.gumbel(D_arr_0p5, D_arr_0p5)
    +random_st.gumbel(D_arr_like_0p5, D_arr_like_0p5)
    +random_st.gumbel(D_arr_0p5, D_arr_0p5, size=1)
    +random_st.gumbel(D_arr_like_0p5, D_arr_like_0p5, size=1)
    +
    +random_st.laplace(0.5, 0.5)
    +random_st.laplace(0.5, 0.5, size=None)
    +random_st.laplace(0.5, 0.5, size=1)
    +random_st.laplace(D_arr_0p5, 0.5)
    +random_st.laplace(0.5, D_arr_0p5)
    +random_st.laplace(D_arr_0p5, 0.5, size=1)
    +random_st.laplace(0.5, D_arr_0p5, size=1)
    +random_st.laplace(D_arr_like_0p5, 0.5)
    +random_st.laplace(0.5, D_arr_like_0p5)
    +random_st.laplace(D_arr_0p5, D_arr_0p5)
    +random_st.laplace(D_arr_like_0p5, D_arr_like_0p5)
    +random_st.laplace(D_arr_0p5, D_arr_0p5, size=1)
    +random_st.laplace(D_arr_like_0p5, D_arr_like_0p5, size=1)
    +
    +random_st.logistic(0.5, 0.5)
    +random_st.logistic(0.5, 0.5, size=None)
    +random_st.logistic(0.5, 0.5, size=1)
    +random_st.logistic(D_arr_0p5, 0.5)
    +random_st.logistic(0.5, D_arr_0p5)
    +random_st.logistic(D_arr_0p5, 0.5, size=1)
    +random_st.logistic(0.5, D_arr_0p5, size=1)
    +random_st.logistic(D_arr_like_0p5, 0.5)
    +random_st.logistic(0.5, D_arr_like_0p5)
    +random_st.logistic(D_arr_0p5, D_arr_0p5)
    +random_st.logistic(D_arr_like_0p5, D_arr_like_0p5)
    +random_st.logistic(D_arr_0p5, D_arr_0p5, size=1)
    +random_st.logistic(D_arr_like_0p5, D_arr_like_0p5, size=1)
    +
    +random_st.lognormal(0.5, 0.5)
    +random_st.lognormal(0.5, 0.5, size=None)
    +random_st.lognormal(0.5, 0.5, size=1)
    +random_st.lognormal(D_arr_0p5, 0.5)
    +random_st.lognormal(0.5, D_arr_0p5)
    +random_st.lognormal(D_arr_0p5, 0.5, size=1)
    +random_st.lognormal(0.5, D_arr_0p5, size=1)
    +random_st.lognormal(D_arr_like_0p5, 0.5)
    +random_st.lognormal(0.5, D_arr_like_0p5)
    +random_st.lognormal(D_arr_0p5, D_arr_0p5)
    +random_st.lognormal(D_arr_like_0p5, D_arr_like_0p5)
    +random_st.lognormal(D_arr_0p5, D_arr_0p5, size=1)
    +random_st.lognormal(D_arr_like_0p5, D_arr_like_0p5, size=1)
    +
    +random_st.noncentral_chisquare(0.5, 0.5)
    +random_st.noncentral_chisquare(0.5, 0.5, size=None)
    +random_st.noncentral_chisquare(0.5, 0.5, size=1)
    +random_st.noncentral_chisquare(D_arr_0p5, 0.5)
    +random_st.noncentral_chisquare(0.5, D_arr_0p5)
    +random_st.noncentral_chisquare(D_arr_0p5, 0.5, size=1)
    +random_st.noncentral_chisquare(0.5, D_arr_0p5, size=1)
    +random_st.noncentral_chisquare(D_arr_like_0p5, 0.5)
    +random_st.noncentral_chisquare(0.5, D_arr_like_0p5)
    +random_st.noncentral_chisquare(D_arr_0p5, D_arr_0p5)
    +random_st.noncentral_chisquare(D_arr_like_0p5, D_arr_like_0p5)
    +random_st.noncentral_chisquare(D_arr_0p5, D_arr_0p5, size=1)
    +random_st.noncentral_chisquare(D_arr_like_0p5, D_arr_like_0p5, size=1)
    +
    +random_st.normal(0.5, 0.5)
    +random_st.normal(0.5, 0.5, size=None)
    +random_st.normal(0.5, 0.5, size=1)
    +random_st.normal(D_arr_0p5, 0.5)
    +random_st.normal(0.5, D_arr_0p5)
    +random_st.normal(D_arr_0p5, 0.5, size=1)
    +random_st.normal(0.5, D_arr_0p5, size=1)
    +random_st.normal(D_arr_like_0p5, 0.5)
    +random_st.normal(0.5, D_arr_like_0p5)
    +random_st.normal(D_arr_0p5, D_arr_0p5)
    +random_st.normal(D_arr_like_0p5, D_arr_like_0p5)
    +random_st.normal(D_arr_0p5, D_arr_0p5, size=1)
    +random_st.normal(D_arr_like_0p5, D_arr_like_0p5, size=1)
    +
    +random_st.triangular(0.1, 0.5, 0.9)
    +random_st.triangular(0.1, 0.5, 0.9, size=None)
    +random_st.triangular(0.1, 0.5, 0.9, size=1)
    +random_st.triangular(D_arr_0p1, 0.5, 0.9)
    +random_st.triangular(0.1, D_arr_0p5, 0.9)
    +random_st.triangular(D_arr_0p1, 0.5, D_arr_like_0p9, size=1)
    +random_st.triangular(0.1, D_arr_0p5, 0.9, size=1)
    +random_st.triangular(D_arr_like_0p1, 0.5, D_arr_0p9)
    +random_st.triangular(0.5, D_arr_like_0p5, 0.9)
    +random_st.triangular(D_arr_0p1, D_arr_0p5, 0.9)
    +random_st.triangular(D_arr_like_0p1, D_arr_like_0p5, 0.9)
    +random_st.triangular(D_arr_0p1, D_arr_0p5, D_arr_0p9, size=1)
    +random_st.triangular(D_arr_like_0p1, D_arr_like_0p5, D_arr_like_0p9, size=1)
    +
    +random_st.noncentral_f(0.1, 0.5, 0.9)
    +random_st.noncentral_f(0.1, 0.5, 0.9, size=None)
    +random_st.noncentral_f(0.1, 0.5, 0.9, size=1)
    +random_st.noncentral_f(D_arr_0p1, 0.5, 0.9)
    +random_st.noncentral_f(0.1, D_arr_0p5, 0.9)
    +random_st.noncentral_f(D_arr_0p1, 0.5, D_arr_like_0p9, size=1)
    +random_st.noncentral_f(0.1, D_arr_0p5, 0.9, size=1)
    +random_st.noncentral_f(D_arr_like_0p1, 0.5, D_arr_0p9)
    +random_st.noncentral_f(0.5, D_arr_like_0p5, 0.9)
    +random_st.noncentral_f(D_arr_0p1, D_arr_0p5, 0.9)
    +random_st.noncentral_f(D_arr_like_0p1, D_arr_like_0p5, 0.9)
    +random_st.noncentral_f(D_arr_0p1, D_arr_0p5, D_arr_0p9, size=1)
    +random_st.noncentral_f(D_arr_like_0p1, D_arr_like_0p5, D_arr_like_0p9, size=1)
    +
    +random_st.binomial(10, 0.5)
    +random_st.binomial(10, 0.5, size=None)
    +random_st.binomial(10, 0.5, size=1)
    +random_st.binomial(I_arr_10, 0.5)
    +random_st.binomial(10, D_arr_0p5)
    +random_st.binomial(I_arr_10, 0.5, size=1)
    +random_st.binomial(10, D_arr_0p5, size=1)
    +random_st.binomial(I_arr_like_10, 0.5)
    +random_st.binomial(10, D_arr_like_0p5)
    +random_st.binomial(I_arr_10, D_arr_0p5)
    +random_st.binomial(I_arr_like_10, D_arr_like_0p5)
    +random_st.binomial(I_arr_10, D_arr_0p5, size=1)
    +random_st.binomial(I_arr_like_10, D_arr_like_0p5, size=1)
    +
    +random_st.negative_binomial(10, 0.5)
    +random_st.negative_binomial(10, 0.5, size=None)
    +random_st.negative_binomial(10, 0.5, size=1)
    +random_st.negative_binomial(I_arr_10, 0.5)
    +random_st.negative_binomial(10, D_arr_0p5)
    +random_st.negative_binomial(I_arr_10, 0.5, size=1)
    +random_st.negative_binomial(10, D_arr_0p5, size=1)
    +random_st.negative_binomial(I_arr_like_10, 0.5)
    +random_st.negative_binomial(10, D_arr_like_0p5)
    +random_st.negative_binomial(I_arr_10, D_arr_0p5)
    +random_st.negative_binomial(I_arr_like_10, D_arr_like_0p5)
    +random_st.negative_binomial(I_arr_10, D_arr_0p5, size=1)
    +random_st.negative_binomial(I_arr_like_10, D_arr_like_0p5, size=1)
    +
    +random_st.hypergeometric(20, 20, 10)
    +random_st.hypergeometric(20, 20, 10, size=None)
    +random_st.hypergeometric(20, 20, 10, size=1)
    +random_st.hypergeometric(I_arr_20, 20, 10)
    +random_st.hypergeometric(20, I_arr_20, 10)
    +random_st.hypergeometric(I_arr_20, 20, I_arr_like_10, size=1)
    +random_st.hypergeometric(20, I_arr_20, 10, size=1)
    +random_st.hypergeometric(I_arr_like_20, 20, I_arr_10)
    +random_st.hypergeometric(20, I_arr_like_20, 10)
    +random_st.hypergeometric(I_arr_20, I_arr_20, 10)
    +random_st.hypergeometric(I_arr_like_20, I_arr_like_20, 10)
    +random_st.hypergeometric(I_arr_20, I_arr_20, I_arr_10, size=1)
    +random_st.hypergeometric(I_arr_like_20, I_arr_like_20, I_arr_like_10, size=1)
    +
    +random_st.randint(0, 100)
    +random_st.randint(100)
    +random_st.randint([100])
    +random_st.randint(0, [100])
    +
    +random_st.randint(2, dtype=bool)
    +random_st.randint(0, 2, dtype=bool)
    +random_st.randint(I_bool_high_open, dtype=bool)
    +random_st.randint(I_bool_low, I_bool_high_open, dtype=bool)
    +random_st.randint(0, I_bool_high_open, dtype=bool)
    +
    +random_st.randint(2, dtype=np.bool)
    +random_st.randint(0, 2, dtype=np.bool)
    +random_st.randint(I_bool_high_open, dtype=np.bool)
    +random_st.randint(I_bool_low, I_bool_high_open, dtype=np.bool)
    +random_st.randint(0, I_bool_high_open, dtype=np.bool)
    +
    +random_st.randint(256, dtype="u1")
    +random_st.randint(0, 256, dtype="u1")
    +random_st.randint(I_u1_high_open, dtype="u1")
    +random_st.randint(I_u1_low, I_u1_high_open, dtype="u1")
    +random_st.randint(0, I_u1_high_open, dtype="u1")
    +
    +random_st.randint(256, dtype="uint8")
    +random_st.randint(0, 256, dtype="uint8")
    +random_st.randint(I_u1_high_open, dtype="uint8")
    +random_st.randint(I_u1_low, I_u1_high_open, dtype="uint8")
    +random_st.randint(0, I_u1_high_open, dtype="uint8")
    +
    +random_st.randint(256, dtype=np.uint8)
    +random_st.randint(0, 256, dtype=np.uint8)
    +random_st.randint(I_u1_high_open, dtype=np.uint8)
    +random_st.randint(I_u1_low, I_u1_high_open, dtype=np.uint8)
    +random_st.randint(0, I_u1_high_open, dtype=np.uint8)
    +
    +random_st.randint(65536, dtype="u2")
    +random_st.randint(0, 65536, dtype="u2")
    +random_st.randint(I_u2_high_open, dtype="u2")
    +random_st.randint(I_u2_low, I_u2_high_open, dtype="u2")
    +random_st.randint(0, I_u2_high_open, dtype="u2")
    +
    +random_st.randint(65536, dtype="uint16")
    +random_st.randint(0, 65536, dtype="uint16")
    +random_st.randint(I_u2_high_open, dtype="uint16")
    +random_st.randint(I_u2_low, I_u2_high_open, dtype="uint16")
    +random_st.randint(0, I_u2_high_open, dtype="uint16")
    +
    +random_st.randint(65536, dtype=np.uint16)
    +random_st.randint(0, 65536, dtype=np.uint16)
    +random_st.randint(I_u2_high_open, dtype=np.uint16)
    +random_st.randint(I_u2_low, I_u2_high_open, dtype=np.uint16)
    +random_st.randint(0, I_u2_high_open, dtype=np.uint16)
    +
    +random_st.randint(4294967296, dtype="u4")
    +random_st.randint(0, 4294967296, dtype="u4")
    +random_st.randint(I_u4_high_open, dtype="u4")
    +random_st.randint(I_u4_low, I_u4_high_open, dtype="u4")
    +random_st.randint(0, I_u4_high_open, dtype="u4")
    +
    +random_st.randint(4294967296, dtype="uint32")
    +random_st.randint(0, 4294967296, dtype="uint32")
    +random_st.randint(I_u4_high_open, dtype="uint32")
    +random_st.randint(I_u4_low, I_u4_high_open, dtype="uint32")
    +random_st.randint(0, I_u4_high_open, dtype="uint32")
    +
    +random_st.randint(4294967296, dtype=np.uint32)
    +random_st.randint(0, 4294967296, dtype=np.uint32)
    +random_st.randint(I_u4_high_open, dtype=np.uint32)
    +random_st.randint(I_u4_low, I_u4_high_open, dtype=np.uint32)
    +random_st.randint(0, I_u4_high_open, dtype=np.uint32)
    +
    +
    +random_st.randint(18446744073709551616, dtype="u8")
    +random_st.randint(0, 18446744073709551616, dtype="u8")
    +random_st.randint(I_u8_high_open, dtype="u8")
    +random_st.randint(I_u8_low, I_u8_high_open, dtype="u8")
    +random_st.randint(0, I_u8_high_open, dtype="u8")
    +
    +random_st.randint(18446744073709551616, dtype="uint64")
    +random_st.randint(0, 18446744073709551616, dtype="uint64")
    +random_st.randint(I_u8_high_open, dtype="uint64")
    +random_st.randint(I_u8_low, I_u8_high_open, dtype="uint64")
    +random_st.randint(0, I_u8_high_open, dtype="uint64")
    +
    +random_st.randint(18446744073709551616, dtype=np.uint64)
    +random_st.randint(0, 18446744073709551616, dtype=np.uint64)
    +random_st.randint(I_u8_high_open, dtype=np.uint64)
    +random_st.randint(I_u8_low, I_u8_high_open, dtype=np.uint64)
    +random_st.randint(0, I_u8_high_open, dtype=np.uint64)
    +
    +random_st.randint(128, dtype="i1")
    +random_st.randint(-128, 128, dtype="i1")
    +random_st.randint(I_i1_high_open, dtype="i1")
    +random_st.randint(I_i1_low, I_i1_high_open, dtype="i1")
    +random_st.randint(-128, I_i1_high_open, dtype="i1")
    +
    +random_st.randint(128, dtype="int8")
    +random_st.randint(-128, 128, dtype="int8")
    +random_st.randint(I_i1_high_open, dtype="int8")
    +random_st.randint(I_i1_low, I_i1_high_open, dtype="int8")
    +random_st.randint(-128, I_i1_high_open, dtype="int8")
    +
    +random_st.randint(128, dtype=np.int8)
    +random_st.randint(-128, 128, dtype=np.int8)
    +random_st.randint(I_i1_high_open, dtype=np.int8)
    +random_st.randint(I_i1_low, I_i1_high_open, dtype=np.int8)
    +random_st.randint(-128, I_i1_high_open, dtype=np.int8)
    +
    +random_st.randint(32768, dtype="i2")
    +random_st.randint(-32768, 32768, dtype="i2")
    +random_st.randint(I_i2_high_open, dtype="i2")
    +random_st.randint(I_i2_low, I_i2_high_open, dtype="i2")
    +random_st.randint(-32768, I_i2_high_open, dtype="i2")
    +random_st.randint(32768, dtype="int16")
    +random_st.randint(-32768, 32768, dtype="int16")
    +random_st.randint(I_i2_high_open, dtype="int16")
    +random_st.randint(I_i2_low, I_i2_high_open, dtype="int16")
    +random_st.randint(-32768, I_i2_high_open, dtype="int16")
    +random_st.randint(32768, dtype=np.int16)
    +random_st.randint(-32768, 32768, dtype=np.int16)
    +random_st.randint(I_i2_high_open, dtype=np.int16)
    +random_st.randint(I_i2_low, I_i2_high_open, dtype=np.int16)
    +random_st.randint(-32768, I_i2_high_open, dtype=np.int16)
    +
    +random_st.randint(2147483648, dtype="i4")
    +random_st.randint(-2147483648, 2147483648, dtype="i4")
    +random_st.randint(I_i4_high_open, dtype="i4")
    +random_st.randint(I_i4_low, I_i4_high_open, dtype="i4")
    +random_st.randint(-2147483648, I_i4_high_open, dtype="i4")
    +
    +random_st.randint(2147483648, dtype="int32")
    +random_st.randint(-2147483648, 2147483648, dtype="int32")
    +random_st.randint(I_i4_high_open, dtype="int32")
    +random_st.randint(I_i4_low, I_i4_high_open, dtype="int32")
    +random_st.randint(-2147483648, I_i4_high_open, dtype="int32")
    +
    +random_st.randint(2147483648, dtype=np.int32)
    +random_st.randint(-2147483648, 2147483648, dtype=np.int32)
    +random_st.randint(I_i4_high_open, dtype=np.int32)
    +random_st.randint(I_i4_low, I_i4_high_open, dtype=np.int32)
    +random_st.randint(-2147483648, I_i4_high_open, dtype=np.int32)
    +
    +random_st.randint(9223372036854775808, dtype="i8")
    +random_st.randint(-9223372036854775808, 9223372036854775808, dtype="i8")
    +random_st.randint(I_i8_high_open, dtype="i8")
    +random_st.randint(I_i8_low, I_i8_high_open, dtype="i8")
    +random_st.randint(-9223372036854775808, I_i8_high_open, dtype="i8")
    +
    +random_st.randint(9223372036854775808, dtype="int64")
    +random_st.randint(-9223372036854775808, 9223372036854775808, dtype="int64")
    +random_st.randint(I_i8_high_open, dtype="int64")
    +random_st.randint(I_i8_low, I_i8_high_open, dtype="int64")
    +random_st.randint(-9223372036854775808, I_i8_high_open, dtype="int64")
    +
    +random_st.randint(9223372036854775808, dtype=np.int64)
    +random_st.randint(-9223372036854775808, 9223372036854775808, dtype=np.int64)
    +random_st.randint(I_i8_high_open, dtype=np.int64)
    +random_st.randint(I_i8_low, I_i8_high_open, dtype=np.int64)
    +random_st.randint(-9223372036854775808, I_i8_high_open, dtype=np.int64)
    +
    +bg: np.random.BitGenerator = random_st._bit_generator
    +
    +random_st.bytes(2)
    +
    +random_st.choice(5)
    +random_st.choice(5, 3)
    +random_st.choice(5, 3, replace=True)
    +random_st.choice(5, 3, p=[1 / 5] * 5)
    +random_st.choice(5, 3, p=[1 / 5] * 5, replace=False)
    +
    +random_st.choice(["pooh", "rabbit", "piglet", "Christopher"])
    +random_st.choice(["pooh", "rabbit", "piglet", "Christopher"], 3)
    +random_st.choice(["pooh", "rabbit", "piglet", "Christopher"], 3, p=[1 / 4] * 4)
    +random_st.choice(["pooh", "rabbit", "piglet", "Christopher"], 3, replace=True)
    +random_st.choice(["pooh", "rabbit", "piglet", "Christopher"], 3, replace=False, p=np.array([1 / 8, 1 / 8, 1 / 2, 1 / 4]))
    +
    +random_st.dirichlet([0.5, 0.5])
    +random_st.dirichlet(np.array([0.5, 0.5]))
    +random_st.dirichlet(np.array([0.5, 0.5]), size=3)
    +
    +random_st.multinomial(20, [1 / 6.0] * 6)
    +random_st.multinomial(20, np.array([0.5, 0.5]))
    +random_st.multinomial(20, [1 / 6.0] * 6, size=2)
    +
    +random_st.multivariate_normal([0.0], [[1.0]])
    +random_st.multivariate_normal([0.0], np.array([[1.0]]))
    +random_st.multivariate_normal(np.array([0.0]), [[1.0]])
    +random_st.multivariate_normal([0.0], np.array([[1.0]]))
    +
    +random_st.permutation(10)
    +random_st.permutation([1, 2, 3, 4])
    +random_st.permutation(np.array([1, 2, 3, 4]))
    +random_st.permutation(D_2D)
    +
    +random_st.shuffle(np.arange(10))
    +random_st.shuffle([1, 2, 3, 4, 5])
    +random_st.shuffle(D_2D)
    +
    +np.random.RandomState(SEED_PCG64)
    +np.random.RandomState(0)
    +np.random.RandomState([0, 1, 2])
    +random_st.__str__()
    +random_st.__repr__()
    +random_st_state = random_st.__getstate__()
    +random_st.__setstate__(random_st_state)
    +random_st.seed()
    +random_st.seed(1)
    +random_st.seed([0, 1])
    +random_st_get_state = random_st.get_state()
    +random_st_get_state_legacy = random_st.get_state(legacy=True)
    +random_st.set_state(random_st_get_state)
    +
    +random_st.rand()
    +random_st.rand(1)
    +random_st.rand(1, 2)
    +random_st.randn()
    +random_st.randn(1)
    +random_st.randn(1, 2)
    +random_st.random_sample()
    +random_st.random_sample(1)
    +random_st.random_sample(size=(1, 2))
    +
    +random_st.tomaxint()
    +random_st.tomaxint(1)
    +random_st.tomaxint((1,))
    +
    +np.random.mtrand.set_bit_generator(SEED_PCG64)
    +np.random.mtrand.get_bit_generator()
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/recfunctions.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/recfunctions.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..586f0502b3660e13063b30d7128b3dab0558c3ba
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/recfunctions.py
    @@ -0,0 +1,164 @@
    +"""These tests are based on the doctests from `numpy/lib/recfunctions.py`."""
    +
    +from typing import Any, assert_type
    +
    +import numpy as np
    +import numpy.typing as npt
    +from numpy.lib import recfunctions as rfn
    +
    +
    +def test_recursive_fill_fields() -> None:
    +    a: npt.NDArray[np.void] = np.array(
    +        [(1, 10.0), (2, 20.0)],
    +        dtype=[("A", np.int64), ("B", np.float64)],
    +    )
    +    b = np.zeros((3,), dtype=a.dtype)
    +    out = rfn.recursive_fill_fields(a, b)
    +    assert_type(out, np.ndarray[tuple[int], np.dtype[np.void]])
    +
    +
    +def test_get_names() -> None:
    +    names: tuple[str | Any, ...]
    +    names = rfn.get_names(np.empty((1,), dtype=[("A", int)]).dtype)
    +    names = rfn.get_names(np.empty((1,), dtype=[("A", int), ("B", float)]).dtype)
    +
    +    adtype = np.dtype([("a", int), ("b", [("b_a", int), ("b_b", int)])])
    +    names = rfn.get_names(adtype)
    +
    +
    +def test_get_names_flat() -> None:
    +    names: tuple[str, ...]
    +    names = rfn.get_names_flat(np.empty((1,), dtype=[("A", int)]).dtype)
    +    names = rfn.get_names_flat(np.empty((1,), dtype=[("A", int), ("B", float)]).dtype)
    +
    +    adtype = np.dtype([("a", int), ("b", [("b_a", int), ("b_b", int)])])
    +    names = rfn.get_names_flat(adtype)
    +
    +
    +def test_flatten_descr() -> None:
    +    ndtype = np.dtype([("a", " None:
    +    ndtype = np.dtype([
    +        ("A", int),
    +        ("B", [("B_A", int), ("B_B", [("B_B_A", int), ("B_B_B", int)])]),
    +    ])
    +    assert_type(rfn.get_fieldstructure(ndtype), dict[str, list[str]])
    +
    +
    +def test_merge_arrays() -> None:
    +    assert_type(
    +        rfn.merge_arrays((
    +            np.ones((2,), np.int_),
    +            np.ones((3,), np.float64),
    +        )),
    +        np.recarray[tuple[int], np.dtype[np.void]],
    +    )
    +
    +
    +def test_drop_fields() -> None:
    +    ndtype = [("a", np.int64), ("b", [("b_a", np.double), ("b_b", np.int64)])]
    +    a = np.ones((3,), dtype=ndtype)
    +
    +    assert_type(
    +        rfn.drop_fields(a, "a"),
    +        np.ndarray[tuple[int], np.dtype[np.void]],
    +    )
    +    assert_type(
    +        rfn.drop_fields(a, "a", asrecarray=True),
    +        np.rec.recarray[tuple[int], np.dtype[np.void]],
    +    )
    +    assert_type(
    +        rfn.rec_drop_fields(a, "a"),
    +        np.rec.recarray[tuple[int], np.dtype[np.void]],
    +    )
    +
    +
    +def test_rename_fields() -> None:
    +    ndtype = [("a", np.int64), ("b", [("b_a", np.double), ("b_b", np.int64)])]
    +    a = np.ones((3,), dtype=ndtype)
    +
    +    assert_type(
    +        rfn.rename_fields(a, {"a": "A", "b_b": "B_B"}),
    +        np.ndarray[tuple[int], np.dtype[np.void]],
    +    )
    +
    +
    +def test_repack_fields() -> None:
    +    dt: np.dtype[np.void] = np.dtype("u1,  None:
    +    a = np.zeros(4, dtype=[("a", "i4"), ("b", "f4,u2"), ("c", "f4", 2)])
    +    assert_type(rfn.structured_to_unstructured(a), npt.NDArray[Any])
    +
    +
    +def unstructured_to_structured() -> None:
    +    dt: np.dtype[np.void] = np.dtype([("a", "i4"), ("b", "f4,u2"), ("c", "f4", 2)])
    +    a = np.arange(20, dtype=np.int32).reshape((4, 5))
    +    assert_type(rfn.unstructured_to_structured(a, dt), npt.NDArray[np.void])
    +
    +
    +def test_apply_along_fields() -> None:
    +    b = np.ones(4, dtype=[("x", "i4"), ("y", "f4"), ("z", "f8")])
    +    assert_type(
    +        rfn.apply_along_fields(np.mean, b),
    +        np.ndarray[tuple[int], np.dtype[np.void]],
    +    )
    +
    +
    +def test_assign_fields_by_name() -> None:
    +    b = np.ones(4, dtype=[("x", "i4"), ("y", "f4"), ("z", "f8")])
    +    assert_type(
    +        rfn.apply_along_fields(np.mean, b),
    +        np.ndarray[tuple[int], np.dtype[np.void]],
    +    )
    +
    +
    +def test_require_fields() -> None:
    +    a = np.ones(4, dtype=[("a", "i4"), ("b", "f8"), ("c", "u1")])
    +    assert_type(
    +        rfn.require_fields(a, [("b", "f4"), ("c", "u1")]),
    +        np.ndarray[tuple[int], np.dtype[np.void]],
    +    )
    +
    +
    +def test_stack_arrays() -> None:
    +    x = np.zeros((2,), np.int32)
    +    assert_type(
    +        rfn.stack_arrays(x),
    +        np.ndarray[tuple[int], np.dtype[np.int32]],
    +    )
    +
    +    z = np.ones((2,), [("A", "|S3"), ("B", float)])
    +    zz = np.ones((2,), [("A", "|S3"), ("B", np.float64), ("C", np.float64)])
    +    assert_type(
    +        rfn.stack_arrays((z, zz)),
    +        np.ma.MaskedArray[tuple[Any, ...], np.dtype[np.void]],
    +    )
    +
    +
    +def test_find_duplicates() -> None:
    +    ndtype = np.dtype([("a", int)])
    +
    +    a = np.ma.ones(7).view(ndtype)
    +    assert_type(
    +        rfn.find_duplicates(a),
    +        np.ma.MaskedArray[tuple[int], np.dtype[np.void]],
    +    )
    +    assert_type(
    +        rfn.find_duplicates(a, ignoremask=True, return_index=True),
    +        tuple[
    +            np.ma.MaskedArray[tuple[int], np.dtype[np.void]],
    +            np.ndarray[tuple[int], np.dtype[np.int_]],
    +        ],
    +    )
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/scalars.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/scalars.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..eeb707b255e103b481e846870b3f9a649e7d8f35
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/scalars.py
    @@ -0,0 +1,249 @@
    +import datetime as dt
    +
    +import pytest
    +
    +import numpy as np
    +
    +b = np.bool()
    +b_ = np.bool_()
    +u8 = np.uint64()
    +i8 = np.int64()
    +f8 = np.float64()
    +c16 = np.complex128()
    +U = np.str_()
    +S = np.bytes_()
    +
    +
    +# Construction
    +class D:
    +    def __index__(self) -> int:
    +        return 0
    +
    +
    +class C:
    +    def __complex__(self) -> complex:
    +        return 3j
    +
    +
    +class B:
    +    def __int__(self) -> int:
    +        return 4
    +
    +
    +class A:
    +    def __float__(self) -> float:
    +        return 4.0
    +
    +
    +np.complex64(3j)
    +np.complex64(A())
    +np.complex64(C())
    +np.complex128(3j)
    +np.complex128(C())
    +np.complex128(None)
    +np.complex64("1.2")
    +np.complex128(b"2j")
    +
    +np.int8(4)
    +np.int16(3.4)
    +np.int32(4)
    +np.int64(-1)
    +np.uint8(B())
    +np.uint32()
    +np.int32("1")
    +np.int64(b"2")
    +
    +np.float16(A())
    +np.float32(16)
    +np.float64(3.0)
    +np.float64(None)
    +np.float32("1")
    +np.float16(b"2.5")
    +
    +np.uint64(D())
    +np.float32(D())
    +np.complex64(D())
    +
    +np.bytes_(b"hello")
    +np.bytes_("hello", 'utf-8')
    +np.bytes_("hello", encoding='utf-8')
    +np.str_("hello")
    +np.str_(b"hello", 'utf-8')
    +np.str_(b"hello", encoding='utf-8')
    +
    +# Array-ish semantics
    +np.int8().real
    +np.int16().imag
    +np.int32().data
    +np.int64().flags
    +
    +np.uint8().itemsize * 2
    +np.uint16().ndim + 1
    +np.uint32().strides
    +np.uint64().shape
    +
    +# Time structures
    +np.datetime64()
    +np.datetime64(0, "D")
    +np.datetime64(0, b"D")
    +np.datetime64(0, ('ms', 3))
    +np.datetime64("2019")
    +np.datetime64(b"2019")
    +np.datetime64("2019", "D")
    +np.datetime64("2019", "us")
    +np.datetime64("2019", "as")
    +np.datetime64(np.datetime64())
    +np.datetime64(np.datetime64())
    +np.datetime64(dt.datetime(2000, 5, 3))
    +np.datetime64(dt.datetime(2000, 5, 3), "D")
    +np.datetime64(dt.datetime(2000, 5, 3), "us")
    +np.datetime64(dt.datetime(2000, 5, 3), "as")
    +np.datetime64(dt.date(2000, 5, 3))
    +np.datetime64(dt.date(2000, 5, 3), "D")
    +np.datetime64(dt.date(2000, 5, 3), "us")
    +np.datetime64(dt.date(2000, 5, 3), "as")
    +np.datetime64(None)
    +np.datetime64(None, "D")
    +
    +np.timedelta64()
    +np.timedelta64(0)
    +np.timedelta64(0, "D")
    +np.timedelta64(0, ('ms', 3))
    +np.timedelta64(0, b"D")
    +np.timedelta64("3")
    +np.timedelta64(b"5")
    +np.timedelta64(np.timedelta64(2))
    +np.timedelta64(dt.timedelta(2))
    +np.timedelta64(None)
    +np.timedelta64(None, "D")
    +
    +np.void(1)
    +np.void(np.int64(1))
    +np.void(True)
    +np.void(np.bool(True))
    +np.void(b"test")
    +np.void(np.bytes_("test"))
    +np.void(object(), [("a", "O"), ("b", "O")])
    +np.void(object(), dtype=[("a", "O"), ("b", "O")])
    +
    +# Protocols
    +i8 = np.int64()
    +u8 = np.uint64()
    +f8 = np.float64()
    +c16 = np.complex128()
    +b = np.bool()
    +td = np.timedelta64()
    +U = np.str_("1")
    +S = np.bytes_("1")
    +AR = np.array(1, dtype=np.float64)
    +
    +int(i8)
    +int(u8)
    +int(f8)
    +int(b)
    +int(td)
    +int(U)
    +int(S)
    +int(AR)
    +with pytest.warns(np.exceptions.ComplexWarning):
    +    int(c16)
    +
    +float(i8)
    +float(u8)
    +float(f8)
    +float(b_)
    +float(td)
    +float(U)
    +float(S)
    +float(AR)
    +with pytest.warns(np.exceptions.ComplexWarning):
    +    float(c16)
    +
    +complex(i8)
    +complex(u8)
    +complex(f8)
    +complex(c16)
    +complex(b_)
    +complex(td)
    +complex(U)
    +complex(AR)
    +
    +
    +# Misc
    +c16.dtype
    +c16.real
    +c16.imag
    +c16.real.real
    +c16.real.imag
    +c16.ndim
    +c16.size
    +c16.itemsize
    +c16.shape
    +c16.strides
    +c16.squeeze()
    +c16.byteswap()
    +c16.transpose()
    +
    +# Aliases
    +np.byte()
    +np.short()
    +np.intc()
    +np.intp()
    +np.int_()
    +np.longlong()
    +
    +np.ubyte()
    +np.ushort()
    +np.uintc()
    +np.uintp()
    +np.uint()
    +np.ulonglong()
    +
    +np.half()
    +np.single()
    +np.double()
    +np.longdouble()
    +
    +np.csingle()
    +np.cdouble()
    +np.clongdouble()
    +
    +b.item()
    +i8.item()
    +u8.item()
    +f8.item()
    +c16.item()
    +U.item()
    +S.item()
    +
    +b.tolist()
    +i8.tolist()
    +u8.tolist()
    +f8.tolist()
    +c16.tolist()
    +U.tolist()
    +S.tolist()
    +
    +b.ravel()
    +i8.ravel()
    +u8.ravel()
    +f8.ravel()
    +c16.ravel()
    +U.ravel()
    +S.ravel()
    +
    +b.flatten()
    +i8.flatten()
    +u8.flatten()
    +f8.flatten()
    +c16.flatten()
    +U.flatten()
    +S.flatten()
    +
    +b.reshape(1)
    +i8.reshape(1)
    +u8.reshape(1)
    +f8.reshape(1)
    +c16.reshape(1)
    +U.reshape(1)
    +S.reshape(1)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/shape.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/shape.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..e3b497bc031065fb7ec147eccc910e9ce78b043e
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/shape.py
    @@ -0,0 +1,19 @@
    +from typing import Any, NamedTuple
    +
    +import numpy as np
    +
    +
    +# Subtype of tuple[int, int]
    +class XYGrid(NamedTuple):
    +    x_axis: int
    +    y_axis: int
    +
    +# Test variance of _ShapeT_co
    +def accepts_2d(a: np.ndarray[tuple[int, int], Any]) -> None:
    +    return None
    +
    +
    +accepts_2d(np.empty(XYGrid(2, 2)))
    +accepts_2d(np.zeros(XYGrid(2, 2), dtype=int))
    +accepts_2d(np.ones(XYGrid(2, 2), dtype=int))
    +accepts_2d(np.full(XYGrid(2, 2), fill_value=5, dtype=int))
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/simple.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/simple.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..003e9ee58bb18ad4df507379c3859ebf11dbce50
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/simple.py
    @@ -0,0 +1,170 @@
    +"""Simple expression that should pass with mypy."""
    +import operator
    +from collections.abc import Iterable
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +# Basic checks
    +array = np.array([1, 2])
    +
    +
    +def ndarray_func(x: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]:
    +    return x
    +
    +
    +ndarray_func(np.array([1, 2], dtype=np.float64))
    +array == 1
    +array.dtype == float
    +
    +# Dtype construction
    +np.dtype(float)
    +np.dtype(np.float64)
    +np.dtype(None)
    +np.dtype("float64")
    +np.dtype(np.dtype(float))
    +np.dtype(("U", 10))
    +np.dtype((np.int32, (2, 2)))
    +# Define the arguments on the previous line to prevent bidirectional
    +# type inference in mypy from broadening the types.
    +two_tuples_dtype = [("R", "u1"), ("G", "u1"), ("B", "u1")]
    +np.dtype(two_tuples_dtype)
    +
    +three_tuples_dtype = [("R", "u1", 2)]
    +np.dtype(three_tuples_dtype)
    +
    +mixed_tuples_dtype = [("R", "u1"), ("G", np.str_, 1)]
    +np.dtype(mixed_tuples_dtype)
    +
    +shape_tuple_dtype = [("R", "u1", (2, 2))]
    +np.dtype(shape_tuple_dtype)
    +
    +shape_like_dtype = [("R", "u1", (2, 2)), ("G", np.str_, 1)]
    +np.dtype(shape_like_dtype)
    +
    +object_dtype = [("field1", object)]
    +np.dtype(object_dtype)
    +
    +np.dtype((np.int32, (np.int8, 4)))
    +
    +# Dtype comparison
    +np.dtype(float) == float
    +np.dtype(float) != np.float64
    +np.dtype(float) < None
    +np.dtype(float) <= "float64"
    +np.dtype(float) > np.dtype(float)
    +np.dtype(float) >= np.dtype(("U", 10))
    +
    +# Iteration and indexing
    +def iterable_func(x: Iterable[object]) -> Iterable[object]:
    +    return x
    +
    +
    +iterable_func(array)
    +list(array)
    +iter(array)
    +zip(array, array)
    +array[1]
    +array[:]
    +array[...]
    +array[:] = 0
    +
    +array_2d = np.ones((3, 3))
    +array_2d[:2, :2]
    +array_2d[:2, :2] = 0
    +array_2d[..., 0]
    +array_2d[..., 0] = 2
    +array_2d[-1, -1] = None
    +
    +array_obj = np.zeros(1, dtype=np.object_)
    +array_obj[0] = slice(None)
    +
    +# Other special methods
    +len(array)
    +str(array)
    +array_scalar = np.array(1)
    +int(array_scalar)
    +float(array_scalar)
    +complex(array_scalar)
    +bytes(array_scalar)
    +operator.index(array_scalar)
    +bool(array_scalar)
    +
    +# comparisons
    +array < 1
    +array <= 1
    +array == 1
    +array != 1
    +array > 1
    +array >= 1
    +1 < array
    +1 <= array
    +1 == array
    +1 != array
    +1 > array
    +1 >= array
    +
    +# binary arithmetic
    +array + 1
    +1 + array
    +array += 1
    +
    +array - 1
    +1 - array
    +array -= 1
    +
    +array * 1
    +1 * array
    +array *= 1
    +
    +nonzero_array = np.array([1, 2])
    +array / 1
    +1 / nonzero_array
    +float_array = np.array([1.0, 2.0])
    +float_array /= 1
    +
    +array // 1
    +1 // nonzero_array
    +array //= 1
    +
    +array % 1
    +1 % nonzero_array
    +array %= 1
    +
    +divmod(array, 1)
    +divmod(1, nonzero_array)
    +
    +array ** 1
    +1 ** array
    +array **= 1
    +
    +array << 1
    +1 << array
    +array <<= 1
    +
    +array >> 1
    +1 >> array
    +array >>= 1
    +
    +array & 1
    +1 & array
    +array &= 1
    +
    +array ^ 1
    +1 ^ array
    +array ^= 1
    +
    +array | 1
    +1 | array
    +array |= 1
    +
    +# unary arithmetic
    +-array
    ++array
    +abs(array)
    +~array
    +
    +# Other methods
    +array.transpose()
    +
    +array @ array
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/ufunc_config.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/ufunc_config.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..778e1b57f7e3831464f0bc9ba37e4baddcdf49d8
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/ufunc_config.py
    @@ -0,0 +1,64 @@
    +"""Typing tests for `numpy._core._ufunc_config`."""
    +
    +import numpy as np
    +
    +
    +def func1(a: str, b: int) -> None:
    +    return None
    +
    +
    +def func2(a: str, b: int, c: float = 1.0) -> None:
    +    return None
    +
    +
    +def func3(a: str, b: int) -> int:
    +    return 0
    +
    +
    +class Write1:
    +    def write(self, a: str) -> None:
    +        return None
    +
    +
    +class Write2:
    +    def write(self, a: str, b: int = 1) -> None:
    +        return None
    +
    +
    +class Write3:
    +    def write(self, a: str) -> int:
    +        return 0
    +
    +
    +_err_default = np.geterr()
    +_bufsize_default = np.getbufsize()
    +_errcall_default = np.geterrcall()
    +
    +try:
    +    np.seterr(all=None)
    +    np.seterr(divide="ignore")
    +    np.seterr(over="warn")
    +    np.seterr(under="call")
    +    np.seterr(invalid="raise")
    +    np.geterr()
    +
    +    np.setbufsize(4096)
    +    np.getbufsize()
    +
    +    np.seterrcall(func1)
    +    np.seterrcall(func2)
    +    np.seterrcall(func3)
    +    np.seterrcall(Write1())
    +    np.seterrcall(Write2())
    +    np.seterrcall(Write3())
    +    np.geterrcall()
    +
    +    with np.errstate(call=func1, all="call"):
    +        pass
    +    with np.errstate(call=Write1(), divide="log", over="log"):
    +        pass
    +
    +finally:
    +    np.seterr(**_err_default)
    +    np.setbufsize(_bufsize_default)
    +    np.seterrcall(_errcall_default)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/ufunclike.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/ufunclike.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..7e556d10bef7badff9fcf5c36a4d229b9ef63297
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/ufunclike.py
    @@ -0,0 +1,52 @@
    +from __future__ import annotations
    +
    +from typing import Any
    +
    +import numpy as np
    +
    +
    +class Object:
    +    def __ceil__(self) -> Object:
    +        return self
    +
    +    def __floor__(self) -> Object:
    +        return self
    +
    +    def __trunc__(self) -> Object:
    +        return self
    +
    +    def __ge__(self, value: object) -> bool:
    +        return True
    +
    +    def __array__(self, dtype: np.typing.DTypeLike | None = None,
    +                  copy: bool | None = None) -> np.ndarray[Any, np.dtype[np.object_]]:
    +        ret = np.empty((), dtype=object)
    +        ret[()] = self
    +        return ret
    +
    +
    +AR_LIKE_b = [True, True, False]
    +AR_LIKE_u = [np.uint32(1), np.uint32(2), np.uint32(3)]
    +AR_LIKE_i = [1, 2, 3]
    +AR_LIKE_f = [1.0, 2.0, 3.0]
    +AR_LIKE_O = [Object(), Object(), Object()]
    +AR_U: np.ndarray[Any, np.dtype[np.str_]] = np.zeros(3, dtype="U5")
    +
    +np.fix(AR_LIKE_b)  # type: ignore[deprecated]
    +np.fix(AR_LIKE_u)  # type: ignore[deprecated]
    +np.fix(AR_LIKE_i)  # type: ignore[deprecated]
    +np.fix(AR_LIKE_f)  # type: ignore[deprecated]
    +np.fix(AR_LIKE_O)  # type: ignore[deprecated]
    +np.fix(AR_LIKE_f, out=AR_U)  # type: ignore[deprecated]
    +
    +np.isposinf(AR_LIKE_b)
    +np.isposinf(AR_LIKE_u)
    +np.isposinf(AR_LIKE_i)
    +np.isposinf(AR_LIKE_f)
    +np.isposinf(AR_LIKE_f, out=AR_U)
    +
    +np.isneginf(AR_LIKE_b)
    +np.isneginf(AR_LIKE_u)
    +np.isneginf(AR_LIKE_i)
    +np.isneginf(AR_LIKE_f)
    +np.isneginf(AR_LIKE_f, out=AR_U)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/ufuncs.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/ufuncs.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..dbc61bb0b17b594865bc909787798238c5b32bf5
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/ufuncs.py
    @@ -0,0 +1,16 @@
    +import numpy as np
    +
    +np.sin(1)
    +np.sin([1, 2, 3])
    +np.sin(1, out=np.empty(1))
    +np.matmul(np.ones((2, 2, 2)), np.ones((2, 2, 2)), axes=[(0, 1), (0, 1), (0, 1)])
    +np.sin(1, signature="D->D")
    +# NOTE: `np.generic` subclasses are not guaranteed to support addition;
    +# re-enable this we can infer the exact return type of `np.sin(...)`.
    +#
    +# np.sin(1) + np.sin(1)
    +np.sin.types[0]
    +np.sin.__name__
    +np.sin.__doc__
    +
    +np.abs(np.array([1]))
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/warnings_and_errors.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/warnings_and_errors.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..c351afb084c57b2d1264a3ff020ed1e63703a623
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/pass/warnings_and_errors.py
    @@ -0,0 +1,6 @@
    +import numpy.exceptions as ex
    +
    +ex.AxisError("test")
    +ex.AxisError(1, ndim=2)
    +ex.AxisError(1, ndim=2, msg_prefix="error")
    +ex.AxisError(1, ndim=2, msg_prefix=None)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/arithmetic.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/arithmetic.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..491bce43fdaef328baebc8d3f3e800079098e959
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/arithmetic.pyi
    @@ -0,0 +1,719 @@
    +import datetime as dt
    +from typing import Any, assert_type
    +
    +import numpy as np
    +import numpy.typing as npt
    +from numpy._typing import _64Bit, _128Bit
    +
    +b: bool
    +c: complex
    +f: float
    +i: int
    +
    +c16: np.complex128
    +c8: np.complex64
    +
    +# Can't directly import `np.float128` as it is not available on all platforms
    +f16: np.floating[_128Bit]
    +f8: np.float64
    +f4: np.float32
    +
    +i8: np.int64
    +i4: np.int32
    +
    +u8: np.uint64
    +u4: np.uint32
    +
    +b_: np.bool
    +
    +M8: np.datetime64
    +M8_none: np.datetime64[None]
    +M8_date: np.datetime64[dt.date]
    +M8_time: np.datetime64[dt.datetime]
    +M8_int: np.datetime64[int]
    +date: dt.date
    +time: dt.datetime
    +
    +m8: np.timedelta64
    +m8_none: np.timedelta64[None]
    +m8_int: np.timedelta64[int]
    +m8_delta: np.timedelta64[dt.timedelta]
    +delta: dt.timedelta
    +
    +AR_b: npt.NDArray[np.bool]
    +AR_u: npt.NDArray[np.uint32]
    +AR_i: npt.NDArray[np.int64]
    +AR_f: npt.NDArray[np.float64]
    +AR_c: npt.NDArray[np.complex128]
    +AR_m: npt.NDArray[np.timedelta64]
    +AR_M: npt.NDArray[np.datetime64]
    +AR_O: npt.NDArray[np.object_]
    +AR_S: npt.NDArray[np.bytes_]
    +AR_U: npt.NDArray[np.str_]
    +AR_T: np.ndarray[tuple[Any, ...], np.dtypes.StringDType]
    +AR_floating: npt.NDArray[np.floating]
    +AR_number: npt.NDArray[np.number]
    +AR_Any: npt.NDArray[Any]
    +
    +AR_LIKE_b: list[bool]
    +AR_LIKE_u: list[np.uint32]
    +AR_LIKE_i: list[int]
    +AR_LIKE_f: list[float]
    +AR_LIKE_c: list[complex]
    +AR_LIKE_m: list[np.timedelta64]
    +AR_LIKE_M: list[np.datetime64]
    +AR_LIKE_O: list[np.object_]
    +
    +# Array subtraction
    +
    +assert_type(AR_number - AR_number, npt.NDArray[np.number])
    +
    +assert_type(AR_b - AR_LIKE_u, npt.NDArray[np.uint32])
    +assert_type(AR_b - AR_LIKE_i, npt.NDArray[np.signedinteger])
    +assert_type(AR_b - AR_LIKE_f, npt.NDArray[np.floating])
    +assert_type(AR_b - AR_LIKE_c, npt.NDArray[np.complexfloating])
    +assert_type(AR_b - AR_LIKE_m, npt.NDArray[np.timedelta64])
    +assert_type(AR_b - AR_LIKE_O, Any)
    +
    +assert_type(AR_LIKE_u - AR_b, npt.NDArray[np.uint32])
    +assert_type(AR_LIKE_i - AR_b, npt.NDArray[np.signedinteger])
    +assert_type(AR_LIKE_f - AR_b, npt.NDArray[np.floating])
    +assert_type(AR_LIKE_c - AR_b, npt.NDArray[np.complexfloating])
    +assert_type(AR_LIKE_m - AR_b, npt.NDArray[np.timedelta64])
    +assert_type(AR_LIKE_M - AR_b, npt.NDArray[np.datetime64])
    +assert_type(AR_LIKE_O - AR_b, Any)
    +
    +assert_type(AR_u - AR_LIKE_b, npt.NDArray[np.uint32])
    +assert_type(AR_u - AR_LIKE_u, npt.NDArray[np.unsignedinteger])
    +assert_type(AR_u - AR_LIKE_i, npt.NDArray[np.signedinteger])
    +assert_type(AR_u - AR_LIKE_f, npt.NDArray[np.floating])
    +assert_type(AR_u - AR_LIKE_c, npt.NDArray[np.complexfloating])
    +assert_type(AR_u - AR_LIKE_m, npt.NDArray[np.timedelta64])
    +assert_type(AR_u - AR_LIKE_O, Any)
    +
    +assert_type(AR_LIKE_b - AR_u, npt.NDArray[np.uint32])
    +assert_type(AR_LIKE_u - AR_u, npt.NDArray[np.unsignedinteger])
    +assert_type(AR_LIKE_i - AR_u, npt.NDArray[np.signedinteger])
    +assert_type(AR_LIKE_f - AR_u, npt.NDArray[np.floating])
    +assert_type(AR_LIKE_c - AR_u, npt.NDArray[np.complexfloating])
    +assert_type(AR_LIKE_m - AR_u, npt.NDArray[np.timedelta64])
    +assert_type(AR_LIKE_M - AR_u, npt.NDArray[np.datetime64])
    +assert_type(AR_LIKE_O - AR_u, Any)
    +
    +assert_type(AR_i - AR_LIKE_b, npt.NDArray[np.int64])
    +assert_type(AR_i - AR_LIKE_u, npt.NDArray[np.signedinteger])
    +assert_type(AR_i - AR_LIKE_i, npt.NDArray[np.signedinteger])
    +assert_type(AR_i - AR_LIKE_f, npt.NDArray[np.floating])
    +assert_type(AR_i - AR_LIKE_c, npt.NDArray[np.complexfloating])
    +assert_type(AR_i - AR_LIKE_m, npt.NDArray[np.timedelta64])
    +assert_type(AR_i - AR_LIKE_O, Any)
    +
    +assert_type(AR_LIKE_b - AR_i, npt.NDArray[np.int64])
    +assert_type(AR_LIKE_u - AR_i, npt.NDArray[np.signedinteger])
    +assert_type(AR_LIKE_i - AR_i, npt.NDArray[np.signedinteger])
    +assert_type(AR_LIKE_f - AR_i, npt.NDArray[np.floating])
    +assert_type(AR_LIKE_c - AR_i, npt.NDArray[np.complexfloating])
    +assert_type(AR_LIKE_m - AR_i, npt.NDArray[np.timedelta64])
    +assert_type(AR_LIKE_M - AR_i, npt.NDArray[np.datetime64])
    +assert_type(AR_LIKE_O - AR_i, Any)
    +
    +assert_type(AR_f - AR_LIKE_b, npt.NDArray[np.float64])
    +assert_type(AR_f - AR_LIKE_u, npt.NDArray[np.float64])
    +assert_type(AR_f - AR_LIKE_i, npt.NDArray[np.float64])
    +assert_type(AR_f - AR_LIKE_f, npt.NDArray[np.float64])
    +assert_type(AR_f - AR_LIKE_c, npt.NDArray[np.complexfloating])
    +assert_type(AR_f - AR_LIKE_O, Any)
    +
    +assert_type(AR_LIKE_b - AR_f, npt.NDArray[np.float64])
    +assert_type(AR_LIKE_u - AR_f, npt.NDArray[np.float64])
    +assert_type(AR_LIKE_i - AR_f, npt.NDArray[np.float64])
    +assert_type(AR_LIKE_f - AR_f, npt.NDArray[np.float64])
    +assert_type(AR_LIKE_c - AR_f, npt.NDArray[np.complexfloating])
    +assert_type(AR_LIKE_O - AR_f, Any)
    +
    +assert_type(AR_c - AR_LIKE_b, npt.NDArray[np.complex128])
    +assert_type(AR_c - AR_LIKE_u, npt.NDArray[np.complex128])
    +assert_type(AR_c - AR_LIKE_i, npt.NDArray[np.complex128])
    +assert_type(AR_c - AR_LIKE_f, npt.NDArray[np.complex128])
    +assert_type(AR_c - AR_LIKE_c, npt.NDArray[np.complex128])
    +assert_type(AR_c - AR_LIKE_O, Any)
    +
    +assert_type(AR_LIKE_b - AR_c, npt.NDArray[np.complex128])
    +assert_type(AR_LIKE_u - AR_c, npt.NDArray[np.complex128])
    +assert_type(AR_LIKE_i - AR_c, npt.NDArray[np.complex128])
    +assert_type(AR_LIKE_f - AR_c, npt.NDArray[np.complex128])
    +assert_type(AR_LIKE_c - AR_c, npt.NDArray[np.complex128])
    +assert_type(AR_LIKE_O - AR_c, Any)
    +
    +assert_type(AR_m - AR_LIKE_b, npt.NDArray[np.timedelta64])
    +assert_type(AR_m - AR_LIKE_u, npt.NDArray[np.timedelta64])
    +assert_type(AR_m - AR_LIKE_i, npt.NDArray[np.timedelta64])
    +assert_type(AR_m - AR_LIKE_m, npt.NDArray[np.timedelta64])
    +assert_type(AR_m - AR_LIKE_O, Any)
    +
    +assert_type(AR_LIKE_b - AR_m, npt.NDArray[np.timedelta64])
    +assert_type(AR_LIKE_u - AR_m, npt.NDArray[np.timedelta64])
    +assert_type(AR_LIKE_i - AR_m, npt.NDArray[np.timedelta64])
    +assert_type(AR_LIKE_m - AR_m, npt.NDArray[np.timedelta64])
    +assert_type(AR_LIKE_M - AR_m, npt.NDArray[np.datetime64])
    +assert_type(AR_LIKE_O - AR_m, Any)
    +
    +assert_type(AR_M - AR_LIKE_b, npt.NDArray[np.datetime64])
    +assert_type(AR_M - AR_LIKE_u, npt.NDArray[np.datetime64])
    +assert_type(AR_M - AR_LIKE_i, npt.NDArray[np.datetime64])
    +assert_type(AR_M - AR_LIKE_m, npt.NDArray[np.datetime64])
    +assert_type(AR_M - AR_LIKE_M, npt.NDArray[np.timedelta64])
    +assert_type(AR_M - AR_LIKE_O, Any)
    +
    +assert_type(AR_LIKE_M - AR_M, npt.NDArray[np.timedelta64])
    +assert_type(AR_LIKE_O - AR_M, Any)
    +
    +assert_type(AR_O - AR_LIKE_b, Any)
    +assert_type(AR_O - AR_LIKE_u, Any)
    +assert_type(AR_O - AR_LIKE_i, Any)
    +assert_type(AR_O - AR_LIKE_f, Any)
    +assert_type(AR_O - AR_LIKE_c, Any)
    +assert_type(AR_O - AR_LIKE_m, Any)
    +assert_type(AR_O - AR_LIKE_M, Any)
    +assert_type(AR_O - AR_LIKE_O, Any)
    +
    +assert_type(AR_LIKE_b - AR_O, Any)
    +assert_type(AR_LIKE_u - AR_O, Any)
    +assert_type(AR_LIKE_i - AR_O, Any)
    +assert_type(AR_LIKE_f - AR_O, Any)
    +assert_type(AR_LIKE_c - AR_O, Any)
    +assert_type(AR_LIKE_m - AR_O, Any)
    +assert_type(AR_LIKE_M - AR_O, Any)
    +assert_type(AR_LIKE_O - AR_O, Any)
    +
    +# Array "true" division
    +
    +assert_type(AR_f / b, npt.NDArray[np.float64])
    +assert_type(AR_f / i, npt.NDArray[np.float64])
    +assert_type(AR_f / f, npt.NDArray[np.float64])
    +
    +assert_type(b / AR_f, npt.NDArray[np.float64])
    +assert_type(i / AR_f, npt.NDArray[np.float64])
    +assert_type(f / AR_f, npt.NDArray[np.float64])
    +
    +assert_type(AR_b / AR_LIKE_b, npt.NDArray[np.float64])
    +assert_type(AR_b / AR_LIKE_u, npt.NDArray[np.float64])
    +assert_type(AR_b / AR_LIKE_i, npt.NDArray[np.float64])
    +assert_type(AR_b / AR_LIKE_f, npt.NDArray[np.float64])
    +assert_type(AR_b / AR_LIKE_O, Any)
    +
    +assert_type(AR_LIKE_b / AR_b, npt.NDArray[np.float64])
    +assert_type(AR_LIKE_u / AR_b, npt.NDArray[np.float64])
    +assert_type(AR_LIKE_i / AR_b, npt.NDArray[np.float64])
    +assert_type(AR_LIKE_f / AR_b, npt.NDArray[np.float64])
    +assert_type(AR_LIKE_O / AR_b, Any)
    +
    +assert_type(AR_u / AR_LIKE_b, npt.NDArray[np.float64])
    +assert_type(AR_u / AR_LIKE_u, npt.NDArray[np.float64])
    +assert_type(AR_u / AR_LIKE_i, npt.NDArray[np.float64])
    +assert_type(AR_u / AR_LIKE_f, npt.NDArray[np.float64])
    +assert_type(AR_u / AR_LIKE_O, Any)
    +
    +assert_type(AR_LIKE_b / AR_u, npt.NDArray[np.float64])
    +assert_type(AR_LIKE_u / AR_u, npt.NDArray[np.float64])
    +assert_type(AR_LIKE_i / AR_u, npt.NDArray[np.float64])
    +assert_type(AR_LIKE_f / AR_u, npt.NDArray[np.float64])
    +assert_type(AR_LIKE_m / AR_u, npt.NDArray[np.timedelta64])
    +assert_type(AR_LIKE_O / AR_u, Any)
    +
    +assert_type(AR_i / AR_LIKE_b, npt.NDArray[np.float64])
    +assert_type(AR_i / AR_LIKE_u, npt.NDArray[np.float64])
    +assert_type(AR_i / AR_LIKE_i, npt.NDArray[np.float64])
    +assert_type(AR_i / AR_LIKE_f, npt.NDArray[np.float64])
    +assert_type(AR_i / AR_LIKE_O, Any)
    +
    +assert_type(AR_LIKE_b / AR_i, npt.NDArray[np.float64])
    +assert_type(AR_LIKE_u / AR_i, npt.NDArray[np.float64])
    +assert_type(AR_LIKE_i / AR_i, npt.NDArray[np.float64])
    +assert_type(AR_LIKE_f / AR_i, npt.NDArray[np.float64])
    +assert_type(AR_LIKE_m / AR_i, npt.NDArray[np.timedelta64])
    +assert_type(AR_LIKE_O / AR_i, Any)
    +
    +assert_type(AR_f / AR_LIKE_b, npt.NDArray[np.float64])
    +assert_type(AR_f / AR_LIKE_u, npt.NDArray[np.float64])
    +assert_type(AR_f / AR_LIKE_i, npt.NDArray[np.float64])
    +assert_type(AR_f / AR_LIKE_f, npt.NDArray[np.float64])
    +assert_type(AR_f / AR_LIKE_O, Any)
    +
    +assert_type(AR_LIKE_b / AR_f, npt.NDArray[np.float64])
    +assert_type(AR_LIKE_u / AR_f, npt.NDArray[np.float64])
    +assert_type(AR_LIKE_i / AR_f, npt.NDArray[np.float64])
    +assert_type(AR_LIKE_f / AR_f, npt.NDArray[np.float64])
    +assert_type(AR_LIKE_m / AR_f, npt.NDArray[np.timedelta64])
    +assert_type(AR_LIKE_O / AR_f, Any)
    +
    +assert_type(AR_m / AR_LIKE_u, npt.NDArray[np.timedelta64])
    +assert_type(AR_m / AR_LIKE_i, npt.NDArray[np.timedelta64])
    +assert_type(AR_m / AR_LIKE_f, npt.NDArray[np.timedelta64])
    +assert_type(AR_m / AR_LIKE_m, npt.NDArray[np.float64])
    +assert_type(AR_m / AR_LIKE_O, Any)
    +
    +assert_type(AR_LIKE_m / AR_m, npt.NDArray[np.float64])
    +assert_type(AR_LIKE_O / AR_m, Any)
    +
    +assert_type(AR_O / AR_LIKE_b, Any)
    +assert_type(AR_O / AR_LIKE_u, Any)
    +assert_type(AR_O / AR_LIKE_i, Any)
    +assert_type(AR_O / AR_LIKE_f, Any)
    +assert_type(AR_O / AR_LIKE_m, Any)
    +assert_type(AR_O / AR_LIKE_M, Any)
    +assert_type(AR_O / AR_LIKE_O, Any)
    +
    +assert_type(AR_LIKE_b / AR_O, Any)
    +assert_type(AR_LIKE_u / AR_O, Any)
    +assert_type(AR_LIKE_i / AR_O, Any)
    +assert_type(AR_LIKE_f / AR_O, Any)
    +assert_type(AR_LIKE_m / AR_O, Any)
    +assert_type(AR_LIKE_M / AR_O, Any)
    +assert_type(AR_LIKE_O / AR_O, Any)
    +
    +# Array floor division
    +
    +assert_type(AR_b // AR_LIKE_b, npt.NDArray[np.int8])
    +assert_type(AR_b // AR_LIKE_u, npt.NDArray[np.uint32])
    +assert_type(AR_b // AR_LIKE_i, npt.NDArray[np.signedinteger])
    +assert_type(AR_b // AR_LIKE_f, npt.NDArray[np.floating])
    +assert_type(AR_b // AR_LIKE_O, Any)
    +
    +assert_type(AR_LIKE_b // AR_b, npt.NDArray[np.int8])
    +assert_type(AR_LIKE_u // AR_b, npt.NDArray[np.uint32])
    +assert_type(AR_LIKE_i // AR_b, npt.NDArray[np.signedinteger])
    +assert_type(AR_LIKE_f // AR_b, npt.NDArray[np.floating])
    +assert_type(AR_LIKE_O // AR_b, Any)
    +
    +assert_type(AR_u // AR_LIKE_b, npt.NDArray[np.uint32])
    +assert_type(AR_u // AR_LIKE_u, npt.NDArray[np.unsignedinteger])
    +assert_type(AR_u // AR_LIKE_i, npt.NDArray[np.signedinteger])
    +assert_type(AR_u // AR_LIKE_f, npt.NDArray[np.floating])
    +assert_type(AR_u // AR_LIKE_O, Any)
    +
    +assert_type(AR_LIKE_b // AR_u, npt.NDArray[np.uint32])
    +assert_type(AR_LIKE_u // AR_u, npt.NDArray[np.unsignedinteger])
    +assert_type(AR_LIKE_i // AR_u, npt.NDArray[np.signedinteger])
    +assert_type(AR_LIKE_f // AR_u, npt.NDArray[np.floating])
    +assert_type(AR_LIKE_m // AR_u, npt.NDArray[np.timedelta64])
    +assert_type(AR_LIKE_O // AR_u, Any)
    +
    +assert_type(AR_i // AR_LIKE_b, npt.NDArray[np.int64])
    +assert_type(AR_i // AR_LIKE_u, npt.NDArray[np.signedinteger])
    +assert_type(AR_i // AR_LIKE_i, npt.NDArray[np.signedinteger])
    +assert_type(AR_i // AR_LIKE_f, npt.NDArray[np.floating])
    +assert_type(AR_i // AR_LIKE_O, Any)
    +
    +assert_type(AR_LIKE_b // AR_i, npt.NDArray[np.int64])
    +assert_type(AR_LIKE_u // AR_i, npt.NDArray[np.signedinteger])
    +assert_type(AR_LIKE_i // AR_i, npt.NDArray[np.signedinteger])
    +assert_type(AR_LIKE_f // AR_i, npt.NDArray[np.floating])
    +assert_type(AR_LIKE_m // AR_i, npt.NDArray[np.timedelta64])
    +assert_type(AR_LIKE_O // AR_i, Any)
    +
    +assert_type(AR_f // AR_LIKE_b, npt.NDArray[np.float64])
    +assert_type(AR_f // AR_LIKE_u, npt.NDArray[np.float64])
    +assert_type(AR_f // AR_LIKE_i, npt.NDArray[np.float64])
    +assert_type(AR_f // AR_LIKE_f, npt.NDArray[np.float64])
    +assert_type(AR_f // AR_LIKE_O, Any)
    +
    +assert_type(AR_LIKE_b // AR_f, npt.NDArray[np.float64])
    +assert_type(AR_LIKE_u // AR_f, npt.NDArray[np.float64])
    +assert_type(AR_LIKE_i // AR_f, npt.NDArray[np.float64])
    +assert_type(AR_LIKE_f // AR_f, npt.NDArray[np.float64])
    +assert_type(AR_LIKE_m // AR_f, npt.NDArray[np.timedelta64])
    +assert_type(AR_LIKE_O // AR_f, Any)
    +
    +assert_type(AR_m // AR_LIKE_u, npt.NDArray[np.timedelta64])
    +assert_type(AR_m // AR_LIKE_i, npt.NDArray[np.timedelta64])
    +assert_type(AR_m // AR_LIKE_f, npt.NDArray[np.timedelta64])
    +assert_type(AR_m // AR_LIKE_m, npt.NDArray[np.int64])
    +assert_type(AR_m // AR_LIKE_O, Any)
    +
    +assert_type(AR_LIKE_m // AR_m, npt.NDArray[np.int64])
    +assert_type(AR_LIKE_O // AR_m, Any)
    +
    +assert_type(AR_O // AR_LIKE_b, Any)
    +assert_type(AR_O // AR_LIKE_u, Any)
    +assert_type(AR_O // AR_LIKE_i, Any)
    +assert_type(AR_O // AR_LIKE_f, Any)
    +assert_type(AR_O // AR_LIKE_m, Any)
    +assert_type(AR_O // AR_LIKE_M, Any)
    +assert_type(AR_O // AR_LIKE_O, Any)
    +
    +assert_type(AR_LIKE_b // AR_O, Any)
    +assert_type(AR_LIKE_u // AR_O, Any)
    +assert_type(AR_LIKE_i // AR_O, Any)
    +assert_type(AR_LIKE_f // AR_O, Any)
    +assert_type(AR_LIKE_m // AR_O, Any)
    +assert_type(AR_LIKE_M // AR_O, Any)
    +assert_type(AR_LIKE_O // AR_O, Any)
    +
    +# unary ops
    +
    +assert_type(-f16, np.floating[_128Bit])
    +assert_type(-c16, np.complex128)
    +assert_type(-c8, np.complex64)
    +assert_type(-f8, np.float64)
    +assert_type(-f4, np.float32)
    +assert_type(-i8, np.int64)
    +assert_type(-i4, np.int32)
    +assert_type(-u8, np.uint64)
    +assert_type(-u4, np.uint32)
    +assert_type(-m8, np.timedelta64)
    +assert_type(-m8_none, np.timedelta64[None])
    +assert_type(-m8_int, np.timedelta64[int])
    +assert_type(-m8_delta, np.timedelta64[dt.timedelta])
    +assert_type(-AR_f, npt.NDArray[np.float64])
    +
    +assert_type(+f16, np.floating[_128Bit])
    +assert_type(+c16, np.complex128)
    +assert_type(+c8, np.complex64)
    +assert_type(+f8, np.float64)
    +assert_type(+f4, np.float32)
    +assert_type(+i8, np.int64)
    +assert_type(+i4, np.int32)
    +assert_type(+u8, np.uint64)
    +assert_type(+u4, np.uint32)
    +assert_type(+m8_none, np.timedelta64[None])
    +assert_type(+m8_int, np.timedelta64[int])
    +assert_type(+m8_delta, np.timedelta64[dt.timedelta])
    +assert_type(+AR_f, npt.NDArray[np.float64])
    +
    +assert_type(abs(f16), np.floating[_128Bit])
    +assert_type(abs(c16), np.float64)
    +assert_type(abs(c8), np.float32)
    +assert_type(abs(f8), np.float64)
    +assert_type(abs(f4), np.float32)
    +assert_type(abs(i8), np.int64)
    +assert_type(abs(i4), np.int32)
    +assert_type(abs(u8), np.uint64)
    +assert_type(abs(u4), np.uint32)
    +assert_type(abs(m8), np.timedelta64)
    +assert_type(abs(m8_none), np.timedelta64[None])
    +assert_type(abs(m8_int), np.timedelta64[int])
    +assert_type(abs(m8_delta), np.timedelta64[dt.timedelta])
    +assert_type(abs(b_), np.bool)
    +assert_type(abs(AR_O), npt.NDArray[np.object_])
    +
    +# Time structures
    +
    +assert_type(M8 + m8, np.datetime64)
    +assert_type(M8 + i, np.datetime64)
    +assert_type(M8 + i8, np.datetime64)
    +assert_type(M8 - M8, np.timedelta64)
    +assert_type(M8 - i, np.datetime64)
    +assert_type(M8 - i8, np.datetime64)
    +
    +assert_type(M8_none + m8, np.datetime64[None])
    +assert_type(M8_none + i, np.datetime64[None])
    +assert_type(M8_none + i8, np.datetime64[None])
    +assert_type(M8_none - M8, np.timedelta64[None])
    +assert_type(M8_none - m8, np.datetime64[None])
    +assert_type(M8_none - i, np.datetime64[None])
    +assert_type(M8_none - i8, np.datetime64[None])
    +
    +assert_type(m8 + m8, np.timedelta64)
    +assert_type(m8 + i, np.timedelta64)
    +assert_type(m8 + i8, np.timedelta64)
    +assert_type(m8 - m8, np.timedelta64)
    +assert_type(m8 - i, np.timedelta64)
    +assert_type(m8 - i8, np.timedelta64)
    +assert_type(m8 * f, np.timedelta64)
    +assert_type(m8 * f4, np.timedelta64)
    +assert_type(m8 * np.True_, np.timedelta64)
    +assert_type(m8 / f, np.timedelta64)
    +assert_type(m8 / f4, np.timedelta64)
    +assert_type(m8 / m8, np.float64)
    +assert_type(m8 // m8, np.int64)
    +assert_type(m8 % m8, np.timedelta64)
    +assert_type(divmod(m8, m8), tuple[np.int64, np.timedelta64])
    +
    +assert_type(m8_none + m8, np.timedelta64[None])
    +assert_type(m8_none + i, np.timedelta64[None])
    +assert_type(m8_none + i8, np.timedelta64[None])
    +assert_type(m8_none - i, np.timedelta64[None])
    +assert_type(m8_none - i8, np.timedelta64[None])
    +
    +assert_type(m8_int + i, np.timedelta64[int])
    +assert_type(m8_int + m8_delta, np.timedelta64[int])
    +assert_type(m8_int + m8, np.timedelta64[int | None])
    +assert_type(m8_int - i, np.timedelta64[int])
    +assert_type(m8_int - m8_delta, np.timedelta64[int])
    +assert_type(m8_int - m8, np.timedelta64[int | None])
    +
    +assert_type(m8_delta + date, dt.date)
    +assert_type(m8_delta + time, dt.datetime)
    +assert_type(m8_delta + delta, dt.timedelta)
    +assert_type(m8_delta - delta, dt.timedelta)
    +assert_type(m8_delta / delta, float)
    +assert_type(m8_delta // delta, int)
    +assert_type(m8_delta % delta, dt.timedelta)
    +assert_type(divmod(m8_delta, delta), tuple[int, dt.timedelta])
    +
    +# boolean
    +
    +assert_type(b_ / b, np.float64)
    +assert_type(b_ / b_, np.float64)
    +assert_type(b_ / i, np.float64)
    +assert_type(b_ / i8, np.float64)
    +assert_type(b_ / i4, np.float64)
    +assert_type(b_ / u8, np.float64)
    +assert_type(b_ / u4, np.float64)
    +assert_type(b_ / f, np.float64)
    +assert_type(b_ / f16, np.floating[_128Bit])
    +assert_type(b_ / f8, np.float64)
    +assert_type(b_ / f4, np.float32)
    +assert_type(b_ / c, np.complex128)
    +assert_type(b_ / c16, np.complex128)
    +assert_type(b_ / c8, np.complex64)
    +
    +assert_type(b / b_, np.float64)
    +assert_type(b_ / b_, np.float64)
    +assert_type(i / b_, np.float64)
    +assert_type(i8 / b_, np.float64)
    +assert_type(i4 / b_, np.float64)
    +assert_type(u8 / b_, np.float64)
    +assert_type(u4 / b_, np.float64)
    +assert_type(f / b_, np.float64)
    +assert_type(f16 / b_, np.floating[_128Bit])
    +assert_type(f8 / b_, np.float64)
    +assert_type(f4 / b_, np.float32)
    +assert_type(c / b_, np.complex128)
    +assert_type(c16 / b_, np.complex128)
    +assert_type(c8 / b_, np.complex64)
    +
    +# Complex
    +
    +assert_type(c16 + f16, np.complexfloating)
    +assert_type(c16 + c16, np.complex128)
    +assert_type(c16 + f8, np.complex128)
    +assert_type(c16 + i8, np.complex128)
    +assert_type(c16 + c8, np.complex128)
    +assert_type(c16 + f4, np.complex128)
    +assert_type(c16 + i4, np.complex128)
    +assert_type(c16 + b_, np.complex128)
    +assert_type(c16 + b, np.complex128)
    +assert_type(c16 + c, np.complex128)
    +assert_type(c16 + f, np.complex128)
    +assert_type(c16 + AR_f, npt.NDArray[np.complex128])
    +
    +assert_type(f16 + c16, np.complexfloating)
    +assert_type(c16 + c16, np.complex128)
    +assert_type(f8 + c16, np.complex128)
    +assert_type(i8 + c16, np.complex128)
    +assert_type(c8 + c16, np.complex128 | np.complex64)
    +assert_type(f4 + c16, np.complexfloating)
    +assert_type(i4 + c16, np.complex128)
    +assert_type(b_ + c16, np.complex128)
    +assert_type(b + c16, np.complex128)
    +assert_type(c + c16, np.complex128)
    +assert_type(f + c16, np.complex128)
    +assert_type(AR_f + c16, npt.NDArray[np.complex128])
    +
    +assert_type(c8 + f16, np.complex64 | np.complexfloating[_128Bit, _128Bit])
    +assert_type(c8 + c16, np.complex64 | np.complex128)
    +assert_type(c8 + f8, np.complex64 | np.complex128)
    +assert_type(c8 + i8, np.complex64 | np.complexfloating[_64Bit, _64Bit])
    +assert_type(c8 + c8, np.complex64)
    +assert_type(c8 + f4, np.complex64)
    +assert_type(c8 + i4, np.complex64)
    +assert_type(c8 + b_, np.complex64)
    +assert_type(c8 + b, np.complex64)
    +assert_type(c8 + c, np.complex64 | np.complex128)
    +assert_type(c8 + f, np.complex64 | np.complex128)
    +assert_type(c8 + AR_f, npt.NDArray[np.complexfloating])
    +
    +assert_type(f16 + c8, np.complexfloating[_128Bit, _128Bit] | np.complex64)
    +assert_type(c16 + c8, np.complex128)
    +assert_type(f8 + c8, np.complexfloating[_64Bit, _64Bit])
    +assert_type(i8 + c8, np.complexfloating[_64Bit, _64Bit] | np.complex64)
    +assert_type(c8 + c8, np.complex64)
    +assert_type(f4 + c8, np.complex64)
    +assert_type(i4 + c8, np.complex64)
    +assert_type(b_ + c8, np.complex64)
    +assert_type(b + c8, np.complex64)
    +assert_type(c + c8, np.complex64 | np.complex128)
    +assert_type(f + c8, np.complex64 | np.complex128)
    +assert_type(AR_f + c8, npt.NDArray[np.complexfloating])
    +
    +# Float
    +
    +assert_type(f8 + f16, np.floating)
    +assert_type(f8 + f8, np.float64)
    +assert_type(f8 + i8, np.float64)
    +assert_type(f8 + f4, np.float64)
    +assert_type(f8 + i4, np.float64)
    +assert_type(f8 + b_, np.float64)
    +assert_type(f8 + b, np.float64)
    +assert_type(f8 + c, np.float64 | np.complex128)
    +assert_type(f8 + f, np.float64)
    +assert_type(f8 + AR_f, npt.NDArray[np.float64])
    +
    +assert_type(f16 + f8, np.floating)
    +assert_type(f8 + f8, np.float64)
    +assert_type(i8 + f8, np.float64)
    +assert_type(f4 + f8, np.floating)
    +assert_type(i4 + f8, np.float64)
    +assert_type(b_ + f8, np.float64)
    +assert_type(b + f8, np.float64)
    +assert_type(c + f8, np.complex128 | np.float64)
    +assert_type(f + f8, np.float64)
    +assert_type(AR_f + f8, npt.NDArray[np.float64])
    +
    +assert_type(f4 + f16, np.floating)
    +assert_type(f4 + f8, np.floating)
    +assert_type(f4 + i8, np.floating)
    +assert_type(f4 + f4, np.float32)
    +assert_type(f4 + i4, np.floating)
    +assert_type(f4 + b_, np.float32)
    +assert_type(f4 + b, np.float32)
    +assert_type(f4 + c, np.complexfloating)
    +assert_type(f4 + f, np.float32)
    +assert_type(f4 + AR_f, npt.NDArray[np.float64])
    +
    +assert_type(f16 + f4, np.floating)
    +assert_type(f8 + f4, np.float64)
    +assert_type(i8 + f4, np.floating)
    +assert_type(f4 + f4, np.float32)
    +assert_type(i4 + f4, np.floating)
    +assert_type(b_ + f4, np.float32)
    +assert_type(b + f4, np.float32)
    +assert_type(c + f4, np.complexfloating)
    +assert_type(f + f4, np.float32)
    +assert_type(AR_f + f4, npt.NDArray[np.float64])
    +
    +# Int
    +
    +assert_type(i8 + i8, np.int64)
    +assert_type(i8 + u8, Any)
    +assert_type(i8 + i4, np.signedinteger)
    +assert_type(i8 + u4, Any)
    +assert_type(i8 + b_, np.int64)
    +assert_type(i8 + b, np.int64)
    +assert_type(i8 + c, np.complex128)
    +assert_type(i8 + f, np.float64)
    +assert_type(i8 + AR_f, npt.NDArray[np.float64])
    +
    +assert_type(u8 + u8, np.uint64)
    +assert_type(u8 + i4, Any)
    +assert_type(u8 + u4, np.unsignedinteger)
    +assert_type(u8 + b_, np.uint64)
    +assert_type(u8 + b, np.uint64)
    +assert_type(u8 + c, np.complex128)
    +assert_type(u8 + f, np.float64)
    +assert_type(u8 + AR_f, npt.NDArray[np.float64])
    +
    +assert_type(i8 + i8, np.int64)
    +assert_type(u8 + i8, Any)
    +assert_type(i4 + i8, np.signedinteger)
    +assert_type(u4 + i8, Any)
    +assert_type(b_ + i8, np.int64)
    +assert_type(b + i8, np.int64)
    +assert_type(c + i8, np.complex128)
    +assert_type(f + i8, np.float64)
    +assert_type(AR_f + i8, npt.NDArray[np.float64])
    +
    +assert_type(u8 + u8, np.uint64)
    +assert_type(i4 + u8, Any)
    +assert_type(u4 + u8, np.unsignedinteger)
    +assert_type(b_ + u8, np.uint64)
    +assert_type(b + u8, np.uint64)
    +assert_type(c + u8, np.complex128)
    +assert_type(f + u8, np.float64)
    +assert_type(AR_f + u8, npt.NDArray[np.float64])
    +
    +assert_type(i4 + i8, np.signedinteger)
    +assert_type(i4 + i4, np.int32)
    +assert_type(i4 + b_, np.int32)
    +assert_type(i4 + b, np.int32)
    +assert_type(i4 + AR_f, npt.NDArray[np.float64])
    +
    +assert_type(u4 + i8, Any)
    +assert_type(u4 + i4, Any)
    +assert_type(u4 + u8, np.unsignedinteger)
    +assert_type(u4 + u4, np.uint32)
    +assert_type(u4 + b_, np.uint32)
    +assert_type(u4 + b, np.uint32)
    +assert_type(u4 + AR_f, npt.NDArray[np.float64])
    +
    +assert_type(i8 + i4, np.signedinteger)
    +assert_type(i4 + i4, np.int32)
    +assert_type(b_ + i4, np.int32)
    +assert_type(b + i4, np.int32)
    +assert_type(AR_f + i4, npt.NDArray[np.float64])
    +
    +assert_type(i8 + u4, Any)
    +assert_type(i4 + u4, Any)
    +assert_type(u8 + u4, np.unsignedinteger)
    +assert_type(u4 + u4, np.uint32)
    +assert_type(b_ + u4, np.uint32)
    +assert_type(b + u4, np.uint32)
    +assert_type(AR_f + u4, npt.NDArray[np.float64])
    +
    +# Any
    +
    +assert_type(AR_Any + 2, npt.NDArray[Any])
    +
    +# regression tests for https://github.com/numpy/numpy/issues/28805
    +
    +assert_type(AR_floating + f, npt.NDArray[np.floating])
    +assert_type(AR_floating - f, npt.NDArray[np.floating])
    +assert_type(AR_floating * f, npt.NDArray[np.floating])
    +assert_type(AR_floating ** f, npt.NDArray[np.floating])
    +assert_type(AR_floating / f, npt.NDArray[np.floating])
    +assert_type(AR_floating // f, npt.NDArray[np.floating])
    +assert_type(AR_floating % f, npt.NDArray[np.floating])
    +assert_type(divmod(AR_floating, f), tuple[npt.NDArray[np.floating], npt.NDArray[np.floating]])
    +
    +assert_type(f + AR_floating, npt.NDArray[np.floating])
    +assert_type(f - AR_floating, npt.NDArray[np.floating])
    +assert_type(f * AR_floating, npt.NDArray[np.floating])
    +assert_type(f ** AR_floating, npt.NDArray[np.floating])
    +assert_type(f / AR_floating, npt.NDArray[np.floating])
    +assert_type(f // AR_floating, npt.NDArray[np.floating])
    +assert_type(f % AR_floating, npt.NDArray[np.floating])
    +assert_type(divmod(f, AR_floating), tuple[npt.NDArray[np.floating], npt.NDArray[np.floating]])
    +
    +# character-like
    +
    +assert_type(AR_S + b"", npt.NDArray[np.bytes_])
    +assert_type(AR_S + [b""], npt.NDArray[np.bytes_])
    +assert_type([b""] + AR_S, npt.NDArray[np.bytes_])
    +assert_type(AR_S + AR_S, npt.NDArray[np.bytes_])
    +
    +assert_type(AR_U + "", npt.NDArray[np.str_])
    +assert_type(AR_U + [""], npt.NDArray[np.str_])
    +assert_type("" + AR_U, npt.NDArray[np.str_])
    +assert_type([""] + AR_U, npt.NDArray[np.str_])
    +assert_type(AR_U + AR_U, npt.NDArray[np.str_])
    +
    +assert_type(AR_T + "", np.ndarray[tuple[Any, ...], np.dtypes.StringDType])
    +assert_type(AR_T + [""], np.ndarray[tuple[Any, ...], np.dtypes.StringDType])
    +assert_type("" + AR_T, np.ndarray[tuple[Any, ...], np.dtypes.StringDType])
    +assert_type([""] + AR_T, np.ndarray[tuple[Any, ...], np.dtypes.StringDType])
    +assert_type(AR_T + AR_T, np.ndarray[tuple[Any, ...], np.dtypes.StringDType])
    +assert_type(AR_T + AR_U, np.ndarray[tuple[Any, ...], np.dtypes.StringDType])
    +assert_type(AR_U + AR_T, np.ndarray[tuple[Any, ...], np.dtypes.StringDType])
    +
    +assert_type(AR_S * i, np.ndarray[tuple[Any, ...], np.dtype[np.bytes_]])
    +assert_type(AR_S * AR_LIKE_i, np.ndarray[tuple[Any, ...], np.dtype[np.bytes_]])
    +assert_type(AR_S * AR_i, np.ndarray[tuple[Any, ...], np.dtype[np.bytes_]])
    +assert_type(i * AR_S, np.ndarray[tuple[Any, ...], np.dtype[np.bytes_]])
    +# mypy incorrectly infers `AR_LIKE_i * AR_S` as `list[int]`
    +assert_type(AR_i * AR_S, np.ndarray[tuple[Any, ...], np.dtype[np.bytes_]])
    +
    +assert_type(AR_U * i, np.ndarray[tuple[Any, ...], np.dtype[np.str_]])
    +assert_type(AR_U * AR_LIKE_i, np.ndarray[tuple[Any, ...], np.dtype[np.str_]])
    +assert_type(AR_U * AR_i, np.ndarray[tuple[Any, ...], np.dtype[np.str_]])
    +assert_type(i * AR_U, np.ndarray[tuple[Any, ...], np.dtype[np.str_]])
    +# mypy incorrectly infers `AR_LIKE_i * AR_U` as `list[int]`
    +assert_type(AR_i * AR_U, np.ndarray[tuple[Any, ...], np.dtype[np.str_]])
    +
    +assert_type(AR_T * i, np.ndarray[tuple[Any, ...], np.dtypes.StringDType])
    +assert_type(AR_T * AR_LIKE_i, np.ndarray[tuple[Any, ...], np.dtypes.StringDType])
    +assert_type(AR_T * AR_i, np.ndarray[tuple[Any, ...], np.dtypes.StringDType])
    +assert_type(i * AR_T, np.ndarray[tuple[Any, ...], np.dtypes.StringDType])
    +# mypy incorrectly infers `AR_LIKE_i * AR_T` as `list[int]`
    +assert_type(AR_i * AR_T, np.ndarray[tuple[Any, ...], np.dtypes.StringDType])
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/array_api_info.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/array_api_info.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..765f9eff516856657f4aaa8aebf22a4c2851a599
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/array_api_info.pyi
    @@ -0,0 +1,70 @@
    +from typing import Literal, Never, assert_type
    +
    +import numpy as np
    +
    +info = np.__array_namespace_info__()
    +
    +assert_type(info.__module__, Literal["numpy"])
    +
    +assert_type(info.default_device(), Literal["cpu"])
    +assert_type(info.devices()[0], Literal["cpu"])
    +assert_type(info.devices()[-1], Literal["cpu"])
    +
    +assert_type(info.capabilities()["boolean indexing"], Literal[True])
    +assert_type(info.capabilities()["data-dependent shapes"], Literal[True])
    +
    +assert_type(info.default_dtypes()["real floating"], np.dtype[np.float64])
    +assert_type(info.default_dtypes()["complex floating"], np.dtype[np.complex128])
    +assert_type(info.default_dtypes()["integral"], np.dtype[np.int_])
    +assert_type(info.default_dtypes()["indexing"], np.dtype[np.intp])
    +
    +assert_type(info.dtypes()["bool"], np.dtype[np.bool])
    +assert_type(info.dtypes()["int8"], np.dtype[np.int8])
    +assert_type(info.dtypes()["uint8"], np.dtype[np.uint8])
    +assert_type(info.dtypes()["float32"], np.dtype[np.float32])
    +assert_type(info.dtypes()["complex64"], np.dtype[np.complex64])
    +
    +assert_type(info.dtypes(kind="bool")["bool"], np.dtype[np.bool])
    +assert_type(info.dtypes(kind="signed integer")["int64"], np.dtype[np.int64])
    +assert_type(info.dtypes(kind="unsigned integer")["uint64"], np.dtype[np.uint64])
    +assert_type(info.dtypes(kind="integral")["int32"], np.dtype[np.int32])
    +assert_type(info.dtypes(kind="integral")["uint32"], np.dtype[np.uint32])
    +assert_type(info.dtypes(kind="real floating")["float64"], np.dtype[np.float64])
    +assert_type(info.dtypes(kind="complex floating")["complex128"], np.dtype[np.complex128])
    +assert_type(info.dtypes(kind="numeric")["int16"], np.dtype[np.int16])
    +assert_type(info.dtypes(kind="numeric")["uint16"], np.dtype[np.uint16])
    +assert_type(info.dtypes(kind="numeric")["float64"], np.dtype[np.float64])
    +assert_type(info.dtypes(kind="numeric")["complex128"], np.dtype[np.complex128])
    +
    +assert_type(info.dtypes(kind=()), dict[Never, Never])
    +
    +assert_type(info.dtypes(kind=("bool",))["bool"], np.dtype[np.bool])
    +assert_type(info.dtypes(kind=("signed integer",))["int64"], np.dtype[np.int64])
    +assert_type(info.dtypes(kind=("integral",))["uint32"], np.dtype[np.uint32])
    +assert_type(info.dtypes(kind=("complex floating",))["complex128"], np.dtype[np.complex128])
    +assert_type(info.dtypes(kind=("numeric",))["float64"], np.dtype[np.float64])
    +
    +assert_type(
    +    info.dtypes(kind=("signed integer", "unsigned integer"))["int8"],
    +    np.dtype[np.int8],
    +)
    +assert_type(
    +    info.dtypes(kind=("signed integer", "unsigned integer"))["uint8"],
    +    np.dtype[np.uint8],
    +)
    +assert_type(
    +    info.dtypes(kind=("integral", "real floating", "complex floating"))["int16"],
    +    np.dtype[np.int16],
    +)
    +assert_type(
    +    info.dtypes(kind=("integral", "real floating", "complex floating"))["uint16"],
    +    np.dtype[np.uint16],
    +)
    +assert_type(
    +    info.dtypes(kind=("integral", "real floating", "complex floating"))["float32"],
    +    np.dtype[np.float32],
    +)
    +assert_type(
    +    info.dtypes(kind=("integral", "real floating", "complex floating"))["complex64"],
    +    np.dtype[np.complex64],
    +)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/array_constructors.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/array_constructors.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..bfdbe8727bb855793e44448526c70879b7344eff
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/array_constructors.pyi
    @@ -0,0 +1,279 @@
    +import sys
    +from collections import deque
    +from pathlib import Path
    +from typing import Any, Generic, TypeVar, assert_type
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +_ScalarT_co = TypeVar("_ScalarT_co", bound=np.generic, covariant=True)
    +
    +class SubClass(npt.NDArray[_ScalarT_co]): ...
    +
    +class IntoSubClass(Generic[_ScalarT_co]):
    +    def __array__(self) -> SubClass[_ScalarT_co]: ...
    +
    +i8: np.int64
    +
    +A: npt.NDArray[np.float64]
    +B: SubClass[np.float64]
    +C: list[int]
    +D: SubClass[np.float64 | np.int64]
    +E: IntoSubClass[np.float64 | np.int64]
    +
    +mixed_shape: tuple[int, np.int64]
    +
    +def func(i: int, j: int, **kwargs: Any) -> SubClass[np.float64]: ...
    +
    +assert_type(np.empty_like(A), npt.NDArray[np.float64])
    +assert_type(np.empty_like(B), SubClass[np.float64])
    +assert_type(np.empty_like([1, 1.0]), npt.NDArray[Any])
    +assert_type(np.empty_like(A, dtype=np.int64), npt.NDArray[np.int64])
    +assert_type(np.empty_like(A, dtype="c16"), npt.NDArray[Any])
    +
    +assert_type(np.array(A), npt.NDArray[np.float64])
    +assert_type(np.array(B), npt.NDArray[np.float64])
    +assert_type(np.array([1, 1.0]), npt.NDArray[Any])
    +assert_type(np.array(deque([1, 2, 3])), npt.NDArray[Any])
    +assert_type(np.array(A, dtype=np.int64), npt.NDArray[np.int64])
    +assert_type(np.array(A, dtype="c16"), npt.NDArray[Any])
    +assert_type(np.array(A, like=A), npt.NDArray[np.float64])
    +assert_type(np.array(A, subok=True), npt.NDArray[np.float64])
    +assert_type(np.array(B, subok=True), SubClass[np.float64])
    +assert_type(np.array(B, subok=True, ndmin=0), SubClass[np.float64])
    +assert_type(np.array(B, subok=True, ndmin=1), SubClass[np.float64])
    +assert_type(np.array(D), npt.NDArray[np.float64 | np.int64])
    +assert_type(np.array(E, subok=True), SubClass[np.float64 | np.int64])
    +# https://github.com/numpy/numpy/issues/29245
    +assert_type(np.array([], dtype=np.bool), npt.NDArray[np.bool])
    +
    +assert_type(np.zeros([1, 5, 6]), npt.NDArray[np.float64])
    +assert_type(np.zeros([1, 5, 6], dtype=np.int64), npt.NDArray[np.int64])
    +assert_type(np.zeros([1, 5, 6], dtype="c16"), npt.NDArray[Any])
    +assert_type(np.zeros(mixed_shape), npt.NDArray[np.float64])
    +
    +assert_type(np.empty([1, 5, 6]), npt.NDArray[np.float64])
    +assert_type(np.empty([1, 5, 6], dtype=np.int64), npt.NDArray[np.int64])
    +assert_type(np.empty([1, 5, 6], dtype="c16"), npt.NDArray[Any])
    +assert_type(np.empty(mixed_shape), npt.NDArray[np.float64])
    +
    +assert_type(np.concatenate(A), npt.NDArray[np.float64])
    +assert_type(np.concatenate([A, A]), npt.NDArray[Any])  # pyright correctly infers this as NDArray[float64]
    +assert_type(np.concatenate([[1], A]), npt.NDArray[Any])
    +assert_type(np.concatenate([[1], [1]]), npt.NDArray[Any])
    +assert_type(np.concatenate((A, A)), npt.NDArray[np.float64])
    +assert_type(np.concatenate(([1], [1])), npt.NDArray[Any])
    +assert_type(np.concatenate([1, 1.0]), npt.NDArray[Any])
    +assert_type(np.concatenate(A, dtype=np.int64), npt.NDArray[np.int64])
    +assert_type(np.concatenate(A, dtype="c16"), npt.NDArray[Any])
    +assert_type(np.concatenate([1, 1.0], out=A), npt.NDArray[np.float64])
    +
    +assert_type(np.asarray(A), npt.NDArray[np.float64])
    +assert_type(np.asarray(B), npt.NDArray[np.float64])
    +assert_type(np.asarray([1, 1.0]), npt.NDArray[Any])
    +assert_type(np.asarray(A, dtype=np.int64), npt.NDArray[np.int64])
    +assert_type(np.asarray(A, dtype="c16"), npt.NDArray[Any])
    +
    +assert_type(np.asanyarray(A), npt.NDArray[np.float64])
    +assert_type(np.asanyarray(B), SubClass[np.float64])
    +assert_type(np.asanyarray([1, 1.0]), npt.NDArray[Any])
    +assert_type(np.asanyarray(A, dtype=np.int64), npt.NDArray[np.int64])
    +assert_type(np.asanyarray(A, dtype="c16"), npt.NDArray[Any])
    +
    +assert_type(np.ascontiguousarray(A), npt.NDArray[np.float64])
    +assert_type(np.ascontiguousarray(B), npt.NDArray[np.float64])
    +assert_type(np.ascontiguousarray([1, 1.0]), npt.NDArray[Any])
    +assert_type(np.ascontiguousarray(A, dtype=np.int64), npt.NDArray[np.int64])
    +assert_type(np.ascontiguousarray(A, dtype="c16"), npt.NDArray[Any])
    +
    +assert_type(np.asfortranarray(A), npt.NDArray[np.float64])
    +assert_type(np.asfortranarray(B), npt.NDArray[np.float64])
    +assert_type(np.asfortranarray([1, 1.0]), npt.NDArray[Any])
    +assert_type(np.asfortranarray(A, dtype=np.int64), npt.NDArray[np.int64])
    +assert_type(np.asfortranarray(A, dtype="c16"), npt.NDArray[Any])
    +
    +assert_type(np.fromstring("1 1 1", sep=" "), npt.NDArray[np.float64])
    +assert_type(np.fromstring(b"1 1 1", sep=" "), npt.NDArray[np.float64])
    +assert_type(np.fromstring("1 1 1", dtype=np.int64, sep=" "), npt.NDArray[np.int64])
    +assert_type(np.fromstring(b"1 1 1", dtype=np.int64, sep=" "), npt.NDArray[np.int64])
    +assert_type(np.fromstring("1 1 1", dtype="c16", sep=" "), npt.NDArray[Any])
    +assert_type(np.fromstring(b"1 1 1", dtype="c16", sep=" "), npt.NDArray[Any])
    +
    +assert_type(np.fromfile("test.txt", sep=" "), npt.NDArray[np.float64])
    +assert_type(np.fromfile("test.txt", dtype=np.int64, sep=" "), npt.NDArray[np.int64])
    +assert_type(np.fromfile("test.txt", dtype="c16", sep=" "), npt.NDArray[Any])
    +with open("test.txt") as f:
    +    assert_type(np.fromfile(f, sep=" "), npt.NDArray[np.float64])
    +    assert_type(np.fromfile(b"test.txt", sep=" "), npt.NDArray[np.float64])
    +    assert_type(np.fromfile(Path("test.txt"), sep=" "), npt.NDArray[np.float64])
    +
    +assert_type(np.fromiter("12345", np.float64), npt.NDArray[np.float64])
    +assert_type(np.fromiter("12345", float), npt.NDArray[Any])
    +
    +assert_type(np.frombuffer(A), npt.NDArray[np.float64])
    +assert_type(np.frombuffer(A, dtype=np.int64), npt.NDArray[np.int64])
    +assert_type(np.frombuffer(A, dtype="c16"), npt.NDArray[Any])
    +
    +_x_bool: bool
    +_x_int: int
    +_x_float: float
    +_x_timedelta: np.timedelta64
    +_x_datetime: np.datetime64
    +
    +assert_type(np.arange(False, True), np.ndarray[tuple[int], np.dtype[np.int_]])
    +assert_type(np.arange(10), np.ndarray[tuple[int], np.dtype[np.int_]])
    +assert_type(np.arange(0, 10, step=2), np.ndarray[tuple[int], np.dtype[np.int_]])
    +assert_type(np.arange(10.0), np.ndarray[tuple[int], np.dtype[np.float64 | Any]])
    +assert_type(np.arange(0, stop=10.0), np.ndarray[tuple[int], np.dtype[np.float64 | Any]])
    +assert_type(np.arange(_x_timedelta), np.ndarray[tuple[int], np.dtype[np.timedelta64[Any]]])
    +assert_type(np.arange(0, _x_timedelta), np.ndarray[tuple[int], np.dtype[np.timedelta64[Any]]])
    +assert_type(np.arange(_x_datetime, _x_datetime), np.ndarray[tuple[int], np.dtype[np.datetime64[Any]]])
    +assert_type(np.arange(10, dtype=np.float64), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(np.arange(0, 10, step=2, dtype=np.int16), np.ndarray[tuple[int], np.dtype[np.int16]])
    +assert_type(np.arange(10, dtype=int), np.ndarray[tuple[int], np.dtype[np.int_]])
    +assert_type(np.arange(0, 10, dtype="f8"), np.ndarray[tuple[int], np.dtype])
    +# https://github.com/numpy/numpy/issues/30628
    +assert_type(np.arange("2025-12-20", "2025-12-23", dtype="datetime64[D]"), np.ndarray[tuple[int], np.dtype[np.datetime64[Any]]])
    +
    +assert_type(np.require(A), npt.NDArray[np.float64])
    +assert_type(np.require(B), SubClass[np.float64])
    +assert_type(np.require(B, requirements=None), SubClass[np.float64])
    +assert_type(np.require(B, dtype=int), npt.NDArray[Any])
    +assert_type(np.require(B, requirements="E"), npt.NDArray[Any])
    +assert_type(np.require(B, requirements=["ENSUREARRAY"]), npt.NDArray[Any])
    +assert_type(np.require(B, requirements={"F", "E"}), npt.NDArray[Any])
    +assert_type(np.require(B, requirements=["C", "OWNDATA"]), SubClass[np.float64])
    +assert_type(np.require(B, requirements="W"), SubClass[np.float64])
    +assert_type(np.require(B, requirements="A"), SubClass[np.float64])
    +assert_type(np.require(C), npt.NDArray[Any])
    +
    +assert_type(np.linspace(0, 10), npt.NDArray[np.float64])
    +assert_type(np.linspace(0, 10j), npt.NDArray[np.complexfloating])
    +assert_type(np.linspace(0, 10, dtype=np.int64), npt.NDArray[np.int64])
    +assert_type(np.linspace(0, 10, dtype=int), npt.NDArray[Any])
    +assert_type(np.linspace(0, 10, retstep=True), tuple[npt.NDArray[np.float64], np.float64])
    +assert_type(np.linspace(0j, 10, retstep=True), tuple[npt.NDArray[np.complexfloating], np.complexfloating])
    +assert_type(np.linspace(0, 10, retstep=True, dtype=np.int64), tuple[npt.NDArray[np.int64], np.int64])
    +assert_type(np.linspace(0j, 10, retstep=True, dtype=int), tuple[npt.NDArray[Any], Any])
    +
    +assert_type(np.logspace(0, 10), npt.NDArray[np.float64])
    +assert_type(np.logspace(0, 10j), npt.NDArray[np.complexfloating])
    +assert_type(np.logspace(0, 10, dtype=np.int64), npt.NDArray[np.int64])
    +assert_type(np.logspace(0, 10, dtype=int), npt.NDArray[Any])
    +
    +assert_type(np.geomspace(0, 10), npt.NDArray[np.float64])
    +assert_type(np.geomspace(0, 10j), npt.NDArray[np.complexfloating])
    +assert_type(np.geomspace(0, 10, dtype=np.int64), npt.NDArray[np.int64])
    +assert_type(np.geomspace(0, 10, dtype=int), npt.NDArray[Any])
    +
    +assert_type(np.zeros_like(A), npt.NDArray[np.float64])
    +assert_type(np.zeros_like(C), npt.NDArray[Any])
    +assert_type(np.zeros_like(A, dtype=float), npt.NDArray[Any])
    +assert_type(np.zeros_like(B), SubClass[np.float64])
    +assert_type(np.zeros_like(B, dtype=np.int64), npt.NDArray[np.int64])
    +
    +assert_type(np.ones_like(A), npt.NDArray[np.float64])
    +assert_type(np.ones_like(C), npt.NDArray[Any])
    +assert_type(np.ones_like(A, dtype=float), npt.NDArray[Any])
    +assert_type(np.ones_like(B), SubClass[np.float64])
    +assert_type(np.ones_like(B, dtype=np.int64), npt.NDArray[np.int64])
    +
    +assert_type(np.full_like(A, i8), npt.NDArray[np.float64])
    +assert_type(np.full_like(C, i8), npt.NDArray[Any])
    +assert_type(np.full_like(A, i8, dtype=int), npt.NDArray[Any])
    +assert_type(np.full_like(B, i8), SubClass[np.float64])
    +assert_type(np.full_like(B, i8, dtype=np.int64), npt.NDArray[np.int64])
    +
    +_size: int
    +_shape_0d: tuple[()]
    +_shape_1d: tuple[int]
    +_shape_2d: tuple[int, int]
    +_shape_nd: tuple[int, ...]
    +_shape_like: list[int]
    +
    +assert_type(np.ones(_shape_0d), np.ndarray[tuple[()], np.dtype[np.float64]])
    +assert_type(np.ones(_size), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(np.ones(_shape_2d), np.ndarray[tuple[int, int], np.dtype[np.float64]])
    +assert_type(np.ones(_shape_nd), np.ndarray[tuple[int, ...], np.dtype[np.float64]])
    +assert_type(np.ones(_shape_1d, dtype=np.int64), np.ndarray[tuple[int], np.dtype[np.int64]])
    +assert_type(np.ones(_shape_like), npt.NDArray[np.float64])
    +assert_type(
    +    np.ones(_shape_like, dtype=np.dtypes.Int64DType()),
    +    np.ndarray[tuple[Any, ...], np.dtypes.Int64DType],
    +)
    +assert_type(np.ones(_shape_like, dtype=int), npt.NDArray[Any])
    +assert_type(np.ones(mixed_shape), npt.NDArray[np.float64])
    +
    +assert_type(np.full(_size, i8), np.ndarray[tuple[int], np.dtype[np.int64]])
    +assert_type(np.full(_shape_2d, i8), np.ndarray[tuple[int, int], np.dtype[np.int64]])
    +assert_type(np.full(_shape_like, i8), npt.NDArray[np.int64])
    +assert_type(np.full(_shape_like, 42), npt.NDArray[Any])
    +assert_type(np.full(_size, i8, dtype=np.float64), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(np.full(_size, i8, dtype=float), np.ndarray[tuple[int], np.dtype])
    +assert_type(np.full(_shape_like, 42, dtype=float), npt.NDArray[Any])
    +assert_type(np.full(_shape_0d, i8, dtype=object), np.ndarray[tuple[()], np.dtype])
    +
    +assert_type(np.indices([1, 2, 3]), npt.NDArray[np.int_])
    +assert_type(np.indices([1, 2, 3], sparse=True), tuple[npt.NDArray[np.int_], ...])
    +
    +assert_type(np.fromfunction(func, (3, 5)), SubClass[np.float64])
    +
    +assert_type(np.identity(3), np.ndarray[tuple[int, int], np.dtype[np.float64]])
    +assert_type(np.identity(3, dtype=np.int8), np.ndarray[tuple[int, int], np.dtype[np.int8]])
    +assert_type(np.identity(3, dtype=bool), np.ndarray[tuple[int, int], np.dtype[np.bool]])
    +assert_type(np.identity(3, dtype="bool"), np.ndarray[tuple[int, int], np.dtype[np.bool]])
    +assert_type(np.identity(3, dtype="b1"), np.ndarray[tuple[int, int], np.dtype[np.bool]])
    +assert_type(np.identity(3, dtype="?"), np.ndarray[tuple[int, int], np.dtype[np.bool]])
    +assert_type(np.identity(3, dtype=int), np.ndarray[tuple[int, int], np.dtype[np.int_ | Any]])
    +assert_type(np.identity(3, dtype="int"), np.ndarray[tuple[int, int], np.dtype[np.int_ | Any]])
    +assert_type(np.identity(3, dtype="n"), np.ndarray[tuple[int, int], np.dtype[np.int_ | Any]])
    +assert_type(np.identity(3, dtype=float), np.ndarray[tuple[int, int], np.dtype[np.float64 | Any]])
    +assert_type(np.identity(3, dtype="float"), np.ndarray[tuple[int, int], np.dtype[np.float64 | Any]])
    +assert_type(np.identity(3, dtype="f8"), np.ndarray[tuple[int, int], np.dtype[np.float64 | Any]])
    +assert_type(np.identity(3, dtype="d"), np.ndarray[tuple[int, int], np.dtype[np.float64 | Any]])
    +assert_type(np.identity(3, dtype=complex), np.ndarray[tuple[int, int], np.dtype[np.complex128 | Any]])
    +assert_type(np.identity(3, dtype="complex"), np.ndarray[tuple[int, int], np.dtype[np.complex128 | Any]])
    +assert_type(np.identity(3, dtype="c16"), np.ndarray[tuple[int, int], np.dtype[np.complex128 | Any]])
    +assert_type(np.identity(3, dtype="D"), np.ndarray[tuple[int, int], np.dtype[np.complex128 | Any]])
    +
    +assert_type(np.atleast_1d(A), npt.NDArray[np.float64])
    +assert_type(np.atleast_1d(C), npt.NDArray[Any])
    +assert_type(np.atleast_1d(A, A), tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]])
    +assert_type(np.atleast_1d(A, C), tuple[npt.NDArray[Any], npt.NDArray[Any]])
    +assert_type(np.atleast_1d(C, C), tuple[npt.NDArray[Any], npt.NDArray[Any]])
    +assert_type(np.atleast_1d(A, A, A), tuple[npt.NDArray[np.float64], ...])
    +assert_type(np.atleast_1d(C, C, C), tuple[npt.NDArray[Any], ...])
    +
    +assert_type(np.atleast_2d(A), npt.NDArray[np.float64])
    +assert_type(np.atleast_2d(A, A), tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]])
    +assert_type(np.atleast_2d(A, A, A), tuple[npt.NDArray[np.float64], ...])
    +
    +assert_type(np.atleast_3d(A), npt.NDArray[np.float64])
    +assert_type(np.atleast_3d(A, A), tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]])
    +assert_type(np.atleast_3d(A, A, A), tuple[npt.NDArray[np.float64], ...])
    +
    +assert_type(np.vstack([A, A]), npt.NDArray[np.float64])
    +assert_type(np.vstack([A, A], dtype=np.float32), npt.NDArray[np.float32])
    +assert_type(np.vstack([A, C]), npt.NDArray[Any])
    +assert_type(np.vstack([C, C]), npt.NDArray[Any])
    +
    +assert_type(np.hstack([A, A]), npt.NDArray[np.float64])
    +assert_type(np.hstack([A, A], dtype=np.float32), npt.NDArray[np.float32])
    +
    +assert_type(np.stack([A, A]), npt.NDArray[np.float64])
    +assert_type(np.stack([A, A], dtype=np.float32), npt.NDArray[np.float32])
    +assert_type(np.stack([A, C]), npt.NDArray[Any])
    +assert_type(np.stack([C, C]), npt.NDArray[Any])
    +assert_type(np.stack([A, A], axis=0), npt.NDArray[np.float64])
    +assert_type(np.stack([A, A], out=B), SubClass[np.float64])
    +
    +assert_type(np.block([[A, A], [A, A]]), npt.NDArray[Any])  # pyright correctly infers this as NDArray[float64]
    +assert_type(np.block(C), npt.NDArray[Any])
    +
    +if sys.version_info >= (3, 12):
    +    from collections.abc import Buffer
    +
    +    def create_array(obj: npt.ArrayLike) -> npt.NDArray[Any]: ...
    +
    +    buffer: Buffer
    +    assert_type(create_array(buffer), npt.NDArray[Any])
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/arraypad.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/arraypad.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..3d53d913a7705868fe36219cf75d787285c935e5
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/arraypad.pyi
    @@ -0,0 +1,27 @@
    +from collections.abc import Mapping
    +from typing import Any, SupportsIndex, assert_type
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +def mode_func(
    +    ar: npt.NDArray[np.number],
    +    width: tuple[int, int],
    +    iaxis: SupportsIndex,
    +    kwargs: Mapping[str, Any],
    +) -> None: ...
    +
    +AR_i8: npt.NDArray[np.int64]
    +AR_f8: npt.NDArray[np.float64]
    +AR_LIKE: list[int]
    +
    +assert_type(np.pad(AR_i8, (2, 3), "constant"), npt.NDArray[np.int64])
    +assert_type(np.pad(AR_LIKE, (2, 3), "constant"), npt.NDArray[Any])
    +
    +assert_type(np.pad(AR_f8, (2, 3), mode_func), npt.NDArray[np.float64])
    +assert_type(np.pad(AR_f8, (2, 3), mode_func, a=1, b=2), npt.NDArray[np.float64])
    +
    +assert_type(np.pad(AR_i8, {-1: (2, 3)}), npt.NDArray[np.int64])
    +assert_type(np.pad(AR_i8, {-2: 4}), npt.NDArray[np.int64])
    +pad_width: dict[int, int | tuple[int, int]] = {-1: (2, 3), -2: 4}
    +assert_type(np.pad(AR_i8, pad_width), npt.NDArray[np.int64])
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/arrayprint.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/arrayprint.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..17e175edc2b7796578b34eca3eaba9f1656f2e02
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/arrayprint.pyi
    @@ -0,0 +1,25 @@
    +import contextlib
    +from collections.abc import Callable
    +from typing import assert_type
    +
    +import numpy as np
    +import numpy.typing as npt
    +from numpy._core.arrayprint import _FormatOptions
    +
    +AR: npt.NDArray[np.int64]
    +func_float: Callable[[np.floating], str]
    +func_int: Callable[[np.integer], str]
    +
    +assert_type(np.get_printoptions(), _FormatOptions)
    +assert_type(
    +    np.array2string(AR, formatter={"float_kind": func_float, "int_kind": func_int}),
    +    str,
    +)
    +assert_type(np.format_float_scientific(1.0), str)
    +assert_type(np.format_float_positional(1), str)
    +assert_type(np.array_repr(AR), str)
    +assert_type(np.array_str(AR), str)
    +
    +assert_type(np.printoptions(), contextlib._GeneratorContextManager[_FormatOptions])
    +with np.printoptions() as dct:
    +    assert_type(dct, _FormatOptions)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/arraysetops.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/arraysetops.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..7e5ca5c5717baeb13a12af2cb8cfa42469c34fb7
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/arraysetops.pyi
    @@ -0,0 +1,74 @@
    +from typing import Any, assert_type
    +
    +import numpy as np
    +import numpy.typing as npt
    +from numpy.lib._arraysetops_impl import (
    +    UniqueAllResult,
    +    UniqueCountsResult,
    +    UniqueInverseResult,
    +)
    +
    +AR_b: npt.NDArray[np.bool]
    +AR_i8: npt.NDArray[np.int64]
    +AR_f8: npt.NDArray[np.float64]
    +AR_M: npt.NDArray[np.datetime64]
    +AR_O: npt.NDArray[np.object_]
    +
    +AR_LIKE_f8: list[float]
    +
    +assert_type(np.ediff1d(AR_b), npt.NDArray[np.int8])
    +assert_type(np.ediff1d(AR_i8, to_end=[1, 2, 3]), npt.NDArray[np.int64])
    +assert_type(np.ediff1d(AR_M), npt.NDArray[np.timedelta64])
    +assert_type(np.ediff1d(AR_O), npt.NDArray[np.object_])
    +assert_type(np.ediff1d(AR_LIKE_f8, to_begin=[1, 1.5]), npt.NDArray[Any])
    +
    +assert_type(np.intersect1d(AR_i8, AR_i8), npt.NDArray[np.int64])
    +assert_type(np.intersect1d(AR_M, AR_M, assume_unique=True), npt.NDArray[np.datetime64])
    +assert_type(np.intersect1d(AR_f8, AR_i8), npt.NDArray[Any])
    +assert_type(
    +    np.intersect1d(AR_f8, AR_f8, return_indices=True),
    +    tuple[npt.NDArray[np.float64], npt.NDArray[np.intp], npt.NDArray[np.intp]],
    +)
    +
    +assert_type(np.setxor1d(AR_i8, AR_i8), npt.NDArray[np.int64])
    +assert_type(np.setxor1d(AR_M, AR_M, assume_unique=True), npt.NDArray[np.datetime64])
    +assert_type(np.setxor1d(AR_f8, AR_i8), npt.NDArray[Any])
    +
    +assert_type(np.isin(AR_i8, AR_i8), npt.NDArray[np.bool])
    +assert_type(np.isin(AR_M, AR_M, assume_unique=True), npt.NDArray[np.bool])
    +assert_type(np.isin(AR_f8, AR_i8), npt.NDArray[np.bool])
    +assert_type(np.isin(AR_f8, AR_LIKE_f8, invert=True), npt.NDArray[np.bool])
    +
    +assert_type(np.union1d(AR_i8, AR_i8), npt.NDArray[np.int64])
    +assert_type(np.union1d(AR_M, AR_M), npt.NDArray[np.datetime64])
    +assert_type(np.union1d(AR_f8, AR_i8), npt.NDArray[Any])
    +
    +assert_type(np.setdiff1d(AR_i8, AR_i8), npt.NDArray[np.int64])
    +assert_type(np.setdiff1d(AR_M, AR_M, assume_unique=True), npt.NDArray[np.datetime64])
    +assert_type(np.setdiff1d(AR_f8, AR_i8), npt.NDArray[Any])
    +
    +assert_type(np.unique(AR_f8), npt.NDArray[np.float64])
    +assert_type(np.unique(AR_LIKE_f8, axis=0), npt.NDArray[Any])
    +assert_type(np.unique(AR_f8, return_index=True), tuple[npt.NDArray[np.float64], npt.NDArray[np.intp]])
    +assert_type(np.unique(AR_LIKE_f8, return_index=True), tuple[npt.NDArray[Any], npt.NDArray[np.intp]])
    +assert_type(np.unique(AR_f8, return_inverse=True), tuple[npt.NDArray[np.float64], npt.NDArray[np.intp]])
    +assert_type(np.unique(AR_LIKE_f8, return_inverse=True), tuple[npt.NDArray[Any], npt.NDArray[np.intp]])
    +assert_type(np.unique(AR_f8, return_counts=True), tuple[npt.NDArray[np.float64], npt.NDArray[np.intp]])
    +assert_type(np.unique(AR_LIKE_f8, return_counts=True), tuple[npt.NDArray[Any], npt.NDArray[np.intp]])
    +assert_type(np.unique(AR_f8, return_index=True, return_inverse=True), tuple[npt.NDArray[np.float64], npt.NDArray[np.intp], npt.NDArray[np.intp]])
    +assert_type(np.unique(AR_LIKE_f8, return_index=True, return_inverse=True), tuple[npt.NDArray[Any], npt.NDArray[np.intp], npt.NDArray[np.intp]])
    +assert_type(np.unique(AR_f8, return_index=True, return_counts=True), tuple[npt.NDArray[np.float64], npt.NDArray[np.intp], npt.NDArray[np.intp]])
    +assert_type(np.unique(AR_LIKE_f8, return_index=True, return_counts=True), tuple[npt.NDArray[Any], npt.NDArray[np.intp], npt.NDArray[np.intp]])
    +assert_type(np.unique(AR_f8, return_inverse=True, return_counts=True), tuple[npt.NDArray[np.float64], npt.NDArray[np.intp], npt.NDArray[np.intp]])
    +assert_type(np.unique(AR_LIKE_f8, return_inverse=True, return_counts=True), tuple[npt.NDArray[Any], npt.NDArray[np.intp], npt.NDArray[np.intp]])
    +assert_type(np.unique(AR_f8, return_index=True, return_inverse=True, return_counts=True), tuple[npt.NDArray[np.float64], npt.NDArray[np.intp], npt.NDArray[np.intp], npt.NDArray[np.intp]])
    +assert_type(np.unique(AR_LIKE_f8, return_index=True, return_inverse=True, return_counts=True), tuple[npt.NDArray[Any], npt.NDArray[np.intp], npt.NDArray[np.intp], npt.NDArray[np.intp]])
    +
    +assert_type(np.unique_all(AR_f8), UniqueAllResult[np.float64])
    +assert_type(np.unique_all(AR_LIKE_f8), UniqueAllResult[Any])
    +assert_type(np.unique_counts(AR_f8), UniqueCountsResult[np.float64])
    +assert_type(np.unique_counts(AR_LIKE_f8), UniqueCountsResult[Any])
    +assert_type(np.unique_inverse(AR_f8), UniqueInverseResult[np.float64])
    +assert_type(np.unique_inverse(AR_LIKE_f8), UniqueInverseResult[Any])
    +assert_type(np.unique_values(AR_f8), npt.NDArray[np.float64])
    +assert_type(np.unique_values(AR_LIKE_f8), npt.NDArray[Any])
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/arrayterator.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/arrayterator.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..85eeff4add0873053ae76f9b0c24bc7c8cbf58c0
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/arrayterator.pyi
    @@ -0,0 +1,27 @@
    +from collections.abc import Generator
    +from typing import Any, assert_type
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +AR_i8: npt.NDArray[np.int64]
    +ar_iter = np.lib.Arrayterator(AR_i8)
    +
    +assert_type(ar_iter.var, npt.NDArray[np.int64])
    +assert_type(ar_iter.buf_size, int | None)
    +assert_type(ar_iter.start, list[int])
    +assert_type(ar_iter.stop, list[int])
    +assert_type(ar_iter.step, list[int])
    +assert_type(ar_iter.shape, tuple[Any, ...])
    +assert_type(ar_iter.flat, Generator[np.int64])
    +
    +assert_type(ar_iter.__array__(), npt.NDArray[np.int64])
    +
    +for i in ar_iter:
    +    assert_type(i, npt.NDArray[np.int64])
    +
    +assert_type(ar_iter[0], np.lib.Arrayterator[tuple[Any, ...], np.dtype[np.int64]])
    +assert_type(ar_iter[...], np.lib.Arrayterator[tuple[Any, ...], np.dtype[np.int64]])
    +assert_type(ar_iter[:], np.lib.Arrayterator[tuple[Any, ...], np.dtype[np.int64]])
    +assert_type(ar_iter[0, 0, 0], np.lib.Arrayterator[tuple[Any, ...], np.dtype[np.int64]])
    +assert_type(ar_iter[..., 0, :], np.lib.Arrayterator[tuple[Any, ...], np.dtype[np.int64]])
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/bitwise_ops.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/bitwise_ops.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..49986bd5d12c590e92d134efa66307dbcdf9c56a
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/bitwise_ops.pyi
    @@ -0,0 +1,166 @@
    +from typing import Literal as L, TypeAlias, assert_type
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +FalseType: TypeAlias = L[False]
    +TrueType: TypeAlias = L[True]
    +
    +i4: np.int32
    +i8: np.int64
    +
    +u4: np.uint32
    +u8: np.uint64
    +
    +b_: np.bool[bool]
    +b0_: np.bool[FalseType]
    +b1_: np.bool[TrueType]
    +
    +b: bool
    +b0: FalseType
    +b1: TrueType
    +
    +i: int
    +
    +AR: npt.NDArray[np.int32]
    +
    +assert_type(i8 << i8, np.int64)
    +assert_type(i8 >> i8, np.int64)
    +assert_type(i8 | i8, np.int64)
    +assert_type(i8 ^ i8, np.int64)
    +assert_type(i8 & i8, np.int64)
    +
    +assert_type(i8 << AR, npt.NDArray[np.signedinteger])
    +assert_type(i8 >> AR, npt.NDArray[np.signedinteger])
    +assert_type(i8 | AR, npt.NDArray[np.signedinteger])
    +assert_type(i8 ^ AR, npt.NDArray[np.signedinteger])
    +assert_type(i8 & AR, npt.NDArray[np.signedinteger])
    +
    +assert_type(i4 << i4, np.int32)
    +assert_type(i4 >> i4, np.int32)
    +assert_type(i4 | i4, np.int32)
    +assert_type(i4 ^ i4, np.int32)
    +assert_type(i4 & i4, np.int32)
    +
    +assert_type(i8 << i4, np.signedinteger)
    +assert_type(i8 >> i4, np.signedinteger)
    +assert_type(i8 | i4, np.signedinteger)
    +assert_type(i8 ^ i4, np.signedinteger)
    +assert_type(i8 & i4, np.signedinteger)
    +
    +assert_type(i8 << b_, np.int64)
    +assert_type(i8 >> b_, np.int64)
    +assert_type(i8 | b_, np.int64)
    +assert_type(i8 ^ b_, np.int64)
    +assert_type(i8 & b_, np.int64)
    +
    +assert_type(i8 << b, np.int64)
    +assert_type(i8 >> b, np.int64)
    +assert_type(i8 | b, np.int64)
    +assert_type(i8 ^ b, np.int64)
    +assert_type(i8 & b, np.int64)
    +
    +assert_type(u8 << u8, np.uint64)
    +assert_type(u8 >> u8, np.uint64)
    +assert_type(u8 | u8, np.uint64)
    +assert_type(u8 ^ u8, np.uint64)
    +assert_type(u8 & u8, np.uint64)
    +
    +assert_type(u8 << AR, npt.NDArray[np.signedinteger])
    +assert_type(u8 >> AR, npt.NDArray[np.signedinteger])
    +assert_type(u8 | AR, npt.NDArray[np.signedinteger])
    +assert_type(u8 ^ AR, npt.NDArray[np.signedinteger])
    +assert_type(u8 & AR, npt.NDArray[np.signedinteger])
    +
    +assert_type(u4 << u4, np.uint32)
    +assert_type(u4 >> u4, np.uint32)
    +assert_type(u4 | u4, np.uint32)
    +assert_type(u4 ^ u4, np.uint32)
    +assert_type(u4 & u4, np.uint32)
    +
    +assert_type(u4 << i4, np.signedinteger)
    +assert_type(u4 >> i4, np.signedinteger)
    +assert_type(u4 | i4, np.signedinteger)
    +assert_type(u4 ^ i4, np.signedinteger)
    +assert_type(u4 & i4, np.signedinteger)
    +
    +assert_type(u4 << i, np.uint32)
    +assert_type(u4 >> i, np.uint32)
    +assert_type(u4 | i, np.uint32)
    +assert_type(u4 ^ i, np.uint32)
    +assert_type(u4 & i, np.uint32)
    +
    +assert_type(u8 << b_, np.uint64)
    +assert_type(u8 >> b_, np.uint64)
    +assert_type(u8 | b_, np.uint64)
    +assert_type(u8 ^ b_, np.uint64)
    +assert_type(u8 & b_, np.uint64)
    +
    +assert_type(u8 << b, np.uint64)
    +assert_type(u8 >> b, np.uint64)
    +assert_type(u8 | b, np.uint64)
    +assert_type(u8 ^ b, np.uint64)
    +assert_type(u8 & b, np.uint64)
    +
    +assert_type(b_ << b_, np.int8)
    +assert_type(b_ >> b_, np.int8)
    +assert_type(b_ | b_, np.bool)
    +assert_type(b_ ^ b_, np.bool)
    +assert_type(b_ & b_, np.bool)
    +
    +assert_type(b_ << AR, npt.NDArray[np.signedinteger])
    +assert_type(b_ >> AR, npt.NDArray[np.signedinteger])
    +assert_type(b_ | AR, npt.NDArray[np.signedinteger])
    +assert_type(b_ ^ AR, npt.NDArray[np.signedinteger])
    +assert_type(b_ & AR, npt.NDArray[np.signedinteger])
    +
    +assert_type(b_ << b, np.int8)
    +assert_type(b_ >> b, np.int8)
    +assert_type(b_ | b, np.bool)
    +assert_type(b_ ^ b, np.bool)
    +assert_type(b_ & b, np.bool)
    +
    +assert_type(b_ << i, np.int_)
    +assert_type(b_ >> i, np.int_)
    +assert_type(b_ | i, np.bool | np.int_)
    +assert_type(b_ ^ i, np.bool | np.int_)
    +assert_type(b_ & i, np.bool | np.int_)
    +
    +assert_type(~i8, np.int64)
    +assert_type(~i4, np.int32)
    +assert_type(~u8, np.uint64)
    +assert_type(~u4, np.uint32)
    +assert_type(~b_, np.bool)
    +assert_type(~b0_, np.bool[TrueType])
    +assert_type(~b1_, np.bool[FalseType])
    +assert_type(~AR, npt.NDArray[np.int32])
    +
    +assert_type(b_ | b0_, np.bool)
    +assert_type(b0_ | b_, np.bool)
    +assert_type(b_ | b1_, np.bool[TrueType])
    +assert_type(b1_ | b_, np.bool[TrueType])
    +
    +assert_type(b_ ^ b0_, np.bool)
    +assert_type(b0_ ^ b_, np.bool)
    +assert_type(b_ ^ b1_, np.bool)
    +assert_type(b1_ ^ b_, np.bool)
    +
    +assert_type(b_ & b0_, np.bool[FalseType])
    +assert_type(b0_ & b_, np.bool[FalseType])
    +assert_type(b_ & b1_, np.bool)
    +assert_type(b1_ & b_, np.bool)
    +
    +assert_type(b0_ | b0_, np.bool[FalseType])
    +assert_type(b0_ | b1_, np.bool[TrueType])
    +assert_type(b1_ | b0_, np.bool[TrueType])
    +assert_type(b1_ | b1_, np.bool[TrueType])
    +
    +assert_type(b0_ ^ b0_, np.bool[FalseType])
    +assert_type(b0_ ^ b1_, np.bool[TrueType])
    +assert_type(b1_ ^ b0_, np.bool[TrueType])
    +assert_type(b1_ ^ b1_, np.bool[FalseType])
    +
    +assert_type(b0_ & b0_, np.bool[FalseType])
    +assert_type(b0_ & b1_, np.bool[FalseType])
    +assert_type(b1_ & b0_, np.bool[FalseType])
    +assert_type(b1_ & b1_, np.bool[TrueType])
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/char.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/char.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..2fba2feae3851c06bd5ea57f7547159e18fc1a86
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/char.pyi
    @@ -0,0 +1,225 @@
    +from typing import TypeAlias, assert_type
    +
    +import numpy as np
    +import numpy._typing as np_t
    +import numpy.typing as npt
    +
    +AR_T_alias: TypeAlias = np.ndarray[np_t._AnyShape, np.dtypes.StringDType]
    +AR_TU_alias: TypeAlias = AR_T_alias | npt.NDArray[np.str_]
    +
    +AR_U: npt.NDArray[np.str_]
    +AR_S: npt.NDArray[np.bytes_]
    +AR_T: AR_T_alias
    +
    +assert_type(np.char.equal(AR_U, AR_U), npt.NDArray[np.bool])
    +assert_type(np.char.equal(AR_S, AR_S), npt.NDArray[np.bool])
    +assert_type(np.char.equal(AR_T, AR_T), npt.NDArray[np.bool])
    +
    +assert_type(np.char.not_equal(AR_U, AR_U), npt.NDArray[np.bool])
    +assert_type(np.char.not_equal(AR_S, AR_S), npt.NDArray[np.bool])
    +assert_type(np.char.not_equal(AR_T, AR_T), npt.NDArray[np.bool])
    +
    +assert_type(np.char.greater_equal(AR_U, AR_U), npt.NDArray[np.bool])
    +assert_type(np.char.greater_equal(AR_S, AR_S), npt.NDArray[np.bool])
    +assert_type(np.char.greater_equal(AR_T, AR_T), npt.NDArray[np.bool])
    +
    +assert_type(np.char.less_equal(AR_U, AR_U), npt.NDArray[np.bool])
    +assert_type(np.char.less_equal(AR_S, AR_S), npt.NDArray[np.bool])
    +assert_type(np.char.less_equal(AR_T, AR_T), npt.NDArray[np.bool])
    +
    +assert_type(np.char.greater(AR_U, AR_U), npt.NDArray[np.bool])
    +assert_type(np.char.greater(AR_S, AR_S), npt.NDArray[np.bool])
    +assert_type(np.char.greater(AR_T, AR_T), npt.NDArray[np.bool])
    +
    +assert_type(np.char.less(AR_U, AR_U), npt.NDArray[np.bool])
    +assert_type(np.char.less(AR_S, AR_S), npt.NDArray[np.bool])
    +assert_type(np.char.less(AR_T, AR_T), npt.NDArray[np.bool])
    +
    +assert_type(np.char.multiply(AR_U, 5), npt.NDArray[np.str_])
    +assert_type(np.char.multiply(AR_S, [5, 4, 3]), npt.NDArray[np.bytes_])
    +assert_type(np.char.multiply(AR_T, 5), AR_T_alias)
    +
    +assert_type(np.char.mod(AR_U, "test"), npt.NDArray[np.str_])
    +assert_type(np.char.mod(AR_S, "test"), npt.NDArray[np.bytes_])
    +assert_type(np.char.mod(AR_T, "test"), AR_T_alias)
    +
    +assert_type(np.char.capitalize(AR_U), npt.NDArray[np.str_])
    +assert_type(np.char.capitalize(AR_S), npt.NDArray[np.bytes_])
    +assert_type(np.char.capitalize(AR_T), AR_T_alias)
    +
    +assert_type(np.char.center(AR_U, 5), npt.NDArray[np.str_])
    +assert_type(np.char.center(AR_S, [2, 3, 4], b"a"), npt.NDArray[np.bytes_])
    +assert_type(np.char.center(AR_T, 5), AR_T_alias)
    +
    +assert_type(np.char.encode(AR_U), npt.NDArray[np.bytes_])
    +assert_type(np.char.encode(AR_T), npt.NDArray[np.bytes_])
    +assert_type(np.char.decode(AR_S), npt.NDArray[np.str_])
    +
    +assert_type(np.char.expandtabs(AR_U), npt.NDArray[np.str_])
    +assert_type(np.char.expandtabs(AR_S, tabsize=4), npt.NDArray[np.bytes_])
    +assert_type(np.char.expandtabs(AR_T), AR_T_alias)
    +
    +assert_type(np.char.join(AR_U, "_"), npt.NDArray[np.str_])
    +assert_type(np.char.join(AR_S, [b"_", b""]), npt.NDArray[np.bytes_])
    +assert_type(np.char.join(AR_T, "_"), AR_TU_alias)
    +
    +assert_type(np.char.ljust(AR_U, 5), npt.NDArray[np.str_])
    +assert_type(np.char.ljust(AR_S, [4, 3, 1], fillchar=[b"a", b"b", b"c"]), npt.NDArray[np.bytes_])
    +assert_type(np.char.ljust(AR_S, [4, 3, 1], fillchar="a"), npt.NDArray[np.bytes_])
    +assert_type(np.char.ljust(AR_T, 5), AR_T_alias)
    +assert_type(np.char.ljust(AR_T, [4, 2, 1], fillchar=["a", "b", "c"]), AR_TU_alias)
    +
    +assert_type(np.char.rjust(AR_U, 5), npt.NDArray[np.str_])
    +assert_type(np.char.rjust(AR_S, [4, 3, 1], fillchar=[b"a", b"b", b"c"]), npt.NDArray[np.bytes_])
    +assert_type(np.char.rjust(AR_T, 5), AR_T_alias)
    +assert_type(np.char.rjust(AR_T, [4, 2, 1], fillchar=["a", "b", "c"]), AR_TU_alias)
    +
    +assert_type(np.char.lstrip(AR_U), npt.NDArray[np.str_])
    +assert_type(np.char.lstrip(AR_S, b"_"), npt.NDArray[np.bytes_])
    +assert_type(np.char.lstrip(AR_T), AR_T_alias)
    +assert_type(np.char.lstrip(AR_T, "_"), AR_TU_alias)
    +
    +assert_type(np.char.rstrip(AR_U), npt.NDArray[np.str_])
    +assert_type(np.char.rstrip(AR_S, b"_"), npt.NDArray[np.bytes_])
    +assert_type(np.char.rstrip(AR_T), AR_T_alias)
    +assert_type(np.char.rstrip(AR_T, "_"), AR_TU_alias)
    +
    +assert_type(np.char.strip(AR_U), npt.NDArray[np.str_])
    +assert_type(np.char.strip(AR_S, b"_"), npt.NDArray[np.bytes_])
    +assert_type(np.char.strip(AR_T), AR_T_alias)
    +assert_type(np.char.strip(AR_T, "_"), AR_TU_alias)
    +
    +assert_type(np.char.count(AR_U, "a", start=[1, 2, 3]), npt.NDArray[np.int_])
    +assert_type(np.char.count(AR_S, [b"a", b"b", b"c"], end=9), npt.NDArray[np.int_])
    +assert_type(np.char.count(AR_T, AR_T, start=[1, 2, 3]), npt.NDArray[np.int_])
    +assert_type(np.char.count(AR_T, ["a", "b", "c"], end=9), npt.NDArray[np.int_])
    +
    +assert_type(np.char.partition(AR_U, "\n"), npt.NDArray[np.str_])
    +assert_type(np.char.partition(AR_S, [b"a", b"b", b"c"]), npt.NDArray[np.bytes_])
    +assert_type(np.char.partition(AR_T, "\n"), AR_TU_alias)
    +
    +assert_type(np.char.rpartition(AR_U, "\n"), npt.NDArray[np.str_])
    +assert_type(np.char.rpartition(AR_S, [b"a", b"b", b"c"]), npt.NDArray[np.bytes_])
    +assert_type(np.char.rpartition(AR_T, "\n"), AR_TU_alias)
    +
    +assert_type(np.char.replace(AR_U, "_", "-"), npt.NDArray[np.str_])
    +assert_type(np.char.replace(AR_S, [b"_", b""], [b"a", b"b"]), npt.NDArray[np.bytes_])
    +assert_type(np.char.replace(AR_T, "_", "_"), AR_TU_alias)
    +
    +assert_type(np.char.split(AR_U, "_"), npt.NDArray[np.object_])
    +assert_type(np.char.split(AR_S, maxsplit=[1, 2, 3]), npt.NDArray[np.object_])
    +assert_type(np.char.split(AR_T, "_"), npt.NDArray[np.object_])
    +
    +assert_type(np.char.rsplit(AR_U, "_"), npt.NDArray[np.object_])
    +assert_type(np.char.rsplit(AR_S, maxsplit=[1, 2, 3]), npt.NDArray[np.object_])
    +assert_type(np.char.rsplit(AR_T, "_"), npt.NDArray[np.object_])
    +
    +assert_type(np.char.splitlines(AR_U), npt.NDArray[np.object_])
    +assert_type(np.char.splitlines(AR_S, keepends=[True, True, False]), npt.NDArray[np.object_])
    +assert_type(np.char.splitlines(AR_T), npt.NDArray[np.object_])
    +
    +assert_type(np.char.lower(AR_U), npt.NDArray[np.str_])
    +assert_type(np.char.lower(AR_S), npt.NDArray[np.bytes_])
    +assert_type(np.char.lower(AR_T), AR_T_alias)
    +
    +assert_type(np.char.upper(AR_U), npt.NDArray[np.str_])
    +assert_type(np.char.upper(AR_S), npt.NDArray[np.bytes_])
    +assert_type(np.char.upper(AR_T), AR_T_alias)
    +
    +assert_type(np.char.swapcase(AR_U), npt.NDArray[np.str_])
    +assert_type(np.char.swapcase(AR_S), npt.NDArray[np.bytes_])
    +assert_type(np.char.swapcase(AR_T), AR_T_alias)
    +
    +assert_type(np.char.title(AR_U), npt.NDArray[np.str_])
    +assert_type(np.char.title(AR_S), npt.NDArray[np.bytes_])
    +assert_type(np.char.title(AR_T), AR_T_alias)
    +
    +assert_type(np.char.zfill(AR_U, 5), npt.NDArray[np.str_])
    +assert_type(np.char.zfill(AR_S, [2, 3, 4]), npt.NDArray[np.bytes_])
    +assert_type(np.char.zfill(AR_T, 5), AR_T_alias)
    +
    +assert_type(np.char.endswith(AR_U, "a", start=[1, 2, 3]), npt.NDArray[np.bool])
    +assert_type(np.char.endswith(AR_S, [b"a", b"b", b"c"], end=9), npt.NDArray[np.bool])
    +assert_type(np.char.endswith(AR_T, "a", start=[1, 2, 3]), npt.NDArray[np.bool])
    +
    +assert_type(np.char.startswith(AR_U, "a", start=[1, 2, 3]), npt.NDArray[np.bool])
    +assert_type(np.char.startswith(AR_S, [b"a", b"b", b"c"], end=9), npt.NDArray[np.bool])
    +assert_type(np.char.startswith(AR_T, "a", start=[1, 2, 3]), npt.NDArray[np.bool])
    +
    +assert_type(np.char.find(AR_U, "a", start=[1, 2, 3]), npt.NDArray[np.int_])
    +assert_type(np.char.find(AR_S, [b"a", b"b", b"c"], end=9), npt.NDArray[np.int_])
    +assert_type(np.char.find(AR_T, "a", start=[1, 2, 3]), npt.NDArray[np.int_])
    +
    +assert_type(np.char.rfind(AR_U, "a", start=[1, 2, 3]), npt.NDArray[np.int_])
    +assert_type(np.char.rfind(AR_S, [b"a", b"b", b"c"], end=9), npt.NDArray[np.int_])
    +assert_type(np.char.rfind(AR_T, "a", start=[1, 2, 3]), npt.NDArray[np.int_])
    +
    +assert_type(np.char.index(AR_U, "a", start=[1, 2, 3]), npt.NDArray[np.int_])
    +assert_type(np.char.index(AR_S, [b"a", b"b", b"c"], end=9), npt.NDArray[np.int_])
    +assert_type(np.char.index(AR_T, "a", start=[1, 2, 3]), npt.NDArray[np.int_])
    +
    +assert_type(np.char.rindex(AR_U, "a", start=[1, 2, 3]), npt.NDArray[np.int_])
    +assert_type(np.char.rindex(AR_S, [b"a", b"b", b"c"], end=9), npt.NDArray[np.int_])
    +assert_type(np.char.rindex(AR_T, "a", start=[1, 2, 3]), npt.NDArray[np.int_])
    +
    +assert_type(np.char.isalpha(AR_U), npt.NDArray[np.bool])
    +assert_type(np.char.isalpha(AR_S), npt.NDArray[np.bool])
    +assert_type(np.char.isalpha(AR_T), npt.NDArray[np.bool])
    +
    +assert_type(np.char.isalnum(AR_U), npt.NDArray[np.bool])
    +assert_type(np.char.isalnum(AR_S), npt.NDArray[np.bool])
    +assert_type(np.char.isalnum(AR_T), npt.NDArray[np.bool])
    +
    +assert_type(np.char.isdecimal(AR_U), npt.NDArray[np.bool])
    +assert_type(np.char.isdecimal(AR_T), npt.NDArray[np.bool])
    +
    +assert_type(np.char.isdigit(AR_U), npt.NDArray[np.bool])
    +assert_type(np.char.isdigit(AR_S), npt.NDArray[np.bool])
    +assert_type(np.char.isdigit(AR_T), npt.NDArray[np.bool])
    +
    +assert_type(np.char.islower(AR_U), npt.NDArray[np.bool])
    +assert_type(np.char.islower(AR_S), npt.NDArray[np.bool])
    +assert_type(np.char.islower(AR_T), npt.NDArray[np.bool])
    +
    +assert_type(np.char.isnumeric(AR_U), npt.NDArray[np.bool])
    +assert_type(np.char.isnumeric(AR_T), npt.NDArray[np.bool])
    +
    +assert_type(np.char.isspace(AR_U), npt.NDArray[np.bool])
    +assert_type(np.char.isspace(AR_S), npt.NDArray[np.bool])
    +assert_type(np.char.isspace(AR_T), npt.NDArray[np.bool])
    +
    +assert_type(np.char.istitle(AR_U), npt.NDArray[np.bool])
    +assert_type(np.char.istitle(AR_S), npt.NDArray[np.bool])
    +assert_type(np.char.istitle(AR_T), npt.NDArray[np.bool])
    +
    +assert_type(np.char.isupper(AR_U), npt.NDArray[np.bool])
    +assert_type(np.char.isupper(AR_S), npt.NDArray[np.bool])
    +assert_type(np.char.isupper(AR_T), npt.NDArray[np.bool])
    +
    +assert_type(np.char.str_len(AR_U), npt.NDArray[np.int_])
    +assert_type(np.char.str_len(AR_S), npt.NDArray[np.int_])
    +assert_type(np.char.str_len(AR_T), npt.NDArray[np.int_])
    +
    +assert_type(np.char.translate(AR_U, ""), npt.NDArray[np.str_])
    +assert_type(np.char.translate(AR_S, ""), npt.NDArray[np.bytes_])
    +assert_type(np.char.translate(AR_T, ""), AR_T_alias)
    +
    +assert_type(np.char.array(AR_U), np.char.chararray[np_t._AnyShape, np.dtype[np.str_]])
    +assert_type(np.char.array(AR_S, order="K"), np.char.chararray[np_t._AnyShape, np.dtype[np.bytes_]])
    +assert_type(np.char.array("bob", copy=True), np.char.chararray[np_t._AnyShape, np.dtype[np.str_]])
    +assert_type(np.char.array(b"bob", itemsize=5), np.char.chararray[np_t._AnyShape, np.dtype[np.bytes_]])
    +assert_type(np.char.array(1, unicode=False), np.char.chararray[np_t._AnyShape, np.dtype[np.bytes_]])
    +assert_type(np.char.array(1, unicode=True), np.char.chararray[np_t._AnyShape, np.dtype[np.str_]])
    +assert_type(np.char.array(1), np.char.chararray[np_t._AnyShape, np.dtype[np.str_]] | np.char.chararray[np_t._AnyShape, np.dtype[np.bytes_]])
    +assert_type(np.char.array(AR_U, unicode=False), np.char.chararray[np_t._AnyShape, np.dtype[np.bytes_]])
    +assert_type(np.char.array(AR_S, unicode=True), np.char.chararray[np_t._AnyShape, np.dtype[np.str_]])
    +
    +assert_type(np.char.asarray(AR_U), np.char.chararray[np_t._AnyShape, np.dtype[np.str_]])
    +assert_type(np.char.asarray(AR_S, order="K"), np.char.chararray[np_t._AnyShape, np.dtype[np.bytes_]])
    +assert_type(np.char.asarray("bob"), np.char.chararray[np_t._AnyShape, np.dtype[np.str_]])
    +assert_type(np.char.asarray(b"bob", itemsize=5), np.char.chararray[np_t._AnyShape, np.dtype[np.bytes_]])
    +assert_type(np.char.asarray(1, unicode=False), np.char.chararray[np_t._AnyShape, np.dtype[np.bytes_]])
    +assert_type(np.char.asarray(1, unicode=True), np.char.chararray[np_t._AnyShape, np.dtype[np.str_]])
    +assert_type(np.char.asarray(1), np.char.chararray[np_t._AnyShape, np.dtype[np.str_]] | np.char.chararray[np_t._AnyShape, np.dtype[np.bytes_]])
    +assert_type(np.char.asarray(AR_U, unicode=False), np.char.chararray[np_t._AnyShape, np.dtype[np.bytes_]])
    +assert_type(np.char.asarray(AR_S, unicode=True), np.char.chararray[np_t._AnyShape, np.dtype[np.str_]])
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/chararray.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/chararray.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..5c3dc85038db9d2b190db3e4c423d698eeece3ed
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/chararray.pyi
    @@ -0,0 +1,138 @@
    +from typing import Any, TypeAlias, assert_type
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +_BytesCharArray: TypeAlias = np.char.chararray[tuple[Any, ...], np.dtype[np.bytes_]]
    +_StrCharArray: TypeAlias = np.char.chararray[tuple[Any, ...], np.dtype[np.str_]]
    +
    +AR_U: _StrCharArray
    +AR_S: _BytesCharArray
    +
    +assert_type(AR_U == AR_U, npt.NDArray[np.bool])
    +assert_type(AR_S == AR_S, npt.NDArray[np.bool])
    +
    +assert_type(AR_U != AR_U, npt.NDArray[np.bool])
    +assert_type(AR_S != AR_S, npt.NDArray[np.bool])
    +
    +assert_type(AR_U >= AR_U, npt.NDArray[np.bool])
    +assert_type(AR_S >= AR_S, npt.NDArray[np.bool])
    +
    +assert_type(AR_U <= AR_U, npt.NDArray[np.bool])
    +assert_type(AR_S <= AR_S, npt.NDArray[np.bool])
    +
    +assert_type(AR_U > AR_U, npt.NDArray[np.bool])
    +assert_type(AR_S > AR_S, npt.NDArray[np.bool])
    +
    +assert_type(AR_U < AR_U, npt.NDArray[np.bool])
    +assert_type(AR_S < AR_S, npt.NDArray[np.bool])
    +
    +assert_type(AR_U * 5, _StrCharArray)
    +assert_type(AR_S * [5], _BytesCharArray)
    +
    +assert_type(AR_U % "test", _StrCharArray)
    +assert_type(AR_S % b"test", _BytesCharArray)
    +
    +assert_type(AR_U.capitalize(), _StrCharArray)
    +assert_type(AR_S.capitalize(), _BytesCharArray)
    +
    +assert_type(AR_U.center(5), _StrCharArray)
    +assert_type(AR_S.center([2, 3, 4], b"a"), _BytesCharArray)
    +
    +assert_type(AR_U.encode(), _BytesCharArray)
    +assert_type(AR_S.decode(), _StrCharArray)
    +
    +assert_type(AR_U.expandtabs(), _StrCharArray)
    +assert_type(AR_S.expandtabs(tabsize=4), _BytesCharArray)
    +
    +assert_type(AR_U.join("_"), _StrCharArray)
    +assert_type(AR_S.join([b"_", b""]), _BytesCharArray)
    +
    +assert_type(AR_U.ljust(5), _StrCharArray)
    +assert_type(AR_S.ljust([4, 3, 1], fillchar=[b"a", b"b", b"c"]), _BytesCharArray)
    +assert_type(AR_S.ljust([4, 3, 1], fillchar="a"), _BytesCharArray)
    +assert_type(AR_U.rjust(5), _StrCharArray)
    +assert_type(AR_S.rjust([4, 3, 1], fillchar=[b"a", b"b", b"c"]), _BytesCharArray)
    +
    +assert_type(AR_U.lstrip(), _StrCharArray)
    +assert_type(AR_S.lstrip(chars=b"_"), _BytesCharArray)
    +assert_type(AR_U.rstrip(), _StrCharArray)
    +assert_type(AR_S.rstrip(chars=b"_"), _BytesCharArray)
    +assert_type(AR_U.strip(), _StrCharArray)
    +assert_type(AR_S.strip(chars=b"_"), _BytesCharArray)
    +
    +assert_type(AR_U.partition("\n"), _StrCharArray)
    +assert_type(AR_S.partition([b"a", b"b", b"c"]), _BytesCharArray)
    +assert_type(AR_U.rpartition("\n"), _StrCharArray)
    +assert_type(AR_S.rpartition([b"a", b"b", b"c"]), _BytesCharArray)
    +
    +assert_type(AR_U.replace("_", "-"), _StrCharArray)
    +assert_type(AR_S.replace([b"_", b""], [b"a", b"b"]), _BytesCharArray)
    +
    +assert_type(AR_U.split("_"), npt.NDArray[np.object_])
    +assert_type(AR_S.split(maxsplit=[1, 2, 3]), npt.NDArray[np.object_])
    +assert_type(AR_U.rsplit("_"), npt.NDArray[np.object_])
    +assert_type(AR_S.rsplit(maxsplit=[1, 2, 3]), npt.NDArray[np.object_])
    +
    +assert_type(AR_U.splitlines(), npt.NDArray[np.object_])
    +assert_type(AR_S.splitlines(keepends=[True, True, False]), npt.NDArray[np.object_])
    +
    +assert_type(AR_U.swapcase(), _StrCharArray)
    +assert_type(AR_S.swapcase(), _BytesCharArray)
    +
    +assert_type(AR_U.title(), _StrCharArray)
    +assert_type(AR_S.title(), _BytesCharArray)
    +
    +assert_type(AR_U.upper(), _StrCharArray)
    +assert_type(AR_S.upper(), _BytesCharArray)
    +
    +assert_type(AR_U.zfill(5), _StrCharArray)
    +assert_type(AR_S.zfill([2, 3, 4]), _BytesCharArray)
    +
    +assert_type(AR_U.count("a", start=[1, 2, 3]), npt.NDArray[np.int_])
    +assert_type(AR_S.count([b"a", b"b", b"c"], end=9), npt.NDArray[np.int_])
    +
    +assert_type(AR_U.endswith("a", start=[1, 2, 3]), npt.NDArray[np.bool])
    +assert_type(AR_S.endswith([b"a", b"b", b"c"], end=9), npt.NDArray[np.bool])
    +assert_type(AR_U.startswith("a", start=[1, 2, 3]), npt.NDArray[np.bool])
    +assert_type(AR_S.startswith([b"a", b"b", b"c"], end=9), npt.NDArray[np.bool])
    +
    +assert_type(AR_U.find("a", start=[1, 2, 3]), npt.NDArray[np.int_])
    +assert_type(AR_S.find([b"a", b"b", b"c"], end=9), npt.NDArray[np.int_])
    +assert_type(AR_U.rfind("a", start=[1, 2, 3]), npt.NDArray[np.int_])
    +assert_type(AR_S.rfind([b"a", b"b", b"c"], end=9), npt.NDArray[np.int_])
    +
    +assert_type(AR_U.index("a", start=[1, 2, 3]), npt.NDArray[np.int_])
    +assert_type(AR_S.index([b"a", b"b", b"c"], end=9), npt.NDArray[np.int_])
    +assert_type(AR_U.rindex("a", start=[1, 2, 3]), npt.NDArray[np.int_])
    +assert_type(AR_S.rindex([b"a", b"b", b"c"], end=9), npt.NDArray[np.int_])
    +
    +assert_type(AR_U.isalpha(), npt.NDArray[np.bool])
    +assert_type(AR_S.isalpha(), npt.NDArray[np.bool])
    +
    +assert_type(AR_U.isalnum(), npt.NDArray[np.bool])
    +assert_type(AR_S.isalnum(), npt.NDArray[np.bool])
    +
    +assert_type(AR_U.isdecimal(), npt.NDArray[np.bool])
    +assert_type(AR_S.isdecimal(), npt.NDArray[np.bool])
    +
    +assert_type(AR_U.isdigit(), npt.NDArray[np.bool])
    +assert_type(AR_S.isdigit(), npt.NDArray[np.bool])
    +
    +assert_type(AR_U.islower(), npt.NDArray[np.bool])
    +assert_type(AR_S.islower(), npt.NDArray[np.bool])
    +
    +assert_type(AR_U.isnumeric(), npt.NDArray[np.bool])
    +assert_type(AR_S.isnumeric(), npt.NDArray[np.bool])
    +
    +assert_type(AR_U.isspace(), npt.NDArray[np.bool])
    +assert_type(AR_S.isspace(), npt.NDArray[np.bool])
    +
    +assert_type(AR_U.istitle(), npt.NDArray[np.bool])
    +assert_type(AR_S.istitle(), npt.NDArray[np.bool])
    +
    +assert_type(AR_U.isupper(), npt.NDArray[np.bool])
    +assert_type(AR_S.isupper(), npt.NDArray[np.bool])
    +
    +assert_type(AR_U.__array_finalize__(object()), None)
    +assert_type(AR_S.__array_finalize__(object()), None)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/comparisons.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/comparisons.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..6df5a3d94314629213c5c28fa77ae62a310872dd
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/comparisons.pyi
    @@ -0,0 +1,264 @@
    +import decimal
    +import fractions
    +from typing import assert_type
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +c16 = np.complex128()
    +f8 = np.float64()
    +i8 = np.int64()
    +u8 = np.uint64()
    +
    +c8 = np.complex64()
    +f4 = np.float32()
    +i4 = np.int32()
    +u4 = np.uint32()
    +
    +dt = np.datetime64(0, "D")
    +td = np.timedelta64(0, "D")
    +
    +b_ = np.bool()
    +
    +b = False
    +c = complex()
    +f = 0.0
    +i = 0
    +
    +AR = np.array([0], dtype=np.int64)
    +AR.setflags(write=False)
    +
    +SEQ = (0, 1, 2, 3, 4)
    +
    +# object-like comparisons
    +
    +assert_type(i8 > fractions.Fraction(1, 5), np.bool)
    +assert_type(i8 > [fractions.Fraction(1, 5)], npt.NDArray[np.bool])
    +assert_type(i8 > decimal.Decimal("1.5"), np.bool)
    +assert_type(i8 > [decimal.Decimal("1.5")], npt.NDArray[np.bool])
    +
    +# Time structures
    +
    +assert_type(dt > dt, np.bool)
    +
    +assert_type(td > td, np.bool)
    +assert_type(td > i, np.bool)
    +assert_type(td > i4, np.bool)
    +assert_type(td > i8, np.bool)
    +
    +assert_type(td > AR, npt.NDArray[np.bool])
    +assert_type(td > SEQ, npt.NDArray[np.bool])
    +assert_type(AR > SEQ, npt.NDArray[np.bool])
    +assert_type(AR > td, npt.NDArray[np.bool])
    +assert_type(SEQ > td, npt.NDArray[np.bool])
    +assert_type(SEQ > AR, npt.NDArray[np.bool])
    +
    +# boolean
    +
    +assert_type(b_ > b, np.bool)
    +assert_type(b_ > b_, np.bool)
    +assert_type(b_ > i, np.bool)
    +assert_type(b_ > i8, np.bool)
    +assert_type(b_ > i4, np.bool)
    +assert_type(b_ > u8, np.bool)
    +assert_type(b_ > u4, np.bool)
    +assert_type(b_ > f, np.bool)
    +assert_type(b_ > f8, np.bool)
    +assert_type(b_ > f4, np.bool)
    +assert_type(b_ > c, np.bool)
    +assert_type(b_ > c16, np.bool)
    +assert_type(b_ > c8, np.bool)
    +assert_type(b_ > AR, npt.NDArray[np.bool])
    +assert_type(b_ > SEQ, npt.NDArray[np.bool])
    +
    +# Complex
    +
    +assert_type(c16 > c16, np.bool)
    +assert_type(c16 > f8, np.bool)
    +assert_type(c16 > i8, np.bool)
    +assert_type(c16 > c8, np.bool)
    +assert_type(c16 > f4, np.bool)
    +assert_type(c16 > i4, np.bool)
    +assert_type(c16 > b_, np.bool)
    +assert_type(c16 > b, np.bool)
    +assert_type(c16 > c, np.bool)
    +assert_type(c16 > f, np.bool)
    +assert_type(c16 > i, np.bool)
    +assert_type(c16 > AR, npt.NDArray[np.bool])
    +assert_type(c16 > SEQ, npt.NDArray[np.bool])
    +
    +assert_type(c16 > c16, np.bool)
    +assert_type(f8 > c16, np.bool)
    +assert_type(i8 > c16, np.bool)
    +assert_type(c8 > c16, np.bool)
    +assert_type(f4 > c16, np.bool)
    +assert_type(i4 > c16, np.bool)
    +assert_type(b_ > c16, np.bool)
    +assert_type(b > c16, np.bool)
    +assert_type(c > c16, np.bool)
    +assert_type(f > c16, np.bool)
    +assert_type(i > c16, np.bool)
    +assert_type(AR > c16, npt.NDArray[np.bool])
    +assert_type(SEQ > c16, npt.NDArray[np.bool])
    +
    +assert_type(c8 > c16, np.bool)
    +assert_type(c8 > f8, np.bool)
    +assert_type(c8 > i8, np.bool)
    +assert_type(c8 > c8, np.bool)
    +assert_type(c8 > f4, np.bool)
    +assert_type(c8 > i4, np.bool)
    +assert_type(c8 > b_, np.bool)
    +assert_type(c8 > b, np.bool)
    +assert_type(c8 > c, np.bool)
    +assert_type(c8 > f, np.bool)
    +assert_type(c8 > i, np.bool)
    +assert_type(c8 > AR, npt.NDArray[np.bool])
    +assert_type(c8 > SEQ, npt.NDArray[np.bool])
    +
    +assert_type(c16 > c8, np.bool)
    +assert_type(f8 > c8, np.bool)
    +assert_type(i8 > c8, np.bool)
    +assert_type(c8 > c8, np.bool)
    +assert_type(f4 > c8, np.bool)
    +assert_type(i4 > c8, np.bool)
    +assert_type(b_ > c8, np.bool)
    +assert_type(b > c8, np.bool)
    +assert_type(c > c8, np.bool)
    +assert_type(f > c8, np.bool)
    +assert_type(i > c8, np.bool)
    +assert_type(AR > c8, npt.NDArray[np.bool])
    +assert_type(SEQ > c8, npt.NDArray[np.bool])
    +
    +# Float
    +
    +assert_type(f8 > f8, np.bool)
    +assert_type(f8 > i8, np.bool)
    +assert_type(f8 > f4, np.bool)
    +assert_type(f8 > i4, np.bool)
    +assert_type(f8 > b_, np.bool)
    +assert_type(f8 > b, np.bool)
    +assert_type(f8 > c, np.bool)
    +assert_type(f8 > f, np.bool)
    +assert_type(f8 > i, np.bool)
    +assert_type(f8 > AR, npt.NDArray[np.bool])
    +assert_type(f8 > SEQ, npt.NDArray[np.bool])
    +
    +assert_type(f8 > f8, np.bool)
    +assert_type(i8 > f8, np.bool)
    +assert_type(f4 > f8, np.bool)
    +assert_type(i4 > f8, np.bool)
    +assert_type(b_ > f8, np.bool)
    +assert_type(b > f8, np.bool)
    +assert_type(c > f8, np.bool)
    +assert_type(f > f8, np.bool)
    +assert_type(i > f8, np.bool)
    +assert_type(AR > f8, npt.NDArray[np.bool])
    +assert_type(SEQ > f8, npt.NDArray[np.bool])
    +
    +assert_type(f4 > f8, np.bool)
    +assert_type(f4 > i8, np.bool)
    +assert_type(f4 > f4, np.bool)
    +assert_type(f4 > i4, np.bool)
    +assert_type(f4 > b_, np.bool)
    +assert_type(f4 > b, np.bool)
    +assert_type(f4 > c, np.bool)
    +assert_type(f4 > f, np.bool)
    +assert_type(f4 > i, np.bool)
    +assert_type(f4 > AR, npt.NDArray[np.bool])
    +assert_type(f4 > SEQ, npt.NDArray[np.bool])
    +
    +assert_type(f8 > f4, np.bool)
    +assert_type(i8 > f4, np.bool)
    +assert_type(f4 > f4, np.bool)
    +assert_type(i4 > f4, np.bool)
    +assert_type(b_ > f4, np.bool)
    +assert_type(b > f4, np.bool)
    +assert_type(c > f4, np.bool)
    +assert_type(f > f4, np.bool)
    +assert_type(i > f4, np.bool)
    +assert_type(AR > f4, npt.NDArray[np.bool])
    +assert_type(SEQ > f4, npt.NDArray[np.bool])
    +
    +# Int
    +
    +assert_type(i8 > i8, np.bool)
    +assert_type(i8 > u8, np.bool)
    +assert_type(i8 > i4, np.bool)
    +assert_type(i8 > u4, np.bool)
    +assert_type(i8 > b_, np.bool)
    +assert_type(i8 > b, np.bool)
    +assert_type(i8 > c, np.bool)
    +assert_type(i8 > f, np.bool)
    +assert_type(i8 > i, np.bool)
    +assert_type(i8 > AR, npt.NDArray[np.bool])
    +assert_type(i8 > SEQ, npt.NDArray[np.bool])
    +
    +assert_type(u8 > u8, np.bool)
    +assert_type(u8 > i4, np.bool)
    +assert_type(u8 > u4, np.bool)
    +assert_type(u8 > b_, np.bool)
    +assert_type(u8 > b, np.bool)
    +assert_type(u8 > c, np.bool)
    +assert_type(u8 > f, np.bool)
    +assert_type(u8 > i, np.bool)
    +assert_type(u8 > AR, npt.NDArray[np.bool])
    +assert_type(u8 > SEQ, npt.NDArray[np.bool])
    +
    +assert_type(i8 > i8, np.bool)
    +assert_type(u8 > i8, np.bool)
    +assert_type(i4 > i8, np.bool)
    +assert_type(u4 > i8, np.bool)
    +assert_type(b_ > i8, np.bool)
    +assert_type(b > i8, np.bool)
    +assert_type(c > i8, np.bool)
    +assert_type(f > i8, np.bool)
    +assert_type(i > i8, np.bool)
    +assert_type(AR > i8, npt.NDArray[np.bool])
    +assert_type(SEQ > i8, npt.NDArray[np.bool])
    +
    +assert_type(u8 > u8, np.bool)
    +assert_type(i4 > u8, np.bool)
    +assert_type(u4 > u8, np.bool)
    +assert_type(b_ > u8, np.bool)
    +assert_type(b > u8, np.bool)
    +assert_type(c > u8, np.bool)
    +assert_type(f > u8, np.bool)
    +assert_type(i > u8, np.bool)
    +assert_type(AR > u8, npt.NDArray[np.bool])
    +assert_type(SEQ > u8, npt.NDArray[np.bool])
    +
    +assert_type(i4 > i8, np.bool)
    +assert_type(i4 > i4, np.bool)
    +assert_type(i4 > i, np.bool)
    +assert_type(i4 > b_, np.bool)
    +assert_type(i4 > b, np.bool)
    +assert_type(i4 > AR, npt.NDArray[np.bool])
    +assert_type(i4 > SEQ, npt.NDArray[np.bool])
    +
    +assert_type(u4 > i8, np.bool)
    +assert_type(u4 > i4, np.bool)
    +assert_type(u4 > u8, np.bool)
    +assert_type(u4 > u4, np.bool)
    +assert_type(u4 > i, np.bool)
    +assert_type(u4 > b_, np.bool)
    +assert_type(u4 > b, np.bool)
    +assert_type(u4 > AR, npt.NDArray[np.bool])
    +assert_type(u4 > SEQ, npt.NDArray[np.bool])
    +
    +assert_type(i8 > i4, np.bool)
    +assert_type(i4 > i4, np.bool)
    +assert_type(i > i4, np.bool)
    +assert_type(b_ > i4, np.bool)
    +assert_type(b > i4, np.bool)
    +assert_type(AR > i4, npt.NDArray[np.bool])
    +assert_type(SEQ > i4, npt.NDArray[np.bool])
    +
    +assert_type(i8 > u4, np.bool)
    +assert_type(i4 > u4, np.bool)
    +assert_type(u8 > u4, np.bool)
    +assert_type(u4 > u4, np.bool)
    +assert_type(b_ > u4, np.bool)
    +assert_type(b > u4, np.bool)
    +assert_type(i > u4, np.bool)
    +assert_type(AR > u4, npt.NDArray[np.bool])
    +assert_type(SEQ > u4, npt.NDArray[np.bool])
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/constants.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/constants.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..d4474f46ce7e19571125dc45669692a1c501a5b9
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/constants.pyi
    @@ -0,0 +1,14 @@
    +from typing import Literal, assert_type
    +
    +import numpy as np
    +
    +assert_type(np.e, float)
    +assert_type(np.euler_gamma, float)
    +assert_type(np.inf, float)
    +assert_type(np.nan, float)
    +assert_type(np.pi, float)
    +
    +assert_type(np.little_endian, bool)
    +
    +assert_type(np.True_, np.bool[Literal[True]])
    +assert_type(np.False_, np.bool[Literal[False]])
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/ctypeslib.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/ctypeslib.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..0564d725cf62f2419460b0f7cdabfd890947438e
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/ctypeslib.pyi
    @@ -0,0 +1,81 @@
    +import ctypes as ct
    +from typing import Any, assert_type
    +
    +import numpy as np
    +import numpy.typing as npt
    +from numpy import ctypeslib
    +
    +AR_bool: npt.NDArray[np.bool]
    +AR_ubyte: npt.NDArray[np.ubyte]
    +AR_ushort: npt.NDArray[np.ushort]
    +AR_uintc: npt.NDArray[np.uintc]
    +AR_ulong: npt.NDArray[np.ulong]
    +AR_ulonglong: npt.NDArray[np.ulonglong]
    +AR_byte: npt.NDArray[np.byte]
    +AR_short: npt.NDArray[np.short]
    +AR_intc: npt.NDArray[np.intc]
    +AR_long: npt.NDArray[np.long]
    +AR_longlong: npt.NDArray[np.longlong]
    +AR_single: npt.NDArray[np.single]
    +AR_double: npt.NDArray[np.double]
    +AR_longdouble: npt.NDArray[np.longdouble]
    +AR_void: npt.NDArray[np.void]
    +
    +pointer: ct._Pointer[Any]
    +
    +assert_type(np.ctypeslib.c_intp(), ctypeslib.c_intp)
    +
    +assert_type(np.ctypeslib.ndpointer(), type[ctypeslib._ndptr[None]])
    +assert_type(np.ctypeslib.ndpointer(dtype=np.float64), type[ctypeslib._ndptr[np.dtype[np.float64]]])
    +assert_type(np.ctypeslib.ndpointer(dtype=float), type[ctypeslib._ndptr[np.dtype]])
    +assert_type(np.ctypeslib.ndpointer(shape=(10, 3)), type[ctypeslib._ndptr[None]])
    +assert_type(np.ctypeslib.ndpointer(np.int64, shape=(10, 3)), type[ctypeslib._concrete_ndptr[np.dtype[np.int64]]])
    +assert_type(np.ctypeslib.ndpointer(int, shape=(1,)), type[np.ctypeslib._concrete_ndptr[np.dtype]])
    +
    +assert_type(np.ctypeslib.as_ctypes_type(np.bool), type[ct.c_bool])
    +assert_type(np.ctypeslib.as_ctypes_type(np.ubyte), type[ct.c_ubyte])
    +assert_type(np.ctypeslib.as_ctypes_type(np.ushort), type[ct.c_ushort])
    +assert_type(np.ctypeslib.as_ctypes_type(np.uintc), type[ct.c_uint])
    +assert_type(np.ctypeslib.as_ctypes_type(np.byte), type[ct.c_byte])
    +assert_type(np.ctypeslib.as_ctypes_type(np.short), type[ct.c_short])
    +assert_type(np.ctypeslib.as_ctypes_type(np.intc), type[ct.c_int])
    +assert_type(np.ctypeslib.as_ctypes_type(np.single), type[ct.c_float])
    +assert_type(np.ctypeslib.as_ctypes_type(np.double), type[ct.c_double])
    +assert_type(np.ctypeslib.as_ctypes_type(ct.c_double), type[ct.c_double])
    +assert_type(np.ctypeslib.as_ctypes_type("q"), type[ct.c_longlong])
    +assert_type(np.ctypeslib.as_ctypes_type([("i8", np.int64), ("f8", np.float64)]), type[Any])
    +assert_type(np.ctypeslib.as_ctypes_type("i8"), type[Any])
    +assert_type(np.ctypeslib.as_ctypes_type("f8"), type[Any])
    +
    +assert_type(np.ctypeslib.as_ctypes(AR_bool.take(0)), ct.c_bool)
    +assert_type(np.ctypeslib.as_ctypes(AR_ubyte.take(0)), ct.c_ubyte)
    +assert_type(np.ctypeslib.as_ctypes(AR_ushort.take(0)), ct.c_ushort)
    +assert_type(np.ctypeslib.as_ctypes(AR_uintc.take(0)), ct.c_uint)
    +
    +assert_type(np.ctypeslib.as_ctypes(AR_byte.take(0)), ct.c_byte)
    +assert_type(np.ctypeslib.as_ctypes(AR_short.take(0)), ct.c_short)
    +assert_type(np.ctypeslib.as_ctypes(AR_intc.take(0)), ct.c_int)
    +assert_type(np.ctypeslib.as_ctypes(AR_single.take(0)), ct.c_float)
    +assert_type(np.ctypeslib.as_ctypes(AR_double.take(0)), ct.c_double)
    +assert_type(np.ctypeslib.as_ctypes(AR_void.take(0)), Any)
    +assert_type(np.ctypeslib.as_ctypes(AR_bool), ct.Array[ct.c_bool])
    +assert_type(np.ctypeslib.as_ctypes(AR_ubyte), ct.Array[ct.c_ubyte])
    +assert_type(np.ctypeslib.as_ctypes(AR_ushort), ct.Array[ct.c_ushort])
    +assert_type(np.ctypeslib.as_ctypes(AR_uintc), ct.Array[ct.c_uint])
    +assert_type(np.ctypeslib.as_ctypes(AR_byte), ct.Array[ct.c_byte])
    +assert_type(np.ctypeslib.as_ctypes(AR_short), ct.Array[ct.c_short])
    +assert_type(np.ctypeslib.as_ctypes(AR_intc), ct.Array[ct.c_int])
    +assert_type(np.ctypeslib.as_ctypes(AR_single), ct.Array[ct.c_float])
    +assert_type(np.ctypeslib.as_ctypes(AR_double), ct.Array[ct.c_double])
    +assert_type(np.ctypeslib.as_ctypes(AR_void), ct.Array[Any])
    +
    +assert_type(np.ctypeslib.as_array(AR_ubyte), npt.NDArray[np.ubyte])
    +assert_type(np.ctypeslib.as_array(1), npt.NDArray[Any])
    +assert_type(np.ctypeslib.as_array(pointer), npt.NDArray[Any])
    +
    +assert_type(np.ctypeslib.as_ctypes_type(np.long), type[ct.c_long])
    +assert_type(np.ctypeslib.as_ctypes_type(np.ulong), type[ct.c_ulong])
    +assert_type(np.ctypeslib.as_ctypes(AR_ulong), ct.Array[ct.c_ulong])
    +assert_type(np.ctypeslib.as_ctypes(AR_long), ct.Array[ct.c_long])
    +assert_type(np.ctypeslib.as_ctypes(AR_long.take(0)), ct.c_long)
    +assert_type(np.ctypeslib.as_ctypes(AR_ulong.take(0)), ct.c_ulong)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/datasource.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/datasource.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..9f017911a1ff89dd1f80c059fb13976bc60cd12b
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/datasource.pyi
    @@ -0,0 +1,23 @@
    +from pathlib import Path
    +from typing import IO, Any, assert_type
    +
    +import numpy as np
    +
    +path1: Path
    +path2: str
    +
    +d1 = np.lib.npyio.DataSource(path1)
    +d2 = np.lib.npyio.DataSource(path2)
    +d3 = np.lib.npyio.DataSource(None)
    +
    +assert_type(d1.abspath("..."), str)
    +assert_type(d2.abspath("..."), str)
    +assert_type(d3.abspath("..."), str)
    +
    +assert_type(d1.exists("..."), bool)
    +assert_type(d2.exists("..."), bool)
    +assert_type(d3.exists("..."), bool)
    +
    +assert_type(d1.open("...", "r"), IO[Any])
    +assert_type(d2.open("...", encoding="utf8"), IO[Any])
    +assert_type(d3.open("...", newline="/n"), IO[Any])
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/dtype.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/dtype.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..7f670b3be51c6507b5cba4469f7e96c878cea571
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/dtype.pyi
    @@ -0,0 +1,132 @@
    +import ctypes as ct
    +import datetime as dt
    +from decimal import Decimal
    +from fractions import Fraction
    +from typing import Any, Literal, LiteralString, TypeAlias, assert_type
    +
    +import numpy as np
    +from numpy.dtypes import StringDType
    +
    +# a combination of likely `object` dtype-like candidates (no `_co`)
    +_PyObjectLike: TypeAlias = Decimal | Fraction | dt.datetime | dt.timedelta
    +
    +dtype_U: np.dtype[np.str_]
    +dtype_V: np.dtype[np.void]
    +dtype_i8: np.dtype[np.int64]
    +
    +py_object: type[_PyObjectLike]
    +py_character: type[str | bytes]
    +
    +ct_floating: type[ct.c_float | ct.c_double | ct.c_longdouble]
    +ct_number: type[ct.c_uint8 | ct.c_float]
    +ct_generic: type[ct.c_bool | ct.c_char]
    +
    +cs_integer: Literal["u1", "V", "S"]
    +cs_generic: Literal["H", "U", "h", "|M8[Y]", "?"]
    +
    +dt_inexact: np.dtype[np.inexact]
    +dt_string: StringDType
    +
    +assert_type(np.dtype(np.float64), np.dtype[np.float64])
    +assert_type(np.dtype(np.float64, metadata={"test": "test"}), np.dtype[np.float64])
    +assert_type(np.dtype(np.int64), np.dtype[np.int64])
    +
    +# String aliases
    +assert_type(np.dtype("float64"), np.dtype[np.float64])
    +assert_type(np.dtype("float32"), np.dtype[np.float32])
    +assert_type(np.dtype("int64"), np.dtype[np.int64])
    +assert_type(np.dtype("int32"), np.dtype[np.int32])
    +assert_type(np.dtype("bool"), np.dtype[np.bool])
    +assert_type(np.dtype("bytes"), np.dtype[np.bytes_])
    +assert_type(np.dtype("str"), np.dtype[np.str_])
    +
    +# Python types
    +assert_type(np.dtype(bool), np.dtype[np.bool])
    +assert_type(np.dtype(int), np.dtype[np.int_ | np.bool])
    +assert_type(np.dtype(float), np.dtype[np.float64 | np.int_ | np.bool])
    +assert_type(np.dtype(complex), np.dtype[np.complex128 | np.float64 | np.int_ | np.bool])
    +assert_type(np.dtype(py_object), np.dtype[np.object_])
    +assert_type(np.dtype(str), np.dtype[np.str_])
    +assert_type(np.dtype(bytes), np.dtype[np.bytes_])
    +assert_type(np.dtype(memoryview), np.dtype[np.void])
    +assert_type(np.dtype(py_character), np.dtype[np.character])
    +
    +# object types
    +assert_type(np.dtype(list), np.dtype[np.object_])
    +assert_type(np.dtype(dt.datetime), np.dtype[np.object_])
    +assert_type(np.dtype(dt.timedelta), np.dtype[np.object_])
    +assert_type(np.dtype(Decimal), np.dtype[np.object_])
    +assert_type(np.dtype(Fraction), np.dtype[np.object_])
    +
    +# char-codes
    +assert_type(np.dtype("?"), np.dtype[np.bool])
    +assert_type(np.dtype("|b1"), np.dtype[np.bool])
    +assert_type(np.dtype("u1"), np.dtype[np.uint8])
    +assert_type(np.dtype("l"), np.dtype[np.long])
    +assert_type(np.dtype("longlong"), np.dtype[np.longlong])
    +assert_type(np.dtype(">g"), np.dtype[np.longdouble])
    +assert_type(np.dtype(cs_integer), np.dtype[np.integer])
    +
    +# ctypes
    +assert_type(np.dtype(ct.c_double), np.dtype[np.float64])  # see numpy/numpy#29155
    +assert_type(np.dtype(ct.c_longlong), np.dtype[np.longlong])
    +assert_type(np.dtype(ct.c_uint32), np.dtype[np.uint32])
    +assert_type(np.dtype(ct.c_bool), np.dtype[np.bool])
    +assert_type(np.dtype(ct.c_char), np.dtype[np.bytes_])
    +assert_type(np.dtype(ct.py_object), np.dtype[np.object_])
    +
    +# Special case for None
    +assert_type(np.dtype(None), np.dtype[np.float64])
    +
    +# dtypes of dtypes
    +assert_type(np.dtype(np.dtype(np.float64)), np.dtype[np.float64])
    +assert_type(np.dtype(dt_inexact), np.dtype[np.inexact])
    +
    +# Parameterized dtypes
    +assert_type(np.dtype("S8"), np.dtype)
    +
    +# Void
    +assert_type(np.dtype(("U", 10)), np.dtype[np.void])
    +assert_type(np.dtype({"formats": (int, "u8"), "names": ("n", "B")}), np.dtype[np.void])
    +
    +# StringDType
    +assert_type(np.dtype(dt_string), StringDType)
    +assert_type(np.dtype("T"), StringDType)
    +assert_type(np.dtype("=T"), StringDType)
    +assert_type(np.dtype("|T"), StringDType)
    +
    +# Methods and attributes
    +assert_type(dtype_U.base, np.dtype)
    +assert_type(dtype_U.subdtype, tuple[np.dtype, tuple[Any, ...]] | None)
    +assert_type(dtype_U.newbyteorder(), np.dtype[np.str_])
    +assert_type(dtype_U.type, type[np.str_])
    +assert_type(dtype_U.name, LiteralString)
    +assert_type(dtype_U.names, tuple[str, ...] | None)
    +
    +assert_type(dtype_U * 0, np.dtype[np.str_])
    +assert_type(dtype_U * 1, np.dtype[np.str_])
    +assert_type(dtype_U * 2, np.dtype[np.str_])
    +
    +assert_type(dtype_i8 * 0, np.dtype[np.void])
    +assert_type(dtype_i8 * 1, np.dtype[np.int64])
    +assert_type(dtype_i8 * 2, np.dtype[np.void])
    +
    +assert_type(0 * dtype_U, np.dtype[np.str_])
    +assert_type(1 * dtype_U, np.dtype[np.str_])
    +assert_type(2 * dtype_U, np.dtype[np.str_])
    +
    +assert_type(0 * dtype_i8, np.dtype)
    +assert_type(1 * dtype_i8, np.dtype)
    +assert_type(2 * dtype_i8, np.dtype)
    +
    +assert_type(dtype_V["f0"], np.dtype)
    +assert_type(dtype_V[0], np.dtype)
    +assert_type(dtype_V[["f0", "f1"]], np.dtype[np.void])
    +assert_type(dtype_V[["f0"]], np.dtype[np.void])
    +
    +class _D:
    +    __numpy_dtype__: np.dtype[np.int8]
    +
    +assert_type(np.dtype(_D()), np.dtype[np.int8])
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/einsumfunc.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/einsumfunc.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..cc58f006e249bb5fd520fa45bbe1f1690e4c203d
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/einsumfunc.pyi
    @@ -0,0 +1,39 @@
    +from typing import Any, assert_type
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +AR_LIKE_b: list[bool]
    +AR_LIKE_u: list[np.uint32]
    +AR_LIKE_i: list[int]
    +AR_LIKE_f: list[float]
    +AR_LIKE_c: list[complex]
    +AR_LIKE_U: list[str]
    +AR_o: npt.NDArray[np.object_]
    +
    +OUT_f: npt.NDArray[np.float64]
    +
    +assert_type(np.einsum("i,i->i", AR_LIKE_b, AR_LIKE_b), Any)
    +assert_type(np.einsum("i,i->i", AR_o, AR_o), Any)
    +assert_type(np.einsum("i,i->i", AR_LIKE_u, AR_LIKE_u), Any)
    +assert_type(np.einsum("i,i->i", AR_LIKE_i, AR_LIKE_i), Any)
    +assert_type(np.einsum("i,i->i", AR_LIKE_f, AR_LIKE_f), Any)
    +assert_type(np.einsum("i,i->i", AR_LIKE_c, AR_LIKE_c), Any)
    +assert_type(np.einsum("i,i->i", AR_LIKE_b, AR_LIKE_i), Any)
    +assert_type(np.einsum("i,i,i,i->i", AR_LIKE_b, AR_LIKE_u, AR_LIKE_i, AR_LIKE_c), Any)
    +
    +assert_type(np.einsum("i,i->i", AR_LIKE_c, AR_LIKE_c, out=OUT_f), npt.NDArray[np.float64])
    +assert_type(np.einsum("i,i->i", AR_LIKE_U, AR_LIKE_U, dtype=bool, casting="unsafe", out=OUT_f), npt.NDArray[np.float64])
    +assert_type(np.einsum("i,i->i", AR_LIKE_f, AR_LIKE_f, dtype="c16"), Any)
    +assert_type(np.einsum("i,i->i", AR_LIKE_U, AR_LIKE_U, dtype=bool, casting="unsafe"), Any)
    +
    +assert_type(np.einsum_path("i,i->i", AR_LIKE_b, AR_LIKE_b), tuple[list[Any], str])
    +assert_type(np.einsum_path("i,i->i", AR_LIKE_u, AR_LIKE_u), tuple[list[Any], str])
    +assert_type(np.einsum_path("i,i->i", AR_LIKE_i, AR_LIKE_i), tuple[list[Any], str])
    +assert_type(np.einsum_path("i,i->i", AR_LIKE_f, AR_LIKE_f), tuple[list[Any], str])
    +assert_type(np.einsum_path("i,i->i", AR_LIKE_c, AR_LIKE_c), tuple[list[Any], str])
    +assert_type(np.einsum_path("i,i->i", AR_LIKE_b, AR_LIKE_i), tuple[list[Any], str])
    +assert_type(np.einsum_path("i,i,i,i->i", AR_LIKE_b, AR_LIKE_u, AR_LIKE_i, AR_LIKE_c), tuple[list[Any], str])
    +
    +assert_type(np.einsum([[1, 1], [1, 1]], AR_LIKE_i, AR_LIKE_i), Any)
    +assert_type(np.einsum_path([[1, 1], [1, 1]], AR_LIKE_i, AR_LIKE_i), tuple[list[Any], str])
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/emath.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/emath.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..1d7bff893e73445dec3f34e5078ebe19a7d893ed
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/emath.pyi
    @@ -0,0 +1,54 @@
    +from typing import Any, assert_type
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +AR_f8: npt.NDArray[np.float64]
    +AR_c16: npt.NDArray[np.complex128]
    +f8: np.float64
    +c16: np.complex128
    +
    +assert_type(np.emath.sqrt(f8), Any)
    +assert_type(np.emath.sqrt(AR_f8), npt.NDArray[Any])
    +assert_type(np.emath.sqrt(c16), np.complexfloating)
    +assert_type(np.emath.sqrt(AR_c16), npt.NDArray[np.complexfloating])
    +
    +assert_type(np.emath.log(f8), Any)
    +assert_type(np.emath.log(AR_f8), npt.NDArray[Any])
    +assert_type(np.emath.log(c16), np.complexfloating)
    +assert_type(np.emath.log(AR_c16), npt.NDArray[np.complexfloating])
    +
    +assert_type(np.emath.log10(f8), Any)
    +assert_type(np.emath.log10(AR_f8), npt.NDArray[Any])
    +assert_type(np.emath.log10(c16), np.complexfloating)
    +assert_type(np.emath.log10(AR_c16), npt.NDArray[np.complexfloating])
    +
    +assert_type(np.emath.log2(f8), Any)
    +assert_type(np.emath.log2(AR_f8), npt.NDArray[Any])
    +assert_type(np.emath.log2(c16), np.complexfloating)
    +assert_type(np.emath.log2(AR_c16), npt.NDArray[np.complexfloating])
    +
    +assert_type(np.emath.logn(f8, 2), Any)
    +assert_type(np.emath.logn(AR_f8, 4), npt.NDArray[Any])
    +assert_type(np.emath.logn(f8, 1j), np.complexfloating)
    +assert_type(np.emath.logn(AR_c16, 1.5), npt.NDArray[np.complexfloating])
    +
    +assert_type(np.emath.power(f8, 2), Any)
    +assert_type(np.emath.power(AR_f8, 4), npt.NDArray[Any])
    +assert_type(np.emath.power(f8, 2j), np.complexfloating)
    +assert_type(np.emath.power(AR_c16, 1.5), npt.NDArray[np.complexfloating])
    +
    +assert_type(np.emath.arccos(f8), Any)
    +assert_type(np.emath.arccos(AR_f8), npt.NDArray[Any])
    +assert_type(np.emath.arccos(c16), np.complexfloating)
    +assert_type(np.emath.arccos(AR_c16), npt.NDArray[np.complexfloating])
    +
    +assert_type(np.emath.arcsin(f8), Any)
    +assert_type(np.emath.arcsin(AR_f8), npt.NDArray[Any])
    +assert_type(np.emath.arcsin(c16), np.complexfloating)
    +assert_type(np.emath.arcsin(AR_c16), npt.NDArray[np.complexfloating])
    +
    +assert_type(np.emath.arctanh(f8), Any)
    +assert_type(np.emath.arctanh(AR_f8), npt.NDArray[Any])
    +assert_type(np.emath.arctanh(c16), np.complexfloating)
    +assert_type(np.emath.arctanh(AR_c16), npt.NDArray[np.complexfloating])
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/fft.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/fft.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..dacd2b89777c1453088f2fedee82996bfb1e66ca
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/fft.pyi
    @@ -0,0 +1,37 @@
    +from typing import Any, assert_type
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +AR_f8: npt.NDArray[np.float64]
    +AR_c16: npt.NDArray[np.complex128]
    +AR_LIKE_f8: list[float]
    +
    +assert_type(np.fft.fftshift(AR_f8), npt.NDArray[np.float64])
    +assert_type(np.fft.fftshift(AR_LIKE_f8, axes=0), npt.NDArray[Any])
    +
    +assert_type(np.fft.ifftshift(AR_f8), npt.NDArray[np.float64])
    +assert_type(np.fft.ifftshift(AR_LIKE_f8, axes=0), npt.NDArray[Any])
    +
    +assert_type(np.fft.fftfreq(5, AR_f8), npt.NDArray[np.floating])
    +assert_type(np.fft.fftfreq(np.int64(), AR_c16), npt.NDArray[np.complexfloating])
    +
    +assert_type(np.fft.fftfreq(5, AR_f8), npt.NDArray[np.floating])
    +assert_type(np.fft.fftfreq(np.int64(), AR_c16), npt.NDArray[np.complexfloating])
    +
    +assert_type(np.fft.fft(AR_f8), npt.NDArray[np.complex128])
    +assert_type(np.fft.ifft(AR_f8, axis=1), npt.NDArray[np.complex128])
    +assert_type(np.fft.rfft(AR_f8, n=None), npt.NDArray[np.complex128])
    +assert_type(np.fft.irfft(AR_f8, norm="ortho"), npt.NDArray[np.float64])
    +assert_type(np.fft.hfft(AR_f8, n=2), npt.NDArray[np.float64])
    +assert_type(np.fft.ihfft(AR_f8), npt.NDArray[np.complex128])
    +
    +assert_type(np.fft.fftn(AR_f8), npt.NDArray[np.complex128])
    +assert_type(np.fft.ifftn(AR_f8), npt.NDArray[np.complex128])
    +assert_type(np.fft.rfftn(AR_f8), npt.NDArray[np.complex128])
    +assert_type(np.fft.irfftn(AR_f8), npt.NDArray[np.float64])
    +
    +assert_type(np.fft.rfft2(AR_f8), npt.NDArray[np.complex128])
    +assert_type(np.fft.ifft2(AR_f8), npt.NDArray[np.complex128])
    +assert_type(np.fft.fft2(AR_f8), npt.NDArray[np.complex128])
    +assert_type(np.fft.irfft2(AR_f8), npt.NDArray[np.float64])
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/flatiter.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/flatiter.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..98d61a6d342825dc67755dae40f8407e6f96aa1c
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/flatiter.pyi
    @@ -0,0 +1,86 @@
    +from typing import Any, TypeAlias, assert_type
    +
    +import numpy as np
    +
    +_ArrayND: TypeAlias = np.ndarray[tuple[Any, ...], np.dtypes.StrDType]
    +_Array1D: TypeAlias = np.ndarray[tuple[int], np.dtypes.BytesDType]
    +_Array2D: TypeAlias = np.ndarray[tuple[int, int], np.dtypes.Int8DType]
    +
    +_a_nd: np.flatiter[_ArrayND]
    +_a_1d: np.flatiter[_Array1D]
    +_a_2d: np.flatiter[_Array2D]
    +
    +###
    +
    +# .base
    +assert_type(_a_nd.base, _ArrayND)
    +assert_type(_a_1d.base, _Array1D)
    +assert_type(_a_2d.base, _Array2D)
    +
    +# .coords
    +assert_type(_a_nd.coords, tuple[Any, ...])
    +assert_type(_a_1d.coords, tuple[int])
    +assert_type(_a_2d.coords, tuple[int, int])
    +
    +# .index
    +assert_type(_a_nd.index, int)
    +assert_type(_a_1d.index, int)
    +assert_type(_a_2d.index, int)
    +
    +# .__len__()
    +assert_type(len(_a_nd), int)
    +assert_type(len(_a_1d), int)
    +assert_type(len(_a_2d), int)
    +
    +# .__iter__()
    +assert_type(iter(_a_nd), np.flatiter[_ArrayND])
    +assert_type(iter(_a_1d), np.flatiter[_Array1D])
    +assert_type(iter(_a_2d), np.flatiter[_Array2D])
    +
    +# .__next__()
    +assert_type(next(_a_nd), np.str_)
    +assert_type(next(_a_1d), np.bytes_)
    +assert_type(next(_a_2d), np.int8)
    +
    +# .__getitem__(())
    +assert_type(_a_nd[()], _ArrayND)
    +assert_type(_a_1d[()], _Array1D)
    +assert_type(_a_2d[()], _Array2D)
    +# .__getitem__(int)
    +assert_type(_a_nd[0], np.str_)
    +assert_type(_a_1d[0], np.bytes_)
    +assert_type(_a_2d[0], np.int8)
    +# .__getitem__(slice)
    +assert_type(_a_nd[::], np.ndarray[tuple[int], np.dtypes.StrDType])
    +assert_type(_a_1d[::], np.ndarray[tuple[int], np.dtypes.BytesDType])
    +assert_type(_a_2d[::], np.ndarray[tuple[int], np.dtypes.Int8DType])
    +# .__getitem__(EllipsisType)
    +assert_type(_a_nd[...], np.ndarray[tuple[int], np.dtypes.StrDType])
    +assert_type(_a_1d[...], np.ndarray[tuple[int], np.dtypes.BytesDType])
    +assert_type(_a_2d[...], np.ndarray[tuple[int], np.dtypes.Int8DType])
    +# .__getitem__(list[!])
    +assert_type(_a_nd[[]], np.ndarray[tuple[int], np.dtypes.StrDType])
    +assert_type(_a_1d[[]], np.ndarray[tuple[int], np.dtypes.BytesDType])
    +assert_type(_a_2d[[]], np.ndarray[tuple[int], np.dtypes.Int8DType])
    +# .__getitem__(list[int])
    +assert_type(_a_nd[[0]], np.ndarray[tuple[int], np.dtypes.StrDType])
    +assert_type(_a_1d[[0]], np.ndarray[tuple[int], np.dtypes.BytesDType])
    +assert_type(_a_2d[[0]], np.ndarray[tuple[int], np.dtypes.Int8DType])
    +# .__getitem__(list[list[int]])
    +assert_type(_a_nd[[[0]]], np.ndarray[tuple[int, int], np.dtypes.StrDType])
    +assert_type(_a_1d[[[0]]], np.ndarray[tuple[int, int], np.dtypes.BytesDType])
    +assert_type(_a_2d[[[0]]], np.ndarray[tuple[int, int], np.dtypes.Int8DType])
    +# .__getitem__(list[list[list[list[int]]]])
    +assert_type(_a_nd[[[[[0]]]]], np.ndarray[tuple[Any, ...], np.dtypes.StrDType])
    +assert_type(_a_1d[[[[[0]]]]], np.ndarray[tuple[Any, ...], np.dtypes.BytesDType])
    +assert_type(_a_2d[[[[[0]]]]], np.ndarray[tuple[Any, ...], np.dtypes.Int8DType])
    +
    +# __array__()
    +assert_type(_a_nd.__array__(), np.ndarray[tuple[int], np.dtypes.StrDType])
    +assert_type(_a_1d.__array__(), np.ndarray[tuple[int], np.dtypes.BytesDType])
    +assert_type(_a_2d.__array__(), np.ndarray[tuple[int], np.dtypes.Int8DType])
    +
    +# .copy()
    +assert_type(_a_nd.copy(), np.ndarray[tuple[int], np.dtypes.StrDType])
    +assert_type(_a_1d.copy(), np.ndarray[tuple[int], np.dtypes.BytesDType])
    +assert_type(_a_2d.copy(), np.ndarray[tuple[int], np.dtypes.Int8DType])
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/fromnumeric.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/fromnumeric.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..58ea2c5f8732c5e9deab89e9c0a757d166957469
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/fromnumeric.pyi
    @@ -0,0 +1,347 @@
    +"""Tests for :mod:`_core.fromnumeric`."""
    +
    +from typing import Any, assert_type
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +class NDArraySubclass(npt.NDArray[np.complex128]): ...
    +
    +AR_b: npt.NDArray[np.bool]
    +AR_f4: npt.NDArray[np.float32]
    +AR_c16: npt.NDArray[np.complex128]
    +AR_u8: npt.NDArray[np.uint64]
    +AR_i8: npt.NDArray[np.int64]
    +AR_O: npt.NDArray[np.object_]
    +AR_subclass: NDArraySubclass
    +AR_m: npt.NDArray[np.timedelta64]
    +AR_0d: np.ndarray[tuple[()]]
    +AR_1d: np.ndarray[tuple[int]]
    +AR_nd: np.ndarray
    +
    +b: np.bool
    +f4: np.float32
    +i8: np.int64
    +f: float
    +
    +# integer‑dtype subclass for argmin/argmax
    +class NDArrayIntSubclass(npt.NDArray[np.intp]): ...
    +AR_sub_i: NDArrayIntSubclass
    +
    +assert_type(np.take(b, 0), np.bool)
    +assert_type(np.take(f4, 0), np.float32)
    +assert_type(np.take(f, 0), Any)
    +assert_type(np.take(AR_b, 0), np.bool)
    +assert_type(np.take(AR_f4, 0), np.float32)
    +assert_type(np.take(AR_b, [0]), npt.NDArray[np.bool])
    +assert_type(np.take(AR_f4, [0]), npt.NDArray[np.float32])
    +assert_type(np.take([1], [0]), npt.NDArray[Any])
    +assert_type(np.take(AR_f4, [0], out=AR_subclass), NDArraySubclass)
    +
    +assert_type(np.reshape(b, 1), np.ndarray[tuple[int], np.dtype[np.bool]])
    +assert_type(np.reshape(f4, 1), np.ndarray[tuple[int], np.dtype[np.float32]])
    +assert_type(np.reshape(f, 1), np.ndarray[tuple[int], np.dtype])
    +assert_type(np.reshape(AR_b, 1), np.ndarray[tuple[int], np.dtype[np.bool]])
    +assert_type(np.reshape(AR_f4, 1), np.ndarray[tuple[int], np.dtype[np.float32]])
    +
    +assert_type(np.choose(1, [True, True]), Any)
    +assert_type(np.choose([1], [True, True]), npt.NDArray[Any])
    +assert_type(np.choose([1], AR_b), npt.NDArray[np.bool])
    +assert_type(np.choose([1], AR_b, out=AR_f4), npt.NDArray[np.float32])
    +
    +assert_type(np.repeat(b, 1), np.ndarray[tuple[int], np.dtype[np.bool]])
    +assert_type(np.repeat(b, 1, axis=0), npt.NDArray[np.bool])
    +assert_type(np.repeat(f4, 1), np.ndarray[tuple[int], np.dtype[np.float32]])
    +assert_type(np.repeat(f, 1), np.ndarray[tuple[int], np.dtype[Any]])
    +assert_type(np.repeat(AR_b, 1), np.ndarray[tuple[int], np.dtype[np.bool]])
    +assert_type(np.repeat(AR_f4, 1), np.ndarray[tuple[int], np.dtype[np.float32]])
    +assert_type(np.repeat(AR_f4, 1, axis=0), npt.NDArray[np.float32])
    +
    +# TODO: array_bdd tests for np.put()
    +
    +assert_type(np.swapaxes([[0, 1]], 0, 0), npt.NDArray[Any])
    +assert_type(np.swapaxes(AR_b, 0, 0), npt.NDArray[np.bool])
    +assert_type(np.swapaxes(AR_f4, 0, 0), npt.NDArray[np.float32])
    +
    +assert_type(np.transpose(b), npt.NDArray[np.bool])
    +assert_type(np.transpose(f4), npt.NDArray[np.float32])
    +assert_type(np.transpose(f), npt.NDArray[Any])
    +assert_type(np.transpose(AR_b), npt.NDArray[np.bool])
    +assert_type(np.transpose(AR_f4), npt.NDArray[np.float32])
    +
    +assert_type(np.partition(b, 0, axis=None), npt.NDArray[np.bool])
    +assert_type(np.partition(f4, 0, axis=None), npt.NDArray[np.float32])
    +assert_type(np.partition(f, 0, axis=None), npt.NDArray[Any])
    +assert_type(np.partition(AR_b, 0), npt.NDArray[np.bool])
    +assert_type(np.partition(AR_f4, 0), npt.NDArray[np.float32])
    +
    +assert_type(np.argpartition(b, 0), npt.NDArray[np.intp])
    +assert_type(np.argpartition(f4, 0), npt.NDArray[np.intp])
    +assert_type(np.argpartition(f, 0), npt.NDArray[np.intp])
    +assert_type(np.argpartition(AR_b, 0), npt.NDArray[np.intp])
    +assert_type(np.argpartition(AR_f4, 0), npt.NDArray[np.intp])
    +
    +assert_type(np.sort([2, 1], 0), npt.NDArray[Any])
    +assert_type(np.sort(AR_b, 0), npt.NDArray[np.bool])
    +assert_type(np.sort(AR_f4, 0), npt.NDArray[np.float32])
    +
    +assert_type(np.argsort(AR_b, 0), npt.NDArray[np.intp])
    +assert_type(np.argsort(AR_f4, 0), npt.NDArray[np.intp])
    +
    +assert_type(np.argmax(AR_b), np.intp)
    +assert_type(np.argmax(AR_f4), np.intp)
    +assert_type(np.argmax(AR_b, axis=0), Any)
    +assert_type(np.argmax(AR_f4, axis=0), Any)
    +assert_type(np.argmax(AR_f4, out=AR_sub_i), NDArrayIntSubclass)
    +
    +assert_type(np.argmin(AR_b), np.intp)
    +assert_type(np.argmin(AR_f4), np.intp)
    +assert_type(np.argmin(AR_b, axis=0), Any)
    +assert_type(np.argmin(AR_f4, axis=0), Any)
    +assert_type(np.argmin(AR_f4, out=AR_sub_i), NDArrayIntSubclass)
    +
    +assert_type(np.searchsorted(AR_b[0], 0), np.intp)
    +assert_type(np.searchsorted(AR_f4[0], 0), np.intp)
    +assert_type(np.searchsorted(AR_b[0], [0]), npt.NDArray[np.intp])
    +assert_type(np.searchsorted(AR_f4[0], [0]), npt.NDArray[np.intp])
    +
    +assert_type(np.resize(b, (5, 5)), np.ndarray[tuple[int, int], np.dtype[np.bool]])
    +assert_type(np.resize(f4, (5, 5)), np.ndarray[tuple[int, int], np.dtype[np.float32]])
    +assert_type(np.resize(f, (5, 5)), np.ndarray[tuple[int, int], np.dtype])
    +assert_type(np.resize(AR_b, (5, 5)), np.ndarray[tuple[int, int], np.dtype[np.bool]])
    +assert_type(np.resize(AR_f4, (5, 5)), np.ndarray[tuple[int, int], np.dtype[np.float32]])
    +
    +assert_type(np.squeeze(b), np.bool)
    +assert_type(np.squeeze(f4), np.float32)
    +assert_type(np.squeeze(f), npt.NDArray[Any])
    +assert_type(np.squeeze(AR_b), npt.NDArray[np.bool])
    +assert_type(np.squeeze(AR_f4), npt.NDArray[np.float32])
    +
    +assert_type(np.diagonal(AR_b), npt.NDArray[np.bool])
    +assert_type(np.diagonal(AR_f4), npt.NDArray[np.float32])
    +
    +assert_type(np.trace(AR_b), Any)
    +assert_type(np.trace(AR_f4), Any)
    +assert_type(np.trace(AR_f4, out=AR_subclass), NDArraySubclass)
    +assert_type(np.trace(AR_f4, out=AR_subclass, dtype=None), NDArraySubclass)
    +
    +assert_type(np.ravel(b), np.ndarray[tuple[int], np.dtype[np.bool]])
    +assert_type(np.ravel(f4), np.ndarray[tuple[int], np.dtype[np.float32]])
    +assert_type(np.ravel(f), np.ndarray[tuple[int], np.dtype[np.float64 | Any]])
    +assert_type(np.ravel(AR_b), np.ndarray[tuple[int], np.dtype[np.bool]])
    +assert_type(np.ravel(AR_f4), np.ndarray[tuple[int], np.dtype[np.float32]])
    +
    +assert_type(np.nonzero(AR_b), tuple[np.ndarray[tuple[int], np.dtype[np.intp]], ...])
    +assert_type(np.nonzero(AR_f4), tuple[np.ndarray[tuple[int], np.dtype[np.intp]], ...])
    +assert_type(np.nonzero(AR_1d), tuple[np.ndarray[tuple[int], np.dtype[np.intp]], ...])
    +assert_type(np.nonzero(AR_nd), tuple[np.ndarray[tuple[int], np.dtype[np.intp]], ...])
    +
    +assert_type(np.shape(b), tuple[()])
    +assert_type(np.shape(f), tuple[()])
    +assert_type(np.shape([1]), tuple[int])
    +assert_type(np.shape([[2]]), tuple[int, int])
    +assert_type(np.shape([[[3]]]), tuple[Any, ...])
    +assert_type(np.shape(AR_b), tuple[Any, ...])
    +assert_type(np.shape(AR_nd), tuple[Any, ...])
    +# these fail on mypy, but it works as expected with pyright/pylance
    +# assert_type(np.shape(AR_0d), tuple[()])
    +# assert_type(np.shape(AR_1d), tuple[int])
    +# assert_type(np.shape(AR_2d), tuple[int, int])
    +
    +assert_type(np.compress([True], b), npt.NDArray[np.bool])
    +assert_type(np.compress([True], f4), npt.NDArray[np.float32])
    +assert_type(np.compress([True], f), npt.NDArray[Any])
    +assert_type(np.compress([True], AR_b), npt.NDArray[np.bool])
    +assert_type(np.compress([True], AR_f4), npt.NDArray[np.float32])
    +
    +assert_type(np.clip(b, 0, 1.0), np.bool)
    +assert_type(np.clip(f4, -1, 1), np.float32)
    +assert_type(np.clip(f, 0, 1), Any)
    +assert_type(np.clip(AR_b, 0, 1), npt.NDArray[np.bool])
    +assert_type(np.clip(AR_f4, 0, 1), npt.NDArray[np.float32])
    +assert_type(np.clip([0], 0, 1), npt.NDArray[Any])
    +assert_type(np.clip(AR_b, 0, 1, out=AR_subclass), NDArraySubclass)
    +
    +assert_type(np.sum(b), np.bool)
    +assert_type(np.sum(f4), np.float32)
    +assert_type(np.sum(f), Any)
    +assert_type(np.sum(AR_b), np.bool)
    +assert_type(np.sum(AR_f4), np.float32)
    +assert_type(np.sum(AR_b, axis=0), Any)
    +assert_type(np.sum(AR_f4, axis=0), Any)
    +assert_type(np.sum(AR_f4, out=AR_subclass), NDArraySubclass)
    +assert_type(np.sum(AR_f4, dtype=np.float64), np.float64)
    +assert_type(np.sum(AR_f4, None, np.float64), np.float64)
    +assert_type(np.sum(AR_f4, dtype=np.float64, keepdims=False), np.float64)
    +assert_type(np.sum(AR_f4, None, np.float64, keepdims=False), np.float64)
    +assert_type(np.sum(AR_f4, dtype=np.float64, keepdims=True), np.float64 | npt.NDArray[np.float64])
    +assert_type(np.sum(AR_f4, None, np.float64, keepdims=True), np.float64 | npt.NDArray[np.float64])
    +
    +assert_type(np.all(b), np.bool)
    +assert_type(np.all(f4), np.bool)
    +assert_type(np.all(f), np.bool)
    +assert_type(np.all(AR_b), np.bool)
    +assert_type(np.all(AR_f4), np.bool)
    +assert_type(np.all(AR_b, axis=0), Any)
    +assert_type(np.all(AR_f4, axis=0), Any)
    +assert_type(np.all(AR_b, keepdims=True), Any)
    +assert_type(np.all(AR_f4, keepdims=True), Any)
    +assert_type(np.all(AR_f4, out=AR_subclass), NDArraySubclass)
    +
    +assert_type(np.any(b), np.bool)
    +assert_type(np.any(f4), np.bool)
    +assert_type(np.any(f), np.bool)
    +assert_type(np.any(AR_b), np.bool)
    +assert_type(np.any(AR_f4), np.bool)
    +assert_type(np.any(AR_b, axis=0), Any)
    +assert_type(np.any(AR_f4, axis=0), Any)
    +assert_type(np.any(AR_b, keepdims=True), Any)
    +assert_type(np.any(AR_f4, keepdims=True), Any)
    +assert_type(np.any(AR_f4, out=AR_subclass), NDArraySubclass)
    +
    +assert_type(np.cumsum(b), npt.NDArray[np.bool])
    +assert_type(np.cumsum(f4), npt.NDArray[np.float32])
    +assert_type(np.cumsum(f), npt.NDArray[Any])
    +assert_type(np.cumsum(AR_b), npt.NDArray[np.bool])
    +assert_type(np.cumsum(AR_f4), npt.NDArray[np.float32])
    +assert_type(np.cumsum(f, dtype=float), npt.NDArray[Any])
    +assert_type(np.cumsum(f, dtype=np.float64), npt.NDArray[np.float64])
    +assert_type(np.cumsum(AR_f4, out=AR_subclass), NDArraySubclass)
    +
    +assert_type(np.cumulative_sum(b), npt.NDArray[np.bool])
    +assert_type(np.cumulative_sum(f4), npt.NDArray[np.float32])
    +assert_type(np.cumulative_sum(f), npt.NDArray[Any])
    +assert_type(np.cumulative_sum(AR_b), npt.NDArray[np.bool])
    +assert_type(np.cumulative_sum(AR_f4), npt.NDArray[np.float32])
    +assert_type(np.cumulative_sum(f, dtype=float), npt.NDArray[Any])
    +assert_type(np.cumulative_sum(f, dtype=np.float64), npt.NDArray[np.float64])
    +assert_type(np.cumulative_sum(AR_f4, out=AR_subclass), NDArraySubclass)
    +
    +assert_type(np.ptp(b), np.bool)
    +assert_type(np.ptp(f4), np.float32)
    +assert_type(np.ptp(f), Any)
    +assert_type(np.ptp(AR_b), np.bool)
    +assert_type(np.ptp(AR_f4), np.float32)
    +assert_type(np.ptp(AR_b, axis=0), Any)
    +assert_type(np.ptp(AR_f4, axis=0), Any)
    +assert_type(np.ptp(AR_b, keepdims=True), Any)
    +assert_type(np.ptp(AR_f4, keepdims=True), Any)
    +assert_type(np.ptp(AR_f4, out=AR_subclass), NDArraySubclass)
    +
    +assert_type(np.amax(b), np.bool)
    +assert_type(np.amax(f4), np.float32)
    +assert_type(np.amax(f), Any)
    +assert_type(np.amax(AR_b), np.bool)
    +assert_type(np.amax(AR_f4), np.float32)
    +assert_type(np.amax(AR_b, axis=0), Any)
    +assert_type(np.amax(AR_f4, axis=0), Any)
    +assert_type(np.amax(AR_b, keepdims=True), Any)
    +assert_type(np.amax(AR_f4, keepdims=True), Any)
    +assert_type(np.amax(AR_f4, out=AR_subclass), NDArraySubclass)
    +
    +assert_type(np.amin(b), np.bool)
    +assert_type(np.amin(f4), np.float32)
    +assert_type(np.amin(f), Any)
    +assert_type(np.amin(AR_b), np.bool)
    +assert_type(np.amin(AR_f4), np.float32)
    +assert_type(np.amin(AR_b, axis=0), Any)
    +assert_type(np.amin(AR_f4, axis=0), Any)
    +assert_type(np.amin(AR_b, keepdims=True), Any)
    +assert_type(np.amin(AR_f4, keepdims=True), Any)
    +assert_type(np.amin(AR_f4, out=AR_subclass), NDArraySubclass)
    +
    +assert_type(np.prod(AR_b), np.int_)
    +assert_type(np.prod(AR_u8), np.uint64)
    +assert_type(np.prod(AR_i8), np.int64)
    +assert_type(np.prod(AR_f4), np.floating)
    +assert_type(np.prod(AR_c16), np.complexfloating)
    +assert_type(np.prod(AR_O), Any)
    +assert_type(np.prod(AR_f4, axis=0), Any)
    +assert_type(np.prod(AR_f4, keepdims=True), Any)
    +assert_type(np.prod(AR_f4, dtype=np.float64), np.float64)
    +assert_type(np.prod(AR_f4, dtype=float), Any)
    +assert_type(np.prod(AR_f4, out=AR_subclass), NDArraySubclass)
    +
    +assert_type(np.cumprod(AR_b), npt.NDArray[np.int_])
    +assert_type(np.cumprod(AR_u8), npt.NDArray[np.uint64])
    +assert_type(np.cumprod(AR_i8), npt.NDArray[np.int64])
    +assert_type(np.cumprod(AR_f4), npt.NDArray[np.floating])
    +assert_type(np.cumprod(AR_c16), npt.NDArray[np.complexfloating])
    +assert_type(np.cumprod(AR_O), npt.NDArray[np.object_])
    +assert_type(np.cumprod(AR_f4, axis=0), npt.NDArray[np.floating])
    +assert_type(np.cumprod(AR_f4, dtype=np.float64), npt.NDArray[np.float64])
    +assert_type(np.cumprod(AR_f4, dtype=float), npt.NDArray[Any])
    +assert_type(np.cumprod(AR_f4, out=AR_subclass), NDArraySubclass)
    +
    +assert_type(np.cumulative_prod(AR_b), npt.NDArray[np.int_])
    +assert_type(np.cumulative_prod(AR_u8), npt.NDArray[np.uint64])
    +assert_type(np.cumulative_prod(AR_i8), npt.NDArray[np.int64])
    +assert_type(np.cumulative_prod(AR_f4), npt.NDArray[np.floating])
    +assert_type(np.cumulative_prod(AR_c16), npt.NDArray[np.complexfloating])
    +assert_type(np.cumulative_prod(AR_O), npt.NDArray[np.object_])
    +assert_type(np.cumulative_prod(AR_f4, axis=0), npt.NDArray[np.floating])
    +assert_type(np.cumulative_prod(AR_f4, dtype=np.float64), npt.NDArray[np.float64])
    +assert_type(np.cumulative_prod(AR_f4, dtype=float), npt.NDArray[Any])
    +assert_type(np.cumulative_prod(AR_f4, out=AR_subclass), NDArraySubclass)
    +
    +assert_type(np.ndim(b), int)
    +assert_type(np.ndim(f4), int)
    +assert_type(np.ndim(f), int)
    +assert_type(np.ndim(AR_b), int)
    +assert_type(np.ndim(AR_f4), int)
    +
    +assert_type(np.size(b), int)
    +assert_type(np.size(f4), int)
    +assert_type(np.size(f), int)
    +assert_type(np.size(AR_b), int)
    +assert_type(np.size(AR_f4), int)
    +
    +assert_type(np.around(b), np.float16)
    +assert_type(np.around(f), Any)
    +assert_type(np.around(i8), np.int64)
    +assert_type(np.around(f4), np.float32)
    +assert_type(np.around(AR_b), npt.NDArray[np.float16])
    +assert_type(np.around(AR_i8), npt.NDArray[np.int64])
    +assert_type(np.around(AR_f4), npt.NDArray[np.float32])
    +assert_type(np.around([1.5]), npt.NDArray[Any])
    +assert_type(np.around(AR_f4, out=AR_subclass), NDArraySubclass)
    +
    +assert_type(np.mean(AR_b), np.floating)
    +assert_type(np.mean(AR_i8), np.floating)
    +assert_type(np.mean(AR_f4), np.floating)
    +assert_type(np.mean(AR_m), np.timedelta64)
    +assert_type(np.mean(AR_c16), np.complexfloating)
    +assert_type(np.mean(AR_O), Any)
    +assert_type(np.mean(AR_f4, axis=0), Any)
    +assert_type(np.mean(AR_f4, keepdims=True), Any)
    +assert_type(np.mean(AR_f4, dtype=float), Any)
    +assert_type(np.mean(AR_f4, dtype=np.float64), np.float64)
    +assert_type(np.mean(AR_f4, out=AR_subclass), NDArraySubclass)
    +assert_type(np.mean(AR_f4, dtype=np.float64), np.float64)
    +assert_type(np.mean(AR_f4, None, np.float64), np.float64)
    +assert_type(np.mean(AR_f4, dtype=np.float64, keepdims=False), np.float64)
    +assert_type(np.mean(AR_f4, None, np.float64, keepdims=False), np.float64)
    +assert_type(np.mean(AR_f4, dtype=np.float64, keepdims=True), np.float64 | npt.NDArray[np.float64])
    +assert_type(np.mean(AR_f4, None, np.float64, keepdims=True), np.float64 | npt.NDArray[np.float64])
    +
    +assert_type(np.std(AR_b), np.floating)
    +assert_type(np.std(AR_i8), np.floating)
    +assert_type(np.std(AR_f4), np.floating)
    +assert_type(np.std(AR_c16), np.floating)
    +assert_type(np.std(AR_O), Any)
    +assert_type(np.std(AR_f4, axis=0), Any)
    +assert_type(np.std(AR_f4, keepdims=True), Any)
    +assert_type(np.std(AR_f4, dtype=float), Any)
    +assert_type(np.std(AR_f4, dtype=np.float64), np.float64)
    +assert_type(np.std(AR_f4, out=AR_subclass), NDArraySubclass)
    +
    +assert_type(np.var(AR_b), np.floating)
    +assert_type(np.var(AR_i8), np.floating)
    +assert_type(np.var(AR_f4), np.floating)
    +assert_type(np.var(AR_c16), np.floating)
    +assert_type(np.var(AR_O), Any)
    +assert_type(np.var(AR_f4, axis=0), Any)
    +assert_type(np.var(AR_f4, keepdims=True), Any)
    +assert_type(np.var(AR_f4, dtype=float), Any)
    +assert_type(np.var(AR_f4, dtype=np.float64), np.float64)
    +assert_type(np.var(AR_f4, out=AR_subclass), NDArraySubclass)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/getlimits.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/getlimits.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..cc964d75305500669476231af2b9c974107a673c
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/getlimits.pyi
    @@ -0,0 +1,53 @@
    +from typing import assert_type
    +
    +import numpy as np
    +
    +f: float
    +f8: np.float64
    +c8: np.complex64
    +c16: np.complex128
    +
    +i: int
    +i8: np.int64
    +u4: np.uint32
    +
    +finfo_f8: np.finfo[np.float64]
    +iinfo_i8: np.iinfo[np.int64]
    +
    +assert_type(np.finfo(f), np.finfo[np.float64])
    +assert_type(np.finfo(f8), np.finfo[np.float64])
    +assert_type(np.finfo(c8), np.finfo[np.float32])
    +assert_type(np.finfo(c16), np.finfo[np.float64])
    +assert_type(np.finfo("f2"), np.finfo[np.float16])
    +
    +assert_type(finfo_f8.dtype, np.dtype[np.float64])
    +assert_type(finfo_f8.bits, int)
    +assert_type(finfo_f8.eps, np.float64)
    +assert_type(finfo_f8.epsneg, np.float64)
    +assert_type(finfo_f8.iexp, int)
    +assert_type(finfo_f8.machep, int)
    +assert_type(finfo_f8.max, np.float64)
    +assert_type(finfo_f8.maxexp, int)
    +assert_type(finfo_f8.min, np.float64)
    +assert_type(finfo_f8.minexp, int)
    +assert_type(finfo_f8.negep, int)
    +assert_type(finfo_f8.nexp, int)
    +assert_type(finfo_f8.nmant, int)
    +assert_type(finfo_f8.precision, int)
    +assert_type(finfo_f8.resolution, np.float64)
    +assert_type(finfo_f8.tiny, np.float64)
    +assert_type(finfo_f8.smallest_normal, np.float64)
    +assert_type(finfo_f8.smallest_subnormal, np.float64)
    +
    +assert_type(np.iinfo(i), np.iinfo[np.int_])
    +assert_type(np.iinfo(i8), np.iinfo[np.int64])
    +assert_type(np.iinfo(u4), np.iinfo[np.uint32])
    +assert_type(np.iinfo("i2"), np.iinfo[np.int16])
    +assert_type(np.iinfo("u2"), np.iinfo[np.uint16])
    +
    +assert_type(iinfo_i8.dtype, np.dtype[np.int64])
    +assert_type(iinfo_i8.kind, str)
    +assert_type(iinfo_i8.bits, int)
    +assert_type(iinfo_i8.key, str)
    +assert_type(iinfo_i8.min, int)
    +assert_type(iinfo_i8.max, int)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/histograms.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/histograms.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..c1c63d59cb8805237e1d4907f2e90ef6f6ee4cfd
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/histograms.pyi
    @@ -0,0 +1,25 @@
    +from typing import Any, assert_type
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +AR_i8: npt.NDArray[np.int64]
    +AR_f8: npt.NDArray[np.float64]
    +
    +assert_type(np.histogram_bin_edges(AR_i8, bins="auto"), npt.NDArray[Any])
    +assert_type(np.histogram_bin_edges(AR_i8, bins="rice", range=(0, 3)), npt.NDArray[Any])
    +assert_type(np.histogram_bin_edges(AR_i8, bins="scott", weights=AR_f8), npt.NDArray[Any])
    +
    +assert_type(np.histogram(AR_i8, bins="auto"), tuple[npt.NDArray[Any], npt.NDArray[Any]])
    +assert_type(np.histogram(AR_i8, bins="rice", range=(0, 3)), tuple[npt.NDArray[Any], npt.NDArray[Any]])
    +assert_type(np.histogram(AR_i8, bins="scott", weights=AR_f8), tuple[npt.NDArray[Any], npt.NDArray[Any]])
    +assert_type(np.histogram(AR_f8, bins=1, density=True), tuple[npt.NDArray[Any], npt.NDArray[Any]])
    +
    +assert_type(np.histogramdd(AR_i8, bins=[1]),
    +            tuple[npt.NDArray[Any], tuple[npt.NDArray[Any], ...]])
    +assert_type(np.histogramdd(AR_i8, range=[(0, 3)]),
    +            tuple[npt.NDArray[Any], tuple[npt.NDArray[Any], ...]])
    +assert_type(np.histogramdd(AR_i8, weights=AR_f8),
    +            tuple[npt.NDArray[Any], tuple[npt.NDArray[Any], ...]])
    +assert_type(np.histogramdd(AR_f8, density=True),
    +            tuple[npt.NDArray[Any], tuple[npt.NDArray[Any], ...]])
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/index_tricks.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/index_tricks.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..f6067c3bed6bfd0adecab400a1028f40af479b24
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/index_tricks.pyi
    @@ -0,0 +1,70 @@
    +from types import EllipsisType
    +from typing import Any, Literal, assert_type
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +AR_LIKE_b: list[bool]
    +AR_LIKE_i: list[int]
    +AR_LIKE_f: list[float]
    +AR_LIKE_U: list[str]
    +AR_LIKE_O: list[object]
    +
    +AR_i8: npt.NDArray[np.int64]
    +AR_O: npt.NDArray[np.object_]
    +
    +assert_type(np.ndenumerate(AR_i8), np.ndenumerate[np.int64])
    +assert_type(np.ndenumerate(AR_LIKE_f), np.ndenumerate[np.float64])
    +assert_type(np.ndenumerate(AR_LIKE_U), np.ndenumerate[np.str_])
    +assert_type(np.ndenumerate(AR_LIKE_O), np.ndenumerate[Any])
    +
    +assert_type(next(np.ndenumerate(AR_i8)), tuple[tuple[Any, ...], np.int64])
    +assert_type(next(np.ndenumerate(AR_LIKE_f)), tuple[tuple[Any, ...], np.float64])
    +assert_type(next(np.ndenumerate(AR_LIKE_U)), tuple[tuple[Any, ...], np.str_])
    +assert_type(next(np.ndenumerate(AR_LIKE_O)), tuple[tuple[Any, ...], Any])
    +
    +assert_type(iter(np.ndenumerate(AR_i8)), np.ndenumerate[np.int64])
    +assert_type(iter(np.ndenumerate(AR_LIKE_f)), np.ndenumerate[np.float64])
    +assert_type(iter(np.ndenumerate(AR_LIKE_U)), np.ndenumerate[np.str_])
    +assert_type(iter(np.ndenumerate(AR_LIKE_O)), np.ndenumerate[Any])
    +
    +assert_type(np.ndindex(1, 2, 3), np.ndindex)
    +assert_type(np.ndindex((1, 2, 3)), np.ndindex)
    +assert_type(iter(np.ndindex(1, 2, 3)), np.ndindex)
    +assert_type(next(np.ndindex(1, 2, 3)), tuple[Any, ...])
    +
    +assert_type(np.unravel_index([22, 41, 37], (7, 6)), tuple[npt.NDArray[np.intp], ...])
    +assert_type(np.unravel_index([31, 41, 13], (7, 6), order="F"), tuple[npt.NDArray[np.intp], ...])
    +assert_type(np.unravel_index(1621, (6, 7, 8, 9)), tuple[np.intp, ...])
    +
    +assert_type(np.ravel_multi_index([[1]], (7, 6)), npt.NDArray[np.intp])
    +assert_type(np.ravel_multi_index(AR_LIKE_i, (7, 6)), np.intp)
    +assert_type(np.ravel_multi_index(AR_LIKE_i, (7, 6), order="F"), np.intp)
    +assert_type(np.ravel_multi_index(AR_LIKE_i, (4, 6), mode="clip"), np.intp)
    +assert_type(np.ravel_multi_index(AR_LIKE_i, (4, 4), mode=("clip", "wrap")), np.intp)
    +assert_type(np.ravel_multi_index((3, 1, 4, 1), (6, 7, 8, 9)), np.intp)
    +
    +assert_type(np.mgrid[1:1:2], npt.NDArray[Any])
    +assert_type(np.mgrid[1:1:2, None:10], npt.NDArray[Any])
    +
    +assert_type(np.ogrid[1:1:2], tuple[npt.NDArray[Any], ...])
    +assert_type(np.ogrid[1:1:2, None:10], tuple[npt.NDArray[Any], ...])
    +
    +assert_type(np.index_exp[0:1], tuple[slice[int, int, None]])
    +assert_type(np.index_exp[0:1, None:3], tuple[slice[int, int, None], slice[None, int, None]])
    +assert_type(np.index_exp[0, 0:1, ..., [0, 1, 3]], tuple[Literal[0], slice[int, int, None], EllipsisType, list[int]])
    +
    +assert_type(np.s_[0:1], slice[int, int, None])
    +assert_type(np.s_[0:1, None:3], tuple[slice[int, int, None], slice[None, int, None]])
    +assert_type(np.s_[0, 0:1, ..., [0, 1, 3]], tuple[Literal[0], slice[int, int, None], EllipsisType, list[int]])
    +
    +assert_type(np.ix_(AR_LIKE_b), tuple[npt.NDArray[np.bool], ...])
    +assert_type(np.ix_(AR_LIKE_i, AR_LIKE_f), tuple[npt.NDArray[np.float64], ...])
    +assert_type(np.ix_(AR_i8), tuple[npt.NDArray[np.int64], ...])
    +
    +assert_type(np.fill_diagonal(AR_i8, 5), None)
    +
    +assert_type(np.diag_indices(4), tuple[npt.NDArray[np.int_], ...])
    +assert_type(np.diag_indices(2, 3), tuple[npt.NDArray[np.int_], ...])
    +
    +assert_type(np.diag_indices_from(AR_i8), tuple[npt.NDArray[np.int_], ...])
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/lib_function_base.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/lib_function_base.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..345635d06327608f636a0821c57a321a20d53fd4
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/lib_function_base.pyi
    @@ -0,0 +1,409 @@
    +from collections.abc import Callable
    +from fractions import Fraction
    +from typing import Any, LiteralString, assert_type, type_check_only
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +f8: np.float64
    +AR_LIKE_b: list[bool]
    +AR_LIKE_i8: list[int]
    +AR_LIKE_f8: list[float]
    +AR_LIKE_c16: list[complex]
    +AR_LIKE_O: list[Fraction]
    +
    +AR_u1: npt.NDArray[np.uint8]
    +AR_i8: npt.NDArray[np.int64]
    +AR_f2: npt.NDArray[np.float16]
    +AR_f4: npt.NDArray[np.float32]
    +AR_f8: npt.NDArray[np.float64]
    +AR_f10: npt.NDArray[np.longdouble]
    +AR_c8: npt.NDArray[np.complex64]
    +AR_c16: npt.NDArray[np.complex128]
    +AR_c20: npt.NDArray[np.clongdouble]
    +AR_m: npt.NDArray[np.timedelta64]
    +AR_M: npt.NDArray[np.datetime64]
    +AR_O: npt.NDArray[np.object_]
    +AR_b: npt.NDArray[np.bool]
    +AR_U: npt.NDArray[np.str_]
    +CHAR_AR_U: np.char.chararray[tuple[Any, ...], np.dtype[np.str_]]
    +
    +AR_f8_1d: np.ndarray[tuple[int], np.dtype[np.float64]]
    +AR_f8_2d: np.ndarray[tuple[int, int], np.dtype[np.float64]]
    +AR_f8_3d: np.ndarray[tuple[int, int, int], np.dtype[np.float64]]
    +AR_c16_1d: np.ndarray[tuple[int], np.dtype[np.complex128]]
    +
    +AR_b_list: list[npt.NDArray[np.bool]]
    +
    +@type_check_only
    +def func(a: np.ndarray, posarg: bool = ..., /, arg: int = ..., *, kwarg: str = ...) -> np.ndarray: ...
    +@type_check_only
    +def func_f8(a: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]: ...
    +
    +###
    +
    +# vectorize
    +vectorized_func: np.vectorize
    +assert_type(vectorized_func.pyfunc, Callable[..., Any])
    +assert_type(vectorized_func.cache, bool)
    +assert_type(vectorized_func.signature, LiteralString | None)
    +assert_type(vectorized_func.otypes, LiteralString | None)
    +assert_type(vectorized_func.excluded, set[int | str])
    +assert_type(vectorized_func.__doc__, str | None)
    +assert_type(vectorized_func([1]), Any)
    +assert_type(np.vectorize(int), np.vectorize)
    +assert_type(
    +    np.vectorize(int, otypes="i", doc="doc", excluded=(), cache=True, signature=None),
    +    np.vectorize,
    +)
    +
    +# rot90
    +assert_type(np.rot90(AR_f8_1d), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(np.rot90(AR_f8, k=2), npt.NDArray[np.float64])
    +assert_type(np.rot90(AR_LIKE_f8, axes=(0, 1)), np.ndarray)
    +
    +# flip
    +assert_type(np.flip(AR_f8_1d), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(np.flip(AR_f8, axis=(0, 1)), npt.NDArray[np.float64])
    +assert_type(np.flip(AR_LIKE_f8, axis=0), np.ndarray)
    +
    +# iterable
    +assert_type(np.iterable(1), bool)
    +assert_type(np.iterable([1]), bool)
    +
    +# average
    +assert_type(np.average(AR_f8_2d), np.float64)
    +assert_type(np.average(AR_f8_2d, axis=1), npt.NDArray[np.float64])
    +assert_type(np.average(AR_f8_2d, keepdims=True), np.ndarray[tuple[int, int], np.dtype[np.float64]])
    +assert_type(np.average(AR_f8), np.float64)
    +assert_type(np.average(AR_f8, axis=1), npt.NDArray[np.float64])
    +assert_type(np.average(AR_f8, keepdims=True), npt.NDArray[np.float64])
    +assert_type(np.average(AR_f8, returned=True), tuple[np.float64, np.float64])
    +assert_type(np.average(AR_f8, axis=1, returned=True), tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]])
    +assert_type(np.average(AR_f8, keepdims=True, returned=True), tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]])
    +assert_type(np.average(AR_LIKE_f8), np.float64)
    +assert_type(np.average(AR_LIKE_f8, weights=AR_f8), np.float64)
    +assert_type(np.average(AR_LIKE_f8, axis=1), npt.NDArray[np.float64])
    +assert_type(np.average(AR_LIKE_f8, keepdims=True), npt.NDArray[np.float64])
    +assert_type(np.average(AR_LIKE_f8, returned=True), tuple[np.float64, np.float64])
    +assert_type(np.average(AR_LIKE_f8, axis=1, returned=True), tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]])
    +assert_type(np.average(AR_LIKE_f8, keepdims=True, returned=True), tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]])
    +assert_type(np.average(AR_O), Any)
    +assert_type(np.average(AR_O, axis=1), np.ndarray)
    +assert_type(np.average(AR_O, keepdims=True), np.ndarray)
    +assert_type(np.average(AR_O, returned=True), tuple[Any, Any])
    +assert_type(np.average(AR_O, axis=1, returned=True), tuple[np.ndarray, np.ndarray])
    +assert_type(np.average(AR_O, keepdims=True, returned=True), tuple[np.ndarray, np.ndarray])
    +
    +# asarray_chkfinite
    +assert_type(np.asarray_chkfinite(AR_f8_1d), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(np.asarray_chkfinite(AR_f8), npt.NDArray[np.float64])
    +assert_type(np.asarray_chkfinite(AR_LIKE_f8), np.ndarray)
    +assert_type(np.asarray_chkfinite(AR_f8, dtype=np.float64), npt.NDArray[np.float64])
    +assert_type(np.asarray_chkfinite(AR_f8, dtype=float), np.ndarray)
    +
    +# piecewise
    +assert_type(np.piecewise(AR_f8_1d, AR_b, [func]), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(np.piecewise(AR_f8, AR_b, [func]), npt.NDArray[np.float64])
    +assert_type(np.piecewise(AR_f8, AR_b, [func_f8]), npt.NDArray[np.float64])
    +assert_type(np.piecewise(AR_f8, AR_b_list, [func]), npt.NDArray[np.float64])
    +assert_type(np.piecewise(AR_f8, AR_b_list, [func_f8]), npt.NDArray[np.float64])
    +assert_type(np.piecewise(AR_f8, AR_b_list, [func], True, -1, kwarg=""), npt.NDArray[np.float64])
    +assert_type(np.piecewise(AR_f8, AR_b_list, [func], True, arg=-1, kwarg=""), npt.NDArray[np.float64])
    +assert_type(np.piecewise(AR_LIKE_f8, AR_b_list, [func]), np.ndarray)
    +assert_type(np.piecewise(AR_LIKE_f8, AR_b_list, [func_f8]), npt.NDArray[np.float64])
    +
    +# extract
    +assert_type(np.extract(AR_i8, AR_f8), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(np.extract(AR_i8, AR_LIKE_b), np.ndarray[tuple[int], np.dtype[np.bool]])
    +assert_type(np.extract(AR_i8, AR_LIKE_i8), np.ndarray[tuple[int], np.dtype[np.int_]])
    +assert_type(np.extract(AR_i8, AR_LIKE_f8), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(np.extract(AR_i8, AR_LIKE_c16), np.ndarray[tuple[int], np.dtype[np.complex128]])
    +
    +# select
    +assert_type(np.select([AR_b], [AR_f8_1d]), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(np.select([AR_b], [AR_f8]), npt.NDArray[np.float64])
    +
    +# places
    +assert_type(np.place(AR_f8, mask=AR_i8, vals=5.0), None)
    +
    +# copy
    +assert_type(np.copy(AR_LIKE_f8), np.ndarray)
    +assert_type(np.copy(AR_U), npt.NDArray[np.str_])
    +assert_type(np.copy(CHAR_AR_U, "K", subok=True), np.char.chararray[tuple[Any, ...], np.dtype[np.str_]])
    +assert_type(np.copy(CHAR_AR_U, subok=True), np.char.chararray[tuple[Any, ...], np.dtype[np.str_]])
    +# pyright correctly infers `NDArray[str_]` here
    +assert_type(np.copy(CHAR_AR_U), np.ndarray[Any, Any])  # pyright: ignore[reportAssertTypeFailure]
    +
    +# gradient
    +assert_type(np.gradient(AR_f8_1d, 1), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(
    +    np.gradient(AR_f8_2d, [1, 2], [2, 3.5, 4]),
    +    tuple[
    +        np.ndarray[tuple[int, int], np.dtype[np.float64]],
    +        np.ndarray[tuple[int, int], np.dtype[np.float64]],
    +    ],
    +)
    +assert_type(
    +    np.gradient(AR_f8_3d),
    +    tuple[
    +        np.ndarray[tuple[int, int, int], np.dtype[np.float64]],
    +        np.ndarray[tuple[int, int, int], np.dtype[np.float64]],
    +        np.ndarray[tuple[int, int, int], np.dtype[np.float64]],
    +    ],
    +)
    +assert_type(np.gradient(AR_f8), np.ndarray[tuple[int], np.dtype[np.float64]] | Any)
    +assert_type(np.gradient(AR_LIKE_f8, edge_order=2), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(np.gradient(AR_LIKE_c16, axis=0), np.ndarray[tuple[int], np.dtype[np.complex128]])
    +
    +# diff
    +assert_type(np.diff("git", n=0), str)
    +assert_type(np.diff(AR_f8), npt.NDArray[np.float64])
    +assert_type(np.diff(AR_f8_1d, axis=0), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(np.diff(AR_f8_2d, axis=0), np.ndarray[tuple[int, int], np.dtype[np.float64]])
    +assert_type(np.diff(AR_LIKE_f8, prepend=1.5), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(np.diff(AR_c16), npt.NDArray[np.complex128])
    +assert_type(np.diff(AR_c16_1d), np.ndarray[tuple[int], np.dtype[np.complex128]])
    +assert_type(np.diff(AR_LIKE_c16), np.ndarray[tuple[int], np.dtype[np.complex128]])
    +
    +# interp
    +assert_type(np.interp(1, [1], AR_f8), np.float64)
    +assert_type(np.interp(1, [1], [1]), np.float64)
    +assert_type(np.interp(1, [1], AR_c16), np.complex128)
    +assert_type(np.interp(1, [1], [1j]), np.complex128)
    +assert_type(np.interp([1], [1], AR_f8), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(np.interp([1], [1], [1]),  np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(np.interp([1], [1], AR_c16), np.ndarray[tuple[int], np.dtype[np.complex128]])
    +assert_type(np.interp([1], [1], [1j]), np.ndarray[tuple[int], np.dtype[np.complex128]])
    +
    +# angle
    +assert_type(np.angle(1), np.float64)
    +assert_type(np.angle(1, deg=True), np.float64)
    +assert_type(np.angle(1j), np.float64)
    +assert_type(np.angle(f8), np.float64)
    +assert_type(np.angle(AR_b), npt.NDArray[np.float64])
    +assert_type(np.angle(AR_u1), npt.NDArray[np.float64])
    +assert_type(np.angle(AR_i8), npt.NDArray[np.float64])
    +assert_type(np.angle(AR_f2), npt.NDArray[np.float16])
    +assert_type(np.angle(AR_f4), npt.NDArray[np.float32])
    +assert_type(np.angle(AR_c8), npt.NDArray[np.float32])
    +assert_type(np.angle(AR_f8), npt.NDArray[np.float64])
    +assert_type(np.angle(AR_c16), npt.NDArray[np.float64])
    +assert_type(np.angle(AR_f10), npt.NDArray[np.longdouble])
    +assert_type(np.angle(AR_c20), npt.NDArray[np.longdouble])
    +assert_type(np.angle(AR_f8_1d), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(np.angle(AR_c16_1d), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(np.angle(AR_LIKE_b), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(np.angle(AR_LIKE_i8), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(np.angle(AR_LIKE_f8), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(np.angle(AR_LIKE_c16), np.ndarray[tuple[int], np.dtype[np.float64]])
    +
    +# unwrap
    +assert_type(np.unwrap(AR_f2), npt.NDArray[np.float16])
    +assert_type(np.unwrap(AR_f8), npt.NDArray[np.float64])
    +assert_type(np.unwrap(AR_f10), npt.NDArray[np.longdouble])
    +assert_type(np.unwrap(AR_O), npt.NDArray[np.object_])
    +assert_type(np.unwrap(AR_f8_1d), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(np.unwrap(AR_f8_2d), np.ndarray[tuple[int, int], np.dtype[np.float64]])
    +assert_type(np.unwrap(AR_f8_3d), np.ndarray[tuple[int, int, int], np.dtype[np.float64]])
    +assert_type(np.unwrap(AR_LIKE_b), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(np.unwrap(AR_LIKE_i8), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(np.unwrap(AR_LIKE_f8), np.ndarray[tuple[int], np.dtype[np.float64]])
    +
    +# sort_complex
    +assert_type(np.sort_complex(AR_u1), npt.NDArray[np.complex64])
    +assert_type(np.sort_complex(AR_f8), npt.NDArray[np.complex128])
    +assert_type(np.sort_complex(AR_f10), npt.NDArray[np.clongdouble])
    +assert_type(np.sort_complex(AR_f8_1d), np.ndarray[tuple[int], np.dtype[np.complex128]])
    +assert_type(np.sort_complex(AR_c16_1d), np.ndarray[tuple[int], np.dtype[np.complex128]])
    +
    +# trim_zeros
    +assert_type(np.trim_zeros(AR_f8), npt.NDArray[np.float64])
    +assert_type(np.trim_zeros(AR_LIKE_f8), list[float])
    +
    +# cov
    +assert_type(np.cov(AR_f8_1d), np.ndarray[tuple[()], np.dtype[np.float64]])
    +assert_type(np.cov(AR_f8_2d), npt.NDArray[np.float64])
    +assert_type(np.cov(AR_f8), npt.NDArray[np.float64])
    +assert_type(np.cov(AR_f8, AR_f8), np.ndarray[tuple[int, int], np.dtype[np.float64]])
    +assert_type(np.cov(AR_c16, AR_c16), np.ndarray[tuple[int, int], np.dtype[np.complex128]])
    +assert_type(np.cov(AR_LIKE_f8), np.ndarray[tuple[()], np.dtype[np.float64]])
    +assert_type(np.cov(AR_LIKE_f8, AR_LIKE_f8), np.ndarray[tuple[int, int], np.dtype[np.float64]])
    +assert_type(np.cov(AR_LIKE_f8, dtype=np.float16), np.ndarray[tuple[()], np.dtype[np.float16]])
    +assert_type(np.cov(AR_LIKE_f8, AR_LIKE_f8, dtype=np.float32), np.ndarray[tuple[int, int], np.dtype[np.float32]])
    +assert_type(np.cov(AR_f8, AR_f8, dtype=float), np.ndarray[tuple[int, int]])
    +assert_type(np.cov(AR_LIKE_f8, dtype=float), np.ndarray[tuple[()]])
    +assert_type(np.cov(AR_LIKE_f8, AR_LIKE_f8, dtype=float), np.ndarray[tuple[int, int]])
    +
    +# corrcoef
    +assert_type(np.corrcoef(AR_f8_1d), np.float64)
    +assert_type(np.corrcoef(AR_f8_2d), np.ndarray[tuple[int, int], np.dtype[np.float64]] | np.float64)
    +assert_type(np.corrcoef(AR_f8), np.ndarray[tuple[int, int], np.dtype[np.float64]] | np.float64)
    +assert_type(np.corrcoef(AR_f8, AR_f8), np.ndarray[tuple[int, int], np.dtype[np.float64]])
    +assert_type(np.corrcoef(AR_c16, AR_c16), np.ndarray[tuple[int, int], np.dtype[np.complex128]])
    +assert_type(np.corrcoef(AR_LIKE_f8), np.float64)
    +assert_type(np.corrcoef(AR_LIKE_f8, AR_LIKE_f8), np.ndarray[tuple[int, int], np.dtype[np.float64]])
    +assert_type(np.corrcoef(AR_LIKE_f8, dtype=np.float16), np.float16)
    +assert_type(np.corrcoef(AR_LIKE_f8, AR_LIKE_f8, dtype=np.float32), np.ndarray[tuple[int, int], np.dtype[np.float32]])
    +assert_type(np.corrcoef(AR_f8, AR_f8, dtype=float), np.ndarray[tuple[int, int]])
    +assert_type(np.corrcoef(AR_LIKE_f8, dtype=float), Any)
    +assert_type(np.corrcoef(AR_LIKE_f8, AR_LIKE_f8, dtype=float), np.ndarray[tuple[int, int]])
    +
    +# window functions
    +assert_type(np.blackman(5), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(np.bartlett(6), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(np.hanning(4.5), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(np.hamming(0), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(np.kaiser(4, 5.9), np.ndarray[tuple[int], np.dtype[np.float64]])
    +
    +# i0 (bessel function)
    +assert_type(np.i0(AR_i8), npt.NDArray[np.float64])
    +
    +# sinc (cardinal sine function)
    +assert_type(np.sinc(1.0), np.float64)
    +assert_type(np.sinc(1j), np.complex128 | Any)
    +assert_type(np.sinc(AR_f8), npt.NDArray[np.float64])
    +assert_type(np.sinc(AR_c16), npt.NDArray[np.complex128])
    +assert_type(np.sinc(AR_LIKE_f8), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(np.sinc(AR_LIKE_c16), np.ndarray[tuple[int], np.dtype[np.complex128]])
    +
    +# median
    +assert_type(np.median(AR_f8, keepdims=False), np.float64)
    +assert_type(np.median(AR_c16, overwrite_input=True), np.complex128)
    +assert_type(np.median(AR_m), np.timedelta64)
    +assert_type(np.median(AR_O), Any)
    +assert_type(np.median(AR_f8, keepdims=True), npt.NDArray[np.float64])
    +assert_type(np.median(AR_f8, axis=0), npt.NDArray[np.float64])
    +assert_type(np.median(AR_c16, keepdims=True), npt.NDArray[np.complex128])
    +assert_type(np.median(AR_c16, axis=0), npt.NDArray[np.complex128])
    +assert_type(np.median(AR_LIKE_f8, keepdims=True), npt.NDArray[np.float64])
    +assert_type(np.median(AR_LIKE_c16, keepdims=True), npt.NDArray[np.complex128])
    +assert_type(np.median(AR_LIKE_f8, out=AR_c16), npt.NDArray[np.complex128])
    +
    +# percentile
    +assert_type(np.percentile(AR_f8, 50), np.float64)
    +assert_type(np.percentile(AR_f8, 50, axis=1), npt.NDArray[np.float64])
    +assert_type(np.percentile(AR_f8, 50, axis=(1, 0)), npt.NDArray[np.float64])
    +assert_type(np.percentile(AR_f8, 50, keepdims=True), npt.NDArray[np.float64])
    +assert_type(np.percentile(AR_f8, 50, axis=0, keepdims=True), npt.NDArray[np.float64])
    +assert_type(np.percentile(AR_c16, 50), np.complex128)
    +assert_type(np.percentile(AR_m, 50), np.timedelta64)
    +assert_type(np.percentile(AR_M, 50, overwrite_input=True), np.datetime64)
    +assert_type(np.percentile(AR_O, 50), Any)
    +assert_type(np.percentile(AR_f8, [50]), npt.NDArray[np.float64])
    +assert_type(np.percentile(AR_f8, [50], axis=1), npt.NDArray[np.float64])
    +assert_type(np.percentile(AR_f8, [50], keepdims=True), npt.NDArray[np.float64])
    +assert_type(np.percentile(AR_c16, [50]), npt.NDArray[np.complex128])
    +assert_type(np.percentile(AR_m, [50]), npt.NDArray[np.timedelta64])
    +assert_type(np.percentile(AR_M, [50], method="nearest"), npt.NDArray[np.datetime64])
    +assert_type(np.percentile(AR_O, [50]), npt.NDArray[np.object_])
    +assert_type(np.percentile(AR_f8, [50], keepdims=True), npt.NDArray[np.float64])
    +assert_type(np.percentile(AR_f8, [50], out=AR_c16), npt.NDArray[np.complex128])
    +
    +# quantile
    +assert_type(np.quantile(AR_f8, 0.50), np.float64)
    +assert_type(np.quantile(AR_f8, 0.50, axis=1), npt.NDArray[np.float64])
    +assert_type(np.quantile(AR_f8, 0.50, axis=(1, 0)), npt.NDArray[np.float64])
    +assert_type(np.quantile(AR_f8, 0.50, keepdims=True), npt.NDArray[np.float64])
    +assert_type(np.quantile(AR_f8, 0.50, axis=0, keepdims=True), npt.NDArray[np.float64])
    +assert_type(np.quantile(AR_c16, 0.50), np.complex128)
    +assert_type(np.quantile(AR_m, 0.50), np.timedelta64)
    +assert_type(np.quantile(AR_M, 0.50, overwrite_input=True), np.datetime64)
    +assert_type(np.quantile(AR_O, 0.50), Any)
    +assert_type(np.quantile(AR_f8, [0.50]), npt.NDArray[np.float64])
    +assert_type(np.quantile(AR_f8, [0.50], axis=1), npt.NDArray[np.float64])
    +assert_type(np.quantile(AR_f8, [0.50], keepdims=True), npt.NDArray[np.float64])
    +assert_type(np.quantile(AR_c16, [0.50]), npt.NDArray[np.complex128])
    +assert_type(np.quantile(AR_m, [0.50]), npt.NDArray[np.timedelta64])
    +assert_type(np.quantile(AR_M, [0.50], method="nearest"), npt.NDArray[np.datetime64])
    +assert_type(np.quantile(AR_O, [0.50]), npt.NDArray[np.object_])
    +assert_type(np.quantile(AR_f8, [0.50], keepdims=True), npt.NDArray[np.float64])
    +assert_type(np.quantile(AR_f8, [0.50], out=AR_c16), npt.NDArray[np.complex128])
    +
    +# trapezoid
    +assert_type(np.trapezoid(AR_LIKE_f8), np.float64)
    +assert_type(np.trapezoid(AR_LIKE_f8, AR_LIKE_f8), np.float64)
    +assert_type(np.trapezoid(AR_LIKE_c16), np.complex128)
    +assert_type(np.trapezoid(AR_LIKE_c16, AR_LIKE_f8), np.complex128)
    +assert_type(np.trapezoid(AR_LIKE_f8, AR_LIKE_c16), np.complex128)
    +assert_type(np.trapezoid(AR_LIKE_O), float)
    +assert_type(np.trapezoid(AR_LIKE_O, AR_LIKE_f8), float)
    +assert_type(np.trapezoid(AR_f8), np.float64 | npt.NDArray[np.float64])
    +assert_type(np.trapezoid(AR_f8, AR_f8), np.float64 | npt.NDArray[np.float64])
    +assert_type(np.trapezoid(AR_c16), np.complex128 | npt.NDArray[np.complex128])
    +assert_type(np.trapezoid(AR_c16, AR_c16), np.complex128 | npt.NDArray[np.complex128])
    +assert_type(np.trapezoid(AR_m), np.timedelta64 | npt.NDArray[np.timedelta64])
    +assert_type(np.trapezoid(AR_O), npt.NDArray[np.object_] | Any)
    +assert_type(np.trapezoid(AR_O, AR_LIKE_f8), npt.NDArray[np.object_] | Any)
    +
    +# meshgrid
    +assert_type(np.meshgrid(), tuple[()])
    +assert_type(
    +    np.meshgrid(AR_f8),
    +    tuple[
    +        np.ndarray[tuple[int], np.dtype[np.float64]],
    +    ],
    +)
    +assert_type(
    +    np.meshgrid(AR_c16, indexing="ij"),
    +    tuple[
    +        np.ndarray[tuple[int], np.dtype[np.complex128]],
    +    ],
    +)
    +assert_type(
    +    np.meshgrid(AR_i8, AR_f8, copy=False),
    +    tuple[
    +        np.ndarray[tuple[int, int], np.dtype[np.int64]],
    +        np.ndarray[tuple[int, int], np.dtype[np.float64]],
    +    ],
    +)
    +assert_type(
    +    np.meshgrid(AR_LIKE_f8, AR_f8),
    +    tuple[
    +        np.ndarray[tuple[int, int]],
    +        np.ndarray[tuple[int, int], np.dtype[np.float64]],
    +    ],
    +)
    +assert_type(
    +    np.meshgrid(AR_f8, AR_LIKE_f8),
    +    tuple[
    +        np.ndarray[tuple[int, int], np.dtype[np.float64]],
    +        np.ndarray[tuple[int, int]],
    +    ],
    +)
    +assert_type(
    +    np.meshgrid(AR_LIKE_f8, AR_LIKE_f8),
    +    tuple[
    +        np.ndarray[tuple[int, int]],
    +        np.ndarray[tuple[int, int]],
    +    ],
    +)
    +assert_type(
    +    np.meshgrid(AR_f8, AR_i8, AR_c16),
    +    tuple[
    +        np.ndarray[tuple[int, int, int], np.dtype[np.float64]],
    +        np.ndarray[tuple[int, int, int], np.dtype[np.int64]],
    +        np.ndarray[tuple[int, int, int], np.dtype[np.complex128]],
    +    ],
    +)
    +assert_type(np.meshgrid(AR_f8, AR_f8, AR_f8, AR_f8), tuple[npt.NDArray[np.float64], ...])
    +assert_type(np.meshgrid(AR_f8, AR_f8, AR_f8, AR_LIKE_f8), tuple[np.ndarray, ...])
    +assert_type(np.meshgrid(*AR_LIKE_f8), tuple[np.ndarray, ...])
    +
    +# delete
    +assert_type(np.delete(AR_f8, np.s_[:5]), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(np.delete(AR_LIKE_f8, [0, 4, 9], axis=0), np.ndarray)
    +
    +# insert
    +assert_type(np.insert(AR_f8, np.s_[:5], 5), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(np.insert(AR_LIKE_f8, [0, 4, 9], [0.5, 9.2, 7], axis=0), np.ndarray)
    +
    +# append
    +assert_type(np.append(f8, f8), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(np.append(AR_f8, AR_f8), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(np.append(AR_LIKE_f8, AR_LIKE_c16, axis=0), np.ndarray)
    +assert_type(np.append(AR_f8, AR_LIKE_f8, axis=0), np.ndarray)
    +
    +# digitize
    +assert_type(np.digitize(4.5, [1]), np.intp)
    +assert_type(np.digitize(AR_f8, [1, 2, 3]), npt.NDArray[np.intp])
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/lib_polynomial.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/lib_polynomial.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..b7cbadefc610c7a3ebf7a6aeecac1ee7647e973b
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/lib_polynomial.pyi
    @@ -0,0 +1,147 @@
    +from collections.abc import Iterator
    +from typing import Any, NoReturn, assert_type
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +AR_b: npt.NDArray[np.bool]
    +AR_u4: npt.NDArray[np.uint32]
    +AR_i8: npt.NDArray[np.int64]
    +AR_f8: npt.NDArray[np.float64]
    +AR_c16: npt.NDArray[np.complex128]
    +AR_O: npt.NDArray[np.object_]
    +
    +poly_obj: np.poly1d
    +
    +assert_type(poly_obj.variable, str)
    +assert_type(poly_obj.order, int)
    +assert_type(poly_obj.o, int)
    +assert_type(poly_obj.roots, npt.NDArray[Any])
    +assert_type(poly_obj.r, npt.NDArray[Any])
    +assert_type(poly_obj.coeffs, npt.NDArray[Any])
    +assert_type(poly_obj.c, npt.NDArray[Any])
    +assert_type(poly_obj.coef, npt.NDArray[Any])
    +assert_type(poly_obj.coefficients, npt.NDArray[Any])
    +assert_type(poly_obj.__hash__, None)
    +
    +assert_type(poly_obj(1), Any)
    +assert_type(poly_obj([1]), npt.NDArray[Any])
    +assert_type(poly_obj(poly_obj), np.poly1d)
    +
    +assert_type(len(poly_obj), int)
    +assert_type(-poly_obj, np.poly1d)
    +assert_type(+poly_obj, np.poly1d)
    +
    +assert_type(poly_obj * 5, np.poly1d)
    +assert_type(5 * poly_obj, np.poly1d)
    +assert_type(poly_obj + 5, np.poly1d)
    +assert_type(5 + poly_obj, np.poly1d)
    +assert_type(poly_obj - 5, np.poly1d)
    +assert_type(5 - poly_obj, np.poly1d)
    +assert_type(poly_obj**1, np.poly1d)
    +assert_type(poly_obj**1.0, np.poly1d)
    +assert_type(poly_obj / 5, np.poly1d)
    +assert_type(5 / poly_obj, np.poly1d)
    +
    +assert_type(poly_obj[0], Any)
    +poly_obj[0] = 5
    +assert_type(iter(poly_obj), Iterator[Any])
    +assert_type(poly_obj.deriv(), np.poly1d)
    +assert_type(poly_obj.integ(), np.poly1d)
    +
    +assert_type(np.poly(poly_obj), npt.NDArray[np.floating])
    +assert_type(np.poly(AR_f8), npt.NDArray[np.floating])
    +assert_type(np.poly(AR_c16), npt.NDArray[np.floating])
    +
    +assert_type(np.polyint(poly_obj), np.poly1d)
    +assert_type(np.polyint(AR_f8), npt.NDArray[np.floating])
    +assert_type(np.polyint(AR_f8, k=AR_c16), npt.NDArray[np.complexfloating])
    +assert_type(np.polyint(AR_O, m=2), npt.NDArray[np.object_])
    +
    +assert_type(np.polyder(poly_obj), np.poly1d)
    +assert_type(np.polyder(AR_f8), npt.NDArray[np.floating])
    +assert_type(np.polyder(AR_c16), npt.NDArray[np.complexfloating])
    +assert_type(np.polyder(AR_O, m=2), npt.NDArray[np.object_])
    +
    +assert_type(np.polyfit(AR_f8, AR_f8, 2), npt.NDArray[np.float64])
    +assert_type(
    +    np.polyfit(AR_f8, AR_i8, 1, full=True),
    +    tuple[
    +        npt.NDArray[np.float64],
    +        npt.NDArray[np.float64],
    +        npt.NDArray[np.int32],
    +        npt.NDArray[np.float64],
    +        npt.NDArray[np.float64],
    +    ],
    +)
    +assert_type(
    +    np.polyfit(AR_u4, AR_f8, 1.0, cov="unscaled"),
    +    tuple[
    +        npt.NDArray[np.float64],
    +        npt.NDArray[np.float64],
    +    ],
    +)
    +assert_type(np.polyfit(AR_c16, AR_f8, 2), npt.NDArray[np.complex128])
    +assert_type(
    +    np.polyfit(AR_f8, AR_c16, 1, full=True),
    +    tuple[
    +        npt.NDArray[np.complex128],
    +        npt.NDArray[np.float64],
    +        npt.NDArray[np.int32],
    +        npt.NDArray[np.float64],
    +        npt.NDArray[np.float64],
    +    ],
    +)
    +assert_type(
    +    np.polyfit(AR_u4, AR_c16, 1.0, cov=True),
    +    tuple[
    +        npt.NDArray[np.complex128],
    +        npt.NDArray[np.complex128],
    +    ],
    +)
    +
    +assert_type(np.polyval(AR_b, AR_b), npt.NDArray[np.int64])
    +assert_type(np.polyval(AR_u4, AR_b), npt.NDArray[np.unsignedinteger])
    +assert_type(np.polyval(AR_i8, AR_i8), npt.NDArray[np.signedinteger])
    +assert_type(np.polyval(AR_f8, AR_i8), npt.NDArray[np.floating])
    +assert_type(np.polyval(AR_i8, AR_c16), npt.NDArray[np.complexfloating])
    +assert_type(np.polyval(AR_O, AR_O), npt.NDArray[np.object_])
    +
    +assert_type(np.polyadd(poly_obj, AR_i8), np.poly1d)
    +assert_type(np.polyadd(AR_f8, poly_obj), np.poly1d)
    +assert_type(np.polyadd(AR_b, AR_b), npt.NDArray[np.bool])
    +assert_type(np.polyadd(AR_u4, AR_b), npt.NDArray[np.unsignedinteger])
    +assert_type(np.polyadd(AR_i8, AR_i8), npt.NDArray[np.signedinteger])
    +assert_type(np.polyadd(AR_f8, AR_i8), npt.NDArray[np.floating])
    +assert_type(np.polyadd(AR_i8, AR_c16), npt.NDArray[np.complexfloating])
    +assert_type(np.polyadd(AR_O, AR_O), npt.NDArray[np.object_])
    +
    +assert_type(np.polysub(poly_obj, AR_i8), np.poly1d)
    +assert_type(np.polysub(AR_f8, poly_obj), np.poly1d)
    +
    +def test_invalid_polysub() -> None:
    +    assert_type(np.polysub(AR_b, AR_b), NoReturn)
    +
    +assert_type(np.polysub(AR_u4, AR_b), npt.NDArray[np.unsignedinteger])
    +assert_type(np.polysub(AR_i8, AR_i8), npt.NDArray[np.signedinteger])
    +assert_type(np.polysub(AR_f8, AR_i8), npt.NDArray[np.floating])
    +assert_type(np.polysub(AR_i8, AR_c16), npt.NDArray[np.complexfloating])
    +assert_type(np.polysub(AR_O, AR_O), npt.NDArray[np.object_])
    +
    +assert_type(np.polymul(poly_obj, AR_i8), np.poly1d)
    +assert_type(np.polymul(AR_f8, poly_obj), np.poly1d)
    +assert_type(np.polymul(AR_b, AR_b), npt.NDArray[np.bool])
    +assert_type(np.polymul(AR_u4, AR_b), npt.NDArray[np.unsignedinteger])
    +assert_type(np.polymul(AR_i8, AR_i8), npt.NDArray[np.signedinteger])
    +assert_type(np.polymul(AR_f8, AR_i8), npt.NDArray[np.floating])
    +assert_type(np.polymul(AR_i8, AR_c16), npt.NDArray[np.complexfloating])
    +assert_type(np.polymul(AR_O, AR_O), npt.NDArray[np.object_])
    +
    +assert_type(np.polydiv(poly_obj, AR_i8), tuple[np.poly1d, np.poly1d])
    +assert_type(np.polydiv(AR_f8, poly_obj), tuple[np.poly1d, np.poly1d])
    +assert_type(np.polydiv(AR_b, AR_b), tuple[npt.NDArray[np.floating], npt.NDArray[np.floating]])
    +assert_type(np.polydiv(AR_u4, AR_b), tuple[npt.NDArray[np.floating], npt.NDArray[np.floating]])
    +assert_type(np.polydiv(AR_i8, AR_i8), tuple[npt.NDArray[np.floating], npt.NDArray[np.floating]])
    +assert_type(np.polydiv(AR_f8, AR_i8), tuple[npt.NDArray[np.floating], npt.NDArray[np.floating]])
    +assert_type(np.polydiv(AR_i8, AR_c16), tuple[npt.NDArray[np.complexfloating], npt.NDArray[np.complexfloating]])
    +assert_type(np.polydiv(AR_O, AR_O), tuple[npt.NDArray[Any], npt.NDArray[Any]])
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/lib_utils.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/lib_utils.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..c9470e00a3590c00594d87856a232575bc9ff63e
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/lib_utils.pyi
    @@ -0,0 +1,17 @@
    +from io import StringIO
    +from typing import assert_type
    +
    +import numpy as np
    +import numpy.lib.array_utils as array_utils
    +import numpy.typing as npt
    +
    +AR: npt.NDArray[np.float64]
    +AR_DICT: dict[str, npt.NDArray[np.float64]]
    +FILE: StringIO
    +
    +def func(a: int) -> bool: ...
    +
    +assert_type(array_utils.byte_bounds(AR), tuple[int, int])
    +assert_type(array_utils.byte_bounds(np.float64()), tuple[int, int])
    +
    +assert_type(np.info(1, output=FILE), None)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/lib_version.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/lib_version.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..03735375ae3e8945c5f9d8f1cab7c90be60f8cf6
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/lib_version.pyi
    @@ -0,0 +1,20 @@
    +from typing import assert_type
    +
    +from numpy.lib import NumpyVersion
    +
    +version = NumpyVersion("1.8.0")
    +
    +assert_type(version.vstring, str)
    +assert_type(version.version, str)
    +assert_type(version.major, int)
    +assert_type(version.minor, int)
    +assert_type(version.bugfix, int)
    +assert_type(version.pre_release, str)
    +assert_type(version.is_devversion, bool)
    +
    +assert_type(version == version, bool)
    +assert_type(version != version, bool)
    +assert_type(version < "1.8.0", bool)
    +assert_type(version <= version, bool)
    +assert_type(version > version, bool)
    +assert_type(version >= "1.8.0", bool)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/linalg.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/linalg.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..60056516def0e06f168ee5f6c165160bc6c38be6
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/linalg.pyi
    @@ -0,0 +1,154 @@
    +from typing import Any, assert_type
    +
    +import numpy as np
    +import numpy.typing as npt
    +from numpy.linalg._linalg import (
    +    EighResult,
    +    EigResult,
    +    QRResult,
    +    SlogdetResult,
    +    SVDResult,
    +)
    +
    +float_list_2d: list[list[float]]
    +AR_i8: npt.NDArray[np.int64]
    +AR_f4: npt.NDArray[np.float32]
    +AR_f8: npt.NDArray[np.float64]
    +AR_c8: npt.NDArray[np.complex64]
    +AR_c16: npt.NDArray[np.complex128]
    +AR_O: npt.NDArray[np.object_]
    +AR_m: npt.NDArray[np.timedelta64]
    +AR_S: npt.NDArray[np.str_]
    +AR_b: npt.NDArray[np.bool]
    +
    +assert_type(np.linalg.tensorsolve(AR_i8, AR_i8), npt.NDArray[np.float64])
    +assert_type(np.linalg.tensorsolve(AR_i8, AR_f8), npt.NDArray[np.floating])
    +assert_type(np.linalg.tensorsolve(AR_c16, AR_f8), npt.NDArray[np.complexfloating])
    +
    +assert_type(np.linalg.solve(AR_i8, AR_i8), npt.NDArray[np.float64])
    +assert_type(np.linalg.solve(AR_i8, AR_f8), npt.NDArray[np.floating])
    +assert_type(np.linalg.solve(AR_c16, AR_f8), npt.NDArray[np.complexfloating])
    +
    +assert_type(np.linalg.tensorinv(AR_i8), npt.NDArray[np.float64])
    +assert_type(np.linalg.tensorinv(AR_f8), npt.NDArray[np.floating])
    +assert_type(np.linalg.tensorinv(AR_c16), npt.NDArray[np.complexfloating])
    +
    +assert_type(np.linalg.inv(AR_i8), npt.NDArray[np.float64])
    +assert_type(np.linalg.inv(AR_f8), npt.NDArray[np.floating])
    +assert_type(np.linalg.inv(AR_c16), npt.NDArray[np.complexfloating])
    +
    +assert_type(np.linalg.matrix_power(AR_i8, -1), npt.NDArray[Any])
    +assert_type(np.linalg.matrix_power(AR_f8, 0), npt.NDArray[Any])
    +assert_type(np.linalg.matrix_power(AR_c16, 1), npt.NDArray[Any])
    +assert_type(np.linalg.matrix_power(AR_O, 2), npt.NDArray[Any])
    +
    +assert_type(np.linalg.cholesky(AR_i8), npt.NDArray[np.float64])
    +assert_type(np.linalg.cholesky(AR_f8), npt.NDArray[np.floating])
    +assert_type(np.linalg.cholesky(AR_c16), npt.NDArray[np.complexfloating])
    +
    +assert_type(np.linalg.outer(AR_i8, AR_i8), npt.NDArray[np.int64])
    +assert_type(np.linalg.outer(AR_f8, AR_f8), npt.NDArray[np.float64])
    +assert_type(np.linalg.outer(AR_c16, AR_c16), npt.NDArray[np.complex128])
    +assert_type(np.linalg.outer(AR_b, AR_b), npt.NDArray[np.bool])
    +assert_type(np.linalg.outer(AR_O, AR_O), npt.NDArray[np.object_])
    +assert_type(np.linalg.outer(AR_i8, AR_m), npt.NDArray[np.timedelta64])
    +
    +assert_type(np.linalg.qr(AR_i8), QRResult)
    +assert_type(np.linalg.qr(AR_f8), QRResult)
    +assert_type(np.linalg.qr(AR_c16), QRResult)
    +
    +assert_type(np.linalg.eigvals(AR_i8), npt.NDArray[np.float64] | npt.NDArray[np.complex128])
    +assert_type(np.linalg.eigvals(AR_f8), npt.NDArray[np.floating] | npt.NDArray[np.complexfloating])
    +assert_type(np.linalg.eigvals(AR_c16), npt.NDArray[np.complexfloating])
    +
    +assert_type(np.linalg.eigvalsh(AR_i8), npt.NDArray[np.float64])
    +assert_type(np.linalg.eigvalsh(AR_f8), npt.NDArray[np.floating])
    +assert_type(np.linalg.eigvalsh(AR_c16), npt.NDArray[np.floating])
    +
    +assert_type(np.linalg.eig(AR_i8), EigResult)
    +assert_type(np.linalg.eig(AR_f8), EigResult)
    +assert_type(np.linalg.eig(AR_c16), EigResult)
    +
    +assert_type(np.linalg.eigh(AR_i8), EighResult)
    +assert_type(np.linalg.eigh(AR_f8), EighResult)
    +assert_type(np.linalg.eigh(AR_c16), EighResult)
    +
    +assert_type(np.linalg.svd(AR_i8), SVDResult)
    +assert_type(np.linalg.svd(AR_f8), SVDResult)
    +assert_type(np.linalg.svd(AR_c16), SVDResult)
    +assert_type(np.linalg.svd(AR_i8, compute_uv=False), npt.NDArray[np.float64])
    +assert_type(np.linalg.svd(AR_i8, True, False), npt.NDArray[np.float64])
    +assert_type(np.linalg.svd(AR_f8, compute_uv=False), npt.NDArray[np.floating])
    +assert_type(np.linalg.svd(AR_c16, compute_uv=False), npt.NDArray[np.floating])
    +assert_type(np.linalg.svd(AR_c16, True, False), npt.NDArray[np.floating])
    +
    +assert_type(np.linalg.svdvals(AR_b), npt.NDArray[np.float64])
    +assert_type(np.linalg.svdvals(AR_i8), npt.NDArray[np.float64])
    +assert_type(np.linalg.svdvals(AR_f4), npt.NDArray[np.float32])
    +assert_type(np.linalg.svdvals(AR_c8), npt.NDArray[np.float32])
    +assert_type(np.linalg.svdvals(AR_f8), npt.NDArray[np.float64])
    +assert_type(np.linalg.svdvals(AR_c16), npt.NDArray[np.float64])
    +assert_type(np.linalg.svdvals([[1, 2], [3, 4]]), npt.NDArray[np.float64])
    +assert_type(np.linalg.svdvals([[1.0, 2.0], [3.0, 4.0]]), npt.NDArray[np.float64])
    +assert_type(np.linalg.svdvals([[1j, 2j], [3j, 4j]]), npt.NDArray[np.float64])
    +
    +assert_type(np.linalg.cond(AR_i8), Any)
    +assert_type(np.linalg.cond(AR_f8), Any)
    +assert_type(np.linalg.cond(AR_c16), Any)
    +
    +assert_type(np.linalg.matrix_rank(AR_i8), Any)
    +assert_type(np.linalg.matrix_rank(AR_f8), Any)
    +assert_type(np.linalg.matrix_rank(AR_c16), Any)
    +
    +assert_type(np.linalg.pinv(AR_i8), npt.NDArray[np.float64])
    +assert_type(np.linalg.pinv(AR_f8), npt.NDArray[np.floating])
    +assert_type(np.linalg.pinv(AR_c16), npt.NDArray[np.complexfloating])
    +
    +assert_type(np.linalg.slogdet(AR_i8), SlogdetResult)
    +assert_type(np.linalg.slogdet(AR_f8), SlogdetResult)
    +assert_type(np.linalg.slogdet(AR_c16), SlogdetResult)
    +
    +assert_type(np.linalg.det(AR_i8), Any)
    +assert_type(np.linalg.det(AR_f8), Any)
    +assert_type(np.linalg.det(AR_c16), Any)
    +
    +assert_type(np.linalg.lstsq(AR_i8, AR_i8), tuple[npt.NDArray[np.float64], npt.NDArray[np.float64], np.int32, npt.NDArray[np.float64]])
    +assert_type(np.linalg.lstsq(AR_i8, AR_f8), tuple[npt.NDArray[np.floating], npt.NDArray[np.floating], np.int32, npt.NDArray[np.floating]])
    +assert_type(np.linalg.lstsq(AR_f8, AR_c16), tuple[npt.NDArray[np.complexfloating], npt.NDArray[np.floating], np.int32, npt.NDArray[np.floating]])
    +
    +assert_type(np.linalg.norm(AR_i8), np.floating)
    +assert_type(np.linalg.norm(AR_f8), np.floating)
    +assert_type(np.linalg.norm(AR_c16), np.floating)
    +assert_type(np.linalg.norm(AR_S), np.floating)
    +assert_type(np.linalg.norm(AR_f8, axis=0), Any)
    +
    +assert_type(np.linalg.matrix_norm(AR_i8), np.floating)
    +assert_type(np.linalg.matrix_norm(AR_f8), np.floating)
    +assert_type(np.linalg.matrix_norm(AR_c16), np.floating)
    +assert_type(np.linalg.matrix_norm(AR_S), np.floating)
    +
    +assert_type(np.linalg.vector_norm(AR_i8), np.floating)
    +assert_type(np.linalg.vector_norm(AR_f8), np.floating)
    +assert_type(np.linalg.vector_norm(AR_c16), np.floating)
    +assert_type(np.linalg.vector_norm(AR_S), np.floating)
    +
    +assert_type(np.linalg.tensordot(AR_b, AR_b), npt.NDArray[np.bool])
    +assert_type(np.linalg.tensordot(AR_i8, AR_i8), npt.NDArray[np.int64])
    +assert_type(np.linalg.tensordot(AR_f8, AR_f8), npt.NDArray[np.float64])
    +assert_type(np.linalg.tensordot(AR_c16, AR_c16), npt.NDArray[np.complex128])
    +assert_type(np.linalg.tensordot(AR_m, AR_m), npt.NDArray[np.timedelta64])
    +assert_type(np.linalg.tensordot(AR_O, AR_O), npt.NDArray[np.object_])
    +
    +assert_type(np.linalg.multi_dot([AR_i8, AR_i8]), Any)
    +assert_type(np.linalg.multi_dot([AR_i8, AR_f8]), Any)
    +assert_type(np.linalg.multi_dot([AR_f8, AR_c16]), Any)
    +assert_type(np.linalg.multi_dot([AR_O, AR_O]), Any)
    +assert_type(np.linalg.multi_dot([AR_m, AR_m]), Any)
    +
    +assert_type(np.linalg.cross(AR_i8, AR_i8), npt.NDArray[np.signedinteger])
    +assert_type(np.linalg.cross(AR_f8, AR_f8), npt.NDArray[np.floating])
    +assert_type(np.linalg.cross(AR_c16, AR_c16), npt.NDArray[np.complexfloating])
    +
    +assert_type(np.linalg.matmul(AR_i8, AR_i8), npt.NDArray[np.int64])
    +assert_type(np.linalg.matmul(AR_f8, AR_f8), npt.NDArray[np.float64])
    +assert_type(np.linalg.matmul(AR_c16, AR_c16), npt.NDArray[np.complex128])
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/ma.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/ma.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..8eef32ddd59375fd2ae1dd7044c0ecd950227f69
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/ma.pyi
    @@ -0,0 +1,1098 @@
    +from typing import Any, Generic, Literal, NoReturn, TypeAlias, TypeVar, assert_type
    +
    +import numpy as np
    +from numpy import dtype, generic
    +from numpy._typing import NDArray, _AnyShape
    +
    +_ScalarT = TypeVar("_ScalarT", bound=generic)
    +_ScalarT_co = TypeVar("_ScalarT_co", bound=generic, covariant=True)
    +
    +MaskedArray: TypeAlias = np.ma.MaskedArray[_AnyShape, dtype[_ScalarT]]
    +_NoMaskType: TypeAlias = np.bool[Literal[False]]
    +_Array1D: TypeAlias = np.ndarray[tuple[int], np.dtype[_ScalarT]]
    +
    +class MaskedArraySubclass(MaskedArray[_ScalarT_co]): ...
    +
    +class IntoMaskedArraySubClass(Generic[_ScalarT_co]):
    +    def __array__(self) -> MaskedArraySubclass[_ScalarT_co]: ...
    +
    +MaskedArraySubclassC: TypeAlias = MaskedArraySubclass[np.complex128]
    +
    +AR_b: NDArray[np.bool]
    +AR_f4: NDArray[np.float32]
    +AR_i8: NDArray[np.int64]
    +AR_u4: NDArray[np.uint32]
    +AR_dt64: NDArray[np.datetime64]
    +AR_td64: NDArray[np.timedelta64]
    +AR_o: NDArray[np.timedelta64]
    +
    +AR_LIKE_b: list[bool]
    +AR_LIKE_u: list[np.uint32]
    +AR_LIKE_i: list[int]
    +AR_LIKE_f: list[float]
    +AR_LIKE_c: list[complex]
    +AR_LIKE_td64: list[np.timedelta64]
    +AR_LIKE_dt64: list[np.datetime64]
    +AR_LIKE_o: list[np.object_]
    +AR_number: NDArray[np.number]
    +
    +MAR_c8: MaskedArray[np.complex64]
    +MAR_c16: MaskedArray[np.complex128]
    +MAR_b: MaskedArray[np.bool]
    +MAR_f4: MaskedArray[np.float32]
    +MAR_f8: MaskedArray[np.float64]
    +MAR_i8: MaskedArray[np.int64]
    +MAR_u4: MaskedArray[np.uint32]
    +MAR_dt64: MaskedArray[np.datetime64]
    +MAR_td64: MaskedArray[np.timedelta64]
    +MAR_o: MaskedArray[np.object_]
    +MAR_s: MaskedArray[np.str_]
    +MAR_byte: MaskedArray[np.bytes_]
    +MAR_V: MaskedArray[np.void]
    +MAR_floating: MaskedArray[np.floating]
    +MAR_number: MaskedArray[np.number]
    +
    +MAR_subclass: MaskedArraySubclassC
    +MAR_into_subclass: IntoMaskedArraySubClass[np.float32]
    +
    +MAR_1d: np.ma.MaskedArray[tuple[int], np.dtype]
    +MAR_2d_f4: np.ma.MaskedArray[tuple[int, int], np.dtype[np.float32]]
    +MAR_2d_V: np.ma.MaskedArray[tuple[int, int], np.dtype[np.void]]
    +
    +b: np.bool
    +f4: np.float32
    +f: float
    +i: int
    +
    +assert_type(MAR_1d.shape, tuple[int])
    +
    +assert_type(MAR_f4.dtype, np.dtype[np.float32])
    +
    +assert_type(int(MAR_i8), int)
    +assert_type(float(MAR_f4), float)
    +
    +assert_type(np.ma.min(MAR_b), np.bool)
    +assert_type(np.ma.min(MAR_f4), np.float32)
    +assert_type(np.ma.min(MAR_b, axis=0), Any)
    +assert_type(np.ma.min(MAR_f4, axis=0), Any)
    +assert_type(np.ma.min(MAR_b, keepdims=True), Any)
    +assert_type(np.ma.min(MAR_f4, keepdims=True), Any)
    +assert_type(np.ma.min(MAR_f4, out=MAR_subclass), MaskedArraySubclassC)
    +assert_type(np.ma.min(MAR_f4, 0, MAR_subclass), MaskedArraySubclassC)
    +assert_type(np.ma.min(MAR_f4, None, MAR_subclass), MaskedArraySubclassC)
    +
    +assert_type(MAR_b.min(), np.bool)
    +assert_type(MAR_f4.min(), np.float32)
    +assert_type(MAR_b.min(axis=0), Any)
    +assert_type(MAR_f4.min(axis=0), Any)
    +assert_type(MAR_b.min(keepdims=True), Any)
    +assert_type(MAR_f4.min(keepdims=True), Any)
    +assert_type(MAR_f4.min(out=MAR_subclass), MaskedArraySubclassC)
    +assert_type(MAR_f4.min(0, MAR_subclass), MaskedArraySubclassC)
    +assert_type(MAR_f4.min(None, MAR_subclass), MaskedArraySubclassC)
    +
    +assert_type(np.ma.max(MAR_b), np.bool)
    +assert_type(np.ma.max(MAR_f4), np.float32)
    +assert_type(np.ma.max(MAR_b, axis=0), Any)
    +assert_type(np.ma.max(MAR_f4, axis=0), Any)
    +assert_type(np.ma.max(MAR_b, keepdims=True), Any)
    +assert_type(np.ma.max(MAR_f4, keepdims=True), Any)
    +assert_type(np.ma.max(MAR_f4, out=MAR_subclass), MaskedArraySubclassC)
    +assert_type(np.ma.max(MAR_f4, 0, MAR_subclass), MaskedArraySubclassC)
    +assert_type(np.ma.max(MAR_f4, None, MAR_subclass), MaskedArraySubclassC)
    +
    +assert_type(MAR_b.max(), np.bool)
    +assert_type(MAR_f4.max(), np.float32)
    +assert_type(MAR_b.max(axis=0), Any)
    +assert_type(MAR_f4.max(axis=0), Any)
    +assert_type(MAR_b.max(keepdims=True), Any)
    +assert_type(MAR_f4.max(keepdims=True), Any)
    +assert_type(MAR_f4.max(out=MAR_subclass), MaskedArraySubclassC)
    +assert_type(MAR_f4.max(0, MAR_subclass), MaskedArraySubclassC)
    +assert_type(MAR_f4.max(None, MAR_subclass), MaskedArraySubclassC)
    +
    +assert_type(np.ma.ptp(MAR_b), np.bool)
    +assert_type(np.ma.ptp(MAR_f4), np.float32)
    +assert_type(np.ma.ptp(MAR_b, axis=0), Any)
    +assert_type(np.ma.ptp(MAR_f4, axis=0), Any)
    +assert_type(np.ma.ptp(MAR_b, keepdims=True), Any)
    +assert_type(np.ma.ptp(MAR_f4, keepdims=True), Any)
    +assert_type(np.ma.ptp(MAR_f4, out=MAR_subclass), MaskedArraySubclassC)
    +assert_type(np.ma.ptp(MAR_f4, 0, MAR_subclass), MaskedArraySubclassC)
    +assert_type(np.ma.ptp(MAR_f4, None, MAR_subclass), MaskedArraySubclassC)
    +
    +assert_type(MAR_b.ptp(), np.bool)
    +assert_type(MAR_f4.ptp(), np.float32)
    +assert_type(MAR_b.ptp(axis=0), Any)
    +assert_type(MAR_f4.ptp(axis=0), Any)
    +assert_type(MAR_b.ptp(keepdims=True), Any)
    +assert_type(MAR_f4.ptp(keepdims=True), Any)
    +assert_type(MAR_f4.ptp(out=MAR_subclass), MaskedArraySubclassC)
    +assert_type(MAR_f4.ptp(0, MAR_subclass), MaskedArraySubclassC)
    +assert_type(MAR_f4.ptp(None, MAR_subclass), MaskedArraySubclassC)
    +
    +assert_type(MAR_b.argmin(), np.intp)
    +assert_type(MAR_f4.argmin(), np.intp)
    +assert_type(MAR_f4.argmax(fill_value=6.28318, keepdims=False), np.intp)
    +assert_type(MAR_b.argmin(axis=0), Any)
    +assert_type(MAR_f4.argmin(axis=0), Any)
    +assert_type(MAR_b.argmin(keepdims=True), Any)
    +assert_type(MAR_f4.argmin(out=MAR_subclass), MaskedArraySubclassC)
    +assert_type(MAR_f4.argmin(None, None, out=MAR_subclass), MaskedArraySubclassC)
    +
    +assert_type(np.ma.argmin(MAR_b), np.intp)
    +assert_type(np.ma.argmin(MAR_f4), np.intp)
    +assert_type(np.ma.argmin(MAR_f4, fill_value=6.28318, keepdims=False), np.intp)
    +assert_type(np.ma.argmin(MAR_b, axis=0), Any)
    +assert_type(np.ma.argmin(MAR_f4, axis=0), Any)
    +assert_type(np.ma.argmin(MAR_b, keepdims=True), Any)
    +assert_type(np.ma.argmin(MAR_f4, out=MAR_subclass), MaskedArraySubclassC)
    +assert_type(np.ma.argmin(MAR_f4, None, None, out=MAR_subclass), MaskedArraySubclassC)
    +
    +assert_type(MAR_b.argmax(), np.intp)
    +assert_type(MAR_f4.argmax(), np.intp)
    +assert_type(MAR_f4.argmax(fill_value=6.28318, keepdims=False), np.intp)
    +assert_type(MAR_b.argmax(axis=0), Any)
    +assert_type(MAR_f4.argmax(axis=0), Any)
    +assert_type(MAR_b.argmax(keepdims=True), Any)
    +assert_type(MAR_f4.argmax(out=MAR_subclass), MaskedArraySubclassC)
    +assert_type(MAR_f4.argmax(None, None, out=MAR_subclass), MaskedArraySubclassC)
    +
    +assert_type(np.ma.argmax(MAR_b), np.intp)
    +assert_type(np.ma.argmax(MAR_f4), np.intp)
    +assert_type(np.ma.argmax(MAR_f4, fill_value=6.28318, keepdims=False), np.intp)
    +assert_type(np.ma.argmax(MAR_b, axis=0), Any)
    +assert_type(np.ma.argmax(MAR_f4, axis=0), Any)
    +assert_type(np.ma.argmax(MAR_b, keepdims=True), Any)
    +assert_type(np.ma.argmax(MAR_f4, out=MAR_subclass), MaskedArraySubclassC)
    +assert_type(np.ma.argmax(MAR_f4, None, None, out=MAR_subclass), MaskedArraySubclassC)
    +
    +assert_type(MAR_b.all(), np.bool)
    +assert_type(MAR_f4.all(), np.bool)
    +assert_type(MAR_f4.all(keepdims=False), np.bool)
    +assert_type(MAR_b.all(axis=0), np.bool | MaskedArray[np.bool])
    +assert_type(MAR_b.all(axis=0, keepdims=True), MaskedArray[np.bool])
    +assert_type(MAR_b.all(0, None, True), MaskedArray[np.bool])
    +assert_type(MAR_f4.all(axis=0), np.bool | MaskedArray[np.bool])
    +assert_type(MAR_b.all(keepdims=True), MaskedArray[np.bool])
    +assert_type(MAR_f4.all(out=MAR_subclass), MaskedArraySubclassC)
    +assert_type(MAR_f4.all(None, out=MAR_subclass), MaskedArraySubclassC)
    +
    +assert_type(MAR_b.any(), np.bool)
    +assert_type(MAR_f4.any(), np.bool)
    +assert_type(MAR_f4.any(keepdims=False), np.bool)
    +assert_type(MAR_b.any(axis=0), np.bool | MaskedArray[np.bool])
    +assert_type(MAR_b.any(axis=0, keepdims=True), MaskedArray[np.bool])
    +assert_type(MAR_b.any(0, None, True), MaskedArray[np.bool])
    +assert_type(MAR_f4.any(axis=0), np.bool | MaskedArray[np.bool])
    +assert_type(MAR_b.any(keepdims=True), MaskedArray[np.bool])
    +assert_type(MAR_f4.any(out=MAR_subclass), MaskedArraySubclassC)
    +assert_type(MAR_f4.any(None, out=MAR_subclass), MaskedArraySubclassC)
    +
    +assert_type(MAR_f4.sort(), None)
    +assert_type(MAR_f4.sort(axis=0, kind="quicksort", order="K", endwith=False, fill_value=42., stable=False), None)
    +
    +assert_type(np.ma.sort(MAR_f4), MaskedArray[np.float32])
    +assert_type(np.ma.sort(MAR_subclass), MaskedArraySubclassC)
    +assert_type(np.ma.sort([[0, 1], [2, 3]]), NDArray[Any])
    +assert_type(np.ma.sort(AR_f4), NDArray[np.float32])
    +
    +assert_type(MAR_f8.take(0), np.float64)
    +assert_type(MAR_1d.take(0), Any)
    +assert_type(MAR_f8.take([0]), MaskedArray[np.float64])
    +assert_type(MAR_f8.take(0, out=MAR_subclass), MaskedArraySubclassC)
    +assert_type(MAR_f8.take([0], out=MAR_subclass), MaskedArraySubclassC)
    +
    +assert_type(np.ma.take(f, 0), Any)
    +assert_type(np.ma.take(f4, 0), np.float32)
    +assert_type(np.ma.take(MAR_f8, 0), np.float64)
    +assert_type(np.ma.take(AR_f4, 0), np.float32)
    +assert_type(np.ma.take(MAR_1d, 0), Any)
    +assert_type(np.ma.take(MAR_f8, [0]), MaskedArray[np.float64])
    +assert_type(np.ma.take(AR_f4, [0]), MaskedArray[np.float32])
    +assert_type(np.ma.take(MAR_f8, 0, out=MAR_subclass), MaskedArraySubclassC)
    +assert_type(np.ma.take(MAR_f8, [0], out=MAR_subclass), MaskedArraySubclassC)
    +assert_type(np.ma.take([1], [0]), MaskedArray[Any])
    +assert_type(np.ma.take(np.eye(2), 1, axis=0), MaskedArray[np.float64])
    +
    +assert_type(MAR_f4.partition(1), None)
    +assert_type(MAR_V.partition(1, axis=0, kind="introselect", order="K"), None)
    +
    +assert_type(MAR_f4.argpartition(1), MaskedArray[np.intp])
    +assert_type(MAR_1d.argpartition(1, axis=0, kind="introselect", order="K"), MaskedArray[np.intp])
    +
    +assert_type(np.ma.ndim(f4), int)
    +assert_type(np.ma.ndim(MAR_b), int)
    +assert_type(np.ma.ndim(AR_f4), int)
    +
    +assert_type(np.ma.size(b), int)
    +assert_type(np.ma.size(MAR_f4, axis=0), int)
    +assert_type(np.ma.size(AR_f4), int)
    +
    +assert_type(np.ma.is_masked(MAR_f4), bool)
    +
    +assert_type(MAR_f4.ids(), tuple[int, int])
    +
    +assert_type(MAR_f4.iscontiguous(), bool)
    +
    +assert_type(MAR_f4 >= 3, MaskedArray[np.bool])
    +assert_type(MAR_i8 >= AR_td64, MaskedArray[np.bool])
    +assert_type(MAR_b >= AR_td64, MaskedArray[np.bool])
    +assert_type(MAR_td64 >= AR_td64, MaskedArray[np.bool])
    +assert_type(MAR_dt64 >= AR_dt64, MaskedArray[np.bool])
    +assert_type(MAR_o >= AR_o, MaskedArray[np.bool])
    +assert_type(MAR_1d >= 0, MaskedArray[np.bool])
    +assert_type(MAR_s >= MAR_s, MaskedArray[np.bool])
    +assert_type(MAR_byte >= MAR_byte, MaskedArray[np.bool])
    +
    +assert_type(MAR_f4 > 3, MaskedArray[np.bool])
    +assert_type(MAR_i8 > AR_td64, MaskedArray[np.bool])
    +assert_type(MAR_b > AR_td64, MaskedArray[np.bool])
    +assert_type(MAR_td64 > AR_td64, MaskedArray[np.bool])
    +assert_type(MAR_dt64 > AR_dt64, MaskedArray[np.bool])
    +assert_type(MAR_o > AR_o, MaskedArray[np.bool])
    +assert_type(MAR_1d > 0, MaskedArray[np.bool])
    +assert_type(MAR_s > MAR_s, MaskedArray[np.bool])
    +assert_type(MAR_byte > MAR_byte, MaskedArray[np.bool])
    +
    +assert_type(MAR_f4 <= 3, MaskedArray[np.bool])
    +assert_type(MAR_i8 <= AR_td64, MaskedArray[np.bool])
    +assert_type(MAR_b <= AR_td64, MaskedArray[np.bool])
    +assert_type(MAR_td64 <= AR_td64, MaskedArray[np.bool])
    +assert_type(MAR_dt64 <= AR_dt64, MaskedArray[np.bool])
    +assert_type(MAR_o <= AR_o, MaskedArray[np.bool])
    +assert_type(MAR_1d <= 0, MaskedArray[np.bool])
    +assert_type(MAR_s <= MAR_s, MaskedArray[np.bool])
    +assert_type(MAR_byte <= MAR_byte, MaskedArray[np.bool])
    +
    +assert_type(MAR_f4 < 3, MaskedArray[np.bool])
    +assert_type(MAR_i8 < AR_td64, MaskedArray[np.bool])
    +assert_type(MAR_b < AR_td64, MaskedArray[np.bool])
    +assert_type(MAR_td64 < AR_td64, MaskedArray[np.bool])
    +assert_type(MAR_dt64 < AR_dt64, MaskedArray[np.bool])
    +assert_type(MAR_o < AR_o, MaskedArray[np.bool])
    +assert_type(MAR_1d < 0, MaskedArray[np.bool])
    +assert_type(MAR_s < MAR_s, MaskedArray[np.bool])
    +assert_type(MAR_byte < MAR_byte, MaskedArray[np.bool])
    +
    +assert_type(MAR_f4 <= 3, MaskedArray[np.bool])
    +assert_type(MAR_i8 <= AR_td64, MaskedArray[np.bool])
    +assert_type(MAR_b <= AR_td64, MaskedArray[np.bool])
    +assert_type(MAR_td64 <= AR_td64, MaskedArray[np.bool])
    +assert_type(MAR_dt64 <= AR_dt64, MaskedArray[np.bool])
    +assert_type(MAR_o <= AR_o, MaskedArray[np.bool])
    +assert_type(MAR_1d <= 0, MaskedArray[np.bool])
    +assert_type(MAR_s <= MAR_s, MaskedArray[np.bool])
    +assert_type(MAR_byte <= MAR_byte, MaskedArray[np.bool])
    +
    +assert_type(MAR_byte.count(), int)
    +assert_type(MAR_f4.count(axis=None), int)
    +assert_type(MAR_f4.count(axis=0), NDArray[np.int_])
    +assert_type(MAR_b.count(axis=(0, 1)), NDArray[np.int_])
    +assert_type(MAR_o.count(keepdims=True), NDArray[np.int_])
    +assert_type(MAR_o.count(axis=None, keepdims=True), NDArray[np.int_])
    +assert_type(MAR_o.count(None, True), NDArray[np.int_])
    +
    +assert_type(np.ma.count(MAR_byte), int)
    +assert_type(np.ma.count(MAR_byte, axis=None), int)
    +assert_type(np.ma.count(MAR_f4, axis=0), NDArray[np.int_])
    +assert_type(np.ma.count(MAR_b, axis=(0, 1)), NDArray[np.int_])
    +assert_type(np.ma.count(MAR_o, keepdims=True), NDArray[np.int_])
    +assert_type(np.ma.count(MAR_o, axis=None, keepdims=True), NDArray[np.int_])
    +assert_type(np.ma.count(MAR_o, None, True), NDArray[np.int_])
    +
    +assert_type(MAR_f4.compressed(), np.ndarray[tuple[int], np.dtype[np.float32]])
    +
    +assert_type(MAR_f4.compress([True, False]), np.ma.MaskedArray[tuple[int], np.dtype[np.float32]])
    +assert_type(MAR_f4.compress([True, False], axis=0), MaskedArray[np.float32])
    +assert_type(MAR_f4.compress([True, False], axis=0, out=MAR_subclass), MaskedArraySubclassC)
    +assert_type(MAR_f4.compress([True, False], 0, MAR_subclass), MaskedArraySubclassC)
    +
    +assert_type(np.ma.compressed(MAR_i8), np.ndarray[tuple[int], np.dtype[np.int64]])
    +assert_type(np.ma.compressed([[1, 2, 3]]), np.ndarray[tuple[int], np.dtype])
    +
    +assert_type(MAR_f4.put([0, 4, 8], [10, 20, 30]), None)
    +assert_type(MAR_f4.put(4, 999), None)
    +assert_type(MAR_f4.put(4, 999, mode="clip"), None)
    +
    +assert_type(MAR_c8.__array_wrap__(AR_b), MaskedArray[np.bool])
    +
    +assert_type(np.ma.put(MAR_f4, [0, 4, 8], [10, 20, 30]), None)
    +assert_type(np.ma.put(MAR_f4, 4, 999), None)
    +assert_type(np.ma.put(MAR_f4, 4, 999, mode="clip"), None)
    +
    +assert_type(np.ma.putmask(MAR_f4, [True, False], [0, 1]), None)
    +assert_type(np.ma.putmask(MAR_f4, np.False_, [0, 1]), None)
    +
    +assert_type(MAR_f4.filled(float("nan")), NDArray[np.float32])
    +assert_type(MAR_i8.filled(), NDArray[np.int64])
    +assert_type(MAR_1d.filled(), np.ndarray[tuple[int], np.dtype])
    +
    +assert_type(np.ma.filled(MAR_f4, float("nan")), NDArray[np.float32])
    +assert_type(np.ma.filled([[1, 2, 3]]), NDArray[Any])
    +# PyRight detects this one correctly, but mypy doesn't.
    +# https://github.com/numpy/numpy/pull/28742#discussion_r2048968375
    +assert_type(np.ma.filled(MAR_1d), np.ndarray[tuple[int], np.dtype])  # type: ignore[assert-type]
    +
    +assert_type(MAR_b.repeat(3), np.ma.MaskedArray[tuple[int], np.dtype[np.bool]])
    +assert_type(MAR_2d_f4.repeat(MAR_i8), np.ma.MaskedArray[tuple[int], np.dtype[np.float32]])
    +assert_type(MAR_2d_f4.repeat(MAR_i8, axis=None), np.ma.MaskedArray[tuple[int], np.dtype[np.float32]])
    +assert_type(MAR_2d_f4.repeat(MAR_i8, axis=0), MaskedArray[np.float32])
    +
    +assert_type(np.ma.allequal(AR_f4, MAR_f4), bool)
    +assert_type(np.ma.allequal(AR_f4, MAR_f4, fill_value=False), bool)
    +
    +assert_type(np.ma.allclose(AR_f4, MAR_f4), bool)
    +assert_type(np.ma.allclose(AR_f4, MAR_f4, masked_equal=False), bool)
    +assert_type(np.ma.allclose(AR_f4, MAR_f4, rtol=.4, atol=.3), bool)
    +
    +assert_type(MAR_2d_f4.ravel(), np.ma.MaskedArray[tuple[int], np.dtype[np.float32]])
    +assert_type(MAR_1d.ravel(order="A"), np.ma.MaskedArray[tuple[int], np.dtype[Any]])
    +
    +assert_type(np.ma.getmask(MAR_f4), NDArray[np.bool] | _NoMaskType)
    +# PyRight detects this one correctly, but mypy doesn't:
    +# `Revealed type is "Union[numpy.ndarray[Any, Any], numpy.bool[Any]]"`
    +assert_type(np.ma.getmask(MAR_1d), np.ndarray[tuple[int], np.dtype[np.bool]] | np.bool)  # type: ignore[assert-type]
    +assert_type(np.ma.getmask(MAR_2d_f4), np.ndarray[tuple[int, int], np.dtype[np.bool]] | _NoMaskType)
    +assert_type(np.ma.getmask([1, 2]), NDArray[np.bool] | _NoMaskType)
    +assert_type(np.ma.getmask(np.int64(1)), _NoMaskType)
    +
    +assert_type(np.ma.is_mask(MAR_1d), bool)
    +assert_type(np.ma.is_mask(AR_b), bool)
    +
    +def func(x: object) -> None:
    +    if np.ma.is_mask(x):
    +        assert_type(x, NDArray[np.bool])
    +    else:
    +        assert_type(x, object)
    +
    +assert_type(MAR_2d_f4.mT, np.ma.MaskedArray[tuple[int, int], np.dtype[np.float32]])
    +
    +assert_type(MAR_c16.real, MaskedArray[np.float64])
    +assert_type(MAR_c16.imag, MaskedArray[np.float64])
    +
    +assert_type(MAR_2d_f4.baseclass, type[NDArray[Any]])
    +
    +assert_type(MAR_b.swapaxes(0, 1), MaskedArray[np.bool])
    +assert_type(MAR_2d_f4.swapaxes(1, 0), np.ma.MaskedArray[tuple[int, int], np.dtype[np.float32]])
    +
    +assert_type(MAR_2d_f4[AR_i8], MaskedArray[np.float32])
    +assert_type(MAR_2d_f4[[1, 2, 3]], MaskedArray[np.float32])
    +assert_type(MAR_2d_f4[1:], MaskedArray[np.float32])
    +assert_type(MAR_2d_f4[:], MaskedArray[np.float32])
    +assert_type(MAR_2d_f4[0, 0], Any)
    +assert_type(MAR_2d_f4[:, np.newaxis], MaskedArray[np.float32])
    +assert_type(MAR_2d_f4[..., -1], MaskedArray[np.float32])
    +assert_type(MAR_2d_V["field_0"], np.ma.MaskedArray[tuple[int, int], np.dtype])
    +assert_type(MAR_2d_V[["field_0", "field_1"]], np.ma.MaskedArray[tuple[int, int], np.dtype[np.void]])
    +
    +assert_type(np.ma.nomask, np.bool[Literal[False]])
    +assert_type(np.ma.MaskType, type[np.bool])
    +
    +assert_type(MAR_1d.__setmask__([True, False]), None)
    +assert_type(MAR_1d.__setmask__(np.False_), None)
    +
    +assert_type(MAR_2d_f4.harden_mask(), np.ma.MaskedArray[tuple[int, int], np.dtype[np.float32]])
    +assert_type(MAR_i8.harden_mask(), MaskedArray[np.int64])
    +assert_type(MAR_2d_f4.soften_mask(), np.ma.MaskedArray[tuple[int, int], np.dtype[np.float32]])
    +assert_type(MAR_i8.soften_mask(), MaskedArray[np.int64])
    +assert_type(MAR_f4.unshare_mask(), MaskedArray[np.float32])
    +assert_type(MAR_b.shrink_mask(), MaskedArray[np.bool_])
    +
    +assert_type(MAR_i8.hardmask, bool)
    +assert_type(MAR_i8.sharedmask, bool)
    +
    +assert_type(MAR_i8.recordmask, np.ma.MaskType | NDArray[np.ma.MaskType])
    +assert_type(MAR_2d_f4.recordmask, np.ma.MaskType | np.ndarray[tuple[int, int], np.dtype[np.ma.MaskType]])
    +
    +assert_type(MAR_2d_f4.anom(), np.ma.MaskedArray[tuple[int, int], np.dtype[np.float32]])
    +assert_type(MAR_2d_f4.anom(axis=0, dtype=np.float16), np.ma.MaskedArray[tuple[int, int], np.dtype])
    +assert_type(MAR_2d_f4.anom(0, np.float16), np.ma.MaskedArray[tuple[int, int], np.dtype])
    +assert_type(MAR_2d_f4.anom(0, "float16"), np.ma.MaskedArray[tuple[int, int], np.dtype])
    +
    +assert_type(MAR_i8.fill_value, np.int64)
    +
    +assert_type(MAR_b.transpose(), MaskedArray[np.bool])
    +assert_type(MAR_2d_f4.transpose(), np.ma.MaskedArray[tuple[int, int], np.dtype[np.float32]])
    +assert_type(MAR_2d_f4.transpose(1, 0), np.ma.MaskedArray[tuple[int, int], np.dtype[np.float32]])
    +assert_type(MAR_2d_f4.transpose((1, 0)), np.ma.MaskedArray[tuple[int, int], np.dtype[np.float32]])
    +assert_type(MAR_b.T, MaskedArray[np.bool])
    +assert_type(MAR_2d_f4.T, np.ma.MaskedArray[tuple[int, int], np.dtype[np.float32]])
    +
    +assert_type(MAR_2d_f4.dot(1), MaskedArray[Any])
    +assert_type(MAR_2d_f4.dot([1]), MaskedArray[Any])
    +assert_type(MAR_2d_f4.dot(1, out=MAR_subclass), MaskedArraySubclassC)
    +
    +assert_type(MAR_2d_f4.nonzero(), tuple[_Array1D[np.intp], ...])
    +assert_type(MAR_2d_f4.nonzero()[0], _Array1D[np.intp])
    +
    +assert_type(MAR_f8.trace(), Any)
    +assert_type(MAR_f8.trace(out=MAR_subclass), MaskedArraySubclassC)
    +assert_type(MAR_f8.trace(out=MAR_subclass, dtype=None), MaskedArraySubclassC)
    +
    +assert_type(MAR_f8.round(), MaskedArray[np.float64])
    +assert_type(MAR_f8.round(out=MAR_subclass), MaskedArraySubclassC)
    +
    +assert_type(MAR_i8.reshape(None), MaskedArray[np.int64])
    +assert_type(MAR_f8.reshape(-1), np.ma.MaskedArray[tuple[int], np.dtype[np.float64]])
    +assert_type(MAR_c8.reshape(2, 3, 4, 5), np.ma.MaskedArray[tuple[int, int, int, int], np.dtype[np.complex64]])
    +assert_type(MAR_td64.reshape(()), np.ma.MaskedArray[tuple[()], np.dtype[np.timedelta64]])
    +assert_type(MAR_s.reshape([]), np.ma.MaskedArray[tuple[()], np.dtype[np.str_]])
    +assert_type(MAR_V.reshape((480, 720, 4)), np.ma.MaskedArray[tuple[int, int, int], np.dtype[np.void]])
    +
    +assert_type(MAR_f8.cumprod(), MaskedArray[Any])
    +assert_type(MAR_f8.cumprod(out=MAR_subclass), MaskedArraySubclassC)
    +
    +assert_type(MAR_f8.cumsum(), MaskedArray[Any])
    +assert_type(MAR_f8.cumsum(out=MAR_subclass), MaskedArraySubclassC)
    +
    +assert_type(MAR_f8.view(), MaskedArray[np.float64])
    +assert_type(MAR_f8.view(dtype=np.float32), MaskedArray[np.float32])
    +assert_type(MAR_f8.view(dtype=np.dtype(np.float32)), MaskedArray[np.float32])
    +assert_type(MAR_f8.view(dtype=np.float32, fill_value=0), MaskedArray[np.float32])
    +assert_type(MAR_f8.view(type=np.ndarray), np.ndarray[Any, Any])
    +assert_type(MAR_f8.view(None, np.ndarray), np.ndarray[Any, Any])
    +assert_type(MAR_f8.view(dtype=np.ndarray), np.ndarray[Any, Any])
    +assert_type(MAR_f8.view(dtype="float32"), MaskedArray[Any])
    +assert_type(MAR_f8.view(dtype="float32", type=np.ndarray), np.ndarray[Any, Any])
    +assert_type(MAR_2d_f4.view(dtype=np.float16), np.ma.MaskedArray[tuple[int, int], np.dtype[np.float16]])
    +assert_type(MAR_2d_f4.view(dtype=np.dtype(np.float16)), np.ma.MaskedArray[tuple[int, int], np.dtype[np.float16]])
    +
    +assert_type(MAR_f8.__deepcopy__(), MaskedArray[np.float64])
    +
    +assert_type(MAR_f8.argsort(), MaskedArray[np.intp])
    +assert_type(MAR_f8.argsort(axis=0, kind="heap", order=("x", "y")), MaskedArray[np.intp])
    +assert_type(MAR_f8.argsort(endwith=True, fill_value=1.5, stable=False), MaskedArray[np.intp])
    +
    +assert_type(MAR_2d_f4.flat, np.ma.core.MaskedIterator[tuple[int, int], np.dtype[np.float32]])
    +assert_type(MAR_2d_f4.flat.ma, np.ma.MaskedArray[tuple[int, int], np.dtype[np.float32]])
    +assert_type(MAR_2d_f4.flat[AR_i8], MaskedArray[np.float32])
    +assert_type(MAR_2d_f4.flat[[1, 2, 3]], MaskedArray[np.float32])
    +assert_type(MAR_2d_f4.flat[1:], MaskedArray[np.float32])
    +assert_type(MAR_2d_f4.flat[:], MaskedArray[np.float32])
    +assert_type(MAR_2d_f4.flat[0, 0], Any)
    +assert_type(MAR_2d_f4.flat[:, np.newaxis], MaskedArray[np.float32])
    +assert_type(MAR_2d_f4.flat[..., -1], MaskedArray[np.float32])
    +
    +def invalid_resize() -> None:
    +    assert_type(MAR_f8.resize((1, 1)), NoReturn)  # type: ignore[arg-type]
    +
    +assert_type(np.ma.MaskedArray(AR_f4), MaskedArray[np.float32])
    +assert_type(np.ma.MaskedArray(np.array([1, 2, 3]), [True, True, False], np.float16), MaskedArray[np.float16])
    +assert_type(np.ma.MaskedArray(np.array([1, 2, 3]), dtype=np.float16), MaskedArray[np.float16])
    +assert_type(np.ma.MaskedArray(np.array([1, 2, 3]), copy=True), MaskedArray[Any])
    +# TODO: This one could be made more precise, the return type could be `MaskedArraySubclassC`
    +assert_type(np.ma.MaskedArray(MAR_subclass), MaskedArray[np.complex128])
    +# TODO: This one could be made more precise, the return type could be `MaskedArraySubclass[np.float32]`
    +assert_type(np.ma.MaskedArray(MAR_into_subclass), MaskedArray[np.float32])
    +
    +# Masked Array addition
    +
    +assert_type(MAR_b + AR_LIKE_u, MaskedArray[np.uint32])
    +assert_type(MAR_b + AR_LIKE_i, MaskedArray[np.signedinteger])
    +assert_type(MAR_b + AR_LIKE_f, MaskedArray[np.floating])
    +assert_type(MAR_b + AR_LIKE_c, MaskedArray[np.complexfloating])
    +assert_type(MAR_b + AR_LIKE_td64, MaskedArray[np.timedelta64])
    +assert_type(MAR_b + AR_LIKE_o, Any)
    +
    +assert_type(AR_LIKE_u + MAR_b, MaskedArray[np.uint32])
    +assert_type(AR_LIKE_i + MAR_b, MaskedArray[np.signedinteger])
    +assert_type(AR_LIKE_f + MAR_b, MaskedArray[np.floating])
    +assert_type(AR_LIKE_c + MAR_b, MaskedArray[np.complexfloating])
    +assert_type(AR_LIKE_td64 + MAR_b, MaskedArray[np.timedelta64])
    +assert_type(AR_LIKE_dt64 + MAR_b, MaskedArray[np.datetime64])
    +assert_type(AR_LIKE_o + MAR_b, Any)
    +
    +assert_type(MAR_u4 + AR_LIKE_b, MaskedArray[np.uint32])
    +assert_type(MAR_u4 + AR_LIKE_u, MaskedArray[np.unsignedinteger])
    +assert_type(MAR_u4 + AR_LIKE_i, MaskedArray[np.signedinteger])
    +assert_type(MAR_u4 + AR_LIKE_f, MaskedArray[np.floating])
    +assert_type(MAR_u4 + AR_LIKE_c, MaskedArray[np.complexfloating])
    +assert_type(MAR_u4 + AR_LIKE_td64, MaskedArray[np.timedelta64])
    +assert_type(MAR_u4 + AR_LIKE_o, Any)
    +
    +assert_type(AR_LIKE_b + MAR_u4, MaskedArray[np.uint32])
    +assert_type(AR_LIKE_u + MAR_u4, MaskedArray[np.unsignedinteger])
    +assert_type(AR_LIKE_i + MAR_u4, MaskedArray[np.signedinteger])
    +assert_type(AR_LIKE_f + MAR_u4, MaskedArray[np.floating])
    +assert_type(AR_LIKE_c + MAR_u4, MaskedArray[np.complexfloating])
    +assert_type(AR_LIKE_td64 + MAR_u4, MaskedArray[np.timedelta64])
    +assert_type(AR_LIKE_dt64 + MAR_u4, MaskedArray[np.datetime64])
    +assert_type(AR_LIKE_o + MAR_u4, Any)
    +
    +assert_type(MAR_i8 + AR_LIKE_b, MaskedArray[np.int64])
    +assert_type(MAR_i8 + AR_LIKE_u, MaskedArray[np.signedinteger])
    +assert_type(MAR_i8 + AR_LIKE_i, MaskedArray[np.signedinteger])
    +assert_type(MAR_i8 + AR_LIKE_f, MaskedArray[np.floating])
    +assert_type(MAR_i8 + AR_LIKE_c, MaskedArray[np.complexfloating])
    +assert_type(MAR_i8 + AR_LIKE_td64, MaskedArray[np.timedelta64])
    +assert_type(MAR_i8 + AR_LIKE_o, Any)
    +
    +assert_type(AR_LIKE_b + MAR_i8, MaskedArray[np.int64])
    +assert_type(AR_LIKE_u + MAR_i8, MaskedArray[np.signedinteger])
    +assert_type(AR_LIKE_i + MAR_i8, MaskedArray[np.signedinteger])
    +assert_type(AR_LIKE_f + MAR_i8, MaskedArray[np.floating])
    +assert_type(AR_LIKE_c + MAR_i8, MaskedArray[np.complexfloating])
    +assert_type(AR_LIKE_td64 + MAR_i8, MaskedArray[np.timedelta64])
    +assert_type(AR_LIKE_dt64 + MAR_i8, MaskedArray[np.datetime64])
    +assert_type(AR_LIKE_o + MAR_i8, Any)
    +
    +assert_type(MAR_f8 + AR_LIKE_b, MaskedArray[np.float64])
    +assert_type(MAR_f8 + AR_LIKE_u, MaskedArray[np.float64])
    +assert_type(MAR_f8 + AR_LIKE_i, MaskedArray[np.float64])
    +assert_type(MAR_f8 + AR_LIKE_f, MaskedArray[np.float64])
    +assert_type(MAR_f8 + AR_LIKE_c, MaskedArray[np.complexfloating])
    +assert_type(MAR_f8 + AR_LIKE_o, Any)
    +
    +assert_type(AR_LIKE_b + MAR_f8, MaskedArray[np.float64])
    +assert_type(AR_LIKE_u + MAR_f8, MaskedArray[np.float64])
    +assert_type(AR_LIKE_i + MAR_f8, MaskedArray[np.float64])
    +assert_type(AR_LIKE_f + MAR_f8, MaskedArray[np.float64])
    +assert_type(AR_LIKE_c + MAR_f8, MaskedArray[np.complexfloating])
    +assert_type(AR_LIKE_o + MAR_f8, Any)
    +
    +assert_type(MAR_c16 + AR_LIKE_b, MaskedArray[np.complex128])
    +assert_type(MAR_c16 + AR_LIKE_u, MaskedArray[np.complex128])
    +assert_type(MAR_c16 + AR_LIKE_i, MaskedArray[np.complex128])
    +assert_type(MAR_c16 + AR_LIKE_f, MaskedArray[np.complex128])
    +assert_type(MAR_c16 + AR_LIKE_c, MaskedArray[np.complex128])
    +assert_type(MAR_c16 + AR_LIKE_o, Any)
    +
    +assert_type(AR_LIKE_b + MAR_c16, MaskedArray[np.complex128])
    +assert_type(AR_LIKE_u + MAR_c16, MaskedArray[np.complex128])
    +assert_type(AR_LIKE_i + MAR_c16, MaskedArray[np.complex128])
    +assert_type(AR_LIKE_f + MAR_c16, MaskedArray[np.complex128])
    +assert_type(AR_LIKE_c + MAR_c16, MaskedArray[np.complex128])
    +assert_type(AR_LIKE_o + MAR_c16, Any)
    +
    +assert_type(MAR_td64 + AR_LIKE_b, MaskedArray[np.timedelta64])
    +assert_type(MAR_td64 + AR_LIKE_u, MaskedArray[np.timedelta64])
    +assert_type(MAR_td64 + AR_LIKE_i, MaskedArray[np.timedelta64])
    +assert_type(MAR_td64 + AR_LIKE_td64, MaskedArray[np.timedelta64])
    +assert_type(MAR_td64 + AR_LIKE_o, Any)
    +
    +assert_type(AR_LIKE_b + MAR_td64, MaskedArray[np.timedelta64])
    +assert_type(AR_LIKE_u + MAR_td64, MaskedArray[np.timedelta64])
    +assert_type(AR_LIKE_i + MAR_td64, MaskedArray[np.timedelta64])
    +assert_type(AR_LIKE_td64 + MAR_td64, MaskedArray[np.timedelta64])
    +assert_type(AR_LIKE_dt64 + MAR_td64, MaskedArray[np.datetime64])
    +assert_type(AR_LIKE_o + MAR_td64, Any)
    +
    +assert_type(MAR_dt64 + AR_LIKE_b, MaskedArray[np.datetime64])
    +assert_type(MAR_dt64 + AR_LIKE_u, MaskedArray[np.datetime64])
    +assert_type(MAR_dt64 + AR_LIKE_i, MaskedArray[np.datetime64])
    +assert_type(MAR_dt64 + AR_LIKE_td64, MaskedArray[np.datetime64])
    +assert_type(MAR_dt64 + AR_LIKE_o, Any)
    +
    +assert_type(AR_LIKE_o + MAR_dt64, Any)
    +
    +assert_type(MAR_o + AR_LIKE_b, Any)
    +assert_type(MAR_o + AR_LIKE_u, Any)
    +assert_type(MAR_o + AR_LIKE_i, Any)
    +assert_type(MAR_o + AR_LIKE_f, Any)
    +assert_type(MAR_o + AR_LIKE_c, Any)
    +assert_type(MAR_o + AR_LIKE_td64, Any)
    +assert_type(MAR_o + AR_LIKE_dt64, Any)
    +assert_type(MAR_o + AR_LIKE_o, Any)
    +
    +assert_type(AR_LIKE_b + MAR_o, Any)
    +assert_type(AR_LIKE_u + MAR_o, Any)
    +assert_type(AR_LIKE_i + MAR_o, Any)
    +assert_type(AR_LIKE_f + MAR_o, Any)
    +assert_type(AR_LIKE_c + MAR_o, Any)
    +assert_type(AR_LIKE_td64 + MAR_o, Any)
    +assert_type(AR_LIKE_dt64 + MAR_o, Any)
    +assert_type(AR_LIKE_o + MAR_o, Any)
    +
    +# Masked Array subtraction
    +# Keep in sync with numpy/typing/tests/data/reveal/arithmetic.pyi
    +
    +assert_type(MAR_number - AR_number, MaskedArray[np.number])
    +
    +assert_type(MAR_b - AR_LIKE_u, MaskedArray[np.uint32])
    +assert_type(MAR_b - AR_LIKE_i, MaskedArray[np.signedinteger])
    +assert_type(MAR_b - AR_LIKE_f, MaskedArray[np.floating])
    +assert_type(MAR_b - AR_LIKE_c, MaskedArray[np.complexfloating])
    +assert_type(MAR_b - AR_LIKE_td64, MaskedArray[np.timedelta64])
    +assert_type(MAR_b - AR_LIKE_o, Any)
    +
    +assert_type(AR_LIKE_u - MAR_b, MaskedArray[np.uint32])
    +assert_type(AR_LIKE_i - MAR_b, MaskedArray[np.signedinteger])
    +assert_type(AR_LIKE_f - MAR_b, MaskedArray[np.floating])
    +assert_type(AR_LIKE_c - MAR_b, MaskedArray[np.complexfloating])
    +assert_type(AR_LIKE_td64 - MAR_b, MaskedArray[np.timedelta64])
    +assert_type(AR_LIKE_dt64 - MAR_b, MaskedArray[np.datetime64])
    +assert_type(AR_LIKE_o - MAR_b, Any)
    +
    +assert_type(MAR_u4 - AR_LIKE_b, MaskedArray[np.uint32])
    +assert_type(MAR_u4 - AR_LIKE_u, MaskedArray[np.unsignedinteger])
    +assert_type(MAR_u4 - AR_LIKE_i, MaskedArray[np.signedinteger])
    +assert_type(MAR_u4 - AR_LIKE_f, MaskedArray[np.floating])
    +assert_type(MAR_u4 - AR_LIKE_c, MaskedArray[np.complexfloating])
    +assert_type(MAR_u4 - AR_LIKE_td64, MaskedArray[np.timedelta64])
    +assert_type(MAR_u4 - AR_LIKE_o, Any)
    +
    +assert_type(AR_LIKE_b - MAR_u4, MaskedArray[np.uint32])
    +assert_type(AR_LIKE_u - MAR_u4, MaskedArray[np.unsignedinteger])
    +assert_type(AR_LIKE_i - MAR_u4, MaskedArray[np.signedinteger])
    +assert_type(AR_LIKE_f - MAR_u4, MaskedArray[np.floating])
    +assert_type(AR_LIKE_c - MAR_u4, MaskedArray[np.complexfloating])
    +assert_type(AR_LIKE_td64 - MAR_u4, MaskedArray[np.timedelta64])
    +assert_type(AR_LIKE_dt64 - MAR_u4, MaskedArray[np.datetime64])
    +assert_type(AR_LIKE_o - MAR_u4, Any)
    +
    +assert_type(MAR_i8 - AR_LIKE_b, MaskedArray[np.int64])
    +assert_type(MAR_i8 - AR_LIKE_u, MaskedArray[np.signedinteger])
    +assert_type(MAR_i8 - AR_LIKE_i, MaskedArray[np.signedinteger])
    +assert_type(MAR_i8 - AR_LIKE_f, MaskedArray[np.floating])
    +assert_type(MAR_i8 - AR_LIKE_c, MaskedArray[np.complexfloating])
    +assert_type(MAR_i8 - AR_LIKE_td64, MaskedArray[np.timedelta64])
    +assert_type(MAR_i8 - AR_LIKE_o, Any)
    +
    +assert_type(AR_LIKE_b - MAR_i8, MaskedArray[np.int64])
    +assert_type(AR_LIKE_u - MAR_i8, MaskedArray[np.signedinteger])
    +assert_type(AR_LIKE_i - MAR_i8, MaskedArray[np.signedinteger])
    +assert_type(AR_LIKE_f - MAR_i8, MaskedArray[np.floating])
    +assert_type(AR_LIKE_c - MAR_i8, MaskedArray[np.complexfloating])
    +assert_type(AR_LIKE_td64 - MAR_i8, MaskedArray[np.timedelta64])
    +assert_type(AR_LIKE_dt64 - MAR_i8, MaskedArray[np.datetime64])
    +assert_type(AR_LIKE_o - MAR_i8, Any)
    +
    +assert_type(MAR_f8 - AR_LIKE_b, MaskedArray[np.float64])
    +assert_type(MAR_f8 - AR_LIKE_u, MaskedArray[np.float64])
    +assert_type(MAR_f8 - AR_LIKE_i, MaskedArray[np.float64])
    +assert_type(MAR_f8 - AR_LIKE_f, MaskedArray[np.float64])
    +assert_type(MAR_f8 - AR_LIKE_c, MaskedArray[np.complexfloating])
    +assert_type(MAR_f8 - AR_LIKE_o, Any)
    +
    +assert_type(AR_LIKE_b - MAR_f8, MaskedArray[np.float64])
    +assert_type(AR_LIKE_u - MAR_f8, MaskedArray[np.float64])
    +assert_type(AR_LIKE_i - MAR_f8, MaskedArray[np.float64])
    +assert_type(AR_LIKE_f - MAR_f8, MaskedArray[np.float64])
    +assert_type(AR_LIKE_c - MAR_f8, MaskedArray[np.complexfloating])
    +assert_type(AR_LIKE_o - MAR_f8, Any)
    +
    +assert_type(MAR_c16 - AR_LIKE_b, MaskedArray[np.complex128])
    +assert_type(MAR_c16 - AR_LIKE_u, MaskedArray[np.complex128])
    +assert_type(MAR_c16 - AR_LIKE_i, MaskedArray[np.complex128])
    +assert_type(MAR_c16 - AR_LIKE_f, MaskedArray[np.complex128])
    +assert_type(MAR_c16 - AR_LIKE_c, MaskedArray[np.complex128])
    +assert_type(MAR_c16 - AR_LIKE_o, Any)
    +
    +assert_type(AR_LIKE_b - MAR_c16, MaskedArray[np.complex128])
    +assert_type(AR_LIKE_u - MAR_c16, MaskedArray[np.complex128])
    +assert_type(AR_LIKE_i - MAR_c16, MaskedArray[np.complex128])
    +assert_type(AR_LIKE_f - MAR_c16, MaskedArray[np.complex128])
    +assert_type(AR_LIKE_c - MAR_c16, MaskedArray[np.complex128])
    +assert_type(AR_LIKE_o - MAR_c16, Any)
    +
    +assert_type(MAR_td64 - AR_LIKE_b, MaskedArray[np.timedelta64])
    +assert_type(MAR_td64 - AR_LIKE_u, MaskedArray[np.timedelta64])
    +assert_type(MAR_td64 - AR_LIKE_i, MaskedArray[np.timedelta64])
    +assert_type(MAR_td64 - AR_LIKE_td64, MaskedArray[np.timedelta64])
    +assert_type(MAR_td64 - AR_LIKE_o, Any)
    +
    +assert_type(AR_LIKE_b - MAR_td64, MaskedArray[np.timedelta64])
    +assert_type(AR_LIKE_u - MAR_td64, MaskedArray[np.timedelta64])
    +assert_type(AR_LIKE_i - MAR_td64, MaskedArray[np.timedelta64])
    +assert_type(AR_LIKE_td64 - MAR_td64, MaskedArray[np.timedelta64])
    +assert_type(AR_LIKE_dt64 - MAR_td64, MaskedArray[np.datetime64])
    +assert_type(AR_LIKE_o - MAR_td64, Any)
    +
    +assert_type(MAR_dt64 - AR_LIKE_b, MaskedArray[np.datetime64])
    +assert_type(MAR_dt64 - AR_LIKE_u, MaskedArray[np.datetime64])
    +assert_type(MAR_dt64 - AR_LIKE_i, MaskedArray[np.datetime64])
    +assert_type(MAR_dt64 - AR_LIKE_td64, MaskedArray[np.datetime64])
    +assert_type(MAR_dt64 - AR_LIKE_dt64, MaskedArray[np.timedelta64])
    +assert_type(MAR_dt64 - AR_LIKE_o, Any)
    +
    +assert_type(AR_LIKE_dt64 - MAR_dt64, MaskedArray[np.timedelta64])
    +assert_type(AR_LIKE_o - MAR_dt64, Any)
    +
    +assert_type(MAR_o - AR_LIKE_b, Any)
    +assert_type(MAR_o - AR_LIKE_u, Any)
    +assert_type(MAR_o - AR_LIKE_i, Any)
    +assert_type(MAR_o - AR_LIKE_f, Any)
    +assert_type(MAR_o - AR_LIKE_c, Any)
    +assert_type(MAR_o - AR_LIKE_td64, Any)
    +assert_type(MAR_o - AR_LIKE_dt64, Any)
    +assert_type(MAR_o - AR_LIKE_o, Any)
    +
    +assert_type(AR_LIKE_b - MAR_o, Any)
    +assert_type(AR_LIKE_u - MAR_o, Any)
    +assert_type(AR_LIKE_i - MAR_o, Any)
    +assert_type(AR_LIKE_f - MAR_o, Any)
    +assert_type(AR_LIKE_c - MAR_o, Any)
    +assert_type(AR_LIKE_td64 - MAR_o, Any)
    +assert_type(AR_LIKE_dt64 - MAR_o, Any)
    +assert_type(AR_LIKE_o - MAR_o, Any)
    +
    +# Masked Array multiplication
    +
    +assert_type(MAR_b * AR_LIKE_u, MaskedArray[np.uint32])
    +assert_type(MAR_b * AR_LIKE_i, MaskedArray[np.signedinteger])
    +assert_type(MAR_b * AR_LIKE_f, MaskedArray[np.floating])
    +assert_type(MAR_b * AR_LIKE_c, MaskedArray[np.complexfloating])
    +assert_type(MAR_b * AR_LIKE_td64, MaskedArray[np.timedelta64])
    +assert_type(MAR_b * AR_LIKE_o, Any)
    +
    +# Ignore due to https://github.com/python/mypy/issues/19341
    +assert_type(AR_LIKE_u * MAR_b, MaskedArray[np.uint32])  # type: ignore[assert-type]
    +assert_type(AR_LIKE_i * MAR_b, MaskedArray[np.signedinteger])  # type: ignore[assert-type]
    +assert_type(AR_LIKE_f * MAR_b, MaskedArray[np.floating])  # type: ignore[assert-type]
    +assert_type(AR_LIKE_c * MAR_b, MaskedArray[np.complexfloating])  # type: ignore[assert-type]
    +assert_type(AR_LIKE_td64 * MAR_b, MaskedArray[np.timedelta64])  # type: ignore[assert-type]
    +assert_type(AR_LIKE_o * MAR_b, Any)  # type: ignore[assert-type]
    +
    +assert_type(MAR_u4 * AR_LIKE_b, MaskedArray[np.uint32])
    +assert_type(MAR_u4 * AR_LIKE_u, MaskedArray[np.unsignedinteger])
    +assert_type(MAR_u4 * AR_LIKE_i, MaskedArray[np.signedinteger])
    +assert_type(MAR_u4 * AR_LIKE_f, MaskedArray[np.floating])
    +assert_type(MAR_u4 * AR_LIKE_c, MaskedArray[np.complexfloating])
    +assert_type(MAR_u4 * AR_LIKE_td64, MaskedArray[np.timedelta64])
    +assert_type(MAR_u4 * AR_LIKE_o, Any)
    +
    +assert_type(MAR_i8 * AR_LIKE_b, MaskedArray[np.int64])
    +assert_type(MAR_i8 * AR_LIKE_u, MaskedArray[np.signedinteger])
    +assert_type(MAR_i8 * AR_LIKE_i, MaskedArray[np.signedinteger])
    +assert_type(MAR_i8 * AR_LIKE_f, MaskedArray[np.floating])
    +assert_type(MAR_i8 * AR_LIKE_c, MaskedArray[np.complexfloating])
    +assert_type(MAR_i8 * AR_LIKE_td64, MaskedArray[np.timedelta64])
    +assert_type(MAR_i8 * AR_LIKE_o, Any)
    +
    +assert_type(MAR_f8 * AR_LIKE_b, MaskedArray[np.float64])
    +assert_type(MAR_f8 * AR_LIKE_u, MaskedArray[np.float64])
    +assert_type(MAR_f8 * AR_LIKE_i, MaskedArray[np.float64])
    +assert_type(MAR_f8 * AR_LIKE_f, MaskedArray[np.float64])
    +assert_type(MAR_f8 * AR_LIKE_c, MaskedArray[np.complexfloating])
    +assert_type(MAR_f8 * AR_LIKE_o, Any)
    +
    +# Ignore due to https://github.com/python/mypy/issues/19341
    +assert_type(AR_LIKE_b * MAR_f8, MaskedArray[np.float64])  # type: ignore[assert-type]
    +assert_type(AR_LIKE_u * MAR_f8, MaskedArray[np.float64])  # type: ignore[assert-type]
    +assert_type(AR_LIKE_i * MAR_f8, MaskedArray[np.float64])  # type: ignore[assert-type]
    +assert_type(AR_LIKE_f * MAR_f8, MaskedArray[np.float64])  # type: ignore[assert-type]
    +assert_type(AR_LIKE_c * MAR_f8, MaskedArray[np.complexfloating])  # type: ignore[assert-type]
    +assert_type(AR_LIKE_o * MAR_f8, Any)  # type: ignore[assert-type]
    +
    +assert_type(MAR_c16 * AR_LIKE_b, MaskedArray[np.complex128])
    +assert_type(MAR_c16 * AR_LIKE_u, MaskedArray[np.complex128])
    +assert_type(MAR_c16 * AR_LIKE_i, MaskedArray[np.complex128])
    +assert_type(MAR_c16 * AR_LIKE_f, MaskedArray[np.complex128])
    +assert_type(MAR_c16 * AR_LIKE_c, MaskedArray[np.complex128])
    +assert_type(MAR_c16 * AR_LIKE_o, Any)
    +
    +# Ignore due to https://github.com/python/mypy/issues/19341
    +assert_type(AR_LIKE_b * MAR_c16, MaskedArray[np.complex128])  # type: ignore[assert-type]
    +assert_type(AR_LIKE_u * MAR_c16, MaskedArray[np.complex128])  # type: ignore[assert-type]
    +assert_type(AR_LIKE_i * MAR_c16, MaskedArray[np.complex128])  # type: ignore[assert-type]
    +assert_type(AR_LIKE_f * MAR_c16, MaskedArray[np.complex128])  # type: ignore[assert-type]
    +assert_type(AR_LIKE_c * MAR_c16, MaskedArray[np.complex128])  # type: ignore[assert-type]
    +assert_type(AR_LIKE_o * MAR_c16, Any)  # type: ignore[assert-type]
    +
    +assert_type(MAR_td64 * AR_LIKE_b, MaskedArray[np.timedelta64])
    +assert_type(MAR_td64 * AR_LIKE_u, MaskedArray[np.timedelta64])
    +assert_type(MAR_td64 * AR_LIKE_i, MaskedArray[np.timedelta64])
    +assert_type(MAR_td64 * AR_LIKE_o, Any)
    +
    +# Ignore due to https://github.com/python/mypy/issues/19341
    +assert_type(AR_LIKE_b * MAR_td64, MaskedArray[np.timedelta64])  # type: ignore[assert-type]
    +assert_type(AR_LIKE_u * MAR_td64, MaskedArray[np.timedelta64])  # type: ignore[assert-type]
    +assert_type(AR_LIKE_i * MAR_td64, MaskedArray[np.timedelta64])  # type: ignore[assert-type]
    +assert_type(AR_LIKE_td64 * MAR_td64, MaskedArray[np.timedelta64])  # type: ignore[assert-type]
    +assert_type(AR_LIKE_dt64 * MAR_td64, MaskedArray[np.datetime64])  # type: ignore[assert-type]
    +assert_type(AR_LIKE_o * MAR_td64, Any)  # type: ignore[assert-type]
    +
    +assert_type(AR_LIKE_o * MAR_dt64, Any)  # type: ignore[assert-type]
    +
    +assert_type(MAR_o * AR_LIKE_b, Any)
    +assert_type(MAR_o * AR_LIKE_u, Any)
    +assert_type(MAR_o * AR_LIKE_i, Any)
    +assert_type(MAR_o * AR_LIKE_f, Any)
    +assert_type(MAR_o * AR_LIKE_c, Any)
    +assert_type(MAR_o * AR_LIKE_td64, Any)
    +assert_type(MAR_o * AR_LIKE_dt64, Any)
    +assert_type(MAR_o * AR_LIKE_o, Any)
    +
    +# Ignore due to https://github.com/python/mypy/issues/19341
    +assert_type(AR_LIKE_b * MAR_o, Any)  # type: ignore[assert-type]
    +assert_type(AR_LIKE_u * MAR_o, Any)  # type: ignore[assert-type]
    +assert_type(AR_LIKE_i * MAR_o, Any)  # type: ignore[assert-type]
    +assert_type(AR_LIKE_f * MAR_o, Any)  # type: ignore[assert-type]
    +assert_type(AR_LIKE_c * MAR_o, Any)  # type: ignore[assert-type]
    +assert_type(AR_LIKE_td64 * MAR_o, Any)  # type: ignore[assert-type]
    +assert_type(AR_LIKE_dt64 * MAR_o, Any)  # type: ignore[assert-type]
    +assert_type(AR_LIKE_o * MAR_o, Any)  # type: ignore[assert-type]
    +
    +assert_type(MAR_f8.sum(), Any)
    +assert_type(MAR_f8.sum(axis=0), Any)
    +assert_type(MAR_f8.sum(keepdims=True), Any)
    +assert_type(MAR_f8.sum(out=MAR_subclass), MaskedArraySubclassC)
    +
    +assert_type(MAR_f8.std(), Any)
    +assert_type(MAR_f8.std(axis=0), Any)
    +assert_type(MAR_f8.std(keepdims=True, mean=0.), Any)
    +assert_type(MAR_f8.std(out=MAR_subclass), MaskedArraySubclassC)
    +
    +assert_type(MAR_f8.var(), Any)
    +assert_type(MAR_f8.var(axis=0), Any)
    +assert_type(MAR_f8.var(keepdims=True, mean=0.), Any)
    +assert_type(MAR_f8.var(out=MAR_subclass), MaskedArraySubclassC)
    +
    +assert_type(MAR_f8.mean(), Any)
    +assert_type(MAR_f8.mean(axis=0), Any)
    +assert_type(MAR_f8.mean(keepdims=True), Any)
    +assert_type(MAR_f8.mean(out=MAR_subclass), MaskedArraySubclassC)
    +
    +assert_type(MAR_f8.prod(), Any)
    +assert_type(MAR_f8.prod(axis=0), Any)
    +assert_type(MAR_f8.prod(keepdims=True), Any)
    +assert_type(MAR_f8.prod(out=MAR_subclass), MaskedArraySubclassC)
    +
    +# MaskedArray "true" division
    +
    +assert_type(MAR_f8 / b, MaskedArray[np.float64])
    +assert_type(MAR_f8 / i, MaskedArray[np.float64])
    +assert_type(MAR_f8 / f, MaskedArray[np.float64])
    +
    +assert_type(b / MAR_f8, MaskedArray[np.float64])
    +assert_type(i / MAR_f8, MaskedArray[np.float64])
    +assert_type(f / MAR_f8, MaskedArray[np.float64])
    +
    +assert_type(MAR_b / AR_LIKE_b, MaskedArray[np.float64])
    +assert_type(MAR_b / AR_LIKE_u, MaskedArray[np.float64])
    +assert_type(MAR_b / AR_LIKE_i, MaskedArray[np.float64])
    +assert_type(MAR_b / AR_LIKE_f, MaskedArray[np.float64])
    +assert_type(MAR_b / AR_LIKE_o, Any)
    +
    +assert_type(AR_LIKE_b / MAR_b, MaskedArray[np.float64])
    +assert_type(AR_LIKE_u / MAR_b, MaskedArray[np.float64])
    +assert_type(AR_LIKE_i / MAR_b, MaskedArray[np.float64])
    +assert_type(AR_LIKE_f / MAR_b, MaskedArray[np.float64])
    +assert_type(AR_LIKE_o / MAR_b, Any)
    +
    +assert_type(MAR_u4 / AR_LIKE_b, MaskedArray[np.float64])
    +assert_type(MAR_u4 / AR_LIKE_u, MaskedArray[np.float64])
    +assert_type(MAR_u4 / AR_LIKE_i, MaskedArray[np.float64])
    +assert_type(MAR_u4 / AR_LIKE_f, MaskedArray[np.float64])
    +assert_type(MAR_u4 / AR_LIKE_o, Any)
    +
    +assert_type(AR_LIKE_b / MAR_u4, MaskedArray[np.float64])
    +assert_type(AR_LIKE_u / MAR_u4, MaskedArray[np.float64])
    +assert_type(AR_LIKE_i / MAR_u4, MaskedArray[np.float64])
    +assert_type(AR_LIKE_f / MAR_u4, MaskedArray[np.float64])
    +assert_type(AR_LIKE_td64 / MAR_u4, MaskedArray[np.timedelta64])
    +assert_type(AR_LIKE_o / MAR_u4, Any)
    +
    +assert_type(MAR_i8 / AR_LIKE_b, MaskedArray[np.float64])
    +assert_type(MAR_i8 / AR_LIKE_u, MaskedArray[np.float64])
    +assert_type(MAR_i8 / AR_LIKE_i, MaskedArray[np.float64])
    +assert_type(MAR_i8 / AR_LIKE_f, MaskedArray[np.float64])
    +assert_type(MAR_i8 / AR_LIKE_o, Any)
    +
    +assert_type(AR_LIKE_b / MAR_i8, MaskedArray[np.float64])
    +assert_type(AR_LIKE_u / MAR_i8, MaskedArray[np.float64])
    +assert_type(AR_LIKE_i / MAR_i8, MaskedArray[np.float64])
    +assert_type(AR_LIKE_f / MAR_i8, MaskedArray[np.float64])
    +assert_type(AR_LIKE_td64 / MAR_i8, MaskedArray[np.timedelta64])
    +assert_type(AR_LIKE_o / MAR_i8, Any)
    +
    +assert_type(MAR_f8 / AR_LIKE_b, MaskedArray[np.float64])
    +assert_type(MAR_f8 / AR_LIKE_u, MaskedArray[np.float64])
    +assert_type(MAR_f8 / AR_LIKE_i, MaskedArray[np.float64])
    +assert_type(MAR_f8 / AR_LIKE_f, MaskedArray[np.float64])
    +assert_type(MAR_f8 / AR_LIKE_o, Any)
    +
    +assert_type(AR_LIKE_b / MAR_f8, MaskedArray[np.float64])
    +assert_type(AR_LIKE_u / MAR_f8, MaskedArray[np.float64])
    +assert_type(AR_LIKE_i / MAR_f8, MaskedArray[np.float64])
    +assert_type(AR_LIKE_f / MAR_f8, MaskedArray[np.float64])
    +assert_type(AR_LIKE_td64 / MAR_f8, MaskedArray[np.timedelta64])
    +assert_type(AR_LIKE_o / MAR_f8, Any)
    +
    +assert_type(MAR_td64 / AR_LIKE_u, MaskedArray[np.timedelta64])
    +assert_type(MAR_td64 / AR_LIKE_i, MaskedArray[np.timedelta64])
    +assert_type(MAR_td64 / AR_LIKE_f, MaskedArray[np.timedelta64])
    +assert_type(MAR_td64 / AR_LIKE_td64, MaskedArray[np.float64])
    +assert_type(MAR_td64 / AR_LIKE_o, Any)
    +
    +assert_type(AR_LIKE_td64 / MAR_td64, MaskedArray[np.float64])
    +assert_type(AR_LIKE_o / MAR_td64, Any)
    +
    +assert_type(MAR_o / AR_LIKE_b, Any)
    +assert_type(MAR_o / AR_LIKE_u, Any)
    +assert_type(MAR_o / AR_LIKE_i, Any)
    +assert_type(MAR_o / AR_LIKE_f, Any)
    +assert_type(MAR_o / AR_LIKE_td64, Any)
    +assert_type(MAR_o / AR_LIKE_dt64, Any)
    +assert_type(MAR_o / AR_LIKE_o, Any)
    +
    +assert_type(AR_LIKE_b / MAR_o, Any)
    +assert_type(AR_LIKE_u / MAR_o, Any)
    +assert_type(AR_LIKE_i / MAR_o, Any)
    +assert_type(AR_LIKE_f / MAR_o, Any)
    +assert_type(AR_LIKE_td64 / MAR_o, Any)
    +assert_type(AR_LIKE_dt64 / MAR_o, Any)
    +assert_type(AR_LIKE_o / MAR_o, Any)
    +
    +# MaskedArray floor division
    +
    +assert_type(MAR_b // AR_LIKE_b, MaskedArray[np.int8])
    +assert_type(MAR_b // AR_LIKE_u, MaskedArray[np.uint32])
    +assert_type(MAR_b // AR_LIKE_i, MaskedArray[np.signedinteger])
    +assert_type(MAR_b // AR_LIKE_f, MaskedArray[np.floating])
    +assert_type(MAR_b // AR_LIKE_o, Any)
    +
    +assert_type(AR_LIKE_b // MAR_b, MaskedArray[np.int8])
    +assert_type(AR_LIKE_u // MAR_b, MaskedArray[np.uint32])
    +assert_type(AR_LIKE_i // MAR_b, MaskedArray[np.signedinteger])
    +assert_type(AR_LIKE_f // MAR_b, MaskedArray[np.floating])
    +assert_type(AR_LIKE_o // MAR_b, Any)
    +
    +assert_type(MAR_u4 // AR_LIKE_b, MaskedArray[np.uint32])
    +assert_type(MAR_u4 // AR_LIKE_u, MaskedArray[np.unsignedinteger])
    +assert_type(MAR_u4 // AR_LIKE_i, MaskedArray[np.signedinteger])
    +assert_type(MAR_u4 // AR_LIKE_f, MaskedArray[np.floating])
    +assert_type(MAR_u4 // AR_LIKE_o, Any)
    +
    +assert_type(AR_LIKE_b // MAR_u4, MaskedArray[np.uint32])
    +assert_type(AR_LIKE_u // MAR_u4, MaskedArray[np.unsignedinteger])
    +assert_type(AR_LIKE_i // MAR_u4, MaskedArray[np.signedinteger])
    +assert_type(AR_LIKE_f // MAR_u4, MaskedArray[np.floating])
    +assert_type(AR_LIKE_td64 // MAR_u4, MaskedArray[np.timedelta64])
    +assert_type(AR_LIKE_o // MAR_u4, Any)
    +
    +assert_type(MAR_i8 // AR_LIKE_b, MaskedArray[np.int64])
    +assert_type(MAR_i8 // AR_LIKE_u, MaskedArray[np.signedinteger])
    +assert_type(MAR_i8 // AR_LIKE_i, MaskedArray[np.signedinteger])
    +assert_type(MAR_i8 // AR_LIKE_f, MaskedArray[np.floating])
    +assert_type(MAR_i8 // AR_LIKE_o, Any)
    +
    +assert_type(AR_LIKE_b // MAR_i8, MaskedArray[np.int64])
    +assert_type(AR_LIKE_u // MAR_i8, MaskedArray[np.signedinteger])
    +assert_type(AR_LIKE_i // MAR_i8, MaskedArray[np.signedinteger])
    +assert_type(AR_LIKE_f // MAR_i8, MaskedArray[np.floating])
    +assert_type(AR_LIKE_td64 // MAR_i8, MaskedArray[np.timedelta64])
    +assert_type(AR_LIKE_o // MAR_i8, Any)
    +
    +assert_type(MAR_f8 // AR_LIKE_b, MaskedArray[np.float64])
    +assert_type(MAR_f8 // AR_LIKE_u, MaskedArray[np.float64])
    +assert_type(MAR_f8 // AR_LIKE_i, MaskedArray[np.float64])
    +assert_type(MAR_f8 // AR_LIKE_f, MaskedArray[np.float64])
    +assert_type(MAR_f8 // AR_LIKE_o, Any)
    +
    +assert_type(AR_LIKE_b // MAR_f8, MaskedArray[np.float64])
    +assert_type(AR_LIKE_u // MAR_f8, MaskedArray[np.float64])
    +assert_type(AR_LIKE_i // MAR_f8, MaskedArray[np.float64])
    +assert_type(AR_LIKE_f // MAR_f8, MaskedArray[np.float64])
    +assert_type(AR_LIKE_td64 // MAR_f8, MaskedArray[np.timedelta64])
    +assert_type(AR_LIKE_o // MAR_f8, Any)
    +
    +assert_type(MAR_td64 // AR_LIKE_u, MaskedArray[np.timedelta64])
    +assert_type(MAR_td64 // AR_LIKE_i, MaskedArray[np.timedelta64])
    +assert_type(MAR_td64 // AR_LIKE_f, MaskedArray[np.timedelta64])
    +assert_type(MAR_td64 // AR_LIKE_td64, MaskedArray[np.int64])
    +assert_type(MAR_td64 // AR_LIKE_o, Any)
    +
    +assert_type(AR_LIKE_td64 // MAR_td64, MaskedArray[np.int64])
    +assert_type(AR_LIKE_o // MAR_td64, Any)
    +
    +assert_type(MAR_o // AR_LIKE_b, Any)
    +assert_type(MAR_o // AR_LIKE_u, Any)
    +assert_type(MAR_o // AR_LIKE_i, Any)
    +assert_type(MAR_o // AR_LIKE_f, Any)
    +assert_type(MAR_o // AR_LIKE_td64, Any)
    +assert_type(MAR_o // AR_LIKE_dt64, Any)
    +assert_type(MAR_o // AR_LIKE_o, Any)
    +
    +assert_type(AR_LIKE_b // MAR_o, Any)
    +assert_type(AR_LIKE_u // MAR_o, Any)
    +assert_type(AR_LIKE_i // MAR_o, Any)
    +assert_type(AR_LIKE_f // MAR_o, Any)
    +assert_type(AR_LIKE_td64 // MAR_o, Any)
    +assert_type(AR_LIKE_dt64 // MAR_o, Any)
    +assert_type(AR_LIKE_o // MAR_o, Any)
    +
    +# Masked Array power
    +
    +assert_type(MAR_b ** AR_LIKE_u, MaskedArray[np.uint32])
    +assert_type(MAR_b ** AR_LIKE_i, MaskedArray[np.signedinteger])
    +assert_type(MAR_b ** AR_LIKE_f, MaskedArray[np.floating])
    +assert_type(MAR_b ** AR_LIKE_c, MaskedArray[np.complexfloating])
    +assert_type(MAR_b ** AR_LIKE_o, Any)
    +
    +assert_type(AR_LIKE_u ** MAR_b, MaskedArray[np.uint32])
    +assert_type(AR_LIKE_i ** MAR_b, MaskedArray[np.signedinteger])
    +assert_type(AR_LIKE_f ** MAR_b, MaskedArray[np.floating])
    +assert_type(AR_LIKE_c ** MAR_b, MaskedArray[np.complexfloating])
    +assert_type(AR_LIKE_o ** MAR_b, Any)
    +
    +assert_type(MAR_u4 ** AR_LIKE_b, MaskedArray[np.uint32])
    +assert_type(MAR_u4 ** AR_LIKE_u, MaskedArray[np.unsignedinteger])
    +assert_type(MAR_u4 ** AR_LIKE_i, MaskedArray[np.signedinteger])
    +assert_type(MAR_u4 ** AR_LIKE_f, MaskedArray[np.floating])
    +assert_type(MAR_u4 ** AR_LIKE_c, MaskedArray[np.complexfloating])
    +assert_type(MAR_u4 ** AR_LIKE_o, Any)
    +
    +assert_type(AR_LIKE_b ** MAR_u4, MaskedArray[np.uint32])
    +assert_type(AR_LIKE_u ** MAR_u4, MaskedArray[np.unsignedinteger])
    +assert_type(AR_LIKE_i ** MAR_u4, MaskedArray[np.signedinteger])
    +assert_type(AR_LIKE_f ** MAR_u4, MaskedArray[np.floating])
    +assert_type(AR_LIKE_c ** MAR_u4, MaskedArray[np.complexfloating])
    +assert_type(AR_LIKE_o ** MAR_u4, Any)
    +
    +assert_type(MAR_i8 ** AR_LIKE_b, MaskedArray[np.int64])
    +assert_type(MAR_i8 ** AR_LIKE_u, MaskedArray[np.signedinteger])
    +assert_type(MAR_i8 ** AR_LIKE_i, MaskedArray[np.signedinteger])
    +assert_type(MAR_i8 ** AR_LIKE_f, MaskedArray[np.floating])
    +assert_type(MAR_i8 ** AR_LIKE_c, MaskedArray[np.complexfloating])
    +assert_type(MAR_i8 ** AR_LIKE_o, Any)
    +assert_type(MAR_i8 ** AR_LIKE_b, MaskedArray[np.int64])
    +
    +assert_type(AR_LIKE_u ** MAR_i8, MaskedArray[np.signedinteger])
    +assert_type(AR_LIKE_i ** MAR_i8, MaskedArray[np.signedinteger])
    +assert_type(AR_LIKE_f ** MAR_i8, MaskedArray[np.floating])
    +assert_type(AR_LIKE_c ** MAR_i8, MaskedArray[np.complexfloating])
    +assert_type(AR_LIKE_o ** MAR_i8, Any)
    +
    +assert_type(MAR_f8 ** AR_LIKE_b, MaskedArray[np.float64])
    +assert_type(MAR_f8 ** AR_LIKE_u, MaskedArray[np.float64])
    +assert_type(MAR_f8 ** AR_LIKE_i, MaskedArray[np.float64])
    +assert_type(MAR_f8 ** AR_LIKE_f, MaskedArray[np.float64])
    +assert_type(MAR_f8 ** AR_LIKE_c, MaskedArray[np.complexfloating])
    +assert_type(MAR_f8 ** AR_LIKE_o, Any)
    +
    +assert_type(AR_LIKE_b ** MAR_f8, MaskedArray[np.float64])
    +assert_type(AR_LIKE_u ** MAR_f8, MaskedArray[np.float64])
    +assert_type(AR_LIKE_i ** MAR_f8, MaskedArray[np.float64])
    +assert_type(AR_LIKE_f ** MAR_f8, MaskedArray[np.float64])
    +assert_type(AR_LIKE_c ** MAR_f8, MaskedArray[np.complexfloating])
    +assert_type(AR_LIKE_o ** MAR_f8, Any)
    +
    +assert_type(MAR_c16 ** AR_LIKE_b, MaskedArray[np.complex128])
    +assert_type(MAR_c16 ** AR_LIKE_u, MaskedArray[np.complex128])
    +assert_type(MAR_c16 ** AR_LIKE_i, MaskedArray[np.complex128])
    +assert_type(MAR_c16 ** AR_LIKE_f, MaskedArray[np.complex128])
    +assert_type(MAR_c16 ** AR_LIKE_c, MaskedArray[np.complex128])
    +assert_type(MAR_c16 ** AR_LIKE_o, Any)
    +
    +assert_type(AR_LIKE_b ** MAR_c16, MaskedArray[np.complex128])
    +assert_type(AR_LIKE_u ** MAR_c16, MaskedArray[np.complex128])
    +assert_type(AR_LIKE_i ** MAR_c16, MaskedArray[np.complex128])
    +assert_type(AR_LIKE_f ** MAR_c16, MaskedArray[np.complex128])
    +assert_type(AR_LIKE_c ** MAR_c16, MaskedArray[np.complex128])
    +assert_type(AR_LIKE_o ** MAR_c16, Any)
    +
    +assert_type(MAR_o ** AR_LIKE_b, Any)
    +assert_type(MAR_o ** AR_LIKE_u, Any)
    +assert_type(MAR_o ** AR_LIKE_i, Any)
    +assert_type(MAR_o ** AR_LIKE_f, Any)
    +assert_type(MAR_o ** AR_LIKE_c, Any)
    +assert_type(MAR_o ** AR_LIKE_o, Any)
    +
    +assert_type(AR_LIKE_b ** MAR_o, Any)
    +assert_type(AR_LIKE_u ** MAR_o, Any)
    +assert_type(AR_LIKE_i ** MAR_o, Any)
    +assert_type(AR_LIKE_f ** MAR_o, Any)
    +assert_type(AR_LIKE_c ** MAR_o, Any)
    +assert_type(AR_LIKE_o ** MAR_o, Any)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/matrix.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/matrix.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..3a32b3d394f0da125da06a96327c23f60f5dc504
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/matrix.pyi
    @@ -0,0 +1,73 @@
    +from typing import Any, TypeAlias, assert_type
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +_Shape2D: TypeAlias = tuple[int, int]
    +
    +mat: np.matrix[_Shape2D, np.dtype[np.int64]]
    +ar_f8: npt.NDArray[np.float64]
    +ar_ip: npt.NDArray[np.intp]
    +
    +assert_type(mat * 5, np.matrix)
    +assert_type(5 * mat, np.matrix)
    +mat *= 5
    +
    +assert_type(mat**5, np.matrix)
    +mat **= 5
    +
    +assert_type(mat.sum(), Any)
    +assert_type(mat.mean(), Any)
    +assert_type(mat.std(), Any)
    +assert_type(mat.var(), Any)
    +assert_type(mat.prod(), Any)
    +assert_type(mat.any(), np.bool)
    +assert_type(mat.all(), np.bool)
    +assert_type(mat.max(), np.int64)
    +assert_type(mat.min(), np.int64)
    +assert_type(mat.argmax(), np.intp)
    +assert_type(mat.argmin(), np.intp)
    +assert_type(mat.ptp(), np.int64)
    +
    +assert_type(mat.sum(axis=0), np.matrix)
    +assert_type(mat.mean(axis=0), np.matrix)
    +assert_type(mat.std(axis=0), np.matrix)
    +assert_type(mat.var(axis=0), np.matrix)
    +assert_type(mat.prod(axis=0), np.matrix)
    +assert_type(mat.any(axis=0), np.matrix[_Shape2D, np.dtype[np.bool]])
    +assert_type(mat.all(axis=0), np.matrix[_Shape2D, np.dtype[np.bool]])
    +assert_type(mat.max(axis=0), np.matrix[_Shape2D, np.dtype[np.int64]])
    +assert_type(mat.min(axis=0), np.matrix[_Shape2D, np.dtype[np.int64]])
    +assert_type(mat.argmax(axis=0), np.matrix[_Shape2D, np.dtype[np.intp]])
    +assert_type(mat.argmin(axis=0), np.matrix[_Shape2D, np.dtype[np.intp]])
    +assert_type(mat.ptp(axis=0), np.matrix[_Shape2D, np.dtype[np.int64]])
    +
    +assert_type(mat.sum(out=ar_f8), npt.NDArray[np.float64])
    +assert_type(mat.mean(out=ar_f8), npt.NDArray[np.float64])
    +assert_type(mat.std(out=ar_f8), npt.NDArray[np.float64])
    +assert_type(mat.var(out=ar_f8), npt.NDArray[np.float64])
    +assert_type(mat.prod(out=ar_f8), npt.NDArray[np.float64])
    +assert_type(mat.any(out=ar_f8), npt.NDArray[np.float64])
    +assert_type(mat.all(out=ar_f8), npt.NDArray[np.float64])
    +assert_type(mat.max(out=ar_f8), npt.NDArray[np.float64])
    +assert_type(mat.min(out=ar_f8), npt.NDArray[np.float64])
    +assert_type(mat.argmax(out=ar_ip), npt.NDArray[np.intp])
    +assert_type(mat.argmin(out=ar_ip), npt.NDArray[np.intp])
    +assert_type(mat.ptp(out=ar_f8), npt.NDArray[np.float64])
    +
    +assert_type(mat.T, np.matrix[_Shape2D, np.dtype[np.int64]])
    +assert_type(mat.I, np.matrix)
    +assert_type(mat.A, np.ndarray[_Shape2D, np.dtype[np.int64]])
    +assert_type(mat.A1, npt.NDArray[np.int64])
    +assert_type(mat.H, np.matrix[_Shape2D, np.dtype[np.int64]])
    +assert_type(mat.getT(), np.matrix[_Shape2D, np.dtype[np.int64]])
    +assert_type(mat.getI(), np.matrix)
    +assert_type(mat.getA(), np.ndarray[_Shape2D, np.dtype[np.int64]])
    +assert_type(mat.getA1(), npt.NDArray[np.int64])
    +assert_type(mat.getH(), np.matrix[_Shape2D, np.dtype[np.int64]])
    +
    +assert_type(np.bmat(ar_f8), np.matrix)
    +assert_type(np.bmat([[0, 1, 2]]), np.matrix)
    +assert_type(np.bmat("mat"), np.matrix)
    +
    +assert_type(np.asmatrix(ar_f8, dtype=np.int64), np.matrix)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/memmap.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/memmap.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..f3e20ed2d5e7df74e3e2fa66d743430fece8cb62
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/memmap.pyi
    @@ -0,0 +1,19 @@
    +from typing import Any, assert_type
    +
    +import numpy as np
    +
    +memmap_obj: np.memmap[Any, np.dtype[np.str_]]
    +
    +assert_type(np.memmap.__array_priority__, float)
    +assert_type(memmap_obj.__array_priority__, float)
    +assert_type(memmap_obj.filename, str | None)
    +assert_type(memmap_obj.offset, int)
    +assert_type(memmap_obj.mode, str)
    +assert_type(memmap_obj.flush(), None)
    +
    +assert_type(np.memmap("file.txt", offset=5), np.memmap[Any, np.dtype[np.uint8]])
    +assert_type(np.memmap(b"file.txt", dtype=np.float64, shape=(10, 3)), np.memmap[Any, np.dtype[np.float64]])
    +with open("file.txt", "rb") as f:
    +    assert_type(np.memmap(f, dtype=float, order="K"), np.memmap[Any, np.dtype])
    +
    +assert_type(memmap_obj.__array_finalize__(object()), None)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/mod.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/mod.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..ef07dc0c8c8aab9a6615f04a5903cf07be667548
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/mod.pyi
    @@ -0,0 +1,178 @@
    +import datetime as dt
    +from typing import Literal as L, assert_type
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +f8: np.float64
    +i8: np.int64
    +u8: np.uint64
    +
    +f4: np.float32
    +i4: np.int32
    +u4: np.uint32
    +
    +m: np.timedelta64
    +m_nat: np.timedelta64[None]
    +m_int0: np.timedelta64[L[0]]
    +m_int: np.timedelta64[int]
    +m_td: np.timedelta64[dt.timedelta]
    +
    +b_: np.bool
    +
    +b: bool
    +i: int
    +f: float
    +
    +AR_b: npt.NDArray[np.bool]
    +AR_m: npt.NDArray[np.timedelta64]
    +
    +# Time structures
    +
    +assert_type(m % m, np.timedelta64)
    +assert_type(m % m_nat, np.timedelta64[None])
    +assert_type(m % m_int0, np.timedelta64[None])
    +assert_type(m % m_int, np.timedelta64[int | None])
    +assert_type(m_nat % m, np.timedelta64[None])
    +assert_type(m_int % m_nat, np.timedelta64[None])
    +assert_type(m_int % m_int0, np.timedelta64[None])
    +assert_type(m_int % m_int, np.timedelta64[int | None])
    +assert_type(m_int % m_td, np.timedelta64[int | None])
    +assert_type(m_td % m_nat, np.timedelta64[None])
    +assert_type(m_td % m_int0, np.timedelta64[None])
    +assert_type(m_td % m_int, np.timedelta64[int | None])
    +assert_type(m_td % m_td, np.timedelta64[dt.timedelta | None])
    +
    +assert_type(AR_m % m, npt.NDArray[np.timedelta64])
    +assert_type(m % AR_m, npt.NDArray[np.timedelta64])
    +
    +assert_type(divmod(m, m), tuple[np.int64, np.timedelta64])
    +assert_type(divmod(m, m_nat), tuple[np.int64, np.timedelta64[None]])
    +assert_type(divmod(m, m_int0), tuple[np.int64, np.timedelta64[None]])
    +# workarounds for https://github.com/microsoft/pyright/issues/9663
    +assert_type(m.__divmod__(m_int), tuple[np.int64, np.timedelta64[int | None]])
    +assert_type(divmod(m_nat, m), tuple[np.int64, np.timedelta64[None]])
    +assert_type(divmod(m_int, m_nat), tuple[np.int64, np.timedelta64[None]])
    +assert_type(divmod(m_int, m_int0), tuple[np.int64, np.timedelta64[None]])
    +assert_type(divmod(m_int, m_int), tuple[np.int64, np.timedelta64[int | None]])
    +assert_type(divmod(m_int, m_td), tuple[np.int64, np.timedelta64[int | None]])
    +assert_type(divmod(m_td, m_nat), tuple[np.int64, np.timedelta64[None]])
    +assert_type(divmod(m_td, m_int0), tuple[np.int64, np.timedelta64[None]])
    +assert_type(divmod(m_td, m_int), tuple[np.int64, np.timedelta64[int | None]])
    +assert_type(divmod(m_td, m_td), tuple[np.int64, np.timedelta64[dt.timedelta | None]])
    +
    +assert_type(divmod(AR_m, m), tuple[npt.NDArray[np.int64], npt.NDArray[np.timedelta64]])
    +assert_type(divmod(m, AR_m), tuple[npt.NDArray[np.int64], npt.NDArray[np.timedelta64]])
    +
    +# Bool
    +
    +assert_type(b_ % b, np.int8)
    +assert_type(b_ % i, np.int_)
    +assert_type(b_ % f, np.float64)
    +assert_type(b_ % b_, np.int8)
    +assert_type(b_ % i8, np.int64)
    +assert_type(b_ % u8, np.uint64)
    +assert_type(b_ % f8, np.float64)
    +assert_type(b_ % AR_b, npt.NDArray[np.int8])
    +
    +assert_type(divmod(b_, b), tuple[np.int8, np.int8])
    +assert_type(divmod(b_, b_), tuple[np.int8, np.int8])
    +# workarounds for https://github.com/microsoft/pyright/issues/9663
    +assert_type(b_.__divmod__(i), tuple[np.int_, np.int_])
    +assert_type(b_.__divmod__(f), tuple[np.float64, np.float64])
    +assert_type(b_.__divmod__(i8), tuple[np.int64, np.int64])
    +assert_type(b_.__divmod__(u8), tuple[np.uint64, np.uint64])
    +assert_type(divmod(b_, f8), tuple[np.float64, np.float64])
    +assert_type(divmod(b_, AR_b), tuple[npt.NDArray[np.int8], npt.NDArray[np.int8]])
    +
    +assert_type(b % b_, np.int8)
    +assert_type(i % b_, np.int_)
    +assert_type(f % b_, np.float64)
    +assert_type(b_ % b_, np.int8)
    +assert_type(i8 % b_, np.int64)
    +assert_type(u8 % b_, np.uint64)
    +assert_type(f8 % b_, np.float64)
    +assert_type(AR_b % b_, npt.NDArray[np.int8])
    +
    +assert_type(divmod(b, b_), tuple[np.int8, np.int8])
    +assert_type(divmod(i, b_), tuple[np.int_, np.int_])
    +assert_type(divmod(f, b_), tuple[np.float64, np.float64])
    +assert_type(divmod(b_, b_), tuple[np.int8, np.int8])
    +assert_type(divmod(i8, b_), tuple[np.int64, np.int64])
    +assert_type(divmod(u8, b_), tuple[np.uint64, np.uint64])
    +assert_type(divmod(f8, b_), tuple[np.float64, np.float64])
    +assert_type(divmod(AR_b, b_), tuple[npt.NDArray[np.int8], npt.NDArray[np.int8]])
    +
    +# int
    +
    +assert_type(i8 % b, np.int64)
    +assert_type(i8 % i8, np.int64)
    +assert_type(i8 % f, np.float64)
    +assert_type(i8 % f8, np.float64)
    +assert_type(i4 % i8, np.signedinteger)
    +assert_type(i4 % f8, np.float64)
    +assert_type(i4 % i4, np.int32)
    +assert_type(i4 % f4, np.floating)
    +assert_type(i8 % AR_b, npt.NDArray[np.int64])
    +
    +assert_type(divmod(i8, b), tuple[np.int64, np.int64])
    +assert_type(divmod(i8, i4), tuple[np.signedinteger, np.signedinteger])
    +assert_type(divmod(i8, i8), tuple[np.int64, np.int64])
    +# workarounds for https://github.com/microsoft/pyright/issues/9663
    +assert_type(i8.__divmod__(f), tuple[np.float64, np.float64])
    +assert_type(i8.__divmod__(f8), tuple[np.float64, np.float64])
    +assert_type(divmod(i8, f4), tuple[np.floating, np.floating])
    +assert_type(divmod(i4, i4), tuple[np.int32, np.int32])
    +assert_type(divmod(i4, f4), tuple[np.floating, np.floating])
    +assert_type(divmod(i8, AR_b), tuple[npt.NDArray[np.int64], npt.NDArray[np.int64]])
    +
    +assert_type(b % i8, np.int64)
    +assert_type(f % i8, np.float64)
    +assert_type(i8 % i8, np.int64)
    +assert_type(f8 % i8, np.float64)
    +assert_type(i8 % i4, np.signedinteger)
    +assert_type(f8 % i4, np.float64)
    +assert_type(i4 % i4, np.int32)
    +assert_type(f4 % i4, np.floating)
    +assert_type(AR_b % i8, npt.NDArray[np.int64])
    +
    +assert_type(divmod(b, i8), tuple[np.int64, np.int64])
    +assert_type(divmod(f, i8), tuple[np.float64, np.float64])
    +assert_type(divmod(i8, i8), tuple[np.int64, np.int64])
    +assert_type(divmod(f8, i8), tuple[np.float64, np.float64])
    +assert_type(divmod(i4, i8), tuple[np.signedinteger, np.signedinteger])
    +assert_type(divmod(i4, i4), tuple[np.int32, np.int32])
    +# workarounds for https://github.com/microsoft/pyright/issues/9663
    +assert_type(f4.__divmod__(i8), tuple[np.floating, np.floating])
    +assert_type(f4.__divmod__(i4), tuple[np.floating, np.floating])
    +assert_type(AR_b.__divmod__(i8), tuple[npt.NDArray[np.int64], npt.NDArray[np.int64]])
    +
    +# float
    +
    +assert_type(f8 % b, np.float64)
    +assert_type(f8 % f, np.float64)
    +assert_type(i8 % f4, np.floating)
    +assert_type(f4 % f4, np.float32)
    +assert_type(f8 % AR_b, npt.NDArray[np.float64])
    +
    +assert_type(divmod(f8, b), tuple[np.float64, np.float64])
    +assert_type(divmod(f8, f), tuple[np.float64, np.float64])
    +assert_type(divmod(f8, f8), tuple[np.float64, np.float64])
    +assert_type(divmod(f8, f4), tuple[np.float64, np.float64])
    +assert_type(divmod(f4, f4), tuple[np.float32, np.float32])
    +assert_type(divmod(f8, AR_b), tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]])
    +
    +assert_type(b % f8, np.float64)
    +assert_type(f % f8, np.float64)  # pyright: ignore[reportAssertTypeFailure]  # pyright incorrectly infers `builtins.float`
    +assert_type(f8 % f8, np.float64)
    +assert_type(f8 % f8, np.float64)
    +assert_type(f4 % f4, np.float32)
    +assert_type(AR_b % f8, npt.NDArray[np.float64])
    +
    +assert_type(divmod(b, f8), tuple[np.float64, np.float64])
    +assert_type(divmod(f8, f8), tuple[np.float64, np.float64])
    +assert_type(divmod(f4, f4), tuple[np.float32, np.float32])
    +# workarounds for https://github.com/microsoft/pyright/issues/9663
    +assert_type(f8.__rdivmod__(f), tuple[np.float64, np.float64])
    +assert_type(f8.__rdivmod__(f4), tuple[np.float64, np.float64])
    +assert_type(AR_b.__divmod__(f8), tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]])
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/modules.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/modules.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..628fb500bfda11752336fa52fea5b81c259501ae
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/modules.pyi
    @@ -0,0 +1,51 @@
    +import types
    +from typing import assert_type
    +
    +import numpy as np
    +from numpy import f2py
    +
    +assert_type(np, types.ModuleType)
    +
    +assert_type(np.char, types.ModuleType)
    +assert_type(np.ctypeslib, types.ModuleType)
    +assert_type(np.emath, types.ModuleType)
    +assert_type(np.fft, types.ModuleType)
    +assert_type(np.lib, types.ModuleType)
    +assert_type(np.linalg, types.ModuleType)
    +assert_type(np.ma, types.ModuleType)
    +assert_type(np.matrixlib, types.ModuleType)
    +assert_type(np.polynomial, types.ModuleType)
    +assert_type(np.random, types.ModuleType)
    +assert_type(np.rec, types.ModuleType)
    +assert_type(np.testing, types.ModuleType)
    +assert_type(np.version, types.ModuleType)
    +assert_type(np.exceptions, types.ModuleType)
    +assert_type(np.dtypes, types.ModuleType)
    +
    +assert_type(np.lib.format, types.ModuleType)
    +assert_type(np.lib.mixins, types.ModuleType)
    +assert_type(np.lib.scimath, types.ModuleType)
    +assert_type(np.lib.stride_tricks, types.ModuleType)
    +assert_type(np.ma.extras, types.ModuleType)
    +assert_type(np.polynomial.chebyshev, types.ModuleType)
    +assert_type(np.polynomial.hermite, types.ModuleType)
    +assert_type(np.polynomial.hermite_e, types.ModuleType)
    +assert_type(np.polynomial.laguerre, types.ModuleType)
    +assert_type(np.polynomial.legendre, types.ModuleType)
    +assert_type(np.polynomial.polynomial, types.ModuleType)
    +
    +assert_type(np.__path__, list[str])
    +assert_type(np.__version__, str)
    +assert_type(np.test, np._pytesttester.PytestTester)
    +assert_type(np.test.module_name, str)
    +
    +assert_type(np.__all__, list[str])
    +assert_type(np.char.__all__, list[str])
    +assert_type(np.ctypeslib.__all__, list[str])
    +assert_type(np.emath.__all__, list[str])
    +assert_type(np.lib.__all__, list[str])
    +assert_type(np.ma.__all__, list[str])
    +assert_type(np.random.__all__, list[str])
    +assert_type(np.rec.__all__, list[str])
    +assert_type(np.testing.__all__, list[str])
    +assert_type(f2py.__all__, list[str])
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/multiarray.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/multiarray.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..424f60df27e7ce49d894415add02579f89dc69f9
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/multiarray.pyi
    @@ -0,0 +1,197 @@
    +import datetime as dt
    +from typing import Any, Literal, TypeVar, assert_type
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +_ScalarT_co = TypeVar("_ScalarT_co", bound=np.generic, covariant=True)
    +
    +class SubClass(npt.NDArray[_ScalarT_co]): ...
    +
    +subclass: SubClass[np.float64]
    +
    +AR_f8: npt.NDArray[np.float64]
    +AR_i8: npt.NDArray[np.int64]
    +AR_u1: npt.NDArray[np.uint8]
    +AR_m: npt.NDArray[np.timedelta64]
    +AR_M: npt.NDArray[np.datetime64]
    +
    +AR_LIKE_f: list[float]
    +AR_LIKE_i: list[int]
    +
    +m: np.timedelta64
    +M: np.datetime64
    +
    +b_f8 = np.broadcast(AR_f8)
    +b_i8_f8_f8 = np.broadcast(AR_i8, AR_f8, AR_f8)
    +
    +nditer_obj: np.nditer
    +
    +date_scalar: dt.date
    +date_seq: list[dt.date]
    +timedelta_seq: list[dt.timedelta]
    +
    +n1: Literal[1]
    +n2: Literal[2]
    +n3: Literal[3]
    +
    +f8: np.float64
    +
    +def func11(a: int) -> bool: ...
    +def func21(a: int, b: int) -> int: ...
    +def func12(a: int) -> tuple[complex, bool]: ...
    +
    +assert_type(next(b_f8), tuple[Any, ...])
    +assert_type(b_f8.reset(), None)
    +assert_type(b_f8.index, int)
    +assert_type(b_f8.iters, tuple[np.flatiter[Any], ...])
    +assert_type(b_f8.nd, int)
    +assert_type(b_f8.ndim, int)
    +assert_type(b_f8.numiter, int)
    +assert_type(b_f8.shape, tuple[Any, ...])
    +assert_type(b_f8.size, int)
    +
    +assert_type(next(b_i8_f8_f8), tuple[Any, ...])
    +assert_type(b_i8_f8_f8.reset(), None)
    +assert_type(b_i8_f8_f8.index, int)
    +assert_type(b_i8_f8_f8.iters, tuple[np.flatiter[Any], ...])
    +assert_type(b_i8_f8_f8.nd, int)
    +assert_type(b_i8_f8_f8.ndim, int)
    +assert_type(b_i8_f8_f8.numiter, int)
    +assert_type(b_i8_f8_f8.shape, tuple[Any, ...])
    +assert_type(b_i8_f8_f8.size, int)
    +
    +assert_type(np.inner(AR_f8, AR_i8), Any)
    +
    +assert_type(np.where([True, True, False]), tuple[npt.NDArray[np.intp], ...])
    +assert_type(np.where([True, True, False], 1, 0), npt.NDArray[Any])
    +
    +assert_type(np.lexsort([0, 1, 2]), npt.NDArray[np.intp])
    +
    +assert_type(np.can_cast(np.dtype("i8"), int), bool)
    +assert_type(np.can_cast(AR_f8, "f8"), bool)
    +assert_type(np.can_cast(AR_f8, np.complex128, casting="unsafe"), bool)
    +
    +assert_type(np.min_scalar_type([1]), np.dtype)
    +assert_type(np.min_scalar_type(AR_f8), np.dtype)
    +
    +assert_type(np.result_type(int, [1]), np.dtype)
    +assert_type(np.result_type(AR_f8, AR_u1), np.dtype)
    +assert_type(np.result_type(AR_f8, np.complex128), np.dtype)
    +
    +assert_type(np.dot(AR_LIKE_f, AR_i8), Any)
    +assert_type(np.dot(AR_u1, 1), Any)
    +assert_type(np.dot(1.5j, 1), Any)
    +assert_type(np.dot(AR_u1, 1, out=AR_f8), npt.NDArray[np.float64])
    +
    +assert_type(np.vdot(AR_LIKE_f, AR_i8), np.floating)
    +assert_type(np.vdot(AR_u1, 1), np.signedinteger)
    +assert_type(np.vdot(1.5j, 1), np.complexfloating)
    +
    +assert_type(np.bincount(AR_i8), npt.NDArray[np.intp])
    +
    +assert_type(np.copyto(AR_f8, [1., 1.5, 1.6]), None)
    +
    +assert_type(np.putmask(AR_f8, [True, True, False], 1.5), None)
    +
    +assert_type(np.packbits(AR_i8),  np.ndarray[tuple[int], np.dtype[np.uint8]])
    +assert_type(np.packbits(AR_u1),  np.ndarray[tuple[int], np.dtype[np.uint8]])
    +assert_type(np.packbits(AR_i8, axis=1), npt.NDArray[np.uint8])
    +assert_type(np.packbits(AR_u1, axis=1), npt.NDArray[np.uint8])
    +
    +assert_type(np.unpackbits(AR_u1), np.ndarray[tuple[int], np.dtype[np.uint8]])
    +assert_type(np.unpackbits(AR_u1, axis=1), npt.NDArray[np.uint8])
    +
    +assert_type(np.shares_memory(1, 2), bool)
    +assert_type(np.shares_memory(AR_f8, AR_f8, max_work=-1), bool)
    +
    +assert_type(np.may_share_memory(1, 2), bool)
    +assert_type(np.may_share_memory(AR_f8, AR_f8, max_work=0), bool)
    +
    +assert_type(np.promote_types(np.int32, np.int64), np.dtype)
    +assert_type(np.promote_types("f4", float), np.dtype)
    +
    +assert_type(np.frompyfunc(func11, n1, n1).nin, Literal[1])
    +assert_type(np.frompyfunc(func11, n1, n1).nout, Literal[1])
    +assert_type(np.frompyfunc(func11, n1, n1).nargs, Literal[2])
    +assert_type(np.frompyfunc(func11, n1, n1).ntypes, Literal[1])
    +assert_type(np.frompyfunc(func11, n1, n1).identity, None)
    +assert_type(np.frompyfunc(func11, n1, n1).signature, None)
    +assert_type(np.frompyfunc(func11, n1, n1)(f8), bool)
    +assert_type(np.frompyfunc(func11, n1, n1)(AR_f8), bool | npt.NDArray[np.object_])
    +assert_type(np.frompyfunc(func11, n1, n1).at(AR_f8, AR_i8), None)
    +
    +assert_type(np.frompyfunc(func21, n2, n1).nin, Literal[2])
    +assert_type(np.frompyfunc(func21, n2, n1).nout, Literal[1])
    +assert_type(np.frompyfunc(func21, n2, n1).nargs, Literal[3])
    +assert_type(np.frompyfunc(func21, n2, n1).ntypes, Literal[1])
    +assert_type(np.frompyfunc(func21, n2, n1).identity, None)
    +assert_type(np.frompyfunc(func21, n2, n1).signature, None)
    +assert_type(np.frompyfunc(func21, n2, n1)(f8, f8), int)
    +assert_type(np.frompyfunc(func21, n2, n1)(AR_f8, f8), int | npt.NDArray[np.object_])
    +assert_type(np.frompyfunc(func21, n2, n1)(f8, AR_f8), int | npt.NDArray[np.object_])
    +assert_type(np.frompyfunc(func21, n2, n1).reduce(AR_f8, axis=0), int | npt.NDArray[np.object_])
    +assert_type(np.frompyfunc(func21, n2, n1).accumulate(AR_f8), npt.NDArray[np.object_])
    +assert_type(np.frompyfunc(func21, n2, n1).reduceat(AR_f8, AR_i8), npt.NDArray[np.object_])
    +assert_type(np.frompyfunc(func21, n2, n1).outer(f8, f8), int)
    +assert_type(np.frompyfunc(func21, n2, n1).outer(AR_f8, f8), int | npt.NDArray[np.object_])
    +
    +assert_type(np.frompyfunc(func21, n2, n1, identity=0).nin, Literal[2])
    +assert_type(np.frompyfunc(func21, n2, n1, identity=0).nout, Literal[1])
    +assert_type(np.frompyfunc(func21, n2, n1, identity=0).nargs, Literal[3])
    +assert_type(np.frompyfunc(func21, n2, n1, identity=0).ntypes, Literal[1])
    +assert_type(np.frompyfunc(func21, n2, n1, identity=0).identity, int)
    +assert_type(np.frompyfunc(func21, n2, n1, identity=0).signature, None)
    +
    +assert_type(np.frompyfunc(func12, n1, n2).nin, Literal[1])
    +assert_type(np.frompyfunc(func12, n1, n2).nout, Literal[2])
    +assert_type(np.frompyfunc(func12, n1, n2).nargs, int)
    +assert_type(np.frompyfunc(func12, n1, n2).ntypes, Literal[1])
    +assert_type(np.frompyfunc(func12, n1, n2).identity, None)
    +assert_type(np.frompyfunc(func12, n1, n2).signature, None)
    +assert_type(
    +    np.frompyfunc(func12, n2, n2)(f8, f8),
    +    tuple[complex, complex, *tuple[complex, ...]],
    +)
    +assert_type(
    +    np.frompyfunc(func12, n2, n2)(AR_f8, f8),
    +    tuple[
    +        complex | npt.NDArray[np.object_],
    +        complex | npt.NDArray[np.object_],
    +        *tuple[complex | npt.NDArray[np.object_], ...],
    +    ],
    +)
    +
    +assert_type(np.datetime_data("m8[D]"), tuple[str, int])
    +assert_type(np.datetime_data(np.datetime64), tuple[str, int])
    +assert_type(np.datetime_data(np.dtype(np.timedelta64)), tuple[str, int])
    +
    +assert_type(np.busday_count("2011-01", "2011-02"), np.int_)
    +assert_type(np.busday_count(["2011-01"], "2011-02"), npt.NDArray[np.int_])
    +assert_type(np.busday_count(["2011-01"], date_scalar), npt.NDArray[np.int_])
    +
    +assert_type(np.busday_offset(M, m), np.datetime64)
    +assert_type(np.busday_offset(date_scalar, m), np.datetime64)
    +assert_type(np.busday_offset(M, 5), np.datetime64)
    +assert_type(np.busday_offset(AR_M, m), npt.NDArray[np.datetime64])
    +assert_type(np.busday_offset(M, timedelta_seq), npt.NDArray[np.datetime64])
    +assert_type(np.busday_offset("2011-01", "2011-02", roll="forward"), np.datetime64)
    +assert_type(np.busday_offset(["2011-01"], "2011-02", roll="forward"), npt.NDArray[np.datetime64])
    +
    +assert_type(np.is_busday("2012"), np.bool)
    +assert_type(np.is_busday(date_scalar), np.bool)
    +assert_type(np.is_busday(["2012"]), npt.NDArray[np.bool])
    +
    +assert_type(np.datetime_as_string(M), np.str_)
    +assert_type(np.datetime_as_string(AR_M), npt.NDArray[np.str_])
    +
    +assert_type(np.busdaycalendar(holidays=date_seq), np.busdaycalendar)
    +assert_type(np.busdaycalendar(holidays=[M]), np.busdaycalendar)
    +
    +assert_type(np.char.compare_chararrays("a", "b", "!=", rstrip=False), npt.NDArray[np.bool])
    +assert_type(np.char.compare_chararrays(b"a", b"a", "==", True), npt.NDArray[np.bool])
    +
    +assert_type(np.nested_iters([AR_i8, AR_i8], [[0], [1]], flags=["c_index"]), tuple[np.nditer, ...])
    +assert_type(np.nested_iters([AR_i8, AR_i8], [[0], [1]], op_flags=[["readonly", "readonly"]]), tuple[np.nditer, ...])
    +assert_type(np.nested_iters([AR_i8, AR_i8], [[0], [1]], op_dtypes=np.int_), tuple[np.nditer, ...])
    +assert_type(np.nested_iters([AR_i8, AR_i8], [[0], [1]], order="C", casting="no"), tuple[np.nditer, ...])
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/nbit_base_example.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/nbit_base_example.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..66470b95bf156dcc36af597d01adab6fcc26a45b
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/nbit_base_example.pyi
    @@ -0,0 +1,20 @@
    +from typing import TypeVar, assert_type
    +
    +import numpy as np
    +import numpy.typing as npt
    +from numpy._typing import _32Bit, _64Bit
    +
    +T1 = TypeVar("T1", bound=npt.NBitBase)  # type: ignore[deprecated]  # pyright: ignore[reportDeprecated]
    +T2 = TypeVar("T2", bound=npt.NBitBase)  # type: ignore[deprecated]  # pyright: ignore[reportDeprecated]
    +
    +def add(a: np.floating[T1], b: np.integer[T2]) -> np.floating[T1 | T2]: ...
    +
    +i8: np.int64
    +i4: np.int32
    +f8: np.float64
    +f4: np.float32
    +
    +assert_type(add(f8, i8), np.floating[_64Bit])
    +assert_type(add(f4, i8), np.floating[_32Bit | _64Bit])
    +assert_type(add(f8, i4), np.floating[_32Bit | _64Bit])
    +assert_type(add(f4, i4), np.floating[_32Bit])
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/ndarray_assignability.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/ndarray_assignability.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..feaccf28f57830e2f258bc82a9ccd53c04d5db57
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/ndarray_assignability.pyi
    @@ -0,0 +1,82 @@
    +from typing import Any, Protocol, TypeAlias, TypeVar, assert_type
    +
    +import numpy as np
    +from numpy._typing import _64Bit
    +
    +_T = TypeVar("_T")
    +_T_co = TypeVar("_T_co", covariant=True)
    +
    +class CanAbs(Protocol[_T_co]):
    +    def __abs__(self, /) -> _T_co: ...
    +
    +class CanInvert(Protocol[_T_co]):
    +    def __invert__(self, /) -> _T_co: ...
    +
    +class CanNeg(Protocol[_T_co]):
    +    def __neg__(self, /) -> _T_co: ...
    +
    +class CanPos(Protocol[_T_co]):
    +    def __pos__(self, /) -> _T_co: ...
    +
    +def do_abs(x: CanAbs[_T]) -> _T: ...
    +def do_invert(x: CanInvert[_T]) -> _T: ...
    +def do_neg(x: CanNeg[_T]) -> _T: ...
    +def do_pos(x: CanPos[_T]) -> _T: ...
    +
    +_Bool_1d: TypeAlias = np.ndarray[tuple[int], np.dtype[np.bool]]
    +_UInt8_1d: TypeAlias = np.ndarray[tuple[int], np.dtype[np.uint8]]
    +_Int16_1d: TypeAlias = np.ndarray[tuple[int], np.dtype[np.int16]]
    +_LongLong_1d: TypeAlias = np.ndarray[tuple[int], np.dtype[np.longlong]]
    +_Float32_1d: TypeAlias = np.ndarray[tuple[int], np.dtype[np.float32]]
    +_Float64_1d: TypeAlias = np.ndarray[tuple[int], np.dtype[np.float64]]
    +_LongDouble_1d: TypeAlias = np.ndarray[tuple[int], np.dtype[np.longdouble]]
    +_Complex64_1d: TypeAlias = np.ndarray[tuple[int], np.dtype[np.complex64]]
    +_Complex128_1d: TypeAlias = np.ndarray[tuple[int], np.dtype[np.complex128]]
    +_CLongDouble_1d: TypeAlias = np.ndarray[tuple[int], np.dtype[np.clongdouble]]
    +_Void_1d: TypeAlias = np.ndarray[tuple[int], np.dtype[np.void]]
    +
    +b1_1d: _Bool_1d
    +u1_1d: _UInt8_1d
    +i2_1d: _Int16_1d
    +q_1d: _LongLong_1d
    +f4_1d: _Float32_1d
    +f8_1d: _Float64_1d
    +g_1d: _LongDouble_1d
    +c8_1d: _Complex64_1d
    +c16_1d: _Complex128_1d
    +G_1d: _CLongDouble_1d
    +V_1d: _Void_1d
    +
    +assert_type(do_abs(b1_1d), _Bool_1d)
    +assert_type(do_abs(u1_1d), _UInt8_1d)
    +assert_type(do_abs(i2_1d), _Int16_1d)
    +assert_type(do_abs(q_1d), _LongLong_1d)
    +assert_type(do_abs(f4_1d), _Float32_1d)
    +assert_type(do_abs(f8_1d), _Float64_1d)
    +assert_type(do_abs(g_1d), _LongDouble_1d)
    +
    +assert_type(do_abs(c8_1d), _Float32_1d)
    +# NOTE: Unfortunately it's not possible to have this return a `float64` sctype, see
    +# https://github.com/python/mypy/issues/14070
    +assert_type(do_abs(c16_1d), np.ndarray[tuple[int], np.dtype[np.floating[_64Bit]]])
    +assert_type(do_abs(G_1d), _LongDouble_1d)
    +
    +assert_type(do_invert(b1_1d), _Bool_1d)
    +assert_type(do_invert(u1_1d), _UInt8_1d)
    +assert_type(do_invert(i2_1d), _Int16_1d)
    +assert_type(do_invert(q_1d), _LongLong_1d)
    +
    +assert_type(do_neg(u1_1d), _UInt8_1d)
    +assert_type(do_neg(i2_1d), _Int16_1d)
    +assert_type(do_neg(q_1d), _LongLong_1d)
    +assert_type(do_neg(f4_1d), _Float32_1d)
    +assert_type(do_neg(c16_1d), _Complex128_1d)
    +
    +assert_type(do_pos(u1_1d), _UInt8_1d)
    +assert_type(do_pos(i2_1d), _Int16_1d)
    +assert_type(do_pos(q_1d), _LongLong_1d)
    +assert_type(do_pos(f4_1d), _Float32_1d)
    +assert_type(do_pos(c16_1d), _Complex128_1d)
    +
    +# this shape is effectively equivalent to `tuple[int, *tuple[Any, ...]]`, i.e. ndim >= 1
    +assert_type(V_1d["field"], np.ndarray[tuple[int] | tuple[Any, ...]])
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/ndarray_conversion.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/ndarray_conversion.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..2af616440c5e7b35ab4697b520fb879c0aebfde9
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/ndarray_conversion.pyi
    @@ -0,0 +1,83 @@
    +from typing import Any, assert_type
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +b1_0d: np.ndarray[tuple[()], np.dtype[np.bool]]
    +u2_1d: np.ndarray[tuple[int], np.dtype[np.uint16]]
    +i4_2d: np.ndarray[tuple[int, int], np.dtype[np.int32]]
    +f8_3d: np.ndarray[tuple[int, int, int], np.dtype[np.float64]]
    +cG_4d: np.ndarray[tuple[int, int, int, int], np.dtype[np.clongdouble]]
    +i0_nd: npt.NDArray[np.int_]
    +uncertain_dtype: np.int32 | np.float64 | np.str_
    +
    +# item
    +assert_type(i0_nd.item(), int)
    +assert_type(i0_nd.item(1), int)
    +assert_type(i0_nd.item(0, 1), int)
    +assert_type(i0_nd.item((0, 1)), int)
    +
    +assert_type(b1_0d.item(()), bool)
    +assert_type(u2_1d.item((0,)), int)
    +assert_type(i4_2d.item(-1, 2), int)
    +assert_type(f8_3d.item(2, 1, -1), float)
    +assert_type(cG_4d.item(-0xEd_fed_Deb_a_dead_bee), complex)  # c'mon Ed, we talked about this...
    +
    +# tolist
    +assert_type(b1_0d.tolist(), bool)
    +assert_type(u2_1d.tolist(), list[int])
    +assert_type(i4_2d.tolist(), list[list[int]])
    +assert_type(f8_3d.tolist(), list[list[list[float]]])
    +assert_type(cG_4d.tolist(), Any)
    +assert_type(i0_nd.tolist(), Any)
    +
    +# regression tests for numpy/numpy#27944
    +any_dtype: np.ndarray[Any, Any]
    +any_sctype: np.ndarray[Any, Any]
    +assert_type(any_dtype.tolist(), Any)
    +assert_type(any_sctype.tolist(), Any)
    +
    +# tobytes is pretty simple
    +# tofile does not return a value
    +# dump does not return a value
    +# dumps is pretty simple
    +
    +# astype
    +assert_type(i0_nd.astype("float"), npt.NDArray[Any])
    +assert_type(i0_nd.astype(float), npt.NDArray[Any])
    +assert_type(i0_nd.astype(np.float64), npt.NDArray[np.float64])
    +assert_type(i0_nd.astype(np.float64, "K"), npt.NDArray[np.float64])
    +assert_type(i0_nd.astype(np.float64, "K", "unsafe"), npt.NDArray[np.float64])
    +assert_type(i0_nd.astype(np.float64, "K", "unsafe", True), npt.NDArray[np.float64])
    +assert_type(i0_nd.astype(np.float64, "K", "unsafe", True, True), npt.NDArray[np.float64])
    +
    +assert_type(np.astype(i0_nd, np.float64), npt.NDArray[np.float64])
    +
    +assert_type(i4_2d.astype(np.uint16), np.ndarray[tuple[int, int], np.dtype[np.uint16]])
    +assert_type(np.astype(i4_2d, np.uint16), np.ndarray[tuple[int, int], np.dtype[np.uint16]])
    +assert_type(f8_3d.astype(np.int16), np.ndarray[tuple[int, int, int], np.dtype[np.int16]])
    +assert_type(np.astype(f8_3d, np.int16), np.ndarray[tuple[int, int, int], np.dtype[np.int16]])
    +assert_type(i4_2d.astype(uncertain_dtype), np.ndarray[tuple[int, int], np.dtype[np.generic]])
    +assert_type(np.astype(i4_2d, uncertain_dtype), np.ndarray[tuple[int, int], np.dtype])
    +
    +# byteswap
    +assert_type(i0_nd.byteswap(), npt.NDArray[np.int_])
    +assert_type(i0_nd.byteswap(True), npt.NDArray[np.int_])
    +
    +# copy
    +assert_type(i0_nd.copy(), npt.NDArray[np.int_])
    +assert_type(i0_nd.copy("C"), npt.NDArray[np.int_])
    +
    +assert_type(i0_nd.view(), npt.NDArray[np.int_])
    +assert_type(i0_nd.view(np.float64), npt.NDArray[np.float64])
    +assert_type(i0_nd.view(float), npt.NDArray[Any])
    +assert_type(i0_nd.view(np.float64, np.matrix), np.matrix)
    +
    +# getfield
    +assert_type(i0_nd.getfield("float"), npt.NDArray[Any])
    +assert_type(i0_nd.getfield(float), npt.NDArray[Any])
    +assert_type(i0_nd.getfield(np.float64), npt.NDArray[np.float64])
    +assert_type(i0_nd.getfield(np.float64, 8), npt.NDArray[np.float64])
    +
    +# setflags does not return a value
    +# fill does not return a value
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/ndarray_misc.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/ndarray_misc.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..2972a58c328f311a2b7dc5d9a9c90737b9f6c04f
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/ndarray_misc.pyi
    @@ -0,0 +1,246 @@
    +"""
    +Tests for miscellaneous (non-magic) ``np.ndarray``/``np.generic`` methods.
    +
    +More extensive tests are performed for the methods'
    +function-based counterpart in `../from_numeric.py`.
    +
    +"""
    +
    +import ctypes as ct
    +import operator
    +from collections.abc import Iterator
    +from types import ModuleType
    +from typing import Any, Literal, assert_type
    +from typing_extensions import CapsuleType
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +class SubClass(npt.NDArray[np.object_]): ...
    +
    +f8: np.float64
    +i8: np.int64
    +B: SubClass
    +AR_f8: npt.NDArray[np.float64]
    +AR_i8: npt.NDArray[np.int64]
    +AR_u1: npt.NDArray[np.uint8]
    +AR_c8: npt.NDArray[np.complex64]
    +AR_m: npt.NDArray[np.timedelta64]
    +AR_U: npt.NDArray[np.str_]
    +AR_V: npt.NDArray[np.void]
    +
    +AR_f8_1d: np.ndarray[tuple[int], np.dtype[np.float64]]
    +AR_f8_2d: np.ndarray[tuple[int, int], np.dtype[np.float64]]
    +AR_f8_3d: np.ndarray[tuple[int, int, int], np.dtype[np.float64]]
    +
    +ctypes_obj = AR_f8.ctypes
    +
    +assert_type(AR_f8.__dlpack__(), CapsuleType)
    +assert_type(AR_f8.__dlpack_device__(), tuple[Literal[1], Literal[0]])
    +
    +assert_type(ctypes_obj.data, int)
    +assert_type(ctypes_obj.shape, ct.Array[np.ctypeslib.c_intp])
    +assert_type(ctypes_obj.strides, ct.Array[np.ctypeslib.c_intp])
    +assert_type(ctypes_obj._as_parameter_, ct.c_void_p)
    +
    +assert_type(ctypes_obj.data_as(ct.c_void_p), ct.c_void_p)
    +assert_type(ctypes_obj.shape_as(ct.c_longlong), ct.Array[ct.c_longlong])
    +assert_type(ctypes_obj.strides_as(ct.c_ubyte), ct.Array[ct.c_ubyte])
    +
    +assert_type(f8.all(), np.bool)
    +assert_type(AR_f8.all(), np.bool)
    +assert_type(AR_f8.all(axis=0), np.bool | npt.NDArray[np.bool])
    +assert_type(AR_f8.all(keepdims=True), np.bool | npt.NDArray[np.bool])
    +assert_type(AR_f8.all(out=B), SubClass)
    +
    +assert_type(f8.any(), np.bool)
    +assert_type(AR_f8.any(), np.bool)
    +assert_type(AR_f8.any(axis=0), np.bool | npt.NDArray[np.bool])
    +assert_type(AR_f8.any(keepdims=True), np.bool | npt.NDArray[np.bool])
    +assert_type(AR_f8.any(out=B), SubClass)
    +
    +assert_type(f8.argmax(), np.intp)
    +assert_type(AR_f8.argmax(), np.intp)
    +assert_type(AR_f8.argmax(axis=0), Any)
    +assert_type(AR_f8.argmax(out=AR_i8), npt.NDArray[np.intp])
    +
    +assert_type(f8.argmin(), np.intp)
    +assert_type(AR_f8.argmin(), np.intp)
    +assert_type(AR_f8.argmin(axis=0), Any)
    +assert_type(AR_f8.argmin(out=AR_i8), npt.NDArray[np.intp])
    +
    +assert_type(f8.argsort(), npt.NDArray[np.intp])
    +assert_type(AR_f8.argsort(), npt.NDArray[np.intp])
    +
    +assert_type(f8.astype(np.int64).choose([()]), npt.NDArray[Any])
    +assert_type(AR_f8.choose([0]), npt.NDArray[Any])
    +assert_type(AR_f8.choose([0], out=B), SubClass)
    +
    +assert_type(f8.clip(1), npt.NDArray[Any])
    +assert_type(AR_f8.clip(1), npt.NDArray[Any])
    +assert_type(AR_f8.clip(None, 1), npt.NDArray[Any])
    +assert_type(AR_f8.clip(1, out=B), SubClass)
    +assert_type(AR_f8.clip(None, 1, out=B), SubClass)
    +
    +assert_type(f8.compress([0]), npt.NDArray[Any])
    +assert_type(AR_f8.compress([0]), npt.NDArray[Any])
    +assert_type(AR_f8.compress([0], out=B), SubClass)
    +
    +assert_type(f8.conj(), np.float64)
    +assert_type(AR_f8.conj(), npt.NDArray[np.float64])
    +assert_type(B.conj(), SubClass)
    +
    +assert_type(f8.conjugate(), np.float64)
    +assert_type(AR_f8.conjugate(), npt.NDArray[np.float64])
    +assert_type(B.conjugate(), SubClass)
    +
    +assert_type(f8.cumprod(), npt.NDArray[Any])
    +assert_type(AR_f8.cumprod(), npt.NDArray[Any])
    +assert_type(AR_f8.cumprod(out=B), SubClass)
    +
    +assert_type(f8.cumsum(), npt.NDArray[Any])
    +assert_type(AR_f8.cumsum(), npt.NDArray[Any])
    +assert_type(AR_f8.cumsum(out=B), SubClass)
    +
    +assert_type(f8.max(), Any)
    +assert_type(AR_f8.max(), Any)
    +assert_type(AR_f8.max(axis=0), Any)
    +assert_type(AR_f8.max(keepdims=True), Any)
    +assert_type(AR_f8.max(out=B), SubClass)
    +
    +assert_type(f8.mean(), Any)
    +assert_type(AR_f8.mean(), Any)
    +assert_type(AR_f8.mean(axis=0), Any)
    +assert_type(AR_f8.mean(keepdims=True), Any)
    +assert_type(AR_f8.mean(out=B), SubClass)
    +
    +assert_type(f8.min(), Any)
    +assert_type(AR_f8.min(), Any)
    +assert_type(AR_f8.min(axis=0), Any)
    +assert_type(AR_f8.min(keepdims=True), Any)
    +assert_type(AR_f8.min(out=B), SubClass)
    +
    +assert_type(f8.prod(), Any)
    +assert_type(AR_f8.prod(), Any)
    +assert_type(AR_f8.prod(axis=0), Any)
    +assert_type(AR_f8.prod(keepdims=True), Any)
    +assert_type(AR_f8.prod(out=B), SubClass)
    +
    +assert_type(f8.round(), np.float64)
    +assert_type(AR_f8.round(), npt.NDArray[np.float64])
    +assert_type(AR_f8.round(out=B), SubClass)
    +
    +assert_type(f8.repeat(1), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(f8.repeat(1, axis=0), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(AR_f8.repeat(1), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(AR_f8.repeat(1, axis=0), npt.NDArray[np.float64])
    +assert_type(B.repeat(1), np.ndarray[tuple[int], np.dtype[np.object_]])
    +assert_type(B.repeat(1, axis=0), npt.NDArray[np.object_])
    +
    +assert_type(f8.std(), Any)
    +assert_type(AR_f8.std(), Any)
    +assert_type(AR_f8.std(axis=0), Any)
    +assert_type(AR_f8.std(keepdims=True), Any)
    +assert_type(AR_f8.std(out=B), SubClass)
    +
    +assert_type(f8.sum(), Any)
    +assert_type(AR_f8.sum(), Any)
    +assert_type(AR_f8.sum(axis=0), Any)
    +assert_type(AR_f8.sum(keepdims=True), Any)
    +assert_type(AR_f8.sum(out=B), SubClass)
    +
    +assert_type(f8.take(0), np.float64)
    +assert_type(AR_f8.take(0), np.float64)
    +assert_type(AR_f8.take([0]), npt.NDArray[np.float64])
    +assert_type(AR_f8.take(0, out=B), SubClass)
    +assert_type(AR_f8.take([0], out=B), SubClass)
    +
    +assert_type(f8.var(), Any)
    +assert_type(AR_f8.var(), Any)
    +assert_type(AR_f8.var(axis=0), Any)
    +assert_type(AR_f8.var(keepdims=True), Any)
    +assert_type(AR_f8.var(out=B), SubClass)
    +
    +assert_type(AR_f8.argpartition([0]), npt.NDArray[np.intp])
    +
    +assert_type(AR_f8.diagonal(), npt.NDArray[np.float64])
    +
    +assert_type(AR_f8.dot(1), npt.NDArray[Any])
    +assert_type(AR_f8.dot([1]), Any)
    +assert_type(AR_f8.dot(1, out=B), SubClass)
    +
    +assert_type(AR_f8.nonzero(), tuple[np.ndarray[tuple[int], np.dtype[np.intp]], ...])
    +
    +assert_type(AR_f8.searchsorted(1), np.intp)
    +assert_type(AR_f8.searchsorted([1]), npt.NDArray[np.intp])
    +
    +assert_type(AR_f8.trace(), Any)
    +assert_type(AR_f8.trace(out=B), SubClass)
    +
    +assert_type(AR_f8.item(), float)
    +assert_type(AR_U.item(), str)
    +
    +assert_type(AR_f8.ravel(), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(AR_U.ravel(), np.ndarray[tuple[int], np.dtype[np.str_]])
    +
    +assert_type(AR_f8.flatten(), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(AR_U.flatten(), np.ndarray[tuple[int], np.dtype[np.str_]])
    +
    +assert_type(AR_i8.reshape(None), npt.NDArray[np.int64])
    +assert_type(AR_f8.reshape(-1), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(AR_c8.reshape(2, 3, 4, 5), np.ndarray[tuple[int, int, int, int], np.dtype[np.complex64]])
    +assert_type(AR_m.reshape(()), np.ndarray[tuple[()], np.dtype[np.timedelta64]])
    +assert_type(AR_U.reshape([]), np.ndarray[tuple[()], np.dtype[np.str_]])
    +assert_type(AR_V.reshape((480, 720, 4)), np.ndarray[tuple[int, int, int], np.dtype[np.void]])
    +
    +assert_type(int(AR_f8), int)
    +assert_type(int(AR_U), int)
    +
    +assert_type(float(AR_f8), float)
    +assert_type(float(AR_U), float)
    +
    +assert_type(complex(AR_f8), complex)
    +
    +assert_type(operator.index(AR_i8), int)
    +
    +assert_type(AR_f8.__array_wrap__(B), npt.NDArray[np.object_])
    +
    +assert_type(AR_V[0], Any)
    +assert_type(AR_V[0, 0], Any)
    +assert_type(AR_V[AR_i8], npt.NDArray[np.void])
    +assert_type(AR_V[AR_i8, AR_i8], npt.NDArray[np.void])
    +assert_type(AR_V[AR_i8, None], npt.NDArray[np.void])
    +assert_type(AR_V[0, ...], npt.NDArray[np.void])
    +assert_type(AR_V[[0]], npt.NDArray[np.void])
    +assert_type(AR_V[[0], [0]], npt.NDArray[np.void])
    +assert_type(AR_V[:], npt.NDArray[np.void])
    +assert_type(AR_V["a"], npt.NDArray[Any])
    +assert_type(AR_V[["a", "b"]], npt.NDArray[np.void])
    +
    +assert_type(AR_f8.dump("test_file"), None)
    +assert_type(AR_f8.dump(b"test_file"), None)
    +with open("test_file", "wb") as f:
    +    assert_type(AR_f8.dump(f), None)
    +
    +assert_type(AR_f8.__array_finalize__(None), None)
    +assert_type(AR_f8.__array_finalize__(B), None)
    +assert_type(AR_f8.__array_finalize__(AR_f8), None)
    +
    +assert_type(f8.device, Literal["cpu"])
    +assert_type(AR_f8.device, Literal["cpu"])
    +
    +assert_type(f8.to_device("cpu"), np.float64)
    +assert_type(i8.to_device("cpu"), np.int64)
    +assert_type(AR_f8.to_device("cpu"), npt.NDArray[np.float64])
    +assert_type(AR_i8.to_device("cpu"), npt.NDArray[np.int64])
    +assert_type(AR_u1.to_device("cpu"), npt.NDArray[np.uint8])
    +assert_type(AR_c8.to_device("cpu"), npt.NDArray[np.complex64])
    +assert_type(AR_m.to_device("cpu"), npt.NDArray[np.timedelta64])
    +
    +assert_type(f8.__array_namespace__(), ModuleType)
    +assert_type(AR_f8.__array_namespace__(), ModuleType)
    +
    +assert_type(iter(AR_f8), Iterator[Any])  # any-D
    +assert_type(iter(AR_f8_1d), Iterator[np.float64])  # 1-D
    +assert_type(iter(AR_f8_2d), Iterator[npt.NDArray[np.float64]])  # 2-D
    +assert_type(iter(AR_f8_3d), Iterator[npt.NDArray[np.float64]])  # 3-D
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/ndarray_shape_manipulation.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/ndarray_shape_manipulation.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..0ce599a4031049b366a66b30537c503b612588fe
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/ndarray_shape_manipulation.pyi
    @@ -0,0 +1,47 @@
    +from typing import TypeAlias, assert_type
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +_ArrayND: TypeAlias = npt.NDArray[np.int64]
    +_Array2D: TypeAlias = np.ndarray[tuple[int, int], np.dtype[np.int8]]
    +_Array3D: TypeAlias = np.ndarray[tuple[int, int, int], np.dtype[np.bool]]
    +
    +_nd: _ArrayND
    +_2d: _Array2D
    +_3d: _Array3D
    +
    +# reshape
    +assert_type(_nd.reshape(None), npt.NDArray[np.int64])
    +assert_type(_nd.reshape(4), np.ndarray[tuple[int], np.dtype[np.int64]])
    +assert_type(_nd.reshape((4,)), np.ndarray[tuple[int], np.dtype[np.int64]])
    +assert_type(_nd.reshape(2, 2), np.ndarray[tuple[int, int], np.dtype[np.int64]])
    +assert_type(_nd.reshape((2, 2)), np.ndarray[tuple[int, int], np.dtype[np.int64]])
    +
    +assert_type(_nd.reshape((2, 2), order="C"),  np.ndarray[tuple[int, int], np.dtype[np.int64]])
    +assert_type(_nd.reshape(4, order="C"),  np.ndarray[tuple[int], np.dtype[np.int64]])
    +
    +# resize does not return a value
    +
    +# transpose
    +assert_type(_nd.transpose(), npt.NDArray[np.int64])
    +assert_type(_nd.transpose(1, 0), npt.NDArray[np.int64])
    +assert_type(_nd.transpose((1, 0)), npt.NDArray[np.int64])
    +
    +# swapaxes
    +assert_type(_nd.swapaxes(0, 1), _ArrayND)
    +assert_type(_2d.swapaxes(0, 1), _Array2D)
    +assert_type(_3d.swapaxes(0, 1), _Array3D)
    +
    +# flatten
    +assert_type(_nd.flatten(), np.ndarray[tuple[int], np.dtype[np.int64]])
    +assert_type(_nd.flatten("C"), np.ndarray[tuple[int], np.dtype[np.int64]])
    +
    +# ravel
    +assert_type(_nd.ravel(), np.ndarray[tuple[int], np.dtype[np.int64]])
    +assert_type(_nd.ravel("C"), np.ndarray[tuple[int], np.dtype[np.int64]])
    +
    +# squeeze
    +assert_type(_nd.squeeze(), npt.NDArray[np.int64])
    +assert_type(_nd.squeeze(0), npt.NDArray[np.int64])
    +assert_type(_nd.squeeze((0, 2)), npt.NDArray[np.int64])
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/nditer.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/nditer.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..8965f3c03e6de46740c7b1aa8978f1730a0ac799
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/nditer.pyi
    @@ -0,0 +1,49 @@
    +from typing import Any, assert_type
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +nditer_obj: np.nditer
    +
    +assert_type(np.nditer([0, 1], flags=["c_index"]), np.nditer)
    +assert_type(np.nditer([0, 1], op_flags=[["readonly", "readonly"]]), np.nditer)
    +assert_type(np.nditer([0, 1], op_dtypes=np.int_), np.nditer)
    +assert_type(np.nditer([0, 1], order="C", casting="no"), np.nditer)
    +
    +assert_type(nditer_obj.dtypes, tuple[np.dtype, ...])
    +assert_type(nditer_obj.finished, bool)
    +assert_type(nditer_obj.has_delayed_bufalloc, bool)
    +assert_type(nditer_obj.has_index, bool)
    +assert_type(nditer_obj.has_multi_index, bool)
    +assert_type(nditer_obj.index, int)
    +assert_type(nditer_obj.iterationneedsapi, bool)
    +assert_type(nditer_obj.iterindex, int)
    +assert_type(nditer_obj.iterrange, tuple[int, ...])
    +assert_type(nditer_obj.itersize, int)
    +assert_type(nditer_obj.itviews, tuple[npt.NDArray[Any], ...])
    +assert_type(nditer_obj.multi_index, tuple[int, ...])
    +assert_type(nditer_obj.ndim, int)
    +assert_type(nditer_obj.nop, int)
    +assert_type(nditer_obj.operands, tuple[npt.NDArray[Any], ...])
    +assert_type(nditer_obj.shape, tuple[int, ...])
    +assert_type(nditer_obj.value, tuple[npt.NDArray[Any], ...])
    +
    +assert_type(nditer_obj.close(), None)
    +assert_type(nditer_obj.copy(), np.nditer)
    +assert_type(nditer_obj.debug_print(), None)
    +assert_type(nditer_obj.enable_external_loop(), None)
    +assert_type(nditer_obj.iternext(), bool)
    +assert_type(nditer_obj.remove_axis(0), None)
    +assert_type(nditer_obj.remove_multi_index(), None)
    +assert_type(nditer_obj.reset(), None)
    +
    +assert_type(len(nditer_obj), int)
    +assert_type(iter(nditer_obj), np.nditer)
    +assert_type(next(nditer_obj), tuple[npt.NDArray[Any], ...])
    +assert_type(nditer_obj.__copy__(), np.nditer)
    +with nditer_obj as f:
    +    assert_type(f, np.nditer)
    +assert_type(nditer_obj[0], npt.NDArray[Any])
    +assert_type(nditer_obj[:], tuple[npt.NDArray[Any], ...])
    +nditer_obj[0] = 0
    +nditer_obj[:] = [0, 1]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/nested_sequence.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/nested_sequence.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..b4f98b79c3339fa856a09cd639961b5bb8156fd3
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/nested_sequence.pyi
    @@ -0,0 +1,25 @@
    +from collections.abc import Sequence
    +from typing import Any, assert_type
    +
    +from numpy._typing import _NestedSequence
    +
    +a: Sequence[int]
    +b: Sequence[Sequence[int]]
    +c: Sequence[Sequence[Sequence[int]]]
    +d: Sequence[Sequence[Sequence[Sequence[int]]]]
    +e: Sequence[bool]
    +f: tuple[int, ...]
    +g: list[int]
    +h: Sequence[Any]
    +
    +def func(a: _NestedSequence[int]) -> None: ...
    +
    +assert_type(func(a), None)
    +assert_type(func(b), None)
    +assert_type(func(c), None)
    +assert_type(func(d), None)
    +assert_type(func(e), None)
    +assert_type(func(f), None)
    +assert_type(func(g), None)
    +assert_type(func(h), None)
    +assert_type(func(range(15)), None)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/npyio.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/npyio.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..e3eaa45a5fa1a4cf0366f674e7a1629fac9b9dd8
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/npyio.pyi
    @@ -0,0 +1,83 @@
    +import pathlib
    +import re
    +import zipfile
    +from collections.abc import Mapping
    +from typing import IO, Any, assert_type
    +
    +import numpy as np
    +import numpy.typing as npt
    +from numpy.lib._npyio_impl import BagObj
    +
    +str_path: str
    +pathlib_path: pathlib.Path
    +str_file: IO[str]
    +bytes_file: IO[bytes]
    +
    +npz_file: np.lib.npyio.NpzFile
    +
    +AR_i8: npt.NDArray[np.int64]
    +AR_LIKE_f8: list[float]
    +
    +class BytesWriter:
    +    def write(self, data: bytes) -> None: ...
    +
    +class BytesReader:
    +    def read(self, n: int = ...) -> bytes: ...
    +    def seek(self, offset: int, whence: int = ...) -> int: ...
    +
    +bytes_writer: BytesWriter
    +bytes_reader: BytesReader
    +
    +assert_type(npz_file.zip, zipfile.ZipFile | None)
    +assert_type(npz_file.fid, IO[str] | None)
    +assert_type(npz_file.files, list[str])
    +assert_type(npz_file.allow_pickle, bool)
    +assert_type(npz_file.pickle_kwargs, Mapping[str, Any] | None)
    +assert_type(npz_file.f, BagObj[np.lib.npyio.NpzFile])
    +assert_type(npz_file["test"], npt.NDArray[Any])
    +assert_type(len(npz_file), int)
    +with npz_file as f:
    +    assert_type(f, np.lib.npyio.NpzFile)
    +
    +assert_type(np.load(bytes_file), Any)
    +assert_type(np.load(pathlib_path, allow_pickle=True), Any)
    +assert_type(np.load(str_path, encoding="bytes"), Any)
    +assert_type(np.load(bytes_reader), Any)
    +
    +assert_type(np.save(bytes_file, AR_LIKE_f8), None)
    +assert_type(np.save(pathlib_path, AR_i8, allow_pickle=True), None)
    +assert_type(np.save(str_path, AR_LIKE_f8), None)
    +assert_type(np.save(bytes_writer, AR_LIKE_f8), None)
    +
    +assert_type(np.savez(bytes_file, AR_LIKE_f8), None)
    +assert_type(np.savez(pathlib_path, ar1=AR_i8, ar2=AR_i8), None)
    +assert_type(np.savez(str_path, AR_LIKE_f8, ar1=AR_i8), None)
    +assert_type(np.savez(bytes_writer, AR_LIKE_f8, ar1=AR_i8), None)
    +
    +assert_type(np.savez_compressed(bytes_file, AR_LIKE_f8), None)
    +assert_type(np.savez_compressed(pathlib_path, ar1=AR_i8, ar2=AR_i8), None)
    +assert_type(np.savez_compressed(str_path, AR_LIKE_f8, ar1=AR_i8), None)
    +assert_type(np.savez_compressed(bytes_writer, AR_LIKE_f8, ar1=AR_i8), None)
    +
    +assert_type(np.loadtxt(bytes_file), npt.NDArray[np.float64])
    +assert_type(np.loadtxt(pathlib_path, dtype=np.str_), npt.NDArray[np.str_])
    +assert_type(np.loadtxt(str_path, dtype=str, skiprows=2), npt.NDArray[Any])
    +assert_type(np.loadtxt(str_file, comments="test"), npt.NDArray[np.float64])
    +assert_type(np.loadtxt(str_file, comments=None), npt.NDArray[np.float64])
    +assert_type(np.loadtxt(str_path, delimiter="\n"), npt.NDArray[np.float64])
    +assert_type(np.loadtxt(str_path, ndmin=2), npt.NDArray[np.float64])
    +assert_type(np.loadtxt(["1", "2", "3"]), npt.NDArray[np.float64])
    +
    +assert_type(np.fromregex(bytes_file, "test", np.float64), npt.NDArray[np.float64])
    +assert_type(np.fromregex(str_file, b"test", dtype=float), npt.NDArray[Any])
    +assert_type(np.fromregex(str_path, re.compile("test"), dtype=np.str_, encoding="utf8"), npt.NDArray[np.str_])
    +assert_type(np.fromregex(pathlib_path, "test", np.float64), npt.NDArray[np.float64])
    +assert_type(np.fromregex(bytes_reader, "test", np.float64), npt.NDArray[np.float64])
    +
    +assert_type(np.genfromtxt(bytes_file), npt.NDArray[Any])
    +assert_type(np.genfromtxt(pathlib_path, dtype=np.str_), npt.NDArray[np.str_])
    +assert_type(np.genfromtxt(str_path, dtype=str, skip_header=2), npt.NDArray[Any])
    +assert_type(np.genfromtxt(str_file, comments="test"), npt.NDArray[Any])
    +assert_type(np.genfromtxt(str_path, delimiter="\n"), npt.NDArray[Any])
    +assert_type(np.genfromtxt(str_path, ndmin=2), npt.NDArray[Any])
    +assert_type(np.genfromtxt(["1", "2", "3"], ndmin=2), npt.NDArray[Any])
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/numeric.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/numeric.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..24f97d2d078436f0b99420714e8aef11b1297da3
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/numeric.pyi
    @@ -0,0 +1,170 @@
    +"""
    +Tests for :mod:`_core.numeric`.
    +
    +Does not include tests which fall under ``array_constructors``.
    +
    +"""
    +
    +from typing import Any, assert_type
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +class SubClass(npt.NDArray[np.int64]): ...
    +
    +i8: np.int64
    +
    +AR_b: npt.NDArray[np.bool]
    +AR_u8: npt.NDArray[np.uint64]
    +AR_i8: npt.NDArray[np.int64]
    +AR_f8: npt.NDArray[np.float64]
    +AR_c16: npt.NDArray[np.complex128]
    +AR_m: npt.NDArray[np.timedelta64]
    +AR_O: npt.NDArray[np.object_]
    +
    +_sub_nd_i8: SubClass
    +
    +_to_1d_bool: list[bool]
    +_to_1d_int: list[int]
    +_to_1d_float: list[float]
    +_to_1d_complex: list[complex]
    +
    +###
    +
    +assert_type(np.count_nonzero(i8), np.intp)
    +assert_type(np.count_nonzero(AR_i8), np.intp)
    +assert_type(np.count_nonzero(_to_1d_int), np.intp)
    +assert_type(np.count_nonzero(AR_i8, keepdims=True), npt.NDArray[np.intp])
    +assert_type(np.count_nonzero(AR_i8, axis=0), Any)
    +
    +assert_type(np.isfortran(i8), bool)
    +assert_type(np.isfortran(AR_i8), bool)
    +
    +assert_type(np.argwhere(i8), np.ndarray[tuple[int, int], np.dtype[np.intp]])
    +assert_type(np.argwhere(AR_i8), np.ndarray[tuple[int, int], np.dtype[np.intp]])
    +
    +assert_type(np.flatnonzero(i8), np.ndarray[tuple[int], np.dtype[np.intp]])
    +assert_type(np.flatnonzero(AR_i8), np.ndarray[tuple[int], np.dtype[np.intp]])
    +
    +# correlate
    +assert_type(np.correlate(AR_i8, AR_i8), np.ndarray[tuple[int], np.dtype[np.int64]])
    +assert_type(np.correlate(AR_b, AR_b), np.ndarray[tuple[int], np.dtype[np.bool]])
    +assert_type(np.correlate(AR_u8, AR_u8), np.ndarray[tuple[int], np.dtype[np.uint64]])
    +assert_type(np.correlate(AR_i8, AR_i8), np.ndarray[tuple[int], np.dtype[np.int64]])
    +assert_type(np.correlate(AR_f8, AR_f8), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(np.correlate(AR_f8, AR_i8), np.ndarray[tuple[int], np.dtype[np.float64 | Any]])
    +assert_type(np.correlate(AR_c16, AR_c16), np.ndarray[tuple[int], np.dtype[np.complex128]])
    +assert_type(np.correlate(AR_c16, AR_f8), np.ndarray[tuple[int], np.dtype[np.complex128 | Any]])
    +assert_type(np.correlate(AR_m, AR_m), np.ndarray[tuple[int], np.dtype[np.timedelta64]])
    +assert_type(np.correlate(AR_i8, AR_m), np.ndarray[tuple[int], np.dtype[np.timedelta64 | Any]])
    +assert_type(np.correlate(AR_O, AR_O), np.ndarray[tuple[int], np.dtype[np.object_]])
    +assert_type(np.correlate(_to_1d_bool, _to_1d_bool), np.ndarray[tuple[int], np.dtype[np.bool]])
    +assert_type(np.correlate(_to_1d_int, _to_1d_int), np.ndarray[tuple[int], np.dtype[np.int_ | Any]])
    +assert_type(np.correlate(_to_1d_float, _to_1d_float), np.ndarray[tuple[int], np.dtype[np.float64 | Any]])
    +assert_type(np.correlate(_to_1d_complex, _to_1d_complex), np.ndarray[tuple[int], np.dtype[np.complex128 | Any]])
    +
    +# convolve (same as correlate)
    +assert_type(np.convolve(AR_i8, AR_i8), np.ndarray[tuple[int], np.dtype[np.int64]])
    +assert_type(np.convolve(AR_b, AR_b), np.ndarray[tuple[int], np.dtype[np.bool]])
    +assert_type(np.convolve(AR_u8, AR_u8), np.ndarray[tuple[int], np.dtype[np.uint64]])
    +assert_type(np.convolve(AR_i8, AR_i8), np.ndarray[tuple[int], np.dtype[np.int64]])
    +assert_type(np.convolve(AR_f8, AR_f8), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(np.convolve(AR_f8, AR_i8), np.ndarray[tuple[int], np.dtype[np.float64 | Any]])
    +assert_type(np.convolve(AR_c16, AR_c16), np.ndarray[tuple[int], np.dtype[np.complex128]])
    +assert_type(np.convolve(AR_c16, AR_f8), np.ndarray[tuple[int], np.dtype[np.complex128 | Any]])
    +assert_type(np.convolve(AR_m, AR_m), np.ndarray[tuple[int], np.dtype[np.timedelta64]])
    +assert_type(np.convolve(AR_i8, AR_m), np.ndarray[tuple[int], np.dtype[np.timedelta64 | Any]])
    +assert_type(np.convolve(AR_O, AR_O), np.ndarray[tuple[int], np.dtype[np.object_]])
    +assert_type(np.convolve(_to_1d_bool, _to_1d_bool), np.ndarray[tuple[int], np.dtype[np.bool]])
    +assert_type(np.convolve(_to_1d_int, _to_1d_int), np.ndarray[tuple[int], np.dtype[np.int_ | Any]])
    +assert_type(np.convolve(_to_1d_float, _to_1d_float), np.ndarray[tuple[int], np.dtype[np.float64 | Any]])
    +assert_type(np.convolve(_to_1d_complex, _to_1d_complex), np.ndarray[tuple[int], np.dtype[np.complex128 | Any]])
    +
    +# outer (very similar to above, but 2D output)
    +assert_type(np.outer(AR_i8, AR_i8), np.ndarray[tuple[int, int], np.dtype[np.int64]])
    +assert_type(np.outer(AR_b, AR_b), np.ndarray[tuple[int, int], np.dtype[np.bool]])
    +assert_type(np.outer(AR_u8, AR_u8), np.ndarray[tuple[int, int], np.dtype[np.uint64]])
    +assert_type(np.outer(AR_i8, AR_i8), np.ndarray[tuple[int, int], np.dtype[np.int64]])
    +assert_type(np.outer(AR_f8, AR_f8), np.ndarray[tuple[int, int], np.dtype[np.float64]])
    +assert_type(np.outer(AR_f8, AR_i8), np.ndarray[tuple[int, int], np.dtype[np.float64 | Any]])
    +assert_type(np.outer(AR_c16, AR_c16), np.ndarray[tuple[int, int], np.dtype[np.complex128]])
    +assert_type(np.outer(AR_c16, AR_f8), np.ndarray[tuple[int, int], np.dtype[np.complex128 | Any]])
    +assert_type(np.outer(AR_m, AR_m), np.ndarray[tuple[int, int], np.dtype[np.timedelta64]])
    +assert_type(np.outer(AR_i8, AR_m), np.ndarray[tuple[int, int], np.dtype[np.timedelta64 | Any]])
    +assert_type(np.outer(AR_O, AR_O), np.ndarray[tuple[int, int], np.dtype[np.object_]])
    +assert_type(np.outer(AR_i8, AR_i8, out=_sub_nd_i8), SubClass)
    +assert_type(np.outer(_to_1d_bool, _to_1d_bool), np.ndarray[tuple[int, int], np.dtype[np.bool]])
    +assert_type(np.outer(_to_1d_int, _to_1d_int), np.ndarray[tuple[int, int], np.dtype[np.int_ | Any]])
    +assert_type(np.outer(_to_1d_float, _to_1d_float), np.ndarray[tuple[int, int], np.dtype[np.float64 | Any]])
    +assert_type(np.outer(_to_1d_complex, _to_1d_complex), np.ndarray[tuple[int, int], np.dtype[np.complex128 | Any]])
    +
    +# tensordot
    +assert_type(np.tensordot(AR_i8, AR_i8), npt.NDArray[np.int64])
    +assert_type(np.tensordot(AR_b, AR_b), npt.NDArray[np.bool])
    +assert_type(np.tensordot(AR_u8, AR_u8), npt.NDArray[np.uint64])
    +assert_type(np.tensordot(AR_i8, AR_i8), npt.NDArray[np.int64])
    +assert_type(np.tensordot(AR_f8, AR_f8), npt.NDArray[np.float64])
    +assert_type(np.tensordot(AR_f8, AR_i8), npt.NDArray[np.float64 | Any])
    +assert_type(np.tensordot(AR_c16, AR_c16), npt.NDArray[np.complex128])
    +assert_type(np.tensordot(AR_c16, AR_f8), npt.NDArray[np.complex128 | Any])
    +assert_type(np.tensordot(AR_m, AR_m), npt.NDArray[np.timedelta64])
    +assert_type(np.tensordot(AR_O, AR_O), npt.NDArray[np.object_])
    +assert_type(np.tensordot(_to_1d_bool, _to_1d_bool), npt.NDArray[np.bool])
    +assert_type(np.tensordot(_to_1d_int, _to_1d_int), npt.NDArray[np.int_ | Any])
    +assert_type(np.tensordot(_to_1d_float, _to_1d_float), npt.NDArray[np.float64 | Any])
    +assert_type(np.tensordot(_to_1d_complex, _to_1d_complex), npt.NDArray[np.complex128 | Any])
    +
    +# cross
    +assert_type(np.cross(AR_i8, AR_i8), npt.NDArray[np.int64])
    +assert_type(np.cross(AR_u8, AR_u8), npt.NDArray[np.uint64])
    +assert_type(np.cross(AR_i8, AR_i8), npt.NDArray[np.int64])
    +assert_type(np.cross(AR_f8, AR_f8), npt.NDArray[np.float64])
    +assert_type(np.cross(AR_f8, AR_i8), npt.NDArray[np.float64 | Any])
    +assert_type(np.cross(AR_c16, AR_c16), npt.NDArray[np.complex128])
    +assert_type(np.cross(AR_c16, AR_f8), npt.NDArray[np.complex128 | Any])
    +assert_type(np.cross(AR_m, AR_m), npt.NDArray[np.timedelta64])
    +assert_type(np.cross(AR_O, AR_O), npt.NDArray[np.object_])
    +assert_type(np.cross(_to_1d_int, _to_1d_int), npt.NDArray[np.int_ | Any])
    +assert_type(np.cross(_to_1d_float, _to_1d_float), npt.NDArray[np.float64 | Any])
    +assert_type(np.cross(_to_1d_complex, _to_1d_complex), npt.NDArray[np.complex128 | Any])
    +
    +assert_type(np.isscalar(i8), bool)
    +assert_type(np.isscalar(AR_i8), bool)
    +assert_type(np.isscalar(_to_1d_int), bool)
    +
    +assert_type(np.roll(AR_i8, 1), npt.NDArray[np.int64])
    +assert_type(np.roll(AR_i8, (1, 2)), npt.NDArray[np.int64])
    +assert_type(np.roll(_to_1d_int, 1), npt.NDArray[Any])
    +
    +assert_type(np.rollaxis(AR_i8, 0, 1), npt.NDArray[np.int64])
    +
    +assert_type(np.moveaxis(AR_i8, 0, 1), npt.NDArray[np.int64])
    +assert_type(np.moveaxis(AR_i8, (0, 1), (1, 2)), npt.NDArray[np.int64])
    +
    +assert_type(np.indices([0, 1, 2]), npt.NDArray[np.int_])
    +assert_type(np.indices([0, 1, 2], sparse=True), tuple[npt.NDArray[np.int_], ...])
    +assert_type(np.indices([0, 1, 2], dtype=np.float64), npt.NDArray[np.float64])
    +assert_type(np.indices([0, 1, 2], sparse=True, dtype=np.float64), tuple[npt.NDArray[np.float64], ...])
    +assert_type(np.indices([0, 1, 2], dtype=float), npt.NDArray[Any])
    +assert_type(np.indices([0, 1, 2], sparse=True, dtype=float), tuple[npt.NDArray[Any], ...])
    +
    +assert_type(np.binary_repr(1), str)
    +
    +assert_type(np.base_repr(1), str)
    +
    +assert_type(np.allclose(i8, AR_i8), bool)
    +assert_type(np.allclose(_to_1d_int, AR_i8), bool)
    +assert_type(np.allclose(AR_i8, AR_i8), bool)
    +
    +assert_type(np.isclose(i8, i8), np.bool)
    +assert_type(np.isclose(i8, AR_i8), npt.NDArray[np.bool])
    +assert_type(np.isclose(_to_1d_int, _to_1d_int), np.ndarray[tuple[int], np.dtype[np.bool]])
    +assert_type(np.isclose(AR_i8, AR_i8), npt.NDArray[np.bool])
    +
    +assert_type(np.array_equal(i8, AR_i8), bool)
    +assert_type(np.array_equal(_to_1d_int, AR_i8), bool)
    +assert_type(np.array_equal(AR_i8, AR_i8), bool)
    +
    +assert_type(np.array_equiv(i8, AR_i8), bool)
    +assert_type(np.array_equiv(_to_1d_int, AR_i8), bool)
    +assert_type(np.array_equiv(AR_i8, AR_i8), bool)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/numerictypes.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/numerictypes.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..75d108ce5a0ffb3fdbd6cf4c4712909298ad5780
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/numerictypes.pyi
    @@ -0,0 +1,16 @@
    +from typing import Literal, assert_type
    +
    +import numpy as np
    +
    +assert_type(np.ScalarType[0], type[int])
    +assert_type(np.ScalarType[3], type[bool])
    +assert_type(np.ScalarType[8], type[np.complex64])
    +assert_type(np.ScalarType[9], type[np.complex128])
    +assert_type(np.ScalarType[-1], type[np.void])
    +assert_type(np.bool_(object()), np.bool)
    +
    +assert_type(np.typecodes["Character"], Literal["c"])
    +assert_type(np.typecodes["Complex"], Literal["FDG"])
    +assert_type(np.typecodes["All"], Literal["?bhilqnpBHILQNPefdgFDGSUVOMm"])
    +
    +assert_type(np.sctypeDict["uint8"], type[np.generic])
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/polynomial_polybase.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/polynomial_polybase.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..4c4899ad6308722b14ac67d1585c2b77b58755f6
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/polynomial_polybase.pyi
    @@ -0,0 +1,217 @@
    +from collections.abc import Sequence
    +from decimal import Decimal
    +from typing import Any, Literal as L, TypeAlias, TypeVar, assert_type
    +
    +import numpy as np
    +import numpy.polynomial as npp
    +import numpy.typing as npt
    +
    +_Ar_x: TypeAlias = npt.NDArray[np.inexact | np.object_]
    +_Ar_f: TypeAlias = npt.NDArray[np.floating]
    +_Ar_c: TypeAlias = npt.NDArray[np.complexfloating]
    +_Ar_O: TypeAlias = npt.NDArray[np.object_]
    +
    +_Ar_x_n: TypeAlias = np.ndarray[tuple[int], np.dtype[np.inexact | np.object_]]
    +_Ar_f_n: TypeAlias = np.ndarray[tuple[int], np.dtype[np.floating]]
    +_Ar_c_n: TypeAlias = np.ndarray[tuple[int], np.dtype[np.complexfloating]]
    +_Ar_O_n: TypeAlias = np.ndarray[tuple[int], np.dtype[np.object_]]
    +
    +_Ar_x_2: TypeAlias = np.ndarray[tuple[L[2]], np.dtype[np.float64 | Any]]
    +_Ar_f_2: TypeAlias = np.ndarray[tuple[L[2]], np.dtype[np.floating]]
    +_Ar_c_2: TypeAlias = np.ndarray[tuple[L[2]], np.dtype[np.complexfloating]]
    +_Ar_O_2: TypeAlias = np.ndarray[tuple[L[2]], np.dtype[np.object_]]
    +
    +_ScalarT = TypeVar("_ScalarT", bound=np.generic)
    +_Ar_1d: TypeAlias = np.ndarray[tuple[int], np.dtype[_ScalarT]]
    +
    +_BasisName: TypeAlias = L["X"]
    +
    +SC_i: np.int_
    +SC_i_co: int | np.int_
    +SC_f: np.float64
    +SC_f_co: float | np.float64 | np.int_
    +SC_c: np.complex128
    +SC_c_co: complex | np.complex128
    +SC_O: Decimal
    +
    +AR_i: npt.NDArray[np.int_]
    +AR_f: npt.NDArray[np.float64]
    +AR_f_co: npt.NDArray[np.float64] | npt.NDArray[np.int_]
    +AR_c: npt.NDArray[np.complex128]
    +AR_c_co: npt.NDArray[np.complex128] | npt.NDArray[np.float64] | npt.NDArray[np.int_]
    +AR_O: npt.NDArray[np.object_]
    +AR_O_co: npt.NDArray[np.object_ | np.number]
    +
    +SQ_i: Sequence[int]
    +SQ_f: Sequence[float]
    +SQ_c: Sequence[complex]
    +SQ_O: Sequence[Decimal]
    +
    +PS_poly: npp.Polynomial
    +PS_cheb: npp.Chebyshev
    +PS_herm: npp.Hermite
    +PS_herme: npp.HermiteE
    +PS_lag: npp.Laguerre
    +PS_leg: npp.Legendre
    +PS_all: (
    +    npp.Polynomial
    +    | npp.Chebyshev
    +    | npp.Hermite
    +    | npp.HermiteE
    +    | npp.Laguerre
    +    | npp.Legendre
    +)
    +
    +# static- and classmethods
    +
    +assert_type(type(PS_poly).basis_name, None)
    +assert_type(type(PS_cheb).basis_name, L["T"])
    +assert_type(type(PS_herm).basis_name, L["H"])
    +assert_type(type(PS_herme).basis_name, L["He"])
    +assert_type(type(PS_lag).basis_name, L["L"])
    +assert_type(type(PS_leg).basis_name, L["P"])
    +
    +assert_type(type(PS_all).__hash__, None)
    +assert_type(type(PS_all).__array_ufunc__, None)
    +assert_type(type(PS_all).maxpower, L[100])
    +
    +assert_type(type(PS_poly).fromroots(SC_i), npp.Polynomial)
    +assert_type(type(PS_poly).fromroots(SQ_i), npp.Polynomial)
    +assert_type(type(PS_poly).fromroots(AR_i), npp.Polynomial)
    +assert_type(type(PS_cheb).fromroots(SC_f), npp.Chebyshev)
    +assert_type(type(PS_cheb).fromroots(SQ_f), npp.Chebyshev)
    +assert_type(type(PS_cheb).fromroots(AR_f_co), npp.Chebyshev)
    +assert_type(type(PS_herm).fromroots(SC_c), npp.Hermite)
    +assert_type(type(PS_herm).fromroots(SQ_c), npp.Hermite)
    +assert_type(type(PS_herm).fromroots(AR_c_co), npp.Hermite)
    +assert_type(type(PS_leg).fromroots(SC_O), npp.Legendre)
    +assert_type(type(PS_leg).fromroots(SQ_O), npp.Legendre)
    +assert_type(type(PS_leg).fromroots(AR_O_co), npp.Legendre)
    +
    +assert_type(type(PS_poly).identity(), npp.Polynomial)
    +assert_type(type(PS_cheb).identity(symbol="z"), npp.Chebyshev)
    +
    +assert_type(type(PS_lag).basis(SC_i), npp.Laguerre)
    +assert_type(type(PS_leg).basis(32, symbol="u"), npp.Legendre)
    +
    +assert_type(type(PS_herm).cast(PS_poly), npp.Hermite)
    +assert_type(type(PS_herme).cast(PS_leg), npp.HermiteE)
    +
    +# attributes / properties
    +
    +assert_type(PS_all.coef, _Ar_x_n)
    +assert_type(PS_all.domain, _Ar_x_2)
    +assert_type(PS_all.window, _Ar_x_2)
    +assert_type(PS_all.symbol, str)
    +
    +# instance methods
    +
    +assert_type(PS_all.has_samecoef(PS_all), bool)
    +assert_type(PS_all.has_samedomain(PS_all), bool)
    +assert_type(PS_all.has_samewindow(PS_all), bool)
    +assert_type(PS_all.has_sametype(PS_all), bool)
    +assert_type(PS_poly.has_sametype(PS_poly), bool)
    +assert_type(PS_poly.has_sametype(PS_leg), bool)
    +assert_type(PS_poly.has_sametype(NotADirectoryError), bool)
    +
    +assert_type(PS_poly.copy(), npp.Polynomial)
    +assert_type(PS_cheb.copy(), npp.Chebyshev)
    +assert_type(PS_herm.copy(), npp.Hermite)
    +assert_type(PS_herme.copy(), npp.HermiteE)
    +assert_type(PS_lag.copy(), npp.Laguerre)
    +assert_type(PS_leg.copy(), npp.Legendre)
    +
    +assert_type(PS_leg.cutdeg(3), npp.Legendre)
    +assert_type(PS_leg.trim(), npp.Legendre)
    +assert_type(PS_leg.trim(tol=SC_f_co), npp.Legendre)
    +assert_type(PS_leg.truncate(SC_i_co), npp.Legendre)
    +
    +assert_type(PS_all.convert(None, npp.Chebyshev), npp.Chebyshev)
    +assert_type(PS_all.convert((0, 1), npp.Laguerre), npp.Laguerre)
    +assert_type(PS_all.convert([0, 1], npp.Hermite, [-1, 1]), npp.Hermite)
    +
    +assert_type(PS_all.degree(), int)
    +assert_type(PS_all.mapparms(), tuple[Any, Any])
    +
    +assert_type(PS_poly.integ(), npp.Polynomial)
    +assert_type(PS_herme.integ(SC_i_co), npp.HermiteE)
    +assert_type(PS_lag.integ(SC_i_co, SC_f_co), npp.Laguerre)
    +assert_type(PS_poly.deriv(), npp.Polynomial)
    +assert_type(PS_herm.deriv(SC_i_co), npp.Hermite)
    +
    +assert_type(PS_poly.roots(), _Ar_x_n)
    +
    +assert_type(
    +    PS_poly.linspace(),
    +    tuple[_Ar_1d[np.float64 | np.complex128], _Ar_1d[np.float64 | np.complex128]],
    +)
    +
    +assert_type(
    +    PS_poly.linspace(9),
    +    tuple[_Ar_1d[np.float64 | np.complex128], _Ar_1d[np.float64 | np.complex128]],
    +)
    +
    +assert_type(PS_cheb.fit(AR_c_co, AR_c_co, SC_i_co), npp.Chebyshev)
    +assert_type(PS_leg.fit(AR_c_co, AR_c_co, AR_i), npp.Legendre)
    +assert_type(PS_herm.fit(AR_c_co, AR_c_co, SQ_i), npp.Hermite)
    +assert_type(PS_poly.fit(AR_c_co, SQ_c, SQ_i), npp.Polynomial)
    +assert_type(PS_lag.fit(SQ_c, SQ_c, SQ_i, full=False), npp.Laguerre)
    +assert_type(
    +    PS_herme.fit(SQ_c, AR_c_co, SC_i_co, full=True),
    +    tuple[npp.HermiteE, Sequence[np.inexact | np.int32]],
    +)
    +
    +# custom operations
    +
    +assert_type(PS_all.__hash__, None)
    +assert_type(PS_all.__array_ufunc__, None)
    +
    +assert_type(str(PS_all), str)
    +assert_type(repr(PS_all), str)
    +assert_type(format(PS_all), str)
    +
    +assert_type(len(PS_all), int)
    +assert_type(next(iter(PS_all)), np.float64 | Any)
    +
    +assert_type(PS_all(SC_f_co), np.float64 | Any)
    +assert_type(PS_all(SC_c_co), np.complex128 | Any)
    +assert_type(PS_all(Decimal()), np.float64 | Any)
    +assert_type(PS_poly(SQ_f), npt.NDArray[np.float64 | Any])
    +assert_type(PS_poly(SQ_c), npt.NDArray[np.complex128 | Any])
    +assert_type(PS_poly(SQ_O), npt.NDArray[np.object_])
    +assert_type(PS_poly(AR_f), npt.NDArray[np.float64 | Any])
    +assert_type(PS_poly(AR_c), npt.NDArray[np.complex128 | Any])
    +assert_type(PS_poly(AR_O), npt.NDArray[np.object_])
    +assert_type(PS_all(PS_poly), npp.Polynomial)
    +
    +assert_type(PS_poly == PS_poly, bool)
    +assert_type(PS_poly != PS_poly, bool)
    +
    +assert_type(-PS_poly, npp.Polynomial)
    +assert_type(+PS_poly, npp.Polynomial)
    +
    +assert_type(PS_poly + 5, npp.Polynomial)
    +assert_type(PS_poly - 5, npp.Polynomial)
    +assert_type(PS_poly * 5, npp.Polynomial)
    +assert_type(PS_poly / 5, npp.Polynomial)
    +assert_type(PS_poly // 5, npp.Polynomial)
    +assert_type(PS_poly % 5, npp.Polynomial)
    +
    +assert_type(PS_poly + PS_leg, npp.Polynomial)
    +assert_type(PS_poly - PS_leg, npp.Polynomial)
    +assert_type(PS_poly * PS_leg, npp.Polynomial)
    +assert_type(PS_poly / PS_leg, npp.Polynomial)
    +assert_type(PS_poly // PS_leg, npp.Polynomial)
    +assert_type(PS_poly % PS_leg, npp.Polynomial)
    +
    +assert_type(5 + PS_poly, npp.Polynomial)
    +assert_type(5 - PS_poly, npp.Polynomial)
    +assert_type(5 * PS_poly, npp.Polynomial)
    +assert_type(5 / PS_poly, npp.Polynomial)
    +assert_type(5 // PS_poly, npp.Polynomial)
    +assert_type(5 % PS_poly, npp.Polynomial)
    +assert_type(divmod(PS_poly, 5), tuple[npp.Polynomial, npp.Polynomial])
    +assert_type(divmod(5, PS_poly), tuple[npp.Polynomial, npp.Polynomial])
    +
    +assert_type(PS_poly**1, npp.Polynomial)
    +assert_type(PS_poly**1.0, npp.Polynomial)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/polynomial_polyutils.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/polynomial_polyutils.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..07d6c9d1af65805fbf0a9cebfb77e552fdb02ead
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/polynomial_polyutils.pyi
    @@ -0,0 +1,218 @@
    +from collections.abc import Sequence
    +from decimal import Decimal
    +from fractions import Fraction
    +from typing import Literal as L, TypeAlias, assert_type
    +
    +import numpy as np
    +import numpy.polynomial.polyutils as pu
    +import numpy.typing as npt
    +from numpy.polynomial._polytypes import _Tuple2
    +
    +_ArrFloat1D: TypeAlias = np.ndarray[tuple[int], np.dtype[np.floating]]
    +_ArrComplex1D: TypeAlias = np.ndarray[tuple[int], np.dtype[np.complexfloating]]
    +_ArrObject1D: TypeAlias = np.ndarray[tuple[int], np.dtype[np.object_]]
    +
    +_ArrFloat1D_2: TypeAlias = np.ndarray[tuple[L[2]], np.dtype[np.float64]]
    +_ArrComplex1D_2: TypeAlias = np.ndarray[tuple[L[2]], np.dtype[np.complex128]]
    +_ArrObject1D_2: TypeAlias = np.ndarray[tuple[L[2]], np.dtype[np.object_]]
    +
    +num_int: int
    +num_float: float
    +num_complex: complex
    +# will result in an `object_` dtype
    +num_object: Decimal | Fraction
    +
    +sct_int: np.int_
    +sct_float: np.float64
    +sct_complex: np.complex128
    +sct_object: np.object_  # doesn't exist at runtime
    +
    +arr_int: npt.NDArray[np.int_]
    +arr_float: npt.NDArray[np.float64]
    +arr_complex: npt.NDArray[np.complex128]
    +arr_object: npt.NDArray[np.object_]
    +
    +seq_num_int: Sequence[int]
    +seq_num_float: Sequence[float]
    +seq_num_complex: Sequence[complex]
    +seq_num_object: Sequence[Decimal | Fraction]
    +
    +seq_sct_int: Sequence[np.int_]
    +seq_sct_float: Sequence[np.float64]
    +seq_sct_complex: Sequence[np.complex128]
    +seq_sct_object: Sequence[np.object_]
    +
    +seq_arr_int: Sequence[npt.NDArray[np.int_]]
    +seq_arr_float: Sequence[npt.NDArray[np.float64]]
    +seq_arr_complex: Sequence[npt.NDArray[np.complex128]]
    +seq_arr_object: Sequence[npt.NDArray[np.object_]]
    +
    +seq_seq_num_int: Sequence[Sequence[int]]
    +seq_seq_num_float: Sequence[Sequence[float]]
    +seq_seq_num_complex: Sequence[Sequence[complex]]
    +seq_seq_num_object: Sequence[Sequence[Decimal | Fraction]]
    +
    +seq_seq_sct_int: Sequence[Sequence[np.int_]]
    +seq_seq_sct_float: Sequence[Sequence[np.float64]]
    +seq_seq_sct_complex: Sequence[Sequence[np.complex128]]
    +seq_seq_sct_object: Sequence[Sequence[np.object_]]  # doesn't exist at runtime
    +
    +# as_series
    +
    +assert_type(pu.as_series(arr_int), list[_ArrFloat1D])
    +assert_type(pu.as_series(arr_float), list[_ArrFloat1D])
    +assert_type(pu.as_series(arr_complex), list[_ArrComplex1D])
    +assert_type(pu.as_series(arr_object), list[_ArrObject1D])
    +
    +assert_type(pu.as_series(seq_num_int), list[_ArrFloat1D])
    +assert_type(pu.as_series(seq_num_float), list[_ArrFloat1D])
    +assert_type(pu.as_series(seq_num_complex), list[_ArrComplex1D])
    +assert_type(pu.as_series(seq_num_object), list[_ArrObject1D])
    +
    +assert_type(pu.as_series(seq_sct_int), list[_ArrFloat1D])
    +assert_type(pu.as_series(seq_sct_float), list[_ArrFloat1D])
    +assert_type(pu.as_series(seq_sct_complex), list[_ArrComplex1D])
    +assert_type(pu.as_series(seq_sct_object), list[_ArrObject1D])
    +
    +assert_type(pu.as_series(seq_arr_int), list[_ArrFloat1D])
    +assert_type(pu.as_series(seq_arr_float), list[_ArrFloat1D])
    +assert_type(pu.as_series(seq_arr_complex), list[_ArrComplex1D])
    +assert_type(pu.as_series(seq_arr_object), list[_ArrObject1D])
    +
    +assert_type(pu.as_series(seq_seq_num_int), list[_ArrFloat1D])
    +assert_type(pu.as_series(seq_seq_num_float), list[_ArrFloat1D])
    +assert_type(pu.as_series(seq_seq_num_complex), list[_ArrComplex1D])
    +assert_type(pu.as_series(seq_seq_num_object), list[_ArrObject1D])
    +
    +assert_type(pu.as_series(seq_seq_sct_int), list[_ArrFloat1D])
    +assert_type(pu.as_series(seq_seq_sct_float), list[_ArrFloat1D])
    +assert_type(pu.as_series(seq_seq_sct_complex), list[_ArrComplex1D])
    +assert_type(pu.as_series(seq_seq_sct_object), list[_ArrObject1D])
    +
    +# trimcoef
    +
    +assert_type(pu.trimcoef(num_int), _ArrFloat1D)
    +assert_type(pu.trimcoef(num_float), _ArrFloat1D)
    +assert_type(pu.trimcoef(num_complex), _ArrComplex1D)
    +assert_type(pu.trimcoef(num_object), _ArrObject1D)
    +assert_type(pu.trimcoef(num_object), _ArrObject1D)
    +
    +assert_type(pu.trimcoef(sct_int), _ArrFloat1D)
    +assert_type(pu.trimcoef(sct_float), _ArrFloat1D)
    +assert_type(pu.trimcoef(sct_complex), _ArrComplex1D)
    +assert_type(pu.trimcoef(sct_object), _ArrObject1D)
    +
    +assert_type(pu.trimcoef(arr_int), _ArrFloat1D)
    +assert_type(pu.trimcoef(arr_float), _ArrFloat1D)
    +assert_type(pu.trimcoef(arr_complex), _ArrComplex1D)
    +assert_type(pu.trimcoef(arr_object), _ArrObject1D)
    +
    +assert_type(pu.trimcoef(seq_num_int), _ArrFloat1D)
    +assert_type(pu.trimcoef(seq_num_float), _ArrFloat1D)
    +assert_type(pu.trimcoef(seq_num_complex), _ArrComplex1D)
    +assert_type(pu.trimcoef(seq_num_object), _ArrObject1D)
    +
    +assert_type(pu.trimcoef(seq_sct_int), _ArrFloat1D)
    +assert_type(pu.trimcoef(seq_sct_float), _ArrFloat1D)
    +assert_type(pu.trimcoef(seq_sct_complex), _ArrComplex1D)
    +assert_type(pu.trimcoef(seq_sct_object), _ArrObject1D)
    +
    +# getdomain
    +
    +assert_type(pu.getdomain(num_int), _ArrFloat1D_2)
    +assert_type(pu.getdomain(num_float), _ArrFloat1D_2)
    +assert_type(pu.getdomain(num_complex), _ArrComplex1D_2)
    +assert_type(pu.getdomain(num_object), _ArrObject1D_2)
    +assert_type(pu.getdomain(num_object), _ArrObject1D_2)
    +
    +assert_type(pu.getdomain(sct_int), _ArrFloat1D_2)
    +assert_type(pu.getdomain(sct_float), _ArrFloat1D_2)
    +assert_type(pu.getdomain(sct_complex), _ArrComplex1D_2)
    +assert_type(pu.getdomain(sct_object), _ArrObject1D_2)
    +
    +assert_type(pu.getdomain(arr_int), _ArrFloat1D_2)
    +assert_type(pu.getdomain(arr_float), _ArrFloat1D_2)
    +assert_type(pu.getdomain(arr_complex), _ArrComplex1D_2)
    +assert_type(pu.getdomain(arr_object), _ArrObject1D_2)
    +
    +assert_type(pu.getdomain(seq_num_int), _ArrFloat1D_2)
    +assert_type(pu.getdomain(seq_num_float), _ArrFloat1D_2)
    +assert_type(pu.getdomain(seq_num_complex), _ArrComplex1D_2)
    +assert_type(pu.getdomain(seq_num_object), _ArrObject1D_2)
    +
    +assert_type(pu.getdomain(seq_sct_int), _ArrFloat1D_2)
    +assert_type(pu.getdomain(seq_sct_float), _ArrFloat1D_2)
    +assert_type(pu.getdomain(seq_sct_complex), _ArrComplex1D_2)
    +assert_type(pu.getdomain(seq_sct_object), _ArrObject1D_2)
    +
    +# mapparms
    +
    +assert_type(pu.mapparms(seq_num_int, seq_num_int), _Tuple2[float])
    +assert_type(pu.mapparms(seq_num_int, seq_num_float), _Tuple2[float])
    +assert_type(pu.mapparms(seq_num_float, seq_num_float), _Tuple2[float])
    +assert_type(pu.mapparms(seq_num_float, seq_num_complex), _Tuple2[complex])
    +assert_type(pu.mapparms(seq_num_complex, seq_num_complex), _Tuple2[complex])
    +assert_type(pu.mapparms(seq_num_complex, seq_num_object), _Tuple2[object])
    +assert_type(pu.mapparms(seq_num_object, seq_num_object), _Tuple2[object])
    +
    +assert_type(pu.mapparms(seq_sct_int, seq_sct_int), _Tuple2[np.floating])
    +assert_type(pu.mapparms(seq_sct_int, seq_sct_float), _Tuple2[np.floating])
    +assert_type(pu.mapparms(seq_sct_float, seq_sct_float), _Tuple2[float])
    +assert_type(pu.mapparms(seq_sct_float, seq_sct_complex), _Tuple2[complex])
    +assert_type(pu.mapparms(seq_sct_complex, seq_sct_complex), _Tuple2[complex])
    +assert_type(pu.mapparms(seq_sct_complex, seq_sct_object), _Tuple2[object])
    +assert_type(pu.mapparms(seq_sct_object, seq_sct_object), _Tuple2[object])
    +
    +assert_type(pu.mapparms(arr_int, arr_int), _Tuple2[np.floating])
    +assert_type(pu.mapparms(arr_int, arr_float), _Tuple2[np.floating])
    +assert_type(pu.mapparms(arr_float, arr_float), _Tuple2[np.floating])
    +assert_type(pu.mapparms(arr_float, arr_complex), _Tuple2[np.complexfloating])
    +assert_type(pu.mapparms(arr_complex, arr_complex), _Tuple2[np.complexfloating])
    +assert_type(pu.mapparms(arr_complex, arr_object), _Tuple2[object])
    +assert_type(pu.mapparms(arr_object, arr_object), _Tuple2[object])
    +
    +# mapdomain
    +
    +assert_type(pu.mapdomain(num_int, seq_num_int, seq_num_int), np.floating)
    +assert_type(pu.mapdomain(num_int, seq_num_int, seq_num_float), np.floating)
    +assert_type(pu.mapdomain(num_int, seq_num_float, seq_num_float), np.floating)
    +assert_type(pu.mapdomain(num_float, seq_num_float, seq_num_float), np.floating)
    +assert_type(pu.mapdomain(num_float, seq_num_float, seq_num_complex), np.complexfloating)
    +assert_type(pu.mapdomain(num_float, seq_num_complex, seq_num_complex), np.complexfloating)
    +assert_type(pu.mapdomain(num_complex, seq_num_complex, seq_num_complex), np.complexfloating)
    +assert_type(pu.mapdomain(num_complex, seq_num_complex, seq_num_object), object)
    +assert_type(pu.mapdomain(num_complex, seq_num_object, seq_num_object), object)
    +assert_type(pu.mapdomain(num_object, seq_num_object, seq_num_object), object)
    +
    +assert_type(pu.mapdomain(seq_num_int, seq_num_int, seq_num_int), _ArrFloat1D)
    +assert_type(pu.mapdomain(seq_num_int, seq_num_int, seq_num_float), _ArrFloat1D)
    +assert_type(pu.mapdomain(seq_num_int, seq_num_float, seq_num_float), _ArrFloat1D)
    +assert_type(pu.mapdomain(seq_num_float, seq_num_float, seq_num_float), _ArrFloat1D)
    +assert_type(pu.mapdomain(seq_num_float, seq_num_float, seq_num_complex), _ArrComplex1D)
    +assert_type(pu.mapdomain(seq_num_float, seq_num_complex, seq_num_complex), _ArrComplex1D)
    +assert_type(pu.mapdomain(seq_num_complex, seq_num_complex, seq_num_complex), _ArrComplex1D)
    +assert_type(pu.mapdomain(seq_num_complex, seq_num_complex, seq_num_object), _ArrObject1D)
    +assert_type(pu.mapdomain(seq_num_complex, seq_num_object, seq_num_object), _ArrObject1D)
    +assert_type(pu.mapdomain(seq_num_object, seq_num_object, seq_num_object), _ArrObject1D)
    +
    +assert_type(pu.mapdomain(seq_sct_int, seq_sct_int, seq_sct_int), _ArrFloat1D)
    +assert_type(pu.mapdomain(seq_sct_int, seq_sct_int, seq_sct_float), _ArrFloat1D)
    +assert_type(pu.mapdomain(seq_sct_int, seq_sct_float, seq_sct_float), _ArrFloat1D)
    +assert_type(pu.mapdomain(seq_sct_float, seq_sct_float, seq_sct_float), _ArrFloat1D)
    +assert_type(pu.mapdomain(seq_sct_float, seq_sct_float, seq_sct_complex), _ArrComplex1D)
    +assert_type(pu.mapdomain(seq_sct_float, seq_sct_complex, seq_sct_complex), _ArrComplex1D)
    +assert_type(pu.mapdomain(seq_sct_complex, seq_sct_complex, seq_sct_complex), _ArrComplex1D)
    +assert_type(pu.mapdomain(seq_sct_complex, seq_sct_complex, seq_sct_object), _ArrObject1D)
    +assert_type(pu.mapdomain(seq_sct_complex, seq_sct_object, seq_sct_object), _ArrObject1D)
    +assert_type(pu.mapdomain(seq_sct_object, seq_sct_object, seq_sct_object), _ArrObject1D)
    +
    +assert_type(pu.mapdomain(arr_int, arr_int, arr_int), _ArrFloat1D)
    +assert_type(pu.mapdomain(arr_int, arr_int, arr_float), _ArrFloat1D)
    +assert_type(pu.mapdomain(arr_int, arr_float, arr_float), _ArrFloat1D)
    +assert_type(pu.mapdomain(arr_float, arr_float, arr_float), _ArrFloat1D)
    +assert_type(pu.mapdomain(arr_float, arr_float, arr_complex), _ArrComplex1D)
    +assert_type(pu.mapdomain(arr_float, arr_complex, arr_complex), _ArrComplex1D)
    +assert_type(pu.mapdomain(arr_complex, arr_complex, arr_complex), _ArrComplex1D)
    +assert_type(pu.mapdomain(arr_complex, arr_complex, arr_object), _ArrObject1D)
    +assert_type(pu.mapdomain(arr_complex, arr_object, arr_object), _ArrObject1D)
    +assert_type(pu.mapdomain(arr_object, arr_object, arr_object), _ArrObject1D)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/polynomial_series.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/polynomial_series.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..3188ad9a123909449fb9f007035f243d8eba8b86
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/polynomial_series.pyi
    @@ -0,0 +1,138 @@
    +from collections.abc import Sequence
    +from typing import Any, TypeAlias, assert_type
    +
    +import numpy as np
    +import numpy.polynomial as npp
    +import numpy.typing as npt
    +
    +_ArrFloat1D: TypeAlias = np.ndarray[tuple[int], np.dtype[np.floating]]
    +_ArrFloat1D64: TypeAlias = np.ndarray[tuple[int], np.dtype[np.float64]]
    +_ArrComplex1D: TypeAlias = np.ndarray[tuple[int], np.dtype[np.complexfloating]]
    +_ArrComplex1D128: TypeAlias = np.ndarray[tuple[int], np.dtype[np.complex128]]
    +_ArrObject1D: TypeAlias = np.ndarray[tuple[int], np.dtype[np.object_]]
    +
    +AR_b: npt.NDArray[np.bool]
    +AR_u4: npt.NDArray[np.uint32]
    +AR_i8: npt.NDArray[np.int64]
    +AR_f8: npt.NDArray[np.float64]
    +AR_c16: npt.NDArray[np.complex128]
    +AR_O: npt.NDArray[np.object_]
    +
    +PS_poly: npp.Polynomial
    +PS_cheb: npp.Chebyshev
    +
    +assert_type(npp.polynomial.polyroots(AR_f8), _ArrFloat1D64)
    +assert_type(npp.polynomial.polyroots(AR_c16), _ArrComplex1D128)
    +assert_type(npp.polynomial.polyroots(AR_O), _ArrObject1D)
    +
    +assert_type(npp.polynomial.polyfromroots(AR_f8), _ArrFloat1D)
    +assert_type(npp.polynomial.polyfromroots(AR_c16), _ArrComplex1D)
    +assert_type(npp.polynomial.polyfromroots(AR_O), _ArrObject1D)
    +
    +# assert_type(npp.polynomial.polyadd(AR_b, AR_b), NoReturn)
    +assert_type(npp.polynomial.polyadd(AR_u4, AR_b), _ArrFloat1D)
    +assert_type(npp.polynomial.polyadd(AR_i8, AR_i8), _ArrFloat1D)
    +assert_type(npp.polynomial.polyadd(AR_f8, AR_i8), _ArrFloat1D)
    +assert_type(npp.polynomial.polyadd(AR_i8, AR_c16), _ArrComplex1D)
    +assert_type(npp.polynomial.polyadd(AR_O, AR_O), _ArrObject1D)
    +
    +assert_type(npp.polynomial.polymulx(AR_u4), _ArrFloat1D)
    +assert_type(npp.polynomial.polymulx(AR_i8), _ArrFloat1D)
    +assert_type(npp.polynomial.polymulx(AR_f8), _ArrFloat1D)
    +assert_type(npp.polynomial.polymulx(AR_c16), _ArrComplex1D)
    +assert_type(npp.polynomial.polymulx(AR_O), _ArrObject1D)
    +
    +assert_type(npp.polynomial.polypow(AR_u4, 2), _ArrFloat1D)
    +assert_type(npp.polynomial.polypow(AR_i8, 2), _ArrFloat1D)
    +assert_type(npp.polynomial.polypow(AR_f8, 2), _ArrFloat1D)
    +assert_type(npp.polynomial.polypow(AR_c16, 2), _ArrComplex1D)
    +assert_type(npp.polynomial.polypow(AR_O, 2), _ArrObject1D)
    +
    +# assert_type(npp.polynomial.polyder(PS_poly), npt.NDArray[np.object_])
    +assert_type(npp.polynomial.polyder(AR_f8), npt.NDArray[np.floating])
    +assert_type(npp.polynomial.polyder(AR_c16), npt.NDArray[np.complexfloating])
    +assert_type(npp.polynomial.polyder(AR_O, m=2), npt.NDArray[np.object_])
    +
    +# assert_type(npp.polynomial.polyint(PS_poly), npt.NDArray[np.object_])
    +assert_type(npp.polynomial.polyint(AR_f8), npt.NDArray[np.floating])
    +assert_type(npp.polynomial.polyint(AR_f8, k=AR_c16), npt.NDArray[np.complexfloating])
    +assert_type(npp.polynomial.polyint(AR_O, m=2), npt.NDArray[np.object_])
    +
    +assert_type(npp.polynomial.polyval(AR_b, AR_b), npt.NDArray[np.floating])
    +assert_type(npp.polynomial.polyval(AR_u4, AR_b), npt.NDArray[np.floating])
    +assert_type(npp.polynomial.polyval(AR_i8, AR_i8), npt.NDArray[np.floating])
    +assert_type(npp.polynomial.polyval(AR_f8, AR_i8), npt.NDArray[np.floating])
    +assert_type(npp.polynomial.polyval(AR_i8, AR_c16), npt.NDArray[np.complexfloating])
    +assert_type(npp.polynomial.polyval(AR_O, AR_O), npt.NDArray[np.object_])
    +
    +assert_type(npp.polynomial.polyval2d(AR_b, AR_b, AR_b), npt.NDArray[np.floating])
    +assert_type(npp.polynomial.polyval2d(AR_u4, AR_u4, AR_b), npt.NDArray[np.floating])
    +assert_type(npp.polynomial.polyval2d(AR_i8, AR_i8, AR_i8), npt.NDArray[np.floating])
    +assert_type(npp.polynomial.polyval2d(AR_f8, AR_f8, AR_i8), npt.NDArray[np.floating])
    +assert_type(npp.polynomial.polyval2d(AR_i8, AR_i8, AR_c16), npt.NDArray[np.complexfloating])
    +assert_type(npp.polynomial.polyval2d(AR_O, AR_O, AR_O), npt.NDArray[np.object_])
    +
    +assert_type(npp.polynomial.polyval3d(AR_b, AR_b, AR_b, AR_b), npt.NDArray[np.floating])
    +assert_type(npp.polynomial.polyval3d(AR_u4, AR_u4, AR_u4, AR_b), npt.NDArray[np.floating])
    +assert_type(npp.polynomial.polyval3d(AR_i8, AR_i8, AR_i8, AR_i8), npt.NDArray[np.floating])
    +assert_type(npp.polynomial.polyval3d(AR_f8, AR_f8, AR_f8, AR_i8), npt.NDArray[np.floating])
    +assert_type(npp.polynomial.polyval3d(AR_i8, AR_i8, AR_i8, AR_c16), npt.NDArray[np.complexfloating])
    +assert_type(npp.polynomial.polyval3d(AR_O, AR_O, AR_O, AR_O), npt.NDArray[np.object_])
    +
    +assert_type(npp.polynomial.polyvalfromroots(AR_b, AR_b), npt.NDArray[np.float64 | Any])
    +assert_type(npp.polynomial.polyvalfromroots(AR_u4, AR_b), npt.NDArray[np.float64 | Any])
    +assert_type(npp.polynomial.polyvalfromroots(AR_i8, AR_i8), npt.NDArray[np.float64 | Any])
    +assert_type(npp.polynomial.polyvalfromroots(AR_f8, AR_i8), npt.NDArray[np.float64 | Any])
    +assert_type(npp.polynomial.polyvalfromroots(AR_i8, AR_c16), npt.NDArray[np.complex128 | Any])
    +assert_type(npp.polynomial.polyvalfromroots(AR_O, AR_O), npt.NDArray[np.object_ | Any])
    +
    +assert_type(npp.polynomial.polyvander(AR_f8, 3), npt.NDArray[np.floating])
    +assert_type(npp.polynomial.polyvander(AR_c16, 3), npt.NDArray[np.complexfloating])
    +assert_type(npp.polynomial.polyvander(AR_O, 3), npt.NDArray[np.object_])
    +
    +assert_type(npp.polynomial.polyvander2d(AR_f8, AR_f8, [4, 2]), npt.NDArray[np.floating])
    +assert_type(npp.polynomial.polyvander2d(AR_c16, AR_c16, [4, 2]), npt.NDArray[np.complexfloating])
    +assert_type(npp.polynomial.polyvander2d(AR_O, AR_O, [4, 2]), npt.NDArray[np.object_])
    +
    +assert_type(npp.polynomial.polyvander3d(AR_f8, AR_f8, AR_f8, [4, 3, 2]), npt.NDArray[np.floating])
    +assert_type(npp.polynomial.polyvander3d(AR_c16, AR_c16, AR_c16, [4, 3, 2]), npt.NDArray[np.complexfloating])
    +assert_type(npp.polynomial.polyvander3d(AR_O, AR_O, AR_O, [4, 3, 2]), npt.NDArray[np.object_])
    +
    +assert_type(
    +    npp.polynomial.polyfit(AR_f8, AR_f8, 2),
    +    npt.NDArray[np.floating],
    +)
    +assert_type(
    +    npp.polynomial.polyfit(AR_f8, AR_i8, 1, full=True),
    +    tuple[npt.NDArray[np.floating], Sequence[np.inexact | np.int32]],
    +)
    +assert_type(
    +    npp.polynomial.polyfit(AR_c16, AR_f8, 2),
    +    npt.NDArray[np.complexfloating],
    +)
    +assert_type(
    +    npp.polynomial.polyfit(AR_f8, AR_c16, 1, full=True)[0],
    +    npt.NDArray[np.complexfloating],
    +)
    +
    +assert_type(npp.chebyshev.chebgauss(2), tuple[_ArrFloat1D64, _ArrFloat1D64])
    +
    +assert_type(npp.chebyshev.chebweight(AR_f8), npt.NDArray[np.float64])
    +assert_type(npp.chebyshev.chebweight(AR_c16), npt.NDArray[np.complex128])
    +assert_type(npp.chebyshev.chebweight(AR_O), npt.NDArray[np.object_])
    +
    +assert_type(npp.chebyshev.poly2cheb(AR_f8), _ArrFloat1D)
    +assert_type(npp.chebyshev.poly2cheb(AR_c16), _ArrComplex1D)
    +assert_type(npp.chebyshev.poly2cheb(AR_O), _ArrObject1D)
    +
    +assert_type(npp.chebyshev.cheb2poly(AR_f8), _ArrFloat1D)
    +assert_type(npp.chebyshev.cheb2poly(AR_c16), _ArrComplex1D)
    +assert_type(npp.chebyshev.cheb2poly(AR_O), _ArrObject1D)
    +
    +assert_type(npp.chebyshev.chebpts1(6), _ArrFloat1D64)
    +assert_type(npp.chebyshev.chebpts2(6), _ArrFloat1D64)
    +
    +assert_type(
    +    npp.chebyshev.chebinterpolate(np.tanh, 3),
    +    npt.NDArray[np.float64 | np.complex128 | np.object_],
    +)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/random.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/random.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..e188eb02893f06faf35b1b5265c83dc24f1d84c9
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/random.pyi
    @@ -0,0 +1,1546 @@
    +import threading
    +from collections.abc import Sequence
    +from typing import Any, assert_type
    +
    +import numpy as np
    +import numpy.typing as npt
    +from numpy.random._generator import Generator
    +from numpy.random._mt19937 import MT19937
    +from numpy.random._pcg64 import PCG64
    +from numpy.random._philox import Philox
    +from numpy.random._sfc64 import SFC64
    +from numpy.random.bit_generator import SeedlessSeedSequence, SeedSequence
    +
    +def_rng = np.random.default_rng()
    +seed_seq = np.random.SeedSequence()
    +mt19937 = np.random.MT19937()
    +pcg64 = np.random.PCG64()
    +sfc64 = np.random.SFC64()
    +philox = np.random.Philox()
    +seedless_seq = SeedlessSeedSequence()
    +
    +assert_type(def_rng, Generator)
    +assert_type(mt19937, MT19937)
    +assert_type(pcg64, PCG64)
    +assert_type(sfc64, SFC64)
    +assert_type(philox, Philox)
    +assert_type(seed_seq, SeedSequence)
    +assert_type(seedless_seq, SeedlessSeedSequence)
    +
    +mt19937_jumped = mt19937.jumped()
    +mt19937_jumped3 = mt19937.jumped(3)
    +mt19937_raw = mt19937.random_raw()
    +mt19937_raw_arr = mt19937.random_raw(5)
    +
    +assert_type(mt19937_jumped, MT19937)
    +assert_type(mt19937_jumped3, MT19937)
    +assert_type(mt19937_raw, int)
    +assert_type(mt19937_raw_arr, npt.NDArray[np.uint64])
    +assert_type(mt19937.lock, threading.Lock)
    +
    +pcg64_jumped = pcg64.jumped()
    +pcg64_jumped3 = pcg64.jumped(3)
    +pcg64_adv = pcg64.advance(3)
    +pcg64_raw = pcg64.random_raw()
    +pcg64_raw_arr = pcg64.random_raw(5)
    +
    +assert_type(pcg64_jumped, PCG64)
    +assert_type(pcg64_jumped3, PCG64)
    +assert_type(pcg64_adv, PCG64)
    +assert_type(pcg64_raw, int)
    +assert_type(pcg64_raw_arr, npt.NDArray[np.uint64])
    +assert_type(pcg64.lock, threading.Lock)
    +
    +philox_jumped = philox.jumped()
    +philox_jumped3 = philox.jumped(3)
    +philox_adv = philox.advance(3)
    +philox_raw = philox.random_raw()
    +philox_raw_arr = philox.random_raw(5)
    +
    +assert_type(philox_jumped, Philox)
    +assert_type(philox_jumped3, Philox)
    +assert_type(philox_adv, Philox)
    +assert_type(philox_raw, int)
    +assert_type(philox_raw_arr, npt.NDArray[np.uint64])
    +assert_type(philox.lock, threading.Lock)
    +
    +sfc64_raw = sfc64.random_raw()
    +sfc64_raw_arr = sfc64.random_raw(5)
    +
    +assert_type(sfc64_raw, int)
    +assert_type(sfc64_raw_arr, npt.NDArray[np.uint64])
    +assert_type(sfc64.lock, threading.Lock)
    +
    +assert_type(seed_seq.pool, npt.NDArray[np.uint32])
    +assert_type(seed_seq.entropy, int | Sequence[int] | None)
    +assert_type(seed_seq.spawn(1), list[np.random.SeedSequence])
    +assert_type(seed_seq.generate_state(8, "uint32"), npt.NDArray[np.uint32 | np.uint64])
    +assert_type(seed_seq.generate_state(8, "uint64"), npt.NDArray[np.uint32 | np.uint64])
    +
    +def_gen: np.random.Generator = np.random.default_rng()
    +
    +D_arr_0p1: npt.NDArray[np.float64] = np.array([0.1])
    +D_arr_0p5: npt.NDArray[np.float64] = np.array([0.5])
    +D_arr_0p9: npt.NDArray[np.float64] = np.array([0.9])
    +D_arr_1p5: npt.NDArray[np.float64] = np.array([1.5])
    +I_arr_10: npt.NDArray[np.int_] = np.array([10], dtype=np.int_)
    +I_arr_20: npt.NDArray[np.int_] = np.array([20], dtype=np.int_)
    +D_arr_like_0p1: list[float] = [0.1]
    +D_arr_like_0p5: list[float] = [0.5]
    +D_arr_like_0p9: list[float] = [0.9]
    +D_arr_like_1p5: list[float] = [1.5]
    +I_arr_like_10: list[int] = [10]
    +I_arr_like_20: list[int] = [20]
    +D_2D_like: list[list[float]] = [[1, 2], [2, 3], [3, 4], [4, 5.1]]
    +D_2D: npt.NDArray[np.float64] = np.array(D_2D_like)
    +S_out: npt.NDArray[np.float32] = np.empty(1, dtype=np.float32)
    +D_out: npt.NDArray[np.float64] = np.empty(1)
    +
    +assert_type(def_gen.standard_normal(), float)
    +assert_type(def_gen.standard_normal(dtype=np.float32), float)
    +assert_type(def_gen.standard_normal(dtype="float32"), float)
    +assert_type(def_gen.standard_normal(dtype="double"), float)
    +assert_type(def_gen.standard_normal(dtype=np.float64), float)
    +assert_type(def_gen.standard_normal(size=None), float)
    +assert_type(def_gen.standard_normal(size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.standard_normal(size=1, dtype=np.float32), npt.NDArray[np.float32])
    +assert_type(def_gen.standard_normal(size=1, dtype="f4"), npt.NDArray[np.float32])
    +assert_type(def_gen.standard_normal(size=1, dtype="float32", out=S_out), npt.NDArray[np.float32])
    +assert_type(def_gen.standard_normal(dtype=np.float32, out=S_out), npt.NDArray[np.float32])
    +assert_type(def_gen.standard_normal(size=1, dtype=np.float64), npt.NDArray[np.float64])
    +assert_type(def_gen.standard_normal(size=1, dtype="float64"), npt.NDArray[np.float64])
    +assert_type(def_gen.standard_normal(size=1, dtype="f8"), npt.NDArray[np.float64])
    +assert_type(def_gen.standard_normal(out=D_out), npt.NDArray[np.float64])
    +assert_type(def_gen.standard_normal(size=1, dtype="float64"), npt.NDArray[np.float64])
    +assert_type(def_gen.standard_normal(size=1, dtype="float64", out=D_out), npt.NDArray[np.float64])
    +
    +assert_type(def_gen.random(), float)
    +assert_type(def_gen.random(dtype=np.float32), float)
    +assert_type(def_gen.random(dtype="float32"), float)
    +assert_type(def_gen.random(dtype="double"), float)
    +assert_type(def_gen.random(dtype=np.float64), float)
    +assert_type(def_gen.random(size=None), float)
    +assert_type(def_gen.random(size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.random(size=1, dtype=np.float32), npt.NDArray[np.float32])
    +assert_type(def_gen.random(size=1, dtype="f4"), npt.NDArray[np.float32])
    +assert_type(def_gen.random(size=1, dtype="float32", out=S_out), npt.NDArray[np.float32])
    +assert_type(def_gen.random(dtype=np.float32, out=S_out), npt.NDArray[np.float32])
    +assert_type(def_gen.random(size=1, dtype=np.float64), npt.NDArray[np.float64])
    +assert_type(def_gen.random(size=1, dtype="float64"), npt.NDArray[np.float64])
    +assert_type(def_gen.random(size=1, dtype="f8"), npt.NDArray[np.float64])
    +assert_type(def_gen.random(out=D_out), npt.NDArray[np.float64])
    +assert_type(def_gen.random(size=1, dtype="float64"), npt.NDArray[np.float64])
    +assert_type(def_gen.random(size=1, dtype="float64", out=D_out), npt.NDArray[np.float64])
    +
    +assert_type(def_gen.standard_cauchy(), float)
    +assert_type(def_gen.standard_cauchy(size=None), float)
    +assert_type(def_gen.standard_cauchy(size=1), npt.NDArray[np.float64])
    +
    +assert_type(def_gen.standard_exponential(), float)
    +assert_type(def_gen.standard_exponential(method="inv"), float)
    +assert_type(def_gen.standard_exponential(dtype=np.float32), float)
    +assert_type(def_gen.standard_exponential(dtype="float32"), float)
    +assert_type(def_gen.standard_exponential(dtype="double"), float)
    +assert_type(def_gen.standard_exponential(dtype=np.float64), float)
    +assert_type(def_gen.standard_exponential(size=None), float)
    +assert_type(def_gen.standard_exponential(size=None, method="inv"), float)
    +assert_type(def_gen.standard_exponential(size=1, method="inv"), npt.NDArray[np.float64])
    +assert_type(def_gen.standard_exponential(size=1, dtype=np.float32), npt.NDArray[np.float32])
    +assert_type(def_gen.standard_exponential(size=1, dtype="f4", method="inv"), npt.NDArray[np.float32])
    +assert_type(def_gen.standard_exponential(size=1, dtype="float32", out=S_out), npt.NDArray[np.float32])
    +assert_type(def_gen.standard_exponential(dtype=np.float32, out=S_out), npt.NDArray[np.float32])
    +assert_type(def_gen.standard_exponential(size=1, dtype=np.float64, method="inv"), npt.NDArray[np.float64])
    +assert_type(def_gen.standard_exponential(size=1, dtype="float64"), npt.NDArray[np.float64])
    +assert_type(def_gen.standard_exponential(size=1, dtype="f8"), npt.NDArray[np.float64])
    +assert_type(def_gen.standard_exponential(out=D_out), npt.NDArray[np.float64])
    +assert_type(def_gen.standard_exponential(size=1, dtype="float64"), npt.NDArray[np.float64])
    +assert_type(def_gen.standard_exponential(size=1, dtype="float64", out=D_out), npt.NDArray[np.float64])
    +
    +assert_type(def_gen.zipf(1.5), int)
    +assert_type(def_gen.zipf(1.5, size=None), int)
    +assert_type(def_gen.zipf(1.5, size=1), npt.NDArray[np.int64])
    +assert_type(def_gen.zipf(D_arr_1p5), npt.NDArray[np.int64])
    +assert_type(def_gen.zipf(D_arr_1p5, size=1), npt.NDArray[np.int64])
    +assert_type(def_gen.zipf(D_arr_like_1p5), npt.NDArray[np.int64])
    +assert_type(def_gen.zipf(D_arr_like_1p5, size=1), npt.NDArray[np.int64])
    +
    +assert_type(def_gen.weibull(0.5), float)
    +assert_type(def_gen.weibull(0.5, size=None), float)
    +assert_type(def_gen.weibull(0.5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.weibull(D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.weibull(D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.weibull(D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.weibull(D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +
    +assert_type(def_gen.standard_t(0.5), float)
    +assert_type(def_gen.standard_t(0.5, size=None), float)
    +assert_type(def_gen.standard_t(0.5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.standard_t(D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.standard_t(D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.standard_t(D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.standard_t(D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +
    +assert_type(def_gen.poisson(0.5), int)
    +assert_type(def_gen.poisson(0.5, size=None), int)
    +assert_type(def_gen.poisson(0.5, size=1), npt.NDArray[np.int64])
    +assert_type(def_gen.poisson(D_arr_0p5), npt.NDArray[np.int64])
    +assert_type(def_gen.poisson(D_arr_0p5, size=1), npt.NDArray[np.int64])
    +assert_type(def_gen.poisson(D_arr_like_0p5), npt.NDArray[np.int64])
    +assert_type(def_gen.poisson(D_arr_like_0p5, size=1), npt.NDArray[np.int64])
    +
    +assert_type(def_gen.power(0.5), float)
    +assert_type(def_gen.power(0.5, size=None), float)
    +assert_type(def_gen.power(0.5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.power(D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.power(D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.power(D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.power(D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +
    +assert_type(def_gen.pareto(0.5), float)
    +assert_type(def_gen.pareto(0.5, size=None), float)
    +assert_type(def_gen.pareto(0.5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.pareto(D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.pareto(D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.pareto(D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.pareto(D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +
    +assert_type(def_gen.chisquare(0.5), float)
    +assert_type(def_gen.chisquare(0.5, size=None), float)
    +assert_type(def_gen.chisquare(0.5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.chisquare(D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.chisquare(D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.chisquare(D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.chisquare(D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +
    +assert_type(def_gen.exponential(0.5), float)
    +assert_type(def_gen.exponential(0.5, size=None), float)
    +assert_type(def_gen.exponential(0.5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.exponential(D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.exponential(D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.exponential(D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.exponential(D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +
    +assert_type(def_gen.geometric(0.5), int)
    +assert_type(def_gen.geometric(0.5, size=None), int)
    +assert_type(def_gen.geometric(0.5, size=1), npt.NDArray[np.int64])
    +assert_type(def_gen.geometric(D_arr_0p5), npt.NDArray[np.int64])
    +assert_type(def_gen.geometric(D_arr_0p5, size=1), npt.NDArray[np.int64])
    +assert_type(def_gen.geometric(D_arr_like_0p5), npt.NDArray[np.int64])
    +assert_type(def_gen.geometric(D_arr_like_0p5, size=1), npt.NDArray[np.int64])
    +
    +assert_type(def_gen.logseries(0.5), int)
    +assert_type(def_gen.logseries(0.5, size=None), int)
    +assert_type(def_gen.logseries(0.5, size=1), npt.NDArray[np.int64])
    +assert_type(def_gen.logseries(D_arr_0p5), npt.NDArray[np.int64])
    +assert_type(def_gen.logseries(D_arr_0p5, size=1), npt.NDArray[np.int64])
    +assert_type(def_gen.logseries(D_arr_like_0p5), npt.NDArray[np.int64])
    +assert_type(def_gen.logseries(D_arr_like_0p5, size=1), npt.NDArray[np.int64])
    +
    +assert_type(def_gen.rayleigh(0.5), float)
    +assert_type(def_gen.rayleigh(0.5, size=None), float)
    +assert_type(def_gen.rayleigh(0.5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.rayleigh(D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.rayleigh(D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.rayleigh(D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.rayleigh(D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +
    +assert_type(def_gen.standard_gamma(0.5), float)
    +assert_type(def_gen.standard_gamma(0.5, size=None), float)
    +assert_type(def_gen.standard_gamma(0.5, dtype="float32"), float)
    +assert_type(def_gen.standard_gamma(0.5, size=None, dtype="float32"), float)
    +assert_type(def_gen.standard_gamma(0.5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.standard_gamma(D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.standard_gamma(D_arr_0p5, dtype="f4"), npt.NDArray[np.float32])
    +assert_type(def_gen.standard_gamma(0.5, size=1, dtype="float32", out=S_out), npt.NDArray[np.float32])
    +assert_type(def_gen.standard_gamma(D_arr_0p5, dtype=np.float32, out=S_out), npt.NDArray[np.float32])
    +assert_type(def_gen.standard_gamma(D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.standard_gamma(D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.standard_gamma(D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.standard_gamma(0.5, out=D_out), npt.NDArray[np.float64])
    +assert_type(def_gen.standard_gamma(D_arr_like_0p5, out=D_out), npt.NDArray[np.float64])
    +assert_type(def_gen.standard_gamma(D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.standard_gamma(D_arr_like_0p5, size=1, out=D_out, dtype=np.float64), npt.NDArray[np.float64])
    +
    +assert_type(def_gen.vonmises(0.5, 0.5), float)
    +assert_type(def_gen.vonmises(0.5, 0.5, size=None), float)
    +assert_type(def_gen.vonmises(0.5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.vonmises(D_arr_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(def_gen.vonmises(0.5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.vonmises(D_arr_0p5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.vonmises(0.5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.vonmises(D_arr_like_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(def_gen.vonmises(0.5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.vonmises(D_arr_0p5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.vonmises(D_arr_like_0p5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.vonmises(D_arr_0p5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.vonmises(D_arr_like_0p5, D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +
    +assert_type(def_gen.wald(0.5, 0.5), float)
    +assert_type(def_gen.wald(0.5, 0.5, size=None), float)
    +assert_type(def_gen.wald(0.5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.wald(D_arr_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(def_gen.wald(0.5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.wald(D_arr_0p5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.wald(0.5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.wald(D_arr_like_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(def_gen.wald(0.5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.wald(D_arr_0p5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.wald(D_arr_like_0p5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.wald(D_arr_0p5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.wald(D_arr_like_0p5, D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +
    +assert_type(def_gen.uniform(0.5, 0.5), float)
    +assert_type(def_gen.uniform(0.5, 0.5, size=None), float)
    +assert_type(def_gen.uniform(0.5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.uniform(D_arr_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(def_gen.uniform(0.5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.uniform(D_arr_0p5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.uniform(0.5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.uniform(D_arr_like_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(def_gen.uniform(0.5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.uniform(D_arr_0p5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.uniform(D_arr_like_0p5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.uniform(D_arr_0p5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.uniform(D_arr_like_0p5, D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +
    +assert_type(def_gen.beta(0.5, 0.5), float)
    +assert_type(def_gen.beta(0.5, 0.5, size=None), float)
    +assert_type(def_gen.beta(0.5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.beta(D_arr_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(def_gen.beta(0.5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.beta(D_arr_0p5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.beta(0.5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.beta(D_arr_like_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(def_gen.beta(0.5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.beta(D_arr_0p5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.beta(D_arr_like_0p5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.beta(D_arr_0p5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.beta(D_arr_like_0p5, D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +
    +assert_type(def_gen.f(0.5, 0.5), float)
    +assert_type(def_gen.f(0.5, 0.5, size=None), float)
    +assert_type(def_gen.f(0.5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.f(D_arr_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(def_gen.f(0.5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.f(D_arr_0p5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.f(0.5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.f(D_arr_like_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(def_gen.f(0.5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.f(D_arr_0p5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.f(D_arr_like_0p5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.f(D_arr_0p5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.f(D_arr_like_0p5, D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +
    +assert_type(def_gen.gamma(0.5, 0.5), float)
    +assert_type(def_gen.gamma(0.5, 0.5, size=None), float)
    +assert_type(def_gen.gamma(0.5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.gamma(D_arr_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(def_gen.gamma(0.5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.gamma(D_arr_0p5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.gamma(0.5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.gamma(D_arr_like_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(def_gen.gamma(0.5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.gamma(D_arr_0p5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.gamma(D_arr_like_0p5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.gamma(D_arr_0p5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.gamma(D_arr_like_0p5, D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +
    +assert_type(def_gen.gumbel(0.5, 0.5), float)
    +assert_type(def_gen.gumbel(0.5, 0.5, size=None), float)
    +assert_type(def_gen.gumbel(0.5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.gumbel(D_arr_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(def_gen.gumbel(0.5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.gumbel(D_arr_0p5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.gumbel(0.5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.gumbel(D_arr_like_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(def_gen.gumbel(0.5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.gumbel(D_arr_0p5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.gumbel(D_arr_like_0p5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.gumbel(D_arr_0p5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.gumbel(D_arr_like_0p5, D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +
    +assert_type(def_gen.laplace(0.5, 0.5), float)
    +assert_type(def_gen.laplace(0.5, 0.5, size=None), float)
    +assert_type(def_gen.laplace(0.5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.laplace(D_arr_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(def_gen.laplace(0.5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.laplace(D_arr_0p5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.laplace(0.5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.laplace(D_arr_like_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(def_gen.laplace(0.5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.laplace(D_arr_0p5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.laplace(D_arr_like_0p5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.laplace(D_arr_0p5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.laplace(D_arr_like_0p5, D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +
    +assert_type(def_gen.logistic(0.5, 0.5), float)
    +assert_type(def_gen.logistic(0.5, 0.5, size=None), float)
    +assert_type(def_gen.logistic(0.5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.logistic(D_arr_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(def_gen.logistic(0.5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.logistic(D_arr_0p5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.logistic(0.5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.logistic(D_arr_like_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(def_gen.logistic(0.5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.logistic(D_arr_0p5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.logistic(D_arr_like_0p5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.logistic(D_arr_0p5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.logistic(D_arr_like_0p5, D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +
    +assert_type(def_gen.lognormal(0.5, 0.5), float)
    +assert_type(def_gen.lognormal(0.5, 0.5, size=None), float)
    +assert_type(def_gen.lognormal(0.5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.lognormal(D_arr_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(def_gen.lognormal(0.5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.lognormal(D_arr_0p5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.lognormal(0.5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.lognormal(D_arr_like_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(def_gen.lognormal(0.5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.lognormal(D_arr_0p5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.lognormal(D_arr_like_0p5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.lognormal(D_arr_0p5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.lognormal(D_arr_like_0p5, D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +
    +assert_type(def_gen.noncentral_chisquare(0.5, 0.5), float)
    +assert_type(def_gen.noncentral_chisquare(0.5, 0.5, size=None), float)
    +assert_type(def_gen.noncentral_chisquare(0.5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.noncentral_chisquare(D_arr_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(def_gen.noncentral_chisquare(0.5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.noncentral_chisquare(D_arr_0p5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.noncentral_chisquare(0.5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.noncentral_chisquare(D_arr_like_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(def_gen.noncentral_chisquare(0.5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.noncentral_chisquare(D_arr_0p5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.noncentral_chisquare(D_arr_like_0p5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.noncentral_chisquare(D_arr_0p5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.noncentral_chisquare(D_arr_like_0p5, D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +
    +assert_type(def_gen.normal(0.5, 0.5), float)
    +assert_type(def_gen.normal(0.5, 0.5, size=None), float)
    +assert_type(def_gen.normal(0.5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.normal(D_arr_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(def_gen.normal(0.5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.normal(D_arr_0p5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.normal(0.5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.normal(D_arr_like_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(def_gen.normal(0.5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.normal(D_arr_0p5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.normal(D_arr_like_0p5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(def_gen.normal(D_arr_0p5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.normal(D_arr_like_0p5, D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +
    +assert_type(def_gen.triangular(0.1, 0.5, 0.9), float)
    +assert_type(def_gen.triangular(0.1, 0.5, 0.9, size=None), float)
    +assert_type(def_gen.triangular(0.1, 0.5, 0.9, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.triangular(D_arr_0p1, 0.5, 0.9), npt.NDArray[np.float64])
    +assert_type(def_gen.triangular(0.1, D_arr_0p5, 0.9), npt.NDArray[np.float64])
    +assert_type(def_gen.triangular(D_arr_0p1, 0.5, D_arr_like_0p9, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.triangular(0.1, D_arr_0p5, 0.9, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.triangular(D_arr_like_0p1, 0.5, D_arr_0p9), npt.NDArray[np.float64])
    +assert_type(def_gen.triangular(0.5, D_arr_like_0p5, 0.9), npt.NDArray[np.float64])
    +assert_type(def_gen.triangular(D_arr_0p1, D_arr_0p5, 0.9), npt.NDArray[np.float64])
    +assert_type(def_gen.triangular(D_arr_like_0p1, D_arr_like_0p5, 0.9), npt.NDArray[np.float64])
    +assert_type(def_gen.triangular(D_arr_0p1, D_arr_0p5, D_arr_0p9, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.triangular(D_arr_like_0p1, D_arr_like_0p5, D_arr_like_0p9, size=1), npt.NDArray[np.float64])
    +
    +assert_type(def_gen.noncentral_f(0.1, 0.5, 0.9), float)
    +assert_type(def_gen.noncentral_f(0.1, 0.5, 0.9, size=None), float)
    +assert_type(def_gen.noncentral_f(0.1, 0.5, 0.9, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.noncentral_f(D_arr_0p1, 0.5, 0.9), npt.NDArray[np.float64])
    +assert_type(def_gen.noncentral_f(0.1, D_arr_0p5, 0.9), npt.NDArray[np.float64])
    +assert_type(def_gen.noncentral_f(D_arr_0p1, 0.5, D_arr_like_0p9, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.noncentral_f(0.1, D_arr_0p5, 0.9, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.noncentral_f(D_arr_like_0p1, 0.5, D_arr_0p9), npt.NDArray[np.float64])
    +assert_type(def_gen.noncentral_f(0.5, D_arr_like_0p5, 0.9), npt.NDArray[np.float64])
    +assert_type(def_gen.noncentral_f(D_arr_0p1, D_arr_0p5, 0.9), npt.NDArray[np.float64])
    +assert_type(def_gen.noncentral_f(D_arr_like_0p1, D_arr_like_0p5, 0.9), npt.NDArray[np.float64])
    +assert_type(def_gen.noncentral_f(D_arr_0p1, D_arr_0p5, D_arr_0p9, size=1), npt.NDArray[np.float64])
    +assert_type(def_gen.noncentral_f(D_arr_like_0p1, D_arr_like_0p5, D_arr_like_0p9, size=1), npt.NDArray[np.float64])
    +
    +assert_type(def_gen.binomial(10, 0.5), int)
    +assert_type(def_gen.binomial(10, 0.5, size=None), int)
    +assert_type(def_gen.binomial(10, 0.5, size=1), npt.NDArray[np.int64])
    +assert_type(def_gen.binomial(I_arr_10, 0.5), npt.NDArray[np.int64])
    +assert_type(def_gen.binomial(10, D_arr_0p5), npt.NDArray[np.int64])
    +assert_type(def_gen.binomial(I_arr_10, 0.5, size=1), npt.NDArray[np.int64])
    +assert_type(def_gen.binomial(10, D_arr_0p5, size=1), npt.NDArray[np.int64])
    +assert_type(def_gen.binomial(I_arr_like_10, 0.5), npt.NDArray[np.int64])
    +assert_type(def_gen.binomial(10, D_arr_like_0p5), npt.NDArray[np.int64])
    +assert_type(def_gen.binomial(I_arr_10, D_arr_0p5), npt.NDArray[np.int64])
    +assert_type(def_gen.binomial(I_arr_like_10, D_arr_like_0p5), npt.NDArray[np.int64])
    +assert_type(def_gen.binomial(I_arr_10, D_arr_0p5, size=1), npt.NDArray[np.int64])
    +assert_type(def_gen.binomial(I_arr_like_10, D_arr_like_0p5, size=1), npt.NDArray[np.int64])
    +
    +assert_type(def_gen.negative_binomial(10, 0.5), int)
    +assert_type(def_gen.negative_binomial(10, 0.5, size=None), int)
    +assert_type(def_gen.negative_binomial(10, 0.5, size=1), npt.NDArray[np.int64])
    +assert_type(def_gen.negative_binomial(I_arr_10, 0.5), npt.NDArray[np.int64])
    +assert_type(def_gen.negative_binomial(10, D_arr_0p5), npt.NDArray[np.int64])
    +assert_type(def_gen.negative_binomial(I_arr_10, 0.5, size=1), npt.NDArray[np.int64])
    +assert_type(def_gen.negative_binomial(10, D_arr_0p5, size=1), npt.NDArray[np.int64])
    +assert_type(def_gen.negative_binomial(I_arr_like_10, 0.5), npt.NDArray[np.int64])
    +assert_type(def_gen.negative_binomial(10, D_arr_like_0p5), npt.NDArray[np.int64])
    +assert_type(def_gen.negative_binomial(I_arr_10, D_arr_0p5), npt.NDArray[np.int64])
    +assert_type(def_gen.negative_binomial(I_arr_like_10, D_arr_like_0p5), npt.NDArray[np.int64])
    +assert_type(def_gen.negative_binomial(I_arr_10, D_arr_0p5, size=1), npt.NDArray[np.int64])
    +assert_type(def_gen.negative_binomial(I_arr_like_10, D_arr_like_0p5, size=1), npt.NDArray[np.int64])
    +
    +assert_type(def_gen.hypergeometric(20, 20, 10), int)
    +assert_type(def_gen.hypergeometric(20, 20, 10, size=None), int)
    +assert_type(def_gen.hypergeometric(20, 20, 10, size=1), npt.NDArray[np.int64])
    +assert_type(def_gen.hypergeometric(I_arr_20, 20, 10), npt.NDArray[np.int64])
    +assert_type(def_gen.hypergeometric(20, I_arr_20, 10), npt.NDArray[np.int64])
    +assert_type(def_gen.hypergeometric(I_arr_20, 20, I_arr_like_10, size=1), npt.NDArray[np.int64])
    +assert_type(def_gen.hypergeometric(20, I_arr_20, 10, size=1), npt.NDArray[np.int64])
    +assert_type(def_gen.hypergeometric(I_arr_like_20, 20, I_arr_10), npt.NDArray[np.int64])
    +assert_type(def_gen.hypergeometric(20, I_arr_like_20, 10), npt.NDArray[np.int64])
    +assert_type(def_gen.hypergeometric(I_arr_20, I_arr_20, 10), npt.NDArray[np.int64])
    +assert_type(def_gen.hypergeometric(I_arr_like_20, I_arr_like_20, 10), npt.NDArray[np.int64])
    +assert_type(def_gen.hypergeometric(I_arr_20, I_arr_20, I_arr_10, size=1), npt.NDArray[np.int64])
    +assert_type(def_gen.hypergeometric(I_arr_like_20, I_arr_like_20, I_arr_like_10, size=1), npt.NDArray[np.int64])
    +
    +I_int64_100: npt.NDArray[np.int64] = np.array([100], dtype=np.int64)
    +
    +assert_type(def_gen.integers(0, 100), np.int64)
    +assert_type(def_gen.integers(100), np.int64)
    +assert_type(def_gen.integers([100]), npt.NDArray[np.int64])
    +assert_type(def_gen.integers(0, [100]), npt.NDArray[np.int64])
    +
    +I_bool_low: npt.NDArray[np.bool] = np.array([0], dtype=np.bool)
    +I_bool_low_like: list[int] = [0]
    +I_bool_high_open: npt.NDArray[np.bool] = np.array([1], dtype=np.bool)
    +I_bool_high_closed: npt.NDArray[np.bool] = np.array([1], dtype=np.bool)
    +
    +assert_type(def_gen.integers(2, dtype=bool), bool)
    +assert_type(def_gen.integers(0, 2, dtype=bool), bool)
    +assert_type(def_gen.integers(1, dtype=bool, endpoint=True), bool)
    +assert_type(def_gen.integers(0, 1, dtype=bool, endpoint=True), bool)
    +assert_type(def_gen.integers(I_bool_low_like, 1, dtype=bool, endpoint=True), npt.NDArray[np.bool])
    +assert_type(def_gen.integers(I_bool_high_open, dtype=bool), npt.NDArray[np.bool])
    +assert_type(def_gen.integers(I_bool_low, I_bool_high_open, dtype=bool), npt.NDArray[np.bool])
    +assert_type(def_gen.integers(0, I_bool_high_open, dtype=bool), npt.NDArray[np.bool])
    +assert_type(def_gen.integers(I_bool_high_closed, dtype=bool, endpoint=True), npt.NDArray[np.bool])
    +assert_type(def_gen.integers(I_bool_low, I_bool_high_closed, dtype=bool, endpoint=True), npt.NDArray[np.bool])
    +assert_type(def_gen.integers(0, I_bool_high_closed, dtype=bool, endpoint=True), npt.NDArray[np.bool])
    +
    +assert_type(def_gen.integers(2, dtype=np.bool), np.bool)
    +assert_type(def_gen.integers(0, 2, dtype=np.bool), np.bool)
    +assert_type(def_gen.integers(1, dtype=np.bool, endpoint=True), np.bool)
    +assert_type(def_gen.integers(0, 1, dtype=np.bool, endpoint=True), np.bool)
    +assert_type(def_gen.integers(I_bool_low_like, 1, dtype=np.bool, endpoint=True), npt.NDArray[np.bool])
    +assert_type(def_gen.integers(I_bool_high_open, dtype=np.bool), npt.NDArray[np.bool])
    +assert_type(def_gen.integers(I_bool_low, I_bool_high_open, dtype=np.bool), npt.NDArray[np.bool])
    +assert_type(def_gen.integers(0, I_bool_high_open, dtype=np.bool), npt.NDArray[np.bool])
    +assert_type(def_gen.integers(I_bool_high_closed, dtype=np.bool, endpoint=True), npt.NDArray[np.bool])
    +assert_type(def_gen.integers(I_bool_low, I_bool_high_closed, dtype=np.bool, endpoint=True), npt.NDArray[np.bool])
    +assert_type(def_gen.integers(0, I_bool_high_closed, dtype=np.bool, endpoint=True), npt.NDArray[np.bool])
    +
    +I_u1_low: npt.NDArray[np.uint8] = np.array([0], dtype=np.uint8)
    +I_u1_low_like: list[int] = [0]
    +I_u1_high_open: npt.NDArray[np.uint8] = np.array([255], dtype=np.uint8)
    +I_u1_high_closed: npt.NDArray[np.uint8] = np.array([255], dtype=np.uint8)
    +
    +assert_type(def_gen.integers(256, dtype="u1"), np.uint8)
    +assert_type(def_gen.integers(0, 256, dtype="u1"), np.uint8)
    +assert_type(def_gen.integers(255, dtype="u1", endpoint=True), np.uint8)
    +assert_type(def_gen.integers(0, 255, dtype="u1", endpoint=True), np.uint8)
    +assert_type(def_gen.integers(I_u1_low_like, 255, dtype="u1", endpoint=True), npt.NDArray[np.uint8])
    +assert_type(def_gen.integers(I_u1_high_open, dtype="u1"), npt.NDArray[np.uint8])
    +assert_type(def_gen.integers(I_u1_low, I_u1_high_open, dtype="u1"), npt.NDArray[np.uint8])
    +assert_type(def_gen.integers(0, I_u1_high_open, dtype="u1"), npt.NDArray[np.uint8])
    +assert_type(def_gen.integers(I_u1_high_closed, dtype="u1", endpoint=True), npt.NDArray[np.uint8])
    +assert_type(def_gen.integers(I_u1_low, I_u1_high_closed, dtype="u1", endpoint=True), npt.NDArray[np.uint8])
    +assert_type(def_gen.integers(0, I_u1_high_closed, dtype="u1", endpoint=True), npt.NDArray[np.uint8])
    +
    +assert_type(def_gen.integers(256, dtype="uint8"), np.uint8)
    +assert_type(def_gen.integers(0, 256, dtype="uint8"), np.uint8)
    +assert_type(def_gen.integers(255, dtype="uint8", endpoint=True), np.uint8)
    +assert_type(def_gen.integers(0, 255, dtype="uint8", endpoint=True), np.uint8)
    +assert_type(def_gen.integers(I_u1_low_like, 255, dtype="uint8", endpoint=True), npt.NDArray[np.uint8])
    +assert_type(def_gen.integers(I_u1_high_open, dtype="uint8"), npt.NDArray[np.uint8])
    +assert_type(def_gen.integers(I_u1_low, I_u1_high_open, dtype="uint8"), npt.NDArray[np.uint8])
    +assert_type(def_gen.integers(0, I_u1_high_open, dtype="uint8"), npt.NDArray[np.uint8])
    +assert_type(def_gen.integers(I_u1_high_closed, dtype="uint8", endpoint=True), npt.NDArray[np.uint8])
    +assert_type(def_gen.integers(I_u1_low, I_u1_high_closed, dtype="uint8", endpoint=True), npt.NDArray[np.uint8])
    +assert_type(def_gen.integers(0, I_u1_high_closed, dtype="uint8", endpoint=True), npt.NDArray[np.uint8])
    +
    +assert_type(def_gen.integers(256, dtype=np.uint8), np.uint8)
    +assert_type(def_gen.integers(0, 256, dtype=np.uint8), np.uint8)
    +assert_type(def_gen.integers(255, dtype=np.uint8, endpoint=True), np.uint8)
    +assert_type(def_gen.integers(0, 255, dtype=np.uint8, endpoint=True), np.uint8)
    +assert_type(def_gen.integers(I_u1_low_like, 255, dtype=np.uint8, endpoint=True), npt.NDArray[np.uint8])
    +assert_type(def_gen.integers(I_u1_high_open, dtype=np.uint8), npt.NDArray[np.uint8])
    +assert_type(def_gen.integers(I_u1_low, I_u1_high_open, dtype=np.uint8), npt.NDArray[np.uint8])
    +assert_type(def_gen.integers(0, I_u1_high_open, dtype=np.uint8), npt.NDArray[np.uint8])
    +assert_type(def_gen.integers(I_u1_high_closed, dtype=np.uint8, endpoint=True), npt.NDArray[np.uint8])
    +assert_type(def_gen.integers(I_u1_low, I_u1_high_closed, dtype=np.uint8, endpoint=True), npt.NDArray[np.uint8])
    +assert_type(def_gen.integers(0, I_u1_high_closed, dtype=np.uint8, endpoint=True), npt.NDArray[np.uint8])
    +
    +I_u2_low: npt.NDArray[np.uint16] = np.array([0], dtype=np.uint16)
    +I_u2_low_like: list[int] = [0]
    +I_u2_high_open: npt.NDArray[np.uint16] = np.array([65535], dtype=np.uint16)
    +I_u2_high_closed: npt.NDArray[np.uint16] = np.array([65535], dtype=np.uint16)
    +
    +assert_type(def_gen.integers(65536, dtype="u2"), np.uint16)
    +assert_type(def_gen.integers(0, 65536, dtype="u2"), np.uint16)
    +assert_type(def_gen.integers(65535, dtype="u2", endpoint=True), np.uint16)
    +assert_type(def_gen.integers(0, 65535, dtype="u2", endpoint=True), np.uint16)
    +assert_type(def_gen.integers(I_u2_low_like, 65535, dtype="u2", endpoint=True), npt.NDArray[np.uint16])
    +assert_type(def_gen.integers(I_u2_high_open, dtype="u2"), npt.NDArray[np.uint16])
    +assert_type(def_gen.integers(I_u2_low, I_u2_high_open, dtype="u2"), npt.NDArray[np.uint16])
    +assert_type(def_gen.integers(0, I_u2_high_open, dtype="u2"), npt.NDArray[np.uint16])
    +assert_type(def_gen.integers(I_u2_high_closed, dtype="u2", endpoint=True), npt.NDArray[np.uint16])
    +assert_type(def_gen.integers(I_u2_low, I_u2_high_closed, dtype="u2", endpoint=True), npt.NDArray[np.uint16])
    +assert_type(def_gen.integers(0, I_u2_high_closed, dtype="u2", endpoint=True), npt.NDArray[np.uint16])
    +
    +assert_type(def_gen.integers(65536, dtype="uint16"), np.uint16)
    +assert_type(def_gen.integers(0, 65536, dtype="uint16"), np.uint16)
    +assert_type(def_gen.integers(65535, dtype="uint16", endpoint=True), np.uint16)
    +assert_type(def_gen.integers(0, 65535, dtype="uint16", endpoint=True), np.uint16)
    +assert_type(def_gen.integers(I_u2_low_like, 65535, dtype="uint16", endpoint=True), npt.NDArray[np.uint16])
    +assert_type(def_gen.integers(I_u2_high_open, dtype="uint16"), npt.NDArray[np.uint16])
    +assert_type(def_gen.integers(I_u2_low, I_u2_high_open, dtype="uint16"), npt.NDArray[np.uint16])
    +assert_type(def_gen.integers(0, I_u2_high_open, dtype="uint16"), npt.NDArray[np.uint16])
    +assert_type(def_gen.integers(I_u2_high_closed, dtype="uint16", endpoint=True), npt.NDArray[np.uint16])
    +assert_type(def_gen.integers(I_u2_low, I_u2_high_closed, dtype="uint16", endpoint=True), npt.NDArray[np.uint16])
    +assert_type(def_gen.integers(0, I_u2_high_closed, dtype="uint16", endpoint=True), npt.NDArray[np.uint16])
    +
    +assert_type(def_gen.integers(65536, dtype=np.uint16), np.uint16)
    +assert_type(def_gen.integers(0, 65536, dtype=np.uint16), np.uint16)
    +assert_type(def_gen.integers(65535, dtype=np.uint16, endpoint=True), np.uint16)
    +assert_type(def_gen.integers(0, 65535, dtype=np.uint16, endpoint=True), np.uint16)
    +assert_type(def_gen.integers(I_u2_low_like, 65535, dtype=np.uint16, endpoint=True), npt.NDArray[np.uint16])
    +assert_type(def_gen.integers(I_u2_high_open, dtype=np.uint16), npt.NDArray[np.uint16])
    +assert_type(def_gen.integers(I_u2_low, I_u2_high_open, dtype=np.uint16), npt.NDArray[np.uint16])
    +assert_type(def_gen.integers(0, I_u2_high_open, dtype=np.uint16), npt.NDArray[np.uint16])
    +assert_type(def_gen.integers(I_u2_high_closed, dtype=np.uint16, endpoint=True), npt.NDArray[np.uint16])
    +assert_type(def_gen.integers(I_u2_low, I_u2_high_closed, dtype=np.uint16, endpoint=True), npt.NDArray[np.uint16])
    +assert_type(def_gen.integers(0, I_u2_high_closed, dtype=np.uint16, endpoint=True), npt.NDArray[np.uint16])
    +
    +I_u4_low: npt.NDArray[np.uint32] = np.array([0], dtype=np.uint32)
    +I_u4_low_like: list[int] = [0]
    +I_u4_high_open: npt.NDArray[np.uint32] = np.array([4294967295], dtype=np.uint32)
    +I_u4_high_closed: npt.NDArray[np.uint32] = np.array([4294967295], dtype=np.uint32)
    +
    +assert_type(def_gen.integers(4294967296, dtype=np.int_), np.int_)
    +assert_type(def_gen.integers(0, 4294967296, dtype=np.int_), np.int_)
    +assert_type(def_gen.integers(4294967295, dtype=np.int_, endpoint=True), np.int_)
    +assert_type(def_gen.integers(0, 4294967295, dtype=np.int_, endpoint=True), np.int_)
    +assert_type(def_gen.integers(I_u4_low_like, 4294967295, dtype=np.int_, endpoint=True), npt.NDArray[np.int_])
    +assert_type(def_gen.integers(I_u4_high_open, dtype=np.int_), npt.NDArray[np.int_])
    +assert_type(def_gen.integers(I_u4_low, I_u4_high_open, dtype=np.int_), npt.NDArray[np.int_])
    +assert_type(def_gen.integers(0, I_u4_high_open, dtype=np.int_), npt.NDArray[np.int_])
    +assert_type(def_gen.integers(I_u4_high_closed, dtype=np.int_, endpoint=True), npt.NDArray[np.int_])
    +assert_type(def_gen.integers(I_u4_low, I_u4_high_closed, dtype=np.int_, endpoint=True), npt.NDArray[np.int_])
    +assert_type(def_gen.integers(0, I_u4_high_closed, dtype=np.int_, endpoint=True), npt.NDArray[np.int_])
    +
    +assert_type(def_gen.integers(4294967296, dtype="u4"), np.uint32)
    +assert_type(def_gen.integers(0, 4294967296, dtype="u4"), np.uint32)
    +assert_type(def_gen.integers(4294967295, dtype="u4", endpoint=True), np.uint32)
    +assert_type(def_gen.integers(0, 4294967295, dtype="u4", endpoint=True), np.uint32)
    +assert_type(def_gen.integers(I_u4_low_like, 4294967295, dtype="u4", endpoint=True), npt.NDArray[np.uint32])
    +assert_type(def_gen.integers(I_u4_high_open, dtype="u4"), npt.NDArray[np.uint32])
    +assert_type(def_gen.integers(I_u4_low, I_u4_high_open, dtype="u4"), npt.NDArray[np.uint32])
    +assert_type(def_gen.integers(0, I_u4_high_open, dtype="u4"), npt.NDArray[np.uint32])
    +assert_type(def_gen.integers(I_u4_high_closed, dtype="u4", endpoint=True), npt.NDArray[np.uint32])
    +assert_type(def_gen.integers(I_u4_low, I_u4_high_closed, dtype="u4", endpoint=True), npt.NDArray[np.uint32])
    +assert_type(def_gen.integers(0, I_u4_high_closed, dtype="u4", endpoint=True), npt.NDArray[np.uint32])
    +
    +assert_type(def_gen.integers(4294967296, dtype="uint32"), np.uint32)
    +assert_type(def_gen.integers(0, 4294967296, dtype="uint32"), np.uint32)
    +assert_type(def_gen.integers(4294967295, dtype="uint32", endpoint=True), np.uint32)
    +assert_type(def_gen.integers(0, 4294967295, dtype="uint32", endpoint=True), np.uint32)
    +assert_type(def_gen.integers(I_u4_low_like, 4294967295, dtype="uint32", endpoint=True), npt.NDArray[np.uint32])
    +assert_type(def_gen.integers(I_u4_high_open, dtype="uint32"), npt.NDArray[np.uint32])
    +assert_type(def_gen.integers(I_u4_low, I_u4_high_open, dtype="uint32"), npt.NDArray[np.uint32])
    +assert_type(def_gen.integers(0, I_u4_high_open, dtype="uint32"), npt.NDArray[np.uint32])
    +assert_type(def_gen.integers(I_u4_high_closed, dtype="uint32", endpoint=True), npt.NDArray[np.uint32])
    +assert_type(def_gen.integers(I_u4_low, I_u4_high_closed, dtype="uint32", endpoint=True), npt.NDArray[np.uint32])
    +assert_type(def_gen.integers(0, I_u4_high_closed, dtype="uint32", endpoint=True), npt.NDArray[np.uint32])
    +
    +assert_type(def_gen.integers(4294967296, dtype=np.uint32), np.uint32)
    +assert_type(def_gen.integers(0, 4294967296, dtype=np.uint32), np.uint32)
    +assert_type(def_gen.integers(4294967295, dtype=np.uint32, endpoint=True), np.uint32)
    +assert_type(def_gen.integers(0, 4294967295, dtype=np.uint32, endpoint=True), np.uint32)
    +assert_type(def_gen.integers(I_u4_low_like, 4294967295, dtype=np.uint32, endpoint=True), npt.NDArray[np.uint32])
    +assert_type(def_gen.integers(I_u4_high_open, dtype=np.uint32), npt.NDArray[np.uint32])
    +assert_type(def_gen.integers(I_u4_low, I_u4_high_open, dtype=np.uint32), npt.NDArray[np.uint32])
    +assert_type(def_gen.integers(0, I_u4_high_open, dtype=np.uint32), npt.NDArray[np.uint32])
    +assert_type(def_gen.integers(I_u4_high_closed, dtype=np.uint32, endpoint=True), npt.NDArray[np.uint32])
    +assert_type(def_gen.integers(I_u4_low, I_u4_high_closed, dtype=np.uint32, endpoint=True), npt.NDArray[np.uint32])
    +assert_type(def_gen.integers(0, I_u4_high_closed, dtype=np.uint32, endpoint=True), npt.NDArray[np.uint32])
    +
    +assert_type(def_gen.integers(4294967296, dtype=np.uint), np.uint)
    +assert_type(def_gen.integers(0, 4294967296, dtype=np.uint), np.uint)
    +assert_type(def_gen.integers(4294967295, dtype=np.uint, endpoint=True), np.uint)
    +assert_type(def_gen.integers(0, 4294967295, dtype=np.uint, endpoint=True), np.uint)
    +assert_type(def_gen.integers(I_u4_low_like, 4294967295, dtype=np.uint, endpoint=True), npt.NDArray[np.uint])
    +assert_type(def_gen.integers(I_u4_high_open, dtype=np.uint), npt.NDArray[np.uint])
    +assert_type(def_gen.integers(I_u4_low, I_u4_high_open, dtype=np.uint), npt.NDArray[np.uint])
    +assert_type(def_gen.integers(0, I_u4_high_open, dtype=np.uint), npt.NDArray[np.uint])
    +assert_type(def_gen.integers(I_u4_high_closed, dtype=np.uint, endpoint=True), npt.NDArray[np.uint])
    +assert_type(def_gen.integers(I_u4_low, I_u4_high_closed, dtype=np.uint, endpoint=True), npt.NDArray[np.uint])
    +assert_type(def_gen.integers(0, I_u4_high_closed, dtype=np.uint, endpoint=True), npt.NDArray[np.uint])
    +
    +I_u8_low: npt.NDArray[np.uint64] = np.array([0], dtype=np.uint64)
    +I_u8_low_like: list[int] = [0]
    +I_u8_high_open: npt.NDArray[np.uint64] = np.array([18446744073709551615], dtype=np.uint64)
    +I_u8_high_closed: npt.NDArray[np.uint64] = np.array([18446744073709551615], dtype=np.uint64)
    +
    +assert_type(def_gen.integers(18446744073709551616, dtype="u8"), np.uint64)
    +assert_type(def_gen.integers(0, 18446744073709551616, dtype="u8"), np.uint64)
    +assert_type(def_gen.integers(18446744073709551615, dtype="u8", endpoint=True), np.uint64)
    +assert_type(def_gen.integers(0, 18446744073709551615, dtype="u8", endpoint=True), np.uint64)
    +assert_type(def_gen.integers(I_u8_low_like, 18446744073709551615, dtype="u8", endpoint=True), npt.NDArray[np.uint64])
    +assert_type(def_gen.integers(I_u8_high_open, dtype="u8"), npt.NDArray[np.uint64])
    +assert_type(def_gen.integers(I_u8_low, I_u8_high_open, dtype="u8"), npt.NDArray[np.uint64])
    +assert_type(def_gen.integers(0, I_u8_high_open, dtype="u8"), npt.NDArray[np.uint64])
    +assert_type(def_gen.integers(I_u8_high_closed, dtype="u8", endpoint=True), npt.NDArray[np.uint64])
    +assert_type(def_gen.integers(I_u8_low, I_u8_high_closed, dtype="u8", endpoint=True), npt.NDArray[np.uint64])
    +assert_type(def_gen.integers(0, I_u8_high_closed, dtype="u8", endpoint=True), npt.NDArray[np.uint64])
    +
    +assert_type(def_gen.integers(18446744073709551616, dtype="uint64"), np.uint64)
    +assert_type(def_gen.integers(0, 18446744073709551616, dtype="uint64"), np.uint64)
    +assert_type(def_gen.integers(18446744073709551615, dtype="uint64", endpoint=True), np.uint64)
    +assert_type(def_gen.integers(0, 18446744073709551615, dtype="uint64", endpoint=True), np.uint64)
    +assert_type(def_gen.integers(I_u8_low_like, 18446744073709551615, dtype="uint64", endpoint=True), npt.NDArray[np.uint64])
    +assert_type(def_gen.integers(I_u8_high_open, dtype="uint64"), npt.NDArray[np.uint64])
    +assert_type(def_gen.integers(I_u8_low, I_u8_high_open, dtype="uint64"), npt.NDArray[np.uint64])
    +assert_type(def_gen.integers(0, I_u8_high_open, dtype="uint64"), npt.NDArray[np.uint64])
    +assert_type(def_gen.integers(I_u8_high_closed, dtype="uint64", endpoint=True), npt.NDArray[np.uint64])
    +assert_type(def_gen.integers(I_u8_low, I_u8_high_closed, dtype="uint64", endpoint=True), npt.NDArray[np.uint64])
    +assert_type(def_gen.integers(0, I_u8_high_closed, dtype="uint64", endpoint=True), npt.NDArray[np.uint64])
    +
    +assert_type(def_gen.integers(18446744073709551616, dtype=np.uint64), np.uint64)
    +assert_type(def_gen.integers(0, 18446744073709551616, dtype=np.uint64), np.uint64)
    +assert_type(def_gen.integers(18446744073709551615, dtype=np.uint64, endpoint=True), np.uint64)
    +assert_type(def_gen.integers(0, 18446744073709551615, dtype=np.uint64, endpoint=True), np.uint64)
    +assert_type(def_gen.integers(I_u8_low_like, 18446744073709551615, dtype=np.uint64, endpoint=True), npt.NDArray[np.uint64])
    +assert_type(def_gen.integers(I_u8_high_open, dtype=np.uint64), npt.NDArray[np.uint64])
    +assert_type(def_gen.integers(I_u8_low, I_u8_high_open, dtype=np.uint64), npt.NDArray[np.uint64])
    +assert_type(def_gen.integers(0, I_u8_high_open, dtype=np.uint64), npt.NDArray[np.uint64])
    +assert_type(def_gen.integers(I_u8_high_closed, dtype=np.uint64, endpoint=True), npt.NDArray[np.uint64])
    +assert_type(def_gen.integers(I_u8_low, I_u8_high_closed, dtype=np.uint64, endpoint=True), npt.NDArray[np.uint64])
    +assert_type(def_gen.integers(0, I_u8_high_closed, dtype=np.uint64, endpoint=True), npt.NDArray[np.uint64])
    +
    +I_i1_low: npt.NDArray[np.int8] = np.array([-128], dtype=np.int8)
    +I_i1_low_like: list[int] = [-128]
    +I_i1_high_open: npt.NDArray[np.int8] = np.array([127], dtype=np.int8)
    +I_i1_high_closed: npt.NDArray[np.int8] = np.array([127], dtype=np.int8)
    +
    +assert_type(def_gen.integers(128, dtype="i1"), np.int8)
    +assert_type(def_gen.integers(-128, 128, dtype="i1"), np.int8)
    +assert_type(def_gen.integers(127, dtype="i1", endpoint=True), np.int8)
    +assert_type(def_gen.integers(-128, 127, dtype="i1", endpoint=True), np.int8)
    +assert_type(def_gen.integers(I_i1_low_like, 127, dtype="i1", endpoint=True), npt.NDArray[np.int8])
    +assert_type(def_gen.integers(I_i1_high_open, dtype="i1"), npt.NDArray[np.int8])
    +assert_type(def_gen.integers(I_i1_low, I_i1_high_open, dtype="i1"), npt.NDArray[np.int8])
    +assert_type(def_gen.integers(-128, I_i1_high_open, dtype="i1"), npt.NDArray[np.int8])
    +assert_type(def_gen.integers(I_i1_high_closed, dtype="i1", endpoint=True), npt.NDArray[np.int8])
    +assert_type(def_gen.integers(I_i1_low, I_i1_high_closed, dtype="i1", endpoint=True), npt.NDArray[np.int8])
    +assert_type(def_gen.integers(-128, I_i1_high_closed, dtype="i1", endpoint=True), npt.NDArray[np.int8])
    +
    +assert_type(def_gen.integers(128, dtype="int8"), np.int8)
    +assert_type(def_gen.integers(-128, 128, dtype="int8"), np.int8)
    +assert_type(def_gen.integers(127, dtype="int8", endpoint=True), np.int8)
    +assert_type(def_gen.integers(-128, 127, dtype="int8", endpoint=True), np.int8)
    +assert_type(def_gen.integers(I_i1_low_like, 127, dtype="int8", endpoint=True), npt.NDArray[np.int8])
    +assert_type(def_gen.integers(I_i1_high_open, dtype="int8"), npt.NDArray[np.int8])
    +assert_type(def_gen.integers(I_i1_low, I_i1_high_open, dtype="int8"), npt.NDArray[np.int8])
    +assert_type(def_gen.integers(-128, I_i1_high_open, dtype="int8"), npt.NDArray[np.int8])
    +assert_type(def_gen.integers(I_i1_high_closed, dtype="int8", endpoint=True), npt.NDArray[np.int8])
    +assert_type(def_gen.integers(I_i1_low, I_i1_high_closed, dtype="int8", endpoint=True), npt.NDArray[np.int8])
    +assert_type(def_gen.integers(-128, I_i1_high_closed, dtype="int8", endpoint=True), npt.NDArray[np.int8])
    +
    +assert_type(def_gen.integers(128, dtype=np.int8), np.int8)
    +assert_type(def_gen.integers(-128, 128, dtype=np.int8), np.int8)
    +assert_type(def_gen.integers(127, dtype=np.int8, endpoint=True), np.int8)
    +assert_type(def_gen.integers(-128, 127, dtype=np.int8, endpoint=True), np.int8)
    +assert_type(def_gen.integers(I_i1_low_like, 127, dtype=np.int8, endpoint=True), npt.NDArray[np.int8])
    +assert_type(def_gen.integers(I_i1_high_open, dtype=np.int8), npt.NDArray[np.int8])
    +assert_type(def_gen.integers(I_i1_low, I_i1_high_open, dtype=np.int8), npt.NDArray[np.int8])
    +assert_type(def_gen.integers(-128, I_i1_high_open, dtype=np.int8), npt.NDArray[np.int8])
    +assert_type(def_gen.integers(I_i1_high_closed, dtype=np.int8, endpoint=True), npt.NDArray[np.int8])
    +assert_type(def_gen.integers(I_i1_low, I_i1_high_closed, dtype=np.int8, endpoint=True), npt.NDArray[np.int8])
    +assert_type(def_gen.integers(-128, I_i1_high_closed, dtype=np.int8, endpoint=True), npt.NDArray[np.int8])
    +
    +I_i2_low: npt.NDArray[np.int16] = np.array([-32768], dtype=np.int16)
    +I_i2_low_like: list[int] = [-32768]
    +I_i2_high_open: npt.NDArray[np.int16] = np.array([32767], dtype=np.int16)
    +I_i2_high_closed: npt.NDArray[np.int16] = np.array([32767], dtype=np.int16)
    +
    +assert_type(def_gen.integers(32768, dtype="i2"), np.int16)
    +assert_type(def_gen.integers(-32768, 32768, dtype="i2"), np.int16)
    +assert_type(def_gen.integers(32767, dtype="i2", endpoint=True), np.int16)
    +assert_type(def_gen.integers(-32768, 32767, dtype="i2", endpoint=True), np.int16)
    +assert_type(def_gen.integers(I_i2_low_like, 32767, dtype="i2", endpoint=True), npt.NDArray[np.int16])
    +assert_type(def_gen.integers(I_i2_high_open, dtype="i2"), npt.NDArray[np.int16])
    +assert_type(def_gen.integers(I_i2_low, I_i2_high_open, dtype="i2"), npt.NDArray[np.int16])
    +assert_type(def_gen.integers(-32768, I_i2_high_open, dtype="i2"), npt.NDArray[np.int16])
    +assert_type(def_gen.integers(I_i2_high_closed, dtype="i2", endpoint=True), npt.NDArray[np.int16])
    +assert_type(def_gen.integers(I_i2_low, I_i2_high_closed, dtype="i2", endpoint=True), npt.NDArray[np.int16])
    +assert_type(def_gen.integers(-32768, I_i2_high_closed, dtype="i2", endpoint=True), npt.NDArray[np.int16])
    +
    +assert_type(def_gen.integers(32768, dtype="int16"), np.int16)
    +assert_type(def_gen.integers(-32768, 32768, dtype="int16"), np.int16)
    +assert_type(def_gen.integers(32767, dtype="int16", endpoint=True), np.int16)
    +assert_type(def_gen.integers(-32768, 32767, dtype="int16", endpoint=True), np.int16)
    +assert_type(def_gen.integers(I_i2_low_like, 32767, dtype="int16", endpoint=True), npt.NDArray[np.int16])
    +assert_type(def_gen.integers(I_i2_high_open, dtype="int16"), npt.NDArray[np.int16])
    +assert_type(def_gen.integers(I_i2_low, I_i2_high_open, dtype="int16"), npt.NDArray[np.int16])
    +assert_type(def_gen.integers(-32768, I_i2_high_open, dtype="int16"), npt.NDArray[np.int16])
    +assert_type(def_gen.integers(I_i2_high_closed, dtype="int16", endpoint=True), npt.NDArray[np.int16])
    +assert_type(def_gen.integers(I_i2_low, I_i2_high_closed, dtype="int16", endpoint=True), npt.NDArray[np.int16])
    +assert_type(def_gen.integers(-32768, I_i2_high_closed, dtype="int16", endpoint=True), npt.NDArray[np.int16])
    +
    +assert_type(def_gen.integers(32768, dtype=np.int16), np.int16)
    +assert_type(def_gen.integers(-32768, 32768, dtype=np.int16), np.int16)
    +assert_type(def_gen.integers(32767, dtype=np.int16, endpoint=True), np.int16)
    +assert_type(def_gen.integers(-32768, 32767, dtype=np.int16, endpoint=True), np.int16)
    +assert_type(def_gen.integers(I_i2_low_like, 32767, dtype=np.int16, endpoint=True), npt.NDArray[np.int16])
    +assert_type(def_gen.integers(I_i2_high_open, dtype=np.int16), npt.NDArray[np.int16])
    +assert_type(def_gen.integers(I_i2_low, I_i2_high_open, dtype=np.int16), npt.NDArray[np.int16])
    +assert_type(def_gen.integers(-32768, I_i2_high_open, dtype=np.int16), npt.NDArray[np.int16])
    +assert_type(def_gen.integers(I_i2_high_closed, dtype=np.int16, endpoint=True), npt.NDArray[np.int16])
    +assert_type(def_gen.integers(I_i2_low, I_i2_high_closed, dtype=np.int16, endpoint=True), npt.NDArray[np.int16])
    +assert_type(def_gen.integers(-32768, I_i2_high_closed, dtype=np.int16, endpoint=True), npt.NDArray[np.int16])
    +
    +I_i4_low: npt.NDArray[np.int32] = np.array([-2147483648], dtype=np.int32)
    +I_i4_low_like: list[int] = [-2147483648]
    +I_i4_high_open: npt.NDArray[np.int32] = np.array([2147483647], dtype=np.int32)
    +I_i4_high_closed: npt.NDArray[np.int32] = np.array([2147483647], dtype=np.int32)
    +
    +assert_type(def_gen.integers(2147483648, dtype="i4"), np.int32)
    +assert_type(def_gen.integers(-2147483648, 2147483648, dtype="i4"), np.int32)
    +assert_type(def_gen.integers(2147483647, dtype="i4", endpoint=True), np.int32)
    +assert_type(def_gen.integers(-2147483648, 2147483647, dtype="i4", endpoint=True), np.int32)
    +assert_type(def_gen.integers(I_i4_low_like, 2147483647, dtype="i4", endpoint=True), npt.NDArray[np.int32])
    +assert_type(def_gen.integers(I_i4_high_open, dtype="i4"), npt.NDArray[np.int32])
    +assert_type(def_gen.integers(I_i4_low, I_i4_high_open, dtype="i4"), npt.NDArray[np.int32])
    +assert_type(def_gen.integers(-2147483648, I_i4_high_open, dtype="i4"), npt.NDArray[np.int32])
    +assert_type(def_gen.integers(I_i4_high_closed, dtype="i4", endpoint=True), npt.NDArray[np.int32])
    +assert_type(def_gen.integers(I_i4_low, I_i4_high_closed, dtype="i4", endpoint=True), npt.NDArray[np.int32])
    +assert_type(def_gen.integers(-2147483648, I_i4_high_closed, dtype="i4", endpoint=True), npt.NDArray[np.int32])
    +
    +assert_type(def_gen.integers(2147483648, dtype="int32"), np.int32)
    +assert_type(def_gen.integers(-2147483648, 2147483648, dtype="int32"), np.int32)
    +assert_type(def_gen.integers(2147483647, dtype="int32", endpoint=True), np.int32)
    +assert_type(def_gen.integers(-2147483648, 2147483647, dtype="int32", endpoint=True), np.int32)
    +assert_type(def_gen.integers(I_i4_low_like, 2147483647, dtype="int32", endpoint=True), npt.NDArray[np.int32])
    +assert_type(def_gen.integers(I_i4_high_open, dtype="int32"), npt.NDArray[np.int32])
    +assert_type(def_gen.integers(I_i4_low, I_i4_high_open, dtype="int32"), npt.NDArray[np.int32])
    +assert_type(def_gen.integers(-2147483648, I_i4_high_open, dtype="int32"), npt.NDArray[np.int32])
    +assert_type(def_gen.integers(I_i4_high_closed, dtype="int32", endpoint=True), npt.NDArray[np.int32])
    +assert_type(def_gen.integers(I_i4_low, I_i4_high_closed, dtype="int32", endpoint=True), npt.NDArray[np.int32])
    +assert_type(def_gen.integers(-2147483648, I_i4_high_closed, dtype="int32", endpoint=True), npt.NDArray[np.int32])
    +
    +assert_type(def_gen.integers(2147483648, dtype=np.int32), np.int32)
    +assert_type(def_gen.integers(-2147483648, 2147483648, dtype=np.int32), np.int32)
    +assert_type(def_gen.integers(2147483647, dtype=np.int32, endpoint=True), np.int32)
    +assert_type(def_gen.integers(-2147483648, 2147483647, dtype=np.int32, endpoint=True), np.int32)
    +assert_type(def_gen.integers(I_i4_low_like, 2147483647, dtype=np.int32, endpoint=True), npt.NDArray[np.int32])
    +assert_type(def_gen.integers(I_i4_high_open, dtype=np.int32), npt.NDArray[np.int32])
    +assert_type(def_gen.integers(I_i4_low, I_i4_high_open, dtype=np.int32), npt.NDArray[np.int32])
    +assert_type(def_gen.integers(-2147483648, I_i4_high_open, dtype=np.int32), npt.NDArray[np.int32])
    +assert_type(def_gen.integers(I_i4_high_closed, dtype=np.int32, endpoint=True), npt.NDArray[np.int32])
    +assert_type(def_gen.integers(I_i4_low, I_i4_high_closed, dtype=np.int32, endpoint=True), npt.NDArray[np.int32])
    +assert_type(def_gen.integers(-2147483648, I_i4_high_closed, dtype=np.int32, endpoint=True), npt.NDArray[np.int32])
    +
    +I_i8_low: npt.NDArray[np.int64] = np.array([-9223372036854775808], dtype=np.int64)
    +I_i8_low_like: list[int] = [-9223372036854775808]
    +I_i8_high_open: npt.NDArray[np.int64] = np.array([9223372036854775807], dtype=np.int64)
    +I_i8_high_closed: npt.NDArray[np.int64] = np.array([9223372036854775807], dtype=np.int64)
    +
    +assert_type(def_gen.integers(9223372036854775808, dtype="i8"), np.int64)
    +assert_type(def_gen.integers(-9223372036854775808, 9223372036854775808, dtype="i8"), np.int64)
    +assert_type(def_gen.integers(9223372036854775807, dtype="i8", endpoint=True), np.int64)
    +assert_type(def_gen.integers(-9223372036854775808, 9223372036854775807, dtype="i8", endpoint=True), np.int64)
    +assert_type(def_gen.integers(I_i8_low_like, 9223372036854775807, dtype="i8", endpoint=True), npt.NDArray[np.int64])
    +assert_type(def_gen.integers(I_i8_high_open, dtype="i8"), npt.NDArray[np.int64])
    +assert_type(def_gen.integers(I_i8_low, I_i8_high_open, dtype="i8"), npt.NDArray[np.int64])
    +assert_type(def_gen.integers(-9223372036854775808, I_i8_high_open, dtype="i8"), npt.NDArray[np.int64])
    +assert_type(def_gen.integers(I_i8_high_closed, dtype="i8", endpoint=True), npt.NDArray[np.int64])
    +assert_type(def_gen.integers(I_i8_low, I_i8_high_closed, dtype="i8", endpoint=True), npt.NDArray[np.int64])
    +assert_type(def_gen.integers(-9223372036854775808, I_i8_high_closed, dtype="i8", endpoint=True), npt.NDArray[np.int64])
    +
    +assert_type(def_gen.integers(9223372036854775808, dtype="int64"), np.int64)
    +assert_type(def_gen.integers(-9223372036854775808, 9223372036854775808, dtype="int64"), np.int64)
    +assert_type(def_gen.integers(9223372036854775807, dtype="int64", endpoint=True), np.int64)
    +assert_type(def_gen.integers(-9223372036854775808, 9223372036854775807, dtype="int64", endpoint=True), np.int64)
    +assert_type(def_gen.integers(I_i8_low_like, 9223372036854775807, dtype="int64", endpoint=True), npt.NDArray[np.int64])
    +assert_type(def_gen.integers(I_i8_high_open, dtype="int64"), npt.NDArray[np.int64])
    +assert_type(def_gen.integers(I_i8_low, I_i8_high_open, dtype="int64"), npt.NDArray[np.int64])
    +assert_type(def_gen.integers(-9223372036854775808, I_i8_high_open, dtype="int64"), npt.NDArray[np.int64])
    +assert_type(def_gen.integers(I_i8_high_closed, dtype="int64", endpoint=True), npt.NDArray[np.int64])
    +assert_type(def_gen.integers(I_i8_low, I_i8_high_closed, dtype="int64", endpoint=True), npt.NDArray[np.int64])
    +assert_type(def_gen.integers(-9223372036854775808, I_i8_high_closed, dtype="int64", endpoint=True), npt.NDArray[np.int64])
    +
    +assert_type(def_gen.integers(9223372036854775808, dtype=np.int64), np.int64)
    +assert_type(def_gen.integers(-9223372036854775808, 9223372036854775808, dtype=np.int64), np.int64)
    +assert_type(def_gen.integers(9223372036854775807, dtype=np.int64, endpoint=True), np.int64)
    +assert_type(def_gen.integers(-9223372036854775808, 9223372036854775807, dtype=np.int64, endpoint=True), np.int64)
    +assert_type(def_gen.integers(I_i8_low_like, 9223372036854775807, dtype=np.int64, endpoint=True), npt.NDArray[np.int64])
    +assert_type(def_gen.integers(I_i8_high_open, dtype=np.int64), npt.NDArray[np.int64])
    +assert_type(def_gen.integers(I_i8_low, I_i8_high_open, dtype=np.int64), npt.NDArray[np.int64])
    +assert_type(def_gen.integers(-9223372036854775808, I_i8_high_open, dtype=np.int64), npt.NDArray[np.int64])
    +assert_type(def_gen.integers(I_i8_high_closed, dtype=np.int64, endpoint=True), npt.NDArray[np.int64])
    +assert_type(def_gen.integers(I_i8_low, I_i8_high_closed, dtype=np.int64, endpoint=True), npt.NDArray[np.int64])
    +assert_type(def_gen.integers(-9223372036854775808, I_i8_high_closed, dtype=np.int64, endpoint=True), npt.NDArray[np.int64])
    +
    +assert_type(def_gen.bit_generator, np.random.BitGenerator)
    +
    +assert_type(def_gen.bytes(2), bytes)
    +
    +assert_type(def_gen.choice(5), int)
    +assert_type(def_gen.choice(5, 3), npt.NDArray[np.int64])
    +assert_type(def_gen.choice(5, 3, replace=True), npt.NDArray[np.int64])
    +assert_type(def_gen.choice(5, 3, p=[1 / 5] * 5), npt.NDArray[np.int64])
    +assert_type(def_gen.choice(5, 3, p=[1 / 5] * 5, replace=False), npt.NDArray[np.int64])
    +
    +assert_type(def_gen.choice(["pooh", "rabbit", "piglet", "Christopher"]), Any)
    +assert_type(def_gen.choice(["pooh", "rabbit", "piglet", "Christopher"], 3), npt.NDArray[Any])
    +assert_type(def_gen.choice(["pooh", "rabbit", "piglet", "Christopher"], 3, p=[1 / 4] * 4), npt.NDArray[Any])
    +assert_type(def_gen.choice(["pooh", "rabbit", "piglet", "Christopher"], 3, replace=True), npt.NDArray[Any])
    +assert_type(def_gen.choice(["pooh", "rabbit", "piglet", "Christopher"], 3, replace=False, p=np.array([1 / 8, 1 / 8, 1 / 2, 1 / 4])), npt.NDArray[Any])
    +
    +assert_type(def_gen.dirichlet([0.5, 0.5]), npt.NDArray[np.float64])
    +assert_type(def_gen.dirichlet(np.array([0.5, 0.5])), npt.NDArray[np.float64])
    +assert_type(def_gen.dirichlet(np.array([0.5, 0.5]), size=3), npt.NDArray[np.float64])
    +
    +assert_type(def_gen.multinomial(20, [1 / 6.0] * 6), npt.NDArray[np.int64])
    +assert_type(def_gen.multinomial(20, np.array([0.5, 0.5])), npt.NDArray[np.int64])
    +assert_type(def_gen.multinomial(20, [1 / 6.0] * 6, size=2), npt.NDArray[np.int64])
    +assert_type(def_gen.multinomial([[10], [20]], [1 / 6.0] * 6, size=(2, 2)), npt.NDArray[np.int64])
    +assert_type(def_gen.multinomial(np.array([[10], [20]]), np.array([0.5, 0.5]), size=(2, 2)), npt.NDArray[np.int64])
    +
    +assert_type(def_gen.multivariate_hypergeometric([3, 5, 7], 2), npt.NDArray[np.int64])
    +assert_type(def_gen.multivariate_hypergeometric(np.array([3, 5, 7]), 2), npt.NDArray[np.int64])
    +assert_type(def_gen.multivariate_hypergeometric(np.array([3, 5, 7]), 2, size=4), npt.NDArray[np.int64])
    +assert_type(def_gen.multivariate_hypergeometric(np.array([3, 5, 7]), 2, size=(4, 7)), npt.NDArray[np.int64])
    +assert_type(def_gen.multivariate_hypergeometric([3, 5, 7], 2, method="count"), npt.NDArray[np.int64])
    +assert_type(def_gen.multivariate_hypergeometric(np.array([3, 5, 7]), 2, method="marginals"), npt.NDArray[np.int64])
    +
    +assert_type(def_gen.multivariate_normal([0.0], [[1.0]]), npt.NDArray[np.float64])
    +assert_type(def_gen.multivariate_normal([0.0], np.array([[1.0]])), npt.NDArray[np.float64])
    +assert_type(def_gen.multivariate_normal(np.array([0.0]), [[1.0]]), npt.NDArray[np.float64])
    +assert_type(def_gen.multivariate_normal([0.0], np.array([[1.0]])), npt.NDArray[np.float64])
    +
    +assert_type(def_gen.permutation(10), npt.NDArray[np.int64])
    +assert_type(def_gen.permutation([1, 2, 3, 4]), npt.NDArray[Any])
    +assert_type(def_gen.permutation(np.array([1, 2, 3, 4])), npt.NDArray[Any])
    +assert_type(def_gen.permutation(D_2D, axis=1), npt.NDArray[Any])
    +assert_type(def_gen.permuted(D_2D), npt.NDArray[Any])
    +assert_type(def_gen.permuted(D_2D_like), npt.NDArray[Any])
    +assert_type(def_gen.permuted(D_2D, axis=1), npt.NDArray[Any])
    +assert_type(def_gen.permuted(D_2D, out=D_2D), npt.NDArray[Any])
    +assert_type(def_gen.permuted(D_2D_like, out=D_2D), npt.NDArray[Any])
    +assert_type(def_gen.permuted(D_2D_like, out=D_2D), npt.NDArray[Any])
    +assert_type(def_gen.permuted(D_2D, axis=1, out=D_2D), npt.NDArray[Any])
    +
    +assert_type(def_gen.shuffle(np.arange(10)), None)
    +assert_type(def_gen.shuffle([1, 2, 3, 4, 5]), None)
    +assert_type(def_gen.shuffle(D_2D, axis=1), None)
    +
    +assert_type(np.random.Generator(pcg64), np.random.Generator)
    +assert_type(def_gen.__str__(), str)
    +assert_type(def_gen.__repr__(), str)
    +assert_type(def_gen.__setstate__(dict(def_gen.bit_generator.state)), None)
    +
    +# RandomState
    +random_st: np.random.RandomState = np.random.RandomState()
    +
    +assert_type(random_st.standard_normal(), float)
    +assert_type(random_st.standard_normal(size=None), float)
    +assert_type(random_st.standard_normal(size=1), npt.NDArray[np.float64])
    +
    +assert_type(random_st.random(), float)
    +assert_type(random_st.random(size=None), float)
    +assert_type(random_st.random(size=1), npt.NDArray[np.float64])
    +
    +assert_type(random_st.standard_cauchy(), float)
    +assert_type(random_st.standard_cauchy(size=None), float)
    +assert_type(random_st.standard_cauchy(size=1), npt.NDArray[np.float64])
    +
    +assert_type(random_st.standard_exponential(), float)
    +assert_type(random_st.standard_exponential(size=None), float)
    +assert_type(random_st.standard_exponential(size=1), npt.NDArray[np.float64])
    +
    +assert_type(random_st.zipf(1.5), int)
    +assert_type(random_st.zipf(1.5, size=None), int)
    +assert_type(random_st.zipf(1.5, size=1), npt.NDArray[np.long])
    +assert_type(random_st.zipf(D_arr_1p5), npt.NDArray[np.long])
    +assert_type(random_st.zipf(D_arr_1p5, size=1), npt.NDArray[np.long])
    +assert_type(random_st.zipf(D_arr_like_1p5), npt.NDArray[np.long])
    +assert_type(random_st.zipf(D_arr_like_1p5, size=1), npt.NDArray[np.long])
    +
    +assert_type(random_st.weibull(0.5), float)
    +assert_type(random_st.weibull(0.5, size=None), float)
    +assert_type(random_st.weibull(0.5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.weibull(D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.weibull(D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.weibull(D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.weibull(D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +
    +assert_type(random_st.standard_t(0.5), float)
    +assert_type(random_st.standard_t(0.5, size=None), float)
    +assert_type(random_st.standard_t(0.5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.standard_t(D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.standard_t(D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.standard_t(D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.standard_t(D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +
    +assert_type(random_st.poisson(0.5), int)
    +assert_type(random_st.poisson(0.5, size=None), int)
    +assert_type(random_st.poisson(0.5, size=1), npt.NDArray[np.long])
    +assert_type(random_st.poisson(D_arr_0p5), npt.NDArray[np.long])
    +assert_type(random_st.poisson(D_arr_0p5, size=1), npt.NDArray[np.long])
    +assert_type(random_st.poisson(D_arr_like_0p5), npt.NDArray[np.long])
    +assert_type(random_st.poisson(D_arr_like_0p5, size=1), npt.NDArray[np.long])
    +
    +assert_type(random_st.power(0.5), float)
    +assert_type(random_st.power(0.5, size=None), float)
    +assert_type(random_st.power(0.5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.power(D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.power(D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.power(D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.power(D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +
    +assert_type(random_st.pareto(0.5), float)
    +assert_type(random_st.pareto(0.5, size=None), float)
    +assert_type(random_st.pareto(0.5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.pareto(D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.pareto(D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.pareto(D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.pareto(D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +
    +assert_type(random_st.chisquare(0.5), float)
    +assert_type(random_st.chisquare(0.5, size=None), float)
    +assert_type(random_st.chisquare(0.5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.chisquare(D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.chisquare(D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.chisquare(D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.chisquare(D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +
    +assert_type(random_st.exponential(0.5), float)
    +assert_type(random_st.exponential(0.5, size=None), float)
    +assert_type(random_st.exponential(0.5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.exponential(D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.exponential(D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.exponential(D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.exponential(D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +
    +assert_type(random_st.geometric(0.5), int)
    +assert_type(random_st.geometric(0.5, size=None), int)
    +assert_type(random_st.geometric(0.5, size=1), npt.NDArray[np.long])
    +assert_type(random_st.geometric(D_arr_0p5), npt.NDArray[np.long])
    +assert_type(random_st.geometric(D_arr_0p5, size=1), npt.NDArray[np.long])
    +assert_type(random_st.geometric(D_arr_like_0p5), npt.NDArray[np.long])
    +assert_type(random_st.geometric(D_arr_like_0p5, size=1), npt.NDArray[np.long])
    +
    +assert_type(random_st.logseries(0.5), int)
    +assert_type(random_st.logseries(0.5, size=None), int)
    +assert_type(random_st.logseries(0.5, size=1), npt.NDArray[np.long])
    +assert_type(random_st.logseries(D_arr_0p5), npt.NDArray[np.long])
    +assert_type(random_st.logseries(D_arr_0p5, size=1), npt.NDArray[np.long])
    +assert_type(random_st.logseries(D_arr_like_0p5), npt.NDArray[np.long])
    +assert_type(random_st.logseries(D_arr_like_0p5, size=1), npt.NDArray[np.long])
    +
    +assert_type(random_st.rayleigh(0.5), float)
    +assert_type(random_st.rayleigh(0.5, size=None), float)
    +assert_type(random_st.rayleigh(0.5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.rayleigh(D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.rayleigh(D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.rayleigh(D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.rayleigh(D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +
    +assert_type(random_st.standard_gamma(0.5), float)
    +assert_type(random_st.standard_gamma(0.5, size=None), float)
    +assert_type(random_st.standard_gamma(0.5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.standard_gamma(D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.standard_gamma(D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.standard_gamma(D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.standard_gamma(D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.standard_gamma(D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +
    +assert_type(random_st.vonmises(0.5, 0.5), float)
    +assert_type(random_st.vonmises(0.5, 0.5, size=None), float)
    +assert_type(random_st.vonmises(0.5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.vonmises(D_arr_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(random_st.vonmises(0.5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.vonmises(D_arr_0p5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.vonmises(0.5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.vonmises(D_arr_like_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(random_st.vonmises(0.5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.vonmises(D_arr_0p5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.vonmises(D_arr_like_0p5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.vonmises(D_arr_0p5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.vonmises(D_arr_like_0p5, D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +
    +assert_type(random_st.wald(0.5, 0.5), float)
    +assert_type(random_st.wald(0.5, 0.5, size=None), float)
    +assert_type(random_st.wald(0.5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.wald(D_arr_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(random_st.wald(0.5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.wald(D_arr_0p5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.wald(0.5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.wald(D_arr_like_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(random_st.wald(0.5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.wald(D_arr_0p5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.wald(D_arr_like_0p5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.wald(D_arr_0p5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.wald(D_arr_like_0p5, D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +
    +assert_type(random_st.uniform(0.5, 0.5), float)
    +assert_type(random_st.uniform(0.5, 0.5, size=None), float)
    +assert_type(random_st.uniform(0.5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.uniform(D_arr_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(random_st.uniform(0.5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.uniform(D_arr_0p5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.uniform(0.5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.uniform(D_arr_like_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(random_st.uniform(0.5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.uniform(D_arr_0p5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.uniform(D_arr_like_0p5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.uniform(D_arr_0p5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.uniform(D_arr_like_0p5, D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +
    +assert_type(random_st.beta(0.5, 0.5), float)
    +assert_type(random_st.beta(0.5, 0.5, size=None), float)
    +assert_type(random_st.beta(0.5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.beta(D_arr_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(random_st.beta(0.5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.beta(D_arr_0p5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.beta(0.5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.beta(D_arr_like_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(random_st.beta(0.5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.beta(D_arr_0p5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.beta(D_arr_like_0p5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.beta(D_arr_0p5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.beta(D_arr_like_0p5, D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +
    +assert_type(random_st.f(0.5, 0.5), float)
    +assert_type(random_st.f(0.5, 0.5, size=None), float)
    +assert_type(random_st.f(0.5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.f(D_arr_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(random_st.f(0.5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.f(D_arr_0p5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.f(0.5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.f(D_arr_like_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(random_st.f(0.5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.f(D_arr_0p5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.f(D_arr_like_0p5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.f(D_arr_0p5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.f(D_arr_like_0p5, D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +
    +assert_type(random_st.gamma(0.5, 0.5), float)
    +assert_type(random_st.gamma(0.5, 0.5, size=None), float)
    +assert_type(random_st.gamma(0.5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.gamma(D_arr_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(random_st.gamma(0.5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.gamma(D_arr_0p5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.gamma(0.5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.gamma(D_arr_like_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(random_st.gamma(0.5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.gamma(D_arr_0p5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.gamma(D_arr_like_0p5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.gamma(D_arr_0p5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.gamma(D_arr_like_0p5, D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +
    +assert_type(random_st.gumbel(0.5, 0.5), float)
    +assert_type(random_st.gumbel(0.5, 0.5, size=None), float)
    +assert_type(random_st.gumbel(0.5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.gumbel(D_arr_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(random_st.gumbel(0.5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.gumbel(D_arr_0p5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.gumbel(0.5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.gumbel(D_arr_like_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(random_st.gumbel(0.5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.gumbel(D_arr_0p5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.gumbel(D_arr_like_0p5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.gumbel(D_arr_0p5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.gumbel(D_arr_like_0p5, D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +
    +assert_type(random_st.laplace(0.5, 0.5), float)
    +assert_type(random_st.laplace(0.5, 0.5, size=None), float)
    +assert_type(random_st.laplace(0.5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.laplace(D_arr_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(random_st.laplace(0.5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.laplace(D_arr_0p5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.laplace(0.5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.laplace(D_arr_like_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(random_st.laplace(0.5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.laplace(D_arr_0p5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.laplace(D_arr_like_0p5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.laplace(D_arr_0p5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.laplace(D_arr_like_0p5, D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +
    +assert_type(random_st.logistic(0.5, 0.5), float)
    +assert_type(random_st.logistic(0.5, 0.5, size=None), float)
    +assert_type(random_st.logistic(0.5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.logistic(D_arr_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(random_st.logistic(0.5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.logistic(D_arr_0p5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.logistic(0.5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.logistic(D_arr_like_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(random_st.logistic(0.5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.logistic(D_arr_0p5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.logistic(D_arr_like_0p5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.logistic(D_arr_0p5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.logistic(D_arr_like_0p5, D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +
    +assert_type(random_st.lognormal(0.5, 0.5), float)
    +assert_type(random_st.lognormal(0.5, 0.5, size=None), float)
    +assert_type(random_st.lognormal(0.5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.lognormal(D_arr_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(random_st.lognormal(0.5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.lognormal(D_arr_0p5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.lognormal(0.5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.lognormal(D_arr_like_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(random_st.lognormal(0.5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.lognormal(D_arr_0p5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.lognormal(D_arr_like_0p5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.lognormal(D_arr_0p5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.lognormal(D_arr_like_0p5, D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +
    +assert_type(random_st.noncentral_chisquare(0.5, 0.5), float)
    +assert_type(random_st.noncentral_chisquare(0.5, 0.5, size=None), float)
    +assert_type(random_st.noncentral_chisquare(0.5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.noncentral_chisquare(D_arr_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(random_st.noncentral_chisquare(0.5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.noncentral_chisquare(D_arr_0p5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.noncentral_chisquare(0.5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.noncentral_chisquare(D_arr_like_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(random_st.noncentral_chisquare(0.5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.noncentral_chisquare(D_arr_0p5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.noncentral_chisquare(D_arr_like_0p5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.noncentral_chisquare(D_arr_0p5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.noncentral_chisquare(D_arr_like_0p5, D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +
    +assert_type(random_st.normal(0.5, 0.5), float)
    +assert_type(random_st.normal(0.5, 0.5, size=None), float)
    +assert_type(random_st.normal(0.5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.normal(D_arr_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(random_st.normal(0.5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.normal(D_arr_0p5, 0.5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.normal(0.5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.normal(D_arr_like_0p5, 0.5), npt.NDArray[np.float64])
    +assert_type(random_st.normal(0.5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.normal(D_arr_0p5, D_arr_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.normal(D_arr_like_0p5, D_arr_like_0p5), npt.NDArray[np.float64])
    +assert_type(random_st.normal(D_arr_0p5, D_arr_0p5, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.normal(D_arr_like_0p5, D_arr_like_0p5, size=1), npt.NDArray[np.float64])
    +
    +assert_type(random_st.triangular(0.1, 0.5, 0.9), float)
    +assert_type(random_st.triangular(0.1, 0.5, 0.9, size=None), float)
    +assert_type(random_st.triangular(0.1, 0.5, 0.9, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.triangular(D_arr_0p1, 0.5, 0.9), npt.NDArray[np.float64])
    +assert_type(random_st.triangular(0.1, D_arr_0p5, 0.9), npt.NDArray[np.float64])
    +assert_type(random_st.triangular(D_arr_0p1, 0.5, D_arr_like_0p9, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.triangular(0.1, D_arr_0p5, 0.9, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.triangular(D_arr_like_0p1, 0.5, D_arr_0p9), npt.NDArray[np.float64])
    +assert_type(random_st.triangular(0.5, D_arr_like_0p5, 0.9), npt.NDArray[np.float64])
    +assert_type(random_st.triangular(D_arr_0p1, D_arr_0p5, 0.9), npt.NDArray[np.float64])
    +assert_type(random_st.triangular(D_arr_like_0p1, D_arr_like_0p5, 0.9), npt.NDArray[np.float64])
    +assert_type(random_st.triangular(D_arr_0p1, D_arr_0p5, D_arr_0p9, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.triangular(D_arr_like_0p1, D_arr_like_0p5, D_arr_like_0p9, size=1), npt.NDArray[np.float64])
    +
    +assert_type(random_st.noncentral_f(0.1, 0.5, 0.9), float)
    +assert_type(random_st.noncentral_f(0.1, 0.5, 0.9, size=None), float)
    +assert_type(random_st.noncentral_f(0.1, 0.5, 0.9, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.noncentral_f(D_arr_0p1, 0.5, 0.9), npt.NDArray[np.float64])
    +assert_type(random_st.noncentral_f(0.1, D_arr_0p5, 0.9), npt.NDArray[np.float64])
    +assert_type(random_st.noncentral_f(D_arr_0p1, 0.5, D_arr_like_0p9, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.noncentral_f(0.1, D_arr_0p5, 0.9, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.noncentral_f(D_arr_like_0p1, 0.5, D_arr_0p9), npt.NDArray[np.float64])
    +assert_type(random_st.noncentral_f(0.5, D_arr_like_0p5, 0.9), npt.NDArray[np.float64])
    +assert_type(random_st.noncentral_f(D_arr_0p1, D_arr_0p5, 0.9), npt.NDArray[np.float64])
    +assert_type(random_st.noncentral_f(D_arr_like_0p1, D_arr_like_0p5, 0.9), npt.NDArray[np.float64])
    +assert_type(random_st.noncentral_f(D_arr_0p1, D_arr_0p5, D_arr_0p9, size=1), npt.NDArray[np.float64])
    +assert_type(random_st.noncentral_f(D_arr_like_0p1, D_arr_like_0p5, D_arr_like_0p9, size=1), npt.NDArray[np.float64])
    +
    +assert_type(random_st.binomial(10, 0.5), int)
    +assert_type(random_st.binomial(10, 0.5, size=None), int)
    +assert_type(random_st.binomial(10, 0.5, size=1), npt.NDArray[np.long])
    +assert_type(random_st.binomial(I_arr_10, 0.5), npt.NDArray[np.long])
    +assert_type(random_st.binomial(10, D_arr_0p5), npt.NDArray[np.long])
    +assert_type(random_st.binomial(I_arr_10, 0.5, size=1), npt.NDArray[np.long])
    +assert_type(random_st.binomial(10, D_arr_0p5, size=1), npt.NDArray[np.long])
    +assert_type(random_st.binomial(I_arr_like_10, 0.5), npt.NDArray[np.long])
    +assert_type(random_st.binomial(10, D_arr_like_0p5), npt.NDArray[np.long])
    +assert_type(random_st.binomial(I_arr_10, D_arr_0p5), npt.NDArray[np.long])
    +assert_type(random_st.binomial(I_arr_like_10, D_arr_like_0p5), npt.NDArray[np.long])
    +assert_type(random_st.binomial(I_arr_10, D_arr_0p5, size=1), npt.NDArray[np.long])
    +assert_type(random_st.binomial(I_arr_like_10, D_arr_like_0p5, size=1), npt.NDArray[np.long])
    +
    +assert_type(random_st.negative_binomial(10, 0.5), int)
    +assert_type(random_st.negative_binomial(10, 0.5, size=None), int)
    +assert_type(random_st.negative_binomial(10, 0.5, size=1), npt.NDArray[np.long])
    +assert_type(random_st.negative_binomial(I_arr_10, 0.5), npt.NDArray[np.long])
    +assert_type(random_st.negative_binomial(10, D_arr_0p5), npt.NDArray[np.long])
    +assert_type(random_st.negative_binomial(I_arr_10, 0.5, size=1), npt.NDArray[np.long])
    +assert_type(random_st.negative_binomial(10, D_arr_0p5, size=1), npt.NDArray[np.long])
    +assert_type(random_st.negative_binomial(I_arr_like_10, 0.5), npt.NDArray[np.long])
    +assert_type(random_st.negative_binomial(10, D_arr_like_0p5), npt.NDArray[np.long])
    +assert_type(random_st.negative_binomial(I_arr_10, D_arr_0p5), npt.NDArray[np.long])
    +assert_type(random_st.negative_binomial(I_arr_like_10, D_arr_like_0p5), npt.NDArray[np.long])
    +assert_type(random_st.negative_binomial(I_arr_10, D_arr_0p5, size=1), npt.NDArray[np.long])
    +assert_type(random_st.negative_binomial(I_arr_like_10, D_arr_like_0p5, size=1), npt.NDArray[np.long])
    +
    +assert_type(random_st.hypergeometric(20, 20, 10), int)
    +assert_type(random_st.hypergeometric(20, 20, 10, size=None), int)
    +assert_type(random_st.hypergeometric(20, 20, 10, size=1), npt.NDArray[np.long])
    +assert_type(random_st.hypergeometric(I_arr_20, 20, 10), npt.NDArray[np.long])
    +assert_type(random_st.hypergeometric(20, I_arr_20, 10), npt.NDArray[np.long])
    +assert_type(random_st.hypergeometric(I_arr_20, 20, I_arr_like_10, size=1), npt.NDArray[np.long])
    +assert_type(random_st.hypergeometric(20, I_arr_20, 10, size=1), npt.NDArray[np.long])
    +assert_type(random_st.hypergeometric(I_arr_like_20, 20, I_arr_10), npt.NDArray[np.long])
    +assert_type(random_st.hypergeometric(20, I_arr_like_20, 10), npt.NDArray[np.long])
    +assert_type(random_st.hypergeometric(I_arr_20, I_arr_20, 10), npt.NDArray[np.long])
    +assert_type(random_st.hypergeometric(I_arr_like_20, I_arr_like_20, 10), npt.NDArray[np.long])
    +assert_type(random_st.hypergeometric(I_arr_20, I_arr_20, I_arr_10, size=1), npt.NDArray[np.long])
    +assert_type(random_st.hypergeometric(I_arr_like_20, I_arr_like_20, I_arr_like_10, size=1), npt.NDArray[np.long])
    +
    +assert_type(random_st.randint(0, 100), int)
    +assert_type(random_st.randint(100), int)
    +assert_type(random_st.randint([100]), npt.NDArray[np.long])
    +assert_type(random_st.randint(0, [100]), npt.NDArray[np.long])
    +
    +assert_type(random_st.randint(2, dtype=bool), bool)
    +assert_type(random_st.randint(0, 2, dtype=bool), bool)
    +assert_type(random_st.randint(I_bool_high_open, dtype=bool), npt.NDArray[np.bool])
    +assert_type(random_st.randint(I_bool_low, I_bool_high_open, dtype=bool), npt.NDArray[np.bool])
    +assert_type(random_st.randint(0, I_bool_high_open, dtype=bool), npt.NDArray[np.bool])
    +
    +assert_type(random_st.randint(2, dtype=np.bool), np.bool)
    +assert_type(random_st.randint(0, 2, dtype=np.bool), np.bool)
    +assert_type(random_st.randint(I_bool_high_open, dtype=np.bool), npt.NDArray[np.bool])
    +assert_type(random_st.randint(I_bool_low, I_bool_high_open, dtype=np.bool), npt.NDArray[np.bool])
    +assert_type(random_st.randint(0, I_bool_high_open, dtype=np.bool), npt.NDArray[np.bool])
    +
    +assert_type(random_st.randint(256, dtype="u1"), np.uint8)
    +assert_type(random_st.randint(0, 256, dtype="u1"), np.uint8)
    +assert_type(random_st.randint(I_u1_high_open, dtype="u1"), npt.NDArray[np.uint8])
    +assert_type(random_st.randint(I_u1_low, I_u1_high_open, dtype="u1"), npt.NDArray[np.uint8])
    +assert_type(random_st.randint(0, I_u1_high_open, dtype="u1"), npt.NDArray[np.uint8])
    +
    +assert_type(random_st.randint(256, dtype="uint8"), np.uint8)
    +assert_type(random_st.randint(0, 256, dtype="uint8"), np.uint8)
    +assert_type(random_st.randint(I_u1_high_open, dtype="uint8"), npt.NDArray[np.uint8])
    +assert_type(random_st.randint(I_u1_low, I_u1_high_open, dtype="uint8"), npt.NDArray[np.uint8])
    +assert_type(random_st.randint(0, I_u1_high_open, dtype="uint8"), npt.NDArray[np.uint8])
    +
    +assert_type(random_st.randint(256, dtype=np.uint8), np.uint8)
    +assert_type(random_st.randint(0, 256, dtype=np.uint8), np.uint8)
    +assert_type(random_st.randint(I_u1_high_open, dtype=np.uint8), npt.NDArray[np.uint8])
    +assert_type(random_st.randint(I_u1_low, I_u1_high_open, dtype=np.uint8), npt.NDArray[np.uint8])
    +assert_type(random_st.randint(0, I_u1_high_open, dtype=np.uint8), npt.NDArray[np.uint8])
    +
    +assert_type(random_st.randint(65536, dtype="u2"), np.uint16)
    +assert_type(random_st.randint(0, 65536, dtype="u2"), np.uint16)
    +assert_type(random_st.randint(I_u2_high_open, dtype="u2"), npt.NDArray[np.uint16])
    +assert_type(random_st.randint(I_u2_low, I_u2_high_open, dtype="u2"), npt.NDArray[np.uint16])
    +assert_type(random_st.randint(0, I_u2_high_open, dtype="u2"), npt.NDArray[np.uint16])
    +
    +assert_type(random_st.randint(65536, dtype="uint16"), np.uint16)
    +assert_type(random_st.randint(0, 65536, dtype="uint16"), np.uint16)
    +assert_type(random_st.randint(I_u2_high_open, dtype="uint16"), npt.NDArray[np.uint16])
    +assert_type(random_st.randint(I_u2_low, I_u2_high_open, dtype="uint16"), npt.NDArray[np.uint16])
    +assert_type(random_st.randint(0, I_u2_high_open, dtype="uint16"), npt.NDArray[np.uint16])
    +
    +assert_type(random_st.randint(65536, dtype=np.uint16), np.uint16)
    +assert_type(random_st.randint(0, 65536, dtype=np.uint16), np.uint16)
    +assert_type(random_st.randint(I_u2_high_open, dtype=np.uint16), npt.NDArray[np.uint16])
    +assert_type(random_st.randint(I_u2_low, I_u2_high_open, dtype=np.uint16), npt.NDArray[np.uint16])
    +assert_type(random_st.randint(0, I_u2_high_open, dtype=np.uint16), npt.NDArray[np.uint16])
    +
    +assert_type(random_st.randint(4294967296, dtype="u4"), np.uint32)
    +assert_type(random_st.randint(0, 4294967296, dtype="u4"), np.uint32)
    +assert_type(random_st.randint(I_u4_high_open, dtype="u4"), npt.NDArray[np.uint32])
    +assert_type(random_st.randint(I_u4_low, I_u4_high_open, dtype="u4"), npt.NDArray[np.uint32])
    +assert_type(random_st.randint(0, I_u4_high_open, dtype="u4"), npt.NDArray[np.uint32])
    +
    +assert_type(random_st.randint(4294967296, dtype="uint32"), np.uint32)
    +assert_type(random_st.randint(0, 4294967296, dtype="uint32"), np.uint32)
    +assert_type(random_st.randint(I_u4_high_open, dtype="uint32"), npt.NDArray[np.uint32])
    +assert_type(random_st.randint(I_u4_low, I_u4_high_open, dtype="uint32"), npt.NDArray[np.uint32])
    +assert_type(random_st.randint(0, I_u4_high_open, dtype="uint32"), npt.NDArray[np.uint32])
    +
    +assert_type(random_st.randint(4294967296, dtype=np.uint32), np.uint32)
    +assert_type(random_st.randint(0, 4294967296, dtype=np.uint32), np.uint32)
    +assert_type(random_st.randint(I_u4_high_open, dtype=np.uint32), npt.NDArray[np.uint32])
    +assert_type(random_st.randint(I_u4_low, I_u4_high_open, dtype=np.uint32), npt.NDArray[np.uint32])
    +assert_type(random_st.randint(0, I_u4_high_open, dtype=np.uint32), npt.NDArray[np.uint32])
    +
    +assert_type(random_st.randint(4294967296, dtype=np.uint), np.uint)
    +assert_type(random_st.randint(0, 4294967296, dtype=np.uint), np.uint)
    +assert_type(random_st.randint(I_u4_high_open, dtype=np.uint), npt.NDArray[np.uint])
    +assert_type(random_st.randint(I_u4_low, I_u4_high_open, dtype=np.uint), npt.NDArray[np.uint])
    +assert_type(random_st.randint(0, I_u4_high_open, dtype=np.uint), npt.NDArray[np.uint])
    +
    +assert_type(random_st.randint(18446744073709551616, dtype="u8"), np.uint64)
    +assert_type(random_st.randint(0, 18446744073709551616, dtype="u8"), np.uint64)
    +assert_type(random_st.randint(I_u8_high_open, dtype="u8"), npt.NDArray[np.uint64])
    +assert_type(random_st.randint(I_u8_low, I_u8_high_open, dtype="u8"), npt.NDArray[np.uint64])
    +assert_type(random_st.randint(0, I_u8_high_open, dtype="u8"), npt.NDArray[np.uint64])
    +
    +assert_type(random_st.randint(18446744073709551616, dtype="uint64"), np.uint64)
    +assert_type(random_st.randint(0, 18446744073709551616, dtype="uint64"), np.uint64)
    +assert_type(random_st.randint(I_u8_high_open, dtype="uint64"), npt.NDArray[np.uint64])
    +assert_type(random_st.randint(I_u8_low, I_u8_high_open, dtype="uint64"), npt.NDArray[np.uint64])
    +assert_type(random_st.randint(0, I_u8_high_open, dtype="uint64"), npt.NDArray[np.uint64])
    +
    +assert_type(random_st.randint(18446744073709551616, dtype=np.uint64), np.uint64)
    +assert_type(random_st.randint(0, 18446744073709551616, dtype=np.uint64), np.uint64)
    +assert_type(random_st.randint(I_u8_high_open, dtype=np.uint64), npt.NDArray[np.uint64])
    +assert_type(random_st.randint(I_u8_low, I_u8_high_open, dtype=np.uint64), npt.NDArray[np.uint64])
    +assert_type(random_st.randint(0, I_u8_high_open, dtype=np.uint64), npt.NDArray[np.uint64])
    +
    +assert_type(random_st.randint(128, dtype="i1"), np.int8)
    +assert_type(random_st.randint(-128, 128, dtype="i1"), np.int8)
    +assert_type(random_st.randint(I_i1_high_open, dtype="i1"), npt.NDArray[np.int8])
    +assert_type(random_st.randint(I_i1_low, I_i1_high_open, dtype="i1"), npt.NDArray[np.int8])
    +assert_type(random_st.randint(-128, I_i1_high_open, dtype="i1"), npt.NDArray[np.int8])
    +
    +assert_type(random_st.randint(128, dtype="int8"), np.int8)
    +assert_type(random_st.randint(-128, 128, dtype="int8"), np.int8)
    +assert_type(random_st.randint(I_i1_high_open, dtype="int8"), npt.NDArray[np.int8])
    +assert_type(random_st.randint(I_i1_low, I_i1_high_open, dtype="int8"), npt.NDArray[np.int8])
    +assert_type(random_st.randint(-128, I_i1_high_open, dtype="int8"), npt.NDArray[np.int8])
    +
    +assert_type(random_st.randint(128, dtype=np.int8), np.int8)
    +assert_type(random_st.randint(-128, 128, dtype=np.int8), np.int8)
    +assert_type(random_st.randint(I_i1_high_open, dtype=np.int8), npt.NDArray[np.int8])
    +assert_type(random_st.randint(I_i1_low, I_i1_high_open, dtype=np.int8), npt.NDArray[np.int8])
    +assert_type(random_st.randint(-128, I_i1_high_open, dtype=np.int8), npt.NDArray[np.int8])
    +
    +assert_type(random_st.randint(32768, dtype="i2"), np.int16)
    +assert_type(random_st.randint(-32768, 32768, dtype="i2"), np.int16)
    +assert_type(random_st.randint(I_i2_high_open, dtype="i2"), npt.NDArray[np.int16])
    +assert_type(random_st.randint(I_i2_low, I_i2_high_open, dtype="i2"), npt.NDArray[np.int16])
    +assert_type(random_st.randint(-32768, I_i2_high_open, dtype="i2"), npt.NDArray[np.int16])
    +
    +assert_type(random_st.randint(32768, dtype="int16"), np.int16)
    +assert_type(random_st.randint(-32768, 32768, dtype="int16"), np.int16)
    +assert_type(random_st.randint(I_i2_high_open, dtype="int16"), npt.NDArray[np.int16])
    +assert_type(random_st.randint(I_i2_low, I_i2_high_open, dtype="int16"), npt.NDArray[np.int16])
    +assert_type(random_st.randint(-32768, I_i2_high_open, dtype="int16"), npt.NDArray[np.int16])
    +
    +assert_type(random_st.randint(32768, dtype=np.int16), np.int16)
    +assert_type(random_st.randint(-32768, 32768, dtype=np.int16), np.int16)
    +assert_type(random_st.randint(I_i2_high_open, dtype=np.int16), npt.NDArray[np.int16])
    +assert_type(random_st.randint(I_i2_low, I_i2_high_open, dtype=np.int16), npt.NDArray[np.int16])
    +assert_type(random_st.randint(-32768, I_i2_high_open, dtype=np.int16), npt.NDArray[np.int16])
    +
    +assert_type(random_st.randint(2147483648, dtype="i4"), np.int32)
    +assert_type(random_st.randint(-2147483648, 2147483648, dtype="i4"), np.int32)
    +assert_type(random_st.randint(I_i4_high_open, dtype="i4"), npt.NDArray[np.int32])
    +assert_type(random_st.randint(I_i4_low, I_i4_high_open, dtype="i4"), npt.NDArray[np.int32])
    +assert_type(random_st.randint(-2147483648, I_i4_high_open, dtype="i4"), npt.NDArray[np.int32])
    +
    +assert_type(random_st.randint(2147483648, dtype="int32"), np.int32)
    +assert_type(random_st.randint(-2147483648, 2147483648, dtype="int32"), np.int32)
    +assert_type(random_st.randint(I_i4_high_open, dtype="int32"), npt.NDArray[np.int32])
    +assert_type(random_st.randint(I_i4_low, I_i4_high_open, dtype="int32"), npt.NDArray[np.int32])
    +assert_type(random_st.randint(-2147483648, I_i4_high_open, dtype="int32"), npt.NDArray[np.int32])
    +
    +assert_type(random_st.randint(2147483648, dtype=np.int32), np.int32)
    +assert_type(random_st.randint(-2147483648, 2147483648, dtype=np.int32), np.int32)
    +assert_type(random_st.randint(I_i4_high_open, dtype=np.int32), npt.NDArray[np.int32])
    +assert_type(random_st.randint(I_i4_low, I_i4_high_open, dtype=np.int32), npt.NDArray[np.int32])
    +assert_type(random_st.randint(-2147483648, I_i4_high_open, dtype=np.int32), npt.NDArray[np.int32])
    +
    +assert_type(random_st.randint(2147483648, dtype=np.int_), np.int_)
    +assert_type(random_st.randint(-2147483648, 2147483648, dtype=np.int_), np.int_)
    +assert_type(random_st.randint(I_i4_high_open, dtype=np.int_), npt.NDArray[np.int_])
    +assert_type(random_st.randint(I_i4_low, I_i4_high_open, dtype=np.int_), npt.NDArray[np.int_])
    +assert_type(random_st.randint(-2147483648, I_i4_high_open, dtype=np.int_), npt.NDArray[np.int_])
    +
    +assert_type(random_st.randint(9223372036854775808, dtype="i8"), np.int64)
    +assert_type(random_st.randint(-9223372036854775808, 9223372036854775808, dtype="i8"), np.int64)
    +assert_type(random_st.randint(I_i8_high_open, dtype="i8"), npt.NDArray[np.int64])
    +assert_type(random_st.randint(I_i8_low, I_i8_high_open, dtype="i8"), npt.NDArray[np.int64])
    +assert_type(random_st.randint(-9223372036854775808, I_i8_high_open, dtype="i8"), npt.NDArray[np.int64])
    +
    +assert_type(random_st.randint(9223372036854775808, dtype="int64"), np.int64)
    +assert_type(random_st.randint(-9223372036854775808, 9223372036854775808, dtype="int64"), np.int64)
    +assert_type(random_st.randint(I_i8_high_open, dtype="int64"), npt.NDArray[np.int64])
    +assert_type(random_st.randint(I_i8_low, I_i8_high_open, dtype="int64"), npt.NDArray[np.int64])
    +assert_type(random_st.randint(-9223372036854775808, I_i8_high_open, dtype="int64"), npt.NDArray[np.int64])
    +
    +assert_type(random_st.randint(9223372036854775808, dtype=np.int64), np.int64)
    +assert_type(random_st.randint(-9223372036854775808, 9223372036854775808, dtype=np.int64), np.int64)
    +assert_type(random_st.randint(I_i8_high_open, dtype=np.int64), npt.NDArray[np.int64])
    +assert_type(random_st.randint(I_i8_low, I_i8_high_open, dtype=np.int64), npt.NDArray[np.int64])
    +assert_type(random_st.randint(-9223372036854775808, I_i8_high_open, dtype=np.int64), npt.NDArray[np.int64])
    +
    +assert_type(random_st._bit_generator, np.random.BitGenerator)
    +
    +assert_type(random_st.bytes(2), bytes)
    +
    +assert_type(random_st.choice(5), int)
    +assert_type(random_st.choice(5, 3), npt.NDArray[np.long])
    +assert_type(random_st.choice(5, 3, replace=True), npt.NDArray[np.long])
    +assert_type(random_st.choice(5, 3, p=[1 / 5] * 5), npt.NDArray[np.long])
    +assert_type(random_st.choice(5, 3, p=[1 / 5] * 5, replace=False), npt.NDArray[np.long])
    +
    +assert_type(random_st.choice(["pooh", "rabbit", "piglet", "Christopher"]), Any)
    +assert_type(random_st.choice(["pooh", "rabbit", "piglet", "Christopher"], 3), npt.NDArray[Any])
    +assert_type(random_st.choice(["pooh", "rabbit", "piglet", "Christopher"], 3, p=[1 / 4] * 4), npt.NDArray[Any])
    +assert_type(random_st.choice(["pooh", "rabbit", "piglet", "Christopher"], 3, replace=True), npt.NDArray[Any])
    +assert_type(random_st.choice(["pooh", "rabbit", "piglet", "Christopher"], 3, replace=False, p=np.array([1 / 8, 1 / 8, 1 / 2, 1 / 4])), npt.NDArray[Any])
    +
    +assert_type(random_st.dirichlet([0.5, 0.5]), npt.NDArray[np.float64])
    +assert_type(random_st.dirichlet(np.array([0.5, 0.5])), npt.NDArray[np.float64])
    +assert_type(random_st.dirichlet(np.array([0.5, 0.5]), size=3), npt.NDArray[np.float64])
    +
    +assert_type(random_st.multinomial(20, [1 / 6.0] * 6), npt.NDArray[np.long])
    +assert_type(random_st.multinomial(20, np.array([0.5, 0.5])), npt.NDArray[np.long])
    +assert_type(random_st.multinomial(20, [1 / 6.0] * 6, size=2), npt.NDArray[np.long])
    +
    +assert_type(random_st.multivariate_normal([0.0], [[1.0]]), npt.NDArray[np.float64])
    +assert_type(random_st.multivariate_normal([0.0], np.array([[1.0]])), npt.NDArray[np.float64])
    +assert_type(random_st.multivariate_normal(np.array([0.0]), [[1.0]]), npt.NDArray[np.float64])
    +assert_type(random_st.multivariate_normal([0.0], np.array([[1.0]])), npt.NDArray[np.float64])
    +
    +assert_type(random_st.permutation(10), npt.NDArray[np.long])
    +assert_type(random_st.permutation([1, 2, 3, 4]), npt.NDArray[Any])
    +assert_type(random_st.permutation(np.array([1, 2, 3, 4])), npt.NDArray[Any])
    +assert_type(random_st.permutation(D_2D), npt.NDArray[Any])
    +
    +assert_type(random_st.shuffle(np.arange(10)), None)
    +assert_type(random_st.shuffle([1, 2, 3, 4, 5]), None)
    +assert_type(random_st.shuffle(D_2D), None)
    +
    +assert_type(np.random.RandomState(pcg64), np.random.RandomState)
    +assert_type(np.random.RandomState(0), np.random.RandomState)
    +assert_type(np.random.RandomState([0, 1, 2]), np.random.RandomState)
    +assert_type(random_st.__str__(), str)
    +assert_type(random_st.__repr__(), str)
    +random_st_state = random_st.__getstate__()
    +assert_type(random_st_state, dict[str, Any])
    +assert_type(random_st.__setstate__(random_st_state), None)
    +assert_type(random_st.seed(), None)
    +assert_type(random_st.seed(1), None)
    +assert_type(random_st.seed([0, 1]), None)
    +random_st_get_state = random_st.get_state()
    +assert_type(random_st_state, dict[str, Any])
    +random_st_get_state_legacy = random_st.get_state(legacy=True)
    +assert_type(random_st_get_state_legacy, dict[str, Any] | tuple[str, npt.NDArray[np.uint32], int, int, float])
    +assert_type(random_st.set_state(random_st_get_state), None)
    +
    +assert_type(random_st.rand(), float)
    +assert_type(random_st.rand(1), npt.NDArray[np.float64])
    +assert_type(random_st.rand(1, 2), npt.NDArray[np.float64])
    +assert_type(random_st.randn(), float)
    +assert_type(random_st.randn(1), npt.NDArray[np.float64])
    +assert_type(random_st.randn(1, 2), npt.NDArray[np.float64])
    +assert_type(random_st.random_sample(), float)
    +assert_type(random_st.random_sample(1), npt.NDArray[np.float64])
    +assert_type(random_st.random_sample(size=(1, 2)), npt.NDArray[np.float64])
    +
    +assert_type(random_st.tomaxint(), int)
    +assert_type(random_st.tomaxint(1), npt.NDArray[np.int64])
    +assert_type(random_st.tomaxint((1,)), npt.NDArray[np.int64])
    +
    +assert_type(np.random.mtrand.set_bit_generator(pcg64), None)
    +assert_type(np.random.mtrand.get_bit_generator(), np.random.BitGenerator)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/rec.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/rec.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..aacf217e42072cacc9a245d7d7faef8cb340f2c2
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/rec.pyi
    @@ -0,0 +1,171 @@
    +import io
    +from typing import Any, TypeAlias, assert_type
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +_RecArray: TypeAlias = np.recarray[tuple[Any, ...], np.dtype[np.record]]
    +
    +AR_i8: npt.NDArray[np.int64]
    +REC_AR_V: _RecArray
    +AR_LIST: list[npt.NDArray[np.int64]]
    +
    +record: np.record
    +file_obj: io.BufferedIOBase
    +
    +assert_type(np.rec.format_parser(
    +    formats=[np.float64, np.int64, np.bool],
    +    names=["f8", "i8", "?"],
    +    titles=None,
    +    aligned=True,
    +), np.rec.format_parser)
    +assert_type(np.rec.format_parser.dtype, np.dtype[np.void])
    +
    +assert_type(record.field_a, Any)
    +assert_type(record.field_b, Any)
    +assert_type(record["field_a"], Any)
    +assert_type(record["field_b"], Any)
    +assert_type(record.pprint(), str)
    +record.field_c = 5
    +
    +assert_type(REC_AR_V.field(0), Any)
    +assert_type(REC_AR_V.field("field_a"), Any)
    +assert_type(REC_AR_V.field(0, AR_i8), None)
    +assert_type(REC_AR_V.field("field_a", AR_i8), None)
    +assert_type(REC_AR_V["field_a"], npt.NDArray[Any])
    +assert_type(REC_AR_V.field_a, Any)
    +assert_type(REC_AR_V.__array_finalize__(object()), None)
    +
    +assert_type(
    +    np.recarray(
    +        shape=(10, 5),
    +        formats=[np.float64, np.int64, np.bool],
    +        order="K",
    +        byteorder="|",
    +    ),
    +    _RecArray,
    +)
    +
    +assert_type(
    +    np.recarray(
    +        shape=(10, 5),
    +        dtype=[("f8", np.float64), ("i8", np.int64)],
    +        strides=(5, 5),
    +    ),
    +    np.recarray,
    +)
    +
    +assert_type(np.rec.fromarrays(AR_LIST), np.recarray)
    +assert_type(
    +    np.rec.fromarrays(AR_LIST, dtype=np.int64),
    +    np.recarray,
    +)
    +assert_type(
    +    np.rec.fromarrays(
    +        AR_LIST,
    +        formats=[np.int64, np.float64],
    +        names=["i8", "f8"]
    +    ),
    +    _RecArray,
    +)
    +
    +assert_type(
    +    np.rec.fromrecords((1, 1.5)),
    +    _RecArray
    +)
    +
    +assert_type(
    +    np.rec.fromrecords(
    +        [(1, 1.5)],
    +        dtype=[("i8", np.int64), ("f8", np.float64)],
    +    ),
    +    _RecArray,
    +)
    +
    +assert_type(
    +    np.rec.fromrecords(
    +        REC_AR_V,
    +        formats=[np.int64, np.float64],
    +        names=["i8", "f8"]
    +    ),
    +    _RecArray,
    +)
    +
    +assert_type(
    +    np.rec.fromstring(
    +        b"(1, 1.5)",
    +        dtype=[("i8", np.int64), ("f8", np.float64)],
    +    ),
    +    _RecArray,
    +)
    +
    +assert_type(
    +    np.rec.fromstring(
    +        REC_AR_V,
    +        formats=[np.int64, np.float64],
    +        names=["i8", "f8"]
    +    ),
    +    _RecArray,
    +)
    +
    +assert_type(
    +    np.rec.fromfile(
    +        "test_file.txt",
    +        dtype=[("i8", np.int64), ("f8", np.float64)],
    +    ),
    +    np.recarray,
    +)
    +
    +assert_type(
    +    np.rec.fromfile(
    +        file_obj,
    +        formats=[np.int64, np.float64],
    +        names=["i8", "f8"]
    +    ),
    +    _RecArray,
    +)
    +
    +assert_type(np.rec.array(AR_i8), np.recarray[tuple[Any, ...], np.dtype[np.int64]])
    +
    +assert_type(
    +    np.rec.array([(1, 1.5)], dtype=[("i8", np.int64), ("f8", np.float64)]),
    +    np.recarray,
    +)
    +
    +assert_type(
    +    np.rec.array(
    +        [(1, 1.5)],
    +        formats=[np.int64, np.float64],
    +        names=["i8", "f8"]
    +    ),
    +    _RecArray,
    +)
    +
    +assert_type(
    +    np.rec.array(
    +        None,
    +        dtype=np.float64,
    +        shape=(10, 3),
    +    ),
    +    np.recarray,
    +)
    +
    +assert_type(
    +    np.rec.array(
    +        None,
    +        formats=[np.int64, np.float64],
    +        names=["i8", "f8"],
    +        shape=(10, 3),
    +    ),
    +    _RecArray,
    +)
    +
    +assert_type(
    +    np.rec.array(file_obj, dtype=np.float64),
    +    np.recarray,
    +)
    +
    +assert_type(
    +    np.rec.array(file_obj, formats=[np.int64, np.float64], names=["i8", "f8"]),
    +    _RecArray,
    +)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/scalars.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/scalars.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..67444e33dfc389e164092afee1423ac29a16ed44
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/scalars.pyi
    @@ -0,0 +1,191 @@
    +from typing import Any, Literal, TypeAlias, assert_type
    +
    +import numpy as np
    +
    +_1: TypeAlias = Literal[1]
    +
    +b: np.bool
    +u8: np.uint64
    +i8: np.int64
    +f8: np.float64
    +c8: np.complex64
    +c16: np.complex128
    +m: np.timedelta64
    +U: np.str_
    +S: np.bytes_
    +V: np.void
    +O: np.object_  # cannot exists at runtime
    +
    +array_nd: np.ndarray[Any, Any]
    +array_0d: np.ndarray[tuple[()], Any]
    +array_2d_2x2: np.ndarray[tuple[Literal[2], Literal[2]], Any]
    +
    +assert_type(c8.real, np.float32)
    +assert_type(c8.imag, np.float32)
    +
    +assert_type(c8.real.real, np.float32)
    +assert_type(c8.real.imag, np.float32)
    +
    +assert_type(c8.itemsize, int)
    +assert_type(c8.shape, tuple[()])
    +assert_type(c8.strides, tuple[()])
    +
    +assert_type(c8.ndim, Literal[0])
    +assert_type(c8.size, Literal[1])
    +
    +assert_type(c8.squeeze(), np.complex64)
    +assert_type(c8.byteswap(), np.complex64)
    +assert_type(c8.transpose(), np.complex64)
    +
    +assert_type(c8.dtype, np.dtype[np.complex64])
    +
    +assert_type(c8.real, np.float32)
    +assert_type(c16.imag, np.float64)
    +
    +assert_type(np.str_("foo"), np.str_)
    +
    +assert_type(V[0], Any)
    +assert_type(V["field1"], Any)
    +assert_type(V[["field1", "field2"]], np.void)
    +V[0] = 5
    +
    +# Aliases
    +assert_type(np.bool_(), np.bool[Literal[False]])
    +assert_type(np.byte(), np.byte)
    +assert_type(np.short(), np.short)
    +assert_type(np.intc(), np.intc)
    +assert_type(np.intp(), np.intp)
    +assert_type(np.int_(), np.int_)
    +assert_type(np.long(), np.long)
    +assert_type(np.longlong(), np.longlong)
    +
    +assert_type(np.ubyte(), np.ubyte)
    +assert_type(np.ushort(), np.ushort)
    +assert_type(np.uintc(), np.uintc)
    +assert_type(np.uintp(), np.uintp)
    +assert_type(np.uint(), np.uint)
    +assert_type(np.ulong(), np.ulong)
    +assert_type(np.ulonglong(), np.ulonglong)
    +
    +assert_type(np.half(), np.half)
    +assert_type(np.single(), np.single)
    +assert_type(np.double(), np.double)
    +assert_type(np.longdouble(), np.longdouble)
    +
    +assert_type(np.csingle(), np.csingle)
    +assert_type(np.cdouble(), np.cdouble)
    +assert_type(np.clongdouble(), np.clongdouble)
    +
    +assert_type(b.item(), bool)
    +assert_type(i8.item(), int)
    +assert_type(u8.item(), int)
    +assert_type(f8.item(), float)
    +assert_type(c16.item(), complex)
    +assert_type(U.item(), str)
    +assert_type(S.item(), bytes)
    +
    +assert_type(b.tolist(), bool)
    +assert_type(i8.tolist(), int)
    +assert_type(u8.tolist(), int)
    +assert_type(f8.tolist(), float)
    +assert_type(c16.tolist(), complex)
    +assert_type(U.tolist(), str)
    +assert_type(S.tolist(), bytes)
    +
    +assert_type(b.ravel(), np.ndarray[tuple[int], np.dtype[np.bool]])
    +assert_type(i8.ravel(), np.ndarray[tuple[int], np.dtype[np.int64]])
    +assert_type(u8.ravel(), np.ndarray[tuple[int], np.dtype[np.uint64]])
    +assert_type(f8.ravel(), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(c16.ravel(), np.ndarray[tuple[int], np.dtype[np.complex128]])
    +assert_type(U.ravel(), np.ndarray[tuple[int], np.dtype[np.str_]])
    +assert_type(S.ravel(), np.ndarray[tuple[int], np.dtype[np.bytes_]])
    +
    +assert_type(b.flatten(), np.ndarray[tuple[int], np.dtype[np.bool]])
    +assert_type(i8.flatten(), np.ndarray[tuple[int], np.dtype[np.int64]])
    +assert_type(u8.flatten(), np.ndarray[tuple[int], np.dtype[np.uint64]])
    +assert_type(f8.flatten(), np.ndarray[tuple[int], np.dtype[np.float64]])
    +assert_type(c16.flatten(), np.ndarray[tuple[int], np.dtype[np.complex128]])
    +assert_type(U.flatten(), np.ndarray[tuple[int], np.dtype[np.str_]])
    +assert_type(S.flatten(), np.ndarray[tuple[int], np.dtype[np.bytes_]])
    +
    +assert_type(b.reshape(()), np.bool)
    +assert_type(i8.reshape([]), np.int64)
    +assert_type(b.reshape(1), np.ndarray[tuple[_1], np.dtype[np.bool]])
    +assert_type(i8.reshape(-1), np.ndarray[tuple[_1], np.dtype[np.int64]])
    +assert_type(u8.reshape(1, 1), np.ndarray[tuple[_1, _1], np.dtype[np.uint64]])
    +assert_type(f8.reshape(1, -1), np.ndarray[tuple[_1, _1], np.dtype[np.float64]])
    +assert_type(c16.reshape(1, 1, 1), np.ndarray[tuple[_1, _1, _1], np.dtype[np.complex128]])
    +assert_type(U.reshape(1, 1, 1, 1), np.ndarray[tuple[_1, _1, _1, _1], np.dtype[np.str_]])
    +assert_type(
    +    S.reshape(1, 1, 1, 1, 1),
    +    np.ndarray[
    +        # len(shape) >= 5
    +        tuple[_1, _1, _1, _1, _1, *tuple[_1, ...]],
    +        np.dtype[np.bytes_],
    +    ],
    +)
    +
    +assert_type(i8.astype(float), Any)
    +assert_type(i8.astype(np.float64), np.float64)
    +
    +assert_type(i8.view(), np.int64)
    +assert_type(i8.view(np.float64), np.float64)
    +assert_type(i8.view(float), Any)
    +assert_type(i8.view(np.float64, np.ndarray), np.float64)
    +
    +assert_type(i8.getfield(float), Any)
    +assert_type(i8.getfield(np.float64), np.float64)
    +assert_type(i8.getfield(np.float64, 8), np.float64)
    +
    +assert_type(f8.as_integer_ratio(), tuple[int, int])
    +assert_type(f8.is_integer(), bool)
    +assert_type(f8.__trunc__(), int)
    +assert_type(f8.__getformat__("float"), str)
    +assert_type(f8.hex(), str)
    +assert_type(np.float64.fromhex("0x0.0p+0"), np.float64)
    +
    +assert_type(f8.__getnewargs__(), tuple[float])
    +assert_type(c16.__getnewargs__(), tuple[float, float])
    +
    +assert_type(i8.numerator, np.int64)
    +assert_type(i8.denominator, Literal[1])
    +assert_type(u8.numerator, np.uint64)
    +assert_type(u8.denominator, Literal[1])
    +assert_type(m.numerator, np.timedelta64)
    +assert_type(m.denominator, Literal[1])
    +
    +assert_type(round(i8), int)
    +assert_type(round(i8, 3), np.int64)
    +assert_type(round(u8), int)
    +assert_type(round(u8, 3), np.uint64)
    +assert_type(round(f8), int)
    +assert_type(round(f8, 3), np.float64)
    +
    +assert_type(f8.__ceil__(), int)
    +assert_type(f8.__floor__(), int)
    +
    +assert_type(i8.is_integer(), Literal[True])
    +
    +assert_type(O.real, np.object_)
    +assert_type(O.imag, np.object_)
    +assert_type(int(O), int)
    +assert_type(float(O), float)
    +assert_type(complex(O), complex)
    +
    +# These fail fail because of a mypy __new__ bug:
    +# https://github.com/python/mypy/issues/15182
    +# According to the typing spec, the following statements are valid, see
    +# https://typing.readthedocs.io/en/latest/spec/constructors.html#new-method
    +
    +# assert_type(np.object_(), None)
    +# assert_type(np.object_(None), None)
    +# assert_type(np.object_(array_nd), np.ndarray[Any, np.dtype[np.object_]])
    +# assert_type(np.object_([]), npt.NDArray[np.object_])
    +# assert_type(np.object_(()), npt.NDArray[np.object_])
    +# assert_type(np.object_(range(4)), npt.NDArray[np.object_])
    +# assert_type(np.object_(+42), int)
    +# assert_type(np.object_(1 / 137), float)
    +# assert_type(np.object_('Developers! ' * (1 << 6)), str)
    +# assert_type(np.object_(object()), object)
    +# assert_type(np.object_({False, True, NotADirectoryError}), set[Any])
    +# assert_type(np.object_({'spam': 'food', 'ham': 'food'}), dict[str, str])
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/shape.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/shape.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..2406a39f9682b66623bd9879afdfcfe311c5f869
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/shape.pyi
    @@ -0,0 +1,13 @@
    +from typing import Any, NamedTuple, assert_type
    +
    +import numpy as np
    +
    +# Subtype of tuple[int, int]
    +class XYGrid(NamedTuple):
    +    x_axis: int
    +    y_axis: int
    +
    +arr: np.ndarray[XYGrid, Any]
    +
    +# Test shape property matches shape typevar
    +assert_type(arr.shape, XYGrid)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/shape_base.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/shape_base.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..e409a53bcef95ad549769011cf0a7d28ea78320e
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/shape_base.pyi
    @@ -0,0 +1,52 @@
    +from typing import Any, assert_type
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +i8: np.int64
    +f8: np.float64
    +
    +AR_b: npt.NDArray[np.bool]
    +AR_i8: npt.NDArray[np.int64]
    +AR_f8: npt.NDArray[np.float64]
    +
    +AR_LIKE_f8: list[float]
    +
    +assert_type(np.take_along_axis(AR_f8, AR_i8, axis=1), npt.NDArray[np.float64])
    +assert_type(np.take_along_axis(f8, AR_i8, axis=None), npt.NDArray[np.float64])
    +
    +assert_type(np.put_along_axis(AR_f8, AR_i8, "1.0", axis=1), None)
    +
    +assert_type(np.expand_dims(AR_i8, 2), npt.NDArray[np.int64])
    +assert_type(np.expand_dims(AR_LIKE_f8, 2), npt.NDArray[Any])
    +
    +assert_type(np.column_stack([AR_i8]), npt.NDArray[np.int64])
    +assert_type(np.column_stack([AR_LIKE_f8]), npt.NDArray[Any])
    +
    +assert_type(np.dstack([AR_i8]), npt.NDArray[np.int64])
    +assert_type(np.dstack([AR_LIKE_f8]), npt.NDArray[Any])
    +
    +assert_type(np.array_split(AR_i8, [3, 5, 6, 10]), list[npt.NDArray[np.int64]])
    +assert_type(np.array_split(AR_LIKE_f8, [3, 5, 6, 10]), list[npt.NDArray[Any]])
    +
    +assert_type(np.split(AR_i8, [3, 5, 6, 10]), list[npt.NDArray[np.int64]])
    +assert_type(np.split(AR_LIKE_f8, [3, 5, 6, 10]), list[npt.NDArray[Any]])
    +
    +assert_type(np.hsplit(AR_i8, [3, 5, 6, 10]), list[npt.NDArray[np.int64]])
    +assert_type(np.hsplit(AR_LIKE_f8, [3, 5, 6, 10]), list[npt.NDArray[Any]])
    +
    +assert_type(np.vsplit(AR_i8, [3, 5, 6, 10]), list[npt.NDArray[np.int64]])
    +assert_type(np.vsplit(AR_LIKE_f8, [3, 5, 6, 10]), list[npt.NDArray[Any]])
    +
    +assert_type(np.dsplit(AR_i8, [3, 5, 6, 10]), list[npt.NDArray[np.int64]])
    +assert_type(np.dsplit(AR_LIKE_f8, [3, 5, 6, 10]), list[npt.NDArray[Any]])
    +
    +assert_type(np.kron(AR_b, AR_b), npt.NDArray[np.bool])
    +assert_type(np.kron(AR_b, AR_i8), npt.NDArray[np.signedinteger])
    +assert_type(np.kron(AR_f8, AR_f8), npt.NDArray[np.floating])
    +
    +assert_type(np.tile(AR_i8, 5), npt.NDArray[np.int64])
    +assert_type(np.tile(AR_LIKE_f8, [2, 2]), npt.NDArray[Any])
    +
    +assert_type(np.unstack(AR_i8, axis=0), tuple[npt.NDArray[np.int64], ...])
    +assert_type(np.unstack(AR_LIKE_f8, axis=0), tuple[npt.NDArray[Any], ...])
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/stride_tricks.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/stride_tricks.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..8fde9b8ae30dd6dd25b1d4f6f68c1321e6f3c0a0
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/stride_tricks.pyi
    @@ -0,0 +1,27 @@
    +from typing import Any, assert_type
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +AR_f8: npt.NDArray[np.float64]
    +AR_LIKE_f: list[float]
    +interface_dict: dict[str, Any]
    +
    +assert_type(np.lib.stride_tricks.as_strided(AR_f8), npt.NDArray[np.float64])
    +assert_type(np.lib.stride_tricks.as_strided(AR_LIKE_f), npt.NDArray[Any])
    +assert_type(np.lib.stride_tricks.as_strided(AR_f8, strides=(1, 5)), npt.NDArray[np.float64])
    +assert_type(np.lib.stride_tricks.as_strided(AR_f8, shape=[9, 20]), npt.NDArray[np.float64])
    +
    +assert_type(np.lib.stride_tricks.sliding_window_view(AR_f8, 5), npt.NDArray[np.float64])
    +assert_type(np.lib.stride_tricks.sliding_window_view(AR_LIKE_f, (1, 5)), npt.NDArray[Any])
    +assert_type(np.lib.stride_tricks.sliding_window_view(AR_f8, [9], axis=1), npt.NDArray[np.float64])
    +
    +assert_type(np.broadcast_to(AR_f8, 5), npt.NDArray[np.float64])
    +assert_type(np.broadcast_to(AR_LIKE_f, (1, 5)), npt.NDArray[Any])
    +assert_type(np.broadcast_to(AR_f8, [4, 6], subok=True), npt.NDArray[np.float64])
    +
    +assert_type(np.broadcast_shapes((1, 2), [3, 1], (3, 2)), tuple[Any, ...])
    +assert_type(np.broadcast_shapes((6, 7), (5, 6, 1), 7, (5, 1, 7)), tuple[Any, ...])
    +
    +assert_type(np.broadcast_arrays(AR_f8, AR_f8), tuple[npt.NDArray[Any], ...])
    +assert_type(np.broadcast_arrays(AR_f8, AR_LIKE_f), tuple[npt.NDArray[Any], ...])
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/strings.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/strings.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..18bd252d5ff90f34878ba34fd150bf17093cdc61
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/strings.pyi
    @@ -0,0 +1,196 @@
    +from typing import TypeAlias, assert_type
    +
    +import numpy as np
    +import numpy._typing as np_t
    +import numpy.typing as npt
    +
    +AR_T_alias: TypeAlias = np.ndarray[np_t._AnyShape, np.dtypes.StringDType]
    +AR_TU_alias: TypeAlias = AR_T_alias | npt.NDArray[np.str_]
    +
    +AR_U: npt.NDArray[np.str_]
    +AR_S: npt.NDArray[np.bytes_]
    +AR_T: AR_T_alias
    +
    +assert_type(np.strings.equal(AR_U, AR_U), npt.NDArray[np.bool])
    +assert_type(np.strings.equal(AR_S, AR_S), npt.NDArray[np.bool])
    +assert_type(np.strings.equal(AR_T, AR_T), npt.NDArray[np.bool])
    +
    +assert_type(np.strings.not_equal(AR_U, AR_U), npt.NDArray[np.bool])
    +assert_type(np.strings.not_equal(AR_S, AR_S), npt.NDArray[np.bool])
    +assert_type(np.strings.not_equal(AR_T, AR_T), npt.NDArray[np.bool])
    +
    +assert_type(np.strings.greater_equal(AR_U, AR_U), npt.NDArray[np.bool])
    +assert_type(np.strings.greater_equal(AR_S, AR_S), npt.NDArray[np.bool])
    +assert_type(np.strings.greater_equal(AR_T, AR_T), npt.NDArray[np.bool])
    +
    +assert_type(np.strings.less_equal(AR_U, AR_U), npt.NDArray[np.bool])
    +assert_type(np.strings.less_equal(AR_S, AR_S), npt.NDArray[np.bool])
    +assert_type(np.strings.less_equal(AR_T, AR_T), npt.NDArray[np.bool])
    +
    +assert_type(np.strings.greater(AR_U, AR_U), npt.NDArray[np.bool])
    +assert_type(np.strings.greater(AR_S, AR_S), npt.NDArray[np.bool])
    +assert_type(np.strings.greater(AR_T, AR_T), npt.NDArray[np.bool])
    +
    +assert_type(np.strings.less(AR_U, AR_U), npt.NDArray[np.bool])
    +assert_type(np.strings.less(AR_S, AR_S), npt.NDArray[np.bool])
    +assert_type(np.strings.less(AR_T, AR_T), npt.NDArray[np.bool])
    +
    +assert_type(np.strings.add(AR_U, AR_U), npt.NDArray[np.str_])
    +assert_type(np.strings.add(AR_S, AR_S), npt.NDArray[np.bytes_])
    +assert_type(np.strings.add(AR_T, AR_T), AR_T_alias)
    +
    +assert_type(np.strings.multiply(AR_U, 5), npt.NDArray[np.str_])
    +assert_type(np.strings.multiply(AR_S, [5, 4, 3]), npt.NDArray[np.bytes_])
    +assert_type(np.strings.multiply(AR_T, 5), AR_T_alias)
    +
    +assert_type(np.strings.mod(AR_U, "test"), npt.NDArray[np.str_])
    +assert_type(np.strings.mod(AR_S, "test"), npt.NDArray[np.bytes_])
    +assert_type(np.strings.mod(AR_T, "test"), AR_T_alias)
    +
    +assert_type(np.strings.capitalize(AR_U), npt.NDArray[np.str_])
    +assert_type(np.strings.capitalize(AR_S), npt.NDArray[np.bytes_])
    +assert_type(np.strings.capitalize(AR_T), AR_T_alias)
    +
    +assert_type(np.strings.center(AR_U, 5), npt.NDArray[np.str_])
    +assert_type(np.strings.center(AR_S, [2, 3, 4], b"a"), npt.NDArray[np.bytes_])
    +assert_type(np.strings.center(AR_T, 5), AR_T_alias)
    +
    +assert_type(np.strings.encode(AR_U), npt.NDArray[np.bytes_])
    +assert_type(np.strings.encode(AR_T), npt.NDArray[np.bytes_])
    +assert_type(np.strings.decode(AR_S), npt.NDArray[np.str_])
    +
    +assert_type(np.strings.expandtabs(AR_U), npt.NDArray[np.str_])
    +assert_type(np.strings.expandtabs(AR_S, tabsize=4), npt.NDArray[np.bytes_])
    +assert_type(np.strings.expandtabs(AR_T), AR_T_alias)
    +
    +assert_type(np.strings.ljust(AR_U, 5), npt.NDArray[np.str_])
    +assert_type(np.strings.ljust(AR_S, [4, 3, 1], fillchar=[b"a", b"b", b"c"]), npt.NDArray[np.bytes_])
    +assert_type(np.strings.ljust(AR_T, 5), AR_T_alias)
    +assert_type(np.strings.ljust(AR_T, [4, 2, 1], fillchar=["a", "b", "c"]), AR_T_alias)
    +
    +assert_type(np.strings.rjust(AR_U, 5), npt.NDArray[np.str_])
    +assert_type(np.strings.rjust(AR_S, [4, 3, 1], fillchar=[b"a", b"b", b"c"]), npt.NDArray[np.bytes_])
    +assert_type(np.strings.rjust(AR_T, 5), AR_T_alias)
    +assert_type(np.strings.rjust(AR_T, [4, 2, 1], fillchar=["a", "b", "c"]), AR_T_alias)
    +
    +assert_type(np.strings.lstrip(AR_U), npt.NDArray[np.str_])
    +assert_type(np.strings.lstrip(AR_S, b"_"), npt.NDArray[np.bytes_])
    +assert_type(np.strings.lstrip(AR_T), AR_T_alias)
    +assert_type(np.strings.lstrip(AR_T, "_"), AR_T_alias)
    +
    +assert_type(np.strings.rstrip(AR_U), npt.NDArray[np.str_])
    +assert_type(np.strings.rstrip(AR_S, b"_"), npt.NDArray[np.bytes_])
    +assert_type(np.strings.rstrip(AR_T), AR_T_alias)
    +assert_type(np.strings.rstrip(AR_T, "_"), AR_T_alias)
    +
    +assert_type(np.strings.strip(AR_U), npt.NDArray[np.str_])
    +assert_type(np.strings.strip(AR_S, b"_"), npt.NDArray[np.bytes_])
    +assert_type(np.strings.strip(AR_T), AR_T_alias)
    +assert_type(np.strings.strip(AR_T, "_"), AR_T_alias)
    +
    +assert_type(np.strings.count(AR_U, "a", start=[1, 2, 3]), npt.NDArray[np.int_])
    +assert_type(np.strings.count(AR_S, [b"a", b"b", b"c"], end=9), npt.NDArray[np.int_])
    +assert_type(np.strings.count(AR_T, "a", start=[1, 2, 3]), npt.NDArray[np.int_])
    +assert_type(np.strings.count(AR_T, ["a", "b", "c"], end=9), npt.NDArray[np.int_])
    +
    +assert_type(np.strings.partition(AR_U, "\n"), npt.NDArray[np.str_])
    +assert_type(np.strings.partition(AR_S, [b"a", b"b", b"c"]), npt.NDArray[np.bytes_])
    +assert_type(np.strings.partition(AR_T, "\n"), AR_TU_alias)
    +
    +assert_type(np.strings.rpartition(AR_U, "\n"), npt.NDArray[np.str_])
    +assert_type(np.strings.rpartition(AR_S, [b"a", b"b", b"c"]), npt.NDArray[np.bytes_])
    +assert_type(np.strings.rpartition(AR_T, "\n"), AR_TU_alias)
    +
    +assert_type(np.strings.replace(AR_U, "_", "-"), npt.NDArray[np.str_])
    +assert_type(np.strings.replace(AR_S, [b"_", b""], [b"a", b"b"]), npt.NDArray[np.bytes_])
    +assert_type(np.strings.replace(AR_T, "_", "_"), AR_TU_alias)
    +
    +assert_type(np.strings.lower(AR_U), npt.NDArray[np.str_])
    +assert_type(np.strings.lower(AR_S), npt.NDArray[np.bytes_])
    +assert_type(np.strings.lower(AR_T), AR_T_alias)
    +
    +assert_type(np.strings.upper(AR_U), npt.NDArray[np.str_])
    +assert_type(np.strings.upper(AR_S), npt.NDArray[np.bytes_])
    +assert_type(np.strings.upper(AR_T), AR_T_alias)
    +
    +assert_type(np.strings.swapcase(AR_U), npt.NDArray[np.str_])
    +assert_type(np.strings.swapcase(AR_S), npt.NDArray[np.bytes_])
    +assert_type(np.strings.swapcase(AR_T), AR_T_alias)
    +
    +assert_type(np.strings.title(AR_U), npt.NDArray[np.str_])
    +assert_type(np.strings.title(AR_S), npt.NDArray[np.bytes_])
    +assert_type(np.strings.title(AR_T), AR_T_alias)
    +
    +assert_type(np.strings.zfill(AR_U, 5), npt.NDArray[np.str_])
    +assert_type(np.strings.zfill(AR_S, [2, 3, 4]), npt.NDArray[np.bytes_])
    +assert_type(np.strings.zfill(AR_T, 5), AR_T_alias)
    +
    +assert_type(np.strings.endswith(AR_U, "a", start=[1, 2, 3]), npt.NDArray[np.bool])
    +assert_type(np.strings.endswith(AR_S, [b"a", b"b", b"c"], end=9), npt.NDArray[np.bool])
    +assert_type(np.strings.endswith(AR_T, "a", start=[1, 2, 3]), npt.NDArray[np.bool])
    +
    +assert_type(np.strings.startswith(AR_U, "a", start=[1, 2, 3]), npt.NDArray[np.bool])
    +assert_type(np.strings.startswith(AR_S, [b"a", b"b", b"c"], end=9), npt.NDArray[np.bool])
    +assert_type(np.strings.startswith(AR_T, "a", start=[1, 2, 3]), npt.NDArray[np.bool])
    +
    +assert_type(np.strings.find(AR_U, "a", start=[1, 2, 3]), npt.NDArray[np.int_])
    +assert_type(np.strings.find(AR_S, [b"a", b"b", b"c"], end=9), npt.NDArray[np.int_])
    +assert_type(np.strings.find(AR_T, "a", start=[1, 2, 3]), npt.NDArray[np.int_])
    +
    +assert_type(np.strings.rfind(AR_U, "a", start=[1, 2, 3]), npt.NDArray[np.int_])
    +assert_type(np.strings.rfind(AR_S, [b"a", b"b", b"c"], end=9), npt.NDArray[np.int_])
    +assert_type(np.strings.rfind(AR_T, "a", start=[1, 2, 3]), npt.NDArray[np.int_])
    +
    +assert_type(np.strings.index(AR_U, "a", start=[1, 2, 3]), npt.NDArray[np.int_])
    +assert_type(np.strings.index(AR_S, [b"a", b"b", b"c"], end=9), npt.NDArray[np.int_])
    +assert_type(np.strings.index(AR_T, "a", start=[1, 2, 3]), npt.NDArray[np.int_])
    +
    +assert_type(np.strings.rindex(AR_U, "a", start=[1, 2, 3]), npt.NDArray[np.int_])
    +assert_type(np.strings.rindex(AR_S, [b"a", b"b", b"c"], end=9), npt.NDArray[np.int_])
    +assert_type(np.strings.rindex(AR_T, "a", start=[1, 2, 3]), npt.NDArray[np.int_])
    +
    +assert_type(np.strings.isalpha(AR_U), npt.NDArray[np.bool])
    +assert_type(np.strings.isalpha(AR_S), npt.NDArray[np.bool])
    +assert_type(np.strings.isalpha(AR_T), npt.NDArray[np.bool])
    +
    +assert_type(np.strings.isalnum(AR_U), npt.NDArray[np.bool])
    +assert_type(np.strings.isalnum(AR_S), npt.NDArray[np.bool])
    +assert_type(np.strings.isalnum(AR_T), npt.NDArray[np.bool])
    +
    +assert_type(np.strings.isdecimal(AR_U), npt.NDArray[np.bool])
    +assert_type(np.strings.isdecimal(AR_T), npt.NDArray[np.bool])
    +
    +assert_type(np.strings.isdigit(AR_U), npt.NDArray[np.bool])
    +assert_type(np.strings.isdigit(AR_S), npt.NDArray[np.bool])
    +assert_type(np.strings.isdigit(AR_T), npt.NDArray[np.bool])
    +
    +assert_type(np.strings.islower(AR_U), npt.NDArray[np.bool])
    +assert_type(np.strings.islower(AR_S), npt.NDArray[np.bool])
    +assert_type(np.strings.islower(AR_T), npt.NDArray[np.bool])
    +
    +assert_type(np.strings.isnumeric(AR_U), npt.NDArray[np.bool])
    +assert_type(np.strings.isnumeric(AR_T), npt.NDArray[np.bool])
    +
    +assert_type(np.strings.isspace(AR_U), npt.NDArray[np.bool])
    +assert_type(np.strings.isspace(AR_S), npt.NDArray[np.bool])
    +assert_type(np.strings.isspace(AR_T), npt.NDArray[np.bool])
    +
    +assert_type(np.strings.istitle(AR_U), npt.NDArray[np.bool])
    +assert_type(np.strings.istitle(AR_S), npt.NDArray[np.bool])
    +assert_type(np.strings.istitle(AR_T), npt.NDArray[np.bool])
    +
    +assert_type(np.strings.isupper(AR_U), npt.NDArray[np.bool])
    +assert_type(np.strings.isupper(AR_S), npt.NDArray[np.bool])
    +assert_type(np.strings.isupper(AR_T), npt.NDArray[np.bool])
    +
    +assert_type(np.strings.str_len(AR_U), npt.NDArray[np.int_])
    +assert_type(np.strings.str_len(AR_S), npt.NDArray[np.int_])
    +assert_type(np.strings.str_len(AR_T), npt.NDArray[np.int_])
    +
    +assert_type(np.strings.translate(AR_U, ""), npt.NDArray[np.str_])
    +assert_type(np.strings.translate(AR_S, ""), npt.NDArray[np.bytes_])
    +assert_type(np.strings.translate(AR_T, ""), AR_T_alias)
    +
    +assert_type(np.strings.slice(AR_U, 1, 5, 2), npt.NDArray[np.str_])
    +assert_type(np.strings.slice(AR_S, 1, 5, 2), npt.NDArray[np.bytes_])
    +assert_type(np.strings.slice(AR_T, 1, 5, 2), AR_T_alias)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/testing.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/testing.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..34fbc5feeb41b930293170c8ba78734a37a9eb07
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/testing.pyi
    @@ -0,0 +1,198 @@
    +import contextlib
    +import re
    +import sys
    +import types
    +import unittest
    +import warnings
    +from collections.abc import Callable
    +from pathlib import Path
    +from typing import Any, TypeVar, assert_type
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +AR_f8: npt.NDArray[np.float64]
    +AR_i8: npt.NDArray[np.int64]
    +
    +bool_obj: bool
    +suppress_obj: np.testing.suppress_warnings  # type: ignore[deprecated]  # pyright: ignore[reportDeprecated]
    +FT = TypeVar("FT", bound=Callable[..., Any])
    +
    +def func() -> int: ...
    +
    +def func2(
    +    x: npt.NDArray[np.number],
    +    y: npt.NDArray[np.number],
    +) -> npt.NDArray[np.bool]: ...
    +
    +assert_type(np.testing.KnownFailureException(), np.testing.KnownFailureException)
    +assert_type(np.testing.IgnoreException(), np.testing.IgnoreException)
    +
    +assert_type(
    +    np.testing.clear_and_catch_warnings(modules=[np.testing]),
    +    np.testing.clear_and_catch_warnings[None],
    +)
    +assert_type(
    +    np.testing.clear_and_catch_warnings(True),
    +    np.testing.clear_and_catch_warnings[list[warnings.WarningMessage]],
    +)
    +assert_type(
    +    np.testing.clear_and_catch_warnings(False),
    +    np.testing.clear_and_catch_warnings[None],
    +)
    +assert_type(
    +    np.testing.clear_and_catch_warnings(bool_obj),
    +    np.testing.clear_and_catch_warnings,
    +)
    +assert_type(
    +    np.testing.clear_and_catch_warnings.class_modules,
    +    tuple[types.ModuleType, ...],
    +)
    +assert_type(
    +    np.testing.clear_and_catch_warnings.modules,
    +    set[types.ModuleType],
    +)
    +
    +with np.testing.clear_and_catch_warnings(True) as c1:
    +    assert_type(c1, list[warnings.WarningMessage])
    +with np.testing.clear_and_catch_warnings() as c2:
    +    assert_type(c2, None)
    +
    +assert_type(np.testing.suppress_warnings("once"), np.testing.suppress_warnings)  # type: ignore[deprecated]  # pyright: ignore[reportDeprecated]
    +assert_type(np.testing.suppress_warnings()(func), Callable[[], int])  # type: ignore[deprecated]  # pyright: ignore[reportDeprecated]
    +assert_type(suppress_obj.filter(RuntimeWarning), None)
    +assert_type(suppress_obj.record(RuntimeWarning), list[warnings.WarningMessage])
    +with suppress_obj as c3:
    +    assert_type(c3, np.testing.suppress_warnings)  # type: ignore[deprecated]  # pyright: ignore[reportDeprecated]
    +
    +assert_type(np.testing.verbose, int)
    +assert_type(np.testing.IS_PYPY, bool)
    +assert_type(np.testing.HAS_REFCOUNT, bool)
    +assert_type(np.testing.HAS_LAPACK64, bool)
    +
    +assert_type(np.testing.assert_(1, msg="test"), None)
    +assert_type(np.testing.assert_(2, msg=lambda: "test"), None)
    +
    +if sys.platform == "win32" or sys.platform == "cygwin":
    +    assert_type(np.testing.memusage(), int)
    +elif sys.platform == "linux":
    +    assert_type(np.testing.memusage(), int | None)
    +
    +assert_type(np.testing.jiffies(), int)
    +
    +assert_type(np.testing.build_err_msg([0, 1, 2], "test"), str)
    +assert_type(np.testing.build_err_msg(range(2), "test", header="header"), str)
    +assert_type(np.testing.build_err_msg(np.arange(9).reshape(3, 3), "test", verbose=False), str)
    +assert_type(np.testing.build_err_msg("abc", "test", names=["x", "y"]), str)
    +assert_type(np.testing.build_err_msg([1.0, 2.0], "test", precision=5), str)
    +
    +assert_type(np.testing.assert_equal({1}, {1}), None)
    +assert_type(np.testing.assert_equal([1, 2, 3], [1, 2, 3], err_msg="fail"), None)
    +assert_type(np.testing.assert_equal(1, 1.0, verbose=True), None)
    +
    +assert_type(np.testing.print_assert_equal("Test XYZ of func xyz", [0, 1], [0, 1]), None)
    +
    +assert_type(np.testing.assert_almost_equal(1.0, 1.1), None)
    +assert_type(np.testing.assert_almost_equal([1, 2, 3], [1, 2, 3], err_msg="fail"), None)
    +assert_type(np.testing.assert_almost_equal(1, 1.0, verbose=True), None)
    +assert_type(np.testing.assert_almost_equal(1, 1.0001, decimal=2), None)
    +
    +assert_type(np.testing.assert_approx_equal(1.0, 1.1), None)
    +assert_type(np.testing.assert_approx_equal("1", "2", err_msg="fail"), None)
    +assert_type(np.testing.assert_approx_equal(1, 1.0, verbose=True), None)
    +assert_type(np.testing.assert_approx_equal(1, 1.0001, significant=2), None)
    +
    +assert_type(np.testing.assert_array_compare(func2, AR_i8, AR_f8, err_msg="test"), None)
    +assert_type(np.testing.assert_array_compare(func2, AR_i8, AR_f8, verbose=True), None)
    +assert_type(np.testing.assert_array_compare(func2, AR_i8, AR_f8, header="header"), None)
    +assert_type(np.testing.assert_array_compare(func2, AR_i8, AR_f8, precision=np.int64()), None)
    +assert_type(np.testing.assert_array_compare(func2, AR_i8, AR_f8, equal_nan=False), None)
    +assert_type(np.testing.assert_array_compare(func2, AR_i8, AR_f8, equal_inf=True), None)
    +
    +assert_type(np.testing.assert_array_equal(AR_i8, AR_f8), None)
    +assert_type(np.testing.assert_array_equal(AR_i8, AR_f8, err_msg="test"), None)
    +assert_type(np.testing.assert_array_equal(AR_i8, AR_f8, verbose=True), None)
    +
    +assert_type(np.testing.assert_array_almost_equal(AR_i8, AR_f8), None)
    +assert_type(np.testing.assert_array_almost_equal(AR_i8, AR_f8, err_msg="test"), None)
    +assert_type(np.testing.assert_array_almost_equal(AR_i8, AR_f8, verbose=True), None)
    +assert_type(np.testing.assert_array_almost_equal(AR_i8, AR_f8, decimal=1), None)
    +
    +assert_type(np.testing.assert_array_less(AR_i8, AR_f8), None)
    +assert_type(np.testing.assert_array_less(AR_i8, AR_f8, err_msg="test"), None)
    +assert_type(np.testing.assert_array_less(AR_i8, AR_f8, verbose=True), None)
    +
    +assert_type(np.testing.runstring("1 + 1", {}), Any)
    +assert_type(np.testing.runstring("int64() + 1", {"int64": np.int64}), Any)
    +
    +assert_type(np.testing.assert_string_equal("1", "1"), None)
    +
    +assert_type(np.testing.rundocs(), None)
    +assert_type(np.testing.rundocs("test.py"), None)
    +assert_type(np.testing.rundocs(Path("test.py"), raise_on_error=True), None)
    +
    +def func3(a: int) -> bool: ...
    +
    +assert_type(
    +    np.testing.assert_raises(RuntimeWarning),
    +    unittest.case._AssertRaisesContext[RuntimeWarning],
    +)
    +assert_type(np.testing.assert_raises(RuntimeWarning, func3, 5), None)
    +
    +assert_type(
    +    np.testing.assert_raises_regex(RuntimeWarning, r"test"),
    +    unittest.case._AssertRaisesContext[RuntimeWarning],
    +)
    +assert_type(np.testing.assert_raises_regex(RuntimeWarning, b"test", func3, 5), None)
    +assert_type(np.testing.assert_raises_regex(RuntimeWarning, re.compile(b"test"), func3, 5), None)
    +
    +class Test: ...
    +
    +def decorate(a: FT) -> FT:
    +    return a
    +
    +assert_type(np.testing.decorate_methods(Test, decorate), None)
    +assert_type(np.testing.decorate_methods(Test, decorate, None), None)
    +assert_type(np.testing.decorate_methods(Test, decorate, "test"), None)
    +assert_type(np.testing.decorate_methods(Test, decorate, b"test"), None)
    +assert_type(np.testing.decorate_methods(Test, decorate, re.compile("test")), None)
    +
    +assert_type(np.testing.measure("for i in range(1000): np.sqrt(i**2)"), float)
    +assert_type(np.testing.measure(b"for i in range(1000): np.sqrt(i**2)", times=5), float)
    +
    +assert_type(np.testing.assert_allclose(AR_i8, AR_f8), None)
    +assert_type(np.testing.assert_allclose(AR_i8, AR_f8, rtol=0.005), None)
    +assert_type(np.testing.assert_allclose(AR_i8, AR_f8, atol=1), None)
    +assert_type(np.testing.assert_allclose(AR_i8, AR_f8, equal_nan=True), None)
    +assert_type(np.testing.assert_allclose(AR_i8, AR_f8, err_msg="err"), None)
    +assert_type(np.testing.assert_allclose(AR_i8, AR_f8, verbose=False), None)
    +
    +assert_type(np.testing.assert_array_almost_equal_nulp(AR_i8, AR_f8, nulp=2), None)
    +
    +assert_type(np.testing.assert_array_max_ulp(AR_i8, AR_f8, maxulp=2), npt.NDArray[Any])
    +assert_type(np.testing.assert_array_max_ulp(AR_i8, AR_f8, dtype=np.float32), npt.NDArray[Any])
    +
    +assert_type(np.testing.assert_warns(RuntimeWarning), contextlib._GeneratorContextManager[None])  # type: ignore[deprecated]  # pyright: ignore[reportDeprecated]
    +assert_type(np.testing.assert_warns(RuntimeWarning, func3, 5), bool)  # type: ignore[deprecated]  # pyright: ignore[reportDeprecated]
    +
    +def func4(a: int, b: str) -> bool: ...
    +
    +assert_type(np.testing.assert_no_warnings(), contextlib._GeneratorContextManager[None])
    +assert_type(np.testing.assert_no_warnings(func3, 5), bool)
    +assert_type(np.testing.assert_no_warnings(func4, a=1, b="test"), bool)
    +assert_type(np.testing.assert_no_warnings(func4, 1, "test"), bool)
    +
    +assert_type(np.testing.tempdir("test_dir"), contextlib._GeneratorContextManager[str])
    +assert_type(np.testing.tempdir(prefix=b"test"), contextlib._GeneratorContextManager[bytes])
    +assert_type(np.testing.tempdir("test_dir", dir=Path("here")), contextlib._GeneratorContextManager[str])
    +
    +assert_type(np.testing.temppath("test_dir", text=True), contextlib._GeneratorContextManager[str])
    +assert_type(np.testing.temppath(prefix=b"test"), contextlib._GeneratorContextManager[bytes])
    +assert_type(np.testing.temppath("test_dir", dir=Path("here")), contextlib._GeneratorContextManager[str])
    +
    +assert_type(np.testing.assert_no_gc_cycles(), contextlib._GeneratorContextManager[None])
    +assert_type(np.testing.assert_no_gc_cycles(func3, 5), None)
    +
    +assert_type(np.testing.break_cycles(), None)
    +
    +assert_type(np.testing.TestCase(), unittest.case.TestCase)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/twodim_base.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/twodim_base.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..1fd17e36279bb87bf39fb26e3b3148945893f425
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/twodim_base.pyi
    @@ -0,0 +1,225 @@
    +from typing import Any, TypeAlias, TypeVar, assert_type, type_check_only
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +_ScalarT = TypeVar("_ScalarT", bound=np.generic)
    +
    +_1D: TypeAlias = tuple[int]
    +_2D: TypeAlias = tuple[int, int]
    +_ND: TypeAlias = tuple[Any, ...]
    +
    +_Indices2D: TypeAlias = tuple[
    +    np.ndarray[_1D, np.dtype[np.intp]],
    +    np.ndarray[_1D, np.dtype[np.intp]],
    +]
    +
    +###
    +
    +_nd_bool: np.ndarray[_ND, np.dtype[np.bool]]
    +_1d_bool: np.ndarray[_1D, np.dtype[np.bool]]
    +_2d_bool: np.ndarray[_2D, np.dtype[np.bool]]
    +_nd_u64: np.ndarray[_ND, np.dtype[np.uint64]]
    +_nd_i64: np.ndarray[_ND, np.dtype[np.int64]]
    +_nd_f64: np.ndarray[_ND, np.dtype[np.float64]]
    +_nd_c128: np.ndarray[_ND, np.dtype[np.complex128]]
    +_nd_obj: np.ndarray[_ND, np.dtype[np.object_]]
    +
    +_to_nd_bool: list[bool] | list[list[bool]]
    +_to_1d_bool: list[bool]
    +_to_2d_bool: list[list[bool]]
    +
    +_to_1d_f64: list[float]
    +_to_1d_c128: list[complex]
    +
    +@type_check_only
    +def func1(ar: npt.NDArray[_ScalarT], a: int) -> npt.NDArray[_ScalarT]: ...
    +@type_check_only
    +def func2(ar: npt.NDArray[np.number], a: str) -> npt.NDArray[np.float64]: ...
    +
    +@type_check_only
    +class _Cube:
    +    shape = 3, 4
    +    ndim = 2
    +
    +###
    +
    +# fliplr
    +assert_type(np.fliplr(_nd_bool), np.ndarray[_ND, np.dtype[np.bool]])
    +assert_type(np.fliplr(_1d_bool), np.ndarray[_1D, np.dtype[np.bool]])
    +assert_type(np.fliplr(_2d_bool), np.ndarray[_2D, np.dtype[np.bool]])
    +assert_type(np.fliplr(_to_nd_bool), np.ndarray)
    +assert_type(np.fliplr(_to_1d_bool), np.ndarray)
    +assert_type(np.fliplr(_to_2d_bool), np.ndarray)
    +
    +# flipud
    +assert_type(np.flipud(_nd_bool), np.ndarray[_ND, np.dtype[np.bool]])
    +assert_type(np.flipud(_1d_bool), np.ndarray[_1D, np.dtype[np.bool]])
    +assert_type(np.flipud(_2d_bool), np.ndarray[_2D, np.dtype[np.bool]])
    +assert_type(np.flipud(_to_nd_bool), np.ndarray)
    +assert_type(np.flipud(_to_1d_bool), np.ndarray)
    +assert_type(np.flipud(_to_2d_bool), np.ndarray)
    +
    +# eye
    +assert_type(np.eye(10), np.ndarray[_2D, np.dtype[np.float64]])
    +assert_type(np.eye(10, M=20, dtype=np.int64), np.ndarray[_2D, np.dtype[np.int64]])
    +assert_type(np.eye(10, k=2, dtype=int), np.ndarray[_2D])
    +
    +# diag
    +assert_type(np.diag(_nd_bool), np.ndarray[_ND, np.dtype[np.bool]])
    +assert_type(np.diag(_1d_bool), np.ndarray[_2D, np.dtype[np.bool]])
    +assert_type(np.diag(_2d_bool), np.ndarray[_1D, np.dtype[np.bool]])
    +assert_type(np.diag(_to_nd_bool, k=0), np.ndarray)
    +assert_type(np.diag(_to_1d_bool, k=0), np.ndarray[_2D])
    +assert_type(np.diag(_to_2d_bool, k=0), np.ndarray[_1D])
    +
    +# diagflat
    +assert_type(np.diagflat(_nd_bool), np.ndarray[_2D, np.dtype[np.bool]])
    +assert_type(np.diagflat(_1d_bool), np.ndarray[_2D, np.dtype[np.bool]])
    +assert_type(np.diagflat(_2d_bool), np.ndarray[_2D, np.dtype[np.bool]])
    +assert_type(np.diagflat(_to_nd_bool, k=0), np.ndarray[_2D])
    +assert_type(np.diagflat(_to_1d_bool, k=0), np.ndarray[_2D])
    +assert_type(np.diagflat(_to_2d_bool, k=0), np.ndarray[_2D])
    +
    +# tri
    +assert_type(np.tri(10), np.ndarray[_2D, np.dtype[np.float64]])
    +assert_type(np.tri(10, M=20, dtype=np.int64), np.ndarray[_2D, np.dtype[np.int64]])
    +assert_type(np.tri(10, k=2, dtype=int), np.ndarray[_2D])
    +
    +# tril
    +assert_type(np.tril(_nd_bool), np.ndarray[_ND, np.dtype[np.bool]])
    +assert_type(np.tril(_to_nd_bool, k=0), np.ndarray)
    +assert_type(np.tril(_to_1d_bool, k=0), np.ndarray)
    +assert_type(np.tril(_to_2d_bool, k=0), np.ndarray)
    +
    +# triu
    +assert_type(np.triu(_nd_bool), np.ndarray[_ND, np.dtype[np.bool]])
    +assert_type(np.triu(_to_nd_bool, k=0), np.ndarray)
    +assert_type(np.triu(_to_1d_bool, k=0), np.ndarray)
    +assert_type(np.triu(_to_2d_bool, k=0), np.ndarray)
    +
    +# vander
    +assert_type(np.vander(_nd_bool), np.ndarray[_2D, np.dtype[np.int_]])
    +assert_type(np.vander(_nd_u64), np.ndarray[_2D, np.dtype[np.uint64]])
    +assert_type(np.vander(_nd_i64, N=2), np.ndarray[_2D, np.dtype[np.int64]])
    +assert_type(np.vander(_nd_f64, increasing=True), np.ndarray[_2D, np.dtype[np.float64]])
    +assert_type(np.vander(_nd_c128), np.ndarray[_2D, np.dtype[np.complex128]])
    +assert_type(np.vander(_nd_obj), np.ndarray[_2D, np.dtype[np.object_]])
    +
    +# histogram2d
    +assert_type(
    +    np.histogram2d(_to_1d_f64, _to_1d_f64),
    +    tuple[
    +        np.ndarray[_2D, np.dtype[np.float64]],
    +        np.ndarray[_1D, np.dtype[np.float64]],
    +        np.ndarray[_1D, np.dtype[np.float64]],
    +    ],
    +)
    +assert_type(
    +    np.histogram2d(_to_1d_c128, _to_1d_c128),
    +    tuple[
    +        np.ndarray[_2D, np.dtype[np.float64]],
    +        np.ndarray[_1D, np.dtype[np.complex128 | Any]],
    +        np.ndarray[_1D, np.dtype[np.complex128 | Any]],
    +    ],
    +)
    +assert_type(
    +    np.histogram2d(_nd_i64, _nd_bool),
    +    tuple[
    +        np.ndarray[_2D, np.dtype[np.float64]],
    +        np.ndarray[_1D, np.dtype[np.float64]],
    +        np.ndarray[_1D, np.dtype[np.float64]],
    +    ],
    +)
    +assert_type(
    +    np.histogram2d(_nd_f64, _nd_i64),
    +    tuple[
    +        np.ndarray[_2D, np.dtype[np.float64]],
    +        np.ndarray[_1D, np.dtype[np.float64]],
    +        np.ndarray[_1D, np.dtype[np.float64]],
    +    ],
    +)
    +assert_type(
    +    np.histogram2d(_nd_i64, _nd_f64),
    +    tuple[
    +        np.ndarray[_2D, np.dtype[np.float64]],
    +        np.ndarray[_1D, np.dtype[np.float64]],
    +        np.ndarray[_1D, np.dtype[np.float64]],
    +    ],
    +)
    +assert_type(
    +    np.histogram2d(_nd_f64, _nd_c128, weights=_to_1d_bool),
    +    tuple[
    +        np.ndarray[_2D, np.dtype[np.float64]],
    +        np.ndarray[_1D, np.dtype[np.complex128]],
    +        np.ndarray[_1D, np.dtype[np.complex128]],
    +    ],
    +)
    +assert_type(
    +    np.histogram2d(_nd_f64, _nd_c128, bins=8),
    +    tuple[
    +        np.ndarray[_2D, np.dtype[np.float64]],
    +        np.ndarray[_1D, np.dtype[np.complex128]],
    +        np.ndarray[_1D, np.dtype[np.complex128]],
    +    ],
    +)
    +assert_type(
    +    np.histogram2d(_nd_c128, _nd_f64, bins=(8, 5)),
    +    tuple[
    +        np.ndarray[_2D, np.dtype[np.float64]],
    +        np.ndarray[_1D, np.dtype[np.complex128]],
    +        np.ndarray[_1D, np.dtype[np.complex128]],
    +    ],
    +)
    +assert_type(
    +    np.histogram2d(_nd_c128, _nd_i64, bins=_nd_u64),
    +    tuple[
    +        np.ndarray[_2D, np.dtype[np.float64]],
    +        np.ndarray[_1D, np.dtype[np.uint64]],
    +        np.ndarray[_1D, np.dtype[np.uint64]],
    +    ],
    +)
    +assert_type(
    +    np.histogram2d(_nd_c128, _nd_c128, bins=(_nd_u64, _nd_u64)),
    +    tuple[
    +        np.ndarray[_2D, np.dtype[np.float64]],
    +        np.ndarray[_1D, np.dtype[np.uint64]],
    +        np.ndarray[_1D, np.dtype[np.uint64]],
    +    ],
    +)
    +assert_type(
    +    np.histogram2d(_nd_c128, _nd_c128, bins=(_nd_bool, 8)),
    +    tuple[
    +        np.ndarray[_2D, np.dtype[np.float64]],
    +        np.ndarray[_1D, np.dtype[np.complex128 | np.bool]],
    +        np.ndarray[_1D, np.dtype[np.complex128 | np.bool]],
    +    ],
    +)
    +assert_type(
    +    np.histogram2d(_nd_c128, _nd_c128, bins=(_to_1d_f64, 8)),
    +    tuple[
    +        np.ndarray[_2D, np.dtype[np.float64]],
    +        np.ndarray[_1D, np.dtype[np.complex128 | Any]],
    +        np.ndarray[_1D, np.dtype[np.complex128 | Any]],
    +    ],
    +)
    +
    +# mask_indices
    +assert_type(np.mask_indices(10, func1), _Indices2D)
    +assert_type(np.mask_indices(8, func2, "0"), _Indices2D)
    +
    +# tril_indices
    +assert_type(np.tril_indices(3), _Indices2D)
    +assert_type(np.tril_indices(3, 1), _Indices2D)
    +assert_type(np.tril_indices(3, 1, 2), _Indices2D)
    +# tril_indices
    +assert_type(np.triu_indices(3), _Indices2D)
    +assert_type(np.triu_indices(3, 1), _Indices2D)
    +assert_type(np.triu_indices(3, 1, 2), _Indices2D)
    +
    +# tril_indices_from
    +assert_type(np.tril_indices_from(_2d_bool), _Indices2D)
    +assert_type(np.tril_indices_from(_Cube()), _Indices2D)
    +# triu_indices_from
    +assert_type(np.triu_indices_from(_2d_bool), _Indices2D)
    +assert_type(np.triu_indices_from(_Cube()), _Indices2D)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/type_check.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/type_check.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..df95da78ffb7a4669b88310739bb710d7d41f6ae
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/type_check.pyi
    @@ -0,0 +1,67 @@
    +from typing import Any, Literal, assert_type
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +f8: np.float64
    +f: float
    +
    +# NOTE: Avoid importing the platform specific `np.float128` type
    +AR_i8: npt.NDArray[np.int64]
    +AR_i4: npt.NDArray[np.int32]
    +AR_f2: npt.NDArray[np.float16]
    +AR_f8: npt.NDArray[np.float64]
    +AR_f16: npt.NDArray[np.longdouble]
    +AR_c8: npt.NDArray[np.complex64]
    +AR_c16: npt.NDArray[np.complex128]
    +
    +AR_LIKE_f: list[float]
    +
    +class ComplexObj:
    +    real: slice
    +    imag: slice
    +
    +assert_type(np.mintypecode(["f8"], typeset="qfQF"), str)
    +
    +assert_type(np.real(ComplexObj()), slice)
    +assert_type(np.real(AR_f8), npt.NDArray[np.float64])
    +assert_type(np.real(AR_c16), npt.NDArray[np.float64])
    +assert_type(np.real(AR_LIKE_f), npt.NDArray[Any])
    +
    +assert_type(np.imag(ComplexObj()), slice)
    +assert_type(np.imag(AR_f8), npt.NDArray[np.float64])
    +assert_type(np.imag(AR_c16), npt.NDArray[np.float64])
    +assert_type(np.imag(AR_LIKE_f), npt.NDArray[Any])
    +
    +assert_type(np.iscomplex(f8), np.bool)
    +assert_type(np.iscomplex(AR_f8), npt.NDArray[np.bool])
    +assert_type(np.iscomplex(AR_LIKE_f), npt.NDArray[np.bool])
    +
    +assert_type(np.isreal(f8), np.bool)
    +assert_type(np.isreal(AR_f8), npt.NDArray[np.bool])
    +assert_type(np.isreal(AR_LIKE_f), npt.NDArray[np.bool])
    +
    +assert_type(np.iscomplexobj(f8), bool)
    +assert_type(np.isrealobj(f8), bool)
    +
    +assert_type(np.nan_to_num(f8), np.float64)
    +assert_type(np.nan_to_num(f, copy=True), Any)
    +assert_type(np.nan_to_num(AR_f8, nan=1.5), npt.NDArray[np.float64])
    +assert_type(np.nan_to_num(AR_LIKE_f, posinf=9999), npt.NDArray[Any])
    +
    +assert_type(np.real_if_close(AR_f8), npt.NDArray[np.float64])
    +assert_type(np.real_if_close(AR_c16), npt.NDArray[np.float64 | np.complex128])
    +assert_type(np.real_if_close(AR_c8), npt.NDArray[np.float32 | np.complex64])
    +assert_type(np.real_if_close(AR_LIKE_f), npt.NDArray[Any])
    +
    +assert_type(np.typename("h"), Literal["short"])
    +assert_type(np.typename("B"), Literal["unsigned char"])
    +assert_type(np.typename("V"), Literal["void"])
    +assert_type(np.typename("S1"), Literal["character"])
    +
    +assert_type(np.common_type(AR_i4), type[np.float64])
    +assert_type(np.common_type(AR_f2), type[np.float16])
    +assert_type(np.common_type(AR_f2, AR_i4), type[np.float64])
    +assert_type(np.common_type(AR_f16, AR_i4), type[np.longdouble])
    +assert_type(np.common_type(AR_c8, AR_f2), type[np.complex64])
    +assert_type(np.common_type(AR_f2, AR_c8, AR_i4), type[np.complexfloating])
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/ufunc_config.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/ufunc_config.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..77c27eb3b4cabcc70e823ceff2aad7f437c87e9c
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/ufunc_config.pyi
    @@ -0,0 +1,29 @@
    +"""Typing tests for `_core._ufunc_config`."""
    +
    +from _typeshed import SupportsWrite
    +from collections.abc import Callable
    +from typing import Any, assert_type
    +
    +import numpy as np
    +
    +def func(a: str, b: int) -> None: ...
    +
    +class Write:
    +    def write(self, value: str) -> None: ...
    +
    +assert_type(np.seterr(all=None), np._core._ufunc_config._ErrDict)
    +assert_type(np.seterr(divide="ignore"), np._core._ufunc_config._ErrDict)
    +assert_type(np.seterr(over="warn"), np._core._ufunc_config._ErrDict)
    +assert_type(np.seterr(under="call"), np._core._ufunc_config._ErrDict)
    +assert_type(np.seterr(invalid="raise"), np._core._ufunc_config._ErrDict)
    +assert_type(np.geterr(), np._core._ufunc_config._ErrDict)
    +
    +assert_type(np.setbufsize(4096), int)
    +assert_type(np.getbufsize(), int)
    +
    +assert_type(np.seterrcall(func), Callable[[str, int], Any] | SupportsWrite[str] | None)
    +assert_type(np.seterrcall(Write()), Callable[[str, int], Any] | SupportsWrite[str] | None)
    +assert_type(np.geterrcall(), Callable[[str, int], Any] | SupportsWrite[str] | None)
    +
    +assert_type(np.errstate(call=func, all="call"), np.errstate)
    +assert_type(np.errstate(call=Write(), divide="log", over="log"), np.errstate)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/ufunclike.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/ufunclike.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..c679b82d28367964f5cc15683fd3e388ced08ce1
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/ufunclike.pyi
    @@ -0,0 +1,31 @@
    +from typing import assert_type
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +AR_LIKE_b: list[bool]
    +AR_LIKE_u: list[np.uint32]
    +AR_LIKE_i: list[int]
    +AR_LIKE_f: list[float]
    +AR_LIKE_O: list[np.object_]
    +
    +AR_U: npt.NDArray[np.str_]
    +
    +assert_type(np.fix(AR_LIKE_b), npt.NDArray[np.floating])  # type: ignore[deprecated]
    +assert_type(np.fix(AR_LIKE_u), npt.NDArray[np.floating])  # type: ignore[deprecated]
    +assert_type(np.fix(AR_LIKE_i), npt.NDArray[np.floating])  # type: ignore[deprecated]
    +assert_type(np.fix(AR_LIKE_f), npt.NDArray[np.floating])  # type: ignore[deprecated]
    +assert_type(np.fix(AR_LIKE_O), npt.NDArray[np.object_])  # type: ignore[deprecated]
    +assert_type(np.fix(AR_LIKE_f, out=AR_U), npt.NDArray[np.str_])  # type: ignore[deprecated]
    +
    +assert_type(np.isposinf(AR_LIKE_b), npt.NDArray[np.bool])
    +assert_type(np.isposinf(AR_LIKE_u), npt.NDArray[np.bool])
    +assert_type(np.isposinf(AR_LIKE_i), npt.NDArray[np.bool])
    +assert_type(np.isposinf(AR_LIKE_f), npt.NDArray[np.bool])
    +assert_type(np.isposinf(AR_LIKE_f, out=AR_U), npt.NDArray[np.str_])
    +
    +assert_type(np.isneginf(AR_LIKE_b), npt.NDArray[np.bool])
    +assert_type(np.isneginf(AR_LIKE_u), npt.NDArray[np.bool])
    +assert_type(np.isneginf(AR_LIKE_i), npt.NDArray[np.bool])
    +assert_type(np.isneginf(AR_LIKE_f), npt.NDArray[np.bool])
    +assert_type(np.isneginf(AR_LIKE_f, out=AR_U), npt.NDArray[np.str_])
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/ufuncs.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/ufuncs.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..eda92f2117c6d64e4f0718f3270f70e103496ea9
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/ufuncs.pyi
    @@ -0,0 +1,142 @@
    +from typing import Any, Literal, NoReturn, assert_type
    +
    +import numpy as np
    +import numpy.typing as npt
    +
    +i8: np.int64
    +f8: np.float64
    +AR_f8: npt.NDArray[np.float64]
    +AR_i8: npt.NDArray[np.int64]
    +
    +assert_type(np.absolute.__doc__, str)
    +assert_type(np.absolute.types, list[str])
    +
    +assert_type(np.absolute.__name__, Literal["absolute"])
    +assert_type(np.absolute.__qualname__, Literal["absolute"])
    +assert_type(np.absolute.ntypes, Literal[20])
    +assert_type(np.absolute.identity, None)
    +assert_type(np.absolute.nin, Literal[1])
    +assert_type(np.absolute.nin, Literal[1])
    +assert_type(np.absolute.nout, Literal[1])
    +assert_type(np.absolute.nargs, Literal[2])
    +assert_type(np.absolute.signature, None)
    +assert_type(np.absolute(f8), Any)
    +assert_type(np.absolute(AR_f8), npt.NDArray[Any])
    +assert_type(np.absolute.at(AR_f8, AR_i8), None)
    +
    +assert_type(np.add.__name__, Literal["add"])
    +assert_type(np.add.__qualname__, Literal["add"])
    +assert_type(np.add.ntypes, Literal[22])
    +assert_type(np.add.identity, Literal[0])
    +assert_type(np.add.nin, Literal[2])
    +assert_type(np.add.nout, Literal[1])
    +assert_type(np.add.nargs, Literal[3])
    +assert_type(np.add.signature, None)
    +assert_type(np.add(f8, f8), Any)
    +assert_type(np.add(AR_f8, f8), npt.NDArray[Any])
    +assert_type(np.add.at(AR_f8, AR_i8, f8), None)
    +assert_type(np.add.reduce(AR_f8, axis=0), Any)
    +assert_type(np.add.accumulate(AR_f8), npt.NDArray[Any])
    +assert_type(np.add.reduceat(AR_f8, AR_i8), npt.NDArray[Any])
    +assert_type(np.add.outer(f8, f8), Any)
    +assert_type(np.add.outer(AR_f8, f8), npt.NDArray[Any])
    +
    +assert_type(np.frexp.__name__, Literal["frexp"])
    +assert_type(np.frexp.__qualname__, Literal["frexp"])
    +assert_type(np.frexp.ntypes, Literal[4])
    +assert_type(np.frexp.identity, None)
    +assert_type(np.frexp.nin, Literal[1])
    +assert_type(np.frexp.nout, Literal[2])
    +assert_type(np.frexp.nargs, Literal[3])
    +assert_type(np.frexp.signature, None)
    +assert_type(np.frexp(f8), tuple[Any, Any])
    +assert_type(np.frexp(AR_f8), tuple[npt.NDArray[Any], npt.NDArray[Any]])
    +
    +assert_type(np.divmod.__name__, Literal["divmod"])
    +assert_type(np.divmod.__qualname__, Literal["divmod"])
    +assert_type(np.divmod.ntypes, Literal[15])
    +assert_type(np.divmod.identity, None)
    +assert_type(np.divmod.nin, Literal[2])
    +assert_type(np.divmod.nout, Literal[2])
    +assert_type(np.divmod.nargs, Literal[4])
    +assert_type(np.divmod.signature, None)
    +assert_type(np.divmod(f8, f8), tuple[Any, Any])
    +assert_type(np.divmod(AR_f8, f8), tuple[npt.NDArray[Any], npt.NDArray[Any]])
    +
    +assert_type(np.matmul.__name__, Literal["matmul"])
    +assert_type(np.matmul.__qualname__, Literal["matmul"])
    +assert_type(np.matmul.ntypes, Literal[19])
    +assert_type(np.matmul.identity, None)
    +assert_type(np.matmul.nin, Literal[2])
    +assert_type(np.matmul.nout, Literal[1])
    +assert_type(np.matmul.nargs, Literal[3])
    +assert_type(np.matmul.signature, Literal["(n?,k),(k,m?)->(n?,m?)"])
    +assert_type(np.matmul.identity, None)
    +assert_type(np.matmul(AR_f8, AR_f8), Any)
    +assert_type(np.matmul(AR_f8, AR_f8, axes=[(0, 1), (0, 1), (0, 1)]), Any)
    +
    +assert_type(np.vecdot.__name__, Literal["vecdot"])
    +assert_type(np.vecdot.__qualname__, Literal["vecdot"])
    +assert_type(np.vecdot.ntypes, Literal[19])
    +assert_type(np.vecdot.identity, None)
    +assert_type(np.vecdot.nin, Literal[2])
    +assert_type(np.vecdot.nout, Literal[1])
    +assert_type(np.vecdot.nargs, Literal[3])
    +assert_type(np.vecdot.signature, Literal["(n),(n)->()"])
    +assert_type(np.vecdot.identity, None)
    +assert_type(np.vecdot(AR_f8, AR_f8), Any)
    +
    +assert_type(np.bitwise_count.__name__, Literal["bitwise_count"])
    +assert_type(np.bitwise_count.__qualname__, Literal["bitwise_count"])
    +assert_type(np.bitwise_count.ntypes, Literal[11])
    +assert_type(np.bitwise_count.identity, None)
    +assert_type(np.bitwise_count.nin, Literal[1])
    +assert_type(np.bitwise_count.nout, Literal[1])
    +assert_type(np.bitwise_count.nargs, Literal[2])
    +assert_type(np.bitwise_count.signature, None)
    +assert_type(np.bitwise_count.identity, None)
    +assert_type(np.bitwise_count(i8), Any)
    +assert_type(np.bitwise_count(AR_i8), npt.NDArray[Any])
    +
    +def test_absolute_outer_invalid() -> None:
    +    assert_type(np.absolute.outer(AR_f8, AR_f8), NoReturn)  # type: ignore[arg-type]
    +def test_frexp_outer_invalid() -> None:
    +    assert_type(np.frexp.outer(AR_f8, AR_f8), NoReturn)  # type: ignore[arg-type]
    +def test_divmod_outer_invalid() -> None:
    +    assert_type(np.divmod.outer(AR_f8, AR_f8), NoReturn)  # type: ignore[arg-type]
    +def test_matmul_outer_invalid() -> None:
    +    assert_type(np.matmul.outer(AR_f8, AR_f8), NoReturn)  # type: ignore[arg-type]
    +
    +def test_absolute_reduceat_invalid() -> None:
    +    assert_type(np.absolute.reduceat(AR_f8, AR_i8), NoReturn)  # type: ignore[arg-type]
    +def test_frexp_reduceat_invalid() -> None:
    +    assert_type(np.frexp.reduceat(AR_f8, AR_i8), NoReturn)  # type: ignore[arg-type]
    +def test_divmod_reduceat_invalid() -> None:
    +    assert_type(np.divmod.reduceat(AR_f8, AR_i8), NoReturn)  # type: ignore[arg-type]
    +def test_matmul_reduceat_invalid() -> None:
    +    assert_type(np.matmul.reduceat(AR_f8, AR_i8), NoReturn)  # type: ignore[arg-type]
    +
    +def test_absolute_reduce_invalid() -> None:
    +    assert_type(np.absolute.reduce(AR_f8), NoReturn)  # type: ignore[arg-type]
    +def test_frexp_reduce_invalid() -> None:
    +    assert_type(np.frexp.reduce(AR_f8), NoReturn)  # type: ignore[arg-type]
    +def test_divmod_reduce_invalid() -> None:
    +    assert_type(np.divmod.reduce(AR_f8), NoReturn)  # type: ignore[arg-type]
    +def test_matmul_reduce_invalid() -> None:
    +    assert_type(np.matmul.reduce(AR_f8), NoReturn)  # type: ignore[arg-type]
    +
    +def test_absolute_accumulate_invalid() -> None:
    +    assert_type(np.absolute.accumulate(AR_f8), NoReturn)  # type: ignore[arg-type]
    +def test_frexp_accumulate_invalid() -> None:
    +    assert_type(np.frexp.accumulate(AR_f8), NoReturn)  # type: ignore[arg-type]
    +def test_divmod_accumulate_invalid() -> None:
    +    assert_type(np.divmod.accumulate(AR_f8), NoReturn)  # type: ignore[arg-type]
    +def test_matmul_accumulate_invalid() -> None:
    +    assert_type(np.matmul.accumulate(AR_f8), NoReturn)  # type: ignore[arg-type]
    +
    +def test_frexp_at_invalid() -> None:
    +    assert_type(np.frexp.at(AR_f8, i8), NoReturn)  # type: ignore[arg-type]
    +def test_divmod_at_invalid() -> None:
    +    assert_type(np.divmod.at(AR_f8, i8, AR_f8), NoReturn)  # type: ignore[arg-type]
    +def test_matmul_at_invalid() -> None:
    +    assert_type(np.matmul.at(AR_f8, i8, AR_f8), NoReturn)  # type: ignore[arg-type]
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/warnings_and_errors.pyi b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/warnings_and_errors.pyi
    new file mode 100644
    index 0000000000000000000000000000000000000000..f756a8e45d46629d499612c5452932811dc95084
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/data/reveal/warnings_and_errors.pyi
    @@ -0,0 +1,11 @@
    +from typing import assert_type
    +
    +import numpy.exceptions as ex
    +
    +assert_type(ex.ModuleDeprecationWarning(), ex.ModuleDeprecationWarning)
    +assert_type(ex.VisibleDeprecationWarning(), ex.VisibleDeprecationWarning)
    +assert_type(ex.ComplexWarning(), ex.ComplexWarning)
    +assert_type(ex.RankWarning(), ex.RankWarning)
    +assert_type(ex.TooHardError(), ex.TooHardError)
    +assert_type(ex.AxisError("test"), ex.AxisError)
    +assert_type(ex.AxisError(5, 1), ex.AxisError)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/test_isfile.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/test_isfile.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..0e3157a1e54df6962c25b0b7a72a387b333156bb
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/test_isfile.py
    @@ -0,0 +1,38 @@
    +import os
    +import sys
    +from pathlib import Path
    +
    +import pytest
    +
    +import numpy as np
    +from numpy.testing import assert_
    +
    +ROOT = Path(np.__file__).parents[0]
    +FILES = [
    +    ROOT / "py.typed",
    +    ROOT / "__init__.pyi",
    +    ROOT / "ctypeslib" / "__init__.pyi",
    +    ROOT / "_core" / "__init__.pyi",
    +    ROOT / "f2py" / "__init__.pyi",
    +    ROOT / "fft" / "__init__.pyi",
    +    ROOT / "lib" / "__init__.pyi",
    +    ROOT / "linalg" / "__init__.pyi",
    +    ROOT / "ma" / "__init__.pyi",
    +    ROOT / "matrixlib" / "__init__.pyi",
    +    ROOT / "polynomial" / "__init__.pyi",
    +    ROOT / "random" / "__init__.pyi",
    +    ROOT / "testing" / "__init__.pyi",
    +]
    +if sys.version_info < (3, 12):
    +    FILES += [ROOT / "distutils" / "__init__.pyi"]
    +
    +
    +@pytest.mark.thread_unsafe(
    +    reason="os.path has a thread-safety bug (python/cpython#140054). "
    +           "Expected to only be a problem in 3.14.0"
    +)
    +class TestIsFile:
    +    def test_isfile(self):
    +        """Test if all ``.pyi`` files are properly installed."""
    +        for file in FILES:
    +            assert_(os.path.isfile(file))
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/test_runtime.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/test_runtime.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..462fe4eabdc024655807eb42394f0e244213500e
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/test_runtime.py
    @@ -0,0 +1,110 @@
    +"""Test the runtime usage of `numpy.typing`."""
    +
    +from typing import (
    +    Any,
    +    NamedTuple,
    +    Union,  # pyright: ignore[reportDeprecated]
    +    get_args,
    +    get_origin,
    +    get_type_hints,
    +)
    +
    +import pytest
    +
    +import numpy as np
    +import numpy._typing as _npt
    +import numpy.typing as npt
    +
    +
    +class TypeTup(NamedTuple):
    +    typ: type
    +    args: tuple[type, ...]
    +    origin: type | None
    +
    +
    +def _flatten_type_alias(t: Any) -> Any:
    +    # "flattens" a TypeAliasType to its underlying type alias
    +    return getattr(t, "__value__", t)
    +
    +
    +NDArrayTup = TypeTup(npt.NDArray, npt.NDArray.__args__, np.ndarray)
    +
    +TYPES = {
    +    "ArrayLike": TypeTup(
    +        _flatten_type_alias(npt.ArrayLike),
    +        _flatten_type_alias(npt.ArrayLike).__args__,
    +        Union,
    +    ),
    +    "DTypeLike": TypeTup(
    +        _flatten_type_alias(npt.DTypeLike),
    +        _flatten_type_alias(npt.DTypeLike).__args__,
    +        Union,
    +    ),
    +    "NBitBase": TypeTup(npt.NBitBase, (), None),  # type: ignore[deprecated]  # pyright: ignore[reportDeprecated]
    +    "NDArray": NDArrayTup,
    +}
    +
    +
    +@pytest.mark.parametrize("name,tup", TYPES.items(), ids=TYPES.keys())
    +def test_get_args(name: type, tup: TypeTup) -> None:
    +    """Test `typing.get_args`."""
    +    typ, ref = tup.typ, tup.args
    +    out = get_args(typ)
    +    assert out == ref
    +
    +
    +@pytest.mark.parametrize("name,tup", TYPES.items(), ids=TYPES.keys())
    +def test_get_origin(name: type, tup: TypeTup) -> None:
    +    """Test `typing.get_origin`."""
    +    typ, ref = tup.typ, tup.origin
    +    out = get_origin(typ)
    +    assert out == ref
    +
    +
    +@pytest.mark.parametrize("name,tup", TYPES.items(), ids=TYPES.keys())
    +def test_get_type_hints(name: type, tup: TypeTup) -> None:
    +    """Test `typing.get_type_hints`."""
    +    typ = tup.typ
    +
    +    def func(a: typ) -> None: pass
    +
    +    out = get_type_hints(func)
    +    ref = {"a": typ, "return": type(None)}
    +    assert out == ref
    +
    +
    +@pytest.mark.parametrize("name,tup", TYPES.items(), ids=TYPES.keys())
    +def test_get_type_hints_str(name: type, tup: TypeTup) -> None:
    +    """Test `typing.get_type_hints` with string-representation of types."""
    +    typ_str, typ = f"npt.{name}", tup.typ
    +
    +    def func(a: typ_str) -> None: pass
    +
    +    out = get_type_hints(func)
    +    ref = {"a": getattr(npt, str(name)), "return": type(None)}
    +    assert out == ref
    +
    +
    +def test_keys() -> None:
    +    """Test that ``TYPES.keys()`` and ``numpy.typing.__all__`` are synced."""
    +    keys = TYPES.keys()
    +    ref = set(npt.__all__)
    +    assert keys == ref
    +
    +
    +PROTOCOLS: dict[str, tuple[type[Any], object]] = {
    +    "_SupportsArray": (_npt._SupportsArray, np.arange(10)),
    +    "_SupportsArrayFunc": (_npt._SupportsArrayFunc, np.arange(10)),
    +    "_NestedSequence": (_npt._NestedSequence, [1]),
    +}
    +
    +
    +@pytest.mark.parametrize("cls,obj", PROTOCOLS.values(), ids=PROTOCOLS.keys())
    +class TestRuntimeProtocol:
    +    def test_isinstance(self, cls: type[Any], obj: object) -> None:
    +        assert isinstance(obj, cls)
    +        assert not isinstance(None, cls)
    +
    +    def test_issubclass(self, cls: type[Any], obj: object) -> None:
    +        assert issubclass(type(obj), cls)
    +        assert not issubclass(type(None), cls)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/test_typing.py b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/test_typing.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..ca4cf37fec3b6e151b2396af6f1532cdf1dd49c7
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/numpy/typing/tests/test_typing.py
    @@ -0,0 +1,205 @@
    +import importlib.util
    +import os
    +import re
    +import shutil
    +import textwrap
    +from collections import defaultdict
    +from typing import TYPE_CHECKING
    +
    +import pytest
    +
    +# Only trigger a full `mypy` run if this environment variable is set
    +# Note that these tests tend to take over a minute even on a macOS M1 CPU,
    +# and more than that in CI.
    +RUN_MYPY = "NPY_RUN_MYPY_IN_TESTSUITE" in os.environ
    +if RUN_MYPY and RUN_MYPY not in ('0', '', 'false'):
    +    RUN_MYPY = True
    +
    +# Skips all functions in this file
    +pytestmark = pytest.mark.skipif(
    +    not RUN_MYPY,
    +    reason="`NPY_RUN_MYPY_IN_TESTSUITE` not set"
    +)
    +
    +
    +try:
    +    from mypy import api
    +except ImportError:
    +    NO_MYPY = True
    +else:
    +    NO_MYPY = False
    +
    +if TYPE_CHECKING:
    +    from collections.abc import Iterator
    +
    +    # We need this as annotation, but it's located in a private namespace.
    +    # As a compromise, do *not* import it during runtime
    +    from _pytest.mark.structures import ParameterSet
    +
    +DATA_DIR = os.path.join(os.path.dirname(__file__), "data")
    +PASS_DIR = os.path.join(DATA_DIR, "pass")
    +FAIL_DIR = os.path.join(DATA_DIR, "fail")
    +REVEAL_DIR = os.path.join(DATA_DIR, "reveal")
    +MISC_DIR = os.path.join(DATA_DIR, "misc")
    +MYPY_INI = os.path.join(DATA_DIR, "mypy.ini")
    +CACHE_DIR = os.path.join(DATA_DIR, ".mypy_cache")
    +
    +#: A dictionary with file names as keys and lists of the mypy stdout as values.
    +#: To-be populated by `run_mypy`.
    +OUTPUT_MYPY: defaultdict[str, list[str]] = defaultdict(list)
    +
    +
    +def _key_func(key: str) -> str:
    +    """Split at the first occurrence of the ``:`` character.
    +
    +    Windows drive-letters (*e.g.* ``C:``) are ignored herein.
    +    """
    +    drive, tail = os.path.splitdrive(key)
    +    return os.path.join(drive, tail.split(":", 1)[0])
    +
    +
    +def _strip_filename(msg: str) -> tuple[int, str]:
    +    """Strip the filename and line number from a mypy message."""
    +    _, tail = os.path.splitdrive(msg)
    +    _, lineno, msg = tail.split(":", 2)
    +    return int(lineno), msg.strip()
    +
    +
    +def strip_func(match: re.Match[str]) -> str:
    +    """`re.sub` helper function for stripping module names."""
    +    return match.groups()[1]
    +
    +
    +@pytest.fixture(scope="module", autouse=True)
    +def run_mypy() -> None:
    +    """Clears the cache and run mypy before running any of the typing tests.
    +
    +    The mypy results are cached in `OUTPUT_MYPY` for further use.
    +
    +    The cache refresh can be skipped using
    +
    +    NUMPY_TYPING_TEST_CLEAR_CACHE=0 pytest numpy/typing/tests
    +    """
    +    if (
    +        os.path.isdir(CACHE_DIR)
    +        and bool(os.environ.get("NUMPY_TYPING_TEST_CLEAR_CACHE", True))  # noqa: PLW1508
    +    ):
    +        shutil.rmtree(CACHE_DIR)
    +
    +    split_pattern = re.compile(r"(\s+)?\^(\~+)?")
    +    for directory in (PASS_DIR, REVEAL_DIR, FAIL_DIR, MISC_DIR):
    +        # Run mypy
    +        stdout, stderr, exit_code = api.run([
    +            "--config-file",
    +            MYPY_INI,
    +            "--cache-dir",
    +            CACHE_DIR,
    +            directory,
    +        ])
    +        if stderr:
    +            pytest.fail(f"Unexpected mypy standard error\n\n{stderr}", False)
    +        elif exit_code not in {0, 1}:
    +            pytest.fail(f"Unexpected mypy exit code: {exit_code}\n\n{stdout}", False)
    +
    +        str_concat = ""
    +        filename: str | None = None
    +        for i in stdout.split("\n"):
    +            if "note:" in i:
    +                continue
    +            if filename is None:
    +                filename = _key_func(i)
    +
    +            str_concat += f"{i}\n"
    +            if split_pattern.match(i) is not None:
    +                OUTPUT_MYPY[filename].append(str_concat)
    +                str_concat = ""
    +                filename = None
    +
    +
    +def get_test_cases(*directories: str) -> "Iterator[ParameterSet]":
    +    for directory in directories:
    +        for root, _, files in os.walk(directory):
    +            for fname in files:
    +                short_fname, ext = os.path.splitext(fname)
    +                if ext not in (".pyi", ".py"):
    +                    continue
    +
    +                fullpath = os.path.join(root, fname)
    +                yield pytest.param(fullpath, id=short_fname)
    +
    +
    +_FAIL_INDENT = " " * 4
    +_FAIL_SEP = "\n" + "_" * 79 + "\n\n"
    +
    +_FAIL_MSG_REVEAL = """{}:{} - reveal mismatch:
    +
    +{}"""
    +
    +
    +@pytest.mark.slow
    +@pytest.mark.skipif(NO_MYPY, reason="Mypy is not installed")
    +@pytest.mark.parametrize("path", get_test_cases(PASS_DIR, FAIL_DIR))
    +def test_pass(path) -> None:
    +    # Alias `OUTPUT_MYPY` so that it appears in the local namespace
    +    output_mypy = OUTPUT_MYPY
    +
    +    if path not in output_mypy:
    +        return
    +
    +    relpath = os.path.relpath(path)
    +
    +    # collect any reported errors, and clean up the output
    +    messages = []
    +    for message in output_mypy[path]:
    +        lineno, content = _strip_filename(message)
    +        content = content.removeprefix("error:").lstrip()
    +        messages.append(f"{relpath}:{lineno} - {content}")
    +
    +    if messages:
    +        pytest.fail("\n".join(messages), pytrace=False)
    +
    +
    +@pytest.mark.slow
    +@pytest.mark.skipif(NO_MYPY, reason="Mypy is not installed")
    +@pytest.mark.parametrize("path", get_test_cases(REVEAL_DIR))
    +def test_reveal(path: str) -> None:
    +    """Validate that mypy correctly infers the return-types of
    +    the expressions in `path`.
    +    """
    +    __tracebackhide__ = True
    +
    +    output_mypy = OUTPUT_MYPY
    +    if path not in output_mypy:
    +        return
    +
    +    relpath = os.path.relpath(path)
    +
    +    # collect any reported errors, and clean up the output
    +    failures = []
    +    for error_line in output_mypy[path]:
    +        lineno, error_msg = _strip_filename(error_line)
    +        error_msg = textwrap.indent(error_msg, _FAIL_INDENT)
    +        reason = _FAIL_MSG_REVEAL.format(relpath, lineno, error_msg)
    +        failures.append(reason)
    +
    +    if failures:
    +        reasons = _FAIL_SEP.join(failures)
    +        pytest.fail(reasons, pytrace=False)
    +
    +
    +@pytest.mark.slow
    +@pytest.mark.skipif(NO_MYPY, reason="Mypy is not installed")
    +@pytest.mark.parametrize("path", get_test_cases(PASS_DIR))
    +def test_code_runs(path: str) -> None:
    +    """Validate that the code in `path` properly during runtime."""
    +    path_without_extension, _ = os.path.splitext(path)
    +    dirname, filename = path.split(os.sep)[-2:]
    +
    +    spec = importlib.util.spec_from_file_location(
    +        f"{dirname}.{filename}", path
    +    )
    +    assert spec is not None
    +    assert spec.loader is not None
    +
    +    test_module = importlib.util.module_from_spec(spec)
    +    spec.loader.exec_module(test_module)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/__init__.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..a6bdf59cd713d41593ba3da47032a831a1be1504
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/__init__.py
    @@ -0,0 +1,15 @@
    +# This file is dual licensed under the terms of the Apache License, Version
    +# 2.0, and the BSD License. See the LICENSE file in the root of this repository
    +# for complete details.
    +
    +__title__ = "packaging"
    +__summary__ = "Core utilities for Python packages"
    +__uri__ = "https://github.com/pypa/packaging"
    +
    +__version__ = "26.2"
    +
    +__author__ = "Donald Stufft and individual contributors"
    +__email__ = "donald@stufft.io"
    +
    +__license__ = "BSD-2-Clause or Apache-2.0"
    +__copyright__ = f"2014 {__author__}"
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/_elffile.py b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/_elffile.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..497b0645217512ae2ba8ff61341fd2bbfa3648cd
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/_elffile.py
    @@ -0,0 +1,108 @@
    +"""
    +ELF file parser.
    +
    +This provides a class ``ELFFile`` that parses an ELF executable in a similar
    +interface to ``ZipFile``. Only the read interface is implemented.
    +
    +ELF header: https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html
    +"""
    +
    +from __future__ import annotations
    +
    +import enum
    +import os
    +import struct
    +from typing import IO
    +
    +
    +class ELFInvalid(ValueError):
    +    pass
    +
    +
    +class EIClass(enum.IntEnum):
    +    C32 = 1
    +    C64 = 2
    +
    +
    +class EIData(enum.IntEnum):
    +    Lsb = 1
    +    Msb = 2
    +
    +
    +class EMachine(enum.IntEnum):
    +    I386 = 3
    +    S390 = 22
    +    Arm = 40
    +    X8664 = 62
    +    AArc64 = 183
    +
    +
    +class ELFFile:
    +    """
    +    Representation of an ELF executable.
    +    """
    +
    +    def __init__(self, f: IO[bytes]) -> None:
    +        self._f = f
    +
    +        try:
    +            ident = self._read("16B")
    +        except struct.error as e:
    +            raise ELFInvalid("unable to parse identification") from e
    +        magic = bytes(ident[:4])
    +        if magic != b"\x7fELF":
    +            raise ELFInvalid(f"invalid magic: {magic!r}")
    +
    +        self.capacity = ident[4]  # Format for program header (bitness).
    +        self.encoding = ident[5]  # Data structure encoding (endianness).
    +
    +        try:
    +            # e_fmt: Format for program header.
    +            # p_fmt: Format for section header.
    +            # p_idx: Indexes to find p_type, p_offset, and p_filesz.
    +            e_fmt, self._p_fmt, self._p_idx = {
    +                (1, 1): ("HHIIIIIHHH", ">IIIIIIII", (0, 1, 4)),  # 32-bit MSB.
    +                (2, 1): ("HHIQQQIHHH", ">IIQQQQQQ", (0, 2, 5)),  # 64-bit MSB.
    +            }[(self.capacity, self.encoding)]
    +        except KeyError as e:
    +            raise ELFInvalid(
    +                f"unrecognized capacity ({self.capacity}) or encoding ({self.encoding})"
    +            ) from e
    +
    +        try:
    +            (
    +                _,
    +                self.machine,  # Architecture type.
    +                _,
    +                _,
    +                self._e_phoff,  # Offset of program header.
    +                _,
    +                self.flags,  # Processor-specific flags.
    +                _,
    +                self._e_phentsize,  # Size of section.
    +                self._e_phnum,  # Number of sections.
    +            ) = self._read(e_fmt)
    +        except struct.error as e:
    +            raise ELFInvalid("unable to parse machine and section information") from e
    +
    +    def _read(self, fmt: str) -> tuple[int, ...]:
    +        return struct.unpack(fmt, self._f.read(struct.calcsize(fmt)))
    +
    +    @property
    +    def interpreter(self) -> str | None:
    +        """
    +        The path recorded in the ``PT_INTERP`` section header.
    +        """
    +        for index in range(self._e_phnum):
    +            self._f.seek(self._e_phoff + self._e_phentsize * index)
    +            try:
    +                data = self._read(self._p_fmt)
    +            except struct.error:
    +                continue
    +            if data[self._p_idx[0]] != 3:  # Not PT_INTERP.
    +                continue
    +            self._f.seek(data[self._p_idx[1]])
    +            return os.fsdecode(self._f.read(data[self._p_idx[2]])).strip("\0")
    +        return None
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/_manylinux.py b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/_manylinux.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..0e79e8a882be74fe76c80ccf49a9cd68fb636fd4
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/_manylinux.py
    @@ -0,0 +1,262 @@
    +from __future__ import annotations
    +
    +import collections
    +import contextlib
    +import functools
    +import os
    +import re
    +import sys
    +import warnings
    +from typing import Generator, Iterator, NamedTuple, Sequence
    +
    +from ._elffile import EIClass, EIData, ELFFile, EMachine
    +
    +EF_ARM_ABIMASK = 0xFF000000
    +EF_ARM_ABI_VER5 = 0x05000000
    +EF_ARM_ABI_FLOAT_HARD = 0x00000400
    +
    +_ALLOWED_ARCHS = {
    +    "x86_64",
    +    "aarch64",
    +    "ppc64",
    +    "ppc64le",
    +    "s390x",
    +    "loongarch64",
    +    "riscv64",
    +}
    +
    +
    +# `os.PathLike` not a generic type until Python 3.9, so sticking with `str`
    +# as the type for `path` until then.
    +@contextlib.contextmanager
    +def _parse_elf(path: str) -> Generator[ELFFile | None, None, None]:
    +    try:
    +        with open(path, "rb") as f:
    +            yield ELFFile(f)
    +    except (OSError, TypeError, ValueError):
    +        yield None
    +
    +
    +def _is_linux_armhf(executable: str) -> bool:
    +    # hard-float ABI can be detected from the ELF header of the running
    +    # process
    +    # https://static.docs.arm.com/ihi0044/g/aaelf32.pdf
    +    with _parse_elf(executable) as f:
    +        return (
    +            f is not None
    +            and f.capacity == EIClass.C32
    +            and f.encoding == EIData.Lsb
    +            and f.machine == EMachine.Arm
    +            and f.flags & EF_ARM_ABIMASK == EF_ARM_ABI_VER5
    +            and f.flags & EF_ARM_ABI_FLOAT_HARD == EF_ARM_ABI_FLOAT_HARD
    +        )
    +
    +
    +def _is_linux_i686(executable: str) -> bool:
    +    with _parse_elf(executable) as f:
    +        return (
    +            f is not None
    +            and f.capacity == EIClass.C32
    +            and f.encoding == EIData.Lsb
    +            and f.machine == EMachine.I386
    +        )
    +
    +
    +def _have_compatible_abi(executable: str, archs: Sequence[str]) -> bool:
    +    if "armv7l" in archs:
    +        return _is_linux_armhf(executable)
    +    if "i686" in archs:
    +        return _is_linux_i686(executable)
    +    return any(arch in _ALLOWED_ARCHS for arch in archs)
    +
    +
    +# If glibc ever changes its major version, we need to know what the last
    +# minor version was, so we can build the complete list of all versions.
    +# For now, guess what the highest minor version might be, assume it will
    +# be 50 for testing. Once this actually happens, update the dictionary
    +# with the actual value.
    +_LAST_GLIBC_MINOR: dict[int, int] = collections.defaultdict(lambda: 50)
    +
    +
    +class _GLibCVersion(NamedTuple):
    +    major: int
    +    minor: int
    +
    +
    +def _glibc_version_string_confstr() -> str | None:
    +    """
    +    Primary implementation of glibc_version_string using os.confstr.
    +    """
    +    # os.confstr is quite a bit faster than ctypes.DLL. It's also less likely
    +    # to be broken or missing. This strategy is used in the standard library
    +    # platform module.
    +    # https://github.com/python/cpython/blob/fcf1d003bf4f0100c/Lib/platform.py#L175-L183
    +    try:
    +        # Should be a string like "glibc 2.17".
    +        version_string: str | None = os.confstr("CS_GNU_LIBC_VERSION")
    +        assert version_string is not None
    +        _, version = version_string.rsplit()
    +    except (AssertionError, AttributeError, OSError, ValueError):
    +        # os.confstr() or CS_GNU_LIBC_VERSION not available (or a bad value)...
    +        return None
    +    return version
    +
    +
    +def _glibc_version_string_ctypes() -> str | None:
    +    """
    +    Fallback implementation of glibc_version_string using ctypes.
    +    """
    +    try:
    +        import ctypes  # noqa: PLC0415
    +    except ImportError:
    +        return None
    +
    +    # ctypes.CDLL(None) internally calls dlopen(NULL), and as the dlopen
    +    # manpage says, "If filename is NULL, then the returned handle is for the
    +    # main program". This way we can let the linker do the work to figure out
    +    # which libc our process is actually using.
    +    #
    +    # We must also handle the special case where the executable is not a
    +    # dynamically linked executable. This can occur when using musl libc,
    +    # for example. In this situation, dlopen() will error, leading to an
    +    # OSError. Interestingly, at least in the case of musl, there is no
    +    # errno set on the OSError. The single string argument used to construct
    +    # OSError comes from libc itself and is therefore not portable to
    +    # hard code here. In any case, failure to call dlopen() means we
    +    # can proceed, so we bail on our attempt.
    +    try:
    +        process_namespace = ctypes.CDLL(None)
    +    except OSError:
    +        return None
    +
    +    try:
    +        gnu_get_libc_version = process_namespace.gnu_get_libc_version
    +    except AttributeError:
    +        # Symbol doesn't exist -> therefore, we are not linked to
    +        # glibc.
    +        return None
    +
    +    # Call gnu_get_libc_version, which returns a string like "2.5"
    +    gnu_get_libc_version.restype = ctypes.c_char_p
    +    version_str: str = gnu_get_libc_version()
    +    # py2 / py3 compatibility:
    +    if not isinstance(version_str, str):
    +        version_str = version_str.decode("ascii")
    +
    +    return version_str
    +
    +
    +def _glibc_version_string() -> str | None:
    +    """Returns glibc version string, or None if not using glibc."""
    +    return _glibc_version_string_confstr() or _glibc_version_string_ctypes()
    +
    +
    +def _parse_glibc_version(version_str: str) -> _GLibCVersion:
    +    """Parse glibc version.
    +
    +    We use a regexp instead of str.split because we want to discard any
    +    random junk that might come after the minor version -- this might happen
    +    in patched/forked versions of glibc (e.g. Linaro's version of glibc
    +    uses version strings like "2.20-2014.11"). See gh-3588.
    +    """
    +    m = re.match(r"(?P[0-9]+)\.(?P[0-9]+)", version_str)
    +    if not m:
    +        warnings.warn(
    +            f"Expected glibc version with 2 components major.minor, got: {version_str}",
    +            RuntimeWarning,
    +            stacklevel=2,
    +        )
    +        return _GLibCVersion(-1, -1)
    +    return _GLibCVersion(int(m.group("major")), int(m.group("minor")))
    +
    +
    +@functools.lru_cache
    +def _get_glibc_version() -> _GLibCVersion:
    +    version_str = _glibc_version_string()
    +    if version_str is None:
    +        return _GLibCVersion(-1, -1)
    +    return _parse_glibc_version(version_str)
    +
    +
    +# From PEP 513, PEP 600
    +def _is_compatible(arch: str, version: _GLibCVersion) -> bool:
    +    sys_glibc = _get_glibc_version()
    +    if sys_glibc < version:
    +        return False
    +    # Check for presence of _manylinux module.
    +    try:
    +        import _manylinux  # noqa: PLC0415
    +    except ImportError:
    +        return True
    +    if hasattr(_manylinux, "manylinux_compatible"):
    +        result = _manylinux.manylinux_compatible(version[0], version[1], arch)
    +        if result is not None:
    +            return bool(result)
    +        return True
    +    if version == _GLibCVersion(2, 5) and hasattr(_manylinux, "manylinux1_compatible"):
    +        return bool(_manylinux.manylinux1_compatible)
    +    if version == _GLibCVersion(2, 12) and hasattr(
    +        _manylinux, "manylinux2010_compatible"
    +    ):
    +        return bool(_manylinux.manylinux2010_compatible)
    +    if version == _GLibCVersion(2, 17) and hasattr(
    +        _manylinux, "manylinux2014_compatible"
    +    ):
    +        return bool(_manylinux.manylinux2014_compatible)
    +    return True
    +
    +
    +_LEGACY_MANYLINUX_MAP: dict[_GLibCVersion, str] = {
    +    # CentOS 7 w/ glibc 2.17 (PEP 599)
    +    _GLibCVersion(2, 17): "manylinux2014",
    +    # CentOS 6 w/ glibc 2.12 (PEP 571)
    +    _GLibCVersion(2, 12): "manylinux2010",
    +    # CentOS 5 w/ glibc 2.5 (PEP 513)
    +    _GLibCVersion(2, 5): "manylinux1",
    +}
    +
    +
    +def platform_tags(archs: Sequence[str]) -> Iterator[str]:
    +    """Generate manylinux tags compatible to the current platform.
    +
    +    :param archs: Sequence of compatible architectures.
    +        The first one shall be the closest to the actual architecture and be the part of
    +        platform tag after the ``linux_`` prefix, e.g. ``x86_64``.
    +        The ``linux_`` prefix is assumed as a prerequisite for the current platform to
    +        be manylinux-compatible.
    +
    +    :returns: An iterator of compatible manylinux tags.
    +    """
    +    if not _have_compatible_abi(sys.executable, archs):
    +        return
    +    # Oldest glibc to be supported regardless of architecture is (2, 17).
    +    too_old_glibc2 = _GLibCVersion(2, 16)
    +    if set(archs) & {"x86_64", "i686"}:
    +        # On x86/i686 also oldest glibc to be supported is (2, 5).
    +        too_old_glibc2 = _GLibCVersion(2, 4)
    +    current_glibc = _GLibCVersion(*_get_glibc_version())
    +    glibc_max_list = [current_glibc]
    +    # We can assume compatibility across glibc major versions.
    +    # https://sourceware.org/bugzilla/show_bug.cgi?id=24636
    +    #
    +    # Build a list of maximum glibc versions so that we can
    +    # output the canonical list of all glibc from current_glibc
    +    # down to too_old_glibc2, including all intermediary versions.
    +    for glibc_major in range(current_glibc.major - 1, 1, -1):
    +        glibc_minor = _LAST_GLIBC_MINOR[glibc_major]
    +        glibc_max_list.append(_GLibCVersion(glibc_major, glibc_minor))
    +    for arch in archs:
    +        for glibc_max in glibc_max_list:
    +            if glibc_max.major == too_old_glibc2.major:
    +                min_minor = too_old_glibc2.minor
    +            else:
    +                # For other glibc major versions oldest supported is (x, 0).
    +                min_minor = -1
    +            for glibc_minor in range(glibc_max.minor, min_minor, -1):
    +                glibc_version = _GLibCVersion(glibc_max.major, glibc_minor)
    +                if _is_compatible(arch, glibc_version):
    +                    yield "manylinux_{}_{}_{}".format(*glibc_version, arch)
    +
    +                    # Handle the legacy manylinux1, manylinux2010, manylinux2014 tags.
    +                    if legacy_tag := _LEGACY_MANYLINUX_MAP.get(glibc_version):
    +                        yield f"{legacy_tag}_{arch}"
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/_musllinux.py b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/_musllinux.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..4e8116a79ca80d60657542a23b4bbcbc3c518eaf
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/_musllinux.py
    @@ -0,0 +1,85 @@
    +"""PEP 656 support.
    +
    +This module implements logic to detect if the currently running Python is
    +linked against musl, and what musl version is used.
    +"""
    +
    +from __future__ import annotations
    +
    +import functools
    +import re
    +import subprocess
    +import sys
    +from typing import Iterator, NamedTuple, Sequence
    +
    +from ._elffile import ELFFile
    +
    +
    +class _MuslVersion(NamedTuple):
    +    major: int
    +    minor: int
    +
    +
    +def _parse_musl_version(output: str) -> _MuslVersion | None:
    +    lines = [n for n in (n.strip() for n in output.splitlines()) if n]
    +    if len(lines) < 2 or lines[0][:4] != "musl":
    +        return None
    +    m = re.match(r"Version (\d+)\.(\d+)", lines[1])
    +    if not m:
    +        return None
    +    return _MuslVersion(major=int(m.group(1)), minor=int(m.group(2)))
    +
    +
    +@functools.lru_cache
    +def _get_musl_version(executable: str) -> _MuslVersion | None:
    +    """Detect currently-running musl runtime version.
    +
    +    This is done by checking the specified executable's dynamic linking
    +    information, and invoking the loader to parse its output for a version
    +    string. If the loader is musl, the output would be something like::
    +
    +        musl libc (x86_64)
    +        Version 1.2.2
    +        Dynamic Program Loader
    +    """
    +    try:
    +        with open(executable, "rb") as f:
    +            ld = ELFFile(f).interpreter
    +    except (OSError, TypeError, ValueError):
    +        return None
    +    if ld is None or "musl" not in ld:
    +        return None
    +    proc = subprocess.run([ld], check=False, stderr=subprocess.PIPE, text=True)
    +    return _parse_musl_version(proc.stderr)
    +
    +
    +def platform_tags(archs: Sequence[str]) -> Iterator[str]:
    +    """Generate musllinux tags compatible to the current platform.
    +
    +    :param archs: Sequence of compatible architectures.
    +        The first one shall be the closest to the actual architecture and be the part of
    +        platform tag after the ``linux_`` prefix, e.g. ``x86_64``.
    +        The ``linux_`` prefix is assumed as a prerequisite for the current platform to
    +        be musllinux-compatible.
    +
    +    :returns: An iterator of compatible musllinux tags.
    +    """
    +    sys_musl = _get_musl_version(sys.executable)
    +    if sys_musl is None:  # Python not dynamically linked against musl.
    +        return
    +    for arch in archs:
    +        for minor in range(sys_musl.minor, -1, -1):
    +            yield f"musllinux_{sys_musl.major}_{minor}_{arch}"
    +
    +
    +if __name__ == "__main__":  # pragma: no cover
    +    import sysconfig
    +
    +    plat = sysconfig.get_platform()
    +    assert plat.startswith("linux-"), "not linux"
    +
    +    print("plat:", plat)
    +    print("musl:", _get_musl_version(sys.executable))
    +    print("tags:", end=" ")
    +    for t in platform_tags(re.sub(r"[.-]", "_", plat.split("-", 1)[-1])):
    +        print(t, end="\n      ")
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/_parser.py b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/_parser.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..d320269e1531d822003bde7a826ba24eecca3bae
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/_parser.py
    @@ -0,0 +1,393 @@
    +"""Handwritten parser of dependency specifiers.
    +
    +The docstring for each __parse_* function contains EBNF-inspired grammar representing
    +the implementation.
    +"""
    +
    +from __future__ import annotations
    +
    +import ast
    +from typing import List, Literal, NamedTuple, Sequence, Tuple, Union
    +
    +from ._tokenizer import DEFAULT_RULES, Tokenizer
    +
    +
    +class Node:
    +    __slots__ = ("value",)
    +
    +    def __init__(self, value: str) -> None:
    +        self.value = value
    +
    +    def __str__(self) -> str:
    +        return self.value
    +
    +    def __repr__(self) -> str:
    +        return f"<{self.__class__.__name__}({self.value!r})>"
    +
    +    def serialize(self) -> str:
    +        raise NotImplementedError
    +
    +    def __getstate__(self) -> str:
    +        # Return just the value string for compactness and stability.
    +        return self.value
    +
    +    def _restore_value(self, value: object) -> None:
    +        if not isinstance(value, str):
    +            raise TypeError(
    +                f"Cannot restore {self.__class__.__name__} value from {value!r}"
    +            )
    +        self.value = value
    +
    +    def __setstate__(self, state: object) -> None:
    +        if isinstance(state, str):
    +            # New format (26.2+): just the value string.
    +            self._restore_value(state)
    +            return
    +        if isinstance(state, tuple) and len(state) == 2:
    +            # Old format (packaging <= 26.0, __slots__): (None, {slot: value}).
    +            _, slot_dict = state
    +            if isinstance(slot_dict, dict) and "value" in slot_dict:
    +                self._restore_value(slot_dict["value"])
    +                return
    +        if isinstance(state, dict) and "value" in state:
    +            # Old format (packaging <= 25.0, no __slots__): plain __dict__.
    +            self._restore_value(state["value"])
    +            return
    +        raise TypeError(f"Cannot restore {self.__class__.__name__} from {state!r}")
    +
    +
    +class Variable(Node):
    +    __slots__ = ()
    +
    +    def serialize(self) -> str:
    +        return str(self)
    +
    +
    +class Value(Node):
    +    __slots__ = ()
    +
    +    def serialize(self) -> str:
    +        return f'"{self}"'
    +
    +
    +class Op(Node):
    +    __slots__ = ()
    +
    +    def serialize(self) -> str:
    +        return str(self)
    +
    +
    +MarkerLogical = Literal["and", "or"]
    +MarkerVar = Union[Variable, Value]
    +MarkerItem = Tuple[MarkerVar, Op, MarkerVar]
    +MarkerAtom = Union[MarkerItem, Sequence["MarkerAtom"]]
    +MarkerList = List[Union["MarkerList", MarkerAtom, MarkerLogical]]
    +
    +
    +class ParsedRequirement(NamedTuple):
    +    name: str
    +    url: str
    +    extras: list[str]
    +    specifier: str
    +    marker: MarkerList | None
    +
    +
    +# --------------------------------------------------------------------------------------
    +# Recursive descent parser for dependency specifier
    +# --------------------------------------------------------------------------------------
    +def parse_requirement(source: str) -> ParsedRequirement:
    +    return _parse_requirement(Tokenizer(source, rules=DEFAULT_RULES))
    +
    +
    +def _parse_requirement(tokenizer: Tokenizer) -> ParsedRequirement:
    +    """
    +    requirement = WS? IDENTIFIER WS? extras WS? requirement_details
    +    """
    +    tokenizer.consume("WS")
    +
    +    name_token = tokenizer.expect(
    +        "IDENTIFIER", expected="package name at the start of dependency specifier"
    +    )
    +    name = name_token.text
    +    tokenizer.consume("WS")
    +
    +    extras = _parse_extras(tokenizer)
    +    tokenizer.consume("WS")
    +
    +    url, specifier, marker = _parse_requirement_details(tokenizer)
    +    tokenizer.expect("END", expected="end of dependency specifier")
    +
    +    return ParsedRequirement(name, url, extras, specifier, marker)
    +
    +
    +def _parse_requirement_details(
    +    tokenizer: Tokenizer,
    +) -> tuple[str, str, MarkerList | None]:
    +    """
    +    requirement_details = AT URL (WS requirement_marker?)?
    +                        | specifier WS? (requirement_marker)?
    +    """
    +
    +    specifier = ""
    +    url = ""
    +    marker = None
    +
    +    if tokenizer.check("AT"):
    +        tokenizer.read()
    +        tokenizer.consume("WS")
    +
    +        url_start = tokenizer.position
    +        url = tokenizer.expect("URL", expected="URL after @").text
    +        if tokenizer.check("END", peek=True):
    +            return (url, specifier, marker)
    +
    +        tokenizer.expect("WS", expected="whitespace after URL")
    +
    +        # The input might end after whitespace.
    +        if tokenizer.check("END", peek=True):
    +            return (url, specifier, marker)
    +
    +        marker = _parse_requirement_marker(
    +            tokenizer,
    +            span_start=url_start,
    +            expected="semicolon (after URL and whitespace)",
    +        )
    +    else:
    +        specifier_start = tokenizer.position
    +        specifier = _parse_specifier(tokenizer)
    +        tokenizer.consume("WS")
    +
    +        if tokenizer.check("END", peek=True):
    +            return (url, specifier, marker)
    +
    +        marker = _parse_requirement_marker(
    +            tokenizer,
    +            span_start=specifier_start,
    +            expected=(
    +                "comma (within version specifier), semicolon (after version specifier)"
    +                if specifier
    +                else "semicolon (after name with no version specifier)"
    +            ),
    +        )
    +
    +    return (url, specifier, marker)
    +
    +
    +def _parse_requirement_marker(
    +    tokenizer: Tokenizer, *, span_start: int, expected: str
    +) -> MarkerList:
    +    """
    +    requirement_marker = SEMICOLON marker WS?
    +    """
    +
    +    if not tokenizer.check("SEMICOLON"):
    +        tokenizer.raise_syntax_error(
    +            f"Expected {expected} or end",
    +            span_start=span_start,
    +            span_end=None,
    +        )
    +    tokenizer.read()
    +
    +    marker = _parse_marker(tokenizer)
    +    tokenizer.consume("WS")
    +
    +    return marker
    +
    +
    +def _parse_extras(tokenizer: Tokenizer) -> list[str]:
    +    """
    +    extras = (LEFT_BRACKET wsp* extras_list? wsp* RIGHT_BRACKET)?
    +    """
    +    if not tokenizer.check("LEFT_BRACKET", peek=True):
    +        return []
    +
    +    with tokenizer.enclosing_tokens(
    +        "LEFT_BRACKET",
    +        "RIGHT_BRACKET",
    +        around="extras",
    +    ):
    +        tokenizer.consume("WS")
    +        extras = _parse_extras_list(tokenizer)
    +        tokenizer.consume("WS")
    +
    +    return extras
    +
    +
    +def _parse_extras_list(tokenizer: Tokenizer) -> list[str]:
    +    """
    +    extras_list = identifier (wsp* ',' wsp* identifier)*
    +    """
    +    extras: list[str] = []
    +
    +    if not tokenizer.check("IDENTIFIER"):
    +        return extras
    +
    +    extras.append(tokenizer.read().text)
    +
    +    while True:
    +        tokenizer.consume("WS")
    +        if tokenizer.check("IDENTIFIER", peek=True):
    +            tokenizer.raise_syntax_error("Expected comma between extra names")
    +        elif not tokenizer.check("COMMA"):
    +            break
    +
    +        tokenizer.read()
    +        tokenizer.consume("WS")
    +
    +        extra_token = tokenizer.expect("IDENTIFIER", expected="extra name after comma")
    +        extras.append(extra_token.text)
    +
    +    return extras
    +
    +
    +def _parse_specifier(tokenizer: Tokenizer) -> str:
    +    """
    +    specifier = LEFT_PARENTHESIS WS? version_many WS? RIGHT_PARENTHESIS
    +              | WS? version_many WS?
    +    """
    +    with tokenizer.enclosing_tokens(
    +        "LEFT_PARENTHESIS",
    +        "RIGHT_PARENTHESIS",
    +        around="version specifier",
    +    ):
    +        tokenizer.consume("WS")
    +        parsed_specifiers = _parse_version_many(tokenizer)
    +        tokenizer.consume("WS")
    +
    +    return parsed_specifiers
    +
    +
    +def _parse_version_many(tokenizer: Tokenizer) -> str:
    +    """
    +    version_many = (SPECIFIER (WS? COMMA WS? SPECIFIER)*)?
    +    """
    +    parsed_specifiers = ""
    +    while tokenizer.check("SPECIFIER"):
    +        span_start = tokenizer.position
    +        parsed_specifiers += tokenizer.read().text
    +        if tokenizer.check("VERSION_PREFIX_TRAIL", peek=True):
    +            tokenizer.raise_syntax_error(
    +                ".* suffix can only be used with `==` or `!=` operators",
    +                span_start=span_start,
    +                span_end=tokenizer.position + 1,
    +            )
    +        if tokenizer.check("VERSION_LOCAL_LABEL_TRAIL", peek=True):
    +            tokenizer.raise_syntax_error(
    +                "Local version label can only be used with `==` or `!=` operators",
    +                span_start=span_start,
    +                span_end=tokenizer.position,
    +            )
    +        tokenizer.consume("WS")
    +        if not tokenizer.check("COMMA"):
    +            break
    +        parsed_specifiers += tokenizer.read().text
    +        tokenizer.consume("WS")
    +
    +    return parsed_specifiers
    +
    +
    +# --------------------------------------------------------------------------------------
    +# Recursive descent parser for marker expression
    +# --------------------------------------------------------------------------------------
    +def parse_marker(source: str) -> MarkerList:
    +    return _parse_full_marker(Tokenizer(source, rules=DEFAULT_RULES))
    +
    +
    +def _parse_full_marker(tokenizer: Tokenizer) -> MarkerList:
    +    retval = _parse_marker(tokenizer)
    +    tokenizer.expect("END", expected="end of marker expression")
    +    return retval
    +
    +
    +def _parse_marker(tokenizer: Tokenizer) -> MarkerList:
    +    """
    +    marker = marker_atom (BOOLOP marker_atom)+
    +    """
    +    expression = [_parse_marker_atom(tokenizer)]
    +    while tokenizer.check("BOOLOP"):
    +        token = tokenizer.read()
    +        expr_right = _parse_marker_atom(tokenizer)
    +        expression.extend((token.text, expr_right))
    +    return expression
    +
    +
    +def _parse_marker_atom(tokenizer: Tokenizer) -> MarkerAtom:
    +    """
    +    marker_atom = WS? LEFT_PARENTHESIS WS? marker WS? RIGHT_PARENTHESIS WS?
    +                | WS? marker_item WS?
    +    """
    +
    +    tokenizer.consume("WS")
    +    if tokenizer.check("LEFT_PARENTHESIS", peek=True):
    +        with tokenizer.enclosing_tokens(
    +            "LEFT_PARENTHESIS",
    +            "RIGHT_PARENTHESIS",
    +            around="marker expression",
    +        ):
    +            tokenizer.consume("WS")
    +            marker: MarkerAtom = _parse_marker(tokenizer)
    +            tokenizer.consume("WS")
    +    else:
    +        marker = _parse_marker_item(tokenizer)
    +    tokenizer.consume("WS")
    +    return marker
    +
    +
    +def _parse_marker_item(tokenizer: Tokenizer) -> MarkerItem:
    +    """
    +    marker_item = WS? marker_var WS? marker_op WS? marker_var WS?
    +    """
    +    tokenizer.consume("WS")
    +    marker_var_left = _parse_marker_var(tokenizer)
    +    tokenizer.consume("WS")
    +    marker_op = _parse_marker_op(tokenizer)
    +    tokenizer.consume("WS")
    +    marker_var_right = _parse_marker_var(tokenizer)
    +    tokenizer.consume("WS")
    +    return (marker_var_left, marker_op, marker_var_right)
    +
    +
    +def _parse_marker_var(tokenizer: Tokenizer) -> MarkerVar:  # noqa: RET503
    +    """
    +    marker_var = VARIABLE | QUOTED_STRING
    +    """
    +    if tokenizer.check("VARIABLE"):
    +        return process_env_var(tokenizer.read().text.replace(".", "_"))
    +    elif tokenizer.check("QUOTED_STRING"):
    +        return process_python_str(tokenizer.read().text)
    +    else:
    +        tokenizer.raise_syntax_error(
    +            message="Expected a marker variable or quoted string"
    +        )
    +
    +
    +def process_env_var(env_var: str) -> Variable:
    +    if env_var in ("platform_python_implementation", "python_implementation"):
    +        return Variable("platform_python_implementation")
    +    else:
    +        return Variable(env_var)
    +
    +
    +def process_python_str(python_str: str) -> Value:
    +    value = ast.literal_eval(python_str)
    +    return Value(str(value))
    +
    +
    +def _parse_marker_op(tokenizer: Tokenizer) -> Op:
    +    """
    +    marker_op = IN | NOT IN | OP
    +    """
    +    if tokenizer.check("IN"):
    +        tokenizer.read()
    +        return Op("in")
    +    elif tokenizer.check("NOT"):
    +        tokenizer.read()
    +        tokenizer.expect("WS", expected="whitespace after 'not'")
    +        tokenizer.expect("IN", expected="'in' after 'not'")
    +        return Op("not in")
    +    elif tokenizer.check("OP"):
    +        return Op(tokenizer.read().text)
    +    else:
    +        return tokenizer.raise_syntax_error(
    +            "Expected marker operator, one of <=, <, !=, ==, >=, >, ~=, ===, in, not in"
    +        )
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/_structures.py b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/_structures.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..4306784d9a26466aa0a210cb2f262903c67d46ca
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/_structures.py
    @@ -0,0 +1,33 @@
    +# This file is dual licensed under the terms of the Apache License, Version
    +# 2.0, and the BSD License. See the LICENSE file in the root of this repository
    +# for complete details.
    +
    +"""Backward-compatibility shim for unpickling Version objects serialized before
    +packaging 26.1.
    +
    +Old pickles reference ``packaging._structures.InfinityType`` and
    +``packaging._structures.NegativeInfinityType``.  This module provides minimal
    +stand-in classes so that ``pickle.loads()`` can resolve those references.
    +The deserialized objects are not used for comparisons — ``Version.__setstate__``
    +discards the stale ``_key`` cache and recomputes it from the core version fields.
    +"""
    +
    +from __future__ import annotations
    +
    +
    +class InfinityType:
    +    """Stand-in for the removed ``InfinityType`` used in old comparison keys."""
    +
    +    def __repr__(self) -> str:
    +        return "Infinity"
    +
    +
    +class NegativeInfinityType:
    +    """Stand-in for the removed ``NegativeInfinityType`` used in old comparison keys."""
    +
    +    def __repr__(self) -> str:
    +        return "-Infinity"
    +
    +
    +Infinity = InfinityType()
    +NegativeInfinity = NegativeInfinityType()
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/_tokenizer.py b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/_tokenizer.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..5ab891ccb8e9048cf64573ca303357efe6afae32
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/_tokenizer.py
    @@ -0,0 +1,193 @@
    +from __future__ import annotations
    +
    +import contextlib
    +import re
    +from dataclasses import dataclass
    +from typing import Generator, Mapping, NoReturn
    +
    +from .specifiers import Specifier
    +
    +
    +@dataclass
    +class Token:
    +    name: str
    +    text: str
    +    position: int
    +
    +
    +class ParserSyntaxError(Exception):
    +    """The provided source text could not be parsed correctly."""
    +
    +    def __init__(
    +        self,
    +        message: str,
    +        *,
    +        source: str,
    +        span: tuple[int, int],
    +    ) -> None:
    +        self.span = span
    +        self.message = message
    +        self.source = source
    +
    +        super().__init__()
    +
    +    def __str__(self) -> str:
    +        marker = " " * self.span[0] + "~" * (self.span[1] - self.span[0]) + "^"
    +        return f"{self.message}\n    {self.source}\n    {marker}"
    +
    +
    +DEFAULT_RULES: dict[str, re.Pattern[str]] = {
    +    "LEFT_PARENTHESIS": re.compile(r"\("),
    +    "RIGHT_PARENTHESIS": re.compile(r"\)"),
    +    "LEFT_BRACKET": re.compile(r"\["),
    +    "RIGHT_BRACKET": re.compile(r"\]"),
    +    "SEMICOLON": re.compile(r";"),
    +    "COMMA": re.compile(r","),
    +    "QUOTED_STRING": re.compile(
    +        r"""
    +            (
    +                ('[^']*')
    +                |
    +                ("[^"]*")
    +            )
    +        """,
    +        re.VERBOSE,
    +    ),
    +    "OP": re.compile(r"(===|==|~=|!=|<=|>=|<|>)"),
    +    "BOOLOP": re.compile(r"\b(or|and)\b"),
    +    "IN": re.compile(r"\bin\b"),
    +    "NOT": re.compile(r"\bnot\b"),
    +    "VARIABLE": re.compile(
    +        r"""
    +            \b(
    +                python_version
    +                |python_full_version
    +                |os[._]name
    +                |sys[._]platform
    +                |platform_(release|system)
    +                |platform[._](version|machine|python_implementation)
    +                |python_implementation
    +                |implementation_(name|version)
    +                |extras?
    +                |dependency_groups
    +            )\b
    +        """,
    +        re.VERBOSE,
    +    ),
    +    "SPECIFIER": re.compile(
    +        Specifier._specifier_regex_str,
    +        re.VERBOSE | re.IGNORECASE,
    +    ),
    +    "AT": re.compile(r"\@"),
    +    "URL": re.compile(r"[^ \t]+"),
    +    "IDENTIFIER": re.compile(r"\b[a-zA-Z0-9][a-zA-Z0-9._-]*\b"),
    +    "VERSION_PREFIX_TRAIL": re.compile(r"\.\*"),
    +    "VERSION_LOCAL_LABEL_TRAIL": re.compile(r"\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*"),
    +    "WS": re.compile(r"[ \t]+"),
    +    "END": re.compile(r"$"),
    +}
    +
    +
    +class Tokenizer:
    +    """Context-sensitive token parsing.
    +
    +    Provides methods to examine the input stream to check whether the next token
    +    matches.
    +    """
    +
    +    def __init__(
    +        self,
    +        source: str,
    +        *,
    +        rules: Mapping[str, re.Pattern[str]],
    +    ) -> None:
    +        self.source = source
    +        self.rules = rules
    +        self.next_token: Token | None = None
    +        self.position = 0
    +
    +    def consume(self, name: str) -> None:
    +        """Move beyond provided token name, if at current position."""
    +        if self.check(name):
    +            self.read()
    +
    +    def check(self, name: str, *, peek: bool = False) -> bool:
    +        """Check whether the next token has the provided name.
    +
    +        By default, if the check succeeds, the token *must* be read before
    +        another check. If `peek` is set to `True`, the token is not loaded and
    +        would need to be checked again.
    +        """
    +        assert self.next_token is None, (
    +            f"Cannot check for {name!r}, already have {self.next_token!r}"
    +        )
    +        assert name in self.rules, f"Unknown token name: {name!r}"
    +
    +        expression = self.rules[name]
    +
    +        match = expression.match(self.source, self.position)
    +        if match is None:
    +            return False
    +        if not peek:
    +            self.next_token = Token(name, match[0], self.position)
    +        return True
    +
    +    def expect(self, name: str, *, expected: str) -> Token:
    +        """Expect a certain token name next, failing with a syntax error otherwise.
    +
    +        The token is *not* read.
    +        """
    +        if not self.check(name):
    +            raise self.raise_syntax_error(f"Expected {expected}")
    +        return self.read()
    +
    +    def read(self) -> Token:
    +        """Consume the next token and return it."""
    +        token = self.next_token
    +        assert token is not None
    +
    +        self.position += len(token.text)
    +        self.next_token = None
    +
    +        return token
    +
    +    def raise_syntax_error(
    +        self,
    +        message: str,
    +        *,
    +        span_start: int | None = None,
    +        span_end: int | None = None,
    +    ) -> NoReturn:
    +        """Raise ParserSyntaxError at the given position."""
    +        span = (
    +            self.position if span_start is None else span_start,
    +            self.position if span_end is None else span_end,
    +        )
    +        raise ParserSyntaxError(
    +            message,
    +            source=self.source,
    +            span=span,
    +        )
    +
    +    @contextlib.contextmanager
    +    def enclosing_tokens(
    +        self, open_token: str, close_token: str, *, around: str
    +    ) -> Generator[None, None, None]:
    +        if self.check(open_token):
    +            open_position = self.position
    +            self.read()
    +        else:
    +            open_position = None
    +
    +        yield
    +
    +        if open_position is None:
    +            return
    +
    +        if not self.check(close_token):
    +            self.raise_syntax_error(
    +                f"Expected matching {close_token} for {open_token}, after {around}",
    +                span_start=open_position,
    +            )
    +
    +        self.read()
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/dependency_groups.py b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/dependency_groups.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..413e5cb4b40d734564b99092320bb58a8f3c2c2b
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/dependency_groups.py
    @@ -0,0 +1,302 @@
    +from __future__ import annotations
    +
    +import re
    +from collections.abc import Mapping, Sequence
    +
    +from .errors import _ErrorCollector
    +from .requirements import Requirement
    +
    +__all__ = [
    +    "CyclicDependencyGroup",
    +    "DependencyGroupInclude",
    +    "DependencyGroupResolver",
    +    "DuplicateGroupNames",
    +    "InvalidDependencyGroupObject",
    +    "resolve_dependency_groups",
    +]
    +
    +
    +def __dir__() -> list[str]:
    +    return __all__
    +
    +
    +# -----------
    +# Error Types
    +# -----------
    +
    +
    +class DuplicateGroupNames(ValueError):
    +    """
    +    The same dependency groups were defined twice, with different non-normalized names.
    +    """
    +
    +
    +class CyclicDependencyGroup(ValueError):
    +    """
    +    The dependency group includes form a cycle.
    +    """
    +
    +    def __init__(self, requested_group: str, group: str, include_group: str) -> None:
    +        self.requested_group = requested_group
    +        self.group = group
    +        self.include_group = include_group
    +
    +        if include_group == group:
    +            reason = f"{group} includes itself"
    +        else:
    +            reason = f"{include_group} -> {group}, {group} -> {include_group}"
    +        super().__init__(
    +            "Cyclic dependency group include while resolving "
    +            f"{requested_group}: {reason}"
    +        )
    +
    +
    +# in the PEP 735 spec, the tables in dependency group lists were described as
    +# "Dependency Object Specifiers", but the only defined type of object was a
    +# "Dependency Group Include" -- hence the naming of this error as "Object"
    +class InvalidDependencyGroupObject(ValueError):
    +    """
    +    A member of a dependency group was identified as a dict, but was not in a valid
    +    format.
    +    """
    +
    +
    +# ------------------------
    +# Object Model & Interface
    +# ------------------------
    +
    +
    +class DependencyGroupInclude:
    +    __slots__ = ("include_group",)
    +
    +    def __init__(self, include_group: str) -> None:
    +        """
    +        Initialize a DependencyGroupInclude.
    +
    +        :param include_group: The name of the group referred to by this include.
    +        """
    +        self.include_group = include_group
    +
    +    def __repr__(self) -> str:
    +        return f"{self.__class__.__name__}({self.include_group!r})"
    +
    +
    +class DependencyGroupResolver:
    +    """
    +    A resolver for Dependency Group data.
    +
    +    This class handles caching, name normalization, cycle detection, and other
    +    parsing requirements. There are only two public methods for exploring the data:
    +    ``lookup()`` and ``resolve()``.
    +
    +    :param dependency_groups: A mapping, as provided via pyproject
    +        ``[dependency-groups]``.
    +    """
    +
    +    def __init__(
    +        self,
    +        dependency_groups: Mapping[str, Sequence[str | Mapping[str, str]]],
    +    ) -> None:
    +        errors = _ErrorCollector()
    +
    +        self.dependency_groups = _normalize_group_names(dependency_groups, errors)
    +
    +        # a map of group names to parsed data
    +        self._parsed_groups: dict[
    +            str, tuple[Requirement | DependencyGroupInclude, ...]
    +        ] = {}
    +        # a map of group names to their ancestors, used for cycle detection
    +        self._include_graph_ancestors: dict[str, tuple[str, ...]] = {}
    +        # a cache of completed resolutions to Requirement lists
    +        self._resolve_cache: dict[str, tuple[Requirement, ...]] = {}
    +
    +        errors.finalize("[dependency-groups] data was invalid")
    +
    +    def lookup(self, group: str) -> tuple[Requirement | DependencyGroupInclude, ...]:
    +        """
    +        Lookup a group name, returning the parsed dependency data for that group.
    +        This will not resolve includes.
    +
    +        :param group: the name of the group to lookup
    +        """
    +        group = _normalize_name(group)
    +
    +        with _ErrorCollector().on_exit(
    +            f"[dependency-groups] data for {group!r} was malformed"
    +        ) as errors:
    +            return self._parse_group(group, errors)
    +
    +    def resolve(self, group: str) -> tuple[Requirement, ...]:
    +        """
    +        Resolve a dependency group to a list of requirements.
    +
    +        :param group: the name of the group to resolve
    +        """
    +        group = _normalize_name(group)
    +
    +        with _ErrorCollector().on_exit(
    +            f"[dependency-groups] data for {group!r} was malformed"
    +        ) as errors:
    +            return self._resolve(group, group, errors)
    +
    +    def _resolve(
    +        self, group: str, requested_group: str, errors: _ErrorCollector
    +    ) -> tuple[Requirement, ...]:
    +        """
    +        This is a helper for cached resolution to strings. It preserves the name of the
    +        group which the user initially requested in order to present a clearer error in
    +        the event that a cycle is detected.
    +
    +        :param group: The normalized name of the group to resolve.
    +        :param requested_group: The group which was used in the original, user-facing
    +            request.
    +        """
    +        if group in self._resolve_cache:
    +            return self._resolve_cache[group]
    +
    +        parsed = self._parse_group(group, errors)
    +
    +        resolved_group = []
    +
    +        for item in parsed:
    +            if isinstance(item, Requirement):
    +                resolved_group.append(item)
    +            elif isinstance(item, DependencyGroupInclude):
    +                include_group = _normalize_name(item.include_group)
    +
    +                # if a group is cyclic, record the error
    +                # otherwise, follow the include_group reference
    +                #
    +                # this allows us to examine all includes in a group, even in the
    +                # presence of errors
    +                if include_group in self._include_graph_ancestors.get(group, ()):
    +                    errors.error(
    +                        CyclicDependencyGroup(
    +                            requested_group, group, item.include_group
    +                        )
    +                    )
    +                else:
    +                    self._include_graph_ancestors[include_group] = (
    +                        *self._include_graph_ancestors.get(group, ()),
    +                        group,
    +                    )
    +                    resolved_group.extend(
    +                        self._resolve(include_group, requested_group, errors)
    +                    )
    +            else:  # pragma: no cover
    +                raise NotImplementedError(
    +                    f"Invalid dependency group item after parse: {item}"
    +                )
    +
    +        # in the event that errors were detected, present the group as empty and do not
    +        # cache the result
    +        # this ensures that repeated access to a cyclic group will raise multiple errors
    +        if errors.errors:
    +            return ()
    +
    +        self._resolve_cache[group] = tuple(resolved_group)
    +        return self._resolve_cache[group]
    +
    +    def _parse_group(
    +        self, group: str, errors: _ErrorCollector
    +    ) -> tuple[Requirement | DependencyGroupInclude, ...]:
    +        # short circuit -- never do the work twice
    +        if group in self._parsed_groups:
    +            return self._parsed_groups[group]
    +
    +        if group not in self.dependency_groups:
    +            errors.error(LookupError(f"Dependency group '{group}' not found"))
    +            return ()
    +
    +        raw_group = self.dependency_groups[group]
    +        if isinstance(raw_group, str):
    +            errors.error(
    +                TypeError(
    +                    f"Dependency group {group!r} contained a string rather than a list."
    +                )
    +            )
    +            return ()
    +
    +        if not isinstance(raw_group, Sequence):
    +            errors.error(
    +                TypeError(f"Dependency group {group!r} is not a sequence type.")
    +            )
    +            return ()
    +
    +        elements: list[Requirement | DependencyGroupInclude] = []
    +        for item in raw_group:
    +            if isinstance(item, str):
    +                # packaging.requirements.Requirement parsing ensures that this is a
    +                # valid PEP 508 Dependency Specifier
    +                # raises InvalidRequirement on failure
    +                elements.append(Requirement(item))
    +            elif isinstance(item, Mapping):
    +                if tuple(item.keys()) != ("include-group",):
    +                    errors.error(
    +                        InvalidDependencyGroupObject(
    +                            f"Invalid dependency group item: {item!r}"
    +                        )
    +                    )
    +                else:
    +                    include_group = item["include-group"]
    +                    elements.append(DependencyGroupInclude(include_group=include_group))
    +            else:
    +                errors.error(TypeError(f"Invalid dependency group item: {item!r}"))
    +
    +        self._parsed_groups[group] = tuple(elements)
    +        return self._parsed_groups[group]
    +
    +
    +# --------------------
    +# Functional Interface
    +# --------------------
    +
    +
    +def resolve_dependency_groups(
    +    dependency_groups: Mapping[str, Sequence[str | Mapping[str, str]]], /, *groups: str
    +) -> tuple[str, ...]:
    +    """
    +    Resolve a dependency group to a tuple of requirements, as strings.
    +
    +    :param dependency_groups: the parsed contents of the ``[dependency-groups]`` table
    +        from ``pyproject.toml``
    +    :param groups: the name of the group(s) to resolve
    +    """
    +    resolver = DependencyGroupResolver(dependency_groups)
    +    return tuple(str(r) for group in groups for r in resolver.resolve(group))
    +
    +
    +# ----------------
    +# internal helpers
    +# ----------------
    +
    +
    +_NORMALIZE_PATTERN = re.compile(r"[-_.]+")
    +
    +
    +def _normalize_name(name: str) -> str:
    +    return _NORMALIZE_PATTERN.sub("-", name).lower()
    +
    +
    +def _normalize_group_names(
    +    dependency_groups: Mapping[str, Sequence[str | Mapping[str, str]]],
    +    errors: _ErrorCollector,
    +) -> dict[str, Sequence[str | Mapping[str, str]]]:
    +    original_names: dict[str, list[str]] = {}
    +    normalized_groups: dict[str, Sequence[str | Mapping[str, str]]] = {}
    +
    +    for group_name, value in dependency_groups.items():
    +        normed_group_name = _normalize_name(group_name)
    +        original_names.setdefault(normed_group_name, []).append(group_name)
    +        normalized_groups[normed_group_name] = value
    +
    +    for normed_name, names in original_names.items():
    +        if len(names) > 1:
    +            errors.error(
    +                DuplicateGroupNames(
    +                    "Duplicate dependency group names: "
    +                    f"{normed_name} ({', '.join(names)})"
    +                )
    +            )
    +
    +    return normalized_groups
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/direct_url.py b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/direct_url.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..5d1c56ca9d6fcd92111b0a86258853224509abb0
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/direct_url.py
    @@ -0,0 +1,325 @@
    +from __future__ import annotations
    +
    +import dataclasses
    +import re
    +import urllib.parse
    +from collections.abc import Mapping
    +from typing import TYPE_CHECKING, Any, Protocol, TypeVar
    +
    +if TYPE_CHECKING:  # pragma: no cover
    +    import sys
    +    from collections.abc import Collection
    +
    +    if sys.version_info >= (3, 11):
    +        from typing import Self
    +    else:
    +        from typing_extensions import Self
    +
    +__all__ = [
    +    "ArchiveInfo",
    +    "DirInfo",
    +    "DirectUrl",
    +    "DirectUrlValidationError",
    +    "VcsInfo",
    +]
    +
    +
    +def __dir__() -> list[str]:
    +    return __all__
    +
    +
    +_T = TypeVar("_T")
    +
    +
    +class _FromMappingProtocol(Protocol):  # pragma: no cover
    +    @classmethod
    +    def _from_dict(cls, d: Mapping[str, Any]) -> Self: ...
    +
    +
    +_FromMappingProtocolT = TypeVar("_FromMappingProtocolT", bound=_FromMappingProtocol)
    +
    +
    +def _json_dict_factory(data: list[tuple[str, Any]]) -> dict[str, Any]:
    +    return {key: value for key, value in data if value is not None}
    +
    +
    +def _get(d: Mapping[str, Any], expected_type: type[_T], key: str) -> _T | None:
    +    """Get a value from the dictionary and verify it's the expected type."""
    +    if (value := d.get(key)) is None:
    +        return None
    +    if not isinstance(value, expected_type):
    +        raise DirectUrlValidationError(
    +            f"Unexpected type {type(value).__name__} "
    +            f"(expected {expected_type.__name__})",
    +            context=key,
    +        )
    +    return value
    +
    +
    +def _get_required(d: Mapping[str, Any], expected_type: type[_T], key: str) -> _T:
    +    """Get a required value from the dictionary and verify it's the expected type."""
    +    if (value := _get(d, expected_type, key)) is None:
    +        raise _DirectUrlRequiredKeyError(key)
    +    return value
    +
    +
    +def _get_object(
    +    d: Mapping[str, Any], target_type: type[_FromMappingProtocolT], key: str
    +) -> _FromMappingProtocolT | None:
    +    """Get a dictionary value from the dictionary and convert it to a dataclass."""
    +    if (value := _get(d, Mapping, key)) is None:  # type: ignore[type-abstract]
    +        return None
    +    try:
    +        return target_type._from_dict(value)
    +    except Exception as e:
    +        raise DirectUrlValidationError(e, context=key) from e
    +
    +
    +_PEP610_USER_PASS_ENV_VARS_REGEX = re.compile(
    +    r"^\$\{[A-Za-z0-9-_]+\}(:\$\{[A-Za-z0-9-_]+\})?$"
    +)
    +
    +
    +def _strip_auth_from_netloc(netloc: str, safe_user_passwords: Collection[str]) -> str:
    +    if "@" not in netloc:
    +        return netloc
    +    user_pass, netloc_no_user_pass = netloc.split("@", 1)
    +    if user_pass in safe_user_passwords:
    +        return netloc
    +    if _PEP610_USER_PASS_ENV_VARS_REGEX.match(user_pass):
    +        return netloc
    +    return netloc_no_user_pass
    +
    +
    +def _strip_url(url: str, safe_user_passwords: Collection[str]) -> str:
    +    """url with user:password part removed unless it is formed with
    +    environment variables as specified in PEP 610, or it is a safe user:password
    +    such as `git`.
    +    """
    +    parsed_url = urllib.parse.urlsplit(url)
    +    netloc = _strip_auth_from_netloc(parsed_url.netloc, safe_user_passwords)
    +    return urllib.parse.urlunsplit(
    +        (
    +            parsed_url.scheme,
    +            netloc,
    +            parsed_url.path,
    +            parsed_url.query,
    +            parsed_url.fragment,
    +        )
    +    )
    +
    +
    +class DirectUrlValidationError(Exception):
    +    """Raised when when input data is not spec-compliant."""
    +
    +    context: str | None = None
    +    message: str
    +
    +    def __init__(
    +        self,
    +        cause: str | Exception,
    +        *,
    +        context: str | None = None,
    +    ) -> None:
    +        if isinstance(cause, DirectUrlValidationError):
    +            if cause.context:
    +                self.context = (
    +                    f"{context}.{cause.context}" if context else cause.context
    +                )
    +            else:
    +                self.context = context  # pragma: no cover
    +            self.message = cause.message
    +        else:
    +            self.context = context
    +            self.message = str(cause)
    +
    +    def __str__(self) -> str:
    +        if self.context:
    +            return f"{self.message} in {self.context!r}"
    +        return self.message
    +
    +
    +class _DirectUrlRequiredKeyError(DirectUrlValidationError):
    +    def __init__(self, key: str) -> None:
    +        super().__init__("Missing required value", context=key)
    +
    +
    +@dataclasses.dataclass(frozen=True, init=False)
    +class VcsInfo:
    +    vcs: str
    +    commit_id: str
    +    requested_revision: str | None = None
    +
    +    def __init__(
    +        self,
    +        *,
    +        vcs: str,
    +        commit_id: str,
    +        requested_revision: str | None = None,
    +    ) -> None:
    +        object.__setattr__(self, "vcs", vcs)
    +        object.__setattr__(self, "commit_id", commit_id)
    +        object.__setattr__(self, "requested_revision", requested_revision)
    +
    +    @classmethod
    +    def _from_dict(cls, d: Mapping[str, Any]) -> Self:
    +        # We can't validate vcs value because is not closed.
    +        return cls(
    +            vcs=_get_required(d, str, "vcs"),
    +            requested_revision=_get(d, str, "requested_revision"),
    +            commit_id=_get_required(d, str, "commit_id"),
    +        )
    +
    +
    +@dataclasses.dataclass(frozen=True, init=False)
    +class ArchiveInfo:
    +    hashes: Mapping[str, str] | None = None
    +
    +    def __init__(
    +        self,
    +        *,
    +        hashes: Mapping[str, str] | None = None,
    +    ) -> None:
    +        object.__setattr__(self, "hashes", hashes)
    +
    +    @classmethod
    +    def _from_dict(cls, d: Mapping[str, Any]) -> Self:
    +        hashes = _get(d, Mapping, "hashes")  # type: ignore[type-abstract]
    +        if hashes is not None and not all(isinstance(h, str) for h in hashes.values()):
    +            raise DirectUrlValidationError(
    +                "Hash values must be strings", context="hashes"
    +            )
    +        legacy_hash = _get(d, str, "hash")
    +        if legacy_hash is not None:
    +            if "=" not in legacy_hash:
    +                raise DirectUrlValidationError(
    +                    "Invalid hash format (expected '=')",
    +                    context="hash",
    +                )
    +            hash_algorithm, hash_value = legacy_hash.split("=", 1)
    +            if hashes is None:
    +                # if `hashes` are not present, we can derive it from the legacy `hash`
    +                hashes = {hash_algorithm: hash_value}
    +            else:
    +                # if `hashes` are present, the legacy `hash` must match one of them
    +                if hash_algorithm not in hashes:
    +                    raise DirectUrlValidationError(
    +                        f"Algorithm {hash_algorithm!r} used in hash field "
    +                        f"is not present in hashes field",
    +                        context="hashes",
    +                    )
    +                if hashes[hash_algorithm] != hash_value:
    +                    raise DirectUrlValidationError(
    +                        f"Algorithm {hash_algorithm!r} used in hash field "
    +                        f"has different value in hashes field",
    +                        context="hash",
    +                    )
    +        return cls(hashes=hashes)
    +
    +
    +@dataclasses.dataclass(frozen=True, init=False)
    +class DirInfo:
    +    editable: bool | None = None
    +
    +    def __init__(
    +        self,
    +        *,
    +        editable: bool | None = None,
    +    ) -> None:
    +        object.__setattr__(self, "editable", editable)
    +
    +    @classmethod
    +    def _from_dict(cls, d: Mapping[str, Any]) -> Self:
    +        return cls(
    +            editable=_get(d, bool, "editable"),
    +        )
    +
    +
    +@dataclasses.dataclass(frozen=True, init=False)
    +class DirectUrl:
    +    """A class representing a direct URL."""
    +
    +    url: str
    +    archive_info: ArchiveInfo | None = None
    +    vcs_info: VcsInfo | None = None
    +    dir_info: DirInfo | None = None
    +    subdirectory: str | None = None  # XXX Path or str?
    +
    +    def __init__(
    +        self,
    +        *,
    +        url: str,
    +        archive_info: ArchiveInfo | None = None,
    +        vcs_info: VcsInfo | None = None,
    +        dir_info: DirInfo | None = None,
    +        subdirectory: str | None = None,
    +    ) -> None:
    +        object.__setattr__(self, "url", url)
    +        object.__setattr__(self, "archive_info", archive_info)
    +        object.__setattr__(self, "vcs_info", vcs_info)
    +        object.__setattr__(self, "dir_info", dir_info)
    +        object.__setattr__(self, "subdirectory", subdirectory)
    +
    +    @classmethod
    +    def _from_dict(cls, d: Mapping[str, Any]) -> Self:
    +        direct_url = cls(
    +            url=_get_required(d, str, "url"),
    +            archive_info=_get_object(d, ArchiveInfo, "archive_info"),
    +            vcs_info=_get_object(d, VcsInfo, "vcs_info"),
    +            dir_info=_get_object(d, DirInfo, "dir_info"),
    +            subdirectory=_get(d, str, "subdirectory"),
    +        )
    +        if (
    +            bool(direct_url.vcs_info)
    +            + bool(direct_url.archive_info)
    +            + bool(direct_url.dir_info)
    +        ) != 1:
    +            raise DirectUrlValidationError(
    +                "Exactly one of vcs_info, archive_info, dir_info must be present"
    +            )
    +        if direct_url.dir_info is not None and not direct_url.url.startswith("file://"):
    +            raise DirectUrlValidationError(
    +                "URL scheme must be file:// when dir_info is present",
    +                context="url",
    +            )
    +        # XXX subdirectory must be relative, can we, should we validate that here?
    +        return direct_url
    +
    +    @classmethod
    +    def from_dict(cls, d: Mapping[str, Any], /) -> Self:
    +        """Create and validate a DirectUrl instance from a JSON dictionary."""
    +        return cls._from_dict(d)
    +
    +    def to_dict(
    +        self,
    +        *,
    +        generate_legacy_hash: bool = False,
    +        strip_user_password: bool = True,
    +        safe_user_passwords: Collection[str] = ("git",),
    +    ) -> Mapping[str, Any]:
    +        """Convert the DirectUrl instance to a JSON dictionary.
    +
    +        :param generate_legacy_hash: If True, include a legacy `hash` field in
    +            `archive_info` for backward compatibility with tools that don't
    +            support the `hashes` field.
    +        :param strip_user_password: If True, strip user:password from the URL
    +            unless it is formed with environment variables as specified in PEP
    +            610, or it is a safe user:password such as `git`.
    +        :param safe_user_passwords: A collection of user:password strings that
    +            should not be stripped from the URL even if `strip_user_password` is
    +            True.
    +        """
    +        res = dataclasses.asdict(self, dict_factory=_json_dict_factory)
    +        if generate_legacy_hash and self.archive_info and self.archive_info.hashes:
    +            hash_algorithm, hash_value = next(iter(self.archive_info.hashes.items()))
    +            res["archive_info"]["hash"] = f"{hash_algorithm}={hash_value}"
    +        if strip_user_password:
    +            res["url"] = _strip_url(self.url, safe_user_passwords)
    +        return res
    +
    +    def validate(self) -> None:
    +        """Validate the DirectUrl instance against the specification.
    +
    +        Raises :class:`DirectUrlValidationError` if invalid.
    +        """
    +        self.from_dict(self.to_dict())
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/errors.py b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/errors.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..d1d47cf6c3416179750bf5bf170aafe9d0fa6497
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/errors.py
    @@ -0,0 +1,94 @@
    +from __future__ import annotations
    +
    +import contextlib
    +import dataclasses
    +import sys
    +import typing
    +
    +__all__ = ["ExceptionGroup"]
    +
    +
    +def __dir__() -> list[str]:
    +    return __all__
    +
    +
    +if sys.version_info >= (3, 11):  # pragma: no cover
    +    from builtins import ExceptionGroup
    +else:  # pragma: no cover
    +
    +    class ExceptionGroup(Exception):
    +        """A minimal implementation of :external:exc:`ExceptionGroup` from Python 3.11.
    +
    +        If :external:exc:`ExceptionGroup` is already defined by Python itself,
    +        that version is used instead.
    +        """
    +
    +        message: str
    +        exceptions: list[Exception]
    +
    +        def __init__(self, message: str, exceptions: list[Exception]) -> None:
    +            self.message = message
    +            self.exceptions = exceptions
    +
    +        def __repr__(self) -> str:
    +            return f"{self.__class__.__name__}({self.message!r}, {self.exceptions!r})"
    +
    +
    +@dataclasses.dataclass
    +class _ErrorCollector:
    +    """
    +    Collect errors into ExceptionGroups.
    +
    +    Used like this:
    +
    +        collector = _ErrorCollector()
    +        # Add a single exception
    +        collector.error(ValueError("one"))
    +
    +        # Supports nesting, including combining ExceptionGroups
    +        with collector.collect():
    +            raise ValueError("two")
    +        collector.finalize("Found some errors")
    +
    +    Since making a collector and then calling finalize later is a common pattern,
    +    a convenience method ``on_exit`` is provided.
    +    """
    +
    +    errors: list[Exception] = dataclasses.field(default_factory=list, init=False)
    +
    +    def finalize(self, msg: str) -> None:
    +        """Raise a group exception if there are any errors."""
    +        if self.errors:
    +            raise ExceptionGroup(msg, self.errors)
    +
    +    @contextlib.contextmanager
    +    def on_exit(self, msg: str) -> typing.Generator[_ErrorCollector, None, None]:
    +        """
    +        Calls finalize if no uncollected errors were present.
    +
    +        Uncollected errors are raised normally.
    +        """
    +        yield self
    +        self.finalize(msg)
    +
    +    @contextlib.contextmanager
    +    def collect(self, *err_cls: type[Exception]) -> typing.Generator[None, None, None]:
    +        """
    +        Context manager to collect errors into the error list.
    +
    +        Must be inside loops, as only one error can be collected at a time.
    +        """
    +        error_classes = err_cls or (Exception,)
    +        try:
    +            yield
    +        except ExceptionGroup as error:
    +            self.errors.extend(error.exceptions)
    +        except error_classes as error:
    +            self.errors.append(error)
    +
    +    def error(
    +        self,
    +        error: Exception,
    +    ) -> None:
    +        """Add an error to the list."""
    +        self.errors.append(error)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/licenses/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/licenses/__init__.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..36e46ed02ca7aa6690928cf33868ae9cd1ab1d17
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/licenses/__init__.py
    @@ -0,0 +1,186 @@
    +#######################################################################################
    +#
    +# Adapted from:
    +#  https://github.com/pypa/hatch/blob/5352e44/backend/src/hatchling/licenses/parse.py
    +#
    +# MIT License
    +#
    +# Copyright (c) 2017-present Ofek Lev 
    +#
    +# Permission is hereby granted, free of charge, to any person obtaining a copy of this
    +# software and associated documentation files (the "Software"), to deal in the Software
    +# without restriction, including without limitation the rights to use, copy, modify,
    +# merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
    +# permit persons to whom the Software is furnished to do so, subject to the following
    +# conditions:
    +#
    +# The above copyright notice and this permission notice shall be included in all copies
    +# or substantial portions of the Software.
    +#
    +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
    +# INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
    +# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
    +# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
    +# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
    +# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    +#
    +#
    +# With additional allowance of arbitrary `LicenseRef-` identifiers, not just
    +# `LicenseRef-Public-Domain` and `LicenseRef-Proprietary`.
    +#
    +#######################################################################################
    +from __future__ import annotations
    +
    +import re
    +from typing import NewType, cast
    +
    +from ._spdx import EXCEPTIONS, LICENSES
    +
    +__all__ = [
    +    "InvalidLicenseExpression",
    +    "NormalizedLicenseExpression",
    +    "canonicalize_license_expression",
    +]
    +
    +
    +# Simple __dir__ implementation since there are no public submodules
    +def __dir__() -> list[str]:
    +    return __all__
    +
    +
    +license_ref_allowed = re.compile("^[A-Za-z0-9.-]*$")
    +
    +NormalizedLicenseExpression = NewType("NormalizedLicenseExpression", str)
    +"""
    +A :class:`typing.NewType` of :class:`str`, representing a normalized
    +License-Expression.
    +"""
    +
    +
    +class InvalidLicenseExpression(ValueError):
    +    """Raised when a license-expression string is invalid
    +
    +    >>> from packaging.licenses import canonicalize_license_expression
    +    >>> canonicalize_license_expression("invalid")
    +    Traceback (most recent call last):
    +        ...
    +    packaging.licenses.InvalidLicenseExpression: Invalid license expression: 'invalid'
    +    """
    +
    +
    +def canonicalize_license_expression(
    +    raw_license_expression: str,
    +) -> NormalizedLicenseExpression:
    +    """
    +    This function takes a valid License-Expression, and returns the normalized
    +    form of it.
    +
    +    The return type is typed as :class:`NormalizedLicenseExpression`. This
    +    allows type checkers to help require that a string has passed through this
    +    function before use.
    +
    +    :param str raw_license_expression: The License-Expression to canonicalize.
    +    :raises InvalidLicenseExpression: If the License-Expression is invalid due to an
    +        invalid/unknown license identifier or invalid syntax.
    +
    +    .. doctest::
    +
    +        >>> from packaging.licenses import canonicalize_license_expression
    +        >>> canonicalize_license_expression("mit")
    +        'MIT'
    +        >>> canonicalize_license_expression("mit and (apache-2.0 or bsd-2-clause)")
    +        'MIT AND (Apache-2.0 OR BSD-2-Clause)'
    +        >>> canonicalize_license_expression("(mit")
    +        Traceback (most recent call last):
    +          ...
    +        InvalidLicenseExpression: Invalid license expression: '(mit'
    +        >>> canonicalize_license_expression("Use-it-after-midnight")
    +        Traceback (most recent call last):
    +          ...
    +        InvalidLicenseExpression: Unknown license: 'Use-it-after-midnight'
    +    """
    +    if not raw_license_expression:
    +        message = f"Invalid license expression: {raw_license_expression!r}"
    +        raise InvalidLicenseExpression(message)
    +
    +    # Pad any parentheses so tokenization can be achieved by merely splitting on
    +    # whitespace.
    +    license_expression = raw_license_expression.replace("(", " ( ").replace(")", " ) ")
    +    licenseref_prefix = "LicenseRef-"
    +    license_refs = {
    +        ref.lower(): "LicenseRef-" + ref[len(licenseref_prefix) :]
    +        for ref in license_expression.split()
    +        if ref.lower().startswith(licenseref_prefix.lower())
    +    }
    +
    +    # Normalize to lower case so we can look up licenses/exceptions
    +    # and so boolean operators are Python-compatible.
    +    license_expression = license_expression.lower()
    +
    +    tokens = license_expression.split()
    +
    +    # Rather than implementing a parenthesis/boolean logic parser, create an
    +    # expression that Python can parse. Everything that is not involved with the
    +    # grammar itself is replaced with the placeholder `False` and the resultant
    +    # expression should become a valid Python expression.
    +    python_tokens = []
    +    for token in tokens:
    +        if token not in {"or", "and", "with", "(", ")"}:
    +            python_tokens.append("False")
    +        elif token == "with":
    +            python_tokens.append("or")
    +        elif (
    +            token == "("
    +            and python_tokens
    +            and python_tokens[-1] not in {"or", "and", "("}
    +        ) or (token == ")" and python_tokens and python_tokens[-1] == "("):
    +            message = f"Invalid license expression: {raw_license_expression!r}"
    +            raise InvalidLicenseExpression(message)
    +        else:
    +            python_tokens.append(token)
    +
    +    python_expression = " ".join(python_tokens)
    +    try:
    +        compile(python_expression, "", "eval")
    +    except SyntaxError:
    +        message = f"Invalid license expression: {raw_license_expression!r}"
    +        raise InvalidLicenseExpression(message) from None
    +
    +    # Take a final pass to check for unknown licenses/exceptions.
    +    normalized_tokens = []
    +    for token in tokens:
    +        if token in {"or", "and", "with", "(", ")"}:
    +            normalized_tokens.append(token.upper())
    +            continue
    +
    +        if normalized_tokens and normalized_tokens[-1] == "WITH":
    +            if token not in EXCEPTIONS:
    +                message = f"Unknown license exception: {token!r}"
    +                raise InvalidLicenseExpression(message)
    +
    +            normalized_tokens.append(EXCEPTIONS[token]["id"])
    +        else:
    +            if token.endswith("+"):
    +                final_token = token[:-1]
    +                suffix = "+"
    +            else:
    +                final_token = token
    +                suffix = ""
    +
    +            if final_token.startswith("licenseref-"):
    +                if not license_ref_allowed.match(final_token):
    +                    message = f"Invalid licenseref: {final_token!r}"
    +                    raise InvalidLicenseExpression(message)
    +                normalized_tokens.append(license_refs[final_token] + suffix)
    +            else:
    +                if final_token not in LICENSES:
    +                    message = f"Unknown license: {final_token!r}"
    +                    raise InvalidLicenseExpression(message)
    +                normalized_tokens.append(LICENSES[final_token]["id"] + suffix)
    +
    +    normalized_expression = " ".join(normalized_tokens)
    +
    +    return cast(
    +        "NormalizedLicenseExpression",
    +        normalized_expression.replace("( ", "(").replace(" )", ")"),
    +    )
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/licenses/_spdx.py b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/licenses/_spdx.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..a277af28220b6dbe4599471104d1c7a2bd1e1288
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/licenses/_spdx.py
    @@ -0,0 +1,799 @@
    +
    +from __future__ import annotations
    +
    +from typing import TypedDict
    +
    +class SPDXLicense(TypedDict):
    +    id: str
    +    deprecated: bool
    +
    +class SPDXException(TypedDict):
    +    id: str
    +    deprecated: bool
    +
    +
    +VERSION = '3.27.0'
    +
    +LICENSES: dict[str, SPDXLicense] = {
    +    '0bsd': {'id': '0BSD', 'deprecated': False},
    +    '3d-slicer-1.0': {'id': '3D-Slicer-1.0', 'deprecated': False},
    +    'aal': {'id': 'AAL', 'deprecated': False},
    +    'abstyles': {'id': 'Abstyles', 'deprecated': False},
    +    'adacore-doc': {'id': 'AdaCore-doc', 'deprecated': False},
    +    'adobe-2006': {'id': 'Adobe-2006', 'deprecated': False},
    +    'adobe-display-postscript': {'id': 'Adobe-Display-PostScript', 'deprecated': False},
    +    'adobe-glyph': {'id': 'Adobe-Glyph', 'deprecated': False},
    +    'adobe-utopia': {'id': 'Adobe-Utopia', 'deprecated': False},
    +    'adsl': {'id': 'ADSL', 'deprecated': False},
    +    'afl-1.1': {'id': 'AFL-1.1', 'deprecated': False},
    +    'afl-1.2': {'id': 'AFL-1.2', 'deprecated': False},
    +    'afl-2.0': {'id': 'AFL-2.0', 'deprecated': False},
    +    'afl-2.1': {'id': 'AFL-2.1', 'deprecated': False},
    +    'afl-3.0': {'id': 'AFL-3.0', 'deprecated': False},
    +    'afmparse': {'id': 'Afmparse', 'deprecated': False},
    +    'agpl-1.0': {'id': 'AGPL-1.0', 'deprecated': True},
    +    'agpl-1.0-only': {'id': 'AGPL-1.0-only', 'deprecated': False},
    +    'agpl-1.0-or-later': {'id': 'AGPL-1.0-or-later', 'deprecated': False},
    +    'agpl-3.0': {'id': 'AGPL-3.0', 'deprecated': True},
    +    'agpl-3.0-only': {'id': 'AGPL-3.0-only', 'deprecated': False},
    +    'agpl-3.0-or-later': {'id': 'AGPL-3.0-or-later', 'deprecated': False},
    +    'aladdin': {'id': 'Aladdin', 'deprecated': False},
    +    'amd-newlib': {'id': 'AMD-newlib', 'deprecated': False},
    +    'amdplpa': {'id': 'AMDPLPA', 'deprecated': False},
    +    'aml': {'id': 'AML', 'deprecated': False},
    +    'aml-glslang': {'id': 'AML-glslang', 'deprecated': False},
    +    'ampas': {'id': 'AMPAS', 'deprecated': False},
    +    'antlr-pd': {'id': 'ANTLR-PD', 'deprecated': False},
    +    'antlr-pd-fallback': {'id': 'ANTLR-PD-fallback', 'deprecated': False},
    +    'any-osi': {'id': 'any-OSI', 'deprecated': False},
    +    'any-osi-perl-modules': {'id': 'any-OSI-perl-modules', 'deprecated': False},
    +    'apache-1.0': {'id': 'Apache-1.0', 'deprecated': False},
    +    'apache-1.1': {'id': 'Apache-1.1', 'deprecated': False},
    +    'apache-2.0': {'id': 'Apache-2.0', 'deprecated': False},
    +    'apafml': {'id': 'APAFML', 'deprecated': False},
    +    'apl-1.0': {'id': 'APL-1.0', 'deprecated': False},
    +    'app-s2p': {'id': 'App-s2p', 'deprecated': False},
    +    'apsl-1.0': {'id': 'APSL-1.0', 'deprecated': False},
    +    'apsl-1.1': {'id': 'APSL-1.1', 'deprecated': False},
    +    'apsl-1.2': {'id': 'APSL-1.2', 'deprecated': False},
    +    'apsl-2.0': {'id': 'APSL-2.0', 'deprecated': False},
    +    'arphic-1999': {'id': 'Arphic-1999', 'deprecated': False},
    +    'artistic-1.0': {'id': 'Artistic-1.0', 'deprecated': False},
    +    'artistic-1.0-cl8': {'id': 'Artistic-1.0-cl8', 'deprecated': False},
    +    'artistic-1.0-perl': {'id': 'Artistic-1.0-Perl', 'deprecated': False},
    +    'artistic-2.0': {'id': 'Artistic-2.0', 'deprecated': False},
    +    'artistic-dist': {'id': 'Artistic-dist', 'deprecated': False},
    +    'aspell-ru': {'id': 'Aspell-RU', 'deprecated': False},
    +    'aswf-digital-assets-1.0': {'id': 'ASWF-Digital-Assets-1.0', 'deprecated': False},
    +    'aswf-digital-assets-1.1': {'id': 'ASWF-Digital-Assets-1.1', 'deprecated': False},
    +    'baekmuk': {'id': 'Baekmuk', 'deprecated': False},
    +    'bahyph': {'id': 'Bahyph', 'deprecated': False},
    +    'barr': {'id': 'Barr', 'deprecated': False},
    +    'bcrypt-solar-designer': {'id': 'bcrypt-Solar-Designer', 'deprecated': False},
    +    'beerware': {'id': 'Beerware', 'deprecated': False},
    +    'bitstream-charter': {'id': 'Bitstream-Charter', 'deprecated': False},
    +    'bitstream-vera': {'id': 'Bitstream-Vera', 'deprecated': False},
    +    'bittorrent-1.0': {'id': 'BitTorrent-1.0', 'deprecated': False},
    +    'bittorrent-1.1': {'id': 'BitTorrent-1.1', 'deprecated': False},
    +    'blessing': {'id': 'blessing', 'deprecated': False},
    +    'blueoak-1.0.0': {'id': 'BlueOak-1.0.0', 'deprecated': False},
    +    'boehm-gc': {'id': 'Boehm-GC', 'deprecated': False},
    +    'boehm-gc-without-fee': {'id': 'Boehm-GC-without-fee', 'deprecated': False},
    +    'borceux': {'id': 'Borceux', 'deprecated': False},
    +    'brian-gladman-2-clause': {'id': 'Brian-Gladman-2-Clause', 'deprecated': False},
    +    'brian-gladman-3-clause': {'id': 'Brian-Gladman-3-Clause', 'deprecated': False},
    +    'bsd-1-clause': {'id': 'BSD-1-Clause', 'deprecated': False},
    +    'bsd-2-clause': {'id': 'BSD-2-Clause', 'deprecated': False},
    +    'bsd-2-clause-darwin': {'id': 'BSD-2-Clause-Darwin', 'deprecated': False},
    +    'bsd-2-clause-first-lines': {'id': 'BSD-2-Clause-first-lines', 'deprecated': False},
    +    'bsd-2-clause-freebsd': {'id': 'BSD-2-Clause-FreeBSD', 'deprecated': True},
    +    'bsd-2-clause-netbsd': {'id': 'BSD-2-Clause-NetBSD', 'deprecated': True},
    +    'bsd-2-clause-patent': {'id': 'BSD-2-Clause-Patent', 'deprecated': False},
    +    'bsd-2-clause-pkgconf-disclaimer': {'id': 'BSD-2-Clause-pkgconf-disclaimer', 'deprecated': False},
    +    'bsd-2-clause-views': {'id': 'BSD-2-Clause-Views', 'deprecated': False},
    +    'bsd-3-clause': {'id': 'BSD-3-Clause', 'deprecated': False},
    +    'bsd-3-clause-acpica': {'id': 'BSD-3-Clause-acpica', 'deprecated': False},
    +    'bsd-3-clause-attribution': {'id': 'BSD-3-Clause-Attribution', 'deprecated': False},
    +    'bsd-3-clause-clear': {'id': 'BSD-3-Clause-Clear', 'deprecated': False},
    +    'bsd-3-clause-flex': {'id': 'BSD-3-Clause-flex', 'deprecated': False},
    +    'bsd-3-clause-hp': {'id': 'BSD-3-Clause-HP', 'deprecated': False},
    +    'bsd-3-clause-lbnl': {'id': 'BSD-3-Clause-LBNL', 'deprecated': False},
    +    'bsd-3-clause-modification': {'id': 'BSD-3-Clause-Modification', 'deprecated': False},
    +    'bsd-3-clause-no-military-license': {'id': 'BSD-3-Clause-No-Military-License', 'deprecated': False},
    +    'bsd-3-clause-no-nuclear-license': {'id': 'BSD-3-Clause-No-Nuclear-License', 'deprecated': False},
    +    'bsd-3-clause-no-nuclear-license-2014': {'id': 'BSD-3-Clause-No-Nuclear-License-2014', 'deprecated': False},
    +    'bsd-3-clause-no-nuclear-warranty': {'id': 'BSD-3-Clause-No-Nuclear-Warranty', 'deprecated': False},
    +    'bsd-3-clause-open-mpi': {'id': 'BSD-3-Clause-Open-MPI', 'deprecated': False},
    +    'bsd-3-clause-sun': {'id': 'BSD-3-Clause-Sun', 'deprecated': False},
    +    'bsd-4-clause': {'id': 'BSD-4-Clause', 'deprecated': False},
    +    'bsd-4-clause-shortened': {'id': 'BSD-4-Clause-Shortened', 'deprecated': False},
    +    'bsd-4-clause-uc': {'id': 'BSD-4-Clause-UC', 'deprecated': False},
    +    'bsd-4.3reno': {'id': 'BSD-4.3RENO', 'deprecated': False},
    +    'bsd-4.3tahoe': {'id': 'BSD-4.3TAHOE', 'deprecated': False},
    +    'bsd-advertising-acknowledgement': {'id': 'BSD-Advertising-Acknowledgement', 'deprecated': False},
    +    'bsd-attribution-hpnd-disclaimer': {'id': 'BSD-Attribution-HPND-disclaimer', 'deprecated': False},
    +    'bsd-inferno-nettverk': {'id': 'BSD-Inferno-Nettverk', 'deprecated': False},
    +    'bsd-protection': {'id': 'BSD-Protection', 'deprecated': False},
    +    'bsd-source-beginning-file': {'id': 'BSD-Source-beginning-file', 'deprecated': False},
    +    'bsd-source-code': {'id': 'BSD-Source-Code', 'deprecated': False},
    +    'bsd-systemics': {'id': 'BSD-Systemics', 'deprecated': False},
    +    'bsd-systemics-w3works': {'id': 'BSD-Systemics-W3Works', 'deprecated': False},
    +    'bsl-1.0': {'id': 'BSL-1.0', 'deprecated': False},
    +    'busl-1.1': {'id': 'BUSL-1.1', 'deprecated': False},
    +    'bzip2-1.0.5': {'id': 'bzip2-1.0.5', 'deprecated': True},
    +    'bzip2-1.0.6': {'id': 'bzip2-1.0.6', 'deprecated': False},
    +    'c-uda-1.0': {'id': 'C-UDA-1.0', 'deprecated': False},
    +    'cal-1.0': {'id': 'CAL-1.0', 'deprecated': False},
    +    'cal-1.0-combined-work-exception': {'id': 'CAL-1.0-Combined-Work-Exception', 'deprecated': False},
    +    'caldera': {'id': 'Caldera', 'deprecated': False},
    +    'caldera-no-preamble': {'id': 'Caldera-no-preamble', 'deprecated': False},
    +    'catharon': {'id': 'Catharon', 'deprecated': False},
    +    'catosl-1.1': {'id': 'CATOSL-1.1', 'deprecated': False},
    +    'cc-by-1.0': {'id': 'CC-BY-1.0', 'deprecated': False},
    +    'cc-by-2.0': {'id': 'CC-BY-2.0', 'deprecated': False},
    +    'cc-by-2.5': {'id': 'CC-BY-2.5', 'deprecated': False},
    +    'cc-by-2.5-au': {'id': 'CC-BY-2.5-AU', 'deprecated': False},
    +    'cc-by-3.0': {'id': 'CC-BY-3.0', 'deprecated': False},
    +    'cc-by-3.0-at': {'id': 'CC-BY-3.0-AT', 'deprecated': False},
    +    'cc-by-3.0-au': {'id': 'CC-BY-3.0-AU', 'deprecated': False},
    +    'cc-by-3.0-de': {'id': 'CC-BY-3.0-DE', 'deprecated': False},
    +    'cc-by-3.0-igo': {'id': 'CC-BY-3.0-IGO', 'deprecated': False},
    +    'cc-by-3.0-nl': {'id': 'CC-BY-3.0-NL', 'deprecated': False},
    +    'cc-by-3.0-us': {'id': 'CC-BY-3.0-US', 'deprecated': False},
    +    'cc-by-4.0': {'id': 'CC-BY-4.0', 'deprecated': False},
    +    'cc-by-nc-1.0': {'id': 'CC-BY-NC-1.0', 'deprecated': False},
    +    'cc-by-nc-2.0': {'id': 'CC-BY-NC-2.0', 'deprecated': False},
    +    'cc-by-nc-2.5': {'id': 'CC-BY-NC-2.5', 'deprecated': False},
    +    'cc-by-nc-3.0': {'id': 'CC-BY-NC-3.0', 'deprecated': False},
    +    'cc-by-nc-3.0-de': {'id': 'CC-BY-NC-3.0-DE', 'deprecated': False},
    +    'cc-by-nc-4.0': {'id': 'CC-BY-NC-4.0', 'deprecated': False},
    +    'cc-by-nc-nd-1.0': {'id': 'CC-BY-NC-ND-1.0', 'deprecated': False},
    +    'cc-by-nc-nd-2.0': {'id': 'CC-BY-NC-ND-2.0', 'deprecated': False},
    +    'cc-by-nc-nd-2.5': {'id': 'CC-BY-NC-ND-2.5', 'deprecated': False},
    +    'cc-by-nc-nd-3.0': {'id': 'CC-BY-NC-ND-3.0', 'deprecated': False},
    +    'cc-by-nc-nd-3.0-de': {'id': 'CC-BY-NC-ND-3.0-DE', 'deprecated': False},
    +    'cc-by-nc-nd-3.0-igo': {'id': 'CC-BY-NC-ND-3.0-IGO', 'deprecated': False},
    +    'cc-by-nc-nd-4.0': {'id': 'CC-BY-NC-ND-4.0', 'deprecated': False},
    +    'cc-by-nc-sa-1.0': {'id': 'CC-BY-NC-SA-1.0', 'deprecated': False},
    +    'cc-by-nc-sa-2.0': {'id': 'CC-BY-NC-SA-2.0', 'deprecated': False},
    +    'cc-by-nc-sa-2.0-de': {'id': 'CC-BY-NC-SA-2.0-DE', 'deprecated': False},
    +    'cc-by-nc-sa-2.0-fr': {'id': 'CC-BY-NC-SA-2.0-FR', 'deprecated': False},
    +    'cc-by-nc-sa-2.0-uk': {'id': 'CC-BY-NC-SA-2.0-UK', 'deprecated': False},
    +    'cc-by-nc-sa-2.5': {'id': 'CC-BY-NC-SA-2.5', 'deprecated': False},
    +    'cc-by-nc-sa-3.0': {'id': 'CC-BY-NC-SA-3.0', 'deprecated': False},
    +    'cc-by-nc-sa-3.0-de': {'id': 'CC-BY-NC-SA-3.0-DE', 'deprecated': False},
    +    'cc-by-nc-sa-3.0-igo': {'id': 'CC-BY-NC-SA-3.0-IGO', 'deprecated': False},
    +    'cc-by-nc-sa-4.0': {'id': 'CC-BY-NC-SA-4.0', 'deprecated': False},
    +    'cc-by-nd-1.0': {'id': 'CC-BY-ND-1.0', 'deprecated': False},
    +    'cc-by-nd-2.0': {'id': 'CC-BY-ND-2.0', 'deprecated': False},
    +    'cc-by-nd-2.5': {'id': 'CC-BY-ND-2.5', 'deprecated': False},
    +    'cc-by-nd-3.0': {'id': 'CC-BY-ND-3.0', 'deprecated': False},
    +    'cc-by-nd-3.0-de': {'id': 'CC-BY-ND-3.0-DE', 'deprecated': False},
    +    'cc-by-nd-4.0': {'id': 'CC-BY-ND-4.0', 'deprecated': False},
    +    'cc-by-sa-1.0': {'id': 'CC-BY-SA-1.0', 'deprecated': False},
    +    'cc-by-sa-2.0': {'id': 'CC-BY-SA-2.0', 'deprecated': False},
    +    'cc-by-sa-2.0-uk': {'id': 'CC-BY-SA-2.0-UK', 'deprecated': False},
    +    'cc-by-sa-2.1-jp': {'id': 'CC-BY-SA-2.1-JP', 'deprecated': False},
    +    'cc-by-sa-2.5': {'id': 'CC-BY-SA-2.5', 'deprecated': False},
    +    'cc-by-sa-3.0': {'id': 'CC-BY-SA-3.0', 'deprecated': False},
    +    'cc-by-sa-3.0-at': {'id': 'CC-BY-SA-3.0-AT', 'deprecated': False},
    +    'cc-by-sa-3.0-de': {'id': 'CC-BY-SA-3.0-DE', 'deprecated': False},
    +    'cc-by-sa-3.0-igo': {'id': 'CC-BY-SA-3.0-IGO', 'deprecated': False},
    +    'cc-by-sa-4.0': {'id': 'CC-BY-SA-4.0', 'deprecated': False},
    +    'cc-pddc': {'id': 'CC-PDDC', 'deprecated': False},
    +    'cc-pdm-1.0': {'id': 'CC-PDM-1.0', 'deprecated': False},
    +    'cc-sa-1.0': {'id': 'CC-SA-1.0', 'deprecated': False},
    +    'cc0-1.0': {'id': 'CC0-1.0', 'deprecated': False},
    +    'cddl-1.0': {'id': 'CDDL-1.0', 'deprecated': False},
    +    'cddl-1.1': {'id': 'CDDL-1.1', 'deprecated': False},
    +    'cdl-1.0': {'id': 'CDL-1.0', 'deprecated': False},
    +    'cdla-permissive-1.0': {'id': 'CDLA-Permissive-1.0', 'deprecated': False},
    +    'cdla-permissive-2.0': {'id': 'CDLA-Permissive-2.0', 'deprecated': False},
    +    'cdla-sharing-1.0': {'id': 'CDLA-Sharing-1.0', 'deprecated': False},
    +    'cecill-1.0': {'id': 'CECILL-1.0', 'deprecated': False},
    +    'cecill-1.1': {'id': 'CECILL-1.1', 'deprecated': False},
    +    'cecill-2.0': {'id': 'CECILL-2.0', 'deprecated': False},
    +    'cecill-2.1': {'id': 'CECILL-2.1', 'deprecated': False},
    +    'cecill-b': {'id': 'CECILL-B', 'deprecated': False},
    +    'cecill-c': {'id': 'CECILL-C', 'deprecated': False},
    +    'cern-ohl-1.1': {'id': 'CERN-OHL-1.1', 'deprecated': False},
    +    'cern-ohl-1.2': {'id': 'CERN-OHL-1.2', 'deprecated': False},
    +    'cern-ohl-p-2.0': {'id': 'CERN-OHL-P-2.0', 'deprecated': False},
    +    'cern-ohl-s-2.0': {'id': 'CERN-OHL-S-2.0', 'deprecated': False},
    +    'cern-ohl-w-2.0': {'id': 'CERN-OHL-W-2.0', 'deprecated': False},
    +    'cfitsio': {'id': 'CFITSIO', 'deprecated': False},
    +    'check-cvs': {'id': 'check-cvs', 'deprecated': False},
    +    'checkmk': {'id': 'checkmk', 'deprecated': False},
    +    'clartistic': {'id': 'ClArtistic', 'deprecated': False},
    +    'clips': {'id': 'Clips', 'deprecated': False},
    +    'cmu-mach': {'id': 'CMU-Mach', 'deprecated': False},
    +    'cmu-mach-nodoc': {'id': 'CMU-Mach-nodoc', 'deprecated': False},
    +    'cnri-jython': {'id': 'CNRI-Jython', 'deprecated': False},
    +    'cnri-python': {'id': 'CNRI-Python', 'deprecated': False},
    +    'cnri-python-gpl-compatible': {'id': 'CNRI-Python-GPL-Compatible', 'deprecated': False},
    +    'coil-1.0': {'id': 'COIL-1.0', 'deprecated': False},
    +    'community-spec-1.0': {'id': 'Community-Spec-1.0', 'deprecated': False},
    +    'condor-1.1': {'id': 'Condor-1.1', 'deprecated': False},
    +    'copyleft-next-0.3.0': {'id': 'copyleft-next-0.3.0', 'deprecated': False},
    +    'copyleft-next-0.3.1': {'id': 'copyleft-next-0.3.1', 'deprecated': False},
    +    'cornell-lossless-jpeg': {'id': 'Cornell-Lossless-JPEG', 'deprecated': False},
    +    'cpal-1.0': {'id': 'CPAL-1.0', 'deprecated': False},
    +    'cpl-1.0': {'id': 'CPL-1.0', 'deprecated': False},
    +    'cpol-1.02': {'id': 'CPOL-1.02', 'deprecated': False},
    +    'cronyx': {'id': 'Cronyx', 'deprecated': False},
    +    'crossword': {'id': 'Crossword', 'deprecated': False},
    +    'cryptoswift': {'id': 'CryptoSwift', 'deprecated': False},
    +    'crystalstacker': {'id': 'CrystalStacker', 'deprecated': False},
    +    'cua-opl-1.0': {'id': 'CUA-OPL-1.0', 'deprecated': False},
    +    'cube': {'id': 'Cube', 'deprecated': False},
    +    'curl': {'id': 'curl', 'deprecated': False},
    +    'cve-tou': {'id': 'cve-tou', 'deprecated': False},
    +    'd-fsl-1.0': {'id': 'D-FSL-1.0', 'deprecated': False},
    +    'dec-3-clause': {'id': 'DEC-3-Clause', 'deprecated': False},
    +    'diffmark': {'id': 'diffmark', 'deprecated': False},
    +    'dl-de-by-2.0': {'id': 'DL-DE-BY-2.0', 'deprecated': False},
    +    'dl-de-zero-2.0': {'id': 'DL-DE-ZERO-2.0', 'deprecated': False},
    +    'doc': {'id': 'DOC', 'deprecated': False},
    +    'docbook-dtd': {'id': 'DocBook-DTD', 'deprecated': False},
    +    'docbook-schema': {'id': 'DocBook-Schema', 'deprecated': False},
    +    'docbook-stylesheet': {'id': 'DocBook-Stylesheet', 'deprecated': False},
    +    'docbook-xml': {'id': 'DocBook-XML', 'deprecated': False},
    +    'dotseqn': {'id': 'Dotseqn', 'deprecated': False},
    +    'drl-1.0': {'id': 'DRL-1.0', 'deprecated': False},
    +    'drl-1.1': {'id': 'DRL-1.1', 'deprecated': False},
    +    'dsdp': {'id': 'DSDP', 'deprecated': False},
    +    'dtoa': {'id': 'dtoa', 'deprecated': False},
    +    'dvipdfm': {'id': 'dvipdfm', 'deprecated': False},
    +    'ecl-1.0': {'id': 'ECL-1.0', 'deprecated': False},
    +    'ecl-2.0': {'id': 'ECL-2.0', 'deprecated': False},
    +    'ecos-2.0': {'id': 'eCos-2.0', 'deprecated': True},
    +    'efl-1.0': {'id': 'EFL-1.0', 'deprecated': False},
    +    'efl-2.0': {'id': 'EFL-2.0', 'deprecated': False},
    +    'egenix': {'id': 'eGenix', 'deprecated': False},
    +    'elastic-2.0': {'id': 'Elastic-2.0', 'deprecated': False},
    +    'entessa': {'id': 'Entessa', 'deprecated': False},
    +    'epics': {'id': 'EPICS', 'deprecated': False},
    +    'epl-1.0': {'id': 'EPL-1.0', 'deprecated': False},
    +    'epl-2.0': {'id': 'EPL-2.0', 'deprecated': False},
    +    'erlpl-1.1': {'id': 'ErlPL-1.1', 'deprecated': False},
    +    'etalab-2.0': {'id': 'etalab-2.0', 'deprecated': False},
    +    'eudatagrid': {'id': 'EUDatagrid', 'deprecated': False},
    +    'eupl-1.0': {'id': 'EUPL-1.0', 'deprecated': False},
    +    'eupl-1.1': {'id': 'EUPL-1.1', 'deprecated': False},
    +    'eupl-1.2': {'id': 'EUPL-1.2', 'deprecated': False},
    +    'eurosym': {'id': 'Eurosym', 'deprecated': False},
    +    'fair': {'id': 'Fair', 'deprecated': False},
    +    'fbm': {'id': 'FBM', 'deprecated': False},
    +    'fdk-aac': {'id': 'FDK-AAC', 'deprecated': False},
    +    'ferguson-twofish': {'id': 'Ferguson-Twofish', 'deprecated': False},
    +    'frameworx-1.0': {'id': 'Frameworx-1.0', 'deprecated': False},
    +    'freebsd-doc': {'id': 'FreeBSD-DOC', 'deprecated': False},
    +    'freeimage': {'id': 'FreeImage', 'deprecated': False},
    +    'fsfap': {'id': 'FSFAP', 'deprecated': False},
    +    'fsfap-no-warranty-disclaimer': {'id': 'FSFAP-no-warranty-disclaimer', 'deprecated': False},
    +    'fsful': {'id': 'FSFUL', 'deprecated': False},
    +    'fsfullr': {'id': 'FSFULLR', 'deprecated': False},
    +    'fsfullrsd': {'id': 'FSFULLRSD', 'deprecated': False},
    +    'fsfullrwd': {'id': 'FSFULLRWD', 'deprecated': False},
    +    'fsl-1.1-alv2': {'id': 'FSL-1.1-ALv2', 'deprecated': False},
    +    'fsl-1.1-mit': {'id': 'FSL-1.1-MIT', 'deprecated': False},
    +    'ftl': {'id': 'FTL', 'deprecated': False},
    +    'furuseth': {'id': 'Furuseth', 'deprecated': False},
    +    'fwlw': {'id': 'fwlw', 'deprecated': False},
    +    'game-programming-gems': {'id': 'Game-Programming-Gems', 'deprecated': False},
    +    'gcr-docs': {'id': 'GCR-docs', 'deprecated': False},
    +    'gd': {'id': 'GD', 'deprecated': False},
    +    'generic-xts': {'id': 'generic-xts', 'deprecated': False},
    +    'gfdl-1.1': {'id': 'GFDL-1.1', 'deprecated': True},
    +    'gfdl-1.1-invariants-only': {'id': 'GFDL-1.1-invariants-only', 'deprecated': False},
    +    'gfdl-1.1-invariants-or-later': {'id': 'GFDL-1.1-invariants-or-later', 'deprecated': False},
    +    'gfdl-1.1-no-invariants-only': {'id': 'GFDL-1.1-no-invariants-only', 'deprecated': False},
    +    'gfdl-1.1-no-invariants-or-later': {'id': 'GFDL-1.1-no-invariants-or-later', 'deprecated': False},
    +    'gfdl-1.1-only': {'id': 'GFDL-1.1-only', 'deprecated': False},
    +    'gfdl-1.1-or-later': {'id': 'GFDL-1.1-or-later', 'deprecated': False},
    +    'gfdl-1.2': {'id': 'GFDL-1.2', 'deprecated': True},
    +    'gfdl-1.2-invariants-only': {'id': 'GFDL-1.2-invariants-only', 'deprecated': False},
    +    'gfdl-1.2-invariants-or-later': {'id': 'GFDL-1.2-invariants-or-later', 'deprecated': False},
    +    'gfdl-1.2-no-invariants-only': {'id': 'GFDL-1.2-no-invariants-only', 'deprecated': False},
    +    'gfdl-1.2-no-invariants-or-later': {'id': 'GFDL-1.2-no-invariants-or-later', 'deprecated': False},
    +    'gfdl-1.2-only': {'id': 'GFDL-1.2-only', 'deprecated': False},
    +    'gfdl-1.2-or-later': {'id': 'GFDL-1.2-or-later', 'deprecated': False},
    +    'gfdl-1.3': {'id': 'GFDL-1.3', 'deprecated': True},
    +    'gfdl-1.3-invariants-only': {'id': 'GFDL-1.3-invariants-only', 'deprecated': False},
    +    'gfdl-1.3-invariants-or-later': {'id': 'GFDL-1.3-invariants-or-later', 'deprecated': False},
    +    'gfdl-1.3-no-invariants-only': {'id': 'GFDL-1.3-no-invariants-only', 'deprecated': False},
    +    'gfdl-1.3-no-invariants-or-later': {'id': 'GFDL-1.3-no-invariants-or-later', 'deprecated': False},
    +    'gfdl-1.3-only': {'id': 'GFDL-1.3-only', 'deprecated': False},
    +    'gfdl-1.3-or-later': {'id': 'GFDL-1.3-or-later', 'deprecated': False},
    +    'giftware': {'id': 'Giftware', 'deprecated': False},
    +    'gl2ps': {'id': 'GL2PS', 'deprecated': False},
    +    'glide': {'id': 'Glide', 'deprecated': False},
    +    'glulxe': {'id': 'Glulxe', 'deprecated': False},
    +    'glwtpl': {'id': 'GLWTPL', 'deprecated': False},
    +    'gnuplot': {'id': 'gnuplot', 'deprecated': False},
    +    'gpl-1.0': {'id': 'GPL-1.0', 'deprecated': True},
    +    'gpl-1.0+': {'id': 'GPL-1.0+', 'deprecated': True},
    +    'gpl-1.0-only': {'id': 'GPL-1.0-only', 'deprecated': False},
    +    'gpl-1.0-or-later': {'id': 'GPL-1.0-or-later', 'deprecated': False},
    +    'gpl-2.0': {'id': 'GPL-2.0', 'deprecated': True},
    +    'gpl-2.0+': {'id': 'GPL-2.0+', 'deprecated': True},
    +    'gpl-2.0-only': {'id': 'GPL-2.0-only', 'deprecated': False},
    +    'gpl-2.0-or-later': {'id': 'GPL-2.0-or-later', 'deprecated': False},
    +    'gpl-2.0-with-autoconf-exception': {'id': 'GPL-2.0-with-autoconf-exception', 'deprecated': True},
    +    'gpl-2.0-with-bison-exception': {'id': 'GPL-2.0-with-bison-exception', 'deprecated': True},
    +    'gpl-2.0-with-classpath-exception': {'id': 'GPL-2.0-with-classpath-exception', 'deprecated': True},
    +    'gpl-2.0-with-font-exception': {'id': 'GPL-2.0-with-font-exception', 'deprecated': True},
    +    'gpl-2.0-with-gcc-exception': {'id': 'GPL-2.0-with-GCC-exception', 'deprecated': True},
    +    'gpl-3.0': {'id': 'GPL-3.0', 'deprecated': True},
    +    'gpl-3.0+': {'id': 'GPL-3.0+', 'deprecated': True},
    +    'gpl-3.0-only': {'id': 'GPL-3.0-only', 'deprecated': False},
    +    'gpl-3.0-or-later': {'id': 'GPL-3.0-or-later', 'deprecated': False},
    +    'gpl-3.0-with-autoconf-exception': {'id': 'GPL-3.0-with-autoconf-exception', 'deprecated': True},
    +    'gpl-3.0-with-gcc-exception': {'id': 'GPL-3.0-with-GCC-exception', 'deprecated': True},
    +    'graphics-gems': {'id': 'Graphics-Gems', 'deprecated': False},
    +    'gsoap-1.3b': {'id': 'gSOAP-1.3b', 'deprecated': False},
    +    'gtkbook': {'id': 'gtkbook', 'deprecated': False},
    +    'gutmann': {'id': 'Gutmann', 'deprecated': False},
    +    'haskellreport': {'id': 'HaskellReport', 'deprecated': False},
    +    'hdf5': {'id': 'HDF5', 'deprecated': False},
    +    'hdparm': {'id': 'hdparm', 'deprecated': False},
    +    'hidapi': {'id': 'HIDAPI', 'deprecated': False},
    +    'hippocratic-2.1': {'id': 'Hippocratic-2.1', 'deprecated': False},
    +    'hp-1986': {'id': 'HP-1986', 'deprecated': False},
    +    'hp-1989': {'id': 'HP-1989', 'deprecated': False},
    +    'hpnd': {'id': 'HPND', 'deprecated': False},
    +    'hpnd-dec': {'id': 'HPND-DEC', 'deprecated': False},
    +    'hpnd-doc': {'id': 'HPND-doc', 'deprecated': False},
    +    'hpnd-doc-sell': {'id': 'HPND-doc-sell', 'deprecated': False},
    +    'hpnd-export-us': {'id': 'HPND-export-US', 'deprecated': False},
    +    'hpnd-export-us-acknowledgement': {'id': 'HPND-export-US-acknowledgement', 'deprecated': False},
    +    'hpnd-export-us-modify': {'id': 'HPND-export-US-modify', 'deprecated': False},
    +    'hpnd-export2-us': {'id': 'HPND-export2-US', 'deprecated': False},
    +    'hpnd-fenneberg-livingston': {'id': 'HPND-Fenneberg-Livingston', 'deprecated': False},
    +    'hpnd-inria-imag': {'id': 'HPND-INRIA-IMAG', 'deprecated': False},
    +    'hpnd-intel': {'id': 'HPND-Intel', 'deprecated': False},
    +    'hpnd-kevlin-henney': {'id': 'HPND-Kevlin-Henney', 'deprecated': False},
    +    'hpnd-markus-kuhn': {'id': 'HPND-Markus-Kuhn', 'deprecated': False},
    +    'hpnd-merchantability-variant': {'id': 'HPND-merchantability-variant', 'deprecated': False},
    +    'hpnd-mit-disclaimer': {'id': 'HPND-MIT-disclaimer', 'deprecated': False},
    +    'hpnd-netrek': {'id': 'HPND-Netrek', 'deprecated': False},
    +    'hpnd-pbmplus': {'id': 'HPND-Pbmplus', 'deprecated': False},
    +    'hpnd-sell-mit-disclaimer-xserver': {'id': 'HPND-sell-MIT-disclaimer-xserver', 'deprecated': False},
    +    'hpnd-sell-regexpr': {'id': 'HPND-sell-regexpr', 'deprecated': False},
    +    'hpnd-sell-variant': {'id': 'HPND-sell-variant', 'deprecated': False},
    +    'hpnd-sell-variant-mit-disclaimer': {'id': 'HPND-sell-variant-MIT-disclaimer', 'deprecated': False},
    +    'hpnd-sell-variant-mit-disclaimer-rev': {'id': 'HPND-sell-variant-MIT-disclaimer-rev', 'deprecated': False},
    +    'hpnd-uc': {'id': 'HPND-UC', 'deprecated': False},
    +    'hpnd-uc-export-us': {'id': 'HPND-UC-export-US', 'deprecated': False},
    +    'htmltidy': {'id': 'HTMLTIDY', 'deprecated': False},
    +    'ibm-pibs': {'id': 'IBM-pibs', 'deprecated': False},
    +    'icu': {'id': 'ICU', 'deprecated': False},
    +    'iec-code-components-eula': {'id': 'IEC-Code-Components-EULA', 'deprecated': False},
    +    'ijg': {'id': 'IJG', 'deprecated': False},
    +    'ijg-short': {'id': 'IJG-short', 'deprecated': False},
    +    'imagemagick': {'id': 'ImageMagick', 'deprecated': False},
    +    'imatix': {'id': 'iMatix', 'deprecated': False},
    +    'imlib2': {'id': 'Imlib2', 'deprecated': False},
    +    'info-zip': {'id': 'Info-ZIP', 'deprecated': False},
    +    'inner-net-2.0': {'id': 'Inner-Net-2.0', 'deprecated': False},
    +    'innosetup': {'id': 'InnoSetup', 'deprecated': False},
    +    'intel': {'id': 'Intel', 'deprecated': False},
    +    'intel-acpi': {'id': 'Intel-ACPI', 'deprecated': False},
    +    'interbase-1.0': {'id': 'Interbase-1.0', 'deprecated': False},
    +    'ipa': {'id': 'IPA', 'deprecated': False},
    +    'ipl-1.0': {'id': 'IPL-1.0', 'deprecated': False},
    +    'isc': {'id': 'ISC', 'deprecated': False},
    +    'isc-veillard': {'id': 'ISC-Veillard', 'deprecated': False},
    +    'jam': {'id': 'Jam', 'deprecated': False},
    +    'jasper-2.0': {'id': 'JasPer-2.0', 'deprecated': False},
    +    'jove': {'id': 'jove', 'deprecated': False},
    +    'jpl-image': {'id': 'JPL-image', 'deprecated': False},
    +    'jpnic': {'id': 'JPNIC', 'deprecated': False},
    +    'json': {'id': 'JSON', 'deprecated': False},
    +    'kastrup': {'id': 'Kastrup', 'deprecated': False},
    +    'kazlib': {'id': 'Kazlib', 'deprecated': False},
    +    'knuth-ctan': {'id': 'Knuth-CTAN', 'deprecated': False},
    +    'lal-1.2': {'id': 'LAL-1.2', 'deprecated': False},
    +    'lal-1.3': {'id': 'LAL-1.3', 'deprecated': False},
    +    'latex2e': {'id': 'Latex2e', 'deprecated': False},
    +    'latex2e-translated-notice': {'id': 'Latex2e-translated-notice', 'deprecated': False},
    +    'leptonica': {'id': 'Leptonica', 'deprecated': False},
    +    'lgpl-2.0': {'id': 'LGPL-2.0', 'deprecated': True},
    +    'lgpl-2.0+': {'id': 'LGPL-2.0+', 'deprecated': True},
    +    'lgpl-2.0-only': {'id': 'LGPL-2.0-only', 'deprecated': False},
    +    'lgpl-2.0-or-later': {'id': 'LGPL-2.0-or-later', 'deprecated': False},
    +    'lgpl-2.1': {'id': 'LGPL-2.1', 'deprecated': True},
    +    'lgpl-2.1+': {'id': 'LGPL-2.1+', 'deprecated': True},
    +    'lgpl-2.1-only': {'id': 'LGPL-2.1-only', 'deprecated': False},
    +    'lgpl-2.1-or-later': {'id': 'LGPL-2.1-or-later', 'deprecated': False},
    +    'lgpl-3.0': {'id': 'LGPL-3.0', 'deprecated': True},
    +    'lgpl-3.0+': {'id': 'LGPL-3.0+', 'deprecated': True},
    +    'lgpl-3.0-only': {'id': 'LGPL-3.0-only', 'deprecated': False},
    +    'lgpl-3.0-or-later': {'id': 'LGPL-3.0-or-later', 'deprecated': False},
    +    'lgpllr': {'id': 'LGPLLR', 'deprecated': False},
    +    'libpng': {'id': 'Libpng', 'deprecated': False},
    +    'libpng-1.6.35': {'id': 'libpng-1.6.35', 'deprecated': False},
    +    'libpng-2.0': {'id': 'libpng-2.0', 'deprecated': False},
    +    'libselinux-1.0': {'id': 'libselinux-1.0', 'deprecated': False},
    +    'libtiff': {'id': 'libtiff', 'deprecated': False},
    +    'libutil-david-nugent': {'id': 'libutil-David-Nugent', 'deprecated': False},
    +    'liliq-p-1.1': {'id': 'LiLiQ-P-1.1', 'deprecated': False},
    +    'liliq-r-1.1': {'id': 'LiLiQ-R-1.1', 'deprecated': False},
    +    'liliq-rplus-1.1': {'id': 'LiLiQ-Rplus-1.1', 'deprecated': False},
    +    'linux-man-pages-1-para': {'id': 'Linux-man-pages-1-para', 'deprecated': False},
    +    'linux-man-pages-copyleft': {'id': 'Linux-man-pages-copyleft', 'deprecated': False},
    +    'linux-man-pages-copyleft-2-para': {'id': 'Linux-man-pages-copyleft-2-para', 'deprecated': False},
    +    'linux-man-pages-copyleft-var': {'id': 'Linux-man-pages-copyleft-var', 'deprecated': False},
    +    'linux-openib': {'id': 'Linux-OpenIB', 'deprecated': False},
    +    'loop': {'id': 'LOOP', 'deprecated': False},
    +    'lpd-document': {'id': 'LPD-document', 'deprecated': False},
    +    'lpl-1.0': {'id': 'LPL-1.0', 'deprecated': False},
    +    'lpl-1.02': {'id': 'LPL-1.02', 'deprecated': False},
    +    'lppl-1.0': {'id': 'LPPL-1.0', 'deprecated': False},
    +    'lppl-1.1': {'id': 'LPPL-1.1', 'deprecated': False},
    +    'lppl-1.2': {'id': 'LPPL-1.2', 'deprecated': False},
    +    'lppl-1.3a': {'id': 'LPPL-1.3a', 'deprecated': False},
    +    'lppl-1.3c': {'id': 'LPPL-1.3c', 'deprecated': False},
    +    'lsof': {'id': 'lsof', 'deprecated': False},
    +    'lucida-bitmap-fonts': {'id': 'Lucida-Bitmap-Fonts', 'deprecated': False},
    +    'lzma-sdk-9.11-to-9.20': {'id': 'LZMA-SDK-9.11-to-9.20', 'deprecated': False},
    +    'lzma-sdk-9.22': {'id': 'LZMA-SDK-9.22', 'deprecated': False},
    +    'mackerras-3-clause': {'id': 'Mackerras-3-Clause', 'deprecated': False},
    +    'mackerras-3-clause-acknowledgment': {'id': 'Mackerras-3-Clause-acknowledgment', 'deprecated': False},
    +    'magaz': {'id': 'magaz', 'deprecated': False},
    +    'mailprio': {'id': 'mailprio', 'deprecated': False},
    +    'makeindex': {'id': 'MakeIndex', 'deprecated': False},
    +    'man2html': {'id': 'man2html', 'deprecated': False},
    +    'martin-birgmeier': {'id': 'Martin-Birgmeier', 'deprecated': False},
    +    'mcphee-slideshow': {'id': 'McPhee-slideshow', 'deprecated': False},
    +    'metamail': {'id': 'metamail', 'deprecated': False},
    +    'minpack': {'id': 'Minpack', 'deprecated': False},
    +    'mips': {'id': 'MIPS', 'deprecated': False},
    +    'miros': {'id': 'MirOS', 'deprecated': False},
    +    'mit': {'id': 'MIT', 'deprecated': False},
    +    'mit-0': {'id': 'MIT-0', 'deprecated': False},
    +    'mit-advertising': {'id': 'MIT-advertising', 'deprecated': False},
    +    'mit-click': {'id': 'MIT-Click', 'deprecated': False},
    +    'mit-cmu': {'id': 'MIT-CMU', 'deprecated': False},
    +    'mit-enna': {'id': 'MIT-enna', 'deprecated': False},
    +    'mit-feh': {'id': 'MIT-feh', 'deprecated': False},
    +    'mit-festival': {'id': 'MIT-Festival', 'deprecated': False},
    +    'mit-khronos-old': {'id': 'MIT-Khronos-old', 'deprecated': False},
    +    'mit-modern-variant': {'id': 'MIT-Modern-Variant', 'deprecated': False},
    +    'mit-open-group': {'id': 'MIT-open-group', 'deprecated': False},
    +    'mit-testregex': {'id': 'MIT-testregex', 'deprecated': False},
    +    'mit-wu': {'id': 'MIT-Wu', 'deprecated': False},
    +    'mitnfa': {'id': 'MITNFA', 'deprecated': False},
    +    'mmixware': {'id': 'MMIXware', 'deprecated': False},
    +    'motosoto': {'id': 'Motosoto', 'deprecated': False},
    +    'mpeg-ssg': {'id': 'MPEG-SSG', 'deprecated': False},
    +    'mpi-permissive': {'id': 'mpi-permissive', 'deprecated': False},
    +    'mpich2': {'id': 'mpich2', 'deprecated': False},
    +    'mpl-1.0': {'id': 'MPL-1.0', 'deprecated': False},
    +    'mpl-1.1': {'id': 'MPL-1.1', 'deprecated': False},
    +    'mpl-2.0': {'id': 'MPL-2.0', 'deprecated': False},
    +    'mpl-2.0-no-copyleft-exception': {'id': 'MPL-2.0-no-copyleft-exception', 'deprecated': False},
    +    'mplus': {'id': 'mplus', 'deprecated': False},
    +    'ms-lpl': {'id': 'MS-LPL', 'deprecated': False},
    +    'ms-pl': {'id': 'MS-PL', 'deprecated': False},
    +    'ms-rl': {'id': 'MS-RL', 'deprecated': False},
    +    'mtll': {'id': 'MTLL', 'deprecated': False},
    +    'mulanpsl-1.0': {'id': 'MulanPSL-1.0', 'deprecated': False},
    +    'mulanpsl-2.0': {'id': 'MulanPSL-2.0', 'deprecated': False},
    +    'multics': {'id': 'Multics', 'deprecated': False},
    +    'mup': {'id': 'Mup', 'deprecated': False},
    +    'naist-2003': {'id': 'NAIST-2003', 'deprecated': False},
    +    'nasa-1.3': {'id': 'NASA-1.3', 'deprecated': False},
    +    'naumen': {'id': 'Naumen', 'deprecated': False},
    +    'nbpl-1.0': {'id': 'NBPL-1.0', 'deprecated': False},
    +    'ncbi-pd': {'id': 'NCBI-PD', 'deprecated': False},
    +    'ncgl-uk-2.0': {'id': 'NCGL-UK-2.0', 'deprecated': False},
    +    'ncl': {'id': 'NCL', 'deprecated': False},
    +    'ncsa': {'id': 'NCSA', 'deprecated': False},
    +    'net-snmp': {'id': 'Net-SNMP', 'deprecated': True},
    +    'netcdf': {'id': 'NetCDF', 'deprecated': False},
    +    'newsletr': {'id': 'Newsletr', 'deprecated': False},
    +    'ngpl': {'id': 'NGPL', 'deprecated': False},
    +    'ngrep': {'id': 'ngrep', 'deprecated': False},
    +    'nicta-1.0': {'id': 'NICTA-1.0', 'deprecated': False},
    +    'nist-pd': {'id': 'NIST-PD', 'deprecated': False},
    +    'nist-pd-fallback': {'id': 'NIST-PD-fallback', 'deprecated': False},
    +    'nist-software': {'id': 'NIST-Software', 'deprecated': False},
    +    'nlod-1.0': {'id': 'NLOD-1.0', 'deprecated': False},
    +    'nlod-2.0': {'id': 'NLOD-2.0', 'deprecated': False},
    +    'nlpl': {'id': 'NLPL', 'deprecated': False},
    +    'nokia': {'id': 'Nokia', 'deprecated': False},
    +    'nosl': {'id': 'NOSL', 'deprecated': False},
    +    'noweb': {'id': 'Noweb', 'deprecated': False},
    +    'npl-1.0': {'id': 'NPL-1.0', 'deprecated': False},
    +    'npl-1.1': {'id': 'NPL-1.1', 'deprecated': False},
    +    'nposl-3.0': {'id': 'NPOSL-3.0', 'deprecated': False},
    +    'nrl': {'id': 'NRL', 'deprecated': False},
    +    'ntia-pd': {'id': 'NTIA-PD', 'deprecated': False},
    +    'ntp': {'id': 'NTP', 'deprecated': False},
    +    'ntp-0': {'id': 'NTP-0', 'deprecated': False},
    +    'nunit': {'id': 'Nunit', 'deprecated': True},
    +    'o-uda-1.0': {'id': 'O-UDA-1.0', 'deprecated': False},
    +    'oar': {'id': 'OAR', 'deprecated': False},
    +    'occt-pl': {'id': 'OCCT-PL', 'deprecated': False},
    +    'oclc-2.0': {'id': 'OCLC-2.0', 'deprecated': False},
    +    'odbl-1.0': {'id': 'ODbL-1.0', 'deprecated': False},
    +    'odc-by-1.0': {'id': 'ODC-By-1.0', 'deprecated': False},
    +    'offis': {'id': 'OFFIS', 'deprecated': False},
    +    'ofl-1.0': {'id': 'OFL-1.0', 'deprecated': False},
    +    'ofl-1.0-no-rfn': {'id': 'OFL-1.0-no-RFN', 'deprecated': False},
    +    'ofl-1.0-rfn': {'id': 'OFL-1.0-RFN', 'deprecated': False},
    +    'ofl-1.1': {'id': 'OFL-1.1', 'deprecated': False},
    +    'ofl-1.1-no-rfn': {'id': 'OFL-1.1-no-RFN', 'deprecated': False},
    +    'ofl-1.1-rfn': {'id': 'OFL-1.1-RFN', 'deprecated': False},
    +    'ogc-1.0': {'id': 'OGC-1.0', 'deprecated': False},
    +    'ogdl-taiwan-1.0': {'id': 'OGDL-Taiwan-1.0', 'deprecated': False},
    +    'ogl-canada-2.0': {'id': 'OGL-Canada-2.0', 'deprecated': False},
    +    'ogl-uk-1.0': {'id': 'OGL-UK-1.0', 'deprecated': False},
    +    'ogl-uk-2.0': {'id': 'OGL-UK-2.0', 'deprecated': False},
    +    'ogl-uk-3.0': {'id': 'OGL-UK-3.0', 'deprecated': False},
    +    'ogtsl': {'id': 'OGTSL', 'deprecated': False},
    +    'oldap-1.1': {'id': 'OLDAP-1.1', 'deprecated': False},
    +    'oldap-1.2': {'id': 'OLDAP-1.2', 'deprecated': False},
    +    'oldap-1.3': {'id': 'OLDAP-1.3', 'deprecated': False},
    +    'oldap-1.4': {'id': 'OLDAP-1.4', 'deprecated': False},
    +    'oldap-2.0': {'id': 'OLDAP-2.0', 'deprecated': False},
    +    'oldap-2.0.1': {'id': 'OLDAP-2.0.1', 'deprecated': False},
    +    'oldap-2.1': {'id': 'OLDAP-2.1', 'deprecated': False},
    +    'oldap-2.2': {'id': 'OLDAP-2.2', 'deprecated': False},
    +    'oldap-2.2.1': {'id': 'OLDAP-2.2.1', 'deprecated': False},
    +    'oldap-2.2.2': {'id': 'OLDAP-2.2.2', 'deprecated': False},
    +    'oldap-2.3': {'id': 'OLDAP-2.3', 'deprecated': False},
    +    'oldap-2.4': {'id': 'OLDAP-2.4', 'deprecated': False},
    +    'oldap-2.5': {'id': 'OLDAP-2.5', 'deprecated': False},
    +    'oldap-2.6': {'id': 'OLDAP-2.6', 'deprecated': False},
    +    'oldap-2.7': {'id': 'OLDAP-2.7', 'deprecated': False},
    +    'oldap-2.8': {'id': 'OLDAP-2.8', 'deprecated': False},
    +    'olfl-1.3': {'id': 'OLFL-1.3', 'deprecated': False},
    +    'oml': {'id': 'OML', 'deprecated': False},
    +    'openpbs-2.3': {'id': 'OpenPBS-2.3', 'deprecated': False},
    +    'openssl': {'id': 'OpenSSL', 'deprecated': False},
    +    'openssl-standalone': {'id': 'OpenSSL-standalone', 'deprecated': False},
    +    'openvision': {'id': 'OpenVision', 'deprecated': False},
    +    'opl-1.0': {'id': 'OPL-1.0', 'deprecated': False},
    +    'opl-uk-3.0': {'id': 'OPL-UK-3.0', 'deprecated': False},
    +    'opubl-1.0': {'id': 'OPUBL-1.0', 'deprecated': False},
    +    'oset-pl-2.1': {'id': 'OSET-PL-2.1', 'deprecated': False},
    +    'osl-1.0': {'id': 'OSL-1.0', 'deprecated': False},
    +    'osl-1.1': {'id': 'OSL-1.1', 'deprecated': False},
    +    'osl-2.0': {'id': 'OSL-2.0', 'deprecated': False},
    +    'osl-2.1': {'id': 'OSL-2.1', 'deprecated': False},
    +    'osl-3.0': {'id': 'OSL-3.0', 'deprecated': False},
    +    'padl': {'id': 'PADL', 'deprecated': False},
    +    'parity-6.0.0': {'id': 'Parity-6.0.0', 'deprecated': False},
    +    'parity-7.0.0': {'id': 'Parity-7.0.0', 'deprecated': False},
    +    'pddl-1.0': {'id': 'PDDL-1.0', 'deprecated': False},
    +    'php-3.0': {'id': 'PHP-3.0', 'deprecated': False},
    +    'php-3.01': {'id': 'PHP-3.01', 'deprecated': False},
    +    'pixar': {'id': 'Pixar', 'deprecated': False},
    +    'pkgconf': {'id': 'pkgconf', 'deprecated': False},
    +    'plexus': {'id': 'Plexus', 'deprecated': False},
    +    'pnmstitch': {'id': 'pnmstitch', 'deprecated': False},
    +    'polyform-noncommercial-1.0.0': {'id': 'PolyForm-Noncommercial-1.0.0', 'deprecated': False},
    +    'polyform-small-business-1.0.0': {'id': 'PolyForm-Small-Business-1.0.0', 'deprecated': False},
    +    'postgresql': {'id': 'PostgreSQL', 'deprecated': False},
    +    'ppl': {'id': 'PPL', 'deprecated': False},
    +    'psf-2.0': {'id': 'PSF-2.0', 'deprecated': False},
    +    'psfrag': {'id': 'psfrag', 'deprecated': False},
    +    'psutils': {'id': 'psutils', 'deprecated': False},
    +    'python-2.0': {'id': 'Python-2.0', 'deprecated': False},
    +    'python-2.0.1': {'id': 'Python-2.0.1', 'deprecated': False},
    +    'python-ldap': {'id': 'python-ldap', 'deprecated': False},
    +    'qhull': {'id': 'Qhull', 'deprecated': False},
    +    'qpl-1.0': {'id': 'QPL-1.0', 'deprecated': False},
    +    'qpl-1.0-inria-2004': {'id': 'QPL-1.0-INRIA-2004', 'deprecated': False},
    +    'radvd': {'id': 'radvd', 'deprecated': False},
    +    'rdisc': {'id': 'Rdisc', 'deprecated': False},
    +    'rhecos-1.1': {'id': 'RHeCos-1.1', 'deprecated': False},
    +    'rpl-1.1': {'id': 'RPL-1.1', 'deprecated': False},
    +    'rpl-1.5': {'id': 'RPL-1.5', 'deprecated': False},
    +    'rpsl-1.0': {'id': 'RPSL-1.0', 'deprecated': False},
    +    'rsa-md': {'id': 'RSA-MD', 'deprecated': False},
    +    'rscpl': {'id': 'RSCPL', 'deprecated': False},
    +    'ruby': {'id': 'Ruby', 'deprecated': False},
    +    'ruby-pty': {'id': 'Ruby-pty', 'deprecated': False},
    +    'sax-pd': {'id': 'SAX-PD', 'deprecated': False},
    +    'sax-pd-2.0': {'id': 'SAX-PD-2.0', 'deprecated': False},
    +    'saxpath': {'id': 'Saxpath', 'deprecated': False},
    +    'scea': {'id': 'SCEA', 'deprecated': False},
    +    'schemereport': {'id': 'SchemeReport', 'deprecated': False},
    +    'sendmail': {'id': 'Sendmail', 'deprecated': False},
    +    'sendmail-8.23': {'id': 'Sendmail-8.23', 'deprecated': False},
    +    'sendmail-open-source-1.1': {'id': 'Sendmail-Open-Source-1.1', 'deprecated': False},
    +    'sgi-b-1.0': {'id': 'SGI-B-1.0', 'deprecated': False},
    +    'sgi-b-1.1': {'id': 'SGI-B-1.1', 'deprecated': False},
    +    'sgi-b-2.0': {'id': 'SGI-B-2.0', 'deprecated': False},
    +    'sgi-opengl': {'id': 'SGI-OpenGL', 'deprecated': False},
    +    'sgp4': {'id': 'SGP4', 'deprecated': False},
    +    'shl-0.5': {'id': 'SHL-0.5', 'deprecated': False},
    +    'shl-0.51': {'id': 'SHL-0.51', 'deprecated': False},
    +    'simpl-2.0': {'id': 'SimPL-2.0', 'deprecated': False},
    +    'sissl': {'id': 'SISSL', 'deprecated': False},
    +    'sissl-1.2': {'id': 'SISSL-1.2', 'deprecated': False},
    +    'sl': {'id': 'SL', 'deprecated': False},
    +    'sleepycat': {'id': 'Sleepycat', 'deprecated': False},
    +    'smail-gpl': {'id': 'SMAIL-GPL', 'deprecated': False},
    +    'smlnj': {'id': 'SMLNJ', 'deprecated': False},
    +    'smppl': {'id': 'SMPPL', 'deprecated': False},
    +    'snia': {'id': 'SNIA', 'deprecated': False},
    +    'snprintf': {'id': 'snprintf', 'deprecated': False},
    +    'sofa': {'id': 'SOFA', 'deprecated': False},
    +    'softsurfer': {'id': 'softSurfer', 'deprecated': False},
    +    'soundex': {'id': 'Soundex', 'deprecated': False},
    +    'spencer-86': {'id': 'Spencer-86', 'deprecated': False},
    +    'spencer-94': {'id': 'Spencer-94', 'deprecated': False},
    +    'spencer-99': {'id': 'Spencer-99', 'deprecated': False},
    +    'spl-1.0': {'id': 'SPL-1.0', 'deprecated': False},
    +    'ssh-keyscan': {'id': 'ssh-keyscan', 'deprecated': False},
    +    'ssh-openssh': {'id': 'SSH-OpenSSH', 'deprecated': False},
    +    'ssh-short': {'id': 'SSH-short', 'deprecated': False},
    +    'ssleay-standalone': {'id': 'SSLeay-standalone', 'deprecated': False},
    +    'sspl-1.0': {'id': 'SSPL-1.0', 'deprecated': False},
    +    'standardml-nj': {'id': 'StandardML-NJ', 'deprecated': True},
    +    'sugarcrm-1.1.3': {'id': 'SugarCRM-1.1.3', 'deprecated': False},
    +    'sul-1.0': {'id': 'SUL-1.0', 'deprecated': False},
    +    'sun-ppp': {'id': 'Sun-PPP', 'deprecated': False},
    +    'sun-ppp-2000': {'id': 'Sun-PPP-2000', 'deprecated': False},
    +    'sunpro': {'id': 'SunPro', 'deprecated': False},
    +    'swl': {'id': 'SWL', 'deprecated': False},
    +    'swrule': {'id': 'swrule', 'deprecated': False},
    +    'symlinks': {'id': 'Symlinks', 'deprecated': False},
    +    'tapr-ohl-1.0': {'id': 'TAPR-OHL-1.0', 'deprecated': False},
    +    'tcl': {'id': 'TCL', 'deprecated': False},
    +    'tcp-wrappers': {'id': 'TCP-wrappers', 'deprecated': False},
    +    'termreadkey': {'id': 'TermReadKey', 'deprecated': False},
    +    'tgppl-1.0': {'id': 'TGPPL-1.0', 'deprecated': False},
    +    'thirdeye': {'id': 'ThirdEye', 'deprecated': False},
    +    'threeparttable': {'id': 'threeparttable', 'deprecated': False},
    +    'tmate': {'id': 'TMate', 'deprecated': False},
    +    'torque-1.1': {'id': 'TORQUE-1.1', 'deprecated': False},
    +    'tosl': {'id': 'TOSL', 'deprecated': False},
    +    'tpdl': {'id': 'TPDL', 'deprecated': False},
    +    'tpl-1.0': {'id': 'TPL-1.0', 'deprecated': False},
    +    'trustedqsl': {'id': 'TrustedQSL', 'deprecated': False},
    +    'ttwl': {'id': 'TTWL', 'deprecated': False},
    +    'ttyp0': {'id': 'TTYP0', 'deprecated': False},
    +    'tu-berlin-1.0': {'id': 'TU-Berlin-1.0', 'deprecated': False},
    +    'tu-berlin-2.0': {'id': 'TU-Berlin-2.0', 'deprecated': False},
    +    'ubuntu-font-1.0': {'id': 'Ubuntu-font-1.0', 'deprecated': False},
    +    'ucar': {'id': 'UCAR', 'deprecated': False},
    +    'ucl-1.0': {'id': 'UCL-1.0', 'deprecated': False},
    +    'ulem': {'id': 'ulem', 'deprecated': False},
    +    'umich-merit': {'id': 'UMich-Merit', 'deprecated': False},
    +    'unicode-3.0': {'id': 'Unicode-3.0', 'deprecated': False},
    +    'unicode-dfs-2015': {'id': 'Unicode-DFS-2015', 'deprecated': False},
    +    'unicode-dfs-2016': {'id': 'Unicode-DFS-2016', 'deprecated': False},
    +    'unicode-tou': {'id': 'Unicode-TOU', 'deprecated': False},
    +    'unixcrypt': {'id': 'UnixCrypt', 'deprecated': False},
    +    'unlicense': {'id': 'Unlicense', 'deprecated': False},
    +    'unlicense-libtelnet': {'id': 'Unlicense-libtelnet', 'deprecated': False},
    +    'unlicense-libwhirlpool': {'id': 'Unlicense-libwhirlpool', 'deprecated': False},
    +    'upl-1.0': {'id': 'UPL-1.0', 'deprecated': False},
    +    'urt-rle': {'id': 'URT-RLE', 'deprecated': False},
    +    'vim': {'id': 'Vim', 'deprecated': False},
    +    'vostrom': {'id': 'VOSTROM', 'deprecated': False},
    +    'vsl-1.0': {'id': 'VSL-1.0', 'deprecated': False},
    +    'w3c': {'id': 'W3C', 'deprecated': False},
    +    'w3c-19980720': {'id': 'W3C-19980720', 'deprecated': False},
    +    'w3c-20150513': {'id': 'W3C-20150513', 'deprecated': False},
    +    'w3m': {'id': 'w3m', 'deprecated': False},
    +    'watcom-1.0': {'id': 'Watcom-1.0', 'deprecated': False},
    +    'widget-workshop': {'id': 'Widget-Workshop', 'deprecated': False},
    +    'wsuipa': {'id': 'Wsuipa', 'deprecated': False},
    +    'wtfpl': {'id': 'WTFPL', 'deprecated': False},
    +    'wwl': {'id': 'wwl', 'deprecated': False},
    +    'wxwindows': {'id': 'wxWindows', 'deprecated': True},
    +    'x11': {'id': 'X11', 'deprecated': False},
    +    'x11-distribute-modifications-variant': {'id': 'X11-distribute-modifications-variant', 'deprecated': False},
    +    'x11-swapped': {'id': 'X11-swapped', 'deprecated': False},
    +    'xdebug-1.03': {'id': 'Xdebug-1.03', 'deprecated': False},
    +    'xerox': {'id': 'Xerox', 'deprecated': False},
    +    'xfig': {'id': 'Xfig', 'deprecated': False},
    +    'xfree86-1.1': {'id': 'XFree86-1.1', 'deprecated': False},
    +    'xinetd': {'id': 'xinetd', 'deprecated': False},
    +    'xkeyboard-config-zinoviev': {'id': 'xkeyboard-config-Zinoviev', 'deprecated': False},
    +    'xlock': {'id': 'xlock', 'deprecated': False},
    +    'xnet': {'id': 'Xnet', 'deprecated': False},
    +    'xpp': {'id': 'xpp', 'deprecated': False},
    +    'xskat': {'id': 'XSkat', 'deprecated': False},
    +    'xzoom': {'id': 'xzoom', 'deprecated': False},
    +    'ypl-1.0': {'id': 'YPL-1.0', 'deprecated': False},
    +    'ypl-1.1': {'id': 'YPL-1.1', 'deprecated': False},
    +    'zed': {'id': 'Zed', 'deprecated': False},
    +    'zeeff': {'id': 'Zeeff', 'deprecated': False},
    +    'zend-2.0': {'id': 'Zend-2.0', 'deprecated': False},
    +    'zimbra-1.3': {'id': 'Zimbra-1.3', 'deprecated': False},
    +    'zimbra-1.4': {'id': 'Zimbra-1.4', 'deprecated': False},
    +    'zlib': {'id': 'Zlib', 'deprecated': False},
    +    'zlib-acknowledgement': {'id': 'zlib-acknowledgement', 'deprecated': False},
    +    'zpl-1.1': {'id': 'ZPL-1.1', 'deprecated': False},
    +    'zpl-2.0': {'id': 'ZPL-2.0', 'deprecated': False},
    +    'zpl-2.1': {'id': 'ZPL-2.1', 'deprecated': False},
    +}
    +
    +EXCEPTIONS: dict[str, SPDXException] = {
    +    '389-exception': {'id': '389-exception', 'deprecated': False},
    +    'asterisk-exception': {'id': 'Asterisk-exception', 'deprecated': False},
    +    'asterisk-linking-protocols-exception': {'id': 'Asterisk-linking-protocols-exception', 'deprecated': False},
    +    'autoconf-exception-2.0': {'id': 'Autoconf-exception-2.0', 'deprecated': False},
    +    'autoconf-exception-3.0': {'id': 'Autoconf-exception-3.0', 'deprecated': False},
    +    'autoconf-exception-generic': {'id': 'Autoconf-exception-generic', 'deprecated': False},
    +    'autoconf-exception-generic-3.0': {'id': 'Autoconf-exception-generic-3.0', 'deprecated': False},
    +    'autoconf-exception-macro': {'id': 'Autoconf-exception-macro', 'deprecated': False},
    +    'bison-exception-1.24': {'id': 'Bison-exception-1.24', 'deprecated': False},
    +    'bison-exception-2.2': {'id': 'Bison-exception-2.2', 'deprecated': False},
    +    'bootloader-exception': {'id': 'Bootloader-exception', 'deprecated': False},
    +    'cgal-linking-exception': {'id': 'CGAL-linking-exception', 'deprecated': False},
    +    'classpath-exception-2.0': {'id': 'Classpath-exception-2.0', 'deprecated': False},
    +    'clisp-exception-2.0': {'id': 'CLISP-exception-2.0', 'deprecated': False},
    +    'cryptsetup-openssl-exception': {'id': 'cryptsetup-OpenSSL-exception', 'deprecated': False},
    +    'digia-qt-lgpl-exception-1.1': {'id': 'Digia-Qt-LGPL-exception-1.1', 'deprecated': False},
    +    'digirule-foss-exception': {'id': 'DigiRule-FOSS-exception', 'deprecated': False},
    +    'ecos-exception-2.0': {'id': 'eCos-exception-2.0', 'deprecated': False},
    +    'erlang-otp-linking-exception': {'id': 'erlang-otp-linking-exception', 'deprecated': False},
    +    'fawkes-runtime-exception': {'id': 'Fawkes-Runtime-exception', 'deprecated': False},
    +    'fltk-exception': {'id': 'FLTK-exception', 'deprecated': False},
    +    'fmt-exception': {'id': 'fmt-exception', 'deprecated': False},
    +    'font-exception-2.0': {'id': 'Font-exception-2.0', 'deprecated': False},
    +    'freertos-exception-2.0': {'id': 'freertos-exception-2.0', 'deprecated': False},
    +    'gcc-exception-2.0': {'id': 'GCC-exception-2.0', 'deprecated': False},
    +    'gcc-exception-2.0-note': {'id': 'GCC-exception-2.0-note', 'deprecated': False},
    +    'gcc-exception-3.1': {'id': 'GCC-exception-3.1', 'deprecated': False},
    +    'gmsh-exception': {'id': 'Gmsh-exception', 'deprecated': False},
    +    'gnat-exception': {'id': 'GNAT-exception', 'deprecated': False},
    +    'gnome-examples-exception': {'id': 'GNOME-examples-exception', 'deprecated': False},
    +    'gnu-compiler-exception': {'id': 'GNU-compiler-exception', 'deprecated': False},
    +    'gnu-javamail-exception': {'id': 'gnu-javamail-exception', 'deprecated': False},
    +    'gpl-3.0-389-ds-base-exception': {'id': 'GPL-3.0-389-ds-base-exception', 'deprecated': False},
    +    'gpl-3.0-interface-exception': {'id': 'GPL-3.0-interface-exception', 'deprecated': False},
    +    'gpl-3.0-linking-exception': {'id': 'GPL-3.0-linking-exception', 'deprecated': False},
    +    'gpl-3.0-linking-source-exception': {'id': 'GPL-3.0-linking-source-exception', 'deprecated': False},
    +    'gpl-cc-1.0': {'id': 'GPL-CC-1.0', 'deprecated': False},
    +    'gstreamer-exception-2005': {'id': 'GStreamer-exception-2005', 'deprecated': False},
    +    'gstreamer-exception-2008': {'id': 'GStreamer-exception-2008', 'deprecated': False},
    +    'harbour-exception': {'id': 'harbour-exception', 'deprecated': False},
    +    'i2p-gpl-java-exception': {'id': 'i2p-gpl-java-exception', 'deprecated': False},
    +    'independent-modules-exception': {'id': 'Independent-modules-exception', 'deprecated': False},
    +    'kicad-libraries-exception': {'id': 'KiCad-libraries-exception', 'deprecated': False},
    +    'lgpl-3.0-linking-exception': {'id': 'LGPL-3.0-linking-exception', 'deprecated': False},
    +    'libpri-openh323-exception': {'id': 'libpri-OpenH323-exception', 'deprecated': False},
    +    'libtool-exception': {'id': 'Libtool-exception', 'deprecated': False},
    +    'linux-syscall-note': {'id': 'Linux-syscall-note', 'deprecated': False},
    +    'llgpl': {'id': 'LLGPL', 'deprecated': False},
    +    'llvm-exception': {'id': 'LLVM-exception', 'deprecated': False},
    +    'lzma-exception': {'id': 'LZMA-exception', 'deprecated': False},
    +    'mif-exception': {'id': 'mif-exception', 'deprecated': False},
    +    'mxml-exception': {'id': 'mxml-exception', 'deprecated': False},
    +    'nokia-qt-exception-1.1': {'id': 'Nokia-Qt-exception-1.1', 'deprecated': True},
    +    'ocaml-lgpl-linking-exception': {'id': 'OCaml-LGPL-linking-exception', 'deprecated': False},
    +    'occt-exception-1.0': {'id': 'OCCT-exception-1.0', 'deprecated': False},
    +    'openjdk-assembly-exception-1.0': {'id': 'OpenJDK-assembly-exception-1.0', 'deprecated': False},
    +    'openvpn-openssl-exception': {'id': 'openvpn-openssl-exception', 'deprecated': False},
    +    'pcre2-exception': {'id': 'PCRE2-exception', 'deprecated': False},
    +    'polyparse-exception': {'id': 'polyparse-exception', 'deprecated': False},
    +    'ps-or-pdf-font-exception-20170817': {'id': 'PS-or-PDF-font-exception-20170817', 'deprecated': False},
    +    'qpl-1.0-inria-2004-exception': {'id': 'QPL-1.0-INRIA-2004-exception', 'deprecated': False},
    +    'qt-gpl-exception-1.0': {'id': 'Qt-GPL-exception-1.0', 'deprecated': False},
    +    'qt-lgpl-exception-1.1': {'id': 'Qt-LGPL-exception-1.1', 'deprecated': False},
    +    'qwt-exception-1.0': {'id': 'Qwt-exception-1.0', 'deprecated': False},
    +    'romic-exception': {'id': 'romic-exception', 'deprecated': False},
    +    'rrdtool-floss-exception-2.0': {'id': 'RRDtool-FLOSS-exception-2.0', 'deprecated': False},
    +    'sane-exception': {'id': 'SANE-exception', 'deprecated': False},
    +    'shl-2.0': {'id': 'SHL-2.0', 'deprecated': False},
    +    'shl-2.1': {'id': 'SHL-2.1', 'deprecated': False},
    +    'stunnel-exception': {'id': 'stunnel-exception', 'deprecated': False},
    +    'swi-exception': {'id': 'SWI-exception', 'deprecated': False},
    +    'swift-exception': {'id': 'Swift-exception', 'deprecated': False},
    +    'texinfo-exception': {'id': 'Texinfo-exception', 'deprecated': False},
    +    'u-boot-exception-2.0': {'id': 'u-boot-exception-2.0', 'deprecated': False},
    +    'ubdl-exception': {'id': 'UBDL-exception', 'deprecated': False},
    +    'universal-foss-exception-1.0': {'id': 'Universal-FOSS-exception-1.0', 'deprecated': False},
    +    'vsftpd-openssl-exception': {'id': 'vsftpd-openssl-exception', 'deprecated': False},
    +    'wxwindows-exception-3.1': {'id': 'WxWindows-exception-3.1', 'deprecated': False},
    +    'x11vnc-openssl-exception': {'id': 'x11vnc-openssl-exception', 'deprecated': False},
    +}
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/markers.py b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/markers.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..564451f530bad3dbbd95ae7575835967b1ea45df
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/markers.py
    @@ -0,0 +1,492 @@
    +# This file is dual licensed under the terms of the Apache License, Version
    +# 2.0, and the BSD License. See the LICENSE file in the root of this repository
    +# for complete details.
    +
    +from __future__ import annotations
    +
    +import operator
    +import os
    +import platform
    +import sys
    +from typing import AbstractSet, Callable, Literal, Mapping, TypedDict, Union, cast
    +
    +from ._parser import MarkerAtom, MarkerList, Op, Value, Variable
    +from ._parser import parse_marker as _parse_marker
    +from ._tokenizer import ParserSyntaxError
    +from .specifiers import InvalidSpecifier, Specifier
    +from .utils import canonicalize_name
    +
    +__all__ = [
    +    "Environment",
    +    "EvaluateContext",
    +    "InvalidMarker",
    +    "Marker",
    +    "UndefinedComparison",
    +    "UndefinedEnvironmentName",
    +    "default_environment",
    +]
    +
    +
    +def __dir__() -> list[str]:
    +    return __all__
    +
    +
    +Operator = Callable[[str, Union[str, AbstractSet[str]]], bool]
    +EvaluateContext = Literal["metadata", "lock_file", "requirement"]
    +"""A ``typing.Literal`` enumerating valid marker evaluation contexts.
    +
    +Valid values for the ``context`` passed to :meth:`Marker.evaluate` are:
    +
    +* ``"metadata"`` (for core metadata; default)
    +* ``"lock_file"`` (for lock files)
    +* ``"requirement"`` (i.e. all other situations)
    +"""
    +
    +MARKERS_ALLOWING_SET = {"extras", "dependency_groups"}
    +MARKERS_REQUIRING_VERSION = {
    +    "implementation_version",
    +    "platform_release",
    +    "python_full_version",
    +    "python_version",
    +}
    +
    +
    +class InvalidMarker(ValueError):
    +    """Raised when attempting to create a :class:`Marker` from invalid input.
    +
    +    This error indicates that the given marker string does not conform to the
    +    :ref:`specification of dependency specifiers `.
    +    """
    +
    +
    +class UndefinedComparison(ValueError):
    +    """Raised when evaluating an unsupported marker comparison.
    +
    +    This can happen when marker values are compared as versions but do not
    +    conform to the :ref:`specification of version specifiers
    +    `.
    +    """
    +
    +
    +class UndefinedEnvironmentName(ValueError):
    +    """Raised when evaluating a marker that references a missing environment key."""
    +
    +
    +class Environment(TypedDict):
    +    """
    +    A dictionary that represents a Python environment as captured by
    +    :func:`default_environment`. All fields are required.
    +    """
    +
    +    implementation_name: str
    +    """The implementation's identifier, e.g. ``'cpython'``."""
    +
    +    implementation_version: str
    +    """
    +    The implementation's version, e.g. ``'3.13.0a2'`` for CPython 3.13.0a2, or
    +    ``'7.3.13'`` for PyPy3.10 v7.3.13.
    +    """
    +
    +    os_name: str
    +    """
    +    The value of :py:data:`os.name`. The name of the operating system dependent module
    +    imported, e.g. ``'posix'``.
    +    """
    +
    +    platform_machine: str
    +    """
    +    Returns the machine type, e.g. ``'i386'``.
    +
    +    An empty string if the value cannot be determined.
    +    """
    +
    +    platform_release: str
    +    """
    +    The system's release, e.g. ``'2.2.0'`` or ``'NT'``.
    +
    +    An empty string if the value cannot be determined.
    +    """
    +
    +    platform_system: str
    +    """
    +    The system/OS name, e.g. ``'Linux'``, ``'Windows'`` or ``'Java'``.
    +
    +    An empty string if the value cannot be determined.
    +    """
    +
    +    platform_version: str
    +    """
    +    The system's release version, e.g. ``'#3 on degas'``.
    +
    +    An empty string if the value cannot be determined.
    +    """
    +
    +    python_full_version: str
    +    """
    +    The Python version as string ``'major.minor.patchlevel'``.
    +
    +    Note that unlike the Python :py:data:`sys.version`, this value will always include
    +    the patchlevel (it defaults to 0).
    +    """
    +
    +    platform_python_implementation: str
    +    """
    +    A string identifying the Python implementation, e.g. ``'CPython'``.
    +    """
    +
    +    python_version: str
    +    """The Python version as string ``'major.minor'``."""
    +
    +    sys_platform: str
    +    """
    +    This string contains a platform identifier that can be used to append
    +    platform-specific components to :py:data:`sys.path`, for instance.
    +
    +    For Unix systems, except on Linux and AIX, this is the lowercased OS name as
    +    returned by ``uname -s`` with the first part of the version as returned by
    +    ``uname -r`` appended, e.g. ``'sunos5'`` or ``'freebsd8'``, at the time when Python
    +    was built.
    +    """
    +
    +
    +def _normalize_extras(
    +    result: MarkerList | MarkerAtom | str,
    +) -> MarkerList | MarkerAtom | str:
    +    if not isinstance(result, tuple):
    +        return result
    +
    +    lhs, op, rhs = result
    +    if isinstance(lhs, Variable) and lhs.value == "extra":
    +        normalized_extra = canonicalize_name(rhs.value)
    +        rhs = Value(normalized_extra)
    +    elif isinstance(rhs, Variable) and rhs.value == "extra":
    +        normalized_extra = canonicalize_name(lhs.value)
    +        lhs = Value(normalized_extra)
    +    return lhs, op, rhs
    +
    +
    +def _normalize_extra_values(results: MarkerList) -> MarkerList:
    +    """
    +    Normalize extra values.
    +    """
    +
    +    return [_normalize_extras(r) for r in results]
    +
    +
    +def _format_marker(
    +    marker: list[str] | MarkerAtom | str, first: bool | None = True
    +) -> str:
    +    assert isinstance(marker, (list, tuple, str))
    +
    +    # Sometimes we have a structure like [[...]] which is a single item list
    +    # where the single item is itself it's own list. In that case we want skip
    +    # the rest of this function so that we don't get extraneous () on the
    +    # outside.
    +    if (
    +        isinstance(marker, list)
    +        and len(marker) == 1
    +        and isinstance(marker[0], (list, tuple))
    +    ):
    +        return _format_marker(marker[0])
    +
    +    if isinstance(marker, list):
    +        inner = (_format_marker(m, first=False) for m in marker)
    +        if first:
    +            return " ".join(inner)
    +        else:
    +            return "(" + " ".join(inner) + ")"
    +    elif isinstance(marker, tuple):
    +        return " ".join([m.serialize() for m in marker])
    +    else:
    +        return marker
    +
    +
    +_operators: dict[str, Operator] = {
    +    "in": lambda lhs, rhs: lhs in rhs,
    +    "not in": lambda lhs, rhs: lhs not in rhs,
    +    "<": lambda _lhs, _rhs: False,
    +    "<=": operator.eq,
    +    "==": operator.eq,
    +    "!=": operator.ne,
    +    ">=": operator.eq,
    +    ">": lambda _lhs, _rhs: False,
    +}
    +
    +
    +def _eval_op(lhs: str, op: Op, rhs: str | AbstractSet[str], *, key: str) -> bool:
    +    op_str = op.serialize()
    +    if key in MARKERS_REQUIRING_VERSION:
    +        try:
    +            spec = Specifier(f"{op_str}{rhs}")
    +        except InvalidSpecifier:
    +            pass
    +        else:
    +            return spec.contains(lhs, prereleases=True)
    +
    +    oper: Operator | None = _operators.get(op_str)
    +    if oper is None:
    +        raise UndefinedComparison(f"Undefined {op!r} on {lhs!r} and {rhs!r}.")
    +
    +    return oper(lhs, rhs)
    +
    +
    +def _normalize(
    +    lhs: str, rhs: str | AbstractSet[str], key: str
    +) -> tuple[str, str | AbstractSet[str]]:
    +    # PEP 685 - Comparison of extra names for optional distribution dependencies
    +    # https://peps.python.org/pep-0685/
    +    # > When comparing extra names, tools MUST normalize the names being
    +    # > compared using the semantics outlined in PEP 503 for names
    +    if key == "extra":
    +        assert isinstance(rhs, str), "extra value must be a string"
    +        # Both sides are normalized at this point already
    +        return (lhs, rhs)
    +    if key in MARKERS_ALLOWING_SET:
    +        if isinstance(rhs, str):  # pragma: no cover
    +            return (canonicalize_name(lhs), canonicalize_name(rhs))
    +        else:
    +            return (canonicalize_name(lhs), {canonicalize_name(v) for v in rhs})
    +
    +    # other environment markers don't have such standards
    +    return lhs, rhs
    +
    +
    +def _evaluate_markers(
    +    markers: MarkerList, environment: dict[str, str | AbstractSet[str]]
    +) -> bool:
    +    groups: list[list[bool]] = [[]]
    +
    +    for marker in markers:
    +        if isinstance(marker, list):
    +            groups[-1].append(_evaluate_markers(marker, environment))
    +        elif isinstance(marker, tuple):
    +            lhs, op, rhs = marker
    +
    +            if isinstance(lhs, Variable):
    +                environment_key = lhs.value
    +                lhs_value = environment[environment_key]
    +                rhs_value = rhs.value
    +            else:
    +                lhs_value = lhs.value
    +                environment_key = rhs.value
    +                rhs_value = environment[environment_key]
    +
    +            assert isinstance(lhs_value, str), "lhs must be a string"
    +            lhs_value, rhs_value = _normalize(lhs_value, rhs_value, key=environment_key)
    +            groups[-1].append(_eval_op(lhs_value, op, rhs_value, key=environment_key))
    +        elif marker == "or":
    +            groups.append([])
    +        elif marker == "and":
    +            pass
    +        else:  # pragma: nocover
    +            raise TypeError(f"Unexpected marker {marker!r}")
    +
    +    return any(all(item) for item in groups)
    +
    +
    +def _format_full_version(info: sys._version_info) -> str:
    +    version = f"{info.major}.{info.minor}.{info.micro}"
    +    kind = info.releaselevel
    +    if kind != "final":
    +        version += kind[0] + str(info.serial)
    +    return version
    +
    +
    +def default_environment() -> Environment:
    +    """Return the default marker environment for the current Python process.
    +
    +    This is the base environment used by :meth:`Marker.evaluate`.
    +    """
    +    iver = _format_full_version(sys.implementation.version)
    +    implementation_name = sys.implementation.name
    +    return {
    +        "implementation_name": implementation_name,
    +        "implementation_version": iver,
    +        "os_name": os.name,
    +        "platform_machine": platform.machine(),
    +        "platform_release": platform.release(),
    +        "platform_system": platform.system(),
    +        "platform_version": platform.version(),
    +        "python_full_version": platform.python_version(),
    +        "platform_python_implementation": platform.python_implementation(),
    +        "python_version": ".".join(platform.python_version_tuple()[:2]),
    +        "sys_platform": sys.platform,
    +    }
    +
    +
    +class Marker:
    +    """Represents a parsed dependency marker expression.
    +
    +    Marker expressions are parsed according to the
    +    :ref:`specification of dependency specifiers `.
    +
    +    :param marker: The string representation of a marker expression.
    +    :raises InvalidMarker: If ``marker`` cannot be parsed.
    +
    +    Instances are safe to serialize with :mod:`pickle`. They use a stable
    +    format so the same pickle can be loaded in future packaging releases.
    +
    +    .. versionchanged:: 26.2
    +
    +        Added a stable pickle format. Pickles created with packaging 26.2+ can
    +        be unpickled with future releases.  Backward compatibility with pickles
    +        from packaging < 26.2 is supported but may be removed in a future
    +        release.
    +    """
    +
    +    __slots__ = ("_markers",)
    +
    +    def __init__(self, marker: str) -> None:
    +        # Note: We create a Marker object without calling this constructor in
    +        #       packaging.requirements.Requirement. If any additional logic is
    +        #       added here, make sure to mirror/adapt Requirement.
    +
    +        # If this fails and throws an error, the repr still expects _markers to
    +        # be defined.
    +        self._markers: MarkerList = []
    +
    +        try:
    +            self._markers = _normalize_extra_values(_parse_marker(marker))
    +            # The attribute `_markers` can be described in terms of a recursive type:
    +            # MarkerList = List[Union[Tuple[Node, ...], str, MarkerList]]
    +            #
    +            # For example, the following expression:
    +            # python_version > "3.6" or (python_version == "3.6" and os_name == "unix")
    +            #
    +            # is parsed into:
    +            # [
    +            #     (, ')>, ),
    +            #     'and',
    +            #     [
    +            #         (, , ),
    +            #         'or',
    +            #         (, , )
    +            #     ]
    +            # ]
    +        except ParserSyntaxError as e:
    +            raise InvalidMarker(str(e)) from e
    +
    +    @classmethod
    +    def _from_markers(cls, markers: MarkerList) -> Marker:
    +        """Create a Marker instance from a pre-parsed marker tree.
    +
    +        This avoids re-parsing serialised marker strings when combining markers.
    +        """
    +        new = cls.__new__(cls)
    +        new._markers = markers
    +        return new
    +
    +    def __str__(self) -> str:
    +        return _format_marker(self._markers)
    +
    +    def __repr__(self) -> str:
    +        return f"<{self.__class__.__name__}({str(self)!r})>"
    +
    +    def __hash__(self) -> int:
    +        return hash(str(self))
    +
    +    def __eq__(self, other: object) -> bool:
    +        if not isinstance(other, Marker):
    +            return NotImplemented
    +
    +        return str(self) == str(other)
    +
    +    def __getstate__(self) -> str:
    +        # Return the marker expression string for compactness and stability.
    +        # Internal Node objects are excluded; the string is re-parsed on load.
    +        return str(self)
    +
    +    def __setstate__(self, state: object) -> None:
    +        if isinstance(state, str):
    +            # New format (26.2+): just the marker expression string.
    +            try:
    +                self._markers = _normalize_extra_values(_parse_marker(state))
    +            except ParserSyntaxError as exc:
    +                raise TypeError(f"Cannot restore Marker from {state!r}") from exc
    +            return
    +        if isinstance(state, dict) and "_markers" in state:
    +            # Old format (packaging <= 26.1, no __slots__): plain __dict__.
    +            markers = state["_markers"]
    +            if isinstance(markers, list):
    +                self._markers = markers
    +                return
    +        if isinstance(state, tuple) and len(state) == 2:
    +            # Old format (packaging <= 26.1, __slots__): (None, {slot: value}).
    +            _, slot_dict = state
    +            if isinstance(slot_dict, dict) and "_markers" in slot_dict:
    +                markers = slot_dict["_markers"]
    +                if isinstance(markers, list):
    +                    self._markers = markers
    +                    return
    +        raise TypeError(f"Cannot restore Marker from {state!r}")
    +
    +    def __and__(self, other: Marker) -> Marker:
    +        if not isinstance(other, Marker):
    +            return NotImplemented
    +        return self._from_markers([self._markers, "and", other._markers])
    +
    +    def __or__(self, other: Marker) -> Marker:
    +        if not isinstance(other, Marker):
    +            return NotImplemented
    +        return self._from_markers([self._markers, "or", other._markers])
    +
    +    def evaluate(
    +        self,
    +        environment: Mapping[str, str | AbstractSet[str]] | None = None,
    +        context: EvaluateContext = "metadata",
    +    ) -> bool:
    +        """Evaluate a marker.
    +
    +        Return the boolean from evaluating this marker against the environment.
    +        The environment is determined from the current Python process unless
    +        passed in explicitly.
    +
    +        :param environment: Mapping containing keys and values to override the
    +           detected environment.
    +        :param EvaluateContext context: The context in which the marker is
    +            evaluated, which influences what marker names are considered valid.
    +            Accepted values are ``"metadata"`` (for core metadata; default),
    +            ``"lock_file"``, and ``"requirement"`` (i.e. all other situations).
    +        :raises UndefinedComparison: If the marker uses a comparison on values
    +            that are not valid versions per the :ref:`specification of version
    +            specifiers `.
    +        :raises UndefinedEnvironmentName: If the marker references a value that
    +            is missing from the evaluation environment.
    +        :returns: ``True`` if the marker matches, otherwise ``False``.
    +
    +        """
    +        current_environment = cast(
    +            "dict[str, str | AbstractSet[str]]", default_environment()
    +        )
    +        if context == "lock_file":
    +            current_environment.update(
    +                extras=frozenset(), dependency_groups=frozenset()
    +            )
    +        elif context == "metadata":
    +            current_environment["extra"] = ""
    +
    +        if environment is not None:
    +            current_environment.update(environment)
    +            if "extra" in current_environment:
    +                # The API used to allow setting extra to None. We need to handle
    +                # this case for backwards compatibility. Also skip running
    +                # normalize name if extra is empty.
    +                extra = cast("str | None", current_environment["extra"])
    +                current_environment["extra"] = canonicalize_name(extra) if extra else ""
    +
    +        return _evaluate_markers(
    +            self._markers, _repair_python_full_version(current_environment)
    +        )
    +
    +
    +def _repair_python_full_version(
    +    env: dict[str, str | AbstractSet[str]],
    +) -> dict[str, str | AbstractSet[str]]:
    +    """
    +    Work around platform.python_version() returning something that is not PEP 440
    +    compliant for non-tagged Python builds.
    +    """
    +    python_full_version = cast("str", env["python_full_version"])
    +    if python_full_version.endswith("+"):
    +        env["python_full_version"] = f"{python_full_version}local"
    +    return env
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/metadata.py b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/metadata.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..dccb627dea31193b8bb1082223dcdc4305fcfb18
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/metadata.py
    @@ -0,0 +1,964 @@
    +from __future__ import annotations
    +
    +import email.header
    +import email.message
    +import email.parser
    +import email.policy
    +import keyword
    +import pathlib
    +import typing
    +from typing import (
    +    Any,
    +    Callable,
    +    Generic,
    +    Literal,
    +    TypedDict,
    +    cast,
    +)
    +
    +from . import licenses, requirements, specifiers, utils
    +from . import version as version_module
    +from .errors import ExceptionGroup, _ErrorCollector
    +
    +if typing.TYPE_CHECKING:
    +    from .licenses import NormalizedLicenseExpression
    +
    +T = typing.TypeVar("T")
    +
    +
    +__all__ = [
    +    "ExceptionGroup",  # Keep this for a bit (makes mypy happy w/ 26.0 compat)
    +    "InvalidMetadata",
    +    "Metadata",
    +    "RFC822Message",
    +    "RFC822Policy",
    +    "RawMetadata",
    +    "parse_email",
    +]
    +
    +
    +def __dir__() -> list[str]:
    +    return __all__
    +
    +
    +class InvalidMetadata(ValueError):
    +    """A metadata field contains invalid data."""
    +
    +    field: str
    +    """The name of the field that contains invalid data."""
    +
    +    def __init__(self, field: str, message: str) -> None:
    +        self.field = field
    +        super().__init__(message)
    +
    +
    +# The RawMetadata class attempts to make as few assumptions about the underlying
    +# serialization formats as possible. The idea is that as long as a serialization
    +# formats offer some very basic primitives in *some* way then we can support
    +# serializing to and from that format.
    +class RawMetadata(TypedDict, total=False):
    +    """A dictionary of raw core metadata.
    +
    +    Each field in core metadata maps to a key of this dictionary (when data is
    +    provided). The key is lower-case and underscores are used instead of dashes
    +    compared to the equivalent core metadata field. Any core metadata field that
    +    can be specified multiple times or can hold multiple values in a single
    +    field have a key with a plural name. See :class:`Metadata` whose attributes
    +    match the keys of this dictionary.
    +
    +    Core metadata fields that can be specified multiple times are stored as a
    +    list or dict depending on which is appropriate for the field. Any fields
    +    which hold multiple values in a single field are stored as a list. All fields
    +    are considered optional.
    +    """
    +
    +    # Metadata 1.0 - PEP 241
    +    metadata_version: str
    +    name: str
    +    version: str
    +    platforms: list[str]
    +    summary: str
    +    description: str
    +    keywords: list[str]
    +    home_page: str
    +    author: str
    +    author_email: str
    +    license: str
    +
    +    # Metadata 1.1 - PEP 314
    +    supported_platforms: list[str]
    +    download_url: str
    +    classifiers: list[str]
    +    requires: list[str]
    +    provides: list[str]
    +    obsoletes: list[str]
    +
    +    # Metadata 1.2 - PEP 345
    +    maintainer: str
    +    maintainer_email: str
    +    requires_dist: list[str]
    +    provides_dist: list[str]
    +    obsoletes_dist: list[str]
    +    requires_python: str
    +    requires_external: list[str]
    +    project_urls: dict[str, str]
    +
    +    # Metadata 2.0
    +    # PEP 426 attempted to completely revamp the metadata format
    +    # but got stuck without ever being able to build consensus on
    +    # it and ultimately ended up withdrawn.
    +    #
    +    # However, a number of tools had started emitting METADATA with
    +    # `2.0` Metadata-Version, so for historical reasons, this version
    +    # was skipped.
    +
    +    # Metadata 2.1 - PEP 566
    +    description_content_type: str
    +    provides_extra: list[str]
    +
    +    # Metadata 2.2 - PEP 643
    +    dynamic: list[str]
    +
    +    # Metadata 2.3 - PEP 685
    +    # No new fields were added in PEP 685, just some edge case were
    +    # tightened up to provide better interoperability.
    +
    +    # Metadata 2.4 - PEP 639
    +    license_expression: str
    +    license_files: list[str]
    +
    +    # Metadata 2.5 - PEP 794
    +    import_names: list[str]
    +    import_namespaces: list[str]
    +
    +
    +# 'keywords' is special as it's a string in the core metadata spec, but we
    +# represent it as a list.
    +_STRING_FIELDS = {
    +    "author",
    +    "author_email",
    +    "description",
    +    "description_content_type",
    +    "download_url",
    +    "home_page",
    +    "license",
    +    "license_expression",
    +    "maintainer",
    +    "maintainer_email",
    +    "metadata_version",
    +    "name",
    +    "requires_python",
    +    "summary",
    +    "version",
    +}
    +
    +_LIST_FIELDS = {
    +    "classifiers",
    +    "dynamic",
    +    "license_files",
    +    "obsoletes",
    +    "obsoletes_dist",
    +    "platforms",
    +    "provides",
    +    "provides_dist",
    +    "provides_extra",
    +    "requires",
    +    "requires_dist",
    +    "requires_external",
    +    "supported_platforms",
    +    "import_names",
    +    "import_namespaces",
    +}
    +
    +_DICT_FIELDS = {
    +    "project_urls",
    +}
    +
    +
    +def _parse_keywords(data: str) -> list[str]:
    +    """Split a string of comma-separated keywords into a list of keywords."""
    +    return [k.strip() for k in data.split(",")]
    +
    +
    +def _parse_project_urls(data: list[str]) -> dict[str, str]:
    +    """Parse a list of label/URL string pairings separated by a comma."""
    +    urls = {}
    +    for pair in data:
    +        # Our logic is slightly tricky here as we want to try and do
    +        # *something* reasonable with malformed data.
    +        #
    +        # The main thing that we have to worry about, is data that does
    +        # not have a ',' at all to split the label from the Value. There
    +        # isn't a singular right answer here, and we will fail validation
    +        # later on (if the caller is validating) so it doesn't *really*
    +        # matter, but since the missing value has to be an empty str
    +        # and our return value is dict[str, str], if we let the key
    +        # be the missing value, then they'd have multiple '' values that
    +        # overwrite each other in a accumulating dict.
    +        #
    +        # The other potential issue is that it's possible to have the
    +        # same label multiple times in the metadata, with no solid "right"
    +        # answer with what to do in that case. As such, we'll do the only
    +        # thing we can, which is treat the field as unparsable and add it
    +        # to our list of unparsed fields.
    +        #
    +        # TODO: The spec doesn't say anything about if the keys should be
    +        #       considered case sensitive or not... logically they should
    +        #       be case-preserving and case-insensitive, but doing that
    +        #       would open up more cases where we might have duplicate
    +        #       entries.
    +        label, _, url = (s.strip() for s in pair.partition(","))
    +
    +        if label in urls:
    +            # The label already exists in our set of urls, so this field
    +            # is unparsable, and we can just add the whole thing to our
    +            # unparsable data and stop processing it.
    +            raise KeyError("duplicate labels in project urls")
    +        urls[label] = url
    +
    +    return urls
    +
    +
    +def _get_payload(msg: email.message.Message, source: bytes | str) -> str:
    +    """Get the body of the message."""
    +    # If our source is a str, then our caller has managed encodings for us,
    +    # and we don't need to deal with it.
    +    if isinstance(source, str):
    +        payload = msg.get_payload()
    +        assert isinstance(payload, str)
    +        return payload
    +    # If our source is a bytes, then we're managing the encoding and we need
    +    # to deal with it.
    +    else:
    +        bpayload = msg.get_payload(decode=True)
    +        assert isinstance(bpayload, bytes)
    +        try:
    +            return bpayload.decode("utf8", "strict")
    +        except UnicodeDecodeError as exc:
    +            raise ValueError("payload in an invalid encoding") from exc
    +
    +
    +# The various parse_FORMAT functions here are intended to be as lenient as
    +# possible in their parsing, while still returning a correctly typed
    +# RawMetadata.
    +#
    +# To aid in this, we also generally want to do as little touching of the
    +# data as possible, except where there are possibly some historic holdovers
    +# that make valid data awkward to work with.
    +#
    +# While this is a lower level, intermediate format than our ``Metadata``
    +# class, some light touch ups can make a massive difference in usability.
    +
    +# Map METADATA fields to RawMetadata.
    +_EMAIL_TO_RAW_MAPPING = {
    +    "author": "author",
    +    "author-email": "author_email",
    +    "classifier": "classifiers",
    +    "description": "description",
    +    "description-content-type": "description_content_type",
    +    "download-url": "download_url",
    +    "dynamic": "dynamic",
    +    "home-page": "home_page",
    +    "import-name": "import_names",
    +    "import-namespace": "import_namespaces",
    +    "keywords": "keywords",
    +    "license": "license",
    +    "license-expression": "license_expression",
    +    "license-file": "license_files",
    +    "maintainer": "maintainer",
    +    "maintainer-email": "maintainer_email",
    +    "metadata-version": "metadata_version",
    +    "name": "name",
    +    "obsoletes": "obsoletes",
    +    "obsoletes-dist": "obsoletes_dist",
    +    "platform": "platforms",
    +    "project-url": "project_urls",
    +    "provides": "provides",
    +    "provides-dist": "provides_dist",
    +    "provides-extra": "provides_extra",
    +    "requires": "requires",
    +    "requires-dist": "requires_dist",
    +    "requires-external": "requires_external",
    +    "requires-python": "requires_python",
    +    "summary": "summary",
    +    "supported-platform": "supported_platforms",
    +    "version": "version",
    +}
    +_RAW_TO_EMAIL_MAPPING = {raw: email for email, raw in _EMAIL_TO_RAW_MAPPING.items()}
    +
    +
    +# This class is for writing RFC822 messages
    +class RFC822Policy(email.policy.EmailPolicy):
    +    """
    +    This is :class:`email.policy.EmailPolicy`, but with a simple ``header_store_parse``
    +    implementation that handles multi-line values, and some nice defaults.
    +    """
    +
    +    utf8 = True
    +    mangle_from_ = False
    +    max_line_length = 0
    +
    +    def header_store_parse(self, name: str, value: str) -> tuple[str, str]:
    +        size = len(name) + 2
    +        value = value.replace("\n", "\n" + " " * size)
    +        return (name, value)
    +
    +
    +# This class is for writing RFC822 messages
    +class RFC822Message(email.message.EmailMessage):
    +    """
    +    This is :class:`email.message.EmailMessage` with two small changes: it defaults to
    +    our `RFC822Policy`, and it correctly writes unicode when being called
    +    with `bytes()`.
    +    """
    +
    +    def __init__(self) -> None:
    +        super().__init__(policy=RFC822Policy())
    +
    +    def as_bytes(
    +        self, unixfrom: bool = False, policy: email.policy.Policy | None = None
    +    ) -> bytes:
    +        """
    +        Return the bytes representation of the message.
    +
    +        This handles unicode encoding.
    +        """
    +        return self.as_string(unixfrom, policy=policy).encode("utf-8")
    +
    +
    +def parse_email(data: bytes | str) -> tuple[RawMetadata, dict[str, list[str]]]:
    +    """Parse a distribution's metadata stored as email headers (e.g. from ``METADATA``).
    +
    +    This function returns a two-item tuple of dicts. The first dict is of
    +    recognized fields from the core metadata specification. Fields that can be
    +    parsed and translated into Python's built-in types are converted
    +    appropriately. All other fields are left as-is. Fields that are allowed to
    +    appear multiple times are stored as lists.
    +
    +    The second dict contains all other fields from the metadata. This includes
    +    any unrecognized fields. It also includes any fields which are expected to
    +    be parsed into a built-in type but were not formatted appropriately. Finally,
    +    any fields that are expected to appear only once but are repeated are
    +    included in this dict.
    +
    +    """
    +    raw: dict[str, str | list[str] | dict[str, str]] = {}
    +    unparsed: dict[str, list[str]] = {}
    +
    +    if isinstance(data, str):
    +        parsed = email.parser.Parser(policy=email.policy.compat32).parsestr(data)
    +    else:
    +        parsed = email.parser.BytesParser(policy=email.policy.compat32).parsebytes(data)
    +
    +    # We have to wrap parsed.keys() in a set, because in the case of multiple
    +    # values for a key (a list), the key will appear multiple times in the
    +    # list of keys, but we're avoiding that by using get_all().
    +    for name_with_case in frozenset(parsed.keys()):
    +        # Header names in RFC are case insensitive, so we'll normalize to all
    +        # lower case to make comparisons easier.
    +        name = name_with_case.lower()
    +
    +        # We use get_all() here, even for fields that aren't multiple use,
    +        # because otherwise someone could have e.g. two Name fields, and we
    +        # would just silently ignore it rather than doing something about it.
    +        headers = parsed.get_all(name) or []
    +
    +        # The way the email module works when parsing bytes is that it
    +        # unconditionally decodes the bytes as ascii using the surrogateescape
    +        # handler. When you pull that data back out (such as with get_all() ),
    +        # it looks to see if the str has any surrogate escapes, and if it does
    +        # it wraps it in a Header object instead of returning the string.
    +        #
    +        # As such, we'll look for those Header objects, and fix up the encoding.
    +        value = []
    +        # Flag if we have run into any issues processing the headers, thus
    +        # signalling that the data belongs in 'unparsed'.
    +        valid_encoding = True
    +        for h in headers:
    +            # It's unclear if this can return more types than just a Header or
    +            # a str, so we'll just assert here to make sure.
    +            assert isinstance(h, (email.header.Header, str))
    +
    +            # If it's a header object, we need to do our little dance to get
    +            # the real data out of it. In cases where there is invalid data
    +            # we're going to end up with mojibake, but there's no obvious, good
    +            # way around that without reimplementing parts of the Header object
    +            # ourselves.
    +            #
    +            # That should be fine since, if mojibacked happens, this key is
    +            # going into the unparsed dict anyways.
    +            if isinstance(h, email.header.Header):
    +                # The Header object stores it's data as chunks, and each chunk
    +                # can be independently encoded, so we'll need to check each
    +                # of them.
    +                chunks: list[tuple[bytes, str | None]] = []
    +                for binary, _encoding in email.header.decode_header(h):
    +                    try:
    +                        binary.decode("utf8", "strict")
    +                    except UnicodeDecodeError:
    +                        # Enable mojibake.
    +                        encoding = "latin1"
    +                        valid_encoding = False
    +                    else:
    +                        encoding = "utf8"
    +                    chunks.append((binary, encoding))
    +
    +                # Turn our chunks back into a Header object, then let that
    +                # Header object do the right thing to turn them into a
    +                # string for us.
    +                value.append(str(email.header.make_header(chunks)))
    +            # This is already a string, so just add it.
    +            else:
    +                value.append(h)
    +
    +        # We've processed all of our values to get them into a list of str,
    +        # but we may have mojibake data, in which case this is an unparsed
    +        # field.
    +        if not valid_encoding:
    +            unparsed[name] = value
    +            continue
    +
    +        raw_name = _EMAIL_TO_RAW_MAPPING.get(name)
    +        if raw_name is None:
    +            # This is a bit of a weird situation, we've encountered a key that
    +            # we don't know what it means, so we don't know whether it's meant
    +            # to be a list or not.
    +            #
    +            # Since we can't really tell one way or another, we'll just leave it
    +            # as a list, even though it may be a single item list, because that's
    +            # what makes the most sense for email headers.
    +            unparsed[name] = value
    +            continue
    +
    +        # If this is one of our string fields, then we'll check to see if our
    +        # value is a list of a single item. If it is then we'll assume that
    +        # it was emitted as a single string, and unwrap the str from inside
    +        # the list.
    +        #
    +        # If it's any other kind of data, then we haven't the faintest clue
    +        # what we should parse it as, and we have to just add it to our list
    +        # of unparsed stuff.
    +        if raw_name in _STRING_FIELDS and len(value) == 1:
    +            raw[raw_name] = value[0]
    +        # If this is import_names, we need to special case the empty field
    +        # case, which converts to an empty list instead of None. We can't let
    +        # the empty case slip through, as it will fail validation.
    +        elif raw_name == "import_names" and value == [""]:
    +            raw[raw_name] = []
    +        # If this is one of our list of string fields, then we can just assign
    +        # the value, since email *only* has strings, and our get_all() call
    +        # above ensures that this is a list.
    +        elif raw_name in _LIST_FIELDS:
    +            raw[raw_name] = value
    +        # Special Case: Keywords
    +        # The keywords field is implemented in the metadata spec as a str,
    +        # but it conceptually is a list of strings, and is serialized using
    +        # ", ".join(keywords), so we'll do some light data massaging to turn
    +        # this into what it logically is.
    +        elif raw_name == "keywords" and len(value) == 1:
    +            raw[raw_name] = _parse_keywords(value[0])
    +        # Special Case: Project-URL
    +        # The project urls is implemented in the metadata spec as a list of
    +        # specially-formatted strings that represent a key and a value, which
    +        # is fundamentally a mapping, however the email format doesn't support
    +        # mappings in a sane way, so it was crammed into a list of strings
    +        # instead.
    +        #
    +        # We will do a little light data massaging to turn this into a map as
    +        # it logically should be.
    +        elif raw_name == "project_urls":
    +            try:
    +                raw[raw_name] = _parse_project_urls(value)
    +            except KeyError:
    +                unparsed[name] = value
    +        # Nothing that we've done has managed to parse this, so it'll just
    +        # throw it in our unparsable data and move on.
    +        else:
    +            unparsed[name] = value
    +
    +    # We need to support getting the Description from the message payload in
    +    # addition to getting it from the the headers. This does mean, though, there
    +    # is the possibility of it being set both ways, in which case we put both
    +    # in 'unparsed' since we don't know which is right.
    +    try:
    +        payload = _get_payload(parsed, data)
    +    except ValueError:
    +        unparsed.setdefault("description", []).append(
    +            parsed.get_payload(decode=isinstance(data, bytes))  # type: ignore[call-overload]
    +        )
    +    else:
    +        if payload:
    +            # Check to see if we've already got a description, if so then both
    +            # it, and this body move to unparsable.
    +            if "description" in raw:
    +                description_header = cast("str", raw.pop("description"))
    +                unparsed.setdefault("description", []).extend(
    +                    [description_header, payload]
    +                )
    +            elif "description" in unparsed:
    +                unparsed["description"].append(payload)
    +            else:
    +                raw["description"] = payload
    +
    +    # We need to cast our `raw` to a metadata, because a TypedDict only support
    +    # literal key names, but we're computing our key names on purpose, but the
    +    # way this function is implemented, our `TypedDict` can only have valid key
    +    # names.
    +    return cast("RawMetadata", raw), unparsed
    +
    +
    +_NOT_FOUND = object()
    +
    +
    +# Keep the two values in sync.
    +_VALID_METADATA_VERSIONS = ["1.0", "1.1", "1.2", "2.1", "2.2", "2.3", "2.4", "2.5"]
    +_MetadataVersion = Literal["1.0", "1.1", "1.2", "2.1", "2.2", "2.3", "2.4", "2.5"]
    +
    +_REQUIRED_ATTRS = frozenset(["metadata_version", "name", "version"])
    +
    +
    +class _Validator(Generic[T]):
    +    """Validate a metadata field.
    +
    +    All _process_*() methods correspond to a core metadata field. The method is
    +    called with the field's raw value. If the raw value is valid it is returned
    +    in its "enriched" form (e.g. ``version.Version`` for the ``Version`` field).
    +    If the raw value is invalid, :exc:`InvalidMetadata` is raised (with a cause
    +    as appropriate).
    +    """
    +
    +    name: str
    +    raw_name: str
    +    added: _MetadataVersion
    +
    +    def __init__(
    +        self,
    +        *,
    +        added: _MetadataVersion = "1.0",
    +    ) -> None:
    +        self.added = added
    +
    +    def __set_name__(self, _owner: Metadata, name: str) -> None:
    +        self.name = name
    +        self.raw_name = _RAW_TO_EMAIL_MAPPING[name]
    +
    +    def __get__(self, instance: Metadata, _owner: type[Metadata]) -> T:
    +        # With Python 3.8, the caching can be replaced with functools.cached_property().
    +        # No need to check the cache as attribute lookup will resolve into the
    +        # instance's __dict__ before __get__ is called.
    +        cache = instance.__dict__
    +        value = instance._raw.get(self.name)
    +
    +        # To make the _process_* methods easier, we'll check if the value is None
    +        # and if this field is NOT a required attribute, and if both of those
    +        # things are true, we'll skip the the converter. This will mean that the
    +        # converters never have to deal with the None union.
    +        if self.name in _REQUIRED_ATTRS or value is not None:
    +            try:
    +                converter: Callable[[Any], T] = getattr(self, f"_process_{self.name}")
    +            except AttributeError:
    +                pass
    +            else:
    +                value = converter(value)
    +
    +        cache[self.name] = value
    +        try:
    +            del instance._raw[self.name]  # type: ignore[misc]
    +        except KeyError:
    +            pass
    +
    +        return cast("T", value)
    +
    +    def _invalid_metadata(
    +        self, msg: str, cause: Exception | None = None
    +    ) -> InvalidMetadata:
    +        exc = InvalidMetadata(
    +            self.raw_name, msg.format_map({"field": repr(self.raw_name)})
    +        )
    +        exc.__cause__ = cause
    +        return exc
    +
    +    def _process_metadata_version(self, value: str) -> _MetadataVersion:
    +        # Implicitly makes Metadata-Version required.
    +        if value not in _VALID_METADATA_VERSIONS:
    +            raise self._invalid_metadata(f"{value!r} is not a valid metadata version")
    +        return cast("_MetadataVersion", value)
    +
    +    def _process_name(self, value: str) -> str:
    +        if not value:
    +            raise self._invalid_metadata("{field} is a required field")
    +        # Validate the name as a side-effect.
    +        try:
    +            utils.canonicalize_name(value, validate=True)
    +        except utils.InvalidName as exc:
    +            raise self._invalid_metadata(
    +                f"{value!r} is invalid for {{field}}", cause=exc
    +            ) from exc
    +        else:
    +            return value
    +
    +    def _process_version(self, value: str) -> version_module.Version:
    +        if not value:
    +            raise self._invalid_metadata("{field} is a required field")
    +        try:
    +            return version_module.parse(value)
    +        except version_module.InvalidVersion as exc:
    +            raise self._invalid_metadata(
    +                f"{value!r} is invalid for {{field}}", cause=exc
    +            ) from exc
    +
    +    def _process_summary(self, value: str) -> str:
    +        """Check the field contains no newlines."""
    +        if "\n" in value:
    +            raise self._invalid_metadata("{field} must be a single line")
    +        return value
    +
    +    def _process_description_content_type(self, value: str) -> str:
    +        content_types = {"text/plain", "text/x-rst", "text/markdown"}
    +        message = email.message.EmailMessage()
    +        message["content-type"] = value
    +
    +        content_type, parameters = (
    +            # Defaults to `text/plain` if parsing failed.
    +            message.get_content_type().lower(),
    +            message["content-type"].params,
    +        )
    +        # Check if content-type is valid or defaulted to `text/plain` and thus was
    +        # not parseable.
    +        if content_type not in content_types or content_type not in value.lower():
    +            raise self._invalid_metadata(
    +                f"{{field}} must be one of {list(content_types)}, not {value!r}"
    +            )
    +
    +        charset = parameters.get("charset", "UTF-8")
    +        if charset != "UTF-8":
    +            raise self._invalid_metadata(
    +                f"{{field}} can only specify the UTF-8 charset, not {charset!r}"
    +            )
    +
    +        markdown_variants = {"GFM", "CommonMark"}
    +        variant = parameters.get("variant", "GFM")  # Use an acceptable default.
    +        if content_type == "text/markdown" and variant not in markdown_variants:
    +            raise self._invalid_metadata(
    +                f"valid Markdown variants for {{field}} are {list(markdown_variants)}, "
    +                f"not {variant!r}",
    +            )
    +        return value
    +
    +    def _process_dynamic(self, value: list[str]) -> list[str]:
    +        for dynamic_field in map(str.lower, value):
    +            if dynamic_field in {"name", "version", "metadata-version"}:
    +                raise self._invalid_metadata(
    +                    f"{dynamic_field!r} is not allowed as a dynamic field"
    +                )
    +            elif dynamic_field not in _EMAIL_TO_RAW_MAPPING:
    +                raise self._invalid_metadata(
    +                    f"{dynamic_field!r} is not a valid dynamic field"
    +                )
    +        return list(map(str.lower, value))
    +
    +    def _process_provides_extra(
    +        self,
    +        value: list[str],
    +    ) -> list[utils.NormalizedName]:
    +        normalized_names = []
    +        try:
    +            for name in value:
    +                normalized_names.append(utils.canonicalize_name(name, validate=True))
    +        except utils.InvalidName as exc:
    +            raise self._invalid_metadata(
    +                f"{name!r} is invalid for {{field}}", cause=exc
    +            ) from exc
    +        else:
    +            return normalized_names
    +
    +    def _process_requires_python(self, value: str) -> specifiers.SpecifierSet:
    +        try:
    +            return specifiers.SpecifierSet(value)
    +        except specifiers.InvalidSpecifier as exc:
    +            raise self._invalid_metadata(
    +                f"{value!r} is invalid for {{field}}", cause=exc
    +            ) from exc
    +
    +    def _process_requires_dist(
    +        self,
    +        value: list[str],
    +    ) -> list[requirements.Requirement]:
    +        reqs = []
    +        try:
    +            for req in value:
    +                reqs.append(requirements.Requirement(req))
    +        except requirements.InvalidRequirement as exc:
    +            raise self._invalid_metadata(
    +                f"{req!r} is invalid for {{field}}", cause=exc
    +            ) from exc
    +        else:
    +            return reqs
    +
    +    def _process_license_expression(self, value: str) -> NormalizedLicenseExpression:
    +        try:
    +            return licenses.canonicalize_license_expression(value)
    +        except ValueError as exc:
    +            raise self._invalid_metadata(
    +                f"{value!r} is invalid for {{field}}", cause=exc
    +            ) from exc
    +
    +    def _process_license_files(self, value: list[str]) -> list[str]:
    +        paths = []
    +        for path in value:
    +            if ".." in path:
    +                raise self._invalid_metadata(
    +                    f"{path!r} is invalid for {{field}}, "
    +                    "parent directory indicators are not allowed"
    +                )
    +            if "*" in path:
    +                raise self._invalid_metadata(
    +                    f"{path!r} is invalid for {{field}}, paths must be resolved"
    +                )
    +            if (
    +                pathlib.PurePosixPath(path).is_absolute()
    +                or pathlib.PureWindowsPath(path).is_absolute()
    +            ):
    +                raise self._invalid_metadata(
    +                    f"{path!r} is invalid for {{field}}, paths must be relative"
    +                )
    +            if pathlib.PureWindowsPath(path).as_posix() != path:
    +                raise self._invalid_metadata(
    +                    f"{path!r} is invalid for {{field}}, paths must use '/' delimiter"
    +                )
    +            paths.append(path)
    +        return paths
    +
    +    def _process_import_names(self, value: list[str]) -> list[str]:
    +        for import_name in value:
    +            name, semicolon, private = import_name.partition(";")
    +            name = name.rstrip()
    +            for identifier in name.split("."):
    +                if not identifier.isidentifier():
    +                    raise self._invalid_metadata(
    +                        f"{name!r} is invalid for {{field}}; "
    +                        f"{identifier!r} is not a valid identifier"
    +                    )
    +                elif keyword.iskeyword(identifier):
    +                    raise self._invalid_metadata(
    +                        f"{name!r} is invalid for {{field}}; "
    +                        f"{identifier!r} is a keyword"
    +                    )
    +            if semicolon and private.lstrip() != "private":
    +                raise self._invalid_metadata(
    +                    f"{import_name!r} is invalid for {{field}}; "
    +                    "the only valid option is 'private'"
    +                )
    +        return value
    +
    +    _process_import_namespaces = _process_import_names
    +
    +
    +class Metadata:
    +    """Representation of distribution metadata.
    +
    +    Compared to :class:`RawMetadata`, this class provides objects representing
    +    metadata fields instead of only using built-in types. Any invalid metadata
    +    will cause :exc:`InvalidMetadata` to be raised (with a
    +    :py:attr:`~BaseException.__cause__` attribute as appropriate).
    +    """
    +
    +    _raw: RawMetadata
    +
    +    @classmethod
    +    def from_raw(cls, data: RawMetadata, *, validate: bool = True) -> Metadata:
    +        """Create an instance from :class:`RawMetadata`.
    +
    +        If *validate* is true, all metadata will be validated. All exceptions
    +        related to validation will be gathered and raised as an :class:`ExceptionGroup`.
    +        """
    +        ins = cls()
    +        ins._raw = data.copy()  # Mutations occur due to caching enriched values.
    +
    +        if validate:
    +            collector = _ErrorCollector()
    +            metadata_version = None
    +            with collector.collect(InvalidMetadata):
    +                metadata_version = ins.metadata_version
    +                metadata_age = _VALID_METADATA_VERSIONS.index(metadata_version)
    +
    +            # Make sure to check for the fields that are present, the required
    +            # fields (so their absence can be reported).
    +            fields_to_check = frozenset(ins._raw) | _REQUIRED_ATTRS
    +            # Remove fields that have already been checked.
    +            fields_to_check -= {"metadata_version"}
    +
    +            for key in fields_to_check:
    +                try:
    +                    if metadata_version:
    +                        # Can't use getattr() as that triggers descriptor protocol which
    +                        # will fail due to no value for the instance argument.
    +                        try:
    +                            field_metadata_version = cls.__dict__[key].added
    +                        except KeyError:
    +                            exc = InvalidMetadata(key, f"unrecognized field: {key!r}")
    +                            collector.error(exc)
    +                            continue
    +                        field_age = _VALID_METADATA_VERSIONS.index(
    +                            field_metadata_version
    +                        )
    +                        if field_age > metadata_age:
    +                            field = _RAW_TO_EMAIL_MAPPING[key]
    +                            exc = InvalidMetadata(
    +                                field,
    +                                f"{field} introduced in metadata version "
    +                                f"{field_metadata_version}, not {metadata_version}",
    +                            )
    +                            collector.error(exc)
    +                            continue
    +                    getattr(ins, key)
    +                except InvalidMetadata as exc:
    +                    collector.error(exc)
    +
    +            collector.finalize("invalid metadata")
    +
    +        return ins
    +
    +    @classmethod
    +    def from_email(cls, data: bytes | str, *, validate: bool = True) -> Metadata:
    +        """Parse metadata from email headers.
    +
    +        If *validate* is true, the metadata will be validated. All exceptions
    +        related to validation will be gathered and raised as an :class:`ExceptionGroup`.
    +        """
    +        raw, unparsed = parse_email(data)
    +
    +        if validate:
    +            with _ErrorCollector().on_exit("unparsed") as collector:
    +                for unparsed_key in unparsed:
    +                    if unparsed_key in _EMAIL_TO_RAW_MAPPING:
    +                        message = f"{unparsed_key!r} has invalid data"
    +                    else:
    +                        message = f"unrecognized field: {unparsed_key!r}"
    +                    collector.error(InvalidMetadata(unparsed_key, message))
    +
    +        try:
    +            return cls.from_raw(raw, validate=validate)
    +        except ExceptionGroup as exc_group:
    +            raise ExceptionGroup(
    +                "invalid or unparsed metadata", exc_group.exceptions
    +            ) from None
    +
    +    metadata_version: _Validator[_MetadataVersion] = _Validator()
    +    """:external:ref:`core-metadata-metadata-version`
    +    (required; validated to be a valid metadata version)"""
    +    # `name` is not normalized/typed to NormalizedName so as to provide access to
    +    # the original/raw name.
    +    name: _Validator[str] = _Validator()
    +    """:external:ref:`core-metadata-name`
    +    (required; validated using :func:`~packaging.utils.canonicalize_name` and its
    +    *validate* parameter)"""
    +    version: _Validator[version_module.Version] = _Validator()
    +    """:external:ref:`core-metadata-version` (required)"""
    +    dynamic: _Validator[list[str] | None] = _Validator(
    +        added="2.2",
    +    )
    +    """:external:ref:`core-metadata-dynamic`
    +    (validated against core metadata field names and lowercased)"""
    +    platforms: _Validator[list[str] | None] = _Validator()
    +    """:external:ref:`core-metadata-platform`"""
    +    supported_platforms: _Validator[list[str] | None] = _Validator(added="1.1")
    +    """:external:ref:`core-metadata-supported-platform`"""
    +    summary: _Validator[str | None] = _Validator()
    +    """:external:ref:`core-metadata-summary` (validated to contain no newlines)"""
    +    description: _Validator[str | None] = _Validator()  # TODO 2.1: can be in body
    +    """:external:ref:`core-metadata-description`"""
    +    description_content_type: _Validator[str | None] = _Validator(added="2.1")
    +    """:external:ref:`core-metadata-description-content-type` (validated)"""
    +    keywords: _Validator[list[str] | None] = _Validator()
    +    """:external:ref:`core-metadata-keywords`"""
    +    home_page: _Validator[str | None] = _Validator()
    +    """:external:ref:`core-metadata-home-page`"""
    +    download_url: _Validator[str | None] = _Validator(added="1.1")
    +    """:external:ref:`core-metadata-download-url`"""
    +    author: _Validator[str | None] = _Validator()
    +    """:external:ref:`core-metadata-author`"""
    +    author_email: _Validator[str | None] = _Validator()
    +    """:external:ref:`core-metadata-author-email`"""
    +    maintainer: _Validator[str | None] = _Validator(added="1.2")
    +    """:external:ref:`core-metadata-maintainer`"""
    +    maintainer_email: _Validator[str | None] = _Validator(added="1.2")
    +    """:external:ref:`core-metadata-maintainer-email`"""
    +    license: _Validator[str | None] = _Validator()
    +    """:external:ref:`core-metadata-license`"""
    +    license_expression: _Validator[NormalizedLicenseExpression | None] = _Validator(
    +        added="2.4"
    +    )
    +    """:external:ref:`core-metadata-license-expression`"""
    +    license_files: _Validator[list[str] | None] = _Validator(added="2.4")
    +    """:external:ref:`core-metadata-license-file`"""
    +    classifiers: _Validator[list[str] | None] = _Validator(added="1.1")
    +    """:external:ref:`core-metadata-classifier`"""
    +    requires_dist: _Validator[list[requirements.Requirement] | None] = _Validator(
    +        added="1.2"
    +    )
    +    """:external:ref:`core-metadata-requires-dist`"""
    +    requires_python: _Validator[specifiers.SpecifierSet | None] = _Validator(
    +        added="1.2"
    +    )
    +    """:external:ref:`core-metadata-requires-python`"""
    +    # Because `Requires-External` allows for non-PEP 440 version specifiers, we
    +    # don't do any processing on the values.
    +    requires_external: _Validator[list[str] | None] = _Validator(added="1.2")
    +    """:external:ref:`core-metadata-requires-external`"""
    +    project_urls: _Validator[dict[str, str] | None] = _Validator(added="1.2")
    +    """:external:ref:`core-metadata-project-url`"""
    +    # PEP 685 lets us raise an error if an extra doesn't pass `Name` validation
    +    # regardless of metadata version.
    +    provides_extra: _Validator[list[utils.NormalizedName] | None] = _Validator(
    +        added="2.1",
    +    )
    +    """:external:ref:`core-metadata-provides-extra`"""
    +    provides_dist: _Validator[list[str] | None] = _Validator(added="1.2")
    +    """:external:ref:`core-metadata-provides-dist`"""
    +    obsoletes_dist: _Validator[list[str] | None] = _Validator(added="1.2")
    +    """:external:ref:`core-metadata-obsoletes-dist`"""
    +    import_names: _Validator[list[str] | None] = _Validator(added="2.5")
    +    """:external:ref:`core-metadata-import-name`"""
    +    import_namespaces: _Validator[list[str] | None] = _Validator(added="2.5")
    +    """:external:ref:`core-metadata-import-namespace`"""
    +    requires: _Validator[list[str] | None] = _Validator(added="1.1")
    +    """``Requires`` (deprecated)"""
    +    provides: _Validator[list[str] | None] = _Validator(added="1.1")
    +    """``Provides`` (deprecated)"""
    +    obsoletes: _Validator[list[str] | None] = _Validator(added="1.1")
    +    """``Obsoletes`` (deprecated)"""
    +
    +    def as_rfc822(self) -> RFC822Message:
    +        """
    +        Return an RFC822 message with the metadata.
    +        """
    +        message = RFC822Message()
    +        self._write_metadata(message)
    +        return message
    +
    +    def _write_metadata(self, message: RFC822Message) -> None:
    +        """
    +        Return an RFC822 message with the metadata.
    +        """
    +        for name, validator in self.__class__.__dict__.items():
    +            if isinstance(validator, _Validator) and name != "description":
    +                value = getattr(self, name)
    +                email_name = _RAW_TO_EMAIL_MAPPING[name]
    +                if value is not None:
    +                    if email_name == "project-url":
    +                        for label, url in value.items():
    +                            message[email_name] = f"{label}, {url}"
    +                    elif email_name == "keywords":
    +                        message[email_name] = ",".join(value)
    +                    elif email_name == "import-name" and value == []:
    +                        message[email_name] = ""
    +                    elif isinstance(value, list):
    +                        for item in value:
    +                            message[email_name] = str(item)
    +                    else:
    +                        message[email_name] = str(value)
    +
    +        # The description is a special case because it is in the body of the message.
    +        if self.description is not None:
    +            message.set_payload(self.description)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/py.typed b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/py.typed
    new file mode 100644
    index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/pylock.py b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/pylock.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..84e25378fc6fa21d2d637dbcc6cd43c0dd2ad1d4
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/pylock.py
    @@ -0,0 +1,905 @@
    +from __future__ import annotations
    +
    +import dataclasses
    +import logging
    +import re
    +from collections.abc import Mapping, Sequence
    +from dataclasses import dataclass
    +from datetime import datetime
    +from typing import (
    +    TYPE_CHECKING,
    +    Any,
    +    Callable,
    +    Protocol,
    +    TypeVar,
    +    cast,
    +)
    +from urllib.parse import urlparse
    +
    +from .markers import Environment, Marker, default_environment
    +from .specifiers import SpecifierSet
    +from .tags import create_compatible_tags_selector, sys_tags
    +from .utils import (
    +    NormalizedName,
    +    is_normalized_name,
    +    parse_sdist_filename,
    +    parse_wheel_filename,
    +)
    +from .version import Version
    +
    +if TYPE_CHECKING:  # pragma: no cover
    +    from collections.abc import Collection, Iterator
    +    from pathlib import Path
    +
    +    from typing_extensions import Self
    +
    +    from .tags import Tag
    +
    +_logger = logging.getLogger(__name__)
    +
    +__all__ = [
    +    "Package",
    +    "PackageArchive",
    +    "PackageDirectory",
    +    "PackageSdist",
    +    "PackageVcs",
    +    "PackageWheel",
    +    "Pylock",
    +    "PylockUnsupportedVersionError",
    +    "PylockValidationError",
    +    "is_valid_pylock_path",
    +]
    +
    +
    +def __dir__() -> list[str]:
    +    return __all__
    +
    +
    +_T = TypeVar("_T")
    +_T2 = TypeVar("_T2")
    +
    +
    +class _FromMappingProtocol(Protocol):  # pragma: no cover
    +    @classmethod
    +    def _from_dict(cls, d: Mapping[str, Any]) -> Self: ...
    +
    +
    +_FromMappingProtocolT = TypeVar("_FromMappingProtocolT", bound=_FromMappingProtocol)
    +
    +
    +_PYLOCK_FILE_NAME_RE = re.compile(r"^pylock\.([^.]+)\.toml$")
    +
    +
    +def is_valid_pylock_path(path: Path) -> bool:
    +    """Check if the given path is a valid pylock file path."""
    +    return path.name == "pylock.toml" or bool(_PYLOCK_FILE_NAME_RE.match(path.name))
    +
    +
    +def _toml_key(key: str) -> str:
    +    return key.replace("_", "-")
    +
    +
    +def _toml_value(key: str, value: Any) -> Any:  # noqa: ANN401
    +    if isinstance(value, (Version, Marker, SpecifierSet)):
    +        return str(value)
    +    if isinstance(value, Sequence) and key == "environments":
    +        return [str(v) for v in value]
    +    return value
    +
    +
    +def _toml_dict_factory(data: list[tuple[str, Any]]) -> dict[str, Any]:
    +    return {
    +        _toml_key(key): _toml_value(key, value)
    +        for key, value in data
    +        if value is not None
    +    }
    +
    +
    +def _get(d: Mapping[str, Any], expected_type: type[_T], key: str) -> _T | None:
    +    """Get a value from the dictionary and verify it's the expected type."""
    +    if (value := d.get(key)) is None:
    +        return None
    +    if not isinstance(value, expected_type):
    +        raise PylockValidationError(
    +            f"Unexpected type {type(value).__name__} "
    +            f"(expected {expected_type.__name__})",
    +            context=key,
    +        )
    +    return value
    +
    +
    +def _get_required(d: Mapping[str, Any], expected_type: type[_T], key: str) -> _T:
    +    """Get a required value from the dictionary and verify it's the expected type."""
    +    if (value := _get(d, expected_type, key)) is None:
    +        raise _PylockRequiredKeyError(key)
    +    return value
    +
    +
    +def _get_sequence(
    +    d: Mapping[str, Any], expected_item_type: type[_T], key: str
    +) -> Sequence[_T] | None:
    +    """Get a list value from the dictionary and verify it's the expected items type."""
    +    if (value := _get(d, Sequence, key)) is None:  # type: ignore[type-abstract]
    +        return None
    +    if isinstance(value, (str, bytes)):
    +        # special case: str and bytes are Sequences, but we want to reject it
    +        raise PylockValidationError(
    +            f"Unexpected type {type(value).__name__} (expected Sequence)",
    +            context=key,
    +        )
    +    for i, item in enumerate(value):
    +        if not isinstance(item, expected_item_type):
    +            raise PylockValidationError(
    +                f"Unexpected type {type(item).__name__} "
    +                f"(expected {expected_item_type.__name__})",
    +                context=f"{key}[{i}]",
    +            )
    +    return value
    +
    +
    +def _get_as(
    +    d: Mapping[str, Any],
    +    expected_type: type[_T],
    +    target_type: Callable[[_T], _T2],
    +    key: str,
    +) -> _T2 | None:
    +    """Get a value from the dictionary, verify it's the expected type,
    +    and convert to the target type.
    +
    +    This assumes the target_type constructor accepts the value.
    +    """
    +    if (value := _get(d, expected_type, key)) is None:
    +        return None
    +    try:
    +        return target_type(value)
    +    except Exception as e:
    +        raise PylockValidationError(e, context=key) from e
    +
    +
    +def _get_required_as(
    +    d: Mapping[str, Any],
    +    expected_type: type[_T],
    +    target_type: Callable[[_T], _T2],
    +    key: str,
    +) -> _T2:
    +    """Get a required value from the dict, verify it's the expected type,
    +    and convert to the target type."""
    +    if (value := _get_as(d, expected_type, target_type, key)) is None:
    +        raise _PylockRequiredKeyError(key)
    +    return value
    +
    +
    +def _get_sequence_as(
    +    d: Mapping[str, Any],
    +    expected_item_type: type[_T],
    +    target_item_type: Callable[[_T], _T2],
    +    key: str,
    +) -> list[_T2] | None:
    +    """Get list value from dictionary and verify expected items type."""
    +    if (value := _get_sequence(d, expected_item_type, key)) is None:
    +        return None
    +    result = []
    +    try:
    +        for item in value:
    +            typed_item = target_item_type(item)
    +            result.append(typed_item)
    +    except Exception as e:
    +        raise PylockValidationError(e, context=f"{key}[{len(result)}]") from e
    +    return result
    +
    +
    +def _get_object(
    +    d: Mapping[str, Any], target_type: type[_FromMappingProtocolT], key: str
    +) -> _FromMappingProtocolT | None:
    +    """Get a dictionary value from the dictionary and convert it to a dataclass."""
    +    if (value := _get(d, Mapping, key)) is None:  # type: ignore[type-abstract]
    +        return None
    +    try:
    +        return target_type._from_dict(value)
    +    except Exception as e:
    +        raise PylockValidationError(e, context=key) from e
    +
    +
    +def _get_sequence_of_objects(
    +    d: Mapping[str, Any], target_item_type: type[_FromMappingProtocolT], key: str
    +) -> list[_FromMappingProtocolT] | None:
    +    """Get a list value from the dictionary and convert its items to a dataclass."""
    +    if (value := _get_sequence(d, Mapping, key)) is None:  # type: ignore[type-abstract]
    +        return None
    +    result: list[_FromMappingProtocolT] = []
    +    try:
    +        for item in value:
    +            typed_item = target_item_type._from_dict(item)
    +            result.append(typed_item)
    +    except Exception as e:
    +        raise PylockValidationError(e, context=f"{key}[{len(result)}]") from e
    +    return result
    +
    +
    +def _get_required_sequence_of_objects(
    +    d: Mapping[str, Any], target_item_type: type[_FromMappingProtocolT], key: str
    +) -> Sequence[_FromMappingProtocolT]:
    +    """Get a required list value from the dictionary and convert its items to a
    +    dataclass."""
    +    if (result := _get_sequence_of_objects(d, target_item_type, key)) is None:
    +        raise _PylockRequiredKeyError(key)
    +    return result
    +
    +
    +def _validate_normalized_name(name: str) -> NormalizedName:
    +    """Validate that a string is a NormalizedName."""
    +    if not is_normalized_name(name):
    +        raise PylockValidationError(f"Name {name!r} is not normalized")
    +    return NormalizedName(name)
    +
    +
    +def _validate_path_url(path: str | None, url: str | None) -> None:
    +    if not path and not url:
    +        raise PylockValidationError("path or url must be provided")
    +
    +
    +def _path_name(path: str | None) -> str | None:
    +    if not path:
    +        return None
    +    # If the path is relative it MAY use POSIX-style path separators explicitly
    +    # for portability
    +    if "/" in path:
    +        return path.rsplit("/", 1)[-1]
    +    elif "\\" in path:
    +        return path.rsplit("\\", 1)[-1]
    +    else:
    +        return path
    +
    +
    +def _url_name(url: str | None) -> str | None:
    +    if not url:
    +        return None
    +    url_path = urlparse(url).path
    +    return url_path.rsplit("/", 1)[-1]
    +
    +
    +def _validate_hashes(hashes: Mapping[str, Any]) -> Mapping[str, Any]:
    +    if not hashes:
    +        raise PylockValidationError("At least one hash must be provided")
    +    if not all(isinstance(hash_val, str) for hash_val in hashes.values()):
    +        raise PylockValidationError("Hash values must be strings")
    +    return hashes
    +
    +
    +class PylockValidationError(Exception):
    +    """Raised when when input data is not spec-compliant."""
    +
    +    context: str | None = None
    +    message: str
    +
    +    def __init__(
    +        self,
    +        cause: str | Exception,
    +        *,
    +        context: str | None = None,
    +    ) -> None:
    +        if isinstance(cause, PylockValidationError):
    +            if cause.context:
    +                self.context = (
    +                    f"{context}.{cause.context}" if context else cause.context
    +                )
    +            else:
    +                self.context = context
    +            self.message = cause.message
    +        else:
    +            self.context = context
    +            self.message = str(cause)
    +
    +    def __str__(self) -> str:
    +        if self.context:
    +            return f"{self.message} in {self.context!r}"
    +        return self.message
    +
    +
    +class _PylockRequiredKeyError(PylockValidationError):
    +    def __init__(self, key: str) -> None:
    +        super().__init__("Missing required value", context=key)
    +
    +
    +class PylockUnsupportedVersionError(PylockValidationError):
    +    """Raised when encountering an unsupported `lock_version`."""
    +
    +
    +class PylockSelectError(Exception):
    +    """Base exception for errors raised by :meth:`Pylock.select`."""
    +
    +
    +@dataclass(frozen=True, init=False)
    +class PackageVcs:
    +    type: str
    +    url: str | None = None
    +    path: str | None = None
    +    requested_revision: str | None = None
    +    commit_id: str  # type: ignore[misc]
    +    subdirectory: str | None = None
    +
    +    def __init__(
    +        self,
    +        *,
    +        type: str,
    +        url: str | None = None,
    +        path: str | None = None,
    +        requested_revision: str | None = None,
    +        commit_id: str,
    +        subdirectory: str | None = None,
    +    ) -> None:
    +        # In Python 3.10+ make dataclass kw_only=True and remove __init__
    +        object.__setattr__(self, "type", type)
    +        object.__setattr__(self, "url", url)
    +        object.__setattr__(self, "path", path)
    +        object.__setattr__(self, "requested_revision", requested_revision)
    +        object.__setattr__(self, "commit_id", commit_id)
    +        object.__setattr__(self, "subdirectory", subdirectory)
    +
    +    @classmethod
    +    def _from_dict(cls, d: Mapping[str, Any]) -> Self:
    +        package_vcs = cls(
    +            type=_get_required(d, str, "type"),
    +            url=_get(d, str, "url"),
    +            path=_get(d, str, "path"),
    +            requested_revision=_get(d, str, "requested-revision"),
    +            commit_id=_get_required(d, str, "commit-id"),
    +            subdirectory=_get(d, str, "subdirectory"),
    +        )
    +        _validate_path_url(package_vcs.path, package_vcs.url)
    +        return package_vcs
    +
    +
    +@dataclass(frozen=True, init=False)
    +class PackageDirectory:
    +    path: str
    +    editable: bool | None = None
    +    subdirectory: str | None = None
    +
    +    def __init__(
    +        self,
    +        *,
    +        path: str,
    +        editable: bool | None = None,
    +        subdirectory: str | None = None,
    +    ) -> None:
    +        # In Python 3.10+ make dataclass kw_only=True and remove __init__
    +        object.__setattr__(self, "path", path)
    +        object.__setattr__(self, "editable", editable)
    +        object.__setattr__(self, "subdirectory", subdirectory)
    +
    +    @classmethod
    +    def _from_dict(cls, d: Mapping[str, Any]) -> Self:
    +        return cls(
    +            path=_get_required(d, str, "path"),
    +            editable=_get(d, bool, "editable"),
    +            subdirectory=_get(d, str, "subdirectory"),
    +        )
    +
    +
    +@dataclass(frozen=True, init=False)
    +class PackageArchive:
    +    url: str | None = None
    +    path: str | None = None
    +    size: int | None = None
    +    upload_time: datetime | None = None
    +    hashes: Mapping[str, str]  # type: ignore[misc]
    +    subdirectory: str | None = None
    +
    +    def __init__(
    +        self,
    +        *,
    +        url: str | None = None,
    +        path: str | None = None,
    +        size: int | None = None,
    +        upload_time: datetime | None = None,
    +        hashes: Mapping[str, str],
    +        subdirectory: str | None = None,
    +    ) -> None:
    +        # In Python 3.10+ make dataclass kw_only=True and remove __init__
    +        object.__setattr__(self, "url", url)
    +        object.__setattr__(self, "path", path)
    +        object.__setattr__(self, "size", size)
    +        object.__setattr__(self, "upload_time", upload_time)
    +        object.__setattr__(self, "hashes", hashes)
    +        object.__setattr__(self, "subdirectory", subdirectory)
    +
    +    @classmethod
    +    def _from_dict(cls, d: Mapping[str, Any]) -> Self:
    +        package_archive = cls(
    +            url=_get(d, str, "url"),
    +            path=_get(d, str, "path"),
    +            size=_get(d, int, "size"),
    +            upload_time=_get(d, datetime, "upload-time"),
    +            hashes=_get_required_as(d, Mapping, _validate_hashes, "hashes"),  # type: ignore[type-abstract]
    +            subdirectory=_get(d, str, "subdirectory"),
    +        )
    +        _validate_path_url(package_archive.path, package_archive.url)
    +        return package_archive
    +
    +
    +@dataclass(frozen=True, init=False)
    +class PackageSdist:
    +    name: str | None = None
    +    upload_time: datetime | None = None
    +    url: str | None = None
    +    path: str | None = None
    +    size: int | None = None
    +    hashes: Mapping[str, str]  # type: ignore[misc]
    +
    +    def __init__(
    +        self,
    +        *,
    +        name: str | None = None,
    +        upload_time: datetime | None = None,
    +        url: str | None = None,
    +        path: str | None = None,
    +        size: int | None = None,
    +        hashes: Mapping[str, str],
    +    ) -> None:
    +        # In Python 3.10+ make dataclass kw_only=True and remove __init__
    +        object.__setattr__(self, "name", name)
    +        object.__setattr__(self, "upload_time", upload_time)
    +        object.__setattr__(self, "url", url)
    +        object.__setattr__(self, "path", path)
    +        object.__setattr__(self, "size", size)
    +        object.__setattr__(self, "hashes", hashes)
    +
    +    @classmethod
    +    def _from_dict(cls, d: Mapping[str, Any]) -> Self:
    +        package_sdist = cls(
    +            name=_get(d, str, "name"),
    +            upload_time=_get(d, datetime, "upload-time"),
    +            url=_get(d, str, "url"),
    +            path=_get(d, str, "path"),
    +            size=_get(d, int, "size"),
    +            hashes=_get_required_as(d, Mapping, _validate_hashes, "hashes"),  # type: ignore[type-abstract]
    +        )
    +        _validate_path_url(package_sdist.path, package_sdist.url)
    +        return package_sdist
    +
    +    @property
    +    def filename(self) -> str:
    +        """Get the filename of the sdist."""
    +        filename = self.name or _path_name(self.path) or _url_name(self.url)
    +        if not filename:
    +            raise PylockValidationError("Cannot determine sdist filename")
    +        return filename
    +
    +
    +@dataclass(frozen=True, init=False)
    +class PackageWheel:
    +    name: str | None = None
    +    upload_time: datetime | None = None
    +    url: str | None = None
    +    path: str | None = None
    +    size: int | None = None
    +    hashes: Mapping[str, str]  # type: ignore[misc]
    +
    +    def __init__(
    +        self,
    +        *,
    +        name: str | None = None,
    +        upload_time: datetime | None = None,
    +        url: str | None = None,
    +        path: str | None = None,
    +        size: int | None = None,
    +        hashes: Mapping[str, str],
    +    ) -> None:
    +        # In Python 3.10+ make dataclass kw_only=True and remove __init__
    +        object.__setattr__(self, "name", name)
    +        object.__setattr__(self, "upload_time", upload_time)
    +        object.__setattr__(self, "url", url)
    +        object.__setattr__(self, "path", path)
    +        object.__setattr__(self, "size", size)
    +        object.__setattr__(self, "hashes", hashes)
    +
    +    @classmethod
    +    def _from_dict(cls, d: Mapping[str, Any]) -> Self:
    +        package_wheel = cls(
    +            name=_get(d, str, "name"),
    +            upload_time=_get(d, datetime, "upload-time"),
    +            url=_get(d, str, "url"),
    +            path=_get(d, str, "path"),
    +            size=_get(d, int, "size"),
    +            hashes=_get_required_as(d, Mapping, _validate_hashes, "hashes"),  # type: ignore[type-abstract]
    +        )
    +        _validate_path_url(package_wheel.path, package_wheel.url)
    +        return package_wheel
    +
    +    @property
    +    def filename(self) -> str:
    +        """Get the filename of the wheel."""
    +        filename = self.name or _path_name(self.path) or _url_name(self.url)
    +        if not filename:
    +            raise PylockValidationError("Cannot determine wheel filename")
    +        return filename
    +
    +
    +@dataclass(frozen=True, init=False)
    +class Package:
    +    name: NormalizedName
    +    version: Version | None = None
    +    marker: Marker | None = None
    +    requires_python: SpecifierSet | None = None
    +    dependencies: Sequence[Mapping[str, Any]] | None = None
    +    vcs: PackageVcs | None = None
    +    directory: PackageDirectory | None = None
    +    archive: PackageArchive | None = None
    +    index: str | None = None
    +    sdist: PackageSdist | None = None
    +    wheels: Sequence[PackageWheel] | None = None
    +    attestation_identities: Sequence[Mapping[str, Any]] | None = None
    +    tool: Mapping[str, Any] | None = None
    +
    +    def __init__(
    +        self,
    +        *,
    +        name: NormalizedName,
    +        version: Version | None = None,
    +        marker: Marker | None = None,
    +        requires_python: SpecifierSet | None = None,
    +        dependencies: Sequence[Mapping[str, Any]] | None = None,
    +        vcs: PackageVcs | None = None,
    +        directory: PackageDirectory | None = None,
    +        archive: PackageArchive | None = None,
    +        index: str | None = None,
    +        sdist: PackageSdist | None = None,
    +        wheels: Sequence[PackageWheel] | None = None,
    +        attestation_identities: Sequence[Mapping[str, Any]] | None = None,
    +        tool: Mapping[str, Any] | None = None,
    +    ) -> None:
    +        # In Python 3.10+ make dataclass kw_only=True and remove __init__
    +        object.__setattr__(self, "name", name)
    +        object.__setattr__(self, "version", version)
    +        object.__setattr__(self, "marker", marker)
    +        object.__setattr__(self, "requires_python", requires_python)
    +        object.__setattr__(self, "dependencies", dependencies)
    +        object.__setattr__(self, "vcs", vcs)
    +        object.__setattr__(self, "directory", directory)
    +        object.__setattr__(self, "archive", archive)
    +        object.__setattr__(self, "index", index)
    +        object.__setattr__(self, "sdist", sdist)
    +        object.__setattr__(self, "wheels", wheels)
    +        object.__setattr__(self, "attestation_identities", attestation_identities)
    +        object.__setattr__(self, "tool", tool)
    +
    +    @classmethod
    +    def _from_dict(cls, d: Mapping[str, Any]) -> Self:
    +        package = cls(
    +            name=_get_required_as(d, str, _validate_normalized_name, "name"),
    +            version=_get_as(d, str, Version, "version"),
    +            requires_python=_get_as(d, str, SpecifierSet, "requires-python"),
    +            dependencies=_get_sequence(d, Mapping, "dependencies"),  # type: ignore[type-abstract]
    +            marker=_get_as(d, str, Marker, "marker"),
    +            vcs=_get_object(d, PackageVcs, "vcs"),
    +            directory=_get_object(d, PackageDirectory, "directory"),
    +            archive=_get_object(d, PackageArchive, "archive"),
    +            index=_get(d, str, "index"),
    +            sdist=_get_object(d, PackageSdist, "sdist"),
    +            wheels=_get_sequence_of_objects(d, PackageWheel, "wheels"),
    +            attestation_identities=_get_sequence(d, Mapping, "attestation-identities"),  # type: ignore[type-abstract]
    +            tool=_get(d, Mapping, "tool"),  # type: ignore[type-abstract]
    +        )
    +        distributions = bool(package.sdist) + len(package.wheels or [])
    +        direct_urls = (
    +            bool(package.vcs) + bool(package.directory) + bool(package.archive)
    +        )
    +        if distributions > 0 and direct_urls > 0:
    +            raise PylockValidationError(
    +                "None of vcs, directory, archive must be set if sdist or wheels are set"
    +            )
    +        if distributions == 0 and direct_urls != 1:
    +            raise PylockValidationError(
    +                "Exactly one of vcs, directory, archive must be set "
    +                "if sdist and wheels are not set"
    +            )
    +        for i, wheel in enumerate(package.wheels or []):
    +            try:
    +                (name, version, _, _) = parse_wheel_filename(wheel.filename)
    +            except Exception as e:
    +                raise PylockValidationError(
    +                    f"Invalid wheel filename {wheel.filename!r}",
    +                    context=f"wheels[{i}]",
    +                ) from e
    +            if name != package.name:
    +                raise PylockValidationError(
    +                    f"Name in {wheel.filename!r} is not consistent with "
    +                    f"package name {package.name!r}",
    +                    context=f"wheels[{i}]",
    +                )
    +            if package.version and version != package.version:
    +                raise PylockValidationError(
    +                    f"Version in {wheel.filename!r} is not consistent with "
    +                    f"package version {str(package.version)!r}",
    +                    context=f"wheels[{i}]",
    +                )
    +        if package.sdist:
    +            try:
    +                name, version = parse_sdist_filename(package.sdist.filename)
    +            except Exception as e:
    +                raise PylockValidationError(
    +                    f"Invalid sdist filename {package.sdist.filename!r}",
    +                    context="sdist",
    +                ) from e
    +            if name != package.name:
    +                raise PylockValidationError(
    +                    f"Name in {package.sdist.filename!r} is not consistent with "
    +                    f"package name {package.name!r}",
    +                    context="sdist",
    +                )
    +            if package.version and version != package.version:
    +                raise PylockValidationError(
    +                    f"Version in {package.sdist.filename!r} is not consistent with "
    +                    f"package version {str(package.version)!r}",
    +                    context="sdist",
    +                )
    +        try:
    +            for i, attestation_identity in enumerate(  # noqa: B007
    +                package.attestation_identities or []
    +            ):
    +                _get_required(attestation_identity, str, "kind")
    +        except Exception as e:
    +            raise PylockValidationError(
    +                e, context=f"attestation-identities[{i}]"
    +            ) from e
    +        return package
    +
    +    @property
    +    def is_direct(self) -> bool:
    +        return not (self.sdist or self.wheels)
    +
    +
    +@dataclass(frozen=True, init=False)
    +class Pylock:
    +    """A class representing a pylock file."""
    +
    +    lock_version: Version
    +    environments: Sequence[Marker] | None = None
    +    requires_python: SpecifierSet | None = None
    +    extras: Sequence[NormalizedName] | None = None
    +    dependency_groups: Sequence[str] | None = None
    +    default_groups: Sequence[str] | None = None
    +    created_by: str  # type: ignore[misc]
    +    packages: Sequence[Package]  # type: ignore[misc]
    +    tool: Mapping[str, Any] | None = None
    +
    +    def __init__(
    +        self,
    +        *,
    +        lock_version: Version,
    +        environments: Sequence[Marker] | None = None,
    +        requires_python: SpecifierSet | None = None,
    +        extras: Sequence[NormalizedName] | None = None,
    +        dependency_groups: Sequence[str] | None = None,
    +        default_groups: Sequence[str] | None = None,
    +        created_by: str,
    +        packages: Sequence[Package],
    +        tool: Mapping[str, Any] | None = None,
    +    ) -> None:
    +        # In Python 3.10+ make dataclass kw_only=True and remove __init__
    +        object.__setattr__(self, "lock_version", lock_version)
    +        object.__setattr__(self, "environments", environments)
    +        object.__setattr__(self, "requires_python", requires_python)
    +        object.__setattr__(self, "extras", extras)
    +        object.__setattr__(self, "dependency_groups", dependency_groups)
    +        object.__setattr__(self, "default_groups", default_groups)
    +        object.__setattr__(self, "created_by", created_by)
    +        object.__setattr__(self, "packages", packages)
    +        object.__setattr__(self, "tool", tool)
    +
    +    @classmethod
    +    def _from_dict(cls, d: Mapping[str, Any]) -> Self:
    +        pylock = cls(
    +            lock_version=_get_required_as(d, str, Version, "lock-version"),
    +            environments=_get_sequence_as(d, str, Marker, "environments"),
    +            extras=_get_sequence_as(d, str, _validate_normalized_name, "extras"),
    +            dependency_groups=_get_sequence(d, str, "dependency-groups"),
    +            default_groups=_get_sequence(d, str, "default-groups"),
    +            created_by=_get_required(d, str, "created-by"),
    +            requires_python=_get_as(d, str, SpecifierSet, "requires-python"),
    +            packages=_get_required_sequence_of_objects(d, Package, "packages"),
    +            tool=_get(d, Mapping, "tool"),  # type: ignore[type-abstract]
    +        )
    +        if not Version("1") <= pylock.lock_version < Version("2"):
    +            raise PylockUnsupportedVersionError(
    +                f"pylock version {pylock.lock_version} is not supported"
    +            )
    +        if pylock.lock_version > Version("1.0"):
    +            _logger.warning(
    +                "pylock minor version %s is not supported", pylock.lock_version
    +            )
    +        return pylock
    +
    +    @classmethod
    +    def from_dict(cls, d: Mapping[str, Any], /) -> Self:
    +        """Create and validate a Pylock instance from a TOML dictionary.
    +
    +        Raises :class:`PylockValidationError` if the input data is not
    +        spec-compliant.
    +        """
    +        return cls._from_dict(d)
    +
    +    def to_dict(self) -> Mapping[str, Any]:
    +        """Convert the Pylock instance to a TOML dictionary."""
    +        return dataclasses.asdict(self, dict_factory=_toml_dict_factory)
    +
    +    def validate(self) -> None:
    +        """Validate the Pylock instance against the specification.
    +
    +        Raises :class:`PylockValidationError` otherwise."""
    +        self.from_dict(self.to_dict())
    +
    +    def select(
    +        self,
    +        *,
    +        environment: Environment | None = None,
    +        tags: Sequence[Tag] | None = None,
    +        extras: Collection[str] | None = None,
    +        dependency_groups: Collection[str] | None = None,
    +    ) -> Iterator[
    +        tuple[
    +            Package,
    +            PackageVcs
    +            | PackageDirectory
    +            | PackageArchive
    +            | PackageWheel
    +            | PackageSdist,
    +        ]
    +    ]:
    +        """Select what to install from the lock file.
    +
    +        The *environment* and *tags* parameters represent the environment being
    +        selected for. If unspecified, ``packaging.markers.default_environment()`` and
    +        ``packaging.tags.sys_tags()`` are used.
    +
    +        The *extras* parameter represents the extras to install.
    +
    +        The *dependency_groups* parameter represents the groups to install. If
    +        unspecified, the default groups are used.
    +
    +        This method must be used on valid Pylock instances (i.e. one obtained
    +        from :meth:`Pylock.from_dict` or if constructed manually, after calling
    +        :meth:`Pylock.validate`).
    +        """
    +        compatible_tags_selector = create_compatible_tags_selector(tags or sys_tags())
    +
    +        # #. Gather the extras and dependency groups to install and set ``extras`` and
    +        #    ``dependency_groups`` for marker evaluation, respectively.
    +        #
    +        #    #. ``extras`` SHOULD be set to the empty set by default.
    +        #    #. ``dependency_groups`` SHOULD be the set created from
    +        #       :ref:`pylock-default-groups` by default.
    +        env = cast(
    +            "dict[str, str | frozenset[str]]",
    +            dict(
    +                environment or {},  # Marker.evaluate will fill-up
    +                extras=frozenset(extras or []),
    +                dependency_groups=frozenset(
    +                    (self.default_groups or [])
    +                    if dependency_groups is None  # to allow selecting no group
    +                    else dependency_groups
    +                ),
    +            ),
    +        )
    +        env_python_full_version = (
    +            environment["python_full_version"]
    +            if environment
    +            else default_environment()["python_full_version"]
    +        )
    +
    +        # #. Check if the metadata version specified by :ref:`pylock-lock-version` is
    +        #    supported; an error or warning MUST be raised as appropriate.
    +        # Covered by lock.validate() which is a precondition for this method.
    +
    +        # #. If :ref:`pylock-requires-python` is specified, check that the environment
    +        #    being installed for meets the requirement; an error MUST be raised if it is
    +        #    not met.
    +        if self.requires_python and not self.requires_python.contains(
    +            env_python_full_version,
    +        ):
    +            raise PylockSelectError(
    +                f"python_full_version {env_python_full_version!r} "
    +                f"in provided environment does not satisfy the Python version "
    +                f"requirement {str(self.requires_python)!r}"
    +            )
    +
    +        # #. If :ref:`pylock-environments` is specified, check that at least one of the
    +        #    environment marker expressions is satisfied; an error MUST be raised if no
    +        #    expression is satisfied.
    +        if self.environments:
    +            for env_marker in self.environments:
    +                if env_marker.evaluate(
    +                    cast("dict[str, str]", environment or {}), context="requirement"
    +                ):
    +                    break
    +            else:
    +                raise PylockSelectError(
    +                    "Provided environment does not satisfy any of the "
    +                    "environments specified in the lock file"
    +                )
    +
    +        # #. For each package listed in :ref:`pylock-packages`:
    +        selected_packages_by_name: dict[str, tuple[int, Package]] = {}
    +        for package_index, package in enumerate(self.packages):
    +            # #. If :ref:`pylock-packages-marker` is specified, check if it is
    +            #    satisfied;if it isn't, skip to the next package.
    +            if package.marker and not package.marker.evaluate(env, context="lock_file"):
    +                continue
    +
    +            # #. If :ref:`pylock-packages-requires-python` is specified, check if it is
    +            #    satisfied; an error MUST be raised if it isn't.
    +            if package.requires_python and not package.requires_python.contains(
    +                env_python_full_version,
    +            ):
    +                raise PylockSelectError(
    +                    f"python_full_version {env_python_full_version!r} "
    +                    f"in provided environment does not satisfy the Python version "
    +                    f"requirement {str(package.requires_python)!r} for package "
    +                    f"{package.name!r} at packages[{package_index}]"
    +                )
    +
    +            # #. Check that no other conflicting instance of the package has been slated
    +            #    to be installed; an error about the ambiguity MUST be raised otherwise.
    +            if package.name in selected_packages_by_name:
    +                raise PylockSelectError(
    +                    f"Multiple packages with the name {package.name!r} are "
    +                    f"selected at packages[{package_index}] and "
    +                    f"packages[{selected_packages_by_name[package.name][0]}]"
    +                )
    +
    +            # #. Check that the source of the package is specified appropriately (i.e.
    +            #    there are no conflicting sources in the package entry);
    +            #    an error MUST be raised if any issues are found.
    +            # Covered by lock.validate() which is a precondition for this method.
    +
    +            # #. Add the package to the set of packages to install.
    +            selected_packages_by_name[package.name] = (package_index, package)
    +
    +        # #. For each package to be installed:
    +        for package_index, package in selected_packages_by_name.values():
    +            # - If :ref:`pylock-packages-vcs` is set:
    +            if package.vcs is not None:
    +                yield package, package.vcs
    +
    +            # - Else if :ref:`pylock-packages-directory` is set:
    +            elif package.directory is not None:
    +                yield package, package.directory
    +
    +            # - Else if :ref:`pylock-packages-archive` is set:
    +            elif package.archive is not None:
    +                yield package, package.archive
    +
    +            # - Else if there are entries for :ref:`pylock-packages-wheels`:
    +            elif package.wheels:
    +                # #. Look for the appropriate wheel file based on
    +                #    :ref:`pylock-packages-wheels-name`; if one is not found then move
    +                #    on to :ref:`pylock-packages-sdist` or an error MUST be raised about
    +                #    a lack of source for the project.
    +                best_wheel = next(
    +                    compatible_tags_selector(
    +                        (wheel, parse_wheel_filename(wheel.filename)[-1])
    +                        for wheel in package.wheels
    +                    ),
    +                    None,
    +                )
    +                if best_wheel:
    +                    yield package, best_wheel
    +                elif package.sdist is not None:
    +                    yield package, package.sdist
    +                else:
    +                    raise PylockSelectError(
    +                        f"No wheel found matching the provided tags "
    +                        f"for package {package.name!r} "
    +                        f"at packages[{package_index}], "
    +                        f"and no sdist available as a fallback"
    +                    )
    +
    +            # - Else if no :ref:`pylock-packages-wheels` file is found or
    +            #   :ref:`pylock-packages-sdist` is solely set:
    +            elif package.sdist is not None:
    +                yield package, package.sdist
    +
    +            else:
    +                # Covered by lock.validate() which is a precondition for this method.
    +                raise NotImplementedError  # pragma: no cover
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/requirements.py b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/requirements.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..5892aee6ee2e391673a8609eecb5e43be4931a89
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/requirements.py
    @@ -0,0 +1,129 @@
    +# This file is dual licensed under the terms of the Apache License, Version
    +# 2.0, and the BSD License. See the LICENSE file in the root of this repository
    +# for complete details.
    +from __future__ import annotations
    +
    +from typing import Iterator
    +
    +from ._parser import parse_requirement as _parse_requirement
    +from ._tokenizer import ParserSyntaxError
    +from .markers import Marker, _normalize_extra_values
    +from .specifiers import SpecifierSet
    +from .utils import canonicalize_name
    +
    +__all__ = [
    +    "InvalidRequirement",
    +    "Requirement",
    +]
    +
    +
    +def __dir__() -> list[str]:
    +    return __all__
    +
    +
    +class InvalidRequirement(ValueError):
    +    """
    +    An invalid requirement was found, users should refer to PEP 508.
    +    """
    +
    +
    +class Requirement:
    +    """Parse a requirement.
    +
    +    Parse a given requirement string into its parts, such as name, specifier,
    +    URL, and extras. Raises InvalidRequirement on a badly-formed requirement
    +    string.
    +
    +    Instances are safe to serialize with :mod:`pickle`. They use a stable
    +    format so the same pickle can be loaded in future packaging releases.
    +
    +    .. versionchanged:: 26.2
    +
    +        Added a stable pickle format. Pickles created with packaging 26.2+ can
    +        be unpickled with future releases.  Backward compatibility with pickles
    +        from packaging < 26.2 is supported but may be removed in a future
    +        release.
    +    """
    +
    +    # TODO: Can we test whether something is contained within a requirement?
    +    #       If so how do we do that? Do we need to test against the _name_ of
    +    #       the thing as well as the version? What about the markers?
    +    # TODO: Can we normalize the name and extra name?
    +
    +    def __init__(self, requirement_string: str) -> None:
    +        try:
    +            parsed = _parse_requirement(requirement_string)
    +        except ParserSyntaxError as e:
    +            raise InvalidRequirement(str(e)) from e
    +
    +        self.name: str = parsed.name
    +        self.url: str | None = parsed.url or None
    +        self.extras: set[str] = set(parsed.extras or [])
    +        self.specifier: SpecifierSet = SpecifierSet(parsed.specifier)
    +        self.marker: Marker | None = None
    +        if parsed.marker is not None:
    +            self.marker = Marker.__new__(Marker)
    +            self.marker._markers = _normalize_extra_values(parsed.marker)
    +
    +    def _iter_parts(self, name: str) -> Iterator[str]:
    +        yield name
    +
    +        if self.extras:
    +            formatted_extras = ",".join(sorted(self.extras))
    +            yield f"[{formatted_extras}]"
    +
    +        if self.specifier:
    +            yield str(self.specifier)
    +
    +        if self.url:
    +            yield f" @ {self.url}"
    +            if self.marker:
    +                yield " "
    +
    +        if self.marker:
    +            yield f"; {self.marker}"
    +
    +    def __getstate__(self) -> str:
    +        # Return the requirement string for compactness and stability.
    +        # Re-parsed on load to reconstruct all fields.
    +        return str(self)
    +
    +    def __setstate__(self, state: object) -> None:
    +        if isinstance(state, str):
    +            # New format (26.2+): just the requirement string.
    +            try:
    +                tmp = Requirement(state)
    +            except InvalidRequirement as exc:
    +                raise TypeError(f"Cannot restore Requirement from {state!r}") from exc
    +            self.name = tmp.name
    +            self.url = tmp.url
    +            self.extras = tmp.extras
    +            self.specifier = tmp.specifier
    +            self.marker = tmp.marker
    +            return
    +        if isinstance(state, dict):
    +            # Old format (packaging <= 26.1, no __slots__): plain __dict__.
    +            self.__dict__.update(state)
    +            return
    +        raise TypeError(f"Cannot restore Requirement from {state!r}")
    +
    +    def __str__(self) -> str:
    +        return "".join(self._iter_parts(self.name))
    +
    +    def __repr__(self) -> str:
    +        return f"<{self.__class__.__name__}({str(self)!r})>"
    +
    +    def __hash__(self) -> int:
    +        return hash(tuple(self._iter_parts(canonicalize_name(self.name))))
    +
    +    def __eq__(self, other: object) -> bool:
    +        if not isinstance(other, Requirement):
    +            return NotImplemented
    +
    +        return (
    +            canonicalize_name(self.name) == canonicalize_name(other.name)
    +            and self.extras == other.extras
    +            and self.specifier == other.specifier
    +            and self.url == other.url
    +            and self.marker == other.marker
    +        )
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/specifiers.py b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/specifiers.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..b165dc0f7c8201f2291780ce0273af8127105ab0
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/specifiers.py
    @@ -0,0 +1,1943 @@
    +# This file is dual licensed under the terms of the Apache License, Version
    +# 2.0, and the BSD License. See the LICENSE file in the root of this repository
    +# for complete details.
    +"""
    +.. testsetup::
    +
    +    from packaging.specifiers import Specifier, SpecifierSet, InvalidSpecifier
    +    from packaging.version import Version
    +"""
    +
    +from __future__ import annotations
    +
    +import abc
    +import enum
    +import functools
    +import itertools
    +import re
    +import sys
    +import typing
    +from typing import (
    +    TYPE_CHECKING,
    +    Any,
    +    Callable,
    +    Final,
    +    Iterable,
    +    Iterator,
    +    Sequence,
    +    TypeVar,
    +    Union,
    +)
    +
    +from .utils import canonicalize_version
    +from .version import InvalidVersion, Version
    +
    +if sys.version_info >= (3, 10):
    +    from typing import TypeGuard  # pragma: no cover
    +elif TYPE_CHECKING:
    +    from typing_extensions import TypeGuard
    +
    +__all__ = [
    +    "BaseSpecifier",
    +    "InvalidSpecifier",
    +    "Specifier",
    +    "SpecifierSet",
    +]
    +
    +
    +def __dir__() -> list[str]:
    +    return __all__
    +
    +
    +def _validate_spec(spec: object, /) -> TypeGuard[tuple[str, str]]:
    +    return (
    +        isinstance(spec, tuple)
    +        and len(spec) == 2
    +        and isinstance(spec[0], str)
    +        and isinstance(spec[1], str)
    +    )
    +
    +
    +def _validate_pre(pre: object, /) -> TypeGuard[bool | None]:
    +    return pre is None or isinstance(pre, bool)
    +
    +
    +T = TypeVar("T")
    +UnparsedVersion = Union[Version, str]
    +UnparsedVersionVar = TypeVar("UnparsedVersionVar", bound=UnparsedVersion)
    +CallableOperator = Callable[[Version, str], bool]
    +
    +# The smallest possible PEP 440 version. No valid version is less than this.
    +_MIN_VERSION: Final[Version] = Version("0.dev0")
    +
    +
    +def _trim_release(release: tuple[int, ...]) -> tuple[int, ...]:
    +    """Strip trailing zeros from a release tuple for normalized comparison."""
    +    end = len(release)
    +    while end > 1 and release[end - 1] == 0:
    +        end -= 1
    +    return release if end == len(release) else release[:end]
    +
    +
    +class _BoundaryKind(enum.Enum):
    +    """Where a boundary marker sits in the version ordering."""
    +
    +    AFTER_LOCALS = enum.auto()  # after V+local, before V.post0
    +    AFTER_POSTS = enum.auto()  # after V.postN, before next release
    +
    +
    +@functools.total_ordering
    +class _BoundaryVersion:
    +    """A point on the version line between two real PEP 440 versions.
    +
    +    Some specifier semantics imply boundaries between real versions:
    +    ``<=1.0`` includes ``1.0+local`` and ``>1.0`` excludes
    +    ``1.0.post0``.  No real :class:`Version` falls on those boundaries,
    +    so this class creates values that sort between the real versions
    +    on either side.
    +
    +    Two kinds exist, shown relative to a base version V::
    +
    +        V < V+local < AFTER_LOCALS(V) < V.post0 < AFTER_POSTS(V)
    +
    +    ``AFTER_LOCALS`` sits after V and every V+local, but before
    +    V.post0.  Upper bound of ``<=V``, ``==V``, ``!=V``.
    +
    +    ``AFTER_POSTS`` sits after every V.postN, but before the next
    +    release segment.  Lower bound of ``>V`` (final or pre-release V)
    +    to exclude post-releases per PEP 440.
    +    """
    +
    +    __slots__ = ("_kind", "_trimmed_release", "version")
    +
    +    def __init__(self, version: Version, kind: _BoundaryKind) -> None:
    +        self.version = version
    +        self._kind = kind
    +        self._trimmed_release = _trim_release(version.release)
    +
    +    def _is_family(self, other: Version) -> bool:
    +        """Is ``other`` a version that this boundary sorts above?"""
    +        v = self.version
    +        if not (
    +            other.epoch == v.epoch
    +            and _trim_release(other.release) == self._trimmed_release
    +            and other.pre == v.pre
    +        ):
    +            return False
    +        if self._kind == _BoundaryKind.AFTER_LOCALS:
    +            # Local family: exact same public version (any local label).
    +            return other.post == v.post and other.dev == v.dev
    +        # Post family: same base + any post-release (or identical).
    +        return other.dev == v.dev or other.post is not None
    +
    +    def __eq__(self, other: object) -> bool:
    +        if isinstance(other, _BoundaryVersion):
    +            return self.version == other.version and self._kind == other._kind
    +        return NotImplemented
    +
    +    def __lt__(self, other: _BoundaryVersion | Version) -> bool:
    +        if isinstance(other, _BoundaryVersion):
    +            if self.version != other.version:
    +                return self.version < other.version
    +            return self._kind.value < other._kind.value
    +        return not self._is_family(other) and self.version < other
    +
    +    def __hash__(self) -> int:
    +        return hash((self.version, self._kind))
    +
    +    def __repr__(self) -> str:
    +        return f"{self.__class__.__name__}({self.version!r}, {self._kind.name})"
    +
    +
    +@functools.total_ordering
    +class _LowerBound:
    +    """Lower bound of a version range.
    +
    +    A version *v* of ``None`` means unbounded below (-inf).
    +    At equal versions, ``[v`` sorts before ``(v`` because an inclusive
    +    bound starts earlier.
    +    """
    +
    +    __slots__ = ("inclusive", "version")
    +
    +    def __init__(self, version: _VersionOrBoundary, inclusive: bool) -> None:
    +        self.version = version
    +        self.inclusive = inclusive
    +
    +    def __eq__(self, other: object) -> bool:
    +        if not isinstance(other, _LowerBound):
    +            return NotImplemented  # pragma: no cover
    +        return self.version == other.version and self.inclusive == other.inclusive
    +
    +    def __lt__(self, other: _LowerBound) -> bool:
    +        if not isinstance(other, _LowerBound):  # pragma: no cover
    +            return NotImplemented
    +        # -inf < anything (except -inf).
    +        if self.version is None:
    +            return other.version is not None
    +        if other.version is None:
    +            return False
    +        if self.version != other.version:
    +            return self.version < other.version
    +        # [v < (v: inclusive starts earlier.
    +        return self.inclusive and not other.inclusive
    +
    +    def __hash__(self) -> int:
    +        return hash((self.version, self.inclusive))
    +
    +    def __repr__(self) -> str:
    +        bracket = "[" if self.inclusive else "("
    +        return f"<{self.__class__.__name__} {bracket}{self.version!r}>"
    +
    +
    +@functools.total_ordering
    +class _UpperBound:
    +    """Upper bound of a version range.
    +
    +    A version *v* of ``None`` means unbounded above (+inf).
    +    At equal versions, ``v)`` sorts before ``v]`` because an exclusive
    +    bound ends earlier.
    +    """
    +
    +    __slots__ = ("inclusive", "version")
    +
    +    def __init__(self, version: _VersionOrBoundary, inclusive: bool) -> None:
    +        self.version = version
    +        self.inclusive = inclusive
    +
    +    def __eq__(self, other: object) -> bool:
    +        if not isinstance(other, _UpperBound):
    +            return NotImplemented  # pragma: no cover
    +        return self.version == other.version and self.inclusive == other.inclusive
    +
    +    def __lt__(self, other: _UpperBound) -> bool:
    +        if not isinstance(other, _UpperBound):  # pragma: no cover
    +            return NotImplemented
    +        # Nothing < +inf (except +inf itself).
    +        if self.version is None:
    +            return False
    +        if other.version is None:
    +            return True
    +        if self.version != other.version:
    +            return self.version < other.version
    +        # v) < v]: exclusive ends earlier.
    +        return not self.inclusive and other.inclusive
    +
    +    def __hash__(self) -> int:
    +        return hash((self.version, self.inclusive))
    +
    +    def __repr__(self) -> str:
    +        bracket = "]" if self.inclusive else ")"
    +        return f"<{self.__class__.__name__} {self.version!r}{bracket}>"
    +
    +
    +if typing.TYPE_CHECKING:
    +    _VersionOrBoundary = Union[Version, _BoundaryVersion, None]
    +
    +    #: A single contiguous version range, represented as a
    +    #: (lower bound, upper bound) pair.
    +    _VersionRange = tuple[_LowerBound, _UpperBound]
    +
    +_NEG_INF = _LowerBound(None, False)
    +_POS_INF = _UpperBound(None, False)
    +_FULL_RANGE: tuple[_VersionRange] = ((_NEG_INF, _POS_INF),)
    +
    +
    +def _range_is_empty(lower: _LowerBound, upper: _UpperBound) -> bool:
    +    """True when the range defined by *lower* and *upper* contains no versions."""
    +    if lower.version is None or upper.version is None:
    +        return False
    +    if lower.version == upper.version:
    +        return not (lower.inclusive and upper.inclusive)
    +    return lower.version > upper.version
    +
    +
    +def _intersect_ranges(
    +    left: Sequence[_VersionRange],
    +    right: Sequence[_VersionRange],
    +) -> list[_VersionRange]:
    +    """Intersect two sorted, non-overlapping range lists (two-pointer merge)."""
    +    result: list[_VersionRange] = []
    +    left_index = right_index = 0
    +    while left_index < len(left) and right_index < len(right):
    +        left_lower, left_upper = left[left_index]
    +        right_lower, right_upper = right[right_index]
    +
    +        lower = max(left_lower, right_lower)
    +        upper = min(left_upper, right_upper)
    +
    +        if not _range_is_empty(lower, upper):
    +            result.append((lower, upper))
    +
    +        # Advance whichever side has the smaller upper bound.
    +        if left_upper < right_upper:
    +            left_index += 1
    +        else:
    +            right_index += 1
    +
    +    return result
    +
    +
    +def _next_prefix_dev0(version: Version) -> Version:
    +    """Smallest version in the next prefix: 1.2 -> 1.3.dev0."""
    +    release = (*version.release[:-1], version.release[-1] + 1)
    +    return Version.from_parts(epoch=version.epoch, release=release, dev=0)
    +
    +
    +def _base_dev0(version: Version) -> Version:
    +    """The .dev0 of a version's base release: 1.2 -> 1.2.dev0."""
    +    return Version.from_parts(epoch=version.epoch, release=version.release, dev=0)
    +
    +
    +def _coerce_version(version: UnparsedVersion) -> Version | None:
    +    if not isinstance(version, Version):
    +        try:
    +            version = Version(version)
    +        except InvalidVersion:
    +            return None
    +    return version
    +
    +
    +def _public_version(version: Version) -> Version:
    +    if version.local is None:
    +        return version
    +    return version.__replace__(local=None)
    +
    +
    +def _post_base(version: Version) -> Version:
    +    """The version that *version* is a post-release of.
    +
    +    1.0.post1 -> 1.0, 1.0a1.post0 -> 1.0a1, 1.0.post0.dev1 -> 1.0.
    +    """
    +    return version.__replace__(post=None, dev=None, local=None)
    +
    +
    +def _earliest_prerelease(version: Version) -> Version:
    +    """Earliest pre-release of *version*.
    +
    +    1.2 -> 1.2.dev0, 1.2.post1 -> 1.2.post1.dev0.
    +    """
    +    return version.__replace__(dev=0, local=None)
    +
    +
    +def _nearest_non_prerelease(
    +    v: _VersionOrBoundary,
    +) -> Version | None:
    +    """Smallest non-pre-release version at or above *v*, or None."""
    +    if v is None:
    +        return None
    +    if isinstance(v, _BoundaryVersion):
    +        inner = v.version
    +        if inner.is_prerelease:
    +            # AFTER_LOCALS(1.0a1) -> nearest non-pre is 1.0
    +            return inner.__replace__(pre=None, dev=None, local=None)
    +        # AFTER_LOCALS(1.0) -> nearest non-pre is 1.0.post0
    +        # AFTER_LOCALS(1.0.post0) -> nearest non-pre is 1.0.post1
    +        k = (inner.post + 1) if inner.post is not None else 0
    +        return inner.__replace__(post=k, local=None)
    +    if not v.is_prerelease:
    +        return v
    +    # Strip pre/dev to get the final or post-release form.
    +    return v.__replace__(pre=None, dev=None, local=None)
    +
    +
    +class InvalidSpecifier(ValueError):
    +    """
    +    Raised when attempting to create a :class:`Specifier` with a specifier
    +    string that is invalid.
    +
    +    >>> Specifier("lolwat")
    +    Traceback (most recent call last):
    +        ...
    +    packaging.specifiers.InvalidSpecifier: Invalid specifier: 'lolwat'
    +    """
    +
    +
    +class BaseSpecifier(metaclass=abc.ABCMeta):
    +    __slots__ = ()
    +    __match_args__ = ("_str",)
    +
    +    @property
    +    def _str(self) -> str:
    +        """Internal property for match_args"""
    +        return str(self)
    +
    +    @abc.abstractmethod
    +    def __str__(self) -> str:
    +        """
    +        Returns the str representation of this Specifier-like object. This
    +        should be representative of the Specifier itself.
    +        """
    +
    +    @abc.abstractmethod
    +    def __hash__(self) -> int:
    +        """
    +        Returns a hash value for this Specifier-like object.
    +        """
    +
    +    @abc.abstractmethod
    +    def __eq__(self, other: object) -> bool:
    +        """
    +        Returns a boolean representing whether or not the two Specifier-like
    +        objects are equal.
    +
    +        :param other: The other object to check against.
    +        """
    +
    +    @property
    +    @abc.abstractmethod
    +    def prereleases(self) -> bool | None:
    +        """Whether or not pre-releases as a whole are allowed.
    +
    +        This can be set to either ``True`` or ``False`` to explicitly enable or disable
    +        prereleases or it can be set to ``None`` (the default) to use default semantics.
    +        """
    +
    +    @prereleases.setter  # noqa: B027
    +    def prereleases(self, value: bool) -> None:
    +        """Setter for :attr:`prereleases`.
    +
    +        :param value: The value to set.
    +        """
    +
    +    @abc.abstractmethod
    +    def contains(self, item: str, prereleases: bool | None = None) -> bool:
    +        """
    +        Determines if the given item is contained within this specifier.
    +        """
    +
    +    @typing.overload
    +    def filter(
    +        self,
    +        iterable: Iterable[UnparsedVersionVar],
    +        prereleases: bool | None = None,
    +        key: None = ...,
    +    ) -> Iterator[UnparsedVersionVar]: ...
    +
    +    @typing.overload
    +    def filter(
    +        self,
    +        iterable: Iterable[T],
    +        prereleases: bool | None = None,
    +        key: Callable[[T], UnparsedVersion] = ...,
    +    ) -> Iterator[T]: ...
    +
    +    @abc.abstractmethod
    +    def filter(
    +        self,
    +        iterable: Iterable[Any],
    +        prereleases: bool | None = None,
    +        key: Callable[[Any], UnparsedVersion] | None = None,
    +    ) -> Iterator[Any]:
    +        """
    +        Takes an iterable of items and filters them so that only items which
    +        are contained within this specifier are allowed in it.
    +        """
    +
    +
    +class Specifier(BaseSpecifier):
    +    """This class abstracts handling of version specifiers.
    +
    +    .. tip::
    +
    +        It is generally not required to instantiate this manually. You should instead
    +        prefer to work with :class:`SpecifierSet` instead, which can parse
    +        comma-separated version specifiers (which is what package metadata contains).
    +
    +    Instances are safe to serialize with :mod:`pickle`. They use a stable
    +    format so the same pickle can be loaded in future packaging releases.
    +
    +    .. versionchanged:: 26.2
    +
    +        Added a stable pickle format. Pickles created with packaging 26.2+ can
    +        be unpickled with future releases.  Backward compatibility with pickles
    +        from packaging < 26.2 is supported but may be removed in a future
    +        release.
    +    """
    +
    +    __slots__ = (
    +        "_prereleases",
    +        "_ranges",
    +        "_spec",
    +        "_spec_version",
    +        "_wildcard_split",
    +    )
    +
    +    _specifier_regex_str = r"""
    +        (?:
    +            (?:
    +                # The identity operators allow for an escape hatch that will
    +                # do an exact string match of the version you wish to install.
    +                # This will not be parsed by PEP 440 and we cannot determine
    +                # any semantic meaning from it. This operator is discouraged
    +                # but included entirely as an escape hatch.
    +                ===  # Only match for the identity operator
    +                \s*
    +                [^\s;)]*  # The arbitrary version can be just about anything,
    +                          # we match everything except for whitespace, a
    +                          # semi-colon for marker support, and a closing paren
    +                          # since versions can be enclosed in them.
    +            )
    +            |
    +            (?:
    +                # The (non)equality operators allow for wild card and local
    +                # versions to be specified so we have to define these two
    +                # operators separately to enable that.
    +                (?:==|!=)            # Only match for equals and not equals
    +
    +                \s*
    +                v?
    +                (?:[0-9]+!)?          # epoch
    +                [0-9]+(?:\.[0-9]+)*   # release
    +
    +                # You cannot use a wild card and a pre-release, post-release, a dev or
    +                # local version together so group them with a | and make them optional.
    +                (?:
    +                    \.\*  # Wild card syntax of .*
    +                    |
    +                    (?a:                                  # pre release
    +                        [-_\.]?
    +                        (alpha|beta|preview|pre|a|b|c|rc)
    +                        [-_\.]?
    +                        [0-9]*
    +                    )?
    +                    (?a:                                  # post release
    +                        (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
    +                    )?
    +                    (?a:[-_\.]?dev[-_\.]?[0-9]*)?         # dev release
    +                    (?a:\+[a-z0-9]+(?:[-_\.][a-z0-9]+)*)? # local
    +                )?
    +            )
    +            |
    +            (?:
    +                # The compatible operator requires at least two digits in the
    +                # release segment.
    +                (?:~=)               # Only match for the compatible operator
    +
    +                \s*
    +                v?
    +                (?:[0-9]+!)?          # epoch
    +                [0-9]+(?:\.[0-9]+)+   # release  (We have a + instead of a *)
    +                (?:                   # pre release
    +                    [-_\.]?
    +                    (alpha|beta|preview|pre|a|b|c|rc)
    +                    [-_\.]?
    +                    [0-9]*
    +                )?
    +                (?:                                   # post release
    +                    (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
    +                )?
    +                (?:[-_\.]?dev[-_\.]?[0-9]*)?          # dev release
    +            )
    +            |
    +            (?:
    +                # All other operators only allow a sub set of what the
    +                # (non)equality operators do. Specifically they do not allow
    +                # local versions to be specified nor do they allow the prefix
    +                # matching wild cards.
    +                (?:<=|>=|<|>)
    +
    +                \s*
    +                v?
    +                (?:[0-9]+!)?          # epoch
    +                [0-9]+(?:\.[0-9]+)*   # release
    +                (?a:                   # pre release
    +                    [-_\.]?
    +                    (alpha|beta|preview|pre|a|b|c|rc)
    +                    [-_\.]?
    +                    [0-9]*
    +                )?
    +                (?a:                                   # post release
    +                    (?:-[0-9]+)|(?:[-_\.]?(post|rev|r)[-_\.]?[0-9]*)
    +                )?
    +                (?a:[-_\.]?dev[-_\.]?[0-9]*)?          # dev release
    +            )
    +        )
    +        """
    +
    +    _regex = re.compile(
    +        r"\s*" + _specifier_regex_str + r"\s*", re.VERBOSE | re.IGNORECASE
    +    )
    +
    +    _operators: Final = {
    +        "~=": "compatible",
    +        "==": "equal",
    +        "!=": "not_equal",
    +        "<=": "less_than_equal",
    +        ">=": "greater_than_equal",
    +        "<": "less_than",
    +        ">": "greater_than",
    +        "===": "arbitrary",
    +    }
    +
    +    def __init__(self, spec: str = "", prereleases: bool | None = None) -> None:
    +        """Initialize a Specifier instance.
    +
    +        :param spec:
    +            The string representation of a specifier which will be parsed and
    +            normalized before use.
    +        :param prereleases:
    +            This tells the specifier if it should accept prerelease versions if
    +            applicable or not. The default of ``None`` will autodetect it from the
    +            given specifiers.
    +        :raises InvalidSpecifier:
    +            If the given specifier is invalid (i.e. bad syntax).
    +        """
    +        if not self._regex.fullmatch(spec):
    +            raise InvalidSpecifier(f"Invalid specifier: {spec!r}")
    +
    +        spec = spec.strip()
    +        if spec.startswith("==="):
    +            operator, version = spec[:3], spec[3:].strip()
    +        elif spec.startswith(("~=", "==", "!=", "<=", ">=")):
    +            operator, version = spec[:2], spec[2:].strip()
    +        else:
    +            operator, version = spec[:1], spec[1:].strip()
    +
    +        self._spec: tuple[str, str] = (operator, version)
    +
    +        # Store whether or not this Specifier should accept prereleases
    +        self._prereleases = prereleases
    +
    +        # Specifier version cache
    +        self._spec_version: tuple[str, Version] | None = None
    +
    +        # Populated on first wildcard (==X.*) comparison
    +        self._wildcard_split: tuple[list[str], int] | None = None
    +
    +        # Version range cache (populated by _to_ranges)
    +        self._ranges: Sequence[_VersionRange] | None = None
    +
    +    def _get_spec_version(self, version: str) -> Version | None:
    +        """One element cache, as only one spec Version is needed per Specifier."""
    +        if self._spec_version is not None and self._spec_version[0] == version:
    +            return self._spec_version[1]
    +
    +        version_specifier = _coerce_version(version)
    +        if version_specifier is None:
    +            return None
    +
    +        self._spec_version = (version, version_specifier)
    +        return version_specifier
    +
    +    def _require_spec_version(self, version: str) -> Version:
    +        """Get spec version, asserting it's valid (not for === operator).
    +
    +        This method should only be called for operators where version
    +        strings are guaranteed to be valid PEP 440 versions (not ===).
    +        """
    +        spec_version = self._get_spec_version(version)
    +        assert spec_version is not None
    +        return spec_version
    +
    +    def _to_ranges(self) -> Sequence[_VersionRange]:
    +        """Convert this specifier to sorted, non-overlapping version ranges.
    +
    +        Each standard operator maps to one or two ranges.  ``===`` is
    +        modeled as full range (actual check done separately).  Cached.
    +        """
    +        if self._ranges is not None:
    +            return self._ranges
    +
    +        op = self.operator
    +        ver_str = self.version
    +
    +        if op == "===":
    +            self._ranges = _FULL_RANGE
    +            return _FULL_RANGE
    +
    +        if ver_str.endswith(".*"):
    +            result = self._wildcard_ranges(op, ver_str)
    +        else:
    +            result = self._standard_ranges(op, ver_str)
    +
    +        self._ranges = result
    +        return result
    +
    +    def _wildcard_ranges(self, op: str, ver_str: str) -> list[_VersionRange]:
    +        # ==1.2.* -> [1.2.dev0, 1.3.dev0);  !=1.2.* -> complement.
    +        base = self._require_spec_version(ver_str[:-2])
    +        lower = _base_dev0(base)
    +        upper = _next_prefix_dev0(base)
    +        if op == "==":
    +            return [(_LowerBound(lower, True), _UpperBound(upper, False))]
    +        # !=
    +        return [
    +            (_NEG_INF, _UpperBound(lower, False)),
    +            (_LowerBound(upper, True), _POS_INF),
    +        ]
    +
    +    def _standard_ranges(self, op: str, ver_str: str) -> list[_VersionRange]:
    +        v = self._require_spec_version(ver_str)
    +
    +        if op == ">=":
    +            return [(_LowerBound(v, True), _POS_INF)]
    +
    +        if op == "<=":
    +            return [
    +                (
    +                    _NEG_INF,
    +                    _UpperBound(_BoundaryVersion(v, _BoundaryKind.AFTER_LOCALS), True),
    +                )
    +            ]
    +
    +        if op == ">":
    +            if v.dev is not None:
    +                # >V.devN: dev versions have no post-releases, so the
    +                # next real version is V.dev(N+1).
    +                lower_ver = v.__replace__(dev=v.dev + 1, local=None)
    +                return [(_LowerBound(lower_ver, True), _POS_INF)]
    +            if v.post is not None:
    +                # >V.postN: next real version is V.post(N+1).dev0.
    +                lower_ver = v.__replace__(post=v.post + 1, dev=0, local=None)
    +                return [(_LowerBound(lower_ver, True), _POS_INF)]
    +            # >V (final or pre-release): skip V+local and all V.postN.
    +            return [
    +                (
    +                    _LowerBound(_BoundaryVersion(v, _BoundaryKind.AFTER_POSTS), False),
    +                    _POS_INF,
    +                )
    +            ]
    +
    +        if op == "<":
    +            #  bool | None:
    +        # If there is an explicit prereleases set for this, then we'll just
    +        # blindly use that.
    +        if self._prereleases is not None:
    +            return self._prereleases
    +
    +        # Only the "!=" operator does not imply prereleases when
    +        # the version in the specifier is a prerelease.
    +        operator, version_str = self._spec
    +        if operator == "!=":
    +            return False
    +
    +        # The == specifier with trailing .* cannot include prereleases
    +        # e.g. "==1.0a1.*" is not valid.
    +        if operator == "==" and version_str.endswith(".*"):
    +            return False
    +
    +        # "===" can have arbitrary string versions, so we cannot parse
    +        # those, we take prereleases as unknown (None) for those.
    +        version = self._get_spec_version(version_str)
    +        if version is None:
    +            return None
    +
    +        # For all other operators, use the check if spec Version
    +        # object implies pre-releases.
    +        return version.is_prerelease
    +
    +    @prereleases.setter
    +    def prereleases(self, value: bool | None) -> None:
    +        self._prereleases = value
    +
    +    def __getstate__(self) -> tuple[tuple[str, str], bool | None]:
    +        # Return state as a 2-item tuple for compactness:
    +        #   ((operator, version), prereleases)
    +        # Cache members are excluded and will be recomputed on demand.
    +        return (self._spec, self._prereleases)
    +
    +    def __setstate__(self, state: object) -> None:
    +        # Always discard cached values - they will be recomputed on demand.
    +        self._spec_version = None
    +        self._wildcard_split = None
    +        self._ranges = None
    +
    +        if isinstance(state, tuple):
    +            if len(state) == 2:
    +                # New format (26.2+): ((operator, version), prereleases)
    +                spec, prereleases = state
    +                if _validate_spec(spec) and _validate_pre(prereleases):
    +                    self._spec = spec
    +                    self._prereleases = prereleases
    +                    return
    +            if len(state) == 2 and isinstance(state[1], dict):
    +                # Format (packaging 26.0-26.1): (None, {slot: value}).
    +                _, slot_dict = state
    +                spec = slot_dict.get("_spec")
    +                prereleases = slot_dict.get("_prereleases", "invalid")
    +                if _validate_spec(spec) and _validate_pre(prereleases):
    +                    self._spec = spec
    +                    self._prereleases = prereleases
    +                    return
    +        if isinstance(state, dict):
    +            # Old format (packaging <= 25.x, no __slots__): state is a plain dict.
    +            spec = state.get("_spec")
    +            prereleases = state.get("_prereleases", "invalid")
    +            if _validate_spec(spec) and _validate_pre(prereleases):
    +                self._spec = spec
    +                self._prereleases = prereleases
    +                return
    +
    +        raise TypeError(f"Cannot restore Specifier from {state!r}")
    +
    +    @property
    +    def operator(self) -> str:
    +        """The operator of this specifier.
    +
    +        >>> Specifier("==1.2.3").operator
    +        '=='
    +        """
    +        return self._spec[0]
    +
    +    @property
    +    def version(self) -> str:
    +        """The version of this specifier.
    +
    +        >>> Specifier("==1.2.3").version
    +        '1.2.3'
    +        """
    +        return self._spec[1]
    +
    +    def __repr__(self) -> str:
    +        """A representation of the Specifier that shows all internal state.
    +
    +        >>> Specifier('>=1.0.0')
    +        =1.0.0')>
    +        >>> Specifier('>=1.0.0', prereleases=False)
    +        =1.0.0', prereleases=False)>
    +        >>> Specifier('>=1.0.0', prereleases=True)
    +        =1.0.0', prereleases=True)>
    +        """
    +        pre = (
    +            f", prereleases={self.prereleases!r}"
    +            if self._prereleases is not None
    +            else ""
    +        )
    +
    +        return f"<{self.__class__.__name__}({str(self)!r}{pre})>"
    +
    +    def __str__(self) -> str:
    +        """A string representation of the Specifier that can be round-tripped.
    +
    +        >>> str(Specifier('>=1.0.0'))
    +        '>=1.0.0'
    +        >>> str(Specifier('>=1.0.0', prereleases=False))
    +        '>=1.0.0'
    +        """
    +        return "{}{}".format(*self._spec)
    +
    +    @property
    +    def _canonical_spec(self) -> tuple[str, str]:
    +        operator, version = self._spec
    +        if operator == "===" or version.endswith(".*"):
    +            return operator, version
    +
    +        spec_version = self._require_spec_version(version)
    +
    +        canonical_version = canonicalize_version(
    +            spec_version, strip_trailing_zero=(operator != "~=")
    +        )
    +
    +        return operator, canonical_version
    +
    +    def __hash__(self) -> int:
    +        return hash(self._canonical_spec)
    +
    +    def __eq__(self, other: object) -> bool:
    +        """Whether or not the two Specifier-like objects are equal.
    +
    +        :param other: The other object to check against.
    +
    +        The value of :attr:`prereleases` is ignored.
    +
    +        >>> Specifier("==1.2.3") == Specifier("== 1.2.3.0")
    +        True
    +        >>> (Specifier("==1.2.3", prereleases=False) ==
    +        ...  Specifier("==1.2.3", prereleases=True))
    +        True
    +        >>> Specifier("==1.2.3") == "==1.2.3"
    +        True
    +        >>> Specifier("==1.2.3") == Specifier("==1.2.4")
    +        False
    +        >>> Specifier("==1.2.3") == Specifier("~=1.2.3")
    +        False
    +        """
    +        if isinstance(other, str):
    +            try:
    +                other = self.__class__(str(other))
    +            except InvalidSpecifier:
    +                return NotImplemented
    +        elif not isinstance(other, self.__class__):
    +            return NotImplemented
    +
    +        return self._canonical_spec == other._canonical_spec
    +
    +    def _get_operator(self, op: str) -> CallableOperator:
    +        operator_callable: CallableOperator = getattr(
    +            self, f"_compare_{self._operators[op]}"
    +        )
    +        return operator_callable
    +
    +    def _compare_compatible(self, prospective: Version, spec: str) -> bool:
    +        # Compatible releases have an equivalent combination of >= and ==. That
    +        # is that ~=2.2 is equivalent to >=2.2,==2.*. This allows us to
    +        # implement this in terms of the other specifiers instead of
    +        # implementing it ourselves. The only thing we need to do is construct
    +        # the other specifiers.
    +
    +        # We want everything but the last item in the version, but we want to
    +        # ignore suffix segments.
    +        prefix = _version_join(
    +            list(itertools.takewhile(_is_not_suffix, _version_split(spec)))[:-1]
    +        )
    +
    +        # Add the prefix notation to the end of our string
    +        prefix += ".*"
    +
    +        return (self._compare_greater_than_equal(prospective, spec)) and (
    +            self._compare_equal(prospective, prefix)
    +        )
    +
    +    def _get_wildcard_split(self, spec: str) -> tuple[list[str], int]:
    +        """Cached split of a wildcard spec into components and numeric length.
    +
    +        >>> Specifier("==1.*")._get_wildcard_split("1.*")
    +        (['0', '1'], 2)
    +        >>> Specifier("==3.10.*")._get_wildcard_split("3.10.*")
    +        (['0', '3', '10'], 3)
    +        """
    +        wildcard_split = self._wildcard_split
    +        if wildcard_split is None:
    +            normalized = canonicalize_version(spec[:-2], strip_trailing_zero=False)
    +            split_spec = _version_split(normalized)
    +            wildcard_split = (split_spec, _numeric_prefix_len(split_spec))
    +            self._wildcard_split = wildcard_split
    +        return wildcard_split
    +
    +    def _compare_equal(self, prospective: Version, spec: str) -> bool:
    +        # We need special logic to handle prefix matching
    +        if spec.endswith(".*"):
    +            split_spec, spec_numeric_len = self._get_wildcard_split(spec)
    +
    +            # In the case of prefix matching we want to ignore local segment.
    +            normalized_prospective = canonicalize_version(
    +                _public_version(prospective), strip_trailing_zero=False
    +            )
    +            # Split the prospective version out by bangs and dots, and pretend
    +            # that there is an implicit dot in between a release segment and
    +            # a pre-release segment.
    +            split_prospective = _version_split(normalized_prospective)
    +
    +            # 0-pad the prospective version before shortening it to get the correct
    +            # shortened version.
    +            padded_prospective = _left_pad(split_prospective, spec_numeric_len)
    +
    +            # Shorten the prospective version to be the same length as the spec
    +            # so that we can determine if the specifier is a prefix of the
    +            # prospective version or not.
    +            shortened_prospective = padded_prospective[: len(split_spec)]
    +
    +            return shortened_prospective == split_spec
    +        else:
    +            # Convert our spec string into a Version
    +            spec_version = self._require_spec_version(spec)
    +
    +            # If the specifier does not have a local segment, then we want to
    +            # act as if the prospective version also does not have a local
    +            # segment.
    +            if not spec_version.local:
    +                prospective = _public_version(prospective)
    +
    +            return prospective == spec_version
    +
    +    def _compare_not_equal(self, prospective: Version, spec: str) -> bool:
    +        return not self._compare_equal(prospective, spec)
    +
    +    def _compare_less_than_equal(self, prospective: Version, spec: str) -> bool:
    +        # NB: Local version identifiers are NOT permitted in the version
    +        # specifier, so local version labels can be universally removed from
    +        # the prospective version.
    +        return _public_version(prospective) <= self._require_spec_version(spec)
    +
    +    def _compare_greater_than_equal(self, prospective: Version, spec: str) -> bool:
    +        # NB: Local version identifiers are NOT permitted in the version
    +        # specifier, so local version labels can be universally removed from
    +        # the prospective version.
    +        return _public_version(prospective) >= self._require_spec_version(spec)
    +
    +    def _compare_less_than(self, prospective: Version, spec_str: str) -> bool:
    +        # Convert our spec to a Version instance, since we'll want to work with
    +        # it as a version.
    +        spec = self._require_spec_version(spec_str)
    +
    +        # Check to see if the prospective version is less than the spec
    +        # version. If it's not we can short circuit and just return False now
    +        # instead of doing extra unneeded work.
    +        if not prospective < spec:
    +            return False
    +
    +        # The spec says: "= _earliest_prerelease(spec)
    +        ):
    +            return False
    +
    +        # If we've gotten to here, it means that prospective version is both
    +        # less than the spec version *and* it's not a pre-release of the same
    +        # version in the spec.
    +        return True
    +
    +    def _compare_greater_than(self, prospective: Version, spec_str: str) -> bool:
    +        # Convert our spec to a Version instance, since we'll want to work with
    +        # it as a version.
    +        spec = self._require_spec_version(spec_str)
    +
    +        # Check to see if the prospective version is greater than the spec
    +        # version. If it's not we can short circuit and just return False now
    +        # instead of doing extra unneeded work.
    +        if not prospective > spec:
    +            return False
    +
    +        # The spec says: ">V MUST NOT allow a post-release of the specified
    +        # version unless the specified version is itself a post-release."
    +        if (
    +            not spec.is_postrelease
    +            and prospective.is_postrelease
    +            and _post_base(prospective) == spec
    +        ):
    +            return False
    +
    +        # Per the spec: ">V MUST NOT match a local version of the specified
    +        # version". A "local version of V" is any version whose public part
    +        # equals V. So >1.0a1 must not match 1.0a1+local, but must still
    +        # match 1.0a2+local.
    +        if prospective.local is not None and _public_version(prospective) == spec:
    +            return False
    +
    +        # If we've gotten to here, it means that prospective version is both
    +        # greater than the spec version *and* it's not a pre-release of the
    +        # same version in the spec.
    +        return True
    +
    +    def _compare_arbitrary(self, prospective: Version | str, spec: str) -> bool:
    +        return str(prospective).lower() == str(spec).lower()
    +
    +    def __contains__(self, item: str | Version) -> bool:
    +        """Return whether or not the item is contained in this specifier.
    +
    +        :param item: The item to check for.
    +
    +        This is used for the ``in`` operator and behaves the same as
    +        :meth:`contains` with no ``prereleases`` argument passed.
    +
    +        >>> "1.2.3" in Specifier(">=1.2.3")
    +        True
    +        >>> Version("1.2.3") in Specifier(">=1.2.3")
    +        True
    +        >>> "1.0.0" in Specifier(">=1.2.3")
    +        False
    +        >>> "1.3.0a1" in Specifier(">=1.2.3")
    +        True
    +        >>> "1.3.0a1" in Specifier(">=1.2.3", prereleases=True)
    +        True
    +        """
    +        return self.contains(item)
    +
    +    def contains(self, item: UnparsedVersion, prereleases: bool | None = None) -> bool:
    +        """Return whether or not the item is contained in this specifier.
    +
    +        :param item:
    +            The item to check for, which can be a version string or a
    +            :class:`Version` instance.
    +        :param prereleases:
    +            Whether or not to match prereleases with this Specifier. If set to
    +            ``None`` (the default), it will follow the recommendation from
    +            :pep:`440` and match prereleases, as there are no other versions.
    +
    +        >>> Specifier(">=1.2.3").contains("1.2.3")
    +        True
    +        >>> Specifier(">=1.2.3").contains(Version("1.2.3"))
    +        True
    +        >>> Specifier(">=1.2.3").contains("1.0.0")
    +        False
    +        >>> Specifier(">=1.2.3").contains("1.3.0a1")
    +        True
    +        >>> Specifier(">=1.2.3", prereleases=False).contains("1.3.0a1")
    +        False
    +        >>> Specifier(">=1.2.3").contains("1.3.0a1")
    +        True
    +        """
    +
    +        return bool(list(self.filter([item], prereleases=prereleases)))
    +
    +    @typing.overload
    +    def filter(
    +        self,
    +        iterable: Iterable[UnparsedVersionVar],
    +        prereleases: bool | None = None,
    +        key: None = ...,
    +    ) -> Iterator[UnparsedVersionVar]: ...
    +
    +    @typing.overload
    +    def filter(
    +        self,
    +        iterable: Iterable[T],
    +        prereleases: bool | None = None,
    +        key: Callable[[T], UnparsedVersion] = ...,
    +    ) -> Iterator[T]: ...
    +
    +    def filter(
    +        self,
    +        iterable: Iterable[Any],
    +        prereleases: bool | None = None,
    +        key: Callable[[Any], UnparsedVersion] | None = None,
    +    ) -> Iterator[Any]:
    +        """Filter items in the given iterable, that match the specifier.
    +
    +        :param iterable:
    +            An iterable that can contain version strings and :class:`Version` instances.
    +            The items in the iterable will be filtered according to the specifier.
    +        :param prereleases:
    +            Whether or not to allow prereleases in the returned iterator. If set to
    +            ``None`` (the default), it will follow the recommendation from :pep:`440`
    +            and match prereleases if there are no other versions.
    +        :param key:
    +            A callable that takes a single argument (an item from the iterable) and
    +            returns a version string or :class:`Version` instance to be used for
    +            filtering.
    +
    +        >>> list(Specifier(">=1.2.3").filter(["1.2", "1.3", "1.5a1"]))
    +        ['1.3']
    +        >>> list(Specifier(">=1.2.3").filter(["1.2", "1.2.3", "1.3", Version("1.4")]))
    +        ['1.2.3', '1.3', ]
    +        >>> list(Specifier(">=1.2.3").filter(["1.2", "1.5a1"]))
    +        ['1.5a1']
    +        >>> list(Specifier(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True))
    +        ['1.3', '1.5a1']
    +        >>> list(Specifier(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"]))
    +        ['1.3', '1.5a1']
    +        >>> list(Specifier(">=1.2.3").filter(
    +        ... [{"ver": "1.2"}, {"ver": "1.3"}],
    +        ... key=lambda x: x["ver"]))
    +        [{'ver': '1.3'}]
    +        """
    +        prereleases_versions = []
    +        found_non_prereleases = False
    +
    +        # Determine if to include prereleases by default
    +        include_prereleases = (
    +            prereleases if prereleases is not None else self.prereleases
    +        )
    +
    +        # Get the matching operator
    +        operator_callable = self._get_operator(self.operator)
    +
    +        # Filter versions
    +        for version in iterable:
    +            parsed_version = _coerce_version(version if key is None else key(version))
    +            match = False
    +            if parsed_version is None:
    +                # === operator can match arbitrary (non-version) strings
    +                if self.operator == "===" and self._compare_arbitrary(
    +                    version, self.version
    +                ):
    +                    yield version
    +            elif self.operator == "===":
    +                match = self._compare_arbitrary(
    +                    version if key is None else key(version), self.version
    +                )
    +            else:
    +                match = operator_callable(parsed_version, self.version)
    +
    +            if match and parsed_version is not None:
    +                # If it's not a prerelease or prereleases are allowed, yield it directly
    +                if not parsed_version.is_prerelease or include_prereleases:
    +                    found_non_prereleases = True
    +                    yield version
    +                # Otherwise collect prereleases for potential later use
    +                elif prereleases is None and self._prereleases is not False:
    +                    prereleases_versions.append(version)
    +
    +        # If no non-prereleases were found and prereleases weren't
    +        # explicitly forbidden, yield the collected prereleases
    +        if (
    +            not found_non_prereleases
    +            and prereleases is None
    +            and self._prereleases is not False
    +        ):
    +            yield from prereleases_versions
    +
    +
    +_prefix_regex = re.compile(r"([0-9]+)((?:a|b|c|rc)[0-9]+)")
    +
    +
    +def _pep440_filter_prereleases(
    +    iterable: Iterable[Any], key: Callable[[Any], UnparsedVersion] | None
    +) -> Iterator[Any]:
    +    """Filter per PEP 440: exclude prereleases unless no finals exist."""
    +    # Two lists used:
    +    #   * all_nonfinal to preserve order if no finals exist
    +    #   * arbitrary_strings for streaming when first final found
    +    all_nonfinal: list[Any] = []
    +    arbitrary_strings: list[Any] = []
    +
    +    found_final = False
    +    for item in iterable:
    +        parsed = _coerce_version(item if key is None else key(item))
    +
    +        if parsed is None:
    +            # Arbitrary strings are always included as it is not
    +            # possible to determine if they are prereleases,
    +            # and they have already passed all specifiers.
    +            if found_final:
    +                yield item
    +            else:
    +                arbitrary_strings.append(item)
    +                all_nonfinal.append(item)
    +            continue
    +
    +        if not parsed.is_prerelease:
    +            # Final release found - flush arbitrary strings, then yield
    +            if not found_final:
    +                yield from arbitrary_strings
    +                found_final = True
    +            yield item
    +            continue
    +
    +        # Prerelease - buffer if no finals yet, otherwise skip
    +        if not found_final:
    +            all_nonfinal.append(item)
    +
    +    # No finals found - yield all buffered items
    +    if not found_final:
    +        yield from all_nonfinal
    +
    +
    +def _version_split(version: str) -> list[str]:
    +    """Split version into components.
    +
    +    The split components are intended for version comparison. The logic does
    +    not attempt to retain the original version string, so joining the
    +    components back with :func:`_version_join` may not produce the original
    +    version string.
    +    """
    +    result: list[str] = []
    +
    +    epoch, _, rest = version.rpartition("!")
    +    result.append(epoch or "0")
    +
    +    for item in rest.split("."):
    +        match = _prefix_regex.fullmatch(item)
    +        if match:
    +            result.extend(match.groups())
    +        else:
    +            result.append(item)
    +    return result
    +
    +
    +def _version_join(components: list[str]) -> str:
    +    """Join split version components into a version string.
    +
    +    This function assumes the input came from :func:`_version_split`, where the
    +    first component must be the epoch (either empty or numeric), and all other
    +    components numeric.
    +    """
    +    epoch, *rest = components
    +    return f"{epoch}!{'.'.join(rest)}"
    +
    +
    +def _is_not_suffix(segment: str) -> bool:
    +    return not any(
    +        segment.startswith(prefix) for prefix in ("dev", "a", "b", "rc", "post")
    +    )
    +
    +
    +def _numeric_prefix_len(split: list[str]) -> int:
    +    """Count leading numeric components in a :func:`_version_split` result.
    +
    +    >>> _numeric_prefix_len(["0", "1", "2", "a1"])
    +    3
    +    """
    +    count = 0
    +    for segment in split:
    +        if not segment.isdigit():
    +            break
    +        count += 1
    +    return count
    +
    +
    +def _left_pad(split: list[str], target_numeric_len: int) -> list[str]:
    +    """Pad a :func:`_version_split` result with ``"0"`` segments to reach
    +    ``target_numeric_len`` numeric components.  Suffix segments are preserved.
    +
    +    >>> _left_pad(["0", "1", "a1"], 4)
    +    ['0', '1', '0', '0', 'a1']
    +    """
    +    numeric_len = _numeric_prefix_len(split)
    +    pad_needed = target_numeric_len - numeric_len
    +    if pad_needed <= 0:
    +        return split
    +    return [*split[:numeric_len], *(["0"] * pad_needed), *split[numeric_len:]]
    +
    +
    +def _operator_cost(op_entry: tuple[CallableOperator, str, str]) -> int:
    +    """Sort key for Cost Based Ordering of specifier operators in _filter_versions.
    +
    +    Operators run sequentially on a shrinking candidate set, so operators that
    +    reject the most versions should run first to minimize work for later ones.
    +
    +    Tier 0: Exact equality (==, ===), likely to narrow candidates to one version
    +    Tier 1: Range checks (>=, <=, >, <), cheap and usually reject a large portion
    +    Tier 2: Wildcard equality (==.*) and compatible release (~=), more expensive
    +    Tier 3: Exact !=, cheap but rarely rejects
    +    Tier 4: Wildcard !=.*, expensive and rarely rejects
    +    """
    +    _, ver, op = op_entry
    +    if op == "==":
    +        return 0 if not ver.endswith(".*") else 2
    +    if op in (">=", "<=", ">", "<"):
    +        return 1
    +    if op == "~=":
    +        return 2
    +    if op == "!=":
    +        return 3 if not ver.endswith(".*") else 4
    +    if op == "===":
    +        return 0
    +
    +    raise ValueError(f"Unknown operator: {op!r}")  # pragma: no cover
    +
    +
    +class SpecifierSet(BaseSpecifier):
    +    """This class abstracts handling of a set of version specifiers.
    +
    +    It can be passed a single specifier (``>=3.0``), a comma-separated list of
    +    specifiers (``>=3.0,!=3.1``), or no specifier at all.
    +
    +    Instances are safe to serialize with :mod:`pickle`. They use a stable
    +    format so the same pickle can be loaded in future packaging
    +    releases.
    +
    +    .. versionchanged:: 26.2
    +
    +        Added a stable pickle format. Pickles created with
    +        packaging 26.2+ can be unpickled with future releases.
    +        Backward compatibility with pickles from
    +        packaging < 26.2 is supported but may be removed in a future
    +        release.
    +    """
    +
    +    __slots__ = (
    +        "_canonicalized",
    +        "_has_arbitrary",
    +        "_is_unsatisfiable",
    +        "_prereleases",
    +        "_resolved_ops",
    +        "_specs",
    +    )
    +
    +    def __init__(
    +        self,
    +        specifiers: str | Iterable[Specifier] = "",
    +        prereleases: bool | None = None,
    +    ) -> None:
    +        """Initialize a SpecifierSet instance.
    +
    +        :param specifiers:
    +            The string representation of a specifier or a comma-separated list of
    +            specifiers which will be parsed and normalized before use.
    +            May also be an iterable of ``Specifier`` instances, which will be used
    +            as is.
    +        :param prereleases:
    +            This tells the SpecifierSet if it should accept prerelease versions if
    +            applicable or not. The default of ``None`` will autodetect it from the
    +            given specifiers.
    +
    +        :raises InvalidSpecifier:
    +            If the given ``specifiers`` are not parseable than this exception will be
    +            raised.
    +        """
    +
    +        if isinstance(specifiers, str):
    +            # Split on `,` to break each individual specifier into its own item, and
    +            # strip each item to remove leading/trailing whitespace.
    +            split_specifiers = [s.strip() for s in specifiers.split(",") if s.strip()]
    +
    +            self._specs: tuple[Specifier, ...] = tuple(map(Specifier, split_specifiers))
    +            # Fast substring check; avoids iterating parsed specs.
    +            self._has_arbitrary = "===" in specifiers
    +        else:
    +            self._specs = tuple(specifiers)
    +            # Substring check works for both Specifier objects and plain
    +            # strings (setuptools passes lists of strings).
    +            self._has_arbitrary = any("===" in str(s) for s in self._specs)
    +
    +        self._canonicalized = len(self._specs) <= 1
    +        self._resolved_ops: list[tuple[CallableOperator, str, str]] | None = None
    +
    +        # Store our prereleases value so we can use it later to determine if
    +        # we accept prereleases or not.
    +        self._prereleases = prereleases
    +
    +        self._is_unsatisfiable: bool | None = None
    +
    +    def _canonical_specs(self) -> tuple[Specifier, ...]:
    +        """Deduplicate, sort, and cache specs for order-sensitive operations."""
    +        if not self._canonicalized:
    +            self._specs = tuple(dict.fromkeys(sorted(self._specs, key=str)))
    +            self._canonicalized = True
    +            self._resolved_ops = None
    +            self._is_unsatisfiable = None
    +        return self._specs
    +
    +    @property
    +    def prereleases(self) -> bool | None:
    +        # If we have been given an explicit prerelease modifier, then we'll
    +        # pass that through here.
    +        if self._prereleases is not None:
    +            return self._prereleases
    +
    +        # If we don't have any specifiers, and we don't have a forced value,
    +        # then we'll just return None since we don't know if this should have
    +        # pre-releases or not.
    +        if not self._specs:
    +            return None
    +
    +        # Otherwise we'll see if any of the given specifiers accept
    +        # prereleases, if any of them do we'll return True, otherwise False.
    +        if any(s.prereleases for s in self._specs):
    +            return True
    +
    +        return None
    +
    +    @prereleases.setter
    +    def prereleases(self, value: bool | None) -> None:
    +        self._prereleases = value
    +        self._is_unsatisfiable = None
    +
    +    def __getstate__(self) -> tuple[tuple[Specifier, ...], bool | None]:
    +        # Return state as a 2-item tuple for compactness:
    +        #   (specs, prereleases)
    +        # Cache members are excluded and will be recomputed on demand.
    +        return (self._specs, self._prereleases)
    +
    +    def __setstate__(self, state: object) -> None:
    +        # Always discard cached values - they will be recomputed on demand.
    +        self._resolved_ops = None
    +        self._is_unsatisfiable = None
    +
    +        if isinstance(state, tuple):
    +            if len(state) == 2:
    +                # New format (26.2+): (specs, prereleases)
    +                specs, prereleases = state
    +                if (
    +                    isinstance(specs, tuple)
    +                    and all(isinstance(s, Specifier) for s in specs)
    +                    and _validate_pre(prereleases)
    +                ):
    +                    self._specs = specs
    +                    self._prereleases = prereleases
    +                    self._canonicalized = len(specs) <= 1
    +                    self._has_arbitrary = any("===" in str(s) for s in specs)
    +                    return
    +            if len(state) == 2 and isinstance(state[1], dict):
    +                # Format (packaging 26.0-26.1): (None, {slot: value}).
    +                _, slot_dict = state
    +                specs = slot_dict.get("_specs", ())
    +                prereleases = slot_dict.get("_prereleases")
    +                # Convert frozenset to tuple (26.0 stored as frozenset)
    +                if isinstance(specs, frozenset):
    +                    specs = tuple(sorted(specs, key=str))
    +                if (
    +                    isinstance(specs, tuple)
    +                    and all(isinstance(s, Specifier) for s in specs)
    +                    and _validate_pre(prereleases)
    +                ):
    +                    self._specs = specs
    +                    self._prereleases = prereleases
    +                    self._canonicalized = len(self._specs) <= 1
    +                    self._has_arbitrary = any("===" in str(s) for s in self._specs)
    +                    return
    +        if isinstance(state, dict):
    +            # Old format (packaging <= 25.x, no __slots__): state is a plain dict.
    +            specs = state.get("_specs", ())
    +            prereleases = state.get("_prereleases")
    +            # Convert frozenset to tuple (26.0 stored as frozenset)
    +            if isinstance(specs, frozenset):
    +                specs = tuple(sorted(specs, key=str))
    +            if (
    +                isinstance(specs, tuple)
    +                and all(isinstance(s, Specifier) for s in specs)
    +                and _validate_pre(prereleases)
    +            ):
    +                self._specs = specs
    +                self._prereleases = prereleases
    +                self._canonicalized = len(self._specs) <= 1
    +                self._has_arbitrary = any("===" in str(s) for s in self._specs)
    +                return
    +
    +        raise TypeError(f"Cannot restore SpecifierSet from {state!r}")
    +
    +    def __repr__(self) -> str:
    +        """A representation of the specifier set that shows all internal state.
    +
    +        Note that the ordering of the individual specifiers within the set may not
    +        match the input string.
    +
    +        >>> SpecifierSet('>=1.0.0,!=2.0.0')
    +        =1.0.0')>
    +        >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=False)
    +        =1.0.0', prereleases=False)>
    +        >>> SpecifierSet('>=1.0.0,!=2.0.0', prereleases=True)
    +        =1.0.0', prereleases=True)>
    +        """
    +        pre = (
    +            f", prereleases={self.prereleases!r}"
    +            if self._prereleases is not None
    +            else ""
    +        )
    +
    +        return f"<{self.__class__.__name__}({str(self)!r}{pre})>"
    +
    +    def __str__(self) -> str:
    +        """A string representation of the specifier set that can be round-tripped.
    +
    +        Note that the ordering of the individual specifiers within the set may not
    +        match the input string.
    +
    +        >>> str(SpecifierSet(">=1.0.0,!=1.0.1"))
    +        '!=1.0.1,>=1.0.0'
    +        >>> str(SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False))
    +        '!=1.0.1,>=1.0.0'
    +        """
    +        return ",".join(str(s) for s in self._canonical_specs())
    +
    +    def __hash__(self) -> int:
    +        return hash(self._canonical_specs())
    +
    +    def __and__(self, other: SpecifierSet | str) -> SpecifierSet:
    +        """Return a SpecifierSet which is a combination of the two sets.
    +
    +        :param other: The other object to combine with.
    +
    +        >>> SpecifierSet(">=1.0.0,!=1.0.1") & '<=2.0.0,!=2.0.1'
    +        =1.0.0')>
    +        >>> SpecifierSet(">=1.0.0,!=1.0.1") & SpecifierSet('<=2.0.0,!=2.0.1')
    +        =1.0.0')>
    +        """
    +        if isinstance(other, str):
    +            other = SpecifierSet(other)
    +        elif not isinstance(other, SpecifierSet):
    +            return NotImplemented
    +
    +        specifier = SpecifierSet()
    +        specifier._specs = self._specs + other._specs
    +        specifier._canonicalized = len(specifier._specs) <= 1
    +        specifier._has_arbitrary = self._has_arbitrary or other._has_arbitrary
    +        specifier._resolved_ops = None
    +
    +        # Combine prerelease settings: use common or non-None value
    +        if self._prereleases is None or self._prereleases == other._prereleases:
    +            specifier._prereleases = other._prereleases
    +        elif other._prereleases is None:
    +            specifier._prereleases = self._prereleases
    +        else:
    +            raise ValueError(
    +                "Cannot combine SpecifierSets with True and False prerelease overrides."
    +            )
    +
    +        return specifier
    +
    +    def __eq__(self, other: object) -> bool:
    +        """Whether or not the two SpecifierSet-like objects are equal.
    +
    +        :param other: The other object to check against.
    +
    +        The value of :attr:`prereleases` is ignored.
    +
    +        >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.1")
    +        True
    +        >>> (SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False) ==
    +        ...  SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True))
    +        True
    +        >>> SpecifierSet(">=1.0.0,!=1.0.1") == ">=1.0.0,!=1.0.1"
    +        True
    +        >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0")
    +        False
    +        >>> SpecifierSet(">=1.0.0,!=1.0.1") == SpecifierSet(">=1.0.0,!=1.0.2")
    +        False
    +        """
    +        if isinstance(other, (str, Specifier)):
    +            other = SpecifierSet(str(other))
    +        elif not isinstance(other, SpecifierSet):
    +            return NotImplemented
    +
    +        return self._canonical_specs() == other._canonical_specs()
    +
    +    def __len__(self) -> int:
    +        """Returns the number of specifiers in this specifier set."""
    +        return len(self._specs)
    +
    +    def __iter__(self) -> Iterator[Specifier]:
    +        """
    +        Returns an iterator over all the underlying :class:`Specifier` instances
    +        in this specifier set.
    +
    +        >>> sorted(SpecifierSet(">=1.0.0,!=1.0.1"), key=str)
    +        [, =1.0.0')>]
    +        """
    +        return iter(self._specs)
    +
    +    def _get_ranges(self) -> Sequence[_VersionRange]:
    +        """Intersect all specifiers into a single list of version ranges.
    +
    +        Returns an empty list when unsatisfiable.  ``===`` specs are
    +        modeled as full range; string matching is checked separately
    +        by :meth:`_check_arbitrary_unsatisfiable`.
    +        """
    +        specs = self._specs
    +
    +        result: Sequence[_VersionRange] | None = None
    +        for s in specs:
    +            if result is None:
    +                result = s._to_ranges()
    +            else:
    +                result = _intersect_ranges(result, s._to_ranges())
    +                if not result:
    +                    break
    +
    +        if result is None:  # pragma: no cover
    +            raise RuntimeError("_get_ranges called with no specs")
    +        return result
    +
    +    def is_unsatisfiable(self) -> bool:
    +        """Check whether this specifier set can never be satisfied.
    +
    +        Returns True if no version can satisfy all specifiers simultaneously.
    +
    +        >>> SpecifierSet(">=2.0,<1.0").is_unsatisfiable()
    +        True
    +        >>> SpecifierSet(">=1.0,<2.0").is_unsatisfiable()
    +        False
    +        >>> SpecifierSet("").is_unsatisfiable()
    +        False
    +        >>> SpecifierSet("==1.0,!=1.0").is_unsatisfiable()
    +        True
    +        """
    +        cached = self._is_unsatisfiable
    +        if cached is not None:
    +            return cached
    +
    +        if not self._specs:
    +            self._is_unsatisfiable = False
    +            return False
    +
    +        result = not self._get_ranges()
    +
    +        if not result:
    +            result = self._check_arbitrary_unsatisfiable()
    +
    +        if not result and self.prereleases is False:
    +            result = self._check_prerelease_only_ranges()
    +
    +        self._is_unsatisfiable = result
    +        return result
    +
    +    def _check_prerelease_only_ranges(self) -> bool:
    +        """With prereleases=False, check if every range contains only
    +        pre-release versions (which would be excluded from matching)."""
    +        for lower, upper in self._get_ranges():
    +            nearest = _nearest_non_prerelease(lower.version)
    +            if nearest is None:
    +                return False
    +            if upper.version is None or nearest < upper.version:
    +                return False
    +            if nearest == upper.version and upper.inclusive:
    +                return False
    +        return True
    +
    +    def _check_arbitrary_unsatisfiable(self) -> bool:
    +        """Check === (arbitrary equality) specs for unsatisfiability.
    +
    +        === uses case-insensitive string comparison, so the only candidate
    +        that can match ``===V`` is the literal string V.  This method
    +        checks whether that candidate is excluded by other specifiers.
    +        """
    +        arbitrary = [s for s in self._specs if s.operator == "==="]
    +        if not arbitrary:
    +            return False
    +
    +        # Multiple === must agree on the same string (case-insensitive).
    +        first = arbitrary[0].version.lower()
    +        if any(s.version.lower() != first for s in arbitrary[1:]):
    +            return True
    +
    +        # The sole candidate is the === version string.  Check whether
    +        # it can satisfy every standard spec.
    +        candidate = _coerce_version(arbitrary[0].version)
    +
    +        # With prereleases=False, a prerelease candidate is excluded
    +        # by contains() before the === string check even runs.
    +        if (
    +            self.prereleases is False
    +            and candidate is not None
    +            and candidate.is_prerelease
    +        ):
    +            return True
    +
    +        standard = [s for s in self._specs if s.operator != "==="]
    +        if not standard:
    +            return False
    +
    +        if candidate is None:
    +            # Unparsable string cannot satisfy any standard spec.
    +            return True
    +
    +        return not all(s.contains(candidate) for s in standard)
    +
    +    def __contains__(self, item: UnparsedVersion) -> bool:
    +        """Return whether or not the item is contained in this specifier.
    +
    +        :param item: The item to check for.
    +
    +        This is used for the ``in`` operator and behaves the same as
    +        :meth:`contains` with no ``prereleases`` argument passed.
    +
    +        >>> "1.2.3" in SpecifierSet(">=1.0.0,!=1.0.1")
    +        True
    +        >>> Version("1.2.3") in SpecifierSet(">=1.0.0,!=1.0.1")
    +        True
    +        >>> "1.0.1" in SpecifierSet(">=1.0.0,!=1.0.1")
    +        False
    +        >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1")
    +        True
    +        >>> "1.3.0a1" in SpecifierSet(">=1.0.0,!=1.0.1", prereleases=True)
    +        True
    +        """
    +        return self.contains(item)
    +
    +    def contains(
    +        self,
    +        item: UnparsedVersion,
    +        prereleases: bool | None = None,
    +        installed: bool | None = None,
    +    ) -> bool:
    +        """Return whether or not the item is contained in this SpecifierSet.
    +
    +        :param item:
    +            The item to check for, which can be a version string or a
    +            :class:`Version` instance.
    +        :param prereleases:
    +            Whether or not to match prereleases with this SpecifierSet. If set to
    +            ``None`` (the default), it will follow the recommendation from :pep:`440`
    +            and match prereleases, as there are no other versions.
    +        :param installed:
    +            Whether or not the item is installed. If set to ``True``, it will
    +            accept prerelease versions even if the specifier does not allow them.
    +
    +        >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.2.3")
    +        True
    +        >>> SpecifierSet(">=1.0.0,!=1.0.1").contains(Version("1.2.3"))
    +        True
    +        >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.0.1")
    +        False
    +        >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1")
    +        True
    +        >>> SpecifierSet(">=1.0.0,!=1.0.1", prereleases=False).contains("1.3.0a1")
    +        False
    +        >>> SpecifierSet(">=1.0.0,!=1.0.1").contains("1.3.0a1", prereleases=True)
    +        True
    +        """
    +        version = _coerce_version(item)
    +
    +        if version is not None and installed and version.is_prerelease:
    +            prereleases = True
    +
    +        # When item is a string and === is involved, keep it as-is
    +        # so the comparison isn't done against the normalized form.
    +        if version is None or (self._has_arbitrary and not isinstance(item, Version)):
    +            check_item = item
    +        else:
    +            check_item = version
    +        return bool(list(self.filter([check_item], prereleases=prereleases)))
    +
    +    @typing.overload
    +    def filter(
    +        self,
    +        iterable: Iterable[UnparsedVersionVar],
    +        prereleases: bool | None = None,
    +        key: None = ...,
    +    ) -> Iterator[UnparsedVersionVar]: ...
    +
    +    @typing.overload
    +    def filter(
    +        self,
    +        iterable: Iterable[T],
    +        prereleases: bool | None = None,
    +        key: Callable[[T], UnparsedVersion] = ...,
    +    ) -> Iterator[T]: ...
    +
    +    def filter(
    +        self,
    +        iterable: Iterable[Any],
    +        prereleases: bool | None = None,
    +        key: Callable[[Any], UnparsedVersion] | None = None,
    +    ) -> Iterator[Any]:
    +        """Filter items in the given iterable, that match the specifiers in this set.
    +
    +        :param iterable:
    +            An iterable that can contain version strings and :class:`Version` instances.
    +            The items in the iterable will be filtered according to the specifier.
    +        :param prereleases:
    +            Whether or not to allow prereleases in the returned iterator. If set to
    +            ``None`` (the default), it will follow the recommendation from :pep:`440`
    +            and match prereleases if there are no other versions.
    +        :param key:
    +            A callable that takes a single argument (an item from the iterable) and
    +            returns a version string or :class:`Version` instance to be used for
    +            filtering.
    +
    +        >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", "1.5a1"]))
    +        ['1.3']
    +        >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.3", Version("1.4")]))
    +        ['1.3', ]
    +        >>> list(SpecifierSet(">=1.2.3").filter(["1.2", "1.5a1"]))
    +        ['1.5a1']
    +        >>> list(SpecifierSet(">=1.2.3").filter(["1.3", "1.5a1"], prereleases=True))
    +        ['1.3', '1.5a1']
    +        >>> list(SpecifierSet(">=1.2.3", prereleases=True).filter(["1.3", "1.5a1"]))
    +        ['1.3', '1.5a1']
    +        >>> list(SpecifierSet(">=1.2.3").filter(
    +        ... [{"ver": "1.2"}, {"ver": "1.3"}],
    +        ... key=lambda x: x["ver"]))
    +        [{'ver': '1.3'}]
    +
    +        An "empty" SpecifierSet will filter items based on the presence of prerelease
    +        versions in the set.
    +
    +        >>> list(SpecifierSet("").filter(["1.3", "1.5a1"]))
    +        ['1.3']
    +        >>> list(SpecifierSet("").filter(["1.5a1"]))
    +        ['1.5a1']
    +        >>> list(SpecifierSet("", prereleases=True).filter(["1.3", "1.5a1"]))
    +        ['1.3', '1.5a1']
    +        >>> list(SpecifierSet("").filter(["1.3", "1.5a1"], prereleases=True))
    +        ['1.3', '1.5a1']
    +        """
    +        # Determine if we're forcing a prerelease or not, if we're not forcing
    +        # one for this particular filter call, then we'll use whatever the
    +        # SpecifierSet thinks for whether or not we should support prereleases.
    +        if prereleases is None and self.prereleases is not None:
    +            prereleases = self.prereleases
    +
    +        # Filter versions that match all specifiers using Cost Based Ordering.
    +        if self._specs:
    +            # When prereleases is None, we need to let all versions through
    +            # the individual filters, then decide about prereleases at the end
    +            # based on whether any non-prereleases matched ALL specs.
    +
    +            # Fast path: single specifier, delegate directly.
    +            if len(self._specs) == 1:
    +                filtered = self._specs[0].filter(
    +                    iterable,
    +                    prereleases=True if prereleases is None else prereleases,
    +                    key=key,
    +                )
    +            else:
    +                filtered = self._filter_versions(
    +                    iterable,
    +                    key,
    +                    prereleases=True if prereleases is None else prereleases,
    +                )
    +
    +            if prereleases is not None:
    +                return filtered
    +
    +            return _pep440_filter_prereleases(filtered, key)
    +
    +        # Handle Empty SpecifierSet.
    +        if prereleases is True:
    +            return iter(iterable)
    +
    +        if prereleases is False:
    +            return (
    +                item
    +                for item in iterable
    +                if (
    +                    (version := _coerce_version(item if key is None else key(item)))
    +                    is None
    +                    or not version.is_prerelease
    +                )
    +            )
    +
    +        # PEP 440: exclude prereleases unless no final releases matched
    +        return _pep440_filter_prereleases(iterable, key)
    +
    +    def _filter_versions(
    +        self,
    +        iterable: Iterable[Any],
    +        key: Callable[[Any], UnparsedVersion] | None,
    +        prereleases: bool | None = None,
    +    ) -> Iterator[Any]:
    +        """Filter versions against all specifiers in a single pass.
    +
    +        Uses Cost Based Ordering: specifiers are sorted by _operator_cost so
    +        that cheap range operators reject versions early, avoiding expensive
    +        wildcard or compatible operators on versions that would have been
    +        rejected anyway.
    +        """
    +        # Pre-resolve operators and sort (cached after first call).
    +        if self._resolved_ops is None:
    +            self._resolved_ops = sorted(
    +                (
    +                    (spec._get_operator(spec.operator), spec.version, spec.operator)
    +                    for spec in self._specs
    +                ),
    +                key=_operator_cost,
    +            )
    +        ops = self._resolved_ops
    +        exclude_prereleases = prereleases is False
    +
    +        for item in iterable:
    +            parsed = _coerce_version(item if key is None else key(item))
    +
    +            if parsed is None:
    +                # Only === can match non-parseable versions.
    +                if all(
    +                    op == "===" and str(item).lower() == ver.lower()
    +                    for _, ver, op in ops
    +                ):
    +                    yield item
    +            elif exclude_prereleases and parsed.is_prerelease:
    +                pass
    +            elif all(
    +                str(item if key is None else key(item)).lower() == ver.lower()
    +                if op == "==="
    +                else op_fn(parsed, ver)
    +                for op_fn, ver, op in ops
    +            ):
    +                # Short-circuits on the first failing operator.
    +                yield item
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/tags.py b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/tags.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..9980ab3c63c0c4caf0045af55751a19a4d68ed92
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/tags.py
    @@ -0,0 +1,932 @@
    +# This file is dual licensed under the terms of the Apache License, Version
    +# 2.0, and the BSD License. See the LICENSE file in the root of this repository
    +# for complete details.
    +
    +from __future__ import annotations
    +
    +import logging
    +import operator
    +import platform
    +import re
    +import struct
    +import subprocess
    +import sys
    +import sysconfig
    +from importlib.machinery import EXTENSION_SUFFIXES
    +from typing import (
    +    TYPE_CHECKING,
    +    Iterable,
    +    Iterator,
    +    Sequence,
    +    Tuple,
    +    TypeVar,
    +    cast,
    +)
    +
    +from . import _manylinux, _musllinux
    +
    +if TYPE_CHECKING:
    +    from collections.abc import Callable, Iterable
    +    from typing import AbstractSet
    +
    +
    +__all__ = [
    +    "INTERPRETER_SHORT_NAMES",
    +    "AppleVersion",
    +    "PythonVersion",
    +    "Tag",
    +    "UnsortedTagsError",
    +    "android_platforms",
    +    "compatible_tags",
    +    "cpython_tags",
    +    "create_compatible_tags_selector",
    +    "generic_tags",
    +    "interpreter_name",
    +    "interpreter_version",
    +    "ios_platforms",
    +    "mac_platforms",
    +    "parse_tag",
    +    "platform_tags",
    +    "sys_tags",
    +]
    +
    +
    +def __dir__() -> list[str]:
    +    return __all__
    +
    +
    +logger = logging.getLogger(__name__)
    +
    +PythonVersion = Sequence[int]
    +AppleVersion = Tuple[int, int]
    +_T = TypeVar("_T")
    +
    +INTERPRETER_SHORT_NAMES: dict[str, str] = {
    +    "python": "py",  # Generic.
    +    "cpython": "cp",
    +    "pypy": "pp",
    +    "ironpython": "ip",
    +    "jython": "jy",
    +}
    +
    +
    +# This function can be unit tested without reloading the module
    +# (Unlike _32_BIT_INTERPRETER)
    +def _compute_32_bit_interpreter() -> bool:
    +    return struct.calcsize("P") == 4
    +
    +
    +_32_BIT_INTERPRETER = _compute_32_bit_interpreter()
    +
    +
    +class UnsortedTagsError(ValueError):
    +    """
    +    Raised when a tag component is not in sorted order per PEP 425.
    +    """
    +
    +
    +class Tag:
    +    """
    +    A representation of the tag triple for a wheel.
    +
    +    Instances are considered immutable and thus are hashable. Equality checking
    +    is also supported.
    +
    +    Instances are safe to serialize with :mod:`pickle`. They use a stable
    +    format so the same pickle can be loaded in future packaging releases.
    +
    +    .. versionchanged:: 26.2
    +
    +        Added a stable pickle format. Pickles created with packaging 26.2+ can
    +        be unpickled with future releases.  Backward compatibility with pickles
    +        from packaging < 26.2 is supported but may be removed in a future
    +        release.
    +    """
    +
    +    __slots__ = ["_abi", "_hash", "_interpreter", "_platform"]
    +
    +    def __init__(self, interpreter: str, abi: str, platform: str) -> None:
    +        """
    +        :param str interpreter: The interpreter name, e.g. ``"py"``
    +                                (see :attr:`INTERPRETER_SHORT_NAMES` for mapping
    +                                well-known interpreter names to their short names).
    +        :param str abi: The ABI that a wheel supports, e.g. ``"cp37m"``.
    +        :param str platform: The OS/platform the wheel supports,
    +                            e.g. ``"win_amd64"``.
    +        """
    +        self._interpreter = interpreter.lower()
    +        self._abi = abi.lower()
    +        self._platform = platform.lower()
    +        # The __hash__ of every single element in a Set[Tag] will be evaluated each time
    +        # that a set calls its `.disjoint()` method, which may be called hundreds of
    +        # times when scanning a page of links for packages with tags matching that
    +        # Set[Tag]. Pre-computing the value here produces significant speedups for
    +        # downstream consumers.
    +        self._hash = hash((self._interpreter, self._abi, self._platform))
    +
    +    @property
    +    def interpreter(self) -> str:
    +        """
    +        The interpreter name, e.g. ``"py"`` (see
    +        :attr:`INTERPRETER_SHORT_NAMES` for mapping well-known interpreter
    +        names to their short names).
    +        """
    +        return self._interpreter
    +
    +    @property
    +    def abi(self) -> str:
    +        """
    +        The supported ABI.
    +        """
    +        return self._abi
    +
    +    @property
    +    def platform(self) -> str:
    +        """
    +        The OS/platform.
    +        """
    +        return self._platform
    +
    +    def __eq__(self, other: object) -> bool:
    +        if not isinstance(other, Tag):
    +            return NotImplemented
    +
    +        return (
    +            (self._hash == other._hash)  # Short-circuit ASAP for perf reasons.
    +            and (self._platform == other._platform)
    +            and (self._abi == other._abi)
    +            and (self._interpreter == other._interpreter)
    +        )
    +
    +    def __hash__(self) -> int:
    +        return self._hash
    +
    +    def __str__(self) -> str:
    +        return f"{self._interpreter}-{self._abi}-{self._platform}"
    +
    +    def __repr__(self) -> str:
    +        return f"<{self} @ {id(self)}>"
    +
    +    def __getstate__(self) -> tuple[str, str, str]:
    +        # Return state as a 3-item tuple: (interpreter, abi, platform).
    +        # Cache member _hash is excluded and will be recomputed.
    +        return (self._interpreter, self._abi, self._platform)
    +
    +    def __setstate__(self, state: object) -> None:
    +        if isinstance(state, tuple):
    +            if len(state) == 3 and all(isinstance(s, str) for s in state):
    +                # New format (26.2+): (interpreter, abi, platform)
    +                self._interpreter, self._abi, self._platform = state
    +                self._hash = hash((self._interpreter, self._abi, self._platform))
    +                return
    +            if len(state) == 2 and isinstance(state[1], dict):
    +                # Old format (packaging <= 26.1, __slots__): (None, {slot: value}).
    +                _, slots = state
    +                try:
    +                    interpreter = slots["_interpreter"]
    +                    abi = slots["_abi"]
    +                    platform = slots["_platform"]
    +                except KeyError:
    +                    raise TypeError(f"Cannot restore Tag from {state!r}") from None
    +                if not all(
    +                    isinstance(value, str) for value in (interpreter, abi, platform)
    +                ):
    +                    raise TypeError(f"Cannot restore Tag from {state!r}")
    +                self._interpreter = interpreter.lower()
    +                self._abi = abi.lower()
    +                self._platform = platform.lower()
    +                self._hash = hash((self._interpreter, self._abi, self._platform))
    +                return
    +        raise TypeError(f"Cannot restore Tag from {state!r}")
    +
    +
    +def parse_tag(tag: str, *, validate_order: bool = False) -> frozenset[Tag]:
    +    """
    +    Parses the provided tag (e.g. `py3-none-any`) into a frozenset of
    +    :class:`Tag` instances.
    +
    +    Returning a set is required due to the possibility that the tag is a
    +    `compressed tag set`_, e.g. ``"py2.py3-none-any"`` which supports both
    +    Python 2 and Python 3.
    +
    +    If **validate_order** is true, compressed tag set components are checked
    +    to be in sorted order as required by PEP 425.
    +
    +    :param str tag: The tag to parse, e.g. ``"py3-none-any"``.
    +    :param bool validate_order: Check whether compressed tag set components
    +        are in sorted order.
    +    :raises UnsortedTagsError: If **validate_order** is true and any compressed tag
    +        set component is not in sorted order.
    +
    +    .. versionadded:: 26.1
    +       The *validate_order* parameter.
    +    """
    +    tags = set()
    +    interpreters, abis, platforms = tag.split("-")
    +    if validate_order:
    +        for component in (interpreters, abis, platforms):
    +            parts = component.split(".")
    +            if parts != sorted(parts):
    +                raise UnsortedTagsError(
    +                    f"Tag component {component!r} is not in sorted order per PEP 425"
    +                )
    +    for interpreter in interpreters.split("."):
    +        for abi in abis.split("."):
    +            for platform_ in platforms.split("."):
    +                tags.add(Tag(interpreter, abi, platform_))
    +    return frozenset(tags)
    +
    +
    +def _get_config_var(name: str, warn: bool = False) -> int | str | None:
    +    value: int | str | None = sysconfig.get_config_var(name)
    +    if value is None and warn:
    +        logger.debug(
    +            "Config variable '%s' is unset, Python ABI tag may be incorrect", name
    +        )
    +    return value
    +
    +
    +def _normalize_string(string: str) -> str:
    +    return string.replace(".", "_").replace("-", "_").replace(" ", "_")
    +
    +
    +def _is_threaded_cpython(abis: list[str]) -> bool:
    +    """
    +    Determine if the ABI corresponds to a threaded (`--disable-gil`) build.
    +
    +    The threaded builds are indicated by a "t" in the abiflags.
    +    """
    +    if len(abis) == 0:
    +        return False
    +    # expect e.g., cp313
    +    m = re.match(r"cp\d+(.*)", abis[0])
    +    if not m:
    +        return False
    +    abiflags = m.group(1)
    +    return "t" in abiflags
    +
    +
    +def _abi3_applies(python_version: PythonVersion, threading: bool) -> bool:
    +    """
    +    Determine if the Python version supports abi3.
    +
    +    PEP 384 was first implemented in Python 3.2. The free-threaded
    +    builds do not support abi3.
    +    """
    +    return len(python_version) > 1 and tuple(python_version) >= (3, 2) and not threading
    +
    +
    +def _abi3t_applies(python_version: PythonVersion, threading: bool) -> bool:
    +    """
    +    Determine if the Python version supports abi3t.
    +
    +    PEP 803 was first implemented in Python 3.15 but, per PEP 803, this
    +    returns tags going back to Python 3.2 to mirror the abi3
    +    implementation and leave open the possibility of abi3t wheels
    +    supporting older Python versions.
    +
    +    """
    +    return len(python_version) > 1 and tuple(python_version) >= (3, 2) and threading
    +
    +
    +def _cpython_abis(py_version: PythonVersion, warn: bool = False) -> list[str]:
    +    py_version = tuple(py_version)  # To allow for version comparison.
    +    abis = []
    +    version = _version_nodot(py_version[:2])
    +    threading = debug = pymalloc = ucs4 = ""
    +    with_debug = _get_config_var("Py_DEBUG", warn)
    +    has_refcount = hasattr(sys, "gettotalrefcount")
    +    # Windows doesn't set Py_DEBUG, so checking for support of debug-compiled
    +    # extension modules is the best option.
    +    # https://github.com/pypa/pip/issues/3383#issuecomment-173267692
    +    has_ext = "_d.pyd" in EXTENSION_SUFFIXES
    +    if with_debug or (with_debug is None and (has_refcount or has_ext)):
    +        debug = "d"
    +    if py_version >= (3, 13) and _get_config_var("Py_GIL_DISABLED", warn):
    +        threading = "t"
    +    if py_version < (3, 8):
    +        with_pymalloc = _get_config_var("WITH_PYMALLOC", warn)
    +        if with_pymalloc or with_pymalloc is None:
    +            pymalloc = "m"
    +        if py_version < (3, 3):
    +            unicode_size = _get_config_var("Py_UNICODE_SIZE", warn)
    +            if unicode_size == 4 or (
    +                unicode_size is None and sys.maxunicode == 0x10FFFF
    +            ):
    +                ucs4 = "u"
    +    elif debug:
    +        # Debug builds can also load "normal" extension modules.
    +        # We can also assume no UCS-4 or pymalloc requirement.
    +        abis.append(f"cp{version}{threading}")
    +    abis.insert(0, f"cp{version}{threading}{debug}{pymalloc}{ucs4}")
    +    return abis
    +
    +
    +def cpython_tags(
    +    python_version: PythonVersion | None = None,
    +    abis: Iterable[str] | None = None,
    +    platforms: Iterable[str] | None = None,
    +    *,
    +    warn: bool = False,
    +) -> Iterator[Tag]:
    +    """
    +    Yields the tags for the CPython interpreter.
    +
    +    The specific tags generated are:
    +
    +    - ``cp--``
    +    - ``cp--``
    +    - ``cp-none-``
    +    - ``cp--`` where "older version" is all older
    +      minor versions down to Python 3.2 (when ``abi3`` was introduced)
    +
    +    If ``python_version`` only provides a major-only version then only
    +    user-provided ABIs via ``abis`` and the ``none`` ABI will be used.
    +
    +    The ``stable_abi`` will be either ``abi3`` or ``abi3t`` if `abi` is a
    +    GIL-enabled ABI like `"cp315"` or a free-threaded ABI like `"cp315t"`,
    +    respectively.
    +
    +    :param Sequence python_version: A one- or two-item sequence representing the
    +                                 targeted Python version. Defaults to
    +                                 ``sys.version_info[:2]``.
    +    :param Iterable abis: Iterable of compatible ABIs. Defaults to the ABIs
    +                          compatible with the current system.
    +    :param Iterable platforms: Iterable of compatible platforms. Defaults to the
    +                               platforms compatible with the current system.
    +    :param bool warn: Whether warnings should be logged. Defaults to ``False``.
    +    """
    +    if not python_version:
    +        python_version = sys.version_info[:2]
    +
    +    interpreter = f"cp{_version_nodot(python_version[:2])}"
    +
    +    if abis is None:
    +        abis = _cpython_abis(python_version, warn) if len(python_version) > 1 else []
    +    abis = list(abis)
    +    # 'abi3' and 'none' are explicitly handled later.
    +    for explicit_abi in ("abi3", "none"):
    +        try:
    +            abis.remove(explicit_abi)
    +        except ValueError:  # noqa: PERF203
    +            pass
    +
    +    platforms = list(platforms or platform_tags())
    +    for abi in abis:
    +        for platform_ in platforms:
    +            yield Tag(interpreter, abi, platform_)
    +
    +    threading = _is_threaded_cpython(abis)
    +    use_abi3 = _abi3_applies(python_version, threading)
    +    use_abi3t = _abi3t_applies(python_version, threading)
    +
    +    if use_abi3:
    +        yield from (Tag(interpreter, "abi3", platform_) for platform_ in platforms)
    +    if use_abi3t:
    +        yield from (Tag(interpreter, "abi3t", platform_) for platform_ in platforms)
    +
    +    yield from (Tag(interpreter, "none", platform_) for platform_ in platforms)
    +
    +    if use_abi3 or use_abi3t:
    +        for minor_version in range(python_version[1] - 1, 1, -1):
    +            for platform_ in platforms:
    +                version = _version_nodot((python_version[0], minor_version))
    +                interpreter = f"cp{version}"
    +                if use_abi3:
    +                    yield Tag(interpreter, "abi3", platform_)
    +                if use_abi3t:
    +                    # Support for abi3t was introduced in Python 3.15, but in
    +                    # principle abi3t wheels are possible for older limited API
    +                    # versions, so allow things like ("cp37", "abi3t", "platform")
    +                    yield Tag(interpreter, "abi3t", platform_)
    +
    +
    +def _generic_abi() -> list[str]:
    +    """
    +    Return the ABI tag based on EXT_SUFFIX.
    +    """
    +    # The following are examples of `EXT_SUFFIX`.
    +    # We want to keep the parts which are related to the ABI and remove the
    +    # parts which are related to the platform:
    +    # - linux:   '.cpython-310-x86_64-linux-gnu.so' => cp310
    +    # - mac:     '.cpython-310-darwin.so'           => cp310
    +    # - win:     '.cp310-win_amd64.pyd'             => cp310
    +    # - win:     '.pyd'                             => cp37 (uses _cpython_abis())
    +    # - pypy:    '.pypy38-pp73-x86_64-linux-gnu.so' => pypy38_pp73
    +    # - graalpy: '.graalpy-38-native-x86_64-darwin.dylib'
    +    #                                               => graalpy_38_native
    +
    +    ext_suffix = _get_config_var("EXT_SUFFIX", warn=True)
    +    if not isinstance(ext_suffix, str) or ext_suffix[0] != ".":
    +        raise SystemError("invalid sysconfig.get_config_var('EXT_SUFFIX')")
    +    parts = ext_suffix.split(".")
    +    if len(parts) < 3:
    +        # CPython3.7 and earlier uses ".pyd" on Windows.
    +        return _cpython_abis(sys.version_info[:2])
    +    soabi = parts[1]
    +    if soabi.startswith("cpython"):
    +        # non-windows
    +        abi = "cp" + soabi.split("-")[1]
    +    elif soabi.startswith("cp"):
    +        # windows
    +        abi = soabi.split("-")[0]
    +    elif soabi.startswith("pypy"):
    +        abi = "-".join(soabi.split("-")[:2])
    +    elif soabi.startswith("graalpy"):
    +        abi = "-".join(soabi.split("-")[:3])
    +    elif soabi:
    +        # pyston, ironpython, others?
    +        abi = soabi
    +    else:
    +        return []
    +    return [_normalize_string(abi)]
    +
    +
    +def generic_tags(
    +    interpreter: str | None = None,
    +    abis: Iterable[str] | None = None,
    +    platforms: Iterable[str] | None = None,
    +    *,
    +    warn: bool = False,
    +) -> Iterator[Tag]:
    +    """
    +    Yields the tags for an interpreter which requires no specialization.
    +
    +    This function should be used if one of the other interpreter-specific
    +    functions provided by this module is not appropriate (i.e. not calculating
    +    tags for a CPython interpreter).
    +
    +    The specific tags generated are:
    +
    +    - ``--``
    +
    +    The ``"none"`` ABI will be added if it was not explicitly provided.
    +
    +    :param str interpreter: The name of the interpreter. Defaults to being
    +                            calculated.
    +    :param Iterable abis: Iterable of compatible ABIs. Defaults to the ABIs
    +                          compatible with the current system.
    +    :param Iterable platforms: Iterable of compatible platforms. Defaults to the
    +                               platforms compatible with the current system.
    +    :param bool warn: Whether warnings should be logged. Defaults to ``False``.
    +    """
    +    if not interpreter:
    +        interp_name = interpreter_name()
    +        interp_version = interpreter_version(warn=warn)
    +        interpreter = f"{interp_name}{interp_version}"
    +    abis = _generic_abi() if abis is None else list(abis)
    +    platforms = list(platforms or platform_tags())
    +    if "none" not in abis:
    +        abis.append("none")
    +    for abi in abis:
    +        for platform_ in platforms:
    +            yield Tag(interpreter, abi, platform_)
    +
    +
    +def _py_interpreter_range(py_version: PythonVersion) -> Iterator[str]:
    +    """
    +    Yields Python versions in descending order.
    +
    +    After the latest version, the major-only version will be yielded, and then
    +    all previous versions of that major version.
    +    """
    +    if len(py_version) > 1:
    +        yield f"py{_version_nodot(py_version[:2])}"
    +    yield f"py{py_version[0]}"
    +    if len(py_version) > 1:
    +        for minor in range(py_version[1] - 1, -1, -1):
    +            yield f"py{_version_nodot((py_version[0], minor))}"
    +
    +
    +def compatible_tags(
    +    python_version: PythonVersion | None = None,
    +    interpreter: str | None = None,
    +    platforms: Iterable[str] | None = None,
    +) -> Iterator[Tag]:
    +    """
    +    Yields the tags for an interpreter compatible with the Python version
    +    specified by ``python_version``.
    +
    +    The specific tags generated are:
    +
    +    - ``py*-none-``
    +    - ``-none-any`` if ``interpreter`` is provided
    +    - ``py*-none-any``
    +
    +    :param Sequence python_version: A one- or two-item sequence representing the
    +                                 compatible version of Python. Defaults to
    +                                 ``sys.version_info[:2]``.
    +    :param str interpreter: The name of the interpreter (if known), e.g.
    +                            ``"cp38"``. Defaults to the current interpreter.
    +    :param Iterable platforms: Iterable of compatible platforms. Defaults to the
    +                               platforms compatible with the current system.
    +    """
    +    if not python_version:
    +        python_version = sys.version_info[:2]
    +    platforms = list(platforms or platform_tags())
    +    for version in _py_interpreter_range(python_version):
    +        for platform_ in platforms:
    +            yield Tag(version, "none", platform_)
    +    if interpreter:
    +        yield Tag(interpreter, "none", "any")
    +    for version in _py_interpreter_range(python_version):
    +        yield Tag(version, "none", "any")
    +
    +
    +def _mac_arch(arch: str, is_32bit: bool = _32_BIT_INTERPRETER) -> str:
    +    if not is_32bit:
    +        return arch
    +
    +    if arch.startswith("ppc"):
    +        return "ppc"
    +
    +    return "i386"
    +
    +
    +def _mac_binary_formats(version: AppleVersion, cpu_arch: str) -> list[str]:
    +    formats = [cpu_arch]
    +    if cpu_arch == "x86_64":
    +        if version < (10, 4):
    +            return []
    +        formats.extend(["intel", "fat64", "fat32"])
    +
    +    elif cpu_arch == "i386":
    +        if version < (10, 4):
    +            return []
    +        formats.extend(["intel", "fat32", "fat"])
    +
    +    elif cpu_arch == "ppc64":
    +        # TODO: Need to care about 32-bit PPC for ppc64 through 10.2?
    +        if version > (10, 5) or version < (10, 4):
    +            return []
    +        formats.append("fat64")
    +
    +    elif cpu_arch == "ppc":
    +        if version > (10, 6):
    +            return []
    +        formats.extend(["fat32", "fat"])
    +
    +    if cpu_arch in {"arm64", "x86_64"}:
    +        formats.append("universal2")
    +
    +    if cpu_arch in {"x86_64", "i386", "ppc64", "ppc", "intel"}:
    +        formats.append("universal")
    +
    +    return formats
    +
    +
    +def mac_platforms(
    +    version: AppleVersion | None = None, arch: str | None = None
    +) -> Iterator[str]:
    +    """
    +    Yields the :attr:`~Tag.platform` tags for macOS.
    +
    +    The `version` parameter is a two-item tuple specifying the macOS version to
    +    generate platform tags for. The `arch` parameter is the CPU architecture to
    +    generate platform tags for. Both parameters default to the appropriate value
    +    for the current system.
    +
    +    :param tuple version: A two-item tuple representing the version of macOS.
    +                          Defaults to the current system's version.
    +    :param str arch: The CPU architecture. Defaults to the architecture of the
    +                     current system, e.g. ``"x86_64"``.
    +
    +    .. note::
    +        Equivalent support for the other major platforms is purposefully not
    +        provided:
    +
    +        - On Windows, platform compatibility is statically specified
    +        - On Linux, code must be run on the system itself to determine
    +          compatibility
    +    """
    +    version_str, _, cpu_arch = platform.mac_ver()
    +    if version is None:
    +        version = cast("AppleVersion", tuple(map(int, version_str.split(".")[:2])))
    +        if version == (10, 16):
    +            # When built against an older macOS SDK, Python will report macOS 10.16
    +            # instead of the real version.
    +            version_str = subprocess.run(
    +                [
    +                    sys.executable,
    +                    "-sS",
    +                    "-c",
    +                    "import platform; print(platform.mac_ver()[0])",
    +                ],
    +                check=True,
    +                env={"SYSTEM_VERSION_COMPAT": "0"},
    +                stdout=subprocess.PIPE,
    +                text=True,
    +            ).stdout
    +            version = cast("AppleVersion", tuple(map(int, version_str.split(".")[:2])))
    +
    +    if arch is None:
    +        arch = _mac_arch(cpu_arch)
    +
    +    if (10, 0) <= version < (11, 0):
    +        # Prior to Mac OS 11, each yearly release of Mac OS bumped the
    +        # "minor" version number.  The major version was always 10.
    +        major_version = 10
    +        for minor_version in range(version[1], -1, -1):
    +            compat_version = major_version, minor_version
    +            binary_formats = _mac_binary_formats(compat_version, arch)
    +            for binary_format in binary_formats:
    +                yield f"macosx_{major_version}_{minor_version}_{binary_format}"
    +
    +    if version >= (11, 0):
    +        # Starting with Mac OS 11, each yearly release bumps the major version
    +        # number.   The minor versions are now the midyear updates.
    +        minor_version = 0
    +        for major_version in range(version[0], 10, -1):
    +            compat_version = major_version, minor_version
    +            binary_formats = _mac_binary_formats(compat_version, arch)
    +            for binary_format in binary_formats:
    +                yield f"macosx_{major_version}_{minor_version}_{binary_format}"
    +
    +    if version >= (11, 0):
    +        # Mac OS 11 on x86_64 is compatible with binaries from previous releases.
    +        # Arm64 support was introduced in 11.0, so no Arm binaries from previous
    +        # releases exist.
    +        #
    +        # However, the "universal2" binary format can have a
    +        # macOS version earlier than 11.0 when the x86_64 part of the binary supports
    +        # that version of macOS.
    +        major_version = 10
    +        if arch == "x86_64":
    +            for minor_version in range(16, 3, -1):
    +                compat_version = major_version, minor_version
    +                binary_formats = _mac_binary_formats(compat_version, arch)
    +                for binary_format in binary_formats:
    +                    yield f"macosx_{major_version}_{minor_version}_{binary_format}"
    +        else:
    +            for minor_version in range(16, 3, -1):
    +                compat_version = major_version, minor_version
    +                binary_format = "universal2"
    +                yield f"macosx_{major_version}_{minor_version}_{binary_format}"
    +
    +
    +def ios_platforms(
    +    version: AppleVersion | None = None, multiarch: str | None = None
    +) -> Iterator[str]:
    +    """
    +
    +    Yields the :attr:`~Tag.platform` tags for iOS.
    +
    +    :param tuple version: A two-item tuple representing the version of iOS.
    +                          Defaults to the current system's version.
    +    :param str multiarch: The CPU architecture+ABI to be used. This should be in
    +                          the format by ``sys.implementation._multiarch`` (e.g.,
    +                          ``arm64_iphoneos`` or ``x86_64_iphonesimulator``).
    +                          Defaults to the current system's multiarch value.
    +
    +    .. note::
    +        Behavior of this method is undefined if invoked on non-iOS platforms
    +        without providing explicit version and multiarch arguments.
    +    """
    +    if version is None:
    +        # if iOS is the current platform, ios_ver *must* be defined. However,
    +        # it won't exist for CPython versions before 3.13, which causes a mypy
    +        # error.
    +        _, release, _, _ = platform.ios_ver()  # type: ignore[attr-defined, unused-ignore]
    +        version = cast("AppleVersion", tuple(map(int, release.split(".")[:2])))
    +
    +    if multiarch is None:
    +        multiarch = sys.implementation._multiarch
    +    multiarch = multiarch.replace("-", "_")
    +
    +    ios_platform_template = "ios_{major}_{minor}_{multiarch}"
    +
    +    # Consider any iOS major.minor version from the version requested, down to
    +    # 12.0. 12.0 is the first iOS version that is known to have enough features
    +    # to support CPython. Consider every possible minor release up to X.9. There
    +    # highest the minor has ever gone is 8 (14.8 and 15.8) but having some extra
    +    # candidates that won't ever match doesn't really hurt, and it saves us from
    +    # having to keep an explicit list of known iOS versions in the code. Return
    +    # the results descending order of version number.
    +
    +    # If the requested major version is less than 12, there won't be any matches.
    +    if version[0] < 12:
    +        return
    +
    +    # Consider the actual X.Y version that was requested.
    +    yield ios_platform_template.format(
    +        major=version[0], minor=version[1], multiarch=multiarch
    +    )
    +
    +    # Consider every minor version from X.0 to the minor version prior to the
    +    # version requested by the platform.
    +    for minor in range(version[1] - 1, -1, -1):
    +        yield ios_platform_template.format(
    +            major=version[0], minor=minor, multiarch=multiarch
    +        )
    +
    +    for major in range(version[0] - 1, 11, -1):
    +        for minor in range(9, -1, -1):
    +            yield ios_platform_template.format(
    +                major=major, minor=minor, multiarch=multiarch
    +            )
    +
    +
    +def android_platforms(
    +    api_level: int | None = None, abi: str | None = None
    +) -> Iterator[str]:
    +    """
    +    Yields the :attr:`~Tag.platform` tags for Android. If this function is invoked on
    +    non-Android platforms, the ``api_level`` and ``abi`` arguments are required.
    +
    +    :param int api_level: The maximum `API level
    +        `__ to return. Defaults
    +        to the current system's version, as returned by ``platform.android_ver``.
    +    :param str abi: The `Android ABI `__,
    +        e.g. ``arm64_v8a``. Defaults to the current system's ABI , as returned by
    +        ``sysconfig.get_platform``. Hyphens and periods will be replaced with
    +        underscores.
    +    """
    +    if platform.system() != "Android" and (api_level is None or abi is None):
    +        raise TypeError(
    +            "on non-Android platforms, the api_level and abi arguments are required"
    +        )
    +
    +    if api_level is None:
    +        # Python 3.13 was the first version to return platform.system() == "Android",
    +        # and also the first version to define platform.android_ver().
    +        api_level = platform.android_ver().api_level  # type: ignore[attr-defined]
    +
    +    if abi is None:
    +        abi = sysconfig.get_platform().split("-")[-1]
    +    abi = _normalize_string(abi)
    +
    +    # 16 is the minimum API level known to have enough features to support CPython
    +    # without major patching. Yield every API level from the maximum down to the
    +    # minimum, inclusive.
    +    min_api_level = 16
    +    for ver in range(api_level, min_api_level - 1, -1):
    +        yield f"android_{ver}_{abi}"
    +
    +
    +def _linux_platforms(is_32bit: bool = _32_BIT_INTERPRETER) -> Iterator[str]:
    +    linux = _normalize_string(sysconfig.get_platform())
    +    if not linux.startswith("linux_"):
    +        # we should never be here, just yield the sysconfig one and return
    +        yield linux
    +        return
    +    if is_32bit:
    +        if linux == "linux_x86_64":
    +            linux = "linux_i686"
    +        elif linux == "linux_aarch64":
    +            linux = "linux_armv8l"
    +    _, arch = linux.split("_", 1)
    +    archs = {"armv8l": ["armv8l", "armv7l"]}.get(arch, [arch])
    +    yield from _manylinux.platform_tags(archs)
    +    yield from _musllinux.platform_tags(archs)
    +    for arch in archs:
    +        yield f"linux_{arch}"
    +
    +
    +def _emscripten_platforms() -> Iterator[str]:
    +    pyemscripten_platform_version = sysconfig.get_config_var(
    +        "PYEMSCRIPTEN_PLATFORM_VERSION"
    +    )
    +    if pyemscripten_platform_version:
    +        yield f"pyemscripten_{pyemscripten_platform_version}_wasm32"
    +    yield from _generic_platforms()
    +
    +
    +def _generic_platforms() -> Iterator[str]:
    +    yield _normalize_string(sysconfig.get_platform())
    +
    +
    +def platform_tags() -> Iterator[str]:
    +    """
    +    Yields the :attr:`~Tag.platform` tags for the running interpreter.
    +    """
    +    if platform.system() == "Darwin":
    +        return mac_platforms()
    +    elif platform.system() == "iOS":
    +        return ios_platforms()
    +    elif platform.system() == "Android":
    +        return android_platforms()
    +    elif platform.system() == "Linux":
    +        return _linux_platforms()
    +    elif platform.system() == "Emscripten":
    +        return _emscripten_platforms()
    +    else:
    +        return _generic_platforms()
    +
    +
    +def interpreter_name() -> str:
    +    """
    +    Returns the name of the running interpreter.
    +
    +    Some implementations have a reserved, two-letter abbreviation which will
    +    be returned when appropriate.
    +
    +    This typically acts as the prefix to the :attr:`~Tag.interpreter` tag.
    +    """
    +    name = sys.implementation.name
    +    return INTERPRETER_SHORT_NAMES.get(name) or name
    +
    +
    +def interpreter_version(*, warn: bool = False) -> str:
    +    """
    +    Returns the running interpreter's version.
    +
    +    This typically acts as the suffix to the :attr:`~Tag.interpreter` tag.
    +
    +    :param bool warn: Whether warnings should be logged. Defaults to ``False``.
    +    """
    +    version = _get_config_var("py_version_nodot", warn=warn)
    +    return str(version) if version else _version_nodot(sys.version_info[:2])
    +
    +
    +def _version_nodot(version: PythonVersion) -> str:
    +    return "".join(map(str, version))
    +
    +
    +def sys_tags(*, warn: bool = False) -> Iterator[Tag]:
    +    """
    +    Yields the sequence of tag triples that the running interpreter supports.
    +
    +    The iterable is ordered so that the best-matching tag is first in the
    +    sequence. The exact preferential order to tags is interpreter-specific, but
    +    in general the tag importance is in the order of:
    +
    +    1. Interpreter
    +    2. Platform
    +    3. ABI
    +
    +    This order is due to the fact that an ABI is inherently tied to the
    +    platform, but platform-specific code is not necessarily tied to the ABI. The
    +    interpreter is the most important tag as it dictates basic support for any
    +    wheel.
    +
    +    The function returns an iterable in order to allow for the possible
    +    short-circuiting of tag generation if the entire sequence is not necessary
    +    and tag calculation happens to be expensive.
    +
    +    :param bool warn: Whether warnings should be logged. Defaults to ``False``.
    +
    +    .. versionchanged:: 21.3
    +        Added the `pp3-none-any` tag (:issue:`311`).
    +    .. versionchanged:: 27.0
    +        Added the `abi3t` tag (:issue:`1099`).
    +    """
    +
    +    interp_name = interpreter_name()
    +    if interp_name == "cp":
    +        yield from cpython_tags(warn=warn)
    +    else:
    +        yield from generic_tags()
    +
    +    if interp_name == "pp":
    +        interp = "pp3"
    +    elif interp_name == "cp":
    +        interp = "cp" + interpreter_version(warn=warn)
    +    else:
    +        interp = None
    +    yield from compatible_tags(interpreter=interp)
    +
    +
    +def create_compatible_tags_selector(
    +    tags: Iterable[Tag],
    +) -> Callable[[Iterable[tuple[_T, AbstractSet[Tag]]]], Iterator[_T]]:
    +    """Create a callable to select things compatible with supported tags.
    +
    +    This function accepts an ordered sequence of tags, with the preferred
    +    tags first.
    +
    +    The returned callable accepts an iterable of tuples (thing, set[Tag]),
    +    and returns an iterator of things, with the things with the best
    +    matching tags first.
    +
    +    Example to select compatible wheel filenames:
    +
    +    >>> from packaging import tags
    +    >>> from packaging.utils import parse_wheel_filename
    +    >>> selector = tags.create_compatible_tags_selector(tags.sys_tags())
    +    >>> filenames = ["foo-1.0-py3-none-any.whl", "foo-1.0-py2-none-any.whl"]
    +    >>> list(selector([
    +    ...     (filename, parse_wheel_filename(filename)[-1]) for filename in filenames
    +    ... ]))
    +    ['foo-1.0-py3-none-any.whl']
    +
    +    .. versionadded:: 26.1
    +    """
    +    tag_ranks: dict[Tag, int] = {}
    +    for rank, tag in enumerate(tags):
    +        tag_ranks.setdefault(tag, rank)  # ignore duplicate tags, keep first
    +    supported_tags = tag_ranks.keys()
    +
    +    def selector(
    +        tagged_things: Iterable[tuple[_T, AbstractSet[Tag]]],
    +    ) -> Iterator[_T]:
    +        ranked_things: list[tuple[_T, int]] = []
    +        for thing, thing_tags in tagged_things:
    +            supported_thing_tags = thing_tags & supported_tags
    +            if supported_thing_tags:
    +                thing_rank = min(tag_ranks[t] for t in supported_thing_tags)
    +                ranked_things.append((thing, thing_rank))
    +        return iter(
    +            thing for thing, _ in sorted(ranked_things, key=operator.itemgetter(1))
    +        )
    +
    +    return selector
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/utils.py b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/utils.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..cbd3be27c4211f73330a318debf6a490b8b9d8ba
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/utils.py
    @@ -0,0 +1,296 @@
    +# This file is dual licensed under the terms of the Apache License, Version
    +# 2.0, and the BSD License. See the LICENSE file in the root of this repository
    +# for complete details.
    +
    +from __future__ import annotations
    +
    +import re
    +from typing import NewType, Tuple, Union, cast
    +
    +from .tags import Tag, UnsortedTagsError, parse_tag
    +from .version import InvalidVersion, Version, _TrimmedRelease
    +
    +__all__ = [
    +    "BuildTag",
    +    "InvalidName",
    +    "InvalidSdistFilename",
    +    "InvalidWheelFilename",
    +    "NormalizedName",
    +    "canonicalize_name",
    +    "canonicalize_version",
    +    "is_normalized_name",
    +    "parse_sdist_filename",
    +    "parse_wheel_filename",
    +]
    +
    +
    +def __dir__() -> list[str]:
    +    return __all__
    +
    +
    +BuildTag = Union[Tuple[()], Tuple[int, str]]
    +
    +NormalizedName = NewType("NormalizedName", str)
    +"""
    +A :class:`typing.NewType` of :class:`str`, representing a normalized name.
    +"""
    +
    +
    +class InvalidName(ValueError):
    +    """
    +    An invalid distribution name; users should refer to the packaging user guide.
    +    """
    +
    +
    +class InvalidWheelFilename(ValueError):
    +    """
    +    An invalid wheel filename was found, users should refer to PEP 427.
    +    """
    +
    +
    +class InvalidSdistFilename(ValueError):
    +    """
    +    An invalid sdist filename was found, users should refer to the packaging user guide.
    +    """
    +
    +
    +# Core metadata spec for `Name`
    +_validate_regex = re.compile(
    +    r"[a-z0-9]|[a-z0-9][a-z0-9._-]*[a-z0-9]", re.IGNORECASE | re.ASCII
    +)
    +_normalized_regex = re.compile(r"[a-z0-9]|[a-z0-9]([a-z0-9-](?!--))*[a-z0-9]", re.ASCII)
    +# PEP 427: The build number must start with a digit.
    +_build_tag_regex = re.compile(r"(\d+)(.*)", re.ASCII)
    +
    +
    +def canonicalize_name(name: str, *, validate: bool = False) -> NormalizedName:
    +    """
    +    This function takes a valid Python package or extra name, and returns the
    +    normalized form of it.
    +
    +    The return type is typed as :class:`NormalizedName`. This allows type
    +    checkers to help require that a string has passed through this function
    +    before use.
    +
    +    If **validate** is true, then the function will check if **name** is a valid
    +    distribution name before normalizing.
    +
    +    :param str name: The name to normalize.
    +    :param bool validate: Check whether the name is a valid distribution name.
    +    :raises InvalidName: If **validate** is true and the name is not an
    +        acceptable distribution name.
    +
    +    >>> from packaging.utils import canonicalize_name
    +    >>> canonicalize_name("Django")
    +    'django'
    +    >>> canonicalize_name("oslo.concurrency")
    +    'oslo-concurrency'
    +    >>> canonicalize_name("requests")
    +    'requests'
    +    """
    +    if validate and not _validate_regex.fullmatch(name):
    +        raise InvalidName(f"name is invalid: {name!r}")
    +    # Ensure all ``.`` and ``_`` are ``-``
    +    # Emulates ``re.sub(r"[-_.]+", "-", name).lower()`` from PEP 503
    +    # Much faster than re, and even faster than str.translate
    +    value = name.lower().replace("_", "-").replace(".", "-")
    +    # Condense repeats (faster than regex)
    +    while "--" in value:
    +        value = value.replace("--", "-")
    +    return cast("NormalizedName", value)
    +
    +
    +def is_normalized_name(name: str) -> bool:
    +    """
    +    Check if a name is already normalized (i.e. :func:`canonicalize_name` would
    +    roundtrip to the same value).
    +
    +    :param str name: The name to check.
    +
    +    >>> from packaging.utils import is_normalized_name
    +    >>> is_normalized_name("requests")
    +    True
    +    >>> is_normalized_name("Django")
    +    False
    +    """
    +    return _normalized_regex.fullmatch(name) is not None
    +
    +
    +def canonicalize_version(
    +    version: Version | str, *, strip_trailing_zero: bool = True
    +) -> str:
    +    """Return a canonical form of a version as a string.
    +
    +    This function takes a string representing a package version (or a
    +    :class:`~packaging.version.Version` instance), and returns the
    +    normalized form of it. By default, it strips trailing zeros from
    +    the release segment.
    +
    +    >>> from packaging.utils import canonicalize_version
    +    >>> canonicalize_version('1.0.1')
    +    '1.0.1'
    +
    +    Per PEP 625, versions may have multiple canonical forms, differing
    +    only by trailing zeros.
    +
    +    >>> canonicalize_version('1.0.0')
    +    '1'
    +    >>> canonicalize_version('1.0.0', strip_trailing_zero=False)
    +    '1.0.0'
    +
    +    Invalid versions are returned unaltered.
    +
    +    >>> canonicalize_version('foo bar baz')
    +    'foo bar baz'
    +
    +    >>> canonicalize_version('1.4.0.0.0')
    +    '1.4'
    +    """
    +    if isinstance(version, str):
    +        try:
    +            version = Version(version)
    +        except InvalidVersion:
    +            return str(version)
    +    return str(_TrimmedRelease(version) if strip_trailing_zero else version)
    +
    +
    +def parse_wheel_filename(
    +    filename: str,
    +    *,
    +    validate_order: bool = False,
    +) -> tuple[NormalizedName, Version, BuildTag, frozenset[Tag]]:
    +    """
    +    This function takes the filename of a wheel file, and parses it,
    +    returning a tuple of name, version, build number, and tags.
    +
    +    The name part of the tuple is normalized and typed as
    +    :class:`NormalizedName`. The version portion is an instance of
    +    :class:`~packaging.version.Version`. The build number is ``()`` if
    +    there is no build number in the wheel filename, otherwise a
    +    two-item tuple of an integer for the leading digits and
    +    a string for the rest of the build number. The tags portion is a
    +    frozen set of :class:`~packaging.tags.Tag` instances (as the tag
    +    string format allows multiple tags to be combined into a single
    +    string).
    +
    +    If **validate_order** is true, compressed tag set components are
    +    checked to be in sorted order as required by PEP 425.
    +
    +    :param str filename: The name of the wheel file.
    +    :param bool validate_order: Check whether compressed tag set components
    +        are in sorted order.
    +    :raises InvalidWheelFilename: If the filename in question
    +        does not follow the :ref:`wheel specification
    +        `.
    +
    +    >>> from packaging.utils import parse_wheel_filename
    +    >>> from packaging.tags import Tag
    +    >>> from packaging.version import Version
    +    >>> name, ver, build, tags = parse_wheel_filename("foo-1.0-py3-none-any.whl")
    +    >>> name
    +    'foo'
    +    >>> ver == Version('1.0')
    +    True
    +    >>> tags == {Tag("py3", "none", "any")}
    +    True
    +    >>> not build
    +    True
    +
    +    .. versionadded:: 26.1
    +       The *validate_order* parameter.
    +    """
    +    if not filename.endswith(".whl"):
    +        raise InvalidWheelFilename(
    +            f"Invalid wheel filename (extension must be '.whl'): {filename!r}"
    +        )
    +
    +    filename = filename[:-4]
    +    dashes = filename.count("-")
    +    if dashes not in (4, 5):
    +        raise InvalidWheelFilename(
    +            f"Invalid wheel filename (wrong number of parts): {filename!r}"
    +        )
    +
    +    parts = filename.split("-", dashes - 2)
    +    name_part = parts[0]
    +    # See PEP 427 for the rules on escaping the project name.
    +    if "__" in name_part or re.match(r"^[\w\d._]*$", name_part, re.UNICODE) is None:
    +        raise InvalidWheelFilename(f"Invalid project name: {filename!r}")
    +    name = canonicalize_name(name_part)
    +
    +    try:
    +        version = Version(parts[1])
    +    except InvalidVersion as e:
    +        raise InvalidWheelFilename(
    +            f"Invalid wheel filename (invalid version): {filename!r}"
    +        ) from e
    +
    +    if dashes == 5:
    +        build_part = parts[2]
    +        build_match = _build_tag_regex.match(build_part)
    +        if build_match is None:
    +            raise InvalidWheelFilename(
    +                f"Invalid build number: {build_part} in {filename!r}"
    +            )
    +        build = cast("BuildTag", (int(build_match.group(1)), build_match.group(2)))
    +    else:
    +        build = ()
    +    tag_str = parts[-1]
    +    try:
    +        tags = parse_tag(tag_str, validate_order=validate_order)
    +    except UnsortedTagsError:
    +        raise InvalidWheelFilename(
    +            f"Invalid wheel filename (compressed tag set components must be in "
    +            f"sorted order per PEP 425): {filename!r}"
    +        ) from None
    +    return (name, version, build, tags)
    +
    +
    +def parse_sdist_filename(filename: str) -> tuple[NormalizedName, Version]:
    +    """
    +    This function takes the filename of a sdist file (as specified
    +    in the `Source distribution format`_ documentation), and parses
    +    it, returning a tuple of the normalized name and version as
    +    represented by an instance of :class:`~packaging.version.Version`.
    +
    +    :param str filename: The name of the sdist file.
    +    :raises InvalidSdistFilename: If the filename does not end
    +        with an sdist extension (``.zip`` or ``.tar.gz``), or if it does not
    +        contain a dash separating the name and the version of the distribution.
    +
    +    >>> from packaging.utils import parse_sdist_filename
    +    >>> from packaging.version import Version
    +    >>> name, ver = parse_sdist_filename("foo-1.0.tar.gz")
    +    >>> name
    +    'foo'
    +    >>> ver == Version('1.0')
    +    True
    +
    +    .. _Source distribution format: https://packaging.python.org/specifications/source-distribution-format/#source-distribution-file-name
    +    """
    +    if filename.endswith(".tar.gz"):
    +        file_stem = filename[: -len(".tar.gz")]
    +    elif filename.endswith(".zip"):
    +        file_stem = filename[: -len(".zip")]
    +    else:
    +        raise InvalidSdistFilename(
    +            f"Invalid sdist filename (extension must be '.tar.gz' or '.zip'):"
    +            f" {filename!r}"
    +        )
    +
    +    # We are requiring a PEP 440 version, which cannot contain dashes,
    +    # so we split on the last dash.
    +    name_part, sep, version_part = file_stem.rpartition("-")
    +    if not sep:
    +        raise InvalidSdistFilename(f"Invalid sdist filename: {filename!r}")
    +
    +    name = canonicalize_name(name_part)
    +
    +    try:
    +        version = Version(version_part)
    +    except InvalidVersion as e:
    +        raise InvalidSdistFilename(
    +            f"Invalid sdist filename (invalid version): {filename!r}"
    +        ) from e
    +
    +    return (name, version)
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/version.py b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/version.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..97f23fd9ad66a66ecfb8ebbabebc08d4cc51d683
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/packaging/version.py
    @@ -0,0 +1,1231 @@
    +# This file is dual licensed under the terms of the Apache License, Version
    +# 2.0, and the BSD License. See the LICENSE file in the root of this repository
    +# for complete details.
    +"""
    +.. testsetup::
    +
    +    from packaging.version import parse, normalize_pre, Version, _cmpkey
    +"""
    +
    +from __future__ import annotations
    +
    +import re
    +import sys
    +import typing
    +from typing import (
    +    Any,
    +    Callable,
    +    Literal,
    +    NamedTuple,
    +    SupportsInt,
    +    Tuple,
    +    TypedDict,
    +    Union,
    +)
    +
    +if typing.TYPE_CHECKING:
    +    from typing_extensions import Self, Unpack
    +
    +if sys.version_info >= (3, 13):  # pragma: no cover
    +    from warnings import deprecated as _deprecated
    +elif typing.TYPE_CHECKING:
    +    from typing_extensions import deprecated as _deprecated
    +else:  # pragma: no cover
    +    import functools
    +    import warnings
    +
    +    def _deprecated(message: str) -> object:
    +        def decorator(func: Callable[[...], object]) -> object:
    +            @functools.wraps(func)
    +            def wrapper(*args: object, **kwargs: object) -> object:
    +                warnings.warn(
    +                    message,
    +                    category=DeprecationWarning,
    +                    stacklevel=2,
    +                )
    +                return func(*args, **kwargs)
    +
    +            return wrapper
    +
    +        return decorator
    +
    +
    +_LETTER_NORMALIZATION = {
    +    "alpha": "a",
    +    "beta": "b",
    +    "c": "rc",
    +    "pre": "rc",
    +    "preview": "rc",
    +    "rev": "post",
    +    "r": "post",
    +}
    +
    +__all__ = ["VERSION_PATTERN", "InvalidVersion", "Version", "normalize_pre", "parse"]
    +
    +
    +def __dir__() -> list[str]:
    +    return __all__
    +
    +
    +LocalType = Tuple[Union[int, str], ...]
    +
    +CmpLocalType = Tuple[Tuple[int, str], ...]
    +CmpSuffix = Tuple[int, int, int, int, int, int]
    +CmpKey = Union[
    +    Tuple[int, Tuple[int, ...], CmpSuffix],
    +    Tuple[int, Tuple[int, ...], CmpSuffix, CmpLocalType],
    +]
    +VersionComparisonMethod = Callable[[CmpKey, CmpKey], bool]
    +
    +
    +class _VersionReplace(TypedDict, total=False):
    +    epoch: int | None
    +    release: tuple[int, ...] | None
    +    pre: tuple[str, int] | None
    +    post: int | None
    +    dev: int | None
    +    local: str | None
    +
    +
    +def normalize_pre(letter: str, /) -> str:
    +    """Normalize the pre-release segment of a version string.
    +
    +    Returns a lowercase version of the string if not a known pre-release
    +    identifier.
    +
    +    >>> normalize_pre('alpha')
    +    'a'
    +    >>> normalize_pre('BETA')
    +    'b'
    +    >>> normalize_pre('rc')
    +    'rc'
    +
    +    :param letter:
    +
    +    .. versionadded:: 26.1
    +    """
    +    letter = letter.lower()
    +    return _LETTER_NORMALIZATION.get(letter, letter)
    +
    +
    +def parse(version: str) -> Version:
    +    """Parse the given version string.
    +
    +    This is identical to the :class:`Version` constructor.
    +
    +    >>> parse('1.0.dev1')
    +    
    +
    +    :param version: The version string to parse.
    +    :raises InvalidVersion: When the version string is not a valid version.
    +    """
    +    return Version(version)
    +
    +
    +class InvalidVersion(ValueError):
    +    """Raised when a version string is not a valid version.
    +
    +    >>> Version("invalid")
    +    Traceback (most recent call last):
    +        ...
    +    packaging.version.InvalidVersion: Invalid version: 'invalid'
    +    """
    +
    +
    +class _BaseVersion:
    +    __slots__ = ()
    +
    +    # This can also be a normal member (see the packaging_legacy package);
    +    # we are just requiring it to be readable. Actually defining a property
    +    # has runtime effect on subclasses, so it's typing only.
    +    if typing.TYPE_CHECKING:
    +
    +        @property
    +        def _key(self) -> tuple[Any, ...]: ...
    +
    +    def __hash__(self) -> int:
    +        return hash(self._key)
    +
    +    # Please keep the duplicated `isinstance` check
    +    # in the six comparisons hereunder
    +    # unless you find a way to avoid adding overhead function calls.
    +    def __lt__(self, other: _BaseVersion) -> bool:
    +        if not isinstance(other, _BaseVersion):
    +            return NotImplemented
    +
    +        return self._key < other._key
    +
    +    def __le__(self, other: _BaseVersion) -> bool:
    +        if not isinstance(other, _BaseVersion):
    +            return NotImplemented
    +
    +        return self._key <= other._key
    +
    +    def __eq__(self, other: object) -> bool:
    +        if not isinstance(other, _BaseVersion):
    +            return NotImplemented
    +
    +        return self._key == other._key
    +
    +    def __ge__(self, other: _BaseVersion) -> bool:
    +        if not isinstance(other, _BaseVersion):
    +            return NotImplemented
    +
    +        return self._key >= other._key
    +
    +    def __gt__(self, other: _BaseVersion) -> bool:
    +        if not isinstance(other, _BaseVersion):
    +            return NotImplemented
    +
    +        return self._key > other._key
    +
    +    def __ne__(self, other: object) -> bool:
    +        if not isinstance(other, _BaseVersion):
    +            return NotImplemented
    +
    +        return self._key != other._key
    +
    +
    +# Deliberately not anchored to the start and end of the string, to make it
    +# easier for 3rd party code to reuse
    +
    +# Note that ++ doesn't behave identically on CPython and PyPy, so not using it here
    +_VERSION_PATTERN = r"""
    +    v?+                                                   # optional leading v
    +    (?a:
    +        (?:(?P[0-9]+)!)?+                          # epoch
    +        (?P[0-9]+(?:\.[0-9]+)*+)                 # release segment
    +        (?P
                                              # pre-release
    +            [._-]?+
    +            (?Palpha|a|beta|b|preview|pre|c|rc)
    +            [._-]?+
    +            (?P[0-9]+)?
    +        )?+
    +        (?P                                         # post release
    +            (?:-(?P[0-9]+))
    +            |
    +            (?:
    +                [._-]?
    +                (?Ppost|rev|r)
    +                [._-]?
    +                (?P[0-9]+)?
    +            )
    +        )?+
    +        (?P                                          # dev release
    +            [._-]?+
    +            (?Pdev)
    +            [._-]?+
    +            (?P[0-9]+)?
    +        )?+
    +    )
    +    (?a:\+
    +        (?P                                        # local version
    +            [a-z0-9]+
    +            (?:[._-][a-z0-9]+)*+
    +        )
    +    )?+
    +"""
    +
    +_VERSION_PATTERN_OLD = _VERSION_PATTERN.replace("*+", "*").replace("?+", "?")
    +
    +# Possessive qualifiers were added in Python 3.11.
    +# CPython 3.11.0-3.11.4 had a bug: https://github.com/python/cpython/pull/107795
    +# Older PyPy also had a bug.
    +VERSION_PATTERN = (
    +    _VERSION_PATTERN_OLD
    +    if (sys.implementation.name == "cpython" and sys.version_info < (3, 11, 5))
    +    or (sys.implementation.name == "pypy" and sys.version_info < (3, 11, 13))
    +    or sys.version_info < (3, 11)
    +    else _VERSION_PATTERN
    +)
    +"""
    +A string containing the regular expression used to match a valid version.
    +
    +The pattern is not anchored at either end, and is intended for embedding in larger
    +expressions (for example, matching a version number as part of a file name). The
    +regular expression should be compiled with the ``re.VERBOSE`` and ``re.IGNORECASE``
    +flags set.
    +
    +.. versionchanged:: 26.0
    +
    +   The regex now uses possessive qualifiers on Python 3.11 if they are
    +   supported (CPython 3.11.5+, PyPy 3.11.13+).
    +
    +:meta hide-value:
    +"""
    +
    +
    +# Validation pattern for local version in replace()
    +_LOCAL_PATTERN = re.compile(r"[a-z0-9]+(?:[._-][a-z0-9]+)*", re.IGNORECASE | re.ASCII)
    +
    +# Fast path: If a version has only digits and dots then we
    +# can skip the regex and parse it as a release segment
    +_SIMPLE_VERSION_INDICATORS = frozenset(".0123456789")
    +
    +
    +def _validate_epoch(value: object, /) -> int:
    +    epoch = value or 0
    +    if isinstance(epoch, int) and epoch >= 0:
    +        return epoch
    +    msg = f"epoch must be non-negative integer, got {epoch}"
    +    raise InvalidVersion(msg)
    +
    +
    +def _validate_release(value: object, /) -> tuple[int, ...]:
    +    release = (0,) if value is None else value
    +    if (
    +        isinstance(release, tuple)
    +        and len(release) > 0
    +        and all(isinstance(i, int) and i >= 0 for i in release)
    +    ):
    +        return release
    +    msg = f"release must be a non-empty tuple of non-negative integers, got {release}"
    +    raise InvalidVersion(msg)
    +
    +
    +def _validate_pre(value: object, /) -> tuple[Literal["a", "b", "rc"], int] | None:
    +    if value is None:
    +        return value
    +    if isinstance(value, tuple) and len(value) == 2:
    +        letter, number = value
    +        letter = normalize_pre(letter)
    +        if letter in {"a", "b", "rc"} and isinstance(number, int) and number >= 0:
    +            # type checkers can't infer the Literal type here on letter
    +            return (letter, number)  # type: ignore[return-value]
    +    msg = f"pre must be a tuple of ('a'|'b'|'rc', non-negative int), got {value}"
    +    raise InvalidVersion(msg)
    +
    +
    +def _validate_post(value: object, /) -> tuple[Literal["post"], int] | None:
    +    if value is None:
    +        return value
    +    if isinstance(value, int) and value >= 0:
    +        return ("post", value)
    +    msg = f"post must be non-negative integer, got {value}"
    +    raise InvalidVersion(msg)
    +
    +
    +def _validate_dev(value: object, /) -> tuple[Literal["dev"], int] | None:
    +    if value is None:
    +        return value
    +    if isinstance(value, int) and value >= 0:
    +        return ("dev", value)
    +    msg = f"dev must be non-negative integer, got {value}"
    +    raise InvalidVersion(msg)
    +
    +
    +def _validate_local(value: object, /) -> LocalType | None:
    +    if value is None:
    +        return value
    +    if isinstance(value, str) and _LOCAL_PATTERN.fullmatch(value):
    +        return _parse_local_version(value)
    +    msg = f"local must be a valid version string, got {value!r}"
    +    raise InvalidVersion(msg)
    +
    +
    +# Backward compatibility for internals before 26.0. Do not use.
    +class _Version(NamedTuple):
    +    epoch: int
    +    release: tuple[int, ...]
    +    dev: tuple[Literal["dev"], int] | None
    +    pre: tuple[Literal["a", "b", "rc"], int] | None
    +    post: tuple[Literal["post"], int] | None
    +    local: LocalType | None
    +
    +
    +class Version(_BaseVersion):
    +    """This class abstracts handling of a project's versions.
    +
    +    A :class:`Version` instance is comparison aware and can be compared and
    +    sorted using the standard Python interfaces.
    +
    +    >>> v1 = Version("1.0a5")
    +    >>> v2 = Version("1.0")
    +    >>> v1
    +    
    +    >>> v2
    +    
    +    >>> v1 < v2
    +    True
    +    >>> v1 == v2
    +    False
    +    >>> v1 > v2
    +    False
    +    >>> v1 >= v2
    +    False
    +    >>> v1 <= v2
    +    True
    +
    +    :class:`Version` is immutable; use :meth:`__replace__` to change
    +    part of a version.
    +
    +    Instances are safe to serialize with :mod:`pickle`. They use a stable
    +    format so the same pickle can be loaded in future packaging releases.
    +
    +    .. versionchanged:: 26.2
    +
    +        Added a stable pickle format. Pickles created with packaging 26.2+ can
    +        be unpickled with future releases.  Backward compatibility with pickles
    +        from packaging < 26.2 is supported but may be removed in a future
    +        release.
    +    """
    +
    +    __slots__ = (
    +        "_dev",
    +        "_epoch",
    +        "_hash_cache",
    +        "_key_cache",
    +        "_local",
    +        "_post",
    +        "_pre",
    +        "_release",
    +    )
    +    __match_args__ = ("_str",)
    +    """
    +    Pattern matching is supported on Python 3.10+.
    +
    +    .. versionadded:: 26.0
    +
    +    :meta hide-value:
    +    """
    +
    +    _regex = re.compile(r"\s*" + VERSION_PATTERN + r"\s*", re.VERBOSE | re.IGNORECASE)
    +
    +    _epoch: int
    +    _release: tuple[int, ...]
    +    _dev: tuple[Literal["dev"], int] | None
    +    _pre: tuple[Literal["a", "b", "rc"], int] | None
    +    _post: tuple[Literal["post"], int] | None
    +    _local: LocalType | None
    +
    +    _hash_cache: int | None
    +    _key_cache: CmpKey | None
    +
    +    def __init__(self, version: str) -> None:
    +        """Initialize a Version object.
    +
    +        :param version:
    +            The string representation of a version which will be parsed and normalized
    +            before use.
    +        :raises InvalidVersion:
    +            If the ``version`` does not conform to PEP 440 in any way then this
    +            exception will be raised.
    +        """
    +        if _SIMPLE_VERSION_INDICATORS.issuperset(version):
    +            try:
    +                self._release = tuple(map(int, version.split(".")))
    +            except ValueError:
    +                # Empty parts (from "1..2", ".1", etc.) are invalid versions.
    +                # Any other ValueError (e.g. int str-digits limit) should
    +                # propagate to the caller.
    +                if "" in version.split("."):
    +                    raise InvalidVersion(f"Invalid version: {version!r}") from None
    +                # TODO: remove "no cover" when Python 3.9 is dropped.
    +                raise  # pragma: no cover
    +
    +            self._epoch = 0
    +            self._pre = None
    +            self._post = None
    +            self._dev = None
    +            self._local = None
    +            self._key_cache = None
    +            self._hash_cache = None
    +            return
    +
    +        # Validate the version and parse it into pieces
    +        match = self._regex.fullmatch(version)
    +        if not match:
    +            raise InvalidVersion(f"Invalid version: {version!r}")
    +        self._epoch = int(match.group("epoch")) if match.group("epoch") else 0
    +        self._release = tuple(map(int, match.group("release").split(".")))
    +        # We can type ignore the assignments below because the regex guarantees
    +        # the correct strings
    +        self._pre = _parse_letter_version(match.group("pre_l"), match.group("pre_n"))  # type: ignore[assignment]
    +        self._post = _parse_letter_version(  # type: ignore[assignment]
    +            match.group("post_l"), match.group("post_n1") or match.group("post_n2")
    +        )
    +        self._dev = _parse_letter_version(match.group("dev_l"), match.group("dev_n"))  # type: ignore[assignment]
    +        self._local = _parse_local_version(match.group("local"))
    +
    +        # Key which will be used for sorting
    +        self._key_cache = None
    +        self._hash_cache = None
    +
    +    @classmethod
    +    def from_parts(
    +        cls,
    +        *,
    +        epoch: int = 0,
    +        release: tuple[int, ...],
    +        pre: tuple[str, int] | None = None,
    +        post: int | None = None,
    +        dev: int | None = None,
    +        local: str | None = None,
    +    ) -> Self:
    +        """
    +        Return a new version composed of the various parts.
    +
    +        This allows you to build a version without going though a string and
    +        running a regular expression. It normalizes pre-release strings. The
    +        ``release=`` keyword argument is required.
    +
    +        >>> Version.from_parts(release=(1,2,3))
    +        
    +        >>> Version.from_parts(release=(0,1,0), pre=("b", 1))
    +        
    +
    +        :param epoch:
    +        :param release: This version tuple is required
    +
    +        .. versionadded:: 26.1
    +        """
    +        _epoch = _validate_epoch(epoch)
    +        _release = _validate_release(release)
    +        _pre = _validate_pre(pre) if pre is not None else None
    +        _post = _validate_post(post) if post is not None else None
    +        _dev = _validate_dev(dev) if dev is not None else None
    +        _local = _validate_local(local) if local is not None else None
    +
    +        new_version = cls.__new__(cls)
    +        new_version._key_cache = None
    +        new_version._hash_cache = None
    +        new_version._epoch = _epoch
    +        new_version._release = _release
    +        new_version._pre = _pre
    +        new_version._post = _post
    +        new_version._dev = _dev
    +        new_version._local = _local
    +
    +        return new_version
    +
    +    def __replace__(self, **kwargs: Unpack[_VersionReplace]) -> Self:
    +        """
    +        __replace__(*, epoch=..., release=..., pre=..., post=..., dev=..., local=...)
    +
    +        Return a new version with parts replaced.
    +
    +        This returns a new version (unless no parts were changed). The
    +        pre-release is normalized. Setting a value to ``None`` clears it.
    +
    +        >>> v = Version("1.2.3")
    +        >>> v.__replace__(pre=("a", 1))
    +        
    +
    +        :param int | None epoch:
    +        :param tuple[int, ...] | None release:
    +        :param tuple[str, int] | None pre:
    +        :param int | None post:
    +        :param int | None dev:
    +        :param str | None local:
    +
    +        .. versionadded:: 26.0
    +        .. versionchanged:: 26.1
    +
    +           The pre-release portion is now normalized.
    +        """
    +        epoch = _validate_epoch(kwargs["epoch"]) if "epoch" in kwargs else self._epoch
    +        release = (
    +            _validate_release(kwargs["release"])
    +            if "release" in kwargs
    +            else self._release
    +        )
    +        pre = _validate_pre(kwargs["pre"]) if "pre" in kwargs else self._pre
    +        post = _validate_post(kwargs["post"]) if "post" in kwargs else self._post
    +        dev = _validate_dev(kwargs["dev"]) if "dev" in kwargs else self._dev
    +        local = _validate_local(kwargs["local"]) if "local" in kwargs else self._local
    +
    +        if (
    +            epoch == self._epoch
    +            and release == self._release
    +            and pre == self._pre
    +            and post == self._post
    +            and dev == self._dev
    +            and local == self._local
    +        ):
    +            return self
    +
    +        new_version = self.__class__.__new__(self.__class__)
    +        new_version._key_cache = None
    +        new_version._hash_cache = None
    +        new_version._epoch = epoch
    +        new_version._release = release
    +        new_version._pre = pre
    +        new_version._post = post
    +        new_version._dev = dev
    +        new_version._local = local
    +
    +        return new_version
    +
    +    @property
    +    def _key(self) -> CmpKey:
    +        if self._key_cache is None:
    +            self._key_cache = _cmpkey(
    +                self._epoch,
    +                self._release,
    +                self._pre,
    +                self._post,
    +                self._dev,
    +                self._local,
    +            )
    +        return self._key_cache
    +
    +    # __hash__ must be defined when __eq__ is overridden,
    +    # otherwise Python sets __hash__ to None.
    +    def __hash__(self) -> int:
    +        if (cached_hash := self._hash_cache) is not None:
    +            return cached_hash
    +
    +        if (key := self._key_cache) is None:
    +            self._key_cache = key = _cmpkey(
    +                self._epoch,
    +                self._release,
    +                self._pre,
    +                self._post,
    +                self._dev,
    +                self._local,
    +            )
    +        self._hash_cache = cached_hash = hash(key)
    +        return cached_hash
    +
    +    # Override comparison methods to use direct _key_cache access
    +    # This is faster than property access, especially before Python 3.12
    +    def __lt__(self, other: _BaseVersion) -> bool:
    +        if isinstance(other, Version):
    +            if self._key_cache is None:
    +                self._key_cache = _cmpkey(
    +                    self._epoch,
    +                    self._release,
    +                    self._pre,
    +                    self._post,
    +                    self._dev,
    +                    self._local,
    +                )
    +            if other._key_cache is None:
    +                other._key_cache = _cmpkey(
    +                    other._epoch,
    +                    other._release,
    +                    other._pre,
    +                    other._post,
    +                    other._dev,
    +                    other._local,
    +                )
    +            return self._key_cache < other._key_cache
    +
    +        if not isinstance(other, _BaseVersion):
    +            return NotImplemented
    +
    +        return super().__lt__(other)
    +
    +    def __le__(self, other: _BaseVersion) -> bool:
    +        if isinstance(other, Version):
    +            if self._key_cache is None:
    +                self._key_cache = _cmpkey(
    +                    self._epoch,
    +                    self._release,
    +                    self._pre,
    +                    self._post,
    +                    self._dev,
    +                    self._local,
    +                )
    +            if other._key_cache is None:
    +                other._key_cache = _cmpkey(
    +                    other._epoch,
    +                    other._release,
    +                    other._pre,
    +                    other._post,
    +                    other._dev,
    +                    other._local,
    +                )
    +            return self._key_cache <= other._key_cache
    +
    +        if not isinstance(other, _BaseVersion):
    +            return NotImplemented
    +
    +        return super().__le__(other)
    +
    +    def __eq__(self, other: object) -> bool:
    +        if isinstance(other, Version):
    +            if self._key_cache is None:
    +                self._key_cache = _cmpkey(
    +                    self._epoch,
    +                    self._release,
    +                    self._pre,
    +                    self._post,
    +                    self._dev,
    +                    self._local,
    +                )
    +            if other._key_cache is None:
    +                other._key_cache = _cmpkey(
    +                    other._epoch,
    +                    other._release,
    +                    other._pre,
    +                    other._post,
    +                    other._dev,
    +                    other._local,
    +                )
    +            return self._key_cache == other._key_cache
    +
    +        if not isinstance(other, _BaseVersion):
    +            return NotImplemented
    +
    +        return super().__eq__(other)
    +
    +    def __ge__(self, other: _BaseVersion) -> bool:
    +        if isinstance(other, Version):
    +            if self._key_cache is None:
    +                self._key_cache = _cmpkey(
    +                    self._epoch,
    +                    self._release,
    +                    self._pre,
    +                    self._post,
    +                    self._dev,
    +                    self._local,
    +                )
    +            if other._key_cache is None:
    +                other._key_cache = _cmpkey(
    +                    other._epoch,
    +                    other._release,
    +                    other._pre,
    +                    other._post,
    +                    other._dev,
    +                    other._local,
    +                )
    +            return self._key_cache >= other._key_cache
    +
    +        if not isinstance(other, _BaseVersion):
    +            return NotImplemented
    +
    +        return super().__ge__(other)
    +
    +    def __gt__(self, other: _BaseVersion) -> bool:
    +        if isinstance(other, Version):
    +            if self._key_cache is None:
    +                self._key_cache = _cmpkey(
    +                    self._epoch,
    +                    self._release,
    +                    self._pre,
    +                    self._post,
    +                    self._dev,
    +                    self._local,
    +                )
    +            if other._key_cache is None:
    +                other._key_cache = _cmpkey(
    +                    other._epoch,
    +                    other._release,
    +                    other._pre,
    +                    other._post,
    +                    other._dev,
    +                    other._local,
    +                )
    +            return self._key_cache > other._key_cache
    +
    +        if not isinstance(other, _BaseVersion):
    +            return NotImplemented
    +
    +        return super().__gt__(other)
    +
    +    def __ne__(self, other: object) -> bool:
    +        if isinstance(other, Version):
    +            if self._key_cache is None:
    +                self._key_cache = _cmpkey(
    +                    self._epoch,
    +                    self._release,
    +                    self._pre,
    +                    self._post,
    +                    self._dev,
    +                    self._local,
    +                )
    +            if other._key_cache is None:
    +                other._key_cache = _cmpkey(
    +                    other._epoch,
    +                    other._release,
    +                    other._pre,
    +                    other._post,
    +                    other._dev,
    +                    other._local,
    +                )
    +            return self._key_cache != other._key_cache
    +
    +        if not isinstance(other, _BaseVersion):
    +            return NotImplemented
    +
    +        return super().__ne__(other)
    +
    +    def __getstate__(
    +        self,
    +    ) -> tuple[
    +        int,
    +        tuple[int, ...],
    +        tuple[str, int] | None,
    +        tuple[str, int] | None,
    +        tuple[str, int] | None,
    +        LocalType | None,
    +    ]:
    +        # Return state as a 6-item tuple for compactness:
    +        #   (epoch, release, pre, post, dev, local)
    +        # Cache members are excluded and will be recomputed on demand
    +        return (
    +            self._epoch,
    +            self._release,
    +            self._pre,
    +            self._post,
    +            self._dev,
    +            self._local,
    +        )
    +
    +    def __setstate__(self, state: object) -> None:
    +        # Always discard cached values — they may contain stale references
    +        # (e.g. packaging._structures.InfinityType from pre-26.1 pickles)
    +        # and will be recomputed on demand from the core fields above.
    +        self._key_cache = None
    +        self._hash_cache = None
    +
    +        if isinstance(state, tuple):
    +            if len(state) == 6:
    +                # New format (26.2+): (epoch, release, pre, post, dev, local)
    +                (
    +                    self._epoch,
    +                    self._release,
    +                    self._pre,
    +                    self._post,
    +                    self._dev,
    +                    self._local,
    +                ) = state
    +                return
    +            if len(state) == 2:
    +                # Format (packaging 26.0-26.1): (None, {slot: value}).
    +                _, slot_dict = state
    +                if isinstance(slot_dict, dict):
    +                    self._epoch = slot_dict["_epoch"]
    +                    self._release = slot_dict["_release"]
    +                    self._pre = slot_dict.get("_pre")
    +                    self._post = slot_dict.get("_post")
    +                    self._dev = slot_dict.get("_dev")
    +                    self._local = slot_dict.get("_local")
    +                    return
    +        if isinstance(state, dict):
    +            # Old format (packaging <= 25.x, no __slots__): state is a plain
    +            # dict with "_version" (_Version NamedTuple) and "_key" entries.
    +            version_nt = state.get("_version")
    +            if version_nt is not None:
    +                self._epoch = version_nt.epoch
    +                self._release = version_nt.release
    +                self._pre = version_nt.pre
    +                self._post = version_nt.post
    +                self._dev = version_nt.dev
    +                self._local = version_nt.local
    +                return
    +
    +        raise TypeError(f"Cannot restore Version from {state!r}")
    +
    +    @property
    +    @_deprecated("Version._version is private and will be removed soon")
    +    def _version(self) -> _Version:
    +        return _Version(
    +            self._epoch, self._release, self._dev, self._pre, self._post, self._local
    +        )
    +
    +    @_version.setter
    +    @_deprecated("Version._version is private and will be removed soon")
    +    def _version(self, value: _Version) -> None:
    +        self._epoch = value.epoch
    +        self._release = value.release
    +        self._dev = value.dev
    +        self._pre = value.pre
    +        self._post = value.post
    +        self._local = value.local
    +        self._key_cache = None
    +        self._hash_cache = None
    +
    +    def __repr__(self) -> str:
    +        """A representation of the Version that shows all internal state.
    +
    +        >>> Version('1.0.0')
    +        
    +        """
    +        return f"<{self.__class__.__name__}({str(self)!r})>"
    +
    +    def __str__(self) -> str:
    +        """A string representation of the version that can be round-tripped.
    +
    +        >>> str(Version("1.0a5"))
    +        '1.0a5'
    +        """
    +        # This is a hot function, so not calling self.base_version
    +        version = ".".join(map(str, self.release))
    +
    +        # Epoch
    +        if self.epoch:
    +            version = f"{self.epoch}!{version}"
    +
    +        # Pre-release
    +        if self.pre is not None:
    +            version += "".join(map(str, self.pre))
    +
    +        # Post-release
    +        if self.post is not None:
    +            version += f".post{self.post}"
    +
    +        # Development release
    +        if self.dev is not None:
    +            version += f".dev{self.dev}"
    +
    +        # Local version segment
    +        if self.local is not None:
    +            version += f"+{self.local}"
    +
    +        return version
    +
    +    @property
    +    def _str(self) -> str:
    +        """Internal property for match_args"""
    +        return str(self)
    +
    +    @property
    +    def epoch(self) -> int:
    +        """The epoch of the version.
    +
    +        >>> Version("2.0.0").epoch
    +        0
    +        >>> Version("1!2.0.0").epoch
    +        1
    +        """
    +        return self._epoch
    +
    +    @property
    +    def release(self) -> tuple[int, ...]:
    +        """The components of the "release" segment of the version.
    +
    +        >>> Version("1.2.3").release
    +        (1, 2, 3)
    +        >>> Version("2.0.0").release
    +        (2, 0, 0)
    +        >>> Version("1!2.0.0.post0").release
    +        (2, 0, 0)
    +
    +        Includes trailing zeroes but not the epoch or any pre-release / development /
    +        post-release suffixes.
    +        """
    +        return self._release
    +
    +    @property
    +    def pre(self) -> tuple[Literal["a", "b", "rc"], int] | None:
    +        """The pre-release segment of the version.
    +
    +        >>> print(Version("1.2.3").pre)
    +        None
    +        >>> Version("1.2.3a1").pre
    +        ('a', 1)
    +        >>> Version("1.2.3b1").pre
    +        ('b', 1)
    +        >>> Version("1.2.3rc1").pre
    +        ('rc', 1)
    +        """
    +        return self._pre
    +
    +    @property
    +    def post(self) -> int | None:
    +        """The post-release number of the version.
    +
    +        >>> print(Version("1.2.3").post)
    +        None
    +        >>> Version("1.2.3.post1").post
    +        1
    +        """
    +        return self._post[1] if self._post else None
    +
    +    @property
    +    def dev(self) -> int | None:
    +        """The development number of the version.
    +
    +        >>> print(Version("1.2.3").dev)
    +        None
    +        >>> Version("1.2.3.dev1").dev
    +        1
    +        """
    +        return self._dev[1] if self._dev else None
    +
    +    @property
    +    def local(self) -> str | None:
    +        """The local version segment of the version.
    +
    +        >>> print(Version("1.2.3").local)
    +        None
    +        >>> Version("1.2.3+abc").local
    +        'abc'
    +        """
    +        if self._local:
    +            return ".".join(str(x) for x in self._local)
    +        else:
    +            return None
    +
    +    @property
    +    def public(self) -> str:
    +        """The public portion of the version.
    +
    +        This returns a string. If you want a :class:`Version` again and care
    +        about performance, use ``v.__replace__(local=None)`` instead.
    +
    +        >>> Version("1.2.3").public
    +        '1.2.3'
    +        >>> Version("1.2.3+abc").public
    +        '1.2.3'
    +        >>> Version("1!1.2.3dev1+abc").public
    +        '1!1.2.3.dev1'
    +        """
    +        return str(self).split("+", 1)[0]
    +
    +    @property
    +    def base_version(self) -> str:
    +        """The "base version" of the version.
    +
    +        This returns a string. If you want a :class:`Version` again and care
    +        about performance, use
    +        ``v.__replace__(pre=None, post=None, dev=None, local=None)`` instead.
    +
    +        >>> Version("1.2.3").base_version
    +        '1.2.3'
    +        >>> Version("1.2.3+abc").base_version
    +        '1.2.3'
    +        >>> Version("1!1.2.3dev1+abc").base_version
    +        '1!1.2.3'
    +
    +        The "base version" is the public version of the project without any pre or post
    +        release markers.
    +        """
    +        release_segment = ".".join(map(str, self.release))
    +        return f"{self.epoch}!{release_segment}" if self.epoch else release_segment
    +
    +    @property
    +    def is_prerelease(self) -> bool:
    +        """Whether this version is a pre-release.
    +
    +        >>> Version("1.2.3").is_prerelease
    +        False
    +        >>> Version("1.2.3a1").is_prerelease
    +        True
    +        >>> Version("1.2.3b1").is_prerelease
    +        True
    +        >>> Version("1.2.3rc1").is_prerelease
    +        True
    +        >>> Version("1.2.3dev1").is_prerelease
    +        True
    +        """
    +        return self.dev is not None or self.pre is not None
    +
    +    @property
    +    def is_postrelease(self) -> bool:
    +        """Whether this version is a post-release.
    +
    +        >>> Version("1.2.3").is_postrelease
    +        False
    +        >>> Version("1.2.3.post1").is_postrelease
    +        True
    +        """
    +        return self.post is not None
    +
    +    @property
    +    def is_devrelease(self) -> bool:
    +        """Whether this version is a development release.
    +
    +        >>> Version("1.2.3").is_devrelease
    +        False
    +        >>> Version("1.2.3.dev1").is_devrelease
    +        True
    +        """
    +        return self.dev is not None
    +
    +    @property
    +    def major(self) -> int:
    +        """The first item of :attr:`release` or ``0`` if unavailable.
    +
    +        >>> Version("1.2.3").major
    +        1
    +        """
    +        return self.release[0] if len(self.release) >= 1 else 0
    +
    +    @property
    +    def minor(self) -> int:
    +        """The second item of :attr:`release` or ``0`` if unavailable.
    +
    +        >>> Version("1.2.3").minor
    +        2
    +        >>> Version("1").minor
    +        0
    +        """
    +        return self.release[1] if len(self.release) >= 2 else 0
    +
    +    @property
    +    def micro(self) -> int:
    +        """The third item of :attr:`release` or ``0`` if unavailable.
    +
    +        >>> Version("1.2.3").micro
    +        3
    +        >>> Version("1").micro
    +        0
    +        """
    +        return self.release[2] if len(self.release) >= 3 else 0
    +
    +
    +class _TrimmedRelease(Version):
    +    __slots__ = ()
    +
    +    def __init__(self, version: str | Version) -> None:
    +        if isinstance(version, Version):
    +            self._epoch = version._epoch
    +            self._release = version._release
    +            self._dev = version._dev
    +            self._pre = version._pre
    +            self._post = version._post
    +            self._local = version._local
    +            self._key_cache = version._key_cache
    +            return
    +        super().__init__(version)  # pragma: no cover
    +
    +    @property
    +    def release(self) -> tuple[int, ...]:
    +        """
    +        Release segment without any trailing zeros.
    +
    +        >>> _TrimmedRelease('1.0.0').release
    +        (1,)
    +        >>> _TrimmedRelease('0.0').release
    +        (0,)
    +        """
    +        # This leaves one 0.
    +        rel = super().release
    +        len_release = len(rel)
    +        i = len_release
    +        while i > 1 and rel[i - 1] == 0:
    +            i -= 1
    +        return rel if i == len_release else rel[:i]
    +
    +
    +def _parse_letter_version(
    +    letter: str | None, number: str | bytes | SupportsInt | None
    +) -> tuple[str, int] | None:
    +    if letter:
    +        # We normalize any letters to their lower case form
    +        letter = letter.lower()
    +
    +        # We consider some words to be alternate spellings of other words and
    +        # in those cases we want to normalize the spellings to our preferred
    +        # spelling.
    +        letter = _LETTER_NORMALIZATION.get(letter, letter)
    +
    +        # We consider there to be an implicit 0 in a pre-release if there is
    +        # not a numeral associated with it.
    +        return letter, int(number or 0)
    +
    +    if number:
    +        # We assume if we are given a number, but we are not given a letter
    +        # then this is using the implicit post release syntax (e.g. 1.0-1)
    +        return "post", int(number)
    +
    +    return None
    +
    +
    +_local_version_separators = re.compile(r"[\._-]")
    +
    +
    +def _parse_local_version(local: str | None) -> LocalType | None:
    +    """
    +    Takes a string like ``"abc.1.twelve"`` and turns it into
    +    ``("abc", 1, "twelve")``.
    +    """
    +    if local is not None:
    +        return tuple(
    +            part.lower() if not part.isdigit() else int(part)
    +            for part in _local_version_separators.split(local)
    +        )
    +    return None
    +
    +
    +# Sort ranks for pre-release: dev-only < a < b < rc < stable (no pre-release).
    +_PRE_RANK = {"a": 0, "b": 1, "rc": 2}
    +_PRE_RANK_DEV_ONLY = -1  # sorts before a(0)
    +_PRE_RANK_STABLE = 3  # sorts after rc(2)
    +
    +# In local version segments, strings sort before ints per PEP 440.
    +_LOCAL_STR_RANK = -1  # sorts before all non-negative ints
    +
    +# Pre-computed suffix for stable releases (no pre, post, or dev segments).
    +# See _cmpkey() for the suffix layout.
    +_STABLE_SUFFIX = (_PRE_RANK_STABLE, 0, 0, 0, 1, 0)
    +
    +
    +def _cmpkey(
    +    epoch: int,
    +    release: tuple[int, ...],
    +    pre: tuple[str, int] | None,
    +    post: tuple[str, int] | None,
    +    dev: tuple[str, int] | None,
    +    local: LocalType | None,
    +) -> CmpKey:
    +    """Build a comparison key for PEP 440 ordering.
    +
    +    Returns ``(epoch, release, suffix)`` or
    +    ``(epoch, release, suffix, local)`` so that plain tuple
    +    comparison gives the correct order.
    +
    +    Trailing zeros are stripped from the release so that ``1.0.0 == 1``.
    +
    +    The suffix is a flat 6-int tuple that encodes pre/post/dev:
    +    ``(pre_rank, pre_n, post_rank, post_n, dev_rank, dev_n)``
    +
    +    pre_rank: dev-only=-1, a=0, b=1, rc=2, no-pre=3
    +        Dev-only releases (no pre or post) get -1 so they sort before
    +        any alpha/beta/rc.  Releases without a pre-release tag get 3
    +        so they sort after rc.
    +    post_rank: no-post=0, post=1
    +        Releases without a post segment sort before those with one.
    +    dev_rank: dev=0, no-dev=1
    +        Releases without a dev segment sort after those with one.
    +
    +    Local segments use ``(n, "")`` for ints and ``(-1, s)`` for strings,
    +    following PEP 440: strings sort before ints, strings compare
    +    lexicographically, ints compare numerically, and shorter segments
    +    sort before longer when prefixes match.  Versions without a local
    +    segment sort before those with one (3-tuple < 4-tuple).
    +
    +    >>> _cmpkey(0, (1, 0, 0), None, None, None, None)
    +    (0, (1,), (3, 0, 0, 0, 1, 0))
    +    >>> _cmpkey(0, (1,), ("a", 1), None, None, None)
    +    (0, (1,), (0, 1, 0, 0, 1, 0))
    +    >>> _cmpkey(0, (1,), None, None, None, ("ubuntu", 1))
    +    (0, (1,), (3, 0, 0, 0, 1, 0), ((-1, 'ubuntu'), (1, '')))
    +    """
    +    # Strip trailing zeros: 1.0.0 compares equal to 1.
    +    len_release = len(release)
    +    i = len_release
    +    while i and release[i - 1] == 0:
    +        i -= 1
    +    trimmed = release if i == len_release else release[:i]
    +
    +    # Fast path: stable release with no local segment.
    +    if pre is None and post is None and dev is None and local is None:
    +        return epoch, trimmed, _STABLE_SUFFIX
    +
    +    if pre is None and post is None and dev is not None:
    +        # dev-only (e.g. 1.0.dev1) sorts before all pre-releases.
    +        pre_rank, pre_n = _PRE_RANK_DEV_ONLY, 0
    +    elif pre is None:
    +        pre_rank, pre_n = _PRE_RANK_STABLE, 0
    +    else:
    +        pre_rank, pre_n = _PRE_RANK[pre[0]], pre[1]
    +
    +    post_rank = 0 if post is None else 1
    +    post_n = 0 if post is None else post[1]
    +
    +    dev_rank = 1 if dev is None else 0
    +    dev_n = 0 if dev is None else dev[1]
    +
    +    suffix = (pre_rank, pre_n, post_rank, post_n, dev_rank, dev_n)
    +
    +    if local is None:
    +        return epoch, trimmed, suffix
    +
    +    cmp_local: CmpLocalType = tuple(
    +        (seg, "") if isinstance(seg, int) else (_LOCAL_STR_RANK, seg) for seg in local
    +    )
    +    return epoch, trimmed, suffix, cmp_local
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas-3.0.2.dist-info/INSTALLER b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas-3.0.2.dist-info/INSTALLER
    new file mode 100644
    index 0000000000000000000000000000000000000000..5c69047b2eb8235994febeeae1da4a82365a240a
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas-3.0.2.dist-info/INSTALLER
    @@ -0,0 +1 @@
    +uv
    \ No newline at end of file
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas-3.0.2.dist-info/LICENSE b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas-3.0.2.dist-info/LICENSE
    new file mode 100644
    index 0000000000000000000000000000000000000000..7ce451a64a3481e189ff2a0b5e8613d5e0dc2a17
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas-3.0.2.dist-info/LICENSE
    @@ -0,0 +1,1060 @@
    +BSD 3-Clause License
    +
    +Copyright (c) 2008-2011, AQR Capital Management, LLC, Lambda Foundry, Inc. and PyData Development Team
    +All rights reserved.
    +
    +Copyright (c) 2011-2026, Open source contributors.
    +
    +Redistribution and use in source and binary forms, with or without
    +modification, are permitted provided that the following conditions are met:
    +
    +* Redistributions of source code must retain the above copyright notice, this
    +  list of conditions and the following disclaimer.
    +
    +* Redistributions in binary form must reproduce the above copyright notice,
    +  this list of conditions and the following disclaimer in the documentation
    +  and/or other materials provided with the distribution.
    +
    +* Neither the name of the copyright holder nor the names of its
    +  contributors may be used to endorse or promote products derived from
    +  this software without specific prior written permission.
    +
    +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
    +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
    +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    +Copyright (c) 2010-2019 Keith Goodman
    +Copyright (c) 2019 Bottleneck Developers
    +All rights reserved.
    +
    +Redistribution and use in source and binary forms, with or without
    +modification, are permitted provided that the following conditions are met:
    +
    +    * Redistributions of source code must retain the above copyright notice,
    +      this list of conditions and the following disclaimer.
    +
    +    * Redistributions in binary form must reproduce the above copyright
    +      notice, this list of conditions and the following disclaimer in the
    +      documentation and/or other materials provided with the distribution.
    +
    +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    +POSSIBILITY OF SUCH DAMAGE.Copyright 2017- Paul Ganssle 
    +Copyright 2017- dateutil contributors (see AUTHORS file)
    +
    +   Licensed under the Apache License, Version 2.0 (the "License");
    +   you may not use this file except in compliance with the License.
    +   You may obtain a copy of the License at
    +
    +       http://www.apache.org/licenses/LICENSE-2.0
    +
    +   Unless required by applicable law or agreed to in writing, software
    +   distributed under the License is distributed on an "AS IS" BASIS,
    +   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +   See the License for the specific language governing permissions and
    +   limitations under the License.
    +
    +The above license applies to all contributions after 2017-12-01, as well as
    +all contributions that have been re-licensed (see AUTHORS file for the list of
    +contributors who have re-licensed their code).
    +--------------------------------------------------------------------------------
    +dateutil - Extensions to the standard Python datetime module.
    +
    +Copyright (c) 2003-2011 - Gustavo Niemeyer 
    +Copyright (c) 2012-2014 - Tomi Pieviläinen 
    +Copyright (c) 2014-2016 - Yaron de Leeuw 
    +Copyright (c) 2015-     - Paul Ganssle 
    +Copyright (c) 2015-     - dateutil contributors (see AUTHORS file)
    +
    +All rights reserved.
    +
    +Redistribution and use in source and binary forms, with or without
    +modification, are permitted provided that the following conditions are met:
    +
    +    * Redistributions of source code must retain the above copyright notice,
    +      this list of conditions and the following disclaimer.
    +    * Redistributions in binary form must reproduce the above copyright notice,
    +      this list of conditions and the following disclaimer in the documentation
    +      and/or other materials provided with the distribution.
    +    * Neither the name of the copyright holder nor the names of its
    +      contributors may be used to endorse or promote products derived from
    +      this software without specific prior written permission.
    +
    +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
    +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
    +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
    +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
    +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
    +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
    +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
    +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    +
    +The above BSD License Applies to all code, even that also covered by Apache 2.0.# MIT License
    +
    +Copyright (c) 2019 Hadley Wickham; RStudio; and Evan Miller
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.
    +Based on http://opensource.org/licenses/MIT
    +
    +This is a template. Complete and ship as file LICENSE the following 2
    +lines (only)
    +
    +YEAR:
    +COPYRIGHT HOLDER:
    +
    +and specify as
    +
    +License: MIT + file LICENSE
    +
    +Copyright (c) , 
    +
    +Permission is hereby granted, free of charge, to any person obtaining
    +a copy of this software and associated documentation files (the
    +"Software"), to deal in the Software without restriction, including
    +without limitation the rights to use, copy, modify, merge, publish,
    +distribute, sublicense, and/or sell copies of the Software, and to
    +permit persons to whom the Software is furnished to do so, subject to
    +the following conditions:
    +
    +The above copyright notice and this permission notice shall be
    +included in all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
    +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
    +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    +The MIT License
    +
    +Copyright (c) 2008-     Attractive Chaos 
    +
    +Permission is hereby granted, free of charge, to any person obtaining
    +a copy of this software and associated documentation files (the
    +"Software"), to deal in the Software without restriction, including
    +without limitation the rights to use, copy, modify, merge, publish,
    +distribute, sublicense, and/or sell copies of the Software, and to
    +permit persons to whom the Software is furnished to do so, subject to
    +the following conditions:
    +
    +The above copyright notice and this permission notice shall be
    +included in all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
    +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
    +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
    +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.musl as a whole is licensed under the following standard MIT license:
    +
    +----------------------------------------------------------------------
    +Copyright © 2005-2020 Rich Felker, et al.
    +
    +Permission is hereby granted, free of charge, to any person obtaining
    +a copy of this software and associated documentation files (the
    +"Software"), to deal in the Software without restriction, including
    +without limitation the rights to use, copy, modify, merge, publish,
    +distribute, sublicense, and/or sell copies of the Software, and to
    +permit persons to whom the Software is furnished to do so, subject to
    +the following conditions:
    +
    +The above copyright notice and this permission notice shall be
    +included in all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    +----------------------------------------------------------------------
    +
    +Authors/contributors include:
    +
    +A. Wilcox
    +Ada Worcester
    +Alex Dowad
    +Alex Suykov
    +Alexander Monakov
    +Andre McCurdy
    +Andrew Kelley
    +Anthony G. Basile
    +Aric Belsito
    +Arvid Picciani
    +Bartosz Brachaczek
    +Benjamin Peterson
    +Bobby Bingham
    +Boris Brezillon
    +Brent Cook
    +Chris Spiegel
    +Clément Vasseur
    +Daniel Micay
    +Daniel Sabogal
    +Daurnimator
    +David Carlier
    +David Edelsohn
    +Denys Vlasenko
    +Dmitry Ivanov
    +Dmitry V. Levin
    +Drew DeVault
    +Emil Renner Berthing
    +Fangrui Song
    +Felix Fietkau
    +Felix Janda
    +Gianluca Anzolin
    +Hauke Mehrtens
    +He X
    +Hiltjo Posthuma
    +Isaac Dunham
    +Jaydeep Patil
    +Jens Gustedt
    +Jeremy Huntwork
    +Jo-Philipp Wich
    +Joakim Sindholt
    +John Spencer
    +Julien Ramseier
    +Justin Cormack
    +Kaarle Ritvanen
    +Khem Raj
    +Kylie McClain
    +Leah Neukirchen
    +Luca Barbato
    +Luka Perkov
    +M Farkas-Dyck (Strake)
    +Mahesh Bodapati
    +Markus Wichmann
    +Masanori Ogino
    +Michael Clark
    +Michael Forney
    +Mikhail Kremnyov
    +Natanael Copa
    +Nicholas J. Kain
    +orc
    +Pascal Cuoq
    +Patrick Oppenlander
    +Petr Hosek
    +Petr Skocik
    +Pierre Carrier
    +Reini Urban
    +Rich Felker
    +Richard Pennington
    +Ryan Fairfax
    +Samuel Holland
    +Segev Finer
    +Shiz
    +sin
    +Solar Designer
    +Stefan Kristiansson
    +Stefan O'Rear
    +Szabolcs Nagy
    +Timo Teräs
    +Trutz Behn
    +Valentin Ochs
    +Will Dietz
    +William Haddon
    +William Pitcock
    +
    +Portions of this software are derived from third-party works licensed
    +under terms compatible with the above MIT license:
    +
    +The TRE regular expression implementation (src/regex/reg* and
    +src/regex/tre*) is Copyright © 2001-2008 Ville Laurikari and licensed
    +under a 2-clause BSD license (license text in the source files). The
    +included version has been heavily modified by Rich Felker in 2012, in
    +the interests of size, simplicity, and namespace cleanliness.
    +
    +Much of the math library code (src/math/* and src/complex/*) is
    +Copyright © 1993,2004 Sun Microsystems or
    +Copyright © 2003-2011 David Schultz or
    +Copyright © 2003-2009 Steven G. Kargl or
    +Copyright © 2003-2009 Bruce D. Evans or
    +Copyright © 2008 Stephen L. Moshier or
    +Copyright © 2017-2018 Arm Limited
    +and labelled as such in comments in the individual source files. All
    +have been licensed under extremely permissive terms.
    +
    +The ARM memcpy code (src/string/arm/memcpy.S) is Copyright © 2008
    +The Android Open Source Project and is licensed under a two-clause BSD
    +license. It was taken from Bionic libc, used on Android.
    +
    +The AArch64 memcpy and memset code (src/string/aarch64/*) are
    +Copyright © 1999-2019, Arm Limited.
    +
    +The implementation of DES for crypt (src/crypt/crypt_des.c) is
    +Copyright © 1994 David Burren. It is licensed under a BSD license.
    +
    +The implementation of blowfish crypt (src/crypt/crypt_blowfish.c) was
    +originally written by Solar Designer and placed into the public
    +domain. The code also comes with a fallback permissive license for use
    +in jurisdictions that may not recognize the public domain.
    +
    +The smoothsort implementation (src/stdlib/qsort.c) is Copyright © 2011
    +Valentin Ochs and is licensed under an MIT-style license.
    +
    +The x86_64 port was written by Nicholas J. Kain and is licensed under
    +the standard MIT terms.
    +
    +The mips and microblaze ports were originally written by Richard
    +Pennington for use in the ellcc project. The original code was adapted
    +by Rich Felker for build system and code conventions during upstream
    +integration. It is licensed under the standard MIT terms.
    +
    +The mips64 port was contributed by Imagination Technologies and is
    +licensed under the standard MIT terms.
    +
    +The powerpc port was also originally written by Richard Pennington,
    +and later supplemented and integrated by John Spencer. It is licensed
    +under the standard MIT terms.
    +
    +All other files which have no copyright comments are original works
    +produced specifically for use as part of this library, written either
    +by Rich Felker, the main author of the library, or by one or more
    +contibutors listed above. Details on authorship of individual files
    +can be found in the git version control history of the project. The
    +omission of copyright and license comments in each file is in the
    +interest of source tree size.
    +
    +In addition, permission is hereby granted for all public header files
    +(include/* and arch/*/bits/*) and crt files intended to be linked into
    +applications (crt/*, ldso/dlstart.c, and arch/*/crt_arch.h) to omit
    +the copyright notice and permission notice otherwise required by the
    +license, and to use these files without any requirement of
    +attribution. These files include substantial contributions from:
    +
    +Bobby Bingham
    +John Spencer
    +Nicholas J. Kain
    +Rich Felker
    +Richard Pennington
    +Stefan Kristiansson
    +Szabolcs Nagy
    +
    +all of whom have explicitly granted such permission.
    +
    +This file previously contained text expressing a belief that most of
    +the files covered by the above exception were sufficiently trivial not
    +to be subject to copyright, resulting in confusion over whether it
    +negated the permissions granted in the license. In the spirit of
    +permissive licensing, and of not having licensing issues being an
    +obstacle to adoption, that text has been removed.Copyright (c) 2005-2023, NumPy Developers.
    +All rights reserved.
    +
    +Redistribution and use in source and binary forms, with or without
    +modification, are permitted provided that the following conditions are
    +met:
    +
    +    * Redistributions of source code must retain the above copyright
    +       notice, this list of conditions and the following disclaimer.
    +
    +    * Redistributions in binary form must reproduce the above
    +       copyright notice, this list of conditions and the following
    +       disclaimer in the documentation and/or other materials provided
    +       with the distribution.
    +
    +    * Neither the name of the NumPy Developers nor the names of any
    +       contributors may be used to endorse or promote products derived
    +       from this software without specific prior written permission.
    +
    +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    +                                 Apache License
    +                           Version 2.0, January 2004
    +                        http://www.apache.org/licenses/
    +
    +   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
    +
    +   1. Definitions.
    +
    +      "License" shall mean the terms and conditions for use, reproduction,
    +      and distribution as defined by Sections 1 through 9 of this document.
    +
    +      "Licensor" shall mean the copyright owner or entity authorized by
    +      the copyright owner that is granting the License.
    +
    +      "Legal Entity" shall mean the union of the acting entity and all
    +      other entities that control, are controlled by, or are under common
    +      control with that entity. For the purposes of this definition,
    +      "control" means (i) the power, direct or indirect, to cause the
    +      direction or management of such entity, whether by contract or
    +      otherwise, or (ii) ownership of fifty percent (50%) or more of the
    +      outstanding shares, or (iii) beneficial ownership of such entity.
    +
    +      "You" (or "Your") shall mean an individual or Legal Entity
    +      exercising permissions granted by this License.
    +
    +      "Source" form shall mean the preferred form for making modifications,
    +      including but not limited to software source code, documentation
    +      source, and configuration files.
    +
    +      "Object" form shall mean any form resulting from mechanical
    +      transformation or translation of a Source form, including but
    +      not limited to compiled object code, generated documentation,
    +      and conversions to other media types.
    +
    +      "Work" shall mean the work of authorship, whether in Source or
    +      Object form, made available under the License, as indicated by a
    +      copyright notice that is included in or attached to the work
    +      (an example is provided in the Appendix below).
    +
    +      "Derivative Works" shall mean any work, whether in Source or Object
    +      form, that is based on (or derived from) the Work and for which the
    +      editorial revisions, annotations, elaborations, or other modifications
    +      represent, as a whole, an original work of authorship. For the purposes
    +      of this License, Derivative Works shall not include works that remain
    +      separable from, or merely link (or bind by name) to the interfaces of,
    +      the Work and Derivative Works thereof.
    +
    +      "Contribution" shall mean any work of authorship, including
    +      the original version of the Work and any modifications or additions
    +      to that Work or Derivative Works thereof, that is intentionally
    +      submitted to Licensor for inclusion in the Work by the copyright owner
    +      or by an individual or Legal Entity authorized to submit on behalf of
    +      the copyright owner. For the purposes of this definition, "submitted"
    +      means any form of electronic, verbal, or written communication sent
    +      to the Licensor or its representatives, including but not limited to
    +      communication on electronic mailing lists, source code control systems,
    +      and issue tracking systems that are managed by, or on behalf of, the
    +      Licensor for the purpose of discussing and improving the Work, but
    +      excluding communication that is conspicuously marked or otherwise
    +      designated in writing by the copyright owner as "Not a Contribution."
    +
    +      "Contributor" shall mean Licensor and any individual or Legal Entity
    +      on behalf of whom a Contribution has been received by Licensor and
    +      subsequently incorporated within the Work.
    +
    +   2. Grant of Copyright License. Subject to the terms and conditions of
    +      this License, each Contributor hereby grants to You a perpetual,
    +      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
    +      copyright license to reproduce, prepare Derivative Works of,
    +      publicly display, publicly perform, sublicense, and distribute the
    +      Work and such Derivative Works in Source or Object form.
    +
    +   3. Grant of Patent License. Subject to the terms and conditions of
    +      this License, each Contributor hereby grants to You a perpetual,
    +      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
    +      (except as stated in this section) patent license to make, have made,
    +      use, offer to sell, sell, import, and otherwise transfer the Work,
    +      where such license applies only to those patent claims licensable
    +      by such Contributor that are necessarily infringed by their
    +      Contribution(s) alone or by combination of their Contribution(s)
    +      with the Work to which such Contribution(s) was submitted. If You
    +      institute patent litigation against any entity (including a
    +      cross-claim or counterclaim in a lawsuit) alleging that the Work
    +      or a Contribution incorporated within the Work constitutes direct
    +      or contributory patent infringement, then any patent licenses
    +      granted to You under this License for that Work shall terminate
    +      as of the date such litigation is filed.
    +
    +   4. Redistribution. You may reproduce and distribute copies of the
    +      Work or Derivative Works thereof in any medium, with or without
    +      modifications, and in Source or Object form, provided that You
    +      meet the following conditions:
    +
    +      (a) You must give any other recipients of the Work or
    +          Derivative Works a copy of this License; and
    +
    +      (b) You must cause any modified files to carry prominent notices
    +          stating that You changed the files; and
    +
    +      (c) You must retain, in the Source form of any Derivative Works
    +          that You distribute, all copyright, patent, trademark, and
    +          attribution notices from the Source form of the Work,
    +          excluding those notices that do not pertain to any part of
    +          the Derivative Works; and
    +
    +      (d) If the Work includes a "NOTICE" text file as part of its
    +          distribution, then any Derivative Works that You distribute must
    +          include a readable copy of the attribution notices contained
    +          within such NOTICE file, excluding those notices that do not
    +          pertain to any part of the Derivative Works, in at least one
    +          of the following places: within a NOTICE text file distributed
    +          as part of the Derivative Works; within the Source form or
    +          documentation, if provided along with the Derivative Works; or,
    +          within a display generated by the Derivative Works, if and
    +          wherever such third-party notices normally appear. The contents
    +          of the NOTICE file are for informational purposes only and
    +          do not modify the License. You may add Your own attribution
    +          notices within Derivative Works that You distribute, alongside
    +          or as an addendum to the NOTICE text from the Work, provided
    +          that such additional attribution notices cannot be construed
    +          as modifying the License.
    +
    +      You may add Your own copyright statement to Your modifications and
    +      may provide additional or different license terms and conditions
    +      for use, reproduction, or distribution of Your modifications, or
    +      for any such Derivative Works as a whole, provided Your use,
    +      reproduction, and distribution of the Work otherwise complies with
    +      the conditions stated in this License.
    +
    +   5. Submission of Contributions. Unless You explicitly state otherwise,
    +      any Contribution intentionally submitted for inclusion in the Work
    +      by You to the Licensor shall be under the terms and conditions of
    +      this License, without any additional terms or conditions.
    +      Notwithstanding the above, nothing herein shall supersede or modify
    +      the terms of any separate license agreement you may have executed
    +      with Licensor regarding such Contributions.
    +
    +   6. Trademarks. This License does not grant permission to use the trade
    +      names, trademarks, service marks, or product names of the Licensor,
    +      except as required for reasonable and customary use in describing the
    +      origin of the Work and reproducing the content of the NOTICE file.
    +
    +   7. Disclaimer of Warranty. Unless required by applicable law or
    +      agreed to in writing, Licensor provides the Work (and each
    +      Contributor provides its Contributions) on an "AS IS" BASIS,
    +      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
    +      implied, including, without limitation, any warranties or conditions
    +      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
    +      PARTICULAR PURPOSE. You are solely responsible for determining the
    +      appropriateness of using or redistributing the Work and assume any
    +      risks associated with Your exercise of permissions under this License.
    +
    +   8. Limitation of Liability. In no event and under no legal theory,
    +      whether in tort (including negligence), contract, or otherwise,
    +      unless required by applicable law (such as deliberate and grossly
    +      negligent acts) or agreed to in writing, shall any Contributor be
    +      liable to You for damages, including any direct, indirect, special,
    +      incidental, or consequential damages of any character arising as a
    +      result of this License or out of the use or inability to use the
    +      Work (including but not limited to damages for loss of goodwill,
    +      work stoppage, computer failure or malfunction, or any and all
    +      other commercial damages or losses), even if such Contributor
    +      has been advised of the possibility of such damages.
    +
    +   9. Accepting Warranty or Additional Liability. While redistributing
    +      the Work or Derivative Works thereof, You may choose to offer,
    +      and charge a fee for, acceptance of support, warranty, indemnity,
    +      or other liability obligations and/or rights consistent with this
    +      License. However, in accepting such obligations, You may act only
    +      on Your own behalf and on Your sole responsibility, not on behalf
    +      of any other Contributor, and only if You agree to indemnify,
    +      defend, and hold each Contributor harmless for any liability
    +      incurred by, or claims asserted against, such Contributor by reason
    +      of your accepting any such warranty or additional liability.
    +
    +   END OF TERMS AND CONDITIONS
    +
    +
    +Copyright (c) Donald Stufft and individual contributors.
    +All rights reserved.
    +
    +Redistribution and use in source and binary forms, with or without
    +modification, are permitted provided that the following conditions are met:
    +
    +    1. Redistributions of source code must retain the above copyright notice,
    +       this list of conditions and the following disclaimer.
    +
    +    2. Redistributions in binary form must reproduce the above copyright
    +       notice, this list of conditions and the following disclaimer in the
    +       documentation and/or other materials provided with the distribution.
    +
    +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
    +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
    +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
    +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.A. HISTORY OF THE SOFTWARE
    +==========================
    +
    +Python was created in the early 1990s by Guido van Rossum at Stichting
    +Mathematisch Centrum (CWI, see https://www.cwi.nl) in the Netherlands
    +as a successor of a language called ABC.  Guido remains Python's
    +principal author, although it includes many contributions from others.
    +
    +In 1995, Guido continued his work on Python at the Corporation for
    +National Research Initiatives (CNRI, see https://www.cnri.reston.va.us)
    +in Reston, Virginia where he released several versions of the
    +software.
    +
    +In May 2000, Guido and the Python core development team moved to
    +BeOpen.com to form the BeOpen PythonLabs team.  In October of the same
    +year, the PythonLabs team moved to Digital Creations, which became
    +Zope Corporation.  In 2001, the Python Software Foundation (PSF, see
    +https://www.python.org/psf/) was formed, a non-profit organization
    +created specifically to own Python-related Intellectual Property.
    +Zope Corporation was a sponsoring member of the PSF.
    +
    +All Python releases are Open Source (see https://opensource.org for
    +the Open Source Definition).  Historically, most, but not all, Python
    +releases have also been GPL-compatible; the table below summarizes
    +the various releases.
    +
    +    Release         Derived     Year        Owner       GPL-
    +                    from                                compatible? (1)
    +
    +    0.9.0 thru 1.2              1991-1995   CWI         yes
    +    1.3 thru 1.5.2  1.2         1995-1999   CNRI        yes
    +    1.6             1.5.2       2000        CNRI        no
    +    2.0             1.6         2000        BeOpen.com  no
    +    1.6.1           1.6         2001        CNRI        yes (2)
    +    2.1             2.0+1.6.1   2001        PSF         no
    +    2.0.1           2.0+1.6.1   2001        PSF         yes
    +    2.1.1           2.1+2.0.1   2001        PSF         yes
    +    2.1.2           2.1.1       2002        PSF         yes
    +    2.1.3           2.1.2       2002        PSF         yes
    +    2.2 and above   2.1.1       2001-now    PSF         yes
    +
    +Footnotes:
    +
    +(1) GPL-compatible doesn't mean that we're distributing Python under
    +    the GPL.  All Python licenses, unlike the GPL, let you distribute
    +    a modified version without making your changes open source.  The
    +    GPL-compatible licenses make it possible to combine Python with
    +    other software that is released under the GPL; the others don't.
    +
    +(2) According to Richard Stallman, 1.6.1 is not GPL-compatible,
    +    because its license has a choice of law clause.  According to
    +    CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1
    +    is "not incompatible" with the GPL.
    +
    +Thanks to the many outside volunteers who have worked under Guido's
    +direction to make these releases possible.
    +
    +
    +B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON
    +===============================================================
    +
    +Python software and documentation are licensed under the
    +Python Software Foundation License Version 2.
    +
    +Starting with Python 3.8.6, examples, recipes, and other code in
    +the documentation are dual licensed under the PSF License Version 2
    +and the Zero-Clause BSD license.
    +
    +Some software incorporated into Python is under different licenses.
    +The licenses are listed with code falling under that license.
    +
    +
    +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
    +--------------------------------------------
    +
    +1. This LICENSE AGREEMENT is between the Python Software Foundation
    +("PSF"), and the Individual or Organization ("Licensee") accessing and
    +otherwise using this software ("Python") in source or binary form and
    +its associated documentation.
    +
    +2. Subject to the terms and conditions of this License Agreement, PSF hereby
    +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
    +analyze, test, perform and/or display publicly, prepare derivative works,
    +distribute, and otherwise use Python alone or in any derivative version,
    +provided, however, that PSF's License Agreement and PSF's notice of copyright,
    +i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
    +2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023 Python Software Foundation;
    +All Rights Reserved" are retained in Python alone or in any derivative version
    +prepared by Licensee.
    +
    +3. In the event Licensee prepares a derivative work that is based on
    +or incorporates Python or any part thereof, and wants to make
    +the derivative work available to others as provided herein, then
    +Licensee hereby agrees to include in any such work a brief summary of
    +the changes made to Python.
    +
    +4. PSF is making Python available to Licensee on an "AS IS"
    +basis.  PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
    +IMPLIED.  BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
    +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
    +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
    +INFRINGE ANY THIRD PARTY RIGHTS.
    +
    +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
    +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
    +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
    +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
    +
    +6. This License Agreement will automatically terminate upon a material
    +breach of its terms and conditions.
    +
    +7. Nothing in this License Agreement shall be deemed to create any
    +relationship of agency, partnership, or joint venture between PSF and
    +Licensee.  This License Agreement does not grant permission to use PSF
    +trademarks or trade name in a trademark sense to endorse or promote
    +products or services of Licensee, or any third party.
    +
    +8. By copying, installing or otherwise using Python, Licensee
    +agrees to be bound by the terms and conditions of this License
    +Agreement.
    +
    +
    +BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0
    +-------------------------------------------
    +
    +BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1
    +
    +1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an
    +office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the
    +Individual or Organization ("Licensee") accessing and otherwise using
    +this software in source or binary form and its associated
    +documentation ("the Software").
    +
    +2. Subject to the terms and conditions of this BeOpen Python License
    +Agreement, BeOpen hereby grants Licensee a non-exclusive,
    +royalty-free, world-wide license to reproduce, analyze, test, perform
    +and/or display publicly, prepare derivative works, distribute, and
    +otherwise use the Software alone or in any derivative version,
    +provided, however, that the BeOpen Python License is retained in the
    +Software, alone or in any derivative version prepared by Licensee.
    +
    +3. BeOpen is making the Software available to Licensee on an "AS IS"
    +basis.  BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
    +IMPLIED.  BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND
    +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
    +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT
    +INFRINGE ANY THIRD PARTY RIGHTS.
    +
    +4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE
    +SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS
    +AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY
    +DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
    +
    +5. This License Agreement will automatically terminate upon a material
    +breach of its terms and conditions.
    +
    +6. This License Agreement shall be governed by and interpreted in all
    +respects by the law of the State of California, excluding conflict of
    +law provisions.  Nothing in this License Agreement shall be deemed to
    +create any relationship of agency, partnership, or joint venture
    +between BeOpen and Licensee.  This License Agreement does not grant
    +permission to use BeOpen trademarks or trade names in a trademark
    +sense to endorse or promote products or services of Licensee, or any
    +third party.  As an exception, the "BeOpen Python" logos available at
    +http://www.pythonlabs.com/logos.html may be used according to the
    +permissions granted on that web page.
    +
    +7. By copying, installing or otherwise using the software, Licensee
    +agrees to be bound by the terms and conditions of this License
    +Agreement.
    +
    +
    +CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1
    +---------------------------------------
    +
    +1. This LICENSE AGREEMENT is between the Corporation for National
    +Research Initiatives, having an office at 1895 Preston White Drive,
    +Reston, VA 20191 ("CNRI"), and the Individual or Organization
    +("Licensee") accessing and otherwise using Python 1.6.1 software in
    +source or binary form and its associated documentation.
    +
    +2. Subject to the terms and conditions of this License Agreement, CNRI
    +hereby grants Licensee a nonexclusive, royalty-free, world-wide
    +license to reproduce, analyze, test, perform and/or display publicly,
    +prepare derivative works, distribute, and otherwise use Python 1.6.1
    +alone or in any derivative version, provided, however, that CNRI's
    +License Agreement and CNRI's notice of copyright, i.e., "Copyright (c)
    +1995-2001 Corporation for National Research Initiatives; All Rights
    +Reserved" are retained in Python 1.6.1 alone or in any derivative
    +version prepared by Licensee.  Alternately, in lieu of CNRI's License
    +Agreement, Licensee may substitute the following text (omitting the
    +quotes): "Python 1.6.1 is made available subject to the terms and
    +conditions in CNRI's License Agreement.  This Agreement together with
    +Python 1.6.1 may be located on the internet using the following
    +unique, persistent identifier (known as a handle): 1895.22/1013.  This
    +Agreement may also be obtained from a proxy server on the internet
    +using the following URL: http://hdl.handle.net/1895.22/1013".
    +
    +3. In the event Licensee prepares a derivative work that is based on
    +or incorporates Python 1.6.1 or any part thereof, and wants to make
    +the derivative work available to others as provided herein, then
    +Licensee hereby agrees to include in any such work a brief summary of
    +the changes made to Python 1.6.1.
    +
    +4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS"
    +basis.  CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
    +IMPLIED.  BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND
    +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
    +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT
    +INFRINGE ANY THIRD PARTY RIGHTS.
    +
    +5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
    +1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
    +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1,
    +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
    +
    +6. This License Agreement will automatically terminate upon a material
    +breach of its terms and conditions.
    +
    +7. This License Agreement shall be governed by the federal
    +intellectual property law of the United States, including without
    +limitation the federal copyright law, and, to the extent such
    +U.S. federal law does not apply, by the law of the Commonwealth of
    +Virginia, excluding Virginia's conflict of law provisions.
    +Notwithstanding the foregoing, with regard to derivative works based
    +on Python 1.6.1 that incorporate non-separable material that was
    +previously distributed under the GNU General Public License (GPL), the
    +law of the Commonwealth of Virginia shall govern this License
    +Agreement only as to issues arising under or with respect to
    +Paragraphs 4, 5, and 7 of this License Agreement.  Nothing in this
    +License Agreement shall be deemed to create any relationship of
    +agency, partnership, or joint venture between CNRI and Licensee.  This
    +License Agreement does not grant permission to use CNRI trademarks or
    +trade name in a trademark sense to endorse or promote products or
    +services of Licensee, or any third party.
    +
    +8. By clicking on the "ACCEPT" button where indicated, or by copying,
    +installing or otherwise using Python 1.6.1, Licensee agrees to be
    +bound by the terms and conditions of this License Agreement.
    +
    +        ACCEPT
    +
    +
    +CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2
    +--------------------------------------------------
    +
    +Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam,
    +The Netherlands.  All rights reserved.
    +
    +Permission to use, copy, modify, and distribute this software and its
    +documentation for any purpose and without fee is hereby granted,
    +provided that the above copyright notice appear in all copies and that
    +both that copyright notice and this permission notice appear in
    +supporting documentation, and that the name of Stichting Mathematisch
    +Centrum or CWI not be used in advertising or publicity pertaining to
    +distribution of the software without specific, written prior
    +permission.
    +
    +STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
    +THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
    +FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
    +FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
    +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
    +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
    +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
    +
    +ZERO-CLAUSE BSD LICENSE FOR CODE IN THE PYTHON DOCUMENTATION
    +----------------------------------------------------------------------
    +
    +Permission to use, copy, modify, and/or distribute this software for any
    +purpose with or without fee is hereby granted.
    +
    +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
    +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
    +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
    +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
    +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
    +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
    +PERFORMANCE OF THIS SOFTWARE.
    +Copyright (c) 2014, Al Sweigart
    +All rights reserved.
    +
    +Redistribution and use in source and binary forms, with or without
    +modification, are permitted provided that the following conditions are met:
    +
    +* Redistributions of source code must retain the above copyright notice, this
    +  list of conditions and the following disclaimer.
    +
    +* Redistributions in binary form must reproduce the above copyright notice,
    +  this list of conditions and the following disclaimer in the documentation
    +  and/or other materials provided with the distribution.
    +
    +* Neither the name of the {organization} nor the names of its
    +  contributors may be used to endorse or promote products derived from
    +  this software without specific prior written permission.
    +
    +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
    +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
    +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.Copyright (c) 2017 Anthony Sottile
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy
    +of this software and associated documentation files (the "Software"), to deal
    +in the Software without restriction, including without limitation the rights
    +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +copies of the Software, and to permit persons to whom the Software is
    +furnished to do so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in
    +all copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +THE SOFTWARE.Copyright (c) 2015-2019 Jared Hobbs
    +
    +Permission is hereby granted, free of charge, to any person obtaining a copy of
    +this software and associated documentation files (the "Software"), to deal in
    +the Software without restriction, including without limitation the rights to
    +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
    +of the Software, and to permit persons to whom the Software is furnished to do
    +so, subject to the following conditions:
    +
    +The above copyright notice and this permission notice shall be included in all
    +copies or substantial portions of the Software.
    +
    +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +SOFTWARE.Developed by ESN, an Electronic Arts Inc. studio.
    +Copyright (c) 2014, Electronic Arts Inc.
    +All rights reserved.
    +
    +Redistribution and use in source and binary forms, with or without
    +modification, are permitted provided that the following conditions are met:
    +* Redistributions of source code must retain the above copyright
    +notice, this list of conditions and the following disclaimer.
    +* Redistributions in binary form must reproduce the above copyright
    +notice, this list of conditions and the following disclaimer in the
    +documentation and/or other materials provided with the distribution.
    +* Neither the name of ESN, Electronic Arts Inc. nor the
    +names of its contributors may be used to endorse or promote products
    +derived from this software without specific prior written permission.
    +
    +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
    +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    +DISCLAIMED. IN NO EVENT SHALL ELECTRONIC ARTS INC. BE LIABLE
    +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
    +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
    +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
    +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    +
    +----
    +
    +Portions of code from MODP_ASCII - Ascii transformations (upper/lower, etc)
    +https://github.com/client9/stringencoders
    +
    +  Copyright 2005, 2006, 2007
    +  Nick Galbreath -- nickg [at] modp [dot] com
    +  All rights reserved.
    +
    +  Redistribution and use in source and binary forms, with or without
    +  modification, are permitted provided that the following conditions are
    +  met:
    +
    +    Redistributions of source code must retain the above copyright
    +    notice, this list of conditions and the following disclaimer.
    +
    +    Redistributions in binary form must reproduce the above copyright
    +    notice, this list of conditions and the following disclaimer in the
    +    documentation and/or other materials provided with the distribution.
    +
    +    Neither the name of the modp.com nor the names of its
    +    contributors may be used to endorse or promote products derived from
    +    this software without specific prior written permission.
    +
    +  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    +  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    +  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    +  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    +  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    +  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    +  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    +  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    +  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    +  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    +  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    +
    +  This is the standard "new" BSD license:
    +  http://www.opensource.org/licenses/bsd-license.php
    +
    +https://github.com/client9/stringencoders/blob/cfd5c1507325ae497ea9bacdacba12c0ffd79d30/COPYING
    +
    +----
    +
    +Numeric decoder derived from from TCL library
    +https://opensource.apple.com/source/tcl/tcl-14/tcl/license.terms
    + * Copyright (c) 1988-1993 The Regents of the University of California.
    + * Copyright (c) 1994 Sun Microsystems, Inc.
    +
    +  This software is copyrighted by the Regents of the University of
    +  California, Sun Microsystems, Inc., Scriptics Corporation, ActiveState
    +  Corporation and other parties.  The following terms apply to all files
    +  associated with the software unless explicitly disclaimed in
    +  individual files.
    +
    +  The authors hereby grant permission to use, copy, modify, distribute,
    +  and license this software and its documentation for any purpose, provided
    +  that existing copyright notices are retained in all copies and that this
    +  notice is included verbatim in any distributions. No written agreement,
    +  license, or royalty fee is required for any of the authorized uses.
    +  Modifications to this software may be copyrighted by their authors
    +  and need not follow the licensing terms described here, provided that
    +  the new terms are clearly indicated on the first page of each file where
    +  they apply.
    +
    +  IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
    +  FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
    +  ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
    +  DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
    +  POSSIBILITY OF SUCH DAMAGE.
    +
    +  THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
    +  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
    +  FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.  THIS SOFTWARE
    +  IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
    +  NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
    +  MODIFICATIONS.
    +
    +  GOVERNMENT USE: If you are acquiring this software on behalf of the
    +  U.S. government, the Government shall have only "Restricted Rights"
    +  in the software and related documentation as defined in the Federal
    +  Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2).  If you
    +  are acquiring the software on behalf of the Department of Defense, the
    +  software shall be classified as "Commercial Computer Software" and the
    +  Government shall have only "Restricted Rights" as defined in Clause
    +  252.227-7013 (c) (1) of DFARs.  Notwithstanding the foregoing, the
    +  authors grant the U.S. Government and others acting in its behalf
    +  permission to use and distribute the software in accordance with the
    +  terms specified in this license.
    \ No newline at end of file
    diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas-3.0.2.dist-info/METADATA b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas-3.0.2.dist-info/METADATA
    new file mode 100644
    index 0000000000000000000000000000000000000000..e9f890dff1aed5b192ff74b522337061a9532659
    --- /dev/null
    +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas-3.0.2.dist-info/METADATA
    @@ -0,0 +1,1383 @@
    +Metadata-Version: 2.1
    +Name: pandas
    +Version: 3.0.2
    +Summary: Powerful data structures for data analysis, time series, and statistics
    +Author-Email: The Pandas Development Team 
    +License: BSD 3-Clause License
    +         
    +         Copyright (c) 2008-2011, AQR Capital Management, LLC, Lambda Foundry, Inc. and PyData Development Team
    +         All rights reserved.
    +         
    +         Copyright (c) 2011-2026, Open source contributors.
    +         
    +         Redistribution and use in source and binary forms, with or without
    +         modification, are permitted provided that the following conditions are met:
    +         
    +         * Redistributions of source code must retain the above copyright notice, this
    +           list of conditions and the following disclaimer.
    +         
    +         * Redistributions in binary form must reproduce the above copyright notice,
    +           this list of conditions and the following disclaimer in the documentation
    +           and/or other materials provided with the distribution.
    +         
    +         * Neither the name of the copyright holder nor the names of its
    +           contributors may be used to endorse or promote products derived from
    +           this software without specific prior written permission.
    +         
    +         THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    +         AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    +         IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    +         DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
    +         FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    +         DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    +         SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    +         CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
    +         OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    +         OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    +         Copyright (c) 2010-2019 Keith Goodman
    +         Copyright (c) 2019 Bottleneck Developers
    +         All rights reserved.
    +         
    +         Redistribution and use in source and binary forms, with or without
    +         modification, are permitted provided that the following conditions are met:
    +         
    +             * Redistributions of source code must retain the above copyright notice,
    +               this list of conditions and the following disclaimer.
    +         
    +             * Redistributions in binary form must reproduce the above copyright
    +               notice, this list of conditions and the following disclaimer in the
    +               documentation and/or other materials provided with the distribution.
    +         
    +         THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    +         AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    +         IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    +         ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    +         LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    +         CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    +         SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    +         INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    +         CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    +         ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    +         POSSIBILITY OF SUCH DAMAGE.Copyright 2017- Paul Ganssle 
    +         Copyright 2017- dateutil contributors (see AUTHORS file)
    +         
    +            Licensed under the Apache License, Version 2.0 (the "License");
    +            you may not use this file except in compliance with the License.
    +            You may obtain a copy of the License at
    +         
    +                http://www.apache.org/licenses/LICENSE-2.0
    +         
    +            Unless required by applicable law or agreed to in writing, software
    +            distributed under the License is distributed on an "AS IS" BASIS,
    +            WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +            See the License for the specific language governing permissions and
    +            limitations under the License.
    +         
    +         The above license applies to all contributions after 2017-12-01, as well as
    +         all contributions that have been re-licensed (see AUTHORS file for the list of
    +         contributors who have re-licensed their code).
    +         --------------------------------------------------------------------------------
    +         dateutil - Extensions to the standard Python datetime module.
    +         
    +         Copyright (c) 2003-2011 - Gustavo Niemeyer 
    +         Copyright (c) 2012-2014 - Tomi Pieviläinen 
    +         Copyright (c) 2014-2016 - Yaron de Leeuw 
    +         Copyright (c) 2015-     - Paul Ganssle 
    +         Copyright (c) 2015-     - dateutil contributors (see AUTHORS file)
    +         
    +         All rights reserved.
    +         
    +         Redistribution and use in source and binary forms, with or without
    +         modification, are permitted provided that the following conditions are met:
    +         
    +             * Redistributions of source code must retain the above copyright notice,
    +               this list of conditions and the following disclaimer.
    +             * Redistributions in binary form must reproduce the above copyright notice,
    +               this list of conditions and the following disclaimer in the documentation
    +               and/or other materials provided with the distribution.
    +             * Neither the name of the copyright holder nor the names of its
    +               contributors may be used to endorse or promote products derived from
    +               this software without specific prior written permission.
    +         
    +         THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    +         "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    +         LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    +         A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
    +         CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
    +         EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
    +         PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
    +         PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
    +         LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
    +         NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
    +         SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    +         
    +         The above BSD License Applies to all code, even that also covered by Apache 2.0.# MIT License
    +         
    +         Copyright (c) 2019 Hadley Wickham; RStudio; and Evan Miller
    +         
    +         Permission is hereby granted, free of charge, to any person obtaining a copy
    +         of this software and associated documentation files (the "Software"), to deal
    +         in the Software without restriction, including without limitation the rights
    +         to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +         copies of the Software, and to permit persons to whom the Software is
    +         furnished to do so, subject to the following conditions:
    +         
    +         The above copyright notice and this permission notice shall be included in all
    +         copies or substantial portions of the Software.
    +         
    +         THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +         IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +         FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +         AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +         LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +         OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +         SOFTWARE.
    +         Based on http://opensource.org/licenses/MIT
    +         
    +         This is a template. Complete and ship as file LICENSE the following 2
    +         lines (only)
    +         
    +         YEAR:
    +         COPYRIGHT HOLDER:
    +         
    +         and specify as
    +         
    +         License: MIT + file LICENSE
    +         
    +         Copyright (c) , 
    +         
    +         Permission is hereby granted, free of charge, to any person obtaining
    +         a copy of this software and associated documentation files (the
    +         "Software"), to deal in the Software without restriction, including
    +         without limitation the rights to use, copy, modify, merge, publish,
    +         distribute, sublicense, and/or sell copies of the Software, and to
    +         permit persons to whom the Software is furnished to do so, subject to
    +         the following conditions:
    +         
    +         The above copyright notice and this permission notice shall be
    +         included in all copies or substantial portions of the Software.
    +         
    +         THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    +         EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    +         MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    +         NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
    +         LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
    +         OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
    +         WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    +         The MIT License
    +         
    +         Copyright (c) 2008-     Attractive Chaos 
    +         
    +         Permission is hereby granted, free of charge, to any person obtaining
    +         a copy of this software and associated documentation files (the
    +         "Software"), to deal in the Software without restriction, including
    +         without limitation the rights to use, copy, modify, merge, publish,
    +         distribute, sublicense, and/or sell copies of the Software, and to
    +         permit persons to whom the Software is furnished to do so, subject to
    +         the following conditions:
    +         
    +         The above copyright notice and this permission notice shall be
    +         included in all copies or substantial portions of the Software.
    +         
    +         THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    +         EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    +         MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
    +         NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
    +         BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
    +         ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
    +         CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +         SOFTWARE.musl as a whole is licensed under the following standard MIT license:
    +         
    +         ----------------------------------------------------------------------
    +         Copyright © 2005-2020 Rich Felker, et al.
    +         
    +         Permission is hereby granted, free of charge, to any person obtaining
    +         a copy of this software and associated documentation files (the
    +         "Software"), to deal in the Software without restriction, including
    +         without limitation the rights to use, copy, modify, merge, publish,
    +         distribute, sublicense, and/or sell copies of the Software, and to
    +         permit persons to whom the Software is furnished to do so, subject to
    +         the following conditions:
    +         
    +         The above copyright notice and this permission notice shall be
    +         included in all copies or substantial portions of the Software.
    +         
    +         THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    +         EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    +         MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    +         IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    +         CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    +         TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    +         SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    +         ----------------------------------------------------------------------
    +         
    +         Authors/contributors include:
    +         
    +         A. Wilcox
    +         Ada Worcester
    +         Alex Dowad
    +         Alex Suykov
    +         Alexander Monakov
    +         Andre McCurdy
    +         Andrew Kelley
    +         Anthony G. Basile
    +         Aric Belsito
    +         Arvid Picciani
    +         Bartosz Brachaczek
    +         Benjamin Peterson
    +         Bobby Bingham
    +         Boris Brezillon
    +         Brent Cook
    +         Chris Spiegel
    +         Clément Vasseur
    +         Daniel Micay
    +         Daniel Sabogal
    +         Daurnimator
    +         David Carlier
    +         David Edelsohn
    +         Denys Vlasenko
    +         Dmitry Ivanov
    +         Dmitry V. Levin
    +         Drew DeVault
    +         Emil Renner Berthing
    +         Fangrui Song
    +         Felix Fietkau
    +         Felix Janda
    +         Gianluca Anzolin
    +         Hauke Mehrtens
    +         He X
    +         Hiltjo Posthuma
    +         Isaac Dunham
    +         Jaydeep Patil
    +         Jens Gustedt
    +         Jeremy Huntwork
    +         Jo-Philipp Wich
    +         Joakim Sindholt
    +         John Spencer
    +         Julien Ramseier
    +         Justin Cormack
    +         Kaarle Ritvanen
    +         Khem Raj
    +         Kylie McClain
    +         Leah Neukirchen
    +         Luca Barbato
    +         Luka Perkov
    +         M Farkas-Dyck (Strake)
    +         Mahesh Bodapati
    +         Markus Wichmann
    +         Masanori Ogino
    +         Michael Clark
    +         Michael Forney
    +         Mikhail Kremnyov
    +         Natanael Copa
    +         Nicholas J. Kain
    +         orc
    +         Pascal Cuoq
    +         Patrick Oppenlander
    +         Petr Hosek
    +         Petr Skocik
    +         Pierre Carrier
    +         Reini Urban
    +         Rich Felker
    +         Richard Pennington
    +         Ryan Fairfax
    +         Samuel Holland
    +         Segev Finer
    +         Shiz
    +         sin
    +         Solar Designer
    +         Stefan Kristiansson
    +         Stefan O'Rear
    +         Szabolcs Nagy
    +         Timo Teräs
    +         Trutz Behn
    +         Valentin Ochs
    +         Will Dietz
    +         William Haddon
    +         William Pitcock
    +         
    +         Portions of this software are derived from third-party works licensed
    +         under terms compatible with the above MIT license:
    +         
    +         The TRE regular expression implementation (src/regex/reg* and
    +         src/regex/tre*) is Copyright © 2001-2008 Ville Laurikari and licensed
    +         under a 2-clause BSD license (license text in the source files). The
    +         included version has been heavily modified by Rich Felker in 2012, in
    +         the interests of size, simplicity, and namespace cleanliness.
    +         
    +         Much of the math library code (src/math/* and src/complex/*) is
    +         Copyright © 1993,2004 Sun Microsystems or
    +         Copyright © 2003-2011 David Schultz or
    +         Copyright © 2003-2009 Steven G. Kargl or
    +         Copyright © 2003-2009 Bruce D. Evans or
    +         Copyright © 2008 Stephen L. Moshier or
    +         Copyright © 2017-2018 Arm Limited
    +         and labelled as such in comments in the individual source files. All
    +         have been licensed under extremely permissive terms.
    +         
    +         The ARM memcpy code (src/string/arm/memcpy.S) is Copyright © 2008
    +         The Android Open Source Project and is licensed under a two-clause BSD
    +         license. It was taken from Bionic libc, used on Android.
    +         
    +         The AArch64 memcpy and memset code (src/string/aarch64/*) are
    +         Copyright © 1999-2019, Arm Limited.
    +         
    +         The implementation of DES for crypt (src/crypt/crypt_des.c) is
    +         Copyright © 1994 David Burren. It is licensed under a BSD license.
    +         
    +         The implementation of blowfish crypt (src/crypt/crypt_blowfish.c) was
    +         originally written by Solar Designer and placed into the public
    +         domain. The code also comes with a fallback permissive license for use
    +         in jurisdictions that may not recognize the public domain.
    +         
    +         The smoothsort implementation (src/stdlib/qsort.c) is Copyright © 2011
    +         Valentin Ochs and is licensed under an MIT-style license.
    +         
    +         The x86_64 port was written by Nicholas J. Kain and is licensed under
    +         the standard MIT terms.
    +         
    +         The mips and microblaze ports were originally written by Richard
    +         Pennington for use in the ellcc project. The original code was adapted
    +         by Rich Felker for build system and code conventions during upstream
    +         integration. It is licensed under the standard MIT terms.
    +         
    +         The mips64 port was contributed by Imagination Technologies and is
    +         licensed under the standard MIT terms.
    +         
    +         The powerpc port was also originally written by Richard Pennington,
    +         and later supplemented and integrated by John Spencer. It is licensed
    +         under the standard MIT terms.
    +         
    +         All other files which have no copyright comments are original works
    +         produced specifically for use as part of this library, written either
    +         by Rich Felker, the main author of the library, or by one or more
    +         contibutors listed above. Details on authorship of individual files
    +         can be found in the git version control history of the project. The
    +         omission of copyright and license comments in each file is in the
    +         interest of source tree size.
    +         
    +         In addition, permission is hereby granted for all public header files
    +         (include/* and arch/*/bits/*) and crt files intended to be linked into
    +         applications (crt/*, ldso/dlstart.c, and arch/*/crt_arch.h) to omit
    +         the copyright notice and permission notice otherwise required by the
    +         license, and to use these files without any requirement of
    +         attribution. These files include substantial contributions from:
    +         
    +         Bobby Bingham
    +         John Spencer
    +         Nicholas J. Kain
    +         Rich Felker
    +         Richard Pennington
    +         Stefan Kristiansson
    +         Szabolcs Nagy
    +         
    +         all of whom have explicitly granted such permission.
    +         
    +         This file previously contained text expressing a belief that most of
    +         the files covered by the above exception were sufficiently trivial not
    +         to be subject to copyright, resulting in confusion over whether it
    +         negated the permissions granted in the license. In the spirit of
    +         permissive licensing, and of not having licensing issues being an
    +         obstacle to adoption, that text has been removed.Copyright (c) 2005-2023, NumPy Developers.
    +         All rights reserved.
    +         
    +         Redistribution and use in source and binary forms, with or without
    +         modification, are permitted provided that the following conditions are
    +         met:
    +         
    +             * Redistributions of source code must retain the above copyright
    +                notice, this list of conditions and the following disclaimer.
    +         
    +             * Redistributions in binary form must reproduce the above
    +                copyright notice, this list of conditions and the following
    +                disclaimer in the documentation and/or other materials provided
    +                with the distribution.
    +         
    +             * Neither the name of the NumPy Developers nor the names of any
    +                contributors may be used to endorse or promote products derived
    +                from this software without specific prior written permission.
    +         
    +         THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    +         "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    +         LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    +         A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    +         OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    +         SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    +         LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    +         DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    +         THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    +         (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    +         OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    +                                          Apache License
    +                                    Version 2.0, January 2004
    +                                 http://www.apache.org/licenses/
    +         
    +            TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
    +         
    +            1. Definitions.
    +         
    +               "License" shall mean the terms and conditions for use, reproduction,
    +               and distribution as defined by Sections 1 through 9 of this document.
    +         
    +               "Licensor" shall mean the copyright owner or entity authorized by
    +               the copyright owner that is granting the License.
    +         
    +               "Legal Entity" shall mean the union of the acting entity and all
    +               other entities that control, are controlled by, or are under common
    +               control with that entity. For the purposes of this definition,
    +               "control" means (i) the power, direct or indirect, to cause the
    +               direction or management of such entity, whether by contract or
    +               otherwise, or (ii) ownership of fifty percent (50%) or more of the
    +               outstanding shares, or (iii) beneficial ownership of such entity.
    +         
    +               "You" (or "Your") shall mean an individual or Legal Entity
    +               exercising permissions granted by this License.
    +         
    +               "Source" form shall mean the preferred form for making modifications,
    +               including but not limited to software source code, documentation
    +               source, and configuration files.
    +         
    +               "Object" form shall mean any form resulting from mechanical
    +               transformation or translation of a Source form, including but
    +               not limited to compiled object code, generated documentation,
    +               and conversions to other media types.
    +         
    +               "Work" shall mean the work of authorship, whether in Source or
    +               Object form, made available under the License, as indicated by a
    +               copyright notice that is included in or attached to the work
    +               (an example is provided in the Appendix below).
    +         
    +               "Derivative Works" shall mean any work, whether in Source or Object
    +               form, that is based on (or derived from) the Work and for which the
    +               editorial revisions, annotations, elaborations, or other modifications
    +               represent, as a whole, an original work of authorship. For the purposes
    +               of this License, Derivative Works shall not include works that remain
    +               separable from, or merely link (or bind by name) to the interfaces of,
    +               the Work and Derivative Works thereof.
    +         
    +               "Contribution" shall mean any work of authorship, including
    +               the original version of the Work and any modifications or additions
    +               to that Work or Derivative Works thereof, that is intentionally
    +               submitted to Licensor for inclusion in the Work by the copyright owner
    +               or by an individual or Legal Entity authorized to submit on behalf of
    +               the copyright owner. For the purposes of this definition, "submitted"
    +               means any form of electronic, verbal, or written communication sent
    +               to the Licensor or its representatives, including but not limited to
    +               communication on electronic mailing lists, source code control systems,
    +               and issue tracking systems that are managed by, or on behalf of, the
    +               Licensor for the purpose of discussing and improving the Work, but
    +               excluding communication that is conspicuously marked or otherwise
    +               designated in writing by the copyright owner as "Not a Contribution."
    +         
    +               "Contributor" shall mean Licensor and any individual or Legal Entity
    +               on behalf of whom a Contribution has been received by Licensor and
    +               subsequently incorporated within the Work.
    +         
    +            2. Grant of Copyright License. Subject to the terms and conditions of
    +               this License, each Contributor hereby grants to You a perpetual,
    +               worldwide, non-exclusive, no-charge, royalty-free, irrevocable
    +               copyright license to reproduce, prepare Derivative Works of,
    +               publicly display, publicly perform, sublicense, and distribute the
    +               Work and such Derivative Works in Source or Object form.
    +         
    +            3. Grant of Patent License. Subject to the terms and conditions of
    +               this License, each Contributor hereby grants to You a perpetual,
    +               worldwide, non-exclusive, no-charge, royalty-free, irrevocable
    +               (except as stated in this section) patent license to make, have made,
    +               use, offer to sell, sell, import, and otherwise transfer the Work,
    +               where such license applies only to those patent claims licensable
    +               by such Contributor that are necessarily infringed by their
    +               Contribution(s) alone or by combination of their Contribution(s)
    +               with the Work to which such Contribution(s) was submitted. If You
    +               institute patent litigation against any entity (including a
    +               cross-claim or counterclaim in a lawsuit) alleging that the Work
    +               or a Contribution incorporated within the Work constitutes direct
    +               or contributory patent infringement, then any patent licenses
    +               granted to You under this License for that Work shall terminate
    +               as of the date such litigation is filed.
    +         
    +            4. Redistribution. You may reproduce and distribute copies of the
    +               Work or Derivative Works thereof in any medium, with or without
    +               modifications, and in Source or Object form, provided that You
    +               meet the following conditions:
    +         
    +               (a) You must give any other recipients of the Work or
    +                   Derivative Works a copy of this License; and
    +         
    +               (b) You must cause any modified files to carry prominent notices
    +                   stating that You changed the files; and
    +         
    +               (c) You must retain, in the Source form of any Derivative Works
    +                   that You distribute, all copyright, patent, trademark, and
    +                   attribution notices from the Source form of the Work,
    +                   excluding those notices that do not pertain to any part of
    +                   the Derivative Works; and
    +         
    +               (d) If the Work includes a "NOTICE" text file as part of its
    +                   distribution, then any Derivative Works that You distribute must
    +                   include a readable copy of the attribution notices contained
    +                   within such NOTICE file, excluding those notices that do not
    +                   pertain to any part of the Derivative Works, in at least one
    +                   of the following places: within a NOTICE text file distributed
    +                   as part of the Derivative Works; within the Source form or
    +                   documentation, if provided along with the Derivative Works; or,
    +                   within a display generated by the Derivative Works, if and
    +                   wherever such third-party notices normally appear. The contents
    +                   of the NOTICE file are for informational purposes only and
    +                   do not modify the License. You may add Your own attribution
    +                   notices within Derivative Works that You distribute, alongside
    +                   or as an addendum to the NOTICE text from the Work, provided
    +                   that such additional attribution notices cannot be construed
    +                   as modifying the License.
    +         
    +               You may add Your own copyright statement to Your modifications and
    +               may provide additional or different license terms and conditions
    +               for use, reproduction, or distribution of Your modifications, or
    +               for any such Derivative Works as a whole, provided Your use,
    +               reproduction, and distribution of the Work otherwise complies with
    +               the conditions stated in this License.
    +         
    +            5. Submission of Contributions. Unless You explicitly state otherwise,
    +               any Contribution intentionally submitted for inclusion in the Work
    +               by You to the Licensor shall be under the terms and conditions of
    +               this License, without any additional terms or conditions.
    +               Notwithstanding the above, nothing herein shall supersede or modify
    +               the terms of any separate license agreement you may have executed
    +               with Licensor regarding such Contributions.
    +         
    +            6. Trademarks. This License does not grant permission to use the trade
    +               names, trademarks, service marks, or product names of the Licensor,
    +               except as required for reasonable and customary use in describing the
    +               origin of the Work and reproducing the content of the NOTICE file.
    +         
    +            7. Disclaimer of Warranty. Unless required by applicable law or
    +               agreed to in writing, Licensor provides the Work (and each
    +               Contributor provides its Contributions) on an "AS IS" BASIS,
    +               WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
    +               implied, including, without limitation, any warranties or conditions
    +               of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
    +               PARTICULAR PURPOSE. You are solely responsible for determining the
    +               appropriateness of using or redistributing the Work and assume any
    +               risks associated with Your exercise of permissions under this License.
    +         
    +            8. Limitation of Liability. In no event and under no legal theory,
    +               whether in tort (including negligence), contract, or otherwise,
    +               unless required by applicable law (such as deliberate and grossly
    +               negligent acts) or agreed to in writing, shall any Contributor be
    +               liable to You for damages, including any direct, indirect, special,
    +               incidental, or consequential damages of any character arising as a
    +               result of this License or out of the use or inability to use the
    +               Work (including but not limited to damages for loss of goodwill,
    +               work stoppage, computer failure or malfunction, or any and all
    +               other commercial damages or losses), even if such Contributor
    +               has been advised of the possibility of such damages.
    +         
    +            9. Accepting Warranty or Additional Liability. While redistributing
    +               the Work or Derivative Works thereof, You may choose to offer,
    +               and charge a fee for, acceptance of support, warranty, indemnity,
    +               or other liability obligations and/or rights consistent with this
    +               License. However, in accepting such obligations, You may act only
    +               on Your own behalf and on Your sole responsibility, not on behalf
    +               of any other Contributor, and only if You agree to indemnify,
    +               defend, and hold each Contributor harmless for any liability
    +               incurred by, or claims asserted against, such Contributor by reason
    +               of your accepting any such warranty or additional liability.
    +         
    +            END OF TERMS AND CONDITIONS
    +         
    +         
    +         Copyright (c) Donald Stufft and individual contributors.
    +         All rights reserved.
    +         
    +         Redistribution and use in source and binary forms, with or without
    +         modification, are permitted provided that the following conditions are met:
    +         
    +             1. Redistributions of source code must retain the above copyright notice,
    +                this list of conditions and the following disclaimer.
    +         
    +             2. Redistributions in binary form must reproduce the above copyright
    +                notice, this list of conditions and the following disclaimer in the
    +                documentation and/or other materials provided with the distribution.
    +         
    +         THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
    +         ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    +         WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    +         DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
    +         FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    +         DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    +         SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    +         CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
    +         OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    +         OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.A. HISTORY OF THE SOFTWARE
    +         ==========================
    +         
    +         Python was created in the early 1990s by Guido van Rossum at Stichting
    +         Mathematisch Centrum (CWI, see https://www.cwi.nl) in the Netherlands
    +         as a successor of a language called ABC.  Guido remains Python's
    +         principal author, although it includes many contributions from others.
    +         
    +         In 1995, Guido continued his work on Python at the Corporation for
    +         National Research Initiatives (CNRI, see https://www.cnri.reston.va.us)
    +         in Reston, Virginia where he released several versions of the
    +         software.
    +         
    +         In May 2000, Guido and the Python core development team moved to
    +         BeOpen.com to form the BeOpen PythonLabs team.  In October of the same
    +         year, the PythonLabs team moved to Digital Creations, which became
    +         Zope Corporation.  In 2001, the Python Software Foundation (PSF, see
    +         https://www.python.org/psf/) was formed, a non-profit organization
    +         created specifically to own Python-related Intellectual Property.
    +         Zope Corporation was a sponsoring member of the PSF.
    +         
    +         All Python releases are Open Source (see https://opensource.org for
    +         the Open Source Definition).  Historically, most, but not all, Python
    +         releases have also been GPL-compatible; the table below summarizes
    +         the various releases.
    +         
    +             Release         Derived     Year        Owner       GPL-
    +                             from                                compatible? (1)
    +         
    +             0.9.0 thru 1.2              1991-1995   CWI         yes
    +             1.3 thru 1.5.2  1.2         1995-1999   CNRI        yes
    +             1.6             1.5.2       2000        CNRI        no
    +             2.0             1.6         2000        BeOpen.com  no
    +             1.6.1           1.6         2001        CNRI        yes (2)
    +             2.1             2.0+1.6.1   2001        PSF         no
    +             2.0.1           2.0+1.6.1   2001        PSF         yes
    +             2.1.1           2.1+2.0.1   2001        PSF         yes
    +             2.1.2           2.1.1       2002        PSF         yes
    +             2.1.3           2.1.2       2002        PSF         yes
    +             2.2 and above   2.1.1       2001-now    PSF         yes
    +         
    +         Footnotes:
    +         
    +         (1) GPL-compatible doesn't mean that we're distributing Python under
    +             the GPL.  All Python licenses, unlike the GPL, let you distribute
    +             a modified version without making your changes open source.  The
    +             GPL-compatible licenses make it possible to combine Python with
    +             other software that is released under the GPL; the others don't.
    +         
    +         (2) According to Richard Stallman, 1.6.1 is not GPL-compatible,
    +             because its license has a choice of law clause.  According to
    +             CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1
    +             is "not incompatible" with the GPL.
    +         
    +         Thanks to the many outside volunteers who have worked under Guido's
    +         direction to make these releases possible.
    +         
    +         
    +         B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON
    +         ===============================================================
    +         
    +         Python software and documentation are licensed under the
    +         Python Software Foundation License Version 2.
    +         
    +         Starting with Python 3.8.6, examples, recipes, and other code in
    +         the documentation are dual licensed under the PSF License Version 2
    +         and the Zero-Clause BSD license.
    +         
    +         Some software incorporated into Python is under different licenses.
    +         The licenses are listed with code falling under that license.
    +         
    +         
    +         PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
    +         --------------------------------------------
    +         
    +         1. This LICENSE AGREEMENT is between the Python Software Foundation
    +         ("PSF"), and the Individual or Organization ("Licensee") accessing and
    +         otherwise using this software ("Python") in source or binary form and
    +         its associated documentation.
    +         
    +         2. Subject to the terms and conditions of this License Agreement, PSF hereby
    +         grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
    +         analyze, test, perform and/or display publicly, prepare derivative works,
    +         distribute, and otherwise use Python alone or in any derivative version,
    +         provided, however, that PSF's License Agreement and PSF's notice of copyright,
    +         i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
    +         2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023 Python Software Foundation;
    +         All Rights Reserved" are retained in Python alone or in any derivative version
    +         prepared by Licensee.
    +         
    +         3. In the event Licensee prepares a derivative work that is based on
    +         or incorporates Python or any part thereof, and wants to make
    +         the derivative work available to others as provided herein, then
    +         Licensee hereby agrees to include in any such work a brief summary of
    +         the changes made to Python.
    +         
    +         4. PSF is making Python available to Licensee on an "AS IS"
    +         basis.  PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
    +         IMPLIED.  BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
    +         DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
    +         FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
    +         INFRINGE ANY THIRD PARTY RIGHTS.
    +         
    +         5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
    +         FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
    +         A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
    +         OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
    +         
    +         6. This License Agreement will automatically terminate upon a material
    +         breach of its terms and conditions.
    +         
    +         7. Nothing in this License Agreement shall be deemed to create any
    +         relationship of agency, partnership, or joint venture between PSF and
    +         Licensee.  This License Agreement does not grant permission to use PSF
    +         trademarks or trade name in a trademark sense to endorse or promote
    +         products or services of Licensee, or any third party.
    +         
    +         8. By copying, installing or otherwise using Python, Licensee
    +         agrees to be bound by the terms and conditions of this License
    +         Agreement.
    +         
    +         
    +         BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0
    +         -------------------------------------------
    +         
    +         BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1
    +         
    +         1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an
    +         office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the
    +         Individual or Organization ("Licensee") accessing and otherwise using
    +         this software in source or binary form and its associated
    +         documentation ("the Software").
    +         
    +         2. Subject to the terms and conditions of this BeOpen Python License
    +         Agreement, BeOpen hereby grants Licensee a non-exclusive,
    +         royalty-free, world-wide license to reproduce, analyze, test, perform
    +         and/or display publicly, prepare derivative works, distribute, and
    +         otherwise use the Software alone or in any derivative version,
    +         provided, however, that the BeOpen Python License is retained in the
    +         Software, alone or in any derivative version prepared by Licensee.
    +         
    +         3. BeOpen is making the Software available to Licensee on an "AS IS"
    +         basis.  BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
    +         IMPLIED.  BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND
    +         DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
    +         FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT
    +         INFRINGE ANY THIRD PARTY RIGHTS.
    +         
    +         4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE
    +         SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS
    +         AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY
    +         DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
    +         
    +         5. This License Agreement will automatically terminate upon a material
    +         breach of its terms and conditions.
    +         
    +         6. This License Agreement shall be governed by and interpreted in all
    +         respects by the law of the State of California, excluding conflict of
    +         law provisions.  Nothing in this License Agreement shall be deemed to
    +         create any relationship of agency, partnership, or joint venture
    +         between BeOpen and Licensee.  This License Agreement does not grant
    +         permission to use BeOpen trademarks or trade names in a trademark
    +         sense to endorse or promote products or services of Licensee, or any
    +         third party.  As an exception, the "BeOpen Python" logos available at
    +         http://www.pythonlabs.com/logos.html may be used according to the
    +         permissions granted on that web page.
    +         
    +         7. By copying, installing or otherwise using the software, Licensee
    +         agrees to be bound by the terms and conditions of this License
    +         Agreement.
    +         
    +         
    +         CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1
    +         ---------------------------------------
    +         
    +         1. This LICENSE AGREEMENT is between the Corporation for National
    +         Research Initiatives, having an office at 1895 Preston White Drive,
    +         Reston, VA 20191 ("CNRI"), and the Individual or Organization
    +         ("Licensee") accessing and otherwise using Python 1.6.1 software in
    +         source or binary form and its associated documentation.
    +         
    +         2. Subject to the terms and conditions of this License Agreement, CNRI
    +         hereby grants Licensee a nonexclusive, royalty-free, world-wide
    +         license to reproduce, analyze, test, perform and/or display publicly,
    +         prepare derivative works, distribute, and otherwise use Python 1.6.1
    +         alone or in any derivative version, provided, however, that CNRI's
    +         License Agreement and CNRI's notice of copyright, i.e., "Copyright (c)
    +         1995-2001 Corporation for National Research Initiatives; All Rights
    +         Reserved" are retained in Python 1.6.1 alone or in any derivative
    +         version prepared by Licensee.  Alternately, in lieu of CNRI's License
    +         Agreement, Licensee may substitute the following text (omitting the
    +         quotes): "Python 1.6.1 is made available subject to the terms and
    +         conditions in CNRI's License Agreement.  This Agreement together with
    +         Python 1.6.1 may be located on the internet using the following
    +         unique, persistent identifier (known as a handle): 1895.22/1013.  This
    +         Agreement may also be obtained from a proxy server on the internet
    +         using the following URL: http://hdl.handle.net/1895.22/1013".
    +         
    +         3. In the event Licensee prepares a derivative work that is based on
    +         or incorporates Python 1.6.1 or any part thereof, and wants to make
    +         the derivative work available to others as provided herein, then
    +         Licensee hereby agrees to include in any such work a brief summary of
    +         the changes made to Python 1.6.1.
    +         
    +         4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS"
    +         basis.  CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
    +         IMPLIED.  BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND
    +         DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
    +         FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT
    +         INFRINGE ANY THIRD PARTY RIGHTS.
    +         
    +         5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
    +         1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
    +         A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1,
    +         OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
    +         
    +         6. This License Agreement will automatically terminate upon a material
    +         breach of its terms and conditions.
    +         
    +         7. This License Agreement shall be governed by the federal
    +         intellectual property law of the United States, including without
    +         limitation the federal copyright law, and, to the extent such
    +         U.S. federal law does not apply, by the law of the Commonwealth of
    +         Virginia, excluding Virginia's conflict of law provisions.
    +         Notwithstanding the foregoing, with regard to derivative works based
    +         on Python 1.6.1 that incorporate non-separable material that was
    +         previously distributed under the GNU General Public License (GPL), the
    +         law of the Commonwealth of Virginia shall govern this License
    +         Agreement only as to issues arising under or with respect to
    +         Paragraphs 4, 5, and 7 of this License Agreement.  Nothing in this
    +         License Agreement shall be deemed to create any relationship of
    +         agency, partnership, or joint venture between CNRI and Licensee.  This
    +         License Agreement does not grant permission to use CNRI trademarks or
    +         trade name in a trademark sense to endorse or promote products or
    +         services of Licensee, or any third party.
    +         
    +         8. By clicking on the "ACCEPT" button where indicated, or by copying,
    +         installing or otherwise using Python 1.6.1, Licensee agrees to be
    +         bound by the terms and conditions of this License Agreement.
    +         
    +                 ACCEPT
    +         
    +         
    +         CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2
    +         --------------------------------------------------
    +         
    +         Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam,
    +         The Netherlands.  All rights reserved.
    +         
    +         Permission to use, copy, modify, and distribute this software and its
    +         documentation for any purpose and without fee is hereby granted,
    +         provided that the above copyright notice appear in all copies and that
    +         both that copyright notice and this permission notice appear in
    +         supporting documentation, and that the name of Stichting Mathematisch
    +         Centrum or CWI not be used in advertising or publicity pertaining to
    +         distribution of the software without specific, written prior
    +         permission.
    +         
    +         STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
    +         THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
    +         FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
    +         FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
    +         WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
    +         ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
    +         OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
    +         
    +         ZERO-CLAUSE BSD LICENSE FOR CODE IN THE PYTHON DOCUMENTATION
    +         ----------------------------------------------------------------------
    +         
    +         Permission to use, copy, modify, and/or distribute this software for any
    +         purpose with or without fee is hereby granted.
    +         
    +         THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
    +         REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
    +         AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
    +         INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
    +         LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
    +         OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
    +         PERFORMANCE OF THIS SOFTWARE.
    +         Copyright (c) 2014, Al Sweigart
    +         All rights reserved.
    +         
    +         Redistribution and use in source and binary forms, with or without
    +         modification, are permitted provided that the following conditions are met:
    +         
    +         * Redistributions of source code must retain the above copyright notice, this
    +           list of conditions and the following disclaimer.
    +         
    +         * Redistributions in binary form must reproduce the above copyright notice,
    +           this list of conditions and the following disclaimer in the documentation
    +           and/or other materials provided with the distribution.
    +         
    +         * Neither the name of the {organization} nor the names of its
    +           contributors may be used to endorse or promote products derived from
    +           this software without specific prior written permission.
    +         
    +         THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    +         AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    +         IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    +         DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
    +         FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    +         DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    +         SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    +         CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
    +         OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    +         OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.Copyright (c) 2017 Anthony Sottile
    +         
    +         Permission is hereby granted, free of charge, to any person obtaining a copy
    +         of this software and associated documentation files (the "Software"), to deal
    +         in the Software without restriction, including without limitation the rights
    +         to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    +         copies of the Software, and to permit persons to whom the Software is
    +         furnished to do so, subject to the following conditions:
    +         
    +         The above copyright notice and this permission notice shall be included in
    +         all copies or substantial portions of the Software.
    +         
    +         THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +         IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +         FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +         AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +         LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +         OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    +         THE SOFTWARE.Copyright (c) 2015-2019 Jared Hobbs
    +         
    +         Permission is hereby granted, free of charge, to any person obtaining a copy of
    +         this software and associated documentation files (the "Software"), to deal in
    +         the Software without restriction, including without limitation the rights to
    +         use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
    +         of the Software, and to permit persons to whom the Software is furnished to do
    +         so, subject to the following conditions:
    +         
    +         The above copyright notice and this permission notice shall be included in all
    +         copies or substantial portions of the Software.
    +         
    +         THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    +         IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    +         FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    +         AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    +         LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    +         OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    +         SOFTWARE.Developed by ESN, an Electronic Arts Inc. studio.
    +         Copyright (c) 2014, Electronic Arts Inc.
    +         All rights reserved.
    +         
    +         Redistribution and use in source and binary forms, with or without
    +         modification, are permitted provided that the following conditions are met:
    +         * Redistributions of source code must retain the above copyright
    +         notice, this list of conditions and the following disclaimer.
    +         * Redistributions in binary form must reproduce the above copyright
    +         notice, this list of conditions and the following disclaimer in the
    +         documentation and/or other materials provided with the distribution.
    +         * Neither the name of ESN, Electronic Arts Inc. nor the
    +         names of its contributors may be used to endorse or promote products
    +         derived from this software without specific prior written permission.
    +         
    +         THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
    +         ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    +         WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    +         DISCLAIMED. IN NO EVENT SHALL ELECTRONIC ARTS INC. BE LIABLE
    +         FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    +         (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
    +         LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
    +         ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    +         (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
    +         SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    +         
    +         ----
    +         
    +         Portions of code from MODP_ASCII - Ascii transformations (upper/lower, etc)
    +         https://github.com/client9/stringencoders
    +         
    +           Copyright 2005, 2006, 2007
    +           Nick Galbreath -- nickg [at] modp [dot] com
    +           All rights reserved.
    +         
    +           Redistribution and use in source and binary forms, with or without
    +           modification, are permitted provided that the following conditions are
    +           met:
    +         
    +             Redistributions of source code must retain the above copyright
    +             notice, this list of conditions and the following disclaimer.
    +         
    +             Redistributions in binary form must reproduce the above copyright
    +             notice, this list of conditions and the following disclaimer in the
    +             documentation and/or other materials provided with the distribution.
    +         
    +             Neither the name of the modp.com nor the names of its
    +             contributors may be used to endorse or promote products derived from
    +             this software without specific prior written permission.
    +         
    +           THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    +           "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    +           LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    +           A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    +           OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    +           SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    +           LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    +           DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    +           THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    +           (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    +           OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    +         
    +           This is the standard "new" BSD license:
    +           http://www.opensource.org/licenses/bsd-license.php
    +         
    +         https://github.com/client9/stringencoders/blob/cfd5c1507325ae497ea9bacdacba12c0ffd79d30/COPYING
    +         
    +         ----
    +         
    +         Numeric decoder derived from from TCL library
    +         https://opensource.apple.com/source/tcl/tcl-14/tcl/license.terms
    +          * Copyright (c) 1988-1993 The Regents of the University of California.
    +          * Copyright (c) 1994 Sun Microsystems, Inc.
    +         
    +           This software is copyrighted by the Regents of the University of
    +           California, Sun Microsystems, Inc., Scriptics Corporation, ActiveState
    +           Corporation and other parties.  The following terms apply to all files
    +           associated with the software unless explicitly disclaimed in
    +           individual files.
    +         
    +           The authors hereby grant permission to use, copy, modify, distribute,
    +           and license this software and its documentation for any purpose, provided
    +           that existing copyright notices are retained in all copies and that this
    +           notice is included verbatim in any distributions. No written agreement,
    +           license, or royalty fee is required for any of the authorized uses.
    +           Modifications to this software may be copyrighted by their authors
    +           and need not follow the licensing terms described here, provided that
    +           the new terms are clearly indicated on the first page of each file where
    +           they apply.
    +         
    +           IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY
    +           FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
    +           ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY
    +           DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE
    +           POSSIBILITY OF SUCH DAMAGE.
    +         
    +           THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
    +           INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,
    +           FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.  THIS SOFTWARE
    +           IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE
    +           NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
    +           MODIFICATIONS.
    +         
    +           GOVERNMENT USE: If you are acquiring this software on behalf of the
    +           U.S. government, the Government shall have only "Restricted Rights"
    +           in the software and related documentation as defined in the Federal
    +           Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2).  If you
    +           are acquiring the software on behalf of the Department of Defense, the
    +           software shall be classified as "Commercial Computer Software" and the
    +           Government shall have only "Restricted Rights" as defined in Clause
    +           252.227-7013 (c) (1) of DFARs.  Notwithstanding the foregoing, the
    +           authors grant the U.S. Government and others acting in its behalf
    +           permission to use and distribute the software in accordance with the
    +           terms specified in this license.
    +Classifier: Development Status :: 5 - Production/Stable
    +Classifier: Environment :: Console
    +Classifier: Intended Audience :: Science/Research
    +Classifier: License :: OSI Approved :: BSD License
    +Classifier: Operating System :: OS Independent
    +Classifier: Programming Language :: Cython
    +Classifier: Programming Language :: Python
    +Classifier: Programming Language :: Python :: 3
    +Classifier: Programming Language :: Python :: 3 :: Only
    +Classifier: Programming Language :: Python :: 3.11
    +Classifier: Programming Language :: Python :: 3.12
    +Classifier: Programming Language :: Python :: 3.13
    +Classifier: Programming Language :: Python :: 3.14
    +Classifier: Topic :: Scientific/Engineering
    +Project-URL: homepage, https://pandas.pydata.org
    +Project-URL: documentation, https://pandas.pydata.org/docs/
    +Project-URL: repository, https://github.com/pandas-dev/pandas
    +Requires-Python: >=3.11
    +Requires-Dist: numpy>=1.26.0; python_version < "3.14"
    +Requires-Dist: numpy>=2.3.3; python_version >= "3.14"
    +Requires-Dist: python-dateutil>=2.8.2
    +Requires-Dist: tzdata; sys_platform == "win32"
    +Requires-Dist: tzdata; sys_platform == "emscripten"
    +Provides-Extra: test
    +Requires-Dist: hypothesis>=6.116.0; extra == "test"
    +Requires-Dist: pytest>=8.3.4; extra == "test"
    +Requires-Dist: pytest-xdist>=3.6.1; extra == "test"
    +Provides-Extra: pyarrow
    +Requires-Dist: pyarrow>=13.0.0; extra == "pyarrow"
    +Provides-Extra: performance
    +Requires-Dist: bottleneck>=1.4.2; extra == "performance"
    +Requires-Dist: numba>=0.60.0; extra == "performance"
    +Requires-Dist: numexpr>=2.10.2; extra == "performance"
    +Provides-Extra: computation
    +Requires-Dist: scipy>=1.14.1; extra == "computation"
    +Requires-Dist: xarray>=2024.10.0; extra == "computation"
    +Provides-Extra: fss
    +Requires-Dist: fsspec>=2024.10.0; extra == "fss"
    +Provides-Extra: aws
    +Requires-Dist: s3fs>=2024.10.0; extra == "aws"
    +Provides-Extra: gcp
    +Requires-Dist: gcsfs>=2024.10.0; extra == "gcp"
    +Provides-Extra: excel
    +Requires-Dist: odfpy>=1.4.1; extra == "excel"
    +Requires-Dist: openpyxl>=3.1.5; extra == "excel"
    +Requires-Dist: python-calamine>=0.3.0; extra == "excel"
    +Requires-Dist: pyxlsb>=1.0.10; extra == "excel"
    +Requires-Dist: xlrd>=2.0.1; extra == "excel"
    +Requires-Dist: xlsxwriter>=3.2.0; extra == "excel"
    +Provides-Extra: parquet
    +Requires-Dist: pyarrow>=13.0.0; extra == "parquet"
    +Provides-Extra: feather
    +Requires-Dist: pyarrow>=13.0.0; extra == "feather"
    +Provides-Extra: iceberg
    +Requires-Dist: pyiceberg>=0.8.1; extra == "iceberg"
    +Provides-Extra: hdf5
    +Requires-Dist: tables>=3.10.1; extra == "hdf5"
    +Provides-Extra: spss
    +Requires-Dist: pyreadstat>=1.2.8; extra == "spss"
    +Provides-Extra: postgresql
    +Requires-Dist: SQLAlchemy>=2.0.36; extra == "postgresql"
    +Requires-Dist: psycopg2>=2.9.10; extra == "postgresql"
    +Requires-Dist: adbc-driver-postgresql>=1.2.0; extra == "postgresql"
    +Provides-Extra: mysql
    +Requires-Dist: SQLAlchemy>=2.0.36; extra == "mysql"
    +Requires-Dist: pymysql>=1.1.1; extra == "mysql"
    +Provides-Extra: sql-other
    +Requires-Dist: SQLAlchemy>=2.0.36; extra == "sql-other"
    +Requires-Dist: adbc-driver-postgresql>=1.2.0; extra == "sql-other"
    +Requires-Dist: adbc-driver-sqlite>=1.2.0; extra == "sql-other"
    +Provides-Extra: html
    +Requires-Dist: beautifulsoup4>=4.12.3; extra == "html"
    +Requires-Dist: html5lib>=1.1; extra == "html"
    +Requires-Dist: lxml>=5.3.0; extra == "html"
    +Provides-Extra: xml
    +Requires-Dist: lxml>=5.3.0; extra == "xml"
    +Provides-Extra: plot
    +Requires-Dist: matplotlib>=3.9.3; extra == "plot"
    +Provides-Extra: output-formatting
    +Requires-Dist: jinja2>=3.1.5; extra == "output-formatting"
    +Requires-Dist: tabulate>=0.9.0; extra == "output-formatting"
    +Provides-Extra: clipboard
    +Requires-Dist: PyQt5>=5.15.9; extra == "clipboard"
    +Requires-Dist: qtpy>=2.4.2; extra == "clipboard"
    +Provides-Extra: compression
    +Requires-Dist: zstandard>=0.23.0; extra == "compression"
    +Provides-Extra: timezone
    +Requires-Dist: pytz>=2024.2; extra == "timezone"
    +Provides-Extra: all
    +Requires-Dist: adbc-driver-postgresql>=1.2.0; extra == "all"
    +Requires-Dist: adbc-driver-sqlite>=1.2.0; extra == "all"
    +Requires-Dist: beautifulsoup4>=4.12.3; extra == "all"
    +Requires-Dist: bottleneck>=1.4.2; extra == "all"
    +Requires-Dist: fastparquet>=2024.11.0; extra == "all"
    +Requires-Dist: fsspec>=2024.10.0; extra == "all"
    +Requires-Dist: gcsfs>=2024.10.0; extra == "all"
    +Requires-Dist: html5lib>=1.1; extra == "all"
    +Requires-Dist: hypothesis>=6.116.0; extra == "all"
    +Requires-Dist: jinja2>=3.1.5; extra == "all"
    +Requires-Dist: lxml>=5.3.0; extra == "all"
    +Requires-Dist: matplotlib>=3.9.3; extra == "all"
    +Requires-Dist: numba>=0.60.0; extra == "all"
    +Requires-Dist: numexpr>=2.10.2; extra == "all"
    +Requires-Dist: odfpy>=1.4.1; extra == "all"
    +Requires-Dist: openpyxl>=3.1.5; extra == "all"
    +Requires-Dist: psycopg2>=2.9.10; extra == "all"
    +Requires-Dist: pyarrow>=13.0.0; extra == "all"
    +Requires-Dist: pyiceberg>=0.8.1; extra == "all"
    +Requires-Dist: pymysql>=1.1.1; extra == "all"
    +Requires-Dist: PyQt5>=5.15.9; extra == "all"
    +Requires-Dist: pyreadstat>=1.2.8; extra == "all"
    +Requires-Dist: pytest>=8.3.4; extra == "all"
    +Requires-Dist: pytest-xdist>=3.6.1; extra == "all"
    +Requires-Dist: python-calamine>=0.3.0; extra == "all"
    +Requires-Dist: pytz>=2024.2; extra == "all"
    +Requires-Dist: pyxlsb>=1.0.10; extra == "all"
    +Requires-Dist: qtpy>=2.4.2; extra == "all"
    +Requires-Dist: scipy>=1.14.1; extra == "all"
    +Requires-Dist: s3fs>=2024.10.0; extra == "all"
    +Requires-Dist: SQLAlchemy>=2.0.36; extra == "all"
    +Requires-Dist: tables>=3.10.1; extra == "all"
    +Requires-Dist: tabulate>=0.9.0; extra == "all"
    +Requires-Dist: xarray>=2024.10.0; extra == "all"
    +Requires-Dist: xlrd>=2.0.1; extra == "all"
    +Requires-Dist: xlsxwriter>=3.2.0; extra == "all"
    +Requires-Dist: zstandard>=0.23.0; extra == "all"
    +Description-Content-Type: text/markdown
    +
    +
    +  
    +  Pandas Logo
    +
    +
    +-----------------
    +
    +# pandas: A Powerful Python Data Analysis Toolkit
    +
    +| | |
    +| --- | --- |
    +| Testing | [![CI - Test](https://github.com/pandas-dev/pandas/actions/workflows/unit-tests.yml/badge.svg)](https://github.com/pandas-dev/pandas/actions/workflows/unit-tests.yml) [![Coverage](https://codecov.io/github/pandas-dev/pandas/coverage.svg?branch=main)](https://codecov.io/gh/pandas-dev/pandas) |
    +| Package | [![PyPI Latest Release](https://img.shields.io/pypi/v/pandas.svg)](https://pypi.org/project/pandas/) [![PyPI Downloads](https://img.shields.io/pypi/dm/pandas.svg?label=PyPI%20downloads)](https://pypi.org/project/pandas/) [![Conda Latest Release](https://anaconda.org/conda-forge/pandas/badges/version.svg)](https://anaconda.org/conda-forge/pandas) [![Conda Downloads](https://img.shields.io/conda/dn/conda-forge/pandas.svg?label=Conda%20downloads)](https://anaconda.org/conda-forge/pandas) |
    +| Meta | [![Powered by NumFOCUS](https://img.shields.io/badge/powered%20by-NumFOCUS-orange.svg?style=flat&colorA=E1523D&colorB=007D8A)](https://numfocus.org) [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.3509134.svg)](https://doi.org/10.5281/zenodo.3509134) [![License - BSD 3-Clause](https://img.shields.io/pypi/l/pandas.svg)](https://github.com/pandas-dev/pandas/blob/main/LICENSE) [![Slack](https://img.shields.io/badge/join_Slack-information-brightgreen.svg?logo=slack)](https://pandas.pydata.org/docs/dev/development/community.html?highlight=slack#community-slack) [![LFX Health Score](https://insights.linuxfoundation.org/api/badge/health-score?project=pandas-dev-pandas)](https://insights.linuxfoundation.org/project/pandas-dev-pandas) |
    +
    +
    +## What is it?
    +
    +**pandas** is a Python package that provides fast, flexible, and expressive data
    +structures designed to make working with "relational" or "labeled" data both
    +easy and intuitive. It aims to be the fundamental high-level building block for
    +doing practical, **real-world** data analysis in Python. Additionally, it has
    +the broader goal of becoming **the most powerful and flexible open-source data
    +analysis/manipulation tool available in any language**. It is already well on
    +its way towards this goal.
    +
    +## Table of Contents
    +
    +- [Main Features](#main-features)
    +- [Where to get it](#where-to-get-it)
    +- [Dependencies](#dependencies)
    +- [Installation from sources](#installation-from-sources)
    +- [License](#license)
    +- [Documentation](#documentation)
    +- [Background](#background)
    +- [Getting Help](#getting-help)
    +- [Discussion and Development](#discussion-and-development)
    +- [Contributing to pandas](#contributing-to-pandas)
    +
    +## Main Features
    +Here are just a few of the things that pandas does well:
    +
    +  - Easy handling of [**missing data**][missing-data] (represented as
    +    `NaN`, `NA`, or `NaT`) in floating point as well as non-floating point data
    +  - Size mutability: columns can be [**inserted and
    +    deleted**][insertion-deletion] from DataFrame and higher dimensional
    +    objects
    +  - Automatic and explicit [**data alignment**][alignment]: objects can
    +    be explicitly aligned to a set of labels, or the user can simply
    +    ignore the labels and let `Series`, `DataFrame`, etc. automatically
    +    align the data for you in computations
    +  - Powerful, flexible [**group by**][groupby] functionality to perform
    +    split-apply-combine operations on data sets, for both aggregating
    +    and transforming data
    +  - Make it [**easy to convert**][conversion] ragged,
    +    differently-indexed data in other Python and NumPy data structures
    +    into DataFrame objects
    +  - Intelligent label-based [**slicing**][slicing], [**fancy
    +    indexing**][fancy-indexing], and [**subsetting**][subsetting] of
    +    large data sets
    +  - Intuitive [**merging**][merging] and [**joining**][joining] data
    +    sets
    +  - Flexible [**reshaping**][reshape] and [**pivoting**][pivot-table] of
    +    data sets
    +  - [**Hierarchical**][mi] labeling of axes (possible to have multiple
    +    labels per tick)
    +  - Robust I/O tools for loading data from [**flat files**][flat-files]
    +    (CSV and delimited), [**Excel files**][excel], [**databases**][db],
    +    and saving/loading data from the ultrafast [**HDF5 format**][hdfstore]
    +  - [**Time series**][timeseries]-specific functionality: date range
    +    generation and frequency conversion, moving window statistics,
    +    date shifting and lagging
    +
    +
    +   [missing-data]: https://pandas.pydata.org/pandas-docs/stable/user_guide/missing_data.html
    +   [insertion-deletion]: https://pandas.pydata.org/pandas-docs/stable/user_guide/dsintro.html#column-selection-addition-deletion
    +   [alignment]: https://pandas.pydata.org/pandas-docs/stable/user_guide/dsintro.html?highlight=alignment#intro-to-data-structures
    +   [groupby]: https://pandas.pydata.org/pandas-docs/stable/user_guide/groupby.html#group-by-split-apply-combine
    +   [conversion]: https://pandas.pydata.org/pandas-docs/stable/user_guide/dsintro.html#dataframe
    +   [slicing]: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#slicing-ranges
    +   [fancy-indexing]: https://pandas.pydata.org/pandas-docs/stable/user_guide/advanced.html#advanced
    +   [subsetting]: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#boolean-indexing
    +   [merging]: https://pandas.pydata.org/pandas-docs/stable/user_guide/merging.html#database-style-dataframe-or-named-series-joining-merging
    +   [joining]: https://pandas.pydata.org/pandas-docs/stable/user_guide/merging.html#joining-on-index
    +   [reshape]: https://pandas.pydata.org/pandas-docs/stable/user_guide/reshaping.html
    +   [pivot-table]: https://pandas.pydata.org/pandas-docs/stable/user_guide/reshaping.html
    +   [mi]: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#hierarchical-indexing-multiindex
    +   [flat-files]: https://pandas.pydata.org/pandas-docs/stable/user_guide/io.html#csv-text-files
    +   [excel]: https://pandas.pydata.org/pandas-docs/stable/user_guide/io.html#excel-files
    +   [db]: https://pandas.pydata.org/pandas-docs/stable/user_guide/io.html#sql-queries
    +   [hdfstore]: https://pandas.pydata.org/pandas-docs/stable/user_guide/io.html#hdf5-pytables
    +   [timeseries]: https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#time-series-date-functionality
    +
    +## Where to get it
    +The source code is currently hosted on GitHub at:
    +https://github.com/pandas-dev/pandas
    +
    +Binary installers for the latest released version are available at the [Python
    +Package Index (PyPI)](https://pypi.org/project/pandas) and on [Conda](https://anaconda.org/conda-forge/pandas).
    +
    +```sh
    +# conda
    +conda install -c conda-forge pandas
    +```
    +
    +```sh
    +# or PyPI
    +pip install pandas
    +```
    +
    +The list of changes to pandas between each release can be found
    +[here](https://pandas.pydata.org/pandas-docs/stable/whatsnew/index.html). For full
    +details, see the commit logs at https://github.com/pandas-dev/pandas.
    +
    +## Dependencies
    +- [NumPy - Adds support for large, multi-dimensional arrays, matrices and high-level mathematical functions to operate on these arrays](https://www.numpy.org)
    +- [python-dateutil - Provides powerful extensions to the standard datetime module](https://dateutil.readthedocs.io/en/stable/index.html)
    +- [tzdata - Provides an IANA time zone database](https://tzdata.readthedocs.io/en/latest/) (Only required on Windows/Emscripten)
    +
    +See the [full installation instructions](https://pandas.pydata.org/pandas-docs/stable/install.html#dependencies) for minimum supported versions of required, recommended and optional dependencies.
    +
    +## Installation from sources
    +To install pandas from source you need [Cython](https://cython.org/) in addition to the normal
    +dependencies above. Cython can be installed from PyPI:
    +
    +```sh
    +pip install cython
    +```
    +
    +In the `pandas` directory (same one where you found this file after
    +cloning the git repo), execute:
    +
    +```sh
    +pip install .
    +```
    +
    +or for installing in [development mode](https://pip.pypa.io/en/latest/cli/pip_install/#install-editable):
    +
    +
    +```sh
    +python -m pip install -ve . --no-build-isolation --config-settings editable-verbose=true
    +```
    +
    +See the full instructions for [installing from source](https://pandas.pydata.org/docs/dev/development/contributing_environment.html).
    +
    +## License
    +[BSD 3](LICENSE)
    +
    +## Documentation
    +The official documentation is hosted on [PyData.org](https://pandas.pydata.org/pandas-docs/stable/).
    +
    +## Background
    +Work on ``pandas`` started at [AQR](https://www.aqr.com/) (a quantitative hedge fund) in 2008 and
    +has been under active development since then.
    +
    +## Getting Help
    +
    +For usage questions, the best place to go to is [Stack Overflow](https://stackoverflow.com/questions/tagged/pandas).
    +Further, general questions and discussions can also take place on the [pydata mailing list](https://groups.google.com/forum/?fromgroups#!forum/pydata).
    +
    +## Discussion and Development
    +Most development discussions take place on GitHub in this repo, via the [GitHub issue tracker](https://github.com/pandas-dev/pandas/issues).
    +
    +Further, the [pandas-dev mailing list](https://mail.python.org/mailman/listinfo/pandas-dev) can also be used for specialized discussions or design issues, and a [Slack channel](https://pandas.pydata.org/docs/dev/development/community.html?highlight=slack#community-slack) is available for quick development related questions.
    +
    +There are also frequent [community meetings](https://pandas.pydata.org/docs/dev/development/community.html#community-meeting) for project maintainers open to the community as well as monthly [new contributor meetings](https://pandas.pydata.org/docs/dev/development/community.html#new-contributor-meeting) to help support new contributors.
    +
    +Additional information on the communication channels can be found on the [contributor community](https://pandas.pydata.org/docs/development/community.html) page.
    +
    +## Contributing to pandas
    +
    +[![Open Source Helpers](https://www.codetriage.com/pandas-dev/pandas/badges/users.svg)](https://www.codetriage.com/pandas-dev/pandas)
    +
    +All contributions, bug reports, bug fixes, documentation improvements, enhancements, and ideas are welcome.
    +
    +A detailed overview on how to contribute can be found in the **[contributing guide](https://pandas.pydata.org/docs/dev/development/contributing.html)**.
    +
    +If you are simply looking to start working with the pandas codebase, navigate to the [GitHub "issues" tab](https://github.com/pandas-dev/pandas/issues) and start looking through interesting issues. There are a number of issues listed under [Docs](https://github.com/pandas-dev/pandas/issues?q=is%3Aissue%20state%3Aopen%20label%3ADocs%20sort%3Aupdated-desc) and [good first issue](https://github.com/pandas-dev/pandas/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22good%20first%20issue%22%20sort%3Aupdated-desc) where you could start out.
    +
    +You can also triage issues which may include reproducing bug reports, or asking for vital information such as version numbers or reproduction instructions. If you would like to start triaging issues, one easy way to get started is to [subscribe to pandas on CodeTriage](https://www.codetriage.com/pandas-dev/pandas).
    +
    +Or maybe through using pandas you have an idea of your own or are looking for something in the documentation and thinking ‘this can be improved’... you can do something about it!
    +
    +Feel free to ask questions on the [mailing list](https://groups.google.com/forum/?fromgroups#!forum/pydata) or on [Slack](https://pandas.pydata.org/docs/dev/development/community.html?highlight=slack#community-slack).
    +
    +As contributors and maintainers to this project, you are expected to abide by pandas' code of conduct. More information can be found at: [Contributor Code of Conduct](https://github.com/pandas-dev/.github/blob/master/CODE_OF_CONDUCT.md)
    +
    +
    + +[Go to Top](#table-of-contents) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas-3.0.2.dist-info/RECORD b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas-3.0.2.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..e823181c750c41a259d7ef590cb8d8f8e3c49727 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas-3.0.2.dist-info/RECORD @@ -0,0 +1,1522 @@ +pandas-3.0.2.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2 +pandas-3.0.2.dist-info/LICENSE,sha256=XBBS6SHmLTbM2lCmFYXpwERLgMYvObV8Tz8NX7YvUHE,52127 +pandas-3.0.2.dist-info/METADATA,sha256=RVxtn8qEhmBSVQHv6FXo6OnJ5byGYD9XXH_C57sBXIk,79468 +pandas-3.0.2.dist-info/RECORD,, +pandas-3.0.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas-3.0.2.dist-info/WHEEL,sha256=epWLuUEAjiWPFyst9biBtJhk29BQLtKqZosYwNv0jF4,138 +pandas-3.0.2.dist-info/entry_points.txt,sha256=OVLKNEPs-Q7IWypWBL6fxv56_zt4sRnEI7zawo6y_0w,69 +pandas/__init__.py,sha256=dC-FwTXuZUv_EgPvzudmS_gvxzBaw5Ev8wSgY_X36jw,8223 +pandas/_config/__init__.py,sha256=O-9vFKaJr5HE5juz0QOwFeacMqcCJ3WFWSO7ZXkm1ns,1105 +pandas/_config/config.py,sha256=A4VjujT5r25hSUzCByG88tYeg35tRMGAVbwhPOZ-15Q,27125 +pandas/_config/dates.py,sha256=PkwTRpJEX-yD_GuNsweYrKXH4F2UjkaIxXXjats1gqA,669 +pandas/_config/display.py,sha256=xv_TetWUhFlVpog23QzyhMYsScops_OOsWIAGnmKdJ8,1804 +pandas/_config/localization.py,sha256=Mr6dbIo_ANUluYw8S6bLRdBDffeaWFXYtYfu4YpSRCM,5210 +pandas/_libs/__init__.py,sha256=sWDA_DczzT-1wEBE4Xf5wRL2tB7d2RKtbbHrwCNZyeM,673 +pandas/_libs/_cyutility.cpython-314-x86_64-linux-gnu.so,sha256=yFvluxKIC1cGBiklnJCd_xwFKF77yWn1wETEAqn2hhs,194352 +pandas/_libs/algos.cpython-314-x86_64-linux-gnu.so,sha256=4Sa8Is2JEefGZf4ZzALVgSbHmZ-PrUv47aFM27EaeuM,1955400 +pandas/_libs/algos.pyi,sha256=HxigaFoEUtmsQCsGVQLndS5JXS0LbyhrUEc6cdZHxEE,16379 +pandas/_libs/arrays.cpython-314-x86_64-linux-gnu.so,sha256=o73i8nPUrhBonjag6HlLAUCzGZfvFGvYb_A2atMr8QA,112712 +pandas/_libs/arrays.pyi,sha256=xYEJXc_4qeY8lV35lC6otEl9vcCQQCYEn0wyhY4eVrQ,1208 +pandas/_libs/byteswap.cpython-314-x86_64-linux-gnu.so,sha256=v6O_OcGiwAJDS6NHl4rzV6nRRKK1qZ_GaawYih4Q1r8,49768 +pandas/_libs/byteswap.pyi,sha256=SxL2I1rKqe73WZgkO511PWJx20P160V4hrws1TG0JTk,423 +pandas/_libs/groupby.cpython-314-x86_64-linux-gnu.so,sha256=cW2h4Wr3DDSym_jmp-OHlyQr11QoABKPfAu8KRiPd4M,2225488 +pandas/_libs/groupby.pyi,sha256=9aORk-dj0gdHA-YP9wlHrzvrDf7EBEzZG8u8q1UJ2Pg,7784 +pandas/_libs/hashing.cpython-314-x86_64-linux-gnu.so,sha256=Sk1xFCcOjH-qDDTqV2Kj5iyaQ3IlzwQssOVIWiV67Wo,78600 +pandas/_libs/hashing.pyi,sha256=cdNwppEilaMnVN77ABt3TBadjUawMtMFgSQb1PCqwQk,181 +pandas/_libs/hashtable.cpython-314-x86_64-linux-gnu.so,sha256=weUo7FJ06wdADcN_yUyesglICbgpeUiM8ofccoFAXMc,2382568 +pandas/_libs/hashtable.pyi,sha256=F3EG6V1dm41RcR8Fyu66LpHsn3fy0g_u7Lh7OL9Y5VI,8197 +pandas/_libs/index.cpython-314-x86_64-linux-gnu.so,sha256=hRcDPb6E851vo-Xw7UU3BmuFbStmQ88FTxqNuEJSlcw,700232 +pandas/_libs/index.pyi,sha256=zfOuZNwby2LLZEIVmZTq0AwYaJrHBRBEm11_ycPof40,3842 +pandas/_libs/indexing.cpython-314-x86_64-linux-gnu.so,sha256=-thQO1CoEtIL5vZ3JzPCJ_CueifXxxmS_nrIqnvPUIU,58504 +pandas/_libs/indexing.pyi,sha256=hlJwakbofPRdt1Lm31bfQ3CvHW-nMxm0nrInSWAey58,427 +pandas/_libs/internals.cpython-314-x86_64-linux-gnu.so,sha256=mBPiZQPCfcAkG0At0o3gIcbQkZj9i4MdIh-wych0cbE,271368 +pandas/_libs/internals.pyi,sha256=FgNTZkoElYu2Wyck2DHsuDwtgcCXe4r8eDacg-rETbY,2857 +pandas/_libs/interval.cpython-314-x86_64-linux-gnu.so,sha256=xJZyyzUsPEEd6IBd71CRLlEZM0h1zWqY8xT9wzlq7zE,1021256 +pandas/_libs/interval.pyi,sha256=cotxOfoqp7DX7XgIeKrGd31mfAeNerW1WD-yBrLfTlE,5378 +pandas/_libs/join.cpython-314-x86_64-linux-gnu.so,sha256=ZBL8oIYN5oYH395Ysf7RKF0rqqtPVVHuM-zBbcVrnhg,1105992 +pandas/_libs/join.pyi,sha256=O61ZOIYi-I3gZJjDpLYIWWEe3iG0pogEQIB0ZxJ_E3Y,2780 +pandas/_libs/json.cpython-314-x86_64-linux-gnu.so,sha256=bDihx6trUOya0T1SSmmTkh8LOH3p-CqS9ie5N4rpHxc,68704 +pandas/_libs/json.pyi,sha256=sxLMqRma6XL2Pw37suIRq5eVt7DkH4b3iFM5X_97dDA,519 +pandas/_libs/lib.cpython-314-x86_64-linux-gnu.so,sha256=T8TyaYtgAI7gu5ezCFEdUQkj-uVvVR2VEKB6mFdbHKY,616008 +pandas/_libs/lib.pyi,sha256=Q7DbaCvnWSVzM3bQSMY_dlCD1MorF0jrxMuSXN6en9U,7424 +pandas/_libs/missing.cpython-314-x86_64-linux-gnu.so,sha256=dqsXbFaVXOaoqJy97AFA0VcezFZ_-4bfvkVLc19d4Zg,174184 +pandas/_libs/missing.pyi,sha256=J4_1W0Xdu5ORDghNY3wXCLUbEIuZq1Jjx64NzxMhIV0,548 +pandas/_libs/ops.cpython-314-x86_64-linux-gnu.so,sha256=TuO5ydfMUvUPyf4lEl0TkwxLqojQDttkToYc09ltdJw,127848 +pandas/_libs/ops.pyi,sha256=1JUKe_T45_DdFaVKoT7hJAAqm3DM4SjUgf90UnEH2ik,1350 +pandas/_libs/ops_dispatch.cpython-314-x86_64-linux-gnu.so,sha256=i1vZ4HaGxgVmMgUyLNo4Zii5s5CNSDXvDLqPG0qjG7Q,53864 +pandas/_libs/ops_dispatch.pyi,sha256=Yxq3SUJ-qoMZ8ErL7wfHfCsTTcETOuu0FuoCOyhmGl0,124 +pandas/_libs/pandas_datetime.cpython-314-x86_64-linux-gnu.so,sha256=niVnJuQCDe1NMfp37ovKJp_ECixSrQq1oqPg-zj_7VE,39616 +pandas/_libs/pandas_parser.cpython-314-x86_64-linux-gnu.so,sha256=QIzR8gfyOglezB-VgDu05GQuJaqpTt7Wflmck3oxM_0,43808 +pandas/_libs/parsers.cpython-314-x86_64-linux-gnu.so,sha256=mV2hu-630j8JEO4MaD3uEtV8UBUnHAYSD8qapArrkNA,426408 +pandas/_libs/parsers.pyi,sha256=1gCRrbGEmyy9dB88E9m4bC9kzvqy0XTfeEg99Ad-te0,2401 +pandas/_libs/properties.cpython-314-x86_64-linux-gnu.so,sha256=tynFiNi2251kKSMQ6oLrq_E3Z8XLWYCqveV8F4LAzDc,75432 +pandas/_libs/properties.pyi,sha256=i2CSa1eOYCrYd-p6Fr2xgY8OeOjppdDpskO7pvCTEMQ,740 +pandas/_libs/reshape.cpython-314-x86_64-linux-gnu.so,sha256=sz0pe3lnszD1Vu-nbz6PZCKd00LMBJuFaFiZMCgX5BU,157416 +pandas/_libs/reshape.pyi,sha256=xaU-NNnRhXVT9AVrksVXrbKfAC7Ny9p-Vwp6srRoGns,419 +pandas/_libs/sas.cpython-314-x86_64-linux-gnu.so,sha256=uVuZ5TGAryOhFmSPzWpMEVWu4UflXOuGRC9US33tufw,128232 +pandas/_libs/sas.pyi,sha256=qkrJiuBd7GQbw3DQyhH9M6cMfNSkovArOXRdhJ8PFDA,224 +pandas/_libs/sparse.cpython-314-x86_64-linux-gnu.so,sha256=ocMG0Kb7JIPUwBcGVftz3EAHqZeTF_ESkBYKchoBRZE,754504 +pandas/_libs/sparse.pyi,sha256=1MV4mNfsdQhVqfYSkm1Of723VP1Y9ZVPDfxS8pF1aKk,1486 +pandas/_libs/testing.cpython-314-x86_64-linux-gnu.so,sha256=RTdE1UEzW27q11Ybiwnmi-8-rdHV8iFL5ubBGL6EQmg,99080 +pandas/_libs/testing.pyi,sha256=7a-fZvWsAEZYf1GeXly8VdQ6bz9Fr9xrTIN4w0WEFGo,314 +pandas/_libs/tslib.cpython-314-x86_64-linux-gnu.so,sha256=YiZ_u8l8hx5MOQhZRgF3mRNLq6aanJjJgidBSJxIIeU,140360 +pandas/_libs/tslib.pyi,sha256=xz19XaaaUk5XBy_327JPZQGobq11VrBCgCMa6-mGHwI,859 +pandas/_libs/tslibs/__init__.py,sha256=0da7GS5rruyvTmEiP6LkIFts8sbT9TS0HTA3ajTckz8,2114 +pandas/_libs/tslibs/base.cpython-314-x86_64-linux-gnu.so,sha256=scDCTc2GmPh28KDCCly5MJ8VCXfx6tH8P9umt-aiMMk,50024 +pandas/_libs/tslibs/ccalendar.cpython-314-x86_64-linux-gnu.so,sha256=1yn8JzSPxj3Clka3l0brmv3sQfg_jS1xBM_XYAtODD4,90824 +pandas/_libs/tslibs/ccalendar.pyi,sha256=dizWWmYtxWa5Lc4Hv69iRaJoazRhegJaDGWYgWtJu-U,502 +pandas/_libs/tslibs/conversion.cpython-314-x86_64-linux-gnu.so,sha256=97vKLCFjKOIqOSR29FyMXvzM9T26JEhGz1R2zh2NBTo,153128 +pandas/_libs/tslibs/conversion.pyi,sha256=sHO9CBRrDh0wovkr736kI5G6gaW1WY9tSOOAkBi63MA,300 +pandas/_libs/tslibs/dtypes.cpython-314-x86_64-linux-gnu.so,sha256=vqEc5vij4COTuwKVn0rMqYkamO2_QHJid66Dz_u4yP8,149352 +pandas/_libs/tslibs/dtypes.pyi,sha256=JoO1K3nAkYagkF-0USQRh9gUodLkqDg6h-zWd6kI7hE,2040 +pandas/_libs/tslibs/fields.cpython-314-x86_64-linux-gnu.so,sha256=6Spmu9O4jPEAZDRbbxF2A9BcFpRpDoJ7Y6DoxUIiukM,189896 +pandas/_libs/tslibs/fields.pyi,sha256=of31FZoCVlE461o-4JaBOy1ibRH_zoQEDb2B8nZ7oWI,1862 +pandas/_libs/tslibs/nattype.cpython-314-x86_64-linux-gnu.so,sha256=sfn1at-JXbTnlvbiXVZpdXB-aoQIqOp-KJSI3gJsnpY,183752 +pandas/_libs/tslibs/nattype.pyi,sha256=FsWXZ-Ovm7aaZEVJJr5Xn-GKSXIIJBNOLPNSpvlEj8E,5745 +pandas/_libs/tslibs/np_datetime.cpython-314-x86_64-linux-gnu.so,sha256=UwIdStjY9uCYwIYi0hFsNmw53KIxz3sGMs8zBGNrVyA,131976 +pandas/_libs/tslibs/np_datetime.pyi,sha256=Y6l1KVdyKTMiYfzOjXNwV946GjoFAHaCEEhLDsHRCxI,831 +pandas/_libs/tslibs/offsets.cpython-314-x86_64-linux-gnu.so,sha256=XpCb6doZQwaqjxehHoctPChBc9-i5vNWxAt8JbS2LFE,1027176 +pandas/_libs/tslibs/offsets.pyi,sha256=qwT4DyENq-0DvL1xnW4LGWioVUCNsGYU7AySNyMw76k,8987 +pandas/_libs/tslibs/parsing.cpython-314-x86_64-linux-gnu.so,sha256=V1AKdMMvItu74z8Hlk6P0lNWd1XI8mZPNFEbRZdarXk,247240 +pandas/_libs/tslibs/parsing.pyi,sha256=lpTL5Jfut8GeckPCmbV1yEtcaPPDANxHl_9UkqTlxVw,836 +pandas/_libs/tslibs/period.cpython-314-x86_64-linux-gnu.so,sha256=WSomd5FVXjgj-d9XHUs0lO9Il-8AJCwDhoqC2L8msno,348104 +pandas/_libs/tslibs/period.pyi,sha256=8GsJC_H5b9qmwjBuP8ex6AiEIG0A3I55o7dlYkijUrI,3907 +pandas/_libs/tslibs/strptime.cpython-314-x86_64-linux-gnu.so,sha256=L_Wqgbw4PPKCRnWxKTyuq_sZcIxJvyW7IU1QIU5zO8o,202280 +pandas/_libs/tslibs/strptime.pyi,sha256=dizASoJenvjCydaWDo72_FQmiNOjLmnCZbUZgCm8EnI,349 +pandas/_libs/tslibs/timedeltas.cpython-314-x86_64-linux-gnu.so,sha256=idUTddn5JQRrwIVutQ4xkCVXyREEbVRuLxFQ7dCHiEA,467976 +pandas/_libs/tslibs/timedeltas.pyi,sha256=zZCDzLoc1wCyopjeZVU4cqjx0aKuxoDuyF1ZiYfRoDA,4963 +pandas/_libs/tslibs/timestamps.cpython-314-x86_64-linux-gnu.so,sha256=qBCco2LJDDClaZzCKVKptP3493V9zn8Z7Q_SGuIv5-I,469000 +pandas/_libs/tslibs/timestamps.pyi,sha256=mbtuv7jDUz1_CiadL0Schhdcu6-XaQpQgILV366MytQ,7858 +pandas/_libs/tslibs/timezones.cpython-314-x86_64-linux-gnu.so,sha256=VQDmdsLgu1ec5HRCwslxOjzKTDrQKRZCpOTQphM_lP0,136168 +pandas/_libs/tslibs/timezones.pyi,sha256=eH4wh111hhQhTtqIDcrKd9qZO3SVzgfMlcvydIGtHUU,609 +pandas/_libs/tslibs/tzconversion.cpython-314-x86_64-linux-gnu.so,sha256=PGnXPDaR2IAbzDMLmoEurnQK9uKRl1lLgru70de-ZCM,189800 +pandas/_libs/tslibs/tzconversion.pyi,sha256=Pdvbn49SNuHeY3CrrHHmVT-Jn5BCb5KEOdXILsf6Bso,569 +pandas/_libs/tslibs/vectorized.cpython-314-x86_64-linux-gnu.so,sha256=NGs0InQzh86x7UrP7avrnNwehi882-0pT033_Foy-dI,107400 +pandas/_libs/tslibs/vectorized.pyi,sha256=SY5nxyTfloF0NaWIkdgcJbDsfjQw-2D24P1WvsEqO7I,1235 +pandas/_libs/window/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/_libs/window/aggregations.cpython-314-x86_64-linux-gnu.so,sha256=P2mdfe3DDpR-H75W1LyK9Gi3ZRrGrq2l2uQHW9kBq3Y,419328 +pandas/_libs/window/aggregations.pyi,sha256=wa6oebW_IkrbB2h1dnfLwepv6lyPXdPJWkntOXWy9vw,4761 +pandas/_libs/window/indexers.cpython-314-x86_64-linux-gnu.so,sha256=XCNFS9fiGS6JC0c4phlM-ayT62Y8GhZA_eUmzwbK-r4,199656 +pandas/_libs/window/indexers.pyi,sha256=53aBxew7jBcAc9sbSoOlvpQHhiLDSWPXFcVbCeJDbQA,319 +pandas/_libs/writers.cpython-314-x86_64-linux-gnu.so,sha256=E27Gox2OSUTtC_zlyG5Y67hOErJGgY_YPaDSJb4Uut4,99912 +pandas/_libs/writers.pyi,sha256=RvwFCzrsU4RkKm7Mc3wo12RqdGdo-PuANkMo3Z9hLiU,516 +pandas/_testing/__init__.py,sha256=vfA0YkHHkwVKKa6HxR7kgPTdvkjAdeels8H7zX_P6mQ,17843 +pandas/_testing/_hypothesis.py,sha256=BtM_UV3iXmhtayyIqgY_qhh5j11i4CzZ7S-PQkHadHo,2325 +pandas/_testing/_io.py,sha256=WJ_KUUjV67U85wHhRbFUkWLnPffgnVJnS02vo_vSbOI,3468 +pandas/_testing/_warnings.py,sha256=mi5D6m0Dk8lU5gCMOKlYLxtEpuVjd1VZs5sr7Kv-jzU,10121 +pandas/_testing/asserters.py,sha256=D4G6Jyl9LtOrHmgBxu-xmgRQUBS7lUjE1vYlIg3AaYE,50070 +pandas/_testing/compat.py,sha256=PHo0paARZfORUcUZKqjUjdH837ETrtiV-fEedmcUErE,659 +pandas/_testing/contexts.py,sha256=PwdRp6LRIET1GV0iBza-VY1PhW798PQMM4Obq7cLcZg,3616 +pandas/_typing.py,sha256=j4ZXj53inVBfzr--xVkg3ZSjoIQf39Oby8zIc6rM9as,16692 +pandas/_version.py,sha256=I9BCdSmUajzueGSzlV_RyPsipIRFav2ErFoRQZnzkgg,23695 +pandas/_version_meson.py,sha256=QvVovuZCvSqfbK_izBmu-g7lYs6zk6hri8Ohq55O0zY,79 +pandas/api/__init__.py,sha256=rpLKlHQzEjuDW109ryEWLAhw0vkrSIHW_H9DGFzh7E4,250 +pandas/api/executors/__init__.py,sha256=WFC8QAIJNaa1DXzf-1ulnrZvOzoAIwLx1PflPoNxGsg,174 +pandas/api/extensions/__init__.py,sha256=PBbwJsFSUIz0tsK91qCRVb_fJLkX6XB2UjNfyKPvoHQ,685 +pandas/api/indexers/__init__.py,sha256=oJ_ks5iNyfEwsOHsS1n0PQMxmkpBe9r1LqJvzWpPb1w,357 +pandas/api/interchange/__init__.py,sha256=6btvqA-HE759VF7sTKt31BY-mXUEWVKyqdiJ6dbytBU,230 +pandas/api/internals.py,sha256=JQoljQ8k4DupvWWRC3lgNlyvNuVB4MaKOwybCuQbe6Q,2386 +pandas/api/types/__init__.py,sha256=MP0Vbhd9MsZU_8w_L8VzcrJgCFp5IlJ5-WakcsjB35Q,447 +pandas/api/typing/__init__.py,sha256=fxDrTMpXY-_3zqbsQK3ZpsfEMZpfgG1BhjVTK_884VE,1451 +pandas/api/typing/aliases.py,sha256=gmNIc_bJgdhlNhjsIuTsVG6d5ACeCA1Xu9lVq-0BTs0,2808 +pandas/arrays/__init__.py,sha256=Ehjr9BiyGbZcncVrrE7mwsplgnaFcbfvnjEQKeY3MHo,671 +pandas/compat/__init__.py,sha256=iOTHqg6hAiPffMKKortmydzPGtJbsX0umU2U1G9aMeI,3447 +pandas/compat/_constants.py,sha256=qXLXpk5kJTWSDKgN0r0jppBte3t2TaxFF-xHrjRxkiM,751 +pandas/compat/_optional.py,sha256=7aOq5ZNh-UWXc0GvgfiGR0FptMR6T6DqEnoQnQqWwvg,5568 +pandas/compat/numpy/__init__.py,sha256=BRH9PBvCVQCrr90f1T_M9W2x-hS75oEh6iCSutGkWQo,1032 +pandas/compat/numpy/function.py,sha256=t9jhsilYP1Nw6zdUaM6ssig48jwnKGKgAikPf_lVaxU,11752 +pandas/compat/pickle_compat.py,sha256=8ltTWsOqOGDo5qe5jlhqsbkjVniUOWcSmNO4Er8qdD8,4184 +pandas/compat/pyarrow.py,sha256=NRvOfGkoLR_9NVpUHp9hmG7pv4DMRc_mbE2cAx_0pw4,3147 +pandas/conftest.py,sha256=8jpxhl1CdwsN725LS11PlaHhHzBcxNjCSDMuX9s_Lyo,53450 +pandas/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/core/_numba/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/core/_numba/executor.py,sha256=nK2d0AVnOWdZ8e_qqBpSGEpq7PAn-zaLRR91DIGH8YA,8028 +pandas/core/_numba/extensions.py,sha256=2rysh7F9tTEDH0XsYj3t4iPkFm3ljmP_xIEgt4Dc1ms,18509 +pandas/core/_numba/kernels/__init__.py,sha256=0qDqF5R8ykYS8CAsLgb3_Jsr4XQ7pgcQRltGxypW8gk,520 +pandas/core/_numba/kernels/mean_.py,sha256=dc7vf40sQCk5v1qYdDZuLDcpG1wCVTYtaRjT57_8DS8,5663 +pandas/core/_numba/kernels/min_max_.py,sha256=5xMj0q7DeEOEZTnBiDUw1D6cT-Jcqmx8RxeICY6G2mo,5206 +pandas/core/_numba/kernels/shared.py,sha256=JUBa96LX4NmXhgXNyo859IwMXEl29EyhmRdMoQo1n78,611 +pandas/core/_numba/kernels/sum_.py,sha256=cmmujJIm40-D5HBPxgUdJiHTERR6C9m9krOxHgfmCxE,6796 +pandas/core/_numba/kernels/var_.py,sha256=B3_nE4-bc5vExbOSKy1_ygNcwIH081VZpqu8ckbrguY,7120 +pandas/core/accessor.py,sha256=GozrgRLbV0RzvflfO3Hi_IfFJltb1jCMad2Dro06XuI,17375 +pandas/core/algorithms.py,sha256=44si9PyHY_g2FgEmHEIeQL_B7AQjYbh1KPi6r2EE4BY,54656 +pandas/core/api.py,sha256=5OQPuHnkCVk7QCAM4MmMmzxisXw_J5hTc61w-KKpJ1w,2889 +pandas/core/apply.py,sha256=YvXyWSzy80s0Ah-W6WcXqZHKP-z8cOlPYCB3iFLIAYY,70029 +pandas/core/array_algos/__init__.py,sha256=8YLlO6TysEPxltfbNKdG9MlVXeDLfTIGNo2nUR-Zwl0,408 +pandas/core/array_algos/datetimelike_accumulations.py,sha256=5-V0of8pdndACP4fPzSjKGY_jfUj2II6W7U9dI83HWw,1854 +pandas/core/array_algos/masked_accumulations.py,sha256=MMqXBAgFmMm_9ootiqtIFmExgBpqJvPS6-ZAxo9bRNE,2897 +pandas/core/array_algos/masked_reductions.py,sha256=VYm1X9SXPxWQWKAAyIPuUp1oM1e-G4EkEI8JhZBur8Q,5331 +pandas/core/array_algos/putmask.py,sha256=o9WesECFS5jQZpLzrUBp1Z-8-Ip_6KUtaLcRFz91bK4,4594 +pandas/core/array_algos/quantile.py,sha256=NTV2RyDEuDqM9FMP7mSqOZ-XoUr5ThWYaxNdz8FousQ,6531 +pandas/core/array_algos/replace.py,sha256=Ty2QroTH_rxi1ewuYihWYVcKUtihhnMG543q5VsXzvk,4139 +pandas/core/array_algos/take.py,sha256=gkTYsAQelW6zsOIjkAfH0tmS1s00GB4irPdbSO0Ta7A,19529 +pandas/core/array_algos/transforms.py,sha256=TPpSPX5CiePVGTFUwnimpcC5YeBOtjAPK20wQvG92QI,1104 +pandas/core/arraylike.py,sha256=b5wGE1cb7wb8dMQpA2fYI9fPtO2QV91qs7m7kubNuQ8,17836 +pandas/core/arrays/__init__.py,sha256=f00N1VdEGdOEuLUDpw2o1vLInlUeR-rjeuUVL5h9Hwk,1314 +pandas/core/arrays/_arrow_string_mixins.py,sha256=FFAunhNL9DjhTjh4MHkY3EvXaqrlqFXdii7HWfNHWYc,15549 +pandas/core/arrays/_mixins.py,sha256=XNYzXB3lt_vnVr9TE2Q6N2GRFSUXhbxFodySkUQeE_c,17243 +pandas/core/arrays/_ranges.py,sha256=WVG61E0aGYDBQe3iZ0GC6LA5VR9Uoq0MGutVfdK_NGo,6919 +pandas/core/arrays/_utils.py,sha256=2tUfTarSjCCp1zPOZt6OVSFb2WbTB9IkZof187a9QJc,2253 +pandas/core/arrays/arrow/__init__.py,sha256=OK9PZIFiJ1GrLpplKHJ6gV5Zfhex20P_avvdO5ZXzc4,221 +pandas/core/arrays/arrow/_arrow_utils.py,sha256=RulRjyWzUn9gVFVeypndUwDLezpR2SWACD6_BYmxKDg,1586 +pandas/core/arrays/arrow/accessors.py,sha256=cpXr_sVXlsBaSQysLicSdemFQW0EuB-6wiESiqTnwNg,14748 +pandas/core/arrays/arrow/array.py,sha256=kVMV-IDzOognGxxnk7sqwi2B9493i8V8ygf1Q1uMWGc,119249 +pandas/core/arrays/arrow/extension_types.py,sha256=maymuBhQn2Maar0DS98pH-zDcCVzg8roYjaEgeo9nqc,5476 +pandas/core/arrays/base.py,sha256=86WL0S0tegTAo9gFH5L9UWADxrM_ojln5LzMxHY1kEo,101556 +pandas/core/arrays/boolean.py,sha256=JcsnZvwu6pg1IdysPPejHRlBwVxrLD_hBYGC_NdZ0ek,13414 +pandas/core/arrays/categorical.py,sha256=7OeyjSqqYx6zivHUib4tf1n4XIqTRPk37s3C4RfpoRE,103012 +pandas/core/arrays/datetimelike.py,sha256=MSIYIa5XsUoECvA5uCkzM3V8wxcyJ7JY04Hvg88xyRE,97469 +pandas/core/arrays/datetimes.py,sha256=VbuppzdLaLhZTRs2cEXM338CXAECijd4dtz2Ig8JC2w,100366 +pandas/core/arrays/floating.py,sha256=F4yboqyamt45RZaurIdKUxQROSI_udNBirrNGE3O3Ss,4765 +pandas/core/arrays/integer.py,sha256=J98lw6Rm0KF78xbKtNhV6DBsB9-sYyyta2QgbBJnVGQ,6965 +pandas/core/arrays/interval.py,sha256=W9ZHrEL1_Zr51wb6CBMjFSN07dUjkaukpGp2MhWv3Ww,73770 +pandas/core/arrays/masked.py,sha256=tVSkm985KMiMe99i1yBycIv28rWzTj3DM4f4vFAj9VQ,67450 +pandas/core/arrays/numeric.py,sha256=5gpwbP1-t1sIrFJezskoRD2pPnLl1GXpy2Y3xINkRCw,9896 +pandas/core/arrays/numpy_.py,sha256=DAJ1jIjrT62k7ye_zQPH4P6hx7lpN1Lovl0dWka4uMw,20247 +pandas/core/arrays/period.py,sha256=eCAb_Sa_HUFEe_93VhGqY3BX9eLDJjqDTomfIbXPCXc,47767 +pandas/core/arrays/sparse/__init__.py,sha256=M9tMvwNfj9ATInGPo450gjvvrznKiCfJtXjTaH0CidM,356 +pandas/core/arrays/sparse/accessor.py,sha256=phYdixHHVE-lHosqNGM3gPSsGtCOTFEP3l9I0SkaJsM,15178 +pandas/core/arrays/sparse/array.py,sha256=DT7dti-E3sm4s2NwPaFnCpmDdiPfSIkhOMz5n9Kt570,65849 +pandas/core/arrays/sparse/scipy_sparse.py,sha256=qbFANhd2BV5lcwVdjFluZDB_3zR4IECvZVlACcOMlOE,6508 +pandas/core/arrays/string_.py,sha256=PkXSjQ7y81DKQwGX_PG_GT8-TPJrPHVncJiQD-hmlRk,41774 +pandas/core/arrays/string_arrow.py,sha256=e73MEtvWW8xVLUAu9zBtVKPTy2Lo--iOFGeiudZPbzg,21420 +pandas/core/arrays/timedeltas.py,sha256=HK7mxUHuFUUgaavxCJRIPA26GR-Ls4HQo53EBFgvrrE,42201 +pandas/core/base.py,sha256=tCnU3z6LMdt8J82samg69YKmfCAoVDE9zB5vJPDzQWM,50102 +pandas/core/col.py,sha256=WWQq2Yqir4Gj2S7u6RrobXbCmWp3VgtljwrTB-Qvpu8,15221 +pandas/core/common.py,sha256=stF38Gc9ikSbjvFGxqNUREgL27e4M5sxOHW9A-LfY7U,18014 +pandas/core/computation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/core/computation/align.py,sha256=mDySzo-BonE39yJpcz4X5nh1IYGbNsDNlWHy6ki4QNQ,6762 +pandas/core/computation/api.py,sha256=CQ2AF0hwydcgTHycMCFiyZIAU57RcZT-TVid17SIsV4,65 +pandas/core/computation/check.py,sha256=Vb1YqLq381-nUp8Vjkg6ycJOxP3dV2aO9XjyM1uhe2Q,226 +pandas/core/computation/common.py,sha256=ua9vXbu9Qgk6GC0-jMlTI2GdOFl8W3Bs7oP_zxPv_pw,1442 +pandas/core/computation/engines.py,sha256=IYx68D1EKKyMsxqxVz9UvZvM_aid-LEOKPQ-fLEiv5U,3462 +pandas/core/computation/eval.py,sha256=uNgHB3z1yz4OFiOUWoW0i-aoZpwhGcbtBm0z2kRqRkQ,15403 +pandas/core/computation/expr.py,sha256=ZCIbl_kzO8w8TI7y_O3avBK0GrmsuaP4Y8BPh6-w_Fo,25611 +pandas/core/computation/expressions.py,sha256=7T72EDbnNW1DhBawWFrFJU_pwclNcerzfNRT-cAds-E,7968 +pandas/core/computation/ops.py,sha256=qMSzJcMVL6JEq3XTrMghmN_ns6xTJuafBIRbINXCcWQ,14487 +pandas/core/computation/parsing.py,sha256=VPH2Vn799V5VQRTwuY6xlZMaoUso7-adtTTgxaxcgjA,7711 +pandas/core/computation/pytables.py,sha256=fxRVNIcD411J4jrF6-JAmoaX4gNiSgzNiMiF9fOdUxI,21556 +pandas/core/computation/scope.py,sha256=mzN2mA5FtXai294FoEDsicizAAdCSI2aInghsjKIOY8,10204 +pandas/core/config_init.py,sha256=T7dAyQ4srH1XkqnRoAxsRHgyYllc04nEM4DSqDkG37Q,26657 +pandas/core/construction.py,sha256=6N_6sZb87_z93REbfANiXtN8YDwjXyxC4pHJoC3LDM4,28016 +pandas/core/dtypes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/core/dtypes/api.py,sha256=lkEgxy5kKaA2rU7XpWfSgd4vsQcVUr3Nduddvd_STp4,1783 +pandas/core/dtypes/astype.py,sha256=RMQsETWgHMUvkQwkEODdA2VuQ9Fu6XV5xKHT3PWIV5M,10217 +pandas/core/dtypes/base.py,sha256=G6ulkcFVbp8bsdzsMHtg6Sym_Hr10XNXbByU2tMRK54,17700 +pandas/core/dtypes/cast.py,sha256=orj2Q9xMf1mYG1Az0IiItGK0PeAc0L-yPV4yE30E3Nk,60017 +pandas/core/dtypes/common.py,sha256=-_G1t2SIarmi8qrlkDwe_Zg1mTjzh8omfdRgnpCcf_w,56262 +pandas/core/dtypes/concat.py,sha256=b9L92sjbI4NefD1jUXArjOvKQFSXXcgKZSvVBbbrWTw,12224 +pandas/core/dtypes/dtypes.py,sha256=Xbe_OJAY5EPQWkl9J6RS5kmiEPbYNaaRQA-XOsH_N_c,80669 +pandas/core/dtypes/generic.py,sha256=gjzfB5jmAOjW1sutnSpPJ0wVaIXsuUjGxl_U2qvPo-I,4129 +pandas/core/dtypes/inference.py,sha256=onMdV-Z_Ig1k2fwbvlBkFdFfjrw-1x1G90KSV-EncJ0,12641 +pandas/core/dtypes/missing.py,sha256=vUE5s2lBosYRrVJD-y4WNO_9aqWEx8PESOPg1-4mUzE,21406 +pandas/core/flags.py,sha256=iEvMT4cMEo9JPTSHm-odaGbKUem4xezZkbSW8s-rDys,4219 +pandas/core/frame.py,sha256=HnWLhKeMZ3pLaxbbJDTTheEpbnVVr3SCF0QyEqvUHog,574119 +pandas/core/generic.py,sha256=u-JmkTttFP_5ZOT3KNbWxW7bLryCCR-KCwpV2KMmz44,463165 +pandas/core/groupby/__init__.py,sha256=nyZsjyVF-bFx52HYbK4SQMfDNusvYZ1ANGJVxEeAkfQ,301 +pandas/core/groupby/base.py,sha256=E3J2X0Tz_xjUhzsH4zg5aA6qBX8zJ_2ANsDgN000gaM,2721 +pandas/core/groupby/categorical.py,sha256=kk8IFAKmCBK62KzPan3ZVef9MP4iA6ihaWoo-Wp4-rU,2853 +pandas/core/groupby/generic.py,sha256=SXOZn8Ljg7MdRYSyAboNp7KAgpeUm8T_QTkGomny61M,139394 +pandas/core/groupby/groupby.py,sha256=6e_ltZH4jl4ebx9jBeHIejfUgEfmIIMJrtZnPiKaZMg,202185 +pandas/core/groupby/grouper.py,sha256=68mKzMt3SieOKgXwAGjUxwcEz8xcNIByGa2SQPn1HKY,32642 +pandas/core/groupby/indexing.py,sha256=EGPsmbqGMuoyfSXlgXvJn43drlpWTUgJhVhVK-Dosq4,9453 +pandas/core/groupby/numba_.py,sha256=3DlT5DVLRC0pynkXu6-nlT5GGi8gDs7jMEqLsZBxO6c,4923 +pandas/core/groupby/ops.py,sha256=AbHU7O6Ft7dtQ3RzsSTLfpLm_ALO0CygcNPDZY0zKac,42144 +pandas/core/indexers/__init__.py,sha256=pgaa2DUC74IpoYQN0EbWzFkjj-3GsdfvQxRDlXKCYXk,790 +pandas/core/indexers/objects.py,sha256=OxLe5-mlaFow0MSdYet38hR2udtGSznOXlw-cL65cHw,23382 +pandas/core/indexers/utils.py,sha256=nDJZy40OtYIow1QFUeZ4Wcl-AUAT4kT2-GQhG1riK_k,17035 +pandas/core/indexes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/core/indexes/accessors.py,sha256=Zmg3p8HRgRz67MqQHjDEFjJC_36aTMUmHOrbeUiX4Jo,20342 +pandas/core/indexes/api.py,sha256=LGjI8APw__uvavCIVnjcK-t_H50uQP73-Sp5cVKCVeo,9519 +pandas/core/indexes/base.py,sha256=TUggDIUjFisni2tmL42x5uNqT2KybHaRDG6dW7hKmSU,277653 +pandas/core/indexes/category.py,sha256=JU443TonFzlGuBb4P3I5P1H4MlMApsG2o5d_SjH8Z4Y,17079 +pandas/core/indexes/datetimelike.py,sha256=iitTEQAbMIiISsXZmOMzvvwvoyVmawWZaHh5tKhm_7I,38256 +pandas/core/indexes/datetimes.py,sha256=aC6YkmfrL-h9VjocZTERZSYh4kWpLmWfPRtpZ0pOpyg,57595 +pandas/core/indexes/extension.py,sha256=OyjEfOae53cLwSlWRRfp07V8sQxPOAxq11DzruoB7kQ,5389 +pandas/core/indexes/frozen.py,sha256=Z10C41IUqiWNOHr2c1vn5rJ2eQCFKs2_0PPhvWkh5Bo,3342 +pandas/core/indexes/interval.py,sha256=0BM325T8gnNWEihTtZeGsgbR2_T27886q4E1LQhQS98,50445 +pandas/core/indexes/multi.py,sha256=CceJSv9UHcsVVn_Um2zvL7PNRnD4o9DUka2aIdVsduI,166907 +pandas/core/indexes/period.py,sha256=Lyvln_4GLmXG7gwiKBs1zRjUZdhMmd3ZBmfvKFrHeQE,19368 +pandas/core/indexes/range.py,sha256=NP90yeg6KC1N8fVuFo7bFUNi6OI4DSAowM7tdfywuJw,53786 +pandas/core/indexes/timedeltas.py,sha256=U4KNTqtfZHrypS9lUNAacNLPUBi3hvJ2ND8BIpXLipk,13219 +pandas/core/indexing.py,sha256=kwAGrDNSYh_j0xvN6XrNYr58CipfwXTbaLq5JYAGM8U,96287 +pandas/core/interchange/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/core/interchange/buffer.py,sha256=eTiReDShvrTHZLrnbMuH3SUWoMO_vi1jgmSOtqzUKmo,3428 +pandas/core/interchange/column.py,sha256=wTyyIupuqVCB8GlAyrefRlBohefQsi6yIRrXeuWDamA,18062 +pandas/core/interchange/dataframe.py,sha256=1-DL5KRZo3psA49tl2SE-rzAHPYXs_8-eESL5wAH4uc,3867 +pandas/core/interchange/dataframe_protocol.py,sha256=gm53R0mzHevPrbGAJB7BdOu5HuTXE3lCZo2VRv0hRe4,16263 +pandas/core/interchange/from_dataframe.py,sha256=6X0_GU16wL0a7qRc379XEHCJkZM7roC8o4gfSpQQ1GU,19788 +pandas/core/interchange/utils.py,sha256=TNyR-uXm7J_-wU0CLpIf9ih_bq8p5aBuUv6O_41wKK8,5051 +pandas/core/internals/__init__.py,sha256=blK_6WX4Gs9SzGyLecYQlxRAiW_9hFJX9otPbQ7X2Bc,2116 +pandas/core/internals/api.py,sha256=aPwsdLdqGuxDPfmu8S0sYAiclWGpyTB8kdkZwOtuBqI,5615 +pandas/core/internals/blocks.py,sha256=9yabhmrDs5xXNMPC5vT_1igIZ-wf0uT5LsM2xNIniaY,80959 +pandas/core/internals/concat.py,sha256=oQ7__5xpTHHXFFt_BO9YNLi7GVUgoyXAsNL9CNZ-uCc,15152 +pandas/core/internals/construction.py,sha256=M3J7wp0LQb_sO2DXa7V4NEFJfyppClqa2Hrg8gDndB0,33744 +pandas/core/internals/managers.py,sha256=NIG5FXvIBA9EobQqp0Z9TEyFlfsYDiLA6QW6dcAakqQ,87865 +pandas/core/internals/ops.py,sha256=HD6SBUfaNj446lW9MuTaz0-r7Lhdbb_1HtPNYRkVQuQ,5175 +pandas/core/methods/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/core/methods/describe.py,sha256=2W4_euaDogdEa5Z243P6hqnwbXYrHPyZDMdmkzA9RGI,10554 +pandas/core/methods/selectn.py,sha256=TskYlQkLcFsRmmM0eHhTnC8Rp9_c3Y9WTZlSapjGrZ8,8712 +pandas/core/methods/to_dict.py,sha256=V5wnYpiL41aSnPzAaEZVQ1YTUmd4JtyIBzEw4MJgk6c,9417 +pandas/core/missing.py,sha256=G_Jjue09Ao9AjeaykxYSzvjf7w_o4qkoVjlM2HguSWk,33972 +pandas/core/nanops.py,sha256=CkapA0vz_kEuege8SFQW_9s3o_JC_7iCPaHWlFSlygA,53461 +pandas/core/ops/__init__.py,sha256=Xn6Ybw-rH04j8kTsYVCyBR8Qo6TRtjm_yWqfiar5U7A,1621 +pandas/core/ops/array_ops.py,sha256=Av2rnoq_WdONuC-60dc1cYQOrpMIf1xuprJ9epZqICo,19686 +pandas/core/ops/common.py,sha256=0kVqnDZvRotqvDJ5vxkpXICNGnSpQiI-aI2BxlVcMf0,3802 +pandas/core/ops/dispatch.py,sha256=YPhnM8c7C9rikrUujv0Ut27fZ70yABBp_pdCikGAOA8,636 +pandas/core/ops/docstrings.py,sha256=d1YM6wp3LmsVujJ5aaN0Mv2jj5t34JqRzNBX4PAv6WM,18779 +pandas/core/ops/invalid.py,sha256=ih16WY1IZHT3oCnqPVuh3mqcb5Qn2V_VpeILGKYytxQ,1706 +pandas/core/ops/mask_ops.py,sha256=BVJ4-n6aw8mpOk2zau6HNrRWm3S-ksfHKvP0FV0xDjQ,5638 +pandas/core/ops/missing.py,sha256=xtqvoG-RYR0IXbjC3txiSuwo2irVEL27fxOH2uGOm4s,5069 +pandas/core/resample.py,sha256=aAllvqSEbx4Q39XJ5HoRi2cLxW4IQwNGMJpXFjWK4pA,101425 +pandas/core/reshape/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/core/reshape/api.py,sha256=Qk5y-D5-OdRYKkCgc-ktcxKGNGSCPteISEsByXFWI9M,680 +pandas/core/reshape/concat.py,sha256=LzCvT6wWhIv-HKQ1Ga5Sow21P7nNA--NjKvppBSN1O4,31009 +pandas/core/reshape/encoding.py,sha256=nIhG57P_BOVbP2Div1PfG0YP9tejwMEQ8X3r2MVIAYs,19750 +pandas/core/reshape/melt.py,sha256=0pT24ewPlDQ6qTi1HI7Z9pJMl0pjHnAJ-fxS8ZHjkV8,22076 +pandas/core/reshape/merge.py,sha256=r2b0qV2lvWUEnz0xeX-cSWAOvGHENwYOHC_vtel98Dc,114541 +pandas/core/reshape/pivot.py,sha256=r00uTAMqz8ShNCzp9oMwuFXHbjf0iC81nUAAcf-2s6U,39698 +pandas/core/reshape/reshape.py,sha256=TxQMuMuuVoRC1WgbhGMe--V4EAmEsmsATTwLd8Ze18s,40378 +pandas/core/reshape/tile.py,sha256=Yg4a1PJkPYpCEtWsxlAMx35DcwDFOTgWFKGGQfULQU0,23408 +pandas/core/roperator.py,sha256=P2jJkXT6klCMCfJeU2fNWfzhYE1ULFMpL7EAy8RTkt0,1115 +pandas/core/sample.py,sha256=IUGf0OwYq-8ooMtlfVd3ezX3fbhuEx8bEg8SWA4SU1Q,4975 +pandas/core/series.py,sha256=__ybpWZ-ddVBUwbSipgO1u5c4sghXEbLp6O0tUn8okQ,280462 +pandas/core/shared_docs.py,sha256=Dk-lm1KHwzlisppi_oaHinH3gkcAz9L-ETO51S5WfW4,21933 +pandas/core/sorting.py,sha256=BDJucespZJ7gU8-7CPDpYwHHJAowBxF0XRcEIP7BXW4,22507 +pandas/core/sparse/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/core/sparse/api.py,sha256=y0onCpBKCj_5Iaybw5e-gxk8zAa9d1p5Zu58RLzPT1k,143 +pandas/core/strings/__init__.py,sha256=TRw-H0s6qCbXoQKM01_SixgPOCRmrahYKpljDyHwWkI,807 +pandas/core/strings/accessor.py,sha256=ZQpTcEHl-vfaATl6UkUMZbLl8MQQHuklDJTh0ZLcixQ,163190 +pandas/core/strings/object_array.py,sha256=TlyeyKQjAWz_BavrKWLtGD1MMwV9gPhSX43NxXXliTA,16499 +pandas/core/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/core/tools/datetimes.py,sha256=hc6TZpffdE6QCKcWCF9IebaeUwSCcFW6RXBXWbjHNRc,41948 +pandas/core/tools/numeric.py,sha256=VwtKw5VxdEz7gDPAQlnJiQfnR7FmJcJLj168qRYJ7b0,10859 +pandas/core/tools/timedeltas.py,sha256=aAjFmXCKdhRD-2ljuC5wBCp07NTm3JY60Ql7ATD98AA,7296 +pandas/core/tools/times.py,sha256=yuK5wkkGKimT4T_qIB65yTtnZQLYN_GZhM8hcrijMTU,4825 +pandas/core/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/core/util/hashing.py,sha256=nf4aVGb0gT9j7beYMV_CbLCOVYpQWCLATeYHcqA3zgk,10487 +pandas/core/util/numba_.py,sha256=JhUZxkQec3LwdCFhP0U22jfLSEanSrT8jyrINKUJEYM,4251 +pandas/core/window/__init__.py,sha256=DewB8XXkLGEDgtQqICYPmnkZZ3Y4tN6zPoTYvpNuJGE,450 +pandas/core/window/common.py,sha256=2vJD0_pusJw1rDZgURrCai0YwD-_3KmOnkZ8eDMUy-Y,6785 +pandas/core/window/doc.py,sha256=8sN5dP64mGVvAG88U1L2iRBYzN2kyey6yOOUI5E74Dk,5764 +pandas/core/window/ewm.py,sha256=AxyeqTzHWHdek3P0U3ydQzMTazZavVlqOZo7W0f0Swg,39714 +pandas/core/window/expanding.py,sha256=VEWptF7Ssvh0hEc-TK9mqT1XaTnA0pcqCWbH0FJYS4o,44917 +pandas/core/window/numba_.py,sha256=6dqCtlfiRisF2o44ZONa_SfbdeQwDBlOxrREkb-0zCw,10935 +pandas/core/window/online.py,sha256=VdPLgfLVX5hL0fpGxT7ZQ8LChm6Y3RZiH5Prm1rGis8,3682 +pandas/core/window/rolling.py,sha256=_cQv9oscwCPjWY7PYv3D9g8qVruFV_1apJLb_V5yn0o,115918 +pandas/errors/__init__.py,sha256=_7C66iwv3-zN-QOzdvQ3eZ6I6JF4j4lFxgAx6eEwMx0,34630 +pandas/errors/cow.py,sha256=kJPlbtPs4OTeBeZnXaEMB4f8vmyAk2lWxFVnSn5AsHQ,2153 +pandas/io/__init__.py,sha256=Q1gCoXMCnbUHZxFo7xbUmQbB4g1ySkrXkCm26Jf4xwM,292 +pandas/io/_util.py,sha256=R4yVz8wlNUGKFIxY8RafuzY8ax2scUXa2OvgI19ECNg,6254 +pandas/io/api.py,sha256=inNGQ7iKk-dnLeEGWZO2_8q9r9VbUADx15K0uRY8j28,1277 +pandas/io/clipboard/__init__.py,sha256=3aFzdoqbabE8XM-FYjdYIctTps_sTAJDJMrhEbDv_UU,24235 +pandas/io/clipboards.py,sha256=x_4mMJFVB2x_e6sgaAB_bwORYgLNrvWUk5QbcT4dWvE,6391 +pandas/io/common.py,sha256=V5KS2G_PONjt2grqKbsSGAa4b6Vk1LhbO6caPvNwBeU,44458 +pandas/io/excel/__init__.py,sha256=XEr1lNxnxdf0Mt9eiWCmStxqbwqaaPrBDaVzNQrfLdU,486 +pandas/io/excel/_base.py,sha256=U2NEYk2FoVkgZWXVGOccoyIClEbluKIsHQ5Bv80ELQQ,70410 +pandas/io/excel/_calamine.py,sha256=SvNTL-_hsLriewd4PcNBgL8hbFO5PJU7C19lyDCTqbw,4180 +pandas/io/excel/_odfreader.py,sha256=G-svLn_joF-hNimV4cy2oo2n0jGDJ8Ec2pDPRrQFJ5s,8232 +pandas/io/excel/_odswriter.py,sha256=hfSXhv1uX0ltUbeI6SDM6NeHkOltKDDs7znjJzt9Qe8,11519 +pandas/io/excel/_openpyxl.py,sha256=6ohNijhkaobMUvJWWuiZN9vIEoyt09yN42Hi80l8DoY,20115 +pandas/io/excel/_pyxlsb.py,sha256=lEH3TUEAwdaI1MOKd7yl-ujDfM74yk99baYNz3jeqss,4861 +pandas/io/excel/_util.py,sha256=ezezXSW2SrhRKCp0c9Kq0yqyV5G5n72fYhrHZczXM_s,8081 +pandas/io/excel/_xlrd.py,sha256=yI1qzvG3vWj46F9WkpgLE7fzT8HKKY6zcbPrgnxfNUM,5093 +pandas/io/excel/_xlsxwriter.py,sha256=S9Kyv8E7QyBp8l-ZfO4QfMw9ZB8wSxbr92tGl8xuB8c,9380 +pandas/io/feather_format.py,sha256=LMnUBd-fPk8I5BLMesML4A2HMUvCuc3S9e_39CUANm0,6817 +pandas/io/formats/__init__.py,sha256=chUI3ziL_IxkmSVTJap9ome3pdLgN038xeeCAJ7HLVc,237 +pandas/io/formats/_color_data.py,sha256=fZ_QluvMFUNKUE4-T32x7Pn0nulQgxmsEMHB9URcBOY,4332 +pandas/io/formats/console.py,sha256=r-3xiw2T4pY-wmGu8Pk0paoKrjcaswZw9rKfGW1HC1U,2757 +pandas/io/formats/css.py,sha256=Am9v73Vcu4EGEN0-pT_9gbYL646s5BhXQOzVOXTujiY,12891 +pandas/io/formats/csvs.py,sha256=-Yiy2LsqTDRT79XjIxZ53yQvA1kwa4_V7W2AfFNzmso,10809 +pandas/io/formats/excel.py,sha256=o1i3wBS322iKmrOREQp5P_EYCpO7UkbdVePemp0mmto,35621 +pandas/io/formats/format.py,sha256=CYCszTUKQrt2ujlHDwROAwgU7ikg7b_B0hgrAYzk6rQ,66886 +pandas/io/formats/html.py,sha256=tImmCrQt0rBB89GOcr-VCfeTtO243xaZDbQC24BGx1U,24553 +pandas/io/formats/info.py,sha256=P5hQXkJaraeJfUXQ80snGtAlf8PfIsBhIB1Afd-YBv0,27360 +pandas/io/formats/printing.py,sha256=hZLZv0NTtgMMsnCdXniBVblmtRKBp2a2y2ExrTWHnAo,18768 +pandas/io/formats/string.py,sha256=XWoIzkYxuREBTyy6bXIvAWe34rlbz_cjN-0geSDpuyM,6708 +pandas/io/formats/style.py,sha256=k5sWqRfaVxhpuboXSFDvZo8NQLNz27mX819b0FiL4aI,177431 +pandas/io/formats/style_render.py,sha256=apW-9aGSis_7_YiJpeXOwgq6-XcsQ29SAvH8dJ_avn8,98616 +pandas/io/formats/templates/html.tpl,sha256=KA-w_npfnHM_1c5trtJtkd3OD9j8hqtoQAY4GCC5UgI,412 +pandas/io/formats/templates/html_style.tpl,sha256=_gCqktLyUGAo5TzL3I-UCp1Njj8KyeLCWunHz4nYHsE,694 +pandas/io/formats/templates/html_table.tpl,sha256=MJxwJFwOa4KNli-ix7vYAGjRzw59FLAmYKHMy9nC32k,1811 +pandas/io/formats/templates/latex.tpl,sha256=m-YMxqKVJ52kLd61CA9V2MiC_Dtwwa-apvU8YtH8TYU,127 +pandas/io/formats/templates/latex_longtable.tpl,sha256=opn-JNfuMX81g1UOWYFJLKdQSUwoSP_UAKbK4kYRph4,2877 +pandas/io/formats/templates/latex_table.tpl,sha256=YNvnvjtwYXrWFVXndQZdJqKFIXYTUj8f1YOUdMmxXmQ,2221 +pandas/io/formats/templates/string.tpl,sha256=Opr87f1tY8yp_G7GOY8ouFllR_7vffN_ok7Ndf98joE,344 +pandas/io/formats/templates/typst.tpl,sha256=kvuEPFOxvM1y2i5Yd9P_5OQ8mga9nObAtCmFaqZju0E,366 +pandas/io/formats/xml.py,sha256=wc54eEA9k6uu2Fa6Y13ZRSkUu4j9XzNEg_DADXbSBNE,17159 +pandas/io/html.py,sha256=LEzWhX1wq_O83oJRFDtXc_1Qoygqj1Py6YaBBpzEn4k,39806 +pandas/io/iceberg.py,sha256=RWPIvNFsoq1iy-YOmVXrTzqgqcf1sM257ixkVuG29zQ,5157 +pandas/io/json/__init__.py,sha256=RVdWuAEVioOHPyC4Gr9YXHA3UWA4KJKKlYon8pZJITo,270 +pandas/io/json/_json.py,sha256=RmWtLLy9Ps2HGlaTP3tGKrf4hMSlV0H2IXccscrNzk0,49807 +pandas/io/json/_normalize.py,sha256=S6OdwPFcgFChyIYYBel8glayQpjYPg4jd4I3vRxkM9M,20797 +pandas/io/json/_table_schema.py,sha256=wEaMmxnzIcH4vO075PEqGFOSeOBYQ3vp8KdxcnNBTRA,12100 +pandas/io/orc.py,sha256=JVfLN2OgQTEXpGME3j3A3WTtEFQeWIpdfBB_y2ZX4jo,8635 +pandas/io/parquet.py,sha256=_imR_5P9jLi9E_dde-0c_mPkLJoHeeWPQm7Y908qVuk,24453 +pandas/io/parsers/__init__.py,sha256=7BLx4kn9y5ipgfZUWZ4y_MLEUNgX6MQ5DyDwshhJxVM,204 +pandas/io/parsers/arrow_parser_wrapper.py,sha256=ubRGODzMUb02c0p10_W6eNxFX-Sn2_4ZDrSuN3N57us,11952 +pandas/io/parsers/base_parser.py,sha256=Pjh8hplwHSHDctGVSnkzsmzDKO4nUdut_2dI_STAceo,32912 +pandas/io/parsers/c_parser_wrapper.py,sha256=TpziB0_HOjMVxUJyRK5oKRPTf-4Y0XYqz5zem23-5pM,12915 +pandas/io/parsers/python_parser.py,sha256=uWlKzYWN5Rxhn3OJOQmj3bXIhszglI_7L9uF6FPNp4g,55162 +pandas/io/parsers/readers.py,sha256=6xRFxe7rEFRr0Tux-_Ai3THVauBZnXKHUVyEw41eWfM,92966 +pandas/io/pickle.py,sha256=ocxPT0rhgD8kAPMHSAt4PPq-X1eu56TS_JodR9aaZ1c,9145 +pandas/io/pytables.py,sha256=8BjapN9WMIZRDUUlBpXcExPL-H-mZipkG0_Fb0fNdaw,185357 +pandas/io/sas/__init__.py,sha256=AIAudC9f784kcEzuho8GiXU63vj2ThRitKznl7Imkq4,69 +pandas/io/sas/sas7bdat.py,sha256=1_cszDpRnQleuTnBusizk_FL4PvnItlosv0bTCiiWAA,27023 +pandas/io/sas/sas_constants.py,sha256=qCPgAHEWgZZZvqXb0EM4WuhD9b8DwhdUIxJuiMIJ0uo,8719 +pandas/io/sas/sas_xport.py,sha256=HbpBLJnOVTOAD2h6Eczauok-Ok7XdbYyNu-_tMX2ZM4,15064 +pandas/io/sas/sasreader.py,sha256=ifUHXrCoARYh-fUWoy4u3rib3qmo_Z3MQHQFTrZJOdw,6190 +pandas/io/spss.py,sha256=A0D3VMydfG2lsXmZMV0OCMJn7baequ4l8QlEFZZ1IQM,2923 +pandas/io/sql.py,sha256=2Z_lu0q2y11nlHXJpHmlMCEGLKox3MPtlusldYH20KU,104641 +pandas/io/stata.py,sha256=rFCO9nMgmWTznp656VTIUPGYc1CLHP3P40QHOQNeABk,143720 +pandas/io/xml.py,sha256=BAl4Xrl0d2FS2j_eJ62H8ZJM8u9xB_WFo9VSnwVM31g,39819 +pandas/plotting/__init__.py,sha256=LwqG2LVIvXDb15Hm9LGPC8jBD5qiA5-kxBKyiSwDhL8,2827 +pandas/plotting/_core.py,sha256=vL73Q4okGFlu1dzkMAptFVtC24Terkx3TsY6qKrfkDE,78431 +pandas/plotting/_matplotlib/__init__.py,sha256=kt_h14Dlhyyfjy4_hUYmQBnYCxocknZoEPDi2Jg8tgs,2054 +pandas/plotting/_matplotlib/boxplot.py,sha256=_FyOKxTV9q_0UFyR4SC4EvqxnS0SMmPT0RHtvR1p_6U,18084 +pandas/plotting/_matplotlib/converter.py,sha256=t2oCfijsptU140rSafumLmFFI8uTLSD9kEKu0V0_EqQ,37475 +pandas/plotting/_matplotlib/core.py,sha256=JHCjO_66-W1ia_E_BzPK6lF8gEOLVQyxdB7hS2hMYIA,75322 +pandas/plotting/_matplotlib/groupby.py,sha256=pvYa7qrEITxwDTfCPH1XV16Rka2ZCXi7hJnSJZ4EIeQ,4326 +pandas/plotting/_matplotlib/hist.py,sha256=8yZR3HfeDLrgyPfCbFvtG7OZ-qsx0JW5bJT_JrYPVHk,16789 +pandas/plotting/_matplotlib/misc.py,sha256=pdxbxw8PxjjLKkjyCp5HWxP6BBhH2A-k-sTBQE58oyM,13369 +pandas/plotting/_matplotlib/style.py,sha256=Ffi8Dv6l5LrbtVtdHFMIgcuDeimc93DZIztHsVIJYXs,8525 +pandas/plotting/_matplotlib/timeseries.py,sha256=UgSHX6En_tHq_EyEb6OW5weNsKoS-tCy6_v84KVMrNU,11252 +pandas/plotting/_matplotlib/tools.py,sha256=yMTah_HhNyfKa5VPrvVPaX0JRibFcqrd83amg-uJoWg,15597 +pandas/plotting/_misc.py,sha256=gRH6WtFo9RhLxNS9zB5VJS7qR0p3ehJ5mjRATkWAUXE,24868 +pandas/pyproject.toml,sha256=OqRWoIjVE3JzxBDPBNNAtzA7_a3IUKjR9wCaPXrfjtE,27432 +pandas/testing.py,sha256=qJIhuQwaEy671kEAwnww5MuLzH6M6S92mp9Ptv_1xaY,312 +pandas/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/api/test_api.py,sha256=g3UMK0gJFlypLvXlZdd6ReC-5oSvdPLKtWWOeb8BRY0,14786 +pandas/tests/api/test_types.py,sha256=7sisr-q1lpkC9WLyAo82SsvcQ9SGvLXtzBTE_mDMZWw,1688 +pandas/tests/apply/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/apply/common.py,sha256=A8TqjvKR4h4WaLtovGR9hDULpWs4rV-1Jx_Q4Zz5Dew,298 +pandas/tests/apply/conftest.py,sha256=46OKWScOvo1Kd2B09cB1ETeZHcO4_azeEXjukrKBW3g,2045 +pandas/tests/apply/test_frame_apply.py,sha256=3F7Yho8zwMcpOKBiI171M9bbUhYVCNp7IOA-Pt2MuAA,58466 +pandas/tests/apply/test_frame_apply_relabeling.py,sha256=L7JoRhsjz9-9i0dTDvWhAftlFKZbb9WG-66fwKOBv3o,3377 +pandas/tests/apply/test_frame_transform.py,sha256=bbAcYmXxlfEo8-zPQdxlp26s9LPlRbpVKpQu9yEVkCI,8020 +pandas/tests/apply/test_invalid_arg.py,sha256=s458r-GYbb4dBiix2NAODoteGMRyfhSNSSppVzneR0Y,11625 +pandas/tests/apply/test_numba.py,sha256=P_TcnQwviCktOIs8tC7YWoUEq52HhMVMCPcqWalUjlA,4191 +pandas/tests/apply/test_series_apply.py,sha256=mSB5cMgtvz2sre547GSDluHT7elFR4idSGKNM5hkiYs,20997 +pandas/tests/apply/test_series_apply_relabeling.py,sha256=AMKpxNA0r-YvExVkO7KxkfQX9OhF8Orz2LARZzAcqBc,1202 +pandas/tests/apply/test_series_transform.py,sha256=rrJO-C5HagNKJo542h32eB5TOWVDxirJv1u5PXJkh_I,2404 +pandas/tests/apply/test_str.py,sha256=7vxUfXCtflfCGs8imRSUBJpNc1SizKI76lS0vzrpgF0,10120 +pandas/tests/arithmetic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/arithmetic/common.py,sha256=RCRB9OoR7JXjYcVN31xZmkl2mSiXCvPa7juaSC3Od1Q,4429 +pandas/tests/arithmetic/conftest.py,sha256=IbKSLIqa7iUUGRd4-ZLX1drNgsm43Nsoj3K4oCl317Y,3477 +pandas/tests/arithmetic/test_array_ops.py,sha256=K5_P3RAA6NOSK77HKvxWx6Ge3f5Cv8V8NOjHD-UlVN4,2639 +pandas/tests/arithmetic/test_bool.py,sha256=3jiW1LntJ3_SPGXUMAEKtX9FN6Iepzg_38Ul3wjfFok,781 +pandas/tests/arithmetic/test_categorical.py,sha256=lK5fXv4cRIu69ocvOHfKL5bjeK0jDdW3psvrrssjDoA,742 +pandas/tests/arithmetic/test_datetime64.py,sha256=PmFGFQ9owcP-btJZRsOzcXbhkIUeE41Hzsu-vJZGpbo,91277 +pandas/tests/arithmetic/test_interval.py,sha256=QPGaaq7LathwV4zAriTBNR5dLr98SBiloGlA3mt9IX0,10986 +pandas/tests/arithmetic/test_numeric.py,sha256=SkCJrQs7fjKMnAPMRzsVTKmZCRvBokIOlghxXPKYIpA,56410 +pandas/tests/arithmetic/test_object.py,sha256=NUPAcje9MFO_rrpWqpYiM9ELsNoGYjm2lASYdBBMZ5U,13218 +pandas/tests/arithmetic/test_period.py,sha256=0R9EgWy6H3jC5mAbMoIiIoRoC1xeLfREVyl5q7u-pvc,59771 +pandas/tests/arithmetic/test_string.py,sha256=6_Y3C9QbSFgvQUNQoq7cSsC2tWZ9IyujRyJ8RXAmLzQ,16156 +pandas/tests/arithmetic/test_timedelta64.py,sha256=bMYYqoyr4qbzCvsxoc82L3XotfPiRYSMVk1KTBEthEM,84343 +pandas/tests/arrays/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/arrays/boolean/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/arrays/boolean/test_arithmetic.py,sha256=eKic8lacgMzsm9SkbvDvds1r6EhskdULjwHmbyeewvA,4172 +pandas/tests/arrays/boolean/test_astype.py,sha256=CWuoHBqqPdF9AqIYQ7_dtA87a1QOYlQbaRNKi_WMFIA,1849 +pandas/tests/arrays/boolean/test_comparison.py,sha256=-4eEE1_HQ1RUEyUKQ7YHb8lpQwWWGUoDSmarvQbW-oc,1971 +pandas/tests/arrays/boolean/test_construction.py,sha256=S8AkB035aS8_1EHO70nvE1wqmPFAWRq79IQFU_33u6k,12614 +pandas/tests/arrays/boolean/test_function.py,sha256=eAVsu1XUeokLh7Ko0-bDNUQqmVrGAyOvv9vJdWCQj0M,4061 +pandas/tests/arrays/boolean/test_indexing.py,sha256=BorrK8_ZJbN5HWcIX9fCP-BbTCaJsgAGUiza5IwhYr4,361 +pandas/tests/arrays/boolean/test_logical.py,sha256=FNkxAeJVPXlOhNT2hi6OG6V1HhUYbik2Mv5i5mBQ0vc,9402 +pandas/tests/arrays/boolean/test_ops.py,sha256=iM_FRYMtvvdEpMtLUSuBd_Ww5nHr284v2fRxHaydvIM,975 +pandas/tests/arrays/boolean/test_reduction.py,sha256=rI9tiEAkFZ-HyKKqXvIfzNfQ84fVhq5YLbr8Ehm_NVg,2232 +pandas/tests/arrays/boolean/test_repr.py,sha256=RRljPIDi6jDNhUdbjKMc75Mst-wm92l-H6b5Y-lCCJA,437 +pandas/tests/arrays/categorical/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/arrays/categorical/test_algos.py,sha256=vDKfu4nvTowYGlX3yIftUOjvqsEfDG3ZZdEMbMunvFM,2915 +pandas/tests/arrays/categorical/test_analytics.py,sha256=3aExWntSyIt1Lsq4XDrRATgC3Kr1V710VTqyx-uu8W0,13378 +pandas/tests/arrays/categorical/test_api.py,sha256=eEtZvLsJDVcZDrNiLUKZ_Teu0NlqcPV8lI7O_7rdsLY,19778 +pandas/tests/arrays/categorical/test_astype.py,sha256=e59KSeR62T0IUw-8HvAHtxnJWtdjWN-N9hglRl0pzHA,6018 +pandas/tests/arrays/categorical/test_constructors.py,sha256=xG0dtEiUMpLYmFIdzFzZK1I9Ih07m5nFE6qgxvLqVbU,33281 +pandas/tests/arrays/categorical/test_dtypes.py,sha256=tcI69Dkyv3iA3EvdmAlPg3SJRm9Joz05qLO7h6tCVPo,6210 +pandas/tests/arrays/categorical/test_indexing.py,sha256=92AyJqYDq6vEp9Egz_lSMkVv71BZQhd9uj34qu9Q-A0,12967 +pandas/tests/arrays/categorical/test_map.py,sha256=iCkoHamckYMwXSSa3BRzD2XgdKIrCatSHjOszN2tkA0,4595 +pandas/tests/arrays/categorical/test_missing.py,sha256=o02AkV-KcNTAwUsnO-s7_3f9Ud4Ohd2k-YA-uaRueNY,4840 +pandas/tests/arrays/categorical/test_operators.py,sha256=uDGgojOaqnAvyWKiPyO1Pn8esItFxqk6vdtC7n52aQ0,15909 +pandas/tests/arrays/categorical/test_replace.py,sha256=Hl5yMRGMOfXpFeIFf1xybvOreXvvTb1rhLykUwJA5HY,2051 +pandas/tests/arrays/categorical/test_repr.py,sha256=0ZY6w1ei5oJSdjYP2mNfnmvXWX8ldQN8LOyagwDJ6rM,27595 +pandas/tests/arrays/categorical/test_sorting.py,sha256=gEhLklhDxhqf8UDOB17TMKhrabxS5n0evPg9DWSMd5s,5052 +pandas/tests/arrays/categorical/test_subclass.py,sha256=Y4nURd4hFM0Q3aVET1OO-z11pZzzZ0HFfl2s-9OWemw,903 +pandas/tests/arrays/categorical/test_take.py,sha256=O4g_LYDeK0NzHDId5cBBEp1ns_a762NsYHn088ocYzg,3501 +pandas/tests/arrays/categorical/test_warnings.py,sha256=XqvGeAb9lrXP1VdwKSOvbDuytqDuJ5VSDsLKQAa5gIk,682 +pandas/tests/arrays/datetimes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/arrays/datetimes/test_constructors.py,sha256=EZxV5k6KzS3n7gK3YhvkoeRe89WDqnmOEjnlkE6-vrI,6526 +pandas/tests/arrays/datetimes/test_cumulative.py,sha256=X_SHtt9n_WzA_C2wPlRJHRS8LUmjNNmr2-XL6AszJd0,1307 +pandas/tests/arrays/datetimes/test_reductions.py,sha256=W3ln3-niuPyqAcMUFsay_EeqNIntccBfxOOs8JWYfsA,5525 +pandas/tests/arrays/floating/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/arrays/floating/conftest.py,sha256=gDYuKNRevHwie7NDZ4QLnYxevt6sZxHWCi6oyIElYig,1069 +pandas/tests/arrays/floating/test_arithmetic.py,sha256=vYz4Dg6fkNEky5ATI8MKMm8mZaty7F4jlk_NxmFWKpw,8651 +pandas/tests/arrays/floating/test_astype.py,sha256=EOcBIsfc44V7lUkNFQwqPnHSBtyEj38nhvNOStBbIcc,4337 +pandas/tests/arrays/floating/test_comparison.py,sha256=eV3cojE37nmhIKjUA086IrHngewcjxOAzpu1ZWc5OTs,2395 +pandas/tests/arrays/floating/test_concat.py,sha256=-RO-pwRRY93FQnOjBLs1fMVf7uBCoEGRkGWPAdX8ltU,573 +pandas/tests/arrays/floating/test_construction.py,sha256=yjKh40hlHVqfCkyetN_qPHPRfQ6A3b9cBCIoQyo7bjk,6730 +pandas/tests/arrays/floating/test_contains.py,sha256=cIY2tokcWXSUr62xApLw7h8oZ01Ptk4d2HOUhDkM4wM,286 +pandas/tests/arrays/floating/test_function.py,sha256=ES3gG_K9F6V4LJp2863-hO09zWf5Ovouydym38eMvhE,7097 +pandas/tests/arrays/floating/test_repr.py,sha256=N_BX7NbU8Pljiz2bouWMzrP22xh_6w_8pHePEB2ycVw,1157 +pandas/tests/arrays/floating/test_to_numpy.py,sha256=9M_Bz7aQfcX12YyPRp3t3LVRcG1VmLe8d-DgVSbEHyk,6414 +pandas/tests/arrays/integer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/arrays/integer/conftest.py,sha256=DzWpyqSXKVFJmXAxpUGnexmqDVsccXKr7YdAiWqfehI,1508 +pandas/tests/arrays/integer/test_arithmetic.py,sha256=whrs5Ro9KX9XoAxxt_hjjz8Fd_yczJcgW_sX7oKnt0Y,11126 +pandas/tests/arrays/integer/test_comparison.py,sha256=jUr8dmk_6FQsTNjDkYsazWnioHis4cLi94noy4txG54,1212 +pandas/tests/arrays/integer/test_concat.py,sha256=TmHNsCxxvp-KDLD5SaTmeEuWJDzUS51Eg04uSWet9Pg,2351 +pandas/tests/arrays/integer/test_construction.py,sha256=8DPYtLFhyGffvPw3_hIt6N8NYmxwSpbwuPfpQocCOQo,9006 +pandas/tests/arrays/integer/test_dtypes.py,sha256=FFqeQIUVMyPOwlw4pqbRAYYE89qA4dcOXNyl8b9bQ5Y,9396 +pandas/tests/arrays/integer/test_function.py,sha256=jmMGgA2X85_4-SVcV2YoIBUm07-YKcSbqM7xe1ILl5c,7444 +pandas/tests/arrays/integer/test_indexing.py,sha256=aVk3G9DUSd2jX11bYwF2AGo15H8nEfcYMFx6FlKeCdY,491 +pandas/tests/arrays/integer/test_reduction.py,sha256=PNYa5Kh90yktrNJDkoDFvzx39ZLn6w2qqYvvuvi27qI,4102 +pandas/tests/arrays/integer/test_repr.py,sha256=fLTZusgFHPXO4orpygmHIOG6JQLzYcdbTJHRvvsN0sM,1652 +pandas/tests/arrays/interval/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/arrays/interval/test_astype.py,sha256=8rb7rssqvIoSztzCfFb5pY4oIH_GjDStKrXkC6bnUZk,776 +pandas/tests/arrays/interval/test_formats.py,sha256=RgxzrQYmHX5b1CZ2tN2mNSppFJzzeOcXq5d7QPdKZsM,295 +pandas/tests/arrays/interval/test_interval.py,sha256=FLTtoN_ENfqcGfSLISk0YU4Owh6Xv3JsVddMp09lrAs,9259 +pandas/tests/arrays/interval/test_interval_pyarrow.py,sha256=fLEBRbsSGVgh7GMMXV0O7Af2K7-ASXjFx2VR-TTiFxE,5217 +pandas/tests/arrays/interval/test_overlaps.py,sha256=nuZhiGDC38PLDXexjMM4IIkIH19FuIiwYhiyFQ7KjoQ,3280 +pandas/tests/arrays/masked/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/arrays/masked/test_arithmetic.py,sha256=dIE0xRILQAVXwciCKehzjvjWOvICCMxXWsYXccgF6ho,8921 +pandas/tests/arrays/masked/test_arrow_compat.py,sha256=C3vpz4OU5K8cZaHkVXNQjHA733BE0ZnDhNeesNsH0cY,7194 +pandas/tests/arrays/masked/test_function.py,sha256=8xFx0oihN0ajA-AfN7t8d9WmQn-aapYL0e6f9SGTHU4,2101 +pandas/tests/arrays/masked/test_indexing.py,sha256=jmCa65NDsFJU90R8q_kskTzD34QXq8iBHmYuRannxVk,3198 +pandas/tests/arrays/masked_shared.py,sha256=cRBGnGMOsN_8L6t1InE-BeCcQZhs3GrNuhCAh1L-8Eo,5195 +pandas/tests/arrays/numpy_/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/arrays/numpy_/test_indexing.py,sha256=-0lB-Mw-gzM4Mpe-SRCj-w4C6QxLfp3BH65U_DVULNY,1452 +pandas/tests/arrays/numpy_/test_numpy.py,sha256=oNV_Cvq_9gu1CBIEQWHS2Z4BngrB_Dy1bGe_5LYrcWE,11424 +pandas/tests/arrays/period/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/arrays/period/test_arrow_compat.py,sha256=tZCxVGgBMkzz-w-nXrd736BiTTKsKYfvKXlkLTIKbo8,3452 +pandas/tests/arrays/period/test_astype.py,sha256=lKLDDqZSdU7s6PyHbrywkaCJnMJ4TKSphRqmno7BcbU,2344 +pandas/tests/arrays/period/test_constructors.py,sha256=enK1P23xnwVAQl9RDH7BjHJV52L7UIIey-Pn0947eJU,4734 +pandas/tests/arrays/period/test_reductions.py,sha256=_3ke6g0JOfvowTV-D1XbZqWsWTC3q4qVN9OiGu_IFVo,981 +pandas/tests/arrays/sparse/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/arrays/sparse/test_accessor.py,sha256=LeUCMPTMkYOd0O3jvwkqg9rGvgVfTxEDzkKBNZ2w0c0,9233 +pandas/tests/arrays/sparse/test_arithmetics.py,sha256=hUGf38bAVYZ1pcV-bT5MKDtF4k3YsY5gkMKKy3BCuCQ,20316 +pandas/tests/arrays/sparse/test_array.py,sha256=XxVW3Z3AWbEY6JPMyORM0uSe-xqTGVLlq7BPH0jUYtE,18719 +pandas/tests/arrays/sparse/test_astype.py,sha256=MGW-bxHbKeY7FxpAj-FOFO1kd_wNKmqyEld6t_OuomM,4771 +pandas/tests/arrays/sparse/test_combine_concat.py,sha256=3NMQXaRQc7Bxn5HhSHffcUE24GZi_VYflnFLnixOgbs,2651 +pandas/tests/arrays/sparse/test_constructors.py,sha256=bGNIVDk0tgmdnVe6qCRu7cYm4HFumeNueQdbbZ-0xkE,10573 +pandas/tests/arrays/sparse/test_dtype.py,sha256=f-GrX8sGNhv5d_jWaPTJLeZ5_3FGyoXYm_YG9vLtnNo,6154 +pandas/tests/arrays/sparse/test_indexing.py,sha256=8INC1paA06XrCp8L63FSllr0OK48pgiKda5sOgrUhf8,10425 +pandas/tests/arrays/sparse/test_libsparse.py,sha256=CSXX321XDmXanhNfZpZUZvryB6bHEFqNyOMhaq_DRE4,19043 +pandas/tests/arrays/sparse/test_reductions.py,sha256=5snHOy85I6Ag2o6Y61g5icxDC1ZDbGXahhBjNIf513A,9641 +pandas/tests/arrays/sparse/test_unary.py,sha256=GtqeMdylKdtu-0HPxmTDVjo32riOcEtqPhjI_XK5LkM,2864 +pandas/tests/arrays/string_/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/arrays/string_/test_concat.py,sha256=6mqREqJWdNEPLIR0jfkiLnOFd6KrcBX7fJ7IOJzfQyI,2744 +pandas/tests/arrays/string_/test_string.py,sha256=VAHs3nLZU6qj6F805WDOPn5YJcyVrxx9H_pUcErh0_I,21722 +pandas/tests/arrays/string_/test_string_arrow.py,sha256=YrGLSEWbapjuIpeRzxzgnXVxETo-a8mAY4i4ff3-5X8,9364 +pandas/tests/arrays/test_array.py,sha256=n-PdAuAIwJyvnEYbXZcEu6nEL_5pIvNbhO_J3KrAilU,17755 +pandas/tests/arrays/test_datetimelike.py,sha256=zCltJMwM1vbJDzQVq_MNw9LdiMnwGKSqtALx0nb14qI,47055 +pandas/tests/arrays/test_datetimes.py,sha256=M3UxNuAgtIdnIj5F1dZoG_sXWAq4Uqjz3HJqBKq7o8M,29394 +pandas/tests/arrays/test_ndarray_backed.py,sha256=leQNBVVMi7w-CvMAywX9HPKhRo5dMumy3laWbEX87r4,2332 +pandas/tests/arrays/test_period.py,sha256=S_7TMRLEmVamhGKlVO50qJIj3OFDWRzY_oxEcXzp3zs,5572 +pandas/tests/arrays/test_timedeltas.py,sha256=Oidy9__c1O1Y42YcYchH3r_uIrtnkW4QzXb3T7qj1UU,10717 +pandas/tests/arrays/timedeltas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/arrays/timedeltas/test_constructors.py,sha256=1bVPW2JNi656WZXcIyyqrNlYucRh7wsC2L-HsL-EU-Q,2268 +pandas/tests/arrays/timedeltas/test_cumulative.py,sha256=cRR6I-lIsefG95vEZb8TuXdvmw7pdPFedpBneLVKBG8,692 +pandas/tests/arrays/timedeltas/test_reductions.py,sha256=iRxAs1JBWxBhgF3bFWjbBr55jfbRsrZc9QmUdi7Evek,6521 +pandas/tests/base/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/base/common.py,sha256=-cLXvhzuQi0XMfU-NdqTQAiruN0MU9A9HE2goo7ZzJQ,266 +pandas/tests/base/test_constructors.py,sha256=PTDPNhvLc6JgPYpGq28JLE6Qq2Q_xgXwFZH4UVQDPAw,5893 +pandas/tests/base/test_conversion.py,sha256=KcbFpF5iDxMAwNPLyr43hbnvX8xiq1YOTYyU5h9yrSQ,19335 +pandas/tests/base/test_fillna.py,sha256=sHvC5GxygkUnp4o-r05jC24ZqVZJv288_GPAZeGGb7c,1554 +pandas/tests/base/test_misc.py,sha256=KLRr9s1HcVVVVlattI1xQWtdm2qbVmuwuGk6bUlmTY0,5918 +pandas/tests/base/test_transpose.py,sha256=138_O_JwwdCmfmyjp47PSVa-4Sr7SOuLprr0PzRm6BQ,1694 +pandas/tests/base/test_unique.py,sha256=tZ0pjjYNnxWEPDNJf-rrc9tV6OVzos29BffUN6aFlvI,4478 +pandas/tests/base/test_value_counts.py,sha256=NJD4LaiLVZ4rvH8na32N1jJjlBQnPlIPwr2M-siVHxo,13896 +pandas/tests/computation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/computation/test_compat.py,sha256=dHstyvdaXybrwm1WQndV9aQBwOsOvCIVZb5pxLXsYfM,872 +pandas/tests/computation/test_eval.py,sha256=WajFmVP4rsRED6WeB-ekSwzCHPOOyp-2w7zVh1w4cKU,73144 +pandas/tests/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/config/test_config.py,sha256=4EYQEhqfNxHNKbxWdJBhsKTCgQFlPx1xBovgUtZJeBM,17864 +pandas/tests/config/test_localization.py,sha256=I8YOdIld1ts9VbIGQP__kqk9oJcp0GqMsJobVQxOw7g,4389 +pandas/tests/construction/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/construction/test_extract_array.py,sha256=L3fEjATPsAy3a6zrdQJaXXaQ7FvR2LOeiPJMjGNkwKQ,637 +pandas/tests/copy_view/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/copy_view/index/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/copy_view/index/test_datetimeindex.py,sha256=QYLX19qxsGD3J5xwgp67CICKgE4MQkg3dob5coEDZPY,2603 +pandas/tests/copy_view/index/test_index.py,sha256=GG20iKZ8ZaUwHEiZ2KadxobUkq5rrd866u9xilXfpkA,4575 +pandas/tests/copy_view/index/test_intervalindex.py,sha256=jIOl4EmhJ9Ev3Se55raAgonnhG6rSiRCctLGjMzb7II,848 +pandas/tests/copy_view/index/test_periodindex.py,sha256=ZRghsxqd4J8rsZnkgcqynWnSypRJQxXCAMwNGCB_HMM,1328 +pandas/tests/copy_view/index/test_timedeltaindex.py,sha256=t-Bo2o6TR9FUbDLRasbzbRqoxknv8Pj21LpR74UgovM,1542 +pandas/tests/copy_view/test_array.py,sha256=uAs8R-In8iVPdfqWBtA0is70shHluAGUkSlLQZaXnTI,6985 +pandas/tests/copy_view/test_astype.py,sha256=D2zI3uMcJrWUSKg144y4t2YIQZ3ONNuq302MM_0N3SI,7894 +pandas/tests/copy_view/test_chained_assignment_deprecation.py,sha256=-cPmBNKSRZh-1sbnCjmR4q5mq1zV2PcO59UIt1bmfuU,2790 +pandas/tests/copy_view/test_clip.py,sha256=9OZANEPhSEHam8IJUDAuM6nz94BNYrYepVYSysRm9sI,1928 +pandas/tests/copy_view/test_constructors.py,sha256=Jt7f4iuaB_KLiuOBytubxP3Y4qRh4YtISt9JjdhO430,13225 +pandas/tests/copy_view/test_copy_deprecation.py,sha256=8kn_jH9EgmHSaOGeUd80aInHrQ0uzDfwbnMQz3_8OCs,3132 +pandas/tests/copy_view/test_core_functionalities.py,sha256=elZjsH6zGlJ-IVhOi8qZ9X2wtpo_Q0DvIEJM4e88APE,2899 +pandas/tests/copy_view/test_functions.py,sha256=3kSdgCTyB-IuXd9uCFz122Cpu07Y540-YFGlR2alu_Y,12231 +pandas/tests/copy_view/test_indexing.py,sha256=IlJWUBi8j_92zmnpGkqUEGlMkrHcKv5OGEZ6hSV3AXg,28574 +pandas/tests/copy_view/test_internals.py,sha256=5l0tdVB95PcWIE_2pF0IYe7mjPp9SVy3VOy9pavN5bA,3588 +pandas/tests/copy_view/test_interp_fillna.py,sha256=iOA3XAvhCHMpBtCOFnzy7I3pAoV9zQsSUyzwL94--j8,9726 +pandas/tests/copy_view/test_methods.py,sha256=4cjqGAyNQIEx_FhDczp3UL1PIjy08fTBTGdt7YqlzjE,51839 +pandas/tests/copy_view/test_replace.py,sha256=AVLFlyUvHwpmb-Bz5UcSmUcQgywPpR8MbQeE_R5DPlg,11799 +pandas/tests/copy_view/test_setitem.py,sha256=MppBYOnT2niRrVYEfHZ--SI7R6VjN1IUHMJkQ4mlUgQ,4167 +pandas/tests/copy_view/test_util.py,sha256=ClWLprMJhf6okUNu9AX6Ar9IXZgKkY0nNuDzHRO70Hk,385 +pandas/tests/copy_view/util.py,sha256=oNtCgxmTmkiM1DiUxjnzTeAxCj_7jjeewtby-3gdoo0,899 +pandas/tests/dtypes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/dtypes/cast/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/dtypes/cast/test_box_unbox.py,sha256=0sqYy_9q8tBcJipEvVEDSwS0Ns7iPvjZ4pQqckLWMTc,2930 +pandas/tests/dtypes/cast/test_can_hold_element.py,sha256=s1_AB3htfzxpXN5NABrppxyZsZfEnNSd-2Ak266aoG4,3260 +pandas/tests/dtypes/cast/test_construct_from_scalar.py,sha256=INdOiQ7MowXLr6ZReCiq0JykUeFvRWocxk3f-ilk9v0,1780 +pandas/tests/dtypes/cast/test_construct_ndarray.py,sha256=D52osZAHEuY2w3GdzH05y9WD_ghLIySgfKaIJpnLZAw,1316 +pandas/tests/dtypes/cast/test_construct_object_arr.py,sha256=eOmUu4q0ihGTbYpCleoCnYtvwh1TBCEZQQjLeJaUMNA,717 +pandas/tests/dtypes/cast/test_dict_compat.py,sha256=qyn7kP5b14MywtqOUL5C-NOvjf2qK4PsXGpCvqmo-4E,476 +pandas/tests/dtypes/cast/test_downcast.py,sha256=x86-yEwGdu-9KFC_BBmLrZkf7TF-ATJ5oVfMl2LzYe0,1464 +pandas/tests/dtypes/cast/test_find_common_type.py,sha256=c__GbgnRawwgqWut8g5Q928en8-_O3oTZEQVbqQ8MrE,5226 +pandas/tests/dtypes/cast/test_infer_datetimelike.py,sha256=6vor_eqEbMKcBLEkfayXzVzwwf5BZcCvQhFZuqhvyKU,603 +pandas/tests/dtypes/cast/test_infer_dtype.py,sha256=hT7Jj9WMrSZPagKveZxvFMeUoXtSrxgWyYps9kTv5I0,6060 +pandas/tests/dtypes/cast/test_promote.py,sha256=B4dgs3EWIm8qKuoQMn6FNaGGf_qAm_EAm4l2X3cHDMM,20755 +pandas/tests/dtypes/test_common.py,sha256=xjajtXn50oxsqu_0gUruvC0REWdhJ8FNpjvhNhV9Ngw,29274 +pandas/tests/dtypes/test_concat.py,sha256=5cauuyXBlDFs41YtZLD9WE7qLDdAihoBESOEuoPv69o,2086 +pandas/tests/dtypes/test_dtypes.py,sha256=Bb2ZfufXfag2i-RmuZ5nFOLABzKYt3-6KQwwofKj4UA,44513 +pandas/tests/dtypes/test_generic.py,sha256=CK4oZubxCE4kOaOWtalRt_n_glMFV4QNskNVwQs9oi8,4794 +pandas/tests/dtypes/test_inference.py,sha256=BV5E1x23_ryzRoBdF6WVD7zyTVU_y2e_Y_7UKsK9W3s,74587 +pandas/tests/dtypes/test_missing.py,sha256=nA788RgBEof9x5SNI-YqV5A8xMnn_jMvKX2lSZuYkfk,28653 +pandas/tests/extension/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/extension/array_with_attr/__init__.py,sha256=bXkwWSW6GRX8Xw221iMyaQOQVaWmyuRP3tGhvjXtiV8,149 +pandas/tests/extension/array_with_attr/array.py,sha256=_C4NxQhPjuK6JLPrf6HKFDLdXRQnTG9OTU5dR3uyw3w,2481 +pandas/tests/extension/array_with_attr/test_array_with_attr.py,sha256=TuuBA1lCxjVOgWsWM9jhgc-PyGuXzajO3UWWKZEquZA,1373 +pandas/tests/extension/base/__init__.py,sha256=vZhv_NY2I3z-jumLAEIvmx-P-FbY_ytENliIOSVeL6s,2870 +pandas/tests/extension/base/accumulate.py,sha256=JHnjvzM2WPD93_WXeay6efj1Pr1vso0llfr5RvQFdAI,1501 +pandas/tests/extension/base/base.py,sha256=aSfTPvuvzzQUxEIrGUASWuwcVv6Uw5bvkFXvqjhRV1M,35 +pandas/tests/extension/base/casting.py,sha256=_w7J_Hpw-CiJcDyuppEtk7ECPgZ1_OrmaHhJ6TtgRPY,3254 +pandas/tests/extension/base/constructors.py,sha256=EfQrYciCho5LOAAxbNOlIuneBnASOkoYiYqGe6t5AWw,5623 +pandas/tests/extension/base/dim2.py,sha256=i4Bn1zQibBELjwW9bNv32fnSF4ase2DrdV1b2myrf7A,12454 +pandas/tests/extension/base/dtype.py,sha256=e-juPziIeeg11WBHRmAa_zVi5fe-NhMKsiM1kSW--OA,4005 +pandas/tests/extension/base/getitem.py,sha256=66fC8PaJ8pK-Ugcj9oeSr2q6Dy6vnr1KUlVD3Oewx8s,16534 +pandas/tests/extension/base/groupby.py,sha256=zvFT_JxAp3jSm1GcMZGsLrAAR-PyC89aaQqmVuid32k,6247 +pandas/tests/extension/base/index.py,sha256=TkBotnkl4bzF99PUaBQDQgzZTJTeOW1J8J6lEjLnNNA,518 +pandas/tests/extension/base/interface.py,sha256=hwx4C_MvCq2jm3W-eC9y6u6kJoIZPPpVBw3YlplTqX0,5955 +pandas/tests/extension/base/io.py,sha256=GKIjiAhtPXOJVDTuXmVU5O76CFKYkp_yQrQTKh1cpMw,1474 +pandas/tests/extension/base/methods.py,sha256=nPyOGMZCv3lD8Vx-g6urxVbG1Szu7e9NvngkxggyL2c,27400 +pandas/tests/extension/base/missing.py,sha256=WjpmMMZQEHG4L0sTBiiAzT3SsO33Xz4LOtTMY_DpoQw,6774 +pandas/tests/extension/base/ops.py,sha256=vZgznKhmDJT6-a_diQDTgqJ8iMfxeacfKhZbJ40rtm4,10631 +pandas/tests/extension/base/printing.py,sha256=cNghjo7KwrNv4Atd729LQazrOd8cs3P4DOk7a80HCTk,1110 +pandas/tests/extension/base/reduce.py,sha256=4lBdIlGTbeuOw1TM--OE0aamrYZ0UpuWne_9uo3jqmw,4975 +pandas/tests/extension/base/reshaping.py,sha256=A3Fdz2pDhdKxrew1jORQg29_Sh3Q7DEV7qF-Ovr9RUQ,14477 +pandas/tests/extension/base/setitem.py,sha256=eViwa3IwDrPSgeON_RjLECbJrOAVXV1oMM6StUvwcCM,17820 +pandas/tests/extension/conftest.py,sha256=iViWSBwUGzdo83qmNxTe4342wGePKd9BDT95YgbmvMY,4725 +pandas/tests/extension/date/__init__.py,sha256=-pIaBe_vmgnM_ok6T_-t-wVHetXtNw30SOMWVWNDqLI,118 +pandas/tests/extension/date/array.py,sha256=j5x5VzbdBT8c6HvFQ--jP55gJ93JaRfZ6I3ASHe6Dmc,6182 +pandas/tests/extension/decimal/__init__.py,sha256=Dv6omEVqBNzcg5SDDhX_cyzt8wYjq3NbuSvmmHzoq8A,191 +pandas/tests/extension/decimal/array.py,sha256=NQgcb9AH0ilbBSM9t-zJYBP0k3XGp44EPu34aREUxgg,9966 +pandas/tests/extension/decimal/test_decimal.py,sha256=6gTI7W5RZSg4uF5Do_cJ3n_xChYZ2Ig7D0rX93_Wpnk,14691 +pandas/tests/extension/json/__init__.py,sha256=JvjCnVMfzIUSoHKL-umrkT9H5T8J3Alt8-QoKXMSB4I,146 +pandas/tests/extension/json/array.py,sha256=7bfgJQIqrc7VV2hm74wzkyEaKM9ykvwoqZxrVUtMd4Q,9302 +pandas/tests/extension/json/test_json.py,sha256=qVAJhNCu68eNTDS6JS9LqfXF073dEG2LU8ycg5HN-58,18221 +pandas/tests/extension/list/__init__.py,sha256=FlpTrgdAMl_5puN2zDjvdmosw8aTvaCD-Hi2GtIK-k0,146 +pandas/tests/extension/list/array.py,sha256=wwIDGwpkBPPtfhd6fol2NiK9Hn0b0QYfFrBcZVawoEs,3973 +pandas/tests/extension/list/test_list.py,sha256=81cL0OCMdEm_QfSCCt_NT2FI7wGCpLHQ5_DRmwjRXKk,674 +pandas/tests/extension/test_arrow.py,sha256=jdwkiGN67a_MOTRvgt7NwAT8KuepF7pxYlQ6STDVhlk,134406 +pandas/tests/extension/test_categorical.py,sha256=XJgIVzfSUxxqywm99DuU5BaF71pdRQB8G5ck-YeNIqI,6481 +pandas/tests/extension/test_common.py,sha256=kaG_5luKu6f1p0WCcs4xzQZ7U1gCWqbf8KQUkyW2to8,3071 +pandas/tests/extension/test_datetime.py,sha256=vzcg8U8qoVVr-1WzuLJg0ZHW5L01i8kpmybwNtv7ru8,4678 +pandas/tests/extension/test_extension.py,sha256=wF8q3HwXA6XXXLAt4tNHBgbzX_Xrigc_PuoxCCGrbLA,560 +pandas/tests/extension/test_interval.py,sha256=19LLjMDbI6Wu47WpkyD8qaaoUFTH5NVe0tlJ-1AL71Q,4511 +pandas/tests/extension/test_masked.py,sha256=9fW62mOwH2oI-1tezNqlpoCeaAiSwimUAP33JQ4CX-c,12722 +pandas/tests/extension/test_numpy.py,sha256=4LW2rrgJNZ16nNSoASOAihObxniZtzVf0cLTteF3Egk,15893 +pandas/tests/extension/test_period.py,sha256=xY6GmFtWuP2xzQkV6sKrqz8zNWbB3qD2P_D56uJIuDg,3355 +pandas/tests/extension/test_sparse.py,sha256=mEHrjRnGOuqY8qCw5r_CxHnh3cEfLd7FXzuySSzvag4,18633 +pandas/tests/extension/test_string.py,sha256=zgAUUxIEO8_1dVvGO7ny6tT2Cq1FfBiWQqwph8YQmNc,11021 +pandas/tests/extension/uuid/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/extension/uuid/test_uuid.py,sha256=BFXvYTuP_nfVtGyitB--u-MIHnmenEaigGmmDZD7HOI,2354 +pandas/tests/frame/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/frame/common.py,sha256=BmnEMlREF7G0B5zdaJRsdzqIRdh8diiTisBbCVI6Fp0,1873 +pandas/tests/frame/conftest.py,sha256=NDwRFgysnkp5Prmz0L2xJp49WANvaGF7Jn3FIX1Bu2w,2647 +pandas/tests/frame/constructors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/frame/constructors/test_from_dict.py,sha256=VwZZNOdlTbHQTO4vSUV-s58Bfx2XbsutVd0irNEmhfg,7988 +pandas/tests/frame/constructors/test_from_records.py,sha256=IFspKP9uT-0pzF5ZxNn1kt4lEchcPoC8oRyytbakbdo,19533 +pandas/tests/frame/indexing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/frame/indexing/test_coercion.py,sha256=yISEuNN7Jp2ikav7onaXxeycGtRGXX7bxYUqZk3zQZw,5125 +pandas/tests/frame/indexing/test_delitem.py,sha256=-YERBfZbhTZ3eKzjmWln8AjoQEO7Yvae6elau4njhM0,1832 +pandas/tests/frame/indexing/test_get.py,sha256=ZdKObPi4CUVcs3XB9aR-4KcfMeDdrW-pdkLtPn00Kmc,690 +pandas/tests/frame/indexing/test_get_value.py,sha256=A-GbCHlbDfVPGB10dNGnGg4DtrKrlRbRspYfuDTUmPM,679 +pandas/tests/frame/indexing/test_getitem.py,sha256=HZ_JML63Vfp3xjJWDi6_-d5_YY-DhWywl30EHYr4vWo,14844 +pandas/tests/frame/indexing/test_indexing.py,sha256=ZKHVjVE1JsNn5VsQp8PhycPnsztz7GAWLDDyHl4UFCo,67171 +pandas/tests/frame/indexing/test_insert.py,sha256=xhBdlOkaD9q-EQ7Za-4mNTyAKnwk4EvvtFwTLFziJZE,3882 +pandas/tests/frame/indexing/test_mask.py,sha256=vHrY_nbjz86u1J3kiVdoUMR7uXEzkBN1rDgOBKHiszY,4941 +pandas/tests/frame/indexing/test_set_value.py,sha256=mXxXUehoHsqe8vpH-NF7iehZXzflE4HAK3_GONwFxis,2492 +pandas/tests/frame/indexing/test_setitem.py,sha256=raMj2Yoyp0JL4OuwZocNJinrPRTEjl-kQrzDGWu1TNo,53289 +pandas/tests/frame/indexing/test_take.py,sha256=T2UkOudAk18icTag1S8f4MOiIFQfOb8BWEh2uXCS1XI,3229 +pandas/tests/frame/indexing/test_where.py,sha256=auqY4VvypsrYOJBvaxHinVwWtQr0nQQqPK-Jzp9AjmA,36531 +pandas/tests/frame/indexing/test_xs.py,sha256=zrZrhoZTon8w4EH2_3m1gddDwSc71wzRLANOBZhuScg,13520 +pandas/tests/frame/methods/__init__.py,sha256=M6dCS5d750Fzf9GX7xyNka-SZ2wJFCL66y5j-moHhwo,229 +pandas/tests/frame/methods/test_add_prefix_suffix.py,sha256=iPfzSPx0CArx79na7xcI9ZcPTAwq73IdOCcREVO7k4E,1910 +pandas/tests/frame/methods/test_align.py,sha256=l6uP5SOx36BTXyXmVH1a2Bg7mcbEEyp7Ran9V3vbjGE,12243 +pandas/tests/frame/methods/test_asfreq.py,sha256=2EBEN29AvVC_oAI2sfvGL2NJlG5mxpJrnX8qgcn88Wg,10283 +pandas/tests/frame/methods/test_asof.py,sha256=IHTxmBQTYEImScOqlt8cAJNVhUNjt6Y_hymNLs0DLvU,6248 +pandas/tests/frame/methods/test_assign.py,sha256=xFGREzLhP1wj3MowBimeYbMWBNiII0280DiOXI6WDB0,2982 +pandas/tests/frame/methods/test_astype.py,sha256=U1BjJ2RspL5i7Ssy85PqSI9nt6v6s7Ax08e5eCqDAQo,32567 +pandas/tests/frame/methods/test_at_time.py,sha256=_rawH5yY4uz5HFYC_ZRPwwixub1svsCeEM80ooekjAU,5521 +pandas/tests/frame/methods/test_between_time.py,sha256=rD-k1a4LVOa-nMlLXOaZO7iTa3hL_C9tghqt8DWW0Qs,8083 +pandas/tests/frame/methods/test_clip.py,sha256=ivSuI5O0WhElXdlsQV102oXb5VV3opUBe3_DPJi6Xgk,7355 +pandas/tests/frame/methods/test_combine.py,sha256=jJChWwgtbPWAnUDlrmn64tBF63FOzm-WQFRPYvcWKPQ,1759 +pandas/tests/frame/methods/test_combine_first.py,sha256=LbKJU9xp1mV4Pl5RkRrOapi4MpRGG3bNhNdDckag6Kk,21491 +pandas/tests/frame/methods/test_compare.py,sha256=8ZoQv6xmRKJI3OksSO7zpVjYC7x8qJximjA8X8ADTH4,9526 +pandas/tests/frame/methods/test_convert_dtypes.py,sha256=J2VTHn4nLverHAnrua9eNO1udysJsllfxaH1hXrA0MY,9793 +pandas/tests/frame/methods/test_copy.py,sha256=OiNYAal6v-VDC8J6smUNaX0-RAi7YfBkOi4PstmX3yA,1266 +pandas/tests/frame/methods/test_count.py,sha256=avzIu1dZ3pls4SM6g173M7Q4i8zMUzeAVI2EeIzWC0c,1083 +pandas/tests/frame/methods/test_cov_corr.py,sha256=6_bleJylWweQkGvvwZpUGFL-FaGV9cmIkw2FT9l3EVg,18910 +pandas/tests/frame/methods/test_describe.py,sha256=0YXKYVADTFAlr1YZyxJDaeu8CZplgv_A9tpAZL7Vkus,16010 +pandas/tests/frame/methods/test_diff.py,sha256=tHLOTEUYx2IhxVy6hUFO0pEKrXwjhJmcTuCeNZjIphw,10218 +pandas/tests/frame/methods/test_dot.py,sha256=YGTH17pSNGllb7trAT870f71rqsnJ6aYcG0gCh2QsbU,5116 +pandas/tests/frame/methods/test_drop.py,sha256=dEfHK63HdWZ9LWPjKsMmK0BJr9LLrGbEwfJ1pLb6cIg,20740 +pandas/tests/frame/methods/test_drop_duplicates.py,sha256=r3P2eKj1-qhT_ZUHfZ1mi32Jp7gJW2dkB3v5-yL7N1s,15723 +pandas/tests/frame/methods/test_droplevel.py,sha256=L1gAMjYYPB6eYmSppXfbwPVKa3HCNofqPVUZ3gxLldA,1253 +pandas/tests/frame/methods/test_dropna.py,sha256=QHVhyk6XKLzrjUEYWouEH5Re05HtvMqPuRddjZ_Uxes,10331 +pandas/tests/frame/methods/test_dtypes.py,sha256=GHUJol9WehHOPlxdIebd22Qpnhb2v0Bc-XWs5db4PHU,4672 +pandas/tests/frame/methods/test_duplicated.py,sha256=1DQFuK4KjfSpsl8W0jXne8PPUsL1nFe3lI_9VYBd33I,3305 +pandas/tests/frame/methods/test_equals.py,sha256=L6wX_rxIhbT_SLiuELOQdVdPQahaZ24ObNDA6On-E_I,3407 +pandas/tests/frame/methods/test_explode.py,sha256=b4Q5DAGmecPEsRXM_cNTPPyeR1GX88NwHAqWRFQzcXQ,8952 +pandas/tests/frame/methods/test_fillna.py,sha256=SIc4Q1tdZRGmTCCdADsXjt0dPR2D6nbDeNV1N7X89dw,29085 +pandas/tests/frame/methods/test_filter.py,sha256=hWjcxjxDMGsNzmZqqGem6hx7xMzRDhhGs_R_YkuGpwo,5373 +pandas/tests/frame/methods/test_first_valid_index.py,sha256=tvxyc2l_BIXsEzkF528Iwfbl2PSqDrAwxrqLtHsXSls,2575 +pandas/tests/frame/methods/test_get_numeric_data.py,sha256=RvYb0eiBKeJ3IOATgm5KvLuVrd3ADzPVOYdZ45buDKk,3379 +pandas/tests/frame/methods/test_head_tail.py,sha256=quuFkpS5IgonJDSb9_Po4eO3Wi5wlcNKq723EMYL6Ns,1935 +pandas/tests/frame/methods/test_infer_objects.py,sha256=1uwUOAzwWLakYugupffGubCmjLMIO7900QO2H6JUJIY,1241 +pandas/tests/frame/methods/test_info.py,sha256=UDpr38SEPvNwNbpiJ3PB9LqH8sAoLJpffSk9yUw0g4o,17773 +pandas/tests/frame/methods/test_interpolate.py,sha256=HFwkFRWzOhgT1h-rV95IwckWrJN9Wv0x1_FfR45yhYg,15299 +pandas/tests/frame/methods/test_is_homogeneous_dtype.py,sha256=fCSED5uVWnzczpnoCKgiqSRYO3q9pFdTRo_iq1dUPm8,1303 +pandas/tests/frame/methods/test_isetitem.py,sha256=VoxA-yXow_CRikJ1tlni1PsAAOT1D2X8PtTZyJOGQXU,1428 +pandas/tests/frame/methods/test_isin.py,sha256=P2TVUsL_p366aSxwWcq27VlT9zFstOXlsJSTFlw2n20,7599 +pandas/tests/frame/methods/test_iterrows.py,sha256=hfFRA20tRYmXJAoJZLGI04J131Z7QaaEbINm3FwfVbQ,338 +pandas/tests/frame/methods/test_join.py,sha256=pJOPXq1PihWyChNz44QLMZwMZo3-KEDbnUFO46VXFmQ,18110 +pandas/tests/frame/methods/test_map.py,sha256=5JKXJT8fIYs-kllI0FHmiQQkZVAsgz_tuKhE-SWJnP4,5718 +pandas/tests/frame/methods/test_matmul.py,sha256=i1BG41S9da2R0nATvc3kZXsiwl5t6MHDFIb0IJ4lAbQ,3137 +pandas/tests/frame/methods/test_nlargest.py,sha256=b2Xc8qjrWtPeQ3d7gnusN2NgwXBLEKmIXEFONSZMDG8,8058 +pandas/tests/frame/methods/test_pct_change.py,sha256=aDKHXEXNXo2bG2acBBqv99qt-cP6bE31ZDPLqM9t7Z4,3632 +pandas/tests/frame/methods/test_pipe.py,sha256=ts5ghk8g6PYXKpdsBdovBXxPGO2qq75FEVzBgjAVfRw,1023 +pandas/tests/frame/methods/test_pop.py,sha256=pXEuon1ds2Kut_Kq8q-MW0zEiCZ-RAFzAG4gHyeA-p8,2143 +pandas/tests/frame/methods/test_quantile.py,sha256=MMHWn4V9lgA-1CQqxMLOHpHctTB7_xys-wR7u-Z1LWE,33875 +pandas/tests/frame/methods/test_rank.py,sha256=7LL7hY32NUvLKaS4JXxr7WURWLmPuWWueTz5uZ5Isz4,17401 +pandas/tests/frame/methods/test_reindex.py,sha256=gpRoj2Zv9gJJetvh4x9vOlCU6crHHPFwJsp_PSq4LeY,47624 +pandas/tests/frame/methods/test_reindex_like.py,sha256=aDtkUv5qwvyg_Eas7hOsAzeGk9AnsSb83JXBs2SfckE,1351 +pandas/tests/frame/methods/test_rename.py,sha256=9vqGb5HJcHacDg3mmK13LCRzp_Kf98D1jnotEqg0f6M,16329 +pandas/tests/frame/methods/test_rename_axis.py,sha256=xFKVxgP3Mdvg1DKW9RDm-iQhMucOSVArJGgiokBqKDg,4539 +pandas/tests/frame/methods/test_reorder_levels.py,sha256=VJVEdltyRoz89mQR1Xp0A9yKlTeEFIpsPaKWQujT-C8,2729 +pandas/tests/frame/methods/test_replace.py,sha256=e15yHcsOTy2ObNHmLO7TjhUrYv1_1ARLldFcySCyDPg,57703 +pandas/tests/frame/methods/test_reset_index.py,sha256=hTQ4jkYKp1WBvvH5l9kcEQVdk7iKydjEcPgQeJp-4xA,29136 +pandas/tests/frame/methods/test_round.py,sha256=JNElRHR_gciVU5FB_eRU1Kp2QhnJ-qEDLbDHXyrci70,8268 +pandas/tests/frame/methods/test_sample.py,sha256=L8XF5UQJbjtaDdArsUc35OULfkjh_dmhSZKg4gl9dGA,14228 +pandas/tests/frame/methods/test_select_dtypes.py,sha256=k6Eok4izeC-8I3I9l4zD9rc34-Yx14OCseL7Aya8kE0,18889 +pandas/tests/frame/methods/test_set_axis.py,sha256=7cVnpS3ffXSAYJkjvfh9lu6ER9WfZzq6bnpnmV3v84A,4118 +pandas/tests/frame/methods/test_set_index.py,sha256=RLEUwoniaXjXO_z4CkyYM4uEZBn5VyoS2suTvoipGh8,26833 +pandas/tests/frame/methods/test_shift.py,sha256=rXeHMtR4C7oqpyhwcaWRUT2CRUlxezTqP-2KvEmsSKg,29601 +pandas/tests/frame/methods/test_size.py,sha256=zFzVSvOpjHkA9_tEB2mPnfq9PJIBuBa4lCi6BvXbBDE,571 +pandas/tests/frame/methods/test_sort_index.py,sha256=2J9mduy427H6fI5AjVZToZGDMjXlZQU06OPAUvMxGHA,35057 +pandas/tests/frame/methods/test_sort_values.py,sha256=t8SArnOurYJiGXhW01ZmlpkLfIJ1fAu4jWhy0jOv-qY,32000 +pandas/tests/frame/methods/test_swaplevel.py,sha256=Y8npUpIQM0lSdIwY7auGcLJaF21JOb-KlVU3cvSLsOg,1277 +pandas/tests/frame/methods/test_to_csv.py,sha256=EzVo_jIx9o3F3mf6NqB4fu3CqPbnWuAVkCA6DeJ1q-U,52039 +pandas/tests/frame/methods/test_to_dict.py,sha256=D7Rbd3DiFMDqUxvorANF8briEmMYDO_rzVmhLcEkJxw,18815 +pandas/tests/frame/methods/test_to_dict_of_blocks.py,sha256=obssmPlaJNrPP9G1ySI2Stv1P5mmUfHo3J2MAX5m5cE,1109 +pandas/tests/frame/methods/test_to_numpy.py,sha256=UhsLa2O6BBuwF8vOg73OeCcwRI285CY4nj931fcXLs8,2782 +pandas/tests/frame/methods/test_to_period.py,sha256=Xiebi3IA_vUKrFNftLBkhF4N0gMbpI76ZCQpqhgO4iU,2863 +pandas/tests/frame/methods/test_to_records.py,sha256=xfbqpi1zbdOFtpLPQBDmzm9yfR3Pe-WiT7IbsaSU13M,18597 +pandas/tests/frame/methods/test_to_timestamp.py,sha256=lgdLxkELrKtyJMbM0ChpMRa7Anw7MnENq1d6p7QCrIg,5866 +pandas/tests/frame/methods/test_transpose.py,sha256=6pEegeKjtBbiLEKjW0vhMY9UpC34SULqGLkd0Dc_R8g,6438 +pandas/tests/frame/methods/test_truncate.py,sha256=j0MsKVt9jofIvDwfuFRooVNFnX8nKZe1EyqQgOU8m14,5216 +pandas/tests/frame/methods/test_tz_convert.py,sha256=OR07TwUhNNxBBDKbaUach9MoqGcz5OBt0uwE5aR-bhA,4984 +pandas/tests/frame/methods/test_tz_localize.py,sha256=zLSn28Z-h362SclQE5lnnR9LUfd722WsM-MqLD29NkE,2015 +pandas/tests/frame/methods/test_update.py,sha256=AQlbc7L9Nj1QtIpAZ00AxKwW_eV5fXY_-t45VT5ji9A,8642 +pandas/tests/frame/methods/test_value_counts.py,sha256=rpxC1EaCQyrQPEOmTIYXpTxt8oAvhS-9no4KpoqLnE0,5556 +pandas/tests/frame/methods/test_values.py,sha256=rw5MP_Ls9Iz21whXksM51LMbA-YeSnbmQT6pMFYCUmY,8722 +pandas/tests/frame/test_alter_axes.py,sha256=SeG2vPocJ_l12jCEjr920WBkj711SW5ByvnGJ-ia6yI,887 +pandas/tests/frame/test_api.py,sha256=fKiPOmzQXYzUNtpTfkHUDZBlo8XWblv6t-0eaRJFhQ0,13088 +pandas/tests/frame/test_arithmetic.py,sha256=BszNa9E_243Vao7hKco2IC0Nf78H9gTbli8EG7q7bmY,75915 +pandas/tests/frame/test_arrow_interface.py,sha256=SKmj5gXI3RiHJ54zl8IkjDvSgDuEfk-hvy2smKDezGE,3094 +pandas/tests/frame/test_block_internals.py,sha256=cIejXx6TwxEpUZTlARyyS-bjgV4M2RbIYxTcER4hNP4,16514 +pandas/tests/frame/test_constructors.py,sha256=5zH_6rpgIdkleasvvCNkWxEg4fEMN7wKR5sK2if32s4,124399 +pandas/tests/frame/test_cumulative.py,sha256=o8wB3w3qZ4ZnudftR9Y2hVwvW5PDAO7oTSKTnuvgKVQ,3278 +pandas/tests/frame/test_iteration.py,sha256=BuyW6QePxoNZl-Cgxp5WLah_e-kSK2hsN8Gud_g0aoc,5077 +pandas/tests/frame/test_logical_ops.py,sha256=yYxv_afM5NA4Jur7Qa6imOA5aJVVBCsSf-ZgucqJUYI,7066 +pandas/tests/frame/test_nonunique_indexes.py,sha256=DW0cVxjUr5LHoUY0hOVMAmzAbKuucPzoD7mCcMQvjIk,11872 +pandas/tests/frame/test_npfuncs.py,sha256=1jmxF6zHLpFQQQkTgypb4rrZ8PNPctMU7Pglf1eJ2MQ,2477 +pandas/tests/frame/test_query_eval.py,sha256=usng_Kf-JhBEDGEtgm2-mFwSD3p539Z9fevW-cXRLz8,62027 +pandas/tests/frame/test_reductions.py,sha256=i6c-PmvhhcsF8U7yET6grjNdUeFfZFqo3MTawfoROXs,79781 +pandas/tests/frame/test_repr.py,sha256=rINp_YSgbTzg_OclMThX8Ara_ayo1bjXTFpf0zXA9XM,15888 +pandas/tests/frame/test_stack_unstack.py,sha256=3HTtjb03yH6qIBZHjH1NWTysKzEIJ-iWd2wjI4ScBuM,100501 +pandas/tests/frame/test_subclass.py,sha256=h7Ch73prn9huv3s-khS7JlXHEAtQI9VjWVggBOuNIkA,27401 +pandas/tests/frame/test_ufunc.py,sha256=QXd8-dr8tfREy6wnoVtMyVgOd7SiTJsIbSJiefXZqjc,10580 +pandas/tests/frame/test_unary.py,sha256=QJKEGtMFzbaAByUevEWcVbzUpZcG_17bzpDYbJWB_bA,5744 +pandas/tests/frame/test_validate.py,sha256=wI43q_zEAC8MOKlpgDjnm4W3OpIMBsVWUoI2j-TJR_0,1057 +pandas/tests/generic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/generic/test_duplicate_labels.py,sha256=wb4Cam7IPRyxrbMk8RQZK_rbyvSef8zW61mfU8MGntc,13611 +pandas/tests/generic/test_finalize.py,sha256=AiSgyu40_JGF3l841D2tRoS3yloHpZmr8xSE49lnEs4,28550 +pandas/tests/generic/test_frame.py,sha256=6WCBMHY2uXZkGN_LUO20YD7ExvHg-ik5wT5s6dWfBTI,6966 +pandas/tests/generic/test_generic.py,sha256=y8U4Po4VRVN7rtyoWmxipIfTvqaUK7VuXgx0ed0vGI0,17027 +pandas/tests/generic/test_label_or_level_utils.py,sha256=TM1MiZddd3EiMIuu-hbN4n1nPonbxhq0IVbGyd3-z-M,10122 +pandas/tests/generic/test_series.py,sha256=mBouhuP7gh7rIjwmByaSGIa9MU2tNLPou19RgRq2MD0,3994 +pandas/tests/generic/test_to_xarray.py,sha256=XUIEQtgqr4NFvRLoi8KTk-WmGw3If7iQW_kuCgRW4bg,4515 +pandas/tests/groupby/__init__.py,sha256=zHEFNACJrOe_gq6EAbRv3-fQwdTMBHxx5MTk7lOvrko,639 +pandas/tests/groupby/aggregate/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/groupby/aggregate/test_aggregate.py,sha256=BT_4TzV0v4GGzMZJaaAPoHJKhLsGGYsMCMKsftICZVM,64608 +pandas/tests/groupby/aggregate/test_cython.py,sha256=fTervDKP59jKlEgHyKU2fz6e0N4k-4LN-zgrlRkf3Wg,11913 +pandas/tests/groupby/aggregate/test_numba.py,sha256=Q1MHxY0BM8yeXGdSTMllF_Hq-dWrwsmoDmhi93R2xJg,14930 +pandas/tests/groupby/aggregate/test_other.py,sha256=-NhcqjkXl19jMKtBGs-dEMZIcI3SK6rpYqoWlf8amkA,19899 +pandas/tests/groupby/conftest.py,sha256=8Ib7r7ggGR6boPyDbWSdLoUo8pn9rbQQJQ0Dih37GkM,3893 +pandas/tests/groupby/methods/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/groupby/methods/test_describe.py,sha256=HvjzDRCLG_c9KUTnu7J5vWV0PpjxF4sGqCGgvbuev64,8851 +pandas/tests/groupby/methods/test_groupby_shift_diff.py,sha256=bZXfPG-a_HPbM4169aZMK-SPUIA_08m7XAeIWbyenas,8366 +pandas/tests/groupby/methods/test_is_monotonic.py,sha256=OpnlOamR5gX1S7MVtZFGxnbt1Fem_wWH1Irc5aqkdq4,2566 +pandas/tests/groupby/methods/test_kurt.py,sha256=Efx4zz4xtpX3smG-u98QQwujXkinagCRMrpf988r614,2391 +pandas/tests/groupby/methods/test_nlargest_nsmallest.py,sha256=KP1NdcsYXFJZ07deW3TkikpGk2AF-AF2fffpi7T_t40,3363 +pandas/tests/groupby/methods/test_nth.py,sha256=zWl_zLLqJj0sgKqd9zACGP5npB17fe83pS_5rFDpvS8,25860 +pandas/tests/groupby/methods/test_quantile.py,sha256=-hee5JijC5x3gnCaKm2e3Atqz3HKwB0CiNiuKxOaYIM,15402 +pandas/tests/groupby/methods/test_rank.py,sha256=DbkUbT-4YY1KitQFwuKXORncAxmx6vKb-Sbz02-bc0s,21504 +pandas/tests/groupby/methods/test_sample.py,sha256=n_dLYblQo9MWnpngMRIIGLZFGEGOeAfEqsL9c9gLCKg,5155 +pandas/tests/groupby/methods/test_size.py,sha256=ysfjBTi6dlRsr-CvErnS4ba3pWQa78jPqtH_1sRL7Mc,3011 +pandas/tests/groupby/methods/test_skew.py,sha256=_FTlnXtE_fic6ZZ322S583IXUY5hEQggi-3Xbuboahw,841 +pandas/tests/groupby/methods/test_value_counts.py,sha256=zeAarZ10fHRoZapPZy7ZZpziaoFXg28m3DPMDti8ZS8,39563 +pandas/tests/groupby/test_all_methods.py,sha256=ZnE2n0DA9qkuOAlJMMfdSn0DTXgSPJMWy45A5ZefDq0,3564 +pandas/tests/groupby/test_api.py,sha256=EQHD4ICfnNybpi2iU8yl29FkIBrwxVkYurEa-ZnzzjI,8554 +pandas/tests/groupby/test_apply.py,sha256=ALpa77wpcBiwU5XxAdZYjLXww0SEkx-LzsNxYhmdKG4,46178 +pandas/tests/groupby/test_bin_groupby.py,sha256=wn54JkD906d-bLPiu0xwkaDVPfwcbm_m_Dd7ZtW4L40,1553 +pandas/tests/groupby/test_categorical.py,sha256=NT5FHQ2oKVCccIlTAPQ6m21o-JrnKyS1DMOHp25kvCY,73638 +pandas/tests/groupby/test_counting.py,sha256=NdXmZR8vi3e2Pkx2jKYKVriblQHN6h7tjYGa7Vsyq0Y,13495 +pandas/tests/groupby/test_cumulative.py,sha256=c5V4s9fZjIe0hYErXX_KSmj6AQ9HVmtI3QXNjHkDXh8,10997 +pandas/tests/groupby/test_filters.py,sha256=rGkdIKXnNbh1olsrTa5QjlrZRtqFebHljbXkDDaUSjg,21270 +pandas/tests/groupby/test_groupby.py,sha256=jdLcKtk4bELwnEjTJR8UiF_Cs6jF4GYnn_6zBsFmTeY,95083 +pandas/tests/groupby/test_groupby_dropna.py,sha256=jr0u-pJI-InSln0fyhIMzfiFHQkUVIO1RnC8NOPd3rc,22715 +pandas/tests/groupby/test_groupby_subclass.py,sha256=dPTzOL1_0GtVxulGrYfvZSxkWljyYUNuhrTL-UP8FIg,5188 +pandas/tests/groupby/test_grouping.py,sha256=g11MU4AyI2D8tN3Ej_oHPwmecdfx3ejd-v9nl_5Fy9A,44362 +pandas/tests/groupby/test_index_as_string.py,sha256=QM5ye0lc9GMrdNwdfF-8EAwbpiliNpPagV1AC1RmoYo,2162 +pandas/tests/groupby/test_indexing.py,sha256=cp3WHS9tkGFsaeIqL9nAN0ur36-2wBXivqGdwD-rmvs,8970 +pandas/tests/groupby/test_libgroupby.py,sha256=GseZ63pEHnWGT9uTpytKysXQ5jpVH3nN-9dhZ94LBMc,11075 +pandas/tests/groupby/test_missing.py,sha256=fZXCHhgrpqefGe4K03_CJvHINsU_46fqqRcJ9Wz2N-4,3234 +pandas/tests/groupby/test_numba.py,sha256=ecUNpSeGvi33GeYj8ZZrL6MsWzrcpEBOMek9epaFgSQ,3205 +pandas/tests/groupby/test_numeric_only.py,sha256=oFeXT_9G-lhH-5Sob5EvUK11SnjpL1EQFDu7fVB62Ck,15298 +pandas/tests/groupby/test_pipe.py,sha256=P_n3xXvve-lpjxr1K80dt3co8WTqovjfsetlG2iqrOw,2082 +pandas/tests/groupby/test_raises.py,sha256=8yAiJtCi2B6fB1cbkvFAgjpS2Zvj5RSDa8N8zSBltIE,23159 +pandas/tests/groupby/test_reductions.py,sha256=bsbkrYFOm9KQ43FaoqCXBKioE3oQBpk0jW_1XnY_bZY,49320 +pandas/tests/groupby/test_timegrouper.py,sha256=A6mBljxkVBKz6D7ohAcoG_TxXR_ILhDd45ZswyQaNnI,35157 +pandas/tests/groupby/transform/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/groupby/transform/test_numba.py,sha256=cJIhtb_kPcPbIIbCKQMeJWsARr577dT6uq2e8HSne2k,11454 +pandas/tests/groupby/transform/test_transform.py,sha256=oCVfZZE2Tcdts_Nh-5oDZcNfPR2BAMZjJWmkcrNBEuw,51735 +pandas/tests/indexes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/indexes/base_class/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/indexes/base_class/test_constructors.py,sha256=TiY9h4eOb0eh7QVUVR7G5OXyqC11aQF2Y3klLygCsKo,2397 +pandas/tests/indexes/base_class/test_formats.py,sha256=QW_1GZ1CDg9WCUfhhRvQBADuIIHKQKf-JY1qGc99xNI,5862 +pandas/tests/indexes/base_class/test_indexing.py,sha256=1zbBHv-nJCIfXRicDPXPtyLBL3Iy-LvH5bkamnoFGrI,3687 +pandas/tests/indexes/base_class/test_pickle.py,sha256=25JE3ybkI7xscPRJpHZ7gqJ6hmLUUF9qceHCICPuzVM,329 +pandas/tests/indexes/base_class/test_reshape.py,sha256=Ro9f3utklzo9EW-eK4VFz5vZR7Fp_H8W7_rWkJNtVdg,3138 +pandas/tests/indexes/base_class/test_setops.py,sha256=6-KqLjukta4R4S7OOX581SlWbW9-gWJq7OndEusD4L8,9131 +pandas/tests/indexes/base_class/test_where.py,sha256=uq7oB-lk7rsgYQer8qeUsqD5aSECtRPSEUfKzn91BiE,341 +pandas/tests/indexes/categorical/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/indexes/categorical/test_append.py,sha256=tLyVEbLzCIcUXkYW6oHw8CgzMGqp8uVKBpSMQsK0PRw,2245 +pandas/tests/indexes/categorical/test_astype.py,sha256=SSlyBD-fy_ZpilM87HZU6lQZY_Tl9OlKTxBX3DwoFOA,3124 +pandas/tests/indexes/categorical/test_category.py,sha256=tW2zHMMZwyXWLzWp71uXVJgTQbVFBrT-EEpkC-WM6tE,15160 +pandas/tests/indexes/categorical/test_constructors.py,sha256=Lx01eRN2SrBKo0hwOI0oLM09aQkfwhMCRE1gA9c0T9c,5952 +pandas/tests/indexes/categorical/test_equals.py,sha256=F7pq3V9yLTrAYm9LFdxNQ_Kl6iPsZyV2K1BXgc8SZF8,3570 +pandas/tests/indexes/categorical/test_fillna.py,sha256=sH68aWCabI2qy5dbgxQCXeTfvn1NQgDfM1OT4ojFmaU,1850 +pandas/tests/indexes/categorical/test_formats.py,sha256=-c7b9XlheS0LAiz5vVTNfpXIsfyri_ofgf5_PtiyRQI,6601 +pandas/tests/indexes/categorical/test_indexing.py,sha256=nKZH1gP_N1_1T4dsCGeOB3Srwkcdb_i_8TBmtkRWSww,14948 +pandas/tests/indexes/categorical/test_map.py,sha256=VHsSFGWEBmgQLvvquC6-y3QDq3lwzSpqPWZHTLiGdzw,4664 +pandas/tests/indexes/categorical/test_reindex.py,sha256=vPCV9O582vxJpubqCm33UHcaOKMZNg8OMzDF3lQQDiM,2938 +pandas/tests/indexes/categorical/test_setops.py,sha256=YiBoQN3Dor2p32HCUColWIZBH620H1aPa4easA5FMgc,462 +pandas/tests/indexes/conftest.py,sha256=o8V4LpVlZCxVZ-YM-ppOrbAUL0CCxxWOgmQ3IhESM1A,959 +pandas/tests/indexes/datetimelike_/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/indexes/datetimelike_/test_drop_duplicates.py,sha256=4Bjx5Iz4wLN-SGOlHaUNW8gqrZvodOtbsYbuw6IGwf0,2999 +pandas/tests/indexes/datetimelike_/test_equals.py,sha256=D-lcF-1AUuHn8toJL4zhXXSz3-wAufkDAk7psvuJvaQ,6582 +pandas/tests/indexes/datetimelike_/test_indexing.py,sha256=QoTXbCiqjK4tBDHUbq1TKPp0NroYkeheFjRq-VxlsP0,1310 +pandas/tests/indexes/datetimelike_/test_is_monotonic.py,sha256=_5PXF7mVilu1S4EJv7F-XMYIoz40kBkdSs4RJ8jTVdI,1522 +pandas/tests/indexes/datetimelike_/test_nat.py,sha256=ggX3X6HwAjYpK3z4zttGPQn20OU8mtr5FRciT_gCMuQ,1040 +pandas/tests/indexes/datetimelike_/test_sort_values.py,sha256=qtiNhFU5bNpTwU18VAoSePl5gDRgvB0ht2F0-C6Jbm0,11258 +pandas/tests/indexes/datetimelike_/test_value_counts.py,sha256=o090A9QuhmahJjH0WgKBIxXdBVxPkAc8vikXqZLuoD4,3150 +pandas/tests/indexes/datetimes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/indexes/datetimes/methods/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/indexes/datetimes/methods/test_asof.py,sha256=F9iAymn6r0EfACKQ-wCeyg6kr16tzhIchr4yRPKTMLU,1155 +pandas/tests/indexes/datetimes/methods/test_astype.py,sha256=-Cg1Nj2ZmuCXALCbEs32QFctUEK-rQhz0NgOXk-vZPo,12429 +pandas/tests/indexes/datetimes/methods/test_delete.py,sha256=JaaHDwYuTarkta3Qd2fbteZd9k0oOzJsWCPEHUHHG4k,4441 +pandas/tests/indexes/datetimes/methods/test_factorize.py,sha256=Mif09gcfRfIO2uhCqNN9OC_NXggKizbuwaz6ScGzMUE,4468 +pandas/tests/indexes/datetimes/methods/test_fillna.py,sha256=eESnVTQ8J3iBL24bWKt7TmHxC5FJiLZMpKjw1V376qY,2004 +pandas/tests/indexes/datetimes/methods/test_insert.py,sha256=dp7ii8Axeepc9bFKjarYWPWJDhc9gPORzlGNNK3K0M0,9593 +pandas/tests/indexes/datetimes/methods/test_isocalendar.py,sha256=JEABIm6LNySCbSUq6HLS-_qTGK3HgVcScSXLpDsrJ8o,908 +pandas/tests/indexes/datetimes/methods/test_map.py,sha256=1JR2lb_zk_8aIgRqnuWHfeXRPZBsFtdT4tRXeTDNqsQ,1358 +pandas/tests/indexes/datetimes/methods/test_normalize.py,sha256=ckcOm0rFp7OBkbJfXG1FWaIo5YXceqBx6kJ8xvtasMI,3181 +pandas/tests/indexes/datetimes/methods/test_repeat.py,sha256=GN-wTWws2sjodNibctZOi_NDX85y36Lr2BBmAs3LLMM,2740 +pandas/tests/indexes/datetimes/methods/test_resolution.py,sha256=lSIJIrpRQg0QcwBUoEvcQVJ8q7VVBVElgIZoLvgUMSQ,883 +pandas/tests/indexes/datetimes/methods/test_round.py,sha256=bNLqgfIzGyZquOtlaTZEjnYs5SLrv1edH0LAzepht2M,7846 +pandas/tests/indexes/datetimes/methods/test_shift.py,sha256=gKblvsOe1T4y1H6C2I0ZQfIIuizp0hifTlrj8NzjXws,6188 +pandas/tests/indexes/datetimes/methods/test_snap.py,sha256=uEHElue6bRKGGDT2GIvDmDQ9mfSj0hUId7B5pxY6f8Q,1729 +pandas/tests/indexes/datetimes/methods/test_to_frame.py,sha256=C6glyGdxSs-hMDQSt9jkftmRlTGPMCGdIQlfChR9iGk,998 +pandas/tests/indexes/datetimes/methods/test_to_julian_date.py,sha256=u6JLYazILIdltbe1uZE3iBAqE_ixXwx0oqwS6T-Mpng,1608 +pandas/tests/indexes/datetimes/methods/test_to_period.py,sha256=-IVzeZI98Ucrjl88fjMpNhY1AxDly7PBnN6S4MT218w,7782 +pandas/tests/indexes/datetimes/methods/test_to_pydatetime.py,sha256=sM22b33Cxwrpc5nShAp5QH2KQPOlEpi5d8G6fM3vVI8,1345 +pandas/tests/indexes/datetimes/methods/test_to_series.py,sha256=Ljnq3OPEGJvj8hTktJYVAN6R1HVasq6-Wi7eUce7XB4,495 +pandas/tests/indexes/datetimes/methods/test_tz_convert.py,sha256=CVJYrppSSFgZ5fSsqnkGrB5nC8_rW9te3rIAYEKIKmY,11704 +pandas/tests/indexes/datetimes/methods/test_tz_localize.py,sha256=UabC9r8uBNeg5gAPPfMN1T1xvgqU-H1uwkgQlT1BUVI,14251 +pandas/tests/indexes/datetimes/methods/test_unique.py,sha256=qZorAPI_oWcz5WdBEr0nQuT_mrApTgShqg3JVlzpVKU,2096 +pandas/tests/indexes/datetimes/test_arithmetic.py,sha256=LmGeLaNpm645REYvJcrSiAu3hNVpsYUv7by8_dIoTg0,2565 +pandas/tests/indexes/datetimes/test_constructors.py,sha256=zTtT1oTHJRsB8ojKGc2X0SoqNCgTlthSNFMnKx00ngc,44095 +pandas/tests/indexes/datetimes/test_date_range.py,sha256=w2M9GZayuY65ejzOjkEvMU5u3ykyoyosiIAjAF2JYPM,66127 +pandas/tests/indexes/datetimes/test_datetime.py,sha256=flAeN5bYeJFxSdEgnB6ZBO31s_8wRNyKHzw7TcgRDK0,5378 +pandas/tests/indexes/datetimes/test_formats.py,sha256=1U1ThV9U8AnMdWfTdf2yyOOadtD-WVFjcHnz4Plmwf4,9859 +pandas/tests/indexes/datetimes/test_freq_attr.py,sha256=oX_cweTcpKd27ywN976KCYpg0oFe77MeDWqnRJQwVRo,1732 +pandas/tests/indexes/datetimes/test_indexing.py,sha256=AQ-ToCLPVoEnh3BWMqxPcmD9hsM5HhVrqMs0ylbtx_g,27575 +pandas/tests/indexes/datetimes/test_iter.py,sha256=cgQO0w_GoCrHyK2aHyWuJdrHkH6EPWATQ0NURrG2CbE,2590 +pandas/tests/indexes/datetimes/test_join.py,sha256=mFxTvHONYg4ELXArFDBo_qPO2_7JO5NoIWgYcCSDtRw,4915 +pandas/tests/indexes/datetimes/test_npfuncs.py,sha256=YJihZytss-MVNprp4p5pAL_emeC5pb3hBwtaS3yMCcU,384 +pandas/tests/indexes/datetimes/test_ops.py,sha256=u7x6d6SkAMMIgj2wjnYn0BocHyMmPgHET4BT6b_eK9k,1351 +pandas/tests/indexes/datetimes/test_partial_slicing.py,sha256=A3aMoviqSqKIy41lDIk5z66M6j5AnPYwFpPQvMDGZhI,16489 +pandas/tests/indexes/datetimes/test_pickle.py,sha256=K2almsO0AKzzVHD9CS1jKn7ettNLnqkXN1iykFJeNik,1468 +pandas/tests/indexes/datetimes/test_reindex.py,sha256=2s2lDVmvjGUeq36j3i_kYdRVkMi7FaiwSn3nldn4g9A,2178 +pandas/tests/indexes/datetimes/test_scalar_compat.py,sha256=MdUM47OoU1QX5Bb6_bpuOycOoRt0kUp9UwZ4qw_KUuk,16357 +pandas/tests/indexes/datetimes/test_setops.py,sha256=HAHtXU16uQmmgNPwM356WV391EMiPU9t2HHXR_9WzaI,28054 +pandas/tests/indexes/datetimes/test_timezones.py,sha256=chDZNP_ZxAc1SlFAdmxDUvXUR9ot_Oji0Swqu2gI53E,8076 +pandas/tests/indexes/interval/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/indexes/interval/test_astype.py,sha256=_MXUvJO5V3JLANzu3K3Wd8vTMShTUNMI144-GQBxjIY,9335 +pandas/tests/indexes/interval/test_constructors.py,sha256=o76Hbyc5RxudYZ28Jie9VLu4DO5k69S7Mke4UZV3ZMs,19915 +pandas/tests/indexes/interval/test_equals.py,sha256=a7GA_whLbOiS4WxUdtDrqKOUhsfqq3TL0nkhqPccuss,1226 +pandas/tests/indexes/interval/test_formats.py,sha256=htz5Wwv78_3UNjEFcJNJZdgjBkfwMvQ76lmOzp0xWx4,3773 +pandas/tests/indexes/interval/test_indexing.py,sha256=Ael0C6UL52WK3-_SG09QoohjHgsoSzgBf_nkXs41NGE,26022 +pandas/tests/indexes/interval/test_interval.py,sha256=B1Lafu9CiXyU0ulyD7sQll2DBsns8oN4jhPEy_rj1XE,34876 +pandas/tests/indexes/interval/test_interval_range.py,sha256=s8XEWX8-23aVwJtNrbm3Gou8JYlA1fJl561oLlBJ78E,14507 +pandas/tests/indexes/interval/test_interval_tree.py,sha256=JbLc76kv-vB2t7o9bZG9sMSs6A2Ar8m7GEmWZUM9j18,7595 +pandas/tests/indexes/interval/test_join.py,sha256=HQJQLS9-RT7de6nBHsw50lBo4arBmXEVZhVMt4iuHyg,1148 +pandas/tests/indexes/interval/test_pickle.py,sha256=caKG3qOVTfZaU_HZR1AiCopW1d6ZUGLj19vjQBh9758,376 +pandas/tests/indexes/interval/test_setops.py,sha256=nwBz1MHuHiM7JQc74w2doEpgTSwg3NYfGwGbQFXWKw8,8346 +pandas/tests/indexes/multi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/indexes/multi/conftest.py,sha256=42mdJqtqvX3PlBSdch1Y6jRBvhe0IzZxOoLt-BGX03Q,698 +pandas/tests/indexes/multi/test_analytics.py,sha256=bHThbj3tdjih25Qxkh2rDZ0q4xl0cQW_AJpiILm_C7c,6723 +pandas/tests/indexes/multi/test_astype.py,sha256=YmTnPF6qXwvYY82wZfQ8XFwVwOYYsIls3LSrdADDW-4,924 +pandas/tests/indexes/multi/test_compat.py,sha256=q53DVV5fYOKRVEQBl_2ws6WXrNsrGr5w4FXvXLUBeuQ,3918 +pandas/tests/indexes/multi/test_constructors.py,sha256=sqEJKSV7y0Y6Jk7jjt-R8oSmWxUuzYsJhnK35n_j46Q,27185 +pandas/tests/indexes/multi/test_conversion.py,sha256=kce6v9ghUkBphWKbSXs8YFcMii4CY6jmREf6EWgCS9w,6337 +pandas/tests/indexes/multi/test_copy.py,sha256=9Xperk7a4yBTQKo8fgk3gCa2SwJr30mH2JYYMYWguWY,2405 +pandas/tests/indexes/multi/test_drop.py,sha256=f-QWEu62UnlFDLH99SFmv2_HEp6Sbp_fRWlZO1mKRwA,6063 +pandas/tests/indexes/multi/test_duplicates.py,sha256=sXniTHCKVkJuP4Fr_bDtapAjO69-GmBl6tW6Ql2L0cU,11480 +pandas/tests/indexes/multi/test_equivalence.py,sha256=Rfr7yM1NzJv8q9j4B-ROFTd9cpz9fPUXwrf_clYKo0M,8549 +pandas/tests/indexes/multi/test_formats.py,sha256=3hZa0a_34vaPDgyp3AWrzmLB_nyPk65fLmV_VJcmEfI,8312 +pandas/tests/indexes/multi/test_get_level_values.py,sha256=0-6mZCOShvxijusZzqL4xFBwfg-T_dFYmV673Jojylo,4308 +pandas/tests/indexes/multi/test_get_set.py,sha256=Ak7MVJtOlyXY1zwUQx5CNWuTVGTe9_e4RiWp0v5ZKOY,12800 +pandas/tests/indexes/multi/test_indexing.py,sha256=ff-hxGABGeDPejn_cmXYiQw60OhsAlc88FYdcPUI8oI,37656 +pandas/tests/indexes/multi/test_integrity.py,sha256=Z1khzb780aJ4GIVclGmZ4CE0c4gerM8Q-sHZqCoqmkA,8997 +pandas/tests/indexes/multi/test_isin.py,sha256=QNZCHSEbyIZhRoW3UZoqMHdZlAKR6q2gzlCXqIUc-Vo,3430 +pandas/tests/indexes/multi/test_join.py,sha256=r2sTQ8v8TwnTwOukhYxdli1RlRpVKE_sWHvgcJND3vw,8507 +pandas/tests/indexes/multi/test_lexsort.py,sha256=KbwMnYF6GTIdefQ7eACQusNNuehbtiuqzBMqsOSfDU0,1358 +pandas/tests/indexes/multi/test_missing.py,sha256=4aW2Bo6KpwVQBkFbv6fjwa7Iifv91JgotS2YEr3ZjH4,3350 +pandas/tests/indexes/multi/test_monotonic.py,sha256=5xlESrQOEcFWdr0iB3OipJtA6-RzriU3Yq2OQGgP0M4,7007 +pandas/tests/indexes/multi/test_names.py,sha256=erOGULwnpwQIalExpvRjhGhN_UVaQ0V8B7JwhlK93Os,6601 +pandas/tests/indexes/multi/test_partial_indexing.py,sha256=sVNIk9_NxMDsHuRQzPCernPmchTF5INAUFkzQV7t8T0,4765 +pandas/tests/indexes/multi/test_pickle.py,sha256=ZJVZo0DcXDtV6BAUuPAKbwMV8aGfazJLU7Lw6lRmBcw,259 +pandas/tests/indexes/multi/test_reindex.py,sha256=ww8fSIx426wfqBTogkJrKS533CjKorf-B4bhyKdEnD4,5856 +pandas/tests/indexes/multi/test_reshape.py,sha256=W-ddEaDSYqQWZuKcAS_R53HYfxjx9SbT-mSKvmtClkM,6711 +pandas/tests/indexes/multi/test_setops.py,sha256=a1lJUcojFac7vORlbDKIC39Fwco0lkboEZPWzlGgQkg,25827 +pandas/tests/indexes/multi/test_sorting.py,sha256=y030oqbbSYON8IxmSuvuEBikJjetejSJ0Jj44HFKxVc,10736 +pandas/tests/indexes/multi/test_take.py,sha256=4MaxPM4ZJQPXJKiqgwEwhZ71TyH4KQfIs5LgS40vvLM,2487 +pandas/tests/indexes/multi/test_util.py,sha256=VDr0K2mxp3CBH3KLLLXILSW_XPTU9OJc0ufH-FCdzlI,2323 +pandas/tests/indexes/numeric/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/indexes/numeric/test_astype.py,sha256=P19W9zZl8tN0EK-PaEi2gIFHLwCbruTMEUm7_ALGH9Q,3618 +pandas/tests/indexes/numeric/test_indexing.py,sha256=ZBQC-ULeewue6n25_9JlLbrCqvYzy8ncSM0iRU17zEc,23316 +pandas/tests/indexes/numeric/test_join.py,sha256=cLeacFwT1Zv_zgZGhuWhQJy4dX_KFtZ58XnBMVjJtNQ,14997 +pandas/tests/indexes/numeric/test_numeric.py,sha256=vpZBjNriWo-0mtOyRri2LRveDt5IpSSbG7PVpdeporI,18506 +pandas/tests/indexes/numeric/test_setops.py,sha256=TI_DxIFXDzqKJZ0egXjS8YMECxWam0v36ljwI1xVKUQ,5968 +pandas/tests/indexes/object/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/indexes/object/test_astype.py,sha256=p1EwqKDlOHAlSOpzxLaUDnAEm6yAQ0VIGnN-hhieCks,368 +pandas/tests/indexes/object/test_indexing.py,sha256=S43FYQkRvZicPt-IngRq6FHohiXDpOL9tyVZF0EylxA,6392 +pandas/tests/indexes/period/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/indexes/period/methods/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/indexes/period/methods/test_asfreq.py,sha256=7nGHRMwhFjj0thYBkuHMpcnOPFQAfHhga3XnGxkVhQ0,6897 +pandas/tests/indexes/period/methods/test_astype.py,sha256=vz7aRsoeDLXvQ_bhob7hNq1B_3gs4n_rCkD-sW5atAc,5671 +pandas/tests/indexes/period/methods/test_factorize.py,sha256=FXQh6VmGkuGkB2IAT4Y-2V5UaD2LCUNjQZ6amfBao80,1425 +pandas/tests/indexes/period/methods/test_fillna.py,sha256=jAYnaWGMuUaG993yxLwr1eT6J1ut43CcBaKds4Ce3-0,1125 +pandas/tests/indexes/period/methods/test_insert.py,sha256=JT9lBhbF90m2zRgIwarhPqPtVbrvkLiihZxO-4WHvTU,482 +pandas/tests/indexes/period/methods/test_is_full.py,sha256=RqIErBofIn5Ewh-MomVePHOn0hViZbe4laMC2vh8nPs,570 +pandas/tests/indexes/period/methods/test_repeat.py,sha256=1Nwn-ePYBEXWY4N9pFdHaqcZoKhWuinKdFJ-EjZtFlY,772 +pandas/tests/indexes/period/methods/test_shift.py,sha256=P7XDpMkLEYarH06RLBglFJKoGPkax4oLdiuI676KLek,4405 +pandas/tests/indexes/period/methods/test_to_timestamp.py,sha256=TAxexnZF5k8a9zm_hRC8ipKpZ_RSQkL_ysdHr5LC6uU,5276 +pandas/tests/indexes/period/test_constructors.py,sha256=_xnQWJp71G8cGbjH3iBJC-DFP8ySW7rYitwIKAdqPp4,25997 +pandas/tests/indexes/period/test_formats.py,sha256=2CYehcP2tVUwjN-ExhTrYnXYnKvT2V6_8JjUk8yR93s,6370 +pandas/tests/indexes/period/test_freq_attr.py,sha256=p9Z4u05F0UeoEu1XdLU2MlT6ZNy6WOoGEw8HKTVErDg,529 +pandas/tests/indexes/period/test_indexing.py,sha256=wy5YBCGyD_MEmiIqB5pgXlRPBUxdnFN4WakcKnN3GgY,27905 +pandas/tests/indexes/period/test_join.py,sha256=EXfdSZiHHntg4AQaPGImHn0gqq9anOL02Yc_HvQdIMw,1813 +pandas/tests/indexes/period/test_monotonic.py,sha256=9Sb4WOykj99hn3MQOfm_MqYRxO5kADZt6OuakhSukp4,1258 +pandas/tests/indexes/period/test_partial_slicing.py,sha256=w7Ng-OHL0s3bFC4CN38xq-QQLmx3b5uFlwpUc9UoLe4,7228 +pandas/tests/indexes/period/test_period.py,sha256=c87QMU8I0oPyzHpXUtDwCzYHmyvjILih3lTT7Ynqb6A,7921 +pandas/tests/indexes/period/test_period_range.py,sha256=7zj-D_Rr5XzQSbAgq1rXY8ZY9eITvW7l346ORaUBAq4,8946 +pandas/tests/indexes/period/test_pickle.py,sha256=uiJlzpbv2TEyviQkUmDzYacf9pAXRqrJWPRkqyzIhLM,736 +pandas/tests/indexes/period/test_resolution.py,sha256=0TmnJeZCOaTWneeWA66DlxKgaUZJTfP0jKgLAY1jiyg,571 +pandas/tests/indexes/period/test_scalar_compat.py,sha256=kDnRNxBKMscsxTEcIfHhhU1upKzjoTjgpz-nyXIda4U,1355 +pandas/tests/indexes/period/test_searchsorted.py,sha256=_u7DlvBnFx0_c8u3FIKYVOUcjlvN7p0gojLl9fZDkMQ,2604 +pandas/tests/indexes/period/test_setops.py,sha256=BcwDXv1-fnqOJLtzNqY2rEOye97Smyk2iXMnZx_IQE8,12547 +pandas/tests/indexes/period/test_tools.py,sha256=DFoxBsCYRWqodmNaDNPnQrZTTXiaSvwNZkwrybe7cl0,1361 +pandas/tests/indexes/ranges/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/indexes/ranges/test_constructors.py,sha256=ceX79fbjGyc5VNkmz29Q1N7WGXLj40BvTuz5PfNAw4I,5328 +pandas/tests/indexes/ranges/test_indexing.py,sha256=KmvZ4LcnfoNPMuHBpq6ftqvuCIFP2LvI7TAXyGR_iWY,5593 +pandas/tests/indexes/ranges/test_join.py,sha256=RS_pAFbf-APhunB9QBmAMDbUa27BpTbhKxj6l8mckYc,8573 +pandas/tests/indexes/ranges/test_range.py,sha256=lJgUDIK0_xykoNMfo7Z-p4jXhUkNXK-D51tPoyf1uyg,29733 +pandas/tests/indexes/ranges/test_setops.py,sha256=J0UR-U4cFt60VMyVLw3m93neKH5ijBUraEjVFWxy_l8,17505 +pandas/tests/indexes/string/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/indexes/string/test_astype.py,sha256=r2hLBYI1NQu3KD6daxOC-m6hYp3FhvMcPPPHZrvfXMs,722 +pandas/tests/indexes/string/test_indexing.py,sha256=vrbnnCMOQaIVkIUUlpbcW2Fgy6XGMr2BrefVBDp95F0,7850 +pandas/tests/indexes/test_any_index.py,sha256=0YkEzkf7xjn6MUiaqT6mHLO1iqJh4rrpb4i1_8GUYf8,5563 +pandas/tests/indexes/test_base.py,sha256=LdsYNH_cUE2YGEkMau92LIXzk4wndQw9VqodP7tAAbE,61124 +pandas/tests/indexes/test_common.py,sha256=8M9cyzuO1wc1LrEOMWcu44ampP-RjBnElNkKab_Eqzg,18365 +pandas/tests/indexes/test_datetimelike.py,sha256=bPlVwx1zl80aGi3-qoYsbqro6PX0YXjA0pmfpiO4qJM,5469 +pandas/tests/indexes/test_engines.py,sha256=rq3JzDXNc2mZS5ZC2mQLpTeydheOX9OLoq1FLR53wbI,6699 +pandas/tests/indexes/test_frozen.py,sha256=lxYJsV1jpfhyn3lUmX_e5WTNr2YofPrgoWlHZvx47XM,3221 +pandas/tests/indexes/test_index_new.py,sha256=tFCuvz_Lq1ByWzYLIU6xvy6FSsrbICq-_JXln8V3N1M,14701 +pandas/tests/indexes/test_indexing.py,sha256=Jmh6vUvT2QBlbui27Xe_k21KJKI-BRakNW5Hd_LjCEQ,11434 +pandas/tests/indexes/test_numpy_compat.py,sha256=I2dRl3oOT57kGfv8oNVvClQQxEPGR5lkB6KAIyKnsDI,5756 +pandas/tests/indexes/test_old_base.py,sha256=rzlb-0wyk9YElFROD1D2_nfQolKSdRFmYnPqZsIyN0w,36962 +pandas/tests/indexes/test_setops.py,sha256=8by8fROlAzmcT2viZbFLRRP-vOgjieWr4E7gFC9YSCg,35680 +pandas/tests/indexes/test_subclass.py,sha256=4WyTJ_yik6lF5ZtQbA5p3M0VrtVfIKznRHi9fTktLqI,1059 +pandas/tests/indexes/timedeltas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/indexes/timedeltas/methods/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/indexes/timedeltas/methods/test_astype.py,sha256=xbvKmv20EHrDvBcjJnUpJfiPkfOCPiFrOn7TMS2H_s8,6331 +pandas/tests/indexes/timedeltas/methods/test_factorize.py,sha256=aqhhwRKZvfGxa3v09X5vZ7uBup8n5OjaUadfJpV6FoI,1292 +pandas/tests/indexes/timedeltas/methods/test_fillna.py,sha256=F7fBoEG-mnu16ypWYmK5wbIovQJKL0h86C1MzGkhPoE,597 +pandas/tests/indexes/timedeltas/methods/test_insert.py,sha256=Vd09nIiqhgYGazcXk3rY-ipl9PINpXnl7mElOma3pF8,4713 +pandas/tests/indexes/timedeltas/methods/test_repeat.py,sha256=vPcNBkY4H2RxsykW1bjTg-FSlTlQ2H1yLb-ZsYffsEg,926 +pandas/tests/indexes/timedeltas/methods/test_shift.py,sha256=sYgIYG8-bmLWpa3fHYmmFBzxr-_TzTk6G8bmAOEuIaI,2810 +pandas/tests/indexes/timedeltas/test_arithmetic.py,sha256=YocDQIovXnrpXEzz3Ac-3l2PdGaDf2_sF8UPcLVF1Z8,1561 +pandas/tests/indexes/timedeltas/test_constructors.py,sha256=NxR6rndaSIM6AB1siF8n2raUwP5Uj_vW6sg7RfrCeN4,9449 +pandas/tests/indexes/timedeltas/test_delete.py,sha256=APpD5A1F02kED0dz8YyJ6Br1rwOViWHsVxPDh6E_oqs,2398 +pandas/tests/indexes/timedeltas/test_formats.py,sha256=BYKN-uVo7sn0PgWEtzTGNxXhVLKKz3OFrckAftAgQjk,3855 +pandas/tests/indexes/timedeltas/test_freq_attr.py,sha256=gYGl9w9UdtcfN26KUx1QyY4mjh6A0m4Csk3gsCIcdos,2176 +pandas/tests/indexes/timedeltas/test_indexing.py,sha256=ryBE_h6c8nEEGBuDnUmfXB8uWh92jZVP-W7jodY1_B0,12501 +pandas/tests/indexes/timedeltas/test_join.py,sha256=7JUirtgNGJMRL1-k2gekrvondwYuIVvuI2548v4nfIo,1396 +pandas/tests/indexes/timedeltas/test_ops.py,sha256=nfGyNJvNy7_jmWebKjevLKhyAMNvI5jytkZTNlpEC-g,393 +pandas/tests/indexes/timedeltas/test_pickle.py,sha256=gdquABZmhDbdi9EmBN-H8-xUICH-QCx5qLe5O9VU4v8,324 +pandas/tests/indexes/timedeltas/test_scalar_compat.py,sha256=-tQq5SK02ywM2t66NFYtZNim2D0xxux3Aeh5vnDJ4vI,4851 +pandas/tests/indexes/timedeltas/test_searchsorted.py,sha256=kCE0PkuPk1CxkZHODe3aZ54V-Hc1AiHkyNNVjN5REIM,967 +pandas/tests/indexes/timedeltas/test_setops.py,sha256=8iAUdN5j_ePsktZz0DiETzVYRLiBrW7gkpSu9myxkP8,9780 +pandas/tests/indexes/timedeltas/test_timedelta.py,sha256=2ozkfkEM3JXI8Li5lwc212JnU_0-WeJ3PNdjtRg7CCg,1938 +pandas/tests/indexes/timedeltas/test_timedelta_range.py,sha256=MJzgDZ5xQFzTatfdftKWu72pyTuP1g9A3PXbKz5ZaU8,6631 +pandas/tests/indexing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/indexing/common.py,sha256=lS9CUBwhlyWFtosnA3XoI8IKckguhvQB8tfd738ql0M,1020 +pandas/tests/indexing/interval/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/indexing/interval/test_interval.py,sha256=r-TIN4zb5JgI8lnmyEcaqb8xMkgO2QLSwSDhR0mDybA,8054 +pandas/tests/indexing/interval/test_interval_new.py,sha256=plTmq7VX6e_vPRQrnzAcEI-nkq1VJA7GSosZi0VRRdc,8134 +pandas/tests/indexing/multiindex/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/indexing/multiindex/test_chaining_and_caching.py,sha256=sltbBiNcZmMAgYAh8iSq77ot7i2ZB4I_URReiypnbvA,2757 +pandas/tests/indexing/multiindex/test_datetime.py,sha256=tl1yr3h50R0t7uvwTcfsRW-jt1n9vsqf4BWp4dNTdd8,1234 +pandas/tests/indexing/multiindex/test_getitem.py,sha256=vQwFStR__h4xNxr06HxfFwv5B4VWFFKDSlhDlxoBd-Q,13283 +pandas/tests/indexing/multiindex/test_iloc.py,sha256=G2CUPRhd5pRImZpH0uOVIPid7fzB4OuJZjH8arQMrE0,4918 +pandas/tests/indexing/multiindex/test_indexing_slow.py,sha256=azzkWz9EIOe_xM-qRbYIYhxS4IcvEt_T0bP2RdF88hU,3353 +pandas/tests/indexing/multiindex/test_loc.py,sha256=f6tr3Y9WP3IhJfpEUUicLJVaY8cCtEyEp8o3TzYqa9A,33452 +pandas/tests/indexing/multiindex/test_multiindex.py,sha256=lENUtoQgj6LA7CVEAQjRholf_6YI_34ouOKAi3Bl2H8,10038 +pandas/tests/indexing/multiindex/test_partial.py,sha256=-oQh8KCYUzLU-VvkTscl480mlCCiSO3T40uU3SsHB7M,8358 +pandas/tests/indexing/multiindex/test_setitem.py,sha256=d6Kz45aYdYgvkpNTpshge5Q8vz0HxtFiWEp-7sSIQ5o,18434 +pandas/tests/indexing/multiindex/test_slice.py,sha256=xh6S43WSG865ESlGCl2lrCtgVLjpi_MHRiS4u6F_9Z0,27152 +pandas/tests/indexing/multiindex/test_sorted.py,sha256=GHEIHy8ebAPNp27vQhVGuyfcl0xz4FpOhRNdF9obPsA,5192 +pandas/tests/indexing/test_at.py,sha256=kony6rIx0bSmL13kBSdhSkDncOyIEoM9_68r5MG3HRs,7213 +pandas/tests/indexing/test_categorical.py,sha256=kL0pQ2gHvzqE3WUf66hbZX4nrbX_TIUSPFeyDxYcivg,20439 +pandas/tests/indexing/test_chaining_and_caching.py,sha256=sYf3atVEKVpmJaLwPG2Go5m3T0VXfu9-UjHLHJM3F1Q,12571 +pandas/tests/indexing/test_check_indexer.py,sha256=tfr2a1h6uokN2MJDE7TKiZ0iRaHvfSWPPC-86RqaaDU,3159 +pandas/tests/indexing/test_coercion.py,sha256=zpEiLPsxrWybTZ3MBwrD5btRZcvhuT5Y283qtsAiYO0,31063 +pandas/tests/indexing/test_datetime.py,sha256=vrAAJRHUurSxGEP_zHZr1Ytm6SRFzh7DnVyg5vum8E8,5714 +pandas/tests/indexing/test_floats.py,sha256=qZOm8AzZjI5gMgJ7i2ycKy-cQmKNOtkMtflbUTTNG20,19937 +pandas/tests/indexing/test_iat.py,sha256=ngulzwfQDY10X72VVdvxvN15iuCbfstwE0vHWFcMzlU,817 +pandas/tests/indexing/test_iloc.py,sha256=lf4DyEPyp7COhvDi_alLNvZbUVD35QnVAo2YtwHAenA,54110 +pandas/tests/indexing/test_indexers.py,sha256=agN_MCo403fOvqapKi_WYQli9AkDFAk4TDB5XpbJ8js,1661 +pandas/tests/indexing/test_indexing.py,sha256=ANLgq4t1wWlC3yxs97P8CgrsAt_jl5LXi1yD2Plz5Ak,39339 +pandas/tests/indexing/test_loc.py,sha256=gr_QgunjjcJ7U6x8p3SL5IFkCwzsH4uMEetyHocF2RI,122941 +pandas/tests/indexing/test_na_indexing.py,sha256=IZmiYBUUQrmILzd3lE0AiLwyBQHTmpbdKU4LhrW5a7o,2276 +pandas/tests/indexing/test_partial.py,sha256=c9lv0cvs5jG-8DAK-_jJdyzrP83jGETmPgEUMfBbT5g,25040 +pandas/tests/indexing/test_scalar.py,sha256=KRd26cIBmUsEjAx-pHF2V4e207IzseBHiZylGT0yWfk,9929 +pandas/tests/interchange/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/interchange/test_impl.py,sha256=ftMTtbbNDTGCqXVxEbQfXs0FtS_3uaMTUxIPfTDALOU,24540 +pandas/tests/interchange/test_spec_conformance.py,sha256=fiNwyVHBgWjwRIUi8_LHZNvaCGlV5D1_EDW_jt2HCJ0,6243 +pandas/tests/interchange/test_utils.py,sha256=15liIDJirQDoP7TxxQkmZJ9gCAVNCd2BwShW_GlwL2A,2965 +pandas/tests/internals/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/internals/test_api.py,sha256=y_L-6pdpFZhSHX8yAS0X4eBLntNhNV5gRbqUiem1ARI,5461 +pandas/tests/internals/test_internals.py,sha256=LKeSDQKH2n66kuXvfoLqGTJing-V0mt6D46vMdaQd-k,50027 +pandas/tests/io/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/io/conftest.py,sha256=NAA32mC6olk1k5P05pvizdbUv09bLeJD43kOA1O1bDU,5023 +pandas/tests/io/excel/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/io/excel/test_odf.py,sha256=FnyjM2Do3ubo5eoCvvBlHh4jaXQmfu_6GaUmlgqKXCA,1884 +pandas/tests/io/excel/test_odswriter.py,sha256=o8lT1Wnce2czwV_mOSVra9tirP-5F1ht4Szzvc8WW2c,3075 +pandas/tests/io/excel/test_openpyxl.py,sha256=__0DVOzbPAHDQq4pSpXiGBuBvokeisyNsfdmUoahH_M,14688 +pandas/tests/io/excel/test_readers.py,sha256=mgTSl3JgFeMCl9_hULYhqb2dbbYSV5CQtfY86PRM9eg,62945 +pandas/tests/io/excel/test_style.py,sha256=jCzDEbPHvtmIUOVV8mJjHz3Aj7GbqpSJfRHnomah5wA,13614 +pandas/tests/io/excel/test_writers.py,sha256=bs6Gl8JhoG63_WyeEuSq8YagNip4uS5Tnanrr8Yoca4,63699 +pandas/tests/io/excel/test_xlrd.py,sha256=O76F0X2wsdIKrOgBegMITMwzMBMLc-AlWShmo0sK5bs,1832 +pandas/tests/io/excel/test_xlsxwriter.py,sha256=8blzvCXFBj64s7jZXh-VMzQ3W1v9E-Ar0rtcLtI1-P8,2559 +pandas/tests/io/formats/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/io/formats/style/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/io/formats/style/test_bar.py,sha256=j_FkCbBRSMI1JpbP_fRgs4WDO9ymRMswDRCpqVyWnX4,12058 +pandas/tests/io/formats/style/test_exceptions.py,sha256=qm62Nu_E61TOrGXzxMSYm5Ciqm7qKhCFaTDP0QJmjJo,1002 +pandas/tests/io/formats/style/test_format.py,sha256=1B04-VWbsnbsR5oprM0UX4tE8d7YvRSvbn4SwkFhsLg,24534 +pandas/tests/io/formats/style/test_highlight.py,sha256=ym2foO3mJCXW8gS3QiPph1cJWkLrsZKqRrAdeJXFpzE,6947 +pandas/tests/io/formats/style/test_html.py,sha256=jTinwNbpJ53YQAyGuEMyjFq77GkEgQJLVrmnKkY-kT4,33821 +pandas/tests/io/formats/style/test_matplotlib.py,sha256=zsnmSdIGZ3Oy5z8zvqD5ZoFtD1hNZ6tE95oQYtlxnow,10838 +pandas/tests/io/formats/style/test_non_unique.py,sha256=JG_rE5A5Zk5exlfivZHnOI3Upzm8dJjmKKHkwEje4LQ,4366 +pandas/tests/io/formats/style/test_style.py,sha256=Meo1MH6FXNaQUH4T6eezDX3kSUjmA-S2cbYa4D4TFZ0,58667 +pandas/tests/io/formats/style/test_to_latex.py,sha256=Q7z2gUxe1dXU7v8yafUKrI9JhOOT2SYVDRehgpdFs-s,33017 +pandas/tests/io/formats/style/test_to_string.py,sha256=w1GvLm3FtKQd9t2nwN3vF55X5f0GQKGCGXpYFZxITpA,1910 +pandas/tests/io/formats/style/test_to_typst.py,sha256=6hii7vY8V6yq4Udky916KFeSVUj3HcgcQOQsNqssWhI,2064 +pandas/tests/io/formats/style/test_tooltip.py,sha256=wp9dqDg0SXoxtkBuYEW4DR9dsdhitm943nOxH-PZzO4,7328 +pandas/tests/io/formats/test_console.py,sha256=jAk1wudhPiLBhhtydTNRlZ43961LqFu3uYt6cVA_jV0,2435 +pandas/tests/io/formats/test_css.py,sha256=gbB7165LizEVcIg-1NGyFaNA7eqyydi-nSOB8mfLbgY,8882 +pandas/tests/io/formats/test_eng_formatting.py,sha256=QqFZJMUBVnU5SpZB63tCOHX3CqZbjgesOZc6nxbhp4c,8454 +pandas/tests/io/formats/test_format.py,sha256=vvDl__CluvjuFfJFrBojVv3sIl6igRTj4MY2Sg2DdU0,84932 +pandas/tests/io/formats/test_ipython_compat.py,sha256=asnw_LpGqqM0aldC0EPrpgV6I4ByR8XJ1Ds5xG_yWnk,3162 +pandas/tests/io/formats/test_printing.py,sha256=9KtldK9iyESyd23U9dKhMYrFstUW5bRvp8qBiVOdjUo,5742 +pandas/tests/io/formats/test_to_csv.py,sha256=xTkqjxvXJfznKUmyeJON04-p9BEVBp__NctjhJRmGP8,30122 +pandas/tests/io/formats/test_to_excel.py,sha256=FAr9Pl9XpNwYFpZZAm56mOmEnAWi-ctw6hVuHAcnBCc,16811 +pandas/tests/io/formats/test_to_html.py,sha256=dd0Pp9qUACATsNqWC8tGPm1zAFEVTNu555rjyeu9WjE,38419 +pandas/tests/io/formats/test_to_latex.py,sha256=ztqFSAv19knyx-13nmhdyseyK0UBsBQmzDhv0P4VBpU,44718 +pandas/tests/io/formats/test_to_markdown.py,sha256=HDmvYFMZWPnzwps5ubX2XHUf-8EVel_dTcY34h7YXn8,2690 +pandas/tests/io/formats/test_to_string.py,sha256=TdSVYFu33vI75CK6z0ouToQhttkVC4vs5pHTUTxEe6w,39956 +pandas/tests/io/generate_legacy_storage_files.py,sha256=ARC4FWvtrEWx8WLqJLnaAX-mYpAdTsuRGIlyLIcbP2U,13016 +pandas/tests/io/json/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/io/json/conftest.py,sha256=Zp83o90PvZ56MbhNRr1NZEPTpho7jRHcLYiEA9R_BZw,205 +pandas/tests/io/json/test_compression.py,sha256=uqKU3AolAE_c2ozivEU6KdxigIR_pnq9U0BHb6uazLE,4331 +pandas/tests/io/json/test_deprecated_kwargs.py,sha256=M4ae-7nGtwhKcHMAy6I7EDbSvvNAC0KM1HDpd-No2l0,691 +pandas/tests/io/json/test_json_table_schema.py,sha256=X5US0qGTvDcVKkq-I8puw3FNHep6bHjgJMXs9y6kaqw,31470 +pandas/tests/io/json/test_json_table_schema_ext_dtype.py,sha256=ia89XMnuZ5NqceVrY5CF5-4aEkRsheG4U7dB2F4RCzA,9338 +pandas/tests/io/json/test_normalize.py,sha256=2P4UbyHf5R45shqiM830FuUzfv7eCTjSkwt4PdaomQk,32565 +pandas/tests/io/json/test_pandas.py,sha256=EOUpYPIjhI1c2V7sOvVAKB0L_xCk9DSjHA86gvpfc-Q,86522 +pandas/tests/io/json/test_readlines.py,sha256=xt3eQw48tEH0I-k21V0WKOe_1P1QZLkanF7ilacIGW8,18639 +pandas/tests/io/json/test_ujson.py,sha256=Ni2QRfmeeFDB4HYhoeqDchD1yfZW5hBZXJWUNePqmYE,35229 +pandas/tests/io/parser/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/io/parser/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/io/parser/common/test_chunksize.py,sha256=NtLJi5GLN7yMGdgFYb3qFXiQnjOIQE6sWNXK0liXfwo,10730 +pandas/tests/io/parser/common/test_common_basic.py,sha256=0rs5PvQfbW7Ssxy40dO2ztYMF67DBeQH0-evohzuXac,26037 +pandas/tests/io/parser/common/test_data_list.py,sha256=OV5-NDcAlHsK74iPNGQJL5kDPuWz6tI75ydlVGnDGM8,2229 +pandas/tests/io/parser/common/test_decimal.py,sha256=Dk2iCT5oqQuxetzEC46geo1md9DLjQZ-nnVe9AP4Iws,1933 +pandas/tests/io/parser/common/test_file_buffer_url.py,sha256=xloY6yNf8840KKlWmfNzHl7xOpcWS97zG4x0mEFWyOk,13643 +pandas/tests/io/parser/common/test_float.py,sha256=VGSyzFyf6u21D4mcMJfq9W-jeW7b4qj4BZDlujdaazY,3414 +pandas/tests/io/parser/common/test_index.py,sha256=I1Rnf-2gfId_ExWVNpP4fXpp_IcovMHlYfIX9m5lZqo,8301 +pandas/tests/io/parser/common/test_inf.py,sha256=ME_R47OHIYsN8RS600l3KYO3VYmllda0Q58zUCy_6y8,1462 +pandas/tests/io/parser/common/test_ints.py,sha256=2aihvxuBxsndErCEPczsU8nZ-29GGwkZFGSkPiyoh4s,7504 +pandas/tests/io/parser/common/test_iterator.py,sha256=nFMWcHq3MbdZYG5mVvBvJ1RchGnYFHWpL0gRXEwYVgM,4339 +pandas/tests/io/parser/common/test_read_errors.py,sha256=76VD52Cb9Dv1QgdlJJ5OGfVGwBW-6Zdn7wbF4z2a83Q,9216 +pandas/tests/io/parser/conftest.py,sha256=JVRpaE0BXy7SZIN3Af0x7vvoqsZhAR-aVRU5QC0tAho,9144 +pandas/tests/io/parser/dtypes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/io/parser/dtypes/test_categorical.py,sha256=L7ZUJGQPAosyU_rd0B33dH4QSqSaMPjKcC3kVLvR8Yg,10053 +pandas/tests/io/parser/dtypes/test_dtypes_basic.py,sha256=mT05xfPXJQy_lgh7ciYTRW8H3cU_yt5-cL543j77U0k,18580 +pandas/tests/io/parser/dtypes/test_empty.py,sha256=4rir0609_8fWuBTRlX5_0pmyOgOgdKJmL0dZOjQDntE,5239 +pandas/tests/io/parser/test_c_parser_only.py,sha256=IXxBxfse2xbUo65VtEYECUKw9IT3uqdqzf6w0ymIXPQ,20515 +pandas/tests/io/parser/test_comment.py,sha256=wFyw-GEGCpie_YZc00Y_RxNJhRicuad6_K2E9IVOxs8,7061 +pandas/tests/io/parser/test_compression.py,sha256=HHFCqN97KGe3ZSGTjRmPWvRoZsfrZmSWe3eFCXwoJ20,6251 +pandas/tests/io/parser/test_concatenate_chunks.py,sha256=A5wrCCTZMy2qIbp1VTRZBmkoasDtX8B7fabWd39qbBY,1220 +pandas/tests/io/parser/test_converters.py,sha256=eGpuj_Ik1skUz2g6aAUTOU-gURgsa8BH9fu6DD6in9c,7445 +pandas/tests/io/parser/test_dialect.py,sha256=tgsdnhEkYBtjIKd-9BKAyQ8ATTSivnzIkiWiuLi513M,5844 +pandas/tests/io/parser/test_encoding.py,sha256=40m_StE89am-rxCorH-2TosQOWmKDaQuFLQX-sXH060,10457 +pandas/tests/io/parser/test_header.py,sha256=9VmYfHn3jSjp62ANyT3zkoqvbruDbidrmkpvpg8ccAY,20797 +pandas/tests/io/parser/test_index_col.py,sha256=gos1H_OR4hWKYgPa__c8RO8HjZvfPDWZQbu0LGbuHOU,11461 +pandas/tests/io/parser/test_mangle_dupes.py,sha256=eUdijSU6oDICuNhTWx9-OW8lyZhjR1pruFGROzmrgn8,5443 +pandas/tests/io/parser/test_multi_thread.py,sha256=VkLxmMIpanzqQ4jZH6I0isXONGT6Ftyl-U3DBQpTU-g,4346 +pandas/tests/io/parser/test_na_values.py,sha256=p1pD_4LVI0CjlzJ49J3wHqhoyiIIu1VA3N_V2q4qk9A,24211 +pandas/tests/io/parser/test_network.py,sha256=Bo5lF699nUppwReVkLRB28Cl_wMyqDfaTJvt1aRUbhQ,8150 +pandas/tests/io/parser/test_parse_dates.py,sha256=QE0Nzce3rGeqNHCgo4DHnx9ZaC05XcQbBHP7yxu2WVQ,25573 +pandas/tests/io/parser/test_python_parser_only.py,sha256=swS4xogtnXZaMO8jPG7XR5QzCNmPJNOTuQ6vhnWjvV4,17794 +pandas/tests/io/parser/test_quoting.py,sha256=prHJXIXZL9hk1DX7OP5eA05wpYXDx2XwRb3L9vjM1HQ,6488 +pandas/tests/io/parser/test_read_fwf.py,sha256=AgA4ME4GH2NYQQBm1fSa1_r0D9593mEQ8NFnwD0PP04,28959 +pandas/tests/io/parser/test_skiprows.py,sha256=1WumdGbulUv3HJu0VLf6DsLhlh6ijZUxfzsOz5_k1cg,9321 +pandas/tests/io/parser/test_textreader.py,sha256=qjsnWkVV1soNazezYYY6yEq6XmmyJi_AtKkkdzQiMnI,12091 +pandas/tests/io/parser/test_unsupported.py,sha256=AbclLLADBCt_iaconMf2CR1uMrhKPIVstqZJl8VkmyI,6985 +pandas/tests/io/parser/test_upcast.py,sha256=uZpeuJF0tugtJI4aJYWTUvnTqXjGfb1_Ug5q4xpuSmQ,3117 +pandas/tests/io/parser/usecols/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/io/parser/usecols/test_parse_dates.py,sha256=i5k83JMuzo1rCUILtKZZ7reTq49hrTPxYBiT5Q3TrfU,2103 +pandas/tests/io/parser/usecols/test_strings.py,sha256=W-yiJV8_gPC-28aMZLlrmKcWVOyxO-LZPfwTdg8ns8I,2508 +pandas/tests/io/parser/usecols/test_usecols_basic.py,sha256=m56D6fxzgAZZG_5xq0VyZB7xUiH9SsNTcjKAezTZsaQ,17033 +pandas/tests/io/pytables/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/io/pytables/common.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/io/pytables/conftest.py,sha256=ByKTV58anIb_BmFOaGwkhv3raeZduwF7f6rRfc9zw8E,569 +pandas/tests/io/pytables/test_append.py,sha256=LTT-7jSrHREbMXRuHKChnpoZXFA_un4c3xnFJIkk8sk,34592 +pandas/tests/io/pytables/test_categorical.py,sha256=3mQKNRwBPdaNvIEia9f3_0P37QPoJdXxNMYHe-hGcV0,6206 +pandas/tests/io/pytables/test_compat.py,sha256=rTK4qJHZ2THo1cpQzHYU00J32XeZRx6rYnYmIqmwxSw,6218 +pandas/tests/io/pytables/test_complex.py,sha256=h3p7thXdZr7cERaJuT_CbT35AQlmgD-iNUlTsqZ4Hl8,6262 +pandas/tests/io/pytables/test_errors.py,sha256=VwSvR1YIsxabOEaPcm9TxtyB4UvfPkZqh_9TPTO1pmo,8120 +pandas/tests/io/pytables/test_file_handling.py,sha256=b5LaSAIAtHB3lbjKG1mzkT2Tgtt0TDf7FeiX0KcDmEw,15043 +pandas/tests/io/pytables/test_keys.py,sha256=eYFng0OxtVD-dMxjrgrBla6vTk3GGk4LUZe6IlSujcE,2454 +pandas/tests/io/pytables/test_put.py,sha256=Qi6aIYvjfcYlfceRAVC0zXoT3G8duY-puEMJ1Hb5xkg,13376 +pandas/tests/io/pytables/test_pytables_missing.py,sha256=O5EhDR708gZUweiXussIAc8p0ohoGmQj5O92gve3G60,284 +pandas/tests/io/pytables/test_read.py,sha256=munAKsYWtwDQZZpz9WvLgmDBybDuXqpNKrAkqkXU0nU,10005 +pandas/tests/io/pytables/test_retain_attributes.py,sha256=Rd-cimRQWQnkE2wvj0hYOhWXS6x0tXzc-HxMNcICqG4,3084 +pandas/tests/io/pytables/test_round_trip.py,sha256=HOqRMxaGU3yUy1S1cZH1PcKqepzziTLRam3ZGLikaBY,18344 +pandas/tests/io/pytables/test_select.py,sha256=7ftJ-wxPK-1IdaPTOe8KKPyuywtg_0KbTQ2wKeoDCQM,35636 +pandas/tests/io/pytables/test_store.py,sha256=YHXalk9GTxZgn0bNnC2Di7irnnF4u2pIDVtRTaGWsFo,35032 +pandas/tests/io/pytables/test_subclass.py,sha256=-dyeNkV8mmnV1o3hK8j7h5SHavOH6FsPP4qKsj3_Jp0,1297 +pandas/tests/io/pytables/test_time_series.py,sha256=tSxRrE5yeqvkzTCdfbsMKDMD1gXkWk8reCkylW8f4Xc,2215 +pandas/tests/io/pytables/test_timezones.py,sha256=NpQwupsnX7ZjoEP7xkan_X6G3A2fdZBFDkgF8EKcBCE,10232 +pandas/tests/io/sas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/io/sas/test_byteswap.py,sha256=a59FncAKlQoP-duxXze-4ZAH0d2s0n_r8dsLtzR4u5E,2023 +pandas/tests/io/sas/test_sas.py,sha256=JmS0euCnxus8cQjdprqC-OMUIHbGJWejszByUz6ei2w,1001 +pandas/tests/io/sas/test_sas7bdat.py,sha256=NVBHoWG5kN91zgWNEVI0L1K1EZBG54zzmMihImIQuns,14818 +pandas/tests/io/sas/test_xport.py,sha256=280Iz_xMPtk_DwBPv5owrRPZPDzztwX2QNmN1c5RKOw,5649 +pandas/tests/io/test_clipboard.py,sha256=Mu-R1zhjqe9BTcEOHS5FTUru7AGEGvFE5fAqAWMyRoU,12580 +pandas/tests/io/test_common.py,sha256=zndke18Z79xeNfl7-oX_s8s2apIvQpAXn2IWoJWgeiE,25197 +pandas/tests/io/test_compression.py,sha256=fNnEAmK8I6ZID85OyA541gQDMDqNzBpPGQ3rsipSkLs,12025 +pandas/tests/io/test_feather.py,sha256=VFTnJ_XWMw5PxcakGkfypAOJ-pNPebj1bkLlXeOHW9c,10351 +pandas/tests/io/test_fsspec.py,sha256=7URlr0RhKug8W6hMHGakMJFPoO_GwFdzNP66lzrvCzA,10739 +pandas/tests/io/test_gcs.py,sha256=2IT-sYxcbg882WcC5Tdt1qHuHA9IJ6vomrn5yFozcx0,7491 +pandas/tests/io/test_html.py,sha256=gWJv9g_eP9IvoNn1e-nVLScu3u2oRAFshOO0PzWcVe0,56905 +pandas/tests/io/test_http_headers.py,sha256=-Omu11d7g-05Vqf8A8c7BOYbjNHnALEoE02sqOlkJEY,4782 +pandas/tests/io/test_iceberg.py,sha256=Y_JxgCfBS5L5-rADEu3R79HMx2tzw7Uib9_MdLFl3aY,6424 +pandas/tests/io/test_orc.py,sha256=CXZBqRW30gDvfizGZZzBX46hF8o4W9iMhonp64ISVrY,13932 +pandas/tests/io/test_parquet.py,sha256=-a9dbOObtmaaT-HcEWYf7rKzLk_gVA5c1Dqte2QMKzM,51747 +pandas/tests/io/test_pickle.py,sha256=AMZmXhaeziwE-UQc3Y6ytSIRfbvhPBrIbP7mhMy31h4,20500 +pandas/tests/io/test_s3.py,sha256=nKEmNr0-l6Aj-OchwDGHXLj6w2EbZ5g2Afv5-1aToWA,908 +pandas/tests/io/test_spss.py,sha256=3TaaQ66yf8cZzuqizTmExwBXAIA6RQN90qgCrAuR4Og,6448 +pandas/tests/io/test_sql.py,sha256=Qmr8KoSQ0lZVLGMfFEOdArIukkuo2Sa1sM1dAgd3y8k,145051 +pandas/tests/io/test_stata.py,sha256=YNQyysSkilPlxuMrgwdUuI6Mh5WvvJuEHaTIVE0T2-Y,100596 +pandas/tests/io/xml/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/io/xml/conftest.py,sha256=gra94hruWijDpMIaUuubUeMObhYdCZDZHanN999-wjw,2668 +pandas/tests/io/xml/test_to_xml.py,sha256=rgpVvhF1C9gmC7DIrZ5BF-9tycWZOd6o3FnldqmjawU,35127 +pandas/tests/io/xml/test_xml.py,sha256=SlxptxEL9zlrvmJifw8d1YzmteDLg2ZNQeiQ40bCR_Q,60443 +pandas/tests/io/xml/test_xml_dtypes.py,sha256=HJElhzaXjv0K32Rbwb3eEI2QQZ8gT55BYyHpgH5crxw,12392 +pandas/tests/libs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/libs/test_hashtable.py,sha256=EaIwTLsRswlf4QFNhaq14QvfGHdU2RK40kzBLACL3LA,27212 +pandas/tests/libs/test_join.py,sha256=MiNaWCAggAJ09u7JCJvnDuQa9tRJYHeOTcBF-kgERg8,10736 +pandas/tests/libs/test_lib.py,sha256=L9dLpEauLifKPSL2zt-tii8P87kSfh6twhoeioAKaAo,13071 +pandas/tests/libs/test_libalgos.py,sha256=saDyCbchGU690HmrfZUJ6q1iCLNeW4x50Y-A2o1fgrg,5322 +pandas/tests/plotting/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/plotting/common.py,sha256=YfOJDB1QVGzfy8XKMSvgXs25X6XjS5YkJ58hrRibfWk,17614 +pandas/tests/plotting/conftest.py,sha256=YH3dBkw3i5_5EeDbTXXgB9CADB0s65Szr3ev8Agt0qw,882 +pandas/tests/plotting/frame/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/plotting/frame/test_frame.py,sha256=_D3knLZZt3Jf3uBvJ1_IMwVlGDSkDvCg2F7crF2112c,101332 +pandas/tests/plotting/frame/test_frame_color.py,sha256=2QRsMmtD58SzUgKlZ0TOK5JEZbX3c2UQmomK-VfaL18,30621 +pandas/tests/plotting/frame/test_frame_groupby.py,sha256=LXJXvw_4ipmE1Q2mpPKgJ4wx43d4rtkqbT1RTwrz8eg,2571 +pandas/tests/plotting/frame/test_frame_legend.py,sha256=bqJbWcmjzzQHsKYNKEIlrTFIBtqsSCOCKGM3R-TjSNU,10089 +pandas/tests/plotting/frame/test_frame_subplots.py,sha256=-RFSV4v91G9n5fx5SPAY7U5oTgzpYUdi-2WpzSf7d8o,28961 +pandas/tests/plotting/frame/test_hist_box_by.py,sha256=8jqVQfLrE5AKvn7iKMX7L5Gbe7e4rv6Ic8MnNp7NALI,10969 +pandas/tests/plotting/test_backend.py,sha256=ZhGAejJIUZlQiNviKNx4qmdrRjcz3jV10WkWl_Ccq24,3419 +pandas/tests/plotting/test_boxplot_method.py,sha256=G8PuQK8e73hvgGfECQ0hkVHwB9KQNag2gSh4uFAtPuo,29925 +pandas/tests/plotting/test_common.py,sha256=if9WnxryRdUhub-3yjdTEKO2PME-Yhf5YIG8e2nvAXU,1869 +pandas/tests/plotting/test_converter.py,sha256=lDlmtrV_dTitrIqMNLLnJ7qvYYKBIr5aHgaA490SxBM,12695 +pandas/tests/plotting/test_datetimelike.py,sha256=-HIRS8FI1ZOLMtFGAS9LpJU0F_asZ8RPUzfZTZu8rKE,65153 +pandas/tests/plotting/test_groupby.py,sha256=W1epVG86SZz0KJxRvLsHL2_ozEyZQNhKKUNNJVsfypk,5745 +pandas/tests/plotting/test_hist_method.py,sha256=-eqEByfX24hH8-ePP9toYhWnYq5Wiydyi9NJQn1-opI,34607 +pandas/tests/plotting/test_misc.py,sha256=SPl1x7PaIxwmmPzIOuflqlYMfDfM3gXmQW6yx01YDzA,30692 +pandas/tests/plotting/test_series.py,sha256=DXZ-mCEHcWu-ngm34CkbHQuP3nvMQuYhWOO1S6KgA5c,35833 +pandas/tests/plotting/test_style.py,sha256=A4pHHhA5L6nDznv_OBGULcJvV4Q9XFxa-BeE37ENAqc,5000 +pandas/tests/reductions/__init__.py,sha256=vflo8yMcocx2X1Rdw9vt8NpiZ4ZFq9xZRC3PW6Gp-Cs,125 +pandas/tests/reductions/test_reductions.py,sha256=-3dRedGwpBCXzVwqPY0SnHrYhqppRG0Cb_ygsavFPRE,57024 +pandas/tests/reductions/test_stat_reductions.py,sha256=vdiJxQsDBzSjitpZpThRT0Qa-QLjZWiNzlD7hSIK3nc,9952 +pandas/tests/resample/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/resample/conftest.py,sha256=Yr1zDF2xNxcULKbgBv4QvUBeEaLRx3f1ENm2S-Cmp_Q,706 +pandas/tests/resample/test_base.py,sha256=J0FF8A6tC60_u5zOJGmMPcfMIaRC8EpP0wM7hPNCSFE,18141 +pandas/tests/resample/test_datetime_index.py,sha256=lUVLn-69bWhWkrNR8eWL3QXpxXui0qnGMyJNfSWO6pw,73695 +pandas/tests/resample/test_period_index.py,sha256=J1dXKL92rZEVbO5CBpVeC5Q8v4wHw4PV6d27YPJKFLI,39512 +pandas/tests/resample/test_resample_api.py,sha256=RFmdv0NVHw0fMBXkFk149CGKQ_Wi53mhFUziEbhDBKg,31396 +pandas/tests/resample/test_resampler_grouper.py,sha256=bAKUZyoEXVaPJO5-eCnxm0hJMqOlxm9dYrdH73Gaqes,20606 +pandas/tests/resample/test_time_grouper.py,sha256=_CbO5ON_ATtV3NxfdWnJiccF2OgJvQmCvagl5eItvsQ,13529 +pandas/tests/resample/test_timedelta.py,sha256=o9FFIDTQvsCSWzh3ga_syozsxmUrqCRnlWO03-PWfMA,7426 +pandas/tests/reshape/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/reshape/concat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/reshape/concat/test_append.py,sha256=M4UiYtVjXVm-9ZpL3alq7p0I6RR_kw1vN_loo4YdqEw,12744 +pandas/tests/reshape/concat/test_append_common.py,sha256=04IqFgj_EkDXXevH5CsaOvfeEExoK4JTUWtsNnbrwgw,25913 +pandas/tests/reshape/concat/test_categorical.py,sha256=uieXfiGwsWOxbX8qrPu22EZX_XlbkeMWkdp_dKJtU68,9572 +pandas/tests/reshape/concat/test_concat.py,sha256=nhCql4Lrv-odKf13gR0UnAvZMbrqbyWxsNSl-c8GGP8,35135 +pandas/tests/reshape/concat/test_dataframe.py,sha256=MFqZSzStp0xwhTuPV_oX6S6hq0h8HeqtJkkYZVMBPQo,9154 +pandas/tests/reshape/concat/test_datetimes.py,sha256=lucGprVV9ztiYC-B8p3VkP-bQiZ__ZxRR5jPy9bbrqI,21521 +pandas/tests/reshape/concat/test_empty.py,sha256=l05Z2NKEypQWWIRh_E9nrsO7YrffLXGMNlAvgnEVbeM,10082 +pandas/tests/reshape/concat/test_index.py,sha256=iea_Bsw7YnwDo10ofCMCGXbSmYIKXzT-x_K7cuX9Jls,16860 +pandas/tests/reshape/concat/test_invalid.py,sha256=FLfwEc4BA0brgFzLR9r5iGpZVQ0YN4qSNMc4Si40X-U,1609 +pandas/tests/reshape/concat/test_series.py,sha256=WVjp1G9gzV5Q4jQUxKjIPNIfxy5Y9RDnjkflScKV5bw,6513 +pandas/tests/reshape/concat/test_sort.py,sha256=RuXIJduLa56IJDmUQaCwyYOz_U0KXMDWf04WEzi8y7E,4350 +pandas/tests/reshape/merge/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/reshape/merge/test_join.py,sha256=1DGcBYzTO_QlmiJ2UTCqfJRCyl5evoNle97BeIushIQ,40938 +pandas/tests/reshape/merge/test_merge.py,sha256=Hu3nzTVcIsA4VfCdt7ydOAov8pfT-lcdtX_IkBbG5V4,114204 +pandas/tests/reshape/merge/test_merge_antijoin.py,sha256=RAfztfXv0zUpW5DPBfyZFW_KJQsp5FUVqYxCQyGuPfE,8673 +pandas/tests/reshape/merge/test_merge_asof.py,sha256=3AH_ldzZ2NVgQt8kfIJ2zfc-DISXmCMvstxb_3XuDZA,121009 +pandas/tests/reshape/merge/test_merge_cross.py,sha256=ooLQTZpA36ctCZrf-W3i_OK3YNcLPlgau9MEWYRZe-I,3124 +pandas/tests/reshape/merge/test_merge_index_as_string.py,sha256=w_9BccpqfB7yPhy_TBlMGx2BPOBwPhfg-pYRKA4HEC8,5357 +pandas/tests/reshape/merge/test_merge_ordered.py,sha256=wroqGLxMqYbuL7G4ak4WjnV0wqlUiTSKPBCdi3Y-zag,7561 +pandas/tests/reshape/merge/test_multi.py,sha256=57_PiohZbiefjFjJ97tX5qsHBfAAOBf-x0ra8alCkcE,31000 +pandas/tests/reshape/test_crosstab.py,sha256=iwj9zbzPkKEvaQzGHZ_l3kNWsym1byCrAczK6Fav-bQ,32141 +pandas/tests/reshape/test_cut.py,sha256=3xajaaSxP5M7_XDgW_-5Yb-4xc3dVipVzfxFp4xPRh0,25764 +pandas/tests/reshape/test_from_dummies.py,sha256=8_40euLzaWU5XLsRSiaP_sCqFTSD5wwXfK_5g9ByJ-o,14068 +pandas/tests/reshape/test_get_dummies.py,sha256=b30YYlHO2uwcofgC85boNphvIKqhrKdrEo_x55dMqvI,27570 +pandas/tests/reshape/test_melt.py,sha256=6ZxNMh-cxXJDYSIizrFg1rw-na1jcBczXBycWLEGHbk,43351 +pandas/tests/reshape/test_pivot.py,sha256=xce2M2k9msPTM6lbZ0Xsy1HrgmUDgAe5_twnst9B9Hc,100397 +pandas/tests/reshape/test_pivot_multilevel.py,sha256=ErEhpzw8NgRM8BbYg_pLWjd0ioZtBKZ3zDQxkHg6_GE,8910 +pandas/tests/reshape/test_qcut.py,sha256=Muy6kdH6Na54lfwYpWrT977JA4aIb3bO7g7Pld-tSr8,8734 +pandas/tests/reshape/test_union_categoricals.py,sha256=UvadOpYUCmkJ-cGmATHVBmVu8LajVsWjMlyS4rAI9hk,15301 +pandas/tests/scalar/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/scalar/interval/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/scalar/interval/test_arithmetic.py,sha256=qrUOEDp9dOkOoEfuuUHhmzKTZuPbj727p2PxO1kgxxM,5937 +pandas/tests/scalar/interval/test_constructors.py,sha256=DI5iRKoIg51lI_-FysKQyyaJnwrd8CqLjk7b7iqFIp0,1599 +pandas/tests/scalar/interval/test_contains.py,sha256=MSjo5U7KLuqugnEtURC8znpldI3-cLIfXQlIhNvQLI4,2354 +pandas/tests/scalar/interval/test_formats.py,sha256=Ep7692gGQMdrYiCxxudqXX-CA6S1sO3L2P2I4NHIreo,344 +pandas/tests/scalar/interval/test_interval.py,sha256=W54SKFbFSlsvFwoXkNhb6JK52klz8is2ww2ZQ7AIjUs,2656 +pandas/tests/scalar/interval/test_overlaps.py,sha256=2FHG23scoclsfZZAngK9sesna_3xgbjgSKoUzlMxHro,2274 +pandas/tests/scalar/period/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/scalar/period/test_arithmetic.py,sha256=HQj3jdrQoNerdMzJqr8EPnH1b3wXJIhl97zT2g9EKeo,16784 +pandas/tests/scalar/period/test_asfreq.py,sha256=8N4gkwPr1UyTMUeeXlhImHRpz-3A0irKO-kZHeDFdKw,37719 +pandas/tests/scalar/period/test_period.py,sha256=BbK4ip8QXKuMG2TnJu_RhvpwuzKYk3jzAM_5nPGmUIU,42434 +pandas/tests/scalar/test_na_scalar.py,sha256=5GRFkIMY2APqJKxsjwm6I0feb8MeEvJFlVWhuhsV5wM,7777 +pandas/tests/scalar/test_nat.py,sha256=uu8MGizeUZIig4nrujOzR5n_6JiAOrdQ-6ecSNz00Ok,19045 +pandas/tests/scalar/timedelta/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/scalar/timedelta/methods/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/scalar/timedelta/methods/test_as_unit.py,sha256=haaxElsbR0uQFvZqbTe40vJ28fi9W3AqOzyAAsss7wc,2509 +pandas/tests/scalar/timedelta/methods/test_round.py,sha256=XVJFPZOtz5sROKt8OPcafO5ihtOxOOlkEErL23hv0Zw,6076 +pandas/tests/scalar/timedelta/test_arithmetic.py,sha256=PFHzLawmjKmESsnLeyNXwobf95nuk-NtQmgBuMxOxkc,40332 +pandas/tests/scalar/timedelta/test_constructors.py,sha256=HhqHoUWydMEbS4tpxhwK1BoYn2t8NjJwufC_ey_EVBM,24803 +pandas/tests/scalar/timedelta/test_formats.py,sha256=wHw6p4pVC0XwJUAmV7-YlW5st3BBhCWum6LF4090dY8,4161 +pandas/tests/scalar/timedelta/test_timedelta.py,sha256=V22GapQIOUSexW37eBqopDD7ldfjOSFXesGL37LV650,25809 +pandas/tests/scalar/timestamp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/scalar/timestamp/methods/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/scalar/timestamp/methods/test_as_unit.py,sha256=Od0YhrglrVPaad4kzpjPKoVf-pBz0_lTbdaj7cpD7eU,2706 +pandas/tests/scalar/timestamp/methods/test_normalize.py,sha256=ppzmweVYFoszEXcArKPc8Q0UPhXMmBe1sWNBj7DlxpQ,1009 +pandas/tests/scalar/timestamp/methods/test_replace.py,sha256=uWAFCmm-kn52XuAvv65F0yjmahH6YUu_ZQIax4zM61E,7803 +pandas/tests/scalar/timestamp/methods/test_round.py,sha256=1p7IYXue-zjc1iJICZjDe_9CCfJoBKwzK2PYpx-MYz4,12705 +pandas/tests/scalar/timestamp/methods/test_timestamp_method.py,sha256=XAV9BmyrlCuG_TOrZp4JLgp5noCba4ehdzqeVp5Flas,1151 +pandas/tests/scalar/timestamp/methods/test_to_julian_date.py,sha256=izPqS1f7lJ3Tqkiz65t3NjZqtgxu1_jbSg-LmZheiD4,810 +pandas/tests/scalar/timestamp/methods/test_to_pydatetime.py,sha256=tCzj0-fQ-BIa5dYvROpBRVPkq_PlDK4Pg43opnYSv-U,2988 +pandas/tests/scalar/timestamp/methods/test_tz_convert.py,sha256=yw1GiCOn7F8ZDof9d7IvG6T28e6nsB-_XswfO0HN-Dc,1710 +pandas/tests/scalar/timestamp/methods/test_tz_localize.py,sha256=27PbKCMXpY7VoZQwBz-cvXolPzpqNCxoAj-vdFKgZpw,12485 +pandas/tests/scalar/timestamp/test_arithmetic.py,sha256=u4o0hfCJ1HuXG6BHeCIzJqk1oqaBiGi0VVV-mny7hnk,11286 +pandas/tests/scalar/timestamp/test_comparisons.py,sha256=UXSCF5beVi5A3YGnP_Wf0ExiZqyKV2SvPr0XhDMD2d4,10027 +pandas/tests/scalar/timestamp/test_constructors.py,sha256=8TO42E2p37vNU6l6BbgEjSFuOsns6xBPb9jexpTAx6E,41034 +pandas/tests/scalar/timestamp/test_formats.py,sha256=Xp5JBP6rznYBULZi1hSUMGpUIgaeNyOLUo1POfWqB9E,6914 +pandas/tests/scalar/timestamp/test_timestamp.py,sha256=S_GngGOm7zdN52SD958cweQCNnZ2pKvzvCUAIF5gTPk,31349 +pandas/tests/scalar/timestamp/test_timezones.py,sha256=KRTWEcDMVcVrSQzlZdUzWVEnNEoYLIzACQppoCHNPY8,667 +pandas/tests/series/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/series/accessors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/series/accessors/test_cat_accessor.py,sha256=uFj-oJS0vs6CcOsAVUdgD4GdXKfT58_3wlhDTWEtB8I,9646 +pandas/tests/series/accessors/test_dt_accessor.py,sha256=LVb_GjoHttqnR4kD1dEnN7YE14wi_Xa6Asua0bIPeiE,29014 +pandas/tests/series/accessors/test_list_accessor.py,sha256=SuRitMrVfNscvYQBCclff8dj2ttt8Bza0MWodRiWNDo,3552 +pandas/tests/series/accessors/test_sparse_accessor.py,sha256=yPxK1Re7RDPLi5v2r9etrgsUfSL9NN45CAvuR3tYVwA,296 +pandas/tests/series/accessors/test_str_accessor.py,sha256=0kPyrz6InkgYizUSTYaqHvclxe-bu62YLP75ptjl7pE,907 +pandas/tests/series/accessors/test_struct_accessor.py,sha256=qk8R_FCahESiPlIX53p-zJO16J1i3eWkUf92mF7ydq8,5121 +pandas/tests/series/indexing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/series/indexing/test_datetime.py,sha256=jlWlWrucIEQhcK7lpw3SkDLyMVPeEqtl0ccae6gWv8w,14447 +pandas/tests/series/indexing/test_delitem.py,sha256=IVd1S6LV2DIELVikf8uw30lNFuFNRIfe1Mg3MbCIyYc,1969 +pandas/tests/series/indexing/test_get.py,sha256=QovFGIoEryipLZokDRs9bdzutvMc8CWziM0TCmGNrYc,5141 +pandas/tests/series/indexing/test_getitem.py,sha256=0pQKBa6lqkyK6jVp_O8RTAVk66hEompJgiY607bFMAA,23328 +pandas/tests/series/indexing/test_indexing.py,sha256=0ssB97Bj8Xzeh9jfLy4iKUCDGcBrl1emmNfSnYG6wUw,15722 +pandas/tests/series/indexing/test_mask.py,sha256=ecPdJ-CM8HbaaZoGUfwcoOuo0eIz7aEq-x8wL0PZWbE,1711 +pandas/tests/series/indexing/test_set_value.py,sha256=W9O5vR5uJSASIwe1EisJt9V9lwm5isqJ6aLmLA9LtS8,1163 +pandas/tests/series/indexing/test_setitem.py,sha256=Al_RIlB4F9dwx6R2tflhueHDRqc70MKCw3flaX0adT4,57345 +pandas/tests/series/indexing/test_take.py,sha256=574cgL0w0fj-YnZma9b188Y0mTWs-Go6ZzB9zQSdpAk,1353 +pandas/tests/series/indexing/test_where.py,sha256=SNfleiA_4bmgQBQD9K-zw0ApeCaBwvAhlCBgJjgAHJE,12309 +pandas/tests/series/indexing/test_xs.py,sha256=8EKGIgnK86_hsBjPIY5lednYnzatv14O6rq3LjR_KxI,2760 +pandas/tests/series/methods/__init__.py,sha256=zVXqGxDIQ-ebxxcetI9KcJ9ZEHeIC4086CoDvyc8CNM,225 +pandas/tests/series/methods/test_add_prefix_suffix.py,sha256=PeUIeDHa9rGggraEbVJRtLi2GcnNcXkrXb0otlthOC4,1556 +pandas/tests/series/methods/test_align.py,sha256=cRNlE9nMB74KYt5KUXgv7P-PKFRxBdhoDdhAtWDFZnk,6023 +pandas/tests/series/methods/test_argsort.py,sha256=jY986VoHOMW2xbraWoYvGhpHiRK48-B6qX920ds4VcE,2539 +pandas/tests/series/methods/test_asof.py,sha256=CqRdyeXFhE7zVdkJB-TxVqK3XPyBNvtOAfb6_a0VGgM,6324 +pandas/tests/series/methods/test_astype.py,sha256=1mNghF8SixBkOJrLVnH4BzEp8qJTvP1oKcp_rlZvmFs,25801 +pandas/tests/series/methods/test_autocorr.py,sha256=SnxELB9bcE8H68tYUDN3UKMMPu-sEfbwTlLUn8WirV8,1015 +pandas/tests/series/methods/test_between.py,sha256=3JHbcMYU3zlMZ7E8lVvO9crDYifyRAx-HL5T0oviugo,2565 +pandas/tests/series/methods/test_case_when.py,sha256=BhE4YdgZYXHS1jY4cDML8SjYo7xb7u_wrBLeqqJD2J8,6376 +pandas/tests/series/methods/test_clip.py,sha256=W1OKH1QSv55Ks_4HpPZ3MV7Zplc0kNnqEID_FpTRpFs,5350 +pandas/tests/series/methods/test_combine.py,sha256=JTsowMo8o4SG8eIf3Eev5dZHA3Z1gPeOkQdD5ggAHdU,862 +pandas/tests/series/methods/test_combine_first.py,sha256=_nxHPWeZetwv6d-ri_9I9BTBwGD0C_DtWBCRhI1AlXc,5962 +pandas/tests/series/methods/test_compare.py,sha256=qg5mmTYwbnxV7tHV4R1yKvkRc8eqgZMfONuu6JgWODw,4514 +pandas/tests/series/methods/test_convert_dtypes.py,sha256=lyfq-KIBoHh44WE2YMJu3mTXqHLFi8VDm4X38KspQek,11489 +pandas/tests/series/methods/test_copy.py,sha256=8NrWjO9A2SZ0Qg8GUEwCmOV-JylMLYwvRjnz14h7uC0,2451 +pandas/tests/series/methods/test_count.py,sha256=0u6o9FWkpsHwl4qUMsvRAA6Y8xf-M5F2ooMc9jKAQD4,567 +pandas/tests/series/methods/test_cov_corr.py,sha256=2fROep2P-wEE7cW62DZF3d0AuD0gpLJvpsJH34-KNvw,5770 +pandas/tests/series/methods/test_describe.py,sha256=Ft4dqyD1wmwkyrdFex6utGomGnDEyr8KGIgnU1uPqOk,6571 +pandas/tests/series/methods/test_diff.py,sha256=ZruDpR1oJs8VCAQMBLLGGJiuRBS22x1uurRZMfXXiVA,2703 +pandas/tests/series/methods/test_drop.py,sha256=c1NUqTaHfDMAG0tDxlB7LJMWTaKeXxzBYA1vJRn_eXY,3309 +pandas/tests/series/methods/test_drop_duplicates.py,sha256=QPczNwP6usv7Kdc-75Er1uL5LXMtAcJLfaOO_gqcuXc,9468 +pandas/tests/series/methods/test_dropna.py,sha256=fezc4siTNn-uOEQtOhaqNAOLYBoWN3Rh6STHAtOdk8U,3577 +pandas/tests/series/methods/test_dtypes.py,sha256=IkYkFl0o2LQ5qurobwoPgp4jqi2uKU7phoAk3oZtiYo,209 +pandas/tests/series/methods/test_duplicated.py,sha256=AAnnxLPlOuFrbxb5xaaq4haijlyHSEYVDzZZp6kaD44,2049 +pandas/tests/series/methods/test_equals.py,sha256=ZxskDNppC-T_-TlImtw95jckjaw3LqpkYZcqisys8S8,3710 +pandas/tests/series/methods/test_explode.py,sha256=Kd1CxulicyqBE-OgQdDOAjJWS5hAwnx3xRY0yo6grMk,5438 +pandas/tests/series/methods/test_fillna.py,sha256=F9DKDDPJogTYM6lBES6aOwAQpeOCli_fezlEGpX6XUE,35519 +pandas/tests/series/methods/test_get_numeric_data.py,sha256=qOJVAEu5iXFyYmzmMfvNMzhKXZyyne_6om0WjdGolHU,915 +pandas/tests/series/methods/test_head_tail.py,sha256=1EWojjTzcLvYH34VvyvEHxczDy7zL3dMTyayFHsVSzY,343 +pandas/tests/series/methods/test_infer_objects.py,sha256=n6ARaDq_lmSMm_SurU8oo_ab1opX7LJrrPl2STzsTrA,1883 +pandas/tests/series/methods/test_info.py,sha256=-NMQ5wPdgO6JazckQckVruM62cu1lRJAFRi_J9N3HFk,5644 +pandas/tests/series/methods/test_interpolate.py,sha256=dVRnVzlUvuFI8fyPPvuYa0Rfz7xyoQf-HO0Aqf7rtGc,31826 +pandas/tests/series/methods/test_is_monotonic.py,sha256=vvyWZFxiSybq88peF0zN5dM16rH2SgCEEA-gT2rRSSY,838 +pandas/tests/series/methods/test_is_unique.py,sha256=d3aLS5q491IVZkfKx8HTc4jkgTtuN0SOaUVfkyBTImE,953 +pandas/tests/series/methods/test_isin.py,sha256=dRe54VPrWPMIngeBwVEiTKFI8y5wxp4zBI6md8brYe4,9236 +pandas/tests/series/methods/test_isna.py,sha256=ZI-RS_N_nXN7x_7uD6vrd3bs6yEFI2kU13QOIP21qf8,941 +pandas/tests/series/methods/test_item.py,sha256=osZR5Ur83EjspKkCKMlgFBxRFxOSX86c69kxlgpIU7E,1624 +pandas/tests/series/methods/test_map.py,sha256=kv3RLNPpnwywNwMA-j3QRTiT5c5I2LN3UvE19zFOI98,20816 +pandas/tests/series/methods/test_matmul.py,sha256=cIj2nJctMnOvEDgTefpB3jypWJ6-RHasqtxywrxXw0g,2767 +pandas/tests/series/methods/test_nlargest.py,sha256=RDnBRV3N1wAJSs2nrhb1PSDGwpsD39Xx5MQbhGAxOBI,8020 +pandas/tests/series/methods/test_nunique.py,sha256=6B7fs9niuN2QYyxjVNX33WLBJvF2SJZRCn6SInTIz0g,481 +pandas/tests/series/methods/test_pct_change.py,sha256=hEGpP0ogirgF0RmdEivtahOZMbZd7XAoXEe6bcGsGSE,2602 +pandas/tests/series/methods/test_pop.py,sha256=xr9ZuFCI7O2gTW8a3WBr-ooQcOhBzoUK4N1x0K5G380,295 +pandas/tests/series/methods/test_quantile.py,sha256=7StIHLpwcX2W7htu3DGa2cRY8FhkAvge5VkEXPAylL4,8499 +pandas/tests/series/methods/test_rank.py,sha256=Afa8XtandUFeGmOxr5ZyhFOBj9ysI4VyFVScGg7vWfU,21833 +pandas/tests/series/methods/test_reindex.py,sha256=4Rp_NAW1vXKGJA7cRr_qWsqCQ39e-l0QsJ-nbbsvubg,15085 +pandas/tests/series/methods/test_reindex_like.py,sha256=Kp3uiGm1b872gP4EErMORfnHGB8W7CK9Ih1zlP8WzYY,1515 +pandas/tests/series/methods/test_rename.py,sha256=GTSoXgg7kw1M4Dcce_x-U8jrRGdYXTzRmrB1k5EPKXk,6059 +pandas/tests/series/methods/test_rename_axis.py,sha256=TqGeZdhB3Ektvj48JfbX2Jr_qsCovtoWimpfX_ViJyg,1520 +pandas/tests/series/methods/test_repeat.py,sha256=WvER_QkoVNYU4bg5hQbLdCXIWxqVnSmJ6K3_3OLLLAI,1274 +pandas/tests/series/methods/test_replace.py,sha256=Z1Ca8MsqOulntvugkWtlHkQiugOO6-_y6pmT5PyGjeA,27914 +pandas/tests/series/methods/test_reset_index.py,sha256=lxoYm2mZOWzf6IkDtZHxRn17eMhdN77MR0db2sLvA2c,7874 +pandas/tests/series/methods/test_round.py,sha256=ygmmfvEa2o4DiwRfrYsEKFxRCwZwKc-P9xIwsPFqcYU,3976 +pandas/tests/series/methods/test_searchsorted.py,sha256=2nk-hXPbFjgZfKm4bO_TiKm2xjd4hj0L9hiqR4nZ2Ss,2493 +pandas/tests/series/methods/test_set_name.py,sha256=oQJkyHeQnAMKYiXDTpVdqnEbzS1rbEFc6K2a29oSPto,595 +pandas/tests/series/methods/test_size.py,sha256=t00s7A01wrvO5Zz5k2oEpPcHMZgvUK6zA-Iciyal9DY,495 +pandas/tests/series/methods/test_sort_index.py,sha256=cFghdhbjHQX5GFr3pllvLo6arD0tr2rOX5kF_EWi-CM,12647 +pandas/tests/series/methods/test_sort_values.py,sha256=BnY-ocaAoKFhm0nsGSq1jfx3o2tBmc2I0OqxsVVKuIQ,8975 +pandas/tests/series/methods/test_to_csv.py,sha256=aqF5O_J7NRNAvnrfJsvBFwq-wideyPY-IozujMMEcx0,6011 +pandas/tests/series/methods/test_to_dict.py,sha256=XGdcF1jD4R0a_vWAQXwal3IVJoNwEANa1tU7qHtpIGA,1178 +pandas/tests/series/methods/test_to_frame.py,sha256=nUkHQTpMTffkpDR7w3EcQvQAevEfflD6tHm3pTBxpTI,1992 +pandas/tests/series/methods/test_to_numpy.py,sha256=pEB2B08IdIPRYp5n7USYFX9HQbClJl4xOegjVd7mYLc,1321 +pandas/tests/series/methods/test_tolist.py,sha256=5F0VAYJTPDUTlqb5zDNEec-BeBY25ZjnjqYHFQq5GPU,1115 +pandas/tests/series/methods/test_truncate.py,sha256=suMKI1jMEVVSd_b5rlLM2iqsQ08c8a9CbN8mbNKdNEU,2307 +pandas/tests/series/methods/test_tz_localize.py,sha256=3SCLDEcRiKsgBGJ9b89l46vqvH-JHT6MQt8hFIJOWyQ,4266 +pandas/tests/series/methods/test_unique.py,sha256=MQB5s4KVopor1V1CgvF6lZNUSX6ZcOS2_H5JRYf7emU,2219 +pandas/tests/series/methods/test_unstack.py,sha256=bTqv7z2UEnEqQbi-B5dkaXOksA4SW-fpkixPfQ3XidE,5057 +pandas/tests/series/methods/test_update.py,sha256=v30YNj74ptA1byHdRhwGd6ZTHy2ujoE3DYwv_S-P34s,4991 +pandas/tests/series/methods/test_value_counts.py,sha256=kFqFpbMBOPTu8zLiTJUhsJZMiVP0QrZXxXPlHWRexEs,9723 +pandas/tests/series/methods/test_values.py,sha256=Q2jACWauws0GxIc_QzxbAOgMrJR6Qs7oyx_6LK7zVt8,747 +pandas/tests/series/test_api.py,sha256=0JHgyBzkwzHA4hm_HX37L89Zyro0vwtD_WovS03PRnE,9452 +pandas/tests/series/test_arithmetic.py,sha256=55v1yfbIVV44GF8U9riuizRyQNuPUCwHG37jD6SQGx8,37013 +pandas/tests/series/test_arrow_interface.py,sha256=Y_CyEjXMszOJf6g56w6ziO-VYYGKr2sqyO_aES_MPBU,3282 +pandas/tests/series/test_constructors.py,sha256=_DtgiFuUg4wcTZyWUR8qTdUXsRcGcBkp11fbMY0aMrA,85538 +pandas/tests/series/test_cumulative.py,sha256=mKqO4fwgWzZm2zwEqt07553j8NJwa_8t5DUcTZyMtNw,9954 +pandas/tests/series/test_formats.py,sha256=V_zd84Mx4mEZpO5jhIO8c4Mjn_h2fOAEVRLRW0pE54Y,17405 +pandas/tests/series/test_iteration.py,sha256=IN_TP52Gb6BWbbeDNBVJmeHy8y-DyxylgaCffXit5Hg,1346 +pandas/tests/series/test_logical_ops.py,sha256=FGmPM1bVMbHLh3YcV75Ic3y1R7esl_emQGwWM21eL18,18134 +pandas/tests/series/test_missing.py,sha256=ikdq93p2L4pE2ROQz2WPLtalsIRDQuvFcRrXOmoCOYs,2593 +pandas/tests/series/test_npfuncs.py,sha256=kNvKdZlIJK4-K6ap2v8jsyzJRMdY2eomEsi36d45y24,1338 +pandas/tests/series/test_reductions.py,sha256=2vqTxvZfdNBk7lh0GN3nFRsapBGablPg_qAxr9jFc4A,7109 +pandas/tests/series/test_subclass.py,sha256=aL5tgGGXZPPIXWIgpCPBrc7Q5KS8h1ipZNKCwciw-jY,2667 +pandas/tests/series/test_ufunc.py,sha256=uJorDnE792Y-Mhw2QTr4asXylWqkurTKsjP6iV-r2WM,15370 +pandas/tests/series/test_unary.py,sha256=Xktw6w940LXm38OKLW-LRqpMZSA9EB5feCt9FMLh-E4,1620 +pandas/tests/series/test_validate.py,sha256=ziCmKi_jYuGyxcnsVaJpVgwSCjBgpHDJ0dbzWLa1-kA,668 +pandas/tests/strings/__init__.py,sha256=bXy3OI--skxWsx5XSeisvRlIrXiyNmNvUZPzTSa-82s,587 +pandas/tests/strings/conftest.py,sha256=2_BS0Gd3_EFdSfeieYQMWnlTVQ2AXJk-BXo8jrIjbbg,3967 +pandas/tests/strings/test_api.py,sha256=RRdJIICXknwX6cVT2Mc_a3MxjxLpGjTvYM_CzOFIbEk,6785 +pandas/tests/strings/test_case_justify.py,sha256=X_Wap9pwqH-XUXi3xSOVfAIk033c1KcHFx9ksYWJrxg,13361 +pandas/tests/strings/test_cat.py,sha256=zyY-HRITkrNwOei8o9EZlaIhRQOLes4rAHXX83nQb2Y,14187 +pandas/tests/strings/test_extract.py,sha256=a-f9N8IYY8GZFluim4aUWr3624dsx5ARaaJbqj1vEos,28906 +pandas/tests/strings/test_find_replace.py,sha256=EipMXErNQR2YKWdsXo7bDcbpwgiFwIaLwHp5HB08C-A,59872 +pandas/tests/strings/test_get_dummies.py,sha256=d_cEGhOn7FuArr28heSZTVvlyycQzNK-7WabIST-iWs,2629 +pandas/tests/strings/test_split_partition.py,sha256=MKkn0LBDb9HWhwAVvJLj4b1jeLV8UOCwL3TNJEUgPoE,24913 +pandas/tests/strings/test_string_array.py,sha256=UUNh0lbavuT9OK5aU-V6kz4d0_o-gzlM4sAjrc2ILhQ,4013 +pandas/tests/strings/test_strings.py,sha256=DsFEGgph5_Eoq5NeIij3mz7KIhaFB286FY91J6YGjG4,33173 +pandas/tests/test_aggregation.py,sha256=uxU70uDtpfDvtl5hcgvFIKCl2OxtaE65kFcHG-pf6i0,2779 +pandas/tests/test_algos.py,sha256=EkyPk80bTytXKpzE7ibIrmsKFTISFJXJ0J8iX0Buc_s,78463 +pandas/tests/test_col.py,sha256=qGvMzT8EGsgaHW80nVN-4QScavpNeZuSk7QQ2kQJ8Dg,12707 +pandas/tests/test_common.py,sha256=-fJ71tSbvD6HSdCDawON35B5l2QrI36ChPyeQdMk6Fo,8005 +pandas/tests/test_downstream.py,sha256=_nnCUudf7hVuydllxvEWUw_XqcnlGijnk-uyxxMTvNg,8678 +pandas/tests/test_errors.py,sha256=nlLIKiB82sk3ahlut9a-TVtt6YqCjZLQTqK53v1Ut4Q,4016 +pandas/tests/test_expressions.py,sha256=7bJWuKOfMgYx0g2dWFOgTB77ig4HvxmDdz8hiorblX8,14893 +pandas/tests/test_flags.py,sha256=Dsu6pvQ5A6Manyt1VlQLK8pRpZtr-S2T3ubJvRQaRlA,1550 +pandas/tests/test_multilevel.py,sha256=Awh4ayS2GiuYvl1knlfuhH15k64dDhgj1_dgQIUjEx8,12785 +pandas/tests/test_nanops.py,sha256=GhuFqwUZL95lGlF0d9R-AVGLg9xZTXoAVxNvwXYe-UE,44665 +pandas/tests/test_optional_dependency.py,sha256=LsOmpEJdXIITyPO1WxePl4rP42dhaKQNUjAQTnn_oCk,3217 +pandas/tests/test_register_accessor.py,sha256=TSWZRIxrMprzgi28IpX6_hQtnOhOSYIcQ12bHBH-p5M,3237 +pandas/tests/test_sorting.py,sha256=jAcMyB_OUZWWxS1ElEDoG6a0JfVULRe6ndvZPC2HRrk,16465 +pandas/tests/test_take.py,sha256=jhzDMPSLRfIDtM1gmBBlkrb1RTScuCERDRKGobhZiSQ,11908 +pandas/tests/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/tools/test_to_datetime.py,sha256=rWHh8DZjGvDu4yMBH7dD57JFkKwtLkcYPhRyKrXzuio,142951 +pandas/tests/tools/test_to_numeric.py,sha256=MDIk928IisOgi3onEJKd6nIW7nbmOYhY7P70kEPi8Uk,26471 +pandas/tests/tools/test_to_time.py,sha256=2JEiYtJ2q0HA6snH8Um0nDLOsxGqeyA_Pycos0kP8zA,2083 +pandas/tests/tools/test_to_timedelta.py,sha256=qpPsiW0zgKB6HCh2s6UzXlq6VohCS_b1K8OZSuSNMvU,13482 +pandas/tests/tseries/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/tseries/frequencies/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/tseries/frequencies/test_freq_code.py,sha256=hvQl37z3W6CwcLOAqrgc2acqtjOJIbqVbnXkEUBY4cM,1727 +pandas/tests/tseries/frequencies/test_frequencies.py,sha256=tyI9e6ve7sEXdALy9GYjMV3mAQHmQF2IqW-xFzPdgjY,821 +pandas/tests/tseries/frequencies/test_inference.py,sha256=AHJnmvzBDRSbE6bVznyWv3e4HFr6Im-LmNHYuOArTSE,15728 +pandas/tests/tseries/holiday/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/tseries/holiday/test_calendar.py,sha256=WKrvH1zsJkaivNORGNToTHrb9uSzLe_z3aI7Z_k-ZqQ,3622 +pandas/tests/tseries/holiday/test_federal.py,sha256=BInZnWn3kWUkFOALtBcSW5cUXJDh4ccwQGZmMTOD7K0,1948 +pandas/tests/tseries/holiday/test_holiday.py,sha256=WIeVbSzNCr-vKBJ1accnzdtVSJP9D97d5yqvbx4OdMA,15160 +pandas/tests/tseries/holiday/test_observance.py,sha256=GJBqIF4W6QG4k3Yzz6_13WMOR4nHSVzPbixHxO8Tukw,2723 +pandas/tests/tseries/offsets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/tseries/offsets/common.py,sha256=GLmqCSf3lfXDZBn3E-I-Kx7KjG77y_HEHk9NxWbZW_g,901 +pandas/tests/tseries/offsets/test_business_day.py,sha256=gp7kUUZRxgAqO8U-Doal_ocOKIZb9w_7QO-zxh_DeZs,6809 +pandas/tests/tseries/offsets/test_business_halfyear.py,sha256=ZF7Bd15yXnB5kCPvWfBu_oRNgy62YZGgh_CaI3nJ8OQ,13968 +pandas/tests/tseries/offsets/test_business_hour.py,sha256=skptCA3jSb81Bo1o7hNT_pVk6hSTJ6AnRg4h7XtlNDk,58547 +pandas/tests/tseries/offsets/test_business_month.py,sha256=RsZ4ubtWtHG5xVJz0jwEdtubplASW3VwAYNOicHHHoA,6718 +pandas/tests/tseries/offsets/test_business_quarter.py,sha256=CTzWcqHIS1plmhteZ6wzfeUKhTFtwdzyKAe5HmL5LxU,11887 +pandas/tests/tseries/offsets/test_business_year.py,sha256=zpQpxiUdiD7CZChuiVJLPLPrdWhElTZPa9v_1pv5MGE,6437 +pandas/tests/tseries/offsets/test_common.py,sha256=WWI-3H52kgYpDcMztBEse4Lm8DbPZrgLurV8-cv_qus,7327 +pandas/tests/tseries/offsets/test_custom_business_day.py,sha256=G7XFrdGGev0LqN0OsNlA_o-_Tci-nneScUAwQGpeYFI,3203 +pandas/tests/tseries/offsets/test_custom_business_hour.py,sha256=1YL5TBRLDrBotGrUT6mDSYVsdmvt4OzLgNqIQ_P3Rx8,12217 +pandas/tests/tseries/offsets/test_custom_business_month.py,sha256=TLRofF7nZrHrStflruDQdpZzawpfYW7PsCj-TIr-BCk,12939 +pandas/tests/tseries/offsets/test_dst.py,sha256=DzAFAd1EKycjms6e7HzuGuUhI7AUeUHAR_nZsvHJWNY,9409 +pandas/tests/tseries/offsets/test_easter.py,sha256=w89tUAe0bzkcrj9sxP7bvSaZLrK9t5rIuyA-r6gIzOc,4675 +pandas/tests/tseries/offsets/test_fiscal.py,sha256=E4Zpt_uNB2f2wAm_iGRLJdNYjNBt2ySLlV7rlnSkTDk,26080 +pandas/tests/tseries/offsets/test_halfyear.py,sha256=8TsQ5aLU4nvFQFCbdG9zn5b350uTVQKDKo5GF8NX6T8,13836 +pandas/tests/tseries/offsets/test_index.py,sha256=6uO2-I0_nSloj_vOrLJKkUNhRifLNafEnnJPEhCr4XY,1148 +pandas/tests/tseries/offsets/test_month.py,sha256=zlj2JRZX-tdBRDQ7mwNoINJeX8rL0_eyF_0CPj-AeUo,23270 +pandas/tests/tseries/offsets/test_offsets.py,sha256=z0WAspC8ieu1OpzmgnBdTnbqPCwl9fEI1-zB-xK9Om4,41299 +pandas/tests/tseries/offsets/test_offsets_properties.py,sha256=0rqMUjtgOLopo8FNuA8tz1wCtQ76-ApAeFcyFxkxeM4,2262 +pandas/tests/tseries/offsets/test_quarter.py,sha256=BJBFPJEnpIpRUE-4XAxaJ-wOSYXRe6YQWo7B5XW8QJE,11143 +pandas/tests/tseries/offsets/test_ticks.py,sha256=I9L4wG7lhHe7CvMuL-y55kTjQJYuVXREUMmaEh9MElU,10899 +pandas/tests/tseries/offsets/test_week.py,sha256=3us7zk4pN07wYptHuhGSPb3dwAFdBAQJQuOTMd2SdnU,11959 +pandas/tests/tseries/offsets/test_year.py,sha256=fEuEQrZ_kWoLL0NnYicCRxnhOtm4FtQXEq7MmgyI9CQ,10456 +pandas/tests/tslibs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/tslibs/test_api.py,sha256=YtFXZ3RFN8eiI9nG5e9YiM3kFKpDKcITowUrKygxt2M,1540 +pandas/tests/tslibs/test_array_to_datetime.py,sha256=jd8hndasDRkzkf9Joee8s_mnq7K0oJuV7VCcJIZ0rTA,10750 +pandas/tests/tslibs/test_ccalendar.py,sha256=8wreW2d8xw2znrIo9Yy02NnqziDz9bb3wnlXqJUtaE4,1921 +pandas/tests/tslibs/test_conversion.py,sha256=txTD_R-TJRvODDjeBZDM6A436bPq5vHPZBZ5Za1IhCU,4696 +pandas/tests/tslibs/test_fields.py,sha256=BQKlBXOC4LsXe7eT2CK5mRGR_25g9qYykQZ6ojoGjbE,1352 +pandas/tests/tslibs/test_libfrequencies.py,sha256=Ai6deDiGlwUHR9mVvlkIbXYzWZADHuPLlaBjDK0R2wU,717 +pandas/tests/tslibs/test_liboffsets.py,sha256=dVn_bjqG31xptHlOft3qWT0EFADbH5LO_pZ6gLDl2ZA,5097 +pandas/tests/tslibs/test_np_datetime.py,sha256=n7MNYHw7i03w4ZcVTM6GkoRN7Y7UIGxnshjHph2eDPs,7889 +pandas/tests/tslibs/test_npy_units.py,sha256=d9NFsygcKGtp-pw-ZpOvIxMhpsRqd1uPBVlqejHkNmU,922 +pandas/tests/tslibs/test_parse_iso8601.py,sha256=XGQ_GBOCosTiOFFjK4rYoDDZcIBitnyIb_0SXxKF9yo,4535 +pandas/tests/tslibs/test_parsing.py,sha256=uivGQhfeppyZzOfw4w_9xn9v-tGpZJ06qhGyNKWWOFY,14512 +pandas/tests/tslibs/test_period.py,sha256=I-Q0LdCxztj3TdA41Y3BBomVauHpwUz-Kjl3alC08tI,3423 +pandas/tests/tslibs/test_resolution.py,sha256=MQ9kZTg__f_58twLxAiQFYY3ybN1X-XnRfDwsu3tR9k,1404 +pandas/tests/tslibs/test_strptime.py,sha256=My4-AyQoCyGcdVa0d9keeUWT_ArND2QN8I_6aINeQo4,3940 +pandas/tests/tslibs/test_timedeltas.py,sha256=B9IdK9eYl33w7H5sJYvUyhZfpzNzLsoTqM2XQoyLZf4,4813 +pandas/tests/tslibs/test_timezones.py,sha256=WPmtrVhUjQo6aORGWkbiUvb2WlgzEwKU34aSnQf03f4,5608 +pandas/tests/tslibs/test_to_offset.py,sha256=ollK0fCIWzXAys_0uNKIXpCwsWAKDi3k7EyHfPKYVTo,6660 +pandas/tests/tslibs/test_tzconversion.py,sha256=vCNHPVEzzA-3R2fBRlCorZeVC3UfTgrPFt3L4gd7a6I,961 +pandas/tests/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/util/conftest.py,sha256=X-ANfN5vkNQFDPKKvKsb3XvF7JAghJo7yA_7NGcyzSg,1183 +pandas/tests/util/test_assert_almost_equal.py,sha256=CEK7SU-CkEzw0gquHfToGQ_Q2SmBvdttBuYXIGLrG3I,16852 +pandas/tests/util/test_assert_attr_equal.py,sha256=ZXTojP4V5Kle96QOFhxCZjq-dQf6gHvNOorYyOuFP1I,1045 +pandas/tests/util/test_assert_categorical_equal.py,sha256=ez_OaBg4NA4gjmd5RCXS6XzXg9efVVWremISMyQA5ac,2706 +pandas/tests/util/test_assert_extension_array_equal.py,sha256=VgKOn8oys3UQHPExXd8SZzyZ-wJ2uZEl49k7vWUfCz8,3831 +pandas/tests/util/test_assert_frame_equal.py,sha256=Reb-fCTfG_DrjgJqmOscfWhaDBc5VDjmqF6AkdWS4mg,14631 +pandas/tests/util/test_assert_index_equal.py,sha256=KVvP4d5_g4aeH9s8teLmZhSCUcEkbUIyofEQl5sgobI,10505 +pandas/tests/util/test_assert_interval_array_equal.py,sha256=uxN8ElLtM6ThMFDPs3FQwukeCSfddnYN-4bkQOJz9Xo,2651 +pandas/tests/util/test_assert_numpy_array_equal.py,sha256=fgb8GdUwX4EYiR3PWbjJULNfAJz4DfJ8RJXchssygO4,6624 +pandas/tests/util/test_assert_produces_warning.py,sha256=jc-7lpv9Pb6JPP9HI6p2dfeBWFtI4ZeZ_YcwQQQsqpk,10123 +pandas/tests/util/test_assert_series_equal.py,sha256=mcjpGi6UgyVP6r3MHPgppsdyl6EKzy7PbwCYpeXL4FE,16068 +pandas/tests/util/test_deprecate.py,sha256=2jrGwkiXFd057pYt5YXiTfwF-Okt46U-FojmD6hUycU,1758 +pandas/tests/util/test_deprecate_kwarg.py,sha256=jajNQIfVMThnNEYdL-Q6I64PLGleVHT7DGegK6mNQfM,2186 +pandas/tests/util/test_deprecate_nonkeyword_arguments.py,sha256=nDktPdti6FB6pATXTqnAXobXjvV6n-l6ArbVDj4fd_s,3965 +pandas/tests/util/test_doc.py,sha256=u0fxCg4zZWhB4SkJYc2huQ0xv7sKKAt0OlpWldmhh_M,1492 +pandas/tests/util/test_hashing.py,sha256=nHyUa_cC3DjIltzEs3zbFgrYj9WNyfQQkZNAoW1C2SI,13947 +pandas/tests/util/test_numba.py,sha256=Z242WoayIC0ZcpuDob3laHE0KtRQi30IfqBfjOB0-B4,313 +pandas/tests/util/test_rewrite_warning.py,sha256=kjw3QoXXML7s_mG3TxJ701Y5FD1rfGyTHJJTlKqVdwk,1231 +pandas/tests/util/test_shares_memory.py,sha256=blPdVD-tSlk07hQxUFzgpf3SbahlY9fuDFPCFE3aW9s,1169 +pandas/tests/util/test_show_versions.py,sha256=FjYUrUMAF7hOzphaXED__8yjeF0HTccZS6q05__rH44,2096 +pandas/tests/util/test_util.py,sha256=4UacWPLyjRQZU697jBxBWO6V1gUgkE4E-KKF6H6aXuE,1463 +pandas/tests/util/test_validate_args.py,sha256=9Z4zTqnKAWn1q9KZNvuO3DF6oszHjQrQgtOOimurWcs,1907 +pandas/tests/util/test_validate_args_and_kwargs.py,sha256=d_XcMRAQ9r--yIAAWSdJML6KeWgksy5qRNFXaY1BMQA,2456 +pandas/tests/util/test_validate_inclusive.py,sha256=w2twetJgIedm6KGQ4WmdmGC_6-RShFjXBMBVxR0gcME,896 +pandas/tests/util/test_validate_kwargs.py,sha256=0YhkYN4KhU2qxMRSbOuK7BiGt5nqPYjW3ydCLfjThYo,1821 +pandas/tests/window/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/window/conftest.py,sha256=7kRzgvzhPViWOFAYYSJIrnbXbRhpFcvmmaBcnP1fvhg,2595 +pandas/tests/window/moments/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pandas/tests/window/moments/conftest.py,sha256=xSkyyVltsAkJETLDHJSksjRkjcVHsnhfyCiNvhsQ3no,1595 +pandas/tests/window/moments/test_moments_consistency_ewm.py,sha256=lpC_O7fVNHr5hSEHbg0NaVSs-V6jW6L5R1F9WwtFR2A,8122 +pandas/tests/window/moments/test_moments_consistency_expanding.py,sha256=yL6bZ5h0EOXIZna8NZMoKf_EWVPM6vRmMtRLXR9VQwE,5703 +pandas/tests/window/moments/test_moments_consistency_rolling.py,sha256=-j_Qvj6FgRHPBZpUynkCfL74fellvoGb63TFAyZ5SJo,7842 +pandas/tests/window/test_api.py,sha256=9EAtNSNJpsPGr_iKAw8fyRU16xJ7sTmkujU_osp43D0,11817 +pandas/tests/window/test_apply.py,sha256=FB9JSmkZ0LTT_mgK5QwyfYeWRPo3O6bieAeo2yjWdfg,9521 +pandas/tests/window/test_base_indexer.py,sha256=-MTmbQxURT6jkL2PrAUOzlAsx_Dvu0KHTUMyJEzvJBg,15946 +pandas/tests/window/test_cython_aggregations.py,sha256=ZtvbONG_rcObyje-6iQB34GfFh6YrNM929FoYkrUnvA,4162 +pandas/tests/window/test_dtypes.py,sha256=a3Xnqcq_jO0kczZmhmuBKkmCsKHOOufy9h6yNCPHlMk,5785 +pandas/tests/window/test_ewm.py,sha256=kVi7crtwubcL7mqmNZ2yE4O9yjUdJ6nSbxidbQjc8eM,23276 +pandas/tests/window/test_expanding.py,sha256=4StW2Zwc79pVRqgLLl1wA6pYsgb_w3HHxhoMt5P_JlQ,26803 +pandas/tests/window/test_groupby.py,sha256=NS3QK1Tw-a7gwX0Y5PPkCqmxUPerHofDGcezRGYh02s,47203 +pandas/tests/window/test_numba.py,sha256=m3gww_EskJKJ1WaK-OyE3zvJSgBciA8ckVp5x5RrWMQ,22246 +pandas/tests/window/test_online.py,sha256=jhtEqYVPRoc9o7h4INf7-w2k5gKiVQJFnArH3AMFsEA,3644 +pandas/tests/window/test_pairwise.py,sha256=9fb-NX4Bwp5qlGNFlLGPRdl11uECECCrM_gzH28Bvlg,16652 +pandas/tests/window/test_rolling.py,sha256=rO1tkMm9itnZ2pEolvu9o3sE6Zyyr1GLaLZFLjg7OVw,64595 +pandas/tests/window/test_rolling_functions.py,sha256=UCp87Ptx458YJa-ygAfbnn9b0n_lgdeZFHeAuGkvLlg,18020 +pandas/tests/window/test_rolling_quantile.py,sha256=xKeSvOz_Nr9rdORU8JDdEyvEoinzYeLXF8dGRhOO3WE,5333 +pandas/tests/window/test_rolling_skew_kurt.py,sha256=Emw9AJhTZyuVnxPg-nfYxpRNGJToWJ-he7obTSOy8iU,7807 +pandas/tests/window/test_timeseries_window.py,sha256=RQTt3xK5ZD-9zfyWK31aONs_otBCDDyjzZDSxnRAVJY,25130 +pandas/tests/window/test_win_type.py,sha256=j2sFpzE5n5MJYkVgMaE3Gbr2kAyGm99ei5jbanmIy6A,16934 +pandas/tseries/__init__.py,sha256=EPx-FGPBq4Vw6aC-wjVI0u5ucWvjPhid58AwyVM3Et4,292 +pandas/tseries/api.py,sha256=73akBLVhbceRBzQejKM5yQRzWkDDYggs-8Qx6irT5vo,234 +pandas/tseries/frequencies.py,sha256=ie94UPEBufwQa-YmWWDK11yKH7ZzATixjVRIlBoJjdQ,18541 +pandas/tseries/holiday.py,sha256=l4m_D-M4XItiP09_e7gNAAfIi822q3qDN2Hxus1UHWs,20445 +pandas/tseries/offsets.py,sha256=pfmrqsT3oZW9CfDfbIAxTo2eXzKidgmF0XYhJ7stglc,1687 +pandas/util/__init__.py,sha256=UjtmOCtEXbbNS9RBjLIjdzvA1QfFt0mUN7pNKJAeabE,863 +pandas/util/_decorators.py,sha256=_XfWGe9Yr_C0fkWfQm9HxDN4k9f9SEvbqBAGs3nmDBM,17570 +pandas/util/_doctools.py,sha256=mgFrDJoN_08mpiSs7EUOUDbF1yzs9ZzHpl4HF2WAXDE,6911 +pandas/util/_exceptions.py,sha256=jTjbii4rda9Tm0F-3M46GK9VHM5uWk09L8ppZbLzTQA,2870 +pandas/util/_print_versions.py,sha256=ri5MmX8vu01oan8pJsCDCFnf-UfiWENbl0SZakf7udI,4824 +pandas/util/_test_decorators.py,sha256=BPOusXH4Ib75w5QDzKrZzPNa-Bnisk2cyfkdWDbT8A0,4428 +pandas/util/_tester.py,sha256=l52vgBA01VIKgHAPr8Uw5pr1tPAlzjz4G68VW19prGc,1643 +pandas/util/_validators.py,sha256=HeZUDgyGiqK0IPT_P-eHO9Z_iGerFBJihi9Yfpg9dNM,14918 +pandas/util/version/__init__.py,sha256=W2PyOE79hfQvJPphuu3wvsqpoZh1ZOG7wqHDx5n3e3k,13142 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas-3.0.2.dist-info/REQUESTED b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas-3.0.2.dist-info/REQUESTED new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas-3.0.2.dist-info/WHEEL b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas-3.0.2.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..5948c78d8feb91a9801be86a88c75c38c31c533c --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas-3.0.2.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: meson +Root-Is-Purelib: false +Tag: cp314-cp314-manylinux_2_24_x86_64 +Tag: cp314-cp314-manylinux_2_28_x86_64 + diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pandas-3.0.2.dist-info/entry_points.txt b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas-3.0.2.dist-info/entry_points.txt new file mode 100644 index 0000000000000000000000000000000000000000..3c1b523d70758fbd0080e21ca4c7ce6d9c9d9bd5 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pandas-3.0.2.dist-info/entry_points.txt @@ -0,0 +1,3 @@ +[pandas_plotting_backends] +matplotlib = pandas:plotting._matplotlib + diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/propcache-0.5.2.dist-info/INSTALLER b/bioagent-bench/.venv/lib/python3.14/site-packages/propcache-0.5.2.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..5c69047b2eb8235994febeeae1da4a82365a240a --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/propcache-0.5.2.dist-info/INSTALLER @@ -0,0 +1 @@ +uv \ No newline at end of file diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/propcache-0.5.2.dist-info/METADATA b/bioagent-bench/.venv/lib/python3.14/site-packages/propcache-0.5.2.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..37cb00053d85735d9823ee5f8f8e1dbe5c59afa2 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/propcache-0.5.2.dist-info/METADATA @@ -0,0 +1,539 @@ +Metadata-Version: 2.4 +Name: propcache +Version: 0.5.2 +Summary: Accelerated property cache +Home-page: https://github.com/aio-libs/propcache +Author: Andrew Svetlov +Author-email: andrew.svetlov@gmail.com +Maintainer: aiohttp team +Maintainer-email: team@aiohttp.org +License: Apache-2.0 +Project-URL: Chat: Matrix, https://matrix.to/#/#aio-libs:matrix.org +Project-URL: Chat: Matrix Space, https://matrix.to/#/#aio-libs-space:matrix.org +Project-URL: CI: GitHub Workflows, https://github.com/aio-libs/propcache/actions?query=branch:master +Project-URL: Code of Conduct, https://github.com/aio-libs/.github/blob/master/CODE_OF_CONDUCT.md +Project-URL: Coverage: codecov, https://codecov.io/github/aio-libs/propcache +Project-URL: Docs: Changelog, https://propcache.readthedocs.io/en/latest/changes/ +Project-URL: Docs: RTD, https://propcache.readthedocs.io +Project-URL: GitHub: issues, https://github.com/aio-libs/propcache/issues +Project-URL: GitHub: repo, https://github.com/aio-libs/propcache +Keywords: cython,cext,propcache +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Programming Language :: Cython +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Topic :: Internet :: WWW/HTTP +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Requires-Python: >=3.10 +Description-Content-Type: text/x-rst +License-File: LICENSE +License-File: NOTICE +Dynamic: license-file + +propcache +========= + +The module provides a fast implementation of cached properties for Python 3.10+. + +.. image:: https://github.com/aio-libs/propcache/actions/workflows/ci-cd.yml/badge.svg + :target: https://github.com/aio-libs/propcache/actions?query=workflow%3ACI + :align: right + +.. image:: https://codecov.io/gh/aio-libs/propcache/branch/master/graph/badge.svg + :target: https://codecov.io/gh/aio-libs/propcache + +.. image:: https://badge.fury.io/py/propcache.svg + :target: https://badge.fury.io/py/propcache + + +.. image:: https://readthedocs.org/projects/propcache/badge/?version=latest + :target: https://propcache.readthedocs.io + + +.. image:: https://img.shields.io/pypi/pyversions/propcache.svg + :target: https://pypi.python.org/pypi/propcache + +.. image:: https://img.shields.io/matrix/aio-libs:matrix.org?label=Discuss%20on%20Matrix%20at%20%23aio-libs%3Amatrix.org&logo=matrix&server_fqdn=matrix.org&style=flat + :target: https://matrix.to/#/%23aio-libs:matrix.org + :alt: Matrix Room — #aio-libs:matrix.org + +.. image:: https://img.shields.io/matrix/aio-libs-space:matrix.org?label=Discuss%20on%20Matrix%20at%20%23aio-libs-space%3Amatrix.org&logo=matrix&server_fqdn=matrix.org&style=flat + :target: https://matrix.to/#/%23aio-libs-space:matrix.org + :alt: Matrix Space — #aio-libs-space:matrix.org + +Introduction +------------ + +The API is designed to be nearly identical to the built-in ``functools.cached_property`` class, +except for the additional ``under_cached_property`` class which uses ``self._cache`` +instead of ``self.__dict__`` to store the cached values and prevents ``__set__`` from being called. + +For full documentation please read https://propcache.readthedocs.io. + +Installation +------------ + +:: + + $ pip install propcache + +The library is Python 3 only! + +PyPI contains binary wheels for Linux, Windows and MacOS. If you want to install +``propcache`` on another operating system where wheels are not provided, +the the tarball will be used to compile the library from +the source code. It requires a C compiler and and Python headers installed. + +To skip the compilation you must explicitly opt-in by using a PEP 517 +configuration setting ``pure-python``, or setting the ``PROPCACHE_NO_EXTENSIONS`` +environment variable to a non-empty value, e.g.: + +.. code-block:: console + + $ pip install propcache --config-settings=pure-python=false + +Please note that the pure-Python (uncompiled) version is much slower. However, +PyPy always uses a pure-Python implementation, and, as such, it is unaffected +by this variable. + + +API documentation +------------------ + +The documentation is located at https://propcache.readthedocs.io. + +Source code +----------- + +The project is hosted on GitHub_ + +Please file an issue on the `bug tracker +`_ if you have found a bug +or have some suggestion in order to improve the library. + +Discussion list +--------------- + +*aio-libs* google group: https://groups.google.com/forum/#!forum/aio-libs + +Feel free to post your questions and ideas here. + + +Authors and License +------------------- + +The ``propcache`` package is derived from ``yarl`` which is written by Andrew Svetlov. + +It's *Apache 2* licensed and freely available. + + +.. _GitHub: https://github.com/aio-libs/propcache + +========= +Changelog +========= + +.. + You should *NOT* be adding new change log entries to this file, this + file is managed by towncrier. You *may* edit previous change logs to + fix problems like typo corrections or such. + To add a new change log entry, please see + https://pip.pypa.io/en/latest/development/#adding-a-news-entry + we named the news folder "changes". + + WARNING: Don't drop the next directive! + +.. towncrier release notes start + +0.5.2 +===== + +*(2026-05-08)* + + +No significant changes. + + +---- + + +0.5.1 +===== + +*(2026-05-08)* + + +No significant changes. + + +---- + + +0.5.0 +===== + +*(2026-05-08)* + + +Features +-------- + +- Added support for newer type hints and remove ``Optional`` and ``Union`` from all annotations + -- by `@Vizonex `__ + + *Related issues and pull requests on GitHub:* + `#193 `__. + + +Removals and backward incompatible breaking changes +--------------------------------------------------- + +- Dropped support for Python 3.9 as it has reached end of life. + + *Related issues and pull requests on GitHub:* + `#216 `__. + + +Packaging updates and notes for downstreams +------------------------------------------- + +- Changed the Cython build dependency from ``~= 3.1.0`` to ``>= 3.2.0``, + removing the upper version bound to avoid conflicts for downstream packagers + -- by `@jameshilliard `__ and `@gundalow `__. + + The upstream Cython version is pinned to 3.2.4 in the CI/CD environment. + + *Related issues and pull requests on GitHub:* + `#184 `__, `#188 `__, `#214 `__. + +- Start building and shipping riscv64 wheels + -- by `@justeph `__. + + *Related issues and pull requests on GitHub:* + `#194 `__. + +- The `PEP 517 `__ build backend now supports a new ``build-inplace`` + config setting (and ``PROPCACHE_BUILD_INPLACE`` environment variable) + for controlling whether to build the project in-tree or in a + temporary directory. It only affects wheels and is set up to build + in a temporary directory by default. It does not affect editable + wheel builds; they will keep being built in-tree regardless. + + Here's an example of using this setting: + + .. code-block:: console + + $ python -m build --config-setting=build-inplace=true + + Additionally, when building wheels in an automatically created + temporary directory, the build backend now normalizes the + respective file system path to a deterministic source checkout + directory by injecting the ``-ffile-prefix-map`` compiler option + into the ``CFLAGS`` environment variable, as suggested by known + `reproducible build practices + `__. + + The effect is that downstreams will get reproducible build results. + + *Related issues and pull requests on GitHub:* + `#218 `__. + + +---- + + +0.4.1 +===== + +*(2025-10-08)* + + +Bug fixes +--------- + +- Fixed reference leak caused by ``Py_INCREF`` because Cython has its own reference counter systems -- by `@Vizonex `__. + + *Related issues and pull requests on GitHub:* + `#162 `__. + + +Contributor-facing changes +-------------------------- + +- Fixes the default value for the ``os`` + parameter in ``reusable-build-wheel.yml`` + to be ``ubuntu-latest`` instead of + ``ubuntu``. + + *Related issues and pull requests on GitHub:* + `#155 `__. + + +---- + + +0.4.0 +===== + +*(2025-10-04)* + + +Features +-------- + +- Optimized propcache by replacing sentinel ``object`` for checking if + the ``object`` is ``NULL`` and changed ``dict`` API for + Python C-API -- by `@Vizonex `__. + + *Related issues and pull requests on GitHub:* + `#121 `__. + + +Contributor-facing changes +-------------------------- + +- Builds have been added for arm64 Windows + wheels and the ``reusable-build-wheel.yml`` + workflow has been modified to allow for + an OS value (``windows-11-arm``) which + does not include the ``-latest`` postfix + -- by `@finnagin `__. + + *Related issues and pull requests on GitHub:* + `#133 `__. + +- Added CI for CPython 3.14 -- by `@kumaraditya303 `__. + + *Related issues and pull requests on GitHub:* + `#140 `__. + + +---- + + +0.3.2 +===== + +*(2025-06-09)* + + +Improved documentation +---------------------- + +- Fixed incorrect decorator usage in the ``~propcache.api.under_cached_property`` example code -- by `@meanmail `__. + + *Related issues and pull requests on GitHub:* + `#109 `__. + + +Packaging updates and notes for downstreams +------------------------------------------- + +- Updated to use Cython 3.1 universally across the build path -- by `@lysnikolaou `__. + + *Related issues and pull requests on GitHub:* + `#117 `__. + +- Made Cython line tracing opt-in via the ``with-cython-tracing`` build config setting -- by `@bdraco `__. + + Previously, line tracing was enabled by default in ``pyproject.toml``, which caused build issues for some users and made wheels nearly twice as slow. + + Now line tracing is only enabled when explicitly requested via ``pip install . --config-setting=with-cython-tracing=true`` or by setting the ``PROPCACHE_CYTHON_TRACING`` environment variable. + + *Related issues and pull requests on GitHub:* + `#118 `__. + + +---- + + +0.3.1 +===== + +*(2025-03-25)* + + +Bug fixes +--------- + +- Improved typing annotations, fixing some type errors under correct usage + and improving typing robustness generally -- by `@Dreamsorcerer `__. + + *Related issues and pull requests on GitHub:* + `#103 `__. + + +---- + + +0.3.0 +===== + +*(2025-02-20)* + + +Features +-------- + +- Implemented support for the free-threaded build of CPython 3.13 -- by `@lysnikolaou `__. + + *Related issues and pull requests on GitHub:* + `#84 `__. + + +Packaging updates and notes for downstreams +------------------------------------------- + +- Started building wheels for the free-threaded build of CPython 3.13 -- by `@lysnikolaou `__. + + *Related issues and pull requests on GitHub:* + `#84 `__. + + +Contributor-facing changes +-------------------------- + +- GitHub Actions CI/CD is now configured to manage caching pip-ecosystem + dependencies using `re-actors/cache-python-deps`_ -- an action by + `@webknjaz `__ that takes into account ABI stability and the exact + version of Python runtime. + + .. _`re-actors/cache-python-deps`: + https://github.com/marketplace/actions/cache-python-deps + + *Related issues and pull requests on GitHub:* + `#93 `__. + + +---- + + +0.2.1 +===== + +*(2024-12-01)* + + +Bug fixes +--------- + +- Stopped implicitly allowing the use of Cython pre-release versions when + building the distribution package -- by `@ajsanchezsanz `__ and + `@markgreene74 `__. + + *Related commits on GitHub:* + `64df0a6 `__. + +- Fixed ``wrapped`` and ``func`` not being accessible in the Cython versions of ``propcache.api.cached_property`` and ``propcache.api.under_cached_property`` decorators -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#72 `__. + + +Removals and backward incompatible breaking changes +--------------------------------------------------- + +- Removed support for Python 3.8 as it has reached end of life -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#57 `__. + + +Packaging updates and notes for downstreams +------------------------------------------- + +- Stopped implicitly allowing the use of Cython pre-release versions when + building the distribution package -- by `@ajsanchezsanz `__ and + `@markgreene74 `__. + + *Related commits on GitHub:* + `64df0a6 `__. + + +---- + + +0.2.0 +===== + +*(2024-10-07)* + + +Bug fixes +--------- + +- Fixed loading the C-extensions on Python 3.8 -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#26 `__. + + +Features +-------- + +- Improved typing for the ``propcache.api.under_cached_property`` decorator -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#38 `__. + + +Improved documentation +---------------------- + +- Added API documentation for the ``propcache.api.cached_property`` and ``propcache.api.under_cached_property`` decorators -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#16 `__. + + +Packaging updates and notes for downstreams +------------------------------------------- + +- Moved ``propcache.api.under_cached_property`` and ``propcache.api.cached_property`` to `propcache.api` -- by `@bdraco `__. + + Both decorators remain importable from the top-level package, however importing from `propcache.api` is now the recommended way to use them. + + *Related issues and pull requests on GitHub:* + `#19 `__, `#24 `__, `#32 `__. + +- Converted project to use a src layout -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#22 `__, `#29 `__, `#37 `__. + + +---- + + +0.1.0 +===== + +*(2024-10-03)* + + +Features +-------- + +- Added ``armv7l`` wheels -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#5 `__. + + +---- + + +0.0.0 +===== + +*(2024-10-02)* + + +- Initial release. diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/propcache-0.5.2.dist-info/RECORD b/bioagent-bench/.venv/lib/python3.14/site-packages/propcache-0.5.2.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..1ea7cab81e9a34daf745396e69af93165958a1fb --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/propcache-0.5.2.dist-info/RECORD @@ -0,0 +1,15 @@ +propcache-0.5.2.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2 +propcache-0.5.2.dist-info/METADATA,sha256=9TRJXN0vkVr45kl4PZX93LGgWhrBgYUPDJc8Cnj3u64,16496 +propcache-0.5.2.dist-info/RECORD,, +propcache-0.5.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +propcache-0.5.2.dist-info/WHEEL,sha256=a6EB0SZmvnUxj7CP1sYpBVvB6TTNx-Vzw2sye5KrYcM,190 +propcache-0.5.2.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358 +propcache-0.5.2.dist-info/licenses/NOTICE,sha256=VtasbIEFwKUTBMIdsGDjYa-ajqCvmnXCOcKLXRNpODg,609 +propcache-0.5.2.dist-info/top_level.txt,sha256=pVF_GbqSAITPMiX27kfU3QP9-ufhRvkADmudDxWdF3w,10 +propcache/__init__.py,sha256=lNXqBAR5xSfCkeeYrdUBmpk154KWjPTdrh1rdqowtYE,965 +propcache/_helpers.py,sha256=68SQm6kETN8Mnt9Ol26LJYgHgmB0mKy1tp92888zN4k,1553 +propcache/_helpers_c.cpython-314-x86_64-linux-gnu.so,sha256=wXD10pnw9LOPkTVpawnFiHMp-N7fyp35B8ck3nqB0Go,151824 +propcache/_helpers_c.pyx,sha256=kcJa1U5lh54TPCqAeZ0cVB7URcb3I8ZbJieOrkNhLQE,3265 +propcache/_helpers_py.py,sha256=G29pl3XJ8T0LqleQxYMTh9SwI3_UdG7r9KGcQwbi0pc,1910 +propcache/api.py,sha256=wvgB-ypkkI5uf72VVYl2NFGc_TnzUQA2CxC7dTlL5ak,179 +propcache/py.typed,sha256=ay5OMO475PlcZ_Fbun9maHW7Y6MBTk0UXL4ztHx3Iug,14 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/propcache-0.5.2.dist-info/REQUESTED b/bioagent-bench/.venv/lib/python3.14/site-packages/propcache-0.5.2.dist-info/REQUESTED new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/propcache-0.5.2.dist-info/WHEEL b/bioagent-bench/.venv/lib/python3.14/site-packages/propcache-0.5.2.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..ebc70a69c0555f6054ce8510274071383de66de8 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/propcache-0.5.2.dist-info/WHEEL @@ -0,0 +1,7 @@ +Wheel-Version: 1.0 +Generator: setuptools (82.0.1) +Root-Is-Purelib: false +Tag: cp314-cp314-manylinux_2_17_x86_64 +Tag: cp314-cp314-manylinux2014_x86_64 +Tag: cp314-cp314-manylinux_2_28_x86_64 + diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/propcache-0.5.2.dist-info/licenses/LICENSE b/bioagent-bench/.venv/lib/python3.14/site-packages/propcache-0.5.2.dist-info/licenses/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..d645695673349e3947e8e5ae42332d0ac3164cd7 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/propcache-0.5.2.dist-info/licenses/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/propcache-0.5.2.dist-info/licenses/NOTICE b/bioagent-bench/.venv/lib/python3.14/site-packages/propcache-0.5.2.dist-info/licenses/NOTICE new file mode 100644 index 0000000000000000000000000000000000000000..fa53b2b138df881c4c95239d0e4bede831b36ab5 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/propcache-0.5.2.dist-info/licenses/NOTICE @@ -0,0 +1,13 @@ + Copyright 2016-2021, Andrew Svetlov and aio-libs team + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/propcache-0.5.2.dist-info/top_level.txt b/bioagent-bench/.venv/lib/python3.14/site-packages/propcache-0.5.2.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..8c9accf6226df7e4011a41ac5d6014223685cfed --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/propcache-0.5.2.dist-info/top_level.txt @@ -0,0 +1 @@ +propcache diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/propcache/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/propcache/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..bbfd78a3628d5b0d12cda99195987d4e454d78da --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/propcache/__init__.py @@ -0,0 +1,32 @@ +"""propcache: An accelerated property cache for Python classes.""" + +from typing import TYPE_CHECKING + +_PUBLIC_API = ("cached_property", "under_cached_property") + +__version__ = "0.5.2" +__all__ = () + +# Imports have moved to `propcache.api` in 0.2.0+. +# This module is now a facade for the API. +if TYPE_CHECKING: + from .api import cached_property as cached_property # noqa: F401 + from .api import under_cached_property as under_cached_property # noqa: F401 + + +def _import_facade(attr: str) -> object: + """Import the public API from the `api` module.""" + if attr in _PUBLIC_API: + from . import api # pylint: disable=import-outside-toplevel + + return getattr(api, attr) + raise AttributeError(f"module '{__package__}' has no attribute '{attr}'") + + +def _dir_facade() -> list[str]: + """Include the public API in the module's dir() output.""" + return [*_PUBLIC_API, *globals().keys()] + + +__getattr__ = _import_facade +__dir__ = _dir_facade diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/propcache/_helpers.py b/bioagent-bench/.venv/lib/python3.14/site-packages/propcache/_helpers.py new file mode 100644 index 0000000000000000000000000000000000000000..1e52895c151c952a100eeaab524fea2ef8d68f7e --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/propcache/_helpers.py @@ -0,0 +1,39 @@ +import os +import sys +from typing import TYPE_CHECKING + +__all__ = ("cached_property", "under_cached_property") + + +NO_EXTENSIONS = bool(os.environ.get("PROPCACHE_NO_EXTENSIONS")) # type: bool +if sys.implementation.name != "cpython": + NO_EXTENSIONS = True + + +# isort: off +if TYPE_CHECKING: + from ._helpers_py import cached_property as cached_property_py + from ._helpers_py import under_cached_property as under_cached_property_py + + cached_property = cached_property_py + under_cached_property = under_cached_property_py +elif not NO_EXTENSIONS: # pragma: no branch + try: + from ._helpers_c import cached_property as cached_property_c # type: ignore[attr-defined, unused-ignore] + from ._helpers_c import under_cached_property as under_cached_property_c # type: ignore[attr-defined, unused-ignore] + + cached_property = cached_property_c + under_cached_property = under_cached_property_c + except ImportError: # pragma: no cover + from ._helpers_py import cached_property as cached_property_py + from ._helpers_py import under_cached_property as under_cached_property_py + + cached_property = cached_property_py # type: ignore[assignment, misc] + under_cached_property = under_cached_property_py +else: + from ._helpers_py import cached_property as cached_property_py + from ._helpers_py import under_cached_property as under_cached_property_py + + cached_property = cached_property_py # type: ignore[assignment, misc] + under_cached_property = under_cached_property_py +# isort: on diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/propcache/_helpers_c.pyx b/bioagent-bench/.venv/lib/python3.14/site-packages/propcache/_helpers_c.pyx new file mode 100644 index 0000000000000000000000000000000000000000..9e9e5589b95954129e14083efaa17cdf1995fd4f --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/propcache/_helpers_c.pyx @@ -0,0 +1,103 @@ +# cython: language_level=3, freethreading_compatible=True +from types import GenericAlias + +from cpython.dict cimport PyDict_GetItem +from cpython.object cimport PyObject + + +cdef extern from "Python.h": + # Call a callable Python object callable with exactly + # 1 positional argument arg and no keyword arguments. + # Return the result of the call on success, or raise + # an exception and return NULL on failure. + PyObject* PyObject_CallOneArg( + object callable, object arg + ) except NULL + int PyDict_SetItem( + object dict, object key, PyObject* value + ) except -1 + void Py_DECREF(PyObject*) + + +cdef class under_cached_property: + """Use as a class method decorator. It operates almost exactly like + the Python `@property` decorator, but it puts the result of the + method it decorates into the instance dict after the first call, + effectively replacing the function it decorates with an instance + variable. It is, in Python parlance, a data descriptor. + + """ + + cdef readonly object wrapped + cdef object name + + def __init__(self, object wrapped): + self.wrapped = wrapped + self.name = wrapped.__name__ + + @property + def __doc__(self): + return self.wrapped.__doc__ + + def __get__(self, object inst, owner): + if inst is None: + return self + cdef dict cache = inst._cache + cdef PyObject* val = PyDict_GetItem(cache, self.name) + if val == NULL: + val = PyObject_CallOneArg(self.wrapped, inst) + PyDict_SetItem(cache, self.name, val) + Py_DECREF(val) + return val + + def __set__(self, inst, value): + raise AttributeError("cached property is read-only") + + __class_getitem__ = classmethod(GenericAlias) + + +cdef class cached_property: + """Use as a class method decorator. It operates almost exactly like + the Python `@property` decorator, but it puts the result of the + method it decorates into the instance dict after the first call, + effectively replacing the function it decorates with an instance + variable. It is, in Python parlance, a data descriptor. + + """ + + cdef readonly object func + cdef object name + + def __init__(self, func): + self.func = func + self.name = None + + @property + def __doc__(self): + return self.func.__doc__ + + def __set_name__(self, owner, object name): + if self.name is None: + self.name = name + elif name != self.name: + raise TypeError( + "Cannot assign the same cached_property to two different names " + f"({self.name!r} and {name!r})." + ) + + def __get__(self, inst, owner): + if inst is None: + return self + if self.name is None: + raise TypeError( + "Cannot use cached_property instance" + " without calling __set_name__ on it.") + cdef dict cache = inst.__dict__ + cdef PyObject* val = PyDict_GetItem(cache, self.name) + if val is NULL: + val = PyObject_CallOneArg(self.func, inst) + PyDict_SetItem(cache, self.name, val) + Py_DECREF(val) + return val + + __class_getitem__ = classmethod(GenericAlias) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/propcache/_helpers_py.py b/bioagent-bench/.venv/lib/python3.14/site-packages/propcache/_helpers_py.py new file mode 100644 index 0000000000000000000000000000000000000000..db5b8d6378f040b4ca6619d43e36f4bfc65dacf3 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/propcache/_helpers_py.py @@ -0,0 +1,64 @@ +"""Various helper functions.""" + +from __future__ import annotations + +import sys +from collections.abc import Callable, Mapping +from functools import cached_property +from typing import Any, Generic, Protocol, TypeVar, overload + +__all__ = ("under_cached_property", "cached_property") + + +if sys.version_info >= (3, 11): + from typing import Self +else: + Self = Any + +_T = TypeVar("_T") +# We use Mapping to make it possible to use TypedDict, but this isn't +# technically type safe as we need to assign into the dict. +_Cache = TypeVar("_Cache", bound=Mapping[str, Any]) + + +class _CacheImpl(Protocol[_Cache]): + _cache: _Cache + + +class under_cached_property(Generic[_T]): + """Use as a class method decorator. + + It operates almost exactly like + the Python `@property` decorator, but it puts the result of the + method it decorates into the instance dict after the first call, + effectively replacing the function it decorates with an instance + variable. It is, in Python parlance, a data descriptor. + """ + + def __init__(self, wrapped: Callable[[Any], _T]) -> None: + self.wrapped = wrapped + self.__doc__ = wrapped.__doc__ + self.name = wrapped.__name__ + + @overload + def __get__(self, inst: None, owner: type[object] | None = None) -> Self: ... + + @overload + def __get__( + self, inst: _CacheImpl[Any], owner: type[object] | None = None + ) -> _T: ... + + def __get__( + self, inst: _CacheImpl[Any] | None, owner: type[object] | None = None + ) -> _T | Self: + if inst is None: + return self + try: + return inst._cache[self.name] # type: ignore[no-any-return] + except KeyError: + val = self.wrapped(inst) + inst._cache[self.name] = val + return val + + def __set__(self, inst: _CacheImpl[Any], value: _T) -> None: + raise AttributeError("cached property is read-only") diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/propcache/api.py b/bioagent-bench/.venv/lib/python3.14/site-packages/propcache/api.py new file mode 100644 index 0000000000000000000000000000000000000000..22389e6337f8f77681b61de5e45d1ae6d474d39b --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/propcache/api.py @@ -0,0 +1,8 @@ +"""Public API of the property caching library.""" + +from ._helpers import cached_property, under_cached_property + +__all__ = ( + "cached_property", + "under_cached_property", +) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/propcache/py.typed b/bioagent-bench/.venv/lib/python3.14/site-packages/propcache/py.typed new file mode 100644 index 0000000000000000000000000000000000000000..dcf2c804da5e19d617a03a6c68aa128d1d1f89a0 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/propcache/py.typed @@ -0,0 +1 @@ +# Placeholder diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pyarrow-24.0.0.dist-info/INSTALLER b/bioagent-bench/.venv/lib/python3.14/site-packages/pyarrow-24.0.0.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..5c69047b2eb8235994febeeae1da4a82365a240a --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pyarrow-24.0.0.dist-info/INSTALLER @@ -0,0 +1 @@ +uv \ No newline at end of file diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pyarrow-24.0.0.dist-info/METADATA b/bioagent-bench/.venv/lib/python3.14/site-packages/pyarrow-24.0.0.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..95718587b732075a0de55c67766cf8b5dac5e5b0 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pyarrow-24.0.0.dist-info/METADATA @@ -0,0 +1,78 @@ +Metadata-Version: 2.4 +Name: pyarrow +Version: 24.0.0 +Summary: Python library for Apache Arrow +Maintainer-Email: Apache Arrow Developers +License-Expression: Apache-2.0 +License-File: LICENSE.txt +License-File: NOTICE.txt +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Programming Language :: Python :: Free Threading :: 2 - Beta +Project-URL: Homepage, https://arrow.apache.org/ +Project-URL: Documentation, https://arrow.apache.org/docs/python +Project-URL: Repository, https://github.com/apache/arrow +Project-URL: Issues, https://github.com/apache/arrow/issues +Requires-Python: >=3.10 +Description-Content-Type: text/markdown + + + +## Python library for Apache Arrow + +[![pypi](https://img.shields.io/pypi/v/pyarrow.svg)](https://pypi.org/project/pyarrow/) [![conda-forge](https://anaconda.org/conda-forge/pyarrow/badges/version.svg)](https://anaconda.org/conda-forge/pyarrow) + +This library provides a Python API for functionality provided by the Arrow C++ +libraries, along with tools for Arrow integration and interoperability with +pandas, NumPy, and other software in the Python ecosystem. + +## Installing + +Across platforms, you can install a recent version of pyarrow with the conda +package manager: + +```shell +conda install pyarrow -c conda-forge +``` + +On Linux, macOS, and Windows, you can also install binary wheels from PyPI with +pip: + +```shell +pip install pyarrow +``` + +If you encounter any issues importing the pip wheels on Windows, you may need +to install the latest [Visual C++ Redistributable for Visual Studio][3]. + +## Development + +See [Python Development][2] in the documentation subproject. + +### Building the documentation + +See [documentation build instructions][1] in the documentation subproject. + +[1]: https://github.com/apache/arrow/blob/main/docs/source/developers/documentation.rst +[2]: https://arrow.apache.org/docs/developers/python/index.html +[3]: https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170#latest-microsoft-visual-c-redistributable-version diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pyarrow-24.0.0.dist-info/RECORD b/bioagent-bench/.venv/lib/python3.14/site-packages/pyarrow-24.0.0.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..17e2d2bb64f3d90e6c95ce748f1efde13d7d1eef --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pyarrow-24.0.0.dist-info/RECORD @@ -0,0 +1,756 @@ +pyarrow-24.0.0.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2 +pyarrow-24.0.0.dist-info/METADATA,sha256=wAbMUIddWPyUVBmOoFTueag0ttO3BnFhvFMGszfUZH8,3031 +pyarrow-24.0.0.dist-info/RECORD,, +pyarrow-24.0.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pyarrow-24.0.0.dist-info/WHEEL,sha256=p7EaWUQ1BkIONoxFVCqpuEnBKaq7NyZp9CNLddnWh24,118 +pyarrow-24.0.0.dist-info/licenses/LICENSE.txt,sha256=0cmBNw8f_U7c3S-zVlyJOcxAx14k4fktWB0E5U8N_4E,112295 +pyarrow-24.0.0.dist-info/licenses/NOTICE.txt,sha256=r9iV1hoAEB9tWkHPUYyDhl2BXoEJ6Vb1EqDOTqlzjAY,2916 +pyarrow/__init__.pxd,sha256=Wnar1phFqM_ZHnZmtbuqm6wJHsXlBoYKhV7Qmo2jUHA,2195 +pyarrow/__init__.py,sha256=BO-F4zDrPNoFe19PjpwGiRU2vwbXsmNB05IVPsqoYeI,19173 +pyarrow/__init__.pyi,sha256=LDubKp56HFwJ8nRPFDFgNwGyzsFNE7_hj-WGFRVjIFo,1129 +pyarrow/_acero.cpython-314-x86_64-linux-gnu.so,sha256=dojl37ca64gamJgWafg0w4-Hyzu8G_XEabH61En4-ko,287240 +pyarrow/_acero.pxd,sha256=5ish_GgGWvit4ebhzoZil7b-m0r2RuG5JwYoxsH34FI,1440 +pyarrow/_acero.pyx,sha256=7Qo0ePiMF2tsytAj4t334f-32KA9XdJKU6LKye87usw,21586 +pyarrow/_azurefs.cpython-314-x86_64-linux-gnu.so,sha256=3Wf_fkEKtnedjV7rlML4SKeRUvO1WyGoylJMfPmNqJE,135208 +pyarrow/_azurefs.pyx,sha256=xriY2OxSESfym3K_dHqybC6AZovrsua6yxdjEXz2Ek8,8688 +pyarrow/_compute.cpython-314-x86_64-linux-gnu.so,sha256=aSG24NQEZVcthD0XLn6xR6A5t-VHqkYk2oDKBrzhaN8,1301576 +pyarrow/_compute.pxd,sha256=fhi87jkN6x4qG8JbAtRksx-4YTn3sFIw2zZxuK1flpM,2022 +pyarrow/_compute.pyx,sha256=kUPfZCZqvIH8dMAPC92ryK2fKwHgCkmBnlZI6q6eO-U,114188 +pyarrow/_compute_docstrings.py,sha256=vfc6YlsHjqItBMIvhgsvKjmL18Uk6DpqTvHAw2dE-gA,7989 +pyarrow/_csv.cpython-314-x86_64-linux-gnu.so,sha256=3VBUs2LHylI-d8qufNs9d6LYDje2F-5ScY0-WbD06XE,392856 +pyarrow/_csv.pxd,sha256=1Zk3Zpvvhy-Tb7c79Aqd4e7bBM21kc1JxWJkl02Y4DE,1638 +pyarrow/_csv.pyx,sha256=SFrT-LYoMDjD-tHxThwu0IKzoF41B_KfwPgJ8EoVgZQ,57783 +pyarrow/_cuda.pxd,sha256=VzhM6j9dpNgrABlvFJKoMpRC0As55im-M3tgPTOuwEk,1922 +pyarrow/_cuda.pyx,sha256=XPM0q9Cy-I0bcB5j9dpSkLfkxYvdfLv2UTJym9YAM8Q,35328 +pyarrow/_dataset.cpython-314-x86_64-linux-gnu.so,sha256=mxXZBj7OR2jw6QDUvNDngtknZtAph03Xat2rgLFrup4,997440 +pyarrow/_dataset.pxd,sha256=Ag9rUhoBySU6ba3wFLeuZyWMJnz9VkAf9TQEzWG4hUU,4944 +pyarrow/_dataset.pyx,sha256=tW56kG-2kD3jTTxwXjDGwfJ9wxpPhL7fWtsxnMBzueA,164256 +pyarrow/_dataset_orc.cpython-314-x86_64-linux-gnu.so,sha256=cp5UKw7NhYv0hzgYGUdtnwVXCESMypVHx2v_9V2JYes,79696 +pyarrow/_dataset_orc.pyx,sha256=JSFoRI0pfHtL2jeIuPg5TJHodcfuCNYmj_iEZ4xY87w,1499 +pyarrow/_dataset_parquet.cpython-314-x86_64-linux-gnu.so,sha256=RefMPZP46p8eWaolLfS1zi3KCWfzKgwZ1E9hMlhdSFM,360416 +pyarrow/_dataset_parquet.pxd,sha256=y-3iKehyB_eB_oeqjtt4aQRbUpVGVN1oUMFGIY13brE,1572 +pyarrow/_dataset_parquet.pyx,sha256=wdcEST6sLbbfKTrqjJpZEXpSZK0RA0h9sxkgsscNuHo,41608 +pyarrow/_dataset_parquet_encryption.cpython-314-x86_64-linux-gnu.so,sha256=HC6EqlMq02wOBRkO0S-VeibnPCSziYR-WYkSa0xCYPU,120136 +pyarrow/_dataset_parquet_encryption.pyx,sha256=p7LDNUsp3jMVcWDcbOFp8a3CYJjASVPI_tfATpY-ePg,7229 +pyarrow/_dlpack.pxi,sha256=clw0FkGoyZMEtUU8zPpO_DMtl2X-27kb2UtyhQuIc1s,1832 +pyarrow/_feather.cpython-314-x86_64-linux-gnu.so,sha256=uDqj7yldvkmcldKQypNZ8uMDgH7cb_e7KMvhaYpJWcA,112464 +pyarrow/_feather.pyx,sha256=DWQI4U0uAWE1ZYUwPreBPJg1TGLEGmF3wPEIRL-PhPw,3773 +pyarrow/_flight.cpython-314-x86_64-linux-gnu.so,sha256=GnD-XzBgfvHzpUh3v_eXsJdHSE5aL_Xn4fAiHyjeUqk,1205536 +pyarrow/_flight.pyx,sha256=DjaT1iEGJyX543tYv5MpgZ4rEGUhkAhQ9-exrgbjcnQ,114770 +pyarrow/_fs.cpython-314-x86_64-linux-gnu.so,sha256=LqsXTtu_p08-16kE7s58fIHdnNO_Al328n5dMbgxvYU,437400 +pyarrow/_fs.pxd,sha256=SmHS31eyYU7VUZlVuP613HKgpd7bENnQGApvX_g2Lfw,2439 +pyarrow/_fs.pyx,sha256=kLqIoSXeovlPrWe8EShzzPC3z3EgSCaeq8cSNH_ZWm4,53271 +pyarrow/_gcsfs.cpython-314-x86_64-linux-gnu.so,sha256=2XZBQxWOxWs1u725aKpb4djT15gBTTgY3vS6fJ2J9Ao,133680 +pyarrow/_gcsfs.pyx,sha256=sooI3ztzEQXVVKcahgin0UGm_6Sgy-uwfcMrACP690w,9046 +pyarrow/_generated_version.py,sha256=QycD3LH4XzEXUz-K5sbr9xQ_0ba3yrUCwBh9Tn7yT-g,522 +pyarrow/_hdfs.cpython-314-x86_64-linux-gnu.so,sha256=J_zsQhD-X3IfPmmWS0tRYBBx4QH0p9YTir5xtS7NB2A,138272 +pyarrow/_hdfs.pyx,sha256=QVkMqzz2iGoipaNufk0GPZRzjy1JKJzuuqqk88MdYA4,5802 +pyarrow/_json.cpython-314-x86_64-linux-gnu.so,sha256=y5aDMSB1QDTMcw3IVemZWfYueDU2-9nlOcJTgF020Zw,142656 +pyarrow/_json.pxd,sha256=tECTP14M12-b_ja5QI3snQbd0uWPWmmC9FwkWq23Vg0,1206 +pyarrow/_json.pyx,sha256=SjzdCrfUK_KfpvWbO_oqbQm980IWoWcQpxyQVYSWYIY,13193 +pyarrow/_orc.cpython-314-x86_64-linux-gnu.so,sha256=uXI7YqHnvL04ODdUQSvLGospfSrTaTspgroJx2TnLH8,211632 +pyarrow/_orc.pxd,sha256=6hL0cq1RufqQD-B_bV3ne1rhu2g-h4rDOFNQsSb6qps,5689 +pyarrow/_orc.pyx,sha256=Pn7r4dzagWaqMf8rymbXBIWisxonBaStZgXCi7pfrZI,15556 +pyarrow/_parquet.cpython-314-x86_64-linux-gnu.so,sha256=1G8V80tKmjFNmVwp2a5CqJxmqgX5F3ZbRFzN58cWkTk,649872 +pyarrow/_parquet.pxd,sha256=zJckCTTshauBzBLmR-5PmFOZDXSvAoMnNg_pdryLJfc,4975 +pyarrow/_parquet.pyx,sha256=FA8f8OeP2T9o-BffRmEz9CY0R6vEF4Ocwn-fVxtpQ0k,82547 +pyarrow/_parquet_encryption.cpython-314-x86_64-linux-gnu.so,sha256=rMrSIH6YqnmAOmko0os5NoiDhuwGs0nK3RmTMi4CJNs,336368 +pyarrow/_parquet_encryption.pxd,sha256=WUPm2Kj50f06y5IYzb9lVF64XREzqIZwqJSWPs-1VSE,2871 +pyarrow/_parquet_encryption.pyx,sha256=zK_xi8K4x4AVzqTn36-VNDKT-2A8X_ukBWN9JwzetGY,27148 +pyarrow/_pyarrow_cpp_tests.cpython-314-x86_64-linux-gnu.so,sha256=vq1KFiZaYDKQ6dwItAe5KHqvsPG4z7UWJKd9Ks1IOrk,87800 +pyarrow/_pyarrow_cpp_tests.pxd,sha256=nPyRmNtFbOUvSXCwegAApQFfh8UI_K9Hq5dN4oPAxdo,1199 +pyarrow/_pyarrow_cpp_tests.pyx,sha256=gLeMzB9RWodZgXEpipX65_0aqWu12SjMld0JZmZVRP0,1753 +pyarrow/_s3fs.cpython-314-x86_64-linux-gnu.so,sha256=qr_e7-uwO-wE4muq-b8C_Hl49DRcRt4si9xjt0FDCWE,213296 +pyarrow/_s3fs.pyx,sha256=I5MF6knp3tYnS0VlWK0enMavJ9r54RcjgHJMoi2t6EQ,20584 +pyarrow/_substrait.cpython-314-x86_64-linux-gnu.so,sha256=oyW6EPejURdpuDveXbIP2fYV1q9ZEN_hIj48SxB-bSQ,203576 +pyarrow/_substrait.pyx,sha256=fZT496mzp1ssSKIzqtmPeGBKeYq5f0vm8cWlWqaC408,15666 +pyarrow/acero.py,sha256=yus6kVAtfYVoSenfPZPHgmeEmnaySb5UCM1lVMkzF1M,15743 +pyarrow/array.pxi,sha256=X5nG1VepvQ5LdokrYjeEXeVrOnbSnPpfoE6qBiNjcMg,161861 +pyarrow/benchmark.pxi,sha256=DYXdu-jMSH7XcTohbc8x8NiKRLtpX9IULfY20ohkffA,869 +pyarrow/benchmark.py,sha256=k9Z3yQyoojpYz4lTA6DkCfqT6fPG3N2fJtsHKjpbYFo,856 +pyarrow/builder.pxi,sha256=9QE4KAiA4JpA7-2JLgX3xo32jRtuWZ3YqC-T9GzUVDc,4634 +pyarrow/cffi.py,sha256=hEcrPH9KeG6NES3ZCpSbOVYhOgDOuBB_2LgMMucgw-8,2396 +pyarrow/compat.pxi,sha256=Sq5c3CKq0uj5aDyOoHHkPEO_VsSpZ90JRaL2rAKHk5I,1920 +pyarrow/compute.py,sha256=EweboAC1SgLDPyrY7zgMV6YThGp2pqhqT-C-w7QxhwQ,24379 +pyarrow/config.pxi,sha256=aOEBK-fPOyRAi2m2_6MmdC7O1UFwWb-Ea8LOxq0t_f8,4504 +pyarrow/conftest.py,sha256=6givkCSjlM8ahXdHwxIkK8-IjrN0n2sjJbiVdD1ifGQ,9910 +pyarrow/csv.py,sha256=S6tm31Bra9HPf9IsYwBLltZBLMvNzypWfeCLySsjmds,974 +pyarrow/cuda.py,sha256=j--8HcBAm5Ib-kbhK4d2M6SVQmDWkr7Mt5fnwU2LzdQ,1087 +pyarrow/dataset.py,sha256=S3g4O9G9IhjWByr7tZ5q8HwMC35InoBmgKsiX2aGCYc,40507 +pyarrow/device.pxi,sha256=kx9WnmI2bAKgt9txyGVQWackkRj9NPZLsE5q5H1RFgU,5563 +pyarrow/error.pxi,sha256=Wj7-NGUfdvlEwAwd8Ta_JqRC8IUOUpm_PmpvizCFvfY,8909 +pyarrow/feather.py,sha256=fOJRqGWBWrVchVSwruTcbNqN_KHDpueQ4xdoaTaEVm8,9943 +pyarrow/flight.py,sha256=HLB04A0SZ35MZJumPIuBu5I2dpetjEc-CGMEdjQeQRQ,2177 +pyarrow/fs.py,sha256=qL7lXu2QSbD3JbOwJEQwf642Y5Nne56tpW5nQ8O9t5A,14972 +pyarrow/gandiva.pyx,sha256=579wuHiaFSj7Q6X9YYwoX9NSjEou-mnkvvq0MeiGUoY,24443 +pyarrow/include/arrow/acero/accumulation_queue.h,sha256=fz0okH6sZqcSpBl2w3vOU9wSa9QlOIAXzikmb3mINPE,5985 +pyarrow/include/arrow/acero/aggregate_node.h,sha256=9fhmBQGUphsqLd7bNmiOJaEg5NJxVyWA5RGs9UaTxR0,2201 +pyarrow/include/arrow/acero/api.h,sha256=fRuKEHbKDYWRCwSHLc7vSD-6mQavyOsztluCR7evFCk,1151 +pyarrow/include/arrow/acero/asof_join_node.h,sha256=Ko6r1wDjxg01FE9-xKkttx7WzCAzf43GxbpvGHgKZp8,1490 +pyarrow/include/arrow/acero/backpressure_handler.h,sha256=mbJYVYeFMQlg40j_k7f0jFXZ8kRjo5lrE2dndC54Z7s,2395 +pyarrow/include/arrow/acero/benchmark_util.h,sha256=T5bNabF1TDAp28S7V_vt_VIDn6l5Be0zOVCHhcTcFf8,1943 +pyarrow/include/arrow/acero/bloom_filter.h,sha256=bFzzAzQrs9ePp2tCPQIuk1Oa9gG_Nyp72M_HM0dhakM,11978 +pyarrow/include/arrow/acero/exec_plan.h,sha256=U0KA3tnNvVb75G0XQFLVbGzXCGdddGyRhW3zMa8oWJc,35909 +pyarrow/include/arrow/acero/hash_join.h,sha256=zjljUjH2AQlI54Vz2_7hvJIAjTgaT0BPC_LVD7xNd2k,3022 +pyarrow/include/arrow/acero/hash_join_dict.h,sha256=_BKJmK3Z_KdJuYHh4KQCuT_1rXlUohrtEgGLtEJ4fgQ,15360 +pyarrow/include/arrow/acero/hash_join_node.h,sha256=FXT-aeXL7nNTuV75f9oXgdGyqMK_72GnqGUm9cmBnko,4378 +pyarrow/include/arrow/acero/map_node.h,sha256=Bd1HcW0N5azoIVth2ATeHxgTKd9XmmEkz42YBNw5eK0,2628 +pyarrow/include/arrow/acero/options.h,sha256=sLrKayGLs0MVJKcopQrO3pAOrqmvd1j3EfloUCMKECI,37654 +pyarrow/include/arrow/acero/order_by_impl.h,sha256=dQqplP-AZWPZRKio8LmTjYWlCYz9VmW-usUrtaLpd_w,1691 +pyarrow/include/arrow/acero/partition_util.h,sha256=xS8hayuTRimyrOHzf1xRm1IVjkyIeVCGXY78B2rJaJc,7437 +pyarrow/include/arrow/acero/query_context.h,sha256=D364aGRS3uWe8lgYqCNRjVvs5sKetLOOXzACdp5GZeg,6212 +pyarrow/include/arrow/acero/schema_util.h,sha256=KA_hV2xy2TRccMyksSzQrdH9_rdGo3tQyHOIvrWWYBQ,7961 +pyarrow/include/arrow/acero/task_util.h,sha256=6pqILuYfcVwt9HqVhRfXFVJoOC-Q_dtk8mQ5SxjgwbY,3706 +pyarrow/include/arrow/acero/test_nodes.h,sha256=xKeLWZZC8iokveVXPjseO1MOvWMcby-0xiMISy0qw8E,2877 +pyarrow/include/arrow/acero/time_series_util.h,sha256=W9yzoaTGkB2jtYm8w2CYknSw1EjMbsdTfmEuuL2zMtk,1210 +pyarrow/include/arrow/acero/tpch_node.h,sha256=l3zocxHTfGmXTjywJxwoXCIk9tjzURgWdYKSgSk8DAQ,2671 +pyarrow/include/arrow/acero/type_fwd.h,sha256=4zLhtLJf_7MSXgrhQIZVGeLxjT7JrEDAn9yW75DTFlc,1103 +pyarrow/include/arrow/acero/util.h,sha256=byhMEj5XoAUy-93AjLrx_p9_iUZdYn5uJ_cDkCJQt5Q,6121 +pyarrow/include/arrow/acero/visibility.h,sha256=E-4G2O4F2YabXnFNJYnsI2VbVoKBtO7AXqh_SPuJi6k,1616 +pyarrow/include/arrow/adapters/orc/adapter.h,sha256=G5SSGGYMSREILC43kqL5fqo94c4tKgukitO15m217tY,11031 +pyarrow/include/arrow/adapters/orc/options.h,sha256=FMxda5YSskRrB6h9FvcAuMxl5qdavWrNYHPlanjtk48,3696 +pyarrow/include/arrow/adapters/tensorflow/convert.h,sha256=ZGFAodnwTJK0ZoXfgYJdjgi_F4vfEhI9E87zejxVb6E,3465 +pyarrow/include/arrow/api.h,sha256=Gs6HiRBYU5N7-a79hjTl9WMSda551XdUKpWthFY2v1s,2491 +pyarrow/include/arrow/array.h,sha256=P5oW6hvD2j97bLaSTE4_UHuV6Y38DTwJVww3Eb3xdTQ,1981 +pyarrow/include/arrow/array/array_base.h,sha256=kq08hubpDlrIVgDRhL4MEqPyaEftgmoznQ91ZVrLnAQ,12371 +pyarrow/include/arrow/array/array_binary.h,sha256=JvtB8DoR0_tqfSFS_9nMRrJ39lt1cTm5yXh-DLkhqjU,11247 +pyarrow/include/arrow/array/array_decimal.h,sha256=xRfrZ1IFO09EmkHEolCwrJ4lsXjLo5DXdfH5_v2gSyw,3105 +pyarrow/include/arrow/array/array_dict.h,sha256=6AMbSnZoMj-nhQhZhG4RNnxy9VVPk2DvZjVblwIUhgY,7611 +pyarrow/include/arrow/array/array_nested.h,sha256=MRFllX_LO6AQXNkyAujEdax2gEYrnQALySwB9tuP85U,37717 +pyarrow/include/arrow/array/array_primitive.h,sha256=_U8_pTHg-aHY5jaSFjHZ4hqRPXdYiyKTpr2y3OcJcdg,8184 +pyarrow/include/arrow/array/array_run_end.h,sha256=4zs3tcUrIgDOhSEOywJ1vGY2lsH-5QuEBn87mxnDbi8,5101 +pyarrow/include/arrow/array/builder_adaptive.h,sha256=92DpiIZDXSI_yOrMftj7P60zlCLjNmwfGM5ubdbXWM4,6861 +pyarrow/include/arrow/array/builder_base.h,sha256=CP9kS8pDFd4XyJQdgIlBp3pTIX9mND1Lvh85re4IC8w,13723 +pyarrow/include/arrow/array/builder_binary.h,sha256=cfrm-LnXOZ73pQpn_kXSXw0Bh252DpsfP4KGHlPNy3g,33744 +pyarrow/include/arrow/array/builder_decimal.h,sha256=DFxyFlpzWRZS9zdBhsjII5fFUOMY9bXHn3EIrIvmOMo,5051 +pyarrow/include/arrow/array/builder_dict.h,sha256=FZjvCRIDmVuwmzx_HCcDK6ZjNoZKCEsSV-fGI0K974Y,27899 +pyarrow/include/arrow/array/builder_nested.h,sha256=_VB9EUamHQcxQtJM0FiSy8Rs4vhzpOuqFMRfKa4qE5U,31231 +pyarrow/include/arrow/array/builder_primitive.h,sha256=WX_3Y-FeCvTtoE2kHJ9L2cew_IN5W4uyQFfjsJ2lMLk,26031 +pyarrow/include/arrow/array/builder_run_end.h,sha256=SZIdsUKK1qAc9pdonPGf0A_aikZHcxxzicezRGR5hLs,11416 +pyarrow/include/arrow/array/builder_time.h,sha256=eLhc7UP9ttNJDbTeDdsf-ihMFORksmh7A0ScA5hLROI,2503 +pyarrow/include/arrow/array/builder_union.h,sha256=8BF532sAMc7JxWIbSN-yX6Z9fqY9jmmsIa054DPvbWE,10144 +pyarrow/include/arrow/array/concatenate.h,sha256=wBy-CBTz9MeRCmcnfXGvkXnvSRApvPOcfCf64A42ys8,2059 +pyarrow/include/arrow/array/data.h,sha256=T6U-5zS8NTCAz0HTh8SnM_4G8LtNg21SvvtroXoeb2E,28974 +pyarrow/include/arrow/array/diff.h,sha256=bYNKy2oLAxtt6VYDWvCfq2bnJTVNjG5KMTsGl-gT_kM,3344 +pyarrow/include/arrow/array/statistics.h,sha256=f0_4nJyk9Su-7u4B-HR6lfyKLe7e5E9J1HNqhgnRU28,6914 +pyarrow/include/arrow/array/util.h,sha256=qVHvCaVlALz8WJwAjyMwsBm5J2iN89CSgj7NpmmqlkI,3652 +pyarrow/include/arrow/array/validate.h,sha256=JdDb3XJg4TmAfpv_zgu2ITfL2H9no10TQit-HPj9Myw,1710 +pyarrow/include/arrow/buffer.h,sha256=eyf4nplN8QG9j35gOrVMvhflzADTo7UKY9EiIOeb9Bw,23209 +pyarrow/include/arrow/buffer_builder.h,sha256=qaMgLKbycOSsn5e5_nlOj3tdPHu0i45if78ZrFN2Lmc,17468 +pyarrow/include/arrow/builder.h,sha256=mBxMko271lJ7Xbku0hCixj943Yx-d2i4Q5Hm2WfwiGM,1546 +pyarrow/include/arrow/c/abi.h,sha256=GCjxzmA2jNWQ0ZWVkQ-hnbCbMHKPikO1-JkzYj96cnY,20318 +pyarrow/include/arrow/c/bridge.h,sha256=6e6ZoIsG-pE8FQ5aU4nRJ71vVL3U9k697g7PYKDsVz4,21789 +pyarrow/include/arrow/c/dlpack.h,sha256=IXIuMdfqbHJ_AdT_25tzp0z48mWC65w4xZMjTP3yl7Q,1983 +pyarrow/include/arrow/c/dlpack_abi.h,sha256=mjp9WWq8qv6gkGirT4y0o3BL_ZI9VyHQpJ5aEpPFetI,9920 +pyarrow/include/arrow/c/helpers.h,sha256=f0Q519PwoliFHpxsHp-QvbP6fpVMN2Ha35Tk-RBK6Ws,6279 +pyarrow/include/arrow/chunk_resolver.h,sha256=atGWubPnp8tCyMwpaNiz6tB7qOn21GGZHLB6w2FV4kw,12827 +pyarrow/include/arrow/chunked_array.h,sha256=z6LA9OB3uhtmn7ZZe5wfi3Am3icVQ-L_e8s3KEMuq18,10647 +pyarrow/include/arrow/compare.h,sha256=yp4ganiaR6xXokExmFAauUyfSPwOM8DuBmmAtyK0NJg,8059 +pyarrow/include/arrow/compute/api.h,sha256=6z6HdNH6S0Amop2GUfrsifxOvgupfTqFDZf76YREt-4,2139 +pyarrow/include/arrow/compute/api_aggregate.h,sha256=GCPpIA6qz6XL2UI4SyxXjyjPeC68OoT1tttpiue8eMY,21945 +pyarrow/include/arrow/compute/api_scalar.h,sha256=qR-K4zRkfJmINCF0vTp3rMWf7hdi4PU4haD59wGO9Xg,70099 +pyarrow/include/arrow/compute/api_vector.h,sha256=axkl_2Kz-VqeZkQgAsGCy9spxV62PkgJKhTklQyWWWs,34507 +pyarrow/include/arrow/compute/cast.h,sha256=y8v-EYs-1zJ5NhulMGNzagKc9CkU14ld36VIbYdV94g,4245 +pyarrow/include/arrow/compute/exec.h,sha256=2baXvq1FWwyfQfTV4r6qhF818LEYOxyn_DiTqE5AMCk,17975 +pyarrow/include/arrow/compute/expression.h,sha256=LBwjf1LTtfCfTT3RGUq_3PI_V0LRf2HxtZ-Ovqslw-c,11164 +pyarrow/include/arrow/compute/function.h,sha256=rtEKbrMURIqOWo3JgdOxHsDMk1F-Kk5_Jz_xcpXavxg,16419 +pyarrow/include/arrow/compute/function_options.h,sha256=Q9rjkXPrU9-Xi64_fMLPbBbW_byhjJFsvHppP1CumdA,3088 +pyarrow/include/arrow/compute/initialize.h,sha256=ms8mtCSHTaStTrWdapbNyNSpM0PO4ox32O9colbBR7k,1193 +pyarrow/include/arrow/compute/kernel.h,sha256=d3BIi5NtSBqPsh2gyCsfT03LvRmO2vM_XgoXip_HuKA,32283 +pyarrow/include/arrow/compute/ordering.h,sha256=8Vw3VzDi1mGgVwKGQZakz9TVj0A40wxcL13EvuqNVjU,4129 +pyarrow/include/arrow/compute/registry.h,sha256=x7LHiaNEVvZ0VUssZFsasB52Z1AxRflkdI5tR1hhzqc,4837 +pyarrow/include/arrow/compute/row/grouper.h,sha256=ID9sFjFawulQC5T_f-TQttvohhsSAmNkHIj1iXzDY-0,7520 +pyarrow/include/arrow/compute/type_fwd.h,sha256=tiG7MrLEfXAPF53JKlnFdJ7kzjhNP9a_xCb2Ept3ozc,1555 +pyarrow/include/arrow/compute/util.h,sha256=l1y6P1hHsI-mQ7lB6KNn_8AzYCxCxuhaSBOOG_wqjcs,9376 +pyarrow/include/arrow/compute/visibility.h,sha256=_h6gB6GG5YSF3aak6oAerylqtJvjhTN9Tq8MZccpO0M,1597 +pyarrow/include/arrow/config.h,sha256=z3VlReS2Y2B9lD2fPy13y0rltGZt_dyPdWSaOAu13CM,3964 +pyarrow/include/arrow/csv/api.h,sha256=LbwWhPyIsi_73hvsSr77RNR9uUxrVyXM__hp7QcSom0,907 +pyarrow/include/arrow/csv/chunker.h,sha256=nTs8hdy4D3Nz3oZWm2JMuA02noY_0pWRYWq_RptqzHY,1171 +pyarrow/include/arrow/csv/column_builder.h,sha256=5t3842eXFsAHtrLGJ4LQkH2epUAr06PbnF7POwlgQQw,2889 +pyarrow/include/arrow/csv/column_decoder.h,sha256=10idcPJE2V_TbvgjzPqmFy1dd_qSGWvu9eDkenTuCz0,2358 +pyarrow/include/arrow/csv/converter.h,sha256=RWMxsrsMogphABkyW9gU2Hk2-x1p83EMasrZTvt-Mr8,2964 +pyarrow/include/arrow/csv/invalid_row.h,sha256=gTHjEbjkpee6syLGA8hFY7spx1ROMJmtMcwhXv21x5Q,1889 +pyarrow/include/arrow/csv/options.h,sha256=0ShMt1PduP3K-n20HjxhWRQMORKP_q4rsIRYeWzoDR0,8202 +pyarrow/include/arrow/csv/parser.h,sha256=8PplRh3Qxckk8VPyM70P_f1MBb4WMGnNVpoeJ9kOdHU,8616 +pyarrow/include/arrow/csv/reader.h,sha256=416pt3yNQsgn4RhIyRMsmSJmvv1sw3ouQotubXG91gQ,4606 +pyarrow/include/arrow/csv/test_common.h,sha256=uEYzw8EROvd1QMBQ98d4MaZ7BqMlw2e0flAyz-du0Z4,1972 +pyarrow/include/arrow/csv/type_fwd.h,sha256=ptVbengmY_a7Yz1w0SKmKL16yyw9yEeym0Q0cnRCSV4,984 +pyarrow/include/arrow/csv/writer.h,sha256=Y1zErZ5H1r2QzjAta3TXpFrdl2btoardCF8USCAGtGg,3549 +pyarrow/include/arrow/dataset/api.h,sha256=p7i-bncJLhmfBkfjJWS7684vD9Lke1m6tb7HQq7Tpn4,1322 +pyarrow/include/arrow/dataset/dataset.h,sha256=NsXdSFYrO4BpJSEV2e4yJRrN-hdLxMJepwAyyqFWt-0,20327 +pyarrow/include/arrow/dataset/dataset_writer.h,sha256=TQV75b_UigfGjIpBnPk8teOncM5WroKfKV15oicBRRY,4589 +pyarrow/include/arrow/dataset/discovery.h,sha256=x7-5NBAyEeQWGlWanJDLZAoWksKiMwM96tlDx_M6n5c,11236 +pyarrow/include/arrow/dataset/file_base.h,sha256=nUMWLbhh7lt5c3iOw5edAtcy4jrCzpBUX5JZ29FW0Tk,20385 +pyarrow/include/arrow/dataset/file_csv.h,sha256=7PlvQW_2FJ5RRN-VH4-OBw5cZ6nkd0KE0sj1TQvCZeo,5016 +pyarrow/include/arrow/dataset/file_ipc.h,sha256=6-btvXhflZsAH90T3wMkwzZkte6T4ixzeCEUn_5uYW8,4083 +pyarrow/include/arrow/dataset/file_json.h,sha256=sPjOeMOtbZZbvOivnOdb4MvYKHltpTnY8fONkhB9PZs,3523 +pyarrow/include/arrow/dataset/file_orc.h,sha256=P7nAD9nacVngDEjH8ChQRt0AQmDg4Z1wBx360LDOoSg,2452 +pyarrow/include/arrow/dataset/file_parquet.h,sha256=_mhpCrrLGn9XPKSt9lsdbG4wosPOkMiqkjXDz-2D_So,16935 +pyarrow/include/arrow/dataset/parquet_encryption_config.h,sha256=Upo0k5MijZaMaRZjPp5Xg8TRt1p8Zwh2c2tdimjVe1A,3425 +pyarrow/include/arrow/dataset/partition.h,sha256=3wrNekD_-fPO1YW91Za-T4muCfQeAX7SZRIcsCN_czI,16815 +pyarrow/include/arrow/dataset/plan.h,sha256=IjuR9K2sWD85_2HpVVoJ-3YUCq--UPblHU46exX5qRg,1181 +pyarrow/include/arrow/dataset/projector.h,sha256=KfZijq09Ht0Z2cJHsrjg-sE3SiZ4TKainflReK-39cg,1135 +pyarrow/include/arrow/dataset/scanner.h,sha256=fWeW3NZF5zQYAjSIDU6wJsitzaVrSRbOmrMUnNQE1RA,26176 +pyarrow/include/arrow/dataset/type_fwd.h,sha256=YOUSRwdNAlXJ7meFLolpAFQ_mSlObs2F81zcOy0DoI4,3170 +pyarrow/include/arrow/dataset/visibility.h,sha256=ckmf_sEI0WBo4W7DIgH1QrOq82skOHtoksl9B3yYvzU,1586 +pyarrow/include/arrow/datum.h,sha256=XYaZ_URrAtVqHMq-_2YtXk_ETeQ4yZWLVAnsi-k2Mac,11511 +pyarrow/include/arrow/device.h,sha256=mLz99tb74VdjxXtKt6RZCYKJQ8TYz93uaCFJ1ZiItMw,15344 +pyarrow/include/arrow/device_allocation_type_set.h,sha256=ynoZ-XyFlOAjh01PU-R11mE_EOxuw3xzc94v5OXa0u4,3306 +pyarrow/include/arrow/engine/api.h,sha256=ORM0M5KQeurjEG8Eoa5IeV_ZgKBRPlWyicyv3ORWkAY,886 +pyarrow/include/arrow/engine/substrait/api.h,sha256=W9NB1RAm0ZVxztRXYA-GD7H8XLQNXFoYT7TdGFHoNTE,1079 +pyarrow/include/arrow/engine/substrait/extension_set.h,sha256=z1IIQVd4bat5kRngg91IyoE5UYTv7bar1JQ5ck63YbY,21552 +pyarrow/include/arrow/engine/substrait/extension_types.h,sha256=x5ZIuynNh6WFt3wRjW--zUsuC3SeDLk1qRg9_xhswWM,3075 +pyarrow/include/arrow/engine/substrait/options.h,sha256=dtvUty_zoDmcFwVflppiDzelYkeOhCO74uRF6izQSzk,5820 +pyarrow/include/arrow/engine/substrait/relation.h,sha256=V3VKFlDdE61e1OS8LbJiwvm5w0uq5bzBLhKqmgmKaws,2385 +pyarrow/include/arrow/engine/substrait/serde.h,sha256=mjxfuFo4aPhCiwefpKAJMIlknF4UOHSr6gWU__1SwCc,16528 +pyarrow/include/arrow/engine/substrait/test_plan_builder.h,sha256=REFa79D1AOIIjp2Iez73iw5gEnzG9Rac9t8WwiGLsuI,3003 +pyarrow/include/arrow/engine/substrait/test_util.h,sha256=IHZeYrk50Sx9anJfC25DWP6XesItKEywDWUqvUJcjEQ,1517 +pyarrow/include/arrow/engine/substrait/type_fwd.h,sha256=P9YRjAQpSgoIjDC0siYyxoQzcPVo3r9y85qjiMtudBs,1028 +pyarrow/include/arrow/engine/substrait/util.h,sha256=_dRiQBaIMWNbsYG7kuXhs3dMk4dI63-pM0uSxYPOvgE,3570 +pyarrow/include/arrow/engine/substrait/visibility.h,sha256=GRzH6U-UCPT8d60cywOkFfcanPSgiZKCDP6X2rIpbMs,1740 +pyarrow/include/arrow/extension/bool8.h,sha256=VsHTtVyrqk6UKgvifad7LouuieoAZuZs_uVvegdGq4Q,2145 +pyarrow/include/arrow/extension/fixed_shape_tensor.h,sha256=26c16q0JdTe-bLc1l1sqe0a4AxD5RZR-T_Iblewq_0U,5541 +pyarrow/include/arrow/extension/json.h,sha256=bNh6R9aP3MK-rbTON8IzVn7FIt9RGMmUHGpQkFGfOMw,2026 +pyarrow/include/arrow/extension/opaque.h,sha256=uMVqSScey_13Ho6V86vfkuoByZni9ufh5BGKgX4bTZk,2920 +pyarrow/include/arrow/extension/parquet_variant.h,sha256=7JIr38ncau8XWkqX5XAyfN3ya1eIeVW8vqTPIuLqm04,2889 +pyarrow/include/arrow/extension/uuid.h,sha256=DBzTD83Mh3oacfZzfU6fQuGC87gvtnGGaDue8s9M0Ws,2364 +pyarrow/include/arrow/extension/variable_shape_tensor.h,sha256=vqT2TpNxsgjJUL70-y3EjLjvZvAcI2tId1mzBNuKWGk,4494 +pyarrow/include/arrow/extension_type.h,sha256=YUvUzPz-0QiG-jOjs__TfyPEpsHWGkGO5PMVG1vMjkY,6639 +pyarrow/include/arrow/filesystem/api.h,sha256=Xgy2GOZtBVwDjTaXPDyPPlS9Bwt9gjWXm5I_QbyRbFo,1383 +pyarrow/include/arrow/filesystem/azurefs.h,sha256=JyJ11eH3PBltohpq44zBPODcan3CmHwihHoNhkbdqLg,15588 +pyarrow/include/arrow/filesystem/filesystem.h,sha256=tjRP-ZEWzES6bMfmDQ2MwnYr_3OGV5ZyfbZr3JqWZXM,29604 +pyarrow/include/arrow/filesystem/filesystem_library.h,sha256=f2_p4Odzvq7zrMePwNcWFFet8oolnDI8SF0t55Z6T_Y,1765 +pyarrow/include/arrow/filesystem/gcsfs.h,sha256=5HSHqA9a1T2zVnZO1rNZKnXjLVyq1aAm6DRmQmg_v5w,10372 +pyarrow/include/arrow/filesystem/hdfs.h,sha256=Jn91pjfk6RMx-MuAWsEAKLTyKQ7bDPNA5jMEVzafSgc,4133 +pyarrow/include/arrow/filesystem/localfs.h,sha256=eIhPrpABheQz21WE845ULleTk83e4EtJnES4jALW6mM,4972 +pyarrow/include/arrow/filesystem/mockfs.h,sha256=kohu7s9s9xtd75sGTE2K_rsHW89swDOtSSSFxBixMcc,4768 +pyarrow/include/arrow/filesystem/path_util.h,sha256=hrDVHk4F9M7oGABB4x2wKfQMjSlSAIS0IaLVv2jHrl4,5698 +pyarrow/include/arrow/filesystem/s3_test_util.h,sha256=22B600fr84Gq4rGALdidIP0IzDs9G3aOqkLN4y-6xrI,2962 +pyarrow/include/arrow/filesystem/s3fs.h,sha256=q79pFbxFmsfA30LE78bOh6hj5Iszh01Vtw8LUpIa6T8,18221 +pyarrow/include/arrow/filesystem/test_util.h,sha256=IcdXbz8kXaZDvUPN6Oc77UpWI2Psv1f_CKZxB3UGg-I,11738 +pyarrow/include/arrow/filesystem/type_fwd.h,sha256=zztDER55Wbt4rVnkd-ReeDO-YnrpemftFeFtZ7ZGidY,1462 +pyarrow/include/arrow/flight/api.h,sha256=YotLTQn-KCl6y5BIg8coEFZ9n7PMtJ02ly7Pc5gmX7U,1257 +pyarrow/include/arrow/flight/client.h,sha256=MdHgubO7ExcWgp8S-CP9B4UfJ_aRLgLat0yz0pXQEdg,17936 +pyarrow/include/arrow/flight/client_auth.h,sha256=a3Dkm_jPOuqzNsDA4eejuMUwCEBMavM8uS7w81ihbRY,2216 +pyarrow/include/arrow/flight/client_cookie_middleware.h,sha256=5zkCP2SxMFQuTX8N9NHxOve5J_ef2rFO6-xY4Tfnygk,1204 +pyarrow/include/arrow/flight/client_middleware.h,sha256=aAZwCahuiBhP85iMPe7xNWvidBR9KeHGto2YAqJioI4,2948 +pyarrow/include/arrow/flight/client_tracing_middleware.h,sha256=d0sTmUOfq5M9FMliIKK-flJkR6-7r69NjU2TpxhfqWo,1217 +pyarrow/include/arrow/flight/middleware.h,sha256=JPQd8JnIVcwjTH6yOBck4BWR-WV95fpnAdhHyEYvfKE,2254 +pyarrow/include/arrow/flight/otel_logging.h,sha256=riS9sZM2C3mH6VMbESizJ6lGmudqdJhfdCY9_cJJqMA,1139 +pyarrow/include/arrow/flight/platform.h,sha256=1ZfzVaollAZosGyH_1JvzEA8iNR0hi9cUGz5eyLT1zc,1209 +pyarrow/include/arrow/flight/server.h,sha256=GAcV0-THuBuj-bXfwqYrZ1P2bwZgKQSJLbu8ToltRvU,13185 +pyarrow/include/arrow/flight/server_auth.h,sha256=xXkot_fmnEZ0yXHEBmiezAQCxM7vQem1Vo1V0vEwcW4,4457 +pyarrow/include/arrow/flight/server_middleware.h,sha256=kRXm15c1mjPduoS7N61m0TG8nGIhN_ielsjK9yFlsgs,3155 +pyarrow/include/arrow/flight/server_tracing_middleware.h,sha256=NzyufqTwzcTZ32FNuY-rwse4KmH8zoyoc1RhZxUMwFs,2186 +pyarrow/include/arrow/flight/test_auth_handlers.h,sha256=XkvMWucv9GQjlt2ttvYxshym4kUubUdMh-timlQIt1I,3315 +pyarrow/include/arrow/flight/test_definitions.h,sha256=U_HhBnNdNZmojraYzICBmBvuef0bTT5eYzFXp8iGFzc,13110 +pyarrow/include/arrow/flight/test_flight_server.h,sha256=SbRhZP0U4ILnbg7lYQvGeXmvPM_B6bai12FTM_HD4RQ,3930 +pyarrow/include/arrow/flight/test_util.h,sha256=1Tv0Yl5lth2j3fIZT7iI8n_kexzGgJHGxplV4xeu7TE,6668 +pyarrow/include/arrow/flight/transport.h,sha256=0FUVFM5zch_NKfBZEJLwyksg63TKyjjLXNQrUqlo9F8,12201 +pyarrow/include/arrow/flight/transport_server.h,sha256=iVdXmrb2pemh4o6BxwvB7OZAV4UeoWrbhe4ePZ5Pi4s,5268 +pyarrow/include/arrow/flight/type_fwd.h,sha256=tQFAM3QNKPdzB4VqUGdEUFjNPYXVZLApwGnSus2GQx8,1797 +pyarrow/include/arrow/flight/types.h,sha256=lZdSGm_DZIevV_vyMD1GW51lgVUJNhrBrCOPrXOBBdA,47502 +pyarrow/include/arrow/flight/types_async.h,sha256=3nIQqwCYO4Ir3Mt2bG7BNntXxuNHYQNNpz-Yl3EaFTQ,2599 +pyarrow/include/arrow/flight/visibility.h,sha256=N1k74cwyRvOaYFa_tCjdgUjiSdPBhmy20UuVGu0wTg0,1596 +pyarrow/include/arrow/io/api.h,sha256=Pn4jZSTsLW8MAlMyXUokmJdupX54u154GYI5AvD5ByA,996 +pyarrow/include/arrow/io/buffered.h,sha256=PHaMwCMaXu3vAjhDpZMC2xt5hKooJXVeMffhoDzTxNc,5912 +pyarrow/include/arrow/io/caching.h,sha256=AAjoyKwQ06m2XiglFS6Ch_cdg2p4-wkA7GakGI_eX1E,6708 +pyarrow/include/arrow/io/compressed.h,sha256=3JxIOo1q8VhjIErfwVM5ZLVkwwQKXd-FT5517j58etA,3774 +pyarrow/include/arrow/io/concurrency.h,sha256=SmIr0OWCgMUR3j9ngVbjMJhWOUrU15jQf_jz2rUw7r4,7934 +pyarrow/include/arrow/io/file.h,sha256=-ZEklW1Q0sj3pYCQLQ1ebirKd3s2GI3vUEIszFr8mVU,7625 +pyarrow/include/arrow/io/hdfs.h,sha256=2s3f49ggAYgSCsX5SoqnomwsXd24_IZhW-VSBJclqTg,8559 +pyarrow/include/arrow/io/interfaces.h,sha256=QIBHTJUobEkwcqnKMT_GEKu5ArzpeGmK-8v7z4qGHIQ,13428 +pyarrow/include/arrow/io/memory.h,sha256=SY535DEFEOIXojttrsGmMtK5PClwGx5sHK8E_TlCGCQ,6321 +pyarrow/include/arrow/io/mman.h,sha256=qoLBAGFcvpYTy96Ga7FNWDJKT3uhxpFAF3hbXIaDSiY,4111 +pyarrow/include/arrow/io/slow.h,sha256=8-ZjQJq49EQJ4esQ6qHHjlKCeZNg4BSND7ire-ZtLYQ,3942 +pyarrow/include/arrow/io/stdio.h,sha256=dqMTHoJbmiXcyNa2fN60tSWQsx0GPphZVCLdGiZNt8I,2095 +pyarrow/include/arrow/io/test_common.h,sha256=SF59odb0E3rtGju5jABsQzZcUi9gl5TwHuKg4f0EQ5Y,2146 +pyarrow/include/arrow/io/transform.h,sha256=W9XWonw69VymQAaQptfW7jD-6ry7VCpfPXlkB7aZzOE,1890 +pyarrow/include/arrow/io/type_fwd.h,sha256=Pi7EFpFvBXsFN1xKOyZjTSP95xNDs6W5hxb5GucoVVE,2315 +pyarrow/include/arrow/ipc/api.h,sha256=sbbb-uYHZtqE59AUlwNnAzUkP41h3Et9Rnu20gNKm9w,972 +pyarrow/include/arrow/ipc/dictionary.h,sha256=UTjZPIG8mLZOk9IW2QnR9RZGr1npexZOp103fv-O70E,6104 +pyarrow/include/arrow/ipc/feather.h,sha256=uCnxwO7eUH18kJ-lWz9IWwSj6AjfejqqLdoifJ-UBDo,4918 +pyarrow/include/arrow/ipc/message.h,sha256=Ed9hyTAAVOGTx6Uuhc48DDvdEQZfcITNSiAtCfOUwsQ,20926 +pyarrow/include/arrow/ipc/options.h,sha256=rAnPbL2wXRg4tZpnC3FNkxWxPdhRR5xUM2tYkc1crZ4,7900 +pyarrow/include/arrow/ipc/reader.h,sha256=NqdrqqAEItO1ecYUINRO7-qhKlYy-CHSJKGI2hdXlRQ,24106 +pyarrow/include/arrow/ipc/test_common.h,sha256=kd6-NmmaT__0cDYIwes8lnOqbQDSqFZn8eg2N-SHfE4,6559 +pyarrow/include/arrow/ipc/type_fwd.h,sha256=Ty8ET7nLI4JJeTqDMyP0pEH9QVj9xs7BpJkZrnrpaPY,1440 +pyarrow/include/arrow/ipc/util.h,sha256=wTkfC9YFKZlAAjyzlmQVZcW90oOj_JatjDN4qz0IxHg,1414 +pyarrow/include/arrow/ipc/writer.h,sha256=hum8E_orkG_X38vgyfyKhGbyvcLJ3AkXEykyBjAXIYg,18870 +pyarrow/include/arrow/json/api.h,sha256=XRW1fP43zVqwy1yabaKctNK9MDZqnxkoHDH1fx5B3Y4,879 +pyarrow/include/arrow/json/chunked_builder.h,sha256=DDuMwrImMECw6Mhfncn2xMOjkFcKUV1O1597_fSFSAs,2365 +pyarrow/include/arrow/json/chunker.h,sha256=dkZOcxsF1Q3ek58P7IoA8f3lQyBQpFvGSFeynNV2Olc,1119 +pyarrow/include/arrow/json/converter.h,sha256=3lXsP3BSdpLPIkFAJnYW9vP8BbX3neVYR_W0zFKClQ0,3134 +pyarrow/include/arrow/json/from_string.h,sha256=y--AK08e-EivbrilSGU6xtPH1VpqBNZIYvCMUT2_V_E,4106 +pyarrow/include/arrow/json/object_parser.h,sha256=Y_6Oceya06aUyeo-1k047dm2-JUMJa2_w9iyZ-goIRQ,1627 +pyarrow/include/arrow/json/object_writer.h,sha256=UrIrjCkIz7Q5n_FpV5NNPD96gHHdTkvTJaekuGBHwTo,1428 +pyarrow/include/arrow/json/options.h,sha256=EypQgDwLZQbrPnAh45nSPfpGGYrxvLgfp1eAG_l0p3Q,2227 +pyarrow/include/arrow/json/parser.h,sha256=3oIzO5kUs2Takc7t_d5mH7bp1uIcc1M-qbuHmPoSI34,3383 +pyarrow/include/arrow/json/rapidjson_defs.h,sha256=lBJlfuYWIeQQ8awPd3bk4jJc81efr_KzKwG8Klw7t1s,1474 +pyarrow/include/arrow/json/reader.h,sha256=KNO9dCyc2RZs7WxUSEW7bpCYBh_h1C3U52YHYxBnP0M,5212 +pyarrow/include/arrow/json/test_common.h,sha256=tEU1c8UN7iSL9nYnlB-QPw1iHgi65L9Eqv8e4iyXc2U,10853 +pyarrow/include/arrow/json/type_fwd.h,sha256=o9aigB5losknJFFei1k25pDVYZgkC2elmRMX1C6aTjo,942 +pyarrow/include/arrow/memory_pool.h,sha256=sKBdu-Aidm_X3qbPnfpur5LX7nuwlsnCraPsGR2Up70,12911 +pyarrow/include/arrow/memory_pool_test.h,sha256=qv7csk6hZiO2ELFF-1yukpppjETDDX0nuBFBbPFHtMU,3350 +pyarrow/include/arrow/pretty_print.h,sha256=Z36O7Rp3wp8gCG51YHh-x8ytrrENDpFZSPmSSXs9TAo,5716 +pyarrow/include/arrow/python/api.h,sha256=2XyYOFAmorPGXrTVYZkiKL67nuH8w1ha6D1reU7K-Vs,1179 +pyarrow/include/arrow/python/arrow_to_pandas.h,sha256=jQiSTN8IL95onwaclaWqCR5Nb2RHrBtubRyEOJR9eRE,6215 +pyarrow/include/arrow/python/async.h,sha256=C0f8YYmgwBGgDau4xEFsdjukiZB4YvpylETHEZryHOo,2352 +pyarrow/include/arrow/python/benchmark.h,sha256=f-kzyMOlPKDse2bcLWhyMrDEMZrG_JHAPpDJgGW0bXU,1192 +pyarrow/include/arrow/python/common.h,sha256=P0VTk_VUwki39_Qa_0CRlUAFKA6meOIXRV78BYuU6mc,14914 +pyarrow/include/arrow/python/config.h,sha256=wAu5-RRtzqKaBwYm7h1iBsItuYN60b3IQMZFzGn0GQU,1298 +pyarrow/include/arrow/python/csv.h,sha256=QxU3B-Hv_RsoEcMGS9-1434ugouL2ygC64Lq6FgviNM,1397 +pyarrow/include/arrow/python/datetime.h,sha256=Bny_THGi2tyUeHxcOuw01O7hNE8B_gave5ABAZQtwTQ,7931 +pyarrow/include/arrow/python/decimal.h,sha256=K9ScmzSa7XCuuwpXKnq0GPbRDTuRU5yXfWr9zGtOchc,6362 +pyarrow/include/arrow/python/extension_type.h,sha256=0gzb42y_mbw4fsYs3u8cwPFLBRlG-kkHQLgbvGtrY0U,3181 +pyarrow/include/arrow/python/filesystem.h,sha256=FG0AcLekqaDf9IQPqKixAfIcY_ZLgIKP5NvvXdtBVUM,5126 +pyarrow/include/arrow/python/flight.h,sha256=t4ZD8gWaubbO_mBDv0XZ58TCo1VQl0hzHn1_7w4U64I,14450 +pyarrow/include/arrow/python/gdb.h,sha256=H-qvM-nU8a_3Z5tk8PvppTwQtBMSZhQKQIVgRAsRfFg,972 +pyarrow/include/arrow/python/helpers.h,sha256=0VW-r2f52cJHjf2v4FLuTSXkHDq9TF79xyU15DZznI0,5534 +pyarrow/include/arrow/python/inference.h,sha256=FUFvB4Zy7V-tueXdmbDcqTeLK4xj5GZEeRW5yhiJlsU,2038 +pyarrow/include/arrow/python/io.h,sha256=4jGnodpSUlnVqAVh9fWId7H4WldlLPkXyroABpdaW6w,3858 +pyarrow/include/arrow/python/ipc.h,sha256=SZbw6jCCqLiLNCY3k632GmwHeD_r_xrDS0dhqV49VhY,2259 +pyarrow/include/arrow/python/iterators.h,sha256=Ugfm3JvetAH0l-oAjjpZfhrUBqRimVMaw4-xusvqLSg,7327 +pyarrow/include/arrow/python/lib.h,sha256=NSqX1jl5ZWssVm0SmNFHp4PckJYy8YCJxlL02MKa4Uc,4562 +pyarrow/include/arrow/python/lib_api.h,sha256=rm6EBrvNbQHNzt_TJGbbdIo5T_1IC2WBAXIQnpMzsNA,20132 +pyarrow/include/arrow/python/numpy_convert.h,sha256=y13eHwfe1lJKzadoTr2-GyX6xPsE6Z7FN31s7PN-2Rk,4870 +pyarrow/include/arrow/python/numpy_init.h,sha256=FniVHP7W2YBlenoMYhQrODvoqqvDMSls2JANGtNPQts,999 +pyarrow/include/arrow/python/numpy_interop.h,sha256=rI6ek8JTOYtjo7gEADSDBS6QuAOHa2A0YQPZ2GeypFw,3418 +pyarrow/include/arrow/python/numpy_to_arrow.h,sha256=z9KapsuoOSpWILPt9bea7GR4BL6AQ28T6DUO0mSkh3k,2760 +pyarrow/include/arrow/python/parquet_encryption.h,sha256=7KjqRuxbO0UxxHikTDvrLH733UVe0uvTf_bQMiI_xCA,5596 +pyarrow/include/arrow/python/platform.h,sha256=AtkGgpUVqPHs9Z8Tm07yb2zN_wOtb2cvlf-5xFBkc4k,1268 +pyarrow/include/arrow/python/pyarrow.h,sha256=TK3BtD9n3QKOQ9dX3LXbQc0hu9alWcufV0O93iQW7B0,2761 +pyarrow/include/arrow/python/pyarrow_api.h,sha256=7l0G4-_m9yALYoifsY8Z6qh3HHD0PgkpVSgCn_JaGU4,867 +pyarrow/include/arrow/python/pyarrow_lib.h,sha256=-70_Ckj3_0ImlzaXSJOE_d3w9pGM66lXiGPyln9c96Y,863 +pyarrow/include/arrow/python/python_test.h,sha256=ea32mM20uHySlygi9MtVxr26O-ydTZHCUQIlxaIMjT4,1195 +pyarrow/include/arrow/python/python_to_arrow.h,sha256=BoVytf6P7PBYXyznchElKZSFvEsFyimB-tLFdw0AUNo,2521 +pyarrow/include/arrow/python/type_traits.h,sha256=U7dW4sajVVmjXALsP-B8ZZwI4GgIUG3t-rbp2fwrHWI,10236 +pyarrow/include/arrow/python/udf.h,sha256=de3R8PhNJO5lT9oCqRxe8e2_SE3jBpHOkwbNqCrlgjQ,3104 +pyarrow/include/arrow/python/util.h,sha256=sP2CEGG7NNqwjrEeNKB-VFqkKxn5P2Tsc0jPUMXQuLg,1732 +pyarrow/include/arrow/python/vendored/pythoncapi_compat.h,sha256=bzMnlHTCfjk5DQRIxwytunYh5aQxU3iSElaaDyNnAY8,40900 +pyarrow/include/arrow/python/visibility.h,sha256=hwJw5sGrWJckQkNaAuLe4Tf-VDjQbXknyzNOVgZI3FI,1381 +pyarrow/include/arrow/record_batch.h,sha256=DZ_GZUGTEBv7uJeNnxPoY1ZnB-m7tfSYcHuNdL6iXVU,18947 +pyarrow/include/arrow/result.h,sha256=J4FgymBSzUGv-rt3_u42H7mkEbG6INj43m7yz-rzV7A,18425 +pyarrow/include/arrow/scalar.h,sha256=rsZGG1akIQJRRTw-_dlXLjkPT6NTONiwv70L_1zPxho,37320 +pyarrow/include/arrow/sparse_tensor.h,sha256=1EetIzwI_JvNITcy3LS9Ra0wFiDWLB0hXjy26VOCg2s,25205 +pyarrow/include/arrow/status.h,sha256=r9B963ikSEuSQpbnuGBV-pl1Dtfi5Co0gFd7hI0AMUE,17392 +pyarrow/include/arrow/stl.h,sha256=2PEb_N_ZOWrwBJnFWah96GuLmEBc5Vl4_IFws9NCF2k,19343 +pyarrow/include/arrow/stl_allocator.h,sha256=TBbvjbuQIH9y88FI2SaqAL7pOIt3wZ1xMKwXqeKNiJE,4956 +pyarrow/include/arrow/stl_iterator.h,sha256=RelNQrADHupKWTuFBCCkqVlyuGHXU3yB6gcsDpQpra8,9953 +pyarrow/include/arrow/table.h,sha256=0RFiXqJcNfcdH9jcDcBiRunPdWcHZn4A0blWdSR-MqE,15300 +pyarrow/include/arrow/table_builder.h,sha256=LRcLCL2iUrj6vF4f9AjPswVjqtqlMw7z_8VBAfUJeCo,3763 +pyarrow/include/arrow/telemetry/logging.h,sha256=T4n5CpX1cJBOzdvmIKfsNVpuaycMdL_31ZlQMV8hdKw,3401 +pyarrow/include/arrow/tensor.h,sha256=t5qdeufYoYG1Tl4DCiDzeZkOhyiQQPOGbMKCLa0u7WM,9089 +pyarrow/include/arrow/tensor/converter.h,sha256=RZq0Try_kiZ085_d_CvhewMsd57InGb2TCeiveaf-Oo,2891 +pyarrow/include/arrow/testing/async_test_util.h,sha256=IrHWfPeIyhrgeTGHUPLt92LdsofmFX6khjngWsZv3dY,2262 +pyarrow/include/arrow/testing/builder.h,sha256=4x0bWOedaVomWU0m7dF99irOv3flR-_p-IMofTDZtwo,8556 +pyarrow/include/arrow/testing/executor_util.h,sha256=38_rF-V_9zF1ttJMspkPiI-34VU1RDjg1ADBS8lUFHk,1885 +pyarrow/include/arrow/testing/extension_type.h,sha256=2xHmtD1bnK-u8Or38keTBvDuXhgKvsVyiiO-exlWHXg,8113 +pyarrow/include/arrow/testing/fixed_width_test_util.h,sha256=g6yB7RkziU7HEhNJnxOhkn2nE5HeXaFX3tbBX3q9_sE,3091 +pyarrow/include/arrow/testing/future_util.h,sha256=qIhi417OGMWSMUSDHjkGTYd-ihZbqw8ZSIRwJ01vbKg,6246 +pyarrow/include/arrow/testing/generator.h,sha256=zwEI_ZS-nxOIoYZUW_X9VE0wg5ILk8PzBpeo6kqXUDM,13525 +pyarrow/include/arrow/testing/gtest_compat.h,sha256=0NqH39my7m1FMpsrQYnxQx4bdEE10SCXZaysN6yjQFA,1311 +pyarrow/include/arrow/testing/gtest_util.h,sha256=2Yb7YlGuooyA5xNsXHQO5iVsxrd0u-vByE-tzfIZFIo,24720 +pyarrow/include/arrow/testing/matchers.h,sha256=Z6jJW4fGlvtpc7oWpPlx5ynLk2tnLwF-dhv5fQIcvqc,16832 +pyarrow/include/arrow/testing/math.h,sha256=YLDNNqYpSHBT1jZo06pYN-TG_RtOJFOJ29YJ0hSP8g0,1424 +pyarrow/include/arrow/testing/process.h,sha256=AzPW3Lh2R4sTm-RUUi4Od3aSba9zoLcS_zHBxztv4zI,1372 +pyarrow/include/arrow/testing/random.h,sha256=d6YdrWY6g-2-bNMK13HUO34Un8xuw9IjU6VeyPQA7hs,39071 +pyarrow/include/arrow/testing/uniform_real.h,sha256=mVaKsFtDPuQbawXatB3wvJtdoaerkRtSjKjqXQr_Aw0,2938 +pyarrow/include/arrow/testing/util.h,sha256=h4_y4IMbpSGOoVmkydoXP4IzLa2JoxZd-Fnqjfk3Qsc,6124 +pyarrow/include/arrow/testing/visibility.h,sha256=-wjc00QIhygXJa7tknbIL685AQ1wnyCPr-EtVzkzmq0,1606 +pyarrow/include/arrow/type.h,sha256=q0xWgRD6rdRiBYjwtk-tP7zQHLigL4WUC8q1kdHOtOE,97566 +pyarrow/include/arrow/type_fwd.h,sha256=uMAHSa-fnssQivd_shqzKC69YWbwrF6bdhqON6JQlYU,23281 +pyarrow/include/arrow/type_traits.h,sha256=nKDVrC4_4EBfyEbHQdVr4mgmAaSvdp6cJE2XPZfUwaA,56056 +pyarrow/include/arrow/util/algorithm.h,sha256=045EVzsC9rThlRVFaCoBmmtWZmFy5y28PR9yapn9sXY,1229 +pyarrow/include/arrow/util/align_util.h,sha256=nLJRf4rKiBo7phCBz52iwq99teLgx5N81LljnBQaqW4,11077 +pyarrow/include/arrow/util/aligned_storage.h,sha256=e0pCTBZ9hn9VAVTnENubbEii_witF73xUeBZNIQd73I,4254 +pyarrow/include/arrow/util/async_generator.h,sha256=0nHf1fKd6baTYt7OvGZanDeUuHrpGw7PUiYZ7NpZNGM,78185 +pyarrow/include/arrow/util/async_generator_fwd.h,sha256=Q8sueYZWwdxkaVDabuglHM3XuP--NerYgq8UxsVHgFY,1728 +pyarrow/include/arrow/util/async_util.h,sha256=1nnAJZ22iK7wSzmvZDo3PMhuWqJIt2qKdlXzTyhoCK4,19759 +pyarrow/include/arrow/util/base64.h,sha256=qzcBE98cg8Tx5iPJAvQ4Pdf2yc6R2r-4yGJS1_DEIeY,1095 +pyarrow/include/arrow/util/basic_decimal.h,sha256=Q2zfksUdYc92TL8po81I_aQOmwHTwZGPjqXvJM7cgek,33569 +pyarrow/include/arrow/util/benchmark_util.h,sha256=SG3gfwE-wGNZAwpL3TvffnSiZGM2cztV5xRBnbqy2Mw,7641 +pyarrow/include/arrow/util/binary_view_util.h,sha256=de4QaIJkyqzmbFmvfjTRnwbUSeCt0lE9dk8saJX9frk,4596 +pyarrow/include/arrow/util/bit_block_counter.h,sha256=88YY9S8rFAZBprmgyhUMMWwec1msILmy5i4mRn98ok8,20106 +pyarrow/include/arrow/util/bit_run_reader.h,sha256=vE98gml0xAW53LeqwhcvA506LlpUiOX2X6j1fysdMTI,17248 +pyarrow/include/arrow/util/bit_util.h,sha256=xqaAvVuO07t3Qdf-jJBIF4CVz18XRTI0VzYBcrIgkBQ,12404 +pyarrow/include/arrow/util/bitmap.h,sha256=Q2XNjZWQTq5RXDNQDIDmlUccMh-lE4J76DTFZyK9DPk,17443 +pyarrow/include/arrow/util/bitmap_builders.h,sha256=1POuk6MD2xSNz_Me26-nE12y2Dm2t0KAuzG9cbnpvwE,1582 +pyarrow/include/arrow/util/bitmap_generate.h,sha256=m6ZsNwx1GhsEktQr63NxXHQkX2B7Nti011XYsPg2xfo,3661 +pyarrow/include/arrow/util/bitmap_ops.h,sha256=fPPD_cvXebYLrAeQOCCYydU1AwF0faN5DwQKV8op-Es,10877 +pyarrow/include/arrow/util/bitmap_reader.h,sha256=-EePBZDJyoF1GDQLHi5a9GK6rPQ-NRZS8iZZUSw6WmE,8392 +pyarrow/include/arrow/util/bitmap_visit.h,sha256=myn8k66VrvZnL6R6VW6IDPTfO68VxjbJ8Up5IuSjFL4,3470 +pyarrow/include/arrow/util/bitmap_writer.h,sha256=JmPa3oUsmKnPJ5WzXzTyX8Qkxw3iN3m-hCaMAeKmISU,9386 +pyarrow/include/arrow/util/byte_size.h,sha256=Pd2c_3a0IeSOUevhPIlXNkDmgoB06g4c9YCsuRwwSKM,3997 +pyarrow/include/arrow/util/cancel.h,sha256=SgAy5S0TBISnCypp4R_OI-jPUQ997qtsLOSdXo8l5bM,3659 +pyarrow/include/arrow/util/checked_cast.h,sha256=SR9Qg8NuLSBJw2w1UfgeGvCfT8k7wrbN7BzADQOZfAU,2076 +pyarrow/include/arrow/util/compare.h,sha256=hNfInJTqu-lF1lHGh00PjBgNGCelWL2bCFSBDTQiHaA,1982 +pyarrow/include/arrow/util/compression.h,sha256=prMIcHQlbjwVP9wBtscNwtxDP6NSCqBJOWyVjefY9Jw,8859 +pyarrow/include/arrow/util/concurrent_map.h,sha256=wMi9WDHfRuJ_aSFgcJPpsVwGJ9vIJ5agaZ3rVUlwGe4,1775 +pyarrow/include/arrow/util/config.h,sha256=K7qIth-eYuiPTaT_Dbd2YbAwMI7zNfDrzlZRBHUZnzA,2085 +pyarrow/include/arrow/util/converter.h,sha256=PILfos6VlnLK6fOFMfLIUhiKl3o1dJo9T4HJXeR7V5E,14637 +pyarrow/include/arrow/util/cpu_info.h,sha256=MqLdJabBZkzDjiScaQ7if9dmoAGvXT2QavGoGkho3lU,3964 +pyarrow/include/arrow/util/crc32.h,sha256=4gN0M-SRnxaGKci2ATPbMWZG2TG3YULXjaTpadV0Udk,1337 +pyarrow/include/arrow/util/debug.h,sha256=CPB_oDOuZ_u89e9wM8bGn88mGvClgfa7UDxDph6v9sY,971 +pyarrow/include/arrow/util/decimal.h,sha256=cEQRH5x5kN_MAsWNOPCLjz57QjW4ErfppMHG4b-0EOA,20291 +pyarrow/include/arrow/util/delimiting.h,sha256=JYe9YcWMeFT_ISuojx_VgVqOYLvZ2TiiR2sNn-WdeBQ,7317 +pyarrow/include/arrow/util/endian.h,sha256=PPai9ig8DtGVouKksjTq7H_anm0BjgoTMaVTkWlMKWY,8236 +pyarrow/include/arrow/util/float16.h,sha256=3RfWoQ0sIPCk2yXniIYw-g7X-EEmaos_IG9z8cnXCmQ,7454 +pyarrow/include/arrow/util/formatting.h,sha256=3D0xE7sTcJR6XEUb12LKSqL-A3bX_Pyp1oVkPScjCwQ,22516 +pyarrow/include/arrow/util/functional.h,sha256=4ljKXSWX3G_lBT2BfLXuG44pzZwVKeaojpLWCniqKyc,5612 +pyarrow/include/arrow/util/future.h,sha256=tsSVDEH2dhXKyvIKl6R9BVBolpPdZXoRRf2-YRbtdxg,32296 +pyarrow/include/arrow/util/hash_util.h,sha256=2oGOxsXConsLB27xX8-81MJxj9_Cv7fAYGs8J1JMvsQ,1918 +pyarrow/include/arrow/util/hashing.h,sha256=7bE6v-kemW2Ag1fVxyGWvR0RKRI4LRpCsCSnyOUPgGQ,34197 +pyarrow/include/arrow/util/int_util.h,sha256=zTOAq57M4pUe469WpnW6I5hNtxe3vGRHlZWhngA1DzM,4859 +pyarrow/include/arrow/util/int_util_overflow.h,sha256=7eLu26N5xY2VYM8aSJOZyfmwqIc3IdMXsshsjmuge74,8218 +pyarrow/include/arrow/util/io_util.h,sha256=C8o-gwiYAEMHsFa5LTbC70kaHYZQxa8Zpy3lw-4okCQ,14098 +pyarrow/include/arrow/util/iterator.h,sha256=FymwfXvx9akIj1hAvLgrFQ01BWJbvAv4xx06TNDc9dc,18345 +pyarrow/include/arrow/util/key_value_metadata.h,sha256=wjU6uQGcSmy-YFqMs6rwLP7E4X-0IFBjPrWZstistzQ,3590 +pyarrow/include/arrow/util/launder.h,sha256=C3rNBRh4reuUp8YuRdGQU95WPc8vl4bAY-z5LXgDiuA,1046 +pyarrow/include/arrow/util/list_util.h,sha256=_OmtsDqe-mnZ_7tVWxB2yHdgCJhpiME_RP3nXHzKbdI,2028 +pyarrow/include/arrow/util/logger.h,sha256=p9i4dNgne36LWpFmNSYBYgTQ4kFSao20dJ40LgRRZKQ,6693 +pyarrow/include/arrow/util/logging.h,sha256=63wlnOug1zSd1ur7caDp0F0z2IG7QjHbUFGChE8f-nA,9139 +pyarrow/include/arrow/util/macros.h,sha256=jPhil2_sZUnrbG0HXzhyZT5FJcMRCBHUI8LA1YYcdH8,9282 +pyarrow/include/arrow/util/math_constants.h,sha256=2sfWoVc8syHz8X26XgBmejzXStl7hmvKiOh9622oUZA,1112 +pyarrow/include/arrow/util/mutex.h,sha256=9LL7o62MKzFo820dVYWKQB38aBZrQAmw8280HpdghkQ,1833 +pyarrow/include/arrow/util/parallel.h,sha256=_43wTVSrtJwOkRt3KJcJF1DyYHwhoWbWPsrAswftP-Q,3817 +pyarrow/include/arrow/util/pcg_random.h,sha256=nbXowfCJFiy4GjVfF9I8VvB6fxkyR5zNB1FKdnFsYTQ,1252 +pyarrow/include/arrow/util/prefetch.h,sha256=vaE4FPdscbtO0cPbzl8F1PzB1NDO18ytYlEmZCHDjHs,1251 +pyarrow/include/arrow/util/queue.h,sha256=X9vRZQX3YL_a2Lzwe-zcNNHguR7FoGYmD-Q0THqsCBM,1017 +pyarrow/include/arrow/util/range.h,sha256=WJ1v4BaQ4iMLuh-fgdGyWqY88rzR6Z9rqDQj5RC84XE,8725 +pyarrow/include/arrow/util/ree_util.h,sha256=NO71naM7twCfCrX52vPmWoNhL90XP4PFEdc6AI9-OCs,22397 +pyarrow/include/arrow/util/regex.h,sha256=Tj92CttOh2HxS0EKQ_9-sxMBAsQrDOUKNP0ngIJFdP8,1742 +pyarrow/include/arrow/util/rows_to_batches.h,sha256=PZNoLeMCfJJdeHVvUny0UHc5AtS0hctUCi7zUztJpeE,7120 +pyarrow/include/arrow/util/secure_string.h,sha256=OHFYcIsjjnbrBXJk_uHSZrGu7V9P7M9ArRtjclMNViM,2473 +pyarrow/include/arrow/util/simd.h,sha256=PpKm-aWpZYIYP0NnyGrQceOO9m3_7JbN4uro0IhIT9w,1679 +pyarrow/include/arrow/util/small_vector.h,sha256=hXkuEMONoYd7vFS_6lLPwP49SVdBpAhSlvMK-1d3PrE,14439 +pyarrow/include/arrow/util/string.h,sha256=UStQlZ3Fkhmwm5IcKDsbUABENDRnjV1cU5dRXnEv8IQ,5296 +pyarrow/include/arrow/util/string_util.h,sha256=0IUIcjSf2zMP8rVoVx5y1satijO7cr0PvOgG32d0lFA,2364 +pyarrow/include/arrow/util/task_group.h,sha256=fI330NoJT8u84AEUA6pSxWrE7UBKn2LaM4DfPFoalqA,4362 +pyarrow/include/arrow/util/test_common.h,sha256=OqhWyOEVmWbzhr2JfiaMn1flC84pU6kHL5vYZV1bhUM,2981 +pyarrow/include/arrow/util/thread_pool.h,sha256=Q2KzKTxcikTEgdNAouFwjwsorFa3wE9IVkKqYd8o_Lw,25530 +pyarrow/include/arrow/util/time.h,sha256=_s6yyeTNkNPiLjtaWceItyD8_yGTx2GMiXj1ICVlzyU,3439 +pyarrow/include/arrow/util/tracing.h,sha256=sVfC_Rj2gwkWKVSKT0l0FOO5c2EGsfYwlkZX4d9ncxA,1286 +pyarrow/include/arrow/util/type_fwd.h,sha256=JeZ1muIiVKxHcAXzBikMCmk_7SxFz_IqmzuT6yY1F-A,1803 +pyarrow/include/arrow/util/type_traits.h,sha256=klLSN7GWceSMPE_BXlbK_TsFsHEBe29kw1gwOgo2Eew,2277 +pyarrow/include/arrow/util/ubsan.h,sha256=dJiINEVEdpLSr5-Z_a7BxoYGvA1-pCfHia_xiF0TfYo,3228 +pyarrow/include/arrow/util/union_util.h,sha256=PSssBiw-v-PDen_q75c6OkNO5PwyIPhGbf9PMJj7P2M,1211 +pyarrow/include/arrow/util/unreachable.h,sha256=O1TG4ozCYT3_xvDpJouKWrlFADIEpIemQ28y4DqIwu4,1070 +pyarrow/include/arrow/util/uri.h,sha256=D24zebazFcrKGt7iGpkcGQ87DuF-2AbjPKVkDlq9Nuk,3886 +pyarrow/include/arrow/util/utf8.h,sha256=flGZ786kHo33Xg_zw0zVA9GAT8jYdPUHTVhIPHGjOj8,2031 +pyarrow/include/arrow/util/value_parsing.h,sha256=p_7p_fUUw3jfUkiVnKADdHb3KCdJ7Xg2JQnc39uQHMI,30654 +pyarrow/include/arrow/util/vector.h,sha256=OKnWECz72otbpw6UZSzZIZHJXCXGb9IsPiFI-l3wODY,5609 +pyarrow/include/arrow/util/visibility.h,sha256=zuHLTwNcR34LcCtbhj1YXzfNL8NxO-3XHY_UgUJgdZY,3238 +pyarrow/include/arrow/util/windows_compatibility.h,sha256=Chme9fWRqYRzfIbLw7V_yeiIWd3F4dFeG6ImHHr4Xqw,1255 +pyarrow/include/arrow/util/windows_fixup.h,sha256=hjoh6zvB8u8OVUQqLtdcrmohMzoAoLy6XJFLxcfFhK0,1435 +pyarrow/include/arrow/vendored/ProducerConsumerQueue.h,sha256=Bz1ks3NDgXXLfT8TMUkE38RpMOSwKRRtwU1e37Y1CUw,6101 +pyarrow/include/arrow/vendored/datetime.h,sha256=tsFbz8LKBFzRzTEOAKZyWRbdFLfnCnZRCK9Tyi1PANs,1103 +pyarrow/include/arrow/vendored/datetime/date.h,sha256=fa_tmkMHwmx9vZHjH5lZVhZFCYEcHi5xmueW3F1tDE4,237808 +pyarrow/include/arrow/vendored/datetime/ios.h,sha256=Qnu0iuy2-ein9KkVoSL1t71_W_VFZkdjDVsOnYTnP38,1641 +pyarrow/include/arrow/vendored/datetime/tz.h,sha256=m5JJv7LE7Vukp8h50r90sCfbOSAD2bMVIVQUUxNZeDQ,85347 +pyarrow/include/arrow/vendored/datetime/tz_private.h,sha256=pDkKXYdzfzQ5uh-jcUhURBLqHo00t0UnlimUdiM53Cs,10706 +pyarrow/include/arrow/vendored/datetime/visibility.h,sha256=VCGKzhQOgL1zwGXKl_7lLULfSy0OsPt8FLWHwA4sOtU,1002 +pyarrow/include/arrow/vendored/double-conversion/bignum-dtoa.h,sha256=imGhcg0RywMsFNMYTqp6rlXw2HZCIAla8SC_n92gCqE,4358 +pyarrow/include/arrow/vendored/double-conversion/bignum.h,sha256=RnQ2CPL8Pt6fVCGh_8VDF11e_GyrrwO0IH0uMnTcsEs,5949 +pyarrow/include/arrow/vendored/double-conversion/cached-powers.h,sha256=jjwfR3bue7mNlE5lbTrFR2KlgjRew2OkmjBa7oQO0Qg,3079 +pyarrow/include/arrow/vendored/double-conversion/diy-fp.h,sha256=J-RgqH27jspT5Ubth9pTA9NAZH6e7n1OVhxModgi8Sc,5088 +pyarrow/include/arrow/vendored/double-conversion/double-conversion.h,sha256=J1Tl5-8aFY0A9SnaA9z5Q90jnMxw55illPIuE-jdD5Q,1804 +pyarrow/include/arrow/vendored/double-conversion/double-to-string.h,sha256=C-tKRi0IuLycXgS6CC1oiFkCroOo_-AO0VOjmfe0tlE,23925 +pyarrow/include/arrow/vendored/double-conversion/fast-dtoa.h,sha256=ZAho25fqeP3t2RM0XgqfhTBXQIIicACLpdyHHMRX3JU,4122 +pyarrow/include/arrow/vendored/double-conversion/fixed-dtoa.h,sha256=HLnpxkHjKldm-FBiDRbADYljJBSYbQGP4Gz-sVbiSJU,2828 +pyarrow/include/arrow/vendored/double-conversion/ieee.h,sha256=CVKA9RXSjv4ZygqDHMiF-H2hUh3QHQvp1GZYC3MAhkE,15281 +pyarrow/include/arrow/vendored/double-conversion/string-to-double.h,sha256=Ul6b-2R0pjUaAWNM3Ki4kH933LqrW6_XfPz4BSiE2v8,10906 +pyarrow/include/arrow/vendored/double-conversion/strtod.h,sha256=6xCRm47vmcghYJug5mhhTVbsZ3m3Y6tQfMehEyVZNx0,3096 +pyarrow/include/arrow/vendored/double-conversion/utils.h,sha256=wFRb5cGABiNoUSCnvKmdv_KIMcBtX1PX89tPFfvgbQI,15614 +pyarrow/include/arrow/vendored/pcg/pcg_extras.hpp,sha256=FEYzq8NFxPfdJyLs4kVtTBLkaD6iO71INz9EJnaxTdc,19784 +pyarrow/include/arrow/vendored/pcg/pcg_random.hpp,sha256=7TaV3nZhcwpf6XxlZ6cod1GaW5gm-iUn67t2fiMPNbA,73501 +pyarrow/include/arrow/vendored/pcg/pcg_uint128.hpp,sha256=DyavmKgq9ob13z--Lee8WKfiMBk1cvKhkZlDxQyQNOA,28750 +pyarrow/include/arrow/vendored/portable-snippets/debug-trap.h,sha256=9KphJ9gRtDT9DXR9iZ7aS23xa2T8tLmLsFEJMg0pLDQ,3081 +pyarrow/include/arrow/vendored/safeint/safe_math.h,sha256=HgcHut9A9VZGoZe6A-669uv1N8ydMJE9a3bJzZiOwFA,78099 +pyarrow/include/arrow/vendored/safeint/safe_math_impl.h,sha256=wzI4AvijLrtEg9aTtqoY1SQNOZ1XxsP5GyuN3juC6K4,61119 +pyarrow/include/arrow/vendored/strptime.h,sha256=q1IZi5CvyUp_PNzbQ4_XLroAV24VEovBEz2TkpwUJ9c,1212 +pyarrow/include/arrow/vendored/xxhash.h,sha256=MUwtyzu7xjkx9mBcS65SaDcCK7tgeqQgj-KYEMxcHWc,844 +pyarrow/include/arrow/vendored/xxhash/xxhash.h,sha256=PDFiHmNL4ul4A0wiSLDpY9JSrDS73RMulr1W9aNEW8s,279426 +pyarrow/include/arrow/visit_array_inline.h,sha256=XuQjuME8XZeJp7W86YuCsuoVVgmG1NulXAA0KJkmmB0,2446 +pyarrow/include/arrow/visit_data_inline.h,sha256=4MkdFVsrjhMyTDNrScQtOYV_nwzqR2ddSS2yYnbyLt0,12460 +pyarrow/include/arrow/visit_scalar_inline.h,sha256=KvNY0j8nE9gs_805LXMV3ATgvxvUqW4UeKpXUxR3rMA,2419 +pyarrow/include/arrow/visit_type_inline.h,sha256=jzNnCn_rcOi_StJ8drGPvvbQ9NebGx9x96KDrl4sPSg,5456 +pyarrow/include/arrow/visitor.h,sha256=NKos98j54uY9tdXzctI_n_nwFRrXNOwanxLDqDZONw4,8690 +pyarrow/include/arrow/visitor_generate.h,sha256=n2YKZW-5hY7ICQSwEUBZIYh2eg9ZoTfD54XRd9OlNDo,3324 +pyarrow/include/parquet/api/io.h,sha256=Ricq0d2R4QXHiGZCbjxZ_0F_QmKq0IrfTidNu5NoXPI,847 +pyarrow/include/parquet/api/reader.h,sha256=vnM5XDPn1TVsDJk4SDgb3ZU2Ta4vdrRzCpDWO90rYHk,1204 +pyarrow/include/parquet/api/schema.h,sha256=KsNJ529pEh7bGUa0rLUCcfanI9rW2uSTirgpvKq0hdc,855 +pyarrow/include/parquet/api/writer.h,sha256=UJZbY8QGVRMtAmozzjoM9TnI4gssqlNFUKCXBw2IfuI,1007 +pyarrow/include/parquet/arrow/reader.h,sha256=zVhAQabJlaz0AydvU6nHZmU9mKGSVBZheOkxiGa8yzk,18095 +pyarrow/include/parquet/arrow/schema.h,sha256=Mi56ul7itNS6NDbMpKOJCufjHVqaSY5_rbsNRNLE560,6204 +pyarrow/include/parquet/arrow/test_util.h,sha256=qCUHfC-7P2D32n2__AV35MbzKoL5KEgNHJQaiElFx6M,18849 +pyarrow/include/parquet/arrow/writer.h,sha256=0edx0fgyze6uxTgpDyacSljKMUay86pRqvIRQ7AZpXk,7005 +pyarrow/include/parquet/benchmark_util.h,sha256=emDpaIbtfUNlK0M2zMP-oTy2S2raQ-LpQULK_G74OtM,1758 +pyarrow/include/parquet/bloom_filter.h,sha256=D3cfbl-VpCltXk0CZWs24wxGn88rSeb0eK0Br35ByHs,16569 +pyarrow/include/parquet/bloom_filter_reader.h,sha256=63kpHYKs5TPrbRamkBLZsDYbD-I9UeVhF-R8d7JHeLg,2892 +pyarrow/include/parquet/bloom_filter_writer.h,sha256=42Ajtm0uOSIcoE1-5Yeh4D8Nj18yw5qtO6i-GwoM9Uo,4341 +pyarrow/include/parquet/column_page.h,sha256=Ms578oyLY5wSgyk1UHDyDZI1aIewN-t3GsEtMpnwsM8,6972 +pyarrow/include/parquet/column_reader.h,sha256=HQPo-owohtevjPItqajvEDWj8OR1u6oW6i9xvjP9Yiw,19255 +pyarrow/include/parquet/column_scanner.h,sha256=HecBvh-z0n_1HJsD-GIdcGHQAvDOHKlLzppB9RBsD9s,8863 +pyarrow/include/parquet/column_writer.h,sha256=Sbrbp01FW4UQiE9SES9WOEGI67HgkLl5Zr4xH0DfFcw,12989 +pyarrow/include/parquet/encoding.h,sha256=e_BF06eatcpBbY_BCzVRQQoD0tI7a5T4EPp39G-E-b4,17711 +pyarrow/include/parquet/encryption/crypto_factory.h,sha256=NutbypTw9971ju95UkWtW-g8TYnR94TWZAnx5-dznPA,7064 +pyarrow/include/parquet/encryption/encryption.h,sha256=uvCsCxm8hQ20hg_M-HBzsp5ov_TiT75xHhJrbNUV51o,16558 +pyarrow/include/parquet/encryption/file_key_material_store.h,sha256=YzAVO3M2H5v5Fz2b_WlmB3GE5wVbMEnFTL3S9XPH6k0,2200 +pyarrow/include/parquet/encryption/file_key_unwrapper.h,sha256=VBuBwYeLqYHq_WGnjL2tfwB6mH3jjFVb-TpPKa6lb50,4717 +pyarrow/include/parquet/encryption/file_key_wrapper.h,sha256=xXlS-yyYhUW20S31REsSj5JxbVbij_td0WlPbgxNaYg,3778 +pyarrow/include/parquet/encryption/file_system_key_material_store.h,sha256=xAccp-7Il2UrnItxLmOE4ywhQKgeapyxVNwshDpan0Y,3695 +pyarrow/include/parquet/encryption/key_encryption_key.h,sha256=6dJNBYA_4Z_dyMph71Q5KI0axe-yzdjTnRgG_zj2OX0,2318 +pyarrow/include/parquet/encryption/key_material.h,sha256=kPTSIuRFYOnH4BCPIB33zG9hp5D2Ba-5kZVlq3rFnRI,6221 +pyarrow/include/parquet/encryption/key_metadata.h,sha256=Pc0nA9LW3Fc9NLMMxz7osbw8si2jSiOVTES-J-9R0y0,4003 +pyarrow/include/parquet/encryption/key_toolkit.h,sha256=xLHCsTK6bcrXd_MQv7GDTrLoZNeE_-WFb192LdYpcH0,4645 +pyarrow/include/parquet/encryption/kms_client.h,sha256=MM870qRC_yc4hkzx7xVPaPm12ErR8sqFgDTHLWHcRX0,3222 +pyarrow/include/parquet/encryption/kms_client_factory.h,sha256=VZ97CMgDQxx5oZWFGprjXsaM1hZ0wNudPmFU1_lniAc,1293 +pyarrow/include/parquet/encryption/local_wrap_kms_client.h,sha256=dW5Fe3lUHP8dmsSPDGMbldGl2GlnER4I9zWhCU6g_78,4037 +pyarrow/include/parquet/encryption/test_encryption_util.h,sha256=rYdlDq1nmZu3gsc7vPcBDVOwdxESyVcnVTDCNMPdmoA,5349 +pyarrow/include/parquet/encryption/test_in_memory_kms.h,sha256=c6GA-eObXQtCtzehYKr4uGMpjtLRUW0C5VIQQk8EynM,3743 +pyarrow/include/parquet/encryption/two_level_cache_with_expiration.h,sha256=2kNvixV36bYjUlUJquqnaaOO-NI7e8O3Vm1x-xDPyDA,4818 +pyarrow/include/parquet/encryption/type_fwd.h,sha256=dL8snyUwNjhTQE2FQ2dXAUjTboEXhH2JOehQovHfixc,955 +pyarrow/include/parquet/exception.h,sha256=TJbx6X_khxV6e61rUfHY0Pr65EoxKTu2xpvOAdh9BMo,6104 +pyarrow/include/parquet/file_reader.h,sha256=Z1RiOP74Y9tMv7kZ7FWX05IR05qzgSOTRKrY71pLOJE,11185 +pyarrow/include/parquet/file_writer.h,sha256=sakjyVxzBMhaQzO3JBtgYdGP7NgoTmiS0JwdaWBr5JY,9940 +pyarrow/include/parquet/geospatial/statistics.h,sha256=77ieKxQcY46FB7jgCZhk03fa2G_J2S9NJ8R0_JU-qGs,8892 +pyarrow/include/parquet/hasher.h,sha256=ry5YmXX9EUgyH82W9c-9uF1fklvslK5U28u6wVbfHtM,5428 +pyarrow/include/parquet/index_location.h,sha256=Gg8R4Fv-3pZ_eMrQhGBA4dYwA3kJHo2vW--Jt3SIcZY,1477 +pyarrow/include/parquet/level_comparison.h,sha256=5z4fUJJPWq9W60l2CsAI7T7E2auGYD7m0fpR5rfLmsw,1306 +pyarrow/include/parquet/level_comparison_inc.h,sha256=r20_6Rv5L7UmFGJ68f-JaZ5hLXb87wvZa80hZNQoF-I,2494 +pyarrow/include/parquet/level_conversion.h,sha256=OsuqK1xiUnEnOLPKwfm9X-pXTaXRMlDIkj3lwGb2ggI,9432 +pyarrow/include/parquet/level_conversion_inc.h,sha256=MoNU-tbJ3OfcyrkJOOYb5NjxDECMc-552JbSQfJYUCE,14218 +pyarrow/include/parquet/metadata.h,sha256=UcuSl7ofRNSBSPN4M72WQKc73Ru4kdopdTTcVTJ4HUY,20168 +pyarrow/include/parquet/page_index.h,sha256=3rj9g0DBzZOi--k3-KKE4z8lPdpf1jAy5W16FKRrBPo,17381 +pyarrow/include/parquet/parquet_version.h,sha256=wrOH78uu1CP4wLLE6qTHvJZNyuoHiHhtW8GREOrSekM,1164 +pyarrow/include/parquet/platform.h,sha256=DUf6vCMAV4eS-dOZzHDE7G_EAGIwDaQ-J3X3bpY8rgk,3939 +pyarrow/include/parquet/printer.h,sha256=hXqzwuj5p1oKY2awZkVOq7qXx-Rsoo6QPH1oTE94AHU,1548 +pyarrow/include/parquet/properties.h,sha256=qbPE_a1yLkUrNlomkPMLhENuDvvIdqZSWGFY9NtgBQs,61974 +pyarrow/include/parquet/schema.h,sha256=CjZh2i9WN5VeoDbLqy7M1AZtopZ43_C9blWG3OT2IfU,18222 +pyarrow/include/parquet/size_statistics.h,sha256=7zDvHVQDnwuru46tRyrEubQBKX58k772tSd9aV0ArpE,4268 +pyarrow/include/parquet/statistics.h,sha256=3LFLy_k0KuIjsYBZWFJJShrkHIEm8Zk_QRDfRKj_9XA,18005 +pyarrow/include/parquet/stream_reader.h,sha256=WoymH1axupulU3yG8XyClLmM5kmkVyPVKEDspqduBBw,8856 +pyarrow/include/parquet/stream_writer.h,sha256=N8JV3vY3y_67S4HuiO2BNkW6lLjjG8ASeJ4qKZ7V138,7807 +pyarrow/include/parquet/test_util.h,sha256=VHF8cjNduIW8P6Kw2sLUJmvbL7tmTG_kWIWOaC7G5OA,33538 +pyarrow/include/parquet/type_fwd.h,sha256=N_LtjEcS9J2DV4ylDiXzdmlKRTlcw1YzkzZu82Mizfs,3128 +pyarrow/include/parquet/types.h,sha256=Ut3TD3SxNL3X6NbSxVFDPQH-aczUWDUfnqgj-hcIqBQ,27445 +pyarrow/include/parquet/visit_type_inline.h,sha256=GNgE2_vYIFRluWedQjM55GXJ6hVp8qdYDBY0KAtuvg4,2321 +pyarrow/include/parquet/windows_compatibility.h,sha256=xIEGHW354URgdIP9A4V303TJL8A1IkCEvp08bMKsHTU,897 +pyarrow/include/parquet/windows_fixup.h,sha256=DpyWCywx8YIqouun6BJcgMrHFMTCBgowWdJ1mnJnQ2s,1052 +pyarrow/include/parquet/xxhasher.h,sha256=efek72ZxiECzupXj5n-sUE1GPe_NIcfY5qtDie6YHZo,2130 +pyarrow/includes/__init__.pxd,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pyarrow/includes/common.pxd,sha256=me4htiESPFmZok1M8eDXk9OS-7p9Om_1pazNowvttuc,4950 +pyarrow/includes/libarrow.pxd,sha256=fP5_tqqVqGtS1my5j9-UxTv3NHNI4oBDPjbBcikTu2A,121041 +pyarrow/includes/libarrow_acero.pxd,sha256=c84RdYfIuFWW_36-1RELJsowfQwXhgUxbdC_xKQyFCI,5298 +pyarrow/includes/libarrow_cuda.pxd,sha256=0fRcHbCZY_gFdwIXIElnpGvTxeA5xVxZH1-vwZh16SM,4942 +pyarrow/includes/libarrow_dataset.pxd,sha256=cqV1LQvfsKtRwePFaWUh8dCFlb6JED8tLGCsrtS_Ml8,17270 +pyarrow/includes/libarrow_dataset_parquet.pxd,sha256=PW8g6CD2yjwgwoJgYQ9Ntg2e2v6Vfu4AYL5kiM4sq0Q,4584 +pyarrow/includes/libarrow_feather.pxd,sha256=MTJUDQbfKP8Ir700Fobl7xcbjX7WcrsUV4mxFXlfwn0,2140 +pyarrow/includes/libarrow_flight.pxd,sha256=QO7RJ5TUVA65gtkJLenjIOEW2eIbPuKLw-nGWD9oHRY,24724 +pyarrow/includes/libarrow_fs.pxd,sha256=tbUP-pPwCW3iErej-zuN7eMr99wyEkpXRfOdBaD8FBw,15525 +pyarrow/includes/libarrow_python.pxd,sha256=f9t4sqSxkI14-FG4IoBQVcqc4wQIsgcUv_e0mGMbrtA,11682 +pyarrow/includes/libarrow_substrait.pxd,sha256=PtgwVFqsCQBzCHzK-VqhQOdTyU9J4SziQ_lnxRwChfs,4061 +pyarrow/includes/libgandiva.pxd,sha256=o28v4Q43dYXQEcwldKO6RYs0tke2U6GZVxQmjq4rAt4,11530 +pyarrow/includes/libparquet.pxd,sha256=cyH4BaQWcE6NoMBK2l6tptE2UtMcMr8vhhCP0LBcHL0,26534 +pyarrow/includes/libparquet_encryption.pxd,sha256=oyvw0ZLyLWrHwMiOt6mbWYyTKb3rN9kmjOMMW1P2EsM,8690 +pyarrow/interchange/__init__.py,sha256=DH0bwbKpdjD1WCW1VinnXEuVLY098uHKkirv7DFc9JM,845 +pyarrow/interchange/buffer.py,sha256=NF_GU1uQ6INqHqCwzY6XQQqRxKDh6znEeDHiRqaEIQ0,3359 +pyarrow/interchange/column.py,sha256=n9f3mlViqUFOa3Kofdwoo52qC-WYVpyF2YilRsl4uaw,19350 +pyarrow/interchange/dataframe.py,sha256=tmSMmBvBAc-ZSUzE8tBNbvQLHuuxLuBkMkK6KYwtS8M,8405 +pyarrow/interchange/from_dataframe.py,sha256=nLwcr3H66Pq0A4EI0KRrfrVnZP9e3h70bsNUVHFeSz0,19072 +pyarrow/io.pxi,sha256=gDtFPrwdBrxT88jpNwddg9mM-KSt_XFv333W2IbF3I4,86228 +pyarrow/ipc.pxi,sha256=NTZxt0qAu-HIjDbbczp91sKUJoJQBz48z-IxcKn9b6w,44752 +pyarrow/ipc.py,sha256=ssicn1x2jRZt2m1nQZ4sB70sa1FB3d8PoqiOnWO-tzg,9603 +pyarrow/json.py,sha256=JMEXB5jNZp-DOYmGBrzs2RtwOCLdKCRa3E5kUfH0Ceg,869 +pyarrow/jvm.py,sha256=_cyen6Bi-cBUlg-OAb_RTG6nj2W57kMrR1HuAWVcEQs,9577 +pyarrow/lib.cpython-314-x86_64-linux-gnu.so,sha256=MZnay7YZ36O100YT32n74z9MLi3mZCJHGe4uOAaJnp0,4462640 +pyarrow/lib.pxd,sha256=HHKBlMGhU4NkQKqPcTCkYPojbExu2jSt8C4YTeCWdck,17838 +pyarrow/lib.pyx,sha256=ho6Qf9pHZ6987dSs3Sj5jJ4r3wtZXYv8GrScGj9_kWY,6342 +pyarrow/libarrow.so.2400,sha256=x2qfaVNUI9wmKDA9bfJVyWboRJjck61jfvJouGsUqqA,55282376 +pyarrow/libarrow_acero.so.2400,sha256=WgsAIE4MQVCfl8LuW2__4Yb6TqC6E__FAL7VrxJ-OOo,1999360 +pyarrow/libarrow_compute.so.2400,sha256=b_y10xPKZ5rjLXMg9tL7dnFoXI7XuK4BV3yeD-hED8k,18950224 +pyarrow/libarrow_dataset.so.2400,sha256=6-e2PXDu9Vmo3YJ64QpzJoRfpxk3A7SsAxcwRJt8U1g,2422808 +pyarrow/libarrow_flight.so.2400,sha256=CZVvJPlstxNwiOSp29CJMJA9QF-qABmdLLjJ-p3TcLo,26884928 +pyarrow/libarrow_python.so,sha256=StkSWlE-2EPGFgP_37IFAbz9VbVcQ7bBOpsJy1Z6nrA,2358696 +pyarrow/libarrow_python.so.2400,sha256=StkSWlE-2EPGFgP_37IFAbz9VbVcQ7bBOpsJy1Z6nrA,2358696 +pyarrow/libarrow_python.so.2400.0.0,sha256=StkSWlE-2EPGFgP_37IFAbz9VbVcQ7bBOpsJy1Z6nrA,2358696 +pyarrow/libarrow_python_flight.so,sha256=TxRRoWVDduCV37ShQAewVlaLQda7rsk5bG-CsOr87Fc,104032 +pyarrow/libarrow_python_flight.so.2400,sha256=TxRRoWVDduCV37ShQAewVlaLQda7rsk5bG-CsOr87Fc,104032 +pyarrow/libarrow_python_flight.so.2400.0.0,sha256=TxRRoWVDduCV37ShQAewVlaLQda7rsk5bG-CsOr87Fc,104032 +pyarrow/libarrow_python_parquet_encryption.so,sha256=UpSM2aFZfn-O9m6z7BY3P_bg1b1CaWAOhv3J83vZLjE,38464 +pyarrow/libarrow_python_parquet_encryption.so.2400,sha256=UpSM2aFZfn-O9m6z7BY3P_bg1b1CaWAOhv3J83vZLjE,38464 +pyarrow/libarrow_python_parquet_encryption.so.2400.0.0,sha256=UpSM2aFZfn-O9m6z7BY3P_bg1b1CaWAOhv3J83vZLjE,38464 +pyarrow/libarrow_substrait.so.2400,sha256=y-xdV4yXTYFANxvmh76RVbeIUQv9v1b81Z1Wf1LNisg,5749920 +pyarrow/libparquet.so.2400,sha256=_hFXocTyhXVopuY7GwF9CohfBmYqVodAiLjgXrlTgyg,12822568 +pyarrow/memory.pxi,sha256=cZSlJ8Brteb9aWa7VzvCrpnw26wRkplF9KtF2nqtH8M,8806 +pyarrow/orc.py,sha256=yDp3vD4gOdOsOBmXKFe24Kq3xO3FDBfIOHvsvzQUkKY,12602 +pyarrow/pandas-shim.pxi,sha256=RM38cacj1TlQ5IN79CprIr4AUbbY5GiWdiLsnx0zn3g,8817 +pyarrow/pandas_compat.py,sha256=y1dLv-lrnpJleFwWnSO5_rBM-Uwky6ZgtKUMYTPhbN8,45916 +pyarrow/parquet/__init__.py,sha256=4W64CbvwvO60tG58nfNtyCwMVCfuPumtu82p-kiGPaE,822 +pyarrow/parquet/core.py,sha256=0a8TEsLVmUoPCFSY0gd4L9J_v776yg4Tk1_tX3XoJQA,97280 +pyarrow/parquet/encryption.py,sha256=-XW7Qcbl-jQhpZsR610uQ8-z9ZVE_NL045Jdnp1TZ9M,1153 +pyarrow/public-api.pxi,sha256=G6tk0gOWMBoQaQGo4lyp0OLSkqEZqMhB0z27MoQR3rU,14064 +pyarrow/py.typed,sha256=9hdXHABrVpkbpjZgUft39kOFL2xSGeG4GEua0Hmelus,785 +pyarrow/scalar.pxi,sha256=u4ILaM4cBunYPD8OtYYOYSXEwX4BGbREyStosi4j4pQ,54291 +pyarrow/src/arrow/python/CMakeLists.txt,sha256=D4Ypror_508aAd_juYkrS9Qu2maeirK4QXzwGEZEj0M,855 +pyarrow/src/arrow/python/api.h,sha256=2XyYOFAmorPGXrTVYZkiKL67nuH8w1ha6D1reU7K-Vs,1179 +pyarrow/src/arrow/python/arrow_to_pandas.cc,sha256=0lQ5wG_eqXa76QfuHdCr7Uex1yDM7nneD5G62ykDCzg,96642 +pyarrow/src/arrow/python/arrow_to_pandas.h,sha256=jQiSTN8IL95onwaclaWqCR5Nb2RHrBtubRyEOJR9eRE,6215 +pyarrow/src/arrow/python/arrow_to_python_internal.h,sha256=nQXPZTL3xa4Sm-a-Gv-8bpFs-qAOZHkqWmA_m-dSLVw,1740 +pyarrow/src/arrow/python/async.h,sha256=C0f8YYmgwBGgDau4xEFsdjukiZB4YvpylETHEZryHOo,2352 +pyarrow/src/arrow/python/benchmark.cc,sha256=z6qYRx4qMuNXPaC8fuPJlQd92aosMN85u1aD50R1-UU,1293 +pyarrow/src/arrow/python/benchmark.h,sha256=f-kzyMOlPKDse2bcLWhyMrDEMZrG_JHAPpDJgGW0bXU,1192 +pyarrow/src/arrow/python/common.cc,sha256=HExYdSkfggXu96XWNXBvK1XukCfPotLVFtk8D9FVaOw,7603 +pyarrow/src/arrow/python/common.h,sha256=P0VTk_VUwki39_Qa_0CRlUAFKA6meOIXRV78BYuU6mc,14914 +pyarrow/src/arrow/python/config.cc,sha256=h5heY5rTH0VSrD5_KDFYknbGSNElktHFCtkqUDvNfZI,1393 +pyarrow/src/arrow/python/config.h,sha256=wAu5-RRtzqKaBwYm7h1iBsItuYN60b3IQMZFzGn0GQU,1298 +pyarrow/src/arrow/python/config_internal.h.cmake,sha256=WCieVYP1P2yLWsdeBKdmDsTlyn_BMDd1gXCu-kpIBYY,890 +pyarrow/src/arrow/python/csv.cc,sha256=ql5AY76AqiFksWsrmzSl551k5s9vS8YcmypM2A9rhw8,1803 +pyarrow/src/arrow/python/csv.h,sha256=QxU3B-Hv_RsoEcMGS9-1434ugouL2ygC64Lq6FgviNM,1397 +pyarrow/src/arrow/python/datetime.cc,sha256=WQoJT6a1ocd3SItRC_q8EA5-4Z9sWNdGFxXen3m0xeU,23013 +pyarrow/src/arrow/python/datetime.h,sha256=Bny_THGi2tyUeHxcOuw01O7hNE8B_gave5ABAZQtwTQ,7931 +pyarrow/src/arrow/python/decimal.cc,sha256=4CNaLDOWZelTllSIPdIKDlIhUy6AgKJxKG0WmzJyq94,9699 +pyarrow/src/arrow/python/decimal.h,sha256=K9ScmzSa7XCuuwpXKnq0GPbRDTuRU5yXfWr9zGtOchc,6362 +pyarrow/src/arrow/python/extension_type.cc,sha256=SOhG7u7MNnJkSe-dWi5-ZsGuzQC-M7PihaYFAr6x6hA,7033 +pyarrow/src/arrow/python/extension_type.h,sha256=0gzb42y_mbw4fsYs3u8cwPFLBRlG-kkHQLgbvGtrY0U,3181 +pyarrow/src/arrow/python/filesystem.cc,sha256=0twavI91TE20Otq5kkVUwnN5sindU_mBWoVAvz1ZMgI,6152 +pyarrow/src/arrow/python/filesystem.h,sha256=FG0AcLekqaDf9IQPqKixAfIcY_ZLgIKP5NvvXdtBVUM,5126 +pyarrow/src/arrow/python/flight.cc,sha256=tL3nFVTjDlmiuqDOSxf7HODdpqIMUSbSsNc1vaveP1w,14192 +pyarrow/src/arrow/python/flight.h,sha256=t4ZD8gWaubbO_mBDv0XZ58TCo1VQl0hzHn1_7w4U64I,14450 +pyarrow/src/arrow/python/gdb.cc,sha256=hkATQV6YlpxkBaLfY9_C24XJ6qYSg2T0DfQX4XfpvFY,22556 +pyarrow/src/arrow/python/gdb.h,sha256=H-qvM-nU8a_3Z5tk8PvppTwQtBMSZhQKQIVgRAsRfFg,972 +pyarrow/src/arrow/python/helpers.cc,sha256=51xLIMHgEd-GGeNk9ZJ_A57_dDARtBAYBFWhDnl6ouc,17468 +pyarrow/src/arrow/python/helpers.h,sha256=0VW-r2f52cJHjf2v4FLuTSXkHDq9TF79xyU15DZznI0,5534 +pyarrow/src/arrow/python/inference.cc,sha256=cuSNCjUJgukZLcHJnP0gWy89f7hcFYcpovoXaC56sY8,26872 +pyarrow/src/arrow/python/inference.h,sha256=FUFvB4Zy7V-tueXdmbDcqTeLK4xj5GZEeRW5yhiJlsU,2038 +pyarrow/src/arrow/python/io.cc,sha256=k2pgFsCFXaI7naqXcxDtZJJp7K06NnEhsDEoMQvbaxc,11948 +pyarrow/src/arrow/python/io.h,sha256=4jGnodpSUlnVqAVh9fWId7H4WldlLPkXyroABpdaW6w,3858 +pyarrow/src/arrow/python/ipc.cc,sha256=3D9iMbOFHlhNXX4432wsfbfjWvDryZWgdA0Ak19V_8Q,4472 +pyarrow/src/arrow/python/ipc.h,sha256=SZbw6jCCqLiLNCY3k632GmwHeD_r_xrDS0dhqV49VhY,2259 +pyarrow/src/arrow/python/iterators.h,sha256=Ugfm3JvetAH0l-oAjjpZfhrUBqRimVMaw4-xusvqLSg,7327 +pyarrow/src/arrow/python/numpy_convert.cc,sha256=166BIW7zVTRMKogxLUuhV4e5jOevmonvRtXDydNujgc,21194 +pyarrow/src/arrow/python/numpy_convert.h,sha256=y13eHwfe1lJKzadoTr2-GyX6xPsE6Z7FN31s7PN-2Rk,4870 +pyarrow/src/arrow/python/numpy_init.cc,sha256=cJKOH946T7VCcB-gVIoGgfbWTrbj3FPkI4TgnsLTf7s,1178 +pyarrow/src/arrow/python/numpy_init.h,sha256=FniVHP7W2YBlenoMYhQrODvoqqvDMSls2JANGtNPQts,999 +pyarrow/src/arrow/python/numpy_internal.h,sha256=1R_tNINnHYMGNTA5k_BMbGWFlA297f3Yj4ufrhm-yiE,5320 +pyarrow/src/arrow/python/numpy_interop.h,sha256=rI6ek8JTOYtjo7gEADSDBS6QuAOHa2A0YQPZ2GeypFw,3418 +pyarrow/src/arrow/python/numpy_to_arrow.cc,sha256=uuQo_rewpuc882cuFy-uMmtRLR1iMT3Ew3JystLBXfc,31961 +pyarrow/src/arrow/python/numpy_to_arrow.h,sha256=z9KapsuoOSpWILPt9bea7GR4BL6AQ28T6DUO0mSkh3k,2760 +pyarrow/src/arrow/python/parquet_encryption.cc,sha256=5ilofycsTj6IP0RGCUZFHucHN53oZAmx32L6el7RQh0,4408 +pyarrow/src/arrow/python/parquet_encryption.h,sha256=7KjqRuxbO0UxxHikTDvrLH733UVe0uvTf_bQMiI_xCA,5596 +pyarrow/src/arrow/python/platform.h,sha256=AtkGgpUVqPHs9Z8Tm07yb2zN_wOtb2cvlf-5xFBkc4k,1268 +pyarrow/src/arrow/python/pyarrow.cc,sha256=vukhk0aEmqI2QZ54XMwyC3930mJHGUVSSW49GSgifWw,3683 +pyarrow/src/arrow/python/pyarrow.h,sha256=TK3BtD9n3QKOQ9dX3LXbQc0hu9alWcufV0O93iQW7B0,2761 +pyarrow/src/arrow/python/pyarrow_api.h,sha256=7l0G4-_m9yALYoifsY8Z6qh3HHD0PgkpVSgCn_JaGU4,867 +pyarrow/src/arrow/python/pyarrow_lib.h,sha256=-70_Ckj3_0ImlzaXSJOE_d3w9pGM66lXiGPyln9c96Y,863 +pyarrow/src/arrow/python/python_test.cc,sha256=7WZnh-qh0SvzAin20DHAAd_vdcj2EZQYCNhgqyECdN0,33047 +pyarrow/src/arrow/python/python_test.h,sha256=ea32mM20uHySlygi9MtVxr26O-ydTZHCUQIlxaIMjT4,1195 +pyarrow/src/arrow/python/python_to_arrow.cc,sha256=rUG9uisDN7ey4Tkpwaicq8_oeztYPnD0GielYBqOHJw,49135 +pyarrow/src/arrow/python/python_to_arrow.h,sha256=BoVytf6P7PBYXyznchElKZSFvEsFyimB-tLFdw0AUNo,2521 +pyarrow/src/arrow/python/type_traits.h,sha256=U7dW4sajVVmjXALsP-B8ZZwI4GgIUG3t-rbp2fwrHWI,10236 +pyarrow/src/arrow/python/udf.cc,sha256=tRd7cPh66N5tU4DdO70F8x1FqODcGZ3HtrYjF4BSs9w,29820 +pyarrow/src/arrow/python/udf.h,sha256=de3R8PhNJO5lT9oCqRxe8e2_SE3jBpHOkwbNqCrlgjQ,3104 +pyarrow/src/arrow/python/util.cc,sha256=Tkgz17XmYFzVybvQ9Vo9LChVo98R5BlbjqCNMHk86DM,1852 +pyarrow/src/arrow/python/util.h,sha256=sP2CEGG7NNqwjrEeNKB-VFqkKxn5P2Tsc0jPUMXQuLg,1732 +pyarrow/src/arrow/python/vendored/CMakeLists.txt,sha256=02XvDJAdKiajCEBOmnMKBpmzbRU7FPkNdlNXtw0-A24,837 +pyarrow/src/arrow/python/vendored/pythoncapi_compat.h,sha256=bzMnlHTCfjk5DQRIxwytunYh5aQxU3iSElaaDyNnAY8,40900 +pyarrow/src/arrow/python/visibility.h,sha256=hwJw5sGrWJckQkNaAuLe4Tf-VDjQbXknyzNOVgZI3FI,1381 +pyarrow/substrait.py,sha256=yOMgCb-A5XW9t2DKgutRydDHpFvQkiNNq6ZIu0hIUB8,1230 +pyarrow/table.pxi,sha256=N7AsR-cVIifJtJj2xkE7Jy0GvfcBkOGfeaIqpYv4nCQ,206478 +pyarrow/tensor.pxi,sha256=HOsUF-VazhZ2avZEa9705QM1l1uaLNOxg53NBNTT3L8,47895 +pyarrow/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pyarrow/tests/arrow_16597.py,sha256=DNb41h9E3ITGvAJJu86i5SfsKrwstQJ0E5gT_bpTS_k,1354 +pyarrow/tests/arrow_39313.py,sha256=0pyBixoX38fldTPO1Vwshi_H0XBACrz8esYoL4o71KI,1431 +pyarrow/tests/arrow_7980.py,sha256=tZKb_tRLfxHaosDk9Yu2GLEsJjMaruXD5CKhbK_6Hq8,1094 +pyarrow/tests/bound_function_visit_strings.pyx,sha256=vDEFoNYR8BWNkCntKDuBUT8sXNRBex_5G2bFKogr1Bs,2026 +pyarrow/tests/conftest.py,sha256=jmJZmgU4VIogZBTuS7vGvmPdDp9Bh0lkIvJLW44K3Do,10041 +pyarrow/tests/data/feather/v0.17.0.version.2-compression.lz4.feather,sha256=qzcc7Bo4OWBXYsyyKdDJwdTRstMqB1Zz0GiGYtndBnE,594 +pyarrow/tests/data/orc/README.md,sha256=_4X5XszZqQtWAVEz5N1Va4VyyayGQgNDKrcdMX2Ib4s,932 +pyarrow/tests/data/orc/TestOrcFile.emptyFile.jsn.gz,sha256=xLjAXd-3scx3DCyeAsmxTO3dv1cj9KRvYopKe5rQNiI,50 +pyarrow/tests/data/orc/TestOrcFile.emptyFile.orc,sha256=zj0579dQBXhF7JuB-ZphkmQ81ybLo6Ca4zPV4HXoImY,523 +pyarrow/tests/data/orc/TestOrcFile.test1.jsn.gz,sha256=kLxmwMVHtfzpHqBztFjfY_PTCloaXpfHq9DDDszb8Wk,323 +pyarrow/tests/data/orc/TestOrcFile.test1.orc,sha256=A4JxgMCffTkz9-XT1QT1tg2TlYZRRz1g7iIMmqzovqA,1711 +pyarrow/tests/data/orc/TestOrcFile.testDate1900.jsn.gz,sha256=oWf7eBR3ZtOA91OTvdeQJYos1an56msGsJwhGOan3lo,182453 +pyarrow/tests/data/orc/TestOrcFile.testDate1900.orc,sha256=nYsVYhUGGOL80gHj37si_vX0dh8QhIMSeU4sHjNideM,30941 +pyarrow/tests/data/orc/decimal.jsn.gz,sha256=kTEyYdPDAASFUX8Niyry5mRDF-Y-LsrhSAjbu453mvA,19313 +pyarrow/tests/data/orc/decimal.orc,sha256=W5cV2WdLy4OrSTnd_Qv5ntphG4TcB-MyG4UpRFwSxJY,16337 +pyarrow/tests/data/parquet/v0.7.1.all-named-index.parquet,sha256=YPGUXtw-TsOPbiNDieZHobNp3or7nHhAxJGjmIDAyqE,3948 +pyarrow/tests/data/parquet/v0.7.1.column-metadata-handling.parquet,sha256=7sebZgpfdcP37QksT3FhDL6vOA9gR6GBaq44NCVtOYw,2012 +pyarrow/tests/data/parquet/v0.7.1.parquet,sha256=vmdzhIzpBbmRkq3Gjww7KqurfSFNtQuSpSIDeQVmqys,4372 +pyarrow/tests/data/parquet/v0.7.1.some-named-index.parquet,sha256=VGgSjqihCRtdBxlUcfP5s3BSR7aUQKukW-bGgJLf_HY,4008 +pyarrow/tests/extensions.pyx,sha256=UVfIEnGYy2SDiYkMW6jp6zTo1tvO7gUREH4OGEWjU2k,3046 +pyarrow/tests/interchange/__init__.py,sha256=9hdXHABrVpkbpjZgUft39kOFL2xSGeG4GEua0Hmelus,785 +pyarrow/tests/interchange/test_conversion.py,sha256=KC8bZgWhdutayp3V7RqBYoxk79_2NCY5UwM_s9D-Eas,18716 +pyarrow/tests/interchange/test_interchange_spec.py,sha256=AwqwD8b2gV1f5HERtzG6UtOIshFGafUI_9CQOVLPnho,9381 +pyarrow/tests/pandas_examples.py,sha256=SDPL87Ea997nGi7afOqlceq6wH4Bh5IR5S0fHZV3Acs,5107 +pyarrow/tests/pandas_threaded_import.py,sha256=b_ubLr5dj4dWJht9552qc3S3Yt3fQQgaUH6208oZvHg,1429 +pyarrow/tests/parquet/__init__.py,sha256=dKsXU9M-sJyz2wYIuqwsKM9meOlK_qY6qhmQzIvEpCE,931 +pyarrow/tests/parquet/common.py,sha256=ezd4yAsRXI1R-MxE1iIbyHHIlEGyKumckQzVHAx46rM,6149 +pyarrow/tests/parquet/conftest.py,sha256=CyKJlVT-pQNb2f2BwlUaIAoWtobcZ65Y0vgpGX2ttnA,3157 +pyarrow/tests/parquet/encryption.py,sha256=Nr232E2ueexYXGWNZQOfQpw3LkPTbSNHWSfwf1DuBVI,5218 +pyarrow/tests/parquet/test_basic.py,sha256=fl7zRxAbTA2LwIgP_sJj2LIWHjRG_GwqtN9W6ZP7tgU,40378 +pyarrow/tests/parquet/test_compliant_nested_type.py,sha256=Lz7tCPrSpv9GrKPMS-eu1LehsCTwz7KdUdCYJ8tF8dE,3901 +pyarrow/tests/parquet/test_data_types.py,sha256=Y_RqQBfDgrxQnOc0IjuHA7ueIOmotH8CMkmqQ4prz9I,19319 +pyarrow/tests/parquet/test_dataset.py,sha256=U144J3RrIA3rCQIJXxpgy5PQVn5-7gYsy-f3pD20nPs,42020 +pyarrow/tests/parquet/test_datetime.py,sha256=A3ZaRj88u0IrlhCNp2KY_A8txrb7y2pKPgEVvI7e7bU,16398 +pyarrow/tests/parquet/test_encryption.py,sha256=eULJdOgRNMrd6klGgW5ElotrejsL9ev_dBz3LG2P9L8,28357 +pyarrow/tests/parquet/test_metadata.py,sha256=340mr8FxLTVfLMSDDDRm8WWblCzRz7FD77mr4BoJodg,27732 +pyarrow/tests/parquet/test_pandas.py,sha256=4r9sip2N0XbkBb6SZuh51SUHJn9BlAtfjbzB3d3Gk_M,23543 +pyarrow/tests/parquet/test_parquet_file.py,sha256=VlsY4JwZUckF5uxDrVlrJRBVnvTmuuajd0h37BMkbOo,13872 +pyarrow/tests/parquet/test_parquet_writer.py,sha256=9-BjKiXjLzgKC0IodiT8KnCtSPBo1ZKinS_spFX8uWs,18215 +pyarrow/tests/pyarrow_cython_example.pyx,sha256=fx6zT1bUb2-cDnwKoG71K3ozpmrNJ53kKQHHJTExGz8,2115 +pyarrow/tests/read_record_batch.py,sha256=9Y0X0h03hUXwOKZz7jBBZSwgIrjxT-FkWIw6pu38Frc,953 +pyarrow/tests/strategies.py,sha256=eJkCeVCPB714DisrR1Q1INIUqIBX9v1rF2lNtBamlTY,15581 +pyarrow/tests/test_acero.py,sha256=t1TIMZJ9zfCYu9V_Mg_i6SvtoIXFo2GZbxDJh3Luu60,18009 +pyarrow/tests/test_adhoc_memory_leak.py,sha256=Pn4PcIbOBRtSJuz9Ar_ocubco0QOMZ-eAE9Bs7Wp4mA,1453 +pyarrow/tests/test_array.py,sha256=Gc7D3Q0Q9W3wiqVu-65_sSbm6lsBU8igYlT0a6t8NwM,148711 +pyarrow/tests/test_builder.py,sha256=zNEcslLwyb40oYbG7lInQcI81QHMKDOzi1zthw1Je7c,2803 +pyarrow/tests/test_cffi.py,sha256=ZkOKn6qfySL7LPqR-1FAlRS7n8h3npF5WmqSV8eBVg8,26339 +pyarrow/tests/test_compute.py,sha256=MC1TtWGhAsz2zTQCE2ZV42cR22fPKfbd0I6pGVQycb0,159108 +pyarrow/tests/test_convert_builtin.py,sha256=Kmyx7EVTWpCu3_M6_pJxcTmdNBuzluy2-b6TmWUMbnQ,82416 +pyarrow/tests/test_cpp_internals.py,sha256=SNRZJo9Fi5aHt7sYvt5290IXvAaewikqHNuzUYZD7sc,1923 +pyarrow/tests/test_csv.py,sha256=P-qtP_ZGU7LdS1dAiyieEBHv3CIvOIVDX_NHoLLaBTo,80478 +pyarrow/tests/test_cuda.py,sha256=81lyEjUUd2UWbeFW-JJECYJ9UZKAXOTys55uMd2i0so,36153 +pyarrow/tests/test_cuda_numba_interop.py,sha256=iHP_FE4sWbsKwNNXRcYnVozp3Wd1o0Mg6BDymx710G4,8794 +pyarrow/tests/test_cython.py,sha256=p3Pz_w8Ysd6pMQfvjGPsBA-fYjX7os9M9B9K9UB42m8,7040 +pyarrow/tests/test_dataset.py,sha256=TA9e4J0nL05TpISLgCzAOyY8g7vAmM4j8PYNaTkCw7U,218325 +pyarrow/tests/test_dataset_encryption.py,sha256=EpD2zXVcLS_3i4fwli7Z45ibDFSgXCFFQ2qiXtAxBsg,18023 +pyarrow/tests/test_deprecations.py,sha256=W_rneq4jC6zqCNoGhBDf1F28Q-0LHI7YKLgtsbV6LHM,891 +pyarrow/tests/test_device.py,sha256=qe9Wiwo-XVazt9pdxyqQJUz6fNR0jTs9CHiyaoppNA4,2550 +pyarrow/tests/test_dlpack.py,sha256=sdubFfaU6ZCjZFoj-vbqnT-Yf79kI2ZBOe9Qi_J0Vfk,5974 +pyarrow/tests/test_exec_plan.py,sha256=pjOkSaWeqjN6celKxUEH3tBGXLh8kKbmSSsvKOWsbQQ,10096 +pyarrow/tests/test_extension_type.py,sha256=hstOgNzG0mI9oichQEZh1qM4C7OOeuXDsNFm-mifKeY,72956 +pyarrow/tests/test_feather.py,sha256=QMFIyOh2bGwatHq3gE1OZWIgIdfJ2i5UjhNTN0DVK5U,25616 +pyarrow/tests/test_flight.py,sha256=pVDSfUFmcBDo0ChO3X5rH25k0lktfxjy_qAGDhr_Ido,103657 +pyarrow/tests/test_flight_async.py,sha256=kP2wIrUz1HIbrr4CE0SxSg4Zc56ta3xX6EdibAI2yVM,2848 +pyarrow/tests/test_fs.py,sha256=G_HceMKQM1ZMZgun3xwf1-3Gru0jDC6yckkA_M7Cb1o,74250 +pyarrow/tests/test_gandiva.py,sha256=AEf9ln-j5MmIMQ0JTQPhnZwbNh82ynSURsWPaKaNing,15623 +pyarrow/tests/test_gdb.py,sha256=mircGfK7H3PQgn4dVYfDUYbC03CzuAXqGW4oOmd-uZw,45052 +pyarrow/tests/test_io.py,sha256=fBEyAu9zTMHu9ovgwOwv7kEslYhY38sd0GNR78d-ECg,63736 +pyarrow/tests/test_ipc.py,sha256=KFK4E6Ee_zZ52k9hbxS6nMFNo41asuoppiFsJW8PGEI,46053 +pyarrow/tests/test_json.py,sha256=nU0jjdMy3PQx0IgNIFiLy3ms6TBJz1e2c9e87PtdUtI,23491 +pyarrow/tests/test_jvm.py,sha256=QLRHeFx8Gbz1UywlnDi2akV6DBtqGHReQEuNl2vSbcE,15475 +pyarrow/tests/test_memory.py,sha256=cZyuZDcp1_NMyIa3oYf8L3X4PGh-AVu5P0OwDIBRqtM,9726 +pyarrow/tests/test_misc.py,sha256=KlwIAiTASoZzhkdtwdC8_vl2CSIhqjfE2ztvNrnxtKM,7657 +pyarrow/tests/test_orc.py,sha256=UrDyNHJoTX72KFGVLf-WRzzC8e6nsP-GsCm97x0l43o,21565 +pyarrow/tests/test_pandas.py,sha256=5ZCuEOrKSHSnip25xbBBZi8FPS6Fz3BkQv3j1jOHRww,196253 +pyarrow/tests/test_scalars.py,sha256=pQvRaMUTh1Nu27OTKmA4m5QyS_yDEg5lUokHs3Jts60,32216 +pyarrow/tests/test_schema.py,sha256=x5MSVt6kOhvSPTCIMIFVTm27Lp6znJ4emk9L5ziqJwc,23347 +pyarrow/tests/test_sparse_tensor.py,sha256=bGO7Es4D65zmGNRN013G9VUCQ_v-mPUb90sAk--3yyw,17666 +pyarrow/tests/test_strategies.py,sha256=ZPJiWvj26qQPsR9aZCF-VUsT6PyWPrJsr6q2oXJshSs,1829 +pyarrow/tests/test_substrait.py,sha256=rsLPNlDPDAxZ1OIu-LAINSD4q_UZJhOvtTzPfXjrWM8,32223 +pyarrow/tests/test_table.py,sha256=aRgarP_oVThWXp41TBe6MLVPgmoZqCgwdKMVhq2KHig,123867 +pyarrow/tests/test_tensor.py,sha256=LYSEYGUjtdnsbL0WAir9jFindo-r0bLySiDA1uAXL8E,6643 +pyarrow/tests/test_types.py,sha256=XaGZSzq556PewtGRcUEQ6T5T6D5QidchC7RrmRQYI-8,44212 +pyarrow/tests/test_udf.py,sha256=WA9E5skUqh7uMr_zH3rQ11LRx0SK2G3WO8HjVHGWyQY,29792 +pyarrow/tests/test_util.py,sha256=216en304o-zqgoYBO08HxG-ZK9N0Aa1sbipRwRN4CWE,5092 +pyarrow/tests/test_without_numpy.py,sha256=ysbB-jML318I04ViQT4Ok7iMg1cI-NU8kguPu-FTSl4,1855 +pyarrow/tests/util.py,sha256=rx6zMuUkzq1TNTEddo5LJShctb4ax5Gpyx0ruJIn4VY,13197 +pyarrow/tests/wsgi_examples.py,sha256=vQIDb5989sRVLsELw-fRHhfX-dE96sTl5J2lEuEKup8,1348 +pyarrow/types.pxi,sha256=5cfQ-OkvtTutms6Ka5WmiiUKZP_xnpMwujNohXwNW94,162780 +pyarrow/types.py,sha256=d79cmgr2MDz79UQy6ChoKf4MScKCtDvtOBVGIbV1644,9734 +pyarrow/util.py,sha256=dTVaDktJnZN3iqdmww837TK_T46jkqlkpQXE1M9u5gY,9267 +pyarrow/vendored/__init__.py,sha256=9hdXHABrVpkbpjZgUft39kOFL2xSGeG4GEua0Hmelus,785 +pyarrow/vendored/docscrape.py,sha256=phTjwuzoO5hB88QerZk3uGu9c5OrZwjFzI7vEIIbCUQ,22975 +pyarrow/vendored/version.py,sha256=5-Vo4Q3kPJrm1DSGusnMlTxuA8ynI4hAryApBd6MnpQ,14345 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pyarrow-24.0.0.dist-info/REQUESTED b/bioagent-bench/.venv/lib/python3.14/site-packages/pyarrow-24.0.0.dist-info/REQUESTED new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pyarrow-24.0.0.dist-info/WHEEL b/bioagent-bench/.venv/lib/python3.14/site-packages/pyarrow-24.0.0.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..b0f14fde4dffe40278275674002551b7b11c3243 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pyarrow-24.0.0.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: scikit-build-core 0.12.2 +Root-Is-Purelib: false +Tag: cp314-cp314-manylinux_2_28_x86_64 + diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pyarrow-24.0.0.dist-info/licenses/LICENSE.txt b/bioagent-bench/.venv/lib/python3.14/site-packages/pyarrow-24.0.0.dist-info/licenses/LICENSE.txt new file mode 100644 index 0000000000000000000000000000000000000000..5382ddfa5d1e1ce88d826ed23c0a88535d4babae --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pyarrow-24.0.0.dist-info/licenses/LICENSE.txt @@ -0,0 +1,2307 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +-------------------------------------------------------------------------------- + +src/arrow/util (some portions): Apache 2.0, and 3-clause BSD + +Some portions of this module are derived from code in the Chromium project, +copyright (c) Google inc and (c) The Chromium Authors and licensed under the +Apache 2.0 License or the under the 3-clause BSD license: + + Copyright (c) 2013 The Chromium Authors. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------------------- + +This project includes code from Daniel Lemire's FrameOfReference project. + +https://github.com/lemire/FrameOfReference/blob/6ccaf9e97160f9a3b299e23a8ef739e711ef0c71/src/bpacking.cpp +https://github.com/lemire/FrameOfReference/blob/146948b6058a976bc7767262ad3a2ce201486b93/scripts/turbopacking64.py + +Copyright: 2013 Daniel Lemire +Home page: http://lemire.me/en/ +Project page: https://github.com/lemire/FrameOfReference +License: Apache License Version 2.0 http://www.apache.org/licenses/LICENSE-2.0 + +-------------------------------------------------------------------------------- + +This project includes code from the TensorFlow project + +Copyright 2015 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +-------------------------------------------------------------------------------- + +This project includes code from the NumPy project. + +https://github.com/numpy/numpy/blob/e1f191c46f2eebd6cb892a4bfe14d9dd43a06c4e/numpy/core/src/multiarray/multiarraymodule.c#L2910 + +https://github.com/numpy/numpy/blob/68fd82271b9ea5a9e50d4e761061dfcca851382a/numpy/core/src/multiarray/datetime.c + +Copyright (c) 2005-2017, NumPy Developers. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of the NumPy Developers nor the names of any + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------------------- + +This project includes code from the Boost project + +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +-------------------------------------------------------------------------------- + +This project includes code from the FlatBuffers project + +Copyright 2014 Google Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +-------------------------------------------------------------------------------- + +This project includes code from the tslib project + +Copyright 2015 Microsoft Corporation. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +-------------------------------------------------------------------------------- + +This project includes code from the jemalloc project + +https://github.com/jemalloc/jemalloc + +Copyright (C) 2002-2017 Jason Evans . +All rights reserved. +Copyright (C) 2007-2012 Mozilla Foundation. All rights reserved. +Copyright (C) 2009-2017 Facebook, Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright notice(s), + this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice(s), + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) ``AS IS'' AND ANY EXPRESS +OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL THE COPYRIGHT HOLDER(S) BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +-------------------------------------------------------------------------------- + +This project includes code from the Go project, BSD 3-clause license + PATENTS +weak patent termination clause +(https://github.com/golang/go/blob/master/PATENTS). + +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------------------- + +This project includes code from the hs2client + +https://github.com/cloudera/hs2client + +Copyright 2016 Cloudera Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +-------------------------------------------------------------------------------- + +The script r/configure has the following license (MIT) + +Copyright (c) 2017, Jeroen Ooms and Jim Hester + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +-------------------------------------------------------------------------------- + +cpp/src/arrow/util/logging.cc, cpp/src/arrow/util/logging.h and +cpp/src/arrow/util/logging-test.cc are adapted from +Ray Project (https://github.com/ray-project/ray) (Apache 2.0). + +Copyright (c) 2016 Ray Project (https://github.com/ray-project/ray) + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +-------------------------------------------------------------------------------- +The files cpp/src/arrow/vendored/datetime/date.h, cpp/src/arrow/vendored/datetime/tz.h, +cpp/src/arrow/vendored/datetime/tz_private.h, cpp/src/arrow/vendored/datetime/ios.h, +cpp/src/arrow/vendored/datetime/ios.mm, +cpp/src/arrow/vendored/datetime/tz.cpp are adapted from +Howard Hinnant's date library (https://github.com/HowardHinnant/date) +It is licensed under MIT license. + +The MIT License (MIT) +Copyright (c) 2015, 2016, 2017 Howard Hinnant +Copyright (c) 2016 Adrian Colomitchi +Copyright (c) 2017 Florian Dang +Copyright (c) 2017 Paul Thompson +Copyright (c) 2018 Tomasz Kamiński + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +-------------------------------------------------------------------------------- + +The file cpp/src/arrow/util/utf8.h includes code adapted from the page + https://bjoern.hoehrmann.de/utf-8/decoder/dfa/ +with the following license (MIT) + +Copyright (c) 2008-2009 Bjoern Hoehrmann + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +-------------------------------------------------------------------------------- + +The files in cpp/src/arrow/vendored/xxhash/ have the following license +(BSD 2-Clause License) + +xxHash Library +Copyright (c) 2012-2014, Yann Collet +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +You can contact the author at : +- xxHash homepage: http://www.xxhash.com +- xxHash source repository : https://github.com/Cyan4973/xxHash + +-------------------------------------------------------------------------------- + +The files in cpp/src/arrow/vendored/double-conversion/ have the following license +(BSD 3-Clause License) + +Copyright 2006-2011, the V8 project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------------------- + +The files in cpp/src/arrow/vendored/uriparser/ have the following license +(BSD 3-Clause License) + +uriparser - RFC 3986 URI parsing library + +Copyright (C) 2007, Weijia Song +Copyright (C) 2007, Sebastian Pipping +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + * Redistributions of source code must retain the above + copyright notice, this list of conditions and the following + disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials + provided with the distribution. + + * Neither the name of the nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior written + permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------------------- + +The files under dev/tasks/conda-recipes have the following license + +BSD 3-clause license +Copyright (c) 2015-2018, conda-forge +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------------------- + +The files in cpp/src/arrow/vendored/utfcpp/ have the following license + +Copyright 2006-2018 Nemanja Trifunovic + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + +-------------------------------------------------------------------------------- + +This project includes code from Apache Kudu. + + * cpp/cmake_modules/CompilerInfo.cmake is based on Kudu's cmake_modules/CompilerInfo.cmake + +Copyright: 2016 The Apache Software Foundation. +Home page: https://kudu.apache.org/ +License: http://www.apache.org/licenses/LICENSE-2.0 + +-------------------------------------------------------------------------------- + +This project includes code from Apache Impala (incubating), formerly +Impala. The Impala code and rights were donated to the ASF as part of the +Incubator process after the initial code imports into Apache Parquet. + +Copyright: 2012 Cloudera, Inc. +Copyright: 2016 The Apache Software Foundation. +Home page: http://impala.apache.org/ +License: http://www.apache.org/licenses/LICENSE-2.0 + +-------------------------------------------------------------------------------- + +This project includes code from Apache Aurora. + +* dev/release/{release,changelog,release-candidate} are based on the scripts from + Apache Aurora + +Copyright: 2016 The Apache Software Foundation. +Home page: https://aurora.apache.org/ +License: http://www.apache.org/licenses/LICENSE-2.0 + +-------------------------------------------------------------------------------- + +This project includes code from Snappy. + +* cpp/cmake_modules/{SnappyCMakeLists.txt,SnappyConfig.h} are based on code + from Google's Snappy project. + +Copyright: 2009 Google Inc. All rights reserved. +Homepage: https://github.com/google/snappy +License: 3-clause BSD + +-------------------------------------------------------------------------------- + +This project includes code from the manylinux project. + +* python/manylinux1/scripts/{build_python.sh,python-tag-abi-tag.py, + requirements.txt} are based on code from the manylinux project. + +Copyright: 2016 manylinux +Homepage: https://github.com/pypa/manylinux +License: The MIT License (MIT) + +-------------------------------------------------------------------------------- + +This project includes code from the cymove project: + +* python/pyarrow/includes/common.pxd includes code from the cymove project + +The MIT License (MIT) +Copyright (c) 2019 Omer Ozarslan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE +OR OTHER DEALINGS IN THE SOFTWARE. + +-------------------------------------------------------------------------------- + +The projects includes code from the Ursabot project under the dev/archery +directory. + +License: BSD 2-Clause + +Copyright 2019 RStudio, Inc. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------------------- + +This project include code from mingw-w64. + +* cpp/src/arrow/util/cpu-info.cc has a polyfill for mingw-w64 < 5 + +Copyright (c) 2009 - 2013 by the mingw-w64 project +Homepage: https://mingw-w64.org +License: Zope Public License (ZPL) Version 2.1. + +--------------------------------------------------------------------------------- + +This project include code from Google's Asylo project. + +* cpp/src/arrow/result.h is based on status_or.h + +Copyright (c) Copyright 2017 Asylo authors +Homepage: https://asylo.dev/ +License: Apache 2.0 + +-------------------------------------------------------------------------------- + +This project includes code from Google's protobuf project + +* cpp/src/arrow/result.h ARROW_ASSIGN_OR_RAISE is based off ASSIGN_OR_RETURN +* cpp/src/arrow/util/bit_stream_utils.h contains code from wire_format_lite.h + +Copyright 2008 Google Inc. All rights reserved. +Homepage: https://developers.google.com/protocol-buffers/ +License: + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Code generated by the Protocol Buffer compiler is owned by the owner +of the input file used when generating it. This code is not +standalone and requires a support library to be linked with it. This +support library is itself covered by the above license. + +-------------------------------------------------------------------------------- + +3rdparty dependency LLVM is statically linked in certain binary distributions. +Additionally some sections of source code have been derived from sources in LLVM +and have been clearly labeled as such. LLVM has the following license: + +============================================================================== +The LLVM Project is under the Apache License v2.0 with LLVM Exceptions: +============================================================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +---- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. + +============================================================================== +Software from third parties included in the LLVM Project: +============================================================================== +The LLVM Project contains third party software which is under different license +terms. All such code will be identified clearly using at least one of two +mechanisms: +1) It will be in a separate directory tree with its own `LICENSE.txt` or + `LICENSE` file at the top containing the specific license and restrictions + which apply to that software, or +2) It will contain specific license and restriction terms at the top of every + file. + +-------------------------------------------------------------------------------- + +3rdparty dependency gRPC is statically linked in certain binary +distributions, like the python wheels. gRPC has the following license: + +Copyright 2014 gRPC authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +-------------------------------------------------------------------------------- + +3rdparty dependency Apache Thrift is statically linked in certain binary +distributions, like the python wheels. Apache Thrift has the following license: + +Apache Thrift +Copyright (C) 2006 - 2019, The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +-------------------------------------------------------------------------------- + +3rdparty dependency Apache ORC is statically linked in certain binary +distributions, like the python wheels. Apache ORC has the following license: + +Apache ORC +Copyright 2013-2019 The Apache Software Foundation + +This product includes software developed by The Apache Software +Foundation (http://www.apache.org/). + +This product includes software developed by Hewlett-Packard: +(c) Copyright [2014-2015] Hewlett-Packard Development Company, L.P + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +-------------------------------------------------------------------------------- + +3rdparty dependency zstd is statically linked in certain binary +distributions, like the python wheels. ZSTD has the following license: + +BSD License + +For Zstandard software + +Copyright (c) 2016-present, Facebook, Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name Facebook nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------------------- + +3rdparty dependency lz4 is statically linked in certain binary +distributions, like the python wheels. lz4 has the following license: + +LZ4 Library +Copyright (c) 2011-2016, Yann Collet +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------------------- + +3rdparty dependency Brotli is statically linked in certain binary +distributions, like the python wheels. Brotli has the following license: + +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +-------------------------------------------------------------------------------- + +3rdparty dependency rapidjson is statically linked in certain binary +distributions, like the python wheels. rapidjson and its dependencies have the +following licenses: + +Tencent is pleased to support the open source community by making RapidJSON +available. + +Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +All rights reserved. + +If you have downloaded a copy of the RapidJSON binary from Tencent, please note +that the RapidJSON binary is licensed under the MIT License. +If you have downloaded a copy of the RapidJSON source code from Tencent, please +note that RapidJSON source code is licensed under the MIT License, except for +the third-party components listed below which are subject to different license +terms. Your integration of RapidJSON into your own projects may require +compliance with the MIT License, as well as the other licenses applicable to +the third-party components included within RapidJSON. To avoid the problematic +JSON license in your own projects, it's sufficient to exclude the +bin/jsonchecker/ directory, as it's the only code under the JSON license. +A copy of the MIT License is included in this file. + +Other dependencies and licenses: + + Open Source Software Licensed Under the BSD License: + -------------------------------------------------------------------- + + The msinttypes r29 + Copyright (c) 2006-2013 Alexander Chemeris + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY + EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + DAMAGE. + + Terms of the MIT License: + -------------------------------------------------------------------- + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + +-------------------------------------------------------------------------------- + +3rdparty dependency snappy is statically linked in certain binary +distributions, like the python wheels. snappy has the following license: + +Copyright 2011, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of Google Inc. nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=== + +Some of the benchmark data in testdata/ is licensed differently: + + - fireworks.jpeg is Copyright 2013 Steinar H. Gunderson, and + is licensed under the Creative Commons Attribution 3.0 license + (CC-BY-3.0). See https://creativecommons.org/licenses/by/3.0/ + for more information. + + - kppkn.gtb is taken from the Gaviota chess tablebase set, and + is licensed under the MIT License. See + https://sites.google.com/site/gaviotachessengine/Home/endgame-tablebases-1 + for more information. + + - paper-100k.pdf is an excerpt (bytes 92160 to 194560) from the paper + “Combinatorial Modeling of Chromatin Features Quantitatively Predicts DNA + Replication Timing in _Drosophila_” by Federico Comoglio and Renato Paro, + which is licensed under the CC-BY license. See + http://www.ploscompbiol.org/static/license for more ifnormation. + + - alice29.txt, asyoulik.txt, plrabn12.txt and lcet10.txt are from Project + Gutenberg. The first three have expired copyrights and are in the public + domain; the latter does not have expired copyright, but is still in the + public domain according to the license information + (http://www.gutenberg.org/ebooks/53). + +-------------------------------------------------------------------------------- + +3rdparty dependency gflags is statically linked in certain binary +distributions, like the python wheels. gflags has the following license: + +Copyright (c) 2006, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------------------- + +3rdparty dependency glog is statically linked in certain binary +distributions, like the python wheels. glog has the following license: + +Copyright (c) 2008, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +A function gettimeofday in utilities.cc is based on + +http://www.google.com/codesearch/p?hl=en#dR3YEbitojA/COPYING&q=GetSystemTimeAsFileTime%20license:bsd + +The license of this code is: + +Copyright (c) 2003-2008, Jouni Malinen and contributors +All Rights Reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name(s) of the above-listed copyright holder(s) nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------------------- + +3rdparty dependency re2 is statically linked in certain binary +distributions, like the python wheels. re2 has the following license: + +Copyright (c) 2009 The RE2 Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its contributors + may be used to endorse or promote products derived from this + software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------------------- + +3rdparty dependency c-ares is statically linked in certain binary +distributions, like the python wheels. c-ares has the following license: + +# c-ares license + +Copyright (c) 2007 - 2018, Daniel Stenberg with many contributors, see AUTHORS +file. + +Copyright 1998 by the Massachusetts Institute of Technology. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, provided that +the above copyright notice appear in all copies and that both that copyright +notice and this permission notice appear in supporting documentation, and that +the name of M.I.T. not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior permission. +M.I.T. makes no representations about the suitability of this software for any +purpose. It is provided "as is" without express or implied warranty. + +-------------------------------------------------------------------------------- + +3rdparty dependency zlib is redistributed as a dynamically linked shared +library in certain binary distributions, like the python wheels. In the future +this will likely change to static linkage. zlib has the following license: + +zlib.h -- interface of the 'zlib' general purpose compression library + version 1.2.11, January 15th, 2017 + + Copyright (C) 1995-2017 Jean-loup Gailly and Mark Adler + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu + +-------------------------------------------------------------------------------- + +3rdparty dependency openssl is redistributed as a dynamically linked shared +library in certain binary distributions, like the python wheels. openssl +preceding version 3 has the following license: + + LICENSE ISSUES + ============== + + The OpenSSL toolkit stays under a double license, i.e. both the conditions of + the OpenSSL License and the original SSLeay license apply to the toolkit. + See below for the actual license texts. + + OpenSSL License + --------------- + +/* ==================================================================== + * Copyright (c) 1998-2019 The OpenSSL Project. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" + * + * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * openssl-core@openssl.org. + * + * 5. Products derived from this software may not be called "OpenSSL" + * nor may "OpenSSL" appear in their names without prior written + * permission of the OpenSSL Project. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the OpenSSL Project + * for use in the OpenSSL Toolkit (http://www.openssl.org/)" + * + * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * ==================================================================== + * + * This product includes cryptographic software written by Eric Young + * (eay@cryptsoft.com). This product includes software written by Tim + * Hudson (tjh@cryptsoft.com). + * + */ + + Original SSLeay License + ----------------------- + +/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) + * All rights reserved. + * + * This package is an SSL implementation written + * by Eric Young (eay@cryptsoft.com). + * The implementation was written so as to conform with Netscapes SSL. + * + * This library is free for commercial and non-commercial use as long as + * the following conditions are aheared to. The following conditions + * apply to all code found in this distribution, be it the RC4, RSA, + * lhash, DES, etc., code; not just the SSL code. The SSL documentation + * included with this distribution is covered by the same copyright terms + * except that the holder is Tim Hudson (tjh@cryptsoft.com). + * + * Copyright remains Eric Young's, and as such any Copyright notices in + * the code are not to be removed. + * If this package is used in a product, Eric Young should be given attribution + * as the author of the parts of the library used. + * This can be in the form of a textual message at program startup or + * in documentation (online or textual) provided with the package. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * "This product includes cryptographic software written by + * Eric Young (eay@cryptsoft.com)" + * The word 'cryptographic' can be left out if the rouines from the library + * being used are not cryptographic related :-). + * 4. If you include any Windows specific code (or a derivative thereof) from + * the apps directory (application code) you must include an acknowledgement: + * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + * + * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * The licence and distribution terms for any publically available version or + * derivative of this code cannot be changed. i.e. this code cannot simply be + * copied and put under another distribution licence + * [including the GNU Public Licence.] + */ + +-------------------------------------------------------------------------------- + +This project includes code from the rtools-backports project. + +* ci/scripts/PKGBUILD and ci/scripts/r_windows_build.sh are based on code + from the rtools-backports project. + +Copyright: Copyright (c) 2013 - 2019, Алексей and Jeroen Ooms. +All rights reserved. +Homepage: https://github.com/r-windows/rtools-backports +License: 3-clause BSD + +-------------------------------------------------------------------------------- + +Some code from pandas has been adapted for the pyarrow codebase. pandas is +available under the 3-clause BSD license, which follows: + +pandas license +============== + +Copyright (c) 2011-2012, Lambda Foundry, Inc. and PyData Development Team +All rights reserved. + +Copyright (c) 2008-2011 AQR Capital Management, LLC +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * Neither the name of the copyright holder nor the names of any + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------------------- + +Some bits from DyND, in particular aspects of the build system, have been +adapted from libdynd and dynd-python under the terms of the BSD 2-clause +license + +The BSD 2-Clause License + + Copyright (C) 2011-12, Dynamic NDArray Developers + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Dynamic NDArray Developers list: + + * Mark Wiebe + * Continuum Analytics + +-------------------------------------------------------------------------------- + +Some source code from Ibis (https://github.com/cloudera/ibis) has been adapted +for PyArrow. Ibis is released under the Apache License, Version 2.0. + +-------------------------------------------------------------------------------- + +dev/tasks/homebrew-formulae/apache-arrow.rb has the following license: + +BSD 2-Clause License + +Copyright (c) 2009-present, Homebrew contributors +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- + +cpp/src/arrow/vendored/base64.cpp has the following license + +ZLIB License + +Copyright (C) 2004-2017 René Nyffenegger + +This source code is provided 'as-is', without any express or implied +warranty. In no event will the author be held liable for any damages arising +from the use of this software. + +Permission is granted to anyone to use this software for any purpose, including +commercial applications, and to alter it and redistribute it freely, subject to +the following restrictions: + +1. The origin of this source code must not be misrepresented; you must not + claim that you wrote the original source code. If you use this source code + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original source code. + +3. This notice may not be removed or altered from any source distribution. + +René Nyffenegger rene.nyffenegger@adp-gmbh.ch + +-------------------------------------------------------------------------------- + +This project includes code from Folly. + + * cpp/src/arrow/vendored/ProducerConsumerQueue.h + +is based on Folly's + + * folly/Portability.h + * folly/lang/Align.h + * folly/ProducerConsumerQueue.h + +Copyright: Copyright (c) Facebook, Inc. and its affiliates. +Home page: https://github.com/facebook/folly +License: http://www.apache.org/licenses/LICENSE-2.0 + +-------------------------------------------------------------------------------- + +The file cpp/src/arrow/vendored/musl/strptime.c has the following license + +Copyright © 2005-2020 Rich Felker, et al. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +-------------------------------------------------------------------------------- + +The file cpp/cmake_modules/BuildUtils.cmake contains code from + +https://gist.github.com/cristianadam/ef920342939a89fae3e8a85ca9459b49 + +which is made available under the MIT license + +Copyright (c) 2019 Cristian Adam + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +-------------------------------------------------------------------------------- + +The files in cpp/src/arrow/vendored/portable-snippets/ contain code from + +https://github.com/nemequ/portable-snippets + +and have the following copyright notice: + +Each source file contains a preamble explaining the license situation +for that file, which takes priority over this file. With the +exception of some code pulled in from other repositories (such as +µnit, an MIT-licensed project which is used for testing), the code is +public domain, released using the CC0 1.0 Universal dedication (*). + +(*) https://creativecommons.org/publicdomain/zero/1.0/legalcode + +-------------------------------------------------------------------------------- + +The files in cpp/src/arrow/vendored/fast_float/ contain code from + +https://github.com/lemire/fast_float + +which is made available under the Apache License 2.0. + +-------------------------------------------------------------------------------- + +The file python/pyarrow/vendored/docscrape.py contains code from + +https://github.com/numpy/numpydoc/ + +which is made available under the BSD 2-clause license. + +-------------------------------------------------------------------------------- + +The file python/pyarrow/vendored/version.py contains code from + +https://github.com/pypa/packaging/ + +which is made available under both the Apache license v2.0 and the +BSD 2-clause license. + +-------------------------------------------------------------------------------- + +The files in cpp/src/arrow/vendored/pcg contain code from + +https://github.com/imneme/pcg-cpp + +and have the following copyright notice: + +Copyright 2014-2019 Melissa O'Neill , + and the PCG Project contributors. + +SPDX-License-Identifier: (Apache-2.0 OR MIT) + +Licensed under the Apache License, Version 2.0 (provided in +LICENSE-APACHE.txt and at http://www.apache.org/licenses/LICENSE-2.0) +or under the MIT license (provided in LICENSE-MIT.txt and at +http://opensource.org/licenses/MIT), at your option. This file may not +be copied, modified, or distributed except according to those terms. + +Distributed on an "AS IS" BASIS, WITHOUT WARRANTY OF ANY KIND, either +express or implied. See your chosen license for details. + +-------------------------------------------------------------------------------- +r/R/dplyr-count-tally.R (some portions) + +Some portions of this file are derived from code from + +https://github.com/tidyverse/dplyr/ + +which is made available under the MIT license + +Copyright (c) 2013-2019 RStudio and others. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the “Software”), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +-------------------------------------------------------------------------------- + +The file src/arrow/util/io_util.cc contains code from the CPython project +which is made available under the Python Software Foundation License Version 2. + +-------------------------------------------------------------------------------- + +3rdparty dependency opentelemetry-cpp is statically linked in certain binary +distributions. opentelemetry-cpp is made available under the Apache License 2.0. + +Copyright The OpenTelemetry Authors +SPDX-License-Identifier: Apache-2.0 + +-------------------------------------------------------------------------------- + +3rdparty dependency UCX is redistributed as a dynamically linked shared +library in certain binary distributions. UCX has the following license: + +Copyright (c) 2014-2015 UT-Battelle, LLC. All rights reserved. +Copyright (C) 2014-2020 Mellanox Technologies Ltd. All rights reserved. +Copyright (C) 2014-2015 The University of Houston System. All rights reserved. +Copyright (C) 2015 The University of Tennessee and The University + of Tennessee Research Foundation. All rights reserved. +Copyright (C) 2016-2020 ARM Ltd. All rights reserved. +Copyright (c) 2016 Los Alamos National Security, LLC. All rights reserved. +Copyright (C) 2016-2020 Advanced Micro Devices, Inc. All rights reserved. +Copyright (C) 2019 UChicago Argonne, LLC. All rights reserved. +Copyright (c) 2018-2020 NVIDIA CORPORATION. All rights reserved. +Copyright (C) 2020 Huawei Technologies Co., Ltd. All rights reserved. +Copyright (C) 2016-2020 Stony Brook University. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holder nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +-------------------------------------------------------------------------------- + +The file dev/tasks/r/github.packages.yml contains code from + +https://github.com/ursa-labs/arrow-r-nightly + +which is made available under the Apache License 2.0. + +-------------------------------------------------------------------------------- +.github/actions/sync-nightlies/action.yml (some portions) + +Some portions of this file are derived from code from + +https://github.com/JoshPiper/rsync-docker + +which is made available under the MIT license + +Copyright (c) 2020 Joshua Piper + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +-------------------------------------------------------------------------------- +.github/actions/sync-nightlies/action.yml (some portions) + +Some portions of this file are derived from code from + +https://github.com/burnett01/rsync-deployments + +which is made available under the MIT license + +Copyright (c) 2019-2022 Contention +Copyright (c) 2019-2022 Burnett01 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +-------------------------------------------------------------------------------- +java/vector/src/main/java/org/apache/arrow/vector/util/IntObjectHashMap.java +java/vector/src/main/java/org/apache/arrow/vector/util/IntObjectMap.java + +These files are derived from code from Netty, which is made available under the +Apache License 2.0. + +-------------------------------------------------------------------------------- +cpp/src/arrow/util/math_internal.cc (some portions) + +Some portions of this file are derived from + +https://github.com/ankane/dist-rust/ + +which is made available under the MIT license + +The MIT License (MIT) + +Copyright (c) 2021-2023 Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +-------------------------------------------------------------------------------- +The files cpp/src/arrow/vendored/whereami/whereami.h, +cpp/src/arrow/vendored/whereami/whereami.cc are adapted from +Grégory Pakosz's whereami library (https://github.com/gpakosz/whereami) +It is dual licensed under both the WTFPLv2 and MIT licenses. + +The WTFPLv2 License + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. + 1. Bla bla bla + 2. Montesqieu et camembert, vive la France, zut alors! + +The MIT License (MIT) +Copyright Gregory Pakosz + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +-------------------------------------------------------------------------------- + +The files in cpp/src/arrow/vendored/safeint/ contain code from + +https://github.com/dcleblanc/SafeInt + +and are made available under the MIT license. + +MIT License + +Copyright (c) 2018 Microsoft + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pyarrow-24.0.0.dist-info/licenses/NOTICE.txt b/bioagent-bench/.venv/lib/python3.14/site-packages/pyarrow-24.0.0.dist-info/licenses/NOTICE.txt new file mode 100644 index 0000000000000000000000000000000000000000..8046f20a0b919dba562dee3d5097fdee7bdd4010 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pyarrow-24.0.0.dist-info/licenses/NOTICE.txt @@ -0,0 +1,81 @@ +Apache Arrow +Copyright 2016-2026 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). + +This product includes software from the SFrame project (BSD, 3-clause). +* Copyright (C) 2015 Dato, Inc. +* Copyright (c) 2009 Carnegie Mellon University. + +This product includes software from the Feather project (Apache 2.0) +https://github.com/wesm/feather + +This product includes software from the DyND project (BSD 2-clause) +https://github.com/libdynd + +This product includes software from the LLVM project + * distributed under the University of Illinois Open Source + +This product includes software from the mman-win32 project + * Copyright https://code.google.com/p/mman-win32/ + * Licensed under the MIT License; + +This product includes software from the LevelDB project + * Copyright (c) 2011 The LevelDB Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * Moved from Kudu http://github.com/cloudera/kudu + +This product includes software from the CMake project + * Copyright 2001-2009 Kitware, Inc. + * Copyright 2012-2014 Continuum Analytics, Inc. + * All rights reserved. + +This product includes software from https://github.com/matthew-brett/multibuild (BSD 2-clause) + * Copyright (c) 2013-2016, Matt Terry and Matthew Brett; all rights reserved. + +This product includes software from the Ibis project (Apache 2.0) + * Copyright (c) 2015 Cloudera, Inc. + * https://github.com/cloudera/ibis + +This product includes software from Dremio (Apache 2.0) + * Copyright (C) 2017-2018 Dremio Corporation + * https://github.com/dremio/dremio-oss + +This product includes software from Google Guava (Apache 2.0) + * Copyright (C) 2007 The Guava Authors + * https://github.com/google/guava + +This product include software from CMake (BSD 3-Clause) + * CMake - Cross Platform Makefile Generator + * Copyright 2000-2019 Kitware, Inc. and Contributors + +The web site includes files generated by Jekyll. + +-------------------------------------------------------------------------------- + +This product includes code from Apache Kudu, which includes the following in +its NOTICE file: + + Apache Kudu + Copyright 2016 The Apache Software Foundation + + This product includes software developed at + The Apache Software Foundation (http://www.apache.org/). + + Portions of this software were developed at + Cloudera, Inc (http://www.cloudera.com/). + +-------------------------------------------------------------------------------- + +This product includes code from Apache ORC, which includes the following in +its NOTICE file: + + Apache ORC + Copyright 2013-2019 The Apache Software Foundation + + This product includes software developed by The Apache Software + Foundation (http://www.apache.org/). + + This product includes software developed by Hewlett-Packard: + (c) Copyright [2014-2015] Hewlett-Packard Development Company, L.P diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pygments-2.20.0.dist-info/INSTALLER b/bioagent-bench/.venv/lib/python3.14/site-packages/pygments-2.20.0.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..5c69047b2eb8235994febeeae1da4a82365a240a --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pygments-2.20.0.dist-info/INSTALLER @@ -0,0 +1 @@ +uv \ No newline at end of file diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pygments-2.20.0.dist-info/METADATA b/bioagent-bench/.venv/lib/python3.14/site-packages/pygments-2.20.0.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..3dea62032aff25161bb49b38d237376f18fbaec1 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pygments-2.20.0.dist-info/METADATA @@ -0,0 +1,57 @@ +Metadata-Version: 2.4 +Name: Pygments +Version: 2.20.0 +Summary: Pygments is a syntax highlighting package written in Python. +Project-URL: Homepage, https://pygments.org +Project-URL: Documentation, https://pygments.org/docs +Project-URL: Source, https://github.com/pygments/pygments +Project-URL: Bug Tracker, https://github.com/pygments/pygments/issues +Project-URL: Changelog, https://github.com/pygments/pygments/blob/master/CHANGES +Author-email: Georg Brandl +Maintainer: Matthäus G. Chajdas +Maintainer-email: Georg Brandl , Jean Abou Samra +License-Expression: BSD-2-Clause +License-File: AUTHORS +License-File: LICENSE +Keywords: syntax highlighting +Classifier: Development Status :: 6 - Mature +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: End Users/Desktop +Classifier: Intended Audience :: System Administrators +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Text Processing :: Filters +Classifier: Topic :: Utilities +Requires-Python: >=3.9 +Provides-Extra: plugins +Provides-Extra: windows-terminal +Requires-Dist: colorama>=0.4.6; extra == 'windows-terminal' +Description-Content-Type: text/x-rst + +Pygments +~~~~~~~~ + +Pygments is a syntax highlighting package written in Python. + +It is a generic syntax highlighter suitable for use in code hosting, forums, +wikis or other applications that need to prettify source code. Highlights +are: + +* a wide range of over 500 languages and other text formats is supported +* special attention is paid to details, increasing quality by a fair amount +* support for new languages and formats are added easily +* a number of output formats, presently HTML, LaTeX, RTF, SVG, all image + formats that PIL supports and ANSI sequences +* it is usable as a command-line tool and as a library + +Copyright 2006-present by the Pygments team, see ``AUTHORS``. +Licensed under the BSD, see ``LICENSE`` for details. diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pygments-2.20.0.dist-info/RECORD b/bioagent-bench/.venv/lib/python3.14/site-packages/pygments-2.20.0.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..136db4cc64b3561044c7dabd762037dbc6b45107 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pygments-2.20.0.dist-info/RECORD @@ -0,0 +1,348 @@ +../../../bin/pygmentize,sha256=Xd2h6eGZ5KPknvfl7PK_HMGDyxtsEjnfCB2DdTOl_lY,332 +pygments-2.20.0.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2 +pygments-2.20.0.dist-info/METADATA,sha256=4FKPUbMEJ_rpRyNmK6Yi-NjbKk2NPxNlaY1npSRQqEU,2476 +pygments-2.20.0.dist-info/RECORD,, +pygments-2.20.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pygments-2.20.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87 +pygments-2.20.0.dist-info/entry_points.txt,sha256=uUXw-XhMKBEX4pWcCtpuTTnPhL3h7OEE2jWi51VQsa8,53 +pygments-2.20.0.dist-info/licenses/AUTHORS,sha256=DbYDpfRJn2kMRCVHf_ZkwWbaMl06zDtajU3j2wckQ9A,10873 +pygments-2.20.0.dist-info/licenses/LICENSE,sha256=qdZvHVJt8C4p3Oc0NtNOVuhjL0bCdbvf_HBWnogvnxc,1331 +pygments/__init__.py,sha256=ZzpnXpvnv0c7r_OIS8h7UeaESqYNKMy__pV7KCiWXxw,2962 +pygments/__main__.py,sha256=QZWj0T6TTRsqr-w-0YvVILo1DyAvzzydGCRONwdEzqo,351 +pygments/cmdline.py,sha256=_dOnrta_2GIe8Jg-1Y0pb5vWi8L6QTiJzyoTuHrYrbM,23542 +pygments/console.py,sha256=C189JAwhC1Qh0AKgshzb1wVDzFqBnv1VZ45kTEDIO30,1721 +pygments/filter.py,sha256=1dnbkq2AdC3AkHt3DaXwnOkTBLChl1kR6naSLwnr_tc,1913 +pygments/filters/__init__.py,sha256=03ZYdIYmxnWCh7gNXD7lEQ869Df4kzHnOGM44iJxan8,40349 +pygments/formatter.py,sha256=PTBnTW0EHke2vzlAKuvHJkj3-_CMj8duIx-3yVUie-o,4369 +pygments/formatters/__init__.py,sha256=qPG4q5cuaZRGBglmEJVLP4SDv43QI3tAUskj30M-mOY,5352 +pygments/formatters/_mapping.py,sha256=1Cw37FuQlNacnxRKmtlPX4nyLoX9_ttko5ZwscNUZZ4,4176 +pygments/formatters/bbcode.py,sha256=lvG1REZJv0pM6VMe-QwLSuo0Yl1Ra_X51wh93fW8A2k,3299 +pygments/formatters/groff.py,sha256=anF3fNbDYwwOlppOUoKTZg4FzzPbZrE4jcJCMd49_1g,5085 +pygments/formatters/html.py,sha256=qe4P6qIV462HkZovS8s5xKKyYXPQQvUjz6Wj7HX1CxY,36053 +pygments/formatters/img.py,sha256=41uSY0pKg9VmKJYrHuavCVgg0z-mg81p28QEMktwf1o,23304 +pygments/formatters/irc.py,sha256=hdOqAvF02bI8CY8_tutnUptBZpWEBLzVAqS4_YA0tew,4907 +pygments/formatters/latex.py,sha256=cQmE1Nj4E9q5N0XQJBqpc6dLtu5UNOtYq3plPyCX1Hk,19261 +pygments/formatters/other.py,sha256=Hq6qY4POBZ_llAWRr1gzSO9UoeYPONlLMBK60RkW4bg,4989 +pygments/formatters/pangomarkup.py,sha256=L4jU6oO18UqEMqXeN5FkPtkisXrXuXq9wcecgy_6dzo,2209 +pygments/formatters/rtf.py,sha256=YQYW8NTrB4XfOjLbQzutyD4j35TimFNZ0T39dET9DOo,11924 +pygments/formatters/svg.py,sha256=oksXT-ZnTHDsn1WtWgBs9V7qmKFswAKx7jV37tb2tYY,7141 +pygments/formatters/terminal.py,sha256=q7jLLanle33eCZkDr4CoHAN-dHBFf1DBhi4FcBQ8_1E,4629 +pygments/formatters/terminal256.py,sha256=PpA_oATHCih3UTjrbfKqIRUcvsyp_TeBaGw3kpIxeBA,11717 +pygments/lexer.py,sha256=gNMYzmdSkTNyWfqiLJ37oUd1KrN_dMXtsPyaX2-n9EA,35154 +pygments/lexers/__init__.py,sha256=G4dtqE5QMEAqoaaD1rwSZZeuqKhMyS4utlMz1szkrTg,12070 +pygments/lexers/_ada_builtins.py,sha256=dZb-lodsSM6L5emLlgLg-rClm2HumvnHK72CetnSRdA,1546 +pygments/lexers/_asy_builtins.py,sha256=zg54fGhgzWXQUk6-qZ6OW5GTL9d4OrnB8SJQkjrD0xs,27290 +pygments/lexers/_cl_builtins.py,sha256=oBF00ZkJyD14LkYuR603EDFIMyitkze12aT7UzDYLYs,13997 +pygments/lexers/_cocoa_builtins.py,sha256=ab6sq-iy5LapE1cNYyl8PJXLg6EINXx-lMRbwERdKYs,105176 +pygments/lexers/_csound_builtins.py,sha256=wuWiQjmEMhaVvsV8b_efsebgU0YKIoMC0ECpOavTCIM,18417 +pygments/lexers/_css_builtins.py,sha256=qhmC4tRGG53zvzMQE1qn794LvbVODRtVacxWCP3kIkA,12449 +pygments/lexers/_googlesql_builtins.py,sha256=mGfOGuKKjZoHHAAZq6Hpc68x0qKycx-SX_wGDScSOYI,16135 +pygments/lexers/_julia_builtins.py,sha256=u6v0yAzZjqUENjiIQ5qMfcso1r_6ERqGbj3aHwFnyqA,11886 +pygments/lexers/_lasso_builtins.py,sha256=YO_c_f05ZoxspHxCR3oWQCpZhbAJUzBJBhVuuwTjr5c,134513 +pygments/lexers/_lilypond_builtins.py,sha256=-_4i4gpgDgcFHvaZMJL2s1rI9dSNY2ZC6vO6ul7Hml0,115114 +pygments/lexers/_lua_builtins.py,sha256=MsDV9sEbJngKoXSZu0xXawGvd6XjFf125nuXBNoffdU,8111 +pygments/lexers/_luau_builtins.py,sha256=YLUj2bcZ0Cb0SBkIlNXWHFGDUFkpddOJx-ukrZx1cMc,958 +pygments/lexers/_mapping.py,sha256=YbQJB1eqeGk7ol5NgMMPLrD3VGdTwgQCl3TvLwtxwxA,70758 +pygments/lexers/_mql_builtins.py,sha256=CkcvMyHYh4U5rGgV6AgShxRnTOv0tmqEbOiVdpaFLqs,24716 +pygments/lexers/_mysql_builtins.py,sha256=SqiVYVVirtnN4lSgtfeRPxtVpcumjUvvUq9n8JObuQ4,26876 +pygments/lexers/_openedge_builtins.py,sha256=AzF1o6eAkMc6vnvmeVqNnUZUVijzbNYkjzrUdtCQe3M,49401 +pygments/lexers/_php_builtins.py,sha256=9g-WLT3qbSGolQJB_4PS6FW_r7HVXBWvI5OWxKtbwok,108054 +pygments/lexers/_postgres_builtins.py,sha256=QGg2mBTThgv4LdXjFm5wrKaKnCU9DubmJi6lXB7y2Mg,13346 +pygments/lexers/_qlik_builtins.py,sha256=3ZkCSDjtxSxXDI9erigWTLSUct_ap_W0LqfZjFeKQYI,12598 +pygments/lexers/_scheme_builtins.py,sha256=O3BRtt5suTESbZ_bSReQ6D0r0G_Sq_i4wCcUkdUrdS0,32567 +pygments/lexers/_scilab_builtins.py,sha256=E33R0mknNc-5eZ9Ugu7u7Av_vUxAaQoMTtdx3MceyxU,52414 +pygments/lexers/_sourcemod_builtins.py,sha256=8qspLxCLsfN9J7N0SWZRNs0A-qJOIGRBAJaNMeWaOBQ,26780 +pygments/lexers/_sql_builtins.py,sha256=fLphonB6wv6Oit8zanJiExgRoLvZoFW4l9lNH-PTYvw,6770 +pygments/lexers/_stan_builtins.py,sha256=sIoONg4TjLkGN7Ab0tZB1HM7tQ071_Z2c41n9lXZl04,16623 +pygments/lexers/_stata_builtins.py,sha256=ymh8GxEca6eSCgHnqsLqsi9OrfRqYnt5O8yBeinJ7DE,27230 +pygments/lexers/_tsql_builtins.py,sha256=Mw6UnMByju0U-OBgT8af3A4utfrS00Tn5lDnaxBtj9c,15463 +pygments/lexers/_usd_builtins.py,sha256=SGp_ePf2VuktrFV59q6df4mTyxZV7AZYs0TJFSWoBAI,1661 +pygments/lexers/_vbscript_builtins.py,sha256=TFtyc11yvpD_OlNrrFbcCOJoUYkWMq3EeHHCpw_zJNc,4228 +pygments/lexers/_vim_builtins.py,sha256=bB6kdLFM33uoY-sAtsRuUZOoccaa4gDAsdFg4WAEsoU,57069 +pygments/lexers/actionscript.py,sha256=kbHhoDl7JjVzREMv1UuxYBRJaPrxHDkmEAC5COmOIVw,11737 +pygments/lexers/ada.py,sha256=m2O7dYzJVc3iuSgaC-Ti-Gh3CG-JmpZit_DPZpu1IIM,5356 +pygments/lexers/agile.py,sha256=Du-vjnGZEPHuE40XxTMO-XLt56pYcIaKIAxIwcYHFH0,899 +pygments/lexers/algebra.py,sha256=2iQSGxfVuBXBHAJdzbbPvtpvROYTfxfMivDagk9yfh8,10032 +pygments/lexers/ambient.py,sha256=8Nk8WX4qrId9zj_zgjJBuFT40yQXG2OXxFhhF3R00g8,2608 +pygments/lexers/amdgpu.py,sha256=KMDLdb-1aK9RJaNsSkPyiU3DQE_HdjBF0paw_yiJMRA,1726 +pygments/lexers/ampl.py,sha256=IKuMVE6aXsa4S6qpuc_DvEsJmJpyKDNga09BPFLvYxA,4179 +pygments/lexers/apdlexer.py,sha256=puSpLBWevciSc8G0wg2Y_yQIoGgqPes0UBkpFImqvO4,30803 +pygments/lexers/apl.py,sha256=-jhPYsBVT4ckL7CZmukIZNi6tfaJJbBk2vutld372rs,3407 +pygments/lexers/archetype.py,sha256=buhH2WHtxBowy_KD5SPm5vgHpmLoKMdV9tgMs_bE4xU,11577 +pygments/lexers/arrow.py,sha256=e6mb3Ix4jL7TMGNUpk8iRbJy_VF7VY2J0VUknlo1EvA,3567 +pygments/lexers/arturo.py,sha256=GkklOaiEJTOz8E0zdxd_hd1zXwv7lYgp8IJl7uh13hg,11417 +pygments/lexers/asc.py,sha256=CvFSi7FYDW6eu-5oiKGc0MN3aF1XVZDoFI5-3mHpBDk,1696 +pygments/lexers/asm.py,sha256=jHJ3CDtvHgu4hX4kVvvJyciFLWM6FAYFDPG_zjBTyrc,42219 +pygments/lexers/asn1.py,sha256=MLakOeBFkdhKKr42eHFzonpGITEJxR_God8vqjWPva4,4267 +pygments/lexers/automation.py,sha256=gdSYslkW9sblDcvMeK1ay15rb9vRuK56pPOJzdxlIvg,19834 +pygments/lexers/bare.py,sha256=AdqL9Z20nd0bhNGWielLhAHej6V8_8m1OWUkCOrSBx8,3023 +pygments/lexers/basic.py,sha256=NqNxIeHhjBUtenwCdJ88WWEE3N7a16DA9g1_D1mdmF0,27992 +pygments/lexers/bdd.py,sha256=KeuOzXBLJRxxM2gV7aMuYsdV_aJFHyBuF8ptBpWxD5M,1644 +pygments/lexers/berry.py,sha256=vIfT4sBtm6ao64WdNkJTVJmS00BiFdEWvb3obBXBeX8,3212 +pygments/lexers/bibtex.py,sha256=sgfFqXyNyCxROHIe8C0gre69rzbADALnpDtTaqHfSas,4814 +pygments/lexers/blueprint.py,sha256=dW-L5bFIiWxRPDashee2EOYmXIRHdwuCZ1y3iN-a6_0,6191 +pygments/lexers/boa.py,sha256=VqYF1Yg_XdDA5tK_Eiuo1rr7ediBsKUTpB6xza_B5d4,3924 +pygments/lexers/bqn.py,sha256=se4W2XPsNiT36z0Am9X5F4yNLrQvMI5X8miY6R9SEM8,3674 +pygments/lexers/business.py,sha256=MXYomjQiYaTmnUgjQ7LUxjcU1i2iVDjZdXPTeqOlDjM,28348 +pygments/lexers/c_cpp.py,sha256=2ViQoLY22Y6xdzXDmwz30rMcij7EemNq4vpTEsfl7sg,18321 +pygments/lexers/c_like.py,sha256=rafLNQqcEbf-97VpDtSIQZSSmdiMD4oBYeEs7DZyByg,32024 +pygments/lexers/capnproto.py,sha256=kc3rT95GkeQmrW1LeP5HMVb7Nk8sqXkW6OJkkWIHYbA,2177 +pygments/lexers/carbon.py,sha256=_M-0YbofjZMrJIaiXKoT6wpkd3_6fFq-OdSZ7V961I4,3214 +pygments/lexers/cddl.py,sha256=7Wri2q2yKexWo0CD7s2c7AoCAMbrwyEE6f_gz5B6x1Y,5079 +pygments/lexers/chapel.py,sha256=SvZXNJijW0greFToDHZQ0yeo4_3niyTx31Iz_yLctck,5159 +pygments/lexers/clean.py,sha256=_oV3Tbmrpl3S1phPMS6gyMWyGZGj6Wy0q8adpf5vunY,6421 +pygments/lexers/codeql.py,sha256=hAX51uWeG5Zk9JhXSnOxPMaznFOpaX4ZnZHZ2krrPGk,2579 +pygments/lexers/comal.py,sha256=0fcTyV5h36zPlxoQPWWGet2yaYi5ESXiFpx6EbsqP00,3182 +pygments/lexers/compiled.py,sha256=PRimy7eb2weP7X6bEukz1JaSLuQAETO34K7maHLGbxA,1429 +pygments/lexers/configs.py,sha256=YDBIWV_R2SiRb0Cbxol-S8RRieDfAg_YcThc1BmZ90I,50925 +pygments/lexers/console.py,sha256=MrTIkXDYQ71z0yDepwbpO9WwXPQRPHAFAOvt_ngscxU,4183 +pygments/lexers/cplint.py,sha256=wPs_Zh0OJ2_UUeF9_mu_r-X_65DJSWMCTKOmOyjWLz8,1392 +pygments/lexers/crystal.py,sha256=k8Xy8TpfGKTPvsP1WikxILScILD8iXZ5qceLZopsLYE,15757 +pygments/lexers/csound.py,sha256=7KHoJv8wtuIe9dques4mFFkQLULcGQ23dgZ_KBd0f_k,17001 +pygments/lexers/css.py,sha256=OVjsd0Kn9pNH6iltS4ikjy9nnilPlCVx-VAZ26n3UJ8,26439 +pygments/lexers/d.py,sha256=ee5oImP1BcHYZ93UwXoHIzw6BztgR3WCCv3ksu2J-o0,9923 +pygments/lexers/dalvik.py,sha256=0f5mB5y8g0l1ID2liJ_5qM2NjyR_SBs7ckuIDeNbbj8,4609 +pygments/lexers/data.py,sha256=MhLXoZXYMZFmmKlqL8RV5zp-eUwVb0yFzsyzXYrpKiw,27049 +pygments/lexers/dax.py,sha256=N-5fxukBfFtFCT2LJoyg1VJsfldWHxXKLY3ZPLnPrig,8101 +pygments/lexers/devicetree.py,sha256=Tig-tTSrpmGzbGxN2ka4MMTiC344roav9A4hoc9o_kw,4826 +pygments/lexers/diff.py,sha256=-627lmuYpJ2uUVUM_orVGgXJGXIBGSVHbmlqflCEeaM,5385 +pygments/lexers/dns.py,sha256=ElzSN3IEoRs1g6iWMnzVIV8H0x9WSEF1wBjm2a0ByxE,3894 +pygments/lexers/dotnet.py,sha256=GmoaOxSoITkYdAUaVJ0Z1RQa6MvcRxh1gQ3XxgEs4X8,39444 +pygments/lexers/dsls.py,sha256=a6Jdv3w1aF3vpQJrnF32MNHAl6pEZkkeGB1cYv-x2Hw,36753 +pygments/lexers/dylan.py,sha256=fi8mSyni4dnGP5x1crE6BKNqLWJs4dDWdmkur-TbbNc,10412 +pygments/lexers/ecl.py,sha256=XJ66PU9EjkXJwA4f1OfC0RdfP49v8N62RZXnFVVAYNo,6374 +pygments/lexers/eiffel.py,sha256=m2eVPDG9J-VXieCeOy0S7zHPjiFHQB9F6bOIy-K3MDQ,2693 +pygments/lexers/elm.py,sha256=Xj3HlbUTEkEUFwhem4NCujx55tQ2TS61_Kcf6BCPHAI,3155 +pygments/lexers/elpi.py,sha256=RZqAVzdxfA_BqBmtMHj8ATw5f9e24SKL-QeO14QuKk4,7904 +pygments/lexers/email.py,sha256=NPlGc2L6F3KWWDop3ZOJGpUM6LDnOEqKZj1YJG3vP2M,4807 +pygments/lexers/erlang.py,sha256=2aUlVBVFtQdR2QISsDeKTOPjGrV7r1ckyXL17KJot9c,19150 +pygments/lexers/esoteric.py,sha256=tmJ9Pa1wYtYgR6yDqgp3zDYuXPFzXw7SLTHOH_eE3p0,10503 +pygments/lexers/ezhil.py,sha256=tbGYtGnqZAB8ffnyPdeMMJHtKYJ7OAciUFRrR_8QpXM,3275 +pygments/lexers/factor.py,sha256=HBUqNcwd4VplYw30ScuC8YgdipFAYiqjRgWsyT0s4xQ,19533 +pygments/lexers/fantom.py,sha256=w9IU1x61Codd7Oxv5DpvVlkVPn_bfvVLiP8Ae3660zM,10234 +pygments/lexers/felix.py,sha256=DvPOs_Em6fFtUBQCaZWWHWgnvAFeBrDe97Cy9XyQ8ps,9658 +pygments/lexers/fift.py,sha256=Q9OLJivp4ngqFso7APCvX85h-ghdbp9ZqpP8GKHws1M,1647 +pygments/lexers/floscript.py,sha256=TcyuCiv9bAb89x1_7_Zq6nUhsdsHAF2DPsKZ6m1W1NU,2670 +pygments/lexers/forth.py,sha256=4e0OCfJGcElkQrHInmQMucvsI9dljuPx4FYg38dyjno,7196 +pygments/lexers/fortran.py,sha256=ouvOHdMbj56j40Nrj21WCRf4rNKpuwphxmtlUVsnzhc,10385 +pygments/lexers/foxpro.py,sha256=nV8yyES39QoFmTNlM8xl3MteOsvmt6yV1l6x6xnXqIU,26298 +pygments/lexers/freefem.py,sha256=ZmZLIYLXtCK2kWqGzBmrkp7ychOy7vgfaorafUkUl1I,26916 +pygments/lexers/func.py,sha256=ZjOerj3njJcaGnHWc_npWjxb4D5O-udxBRK7RLHEibU,3703 +pygments/lexers/functional.py,sha256=4m9JlAnfpXxSkQQIrNOob2VTwin6hPLAqWuhv9aVkAg,697 +pygments/lexers/futhark.py,sha256=mCU2yvloJF5LN7uRE_8rCVqA8HAINny04HXVxjSiM4g,3746 +pygments/lexers/gcodelexer.py,sha256=t8JMWaaDjWZwdGiRGwrpsYtfjKTmibwlRyOLCiJZ8qw,877 +pygments/lexers/gdscript.py,sha256=VNOwd7KcDREvoBAfw66GjxxQygPsHH2akdzRbPNKqoQ,7569 +pygments/lexers/gleam.py,sha256=Jx6LnUpWV9pmjdSYLUMt16c3UamZ-v5X6CcuEhWxf-8,2395 +pygments/lexers/go.py,sha256=ynHVStw2XUJFlUiTS4UqY65fLkvcJBMNzoYtSNSl_BU,3786 +pygments/lexers/grammar_notation.py,sha256=ZGYcwhSvOolw1J7cA9Pc7hVpcIselaCgxwsvAf5yRgc,8046 +pygments/lexers/graph.py,sha256=X_IsdjxXZn5p9Dq4cLXyqwo4C8emcYA8NP6VZ9ZTBaU,4111 +pygments/lexers/graphics.py,sha256=IeruxvEQR1Wsu-YyLpAjkUnisLDzUrk2cd0xXrDCbjw,39148 +pygments/lexers/graphql.py,sha256=RWE_kfVUDEAnv_-SYyOi9IJUks61NvOakmUfWOlorzo,5604 +pygments/lexers/graphviz.py,sha256=DRNWwetEWasJDtiLkdqkKsQHDryJ5W80wUD2PE6oKCs,1937 +pygments/lexers/gsql.py,sha256=pzSj5Pd4vtNLES-pIdxaG0vOVTqUWE9UuOUw0RrY-oc,3993 +pygments/lexers/hare.py,sha256=eCrRPwcswXL-ZHTWNlhE1A05G9rxzS20_IqSddv2M9Y,2652 +pygments/lexers/haskell.py,sha256=sr5gq3U7xt0D2Bi41xdTsBaponX6otP1qPyRMCuxM7E,33323 +pygments/lexers/haxe.py,sha256=_MzQJTWx18kD9bP_sniBshKbtOZDRm4OZnifaOgw_aY,31169 +pygments/lexers/hdl.py,sha256=2jYX3fRWZ9mAZ_QER1WH58SvJaOdYQt0OmOxo3X2JK0,22741 +pygments/lexers/hexdump.py,sha256=VvYp_NTaE-6NUuSG4FMezwcjxbPnAz0fOWMzr3Y2Gm4,3656 +pygments/lexers/html.py,sha256=O6qkpyOylH2n1Zi1qzlmBsIEW31RfvzBn7jKTaAR2LQ,21999 +pygments/lexers/idl.py,sha256=W2QRH1j9LMaNvC5jb6MkP4dLYo_OQU6lQlCAlQveFH4,15452 +pygments/lexers/igor.py,sha256=Sv5EGBJeKy0UOyl6-GF3lBeeoWArVcMDJtQtl2RXRYo,31636 +pygments/lexers/inferno.py,sha256=cjVMyOg6jhJ6nYX333Z6KaCCeS0pNVHv4WTxyFGY8Tg,3138 +pygments/lexers/installers.py,sha256=ioK2JUPSJk9vcsoe3NASvMt-paXd1_Nn3e8WmaqJ7CE,14494 +pygments/lexers/int_fiction.py,sha256=BUjvXilUiuMF-euXEQDzQZs45jn_aLs17eTKcfEptrI,56547 +pygments/lexers/iolang.py,sha256=kMUBUqVvdHlB79ez0pFZPRf0NgiEC2SdtYUwdjnbE90,1908 +pygments/lexers/j.py,sha256=56qUbs1C7wKdZ6-WpNnxHntNqsxWroqiO7keI2yoR1o,4856 +pygments/lexers/javascript.py,sha256=muyCVZQAsXbhT7IHZkoqAkI5myhRaHze6JMAR4zILNs,63246 +pygments/lexers/jmespath.py,sha256=726PDpRr1g2Ky_x5pZORhbXofXxkfr5HXQ8eHMhX5xg,2085 +pygments/lexers/jslt.py,sha256=UOVw1J3uK-hVRWomoFK2jMS_yeuo5iqga-LhXUJI660,3703 +pygments/lexers/json5.py,sha256=GyeZ58AxKkJJiiH7JuPmakDZXropa6bDkFRguJHifqU,2505 +pygments/lexers/jsonnet.py,sha256=_T7Y16sW7nFuAoGFuyWUKCrUyJAC4FTWZVoeSt0mTXk,5639 +pygments/lexers/jsx.py,sha256=O0evHioyudtWLhVmAXRrIfkEd5KRiokgHu8l9_Kn8sM,2696 +pygments/lexers/julia.py,sha256=F0twMg0iLBNDdedsoJG4Rn5LoMciVu9cufyUVdxKQPY,11713 +pygments/lexers/jvm.py,sha256=cf5V7NOLIvB2s7RM9wghESYRCoCDyrGjRuPmY8bfGAw,72936 +pygments/lexers/kuin.py,sha256=n2lMina8SskpfA2KbPYQFwVJOSHgNYu10U5pz5YiajE,11408 +pygments/lexers/kusto.py,sha256=pyUfWLf9Q0Qwqu74DyPAbRh20lkPDLhxnExD1FVl8Z4,3480 +pygments/lexers/ldap.py,sha256=_ya4_InnSIRj-RYjGagWFZJDy1NdgQUocQzVWjiRp68,6554 +pygments/lexers/lean.py,sha256=qP_iwUQ7MJZ1C5s3KYBW8Wu6-uuVj4PAG3a2Ghjp99U,8588 +pygments/lexers/lilypond.py,sha256=o264ovGZFrcx8ZwpKRX-gjUECK1w5T8T0n6LoTjP92U,9755 +pygments/lexers/lisp.py,sha256=a5MGcmtrWTaeEj_5Uv8YMODr3dVV6s1v1_SMBh_Ch9Q,157903 +pygments/lexers/macaulay2.py,sha256=hLNDs1TdubudvqqovYN6a4Ne9JjnU4FvNE6EE2hPn8Q,34139 +pygments/lexers/make.py,sha256=wAV0KRRXTAFMliLfAKXDihQIE2VAVUvWjFZ-yuM7a08,7834 +pygments/lexers/maple.py,sha256=OBjODNLgqanyw522CE7n9thZctWi47Uua2hPBfj7KF4,7963 +pygments/lexers/markup.py,sha256=4TMRfujXvOE9xiWwTiT_fq1lau4dKSoXYac1hWMLYRs,65264 +pygments/lexers/math.py,sha256=Nspl6IZtCyh9egiYUahboWSmy4cJCVZUBxyVnCuF1ag,698 +pygments/lexers/matlab.py,sha256=QYVBdA-IRNcuWggBJNC9lpq3jgX_1w9GQwpv907VfZM,133030 +pygments/lexers/maxima.py,sha256=ha-f-JzGkjggAr5kxmVq7_uJTCcpHstQWnsLHYIhNwU,2718 +pygments/lexers/meson.py,sha256=ZpNVp7lSHwJHEj7pwXlGcy9Lftdc6MMgJiOoY2TnfM8,4345 +pygments/lexers/mime.py,sha256=l5BsFkad3agJ6KDg_IHI6nU0Ao0Y9EcwjYoYP8vQD-g,7585 +pygments/lexers/minecraft.py,sha256=bBbMbqvsVgTfO7CBxf03oGP_qOAgrw06PSzH1QF7txs,13701 +pygments/lexers/mips.py,sha256=QpgBoMPzsk1t90cIF0W_EV-QZ8LwgTapS-fSISxZcFc,4659 +pygments/lexers/ml.py,sha256=shDPgWARTzTEhGDeblV7ZW2j6yZEeBiYqx2z24SDP7A,35393 +pygments/lexers/modeling.py,sha256=2_ucVFy7Z4yoKx90E9-ASI9t4t1LIoeQ3yHarhghz-c,13764 +pygments/lexers/modula2.py,sha256=dXW0KNFu3ETgABsPMsIhdJI0DKF-KWtT831FKsz2VXw,53075 +pygments/lexers/mojo.py,sha256=imOlg8mQCoKwi_rsuMiifZrzsAsSqT2KckCs_MV5uVE,24236 +pygments/lexers/monte.py,sha256=JTx-jSrFKlWRwA1qzhPd647ETGSElwhP4YMlVrfXyX8,6292 +pygments/lexers/mosel.py,sha256=HEYOmZiANhe9VdkTpBt7mhPi_tPGBUkVsnzTh2HVdVI,9300 +pygments/lexers/ncl.py,sha256=RZXkxOK-iHspLX_sFVT0wc5H6v-pa4kKghP79fJGQPQ,64002 +pygments/lexers/nimrod.py,sha256=L8Ww5CUaP-ojywoQZXnjC08m5Be64fBynSf5aWGIaPw,6416 +pygments/lexers/nit.py,sha256=xwu2P49Hu8YxEL7mgpV5WWFw6OYF1iJg9DVnc7H1SXk,2728 +pygments/lexers/nix.py,sha256=M2E-k--F7pneuMAOae9ag4srJsKhWJ1f1fRueO6PtKM,4424 +pygments/lexers/numbair.py,sha256=KZOw96Tj7Gly-f9F8NA-tdGd53SIt5UgbpokZsfnzWM,1761 +pygments/lexers/oberon.py,sha256=uH1FkPeXCfdd0IQ_--S8SHUNaf2dnjiQZjtIT-jxu4A,4216 +pygments/lexers/objective.py,sha256=KME-J0UL2HJYAk2fHNhf7ApQe4XF8GH6qGOvYA1wPS4,23300 +pygments/lexers/ooc.py,sha256=HEjWHdQDVk7tRb_TuEb1_C5qi-peJwwyYBVAhf49MS0,3005 +pygments/lexers/openscad.py,sha256=te2iL8VkfXul_PYXlz8UK3_QtMjdmNgt0YHCYEvdEtM,3703 +pygments/lexers/other.py,sha256=OAlXzsrVDUx4Ma25fyG98U5LaBEHyt-LJZ2IHvMJJWY,1766 +pygments/lexers/parasail.py,sha256=oPcs7fRYNkV0isPSRmw-2cPfMgzTtNPi6bJOm7vq-9o,2722 +pygments/lexers/parsers.py,sha256=g4tVvf36yhT_aH_b737o0o1joojcf-XckD2HdNoNbsQ,26598 +pygments/lexers/pascal.py,sha256=4dewXkwc12f_iiMfdNDqbxb_oqAEX2dCzb5VIZ62V54,30992 +pygments/lexers/pawn.py,sha256=adsa-7sPuPk7mO7lB08auabEpeZdNewVqTBg6FS8mCk,8256 +pygments/lexers/pddl.py,sha256=a8A2keCF9qNQvVZQirgBV5fH4u3cjrta8-eZJ8tsxmY,2992 +pygments/lexers/perl.py,sha256=uYMj6amZPawLf-KjICg9LLU5-ADKzqEYKqi7VXnvm0k,39195 +pygments/lexers/phix.py,sha256=nEWWt5-OoIDapw4IJ_cNL19Th8Ztt6OIQgeuogVed10,23252 +pygments/lexers/php.py,sha256=U6wmxPM-pS-SY1N3qdv0ebUDBxLH6-EFIFcatUvg74w,13171 +pygments/lexers/pointless.py,sha256=gNcuhhOY9cEpIPyGFjMh2DItNB1UD8CmjceXkDRModE,1977 +pygments/lexers/pony.py,sha256=HKqf5AngUOdXN1N1VvSg0jTj3NUAzGBOPdXwSFamvmM,3282 +pygments/lexers/praat.py,sha256=sfVAd7zfRsI-TcCouuYMWlcU8EBxAwJ4wkh0ZuNsZ34,12679 +pygments/lexers/procfile.py,sha256=fhRTtscyMMPcPbvzjxVtXix1mYlpO-4JuWUL7Db4ENI,1158 +pygments/lexers/prolog.py,sha256=4YvPZbAFLWNZMbZhvtIulWib0PQLA_TB93gjkNywFRA,12869 +pygments/lexers/promql.py,sha256=04fS_R6HBWhpKNe3WPp_QXgIdFvJr5p6KxStZYw7yHU,4741 +pygments/lexers/prql.py,sha256=XJhd8dpEWPBIKmQXx_09-z1NyotLXIskpuzX36HykHo,8750 +pygments/lexers/ptx.py,sha256=dEaNSReAjAymIwVM_MnbdY4hd3muK79q200hYwN72cw,4504 +pygments/lexers/python.py,sha256=79A_yJqjVHp_ZeS1rY8Pcc4cwZ_7-zI2WWkeUmZgUrA,54202 +pygments/lexers/q.py,sha256=2CbJYgRu8uz8wOSp5FMr086KCC7IjSRmIODVY0uKrCA,6939 +pygments/lexers/qlik.py,sha256=9b6Q-6jXeeraIRcWtsKsYWCOlBhfmjNzIN49pUvMR-I,3696 +pygments/lexers/qvt.py,sha256=rpT5oD4awEKMs3uBCbNlzY1zCz7AVeqM-BikLTQHo8k,6106 +pygments/lexers/r.py,sha256=hzgUUH9gCsqqwTG9eCt1JVMd9RW0dy6IY_jTvRntF-U,6477 +pygments/lexers/rdf.py,sha256=FM154fB1lxfOpSpeW6bWOVld5kJBgEuYyT9udt-EcK0,16063 +pygments/lexers/rebol.py,sha256=CQ3pMaAz64UMqiQVrYoREnOgjU5QPeF_H3aaHNdVRC8,18262 +pygments/lexers/rego.py,sha256=Yi0G4secTWOL7CUhLLCzJ9gA6SJzDJpPJNij3hXixC0,1751 +pygments/lexers/rell.py,sha256=0gZStI953aFjFMyMVX_UKfwZmzV3uj3Tnl_5F20BPx0,2487 +pygments/lexers/resource.py,sha256=RDEY7iSv2hgQ3V-I1DeOKVOumNKgFqbV2Me_9Y21o10,2930 +pygments/lexers/ride.py,sha256=1zg7kGYPKRIekyuZY3f6OVqRlXyrX-sR-tHEX1M9nz4,5038 +pygments/lexers/rita.py,sha256=fCNElPik6dDIZzGd96kKgdlY4Qqp_zVI87waTBHMBfg,1130 +pygments/lexers/rnc.py,sha256=PNfnnTlnZjNvvG33BNMCqPzdA9LZEYvDsGoaDd_2mn8,1975 +pygments/lexers/roboconf.py,sha256=l6BeJIS-ZAUb3zc5GEuVIb0edrRpPFmKdffEBx74Zxg,2077 +pygments/lexers/robotframework.py,sha256=8s1U7GldhzRPGR7d8_rMSG0ZJz9ZOm-LGqBTGILwt2U,18451 +pygments/lexers/ruby.py,sha256=BxndG-3gLg7wEGvICiNDAMygQR9Qs0xrmq7DwfoLvMs,22756 +pygments/lexers/rust.py,sha256=7qD3KGVir-tUWf_bPBt4EX8l9aIWe8TblMCFI75oorc,8263 +pygments/lexers/sas.py,sha256=9hmGYhmKo7ri9oCmZdsoEFMz6z3AwKD4LcTUIn52lYU,9459 +pygments/lexers/savi.py,sha256=HCllgBe3rzP_7-2b1hSUEbE2dYz08Iysxc6Dcxj3f2c,4881 +pygments/lexers/scdoc.py,sha256=9n64S-bO1dI4x-2Kzh2jrDM6gEmB5YI7ch6ndPB4-cw,2527 +pygments/lexers/scripting.py,sha256=c-i_cMFhYEHQZZbLgJn0jyiD52vWeZVxUK6wFV2Fib4,82959 +pygments/lexers/sgf.py,sha256=ya_sG4TOvDWwzEM6cIi40XSbhkTuHCqvo-uHnNYfi5Q,1988 +pygments/lexers/shell.py,sha256=si6MAn6S7pHfyX65NkwQdjxtLtEu1M4l7K2lUHvyzWw,36384 +pygments/lexers/sieve.py,sha256=CXR9S1nGeVTln2u5R600hxgkAmJFVVTTTBh_k3aw-pk,2517 +pygments/lexers/slash.py,sha256=molh5sNG8UtheHffQGjjHLxVtyg2oCp4B8cvgDyf004,8487 +pygments/lexers/smalltalk.py,sha256=q52NHegl3pjn1jMkJWA9J0nOA_A5VzUULZAPgb-6uNU,7207 +pygments/lexers/smithy.py,sha256=hqEImo4B-i0hddNo1r0k5eHLOlArtRW4W5PsLBdllk4,2662 +pygments/lexers/smv.py,sha256=D51In9Qr2nWFicYAOX_bJpeFgmJ3BUkEXnvXPmA04D4,2808 +pygments/lexers/snobol.py,sha256=BX_1VPUZi-ckKCYlF2sttQprF2bLUHOi8blhInFHv9s,2781 +pygments/lexers/solidity.py,sha256=pz0DZ0xiHwufhbQ9hdCPrHMjA7NMsa6hxZ1nO-A4Y3E,3166 +pygments/lexers/soong.py,sha256=TVqBJzJxLwCEDmb5Aix2_AVuZILYyPWcAzFbzINNQAM,2342 +pygments/lexers/sophia.py,sha256=MAkWLYxhHJNcc3UUzWD3VDLzVRbf6IAd2uz4q7DY3wI,3379 +pygments/lexers/special.py,sha256=8gpTiLICFNwIcahm282mi8Nqi_6gwYMsAfjJIM2Bq6k,3588 +pygments/lexers/spice.py,sha256=UcrjK2KJDDKSEgEOeLXTayqRwirWdsywrfH9MqwPPIY,2801 +pygments/lexers/sql.py,sha256=zz_TFZtf5R29cvkBFG1PDJ-0KKrY7FjA-RDky-gfNlk,41656 +pygments/lexers/srcinfo.py,sha256=MHj02VB7WP3AcoOz1WVYAqNM_3oFLjEaAda075kXVBU,1749 +pygments/lexers/stata.py,sha256=KojmkxWHlEk-HvmxI313a52nzH_JHNqfv6r3jJoFDy8,6418 +pygments/lexers/supercollider.py,sha256=H-qwP6sUaotemsXxuugpui6KnU3jtIdDfS3bGt3Jvh4,3700 +pygments/lexers/tablegen.py,sha256=ryuzw-ArLdvlY55YJBn6ebOzDo3L8UnbR5xcZB8xMso,4012 +pygments/lexers/tact.py,sha256=YbOWYNp302ZBPM5affzM4-CudJ4xRZyJb-_vSDCozCo,10812 +pygments/lexers/tal.py,sha256=xZYmhv8mBr-A3oeoFkcSP7nDbX1r21kKOnhsLTtL3bE,2907 +pygments/lexers/tcl.py,sha256=MXMAo2wCZeq5oR7VzsNbamawun_vMOk2gOEDnGk6yOc,5515 +pygments/lexers/teal.py,sha256=pgpPi9xWPumSr-heSpGBGDcU5s3qN0aFUj2maHLE3MM,3525 +pygments/lexers/templates.py,sha256=ndJMdue33qQ_thsortAra1fm_-szMN40S5bZXBLY54w,75734 +pygments/lexers/teraterm.py,sha256=YCdvILRq-FdOJ_HfiIkYVToH3c6I2QRbQoH17Q49Hx8,10045 +pygments/lexers/testing.py,sha256=GF5SpanGwjgKKoYari32SdtdOWWXXje8xVX5-ZzTjLg,10813 +pygments/lexers/text.py,sha256=yq6mOLz3PizKNMm4_Y8UHn9vEeBfqEY5W-3M7dk1jYs,1071 +pygments/lexers/textedit.py,sha256=V-Ijh0eULTWtx3S_6vT7JcyjQO-CNirpAbEpkczLGCE,7763 +pygments/lexers/textfmts.py,sha256=WSiJDzNKCcOsBoTU4NCYU33ti5ZHGi0Px29eGjT6pB0,15527 +pygments/lexers/theorem.py,sha256=c-eg6tIYWUbxSYgrOtLZfFxro4g91JcAIwrIJGLPooU,17903 +pygments/lexers/thingsdb.py,sha256=FNKjArS1vadHEN3ZXaZdDLqvcG59GbF6Ak5M8RMsTLU,6257 +pygments/lexers/tlb.py,sha256=vWsFq_MrIrzgG2q-qdzpcoNiv9KkNow4x9ebs9_HAwA,1453 +pygments/lexers/tls.py,sha256=GZ1lvZ8PUk1-LThq1KuiqSEnMqJVqiHo_A-16aKuJOc,1543 +pygments/lexers/tnt.py,sha256=MNYTfkix0x5L1pDV8V8zGLS7GU8z7bt-wpiBap2W09U,10459 +pygments/lexers/trafficscript.py,sha256=1Tawom6bKJM-u1kYey21xFaHep3D9kl62rV3RaHSvcI,1509 +pygments/lexers/typoscript.py,sha256=dWAuOYYk0X4xE_fxKgcXz-sMYlG2wxQR4m_d7PW2PYs,8335 +pygments/lexers/typst.py,sha256=ZtV37NCQCoSqEKrAhffuZtUp1kDahKQx8PxMLrR2G6k,7170 +pygments/lexers/ul4.py,sha256=joM-US0-2BEWRHHK6eyZ-Y--306YgbMrevMY0hg8QTY,10502 +pygments/lexers/unicon.py,sha256=9D-GilKOqIEaowGHmdvHRjhqyigRk320ARAr-G_snew,18628 +pygments/lexers/urbi.py,sha256=j87k6fe60kZdbJ6AJA4g9E0no1EP0bxJY06UVP0YgnU,6085 +pygments/lexers/usd.py,sha256=tP9kHPZUJcE_flPvKEsF7wTck4FuQW75S8ELh4IYJEI,3307 +pygments/lexers/varnish.py,sha256=INCtYbol1yV_NXMh51CMDZDacEHgiB3SmR3H5uW-Fkc,7476 +pygments/lexers/verification.py,sha256=QISJmmz7cmYaXZphW5so4PfdKJo0rdZWuj_de64mafk,3937 +pygments/lexers/verifpal.py,sha256=0iZTQWawF9f7lFZnFNPTwYRiu6L_25PtEI83tuCAmKY,2664 +pygments/lexers/vip.py,sha256=pM5xFoeu-2GPWqAVzZJe6xPYr6WJPlozVI5084SHQ80,5714 +pygments/lexers/vyper.py,sha256=rw2yD9c2L7yPtXqKPd0RdxJSZK-aJbW5jEa0gHfKEO0,5618 +pygments/lexers/web.py,sha256=YMrxoHlKlQwfFJlae9_T9F1Vp03YA7IWsmgqLzh6pgI,916 +pygments/lexers/webassembly.py,sha256=Y48VdBp8b4SI10PEt8biOm-gteYgmNc_bRR-KvulfnQ,5701 +pygments/lexers/webidl.py,sha256=MQMaZFskluB29lMGpiGP2f6fzoja4vV8FtoCGFQH6fM,10519 +pygments/lexers/webmisc.py,sha256=GnSTSHfsAUGy5DYF_qwFmMsyabnzTstaEG96Fub53fk,40567 +pygments/lexers/wgsl.py,sha256=vgDtY_q42TGRvcsyS7viSadU8D3eHvatfoCNNnEP0zE,11883 +pygments/lexers/whiley.py,sha256=MC2V7o1s7LIoLy0Fk-i8OymBoN0dpK2BPjIrdQwsnVk,4020 +pygments/lexers/wowtoc.py,sha256=XnLSSX7p_RkwZIzlmYXO873d4byX9-A3dfvzNL5Eocw,4079 +pygments/lexers/wren.py,sha256=2lhzwpS27xW6nERY_NeODgNN9X6mB7vl3odXSPngqY8,3232 +pygments/lexers/x10.py,sha256=77pAyohtv9PFwPvJPGgq-Vw8tEXo9Sny1MnTchFpTTI,1946 +pygments/lexers/xorg.py,sha256=LUKU91t1Opc3W_F64UGzx0nB-HqKlN3Cn_6tFzt5IYw,928 +pygments/lexers/yang.py,sha256=yIvwteHWBWL2-8zZscnCPJOnq3rMOXh5lxJujywA_Zo,4502 +pygments/lexers/yara.py,sha256=_ISzhko7v9AhJ-1cWINZSyG3my2vJOyFSHxSjm4VdTY,2430 +pygments/lexers/zig.py,sha256=q0tuplpRFAUSS29hg_XiRH22Ctve2EFAT5xo7TNAlrk,3975 +pygments/modeline.py,sha256=me8g5rySidvPtMBOrDy2O1sMqZd02lBHMY5KDZefUws,1008 +pygments/plugin.py,sha256=P6zIw-vkSQ0k1WKHrzbBTjpwlCZPbR-6Qe_dO9Bjdu0,1928 +pygments/regexopt.py,sha256=d2hTvazlow5zzZIOCVnfeEG2CY0GrY_igH1kCSSf7ow,3308 +pygments/scanner.py,sha256=DtoLi1pOKpNu-6jiakJpSQLUi_ep29SQQhv7oAwYWME,3095 +pygments/sphinxext.py,sha256=qmiWv5b7qq6bNUT2Y2wZejV2ImQmvtzGJFg6LcGENl0,7901 +pygments/style.py,sha256=Hrie373bgWU81ZNwVjZM-GNBoxBxVe1W1Tr3MzJLdkY,6411 +pygments/styles/__init__.py,sha256=2vgGKnbyt0nf_CSDEtMhHxppPSAPr2jafzl5FiXFuGs,2009 +pygments/styles/_mapping.py,sha256=6lovFUE29tz6EsV3XYY4hgozJ7q1JL7cfO3UOlgnS8w,3312 +pygments/styles/abap.py,sha256=T7Ad121Kjz8ZbuphyhulULwXyvXubSS6Czc0MDOFiJ8,752 +pygments/styles/algol.py,sha256=6v6ZXLxPJsm_1qPqOu5ihAuWhVk644NdPXPcZqiOTTI,2265 +pygments/styles/algol_nu.py,sha256=581Lf5db303g7zDb7z-VlvGlFNpbXMIlAnrRBxtjGts,2286 +pygments/styles/arduino.py,sha256=oFF5gjfbQNBigKxFhC7giUIHWl0ieCshhsm9UxCIzqg,4560 +pygments/styles/autumn.py,sha256=P9IX5utjDvdJgevM0kNGwE-wvVysRh1lgMkjO1dhDDE,2198 +pygments/styles/borland.py,sha256=V8qTD8SrS0wLohp2udIirlijbHfPoIlWZkYpqxSdkSw,1614 +pygments/styles/bw.py,sha256=e4Fo6Kyax2aRhqKsjsqvi2CW6lkU9QnjKoPrESZAj-I,1409 +pygments/styles/coffee.py,sha256=0jxdctCEKbR1AqzA59fFQK4sVBdErrRkGCUn6fPF4sg,2311 +pygments/styles/colorful.py,sha256=opkfOcjFTtDn9Fty6ogM7okFMB6bA0iR0VtTdBPE9NE,2835 +pygments/styles/default.py,sha256=WFLDucKJS_ac5Jqutmglbz4ITc4jTTQEOnqLIMpSttA,2591 +pygments/styles/dracula.py,sha256=H-EM1WM3Ixd2OBY9bzoObKl4IzdmhirfDVFbJ9Wsi0Q,2185 +pygments/styles/emacs.py,sha256=iEYWPgQrDQk4RdDf8-g7j4mtoFRzo2xcKvZE1ys9mJU,2538 +pygments/styles/friendly.py,sha256=S43XMczW53tKftmarEL4-nTroPVdN6k8fbNrINzR_QU,2607 +pygments/styles/friendly_grayscale.py,sha256=el2E804DEkKZ6ApXt3N5FTmHAkIhqkJj-omhE1E8mdE,2831 +pygments/styles/fruity.py,sha256=mrNDixp39QIoitRBo-yMOr8tnYLAwGd0EIMw2A6Gd8g,1327 +pygments/styles/gh_dark.py,sha256=yb4EOBAYsQ9g5IGEOqyZ5LKI-MUbwz32l9WB5EcMLaA,3593 +pygments/styles/gruvbox.py,sha256=_QZtRq9y1s3NYADHqek-DXELQqueOnVDXvWJ_1aTGgo,3390 +pygments/styles/igor.py,sha256=QO_M5-Z1xIxVnusEU4LZr4VT--nTERVt-cpfbnOv4yk,740 +pygments/styles/inkpot.py,sha256=bJvNWikqnAWLjx2AH4t_tW4jR0TsrUyVFXFVPAfOgkM,2407 +pygments/styles/lightbulb.py,sha256=CnDv1mF1X5k1msVvRS_diR4tkp06E_jg0tK9cdHEFCQ,3175 +pygments/styles/lilypond.py,sha256=gSCiazPPWfR_9-BV5BYHe6-2gSor4vOHnz3nyFQI2QA,2069 +pygments/styles/lovelace.py,sha256=vZ-S9tS-QUOwfA0RHYBhSTRE3h1ZU-bZnuMW7O6ksyw,3181 +pygments/styles/manni.py,sha256=dpsJC1Zees0e9IcWirqWuSzQxwD0jIbjhFquM23dmWM,2446 +pygments/styles/material.py,sha256=eyyAaJgp1Fnjl4-_D39_l1wH5CW4W0VWScQ3Q0jtAgU,4204 +pygments/styles/monokai.py,sha256=3CwJJm_YVGibzFi8nhK_b5d4cNzgV8z-L6RzC1UVA7Q,5187 +pygments/styles/murphy.py,sha256=HsPd80nObb3Ov3-7yJQbnJr3z9mCyz30Pjn_nkWkMYo,2808 +pygments/styles/native.py,sha256=oazEJUbuaHqqOZR5vx-jIhTPIw7dC6QZnKxW-xLr7fs,2046 +pygments/styles/nord.py,sha256=t7fZj04LUgsLaHpio66FqOGMKb7a2VC3teUlmInPfRg,5394 +pygments/styles/onedark.py,sha256=UXrMzVvA9OVP7OhCQ5gL0CaZrk5jo_I84HJm7LEN5GQ,2126 +pygments/styles/paraiso_dark.py,sha256=rx5_j4gnZRy2j-3hAhFmWpnjnd-Vfz3Z7fcwEXRUGm0,5665 +pygments/styles/paraiso_light.py,sha256=iERjbYRemqgsisv_9TjanBig4bJ_-80xRz5B5puhMc0,5671 +pygments/styles/pastie.py,sha256=mnKebFiJ2do9A_wTPq4tKtgw-TQqpb0lP3kNbemQ2to,2528 +pygments/styles/perldoc.py,sha256=-e1T4QBEQNE_l17RYW-WBowNprg2uL_hFjhSDcaMiDM,2233 +pygments/styles/rainbow_dash.py,sha256=YP2HPVXJLmOy8Yz8p8u5GbkAyuKxLlNmu6ITZBMa1Mc,2393 +pygments/styles/rrt.py,sha256=vs4pnWwmMIQR0eGczp06AYazsYiR0KFcRiRUzSf3rXc,1295 +pygments/styles/sas.py,sha256=ER-JjZgU2wEJWk5K06iHAFPjVYD1iMkvjjE0rlNOt2Y,1443 +pygments/styles/solarized.py,sha256=VdtLYhQ7xDj_iTHP_Ekr6rAdMcE3fRSrglngVSal63M,4250 +pygments/styles/staroffice.py,sha256=peExkIBqPkRhqXFpW99n49t6pMwehI_pDYVUmQwkgdA,834 +pygments/styles/stata_dark.py,sha256=N6imWykqHoT5drOn-sVRUzt1AZMKG3HHU-Q7LKz2d50,1260 +pygments/styles/stata_light.py,sha256=YW7ih4teXoEuUHImOK2rGSCVxfnj43tlcKPXUsrDkRg,1292 +pygments/styles/tango.py,sha256=Af0WVTxpH_cUdHr_ua69JDijTLI5aG1RmBTF7o2KTgk,7140 +pygments/styles/trac.py,sha256=-h5iU-LjSmt-dQ7y5jXwJ5LQ9ABR5QD5RlPRjhnzmv8,1984 +pygments/styles/vim.py,sha256=dWqCVC2YT45dLSpMTwLBPkKYgRsT1-cFf7ZHoc2PQpA,2022 +pygments/styles/vs.py,sha256=7HJehhtiHE5nibjC3r3X-aYHBm5BccL33LgO_4anEiU,1133 +pygments/styles/xcode.py,sha256=HmB6aPkxvmo4za5DijhLb5AXSyv6UbWnwOUoRUNBaZA,1507 +pygments/styles/zenburn.py,sha256=Ax7iBbMzvVQNTpT3YdMwCEkL-nIXnpr60aVrR4Lpa6o,2206 +pygments/token.py,sha256=DVil5T2ltHkTgQTUYy1dMtgyigjavCs1ypE4Qf2CrGo,6229 +pygments/unistring.py,sha256=Z4w4HfOVUhueCURRkfhAqQ2b-69UzkhY4xuHevYEy5g,63211 +pygments/util.py,sha256=zk935tJSpwSA9zxNmV1TuhcEP0MfboXiRZSGQnLPEqk,10046 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pygments-2.20.0.dist-info/REQUESTED b/bioagent-bench/.venv/lib/python3.14/site-packages/pygments-2.20.0.dist-info/REQUESTED new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pygments-2.20.0.dist-info/licenses/AUTHORS b/bioagent-bench/.venv/lib/python3.14/site-packages/pygments-2.20.0.dist-info/licenses/AUTHORS new file mode 100644 index 0000000000000000000000000000000000000000..909089df67ab84b7f0d06a8b0ec0affb962ac60c --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pygments-2.20.0.dist-info/licenses/AUTHORS @@ -0,0 +1,292 @@ +Pygments is written and maintained by Georg Brandl . + +Major developers are Tim Hatch and Armin Ronacher +. + +Other contributors, listed alphabetically, are: + +* Sam Aaron -- Ioke lexer +* Jean Abou Samra -- LilyPond lexer +* João Abecasis -- JSLT lexer +* Ali Afshar -- image formatter +* Thomas Aglassinger -- Easytrieve, JCL, Rexx, Transact-SQL and VBScript + lexers +* Maxence Ahlouche -- PostgreSQL Explain lexer +* Muthiah Annamalai -- Ezhil lexer +* Nikolay Antipov -- OpenSCAD lexer +* Kumar Appaiah -- Debian control lexer +* Andreas Amann -- AppleScript lexer +* Timothy Armstrong -- Dart lexer fixes +* Jeffrey Arnold -- R/S, Rd, BUGS, Jags, and Stan lexers +* Eiríkr Åsheim -- Uxntal lexer +* Jeremy Ashkenas -- CoffeeScript lexer +* José Joaquín Atria -- Praat lexer +* Stefan Matthias Aust -- Smalltalk lexer +* Lucas Bajolet -- Nit lexer +* Ben Bangert -- Mako lexers +* Max Battcher -- Darcs patch lexer +* Thomas Baruchel -- APL lexer +* Tim Baumann -- (Literate) Agda lexer +* Paul Baumgart, 280 North, Inc. -- Objective-J lexer +* Michael Bayer -- Myghty lexers +* Thomas Beale -- Archetype lexers +* John Benediktsson -- Factor lexer +* David Benjamin, Google LLC -- TLS lexer +* Trevor Bergeron -- mIRC formatter +* Vincent Bernat -- LessCSS lexer +* Christopher Bertels -- Fancy lexer +* Sébastien Bigaret -- QVT Operational lexer +* Jarrett Billingsley -- MiniD lexer +* Adam Blinkinsop -- Haskell, Redcode lexers +* Stéphane Blondon -- Procfile, SGF and Sieve lexers +* Frits van Bommel -- assembler lexers +* Pierre Bourdon -- bugfixes +* Martijn Braam -- Kernel log lexer, BARE lexer +* JD Browne, Google LLC -- GoogleSQL lexer +* Matthias Bussonnier -- ANSI style handling for terminal-256 formatter +* chebee7i -- Python traceback lexer improvements +* Hiram Chirino -- Scaml and Jade lexers +* Mauricio Caceres -- SAS and Stata lexers. +* Michael Camilleri, John Gabriele, sogaiu -- Janet lexer +* Daren Chandisingh -- Gleam lexer +* Ian Cooper -- VGL lexer +* David Corbett -- Inform, Jasmin, JSGF, Snowball, and TADS 3 lexers +* Leaf Corcoran -- MoonScript lexer +* Fraser Cormack -- TableGen lexer +* Gabriel Corona -- ASN.1 lexer +* Christopher Creutzig -- MuPAD lexer +* Daniël W. Crompton -- Pike lexer +* Pete Curry -- bugfixes +* Bryan Davis -- EBNF lexer +* Bruno Deferrari -- Shen lexer +* Walter Dörwald -- UL4 lexer +* Luke Drummond -- Meson lexer +* Giedrius Dubinskas -- HTML formatter improvements +* Owen Durni -- Haxe lexer +* Alexander Dutton, Oxford University Computing Services -- SPARQL lexer +* James Edwards -- Terraform lexer +* Nick Efford -- Python 3 lexer +* Sven Efftinge -- Xtend lexer +* Artem Egorkine -- terminal256 formatter +* Matthew Fernandez -- CAmkES lexer +* Paweł Fertyk -- GDScript lexer, HTML formatter improvements +* Michael Ficarra -- CPSA lexer +* James H. Fisher -- PostScript lexer +* Amanda Fitch, Google LLC -- GoogleSQL lexer +* William S. Fulton -- SWIG lexer +* Carlos Galdino -- Elixir and Elixir Console lexers +* Michael Galloy -- IDL lexer +* Naveen Garg -- Autohotkey lexer +* Simon Garnotel -- FreeFem++ lexer +* Laurent Gautier -- R/S lexer +* Alex Gaynor -- PyPy log lexer +* Richard Gerkin -- Igor Pro lexer +* Alain Gilbert -- TypeScript lexer +* Alex Gilding -- BlitzBasic lexer +* GitHub, Inc -- DASM16, Augeas, TOML, and Slash lexers +* Bertrand Goetzmann -- Groovy lexer +* Krzysiek Goj -- Scala lexer +* Rostyslav Golda -- FloScript lexer +* Andrey Golovizin -- BibTeX lexers +* Matt Good -- Genshi, Cheetah lexers +* Michał Górny -- vim modeline support +* Alex Gosse -- TrafficScript lexer +* Patrick Gotthardt -- PHP namespaces support +* Hubert Gruniaux -- C and C++ lexer improvements +* Olivier Guibe -- Asymptote lexer +* Phil Hagelberg -- Fennel lexer +* Florian Hahn -- Boogie lexer +* Martin Harriman -- SNOBOL lexer +* Matthew Harrison -- SVG formatter +* Steven Hazel -- Tcl lexer +* Dan Michael Heggø -- Turtle lexer +* Aslak Hellesøy -- Gherkin lexer +* Greg Hendershott -- Racket lexer +* Justin Hendrick -- ParaSail lexer +* Jordi Gutiérrez Hermoso -- Octave lexer +* David Hess, Fish Software, Inc. -- Objective-J lexer +* Ken Hilton -- Typographic Number Theory and Arrow lexers +* Varun Hiremath -- Debian control lexer +* Rob Hoelz -- Perl 6 lexer +* Doug Hogan -- Mscgen lexer +* Ben Hollis -- Mason lexer +* Max Horn -- GAP lexer +* Fred Hornsey -- OMG IDL Lexer +* Alastair Houghton -- Lexer inheritance facility +* Tim Howard -- BlitzMax lexer +* Dustin Howett -- Logos lexer +* Ivan Inozemtsev -- Fantom lexer +* Hiroaki Itoh -- Shell console rewrite, Lexers for PowerShell session, + MSDOS session, BC, WDiff +* Brian R. Jackson -- Tea lexer +* Alex Jeffery, ChromaWay AB -- Rell lexer +* Christian Jann -- ShellSession lexer +* Jonas Camillus Jeppesen -- Line numbers and line highlighting for + RTF-formatter +* Dennis Kaarsemaker -- sources.list lexer +* Dmitri Kabak -- Inferno Limbo lexer +* Igor Kalnitsky -- vhdl lexer +* Colin Kennedy - USD lexer +* Alexander Kit -- MaskJS lexer +* Pekka Klärck -- Robot Framework lexer +* Gerwin Klein -- Isabelle lexer +* Eric Knibbe -- Lasso lexer +* Stepan Koltsov -- Clay lexer +* Oliver Kopp - Friendly grayscale style +* Adam Koprowski -- Opa lexer +* Benjamin Kowarsch -- Modula-2 lexer +* Domen Kožar -- Nix lexer +* Oleh Krekel -- Emacs Lisp lexer +* Alexander Kriegisch -- Kconfig and AspectJ lexers +* Marek Kubica -- Scheme lexer +* Jochen Kupperschmidt -- Markdown processor +* Gerd Kurzbach -- Modelica lexer +* Jon Larimer, Google Inc. -- Smali lexer +* Olov Lassus -- Dart lexer +* Matt Layman -- TAP lexer +* Dan Lazin, Google LLC -- GoogleSQL lexer +* Kristian Lyngstøl -- Varnish lexers +* Sylvestre Ledru -- Scilab lexer +* Chee Sing Lee -- Flatline lexer +* Mark Lee -- Vala lexer +* Thomas Linder Puls -- Visual Prolog lexer +* Pete Lomax -- Phix lexer +* Valentin Lorentz -- C++ lexer improvements +* Ben Mabey -- Gherkin lexer +* Angus MacArthur -- QML lexer +* Louis Mandel -- X10 lexer +* Louis Marchand -- Eiffel lexer +* Simone Margaritelli -- Hybris lexer +* Tim Martin - World of Warcraft TOC lexer +* Kirk McDonald -- D lexer +* Gordon McGregor -- SystemVerilog lexer +* Stephen McKamey -- Duel/JBST lexer +* Brian McKenna -- F# lexer +* Charles McLaughlin -- Puppet lexer +* Kurt McKee -- Tera Term macro lexer, PostgreSQL updates, MySQL overhaul, JSON lexer +* Joe Eli McIlvain -- Savi lexer +* Lukas Meuser -- BBCode formatter, Lua lexer +* Cat Miller -- Pig lexer +* Paul Miller -- LiveScript lexer +* Hong Minhee -- HTTP lexer +* Michael Mior -- Awk lexer +* Bruce Mitchener -- Dylan lexer rewrite +* Reuben Morais -- SourcePawn lexer +* Jon Morton -- Rust lexer +* Paulo Moura -- Logtalk lexer +* Mher Movsisyan -- DTD lexer +* Dejan Muhamedagic -- Crmsh lexer +* Adrien Nayrat -- PostgreSQL Explain lexer +* Ana Nelson -- Ragel, ANTLR, R console lexers +* David Neto, Google LLC -- WebGPU Shading Language lexer +* Kurt Neufeld -- Markdown lexer +* Nam T. Nguyen -- Monokai style +* Jesper Noehr -- HTML formatter "anchorlinenos" +* Mike Nolta -- Julia lexer +* Avery Nortonsmith -- Pointless lexer +* Jonas Obrist -- BBCode lexer +* Edward O'Callaghan -- Cryptol lexer +* David Oliva -- Rebol lexer +* Pat Pannuto -- nesC lexer +* Jon Parise -- Protocol buffers and Thrift lexers +* Benjamin Peterson -- Test suite refactoring +* Ronny Pfannschmidt -- BBCode lexer +* Dominik Picheta -- Nimrod lexer +* Andrew Pinkham -- RTF Formatter Refactoring +* Clément Prévost -- UrbiScript lexer +* Tanner Prynn -- cmdline -x option and loading lexers from files +* Oleh Prypin -- Crystal lexer (based on Ruby lexer) +* Nick Psaris -- K and Q lexers +* Xidorn Quan -- Web IDL lexer +* Elias Rabel -- Fortran fixed form lexer +* raichoo -- Idris lexer +* Daniel Ramirez -- GDScript lexer +* Kashif Rasul -- CUDA lexer +* Nathan Reed -- HLSL lexer +* Justin Reidy -- MXML lexer +* Jonathon Reinhart, Google LLC -- Soong lexer +* Norman Richards -- JSON lexer +* Corey Richardson -- Rust lexer updates +* Fabrizio Riguzzi -- cplint leder +* Lubomir Rintel -- GoodData MAQL and CL lexers +* Andre Roberge -- Tango style +* Georg Rollinger -- HSAIL lexer +* Michiel Roos -- TypoScript lexer +* Konrad Rudolph -- LaTeX formatter enhancements +* Mario Ruggier -- Evoque lexers +* Miikka Salminen -- Lovelace style, Hexdump lexer, lexer enhancements +* Stou Sandalski -- NumPy, FORTRAN, tcsh and XSLT lexers +* Matteo Sasso -- Common Lisp lexer +* Joe Schafer -- Ada lexer +* Max Schillinger -- TiddlyWiki5 lexer +* Andrew Schmidt -- X++ lexer +* Ken Schutte -- Matlab lexers +* René Schwaiger -- Rainbow Dash style +* Sebastian Schweizer -- Whiley lexer +* Tassilo Schweyer -- Io, MOOCode lexers +* Pablo Seminario -- PromQL lexer +* Ted Shaw -- AutoIt lexer +* Joerg Sieker -- ABAP lexer +* Robert Simmons -- Standard ML lexer +* Kirill Simonov -- YAML lexer +* Corbin Simpson -- Monte lexer +* Ville Skyttä -- ASCII armored lexer +* Alexander Smishlajev -- Visual FoxPro lexer +* Steve Spigarelli -- XQuery lexer +* Jerome St-Louis -- eC lexer +* Camil Staps -- Clean and NuSMV lexers; Solarized style +* James Strachan -- Kotlin lexer +* Tom Stuart -- Treetop lexer +* Colin Sullivan -- SuperCollider lexer +* Ben Swift -- Extempore lexer +* tatt61880 -- Kuin lexer +* Edoardo Tenani -- Arduino lexer +* Tiberius Teng -- default style overhaul +* Jeremy Thurgood -- Erlang, Squid config lexers +* Brian Tiffin -- OpenCOBOL lexer +* Bob Tolbert -- Hy lexer +* Doug Torrance -- Macaulay2 lexer +* Matthias Trute -- Forth lexer +* Tuoa Spi T4 -- Bdd lexer +* Erick Tryzelaar -- Felix lexer +* Alexander Udalov -- Kotlin lexer improvements +* Thomas Van Doren -- Chapel lexer +* Dave Van Ee -- Uxntal lexer updates +* Daniele Varrazzo -- PostgreSQL lexers +* Abe Voelker -- OpenEdge ABL lexer +* Pepijn de Vos -- HTML formatter CTags support +* Matthias Vallentin -- Bro lexer +* Benoît Vinot -- AMPL lexer +* Linh Vu Hong -- RSL lexer +* Taavi Väänänen -- Debian control, PHP lexers +* Immanuel Washington -- Smithy lexer +* Nathan Weizenbaum -- Haml and Sass lexers +* Nathan Whetsell -- Csound lexers +* Dietmar Winkler -- Modelica lexer +* Nils Winter -- Smalltalk lexer +* Davy Wybiral -- Clojure lexer +* Whitney Young -- ObjectiveC lexer +* Diego Zamboni -- CFengine3 lexer +* Enrique Zamudio -- Ceylon lexer +* Alex Zimin -- Nemerle lexer +* Rob Zimmerman -- Kal lexer +* Evgenii Zheltonozhskii -- Maple lexer +* Vincent Zurczak -- Roboconf lexer +* Hubert Gruniaux -- C and C++ lexer improvements +* Thomas Symalla -- AMDGPU Lexer +* 15b3 -- Image Formatter improvements +* Fabian Neumann -- CDDL lexer +* Thomas Duboucher -- CDDL lexer +* Philipp Imhof -- Pango Markup formatter +* Thomas Voss -- Sed lexer +* Martin Fischer -- WCAG contrast testing +* Marc Auberer -- Spice lexer +* Amr Hesham -- Carbon lexer +* diskdance -- Wikitext lexer +* vanillajonathan -- PRQL lexer +* Nikolay Antipov -- OpenSCAD lexer +* Markus Meyer, Nextron Systems -- YARA lexer +* Hannes Römer -- Mojo lexer +* Jan Frederik Schaefer -- PDDL lexer + +Many thanks for all contributions! diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pygments-2.20.0.dist-info/licenses/LICENSE b/bioagent-bench/.venv/lib/python3.14/site-packages/pygments-2.20.0.dist-info/licenses/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..446a1a805c8a949579fb8a9799f2bec7777349dc --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pygments-2.20.0.dist-info/licenses/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) 2006-2022 by the respective authors (see AUTHORS file). +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/python_dateutil-2.9.0.post0.dist-info/INSTALLER b/bioagent-bench/.venv/lib/python3.14/site-packages/python_dateutil-2.9.0.post0.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..5c69047b2eb8235994febeeae1da4a82365a240a --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/python_dateutil-2.9.0.post0.dist-info/INSTALLER @@ -0,0 +1 @@ +uv \ No newline at end of file diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/python_dateutil-2.9.0.post0.dist-info/LICENSE b/bioagent-bench/.venv/lib/python3.14/site-packages/python_dateutil-2.9.0.post0.dist-info/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..1e65815cf0b3132689485874a93034ede7206bf4 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/python_dateutil-2.9.0.post0.dist-info/LICENSE @@ -0,0 +1,54 @@ +Copyright 2017- Paul Ganssle +Copyright 2017- dateutil contributors (see AUTHORS file) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +The above license applies to all contributions after 2017-12-01, as well as +all contributions that have been re-licensed (see AUTHORS file for the list of +contributors who have re-licensed their code). +-------------------------------------------------------------------------------- +dateutil - Extensions to the standard Python datetime module. + +Copyright (c) 2003-2011 - Gustavo Niemeyer +Copyright (c) 2012-2014 - Tomi Pieviläinen +Copyright (c) 2014-2016 - Yaron de Leeuw +Copyright (c) 2015- - Paul Ganssle +Copyright (c) 2015- - dateutil contributors (see AUTHORS file) + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The above BSD License Applies to all code, even that also covered by Apache 2.0. \ No newline at end of file diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/python_dateutil-2.9.0.post0.dist-info/METADATA b/bioagent-bench/.venv/lib/python3.14/site-packages/python_dateutil-2.9.0.post0.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..577f2bf2b7749e1b123b8225d610b1b257e430cc --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/python_dateutil-2.9.0.post0.dist-info/METADATA @@ -0,0 +1,204 @@ +Metadata-Version: 2.1 +Name: python-dateutil +Version: 2.9.0.post0 +Summary: Extensions to the standard Python datetime module +Home-page: https://github.com/dateutil/dateutil +Author: Gustavo Niemeyer +Author-email: gustavo@niemeyer.net +Maintainer: Paul Ganssle +Maintainer-email: dateutil@python.org +License: Dual License +Project-URL: Documentation, https://dateutil.readthedocs.io/en/stable/ +Project-URL: Source, https://github.com/dateutil/dateutil +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Topic :: Software Development :: Libraries +Requires-Python: !=3.0.*,!=3.1.*,!=3.2.*,>=2.7 +Description-Content-Type: text/x-rst +License-File: LICENSE +Requires-Dist: six >=1.5 + +dateutil - powerful extensions to datetime +========================================== + +|pypi| |support| |licence| + +|gitter| |readthedocs| + +|travis| |appveyor| |pipelines| |coverage| + +.. |pypi| image:: https://img.shields.io/pypi/v/python-dateutil.svg?style=flat-square + :target: https://pypi.org/project/python-dateutil/ + :alt: pypi version + +.. |support| image:: https://img.shields.io/pypi/pyversions/python-dateutil.svg?style=flat-square + :target: https://pypi.org/project/python-dateutil/ + :alt: supported Python version + +.. |travis| image:: https://img.shields.io/travis/dateutil/dateutil/master.svg?style=flat-square&label=Travis%20Build + :target: https://travis-ci.org/dateutil/dateutil + :alt: travis build status + +.. |appveyor| image:: https://img.shields.io/appveyor/ci/dateutil/dateutil/master.svg?style=flat-square&logo=appveyor + :target: https://ci.appveyor.com/project/dateutil/dateutil + :alt: appveyor build status + +.. |pipelines| image:: https://dev.azure.com/pythondateutilazure/dateutil/_apis/build/status/dateutil.dateutil?branchName=master + :target: https://dev.azure.com/pythondateutilazure/dateutil/_build/latest?definitionId=1&branchName=master + :alt: azure pipelines build status + +.. |coverage| image:: https://codecov.io/gh/dateutil/dateutil/branch/master/graphs/badge.svg?branch=master + :target: https://codecov.io/gh/dateutil/dateutil?branch=master + :alt: Code coverage + +.. |gitter| image:: https://badges.gitter.im/dateutil/dateutil.svg + :alt: Join the chat at https://gitter.im/dateutil/dateutil + :target: https://gitter.im/dateutil/dateutil + +.. |licence| image:: https://img.shields.io/pypi/l/python-dateutil.svg?style=flat-square + :target: https://pypi.org/project/python-dateutil/ + :alt: licence + +.. |readthedocs| image:: https://img.shields.io/readthedocs/dateutil/latest.svg?style=flat-square&label=Read%20the%20Docs + :alt: Read the documentation at https://dateutil.readthedocs.io/en/latest/ + :target: https://dateutil.readthedocs.io/en/latest/ + +The `dateutil` module provides powerful extensions to +the standard `datetime` module, available in Python. + +Installation +============ +`dateutil` can be installed from PyPI using `pip` (note that the package name is +different from the importable name):: + + pip install python-dateutil + +Download +======== +dateutil is available on PyPI +https://pypi.org/project/python-dateutil/ + +The documentation is hosted at: +https://dateutil.readthedocs.io/en/stable/ + +Code +==== +The code and issue tracker are hosted on GitHub: +https://github.com/dateutil/dateutil/ + +Features +======== + +* Computing of relative deltas (next month, next year, + next Monday, last week of month, etc); +* Computing of relative deltas between two given + date and/or datetime objects; +* Computing of dates based on very flexible recurrence rules, + using a superset of the `iCalendar `_ + specification. Parsing of RFC strings is supported as well. +* Generic parsing of dates in almost any string format; +* Timezone (tzinfo) implementations for tzfile(5) format + files (/etc/localtime, /usr/share/zoneinfo, etc), TZ + environment string (in all known formats), iCalendar + format files, given ranges (with help from relative deltas), + local machine timezone, fixed offset timezone, UTC timezone, + and Windows registry-based time zones. +* Internal up-to-date world timezone information based on + Olson's database. +* Computing of Easter Sunday dates for any given year, + using Western, Orthodox or Julian algorithms; +* A comprehensive test suite. + +Quick example +============= +Here's a snapshot, just to give an idea about the power of the +package. For more examples, look at the documentation. + +Suppose you want to know how much time is left, in +years/months/days/etc, before the next easter happening on a +year with a Friday 13th in August, and you want to get today's +date out of the "date" unix system command. Here is the code: + +.. code-block:: python3 + + >>> from dateutil.relativedelta import * + >>> from dateutil.easter import * + >>> from dateutil.rrule import * + >>> from dateutil.parser import * + >>> from datetime import * + >>> now = parse("Sat Oct 11 17:13:46 UTC 2003") + >>> today = now.date() + >>> year = rrule(YEARLY,dtstart=now,bymonth=8,bymonthday=13,byweekday=FR)[0].year + >>> rdelta = relativedelta(easter(year), today) + >>> print("Today is: %s" % today) + Today is: 2003-10-11 + >>> print("Year with next Aug 13th on a Friday is: %s" % year) + Year with next Aug 13th on a Friday is: 2004 + >>> print("How far is the Easter of that year: %s" % rdelta) + How far is the Easter of that year: relativedelta(months=+6) + >>> print("And the Easter of that year is: %s" % (today+rdelta)) + And the Easter of that year is: 2004-04-11 + +Being exactly 6 months ahead was **really** a coincidence :) + +Contributing +============ + +We welcome many types of contributions - bug reports, pull requests (code, infrastructure or documentation fixes). For more information about how to contribute to the project, see the ``CONTRIBUTING.md`` file in the repository. + + +Author +====== +The dateutil module was written by Gustavo Niemeyer +in 2003. + +It is maintained by: + +* Gustavo Niemeyer 2003-2011 +* Tomi Pieviläinen 2012-2014 +* Yaron de Leeuw 2014-2016 +* Paul Ganssle 2015- + +Starting with version 2.4.1 and running until 2.8.2, all source and binary +distributions will be signed by a PGP key that has, at the very least, been +signed by the key which made the previous release. A table of release signing +keys can be found below: + +=========== ============================ +Releases Signing key fingerprint +=========== ============================ +2.4.1-2.8.2 `6B49 ACBA DCF6 BD1C A206 67AB CD54 FCE3 D964 BEFB`_ +=========== ============================ + +New releases *may* have signed tags, but binary and source distributions +uploaded to PyPI will no longer have GPG signatures attached. + +Contact +======= +Our mailing list is available at `dateutil@python.org `_. As it is hosted by the PSF, it is subject to the `PSF code of +conduct `_. + +License +======= + +All contributions after December 1, 2017 released under dual license - either `Apache 2.0 License `_ or the `BSD 3-Clause License `_. Contributions before December 1, 2017 - except those those explicitly relicensed - are released only under the BSD 3-Clause License. + + +.. _6B49 ACBA DCF6 BD1C A206 67AB CD54 FCE3 D964 BEFB: + https://pgp.mit.edu/pks/lookup?op=vindex&search=0xCD54FCE3D964BEFB diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/python_dateutil-2.9.0.post0.dist-info/RECORD b/bioagent-bench/.venv/lib/python3.14/site-packages/python_dateutil-2.9.0.post0.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..80cde96581cce34e038232c05925ca054de05960 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/python_dateutil-2.9.0.post0.dist-info/RECORD @@ -0,0 +1,27 @@ +dateutil/__init__.py,sha256=Mqam67WO9IkTmUFyI66vS6IoSXTp9G388DadH2LCMLY,620 +dateutil/_common.py,sha256=77w0yytkrxlYbSn--lDVPUMabUXRR9I3lBv_vQRUqUY,932 +dateutil/_version.py,sha256=BV031OxDDAmy58neUg5yyqLkLaqIw7ibK9As3jiMib0,166 +dateutil/easter.py,sha256=dyBi-lKvimH1u_k6p7Z0JJK72QhqVtVBsqByvpEPKvc,2678 +dateutil/parser/__init__.py,sha256=wWk6GFuxTpjoggCGtgkceJoti4pVjl4_fHQXpNOaSYg,1766 +dateutil/parser/_parser.py,sha256=7klDdyicksQB_Xgl-3UAmBwzCYor1AIZqklIcT6dH_8,58796 +dateutil/parser/isoparser.py,sha256=8Fy999bnCd1frSdOYuOraWfJTtd5W7qQ51NwNuH_hXM,13233 +dateutil/relativedelta.py,sha256=IY_mglMjoZbYfrvloTY2ce02aiVjPIkiZfqgNTZRfuA,24903 +dateutil/rrule.py,sha256=KJzKlaCd1jEbu4A38ZltslaoAUh9nSbdbOFdjp70Kew,66557 +dateutil/tz/__init__.py,sha256=F-Mz13v6jYseklQf9Te9J6nzcLDmq47gORa61K35_FA,444 +dateutil/tz/_common.py,sha256=cgzDTANsOXvEc86cYF77EsliuSab8Puwpsl5-bX3_S4,12977 +dateutil/tz/_factories.py,sha256=unb6XQNXrPMveksTCU-Ag8jmVZs4SojoPUcAHpWnrvU,2569 +dateutil/tz/tz.py,sha256=EUnEdMfeThXiY6l4sh9yBabZ63_POzy01zSsh9thn1o,62855 +dateutil/tz/win.py,sha256=xJszWgSwE1xPx_HJj4ZkepyukC_hNy016WMcXhbRaB8,12935 +dateutil/tzwin.py,sha256=7Ar4vdQCnnM0mKR3MUjbIKsZrBVfHgdwsJZc_mGYRew,59 +dateutil/utils.py,sha256=dKCchEw8eObi0loGTx91unBxm_7UGlU3v_FjFMdqwYM,1965 +dateutil/zoneinfo/__init__.py,sha256=KYg0pthCMjcp5MXSEiBJn3nMjZeNZav7rlJw5-tz1S4,5889 +dateutil/zoneinfo/dateutil-zoneinfo.tar.gz,sha256=0-pS57bpaN4NiE3xKIGTWW-pW4A9tPkqGCeac5gARHU,156400 +dateutil/zoneinfo/rebuild.py,sha256=MiqYzCIHvNbMH-LdRYLv-4T0EIA7hDKt5GLR0IRTLdI,2392 +python_dateutil-2.9.0.post0.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2 +python_dateutil-2.9.0.post0.dist-info/LICENSE,sha256=ugD1Gg2SgjtaHN4n2LW50jIeZ-2NqbwWPv-W1eF-V34,2889 +python_dateutil-2.9.0.post0.dist-info/METADATA,sha256=qdQ22jIr6AgzL5jYgyWZjofLaTpniplp_rTPrXKabpM,8354 +python_dateutil-2.9.0.post0.dist-info/RECORD,, +python_dateutil-2.9.0.post0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +python_dateutil-2.9.0.post0.dist-info/WHEEL,sha256=-G_t0oGuE7UD0DrSpVZnq1hHMBV9DD2XkS5v7XpmTnk,110 +python_dateutil-2.9.0.post0.dist-info/top_level.txt,sha256=4tjdWkhRZvF7LA_BYe_L9gB2w_p2a-z5y6ArjaRkot8,9 +python_dateutil-2.9.0.post0.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/python_dateutil-2.9.0.post0.dist-info/REQUESTED b/bioagent-bench/.venv/lib/python3.14/site-packages/python_dateutil-2.9.0.post0.dist-info/REQUESTED new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/python_dateutil-2.9.0.post0.dist-info/WHEEL b/bioagent-bench/.venv/lib/python3.14/site-packages/python_dateutil-2.9.0.post0.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..4724c45738f6ac125bb3a21787855562e6870440 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/python_dateutil-2.9.0.post0.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.42.0) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pyyaml-6.0.3.dist-info/INSTALLER b/bioagent-bench/.venv/lib/python3.14/site-packages/pyyaml-6.0.3.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..5c69047b2eb8235994febeeae1da4a82365a240a --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pyyaml-6.0.3.dist-info/INSTALLER @@ -0,0 +1 @@ +uv \ No newline at end of file diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pyyaml-6.0.3.dist-info/METADATA b/bioagent-bench/.venv/lib/python3.14/site-packages/pyyaml-6.0.3.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..702ee6da5865b50b297705b077ea7d5904fead58 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pyyaml-6.0.3.dist-info/METADATA @@ -0,0 +1,59 @@ +Metadata-Version: 2.4 +Name: PyYAML +Version: 6.0.3 +Summary: YAML parser and emitter for Python +Home-page: https://pyyaml.org/ +Download-URL: https://pypi.org/project/PyYAML/ +Author: Kirill Simonov +Author-email: xi@resolvent.net +License: MIT +Project-URL: Bug Tracker, https://github.com/yaml/pyyaml/issues +Project-URL: CI, https://github.com/yaml/pyyaml/actions +Project-URL: Documentation, https://pyyaml.org/wiki/PyYAMLDocumentation +Project-URL: Mailing lists, http://lists.sourceforge.net/lists/listinfo/yaml-core +Project-URL: Source Code, https://github.com/yaml/pyyaml +Platform: Any +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Cython +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: Text Processing :: Markup +Requires-Python: >=3.8 +License-File: LICENSE +Dynamic: author +Dynamic: author-email +Dynamic: classifier +Dynamic: description +Dynamic: download-url +Dynamic: home-page +Dynamic: license +Dynamic: license-file +Dynamic: platform +Dynamic: project-url +Dynamic: requires-python +Dynamic: summary + +YAML is a data serialization format designed for human readability +and interaction with scripting languages. PyYAML is a YAML parser +and emitter for Python. + +PyYAML features a complete YAML 1.1 parser, Unicode support, pickle +support, capable extension API, and sensible error messages. PyYAML +supports standard YAML tags and provides Python-specific tags that +allow to represent an arbitrary Python object. + +PyYAML is applicable for a broad range of tasks from complex +configuration files to object serialization and persistence. diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pyyaml-6.0.3.dist-info/RECORD b/bioagent-bench/.venv/lib/python3.14/site-packages/pyyaml-6.0.3.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..569f1afb81abcf9890e5fd37275d113be70b7ca2 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pyyaml-6.0.3.dist-info/RECORD @@ -0,0 +1,26 @@ +_yaml/__init__.py,sha256=04Ae_5osxahpJHa3XBZUAf4wi6XX32gR8D6X6p64GEA,1402 +pyyaml-6.0.3.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2 +pyyaml-6.0.3.dist-info/METADATA,sha256=A8O0Fe040J-u3Ek2DpMHabQMWPaFhebeAOLkkWqFjTQ,2351 +pyyaml-6.0.3.dist-info/RECORD,, +pyyaml-6.0.3.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +pyyaml-6.0.3.dist-info/WHEEL,sha256=DMni2HWXXG4oat8Y6bAVuu-xlkB-grHQiqsTu-lLHls,190 +pyyaml-6.0.3.dist-info/licenses/LICENSE,sha256=jTko-dxEkP1jVwfLiOsmvXZBAqcoKVQwfT5RZ6V36KQ,1101 +pyyaml-6.0.3.dist-info/top_level.txt,sha256=rpj0IVMTisAjh_1vG3Ccf9v5jpCQwAz6cD1IVU5ZdhQ,11 +yaml/__init__.py,sha256=sZ38wzPWp139cwc5ARZFByUvJxtB07X32FUQAzoFR6c,12311 +yaml/_yaml.cpython-314-x86_64-linux-gnu.so,sha256=RYvQywAwECubz_t2iWlqyHvty9U4S8THiD57vgzk4KE,2620168 +yaml/composer.py,sha256=_Ko30Wr6eDWUeUpauUGT3Lcg9QPBnOPVlTnIMRGJ9FM,4883 +yaml/constructor.py,sha256=kNgkfaeLUkwQYY_Q6Ff1Tz2XVw_pG1xVE9Ak7z-viLA,28639 +yaml/cyaml.py,sha256=6ZrAG9fAYvdVe2FK_w0hmXoG7ZYsoYUwapG8CiC72H0,3851 +yaml/dumper.py,sha256=PLctZlYwZLp7XmeUdwRuv4nYOZ2UBnDIUy8-lKfLF-o,2837 +yaml/emitter.py,sha256=jghtaU7eFwg31bG0B7RZea_29Adi9CKmXq_QjgQpCkQ,43006 +yaml/error.py,sha256=Ah9z-toHJUbE9j-M8YpxgSRM5CgLCcwVzJgLLRF2Fxo,2533 +yaml/events.py,sha256=50_TksgQiE4up-lKo_V-nBy-tAIxkIPQxY5qDhKCeHw,2445 +yaml/loader.py,sha256=UVa-zIqmkFSCIYq_PgSGm4NSJttHY2Rf_zQ4_b1fHN0,2061 +yaml/nodes.py,sha256=gPKNj8pKCdh2d4gr3gIYINnPOaOxGhJAUiYhGRnPE84,1440 +yaml/parser.py,sha256=ilWp5vvgoHFGzvOZDItFoGjD6D42nhlZrZyjAwa0oJo,25495 +yaml/reader.py,sha256=0dmzirOiDG4Xo41RnuQS7K9rkY3xjHiVasfDMNTqCNw,6794 +yaml/representer.py,sha256=IuWP-cAW9sHKEnS0gCqSa894k1Bg4cgTxaDwIcbRQ-Y,14190 +yaml/resolver.py,sha256=9L-VYfm4mWHxUD1Vg4X7rjDRK_7VZd6b92wzq7Y2IKY,9004 +yaml/scanner.py,sha256=YEM3iLZSaQwXcQRg2l2R4MdT0zGP2F9eHkKGKnHyWQY,51279 +yaml/serializer.py,sha256=ChuFgmhU01hj4xgI8GaKv6vfM2Bujwa9i7d2FAHj7cA,4165 +yaml/tokens.py,sha256=lTQIzSVw8Mg9wv459-TjiOQe6wVziqaRlqX2_89rp54,2573 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pyyaml-6.0.3.dist-info/REQUESTED b/bioagent-bench/.venv/lib/python3.14/site-packages/pyyaml-6.0.3.dist-info/REQUESTED new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pyyaml-6.0.3.dist-info/WHEEL b/bioagent-bench/.venv/lib/python3.14/site-packages/pyyaml-6.0.3.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..9390e9ea7ce84ac081ab70fa6e7ef82a98a8ff26 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pyyaml-6.0.3.dist-info/WHEEL @@ -0,0 +1,7 @@ +Wheel-Version: 1.0 +Generator: setuptools (80.9.0) +Root-Is-Purelib: false +Tag: cp314-cp314-manylinux_2_17_x86_64 +Tag: cp314-cp314-manylinux2014_x86_64 +Tag: cp314-cp314-manylinux_2_28_x86_64 + diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pyyaml-6.0.3.dist-info/licenses/LICENSE b/bioagent-bench/.venv/lib/python3.14/site-packages/pyyaml-6.0.3.dist-info/licenses/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..2f1b8e15e5627d92f0521605c9870bc8e5505cb4 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pyyaml-6.0.3.dist-info/licenses/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2017-2021 Ingy döt Net +Copyright (c) 2006-2016 Kirill Simonov + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/pyyaml-6.0.3.dist-info/top_level.txt b/bioagent-bench/.venv/lib/python3.14/site-packages/pyyaml-6.0.3.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..e6475e911f628412049bc4090d86f23ac403adde --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/pyyaml-6.0.3.dist-info/top_level.txt @@ -0,0 +1,2 @@ +_yaml +yaml diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests-2.33.1.dist-info/INSTALLER b/bioagent-bench/.venv/lib/python3.14/site-packages/requests-2.33.1.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..5c69047b2eb8235994febeeae1da4a82365a240a --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/requests-2.33.1.dist-info/INSTALLER @@ -0,0 +1 @@ +uv \ No newline at end of file diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests-2.33.1.dist-info/METADATA b/bioagent-bench/.venv/lib/python3.14/site-packages/requests-2.33.1.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..52a7b0361e5b6dc9f5683a34ad9d8f6eaf2436d2 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/requests-2.33.1.dist-info/METADATA @@ -0,0 +1,121 @@ +Metadata-Version: 2.4 +Name: requests +Version: 2.33.1 +Summary: Python HTTP for Humans. +Author-email: Kenneth Reitz +Maintainer-email: Ian Stapleton Cordasco , Nate Prewitt +License: Apache-2.0 +Project-URL: Documentation, https://requests.readthedocs.io +Project-URL: Source, https://github.com/psf/requests +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Web Environment +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Apache Software License +Classifier: Natural Language :: English +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Internet :: WWW/HTTP +Classifier: Topic :: Software Development :: Libraries +Requires-Python: >=3.10 +Description-Content-Type: text/markdown +License-File: LICENSE +License-File: NOTICE +Requires-Dist: charset_normalizer<4,>=2 +Requires-Dist: idna<4,>=2.5 +Requires-Dist: urllib3<3,>=1.26 +Requires-Dist: certifi>=2023.5.7 +Provides-Extra: security +Provides-Extra: socks +Requires-Dist: PySocks!=1.5.7,>=1.5.6; extra == "socks" +Provides-Extra: use-chardet-on-py3 +Requires-Dist: chardet<8,>=3.0.2; extra == "use-chardet-on-py3" +Dynamic: license-file + +# Requests + +[![Version](https://img.shields.io/pypi/v/requests.svg?maxAge=86400)](https://pypi.org/project/requests/) +[![Supported Versions](https://img.shields.io/pypi/pyversions/requests.svg)](https://pypi.org/project/requests) +[![Downloads](https://static.pepy.tech/badge/requests/month)](https://pepy.tech/project/requests) +[![Contributors](https://img.shields.io/github/contributors/psf/requests.svg)](https://github.com/psf/requests/graphs/contributors) + +**Requests** is a simple, yet elegant, HTTP library. + +```python +>>> import requests +>>> r = requests.get('https://httpbin.org/basic-auth/user/pass', auth=('user', 'pass')) +>>> r.status_code +200 +>>> r.headers['content-type'] +'application/json; charset=utf8' +>>> r.encoding +'utf-8' +>>> r.text +'{"authenticated": true, ...' +>>> r.json() +{'authenticated': True, ...} +``` + +Requests allows you to send HTTP/1.1 requests extremely easily. There’s no need to manually add query strings to your URLs, or to form-encode your `PUT` & `POST` data — but nowadays, just use the `json` method! + +Requests is one of the most downloaded Python packages today, pulling in around `30M downloads / week`— according to GitHub, Requests is currently [depended upon](https://github.com/psf/requests/network/dependents?package_id=UGFja2FnZS01NzA4OTExNg%3D%3D) by `1,000,000+` repositories. You may certainly put your trust in this code. + +## Installing Requests and Supported Versions + +Requests is available on PyPI: + +```console +$ python -m pip install requests +``` + +Requests officially supports Python 3.10+. + +## Supported Features & Best–Practices + +Requests is ready for the demands of building robust and reliable HTTP–speaking applications, for the needs of today. + +- Keep-Alive & Connection Pooling +- International Domains and URLs +- Sessions with Cookie Persistence +- Browser-style TLS/SSL Verification +- Basic & Digest Authentication +- Familiar `dict`–like Cookies +- Automatic Content Decompression and Decoding +- Multi-part File Uploads +- SOCKS Proxy Support +- Connection Timeouts +- Streaming Downloads +- Automatic honoring of `.netrc` +- Chunked HTTP Requests + +## API Reference and User Guide available on [Read the Docs](https://requests.readthedocs.io) + +[![Read the Docs](https://raw.githubusercontent.com/psf/requests/main/ext/ss.png)](https://requests.readthedocs.io) + +## Cloning the repository + +When cloning the Requests repository, you may need to add the `-c +fetch.fsck.badTimezone=ignore` flag to avoid an error about a bad commit timestamp (see +[this issue](https://github.com/psf/requests/issues/2690) for more background): + +```shell +git clone -c fetch.fsck.badTimezone=ignore https://github.com/psf/requests.git +``` + +You can also apply this setting to your global Git config: + +```shell +git config --global fetch.fsck.badTimezone ignore +``` + +--- + +[![Kenneth Reitz](https://raw.githubusercontent.com/psf/requests/main/ext/kr.png)](https://kennethreitz.org) [![Python Software Foundation](https://raw.githubusercontent.com/psf/requests/main/ext/psf.png)](https://www.python.org/psf) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests-2.33.1.dist-info/RECORD b/bioagent-bench/.venv/lib/python3.14/site-packages/requests-2.33.1.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..9818d4c682fe2febb197ab75e47acd8804573645 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/requests-2.33.1.dist-info/RECORD @@ -0,0 +1,26 @@ +requests-2.33.1.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2 +requests-2.33.1.dist-info/METADATA,sha256=qYQYGl7MhKY-bwtg61dYkP_jRCblNRlV00KqKzFHuCM,4822 +requests-2.33.1.dist-info/RECORD,, +requests-2.33.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +requests-2.33.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91 +requests-2.33.1.dist-info/licenses/LICENSE,sha256=CeipvOyAZxBGUsFoaFqwkx54aPnIKEtm9a5u2uXxEws,10142 +requests-2.33.1.dist-info/licenses/NOTICE,sha256=9REJct7a0rTp0xRRja87fXLW4C5Jms2AIYHeb3RXHcw,38 +requests-2.33.1.dist-info/top_level.txt,sha256=fMSVmHfb5rbGOo6xv-O_tUX6j-WyixssE-SnwcDRxNQ,9 +requests/__init__.py,sha256=mAC_Qctr__Z43w_-r9EhoqvVtJlU19-S8o5Z4eLWvmQ,5044 +requests/__version__.py,sha256=nZ3xT2HoQjEOL4OW7CM2tBWtrpfclaxuSz3bQZf_mbI,435 +requests/_internal_utils.py,sha256=9_7fcdYfMFDfyK4hD2OsRgiGiq8kDwdZcGuRqkP5R1g,1502 +requests/adapters.py,sha256=9klQ9SLw9aVmJ7lXBkCTxGLcvWz34zEWhII42wG1PmE,26172 +requests/api.py,sha256=_Zb9Oa7tzVIizTKwFrPjDEY9ejtm_OnSRERnADxGsQs,6449 +requests/auth.py,sha256=KHXfnbNH2Fe4rdGJK3raL4O3nxkXyUlLizJJBEguhSc,10170 +requests/certs.py,sha256=_ZxrgzWc75D_bE7uq43MI4jaOC68p9AKSZs8C0NKh-Q,430 +requests/compat.py,sha256=J7sIjR6XoDGp5JTVzOxkK5fSoUVUa_Pjc7iRZhAWGmI,2142 +requests/cookies.py,sha256=bNi-iqEj4NPZ00-ob-rHvzkvObzN3lEpgw3g6paS3Xw,18590 +requests/exceptions.py,sha256=neOkAkeK7evZOfqhtISVsHFIlOiIJn5lBxtS0BTFalA,4261 +requests/help.py,sha256=vMrf-3N5uOksnFoqTLHzbGb3Vw27WwhdhdBJhP5lL-0,3802 +requests/hooks.py,sha256=9frYhALsLBkHH76G-HYqvAvssSlu1C1b7L68cAs-E5g,734 +requests/models.py,sha256=JHM9IC12NjFhI6JKg41R1dZkMmeY38hAaVh26ymJ-tw,35465 +requests/packages.py,sha256=_g0gZ681UyAlKHRjH6kanbaoxx2eAb6qzcXiODyTIoc,904 +requests/sessions.py,sha256=gbmlsNSi96sIig0mrtHzZAZT_fOIWToe-YnW7v5ptKI,30645 +requests/status_codes.py,sha256=iJUAeA25baTdw-6PfD0eF4qhpINDJRJI-yaMqxs4LEI,4322 +requests/structures.py,sha256=-IbmhVz06S-5aPSZuUthZ6-6D9XOjRuTXHOabY041XM,2912 +requests/utils.py,sha256=9nzAZ42ieryePzZZ1AdY-Jy0MQvlt3VevLDX1zpM8rU,32966 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests-2.33.1.dist-info/REQUESTED b/bioagent-bench/.venv/lib/python3.14/site-packages/requests-2.33.1.dist-info/REQUESTED new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests-2.33.1.dist-info/WHEEL b/bioagent-bench/.venv/lib/python3.14/site-packages/requests-2.33.1.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..14a883f292bc96b20c2b76a3081991f2676523a9 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/requests-2.33.1.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (82.0.1) +Root-Is-Purelib: true +Tag: py3-none-any + diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests-2.33.1.dist-info/licenses/LICENSE b/bioagent-bench/.venv/lib/python3.14/site-packages/requests-2.33.1.dist-info/licenses/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..67db8588217f266eb561f75fae738656325deac9 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/requests-2.33.1.dist-info/licenses/LICENSE @@ -0,0 +1,175 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests-2.33.1.dist-info/licenses/NOTICE b/bioagent-bench/.venv/lib/python3.14/site-packages/requests-2.33.1.dist-info/licenses/NOTICE new file mode 100644 index 0000000000000000000000000000000000000000..1ff62db688277b77c83c1766dac7f165364d3528 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/requests-2.33.1.dist-info/licenses/NOTICE @@ -0,0 +1,2 @@ +Requests +Copyright 2019 Kenneth Reitz diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests-2.33.1.dist-info/top_level.txt b/bioagent-bench/.venv/lib/python3.14/site-packages/requests-2.33.1.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..f2293605cf1b01dca72aad0a15c45b72ed5429a2 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/requests-2.33.1.dist-info/top_level.txt @@ -0,0 +1 @@ +requests diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..8ecd8b81499e6b966c6029ea6a6bb90c145ee6e1 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__init__.py @@ -0,0 +1,183 @@ +# __ +# /__) _ _ _ _ _/ _ +# / ( (- (/ (/ (- _) / _) +# / + +""" +Requests HTTP Library +~~~~~~~~~~~~~~~~~~~~~ + +Requests is an HTTP library, written in Python, for human beings. +Basic GET usage: + + >>> import requests + >>> r = requests.get('https://www.python.org') + >>> r.status_code + 200 + >>> b'Python is a programming language' in r.content + True + +... or POST: + + >>> payload = dict(key1='value1', key2='value2') + >>> r = requests.post('https://httpbin.org/post', data=payload) + >>> print(r.text) + { + ... + "form": { + "key1": "value1", + "key2": "value2" + }, + ... + } + +The other HTTP methods are supported - see `requests.api`. Full documentation +is at . + +:copyright: (c) 2017 by Kenneth Reitz. +:license: Apache 2.0, see LICENSE for more details. +""" + +import warnings + +import urllib3 + +from .exceptions import RequestsDependencyWarning + +try: + from charset_normalizer import __version__ as charset_normalizer_version +except ImportError: + charset_normalizer_version = None + +try: + from chardet import __version__ as chardet_version +except ImportError: + chardet_version = None + + +def check_compatibility(urllib3_version, chardet_version, charset_normalizer_version): + urllib3_version = urllib3_version.split(".") + assert urllib3_version != ["dev"] # Verify urllib3 isn't installed from git. + + # Sometimes, urllib3 only reports its version as 16.1. + if len(urllib3_version) == 2: + urllib3_version.append("0") + + # Check urllib3 for compatibility. + major, minor, patch = urllib3_version # noqa: F811 + major, minor, patch = int(major), int(minor), int(patch) + # urllib3 >= 1.21.1 + assert major >= 1 + if major == 1: + assert minor >= 21 + + # Check charset_normalizer for compatibility. + if chardet_version: + major, minor, patch = chardet_version.split(".")[:3] + major, minor, patch = int(major), int(minor), int(patch) + # chardet_version >= 3.0.2, < 8.0.0 + assert (3, 0, 2) <= (major, minor, patch) < (8, 0, 0) + elif charset_normalizer_version: + major, minor, patch = charset_normalizer_version.split(".")[:3] + major, minor, patch = int(major), int(minor), int(patch) + # charset_normalizer >= 2.0.0 < 4.0.0 + assert (2, 0, 0) <= (major, minor, patch) < (4, 0, 0) + else: + warnings.warn( + "Unable to find acceptable character detection dependency " + "(chardet or charset_normalizer).", + RequestsDependencyWarning, + ) + + +def _check_cryptography(cryptography_version): + # cryptography < 1.3.4 + try: + cryptography_version = list(map(int, cryptography_version.split("."))) + except ValueError: + return + + if cryptography_version < [1, 3, 4]: + warning = ( + f"Old version of cryptography ({cryptography_version}) may cause slowdown." + ) + warnings.warn(warning, RequestsDependencyWarning) + + +# Check imported dependencies for compatibility. +try: + check_compatibility( + urllib3.__version__, chardet_version, charset_normalizer_version + ) +except (AssertionError, ValueError): + warnings.warn( + f"urllib3 ({urllib3.__version__}) or chardet " + f"({chardet_version})/charset_normalizer ({charset_normalizer_version}) " + "doesn't match a supported version!", + RequestsDependencyWarning, + ) + +# Attempt to enable urllib3's fallback for SNI support +# if the standard library doesn't support SNI or the +# 'ssl' library isn't available. +try: + try: + import ssl + except ImportError: + ssl = None + + if not getattr(ssl, "HAS_SNI", False): + from urllib3.contrib import pyopenssl + + pyopenssl.inject_into_urllib3() + + # Check cryptography version + from cryptography import __version__ as cryptography_version + + _check_cryptography(cryptography_version) +except ImportError: + pass + +# urllib3's DependencyWarnings should be silenced. +from urllib3.exceptions import DependencyWarning + +warnings.simplefilter("ignore", DependencyWarning) + +# Set default logging handler to avoid "No handler found" warnings. +import logging +from logging import NullHandler + +from . import packages, utils +from .__version__ import ( + __author__, + __author_email__, + __build__, + __cake__, + __copyright__, + __description__, + __license__, + __title__, + __url__, + __version__, +) +from .api import delete, get, head, options, patch, post, put, request +from .exceptions import ( + ConnectionError, + ConnectTimeout, + FileModeWarning, + HTTPError, + JSONDecodeError, + ReadTimeout, + RequestException, + Timeout, + TooManyRedirects, + URLRequired, +) +from .models import PreparedRequest, Request, Response +from .sessions import Session, session +from .status_codes import codes + +logging.getLogger(__name__).addHandler(NullHandler()) + +# FileModeWarnings go off per the default. +warnings.simplefilter("default", FileModeWarning, append=True) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/__init__.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..decbaf713ccd3a15a932d435d22da226ef49e8a5 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/__init__.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/__version__.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/__version__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cf0b8b117d5213daf309d868ed8ef81cfa5963ea Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/__version__.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/_internal_utils.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/_internal_utils.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1d530fdc8da67e29bcd3046c8857b7ce6553b0d8 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/_internal_utils.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/adapters.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/adapters.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3163e5a9a0ec603853236edf36bd95803a577a68 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/adapters.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/api.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/api.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8b617685ac414fbe5066f10bf51e1dbc1ddf4705 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/api.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/auth.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/auth.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e4da74391069c9c99161f90cc521a3c7a6dac546 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/auth.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/certs.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/certs.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..63030a279c69f80e5f3dccca1a62b113b4d7daf3 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/certs.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/compat.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/compat.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c0c1d7c4e700f96f8be86588b4e4225e83488d41 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/compat.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/cookies.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/cookies.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..47aebc3e18468316675b2c224bbb9a73f27418ab Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/cookies.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/exceptions.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/exceptions.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4741c6e6f265d73b74f95cf8069f6b6b61229b7d Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/exceptions.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/hooks.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/hooks.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4410362eff3dce4f868adb7c39b10e5829e54850 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/hooks.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/models.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/models.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e1676a169efac727876ae10594701df01b8928a4 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/models.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/packages.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/packages.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7dce05d63baea34f5be608cc05669962ebe4e42d Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/packages.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/sessions.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/sessions.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9510815fcbd3ae7a0efec8a68db581ef084d1832 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/sessions.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/status_codes.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/status_codes.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3c3cb17fecdbe69d1d4050b837caafc8b8a623d6 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/status_codes.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/structures.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/structures.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6185ac9041ee8a7af2013900bf12cea9294f52a3 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/structures.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/utils.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/utils.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f50bb4b465fbe07375b304aed1ccabd160034590 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__pycache__/utils.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__version__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__version__.py new file mode 100644 index 0000000000000000000000000000000000000000..7f8a52c8565c69b396e9cdc81a60fefdfe032dd7 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/__version__.py @@ -0,0 +1,14 @@ +# .-. .-. .-. . . .-. .-. .-. .-. +# |( |- |.| | | |- `-. | `-. +# ' ' `-' `-`.`-' `-' `-' ' `-' + +__title__ = "requests" +__description__ = "Python HTTP for Humans." +__url__ = "https://requests.readthedocs.io" +__version__ = "2.33.1" +__build__ = 0x023301 +__author__ = "Kenneth Reitz" +__author_email__ = "me@kennethreitz.org" +__license__ = "Apache-2.0" +__copyright__ = "Copyright Kenneth Reitz" +__cake__ = "\u2728 \U0001f370 \u2728" diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests/_internal_utils.py b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/_internal_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..b7cf4695b0702cf5ffaad181980b223279702eb1 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/_internal_utils.py @@ -0,0 +1,51 @@ +""" +requests._internal_utils +~~~~~~~~~~~~~~ + +Provides utility functions that are consumed internally by Requests +which depend on extremely few external helpers (such as compat) +""" + +import re + +from .compat import builtin_str + +_VALID_HEADER_NAME_RE_BYTE = re.compile(rb"^[^:\s][^:\r\n]*\Z") +_VALID_HEADER_NAME_RE_STR = re.compile(r"^[^:\s][^:\r\n]*\Z") +_VALID_HEADER_VALUE_RE_BYTE = re.compile(rb"^\S[^\r\n]*\Z|^\Z") +_VALID_HEADER_VALUE_RE_STR = re.compile(r"^\S[^\r\n]*\Z|^\Z") + +_HEADER_VALIDATORS_STR = (_VALID_HEADER_NAME_RE_STR, _VALID_HEADER_VALUE_RE_STR) +_HEADER_VALIDATORS_BYTE = (_VALID_HEADER_NAME_RE_BYTE, _VALID_HEADER_VALUE_RE_BYTE) +HEADER_VALIDATORS = { + bytes: _HEADER_VALIDATORS_BYTE, + str: _HEADER_VALIDATORS_STR, +} + + +def to_native_string(string, encoding="ascii"): + """Given a string object, regardless of type, returns a representation of + that string in the native string type, encoding and decoding where + necessary. This assumes ASCII unless told otherwise. + """ + if isinstance(string, builtin_str): + out = string + else: + out = string.decode(encoding) + + return out + + +def unicode_is_ascii(u_string): + """Determine if unicode string only contains ASCII characters. + + :param str u_string: unicode string to check. Must be unicode + and not Python 2 `str`. + :rtype: bool + """ + assert isinstance(u_string, str) + try: + u_string.encode("ascii") + return True + except UnicodeEncodeError: + return False diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests/adapters.py b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/adapters.py new file mode 100644 index 0000000000000000000000000000000000000000..98f74465f211078899448fc1737127aa5435db4a --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/adapters.py @@ -0,0 +1,697 @@ +""" +requests.adapters +~~~~~~~~~~~~~~~~~ + +This module contains the transport adapters that Requests uses to define +and maintain connections. +""" + +import os.path +import socket # noqa: F401 +import typing +import warnings + +from urllib3.exceptions import ( + ClosedPoolError, + ConnectTimeoutError, + LocationValueError, + MaxRetryError, + NewConnectionError, + ProtocolError, + ReadTimeoutError, + ResponseError, +) +from urllib3.exceptions import HTTPError as _HTTPError +from urllib3.exceptions import InvalidHeader as _InvalidHeader +from urllib3.exceptions import ProxyError as _ProxyError +from urllib3.exceptions import SSLError as _SSLError +from urllib3.poolmanager import PoolManager, proxy_from_url +from urllib3.util import Timeout as TimeoutSauce +from urllib3.util import parse_url +from urllib3.util.retry import Retry + +from .auth import _basic_auth_str +from .compat import basestring, urlparse +from .cookies import extract_cookies_to_jar +from .exceptions import ( + ConnectionError, + ConnectTimeout, + InvalidHeader, + InvalidProxyURL, + InvalidSchema, + InvalidURL, + ProxyError, + ReadTimeout, + RetryError, + SSLError, +) +from .models import Response +from .structures import CaseInsensitiveDict +from .utils import ( + DEFAULT_CA_BUNDLE_PATH, + get_auth_from_url, + get_encoding_from_headers, + prepend_scheme_if_needed, + select_proxy, + urldefragauth, +) + +try: + from urllib3.contrib.socks import SOCKSProxyManager +except ImportError: + + def SOCKSProxyManager(*args, **kwargs): + raise InvalidSchema("Missing dependencies for SOCKS support.") + + +if typing.TYPE_CHECKING: + from .models import PreparedRequest + + +DEFAULT_POOLBLOCK = False +DEFAULT_POOLSIZE = 10 +DEFAULT_RETRIES = 0 +DEFAULT_POOL_TIMEOUT = None + + +def _urllib3_request_context( + request: "PreparedRequest", + verify: "bool | str | None", + client_cert: "tuple[str, str] | str | None", + poolmanager: "PoolManager", +) -> "(dict[str, typing.Any], dict[str, typing.Any])": + host_params = {} + pool_kwargs = {} + parsed_request_url = urlparse(request.url) + scheme = parsed_request_url.scheme.lower() + port = parsed_request_url.port + + cert_reqs = "CERT_REQUIRED" + if verify is False: + cert_reqs = "CERT_NONE" + elif isinstance(verify, str): + if not os.path.isdir(verify): + pool_kwargs["ca_certs"] = verify + else: + pool_kwargs["ca_cert_dir"] = verify + pool_kwargs["cert_reqs"] = cert_reqs + if client_cert is not None: + if isinstance(client_cert, tuple) and len(client_cert) == 2: + pool_kwargs["cert_file"] = client_cert[0] + pool_kwargs["key_file"] = client_cert[1] + else: + # According to our docs, we allow users to specify just the client + # cert path + pool_kwargs["cert_file"] = client_cert + host_params = { + "scheme": scheme, + "host": parsed_request_url.hostname, + "port": port, + } + return host_params, pool_kwargs + + +class BaseAdapter: + """The Base Transport Adapter""" + + def __init__(self): + super().__init__() + + def send( + self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None + ): + """Sends PreparedRequest object. Returns Response object. + + :param request: The :class:`PreparedRequest ` being sent. + :param stream: (optional) Whether to stream the request content. + :param timeout: (optional) How long to wait for the server to send + data before giving up, as a float, or a :ref:`(connect timeout, + read timeout) ` tuple. + :type timeout: float or tuple + :param verify: (optional) Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use + :param cert: (optional) Any user-provided SSL certificate to be trusted. + :param proxies: (optional) The proxies dictionary to apply to the request. + """ + raise NotImplementedError + + def close(self): + """Cleans up adapter specific items.""" + raise NotImplementedError + + +class HTTPAdapter(BaseAdapter): + """The built-in HTTP Adapter for urllib3. + + Provides a general-case interface for Requests sessions to contact HTTP and + HTTPS urls by implementing the Transport Adapter interface. This class will + usually be created by the :class:`Session ` class under the + covers. + + :param pool_connections: The number of urllib3 connection pools to cache. + :param pool_maxsize: The maximum number of connections to save in the pool. + :param max_retries: The maximum number of retries each connection + should attempt. Note, this applies only to failed DNS lookups, socket + connections and connection timeouts, never to requests where data has + made it to the server. By default, Requests does not retry failed + connections. If you need granular control over the conditions under + which we retry a request, import urllib3's ``Retry`` class and pass + that instead. + :param pool_block: Whether the connection pool should block for connections. + + Usage:: + + >>> import requests + >>> s = requests.Session() + >>> a = requests.adapters.HTTPAdapter(max_retries=3) + >>> s.mount('http://', a) + """ + + __attrs__ = [ + "max_retries", + "config", + "_pool_connections", + "_pool_maxsize", + "_pool_block", + ] + + def __init__( + self, + pool_connections=DEFAULT_POOLSIZE, + pool_maxsize=DEFAULT_POOLSIZE, + max_retries=DEFAULT_RETRIES, + pool_block=DEFAULT_POOLBLOCK, + ): + if max_retries == DEFAULT_RETRIES: + self.max_retries = Retry(0, read=False) + else: + self.max_retries = Retry.from_int(max_retries) + self.config = {} + self.proxy_manager = {} + + super().__init__() + + self._pool_connections = pool_connections + self._pool_maxsize = pool_maxsize + self._pool_block = pool_block + + self.init_poolmanager(pool_connections, pool_maxsize, block=pool_block) + + def __getstate__(self): + return {attr: getattr(self, attr, None) for attr in self.__attrs__} + + def __setstate__(self, state): + # Can't handle by adding 'proxy_manager' to self.__attrs__ because + # self.poolmanager uses a lambda function, which isn't pickleable. + self.proxy_manager = {} + self.config = {} + + for attr, value in state.items(): + setattr(self, attr, value) + + self.init_poolmanager( + self._pool_connections, self._pool_maxsize, block=self._pool_block + ) + + def init_poolmanager( + self, connections, maxsize, block=DEFAULT_POOLBLOCK, **pool_kwargs + ): + """Initializes a urllib3 PoolManager. + + This method should not be called from user code, and is only + exposed for use when subclassing the + :class:`HTTPAdapter `. + + :param connections: The number of urllib3 connection pools to cache. + :param maxsize: The maximum number of connections to save in the pool. + :param block: Block when no free connections are available. + :param pool_kwargs: Extra keyword arguments used to initialize the Pool Manager. + """ + # save these values for pickling + self._pool_connections = connections + self._pool_maxsize = maxsize + self._pool_block = block + + self.poolmanager = PoolManager( + num_pools=connections, + maxsize=maxsize, + block=block, + **pool_kwargs, + ) + + def proxy_manager_for(self, proxy, **proxy_kwargs): + """Return urllib3 ProxyManager for the given proxy. + + This method should not be called from user code, and is only + exposed for use when subclassing the + :class:`HTTPAdapter `. + + :param proxy: The proxy to return a urllib3 ProxyManager for. + :param proxy_kwargs: Extra keyword arguments used to configure the Proxy Manager. + :returns: ProxyManager + :rtype: urllib3.ProxyManager + """ + if proxy in self.proxy_manager: + manager = self.proxy_manager[proxy] + elif proxy.lower().startswith("socks"): + username, password = get_auth_from_url(proxy) + manager = self.proxy_manager[proxy] = SOCKSProxyManager( + proxy, + username=username, + password=password, + num_pools=self._pool_connections, + maxsize=self._pool_maxsize, + block=self._pool_block, + **proxy_kwargs, + ) + else: + proxy_headers = self.proxy_headers(proxy) + manager = self.proxy_manager[proxy] = proxy_from_url( + proxy, + proxy_headers=proxy_headers, + num_pools=self._pool_connections, + maxsize=self._pool_maxsize, + block=self._pool_block, + **proxy_kwargs, + ) + + return manager + + def cert_verify(self, conn, url, verify, cert): + """Verify a SSL certificate. This method should not be called from user + code, and is only exposed for use when subclassing the + :class:`HTTPAdapter `. + + :param conn: The urllib3 connection object associated with the cert. + :param url: The requested URL. + :param verify: Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use + :param cert: The SSL certificate to verify. + """ + if url.lower().startswith("https") and verify: + cert_loc = None + + # Allow self-specified cert location. + if verify is not True: + cert_loc = verify + + if not cert_loc: + cert_loc = DEFAULT_CA_BUNDLE_PATH + + if not cert_loc or not os.path.exists(cert_loc): + raise OSError( + f"Could not find a suitable TLS CA certificate bundle, " + f"invalid path: {cert_loc}" + ) + + conn.cert_reqs = "CERT_REQUIRED" + + if not os.path.isdir(cert_loc): + conn.ca_certs = cert_loc + else: + conn.ca_cert_dir = cert_loc + else: + conn.cert_reqs = "CERT_NONE" + conn.ca_certs = None + conn.ca_cert_dir = None + + if cert: + if not isinstance(cert, basestring): + conn.cert_file = cert[0] + conn.key_file = cert[1] + else: + conn.cert_file = cert + conn.key_file = None + if conn.cert_file and not os.path.exists(conn.cert_file): + raise OSError( + f"Could not find the TLS certificate file, " + f"invalid path: {conn.cert_file}" + ) + if conn.key_file and not os.path.exists(conn.key_file): + raise OSError( + f"Could not find the TLS key file, invalid path: {conn.key_file}" + ) + + def build_response(self, req, resp): + """Builds a :class:`Response ` object from a urllib3 + response. This should not be called from user code, and is only exposed + for use when subclassing the + :class:`HTTPAdapter ` + + :param req: The :class:`PreparedRequest ` used to generate the response. + :param resp: The urllib3 response object. + :rtype: requests.Response + """ + response = Response() + + # Fallback to None if there's no status_code, for whatever reason. + response.status_code = getattr(resp, "status", None) + + # Make headers case-insensitive. + response.headers = CaseInsensitiveDict(getattr(resp, "headers", {})) + + # Set encoding. + response.encoding = get_encoding_from_headers(response.headers) + response.raw = resp + response.reason = response.raw.reason + + if isinstance(req.url, bytes): + response.url = req.url.decode("utf-8") + else: + response.url = req.url + + # Add new cookies from the server. + extract_cookies_to_jar(response.cookies, req, resp) + + # Give the Response some context. + response.request = req + response.connection = self + + return response + + def build_connection_pool_key_attributes(self, request, verify, cert=None): + """Build the PoolKey attributes used by urllib3 to return a connection. + + This looks at the PreparedRequest, the user-specified verify value, + and the value of the cert parameter to determine what PoolKey values + to use to select a connection from a given urllib3 Connection Pool. + + The SSL related pool key arguments are not consistently set. As of + this writing, use the following to determine what keys may be in that + dictionary: + + * If ``verify`` is ``True``, ``"ssl_context"`` will be set and will be the + default Requests SSL Context + * If ``verify`` is ``False``, ``"ssl_context"`` will not be set but + ``"cert_reqs"`` will be set + * If ``verify`` is a string, (i.e., it is a user-specified trust bundle) + ``"ca_certs"`` will be set if the string is not a directory recognized + by :py:func:`os.path.isdir`, otherwise ``"ca_cert_dir"`` will be + set. + * If ``"cert"`` is specified, ``"cert_file"`` will always be set. If + ``"cert"`` is a tuple with a second item, ``"key_file"`` will also + be present + + To override these settings, one may subclass this class, call this + method and use the above logic to change parameters as desired. For + example, if one wishes to use a custom :py:class:`ssl.SSLContext` one + must both set ``"ssl_context"`` and based on what else they require, + alter the other keys to ensure the desired behaviour. + + :param request: + The PreparedReqest being sent over the connection. + :type request: + :class:`~requests.models.PreparedRequest` + :param verify: + Either a boolean, in which case it controls whether + we verify the server's TLS certificate, or a string, in which case it + must be a path to a CA bundle to use. + :param cert: + (optional) Any user-provided SSL certificate for client + authentication (a.k.a., mTLS). This may be a string (i.e., just + the path to a file which holds both certificate and key) or a + tuple of length 2 with the certificate file path and key file + path. + :returns: + A tuple of two dictionaries. The first is the "host parameters" + portion of the Pool Key including scheme, hostname, and port. The + second is a dictionary of SSLContext related parameters. + """ + return _urllib3_request_context(request, verify, cert, self.poolmanager) + + def get_connection_with_tls_context(self, request, verify, proxies=None, cert=None): + """Returns a urllib3 connection for the given request and TLS settings. + This should not be called from user code, and is only exposed for use + when subclassing the :class:`HTTPAdapter `. + + :param request: + The :class:`PreparedRequest ` object to be sent + over the connection. + :param verify: + Either a boolean, in which case it controls whether we verify the + server's TLS certificate, or a string, in which case it must be a + path to a CA bundle to use. + :param proxies: + (optional) The proxies dictionary to apply to the request. + :param cert: + (optional) Any user-provided SSL certificate to be used for client + authentication (a.k.a., mTLS). + :rtype: + urllib3.ConnectionPool + """ + proxy = select_proxy(request.url, proxies) + try: + host_params, pool_kwargs = self.build_connection_pool_key_attributes( + request, + verify, + cert, + ) + except ValueError as e: + raise InvalidURL(e, request=request) + if proxy: + proxy = prepend_scheme_if_needed(proxy, "http") + proxy_url = parse_url(proxy) + if not proxy_url.host: + raise InvalidProxyURL( + "Please check proxy URL. It is malformed " + "and could be missing the host." + ) + proxy_manager = self.proxy_manager_for(proxy) + conn = proxy_manager.connection_from_host( + **host_params, pool_kwargs=pool_kwargs + ) + else: + # Only scheme should be lower case + conn = self.poolmanager.connection_from_host( + **host_params, pool_kwargs=pool_kwargs + ) + + return conn + + def get_connection(self, url, proxies=None): + """DEPRECATED: Users should move to `get_connection_with_tls_context` + for all subclasses of HTTPAdapter using Requests>=2.32.2. + + Returns a urllib3 connection for the given URL. This should not be + called from user code, and is only exposed for use when subclassing the + :class:`HTTPAdapter `. + + :param url: The URL to connect to. + :param proxies: (optional) A Requests-style dictionary of proxies used on this request. + :rtype: urllib3.ConnectionPool + """ + warnings.warn( + ( + "`get_connection` has been deprecated in favor of " + "`get_connection_with_tls_context`. Custom HTTPAdapter subclasses " + "will need to migrate for Requests>=2.32.2. Please see " + "https://github.com/psf/requests/pull/6710 for more details." + ), + DeprecationWarning, + ) + proxy = select_proxy(url, proxies) + + if proxy: + proxy = prepend_scheme_if_needed(proxy, "http") + proxy_url = parse_url(proxy) + if not proxy_url.host: + raise InvalidProxyURL( + "Please check proxy URL. It is malformed " + "and could be missing the host." + ) + proxy_manager = self.proxy_manager_for(proxy) + conn = proxy_manager.connection_from_url(url) + else: + # Only scheme should be lower case + parsed = urlparse(url) + url = parsed.geturl() + conn = self.poolmanager.connection_from_url(url) + + return conn + + def close(self): + """Disposes of any internal state. + + Currently, this closes the PoolManager and any active ProxyManager, + which closes any pooled connections. + """ + self.poolmanager.clear() + for proxy in self.proxy_manager.values(): + proxy.clear() + + def request_url(self, request, proxies): + """Obtain the url to use when making the final request. + + If the message is being sent through a HTTP proxy, the full URL has to + be used. Otherwise, we should only use the path portion of the URL. + + This should not be called from user code, and is only exposed for use + when subclassing the + :class:`HTTPAdapter `. + + :param request: The :class:`PreparedRequest ` being sent. + :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs. + :rtype: str + """ + proxy = select_proxy(request.url, proxies) + scheme = urlparse(request.url).scheme + + is_proxied_http_request = proxy and scheme != "https" + using_socks_proxy = False + if proxy: + proxy_scheme = urlparse(proxy).scheme.lower() + using_socks_proxy = proxy_scheme.startswith("socks") + + url = request.path_url + if url.startswith("//"): # Don't confuse urllib3 + url = f"/{url.lstrip('/')}" + + if is_proxied_http_request and not using_socks_proxy: + url = urldefragauth(request.url) + + return url + + def add_headers(self, request, **kwargs): + """Add any headers needed by the connection. As of v2.0 this does + nothing by default, but is left for overriding by users that subclass + the :class:`HTTPAdapter `. + + This should not be called from user code, and is only exposed for use + when subclassing the + :class:`HTTPAdapter `. + + :param request: The :class:`PreparedRequest ` to add headers to. + :param kwargs: The keyword arguments from the call to send(). + """ + pass + + def proxy_headers(self, proxy): + """Returns a dictionary of the headers to add to any request sent + through a proxy. This works with urllib3 magic to ensure that they are + correctly sent to the proxy, rather than in a tunnelled request if + CONNECT is being used. + + This should not be called from user code, and is only exposed for use + when subclassing the + :class:`HTTPAdapter `. + + :param proxy: The url of the proxy being used for this request. + :rtype: dict + """ + headers = {} + username, password = get_auth_from_url(proxy) + + if username: + headers["Proxy-Authorization"] = _basic_auth_str(username, password) + + return headers + + def send( + self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None + ): + """Sends PreparedRequest object. Returns Response object. + + :param request: The :class:`PreparedRequest ` being sent. + :param stream: (optional) Whether to stream the request content. + :param timeout: (optional) How long to wait for the server to send + data before giving up, as a float, or a :ref:`(connect timeout, + read timeout) ` tuple. + :type timeout: float or tuple or urllib3 Timeout object + :param verify: (optional) Either a boolean, in which case it controls whether + we verify the server's TLS certificate, or a string, in which case it + must be a path to a CA bundle to use + :param cert: (optional) Any user-provided SSL certificate to be trusted. + :param proxies: (optional) The proxies dictionary to apply to the request. + :rtype: requests.Response + """ + + try: + conn = self.get_connection_with_tls_context( + request, verify, proxies=proxies, cert=cert + ) + except LocationValueError as e: + raise InvalidURL(e, request=request) + + self.cert_verify(conn, request.url, verify, cert) + url = self.request_url(request, proxies) + self.add_headers( + request, + stream=stream, + timeout=timeout, + verify=verify, + cert=cert, + proxies=proxies, + ) + + chunked = not (request.body is None or "Content-Length" in request.headers) + + if isinstance(timeout, tuple): + try: + connect, read = timeout + timeout = TimeoutSauce(connect=connect, read=read) + except ValueError: + raise ValueError( + f"Invalid timeout {timeout}. Pass a (connect, read) timeout tuple, " + f"or a single float to set both timeouts to the same value." + ) + elif isinstance(timeout, TimeoutSauce): + pass + else: + timeout = TimeoutSauce(connect=timeout, read=timeout) + + try: + resp = conn.urlopen( + method=request.method, + url=url, + body=request.body, + headers=request.headers, + redirect=False, + assert_same_host=False, + preload_content=False, + decode_content=False, + retries=self.max_retries, + timeout=timeout, + chunked=chunked, + ) + + except (ProtocolError, OSError) as err: + raise ConnectionError(err, request=request) + + except MaxRetryError as e: + if isinstance(e.reason, ConnectTimeoutError): + # TODO: Remove this in 3.0.0: see #2811 + if not isinstance(e.reason, NewConnectionError): + raise ConnectTimeout(e, request=request) + + if isinstance(e.reason, ResponseError): + raise RetryError(e, request=request) + + if isinstance(e.reason, _ProxyError): + raise ProxyError(e, request=request) + + if isinstance(e.reason, _SSLError): + # This branch is for urllib3 v1.22 and later. + raise SSLError(e, request=request) + + raise ConnectionError(e, request=request) + + except ClosedPoolError as e: + raise ConnectionError(e, request=request) + + except _ProxyError as e: + raise ProxyError(e) + + except (_SSLError, _HTTPError) as e: + if isinstance(e, _SSLError): + # This branch is for urllib3 versions earlier than v1.22 + raise SSLError(e, request=request) + elif isinstance(e, ReadTimeoutError): + raise ReadTimeout(e, request=request) + elif isinstance(e, _InvalidHeader): + raise InvalidHeader(e, request=request) + else: + raise + + return self.build_response(request, resp) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests/api.py b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/api.py new file mode 100644 index 0000000000000000000000000000000000000000..5960744552e7f8eea815429e7bdad38b0cc2741d --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/api.py @@ -0,0 +1,157 @@ +""" +requests.api +~~~~~~~~~~~~ + +This module implements the Requests API. + +:copyright: (c) 2012 by Kenneth Reitz. +:license: Apache2, see LICENSE for more details. +""" + +from . import sessions + + +def request(method, url, **kwargs): + """Constructs and sends a :class:`Request `. + + :param method: method for the new :class:`Request` object: ``GET``, ``OPTIONS``, ``HEAD``, ``POST``, ``PUT``, ``PATCH``, or ``DELETE``. + :param url: URL for the new :class:`Request` object. + :param params: (optional) Dictionary, list of tuples or bytes to send + in the query string for the :class:`Request`. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`. + :param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`. + :param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`. + :param files: (optional) Dictionary of ``'name': file-like-objects`` (or ``{'name': file-tuple}``) for multipart encoding upload. + ``file-tuple`` can be a 2-tuple ``('filename', fileobj)``, 3-tuple ``('filename', fileobj, 'content_type')`` + or a 4-tuple ``('filename', fileobj, 'content_type', custom_headers)``, where ``'content_type'`` is a string + defining the content type of the given file and ``custom_headers`` a dict-like object containing additional headers + to add for the file. + :param auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth. + :param timeout: (optional) How many seconds to wait for the server to send data + before giving up, as a float, or a :ref:`(connect timeout, read + timeout) ` tuple. + :type timeout: float or tuple + :param allow_redirects: (optional) Boolean. Enable/disable GET/OPTIONS/POST/PUT/PATCH/DELETE/HEAD redirection. Defaults to ``True``. + :type allow_redirects: bool + :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. + :param verify: (optional) Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use. Defaults to ``True``. + :param stream: (optional) if ``False``, the response content will be immediately downloaded. + :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair. + :return: :class:`Response ` object + :rtype: requests.Response + + Usage:: + + >>> import requests + >>> req = requests.request('GET', 'https://httpbin.org/get') + >>> req + + """ + + # By using the 'with' statement we are sure the session is closed, thus we + # avoid leaving sockets open which can trigger a ResourceWarning in some + # cases, and look like a memory leak in others. + with sessions.Session() as session: + return session.request(method=method, url=url, **kwargs) + + +def get(url, params=None, **kwargs): + r"""Sends a GET request. + + :param url: URL for the new :class:`Request` object. + :param params: (optional) Dictionary, list of tuples or bytes to send + in the query string for the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request("get", url, params=params, **kwargs) + + +def options(url, **kwargs): + r"""Sends an OPTIONS request. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request("options", url, **kwargs) + + +def head(url, **kwargs): + r"""Sends a HEAD request. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. If + `allow_redirects` is not provided, it will be set to `False` (as + opposed to the default :meth:`request` behavior). + :return: :class:`Response ` object + :rtype: requests.Response + """ + + kwargs.setdefault("allow_redirects", False) + return request("head", url, **kwargs) + + +def post(url, data=None, json=None, **kwargs): + r"""Sends a POST request. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request("post", url, data=data, json=json, **kwargs) + + +def put(url, data=None, **kwargs): + r"""Sends a PUT request. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request("put", url, data=data, **kwargs) + + +def patch(url, data=None, **kwargs): + r"""Sends a PATCH request. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request("patch", url, data=data, **kwargs) + + +def delete(url, **kwargs): + r"""Sends a DELETE request. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response + """ + + return request("delete", url, **kwargs) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests/auth.py b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/auth.py new file mode 100644 index 0000000000000000000000000000000000000000..c39b645189ddaf03a5700e62521f39dcc24ecb0a --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/auth.py @@ -0,0 +1,314 @@ +""" +requests.auth +~~~~~~~~~~~~~ + +This module contains the authentication handlers for Requests. +""" + +import hashlib +import os +import re +import threading +import time +import warnings +from base64 import b64encode + +from ._internal_utils import to_native_string +from .compat import basestring, str, urlparse +from .cookies import extract_cookies_to_jar +from .utils import parse_dict_header + +CONTENT_TYPE_FORM_URLENCODED = "application/x-www-form-urlencoded" +CONTENT_TYPE_MULTI_PART = "multipart/form-data" + + +def _basic_auth_str(username, password): + """Returns a Basic Auth string.""" + + # "I want us to put a big-ol' comment on top of it that + # says that this behaviour is dumb but we need to preserve + # it because people are relying on it." + # - Lukasa + # + # These are here solely to maintain backwards compatibility + # for things like ints. This will be removed in 3.0.0. + if not isinstance(username, basestring): + warnings.warn( + "Non-string usernames will no longer be supported in Requests " + f"3.0.0. Please convert the object you've passed in ({username!r}) to " + "a string or bytes object in the near future to avoid " + "problems.", + category=DeprecationWarning, + ) + username = str(username) + + if not isinstance(password, basestring): + warnings.warn( + "Non-string passwords will no longer be supported in Requests " + f"3.0.0. Please convert the object you've passed in ({type(password)!r}) to " + "a string or bytes object in the near future to avoid " + "problems.", + category=DeprecationWarning, + ) + password = str(password) + # -- End Removal -- + + if isinstance(username, str): + username = username.encode("latin1") + + if isinstance(password, str): + password = password.encode("latin1") + + authstr = "Basic " + to_native_string( + b64encode(b":".join((username, password))).strip() + ) + + return authstr + + +class AuthBase: + """Base class that all auth implementations derive from""" + + def __call__(self, r): + raise NotImplementedError("Auth hooks must be callable.") + + +class HTTPBasicAuth(AuthBase): + """Attaches HTTP Basic Authentication to the given Request object.""" + + def __init__(self, username, password): + self.username = username + self.password = password + + def __eq__(self, other): + return all( + [ + self.username == getattr(other, "username", None), + self.password == getattr(other, "password", None), + ] + ) + + def __ne__(self, other): + return not self == other + + def __call__(self, r): + r.headers["Authorization"] = _basic_auth_str(self.username, self.password) + return r + + +class HTTPProxyAuth(HTTPBasicAuth): + """Attaches HTTP Proxy Authentication to a given Request object.""" + + def __call__(self, r): + r.headers["Proxy-Authorization"] = _basic_auth_str(self.username, self.password) + return r + + +class HTTPDigestAuth(AuthBase): + """Attaches HTTP Digest Authentication to the given Request object.""" + + def __init__(self, username, password): + self.username = username + self.password = password + # Keep state in per-thread local storage + self._thread_local = threading.local() + + def init_per_thread_state(self): + # Ensure state is initialized just once per-thread + if not hasattr(self._thread_local, "init"): + self._thread_local.init = True + self._thread_local.last_nonce = "" + self._thread_local.nonce_count = 0 + self._thread_local.chal = {} + self._thread_local.pos = None + self._thread_local.num_401_calls = None + + def build_digest_header(self, method, url): + """ + :rtype: str + """ + + realm = self._thread_local.chal["realm"] + nonce = self._thread_local.chal["nonce"] + qop = self._thread_local.chal.get("qop") + algorithm = self._thread_local.chal.get("algorithm") + opaque = self._thread_local.chal.get("opaque") + hash_utf8 = None + + if algorithm is None: + _algorithm = "MD5" + else: + _algorithm = algorithm.upper() + # lambdas assume digest modules are imported at the top level + if _algorithm == "MD5" or _algorithm == "MD5-SESS": + + def md5_utf8(x): + if isinstance(x, str): + x = x.encode("utf-8") + return hashlib.md5(x).hexdigest() + + hash_utf8 = md5_utf8 + elif _algorithm == "SHA": + + def sha_utf8(x): + if isinstance(x, str): + x = x.encode("utf-8") + return hashlib.sha1(x).hexdigest() + + hash_utf8 = sha_utf8 + elif _algorithm == "SHA-256": + + def sha256_utf8(x): + if isinstance(x, str): + x = x.encode("utf-8") + return hashlib.sha256(x).hexdigest() + + hash_utf8 = sha256_utf8 + elif _algorithm == "SHA-512": + + def sha512_utf8(x): + if isinstance(x, str): + x = x.encode("utf-8") + return hashlib.sha512(x).hexdigest() + + hash_utf8 = sha512_utf8 + + KD = lambda s, d: hash_utf8(f"{s}:{d}") # noqa:E731 + + if hash_utf8 is None: + return None + + # XXX not implemented yet + entdig = None + p_parsed = urlparse(url) + #: path is request-uri defined in RFC 2616 which should not be empty + path = p_parsed.path or "/" + if p_parsed.query: + path += f"?{p_parsed.query}" + + A1 = f"{self.username}:{realm}:{self.password}" + A2 = f"{method}:{path}" + + HA1 = hash_utf8(A1) + HA2 = hash_utf8(A2) + + if nonce == self._thread_local.last_nonce: + self._thread_local.nonce_count += 1 + else: + self._thread_local.nonce_count = 1 + ncvalue = f"{self._thread_local.nonce_count:08x}" + s = str(self._thread_local.nonce_count).encode("utf-8") + s += nonce.encode("utf-8") + s += time.ctime().encode("utf-8") + s += os.urandom(8) + + cnonce = hashlib.sha1(s).hexdigest()[:16] + if _algorithm == "MD5-SESS": + HA1 = hash_utf8(f"{HA1}:{nonce}:{cnonce}") + + if not qop: + respdig = KD(HA1, f"{nonce}:{HA2}") + elif qop == "auth" or "auth" in qop.split(","): + noncebit = f"{nonce}:{ncvalue}:{cnonce}:auth:{HA2}" + respdig = KD(HA1, noncebit) + else: + # XXX handle auth-int. + return None + + self._thread_local.last_nonce = nonce + + # XXX should the partial digests be encoded too? + base = ( + f'username="{self.username}", realm="{realm}", nonce="{nonce}", ' + f'uri="{path}", response="{respdig}"' + ) + if opaque: + base += f', opaque="{opaque}"' + if algorithm: + base += f', algorithm="{algorithm}"' + if entdig: + base += f', digest="{entdig}"' + if qop: + base += f', qop="auth", nc={ncvalue}, cnonce="{cnonce}"' + + return f"Digest {base}" + + def handle_redirect(self, r, **kwargs): + """Reset num_401_calls counter on redirects.""" + if r.is_redirect: + self._thread_local.num_401_calls = 1 + + def handle_401(self, r, **kwargs): + """ + Takes the given response and tries digest-auth, if needed. + + :rtype: requests.Response + """ + + # If response is not 4xx, do not auth + # See https://github.com/psf/requests/issues/3772 + if not 400 <= r.status_code < 500: + self._thread_local.num_401_calls = 1 + return r + + if self._thread_local.pos is not None: + # Rewind the file position indicator of the body to where + # it was to resend the request. + r.request.body.seek(self._thread_local.pos) + s_auth = r.headers.get("www-authenticate", "") + + if "digest" in s_auth.lower() and self._thread_local.num_401_calls < 2: + self._thread_local.num_401_calls += 1 + pat = re.compile(r"digest ", flags=re.IGNORECASE) + self._thread_local.chal = parse_dict_header(pat.sub("", s_auth, count=1)) + + # Consume content and release the original connection + # to allow our new request to reuse the same one. + r.content + r.close() + prep = r.request.copy() + extract_cookies_to_jar(prep._cookies, r.request, r.raw) + prep.prepare_cookies(prep._cookies) + + prep.headers["Authorization"] = self.build_digest_header( + prep.method, prep.url + ) + _r = r.connection.send(prep, **kwargs) + _r.history.append(r) + _r.request = prep + + return _r + + self._thread_local.num_401_calls = 1 + return r + + def __call__(self, r): + # Initialize per-thread state, if needed + self.init_per_thread_state() + # If we have a saved nonce, skip the 401 + if self._thread_local.last_nonce: + r.headers["Authorization"] = self.build_digest_header(r.method, r.url) + try: + self._thread_local.pos = r.body.tell() + except AttributeError: + # In the case of HTTPDigestAuth being reused and the body of + # the previous request was a file-like object, pos has the + # file position of the previous body. Ensure it's set to + # None. + self._thread_local.pos = None + r.register_hook("response", self.handle_401) + r.register_hook("response", self.handle_redirect) + self._thread_local.num_401_calls = 1 + + return r + + def __eq__(self, other): + return all( + [ + self.username == getattr(other, "username", None), + self.password == getattr(other, "password", None), + ] + ) + + def __ne__(self, other): + return not self == other diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests/certs.py b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/certs.py new file mode 100644 index 0000000000000000000000000000000000000000..4f85ac070bc0230af8155bcadfaa96165268cede --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/certs.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python + +""" +requests.certs +~~~~~~~~~~~~~~ + +This module returns the preferred default CA certificate bundle. There is +only one — the one from the certifi package. + +If you are packaging Requests, e.g., for a Linux distribution or a managed +environment, you can change the definition of where() to return a separately +packaged CA bundle. +""" + +from certifi import where + +if __name__ == "__main__": + print(where()) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests/compat.py b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/compat.py new file mode 100644 index 0000000000000000000000000000000000000000..7f9d754350c9fe28db41e328ea880b9e4b20cc8b --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/compat.py @@ -0,0 +1,106 @@ +""" +requests.compat +~~~~~~~~~~~~~~~ + +This module previously handled import compatibility issues +between Python 2 and Python 3. It remains for backwards +compatibility until the next major version. +""" + +import importlib +import sys + +# ------- +# urllib3 +# ------- +from urllib3 import __version__ as urllib3_version + +# Detect which major version of urllib3 is being used. +try: + is_urllib3_1 = int(urllib3_version.split(".")[0]) == 1 +except (TypeError, AttributeError): + # If we can't discern a version, prefer old functionality. + is_urllib3_1 = True + +# ------------------- +# Character Detection +# ------------------- + + +def _resolve_char_detection(): + """Find supported character detection libraries.""" + chardet = None + for lib in ("chardet", "charset_normalizer"): + if chardet is None: + try: + chardet = importlib.import_module(lib) + except ImportError: + pass + return chardet + + +chardet = _resolve_char_detection() + +# ------- +# Pythons +# ------- + +# Syntax sugar. +_ver = sys.version_info + +#: Python 2.x? +is_py2 = _ver[0] == 2 + +#: Python 3.x? +is_py3 = _ver[0] == 3 + +# json/simplejson module import resolution +has_simplejson = False +try: + import simplejson as json + + has_simplejson = True +except ImportError: + import json + +if has_simplejson: + from simplejson import JSONDecodeError +else: + from json import JSONDecodeError + +# Keep OrderedDict for backwards compatibility. +from collections import OrderedDict +from collections.abc import Callable, Mapping, MutableMapping +from http import cookiejar as cookielib +from http.cookies import Morsel +from io import StringIO + +# -------------- +# Legacy Imports +# -------------- +from urllib.parse import ( + quote, + quote_plus, + unquote, + unquote_plus, + urldefrag, + urlencode, + urljoin, + urlparse, + urlsplit, + urlunparse, +) +from urllib.request import ( + getproxies, + getproxies_environment, + parse_http_list, + proxy_bypass, + proxy_bypass_environment, +) + +builtin_str = str +str = str +bytes = bytes +basestring = (str, bytes) +numeric_types = (int, float) +integer_types = (int,) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests/cookies.py b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/cookies.py new file mode 100644 index 0000000000000000000000000000000000000000..f69d0cda9e1c893401015a09f2db2de5a5960fd2 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/cookies.py @@ -0,0 +1,561 @@ +""" +requests.cookies +~~~~~~~~~~~~~~~~ + +Compatibility code to be able to use `http.cookiejar.CookieJar` with requests. + +requests.utils imports from here, so be careful with imports. +""" + +import calendar +import copy +import time + +from ._internal_utils import to_native_string +from .compat import Morsel, MutableMapping, cookielib, urlparse, urlunparse + +try: + import threading +except ImportError: + import dummy_threading as threading + + +class MockRequest: + """Wraps a `requests.Request` to mimic a `urllib2.Request`. + + The code in `http.cookiejar.CookieJar` expects this interface in order to correctly + manage cookie policies, i.e., determine whether a cookie can be set, given the + domains of the request and the cookie. + + The original request object is read-only. The client is responsible for collecting + the new headers via `get_new_headers()` and interpreting them appropriately. You + probably want `get_cookie_header`, defined below. + """ + + def __init__(self, request): + self._r = request + self._new_headers = {} + self.type = urlparse(self._r.url).scheme + + def get_type(self): + return self.type + + def get_host(self): + return urlparse(self._r.url).netloc + + def get_origin_req_host(self): + return self.get_host() + + def get_full_url(self): + # Only return the response's URL if the user hadn't set the Host + # header + if not self._r.headers.get("Host"): + return self._r.url + # If they did set it, retrieve it and reconstruct the expected domain + host = to_native_string(self._r.headers["Host"], encoding="utf-8") + parsed = urlparse(self._r.url) + # Reconstruct the URL as we expect it + return urlunparse( + [ + parsed.scheme, + host, + parsed.path, + parsed.params, + parsed.query, + parsed.fragment, + ] + ) + + def is_unverifiable(self): + return True + + def has_header(self, name): + return name in self._r.headers or name in self._new_headers + + def get_header(self, name, default=None): + return self._r.headers.get(name, self._new_headers.get(name, default)) + + def add_header(self, key, val): + """cookiejar has no legitimate use for this method; add it back if you find one.""" + raise NotImplementedError( + "Cookie headers should be added with add_unredirected_header()" + ) + + def add_unredirected_header(self, name, value): + self._new_headers[name] = value + + def get_new_headers(self): + return self._new_headers + + @property + def unverifiable(self): + return self.is_unverifiable() + + @property + def origin_req_host(self): + return self.get_origin_req_host() + + @property + def host(self): + return self.get_host() + + +class MockResponse: + """Wraps a `httplib.HTTPMessage` to mimic a `urllib.addinfourl`. + + ...what? Basically, expose the parsed HTTP headers from the server response + the way `http.cookiejar` expects to see them. + """ + + def __init__(self, headers): + """Make a MockResponse for `cookiejar` to read. + + :param headers: a httplib.HTTPMessage or analogous carrying the headers + """ + self._headers = headers + + def info(self): + return self._headers + + def getheaders(self, name): + self._headers.getheaders(name) + + +def extract_cookies_to_jar(jar, request, response): + """Extract the cookies from the response into a CookieJar. + + :param jar: http.cookiejar.CookieJar (not necessarily a RequestsCookieJar) + :param request: our own requests.Request object + :param response: urllib3.HTTPResponse object + """ + if not (hasattr(response, "_original_response") and response._original_response): + return + # the _original_response field is the wrapped httplib.HTTPResponse object, + req = MockRequest(request) + # pull out the HTTPMessage with the headers and put it in the mock: + res = MockResponse(response._original_response.msg) + jar.extract_cookies(res, req) + + +def get_cookie_header(jar, request): + """ + Produce an appropriate Cookie header string to be sent with `request`, or None. + + :rtype: str + """ + r = MockRequest(request) + jar.add_cookie_header(r) + return r.get_new_headers().get("Cookie") + + +def remove_cookie_by_name(cookiejar, name, domain=None, path=None): + """Unsets a cookie by name, by default over all domains and paths. + + Wraps CookieJar.clear(), is O(n). + """ + clearables = [] + for cookie in cookiejar: + if cookie.name != name: + continue + if domain is not None and domain != cookie.domain: + continue + if path is not None and path != cookie.path: + continue + clearables.append((cookie.domain, cookie.path, cookie.name)) + + for domain, path, name in clearables: + cookiejar.clear(domain, path, name) + + +class CookieConflictError(RuntimeError): + """There are two cookies that meet the criteria specified in the cookie jar. + Use .get and .set and include domain and path args in order to be more specific. + """ + + +class RequestsCookieJar(cookielib.CookieJar, MutableMapping): + """Compatibility class; is a http.cookiejar.CookieJar, but exposes a dict + interface. + + This is the CookieJar we create by default for requests and sessions that + don't specify one, since some clients may expect response.cookies and + session.cookies to support dict operations. + + Requests does not use the dict interface internally; it's just for + compatibility with external client code. All requests code should work + out of the box with externally provided instances of ``CookieJar``, e.g. + ``LWPCookieJar`` and ``FileCookieJar``. + + Unlike a regular CookieJar, this class is pickleable. + + .. warning:: dictionary operations that are normally O(1) may be O(n). + """ + + def get(self, name, default=None, domain=None, path=None): + """Dict-like get() that also supports optional domain and path args in + order to resolve naming collisions from using one cookie jar over + multiple domains. + + .. warning:: operation is O(n), not O(1). + """ + try: + return self._find_no_duplicates(name, domain, path) + except KeyError: + return default + + def set(self, name, value, **kwargs): + """Dict-like set() that also supports optional domain and path args in + order to resolve naming collisions from using one cookie jar over + multiple domains. + """ + # support client code that unsets cookies by assignment of a None value: + if value is None: + remove_cookie_by_name( + self, name, domain=kwargs.get("domain"), path=kwargs.get("path") + ) + return + + if isinstance(value, Morsel): + c = morsel_to_cookie(value) + else: + c = create_cookie(name, value, **kwargs) + self.set_cookie(c) + return c + + def iterkeys(self): + """Dict-like iterkeys() that returns an iterator of names of cookies + from the jar. + + .. seealso:: itervalues() and iteritems(). + """ + for cookie in iter(self): + yield cookie.name + + def keys(self): + """Dict-like keys() that returns a list of names of cookies from the + jar. + + .. seealso:: values() and items(). + """ + return list(self.iterkeys()) + + def itervalues(self): + """Dict-like itervalues() that returns an iterator of values of cookies + from the jar. + + .. seealso:: iterkeys() and iteritems(). + """ + for cookie in iter(self): + yield cookie.value + + def values(self): + """Dict-like values() that returns a list of values of cookies from the + jar. + + .. seealso:: keys() and items(). + """ + return list(self.itervalues()) + + def iteritems(self): + """Dict-like iteritems() that returns an iterator of name-value tuples + from the jar. + + .. seealso:: iterkeys() and itervalues(). + """ + for cookie in iter(self): + yield cookie.name, cookie.value + + def items(self): + """Dict-like items() that returns a list of name-value tuples from the + jar. Allows client-code to call ``dict(RequestsCookieJar)`` and get a + vanilla python dict of key value pairs. + + .. seealso:: keys() and values(). + """ + return list(self.iteritems()) + + def list_domains(self): + """Utility method to list all the domains in the jar.""" + domains = [] + for cookie in iter(self): + if cookie.domain not in domains: + domains.append(cookie.domain) + return domains + + def list_paths(self): + """Utility method to list all the paths in the jar.""" + paths = [] + for cookie in iter(self): + if cookie.path not in paths: + paths.append(cookie.path) + return paths + + def multiple_domains(self): + """Returns True if there are multiple domains in the jar. + Returns False otherwise. + + :rtype: bool + """ + domains = [] + for cookie in iter(self): + if cookie.domain is not None and cookie.domain in domains: + return True + domains.append(cookie.domain) + return False # there is only one domain in jar + + def get_dict(self, domain=None, path=None): + """Takes as an argument an optional domain and path and returns a plain + old Python dict of name-value pairs of cookies that meet the + requirements. + + :rtype: dict + """ + dictionary = {} + for cookie in iter(self): + if (domain is None or cookie.domain == domain) and ( + path is None or cookie.path == path + ): + dictionary[cookie.name] = cookie.value + return dictionary + + def __contains__(self, name): + try: + return super().__contains__(name) + except CookieConflictError: + return True + + def __getitem__(self, name): + """Dict-like __getitem__() for compatibility with client code. Throws + exception if there are more than one cookie with name. In that case, + use the more explicit get() method instead. + + .. warning:: operation is O(n), not O(1). + """ + return self._find_no_duplicates(name) + + def __setitem__(self, name, value): + """Dict-like __setitem__ for compatibility with client code. Throws + exception if there is already a cookie of that name in the jar. In that + case, use the more explicit set() method instead. + """ + self.set(name, value) + + def __delitem__(self, name): + """Deletes a cookie given a name. Wraps ``http.cookiejar.CookieJar``'s + ``remove_cookie_by_name()``. + """ + remove_cookie_by_name(self, name) + + def set_cookie(self, cookie, *args, **kwargs): + if ( + hasattr(cookie.value, "startswith") + and cookie.value.startswith('"') + and cookie.value.endswith('"') + ): + cookie.value = cookie.value.replace('\\"', "") + return super().set_cookie(cookie, *args, **kwargs) + + def update(self, other): + """Updates this jar with cookies from another CookieJar or dict-like""" + if isinstance(other, cookielib.CookieJar): + for cookie in other: + self.set_cookie(copy.copy(cookie)) + else: + super().update(other) + + def _find(self, name, domain=None, path=None): + """Requests uses this method internally to get cookie values. + + If there are conflicting cookies, _find arbitrarily chooses one. + See _find_no_duplicates if you want an exception thrown if there are + conflicting cookies. + + :param name: a string containing name of cookie + :param domain: (optional) string containing domain of cookie + :param path: (optional) string containing path of cookie + :return: cookie.value + """ + for cookie in iter(self): + if cookie.name == name: + if domain is None or cookie.domain == domain: + if path is None or cookie.path == path: + return cookie.value + + raise KeyError(f"name={name!r}, domain={domain!r}, path={path!r}") + + def _find_no_duplicates(self, name, domain=None, path=None): + """Both ``__get_item__`` and ``get`` call this function: it's never + used elsewhere in Requests. + + :param name: a string containing name of cookie + :param domain: (optional) string containing domain of cookie + :param path: (optional) string containing path of cookie + :raises KeyError: if cookie is not found + :raises CookieConflictError: if there are multiple cookies + that match name and optionally domain and path + :return: cookie.value + """ + toReturn = None + for cookie in iter(self): + if cookie.name == name: + if domain is None or cookie.domain == domain: + if path is None or cookie.path == path: + if toReturn is not None: + # if there are multiple cookies that meet passed in criteria + raise CookieConflictError( + f"There are multiple cookies with name, {name!r}" + ) + # we will eventually return this as long as no cookie conflict + toReturn = cookie.value + + if toReturn: + return toReturn + raise KeyError(f"name={name!r}, domain={domain!r}, path={path!r}") + + def __getstate__(self): + """Unlike a normal CookieJar, this class is pickleable.""" + state = self.__dict__.copy() + # remove the unpickleable RLock object + state.pop("_cookies_lock") + return state + + def __setstate__(self, state): + """Unlike a normal CookieJar, this class is pickleable.""" + self.__dict__.update(state) + if "_cookies_lock" not in self.__dict__: + self._cookies_lock = threading.RLock() + + def copy(self): + """Return a copy of this RequestsCookieJar.""" + new_cj = RequestsCookieJar() + new_cj.set_policy(self.get_policy()) + new_cj.update(self) + return new_cj + + def get_policy(self): + """Return the CookiePolicy instance used.""" + return self._policy + + +def _copy_cookie_jar(jar): + if jar is None: + return None + + if hasattr(jar, "copy"): + # We're dealing with an instance of RequestsCookieJar + return jar.copy() + # We're dealing with a generic CookieJar instance + new_jar = copy.copy(jar) + new_jar.clear() + for cookie in jar: + new_jar.set_cookie(copy.copy(cookie)) + return new_jar + + +def create_cookie(name, value, **kwargs): + """Make a cookie from underspecified parameters. + + By default, the pair of `name` and `value` will be set for the domain '' + and sent on every request (this is sometimes called a "supercookie"). + """ + result = { + "version": 0, + "name": name, + "value": value, + "port": None, + "domain": "", + "path": "/", + "secure": False, + "expires": None, + "discard": True, + "comment": None, + "comment_url": None, + "rest": {"HttpOnly": None}, + "rfc2109": False, + } + + badargs = set(kwargs) - set(result) + if badargs: + raise TypeError( + f"create_cookie() got unexpected keyword arguments: {list(badargs)}" + ) + + result.update(kwargs) + result["port_specified"] = bool(result["port"]) + result["domain_specified"] = bool(result["domain"]) + result["domain_initial_dot"] = result["domain"].startswith(".") + result["path_specified"] = bool(result["path"]) + + return cookielib.Cookie(**result) + + +def morsel_to_cookie(morsel): + """Convert a Morsel object into a Cookie containing the one k/v pair.""" + + expires = None + if morsel["max-age"]: + try: + expires = int(time.time() + int(morsel["max-age"])) + except ValueError: + raise TypeError(f"max-age: {morsel['max-age']} must be integer") + elif morsel["expires"]: + time_template = "%a, %d-%b-%Y %H:%M:%S GMT" + expires = calendar.timegm(time.strptime(morsel["expires"], time_template)) + return create_cookie( + comment=morsel["comment"], + comment_url=bool(morsel["comment"]), + discard=False, + domain=morsel["domain"], + expires=expires, + name=morsel.key, + path=morsel["path"], + port=None, + rest={"HttpOnly": morsel["httponly"]}, + rfc2109=False, + secure=bool(morsel["secure"]), + value=morsel.value, + version=morsel["version"] or 0, + ) + + +def cookiejar_from_dict(cookie_dict, cookiejar=None, overwrite=True): + """Returns a CookieJar from a key/value dictionary. + + :param cookie_dict: Dict of key/values to insert into CookieJar. + :param cookiejar: (optional) A cookiejar to add the cookies to. + :param overwrite: (optional) If False, will not replace cookies + already in the jar with new ones. + :rtype: CookieJar + """ + if cookiejar is None: + cookiejar = RequestsCookieJar() + + if cookie_dict is not None: + names_from_jar = [cookie.name for cookie in cookiejar] + for name in cookie_dict: + if overwrite or (name not in names_from_jar): + cookiejar.set_cookie(create_cookie(name, cookie_dict[name])) + + return cookiejar + + +def merge_cookies(cookiejar, cookies): + """Add cookies to cookiejar and returns a merged CookieJar. + + :param cookiejar: CookieJar object to add the cookies to. + :param cookies: Dictionary or CookieJar object to be added. + :rtype: CookieJar + """ + if not isinstance(cookiejar, cookielib.CookieJar): + raise ValueError("You can only merge into CookieJar") + + if isinstance(cookies, dict): + cookiejar = cookiejar_from_dict(cookies, cookiejar=cookiejar, overwrite=False) + elif isinstance(cookies, cookielib.CookieJar): + try: + cookiejar.update(cookies) + except AttributeError: + for cookie_in_jar in cookies: + cookiejar.set_cookie(cookie_in_jar) + + return cookiejar diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests/exceptions.py b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/exceptions.py new file mode 100644 index 0000000000000000000000000000000000000000..6e71506e968e85c6f539e8292706aa566146f48a --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/exceptions.py @@ -0,0 +1,152 @@ +""" +requests.exceptions +~~~~~~~~~~~~~~~~~~~ + +This module contains the set of Requests' exceptions. +""" + +from urllib3.exceptions import HTTPError as BaseHTTPError + +from .compat import JSONDecodeError as CompatJSONDecodeError + + +class RequestException(IOError): + """There was an ambiguous exception that occurred while handling your + request. + """ + + def __init__(self, *args, **kwargs): + """Initialize RequestException with `request` and `response` objects.""" + response = kwargs.pop("response", None) + self.response = response + self.request = kwargs.pop("request", None) + if response is not None and not self.request and hasattr(response, "request"): + self.request = self.response.request + super().__init__(*args, **kwargs) + + +class InvalidJSONError(RequestException): + """A JSON error occurred.""" + + +class JSONDecodeError(InvalidJSONError, CompatJSONDecodeError): + """Couldn't decode the text into json""" + + def __init__(self, *args, **kwargs): + """ + Construct the JSONDecodeError instance first with all + args. Then use it's args to construct the IOError so that + the json specific args aren't used as IOError specific args + and the error message from JSONDecodeError is preserved. + """ + CompatJSONDecodeError.__init__(self, *args) + InvalidJSONError.__init__(self, *self.args, **kwargs) + + def __reduce__(self): + """ + The __reduce__ method called when pickling the object must + be the one from the JSONDecodeError (be it json/simplejson) + as it expects all the arguments for instantiation, not just + one like the IOError, and the MRO would by default call the + __reduce__ method from the IOError due to the inheritance order. + """ + return CompatJSONDecodeError.__reduce__(self) + + +class HTTPError(RequestException): + """An HTTP error occurred.""" + + +class ConnectionError(RequestException): + """A Connection error occurred.""" + + +class ProxyError(ConnectionError): + """A proxy error occurred.""" + + +class SSLError(ConnectionError): + """An SSL error occurred.""" + + +class Timeout(RequestException): + """The request timed out. + + Catching this error will catch both + :exc:`~requests.exceptions.ConnectTimeout` and + :exc:`~requests.exceptions.ReadTimeout` errors. + """ + + +class ConnectTimeout(ConnectionError, Timeout): + """The request timed out while trying to connect to the remote server. + + Requests that produced this error are safe to retry. + """ + + +class ReadTimeout(Timeout): + """The server did not send any data in the allotted amount of time.""" + + +class URLRequired(RequestException): + """A valid URL is required to make a request.""" + + +class TooManyRedirects(RequestException): + """Too many redirects.""" + + +class MissingSchema(RequestException, ValueError): + """The URL scheme (e.g. http or https) is missing.""" + + +class InvalidSchema(RequestException, ValueError): + """The URL scheme provided is either invalid or unsupported.""" + + +class InvalidURL(RequestException, ValueError): + """The URL provided was somehow invalid.""" + + +class InvalidHeader(RequestException, ValueError): + """The header value provided was somehow invalid.""" + + +class InvalidProxyURL(InvalidURL): + """The proxy URL provided is invalid.""" + + +class ChunkedEncodingError(RequestException): + """The server declared chunked encoding but sent an invalid chunk.""" + + +class ContentDecodingError(RequestException, BaseHTTPError): + """Failed to decode response content.""" + + +class StreamConsumedError(RequestException, TypeError): + """The content for this response was already consumed.""" + + +class RetryError(RequestException): + """Custom retries logic failed""" + + +class UnrewindableBodyError(RequestException): + """Requests encountered an error when trying to rewind a body.""" + + +# Warnings + + +class RequestsWarning(Warning): + """Base warning for Requests.""" + + +class FileModeWarning(RequestsWarning, DeprecationWarning): + """A file was opened in text mode, but Requests determined its binary length.""" + + +class RequestsDependencyWarning(RequestsWarning): + """An imported dependency doesn't match the expected version range.""" diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests/help.py b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/help.py new file mode 100644 index 0000000000000000000000000000000000000000..5d5107895eed204996d3ab9a1a21afe8f6304925 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/help.py @@ -0,0 +1,131 @@ +"""Module containing bug report helper(s).""" + +import json +import platform +import ssl +import sys + +import idna +import urllib3 + +from . import __version__ as requests_version + +try: + import charset_normalizer +except ImportError: + charset_normalizer = None + +try: + import chardet +except ImportError: + chardet = None + +try: + from urllib3.contrib import pyopenssl +except ImportError: + pyopenssl = None + OpenSSL = None + cryptography = None +else: + import cryptography + import OpenSSL + + +def _implementation(): + """Return a dict with the Python implementation and version. + + Provide both the name and the version of the Python implementation + currently running. For example, on CPython 3.10.3 it will return + {'name': 'CPython', 'version': '3.10.3'}. + + This function works best on CPython and PyPy: in particular, it probably + doesn't work for Jython or IronPython. Future investigation should be done + to work out the correct shape of the code for those platforms. + """ + implementation = platform.python_implementation() + + if implementation == "CPython": + implementation_version = platform.python_version() + elif implementation == "PyPy": + pypy = sys.pypy_version_info + implementation_version = f"{pypy.major}.{pypy.minor}.{pypy.micro}" + if sys.pypy_version_info.releaselevel != "final": + implementation_version = "".join( + [implementation_version, sys.pypy_version_info.releaselevel] + ) + elif implementation == "Jython": + implementation_version = platform.python_version() # Complete Guess + elif implementation == "IronPython": + implementation_version = platform.python_version() # Complete Guess + else: + implementation_version = "Unknown" + + return {"name": implementation, "version": implementation_version} + + +def info(): + """Generate information for a bug report.""" + try: + platform_info = { + "system": platform.system(), + "release": platform.release(), + } + except OSError: + platform_info = { + "system": "Unknown", + "release": "Unknown", + } + + implementation_info = _implementation() + urllib3_info = {"version": urllib3.__version__} + charset_normalizer_info = {"version": None} + chardet_info = {"version": None} + if charset_normalizer: + charset_normalizer_info = {"version": charset_normalizer.__version__} + if chardet: + chardet_info = {"version": chardet.__version__} + + pyopenssl_info = { + "version": None, + "openssl_version": "", + } + if OpenSSL: + pyopenssl_info = { + "version": OpenSSL.__version__, + "openssl_version": f"{OpenSSL.SSL.OPENSSL_VERSION_NUMBER:x}", + } + cryptography_info = { + "version": getattr(cryptography, "__version__", ""), + } + idna_info = { + "version": getattr(idna, "__version__", ""), + } + + system_ssl = ssl.OPENSSL_VERSION_NUMBER + system_ssl_info = {"version": f"{system_ssl:x}" if system_ssl is not None else ""} + + return { + "platform": platform_info, + "implementation": implementation_info, + "system_ssl": system_ssl_info, + "using_pyopenssl": pyopenssl is not None, + "using_charset_normalizer": chardet is None, + "pyOpenSSL": pyopenssl_info, + "urllib3": urllib3_info, + "chardet": chardet_info, + "charset_normalizer": charset_normalizer_info, + "cryptography": cryptography_info, + "idna": idna_info, + "requests": { + "version": requests_version, + }, + } + + +def main(): + """Pretty-print the bug information as JSON.""" + print(json.dumps(info(), sort_keys=True, indent=2)) + + +if __name__ == "__main__": + main() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests/hooks.py b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/hooks.py new file mode 100644 index 0000000000000000000000000000000000000000..5976bc7d0f22ad5769194eb7965b2e3fe7302048 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/hooks.py @@ -0,0 +1,34 @@ +""" +requests.hooks +~~~~~~~~~~~~~~ + +This module provides the capabilities for the Requests hooks system. + +Available hooks: + +``response``: + The response generated from a Request. +""" + +HOOKS = ["response"] + + +def default_hooks(): + return {event: [] for event in HOOKS} + + +# TODO: response is the only one + + +def dispatch_hook(key, hooks, hook_data, **kwargs): + """Dispatches a hook dictionary on a given piece of data.""" + hooks = hooks or {} + hooks = hooks.get(key) + if hooks: + if hasattr(hooks, "__call__"): + hooks = [hooks] + for hook in hooks: + _hook_data = hook(hook_data, **kwargs) + if _hook_data is not None: + hook_data = _hook_data + return hook_data diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests/models.py b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/models.py new file mode 100644 index 0000000000000000000000000000000000000000..2d043f59cff71257048fe5d039525d388a54e5a4 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/models.py @@ -0,0 +1,1041 @@ +""" +requests.models +~~~~~~~~~~~~~~~ + +This module contains the primary objects that power Requests. +""" + +import datetime + +# Import encoding now, to avoid implicit import later. +# Implicit import within threads may cause LookupError when standard library is in a ZIP, +# such as in Embedded Python. See https://github.com/psf/requests/issues/3578. +import encodings.idna # noqa: F401 +from io import UnsupportedOperation + +from urllib3.exceptions import ( + DecodeError, + LocationParseError, + ProtocolError, + ReadTimeoutError, + SSLError, +) +from urllib3.fields import RequestField +from urllib3.filepost import encode_multipart_formdata +from urllib3.util import parse_url + +from ._internal_utils import to_native_string, unicode_is_ascii +from .auth import HTTPBasicAuth +from .compat import ( + Callable, + JSONDecodeError, + Mapping, + basestring, + builtin_str, + chardet, + cookielib, + urlencode, + urlsplit, + urlunparse, +) +from .compat import json as complexjson +from .cookies import _copy_cookie_jar, cookiejar_from_dict, get_cookie_header +from .exceptions import ( + ChunkedEncodingError, + ConnectionError, + ContentDecodingError, + HTTPError, + InvalidJSONError, + InvalidURL, + MissingSchema, + StreamConsumedError, +) +from .exceptions import JSONDecodeError as RequestsJSONDecodeError +from .exceptions import SSLError as RequestsSSLError +from .hooks import default_hooks +from .status_codes import codes +from .structures import CaseInsensitiveDict +from .utils import ( + check_header_validity, + get_auth_from_url, + guess_filename, + guess_json_utf, + iter_slices, + parse_header_links, + requote_uri, + stream_decode_response_unicode, + super_len, + to_key_val_list, +) + +#: The set of HTTP status codes that indicate an automatically +#: processable redirect. +REDIRECT_STATI = ( + codes.moved, # 301 + codes.found, # 302 + codes.other, # 303 + codes.temporary_redirect, # 307 + codes.permanent_redirect, # 308 +) + +DEFAULT_REDIRECT_LIMIT = 30 +CONTENT_CHUNK_SIZE = 10 * 1024 +ITER_CHUNK_SIZE = 512 + + +class RequestEncodingMixin: + @property + def path_url(self): + """Build the path URL to use.""" + + url = [] + + p = urlsplit(self.url) + + path = p.path + if not path: + path = "/" + + url.append(path) + + query = p.query + if query: + url.append("?") + url.append(query) + + return "".join(url) + + @staticmethod + def _encode_params(data): + """Encode parameters in a piece of data. + + Will successfully encode parameters when passed as a dict or a list of + 2-tuples. Order is retained if data is a list of 2-tuples but arbitrary + if parameters are supplied as a dict. + """ + + if isinstance(data, (str, bytes)): + return data + elif hasattr(data, "read"): + return data + elif hasattr(data, "__iter__"): + result = [] + for k, vs in to_key_val_list(data): + if isinstance(vs, basestring) or not hasattr(vs, "__iter__"): + vs = [vs] + for v in vs: + if v is not None: + result.append( + ( + k.encode("utf-8") if isinstance(k, str) else k, + v.encode("utf-8") if isinstance(v, str) else v, + ) + ) + return urlencode(result, doseq=True) + else: + return data + + @staticmethod + def _encode_files(files, data): + """Build the body for a multipart/form-data request. + + Will successfully encode files when passed as a dict or a list of + tuples. Order is retained if data is a list of tuples but arbitrary + if parameters are supplied as a dict. + The tuples may be 2-tuples (filename, fileobj), 3-tuples (filename, fileobj, contentype) + or 4-tuples (filename, fileobj, contentype, custom_headers). + """ + if not files: + raise ValueError("Files must be provided.") + elif isinstance(data, basestring): + raise ValueError("Data must not be a string.") + + new_fields = [] + fields = to_key_val_list(data or {}) + files = to_key_val_list(files or {}) + + for field, val in fields: + if isinstance(val, basestring) or not hasattr(val, "__iter__"): + val = [val] + for v in val: + if v is not None: + # Don't call str() on bytestrings: in Py3 it all goes wrong. + if not isinstance(v, bytes): + v = str(v) + + new_fields.append( + ( + field.decode("utf-8") + if isinstance(field, bytes) + else field, + v.encode("utf-8") if isinstance(v, str) else v, + ) + ) + + for k, v in files: + # support for explicit filename + ft = None + fh = None + if isinstance(v, (tuple, list)): + if len(v) == 2: + fn, fp = v + elif len(v) == 3: + fn, fp, ft = v + else: + fn, fp, ft, fh = v + else: + fn = guess_filename(v) or k + fp = v + + if isinstance(fp, (str, bytes, bytearray)): + fdata = fp + elif hasattr(fp, "read"): + fdata = fp.read() + elif fp is None: + continue + else: + fdata = fp + + rf = RequestField(name=k, data=fdata, filename=fn, headers=fh) + rf.make_multipart(content_type=ft) + new_fields.append(rf) + + body, content_type = encode_multipart_formdata(new_fields) + + return body, content_type + + +class RequestHooksMixin: + def register_hook(self, event, hook): + """Properly register a hook.""" + + if event not in self.hooks: + raise ValueError(f'Unsupported event specified, with event name "{event}"') + + if isinstance(hook, Callable): + self.hooks[event].append(hook) + elif hasattr(hook, "__iter__"): + self.hooks[event].extend(h for h in hook if isinstance(h, Callable)) + + def deregister_hook(self, event, hook): + """Deregister a previously registered hook. + Returns True if the hook existed, False if not. + """ + + try: + self.hooks[event].remove(hook) + return True + except ValueError: + return False + + +class Request(RequestHooksMixin): + """A user-created :class:`Request ` object. + + Used to prepare a :class:`PreparedRequest `, which is sent to the server. + + :param method: HTTP method to use. + :param url: URL to send. + :param headers: dictionary of headers to send. + :param files: dictionary of {filename: fileobject} files to multipart upload. + :param data: the body to attach to the request. If a dictionary or + list of tuples ``[(key, value)]`` is provided, form-encoding will + take place. + :param json: json for the body to attach to the request (if files or data is not specified). + :param params: URL parameters to append to the URL. If a dictionary or + list of tuples ``[(key, value)]`` is provided, form-encoding will + take place. + :param auth: Auth handler or (user, pass) tuple. + :param cookies: dictionary or CookieJar of cookies to attach to this request. + :param hooks: dictionary of callback hooks, for internal usage. + + Usage:: + + >>> import requests + >>> req = requests.Request('GET', 'https://httpbin.org/get') + >>> req.prepare() + + """ + + def __init__( + self, + method=None, + url=None, + headers=None, + files=None, + data=None, + params=None, + auth=None, + cookies=None, + hooks=None, + json=None, + ): + # Default empty dicts for dict params. + data = [] if data is None else data + files = [] if files is None else files + headers = {} if headers is None else headers + params = {} if params is None else params + hooks = {} if hooks is None else hooks + + self.hooks = default_hooks() + for k, v in list(hooks.items()): + self.register_hook(event=k, hook=v) + + self.method = method + self.url = url + self.headers = headers + self.files = files + self.data = data + self.json = json + self.params = params + self.auth = auth + self.cookies = cookies + + def __repr__(self): + return f"" + + def prepare(self): + """Constructs a :class:`PreparedRequest ` for transmission and returns it.""" + p = PreparedRequest() + p.prepare( + method=self.method, + url=self.url, + headers=self.headers, + files=self.files, + data=self.data, + json=self.json, + params=self.params, + auth=self.auth, + cookies=self.cookies, + hooks=self.hooks, + ) + return p + + +class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): + """The fully mutable :class:`PreparedRequest ` object, + containing the exact bytes that will be sent to the server. + + Instances are generated from a :class:`Request ` object, and + should not be instantiated manually; doing so may produce undesirable + effects. + + Usage:: + + >>> import requests + >>> req = requests.Request('GET', 'https://httpbin.org/get') + >>> r = req.prepare() + >>> r + + + >>> s = requests.Session() + >>> s.send(r) + + """ + + def __init__(self): + #: HTTP verb to send to the server. + self.method = None + #: HTTP URL to send the request to. + self.url = None + #: dictionary of HTTP headers. + self.headers = None + # The `CookieJar` used to create the Cookie header will be stored here + # after prepare_cookies is called + self._cookies = None + #: request body to send to the server. + self.body = None + #: dictionary of callback hooks, for internal usage. + self.hooks = default_hooks() + #: integer denoting starting position of a readable file-like body. + self._body_position = None + + def prepare( + self, + method=None, + url=None, + headers=None, + files=None, + data=None, + params=None, + auth=None, + cookies=None, + hooks=None, + json=None, + ): + """Prepares the entire request with the given parameters.""" + + self.prepare_method(method) + self.prepare_url(url, params) + self.prepare_headers(headers) + self.prepare_cookies(cookies) + self.prepare_body(data, files, json) + self.prepare_auth(auth, url) + + # Note that prepare_auth must be last to enable authentication schemes + # such as OAuth to work on a fully prepared request. + + # This MUST go after prepare_auth. Authenticators could add a hook + self.prepare_hooks(hooks) + + def __repr__(self): + return f"" + + def copy(self): + p = PreparedRequest() + p.method = self.method + p.url = self.url + p.headers = self.headers.copy() if self.headers is not None else None + p._cookies = _copy_cookie_jar(self._cookies) + p.body = self.body + p.hooks = self.hooks + p._body_position = self._body_position + return p + + def prepare_method(self, method): + """Prepares the given HTTP method.""" + self.method = method + if self.method is not None: + self.method = to_native_string(self.method.upper()) + + @staticmethod + def _get_idna_encoded_host(host): + import idna + + try: + host = idna.encode(host, uts46=True).decode("utf-8") + except idna.IDNAError: + raise UnicodeError + return host + + def prepare_url(self, url, params): + """Prepares the given HTTP URL.""" + #: Accept objects that have string representations. + #: We're unable to blindly call unicode/str functions + #: as this will include the bytestring indicator (b'') + #: on python 3.x. + #: https://github.com/psf/requests/pull/2238 + if isinstance(url, bytes): + url = url.decode("utf8") + else: + url = str(url) + + # Remove leading whitespaces from url + url = url.lstrip() + + # Don't do any URL preparation for non-HTTP schemes like `mailto`, + # `data` etc to work around exceptions from `url_parse`, which + # handles RFC 3986 only. + if ":" in url and not url.lower().startswith("http"): + self.url = url + return + + # Support for unicode domain names and paths. + try: + scheme, auth, host, port, path, query, fragment = parse_url(url) + except LocationParseError as e: + raise InvalidURL(*e.args) + + if not scheme: + raise MissingSchema( + f"Invalid URL {url!r}: No scheme supplied. " + f"Perhaps you meant https://{url}?" + ) + + if not host: + raise InvalidURL(f"Invalid URL {url!r}: No host supplied") + + # In general, we want to try IDNA encoding the hostname if the string contains + # non-ASCII characters. This allows users to automatically get the correct IDNA + # behaviour. For strings containing only ASCII characters, we need to also verify + # it doesn't start with a wildcard (*), before allowing the unencoded hostname. + if not unicode_is_ascii(host): + try: + host = self._get_idna_encoded_host(host) + except UnicodeError: + raise InvalidURL("URL has an invalid label.") + elif host.startswith(("*", ".")): + raise InvalidURL("URL has an invalid label.") + + # Carefully reconstruct the network location + netloc = auth or "" + if netloc: + netloc += "@" + netloc += host + if port: + netloc += f":{port}" + + # Bare domains aren't valid URLs. + if not path: + path = "/" + + if isinstance(params, (str, bytes)): + params = to_native_string(params) + + enc_params = self._encode_params(params) + if enc_params: + if query: + query = f"{query}&{enc_params}" + else: + query = enc_params + + url = requote_uri(urlunparse([scheme, netloc, path, None, query, fragment])) + self.url = url + + def prepare_headers(self, headers): + """Prepares the given HTTP headers.""" + + self.headers = CaseInsensitiveDict() + if headers: + for header in headers.items(): + # Raise exception on invalid header value. + check_header_validity(header) + name, value = header + self.headers[to_native_string(name)] = value + + def prepare_body(self, data, files, json=None): + """Prepares the given HTTP body data.""" + + # Check if file, fo, generator, iterator. + # If not, run through normal process. + + # Nottin' on you. + body = None + content_type = None + + if not data and json is not None: + # urllib3 requires a bytes-like body. Python 2's json.dumps + # provides this natively, but Python 3 gives a Unicode string. + content_type = "application/json" + + try: + body = complexjson.dumps(json, allow_nan=False) + except ValueError as ve: + raise InvalidJSONError(ve, request=self) + + if not isinstance(body, bytes): + body = body.encode("utf-8") + + is_stream = all( + [ + hasattr(data, "__iter__"), + not isinstance(data, (basestring, list, tuple, Mapping)), + ] + ) + + if is_stream: + try: + length = super_len(data) + except (TypeError, AttributeError, UnsupportedOperation): + length = None + + body = data + + if getattr(body, "tell", None) is not None: + # Record the current file position before reading. + # This will allow us to rewind a file in the event + # of a redirect. + try: + self._body_position = body.tell() + except OSError: + # This differentiates from None, allowing us to catch + # a failed `tell()` later when trying to rewind the body + self._body_position = object() + + if files: + raise NotImplementedError( + "Streamed bodies and files are mutually exclusive." + ) + + if length: + self.headers["Content-Length"] = builtin_str(length) + else: + self.headers["Transfer-Encoding"] = "chunked" + else: + # Multi-part file uploads. + if files: + (body, content_type) = self._encode_files(files, data) + else: + if data: + body = self._encode_params(data) + if isinstance(data, basestring) or hasattr(data, "read"): + content_type = None + else: + content_type = "application/x-www-form-urlencoded" + + self.prepare_content_length(body) + + # Add content-type if it wasn't explicitly provided. + if content_type and ("content-type" not in self.headers): + self.headers["Content-Type"] = content_type + + self.body = body + + def prepare_content_length(self, body): + """Prepare Content-Length header based on request method and body""" + if body is not None: + length = super_len(body) + if length: + # If length exists, set it. Otherwise, we fallback + # to Transfer-Encoding: chunked. + self.headers["Content-Length"] = builtin_str(length) + elif ( + self.method not in ("GET", "HEAD") + and self.headers.get("Content-Length") is None + ): + # Set Content-Length to 0 for methods that can have a body + # but don't provide one. (i.e. not GET or HEAD) + self.headers["Content-Length"] = "0" + + def prepare_auth(self, auth, url=""): + """Prepares the given HTTP auth data.""" + + # If no Auth is explicitly provided, extract it from the URL first. + if auth is None: + url_auth = get_auth_from_url(self.url) + auth = url_auth if any(url_auth) else None + + if auth: + if isinstance(auth, tuple) and len(auth) == 2: + # special-case basic HTTP auth + auth = HTTPBasicAuth(*auth) + + # Allow auth to make its changes. + r = auth(self) + + # Update self to reflect the auth changes. + self.__dict__.update(r.__dict__) + + # Recompute Content-Length + self.prepare_content_length(self.body) + + def prepare_cookies(self, cookies): + """Prepares the given HTTP cookie data. + + This function eventually generates a ``Cookie`` header from the + given cookies using cookielib. Due to cookielib's design, the header + will not be regenerated if it already exists, meaning this function + can only be called once for the life of the + :class:`PreparedRequest ` object. Any subsequent calls + to ``prepare_cookies`` will have no actual effect, unless the "Cookie" + header is removed beforehand. + """ + if isinstance(cookies, cookielib.CookieJar): + self._cookies = cookies + else: + self._cookies = cookiejar_from_dict(cookies) + + cookie_header = get_cookie_header(self._cookies, self) + if cookie_header is not None: + self.headers["Cookie"] = cookie_header + + def prepare_hooks(self, hooks): + """Prepares the given hooks.""" + # hooks can be passed as None to the prepare method and to this + # method. To prevent iterating over None, simply use an empty list + # if hooks is False-y + hooks = hooks or [] + for event in hooks: + self.register_hook(event, hooks[event]) + + +class Response: + """The :class:`Response ` object, which contains a + server's response to an HTTP request. + """ + + __attrs__ = [ + "_content", + "status_code", + "headers", + "url", + "history", + "encoding", + "reason", + "cookies", + "elapsed", + "request", + ] + + def __init__(self): + self._content = False + self._content_consumed = False + self._next = None + + #: Integer Code of responded HTTP Status, e.g. 404 or 200. + self.status_code = None + + #: Case-insensitive Dictionary of Response Headers. + #: For example, ``headers['content-encoding']`` will return the + #: value of a ``'Content-Encoding'`` response header. + self.headers = CaseInsensitiveDict() + + #: File-like object representation of response (for advanced usage). + #: Use of ``raw`` requires that ``stream=True`` be set on the request. + #: This requirement does not apply for use internally to Requests. + self.raw = None + + #: Final URL location of Response. + self.url = None + + #: Encoding to decode with when accessing r.text. + self.encoding = None + + #: A list of :class:`Response ` objects from + #: the history of the Request. Any redirect responses will end + #: up here. The list is sorted from the oldest to the most recent request. + self.history = [] + + #: Textual reason of responded HTTP Status, e.g. "Not Found" or "OK". + self.reason = None + + #: A CookieJar of Cookies the server sent back. + self.cookies = cookiejar_from_dict({}) + + #: The amount of time elapsed between sending the request + #: and the arrival of the response (as a timedelta). + #: This property specifically measures the time taken between sending + #: the first byte of the request and finishing parsing the headers. It + #: is therefore unaffected by consuming the response content or the + #: value of the ``stream`` keyword argument. + self.elapsed = datetime.timedelta(0) + + #: The :class:`PreparedRequest ` object to which this + #: is a response. + self.request = None + + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + def __getstate__(self): + # Consume everything; accessing the content attribute makes + # sure the content has been fully read. + if not self._content_consumed: + self.content + + return {attr: getattr(self, attr, None) for attr in self.__attrs__} + + def __setstate__(self, state): + for name, value in state.items(): + setattr(self, name, value) + + # pickled objects do not have .raw + setattr(self, "_content_consumed", True) + setattr(self, "raw", None) + + def __repr__(self): + return f"" + + def __bool__(self): + """Returns True if :attr:`status_code` is less than 400. + + This attribute checks if the status code of the response is between + 400 and 600 to see if there was a client error or a server error. If + the status code, is between 200 and 400, this will return True. This + is **not** a check to see if the response code is ``200 OK``. + """ + return self.ok + + def __nonzero__(self): + """Returns True if :attr:`status_code` is less than 400. + + This attribute checks if the status code of the response is between + 400 and 600 to see if there was a client error or a server error. If + the status code, is between 200 and 400, this will return True. This + is **not** a check to see if the response code is ``200 OK``. + """ + return self.ok + + def __iter__(self): + """Allows you to use a response as an iterator.""" + return self.iter_content(128) + + @property + def ok(self): + """Returns True if :attr:`status_code` is less than 400, False if not. + + This attribute checks if the status code of the response is between + 400 and 600 to see if there was a client error or a server error. If + the status code is between 200 and 400, this will return True. This + is **not** a check to see if the response code is ``200 OK``. + """ + try: + self.raise_for_status() + except HTTPError: + return False + return True + + @property + def is_redirect(self): + """True if this Response is a well-formed HTTP redirect that could have + been processed automatically (by :meth:`Session.resolve_redirects`). + """ + return "location" in self.headers and self.status_code in REDIRECT_STATI + + @property + def is_permanent_redirect(self): + """True if this Response one of the permanent versions of redirect.""" + return "location" in self.headers and self.status_code in ( + codes.moved_permanently, + codes.permanent_redirect, + ) + + @property + def next(self): + """Returns a PreparedRequest for the next request in a redirect chain, if there is one.""" + return self._next + + @property + def apparent_encoding(self): + """The apparent encoding, provided by the charset_normalizer or chardet libraries.""" + if chardet is not None: + return chardet.detect(self.content)["encoding"] + else: + # If no character detection library is available, we'll fall back + # to a standard Python utf-8 str. + return "utf-8" + + def iter_content(self, chunk_size=1, decode_unicode=False): + """Iterates over the response data. When stream=True is set on the + request, this avoids reading the content at once into memory for + large responses. The chunk size is the number of bytes it should + read into memory. This is not necessarily the length of each item + returned as decoding can take place. + + chunk_size must be of type int or None. A value of None will + function differently depending on the value of `stream`. + stream=True will read data as it arrives in whatever size the + chunks are received. If stream=False, data is returned as + a single chunk. + + If decode_unicode is True, content will be decoded using the best + available encoding based on the response. + """ + + def generate(): + # Special case for urllib3. + if hasattr(self.raw, "stream"): + try: + yield from self.raw.stream(chunk_size, decode_content=True) + except ProtocolError as e: + raise ChunkedEncodingError(e) + except DecodeError as e: + raise ContentDecodingError(e) + except ReadTimeoutError as e: + raise ConnectionError(e) + except SSLError as e: + raise RequestsSSLError(e) + else: + # Standard file-like object. + while True: + chunk = self.raw.read(chunk_size) + if not chunk: + break + yield chunk + + self._content_consumed = True + + if self._content_consumed and isinstance(self._content, bool): + raise StreamConsumedError() + elif chunk_size is not None and not isinstance(chunk_size, int): + raise TypeError( + f"chunk_size must be an int, it is instead a {type(chunk_size)}." + ) + # simulate reading small chunks of the content + reused_chunks = iter_slices(self._content, chunk_size) + + stream_chunks = generate() + + chunks = reused_chunks if self._content_consumed else stream_chunks + + if decode_unicode: + chunks = stream_decode_response_unicode(chunks, self) + + return chunks + + def iter_lines( + self, chunk_size=ITER_CHUNK_SIZE, decode_unicode=False, delimiter=None + ): + """Iterates over the response data, one line at a time. When + stream=True is set on the request, this avoids reading the + content at once into memory for large responses. + + .. note:: This method is not reentrant safe. + """ + + pending = None + + for chunk in self.iter_content( + chunk_size=chunk_size, decode_unicode=decode_unicode + ): + if pending is not None: + chunk = pending + chunk + + if delimiter: + lines = chunk.split(delimiter) + else: + lines = chunk.splitlines() + + if lines and lines[-1] and chunk and lines[-1][-1] == chunk[-1]: + pending = lines.pop() + else: + pending = None + + yield from lines + + if pending is not None: + yield pending + + @property + def content(self): + """Content of the response, in bytes.""" + + if self._content is False: + # Read the contents. + if self._content_consumed: + raise RuntimeError("The content for this response was already consumed") + + if self.status_code == 0 or self.raw is None: + self._content = None + else: + self._content = b"".join(self.iter_content(CONTENT_CHUNK_SIZE)) or b"" + + self._content_consumed = True + # don't need to release the connection; that's been handled by urllib3 + # since we exhausted the data. + return self._content + + @property + def text(self): + """Content of the response, in unicode. + + If Response.encoding is None, encoding will be guessed using + ``charset_normalizer`` or ``chardet``. + + The encoding of the response content is determined based solely on HTTP + headers, following RFC 2616 to the letter. If you can take advantage of + non-HTTP knowledge to make a better guess at the encoding, you should + set ``r.encoding`` appropriately before accessing this property. + """ + + # Try charset from content-type + content = None + encoding = self.encoding + + if not self.content: + return "" + + # Fallback to auto-detected encoding. + if self.encoding is None: + encoding = self.apparent_encoding + + # Decode unicode from given encoding. + try: + content = str(self.content, encoding, errors="replace") + except (LookupError, TypeError): + # A LookupError is raised if the encoding was not found which could + # indicate a misspelling or similar mistake. + # + # A TypeError can be raised if encoding is None + # + # So we try blindly encoding. + content = str(self.content, errors="replace") + + return content + + def json(self, **kwargs): + r"""Decodes the JSON response body (if any) as a Python object. + + This may return a dictionary, list, etc. depending on what is in the response. + + :param \*\*kwargs: Optional arguments that ``json.loads`` takes. + :raises requests.exceptions.JSONDecodeError: If the response body does not + contain valid json. + """ + + if not self.encoding and self.content and len(self.content) > 3: + # No encoding set. JSON RFC 4627 section 3 states we should expect + # UTF-8, -16 or -32. Detect which one to use; If the detection or + # decoding fails, fall back to `self.text` (using charset_normalizer to make + # a best guess). + encoding = guess_json_utf(self.content) + if encoding is not None: + try: + return complexjson.loads(self.content.decode(encoding), **kwargs) + except UnicodeDecodeError: + # Wrong UTF codec detected; usually because it's not UTF-8 + # but some other 8-bit codec. This is an RFC violation, + # and the server didn't bother to tell us what codec *was* + # used. + pass + except JSONDecodeError as e: + raise RequestsJSONDecodeError(e.msg, e.doc, e.pos) + + try: + return complexjson.loads(self.text, **kwargs) + except JSONDecodeError as e: + # Catch JSON-related errors and raise as requests.JSONDecodeError + # This aliases json.JSONDecodeError and simplejson.JSONDecodeError + raise RequestsJSONDecodeError(e.msg, e.doc, e.pos) + + @property + def links(self): + """Returns the parsed header links of the response, if any.""" + + header = self.headers.get("link") + + resolved_links = {} + + if header: + links = parse_header_links(header) + + for link in links: + key = link.get("rel") or link.get("url") + resolved_links[key] = link + + return resolved_links + + def raise_for_status(self): + """Raises :class:`HTTPError`, if one occurred.""" + + http_error_msg = "" + if isinstance(self.reason, bytes): + # We attempt to decode utf-8 first because some servers + # choose to localize their reason strings. If the string + # isn't utf-8, we fall back to iso-8859-1 for all other + # encodings. (See PR #3538) + try: + reason = self.reason.decode("utf-8") + except UnicodeDecodeError: + reason = self.reason.decode("iso-8859-1") + else: + reason = self.reason + + if 400 <= self.status_code < 500: + http_error_msg = ( + f"{self.status_code} Client Error: {reason} for url: {self.url}" + ) + + elif 500 <= self.status_code < 600: + http_error_msg = ( + f"{self.status_code} Server Error: {reason} for url: {self.url}" + ) + + if http_error_msg: + raise HTTPError(http_error_msg, response=self) + + def close(self): + """Releases the connection back to the pool. Once this method has been + called the underlying ``raw`` object must not be accessed again. + + *Note: Should not normally need to be called explicitly.* + """ + if not self._content_consumed: + self.raw.close() + + release_conn = getattr(self.raw, "release_conn", None) + if release_conn is not None: + release_conn() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests/packages.py b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/packages.py new file mode 100644 index 0000000000000000000000000000000000000000..5ab3d8e250de8475cb22553f564e5444e02c7460 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/packages.py @@ -0,0 +1,23 @@ +import sys + +from .compat import chardet + +# This code exists for backwards compatibility reasons. +# I don't like it either. Just look the other way. :) + +for package in ("urllib3", "idna"): + locals()[package] = __import__(package) + # This traversal is apparently necessary such that the identities are + # preserved (requests.packages.urllib3.* is urllib3.*) + for mod in list(sys.modules): + if mod == package or mod.startswith(f"{package}."): + sys.modules[f"requests.packages.{mod}"] = sys.modules[mod] + +if chardet is not None: + target = chardet.__name__ + for mod in list(sys.modules): + if mod == target or mod.startswith(f"{target}."): + imported_mod = sys.modules[mod] + sys.modules[f"requests.packages.{mod}"] = imported_mod + mod = mod.replace(target, "chardet") + sys.modules[f"requests.packages.{mod}"] = imported_mod diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests/sessions.py b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/sessions.py new file mode 100644 index 0000000000000000000000000000000000000000..578cc44d5c17a81982c28308b6396760ab9d5946 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/sessions.py @@ -0,0 +1,834 @@ +""" +requests.sessions +~~~~~~~~~~~~~~~~~ + +This module provides a Session object to manage and persist settings across +requests (cookies, auth, proxies). +""" + +import os +import sys +import time +from collections import OrderedDict +from datetime import timedelta + +from ._internal_utils import to_native_string +from .adapters import HTTPAdapter +from .auth import _basic_auth_str +from .compat import Mapping, cookielib, urljoin, urlparse +from .cookies import ( + RequestsCookieJar, + cookiejar_from_dict, + extract_cookies_to_jar, + merge_cookies, +) +from .exceptions import ( + ChunkedEncodingError, + ContentDecodingError, + InvalidSchema, + TooManyRedirects, +) +from .hooks import default_hooks, dispatch_hook + +# formerly defined here, reexposed here for backward compatibility +from .models import ( # noqa: F401 + DEFAULT_REDIRECT_LIMIT, + REDIRECT_STATI, + PreparedRequest, + Request, +) +from .status_codes import codes +from .structures import CaseInsensitiveDict +from .utils import ( # noqa: F401 + DEFAULT_PORTS, + default_headers, + get_auth_from_url, + get_environ_proxies, + get_netrc_auth, + requote_uri, + resolve_proxies, + rewind_body, + should_bypass_proxies, + to_key_val_list, +) + +# Preferred clock, based on which one is more accurate on a given system. +if sys.platform == "win32": + preferred_clock = time.perf_counter +else: + preferred_clock = time.time + + +def merge_setting(request_setting, session_setting, dict_class=OrderedDict): + """Determines appropriate setting for a given request, taking into account + the explicit setting on that request, and the setting in the session. If a + setting is a dictionary, they will be merged together using `dict_class` + """ + + if session_setting is None: + return request_setting + + if request_setting is None: + return session_setting + + # Bypass if not a dictionary (e.g. verify) + if not ( + isinstance(session_setting, Mapping) and isinstance(request_setting, Mapping) + ): + return request_setting + + merged_setting = dict_class(to_key_val_list(session_setting)) + merged_setting.update(to_key_val_list(request_setting)) + + # Remove keys that are set to None. Extract keys first to avoid altering + # the dictionary during iteration. + none_keys = [k for (k, v) in merged_setting.items() if v is None] + for key in none_keys: + del merged_setting[key] + + return merged_setting + + +def merge_hooks(request_hooks, session_hooks, dict_class=OrderedDict): + """Properly merges both requests and session hooks. + + This is necessary because when request_hooks == {'response': []}, the + merge breaks Session hooks entirely. + """ + if session_hooks is None or session_hooks.get("response") == []: + return request_hooks + + if request_hooks is None or request_hooks.get("response") == []: + return session_hooks + + return merge_setting(request_hooks, session_hooks, dict_class) + + +class SessionRedirectMixin: + def get_redirect_target(self, resp): + """Receives a Response. Returns a redirect URI or ``None``""" + # Due to the nature of how requests processes redirects this method will + # be called at least once upon the original response and at least twice + # on each subsequent redirect response (if any). + # If a custom mixin is used to handle this logic, it may be advantageous + # to cache the redirect location onto the response object as a private + # attribute. + if resp.is_redirect: + location = resp.headers["location"] + # Currently the underlying http module on py3 decode headers + # in latin1, but empirical evidence suggests that latin1 is very + # rarely used with non-ASCII characters in HTTP headers. + # It is more likely to get UTF8 header rather than latin1. + # This causes incorrect handling of UTF8 encoded location headers. + # To solve this, we re-encode the location in latin1. + location = location.encode("latin1") + return to_native_string(location, "utf8") + return None + + def should_strip_auth(self, old_url, new_url): + """Decide whether Authorization header should be removed when redirecting""" + old_parsed = urlparse(old_url) + new_parsed = urlparse(new_url) + if old_parsed.hostname != new_parsed.hostname: + return True + # Special case: allow http -> https redirect when using the standard + # ports. This isn't specified by RFC 7235, but is kept to avoid + # breaking backwards compatibility with older versions of requests + # that allowed any redirects on the same host. + if ( + old_parsed.scheme == "http" + and old_parsed.port in (80, None) + and new_parsed.scheme == "https" + and new_parsed.port in (443, None) + ): + return False + + # Handle default port usage corresponding to scheme. + changed_port = old_parsed.port != new_parsed.port + changed_scheme = old_parsed.scheme != new_parsed.scheme + default_port = (DEFAULT_PORTS.get(old_parsed.scheme, None), None) + if ( + not changed_scheme + and old_parsed.port in default_port + and new_parsed.port in default_port + ): + return False + + # Standard case: root URI must match + return changed_port or changed_scheme + + def resolve_redirects( + self, + resp, + req, + stream=False, + timeout=None, + verify=True, + cert=None, + proxies=None, + yield_requests=False, + **adapter_kwargs, + ): + """Receives a Response. Returns a generator of Responses or Requests.""" + + hist = [] # keep track of history + + url = self.get_redirect_target(resp) + previous_fragment = urlparse(req.url).fragment + while url: + prepared_request = req.copy() + + # Update history and keep track of redirects. + # resp.history must ignore the original request in this loop + hist.append(resp) + resp.history = hist[1:] + + try: + resp.content # Consume socket so it can be released + except (ChunkedEncodingError, ContentDecodingError, RuntimeError): + resp.raw.read(decode_content=False) + + if len(resp.history) >= self.max_redirects: + raise TooManyRedirects( + f"Exceeded {self.max_redirects} redirects.", response=resp + ) + + # Release the connection back into the pool. + resp.close() + + # Handle redirection without scheme (see: RFC 1808 Section 4) + if url.startswith("//"): + parsed_rurl = urlparse(resp.url) + url = ":".join([to_native_string(parsed_rurl.scheme), url]) + + # Normalize url case and attach previous fragment if needed (RFC 7231 7.1.2) + parsed = urlparse(url) + if parsed.fragment == "" and previous_fragment: + parsed = parsed._replace(fragment=previous_fragment) + elif parsed.fragment: + previous_fragment = parsed.fragment + url = parsed.geturl() + + # Facilitate relative 'location' headers, as allowed by RFC 7231. + # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource') + # Compliant with RFC3986, we percent encode the url. + if not parsed.netloc: + url = urljoin(resp.url, requote_uri(url)) + else: + url = requote_uri(url) + + prepared_request.url = to_native_string(url) + + self.rebuild_method(prepared_request, resp) + + # https://github.com/psf/requests/issues/1084 + if resp.status_code not in ( + codes.temporary_redirect, + codes.permanent_redirect, + ): + # https://github.com/psf/requests/issues/3490 + purged_headers = ("Content-Length", "Content-Type", "Transfer-Encoding") + for header in purged_headers: + prepared_request.headers.pop(header, None) + prepared_request.body = None + + headers = prepared_request.headers + headers.pop("Cookie", None) + + # Extract any cookies sent on the response to the cookiejar + # in the new request. Because we've mutated our copied prepared + # request, use the old one that we haven't yet touched. + extract_cookies_to_jar(prepared_request._cookies, req, resp.raw) + merge_cookies(prepared_request._cookies, self.cookies) + prepared_request.prepare_cookies(prepared_request._cookies) + + # Rebuild auth and proxy information. + proxies = self.rebuild_proxies(prepared_request, proxies) + self.rebuild_auth(prepared_request, resp) + + # A failed tell() sets `_body_position` to `object()`. This non-None + # value ensures `rewindable` will be True, allowing us to raise an + # UnrewindableBodyError, instead of hanging the connection. + rewindable = prepared_request._body_position is not None and ( + "Content-Length" in headers or "Transfer-Encoding" in headers + ) + + # Attempt to rewind consumed file-like object. + if rewindable: + rewind_body(prepared_request) + + # Override the original request. + req = prepared_request + + if yield_requests: + yield req + else: + resp = self.send( + req, + stream=stream, + timeout=timeout, + verify=verify, + cert=cert, + proxies=proxies, + allow_redirects=False, + **adapter_kwargs, + ) + + extract_cookies_to_jar(self.cookies, prepared_request, resp.raw) + + # extract redirect url, if any, for the next loop + url = self.get_redirect_target(resp) + yield resp + + def rebuild_auth(self, prepared_request, response): + """When being redirected we may want to strip authentication from the + request to avoid leaking credentials. This method intelligently removes + and reapplies authentication where possible to avoid credential loss. + """ + headers = prepared_request.headers + url = prepared_request.url + + if "Authorization" in headers and self.should_strip_auth( + response.request.url, url + ): + # If we get redirected to a new host, we should strip out any + # authentication headers. + del headers["Authorization"] + + # .netrc might have more auth for us on our new host. + new_auth = get_netrc_auth(url) if self.trust_env else None + if new_auth is not None: + prepared_request.prepare_auth(new_auth) + + def rebuild_proxies(self, prepared_request, proxies): + """This method re-evaluates the proxy configuration by considering the + environment variables. If we are redirected to a URL covered by + NO_PROXY, we strip the proxy configuration. Otherwise, we set missing + proxy keys for this URL (in case they were stripped by a previous + redirect). + + This method also replaces the Proxy-Authorization header where + necessary. + + :rtype: dict + """ + headers = prepared_request.headers + scheme = urlparse(prepared_request.url).scheme + new_proxies = resolve_proxies(prepared_request, proxies, self.trust_env) + + if "Proxy-Authorization" in headers: + del headers["Proxy-Authorization"] + + try: + username, password = get_auth_from_url(new_proxies[scheme]) + except KeyError: + username, password = None, None + + # urllib3 handles proxy authorization for us in the standard adapter. + # Avoid appending this to TLS tunneled requests where it may be leaked. + if not scheme.startswith("https") and username and password: + headers["Proxy-Authorization"] = _basic_auth_str(username, password) + + return new_proxies + + def rebuild_method(self, prepared_request, response): + """When being redirected we may want to change the method of the request + based on certain specs or browser behavior. + """ + method = prepared_request.method + + # https://tools.ietf.org/html/rfc7231#section-6.4.4 + if response.status_code == codes.see_other and method != "HEAD": + method = "GET" + + # Do what the browsers do, despite standards... + # First, turn 302s into GETs. + if response.status_code == codes.found and method != "HEAD": + method = "GET" + + # Second, if a POST is responded to with a 301, turn it into a GET. + # This bizarre behaviour is explained in Issue 1704. + if response.status_code == codes.moved and method == "POST": + method = "GET" + + prepared_request.method = method + + +class Session(SessionRedirectMixin): + """A Requests session. + + Provides cookie persistence, connection-pooling, and configuration. + + Basic Usage:: + + >>> import requests + >>> s = requests.Session() + >>> s.get('https://httpbin.org/get') + + + Or as a context manager:: + + >>> with requests.Session() as s: + ... s.get('https://httpbin.org/get') + + """ + + __attrs__ = [ + "headers", + "cookies", + "auth", + "proxies", + "hooks", + "params", + "verify", + "cert", + "adapters", + "stream", + "trust_env", + "max_redirects", + ] + + def __init__(self): + #: A case-insensitive dictionary of headers to be sent on each + #: :class:`Request ` sent from this + #: :class:`Session `. + self.headers = default_headers() + + #: Default Authentication tuple or object to attach to + #: :class:`Request `. + self.auth = None + + #: Dictionary mapping protocol or protocol and host to the URL of the proxy + #: (e.g. {'http': 'foo.bar:3128', 'http://host.name': 'foo.bar:4012'}) to + #: be used on each :class:`Request `. + self.proxies = {} + + #: Event-handling hooks. + self.hooks = default_hooks() + + #: Dictionary of querystring data to attach to each + #: :class:`Request `. The dictionary values may be lists for + #: representing multivalued query parameters. + self.params = {} + + #: Stream response content default. + self.stream = False + + #: SSL Verification default. + #: Defaults to `True`, requiring requests to verify the TLS certificate at the + #: remote end. + #: If verify is set to `False`, requests will accept any TLS certificate + #: presented by the server, and will ignore hostname mismatches and/or + #: expired certificates, which will make your application vulnerable to + #: man-in-the-middle (MitM) attacks. + #: Only set this to `False` for testing. + #: If verify is set to a string, it must be the path to a CA bundle file + #: that will be used to verify the TLS certificate. + self.verify = True + + #: SSL client certificate default, if String, path to ssl client + #: cert file (.pem). If Tuple, ('cert', 'key') pair. + self.cert = None + + #: Maximum number of redirects allowed. If the request exceeds this + #: limit, a :class:`TooManyRedirects` exception is raised. + #: This defaults to requests.models.DEFAULT_REDIRECT_LIMIT, which is + #: 30. + self.max_redirects = DEFAULT_REDIRECT_LIMIT + + #: Trust environment settings for proxy configuration, default + #: authentication and similar. + self.trust_env = True + + #: A CookieJar containing all currently outstanding cookies set on this + #: session. By default it is a + #: :class:`RequestsCookieJar `, but + #: may be any other ``cookielib.CookieJar`` compatible object. + self.cookies = cookiejar_from_dict({}) + + # Default connection adapters. + self.adapters = OrderedDict() + self.mount("https://", HTTPAdapter()) + self.mount("http://", HTTPAdapter()) + + def __enter__(self): + return self + + def __exit__(self, *args): + self.close() + + def prepare_request(self, request): + """Constructs a :class:`PreparedRequest ` for + transmission and returns it. The :class:`PreparedRequest` has settings + merged from the :class:`Request ` instance and those of the + :class:`Session`. + + :param request: :class:`Request` instance to prepare with this + session's settings. + :rtype: requests.PreparedRequest + """ + cookies = request.cookies or {} + + # Bootstrap CookieJar. + if not isinstance(cookies, cookielib.CookieJar): + cookies = cookiejar_from_dict(cookies) + + # Merge with session cookies + merged_cookies = merge_cookies( + merge_cookies(RequestsCookieJar(), self.cookies), cookies + ) + + # Set environment's basic authentication if not explicitly set. + auth = request.auth + if self.trust_env and not auth and not self.auth: + auth = get_netrc_auth(request.url) + + p = PreparedRequest() + p.prepare( + method=request.method.upper(), + url=request.url, + files=request.files, + data=request.data, + json=request.json, + headers=merge_setting( + request.headers, self.headers, dict_class=CaseInsensitiveDict + ), + params=merge_setting(request.params, self.params), + auth=merge_setting(auth, self.auth), + cookies=merged_cookies, + hooks=merge_hooks(request.hooks, self.hooks), + ) + return p + + def request( + self, + method, + url, + params=None, + data=None, + headers=None, + cookies=None, + files=None, + auth=None, + timeout=None, + allow_redirects=True, + proxies=None, + hooks=None, + stream=None, + verify=None, + cert=None, + json=None, + ): + """Constructs a :class:`Request `, prepares it and sends it. + Returns :class:`Response ` object. + + :param method: method for the new :class:`Request` object. + :param url: URL for the new :class:`Request` object. + :param params: (optional) Dictionary or bytes to be sent in the query + string for the :class:`Request`. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) json to send in the body of the + :class:`Request`. + :param headers: (optional) Dictionary of HTTP Headers to send with the + :class:`Request`. + :param cookies: (optional) Dict or CookieJar object to send with the + :class:`Request`. + :param files: (optional) Dictionary of ``'filename': file-like-objects`` + for multipart encoding upload. + :param auth: (optional) Auth tuple or callable to enable + Basic/Digest/Custom HTTP Auth. + :param timeout: (optional) How many seconds to wait for the server to send + data before giving up, as a float, or a :ref:`(connect timeout, + read timeout) ` tuple. + :type timeout: float or tuple + :param allow_redirects: (optional) Set to True by default. + :type allow_redirects: bool + :param proxies: (optional) Dictionary mapping protocol or protocol and + hostname to the URL of the proxy. + :param hooks: (optional) Dictionary mapping hook name to one event or + list of events, event must be callable. + :param stream: (optional) whether to immediately download the response + content. Defaults to ``False``. + :param verify: (optional) Either a boolean, in which case it controls whether we verify + the server's TLS certificate, or a string, in which case it must be a path + to a CA bundle to use. Defaults to ``True``. When set to + ``False``, requests will accept any TLS certificate presented by + the server, and will ignore hostname mismatches and/or expired + certificates, which will make your application vulnerable to + man-in-the-middle (MitM) attacks. Setting verify to ``False`` + may be useful during local development or testing. + :param cert: (optional) if String, path to ssl client cert file (.pem). + If Tuple, ('cert', 'key') pair. + :rtype: requests.Response + """ + # Create the Request. + req = Request( + method=method.upper(), + url=url, + headers=headers, + files=files, + data=data or {}, + json=json, + params=params or {}, + auth=auth, + cookies=cookies, + hooks=hooks, + ) + prep = self.prepare_request(req) + + proxies = proxies or {} + + settings = self.merge_environment_settings( + prep.url, proxies, stream, verify, cert + ) + + # Send the request. + send_kwargs = { + "timeout": timeout, + "allow_redirects": allow_redirects, + } + send_kwargs.update(settings) + resp = self.send(prep, **send_kwargs) + + return resp + + def get(self, url, **kwargs): + r"""Sends a GET request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + kwargs.setdefault("allow_redirects", True) + return self.request("GET", url, **kwargs) + + def options(self, url, **kwargs): + r"""Sends a OPTIONS request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + kwargs.setdefault("allow_redirects", True) + return self.request("OPTIONS", url, **kwargs) + + def head(self, url, **kwargs): + r"""Sends a HEAD request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + kwargs.setdefault("allow_redirects", False) + return self.request("HEAD", url, **kwargs) + + def post(self, url, data=None, json=None, **kwargs): + r"""Sends a POST request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param json: (optional) json to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + return self.request("POST", url, data=data, json=json, **kwargs) + + def put(self, url, data=None, **kwargs): + r"""Sends a PUT request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + return self.request("PUT", url, data=data, **kwargs) + + def patch(self, url, data=None, **kwargs): + r"""Sends a PATCH request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param data: (optional) Dictionary, list of tuples, bytes, or file-like + object to send in the body of the :class:`Request`. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + return self.request("PATCH", url, data=data, **kwargs) + + def delete(self, url, **kwargs): + r"""Sends a DELETE request. Returns :class:`Response` object. + + :param url: URL for the new :class:`Request` object. + :param \*\*kwargs: Optional arguments that ``request`` takes. + :rtype: requests.Response + """ + + return self.request("DELETE", url, **kwargs) + + def send(self, request, **kwargs): + """Send a given PreparedRequest. + + :rtype: requests.Response + """ + # Set defaults that the hooks can utilize to ensure they always have + # the correct parameters to reproduce the previous request. + kwargs.setdefault("stream", self.stream) + kwargs.setdefault("verify", self.verify) + kwargs.setdefault("cert", self.cert) + if "proxies" not in kwargs: + kwargs["proxies"] = resolve_proxies(request, self.proxies, self.trust_env) + + # It's possible that users might accidentally send a Request object. + # Guard against that specific failure case. + if isinstance(request, Request): + raise ValueError("You can only send PreparedRequests.") + + # Set up variables needed for resolve_redirects and dispatching of hooks + allow_redirects = kwargs.pop("allow_redirects", True) + stream = kwargs.get("stream") + hooks = request.hooks + + # Get the appropriate adapter to use + adapter = self.get_adapter(url=request.url) + + # Start time (approximately) of the request + start = preferred_clock() + + # Send the request + r = adapter.send(request, **kwargs) + + # Total elapsed time of the request (approximately) + elapsed = preferred_clock() - start + r.elapsed = timedelta(seconds=elapsed) + + # Response manipulation hooks + r = dispatch_hook("response", hooks, r, **kwargs) + + # Persist cookies + if r.history: + # If the hooks create history then we want those cookies too + for resp in r.history: + extract_cookies_to_jar(self.cookies, resp.request, resp.raw) + + extract_cookies_to_jar(self.cookies, request, r.raw) + + # Resolve redirects if allowed. + if allow_redirects: + # Redirect resolving generator. + gen = self.resolve_redirects(r, request, **kwargs) + history = [resp for resp in gen] + else: + history = [] + + # Shuffle things around if there's history. + if history: + # Insert the first (original) request at the start + history.insert(0, r) + # Get the last request made + r = history.pop() + r.history = history + + # If redirects aren't being followed, store the response on the Request for Response.next(). + if not allow_redirects: + try: + r._next = next( + self.resolve_redirects(r, request, yield_requests=True, **kwargs) + ) + except StopIteration: + pass + + if not stream: + r.content + + return r + + def merge_environment_settings(self, url, proxies, stream, verify, cert): + """ + Check the environment and merge it with some settings. + + :rtype: dict + """ + # Gather clues from the surrounding environment. + if self.trust_env: + # Set environment's proxies. + no_proxy = proxies.get("no_proxy") if proxies is not None else None + env_proxies = get_environ_proxies(url, no_proxy=no_proxy) + for k, v in env_proxies.items(): + proxies.setdefault(k, v) + + # Look for requests environment configuration + # and be compatible with cURL. + if verify is True or verify is None: + verify = ( + os.environ.get("REQUESTS_CA_BUNDLE") + or os.environ.get("CURL_CA_BUNDLE") + or verify + ) + + # Merge all the kwargs. + proxies = merge_setting(proxies, self.proxies) + stream = merge_setting(stream, self.stream) + verify = merge_setting(verify, self.verify) + cert = merge_setting(cert, self.cert) + + return {"proxies": proxies, "stream": stream, "verify": verify, "cert": cert} + + def get_adapter(self, url): + """ + Returns the appropriate connection adapter for the given URL. + + :rtype: requests.adapters.BaseAdapter + """ + for prefix, adapter in self.adapters.items(): + if url.lower().startswith(prefix.lower()): + return adapter + + # Nothing matches :-/ + raise InvalidSchema(f"No connection adapters were found for {url!r}") + + def close(self): + """Closes all adapters and as such the session""" + for v in self.adapters.values(): + v.close() + + def mount(self, prefix, adapter): + """Registers a connection adapter to a prefix. + + Adapters are sorted in descending order by prefix length. + """ + self.adapters[prefix] = adapter + keys_to_move = [k for k in self.adapters if len(k) < len(prefix)] + + for key in keys_to_move: + self.adapters[key] = self.adapters.pop(key) + + def __getstate__(self): + state = {attr: getattr(self, attr, None) for attr in self.__attrs__} + return state + + def __setstate__(self, state): + for attr, value in state.items(): + setattr(self, attr, value) + + +def session(): + """ + Returns a :class:`Session` for context-management. + + .. deprecated:: 1.0.0 + + This method has been deprecated since version 1.0.0 and is only kept for + backwards compatibility. New code should use :class:`~requests.sessions.Session` + to create a session. This may be removed at a future date. + + :rtype: Session + """ + return Session() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests/status_codes.py b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/status_codes.py new file mode 100644 index 0000000000000000000000000000000000000000..c7945a2f06897ed980cc575df2f48d9e6c1a9f7e --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/status_codes.py @@ -0,0 +1,128 @@ +r""" +The ``codes`` object defines a mapping from common names for HTTP statuses +to their numerical codes, accessible either as attributes or as dictionary +items. + +Example:: + + >>> import requests + >>> requests.codes['temporary_redirect'] + 307 + >>> requests.codes.teapot + 418 + >>> requests.codes['\o/'] + 200 + +Some codes have multiple names, and both upper- and lower-case versions of +the names are allowed. For example, ``codes.ok``, ``codes.OK``, and +``codes.okay`` all correspond to the HTTP status code 200. +""" + +from .structures import LookupDict + +_codes = { + # Informational. + 100: ("continue",), + 101: ("switching_protocols",), + 102: ("processing", "early-hints"), + 103: ("checkpoint",), + 122: ("uri_too_long", "request_uri_too_long"), + 200: ("ok", "okay", "all_ok", "all_okay", "all_good", "\\o/", "✓"), + 201: ("created",), + 202: ("accepted",), + 203: ("non_authoritative_info", "non_authoritative_information"), + 204: ("no_content",), + 205: ("reset_content", "reset"), + 206: ("partial_content", "partial"), + 207: ("multi_status", "multiple_status", "multi_stati", "multiple_stati"), + 208: ("already_reported",), + 226: ("im_used",), + # Redirection. + 300: ("multiple_choices",), + 301: ("moved_permanently", "moved", "\\o-"), + 302: ("found",), + 303: ("see_other", "other"), + 304: ("not_modified",), + 305: ("use_proxy",), + 306: ("switch_proxy",), + 307: ("temporary_redirect", "temporary_moved", "temporary"), + 308: ( + "permanent_redirect", + "resume_incomplete", + "resume", + ), # "resume" and "resume_incomplete" to be removed in 3.0 + # Client Error. + 400: ("bad_request", "bad"), + 401: ("unauthorized",), + 402: ("payment_required", "payment"), + 403: ("forbidden",), + 404: ("not_found", "-o-"), + 405: ("method_not_allowed", "not_allowed"), + 406: ("not_acceptable",), + 407: ("proxy_authentication_required", "proxy_auth", "proxy_authentication"), + 408: ("request_timeout", "timeout"), + 409: ("conflict",), + 410: ("gone",), + 411: ("length_required",), + 412: ("precondition_failed", "precondition"), + 413: ("request_entity_too_large", "content_too_large"), + 414: ("request_uri_too_large", "uri_too_long"), + 415: ("unsupported_media_type", "unsupported_media", "media_type"), + 416: ( + "requested_range_not_satisfiable", + "requested_range", + "range_not_satisfiable", + ), + 417: ("expectation_failed",), + 418: ("im_a_teapot", "teapot", "i_am_a_teapot"), + 421: ("misdirected_request",), + 422: ("unprocessable_entity", "unprocessable", "unprocessable_content"), + 423: ("locked",), + 424: ("failed_dependency", "dependency"), + 425: ("unordered_collection", "unordered", "too_early"), + 426: ("upgrade_required", "upgrade"), + 428: ("precondition_required", "precondition"), + 429: ("too_many_requests", "too_many"), + 431: ("header_fields_too_large", "fields_too_large"), + 444: ("no_response", "none"), + 449: ("retry_with", "retry"), + 450: ("blocked_by_windows_parental_controls", "parental_controls"), + 451: ("unavailable_for_legal_reasons", "legal_reasons"), + 499: ("client_closed_request",), + # Server Error. + 500: ("internal_server_error", "server_error", "/o\\", "✗"), + 501: ("not_implemented",), + 502: ("bad_gateway",), + 503: ("service_unavailable", "unavailable"), + 504: ("gateway_timeout",), + 505: ("http_version_not_supported", "http_version"), + 506: ("variant_also_negotiates",), + 507: ("insufficient_storage",), + 509: ("bandwidth_limit_exceeded", "bandwidth"), + 510: ("not_extended",), + 511: ("network_authentication_required", "network_auth", "network_authentication"), +} + +codes = LookupDict(name="status_codes") + + +def _init(): + for code, titles in _codes.items(): + for title in titles: + setattr(codes, title, code) + if not title.startswith(("\\", "/")): + setattr(codes, title.upper(), code) + + def doc(code): + names = ", ".join(f"``{n}``" for n in _codes[code]) + return "* %d: %s" % (code, names) + + global __doc__ + __doc__ = ( + __doc__ + "\n" + "\n".join(doc(code) for code in sorted(_codes)) + if __doc__ is not None + else None + ) + + +_init() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests/structures.py b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/structures.py new file mode 100644 index 0000000000000000000000000000000000000000..188e13e4829591facb23ae0e2eda84b9807cb818 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/structures.py @@ -0,0 +1,99 @@ +""" +requests.structures +~~~~~~~~~~~~~~~~~~~ + +Data structures that power Requests. +""" + +from collections import OrderedDict + +from .compat import Mapping, MutableMapping + + +class CaseInsensitiveDict(MutableMapping): + """A case-insensitive ``dict``-like object. + + Implements all methods and operations of + ``MutableMapping`` as well as dict's ``copy``. Also + provides ``lower_items``. + + All keys are expected to be strings. The structure remembers the + case of the last key to be set, and ``iter(instance)``, + ``keys()``, ``items()``, ``iterkeys()``, and ``iteritems()`` + will contain case-sensitive keys. However, querying and contains + testing is case insensitive:: + + cid = CaseInsensitiveDict() + cid['Accept'] = 'application/json' + cid['aCCEPT'] == 'application/json' # True + list(cid) == ['Accept'] # True + + For example, ``headers['content-encoding']`` will return the + value of a ``'Content-Encoding'`` response header, regardless + of how the header name was originally stored. + + If the constructor, ``.update``, or equality comparison + operations are given keys that have equal ``.lower()``s, the + behavior is undefined. + """ + + def __init__(self, data=None, **kwargs): + self._store = OrderedDict() + if data is None: + data = {} + self.update(data, **kwargs) + + def __setitem__(self, key, value): + # Use the lowercased key for lookups, but store the actual + # key alongside the value. + self._store[key.lower()] = (key, value) + + def __getitem__(self, key): + return self._store[key.lower()][1] + + def __delitem__(self, key): + del self._store[key.lower()] + + def __iter__(self): + return (casedkey for casedkey, mappedvalue in self._store.values()) + + def __len__(self): + return len(self._store) + + def lower_items(self): + """Like iteritems(), but with all lowercase keys.""" + return ((lowerkey, keyval[1]) for (lowerkey, keyval) in self._store.items()) + + def __eq__(self, other): + if isinstance(other, Mapping): + other = CaseInsensitiveDict(other) + else: + return NotImplemented + # Compare insensitively + return dict(self.lower_items()) == dict(other.lower_items()) + + # Copy is required + def copy(self): + return CaseInsensitiveDict(self._store.values()) + + def __repr__(self): + return str(dict(self.items())) + + +class LookupDict(dict): + """Dictionary lookup object.""" + + def __init__(self, name=None): + self.name = name + super().__init__() + + def __repr__(self): + return f"" + + def __getitem__(self, key): + # We allow fall-through here, so values default to None + + return self.__dict__.get(key, None) + + def get(self, key, default=None): + return self.__dict__.get(key, default) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/requests/utils.py b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..42238375c8fd5f0937a473eac7ff5fbe183a284e --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/requests/utils.py @@ -0,0 +1,1083 @@ +""" +requests.utils +~~~~~~~~~~~~~~ + +This module provides utility functions that are used within Requests +that are also useful for external consumption. +""" + +import codecs +import contextlib +import io +import os +import re +import socket +import struct +import sys +import tempfile +import warnings +import zipfile +from collections import OrderedDict + +from urllib3.util import make_headers, parse_url + +from . import certs +from .__version__ import __version__ + +# to_native_string is unused here, but imported here for backwards compatibility +from ._internal_utils import ( # noqa: F401 + _HEADER_VALIDATORS_BYTE, + _HEADER_VALIDATORS_STR, + HEADER_VALIDATORS, + to_native_string, +) +from .compat import ( + Mapping, + basestring, + bytes, + getproxies, + getproxies_environment, + integer_types, + is_urllib3_1, + proxy_bypass, + proxy_bypass_environment, + quote, + str, + unquote, + urlparse, + urlunparse, +) +from .compat import parse_http_list as _parse_list_header +from .cookies import cookiejar_from_dict +from .exceptions import ( + FileModeWarning, + InvalidHeader, + InvalidURL, + UnrewindableBodyError, +) +from .structures import CaseInsensitiveDict + +NETRC_FILES = (".netrc", "_netrc") + +# Certificate is extracted by certifi when needed. +DEFAULT_CA_BUNDLE_PATH = certs.where() + +DEFAULT_PORTS = {"http": 80, "https": 443} + +# Ensure that ', ' is used to preserve previous delimiter behavior. +DEFAULT_ACCEPT_ENCODING = ", ".join( + re.split(r",\s*", make_headers(accept_encoding=True)["accept-encoding"]) +) + + +if sys.platform == "win32": + # provide a proxy_bypass version on Windows without DNS lookups + + def proxy_bypass_registry(host): + try: + import winreg + except ImportError: + return False + + try: + internetSettings = winreg.OpenKey( + winreg.HKEY_CURRENT_USER, + r"Software\Microsoft\Windows\CurrentVersion\Internet Settings", + ) + # ProxyEnable could be REG_SZ or REG_DWORD, normalizing it + proxyEnable = int(winreg.QueryValueEx(internetSettings, "ProxyEnable")[0]) + # ProxyOverride is almost always a string + proxyOverride = winreg.QueryValueEx(internetSettings, "ProxyOverride")[0] + except (OSError, ValueError): + return False + if not proxyEnable or not proxyOverride: + return False + + # make a check value list from the registry entry: replace the + # '' string by the localhost entry and the corresponding + # canonical entry. + proxyOverride = proxyOverride.split(";") + # filter out empty strings to avoid re.match return true in the following code. + proxyOverride = filter(None, proxyOverride) + # now check if we match one of the registry values. + for test in proxyOverride: + if test == "": + if "." not in host: + return True + test = test.replace(".", r"\.") # mask dots + test = test.replace("*", r".*") # change glob sequence + test = test.replace("?", r".") # change glob char + if re.match(test, host, re.I): + return True + return False + + def proxy_bypass(host): # noqa + """Return True, if the host should be bypassed. + + Checks proxy settings gathered from the environment, if specified, + or the registry. + """ + if getproxies_environment(): + return proxy_bypass_environment(host) + else: + return proxy_bypass_registry(host) + + +def dict_to_sequence(d): + """Returns an internal sequence dictionary update.""" + + if hasattr(d, "items"): + d = d.items() + + return d + + +def super_len(o): + total_length = None + current_position = 0 + + if not is_urllib3_1 and isinstance(o, str): + # urllib3 2.x+ treats all strings as utf-8 instead + # of latin-1 (iso-8859-1) like http.client. + o = o.encode("utf-8") + + if hasattr(o, "__len__"): + total_length = len(o) + + elif hasattr(o, "len"): + total_length = o.len + + elif hasattr(o, "fileno"): + try: + fileno = o.fileno() + except (io.UnsupportedOperation, AttributeError): + # AttributeError is a surprising exception, seeing as how we've just checked + # that `hasattr(o, 'fileno')`. It happens for objects obtained via + # `Tarfile.extractfile()`, per issue 5229. + pass + else: + total_length = os.fstat(fileno).st_size + + # Having used fstat to determine the file length, we need to + # confirm that this file was opened up in binary mode. + if "b" not in o.mode: + warnings.warn( + ( + "Requests has determined the content-length for this " + "request using the binary size of the file: however, the " + "file has been opened in text mode (i.e. without the 'b' " + "flag in the mode). This may lead to an incorrect " + "content-length. In Requests 3.0, support will be removed " + "for files in text mode." + ), + FileModeWarning, + ) + + if hasattr(o, "tell"): + try: + current_position = o.tell() + except OSError: + # This can happen in some weird situations, such as when the file + # is actually a special file descriptor like stdin. In this + # instance, we don't know what the length is, so set it to zero and + # let requests chunk it instead. + if total_length is not None: + current_position = total_length + else: + if hasattr(o, "seek") and total_length is None: + # StringIO and BytesIO have seek but no usable fileno + try: + # seek to end of file + o.seek(0, 2) + total_length = o.tell() + + # seek back to current position to support + # partially read file-like objects + o.seek(current_position or 0) + except OSError: + total_length = 0 + + if total_length is None: + total_length = 0 + + return max(0, total_length - current_position) + + +def get_netrc_auth(url, raise_errors=False): + """Returns the Requests tuple auth for a given url from netrc.""" + + netrc_file = os.environ.get("NETRC") + if netrc_file is not None: + netrc_locations = (netrc_file,) + else: + netrc_locations = (f"~/{f}" for f in NETRC_FILES) + + try: + from netrc import NetrcParseError, netrc + + netrc_path = None + + for f in netrc_locations: + loc = os.path.expanduser(f) + if os.path.exists(loc): + netrc_path = loc + break + + # Abort early if there isn't one. + if netrc_path is None: + return + + ri = urlparse(url) + host = ri.hostname + + try: + _netrc = netrc(netrc_path).authenticators(host) + if _netrc and any(_netrc): + # Return with login / password + login_i = 0 if _netrc[0] else 1 + return (_netrc[login_i], _netrc[2]) + except (NetrcParseError, OSError): + # If there was a parsing error or a permissions issue reading the file, + # we'll just skip netrc auth unless explicitly asked to raise errors. + if raise_errors: + raise + + # App Engine hackiness. + except (ImportError, AttributeError): + pass + + +def guess_filename(obj): + """Tries to guess the filename of the given object.""" + name = getattr(obj, "name", None) + if name and isinstance(name, basestring) and name[0] != "<" and name[-1] != ">": + return os.path.basename(name) + + +def extract_zipped_paths(path): + """Replace nonexistent paths that look like they refer to a member of a zip + archive with the location of an extracted copy of the target, or else + just return the provided path unchanged. + """ + if os.path.exists(path): + # this is already a valid path, no need to do anything further + return path + + # find the first valid part of the provided path and treat that as a zip archive + # assume the rest of the path is the name of a member in the archive + archive, member = os.path.split(path) + while archive and not os.path.exists(archive): + archive, prefix = os.path.split(archive) + if not prefix: + # If we don't check for an empty prefix after the split (in other words, archive remains unchanged after the split), + # we _can_ end up in an infinite loop on a rare corner case affecting a small number of users + break + member = "/".join([prefix, member]) + + if not zipfile.is_zipfile(archive): + return path + + zip_file = zipfile.ZipFile(archive) + if member not in zip_file.namelist(): + return path + + # we have a valid zip archive and a valid member of that archive + suffix = os.path.splitext(member.split("/")[-1])[-1] + fd, extracted_path = tempfile.mkstemp(suffix=suffix) + try: + os.write(fd, zip_file.read(member)) + finally: + os.close(fd) + + return extracted_path + + +@contextlib.contextmanager +def atomic_open(filename): + """Write a file to the disk in an atomic fashion""" + tmp_descriptor, tmp_name = tempfile.mkstemp(dir=os.path.dirname(filename)) + try: + with os.fdopen(tmp_descriptor, "wb") as tmp_handler: + yield tmp_handler + os.replace(tmp_name, filename) + except BaseException: + os.remove(tmp_name) + raise + + +def from_key_val_list(value): + """Take an object and test to see if it can be represented as a + dictionary. Unless it can not be represented as such, return an + OrderedDict, e.g., + + :: + + >>> from_key_val_list([('key', 'val')]) + OrderedDict([('key', 'val')]) + >>> from_key_val_list('string') + Traceback (most recent call last): + ... + ValueError: cannot encode objects that are not 2-tuples + >>> from_key_val_list({'key': 'val'}) + OrderedDict([('key', 'val')]) + + :rtype: OrderedDict + """ + if value is None: + return None + + if isinstance(value, (str, bytes, bool, int)): + raise ValueError("cannot encode objects that are not 2-tuples") + + return OrderedDict(value) + + +def to_key_val_list(value): + """Take an object and test to see if it can be represented as a + dictionary. If it can be, return a list of tuples, e.g., + + :: + + >>> to_key_val_list([('key', 'val')]) + [('key', 'val')] + >>> to_key_val_list({'key': 'val'}) + [('key', 'val')] + >>> to_key_val_list('string') + Traceback (most recent call last): + ... + ValueError: cannot encode objects that are not 2-tuples + + :rtype: list + """ + if value is None: + return None + + if isinstance(value, (str, bytes, bool, int)): + raise ValueError("cannot encode objects that are not 2-tuples") + + if isinstance(value, Mapping): + value = value.items() + + return list(value) + + +# From mitsuhiko/werkzeug (used with permission). +def parse_list_header(value): + """Parse lists as described by RFC 2068 Section 2. + + In particular, parse comma-separated lists where the elements of + the list may include quoted-strings. A quoted-string could + contain a comma. A non-quoted string could have quotes in the + middle. Quotes are removed automatically after parsing. + + It basically works like :func:`parse_set_header` just that items + may appear multiple times and case sensitivity is preserved. + + The return value is a standard :class:`list`: + + >>> parse_list_header('token, "quoted value"') + ['token', 'quoted value'] + + To create a header from the :class:`list` again, use the + :func:`dump_header` function. + + :param value: a string with a list header. + :return: :class:`list` + :rtype: list + """ + result = [] + for item in _parse_list_header(value): + if item[:1] == item[-1:] == '"': + item = unquote_header_value(item[1:-1]) + result.append(item) + return result + + +# From mitsuhiko/werkzeug (used with permission). +def parse_dict_header(value): + """Parse lists of key, value pairs as described by RFC 2068 Section 2 and + convert them into a python dict: + + >>> d = parse_dict_header('foo="is a fish", bar="as well"') + >>> type(d) is dict + True + >>> sorted(d.items()) + [('bar', 'as well'), ('foo', 'is a fish')] + + If there is no value for a key it will be `None`: + + >>> parse_dict_header('key_without_value') + {'key_without_value': None} + + To create a header from the :class:`dict` again, use the + :func:`dump_header` function. + + :param value: a string with a dict header. + :return: :class:`dict` + :rtype: dict + """ + result = {} + for item in _parse_list_header(value): + if "=" not in item: + result[item] = None + continue + name, value = item.split("=", 1) + if value[:1] == value[-1:] == '"': + value = unquote_header_value(value[1:-1]) + result[name] = value + return result + + +# From mitsuhiko/werkzeug (used with permission). +def unquote_header_value(value, is_filename=False): + r"""Unquotes a header value. (Reversal of :func:`quote_header_value`). + This does not use the real unquoting but what browsers are actually + using for quoting. + + :param value: the header value to unquote. + :rtype: str + """ + if value and value[0] == value[-1] == '"': + # this is not the real unquoting, but fixing this so that the + # RFC is met will result in bugs with internet explorer and + # probably some other browsers as well. IE for example is + # uploading files with "C:\foo\bar.txt" as filename + value = value[1:-1] + + # if this is a filename and the starting characters look like + # a UNC path, then just return the value without quotes. Using the + # replace sequence below on a UNC path has the effect of turning + # the leading double slash into a single slash and then + # _fix_ie_filename() doesn't work correctly. See #458. + if not is_filename or value[:2] != "\\\\": + return value.replace("\\\\", "\\").replace('\\"', '"') + return value + + +def dict_from_cookiejar(cj): + """Returns a key/value dictionary from a CookieJar. + + :param cj: CookieJar object to extract cookies from. + :rtype: dict + """ + + cookie_dict = {cookie.name: cookie.value for cookie in cj} + return cookie_dict + + +def add_dict_to_cookiejar(cj, cookie_dict): + """Returns a CookieJar from a key/value dictionary. + + :param cj: CookieJar to insert cookies into. + :param cookie_dict: Dict of key/values to insert into CookieJar. + :rtype: CookieJar + """ + + return cookiejar_from_dict(cookie_dict, cj) + + +def get_encodings_from_content(content): + """Returns encodings from given content string. + + :param content: bytestring to extract encodings from. + """ + warnings.warn( + ( + "In requests 3.0, get_encodings_from_content will be removed. For " + "more information, please see the discussion on issue #2266. (This" + " warning should only appear once.)" + ), + DeprecationWarning, + ) + + charset_re = re.compile(r']', flags=re.I) + pragma_re = re.compile(r']', flags=re.I) + xml_re = re.compile(r'^<\?xml.*?encoding=["\']*(.+?)["\'>]') + + return ( + charset_re.findall(content) + + pragma_re.findall(content) + + xml_re.findall(content) + ) + + +def _parse_content_type_header(header): + """Returns content type and parameters from given header. + + :param header: string + :return: tuple containing content type and dictionary of + parameters. + """ + + tokens = header.split(";") + content_type, params = tokens[0].strip(), tokens[1:] + params_dict = {} + strip_chars = "\"' " + + for param in params: + param = param.strip() + if param and (idx := param.find("=")) != -1: + key = param[:idx].strip(strip_chars) + value = param[idx + 1 :].strip(strip_chars) + params_dict[key.lower()] = value + return content_type, params_dict + + +def get_encoding_from_headers(headers): + """Returns encodings from given HTTP Header Dict. + + :param headers: dictionary to extract encoding from. + :rtype: str + """ + + content_type = headers.get("content-type") + + if not content_type: + return None + + content_type, params = _parse_content_type_header(content_type) + + if "charset" in params: + return params["charset"].strip("'\"") + + if "text" in content_type: + return "ISO-8859-1" + + if "application/json" in content_type: + # Assume UTF-8 based on RFC 4627: https://www.ietf.org/rfc/rfc4627.txt since the charset was unset + return "utf-8" + + +def stream_decode_response_unicode(iterator, r): + """Stream decodes an iterator.""" + + if r.encoding is None: + yield from iterator + return + + decoder = codecs.getincrementaldecoder(r.encoding)(errors="replace") + for chunk in iterator: + rv = decoder.decode(chunk) + if rv: + yield rv + rv = decoder.decode(b"", final=True) + if rv: + yield rv + + +def iter_slices(string, slice_length): + """Iterate over slices of a string.""" + pos = 0 + if slice_length is None or slice_length <= 0: + slice_length = len(string) + while pos < len(string): + yield string[pos : pos + slice_length] + pos += slice_length + + +def get_unicode_from_response(r): + """Returns the requested content back in unicode. + + :param r: Response object to get unicode content from. + + Tried: + + 1. charset from content-type + 2. fall back and replace all unicode characters + + :rtype: str + """ + warnings.warn( + ( + "In requests 3.0, get_unicode_from_response will be removed. For " + "more information, please see the discussion on issue #2266. (This" + " warning should only appear once.)" + ), + DeprecationWarning, + ) + + tried_encodings = [] + + # Try charset from content-type + encoding = get_encoding_from_headers(r.headers) + + if encoding: + try: + return str(r.content, encoding) + except UnicodeError: + tried_encodings.append(encoding) + + # Fall back: + try: + return str(r.content, encoding, errors="replace") + except TypeError: + return r.content + + +# The unreserved URI characters (RFC 3986) +UNRESERVED_SET = frozenset( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789-._~" +) + + +def unquote_unreserved(uri): + """Un-escape any percent-escape sequences in a URI that are unreserved + characters. This leaves all reserved, illegal and non-ASCII bytes encoded. + + :rtype: str + """ + parts = uri.split("%") + for i in range(1, len(parts)): + h = parts[i][0:2] + if len(h) == 2 and h.isalnum(): + try: + c = chr(int(h, 16)) + except ValueError: + raise InvalidURL(f"Invalid percent-escape sequence: '{h}'") + + if c in UNRESERVED_SET: + parts[i] = c + parts[i][2:] + else: + parts[i] = f"%{parts[i]}" + else: + parts[i] = f"%{parts[i]}" + return "".join(parts) + + +def requote_uri(uri): + """Re-quote the given URI. + + This function passes the given URI through an unquote/quote cycle to + ensure that it is fully and consistently quoted. + + :rtype: str + """ + safe_with_percent = "!#$%&'()*+,/:;=?@[]~" + safe_without_percent = "!#$&'()*+,/:;=?@[]~" + try: + # Unquote only the unreserved characters + # Then quote only illegal characters (do not quote reserved, + # unreserved, or '%') + return quote(unquote_unreserved(uri), safe=safe_with_percent) + except InvalidURL: + # We couldn't unquote the given URI, so let's try quoting it, but + # there may be unquoted '%'s in the URI. We need to make sure they're + # properly quoted so they do not cause issues elsewhere. + return quote(uri, safe=safe_without_percent) + + +def address_in_network(ip, net): + """This function allows you to check if an IP belongs to a network subnet + + Example: returns True if ip = 192.168.1.1 and net = 192.168.1.0/24 + returns False if ip = 192.168.1.1 and net = 192.168.100.0/24 + + :rtype: bool + """ + ipaddr = struct.unpack("=L", socket.inet_aton(ip))[0] + netaddr, bits = net.split("/") + netmask = struct.unpack("=L", socket.inet_aton(dotted_netmask(int(bits))))[0] + network = struct.unpack("=L", socket.inet_aton(netaddr))[0] & netmask + return (ipaddr & netmask) == (network & netmask) + + +def dotted_netmask(mask): + """Converts mask from /xx format to xxx.xxx.xxx.xxx + + Example: if mask is 24 function returns 255.255.255.0 + + :rtype: str + """ + bits = 0xFFFFFFFF ^ (1 << 32 - mask) - 1 + return socket.inet_ntoa(struct.pack(">I", bits)) + + +def is_ipv4_address(string_ip): + """ + :rtype: bool + """ + try: + socket.inet_aton(string_ip) + except OSError: + return False + return True + + +def is_valid_cidr(string_network): + """ + Very simple check of the cidr format in no_proxy variable. + + :rtype: bool + """ + if string_network.count("/") == 1: + try: + mask = int(string_network.split("/")[1]) + except ValueError: + return False + + if mask < 1 or mask > 32: + return False + + try: + socket.inet_aton(string_network.split("/")[0]) + except OSError: + return False + else: + return False + return True + + +@contextlib.contextmanager +def set_environ(env_name, value): + """Set the environment variable 'env_name' to 'value' + + Save previous value, yield, and then restore the previous value stored in + the environment variable 'env_name'. + + If 'value' is None, do nothing""" + value_changed = value is not None + if value_changed: + old_value = os.environ.get(env_name) + os.environ[env_name] = value + try: + yield + finally: + if value_changed: + if old_value is None: + del os.environ[env_name] + else: + os.environ[env_name] = old_value + + +def should_bypass_proxies(url, no_proxy): + """ + Returns whether we should bypass proxies or not. + + :rtype: bool + """ + + # Prioritize lowercase environment variables over uppercase + # to keep a consistent behaviour with other http projects (curl, wget). + def get_proxy(key): + return os.environ.get(key) or os.environ.get(key.upper()) + + # First check whether no_proxy is defined. If it is, check that the URL + # we're getting isn't in the no_proxy list. + no_proxy_arg = no_proxy + if no_proxy is None: + no_proxy = get_proxy("no_proxy") + parsed = urlparse(url) + + if parsed.hostname is None: + # URLs don't always have hostnames, e.g. file:/// urls. + return True + + if no_proxy: + # We need to check whether we match here. We need to see if we match + # the end of the hostname, both with and without the port. + no_proxy = (host for host in no_proxy.replace(" ", "").split(",") if host) + + if is_ipv4_address(parsed.hostname): + for proxy_ip in no_proxy: + if is_valid_cidr(proxy_ip): + if address_in_network(parsed.hostname, proxy_ip): + return True + elif parsed.hostname == proxy_ip: + # If no_proxy ip was defined in plain IP notation instead of cidr notation & + # matches the IP of the index + return True + else: + host_with_port = parsed.hostname + if parsed.port: + host_with_port += f":{parsed.port}" + + for host in no_proxy: + if parsed.hostname.endswith(host) or host_with_port.endswith(host): + # The URL does match something in no_proxy, so we don't want + # to apply the proxies on this URL. + return True + + with set_environ("no_proxy", no_proxy_arg): + # parsed.hostname can be `None` in cases such as a file URI. + try: + bypass = proxy_bypass(parsed.hostname) + except (TypeError, socket.gaierror): + bypass = False + + if bypass: + return True + + return False + + +def get_environ_proxies(url, no_proxy=None): + """ + Return a dict of environment proxies. + + :rtype: dict + """ + if should_bypass_proxies(url, no_proxy=no_proxy): + return {} + else: + return getproxies() + + +def select_proxy(url, proxies): + """Select a proxy for the url, if applicable. + + :param url: The url being for the request + :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs + """ + proxies = proxies or {} + urlparts = urlparse(url) + if urlparts.hostname is None: + return proxies.get(urlparts.scheme, proxies.get("all")) + + proxy_keys = [ + urlparts.scheme + "://" + urlparts.hostname, + urlparts.scheme, + "all://" + urlparts.hostname, + "all", + ] + proxy = None + for proxy_key in proxy_keys: + if proxy_key in proxies: + proxy = proxies[proxy_key] + break + + return proxy + + +def resolve_proxies(request, proxies, trust_env=True): + """This method takes proxy information from a request and configuration + input to resolve a mapping of target proxies. This will consider settings + such as NO_PROXY to strip proxy configurations. + + :param request: Request or PreparedRequest + :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs + :param trust_env: Boolean declaring whether to trust environment configs + + :rtype: dict + """ + proxies = proxies if proxies is not None else {} + url = request.url + scheme = urlparse(url).scheme + no_proxy = proxies.get("no_proxy") + new_proxies = proxies.copy() + + if trust_env and not should_bypass_proxies(url, no_proxy=no_proxy): + environ_proxies = get_environ_proxies(url, no_proxy=no_proxy) + + proxy = environ_proxies.get(scheme, environ_proxies.get("all")) + + if proxy: + new_proxies.setdefault(scheme, proxy) + return new_proxies + + +def default_user_agent(name="python-requests"): + """ + Return a string representing the default user agent. + + :rtype: str + """ + return f"{name}/{__version__}" + + +def default_headers(): + """ + :rtype: requests.structures.CaseInsensitiveDict + """ + return CaseInsensitiveDict( + { + "User-Agent": default_user_agent(), + "Accept-Encoding": DEFAULT_ACCEPT_ENCODING, + "Accept": "*/*", + "Connection": "keep-alive", + } + ) + + +def parse_header_links(value): + """Return a list of parsed link headers proxies. + + i.e. Link: ; rel=front; type="image/jpeg",; rel=back;type="image/jpeg" + + :rtype: list + """ + + links = [] + + replace_chars = " '\"" + + value = value.strip(replace_chars) + if not value: + return links + + for val in re.split(", *<", value): + try: + url, params = val.split(";", 1) + except ValueError: + url, params = val, "" + + link = {"url": url.strip("<> '\"")} + + for param in params.split(";"): + try: + key, value = param.split("=") + except ValueError: + break + + link[key.strip(replace_chars)] = value.strip(replace_chars) + + links.append(link) + + return links + + +# Null bytes; no need to recreate these on each call to guess_json_utf +_null = "\x00".encode("ascii") # encoding to ASCII for Python 3 +_null2 = _null * 2 +_null3 = _null * 3 + + +def guess_json_utf(data): + """ + :rtype: str + """ + # JSON always starts with two ASCII characters, so detection is as + # easy as counting the nulls and from their location and count + # determine the encoding. Also detect a BOM, if present. + sample = data[:4] + if sample in (codecs.BOM_UTF32_LE, codecs.BOM_UTF32_BE): + return "utf-32" # BOM included + if sample[:3] == codecs.BOM_UTF8: + return "utf-8-sig" # BOM included, MS style (discouraged) + if sample[:2] in (codecs.BOM_UTF16_LE, codecs.BOM_UTF16_BE): + return "utf-16" # BOM included + nullcount = sample.count(_null) + if nullcount == 0: + return "utf-8" + if nullcount == 2: + if sample[::2] == _null2: # 1st and 3rd are null + return "utf-16-be" + if sample[1::2] == _null2: # 2nd and 4th are null + return "utf-16-le" + # Did not detect 2 valid UTF-16 ascii-range characters + if nullcount == 3: + if sample[:3] == _null3: + return "utf-32-be" + if sample[1:] == _null3: + return "utf-32-le" + # Did not detect a valid UTF-32 ascii-range character + return None + + +def prepend_scheme_if_needed(url, new_scheme): + """Given a URL that may or may not have a scheme, prepend the given scheme. + Does not replace a present scheme with the one provided as an argument. + + :rtype: str + """ + parsed = parse_url(url) + scheme, auth, host, port, path, query, fragment = parsed + + # A defect in urlparse determines that there isn't a netloc present in some + # urls. We previously assumed parsing was overly cautious, and swapped the + # netloc and path. Due to a lack of tests on the original defect, this is + # maintained with parse_url for backwards compatibility. + netloc = parsed.netloc + if not netloc: + netloc, path = path, netloc + + if auth: + # parse_url doesn't provide the netloc with auth + # so we'll add it ourselves. + netloc = "@".join([auth, netloc]) + if scheme is None: + scheme = new_scheme + if path is None: + path = "" + + return urlunparse((scheme, netloc, path, "", query, fragment)) + + +def get_auth_from_url(url): + """Given a url with authentication components, extract them into a tuple of + username,password. + + :rtype: (str,str) + """ + parsed = urlparse(url) + + try: + auth = (unquote(parsed.username), unquote(parsed.password)) + except (AttributeError, TypeError): + auth = ("", "") + + return auth + + +def check_header_validity(header): + """Verifies that header parts don't contain leading whitespace + reserved characters, or return characters. + + :param header: tuple, in the format (name, value). + """ + name, value = header + _validate_header_part(header, name, 0) + _validate_header_part(header, value, 1) + + +def _validate_header_part(header, header_part, header_validator_index): + if isinstance(header_part, str): + validator = _HEADER_VALIDATORS_STR[header_validator_index] + elif isinstance(header_part, bytes): + validator = _HEADER_VALIDATORS_BYTE[header_validator_index] + else: + raise InvalidHeader( + f"Header part ({header_part!r}) from {header} " + f"must be of type str or bytes, not {type(header_part)}" + ) + + if not validator.match(header_part): + header_kind = "name" if header_validator_index == 0 else "value" + raise InvalidHeader( + f"Invalid leading whitespace, reserved character(s), or return " + f"character(s) in header {header_kind}: {header_part!r}" + ) + + +def urldefragauth(url): + """ + Given a url remove the fragment and the authentication part. + + :rtype: str + """ + scheme, netloc, path, params, query, fragment = urlparse(url) + + # see func:`prepend_scheme_if_needed` + if not netloc: + netloc, path = path, netloc + + netloc = netloc.rsplit("@", 1)[-1] + + return urlunparse((scheme, netloc, path, params, query, "")) + + +def rewind_body(prepared_request): + """Move file pointer back to its recorded starting position + so it can be read again on redirect. + """ + body_seek = getattr(prepared_request.body, "seek", None) + if body_seek is not None and isinstance( + prepared_request._body_position, integer_types + ): + try: + body_seek(prepared_request._body_position) + except OSError: + raise UnrewindableBodyError( + "An error occurred when rewinding request body for redirect." + ) + else: + raise UnrewindableBodyError("Unable to rewind request body for redirect.") diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/rich-15.0.0.dist-info/INSTALLER b/bioagent-bench/.venv/lib/python3.14/site-packages/rich-15.0.0.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..5c69047b2eb8235994febeeae1da4a82365a240a --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/rich-15.0.0.dist-info/INSTALLER @@ -0,0 +1 @@ +uv \ No newline at end of file diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/rich-15.0.0.dist-info/METADATA b/bioagent-bench/.venv/lib/python3.14/site-packages/rich-15.0.0.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..574ddfb0ed75fea2532f0c08ad5a00059414ce6d --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/rich-15.0.0.dist-info/METADATA @@ -0,0 +1,479 @@ +Metadata-Version: 2.4 +Name: rich +Version: 15.0.0 +Summary: Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal +License: MIT +License-File: LICENSE +Author: Will McGugan +Author-email: willmcgugan@gmail.com +Requires-Python: >=3.9.0 +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Console +Classifier: Framework :: IPython +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: MacOS +Classifier: Operating System :: Microsoft :: Windows +Classifier: Operating System :: POSIX :: Linux +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Typing :: Typed +Provides-Extra: jupyter +Requires-Dist: ipywidgets (>=7.5.1,<9) ; extra == "jupyter" +Requires-Dist: markdown-it-py (>=2.2.0) +Requires-Dist: pygments (>=2.13.0,<3.0.0) +Project-URL: Documentation, https://rich.readthedocs.io/en/latest/ +Project-URL: Homepage, https://github.com/Textualize/rich +Description-Content-Type: text/markdown + +[![Supported Python Versions](https://img.shields.io/pypi/pyversions/rich)](https://pypi.org/project/rich/) [![PyPI version](https://badge.fury.io/py/rich.svg)](https://badge.fury.io/py/rich) + +[![Downloads](https://pepy.tech/badge/rich/month)](https://pepy.tech/project/rich) +[![codecov](https://img.shields.io/codecov/c/github/Textualize/rich?label=codecov&logo=codecov)](https://codecov.io/gh/Textualize/rich) +[![Rich blog](https://img.shields.io/badge/blog-rich%20news-yellowgreen)](https://www.willmcgugan.com/tag/rich/) +[![Twitter Follow](https://img.shields.io/twitter/follow/willmcgugan.svg?style=social)](https://twitter.com/willmcgugan) + +![Logo](https://github.com/textualize/rich/raw/master/imgs/logo.svg) + +[English readme](https://github.com/textualize/rich/blob/master/README.md) + • [简体中文 readme](https://github.com/textualize/rich/blob/master/README.cn.md) + • [正體中文 readme](https://github.com/textualize/rich/blob/master/README.zh-tw.md) + • [Lengua española readme](https://github.com/textualize/rich/blob/master/README.es.md) + • [Deutsche readme](https://github.com/textualize/rich/blob/master/README.de.md) + • [Läs på svenska](https://github.com/textualize/rich/blob/master/README.sv.md) + • [日本語 readme](https://github.com/textualize/rich/blob/master/README.ja.md) + • [한국어 readme](https://github.com/textualize/rich/blob/master/README.kr.md) + • [Français readme](https://github.com/textualize/rich/blob/master/README.fr.md) + • [Schwizerdütsch readme](https://github.com/textualize/rich/blob/master/README.de-ch.md) + • [हिन्दी readme](https://github.com/textualize/rich/blob/master/README.hi.md) + • [Português brasileiro readme](https://github.com/textualize/rich/blob/master/README.pt-br.md) + • [Italian readme](https://github.com/textualize/rich/blob/master/README.it.md) + • [Русский readme](https://github.com/textualize/rich/blob/master/README.ru.md) + • [Indonesian readme](https://github.com/textualize/rich/blob/master/README.id.md) + • [فارسی readme](https://github.com/textualize/rich/blob/master/README.fa.md) + • [Türkçe readme](https://github.com/textualize/rich/blob/master/README.tr.md) + • [Polskie readme](https://github.com/textualize/rich/blob/master/README.pl.md) + + +Rich is a Python library for _rich_ text and beautiful formatting in the terminal. + +The [Rich API](https://rich.readthedocs.io/en/latest/) makes it easy to add color and style to terminal output. Rich can also render pretty tables, progress bars, markdown, syntax highlighted source code, tracebacks, and more — out of the box. + +![Features](https://github.com/textualize/rich/raw/master/imgs/features.png) + +For a video introduction to Rich see [calmcode.io](https://calmcode.io/rich/introduction.html) by [@fishnets88](https://twitter.com/fishnets88). + +See what [people are saying about Rich](https://www.willmcgugan.com/blog/pages/post/rich-tweets/). + +## Compatibility + +Rich works with Linux, macOS and Windows. True color / emoji works with new Windows Terminal, classic terminal is limited to 16 colors. Rich requires Python 3.8 or later. + +Rich works with [Jupyter notebooks](https://jupyter.org/) with no additional configuration required. + +## Installing + +Install with `pip` or your favorite PyPI package manager. + +```sh +python -m pip install rich +``` + +Run the following to test Rich output on your terminal: + +```sh +python -m rich +``` + +## Rich Print + +To effortlessly add rich output to your application, you can import the [rich print](https://rich.readthedocs.io/en/latest/introduction.html#quick-start) method, which has the same signature as the builtin Python function. Try this: + +```python +from rich import print + +print("Hello, [bold magenta]World[/bold magenta]!", ":vampire:", locals()) +``` + +![Hello World](https://github.com/textualize/rich/raw/master/imgs/print.png) + +## Rich REPL + +Rich can be installed in the Python REPL, so that any data structures will be pretty printed and highlighted. + +```python +>>> from rich import pretty +>>> pretty.install() +``` + +![REPL](https://github.com/textualize/rich/raw/master/imgs/repl.png) + +## Using the Console + +For more control over rich terminal content, import and construct a [Console](https://rich.readthedocs.io/en/latest/reference/console.html#rich.console.Console) object. + +```python +from rich.console import Console + +console = Console() +``` + +The Console object has a `print` method which has an intentionally similar interface to the builtin `print` function. Here's an example of use: + +```python +console.print("Hello", "World!") +``` + +As you might expect, this will print `"Hello World!"` to the terminal. Note that unlike the builtin `print` function, Rich will word-wrap your text to fit within the terminal width. + +There are a few ways of adding color and style to your output. You can set a style for the entire output by adding a `style` keyword argument. Here's an example: + +```python +console.print("Hello", "World!", style="bold red") +``` + +The output will be something like the following: + +![Hello World](https://github.com/textualize/rich/raw/master/imgs/hello_world.png) + +That's fine for styling a line of text at a time. For more finely grained styling, Rich renders a special markup which is similar in syntax to [bbcode](https://en.wikipedia.org/wiki/BBCode). Here's an example: + +```python +console.print("Where there is a [bold cyan]Will[/bold cyan] there [u]is[/u] a [i]way[/i].") +``` + +![Console Markup](https://github.com/textualize/rich/raw/master/imgs/where_there_is_a_will.png) + +You can use a Console object to generate sophisticated output with minimal effort. See the [Console API](https://rich.readthedocs.io/en/latest/console.html) docs for details. + +## Rich Inspect + +Rich has an [inspect](https://rich.readthedocs.io/en/latest/reference/init.html?highlight=inspect#rich.inspect) function which can produce a report on any Python object, such as class, instance, or builtin. + +```python +>>> my_list = ["foo", "bar"] +>>> from rich import inspect +>>> inspect(my_list, methods=True) +``` + +![Log](https://github.com/textualize/rich/raw/master/imgs/inspect.png) + +See the [inspect docs](https://rich.readthedocs.io/en/latest/reference/init.html#rich.inspect) for details. + +# Rich Library + +Rich contains a number of builtin _renderables_ you can use to create elegant output in your CLI and help you debug your code. + +Click the following headings for details: + +
    +Log + +The Console object has a `log()` method which has a similar interface to `print()`, but also renders a column for the current time and the file and line which made the call. By default Rich will do syntax highlighting for Python structures and for repr strings. If you log a collection (i.e. a dict or a list) Rich will pretty print it so that it fits in the available space. Here's an example of some of these features. + +```python +from rich.console import Console +console = Console() + +test_data = [ + {"jsonrpc": "2.0", "method": "sum", "params": [None, 1, 2, 4, False, True], "id": "1",}, + {"jsonrpc": "2.0", "method": "notify_hello", "params": [7]}, + {"jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": "2"}, +] + +def test_log(): + enabled = False + context = { + "foo": "bar", + } + movies = ["Deadpool", "Rise of the Skywalker"] + console.log("Hello from", console, "!") + console.log(test_data, log_locals=True) + + +test_log() +``` + +The above produces the following output: + +![Log](https://github.com/textualize/rich/raw/master/imgs/log.png) + +Note the `log_locals` argument, which outputs a table containing the local variables where the log method was called. + +The log method could be used for logging to the terminal for long running applications such as servers, but is also a very nice debugging aid. + +
    +
    +Logging Handler + +You can also use the builtin [Handler class](https://rich.readthedocs.io/en/latest/logging.html) to format and colorize output from Python's logging module. Here's an example of the output: + +![Logging](https://github.com/textualize/rich/raw/master/imgs/logging.png) + +
    + +
    +Emoji + +To insert an emoji in to console output place the name between two colons. Here's an example: + +```python +>>> console.print(":smiley: :vampire: :pile_of_poo: :thumbs_up: :raccoon:") +😃 🧛 💩 👍 🦝 +``` + +Please use this feature wisely. + +
    + +
    +Tables + +Rich can render flexible [tables](https://rich.readthedocs.io/en/latest/tables.html) with unicode box characters. There is a large variety of formatting options for borders, styles, cell alignment etc. + +![table movie](https://github.com/textualize/rich/raw/master/imgs/table_movie.gif) + +The animation above was generated with [table_movie.py](https://github.com/textualize/rich/blob/master/examples/table_movie.py) in the examples directory. + +Here's a simpler table example: + +```python +from rich.console import Console +from rich.table import Table + +console = Console() + +table = Table(show_header=True, header_style="bold magenta") +table.add_column("Date", style="dim", width=12) +table.add_column("Title") +table.add_column("Production Budget", justify="right") +table.add_column("Box Office", justify="right") +table.add_row( + "Dec 20, 2019", "Star Wars: The Rise of Skywalker", "$275,000,000", "$375,126,118" +) +table.add_row( + "May 25, 2018", + "[red]Solo[/red]: A Star Wars Story", + "$275,000,000", + "$393,151,347", +) +table.add_row( + "Dec 15, 2017", + "Star Wars Ep. VIII: The Last Jedi", + "$262,000,000", + "[bold]$1,332,539,889[/bold]", +) + +console.print(table) +``` + +This produces the following output: + +![table](https://github.com/textualize/rich/raw/master/imgs/table.png) + +Note that console markup is rendered in the same way as `print()` and `log()`. In fact, anything that is renderable by Rich may be included in the headers / rows (even other tables). + +The `Table` class is smart enough to resize columns to fit the available width of the terminal, wrapping text as required. Here's the same example, with the terminal made smaller than the table above: + +![table2](https://github.com/textualize/rich/raw/master/imgs/table2.png) + +
    + +
    +Progress Bars + +Rich can render multiple flicker-free [progress](https://rich.readthedocs.io/en/latest/progress.html) bars to track long-running tasks. + +For basic usage, wrap any sequence in the `track` function and iterate over the result. Here's an example: + +```python +from rich.progress import track + +for step in track(range(100)): + do_step(step) +``` + +It's not much harder to add multiple progress bars. Here's an example taken from the docs: + +![progress](https://github.com/textualize/rich/raw/master/imgs/progress.gif) + +The columns may be configured to show any details you want. Built-in columns include percentage complete, file size, file speed, and time remaining. Here's another example showing a download in progress: + +![progress](https://github.com/textualize/rich/raw/master/imgs/downloader.gif) + +To try this out yourself, see [examples/downloader.py](https://github.com/textualize/rich/blob/master/examples/downloader.py) which can download multiple URLs simultaneously while displaying progress. + +
    + +
    +Status + +For situations where it is hard to calculate progress, you can use the [status](https://rich.readthedocs.io/en/latest/reference/console.html#rich.console.Console.status) method which will display a 'spinner' animation and message. The animation won't prevent you from using the console as normal. Here's an example: + +```python +from time import sleep +from rich.console import Console + +console = Console() +tasks = [f"task {n}" for n in range(1, 11)] + +with console.status("[bold green]Working on tasks...") as status: + while tasks: + task = tasks.pop(0) + sleep(1) + console.log(f"{task} complete") +``` + +This generates the following output in the terminal. + +![status](https://github.com/textualize/rich/raw/master/imgs/status.gif) + +The spinner animations were borrowed from [cli-spinners](https://www.npmjs.com/package/cli-spinners). You can select a spinner by specifying the `spinner` parameter. Run the following command to see the available values: + +``` +python -m rich.spinner +``` + +The above command generates the following output in the terminal: + +![spinners](https://github.com/textualize/rich/raw/master/imgs/spinners.gif) + +
    + +
    +Tree + +Rich can render a [tree](https://rich.readthedocs.io/en/latest/tree.html) with guide lines. A tree is ideal for displaying a file structure, or any other hierarchical data. + +The labels of the tree can be simple text or anything else Rich can render. Run the following for a demonstration: + +``` +python -m rich.tree +``` + +This generates the following output: + +![markdown](https://github.com/textualize/rich/raw/master/imgs/tree.png) + +See the [tree.py](https://github.com/textualize/rich/blob/master/examples/tree.py) example for a script that displays a tree view of any directory, similar to the linux `tree` command. + +
    + +
    +Columns + +Rich can render content in neat [columns](https://rich.readthedocs.io/en/latest/columns.html) with equal or optimal width. Here's a very basic clone of the (MacOS / Linux) `ls` command which displays a directory listing in columns: + +```python +import os +import sys + +from rich import print +from rich.columns import Columns + +directory = os.listdir(sys.argv[1]) +print(Columns(directory)) +``` + +The following screenshot is the output from the [columns example](https://github.com/textualize/rich/blob/master/examples/columns.py) which displays data pulled from an API in columns: + +![columns](https://github.com/textualize/rich/raw/master/imgs/columns.png) + +
    + +
    +Markdown + +Rich can render [markdown](https://rich.readthedocs.io/en/latest/markdown.html) and does a reasonable job of translating the formatting to the terminal. + +To render markdown import the `Markdown` class and construct it with a string containing markdown code. Then print it to the console. Here's an example: + +```python +from rich.console import Console +from rich.markdown import Markdown + +console = Console() +with open("README.md") as readme: + markdown = Markdown(readme.read()) +console.print(markdown) +``` + +This will produce output something like the following: + +![markdown](https://github.com/textualize/rich/raw/master/imgs/markdown.png) + +
    + +
    +Syntax Highlighting + +Rich uses the [pygments](https://pygments.org/) library to implement [syntax highlighting](https://rich.readthedocs.io/en/latest/syntax.html). Usage is similar to rendering markdown; construct a `Syntax` object and print it to the console. Here's an example: + +```python +from rich.console import Console +from rich.syntax import Syntax + +my_code = ''' +def iter_first_last(values: Iterable[T]) -> Iterable[Tuple[bool, bool, T]]: + """Iterate and generate a tuple with a flag for first and last value.""" + iter_values = iter(values) + try: + previous_value = next(iter_values) + except StopIteration: + return + first = True + for value in iter_values: + yield first, False, previous_value + first = False + previous_value = value + yield first, True, previous_value +''' +syntax = Syntax(my_code, "python", theme="monokai", line_numbers=True) +console = Console() +console.print(syntax) +``` + +This will produce the following output: + +![syntax](https://github.com/textualize/rich/raw/master/imgs/syntax.png) + +
    + +
    +Tracebacks + +Rich can render [beautiful tracebacks](https://rich.readthedocs.io/en/latest/traceback.html) which are easier to read and show more code than standard Python tracebacks. You can set Rich as the default traceback handler so all uncaught exceptions will be rendered by Rich. + +Here's what it looks like on OSX (similar on Linux): + +![traceback](https://github.com/textualize/rich/raw/master/imgs/traceback.png) + +
    + +All Rich renderables make use of the [Console Protocol](https://rich.readthedocs.io/en/latest/protocol.html), which you can also use to implement your own Rich content. + +# Rich CLI + + +See also [Rich CLI](https://github.com/textualize/rich-cli) for a command line application powered by Rich. Syntax highlight code, render markdown, display CSVs in tables, and more, directly from the command prompt. + + +![Rich CLI](https://raw.githubusercontent.com/Textualize/rich-cli/main/imgs/rich-cli-splash.jpg) + +# Textual + +See also Rich's sister project, [Textual](https://github.com/Textualize/textual), which you can use to build sophisticated User Interfaces in the terminal. + +![textual-splash](https://github.com/user-attachments/assets/4caeb77e-48c0-4cf7-b14d-c53ded855ffd) + +# Toad + +[Toad](https://github.com/batrachianai/toad) is a unified interface for agentic coding. Built with Rich and Textual. + +![toad](https://github.com/user-attachments/assets/6678b707-1aeb-420f-99ad-abfcd4356771) + diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/rich-15.0.0.dist-info/RECORD b/bioagent-bench/.venv/lib/python3.14/site-packages/rich-15.0.0.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..0309979f256a3ad186633342e251977c47437ea8 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/rich-15.0.0.dist-info/RECORD @@ -0,0 +1,107 @@ +rich-15.0.0.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2 +rich-15.0.0.dist-info/METADATA,sha256=-tNPxgP6xEgb-B-N1ilJL8MCwOwxkJyGZEdRUSr7Ljs,18445 +rich-15.0.0.dist-info/RECORD,, +rich-15.0.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +rich-15.0.0.dist-info/WHEEL,sha256=kJCRJT_g0adfAJzTx2GUMmS80rTJIVHRCfG0DQgLq3o,88 +rich-15.0.0.dist-info/licenses/LICENSE,sha256=3u18F6QxgVgZCj6iOcyHmlpQJxzruYrnAl9I--WNyhU,1056 +rich/__init__.py,sha256=j7AA9_z_PANQbmRTwTkWZc_DVdqtBBHxBqGrMsz21CI,6131 +rich/__main__.py,sha256=kcy8BtBEUkYLCogRkwhCdJs0VNx6KF1n8kFU2g-7plg,7725 +rich/_emoji_codes.py,sha256=hu1VL9nbVdppJrVoijVshRlcRRe_v3dju3Mmd2sKZdY,140235 +rich/_emoji_replace.py,sha256=ysSn8smSqQW22XEfCH0lJ80vogUYZKr9dPImfUG2e6Y,1067 +rich/_export_format.py,sha256=RI08pSrm5tBSzPMvnbTqbD9WIalaOoN5d4M1RTmLq1Y,2128 +rich/_extension.py,sha256=G66PkbH_QdTJh6jD-J228O76CmAnr2hLQv72CgPPuzE,241 +rich/_fileno.py,sha256=HWZxP5C2ajMbHryvAQZseflVfQoGzsKOHzKGsLD8ynQ,799 +rich/_inspect.py,sha256=5pUYCqveN4ekb_mgHOYCipyZCrMGEJZYZs3y5QqmCf0,9894 +rich/_log_render.py,sha256=xBKCxqiO4FZk8eG56f8crFdrmJxFrJsQE3V3F-fFekc,3213 +rich/_loop.py,sha256=hV_6CLdoPm0va22Wpw4zKqM0RYsz3TZxXj0PoS-9eDQ,1236 +rich/_null_file.py,sha256=ADGKp1yt-k70FMKV6tnqCqecB-rSJzp-WQsD7LPL-kg,1394 +rich/_palettes.py,sha256=cdev1JQKZ0JvlguV9ipHgznTdnvlIzUFDBb0It2PzjI,7063 +rich/_pick.py,sha256=evDt8QN4lF5CiwrUIXlOJCntitBCOsI3ZLPEIAVRLJU,423 +rich/_ratio.py,sha256=IOtl78sQCYZsmHyxhe45krkb68u9xVz7zFsXVJD-b2Y,5325 +rich/_spinners.py,sha256=U2r1_g_1zSjsjiUdAESc2iAMc3i4ri_S8PYP6kQ5z1I,19919 +rich/_stack.py,sha256=-C8OK7rxn3sIUdVwxZBBpeHhIzX0eI-VM3MemYfaXm0,351 +rich/_timer.py,sha256=zelxbT6oPFZnNrwWPpc1ktUeAT-Vc4fuFcRZLQGLtMI,417 +rich/_unicode_data/__init__.py,sha256=40PhAFp3b88yPnRtTG6i9g5xj-5w98CET3C4Ip1LJAI,2631 +rich/_unicode_data/_versions.py,sha256=ftILR3G5dqmlQmCJYNovuyXfFneJY2eH2RvpV19tyYE,298 +rich/_unicode_data/unicode10-0-0.py,sha256=FUstYPZwDF15BEuAERqXHqIPvC1KY1SL20qLj999Fzo,14496 +rich/_unicode_data/unicode11-0-0.py,sha256=jp8SRKFBlIaHMafxAfhLiU7FizwdcgPVvytR3kSFtZo,14874 +rich/_unicode_data/unicode12-0-0.py,sha256=0XHXSjW5EXue6Qfgl_u0bK7NstiSdmqrR_F_Wqhs5bE,15216 +rich/_unicode_data/unicode12-1-0.py,sha256=kWcRGh1UVwsmK9qflfGsO_aM8AuNhLthJcsyXAOQryI,15189 +rich/_unicode_data/unicode13-0-0.py,sha256=DokduALtgaB-WvapW7X7PmM3kZFbgnCo9ifINWZtuDs,15519 +rich/_unicode_data/unicode14-0-0.py,sha256=Ni-3JnGf5sB4kGl8ip-Iyqoum-awJjZV0RoSNuo0yu8,15884 +rich/_unicode_data/unicode15-0-0.py,sha256=DBHkV5Si9vmtv_MiW6NmPQb8yUeP4N-9hNt2juZr4ZE,16156 +rich/_unicode_data/unicode15-1-0.py,sha256=uyCZLYrg0q7wGt3-uBdsF0Ay_x8MTMvFqK6aMOkl9rA,16129 +rich/_unicode_data/unicode16-0-0.py,sha256=5iGpbczVQ7BD2QGYb6k1h_OdILQXXuhcvI6dQFLKbMc,16480 +rich/_unicode_data/unicode17-0-0.py,sha256=NXbjFsAaTYdj6TYgk5Q9disv40LZHbedFGRl_48_9Y0,16704 +rich/_unicode_data/unicode4-1-0.py,sha256=yIApQGEG7VOFYY2FFfvOdPprE6VpDusjLlTlCy4G_Rw,9488 +rich/_unicode_data/unicode5-0-0.py,sha256=PkEI1x4RGwXSmgqHiu88jyz14VQT8bWiOtBDrECaVZg,9613 +rich/_unicode_data/unicode5-1-0.py,sha256=C0-PWziqpkVkf3PkPz2GWNwidCrjef5AHQGBpqTPkuY,9650 +rich/_unicode_data/unicode5-2-0.py,sha256=gM88qri3_9b0I37DGK1-ah-0SUed2VmvxUddB5UZLto,10390 +rich/_unicode_data/unicode6-0-0.py,sha256=hhcAipqKU81dZ4YbDXIlT7KEK9yNG_Lx-5GLgNTF75g,10604 +rich/_unicode_data/unicode6-1-0.py,sha256=PtUJYxwtU5S2A44YY_69Ka_wTkq-c7QCa-yibmkHvJs,10899 +rich/_unicode_data/unicode6-2-0.py,sha256=TdTOctGwO-OYVGif1DoRJvsaQISHw5EXHPt58fjB55g,10899 +rich/_unicode_data/unicode6-3-0.py,sha256=G9l8chu2MJTwZ_d_0tmH7PBg8KYYHaeuJCJNCHGHkDc,10924 +rich/_unicode_data/unicode7-0-0.py,sha256=po9GGYW7ilGU9MwGE6fny-OofqbDwelQrnOIm-IusjI,11630 +rich/_unicode_data/unicode8-0-0.py,sha256=4PjyI-w2Uj9xtanNlZm1d_2khcetMQlIefaoU2joGkY,11864 +rich/_unicode_data/unicode9-0-0.py,sha256=A-pQjvkTICUGXEDOiWNMqa6z6vv1E__HJffzu7zLi9c,14148 +rich/_win32_console.py,sha256=o2QN_IRx10biGP3Ap1neaqX8FBGlUKSmWM6Kw4OSg-U,22719 +rich/_windows.py,sha256=is3WpbHMj8WaTHYB11hc6lP2t4hlvt4TViTlHSmjsi0,1901 +rich/_windows_renderer.py,sha256=d799xOnxLbCCCzGu9-U7YLmIQkxtxQIBFQQ6iu4veSc,2759 +rich/_wrap.py,sha256=FlSsom5EX0LVkA3KWy34yHnCfLtqX-ZIepXKh-70rpc,3404 +rich/abc.py,sha256=dALMOGfKVNeAbvqq66IpTQxQUerxD7AE4FKwqd0eQKk,878 +rich/align.py,sha256=a8MbP-iJjoOFonxqgoP3YketKMWU3WFZaxjqAv2Qc5E,10726 +rich/ansi.py,sha256=TQ7pdksq0b3XPcw_tSGvL_QthPxNLKOYQImPQYWp6pM,6947 +rich/bar.py,sha256=ldbVHOzKJOnflVNuv1xS7g6dLX2E3wMnXkdPbpzJTcs,3263 +rich/box.py,sha256=SSolg8_pzHzY9QvJQo-qp0tbPsnj8O_2W4hmi1l-Zo0,10650 +rich/cells.py,sha256=fPRSnquYlCO4THgmUokOJOhNlcx8C4NZ2Xv9JM_7GbE,12335 +rich/color.py,sha256=3HSULVDj7qQkXUdFWv78JOiSZzfy5y1nkcYhna296V0,18211 +rich/color_triplet.py,sha256=3lhQkdJbvWPoLDO-AnYImAWmJvV5dlgYNCVZ97ORaN4,1054 +rich/columns.py,sha256=HUX0KcMm9dsKNi11fTbiM_h2iDtl8ySCaVcxlalEzq8,7131 +rich/console.py,sha256=RTP8ZpY2I6KiFEe1Adb1H4z-5-FHdReAk6Xm62HcE1o,101361 +rich/constrain.py,sha256=1VIPuC8AgtKWrcncQrjBdYqA3JVWysu6jZo1rrh7c7Q,1288 +rich/containers.py,sha256=c_56TxcedGYqDepHBMTuZdUIijitAQgnox-Qde0Z1qo,5502 +rich/control.py,sha256=HnsraFTzBaUQDzKJWXsfPv-PPmgGypSgSv7oANackqs,6475 +rich/default_styles.py,sha256=DqEUiGrj4N0eAcgDwjGeUSe8BLMxZw57L9RXnTCroTs,8401 +rich/diagnose.py,sha256=1RWnQoppPXjC_49AB4vtV048DK3ksQSq671C83Y6f-g,977 +rich/emoji.py,sha256=z7ABu0_61WJPWOM_dxhUHNaA0B6RQuZX3reljs1iWE8,2388 +rich/errors.py,sha256=5pP3Kc5d4QJ_c0KFsxrfyhjiPVe7J1zOqSFbFAzcV-Y,642 +rich/file_proxy.py,sha256=MvlB3tSsuVmH4qCWXK9SK3S_OohVRIczUw_Fufmsdt8,1750 +rich/filesize.py,sha256=_iz9lIpRgvW7MNSeCZnLg-HwzbP4GETg543WqD8SFs0,2484 +rich/highlighter.py,sha256=MIapWwjR8ahUcsDicgN4xLRbanbh5x_Qzh5kfdDrKKk,9729 +rich/json.py,sha256=omC2WHTgURxEosna1ftoSJCne2EX7MDuQtCdswS3qsk,5019 +rich/jupyter.py,sha256=G9pOJmR4ESIFYSd4MKGqmHqCtstx0oRWpyeTgv54-Xc,3228 +rich/layout.py,sha256=WR8PCSroYnteIT3zawxQ3k3ad1sQO5wGG1SZOoeBuBM,13944 +rich/live.py,sha256=UKvqLSuSzNHIBz5pxRrU7SPsnG48vJJx2TMHiaHlmnI,15317 +rich/live_render.py,sha256=jQO3X_p2wemGI2bdCAY_e5j9xXNTwDxuujbpoSRsH6c,3803 +rich/logging.py,sha256=7vhk7r_LhWTcwteyjLLM5Nfc839fwBuIiISvBZcGwzo,12622 +rich/markdown.py,sha256=MmbNh8IhWgdGNQkeNTHWNR470KQVACt_9aJu6TMourA,26549 +rich/markup.py,sha256=btpr271BLhiCR1jNglRnv2BpIzVcNefYwSMeW9teDbc,8427 +rich/measure.py,sha256=HmrIJX8sWRTHbgh8MxEay_83VkqNW_70s8aKP5ZcYI8,5305 +rich/padding.py,sha256=h8XnIivLrNtlxI3vQPKHXh4hAwjOJqZx0slM0z3g1_M,4896 +rich/pager.py,sha256=SO_ETBFKbg3n_AgOzXm41Sv36YxXAyI3_R-KOY2_uSc,828 +rich/palette.py,sha256=Ar6ZUrYHiFt6-Rr2k-k9F8V7hxgJYHNdqjk2vVXsLgc,3288 +rich/panel.py,sha256=9sQl00hPIqH5G2gALQo4NepFwpP0k9wT-s_gOms5pIc,11157 +rich/pretty.py,sha256=lxW_bhBfDTKe-grtzP1KQhQsqPZxfZN14th4JRVoLzQ,36349 +rich/progress.py,sha256=Z6Yz6pYO26LFXx24cXKvk2bEX6FkjK2Z5e2fy0Bt78s,60393 +rich/progress_bar.py,sha256=mZTPpJUwcfcdgQCTTz3kyY-fc79ddLwtx6Ghhxfo064,8162 +rich/prompt.py,sha256=1SIuBEelovvwCG2wp22PZwskA3IkmAsOxf0ruFkOhOk,12448 +rich/protocol.py,sha256=9OHzfZXAnidZqaZgSFsX5KsJg8JHHNUc8jk9dsxa1ik,1348 +rich/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +rich/region.py,sha256=rNT9xZrVZTYIXZC0NYn41CJQwYNbR-KecPOxTgQvB8Y,166 +rich/repr.py,sha256=mFexGTVledesZiMD3jBBBBoICoWh-XPFAUq3Hu7iX4U,4436 +rich/rule.py,sha256=umO21Wjw0FcYAeTB3UumNLCsDWhejzxnjlf2VwiXiDI,4590 +rich/scope.py,sha256=x-jkKIE-j7ZxA9I07lya5iVAuDAyUKM58HWtNZQUzRc,3239 +rich/screen.py,sha256=rL_j2wX-4SeuIOI2oOlc418QP9EAvD59GInUmEAE6jQ,1579 +rich/segment.py,sha256=17JAw4bmsV_LrJ8PnhI9XH2DaN-fOEPF-8mgx7joLDM,25740 +rich/spinner.py,sha256=onIhpKlljRHppTZasxO8kXgtYyCHUkpSgKglRJ3o51g,4214 +rich/status.py,sha256=kkPph3YeAZBo-X-4wPp8gTqZyU466NLwZBA4PZTTewo,4424 +rich/style.py,sha256=yp4TsNKNPUxBytoFWfy7kzE2Gh_9TYA7fs2ULXfbm00,27068 +rich/styled.py,sha256=wljVsVTXbABMMZvkzkO43ZEk_-irzEtvUiQ-sNnikQ8,1234 +rich/syntax.py,sha256=ytgeGUt91fdr8LaHKOdkaNMewrPRxgoci5mMd3MPE4I,36305 +rich/table.py,sha256=6yv7wMLXZgOsGgzEDpKXpecXQOwcLBHNOn-MYcbo1Zk,40033 +rich/terminal_theme.py,sha256=1j5-ufJfnvlAo5Qsi_ACZiXDmwMXzqgmFByObT9-yJY,3370 +rich/text.py,sha256=t2J8iMquwR0BUUggm-cVNp30Wxw5M8fIF_p0jioYRV0,47655 +rich/theme.py,sha256=biYZWd_Wu1KW8VtT7UsTQDrDU89RxAK5_Vz1y3ztGYI,3780 +rich/themes.py,sha256=0xgTLozfabebYtcJtDdC5QkX5IVUEaviqDUJJh4YVFk,102 +rich/traceback.py,sha256=TT03eLKEj5XapXFp9kgf0bBwNupIFrHquKxZtwBhz_8,37535 +rich/tree.py,sha256=QoOwg424FkdwGfR8K0tZ6Q7qtzWNAUP_m4sFaYuG6nw,9391 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/rich-15.0.0.dist-info/REQUESTED b/bioagent-bench/.venv/lib/python3.14/site-packages/rich-15.0.0.dist-info/REQUESTED new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/rich-15.0.0.dist-info/WHEEL b/bioagent-bench/.venv/lib/python3.14/site-packages/rich-15.0.0.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..7894e88612ce5ce2c8502e9eeee7ede5b88c9b9e --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/rich-15.0.0.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: poetry-core 2.3.1 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/rich-15.0.0.dist-info/licenses/LICENSE b/bioagent-bench/.venv/lib/python3.14/site-packages/rich-15.0.0.dist-info/licenses/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..4415505566f261c802b671426be529a31f914137 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/rich-15.0.0.dist-info/licenses/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020 Will McGugan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/rich/_emoji_replace.py b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/_emoji_replace.py new file mode 100644 index 0000000000000000000000000000000000000000..fdaff320dfcfa6c76ac18b3ee2e249b7712df805 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/_emoji_replace.py @@ -0,0 +1,31 @@ +import re +from typing import Callable, Match, Optional + +_ReStringMatch = Match[str] # regex match object +_ReSubCallable = Callable[[_ReStringMatch], str] # Callable invoked by re.sub +_EmojiSubMethod = Callable[[_ReSubCallable, str], str] # Sub method of a compiled re + + +def _emoji_replace( + text: str, + default_variant: Optional[str] = None, + _emoji_sub: _EmojiSubMethod = re.compile(r"(:(\S*?)(?:(?:\-)(emoji|text))?:)").sub, +) -> str: + """Replace emoji code in text.""" + from ._emoji_codes import EMOJI + + get_emoji = EMOJI.__getitem__ + variants = {"text": "\ufe0e", "emoji": "\ufe0f"} + get_variant = variants.get + default_variant_code = variants.get(default_variant, "") if default_variant else "" + + def do_replace(match: Match[str]) -> str: + emoji_code, emoji_name, variant = match.groups() + try: + return get_emoji(emoji_name.lower()) + get_variant( + variant, default_variant_code + ) + except KeyError: + return emoji_code + + return _emoji_sub(do_replace, text) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/rich/_export_format.py b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/_export_format.py new file mode 100644 index 0000000000000000000000000000000000000000..e7527e52f6613328630fc6305f957d9ea58027d8 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/_export_format.py @@ -0,0 +1,76 @@ +CONSOLE_HTML_FORMAT = """\ + + + + + + + +
    {code}
    + + +""" + +CONSOLE_SVG_FORMAT = """\ + + + + + + + + + {lines} + + + {chrome} + + {backgrounds} + + {matrix} + + + +""" + +_SVG_FONT_FAMILY = "Rich Fira Code" +_SVG_CLASSES_PREFIX = "rich-svg" diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/rich/_log_render.py b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/_log_render.py new file mode 100644 index 0000000000000000000000000000000000000000..e8810100b323450c63507e16629a09bb2e9dc97f --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/_log_render.py @@ -0,0 +1,94 @@ +from datetime import datetime +from typing import Iterable, List, Optional, TYPE_CHECKING, Union, Callable + + +from .text import Text, TextType + +if TYPE_CHECKING: + from .console import Console, ConsoleRenderable, RenderableType + from .table import Table + +FormatTimeCallable = Callable[[datetime], Text] + + +class LogRender: + def __init__( + self, + show_time: bool = True, + show_level: bool = False, + show_path: bool = True, + time_format: Union[str, FormatTimeCallable] = "[%x %X]", + omit_repeated_times: bool = True, + level_width: Optional[int] = 8, + ) -> None: + self.show_time = show_time + self.show_level = show_level + self.show_path = show_path + self.time_format = time_format + self.omit_repeated_times = omit_repeated_times + self.level_width = level_width + self._last_time: Optional[Text] = None + + def __call__( + self, + console: "Console", + renderables: Iterable["ConsoleRenderable"], + log_time: Optional[datetime] = None, + time_format: Optional[Union[str, FormatTimeCallable]] = None, + level: TextType = "", + path: Optional[str] = None, + line_no: Optional[int] = None, + link_path: Optional[str] = None, + ) -> "Table": + from .containers import Renderables + from .table import Table + + output = Table.grid(padding=(0, 1)) + output.expand = True + if self.show_time: + output.add_column(style="log.time") + if self.show_level: + output.add_column(style="log.level", width=self.level_width) + output.add_column(ratio=1, style="log.message", overflow="fold") + if self.show_path and path: + output.add_column(style="log.path") + row: List["RenderableType"] = [] + if self.show_time: + log_time = log_time or console.get_datetime() + time_format = time_format or self.time_format + if callable(time_format): + log_time_display = time_format(log_time) + else: + log_time_display = Text(log_time.strftime(time_format)) + if log_time_display == self._last_time and self.omit_repeated_times: + row.append(Text(" " * len(log_time_display))) + else: + row.append(log_time_display) + self._last_time = log_time_display + if self.show_level: + row.append(level) + + row.append(Renderables(renderables)) + if self.show_path and path: + path_text = Text() + path_text.append( + path, style=f"link file://{link_path}" if link_path else "" + ) + if line_no: + path_text.append(":") + path_text.append( + f"{line_no}", + style=f"link file://{link_path}#{line_no}" if link_path else "", + ) + row.append(path_text) + + output.add_row(*row) + return output + + +if __name__ == "__main__": # pragma: no cover + from rich.console import Console + + c = Console() + c.print("[on blue]Hello", justify="right") + c.log("[on blue]hello", justify="right") diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/rich/_null_file.py b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/_null_file.py new file mode 100644 index 0000000000000000000000000000000000000000..6ae05d3e2a901af754b1626d911ebc3c45e22a40 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/_null_file.py @@ -0,0 +1,69 @@ +from types import TracebackType +from typing import IO, Iterable, Iterator, List, Optional, Type + + +class NullFile(IO[str]): + def close(self) -> None: + pass + + def isatty(self) -> bool: + return False + + def read(self, __n: int = 1) -> str: + return "" + + def readable(self) -> bool: + return False + + def readline(self, __limit: int = 1) -> str: + return "" + + def readlines(self, __hint: int = 1) -> List[str]: + return [] + + def seek(self, __offset: int, __whence: int = 1) -> int: + return 0 + + def seekable(self) -> bool: + return False + + def tell(self) -> int: + return 0 + + def truncate(self, __size: Optional[int] = 1) -> int: + return 0 + + def writable(self) -> bool: + return False + + def writelines(self, __lines: Iterable[str]) -> None: + pass + + def __next__(self) -> str: + return "" + + def __iter__(self) -> Iterator[str]: + return iter([""]) + + def __enter__(self) -> IO[str]: + return self + + def __exit__( + self, + __t: Optional[Type[BaseException]], + __value: Optional[BaseException], + __traceback: Optional[TracebackType], + ) -> None: + pass + + def write(self, text: str) -> int: + return 0 + + def flush(self) -> None: + pass + + def fileno(self) -> int: + return -1 + + +NULL_FILE = NullFile() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/rich/_timer.py b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/_timer.py new file mode 100644 index 0000000000000000000000000000000000000000..a2ca6be03c43054caaa3660998273ebf704345dd --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/_timer.py @@ -0,0 +1,19 @@ +""" +Timer context manager, only used in debug. + +""" + +from time import time + +import contextlib +from typing import Generator + + +@contextlib.contextmanager +def timer(subject: str = "time") -> Generator[None, None, None]: + """print the elapsed time. (only used in debugging)""" + start = time() + yield + elapsed = time() - start + elapsed_ms = elapsed * 1000 + print(f"{subject} elapsed {elapsed_ms:.1f}ms") diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/rich/_windows_renderer.py b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/_windows_renderer.py new file mode 100644 index 0000000000000000000000000000000000000000..0fc2ba852a92a45ef510d27ca6ce5e5348bec8a1 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/_windows_renderer.py @@ -0,0 +1,56 @@ +from typing import Iterable, Sequence, Tuple, cast + +from rich._win32_console import LegacyWindowsTerm, WindowsCoordinates +from rich.segment import ControlCode, ControlType, Segment + + +def legacy_windows_render(buffer: Iterable[Segment], term: LegacyWindowsTerm) -> None: + """Makes appropriate Windows Console API calls based on the segments in the buffer. + + Args: + buffer (Iterable[Segment]): Iterable of Segments to convert to Win32 API calls. + term (LegacyWindowsTerm): Used to call the Windows Console API. + """ + for text, style, control in buffer: + if not control: + if style: + term.write_styled(text, style) + else: + term.write_text(text) + else: + control_codes: Sequence[ControlCode] = control + for control_code in control_codes: + control_type = control_code[0] + if control_type == ControlType.CURSOR_MOVE_TO: + _, x, y = cast(Tuple[ControlType, int, int], control_code) + term.move_cursor_to(WindowsCoordinates(row=y - 1, col=x - 1)) + elif control_type == ControlType.CARRIAGE_RETURN: + term.write_text("\r") + elif control_type == ControlType.HOME: + term.move_cursor_to(WindowsCoordinates(0, 0)) + elif control_type == ControlType.CURSOR_UP: + term.move_cursor_up() + elif control_type == ControlType.CURSOR_DOWN: + term.move_cursor_down() + elif control_type == ControlType.CURSOR_FORWARD: + term.move_cursor_forward() + elif control_type == ControlType.CURSOR_BACKWARD: + term.move_cursor_backward() + elif control_type == ControlType.CURSOR_MOVE_TO_COLUMN: + _, column = cast(Tuple[ControlType, int], control_code) + term.move_cursor_to_column(column - 1) + elif control_type == ControlType.HIDE_CURSOR: + term.hide_cursor() + elif control_type == ControlType.SHOW_CURSOR: + term.show_cursor() + elif control_type == ControlType.ERASE_IN_LINE: + _, mode = cast(Tuple[ControlType, int], control_code) + if mode == 0: + term.erase_end_of_line() + elif mode == 1: + term.erase_start_of_line() + elif mode == 2: + term.erase_line() + elif control_type == ControlType.SET_WINDOW_TITLE: + _, title = cast(Tuple[ControlType, str], control_code) + term.set_title(title) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/rich/bar.py b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/bar.py new file mode 100644 index 0000000000000000000000000000000000000000..022284b57881d8b133aced5b5a843e6447bb4e0b --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/bar.py @@ -0,0 +1,93 @@ +from typing import Optional, Union + +from .color import Color +from .console import Console, ConsoleOptions, RenderResult +from .jupyter import JupyterMixin +from .measure import Measurement +from .segment import Segment +from .style import Style + +# There are left-aligned characters for 1/8 to 7/8, but +# the right-aligned characters exist only for 1/8 and 4/8. +BEGIN_BLOCK_ELEMENTS = ["█", "█", "█", "▐", "▐", "▐", "▕", "▕"] +END_BLOCK_ELEMENTS = [" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉"] +FULL_BLOCK = "█" + + +class Bar(JupyterMixin): + """Renders a solid block bar. + + Args: + size (float): Value for the end of the bar. + begin (float): Begin point (between 0 and size, inclusive). + end (float): End point (between 0 and size, inclusive). + width (int, optional): Width of the bar, or ``None`` for maximum width. Defaults to None. + color (Union[Color, str], optional): Color of the bar. Defaults to "default". + bgcolor (Union[Color, str], optional): Color of bar background. Defaults to "default". + """ + + def __init__( + self, + size: float, + begin: float, + end: float, + *, + width: Optional[int] = None, + color: Union[Color, str] = "default", + bgcolor: Union[Color, str] = "default", + ): + self.size = size + self.begin = max(begin, 0) + self.end = min(end, size) + self.width = width + self.style = Style(color=color, bgcolor=bgcolor) + + def __repr__(self) -> str: + return f"Bar({self.size}, {self.begin}, {self.end})" + + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: + width = min( + self.width if self.width is not None else options.max_width, + options.max_width, + ) + + if self.begin >= self.end: + yield Segment(" " * width, self.style) + yield Segment.line() + return + + prefix_complete_eights = int(width * 8 * self.begin / self.size) + prefix_bar_count = prefix_complete_eights // 8 + prefix_eights_count = prefix_complete_eights % 8 + + body_complete_eights = int(width * 8 * self.end / self.size) + body_bar_count = body_complete_eights // 8 + body_eights_count = body_complete_eights % 8 + + # When start and end fall into the same cell, we ideally should render + # a symbol that's "center-aligned", but there is no good symbol in Unicode. + # In this case, we fall back to right-aligned block symbol for simplicity. + + prefix = " " * prefix_bar_count + if prefix_eights_count: + prefix += BEGIN_BLOCK_ELEMENTS[prefix_eights_count] + + body = FULL_BLOCK * body_bar_count + if body_eights_count: + body += END_BLOCK_ELEMENTS[body_eights_count] + + suffix = " " * (width - len(body)) + + yield Segment(prefix + body[len(prefix) :] + suffix, self.style) + yield Segment.line() + + def __rich_measure__( + self, console: Console, options: ConsoleOptions + ) -> Measurement: + return ( + Measurement(self.width, self.width) + if self.width is not None + else Measurement(4, options.max_width) + ) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/rich/box.py b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/box.py new file mode 100644 index 0000000000000000000000000000000000000000..82555b61cd29efab220cf47b9fb3c26b80e8adde --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/box.py @@ -0,0 +1,474 @@ +from typing import TYPE_CHECKING, Iterable, List, Literal + + +from ._loop import loop_last + +if TYPE_CHECKING: + from rich.console import ConsoleOptions + + +class Box: + """Defines characters to render boxes. + + ┌─┬┐ top + │ ││ head + ├─┼┤ head_row + │ ││ mid + ├─┼┤ row + ├─┼┤ foot_row + │ ││ foot + └─┴┘ bottom + + Args: + box (str): Characters making up box. + ascii (bool, optional): True if this box uses ascii characters only. Default is False. + """ + + def __init__(self, box: str, *, ascii: bool = False) -> None: + self._box = box + self.ascii = ascii + line1, line2, line3, line4, line5, line6, line7, line8 = box.splitlines() + # top + self.top_left, self.top, self.top_divider, self.top_right = iter(line1) + # head + self.head_left, _, self.head_vertical, self.head_right = iter(line2) + # head_row + ( + self.head_row_left, + self.head_row_horizontal, + self.head_row_cross, + self.head_row_right, + ) = iter(line3) + + # mid + self.mid_left, _, self.mid_vertical, self.mid_right = iter(line4) + # row + self.row_left, self.row_horizontal, self.row_cross, self.row_right = iter(line5) + # foot_row + ( + self.foot_row_left, + self.foot_row_horizontal, + self.foot_row_cross, + self.foot_row_right, + ) = iter(line6) + # foot + self.foot_left, _, self.foot_vertical, self.foot_right = iter(line7) + # bottom + self.bottom_left, self.bottom, self.bottom_divider, self.bottom_right = iter( + line8 + ) + + def __repr__(self) -> str: + return "Box(...)" + + def __str__(self) -> str: + return self._box + + def substitute(self, options: "ConsoleOptions", safe: bool = True) -> "Box": + """Substitute this box for another if it won't render due to platform issues. + + Args: + options (ConsoleOptions): Console options used in rendering. + safe (bool, optional): Substitute this for another Box if there are known problems + displaying on the platform (currently only relevant on Windows). Default is True. + + Returns: + Box: A different Box or the same Box. + """ + box = self + if options.legacy_windows and safe: + box = LEGACY_WINDOWS_SUBSTITUTIONS.get(box, box) + if options.ascii_only and not box.ascii: + box = ASCII + return box + + def get_plain_headed_box(self) -> "Box": + """If this box uses special characters for the borders of the header, then + return the equivalent box that does not. + + Returns: + Box: The most similar Box that doesn't use header-specific box characters. + If the current Box already satisfies this criterion, then it's returned. + """ + return PLAIN_HEADED_SUBSTITUTIONS.get(self, self) + + def get_top(self, widths: Iterable[int]) -> str: + """Get the top of a simple box. + + Args: + widths (List[int]): Widths of columns. + + Returns: + str: A string of box characters. + """ + + parts: List[str] = [] + append = parts.append + append(self.top_left) + for last, width in loop_last(widths): + append(self.top * width) + if not last: + append(self.top_divider) + append(self.top_right) + return "".join(parts) + + def get_row( + self, + widths: Iterable[int], + level: Literal["head", "row", "foot", "mid"] = "row", + edge: bool = True, + ) -> str: + """Get the top of a simple box. + + Args: + width (List[int]): Widths of columns. + + Returns: + str: A string of box characters. + """ + if level == "head": + left = self.head_row_left + horizontal = self.head_row_horizontal + cross = self.head_row_cross + right = self.head_row_right + elif level == "row": + left = self.row_left + horizontal = self.row_horizontal + cross = self.row_cross + right = self.row_right + elif level == "mid": + left = self.mid_left + horizontal = " " + cross = self.mid_vertical + right = self.mid_right + elif level == "foot": + left = self.foot_row_left + horizontal = self.foot_row_horizontal + cross = self.foot_row_cross + right = self.foot_row_right + else: + raise ValueError("level must be 'head', 'row' or 'foot'") + + parts: List[str] = [] + append = parts.append + if edge: + append(left) + for last, width in loop_last(widths): + append(horizontal * width) + if not last: + append(cross) + if edge: + append(right) + return "".join(parts) + + def get_bottom(self, widths: Iterable[int]) -> str: + """Get the bottom of a simple box. + + Args: + widths (List[int]): Widths of columns. + + Returns: + str: A string of box characters. + """ + + parts: List[str] = [] + append = parts.append + append(self.bottom_left) + for last, width in loop_last(widths): + append(self.bottom * width) + if not last: + append(self.bottom_divider) + append(self.bottom_right) + return "".join(parts) + + +# fmt: off +ASCII: Box = Box( + "+--+\n" + "| ||\n" + "|-+|\n" + "| ||\n" + "|-+|\n" + "|-+|\n" + "| ||\n" + "+--+\n", + ascii=True, +) + +ASCII2: Box = Box( + "+-++\n" + "| ||\n" + "+-++\n" + "| ||\n" + "+-++\n" + "+-++\n" + "| ||\n" + "+-++\n", + ascii=True, +) + +ASCII_DOUBLE_HEAD: Box = Box( + "+-++\n" + "| ||\n" + "+=++\n" + "| ||\n" + "+-++\n" + "+-++\n" + "| ||\n" + "+-++\n", + ascii=True, +) + +SQUARE: Box = Box( + "┌─┬┐\n" + "│ ││\n" + "├─┼┤\n" + "│ ││\n" + "├─┼┤\n" + "├─┼┤\n" + "│ ││\n" + "└─┴┘\n" +) + +SQUARE_DOUBLE_HEAD: Box = Box( + "┌─┬┐\n" + "│ ││\n" + "╞═╪╡\n" + "│ ││\n" + "├─┼┤\n" + "├─┼┤\n" + "│ ││\n" + "└─┴┘\n" +) + +MINIMAL: Box = Box( + " ╷ \n" + " │ \n" + "╶─┼╴\n" + " │ \n" + "╶─┼╴\n" + "╶─┼╴\n" + " │ \n" + " ╵ \n" +) + + +MINIMAL_HEAVY_HEAD: Box = Box( + " ╷ \n" + " │ \n" + "╺━┿╸\n" + " │ \n" + "╶─┼╴\n" + "╶─┼╴\n" + " │ \n" + " ╵ \n" +) + +MINIMAL_DOUBLE_HEAD: Box = Box( + " ╷ \n" + " │ \n" + " ═╪ \n" + " │ \n" + " ─┼ \n" + " ─┼ \n" + " │ \n" + " ╵ \n" +) + + +SIMPLE: Box = Box( + " \n" + " \n" + " ── \n" + " \n" + " \n" + " ── \n" + " \n" + " \n" +) + +SIMPLE_HEAD: Box = Box( + " \n" + " \n" + " ── \n" + " \n" + " \n" + " \n" + " \n" + " \n" +) + + +SIMPLE_HEAVY: Box = Box( + " \n" + " \n" + " ━━ \n" + " \n" + " \n" + " ━━ \n" + " \n" + " \n" +) + + +HORIZONTALS: Box = Box( + " ── \n" + " \n" + " ── \n" + " \n" + " ── \n" + " ── \n" + " \n" + " ── \n" +) + +ROUNDED: Box = Box( + "╭─┬╮\n" + "│ ││\n" + "├─┼┤\n" + "│ ││\n" + "├─┼┤\n" + "├─┼┤\n" + "│ ││\n" + "╰─┴╯\n" +) + +HEAVY: Box = Box( + "┏━┳┓\n" + "┃ ┃┃\n" + "┣━╋┫\n" + "┃ ┃┃\n" + "┣━╋┫\n" + "┣━╋┫\n" + "┃ ┃┃\n" + "┗━┻┛\n" +) + +HEAVY_EDGE: Box = Box( + "┏━┯┓\n" + "┃ │┃\n" + "┠─┼┨\n" + "┃ │┃\n" + "┠─┼┨\n" + "┠─┼┨\n" + "┃ │┃\n" + "┗━┷┛\n" +) + +HEAVY_HEAD: Box = Box( + "┏━┳┓\n" + "┃ ┃┃\n" + "┡━╇┩\n" + "│ ││\n" + "├─┼┤\n" + "├─┼┤\n" + "│ ││\n" + "└─┴┘\n" +) + +DOUBLE: Box = Box( + "╔═╦╗\n" + "║ ║║\n" + "╠═╬╣\n" + "║ ║║\n" + "╠═╬╣\n" + "╠═╬╣\n" + "║ ║║\n" + "╚═╩╝\n" +) + +DOUBLE_EDGE: Box = Box( + "╔═╤╗\n" + "║ │║\n" + "╟─┼╢\n" + "║ │║\n" + "╟─┼╢\n" + "╟─┼╢\n" + "║ │║\n" + "╚═╧╝\n" +) + +MARKDOWN: Box = Box( + " \n" + "| ||\n" + "|-||\n" + "| ||\n" + "|-||\n" + "|-||\n" + "| ||\n" + " \n", + ascii=True, +) +# fmt: on + +# Map Boxes that don't render with raster fonts on to equivalent that do +LEGACY_WINDOWS_SUBSTITUTIONS = { + ROUNDED: SQUARE, + MINIMAL_HEAVY_HEAD: MINIMAL, + SIMPLE_HEAVY: SIMPLE, + HEAVY: SQUARE, + HEAVY_EDGE: SQUARE, + HEAVY_HEAD: SQUARE, +} + +# Map headed boxes to their headerless equivalents +PLAIN_HEADED_SUBSTITUTIONS = { + HEAVY_HEAD: SQUARE, + SQUARE_DOUBLE_HEAD: SQUARE, + MINIMAL_DOUBLE_HEAD: MINIMAL, + MINIMAL_HEAVY_HEAD: MINIMAL, + ASCII_DOUBLE_HEAD: ASCII2, +} + + +if __name__ == "__main__": # pragma: no cover + from rich.columns import Columns + from rich.panel import Panel + + from . import box as box + from .console import Console + from .table import Table + from .text import Text + + console = Console(record=True) + + BOXES = [ + "ASCII", + "ASCII2", + "ASCII_DOUBLE_HEAD", + "SQUARE", + "SQUARE_DOUBLE_HEAD", + "MINIMAL", + "MINIMAL_HEAVY_HEAD", + "MINIMAL_DOUBLE_HEAD", + "SIMPLE", + "SIMPLE_HEAD", + "SIMPLE_HEAVY", + "HORIZONTALS", + "ROUNDED", + "HEAVY", + "HEAVY_EDGE", + "HEAVY_HEAD", + "DOUBLE", + "DOUBLE_EDGE", + "MARKDOWN", + ] + + console.print(Panel("[bold green]Box Constants", style="green"), justify="center") + console.print() + + columns = Columns(expand=True, padding=2) + for box_name in sorted(BOXES): + table = Table( + show_footer=True, style="dim", border_style="not dim", expand=True + ) + table.add_column("Header 1", "Footer 1") + table.add_column("Header 2", "Footer 2") + table.add_row("Cell", "Cell") + table.add_row("Cell", "Cell") + table.box = getattr(box, box_name) + table.title = Text(f"box.{box_name}", style="magenta") + columns.add_renderable(table) + console.print(columns) + + # console.save_svg("box.svg") diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/rich/diagnose.py b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/diagnose.py new file mode 100644 index 0000000000000000000000000000000000000000..9d5ff3ec3299994c61171859854c16242468ed8b --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/diagnose.py @@ -0,0 +1,39 @@ +import os +import platform + +from rich import inspect +from rich.console import Console, get_windows_console_features +from rich.panel import Panel +from rich.pretty import Pretty + + +def report() -> None: # pragma: no cover + """Print a report to the terminal with debugging information""" + console = Console() + inspect(console) + features = get_windows_console_features() + inspect(features) + + env_names = ( + "CLICOLOR", + "COLORTERM", + "COLUMNS", + "JPY_PARENT_PID", + "JUPYTER_COLUMNS", + "JUPYTER_LINES", + "LINES", + "NO_COLOR", + "TERM_PROGRAM", + "TERM", + "TTY_COMPATIBLE", + "TTY_INTERACTIVE", + "VSCODE_VERBOSE_LOGGING", + ) + env = {name: os.getenv(name) for name in env_names} + console.print(Panel.fit((Pretty(env)), title="[b]Environment Variables")) + + console.print(f'platform="{platform.system()}"') + + +if __name__ == "__main__": # pragma: no cover + report() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/rich/emoji.py b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/emoji.py new file mode 100644 index 0000000000000000000000000000000000000000..067f93ce3ae1ceb8c5db9d54c0421728a6ea216c --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/emoji.py @@ -0,0 +1,93 @@ +import sys +from typing import TYPE_CHECKING, Literal, Optional, Union + +from ._emoji_replace import _emoji_replace +from .jupyter import JupyterMixin +from .segment import Segment +from .style import Style + +if TYPE_CHECKING: + from .console import Console, ConsoleOptions, RenderResult + + +EmojiVariant = Literal["emoji", "text"] + + +class NoEmoji(Exception): + """No emoji by that name.""" + + +class Emoji(JupyterMixin): + __slots__ = ["name", "style", "_char", "variant"] + + VARIANTS = {"text": "\ufe0e", "emoji": "\ufe0f"} + + def __init__( + self, + name: str, + style: Union[str, Style] = "none", + variant: Optional[EmojiVariant] = None, + ) -> None: + """A single emoji character. + + Args: + name (str): Name of emoji. + style (Union[str, Style], optional): Optional style. Defaults to None. + + Raises: + NoEmoji: If the emoji doesn't exist. + """ + from ._emoji_codes import EMOJI + + self.name = name + self.style = style + self.variant = variant + try: + self._char = EMOJI[name] + except KeyError: + raise NoEmoji(f"No emoji called {name!r}") + if variant is not None: + self._char += self.VARIANTS.get(variant, "") + + @classmethod + def replace(cls, text: str) -> str: + """Replace emoji markup with corresponding unicode characters. + + Args: + text (str): A string with emojis codes, e.g. "Hello :smiley:!" + + Returns: + str: A string with emoji codes replaces with actual emoji. + """ + return _emoji_replace(text) + + def __repr__(self) -> str: + return f"" + + def __str__(self) -> str: + return self._char + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + yield Segment(self._char, console.get_style(self.style)) + + +if __name__ == "__main__": # pragma: no cover + import sys + + from rich.columns import Columns + from rich.console import Console + + console = Console(record=True) + + from ._emoji_codes import EMOJI + + columns = Columns( + (f":{name}: {name}" for name in sorted(EMOJI.keys()) if "\u200d" not in name), + column_first=True, + ) + + console.print(columns) + if len(sys.argv) > 1: + console.save_html(sys.argv[1]) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/rich/errors.py b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/errors.py new file mode 100644 index 0000000000000000000000000000000000000000..0bcbe53ef59373c608e62ea285536f8b22b47ecb --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/errors.py @@ -0,0 +1,34 @@ +class ConsoleError(Exception): + """An error in console operation.""" + + +class StyleError(Exception): + """An error in styles.""" + + +class StyleSyntaxError(ConsoleError): + """Style was badly formatted.""" + + +class MissingStyle(StyleError): + """No such style.""" + + +class StyleStackError(ConsoleError): + """Style stack is invalid.""" + + +class NotRenderableError(ConsoleError): + """Object is not renderable.""" + + +class MarkupError(ConsoleError): + """Markup was badly formatted.""" + + +class LiveError(ConsoleError): + """Error related to Live display.""" + + +class NoAltScreen(ConsoleError): + """Alt screen mode was required.""" diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/rich/live.py b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/live.py new file mode 100644 index 0000000000000000000000000000000000000000..2fd893b0086ae8fe755cb00c20edb2aac1d322ea --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/live.py @@ -0,0 +1,404 @@ +from __future__ import annotations + +import sys +from threading import Event, RLock, Thread +from types import TracebackType +from typing import IO, TYPE_CHECKING, Any, Callable, List, Optional, TextIO, Type, cast + +from . import get_console +from .console import Console, ConsoleRenderable, Group, RenderableType, RenderHook +from .control import Control +from .file_proxy import FileProxy +from .jupyter import JupyterMixin +from .live_render import LiveRender, VerticalOverflowMethod +from .screen import Screen +from .text import Text + +if TYPE_CHECKING: + # Can be replaced with `from typing import Self` in Python 3.11+ + from typing_extensions import Self # pragma: no cover + + +class _RefreshThread(Thread): + """A thread that calls refresh() at regular intervals.""" + + def __init__(self, live: "Live", refresh_per_second: float) -> None: + self.live = live + self.refresh_per_second = refresh_per_second + self.done = Event() + super().__init__(daemon=True) + + def stop(self) -> None: + self.done.set() + + def run(self) -> None: + while not self.done.wait(1 / self.refresh_per_second): + with self.live._lock: + if not self.done.is_set(): + self.live.refresh() + + +class Live(JupyterMixin, RenderHook): + """Renders an auto-updating live display of any given renderable. + + Args: + renderable (RenderableType, optional): The renderable to live display. Defaults to displaying nothing. + console (Console, optional): Optional Console instance. Defaults to an internal Console instance writing to stdout. + screen (bool, optional): Enable alternate screen mode. Defaults to False. + auto_refresh (bool, optional): Enable auto refresh. If disabled, you will need to call `refresh()` or `update()` with refresh flag. Defaults to True + refresh_per_second (float, optional): Number of times per second to refresh the live display. Defaults to 4. + transient (bool, optional): Clear the renderable on exit (has no effect when screen=True). Defaults to False. + redirect_stdout (bool, optional): Enable redirection of stdout, so ``print`` may be used. Defaults to True. + redirect_stderr (bool, optional): Enable redirection of stderr. Defaults to True. + vertical_overflow (VerticalOverflowMethod, optional): How to handle renderable when it is too tall for the console. Defaults to "ellipsis". + get_renderable (Callable[[], RenderableType], optional): Optional callable to get renderable. Defaults to None. + """ + + def __init__( + self, + renderable: Optional[RenderableType] = None, + *, + console: Optional[Console] = None, + screen: bool = False, + auto_refresh: bool = True, + refresh_per_second: float = 4, + transient: bool = False, + redirect_stdout: bool = True, + redirect_stderr: bool = True, + vertical_overflow: VerticalOverflowMethod = "ellipsis", + get_renderable: Optional[Callable[[], RenderableType]] = None, + ) -> None: + assert refresh_per_second > 0, "refresh_per_second must be > 0" + self._renderable = renderable + self.console = console if console is not None else get_console() + self._screen = screen + self._alt_screen = False + + self._redirect_stdout = redirect_stdout + self._redirect_stderr = redirect_stderr + self._restore_stdout: Optional[IO[str]] = None + self._restore_stderr: Optional[IO[str]] = None + + self._lock = RLock() + self.ipy_widget: Optional[Any] = None + self.auto_refresh = auto_refresh + self._started: bool = False + self.transient = True if screen else transient + + self._refresh_thread: Optional[_RefreshThread] = None + self.refresh_per_second = refresh_per_second + + self.vertical_overflow = vertical_overflow + self._get_renderable = get_renderable + self._live_render = LiveRender( + self.get_renderable(), vertical_overflow=vertical_overflow + ) + self._nested = False + + @property + def is_started(self) -> bool: + """Check if live display has been started.""" + return self._started + + def get_renderable(self) -> RenderableType: + renderable = ( + self._get_renderable() + if self._get_renderable is not None + else self._renderable + ) + return renderable or "" + + def start(self, refresh: bool = False) -> None: + """Start live rendering display. + + Args: + refresh (bool, optional): Also refresh. Defaults to False. + """ + with self._lock: + if self._started: + return + self._started = True + + if not self.console.set_live(self): + self._nested = True + return + + if self._screen: + self._alt_screen = self.console.set_alt_screen(True) + self.console.show_cursor(False) + self._enable_redirect_io() + self.console.push_render_hook(self) + if refresh: + try: + self.refresh() + except Exception: + # If refresh fails, we want to stop the redirection of sys.stderr, + # so the error stacktrace is properly displayed in the terminal. + # (or, if the code that calls Rich captures the exception and wants to display something, + # let this be displayed in the terminal). + self.stop() + raise + if self.auto_refresh: + self._refresh_thread = _RefreshThread(self, self.refresh_per_second) + self._refresh_thread.start() + + def stop(self) -> None: + """Stop live rendering display.""" + with self._lock: + if not self._started: + return + self._started = False + self.console.clear_live() + if self._nested: + if not self.transient: + self.console.print(self.renderable) + return + + if self.auto_refresh and self._refresh_thread is not None: + self._refresh_thread.stop() + self._refresh_thread = None + # allow it to fully render on the last even if overflow + self.vertical_overflow = "visible" + with self.console: + try: + if not self._alt_screen and not self.console.is_jupyter: + self.refresh() + finally: + self._disable_redirect_io() + self.console.pop_render_hook() + if ( + not self._alt_screen + and self.console.is_terminal + and self._live_render.last_render_height + ): + self.console.line() + self.console.show_cursor(True) + if self._alt_screen: + self.console.set_alt_screen(False) + if self.transient and not self._alt_screen: + self.console.control(self._live_render.restore_cursor()) + if self.ipy_widget is not None and self.transient: + self.ipy_widget.close() # pragma: no cover + + def __enter__(self) -> Self: + self.start(refresh=self._renderable is not None) + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + self.stop() + + def _enable_redirect_io(self) -> None: + """Enable redirecting of stdout / stderr.""" + if self.console.is_terminal or self.console.is_jupyter: + if self._redirect_stdout and not isinstance(sys.stdout, FileProxy): + self._restore_stdout = sys.stdout + sys.stdout = cast("TextIO", FileProxy(self.console, sys.stdout)) + if self._redirect_stderr and not isinstance(sys.stderr, FileProxy): + self._restore_stderr = sys.stderr + sys.stderr = cast("TextIO", FileProxy(self.console, sys.stderr)) + + def _disable_redirect_io(self) -> None: + """Disable redirecting of stdout / stderr.""" + if self._restore_stdout: + sys.stdout = cast("TextIO", self._restore_stdout) + self._restore_stdout = None + if self._restore_stderr: + sys.stderr = cast("TextIO", self._restore_stderr) + self._restore_stderr = None + + @property + def renderable(self) -> RenderableType: + """Get the renderable that is being displayed + + Returns: + RenderableType: Displayed renderable. + """ + live_stack = self.console._live_stack + renderable: RenderableType + if live_stack and self is live_stack[0]: + # The first Live instance will render everything in the Live stack + renderable = Group(*[live.get_renderable() for live in live_stack]) + else: + renderable = self.get_renderable() + return Screen(renderable) if self._alt_screen else renderable + + def update(self, renderable: RenderableType, *, refresh: bool = False) -> None: + """Update the renderable that is being displayed + + Args: + renderable (RenderableType): New renderable to use. + refresh (bool, optional): Refresh the display. Defaults to False. + """ + if isinstance(renderable, str): + renderable = self.console.render_str(renderable) + with self._lock: + self._renderable = renderable + if refresh: + self.refresh() + + def refresh(self) -> None: + """Update the display of the Live Render.""" + with self._lock: + self._live_render.set_renderable(self.renderable) + if self._nested: + if self.console._live_stack: + self.console._live_stack[0].refresh() + return + + if self.console.is_jupyter: # pragma: no cover + try: + from IPython.display import display + from ipywidgets import Output + except ImportError: + import warnings + + warnings.warn('install "ipywidgets" for Jupyter support') + else: + if self.ipy_widget is None: + self.ipy_widget = Output() + display(self.ipy_widget) + + with self.ipy_widget: + self.ipy_widget.clear_output(wait=True) + self.console.print(self._live_render.renderable) + elif self.console.is_terminal and not self.console.is_dumb_terminal: + with self.console: + self.console.print(Control()) + elif ( + not self._started and not self.transient + ): # if it is finished allow files or dumb-terminals to see final result + with self.console: + self.console.print(Control()) + + def process_renderables( + self, renderables: List[ConsoleRenderable] + ) -> List[ConsoleRenderable]: + """Process renderables to restore cursor and display progress.""" + self._live_render.vertical_overflow = self.vertical_overflow + if self.console.is_interactive: + # lock needs acquiring as user can modify live_render renderable at any time unlike in Progress. + with self._lock: + reset = ( + Control.home() + if self._alt_screen + else self._live_render.position_cursor() + ) + renderables = [reset, *renderables, self._live_render] + elif ( + not self._started and not self.transient + ): # if it is finished render the final output for files or dumb_terminals + renderables = [*renderables, self._live_render] + + return renderables + + +if __name__ == "__main__": # pragma: no cover + import random + import time + from itertools import cycle + from typing import Dict, List, Tuple + + from .align import Align + from .console import Console + from .live import Live as Live + from .panel import Panel + from .rule import Rule + from .syntax import Syntax + from .table import Table + + console = Console() + + syntax = Syntax( + '''def loop_last(values: Iterable[T]) -> Iterable[Tuple[bool, T]]: + """Iterate and generate a tuple with a flag for last value.""" + iter_values = iter(values) + try: + previous_value = next(iter_values) + except StopIteration: + return + for value in iter_values: + yield False, previous_value + previous_value = value + yield True, previous_value''', + "python", + line_numbers=True, + ) + + table = Table("foo", "bar", "baz") + table.add_row("1", "2", "3") + + progress_renderables = [ + "You can make the terminal shorter and taller to see the live table hide" + "Text may be printed while the progress bars are rendering.", + Panel("In fact, [i]any[/i] renderable will work"), + "Such as [magenta]tables[/]...", + table, + "Pretty printed structures...", + {"type": "example", "text": "Pretty printed"}, + "Syntax...", + syntax, + Rule("Give it a try!"), + ] + + examples = cycle(progress_renderables) + + exchanges = [ + "SGD", + "MYR", + "EUR", + "USD", + "AUD", + "JPY", + "CNH", + "HKD", + "CAD", + "INR", + "DKK", + "GBP", + "RUB", + "NZD", + "MXN", + "IDR", + "TWD", + "THB", + "VND", + ] + with Live(console=console) as live_table: + exchange_rate_dict: Dict[Tuple[str, str], float] = {} + + for index in range(100): + select_exchange = exchanges[index % len(exchanges)] + + for exchange in exchanges: + if exchange == select_exchange: + continue + time.sleep(0.4) + if random.randint(0, 10) < 1: + console.log(next(examples)) + exchange_rate_dict[(select_exchange, exchange)] = 200 / ( + (random.random() * 320) + 1 + ) + if len(exchange_rate_dict) > len(exchanges) - 1: + exchange_rate_dict.pop(list(exchange_rate_dict.keys())[0]) + table = Table(title="Exchange Rates") + + table.add_column("Source Currency") + table.add_column("Destination Currency") + table.add_column("Exchange Rate") + + for (source, dest), exchange_rate in exchange_rate_dict.items(): + table.add_row( + source, + dest, + Text( + f"{exchange_rate:.4f}", + style="red" if exchange_rate < 1.0 else "green", + ), + ) + + live_table.update(Align.center(table)) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/rich/live_render.py b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/live_render.py new file mode 100644 index 0000000000000000000000000000000000000000..e7ec970a7812919e192d0e8c43fe625ffc30af6e --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/live_render.py @@ -0,0 +1,116 @@ +from typing import Literal, Optional, Tuple + +from ._loop import loop_last +from .console import Console, ConsoleOptions, RenderableType, RenderResult +from .control import Control +from .segment import ControlType, Segment +from .style import StyleType +from .text import Text + +VerticalOverflowMethod = Literal["crop", "ellipsis", "visible"] + + +class LiveRender: + """Creates a renderable that may be updated. + + Args: + renderable (RenderableType): Any renderable object. + style (StyleType, optional): An optional style to apply to the renderable. Defaults to "". + """ + + def __init__( + self, + renderable: RenderableType, + style: StyleType = "", + vertical_overflow: VerticalOverflowMethod = "ellipsis", + ) -> None: + self.renderable = renderable + self.style = style + self.vertical_overflow = vertical_overflow + self._shape: Optional[Tuple[int, int]] = None + + @property + def last_render_height(self) -> int: + """The number of lines in the last render (may be 0 if nothing was rendered). + + Returns: + Height in lines + """ + if self._shape is None: + return 0 + return self._shape[1] + + def set_renderable(self, renderable: RenderableType) -> None: + """Set a new renderable. + + Args: + renderable (RenderableType): Any renderable object, including str. + """ + self.renderable = renderable + + def position_cursor(self) -> Control: + """Get control codes to move cursor to beginning of live render. + + Returns: + Control: A control instance that may be printed. + """ + if self._shape is not None: + _, height = self._shape + return Control( + ControlType.CARRIAGE_RETURN, + (ControlType.ERASE_IN_LINE, 2), + *( + ( + (ControlType.CURSOR_UP, 1), + (ControlType.ERASE_IN_LINE, 2), + ) + * (height - 1) + ) + ) + return Control() + + def restore_cursor(self) -> Control: + """Get control codes to clear the render and restore the cursor to its previous position. + + Returns: + Control: A Control instance that may be printed. + """ + if self._shape is not None: + _, height = self._shape + return Control( + ControlType.CARRIAGE_RETURN, + *((ControlType.CURSOR_UP, 1), (ControlType.ERASE_IN_LINE, 2)) * height + ) + return Control() + + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: + renderable = self.renderable + style = console.get_style(self.style) + lines = console.render_lines(renderable, options, style=style, pad=False) + shape = Segment.get_shape(lines) + + _, height = shape + if height > options.size.height: + if self.vertical_overflow == "crop": + lines = lines[: options.size.height] + shape = Segment.get_shape(lines) + elif self.vertical_overflow == "ellipsis": + lines = lines[: (options.size.height - 1)] + overflow_text = Text( + "...", + overflow="crop", + justify="center", + end="", + style="live.ellipsis", + ) + lines.append(list(console.render(overflow_text))) + shape = Segment.get_shape(lines) + self._shape = shape + + new_line = Segment.line() + for last, line in loop_last(lines): + yield from line + if not last: + yield new_line diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/rich/markdown.py b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/markdown.py new file mode 100644 index 0000000000000000000000000000000000000000..abbbb07d64e8d6bb16a809218a9d1f6480c0e4f5 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/markdown.py @@ -0,0 +1,802 @@ +from __future__ import annotations + +import sys +from dataclasses import dataclass +from typing import ClassVar, Iterable, get_args + +from markdown_it import MarkdownIt +from markdown_it.token import Token + +from rich.table import Table + +from . import box +from ._loop import loop_first +from ._stack import Stack +from .console import Console, ConsoleOptions, JustifyMethod, RenderResult +from .containers import Renderables +from .jupyter import JupyterMixin +from .rule import Rule +from .segment import Segment +from .style import Style, StyleStack +from .syntax import Syntax +from .text import Text, TextType + + +class MarkdownElement: + new_line: ClassVar[bool] = True + + @classmethod + def create(cls, markdown: Markdown, token: Token) -> MarkdownElement: + """Factory to create markdown element, + + Args: + markdown (Markdown): The parent Markdown object. + token (Token): A node from markdown-it. + + Returns: + MarkdownElement: A new markdown element + """ + return cls() + + def on_enter(self, context: MarkdownContext) -> None: + """Called when the node is entered. + + Args: + context (MarkdownContext): The markdown context. + """ + + def on_text(self, context: MarkdownContext, text: TextType) -> None: + """Called when text is parsed. + + Args: + context (MarkdownContext): The markdown context. + """ + + def on_leave(self, context: MarkdownContext) -> None: + """Called when the parser leaves the element. + + Args: + context (MarkdownContext): [description] + """ + + def on_child_close(self, context: MarkdownContext, child: MarkdownElement) -> bool: + """Called when a child element is closed. + + This method allows a parent element to take over rendering of its children. + + Args: + context (MarkdownContext): The markdown context. + child (MarkdownElement): The child markdown element. + + Returns: + bool: Return True to render the element, or False to not render the element. + """ + return True + + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: + return () + + +class UnknownElement(MarkdownElement): + """An unknown element. + + Hopefully there will be no unknown elements, and we will have a MarkdownElement for + everything in the document. + + """ + + +class TextElement(MarkdownElement): + """Base class for elements that render text.""" + + style_name = "none" + + def on_enter(self, context: MarkdownContext) -> None: + self.style = context.enter_style(self.style_name) + self.text = Text(justify="left") + + def on_text(self, context: MarkdownContext, text: TextType) -> None: + self.text.append(text, context.current_style if isinstance(text, str) else None) + + def on_leave(self, context: MarkdownContext) -> None: + context.leave_style() + + +class Paragraph(TextElement): + """A Paragraph.""" + + style_name = "markdown.paragraph" + justify: JustifyMethod + + @classmethod + def create(cls, markdown: Markdown, token: Token) -> Paragraph: + return cls(justify=markdown.justify or "left") + + def __init__(self, justify: JustifyMethod) -> None: + self.justify = justify + + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: + self.text.justify = self.justify + yield self.text + + +@dataclass +class HeadingFormat: + justify: JustifyMethod = "left" + style: str = "" + + +class Heading(TextElement): + """A heading.""" + + LEVEL_ALIGN: ClassVar[dict[str, JustifyMethod]] = { + "h1": "center", + "h2": "left", + "h3": "left", + "h4": "left", + "h5": "left", + "h6": "left", + } + + @classmethod + def create(cls, markdown: Markdown, token: Token) -> Heading: + return cls(token.tag) + + def on_enter(self, context: MarkdownContext) -> None: + self.text = Text() + context.enter_style(self.style_name) + + def __init__(self, tag: str) -> None: + self.tag = tag + self.style_name = f"markdown.{tag}" + super().__init__() + + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: + text = self.text.copy() + heading_justify = self.LEVEL_ALIGN.get(self.tag, "left") + text.justify = heading_justify + yield text + + +class CodeBlock(TextElement): + """A code block with syntax highlighting.""" + + style_name = "markdown.code_block" + + @classmethod + def create(cls, markdown: Markdown, token: Token) -> CodeBlock: + node_info = token.info or "" + lexer_name = node_info.partition(" ")[0] + return cls(lexer_name or "text", markdown.code_theme) + + def __init__(self, lexer_name: str, theme: str) -> None: + self.lexer_name = lexer_name + self.theme = theme + + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: + code = str(self.text).rstrip() + syntax = Syntax( + code, self.lexer_name, theme=self.theme, word_wrap=True, padding=1 + ) + yield syntax + + +class BlockQuote(TextElement): + """A block quote.""" + + style_name = "markdown.block_quote" + + def __init__(self) -> None: + self.elements: Renderables = Renderables() + + def on_child_close(self, context: MarkdownContext, child: MarkdownElement) -> bool: + self.elements.append(child) + return False + + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: + render_options = options.update(width=options.max_width - 4) + lines = console.render_lines(self.elements, render_options, style=self.style) + style = self.style + new_line = Segment("\n") + padding = Segment("▌ ", style) + for line in lines: + yield padding + yield from line + yield new_line + + +class HorizontalRule(MarkdownElement): + """A horizontal rule to divide sections.""" + + new_line = False + + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: + style = console.get_style("markdown.hr", default="none") + yield Rule(style=style, characters="-") + yield Text() + + +class TableElement(MarkdownElement): + """MarkdownElement corresponding to `table_open`.""" + + def __init__(self) -> None: + self.header: TableHeaderElement | None = None + self.body: TableBodyElement | None = None + + def on_child_close(self, context: MarkdownContext, child: MarkdownElement) -> bool: + if isinstance(child, TableHeaderElement): + self.header = child + elif isinstance(child, TableBodyElement): + self.body = child + else: + raise RuntimeError("Couldn't process markdown table.") + return False + + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: + table = Table( + box=box.SIMPLE, + pad_edge=False, + style="markdown.table.border", + show_edge=True, + collapse_padding=True, + ) + + if self.header is not None and self.header.row is not None: + for column in self.header.row.cells: + heading = column.content.copy() + heading.stylize("markdown.table.header") + table.add_column(heading) + + if self.body is not None: + for row in self.body.rows: + row_content = [element.content for element in row.cells] + table.add_row(*row_content) + + yield table + + +class TableHeaderElement(MarkdownElement): + """MarkdownElement corresponding to `thead_open` and `thead_close`.""" + + def __init__(self) -> None: + self.row: TableRowElement | None = None + + def on_child_close(self, context: MarkdownContext, child: MarkdownElement) -> bool: + assert isinstance(child, TableRowElement) + self.row = child + return False + + +class TableBodyElement(MarkdownElement): + """MarkdownElement corresponding to `tbody_open` and `tbody_close`.""" + + def __init__(self) -> None: + self.rows: list[TableRowElement] = [] + + def on_child_close(self, context: MarkdownContext, child: MarkdownElement) -> bool: + assert isinstance(child, TableRowElement) + self.rows.append(child) + return False + + +class TableRowElement(MarkdownElement): + """MarkdownElement corresponding to `tr_open` and `tr_close`.""" + + def __init__(self) -> None: + self.cells: list[TableDataElement] = [] + + def on_child_close(self, context: MarkdownContext, child: MarkdownElement) -> bool: + assert isinstance(child, TableDataElement) + self.cells.append(child) + return False + + +class TableDataElement(MarkdownElement): + """MarkdownElement corresponding to `td_open` and `td_close` + and `th_open` and `th_close`.""" + + @classmethod + def create(cls, markdown: Markdown, token: Token) -> MarkdownElement: + style = str(token.attrs.get("style")) or "" + + justify: JustifyMethod + if "text-align:right" in style: + justify = "right" + elif "text-align:center" in style: + justify = "center" + elif "text-align:left" in style: + justify = "left" + else: + justify = "default" + + assert justify in get_args(JustifyMethod) + return cls(justify=justify) + + def __init__(self, justify: JustifyMethod) -> None: + self.content: Text = Text("", justify=justify) + self.justify = justify + + def on_text(self, context: MarkdownContext, text: TextType) -> None: + if isinstance(text, str): + self.content.append(text, context.current_style) + else: + self.content.append_text(text) + + +class ListElement(MarkdownElement): + """A list element.""" + + @classmethod + def create(cls, markdown: Markdown, token: Token) -> ListElement: + return cls(token.type, int(token.attrs.get("start", 1))) + + def __init__(self, list_type: str, list_start: int | None) -> None: + self.items: list[ListItem] = [] + self.list_type = list_type + self.list_start = list_start + + def on_child_close(self, context: MarkdownContext, child: MarkdownElement) -> bool: + assert isinstance(child, ListItem) + self.items.append(child) + return False + + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: + if self.list_type == "bullet_list_open": + for item in self.items: + yield from item.render_bullet(console, options) + else: + number = 1 if self.list_start is None else self.list_start + last_number = number + len(self.items) + for index, item in enumerate(self.items): + yield from item.render_number( + console, options, number + index, last_number + ) + + +class ListItem(TextElement): + """An item in a list.""" + + style_name = "markdown.item" + + def __init__(self) -> None: + self.elements: Renderables = Renderables() + + def on_child_close(self, context: MarkdownContext, child: MarkdownElement) -> bool: + self.elements.append(child) + return False + + def render_bullet(self, console: Console, options: ConsoleOptions) -> RenderResult: + render_options = options.update(width=options.max_width - 3) + lines = console.render_lines(self.elements, render_options, style=self.style) + bullet_style = console.get_style("markdown.item.bullet", default="none") + + bullet = Segment(" • ", bullet_style) + padding = Segment(" " * 3, bullet_style) + new_line = Segment("\n") + for first, line in loop_first(lines): + yield bullet if first else padding + yield from line + yield new_line + + def render_number( + self, console: Console, options: ConsoleOptions, number: int, last_number: int + ) -> RenderResult: + number_width = len(str(last_number)) + 2 + render_options = options.update(width=options.max_width - number_width) + lines = console.render_lines(self.elements, render_options, style=self.style) + number_style = console.get_style("markdown.item.number", default="none") + + new_line = Segment("\n") + padding = Segment(" " * number_width, number_style) + numeral = Segment(f"{number}".rjust(number_width - 1) + " ", number_style) + for first, line in loop_first(lines): + yield numeral if first else padding + yield from line + yield new_line + + +class Link(TextElement): + @classmethod + def create(cls, markdown: Markdown, token: Token) -> MarkdownElement: + url = token.attrs.get("href", "#") + return cls(token.content, str(url)) + + def __init__(self, text: str, href: str): + self.text = Text(text) + self.href = href + + +class ImageItem(TextElement): + """Renders a placeholder for an image.""" + + new_line = False + + @classmethod + def create(cls, markdown: Markdown, token: Token) -> MarkdownElement: + """Factory to create markdown element, + + Args: + markdown (Markdown): The parent Markdown object. + token (Any): A token from markdown-it. + + Returns: + MarkdownElement: A new markdown element + """ + return cls(str(token.attrs.get("src", "")), markdown.hyperlinks) + + def __init__(self, destination: str, hyperlinks: bool) -> None: + self.destination = destination + self.hyperlinks = hyperlinks + self.link: str | None = None + super().__init__() + + def on_enter(self, context: MarkdownContext) -> None: + self.link = context.current_style.link + self.text = Text(justify="left") + super().on_enter(context) + + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: + link_style = Style(link=self.link or self.destination or None) + title = self.text or Text(self.destination.strip("/").rsplit("/", 1)[-1]) + if self.hyperlinks: + title.stylize(link_style) + text = Text.assemble("🌆 ", title, " ", end="") + yield text + + +class MarkdownContext: + """Manages the console render state.""" + + def __init__( + self, + console: Console, + options: ConsoleOptions, + style: Style, + inline_code_lexer: str | None = None, + inline_code_theme: str = "monokai", + ) -> None: + self.console = console + self.options = options + self.style_stack: StyleStack = StyleStack(style) + self.stack: Stack[MarkdownElement] = Stack() + + self._syntax: Syntax | None = None + if inline_code_lexer is not None: + self._syntax = Syntax("", inline_code_lexer, theme=inline_code_theme) + + @property + def current_style(self) -> Style: + """Current style which is the product of all styles on the stack.""" + return self.style_stack.current + + def on_text(self, text: str, node_type: str) -> None: + """Called when the parser visits text.""" + if node_type in {"fence", "code_inline"} and self._syntax is not None: + highlight_text = self._syntax.highlight(text) + highlight_text.rstrip() + self.stack.top.on_text( + self, Text.assemble(highlight_text, style=self.style_stack.current) + ) + else: + self.stack.top.on_text(self, text) + + def enter_style(self, style_name: str | Style) -> Style: + """Enter a style context.""" + style = self.console.get_style(style_name, default="none") + self.style_stack.push(style) + return self.current_style + + def leave_style(self) -> Style: + """Leave a style context.""" + style = self.style_stack.pop() + return style + + +class Markdown(JupyterMixin): + """A Markdown renderable. + + Args: + markup (str): A string containing markdown. + code_theme (str, optional): Pygments theme for code blocks. Defaults to "monokai". See https://pygments.org/styles/ for code themes. + justify (JustifyMethod, optional): Justify value for paragraphs. Defaults to None. + style (Union[str, Style], optional): Optional style to apply to markdown. + hyperlinks (bool, optional): Enable hyperlinks. Defaults to ``True``. + inline_code_lexer: (str, optional): Lexer to use if inline code highlighting is + enabled. Defaults to None. + inline_code_theme: (Optional[str], optional): Pygments theme for inline code + highlighting, or None for no highlighting. Defaults to None. + """ + + elements: ClassVar[dict[str, type[MarkdownElement]]] = { + "paragraph_open": Paragraph, + "heading_open": Heading, + "fence": CodeBlock, + "code_block": CodeBlock, + "blockquote_open": BlockQuote, + "hr": HorizontalRule, + "bullet_list_open": ListElement, + "ordered_list_open": ListElement, + "list_item_open": ListItem, + "image": ImageItem, + "table_open": TableElement, + "tbody_open": TableBodyElement, + "thead_open": TableHeaderElement, + "tr_open": TableRowElement, + "td_open": TableDataElement, + "th_open": TableDataElement, + } + + inlines = {"em", "strong", "code", "s"} + + def __init__( + self, + markup: str, + code_theme: str = "monokai", + justify: JustifyMethod | None = None, + style: str | Style = "none", + hyperlinks: bool = True, + inline_code_lexer: str | None = None, + inline_code_theme: str | None = None, + ) -> None: + parser = MarkdownIt().enable("strikethrough").enable("table") + self.markup = markup + self.parsed = parser.parse(markup) + self.code_theme = code_theme + self.justify: JustifyMethod | None = justify + self.style = style + self.hyperlinks = hyperlinks + self.inline_code_lexer = inline_code_lexer + self.inline_code_theme = inline_code_theme or code_theme + + def _flatten_tokens(self, tokens: Iterable[Token]) -> Iterable[Token]: + """Flattens the token stream.""" + for token in tokens: + is_fence = token.type == "fence" + is_image = token.tag == "img" + if token.children and not (is_image or is_fence): + yield from self._flatten_tokens(token.children) + else: + yield token + + def __rich_console__( + self, console: Console, options: ConsoleOptions + ) -> RenderResult: + """Render markdown to the console.""" + style = console.get_style(self.style, default="none") + options = options.update(height=None) + context = MarkdownContext( + console, + options, + style, + inline_code_lexer=self.inline_code_lexer, + inline_code_theme=self.inline_code_theme, + ) + tokens = self.parsed + inline_style_tags = self.inlines + new_line = False + _new_line_segment = Segment.line() + + for token in self._flatten_tokens(tokens): + node_type = token.type + tag = token.tag + + entering = token.nesting == 1 + exiting = token.nesting == -1 + self_closing = token.nesting == 0 + + if node_type == "text": + context.on_text(token.content, node_type) + elif node_type == "hardbreak": + context.on_text("\n", node_type) + elif node_type == "softbreak": + context.on_text(" ", node_type) + elif node_type == "link_open": + href = str(token.attrs.get("href", "")) + if self.hyperlinks: + link_style = console.get_style("markdown.link_url", default="none") + link_style += Style(link=href) + context.enter_style(link_style) + else: + context.stack.push(Link.create(self, token)) + elif node_type == "html_inline": + if token.content == "": + kbd_style = console.get_style("markdown.kbd", default="bold") + context.enter_style(kbd_style) + elif token.content == "": + context.leave_style() + else: + continue + elif node_type == "link_close": + if self.hyperlinks: + context.leave_style() + else: + element = context.stack.pop() + assert isinstance(element, Link) + link_style = console.get_style("markdown.link", default="none") + context.enter_style(link_style) + context.on_text(element.text.plain, node_type) + context.leave_style() + context.on_text(" (", node_type) + link_url_style = console.get_style( + "markdown.link_url", default="none" + ) + context.enter_style(link_url_style) + context.on_text(element.href, node_type) + context.leave_style() + context.on_text(")", node_type) + elif ( + tag in inline_style_tags + and node_type != "fence" + and node_type != "code_block" + ): + if entering: + # If it's an opening inline token e.g. strong, em, etc. + # Then we move into a style context i.e. push to stack. + context.enter_style(f"markdown.{tag}") + elif exiting: + # If it's a closing inline style, then we pop the style + # off of the stack, to move out of the context of it... + context.leave_style() + else: + # If it's a self-closing inline style e.g. `code_inline` + context.enter_style(f"markdown.{tag}") + if token.content: + context.on_text(token.content, node_type) + context.leave_style() + else: + # Map the markdown tag -> MarkdownElement renderable + element_class = self.elements.get(token.type) or UnknownElement + element = element_class.create(self, token) + + if entering or self_closing: + context.stack.push(element) + element.on_enter(context) + + if exiting: # CLOSING tag + element = context.stack.pop() + + should_render = not context.stack or ( + context.stack + and context.stack.top.on_child_close(context, element) + ) + + if should_render: + if new_line: + yield _new_line_segment + + yield from console.render(element, context.options) + elif self_closing: # SELF-CLOSING tags (e.g. text, code, image) + context.stack.pop() + text = token.content + if text is not None: + element.on_text(context, text) + + should_render = ( + not context.stack + or context.stack + and context.stack.top.on_child_close(context, element) + ) + if should_render: + if new_line and node_type != "inline": + yield _new_line_segment + yield from console.render(element, context.options) + + if exiting or self_closing: + element.on_leave(context) + new_line = element.new_line + + +if __name__ == "__main__": # pragma: no cover + import argparse + import sys + + parser = argparse.ArgumentParser( + description="Render Markdown to the console with Rich" + ) + parser.add_argument( + "path", + metavar="PATH", + help="path to markdown file, or - for stdin", + ) + parser.add_argument( + "-c", + "--force-color", + dest="force_color", + action="store_true", + default=None, + help="force color for non-terminals", + ) + parser.add_argument( + "-t", + "--code-theme", + dest="code_theme", + default="monokai", + help="pygments code theme", + ) + parser.add_argument( + "-i", + "--inline-code-lexer", + dest="inline_code_lexer", + default=None, + help="inline_code_lexer", + ) + parser.add_argument( + "-y", + "--hyperlinks", + dest="hyperlinks", + action="store_true", + help="enable hyperlinks", + ) + parser.add_argument( + "-w", + "--width", + type=int, + dest="width", + default=None, + help="width of output (default will auto-detect)", + ) + parser.add_argument( + "-j", + "--justify", + dest="justify", + action="store_true", + help="enable full text justify", + ) + parser.add_argument( + "-p", + "--page", + dest="page", + action="store_true", + help="use pager to scroll output", + ) + args = parser.parse_args() + + from rich.console import Console + + if args.path == "-": + markdown_body = sys.stdin.read() + else: + with open(args.path, encoding="utf-8") as markdown_file: + markdown_body = markdown_file.read() + + markdown = Markdown( + markdown_body, + justify="full" if args.justify else "left", + code_theme=args.code_theme, + hyperlinks=args.hyperlinks, + inline_code_lexer=args.inline_code_lexer, + ) + if args.page: + import io + import pydoc + + fileio = io.StringIO() + console = Console( + file=fileio, force_terminal=args.force_color, width=args.width + ) + console.print(markdown) + pydoc.pager(fileio.getvalue()) + + else: + console = Console( + force_terminal=args.force_color, width=args.width, record=True + ) + console.print(markdown) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/rich/markup.py b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/markup.py new file mode 100644 index 0000000000000000000000000000000000000000..bd9c05a712a8077098507d6870ddc0b0bf3028b0 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/markup.py @@ -0,0 +1,251 @@ +import re +from ast import literal_eval +from operator import attrgetter +from typing import Callable, Iterable, List, Match, NamedTuple, Optional, Tuple, Union + +from ._emoji_replace import _emoji_replace +from .emoji import EmojiVariant +from .errors import MarkupError +from .style import Style +from .text import Span, Text + +RE_TAGS = re.compile( + r"""((\\*)\[([a-z#/@][^[]*?)])""", + re.VERBOSE, +) + +RE_HANDLER = re.compile(r"^([\w.]*?)(\(.*?\))?$") + + +class Tag(NamedTuple): + """A tag in console markup.""" + + name: str + """The tag name. e.g. 'bold'.""" + parameters: Optional[str] + """Any additional parameters after the name.""" + + def __str__(self) -> str: + return ( + self.name if self.parameters is None else f"{self.name} {self.parameters}" + ) + + @property + def markup(self) -> str: + """Get the string representation of this tag.""" + return ( + f"[{self.name}]" + if self.parameters is None + else f"[{self.name}={self.parameters}]" + ) + + +_ReStringMatch = Match[str] # regex match object +_ReSubCallable = Callable[[_ReStringMatch], str] # Callable invoked by re.sub +_EscapeSubMethod = Callable[[_ReSubCallable, str], str] # Sub method of a compiled re + + +def escape( + markup: str, + _escape: _EscapeSubMethod = re.compile(r"(\\*)(\[[a-z#/@][^[]*?])").sub, +) -> str: + """Escapes text so that it won't be interpreted as markup. + + Args: + markup (str): Content to be inserted in to markup. + + Returns: + str: Markup with square brackets escaped. + """ + + def escape_backslashes(match: Match[str]) -> str: + """Called by re.sub replace matches.""" + backslashes, text = match.groups() + return f"{backslashes}{backslashes}\\{text}" + + markup = _escape(escape_backslashes, markup) + if markup.endswith("\\") and not markup.endswith("\\\\"): + return markup + "\\" + + return markup + + +def _parse(markup: str) -> Iterable[Tuple[int, Optional[str], Optional[Tag]]]: + """Parse markup in to an iterable of tuples of (position, text, tag). + + Args: + markup (str): A string containing console markup + + """ + position = 0 + _divmod = divmod + _Tag = Tag + for match in RE_TAGS.finditer(markup): + full_text, escapes, tag_text = match.groups() + start, end = match.span() + if start > position: + yield start, markup[position:start], None + if escapes: + backslashes, escaped = _divmod(len(escapes), 2) + if backslashes: + # Literal backslashes + yield start, "\\" * backslashes, None + start += backslashes * 2 + if escaped: + # Escape of tag + yield start, full_text[len(escapes) :], None + position = end + continue + text, equals, parameters = tag_text.partition("=") + yield start, None, _Tag(text, parameters if equals else None) + position = end + if position < len(markup): + yield position, markup[position:], None + + +def render( + markup: str, + style: Union[str, Style] = "", + emoji: bool = True, + emoji_variant: Optional[EmojiVariant] = None, +) -> Text: + """Render console markup in to a Text instance. + + Args: + markup (str): A string containing console markup. + style: (Union[str, Style]): The style to use. + emoji (bool, optional): Also render emoji code. Defaults to True. + emoji_variant (str, optional): Optional emoji variant, either "text" or "emoji". Defaults to None. + + + Raises: + MarkupError: If there is a syntax error in the markup. + + Returns: + Text: A test instance. + """ + emoji_replace = _emoji_replace + if "[" not in markup: + return Text( + emoji_replace(markup, default_variant=emoji_variant) if emoji else markup, + style=style, + ) + text = Text(style=style) + append = text.append + normalize = Style.normalize + + style_stack: List[Tuple[int, Tag]] = [] + pop = style_stack.pop + + spans: List[Span] = [] + append_span = spans.append + + _Span = Span + _Tag = Tag + + def pop_style(style_name: str) -> Tuple[int, Tag]: + """Pop tag matching given style name.""" + for index, (_, tag) in enumerate(reversed(style_stack), 1): + if tag.name == style_name: + return pop(-index) + raise KeyError(style_name) + + for position, plain_text, tag in _parse(markup): + if plain_text is not None: + # Handle open brace escapes, where the brace is not part of a tag. + plain_text = plain_text.replace("\\[", "[") + append(emoji_replace(plain_text) if emoji else plain_text) + elif tag is not None: + if tag.name.startswith("/"): # Closing tag + style_name = tag.name[1:].strip() + + if style_name: # explicit close + style_name = normalize(style_name) + try: + start, open_tag = pop_style(style_name) + except KeyError: + raise MarkupError( + f"closing tag '{tag.markup}' at position {position} doesn't match any open tag" + ) from None + else: # implicit close + try: + start, open_tag = pop() + except IndexError: + raise MarkupError( + f"closing tag '[/]' at position {position} has nothing to close" + ) from None + + if open_tag.name.startswith("@"): + if open_tag.parameters: + handler_name = "" + parameters = open_tag.parameters.strip() + handler_match = RE_HANDLER.match(parameters) + if handler_match is not None: + handler_name, match_parameters = handler_match.groups() + parameters = ( + "()" if match_parameters is None else match_parameters + ) + + try: + meta_params = literal_eval(parameters) + except SyntaxError as error: + raise MarkupError( + f"error parsing {parameters!r} in {open_tag.parameters!r}; {error.msg}" + ) + except Exception as error: + raise MarkupError( + f"error parsing {open_tag.parameters!r}; {error}" + ) from None + + if handler_name: + meta_params = ( + handler_name, + meta_params + if isinstance(meta_params, tuple) + else (meta_params,), + ) + + else: + meta_params = () + + append_span( + _Span( + start, len(text), Style(meta={open_tag.name: meta_params}) + ) + ) + else: + append_span(_Span(start, len(text), str(open_tag))) + + else: # Opening tag + normalized_tag = _Tag(normalize(tag.name), tag.parameters) + style_stack.append((len(text), normalized_tag)) + + text_length = len(text) + while style_stack: + start, tag = style_stack.pop() + style = str(tag) + if style: + append_span(_Span(start, text_length, style)) + + text.spans = sorted(spans[::-1], key=attrgetter("start")) + return text + + +if __name__ == "__main__": # pragma: no cover + MARKUP = [ + "[red]Hello World[/red]", + "[magenta]Hello [b]World[/b]", + "[bold]Bold[italic] bold and italic [/bold]italic[/italic]", + "Click [link=https://www.willmcgugan.com]here[/link] to visit my Blog", + ":warning-emoji: [bold red blink] DANGER![/]", + ] + + from rich import print + from rich.table import Table + + grid = Table("Markup", "Result", padding=(0, 1)) + + for markup in MARKUP: + grid.add_row(Text(markup), markup) + + print(grid) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/rich/pretty.py b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/pretty.py new file mode 100644 index 0000000000000000000000000000000000000000..00abecab356bcfcec4fc979c71da90a7672843db --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/pretty.py @@ -0,0 +1,1016 @@ +import builtins +import collections +import dataclasses +import inspect +import os +import reprlib +import sys +from array import array +from collections import Counter, UserDict, UserList, defaultdict, deque +from dataclasses import dataclass, fields, is_dataclass +from inspect import isclass +from itertools import islice +from types import MappingProxyType +from typing import ( + TYPE_CHECKING, + Any, + Callable, + DefaultDict, + Deque, + Dict, + Iterable, + List, + Optional, + Sequence, + Set, + Tuple, + Union, +) + +from rich.repr import RichReprResult + +try: + import attr as _attr_module + + _has_attrs = hasattr(_attr_module, "ib") +except ImportError: # pragma: no cover + _has_attrs = False + +from . import get_console +from ._loop import loop_last +from ._pick import pick_bool +from .abc import RichRenderable +from .cells import cell_len +from .highlighter import ReprHighlighter +from .jupyter import JupyterMixin, JupyterRenderable +from .measure import Measurement +from .text import Text + +if TYPE_CHECKING: + from .console import ( + Console, + ConsoleOptions, + HighlighterType, + JustifyMethod, + OverflowMethod, + RenderResult, + ) + + +def _is_attr_object(obj: Any) -> bool: + """Check if an object was created with attrs module.""" + return _has_attrs and _attr_module.has(type(obj)) + + +def _get_attr_fields(obj: Any) -> Sequence["_attr_module.Attribute[Any]"]: + """Get fields for an attrs object.""" + return _attr_module.fields(type(obj)) if _has_attrs else [] + + +def _is_dataclass_repr(obj: object) -> bool: + """Check if an instance of a dataclass contains the default repr. + + Args: + obj (object): A dataclass instance. + + Returns: + bool: True if the default repr is used, False if there is a custom repr. + """ + # Digging in to a lot of internals here + # Catching all exceptions in case something is missing on a non CPython implementation + try: + return obj.__repr__.__code__.co_filename in ( + dataclasses.__file__, + reprlib.__file__, + ) + except Exception: # pragma: no coverage + return False + + +_dummy_namedtuple = collections.namedtuple("_dummy_namedtuple", []) + + +def _has_default_namedtuple_repr(obj: object) -> bool: + """Check if an instance of namedtuple contains the default repr + + Args: + obj (object): A namedtuple + + Returns: + bool: True if the default repr is used, False if there's a custom repr. + """ + obj_file = None + try: + obj_file = inspect.getfile(obj.__repr__) + except (OSError, TypeError): + # OSError handles case where object is defined in __main__ scope, e.g. REPL - no filename available. + # TypeError trapped defensively, in case of object without filename slips through. + pass + default_repr_file = inspect.getfile(_dummy_namedtuple.__repr__) + return obj_file == default_repr_file + + +def _ipy_display_hook( + value: Any, + console: Optional["Console"] = None, + overflow: "OverflowMethod" = "ignore", + crop: bool = False, + indent_guides: bool = False, + max_length: Optional[int] = None, + max_string: Optional[int] = None, + max_depth: Optional[int] = None, + expand_all: bool = False, +) -> Union[str, None]: + # needed here to prevent circular import: + from .console import ConsoleRenderable + + # always skip rich generated jupyter renderables or None values + if _safe_isinstance(value, JupyterRenderable) or value is None: + return None + + console = console or get_console() + + with console.capture() as capture: + # certain renderables should start on a new line + if _safe_isinstance(value, ConsoleRenderable): + console.line() + console.print( + ( + value + if _safe_isinstance(value, RichRenderable) + else Pretty( + value, + overflow=overflow, + indent_guides=indent_guides, + max_length=max_length, + max_string=max_string, + max_depth=max_depth, + expand_all=expand_all, + margin=12, + ) + ), + crop=crop, + new_line_start=True, + end="", + ) + # strip trailing newline, not usually part of a text repr + # I'm not sure if this should be prevented at a lower level + return capture.get().rstrip("\n") + + +def _safe_isinstance( + obj: object, class_or_tuple: Union[type, Tuple[type, ...]] +) -> bool: + """isinstance can fail in rare cases, for example types with no __class__""" + try: + return isinstance(obj, class_or_tuple) + except Exception: + return False + + +def install( + console: Optional["Console"] = None, + overflow: "OverflowMethod" = "ignore", + crop: bool = False, + indent_guides: bool = False, + max_length: Optional[int] = None, + max_string: Optional[int] = None, + max_depth: Optional[int] = None, + expand_all: bool = False, +) -> None: + """Install automatic pretty printing in the Python REPL. + + Args: + console (Console, optional): Console instance or ``None`` to use global console. Defaults to None. + overflow (Optional[OverflowMethod], optional): Overflow method. Defaults to "ignore". + crop (Optional[bool], optional): Enable cropping of long lines. Defaults to False. + indent_guides (bool, optional): Enable indentation guides. Defaults to False. + max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation. + Defaults to None. + max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to None. + max_depth (int, optional): Maximum depth of nested data structures, or None for no maximum. Defaults to None. + expand_all (bool, optional): Expand all containers. Defaults to False. + max_frames (int): Maximum number of frames to show in a traceback, 0 for no maximum. Defaults to 100. + """ + from rich import get_console + + console = console or get_console() + assert console is not None + + def display_hook(value: Any) -> None: + """Replacement sys.displayhook which prettifies objects with Rich.""" + if value is not None: + assert console is not None + builtins._ = None # type: ignore[attr-defined] + console.print( + ( + value + if _safe_isinstance(value, RichRenderable) + else Pretty( + value, + overflow=overflow, + indent_guides=indent_guides, + max_length=max_length, + max_string=max_string, + max_depth=max_depth, + expand_all=expand_all, + ) + ), + crop=crop, + ) + builtins._ = value # type: ignore[attr-defined] + + try: + ip = get_ipython() # type: ignore[name-defined] + except NameError: + sys.displayhook = display_hook + else: + from IPython.core.formatters import BaseFormatter + + class RichFormatter(BaseFormatter): # type: ignore[misc] + pprint: bool = True + + def __call__(self, value: Any) -> Any: + if self.pprint: + return _ipy_display_hook( + value, + console=console, + overflow=overflow, + indent_guides=indent_guides, + max_length=max_length, + max_string=max_string, + max_depth=max_depth, + expand_all=expand_all, + ) + else: + return repr(value) + + # replace plain text formatter with rich formatter + rich_formatter = RichFormatter() + ip.display_formatter.formatters["text/plain"] = rich_formatter + + +class Pretty(JupyterMixin): + """A rich renderable that pretty prints an object. + + Args: + _object (Any): An object to pretty print. + highlighter (HighlighterType, optional): Highlighter object to apply to result, or None for ReprHighlighter. Defaults to None. + indent_size (int, optional): Number of spaces in indent. Defaults to 4. + justify (JustifyMethod, optional): Justify method, or None for default. Defaults to None. + overflow (OverflowMethod, optional): Overflow method, or None for default. Defaults to None. + no_wrap (Optional[bool], optional): Disable word wrapping. Defaults to False. + indent_guides (bool, optional): Enable indentation guides. Defaults to False. + max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation. + Defaults to None. + max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to None. + max_depth (int, optional): Maximum depth of nested data structures, or None for no maximum. Defaults to None. + expand_all (bool, optional): Expand all containers. Defaults to False. + margin (int, optional): Subtrace a margin from width to force containers to expand earlier. Defaults to 0. + insert_line (bool, optional): Insert a new line if the output has multiple new lines. Defaults to False. + """ + + def __init__( + self, + _object: Any, + highlighter: Optional["HighlighterType"] = None, + *, + indent_size: int = 4, + justify: Optional["JustifyMethod"] = None, + overflow: Optional["OverflowMethod"] = None, + no_wrap: Optional[bool] = False, + indent_guides: bool = False, + max_length: Optional[int] = None, + max_string: Optional[int] = None, + max_depth: Optional[int] = None, + expand_all: bool = False, + margin: int = 0, + insert_line: bool = False, + ) -> None: + self._object = _object + self.highlighter = highlighter or ReprHighlighter() + self.indent_size = indent_size + self.justify: Optional["JustifyMethod"] = justify + self.overflow: Optional["OverflowMethod"] = overflow + self.no_wrap = no_wrap + self.indent_guides = indent_guides + self.max_length = max_length + self.max_string = max_string + self.max_depth = max_depth + self.expand_all = expand_all + self.margin = margin + self.insert_line = insert_line + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + pretty_str = pretty_repr( + self._object, + max_width=options.max_width - self.margin, + indent_size=self.indent_size, + max_length=self.max_length, + max_string=self.max_string, + max_depth=self.max_depth, + expand_all=self.expand_all, + ) + pretty_text = Text.from_ansi( + pretty_str, + justify=self.justify or options.justify, + overflow=self.overflow or options.overflow, + no_wrap=pick_bool(self.no_wrap, options.no_wrap), + style="pretty", + ) + pretty_text = ( + self.highlighter(pretty_text) + if pretty_text + else Text( + f"{type(self._object)}.__repr__ returned empty string", + style="dim italic", + ) + ) + if self.indent_guides and not options.ascii_only: + pretty_text = pretty_text.with_indent_guides( + self.indent_size, style="repr.indent" + ) + if self.insert_line and "\n" in pretty_text: + yield "" + yield pretty_text + + def __rich_measure__( + self, console: "Console", options: "ConsoleOptions" + ) -> "Measurement": + pretty_str = pretty_repr( + self._object, + max_width=options.max_width, + indent_size=self.indent_size, + max_length=self.max_length, + max_string=self.max_string, + max_depth=self.max_depth, + expand_all=self.expand_all, + ) + text_width = ( + max(cell_len(line) for line in pretty_str.splitlines()) if pretty_str else 0 + ) + return Measurement(text_width, text_width) + + +def _get_braces_for_defaultdict(_object: DefaultDict[Any, Any]) -> Tuple[str, str, str]: + return ( + f"defaultdict({_object.default_factory!r}, {{", + "})", + f"defaultdict({_object.default_factory!r}, {{}})", + ) + + +def _get_braces_for_deque(_object: Deque[Any]) -> Tuple[str, str, str]: + if _object.maxlen is None: + return ("deque([", "])", "deque()") + return ( + "deque([", + f"], maxlen={_object.maxlen})", + f"deque(maxlen={_object.maxlen})", + ) + + +def _get_braces_for_array(_object: "array[Any]") -> Tuple[str, str, str]: + return (f"array({_object.typecode!r}, [", "])", f"array({_object.typecode!r})") + + +_BRACES: Dict[type, Callable[[Any], Tuple[str, str, str]]] = { + os._Environ: lambda _object: ("environ({", "})", "environ({})"), + array: _get_braces_for_array, + defaultdict: _get_braces_for_defaultdict, + Counter: lambda _object: ("Counter({", "})", "Counter()"), + deque: _get_braces_for_deque, + dict: lambda _object: ("{", "}", "{}"), + UserDict: lambda _object: ("{", "}", "{}"), + frozenset: lambda _object: ("frozenset({", "})", "frozenset()"), + list: lambda _object: ("[", "]", "[]"), + UserList: lambda _object: ("[", "]", "[]"), + set: lambda _object: ("{", "}", "set()"), + tuple: lambda _object: ("(", ")", "()"), + MappingProxyType: lambda _object: ("mappingproxy({", "})", "mappingproxy({})"), +} +_CONTAINERS = tuple(_BRACES.keys()) +_MAPPING_CONTAINERS = (dict, os._Environ, MappingProxyType, UserDict) + + +def is_expandable(obj: Any) -> bool: + """Check if an object may be expanded by pretty print.""" + return ( + _safe_isinstance(obj, _CONTAINERS) + or (is_dataclass(obj)) + or (hasattr(obj, "__rich_repr__")) + or _is_attr_object(obj) + ) and not isclass(obj) + + +@dataclass +class Node: + """A node in a repr tree. May be atomic or a container.""" + + key_repr: str = "" + value_repr: str = "" + open_brace: str = "" + close_brace: str = "" + empty: str = "" + last: bool = False + is_tuple: bool = False + is_namedtuple: bool = False + children: Optional[List["Node"]] = None + key_separator: str = ": " + separator: str = ", " + + def iter_tokens(self) -> Iterable[str]: + """Generate tokens for this node.""" + if self.key_repr: + yield self.key_repr + yield self.key_separator + if self.value_repr: + yield self.value_repr + elif self.children is not None: + if self.children: + yield self.open_brace + if self.is_tuple and not self.is_namedtuple and len(self.children) == 1: + yield from self.children[0].iter_tokens() + yield "," + else: + for child in self.children: + yield from child.iter_tokens() + if not child.last: + yield self.separator + yield self.close_brace + else: + yield self.empty + + def check_length(self, start_length: int, max_length: int) -> bool: + """Check the length fits within a limit. + + Args: + start_length (int): Starting length of the line (indent, prefix, suffix). + max_length (int): Maximum length. + + Returns: + bool: True if the node can be rendered within max length, otherwise False. + """ + total_length = start_length + for token in self.iter_tokens(): + total_length += cell_len(token) + if total_length > max_length: + return False + return True + + def __str__(self) -> str: + repr_text = "".join(self.iter_tokens()) + return repr_text + + def render( + self, max_width: int = 80, indent_size: int = 4, expand_all: bool = False + ) -> str: + """Render the node to a pretty repr. + + Args: + max_width (int, optional): Maximum width of the repr. Defaults to 80. + indent_size (int, optional): Size of indents. Defaults to 4. + expand_all (bool, optional): Expand all levels. Defaults to False. + + Returns: + str: A repr string of the original object. + """ + lines = [_Line(node=self, is_root=True)] + line_no = 0 + while line_no < len(lines): + line = lines[line_no] + if line.expandable and not line.expanded: + if expand_all or not line.check_length(max_width): + lines[line_no : line_no + 1] = line.expand(indent_size) + line_no += 1 + + repr_str = "\n".join(str(line) for line in lines) + return repr_str + + +@dataclass +class _Line: + """A line in repr output.""" + + parent: Optional["_Line"] = None + is_root: bool = False + node: Optional[Node] = None + text: str = "" + suffix: str = "" + whitespace: str = "" + expanded: bool = False + last: bool = False + + @property + def expandable(self) -> bool: + """Check if the line may be expanded.""" + return bool(self.node is not None and self.node.children) + + def check_length(self, max_length: int) -> bool: + """Check this line fits within a given number of cells.""" + start_length = ( + len(self.whitespace) + cell_len(self.text) + cell_len(self.suffix) + ) + assert self.node is not None + return self.node.check_length(start_length, max_length) + + def expand(self, indent_size: int) -> Iterable["_Line"]: + """Expand this line by adding children on their own line.""" + node = self.node + assert node is not None + whitespace = self.whitespace + assert node.children + if node.key_repr: + new_line = yield _Line( + text=f"{node.key_repr}{node.key_separator}{node.open_brace}", + whitespace=whitespace, + ) + else: + new_line = yield _Line(text=node.open_brace, whitespace=whitespace) + child_whitespace = self.whitespace + " " * indent_size + tuple_of_one = node.is_tuple and len(node.children) == 1 + for last, child in loop_last(node.children): + separator = "," if tuple_of_one else node.separator + line = _Line( + parent=new_line, + node=child, + whitespace=child_whitespace, + suffix=separator, + last=last and not tuple_of_one, + ) + yield line + + yield _Line( + text=node.close_brace, + whitespace=whitespace, + suffix=self.suffix, + last=self.last, + ) + + def __str__(self) -> str: + if self.last: + return f"{self.whitespace}{self.text}{self.node or ''}" + else: + return ( + f"{self.whitespace}{self.text}{self.node or ''}{self.suffix.rstrip()}" + ) + + +def _is_namedtuple(obj: Any) -> bool: + """Checks if an object is most likely a namedtuple. It is possible + to craft an object that passes this check and isn't a namedtuple, but + there is only a minuscule chance of this happening unintentionally. + + Args: + obj (Any): The object to test + + Returns: + bool: True if the object is a namedtuple. False otherwise. + """ + try: + fields = getattr(obj, "_fields", None) + except Exception: + # Being very defensive - if we cannot get the attr then its not a namedtuple + return False + return isinstance(obj, tuple) and isinstance(fields, tuple) + + +def traverse( + _object: Any, + max_length: Optional[int] = None, + max_string: Optional[int] = None, + max_depth: Optional[int] = None, +) -> Node: + """Traverse object and generate a tree. + + Args: + _object (Any): Object to be traversed. + max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation. + Defaults to None. + max_string (int, optional): Maximum length of string before truncating, or None to disable truncating. + Defaults to None. + max_depth (int, optional): Maximum depth of data structures, or None for no maximum. + Defaults to None. + + Returns: + Node: The root of a tree structure which can be used to render a pretty repr. + """ + + def to_repr(obj: Any) -> str: + """Get repr string for an object, but catch errors.""" + if ( + max_string is not None + and _safe_isinstance(obj, (bytes, str)) + and len(obj) > max_string + ): + truncated = len(obj) - max_string + obj_repr = f"{obj[:max_string]!r}+{truncated}" + else: + try: + obj_repr = repr(obj) + except Exception as error: + obj_repr = f"" + return obj_repr + + visited_ids: Set[int] = set() + push_visited = visited_ids.add + pop_visited = visited_ids.remove + + def _traverse(obj: Any, root: bool = False, depth: int = 0) -> Node: + """Walk the object depth first.""" + + obj_id = id(obj) + if obj_id in visited_ids: + # Recursion detected + return Node(value_repr="...") + + obj_type = type(obj) + children: List[Node] + reached_max_depth = max_depth is not None and depth >= max_depth + + def iter_rich_args(rich_args: Any) -> Iterable[Union[Any, Tuple[str, Any]]]: + for arg in rich_args: + if _safe_isinstance(arg, tuple): + if len(arg) == 3: + key, child, default = arg + if default == child: + continue + yield key, child + elif len(arg) == 2: + key, child = arg + yield key, child + elif len(arg) == 1: + yield arg[0] + else: + yield arg + + try: + fake_attributes = hasattr( + obj, "awehoi234_wdfjwljet234_234wdfoijsdfmmnxpi492" + ) + except Exception: + fake_attributes = False + + rich_repr_result: Optional[RichReprResult] = None + if not fake_attributes: + try: + if hasattr(obj, "__rich_repr__") and not isclass(obj): + rich_repr_result = obj.__rich_repr__() + except Exception: + pass + + if rich_repr_result is not None: + push_visited(obj_id) + angular = getattr(obj.__rich_repr__, "angular", False) + args = list(iter_rich_args(rich_repr_result)) + class_name = obj.__class__.__name__ + + if args: + children = [] + append = children.append + + if reached_max_depth: + if angular: + node = Node(value_repr=f"<{class_name}...>") + else: + node = Node(value_repr=f"{class_name}(...)") + else: + if angular: + node = Node( + open_brace=f"<{class_name} ", + close_brace=">", + children=children, + last=root, + separator=" ", + ) + else: + node = Node( + open_brace=f"{class_name}(", + close_brace=")", + children=children, + last=root, + ) + for last, arg in loop_last(args): + if _safe_isinstance(arg, tuple): + key, child = arg + child_node = _traverse(child, depth=depth + 1) + child_node.last = last + child_node.key_repr = key + child_node.key_separator = "=" + append(child_node) + else: + child_node = _traverse(arg, depth=depth + 1) + child_node.last = last + append(child_node) + else: + node = Node( + value_repr=f"<{class_name}>" if angular else f"{class_name}()", + children=[], + last=root, + ) + pop_visited(obj_id) + elif _is_attr_object(obj) and not fake_attributes: + push_visited(obj_id) + children = [] + append = children.append + + attr_fields = _get_attr_fields(obj) + if attr_fields: + if reached_max_depth: + node = Node(value_repr=f"{obj.__class__.__name__}(...)") + else: + node = Node( + open_brace=f"{obj.__class__.__name__}(", + close_brace=")", + children=children, + last=root, + ) + + def iter_attrs() -> ( + Iterable[Tuple[str, Any, Optional[Callable[[Any], str]]]] + ): + """Iterate over attr fields and values.""" + for attr in attr_fields: + if attr.repr: + try: + value = getattr(obj, attr.name) + except Exception as error: + # Can happen, albeit rarely + yield (attr.name, error, None) + else: + yield ( + attr.name, + value, + attr.repr if callable(attr.repr) else None, + ) + + for last, (name, value, repr_callable) in loop_last(iter_attrs()): + if repr_callable: + child_node = Node(value_repr=str(repr_callable(value))) + else: + child_node = _traverse(value, depth=depth + 1) + child_node.last = last + child_node.key_repr = name + child_node.key_separator = "=" + append(child_node) + else: + node = Node( + value_repr=f"{obj.__class__.__name__}()", children=[], last=root + ) + pop_visited(obj_id) + elif ( + is_dataclass(obj) + and not _safe_isinstance(obj, type) + and not fake_attributes + and _is_dataclass_repr(obj) + ): + push_visited(obj_id) + children = [] + append = children.append + if reached_max_depth: + node = Node(value_repr=f"{obj.__class__.__name__}(...)") + else: + node = Node( + open_brace=f"{obj.__class__.__name__}(", + close_brace=")", + children=children, + last=root, + empty=f"{obj.__class__.__name__}()", + ) + + for last, field in loop_last( + field + for field in fields(obj) + if field.repr and hasattr(obj, field.name) + ): + child_node = _traverse(getattr(obj, field.name), depth=depth + 1) + child_node.key_repr = field.name + child_node.last = last + child_node.key_separator = "=" + append(child_node) + + pop_visited(obj_id) + elif _is_namedtuple(obj) and _has_default_namedtuple_repr(obj): + push_visited(obj_id) + class_name = obj.__class__.__name__ + if reached_max_depth: + # If we've reached the max depth, we still show the class name, but not its contents + node = Node( + value_repr=f"{class_name}(...)", + ) + else: + children = [] + append = children.append + node = Node( + open_brace=f"{class_name}(", + close_brace=")", + children=children, + empty=f"{class_name}()", + ) + for last, (key, value) in loop_last(obj._asdict().items()): + child_node = _traverse(value, depth=depth + 1) + child_node.key_repr = key + child_node.last = last + child_node.key_separator = "=" + append(child_node) + pop_visited(obj_id) + elif _safe_isinstance(obj, _CONTAINERS): + for container_type in _CONTAINERS: + if _safe_isinstance(obj, container_type): + obj_type = container_type + break + + push_visited(obj_id) + + open_brace, close_brace, empty = _BRACES[obj_type](obj) + + if reached_max_depth: + node = Node(value_repr=f"{open_brace}...{close_brace}") + elif obj_type.__repr__ != type(obj).__repr__: + node = Node(value_repr=to_repr(obj), last=root) + elif obj: + children = [] + node = Node( + open_brace=open_brace, + close_brace=close_brace, + children=children, + last=root, + ) + append = children.append + num_items = len(obj) + last_item_index = num_items - 1 + + if _safe_isinstance(obj, _MAPPING_CONTAINERS): + iter_items = iter(obj.items()) + if max_length is not None: + iter_items = islice(iter_items, max_length) + for index, (key, child) in enumerate(iter_items): + child_node = _traverse(child, depth=depth + 1) + child_node.key_repr = to_repr(key) + child_node.last = index == last_item_index + append(child_node) + else: + iter_values = iter(obj) + if max_length is not None: + iter_values = islice(iter_values, max_length) + for index, child in enumerate(iter_values): + child_node = _traverse(child, depth=depth + 1) + child_node.last = index == last_item_index + append(child_node) + if max_length is not None and num_items > max_length: + append(Node(value_repr=f"... +{num_items - max_length}", last=True)) + else: + node = Node(empty=empty, children=[], last=root) + + pop_visited(obj_id) + else: + node = Node(value_repr=to_repr(obj), last=root) + node.is_tuple = type(obj) == tuple + node.is_namedtuple = _is_namedtuple(obj) + return node + + node = _traverse(_object, root=True) + return node + + +def pretty_repr( + _object: Any, + *, + max_width: int = 80, + indent_size: int = 4, + max_length: Optional[int] = None, + max_string: Optional[int] = None, + max_depth: Optional[int] = None, + expand_all: bool = False, +) -> str: + """Prettify repr string by expanding on to new lines to fit within a given width. + + Args: + _object (Any): Object to repr. + max_width (int, optional): Desired maximum width of repr string. Defaults to 80. + indent_size (int, optional): Number of spaces to indent. Defaults to 4. + max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation. + Defaults to None. + max_string (int, optional): Maximum length of string before truncating, or None to disable truncating. + Defaults to None. + max_depth (int, optional): Maximum depth of nested data structure, or None for no depth. + Defaults to None. + expand_all (bool, optional): Expand all containers regardless of available width. Defaults to False. + + Returns: + str: A possibly multi-line representation of the object. + """ + + if _safe_isinstance(_object, Node): + node = _object + else: + node = traverse( + _object, max_length=max_length, max_string=max_string, max_depth=max_depth + ) + repr_str: str = node.render( + max_width=max_width, indent_size=indent_size, expand_all=expand_all + ) + return repr_str + + +def pprint( + _object: Any, + *, + console: Optional["Console"] = None, + indent_guides: bool = True, + max_length: Optional[int] = None, + max_string: Optional[int] = None, + max_depth: Optional[int] = None, + expand_all: bool = False, +) -> None: + """A convenience function for pretty printing. + + Args: + _object (Any): Object to pretty print. + console (Console, optional): Console instance, or None to use default. Defaults to None. + max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation. + Defaults to None. + max_string (int, optional): Maximum length of strings before truncating, or None to disable. Defaults to None. + max_depth (int, optional): Maximum depth for nested data structures, or None for unlimited depth. Defaults to None. + indent_guides (bool, optional): Enable indentation guides. Defaults to True. + expand_all (bool, optional): Expand all containers. Defaults to False. + """ + _console = get_console() if console is None else console + _console.print( + Pretty( + _object, + max_length=max_length, + max_string=max_string, + max_depth=max_depth, + indent_guides=indent_guides, + expand_all=expand_all, + overflow="ignore", + ), + soft_wrap=True, + ) + + +if __name__ == "__main__": # pragma: no cover + + class BrokenRepr: + def __repr__(self) -> str: + 1 / 0 + return "this will fail" + + from typing import NamedTuple + + class StockKeepingUnit(NamedTuple): + name: str + description: str + price: float + category: str + reviews: List[str] + + d = defaultdict(int) + d["foo"] = 5 + data = { + "foo": [ + 1, + "Hello World!", + 100.123, + 323.232, + 432324.0, + {5, 6, 7, (1, 2, 3, 4), 8}, + ], + "bar": frozenset({1, 2, 3}), + "defaultdict": defaultdict( + list, {"crumble": ["apple", "rhubarb", "butter", "sugar", "flour"]} + ), + "counter": Counter( + [ + "apple", + "orange", + "pear", + "kumquat", + "kumquat", + "durian" * 100, + ] + ), + "atomic": (False, True, None), + "namedtuple": StockKeepingUnit( + "Sparkling British Spring Water", + "Carbonated spring water", + 0.9, + "water", + ["its amazing!", "its terrible!"], + ), + "Broken": BrokenRepr(), + } + data["foo"].append(data) # type: ignore[attr-defined] + + from rich import print + + print(Pretty(data, indent_guides=True, max_string=20)) + + class Thing: + def __repr__(self) -> str: + return "Hello\x1b[38;5;239m World!" + + print(Pretty(Thing())) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/rich/progress.py b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/progress.py new file mode 100644 index 0000000000000000000000000000000000000000..c2de125aec8cc5ed78441f93c25af7df4db30131 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/progress.py @@ -0,0 +1,1716 @@ +from __future__ import annotations + +import io +import typing +import warnings +from abc import ABC, abstractmethod +from collections import deque +from dataclasses import dataclass, field +from datetime import timedelta +from io import RawIOBase, UnsupportedOperation +from math import ceil +from mmap import mmap +from operator import length_hint +from os import PathLike, stat +from threading import Event, RLock, Thread +from types import TracebackType +from typing import ( + TYPE_CHECKING, + Any, + BinaryIO, + Callable, + ContextManager, + Deque, + Dict, + Generic, + Iterable, + List, + Literal, + NamedTuple, + NewType, + Optional, + TextIO, + Tuple, + Type, + TypeVar, + Union, +) + +if TYPE_CHECKING: + # Can be replaced with `from typing import Self` in Python 3.11+ + from typing_extensions import Self # pragma: no cover + +from . import filesize, get_console +from .console import Console, Group, JustifyMethod, RenderableType +from .highlighter import Highlighter +from .jupyter import JupyterMixin +from .live import Live +from .progress_bar import ProgressBar +from .spinner import Spinner +from .style import StyleType +from .table import Column, Table +from .text import Text, TextType + +TaskID = NewType("TaskID", int) + +ProgressType = TypeVar("ProgressType") + +GetTimeCallable = Callable[[], float] + + +_I = typing.TypeVar("_I", TextIO, BinaryIO) + + +class _TrackThread(Thread): + """A thread to periodically update progress.""" + + def __init__(self, progress: "Progress", task_id: "TaskID", update_period: float): + self.progress = progress + self.task_id = task_id + self.update_period = update_period + self.done = Event() + + self.completed = 0 + super().__init__(daemon=True) + + def run(self) -> None: + task_id = self.task_id + advance = self.progress.advance + update_period = self.update_period + last_completed = 0 + wait = self.done.wait + while not wait(update_period) and self.progress.live.is_started: + completed = self.completed + if last_completed != completed: + advance(task_id, completed - last_completed) + last_completed = completed + + self.progress.update(self.task_id, completed=self.completed, refresh=True) + + def __enter__(self) -> "_TrackThread": + self.start() + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + self.done.set() + self.join() + + +def track( + sequence: Iterable[ProgressType], + description: str = "Working...", + total: Optional[float] = None, + completed: int = 0, + auto_refresh: bool = True, + console: Optional[Console] = None, + transient: bool = False, + get_time: Optional[Callable[[], float]] = None, + refresh_per_second: float = 10, + style: StyleType = "bar.back", + complete_style: StyleType = "bar.complete", + finished_style: StyleType = "bar.finished", + pulse_style: StyleType = "bar.pulse", + update_period: float = 0.1, + disable: bool = False, + show_speed: bool = True, +) -> Iterable[ProgressType]: + """Track progress by iterating over a sequence. + + You can also track progress of an iterable, which might require that you additionally specify ``total``. + + Args: + sequence (Iterable[ProgressType]): Values you wish to iterate over and track progress. + description (str, optional): Description of task show next to progress bar. Defaults to "Working". + total: (float, optional): Total number of steps. Default is len(sequence). + completed (int, optional): Number of steps completed so far. Defaults to 0. + auto_refresh (bool, optional): Automatic refresh, disable to force a refresh after each iteration. Default is True. + transient: (bool, optional): Clear the progress on exit. Defaults to False. + console (Console, optional): Console to write to. Default creates internal Console instance. + refresh_per_second (float): Number of times per second to refresh the progress information. Defaults to 10. + style (StyleType, optional): Style for the bar background. Defaults to "bar.back". + complete_style (StyleType, optional): Style for the completed bar. Defaults to "bar.complete". + finished_style (StyleType, optional): Style for a finished bar. Defaults to "bar.finished". + pulse_style (StyleType, optional): Style for pulsing bars. Defaults to "bar.pulse". + update_period (float, optional): Minimum time (in seconds) between calls to update(). Defaults to 0.1. + disable (bool, optional): Disable display of progress. + show_speed (bool, optional): Show speed if total isn't known. Defaults to True. + Returns: + Iterable[ProgressType]: An iterable of the values in the sequence. + + """ + + columns: List["ProgressColumn"] = ( + [TextColumn("[progress.description]{task.description}")] if description else [] + ) + columns.extend( + ( + BarColumn( + style=style, + complete_style=complete_style, + finished_style=finished_style, + pulse_style=pulse_style, + ), + TaskProgressColumn(show_speed=show_speed), + TimeRemainingColumn(elapsed_when_finished=True), + ) + ) + progress = Progress( + *columns, + auto_refresh=auto_refresh, + console=console, + transient=transient, + get_time=get_time, + refresh_per_second=refresh_per_second or 10, + disable=disable, + ) + + with progress: + yield from progress.track( + sequence, + total=total, + completed=completed, + description=description, + update_period=update_period, + ) + + +class _Reader(RawIOBase, BinaryIO): + """A reader that tracks progress while it's being read from.""" + + def __init__( + self, + handle: BinaryIO, + progress: "Progress", + task: TaskID, + close_handle: bool = True, + ) -> None: + self.handle = handle + self.progress = progress + self.task = task + self.close_handle = close_handle + self._closed = False + + def __enter__(self) -> "_Reader": + self.handle.__enter__() + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + self.close() + + def __iter__(self) -> BinaryIO: + return self + + def __next__(self) -> bytes: + line = next(self.handle) + self.progress.advance(self.task, advance=len(line)) + return line + + @property + def closed(self) -> bool: + return self._closed + + def fileno(self) -> int: + return self.handle.fileno() + + def isatty(self) -> bool: + return self.handle.isatty() + + @property + def mode(self) -> str: + return self.handle.mode + + @property + def name(self) -> str: + return self.handle.name + + def readable(self) -> bool: + return self.handle.readable() + + def seekable(self) -> bool: + return self.handle.seekable() + + def writable(self) -> bool: + return False + + def read(self, size: int = -1) -> bytes: + block = self.handle.read(size) + self.progress.advance(self.task, advance=len(block)) + return block + + def readinto(self, b: Union[bytearray, memoryview, mmap]): # type: ignore[no-untyped-def, override] + n = self.handle.readinto(b) # type: ignore[attr-defined] + self.progress.advance(self.task, advance=n) + return n + + def readline(self, size: int = -1) -> bytes: # type: ignore[override] + line = self.handle.readline(size) + self.progress.advance(self.task, advance=len(line)) + return line + + def readlines(self, hint: int = -1) -> List[bytes]: + lines = self.handle.readlines(hint) + self.progress.advance(self.task, advance=sum(map(len, lines))) + return lines + + def close(self) -> None: + if self.close_handle: + self.handle.close() + self._closed = True + + def seek(self, offset: int, whence: int = 0) -> int: + pos = self.handle.seek(offset, whence) + self.progress.update(self.task, completed=pos) + return pos + + def tell(self) -> int: + return self.handle.tell() + + def write(self, s: Any) -> int: + raise UnsupportedOperation("write") + + def writelines(self, lines: Iterable[Any]) -> None: + raise UnsupportedOperation("writelines") + + +class _ReadContext(ContextManager[_I], Generic[_I]): + """A utility class to handle a context for both a reader and a progress.""" + + def __init__(self, progress: "Progress", reader: _I) -> None: + self.progress = progress + self.reader: _I = reader + + def __enter__(self) -> _I: + self.progress.start() + return self.reader.__enter__() + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + self.progress.stop() + self.reader.__exit__(exc_type, exc_val, exc_tb) + + +def wrap_file( + file: BinaryIO, + total: int, + *, + description: str = "Reading...", + auto_refresh: bool = True, + console: Optional[Console] = None, + transient: bool = False, + get_time: Optional[Callable[[], float]] = None, + refresh_per_second: float = 10, + style: StyleType = "bar.back", + complete_style: StyleType = "bar.complete", + finished_style: StyleType = "bar.finished", + pulse_style: StyleType = "bar.pulse", + disable: bool = False, +) -> ContextManager[BinaryIO]: + """Read bytes from a file while tracking progress. + + Args: + file (Union[str, PathLike[str], BinaryIO]): The path to the file to read, or a file-like object in binary mode. + total (int): Total number of bytes to read. + description (str, optional): Description of task show next to progress bar. Defaults to "Reading". + auto_refresh (bool, optional): Automatic refresh, disable to force a refresh after each iteration. Default is True. + transient: (bool, optional): Clear the progress on exit. Defaults to False. + console (Console, optional): Console to write to. Default creates internal Console instance. + refresh_per_second (float): Number of times per second to refresh the progress information. Defaults to 10. + style (StyleType, optional): Style for the bar background. Defaults to "bar.back". + complete_style (StyleType, optional): Style for the completed bar. Defaults to "bar.complete". + finished_style (StyleType, optional): Style for a finished bar. Defaults to "bar.finished". + pulse_style (StyleType, optional): Style for pulsing bars. Defaults to "bar.pulse". + disable (bool, optional): Disable display of progress. + Returns: + ContextManager[BinaryIO]: A context manager yielding a progress reader. + + """ + + columns: List["ProgressColumn"] = ( + [TextColumn("[progress.description]{task.description}")] if description else [] + ) + columns.extend( + ( + BarColumn( + style=style, + complete_style=complete_style, + finished_style=finished_style, + pulse_style=pulse_style, + ), + DownloadColumn(), + TimeRemainingColumn(), + ) + ) + progress = Progress( + *columns, + auto_refresh=auto_refresh, + console=console, + transient=transient, + get_time=get_time, + refresh_per_second=refresh_per_second or 10, + disable=disable, + ) + + reader = progress.wrap_file(file, total=total, description=description) + return _ReadContext(progress, reader) + + +@typing.overload +def open( + file: Union[str, "PathLike[str]", bytes], + mode: Union[Literal["rt"], Literal["r"]], + buffering: int = -1, + encoding: Optional[str] = None, + errors: Optional[str] = None, + newline: Optional[str] = None, + *, + total: Optional[int] = None, + description: str = "Reading...", + auto_refresh: bool = True, + console: Optional[Console] = None, + transient: bool = False, + get_time: Optional[Callable[[], float]] = None, + refresh_per_second: float = 10, + style: StyleType = "bar.back", + complete_style: StyleType = "bar.complete", + finished_style: StyleType = "bar.finished", + pulse_style: StyleType = "bar.pulse", + disable: bool = False, +) -> ContextManager[TextIO]: + pass + + +@typing.overload +def open( + file: Union[str, "PathLike[str]", bytes], + mode: Literal["rb"], + buffering: int = -1, + encoding: Optional[str] = None, + errors: Optional[str] = None, + newline: Optional[str] = None, + *, + total: Optional[int] = None, + description: str = "Reading...", + auto_refresh: bool = True, + console: Optional[Console] = None, + transient: bool = False, + get_time: Optional[Callable[[], float]] = None, + refresh_per_second: float = 10, + style: StyleType = "bar.back", + complete_style: StyleType = "bar.complete", + finished_style: StyleType = "bar.finished", + pulse_style: StyleType = "bar.pulse", + disable: bool = False, +) -> ContextManager[BinaryIO]: + pass + + +def open( + file: Union[str, "PathLike[str]", bytes], + mode: Union[Literal["rb"], Literal["rt"], Literal["r"]] = "r", + buffering: int = -1, + encoding: Optional[str] = None, + errors: Optional[str] = None, + newline: Optional[str] = None, + *, + total: Optional[int] = None, + description: str = "Reading...", + auto_refresh: bool = True, + console: Optional[Console] = None, + transient: bool = False, + get_time: Optional[Callable[[], float]] = None, + refresh_per_second: float = 10, + style: StyleType = "bar.back", + complete_style: StyleType = "bar.complete", + finished_style: StyleType = "bar.finished", + pulse_style: StyleType = "bar.pulse", + disable: bool = False, +) -> Union[ContextManager[BinaryIO], ContextManager[TextIO]]: + """Read bytes from a file while tracking progress. + + Args: + path (Union[str, PathLike[str], BinaryIO]): The path to the file to read, or a file-like object in binary mode. + mode (str): The mode to use to open the file. Only supports "r", "rb" or "rt". + buffering (int): The buffering strategy to use, see :func:`io.open`. + encoding (str, optional): The encoding to use when reading in text mode, see :func:`io.open`. + errors (str, optional): The error handling strategy for decoding errors, see :func:`io.open`. + newline (str, optional): The strategy for handling newlines in text mode, see :func:`io.open` + total: (int, optional): Total number of bytes to read. Must be provided if reading from a file handle. Default for a path is os.stat(file).st_size. + description (str, optional): Description of task show next to progress bar. Defaults to "Reading". + auto_refresh (bool, optional): Automatic refresh, disable to force a refresh after each iteration. Default is True. + transient: (bool, optional): Clear the progress on exit. Defaults to False. + console (Console, optional): Console to write to. Default creates internal Console instance. + refresh_per_second (float): Number of times per second to refresh the progress information. Defaults to 10. + style (StyleType, optional): Style for the bar background. Defaults to "bar.back". + complete_style (StyleType, optional): Style for the completed bar. Defaults to "bar.complete". + finished_style (StyleType, optional): Style for a finished bar. Defaults to "bar.finished". + pulse_style (StyleType, optional): Style for pulsing bars. Defaults to "bar.pulse". + disable (bool, optional): Disable display of progress. + encoding (str, optional): The encoding to use when reading in text mode. + + Returns: + ContextManager[BinaryIO]: A context manager yielding a progress reader. + + """ + + columns: List["ProgressColumn"] = ( + [TextColumn("[progress.description]{task.description}")] if description else [] + ) + columns.extend( + ( + BarColumn( + style=style, + complete_style=complete_style, + finished_style=finished_style, + pulse_style=pulse_style, + ), + DownloadColumn(), + TimeRemainingColumn(), + ) + ) + progress = Progress( + *columns, + auto_refresh=auto_refresh, + console=console, + transient=transient, + get_time=get_time, + refresh_per_second=refresh_per_second or 10, + disable=disable, + ) + + reader = progress.open( + file, + mode=mode, + buffering=buffering, + encoding=encoding, + errors=errors, + newline=newline, + total=total, + description=description, + ) + return _ReadContext(progress, reader) # type: ignore[return-value, type-var] + + +class ProgressColumn(ABC): + """Base class for a widget to use in progress display.""" + + max_refresh: Optional[float] = None + + def __init__(self, table_column: Optional[Column] = None) -> None: + self._table_column = table_column + self._renderable_cache: Dict[TaskID, Tuple[float, RenderableType]] = {} + self._update_time: Optional[float] = None + + def get_table_column(self) -> Column: + """Get a table column, used to build tasks table.""" + return self._table_column or Column() + + def __call__(self, task: "Task") -> RenderableType: + """Called by the Progress object to return a renderable for the given task. + + Args: + task (Task): An object containing information regarding the task. + + Returns: + RenderableType: Anything renderable (including str). + """ + current_time = task.get_time() + if self.max_refresh is not None and not task.completed: + try: + timestamp, renderable = self._renderable_cache[task.id] + except KeyError: + pass + else: + if timestamp + self.max_refresh > current_time: + return renderable + + renderable = self.render(task) + self._renderable_cache[task.id] = (current_time, renderable) + return renderable + + @abstractmethod + def render(self, task: "Task") -> RenderableType: + """Should return a renderable object.""" + + +class RenderableColumn(ProgressColumn): + """A column to insert an arbitrary column. + + Args: + renderable (RenderableType, optional): Any renderable. Defaults to empty string. + """ + + def __init__( + self, renderable: RenderableType = "", *, table_column: Optional[Column] = None + ): + self.renderable = renderable + super().__init__(table_column=table_column) + + def render(self, task: "Task") -> RenderableType: + return self.renderable + + +class SpinnerColumn(ProgressColumn): + """A column with a 'spinner' animation. + + Args: + spinner_name (str, optional): Name of spinner animation. Defaults to "dots". + style (StyleType, optional): Style of spinner. Defaults to "progress.spinner". + speed (float, optional): Speed factor of spinner. Defaults to 1.0. + finished_text (TextType, optional): Text used when task is finished. Defaults to " ". + """ + + def __init__( + self, + spinner_name: str = "dots", + style: Optional[StyleType] = "progress.spinner", + speed: float = 1.0, + finished_text: TextType = " ", + table_column: Optional[Column] = None, + ): + self.spinner = Spinner(spinner_name, style=style, speed=speed) + self.finished_text = ( + Text.from_markup(finished_text) + if isinstance(finished_text, str) + else finished_text + ) + super().__init__(table_column=table_column) + + def set_spinner( + self, + spinner_name: str, + spinner_style: Optional[StyleType] = "progress.spinner", + speed: float = 1.0, + ) -> None: + """Set a new spinner. + + Args: + spinner_name (str): Spinner name, see python -m rich.spinner. + spinner_style (Optional[StyleType], optional): Spinner style. Defaults to "progress.spinner". + speed (float, optional): Speed factor of spinner. Defaults to 1.0. + """ + self.spinner = Spinner(spinner_name, style=spinner_style, speed=speed) + + def render(self, task: "Task") -> RenderableType: + text = ( + self.finished_text + if task.finished + else self.spinner.render(task.get_time()) + ) + return text + + +class TextColumn(ProgressColumn): + """A column containing text.""" + + def __init__( + self, + text_format: str, + style: StyleType = "none", + justify: JustifyMethod = "left", + markup: bool = True, + highlighter: Optional[Highlighter] = None, + table_column: Optional[Column] = None, + ) -> None: + self.text_format = text_format + self.justify: JustifyMethod = justify + self.style = style + self.markup = markup + self.highlighter = highlighter + super().__init__(table_column=table_column or Column(no_wrap=True)) + + def render(self, task: "Task") -> Text: + _text = self.text_format.format(task=task) + if self.markup: + text = Text.from_markup(_text, style=self.style, justify=self.justify) + else: + text = Text(_text, style=self.style, justify=self.justify) + if self.highlighter: + self.highlighter.highlight(text) + return text + + +class BarColumn(ProgressColumn): + """Renders a visual progress bar. + + Args: + bar_width (Optional[int], optional): Width of bar or None for full width. Defaults to 40. + style (StyleType, optional): Style for the bar background. Defaults to "bar.back". + complete_style (StyleType, optional): Style for the completed bar. Defaults to "bar.complete". + finished_style (StyleType, optional): Style for a finished bar. Defaults to "bar.finished". + pulse_style (StyleType, optional): Style for pulsing bars. Defaults to "bar.pulse". + """ + + def __init__( + self, + bar_width: Optional[int] = 40, + style: StyleType = "bar.back", + complete_style: StyleType = "bar.complete", + finished_style: StyleType = "bar.finished", + pulse_style: StyleType = "bar.pulse", + table_column: Optional[Column] = None, + ) -> None: + self.bar_width = bar_width + self.style = style + self.complete_style = complete_style + self.finished_style = finished_style + self.pulse_style = pulse_style + super().__init__(table_column=table_column) + + def render(self, task: "Task") -> ProgressBar: + """Gets a progress bar widget for a task.""" + return ProgressBar( + total=max(0, task.total) if task.total is not None else None, + completed=max(0, task.completed), + width=None if self.bar_width is None else max(1, self.bar_width), + pulse=not task.started, + animation_time=task.get_time(), + style=self.style, + complete_style=self.complete_style, + finished_style=self.finished_style, + pulse_style=self.pulse_style, + ) + + +class TimeElapsedColumn(ProgressColumn): + """Renders time elapsed.""" + + def render(self, task: "Task") -> Text: + """Show time elapsed.""" + elapsed = task.finished_time if task.finished else task.elapsed + if elapsed is None: + return Text("-:--:--", style="progress.elapsed") + delta = timedelta(seconds=max(0, int(elapsed))) + return Text(str(delta), style="progress.elapsed") + + +class TaskProgressColumn(TextColumn): + """Show task progress as a percentage. + + Args: + text_format (str, optional): Format for percentage display. Defaults to "[progress.percentage]{task.percentage:>3.0f}%". + text_format_no_percentage (str, optional): Format if percentage is unknown. Defaults to "". + style (StyleType, optional): Style of output. Defaults to "none". + justify (JustifyMethod, optional): Text justification. Defaults to "left". + markup (bool, optional): Enable markup. Defaults to True. + highlighter (Optional[Highlighter], optional): Highlighter to apply to output. Defaults to None. + table_column (Optional[Column], optional): Table Column to use. Defaults to None. + show_speed (bool, optional): Show speed if total is unknown. Defaults to False. + """ + + def __init__( + self, + text_format: str = "[progress.percentage]{task.percentage:>3.0f}%", + text_format_no_percentage: str = "", + style: StyleType = "none", + justify: JustifyMethod = "left", + markup: bool = True, + highlighter: Optional[Highlighter] = None, + table_column: Optional[Column] = None, + show_speed: bool = False, + ) -> None: + self.text_format_no_percentage = text_format_no_percentage + self.show_speed = show_speed + super().__init__( + text_format=text_format, + style=style, + justify=justify, + markup=markup, + highlighter=highlighter, + table_column=table_column, + ) + + @classmethod + def render_speed(cls, speed: Optional[float]) -> Text: + """Render the speed in iterations per second. + + Args: + task (Task): A Task object. + + Returns: + Text: Text object containing the task speed. + """ + if speed is None: + return Text("", style="progress.percentage") + unit, suffix = filesize.pick_unit_and_suffix( + int(speed), + ["", "×10³", "×10⁶", "×10⁹", "×10¹²"], + 1000, + ) + data_speed = speed / unit + return Text(f"{data_speed:.1f}{suffix} it/s", style="progress.percentage") + + def render(self, task: "Task") -> Text: + if task.total is None and self.show_speed: + return self.render_speed(task.finished_speed or task.speed) + text_format = ( + self.text_format_no_percentage if task.total is None else self.text_format + ) + _text = text_format.format(task=task) + if self.markup: + text = Text.from_markup(_text, style=self.style, justify=self.justify) + else: + text = Text(_text, style=self.style, justify=self.justify) + if self.highlighter: + self.highlighter.highlight(text) + return text + + +class TimeRemainingColumn(ProgressColumn): + """Renders estimated time remaining. + + Args: + compact (bool, optional): Render MM:SS when time remaining is less than an hour. Defaults to False. + elapsed_when_finished (bool, optional): Render time elapsed when the task is finished. Defaults to False. + """ + + # Only refresh twice a second to prevent jitter + max_refresh = 0.5 + + def __init__( + self, + compact: bool = False, + elapsed_when_finished: bool = False, + table_column: Optional[Column] = None, + ): + self.compact = compact + self.elapsed_when_finished = elapsed_when_finished + super().__init__(table_column=table_column) + + def render(self, task: "Task") -> Text: + """Show time remaining.""" + if self.elapsed_when_finished and task.finished: + task_time = task.finished_time + style = "progress.elapsed" + else: + task_time = task.time_remaining + style = "progress.remaining" + + if task.total is None: + return Text("", style=style) + + if task_time is None: + return Text("--:--" if self.compact else "-:--:--", style=style) + + # Based on https://github.com/tqdm/tqdm/blob/master/tqdm/std.py + minutes, seconds = divmod(int(task_time), 60) + hours, minutes = divmod(minutes, 60) + + if self.compact and not hours: + formatted = f"{minutes:02d}:{seconds:02d}" + else: + formatted = f"{hours:d}:{minutes:02d}:{seconds:02d}" + + return Text(formatted, style=style) + + +class FileSizeColumn(ProgressColumn): + """Renders completed filesize.""" + + def render(self, task: "Task") -> Text: + """Show data completed.""" + data_size = filesize.decimal(int(task.completed)) + return Text(data_size, style="progress.filesize") + + +class TotalFileSizeColumn(ProgressColumn): + """Renders total filesize.""" + + def render(self, task: "Task") -> Text: + """Show data completed.""" + data_size = filesize.decimal(int(task.total)) if task.total is not None else "" + return Text(data_size, style="progress.filesize.total") + + +class MofNCompleteColumn(ProgressColumn): + """Renders completed count/total, e.g. ' 10/1000'. + + Best for bounded tasks with int quantities. + + Space pads the completed count so that progress length does not change as task progresses + past powers of 10. + + Args: + separator (str, optional): Text to separate completed and total values. Defaults to "/". + """ + + def __init__(self, separator: str = "/", table_column: Optional[Column] = None): + self.separator = separator + super().__init__(table_column=table_column) + + def render(self, task: "Task") -> Text: + """Show completed/total.""" + completed = int(task.completed) + total = int(task.total) if task.total is not None else "?" + total_width = len(str(total)) + return Text( + f"{completed:{total_width}d}{self.separator}{total}", + style="progress.download", + ) + + +class DownloadColumn(ProgressColumn): + """Renders file size downloaded and total, e.g. '0.5/2.3 GB'. + + Args: + binary_units (bool, optional): Use binary units, KiB, MiB etc. Defaults to False. + """ + + def __init__( + self, binary_units: bool = False, table_column: Optional[Column] = None + ) -> None: + self.binary_units = binary_units + super().__init__(table_column=table_column) + + def render(self, task: "Task") -> Text: + """Calculate common unit for completed and total.""" + completed = int(task.completed) + + unit_and_suffix_calculation_base = ( + int(task.total) if task.total is not None else completed + ) + if self.binary_units: + unit, suffix = filesize.pick_unit_and_suffix( + unit_and_suffix_calculation_base, + ["bytes", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"], + 1024, + ) + else: + unit, suffix = filesize.pick_unit_and_suffix( + unit_and_suffix_calculation_base, + ["bytes", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"], + 1000, + ) + precision = 0 if unit == 1 else 1 + + completed_ratio = completed / unit + completed_str = f"{completed_ratio:,.{precision}f}" + + if task.total is not None: + total = int(task.total) + total_ratio = total / unit + total_str = f"{total_ratio:,.{precision}f}" + else: + total_str = "?" + + download_status = f"{completed_str}/{total_str} {suffix}" + download_text = Text(download_status, style="progress.download") + return download_text + + +class TransferSpeedColumn(ProgressColumn): + """Renders human readable transfer speed.""" + + def render(self, task: "Task") -> Text: + """Show data transfer speed.""" + speed = task.finished_speed or task.speed + if speed is None: + return Text("?", style="progress.data.speed") + data_speed = filesize.decimal(int(speed)) + return Text(f"{data_speed}/s", style="progress.data.speed") + + +class ProgressSample(NamedTuple): + """Sample of progress for a given time.""" + + timestamp: float + """Timestamp of sample.""" + completed: float + """Number of steps completed.""" + + +@dataclass +class Task: + """Information regarding a progress task. + + This object should be considered read-only outside of the :class:`~Progress` class. + + """ + + id: TaskID + """Task ID associated with this task (used in Progress methods).""" + + description: str + """str: Description of the task.""" + + total: Optional[float] + """Optional[float]: Total number of steps in this task.""" + + completed: float + """float: Number of steps completed""" + + _get_time: GetTimeCallable + """Callable to get the current time.""" + + finished_time: Optional[float] = None + """float: Time task was finished.""" + + visible: bool = True + """bool: Indicates if this task is visible in the progress display.""" + + fields: Dict[str, Any] = field(default_factory=dict) + """dict: Arbitrary fields passed in via Progress.update.""" + + start_time: Optional[float] = field(default=None, init=False, repr=False) + """Optional[float]: Time this task was started, or None if not started.""" + + stop_time: Optional[float] = field(default=None, init=False, repr=False) + """Optional[float]: Time this task was stopped, or None if not stopped.""" + + finished_speed: Optional[float] = None + """Optional[float]: The last speed for a finished task.""" + + _progress: Deque[ProgressSample] = field( + default_factory=lambda: deque(maxlen=1000), init=False, repr=False + ) + + _lock: RLock = field(repr=False, default_factory=RLock) + """Thread lock.""" + + def get_time(self) -> float: + """float: Get the current time, in seconds.""" + return self._get_time() + + @property + def started(self) -> bool: + """bool: Check if the task as started.""" + return self.start_time is not None + + @property + def remaining(self) -> Optional[float]: + """Optional[float]: Get the number of steps remaining, if a non-None total was set.""" + if self.total is None: + return None + return self.total - self.completed + + @property + def elapsed(self) -> Optional[float]: + """Optional[float]: Time elapsed since task was started, or ``None`` if the task hasn't started.""" + if self.start_time is None: + return None + if self.stop_time is not None: + return self.stop_time - self.start_time + return self.get_time() - self.start_time + + @property + def finished(self) -> bool: + """Check if the task has finished.""" + return self.finished_time is not None + + @property + def percentage(self) -> float: + """float: Get progress of task as a percentage. If a None total was set, returns 0""" + if not self.total: + return 0.0 + completed = (self.completed / self.total) * 100.0 + completed = min(100.0, max(0.0, completed)) + return completed + + @property + def speed(self) -> Optional[float]: + """Optional[float]: Get the estimated speed in steps per second.""" + if self.start_time is None: + return None + with self._lock: + progress = self._progress + if not progress: + return None + total_time = progress[-1].timestamp - progress[0].timestamp + if total_time == 0: + return None + iter_progress = iter(progress) + next(iter_progress) + total_completed = sum(sample.completed for sample in iter_progress) + speed = total_completed / total_time + return speed + + @property + def time_remaining(self) -> Optional[float]: + """Optional[float]: Get estimated time to completion, or ``None`` if no data.""" + if self.finished: + return 0.0 + speed = self.speed + if not speed: + return None + remaining = self.remaining + if remaining is None: + return None + estimate = ceil(remaining / speed) + return estimate + + def _reset(self) -> None: + """Reset progress.""" + self._progress.clear() + self.finished_time = None + self.finished_speed = None + + +class Progress(JupyterMixin): + """Renders an auto-updating progress bar(s). + + Args: + console (Console, optional): Optional Console instance. Defaults to an internal Console instance writing to stdout. + auto_refresh (bool, optional): Enable auto refresh. If disabled, you will need to call `refresh()`. + refresh_per_second (float, optional): Number of times per second to refresh the progress information. Defaults to 10. + speed_estimate_period: (float, optional): Period (in seconds) used to calculate the speed estimate. Defaults to 30. + transient: (bool, optional): Clear the progress on exit. Defaults to False. + redirect_stdout: (bool, optional): Enable redirection of stdout, so ``print`` may be used. Defaults to True. + redirect_stderr: (bool, optional): Enable redirection of stderr. Defaults to True. + get_time: (Callable, optional): A callable that gets the current time, or None to use Console.get_time. Defaults to None. + disable (bool, optional): Disable progress display. Defaults to False + expand (bool, optional): Expand tasks table to fit width. Defaults to False. + """ + + def __init__( + self, + *columns: Union[str, ProgressColumn], + console: Optional[Console] = None, + auto_refresh: bool = True, + refresh_per_second: float = 10, + speed_estimate_period: float = 30.0, + transient: bool = False, + redirect_stdout: bool = True, + redirect_stderr: bool = True, + get_time: Optional[GetTimeCallable] = None, + disable: bool = False, + expand: bool = False, + ) -> None: + assert refresh_per_second > 0, "refresh_per_second must be > 0" + self._lock = RLock() + self.columns = columns or self.get_default_columns() + self.speed_estimate_period = speed_estimate_period + + self.disable = disable + self.expand = expand + self._tasks: Dict[TaskID, Task] = {} + self._task_index: TaskID = TaskID(0) + self.live = Live( + console=console or get_console(), + auto_refresh=auto_refresh, + refresh_per_second=refresh_per_second, + transient=transient, + redirect_stdout=redirect_stdout, + redirect_stderr=redirect_stderr, + get_renderable=self.get_renderable, + ) + self.get_time = get_time or self.console.get_time + self.print = self.console.print + self.log = self.console.log + + @classmethod + def get_default_columns(cls) -> Tuple[ProgressColumn, ...]: + """Get the default columns used for a new Progress instance: + - a text column for the description (TextColumn) + - the bar itself (BarColumn) + - a text column showing completion percentage (TextColumn) + - an estimated-time-remaining column (TimeRemainingColumn) + If the Progress instance is created without passing a columns argument, + the default columns defined here will be used. + + You can also create a Progress instance using custom columns before + and/or after the defaults, as in this example: + + progress = Progress( + SpinnerColumn(), + *Progress.get_default_columns(), + "Elapsed:", + TimeElapsedColumn(), + ) + + This code shows the creation of a Progress display, containing + a spinner to the left, the default columns, and a labeled elapsed + time column. + """ + return ( + TextColumn("[progress.description]{task.description}"), + BarColumn(), + TaskProgressColumn(), + TimeRemainingColumn(), + ) + + @property + def console(self) -> Console: + return self.live.console + + @property + def tasks(self) -> List[Task]: + """Get a list of Task instances.""" + with self._lock: + return list(self._tasks.values()) + + @property + def task_ids(self) -> List[TaskID]: + """A list of task IDs.""" + with self._lock: + return list(self._tasks.keys()) + + @property + def finished(self) -> bool: + """Check if all tasks have been completed.""" + with self._lock: + if not self._tasks: + return True + return all(task.finished for task in self._tasks.values()) + + def start(self) -> None: + """Start the progress display.""" + if not self.disable: + self.live.start(refresh=True) + + def stop(self) -> None: + """Stop the progress display.""" + if not self.disable: + self.live.stop() + if not self.console.is_interactive and not self.console.is_jupyter: + self.console.print() + + def __enter__(self) -> Self: + self.start() + return self + + def __exit__( + self, + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], + ) -> None: + self.stop() + + def track( + self, + sequence: Iterable[ProgressType], + total: Optional[float] = None, + completed: int = 0, + task_id: Optional[TaskID] = None, + description: str = "Working...", + update_period: float = 0.1, + ) -> Iterable[ProgressType]: + """Track progress by iterating over a sequence. + + You can also track progress of an iterable, which might require that you additionally specify ``total``. + + Args: + sequence (Iterable[ProgressType]): Values you want to iterate over and track progress. + total: (float, optional): Total number of steps. Default is len(sequence). + completed (int, optional): Number of steps completed so far. Defaults to 0. + task_id: (TaskID): Task to track. Default is new task. + description: (str, optional): Description of task, if new task is created. + update_period (float, optional): Minimum time (in seconds) between calls to update(). Defaults to 0.1. + + Returns: + Iterable[ProgressType]: An iterable of values taken from the provided sequence. + """ + if total is None: + total = float(length_hint(sequence)) or None + + if task_id is None: + task_id = self.add_task(description, total=total, completed=completed) + else: + self.update(task_id, total=total, completed=completed) + + if self.live.auto_refresh: + with _TrackThread(self, task_id, update_period) as track_thread: + for value in sequence: + yield value + track_thread.completed += 1 + else: + advance = self.advance + refresh = self.refresh + for value in sequence: + yield value + advance(task_id, 1) + refresh() + + def wrap_file( + self, + file: BinaryIO, + total: Optional[int] = None, + *, + task_id: Optional[TaskID] = None, + description: str = "Reading...", + ) -> BinaryIO: + """Track progress file reading from a binary file. + + Args: + file (BinaryIO): A file-like object opened in binary mode. + total (int, optional): Total number of bytes to read. This must be provided unless a task with a total is also given. + task_id (TaskID): Task to track. Default is new task. + description (str, optional): Description of task, if new task is created. + + Returns: + BinaryIO: A readable file-like object in binary mode. + + Raises: + ValueError: When no total value can be extracted from the arguments or the task. + """ + # attempt to recover the total from the task + total_bytes: Optional[float] = None + if total is not None: + total_bytes = total + elif task_id is not None: + with self._lock: + total_bytes = self._tasks[task_id].total + if total_bytes is None: + raise ValueError( + f"unable to get the total number of bytes, please specify 'total'" + ) + + # update total of task or create new task + if task_id is None: + task_id = self.add_task(description, total=total_bytes) + else: + self.update(task_id, total=total_bytes) + + return _Reader(file, self, task_id, close_handle=False) + + @typing.overload + def open( + self, + file: Union[str, "PathLike[str]", bytes], + mode: Literal["rb"], + buffering: int = -1, + encoding: Optional[str] = None, + errors: Optional[str] = None, + newline: Optional[str] = None, + *, + total: Optional[int] = None, + task_id: Optional[TaskID] = None, + description: str = "Reading...", + ) -> BinaryIO: + pass + + @typing.overload + def open( + self, + file: Union[str, "PathLike[str]", bytes], + mode: Union[Literal["r"], Literal["rt"]], + buffering: int = -1, + encoding: Optional[str] = None, + errors: Optional[str] = None, + newline: Optional[str] = None, + *, + total: Optional[int] = None, + task_id: Optional[TaskID] = None, + description: str = "Reading...", + ) -> TextIO: + pass + + def open( + self, + file: Union[str, "PathLike[str]", bytes], + mode: Union[Literal["rb"], Literal["rt"], Literal["r"]] = "r", + buffering: int = -1, + encoding: Optional[str] = None, + errors: Optional[str] = None, + newline: Optional[str] = None, + *, + total: Optional[int] = None, + task_id: Optional[TaskID] = None, + description: str = "Reading...", + ) -> Union[BinaryIO, TextIO]: + """Track progress while reading from a binary file. + + Args: + path (Union[str, PathLike[str]]): The path to the file to read. + mode (str): The mode to use to open the file. Only supports "r", "rb" or "rt". + buffering (int): The buffering strategy to use, see :func:`io.open`. + encoding (str, optional): The encoding to use when reading in text mode, see :func:`io.open`. + errors (str, optional): The error handling strategy for decoding errors, see :func:`io.open`. + newline (str, optional): The strategy for handling newlines in text mode, see :func:`io.open`. + total (int, optional): Total number of bytes to read. If none given, os.stat(path).st_size is used. + task_id (TaskID): Task to track. Default is new task. + description (str, optional): Description of task, if new task is created. + + Returns: + BinaryIO: A readable file-like object in binary mode. + + Raises: + ValueError: When an invalid mode is given. + """ + # normalize the mode (always rb, rt) + _mode = "".join(sorted(mode, reverse=False)) + if _mode not in ("br", "rt", "r"): + raise ValueError(f"invalid mode {mode!r}") + + # patch buffering to provide the same behaviour as the builtin `open` + line_buffering = buffering == 1 + if _mode == "br" and buffering == 1: + warnings.warn( + "line buffering (buffering=1) isn't supported in binary mode, the default buffer size will be used", + RuntimeWarning, + ) + buffering = -1 + elif _mode in ("rt", "r"): + if buffering == 0: + raise ValueError("can't have unbuffered text I/O") + elif buffering == 1: + buffering = -1 + + # attempt to get the total with `os.stat` + if total is None: + total = stat(file).st_size + + # update total of task or create new task + if task_id is None: + task_id = self.add_task(description, total=total) + else: + self.update(task_id, total=total) + + # open the file in binary mode, + handle = io.open(file, "rb", buffering=buffering) + reader = _Reader(handle, self, task_id, close_handle=True) + + # wrap the reader in a `TextIOWrapper` if text mode + if mode in ("r", "rt"): + return io.TextIOWrapper( + reader, + encoding=encoding, + errors=errors, + newline=newline, + line_buffering=line_buffering, + ) + + return reader + + def start_task(self, task_id: TaskID) -> None: + """Start a task. + + Starts a task (used when calculating elapsed time). You may need to call this manually, + if you called ``add_task`` with ``start=False``. + + Args: + task_id (TaskID): ID of task. + """ + with self._lock: + task = self._tasks[task_id] + if task.start_time is None: + task.start_time = self.get_time() + + def stop_task(self, task_id: TaskID) -> None: + """Stop a task. + + This will freeze the elapsed time on the task. + + Args: + task_id (TaskID): ID of task. + """ + with self._lock: + task = self._tasks[task_id] + current_time = self.get_time() + if task.start_time is None: + task.start_time = current_time + task.stop_time = current_time + + def update( + self, + task_id: TaskID, + *, + total: Optional[float] = None, + completed: Optional[float] = None, + advance: Optional[float] = None, + description: Optional[str] = None, + visible: Optional[bool] = None, + refresh: bool = False, + **fields: Any, + ) -> None: + """Update information associated with a task. + + Args: + task_id (TaskID): Task id (returned by add_task). + total (float, optional): Updates task.total if not None. + completed (float, optional): Updates task.completed if not None. + advance (float, optional): Add a value to task.completed if not None. + description (str, optional): Change task description if not None. + visible (bool, optional): Set visible flag if not None. + refresh (bool): Force a refresh of progress information. Default is False. + **fields (Any): Additional data fields required for rendering. + """ + with self._lock: + task = self._tasks[task_id] + completed_start = task.completed + + if total is not None and total != task.total: + task.total = total + task._reset() + if advance is not None: + task.completed += advance + if completed is not None: + task.completed = completed + if description is not None: + task.description = description + if visible is not None: + task.visible = visible + task.fields.update(fields) + update_completed = task.completed - completed_start + + current_time = self.get_time() + old_sample_time = current_time - self.speed_estimate_period + _progress = task._progress + + popleft = _progress.popleft + while _progress and _progress[0].timestamp < old_sample_time: + popleft() + if update_completed > 0: + _progress.append(ProgressSample(current_time, update_completed)) + if ( + task.total is not None + and task.completed >= task.total + and task.finished_time is None + ): + task.finished_time = task.elapsed + + if refresh: + self.refresh() + + def reset( + self, + task_id: TaskID, + *, + start: bool = True, + total: Optional[float] = None, + completed: int = 0, + visible: Optional[bool] = None, + description: Optional[str] = None, + **fields: Any, + ) -> None: + """Reset a task so completed is 0 and the clock is reset. + + Args: + task_id (TaskID): ID of task. + start (bool, optional): Start the task after reset. Defaults to True. + total (float, optional): New total steps in task, or None to use current total. Defaults to None. + completed (int, optional): Number of steps completed. Defaults to 0. + visible (bool, optional): Set visible flag if not None. + description (str, optional): Change task description if not None. Defaults to None. + **fields (str): Additional data fields required for rendering. + """ + current_time = self.get_time() + with self._lock: + task = self._tasks[task_id] + task._reset() + task.start_time = current_time if start else None + if total is not None: + task.total = total + task.completed = completed + if visible is not None: + task.visible = visible + if fields: + task.fields = fields + if description is not None: + task.description = description + task.finished_time = None + self.refresh() + + def advance(self, task_id: TaskID, advance: float = 1) -> None: + """Advance task by a number of steps. + + Args: + task_id (TaskID): ID of task. + advance (float): Number of steps to advance. Default is 1. + """ + current_time = self.get_time() + with self._lock: + task = self._tasks[task_id] + completed_start = task.completed + task.completed += advance + update_completed = task.completed - completed_start + old_sample_time = current_time - self.speed_estimate_period + _progress = task._progress + + popleft = _progress.popleft + while _progress and _progress[0].timestamp < old_sample_time: + popleft() + while len(_progress) > 1000: + popleft() + _progress.append(ProgressSample(current_time, update_completed)) + if ( + task.total is not None + and task.completed >= task.total + and task.finished_time is None + ): + task.finished_time = task.elapsed + task.finished_speed = task.speed + + def refresh(self) -> None: + """Refresh (render) the progress information.""" + if not self.disable and self.live.is_started: + self.live.refresh() + + def get_renderable(self) -> RenderableType: + """Get a renderable for the progress display.""" + renderable = Group(*self.get_renderables()) + return renderable + + def get_renderables(self) -> Iterable[RenderableType]: + """Get a number of renderables for the progress display.""" + table = self.make_tasks_table(self.tasks) + yield table + + def make_tasks_table(self, tasks: Iterable[Task]) -> Table: + """Get a table to render the Progress display. + + Args: + tasks (Iterable[Task]): An iterable of Task instances, one per row of the table. + + Returns: + Table: A table instance. + """ + table_columns = ( + ( + Column(no_wrap=True) + if isinstance(_column, str) + else _column.get_table_column().copy() + ) + for _column in self.columns + ) + table = Table.grid(*table_columns, padding=(0, 1), expand=self.expand) + + for task in tasks: + if task.visible: + table.add_row( + *( + ( + column.format(task=task) + if isinstance(column, str) + else column(task) + ) + for column in self.columns + ) + ) + return table + + def __rich__(self) -> RenderableType: + """Makes the Progress class itself renderable.""" + with self._lock: + return self.get_renderable() + + def add_task( + self, + description: str, + start: bool = True, + total: Optional[float] = 100.0, + completed: int = 0, + visible: bool = True, + **fields: Any, + ) -> TaskID: + """Add a new 'task' to the Progress display. + + Args: + description (str): A description of the task. + start (bool, optional): Start the task immediately (to calculate elapsed time). If set to False, + you will need to call `start` manually. Defaults to True. + total (float, optional): Number of total steps in the progress if known. + Set to None to render a pulsing animation. Defaults to 100. + completed (int, optional): Number of steps completed so far. Defaults to 0. + visible (bool, optional): Enable display of the task. Defaults to True. + **fields (str): Additional data fields required for rendering. + + Returns: + TaskID: An ID you can use when calling `update`. + """ + with self._lock: + task = Task( + self._task_index, + description, + total, + completed, + visible=visible, + fields=fields, + _get_time=self.get_time, + _lock=self._lock, + ) + self._tasks[self._task_index] = task + if start: + self.start_task(self._task_index) + new_task_index = self._task_index + self._task_index = TaskID(int(self._task_index) + 1) + self.refresh() + return new_task_index + + def remove_task(self, task_id: TaskID) -> None: + """Delete a task if it exists. + + Args: + task_id (TaskID): A task ID. + + """ + with self._lock: + del self._tasks[task_id] + + +if __name__ == "__main__": # pragma: no coverage + import random + import time + + from .panel import Panel + from .rule import Rule + from .syntax import Syntax + from .table import Table + + syntax = Syntax( + '''def loop_last(values: Iterable[T]) -> Iterable[Tuple[bool, T]]: + """Iterate and generate a tuple with a flag for last value.""" + iter_values = iter(values) + try: + previous_value = next(iter_values) + except StopIteration: + return + for value in iter_values: + yield False, previous_value + previous_value = value + yield True, previous_value''', + "python", + line_numbers=True, + ) + + table = Table("foo", "bar", "baz") + table.add_row("1", "2", "3") + + progress_renderables = [ + "Text may be printed while the progress bars are rendering.", + Panel("In fact, [i]any[/i] renderable will work"), + "Such as [magenta]tables[/]...", + table, + "Pretty printed structures...", + {"type": "example", "text": "Pretty printed"}, + "Syntax...", + syntax, + Rule("Give it a try!"), + ] + + from itertools import cycle + + examples = cycle(progress_renderables) + + console = Console(record=True) + + with Progress( + SpinnerColumn(), + *Progress.get_default_columns(), + TimeElapsedColumn(), + console=console, + transient=False, + ) as progress: + task1 = progress.add_task("[red]Downloading", total=1000) + task2 = progress.add_task("[green]Processing", total=1000) + task3 = progress.add_task("[yellow]Thinking", total=None) + + while not progress.finished: + progress.update(task1, advance=0.5) + progress.update(task2, advance=0.3) + time.sleep(0.01) + if random.randint(0, 100) < 1: + progress.log(next(examples)) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/rich/screen.py b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/screen.py new file mode 100644 index 0000000000000000000000000000000000000000..b4f7fd19de7ffc7e3c18702389e093402a633c5b --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/screen.py @@ -0,0 +1,54 @@ +from typing import Optional, TYPE_CHECKING + +from .segment import Segment +from .style import StyleType +from ._loop import loop_last + + +if TYPE_CHECKING: + from .console import ( + Console, + ConsoleOptions, + RenderResult, + RenderableType, + Group, + ) + + +class Screen: + """A renderable that fills the terminal screen and crops excess. + + Args: + renderable (RenderableType): Child renderable. + style (StyleType, optional): Optional background style. Defaults to None. + """ + + renderable: "RenderableType" + + def __init__( + self, + *renderables: "RenderableType", + style: Optional[StyleType] = None, + application_mode: bool = False, + ) -> None: + from rich.console import Group + + self.renderable = Group(*renderables) + self.style = style + self.application_mode = application_mode + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + width, height = options.size + style = console.get_style(self.style) if self.style else None + render_options = options.update(width=width, height=height) + lines = console.render_lines( + self.renderable or "", render_options, style=style, pad=True + ) + lines = Segment.set_shape(lines, width, height, style=style) + new_line = Segment("\n\r") if self.application_mode else Segment.line() + for last, line in loop_last(lines): + yield from line + if not last: + yield new_line diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/rich/styled.py b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/styled.py new file mode 100644 index 0000000000000000000000000000000000000000..27243beb76bb22c2851b97227249e70068ec4420 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/styled.py @@ -0,0 +1,42 @@ +from typing import TYPE_CHECKING + +from .measure import Measurement +from .segment import Segment +from .style import StyleType + +if TYPE_CHECKING: + from .console import Console, ConsoleOptions, RenderResult, RenderableType + + +class Styled: + """Apply a style to a renderable. + + Args: + renderable (RenderableType): Any renderable. + style (StyleType): A style to apply across the entire renderable. + """ + + def __init__(self, renderable: "RenderableType", style: "StyleType") -> None: + self.renderable = renderable + self.style = style + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + style = console.get_style(self.style) + rendered_segments = console.render(self.renderable, options) + segments = Segment.apply_style(rendered_segments, style) + return segments + + def __rich_measure__( + self, console: "Console", options: "ConsoleOptions" + ) -> Measurement: + return Measurement.get(console, options, self.renderable) + + +if __name__ == "__main__": # pragma: no cover + from rich import print + from rich.panel import Panel + + panel = Styled(Panel("hello"), "on blue") + print(panel) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/rich/table.py b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/table.py new file mode 100644 index 0000000000000000000000000000000000000000..1e2eb2b45aca21e15c9d0b183afe3a46c4a6c827 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/rich/table.py @@ -0,0 +1,1015 @@ +from dataclasses import dataclass, field, replace +from typing import ( + TYPE_CHECKING, + Dict, + Iterable, + List, + NamedTuple, + Optional, + Sequence, + Tuple, + Union, +) + +from . import box, errors +from ._loop import loop_first_last, loop_last +from ._pick import pick_bool +from ._ratio import ratio_distribute, ratio_reduce +from .align import VerticalAlignMethod +from .jupyter import JupyterMixin +from .measure import Measurement +from .padding import Padding, PaddingDimensions +from .protocol import is_renderable +from .segment import Segment +from .style import Style, StyleType +from .text import Text, TextType + +if TYPE_CHECKING: + from .console import ( + Console, + ConsoleOptions, + JustifyMethod, + OverflowMethod, + RenderableType, + RenderResult, + ) + + +@dataclass +class Column: + """Defines a column within a ~Table. + + Args: + title (Union[str, Text], optional): The title of the table rendered at the top. Defaults to None. + caption (Union[str, Text], optional): The table caption rendered below. Defaults to None. + width (int, optional): The width in characters of the table, or ``None`` to automatically fit. Defaults to None. + min_width (Optional[int], optional): The minimum width of the table, or ``None`` for no minimum. Defaults to None. + box (box.Box, optional): One of the constants in box.py used to draw the edges (see :ref:`appendix_box`), or ``None`` for no box lines. Defaults to box.HEAVY_HEAD. + safe_box (Optional[bool], optional): Disable box characters that don't display on windows legacy terminal with *raster* fonts. Defaults to True. + padding (PaddingDimensions, optional): Padding for cells (top, right, bottom, left). Defaults to (0, 1). + collapse_padding (bool, optional): Enable collapsing of padding around cells. Defaults to False. + pad_edge (bool, optional): Enable padding of edge cells. Defaults to True. + show_header (bool, optional): Show a header row. Defaults to True. + show_footer (bool, optional): Show a footer row. Defaults to False. + show_edge (bool, optional): Draw a box around the outside of the table. Defaults to True. + show_lines (bool, optional): Draw lines between every row. Defaults to False. + leading (int, optional): Number of blank lines between rows (precludes ``show_lines``). Defaults to 0. + style (Union[str, Style], optional): Default style for the table. Defaults to "none". + row_styles (List[Union, str], optional): Optional list of row styles, if more than one style is given then the styles will alternate. Defaults to None. + header_style (Union[str, Style], optional): Style of the header. Defaults to "table.header". + footer_style (Union[str, Style], optional): Style of the footer. Defaults to "table.footer". + border_style (Union[str, Style], optional): Style of the border. Defaults to None. + title_style (Union[str, Style], optional): Style of the title. Defaults to None. + caption_style (Union[str, Style], optional): Style of the caption. Defaults to None. + title_justify (str, optional): Justify method for title. Defaults to "center". + caption_justify (str, optional): Justify method for caption. Defaults to "center". + highlight (bool, optional): Highlight cell contents (if str). Defaults to False. + """ + + header: "RenderableType" = "" + """RenderableType: Renderable for the header (typically a string)""" + + footer: "RenderableType" = "" + """RenderableType: Renderable for the footer (typically a string)""" + + header_style: StyleType = "" + """StyleType: The style of the header.""" + + footer_style: StyleType = "" + """StyleType: The style of the footer.""" + + style: StyleType = "" + """StyleType: The style of the column.""" + + justify: "JustifyMethod" = "left" + """str: How to justify text within the column ("left", "center", "right", or "full")""" + + vertical: "VerticalAlignMethod" = "top" + """str: How to vertically align content ("top", "middle", or "bottom")""" + + overflow: "OverflowMethod" = "ellipsis" + """str: Overflow method.""" + + width: Optional[int] = None + """Optional[int]: Width of the column, or ``None`` (default) to auto calculate width.""" + + min_width: Optional[int] = None + """Optional[int]: Minimum width of column, or ``None`` for no minimum. Defaults to None.""" + + max_width: Optional[int] = None + """Optional[int]: Maximum width of column, or ``None`` for no maximum. Defaults to None.""" + + ratio: Optional[int] = None + """Optional[int]: Ratio to use when calculating column width, or ``None`` (default) to adapt to column contents.""" + + no_wrap: bool = False + """bool: Prevent wrapping of text within the column. Defaults to ``False``.""" + + highlight: bool = False + """bool: Apply highlighter to column. Defaults to ``False``.""" + + _index: int = 0 + """Index of column.""" + + _cells: List["RenderableType"] = field(default_factory=list) + + def copy(self) -> "Column": + """Return a copy of this Column.""" + return replace(self, _cells=[]) + + @property + def cells(self) -> Iterable["RenderableType"]: + """Get all cells in the column, not including header.""" + yield from self._cells + + @property + def flexible(self) -> bool: + """Check if this column is flexible.""" + return self.ratio is not None + + +@dataclass +class Row: + """Information regarding a row.""" + + style: Optional[StyleType] = None + """Style to apply to row.""" + + end_section: bool = False + """Indicated end of section, which will force a line beneath the row.""" + + +class _Cell(NamedTuple): + """A single cell in a table.""" + + style: StyleType + """Style to apply to cell.""" + renderable: "RenderableType" + """Cell renderable.""" + vertical: VerticalAlignMethod + """Cell vertical alignment.""" + + +class Table(JupyterMixin): + """A console renderable to draw a table. + + Args: + *headers (Union[Column, str]): Column headers, either as a string, or :class:`~rich.table.Column` instance. + title (Union[str, Text], optional): The title of the table rendered at the top. Defaults to None. + caption (Union[str, Text], optional): The table caption rendered below. Defaults to None. + width (int, optional): The width in characters of the table, or ``None`` to automatically fit. Defaults to None. + min_width (Optional[int], optional): The minimum width of the table, or ``None`` for no minimum. Defaults to None. + box (box.Box, optional): One of the constants in box.py used to draw the edges (see :ref:`appendix_box`), or ``None`` for no box lines. Defaults to box.HEAVY_HEAD. + safe_box (Optional[bool], optional): Disable box characters that don't display on windows legacy terminal with *raster* fonts. Defaults to True. + padding (PaddingDimensions, optional): Padding for cells (top, right, bottom, left). Defaults to (0, 1). + collapse_padding (bool, optional): Enable collapsing of padding around cells. Defaults to False. + pad_edge (bool, optional): Enable padding of edge cells. Defaults to True. + expand (bool, optional): Expand the table to fit the available space if ``True``, otherwise the table width will be auto-calculated. Defaults to False. + show_header (bool, optional): Show a header row. Defaults to True. + show_footer (bool, optional): Show a footer row. Defaults to False. + show_edge (bool, optional): Draw a box around the outside of the table. Defaults to True. + show_lines (bool, optional): Draw lines between every row. Defaults to False. + leading (int, optional): Number of blank lines between rows (precludes ``show_lines``). Defaults to 0. + style (Union[str, Style], optional): Default style for the table. Defaults to "none". + row_styles (List[Union, str], optional): Optional list of row styles, if more than one style is given then the styles will alternate. Defaults to None. + header_style (Union[str, Style], optional): Style of the header. Defaults to "table.header". + footer_style (Union[str, Style], optional): Style of the footer. Defaults to "table.footer". + border_style (Union[str, Style], optional): Style of the border. Defaults to None. + title_style (Union[str, Style], optional): Style of the title. Defaults to None. + caption_style (Union[str, Style], optional): Style of the caption. Defaults to None. + title_justify (str, optional): Justify method for title. Defaults to "center". + caption_justify (str, optional): Justify method for caption. Defaults to "center". + highlight (bool, optional): Highlight cell contents (if str). Defaults to False. + """ + + columns: List[Column] + rows: List[Row] + + def __init__( + self, + *headers: Union[Column, str], + title: Optional[TextType] = None, + caption: Optional[TextType] = None, + width: Optional[int] = None, + min_width: Optional[int] = None, + box: Optional[box.Box] = box.HEAVY_HEAD, + safe_box: Optional[bool] = None, + padding: PaddingDimensions = (0, 1), + collapse_padding: bool = False, + pad_edge: bool = True, + expand: bool = False, + show_header: bool = True, + show_footer: bool = False, + show_edge: bool = True, + show_lines: bool = False, + leading: int = 0, + style: StyleType = "none", + row_styles: Optional[Iterable[StyleType]] = None, + header_style: Optional[StyleType] = "table.header", + footer_style: Optional[StyleType] = "table.footer", + border_style: Optional[StyleType] = None, + title_style: Optional[StyleType] = None, + caption_style: Optional[StyleType] = None, + title_justify: "JustifyMethod" = "center", + caption_justify: "JustifyMethod" = "center", + highlight: bool = False, + ) -> None: + self.columns: List[Column] = [] + self.rows: List[Row] = [] + self.title = title + self.caption = caption + self.width = width + self.min_width = min_width + self.box = box + self.safe_box = safe_box + self._padding = Padding.unpack(padding) + self.pad_edge = pad_edge + self._expand = expand + self.show_header = show_header + self.show_footer = show_footer + self.show_edge = show_edge + self.show_lines = show_lines + self.leading = leading + self.collapse_padding = collapse_padding + self.style = style + self.header_style = header_style or "" + self.footer_style = footer_style or "" + self.border_style = border_style + self.title_style = title_style + self.caption_style = caption_style + self.title_justify: "JustifyMethod" = title_justify + self.caption_justify: "JustifyMethod" = caption_justify + self.highlight = highlight + self.row_styles: Sequence[StyleType] = list(row_styles or []) + append_column = self.columns.append + for header in headers: + if isinstance(header, str): + self.add_column(header=header) + else: + header._index = len(self.columns) + append_column(header) + + @classmethod + def grid( + cls, + *headers: Union[Column, str], + padding: PaddingDimensions = 0, + collapse_padding: bool = True, + pad_edge: bool = False, + expand: bool = False, + ) -> "Table": + """Get a table with no lines, headers, or footer. + + Args: + *headers (Union[Column, str]): Column headers, either as a string, or :class:`~rich.table.Column` instance. + padding (PaddingDimensions, optional): Get padding around cells. Defaults to 0. + collapse_padding (bool, optional): Enable collapsing of padding around cells. Defaults to True. + pad_edge (bool, optional): Enable padding around edges of table. Defaults to False. + expand (bool, optional): Expand the table to fit the available space if ``True``, otherwise the table width will be auto-calculated. Defaults to False. + + Returns: + Table: A table instance. + """ + return cls( + *headers, + box=None, + padding=padding, + collapse_padding=collapse_padding, + show_header=False, + show_footer=False, + show_edge=False, + pad_edge=pad_edge, + expand=expand, + ) + + @property + def expand(self) -> bool: + """Setting a non-None self.width implies expand.""" + return self._expand or self.width is not None + + @expand.setter + def expand(self, expand: bool) -> None: + """Set expand.""" + self._expand = expand + + @property + def _extra_width(self) -> int: + """Get extra width to add to cell content.""" + width = 0 + if self.box and self.show_edge: + width += 2 + if self.box: + width += len(self.columns) - 1 + return width + + @property + def row_count(self) -> int: + """Get the current number of rows.""" + return len(self.rows) + + def get_row_style(self, console: "Console", index: int) -> StyleType: + """Get the current row style.""" + style = Style.null() + if self.row_styles: + style += console.get_style(self.row_styles[index % len(self.row_styles)]) + row_style = self.rows[index].style + if row_style is not None: + style += console.get_style(row_style) + return style + + def __rich_measure__( + self, console: "Console", options: "ConsoleOptions" + ) -> Measurement: + max_width = options.max_width + if self.width is not None: + max_width = self.width + if max_width < 0: + return Measurement(0, 0) + + extra_width = self._extra_width + max_width = sum( + self._calculate_column_widths( + console, options.update_width(max_width - extra_width) + ) + ) + _measure_column = self._measure_column + + measurements = [ + _measure_column(console, options.update_width(max_width), column) + for column in self.columns + ] + minimum_width = ( + sum(measurement.minimum for measurement in measurements) + extra_width + ) + maximum_width = ( + sum(measurement.maximum for measurement in measurements) + extra_width + if (self.width is None) + else self.width + ) + measurement = Measurement(minimum_width, maximum_width) + measurement = measurement.clamp(self.min_width) + return measurement + + @property + def padding(self) -> Tuple[int, int, int, int]: + """Get cell padding.""" + return self._padding + + @padding.setter + def padding(self, padding: PaddingDimensions) -> "Table": + """Set cell padding.""" + self._padding = Padding.unpack(padding) + return self + + def add_column( + self, + header: "RenderableType" = "", + footer: "RenderableType" = "", + *, + header_style: Optional[StyleType] = None, + highlight: Optional[bool] = None, + footer_style: Optional[StyleType] = None, + style: Optional[StyleType] = None, + justify: "JustifyMethod" = "left", + vertical: "VerticalAlignMethod" = "top", + overflow: "OverflowMethod" = "ellipsis", + width: Optional[int] = None, + min_width: Optional[int] = None, + max_width: Optional[int] = None, + ratio: Optional[int] = None, + no_wrap: bool = False, + ) -> None: + """Add a column to the table. + + Args: + header (RenderableType, optional): Text or renderable for the header. + Defaults to "". + footer (RenderableType, optional): Text or renderable for the footer. + Defaults to "". + header_style (Union[str, Style], optional): Style for the header, or None for default. Defaults to None. + highlight (bool, optional): Whether to highlight the text. The default of None uses the value of the table (self) object. + footer_style (Union[str, Style], optional): Style for the footer, or None for default. Defaults to None. + style (Union[str, Style], optional): Style for the column cells, or None for default. Defaults to None. + justify (JustifyMethod, optional): Alignment for cells. Defaults to "left". + vertical (VerticalAlignMethod, optional): Vertical alignment, one of "top", "middle", or "bottom". Defaults to "top". + overflow (OverflowMethod): Overflow method: "crop", "fold", "ellipsis". Defaults to "ellipsis". + width (int, optional): Desired width of column in characters, or None to fit to contents. Defaults to None. + min_width (Optional[int], optional): Minimum width of column, or ``None`` for no minimum. Defaults to None. + max_width (Optional[int], optional): Maximum width of column, or ``None`` for no maximum. Defaults to None. + ratio (int, optional): Flexible ratio for the column (requires ``Table.expand`` or ``Table.width``). Defaults to None. + no_wrap (bool, optional): Set to ``True`` to disable wrapping of this column. + """ + + column = Column( + _index=len(self.columns), + header=header, + footer=footer, + header_style=header_style or "", + highlight=highlight if highlight is not None else self.highlight, + footer_style=footer_style or "", + style=style or "", + justify=justify, + vertical=vertical, + overflow=overflow, + width=width, + min_width=min_width, + max_width=max_width, + ratio=ratio, + no_wrap=no_wrap, + ) + self.columns.append(column) + + def add_row( + self, + *renderables: Optional["RenderableType"], + style: Optional[StyleType] = None, + end_section: bool = False, + ) -> None: + """Add a row of renderables. + + Args: + *renderables (None or renderable): Each cell in a row must be a renderable object (including str), + or ``None`` for a blank cell. + style (StyleType, optional): An optional style to apply to the entire row. Defaults to None. + end_section (bool, optional): End a section and draw a line. Defaults to False. + + Raises: + errors.NotRenderableError: If you add something that can't be rendered. + """ + + def add_cell(column: Column, renderable: "RenderableType") -> None: + column._cells.append(renderable) + + cell_renderables: List[Optional["RenderableType"]] = list(renderables) + + columns = self.columns + if len(cell_renderables) < len(columns): + cell_renderables = [ + *cell_renderables, + *[None] * (len(columns) - len(cell_renderables)), + ] + for index, renderable in enumerate(cell_renderables): + if index == len(columns): + column = Column(_index=index, highlight=self.highlight) + for _ in self.rows: + add_cell(column, Text("")) + self.columns.append(column) + else: + column = columns[index] + if renderable is None: + add_cell(column, "") + elif is_renderable(renderable): + add_cell(column, renderable) + else: + raise errors.NotRenderableError( + f"unable to render {type(renderable).__name__}; a string or other renderable object is required" + ) + self.rows.append(Row(style=style, end_section=end_section)) + + def add_section(self) -> None: + """Add a new section (draw a line after current row).""" + + if self.rows: + self.rows[-1].end_section = True + + def __rich_console__( + self, console: "Console", options: "ConsoleOptions" + ) -> "RenderResult": + if not self.columns: + yield Segment("\n") + return + + max_width = options.max_width + if self.width is not None: + max_width = self.width + + extra_width = self._extra_width + + widths = self._calculate_column_widths( + console, options.update_width(max_width - extra_width) + ) + table_width = sum(widths) + extra_width + + render_options = options.update( + width=table_width, highlight=self.highlight, height=None + ) + + def render_annotation( + text: TextType, style: StyleType, justify: "JustifyMethod" = "center" + ) -> "RenderResult": + render_text = ( + console.render_str(text, style=style, highlight=False) + if isinstance(text, str) + else text + ) + return console.render( + render_text, options=render_options.update(justify=justify) + ) + + if self.title: + yield from render_annotation( + self.title, + style=Style.pick_first(self.title_style, "table.title"), + justify=self.title_justify, + ) + yield from self._render(console, render_options, widths) + if self.caption: + yield from render_annotation( + self.caption, + style=Style.pick_first(self.caption_style, "table.caption"), + justify=self.caption_justify, + ) + + def _calculate_column_widths( + self, console: "Console", options: "ConsoleOptions" + ) -> List[int]: + """Calculate the widths of each column, including padding, not including borders.""" + max_width = options.max_width + columns = self.columns + width_ranges = [ + self._measure_column(console, options, column) for column in columns + ] + widths = [_range.maximum or 1 for _range in width_ranges] + + get_padding_width = self._get_padding_width + extra_width = self._extra_width + if self.expand: + ratios = [col.ratio or 0 for col in columns if col.flexible] + if any(ratios): + fixed_widths = [ + 0 if column.flexible else _range.maximum + for _range, column in zip(width_ranges, columns) + ] + flex_minimum = [ + (column.width or 1) + get_padding_width(column._index) + for column in columns + if column.flexible + ] + flexible_width = max_width - sum(fixed_widths) + flex_widths = ratio_distribute(flexible_width, ratios, flex_minimum) + iter_flex_widths = iter(flex_widths) + for index, column in enumerate(columns): + if column.flexible: + widths[index] = fixed_widths[index] + next(iter_flex_widths) + table_width = sum(widths) + + if table_width > max_width: + widths = self._collapse_widths( + widths, + [(column.width is None and not column.no_wrap) for column in columns], + max_width, + ) + table_width = sum(widths) + # last resort, reduce columns evenly + if table_width > max_width: + excess_width = table_width - max_width + widths = ratio_reduce(excess_width, [1] * len(widths), widths, widths) + table_width = sum(widths) + + width_ranges = [ + self._measure_column(console, options.update_width(width), column) + for width, column in zip(widths, columns) + ] + widths = [_range.maximum or 0 for _range in width_ranges] + + if (table_width < max_width and self.expand) or ( + self.min_width is not None and table_width < (self.min_width - extra_width) + ): + _max_width = ( + max_width + if self.min_width is None + else min(self.min_width - extra_width, max_width) + ) + pad_widths = ratio_distribute(_max_width - table_width, widths) + widths = [_width + pad for _width, pad in zip(widths, pad_widths)] + + return widths + + @classmethod + def _collapse_widths( + cls, widths: List[int], wrapable: List[bool], max_width: int + ) -> List[int]: + """Reduce widths so that the total is under max_width. + + Args: + widths (List[int]): List of widths. + wrapable (List[bool]): List of booleans that indicate if a column may shrink. + max_width (int): Maximum width to reduce to. + + Returns: + List[int]: A new list of widths. + """ + total_width = sum(widths) + excess_width = total_width - max_width + if any(wrapable): + while total_width and excess_width > 0: + max_column = max( + width for width, allow_wrap in zip(widths, wrapable) if allow_wrap + ) + second_max_column = max( + width if allow_wrap and width != max_column else 0 + for width, allow_wrap in zip(widths, wrapable) + ) + column_difference = max_column - second_max_column + ratios = [ + (1 if (width == max_column and allow_wrap) else 0) + for width, allow_wrap in zip(widths, wrapable) + ] + if not any(ratios) or not column_difference: + break + max_reduce = [min(excess_width, column_difference)] * len(widths) + widths = ratio_reduce(excess_width, ratios, max_reduce, widths) + + total_width = sum(widths) + excess_width = total_width - max_width + return widths + + def _get_cells( + self, console: "Console", column_index: int, column: Column + ) -> Iterable[_Cell]: + """Get all the cells with padding and optional header.""" + + collapse_padding = self.collapse_padding + pad_edge = self.pad_edge + padding = self.padding + any_padding = any(padding) + + first_column = column_index == 0 + last_column = column_index == len(self.columns) - 1 + + _padding_cache: Dict[Tuple[bool, bool], Tuple[int, int, int, int]] = {} + + def get_padding(first_row: bool, last_row: bool) -> Tuple[int, int, int, int]: + cached = _padding_cache.get((first_row, last_row)) + if cached: + return cached + top, right, bottom, left = padding + + if collapse_padding: + if not first_column: + left = max(0, left - right) + if not last_row: + bottom = max(0, top - bottom) + + if not pad_edge: + if first_column: + left = 0 + if last_column: + right = 0 + if first_row: + top = 0 + if last_row: + bottom = 0 + _padding = (top, right, bottom, left) + _padding_cache[(first_row, last_row)] = _padding + return _padding + + raw_cells: List[Tuple[StyleType, "RenderableType"]] = [] + _append = raw_cells.append + get_style = console.get_style + if self.show_header: + header_style = get_style(self.header_style or "") + get_style( + column.header_style + ) + _append((header_style, column.header)) + cell_style = get_style(column.style or "") + for cell in column.cells: + _append((cell_style, cell)) + if self.show_footer: + footer_style = get_style(self.footer_style or "") + get_style( + column.footer_style + ) + _append((footer_style, column.footer)) + + if any_padding: + _Padding = Padding + for first, last, (style, renderable) in loop_first_last(raw_cells): + yield _Cell( + style, + _Padding(renderable, get_padding(first, last)), + getattr(renderable, "vertical", None) or column.vertical, + ) + else: + for style, renderable in raw_cells: + yield _Cell( + style, + renderable, + getattr(renderable, "vertical", None) or column.vertical, + ) + + def _get_padding_width(self, column_index: int) -> int: + """Get extra width from padding.""" + _, pad_right, _, pad_left = self.padding + + if self.collapse_padding: + pad_left = 0 + pad_right = abs(pad_left - pad_right) + + if not self.pad_edge: + if column_index == 0: + pad_left = 0 + if column_index == len(self.columns) - 1: + pad_right = 0 + + return pad_left + pad_right + + def _measure_column( + self, + console: "Console", + options: "ConsoleOptions", + column: Column, + ) -> Measurement: + """Get the minimum and maximum width of the column.""" + + max_width = options.max_width + if max_width < 1: + return Measurement(0, 0) + + padding_width = self._get_padding_width(column._index) + if column.width is not None: + # Fixed width column + return Measurement( + column.width + padding_width, column.width + padding_width + ).with_maximum(max_width) + # Flexible column, we need to measure contents + min_widths: List[int] = [] + max_widths: List[int] = [] + append_min = min_widths.append + append_max = max_widths.append + get_render_width = Measurement.get + for cell in self._get_cells(console, column._index, column): + _min, _max = get_render_width(console, options, cell.renderable) + append_min(_min) + append_max(_max) + + measurement = Measurement( + max(min_widths) if min_widths else 1, + max(max_widths) if max_widths else max_width, + ).with_maximum(max_width) + measurement = measurement.clamp( + None if column.min_width is None else column.min_width + padding_width, + None if column.max_width is None else column.max_width + padding_width, + ) + return measurement + + def _render( + self, console: "Console", options: "ConsoleOptions", widths: List[int] + ) -> "RenderResult": + table_style = console.get_style(self.style or "") + + border_style = table_style + console.get_style(self.border_style or "") + _column_cells = ( + self._get_cells(console, column_index, column) + for column_index, column in enumerate(self.columns) + ) + + row_cells: List[Tuple[_Cell, ...]] = list(zip(*_column_cells)) + _box = ( + self.box.substitute( + options, safe=pick_bool(self.safe_box, console.safe_box) + ) + if self.box + else None + ) + _box = _box.get_plain_headed_box() if _box and not self.show_header else _box + + new_line = Segment.line() + + columns = self.columns + show_header = self.show_header + show_footer = self.show_footer + show_edge = self.show_edge + show_lines = self.show_lines + leading = self.leading + + _Segment = Segment + if _box: + box_segments = [ + ( + _Segment(_box.head_left, border_style), + _Segment(_box.head_right, border_style), + _Segment(_box.head_vertical, border_style), + ), + ( + _Segment(_box.mid_left, border_style), + _Segment(_box.mid_right, border_style), + _Segment(_box.mid_vertical, border_style), + ), + ( + _Segment(_box.foot_left, border_style), + _Segment(_box.foot_right, border_style), + _Segment(_box.foot_vertical, border_style), + ), + ] + if show_edge: + yield _Segment(_box.get_top(widths), border_style) + yield new_line + else: + box_segments = [] + + get_row_style = self.get_row_style + get_style = console.get_style + + for index, (first, last, row_cell) in enumerate(loop_first_last(row_cells)): + header_row = first and show_header + footer_row = last and show_footer + row = ( + self.rows[index - show_header] + if (not header_row and not footer_row) + else None + ) + max_height = 1 + cells: List[List[List[Segment]]] = [] + if header_row or footer_row: + row_style = Style.null() + else: + row_style = get_style( + get_row_style(console, index - 1 if show_header else index) + ) + for width, cell, column in zip(widths, row_cell, columns): + render_options = options.update( + width=width, + justify=column.justify, + no_wrap=column.no_wrap, + overflow=column.overflow, + height=None, + highlight=column.highlight, + ) + lines = console.render_lines( + cell.renderable, + render_options, + style=get_style(cell.style) + row_style, + ) + max_height = max(max_height, len(lines)) + cells.append(lines) + + row_height = max(len(cell) for cell in cells) + + def align_cell( + cell: List[List[Segment]], + vertical: "VerticalAlignMethod", + width: int, + style: Style, + ) -> List[List[Segment]]: + if header_row: + vertical = "bottom" + elif footer_row: + vertical = "top" + + if vertical == "top": + return _Segment.align_top(cell, width, row_height, style) + elif vertical == "middle": + return _Segment.align_middle(cell, width, row_height, style) + return _Segment.align_bottom(cell, width, row_height, style) + + cells[:] = [ + _Segment.set_shape( + align_cell( + cell, + _cell.vertical, + width, + get_style(_cell.style) + row_style, + ), + width, + max_height, + ) + for width, _cell, cell, column in zip(widths, row_cell, cells, columns) + ] + + if _box: + if last and show_footer: + yield _Segment( + _box.get_row(widths, "foot", edge=show_edge), border_style + ) + yield new_line + left, right, _divider = box_segments[0 if first else (2 if last else 1)] + + # If the column divider is whitespace also style it with the row background + divider = ( + _divider + if _divider.text.strip() + else _Segment( + _divider.text, row_style.background_style + _divider.style + ) + ) + for line_no in range(max_height): + if show_edge: + yield left + for last_cell, rendered_cell in loop_last(cells): + yield from rendered_cell[line_no] + if not last_cell: + yield divider + if show_edge: + yield right + yield new_line + else: + for line_no in range(max_height): + for rendered_cell in cells: + yield from rendered_cell[line_no] + yield new_line + if _box and first and show_header: + yield _Segment( + _box.get_row(widths, "head", edge=show_edge), border_style + ) + yield new_line + end_section = row and row.end_section + if _box and (show_lines or leading or end_section): + if ( + not last + and not (show_footer and index >= len(row_cells) - 2) + and not (show_header and header_row) + ): + if leading: + yield _Segment( + _box.get_row(widths, "mid", edge=show_edge) * leading, + border_style, + ) + else: + yield _Segment( + _box.get_row(widths, "row", edge=show_edge), border_style + ) + yield new_line + + if _box and show_edge: + yield _Segment(_box.get_bottom(widths), border_style) + yield new_line + + +if __name__ == "__main__": # pragma: no cover + from rich.console import Console + from rich.highlighter import ReprHighlighter + + from ._timer import timer + + with timer("Table render"): + table = Table( + title="Star Wars Movies", + caption="Rich example table", + caption_justify="right", + ) + + table.add_column( + "Released", header_style="bright_cyan", style="cyan", no_wrap=True + ) + table.add_column("Title", style="magenta") + table.add_column("Box Office", justify="right", style="green") + + table.add_row( + "Dec 20, 2019", + "Star Wars: The Rise of Skywalker", + "$952,110,690", + ) + table.add_row("May 25, 2018", "Solo: A Star Wars Story", "$393,151,347") + table.add_row( + "Dec 15, 2017", + "Star Wars Ep. V111: The Last Jedi", + "$1,332,539,889", + style="on black", + end_section=True, + ) + table.add_row( + "Dec 16, 2016", + "Rogue One: A Star Wars Story", + "$1,332,439,889", + ) + + def header(text: str) -> None: + console.print() + console.rule(highlight(text)) + console.print() + + console = Console() + highlight = ReprHighlighter() + header("Example Table") + console.print(table, justify="center") + + table.expand = True + header("expand=True") + console.print(table) + + table.width = 50 + header("width=50") + + console.print(table, justify="center") + + table.width = None + table.expand = False + table.row_styles = ["dim", "none"] + header("row_styles=['dim', 'none']") + + console.print(table, justify="center") + + table.width = None + table.expand = False + table.row_styles = ["dim", "none"] + table.leading = 1 + header("leading=1, row_styles=['dim', 'none']") + console.print(table, justify="center") + + table.width = None + table.expand = False + table.row_styles = ["dim", "none"] + table.show_lines = True + table.leading = 0 + header("show_lines=True, row_styles=['dim', 'none']") + console.print(table, justify="center") diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/shellingham-1.5.4.dist-info/METADATA b/bioagent-bench/.venv/lib/python3.14/site-packages/shellingham-1.5.4.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..52118f1e5c83bd7ef39196a749651fc87d176812 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/shellingham-1.5.4.dist-info/METADATA @@ -0,0 +1,106 @@ +Metadata-Version: 2.1 +Name: shellingham +Version: 1.5.4 +Summary: Tool to Detect Surrounding Shell +Home-page: https://github.com/sarugaku/shellingham +Author: Tzu-ping Chung +Author-email: uranusjr@gmail.com +License: ISC License +Keywords: shell +Classifier: Development Status :: 3 - Alpha +Classifier: Environment :: Console +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: ISC License (ISCL) +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Requires-Python: >=3.7 +Description-Content-Type: text/x-rst +License-File: LICENSE + +============================================= +Shellingham: Tool to Detect Surrounding Shell +============================================= + +.. image:: https://img.shields.io/pypi/v/shellingham.svg + :target: https://pypi.org/project/shellingham/ + +Shellingham detects what shell the current Python executable is running in. + + +Usage +===== + +.. code-block:: python + + >>> import shellingham + >>> shellingham.detect_shell() + ('bash', '/bin/bash') + +``detect_shell`` pokes around the process's running environment to determine +what shell it is run in. It returns a 2-tuple: + +* The shell name, always lowercased. +* The command used to run the shell. + +``ShellDetectionFailure`` is raised if ``detect_shell`` fails to detect the +surrounding shell. + + +Notes +===== + +* The shell name is always lowercased. +* On Windows, the shell name is the name of the executable, minus the file + extension. + + +Notes for Application Developers +================================ + +Remember, your application's user is not necessarily using a shell. +Shellingham raises ``ShellDetectionFailure`` if there is no shell to detect, +but *your application should almost never do this to your user*. + +A practical approach to this is to wrap ``detect_shell`` in a try block, and +provide a sane default on failure + +.. code-block:: python + + try: + shell = shellingham.detect_shell() + except shellingham.ShellDetectionFailure: + shell = provide_default() + + +There are a few choices for you to choose from. + +* The POSIX standard mandates the environment variable ``SHELL`` to refer to + "the user's preferred command language interpreter". This is always available + (even if the user is not in an interactive session), and likely the correct + choice to launch an interactive sub-shell with. +* A command ``sh`` is almost guaranteed to exist, likely at ``/bin/sh``, since + several POSIX tools rely on it. This should be suitable if you want to run a + (possibly non-interactive) script. +* All versions of DOS and Windows have an environment variable ``COMSPEC``. + This can always be used to launch a usable command prompt (e.g. `cmd.exe` on + Windows). + +Here's a simple implementation to provide a default shell + +.. code-block:: python + + import os + + def provide_default(): + if os.name == 'posix': + return os.environ['SHELL'] + elif os.name == 'nt': + return os.environ['COMSPEC'] + raise NotImplementedError(f'OS {os.name!r} support not available') diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..8081f77b8812f3b42d7949daa4195d2c35dc70ac --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/__init__.py @@ -0,0 +1,38 @@ +from ._monitor import TMonitor, TqdmSynchronisationWarning +from ._tqdm_pandas import tqdm_pandas +from .cli import main # TODO: remove in v5.0.0 +from .gui import tqdm as tqdm_gui # TODO: remove in v5.0.0 +from .gui import trange as tgrange # TODO: remove in v5.0.0 +from .std import ( + TqdmDeprecationWarning, TqdmExperimentalWarning, TqdmKeyError, TqdmMonitorWarning, + TqdmTypeError, TqdmWarning, tqdm, trange) +from .version import __version__ + +__all__ = ['tqdm', 'tqdm_gui', 'trange', 'tgrange', 'tqdm_pandas', + 'tqdm_notebook', 'tnrange', 'main', 'TMonitor', + 'TqdmTypeError', 'TqdmKeyError', + 'TqdmWarning', 'TqdmDeprecationWarning', + 'TqdmExperimentalWarning', + 'TqdmMonitorWarning', 'TqdmSynchronisationWarning', + '__version__'] + + +def tqdm_notebook(*args, **kwargs): # pragma: no cover + """See tqdm.notebook.tqdm for full documentation""" + from warnings import warn + + from .notebook import tqdm as _tqdm_notebook + warn("This function will be removed in tqdm==5.0.0\n" + "Please use `tqdm.notebook.tqdm` instead of `tqdm.tqdm_notebook`", + TqdmDeprecationWarning, stacklevel=2) + return _tqdm_notebook(*args, **kwargs) + + +def tnrange(*args, **kwargs): # pragma: no cover + """Shortcut for `tqdm.notebook.tqdm(range(*args), **kwargs)`.""" + from warnings import warn + + from .notebook import trange as _tnrange + warn("Please use `tqdm.notebook.trange` instead of `tqdm.tnrange`", + TqdmDeprecationWarning, stacklevel=2) + return _tnrange(*args, **kwargs) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/__main__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/__main__.py new file mode 100644 index 0000000000000000000000000000000000000000..4e28416e104515e90fca4b69cc60d0c61fd15d61 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/__main__.py @@ -0,0 +1,3 @@ +from .cli import main + +main() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/_main.py b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/_main.py new file mode 100644 index 0000000000000000000000000000000000000000..04fdeeff17b5cc84b210f445b54b87d5b99e3748 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/_main.py @@ -0,0 +1,9 @@ +from warnings import warn + +from .cli import * # NOQA +from .cli import __all__ # NOQA +from .std import TqdmDeprecationWarning + +warn("This function will be removed in tqdm==5.0.0\n" + "Please use `tqdm.cli.*` instead of `tqdm._main.*`", + TqdmDeprecationWarning, stacklevel=2) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/_monitor.py b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/_monitor.py new file mode 100644 index 0000000000000000000000000000000000000000..f71aa56817ca77eba5df4a2dd11cb0c4a9a7ea1c --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/_monitor.py @@ -0,0 +1,95 @@ +import atexit +from threading import Event, Thread, current_thread +from time import time +from warnings import warn + +__all__ = ["TMonitor", "TqdmSynchronisationWarning"] + + +class TqdmSynchronisationWarning(RuntimeWarning): + """tqdm multi-thread/-process errors which may cause incorrect nesting + but otherwise no adverse effects""" + pass + + +class TMonitor(Thread): + """ + Monitoring thread for tqdm bars. + Monitors if tqdm bars are taking too much time to display + and readjusts miniters automatically if necessary. + + Parameters + ---------- + tqdm_cls : class + tqdm class to use (can be core tqdm or a submodule). + sleep_interval : float + Time to sleep between monitoring checks. + """ + _test = {} # internal vars for unit testing + + def __init__(self, tqdm_cls, sleep_interval): + Thread.__init__(self) + self.daemon = True # kill thread when main killed (KeyboardInterrupt) + self.woken = 0 # last time woken up, to sync with monitor + self.tqdm_cls = tqdm_cls + self.sleep_interval = sleep_interval + self._time = self._test.get("time", time) + self.was_killed = self._test.get("Event", Event)() + atexit.register(self.exit) + self.start() + + def exit(self): + self.was_killed.set() + if self is not current_thread(): + self.join() + return self.report() + + def get_instances(self): + # returns a copy of started `tqdm_cls` instances + return [i for i in self.tqdm_cls._instances.copy() + # Avoid race by checking that the instance started + if hasattr(i, 'start_t')] + + def run(self): + cur_t = self._time() + while True: + # After processing and before sleeping, notify that we woke + # Need to be done just before sleeping + self.woken = cur_t + # Sleep some time... + self.was_killed.wait(self.sleep_interval) + # Quit if killed + if self.was_killed.is_set(): + return + # Then monitor! + # Acquire lock (to access _instances) + with self.tqdm_cls.get_lock(): + cur_t = self._time() + # Check tqdm instances are waiting too long to print + instances = self.get_instances() + for instance in instances: + # Check event in loop to reduce blocking time on exit + if self.was_killed.is_set(): + return + # Only if mininterval > 1 (else iterations are just slow) + # and last refresh exceeded maxinterval + if ( + instance.miniters > 1 + and (cur_t - instance.last_print_t) >= instance.maxinterval + ): + # force bypassing miniters on next iteration + # (dynamic_miniters adjusts mininterval automatically) + instance.miniters = 1 + # Refresh now! (works only for manual tqdm) + instance.refresh(nolock=True) + # Remove accidental long-lived strong reference + del instance + if instances != self.get_instances(): # pragma: nocover + warn("Set changed size during iteration" + + " (see https://github.com/tqdm/tqdm/issues/481)", + TqdmSynchronisationWarning, stacklevel=2) + # Remove accidental long-lived strong references + del instances + + def report(self): + return not self.was_killed.is_set() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/_tqdm.py b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/_tqdm.py new file mode 100644 index 0000000000000000000000000000000000000000..7fc4962774a4651db7a739a3f143633b6215a9bd --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/_tqdm.py @@ -0,0 +1,9 @@ +from warnings import warn + +from .std import * # NOQA +from .std import __all__ # NOQA +from .std import TqdmDeprecationWarning + +warn("This function will be removed in tqdm==5.0.0\n" + "Please use `tqdm.std.*` instead of `tqdm._tqdm.*`", + TqdmDeprecationWarning, stacklevel=2) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/_tqdm_gui.py b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/_tqdm_gui.py new file mode 100644 index 0000000000000000000000000000000000000000..f32aa894f54b3a5b47a0fbf4263c2fd20df56c9d --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/_tqdm_gui.py @@ -0,0 +1,9 @@ +from warnings import warn + +from .gui import * # NOQA +from .gui import __all__ # NOQA +from .std import TqdmDeprecationWarning + +warn("This function will be removed in tqdm==5.0.0\n" + "Please use `tqdm.gui.*` instead of `tqdm._tqdm_gui.*`", + TqdmDeprecationWarning, stacklevel=2) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/_tqdm_notebook.py b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/_tqdm_notebook.py new file mode 100644 index 0000000000000000000000000000000000000000..f225fbf5b52d04987ccf68f4d5ee4b735e3158b0 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/_tqdm_notebook.py @@ -0,0 +1,9 @@ +from warnings import warn + +from .notebook import * # NOQA +from .notebook import __all__ # NOQA +from .std import TqdmDeprecationWarning + +warn("This function will be removed in tqdm==5.0.0\n" + "Please use `tqdm.notebook.*` instead of `tqdm._tqdm_notebook.*`", + TqdmDeprecationWarning, stacklevel=2) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/_tqdm_pandas.py b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/_tqdm_pandas.py new file mode 100644 index 0000000000000000000000000000000000000000..c4fe6efdc603579e7f8acfa27ac10dccdf3e94ce --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/_tqdm_pandas.py @@ -0,0 +1,24 @@ +import sys + +__author__ = "github.com/casperdcl" +__all__ = ['tqdm_pandas'] + + +def tqdm_pandas(tclass, **tqdm_kwargs): + """ + Registers the given `tqdm` instance with + `pandas.core.groupby.DataFrameGroupBy.progress_apply`. + """ + from tqdm import TqdmDeprecationWarning + + if isinstance(tclass, type) or (getattr(tclass, '__name__', '').startswith( + 'tqdm_')): # delayed adapter case + TqdmDeprecationWarning( + "Please use `tqdm.pandas(...)` instead of `tqdm_pandas(tqdm, ...)`.", + fp_write=getattr(tqdm_kwargs.get('file', None), 'write', sys.stderr.write)) + tclass.pandas(**tqdm_kwargs) + else: + TqdmDeprecationWarning( + "Please use `tqdm.pandas(...)` instead of `tqdm_pandas(tqdm(...))`.", + fp_write=getattr(tclass.fp, 'write', sys.stderr.write)) + type(tclass).pandas(deprecated_t=tclass) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/_utils.py b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/_utils.py new file mode 100644 index 0000000000000000000000000000000000000000..385e849e106d1319fe21045f14eb0aa6552fb153 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/_utils.py @@ -0,0 +1,11 @@ +from warnings import warn + +from .std import TqdmDeprecationWarning +from .utils import ( # NOQA, pylint: disable=unused-import + CUR_OS, IS_NIX, IS_WIN, RE_ANSI, Comparable, FormatReplace, SimpleTextIOWrapper, + _environ_cols_wrapper, _is_ascii, _is_utf, _screen_shape_linux, _screen_shape_tput, + _screen_shape_windows, _screen_shape_wrapper, _supports_unicode, _term_move_up, colorama) + +warn("This function will be removed in tqdm==5.0.0\n" + "Please use `tqdm.utils.*` instead of `tqdm._utils.*`", + TqdmDeprecationWarning, stacklevel=2) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/asyncio.py b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/asyncio.py new file mode 100644 index 0000000000000000000000000000000000000000..2d00a0a2e755f36068d079ccc12ca84d86ff42be --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/asyncio.py @@ -0,0 +1,93 @@ +""" +Asynchronous progressbar decorator for iterators. +Includes a default `range` iterator printing to `stderr`. + +Usage: +>>> from tqdm.asyncio import trange, tqdm +>>> async for i in trange(10): +... ... +""" +import asyncio +from sys import version_info + +from .std import tqdm as std_tqdm + +__author__ = {"github.com/": ["casperdcl"]} +__all__ = ['tqdm_asyncio', 'tarange', 'tqdm', 'trange'] + + +class tqdm_asyncio(std_tqdm): + """ + Asynchronous-friendly version of tqdm. + """ + def __init__(self, iterable=None, *args, **kwargs): + super().__init__(iterable, *args, **kwargs) + self.iterable_awaitable = False + if iterable is not None: + if hasattr(iterable, "__anext__"): + self.iterable_next = iterable.__anext__ + self.iterable_awaitable = True + elif hasattr(iterable, "__next__"): + self.iterable_next = iterable.__next__ + else: + self.iterable_iterator = iter(iterable) + self.iterable_next = self.iterable_iterator.__next__ + + def __aiter__(self): + return self + + async def __anext__(self): + try: + if self.iterable_awaitable: + res = await self.iterable_next() + else: + res = self.iterable_next() + self.update() + return res + except StopIteration: + self.close() + raise StopAsyncIteration + except BaseException: + self.close() + raise + + def send(self, *args, **kwargs): + return self.iterable.send(*args, **kwargs) + + @classmethod + def as_completed(cls, fs, *, loop=None, timeout=None, total=None, **tqdm_kwargs): + """ + Wrapper for `asyncio.as_completed`. + """ + if total is None: + total = len(fs) + kwargs = {} + if version_info[:2] < (3, 10): + kwargs['loop'] = loop + yield from cls(asyncio.as_completed(fs, timeout=timeout, **kwargs), + total=total, **tqdm_kwargs) + + @classmethod + async def gather(cls, *fs, loop=None, timeout=None, total=None, **tqdm_kwargs): + """ + Wrapper for `asyncio.gather`. + """ + async def wrap_awaitable(i, f): + return i, await f + + ifs = [wrap_awaitable(i, f) for i, f in enumerate(fs)] + res = [await f for f in cls.as_completed(ifs, loop=loop, timeout=timeout, + total=total, **tqdm_kwargs)] + return [i for _, i in sorted(res)] + + +def tarange(*args, **kwargs): + """ + A shortcut for `tqdm.asyncio.tqdm(range(*args), **kwargs)`. + """ + return tqdm_asyncio(range(*args), **kwargs) + + +# Aliases +tqdm = tqdm_asyncio +trange = tarange diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/auto.py b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/auto.py new file mode 100644 index 0000000000000000000000000000000000000000..206c4409d5269594bdbab3a092ef6e09e7c01947 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/auto.py @@ -0,0 +1,40 @@ +""" +Enables multiple commonly used features. + +Method resolution order: + +- `tqdm.autonotebook` without import warnings +- `tqdm.asyncio` +- `tqdm.std` base class + +Usage: +>>> from tqdm.auto import trange, tqdm +>>> for i in trange(10): +... ... +""" +import warnings + +from .std import TqdmExperimentalWarning + +with warnings.catch_warnings(): + warnings.simplefilter("ignore", category=TqdmExperimentalWarning) + from .autonotebook import tqdm as notebook_tqdm + +from .asyncio import tqdm as asyncio_tqdm +from .std import tqdm as std_tqdm + +if notebook_tqdm != std_tqdm: + class tqdm(notebook_tqdm, asyncio_tqdm): # pylint: disable=inconsistent-mro + pass +else: + tqdm = asyncio_tqdm + + +def trange(*args, **kwargs): + """ + A shortcut for `tqdm.auto.tqdm(range(*args), **kwargs)`. + """ + return tqdm(range(*args), **kwargs) + + +__all__ = ["tqdm", "trange"] diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/autonotebook.py b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/autonotebook.py new file mode 100644 index 0000000000000000000000000000000000000000..a09f2ec4b8c95f12b8c7b7774f84d5ec55826334 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/autonotebook.py @@ -0,0 +1,29 @@ +""" +Automatically choose between `tqdm.notebook` and `tqdm.std`. + +Usage: +>>> from tqdm.autonotebook import trange, tqdm +>>> for i in trange(10): +... ... +""" +import sys +from warnings import warn + +try: + get_ipython = sys.modules['IPython'].get_ipython + if 'IPKernelApp' not in get_ipython().config: # pragma: no cover + raise ImportError("console") + from .notebook import WARN_NOIPYW, IProgress + if IProgress is None: + from .std import TqdmWarning + warn(WARN_NOIPYW, TqdmWarning, stacklevel=2) + raise ImportError('ipywidgets') +except Exception: + from .std import tqdm, trange +else: # pragma: no cover + from .notebook import tqdm, trange + from .std import TqdmExperimentalWarning + warn("Using `tqdm.autonotebook.tqdm` in notebook mode." + " Use `tqdm.tqdm` instead to force console mode" + " (e.g. in jupyter console)", TqdmExperimentalWarning, stacklevel=2) +__all__ = ["tqdm", "trange"] diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/cli.py b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/cli.py new file mode 100644 index 0000000000000000000000000000000000000000..1dd08f6053b992274dc8242a4e81ec7e66ddd937 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/cli.py @@ -0,0 +1,324 @@ +""" +Module version for monitoring CLI pipes (`... | python -m tqdm | ...`). +""" +import logging +import re +import sys +from ast import literal_eval as numeric +from textwrap import indent + +from .std import TqdmKeyError, TqdmTypeError, tqdm +from .version import __version__ + +__all__ = ["main"] +log = logging.getLogger(__name__) + + +def cast(val, typ): + log.debug((val, typ)) + if " or " in typ: + for t in typ.split(" or "): + try: + return cast(val, t) + except TqdmTypeError: + pass + raise TqdmTypeError(f"{val} : {typ}") + + # sys.stderr.write('\ndebug | `val:type`: `' + val + ':' + typ + '`.\n') + if typ == 'bool': + if (val == 'True') or (val == ''): + return True + if val == 'False': + return False + raise TqdmTypeError(val + ' : ' + typ) + if typ == 'chr': + if len(val) == 1: + return val.encode() + if re.match(r"^\\\w+$", val): + return eval(f'"{val}"').encode() + raise TqdmTypeError(f"{val} : {typ}") + if typ == 'str': + return val + if typ == 'int': + try: + return int(val) + except ValueError as exc: + raise TqdmTypeError(f"{val} : {typ}") from exc + if typ == 'float': + try: + return float(val) + except ValueError as exc: + raise TqdmTypeError(f"{val} : {typ}") from exc + raise TqdmTypeError(f"{val} : {typ}") + + +def posix_pipe(fin, fout, delim=b'\\n', buf_size=256, + callback=lambda float: None, callback_len=True): + """ + Params + ------ + fin : binary file with `read(buf_size : int)` method + fout : binary file with `write` (and optionally `flush`) methods. + callback : function(float), e.g.: `tqdm.update` + callback_len : If (default: True) do `callback(len(buffer))`. + Otherwise, do `callback(data) for data in buffer.split(delim)`. + """ + fp_write = fout.write + + if not delim: + while True: + tmp = fin.read(buf_size) + + # flush at EOF + if not tmp: + getattr(fout, 'flush', lambda: None)() + return + + fp_write(tmp) + callback(len(tmp)) + # return + + buf = b'' + len_delim = len(delim) + # n = 0 + while True: + tmp = fin.read(buf_size) + + # flush at EOF + if not tmp: + if buf: + fp_write(buf) + if callback_len: + # n += 1 + buf.count(delim) + callback(1 + buf.count(delim)) + else: + for i in buf.split(delim): + callback(i) + getattr(fout, 'flush', lambda: None)() + return # n + + while True: + i = tmp.find(delim) + if i < 0: + buf += tmp + break + fp_write(buf + tmp[:i + len(delim)]) + # n += 1 + callback(1 if callback_len else (buf + tmp[:i])) + buf = b'' + tmp = tmp[i + len_delim:] + + +# ((opt, type), ... ) +RE_OPTS = re.compile(r'\n {4}(\S+)\s{2,}:\s*([^,]+)') +# better split method assuming no positional args +RE_SHLEX = re.compile(r'\s*(? : \2', d) + split = RE_OPTS.split(d) + opt_types_desc = zip(split[1::3], split[2::3], split[3::3]) + d = ''.join(('\n --{0} : {2}{3}' if otd[1] == 'bool' else + '\n --{0}=<{1}> : {2}{3}').format( + otd[0].replace('_', '-'), otd[0], *otd[1:]) + for otd in opt_types_desc if otd[0] not in UNSUPPORTED_OPTS) + + help_short = "Usage:\n tqdm [--help | options]\n" + d = help_short + """ +Options: + -h, --help Print this help and exit. + -v, --version Print version and exit. +""" + d.strip('\n') + '\n' + + # opts = docopt(d, version=__version__) + if any(v in argv for v in ('-v', '--version')): + sys.stdout.write(__version__ + '\n') + sys.exit(0) + elif any(v in argv for v in ('-h', '--help')): + sys.stdout.write(d + '\n') + sys.exit(0) + elif argv and argv[0][:2] != '--': + sys.stderr.write(f"Error:Unknown argument:{argv[0]}\n{help_short}") + + argv = RE_SHLEX.split(' '.join(["tqdm"] + argv)) + opts = dict(zip(argv[1::3], argv[3::3])) + + log.debug(opts) + opts.pop('log', True) + + tqdm_args = {'file': fp} + try: + for (o, v) in opts.items(): + o = o.replace('-', '_') + try: + tqdm_args[o] = cast(v, opt_types[o]) + except KeyError as e: + raise TqdmKeyError(str(e)) + log.debug('args:' + str(tqdm_args)) + + delim_per_char = tqdm_args.pop('bytes', False) + update = tqdm_args.pop('update', False) + update_to = tqdm_args.pop('update_to', False) + if sum((delim_per_char, update, update_to)) > 1: + raise TqdmKeyError("Can only have one of --bytes --update --update_to") + except Exception: + fp.write("\nError:\n" + help_short) + stdin, stdout_write = sys.stdin, sys.stdout.write + for i in stdin: + stdout_write(i) + raise + else: + buf_size = tqdm_args.pop('buf_size', 256) + delim = tqdm_args.pop('delim', b'\\n') + tee = tqdm_args.pop('tee', False) + manpath = tqdm_args.pop('manpath', None) + comppath = tqdm_args.pop('comppath', None) + if tqdm_args.pop('null', False): + class stdout: + @staticmethod + def write(_): + pass + else: + stdout = sys.stdout + stdout = getattr(stdout, 'buffer', stdout) + stdin = getattr(sys.stdin, 'buffer', sys.stdin) + if manpath or comppath: + try: # py<3.9 + import importlib_resources as resources + except ImportError: + from importlib import resources + from pathlib import Path + + def cp(name, dst): + """copy resource `name` to `dst`""" + fi = resources.files('tqdm') / name + dst.write_bytes(fi.read_bytes()) + log.info("written:%s", dst) + if manpath is not None: + cp('tqdm.1', Path(manpath) / 'tqdm.1') + if comppath is not None: + cp('completion.sh', Path(comppath) / 'tqdm_completion.sh') + sys.exit(0) + if tee: + stdout_write = stdout.write + fp_write = getattr(fp, 'buffer', fp).write + + class stdout: # pylint: disable=function-redefined + @staticmethod + def write(x): + with tqdm.external_write_mode(file=fp): + fp_write(x) + stdout_write(x) + if delim_per_char: + tqdm_args.setdefault('unit', 'B') + tqdm_args.setdefault('unit_scale', True) + tqdm_args.setdefault('unit_divisor', 1024) + log.debug(tqdm_args) + with tqdm(**tqdm_args) as t: + posix_pipe(stdin, stdout, '', buf_size, t.update) + elif delim == b'\\n': + log.debug(tqdm_args) + write = stdout.write + if update or update_to: + with tqdm(**tqdm_args) as t: + if update: + def callback(i): + t.update(numeric(i.decode())) + else: # update_to + def callback(i): + t.update(numeric(i.decode()) - t.n) + for i in stdin: + write(i) + callback(i) + else: + for i in tqdm(stdin, **tqdm_args): + write(i) + else: + log.debug(tqdm_args) + with tqdm(**tqdm_args) as t: + callback_len = False + if update: + def callback(i): + t.update(numeric(i.decode())) + elif update_to: + def callback(i): + t.update(numeric(i.decode()) - t.n) + else: + callback = t.update + callback_len = True + posix_pipe(stdin, stdout, delim, buf_size, callback, callback_len) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/completion.sh b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/completion.sh new file mode 100644 index 0000000000000000000000000000000000000000..9f61c7f14bb8c1f6099b9eb75dce28ece6a7ae96 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/completion.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +_tqdm(){ + local cur prv + cur="${COMP_WORDS[COMP_CWORD]}" + prv="${COMP_WORDS[COMP_CWORD - 1]}" + + case ${prv} in + --bar_format|--buf_size|--colour|--comppath|--delay|--delim|--desc|--initial|--lock_args|--manpath|--maxinterval|--mininterval|--miniters|--ncols|--nrows|--position|--postfix|--smoothing|--total|--unit|--unit_divisor) + # await user input + ;; + "--log") + COMPREPLY=($(compgen -W 'CRITICAL FATAL ERROR WARN WARNING INFO DEBUG NOTSET' -- ${cur})) + ;; + *) + COMPREPLY=($(compgen -W '--ascii --bar_format --buf_size --bytes --colour --comppath --delay --delim --desc --disable --dynamic_ncols --help --initial --leave --lock_args --log --manpath --maxinterval --mininterval --miniters --ncols --nrows --null --position --postfix --smoothing --tee --total --unit --unit_divisor --unit_scale --update --update_to --version --write_bytes -h -v' -- ${cur})) + ;; + esac +} +complete -F _tqdm tqdm diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/contrib/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/contrib/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..87ec4601fcff138e0882808237375aed93f5ef35 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/contrib/__init__.py @@ -0,0 +1,91 @@ +""" +Thin wrappers around common functions. + +Subpackages contain potentially unstable extensions. +""" +from warnings import warn + +from ..auto import tqdm as tqdm_auto +from ..std import TqdmDeprecationWarning, tqdm +from ..utils import ObjectWrapper + +__author__ = {"github.com/": ["casperdcl"]} +__all__ = ['tenumerate', 'tzip', 'tmap'] + + +class DummyTqdmFile(ObjectWrapper): + """Dummy file-like that will write to tqdm""" + + def __init__(self, wrapped): + super().__init__(wrapped) + self._buf = [] + + def write(self, x, nolock=False): + nl = b"\n" if isinstance(x, bytes) else "\n" + pre, sep, post = x.rpartition(nl) + if sep: + blank = type(nl)() + tqdm.write(blank.join(self._buf + [pre, sep]), + end=blank, file=self._wrapped, nolock=nolock) + self._buf = [post] + else: + self._buf.append(x) + + def __del__(self): + if self._buf: + blank = type(self._buf[0])() + try: + tqdm.write(blank.join(self._buf), end=blank, file=self._wrapped) + except (OSError, ValueError): + pass + + +def builtin_iterable(func): + """Returns `func`""" + warn("This function has no effect, and will be removed in tqdm==5.0.0", + TqdmDeprecationWarning, stacklevel=2) + return func + + +def tenumerate(iterable, start=0, total=None, tqdm_class=tqdm_auto, **tqdm_kwargs): + """ + Equivalent of `numpy.ndenumerate` or builtin `enumerate`. + + Parameters + ---------- + tqdm_class : [default: tqdm.auto.tqdm]. + """ + try: + import numpy as np + except ImportError: + pass + else: + if isinstance(iterable, np.ndarray): + return tqdm_class(np.ndenumerate(iterable), total=total or iterable.size, + **tqdm_kwargs) + return enumerate(tqdm_class(iterable, total=total, **tqdm_kwargs), start) + + +def tzip(iter1, *iter2plus, **tqdm_kwargs): + """ + Equivalent of builtin `zip`. + + Parameters + ---------- + tqdm_class : [default: tqdm.auto.tqdm]. + """ + kwargs = tqdm_kwargs.copy() + tqdm_class = kwargs.pop("tqdm_class", tqdm_auto) + yield from zip(tqdm_class(iter1, **kwargs), *iter2plus) + + +def tmap(function, *sequences, **tqdm_kwargs): + """ + Equivalent of builtin `map`. + + Parameters + ---------- + tqdm_class : [default: tqdm.auto.tqdm]. + """ + for i in tzip(*sequences, **tqdm_kwargs): + yield function(*i) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/contrib/bells.py b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/contrib/bells.py new file mode 100644 index 0000000000000000000000000000000000000000..5b8f4b9ecd894f1edfaa08d9fe730b8d7c8b93e0 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/contrib/bells.py @@ -0,0 +1,26 @@ +""" +Even more features than `tqdm.auto` (all the bells & whistles): + +- `tqdm.auto` +- `tqdm.tqdm.pandas` +- `tqdm.contrib.telegram` + + uses `${TQDM_TELEGRAM_TOKEN}` and `${TQDM_TELEGRAM_CHAT_ID}` +- `tqdm.contrib.discord` + + uses `${TQDM_DISCORD_TOKEN}` and `${TQDM_DISCORD_CHANNEL_ID}` +""" +__all__ = ['tqdm', 'trange'] +import warnings +from os import getenv + +if getenv("TQDM_SLACK_TOKEN") and getenv("TQDM_SLACK_CHANNEL"): + from .slack import tqdm, trange +elif getenv("TQDM_TELEGRAM_TOKEN") and getenv("TQDM_TELEGRAM_CHAT_ID"): + from .telegram import tqdm, trange +elif getenv("TQDM_DISCORD_TOKEN") and getenv("TQDM_DISCORD_CHANNEL_ID"): + from .discord import tqdm, trange +else: + from ..auto import tqdm, trange + +with warnings.catch_warnings(): + warnings.simplefilter("ignore", category=FutureWarning) + tqdm.pandas() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/contrib/concurrent.py b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/contrib/concurrent.py new file mode 100644 index 0000000000000000000000000000000000000000..cd81d622a1309df179042159a56cef4f8c309224 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/contrib/concurrent.py @@ -0,0 +1,105 @@ +""" +Thin wrappers around `concurrent.futures`. +""" +from contextlib import contextmanager +from operator import length_hint +from os import cpu_count + +from ..auto import tqdm as tqdm_auto +from ..std import TqdmWarning + +__author__ = {"github.com/": ["casperdcl"]} +__all__ = ['thread_map', 'process_map'] + + +@contextmanager +def ensure_lock(tqdm_class, lock_name=""): + """get (create if necessary) and then restore `tqdm_class`'s lock""" + old_lock = getattr(tqdm_class, '_lock', None) # don't create a new lock + lock = old_lock or tqdm_class.get_lock() # maybe create a new lock + lock = getattr(lock, lock_name, lock) # maybe subtype + tqdm_class.set_lock(lock) + yield lock + if old_lock is None: + del tqdm_class._lock + else: + tqdm_class.set_lock(old_lock) + + +def _executor_map(PoolExecutor, fn, *iterables, **tqdm_kwargs): + """ + Implementation of `thread_map` and `process_map`. + + Parameters + ---------- + tqdm_class : [default: tqdm.auto.tqdm]. + max_workers : [default: min(32, cpu_count() + 4)]. + chunksize : [default: 1]. + lock_name : [default: "":str]. + """ + kwargs = tqdm_kwargs.copy() + if "total" not in kwargs: + kwargs["total"] = length_hint(iterables[0]) + tqdm_class = kwargs.pop("tqdm_class", tqdm_auto) + max_workers = kwargs.pop("max_workers", min(32, cpu_count() + 4)) + chunksize = kwargs.pop("chunksize", 1) + lock_name = kwargs.pop("lock_name", "") + with ensure_lock(tqdm_class, lock_name=lock_name) as lk: + # share lock in case workers are already using `tqdm` + with PoolExecutor(max_workers=max_workers, initializer=tqdm_class.set_lock, + initargs=(lk,)) as ex: + return list(tqdm_class(ex.map(fn, *iterables, chunksize=chunksize), **kwargs)) + + +def thread_map(fn, *iterables, **tqdm_kwargs): + """ + Equivalent of `list(map(fn, *iterables))` + driven by `concurrent.futures.ThreadPoolExecutor`. + + Parameters + ---------- + tqdm_class : optional + `tqdm` class to use for bars [default: tqdm.auto.tqdm]. + max_workers : int, optional + Maximum number of workers to spawn; passed to + `concurrent.futures.ThreadPoolExecutor.__init__`. + [default: max(32, cpu_count() + 4)]. + """ + from concurrent.futures import ThreadPoolExecutor + return _executor_map(ThreadPoolExecutor, fn, *iterables, **tqdm_kwargs) + + +def process_map(fn, *iterables, **tqdm_kwargs): + """ + Equivalent of `list(map(fn, *iterables))` + driven by `concurrent.futures.ProcessPoolExecutor`. + + Parameters + ---------- + tqdm_class : optional + `tqdm` class to use for bars [default: tqdm.auto.tqdm]. + max_workers : int, optional + Maximum number of workers to spawn; passed to + `concurrent.futures.ProcessPoolExecutor.__init__`. + [default: min(32, cpu_count() + 4)]. + chunksize : int, optional + Size of chunks sent to worker processes; passed to + `concurrent.futures.ProcessPoolExecutor.map`. [default: 1]. + lock_name : str, optional + Member of `tqdm_class.get_lock()` to use [default: mp_lock]. + """ + from concurrent.futures import ProcessPoolExecutor + if iterables and "chunksize" not in tqdm_kwargs: + # default `chunksize=1` has poor performance for large iterables + # (most time spent dispatching items to workers). + longest_iterable_len = max(map(length_hint, iterables)) + if longest_iterable_len > 1000: + from warnings import warn + warn("Iterable length %d > 1000 but `chunksize` is not set." + " This may seriously degrade multiprocess performance." + " Set `chunksize=1` or more." % longest_iterable_len, + TqdmWarning, stacklevel=2) + if "lock_name" not in tqdm_kwargs: + tqdm_kwargs = tqdm_kwargs.copy() + tqdm_kwargs["lock_name"] = "mp_lock" + return _executor_map(ProcessPoolExecutor, fn, *iterables, **tqdm_kwargs) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/contrib/discord.py b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/contrib/discord.py new file mode 100644 index 0000000000000000000000000000000000000000..574baa84bbbeb5afce4a49f23edac894d680ca82 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/contrib/discord.py @@ -0,0 +1,156 @@ +""" +Sends updates to a Discord bot. + +Usage: +>>> from tqdm.contrib.discord import tqdm, trange +>>> for i in trange(10, token='{token}', channel_id='{channel_id}'): +... ... + +![screenshot](https://tqdm.github.io/img/screenshot-discord.png) +""" +from os import getenv +from warnings import warn + +from requests import Session +from requests.utils import default_user_agent + +from ..auto import tqdm as tqdm_auto +from ..std import TqdmWarning +from ..version import __version__ +from .utils_worker import MonoWorker + +__author__ = {"github.com/": ["casperdcl", "guigoruiz1"]} +__all__ = ['DiscordIO', 'tqdm_discord', 'tdrange', 'tqdm', 'trange'] + + +class DiscordIO(MonoWorker): + """Non-blocking file-like IO using a Discord Bot.""" + API = "https://discord.com/api/v10" + UA = f"tqdm (https://tqdm.github.io, {__version__}) {default_user_agent()}" + + def __init__(self, token, channel_id): + """Creates a new message in the given `channel_id`.""" + super().__init__() + self.token = token + self.channel_id = channel_id + self.session = Session() + self.text = self.__class__.__name__ + self.message_id + + @property + def message_id(self): + if hasattr(self, '_message_id'): + return self._message_id + try: + res = self.session.post( + f'{self.API}/channels/{self.channel_id}/messages', + headers={'Authorization': f'Bot {self.token}', 'User-Agent': self.UA}, + json={'content': f"`{self.text}`"}).json() + except Exception as e: + tqdm_auto.write(str(e)) + else: + if res.get('error_code') == 429: + warn("Creation rate limit: try increasing `mininterval`.", + TqdmWarning, stacklevel=2) + else: + self._message_id = res['id'] + return self._message_id + + def write(self, s): + """Replaces internal `message_id`'s text with `s`.""" + if not s: + s = "..." + s = s.replace('\r', '').strip() + if s == self.text: + return # avoid duplicate message Bot error + message_id = self.message_id + if message_id is None: + return + self.text = s + try: + future = self.submit( + self.session.patch, + f'{self.API}/channels/{self.channel_id}/messages/{message_id}', + headers={'Authorization': f'Bot {self.token}', 'User-Agent': self.UA}, + json={'content': f"`{self.text}`"}) + except Exception as e: + tqdm_auto.write(str(e)) + else: + return future + + def delete(self): + """Deletes internal `message_id`.""" + try: + future = self.submit( + self.session.delete, + f'{self.API}/channels/{self.channel_id}/messages/{self.message_id}', + headers={'Authorization': f'Bot {self.token}', 'User-Agent': self.UA}) + except Exception as e: + tqdm_auto.write(str(e)) + else: + return future + + +class tqdm_discord(tqdm_auto): + """ + Standard `tqdm.auto.tqdm` but also sends updates to a Discord Bot. + May take a few seconds to create (`__init__`). + + - create a discord bot (not public, no requirement of OAuth2 code + grant, only send message permissions) & invite it to a channel: + + - copy the bot `{token}` & `{channel_id}` and paste below + + >>> from tqdm.contrib.discord import tqdm, trange + >>> for i in tqdm(iterable, token='{token}', channel_id='{channel_id}'): + ... ... + """ + def __init__(self, *args, **kwargs): + """ + Parameters + ---------- + token : str, required. Discord bot token + [default: ${TQDM_DISCORD_TOKEN}]. + channel_id : int, required. Discord channel ID + [default: ${TQDM_DISCORD_CHANNEL_ID}]. + + See `tqdm.auto.tqdm.__init__` for other parameters. + """ + if not kwargs.get('disable'): + kwargs = kwargs.copy() + self.dio = DiscordIO( + kwargs.pop('token', getenv('TQDM_DISCORD_TOKEN')), + kwargs.pop('channel_id', getenv('TQDM_DISCORD_CHANNEL_ID'))) + super().__init__(*args, **kwargs) + + def display(self, **kwargs): + super().display(**kwargs) + fmt = self.format_dict + if fmt.get('bar_format', None): + fmt['bar_format'] = fmt['bar_format'].replace( + '', '{bar:10u}').replace('{bar}', '{bar:10u}') + else: + fmt['bar_format'] = '{l_bar}{bar:10u}{r_bar}' + self.dio.write(self.format_meter(**fmt)) + + def clear(self, *args, **kwargs): + super().clear(*args, **kwargs) + if not self.disable: + self.dio.write("") + + def close(self): + if self.disable: + return + super().close() + if not (self.leave or (self.leave is None and self.pos == 0)): + self.dio.delete() + + +def tdrange(*args, **kwargs): + """Shortcut for `tqdm.contrib.discord.tqdm(range(*args), **kwargs)`.""" + return tqdm_discord(range(*args), **kwargs) + + +# Aliases +tqdm = tqdm_discord +trange = tdrange diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/contrib/itertools.py b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/contrib/itertools.py new file mode 100644 index 0000000000000000000000000000000000000000..e67651a41a6b8760d9b928ea48239e4611d70315 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/contrib/itertools.py @@ -0,0 +1,35 @@ +""" +Thin wrappers around `itertools`. +""" +import itertools + +from ..auto import tqdm as tqdm_auto + +__author__ = {"github.com/": ["casperdcl"]} +__all__ = ['product'] + + +def product(*iterables, **tqdm_kwargs): + """ + Equivalent of `itertools.product`. + + Parameters + ---------- + tqdm_class : [default: tqdm.auto.tqdm]. + """ + kwargs = tqdm_kwargs.copy() + tqdm_class = kwargs.pop("tqdm_class", tqdm_auto) + try: + lens = list(map(len, iterables)) + except TypeError: + total = None + else: + total = 1 + for i in lens: + total *= i + kwargs.setdefault("total", total) + with tqdm_class(**kwargs) as t: + it = itertools.product(*iterables) + for i in it: + yield i + t.update() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/contrib/logging.py b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/contrib/logging.py new file mode 100644 index 0000000000000000000000000000000000000000..e06febe37b5d70b5296804c55dca48a397c250e3 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/contrib/logging.py @@ -0,0 +1,126 @@ +""" +Helper functionality for interoperability with stdlib `logging`. +""" +import logging +import sys +from contextlib import contextmanager + +try: + from typing import Iterator, List, Optional, Type # noqa: F401 +except ImportError: + pass + +from ..std import tqdm as std_tqdm + + +class _TqdmLoggingHandler(logging.StreamHandler): + def __init__( + self, + tqdm_class=std_tqdm # type: Type[std_tqdm] + ): + super().__init__() + self.tqdm_class = tqdm_class + + def emit(self, record): + try: + msg = self.format(record) + self.tqdm_class.write(msg, file=self.stream) + self.flush() + except (KeyboardInterrupt, SystemExit): + raise + except: # noqa pylint: disable=bare-except + self.handleError(record) + + +def _is_console_logging_handler(handler): + return (isinstance(handler, logging.StreamHandler) + and handler.stream in {sys.stdout, sys.stderr}) + + +def _get_first_found_console_logging_handler(handlers): + for handler in handlers: + if _is_console_logging_handler(handler): + return handler + + +@contextmanager +def logging_redirect_tqdm( + loggers=None, # type: Optional[List[logging.Logger]], + tqdm_class=std_tqdm # type: Type[std_tqdm] +): + # type: (...) -> Iterator[None] + """ + Context manager redirecting console logging to `tqdm.write()`, leaving + other logging handlers (e.g. log files) unaffected. + + Parameters + ---------- + loggers : list, optional + Which handlers to redirect (default: [logging.root]). + tqdm_class : optional + + Example + ------- + ```python + import logging + from tqdm import trange + from tqdm.contrib.logging import logging_redirect_tqdm + + LOG = logging.getLogger(__name__) + + if __name__ == '__main__': + logging.basicConfig(level=logging.INFO) + with logging_redirect_tqdm(): + for i in trange(9): + if i == 4: + LOG.info("console logging redirected to `tqdm.write()`") + # logging restored + ``` + """ + if loggers is None: + loggers = [logging.root] + original_handlers_list = [logger.handlers for logger in loggers] + try: + for logger in loggers: + tqdm_handler = _TqdmLoggingHandler(tqdm_class) + orig_handler = _get_first_found_console_logging_handler(logger.handlers) + if orig_handler is not None: + tqdm_handler.setFormatter(orig_handler.formatter) + tqdm_handler.stream = orig_handler.stream + logger.handlers = [ + handler for handler in logger.handlers + if not _is_console_logging_handler(handler)] + [tqdm_handler] + yield + finally: + for logger, original_handlers in zip(loggers, original_handlers_list): + logger.handlers = original_handlers + + +@contextmanager +def tqdm_logging_redirect( + *args, + # loggers=None, # type: Optional[List[logging.Logger]] + # tqdm=None, # type: Optional[Type[tqdm.tqdm]] + **kwargs +): + # type: (...) -> Iterator[None] + """ + Convenience shortcut for: + ```python + with tqdm_class(*args, **tqdm_kwargs) as pbar: + with logging_redirect_tqdm(loggers=loggers, tqdm_class=tqdm_class): + yield pbar + ``` + + Parameters + ---------- + tqdm_class : optional, (default: tqdm.std.tqdm). + loggers : optional, list. + **tqdm_kwargs : passed to `tqdm_class`. + """ + tqdm_kwargs = kwargs.copy() + loggers = tqdm_kwargs.pop('loggers', None) + tqdm_class = tqdm_kwargs.pop('tqdm_class', std_tqdm) + with tqdm_class(*args, **tqdm_kwargs) as pbar: + with logging_redirect_tqdm(loggers=loggers, tqdm_class=tqdm_class): + yield pbar diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/contrib/slack.py b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/contrib/slack.py new file mode 100644 index 0000000000000000000000000000000000000000..9bca8ee98904ce869a4f8d6417bbdc4f00b38751 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/contrib/slack.py @@ -0,0 +1,120 @@ +""" +Sends updates to a Slack app. + +Usage: +>>> from tqdm.contrib.slack import tqdm, trange +>>> for i in trange(10, token='{token}', channel='{channel}'): +... ... + +![screenshot](https://tqdm.github.io/img/screenshot-slack.png) +""" +import logging +from os import getenv + +try: + from slack_sdk import WebClient +except ImportError: + raise ImportError("Please `pip install slack-sdk`") + +from ..auto import tqdm as tqdm_auto +from .utils_worker import MonoWorker + +__author__ = {"github.com/": ["0x2b3bfa0", "casperdcl"]} +__all__ = ['SlackIO', 'tqdm_slack', 'tsrange', 'tqdm', 'trange'] + + +class SlackIO(MonoWorker): + """Non-blocking file-like IO using a Slack app.""" + def __init__(self, token, channel): + """Creates a new message in the given `channel`.""" + super().__init__() + self.client = WebClient(token=token) + self.text = self.__class__.__name__ + try: + self.message = self.client.chat_postMessage(channel=channel, text=self.text) + except Exception as e: + tqdm_auto.write(str(e)) + self.message = None + + def write(self, s): + """Replaces internal `message`'s text with `s`.""" + if not s: + s = "..." + s = s.replace('\r', '').strip() + if s == self.text: + return # skip duplicate message + message = self.message + if message is None: + return + self.text = s + try: + future = self.submit(self.client.chat_update, channel=message['channel'], + ts=message['ts'], text='`' + s + '`') + except Exception as e: + tqdm_auto.write(str(e)) + else: + return future + + +class tqdm_slack(tqdm_auto): + """ + Standard `tqdm.auto.tqdm` but also sends updates to a Slack app. + May take a few seconds to create (`__init__`). + + - create a Slack app with the `chat:write` scope & invite it to a + channel: + - copy the bot `{token}` & `{channel}` and paste below + >>> from tqdm.contrib.slack import tqdm, trange + >>> for i in tqdm(iterable, token='{token}', channel='{channel}'): + ... ... + """ + def __init__(self, *args, **kwargs): + """ + Parameters + ---------- + token : str, required. Slack token + [default: ${TQDM_SLACK_TOKEN}]. + channel : int, required. Slack channel + [default: ${TQDM_SLACK_CHANNEL}]. + mininterval : float, optional. + Minimum of [default: 1.5] to avoid rate limit. + + See `tqdm.auto.tqdm.__init__` for other parameters. + """ + if not kwargs.get('disable'): + kwargs = kwargs.copy() + logging.getLogger("HTTPClient").setLevel(logging.WARNING) + self.sio = SlackIO( + kwargs.pop('token', getenv("TQDM_SLACK_TOKEN")), + kwargs.pop('channel', getenv("TQDM_SLACK_CHANNEL"))) + kwargs['mininterval'] = max(1.5, kwargs.get('mininterval', 1.5)) + super().__init__(*args, **kwargs) + + def display(self, **kwargs): + super().display(**kwargs) + fmt = self.format_dict + if fmt.get('bar_format', None): + fmt['bar_format'] = fmt['bar_format'].replace( + '', '`{bar:10}`').replace('{bar}', '`{bar:10u}`') + else: + fmt['bar_format'] = '{l_bar}`{bar:10}`{r_bar}' + if fmt['ascii'] is False: + fmt['ascii'] = [":black_square:", ":small_blue_diamond:", ":large_blue_diamond:", + ":large_blue_square:"] + fmt['ncols'] = 336 + self.sio.write(self.format_meter(**fmt)) + + def clear(self, *args, **kwargs): + super().clear(*args, **kwargs) + if not self.disable: + self.sio.write("") + + +def tsrange(*args, **kwargs): + """Shortcut for `tqdm.contrib.slack.tqdm(range(*args), **kwargs)`.""" + return tqdm_slack(range(*args), **kwargs) + + +# Aliases +tqdm = tqdm_slack +trange = tsrange diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/contrib/telegram.py b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/contrib/telegram.py new file mode 100644 index 0000000000000000000000000000000000000000..019151800bc0c4c4fc543314b6398aa602b0692a --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/contrib/telegram.py @@ -0,0 +1,153 @@ +""" +Sends updates to a Telegram bot. + +Usage: +>>> from tqdm.contrib.telegram import tqdm, trange +>>> for i in trange(10, token='{token}', chat_id='{chat_id}'): +... ... + +![screenshot](https://tqdm.github.io/img/screenshot-telegram.gif) +""" +from os import getenv +from warnings import warn + +from requests import Session + +from ..auto import tqdm as tqdm_auto +from ..std import TqdmWarning +from .utils_worker import MonoWorker + +__author__ = {"github.com/": ["casperdcl"]} +__all__ = ['TelegramIO', 'tqdm_telegram', 'ttgrange', 'tqdm', 'trange'] + + +class TelegramIO(MonoWorker): + """Non-blocking file-like IO using a Telegram Bot.""" + API = 'https://api.telegram.org/bot' + + def __init__(self, token, chat_id): + """Creates a new message in the given `chat_id`.""" + super().__init__() + self.token = token + self.chat_id = chat_id + self.session = Session() + self.text = self.__class__.__name__ + self.message_id + + @property + def message_id(self): + if hasattr(self, '_message_id'): + return self._message_id + try: + res = self.session.post( + self.API + '%s/sendMessage' % self.token, + data={'text': '`' + self.text + '`', 'chat_id': self.chat_id, + 'parse_mode': 'MarkdownV2'}).json() + except Exception as e: + tqdm_auto.write(str(e)) + else: + if res.get('error_code') == 429: + warn("Creation rate limit: try increasing `mininterval`.", + TqdmWarning, stacklevel=2) + else: + self._message_id = res['result']['message_id'] + return self._message_id + + def write(self, s): + """Replaces internal `message_id`'s text with `s`.""" + if not s: + s = "..." + s = s.replace('\r', '').strip() + if s == self.text: + return # avoid duplicate message Bot error + message_id = self.message_id + if message_id is None: + return + self.text = s + try: + future = self.submit( + self.session.post, self.API + '%s/editMessageText' % self.token, + data={'text': '`' + s + '`', 'chat_id': self.chat_id, + 'message_id': message_id, 'parse_mode': 'MarkdownV2'}) + except Exception as e: + tqdm_auto.write(str(e)) + else: + return future + + def delete(self): + """Deletes internal `message_id`.""" + try: + future = self.submit( + self.session.post, self.API + '%s/deleteMessage' % self.token, + data={'chat_id': self.chat_id, 'message_id': self.message_id}) + except Exception as e: + tqdm_auto.write(str(e)) + else: + return future + + +class tqdm_telegram(tqdm_auto): + """ + Standard `tqdm.auto.tqdm` but also sends updates to a Telegram Bot. + May take a few seconds to create (`__init__`). + + - create a bot + - copy its `{token}` + - add the bot to a chat and send it a message such as `/start` + - go to to find out + the `{chat_id}` + - paste the `{token}` & `{chat_id}` below + + >>> from tqdm.contrib.telegram import tqdm, trange + >>> for i in tqdm(iterable, token='{token}', chat_id='{chat_id}'): + ... ... + """ + def __init__(self, *args, **kwargs): + """ + Parameters + ---------- + token : str, required. Telegram token + [default: ${TQDM_TELEGRAM_TOKEN}]. + chat_id : str, required. Telegram chat ID + [default: ${TQDM_TELEGRAM_CHAT_ID}]. + + See `tqdm.auto.tqdm.__init__` for other parameters. + """ + if not kwargs.get('disable'): + kwargs = kwargs.copy() + self.tgio = TelegramIO( + kwargs.pop('token', getenv('TQDM_TELEGRAM_TOKEN')), + kwargs.pop('chat_id', getenv('TQDM_TELEGRAM_CHAT_ID'))) + super().__init__(*args, **kwargs) + + def display(self, **kwargs): + super().display(**kwargs) + fmt = self.format_dict + if fmt.get('bar_format', None): + fmt['bar_format'] = fmt['bar_format'].replace( + '', '{bar:10u}').replace('{bar}', '{bar:10u}') + else: + fmt['bar_format'] = '{l_bar}{bar:10u}{r_bar}' + self.tgio.write(self.format_meter(**fmt)) + + def clear(self, *args, **kwargs): + super().clear(*args, **kwargs) + if not self.disable: + self.tgio.write("") + + def close(self): + if self.disable: + return + super().close() + if not (self.leave or (self.leave is None and self.pos == 0)): + self.tgio.delete() + + +def ttgrange(*args, **kwargs): + """Shortcut for `tqdm.contrib.telegram.tqdm(range(*args), **kwargs)`.""" + return tqdm_telegram(range(*args), **kwargs) + + +# Aliases +tqdm = tqdm_telegram +trange = ttgrange diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/contrib/utils_worker.py b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/contrib/utils_worker.py new file mode 100644 index 0000000000000000000000000000000000000000..89fafc8c92f8ed085077b37eb58329f2588bd5d7 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/contrib/utils_worker.py @@ -0,0 +1,38 @@ +""" +IO/concurrency helpers for `tqdm.contrib`. +""" +from collections import deque +from concurrent.futures import ThreadPoolExecutor + +from ..auto import tqdm as tqdm_auto + +__author__ = {"github.com/": ["casperdcl"]} +__all__ = ['MonoWorker'] + + +class MonoWorker: + """ + Supports one running task and one waiting task. + The waiting task is the most recent submitted (others are discarded). + """ + def __init__(self): + self.pool = ThreadPoolExecutor(max_workers=1) + self.futures = deque([], 2) + + def submit(self, func, *args, **kwargs): + """`func(*args, **kwargs)` may replace currently waiting task.""" + futures = self.futures + if len(futures) == futures.maxlen: + running = futures.popleft() + if not running.done(): + if len(futures): # clear waiting + waiting = futures.pop() + waiting.cancel() + futures.appendleft(running) # re-insert running + try: + waiting = self.pool.submit(func, *args, **kwargs) + except Exception as e: + tqdm_auto.write(str(e)) + else: + futures.append(waiting) + return waiting diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/dask.py b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/dask.py new file mode 100644 index 0000000000000000000000000000000000000000..57f1b668f59dc5991019eee34c7df3232a2c2cd7 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/dask.py @@ -0,0 +1,44 @@ +from functools import partial + +from dask.callbacks import Callback + +from .auto import tqdm as tqdm_auto + +__author__ = {"github.com/": ["casperdcl"]} +__all__ = ['TqdmCallback'] + + +class TqdmCallback(Callback): + """Dask callback for task progress.""" + def __init__(self, start=None, pretask=None, tqdm_class=tqdm_auto, + **tqdm_kwargs): + """ + Parameters + ---------- + tqdm_class : optional + `tqdm` class to use for bars [default: `tqdm.auto.tqdm`]. + tqdm_kwargs : optional + Any other arguments used for all bars. + """ + super().__init__(start=start, pretask=pretask) + if tqdm_kwargs: + tqdm_class = partial(tqdm_class, **tqdm_kwargs) + self.tqdm_class = tqdm_class + + def _start_state(self, _, state): + self.pbar = self.tqdm_class(total=sum( + len(state[k]) for k in ['ready', 'waiting', 'running', 'finished'])) + + def _posttask(self, *_, **__): + self.pbar.update() + + def _finish(self, *_, **__): + self.pbar.close() + + def display(self): + """Displays in the current cell in Notebooks.""" + container = getattr(self.bar, 'container', None) + if container is None: + return + from .notebook import display + display(container) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/gui.py b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/gui.py new file mode 100644 index 0000000000000000000000000000000000000000..cb52fb91a8661f4c73edd352bbc6f21b877dcfee --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/gui.py @@ -0,0 +1,179 @@ +""" +Matplotlib GUI progressbar decorator for iterators. + +Usage: +>>> from tqdm.gui import trange, tqdm +>>> for i in trange(10): +... ... +""" +# future division is important to divide integers and get as +# a result precise floating numbers (instead of truncated int) +import re +from warnings import warn + +# to inherit from the tqdm class +from .std import TqdmExperimentalWarning +from .std import tqdm as std_tqdm + +# import compatibility functions and utilities + +__author__ = {"github.com/": ["casperdcl", "lrq3000"]} +__all__ = ['tqdm_gui', 'tgrange', 'tqdm', 'trange'] + + +class tqdm_gui(std_tqdm): # pragma: no cover + """Experimental Matplotlib GUI version of tqdm!""" + # TODO: @classmethod: write() on GUI? + def __init__(self, *args, **kwargs): + from collections import deque + + import matplotlib as mpl + import matplotlib.pyplot as plt + kwargs = kwargs.copy() + kwargs['gui'] = True + colour = kwargs.pop('colour', 'g') + super().__init__(*args, **kwargs) + + if self.disable: + return + + warn("GUI is experimental/alpha", TqdmExperimentalWarning, stacklevel=2) + self.mpl = mpl + self.plt = plt + + # Remember if external environment uses toolbars + self.toolbar = self.mpl.rcParams['toolbar'] + self.mpl.rcParams['toolbar'] = 'None' + + self.mininterval = max(self.mininterval, 0.5) + self.fig, ax = plt.subplots(figsize=(9, 2.2)) + # self.fig.subplots_adjust(bottom=0.2) + total = self.__len__() # avoids TypeError on None #971 + if total is not None: + self.xdata = [] + self.ydata = [] + self.zdata = [] + else: + self.xdata = deque([]) + self.ydata = deque([]) + self.zdata = deque([]) + self.line1, = ax.plot(self.xdata, self.ydata, color='b') + self.line2, = ax.plot(self.xdata, self.zdata, color='k') + ax.set_ylim(0, 0.001) + if total is not None: + ax.set_xlim(0, 100) + ax.set_xlabel("percent") + self.fig.legend((self.line1, self.line2), ("cur", "est"), + loc='center right') + # progressbar + self.hspan = plt.axhspan(0, 0.001, xmin=0, xmax=0, color=colour) + else: + # ax.set_xlim(-60, 0) + ax.set_xlim(0, 60) + ax.invert_xaxis() + ax.set_xlabel("seconds") + ax.legend(("cur", "est"), loc='lower left') + ax.grid() + # ax.set_xlabel('seconds') + ax.set_ylabel((self.unit if self.unit else "it") + "/s") + if self.unit_scale: + plt.ticklabel_format(style='sci', axis='y', scilimits=(0, 0)) + ax.yaxis.get_offset_text().set_x(-0.15) + + # Remember if external environment is interactive + self.wasion = plt.isinteractive() + plt.ion() + self.ax = ax + + def close(self): + if self.disable: + return + + self.disable = True + + with self.get_lock(): + self._instances.remove(self) + + # Restore toolbars + self.mpl.rcParams['toolbar'] = self.toolbar + # Return to non-interactive mode + if not self.wasion: + self.plt.ioff() + if self.leave: + self.display() + else: + self.plt.close(self.fig) + + def clear(self, *_, **__): + pass + + def display(self, *_, **__): + n = self.n + cur_t = self._time() + elapsed = cur_t - self.start_t + delta_it = n - self.last_print_n + delta_t = cur_t - self.last_print_t + + # Inline due to multiple calls + total = self.total + xdata = self.xdata + ydata = self.ydata + zdata = self.zdata + ax = self.ax + line1 = self.line1 + line2 = self.line2 + hspan = getattr(self, 'hspan', None) + # instantaneous rate + y = delta_it / delta_t + # overall rate + z = n / elapsed + # update line data + xdata.append(n * 100.0 / total if total else cur_t) + ydata.append(y) + zdata.append(z) + + # Discard old values + # xmin, xmax = ax.get_xlim() + # if (not total) and elapsed > xmin * 1.1: + if (not total) and elapsed > 66: + xdata.popleft() + ydata.popleft() + zdata.popleft() + + ymin, ymax = ax.get_ylim() + if y > ymax or z > ymax: + ymax = 1.1 * y + ax.set_ylim(ymin, ymax) + ax.figure.canvas.draw() + + if total: + line1.set_data(xdata, ydata) + line2.set_data(xdata, zdata) + if hspan: + hspan.set_xy((0, ymin)) + hspan.set_height(ymax - ymin) + hspan.set_width(n / total) + else: + t_ago = [cur_t - i for i in xdata] + line1.set_data(t_ago, ydata) + line2.set_data(t_ago, zdata) + + d = self.format_dict + # remove {bar} + d['bar_format'] = (d['bar_format'] or "{l_bar}{r_bar}").replace( + "{bar}", "") + msg = self.format_meter(**d) + if '' in msg: + msg = "".join(re.split(r'\|?\|?', msg, maxsplit=1)) + ax.set_title(msg, fontname="DejaVu Sans Mono", fontsize=11) + self.plt.pause(1e-9) + + +def tgrange(*args, **kwargs): + """Shortcut for `tqdm.gui.tqdm(range(*args), **kwargs)`.""" + return tqdm_gui(range(*args), **kwargs) + + +# Aliases +tqdm = tqdm_gui +trange = tgrange diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/keras.py b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/keras.py new file mode 100644 index 0000000000000000000000000000000000000000..cce9467c51a95388aaa502d1da9a42f3ebf0af24 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/keras.py @@ -0,0 +1,122 @@ +from copy import copy +from functools import partial + +from .auto import tqdm as tqdm_auto + +try: + import keras +except (ImportError, AttributeError) as e: + try: + from tensorflow import keras + except ImportError: + raise e +__author__ = {"github.com/": ["casperdcl"]} +__all__ = ['TqdmCallback'] + + +class TqdmCallback(keras.callbacks.Callback): + """Keras callback for epoch and batch progress.""" + @staticmethod + def bar2callback(bar, pop=None, delta=(lambda logs: 1)): + def callback(_, logs=None): + n = delta(logs) + if logs: + if pop: + logs = copy(logs) + [logs.pop(i, 0) for i in pop] + bar.set_postfix(logs, refresh=False) + bar.update(n) + + return callback + + def __init__(self, epochs=None, data_size=None, batch_size=None, verbose=1, + tqdm_class=tqdm_auto, **tqdm_kwargs): + """ + Parameters + ---------- + epochs : int, optional + data_size : int, optional + Number of training pairs. + batch_size : int, optional + Number of training pairs per batch. + verbose : int + 0: epoch, 1: batch (transient), 2: batch. [default: 1]. + Will be set to `0` unless both `data_size` and `batch_size` + are given. + tqdm_class : optional + `tqdm` class to use for bars [default: `tqdm.auto.tqdm`]. + tqdm_kwargs : optional + Any other arguments used for all bars. + """ + if tqdm_kwargs: + tqdm_class = partial(tqdm_class, **tqdm_kwargs) + self.tqdm_class = tqdm_class + self.epoch_bar = tqdm_class(total=epochs, unit='epoch') + self.on_epoch_end = self.bar2callback(self.epoch_bar) + if data_size and batch_size: + self.batches = batches = (data_size + batch_size - 1) // batch_size + else: + self.batches = batches = None + self.verbose = verbose + if verbose == 1: + self.batch_bar = tqdm_class(total=batches, unit='batch', leave=False) + self.on_batch_end = self.bar2callback( + self.batch_bar, pop=['batch', 'size'], + delta=lambda logs: logs.get('size', 1)) + + def on_train_begin(self, *_, **__): + params = self.params.get + auto_total = params('epochs', params('nb_epoch', None)) + if auto_total is not None and auto_total != self.epoch_bar.total: + self.epoch_bar.reset(total=auto_total) + + def on_epoch_begin(self, epoch, *_, **__): + if self.epoch_bar.n < epoch: + ebar = self.epoch_bar + ebar.n = ebar.last_print_n = ebar.initial = epoch + if self.verbose: + params = self.params.get + total = params('samples', params( + 'nb_sample', params('steps', None))) or self.batches + if self.verbose == 2: + if hasattr(self, 'batch_bar'): + self.batch_bar.close() + self.batch_bar = self.tqdm_class( + total=total, unit='batch', leave=True, + unit_scale=1 / (params('batch_size', 1) or 1)) + self.on_batch_end = self.bar2callback( + self.batch_bar, pop=['batch', 'size'], + delta=lambda logs: logs.get('size', 1)) + elif self.verbose == 1: + self.batch_bar.unit_scale = 1 / (params('batch_size', 1) or 1) + self.batch_bar.reset(total=total) + else: + raise KeyError('Unknown verbosity') + + def on_train_end(self, *_, **__): + if hasattr(self, 'batch_bar'): + self.batch_bar.close() + self.epoch_bar.close() + + def display(self): + """Displays in the current cell in Notebooks.""" + container = getattr(self.epoch_bar, 'container', None) + if container is None: + return + from .notebook import display + display(container) + batch_bar = getattr(self, 'batch_bar', None) + if batch_bar is not None: + display(batch_bar.container) + + @staticmethod + def _implements_train_batch_hooks(): + return True + + @staticmethod + def _implements_test_batch_hooks(): + return True + + @staticmethod + def _implements_predict_batch_hooks(): + return True diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/notebook.py b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/notebook.py new file mode 100644 index 0000000000000000000000000000000000000000..83178e981f46c0784a36bee1f9e347022c7678a4 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/notebook.py @@ -0,0 +1,315 @@ +""" +IPython/Jupyter Notebook progressbar decorator for iterators. +Includes a default `range` iterator printing to `stderr`. + +Usage: +>>> from tqdm.notebook import trange, tqdm +>>> for i in trange(10): +... ... +""" +# import compatibility functions and utilities +import re +import sys +from html import escape +from weakref import proxy + +# to inherit from the tqdm class +from .std import tqdm as std_tqdm + +if True: # pragma: no cover + # import IPython/Jupyter base widget and display utilities + IPY = 0 + try: # IPython 4.x + import ipywidgets + IPY = 4 + except ImportError: # IPython 3.x / 2.x + IPY = 32 + import warnings + with warnings.catch_warnings(): + warnings.filterwarnings( + 'ignore', message=".*The `IPython.html` package has been deprecated.*") + try: + import IPython.html.widgets as ipywidgets # NOQA: F401 + except ImportError: + pass + + try: # IPython 4.x / 3.x + if IPY == 32: + from IPython.html.widgets import HTML + from IPython.html.widgets import FloatProgress as IProgress + from IPython.html.widgets import HBox + IPY = 3 + else: + from ipywidgets import HTML + from ipywidgets import FloatProgress as IProgress + from ipywidgets import HBox + except ImportError: + try: # IPython 2.x + from IPython.html.widgets import HTML + from IPython.html.widgets import ContainerWidget as HBox + from IPython.html.widgets import FloatProgressWidget as IProgress + IPY = 2 + except ImportError: + IPY = 0 + IProgress = None + HBox = object + + try: + from IPython.display import display # , clear_output + except ImportError: + pass + +__author__ = {"github.com/": ["lrq3000", "casperdcl", "alexanderkuk"]} +__all__ = ['tqdm_notebook', 'tnrange', 'tqdm', 'trange'] +WARN_NOIPYW = ("IProgress not found. Please update jupyter and ipywidgets." + " See https://ipywidgets.readthedocs.io/en/stable" + "/user_install.html") + + +class TqdmHBox(HBox): + """`ipywidgets.HBox` with a pretty representation""" + def _json_(self, pretty=None): + pbar = getattr(self, 'pbar', None) + if pbar is None: + return {} + d = pbar.format_dict + if pretty is not None: + d["ascii"] = not pretty + return d + + def __repr__(self, pretty=False): + pbar = getattr(self, 'pbar', None) + if pbar is None: + return super().__repr__() + return pbar.format_meter(**self._json_(pretty)) + + def _repr_pretty_(self, pp, *_, **__): + pp.text(self.__repr__(True)) + + +class tqdm_notebook(std_tqdm): + """ + Experimental IPython/Jupyter Notebook widget using tqdm! + """ + @staticmethod + def status_printer(_, total=None, desc=None, ncols=None): + """ + Manage the printing of an IPython/Jupyter Notebook progress bar widget. + """ + # Fallback to text bar if there's no total + # DEPRECATED: replaced with an 'info' style bar + # if not total: + # return super(tqdm_notebook, tqdm_notebook).status_printer(file) + + # fp = file + + # Prepare IPython progress bar + if IProgress is None: # #187 #451 #558 #872 + raise ImportError(WARN_NOIPYW) + if total: + pbar = IProgress(min=0, max=total) + else: # No total? Show info style bar with no progress tqdm status + pbar = IProgress(min=0, max=1) + pbar.value = 1 + pbar.bar_style = 'info' + if ncols is None: + pbar.layout.width = "20px" + + ltext = HTML() + rtext = HTML() + if desc: + ltext.value = desc + container = TqdmHBox(children=[ltext, pbar, rtext]) + # Prepare layout + if ncols is not None: # use default style of ipywidgets + # ncols could be 100, "100px", "100%" + ncols = str(ncols) # ipywidgets only accepts string + try: + if int(ncols) > 0: # isnumeric and positive + ncols += 'px' + except ValueError: + pass + pbar.layout.flex = '2' + container.layout.width = ncols + container.layout.display = 'inline-flex' + container.layout.flex_flow = 'row wrap' + + return container + + def display(self, msg=None, pos=None, + # additional signals + close=False, bar_style=None, check_delay=True): + # Note: contrary to native tqdm, msg='' does NOT clear bar + # goal is to keep all infos if error happens so user knows + # at which iteration the loop failed. + + # Clear previous output (really necessary?) + # clear_output(wait=1) + + if not msg and not close: + d = self.format_dict + # remove {bar} + d['bar_format'] = (d['bar_format'] or "{l_bar}{r_bar}").replace( + "{bar}", "") + msg = self.format_meter(**d) + + ltext, pbar, rtext = self.container.children + pbar.value = self.n + + if msg: + msg = msg.replace(' ', '\u2007') # fix html space padding + # html escape special characters (like '&') + if '' in msg: + left, right = map(escape, re.split(r'\|?\|?', msg, maxsplit=1)) + else: + left, right = '', escape(msg) + + # Update description + ltext.value = left + # never clear the bar (signal: msg='') + if right: + rtext.value = right + + # Change bar style + if bar_style: + # Hack-ish way to avoid the danger bar_style being overridden by + # success because the bar gets closed after the error... + if pbar.bar_style != 'danger' or bar_style != 'success': + pbar.bar_style = bar_style + + # Special signal to close the bar + if close and pbar.bar_style != 'danger': # hide only if no error + try: + self.container.close() + except AttributeError: + self.container.visible = False + self.container.layout.visibility = 'hidden' # IPYW>=8 + + if check_delay and self.delay > 0 and not self.displayed: + display(self.container) + self.displayed = True + + @property + def colour(self): + if hasattr(self, 'container'): + return self.container.children[-2].style.bar_color + + @colour.setter + def colour(self, bar_color): + if hasattr(self, 'container'): + self.container.children[-2].style.bar_color = bar_color + + def __init__(self, *args, **kwargs): + """ + Supports the usual `tqdm.tqdm` parameters as well as those listed below. + + Parameters + ---------- + display : Whether to call `display(self.container)` immediately + [default: True]. + """ + kwargs = kwargs.copy() + # Setup default output + file_kwarg = kwargs.get('file', sys.stderr) + if file_kwarg is sys.stderr or file_kwarg is None: + kwargs['file'] = sys.stdout # avoid the red block in IPython + + # Initialize parent class + avoid printing by using gui=True + kwargs['gui'] = True + # convert disable = None to False + kwargs['disable'] = bool(kwargs.get('disable', False)) + colour = kwargs.pop('colour', None) + display_here = kwargs.pop('display', True) + super().__init__(*args, **kwargs) + if self.disable or not kwargs['gui']: + self.disp = lambda *_, **__: None + return + + # Get bar width + self.ncols = '100%' if self.dynamic_ncols else kwargs.get("ncols", None) + + # Replace with IPython progress bar display (with correct total) + unit_scale = 1 if self.unit_scale is True else self.unit_scale or 1 + total = self.total * unit_scale if self.total else self.total + self.container = self.status_printer(self.fp, total, self.desc, self.ncols) + self.container.pbar = proxy(self) + self.displayed = False + if display_here and self.delay <= 0: + display(self.container) + self.displayed = True + self.disp = self.display + self.colour = colour + + # Print initial bar state + if not self.disable: + self.display(check_delay=False) + + def __iter__(self): + try: + it = super().__iter__() + yield from it + # NB: except ... [ as ...] breaks IPython async KeyboardInterrupt + except: # NOQA + self.disp(bar_style='danger') + raise + # NB: don't `finally: close()` + # since this could be a shared bar which the user will `reset()` + + def update(self, n=1): + try: + return super().update(n=n) + # NB: except ... [ as ...] breaks IPython async KeyboardInterrupt + except: # NOQA + # cannot catch KeyboardInterrupt when using manual tqdm + # as the interrupt will most likely happen on another statement + self.disp(bar_style='danger') + raise + # NB: don't `finally: close()` + # since this could be a shared bar which the user will `reset()` + + def close(self): + if self.disable: + return + super().close() + # Try to detect if there was an error or KeyboardInterrupt + # in manual mode: if n < total, things probably got wrong + if self.total and self.n < self.total: + self.disp(bar_style='danger', check_delay=False) + else: + if self.leave: + self.disp(bar_style='success', check_delay=False) + else: + self.disp(close=True, check_delay=False) + + def clear(self, *_, **__): + pass + + def reset(self, total=None): + """ + Resets to 0 iterations for repeated use. + + Consider combining with `leave=True`. + + Parameters + ---------- + total : int or float, optional. Total to use for the new bar. + """ + if self.disable: + return super().reset(total=total) + _, pbar, _ = self.container.children + pbar.bar_style = '' + if total is not None: + pbar.max = total + if not self.total and self.ncols is None: # no longer unknown total + pbar.layout.width = None # reset width + return super().reset(total=total) + + +def tnrange(*args, **kwargs): + """Shortcut for `tqdm.notebook.tqdm(range(*args), **kwargs)`.""" + return tqdm_notebook(range(*args), **kwargs) + + +# Aliases +tqdm = tqdm_notebook +trange = tnrange diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/rich.py b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/rich.py new file mode 100644 index 0000000000000000000000000000000000000000..3d392edaf115a93f7c145de52cbe8978dcf1ede8 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/rich.py @@ -0,0 +1,151 @@ +""" +`rich.progress` decorator for iterators. + +Usage: +>>> from tqdm.rich import trange, tqdm +>>> for i in trange(10): +... ... +""" +from warnings import warn + +from rich.progress import ( + BarColumn, Progress, ProgressColumn, Text, TimeElapsedColumn, TimeRemainingColumn, filesize) + +from .std import TqdmExperimentalWarning +from .std import tqdm as std_tqdm + +__author__ = {"github.com/": ["casperdcl"]} +__all__ = ['tqdm_rich', 'trrange', 'tqdm', 'trange'] + + +class FractionColumn(ProgressColumn): + """Renders completed/total, e.g. '0.5/2.3 G'.""" + def __init__(self, unit_scale=False, unit_divisor=1000): + self.unit_scale = unit_scale + self.unit_divisor = unit_divisor + super().__init__() + + def render(self, task): + """Calculate common unit for completed and total.""" + completed = int(task.completed) + total = int(task.total) + if self.unit_scale: + unit, suffix = filesize.pick_unit_and_suffix( + total, + ["", "K", "M", "G", "T", "P", "E", "Z", "Y"], + self.unit_divisor, + ) + else: + unit, suffix = filesize.pick_unit_and_suffix(total, [""], 1) + precision = 0 if unit == 1 else 1 + return Text( + f"{completed/unit:,.{precision}f}/{total/unit:,.{precision}f} {suffix}", + style="progress.download") + + +class RateColumn(ProgressColumn): + """Renders human readable transfer speed.""" + def __init__(self, unit="", unit_scale=False, unit_divisor=1000): + self.unit = unit + self.unit_scale = unit_scale + self.unit_divisor = unit_divisor + super().__init__() + + def render(self, task): + """Show data transfer speed.""" + speed = task.speed + if speed is None: + return Text(f"? {self.unit}/s", style="progress.data.speed") + if self.unit_scale: + unit, suffix = filesize.pick_unit_and_suffix( + speed, + ["", "K", "M", "G", "T", "P", "E", "Z", "Y"], + self.unit_divisor, + ) + else: + unit, suffix = filesize.pick_unit_and_suffix(speed, [""], 1) + precision = 0 if unit == 1 else 1 + return Text(f"{speed/unit:,.{precision}f} {suffix}{self.unit}/s", + style="progress.data.speed") + + +class tqdm_rich(std_tqdm): # pragma: no cover + """Experimental rich.progress GUI version of tqdm!""" + # TODO: @classmethod: write()? + def __init__(self, *args, **kwargs): + """ + This class accepts the following parameters *in addition* to + the parameters accepted by `tqdm`. + + Parameters + ---------- + progress : tuple, optional + arguments for `rich.progress.Progress()`. + options : dict, optional + keyword arguments for `rich.progress.Progress()`. + """ + kwargs = kwargs.copy() + kwargs['gui'] = True + # convert disable = None to False + kwargs['disable'] = bool(kwargs.get('disable', False)) + progress = kwargs.pop('progress', None) + options = kwargs.pop('options', {}).copy() + super().__init__(*args, **kwargs) + + if self.disable: + return + + warn("rich is experimental/alpha", TqdmExperimentalWarning, stacklevel=2) + d = self.format_dict + if progress is None: + progress = ( + "[progress.description]{task.description}" + "[progress.percentage]{task.percentage:>4.0f}%", + BarColumn(bar_width=None), + FractionColumn( + unit_scale=d['unit_scale'], unit_divisor=d['unit_divisor']), + "[", TimeElapsedColumn(), "<", TimeRemainingColumn(), + ",", RateColumn(unit=d['unit'], unit_scale=d['unit_scale'], + unit_divisor=d['unit_divisor']), "]" + ) + options.setdefault('transient', not self.leave) + self._prog = Progress(*progress, **options) + self._prog.__enter__() + self._task_id = self._prog.add_task(self.desc or "", **d) + + def close(self): + if self.disable: + return + self.display() # print 100%, vis #1306 + super().close() + self._prog.__exit__(None, None, None) + + def clear(self, *_, **__): + pass + + def display(self, *_, **__): + if not hasattr(self, '_prog'): + return + self._prog.update(self._task_id, completed=self.n, description=self.desc) + + def reset(self, total=None): + """ + Resets to 0 iterations for repeated use. + + Parameters + ---------- + total : int or float, optional. Total to use for the new bar. + """ + if hasattr(self, '_prog'): + self._prog.reset(total=total) + super().reset(total=total) + + +def trrange(*args, **kwargs): + """Shortcut for `tqdm.rich.tqdm(range(*args), **kwargs)`.""" + return tqdm_rich(range(*args), **kwargs) + + +# Aliases +tqdm = tqdm_rich +trange = trrange diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/std.py b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/std.py new file mode 100644 index 0000000000000000000000000000000000000000..14b0c1963156d9aea344889d8c72603ae0265e59 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/std.py @@ -0,0 +1,1524 @@ +""" +Customisable progressbar decorator for iterators. +Includes a default `range` iterator printing to `stderr`. + +Usage: +>>> from tqdm import trange, tqdm +>>> for i in trange(10): +... ... +""" +import sys +from collections import OrderedDict, defaultdict +from contextlib import contextmanager +from datetime import datetime, timedelta, timezone +from numbers import Number +from time import time +from warnings import warn +from weakref import WeakSet + +from ._monitor import TMonitor +from .utils import ( + CallbackIOWrapper, Comparable, DisableOnWriteError, FormatReplace, SimpleTextIOWrapper, + _is_ascii, _screen_shape_wrapper, _supports_unicode, _term_move_up, disp_len, disp_trim, + envwrap) + +__author__ = "https://github.com/tqdm/tqdm#contributions" +__all__ = ['tqdm', 'trange', + 'TqdmTypeError', 'TqdmKeyError', 'TqdmWarning', + 'TqdmExperimentalWarning', 'TqdmDeprecationWarning', + 'TqdmMonitorWarning'] + + +class TqdmTypeError(TypeError): + pass + + +class TqdmKeyError(KeyError): + pass + + +class TqdmWarning(Warning): + """base class for all tqdm warnings. + + Used for non-external-code-breaking errors, such as garbled printing. + """ + def __init__(self, msg, fp_write=None): # noqa: B042 + if fp_write is not None: + fp_write("\n" + self.__class__.__name__ + ": " + str(msg).rstrip() + '\n') + else: + super().__init__(msg) + + +class TqdmExperimentalWarning(TqdmWarning, FutureWarning): + """beta feature, unstable API and behaviour""" + pass + + +class TqdmDeprecationWarning(TqdmWarning, DeprecationWarning): + # not suppressed if raised + pass + + +class TqdmMonitorWarning(TqdmWarning, RuntimeWarning): + """tqdm monitor errors which do not affect external functionality""" + pass + + +def TRLock(*args, **kwargs): + """threading RLock""" + try: + from threading import RLock + return RLock(*args, **kwargs) + except (ImportError, OSError): # pragma: no cover + pass + + +class TqdmDefaultWriteLock: + """ + Provide a default write lock for thread and multiprocessing safety. + Works only on platforms supporting `fork` (so Windows is excluded). + You must initialise a `tqdm` or `TqdmDefaultWriteLock` instance + before forking in order for the write lock to work. + On Windows, you need to supply the lock from the parent to the children as + an argument to joblib or the parallelism lib you use. + """ + # global thread lock so no setup required for multithreading. + # NB: Do not create multiprocessing lock as it sets the multiprocessing + # context, disallowing `spawn()`/`forkserver()` + th_lock = TRLock() + + def __init__(self): + # Create global parallelism locks to avoid racing issues with parallel + # bars works only if fork available (Linux/MacOSX, but not Windows) + cls = type(self) + root_lock = cls.th_lock + if root_lock is not None: + root_lock.acquire() + cls.create_mp_lock() + self.locks = [lk for lk in [cls.mp_lock, cls.th_lock] if lk is not None] + if root_lock is not None: + root_lock.release() + + def acquire(self, *a, **k): + for lock in self.locks: + lock.acquire(*a, **k) + + def release(self): + for lock in self.locks[::-1]: # Release in inverse order of acquisition + lock.release() + + def __enter__(self): + self.acquire() + + def __exit__(self, *exc): + self.release() + + @classmethod + def create_mp_lock(cls): + if not hasattr(cls, 'mp_lock'): + try: + from multiprocessing import RLock + cls.mp_lock = RLock() + except (ImportError, OSError): # pragma: no cover + cls.mp_lock = None + + @classmethod + def create_th_lock(cls): + assert hasattr(cls, 'th_lock') + warn("create_th_lock not needed anymore", TqdmDeprecationWarning, stacklevel=2) + + +class Bar: + """ + `str.format`-able bar with format specifiers: `[width][type]` + + - `width` + + unspecified (default): use `self.default_len` + + `int >= 0`: overrides `self.default_len` + + `int < 0`: subtract from `self.default_len` + - `type` + + `a`: ascii (`charset=self.ASCII` override) + + `u`: unicode (`charset=self.UTF` override) + + `b`: blank (`charset=" "` override) + """ + ASCII = " 123456789#" + UTF = " " + ''.join(map(chr, range(0x258F, 0x2587, -1))) + BLANK = " " + COLOUR_RESET = '\x1b[0m' + COLOUR_RGB = '\x1b[38;2;%d;%d;%dm' + COLOURS = {'BLACK': '\x1b[30m', 'RED': '\x1b[31m', 'GREEN': '\x1b[32m', + 'YELLOW': '\x1b[33m', 'BLUE': '\x1b[34m', 'MAGENTA': '\x1b[35m', + 'CYAN': '\x1b[36m', 'WHITE': '\x1b[37m'} + + def __init__(self, frac, default_len=10, charset=UTF, colour=None): + if not 0 <= frac <= 1: + warn("clamping frac to range [0, 1]", TqdmWarning, stacklevel=2) + frac = max(0, min(1, frac)) + assert default_len > 0 + self.frac = frac + self.default_len = default_len + self.charset = charset + self.colour = colour + + @property + def colour(self): + return self._colour + + @colour.setter + def colour(self, value): + if not value: + self._colour = None + return + try: + if value.upper() in self.COLOURS: + self._colour = self.COLOURS[value.upper()] + elif value[0] == '#' and len(value) == 7: + self._colour = self.COLOUR_RGB % tuple( + int(i, 16) for i in (value[1:3], value[3:5], value[5:7])) + else: + raise KeyError + except (KeyError, AttributeError): + warn(f"Unknown colour ({value}); valid choices:" + f" [hex (#00ff00), {', '.join(self.COLOURS)}]", TqdmWarning, stacklevel=2) + self._colour = None + + def __format__(self, format_spec): + if format_spec: + _type = format_spec[-1].lower() + try: + charset = {'a': self.ASCII, 'u': self.UTF, 'b': self.BLANK}[_type] + except KeyError: + charset = self.charset + else: + format_spec = format_spec[:-1] + if format_spec: + N_BARS = int(format_spec) + if N_BARS < 0: + N_BARS += self.default_len + else: + N_BARS = self.default_len + else: + charset = self.charset + N_BARS = self.default_len + + nsyms = len(charset) - 1 + bar_length, frac_bar_length = divmod(int(self.frac * N_BARS * nsyms), nsyms) + + res = charset[-1] * bar_length + if bar_length < N_BARS: # whitespace padding + res = res + charset[frac_bar_length] + charset[0] * (N_BARS - bar_length - 1) + return self.colour + res + self.COLOUR_RESET if self.colour else res + + +class EMA: + """ + Exponential moving average: smoothing to give progressively lower + weights to older values. + + Parameters + ---------- + smoothing : float, optional + Smoothing factor in range [0, 1], [default: 0.3]. + Increase to give more weight to recent values. + Ranges from 0 (yields old value) to 1 (yields new value). + """ + def __init__(self, smoothing=0.3): + self.alpha = smoothing + self.last = 0 + self.calls = 0 + + def __call__(self, x=None): + """ + Parameters + ---------- + x : float + New value to include in EMA. + """ + beta = 1 - self.alpha + if x is not None: + self.last = self.alpha * x + beta * self.last + self.calls += 1 + return self.last / (1 - beta ** self.calls) if self.calls else self.last + + +class tqdm(Comparable): + """ + Decorate an iterable object, returning an iterator which acts exactly + like the original iterable, but prints a dynamically updating + progressbar every time a value is requested. + + Parameters + ---------- + iterable : iterable, optional + Iterable to decorate with a progressbar. + Leave blank to manually manage the updates. + desc : str, optional + Prefix for the progressbar. + total : int or float, optional + The number of expected iterations. If unspecified, + len(iterable) is used if possible. If float("inf") or as a last + resort, only basic progress statistics are displayed + (no ETA, no progressbar). + If `gui` is True and this parameter needs subsequent updating, + specify an initial arbitrary large positive number, + e.g. 9e9. + leave : bool, optional + If [default: True], keeps all traces of the progressbar + upon termination of iteration. + If `None`, will leave only if `position` is `0`. + file : `io.TextIOWrapper` or `io.StringIO`, optional + Specifies where to output the progress messages + (default: sys.stderr). Uses `file.write(str)` and `file.flush()` + methods. For encoding, see `write_bytes`. + ncols : int, optional + The width of the entire output message. If specified, + dynamically resizes the progressbar to stay within this bound. + If unspecified, attempts to use environment width. The + fallback is a meter width of 10 and no limit for the counter and + statistics. If 0, will not print any meter (only stats). + mininterval : float, optional + Minimum progress display update interval [default: 0.1] seconds. + maxinterval : float, optional + Maximum progress display update interval [default: 10] seconds. + Automatically adjusts `miniters` to correspond to `mininterval` + after long display update lag. Only works if `dynamic_miniters` + or monitor thread is enabled. + miniters : int or float, optional + Minimum progress display update interval, in iterations. + If 0 and `dynamic_miniters`, will automatically adjust to equal + `mininterval` (more CPU efficient, good for tight loops). + If > 0, will skip display of specified number of iterations. + Tweak this and `mininterval` to get very efficient loops. + If your progress is erratic with both fast and slow iterations + (network, skipping items, etc) you should set miniters=1. + ascii : bool or str, optional + If unspecified or False, use unicode (smooth blocks) to fill + the meter. The fallback is to use ASCII characters " 123456789#". + disable : bool, optional + Whether to disable the entire progressbar wrapper + [default: False]. If set to None, disable on non-TTY. + unit : str, optional + String that will be used to define the unit of each iteration + [default: it]. + unit_scale : bool or int or float, optional + If 1 or True, the number of iterations will be reduced/scaled + automatically and a metric prefix following the + International System of Units standard will be added + (kilo, mega, etc.) [default: False]. If any other non-zero + number, will scale `total` and `n`. + dynamic_ncols : bool, optional + If set, constantly alters `ncols` and `nrows` to the + environment (allowing for window resizes) [default: False]. + smoothing : float, optional + Exponential moving average smoothing factor for speed estimates + (ignored in GUI mode). Ranges from 0 (average speed) to 1 + (current/instantaneous speed) [default: 0.3]. + bar_format : str, optional + Specify a custom bar string formatting. May impact performance. + [default: '{l_bar}{bar}{r_bar}'], where + l_bar='{desc}: {percentage:3.0f}%|' and + r_bar='| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, ' + '{rate_fmt}{postfix}]' + Possible vars: l_bar, bar, r_bar, n, n_fmt, total, total_fmt, + percentage, elapsed, elapsed_s, ncols, nrows, desc, unit, + rate, rate_fmt, rate_noinv, rate_noinv_fmt, + rate_inv, rate_inv_fmt, postfix, unit_divisor, + remaining, remaining_s, eta. + Note that a trailing ": " is automatically removed after {desc} + if the latter is empty. + initial : int or float, optional + The initial counter value. Useful when restarting a progress + bar [default: 0]. If using float, consider specifying `{n:.3f}` + or similar in `bar_format`, or specifying `unit_scale`. + position : int, optional + Specify the line offset to print this bar (starting from 0) + Automatic if unspecified. + Useful to manage multiple bars at once (eg, from threads). + postfix : dict or *, optional + Specify additional stats to display at the end of the bar. + Calls `set_postfix(**postfix)` if possible (dict). + unit_divisor : float, optional + [default: 1000], ignored unless `unit_scale` is True. + write_bytes : bool, optional + Whether to write bytes. If (default: False) will write unicode. + lock_args : tuple, optional + Passed to `refresh` for intermediate output + (initialisation, iterating, and updating). + nrows : int, optional + The screen height. If specified, hides nested bars outside this + bound. If unspecified, attempts to use environment height. + The fallback is 20. + colour : str, optional + Bar colour (e.g. 'green', '#00ff00'). + delay : float, optional + Don't display until [default: 0] seconds have elapsed. + gui : bool, optional + WARNING: internal parameter - do not use. + Use tqdm.gui.tqdm(...) instead. If set, will attempt to use + matplotlib animations for a graphical output [default: False]. + + Returns + ------- + out : decorated iterator. + """ + + monitor_interval = 10 # set to 0 to disable the thread + monitor = None + _instances = WeakSet() + + @staticmethod + def format_sizeof(num, suffix='', divisor=1000): + """ + Formats a number (greater than unity) with SI Order of Magnitude + prefixes. + + Parameters + ---------- + num : float + Number ( >= 1) to format. + suffix : str, optional + Post-postfix [default: '']. + divisor : float, optional + Divisor between prefixes [default: 1000]. + + Returns + ------- + out : str + Number with Order of Magnitude SI unit postfix. + """ + for unit in ['', 'k', 'M', 'G', 'T', 'P', 'E', 'Z']: + if abs(num) < 999.5: + if abs(num) < 99.95: + if abs(num) < 9.995: + return f'{num:1.2f}{unit}{suffix}' + return f'{num:2.1f}{unit}{suffix}' + return f'{num:3.0f}{unit}{suffix}' + num /= divisor + return f'{num:3.1f}Y{suffix}' + + @staticmethod + def format_interval(t): + """ + Formats a number of seconds as a clock time, [H:]MM:SS + + Parameters + ---------- + t : int + Number of seconds. + + Returns + ------- + out : str + [H:]MM:SS + """ + sign = '-' if t < 0 else '' + mins, s = divmod(abs(int(t)), 60) + h, m = divmod(mins, 60) + return f'{sign}{h:d}:{m:02d}:{s:02d}' if h else f'{sign}{m:02d}:{s:02d}' + + @staticmethod + def format_num(n): + """ + Intelligent scientific notation (.3g). + + Parameters + ---------- + n : int or float or Numeric + A Number. + + Returns + ------- + out : str + Formatted number. + """ + f = f'{n:.3g}'.replace('e+0', 'e+').replace('e-0', 'e-') + n = str(n) + return f if len(f) < len(n) else n + + @staticmethod + def status_printer(file): + """ + Manage the printing and in-place updating of a line of characters. + Note that if the string is longer than a line, then in-place + updating may not work (it will print a new line at each refresh). + """ + fp = file + fp_flush = getattr(fp, 'flush', lambda: None) # pragma: no cover + if fp in (sys.stderr, sys.stdout): + getattr(sys.stderr, 'flush', lambda: None)() + getattr(sys.stdout, 'flush', lambda: None)() + + def fp_write(s): + fp.write(str(s)) + fp_flush() + + last_len = [0] + + def print_status(s): + len_s = disp_len(s) + fp_write('\r' + s + (' ' * max(last_len[0] - len_s, 0))) + last_len[0] = len_s + + return print_status + + @staticmethod + def format_meter(n, total, elapsed, ncols=None, prefix='', ascii=False, unit='it', + unit_scale=False, rate=None, bar_format=None, postfix=None, + unit_divisor=1000, initial=0, colour=None, **extra_kwargs): + """ + Return a string-based progress bar given some parameters + + Parameters + ---------- + n : int or float + Number of finished iterations. + total : int or float + The expected total number of iterations. If meaningless (None), + only basic progress statistics are displayed (no ETA). + elapsed : float + Number of seconds passed since start. + ncols : int, optional + The width of the entire output message. If specified, + dynamically resizes `{bar}` to stay within this bound + [default: None]. If `0`, will not print any bar (only stats). + The fallback is `{bar:10}`. + prefix : str, optional + Prefix message (included in total width) [default: '']. + Use as {desc} in bar_format string. + ascii : bool, optional or str, optional + If not set, use unicode (smooth blocks) to fill the meter + [default: False]. The fallback is to use ASCII characters + " 123456789#". + unit : str, optional + The iteration unit [default: 'it']. + unit_scale : bool or int or float, optional + If 1 or True, the number of iterations will be printed with an + appropriate SI metric prefix (k = 10^3, M = 10^6, etc.) + [default: False]. If any other non-zero number, will scale + `total` and `n`. + rate : float, optional + Manual override for iteration rate. + If [default: None], uses n/elapsed. + bar_format : str, optional + Specify a custom bar string formatting. May impact performance. + [default: '{l_bar}{bar}{r_bar}'], where + l_bar='{desc}: {percentage:3.0f}%|' and + r_bar='| {n_fmt}/{total_fmt} [{elapsed}<{remaining}, ' + '{rate_fmt}{postfix}]' + Possible vars: l_bar, bar, r_bar, n, n_fmt, total, total_fmt, + percentage, elapsed, elapsed_s, ncols, nrows, desc, unit, + rate, rate_fmt, rate_noinv, rate_noinv_fmt, + rate_inv, rate_inv_fmt, postfix, unit_divisor, + remaining, remaining_s, eta. + Note that a trailing ": " is automatically removed after {desc} + if the latter is empty. + postfix : *, optional + Similar to `prefix`, but placed at the end + (e.g. for additional stats). + Note: postfix is usually a string (not a dict) for this method, + and will if possible be set to postfix = ', ' + postfix. + However other types are supported (#382). + unit_divisor : float, optional + [default: 1000], ignored unless `unit_scale` is True. + initial : int or float, optional + The initial counter value [default: 0]. + colour : str, optional + Bar colour (e.g. 'green', '#00ff00'). + + Returns + ------- + out : Formatted meter and stats, ready to display. + """ + + # sanity check: total + if total and n >= (total + 0.5): # allow float imprecision (#849) + total = None + + # apply custom scale if necessary + if unit_scale and unit_scale not in (True, 1): + if total: + total *= unit_scale + n *= unit_scale + if rate: + rate *= unit_scale # by default rate = self.avg_dn / self.avg_dt + unit_scale = False + + elapsed_str = tqdm.format_interval(elapsed) + + # if unspecified, attempt to use rate = average speed + # (we allow manual override since predicting time is an arcane art) + if rate is None and elapsed: + rate = (n - initial) / elapsed + inv_rate = 1 / rate if rate else None + format_sizeof = tqdm.format_sizeof + rate_noinv_fmt = ((format_sizeof(rate) if unit_scale else f'{rate:5.2f}') + if rate else '?') + unit + '/s' + rate_inv_fmt = ( + (format_sizeof(inv_rate) if unit_scale else f'{inv_rate:5.2f}') + if inv_rate else '?') + 's/' + unit + rate_fmt = rate_inv_fmt if inv_rate and inv_rate > 1 else rate_noinv_fmt + + if unit_scale: + n_fmt = format_sizeof(n, divisor=unit_divisor) + total_fmt = format_sizeof(total, divisor=unit_divisor) if total is not None else '?' + else: + n_fmt = str(n) + total_fmt = str(total) if total is not None else '?' + + try: + postfix = ', ' + postfix if postfix else '' + except TypeError: + pass + + remaining = (total - n) / rate if rate and total else 0 + remaining_str = tqdm.format_interval(remaining) if rate else '?' + try: + eta_dt = (datetime.now() + timedelta(seconds=remaining) + if rate and total else datetime.fromtimestamp(0, timezone.utc)) + except OverflowError: + eta_dt = datetime.max + + # format the stats displayed to the left and right sides of the bar + if prefix: + # old prefix setup work around + bool_prefix_colon_already = (prefix[-2:] == ": ") + l_bar = prefix if bool_prefix_colon_already else prefix + ": " + else: + l_bar = '' + + r_bar = f'| {n_fmt}/{total_fmt} [{elapsed_str}<{remaining_str}, {rate_fmt}{postfix}]' + + # Custom bar formatting + # Populate a dict with all available progress indicators + format_dict = { + # slight extension of self.format_dict + 'n': n, 'n_fmt': n_fmt, 'total': total, 'total_fmt': total_fmt, + 'elapsed': elapsed_str, 'elapsed_s': elapsed, + 'ncols': ncols, 'desc': prefix or '', 'unit': unit, + 'rate': inv_rate if inv_rate and inv_rate > 1 else rate, + 'rate_fmt': rate_fmt, 'rate_noinv': rate, + 'rate_noinv_fmt': rate_noinv_fmt, 'rate_inv': inv_rate, + 'rate_inv_fmt': rate_inv_fmt, + 'postfix': postfix, 'unit_divisor': unit_divisor, + 'colour': colour, + # plus more useful definitions + 'remaining': remaining_str, 'remaining_s': remaining, + 'l_bar': l_bar, 'r_bar': r_bar, 'eta': eta_dt, + **extra_kwargs} + + # total is known: we can predict some stats + if total: + # fractional and percentage progress + frac = n / total + percentage = frac * 100 + + l_bar += f'{percentage:3.0f}%|' + + if ncols == 0: + return l_bar[:-1] + r_bar[1:] + + format_dict.update(l_bar=l_bar) + if bar_format: + format_dict.update(percentage=percentage) + + # auto-remove colon for empty `{desc}` + if not prefix: + bar_format = bar_format.replace("{desc}: ", '') + else: + bar_format = "{l_bar}{bar}{r_bar}" + + full_bar = FormatReplace() + nobar = bar_format.format(bar=full_bar, **format_dict) + if not full_bar.format_called: + return nobar # no `{bar}`; nothing else to do + + # Formatting progress bar space available for bar's display + full_bar = Bar(frac, + max(1, ncols - disp_len(nobar)) if ncols else 10, + charset=Bar.ASCII if ascii is True else ascii or Bar.UTF, + colour=colour) + if not _is_ascii(full_bar.charset) and _is_ascii(bar_format): + bar_format = str(bar_format) + res = bar_format.format(bar=full_bar, **format_dict) + return disp_trim(res, ncols) if ncols else res + + elif bar_format: + # user-specified bar_format but no total + l_bar += '|' + format_dict.update(l_bar=l_bar, percentage=0) + full_bar = FormatReplace() + nobar = bar_format.format(bar=full_bar, **format_dict) + if not full_bar.format_called: + return nobar + full_bar = Bar(0, + max(1, ncols - disp_len(nobar)) if ncols else 10, + charset=Bar.BLANK, colour=colour) + res = bar_format.format(bar=full_bar, **format_dict) + return disp_trim(res, ncols) if ncols else res + else: + # no total: no progressbar, ETA, just progress stats + return (f'{(prefix + ": ") if prefix else ""}' + f'{n_fmt}{unit} [{elapsed_str}, {rate_fmt}{postfix}]') + + def __new__(cls, *_, **__): + instance = object.__new__(cls) + with cls.get_lock(): # also constructs lock if non-existent + cls._instances.add(instance) + # create monitoring thread + if cls.monitor_interval and (cls.monitor is None + or not cls.monitor.report()): + try: + cls.monitor = TMonitor(cls, cls.monitor_interval) + except Exception as e: # pragma: nocover + warn("tqdm:disabling monitor support" + " (monitor_interval = 0) due to:\n" + str(e), + TqdmMonitorWarning, stacklevel=2) + cls.monitor_interval = 0 + return instance + + @classmethod + def _get_free_pos(cls, instance=None): + """Skips specified instance.""" + positions = {abs(inst.pos) for inst in cls._instances + if inst is not instance and hasattr(inst, "pos")} + return min(set(range(len(positions) + 1)).difference(positions)) + + @classmethod + def _decr_instances(cls, instance): + """ + Remove from list and reposition another unfixed bar + to fill the new gap. + + This means that by default (where all nested bars are unfixed), + order is not maintained but screen flicker/blank space is minimised. + (tqdm<=4.44.1 moved ALL subsequent unfixed bars up.) + """ + with cls._lock: + try: + cls._instances.remove(instance) + except KeyError: + # if not instance.gui: # pragma: no cover + # raise + pass # py2: maybe magically removed already + # else: + if not instance.gui: + last = (instance.nrows or 20) - 1 + # find unfixed (`pos >= 0`) overflow (`pos >= nrows - 1`) + instances = list(filter( + lambda i: hasattr(i, "pos") and last <= i.pos, + cls._instances)) + # set first found to current `pos` + if instances: + inst = min(instances, key=lambda i: i.pos) + inst.clear(nolock=True) + inst.pos = abs(instance.pos) + + @classmethod + def write(cls, s, file=None, end="\n", nolock=False): + """Print a message via tqdm (without overlap with bars).""" + fp = file if file is not None else sys.stdout + with cls.external_write_mode(file=file, nolock=nolock): + # Write the message + fp.write(s) + fp.write(end) + + @classmethod + @contextmanager + def external_write_mode(cls, file=None, nolock=False): + """ + Disable tqdm within context and refresh tqdm when exits. + Useful when writing to standard output stream + """ + fp = file if file is not None else sys.stdout + + try: + if not nolock: + cls.get_lock().acquire() + # Clear all bars + inst_cleared = [] + for inst in getattr(cls, '_instances', []): + # Clear instance if in the target output file + # or if write output + tqdm output are both either + # sys.stdout or sys.stderr (because both are mixed in terminal) + if hasattr(inst, "start_t") and (inst.fp == fp or all( + f in (sys.stdout, sys.stderr) for f in (fp, inst.fp))): + inst.clear(nolock=True) + inst_cleared.append(inst) + yield + # Force refresh display of bars we cleared + for inst in inst_cleared: + inst.refresh(nolock=True) + finally: + if not nolock: + cls._lock.release() + + @classmethod + def set_lock(cls, lock): + """Set the global lock.""" + cls._lock = lock + + @classmethod + def get_lock(cls): + """Get the global lock. Construct it if it does not exist.""" + if not hasattr(cls, '_lock'): + cls._lock = TqdmDefaultWriteLock() + return cls._lock + + @classmethod + def pandas(cls, **tqdm_kwargs): + """ + Registers the current `tqdm` class with + pandas.core. + ( frame.DataFrame + | series.Series + | groupby.(generic.)DataFrameGroupBy + | groupby.(generic.)SeriesGroupBy + ).progress_apply + + A new instance will be created every time `progress_apply` is called, + and each instance will automatically `close()` upon completion. + + Parameters + ---------- + tqdm_kwargs : arguments for the tqdm instance + + Examples + -------- + >>> import pandas as pd + >>> import numpy as np + >>> from tqdm import tqdm + >>> from tqdm.gui import tqdm as tqdm_gui + >>> + >>> df = pd.DataFrame(np.random.randint(0, 100, (100000, 6))) + >>> tqdm.pandas(ncols=50) # can use tqdm_gui, optional kwargs, etc + >>> # Now you can use `progress_apply` instead of `apply` + >>> df.groupby(0).progress_apply(lambda x: x**2) + + References + ---------- + + """ + from warnings import catch_warnings, simplefilter + + from pandas.core.frame import DataFrame + from pandas.core.series import Series + try: + with catch_warnings(): + simplefilter("ignore", category=FutureWarning) + from pandas import Panel + except ImportError: # pandas>=1.2.0 + Panel = None + Rolling, Expanding = None, None + try: # pandas>=1.0.0 + from pandas.core.window.rolling import _Rolling_and_Expanding + except ImportError: + try: # pandas>=0.18.0 + from pandas.core.window import _Rolling_and_Expanding + except ImportError: # pandas>=1.2.0 + try: # pandas>=1.2.0 + from pandas.core.window.expanding import Expanding + from pandas.core.window.rolling import Rolling + _Rolling_and_Expanding = Rolling, Expanding + except ImportError: # pragma: no cover + _Rolling_and_Expanding = None + try: # pandas>=0.25.0 + from pandas.core.groupby.generic import SeriesGroupBy # , NDFrameGroupBy + from pandas.core.groupby.generic import DataFrameGroupBy + except ImportError: # pragma: no cover + try: # pandas>=0.23.0 + from pandas.core.groupby.groupby import DataFrameGroupBy, SeriesGroupBy + except ImportError: + from pandas.core.groupby import DataFrameGroupBy, SeriesGroupBy + try: # pandas>=0.23.0 + from pandas.core.groupby.groupby import GroupBy + except ImportError: # pragma: no cover + from pandas.core.groupby import GroupBy + + try: # pandas>=0.23.0 + from pandas.core.groupby.groupby import PanelGroupBy + except ImportError: + try: + from pandas.core.groupby import PanelGroupBy + except ImportError: # pandas>=0.25.0 + PanelGroupBy = None + + tqdm_kwargs = tqdm_kwargs.copy() + deprecated_t = [tqdm_kwargs.pop('deprecated_t', None)] + + def inner_generator(df_function='apply'): + def inner(df, func, *args, **kwargs): + """ + Parameters + ---------- + df : (DataFrame|Series)[GroupBy] + Data (may be grouped). + func : function + To be applied on the (grouped) data. + **kwargs : optional + Transmitted to `df.apply()`. + """ + + # Precompute total iterations + total = tqdm_kwargs.pop("total", getattr(df, 'ngroups', None)) + if total is None: # not grouped + if df_function == 'applymap': + total = df.size + elif isinstance(df, Series): + total = len(df) + elif (_Rolling_and_Expanding is None or + not isinstance(df, _Rolling_and_Expanding)): + # DataFrame or Panel + axis = kwargs.get('axis', 0) + if axis == 'index': + axis = 0 + elif axis == 'columns': + axis = 1 + # when axis=0, total is shape[axis1] + total = df.size // df.shape[axis] + + # Init bar + if deprecated_t[0] is not None: + t = deprecated_t[0] + deprecated_t[0] = None + else: + t = cls(total=total, **tqdm_kwargs) + + if len(args) > 0: + # *args intentionally not supported (see #244, #299) + TqdmDeprecationWarning( + "Except func, normal arguments are intentionally" + + " not supported by" + + " `(DataFrame|Series|GroupBy).progress_apply`." + + " Use keyword arguments instead.", + fp_write=getattr(t.fp, 'write', sys.stderr.write)) + + try: # pandas>=1.3.0,<3.0 + from pandas.core.common import is_builtin_func + except ImportError: # pandas<1.3.0 + is_builtin_func = getattr(df, '_is_builtin_func', lambda f: f) + try: + func = is_builtin_func(func) + except TypeError: + pass + + # Define bar updating wrapper + def wrapper(*args, **kwargs): + # update tbar correctly + # it seems `pandas apply` calls `func` twice + # on the first column/row to decide whether it can + # take a fast or slow code path; so stop when t.total==t.n + t.update(n=1 if not t.total or t.n < t.total else 0) + return func(*args, **kwargs) + + # Apply the provided function (in **kwargs) + # on the df using our wrapper (which provides bar updating) + try: + return getattr(df, df_function)(wrapper, **kwargs) + finally: + t.close() + + return inner + + # Monkeypatch pandas to provide easy methods + # Enable custom tqdm progress in pandas! + Series.progress_apply = inner_generator() + SeriesGroupBy.progress_apply = inner_generator() + Series.progress_map = inner_generator('map') + SeriesGroupBy.progress_map = inner_generator('map') + + DataFrame.progress_apply = inner_generator() + DataFrameGroupBy.progress_apply = inner_generator() + DataFrame.progress_applymap = inner_generator('applymap') + DataFrame.progress_map = inner_generator('map') + DataFrameGroupBy.progress_map = inner_generator('map') + + if Panel is not None: + Panel.progress_apply = inner_generator() + if PanelGroupBy is not None: + PanelGroupBy.progress_apply = inner_generator() + + GroupBy.progress_apply = inner_generator() + GroupBy.progress_aggregate = inner_generator('aggregate') + GroupBy.progress_transform = inner_generator('transform') + + if Rolling is not None and Expanding is not None: + Rolling.progress_apply = inner_generator() + Expanding.progress_apply = inner_generator() + elif _Rolling_and_Expanding is not None: + _Rolling_and_Expanding.progress_apply = inner_generator() + + # override defaults via env vars + @envwrap("TQDM_", is_method=True, types={'total': float, 'ncols': int, 'miniters': float, + 'position': int, 'nrows': int}) + def __init__(self, iterable=None, desc=None, total=None, leave=True, file=None, + ncols=None, mininterval=0.1, maxinterval=10.0, miniters=None, + ascii=None, disable=False, unit='it', unit_scale=False, + dynamic_ncols=False, smoothing=0.3, bar_format=None, initial=0, + position=None, postfix=None, unit_divisor=1000, write_bytes=False, + lock_args=None, nrows=None, colour=None, delay=0.0, gui=False, + **kwargs): + """see tqdm.tqdm for arguments""" + if file is None: + file = sys.stderr + + if write_bytes: + # Despite coercing unicode into bytes, py2 sys.std* streams + # should have bytes written to them. + file = SimpleTextIOWrapper( + file, encoding=getattr(file, 'encoding', None) or 'utf-8') + + file = DisableOnWriteError(file, tqdm_instance=self) + + if disable is None and hasattr(file, "isatty") and not file.isatty(): + disable = True + + if total is None and iterable is not None: + try: + total = len(iterable) + except (TypeError, AttributeError): + total = None + if total == float("inf"): + # Infinite iterations, behave same as unknown + total = None + + if disable: + self.iterable = iterable + self.disable = disable + with self._lock: + self.pos = self._get_free_pos(self) + self._instances.remove(self) + self.n = initial + self.total = total + self.leave = leave + return + + if kwargs: + self.disable = True + with self._lock: + self.pos = self._get_free_pos(self) + self._instances.remove(self) + raise ( + TqdmDeprecationWarning( + "`nested` is deprecated and automated.\n" + "Use `position` instead for manual control.\n", + fp_write=getattr(file, 'write', sys.stderr.write)) + if "nested" in kwargs else + TqdmKeyError("Unknown argument(s): " + str(kwargs))) + + # Preprocess the arguments + if ( + (ncols is None or nrows is None) and (file in (sys.stderr, sys.stdout)) + ) or dynamic_ncols: # pragma: no cover + if dynamic_ncols: + dynamic_ncols = _screen_shape_wrapper() + if dynamic_ncols: + ncols, nrows = dynamic_ncols(file) + else: + _dynamic_ncols = _screen_shape_wrapper() + if _dynamic_ncols: + _ncols, _nrows = _dynamic_ncols(file) + if ncols is None: + ncols = _ncols + if nrows is None: + nrows = _nrows + + if miniters is None: + miniters = 0 + dynamic_miniters = True + else: + dynamic_miniters = False + + if mininterval is None: + mininterval = 0 + + if maxinterval is None: + maxinterval = 0 + + if ascii is None: + ascii = not _supports_unicode(file) + + if bar_format and ascii is not True and not _is_ascii(ascii): + # Convert bar format into unicode since terminal uses unicode + bar_format = str(bar_format) + + if smoothing is None: + smoothing = 0 + + # Store the arguments + self.iterable = iterable + self.desc = desc or '' + self.total = total + self.leave = leave + self.fp = file + self.ncols = ncols + self.nrows = nrows + self.mininterval = mininterval + self.maxinterval = maxinterval + self.miniters = miniters + self.dynamic_miniters = dynamic_miniters + self.ascii = ascii + self.disable = disable + self.unit = unit + self.unit_scale = unit_scale + self.unit_divisor = unit_divisor + self.initial = initial + self.lock_args = lock_args + self.delay = delay + self.gui = gui + self.dynamic_ncols = dynamic_ncols + self.smoothing = smoothing + self._ema_dn = EMA(smoothing) + self._ema_dt = EMA(smoothing) + self._ema_miniters = EMA(smoothing) + self.bar_format = bar_format + self.postfix = None + self.colour = colour + self._time = time + if postfix: + try: + self.set_postfix(refresh=False, **postfix) + except TypeError: + self.postfix = postfix + + # Init the iterations counters + self.last_print_n = initial + self.n = initial + + # if nested, at initial sp() call we replace '\r' by '\n' to + # not overwrite the outer progress bar + with self._lock: + # mark fixed positions as negative + self.pos = self._get_free_pos(self) if position is None else -position + + if not gui: + # Initialize the screen printer + self.sp = self.status_printer(self.fp) + if delay <= 0: + self.refresh(lock_args=self.lock_args) + + # Init the time counter + self.last_print_t = self._time() + # NB: Avoid race conditions by setting start_t at the very end of init + self.start_t = self.last_print_t + + def __bool__(self): + if self.total is not None: + return self.total > 0 + if self.iterable is None: + raise TypeError('bool() undefined when iterable == total == None') + return bool(self.iterable) + + def __len__(self): + return ( + self.total if self.iterable is None + else self.iterable.shape[0] if hasattr(self.iterable, "shape") + else len(self.iterable) if hasattr(self.iterable, "__len__") + else self.iterable.__length_hint__() if hasattr(self.iterable, "__length_hint__") + else getattr(self, "total", None)) + + def __reversed__(self): + try: + orig = self.iterable + except AttributeError: + raise TypeError("'tqdm' object is not reversible") + else: + self.iterable = reversed(self.iterable) + return self.__iter__() + finally: + self.iterable = orig + + def __contains__(self, item): + contains = getattr(self.iterable, '__contains__', None) + return contains(item) if contains is not None else item in self.__iter__() + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + try: + self.close() + except AttributeError: + # maybe eager thread cleanup upon external error + if (exc_type, exc_value, traceback) == (None, None, None): + raise + warn("AttributeError ignored", TqdmWarning, stacklevel=2) + + def __del__(self): + self.close() + + def __str__(self): + return self.format_meter(**self.format_dict) + + @property + def _comparable(self): + return abs(getattr(self, "pos", 1 << 31)) + + def __hash__(self): + return id(self) + + def __iter__(self): + """Backward-compatibility to use: for x in tqdm(iterable)""" + + # Inlining instance variables as locals (speed optimisation) + iterable = self.iterable + + # If the bar is disabled, then just walk the iterable + # (note: keep this check outside the loop for performance) + if self.disable: + for obj in iterable: + yield obj + return + + mininterval = self.mininterval + last_print_t = self.last_print_t + last_print_n = self.last_print_n + min_start_t = self.start_t + self.delay + n = self.n + time = self._time + + try: + for obj in iterable: + yield obj + # Update and possibly print the progressbar. + # Note: does not call self.update(1) for speed optimisation. + n += 1 + + if n - last_print_n >= self.miniters: + cur_t = time() + dt = cur_t - last_print_t + if dt >= mininterval and cur_t >= min_start_t: + self.update(n - last_print_n) + last_print_n = self.last_print_n + last_print_t = self.last_print_t + finally: + self.n = n + self.close() + + def update(self, n=1): + """ + Manually update the progress bar, useful for streams + such as reading files. + E.g.: + >>> t = tqdm(total=filesize) # Initialise + >>> for current_buffer in stream: + ... ... + ... t.update(len(current_buffer)) + >>> t.close() + The last line is highly recommended, but possibly not necessary if + `t.update()` will be called in such a way that `filesize` will be + exactly reached and printed. + + Parameters + ---------- + n : int or float, optional + Increment to add to the internal counter of iterations + [default: 1]. If using float, consider specifying `{n:.3f}` + or similar in `bar_format`, or specifying `unit_scale`. + + Returns + ------- + out : bool or None + True if a `display()` was triggered. + """ + if self.disable: + return + + if n < 0: + self.last_print_n += n # for auto-refresh logic to work + self.n += n + + # check counter first to reduce calls to time() + if self.n - self.last_print_n >= self.miniters: + cur_t = self._time() + dt = cur_t - self.last_print_t + if dt >= self.mininterval and cur_t >= self.start_t + self.delay: + cur_t = self._time() + dn = self.n - self.last_print_n # >= n + if self.smoothing and dt and dn: + # EMA (not just overall average) + self._ema_dn(dn) + self._ema_dt(dt) + self.refresh(lock_args=self.lock_args) + if self.dynamic_miniters: + # If no `miniters` was specified, adjust automatically to the + # maximum iteration rate seen so far between two prints. + # e.g.: After running `tqdm.update(5)`, subsequent + # calls to `tqdm.update()` will only cause an update after + # at least 5 more iterations. + if self.maxinterval and dt >= self.maxinterval: + self.miniters = dn * (self.mininterval or self.maxinterval) / dt + elif self.smoothing: + # EMA miniters update + self.miniters = self._ema_miniters( + dn * (self.mininterval / dt if self.mininterval and dt + else 1)) + else: + # max iters between two prints + self.miniters = max(self.miniters, dn) + + # Store old values for next call + self.last_print_n = self.n + self.last_print_t = cur_t + return True + + def close(self): + """Cleanup and (if leave=False) close the progressbar.""" + if self.disable: + return + + # Prevent multiple closures + self.disable = True + + # decrement instance pos and remove from internal set + pos = abs(self.pos) + self._decr_instances(self) + + if self.last_print_t < self.start_t + self.delay: + # haven't ever displayed; nothing to clear + return + + # GUI mode + if getattr(self, 'sp', None) is None: + return + + # annoyingly, _supports_unicode isn't good enough + def fp_write(s): + self.fp.write(str(s)) + + try: + fp_write('') + except ValueError as e: + if 'closed' in str(e): + return + raise # pragma: no cover + + leave = pos == 0 if self.leave is None else self.leave + + with self._lock: + if leave: + # stats for overall rate (no weighted average) + self._ema_dt = lambda: None + self.display(pos=0) + fp_write('\n') + else: + # clear previous display + if self.display(msg='', pos=pos) and not pos: + fp_write('\r') + + def clear(self, nolock=False): + """Clear current bar display.""" + if self.disable: + return + + if not nolock: + self._lock.acquire() + pos = abs(self.pos) + if pos < (self.nrows or 20): + self.moveto(pos) + self.sp('') + self.fp.write('\r') # place cursor back at the beginning of line + self.moveto(-pos) + if not nolock: + self._lock.release() + + def refresh(self, nolock=False, lock_args=None): + """ + Force refresh the display of this bar. + + Parameters + ---------- + nolock : bool, optional + If `True`, does not lock. + If [default: `False`]: calls `acquire()` on internal lock. + lock_args : tuple, optional + Passed to internal lock's `acquire()`. + If specified, will only `display()` if `acquire()` returns `True`. + """ + if self.disable: + return + + if not nolock: + if lock_args: + if not self._lock.acquire(*lock_args): + return False + else: + self._lock.acquire() + self.display() + if not nolock: + self._lock.release() + return True + + def unpause(self): + """Restart tqdm timer from last print time.""" + if self.disable: + return + cur_t = self._time() + self.start_t += cur_t - self.last_print_t + self.last_print_t = cur_t + + def reset(self, total=None): + """ + Resets to 0 iterations for repeated use. + + Consider combining with `leave=True`. + + Parameters + ---------- + total : int or float, optional. Total to use for the new bar. + """ + self.n = 0 + if total is not None: + self.total = total + if self.disable: + return + self.last_print_n = 0 + self.last_print_t = self.start_t = self._time() + self._ema_dn = EMA(self.smoothing) + self._ema_dt = EMA(self.smoothing) + self._ema_miniters = EMA(self.smoothing) + self.refresh() + + def set_description(self, desc=None, refresh=True): + """ + Set/modify description of the progress bar. + + Parameters + ---------- + desc : str, optional + refresh : bool, optional + Forces refresh [default: True]. + """ + self.desc = desc + ': ' if desc else '' + if refresh: + self.refresh() + + def set_description_str(self, desc=None, refresh=True): + """Set/modify description without ': ' appended.""" + self.desc = desc or '' + if refresh: + self.refresh() + + def set_postfix(self, ordered_dict=None, refresh=True, **kwargs): + """ + Set/modify postfix (additional stats) + with automatic formatting based on datatype. + + Parameters + ---------- + ordered_dict : dict or OrderedDict, optional + refresh : bool, optional + Forces refresh [default: True]. + kwargs : dict, optional + """ + # Sort in alphabetical order to be more deterministic + postfix = OrderedDict([] if ordered_dict is None else ordered_dict) + for key in sorted(kwargs.keys()): + postfix[key] = kwargs[key] + # Preprocess stats according to datatype + for key in postfix.keys(): + # Number: limit the length of the string + if isinstance(postfix[key], Number): + postfix[key] = self.format_num(postfix[key]) + # Else for any other type, try to get the string conversion + elif not isinstance(postfix[key], str): + postfix[key] = str(postfix[key]) + # Else if it's a string, don't need to preprocess anything + # Stitch together to get the final postfix + self.postfix = ', '.join(key + '=' + postfix[key].strip() + for key in postfix.keys()) + if refresh: + self.refresh() + + def set_postfix_str(self, s='', refresh=True): + """ + Postfix without dictionary expansion, similar to prefix handling. + """ + self.postfix = str(s) + if refresh: + self.refresh() + + def moveto(self, n): + # TODO: private method + self.fp.write('\n' * n + _term_move_up() * -n) + getattr(self.fp, 'flush', lambda: None)() + + @property + def format_dict(self): + """Public API for read-only member access.""" + if self.disable and not hasattr(self, 'unit'): + return defaultdict(lambda: None, { + 'n': self.n, 'total': self.total, 'elapsed': 0, 'unit': 'it'}) + if self.dynamic_ncols: + self.ncols, self.nrows = self.dynamic_ncols(self.fp) + return { + 'n': self.n, 'total': self.total, + 'elapsed': self._time() - self.start_t if hasattr(self, 'start_t') else 0, + 'ncols': self.ncols, 'nrows': self.nrows, 'prefix': self.desc, + 'ascii': self.ascii, 'unit': self.unit, 'unit_scale': self.unit_scale, + 'rate': self._ema_dn() / self._ema_dt() if self._ema_dt() else None, + 'bar_format': self.bar_format, 'postfix': self.postfix, + 'unit_divisor': self.unit_divisor, 'initial': self.initial, + 'colour': self.colour} + + def display(self, msg=None, pos=None): + """ + Use `self.sp` to display `msg` in the specified `pos`. + + Consider overloading this function when inheriting to use e.g.: + `self.some_frontend(**self.format_dict)` instead of `self.sp`. + + Parameters + ---------- + msg : str, optional. What to display (default: `repr(self)`). + pos : int, optional. Position to `moveto` + (default: `abs(self.pos)`). + """ + if pos is None: + pos = abs(self.pos) + + nrows = self.nrows or 20 + if pos >= nrows - 1: + if pos >= nrows: + return False + if msg or msg is None: # override at `nrows - 1` + msg = " ... (more hidden) ..." + + if not hasattr(self, "sp"): + raise TqdmDeprecationWarning( + "Please use `tqdm.gui.tqdm(...)`" + " instead of `tqdm(..., gui=True)`\n", + fp_write=getattr(self.fp, 'write', sys.stderr.write)) + + if pos: + self.moveto(pos) + self.sp(self.__str__() if msg is None else msg) + if pos: + self.moveto(-pos) + return True + + @classmethod + @contextmanager + def wrapattr(cls, stream, method, total=None, bytes=True, **tqdm_kwargs): + """ + stream : file-like object. + method : str, "read" or "write". The result of `read()` and + the first argument of `write()` should have a `len()`. + + >>> with tqdm.wrapattr(file_obj, "read", total=file_obj.size) as fobj: + ... while True: + ... chunk = fobj.read(chunk_size) + ... if not chunk: + ... break + """ + with cls(total=total, **tqdm_kwargs) as t: + if bytes: + t.unit = "B" + t.unit_scale = True + t.unit_divisor = 1024 + yield CallbackIOWrapper(t.update, stream, method) + + +def trange(*args, **kwargs): + """Shortcut for tqdm(range(*args), **kwargs).""" + return tqdm(range(*args), **kwargs) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/tk.py b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/tk.py new file mode 100644 index 0000000000000000000000000000000000000000..788303c8687e007338ce816bf9afeec8581f0188 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/tk.py @@ -0,0 +1,196 @@ +""" +Tkinter GUI progressbar decorator for iterators. + +Usage: +>>> from tqdm.tk import trange, tqdm +>>> for i in trange(10): +... ... +""" +import re +import sys +import tkinter +import tkinter.ttk as ttk +from warnings import warn + +from .std import TqdmExperimentalWarning, TqdmWarning +from .std import tqdm as std_tqdm + +__author__ = {"github.com/": ["richardsheridan", "casperdcl"]} +__all__ = ['tqdm_tk', 'ttkrange', 'tqdm', 'trange'] + + +class tqdm_tk(std_tqdm): # pragma: no cover + """ + Experimental Tkinter GUI version of tqdm! + + Note: Window interactivity suffers if `tqdm_tk` is not running within + a Tkinter mainloop and values are generated infrequently. In this case, + consider calling `tqdm_tk.refresh()` frequently in the Tk thread. + """ + + # TODO: @classmethod: write()? + + def __init__(self, *args, **kwargs): + """ + This class accepts the following parameters *in addition* to + the parameters accepted by `tqdm`. + + Parameters + ---------- + grab : bool, optional + Grab the input across all windows of the process. + tk_parent : `tkinter.Wm`, optional + Parent Tk window. + cancel_callback : Callable, optional + Create a cancel button and set `cancel_callback` to be called + when the cancel or window close button is clicked. + """ + kwargs = kwargs.copy() + kwargs['gui'] = True + # convert disable = None to False + kwargs['disable'] = bool(kwargs.get('disable', False)) + self._warn_leave = 'leave' in kwargs + grab = kwargs.pop('grab', False) + tk_parent = kwargs.pop('tk_parent', None) + self._cancel_callback = kwargs.pop('cancel_callback', None) + super().__init__(*args, **kwargs) + + if self.disable: + return + + if tk_parent is None: # Discover parent widget + try: + tk_parent = tkinter._default_root + except AttributeError: + raise AttributeError( + "`tk_parent` required when using `tkinter.NoDefaultRoot()`") + if tk_parent is None: # use new default root window as display + self._tk_window = tkinter.Tk() + else: # some other windows already exist + self._tk_window = tkinter.Toplevel() + else: + self._tk_window = tkinter.Toplevel(tk_parent) + + warn("GUI is experimental/alpha", TqdmExperimentalWarning, stacklevel=2) + self._tk_dispatching = self._tk_dispatching_helper() + + self._tk_window.protocol("WM_DELETE_WINDOW", self.cancel) + self._tk_window.wm_title(self.desc) + self._tk_window.wm_attributes("-topmost", 1) + self._tk_window.after(0, lambda: self._tk_window.wm_attributes("-topmost", 0)) + self._tk_n_var = tkinter.DoubleVar(self._tk_window, value=0) + self._tk_text_var = tkinter.StringVar(self._tk_window) + pbar_frame = ttk.Frame(self._tk_window, padding=5) + pbar_frame.pack() + _tk_label = ttk.Label(pbar_frame, textvariable=self._tk_text_var, + wraplength=600, anchor="center", justify="center") + _tk_label.pack() + self._tk_pbar = ttk.Progressbar( + pbar_frame, variable=self._tk_n_var, length=450) + if self.total is not None: + self._tk_pbar.configure(maximum=self.total) + else: + self._tk_pbar.configure(mode="indeterminate") + self._tk_pbar.pack() + if self._cancel_callback is not None: + _tk_button = ttk.Button(pbar_frame, text="Cancel", command=self.cancel) + _tk_button.pack() + if grab: + self._tk_window.grab_set() + + def close(self): + if self.disable: + return + + self.disable = True + + with self.get_lock(): + self._instances.remove(self) + + def _close(): + self._tk_window.after('idle', self._tk_window.destroy) + if not self._tk_dispatching: + self._tk_window.update() + + self._tk_window.protocol("WM_DELETE_WINDOW", _close) + + # if leave is set but we are self-dispatching, the left window is + # totally unresponsive unless the user manually dispatches + if not self.leave: + _close() + elif not self._tk_dispatching: + if self._warn_leave: + warn("leave flag ignored if not in tkinter mainloop", + TqdmWarning, stacklevel=2) + _close() + + def clear(self, *_, **__): + pass + + def display(self, *_, **__): + self._tk_n_var.set(self.n) + d = self.format_dict + # remove {bar} + d['bar_format'] = (d['bar_format'] or "{l_bar}{r_bar}").replace( + "{bar}", "") + msg = self.format_meter(**d) + if '' in msg: + msg = "".join(re.split(r'\|?\|?', msg, maxsplit=1)) + self._tk_text_var.set(msg) + if not self._tk_dispatching: + self._tk_window.update() + + def set_description(self, desc=None, refresh=True): + self.set_description_str(desc, refresh) + + def set_description_str(self, desc=None, refresh=True): + self.desc = desc + if not self.disable: + self._tk_window.wm_title(desc) + if refresh and not self._tk_dispatching: + self._tk_window.update() + + def cancel(self): + """ + `cancel_callback()` followed by `close()` + when close/cancel buttons clicked. + """ + if self._cancel_callback is not None: + self._cancel_callback() + self.close() + + def reset(self, total=None): + """ + Resets to 0 iterations for repeated use. + + Parameters + ---------- + total : int or float, optional. Total to use for the new bar. + """ + if hasattr(self, '_tk_pbar'): + if total is None: + self._tk_pbar.configure(maximum=100, mode="indeterminate") + else: + self._tk_pbar.configure(maximum=total, mode="determinate") + super().reset(total=total) + + @staticmethod + def _tk_dispatching_helper(): + """determine if Tkinter mainloop is dispatching events""" + codes = {tkinter.mainloop.__code__, tkinter.Misc.mainloop.__code__} + for frame in sys._current_frames().values(): + while frame: + if frame.f_code in codes: + return True + frame = frame.f_back + return False + + +def ttkrange(*args, **kwargs): + """Shortcut for `tqdm.tk.tqdm(range(*args), **kwargs)`.""" + return tqdm_tk(range(*args), **kwargs) + + +# Aliases +tqdm = tqdm_tk +trange = ttkrange diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/tqdm.1 b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/tqdm.1 new file mode 100644 index 0000000000000000000000000000000000000000..fe29b8a7d503aaada0305707c934086997da2d29 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/tqdm.1 @@ -0,0 +1,243 @@ +.\" Automatically generated by Pandoc 3.8.3 +.\" +.TH "TQDM" "1" "2015\-2026" "tqdm User Manuals" +.SH NAME +tqdm \- fast, extensible progress bar for Python and CLI +.SH SYNOPSIS +tqdm [\f[I]options\f[R]] +.SH DESCRIPTION +See \c +.UR https://github.com/tqdm/tqdm +.UE \c +\&. +Can be used as a pipe: +.IP +.EX +$ \f[I]# count lines of code\f[R] +$ cat *.py \f[B]|\f[R] tqdm \f[B]|\f[R] wc \-l +327it [00:00, 981773.38it/s] +327 + +$ \f[I]# find all files\f[R] +$ find . \-name \(dq*.py\(dq \f[B]|\f[R] tqdm \f[B]|\f[R] wc \-l +432it [00:00, 833842.30it/s] +432 + +\f[I]# ... and more info\f[R] +$ find . \-name \(aq*.py\(aq \-exec wc \-l \(rs{} \(rs; \(rs + \f[B]|\f[R] tqdm \-\-total 432 \-\-unit files \-\-desc counting \(rs + \f[B]|\f[R] awk \(aq{ sum += $1 }; END { print sum }\(aq +counting: 100%\f[B]|\f[R]█████████\f[B]|\f[R] 432/432 [00:00<00:00, 794361.83files/s] +131998 +.EE +.SH OPTIONS +.TP +\-h, \-\-help +Print this help and exit. +.TP +\-v, \-\-version +Print version and exit. +.TP +\-\-desc=\f[I]desc\f[R] +str, optional. +Prefix for the progressbar. +.TP +\-\-total=\f[I]total\f[R] +int or float, optional. +The number of expected iterations. +If unspecified, len(iterable) is used if possible. +If float(\(lqinf\(rq) or as a last resort, only basic progress +statistics are displayed (no ETA, no progressbar). +If \f[CR]gui\f[R] is True and this parameter needs subsequent updating, +specify an initial arbitrary large positive number, e.g.\ 9e9. +.TP +\-\-leave +bool, optional. +If [default: True], keeps all traces of the progressbar upon termination +of iteration. +If \f[CR]None\f[R], will leave only if \f[CR]position\f[R] is +\f[CR]0\f[R]. +.TP +\-\-ncols=\f[I]ncols\f[R] +int, optional. +The width of the entire output message. +If specified, dynamically resizes the progressbar to stay within this +bound. +If unspecified, attempts to use environment width. +The fallback is a meter width of 10 and no limit for the counter and +statistics. +If 0, will not print any meter (only stats). +.TP +\-\-mininterval=\f[I]mininterval\f[R] +float, optional. +Minimum progress display update interval [default: 0.1] seconds. +.TP +\-\-maxinterval=\f[I]maxinterval\f[R] +float, optional. +Maximum progress display update interval [default: 10] seconds. +Automatically adjusts \f[CR]miniters\f[R] to correspond to +\f[CR]mininterval\f[R] after long display update lag. +Only works if \f[CR]dynamic_miniters\f[R] or monitor thread is enabled. +.TP +\-\-miniters=\f[I]miniters\f[R] +int or float, optional. +Minimum progress display update interval, in iterations. +If 0 and \f[CR]dynamic_miniters\f[R], will automatically adjust to equal +\f[CR]mininterval\f[R] (more CPU efficient, good for tight loops). +If > 0, will skip display of specified number of iterations. +Tweak this and \f[CR]mininterval\f[R] to get very efficient loops. +If your progress is erratic with both fast and slow iterations (network, +skipping items, etc) you should set miniters=1. +.TP +\-\-ascii=\f[I]ascii\f[R] +bool or str, optional. +If unspecified or False, use unicode (smooth blocks) to fill the meter. +The fallback is to use ASCII characters \(rq 123456789#\(lq. +.TP +\-\-disable +bool, optional. +Whether to disable the entire progressbar wrapper [default: False]. +If set to None, disable on non\-TTY. +.TP +\-\-unit=\f[I]unit\f[R] +str, optional. +String that will be used to define the unit of each iteration [default: +it]. +.TP +\-\-unit\-scale=\f[I]unit_scale\f[R] +bool or int or float, optional. +If 1 or True, the number of iterations will be reduced/scaled +automatically and a metric prefix following the International System of +Units standard will be added (kilo, mega, etc.) +[default: False]. +If any other non\-zero number, will scale \f[CR]total\f[R] and +\f[CR]n\f[R]. +.TP +\-\-dynamic\-ncols +bool, optional. +If set, constantly alters \f[CR]ncols\f[R] and \f[CR]nrows\f[R] to the +environment (allowing for window resizes) [default: False]. +.TP +\-\-smoothing=\f[I]smoothing\f[R] +float, optional. +Exponential moving average smoothing factor for speed estimates (ignored +in GUI mode). +Ranges from 0 (average speed) to 1 (current/instantaneous speed) +[default: 0.3]. +.TP +\-\-bar\-format=\f[I]bar_format\f[R] +str, optional. +Specify a custom bar string formatting. +May impact performance. +[default: `{l_bar}{bar}{r_bar}'], where l_bar=`{desc}: +{percentage:3.0f}%|' and r_bar=`| {n_fmt}/{total_fmt} +[{elapsed}<{remaining}, \(cq \(cq{rate_fmt}{postfix}]' Possible vars: +l_bar, bar, r_bar, n, n_fmt, total, total_fmt, percentage, elapsed, +elapsed_s, ncols, nrows, desc, unit, rate, rate_fmt, rate_noinv, +rate_noinv_fmt, rate_inv, rate_inv_fmt, postfix, unit_divisor, +remaining, remaining_s, eta. +Note that a trailing \(lq:\(rq is automatically removed after {desc} if +the latter is empty. +.TP +\-\-initial=\f[I]initial\f[R] +int or float, optional. +The initial counter value. +Useful when restarting a progress bar [default: 0]. +If using float, consider specifying \f[CR]{n:.3f}\f[R] or similar in +\f[CR]bar_format\f[R], or specifying \f[CR]unit_scale\f[R]. +.TP +\-\-position=\f[I]position\f[R] +int, optional. +Specify the line offset to print this bar (starting from 0) Automatic if +unspecified. +Useful to manage multiple bars at once (eg, from threads). +.TP +\-\-postfix=\f[I]postfix\f[R] +dict or *, optional. +Specify additional stats to display at the end of the bar. +Calls \f[CR]set_postfix(**postfix)\f[R] if possible (dict). +.TP +\-\-unit\-divisor=\f[I]unit_divisor\f[R] +float, optional. +[default: 1000], ignored unless \f[CR]unit_scale\f[R] is True. +.TP +\-\-write\-bytes +bool, optional. +Whether to write bytes. +If (default: False) will write unicode. +.TP +\-\-lock\-args=\f[I]lock_args\f[R] +tuple, optional. +Passed to \f[CR]refresh\f[R] for intermediate output (initialisation, +iterating, and updating). +.TP +\-\-nrows=\f[I]nrows\f[R] +int, optional. +The screen height. +If specified, hides nested bars outside this bound. +If unspecified, attempts to use environment height. +The fallback is 20. +.TP +\-\-colour=\f[I]colour\f[R] +str, optional. +Bar colour (e.g.\ `green', `#00ff00'). +.TP +\-\-delay=\f[I]delay\f[R] +float, optional. +Don\(cqt display until [default: 0] seconds have elapsed. +.TP +\-\-delim=\f[I]delim\f[R] +chr, optional. +Delimiting character [default: `\(rsn']. +Use `\(rs0' for null. +N.B.: on Windows systems, Python converts `\(rsn' to `\(rsr\(rsn'. +.TP +\-\-buf\-size=\f[I]buf_size\f[R] +int, optional. +String buffer size in bytes [default: 256] used when \f[CR]delim\f[R] is +specified. +.TP +\-\-bytes +bool, optional. +If true, will count bytes, ignore \f[CR]delim\f[R], and default +\f[CR]unit_scale\f[R] to True, \f[CR]unit_divisor\f[R] to 1024, and +\f[CR]unit\f[R] to `B'. +.TP +\-\-tee +bool, optional. +If true, passes \f[CR]stdin\f[R] to both \f[CR]stderr\f[R] and +\f[CR]stdout\f[R]. +.TP +\-\-update +bool, optional. +If true, will treat input as newly elapsed iterations, i.e.\ numbers to +pass to \f[CR]update()\f[R]. +Note that this is slow (\(ti2e5 it/s) since every input must be decoded +as a number. +.TP +\-\-update\-to +bool, optional. +If true, will treat input as total elapsed iterations, i.e.\ numbers to +assign to \f[CR]self.n\f[R]. +Note that this is slow (\(ti2e5 it/s) since every input must be decoded +as a number. +.TP +\-\-null +bool, optional. +If true, will discard input (no stdout). +.TP +\-\-manpath=\f[I]manpath\f[R] +str, optional. +Directory in which to install tqdm man pages. +.TP +\-\-comppath=\f[I]comppath\f[R] +str, optional. +Directory in which to place tqdm completion. +.TP +\-\-log=\f[I]log\f[R] +str, optional. +CRITICAL|FATAL|ERROR|WARN(ING)|[default: `INFO']|DEBUG|NOTSET. +.SH AUTHORS +tqdm developers \c +.UR https://github.com/tqdm +.UE \c. diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/utils.py b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..ab454de7c2a7f6c8e47dd9f61e5d288351bd143b --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/utils.py @@ -0,0 +1,399 @@ +""" +General helpers required for `tqdm.std`. +""" +import os +import re +import sys +from functools import partial, partialmethod, wraps +from inspect import signature +# TODO consider using wcswidth third-party package for 0-width characters +from unicodedata import east_asian_width +from warnings import warn +from weakref import proxy + +_range, _unich, _unicode, _basestring = range, chr, str, str +CUR_OS = sys.platform +IS_WIN = any(CUR_OS.startswith(i) for i in ['win32', 'cygwin']) +IS_NIX = any(CUR_OS.startswith(i) for i in ['aix', 'linux', 'darwin', 'freebsd']) +RE_ANSI = re.compile(r"\x1b\[[;\d]*[A-Za-z]") + +try: + if IS_WIN: + import colorama + else: + raise ImportError +except ImportError: + colorama = None +else: + try: + colorama.init(strip=False) + except TypeError: + colorama.init() + + +def envwrap(prefix, types=None, is_method=False): + """ + Override parameter defaults via `os.environ[prefix + param_name]`. + Maps UPPER_CASE env vars map to lower_case param names. + camelCase isn't supported (because Windows ignores case). + + Precedence (highest first): + + - call (`foo(a=3)`) + - environ (`FOO_A=2`) + - signature (`def foo(a=1)`) + + Parameters + ---------- + prefix : str + Env var prefix, e.g. "FOO_" + types : dict, optional + Fallback mappings `{'param_name': type, ...}` if types cannot be + inferred from function signature. + Consider using `types=collections.defaultdict(lambda: ast.literal_eval)`. + is_method : bool, optional + Whether to use `functools.partialmethod`. If (default: False) use `functools.partial`. + + Examples + -------- + ``` + $ cat foo.py + from tqdm.utils import envwrap + @envwrap("FOO_") + def test(a=1, b=2, c=3): + print(f"received: a={a}, b={b}, c={c}") + + $ FOO_A=42 FOO_C=1337 python -c 'import foo; foo.test(c=99)' + received: a=42, b=2, c=99 + ``` + """ + if types is None: + types = {} + i = len(prefix) + env_overrides = {k[i:].lower(): v for k, v in os.environ.items() if k.startswith(prefix)} + part = partialmethod if is_method else partial + + def wrap(func): + params = signature(func).parameters + # ignore unknown env vars + overrides = {k: v for k, v in env_overrides.items() if k in params} + # infer overrides' `type`s + for k in overrides: + param = params[k] + if param.annotation is not param.empty: # typehints + for typ in getattr(param.annotation, '__args__', (param.annotation,)): + try: + overrides[k] = typ(overrides[k]) + except Exception: + pass + else: + break + elif param.default is not None: # type of default value + overrides[k] = type(param.default)(overrides[k]) + else: + try: # `types` fallback + overrides[k] = types[k](overrides[k]) + except KeyError: # keep unconverted (`str`) + pass + return part(func, **overrides) + return wrap + + +class FormatReplace: + """ + >>> a = FormatReplace('something') + >>> f"{a:5d}" + 'something' + """ # NOQA: P102 + def __init__(self, replace=''): + self.replace = replace + self.format_called = 0 + + def __format__(self, _): + self.format_called += 1 + return self.replace + + +class Comparable: + """Assumes child has self._comparable attr/@property""" + def __lt__(self, other): + return self._comparable < other._comparable + + def __le__(self, other): + return (self < other) or (self == other) + + def __eq__(self, other): + return self._comparable == other._comparable + + def __ne__(self, other): + return not self == other + + def __gt__(self, other): + return not self <= other + + def __ge__(self, other): + return not self < other + + +class ObjectWrapper: + def __getattr__(self, name): + return getattr(self._wrapped, name) + + def __setattr__(self, name, value): + return setattr(self._wrapped, name, value) + + def wrapper_getattr(self, name): + """Actual `self.getattr` rather than self._wrapped.getattr""" + try: + return object.__getattr__(self, name) + except AttributeError: # py2 + return getattr(self, name) + + def wrapper_setattr(self, name, value): + """Actual `self.setattr` rather than self._wrapped.setattr""" + return object.__setattr__(self, name, value) + + def __init__(self, wrapped): + """ + Thin wrapper around a given object + """ + self.wrapper_setattr('_wrapped', wrapped) + + +class SimpleTextIOWrapper(ObjectWrapper): + """ + Change only `.write()` of the wrapped object by encoding the passed + value and passing the result to the wrapped object's `.write()` method. + """ + # pylint: disable=too-few-public-methods + def __init__(self, wrapped, encoding): + super().__init__(wrapped) + self.wrapper_setattr('encoding', encoding) + + def write(self, s): + """ + Encode `s` and pass to the wrapped object's `.write()` method. + """ + return self._wrapped.write(s.encode(self.wrapper_getattr('encoding'))) + + def __eq__(self, other): + return self._wrapped == getattr(other, '_wrapped', other) + + +class DisableOnWriteError(ObjectWrapper): + """ + Disable the given `tqdm_instance` upon `write()` or `flush()` errors. + """ + @staticmethod + def disable_on_exception(tqdm_instance, func): + """ + Quietly set `tqdm_instance.miniters=inf` if `func` raises `errno=5`. + """ + tqdm_instance = proxy(tqdm_instance) + + def inner(*args, **kwargs): + try: + return func(*args, **kwargs) + except OSError as e: + if e.errno != 5: + raise + try: + tqdm_instance.miniters = float('inf') + except ReferenceError: + pass + except ValueError as e: + if 'closed' not in str(e): + raise + try: + tqdm_instance.miniters = float('inf') + except ReferenceError: + pass + return inner + + def __init__(self, wrapped, tqdm_instance): # noqa: B042 + super().__init__(wrapped) + if hasattr(wrapped, 'write'): + self.wrapper_setattr( + 'write', self.disable_on_exception(tqdm_instance, wrapped.write)) + if hasattr(wrapped, 'flush'): + self.wrapper_setattr( + 'flush', self.disable_on_exception(tqdm_instance, wrapped.flush)) + + def __eq__(self, other): + return self._wrapped == getattr(other, '_wrapped', other) + + +class CallbackIOWrapper(ObjectWrapper): + def __init__(self, callback, stream, method="read"): + """ + Wrap a given `file`-like object's `read()` or `write()` to report + lengths to the given `callback` + """ + super().__init__(stream) + func = getattr(stream, method) + if method == "write": + @wraps(func) + def write(data, *args, **kwargs): + res = func(data, *args, **kwargs) + callback(len(data)) + return res + self.wrapper_setattr('write', write) + elif method == "read": + @wraps(func) + def read(*args, **kwargs): + data = func(*args, **kwargs) + callback(len(data)) + return data + self.wrapper_setattr('read', read) + else: + raise KeyError("Can only wrap read/write methods") + + +def _is_utf(encoding): + try: + '\u2588\u2589'.encode(encoding) + except UnicodeEncodeError: + return False + except Exception: + try: + return encoding.lower().startswith('utf-') or ('U8' == encoding) + except Exception: + return False + else: + return True + + +def _supports_unicode(fp): + try: + return _is_utf(fp.encoding) + except AttributeError: + return False + + +def _is_ascii(s): + if isinstance(s, str): + for c in s: + if ord(c) > 255: + return False + return True + return _supports_unicode(s) + + +def _screen_shape_wrapper(): # pragma: no cover + """ + Return a function which returns console dimensions (width, height). + Supported: linux, osx, windows, cygwin. + """ + _screen_shape = None + if IS_WIN: + _screen_shape = _screen_shape_windows + if _screen_shape is None: + _screen_shape = _screen_shape_tput + if IS_NIX: + _screen_shape = _screen_shape_linux + return _screen_shape + + +def _screen_shape_windows(fp): # pragma: no cover + try: + import struct + from ctypes import create_string_buffer, windll + from sys import stdin, stdout + + io_handle = -12 # assume stderr + if fp == stdin: + io_handle = -10 + elif fp == stdout: + io_handle = -11 + + h = windll.kernel32.GetStdHandle(io_handle) + csbi = create_string_buffer(22) + res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi) + if res: + (_bufx, _bufy, _curx, _cury, _wattr, left, top, right, bottom, + _maxx, _maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw) + return right - left, bottom - top # +1 + except Exception: # nosec + pass + return None, None + + +def _screen_shape_tput(*_): # pragma: no cover + """cygwin xterm (windows)""" + try: + import shlex + from subprocess import check_call # nosec + return [int(check_call(shlex.split('tput ' + i))) - 1 + for i in ('cols', 'lines')] + except Exception: # nosec + pass + return None, None + + +def _screen_shape_linux(fp): # pragma: no cover + + try: + from array import array + from fcntl import ioctl + from termios import TIOCGWINSZ + except ImportError: + return None, None + else: + try: + rows, cols = array('h', ioctl(fp, TIOCGWINSZ, '\0' * 8))[:2] + return cols, rows + except Exception: + try: + return [int(os.environ[i]) - 1 for i in ("COLUMNS", "LINES")] + except (KeyError, ValueError): + return None, None + + +def _environ_cols_wrapper(): # pragma: no cover + """ + Return a function which returns console width. + Supported: linux, osx, windows, cygwin. + """ + warn("Use `_screen_shape_wrapper()(file)[0]` instead of" + " `_environ_cols_wrapper()(file)`", DeprecationWarning, stacklevel=2) + shape = _screen_shape_wrapper() + if not shape: + return None + + @wraps(shape) + def inner(fp): + return shape(fp)[0] + + return inner + + +def _term_move_up(): # pragma: no cover + return '' if (os.name == 'nt') and (colorama is None) else '\x1b[A' + + +def _text_width(s): + return sum(2 if east_asian_width(ch) in 'FW' else 1 for ch in str(s)) + + +def disp_len(data): + """ + Returns the real on-screen length of a string which may contain + ANSI control codes and wide chars. + """ + return _text_width(RE_ANSI.sub('', data)) + + +def disp_trim(data, length): + """ + Trim a string which may contain ANSI control characters. + """ + if len(data) == disp_len(data): + return data[:length] + + ansi_present = bool(RE_ANSI.search(data)) + while disp_len(data) > length: # carefully delete one char at a time + data = data[:-1] + if ansi_present and bool(RE_ANSI.search(data)): + # assume ANSI reset is required + return data if data.endswith("\033[0m") else data + "\033[0m" + return data diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/version.py b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/version.py new file mode 100644 index 0000000000000000000000000000000000000000..9338fb19799fd1d094357bacb91fc7d8aa350ad7 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/tqdm/version.py @@ -0,0 +1,10 @@ +"""`tqdm` version detector. Precedence: installed dist, git, 'UNKNOWN'.""" +try: + from importlib.metadata import PackageNotFoundError, version +except ImportError: # py<3.8 + from importlib_metadata import PackageNotFoundError, version + +try: + __version__ = version('tqdm') +except PackageNotFoundError: + __version__ = "UNKNOWN" diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/typer-0.25.1.dist-info/INSTALLER b/bioagent-bench/.venv/lib/python3.14/site-packages/typer-0.25.1.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..5c69047b2eb8235994febeeae1da4a82365a240a --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/typer-0.25.1.dist-info/INSTALLER @@ -0,0 +1 @@ +uv \ No newline at end of file diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/typer-0.25.1.dist-info/METADATA b/bioagent-bench/.venv/lib/python3.14/site-packages/typer-0.25.1.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..0340df72d9441af9726fecb9f7e00cb32930cd77 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/typer-0.25.1.dist-info/METADATA @@ -0,0 +1,408 @@ +Metadata-Version: 2.4 +Name: typer +Version: 0.25.1 +Summary: Typer, build great CLIs. Easy to code. Based on Python type hints. +Author-Email: =?utf-8?q?Sebasti=C3=A1n_Ram=C3=ADrez?= +License-Expression: MIT +License-File: LICENSE +Classifier: Intended Audience :: Information Technology +Classifier: Intended Audience :: System Administrators +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python +Classifier: Topic :: Software Development :: Libraries :: Application Frameworks +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: Software Development :: Libraries +Classifier: Topic :: Software Development +Classifier: Typing :: Typed +Classifier: Development Status :: 4 - Beta +Classifier: Intended Audience :: Developers +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Project-URL: Homepage, https://github.com/fastapi/typer +Project-URL: Documentation, https://typer.tiangolo.com +Project-URL: Repository, https://github.com/fastapi/typer +Project-URL: Issues, https://github.com/fastapi/typer/issues +Project-URL: Changelog, https://typer.tiangolo.com/release-notes/ +Requires-Python: >=3.10 +Requires-Dist: click>=8.2.1 +Requires-Dist: shellingham>=1.3.0 +Requires-Dist: rich>=13.8.0 +Requires-Dist: annotated-doc>=0.0.2 +Description-Content-Type: text/markdown + +

    + Typer + +

    +

    + Typer, build great CLIs. Easy to code. Based on Python type hints. +

    +

    + + Test + + + Publish + + + Coverage + + Package version + +

    + +--- + +**Documentation**: https://typer.tiangolo.com + +**Source Code**: https://github.com/fastapi/typer + +--- + +Typer is a library for building CLI applications that users will **love using** and developers will **love creating**. Based on Python type hints. + +It's also a command line tool to run scripts, automatically converting them to CLI applications. + +The key features are: + +* **Intuitive to write**: Great editor support. Completion everywhere. Less time debugging. Designed to be easy to use and learn. Less time reading docs. +* **Easy to use**: It's easy to use for the final users. Automatic help, and automatic completion for all shells. +* **Short**: Minimize code duplication. Multiple features from each parameter declaration. Fewer bugs. +* **Start simple**: The simplest example adds only 2 lines of code to your app: **1 import, 1 function call**. +* **Grow large**: Grow in complexity as much as you want, create arbitrarily complex trees of commands and groups of subcommands, with options and arguments. +* **Run scripts**: Typer includes a `typer` command/program that you can use to run scripts, automatically converting them to CLIs, even if they don't use Typer internally. + +## FastAPI of CLIs + +**Typer** is FastAPI's little sibling, it's the FastAPI of CLIs. + +## Installation + +Create and activate a virtual environment and then install **Typer**: + +
    + +```console +$ pip install typer +---> 100% +Successfully installed typer rich shellingham +``` + +
    + +## Example + +### The absolute minimum + +* Create a file `main.py` with: + +```Python +def main(name: str): + print(f"Hello {name}") +``` + +This script doesn't even use Typer internally. But you can use the `typer` command to run it as a CLI application. + +### Run it + +Run your application with the `typer` command: + +
    + +```console +// Run your application +$ typer main.py run + +// You get a nice error, you are missing NAME +Usage: typer [PATH_OR_MODULE] run [OPTIONS] NAME +Try 'typer [PATH_OR_MODULE] run --help' for help. +╭─ Error ───────────────────────────────────────────╮ +│ Missing argument 'NAME'. │ +╰───────────────────────────────────────────────────╯ + + +// You get a --help for free +$ typer main.py run --help + +Usage: typer [PATH_OR_MODULE] run [OPTIONS] NAME + +Run the provided Typer app. + +╭─ Arguments ───────────────────────────────────────╮ +│ * name TEXT [default: None] [required] | +╰───────────────────────────────────────────────────╯ +╭─ Options ─────────────────────────────────────────╮ +│ --help Show this message and exit. │ +╰───────────────────────────────────────────────────╯ + +// Now pass the NAME argument +$ typer main.py run Camila + +Hello Camila + +// It works! 🎉 +``` + +
    + +This is the simplest use case, not even using Typer internally, but it can already be quite useful for simple scripts. + +**Note**: auto-completion works when you create a Python package and run it with `--install-completion` or when you use the `typer` command. + +## Use Typer in your code + +Now let's start using Typer in your own code, update `main.py` with: + +```Python +import typer + + +def main(name: str): + print(f"Hello {name}") + + +if __name__ == "__main__": + typer.run(main) +``` + +Now you could run it with Python directly: + +
    + +```console +// Run your application +$ python main.py + +// You get a nice error, you are missing NAME +Usage: main.py [OPTIONS] NAME +Try 'main.py --help' for help. +╭─ Error ───────────────────────────────────────────╮ +│ Missing argument 'NAME'. │ +╰───────────────────────────────────────────────────╯ + + +// You get a --help for free +$ python main.py --help + +Usage: main.py [OPTIONS] NAME + +╭─ Arguments ───────────────────────────────────────╮ +│ * name TEXT [default: None] [required] | +╰───────────────────────────────────────────────────╯ +╭─ Options ─────────────────────────────────────────╮ +│ --help Show this message and exit. │ +╰───────────────────────────────────────────────────╯ + +// Now pass the NAME argument +$ python main.py Camila + +Hello Camila + +// It works! 🎉 +``` + +
    + +**Note**: you can also call this same script with the `typer` command, but you don't need to. + +## Example upgrade + +This was the simplest example possible. + +Now let's see one a bit more complex. + +### An example with two subcommands + +Modify the file `main.py`. + +Create a `typer.Typer()` app, and create two subcommands with their parameters. + +```Python hl_lines="3 6 11 20" +import typer + +app = typer.Typer() + + +@app.command() +def hello(name: str): + print(f"Hello {name}") + + +@app.command() +def goodbye(name: str, formal: bool = False): + if formal: + print(f"Goodbye Ms. {name}. Have a good day.") + else: + print(f"Bye {name}!") + + +if __name__ == "__main__": + app() +``` + +And that will: + +* Explicitly create a `typer.Typer` app. + * The previous `typer.run` actually creates one implicitly for you. +* Add two subcommands with `@app.command()`. +* Execute the `app()` itself, as if it was a function (instead of `typer.run`). + +### Run the upgraded example + +Check the new help: + +
    + +```console +$ python main.py --help + + Usage: main.py [OPTIONS] COMMAND [ARGS]... + +╭─ Options ─────────────────────────────────────────╮ +│ --install-completion Install completion │ +│ for the current │ +│ shell. │ +│ --show-completion Show completion for │ +│ the current shell, │ +│ to copy it or │ +│ customize the │ +│ installation. │ +│ --help Show this message │ +│ and exit. │ +╰───────────────────────────────────────────────────╯ +╭─ Commands ────────────────────────────────────────╮ +│ goodbye │ +│ hello │ +╰───────────────────────────────────────────────────╯ + +// When you create a package you get ✨ auto-completion ✨ for free, installed with --install-completion + +// You have 2 subcommands (the 2 functions): goodbye and hello +``` + +
    + +Now check the help for the `hello` command: + +
    + +```console +$ python main.py hello --help + + Usage: main.py hello [OPTIONS] NAME + +╭─ Arguments ───────────────────────────────────────╮ +│ * name TEXT [default: None] [required] │ +╰───────────────────────────────────────────────────╯ +╭─ Options ─────────────────────────────────────────╮ +│ --help Show this message and exit. │ +╰───────────────────────────────────────────────────╯ +``` + +
    + +And now check the help for the `goodbye` command: + +
    + +```console +$ python main.py goodbye --help + + Usage: main.py goodbye [OPTIONS] NAME + +╭─ Arguments ───────────────────────────────────────╮ +│ * name TEXT [default: None] [required] │ +╰───────────────────────────────────────────────────╯ +╭─ Options ─────────────────────────────────────────╮ +│ --formal --no-formal [default: no-formal] │ +│ --help Show this message │ +│ and exit. │ +╰───────────────────────────────────────────────────╯ + +// Automatic --formal and --no-formal for the bool option 🎉 +``` + +
    + +Now you can try out the new command line application: + +
    + +```console +// Use it with the hello command + +$ python main.py hello Camila + +Hello Camila + +// And with the goodbye command + +$ python main.py goodbye Camila + +Bye Camila! + +// And with --formal + +$ python main.py goodbye --formal Camila + +Goodbye Ms. Camila. Have a good day. +``` + +
    + +**Note**: If your app only has one command, by default the command name is **omitted** in usage: `python main.py Camila`. However, when there are multiple commands, you must **explicitly include the command name**: `python main.py hello Camila`. See [One or Multiple Commands](https://typer.tiangolo.com/tutorial/commands/one-or-multiple/) for more details. + +### Recap + +In summary, you declare **once** the types of parameters (*CLI arguments* and *CLI options*) as function parameters. + +You do that with standard modern Python types. + +You don't have to learn a new syntax, the methods or classes of a specific library, etc. + +Just standard **Python**. + +For example, for an `int`: + +```Python +total: int +``` + +or for a `bool` flag: + +```Python +force: bool +``` + +And similarly for **files**, **paths**, **enums** (choices), etc. And there are tools to create **groups of subcommands**, add metadata, extra **validation**, etc. + +**You get**: great editor support, including **completion** and **type checks** everywhere. + +**Your users get**: automatic **`--help`**, **auto-completion** in their terminal (Bash, Zsh, Fish, PowerShell) when they install your package or when using the `typer` command. + +For a more complete example including more features, see the Tutorial - User Guide. + +## Dependencies + +**Typer** stands on the shoulders of giants. It has three required dependencies: + +* Click: a popular tool for building CLIs in Python. Typer is based on it. +* rich: to show nicely formatted errors automatically. +* shellingham: to automatically detect the current shell when installing completion. + +### `typer-slim` + +There used to be a slimmed-down version of Typer called `typer-slim`, which didn't include the dependencies `rich` and `shellingham`, nor the `typer` command. + +However, since version 0.22.0, we have stopped supporting this, and `typer-slim` now simply installs (all of) Typer. + +If you want to disable Rich globally, you can set an environmental variable `TYPER_USE_RICH` to `False` or `0`. + +## License + +This project is licensed under the terms of the MIT license. diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/typer-0.25.1.dist-info/RECORD b/bioagent-bench/.venv/lib/python3.14/site-packages/typer-0.25.1.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..126a0f9c11960d58181deeff238215c2b22fc550 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/typer-0.25.1.dist-info/RECORD @@ -0,0 +1,26 @@ +../../../bin/typer,sha256=AHhN3tF20M8XlgKBrOHqewF2_kjcSCDKwOGHKoE2zHM,325 +typer-0.25.1.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2 +typer-0.25.1.dist-info/METADATA,sha256=MEVT4brybxa876W1Ldfa2GnnULWWff13S4lmdI9dNv8,15852 +typer-0.25.1.dist-info/RECORD,, +typer-0.25.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +typer-0.25.1.dist-info/WHEEL,sha256=Z36eTX6lG3PITRleSd5hAZHCcz52yg3c0JQVxKBbLW0,90 +typer-0.25.1.dist-info/entry_points.txt,sha256=YO13ByiqWeuas9V0JADLUARZFUe_cwU_7wmTNvxBYQ8,57 +typer-0.25.1.dist-info/licenses/LICENSE,sha256=WJks68-N-25AxOIRLtEhJsJDZm3KORKj14t-ysSFnUk,1086 +typer/.agents/skills/typer/SKILL.md,sha256=50o03_KXs_ZhTefLbKpNzEsqLe8cPXXQY7CBe4GjzVM,6172 +typer/__init__.py,sha256=Dlk8QTcly2VA0UDh792Rk_XG8N9HZi9yucy_RQRJcCY,1596 +typer/__main__.py,sha256=bYt9eEaoRQWdejEHFD8REx9jxVEdZptECFsV7F49Ink,30 +typer/_completion_classes.py,sha256=R9v4D8pJ_-n8fLOuyxrRSu7sP5lpXIy5fsLUW8zwsDU,7039 +typer/_completion_shared.py,sha256=-uhCUIMc2S1ywdB-fBSSccH70mIBEsVTxHomcmy-klE,9129 +typer/_types.py,sha256=0lcBDLcsxqr1sxTsqObj_u0Dfa37lWJYUY4PNkX4QlA,974 +typer/_typing.py,sha256=QOw5o-B2L--C3ly2DQH6aUwag6x5brV5FhVaBZ5gzMg,1727 +typer/cli.py,sha256=eg4wafz-7dB-PZqQcRunDbSucc3gSGRYsc834S69d80,10211 +typer/colors.py,sha256=e42j8uB520hLpX5C_0fiR3OOoIFMbhO3ADZvv6hlAV8,430 +typer/completion.py,sha256=FRTR9hP_IPdJp-4GXPOq0btXo5SvgAtLVfS3ZkAMpgQ,4793 +typer/core.py,sha256=N74AEwGx0PbYN6vzimWxqJbd-O5ugJ5_FXjBhRngXQo,27809 +typer/main.py,sha256=XUBRkapHMd5dQNIZhQBHO0AE-RPTV3m5Xs8AW20CovY,69010 +typer/models.py,sha256=OwPG3MAXiUD5ih3p8eNVciXUsL07UIJfNWy3JiNpDfg,19843 +typer/params.py,sha256=AovViRtl-VvUIXnmKKpnxoWK9_gHUbyQgXxxv3h_7lI,59713 +typer/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +typer/rich_utils.py,sha256=lqxrRzQLtnwVdNiDaNcAGPzvSxRYpWAtPHwPEBXLczU,25473 +typer/testing.py,sha256=-ovLNjUNNEFCJoau-41iTJIobsjPbqyTrRq7-8ac4z4,871 +typer/utils.py,sha256=wnJ1DWXBFMnxLHaMN_HDYntxLRby0K-rux63aokHInI,7599 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/typer-0.25.1.dist-info/REQUESTED b/bioagent-bench/.venv/lib/python3.14/site-packages/typer-0.25.1.dist-info/REQUESTED new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/typer-0.25.1.dist-info/WHEEL b/bioagent-bench/.venv/lib/python3.14/site-packages/typer-0.25.1.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..82d3df6a72ef80296e4b58f3038350375ed8fb49 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/typer-0.25.1.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: pdm-backend (2.4.8) +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/typer-0.25.1.dist-info/entry_points.txt b/bioagent-bench/.venv/lib/python3.14/site-packages/typer-0.25.1.dist-info/entry_points.txt new file mode 100644 index 0000000000000000000000000000000000000000..ca44c05a071b87fe02a3724bd895838305fe4e5d --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/typer-0.25.1.dist-info/entry_points.txt @@ -0,0 +1,5 @@ +[console_scripts] +typer = typer.cli:main + +[gui_scripts] + diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/typer-0.25.1.dist-info/licenses/LICENSE b/bioagent-bench/.venv/lib/python3.14/site-packages/typer-0.25.1.dist-info/licenses/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..a7694736cf37716aafec14b24aa8d6316ebe07a3 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/typer-0.25.1.dist-info/licenses/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2019 Sebastián Ramírez + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/typing_extensions-4.15.0.dist-info/INSTALLER b/bioagent-bench/.venv/lib/python3.14/site-packages/typing_extensions-4.15.0.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..5c69047b2eb8235994febeeae1da4a82365a240a --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/typing_extensions-4.15.0.dist-info/INSTALLER @@ -0,0 +1 @@ +uv \ No newline at end of file diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/typing_extensions-4.15.0.dist-info/METADATA b/bioagent-bench/.venv/lib/python3.14/site-packages/typing_extensions-4.15.0.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..b09cb50e1f9a42a8eb58a4339cbdb26250368375 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/typing_extensions-4.15.0.dist-info/METADATA @@ -0,0 +1,72 @@ +Metadata-Version: 2.4 +Name: typing_extensions +Version: 4.15.0 +Summary: Backported and Experimental Type Hints for Python 3.9+ +Keywords: annotations,backport,checker,checking,function,hinting,hints,type,typechecking,typehinting,typehints,typing +Author-email: "Guido van Rossum, Jukka Lehtosalo, Łukasz Langa, Michael Lee" +Requires-Python: >=3.9 +Description-Content-Type: text/markdown +License-Expression: PSF-2.0 +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Console +Classifier: Intended Audience :: Developers +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3 :: Only +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Topic :: Software Development +License-File: LICENSE +Project-URL: Bug Tracker, https://github.com/python/typing_extensions/issues +Project-URL: Changes, https://github.com/python/typing_extensions/blob/main/CHANGELOG.md +Project-URL: Documentation, https://typing-extensions.readthedocs.io/ +Project-URL: Home, https://github.com/python/typing_extensions +Project-URL: Q & A, https://github.com/python/typing/discussions +Project-URL: Repository, https://github.com/python/typing_extensions + +# Typing Extensions + +[![Chat at https://gitter.im/python/typing](https://badges.gitter.im/python/typing.svg)](https://gitter.im/python/typing) + +[Documentation](https://typing-extensions.readthedocs.io/en/latest/#) – +[PyPI](https://pypi.org/project/typing-extensions/) + +## Overview + +The `typing_extensions` module serves two related purposes: + +- Enable use of new type system features on older Python versions. For example, + `typing.TypeGuard` is new in Python 3.10, but `typing_extensions` allows + users on previous Python versions to use it too. +- Enable experimentation with new type system PEPs before they are accepted and + added to the `typing` module. + +`typing_extensions` is treated specially by static type checkers such as +mypy and pyright. Objects defined in `typing_extensions` are treated the same +way as equivalent forms in `typing`. + +`typing_extensions` uses +[Semantic Versioning](https://semver.org/). The +major version will be incremented only for backwards-incompatible changes. +Therefore, it's safe to depend +on `typing_extensions` like this: `typing_extensions ~=x.y`, +where `x.y` is the first version that includes all features you need. +[This](https://packaging.python.org/en/latest/specifications/version-specifiers/#compatible-release) +is equivalent to `typing_extensions >=x.y, <(x+1)`. Do not depend on `~= x.y.z` +unless you really know what you're doing; that defeats the purpose of +semantic versioning. + +## Included items + +See [the documentation](https://typing-extensions.readthedocs.io/en/latest/#) for a +complete listing of module contents. + +## Contributing + +See [CONTRIBUTING.md](https://github.com/python/typing_extensions/blob/main/CONTRIBUTING.md) +for how to contribute to `typing_extensions`. + diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/typing_extensions-4.15.0.dist-info/RECORD b/bioagent-bench/.venv/lib/python3.14/site-packages/typing_extensions-4.15.0.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..ec54c2e277f970bd4ae36e237f8fe0f4dff7f900 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/typing_extensions-4.15.0.dist-info/RECORD @@ -0,0 +1,7 @@ +typing_extensions-4.15.0.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2 +typing_extensions-4.15.0.dist-info/METADATA,sha256=wTg3j-jxiTSsmd4GBTXFPsbBOu7WXpTDJkHafuMZKnI,3259 +typing_extensions-4.15.0.dist-info/RECORD,, +typing_extensions-4.15.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +typing_extensions-4.15.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82 +typing_extensions-4.15.0.dist-info/licenses/LICENSE,sha256=Oy-B_iHRgcSZxZolbI4ZaEVdZonSaaqFNzv7avQdo78,13936 +typing_extensions.py,sha256=Qz0R0XDTok0usGXrwb_oSM6n49fOaFZ6tSvqLUwvftg,160429 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/typing_extensions-4.15.0.dist-info/REQUESTED b/bioagent-bench/.venv/lib/python3.14/site-packages/typing_extensions-4.15.0.dist-info/REQUESTED new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/typing_extensions-4.15.0.dist-info/WHEEL b/bioagent-bench/.venv/lib/python3.14/site-packages/typing_extensions-4.15.0.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..d8b9936dad9ab2513fa6979f411560d3b6b57e37 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/typing_extensions-4.15.0.dist-info/WHEEL @@ -0,0 +1,4 @@ +Wheel-Version: 1.0 +Generator: flit 3.12.0 +Root-Is-Purelib: true +Tag: py3-none-any diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/typing_extensions-4.15.0.dist-info/licenses/LICENSE b/bioagent-bench/.venv/lib/python3.14/site-packages/typing_extensions-4.15.0.dist-info/licenses/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..f26bcf4d2de6eb136e31006ca3ab447d5e488adf --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/typing_extensions-4.15.0.dist-info/licenses/LICENSE @@ -0,0 +1,279 @@ +A. HISTORY OF THE SOFTWARE +========================== + +Python was created in the early 1990s by Guido van Rossum at Stichting +Mathematisch Centrum (CWI, see https://www.cwi.nl) in the Netherlands +as a successor of a language called ABC. Guido remains Python's +principal author, although it includes many contributions from others. + +In 1995, Guido continued his work on Python at the Corporation for +National Research Initiatives (CNRI, see https://www.cnri.reston.va.us) +in Reston, Virginia where he released several versions of the +software. + +In May 2000, Guido and the Python core development team moved to +BeOpen.com to form the BeOpen PythonLabs team. In October of the same +year, the PythonLabs team moved to Digital Creations, which became +Zope Corporation. In 2001, the Python Software Foundation (PSF, see +https://www.python.org/psf/) was formed, a non-profit organization +created specifically to own Python-related Intellectual Property. +Zope Corporation was a sponsoring member of the PSF. + +All Python releases are Open Source (see https://opensource.org for +the Open Source Definition). Historically, most, but not all, Python +releases have also been GPL-compatible; the table below summarizes +the various releases. + + Release Derived Year Owner GPL- + from compatible? (1) + + 0.9.0 thru 1.2 1991-1995 CWI yes + 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes + 1.6 1.5.2 2000 CNRI no + 2.0 1.6 2000 BeOpen.com no + 1.6.1 1.6 2001 CNRI yes (2) + 2.1 2.0+1.6.1 2001 PSF no + 2.0.1 2.0+1.6.1 2001 PSF yes + 2.1.1 2.1+2.0.1 2001 PSF yes + 2.1.2 2.1.1 2002 PSF yes + 2.1.3 2.1.2 2002 PSF yes + 2.2 and above 2.1.1 2001-now PSF yes + +Footnotes: + +(1) GPL-compatible doesn't mean that we're distributing Python under + the GPL. All Python licenses, unlike the GPL, let you distribute + a modified version without making your changes open source. The + GPL-compatible licenses make it possible to combine Python with + other software that is released under the GPL; the others don't. + +(2) According to Richard Stallman, 1.6.1 is not GPL-compatible, + because its license has a choice of law clause. According to + CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 + is "not incompatible" with the GPL. + +Thanks to the many outside volunteers who have worked under Guido's +direction to make these releases possible. + + +B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON +=============================================================== + +Python software and documentation are licensed under the +Python Software Foundation License Version 2. + +Starting with Python 3.8.6, examples, recipes, and other code in +the documentation are dual licensed under the PSF License Version 2 +and the Zero-Clause BSD license. + +Some software incorporated into Python is under different licenses. +The licenses are listed with code falling under that license. + + +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python alone or in any derivative version, +provided, however, that PSF's License Agreement and PSF's notice of copyright, +i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023 Python Software Foundation; +All Rights Reserved" are retained in Python alone or in any derivative version +prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 +------------------------------------------- + +BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 + +1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an +office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the +Individual or Organization ("Licensee") accessing and otherwise using +this software in source or binary form and its associated +documentation ("the Software"). + +2. Subject to the terms and conditions of this BeOpen Python License +Agreement, BeOpen hereby grants Licensee a non-exclusive, +royalty-free, world-wide license to reproduce, analyze, test, perform +and/or display publicly, prepare derivative works, distribute, and +otherwise use the Software alone or in any derivative version, +provided, however, that the BeOpen Python License is retained in the +Software, alone or in any derivative version prepared by Licensee. + +3. BeOpen is making the Software available to Licensee on an "AS IS" +basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE +SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS +AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY +DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +5. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +6. This License Agreement shall be governed by and interpreted in all +respects by the law of the State of California, excluding conflict of +law provisions. Nothing in this License Agreement shall be deemed to +create any relationship of agency, partnership, or joint venture +between BeOpen and Licensee. This License Agreement does not grant +permission to use BeOpen trademarks or trade names in a trademark +sense to endorse or promote products or services of Licensee, or any +third party. As an exception, the "BeOpen Python" logos available at +http://www.pythonlabs.com/logos.html may be used according to the +permissions granted on that web page. + +7. By copying, installing or otherwise using the software, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 +--------------------------------------- + +1. This LICENSE AGREEMENT is between the Corporation for National +Research Initiatives, having an office at 1895 Preston White Drive, +Reston, VA 20191 ("CNRI"), and the Individual or Organization +("Licensee") accessing and otherwise using Python 1.6.1 software in +source or binary form and its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, CNRI +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use Python 1.6.1 +alone or in any derivative version, provided, however, that CNRI's +License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) +1995-2001 Corporation for National Research Initiatives; All Rights +Reserved" are retained in Python 1.6.1 alone or in any derivative +version prepared by Licensee. Alternately, in lieu of CNRI's License +Agreement, Licensee may substitute the following text (omitting the +quotes): "Python 1.6.1 is made available subject to the terms and +conditions in CNRI's License Agreement. This Agreement together with +Python 1.6.1 may be located on the internet using the following +unique, persistent identifier (known as a handle): 1895.22/1013. This +Agreement may also be obtained from a proxy server on the internet +using the following URL: http://hdl.handle.net/1895.22/1013". + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python 1.6.1 or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python 1.6.1. + +4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" +basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. This License Agreement shall be governed by the federal +intellectual property law of the United States, including without +limitation the federal copyright law, and, to the extent such +U.S. federal law does not apply, by the law of the Commonwealth of +Virginia, excluding Virginia's conflict of law provisions. +Notwithstanding the foregoing, with regard to derivative works based +on Python 1.6.1 that incorporate non-separable material that was +previously distributed under the GNU General Public License (GPL), the +law of the Commonwealth of Virginia shall govern this License +Agreement only as to issues arising under or with respect to +Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this +License Agreement shall be deemed to create any relationship of +agency, partnership, or joint venture between CNRI and Licensee. This +License Agreement does not grant permission to use CNRI trademarks or +trade name in a trademark sense to endorse or promote products or +services of Licensee, or any third party. + +8. By clicking on the "ACCEPT" button where indicated, or by copying, +installing or otherwise using Python 1.6.1, Licensee agrees to be +bound by the terms and conditions of this License Agreement. + + ACCEPT + + +CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 +-------------------------------------------------- + +Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, +The Netherlands. All rights reserved. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Stichting Mathematisch +Centrum or CWI not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior +permission. + +STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE +FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +ZERO-CLAUSE BSD LICENSE FOR CODE IN THE PYTHON DOCUMENTATION +---------------------------------------------------------------------- + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..3fe782c8a45bbabcf240f3cac4303ac12b0ec274 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/__init__.py @@ -0,0 +1,211 @@ +""" +Python HTTP library with thread-safe connection pooling, file post support, user friendly, and more +""" + +from __future__ import annotations + +# Set default logging handler to avoid "No handler found" warnings. +import logging +import sys +import typing +import warnings +from logging import NullHandler + +from . import exceptions +from ._base_connection import _TYPE_BODY +from ._collections import HTTPHeaderDict +from ._version import __version__ +from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool, connection_from_url +from .filepost import _TYPE_FIELDS, encode_multipart_formdata +from .poolmanager import PoolManager, ProxyManager, proxy_from_url +from .response import BaseHTTPResponse, HTTPResponse +from .util.request import make_headers +from .util.retry import Retry +from .util.timeout import Timeout + +# Ensure that Python is compiled with OpenSSL 1.1.1+ +# If the 'ssl' module isn't available at all that's +# fine, we only care if the module is available. +try: + import ssl +except ImportError: + pass +else: + if not ssl.OPENSSL_VERSION.startswith("OpenSSL "): # Defensive: + warnings.warn( + "urllib3 v2 only supports OpenSSL 1.1.1+, currently " + f"the 'ssl' module is compiled with {ssl.OPENSSL_VERSION!r}. " + "See: https://github.com/urllib3/urllib3/issues/3020", + exceptions.NotOpenSSLWarning, + ) + elif ssl.OPENSSL_VERSION_INFO < (1, 1, 1): # Defensive: + raise ImportError( + "urllib3 v2 only supports OpenSSL 1.1.1+, currently " + f"the 'ssl' module is compiled with {ssl.OPENSSL_VERSION!r}. " + "See: https://github.com/urllib3/urllib3/issues/2168" + ) + +__author__ = "Andrey Petrov (andrey.petrov@shazow.net)" +__license__ = "MIT" +__version__ = __version__ + +__all__ = ( + "HTTPConnectionPool", + "HTTPHeaderDict", + "HTTPSConnectionPool", + "PoolManager", + "ProxyManager", + "HTTPResponse", + "Retry", + "Timeout", + "add_stderr_logger", + "connection_from_url", + "disable_warnings", + "encode_multipart_formdata", + "make_headers", + "proxy_from_url", + "request", + "BaseHTTPResponse", +) + +logging.getLogger(__name__).addHandler(NullHandler()) + + +def add_stderr_logger( + level: int = logging.DEBUG, +) -> logging.StreamHandler[typing.TextIO]: + """ + Helper for quickly adding a StreamHandler to the logger. Useful for + debugging. + + Returns the handler after adding it. + """ + # This method needs to be in this __init__.py to get the __name__ correct + # even if urllib3 is vendored within another package. + logger = logging.getLogger(__name__) + handler = logging.StreamHandler() + handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s")) + logger.addHandler(handler) + logger.setLevel(level) + logger.debug("Added a stderr logging handler to logger: %s", __name__) + return handler + + +# ... Clean up. +del NullHandler + + +# All warning filters *must* be appended unless you're really certain that they +# shouldn't be: otherwise, it's very hard for users to use most Python +# mechanisms to silence them. +# SecurityWarning's always go off by default. +warnings.simplefilter("always", exceptions.SecurityWarning, append=True) +# InsecurePlatformWarning's don't vary between requests, so we keep it default. +warnings.simplefilter("default", exceptions.InsecurePlatformWarning, append=True) + + +def disable_warnings(category: type[Warning] = exceptions.HTTPWarning) -> None: + """ + Helper for quickly disabling all urllib3 warnings. + """ + warnings.simplefilter("ignore", category) + + +_DEFAULT_POOL = PoolManager() + + +def request( + method: str, + url: str, + *, + body: _TYPE_BODY | None = None, + fields: _TYPE_FIELDS | None = None, + headers: typing.Mapping[str, str] | None = None, + preload_content: bool | None = True, + decode_content: bool | None = True, + redirect: bool | None = True, + retries: Retry | bool | int | None = None, + timeout: Timeout | float | int | None = 3, + json: typing.Any | None = None, +) -> BaseHTTPResponse: + """ + A convenience, top-level request method. It uses a module-global ``PoolManager`` instance. + Therefore, its side effects could be shared across dependencies relying on it. + To avoid side effects create a new ``PoolManager`` instance and use it instead. + The method does not accept low-level ``**urlopen_kw`` keyword arguments. + + :param method: + HTTP request method (such as GET, POST, PUT, etc.) + + :param url: + The URL to perform the request on. + + :param body: + Data to send in the request body, either :class:`str`, :class:`bytes`, + an iterable of :class:`str`/:class:`bytes`, or a file-like object. + + :param fields: + Data to encode and send in the request body. + + :param headers: + Dictionary of custom headers to send, such as User-Agent, + If-None-Match, etc. + + :param bool preload_content: + If True, the response's body will be preloaded into memory. + + :param bool decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + + :param redirect: + If True, automatically handle redirects (status codes 301, 302, + 303, 307, 308). Each redirect counts as a retry. Disabling retries + will disable redirect, too. + + :param retries: + Configure the number of retries to allow before raising a + :class:`~urllib3.exceptions.MaxRetryError` exception. + + If ``None`` (default) will retry 3 times, see ``Retry.DEFAULT``. Pass a + :class:`~urllib3.util.retry.Retry` object for fine-grained control + over different types of retries. + Pass an integer number to retry connection errors that many times, + but no other types of errors. Pass zero to never retry. + + If ``False``, then retries are disabled and any exception is raised + immediately. Also, instead of raising a MaxRetryError on redirects, + the redirect response will be returned. + + :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int. + + :param timeout: + If specified, overrides the default timeout for this one + request. It may be a float (in seconds) or an instance of + :class:`urllib3.util.Timeout`. + + :param json: + Data to encode and send as JSON with UTF-encoded in the request body. + The ``"Content-Type"`` header will be set to ``"application/json"`` + unless specified otherwise. + """ + + return _DEFAULT_POOL.request( + method, + url, + body=body, + fields=fields, + headers=headers, + preload_content=preload_content, + decode_content=decode_content, + redirect=redirect, + retries=retries, + timeout=timeout, + json=json, + ) + + +if sys.platform == "emscripten": + from .contrib.emscripten import inject_into_urllib3 # noqa: 401 + + inject_into_urllib3() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/__pycache__/__init__.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..839ce0a115ac05d4b8432934afbc4e9505de78ad Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/__pycache__/__init__.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/__pycache__/_base_connection.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/__pycache__/_base_connection.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..014b7af0bf915e4c6825f74e80214a507e4ff6a3 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/__pycache__/_base_connection.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/__pycache__/_request_methods.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/__pycache__/_request_methods.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e13107dafa06442d9dd2d94c670147cc1f64cbe1 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/__pycache__/_request_methods.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/__pycache__/_version.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/__pycache__/_version.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0ca5dc2ceb645d5b2e1d57d35ff66cafe6aef506 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/__pycache__/_version.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/__pycache__/connection.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/__pycache__/connection.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..405a2e3f394ea138acda72402dc744a2b3ff857e Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/__pycache__/connection.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/__pycache__/connectionpool.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/__pycache__/connectionpool.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d46941eea7cb51e690767b58ced1b51f29626c0a Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/__pycache__/connectionpool.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/__pycache__/exceptions.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/__pycache__/exceptions.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9d3497b89e54f37ea95ab4643b940e8fd4e4abcc Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/__pycache__/exceptions.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/__pycache__/fields.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/__pycache__/fields.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9459f9e69abd3c696539afc40173bb55f6b44720 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/__pycache__/fields.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/__pycache__/filepost.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/__pycache__/filepost.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..92756db12571171f562057c205ba945f80d83dc1 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/__pycache__/filepost.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/__pycache__/poolmanager.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/__pycache__/poolmanager.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5738d1fc2dea50d1fc936a48a5940c328fed7d9a Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/__pycache__/poolmanager.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/__pycache__/response.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/__pycache__/response.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dd088d2a003390a3b767ee334be11a42ccd237ae Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/__pycache__/response.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/_base_connection.py b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/_base_connection.py new file mode 100644 index 0000000000000000000000000000000000000000..992ec1657a9d0a98354b56b2a55d54b75db0eaa3 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/_base_connection.py @@ -0,0 +1,167 @@ +from __future__ import annotations + +import typing + +from .util.connection import _TYPE_SOCKET_OPTIONS +from .util.timeout import _DEFAULT_TIMEOUT, _TYPE_TIMEOUT +from .util.url import Url + +_TYPE_BODY = typing.Union[ + bytes, typing.IO[typing.Any], typing.Iterable[bytes | str], str +] + + +class ProxyConfig(typing.NamedTuple): + ssl_context: ssl.SSLContext | None + use_forwarding_for_https: bool + assert_hostname: None | str | typing.Literal[False] + assert_fingerprint: str | None + + +class _ResponseOptions(typing.NamedTuple): + # TODO: Remove this in favor of a better + # HTTP request/response lifecycle tracking. + request_method: str + request_url: str + preload_content: bool + decode_content: bool + enforce_content_length: bool + + +if typing.TYPE_CHECKING: + import ssl + from typing import Protocol + + from .response import BaseHTTPResponse + + class BaseHTTPConnection(Protocol): + default_port: typing.ClassVar[int] + default_socket_options: typing.ClassVar[_TYPE_SOCKET_OPTIONS] + + host: str + port: int + timeout: None | ( + float + ) # Instance doesn't store _DEFAULT_TIMEOUT, must be resolved. + blocksize: int + source_address: tuple[str, int] | None + socket_options: _TYPE_SOCKET_OPTIONS | None + + proxy: Url | None + proxy_config: ProxyConfig | None + + is_verified: bool + proxy_is_verified: bool | None + + def __init__( + self, + host: str, + port: int | None = None, + *, + timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + source_address: tuple[str, int] | None = None, + blocksize: int = 8192, + socket_options: _TYPE_SOCKET_OPTIONS | None = ..., + proxy: Url | None = None, + proxy_config: ProxyConfig | None = None, + ) -> None: ... + + def set_tunnel( + self, + host: str, + port: int | None = None, + headers: typing.Mapping[str, str] | None = None, + scheme: str = "http", + ) -> None: ... + + def connect(self) -> None: ... + + def request( + self, + method: str, + url: str, + body: _TYPE_BODY | None = None, + headers: typing.Mapping[str, str] | None = None, + # We know *at least* botocore is depending on the order of the + # first 3 parameters so to be safe we only mark the later ones + # as keyword-only to ensure we have space to extend. + *, + chunked: bool = False, + preload_content: bool = True, + decode_content: bool = True, + enforce_content_length: bool = True, + ) -> None: ... + + def getresponse(self) -> BaseHTTPResponse: ... + + def close(self) -> None: ... + + @property + def is_closed(self) -> bool: + """Whether the connection either is brand new or has been previously closed. + If this property is True then both ``is_connected`` and ``has_connected_to_proxy`` + properties must be False. + """ + + @property + def is_connected(self) -> bool: + """Whether the connection is actively connected to any origin (proxy or target)""" + + @property + def has_connected_to_proxy(self) -> bool: + """Whether the connection has successfully connected to its proxy. + This returns False if no proxy is in use. Used to determine whether + errors are coming from the proxy layer or from tunnelling to the target origin. + """ + + class BaseHTTPSConnection(BaseHTTPConnection, Protocol): + default_port: typing.ClassVar[int] + default_socket_options: typing.ClassVar[_TYPE_SOCKET_OPTIONS] + + # Certificate verification methods + cert_reqs: int | str | None + assert_hostname: None | str | typing.Literal[False] + assert_fingerprint: str | None + ssl_context: ssl.SSLContext | None + + # Trusted CAs + ca_certs: str | None + ca_cert_dir: str | None + ca_cert_data: None | str | bytes + + # TLS version + ssl_minimum_version: int | None + ssl_maximum_version: int | None + ssl_version: int | str | None # Deprecated + + # Client certificates + cert_file: str | None + key_file: str | None + key_password: str | None + + def __init__( + self, + host: str, + port: int | None = None, + *, + timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + source_address: tuple[str, int] | None = None, + blocksize: int = 16384, + socket_options: _TYPE_SOCKET_OPTIONS | None = ..., + proxy: Url | None = None, + proxy_config: ProxyConfig | None = None, + cert_reqs: int | str | None = None, + assert_hostname: None | str | typing.Literal[False] = None, + assert_fingerprint: str | None = None, + server_hostname: str | None = None, + ssl_context: ssl.SSLContext | None = None, + ca_certs: str | None = None, + ca_cert_dir: str | None = None, + ca_cert_data: None | str | bytes = None, + ssl_minimum_version: int | None = None, + ssl_maximum_version: int | None = None, + ssl_version: int | str | None = None, # Deprecated + cert_file: str | None = None, + key_file: str | None = None, + key_password: str | None = None, + ) -> None: ... diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/_collections.py b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/_collections.py new file mode 100644 index 0000000000000000000000000000000000000000..ee9ca662b625ce6b0a4743d05a186301b9a30ee6 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/_collections.py @@ -0,0 +1,486 @@ +from __future__ import annotations + +import typing +from collections import OrderedDict +from enum import Enum, auto +from threading import RLock + +if typing.TYPE_CHECKING: + # We can only import Protocol if TYPE_CHECKING because it's a development + # dependency, and is not available at runtime. + from typing import Protocol + + from typing_extensions import Self + + class HasGettableStringKeys(Protocol): + def keys(self) -> typing.Iterator[str]: ... + + def __getitem__(self, key: str) -> str: ... + + +__all__ = ["RecentlyUsedContainer", "HTTPHeaderDict"] + + +# Key type +_KT = typing.TypeVar("_KT") +# Value type +_VT = typing.TypeVar("_VT") +# Default type +_DT = typing.TypeVar("_DT") + +ValidHTTPHeaderSource = typing.Union[ + "HTTPHeaderDict", + typing.Mapping[str, str], + typing.Iterable[tuple[str, str]], + "HasGettableStringKeys", +] + + +class _Sentinel(Enum): + not_passed = auto() + + +def ensure_can_construct_http_header_dict( + potential: object, +) -> ValidHTTPHeaderSource | None: + if isinstance(potential, HTTPHeaderDict): + return potential + elif isinstance(potential, typing.Mapping): + # Full runtime checking of the contents of a Mapping is expensive, so for the + # purposes of typechecking, we assume that any Mapping is the right shape. + return typing.cast(typing.Mapping[str, str], potential) + elif isinstance(potential, typing.Iterable): + # Similarly to Mapping, full runtime checking of the contents of an Iterable is + # expensive, so for the purposes of typechecking, we assume that any Iterable + # is the right shape. + return typing.cast(typing.Iterable[tuple[str, str]], potential) + elif hasattr(potential, "keys") and hasattr(potential, "__getitem__"): + return typing.cast("HasGettableStringKeys", potential) + else: + return None + + +class RecentlyUsedContainer(typing.Generic[_KT, _VT], typing.MutableMapping[_KT, _VT]): + """ + Provides a thread-safe dict-like container which maintains up to + ``maxsize`` keys while throwing away the least-recently-used keys beyond + ``maxsize``. + + :param maxsize: + Maximum number of recent elements to retain. + + :param dispose_func: + Every time an item is evicted from the container, + ``dispose_func(value)`` is called. Callback which will get called + """ + + _container: typing.OrderedDict[_KT, _VT] + _maxsize: int + dispose_func: typing.Callable[[_VT], None] | None + lock: RLock + + def __init__( + self, + maxsize: int = 10, + dispose_func: typing.Callable[[_VT], None] | None = None, + ) -> None: + super().__init__() + self._maxsize = maxsize + self.dispose_func = dispose_func + self._container = OrderedDict() + self.lock = RLock() + + def __getitem__(self, key: _KT) -> _VT: + # Re-insert the item, moving it to the end of the eviction line. + with self.lock: + item = self._container.pop(key) + self._container[key] = item + return item + + def __setitem__(self, key: _KT, value: _VT) -> None: + evicted_item = None + with self.lock: + # Possibly evict the existing value of 'key' + try: + # If the key exists, we'll overwrite it, which won't change the + # size of the pool. Because accessing a key should move it to + # the end of the eviction line, we pop it out first. + evicted_item = key, self._container.pop(key) + self._container[key] = value + except KeyError: + # When the key does not exist, we insert the value first so that + # evicting works in all cases, including when self._maxsize is 0 + self._container[key] = value + if len(self._container) > self._maxsize: + # If we didn't evict an existing value, and we've hit our maximum + # size, then we have to evict the least recently used item from + # the beginning of the container. + evicted_item = self._container.popitem(last=False) + + # After releasing the lock on the pool, dispose of any evicted value. + if evicted_item is not None and self.dispose_func: + _, evicted_value = evicted_item + self.dispose_func(evicted_value) + + def __delitem__(self, key: _KT) -> None: + with self.lock: + value = self._container.pop(key) + + if self.dispose_func: + self.dispose_func(value) + + def __len__(self) -> int: + with self.lock: + return len(self._container) + + def __iter__(self) -> typing.NoReturn: + raise NotImplementedError( + "Iteration over this class is unlikely to be threadsafe." + ) + + def clear(self) -> None: + with self.lock: + # Copy pointers to all values, then wipe the mapping + values = list(self._container.values()) + self._container.clear() + + if self.dispose_func: + for value in values: + self.dispose_func(value) + + def keys(self) -> set[_KT]: # type: ignore[override] + with self.lock: + return set(self._container.keys()) + + +class HTTPHeaderDictItemView(set[tuple[str, str]]): + """ + HTTPHeaderDict is unusual for a Mapping[str, str] in that it has two modes of + address. + + If we directly try to get an item with a particular name, we will get a string + back that is the concatenated version of all the values: + + >>> d['X-Header-Name'] + 'Value1, Value2, Value3' + + However, if we iterate over an HTTPHeaderDict's items, we will optionally combine + these values based on whether combine=True was called when building up the dictionary + + >>> d = HTTPHeaderDict({"A": "1", "B": "foo"}) + >>> d.add("A", "2", combine=True) + >>> d.add("B", "bar") + >>> list(d.items()) + [ + ('A', '1, 2'), + ('B', 'foo'), + ('B', 'bar'), + ] + + This class conforms to the interface required by the MutableMapping ABC while + also giving us the nonstandard iteration behavior we want; items with duplicate + keys, ordered by time of first insertion. + """ + + _headers: HTTPHeaderDict + + def __init__(self, headers: HTTPHeaderDict) -> None: + self._headers = headers + + def __len__(self) -> int: + return len(list(self._headers.iteritems())) + + def __iter__(self) -> typing.Iterator[tuple[str, str]]: + return self._headers.iteritems() + + def __contains__(self, item: object) -> bool: + if isinstance(item, tuple) and len(item) == 2: + passed_key, passed_val = item + if isinstance(passed_key, str) and isinstance(passed_val, str): + return self._headers._has_value_for_header(passed_key, passed_val) + return False + + +class HTTPHeaderDict(typing.MutableMapping[str, str]): + """ + :param headers: + An iterable of field-value pairs. Must not contain multiple field names + when compared case-insensitively. + + :param kwargs: + Additional field-value pairs to pass in to ``dict.update``. + + A ``dict`` like container for storing HTTP Headers. + + Field names are stored and compared case-insensitively in compliance with + RFC 7230. Iteration provides the first case-sensitive key seen for each + case-insensitive pair. + + Using ``__setitem__`` syntax overwrites fields that compare equal + case-insensitively in order to maintain ``dict``'s api. For fields that + compare equal, instead create a new ``HTTPHeaderDict`` and use ``.add`` + in a loop. + + If multiple fields that are equal case-insensitively are passed to the + constructor or ``.update``, the behavior is undefined and some will be + lost. + + >>> headers = HTTPHeaderDict() + >>> headers.add('Set-Cookie', 'foo=bar') + >>> headers.add('set-cookie', 'baz=quxx') + >>> headers['content-length'] = '7' + >>> headers['SET-cookie'] + 'foo=bar, baz=quxx' + >>> headers['Content-Length'] + '7' + """ + + _container: typing.MutableMapping[str, list[str]] + + def __init__(self, headers: ValidHTTPHeaderSource | None = None, **kwargs: str): + super().__init__() + self._container = {} # 'dict' is insert-ordered + if headers is not None: + if isinstance(headers, HTTPHeaderDict): + self._copy_from(headers) + else: + self.extend(headers) + if kwargs: + self.extend(kwargs) + + def __setitem__(self, key: str, val: str) -> None: + # avoid a bytes/str comparison by decoding before httplib + if isinstance(key, bytes): + key = key.decode("latin-1") + self._container[key.lower()] = [key, val] + + def __getitem__(self, key: str) -> str: + if isinstance(key, bytes): + key = key.decode("latin-1") + val = self._container[key.lower()] + return ", ".join(val[1:]) + + def __delitem__(self, key: str) -> None: + if isinstance(key, bytes): + key = key.decode("latin-1") + del self._container[key.lower()] + + def __contains__(self, key: object) -> bool: + if isinstance(key, bytes): + key = key.decode("latin-1") + if isinstance(key, str): + return key.lower() in self._container + return False + + def setdefault(self, key: str, default: str = "") -> str: + return super().setdefault(key, default) + + def __eq__(self, other: object) -> bool: + maybe_constructable = ensure_can_construct_http_header_dict(other) + if maybe_constructable is None: + return False + else: + other_as_http_header_dict = type(self)(maybe_constructable) + + return {k.lower(): v for k, v in self.itermerged()} == { + k.lower(): v for k, v in other_as_http_header_dict.itermerged() + } + + def __ne__(self, other: object) -> bool: + return not self.__eq__(other) + + def __len__(self) -> int: + return len(self._container) + + def __iter__(self) -> typing.Iterator[str]: + # Only provide the originally cased names + for vals in self._container.values(): + yield vals[0] + + def discard(self, key: str) -> None: + try: + del self[key] + except KeyError: + pass + + def add(self, key: str, val: str, *, combine: bool = False) -> None: + """Adds a (name, value) pair, doesn't overwrite the value if it already + exists. + + If this is called with combine=True, instead of adding a new header value + as a distinct item during iteration, this will instead append the value to + any existing header value with a comma. If no existing header value exists + for the key, then the value will simply be added, ignoring the combine parameter. + + >>> headers = HTTPHeaderDict(foo='bar') + >>> headers.add('Foo', 'baz') + >>> headers['foo'] + 'bar, baz' + >>> list(headers.items()) + [('foo', 'bar'), ('foo', 'baz')] + >>> headers.add('foo', 'quz', combine=True) + >>> list(headers.items()) + [('foo', 'bar, baz, quz')] + """ + # avoid a bytes/str comparison by decoding before httplib + if isinstance(key, bytes): + key = key.decode("latin-1") + key_lower = key.lower() + new_vals = [key, val] + # Keep the common case aka no item present as fast as possible + vals = self._container.setdefault(key_lower, new_vals) + if new_vals is not vals: + # if there are values here, then there is at least the initial + # key/value pair + assert len(vals) >= 2 + if combine: + vals[-1] = vals[-1] + ", " + val + else: + vals.append(val) + + def extend(self, *args: ValidHTTPHeaderSource, **kwargs: str) -> None: + """Generic import function for any type of header-like object. + Adapted version of MutableMapping.update in order to insert items + with self.add instead of self.__setitem__ + """ + if len(args) > 1: + raise TypeError( + f"extend() takes at most 1 positional arguments ({len(args)} given)" + ) + other = args[0] if len(args) >= 1 else () + + if isinstance(other, HTTPHeaderDict): + for key, val in other.iteritems(): + self.add(key, val) + elif isinstance(other, typing.Mapping): + for key, val in other.items(): + self.add(key, val) + elif isinstance(other, typing.Iterable): + for key, value in other: + self.add(key, value) + elif hasattr(other, "keys") and hasattr(other, "__getitem__"): + # THIS IS NOT A TYPESAFE BRANCH + # In this branch, the object has a `keys` attr but is not a Mapping or any of + # the other types indicated in the method signature. We do some stuff with + # it as though it partially implements the Mapping interface, but we're not + # doing that stuff safely AT ALL. + for key in other.keys(): + self.add(key, other[key]) + + for key, value in kwargs.items(): + self.add(key, value) + + @typing.overload + def getlist(self, key: str) -> list[str]: ... + + @typing.overload + def getlist(self, key: str, default: _DT) -> list[str] | _DT: ... + + def getlist( + self, key: str, default: _Sentinel | _DT = _Sentinel.not_passed + ) -> list[str] | _DT: + """Returns a list of all the values for the named field. Returns an + empty list if the key doesn't exist.""" + if isinstance(key, bytes): + key = key.decode("latin-1") + try: + vals = self._container[key.lower()] + except KeyError: + if default is _Sentinel.not_passed: + # _DT is unbound; empty list is instance of List[str] + return [] + # _DT is bound; default is instance of _DT + return default + else: + # _DT may or may not be bound; vals[1:] is instance of List[str], which + # meets our external interface requirement of `Union[List[str], _DT]`. + return vals[1:] + + def _prepare_for_method_change(self) -> Self: + """ + Remove content-specific header fields before changing the request + method to GET or HEAD according to RFC 9110, Section 15.4. + """ + content_specific_headers = [ + "Content-Encoding", + "Content-Language", + "Content-Location", + "Content-Type", + "Content-Length", + "Digest", + "Last-Modified", + ] + for header in content_specific_headers: + self.discard(header) + return self + + # Backwards compatibility for httplib + getheaders = getlist + getallmatchingheaders = getlist + iget = getlist + + # Backwards compatibility for http.cookiejar + get_all = getlist + + def __repr__(self) -> str: + return f"{type(self).__name__}({dict(self.itermerged())})" + + def _copy_from(self, other: HTTPHeaderDict) -> None: + for key in other: + val = other.getlist(key) + self._container[key.lower()] = [key, *val] + + def copy(self) -> Self: + clone = type(self)() + clone._copy_from(self) + return clone + + def iteritems(self) -> typing.Iterator[tuple[str, str]]: + """Iterate over all header lines, including duplicate ones.""" + for key in self: + vals = self._container[key.lower()] + for val in vals[1:]: + yield vals[0], val + + def itermerged(self) -> typing.Iterator[tuple[str, str]]: + """Iterate over all headers, merging duplicate ones together.""" + for key in self: + val = self._container[key.lower()] + yield val[0], ", ".join(val[1:]) + + def items(self) -> HTTPHeaderDictItemView: # type: ignore[override] + return HTTPHeaderDictItemView(self) + + def _has_value_for_header(self, header_name: str, potential_value: str) -> bool: + if header_name in self: + return potential_value in self._container[header_name.lower()][1:] + return False + + def __ior__(self, other: object) -> HTTPHeaderDict: + # Supports extending a header dict in-place using operator |= + # combining items with add instead of __setitem__ + maybe_constructable = ensure_can_construct_http_header_dict(other) + if maybe_constructable is None: + return NotImplemented + self.extend(maybe_constructable) + return self + + def __or__(self, other: object) -> Self: + # Supports merging header dicts using operator | + # combining items with add instead of __setitem__ + maybe_constructable = ensure_can_construct_http_header_dict(other) + if maybe_constructable is None: + return NotImplemented + result = self.copy() + result.extend(maybe_constructable) + return result + + def __ror__(self, other: object) -> Self: + # Supports merging header dicts using operator | when other is on left side + # combining items with add instead of __setitem__ + maybe_constructable = ensure_can_construct_http_header_dict(other) + if maybe_constructable is None: + return NotImplemented + result = type(self)(maybe_constructable) + result.extend(self) + return result diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/_request_methods.py b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/_request_methods.py new file mode 100644 index 0000000000000000000000000000000000000000..297c271bf401c1cb48c6225f8822e78f58c3ca56 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/_request_methods.py @@ -0,0 +1,278 @@ +from __future__ import annotations + +import json as _json +import typing +from urllib.parse import urlencode + +from ._base_connection import _TYPE_BODY +from ._collections import HTTPHeaderDict +from .filepost import _TYPE_FIELDS, encode_multipart_formdata +from .response import BaseHTTPResponse + +__all__ = ["RequestMethods"] + +_TYPE_ENCODE_URL_FIELDS = typing.Union[ + typing.Sequence[tuple[str, typing.Union[str, bytes]]], + typing.Mapping[str, typing.Union[str, bytes]], +] + + +class RequestMethods: + """ + Convenience mixin for classes who implement a :meth:`urlopen` method, such + as :class:`urllib3.HTTPConnectionPool` and + :class:`urllib3.PoolManager`. + + Provides behavior for making common types of HTTP request methods and + decides which type of request field encoding to use. + + Specifically, + + :meth:`.request_encode_url` is for sending requests whose fields are + encoded in the URL (such as GET, HEAD, DELETE). + + :meth:`.request_encode_body` is for sending requests whose fields are + encoded in the *body* of the request using multipart or www-form-urlencoded + (such as for POST, PUT, PATCH). + + :meth:`.request` is for making any kind of request, it will look up the + appropriate encoding format and use one of the above two methods to make + the request. + + Initializer parameters: + + :param headers: + Headers to include with all requests, unless other headers are given + explicitly. + """ + + _encode_url_methods = {"DELETE", "GET", "HEAD", "OPTIONS"} + + def __init__(self, headers: typing.Mapping[str, str] | None = None) -> None: + self.headers = headers or {} + + def urlopen( + self, + method: str, + url: str, + body: _TYPE_BODY | None = None, + headers: typing.Mapping[str, str] | None = None, + encode_multipart: bool = True, + multipart_boundary: str | None = None, + **kw: typing.Any, + ) -> BaseHTTPResponse: # Abstract + raise NotImplementedError( + "Classes extending RequestMethods must implement " + "their own ``urlopen`` method." + ) + + def request( + self, + method: str, + url: str, + body: _TYPE_BODY | None = None, + fields: _TYPE_FIELDS | None = None, + headers: typing.Mapping[str, str] | None = None, + json: typing.Any | None = None, + **urlopen_kw: typing.Any, + ) -> BaseHTTPResponse: + """ + Make a request using :meth:`urlopen` with the appropriate encoding of + ``fields`` based on the ``method`` used. + + This is a convenience method that requires the least amount of manual + effort. It can be used in most situations, while still having the + option to drop down to more specific methods when necessary, such as + :meth:`request_encode_url`, :meth:`request_encode_body`, + or even the lowest level :meth:`urlopen`. + + :param method: + HTTP request method (such as GET, POST, PUT, etc.) + + :param url: + The URL to perform the request on. + + :param body: + Data to send in the request body, either :class:`str`, :class:`bytes`, + an iterable of :class:`str`/:class:`bytes`, or a file-like object. + + :param fields: + Data to encode and send in the URL or request body, depending on ``method``. + + :param headers: + Dictionary of custom headers to send, such as User-Agent, + If-None-Match, etc. If None, pool headers are used. If provided, + these headers completely replace any pool-specific headers. + + :param json: + Data to encode and send as JSON with UTF-encoded in the request body. + The ``"Content-Type"`` header will be set to ``"application/json"`` + unless specified otherwise. + """ + method = method.upper() + + if json is not None and body is not None: + raise TypeError( + "request got values for both 'body' and 'json' parameters which are mutually exclusive" + ) + + if json is not None: + if headers is None: + headers = self.headers + + if not ("content-type" in map(str.lower, headers.keys())): + headers = HTTPHeaderDict(headers) + headers["Content-Type"] = "application/json" + + body = _json.dumps(json, separators=(",", ":"), ensure_ascii=False).encode( + "utf-8" + ) + + if body is not None: + urlopen_kw["body"] = body + + if method in self._encode_url_methods: + return self.request_encode_url( + method, + url, + fields=fields, # type: ignore[arg-type] + headers=headers, + **urlopen_kw, + ) + else: + return self.request_encode_body( + method, url, fields=fields, headers=headers, **urlopen_kw + ) + + def request_encode_url( + self, + method: str, + url: str, + fields: _TYPE_ENCODE_URL_FIELDS | None = None, + headers: typing.Mapping[str, str] | None = None, + **urlopen_kw: str, + ) -> BaseHTTPResponse: + """ + Make a request using :meth:`urlopen` with the ``fields`` encoded in + the url. This is useful for request methods like GET, HEAD, DELETE, etc. + + :param method: + HTTP request method (such as GET, POST, PUT, etc.) + + :param url: + The URL to perform the request on. + + :param fields: + Data to encode and send in the URL. + + :param headers: + Dictionary of custom headers to send, such as User-Agent, + If-None-Match, etc. If None, pool headers are used. If provided, + these headers completely replace any pool-specific headers. + """ + if headers is None: + headers = self.headers + + extra_kw: dict[str, typing.Any] = {"headers": headers} + extra_kw.update(urlopen_kw) + + if fields: + url += "?" + urlencode(fields) + + return self.urlopen(method, url, **extra_kw) + + def request_encode_body( + self, + method: str, + url: str, + fields: _TYPE_FIELDS | None = None, + headers: typing.Mapping[str, str] | None = None, + encode_multipart: bool = True, + multipart_boundary: str | None = None, + **urlopen_kw: str, + ) -> BaseHTTPResponse: + """ + Make a request using :meth:`urlopen` with the ``fields`` encoded in + the body. This is useful for request methods like POST, PUT, PATCH, etc. + + When ``encode_multipart=True`` (default), then + :func:`urllib3.encode_multipart_formdata` is used to encode + the payload with the appropriate content type. Otherwise + :func:`urllib.parse.urlencode` is used with the + 'application/x-www-form-urlencoded' content type. + + Multipart encoding must be used when posting files, and it's reasonably + safe to use it in other times too. However, it may break request + signing, such as with OAuth. + + Supports an optional ``fields`` parameter of key/value strings AND + key/filetuple. A filetuple is a (filename, data, MIME type) tuple where + the MIME type is optional. For example:: + + fields = { + 'foo': 'bar', + 'fakefile': ('foofile.txt', 'contents of foofile'), + 'realfile': ('barfile.txt', open('realfile').read()), + 'typedfile': ('bazfile.bin', open('bazfile').read(), + 'image/jpeg'), + 'nonamefile': 'contents of nonamefile field', + } + + When uploading a file, providing a filename (the first parameter of the + tuple) is optional but recommended to best mimic behavior of browsers. + + Note that if ``headers`` are supplied, the 'Content-Type' header will + be overwritten because it depends on the dynamic random boundary string + which is used to compose the body of the request. The random boundary + string can be explicitly set with the ``multipart_boundary`` parameter. + + :param method: + HTTP request method (such as GET, POST, PUT, etc.) + + :param url: + The URL to perform the request on. + + :param fields: + Data to encode and send in the request body. + + :param headers: + Dictionary of custom headers to send, such as User-Agent, + If-None-Match, etc. If None, pool headers are used. If provided, + these headers completely replace any pool-specific headers. + + :param encode_multipart: + If True, encode the ``fields`` using the multipart/form-data MIME + format. + + :param multipart_boundary: + If not specified, then a random boundary will be generated using + :func:`urllib3.filepost.choose_boundary`. + """ + if headers is None: + headers = self.headers + + extra_kw: dict[str, typing.Any] = {"headers": HTTPHeaderDict(headers)} + body: bytes | str + + if fields: + if "body" in urlopen_kw: + raise TypeError( + "request got values for both 'fields' and 'body', can only specify one." + ) + + if encode_multipart: + body, content_type = encode_multipart_formdata( + fields, boundary=multipart_boundary + ) + else: + body, content_type = ( + urlencode(fields), # type: ignore[arg-type] + "application/x-www-form-urlencoded", + ) + + extra_kw["body"] = body + extra_kw["headers"].setdefault("Content-Type", content_type) + + extra_kw.update(urlopen_kw) + + return self.urlopen(method, url, **extra_kw) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/_version.py b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/_version.py new file mode 100644 index 0000000000000000000000000000000000000000..bfed03985b436e778ef0caa2703c8a6774dda483 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/_version.py @@ -0,0 +1,24 @@ +# file generated by vcs-versioning +# don't change, don't track in version control +from __future__ import annotations + +__all__ = [ + "__version__", + "__version_tuple__", + "version", + "version_tuple", + "__commit_id__", + "commit_id", +] + +version: str +__version__: str +__version_tuple__: tuple[int | str, ...] +version_tuple: tuple[int | str, ...] +commit_id: str | None +__commit_id__: str | None + +__version__ = version = '2.7.0' +__version_tuple__ = version_tuple = (2, 7, 0) + +__commit_id__ = commit_id = None diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/connection.py b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/connection.py new file mode 100644 index 0000000000000000000000000000000000000000..84e1dab9452d18ad0a2020c55f1966f4920f56c2 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/connection.py @@ -0,0 +1,1099 @@ +from __future__ import annotations + +import datetime +import http.client +import logging +import os +import re +import socket +import sys +import threading +import typing +import warnings +from http.client import HTTPConnection as _HTTPConnection +from http.client import HTTPException as HTTPException # noqa: F401 +from http.client import ResponseNotReady +from socket import timeout as SocketTimeout + +if typing.TYPE_CHECKING: + from .response import HTTPResponse + from .util.ssl_ import _TYPE_PEER_CERT_RET_DICT + from .util.ssltransport import SSLTransport + +from ._collections import HTTPHeaderDict +from .http2 import probe as http2_probe +from .util.response import assert_header_parsing +from .util.timeout import _DEFAULT_TIMEOUT, _TYPE_TIMEOUT, Timeout +from .util.util import to_str +from .util.wait import wait_for_read + +try: # Compiled with SSL? + import ssl + + BaseSSLError = ssl.SSLError +except (ImportError, AttributeError): + ssl = None # type: ignore[assignment] + + class BaseSSLError(BaseException): # type: ignore[no-redef] + pass + + +from ._base_connection import _TYPE_BODY +from ._base_connection import ProxyConfig as ProxyConfig +from ._base_connection import _ResponseOptions as _ResponseOptions +from ._version import __version__ +from .exceptions import ( + ConnectTimeoutError, + HeaderParsingError, + NameResolutionError, + NewConnectionError, + ProxyError, + SystemTimeWarning, +) +from .util import SKIP_HEADER, SKIPPABLE_HEADERS, connection, ssl_ +from .util.request import body_to_chunks +from .util.ssl_ import assert_fingerprint as _assert_fingerprint +from .util.ssl_ import ( + create_urllib3_context, + is_ipaddress, + resolve_cert_reqs, + resolve_ssl_version, + ssl_wrap_socket, +) +from .util.ssl_match_hostname import CertificateError, match_hostname +from .util.url import Url + +# Not a no-op, we're adding this to the namespace so it can be imported. +ConnectionError = ConnectionError +BrokenPipeError = BrokenPipeError + + +log = logging.getLogger(__name__) + +port_by_scheme = {"http": 80, "https": 443} + +# When it comes time to update this value as a part of regular maintenance +# (ie test_recent_date is failing) update it to ~6 months before the current date. +RECENT_DATE = datetime.date(2025, 1, 1) + +_CONTAINS_CONTROL_CHAR_RE = re.compile(r"[^-!#$%&'*+.^_`|~0-9a-zA-Z]") + + +class HTTPConnection(_HTTPConnection): + """ + Based on :class:`http.client.HTTPConnection` but provides an extra constructor + backwards-compatibility layer between older and newer Pythons. + + Additional keyword parameters are used to configure attributes of the connection. + Accepted parameters include: + + - ``source_address``: Set the source address for the current connection. + - ``socket_options``: Set specific options on the underlying socket. If not specified, then + defaults are loaded from ``HTTPConnection.default_socket_options`` which includes disabling + Nagle's algorithm (sets TCP_NODELAY to 1) unless the connection is behind a proxy. + + For example, if you wish to enable TCP Keep Alive in addition to the defaults, + you might pass: + + .. code-block:: python + + HTTPConnection.default_socket_options + [ + (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), + ] + + Or you may want to disable the defaults by passing an empty list (e.g., ``[]``). + """ + + default_port: typing.ClassVar[int] = port_by_scheme["http"] # type: ignore[misc] + + #: Disable Nagle's algorithm by default. + #: ``[(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)]`` + default_socket_options: typing.ClassVar[connection._TYPE_SOCKET_OPTIONS] = [ + (socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + ] + + #: Whether this connection verifies the host's certificate. + is_verified: bool = False + + #: Whether this proxy connection verified the proxy host's certificate. + # If no proxy is currently connected to the value will be ``None``. + proxy_is_verified: bool | None = None + + blocksize: int + source_address: tuple[str, int] | None + socket_options: connection._TYPE_SOCKET_OPTIONS | None + + _has_connected_to_proxy: bool + _response_options: _ResponseOptions | None + _tunnel_host: str | None + _tunnel_port: int | None + _tunnel_scheme: str | None + + def __init__( + self, + host: str, + port: int | None = None, + *, + timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + source_address: tuple[str, int] | None = None, + blocksize: int = 16384, + socket_options: None | ( + connection._TYPE_SOCKET_OPTIONS + ) = default_socket_options, + proxy: Url | None = None, + proxy_config: ProxyConfig | None = None, + ) -> None: + super().__init__( + host=host, + port=port, + timeout=Timeout.resolve_default_timeout(timeout), + source_address=source_address, + blocksize=blocksize, + ) + self.socket_options = socket_options + self.proxy = proxy + self.proxy_config = proxy_config + + self._has_connected_to_proxy = False + self._response_options = None + self._tunnel_host: str | None = None + self._tunnel_port: int | None = None + self._tunnel_scheme: str | None = None + + def __str__(self) -> str: + return f"{type(self).__name__}(host={self.host!r}, port={self.port!r})" + + def __repr__(self) -> str: + return f"<{self} at {id(self):#x}>" + + @property + def host(self) -> str: + """ + Getter method to remove any trailing dots that indicate the hostname is an FQDN. + + In general, SSL certificates don't include the trailing dot indicating a + fully-qualified domain name, and thus, they don't validate properly when + checked against a domain name that includes the dot. In addition, some + servers may not expect to receive the trailing dot when provided. + + However, the hostname with trailing dot is critical to DNS resolution; doing a + lookup with the trailing dot will properly only resolve the appropriate FQDN, + whereas a lookup without a trailing dot will search the system's search domain + list. Thus, it's important to keep the original host around for use only in + those cases where it's appropriate (i.e., when doing DNS lookup to establish the + actual TCP connection across which we're going to send HTTP requests). + """ + return self._dns_host.rstrip(".") + + @host.setter + def host(self, value: str) -> None: + """ + Setter for the `host` property. + + We assume that only urllib3 uses the _dns_host attribute; httplib itself + only uses `host`, and it seems reasonable that other libraries follow suit. + """ + self._dns_host = value + + def _new_conn(self) -> socket.socket: + """Establish a socket connection and set nodelay settings on it. + + :return: New socket connection. + """ + try: + sock = connection.create_connection( + (self._dns_host, self.port), + self.timeout, + source_address=self.source_address, + socket_options=self.socket_options, + ) + except socket.gaierror as e: + raise NameResolutionError(self.host, self, e) from e + except SocketTimeout as e: + raise ConnectTimeoutError( + self, + f"Connection to {self.host} timed out. (connect timeout={self.timeout})", + ) from e + + except OSError as e: + raise NewConnectionError( + self, f"Failed to establish a new connection: {e}" + ) from e + + sys.audit("http.client.connect", self, self.host, self.port) + + return sock + + def set_tunnel( + self, + host: str, + port: int | None = None, + headers: typing.Mapping[str, str] | None = None, + scheme: str = "http", + ) -> None: + if scheme not in ("http", "https"): + raise ValueError( + f"Invalid proxy scheme for tunneling: {scheme!r}, must be either 'http' or 'https'" + ) + super().set_tunnel(host, port=port, headers=headers) + self._tunnel_scheme = scheme + + if sys.version_info < (3, 11, 9) or ((3, 12) <= sys.version_info < (3, 12, 3)): + # Taken from python/cpython#100986 which was backported in 3.11.9 and 3.12.3. + # When using connection_from_host, host will come without brackets. + def _wrap_ipv6(self, ip: bytes) -> bytes: + if b":" in ip and ip[0] != b"["[0]: + return b"[" + ip + b"]" + return ip + + if sys.version_info < (3, 11, 9): + # `_tunnel` copied from 3.11.13 backporting + # https://github.com/python/cpython/commit/0d4026432591d43185568dd31cef6a034c4b9261 + # and https://github.com/python/cpython/commit/6fbc61070fda2ffb8889e77e3b24bca4249ab4d1 + def _tunnel(self) -> None: + _MAXLINE = http.client._MAXLINE # type: ignore[attr-defined] + connect = b"CONNECT %s:%d HTTP/1.0\r\n" % ( # type: ignore[str-format] + self._wrap_ipv6(self._tunnel_host.encode("ascii")), # type: ignore[union-attr] + self._tunnel_port, + ) + headers = [connect] + for header, value in self._tunnel_headers.items(): # type: ignore[attr-defined] + headers.append(f"{header}: {value}\r\n".encode("latin-1")) + headers.append(b"\r\n") + # Making a single send() call instead of one per line encourages + # the host OS to use a more optimal packet size instead of + # potentially emitting a series of small packets. + self.send(b"".join(headers)) + del headers + + response = self.response_class(self.sock, method=self._method) # type: ignore[attr-defined] + try: + (version, code, message) = response._read_status() # type: ignore[attr-defined] + + if code != http.HTTPStatus.OK: + self.close() + raise OSError( + f"Tunnel connection failed: {code} {message.strip()}" + ) + while True: + line = response.fp.readline(_MAXLINE + 1) + if len(line) > _MAXLINE: + raise http.client.LineTooLong("header line") + if not line: + # for sites which EOF without sending a trailer + break + if line in (b"\r\n", b"\n", b""): + break + + if self.debuglevel > 0: + print("header:", line.decode()) + finally: + response.close() + + elif (3, 12) <= sys.version_info < (3, 12, 3): + # `_tunnel` copied from 3.12.11 backporting + # https://github.com/python/cpython/commit/23aef575c7629abcd4aaf028ebd226fb41a4b3c8 + def _tunnel(self) -> None: # noqa: F811 + connect = b"CONNECT %s:%d HTTP/1.1\r\n" % ( # type: ignore[str-format] + self._wrap_ipv6(self._tunnel_host.encode("idna")), # type: ignore[union-attr] + self._tunnel_port, + ) + headers = [connect] + for header, value in self._tunnel_headers.items(): # type: ignore[attr-defined] + headers.append(f"{header}: {value}\r\n".encode("latin-1")) + headers.append(b"\r\n") + # Making a single send() call instead of one per line encourages + # the host OS to use a more optimal packet size instead of + # potentially emitting a series of small packets. + self.send(b"".join(headers)) + del headers + + response = self.response_class(self.sock, method=self._method) # type: ignore[attr-defined] + try: + (version, code, message) = response._read_status() # type: ignore[attr-defined] + + self._raw_proxy_headers = http.client._read_headers(response.fp) # type: ignore[attr-defined] + + if self.debuglevel > 0: + for header in self._raw_proxy_headers: + print("header:", header.decode()) + + if code != http.HTTPStatus.OK: + self.close() + raise OSError( + f"Tunnel connection failed: {code} {message.strip()}" + ) + + finally: + response.close() + + def connect(self) -> None: + self.sock = self._new_conn() + if self._tunnel_host: + # If we're tunneling it means we're connected to our proxy. + self._has_connected_to_proxy = True + + # TODO: Fix tunnel so it doesn't depend on self.sock state. + self._tunnel() + + # If there's a proxy to be connected to we are fully connected. + # This is set twice (once above and here) due to forwarding proxies + # not using tunnelling. + self._has_connected_to_proxy = bool(self.proxy) + + if self._has_connected_to_proxy: + self.proxy_is_verified = False + + @property + def is_closed(self) -> bool: + return self.sock is None + + @property + def is_connected(self) -> bool: + if self.sock is None: + return False + return not wait_for_read(self.sock, timeout=0.0) + + @property + def has_connected_to_proxy(self) -> bool: + return self._has_connected_to_proxy + + @property + def proxy_is_forwarding(self) -> bool: + """ + Return True if a forwarding proxy is configured, else return False + """ + return bool(self.proxy) and self._tunnel_host is None + + @property + def proxy_is_tunneling(self) -> bool: + """ + Return True if a tunneling proxy is configured, else return False + """ + return self._tunnel_host is not None + + def close(self) -> None: + try: + super().close() + finally: + # Reset all stateful properties so connection + # can be re-used without leaking prior configs. + self.sock = None + self.is_verified = False + self.proxy_is_verified = None + self._has_connected_to_proxy = False + self._response_options = None + self._tunnel_host = None + self._tunnel_port = None + self._tunnel_scheme = None + + def putrequest( + self, + method: str, + url: str, + skip_host: bool = False, + skip_accept_encoding: bool = False, + ) -> None: + """""" + # Empty docstring because the indentation of CPython's implementation + # is broken but we don't want this method in our documentation. + match = _CONTAINS_CONTROL_CHAR_RE.search(method) + if match: + raise ValueError( + f"Method cannot contain non-token characters {method!r} (found at least {match.group()!r})" + ) + + return super().putrequest( + method, url, skip_host=skip_host, skip_accept_encoding=skip_accept_encoding + ) + + def putheader(self, header: str, *values: str) -> None: # type: ignore[override] + """""" + if not any(isinstance(v, str) and v == SKIP_HEADER for v in values): + super().putheader(header, *values) + elif to_str(header.lower()) not in SKIPPABLE_HEADERS: + skippable_headers = "', '".join( + [str.title(header) for header in sorted(SKIPPABLE_HEADERS)] + ) + raise ValueError( + f"urllib3.util.SKIP_HEADER only supports '{skippable_headers}'" + ) + + # `request` method's signature intentionally violates LSP. + # urllib3's API is different from `http.client.HTTPConnection` and the subclassing is only incidental. + def request( # type: ignore[override] + self, + method: str, + url: str, + body: _TYPE_BODY | None = None, + headers: typing.Mapping[str, str] | None = None, + *, + chunked: bool = False, + preload_content: bool = True, + decode_content: bool = True, + enforce_content_length: bool = True, + ) -> None: + # Update the inner socket's timeout value to send the request. + # This only triggers if the connection is re-used. + if self.sock is not None: + self.sock.settimeout(self.timeout) + + # Store these values to be fed into the HTTPResponse + # object later. TODO: Remove this in favor of a real + # HTTP lifecycle mechanism. + + # We have to store these before we call .request() + # because sometimes we can still salvage a response + # off the wire even if we aren't able to completely + # send the request body. + self._response_options = _ResponseOptions( + request_method=method, + request_url=url, + preload_content=preload_content, + decode_content=decode_content, + enforce_content_length=enforce_content_length, + ) + + if headers is None: + headers = {} + header_keys = frozenset(to_str(k.lower()) for k in headers) + skip_accept_encoding = "accept-encoding" in header_keys + skip_host = "host" in header_keys + self.putrequest( + method, url, skip_accept_encoding=skip_accept_encoding, skip_host=skip_host + ) + + # Transform the body into an iterable of sendall()-able chunks + # and detect if an explicit Content-Length is doable. + chunks_and_cl = body_to_chunks(body, method=method, blocksize=self.blocksize) + chunks = chunks_and_cl.chunks + content_length = chunks_and_cl.content_length + + # When chunked is explicit set to 'True' we respect that. + if chunked: + if "transfer-encoding" not in header_keys: + self.putheader("Transfer-Encoding", "chunked") + else: + # Detect whether a framing mechanism is already in use. If so + # we respect that value, otherwise we pick chunked vs content-length + # depending on the type of 'body'. + if "content-length" in header_keys: + chunked = False + elif "transfer-encoding" in header_keys: + chunked = True + + # Otherwise we go off the recommendation of 'body_to_chunks()'. + else: + chunked = False + if content_length is None: + if chunks is not None: + chunked = True + self.putheader("Transfer-Encoding", "chunked") + else: + self.putheader("Content-Length", str(content_length)) + + # Now that framing headers are out of the way we send all the other headers. + if "user-agent" not in header_keys: + self.putheader("User-Agent", _get_default_user_agent()) + for header, value in headers.items(): + self.putheader(header, value) + self.endheaders() + + # If we're given a body we start sending that in chunks. + if chunks is not None: + for chunk in chunks: + # Sending empty chunks isn't allowed for TE: chunked + # as it indicates the end of the body. + if not chunk: + continue + if isinstance(chunk, str): + chunk = chunk.encode("utf-8") + if chunked: + self.send(b"%x\r\n%b\r\n" % (len(chunk), chunk)) + else: + self.send(chunk) + + # Regardless of whether we have a body or not, if we're in + # chunked mode we want to send an explicit empty chunk. + if chunked: + self.send(b"0\r\n\r\n") + + def request_chunked( + self, + method: str, + url: str, + body: _TYPE_BODY | None = None, + headers: typing.Mapping[str, str] | None = None, + ) -> None: + """ + Alternative to the common request method, which sends the + body with chunked encoding and not as one block + """ + warnings.warn( + "HTTPConnection.request_chunked() is deprecated and will be removed " + "in urllib3 v3.0. Instead use HTTPConnection.request(..., chunked=True).", + category=FutureWarning, + stacklevel=2, + ) + self.request(method, url, body=body, headers=headers, chunked=True) + + def getresponse( # type: ignore[override] + self, + ) -> HTTPResponse: + """ + Get the response from the server. + + If the HTTPConnection is in the correct state, returns an instance of HTTPResponse or of whatever object is returned by the response_class variable. + + If a request has not been sent or if a previous response has not be handled, ResponseNotReady is raised. If the HTTP response indicates that the connection should be closed, then it will be closed before the response is returned. When the connection is closed, the underlying socket is closed. + """ + # Raise the same error as http.client.HTTPConnection + if self._response_options is None: + raise ResponseNotReady() + + # Reset this attribute for being used again. + resp_options = self._response_options + self._response_options = None + + # Since the connection's timeout value may have been updated + # we need to set the timeout on the socket. + self.sock.settimeout(self.timeout) + + # This is needed here to avoid circular import errors + from .response import HTTPResponse + + # Save a reference to the shutdown function before ownership is passed + # to httplib_response + # TODO should we implement it everywhere? + _shutdown = getattr(self.sock, "shutdown", None) + + # Get the response from http.client.HTTPConnection + httplib_response = super().getresponse() + + try: + assert_header_parsing(httplib_response.msg) + except (HeaderParsingError, TypeError) as hpe: + log.warning( + "Failed to parse headers (url=%s): %s", + _url_from_connection(self, resp_options.request_url), + hpe, + exc_info=True, + ) + + headers = HTTPHeaderDict(httplib_response.msg.items()) + + response = HTTPResponse( + body=httplib_response, + headers=headers, + status=httplib_response.status, + version=httplib_response.version, + version_string=getattr(self, "_http_vsn_str", "HTTP/?"), + reason=httplib_response.reason, + preload_content=resp_options.preload_content, + decode_content=resp_options.decode_content, + original_response=httplib_response, + enforce_content_length=resp_options.enforce_content_length, + request_method=resp_options.request_method, + request_url=resp_options.request_url, + sock_shutdown=_shutdown, + ) + return response + + +class HTTPSConnection(HTTPConnection): + """ + Many of the parameters to this constructor are passed to the underlying SSL + socket by means of :py:func:`urllib3.util.ssl_wrap_socket`. + """ + + default_port = port_by_scheme["https"] # type: ignore[misc] + + cert_reqs: int | str | None = None + ca_certs: str | None = None + ca_cert_dir: str | None = None + ca_cert_data: None | str | bytes = None + ssl_version: int | str | None = None + ssl_minimum_version: int | None = None + ssl_maximum_version: int | None = None + assert_fingerprint: str | None = None + _connect_callback: typing.Callable[..., None] | None = None + + def __init__( + self, + host: str, + port: int | None = None, + *, + timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + source_address: tuple[str, int] | None = None, + blocksize: int = 16384, + socket_options: None | ( + connection._TYPE_SOCKET_OPTIONS + ) = HTTPConnection.default_socket_options, + proxy: Url | None = None, + proxy_config: ProxyConfig | None = None, + cert_reqs: int | str | None = None, + assert_hostname: None | str | typing.Literal[False] = None, + assert_fingerprint: str | None = None, + server_hostname: str | None = None, + ssl_context: ssl.SSLContext | None = None, + ca_certs: str | None = None, + ca_cert_dir: str | None = None, + ca_cert_data: None | str | bytes = None, + ssl_minimum_version: int | None = None, + ssl_maximum_version: int | None = None, + ssl_version: int | str | None = None, # Deprecated + cert_file: str | None = None, + key_file: str | None = None, + key_password: str | None = None, + ) -> None: + super().__init__( + host, + port=port, + timeout=timeout, + source_address=source_address, + blocksize=blocksize, + socket_options=socket_options, + proxy=proxy, + proxy_config=proxy_config, + ) + + self.key_file = key_file + self.cert_file = cert_file + self.key_password = key_password + self.ssl_context = ssl_context + self.server_hostname = server_hostname + self.assert_hostname = assert_hostname + self.assert_fingerprint = assert_fingerprint + self.ssl_version = ssl_version + self.ssl_minimum_version = ssl_minimum_version + self.ssl_maximum_version = ssl_maximum_version + self.ca_certs = ca_certs and os.path.expanduser(ca_certs) + self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir) + self.ca_cert_data = ca_cert_data + + # cert_reqs depends on ssl_context so calculate last. + if cert_reqs is None: + if self.ssl_context is not None: + cert_reqs = self.ssl_context.verify_mode + else: + cert_reqs = resolve_cert_reqs(None) + self.cert_reqs = cert_reqs + self._connect_callback = None + + def set_cert( + self, + key_file: str | None = None, + cert_file: str | None = None, + cert_reqs: int | str | None = None, + key_password: str | None = None, + ca_certs: str | None = None, + assert_hostname: None | str | typing.Literal[False] = None, + assert_fingerprint: str | None = None, + ca_cert_dir: str | None = None, + ca_cert_data: None | str | bytes = None, + ) -> None: + """ + This method should only be called once, before the connection is used. + """ + warnings.warn( + "HTTPSConnection.set_cert() is deprecated and will be removed " + "in urllib3 v3.0. Instead provide the parameters to the " + "HTTPSConnection constructor.", + category=FutureWarning, + stacklevel=2, + ) + + # If cert_reqs is not provided we'll assume CERT_REQUIRED unless we also + # have an SSLContext object in which case we'll use its verify_mode. + if cert_reqs is None: + if self.ssl_context is not None: + cert_reqs = self.ssl_context.verify_mode + else: + cert_reqs = resolve_cert_reqs(None) + + self.key_file = key_file + self.cert_file = cert_file + self.cert_reqs = cert_reqs + self.key_password = key_password + self.assert_hostname = assert_hostname + self.assert_fingerprint = assert_fingerprint + self.ca_certs = ca_certs and os.path.expanduser(ca_certs) + self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir) + self.ca_cert_data = ca_cert_data + + def connect(self) -> None: + # Today we don't need to be doing this step before the /actual/ socket + # connection, however in the future we'll need to decide whether to + # create a new socket or re-use an existing "shared" socket as a part + # of the HTTP/2 handshake dance. + if self._tunnel_host is not None and self._tunnel_port is not None: + probe_http2_host = self._tunnel_host + probe_http2_port = self._tunnel_port + else: + probe_http2_host = self.host + probe_http2_port = self.port + + # Check if the target origin supports HTTP/2. + # If the value comes back as 'None' it means that the current thread + # is probing for HTTP/2 support. Otherwise, we're waiting for another + # probe to complete, or we get a value right away. + target_supports_http2: bool | None + if "h2" in ssl_.ALPN_PROTOCOLS: + target_supports_http2 = http2_probe.acquire_and_get( + host=probe_http2_host, port=probe_http2_port + ) + else: + # If HTTP/2 isn't going to be offered it doesn't matter if + # the target supports HTTP/2. Don't want to make a probe. + target_supports_http2 = False + + if self._connect_callback is not None: + self._connect_callback( + "before connect", + thread_id=threading.get_ident(), + target_supports_http2=target_supports_http2, + ) + + try: + sock: socket.socket | ssl.SSLSocket + self.sock = sock = self._new_conn() + server_hostname: str = self.host + tls_in_tls = False + + # Do we need to establish a tunnel? + if self.proxy_is_tunneling: + # We're tunneling to an HTTPS origin so need to do TLS-in-TLS. + if self._tunnel_scheme == "https": + # _connect_tls_proxy will verify and assign proxy_is_verified + self.sock = sock = self._connect_tls_proxy(self.host, sock) + tls_in_tls = True + elif self._tunnel_scheme == "http": + self.proxy_is_verified = False + + # If we're tunneling it means we're connected to our proxy. + self._has_connected_to_proxy = True + + self._tunnel() + # Override the host with the one we're requesting data from. + server_hostname = typing.cast(str, self._tunnel_host) + + if self.server_hostname is not None: + server_hostname = self.server_hostname + + is_time_off = datetime.date.today() < RECENT_DATE + if is_time_off: + warnings.warn( + ( + f"System time is way off (before {RECENT_DATE}). This will probably " + "lead to SSL verification errors" + ), + SystemTimeWarning, + ) + + # Remove trailing '.' from fqdn hostnames to allow certificate validation + server_hostname_rm_dot = server_hostname.rstrip(".") + + sock_and_verified = _ssl_wrap_socket_and_match_hostname( + sock=sock, + cert_reqs=self.cert_reqs, + ssl_version=self.ssl_version, + ssl_minimum_version=self.ssl_minimum_version, + ssl_maximum_version=self.ssl_maximum_version, + ca_certs=self.ca_certs, + ca_cert_dir=self.ca_cert_dir, + ca_cert_data=self.ca_cert_data, + cert_file=self.cert_file, + key_file=self.key_file, + key_password=self.key_password, + server_hostname=server_hostname_rm_dot, + ssl_context=self.ssl_context, + tls_in_tls=tls_in_tls, + assert_hostname=self.assert_hostname, + assert_fingerprint=self.assert_fingerprint, + ) + self.sock = sock_and_verified.socket + + # If an error occurs during connection/handshake we may need to release + # our lock so another connection can probe the origin. + except BaseException: + if self._connect_callback is not None: + self._connect_callback( + "after connect failure", + thread_id=threading.get_ident(), + target_supports_http2=target_supports_http2, + ) + + if target_supports_http2 is None: + http2_probe.set_and_release( + host=probe_http2_host, port=probe_http2_port, supports_http2=None + ) + raise + + # If this connection doesn't know if the origin supports HTTP/2 + # we report back to the HTTP/2 probe our result. + if target_supports_http2 is None: + supports_http2 = sock_and_verified.socket.selected_alpn_protocol() == "h2" + http2_probe.set_and_release( + host=probe_http2_host, + port=probe_http2_port, + supports_http2=supports_http2, + ) + + # Forwarding proxies can never have a verified target since + # the proxy is the one doing the verification. Should instead + # use a CONNECT tunnel in order to verify the target. + # See: https://github.com/urllib3/urllib3/issues/3267. + if self.proxy_is_forwarding: + self.is_verified = False + else: + self.is_verified = sock_and_verified.is_verified + + # If there's a proxy to be connected to we are fully connected. + # This is set twice (once above and here) due to forwarding proxies + # not using tunnelling. + self._has_connected_to_proxy = bool(self.proxy) + + # Set `self.proxy_is_verified` unless it's already set while + # establishing a tunnel. + if self._has_connected_to_proxy and self.proxy_is_verified is None: + self.proxy_is_verified = sock_and_verified.is_verified + + def _connect_tls_proxy(self, hostname: str, sock: socket.socket) -> ssl.SSLSocket: + """ + Establish a TLS connection to the proxy using the provided SSL context. + """ + # `_connect_tls_proxy` is called when self._tunnel_host is truthy. + proxy_config = typing.cast(ProxyConfig, self.proxy_config) + ssl_context = proxy_config.ssl_context + sock_and_verified = _ssl_wrap_socket_and_match_hostname( + sock, + cert_reqs=self.cert_reqs, + ssl_version=self.ssl_version, + ssl_minimum_version=self.ssl_minimum_version, + ssl_maximum_version=self.ssl_maximum_version, + ca_certs=self.ca_certs, + ca_cert_dir=self.ca_cert_dir, + ca_cert_data=self.ca_cert_data, + server_hostname=hostname, + ssl_context=ssl_context, + assert_hostname=proxy_config.assert_hostname, + assert_fingerprint=proxy_config.assert_fingerprint, + # Features that aren't implemented for proxies yet: + cert_file=None, + key_file=None, + key_password=None, + tls_in_tls=False, + ) + self.proxy_is_verified = sock_and_verified.is_verified + return sock_and_verified.socket # type: ignore[return-value] + + +class _WrappedAndVerifiedSocket(typing.NamedTuple): + """ + Wrapped socket and whether the connection is + verified after the TLS handshake + """ + + socket: ssl.SSLSocket | SSLTransport + is_verified: bool + + +def _ssl_wrap_socket_and_match_hostname( + sock: socket.socket, + *, + cert_reqs: None | str | int, + ssl_version: None | str | int, + ssl_minimum_version: int | None, + ssl_maximum_version: int | None, + cert_file: str | None, + key_file: str | None, + key_password: str | None, + ca_certs: str | None, + ca_cert_dir: str | None, + ca_cert_data: None | str | bytes, + assert_hostname: None | str | typing.Literal[False], + assert_fingerprint: str | None, + server_hostname: str | None, + ssl_context: ssl.SSLContext | None, + tls_in_tls: bool = False, +) -> _WrappedAndVerifiedSocket: + """Logic for constructing an SSLContext from all TLS parameters, passing + that down into ssl_wrap_socket, and then doing certificate verification + either via hostname or fingerprint. This function exists to guarantee + that both proxies and targets have the same behavior when connecting via TLS. + """ + default_ssl_context = False + if ssl_context is None: + default_ssl_context = True + context = create_urllib3_context( + ssl_version=resolve_ssl_version(ssl_version), + ssl_minimum_version=ssl_minimum_version, + ssl_maximum_version=ssl_maximum_version, + cert_reqs=resolve_cert_reqs(cert_reqs), + ) + else: + context = ssl_context + + context.verify_mode = resolve_cert_reqs(cert_reqs) + + # In some cases, we want to verify hostnames ourselves + if ( + # `ssl` can't verify fingerprints or alternate hostnames + assert_fingerprint + or assert_hostname + # assert_hostname can be set to False to disable hostname checking + or assert_hostname is False + # We still support OpenSSL 1.0.2, which prevents us from verifying + # hostnames easily: https://github.com/pyca/pyopenssl/pull/933 + or ssl_.IS_PYOPENSSL + or not ssl_.HAS_NEVER_CHECK_COMMON_NAME + ): + context.check_hostname = False + + # Try to load OS default certs if none are given. We need to do the hasattr() check + # for custom pyOpenSSL SSLContext objects because they don't support + # load_default_certs(). + if ( + not ca_certs + and not ca_cert_dir + and not ca_cert_data + and default_ssl_context + and hasattr(context, "load_default_certs") + ): + context.load_default_certs() + + # Ensure that IPv6 addresses are in the proper format and don't have a + # scope ID. Python's SSL module fails to recognize scoped IPv6 addresses + # and interprets them as DNS hostnames. + if server_hostname is not None: + normalized = server_hostname.strip("[]") + if "%" in normalized: + normalized = normalized[: normalized.rfind("%")] + if is_ipaddress(normalized): + server_hostname = normalized + + ssl_sock = ssl_wrap_socket( + sock=sock, + keyfile=key_file, + certfile=cert_file, + key_password=key_password, + ca_certs=ca_certs, + ca_cert_dir=ca_cert_dir, + ca_cert_data=ca_cert_data, + server_hostname=server_hostname, + ssl_context=context, + tls_in_tls=tls_in_tls, + ) + + try: + if assert_fingerprint: + _assert_fingerprint( + ssl_sock.getpeercert(binary_form=True), assert_fingerprint + ) + elif ( + context.verify_mode != ssl.CERT_NONE + and not context.check_hostname + and assert_hostname is not False + ): + cert: _TYPE_PEER_CERT_RET_DICT = ssl_sock.getpeercert() # type: ignore[assignment] + + # Need to signal to our match_hostname whether to use 'commonName' or not. + # If we're using our own constructed SSLContext we explicitly set 'False' + # because PyPy hard-codes 'True' from SSLContext.hostname_checks_common_name. + if default_ssl_context: + hostname_checks_common_name = False + else: + hostname_checks_common_name = ( + getattr(context, "hostname_checks_common_name", False) or False + ) + + _match_hostname( + cert, + assert_hostname or server_hostname, # type: ignore[arg-type] + hostname_checks_common_name, + ) + + return _WrappedAndVerifiedSocket( + socket=ssl_sock, + is_verified=context.verify_mode == ssl.CERT_REQUIRED + or bool(assert_fingerprint), + ) + except BaseException: + ssl_sock.close() + raise + + +def _match_hostname( + cert: _TYPE_PEER_CERT_RET_DICT | None, + asserted_hostname: str, + hostname_checks_common_name: bool = False, +) -> None: + # Our upstream implementation of ssl.match_hostname() + # only applies this normalization to IP addresses so it doesn't + # match DNS SANs so we do the same thing! + stripped_hostname = asserted_hostname.strip("[]") + if is_ipaddress(stripped_hostname): + asserted_hostname = stripped_hostname + + try: + match_hostname(cert, asserted_hostname, hostname_checks_common_name) + except CertificateError as e: + log.warning( + "Certificate did not match expected hostname: %s. Certificate: %s", + asserted_hostname, + cert, + ) + # Add cert to exception and reraise so client code can inspect + # the cert when catching the exception, if they want to + e._peer_cert = cert # type: ignore[attr-defined] + raise + + +def _wrap_proxy_error(err: Exception, proxy_scheme: str | None) -> ProxyError: + # Look for the phrase 'wrong version number', if found + # then we should warn the user that we're very sure that + # this proxy is HTTP-only and they have a configuration issue. + error_normalized = " ".join(re.split("[^a-z]", str(err).lower())) + is_likely_http_proxy = ( + "wrong version number" in error_normalized + or "unknown protocol" in error_normalized + or "record layer failure" in error_normalized + ) + http_proxy_warning = ( + ". Your proxy appears to only use HTTP and not HTTPS, " + "try changing your proxy URL to be HTTP. See: " + "https://urllib3.readthedocs.io/en/latest/advanced-usage.html" + "#https-proxy-error-http-proxy" + ) + new_err = ProxyError( + f"Unable to connect to proxy" + f"{http_proxy_warning if is_likely_http_proxy and proxy_scheme == 'https' else ''}", + err, + ) + new_err.__cause__ = err + return new_err + + +def _get_default_user_agent() -> str: + return f"python-urllib3/{__version__}" + + +class DummyConnection: + """Used to detect a failed ConnectionCls import.""" + + +if not ssl: + HTTPSConnection = DummyConnection # type: ignore[misc, assignment] # noqa: F811 + + +VerifiedHTTPSConnection = HTTPSConnection + + +def _url_from_connection( + conn: HTTPConnection | HTTPSConnection, path: str | None = None +) -> str: + """Returns the URL from a given connection. This is mainly used for testing and logging.""" + + scheme = "https" if isinstance(conn, HTTPSConnection) else "http" + + return Url(scheme=scheme, host=conn.host, port=conn.port, path=path).url diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/connectionpool.py b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/connectionpool.py new file mode 100644 index 0000000000000000000000000000000000000000..70fbc5e725aee571654b1a58748537fa167b498d --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/connectionpool.py @@ -0,0 +1,1191 @@ +from __future__ import annotations + +import errno +import logging +import queue +import sys +import typing +import warnings +import weakref +from socket import timeout as SocketTimeout +from types import TracebackType + +from ._base_connection import _TYPE_BODY +from ._collections import HTTPHeaderDict +from ._request_methods import RequestMethods +from .connection import ( + BaseSSLError, + BrokenPipeError, + DummyConnection, + HTTPConnection, + HTTPException, + HTTPSConnection, + ProxyConfig, + _wrap_proxy_error, +) +from .connection import port_by_scheme as port_by_scheme +from .exceptions import ( + ClosedPoolError, + EmptyPoolError, + FullPoolError, + HostChangedError, + InsecureRequestWarning, + LocationValueError, + MaxRetryError, + NewConnectionError, + ProtocolError, + ProxyError, + ReadTimeoutError, + SSLError, + TimeoutError, +) +from .response import BaseHTTPResponse +from .util.connection import is_connection_dropped +from .util.proxy import connection_requires_http_tunnel +from .util.request import _TYPE_BODY_POSITION, set_file_position +from .util.retry import Retry +from .util.ssl_match_hostname import CertificateError +from .util.timeout import _DEFAULT_TIMEOUT, _TYPE_DEFAULT, Timeout +from .util.url import Url, _encode_target +from .util.url import _normalize_host as normalize_host +from .util.url import parse_url +from .util.util import to_str + +if typing.TYPE_CHECKING: + import ssl + + from typing_extensions import Self + + from ._base_connection import BaseHTTPConnection, BaseHTTPSConnection + +log = logging.getLogger(__name__) + +_TYPE_TIMEOUT = typing.Union[Timeout, float, _TYPE_DEFAULT, None] + + +# Pool objects +class ConnectionPool: + """ + Base class for all connection pools, such as + :class:`.HTTPConnectionPool` and :class:`.HTTPSConnectionPool`. + + .. note:: + ConnectionPool.urlopen() does not normalize or percent-encode target URIs + which is useful if your target server doesn't support percent-encoded + target URIs. + """ + + scheme: str | None = None + QueueCls = queue.LifoQueue + + def __init__(self, host: str, port: int | None = None) -> None: + if not host: + raise LocationValueError("No host specified.") + + self.host = _normalize_host(host, scheme=self.scheme) + self.port = port + + # This property uses 'normalize_host()' (not '_normalize_host()') + # to avoid removing square braces around IPv6 addresses. + # This value is sent to `HTTPConnection.set_tunnel()` if called + # because square braces are required for HTTP CONNECT tunneling. + self._tunnel_host = normalize_host(host, scheme=self.scheme).lower() + + def __str__(self) -> str: + return f"{type(self).__name__}(host={self.host!r}, port={self.port!r})" + + def __enter__(self) -> Self: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> typing.Literal[False]: + self.close() + # Return False to re-raise any potential exceptions + return False + + def close(self) -> None: + """ + Close all pooled connections and disable the pool. + """ + + +# This is taken from http://hg.python.org/cpython/file/7aaba721ebc0/Lib/socket.py#l252 +_blocking_errnos = {errno.EAGAIN, errno.EWOULDBLOCK} + + +class HTTPConnectionPool(ConnectionPool, RequestMethods): + """ + Thread-safe connection pool for one host. + + :param host: + Host used for this HTTP Connection (e.g. "localhost"), passed into + :class:`http.client.HTTPConnection`. + + :param port: + Port used for this HTTP Connection (None is equivalent to 80), passed + into :class:`http.client.HTTPConnection`. + + :param timeout: + Socket timeout in seconds for each individual connection. This can + be a float or integer, which sets the timeout for the HTTP request, + or an instance of :class:`urllib3.util.Timeout` which gives you more + fine-grained control over request timeouts. After the constructor has + been parsed, this is always a `urllib3.util.Timeout` object. + + :param maxsize: + Number of connections to save that can be reused. More than 1 is useful + in multithreaded situations. If ``block`` is set to False, more + connections will be created but they will not be saved once they've + been used. + + :param block: + If set to True, no more than ``maxsize`` connections will be used at + a time. When no free connections are available, the call will block + until a connection has been released. This is a useful side effect for + particular multithreaded situations where one does not want to use more + than maxsize connections per host to prevent flooding. + + :param headers: + Headers to include with all requests, unless other headers are given + explicitly. + + :param retries: + Retry configuration to use by default with requests in this pool. + + :param _proxy: + Parsed proxy URL, should not be used directly, instead, see + :class:`urllib3.ProxyManager` + + :param _proxy_headers: + A dictionary with proxy headers, should not be used directly, + instead, see :class:`urllib3.ProxyManager` + + :param \\**conn_kw: + Additional parameters are used to create fresh :class:`urllib3.connection.HTTPConnection`, + :class:`urllib3.connection.HTTPSConnection` instances. + """ + + scheme = "http" + ConnectionCls: type[BaseHTTPConnection] | type[BaseHTTPSConnection] = HTTPConnection + + def __init__( + self, + host: str, + port: int | None = None, + timeout: _TYPE_TIMEOUT | None = _DEFAULT_TIMEOUT, + maxsize: int = 1, + block: bool = False, + headers: typing.Mapping[str, str] | None = None, + retries: Retry | bool | int | None = None, + _proxy: Url | None = None, + _proxy_headers: typing.Mapping[str, str] | None = None, + _proxy_config: ProxyConfig | None = None, + **conn_kw: typing.Any, + ): + ConnectionPool.__init__(self, host, port) + RequestMethods.__init__(self, headers) + + if not isinstance(timeout, Timeout): + timeout = Timeout.from_float(timeout) + + if retries is None: + retries = Retry.DEFAULT + + self.timeout = timeout + self.retries = retries + + self.pool: queue.LifoQueue[typing.Any] | None = self.QueueCls(maxsize) + self.block = block + + self.proxy = _proxy + self.proxy_headers = _proxy_headers or {} + self.proxy_config = _proxy_config + + # Fill the queue up so that doing get() on it will block properly + for _ in range(maxsize): + self.pool.put(None) + + # These are mostly for testing and debugging purposes. + self.num_connections = 0 + self.num_requests = 0 + self.conn_kw = conn_kw + + if self.proxy: + # Enable Nagle's algorithm for proxies, to avoid packet fragmentation. + # Defaulting `socket_options` to an empty list avoids it defaulting to + # ``HTTPConnection.default_socket_options``. + self.conn_kw.setdefault("socket_options", []) + + self.conn_kw["proxy"] = self.proxy + self.conn_kw["proxy_config"] = self.proxy_config + + # Do not pass 'self' as callback to 'finalize'. + # Then the 'finalize' would keep an endless living (leak) to self. + # By just passing a reference to the pool allows the garbage collector + # to free self if nobody else has a reference to it. + pool = self.pool + + # Close all the HTTPConnections in the pool before the + # HTTPConnectionPool object is garbage collected. + weakref.finalize(self, _close_pool_connections, pool) + + def _new_conn(self) -> BaseHTTPConnection: + """ + Return a fresh :class:`HTTPConnection`. + """ + self.num_connections += 1 + log.debug( + "Starting new HTTP connection (%d): %s:%s", + self.num_connections, + self.host, + self.port or "80", + ) + + conn = self.ConnectionCls( + host=self.host, + port=self.port, + timeout=self.timeout.connect_timeout, + **self.conn_kw, + ) + return conn + + def _get_conn(self, timeout: float | None = None) -> BaseHTTPConnection: + """ + Get a connection. Will return a pooled connection if one is available. + + If no connections are available and :prop:`.block` is ``False``, then a + fresh connection is returned. + + :param timeout: + Seconds to wait before giving up and raising + :class:`urllib3.exceptions.EmptyPoolError` if the pool is empty and + :prop:`.block` is ``True``. + """ + conn = None + + if self.pool is None: + raise ClosedPoolError(self, "Pool is closed.") + + try: + conn = self.pool.get(block=self.block, timeout=timeout) + + except AttributeError: # self.pool is None + raise ClosedPoolError(self, "Pool is closed.") from None # Defensive: + + except queue.Empty: + if self.block: + raise EmptyPoolError( + self, + "Pool is empty and a new connection can't be opened due to blocking mode.", + ) from None + pass # Oh well, we'll create a new connection then + + # If this is a persistent connection, check if it got disconnected + if conn and is_connection_dropped(conn): + log.debug("Resetting dropped connection: %s", self.host) + conn.close() + + return conn or self._new_conn() + + def _put_conn(self, conn: BaseHTTPConnection | None) -> None: + """ + Put a connection back into the pool. + + :param conn: + Connection object for the current host and port as returned by + :meth:`._new_conn` or :meth:`._get_conn`. + + If the pool is already full, the connection is closed and discarded + because we exceeded maxsize. If connections are discarded frequently, + then maxsize should be increased. + + If the pool is closed, then the connection will be closed and discarded. + """ + if self.pool is not None: + try: + self.pool.put(conn, block=False) + return # Everything is dandy, done. + except AttributeError: + # self.pool is None. + pass + except queue.Full: + # Connection never got put back into the pool, close it. + if conn: + conn.close() + + if self.block: + # This should never happen if you got the conn from self._get_conn + raise FullPoolError( + self, + "Pool reached maximum size and no more connections are allowed.", + ) from None + + log.warning( + "Connection pool is full, discarding connection: %s. Connection pool size: %s", + self.host, + self.pool.qsize(), + ) + + # Connection never got put back into the pool, close it. + if conn: + conn.close() + + def _validate_conn(self, conn: BaseHTTPConnection) -> None: + """ + Called right before a request is made, after the socket is created. + """ + + def _prepare_proxy(self, conn: BaseHTTPConnection) -> None: + # Nothing to do for HTTP connections. + pass + + def _get_timeout(self, timeout: _TYPE_TIMEOUT) -> Timeout: + """Helper that always returns a :class:`urllib3.util.Timeout`""" + if timeout is _DEFAULT_TIMEOUT: + return self.timeout.clone() + + if isinstance(timeout, Timeout): + return timeout.clone() + else: + # User passed us an int/float. This is for backwards compatibility, + # can be removed later + return Timeout.from_float(timeout) + + def _raise_timeout( + self, + err: BaseSSLError | OSError | SocketTimeout, + url: str, + timeout_value: _TYPE_TIMEOUT | None, + ) -> None: + """Is the error actually a timeout? Will raise a ReadTimeout or pass""" + + if isinstance(err, SocketTimeout): + raise ReadTimeoutError( + self, url, f"Read timed out. (read timeout={timeout_value})" + ) from err + + # See the above comment about EAGAIN in Python 3. + if hasattr(err, "errno") and err.errno in _blocking_errnos: + raise ReadTimeoutError( + self, url, f"Read timed out. (read timeout={timeout_value})" + ) from err + + def _make_request( + self, + conn: BaseHTTPConnection, + method: str, + url: str, + body: _TYPE_BODY | None = None, + headers: typing.Mapping[str, str] | None = None, + retries: Retry | None = None, + timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + chunked: bool = False, + response_conn: BaseHTTPConnection | None = None, + preload_content: bool = True, + decode_content: bool = True, + enforce_content_length: bool = True, + ) -> BaseHTTPResponse: + """ + Perform a request on a given urllib connection object taken from our + pool. + + :param conn: + a connection from one of our connection pools + + :param method: + HTTP request method (such as GET, POST, PUT, etc.) + + :param url: + The URL to perform the request on. + + :param body: + Data to send in the request body, either :class:`str`, :class:`bytes`, + an iterable of :class:`str`/:class:`bytes`, or a file-like object. + + :param headers: + Dictionary of custom headers to send, such as User-Agent, + If-None-Match, etc. If None, pool headers are used. If provided, + these headers completely replace any pool-specific headers. + + :param retries: + Configure the number of retries to allow before raising a + :class:`~urllib3.exceptions.MaxRetryError` exception. + + Pass ``None`` to retry until you receive a response. Pass a + :class:`~urllib3.util.retry.Retry` object for fine-grained control + over different types of retries. + Pass an integer number to retry connection errors that many times, + but no other types of errors. Pass zero to never retry. + + If ``False``, then retries are disabled and any exception is raised + immediately. Also, instead of raising a MaxRetryError on redirects, + the redirect response will be returned. + + :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int. + + :param timeout: + If specified, overrides the default timeout for this one + request. It may be a float (in seconds) or an instance of + :class:`urllib3.util.Timeout`. + + :param chunked: + If True, urllib3 will send the body using chunked transfer + encoding. Otherwise, urllib3 will send the body using the standard + content-length form. Defaults to False. + + :param response_conn: + Set this to ``None`` if you will handle releasing the connection or + set the connection to have the response release it. + + :param preload_content: + If True, the response's body will be preloaded during construction. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + + :param enforce_content_length: + Enforce content length checking. Body returned by server must match + value of Content-Length header, if present. Otherwise, raise error. + """ + self.num_requests += 1 + + timeout_obj = self._get_timeout(timeout) + timeout_obj.start_connect() + conn.timeout = Timeout.resolve_default_timeout(timeout_obj.connect_timeout) + + try: + # Trigger any extra validation we need to do. + try: + self._validate_conn(conn) + except (SocketTimeout, BaseSSLError) as e: + self._raise_timeout(err=e, url=url, timeout_value=conn.timeout) + raise + + # _validate_conn() starts the connection to an HTTPS proxy + # so we need to wrap errors with 'ProxyError' here too. + except ( + OSError, + NewConnectionError, + TimeoutError, + BaseSSLError, + CertificateError, + SSLError, + ) as e: + new_e: Exception = e + if isinstance(e, (BaseSSLError, CertificateError)): + new_e = SSLError(e) + # If the connection didn't successfully connect to it's proxy + # then there + if isinstance( + new_e, (OSError, NewConnectionError, TimeoutError, SSLError) + ) and (conn and conn.proxy and not conn.has_connected_to_proxy): + new_e = _wrap_proxy_error(new_e, conn.proxy.scheme) + raise new_e + + # conn.request() calls http.client.*.request, not the method in + # urllib3.request. It also calls makefile (recv) on the socket. + try: + conn.request( + method, + url, + body=body, + headers=headers, + chunked=chunked, + preload_content=preload_content, + decode_content=decode_content, + enforce_content_length=enforce_content_length, + ) + + # We are swallowing BrokenPipeError (errno.EPIPE) since the server is + # legitimately able to close the connection after sending a valid response. + # With this behaviour, the received response is still readable. + except BrokenPipeError: + pass + except OSError as e: + # MacOS/Linux + # EPROTOTYPE and ECONNRESET are needed on macOS + # https://erickt.github.io/blog/2014/11/19/adventures-in-debugging-a-potential-osx-kernel-bug/ + # Condition changed later to emit ECONNRESET instead of only EPROTOTYPE. + if e.errno != errno.EPROTOTYPE and e.errno != errno.ECONNRESET: + raise + + # Reset the timeout for the recv() on the socket + read_timeout = timeout_obj.read_timeout + + if not conn.is_closed: + # In Python 3 socket.py will catch EAGAIN and return None when you + # try and read into the file pointer created by http.client, which + # instead raises a BadStatusLine exception. Instead of catching + # the exception and assuming all BadStatusLine exceptions are read + # timeouts, check for a zero timeout before making the request. + if read_timeout == 0: + raise ReadTimeoutError( + self, url, f"Read timed out. (read timeout={read_timeout})" + ) + conn.timeout = read_timeout + + # Receive the response from the server + try: + response = conn.getresponse() + except (BaseSSLError, OSError) as e: + self._raise_timeout(err=e, url=url, timeout_value=read_timeout) + raise + + # Set properties that are used by the pooling layer. + response.retries = retries + response._connection = response_conn # type: ignore[attr-defined] + response._pool = self # type: ignore[attr-defined] + + log.debug( + '%s://%s:%s "%s %s %s" %s %s', + self.scheme, + self.host, + self.port, + method, + url, + response.version_string, + response.status, + response.length_remaining, + ) + + return response + + def close(self) -> None: + """ + Close all pooled connections and disable the pool. + """ + if self.pool is None: + return + # Disable access to the pool + old_pool, self.pool = self.pool, None + + # Close all the HTTPConnections in the pool. + _close_pool_connections(old_pool) + + def is_same_host(self, url: str) -> bool: + """ + Check if the given ``url`` is a member of the same host as this + connection pool. + """ + if url.startswith("/"): + return True + + # TODO: Add optional support for socket.gethostbyname checking. + scheme, _, host, port, *_ = parse_url(url) + scheme = scheme or "http" + if host is not None: + host = _normalize_host(host, scheme=scheme) + + # Use explicit default port for comparison when none is given + if self.port and not port: + port = port_by_scheme.get(scheme) + elif not self.port and port == port_by_scheme.get(scheme): + port = None + + return (scheme, host, port) == (self.scheme, self.host, self.port) + + def urlopen( # type: ignore[override] + self, + method: str, + url: str, + body: _TYPE_BODY | None = None, + headers: typing.Mapping[str, str] | None = None, + retries: Retry | bool | int | None = None, + redirect: bool = True, + assert_same_host: bool = True, + timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + pool_timeout: int | None = None, + release_conn: bool | None = None, + chunked: bool = False, + body_pos: _TYPE_BODY_POSITION | None = None, + preload_content: bool = True, + decode_content: bool = True, + **response_kw: typing.Any, + ) -> BaseHTTPResponse: + """ + Get a connection from the pool and perform an HTTP request. This is the + lowest level call for making a request, so you'll need to specify all + the raw details. + + .. note:: + + More commonly, it's appropriate to use a convenience method + such as :meth:`request`. + + .. note:: + + `release_conn` will only behave as expected if + `preload_content=False` because we want to make + `preload_content=False` the default behaviour someday soon without + breaking backwards compatibility. + + :param method: + HTTP request method (such as GET, POST, PUT, etc.) + + :param url: + The URL to perform the request on. + + :param body: + Data to send in the request body, either :class:`str`, :class:`bytes`, + an iterable of :class:`str`/:class:`bytes`, or a file-like object. + + :param headers: + Dictionary of custom headers to send, such as User-Agent, + If-None-Match, etc. If None, pool headers are used. If provided, + these headers completely replace any pool-specific headers. + + :param retries: + Configure the number of retries to allow before raising a + :class:`~urllib3.exceptions.MaxRetryError` exception. + + If ``None`` (default) will retry 3 times, see ``Retry.DEFAULT``. Pass a + :class:`~urllib3.util.retry.Retry` object for fine-grained control + over different types of retries. + Pass an integer number to retry connection errors that many times, + but no other types of errors. Pass zero to never retry. + + If ``False``, then retries are disabled and any exception is raised + immediately. Also, instead of raising a MaxRetryError on redirects, + the redirect response will be returned. + + :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int. + + :param redirect: + If True, automatically handle redirects (status codes 301, 302, + 303, 307, 308). Each redirect counts as a retry. Disabling retries + will disable redirect, too. + + :param assert_same_host: + If ``True``, will make sure that the host of the pool requests is + consistent else will raise HostChangedError. When ``False``, you can + use the pool on an HTTP proxy and request foreign hosts. + + :param timeout: + If specified, overrides the default timeout for this one + request. It may be a float (in seconds) or an instance of + :class:`urllib3.util.Timeout`. + + :param pool_timeout: + If set and the pool is set to block=True, then this method will + block for ``pool_timeout`` seconds and raise EmptyPoolError if no + connection is available within the time period. + + :param bool preload_content: + If True, the response's body will be preloaded into memory. + + :param bool decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + + :param release_conn: + If False, then the urlopen call will not release the connection + back into the pool once a response is received (but will release if + you read the entire contents of the response such as when + `preload_content=True`). This is useful if you're not preloading + the response's content immediately. You will need to call + ``r.release_conn()`` on the response ``r`` to return the connection + back into the pool. If None, it takes the value of ``preload_content`` + which defaults to ``True``. + + :param bool chunked: + If True, urllib3 will send the body using chunked transfer + encoding. Otherwise, urllib3 will send the body using the standard + content-length form. Defaults to False. + + :param int body_pos: + Position to seek to in file-like body in the event of a retry or + redirect. Typically this won't need to be set because urllib3 will + auto-populate the value when needed. + """ + # Ensure that the URL we're connecting to is properly encoded + if url.startswith("/"): + # URLs starting with / are inherently schemeless. + url = to_str(_encode_target(url)) + destination_scheme = None + else: + parsed_url = parse_url(url) + destination_scheme = parsed_url.scheme + url = to_str(parsed_url.url) + + if headers is None: + headers = self.headers + + if not isinstance(retries, Retry): + retries = Retry.from_int(retries, redirect=redirect, default=self.retries) + + if release_conn is None: + release_conn = preload_content + + # Check host + if assert_same_host and not self.is_same_host(url): + raise HostChangedError(self, url, retries) + + conn = None + + # Track whether `conn` needs to be released before + # returning/raising/recursing. Update this variable if necessary, and + # leave `release_conn` constant throughout the function. That way, if + # the function recurses, the original value of `release_conn` will be + # passed down into the recursive call, and its value will be respected. + # + # See issue #651 [1] for details. + # + # [1] + release_this_conn = release_conn + + http_tunnel_required = connection_requires_http_tunnel( + self.proxy, self.proxy_config, destination_scheme + ) + + # Merge the proxy headers. Only done when not using HTTP CONNECT. We + # have to copy the headers dict so we can safely change it without those + # changes being reflected in anyone else's copy. + if not http_tunnel_required: + headers = headers.copy() # type: ignore[attr-defined] + headers.update(self.proxy_headers) # type: ignore[union-attr] + + # Must keep the exception bound to a separate variable or else Python 3 + # complains about UnboundLocalError. + err = None + + # Keep track of whether we cleanly exited the except block. This + # ensures we do proper cleanup in finally. + clean_exit = False + + # Rewind body position, if needed. Record current position + # for future rewinds in the event of a redirect/retry. + body_pos = set_file_position(body, body_pos) + + try: + # Request a connection from the queue. + timeout_obj = self._get_timeout(timeout) + conn = self._get_conn(timeout=pool_timeout) + + conn.timeout = timeout_obj.connect_timeout # type: ignore[assignment] + + # Is this a closed/new connection that requires CONNECT tunnelling? + if self.proxy is not None and http_tunnel_required and conn.is_closed: + try: + self._prepare_proxy(conn) + except (BaseSSLError, OSError, SocketTimeout) as e: + self._raise_timeout( + err=e, url=self.proxy.url, timeout_value=conn.timeout + ) + raise + + # If we're going to release the connection in ``finally:``, then + # the response doesn't need to know about the connection. Otherwise + # it will also try to release it and we'll have a double-release + # mess. + response_conn = conn if not release_conn else None + + # Make the request on the HTTPConnection object + response = self._make_request( + conn, + method, + url, + timeout=timeout_obj, + body=body, + headers=headers, + chunked=chunked, + retries=retries, + response_conn=response_conn, + preload_content=preload_content, + decode_content=decode_content, + **response_kw, + ) + + # Everything went great! + clean_exit = True + + except EmptyPoolError: + # Didn't get a connection from the pool, no need to clean up + clean_exit = True + release_this_conn = False + raise + + except ( + TimeoutError, + HTTPException, + OSError, + ProtocolError, + BaseSSLError, + SSLError, + CertificateError, + ProxyError, + ) as e: + # Discard the connection for these exceptions. It will be + # replaced during the next _get_conn() call. + clean_exit = False + new_e: Exception = e + if isinstance(e, (BaseSSLError, CertificateError)): + new_e = SSLError(e) + if isinstance( + new_e, + ( + OSError, + NewConnectionError, + TimeoutError, + SSLError, + HTTPException, + ), + ) and (conn and conn.proxy and not conn.has_connected_to_proxy): + new_e = _wrap_proxy_error(new_e, conn.proxy.scheme) + elif isinstance(new_e, (OSError, HTTPException)): + new_e = ProtocolError("Connection aborted.", new_e) + + retries = retries.increment( + method, url, error=new_e, _pool=self, _stacktrace=sys.exc_info()[2] + ) + retries.sleep() + + # Keep track of the error for the retry warning. + err = e + + finally: + if not clean_exit: + # We hit some kind of exception, handled or otherwise. We need + # to throw the connection away unless explicitly told not to. + # Close the connection, set the variable to None, and make sure + # we put the None back in the pool to avoid leaking it. + if conn: + conn.close() + conn = None + release_this_conn = True + + if release_this_conn: + # Put the connection back to be reused. If the connection is + # expired then it will be None, which will get replaced with a + # fresh connection during _get_conn. + self._put_conn(conn) + + if not conn: + # Try again + log.warning( + "Retrying (%r) after connection broken by '%r': %s", retries, err, url + ) + return self.urlopen( + method, + url, + body, + headers, + retries, + redirect, + assert_same_host, + timeout=timeout, + pool_timeout=pool_timeout, + release_conn=release_conn, + chunked=chunked, + body_pos=body_pos, + preload_content=preload_content, + decode_content=decode_content, + **response_kw, + ) + + # Handle redirect? + redirect_location = redirect and response.get_redirect_location() + if redirect_location: + if response.status == 303: + # Change the method according to RFC 9110, Section 15.4.4. + method = "GET" + # And lose the body not to transfer anything sensitive. + body = None + headers = HTTPHeaderDict(headers)._prepare_for_method_change() + + # Strip headers marked as unsafe to forward to the redirected location. + # Check remove_headers_on_redirect to avoid a potential network call within + # self.is_same_host() which may use socket.gethostbyname() in the future. + if retries.remove_headers_on_redirect and not self.is_same_host( + redirect_location + ): + new_headers = headers.copy() # type: ignore[union-attr] + for header in headers: + if header.lower() in retries.remove_headers_on_redirect: + new_headers.pop(header, None) + headers = new_headers + + try: + retries = retries.increment(method, url, response=response, _pool=self) + except MaxRetryError: + if retries.raise_on_redirect: + response.drain_conn() + raise + return response + + response.drain_conn() + retries.sleep_for_retry(response) + log.debug("Redirecting %s -> %s", url, redirect_location) + return self.urlopen( + method, + redirect_location, + body, + headers, + retries=retries, + redirect=redirect, + assert_same_host=assert_same_host, + timeout=timeout, + pool_timeout=pool_timeout, + release_conn=release_conn, + chunked=chunked, + body_pos=body_pos, + preload_content=preload_content, + decode_content=decode_content, + **response_kw, + ) + + # Check if we should retry the HTTP response. + has_retry_after = bool(response.headers.get("Retry-After")) + if retries.is_retry(method, response.status, has_retry_after): + try: + retries = retries.increment(method, url, response=response, _pool=self) + except MaxRetryError: + if retries.raise_on_status: + response.drain_conn() + raise + return response + + response.drain_conn() + retries.sleep(response) + log.debug("Retry: %s", url) + return self.urlopen( + method, + url, + body, + headers, + retries=retries, + redirect=redirect, + assert_same_host=assert_same_host, + timeout=timeout, + pool_timeout=pool_timeout, + release_conn=release_conn, + chunked=chunked, + body_pos=body_pos, + preload_content=preload_content, + decode_content=decode_content, + **response_kw, + ) + + return response + + +class HTTPSConnectionPool(HTTPConnectionPool): + """ + Same as :class:`.HTTPConnectionPool`, but HTTPS. + + :class:`.HTTPSConnection` uses one of ``assert_fingerprint``, + ``assert_hostname`` and ``host`` in this order to verify connections. + If ``assert_hostname`` is False, no verification is done. + + The ``key_file``, ``cert_file``, ``cert_reqs``, ``ca_certs``, + ``ca_cert_dir``, ``ssl_version``, ``key_password`` are only used if :mod:`ssl` + is available and are fed into :meth:`urllib3.util.ssl_wrap_socket` to upgrade + the connection socket into an SSL socket. + """ + + scheme = "https" + ConnectionCls: type[BaseHTTPSConnection] = HTTPSConnection + + def __init__( + self, + host: str, + port: int | None = None, + timeout: _TYPE_TIMEOUT | None = _DEFAULT_TIMEOUT, + maxsize: int = 1, + block: bool = False, + headers: typing.Mapping[str, str] | None = None, + retries: Retry | bool | int | None = None, + _proxy: Url | None = None, + _proxy_headers: typing.Mapping[str, str] | None = None, + key_file: str | None = None, + cert_file: str | None = None, + cert_reqs: int | str | None = None, + key_password: str | None = None, + ca_certs: str | None = None, + ssl_version: int | str | None = None, + ssl_minimum_version: ssl.TLSVersion | None = None, + ssl_maximum_version: ssl.TLSVersion | None = None, + assert_hostname: str | typing.Literal[False] | None = None, + assert_fingerprint: str | None = None, + ca_cert_dir: str | None = None, + **conn_kw: typing.Any, + ) -> None: + super().__init__( + host, + port, + timeout, + maxsize, + block, + headers, + retries, + _proxy, + _proxy_headers, + **conn_kw, + ) + + self.key_file = key_file + self.cert_file = cert_file + self.cert_reqs = cert_reqs + self.key_password = key_password + self.ca_certs = ca_certs + self.ca_cert_dir = ca_cert_dir + self.ssl_version = ssl_version + self.ssl_minimum_version = ssl_minimum_version + self.ssl_maximum_version = ssl_maximum_version + self.assert_hostname = assert_hostname + self.assert_fingerprint = assert_fingerprint + + def _prepare_proxy(self, conn: HTTPSConnection) -> None: # type: ignore[override] + """Establishes a tunnel connection through HTTP CONNECT.""" + if self.proxy and self.proxy.scheme == "https": + tunnel_scheme = "https" + else: + tunnel_scheme = "http" + + conn.set_tunnel( + scheme=tunnel_scheme, + host=self._tunnel_host, + port=self.port, + headers=self.proxy_headers, + ) + conn.connect() + + def _new_conn(self) -> BaseHTTPSConnection: + """ + Return a fresh :class:`urllib3.connection.HTTPConnection`. + """ + self.num_connections += 1 + log.debug( + "Starting new HTTPS connection (%d): %s:%s", + self.num_connections, + self.host, + self.port or "443", + ) + + if not self.ConnectionCls or self.ConnectionCls is DummyConnection: # type: ignore[comparison-overlap] + raise ImportError( + "Can't connect to HTTPS URL because the SSL module is not available." + ) + + actual_host: str = self.host + actual_port = self.port + if self.proxy is not None and self.proxy.host is not None: + actual_host = self.proxy.host + actual_port = self.proxy.port + + return self.ConnectionCls( + host=actual_host, + port=actual_port, + timeout=self.timeout.connect_timeout, + cert_file=self.cert_file, + key_file=self.key_file, + key_password=self.key_password, + cert_reqs=self.cert_reqs, + ca_certs=self.ca_certs, + ca_cert_dir=self.ca_cert_dir, + assert_hostname=self.assert_hostname, + assert_fingerprint=self.assert_fingerprint, + ssl_version=self.ssl_version, + ssl_minimum_version=self.ssl_minimum_version, + ssl_maximum_version=self.ssl_maximum_version, + **self.conn_kw, + ) + + def _validate_conn(self, conn: BaseHTTPConnection) -> None: + """ + Called right before a request is made, after the socket is created. + """ + super()._validate_conn(conn) + + # Force connect early to allow us to validate the connection. + if conn.is_closed: + conn.connect() + + # TODO revise this, see https://github.com/urllib3/urllib3/issues/2791 + if not conn.is_verified and not conn.proxy_is_verified: + warnings.warn( + ( + f"Unverified HTTPS request is being made to host '{conn.host}'. " + "Adding certificate verification is strongly advised. See: " + "https://urllib3.readthedocs.io/en/latest/advanced-usage.html" + "#tls-warnings" + ), + InsecureRequestWarning, + ) + + +def connection_from_url(url: str, **kw: typing.Any) -> HTTPConnectionPool: + """ + Given a url, return an :class:`.ConnectionPool` instance of its host. + + This is a shortcut for not having to parse out the scheme, host, and port + of the url before creating an :class:`.ConnectionPool` instance. + + :param url: + Absolute URL string that must include the scheme. Port is optional. + + :param \\**kw: + Passes additional parameters to the constructor of the appropriate + :class:`.ConnectionPool`. Useful for specifying things like + timeout, maxsize, headers, etc. + + Example:: + + >>> conn = connection_from_url('http://google.com/') + >>> r = conn.request('GET', '/') + """ + scheme, _, host, port, *_ = parse_url(url) + scheme = scheme or "http" + port = port or port_by_scheme.get(scheme, 80) + if scheme == "https": + return HTTPSConnectionPool(host, port=port, **kw) # type: ignore[arg-type] + else: + return HTTPConnectionPool(host, port=port, **kw) # type: ignore[arg-type] + + +@typing.overload +def _normalize_host(host: None, scheme: str | None) -> None: ... + + +@typing.overload +def _normalize_host(host: str, scheme: str | None) -> str: ... + + +def _normalize_host(host: str | None, scheme: str | None) -> str | None: + """ + Normalize hosts for comparisons and use with sockets. + """ + + host = normalize_host(host, scheme) + + # httplib doesn't like it when we include brackets in IPv6 addresses + # Specifically, if we include brackets but also pass the port then + # httplib crazily doubles up the square brackets on the Host header. + # Instead, we need to make sure we never pass ``None`` as the port. + # However, for backward compatibility reasons we can't actually + # *assert* that. See http://bugs.python.org/issue28539 + if host and host.startswith("[") and host.endswith("]"): + host = host[1:-1] + return host + + +def _url_from_pool( + pool: HTTPConnectionPool | HTTPSConnectionPool, path: str | None = None +) -> str: + """Returns the URL from a given connection pool. This is mainly used for testing and logging.""" + return Url(scheme=pool.scheme, host=pool.host, port=pool.port, path=path).url + + +def _close_pool_connections(pool: queue.LifoQueue[typing.Any]) -> None: + """Drains a queue of connections and closes each one.""" + try: + while True: + conn = pool.get(block=False) + if conn: + conn.close() + except queue.Empty: + pass # Done. diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/contrib/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/contrib/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/contrib/__pycache__/__init__.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/contrib/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5a5acc4a1170f18642931aba25f99fa067ef7c7f Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/contrib/__pycache__/__init__.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/contrib/__pycache__/socks.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/contrib/__pycache__/socks.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a6c4b85632a119aacc7123781b9b51dfec1a8f60 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/contrib/__pycache__/socks.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e5b62b25e932566f7ae7599c1cedec2b8f30d95b --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/__init__.py @@ -0,0 +1,17 @@ +from __future__ import annotations + +import urllib3.connection + +from ...connectionpool import HTTPConnectionPool, HTTPSConnectionPool +from .connection import EmscriptenHTTPConnection, EmscriptenHTTPSConnection + + +def inject_into_urllib3() -> None: + # override connection classes to use emscripten specific classes + # n.b. mypy complains about the overriding of classes below + # if it isn't ignored + HTTPConnectionPool.ConnectionCls = EmscriptenHTTPConnection + HTTPSConnectionPool.ConnectionCls = EmscriptenHTTPSConnection + urllib3.connection.HTTPConnection = EmscriptenHTTPConnection # type: ignore[misc,assignment] + urllib3.connection.HTTPSConnection = EmscriptenHTTPSConnection # type: ignore[misc,assignment] + urllib3.connection.VerifiedHTTPSConnection = EmscriptenHTTPSConnection # type: ignore[assignment] diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/connection.py b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/connection.py new file mode 100644 index 0000000000000000000000000000000000000000..63f79dd3be803db09671c909f79316c3f65d6916 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/connection.py @@ -0,0 +1,260 @@ +from __future__ import annotations + +import os +import typing + +# use http.client.HTTPException for consistency with non-emscripten +from http.client import HTTPException as HTTPException # noqa: F401 +from http.client import ResponseNotReady + +from ..._base_connection import _TYPE_BODY +from ...connection import HTTPConnection, ProxyConfig, port_by_scheme +from ...exceptions import TimeoutError +from ...response import BaseHTTPResponse +from ...util.connection import _TYPE_SOCKET_OPTIONS +from ...util.timeout import _DEFAULT_TIMEOUT, _TYPE_TIMEOUT +from ...util.url import Url +from .fetch import _RequestError, _TimeoutError, send_request, send_streaming_request +from .request import EmscriptenRequest +from .response import EmscriptenHttpResponseWrapper, EmscriptenResponse + +if typing.TYPE_CHECKING: + from ..._base_connection import BaseHTTPConnection, BaseHTTPSConnection + + +class EmscriptenHTTPConnection: + default_port: typing.ClassVar[int] = port_by_scheme["http"] + default_socket_options: typing.ClassVar[_TYPE_SOCKET_OPTIONS] + + timeout: None | (float) + + host: str + port: int + blocksize: int + source_address: tuple[str, int] | None + socket_options: _TYPE_SOCKET_OPTIONS | None + + proxy: Url | None + proxy_config: ProxyConfig | None + + is_verified: bool = False + proxy_is_verified: bool | None = None + + response_class: type[BaseHTTPResponse] = EmscriptenHttpResponseWrapper + _response: EmscriptenResponse | None + + def __init__( + self, + host: str, + port: int = 0, + *, + timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + source_address: tuple[str, int] | None = None, + blocksize: int = 8192, + socket_options: _TYPE_SOCKET_OPTIONS | None = None, + proxy: Url | None = None, + proxy_config: ProxyConfig | None = None, + ) -> None: + self.host = host + self.port = port + self.timeout = timeout if isinstance(timeout, float) else 0.0 + self.scheme = "http" + self._closed = True + self._response = None + # ignore these things because we don't + # have control over that stuff + self.proxy = None + self.proxy_config = None + self.blocksize = blocksize + self.source_address = None + self.socket_options = None + self.is_verified = False + + def set_tunnel( + self, + host: str, + port: int | None = 0, + headers: typing.Mapping[str, str] | None = None, + scheme: str = "http", + ) -> None: + pass + + def connect(self) -> None: + pass + + def request( + self, + method: str, + url: str, + body: _TYPE_BODY | None = None, + headers: typing.Mapping[str, str] | None = None, + # We know *at least* botocore is depending on the order of the + # first 3 parameters so to be safe we only mark the later ones + # as keyword-only to ensure we have space to extend. + *, + chunked: bool = False, + preload_content: bool = True, + decode_content: bool = True, + enforce_content_length: bool = True, + ) -> None: + self._closed = False + if url.startswith("/"): + if self.port is not None: + port = f":{self.port}" + else: + port = "" + # no scheme / host / port included, make a full url + url = f"{self.scheme}://{self.host}{port}{url}" + request = EmscriptenRequest( + url=url, + method=method, + timeout=self.timeout if self.timeout else 0, + decode_content=decode_content, + ) + request.set_body(body) + if headers: + for k, v in headers.items(): + request.set_header(k, v) + self._response = None + try: + if not preload_content: + self._response = send_streaming_request(request) + if self._response is None: + self._response = send_request(request) + except _TimeoutError as e: + raise TimeoutError(e.message) from e + except _RequestError as e: + raise HTTPException(e.message) from e + + def getresponse(self) -> BaseHTTPResponse: + if self._response is not None: + return EmscriptenHttpResponseWrapper( + internal_response=self._response, + url=self._response.request.url, + connection=self, + ) + else: + raise ResponseNotReady() + + def close(self) -> None: + self._closed = True + self._response = None + + @property + def is_closed(self) -> bool: + """Whether the connection either is brand new or has been previously closed. + If this property is True then both ``is_connected`` and ``has_connected_to_proxy`` + properties must be False. + """ + return self._closed + + @property + def is_connected(self) -> bool: + """Whether the connection is actively connected to any origin (proxy or target)""" + return True + + @property + def has_connected_to_proxy(self) -> bool: + """Whether the connection has successfully connected to its proxy. + This returns False if no proxy is in use. Used to determine whether + errors are coming from the proxy layer or from tunnelling to the target origin. + """ + return False + + +class EmscriptenHTTPSConnection(EmscriptenHTTPConnection): + default_port = port_by_scheme["https"] + # all this is basically ignored, as browser handles https + cert_reqs: int | str | None = None + ca_certs: str | None = None + ca_cert_dir: str | None = None + ca_cert_data: None | str | bytes = None + cert_file: str | None + key_file: str | None + key_password: str | None + ssl_context: typing.Any | None + ssl_version: int | str | None = None + ssl_minimum_version: int | None = None + ssl_maximum_version: int | None = None + assert_hostname: None | str | typing.Literal[False] + assert_fingerprint: str | None = None + + def __init__( + self, + host: str, + port: int = 0, + *, + timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + source_address: tuple[str, int] | None = None, + blocksize: int = 16384, + socket_options: ( + None | _TYPE_SOCKET_OPTIONS + ) = HTTPConnection.default_socket_options, + proxy: Url | None = None, + proxy_config: ProxyConfig | None = None, + cert_reqs: int | str | None = None, + assert_hostname: None | str | typing.Literal[False] = None, + assert_fingerprint: str | None = None, + server_hostname: str | None = None, + ssl_context: typing.Any | None = None, + ca_certs: str | None = None, + ca_cert_dir: str | None = None, + ca_cert_data: None | str | bytes = None, + ssl_minimum_version: int | None = None, + ssl_maximum_version: int | None = None, + ssl_version: int | str | None = None, # Deprecated + cert_file: str | None = None, + key_file: str | None = None, + key_password: str | None = None, + ) -> None: + super().__init__( + host, + port=port, + timeout=timeout, + source_address=source_address, + blocksize=blocksize, + socket_options=socket_options, + proxy=proxy, + proxy_config=proxy_config, + ) + self.scheme = "https" + + self.key_file = key_file + self.cert_file = cert_file + self.key_password = key_password + self.ssl_context = ssl_context + self.server_hostname = server_hostname + self.assert_hostname = assert_hostname + self.assert_fingerprint = assert_fingerprint + self.ssl_version = ssl_version + self.ssl_minimum_version = ssl_minimum_version + self.ssl_maximum_version = ssl_maximum_version + self.ca_certs = ca_certs and os.path.expanduser(ca_certs) + self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir) + self.ca_cert_data = ca_cert_data + + self.cert_reqs = None + + # The browser will automatically verify all requests. + # We have no control over that setting. + self.is_verified = True + + def set_cert( + self, + key_file: str | None = None, + cert_file: str | None = None, + cert_reqs: int | str | None = None, + key_password: str | None = None, + ca_certs: str | None = None, + assert_hostname: None | str | typing.Literal[False] = None, + assert_fingerprint: str | None = None, + ca_cert_dir: str | None = None, + ca_cert_data: None | str | bytes = None, + ) -> None: + pass + + +# verify that this class implements BaseHTTP(s) connection correctly +if typing.TYPE_CHECKING: + _supports_http_protocol: BaseHTTPConnection = EmscriptenHTTPConnection("", 0) + _supports_https_protocol: BaseHTTPSConnection = EmscriptenHTTPSConnection("", 0) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/emscripten_fetch_worker.js b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/emscripten_fetch_worker.js new file mode 100644 index 0000000000000000000000000000000000000000..faf141e1fa4113a0c14480d1681ddecb9678ced4 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/emscripten_fetch_worker.js @@ -0,0 +1,110 @@ +let Status = { + SUCCESS_HEADER: -1, + SUCCESS_EOF: -2, + ERROR_TIMEOUT: -3, + ERROR_EXCEPTION: -4, +}; + +let connections = new Map(); +let nextConnectionID = 1; +const encoder = new TextEncoder(); + +self.addEventListener("message", async function (event) { + if (event.data.close) { + let connectionID = event.data.close; + connections.delete(connectionID); + return; + } else if (event.data.getMore) { + let connectionID = event.data.getMore; + let { curOffset, value, reader, intBuffer, byteBuffer } = + connections.get(connectionID); + // if we still have some in buffer, then just send it back straight away + if (!value || curOffset >= value.length) { + // read another buffer if required + try { + let readResponse = await reader.read(); + + if (readResponse.done) { + // read everything - clear connection and return + connections.delete(connectionID); + Atomics.store(intBuffer, 0, Status.SUCCESS_EOF); + Atomics.notify(intBuffer, 0); + // finished reading successfully + // return from event handler + return; + } + curOffset = 0; + connections.get(connectionID).value = readResponse.value; + value = readResponse.value; + } catch (error) { + console.log("Request exception:", error); + let errorBytes = encoder.encode(error.message); + let written = errorBytes.length; + byteBuffer.set(errorBytes); + intBuffer[1] = written; + Atomics.store(intBuffer, 0, Status.ERROR_EXCEPTION); + Atomics.notify(intBuffer, 0); + } + } + + // send as much buffer as we can + let curLen = value.length - curOffset; + if (curLen > byteBuffer.length) { + curLen = byteBuffer.length; + } + byteBuffer.set(value.subarray(curOffset, curOffset + curLen), 0); + + Atomics.store(intBuffer, 0, curLen); // store current length in bytes + Atomics.notify(intBuffer, 0); + curOffset += curLen; + connections.get(connectionID).curOffset = curOffset; + + return; + } else { + // start fetch + let connectionID = nextConnectionID; + nextConnectionID += 1; + const intBuffer = new Int32Array(event.data.buffer); + const byteBuffer = new Uint8Array(event.data.buffer, 8); + try { + const response = await fetch(event.data.url, event.data.fetchParams); + // return the headers first via textencoder + var headers = []; + for (const pair of response.headers.entries()) { + headers.push([pair[0], pair[1]]); + } + let headerObj = { + headers: headers, + status: response.status, + connectionID, + }; + const headerText = JSON.stringify(headerObj); + let headerBytes = encoder.encode(headerText); + let written = headerBytes.length; + byteBuffer.set(headerBytes); + intBuffer[1] = written; + // make a connection + connections.set(connectionID, { + reader: response.body.getReader(), + intBuffer: intBuffer, + byteBuffer: byteBuffer, + value: undefined, + curOffset: 0, + }); + // set header ready + Atomics.store(intBuffer, 0, Status.SUCCESS_HEADER); + Atomics.notify(intBuffer, 0); + // all fetching after this goes through a new postmessage call with getMore + // this allows for parallel requests + } catch (error) { + console.log("Request exception:", error); + let errorBytes = encoder.encode(error.message); + let written = errorBytes.length; + byteBuffer.set(errorBytes); + intBuffer[1] = written; + Atomics.store(intBuffer, 0, Status.ERROR_EXCEPTION); + Atomics.notify(intBuffer, 0); + } + } +}); +self.postMessage({ inited: true }); diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/fetch.py b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/fetch.py new file mode 100644 index 0000000000000000000000000000000000000000..612cfddc4c28d2f0edf47522278fa6d9b7906623 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/fetch.py @@ -0,0 +1,726 @@ +""" +Support for streaming http requests in emscripten. + +A few caveats - + +If your browser (or Node.js) has WebAssembly JavaScript Promise Integration enabled +https://github.com/WebAssembly/js-promise-integration/blob/main/proposals/js-promise-integration/Overview.md +*and* you launch pyodide using `pyodide.runPythonAsync`, this will fetch data using the +JavaScript asynchronous fetch api (wrapped via `pyodide.ffi.call_sync`). In this case +timeouts and streaming should just work. + +Otherwise, it uses a combination of XMLHttpRequest and a web-worker for streaming. + +This approach has several caveats: + +Firstly, you can't do streaming http in the main UI thread, because atomics.wait isn't allowed. +Streaming only works if you're running pyodide in a web worker. + +Secondly, this uses an extra web worker and SharedArrayBuffer to do the asynchronous fetch +operation, so it requires that you have crossOriginIsolation enabled, by serving over https +(or from localhost) with the two headers below set: + + Cross-Origin-Opener-Policy: same-origin + Cross-Origin-Embedder-Policy: require-corp + +You can tell if cross origin isolation is successfully enabled by looking at the global crossOriginIsolated variable in +JavaScript console. If it isn't, streaming requests will fallback to XMLHttpRequest, i.e. getting the whole +request into a buffer and then returning it. it shows a warning in the JavaScript console in this case. + +Finally, the webworker which does the streaming fetch is created on initial import, but will only be started once +control is returned to javascript. Call `await wait_for_streaming_ready()` to wait for streaming fetch. + +NB: in this code, there are a lot of JavaScript objects. They are named js_* +to make it clear what type of object they are. +""" + +from __future__ import annotations + +import io +import json +from email.parser import Parser +from importlib.resources import files +from typing import TYPE_CHECKING, Any + +import js # type: ignore[import-not-found] +from pyodide.ffi import ( # type: ignore[import-not-found] + JsArray, + JsException, + JsProxy, + to_js, +) + +if TYPE_CHECKING: + from typing_extensions import Buffer + +from .request import EmscriptenRequest +from .response import EmscriptenResponse + +""" +There are some headers that trigger unintended CORS preflight requests. +See also https://github.com/koenvo/pyodide-http/issues/22 +""" +HEADERS_TO_IGNORE = ("user-agent",) + +SUCCESS_HEADER = -1 +SUCCESS_EOF = -2 +ERROR_TIMEOUT = -3 +ERROR_EXCEPTION = -4 + + +class _RequestError(Exception): + def __init__( + self, + message: str | None = None, + *, + request: EmscriptenRequest | None = None, + response: EmscriptenResponse | None = None, + ): + self.request = request + self.response = response + self.message = message + super().__init__(self.message) + + +class _StreamingError(_RequestError): + pass + + +class _TimeoutError(_RequestError): + pass + + +def _obj_from_dict(dict_val: dict[str, Any]) -> JsProxy: + return to_js(dict_val, dict_converter=js.Object.fromEntries) + + +class _ReadStream(io.RawIOBase): + def __init__( + self, + int_buffer: JsArray, + byte_buffer: JsArray, + timeout: float, + worker: JsProxy, + connection_id: int, + request: EmscriptenRequest, + ): + self.int_buffer = int_buffer + self.byte_buffer = byte_buffer + self.read_pos = 0 + self.read_len = 0 + self.connection_id = connection_id + self.worker = worker + self.timeout = int(1000 * timeout) if timeout > 0 else None + self.is_live = True + self._is_closed = False + self.request: EmscriptenRequest | None = request + + def __del__(self) -> None: + self.close() + + # this is compatible with _base_connection + def is_closed(self) -> bool: + return self._is_closed + + # for compatibility with RawIOBase + @property + def closed(self) -> bool: + return self.is_closed() + + def close(self) -> None: + if self.is_closed(): + return + self.read_len = 0 + self.read_pos = 0 + self.int_buffer = None + self.byte_buffer = None + self._is_closed = True + self.request = None + if self.is_live: + self.worker.postMessage(_obj_from_dict({"close": self.connection_id})) + self.is_live = False + super().close() + + def readable(self) -> bool: + return True + + def writable(self) -> bool: + return False + + def seekable(self) -> bool: + return False + + def readinto(self, byte_obj: Buffer) -> int: + if not self.int_buffer: + raise _StreamingError( + "No buffer for stream in _ReadStream.readinto", + request=self.request, + response=None, + ) + if self.read_len == 0: + # wait for the worker to send something + js.Atomics.store(self.int_buffer, 0, ERROR_TIMEOUT) + self.worker.postMessage(_obj_from_dict({"getMore": self.connection_id})) + if ( + js.Atomics.wait(self.int_buffer, 0, ERROR_TIMEOUT, self.timeout) + == "timed-out" + ): + raise _TimeoutError + data_len = self.int_buffer[0] + if data_len > 0: + self.read_len = data_len + self.read_pos = 0 + elif data_len == ERROR_EXCEPTION: + string_len = self.int_buffer[1] + # decode the error string + js_decoder = js.TextDecoder.new() + json_str = js_decoder.decode(self.byte_buffer.slice(0, string_len)) + raise _StreamingError( + f"Exception thrown in fetch: {json_str}", + request=self.request, + response=None, + ) + else: + # EOF, free the buffers and return zero + # and free the request + self.is_live = False + self.close() + return 0 + # copy from int32array to python bytes + ret_length = min(self.read_len, len(memoryview(byte_obj))) + subarray = self.byte_buffer.subarray( + self.read_pos, self.read_pos + ret_length + ).to_py() + memoryview(byte_obj)[0:ret_length] = subarray + self.read_len -= ret_length + self.read_pos += ret_length + return ret_length + + +class _StreamingFetcher: + def __init__(self) -> None: + # make web-worker and data buffer on startup + self.streaming_ready = False + streaming_worker_code = ( + files(__package__) + .joinpath("emscripten_fetch_worker.js") + .read_text(encoding="utf-8") + ) + js_data_blob = js.Blob.new( + to_js([streaming_worker_code], create_pyproxies=False), + _obj_from_dict({"type": "application/javascript"}), + ) + + def promise_resolver(js_resolve_fn: JsProxy, js_reject_fn: JsProxy) -> None: + def onMsg(e: JsProxy) -> None: + self.streaming_ready = True + js_resolve_fn(e) + + def onErr(e: JsProxy) -> None: + js_reject_fn(e) # Defensive: never happens in ci + + self.js_worker.onmessage = onMsg + self.js_worker.onerror = onErr + + js_data_url = js.URL.createObjectURL(js_data_blob) + self.js_worker = js.globalThis.Worker.new(js_data_url) + self.js_worker_ready_promise = js.globalThis.Promise.new(promise_resolver) + + def send(self, request: EmscriptenRequest) -> EmscriptenResponse: + headers = { + k: v for k, v in request.headers.items() if k not in HEADERS_TO_IGNORE + } + + body = request.body + fetch_data = {"headers": headers, "body": to_js(body), "method": request.method} + # start the request off in the worker + timeout = int(1000 * request.timeout) if request.timeout > 0 else None + js_shared_buffer = js.SharedArrayBuffer.new(1048576) + js_int_buffer = js.Int32Array.new(js_shared_buffer) + js_byte_buffer = js.Uint8Array.new(js_shared_buffer, 8) + + js.Atomics.store(js_int_buffer, 0, ERROR_TIMEOUT) + js.Atomics.notify(js_int_buffer, 0) + js_absolute_url = js.URL.new(request.url, js.location).href + self.js_worker.postMessage( + _obj_from_dict( + { + "buffer": js_shared_buffer, + "url": js_absolute_url, + "fetchParams": fetch_data, + } + ) + ) + # wait for the worker to send something + js.Atomics.wait(js_int_buffer, 0, ERROR_TIMEOUT, timeout) + if js_int_buffer[0] == ERROR_TIMEOUT: + raise _TimeoutError( + "Timeout connecting to streaming request", + request=request, + response=None, + ) + elif js_int_buffer[0] == SUCCESS_HEADER: + # got response + # header length is in second int of intBuffer + string_len = js_int_buffer[1] + # decode the rest to a JSON string + js_decoder = js.TextDecoder.new() + # this does a copy (the slice) because decode can't work on shared array + # for some silly reason + json_str = js_decoder.decode(js_byte_buffer.slice(0, string_len)) + # get it as an object + response_obj = json.loads(json_str) + return EmscriptenResponse( + request=request, + status_code=response_obj["status"], + headers=response_obj["headers"], + body=_ReadStream( + js_int_buffer, + js_byte_buffer, + request.timeout, + self.js_worker, + response_obj["connectionID"], + request, + ), + ) + elif js_int_buffer[0] == ERROR_EXCEPTION: + string_len = js_int_buffer[1] + # decode the error string + js_decoder = js.TextDecoder.new() + json_str = js_decoder.decode(js_byte_buffer.slice(0, string_len)) + raise _StreamingError( + f"Exception thrown in fetch: {json_str}", request=request, response=None + ) + else: + raise _StreamingError( + f"Unknown status from worker in fetch: {js_int_buffer[0]}", + request=request, + response=None, + ) + + +class _JSPIReadStream(io.RawIOBase): + """ + A read stream that uses pyodide.ffi.run_sync to read from a JavaScript fetch + response. This requires support for WebAssembly JavaScript Promise Integration + in the containing browser, and for pyodide to be launched via runPythonAsync. + + :param js_read_stream: + The JavaScript stream reader + + :param timeout: + Timeout in seconds + + :param request: + The request we're handling + + :param response: + The response this stream relates to + + :param js_abort_controller: + A JavaScript AbortController object, used for timeouts + """ + + def __init__( + self, + js_read_stream: Any, + timeout: float, + request: EmscriptenRequest, + response: EmscriptenResponse, + js_abort_controller: Any, # JavaScript AbortController for timeouts + ): + self.js_read_stream = js_read_stream + self.timeout = timeout + self._is_closed = False + self._is_done = False + self.request: EmscriptenRequest | None = request + self.response: EmscriptenResponse | None = response + self.current_buffer = None + self.current_buffer_pos = 0 + self.js_abort_controller = js_abort_controller + + def __del__(self) -> None: + self.close() + + # this is compatible with _base_connection + def is_closed(self) -> bool: + return self._is_closed + + # for compatibility with RawIOBase + @property + def closed(self) -> bool: + return self.is_closed() + + def close(self) -> None: + if self.is_closed(): + return + self.read_len = 0 + self.read_pos = 0 + self.js_read_stream.cancel() + self.js_read_stream = None + self._is_closed = True + self._is_done = True + self.request = None + self.response = None + super().close() + + def readable(self) -> bool: + return True + + def writable(self) -> bool: + return False + + def seekable(self) -> bool: + return False + + def _get_next_buffer(self) -> bool: + result_js = _run_sync_with_timeout( + self.js_read_stream.read(), + self.timeout, + self.js_abort_controller, + request=self.request, + response=self.response, + ) + if result_js.done: + self._is_done = True + return False + else: + self.current_buffer = result_js.value.to_py() + self.current_buffer_pos = 0 + return True + + def readinto(self, byte_obj: Buffer) -> int: + if self.current_buffer is None: + if not self._get_next_buffer() or self.current_buffer is None: + self.close() + return 0 + ret_length = min( + len(byte_obj), len(self.current_buffer) - self.current_buffer_pos + ) + byte_obj[0:ret_length] = self.current_buffer[ + self.current_buffer_pos : self.current_buffer_pos + ret_length + ] + self.current_buffer_pos += ret_length + if self.current_buffer_pos == len(self.current_buffer): + self.current_buffer = None + return ret_length + + +# check if we are in a worker or not +def is_in_browser_main_thread() -> bool: + return hasattr(js, "window") and hasattr(js, "self") and js.self == js.window + + +def is_cross_origin_isolated() -> bool: + return hasattr(js, "crossOriginIsolated") and js.crossOriginIsolated + + +def is_in_node() -> bool: + return ( + hasattr(js, "process") + and hasattr(js.process, "release") + and hasattr(js.process.release, "name") + and js.process.release.name == "node" + ) + + +def is_worker_available() -> bool: + return hasattr(js, "Worker") and hasattr(js, "Blob") + + +_fetcher: _StreamingFetcher | None = None + +if is_worker_available() and ( + (is_cross_origin_isolated() and not is_in_browser_main_thread()) + and (not is_in_node()) +): + _fetcher = _StreamingFetcher() +else: + _fetcher = None + + +NODE_JSPI_ERROR = ( + "urllib3 only works in Node.js with pyodide.runPythonAsync" + " and requires the flag --experimental-wasm-stack-switching in " + " versions of node <24." +) + + +def send_streaming_request(request: EmscriptenRequest) -> EmscriptenResponse | None: + if has_jspi(): + return send_jspi_request(request, True) + elif is_in_node(): + raise _RequestError( + message=NODE_JSPI_ERROR, + request=request, + response=None, + ) + + if _fetcher and streaming_ready(): + return _fetcher.send(request) + else: + _show_streaming_warning() + return None + + +_SHOWN_TIMEOUT_WARNING = False + + +def _show_timeout_warning() -> None: + global _SHOWN_TIMEOUT_WARNING + if not _SHOWN_TIMEOUT_WARNING: + _SHOWN_TIMEOUT_WARNING = True + message = "Warning: Timeout is not available on main browser thread" + js.console.warn(message) + + +_SHOWN_STREAMING_WARNING = False + + +def _show_streaming_warning() -> None: + global _SHOWN_STREAMING_WARNING + if not _SHOWN_STREAMING_WARNING: + _SHOWN_STREAMING_WARNING = True + message = "Can't stream HTTP requests because: \n" + if not is_cross_origin_isolated(): + message += " Page is not cross-origin isolated\n" + if is_in_browser_main_thread(): + message += " Python is running in main browser thread\n" + if not is_worker_available(): + message += " Worker or Blob classes are not available in this environment." # Defensive: this is always False in browsers that we test in + if streaming_ready() is False: + message += """ Streaming fetch worker isn't ready. If you want to be sure that streaming fetch +is working, you need to call: 'await urllib3.contrib.emscripten.fetch.wait_for_streaming_ready()`""" + from js import console + + console.warn(message) + + +def send_request(request: EmscriptenRequest) -> EmscriptenResponse: + if has_jspi(): + return send_jspi_request(request, False) + elif is_in_node(): + raise _RequestError( + message=NODE_JSPI_ERROR, + request=request, + response=None, + ) + try: + js_xhr = js.XMLHttpRequest.new() + + if not is_in_browser_main_thread(): + js_xhr.responseType = "arraybuffer" + if request.timeout: + js_xhr.timeout = int(request.timeout * 1000) + else: + js_xhr.overrideMimeType("text/plain; charset=ISO-8859-15") + if request.timeout: + # timeout isn't available on the main thread - show a warning in console + # if it is set + _show_timeout_warning() + + js_xhr.open(request.method, request.url, False) + for name, value in request.headers.items(): + if name.lower() not in HEADERS_TO_IGNORE: + js_xhr.setRequestHeader(name, value) + + js_xhr.send(to_js(request.body)) + + headers = dict(Parser().parsestr(js_xhr.getAllResponseHeaders())) + + if not is_in_browser_main_thread(): + body = js_xhr.response.to_py().tobytes() + else: + body = js_xhr.response.encode("ISO-8859-15") + return EmscriptenResponse( + status_code=js_xhr.status, headers=headers, body=body, request=request + ) + except JsException as err: + if err.name == "TimeoutError": + raise _TimeoutError(err.message, request=request) + elif err.name == "NetworkError": + raise _RequestError(err.message, request=request) + else: + # general http error + raise _RequestError(err.message, request=request) + + +def send_jspi_request( + request: EmscriptenRequest, streaming: bool +) -> EmscriptenResponse: + """ + Send a request using WebAssembly JavaScript Promise Integration + to wrap the asynchronous JavaScript fetch api (experimental). + + :param request: + Request to send + + :param streaming: + Whether to stream the response + + :return: The response object + :rtype: EmscriptenResponse + """ + timeout = request.timeout + js_abort_controller = js.AbortController.new() + headers = {k: v for k, v in request.headers.items() if k not in HEADERS_TO_IGNORE} + req_body = request.body + fetch_data = { + "headers": headers, + "body": to_js(req_body), + "method": request.method, + "signal": js_abort_controller.signal, + } + # Node.js returns the whole response (unlike opaqueredirect in browsers), + # so urllib3 can set `redirect: manual` to control redirects itself. + # https://stackoverflow.com/a/78524615 + if _is_node_js(): + fetch_data["redirect"] = "manual" + # Call JavaScript fetch (async api, returns a promise) + fetcher_promise_js = js.fetch(request.url, _obj_from_dict(fetch_data)) + # Now suspend WebAssembly until we resolve that promise + # or time out. + response_js = _run_sync_with_timeout( + fetcher_promise_js, + timeout, + js_abort_controller, + request=request, + response=None, + ) + headers = {} + header_iter = response_js.headers.entries() + while True: + iter_value_js = header_iter.next() + if getattr(iter_value_js, "done", False): + break + else: + headers[str(iter_value_js.value[0])] = str(iter_value_js.value[1]) + status_code = response_js.status + body: bytes | io.RawIOBase = b"" + + response = EmscriptenResponse( + status_code=status_code, headers=headers, body=b"", request=request + ) + if streaming: + # get via inputstream + if response_js.body is not None: + # get a reader from the fetch response + body_stream_js = response_js.body.getReader() + body = _JSPIReadStream( + body_stream_js, timeout, request, response, js_abort_controller + ) + else: + # get directly via arraybuffer + # n.b. this is another async JavaScript call. + body = _run_sync_with_timeout( + response_js.arrayBuffer(), + timeout, + js_abort_controller, + request=request, + response=response, + ).to_py() + response.body = body + return response + + +def _run_sync_with_timeout( + promise: Any, + timeout: float, + js_abort_controller: Any, + request: EmscriptenRequest | None, + response: EmscriptenResponse | None, +) -> Any: + """ + Await a JavaScript promise synchronously with a timeout which is implemented + via the AbortController + + :param promise: + Javascript promise to await + + :param timeout: + Timeout in seconds + + :param js_abort_controller: + A JavaScript AbortController object, used on timeout + + :param request: + The request being handled + + :param response: + The response being handled (if it exists yet) + + :raises _TimeoutError: If the request times out + :raises _RequestError: If the request raises a JavaScript exception + + :return: The result of awaiting the promise. + """ + timer_id = None + if timeout > 0: + timer_id = js.setTimeout( + js_abort_controller.abort.bind(js_abort_controller), int(timeout * 1000) + ) + try: + from pyodide.ffi import run_sync + + # run_sync here uses WebAssembly JavaScript Promise Integration to + # suspend python until the JavaScript promise resolves. + return run_sync(promise) + except JsException as err: + if err.name == "AbortError": + raise _TimeoutError( + message="Request timed out", request=request, response=response + ) + else: + raise _RequestError(message=err.message, request=request, response=response) + finally: + if timer_id is not None: + js.clearTimeout(timer_id) + + +def has_jspi() -> bool: + """ + Return true if jspi can be used. + + This requires both browser support and also WebAssembly + to be in the correct state - i.e. that the javascript + call into python was async not sync. + + :return: True if jspi can be used. + :rtype: bool + """ + try: + from pyodide.ffi import can_run_sync, run_sync # noqa: F401 + + return bool(can_run_sync()) + except ImportError: + return False + + +def _is_node_js() -> bool: + """ + Check if we are in Node.js. + + :return: True if we are in Node.js. + :rtype: bool + """ + return ( + hasattr(js, "process") + and hasattr(js.process, "release") + # According to the Node.js documentation, the release name is always "node". + and js.process.release.name == "node" + ) + + +def streaming_ready() -> bool | None: + if _fetcher: + return _fetcher.streaming_ready + else: + return None # no fetcher, return None to signify that + + +async def wait_for_streaming_ready() -> bool: + if _fetcher: + await _fetcher.js_worker_ready_promise + return True + else: + return False diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/request.py b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/request.py new file mode 100644 index 0000000000000000000000000000000000000000..e692e692bd0d38f6a0677992a6993fc68050dff3 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/request.py @@ -0,0 +1,22 @@ +from __future__ import annotations + +from dataclasses import dataclass, field + +from ..._base_connection import _TYPE_BODY + + +@dataclass +class EmscriptenRequest: + method: str + url: str + params: dict[str, str] | None = None + body: _TYPE_BODY | None = None + headers: dict[str, str] = field(default_factory=dict) + timeout: float = 0 + decode_content: bool = True + + def set_header(self, name: str, value: str) -> None: + self.headers[name.capitalize()] = value + + def set_body(self, body: _TYPE_BODY | None) -> None: + self.body = body diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/response.py b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/response.py new file mode 100644 index 0000000000000000000000000000000000000000..ec1e1dbe83722c5fb16290a852859252ae616826 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/contrib/emscripten/response.py @@ -0,0 +1,281 @@ +from __future__ import annotations + +import json as _json +import logging +import typing +from contextlib import contextmanager +from dataclasses import dataclass +from http.client import HTTPException as HTTPException +from io import BytesIO, IOBase + +from ...exceptions import InvalidHeader, TimeoutError +from ...response import BaseHTTPResponse +from ...util.retry import Retry +from .request import EmscriptenRequest + +if typing.TYPE_CHECKING: + from ..._base_connection import BaseHTTPConnection, BaseHTTPSConnection + +log = logging.getLogger(__name__) + + +@dataclass +class EmscriptenResponse: + status_code: int + headers: dict[str, str] + body: IOBase | bytes + request: EmscriptenRequest + + +class EmscriptenHttpResponseWrapper(BaseHTTPResponse): + def __init__( + self, + internal_response: EmscriptenResponse, + url: str | None = None, + connection: BaseHTTPConnection | BaseHTTPSConnection | None = None, + ): + self._pool = None # set by pool class + self._body = None + self._uncached_read_occurred = False + self._response = internal_response + self._url = url + self._connection = connection + self._closed = False + super().__init__( + headers=internal_response.headers, + status=internal_response.status_code, + request_url=url, + version=0, + version_string="HTTP/?", + reason="", + decode_content=True, + ) + self.length_remaining = self._init_length(self._response.request.method) + self.length_is_certain = False + + @property + def url(self) -> str | None: + return self._url + + @url.setter + def url(self, url: str | None) -> None: + self._url = url + + @property + def connection(self) -> BaseHTTPConnection | BaseHTTPSConnection | None: + return self._connection + + @property + def retries(self) -> Retry | None: + return self._retries + + @retries.setter + def retries(self, retries: Retry | None) -> None: + # Override the request_url if retries has a redirect location. + self._retries = retries + + def stream( + self, amt: int | None = 2**16, decode_content: bool | None = None + ) -> typing.Generator[bytes]: + """ + A generator wrapper for the read() method. A call will block until + ``amt`` bytes have been read from the connection or until the + connection is closed. + + :param amt: + How much of the content to read. The generator will return up to + much data per iteration, but may return less. This is particularly + likely when using compressed data. However, the empty string will + never be returned. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + """ + while True: + data = self.read(amt=amt, decode_content=decode_content) + + if data: + yield data + else: + break + + def _init_length(self, request_method: str | None) -> int | None: + length: int | None + content_length: str | None = self.headers.get("content-length") + + if content_length is not None: + try: + # RFC 7230 section 3.3.2 specifies multiple content lengths can + # be sent in a single Content-Length header + # (e.g. Content-Length: 42, 42). This line ensures the values + # are all valid ints and that as long as the `set` length is 1, + # all values are the same. Otherwise, the header is invalid. + lengths = {int(val) for val in content_length.split(",")} + if len(lengths) > 1: + raise InvalidHeader( + "Content-Length contained multiple " + "unmatching values (%s)" % content_length + ) + length = lengths.pop() + except ValueError: + length = None + else: + if length < 0: + length = None + + else: # if content_length is None + length = None + + # Check for responses that shouldn't include a body + if ( + self.status in (204, 304) + or 100 <= self.status < 200 + or request_method == "HEAD" + ): + length = 0 + + return length + + def read( + self, + amt: int | None = None, + decode_content: bool | None = None, # ignored because browser decodes always + cache_content: bool = False, + ) -> bytes: + if ( + self._closed + or self._response is None + or (isinstance(self._response.body, IOBase) and self._response.body.closed) + ): + return b"" + + with self._error_catcher(): + # body has been preloaded as a string by XmlHttpRequest + if not isinstance(self._response.body, IOBase): + self.length_remaining = len(self._response.body) + self.length_is_certain = True + # wrap body in IOStream + self._response.body = BytesIO(self._response.body) + if amt is not None and amt >= 0: + # don't cache partial content + cache_content = False + data = self._response.body.read(amt) + self._uncached_read_occurred = True + else: # read all we can (and cache it) + data = self._response.body.read() + if cache_content and not self._uncached_read_occurred: + self._body = data + else: + self._uncached_read_occurred = True + if self.length_remaining is not None: + self.length_remaining = max(self.length_remaining - len(data), 0) + if len(data) == 0 or ( + self.length_is_certain and self.length_remaining == 0 + ): + # definitely finished reading, close response stream + self._response.body.close() + return typing.cast(bytes, data) + + def read_chunked( + self, + amt: int | None = None, + decode_content: bool | None = None, + ) -> typing.Generator[bytes]: + # chunked is handled by browser + while True: + bytes = self.read(amt, decode_content) + if not bytes: + break + yield bytes + + def release_conn(self) -> None: + if not self._pool or not self._connection: + return None + + self._pool._put_conn(self._connection) + self._connection = None + + def drain_conn(self) -> None: + self.close() + + @property + def data(self) -> bytes: + if self._body: + return self._body + else: + return self.read(cache_content=True) + + def json(self) -> typing.Any: + """ + Deserializes the body of the HTTP response as a Python object. + + The body of the HTTP response must be encoded using UTF-8, as per + `RFC 8529 Section 8.1 `_. + + To use a custom JSON decoder pass the result of :attr:`HTTPResponse.data` to + your custom decoder instead. + + If the body of the HTTP response is not decodable to UTF-8, a + `UnicodeDecodeError` will be raised. If the body of the HTTP response is not a + valid JSON document, a `json.JSONDecodeError` will be raised. + + Read more :ref:`here `. + + :returns: The body of the HTTP response as a Python object. + """ + data = self.data.decode("utf-8") + return _json.loads(data) + + def close(self) -> None: + if not self._closed: + if isinstance(self._response.body, IOBase): + self._response.body.close() + if self._connection: + self._connection.close() + self._connection = None + self._closed = True + + @contextmanager + def _error_catcher(self) -> typing.Generator[None]: + """ + Catch Emscripten specific exceptions thrown by fetch.py, + instead re-raising urllib3 variants, so that low-level exceptions + are not leaked in the high-level api. + + On exit, release the connection back to the pool. + """ + from .fetch import _RequestError, _TimeoutError # avoid circular import + + clean_exit = False + + try: + yield + # If no exception is thrown, we should avoid cleaning up + # unnecessarily. + clean_exit = True + except _TimeoutError as e: + raise TimeoutError(str(e)) + except _RequestError as e: + raise HTTPException(str(e)) + finally: + # If we didn't terminate cleanly, we need to throw away our + # connection. + if not clean_exit: + # The response may not be closed but we're not going to use it + # anymore so close it now + if ( + isinstance(self._response.body, IOBase) + and not self._response.body.closed + ): + self._response.body.close() + # release the connection back to the pool + self.release_conn() + else: + # If we have read everything from the response stream, + # return the connection back to the pool. + if ( + isinstance(self._response.body, IOBase) + and self._response.body.closed + ): + self.release_conn() diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/contrib/pyopenssl.py b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/contrib/pyopenssl.py new file mode 100644 index 0000000000000000000000000000000000000000..f06b8599920fdcf6b8a2f869141378709a30c84c --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/contrib/pyopenssl.py @@ -0,0 +1,563 @@ +""" +Module for using pyOpenSSL as a TLS backend. This module was relevant before +the standard library ``ssl`` module supported SNI, but now that we've dropped +support for Python 2.7 all relevant Python versions support SNI so +**this module is no longer recommended**. + +This needs the following packages installed: + +* `pyOpenSSL`_ (tested with 19.0.0) +* `cryptography`_ (minimum 2.3, from pyopenssl) +* `idna`_ (minimum 2.1, from cryptography) + +However, pyOpenSSL depends on cryptography, so while we use all three directly here we +end up having relatively few packages required. + +You can install them with the following command: + +.. code-block:: bash + + $ python -m pip install pyopenssl cryptography idna + +To activate certificate checking, call +:func:`~urllib3.contrib.pyopenssl.inject_into_urllib3` from your Python code +before you begin making HTTP requests. This can be done in a ``sitecustomize`` +module, or at any other time before your application begins using ``urllib3``, +like this: + +.. code-block:: python + + try: + import urllib3.contrib.pyopenssl + urllib3.contrib.pyopenssl.inject_into_urllib3() + except ImportError: + pass + +.. _pyopenssl: https://www.pyopenssl.org +.. _cryptography: https://cryptography.io +.. _idna: https://github.com/kjd/idna +""" + +from __future__ import annotations + +import OpenSSL.SSL # type: ignore[import-not-found] +from cryptography import x509 + +try: + from cryptography.x509 import UnsupportedExtension # type: ignore[attr-defined] +except ImportError: + # UnsupportedExtension is gone in cryptography >= 2.1.0 + class UnsupportedExtension(Exception): # type: ignore[no-redef] + pass + + +import logging +import ssl +import typing +from io import BytesIO +from socket import socket as socket_cls + +from .. import util + +if typing.TYPE_CHECKING: + from OpenSSL.crypto import X509 # type: ignore[import-not-found] + + +__all__ = ["inject_into_urllib3", "extract_from_urllib3"] + +# Map from urllib3 to PyOpenSSL compatible parameter-values. +_openssl_versions: dict[int, int] = { + util.ssl_.PROTOCOL_TLS: OpenSSL.SSL.SSLv23_METHOD, # type: ignore[attr-defined] + util.ssl_.PROTOCOL_TLS_CLIENT: OpenSSL.SSL.SSLv23_METHOD, # type: ignore[attr-defined] + ssl.PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD, +} + +if hasattr(ssl, "PROTOCOL_TLSv1_1") and hasattr(OpenSSL.SSL, "TLSv1_1_METHOD"): + _openssl_versions[ssl.PROTOCOL_TLSv1_1] = OpenSSL.SSL.TLSv1_1_METHOD + +if hasattr(ssl, "PROTOCOL_TLSv1_2") and hasattr(OpenSSL.SSL, "TLSv1_2_METHOD"): + _openssl_versions[ssl.PROTOCOL_TLSv1_2] = OpenSSL.SSL.TLSv1_2_METHOD + + +_stdlib_to_openssl_verify = { + ssl.CERT_NONE: OpenSSL.SSL.VERIFY_NONE, + ssl.CERT_OPTIONAL: OpenSSL.SSL.VERIFY_PEER, + ssl.CERT_REQUIRED: OpenSSL.SSL.VERIFY_PEER + + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT, +} +_openssl_to_stdlib_verify = {v: k for k, v in _stdlib_to_openssl_verify.items()} + +# The SSLvX values are the most likely to be missing in the future +# but we check them all just to be sure. +_OP_NO_SSLv2_OR_SSLv3: int = getattr(OpenSSL.SSL, "OP_NO_SSLv2", 0) | getattr( + OpenSSL.SSL, "OP_NO_SSLv3", 0 +) +_OP_NO_TLSv1: int = getattr(OpenSSL.SSL, "OP_NO_TLSv1", 0) +_OP_NO_TLSv1_1: int = getattr(OpenSSL.SSL, "OP_NO_TLSv1_1", 0) +_OP_NO_TLSv1_2: int = getattr(OpenSSL.SSL, "OP_NO_TLSv1_2", 0) +_OP_NO_TLSv1_3: int = getattr(OpenSSL.SSL, "OP_NO_TLSv1_3", 0) + +_openssl_to_ssl_minimum_version: dict[int, int] = { + ssl.TLSVersion.MINIMUM_SUPPORTED: _OP_NO_SSLv2_OR_SSLv3, + ssl.TLSVersion.TLSv1: _OP_NO_SSLv2_OR_SSLv3, + ssl.TLSVersion.TLSv1_1: _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1, + ssl.TLSVersion.TLSv1_2: _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1 | _OP_NO_TLSv1_1, + ssl.TLSVersion.TLSv1_3: ( + _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1 | _OP_NO_TLSv1_1 | _OP_NO_TLSv1_2 + ), + ssl.TLSVersion.MAXIMUM_SUPPORTED: ( + _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1 | _OP_NO_TLSv1_1 | _OP_NO_TLSv1_2 + ), +} +_openssl_to_ssl_maximum_version: dict[int, int] = { + ssl.TLSVersion.MINIMUM_SUPPORTED: ( + _OP_NO_SSLv2_OR_SSLv3 + | _OP_NO_TLSv1 + | _OP_NO_TLSv1_1 + | _OP_NO_TLSv1_2 + | _OP_NO_TLSv1_3 + ), + ssl.TLSVersion.TLSv1: ( + _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1_1 | _OP_NO_TLSv1_2 | _OP_NO_TLSv1_3 + ), + ssl.TLSVersion.TLSv1_1: _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1_2 | _OP_NO_TLSv1_3, + ssl.TLSVersion.TLSv1_2: _OP_NO_SSLv2_OR_SSLv3 | _OP_NO_TLSv1_3, + ssl.TLSVersion.TLSv1_3: _OP_NO_SSLv2_OR_SSLv3, + ssl.TLSVersion.MAXIMUM_SUPPORTED: _OP_NO_SSLv2_OR_SSLv3, +} + +# OpenSSL will only write 16K at a time +SSL_WRITE_BLOCKSIZE = 16384 + +orig_util_SSLContext = util.ssl_.SSLContext + + +log = logging.getLogger(__name__) + + +def inject_into_urllib3() -> None: + "Monkey-patch urllib3 with PyOpenSSL-backed SSL-support." + + _validate_dependencies_met() + + util.SSLContext = PyOpenSSLContext # type: ignore[assignment] + util.ssl_.SSLContext = PyOpenSSLContext # type: ignore[assignment] + util.IS_PYOPENSSL = True + util.ssl_.IS_PYOPENSSL = True + + +def extract_from_urllib3() -> None: + "Undo monkey-patching by :func:`inject_into_urllib3`." + + util.SSLContext = orig_util_SSLContext + util.ssl_.SSLContext = orig_util_SSLContext + util.IS_PYOPENSSL = False + util.ssl_.IS_PYOPENSSL = False + + +def _validate_dependencies_met() -> None: + """ + Verifies that PyOpenSSL's package-level dependencies have been met. + Throws `ImportError` if they are not met. + """ + # Method added in `cryptography==1.1`; not available in older versions + from cryptography.x509.extensions import Extensions + + if getattr(Extensions, "get_extension_for_class", None) is None: + raise ImportError( + "'cryptography' module missing required functionality. " + "Try upgrading to v1.3.4 or newer." + ) + + # pyOpenSSL 0.14 and above use cryptography for OpenSSL bindings. The _x509 + # attribute is only present on those versions. + from OpenSSL.crypto import X509 + + x509 = X509() + if getattr(x509, "_x509", None) is None: + raise ImportError( + "'pyOpenSSL' module missing required functionality. " + "Try upgrading to v0.14 or newer." + ) + + +def _dnsname_to_stdlib(name: str) -> str | None: + """ + Converts a dNSName SubjectAlternativeName field to the form used by the + standard library on the given Python version. + + Cryptography produces a dNSName as a unicode string that was idna-decoded + from ASCII bytes. We need to idna-encode that string to get it back, and + then on Python 3 we also need to convert to unicode via UTF-8 (the stdlib + uses PyUnicode_FromStringAndSize on it, which decodes via UTF-8). + + If the name cannot be idna-encoded then we return None signalling that + the name given should be skipped. + """ + + def idna_encode(name: str) -> bytes | None: + """ + Borrowed wholesale from the Python Cryptography Project. It turns out + that we can't just safely call `idna.encode`: it can explode for + wildcard names. This avoids that problem. + """ + import idna + + try: + for prefix in ["*.", "."]: + if name.startswith(prefix): + name = name[len(prefix) :] + return prefix.encode("ascii") + idna.encode(name) + return idna.encode(name) + except idna.core.IDNAError: + return None + + # Don't send IPv6 addresses through the IDNA encoder. + if ":" in name: + return name + + encoded_name = idna_encode(name) + if encoded_name is None: + return None + return encoded_name.decode("utf-8") + + +def get_subj_alt_name(peer_cert: X509) -> list[tuple[str, str]]: + """ + Given an PyOpenSSL certificate, provides all the subject alternative names. + """ + cert = peer_cert.to_cryptography() + + # We want to find the SAN extension. Ask Cryptography to locate it (it's + # faster than looping in Python) + try: + ext = cert.extensions.get_extension_for_class(x509.SubjectAlternativeName).value + except x509.ExtensionNotFound: + # No such extension, return the empty list. + return [] + except ( + x509.DuplicateExtension, + UnsupportedExtension, + x509.UnsupportedGeneralNameType, + UnicodeError, + ) as e: + # A problem has been found with the quality of the certificate. Assume + # no SAN field is present. + log.warning( + "A problem was encountered with the certificate that prevented " + "urllib3 from finding the SubjectAlternativeName field. This can " + "affect certificate validation. The error was %s", + e, + ) + return [] + + # We want to return dNSName and iPAddress fields. We need to cast the IPs + # back to strings because the match_hostname function wants them as + # strings. + # Sadly the DNS names need to be idna encoded and then, on Python 3, UTF-8 + # decoded. This is pretty frustrating, but that's what the standard library + # does with certificates, and so we need to attempt to do the same. + # We also want to skip over names which cannot be idna encoded. + names = [ + ("DNS", name) + for name in map(_dnsname_to_stdlib, ext.get_values_for_type(x509.DNSName)) + if name is not None + ] + names.extend( + ("IP Address", str(name)) for name in ext.get_values_for_type(x509.IPAddress) + ) + + return names + + +class WrappedSocket: + """API-compatibility wrapper for Python OpenSSL's Connection-class.""" + + def __init__( + self, + connection: OpenSSL.SSL.Connection, + socket: socket_cls, + suppress_ragged_eofs: bool = True, + ) -> None: + self.connection = connection + self.socket = socket + self.suppress_ragged_eofs = suppress_ragged_eofs + self._io_refs = 0 + self._closed = False + + def fileno(self) -> int: + return self.socket.fileno() + + # Copy-pasted from Python 3.5 source code + def _decref_socketios(self) -> None: + if self._io_refs > 0: + self._io_refs -= 1 + if self._closed: + self.close() + + def recv(self, *args: typing.Any, **kwargs: typing.Any) -> bytes: + try: + data = self.connection.recv(*args, **kwargs) + except OpenSSL.SSL.SysCallError as e: + if self.suppress_ragged_eofs and e.args == (-1, "Unexpected EOF"): + return b"" + else: + raise OSError(e.args[0], str(e)) from e + except OpenSSL.SSL.ZeroReturnError: + if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: + return b"" + else: + raise + except OpenSSL.SSL.WantReadError as e: + if not util.wait_for_read(self.socket, self.socket.gettimeout()): + raise TimeoutError("The read operation timed out") from e + else: + return self.recv(*args, **kwargs) + + # TLS 1.3 post-handshake authentication + except OpenSSL.SSL.Error as e: + raise ssl.SSLError(f"read error: {e!r}") from e + else: + return data # type: ignore[no-any-return] + + def recv_into(self, *args: typing.Any, **kwargs: typing.Any) -> int: + try: + return self.connection.recv_into(*args, **kwargs) # type: ignore[no-any-return] + except OpenSSL.SSL.SysCallError as e: + if self.suppress_ragged_eofs and e.args == (-1, "Unexpected EOF"): + return 0 + else: + raise OSError(e.args[0], str(e)) from e + except OpenSSL.SSL.ZeroReturnError: + if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: + return 0 + else: + raise + except OpenSSL.SSL.WantReadError as e: + if not util.wait_for_read(self.socket, self.socket.gettimeout()): + raise TimeoutError("The read operation timed out") from e + else: + return self.recv_into(*args, **kwargs) + + # TLS 1.3 post-handshake authentication + except OpenSSL.SSL.Error as e: + raise ssl.SSLError(f"read error: {e!r}") from e + + def settimeout(self, timeout: float) -> None: + return self.socket.settimeout(timeout) + + def _send_until_done(self, data: bytes) -> int: + while True: + try: + return self.connection.send(data) # type: ignore[no-any-return] + except OpenSSL.SSL.WantWriteError as e: + if not util.wait_for_write(self.socket, self.socket.gettimeout()): + raise TimeoutError() from e + continue + except OpenSSL.SSL.SysCallError as e: + raise OSError(e.args[0], str(e)) from e + + def sendall(self, data: bytes) -> None: + total_sent = 0 + while total_sent < len(data): + sent = self._send_until_done( + data[total_sent : total_sent + SSL_WRITE_BLOCKSIZE] + ) + total_sent += sent + + def shutdown(self, how: int) -> None: + try: + self.connection.shutdown() + except OpenSSL.SSL.Error as e: + raise ssl.SSLError(f"shutdown error: {e!r}") from e + + def close(self) -> None: + self._closed = True + if self._io_refs <= 0: + self._real_close() + + def _real_close(self) -> None: + try: + return self.connection.close() # type: ignore[no-any-return] + except OpenSSL.SSL.Error: + return + + def getpeercert( + self, binary_form: bool = False + ) -> dict[str, list[typing.Any]] | None: + x509 = self.connection.get_peer_certificate() + + if not x509: + return x509 # type: ignore[no-any-return] + + if binary_form: + return OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_ASN1, x509) # type: ignore[no-any-return] + + return { + "subject": ((("commonName", x509.get_subject().CN),),), # type: ignore[dict-item] + "subjectAltName": get_subj_alt_name(x509), + } + + def version(self) -> str: + return self.connection.get_protocol_version_name() # type: ignore[no-any-return] + + def selected_alpn_protocol(self) -> str | None: + alpn_proto = self.connection.get_alpn_proto_negotiated() + return alpn_proto.decode() if alpn_proto else None + + +WrappedSocket.makefile = socket_cls.makefile # type: ignore[attr-defined] + + +class PyOpenSSLContext: + """ + I am a wrapper class for the PyOpenSSL ``Context`` object. I am responsible + for translating the interface of the standard library ``SSLContext`` object + to calls into PyOpenSSL. + """ + + def __init__(self, protocol: int) -> None: + self.protocol = _openssl_versions[protocol] + self._ctx = OpenSSL.SSL.Context(self.protocol) + self._options = 0 + self.check_hostname = False + self._minimum_version: int = ssl.TLSVersion.MINIMUM_SUPPORTED + self._maximum_version: int = ssl.TLSVersion.MAXIMUM_SUPPORTED + self._verify_flags: int = ssl.VERIFY_X509_TRUSTED_FIRST + + @property + def options(self) -> int: + return self._options + + @options.setter + def options(self, value: int) -> None: + self._options = value + self._set_ctx_options() + + @property + def verify_flags(self) -> int: + return self._verify_flags + + @verify_flags.setter + def verify_flags(self, value: int) -> None: + self._verify_flags = value + self._ctx.get_cert_store().set_flags(self._verify_flags) + + @property + def verify_mode(self) -> int: + return _openssl_to_stdlib_verify[self._ctx.get_verify_mode()] + + @verify_mode.setter + def verify_mode(self, value: ssl.VerifyMode) -> None: + self._ctx.set_verify(_stdlib_to_openssl_verify[value], _verify_callback) + + def set_default_verify_paths(self) -> None: + self._ctx.set_default_verify_paths() + + def set_ciphers(self, ciphers: bytes | str) -> None: + if isinstance(ciphers, str): + ciphers = ciphers.encode("utf-8") + self._ctx.set_cipher_list(ciphers) + + def load_verify_locations( + self, + cafile: str | None = None, + capath: str | None = None, + cadata: bytes | None = None, + ) -> None: + if cafile is not None: + cafile = cafile.encode("utf-8") # type: ignore[assignment] + if capath is not None: + capath = capath.encode("utf-8") # type: ignore[assignment] + try: + self._ctx.load_verify_locations(cafile, capath) + if cadata is not None: + self._ctx.load_verify_locations(BytesIO(cadata)) + except OpenSSL.SSL.Error as e: + raise ssl.SSLError(f"unable to load trusted certificates: {e!r}") from e + + def load_cert_chain( + self, + certfile: str, + keyfile: str | None = None, + password: str | None = None, + ) -> None: + try: + self._ctx.use_certificate_chain_file(certfile) + if password is not None: + if not isinstance(password, bytes): + password = password.encode("utf-8") # type: ignore[assignment] + self._ctx.set_passwd_cb(lambda *_: password) + self._ctx.use_privatekey_file(keyfile or certfile) + except OpenSSL.SSL.Error as e: + raise ssl.SSLError(f"Unable to load certificate chain: {e!r}") from e + + def set_alpn_protocols(self, protocols: list[bytes | str]) -> None: + protocols = [util.util.to_bytes(p, "ascii") for p in protocols] + return self._ctx.set_alpn_protos(protocols) # type: ignore[no-any-return] + + def wrap_socket( + self, + sock: socket_cls, + server_side: bool = False, + do_handshake_on_connect: bool = True, + suppress_ragged_eofs: bool = True, + server_hostname: bytes | str | None = None, + ) -> WrappedSocket: + cnx = OpenSSL.SSL.Connection(self._ctx, sock) + + # If server_hostname is an IP, don't use it for SNI, per RFC6066 Section 3 + if server_hostname and not util.ssl_.is_ipaddress(server_hostname): + if isinstance(server_hostname, str): + server_hostname = server_hostname.encode("utf-8") + cnx.set_tlsext_host_name(server_hostname) + + cnx.set_connect_state() + + while True: + try: + cnx.do_handshake() + except OpenSSL.SSL.WantReadError as e: + if not util.wait_for_read(sock, sock.gettimeout()): + raise TimeoutError("select timed out") from e + continue + except OpenSSL.SSL.Error as e: + raise ssl.SSLError(f"bad handshake: {e!r}") from e + break + + return WrappedSocket(cnx, sock) + + def _set_ctx_options(self) -> None: + self._ctx.set_options( + self._options + | _openssl_to_ssl_minimum_version[self._minimum_version] + | _openssl_to_ssl_maximum_version[self._maximum_version] + ) + + @property + def minimum_version(self) -> int: + return self._minimum_version + + @minimum_version.setter + def minimum_version(self, minimum_version: int) -> None: + self._minimum_version = minimum_version + self._set_ctx_options() + + @property + def maximum_version(self) -> int: + return self._maximum_version + + @maximum_version.setter + def maximum_version(self, maximum_version: int) -> None: + self._maximum_version = maximum_version + self._set_ctx_options() + + +def _verify_callback( + cnx: OpenSSL.SSL.Connection, + x509: X509, + err_no: int, + err_depth: int, + return_code: int, +) -> bool: + return err_no == 0 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/contrib/socks.py b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/contrib/socks.py new file mode 100644 index 0000000000000000000000000000000000000000..d37da8fc2049a7b3f7e18d2f64e79c205cfe3d3e --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/contrib/socks.py @@ -0,0 +1,228 @@ +""" +This module contains provisional support for SOCKS proxies from within +urllib3. This module supports SOCKS4, SOCKS4A (an extension of SOCKS4), and +SOCKS5. To enable its functionality, either install PySocks or install this +module with the ``socks`` extra. + +The SOCKS implementation supports the full range of urllib3 features. It also +supports the following SOCKS features: + +- SOCKS4A (``proxy_url='socks4a://...``) +- SOCKS4 (``proxy_url='socks4://...``) +- SOCKS5 with remote DNS (``proxy_url='socks5h://...``) +- SOCKS5 with local DNS (``proxy_url='socks5://...``) +- Usernames and passwords for the SOCKS proxy + +.. note:: + It is recommended to use ``socks5h://`` or ``socks4a://`` schemes in + your ``proxy_url`` to ensure that DNS resolution is done from the remote + server instead of client-side when connecting to a domain name. + +SOCKS4 supports IPv4 and domain names with the SOCKS4A extension. SOCKS5 +supports IPv4, IPv6, and domain names. + +When connecting to a SOCKS4 proxy the ``username`` portion of the ``proxy_url`` +will be sent as the ``userid`` section of the SOCKS request: + +.. code-block:: python + + proxy_url="socks4a://@proxy-host" + +When connecting to a SOCKS5 proxy the ``username`` and ``password`` portion +of the ``proxy_url`` will be sent as the username/password to authenticate +with the proxy: + +.. code-block:: python + + proxy_url="socks5h://:@proxy-host" + +""" + +from __future__ import annotations + +try: + import socks # type: ignore[import-untyped] +except ImportError: + import warnings + + from ..exceptions import DependencyWarning + + warnings.warn( + ( + "SOCKS support in urllib3 requires the installation of optional " + "dependencies: specifically, PySocks. For more information, see " + "https://urllib3.readthedocs.io/en/latest/advanced-usage.html#socks-proxies" + ), + DependencyWarning, + ) + raise + +import typing +from socket import timeout as SocketTimeout + +from ..connection import HTTPConnection, HTTPSConnection +from ..connectionpool import HTTPConnectionPool, HTTPSConnectionPool +from ..exceptions import ConnectTimeoutError, NewConnectionError +from ..poolmanager import PoolManager +from ..util.url import parse_url + +try: + import ssl +except ImportError: + ssl = None # type: ignore[assignment] + + +class _TYPE_SOCKS_OPTIONS(typing.TypedDict): + socks_version: int + proxy_host: str | None + proxy_port: str | None + username: str | None + password: str | None + rdns: bool + + +class SOCKSConnection(HTTPConnection): + """ + A plain-text HTTP connection that connects via a SOCKS proxy. + """ + + def __init__( + self, + _socks_options: _TYPE_SOCKS_OPTIONS, + *args: typing.Any, + **kwargs: typing.Any, + ) -> None: + self._socks_options = _socks_options + super().__init__(*args, **kwargs) + + def _new_conn(self) -> socks.socksocket: + """ + Establish a new connection via the SOCKS proxy. + """ + extra_kw: dict[str, typing.Any] = {} + if self.source_address: + extra_kw["source_address"] = self.source_address + + if self.socket_options: + extra_kw["socket_options"] = self.socket_options + + try: + conn = socks.create_connection( + (self.host, self.port), + proxy_type=self._socks_options["socks_version"], + proxy_addr=self._socks_options["proxy_host"], + proxy_port=self._socks_options["proxy_port"], + proxy_username=self._socks_options["username"], + proxy_password=self._socks_options["password"], + proxy_rdns=self._socks_options["rdns"], + timeout=self.timeout, + **extra_kw, + ) + + except SocketTimeout as e: + raise ConnectTimeoutError( + self, + f"Connection to {self.host} timed out. (connect timeout={self.timeout})", + ) from e + + except socks.ProxyError as e: + # This is fragile as hell, but it seems to be the only way to raise + # useful errors here. + if e.socket_err: + error = e.socket_err + if isinstance(error, SocketTimeout): + raise ConnectTimeoutError( + self, + f"Connection to {self.host} timed out. (connect timeout={self.timeout})", + ) from e + else: + # Adding `from e` messes with coverage somehow, so it's omitted. + # See #2386. + raise NewConnectionError( + self, f"Failed to establish a new connection: {error}" + ) + else: # Defensive: see https://github.com/urllib3/urllib3/pull/3728#pullrequestreview-3816302703 + raise NewConnectionError( + self, f"Failed to establish a new connection: {e}" + ) from e + + except OSError as e: # Defensive: PySocks should catch all these. + raise NewConnectionError( + self, f"Failed to establish a new connection: {e}" + ) from e + + return conn + + +# We don't need to duplicate the Verified/Unverified distinction from +# urllib3/connection.py here because the HTTPSConnection will already have been +# correctly set to either the Verified or Unverified form by that module. This +# means the SOCKSHTTPSConnection will automatically be the correct type. +class SOCKSHTTPSConnection(SOCKSConnection, HTTPSConnection): + pass + + +class SOCKSHTTPConnectionPool(HTTPConnectionPool): + ConnectionCls = SOCKSConnection + + +class SOCKSHTTPSConnectionPool(HTTPSConnectionPool): + ConnectionCls = SOCKSHTTPSConnection + + +class SOCKSProxyManager(PoolManager): + """ + A version of the urllib3 ProxyManager that routes connections via the + defined SOCKS proxy. + """ + + pool_classes_by_scheme = { + "http": SOCKSHTTPConnectionPool, + "https": SOCKSHTTPSConnectionPool, + } + + def __init__( + self, + proxy_url: str, + username: str | None = None, + password: str | None = None, + num_pools: int = 10, + headers: typing.Mapping[str, str] | None = None, + **connection_pool_kw: typing.Any, + ): + parsed = parse_url(proxy_url) + + if username is None and password is None and parsed.auth is not None: + split = parsed.auth.split(":") + if len(split) == 2: + username, password = split + if parsed.scheme == "socks5": + socks_version = socks.PROXY_TYPE_SOCKS5 + rdns = False + elif parsed.scheme == "socks5h": + socks_version = socks.PROXY_TYPE_SOCKS5 + rdns = True + elif parsed.scheme == "socks4": + socks_version = socks.PROXY_TYPE_SOCKS4 + rdns = False + elif parsed.scheme == "socks4a": + socks_version = socks.PROXY_TYPE_SOCKS4 + rdns = True + else: + raise ValueError(f"Unable to determine SOCKS version from {proxy_url}") + + self.proxy_url = proxy_url + + socks_options = { + "socks_version": socks_version, + "proxy_host": parsed.host, + "proxy_port": parsed.port, + "username": username, + "password": password, + "rdns": rdns, + } + connection_pool_kw["_socks_options"] = socks_options + + super().__init__(num_pools, headers, **connection_pool_kw) + + self.pool_classes_by_scheme = SOCKSProxyManager.pool_classes_by_scheme diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/exceptions.py b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/exceptions.py new file mode 100644 index 0000000000000000000000000000000000000000..3d7e9b93d307ffe7d54868122227a165cc98383c --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/exceptions.py @@ -0,0 +1,335 @@ +from __future__ import annotations + +import socket +import typing +import warnings +from email.errors import MessageDefect +from http.client import IncompleteRead as httplib_IncompleteRead + +if typing.TYPE_CHECKING: + from .connection import HTTPConnection + from .connectionpool import ConnectionPool + from .response import HTTPResponse + from .util.retry import Retry + +# Base Exceptions + + +class HTTPError(Exception): + """Base exception used by this module.""" + + +class HTTPWarning(Warning): + """Base warning used by this module.""" + + +_TYPE_REDUCE_RESULT = tuple[typing.Callable[..., object], tuple[object, ...]] + + +class PoolError(HTTPError): + """Base exception for errors caused within a pool.""" + + def __init__(self, pool: ConnectionPool, message: str) -> None: + self.pool = pool + self._message = message + super().__init__(f"{pool}: {message}") + + def __reduce__(self) -> _TYPE_REDUCE_RESULT: + # For pickling purposes. + return self.__class__, (None, self._message) + + +class RequestError(PoolError): + """Base exception for PoolErrors that have associated URLs.""" + + def __init__(self, pool: ConnectionPool, url: str | None, message: str) -> None: + self.url = url + super().__init__(pool, message) + + def __reduce__(self) -> _TYPE_REDUCE_RESULT: + # For pickling purposes. + return self.__class__, (None, self.url, self._message) + + +class SSLError(HTTPError): + """Raised when SSL certificate fails in an HTTPS connection.""" + + +class ProxyError(HTTPError): + """Raised when the connection to a proxy fails.""" + + # The original error is also available as __cause__. + original_error: Exception + + def __init__(self, message: str, error: Exception) -> None: + super().__init__(message, error) + self.original_error = error + + +class DecodeError(HTTPError): + """Raised when automatic decoding based on Content-Type fails.""" + + +class ProtocolError(HTTPError): + """Raised when something unexpected happens mid-request/response.""" + + +#: Renamed to ProtocolError but aliased for backwards compatibility. +ConnectionError = ProtocolError + + +# Leaf Exceptions + + +class MaxRetryError(RequestError): + """Raised when the maximum number of retries is exceeded. + + :param pool: The connection pool + :type pool: :class:`~urllib3.connectionpool.HTTPConnectionPool` + :param str url: The requested Url + :param reason: The underlying error + :type reason: :class:`Exception` + + """ + + def __init__( + self, pool: ConnectionPool, url: str | None, reason: Exception | None = None + ) -> None: + self.reason = reason + + message = f"Max retries exceeded with url: {url} (Caused by {reason!r})" + + super().__init__(pool, url, message) + + def __reduce__(self) -> _TYPE_REDUCE_RESULT: + # For pickling purposes. + return self.__class__, (None, self.url, self.reason) + + +class HostChangedError(RequestError): + """Raised when an existing pool gets a request for a foreign host.""" + + def __init__( + self, pool: ConnectionPool, url: str, retries: Retry | int = 3 + ) -> None: + message = f"Tried to open a foreign host with url: {url}" + super().__init__(pool, url, message) + self.retries = retries + + +class TimeoutStateError(HTTPError): + """Raised when passing an invalid state to a timeout""" + + +class TimeoutError(HTTPError): + """Raised when a socket timeout error occurs. + + Catching this error will catch both :exc:`ReadTimeoutErrors + ` and :exc:`ConnectTimeoutErrors `. + """ + + +class ReadTimeoutError(TimeoutError, RequestError): + """Raised when a socket timeout occurs while receiving data from a server""" + + +# This timeout error does not have a URL attached and needs to inherit from the +# base HTTPError +class ConnectTimeoutError(TimeoutError): + """Raised when a socket timeout occurs while connecting to a server""" + + +class NewConnectionError(ConnectTimeoutError, HTTPError): + """Raised when we fail to establish a new connection. Usually ECONNREFUSED.""" + + def __init__(self, conn: HTTPConnection, message: str) -> None: + self.conn = conn + self._message = message + super().__init__(f"{conn}: {message}") + + def __reduce__(self) -> _TYPE_REDUCE_RESULT: + # For pickling purposes. + return self.__class__, (None, self._message) + + @property + def pool(self) -> HTTPConnection: + warnings.warn( + "The 'pool' property is deprecated and will be removed " + "in urllib3 v3.0. Use 'conn' instead.", + FutureWarning, + stacklevel=2, + ) + + return self.conn + + +class NameResolutionError(NewConnectionError): + """Raised when host name resolution fails.""" + + def __init__(self, host: str, conn: HTTPConnection, reason: socket.gaierror): + message = f"Failed to resolve '{host}' ({reason})" + self._host = host + self._reason = reason + super().__init__(conn, message) + + def __reduce__(self) -> _TYPE_REDUCE_RESULT: + # For pickling purposes. + return self.__class__, (self._host, None, self._reason) + + +class EmptyPoolError(PoolError): + """Raised when a pool runs out of connections and no more are allowed.""" + + +class FullPoolError(PoolError): + """Raised when we try to add a connection to a full pool in blocking mode.""" + + +class ClosedPoolError(PoolError): + """Raised when a request enters a pool after the pool has been closed.""" + + +class LocationValueError(ValueError, HTTPError): + """Raised when there is something wrong with a given URL input.""" + + +class LocationParseError(LocationValueError): + """Raised when get_host or similar fails to parse the URL input.""" + + def __init__(self, location: str) -> None: + message = f"Failed to parse: {location}" + super().__init__(message) + + self.location = location + + +class URLSchemeUnknown(LocationValueError): + """Raised when a URL input has an unsupported scheme.""" + + def __init__(self, scheme: str): + message = f"Not supported URL scheme {scheme}" + super().__init__(message) + + self.scheme = scheme + + +class ResponseError(HTTPError): + """Used as a container for an error reason supplied in a MaxRetryError.""" + + GENERIC_ERROR = "too many error responses" + SPECIFIC_ERROR = "too many {status_code} error responses" + + +class SecurityWarning(HTTPWarning): + """Warned when performing security reducing actions""" + + +class InsecureRequestWarning(SecurityWarning): + """Warned when making an unverified HTTPS request.""" + + +class NotOpenSSLWarning(SecurityWarning): + """Warned when using unsupported SSL library""" + + +class SystemTimeWarning(SecurityWarning): + """Warned when system time is suspected to be wrong""" + + +class InsecurePlatformWarning(SecurityWarning): + """Warned when certain TLS/SSL configuration is not available on a platform.""" + + +class DependencyWarning(HTTPWarning): + """ + Warned when an attempt is made to import a module with missing optional + dependencies. + """ + + +class ResponseNotChunked(ProtocolError, ValueError): + """Response needs to be chunked in order to read it as chunks.""" + + +class BodyNotHttplibCompatible(HTTPError): + """ + Body should be :class:`http.client.HTTPResponse` like + (have an fp attribute which returns raw chunks) for read_chunked(). + """ + + +class IncompleteRead(HTTPError, httplib_IncompleteRead): + """ + Response length doesn't match expected Content-Length + + Subclass of :class:`http.client.IncompleteRead` to allow int value + for ``partial`` to avoid creating large objects on streamed reads. + """ + + partial: int # type: ignore[assignment] + expected: int + + def __init__(self, partial: int, expected: int) -> None: + self.partial = partial + self.expected = expected + + def __repr__(self) -> str: + return "IncompleteRead(%i bytes read, %i more expected)" % ( + self.partial, + self.expected, + ) + + +class InvalidChunkLength(HTTPError, httplib_IncompleteRead): + """Invalid chunk length in a chunked response.""" + + def __init__(self, response: HTTPResponse, length: bytes) -> None: + self.partial: int = response.tell() # type: ignore[assignment] + self.expected: int | None = response.length_remaining + self.response = response + self.length = length + + def __repr__(self) -> str: + return "InvalidChunkLength(got length %r, %i bytes read)" % ( + self.length, + self.partial, + ) + + +class InvalidHeader(HTTPError): + """The header provided was somehow invalid.""" + + +class ProxySchemeUnknown(AssertionError, URLSchemeUnknown): + """ProxyManager does not support the supplied scheme""" + + # TODO(t-8ch): Stop inheriting from AssertionError in v2.0. + + def __init__(self, scheme: str | None) -> None: + # 'localhost' is here because our URL parser parses + # localhost:8080 -> scheme=localhost, remove if we fix this. + if scheme == "localhost": + scheme = None + if scheme is None: + message = "Proxy URL had no scheme, should start with http:// or https://" + else: + message = f"Proxy URL had unsupported scheme {scheme}, should use http:// or https://" + super().__init__(message) + + +class ProxySchemeUnsupported(ValueError): + """Fetching HTTPS resources through HTTPS proxies is unsupported""" + + +class HeaderParsingError(HTTPError): + """Raised by assert_header_parsing, but we convert it to a log.warning statement.""" + + def __init__( + self, defects: list[MessageDefect], unparsed_data: bytes | str | None + ) -> None: + message = f"{defects or 'Unknown'}, unparsed data: {unparsed_data!r}" + super().__init__(message) + + +class UnrewindableBodyError(HTTPError): + """urllib3 encountered an error when trying to rewind a body""" diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/fields.py b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/fields.py new file mode 100644 index 0000000000000000000000000000000000000000..fe68e177324bc67f145091e464556a69b89bcda7 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/fields.py @@ -0,0 +1,341 @@ +from __future__ import annotations + +import email.utils +import mimetypes +import typing + +_TYPE_FIELD_VALUE = typing.Union[str, bytes] +_TYPE_FIELD_VALUE_TUPLE = typing.Union[ + _TYPE_FIELD_VALUE, + tuple[str, _TYPE_FIELD_VALUE], + tuple[str, _TYPE_FIELD_VALUE, str], +] + + +def guess_content_type( + filename: str | None, default: str = "application/octet-stream" +) -> str: + """ + Guess the "Content-Type" of a file. + + :param filename: + The filename to guess the "Content-Type" of using :mod:`mimetypes`. + :param default: + If no "Content-Type" can be guessed, default to `default`. + """ + if filename: + return mimetypes.guess_type(filename)[0] or default + return default + + +def format_header_param_rfc2231(name: str, value: _TYPE_FIELD_VALUE) -> str: + """ + Helper function to format and quote a single header parameter using the + strategy defined in RFC 2231. + + Particularly useful for header parameters which might contain + non-ASCII values, like file names. This follows + `RFC 2388 Section 4.4 `_. + + :param name: + The name of the parameter, a string expected to be ASCII only. + :param value: + The value of the parameter, provided as ``bytes`` or `str``. + :returns: + An RFC-2231-formatted unicode string. + + .. deprecated:: 2.0.0 + Will be removed in urllib3 v3.0. This is not valid for + ``multipart/form-data`` header parameters. + """ + import warnings + + warnings.warn( + "'format_header_param_rfc2231' is insecure, deprecated and will be " + "removed in urllib3 v3.0. This is not valid for " + "multipart/form-data header parameters.", + FutureWarning, + stacklevel=2, + ) + + if isinstance(value, bytes): + value = value.decode("utf-8") + + if not any(ch in value for ch in '"\\\r\n'): + result = f'{name}="{value}"' + try: + result.encode("ascii") + except (UnicodeEncodeError, UnicodeDecodeError): + pass + else: + return result + + value = email.utils.encode_rfc2231(value, "utf-8") + value = f"{name}*={value}" + + return value + + +def format_multipart_header_param(name: str, value: _TYPE_FIELD_VALUE) -> str: + """ + Format and quote a single multipart header parameter. + + This follows the `WHATWG HTML Standard`_ as of 2021/06/10, matching + the behavior of current browser and curl versions. Values are + assumed to be UTF-8. The ``\\n``, ``\\r``, and ``"`` characters are + percent encoded. + + .. _WHATWG HTML Standard: + https://html.spec.whatwg.org/multipage/ + form-control-infrastructure.html#multipart-form-data + + :param name: + The name of the parameter, an ASCII-only ``str``. + :param value: + The value of the parameter, a ``str`` or UTF-8 encoded + ``bytes``. + :returns: + A string ``name="value"`` with the escaped value. + + .. versionchanged:: 2.0.0 + Matches the WHATWG HTML Standard as of 2021/06/10. Control + characters are no longer percent encoded. + + .. versionchanged:: 2.0.0 + Renamed from ``format_header_param_html5`` and + ``format_header_param``. The old names will be removed in + urllib3 v3.0. + """ + if isinstance(value, bytes): + value = value.decode("utf-8") + + # percent encode \n \r " + value = value.translate({10: "%0A", 13: "%0D", 34: "%22"}) + return f'{name}="{value}"' + + +def format_header_param_html5(name: str, value: _TYPE_FIELD_VALUE) -> str: + """ + .. deprecated:: 2.0.0 + Renamed to :func:`format_multipart_header_param`. Will be + removed in urllib3 v3.0. + """ + import warnings + + warnings.warn( + "'format_header_param_html5' has been renamed to " + "'format_multipart_header_param'. The old name will be " + "removed in urllib3 v3.0.", + FutureWarning, + stacklevel=2, + ) + return format_multipart_header_param(name, value) + + +def format_header_param(name: str, value: _TYPE_FIELD_VALUE) -> str: + """ + .. deprecated:: 2.0.0 + Renamed to :func:`format_multipart_header_param`. Will be + removed in urllib3 v3.0. + """ + import warnings + + warnings.warn( + "'format_header_param' has been renamed to " + "'format_multipart_header_param'. The old name will be " + "removed in urllib3 v3.0.", + FutureWarning, + stacklevel=2, + ) + return format_multipart_header_param(name, value) + + +class RequestField: + """ + A data container for request body parameters. + + :param name: + The name of this request field. Must be unicode. + :param data: + The data/value body. + :param filename: + An optional filename of the request field. Must be unicode. + :param headers: + An optional dict-like object of headers to initially use for the field. + + .. versionchanged:: 2.0.0 + The ``header_formatter`` parameter is deprecated and will + be removed in urllib3 v3.0. + """ + + def __init__( + self, + name: str, + data: _TYPE_FIELD_VALUE, + filename: str | None = None, + headers: typing.Mapping[str, str] | None = None, + header_formatter: typing.Callable[[str, _TYPE_FIELD_VALUE], str] | None = None, + ): + self._name = name + self._filename = filename + self.data = data + self.headers: dict[str, str | None] = {} + if headers: + self.headers = dict(headers) + + if header_formatter is not None: + import warnings + + warnings.warn( + "The 'header_formatter' parameter is deprecated and " + "will be removed in urllib3 v3.0.", + FutureWarning, + stacklevel=2, + ) + self.header_formatter = header_formatter + else: + self.header_formatter = format_multipart_header_param + + @classmethod + def from_tuples( + cls, + fieldname: str, + value: _TYPE_FIELD_VALUE_TUPLE, + header_formatter: typing.Callable[[str, _TYPE_FIELD_VALUE], str] | None = None, + ) -> RequestField: + """ + A :class:`~urllib3.fields.RequestField` factory from old-style tuple parameters. + + Supports constructing :class:`~urllib3.fields.RequestField` from + parameter of key/value strings AND key/filetuple. A filetuple is a + (filename, data, MIME type) tuple where the MIME type is optional. + For example:: + + 'foo': 'bar', + 'fakefile': ('foofile.txt', 'contents of foofile'), + 'realfile': ('barfile.txt', open('realfile').read()), + 'typedfile': ('bazfile.bin', open('bazfile').read(), 'image/jpeg'), + 'nonamefile': 'contents of nonamefile field', + + Field names and filenames must be unicode. + """ + filename: str | None + content_type: str | None + data: _TYPE_FIELD_VALUE + + if isinstance(value, tuple): + if len(value) == 3: + filename, data, content_type = value + else: + filename, data = value + content_type = guess_content_type(filename) + else: + filename = None + content_type = None + data = value + + request_param = cls( + fieldname, data, filename=filename, header_formatter=header_formatter + ) + request_param.make_multipart(content_type=content_type) + + return request_param + + def _render_part(self, name: str, value: _TYPE_FIELD_VALUE) -> str: + """ + Override this method to change how each multipart header + parameter is formatted. By default, this calls + :func:`format_multipart_header_param`. + + :param name: + The name of the parameter, an ASCII-only ``str``. + :param value: + The value of the parameter, a ``str`` or UTF-8 encoded + ``bytes``. + + :meta public: + """ + return self.header_formatter(name, value) + + def _render_parts( + self, + header_parts: ( + dict[str, _TYPE_FIELD_VALUE | None] + | typing.Sequence[tuple[str, _TYPE_FIELD_VALUE | None]] + ), + ) -> str: + """ + Helper function to format and quote a single header. + + Useful for single headers that are composed of multiple items. E.g., + 'Content-Disposition' fields. + + :param header_parts: + A sequence of (k, v) tuples or a :class:`dict` of (k, v) to format + as `k1="v1"; k2="v2"; ...`. + """ + iterable: typing.Iterable[tuple[str, _TYPE_FIELD_VALUE | None]] + + parts = [] + if isinstance(header_parts, dict): + iterable = header_parts.items() + else: + iterable = header_parts + + for name, value in iterable: + if value is not None: + parts.append(self._render_part(name, value)) + + return "; ".join(parts) + + def render_headers(self) -> str: + """ + Renders the headers for this request field. + """ + lines = [] + + sort_keys = ["Content-Disposition", "Content-Type", "Content-Location"] + for sort_key in sort_keys: + if self.headers.get(sort_key, False): + lines.append(f"{sort_key}: {self.headers[sort_key]}") + + for header_name, header_value in self.headers.items(): + if header_name not in sort_keys: + if header_value: + lines.append(f"{header_name}: {header_value}") + + lines.append("\r\n") + return "\r\n".join(lines) + + def make_multipart( + self, + content_disposition: str | None = None, + content_type: str | None = None, + content_location: str | None = None, + ) -> None: + """ + Makes this request field into a multipart request field. + + This method overrides "Content-Disposition", "Content-Type" and + "Content-Location" headers to the request parameter. + + :param content_disposition: + The 'Content-Disposition' of the request body. Defaults to 'form-data' + :param content_type: + The 'Content-Type' of the request body. + :param content_location: + The 'Content-Location' of the request body. + + """ + content_disposition = (content_disposition or "form-data") + "; ".join( + [ + "", + self._render_parts( + (("name", self._name), ("filename", self._filename)) + ), + ] + ) + + self.headers["Content-Disposition"] = content_disposition + self.headers["Content-Type"] = content_type + self.headers["Content-Location"] = content_location diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/filepost.py b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/filepost.py new file mode 100644 index 0000000000000000000000000000000000000000..14f70b05b4778f91137e4a9e7059d7514aa44d28 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/filepost.py @@ -0,0 +1,89 @@ +from __future__ import annotations + +import binascii +import codecs +import os +import typing +from io import BytesIO + +from .fields import _TYPE_FIELD_VALUE_TUPLE, RequestField + +writer = codecs.lookup("utf-8")[3] + +_TYPE_FIELDS_SEQUENCE = typing.Sequence[ + typing.Union[tuple[str, _TYPE_FIELD_VALUE_TUPLE], RequestField] +] +_TYPE_FIELDS = typing.Union[ + _TYPE_FIELDS_SEQUENCE, + typing.Mapping[str, _TYPE_FIELD_VALUE_TUPLE], +] + + +def choose_boundary() -> str: + """ + Our embarrassingly-simple replacement for mimetools.choose_boundary. + """ + return binascii.hexlify(os.urandom(16)).decode() + + +def iter_field_objects(fields: _TYPE_FIELDS) -> typing.Iterable[RequestField]: + """ + Iterate over fields. + + Supports list of (k, v) tuples and dicts, and lists of + :class:`~urllib3.fields.RequestField`. + + """ + iterable: typing.Iterable[RequestField | tuple[str, _TYPE_FIELD_VALUE_TUPLE]] + + if isinstance(fields, typing.Mapping): + iterable = fields.items() + else: + iterable = fields + + for field in iterable: + if isinstance(field, RequestField): + yield field + else: + yield RequestField.from_tuples(*field) + + +def encode_multipart_formdata( + fields: _TYPE_FIELDS, boundary: str | None = None +) -> tuple[bytes, str]: + """ + Encode a dictionary of ``fields`` using the multipart/form-data MIME format. + + :param fields: + Dictionary of fields or list of (key, :class:`~urllib3.fields.RequestField`). + Values are processed by :func:`urllib3.fields.RequestField.from_tuples`. + + :param boundary: + If not specified, then a random boundary will be generated using + :func:`urllib3.filepost.choose_boundary`. + """ + body = BytesIO() + if boundary is None: + boundary = choose_boundary() + + for field in iter_field_objects(fields): + body.write(f"--{boundary}\r\n".encode("latin-1")) + + writer(body).write(field.render_headers()) + data = field.data + + if isinstance(data, int): + data = str(data) # Backwards compatibility + + if isinstance(data, str): + writer(body).write(data) + else: + body.write(data) + + body.write(b"\r\n") + + body.write(f"--{boundary}--\r\n".encode("latin-1")) + + content_type = f"multipart/form-data; boundary={boundary}" + + return body.getvalue(), content_type diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/http2/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/http2/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..133e1d8f237f6fddd557ae1c0e0cf738f7cc2748 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/http2/__init__.py @@ -0,0 +1,53 @@ +from __future__ import annotations + +from importlib.metadata import version + +__all__ = [ + "inject_into_urllib3", + "extract_from_urllib3", +] + +import typing + +orig_HTTPSConnection: typing.Any = None + + +def inject_into_urllib3() -> None: + # First check if h2 version is valid + h2_version = version("h2") + if not h2_version.startswith("4."): + raise ImportError( + "urllib3 v2 supports h2 version 4.x.x, currently " + f"the 'h2' module is compiled with {h2_version!r}. " + "See: https://github.com/urllib3/urllib3/issues/3290" + ) + + # Import here to avoid circular dependencies. + from .. import connection as urllib3_connection + from .. import util as urllib3_util + from ..connectionpool import HTTPSConnectionPool + from ..util import ssl_ as urllib3_util_ssl + from .connection import HTTP2Connection + + global orig_HTTPSConnection + orig_HTTPSConnection = urllib3_connection.HTTPSConnection + + HTTPSConnectionPool.ConnectionCls = HTTP2Connection + urllib3_connection.HTTPSConnection = HTTP2Connection # type: ignore[misc] + + # TODO: Offer 'http/1.1' as well, but for testing purposes this is handy. + urllib3_util.ALPN_PROTOCOLS = ["h2"] + urllib3_util_ssl.ALPN_PROTOCOLS = ["h2"] + + +def extract_from_urllib3() -> None: + from .. import connection as urllib3_connection + from .. import util as urllib3_util + from ..connectionpool import HTTPSConnectionPool + from ..util import ssl_ as urllib3_util_ssl + + HTTPSConnectionPool.ConnectionCls = orig_HTTPSConnection + urllib3_connection.HTTPSConnection = orig_HTTPSConnection # type: ignore[misc] + + urllib3_util.ALPN_PROTOCOLS = ["http/1.1"] + urllib3_util_ssl.ALPN_PROTOCOLS = ["http/1.1"] diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/http2/__pycache__/__init__.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/http2/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2bb19dc6c002d9e5da617a121f8f36cf0cb69bf0 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/http2/__pycache__/__init__.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/http2/__pycache__/probe.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/http2/__pycache__/probe.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..43e6be2bded7a1935175afee391d5815f87b8254 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/http2/__pycache__/probe.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/http2/connection.py b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/http2/connection.py new file mode 100644 index 0000000000000000000000000000000000000000..0a026da0a8357e324ded47b82b24042713b9bf06 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/http2/connection.py @@ -0,0 +1,356 @@ +from __future__ import annotations + +import logging +import re +import threading +import types +import typing + +import h2.config +import h2.connection +import h2.events + +from .._base_connection import _TYPE_BODY +from .._collections import HTTPHeaderDict +from ..connection import HTTPSConnection, _get_default_user_agent +from ..exceptions import ConnectionError +from ..response import BaseHTTPResponse + +orig_HTTPSConnection = HTTPSConnection + +T = typing.TypeVar("T") + +log = logging.getLogger(__name__) + +RE_IS_LEGAL_HEADER_NAME = re.compile(rb"^[!#$%&'*+\-.^_`|~0-9a-z]+$") +RE_IS_ILLEGAL_HEADER_VALUE = re.compile(rb"[\0\x00\x0a\x0d\r\n]|^[ \r\n\t]|[ \r\n\t]$") + + +def _is_legal_header_name(name: bytes) -> bool: + """ + "An implementation that validates fields according to the definitions in Sections + 5.1 and 5.5 of [HTTP] only needs an additional check that field names do not + include uppercase characters." (https://httpwg.org/specs/rfc9113.html#n-field-validity) + + `http.client._is_legal_header_name` does not validate the field name according to the + HTTP 1.1 spec, so we do that here, in addition to checking for uppercase characters. + + This does not allow for the `:` character in the header name, so should not + be used to validate pseudo-headers. + """ + return bool(RE_IS_LEGAL_HEADER_NAME.match(name)) + + +def _is_illegal_header_value(value: bytes) -> bool: + """ + "A field value MUST NOT contain the zero value (ASCII NUL, 0x00), line feed + (ASCII LF, 0x0a), or carriage return (ASCII CR, 0x0d) at any position. A field + value MUST NOT start or end with an ASCII whitespace character (ASCII SP or HTAB, + 0x20 or 0x09)." (https://httpwg.org/specs/rfc9113.html#n-field-validity) + """ + return bool(RE_IS_ILLEGAL_HEADER_VALUE.search(value)) + + +class _LockedObject(typing.Generic[T]): + """ + A wrapper class that hides a specific object behind a lock. + The goal here is to provide a simple way to protect access to an object + that cannot safely be simultaneously accessed from multiple threads. The + intended use of this class is simple: take hold of it with a context + manager, which returns the protected object. + """ + + __slots__ = ( + "lock", + "_obj", + ) + + def __init__(self, obj: T): + self.lock = threading.RLock() + self._obj = obj + + def __enter__(self) -> T: + self.lock.acquire() + return self._obj + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: types.TracebackType | None, + ) -> None: + self.lock.release() + + +class HTTP2Connection(HTTPSConnection): + def __init__( + self, host: str, port: int | None = None, **kwargs: typing.Any + ) -> None: + self._h2_conn = self._new_h2_conn() + self._h2_stream: int | None = None + self._headers: list[tuple[bytes, bytes]] = [] + + if "proxy" in kwargs or "proxy_config" in kwargs: # Defensive: + raise NotImplementedError("Proxies aren't supported with HTTP/2") + + super().__init__(host, port, **kwargs) + + if self._tunnel_host is not None: + raise NotImplementedError("Tunneling isn't supported with HTTP/2") + + def _new_h2_conn(self) -> _LockedObject[h2.connection.H2Connection]: + config = h2.config.H2Configuration(client_side=True) + return _LockedObject(h2.connection.H2Connection(config=config)) + + def connect(self) -> None: + super().connect() + with self._h2_conn as conn: + conn.initiate_connection() + if data_to_send := conn.data_to_send(): + self.sock.sendall(data_to_send) + + def putrequest( # type: ignore[override] + self, + method: str, + url: str, + **kwargs: typing.Any, + ) -> None: + """putrequest + This deviates from the HTTPConnection method signature since we never need to override + sending accept-encoding headers or the host header. + """ + if "skip_host" in kwargs: + raise NotImplementedError("`skip_host` isn't supported") + if "skip_accept_encoding" in kwargs: + raise NotImplementedError("`skip_accept_encoding` isn't supported") + + self._request_url = url or "/" + self._validate_path(url) # type: ignore[attr-defined] + + if ":" in self.host: + authority = f"[{self.host}]:{self.port or 443}" + else: + authority = f"{self.host}:{self.port or 443}" + + self._headers.append((b":scheme", b"https")) + self._headers.append((b":method", method.encode())) + self._headers.append((b":authority", authority.encode())) + self._headers.append((b":path", url.encode())) + + with self._h2_conn as conn: + self._h2_stream = conn.get_next_available_stream_id() + + def putheader(self, header: str | bytes, *values: str | bytes) -> None: # type: ignore[override] + # TODO SKIPPABLE_HEADERS from urllib3 are ignored. + header = header.encode() if isinstance(header, str) else header + header = header.lower() # A lot of upstream code uses capitalized headers. + if not _is_legal_header_name(header): + raise ValueError(f"Illegal header name {str(header)}") + + for value in values: + value = value.encode() if isinstance(value, str) else value + if _is_illegal_header_value(value): + raise ValueError(f"Illegal header value {str(value)}") + self._headers.append((header, value)) + + def endheaders(self, message_body: typing.Any = None) -> None: # type: ignore[override] + if self._h2_stream is None: + raise ConnectionError("Must call `putrequest` first.") + + with self._h2_conn as conn: + conn.send_headers( + stream_id=self._h2_stream, + headers=self._headers, + end_stream=(message_body is None), + ) + if data_to_send := conn.data_to_send(): + self.sock.sendall(data_to_send) + self._headers = [] # Reset headers for the next request. + + def send(self, data: typing.Any) -> None: + """Send data to the server. + `data` can be: `str`, `bytes`, an iterable, or file-like objects + that support a .read() method. + """ + if self._h2_stream is None: + raise ConnectionError("Must call `putrequest` first.") + + with self._h2_conn as conn: + if data_to_send := conn.data_to_send(): + self.sock.sendall(data_to_send) + + if hasattr(data, "read"): # file-like objects + while True: + chunk = data.read(self.blocksize) + if not chunk: + break + if isinstance(chunk, str): + chunk = chunk.encode() + conn.send_data(self._h2_stream, chunk, end_stream=False) + if data_to_send := conn.data_to_send(): + self.sock.sendall(data_to_send) + conn.end_stream(self._h2_stream) + return + + if isinstance(data, str): # str -> bytes + data = data.encode() + + try: + if isinstance(data, bytes): + conn.send_data(self._h2_stream, data, end_stream=True) + if data_to_send := conn.data_to_send(): + self.sock.sendall(data_to_send) + else: + for chunk in data: + conn.send_data(self._h2_stream, chunk, end_stream=False) + if data_to_send := conn.data_to_send(): + self.sock.sendall(data_to_send) + conn.end_stream(self._h2_stream) + except TypeError: + raise TypeError( + "`data` should be str, bytes, iterable, or file. got %r" + % type(data) + ) + + def set_tunnel( + self, + host: str, + port: int | None = None, + headers: typing.Mapping[str, str] | None = None, + scheme: str = "http", + ) -> None: + raise NotImplementedError( + "HTTP/2 does not support setting up a tunnel through a proxy" + ) + + def getresponse( # type: ignore[override] + self, + ) -> HTTP2Response: + status = None + data = bytearray() + with self._h2_conn as conn: + end_stream = False + while not end_stream: + # TODO: Arbitrary read value. + if received_data := self.sock.recv(65535): + events = conn.receive_data(received_data) + for event in events: + if isinstance(event, h2.events.ResponseReceived): + headers = HTTPHeaderDict() + for header, value in event.headers: + if header == b":status": + status = int(value.decode()) + else: + headers.add( + header.decode("ascii"), value.decode("ascii") + ) + + elif isinstance(event, h2.events.DataReceived): + data += event.data + conn.acknowledge_received_data( + event.flow_controlled_length, event.stream_id + ) + + elif isinstance(event, h2.events.StreamEnded): + end_stream = True + + if data_to_send := conn.data_to_send(): + self.sock.sendall(data_to_send) + + assert status is not None + return HTTP2Response( + status=status, + headers=headers, + request_url=self._request_url, + data=bytes(data), + ) + + def request( # type: ignore[override] + self, + method: str, + url: str, + body: _TYPE_BODY | None = None, + headers: typing.Mapping[str, str] | None = None, + *, + preload_content: bool = True, + decode_content: bool = True, + enforce_content_length: bool = True, + **kwargs: typing.Any, + ) -> None: + """Send an HTTP/2 request""" + if "chunked" in kwargs: + # TODO this is often present from upstream. + # raise NotImplementedError("`chunked` isn't supported with HTTP/2") + pass + + if self.sock is not None: + self.sock.settimeout(self.timeout) + + self.putrequest(method, url) + + headers = headers or {} + for k, v in headers.items(): + if k.lower() == "transfer-encoding" and v == "chunked": + continue + else: + self.putheader(k, v) + + if b"user-agent" not in dict(self._headers): + self.putheader(b"user-agent", _get_default_user_agent()) + + if body: + self.endheaders(message_body=body) + self.send(body) + else: + self.endheaders() + + def close(self) -> None: + with self._h2_conn as conn: + try: + conn.close_connection() + if data := conn.data_to_send(): + self.sock.sendall(data) + except Exception: + pass + + # Reset all our HTTP/2 connection state. + self._h2_conn = self._new_h2_conn() + self._h2_stream = None + self._headers = [] + + super().close() + + +class HTTP2Response(BaseHTTPResponse): + # TODO: This is a woefully incomplete response object, but works for non-streaming. + def __init__( + self, + status: int, + headers: HTTPHeaderDict, + request_url: str, + data: bytes, + decode_content: bool = False, # TODO: support decoding + ) -> None: + super().__init__( + status=status, + headers=headers, + # Following CPython, we map HTTP versions to major * 10 + minor integers + version=20, + version_string="HTTP/2", + # No reason phrase in HTTP/2 + reason=None, + decode_content=decode_content, + request_url=request_url, + ) + self._data = data + self.length_remaining = 0 + + @property + def data(self) -> bytes: + return self._data + + def get_redirect_location(self) -> None: + return None + + def close(self) -> None: + pass diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/http2/probe.py b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/http2/probe.py new file mode 100644 index 0000000000000000000000000000000000000000..9ea900764f0885eafaac9454523417d86e33df2d --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/http2/probe.py @@ -0,0 +1,87 @@ +from __future__ import annotations + +import threading + + +class _HTTP2ProbeCache: + __slots__ = ( + "_lock", + "_cache_locks", + "_cache_values", + ) + + def __init__(self) -> None: + self._lock = threading.Lock() + self._cache_locks: dict[tuple[str, int], threading.RLock] = {} + self._cache_values: dict[tuple[str, int], bool | None] = {} + + def acquire_and_get(self, host: str, port: int) -> bool | None: + # By the end of this block we know that + # _cache_[values,locks] is available. + value = None + with self._lock: + key = (host, port) + try: + value = self._cache_values[key] + # If it's a known value we return right away. + if value is not None: + return value + except KeyError: + self._cache_locks[key] = threading.RLock() + self._cache_values[key] = None + + # If the value is unknown, we acquire the lock to signal + # to the requesting thread that the probe is in progress + # or that the current thread needs to return their findings. + key_lock = self._cache_locks[key] + key_lock.acquire() + try: + # If the by the time we get the lock the value has been + # updated we want to return the updated value. + value = self._cache_values[key] + + # In case an exception like KeyboardInterrupt is raised here. + except BaseException as e: # Defensive: + assert not isinstance(e, KeyError) # KeyError shouldn't be possible. + key_lock.release() + raise + + return value + + def set_and_release( + self, host: str, port: int, supports_http2: bool | None + ) -> None: + key = (host, port) + key_lock = self._cache_locks[key] + with key_lock: # Uses an RLock, so can be locked again from same thread. + if supports_http2 is None and self._cache_values[key] is not None: + raise ValueError( + "Cannot reset HTTP/2 support for origin after value has been set." + ) # Defensive: not expected in normal usage + + self._cache_values[key] = supports_http2 + key_lock.release() + + def _values(self) -> dict[tuple[str, int], bool | None]: + """This function is for testing purposes only. Gets the current state of the probe cache""" + with self._lock: + return {k: v for k, v in self._cache_values.items()} + + def _reset(self) -> None: + """This function is for testing purposes only. Reset the cache values""" + with self._lock: + self._cache_locks = {} + self._cache_values = {} + + +_HTTP2_PROBE_CACHE = _HTTP2ProbeCache() + +set_and_release = _HTTP2_PROBE_CACHE.set_and_release +acquire_and_get = _HTTP2_PROBE_CACHE.acquire_and_get +_values = _HTTP2_PROBE_CACHE._values +_reset = _HTTP2_PROBE_CACHE._reset + +__all__ = [ + "set_and_release", + "acquire_and_get", +] diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/poolmanager.py b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/poolmanager.py new file mode 100644 index 0000000000000000000000000000000000000000..8f2c56745cdf5ee206767e22fd61c2fb6bc21e55 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/poolmanager.py @@ -0,0 +1,653 @@ +from __future__ import annotations + +import functools +import logging +import typing +import warnings +from types import TracebackType +from urllib.parse import urljoin + +from ._collections import HTTPHeaderDict, RecentlyUsedContainer +from ._request_methods import RequestMethods +from .connection import ProxyConfig +from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool, port_by_scheme +from .exceptions import ( + LocationValueError, + MaxRetryError, + ProxySchemeUnknown, + URLSchemeUnknown, +) +from .response import BaseHTTPResponse +from .util.connection import _TYPE_SOCKET_OPTIONS +from .util.proxy import connection_requires_http_tunnel +from .util.retry import Retry +from .util.timeout import Timeout +from .util.url import Url, parse_url + +if typing.TYPE_CHECKING: + import ssl + + from typing_extensions import Self + +__all__ = ["PoolManager", "ProxyManager", "proxy_from_url"] + + +log = logging.getLogger(__name__) + +SSL_KEYWORDS = ( + "key_file", + "cert_file", + "cert_reqs", + "ca_certs", + "ca_cert_data", + "ssl_version", + "ssl_minimum_version", + "ssl_maximum_version", + "ca_cert_dir", + "ssl_context", + "key_password", + "server_hostname", +) +# Default value for `blocksize` - a new parameter introduced to +# http.client.HTTPConnection & http.client.HTTPSConnection in Python 3.7 +_DEFAULT_BLOCKSIZE = 16384 + + +class PoolKey(typing.NamedTuple): + """ + All known keyword arguments that could be provided to the pool manager, its + pools, or the underlying connections. + + All custom key schemes should include the fields in this key at a minimum. + """ + + key_scheme: str + key_host: str + key_port: int | None + key_timeout: Timeout | float | int | None + key_retries: Retry | bool | int | None + key_block: bool | None + key_source_address: tuple[str, int] | None + key_key_file: str | None + key_key_password: str | None + key_cert_file: str | None + key_cert_reqs: str | None + key_ca_certs: str | None + key_ca_cert_data: str | bytes | None + key_ssl_version: int | str | None + key_ssl_minimum_version: ssl.TLSVersion | None + key_ssl_maximum_version: ssl.TLSVersion | None + key_ca_cert_dir: str | None + key_ssl_context: ssl.SSLContext | None + key_maxsize: int | None + key_headers: frozenset[tuple[str, str]] | None + key__proxy: Url | None + key__proxy_headers: frozenset[tuple[str, str]] | None + key__proxy_config: ProxyConfig | None + key_socket_options: _TYPE_SOCKET_OPTIONS | None + key__socks_options: frozenset[tuple[str, str]] | None + key_assert_hostname: bool | str | None + key_assert_fingerprint: str | None + key_server_hostname: str | None + key_blocksize: int | None + + +def _default_key_normalizer( + key_class: type[PoolKey], request_context: dict[str, typing.Any] +) -> PoolKey: + """ + Create a pool key out of a request context dictionary. + + According to RFC 3986, both the scheme and host are case-insensitive. + Therefore, this function normalizes both before constructing the pool + key for an HTTPS request. If you wish to change this behaviour, provide + alternate callables to ``key_fn_by_scheme``. + + :param key_class: + The class to use when constructing the key. This should be a namedtuple + with the ``scheme`` and ``host`` keys at a minimum. + :type key_class: namedtuple + :param request_context: + A dictionary-like object that contain the context for a request. + :type request_context: dict + + :return: A namedtuple that can be used as a connection pool key. + :rtype: PoolKey + """ + # Since we mutate the dictionary, make a copy first + context = request_context.copy() + context["scheme"] = context["scheme"].lower() + context["host"] = context["host"].lower() + + # These are both dictionaries and need to be transformed into frozensets + for key in ("headers", "_proxy_headers", "_socks_options"): + if key in context and context[key] is not None: + context[key] = frozenset(context[key].items()) + + # The socket_options key may be a list and needs to be transformed into a + # tuple. + socket_opts = context.get("socket_options") + if socket_opts is not None: + context["socket_options"] = tuple(socket_opts) + + # Map the kwargs to the names in the namedtuple - this is necessary since + # namedtuples can't have fields starting with '_'. + for key in list(context.keys()): + context["key_" + key] = context.pop(key) + + # Default to ``None`` for keys missing from the context + for field in key_class._fields: + if field not in context: + context[field] = None + + # Default key_blocksize to _DEFAULT_BLOCKSIZE if missing from the context + if context.get("key_blocksize") is None: + context["key_blocksize"] = _DEFAULT_BLOCKSIZE + + return key_class(**context) + + +#: A dictionary that maps a scheme to a callable that creates a pool key. +#: This can be used to alter the way pool keys are constructed, if desired. +#: Each PoolManager makes a copy of this dictionary so they can be configured +#: globally here, or individually on the instance. +key_fn_by_scheme = { + "http": functools.partial(_default_key_normalizer, PoolKey), + "https": functools.partial(_default_key_normalizer, PoolKey), +} + +pool_classes_by_scheme = {"http": HTTPConnectionPool, "https": HTTPSConnectionPool} + + +class PoolManager(RequestMethods): + """ + Allows for arbitrary requests while transparently keeping track of + necessary connection pools for you. + + :param num_pools: + Number of connection pools to cache before discarding the least + recently used pool. + + :param headers: + Headers to include with all requests, unless other headers are given + explicitly. + + :param \\**connection_pool_kw: + Additional parameters are used to create fresh + :class:`urllib3.connectionpool.ConnectionPool` instances. + + Example: + + .. code-block:: python + + import urllib3 + + http = urllib3.PoolManager(num_pools=2) + + resp1 = http.request("GET", "https://google.com/") + resp2 = http.request("GET", "https://google.com/mail") + resp3 = http.request("GET", "https://yahoo.com/") + + print(len(http.pools)) + # 2 + + """ + + proxy: Url | None = None + proxy_config: ProxyConfig | None = None + + def __init__( + self, + num_pools: int = 10, + headers: typing.Mapping[str, str] | None = None, + **connection_pool_kw: typing.Any, + ) -> None: + super().__init__(headers) + # PoolManager handles redirects itself in PoolManager.urlopen(). + # It always passes redirect=False to the underlying connection pool to + # suppress per-pool redirect handling. If the user supplied a non-Retry + # value (int/bool/etc) for retries and we let the pool normalize it + # while redirect=False, the resulting Retry object would have redirect + # handling disabled, which can interfere with PoolManager's own + # redirect logic. Normalize here so redirects remain governed solely by + # PoolManager logic. + if "retries" in connection_pool_kw: + retries = connection_pool_kw["retries"] + if not isinstance(retries, Retry): + retries = Retry.from_int(retries) + connection_pool_kw = connection_pool_kw.copy() + connection_pool_kw["retries"] = retries + self.connection_pool_kw = connection_pool_kw + + self.pools: RecentlyUsedContainer[PoolKey, HTTPConnectionPool] + self.pools = RecentlyUsedContainer(num_pools) + + # Locally set the pool classes and keys so other PoolManagers can + # override them. + self.pool_classes_by_scheme = pool_classes_by_scheme + self.key_fn_by_scheme = key_fn_by_scheme.copy() + + def __enter__(self) -> Self: + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, + ) -> typing.Literal[False]: + self.clear() + # Return False to re-raise any potential exceptions + return False + + def _new_pool( + self, + scheme: str, + host: str, + port: int, + request_context: dict[str, typing.Any] | None = None, + ) -> HTTPConnectionPool: + """ + Create a new :class:`urllib3.connectionpool.ConnectionPool` based on host, port, scheme, and + any additional pool keyword arguments. + + If ``request_context`` is provided, it is provided as keyword arguments + to the pool class used. This method is used to actually create the + connection pools handed out by :meth:`connection_from_url` and + companion methods. It is intended to be overridden for customization. + """ + pool_cls: type[HTTPConnectionPool] = self.pool_classes_by_scheme[scheme] + if request_context is None: + request_context = self.connection_pool_kw.copy() + + # Default blocksize to _DEFAULT_BLOCKSIZE if missing or explicitly + # set to 'None' in the request_context. + if request_context.get("blocksize") is None: + request_context["blocksize"] = _DEFAULT_BLOCKSIZE + + # Although the context has everything necessary to create the pool, + # this function has historically only used the scheme, host, and port + # in the positional args. When an API change is acceptable these can + # be removed. + for key in ("scheme", "host", "port"): + request_context.pop(key, None) + + if scheme == "http": + for kw in SSL_KEYWORDS: + request_context.pop(kw, None) + + return pool_cls(host, port, **request_context) + + def clear(self) -> None: + """ + Empty our store of pools and direct them all to close. + + This will not affect in-flight connections, but they will not be + re-used after completion. + """ + self.pools.clear() + + def connection_from_host( + self, + host: str | None, + port: int | None = None, + scheme: str | None = "http", + pool_kwargs: dict[str, typing.Any] | None = None, + ) -> HTTPConnectionPool: + """ + Get a :class:`urllib3.connectionpool.ConnectionPool` based on the host, port, and scheme. + + If ``port`` isn't given, it will be derived from the ``scheme`` using + ``urllib3.connectionpool.port_by_scheme``. If ``pool_kwargs`` is + provided, it is merged with the instance's ``connection_pool_kw`` + variable and used to create the new connection pool, if one is + needed. + """ + + if not host: + raise LocationValueError("No host specified.") + + request_context = self._merge_pool_kwargs(pool_kwargs) + request_context["scheme"] = scheme or "http" + if not port: + port = port_by_scheme.get(request_context["scheme"].lower(), 80) + request_context["port"] = port + request_context["host"] = host + + return self.connection_from_context(request_context) + + def connection_from_context( + self, request_context: dict[str, typing.Any] + ) -> HTTPConnectionPool: + """ + Get a :class:`urllib3.connectionpool.ConnectionPool` based on the request context. + + ``request_context`` must at least contain the ``scheme`` key and its + value must be a key in ``key_fn_by_scheme`` instance variable. + """ + if "strict" in request_context: + warnings.warn( + "The 'strict' parameter is no longer needed on Python 3+. " + "This will raise an error in urllib3 v3.0.", + FutureWarning, + ) + request_context.pop("strict") + + scheme = request_context["scheme"].lower() + pool_key_constructor = self.key_fn_by_scheme.get(scheme) + if not pool_key_constructor: + raise URLSchemeUnknown(scheme) + pool_key = pool_key_constructor(request_context) + + return self.connection_from_pool_key(pool_key, request_context=request_context) + + def connection_from_pool_key( + self, pool_key: PoolKey, request_context: dict[str, typing.Any] + ) -> HTTPConnectionPool: + """ + Get a :class:`urllib3.connectionpool.ConnectionPool` based on the provided pool key. + + ``pool_key`` should be a namedtuple that only contains immutable + objects. At a minimum it must have the ``scheme``, ``host``, and + ``port`` fields. + """ + with self.pools.lock: + # If the scheme, host, or port doesn't match existing open + # connections, open a new ConnectionPool. + pool = self.pools.get(pool_key) + if pool: + return pool + + # Make a fresh ConnectionPool of the desired type + scheme = request_context["scheme"] + host = request_context["host"] + port = request_context["port"] + pool = self._new_pool(scheme, host, port, request_context=request_context) + self.pools[pool_key] = pool + + return pool + + def connection_from_url( + self, url: str, pool_kwargs: dict[str, typing.Any] | None = None + ) -> HTTPConnectionPool: + """ + Similar to :func:`urllib3.connectionpool.connection_from_url`. + + If ``pool_kwargs`` is not provided and a new pool needs to be + constructed, ``self.connection_pool_kw`` is used to initialize + the :class:`urllib3.connectionpool.ConnectionPool`. If ``pool_kwargs`` + is provided, it is used instead. Note that if a new pool does not + need to be created for the request, the provided ``pool_kwargs`` are + not used. + """ + u = parse_url(url) + return self.connection_from_host( + u.host, port=u.port, scheme=u.scheme, pool_kwargs=pool_kwargs + ) + + def _merge_pool_kwargs( + self, override: dict[str, typing.Any] | None + ) -> dict[str, typing.Any]: + """ + Merge a dictionary of override values for self.connection_pool_kw. + + This does not modify self.connection_pool_kw and returns a new dict. + Any keys in the override dictionary with a value of ``None`` are + removed from the merged dictionary. + """ + base_pool_kwargs = self.connection_pool_kw.copy() + if override: + for key, value in override.items(): + if value is None: + try: + del base_pool_kwargs[key] + except KeyError: + pass + else: + base_pool_kwargs[key] = value + return base_pool_kwargs + + def _proxy_requires_url_absolute_form(self, parsed_url: Url) -> bool: + """ + Indicates if the proxy requires the complete destination URL in the + request. Normally this is only needed when not using an HTTP CONNECT + tunnel. + """ + if self.proxy is None: + return False + + return not connection_requires_http_tunnel( + self.proxy, self.proxy_config, parsed_url.scheme + ) + + def urlopen( # type: ignore[override] + self, method: str, url: str, redirect: bool = True, **kw: typing.Any + ) -> BaseHTTPResponse: + """ + Same as :meth:`urllib3.HTTPConnectionPool.urlopen` + with custom cross-host redirect logic and only sends the request-uri + portion of the ``url``. + + The given ``url`` parameter must be absolute, such that an appropriate + :class:`urllib3.connectionpool.ConnectionPool` can be chosen for it. + """ + u = parse_url(url) + + if u.scheme is None: + warnings.warn( + "URLs without a scheme (ie 'https://') are deprecated and will raise an error " + "in urllib3 v3.0. To avoid this FutureWarning ensure all URLs " + "start with 'https://' or 'http://'. Read more in this issue: " + "https://github.com/urllib3/urllib3/issues/2920", + category=FutureWarning, + stacklevel=2, + ) + + conn = self.connection_from_host(u.host, port=u.port, scheme=u.scheme) + + kw["assert_same_host"] = False + kw["redirect"] = False + + if "headers" not in kw: + kw["headers"] = self.headers + + if self._proxy_requires_url_absolute_form(u): + response = conn.urlopen(method, url, **kw) + else: + response = conn.urlopen(method, u.request_uri, **kw) + + redirect_location = redirect and response.get_redirect_location() + if not redirect_location: + return response + + # Support relative URLs for redirecting. + redirect_location = urljoin(url, redirect_location) + + if response.status == 303: + # Change the method according to RFC 9110, Section 15.4.4. + method = "GET" + # And lose the body not to transfer anything sensitive. + kw["body"] = None + kw["headers"] = HTTPHeaderDict(kw["headers"])._prepare_for_method_change() + + retries = kw.get("retries", response.retries) + if not isinstance(retries, Retry): + retries = Retry.from_int(retries, redirect=redirect) + + # Strip headers marked as unsafe to forward to the redirected location. + # Check remove_headers_on_redirect to avoid a potential network call within + # conn.is_same_host() which may use socket.gethostbyname() in the future. + if retries.remove_headers_on_redirect and not conn.is_same_host( + redirect_location + ): + new_headers = kw["headers"].copy() + for header in kw["headers"]: + if header.lower() in retries.remove_headers_on_redirect: + new_headers.pop(header, None) + kw["headers"] = new_headers + + try: + retries = retries.increment(method, url, response=response, _pool=conn) + except MaxRetryError: + if retries.raise_on_redirect: + response.drain_conn() + raise + return response + + kw["retries"] = retries + kw["redirect"] = redirect + + log.info("Redirecting %s -> %s", url, redirect_location) + + response.drain_conn() + return self.urlopen(method, redirect_location, **kw) + + +class ProxyManager(PoolManager): + """ + Behaves just like :class:`PoolManager`, but sends all requests through + the defined proxy, using the CONNECT method for HTTPS URLs. + + :param proxy_url: + The URL of the proxy to be used. + + :param proxy_headers: + A dictionary containing headers that will be sent to the proxy. In case + of HTTP they are being sent with each request, while in the + HTTPS/CONNECT case they are sent only once. Could be used for proxy + authentication. + + :param proxy_ssl_context: + The proxy SSL context is used to establish the TLS connection to the + proxy when using HTTPS proxies. + + :param use_forwarding_for_https: + (Defaults to False) If set to True will forward requests to the HTTPS + proxy to be made on behalf of the client instead of creating a TLS + tunnel via the CONNECT method. **Enabling this flag means that request + and response headers and content will be visible from the HTTPS proxy** + whereas tunneling keeps request and response headers and content + private. IP address, target hostname, SNI, and port are always visible + to an HTTPS proxy even when this flag is disabled. + + :param proxy_assert_hostname: + The hostname of the certificate to verify against. + + :param proxy_assert_fingerprint: + The fingerprint of the certificate to verify against. + + Example: + + .. code-block:: python + + import urllib3 + + proxy = urllib3.ProxyManager("https://localhost:3128/") + + resp1 = proxy.request("GET", "http://google.com/") + resp2 = proxy.request("GET", "http://httpbin.org/") + + # One pool was shared by both plain HTTP requests. + print(len(proxy.pools)) + # 1 + + resp3 = proxy.request("GET", "https://httpbin.org/") + resp4 = proxy.request("GET", "https://twitter.com/") + + # A separate pool was added for each HTTPS target. + print(len(proxy.pools)) + # 3 + + """ + + def __init__( + self, + proxy_url: str, + num_pools: int = 10, + headers: typing.Mapping[str, str] | None = None, + proxy_headers: typing.Mapping[str, str] | None = None, + proxy_ssl_context: ssl.SSLContext | None = None, + use_forwarding_for_https: bool = False, + proxy_assert_hostname: None | str | typing.Literal[False] = None, + proxy_assert_fingerprint: str | None = None, + **connection_pool_kw: typing.Any, + ) -> None: + if isinstance(proxy_url, HTTPConnectionPool): + str_proxy_url = f"{proxy_url.scheme}://{proxy_url.host}:{proxy_url.port}" + else: + str_proxy_url = proxy_url + proxy = parse_url(str_proxy_url) + + if proxy.scheme not in ("http", "https"): + raise ProxySchemeUnknown(proxy.scheme) + + if not proxy.port: + port = port_by_scheme.get(proxy.scheme, 80) + proxy = proxy._replace(port=port) + + self.proxy = proxy + self.proxy_headers = proxy_headers or {} + self.proxy_ssl_context = proxy_ssl_context + self.proxy_config = ProxyConfig( + proxy_ssl_context, + use_forwarding_for_https, + proxy_assert_hostname, + proxy_assert_fingerprint, + ) + + connection_pool_kw["_proxy"] = self.proxy + connection_pool_kw["_proxy_headers"] = self.proxy_headers + connection_pool_kw["_proxy_config"] = self.proxy_config + + super().__init__(num_pools, headers, **connection_pool_kw) + + def connection_from_host( + self, + host: str | None, + port: int | None = None, + scheme: str | None = "http", + pool_kwargs: dict[str, typing.Any] | None = None, + ) -> HTTPConnectionPool: + if scheme == "https": + return super().connection_from_host( + host, port, scheme, pool_kwargs=pool_kwargs + ) + + return super().connection_from_host( + self.proxy.host, self.proxy.port, self.proxy.scheme, pool_kwargs=pool_kwargs # type: ignore[union-attr] + ) + + def _set_proxy_headers( + self, url: str, headers: typing.Mapping[str, str] | None = None + ) -> typing.Mapping[str, str]: + """ + Sets headers needed by proxies: specifically, the Accept and Host + headers. Only sets headers not provided by the user. + """ + headers_ = {"Accept": "*/*"} + + netloc = parse_url(url).netloc + if netloc: + headers_["Host"] = netloc + + if headers: + headers_.update(headers) + return headers_ + + def urlopen( # type: ignore[override] + self, method: str, url: str, redirect: bool = True, **kw: typing.Any + ) -> BaseHTTPResponse: + "Same as HTTP(S)ConnectionPool.urlopen, ``url`` must be absolute." + u = parse_url(url) + if not connection_requires_http_tunnel(self.proxy, self.proxy_config, u.scheme): + # For connections using HTTP CONNECT, httplib sets the necessary + # headers on the CONNECT to the proxy. If we're not using CONNECT, + # we'll definitely need to set 'Host' at the very least. + headers = kw.get("headers", self.headers) + kw["headers"] = self._set_proxy_headers(url, headers) + + return super().urlopen(method, url, redirect=redirect, **kw) + + +def proxy_from_url(url: str, **kw: typing.Any) -> ProxyManager: + return ProxyManager(proxy_url=url, **kw) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/py.typed b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/py.typed new file mode 100644 index 0000000000000000000000000000000000000000..5f3ea3d919363f08ab03edbc85b6099bc4df5647 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/py.typed @@ -0,0 +1,2 @@ +# Instruct type checkers to look for inline type annotations in this package. +# See PEP 561. diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/response.py b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/response.py new file mode 100644 index 0000000000000000000000000000000000000000..e9246b75e36215b7f956700aa4cb363e8423e526 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/response.py @@ -0,0 +1,1493 @@ +from __future__ import annotations + +import collections +import io +import json as _json +import logging +import socket +import sys +import typing +import warnings +import zlib +from contextlib import contextmanager +from http.client import HTTPMessage as _HttplibHTTPMessage +from http.client import HTTPResponse as _HttplibHTTPResponse +from socket import timeout as SocketTimeout + +if typing.TYPE_CHECKING: + from ._base_connection import BaseHTTPConnection + +try: + try: + import brotlicffi as brotli # type: ignore[import-not-found] + except ImportError: + import brotli # type: ignore[import-not-found] +except ImportError: + brotli = None + +from . import util +from ._base_connection import _TYPE_BODY +from ._collections import HTTPHeaderDict +from .connection import BaseSSLError, HTTPConnection, HTTPException +from .exceptions import ( + BodyNotHttplibCompatible, + DecodeError, + DependencyWarning, + HTTPError, + IncompleteRead, + InvalidChunkLength, + InvalidHeader, + ProtocolError, + ReadTimeoutError, + ResponseNotChunked, + SSLError, +) +from .util.response import is_fp_closed, is_response_to_head +from .util.retry import Retry + +if typing.TYPE_CHECKING: + from .connectionpool import HTTPConnectionPool + +log = logging.getLogger(__name__) + + +class ContentDecoder: + def decompress(self, data: bytes, max_length: int = -1) -> bytes: + raise NotImplementedError() + + @property + def has_unconsumed_tail(self) -> bool: + raise NotImplementedError() + + def flush(self) -> bytes: + raise NotImplementedError() + + +class DeflateDecoder(ContentDecoder): + def __init__(self) -> None: + self._first_try = True + self._first_try_data = b"" + self._unfed_data = b"" + self._obj = zlib.decompressobj() + + def decompress(self, data: bytes, max_length: int = -1) -> bytes: + data = self._unfed_data + data + self._unfed_data = b"" + if not data and not self._obj.unconsumed_tail: + return data + original_max_length = max_length + if original_max_length < 0: + max_length = 0 + elif original_max_length == 0: + # We should not pass 0 to the zlib decompressor because 0 is + # the default value that will make zlib decompress without a + # length limit. + # Data should be stored for subsequent calls. + self._unfed_data = data + return b"" + + # Subsequent calls always reuse `self._obj`. zlib requires + # passing the unconsumed tail if decompression is to continue. + if not self._first_try: + return self._obj.decompress( + self._obj.unconsumed_tail + data, max_length=max_length + ) + + # First call tries with RFC 1950 ZLIB format. + self._first_try_data += data + try: + decompressed = self._obj.decompress(data, max_length=max_length) + if decompressed: + self._first_try = False + self._first_try_data = b"" + return decompressed + # On failure, it falls back to RFC 1951 DEFLATE format. + except zlib.error: + self._first_try = False + self._obj = zlib.decompressobj(-zlib.MAX_WBITS) + try: + return self.decompress( + self._first_try_data, max_length=original_max_length + ) + finally: + self._first_try_data = b"" + + @property + def has_unconsumed_tail(self) -> bool: + return bool(self._unfed_data) or ( + bool(self._obj.unconsumed_tail) and not self._first_try + ) + + def flush(self) -> bytes: + return self._obj.flush() + + +class GzipDecoderState: + FIRST_MEMBER = 0 + OTHER_MEMBERS = 1 + SWALLOW_DATA = 2 + + +class GzipDecoder(ContentDecoder): + def __init__(self) -> None: + self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) + self._state = GzipDecoderState.FIRST_MEMBER + self._unconsumed_tail = b"" + + def decompress(self, data: bytes, max_length: int = -1) -> bytes: + ret = bytearray() + if self._state == GzipDecoderState.SWALLOW_DATA: + return bytes(ret) + + if max_length == 0: + # We should not pass 0 to the zlib decompressor because 0 is + # the default value that will make zlib decompress without a + # length limit. + # Data should be stored for subsequent calls. + self._unconsumed_tail += data + return b"" + + # zlib requires passing the unconsumed tail to the subsequent + # call if decompression is to continue. + data = self._unconsumed_tail + data + if not data and self._obj.eof: + return bytes(ret) + + while True: + try: + ret += self._obj.decompress( + data, max_length=max(max_length - len(ret), 0) + ) + except zlib.error: + previous_state = self._state + # Ignore data after the first error + self._state = GzipDecoderState.SWALLOW_DATA + self._unconsumed_tail = b"" + if previous_state == GzipDecoderState.OTHER_MEMBERS: + # Allow trailing garbage acceptable in other gzip clients + return bytes(ret) + raise + + self._unconsumed_tail = data = ( + self._obj.unconsumed_tail or self._obj.unused_data + ) + if max_length > 0 and len(ret) >= max_length: + break + + if not data: + return bytes(ret) + # When the end of a gzip member is reached, a new decompressor + # must be created for unused (possibly future) data. + if self._obj.eof: + self._state = GzipDecoderState.OTHER_MEMBERS + self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS) + + return bytes(ret) + + @property + def has_unconsumed_tail(self) -> bool: + return bool(self._unconsumed_tail) + + def flush(self) -> bytes: + return self._obj.flush() + + +if brotli is not None: + + class BrotliDecoder(ContentDecoder): + # Supports both 'brotlipy' and 'Brotli' packages + # since they share an import name. The top branches + # are for 'brotlipy' and bottom branches for 'Brotli' + def __init__(self) -> None: + self._obj = brotli.Decompressor() + if hasattr(self._obj, "decompress"): + setattr(self, "_decompress", self._obj.decompress) + else: + setattr(self, "_decompress", self._obj.process) + + # Requires Brotli >= 1.2.0 for `output_buffer_limit`. + def _decompress(self, data: bytes, output_buffer_limit: int = -1) -> bytes: + raise NotImplementedError() + + def decompress(self, data: bytes, max_length: int = -1) -> bytes: + try: + if max_length > 0: + return self._decompress(data, output_buffer_limit=max_length) + else: + return self._decompress(data) + except TypeError: + # Fallback for Brotli/brotlicffi/brotlipy versions without + # the `output_buffer_limit` parameter. + warnings.warn( + "Brotli >= 1.2.0 is required to prevent decompression bombs.", + DependencyWarning, + ) + return self._decompress(data) + + @property + def has_unconsumed_tail(self) -> bool: + try: + return not self._obj.can_accept_more_data() + except AttributeError: + return False + + def flush(self) -> bytes: + if hasattr(self._obj, "flush"): + return self._obj.flush() # type: ignore[no-any-return] + return b"" + + +try: + if sys.version_info >= (3, 14): + from compression import zstd + else: + from backports import zstd +except ImportError: + HAS_ZSTD = False +else: + HAS_ZSTD = True + + class ZstdDecoder(ContentDecoder): + def __init__(self) -> None: + self._obj = zstd.ZstdDecompressor() + + def decompress(self, data: bytes, max_length: int = -1) -> bytes: + if not data and not self.has_unconsumed_tail: + return b"" + if self._obj.eof: + data = self._obj.unused_data + data + self._obj = zstd.ZstdDecompressor() + part = self._obj.decompress(data, max_length=max_length) + length = len(part) + data_parts = [part] + # Every loop iteration is supposed to read data from a separate frame. + # The loop breaks when: + # - enough data is read; + # - no more unused data is available; + # - end of the last read frame has not been reached (i.e., + # more data has to be fed). + while ( + self._obj.eof + and self._obj.unused_data + and (max_length < 0 or length < max_length) + ): + unused_data = self._obj.unused_data + if not self._obj.needs_input: + self._obj = zstd.ZstdDecompressor() + part = self._obj.decompress( + unused_data, + max_length=(max_length - length) if max_length > 0 else -1, + ) + if part_length := len(part): + data_parts.append(part) + length += part_length + elif self._obj.needs_input: + break + return b"".join(data_parts) + + @property + def has_unconsumed_tail(self) -> bool: + return not (self._obj.needs_input or self._obj.eof) or bool( + self._obj.unused_data + ) + + def flush(self) -> bytes: + if not self._obj.eof: + raise DecodeError("Zstandard data is incomplete") + return b"" + + +class MultiDecoder(ContentDecoder): + """ + From RFC7231: + If one or more encodings have been applied to a representation, the + sender that applied the encodings MUST generate a Content-Encoding + header field that lists the content codings in the order in which + they were applied. + """ + + # Maximum allowed number of chained HTTP encodings in the + # Content-Encoding header. + max_decode_links = 5 + + def __init__(self, modes: str) -> None: + encodings = [m.strip() for m in modes.split(",")] + if len(encodings) > self.max_decode_links: + raise DecodeError( + "Too many content encodings in the chain: " + f"{len(encodings)} > {self.max_decode_links}" + ) + self._decoders = [_get_decoder(e) for e in encodings] + + def flush(self) -> bytes: + return self._decoders[0].flush() + + def decompress(self, data: bytes, max_length: int = -1) -> bytes: + if max_length <= 0: + for d in reversed(self._decoders): + data = d.decompress(data) + return data + + ret = bytearray() + # Every while loop iteration goes through all decoders once. + # It exits when enough data is read or no more data can be read. + # It is possible that the while loop iteration does not produce + # any data because we retrieve up to `max_length` from every + # decoder, and the amount of bytes may be insufficient for the + # next decoder to produce enough/any output. + while True: + any_data = False + for d in reversed(self._decoders): + data = d.decompress(data, max_length=max_length - len(ret)) + if data: + any_data = True + # We should not break when no data is returned because + # next decoders may produce data even with empty input. + ret += data + if not any_data or len(ret) >= max_length: + return bytes(ret) + data = b"" + + @property + def has_unconsumed_tail(self) -> bool: + return any(d.has_unconsumed_tail for d in self._decoders) + + +def _get_decoder(mode: str) -> ContentDecoder: + if "," in mode: + return MultiDecoder(mode) + + # According to RFC 9110 section 8.4.1.3, recipients should + # consider x-gzip equivalent to gzip + if mode in ("gzip", "x-gzip"): + return GzipDecoder() + + if brotli is not None and mode == "br": + return BrotliDecoder() + + if HAS_ZSTD and mode == "zstd": + return ZstdDecoder() + + return DeflateDecoder() + + +class BytesQueueBuffer: + """Memory-efficient bytes buffer + + To return decoded data in read() and still follow the BufferedIOBase API, we need a + buffer to always return the correct amount of bytes. + + This buffer should be filled using calls to put() + + Our maximum memory usage is determined by the sum of the size of: + + * self.buffer, which contains the full data + * the largest chunk that we will copy in get() + """ + + def __init__(self) -> None: + self.buffer: typing.Deque[bytes | memoryview[bytes]] = collections.deque() + self._size: int = 0 + + def __len__(self) -> int: + return self._size + + def put(self, data: bytes) -> None: + self.buffer.append(data) + self._size += len(data) + + def get(self, n: int) -> bytes: + if n == 0: + return b"" + elif not self.buffer: + raise RuntimeError("buffer is empty") + elif n < 0: + raise ValueError("n should be > 0") + + if len(self.buffer[0]) == n and isinstance(self.buffer[0], bytes): + self._size -= n + return self.buffer.popleft() + + fetched = 0 + ret = io.BytesIO() + while fetched < n: + remaining = n - fetched + chunk = self.buffer.popleft() + chunk_length = len(chunk) + if remaining < chunk_length: + chunk = memoryview(chunk) + left_chunk, right_chunk = chunk[:remaining], chunk[remaining:] + ret.write(left_chunk) + self.buffer.appendleft(right_chunk) + self._size -= remaining + break + else: + ret.write(chunk) + self._size -= chunk_length + fetched += chunk_length + + if not self.buffer: + break + + return ret.getvalue() + + def get_all(self) -> bytes: + buffer = self.buffer + if not buffer: + assert self._size == 0 + return b"" + if len(buffer) == 1: + result = buffer.pop() + if isinstance(result, memoryview): + result = result.tobytes() + else: + ret = io.BytesIO() + ret.writelines(buffer.popleft() for _ in range(len(buffer))) + result = ret.getvalue() + self._size = 0 + return result + + +class BaseHTTPResponse(io.IOBase): + CONTENT_DECODERS = ["gzip", "x-gzip", "deflate"] + if brotli is not None: + CONTENT_DECODERS += ["br"] + if HAS_ZSTD: + CONTENT_DECODERS += ["zstd"] + REDIRECT_STATUSES = [301, 302, 303, 307, 308] + + DECODER_ERROR_CLASSES: tuple[type[Exception], ...] = (IOError, zlib.error) + if brotli is not None: + DECODER_ERROR_CLASSES += (brotli.error,) + + if HAS_ZSTD: + DECODER_ERROR_CLASSES += (zstd.ZstdError,) + + def __init__( + self, + *, + headers: typing.Mapping[str, str] | typing.Mapping[bytes, bytes] | None = None, + status: int, + version: int, + version_string: str, + reason: str | None, + decode_content: bool, + request_url: str | None, + retries: Retry | None = None, + ) -> None: + if isinstance(headers, HTTPHeaderDict): + self.headers = headers + else: + self.headers = HTTPHeaderDict(headers) # type: ignore[arg-type] + self.status = status + self.version = version + self.version_string = version_string + self.reason = reason + self.decode_content = decode_content + self._has_decoded_content = False + self._request_url: str | None = request_url + self.retries = retries + + self.chunked = False + tr_enc = self.headers.get("transfer-encoding", "").lower() + # Don't incur the penalty of creating a list and then discarding it + encodings = (enc.strip() for enc in tr_enc.split(",")) + if "chunked" in encodings: + self.chunked = True + + self._decoder: ContentDecoder | None = None + self.length_remaining: int | None + + def get_redirect_location(self) -> str | None | typing.Literal[False]: + """ + Should we redirect and where to? + + :returns: Truthy redirect location string if we got a redirect status + code and valid location. ``None`` if redirect status and no + location. ``False`` if not a redirect status code. + """ + if self.status in self.REDIRECT_STATUSES: + return self.headers.get("location") + return False + + @property + def data(self) -> bytes: + raise NotImplementedError() + + def json(self) -> typing.Any: + """ + Deserializes the body of the HTTP response as a Python object. + + The body of the HTTP response must be encoded using UTF-8, as per + `RFC 8529 Section 8.1 `_. + + To use a custom JSON decoder pass the result of :attr:`HTTPResponse.data` to + your custom decoder instead. + + If the body of the HTTP response is not decodable to UTF-8, a + `UnicodeDecodeError` will be raised. If the body of the HTTP response is not a + valid JSON document, a `json.JSONDecodeError` will be raised. + + Read more :ref:`here `. + + :returns: The body of the HTTP response as a Python object. + """ + data = self.data.decode("utf-8") + return _json.loads(data) + + @property + def url(self) -> str | None: + raise NotImplementedError() + + @url.setter + def url(self, url: str | None) -> None: + raise NotImplementedError() + + @property + def connection(self) -> BaseHTTPConnection | None: + raise NotImplementedError() + + @property + def retries(self) -> Retry | None: + return self._retries + + @retries.setter + def retries(self, retries: Retry | None) -> None: + # Override the request_url if retries has a redirect location. + if retries is not None and retries.history: + self.url = retries.history[-1].redirect_location + self._retries = retries + + def stream( + self, amt: int | None = 2**16, decode_content: bool | None = None + ) -> typing.Iterator[bytes]: + raise NotImplementedError() + + def read( + self, + amt: int | None = None, + decode_content: bool | None = None, + cache_content: bool = False, + ) -> bytes: + raise NotImplementedError() + + def read1( + self, + amt: int | None = None, + decode_content: bool | None = None, + ) -> bytes: + raise NotImplementedError() + + def read_chunked( + self, + amt: int | None = None, + decode_content: bool | None = None, + ) -> typing.Iterator[bytes]: + raise NotImplementedError() + + def release_conn(self) -> None: + raise NotImplementedError() + + def drain_conn(self) -> None: + raise NotImplementedError() + + def shutdown(self) -> None: + raise NotImplementedError() + + def close(self) -> None: + raise NotImplementedError() + + def _init_decoder(self) -> None: + """ + Set-up the _decoder attribute if necessary. + """ + # Note: content-encoding value should be case-insensitive, per RFC 7230 + # Section 3.2 + content_encoding = self.headers.get("content-encoding", "").lower() + if self._decoder is None: + if content_encoding in self.CONTENT_DECODERS: + self._decoder = _get_decoder(content_encoding) + elif "," in content_encoding: + encodings = [ + e.strip() + for e in content_encoding.split(",") + if e.strip() in self.CONTENT_DECODERS + ] + if encodings: + self._decoder = _get_decoder(content_encoding) + + def _decode( + self, + data: bytes, + decode_content: bool | None, + flush_decoder: bool, + max_length: int | None = None, + ) -> bytes: + """ + Decode the data passed in and potentially flush the decoder. + """ + if not decode_content: + if self._has_decoded_content: + raise RuntimeError( + "Calling read(decode_content=False) is not supported after " + "read(decode_content=True) was called." + ) + return data + + if max_length is None or flush_decoder: + max_length = -1 + + try: + if self._decoder: + data = self._decoder.decompress(data, max_length=max_length) + self._has_decoded_content = True + except self.DECODER_ERROR_CLASSES as e: + content_encoding = self.headers.get("content-encoding", "").lower() + raise DecodeError( + "Received response with content-encoding: %s, but " + "failed to decode it." % content_encoding, + e, + ) from e + if flush_decoder: + data += self._flush_decoder() + + return data + + def _flush_decoder(self) -> bytes: + """ + Flushes the decoder. Should only be called if the decoder is actually + being used. + """ + if self._decoder: + return self._decoder.decompress(b"") + self._decoder.flush() + return b"" + + # Compatibility methods for `io` module + def readinto(self, b: bytearray | memoryview[int]) -> int: + temp = self.read(len(b)) + if len(temp) == 0: + return 0 + else: + b[: len(temp)] = temp + return len(temp) + + # Methods used by dependent libraries + def getheaders(self) -> HTTPHeaderDict: + return self.headers + + def getheader(self, name: str, default: str | None = None) -> str | None: + return self.headers.get(name, default) + + # Compatibility method for http.cookiejar + def info(self) -> HTTPHeaderDict: + return self.headers + + def geturl(self) -> str | None: + return self.url + + +class HTTPResponse(BaseHTTPResponse): + """ + HTTP Response container. + + Backwards-compatible with :class:`http.client.HTTPResponse` but the response ``body`` is + loaded and decoded on-demand when the ``data`` property is accessed. This + class is also compatible with the Python standard library's :mod:`io` + module, and can hence be treated as a readable object in the context of that + framework. + + Extra parameters for behaviour not present in :class:`http.client.HTTPResponse`: + + :param preload_content: + If True, the response's body will be preloaded during construction. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + + :param original_response: + When this HTTPResponse wrapper is generated from an :class:`http.client.HTTPResponse` + object, it's convenient to include the original for debug purposes. It's + otherwise unused. + + :param retries: + The retries contains the last :class:`~urllib3.util.retry.Retry` that + was used during the request. + + :param enforce_content_length: + Enforce content length checking. Body returned by server must match + value of Content-Length header, if present. Otherwise, raise error. + """ + + def __init__( + self, + body: _TYPE_BODY = "", + headers: typing.Mapping[str, str] | typing.Mapping[bytes, bytes] | None = None, + status: int = 0, + version: int = 0, + version_string: str = "HTTP/?", + reason: str | None = None, + preload_content: bool = True, + decode_content: bool = True, + original_response: _HttplibHTTPResponse | None = None, + pool: HTTPConnectionPool | None = None, + connection: HTTPConnection | None = None, + msg: _HttplibHTTPMessage | None = None, + retries: Retry | None = None, + enforce_content_length: bool = True, + request_method: str | None = None, + request_url: str | None = None, + auto_close: bool = True, + sock_shutdown: typing.Callable[[int], None] | None = None, + ) -> None: + super().__init__( + headers=headers, + status=status, + version=version, + version_string=version_string, + reason=reason, + decode_content=decode_content, + request_url=request_url, + retries=retries, + ) + + self.enforce_content_length = enforce_content_length + self.auto_close = auto_close + + self._body = None + self._uncached_read_occurred = False + self._fp: _HttplibHTTPResponse | None = None + self._original_response = original_response + self._fp_bytes_read = 0 + self.msg = msg + + if body and isinstance(body, (str, bytes)): + self._body = body + + self._pool = pool + self._connection = connection + + if hasattr(body, "read"): + self._fp = body # type: ignore[assignment] + self._sock_shutdown = sock_shutdown + + # Are we using the chunked-style of transfer encoding? + self.chunk_left: int | None = None + + # Determine length of response + self.length_remaining = self._init_length(request_method) + + # Used to return the correct amount of bytes for partial read()s + self._decoded_buffer = BytesQueueBuffer() + + # If requested, preload the body. + if preload_content and not self._body: + self._body = self.read(decode_content=decode_content) + + def release_conn(self) -> None: + if not self._pool or not self._connection: + return None + + self._pool._put_conn(self._connection) + self._connection = None + + def drain_conn(self) -> None: + """ + Read and discard any remaining HTTP response data in the response connection. + + Unread data in the HTTPResponse connection blocks the connection from being released back to the pool. + """ + try: + self._raw_read() + except (HTTPError, OSError, BaseSSLError, HTTPException): + pass + if self._has_decoded_content: + # `_raw_read` skips decompression, so we should clean up the + # decoder to avoid keeping unnecessary data in memory. + self._decoded_buffer = BytesQueueBuffer() + self._decoder = None + + @property + def data(self) -> bytes: + # For backwards-compat with earlier urllib3 0.4 and earlier. + if self._body: + return self._body # type: ignore[return-value] + + if self._fp: + return self.read(cache_content=True) + + return None # type: ignore[return-value] + + @property + def connection(self) -> HTTPConnection | None: + return self._connection + + def isclosed(self) -> bool: + return is_fp_closed(self._fp) + + def tell(self) -> int: + """ + Obtain the number of bytes pulled over the wire so far. May differ from + the amount of content returned by :meth:`HTTPResponse.read` + if bytes are encoded on the wire (e.g, compressed). + """ + return self._fp_bytes_read + + def _init_length(self, request_method: str | None) -> int | None: + """ + Set initial length value for Response content if available. + """ + length: int | None + content_length: str | None = self.headers.get("content-length") + + if content_length is not None: + if self.chunked: + # This Response will fail with an IncompleteRead if it can't be + # received as chunked. This method falls back to attempt reading + # the response before raising an exception. + log.warning( + "Received response with both Content-Length and " + "Transfer-Encoding set. This is expressly forbidden " + "by RFC 7230 sec 3.3.2. Ignoring Content-Length and " + "attempting to process response as Transfer-Encoding: " + "chunked." + ) + return None + + try: + # RFC 7230 section 3.3.2 specifies multiple content lengths can + # be sent in a single Content-Length header + # (e.g. Content-Length: 42, 42). This line ensures the values + # are all valid ints and that as long as the `set` length is 1, + # all values are the same. Otherwise, the header is invalid. + lengths = {int(val) for val in content_length.split(",")} + if len(lengths) > 1: + raise InvalidHeader( + "Content-Length contained multiple " + "unmatching values (%s)" % content_length + ) + length = lengths.pop() + except ValueError: + length = None + else: + if length < 0: + length = None + + else: # if content_length is None + length = None + + # Convert status to int for comparison + # In some cases, httplib returns a status of "_UNKNOWN" + try: + status = int(self.status) + except ValueError: + status = 0 + + # Check for responses that shouldn't include a body + if status in (204, 304) or 100 <= status < 200 or request_method == "HEAD": + length = 0 + + return length + + @contextmanager + def _error_catcher(self) -> typing.Generator[None]: + """ + Catch low-level python exceptions, instead re-raising urllib3 + variants, so that low-level exceptions are not leaked in the + high-level api. + + On exit, release the connection back to the pool. + """ + clean_exit = False + + try: + try: + yield + + except SocketTimeout as e: + # FIXME: Ideally we'd like to include the url in the ReadTimeoutError but + # there is yet no clean way to get at it from this context. + raise ReadTimeoutError(self._pool, None, "Read timed out.") from e # type: ignore[arg-type] + + except BaseSSLError as e: + # SSL errors related to framing/MAC get wrapped and reraised here + raise SSLError(e) from e + + except IncompleteRead as e: + if ( + e.expected is not None + and e.partial is not None + and e.expected == -e.partial + ): + arg = "Response may not contain content." + else: + arg = f"Connection broken: {e!r}" + raise ProtocolError(arg, e) from e + + except (HTTPException, OSError) as e: + raise ProtocolError(f"Connection broken: {e!r}", e) from e + + # If no exception is thrown, we should avoid cleaning up + # unnecessarily. + clean_exit = True + finally: + # If we didn't terminate cleanly, we need to throw away our + # connection. + if not clean_exit: + # The response may not be closed but we're not going to use it + # anymore so close it now to ensure that the connection is + # released back to the pool. + if self._original_response: + self._original_response.close() + + # Closing the response may not actually be sufficient to close + # everything, so if we have a hold of the connection close that + # too. + if self._connection: + self._connection.close() + + # If we hold the original response but it's closed now, we should + # return the connection back to the pool. + if self._original_response and self._original_response.isclosed(): + self.release_conn() + + def _fp_read( + self, + amt: int | None = None, + *, + read1: bool = False, + ) -> bytes: + """ + Read a response with the thought that reading the number of bytes + larger than can fit in a 32-bit int at a time via SSL in some + known cases leads to an overflow error that has to be prevented + if `amt` or `self.length_remaining` indicate that a problem may + happen. + + This happens to urllib3 injected with pyOpenSSL-backed SSL-support. + """ + assert self._fp + c_int_max = 2**31 - 1 + if ( + (amt and amt > c_int_max) + or ( + amt is None + and self.length_remaining + and self.length_remaining > c_int_max + ) + ) and util.IS_PYOPENSSL: + if read1: + return self._fp.read1(c_int_max) + buffer = io.BytesIO() + # Besides `max_chunk_amt` being a maximum chunk size, it + # affects memory overhead of reading a response by this + # method in CPython. + # `c_int_max` equal to 2 GiB - 1 byte is the actual maximum + # chunk size that does not lead to an overflow error, but + # 256 MiB is a compromise. + max_chunk_amt = 2**28 + while amt is None or amt != 0: + if amt is not None: + chunk_amt = min(amt, max_chunk_amt) + amt -= chunk_amt + else: + chunk_amt = max_chunk_amt + data = self._fp.read(chunk_amt) + if not data: + break + buffer.write(data) + del data # to reduce peak memory usage by `max_chunk_amt`. + return buffer.getvalue() + elif read1: + return self._fp.read1(amt) if amt is not None else self._fp.read1() + else: + # StringIO doesn't like amt=None + return self._fp.read(amt) if amt is not None else self._fp.read() + + def _raw_read( + self, + amt: int | None = None, + *, + read1: bool = False, + ) -> bytes: + """ + Reads `amt` of bytes from the socket. + """ + if self._fp is None: + return None # type: ignore[return-value] + + fp_closed = getattr(self._fp, "closed", False) + + with self._error_catcher(): + data = self._fp_read(amt, read1=read1) if not fp_closed else b"" + if amt is not None and amt != 0 and not data: + # Platform-specific: Buggy versions of Python. + # Close the connection when no data is returned + # + # This is redundant to what httplib/http.client _should_ + # already do. However, versions of python released before + # December 15, 2012 (http://bugs.python.org/issue16298) do + # not properly close the connection in all cases. There is + # no harm in redundantly calling close. + self._fp.close() + if ( + self.enforce_content_length + and self.length_remaining is not None + and self.length_remaining != 0 + ): + # This is an edge case that httplib failed to cover due + # to concerns of backward compatibility. We're + # addressing it here to make sure IncompleteRead is + # raised during streaming, so all calls with incorrect + # Content-Length are caught. + raise IncompleteRead(self._fp_bytes_read, self.length_remaining) + elif read1 and ( + (amt != 0 and not data) or self.length_remaining == len(data) + ): + # All data has been read, but `self._fp.read1` in + # CPython 3.12 and older doesn't always close + # `http.client.HTTPResponse`, so we close it here. + # See https://github.com/python/cpython/issues/113199 + self._fp.close() + + if data: + self._fp_bytes_read += len(data) + if self.length_remaining is not None: + self.length_remaining -= len(data) + return data + + def read( + self, + amt: int | None = None, + decode_content: bool | None = None, + cache_content: bool = False, + ) -> bytes: + """ + Similar to :meth:`http.client.HTTPResponse.read`, but with two additional + parameters: ``decode_content`` and ``cache_content``. + + :param amt: + How much of the content to read. If specified, caching is skipped + because it doesn't make sense to cache partial content as the full + response. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + + :param cache_content: + If True, will save the returned data such that the same result is + returned despite of the state of the underlying file object. This + is useful if you want the ``.data`` property to continue working + after having ``.read()`` the file object. (Overridden if ``amt`` is + set.) + """ + self._init_decoder() + if decode_content is None: + decode_content = self.decode_content + + if amt and amt < 0: + # Negative numbers and `None` should be treated the same. + amt = None + elif amt is not None: + cache_content = False + + if ( + self._decoder + and self._decoder.has_unconsumed_tail + and len(self._decoded_buffer) < amt + ): + decoded_data = self._decode( + b"", + decode_content, + flush_decoder=False, + max_length=amt - len(self._decoded_buffer), + ) + self._decoded_buffer.put(decoded_data) + if len(self._decoded_buffer) >= amt: + return self._decoded_buffer.get(amt) + + data = self._raw_read(amt) + if not cache_content: + self._uncached_read_occurred = True + + flush_decoder = amt is None or (amt != 0 and not data) + + if ( + not data + and len(self._decoded_buffer) == 0 + and not (self._decoder and self._decoder.has_unconsumed_tail) + ): + return data + + if amt is None: + data = self._decode(data, decode_content, flush_decoder) + # It's possible that there is buffered decoded data after a + # partial read. + if decode_content and len(self._decoded_buffer) > 0: + self._decoded_buffer.put(data) + data = self._decoded_buffer.get_all() + + if cache_content and not self._uncached_read_occurred: + self._body = data + else: + # do not waste memory on buffer when not decoding + if not decode_content: + if self._has_decoded_content: + raise RuntimeError( + "Calling read(decode_content=False) is not supported after " + "read(decode_content=True) was called." + ) + return data + + decoded_data = self._decode( + data, + decode_content, + flush_decoder, + max_length=amt - len(self._decoded_buffer), + ) + self._decoded_buffer.put(decoded_data) + + while len(self._decoded_buffer) < amt and data: + # TODO make sure to initially read enough data to get past the headers + # For example, the GZ file header takes 10 bytes, we don't want to read + # it one byte at a time + data = self._raw_read(amt) + decoded_data = self._decode( + data, + decode_content, + flush_decoder, + max_length=amt - len(self._decoded_buffer), + ) + self._decoded_buffer.put(decoded_data) + data = self._decoded_buffer.get(amt) + + return data + + def read1( + self, + amt: int | None = None, + decode_content: bool | None = None, + ) -> bytes: + """ + Similar to ``http.client.HTTPResponse.read1`` and documented + in :meth:`io.BufferedReader.read1`, but with an additional parameter: + ``decode_content``. + + :param amt: + How much of the content to read. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + """ + if decode_content is None: + decode_content = self.decode_content + if amt and amt < 0: + # Negative numbers and `None` should be treated the same. + amt = None + # try and respond without going to the network + if self._has_decoded_content: + if not decode_content: + raise RuntimeError( + "Calling read1(decode_content=False) is not supported after " + "read1(decode_content=True) was called." + ) + if ( + self._decoder + and self._decoder.has_unconsumed_tail + and (amt is None or len(self._decoded_buffer) < amt) + ): + decoded_data = self._decode( + b"", + decode_content, + flush_decoder=False, + max_length=( + amt - len(self._decoded_buffer) if amt is not None else None + ), + ) + self._decoded_buffer.put(decoded_data) + if len(self._decoded_buffer) > 0: + if amt is None: + return self._decoded_buffer.get_all() + return self._decoded_buffer.get(amt) + if amt == 0: + return b"" + + # FIXME, this method's type doesn't say returning None is possible + data = self._raw_read(amt, read1=True) + self._uncached_read_occurred = True + if not decode_content or data is None: + return data + + self._init_decoder() + while True: + flush_decoder = not data + decoded_data = self._decode( + data, decode_content, flush_decoder, max_length=amt + ) + self._decoded_buffer.put(decoded_data) + if decoded_data or flush_decoder: + break + data = self._raw_read(8192, read1=True) + + if amt is None: + return self._decoded_buffer.get_all() + return self._decoded_buffer.get(amt) + + def stream( + self, amt: int | None = 2**16, decode_content: bool | None = None + ) -> typing.Generator[bytes]: + """ + A generator wrapper for the read() method. A call will block until + ``amt`` bytes have been read from the connection or until the + connection is closed. + + :param amt: + How much of the content to read. The generator will return up to + much data per iteration, but may return less. This is particularly + likely when using compressed data. However, the empty string will + never be returned. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + """ + if amt == 0: + return + + if self.chunked and self.supports_chunked_reads(): + yield from self.read_chunked(amt, decode_content=decode_content) + else: + while ( + not is_fp_closed(self._fp) + or len(self._decoded_buffer) > 0 + or (self._decoder and self._decoder.has_unconsumed_tail) + ): + data = self.read(amt=amt, decode_content=decode_content) + + if data: + yield data + + # Overrides from io.IOBase + def readable(self) -> bool: + return True + + def shutdown(self) -> None: + if not self._sock_shutdown: + raise ValueError("Cannot shutdown socket as self._sock_shutdown is not set") + if self._connection is None: + raise RuntimeError( + "Cannot shutdown as connection has already been released to the pool" + ) + self._sock_shutdown(socket.SHUT_RD) + + def close(self) -> None: + self._sock_shutdown = None + + if not self.closed and self._fp: + self._fp.close() + + if self._connection: + self._connection.close() + + if not self.auto_close: + io.IOBase.close(self) + + @property + def closed(self) -> bool: + if not self.auto_close: + return io.IOBase.closed.__get__(self) # type: ignore[no-any-return] + elif self._fp is None: + return True + elif hasattr(self._fp, "isclosed"): + return self._fp.isclosed() + elif hasattr(self._fp, "closed"): + return self._fp.closed + else: + return True + + def fileno(self) -> int: + if self._fp is None: + raise OSError("HTTPResponse has no file to get a fileno from") + elif hasattr(self._fp, "fileno"): + return self._fp.fileno() + else: + raise OSError( + "The file-like object this HTTPResponse is wrapped " + "around has no file descriptor" + ) + + def flush(self) -> None: + if ( + self._fp is not None + and hasattr(self._fp, "flush") + and not getattr(self._fp, "closed", False) + ): + return self._fp.flush() + + def supports_chunked_reads(self) -> bool: + """ + Checks if the underlying file-like object looks like a + :class:`http.client.HTTPResponse` object. We do this by testing for + the fp attribute. If it is present we assume it returns raw chunks as + processed by read_chunked(). + """ + return hasattr(self._fp, "fp") + + def _update_chunk_length(self) -> None: + # First, we'll figure out length of a chunk and then + # we'll try to read it from socket. + if self.chunk_left is not None: + return None + line = self._fp.fp.readline() # type: ignore[union-attr] + line = line.split(b";", 1)[0] + try: + self.chunk_left = int(line, 16) + except ValueError: + self.close() + if line: + # Invalid chunked protocol response, abort. + raise InvalidChunkLength(self, line) from None + else: + # Truncated at start of next chunk + raise ProtocolError("Response ended prematurely") from None + + def _handle_chunk(self, amt: int | None) -> bytes: + returned_chunk = None + if amt is None: + chunk = self._fp._safe_read(self.chunk_left) # type: ignore[union-attr] + returned_chunk = chunk + self._fp._safe_read(2) # type: ignore[union-attr] # Toss the CRLF at the end of the chunk. + self.chunk_left = None + elif self.chunk_left is not None and amt < self.chunk_left: + value = self._fp._safe_read(amt) # type: ignore[union-attr] + self.chunk_left = self.chunk_left - amt + returned_chunk = value + elif amt == self.chunk_left: + value = self._fp._safe_read(amt) # type: ignore[union-attr] + self._fp._safe_read(2) # type: ignore[union-attr] # Toss the CRLF at the end of the chunk. + self.chunk_left = None + returned_chunk = value + else: # amt > self.chunk_left + returned_chunk = self._fp._safe_read(self.chunk_left) # type: ignore[union-attr] + self._fp._safe_read(2) # type: ignore[union-attr] # Toss the CRLF at the end of the chunk. + self.chunk_left = None + return returned_chunk # type: ignore[no-any-return] + + def read_chunked( + self, amt: int | None = None, decode_content: bool | None = None + ) -> typing.Generator[bytes]: + """ + Similar to :meth:`HTTPResponse.read`, but with an additional + parameter: ``decode_content``. + + :param amt: + How much of the content to read. If specified, caching is skipped + because it doesn't make sense to cache partial content as the full + response. + + :param decode_content: + If True, will attempt to decode the body based on the + 'content-encoding' header. + """ + self._init_decoder() + # FIXME: Rewrite this method and make it a class with a better structured logic. + if not self.chunked: + raise ResponseNotChunked( + "Response is not chunked. " + "Header 'transfer-encoding: chunked' is missing." + ) + if not self.supports_chunked_reads(): + raise BodyNotHttplibCompatible( + "Body should be http.client.HTTPResponse like. " + "It should have have an fp attribute which returns raw chunks." + ) + + with self._error_catcher(): + # Don't bother reading the body of a HEAD request. + if self._original_response and is_response_to_head(self._original_response): + self._original_response.close() + return None + + # If a response is already read and closed + # then return immediately. + if self._fp.fp is None: # type: ignore[union-attr] + return None + + if amt == 0: + return + elif amt and amt < 0: + # Negative numbers and `None` should be treated the same, + # but httplib handles only `None` correctly. + amt = None + + while True: + # First, check if any data is left in the decoder's buffer. + if self._decoder and self._decoder.has_unconsumed_tail: + chunk = b"" + else: + self._update_chunk_length() + self._uncached_read_occurred = True + if self.chunk_left == 0: + break + chunk = self._handle_chunk(amt) + decoded = self._decode( + chunk, + decode_content=decode_content, + flush_decoder=False, + max_length=amt, + ) + if decoded: + yield decoded + + if decode_content: + # On CPython and PyPy, we should never need to flush the + # decoder. However, on Jython we *might* need to, so + # lets defensively do it anyway. + decoded = self._flush_decoder() + if decoded: # Platform-specific: Jython. + yield decoded + + # Chunk content ends with \r\n: discard it. + while self._fp is not None: + line = self._fp.fp.readline() + if not line: + # Some sites may not end with '\r\n'. + break + if line == b"\r\n": + break + + # We read everything; close the "file". + if self._original_response: + self._original_response.close() + + @property + def url(self) -> str | None: + """ + Returns the URL that was the source of this response. + If the request that generated this response redirected, this method + will return the final redirect location. + """ + return self._request_url + + @url.setter + def url(self, url: str | None) -> None: + self._request_url = url + + def __iter__(self) -> typing.Iterator[bytes]: + buffer: list[bytes] = [] + for chunk in self.stream(decode_content=True): + if b"\n" in chunk: + chunks = chunk.split(b"\n") + yield b"".join(buffer) + chunks[0] + b"\n" + for x in chunks[1:-1]: + yield x + b"\n" + if chunks[-1]: + buffer = [chunks[-1]] + else: + buffer = [] + else: + buffer.append(chunk) + if buffer: + yield b"".join(buffer) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..534126033c083203649022fa9b753a433f005556 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__init__.py @@ -0,0 +1,42 @@ +# For backwards compatibility, provide imports that used to be here. +from __future__ import annotations + +from .connection import is_connection_dropped +from .request import SKIP_HEADER, SKIPPABLE_HEADERS, make_headers +from .response import is_fp_closed +from .retry import Retry +from .ssl_ import ( + ALPN_PROTOCOLS, + IS_PYOPENSSL, + SSLContext, + assert_fingerprint, + create_urllib3_context, + resolve_cert_reqs, + resolve_ssl_version, + ssl_wrap_socket, +) +from .timeout import Timeout +from .url import Url, parse_url +from .wait import wait_for_read, wait_for_write + +__all__ = ( + "IS_PYOPENSSL", + "SSLContext", + "ALPN_PROTOCOLS", + "Retry", + "Timeout", + "Url", + "assert_fingerprint", + "create_urllib3_context", + "is_connection_dropped", + "is_fp_closed", + "parse_url", + "make_headers", + "resolve_cert_reqs", + "resolve_ssl_version", + "ssl_wrap_socket", + "wait_for_read", + "wait_for_write", + "SKIP_HEADER", + "SKIPPABLE_HEADERS", +) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/__init__.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0150721d1b307c612054986f5ce3eb3e08e3e5a4 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/__init__.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/connection.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/connection.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..39ea650735b4a98fbe7822c0015a57863eecb955 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/connection.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/proxy.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/proxy.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4703fee3eeea1295fae83cb8b5ab28cf4e36eaba Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/proxy.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/request.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/request.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..182993ca207ecb9f19f7f07aa528c24cb8f30a98 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/request.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/response.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/response.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d66e48055b2b3bd3e34984e863ce33c9eac64b38 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/response.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/retry.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/retry.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4e2e5f0fd24c205552115242c7a5adb93d64dc7a Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/retry.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/ssl_.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/ssl_.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1086638c89256933571459868f95c3d58887df5f Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/ssl_.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/ssl_match_hostname.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/ssl_match_hostname.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..02c15960a0298ba42b8499b1036440d9ed4576e5 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/ssl_match_hostname.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/ssltransport.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/ssltransport.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5b87368d50b00518728313c354c7e22bdf91a7a7 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/ssltransport.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/timeout.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/timeout.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a61f95b943b3ff8277e7539141e473f2bb3ffc27 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/timeout.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/url.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/url.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..469782874c10c92a639a5e5246b865ef1653efd0 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/url.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/util.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/util.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..94016f76bfdfe332b1cda9e8bb0be0b0df4f0bd2 Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/util.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/wait.cpython-314.pyc b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/wait.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a9b1832154f1b2fb1cd1189341ad96adda4425bd Binary files /dev/null and b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/__pycache__/wait.cpython-314.pyc differ diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/connection.py b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/connection.py new file mode 100644 index 0000000000000000000000000000000000000000..f92519ee9124e91e5da7d60ccc3f274312ed3514 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/connection.py @@ -0,0 +1,137 @@ +from __future__ import annotations + +import socket +import typing + +from ..exceptions import LocationParseError +from .timeout import _DEFAULT_TIMEOUT, _TYPE_TIMEOUT + +_TYPE_SOCKET_OPTIONS = list[tuple[int, int, typing.Union[int, bytes]]] + +if typing.TYPE_CHECKING: + from .._base_connection import BaseHTTPConnection + + +def is_connection_dropped(conn: BaseHTTPConnection) -> bool: # Platform-specific + """ + Returns True if the connection is dropped and should be closed. + :param conn: :class:`urllib3.connection.HTTPConnection` object. + """ + return not conn.is_connected + + +# This function is copied from socket.py in the Python 2.7 standard +# library test suite. Added to its signature is only `socket_options`. +# One additional modification is that we avoid binding to IPv6 servers +# discovered in DNS if the system doesn't have IPv6 functionality. +def create_connection( + address: tuple[str, int], + timeout: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + source_address: tuple[str, int] | None = None, + socket_options: _TYPE_SOCKET_OPTIONS | None = None, +) -> socket.socket: + """Connect to *address* and return the socket object. + + Convenience function. Connect to *address* (a 2-tuple ``(host, + port)``) and return the socket object. Passing the optional + *timeout* parameter will set the timeout on the socket instance + before attempting to connect. If no *timeout* is supplied, the + global default timeout setting returned by :func:`socket.getdefaulttimeout` + is used. If *source_address* is set it must be a tuple of (host, port) + for the socket to bind as a source address before making the connection. + An host of '' or port 0 tells the OS to use the default. + """ + + host, port = address + if host.startswith("["): + host = host.strip("[]") + err = None + + # Using the value from allowed_gai_family() in the context of getaddrinfo lets + # us select whether to work with IPv4 DNS records, IPv6 records, or both. + # The original create_connection function always returns all records. + family = allowed_gai_family() + + try: + host.encode("idna") + except UnicodeError: + raise LocationParseError(f"'{host}', label empty or too long") from None + + for res in socket.getaddrinfo(host, port, family, socket.SOCK_STREAM): + af, socktype, proto, canonname, sa = res + sock = None + try: + sock = socket.socket(af, socktype, proto) + + # If provided, set socket level options before connecting. + _set_socket_options(sock, socket_options) + + if timeout is not _DEFAULT_TIMEOUT: + sock.settimeout(timeout) + if source_address: + sock.bind(source_address) + sock.connect(sa) + # Break explicitly a reference cycle + err = None + return sock + + except OSError as _: + err = _ + if sock is not None: + sock.close() + + if err is not None: + try: + raise err + finally: + # Break explicitly a reference cycle + err = None + else: + raise OSError("getaddrinfo returns an empty list") + + +def _set_socket_options( + sock: socket.socket, options: _TYPE_SOCKET_OPTIONS | None +) -> None: + if options is None: + return + + for opt in options: + sock.setsockopt(*opt) + + +def allowed_gai_family() -> socket.AddressFamily: + """This function is designed to work in the context of + getaddrinfo, where family=socket.AF_UNSPEC is the default and + will perform a DNS search for both IPv6 and IPv4 records.""" + + family = socket.AF_INET + if HAS_IPV6: + family = socket.AF_UNSPEC + return family + + +def _has_ipv6(host: str) -> bool: + """Returns True if the system can bind an IPv6 address.""" + sock = None + has_ipv6 = False + + if socket.has_ipv6: + # has_ipv6 returns true if cPython was compiled with IPv6 support. + # It does not tell us if the system has IPv6 support enabled. To + # determine that we must bind to an IPv6 address. + # https://github.com/urllib3/urllib3/pull/611 + # https://bugs.python.org/issue658327 + try: + sock = socket.socket(socket.AF_INET6) + sock.bind((host, 0)) + has_ipv6 = True + except Exception: + pass + + if sock: + sock.close() + return has_ipv6 + + +HAS_IPV6 = _has_ipv6("::1") diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/proxy.py b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/proxy.py new file mode 100644 index 0000000000000000000000000000000000000000..908fc6621d0afbed16bde2c1957a5cf28d3a84d8 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/proxy.py @@ -0,0 +1,43 @@ +from __future__ import annotations + +import typing + +from .url import Url + +if typing.TYPE_CHECKING: + from ..connection import ProxyConfig + + +def connection_requires_http_tunnel( + proxy_url: Url | None = None, + proxy_config: ProxyConfig | None = None, + destination_scheme: str | None = None, +) -> bool: + """ + Returns True if the connection requires an HTTP CONNECT through the proxy. + + :param URL proxy_url: + URL of the proxy. + :param ProxyConfig proxy_config: + Proxy configuration from poolmanager.py + :param str destination_scheme: + The scheme of the destination. (i.e https, http, etc) + """ + # If we're not using a proxy, no way to use a tunnel. + if proxy_url is None: + return False + + # HTTP destinations never require tunneling, we always forward. + if destination_scheme == "http": + return False + + # Support for forwarding with HTTPS proxies and HTTPS destinations. + if ( + proxy_url.scheme == "https" + and proxy_config + and proxy_config.use_forwarding_for_https + ): + return False + + # Otherwise always use a tunnel. + return True diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/request.py b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/request.py new file mode 100644 index 0000000000000000000000000000000000000000..6c2372ba7e777826a4eb124ddfb54f0240b65d67 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/request.py @@ -0,0 +1,263 @@ +from __future__ import annotations + +import io +import sys +import typing +from base64 import b64encode +from enum import Enum + +from ..exceptions import UnrewindableBodyError +from .util import to_bytes + +if typing.TYPE_CHECKING: + from typing import Final + +# Pass as a value within ``headers`` to skip +# emitting some HTTP headers that are added automatically. +# The only headers that are supported are ``Accept-Encoding``, +# ``Host``, and ``User-Agent``. +SKIP_HEADER = "@@@SKIP_HEADER@@@" +SKIPPABLE_HEADERS = frozenset(["accept-encoding", "host", "user-agent"]) + +ACCEPT_ENCODING = "gzip,deflate" +try: + try: + import brotlicffi as _unused_module_brotli # type: ignore[import-not-found] # noqa: F401 + except ImportError: + import brotli as _unused_module_brotli # type: ignore[import-not-found] # noqa: F401 +except ImportError: + pass +else: + ACCEPT_ENCODING += ",br" + +try: + if sys.version_info >= (3, 14): + from compression import zstd as _unused_module_zstd # noqa: F401 + else: + from backports import zstd as _unused_module_zstd # noqa: F401 +except ImportError: + pass +else: + ACCEPT_ENCODING += ",zstd" + + +class _TYPE_FAILEDTELL(Enum): + token = 0 + + +_FAILEDTELL: Final[_TYPE_FAILEDTELL] = _TYPE_FAILEDTELL.token + +_TYPE_BODY_POSITION = typing.Union[int, _TYPE_FAILEDTELL] + +# When sending a request with these methods we aren't expecting +# a body so don't need to set an explicit 'Content-Length: 0' +# The reason we do this in the negative instead of tracking methods +# which 'should' have a body is because unknown methods should be +# treated as if they were 'POST' which *does* expect a body. +_METHODS_NOT_EXPECTING_BODY = {"GET", "HEAD", "DELETE", "TRACE", "OPTIONS", "CONNECT"} + + +def make_headers( + keep_alive: bool | None = None, + accept_encoding: bool | list[str] | str | None = None, + user_agent: str | None = None, + basic_auth: str | None = None, + proxy_basic_auth: str | None = None, + disable_cache: bool | None = None, +) -> dict[str, str]: + """ + Shortcuts for generating request headers. + + :param keep_alive: + If ``True``, adds 'connection: keep-alive' header. + + :param accept_encoding: + Can be a boolean, list, or string. + ``True`` translates to 'gzip,deflate'. If the dependencies for + Brotli (either the ``brotli`` or ``brotlicffi`` package) and/or + Zstandard (the ``backports.zstd`` package for Python before 3.14) + algorithms are installed, then their encodings are + included in the string ('br' and 'zstd', respectively). + List will get joined by comma. + String will be used as provided. + + :param user_agent: + String representing the user-agent you want, such as + "python-urllib3/0.6" + + :param basic_auth: + Colon-separated username:password string for 'authorization: basic ...' + auth header. + + :param proxy_basic_auth: + Colon-separated username:password string for 'proxy-authorization: basic ...' + auth header. + + :param disable_cache: + If ``True``, adds 'cache-control: no-cache' header. + + Example: + + .. code-block:: python + + import urllib3 + + print(urllib3.util.make_headers(keep_alive=True, user_agent="Batman/1.0")) + # {'connection': 'keep-alive', 'user-agent': 'Batman/1.0'} + print(urllib3.util.make_headers(accept_encoding=True)) + # {'accept-encoding': 'gzip,deflate'} + """ + headers: dict[str, str] = {} + if accept_encoding: + if isinstance(accept_encoding, str): + pass + elif isinstance(accept_encoding, list): + accept_encoding = ",".join(accept_encoding) + else: + accept_encoding = ACCEPT_ENCODING + headers["accept-encoding"] = accept_encoding + + if user_agent: + headers["user-agent"] = user_agent + + if keep_alive: + headers["connection"] = "keep-alive" + + if basic_auth: + headers["authorization"] = ( + f"Basic {b64encode(basic_auth.encode('latin-1')).decode()}" + ) + + if proxy_basic_auth: + headers["proxy-authorization"] = ( + f"Basic {b64encode(proxy_basic_auth.encode('latin-1')).decode()}" + ) + + if disable_cache: + headers["cache-control"] = "no-cache" + + return headers + + +def set_file_position( + body: typing.Any, pos: _TYPE_BODY_POSITION | None +) -> _TYPE_BODY_POSITION | None: + """ + If a position is provided, move file to that point. + Otherwise, we'll attempt to record a position for future use. + """ + if pos is not None: + rewind_body(body, pos) + elif getattr(body, "tell", None) is not None: + try: + pos = body.tell() + except OSError: + # This differentiates from None, allowing us to catch + # a failed `tell()` later when trying to rewind the body. + pos = _FAILEDTELL + + return pos + + +def rewind_body(body: typing.IO[typing.AnyStr], body_pos: _TYPE_BODY_POSITION) -> None: + """ + Attempt to rewind body to a certain position. + Primarily used for request redirects and retries. + + :param body: + File-like object that supports seek. + + :param int pos: + Position to seek to in file. + """ + body_seek = getattr(body, "seek", None) + if body_seek is not None and isinstance(body_pos, int): + try: + body_seek(body_pos) + except OSError as e: + raise UnrewindableBodyError( + "An error occurred when rewinding request body for redirect/retry." + ) from e + elif body_pos is _FAILEDTELL: + raise UnrewindableBodyError( + "Unable to record file position for rewinding " + "request body during a redirect/retry." + ) + else: + raise ValueError( + f"body_pos must be of type integer, instead it was {type(body_pos)}." + ) + + +class ChunksAndContentLength(typing.NamedTuple): + chunks: typing.Iterable[bytes] | None + content_length: int | None + + +def body_to_chunks( + body: typing.Any | None, method: str, blocksize: int +) -> ChunksAndContentLength: + """Takes the HTTP request method, body, and blocksize and + transforms them into an iterable of chunks to pass to + socket.sendall() and an optional 'Content-Length' header. + + A 'Content-Length' of 'None' indicates the length of the body + can't be determined so should use 'Transfer-Encoding: chunked' + for framing instead. + """ + + chunks: typing.Iterable[bytes] | None + content_length: int | None + + # No body, we need to make a recommendation on 'Content-Length' + # based on whether that request method is expected to have + # a body or not. + if body is None: + chunks = None + if method.upper() not in _METHODS_NOT_EXPECTING_BODY: + content_length = 0 + else: + content_length = None + + # Bytes or strings become bytes + elif isinstance(body, (str, bytes)): + chunks = (to_bytes(body),) + content_length = len(chunks[0]) + + # File-like object, TODO: use seek() and tell() for length? + elif hasattr(body, "read"): + + def chunk_readable() -> typing.Iterable[bytes]: + encode = isinstance(body, io.TextIOBase) + while True: + datablock = body.read(blocksize) + if not datablock: + break + if encode: + datablock = datablock.encode("utf-8") + yield datablock + + chunks = chunk_readable() + content_length = None + + # Otherwise we need to start checking via duck-typing. + else: + try: + # Check if the body implements the buffer API. + mv = memoryview(body) + except TypeError: + try: + # Check if the body is an iterable + chunks = iter(body) + content_length = None + except TypeError: + raise TypeError( + f"'body' must be a bytes-like object, file-like " + f"object, or iterable. Instead was {body!r}" + ) from None + else: + # Since it implements the buffer API can be passed directly to socket.sendall() + chunks = (body,) + content_length = mv.nbytes + + return ChunksAndContentLength(chunks=chunks, content_length=content_length) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/response.py b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/response.py new file mode 100644 index 0000000000000000000000000000000000000000..0f4578696fa2e17a900c6890ec26d65e860b0b72 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/response.py @@ -0,0 +1,101 @@ +from __future__ import annotations + +import http.client as httplib +from email.errors import MultipartInvariantViolationDefect, StartBoundaryNotFoundDefect + +from ..exceptions import HeaderParsingError + + +def is_fp_closed(obj: object) -> bool: + """ + Checks whether a given file-like object is closed. + + :param obj: + The file-like object to check. + """ + + try: + # Check `isclosed()` first, in case Python3 doesn't set `closed`. + # GH Issue #928 + return obj.isclosed() # type: ignore[no-any-return, attr-defined] + except AttributeError: + pass + + try: + # Check via the official file-like-object way. + return obj.closed # type: ignore[no-any-return, attr-defined] + except AttributeError: + pass + + try: + # Check if the object is a container for another file-like object that + # gets released on exhaustion (e.g. HTTPResponse). + return obj.fp is None # type: ignore[attr-defined] + except AttributeError: + pass + + raise ValueError("Unable to determine whether fp is closed.") + + +def assert_header_parsing(headers: httplib.HTTPMessage) -> None: + """ + Asserts whether all headers have been successfully parsed. + Extracts encountered errors from the result of parsing headers. + + Only works on Python 3. + + :param http.client.HTTPMessage headers: Headers to verify. + + :raises urllib3.exceptions.HeaderParsingError: + If parsing errors are found. + """ + + # This will fail silently if we pass in the wrong kind of parameter. + # To make debugging easier add an explicit check. + if not isinstance(headers, httplib.HTTPMessage): + raise TypeError(f"expected httplib.Message, got {type(headers)}.") + + unparsed_data = None + + # get_payload is actually email.message.Message.get_payload; + # we're only interested in the result if it's not a multipart message + if not headers.is_multipart(): + payload = headers.get_payload() + + if isinstance(payload, (bytes, str)): + unparsed_data = payload + + # httplib is assuming a response body is available + # when parsing headers even when httplib only sends + # header data to parse_headers() This results in + # defects on multipart responses in particular. + # See: https://github.com/urllib3/urllib3/issues/800 + + # So we ignore the following defects: + # - StartBoundaryNotFoundDefect: + # The claimed start boundary was never found. + # - MultipartInvariantViolationDefect: + # A message claimed to be a multipart but no subparts were found. + defects = [ + defect + for defect in headers.defects + if not isinstance( + defect, (StartBoundaryNotFoundDefect, MultipartInvariantViolationDefect) + ) + ] + + if defects or unparsed_data: + raise HeaderParsingError(defects=defects, unparsed_data=unparsed_data) + + +def is_response_to_head(response: httplib.HTTPResponse) -> bool: + """ + Checks whether the request of a response has been a HEAD-request. + + :param http.client.HTTPResponse response: + Response to check if the originating request + used 'HEAD' as a method. + """ + # FIXME: Can we do this somehow without accessing private httplib _method? + method_str = response._method # type: str # type: ignore[attr-defined] + return method_str.upper() == "HEAD" diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/retry.py b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/retry.py new file mode 100644 index 0000000000000000000000000000000000000000..7649898e1d9930724a456c1c6fdecb66e078b4cc --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/retry.py @@ -0,0 +1,557 @@ +from __future__ import annotations + +import email +import logging +import random +import re +import time +import typing +from itertools import takewhile +from types import TracebackType + +from ..exceptions import ( + ConnectTimeoutError, + InvalidHeader, + MaxRetryError, + ProtocolError, + ProxyError, + ReadTimeoutError, + ResponseError, +) +from .util import reraise + +if typing.TYPE_CHECKING: + from typing_extensions import Self + + from ..connectionpool import ConnectionPool + from ..response import BaseHTTPResponse + +log = logging.getLogger(__name__) + + +# Data structure for representing the metadata of requests that result in a retry. +class RequestHistory(typing.NamedTuple): + method: str | None + url: str | None + error: Exception | None + status: int | None + redirect_location: str | None + + +class Retry: + """Retry configuration. + + Each retry attempt will create a new Retry object with updated values, so + they can be safely reused. + + Retries can be defined as a default for a pool: + + .. code-block:: python + + retries = Retry(connect=5, read=2, redirect=5) + http = PoolManager(retries=retries) + response = http.request("GET", "https://example.com/") + + Or per-request (which overrides the default for the pool): + + .. code-block:: python + + response = http.request("GET", "https://example.com/", retries=Retry(10)) + + Retries can be disabled by passing ``False``: + + .. code-block:: python + + response = http.request("GET", "https://example.com/", retries=False) + + Errors will be wrapped in :class:`~urllib3.exceptions.MaxRetryError` unless + retries are disabled, in which case the causing exception will be raised. + + :param int total: + Total number of retries to allow. Takes precedence over other counts. + + Set to ``None`` to remove this constraint and fall back on other + counts. + + Set to ``0`` to fail on the first retry. + + Set to ``False`` to disable and imply ``raise_on_redirect=False``. + + :param int connect: + How many connection-related errors to retry on. + + These are errors raised before the request is sent to the remote server, + which we assume has not triggered the server to process the request. + + Set to ``0`` to fail on the first retry of this type. + + :param int read: + How many times to retry on read errors. + + These errors are raised after the request was sent to the server, so the + request may have side-effects. + + Set to ``0`` to fail on the first retry of this type. + + :param int redirect: + How many redirects to perform. Limit this to avoid infinite redirect + loops. + + A redirect is a HTTP response with a status code 301, 302, 303, 307 or + 308. + + Set to ``0`` to fail on the first retry of this type. + + Set to ``False`` to disable and imply ``raise_on_redirect=False``. + + :param int status: + How many times to retry on bad status codes. + + These are retries made on responses, where status code matches + ``status_forcelist``. + + Set to ``0`` to fail on the first retry of this type. + + :param int other: + How many times to retry on other errors. + + Other errors are errors that are not connect, read, redirect or status errors. + These errors might be raised after the request was sent to the server, so the + request might have side-effects. + + Set to ``0`` to fail on the first retry of this type. + + If ``total`` is not set, it's a good idea to set this to 0 to account + for unexpected edge cases and avoid infinite retry loops. + + :param Collection allowed_methods: + Set of uppercased HTTP method verbs that we should retry on. + + By default, we only retry on methods which are considered to be + idempotent (multiple requests with the same parameters end with the + same state). See :attr:`Retry.DEFAULT_ALLOWED_METHODS`. + + Set to a ``None`` value to retry on any verb. + + :param Collection status_forcelist: + A set of integer HTTP status codes that we should force a retry on. + A retry is initiated if the request method is in ``allowed_methods`` + and the response status code is in ``status_forcelist``. + + By default, this is disabled with ``None``. + + :param float backoff_factor: + A backoff factor to apply between attempts after the second try + (most errors are resolved immediately by a second try without a + delay). urllib3 will sleep for:: + + {backoff factor} * (2 ** ({number of previous retries})) + + seconds. If `backoff_jitter` is non-zero, this sleep is extended by:: + + random.uniform(0, {backoff jitter}) + + seconds. For example, if the backoff_factor is 0.1, then :func:`Retry.sleep` will + sleep for [0.0s, 0.2s, 0.4s, 0.8s, ...] between retries. No backoff will ever + be longer than `backoff_max`. + + By default, backoff is disabled (factor set to 0). + + :param float backoff_max: + The maximum backoff time (in seconds) between retry attempts. + This value caps the computed backoff from `backoff_factor`. + + :param float backoff_jitter: + Random jitter amount (in seconds) added to the computed backoff. + Jitter is sampled uniformly from `0` to `backoff_jitter`. + + :param bool raise_on_redirect: Whether, if the number of redirects is + exhausted, to raise a MaxRetryError, or to return a response with a + response code in the 3xx range. + + :param bool raise_on_status: Similar meaning to ``raise_on_redirect``: + whether we should raise an exception, or return a response, + if status falls in ``status_forcelist`` range and retries have + been exhausted. + + :param tuple history: The history of the request encountered during + each call to :meth:`~Retry.increment`. The list is in the order + the requests occurred. Each list item is of class :class:`RequestHistory`. + + :param bool respect_retry_after_header: + Whether to respect Retry-After header on status codes defined as + :attr:`Retry.RETRY_AFTER_STATUS_CODES` or not. + + :param Collection remove_headers_on_redirect: + Sequence of headers to remove from the request when a response + indicating a redirect is returned before firing off the redirected + request. + + :param int retry_after_max: Number of seconds to allow as the maximum for + Retry-After headers. Defaults to :attr:`Retry.DEFAULT_RETRY_AFTER_MAX`. + Any Retry-After headers larger than this value will be limited to this + value. + """ + + #: Default methods to be used for ``allowed_methods`` + DEFAULT_ALLOWED_METHODS = frozenset( + ["HEAD", "GET", "PUT", "DELETE", "OPTIONS", "TRACE"] + ) + + #: Default status codes to be used for ``status_forcelist`` + RETRY_AFTER_STATUS_CODES = frozenset([413, 429, 503]) + + #: Default headers to be used for ``remove_headers_on_redirect`` + DEFAULT_REMOVE_HEADERS_ON_REDIRECT = frozenset( + ["Cookie", "Authorization", "Proxy-Authorization"] + ) + + #: Default maximum backoff time. + DEFAULT_BACKOFF_MAX = 120 + + # This is undocumented in the RFC. Setting to 6 hours matches other popular libraries. + #: Default maximum allowed value for Retry-After headers in seconds + DEFAULT_RETRY_AFTER_MAX: typing.Final[int] = 21600 + + # Backward compatibility; assigned outside of the class. + DEFAULT: typing.ClassVar[Retry] + + def __init__( + self, + total: bool | int | None = 10, + connect: int | None = None, + read: int | None = None, + redirect: bool | int | None = None, + status: int | None = None, + other: int | None = None, + allowed_methods: typing.Collection[str] | None = DEFAULT_ALLOWED_METHODS, + status_forcelist: typing.Collection[int] | None = None, + backoff_factor: float = 0, + backoff_max: float = DEFAULT_BACKOFF_MAX, + raise_on_redirect: bool = True, + raise_on_status: bool = True, + history: tuple[RequestHistory, ...] | None = None, + respect_retry_after_header: bool = True, + remove_headers_on_redirect: typing.Collection[ + str + ] = DEFAULT_REMOVE_HEADERS_ON_REDIRECT, + backoff_jitter: float = 0.0, + retry_after_max: int = DEFAULT_RETRY_AFTER_MAX, + ) -> None: + self.total = total + self.connect = connect + self.read = read + self.status = status + self.other = other + + if redirect is False or total is False: + redirect = 0 + raise_on_redirect = False + + self.redirect = redirect + self.status_forcelist = status_forcelist or set() + self.allowed_methods = allowed_methods + self.backoff_factor = backoff_factor + self.backoff_max = backoff_max + self.retry_after_max = retry_after_max + self.raise_on_redirect = raise_on_redirect + self.raise_on_status = raise_on_status + self.history = history or () + self.respect_retry_after_header = respect_retry_after_header + self.remove_headers_on_redirect = frozenset( + h.lower() for h in remove_headers_on_redirect + ) + self.backoff_jitter = backoff_jitter + + def new(self, **kw: typing.Any) -> Self: + params = dict( + total=self.total, + connect=self.connect, + read=self.read, + redirect=self.redirect, + status=self.status, + other=self.other, + allowed_methods=self.allowed_methods, + status_forcelist=self.status_forcelist, + backoff_factor=self.backoff_factor, + backoff_max=self.backoff_max, + retry_after_max=self.retry_after_max, + raise_on_redirect=self.raise_on_redirect, + raise_on_status=self.raise_on_status, + history=self.history, + remove_headers_on_redirect=self.remove_headers_on_redirect, + respect_retry_after_header=self.respect_retry_after_header, + backoff_jitter=self.backoff_jitter, + ) + + params.update(kw) + return type(self)(**params) # type: ignore[arg-type] + + @classmethod + def from_int( + cls, + retries: Retry | bool | int | None, + redirect: bool | int | None = True, + default: Retry | bool | int | None = None, + ) -> Retry: + """Backwards-compatibility for the old retries format.""" + if retries is None: + retries = default if default is not None else cls.DEFAULT + + if isinstance(retries, Retry): + return retries + + redirect = bool(redirect) and None + new_retries = cls(retries, redirect=redirect) + log.debug("Converted retries value: %r -> %r", retries, new_retries) + return new_retries + + def get_backoff_time(self) -> float: + """Formula for computing the current backoff + + :rtype: float + """ + # We want to consider only the last consecutive errors sequence (Ignore redirects). + consecutive_errors_len = len( + list( + takewhile(lambda x: x.redirect_location is None, reversed(self.history)) + ) + ) + if consecutive_errors_len <= 1: + return 0 + + backoff_value = self.backoff_factor * (2 ** (consecutive_errors_len - 1)) + if self.backoff_jitter != 0.0: + backoff_value += random.random() * self.backoff_jitter + return float(max(0, min(self.backoff_max, backoff_value))) + + def parse_retry_after(self, retry_after: str) -> float: + seconds: float + # Whitespace: https://tools.ietf.org/html/rfc7230#section-3.2.4 + if re.match(r"^\s*[0-9]+\s*$", retry_after): + seconds = int(retry_after) + else: + retry_date_tuple = email.utils.parsedate_tz(retry_after) + if retry_date_tuple is None: + raise InvalidHeader(f"Invalid Retry-After header: {retry_after}") + + retry_date = email.utils.mktime_tz(retry_date_tuple) + seconds = retry_date - time.time() + + seconds = max(seconds, 0) + + # Check the seconds do not exceed the specified maximum + if seconds > self.retry_after_max: + seconds = self.retry_after_max + + return seconds + + def get_retry_after(self, response: BaseHTTPResponse) -> float | None: + """Get the value of Retry-After in seconds.""" + + retry_after = response.headers.get("Retry-After") + + if retry_after is None: + return None + + return self.parse_retry_after(retry_after) + + def sleep_for_retry(self, response: BaseHTTPResponse) -> bool: + retry_after = self.get_retry_after(response) + if retry_after: + time.sleep(retry_after) + return True + + return False + + def _sleep_backoff(self) -> None: + backoff = self.get_backoff_time() + if backoff <= 0: + return + time.sleep(backoff) + + def sleep(self, response: BaseHTTPResponse | None = None) -> None: + """Sleep between retry attempts. + + This method will respect a server's ``Retry-After`` response header + and sleep the duration of the time requested. If that is not present, it + will use an exponential backoff. By default, the backoff factor is 0 and + this method will return immediately. + """ + + if self.respect_retry_after_header and response: + slept = self.sleep_for_retry(response) + if slept: + return + + self._sleep_backoff() + + def _is_connection_error(self, err: Exception) -> bool: + """Errors when we're fairly sure that the server did not receive the + request, so it should be safe to retry. + """ + if isinstance(err, ProxyError): + err = err.original_error + return isinstance(err, ConnectTimeoutError) + + def _is_read_error(self, err: Exception) -> bool: + """Errors that occur after the request has been started, so we should + assume that the server began processing it. + """ + return isinstance(err, (ReadTimeoutError, ProtocolError)) + + def _is_method_retryable(self, method: str) -> bool: + """Checks if a given HTTP method should be retried upon, depending if + it is included in the allowed_methods + """ + if self.allowed_methods and method.upper() not in self.allowed_methods: + return False + return True + + def is_retry( + self, method: str, status_code: int, has_retry_after: bool = False + ) -> bool: + """Is this method/status code retryable? (Based on allowlists and control + variables such as the number of total retries to allow, whether to + respect the Retry-After header, whether this header is present, and + whether the returned status code is on the list of status codes to + be retried upon on the presence of the aforementioned header) + """ + if not self._is_method_retryable(method): + return False + + if self.status_forcelist and status_code in self.status_forcelist: + return True + + return bool( + self.total + and self.respect_retry_after_header + and has_retry_after + and (status_code in self.RETRY_AFTER_STATUS_CODES) + ) + + def is_exhausted(self) -> bool: + """Are we out of retries?""" + retry_counts = [ + x + for x in ( + self.total, + self.connect, + self.read, + self.redirect, + self.status, + self.other, + ) + if x + ] + if not retry_counts: + return False + + return min(retry_counts) < 0 + + def increment( + self, + method: str | None = None, + url: str | None = None, + response: BaseHTTPResponse | None = None, + error: Exception | None = None, + _pool: ConnectionPool | None = None, + _stacktrace: TracebackType | None = None, + ) -> Self: + """Return a new Retry object with incremented retry counters. + + :param response: A response object, or None, if the server did not + return a response. + :type response: :class:`~urllib3.response.BaseHTTPResponse` + :param Exception error: An error encountered during the request, or + None if the response was received successfully. + + :return: A new ``Retry`` object. + """ + if self.total is False and error: + # Disabled, indicate to re-raise the error. + raise reraise(type(error), error, _stacktrace) + + total = self.total + if total is not None: + total -= 1 + + connect = self.connect + read = self.read + redirect = self.redirect + status_count = self.status + other = self.other + cause = "unknown" + status = None + redirect_location = None + + if error and self._is_connection_error(error): + # Connect retry? + if connect is False: + raise reraise(type(error), error, _stacktrace) + elif connect is not None: + connect -= 1 + + elif error and self._is_read_error(error): + # Read retry? + if read is False or method is None or not self._is_method_retryable(method): + raise reraise(type(error), error, _stacktrace) + elif read is not None: + read -= 1 + + elif error: + # Other retry? + if other is not None: + other -= 1 + + elif response and response.get_redirect_location(): + # Redirect retry? + if redirect is not None: + redirect -= 1 + cause = "too many redirects" + response_redirect_location = response.get_redirect_location() + if response_redirect_location: + redirect_location = response_redirect_location + status = response.status + + else: + # Incrementing because of a server error like a 500 in + # status_forcelist and the given method is in the allowed_methods + cause = ResponseError.GENERIC_ERROR + if response and response.status: + if status_count is not None: + status_count -= 1 + cause = ResponseError.SPECIFIC_ERROR.format(status_code=response.status) + status = response.status + + history = self.history + ( + RequestHistory(method, url, error, status, redirect_location), + ) + + new_retry = self.new( + total=total, + connect=connect, + read=read, + redirect=redirect, + status=status_count, + other=other, + history=history, + ) + + if new_retry.is_exhausted(): + reason = error or ResponseError(cause) + raise MaxRetryError(_pool, url, reason) from reason # type: ignore[arg-type] + + log.debug("Incremented Retry for (url='%s'): %r", url, new_retry) + + return new_retry + + def __repr__(self) -> str: + return ( + f"{type(self).__name__}(total={self.total}, connect={self.connect}, " + f"read={self.read}, redirect={self.redirect}, status={self.status})" + ) + + +# For backwards compatibility (equivalent to pre-v1.9): +Retry.DEFAULT = Retry(3) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/ssl_.py b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/ssl_.py new file mode 100644 index 0000000000000000000000000000000000000000..e66549a76c4b5821e639e5facfeb63dd1a39d543 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/ssl_.py @@ -0,0 +1,477 @@ +from __future__ import annotations + +import hashlib +import hmac +import os +import socket +import sys +import typing +import warnings +from binascii import unhexlify + +from ..exceptions import ProxySchemeUnsupported, SSLError +from .url import _BRACELESS_IPV6_ADDRZ_RE, _IPV4_RE + +SSLContext = None +SSLTransport = None +HAS_NEVER_CHECK_COMMON_NAME = False +IS_PYOPENSSL = False +ALPN_PROTOCOLS = ["http/1.1"] + +_TYPE_VERSION_INFO = tuple[int, int, int, str, int] + +# Maps the length of a digest to a possible hash function producing this digest +HASHFUNC_MAP = { + length: getattr(hashlib, algorithm, None) + for length, algorithm in ((32, "md5"), (40, "sha1"), (64, "sha256")) +} + + +def _is_has_never_check_common_name_reliable( + openssl_version: str, +) -> bool: + # As of May 2023, all released versions of LibreSSL fail to reject certificates with + # only common names, see https://github.com/urllib3/urllib3/pull/3024 + is_openssl = openssl_version.startswith("OpenSSL ") + + return is_openssl + + +if typing.TYPE_CHECKING: + from ssl import VerifyMode + from typing import TypedDict + + from .ssltransport import SSLTransport as SSLTransportType + + class _TYPE_PEER_CERT_RET_DICT(TypedDict, total=False): + subjectAltName: tuple[tuple[str, str], ...] + subject: tuple[tuple[tuple[str, str], ...], ...] + serialNumber: str + + +# Mapping from 'ssl.PROTOCOL_TLSX' to 'TLSVersion.X' +_SSL_VERSION_TO_TLS_VERSION: dict[int, int] = {} + +try: # Do we have ssl at all? + import ssl + from ssl import ( # type: ignore[assignment] + CERT_REQUIRED, + HAS_NEVER_CHECK_COMMON_NAME, + OP_NO_COMPRESSION, + OP_NO_TICKET, + OPENSSL_VERSION, + PROTOCOL_TLS, + PROTOCOL_TLS_CLIENT, + VERIFY_X509_PARTIAL_CHAIN, + VERIFY_X509_STRICT, + OP_NO_SSLv2, + OP_NO_SSLv3, + SSLContext, + TLSVersion, + ) + + PROTOCOL_SSLv23 = PROTOCOL_TLS + + # Setting SSLContext.hostname_checks_common_name = False didn't work with + # LibreSSL, check details in the used function. + if HAS_NEVER_CHECK_COMMON_NAME and not _is_has_never_check_common_name_reliable( + OPENSSL_VERSION, + ): # Defensive: + HAS_NEVER_CHECK_COMMON_NAME = False + + # Need to be careful here in case old TLS versions get + # removed in future 'ssl' module implementations. + for attr in ("TLSv1", "TLSv1_1", "TLSv1_2"): + try: + _SSL_VERSION_TO_TLS_VERSION[getattr(ssl, f"PROTOCOL_{attr}")] = getattr( + TLSVersion, attr + ) + except AttributeError: # Defensive: + continue + + from .ssltransport import SSLTransport # type: ignore[assignment] +except ImportError: + OP_NO_COMPRESSION = 0x20000 # type: ignore[assignment, misc] + OP_NO_TICKET = 0x4000 # type: ignore[assignment, misc] + OP_NO_SSLv2 = 0x1000000 # type: ignore[assignment, misc] + OP_NO_SSLv3 = 0x2000000 # type: ignore[assignment, misc] + PROTOCOL_SSLv23 = PROTOCOL_TLS = 2 # type: ignore[assignment, misc] + PROTOCOL_TLS_CLIENT = 16 # type: ignore[assignment, misc] + VERIFY_X509_PARTIAL_CHAIN = 0x80000 # type: ignore[assignment,misc] + VERIFY_X509_STRICT = 0x20 # type: ignore[assignment, misc] + + +_TYPE_PEER_CERT_RET = typing.Union["_TYPE_PEER_CERT_RET_DICT", bytes, None] + + +def assert_fingerprint(cert: bytes | None, fingerprint: str) -> None: + """ + Checks if given fingerprint matches the supplied certificate. + + :param cert: + Certificate as bytes object. + :param fingerprint: + Fingerprint as string of hexdigits, can be interspersed by colons. + """ + + if cert is None: + raise SSLError("No certificate for the peer.") + + fingerprint = fingerprint.replace(":", "").lower() + digest_length = len(fingerprint) + if digest_length not in HASHFUNC_MAP: + raise SSLError(f"Fingerprint of invalid length: {fingerprint}") + hashfunc = HASHFUNC_MAP.get(digest_length) + if hashfunc is None: + raise SSLError( + f"Hash function implementation unavailable for fingerprint length: {digest_length}" + ) + + # We need encode() here for py32; works on py2 and p33. + fingerprint_bytes = unhexlify(fingerprint.encode()) + + cert_digest = hashfunc(cert).digest() + + if not hmac.compare_digest(cert_digest, fingerprint_bytes): + raise SSLError( + f'Fingerprints did not match. Expected "{fingerprint}", got "{cert_digest.hex()}"' + ) + + +def resolve_cert_reqs(candidate: None | int | str) -> VerifyMode: + """ + Resolves the argument to a numeric constant, which can be passed to + the wrap_socket function/method from the ssl module. + Defaults to :data:`ssl.CERT_REQUIRED`. + If given a string it is assumed to be the name of the constant in the + :mod:`ssl` module or its abbreviation. + (So you can specify `REQUIRED` instead of `CERT_REQUIRED`. + If it's neither `None` nor a string we assume it is already the numeric + constant which can directly be passed to wrap_socket. + """ + if candidate is None: + return CERT_REQUIRED + + if isinstance(candidate, str): + res = getattr(ssl, candidate, None) + if res is None: + res = getattr(ssl, "CERT_" + candidate) + return res # type: ignore[no-any-return] + + return candidate # type: ignore[return-value] + + +def resolve_ssl_version(candidate: None | int | str) -> int: + """ + like resolve_cert_reqs + """ + if candidate is None: + return PROTOCOL_TLS + + if isinstance(candidate, str): + res = getattr(ssl, candidate, None) + if res is None: + res = getattr(ssl, "PROTOCOL_" + candidate) + return typing.cast(int, res) + + return candidate + + +def create_urllib3_context( + ssl_version: int | None = None, + cert_reqs: int | None = None, + options: int | None = None, + ciphers: str | None = None, + ssl_minimum_version: int | None = None, + ssl_maximum_version: int | None = None, + verify_flags: int | None = None, +) -> ssl.SSLContext: + """Creates and configures an :class:`ssl.SSLContext` instance for use with urllib3. + + :param ssl_version: + The desired protocol version to use. This will default to + PROTOCOL_SSLv23 which will negotiate the highest protocol that both + the server and your installation of OpenSSL support. + + This parameter is deprecated instead use 'ssl_minimum_version'. + :param ssl_minimum_version: + The minimum version of TLS to be used. Use the 'ssl.TLSVersion' enum for specifying the value. + :param ssl_maximum_version: + The maximum version of TLS to be used. Use the 'ssl.TLSVersion' enum for specifying the value. + Not recommended to set to anything other than 'ssl.TLSVersion.MAXIMUM_SUPPORTED' which is the + default value. + :param cert_reqs: + Whether to require the certificate verification. This defaults to + ``ssl.CERT_REQUIRED``. + :param options: + Specific OpenSSL options. These default to ``ssl.OP_NO_SSLv2``, + ``ssl.OP_NO_SSLv3``, ``ssl.OP_NO_COMPRESSION``, and ``ssl.OP_NO_TICKET``. + :param ciphers: + Which cipher suites to allow the server to select. Defaults to either system configured + ciphers if OpenSSL 1.1.1+, otherwise uses a secure default set of ciphers. + :param verify_flags: + The flags for certificate verification operations. These default to + ``ssl.VERIFY_X509_PARTIAL_CHAIN`` and ``ssl.VERIFY_X509_STRICT`` for Python 3.13+. + :returns: + Constructed SSLContext object with specified options + :rtype: SSLContext + """ + if SSLContext is None: + raise TypeError("Can't create an SSLContext object without an ssl module") + + # This means 'ssl_version' was specified as an exact value. + if ssl_version not in (None, PROTOCOL_TLS, PROTOCOL_TLS_CLIENT): + # Disallow setting 'ssl_version' and 'ssl_minimum|maximum_version' + # to avoid conflicts. + if ssl_minimum_version is not None or ssl_maximum_version is not None: + raise ValueError( + "Can't specify both 'ssl_version' and either " + "'ssl_minimum_version' or 'ssl_maximum_version'" + ) + + # 'ssl_version' is deprecated and will be removed in the future. + else: + # Use 'ssl_minimum_version' and 'ssl_maximum_version' instead. + ssl_minimum_version = _SSL_VERSION_TO_TLS_VERSION.get( + ssl_version, TLSVersion.MINIMUM_SUPPORTED + ) + ssl_maximum_version = _SSL_VERSION_TO_TLS_VERSION.get( + ssl_version, TLSVersion.MAXIMUM_SUPPORTED + ) + + # This warning message is pushing users to use 'ssl_minimum_version' + # instead of both min/max. Best practice is to only set the minimum version and + # keep the maximum version to be it's default value: 'TLSVersion.MAXIMUM_SUPPORTED' + warnings.warn( + "'ssl_version' option is deprecated and will be " + "removed in urllib3 v3.0. Instead use 'ssl_minimum_version'", + category=FutureWarning, + stacklevel=2, + ) + + context = SSLContext(PROTOCOL_TLS_CLIENT) + if ssl_minimum_version is not None: + context.minimum_version = ssl_minimum_version + else: # pyOpenSSL defaults to 'MINIMUM_SUPPORTED' so explicitly set TLSv1.2 here + context.minimum_version = TLSVersion.TLSv1_2 + + if ssl_maximum_version is not None: + context.maximum_version = ssl_maximum_version + + # Unless we're given ciphers defer to either system ciphers in + # the case of OpenSSL 1.1.1+ or use our own secure default ciphers. + if ciphers: + context.set_ciphers(ciphers) + + # Setting the default here, as we may have no ssl module on import + cert_reqs = ssl.CERT_REQUIRED if cert_reqs is None else cert_reqs + + if options is None: + options = 0 + # SSLv2 is easily broken and is considered harmful and dangerous + options |= OP_NO_SSLv2 + # SSLv3 has several problems and is now dangerous + options |= OP_NO_SSLv3 + # Disable compression to prevent CRIME attacks for OpenSSL 1.0+ + # (issue #309) + options |= OP_NO_COMPRESSION + # TLSv1.2 only. Unless set explicitly, do not request tickets. + # This may save some bandwidth on wire, and although the ticket is encrypted, + # there is a risk associated with it being on wire, + # if the server is not rotating its ticketing keys properly. + options |= OP_NO_TICKET + + context.options |= options + + if verify_flags is None: + verify_flags = 0 + # In Python 3.13+ ssl.create_default_context() sets VERIFY_X509_PARTIAL_CHAIN + # and VERIFY_X509_STRICT so we do the same + if sys.version_info >= (3, 13): + verify_flags |= VERIFY_X509_PARTIAL_CHAIN + verify_flags |= VERIFY_X509_STRICT + + context.verify_flags |= verify_flags + + # Enable post-handshake authentication for TLS 1.3, see GH #1634. PHA is + # necessary for conditional client cert authentication with TLS 1.3. + # The attribute is None for OpenSSL <= 1.1.0 or does not exist when using + # an SSLContext created by pyOpenSSL. + if getattr(context, "post_handshake_auth", None) is not None: + context.post_handshake_auth = True + + # The order of the below lines setting verify_mode and check_hostname + # matter due to safe-guards SSLContext has to prevent an SSLContext with + # check_hostname=True, verify_mode=NONE/OPTIONAL. + # We always set 'check_hostname=False' for pyOpenSSL so we rely on our own + # 'ssl.match_hostname()' implementation. + if cert_reqs == ssl.CERT_REQUIRED and not IS_PYOPENSSL: + context.verify_mode = cert_reqs + context.check_hostname = True + else: + context.check_hostname = False + context.verify_mode = cert_reqs + + context.hostname_checks_common_name = False + + if "SSLKEYLOGFILE" in os.environ: + sslkeylogfile = os.path.expandvars(os.environ.get("SSLKEYLOGFILE")) + else: + sslkeylogfile = None + if sslkeylogfile: + context.keylog_filename = sslkeylogfile + + return context + + +@typing.overload +def ssl_wrap_socket( + sock: socket.socket, + keyfile: str | None = ..., + certfile: str | None = ..., + cert_reqs: int | None = ..., + ca_certs: str | None = ..., + server_hostname: str | None = ..., + ssl_version: int | None = ..., + ciphers: str | None = ..., + ssl_context: ssl.SSLContext | None = ..., + ca_cert_dir: str | None = ..., + key_password: str | None = ..., + ca_cert_data: None | str | bytes = ..., + tls_in_tls: typing.Literal[False] = ..., +) -> ssl.SSLSocket: ... + + +@typing.overload +def ssl_wrap_socket( + sock: socket.socket, + keyfile: str | None = ..., + certfile: str | None = ..., + cert_reqs: int | None = ..., + ca_certs: str | None = ..., + server_hostname: str | None = ..., + ssl_version: int | None = ..., + ciphers: str | None = ..., + ssl_context: ssl.SSLContext | None = ..., + ca_cert_dir: str | None = ..., + key_password: str | None = ..., + ca_cert_data: None | str | bytes = ..., + tls_in_tls: bool = ..., +) -> ssl.SSLSocket | SSLTransportType: ... + + +def ssl_wrap_socket( + sock: socket.socket, + keyfile: str | None = None, + certfile: str | None = None, + cert_reqs: int | None = None, + ca_certs: str | None = None, + server_hostname: str | None = None, + ssl_version: int | None = None, + ciphers: str | None = None, + ssl_context: ssl.SSLContext | None = None, + ca_cert_dir: str | None = None, + key_password: str | None = None, + ca_cert_data: None | str | bytes = None, + tls_in_tls: bool = False, +) -> ssl.SSLSocket | SSLTransportType: + """ + All arguments except for server_hostname, ssl_context, tls_in_tls, ca_cert_data and + ca_cert_dir have the same meaning as they do when using + :func:`ssl.create_default_context`, :meth:`ssl.SSLContext.load_cert_chain`, + :meth:`ssl.SSLContext.set_ciphers` and :meth:`ssl.SSLContext.wrap_socket`. + + :param server_hostname: + When SNI is supported, the expected hostname of the certificate + :param ssl_context: + A pre-made :class:`SSLContext` object. If none is provided, one will + be created using :func:`create_urllib3_context`. + :param ciphers: + A string of ciphers we wish the client to support. + :param ca_cert_dir: + A directory containing CA certificates in multiple separate files, as + supported by OpenSSL's -CApath flag or the capath argument to + SSLContext.load_verify_locations(). + :param key_password: + Optional password if the keyfile is encrypted. + :param ca_cert_data: + Optional string containing CA certificates in PEM format suitable for + passing as the cadata parameter to SSLContext.load_verify_locations() + :param tls_in_tls: + Use SSLTransport to wrap the existing socket. + """ + context = ssl_context + if context is None: + # Note: This branch of code and all the variables in it are only used in tests. + # We should consider deprecating and removing this code. + context = create_urllib3_context(ssl_version, cert_reqs, ciphers=ciphers) + + if ca_certs or ca_cert_dir or ca_cert_data: + try: + context.load_verify_locations(ca_certs, ca_cert_dir, ca_cert_data) + except OSError as e: + raise SSLError(e) from e + + elif ssl_context is None and hasattr(context, "load_default_certs"): + # try to load OS default certs; works well on Windows. + context.load_default_certs() + + # Attempt to detect if we get the goofy behavior of the + # keyfile being encrypted and OpenSSL asking for the + # passphrase via the terminal and instead error out. + if keyfile and key_password is None and _is_key_file_encrypted(keyfile): + raise SSLError("Client private key is encrypted, password is required") + + if certfile: + if key_password is None: + context.load_cert_chain(certfile, keyfile) + else: + context.load_cert_chain(certfile, keyfile, key_password) + + context.set_alpn_protocols(ALPN_PROTOCOLS) + + ssl_sock = _ssl_wrap_socket_impl(sock, context, tls_in_tls, server_hostname) + return ssl_sock + + +def is_ipaddress(hostname: str | bytes) -> bool: + """Detects whether the hostname given is an IPv4 or IPv6 address. + Also detects IPv6 addresses with Zone IDs. + + :param str hostname: Hostname to examine. + :return: True if the hostname is an IP address, False otherwise. + """ + if isinstance(hostname, bytes): + # IDN A-label bytes are ASCII compatible. + hostname = hostname.decode("ascii") + return bool(_IPV4_RE.match(hostname) or _BRACELESS_IPV6_ADDRZ_RE.match(hostname)) + + +def _is_key_file_encrypted(key_file: str) -> bool: + """Detects if a key file is encrypted or not.""" + with open(key_file) as f: + for line in f: + # Look for Proc-Type: 4,ENCRYPTED + if "ENCRYPTED" in line: + return True + + return False + + +def _ssl_wrap_socket_impl( + sock: socket.socket, + ssl_context: ssl.SSLContext, + tls_in_tls: bool, + server_hostname: str | None = None, +) -> ssl.SSLSocket | SSLTransportType: + if tls_in_tls: + if not SSLTransport: + # Import error, ssl is not available. + raise ProxySchemeUnsupported( + "TLS in TLS requires support for the 'ssl' module" + ) + + SSLTransport._validate_ssl_context_for_tls_in_tls(ssl_context) + return SSLTransport(sock, ssl_context, server_hostname) + + return ssl_context.wrap_socket(sock, server_hostname=server_hostname) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/ssl_match_hostname.py b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/ssl_match_hostname.py new file mode 100644 index 0000000000000000000000000000000000000000..94994f25ae1570211c0b98353790f669ef4585fa --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/ssl_match_hostname.py @@ -0,0 +1,153 @@ +"""The match_hostname() function from Python 3.5, essential when using SSL.""" + +# Note: This file is under the PSF license as the code comes from the python +# stdlib. http://docs.python.org/3/license.html +# It is modified to remove commonName support. + +from __future__ import annotations + +import ipaddress +import re +import typing +from ipaddress import IPv4Address, IPv6Address + +if typing.TYPE_CHECKING: + from .ssl_ import _TYPE_PEER_CERT_RET_DICT + +__version__ = "3.5.0.1" + + +class CertificateError(ValueError): + pass + + +def _dnsname_match( + dn: typing.Any, hostname: str, max_wildcards: int = 1 +) -> typing.Match[str] | None | bool: + """Matching according to RFC 6125, section 6.4.3 + + http://tools.ietf.org/html/rfc6125#section-6.4.3 + """ + pats = [] + if not dn: + return False + + # Ported from python3-syntax: + # leftmost, *remainder = dn.split(r'.') + parts = dn.split(r".") + leftmost = parts[0] + remainder = parts[1:] + + wildcards = leftmost.count("*") + if wildcards > max_wildcards: + # Issue #17980: avoid denials of service by refusing more + # than one wildcard per fragment. A survey of established + # policy among SSL implementations showed it to be a + # reasonable choice. + raise CertificateError( + "too many wildcards in certificate DNS name: " + repr(dn) + ) + + # speed up common case w/o wildcards + if not wildcards: + return bool(dn.lower() == hostname.lower()) + + # RFC 6125, section 6.4.3, subitem 1. + # The client SHOULD NOT attempt to match a presented identifier in which + # the wildcard character comprises a label other than the left-most label. + if leftmost == "*": + # When '*' is a fragment by itself, it matches a non-empty dotless + # fragment. + pats.append("[^.]+") + elif leftmost.startswith("xn--") or hostname.startswith("xn--"): + # RFC 6125, section 6.4.3, subitem 3. + # The client SHOULD NOT attempt to match a presented identifier + # where the wildcard character is embedded within an A-label or + # U-label of an internationalized domain name. + pats.append(re.escape(leftmost)) + else: + # Otherwise, '*' matches any dotless string, e.g. www* + pats.append(re.escape(leftmost).replace(r"\*", "[^.]*")) + + # add the remaining fragments, ignore any wildcards + for frag in remainder: + pats.append(re.escape(frag)) + + pat = re.compile(r"\A" + r"\.".join(pats) + r"\Z", re.IGNORECASE) + return pat.match(hostname) + + +def _ipaddress_match(ipname: str, host_ip: IPv4Address | IPv6Address) -> bool: + """Exact matching of IP addresses. + + RFC 9110 section 4.3.5: "A reference identity of IP-ID contains the decoded + bytes of the IP address. An IP version 4 address is 4 octets, and an IP + version 6 address is 16 octets. [...] A reference identity of type IP-ID + matches if the address is identical to an iPAddress value of the + subjectAltName extension of the certificate." + """ + # OpenSSL may add a trailing newline to a subjectAltName's IP address + # Divergence from upstream: ipaddress can't handle byte str + ip = ipaddress.ip_address(ipname.rstrip()) + return bool(ip.packed == host_ip.packed) + + +def match_hostname( + cert: _TYPE_PEER_CERT_RET_DICT | None, + hostname: str, + hostname_checks_common_name: bool = False, +) -> None: + """Verify that *cert* (in decoded format as returned by + SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125 + rules are followed, but IP addresses are not accepted for *hostname*. + + CertificateError is raised on failure. On success, the function + returns nothing. + """ + if not cert: + raise ValueError( + "empty or no certificate, match_hostname needs a " + "SSL socket or SSL context with either " + "CERT_OPTIONAL or CERT_REQUIRED" + ) + + try: + host_ip = ipaddress.ip_address(hostname) + except ValueError: + # Not an IP address (common case) + host_ip = None + dnsnames = [] + san: tuple[tuple[str, str], ...] = cert.get("subjectAltName", ()) + key: str + value: str + for key, value in san: + if key == "DNS": + if host_ip is None and _dnsname_match(value, hostname): + return + dnsnames.append(value) + elif key == "IP Address": + if host_ip is not None and _ipaddress_match(value, host_ip): + return + dnsnames.append(value) + + # We only check 'commonName' if it's enabled and we're not verifying + # an IP address. IP addresses aren't valid within 'commonName'. + if hostname_checks_common_name and host_ip is None and not dnsnames: + for sub in cert.get("subject", ()): + for key, value in sub: + if key == "commonName": + if _dnsname_match(value, hostname): + return + dnsnames.append( + value + ) # Defensive: for older PyPy and OpenSSL versions + + if len(dnsnames) > 1: + raise CertificateError( + "hostname %r " + "doesn't match either of %s" % (hostname, ", ".join(map(repr, dnsnames))) + ) + elif len(dnsnames) == 1: + raise CertificateError(f"hostname {hostname!r} doesn't match {dnsnames[0]!r}") + else: + raise CertificateError("no appropriate subjectAltName fields were found") diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/ssltransport.py b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/ssltransport.py new file mode 100644 index 0000000000000000000000000000000000000000..6d59bc3bce2489c3a0aa5bcb83b737dcf33c033b --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/ssltransport.py @@ -0,0 +1,271 @@ +from __future__ import annotations + +import io +import socket +import ssl +import typing + +from ..exceptions import ProxySchemeUnsupported + +if typing.TYPE_CHECKING: + from typing_extensions import Self + + from .ssl_ import _TYPE_PEER_CERT_RET, _TYPE_PEER_CERT_RET_DICT + + +_WriteBuffer = typing.Union[bytearray, memoryview] +_ReturnValue = typing.TypeVar("_ReturnValue") + +SSL_BLOCKSIZE = 16384 + + +class SSLTransport: + """ + The SSLTransport wraps an existing socket and establishes an SSL connection. + + Contrary to Python's implementation of SSLSocket, it allows you to chain + multiple TLS connections together. It's particularly useful if you need to + implement TLS within TLS. + + The class supports most of the socket API operations. + """ + + @staticmethod + def _validate_ssl_context_for_tls_in_tls(ssl_context: ssl.SSLContext) -> None: + """ + Raises a ProxySchemeUnsupported if the provided ssl_context can't be used + for TLS in TLS. + + The only requirement is that the ssl_context provides the 'wrap_bio' + methods. + """ + + if not hasattr(ssl_context, "wrap_bio"): + raise ProxySchemeUnsupported( + "TLS in TLS requires SSLContext.wrap_bio() which isn't " + "available on non-native SSLContext" + ) + + def __init__( + self, + socket: socket.socket, + ssl_context: ssl.SSLContext, + server_hostname: str | None = None, + suppress_ragged_eofs: bool = True, + ) -> None: + """ + Create an SSLTransport around socket using the provided ssl_context. + """ + self.incoming = ssl.MemoryBIO() + self.outgoing = ssl.MemoryBIO() + + self.suppress_ragged_eofs = suppress_ragged_eofs + self.socket = socket + + self.sslobj = ssl_context.wrap_bio( + self.incoming, self.outgoing, server_hostname=server_hostname + ) + + # Perform initial handshake. + self._ssl_io_loop(self.sslobj.do_handshake) + + def __enter__(self) -> Self: + return self + + def __exit__(self, *_: typing.Any) -> None: + self.close() + + def fileno(self) -> int: + return self.socket.fileno() + + def read(self, len: int = 1024, buffer: typing.Any | None = None) -> int | bytes: + return self._wrap_ssl_read(len, buffer) + + def recv(self, buflen: int = 1024, flags: int = 0) -> int | bytes: + if flags != 0: + raise ValueError("non-zero flags not allowed in calls to recv") + return self._wrap_ssl_read(buflen) + + def recv_into( + self, + buffer: _WriteBuffer, + nbytes: int | None = None, + flags: int = 0, + ) -> None | int | bytes: + if flags != 0: + raise ValueError("non-zero flags not allowed in calls to recv_into") + if nbytes is None: + nbytes = len(buffer) + return self.read(nbytes, buffer) + + def sendall(self, data: bytes, flags: int = 0) -> None: + if flags != 0: + raise ValueError("non-zero flags not allowed in calls to sendall") + count = 0 + with memoryview(data) as view, view.cast("B") as byte_view: + amount = len(byte_view) + while count < amount: + v = self.send(byte_view[count:]) + count += v + + def send(self, data: bytes, flags: int = 0) -> int: + if flags != 0: + raise ValueError("non-zero flags not allowed in calls to send") + return self._ssl_io_loop(self.sslobj.write, data) + + def makefile( + self, + mode: str, + buffering: int | None = None, + *, + encoding: str | None = None, + errors: str | None = None, + newline: str | None = None, + ) -> typing.BinaryIO | typing.TextIO | socket.SocketIO: + """ + Python's httpclient uses makefile and buffered io when reading HTTP + messages and we need to support it. + + This is unfortunately a copy and paste of socket.py makefile with small + changes to point to the socket directly. + """ + if not set(mode) <= {"r", "w", "b"}: + raise ValueError(f"invalid mode {mode!r} (only r, w, b allowed)") + + writing = "w" in mode + reading = "r" in mode or not writing + assert reading or writing + binary = "b" in mode + rawmode = "" + if reading: + rawmode += "r" + if writing: + rawmode += "w" + raw = socket.SocketIO(self, rawmode) # type: ignore[arg-type] + self.socket._io_refs += 1 # type: ignore[attr-defined] + if buffering is None: + buffering = -1 + if buffering < 0: + buffering = io.DEFAULT_BUFFER_SIZE + if buffering == 0: + if not binary: + raise ValueError("unbuffered streams must be binary") + return raw + buffer: typing.BinaryIO + if reading and writing: + buffer = io.BufferedRWPair(raw, raw, buffering) # type: ignore[assignment] + elif reading: + buffer = io.BufferedReader(raw, buffering) + else: + assert writing + buffer = io.BufferedWriter(raw, buffering) + if binary: + return buffer + text = io.TextIOWrapper(buffer, encoding, errors, newline) + text.mode = mode # type: ignore[misc] + return text + + def unwrap(self) -> None: + self._ssl_io_loop(self.sslobj.unwrap) + + def close(self) -> None: + self.socket.close() + + @typing.overload + def getpeercert( + self, binary_form: typing.Literal[False] = ... + ) -> _TYPE_PEER_CERT_RET_DICT | None: ... + + @typing.overload + def getpeercert(self, binary_form: typing.Literal[True]) -> bytes | None: ... + + def getpeercert(self, binary_form: bool = False) -> _TYPE_PEER_CERT_RET: + return self.sslobj.getpeercert(binary_form) # type: ignore[return-value] + + def version(self) -> str | None: + return self.sslobj.version() + + def cipher(self) -> tuple[str, str, int] | None: + return self.sslobj.cipher() + + def selected_alpn_protocol(self) -> str | None: + return self.sslobj.selected_alpn_protocol() + + def shared_ciphers(self) -> list[tuple[str, str, int]] | None: + return self.sslobj.shared_ciphers() + + def compression(self) -> str | None: + return self.sslobj.compression() + + def settimeout(self, value: float | None) -> None: + self.socket.settimeout(value) + + def gettimeout(self) -> float | None: + return self.socket.gettimeout() + + def _decref_socketios(self) -> None: + self.socket._decref_socketios() # type: ignore[attr-defined] + + def _wrap_ssl_read(self, len: int, buffer: bytearray | None = None) -> int | bytes: + try: + return self._ssl_io_loop(self.sslobj.read, len, buffer) + except ssl.SSLError as e: + if e.errno == ssl.SSL_ERROR_EOF and self.suppress_ragged_eofs: + return 0 # eof, return 0. + else: + raise + + # func is sslobj.do_handshake or sslobj.unwrap + @typing.overload + def _ssl_io_loop(self, func: typing.Callable[[], None]) -> None: ... + + # func is sslobj.write, arg1 is data + @typing.overload + def _ssl_io_loop(self, func: typing.Callable[[bytes], int], arg1: bytes) -> int: ... + + # func is sslobj.read, arg1 is len, arg2 is buffer + @typing.overload + def _ssl_io_loop( + self, + func: typing.Callable[[int, bytearray | None], bytes], + arg1: int, + arg2: bytearray | None, + ) -> bytes: ... + + def _ssl_io_loop( + self, + func: typing.Callable[..., _ReturnValue], + arg1: None | bytes | int = None, + arg2: bytearray | None = None, + ) -> _ReturnValue: + """Performs an I/O loop between incoming/outgoing and the socket.""" + should_loop = True + ret = None + + while should_loop: + errno = None + try: + if arg1 is None and arg2 is None: + ret = func() + elif arg2 is None: + ret = func(arg1) + else: + ret = func(arg1, arg2) + except ssl.SSLError as e: + if e.errno not in (ssl.SSL_ERROR_WANT_READ, ssl.SSL_ERROR_WANT_WRITE): + # WANT_READ, and WANT_WRITE are expected, others are not. + raise e + errno = e.errno + + buf = self.outgoing.read() + self.socket.sendall(buf) + + if errno is None: + should_loop = False + elif errno == ssl.SSL_ERROR_WANT_READ: + buf = self.socket.recv(SSL_BLOCKSIZE) + if buf: + self.incoming.write(buf) + else: + self.incoming.write_eof() + return typing.cast(_ReturnValue, ret) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/timeout.py b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/timeout.py new file mode 100644 index 0000000000000000000000000000000000000000..4bb1be11d9cb06900dd82ecebd06aa6a7c5de916 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/timeout.py @@ -0,0 +1,275 @@ +from __future__ import annotations + +import time +import typing +from enum import Enum +from socket import getdefaulttimeout + +from ..exceptions import TimeoutStateError + +if typing.TYPE_CHECKING: + from typing import Final + + +class _TYPE_DEFAULT(Enum): + # This value should never be passed to socket.settimeout() so for safety we use a -1. + # socket.settimout() raises a ValueError for negative values. + token = -1 + + +_DEFAULT_TIMEOUT: Final[_TYPE_DEFAULT] = _TYPE_DEFAULT.token + +_TYPE_TIMEOUT = typing.Optional[typing.Union[float, _TYPE_DEFAULT]] + + +class Timeout: + """Timeout configuration. + + Timeouts can be defined as a default for a pool: + + .. code-block:: python + + import urllib3 + + timeout = urllib3.util.Timeout(connect=2.0, read=7.0) + + http = urllib3.PoolManager(timeout=timeout) + + resp = http.request("GET", "https://example.com/") + + print(resp.status) + + Or per-request (which overrides the default for the pool): + + .. code-block:: python + + response = http.request("GET", "https://example.com/", timeout=Timeout(10)) + + Timeouts can be disabled by setting all the parameters to ``None``: + + .. code-block:: python + + no_timeout = Timeout(connect=None, read=None) + response = http.request("GET", "https://example.com/", timeout=no_timeout) + + + :param total: + This combines the connect and read timeouts into one; the read timeout + will be set to the time leftover from the connect attempt. In the + event that both a connect timeout and a total are specified, or a read + timeout and a total are specified, the shorter timeout will be applied. + + Defaults to None. + + :type total: int, float, or None + + :param connect: + The maximum amount of time (in seconds) to wait for a connection + attempt to a server to succeed. Omitting the parameter will default the + connect timeout to the system default, probably `the global default + timeout in socket.py + `_. + None will set an infinite timeout for connection attempts. + + :type connect: int, float, or None + + :param read: + The maximum amount of time (in seconds) to wait between consecutive + read operations for a response from the server. Omitting the parameter + will default the read timeout to the system default, probably `the + global default timeout in socket.py + `_. + None will set an infinite timeout. + + :type read: int, float, or None + + .. note:: + + Many factors can affect the total amount of time for urllib3 to return + an HTTP response. + + For example, Python's DNS resolver does not obey the timeout specified + on the socket. Other factors that can affect total request time include + high CPU load, high swap, the program running at a low priority level, + or other behaviors. + + In addition, the read and total timeouts only measure the time between + read operations on the socket connecting the client and the server, + not the total amount of time for the request to return a complete + response. For most requests, the timeout is raised because the server + has not sent the first byte in the specified time. This is not always + the case; if a server streams one byte every fifteen seconds, a timeout + of 20 seconds will not trigger, even though the request will take + several minutes to complete. + """ + + #: A sentinel object representing the default timeout value + DEFAULT_TIMEOUT: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT + + def __init__( + self, + total: _TYPE_TIMEOUT = None, + connect: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + read: _TYPE_TIMEOUT = _DEFAULT_TIMEOUT, + ) -> None: + self._connect = self._validate_timeout(connect, "connect") + self._read = self._validate_timeout(read, "read") + self.total = self._validate_timeout(total, "total") + self._start_connect: float | None = None + + def __repr__(self) -> str: + return f"{type(self).__name__}(connect={self._connect!r}, read={self._read!r}, total={self.total!r})" + + # __str__ provided for backwards compatibility + __str__ = __repr__ + + @staticmethod + def resolve_default_timeout(timeout: _TYPE_TIMEOUT) -> float | None: + return getdefaulttimeout() if timeout is _DEFAULT_TIMEOUT else timeout + + @classmethod + def _validate_timeout(cls, value: _TYPE_TIMEOUT, name: str) -> _TYPE_TIMEOUT: + """Check that a timeout attribute is valid. + + :param value: The timeout value to validate + :param name: The name of the timeout attribute to validate. This is + used to specify in error messages. + :return: The validated and casted version of the given value. + :raises ValueError: If it is a numeric value less than or equal to + zero, or the type is not an integer, float, or None. + """ + if value is None or value is _DEFAULT_TIMEOUT: + return value + + if isinstance(value, bool): + raise ValueError( + "Timeout cannot be a boolean value. It must " + "be an int, float or None." + ) + try: + float(value) + except (TypeError, ValueError): + raise ValueError( + "Timeout value %s was %s, but it must be an " + "int, float or None." % (name, value) + ) from None + + try: + if value <= 0: + raise ValueError( + "Attempted to set %s timeout to %s, but the " + "timeout cannot be set to a value less " + "than or equal to 0." % (name, value) + ) + except TypeError: + raise ValueError( + "Timeout value %s was %s, but it must be an " + "int, float or None." % (name, value) + ) from None + + return value + + @classmethod + def from_float(cls, timeout: _TYPE_TIMEOUT) -> Timeout: + """Create a new Timeout from a legacy timeout value. + + The timeout value used by httplib.py sets the same timeout on the + connect(), and recv() socket requests. This creates a :class:`Timeout` + object that sets the individual timeouts to the ``timeout`` value + passed to this function. + + :param timeout: The legacy timeout value. + :type timeout: integer, float, :attr:`urllib3.util.Timeout.DEFAULT_TIMEOUT`, or None + :return: Timeout object + :rtype: :class:`Timeout` + """ + return Timeout(read=timeout, connect=timeout) + + def clone(self) -> Timeout: + """Create a copy of the timeout object + + Timeout properties are stored per-pool but each request needs a fresh + Timeout object to ensure each one has its own start/stop configured. + + :return: a copy of the timeout object + :rtype: :class:`Timeout` + """ + # We can't use copy.deepcopy because that will also create a new object + # for _GLOBAL_DEFAULT_TIMEOUT, which socket.py uses as a sentinel to + # detect the user default. + return Timeout(connect=self._connect, read=self._read, total=self.total) + + def start_connect(self) -> float: + """Start the timeout clock, used during a connect() attempt + + :raises urllib3.exceptions.TimeoutStateError: if you attempt + to start a timer that has been started already. + """ + if self._start_connect is not None: + raise TimeoutStateError("Timeout timer has already been started.") + self._start_connect = time.monotonic() + return self._start_connect + + def get_connect_duration(self) -> float: + """Gets the time elapsed since the call to :meth:`start_connect`. + + :return: Elapsed time in seconds. + :rtype: float + :raises urllib3.exceptions.TimeoutStateError: if you attempt + to get duration for a timer that hasn't been started. + """ + if self._start_connect is None: + raise TimeoutStateError( + "Can't get connect duration for timer that has not started." + ) + return time.monotonic() - self._start_connect + + @property + def connect_timeout(self) -> _TYPE_TIMEOUT: + """Get the value to use when setting a connection timeout. + + This will be a positive float or integer, the value None + (never timeout), or the default system timeout. + + :return: Connect timeout. + :rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None + """ + if self.total is None: + return self._connect + + if self._connect is None or self._connect is _DEFAULT_TIMEOUT: + return self.total + + return min(self._connect, self.total) # type: ignore[type-var] + + @property + def read_timeout(self) -> float | None: + """Get the value for the read timeout. + + This assumes some time has elapsed in the connection timeout and + computes the read timeout appropriately. + + If self.total is set, the read timeout is dependent on the amount of + time taken by the connect timeout. If the connection time has not been + established, a :exc:`~urllib3.exceptions.TimeoutStateError` will be + raised. + + :return: Value to use for the read timeout. + :rtype: int, float or None + :raises urllib3.exceptions.TimeoutStateError: If :meth:`start_connect` + has not yet been called on this object. + """ + if ( + self.total is not None + and self.total is not _DEFAULT_TIMEOUT + and self._read is not None + and self._read is not _DEFAULT_TIMEOUT + ): + # In case the connect timeout has not yet been established. + if self._start_connect is None: + return self._read + return max(0, min(self.total - self.get_connect_duration(), self._read)) + elif self.total is not None and self.total is not _DEFAULT_TIMEOUT: + return max(0, self.total - self.get_connect_duration()) + else: + return self.resolve_default_timeout(self._read) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/url.py b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/url.py new file mode 100644 index 0000000000000000000000000000000000000000..db057f17be610174f30928748b5004dcbf6c501c --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/url.py @@ -0,0 +1,469 @@ +from __future__ import annotations + +import re +import typing + +from ..exceptions import LocationParseError +from .util import to_str + +# We only want to normalize urls with an HTTP(S) scheme. +# urllib3 infers URLs without a scheme (None) to be http. +_NORMALIZABLE_SCHEMES = ("http", "https", None) + +# Almost all of these patterns were derived from the +# 'rfc3986' module: https://github.com/python-hyper/rfc3986 +_PERCENT_RE = re.compile(r"%[a-fA-F0-9]{2}") +_SCHEME_RE = re.compile(r"^(?:[a-zA-Z][a-zA-Z0-9+-]*:|/)") +_URI_RE = re.compile( + r"^(?:([a-zA-Z][a-zA-Z0-9+.-]*):)?" + r"(?://([^\\/?#]*))?" + r"([^?#]*)" + r"(?:\?([^#]*))?" + r"(?:#(.*))?$", + re.UNICODE | re.DOTALL, +) + +_IPV4_PAT = r"(?:[0-9]{1,3}\.){3}[0-9]{1,3}" +_HEX_PAT = "[0-9A-Fa-f]{1,4}" +_LS32_PAT = "(?:{hex}:{hex}|{ipv4})".format(hex=_HEX_PAT, ipv4=_IPV4_PAT) +_subs = {"hex": _HEX_PAT, "ls32": _LS32_PAT} +_variations = [ + # 6( h16 ":" ) ls32 + "(?:%(hex)s:){6}%(ls32)s", + # "::" 5( h16 ":" ) ls32 + "::(?:%(hex)s:){5}%(ls32)s", + # [ h16 ] "::" 4( h16 ":" ) ls32 + "(?:%(hex)s)?::(?:%(hex)s:){4}%(ls32)s", + # [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32 + "(?:(?:%(hex)s:)?%(hex)s)?::(?:%(hex)s:){3}%(ls32)s", + # [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32 + "(?:(?:%(hex)s:){0,2}%(hex)s)?::(?:%(hex)s:){2}%(ls32)s", + # [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32 + "(?:(?:%(hex)s:){0,3}%(hex)s)?::%(hex)s:%(ls32)s", + # [ *4( h16 ":" ) h16 ] "::" ls32 + "(?:(?:%(hex)s:){0,4}%(hex)s)?::%(ls32)s", + # [ *5( h16 ":" ) h16 ] "::" h16 + "(?:(?:%(hex)s:){0,5}%(hex)s)?::%(hex)s", + # [ *6( h16 ":" ) h16 ] "::" + "(?:(?:%(hex)s:){0,6}%(hex)s)?::", +] + +_UNRESERVED_PAT = r"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._\-~" +_IPV6_PAT = "(?:" + "|".join([x % _subs for x in _variations]) + ")" +_ZONE_ID_PAT = "(?:%25|%)(?:[" + _UNRESERVED_PAT + "]|%[a-fA-F0-9]{2})+" +_IPV6_ADDRZ_PAT = r"\[" + _IPV6_PAT + r"(?:" + _ZONE_ID_PAT + r")?\]" +_REG_NAME_PAT = r"(?:[^\[\]%:/?#]|%[a-fA-F0-9]{2})*" +_TARGET_RE = re.compile(r"^(/[^?#]*)(?:\?([^#]*))?(?:#.*)?$") + +_IPV4_RE = re.compile("^" + _IPV4_PAT + "$") +_IPV6_RE = re.compile("^" + _IPV6_PAT + "$") +_IPV6_ADDRZ_RE = re.compile("^" + _IPV6_ADDRZ_PAT + "$") +_BRACELESS_IPV6_ADDRZ_RE = re.compile("^" + _IPV6_ADDRZ_PAT[2:-2] + "$") +_ZONE_ID_RE = re.compile("(" + _ZONE_ID_PAT + r")\]$") + +_HOST_PORT_PAT = ("^(%s|%s|%s)(?::0*?(|0|[1-9][0-9]{0,4}))?$") % ( + _REG_NAME_PAT, + _IPV4_PAT, + _IPV6_ADDRZ_PAT, +) +_HOST_PORT_RE = re.compile(_HOST_PORT_PAT, re.UNICODE | re.DOTALL) + +_UNRESERVED_CHARS = set( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._-~" +) +_SUB_DELIM_CHARS = set("!$&'()*+,;=") +_USERINFO_CHARS = _UNRESERVED_CHARS | _SUB_DELIM_CHARS | {":"} +_PATH_CHARS = _USERINFO_CHARS | {"@", "/"} +_QUERY_CHARS = _FRAGMENT_CHARS = _PATH_CHARS | {"?"} + + +class Url( + typing.NamedTuple( + "Url", + [ + ("scheme", typing.Optional[str]), + ("auth", typing.Optional[str]), + ("host", typing.Optional[str]), + ("port", typing.Optional[int]), + ("path", typing.Optional[str]), + ("query", typing.Optional[str]), + ("fragment", typing.Optional[str]), + ], + ) +): + """ + Data structure for representing an HTTP URL. Used as a return value for + :func:`parse_url`. Both the scheme and host are normalized as they are + both case-insensitive according to RFC 3986. + """ + + def __new__( # type: ignore[no-untyped-def] + cls, + scheme: str | None = None, + auth: str | None = None, + host: str | None = None, + port: int | None = None, + path: str | None = None, + query: str | None = None, + fragment: str | None = None, + ): + if path and not path.startswith("/"): + path = "/" + path + if scheme is not None: + scheme = scheme.lower() + return super().__new__(cls, scheme, auth, host, port, path, query, fragment) + + @property + def hostname(self) -> str | None: + """For backwards-compatibility with urlparse. We're nice like that.""" + return self.host + + @property + def request_uri(self) -> str: + """Absolute path including the query string.""" + uri = self.path or "/" + + if self.query is not None: + uri += "?" + self.query + + return uri + + @property + def authority(self) -> str | None: + """ + Authority component as defined in RFC 3986 3.2. + This includes userinfo (auth), host and port. + + i.e. + userinfo@host:port + """ + userinfo = self.auth + netloc = self.netloc + if netloc is None or userinfo is None: + return netloc + else: + return f"{userinfo}@{netloc}" + + @property + def netloc(self) -> str | None: + """ + Network location including host and port. + + If you need the equivalent of urllib.parse's ``netloc``, + use the ``authority`` property instead. + """ + if self.host is None: + return None + if self.port: + return f"{self.host}:{self.port}" + return self.host + + @property + def url(self) -> str: + """ + Convert self into a url + + This function should more or less round-trip with :func:`.parse_url`. The + returned url may not be exactly the same as the url inputted to + :func:`.parse_url`, but it should be equivalent by the RFC (e.g., urls + with a blank port will have : removed). + + Example: + + .. code-block:: python + + import urllib3 + + U = urllib3.util.parse_url("https://google.com/mail/") + + print(U.url) + # "https://google.com/mail/" + + print( urllib3.util.Url("https", "username:password", + "host.com", 80, "/path", "query", "fragment" + ).url + ) + # "https://username:password@host.com:80/path?query#fragment" + """ + scheme, auth, host, port, path, query, fragment = self + url = "" + + # We use "is not None" we want things to happen with empty strings (or 0 port) + if scheme is not None: + url += scheme + "://" + if auth is not None: + url += auth + "@" + if host is not None: + url += host + if port is not None: + url += ":" + str(port) + if path is not None: + url += path + if query is not None: + url += "?" + query + if fragment is not None: + url += "#" + fragment + + return url + + def __str__(self) -> str: + return self.url + + +@typing.overload +def _encode_invalid_chars( + component: str, allowed_chars: typing.Container[str] +) -> str: # Abstract + ... + + +@typing.overload +def _encode_invalid_chars( + component: None, allowed_chars: typing.Container[str] +) -> None: # Abstract + ... + + +def _encode_invalid_chars( + component: str | None, allowed_chars: typing.Container[str] +) -> str | None: + """Percent-encodes a URI component without reapplying + onto an already percent-encoded component. + """ + if component is None: + return component + + component = to_str(component) + + # Normalize existing percent-encoded bytes. + # Try to see if the component we're encoding is already percent-encoded + # so we can skip all '%' characters but still encode all others. + component, percent_encodings = _PERCENT_RE.subn( + lambda match: match.group(0).upper(), component + ) + + uri_bytes = component.encode("utf-8", "surrogatepass") + is_percent_encoded = percent_encodings == uri_bytes.count(b"%") + encoded_component = bytearray() + + for i in range(0, len(uri_bytes)): + # Will return a single character bytestring + byte = uri_bytes[i : i + 1] + byte_ord = ord(byte) + if (is_percent_encoded and byte == b"%") or ( + byte_ord < 128 and byte.decode() in allowed_chars + ): + encoded_component += byte + continue + encoded_component.extend(b"%" + (hex(byte_ord)[2:].encode().zfill(2).upper())) + + return encoded_component.decode() + + +def _remove_path_dot_segments(path: str) -> str: + # See http://tools.ietf.org/html/rfc3986#section-5.2.4 for pseudo-code + segments = path.split("/") # Turn the path into a list of segments + output = [] # Initialize the variable to use to store output + + for segment in segments: + # '.' is the current directory, so ignore it, it is superfluous + if segment == ".": + continue + # Anything other than '..', should be appended to the output + if segment != "..": + output.append(segment) + # In this case segment == '..', if we can, we should pop the last + # element + elif output: + output.pop() + + # If the path starts with '/' and the output is empty or the first string + # is non-empty + if path.startswith("/") and (not output or output[0]): + output.insert(0, "") + + # If the path starts with '/.' or '/..' ensure we add one more empty + # string to add a trailing '/' + if path.endswith(("/.", "/..")): + output.append("") + + return "/".join(output) + + +@typing.overload +def _normalize_host(host: None, scheme: str | None) -> None: ... + + +@typing.overload +def _normalize_host(host: str, scheme: str | None) -> str: ... + + +def _normalize_host(host: str | None, scheme: str | None) -> str | None: + if host: + if scheme in _NORMALIZABLE_SCHEMES: + is_ipv6 = _IPV6_ADDRZ_RE.match(host) + if is_ipv6: + # IPv6 hosts of the form 'a::b%zone' are encoded in a URL as + # such per RFC 6874: 'a::b%25zone'. Unquote the ZoneID + # separator as necessary to return a valid RFC 4007 scoped IP. + match = _ZONE_ID_RE.search(host) + if match: + start, end = match.span(1) + zone_id = host[start:end] + + if zone_id.startswith("%25") and zone_id != "%25": + zone_id = zone_id[3:] + else: + zone_id = zone_id[1:] + zone_id = _encode_invalid_chars(zone_id, _UNRESERVED_CHARS) + return f"{host[:start].lower()}%{zone_id}{host[end:]}" + else: + return host.lower() + elif not _IPV4_RE.match(host): + return to_str( + b".".join([_idna_encode(label) for label in host.split(".")]), + "ascii", + ) + return host + + +def _idna_encode(name: str) -> bytes: + if not name.isascii(): + try: + import idna + except ImportError: + raise LocationParseError( + "Unable to parse URL without the 'idna' module" + ) from None + + try: + return idna.encode(name.lower(), strict=True, std3_rules=True) + except idna.IDNAError: + raise LocationParseError( + f"Name '{name}' is not a valid IDNA label" + ) from None + + return name.lower().encode("ascii") + + +def _encode_target(target: str) -> str: + """Percent-encodes a request target so that there are no invalid characters + + Pre-condition for this function is that 'target' must start with '/'. + If that is the case then _TARGET_RE will always produce a match. + """ + match = _TARGET_RE.match(target) + if not match: # Defensive: + raise LocationParseError(f"{target!r} is not a valid request URI") + + path, query = match.groups() + encoded_target = _encode_invalid_chars(path, _PATH_CHARS) + if query is not None: + query = _encode_invalid_chars(query, _QUERY_CHARS) + encoded_target += "?" + query + return encoded_target + + +def parse_url(url: str) -> Url: + """ + Given a url, return a parsed :class:`.Url` namedtuple. Best-effort is + performed to parse incomplete urls. Fields not provided will be None. + This parser is RFC 3986 and RFC 6874 compliant. + + The parser logic and helper functions are based heavily on + work done in the ``rfc3986`` module. + + :param str url: URL to parse into a :class:`.Url` namedtuple. + + Partly backwards-compatible with :mod:`urllib.parse`. + + Example: + + .. code-block:: python + + import urllib3 + + print( urllib3.util.parse_url('http://google.com/mail/')) + # Url(scheme='http', host='google.com', port=None, path='/mail/', ...) + + print( urllib3.util.parse_url('google.com:80')) + # Url(scheme=None, host='google.com', port=80, path=None, ...) + + print( urllib3.util.parse_url('/foo?bar')) + # Url(scheme=None, host=None, port=None, path='/foo', query='bar', ...) + """ + if not url: + # Empty + return Url() + + source_url = url + if not _SCHEME_RE.search(url): + url = "//" + url + + scheme: str | None + authority: str | None + auth: str | None + host: str | None + port: str | None + port_int: int | None + path: str | None + query: str | None + fragment: str | None + + try: + scheme, authority, path, query, fragment = _URI_RE.match(url).groups() # type: ignore[union-attr] + normalize_uri = scheme is None or scheme.lower() in _NORMALIZABLE_SCHEMES + + if scheme: + scheme = scheme.lower() + + if authority: + auth, _, host_port = authority.rpartition("@") + auth = auth or None + host, port = _HOST_PORT_RE.match(host_port).groups() # type: ignore[union-attr] + if auth and normalize_uri: + auth = _encode_invalid_chars(auth, _USERINFO_CHARS) + if port == "": + port = None + else: + auth, host, port = None, None, None + + if port is not None: + port_int = int(port) + if not (0 <= port_int <= 65535): + raise LocationParseError(url) + else: + port_int = None + + host = _normalize_host(host, scheme) + + if normalize_uri and path: + path = _remove_path_dot_segments(path) + path = _encode_invalid_chars(path, _PATH_CHARS) + if normalize_uri and query: + query = _encode_invalid_chars(query, _QUERY_CHARS) + if normalize_uri and fragment: + fragment = _encode_invalid_chars(fragment, _FRAGMENT_CHARS) + + except (ValueError, AttributeError) as e: + raise LocationParseError(source_url) from e + + # For the sake of backwards compatibility we put empty + # string values for path if there are any defined values + # beyond the path in the URL. + # TODO: Remove this when we break backwards compatibility. + if not path: + if query is not None or fragment is not None: + path = "" + else: + path = None + + return Url( + scheme=scheme, + auth=auth, + host=host, + port=port_int, + path=path, + query=query, + fragment=fragment, + ) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/util.py b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/util.py new file mode 100644 index 0000000000000000000000000000000000000000..35c77e4025842f548565334a3c04cba90f9283d6 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/util.py @@ -0,0 +1,42 @@ +from __future__ import annotations + +import typing +from types import TracebackType + + +def to_bytes( + x: str | bytes, encoding: str | None = None, errors: str | None = None +) -> bytes: + if isinstance(x, bytes): + return x + elif not isinstance(x, str): + raise TypeError(f"not expecting type {type(x).__name__}") + if encoding or errors: + return x.encode(encoding or "utf-8", errors=errors or "strict") + return x.encode() + + +def to_str( + x: str | bytes, encoding: str | None = None, errors: str | None = None +) -> str: + if isinstance(x, str): + return x + elif not isinstance(x, bytes): + raise TypeError(f"not expecting type {type(x).__name__}") + if encoding or errors: + return x.decode(encoding or "utf-8", errors=errors or "strict") + return x.decode() + + +def reraise( + tp: type[BaseException] | None, + value: BaseException, + tb: TracebackType | None = None, +) -> typing.NoReturn: + try: + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + finally: + value = None # type: ignore[assignment] + tb = None diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/wait.py b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/wait.py new file mode 100644 index 0000000000000000000000000000000000000000..aeca0c7ad5b232eeb1ad9c43d315bd1d74eaed9a --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/urllib3/util/wait.py @@ -0,0 +1,124 @@ +from __future__ import annotations + +import select +import socket +from functools import partial + +__all__ = ["wait_for_read", "wait_for_write"] + + +# How should we wait on sockets? +# +# There are two types of APIs you can use for waiting on sockets: the fancy +# modern stateful APIs like epoll/kqueue, and the older stateless APIs like +# select/poll. The stateful APIs are more efficient when you have a lots of +# sockets to keep track of, because you can set them up once and then use them +# lots of times. But we only ever want to wait on a single socket at a time +# and don't want to keep track of state, so the stateless APIs are actually +# more efficient. So we want to use select() or poll(). +# +# Now, how do we choose between select() and poll()? On traditional Unixes, +# select() has a strange calling convention that makes it slow, or fail +# altogether, for high-numbered file descriptors. The point of poll() is to fix +# that, so on Unixes, we prefer poll(). +# +# On Windows, there is no poll() (or at least Python doesn't provide a wrapper +# for it), but that's OK, because on Windows, select() doesn't have this +# strange calling convention; plain select() works fine. +# +# So: on Windows we use select(), and everywhere else we use poll(). We also +# fall back to select() in case poll() is somehow broken or missing. + + +def select_wait_for_socket( + sock: socket.socket, + read: bool = False, + write: bool = False, + timeout: float | None = None, +) -> bool: + if not read and not write: + raise RuntimeError("must specify at least one of read=True, write=True") + rcheck = [] + wcheck = [] + if read: + rcheck.append(sock) + if write: + wcheck.append(sock) + # When doing a non-blocking connect, most systems signal success by + # marking the socket writable. Windows, though, signals success by marked + # it as "exceptional". We paper over the difference by checking the write + # sockets for both conditions. (The stdlib selectors module does the same + # thing.) + fn = partial(select.select, rcheck, wcheck, wcheck) + rready, wready, xready = fn(timeout) + return bool(rready or wready or xready) + + +def poll_wait_for_socket( + sock: socket.socket, + read: bool = False, + write: bool = False, + timeout: float | None = None, +) -> bool: + if not read and not write: + raise RuntimeError("must specify at least one of read=True, write=True") + mask = 0 + if read: + mask |= select.POLLIN + if write: + mask |= select.POLLOUT + poll_obj = select.poll() + poll_obj.register(sock, mask) + + # For some reason, poll() takes timeout in milliseconds + def do_poll(t: float | None) -> list[tuple[int, int]]: + if t is not None: + t *= 1000 + return poll_obj.poll(t) + + return bool(do_poll(timeout)) + + +def _have_working_poll() -> bool: + # Apparently some systems have a select.poll that fails as soon as you try + # to use it, either due to strange configuration or broken monkeypatching + # from libraries like eventlet/greenlet. + try: + poll_obj = select.poll() + poll_obj.poll(0) + except (AttributeError, OSError): + return False + else: + return True + + +def wait_for_socket( + sock: socket.socket, + read: bool = False, + write: bool = False, + timeout: float | None = None, +) -> bool: + # We delay choosing which implementation to use until the first time we're + # called. We could do it at import time, but then we might make the wrong + # decision if someone goes wild with monkeypatching select.poll after + # we're imported. + global wait_for_socket + if _have_working_poll(): + wait_for_socket = poll_wait_for_socket + elif hasattr(select, "select"): + wait_for_socket = select_wait_for_socket + return wait_for_socket(sock, read, write, timeout) + + +def wait_for_read(sock: socket.socket, timeout: float | None = None) -> bool: + """Waits for reading to be available on a given socket. + Returns True if the socket is readable, or False if the timeout expired. + """ + return wait_for_socket(sock, read=True, timeout=timeout) + + +def wait_for_write(sock: socket.socket, timeout: float | None = None) -> bool: + """Waits for writing to be available on a given socket. + Returns True if the socket is readable, or False if the timeout expired. + """ + return wait_for_socket(sock, write=True, timeout=timeout) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/yarl-1.23.0.dist-info/INSTALLER b/bioagent-bench/.venv/lib/python3.14/site-packages/yarl-1.23.0.dist-info/INSTALLER new file mode 100644 index 0000000000000000000000000000000000000000..5c69047b2eb8235994febeeae1da4a82365a240a --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/yarl-1.23.0.dist-info/INSTALLER @@ -0,0 +1 @@ +uv \ No newline at end of file diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/yarl-1.23.0.dist-info/METADATA b/bioagent-bench/.venv/lib/python3.14/site-packages/yarl-1.23.0.dist-info/METADATA new file mode 100644 index 0000000000000000000000000000000000000000..4ee73af884f618eed53c9f8718bf6885e85e6a61 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/yarl-1.23.0.dist-info/METADATA @@ -0,0 +1,2609 @@ +Metadata-Version: 2.4 +Name: yarl +Version: 1.23.0 +Summary: Yet another URL library +Home-page: https://github.com/aio-libs/yarl +Author: Andrew Svetlov +Author-email: andrew.svetlov@gmail.com +Maintainer: aiohttp team +Maintainer-email: team@aiohttp.org +License: Apache-2.0 +Project-URL: Chat: Matrix, https://matrix.to/#/#aio-libs:matrix.org +Project-URL: Chat: Matrix Space, https://matrix.to/#/#aio-libs-space:matrix.org +Project-URL: CI: GitHub Workflows, https://github.com/aio-libs/yarl/actions?query=branch:master +Project-URL: Code of Conduct, https://github.com/aio-libs/.github/blob/master/CODE_OF_CONDUCT.md +Project-URL: Coverage: codecov, https://codecov.io/github/aio-libs/yarl +Project-URL: Docs: Changelog, https://yarl.aio-libs.org/en/latest/changes/ +Project-URL: Docs: RTD, https://yarl.aio-libs.org +Project-URL: GitHub: issues, https://github.com/aio-libs/yarl/issues +Project-URL: GitHub: repo, https://github.com/aio-libs/yarl +Keywords: cython,cext,yarl +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: Programming Language :: Cython +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: 3.14 +Classifier: Topic :: Internet :: WWW/HTTP +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Requires-Python: >=3.10 +Description-Content-Type: text/x-rst +License-File: LICENSE +License-File: NOTICE +Requires-Dist: idna>=2.0 +Requires-Dist: multidict>=4.0 +Requires-Dist: propcache>=0.2.1 +Dynamic: license-file + +yarl +==== + +The module provides handy URL class for URL parsing and changing. + +.. image:: https://github.com/aio-libs/yarl/workflows/CI/badge.svg + :target: https://github.com/aio-libs/yarl/actions?query=workflow%3ACI + :align: right + +.. image:: https://codecov.io/gh/aio-libs/yarl/graph/badge.svg?flag=pytest + :target: https://app.codecov.io/gh/aio-libs/yarl?flags[]=pytest + :alt: Codecov coverage for the pytest-driven measurements + +.. image:: https://img.shields.io/endpoint?url=https://codspeed.io/badge.json + :target: https://codspeed.io/aio-libs/yarl + +.. image:: https://badge.fury.io/py/yarl.svg + :target: https://badge.fury.io/py/yarl + +.. image:: https://readthedocs.org/projects/yarl/badge/?version=latest + :target: https://yarl.aio-libs.org + +.. image:: https://img.shields.io/pypi/pyversions/yarl.svg + :target: https://pypi.python.org/pypi/yarl + +.. image:: https://img.shields.io/matrix/aio-libs:matrix.org?label=Discuss%20on%20Matrix%20at%20%23aio-libs%3Amatrix.org&logo=matrix&server_fqdn=matrix.org&style=flat + :target: https://matrix.to/#/%23aio-libs:matrix.org + :alt: Matrix Room — #aio-libs:matrix.org + +.. image:: https://img.shields.io/matrix/aio-libs-space:matrix.org?label=Discuss%20on%20Matrix%20at%20%23aio-libs-space%3Amatrix.org&logo=matrix&server_fqdn=matrix.org&style=flat + :target: https://matrix.to/#/%23aio-libs-space:matrix.org + :alt: Matrix Space — #aio-libs-space:matrix.org + + +Introduction +------------ + +Url is constructed from ``str``: + +.. code-block:: pycon + + >>> from yarl import URL + >>> url = URL('https://www.python.org/~guido?arg=1#frag') + >>> url + URL('https://www.python.org/~guido?arg=1#frag') + +All url parts: *scheme*, *user*, *password*, *host*, *port*, *path*, +*query* and *fragment* are accessible by properties: + +.. code-block:: pycon + + >>> url.scheme + 'https' + >>> url.host + 'www.python.org' + >>> url.path + '/~guido' + >>> url.query_string + 'arg=1' + >>> url.query + + >>> url.fragment + 'frag' + +All url manipulations produce a new url object: + +.. code-block:: pycon + + >>> url = URL('https://www.python.org') + >>> url / 'foo' / 'bar' + URL('https://www.python.org/foo/bar') + >>> url / 'foo' % {'bar': 'baz'} + URL('https://www.python.org/foo?bar=baz') + +Strings passed to constructor and modification methods are +automatically encoded giving canonical representation as result: + +.. code-block:: pycon + + >>> url = URL('https://www.python.org/шлях') + >>> url + URL('https://www.python.org/%D1%88%D0%BB%D1%8F%D1%85') + +Regular properties are *percent-decoded*, use ``raw_`` versions for +getting *encoded* strings: + +.. code-block:: pycon + + >>> url.path + '/шлях' + + >>> url.raw_path + '/%D1%88%D0%BB%D1%8F%D1%85' + +Human readable representation of URL is available as ``.human_repr()``: + +.. code-block:: pycon + + >>> url.human_repr() + 'https://www.python.org/шлях' + +For full documentation please read https://yarl.aio-libs.org. + + +Installation +------------ + +:: + + $ pip install yarl + +The library is Python 3 only! + +PyPI contains binary wheels for Linux, Windows and MacOS. If you want to install +``yarl`` on another operating system where wheels are not provided, +the tarball will be used to compile the library from +the source code. It requires a C compiler and and Python headers installed. + +To skip the compilation you must explicitly opt-in by using a PEP 517 +configuration setting ``pure-python``, or setting the ``YARL_NO_EXTENSIONS`` +environment variable to a non-empty value, e.g.: + +.. code-block:: console + + $ pip install yarl --config-settings=pure-python=false + +Please note that the pure-Python (uncompiled) version is much slower. However, +PyPy always uses a pure-Python implementation, and, as such, it is unaffected +by this variable. + +Dependencies +------------ + +YARL requires multidict_ and propcache_ libraries. + + +API documentation +------------------ + +The documentation is located at https://yarl.aio-libs.org. + + +Why isn't boolean supported by the URL query API? +------------------------------------------------- + +There is no standard for boolean representation of boolean values. + +Some systems prefer ``true``/``false``, others like ``yes``/``no``, ``on``/``off``, +``Y``/``N``, ``1``/``0``, etc. + +``yarl`` cannot make an unambiguous decision on how to serialize ``bool`` values because +it is specific to how the end-user's application is built and would be different for +different apps. The library doesn't accept booleans in the API; a user should convert +bools into strings using own preferred translation protocol. + + +Comparison with other URL libraries +------------------------------------ + +* furl (https://pypi.python.org/pypi/furl) + + The library has rich functionality but the ``furl`` object is mutable. + + I'm afraid to pass this object into foreign code: who knows if the + code will modify my url in a terrible way while I just want to send URL + with handy helpers for accessing URL properties. + + ``furl`` has other non-obvious tricky things but the main objection + is mutability. + +* URLObject (https://pypi.python.org/pypi/URLObject) + + URLObject is immutable, that's pretty good. + + Every URL change generates a new URL object. + + But the library doesn't do any decode/encode transformations leaving the + end user to cope with these gory details. + + +Source code +----------- + +The project is hosted on GitHub_ + +Please file an issue on the `bug tracker +`_ if you have found a bug +or have some suggestion in order to improve the library. + +Discussion list +--------------- + +*aio-libs* google group: https://groups.google.com/forum/#!forum/aio-libs + +Feel free to post your questions and ideas here. + + +Authors and License +------------------- + +The ``yarl`` package is written by Andrew Svetlov. + +It's *Apache 2* licensed and freely available. + + +.. _GitHub: https://github.com/aio-libs/yarl + +.. _multidict: https://github.com/aio-libs/multidict + +.. _propcache: https://github.com/aio-libs/propcache + +========= +Changelog +========= + +.. + You should *NOT* be adding new change log entries to this file, this + file is managed by towncrier. You *may* edit previous change logs to + fix problems like typo corrections or such. + To add a new change log entry, please see + https://pip.pypa.io/en/latest/development/#adding-a-news-entry + we named the news folder "changes". + + WARNING: Don't drop the next directive! + +.. towncrier release notes start + +1.23.0 +====== + +*(2025-12-16)* + + +Features +-------- + +- Added support for ``pydantic``, the ``~yarl.URL`` could be used as a + field type in ``pydantic`` models seamlessly. + + *Related issues and pull requests on GitHub:* + `#1607 `__. + + +Packaging updates and notes for downstreams +------------------------------------------- + +- The CI has been set up to notify Codecov about upload completion + -- by `@webknjaz `__. + + With this, Codecov no longer needs to guess whether it received all + the intended coverage reports or not. + + *Related issues and pull requests on GitHub:* + `#1577 `__. + +- The in-tree build backend allows the end-users appending + ``CFLAGS`` and ``LDFLAGS`` by setting respective environment + variables externally. + + It additionally sets up default compiler flags to perform + building with maximum optimization in release mode. This + makes the resulting artifacts shipped to PyPI smaller. + + When line tracing is requested, the compiler and linker + flags are configured to include as much information as + possible for debugging and coverage tracking. The + development builds are therefore smaller. + + -- by `@webknjaz `__ + + *Related issues and pull requests on GitHub:* + `#1586 `__. + +- The `PEP 517 `__ build backend now supports a new config + setting for controlling whether to build the project in-tree + or in a temporary directory. It only affects wheels and is + set up to build in a temporary directory by default. It does + not affect editable wheel builds — they will keep being + built in-tree regardless. + + -- by `@webknjaz `__ + + Here's an example of using this setting: + + .. code-block:: console + + $ python -m build \ + --config-setting=build-inplace=true + + *Related issues and pull requests on GitHub:* + `#1590 `__. + +- Starting this version, when building the wheels is happening + in an automatically created temporary directory, the build + backend makes an effort to normalize the respective file + system path to a deterministic source checkout directory. + + -- by `@webknjaz `__ + + It does so by injecting the ``-ffile-prefix-map`` compiler + option into the ``CFLAGS`` environment variable as suggested + by known `reproducible build practices + `__. + + The effect is that downstreams will get more reproducible + build results. + + *Related issues and pull requests on GitHub:* + `#1591 `__. + +- Dropped Python 3.9 support; Python 3.10 is the minimal supported Python version + -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1609 `__. + + +Contributor-facing changes +-------------------------- + +- The deprecated license classifier was removed from ``setup.cfg`` + -- by `@yegorich `__. + + *Related issues and pull requests on GitHub:* + `#1550 `__. + +- The in-tree build backend allows the end-users appending + ``CFLAGS`` and ``LDFLAGS`` by setting respective environment + variables externally. + + It additionally sets up default compiler flags to perform + building with maximum optimization in release mode. This + makes the resulting artifacts shipped to PyPI smaller. + + When line tracing is requested, the compiler and linker + flags are configured to include as much information as + possible for debugging and coverage tracking. The + development builds are therefore smaller. + + -- by `@webknjaz `__ + + *Related issues and pull requests on GitHub:* + `#1586 `__. + +- The CI has been updated to consistently benchmark optimized + release builds -- by `@webknjaz `__. + + When the release workflow is triggered, the pre-built wheels + ready to hit PyPI are being tested. Otherwise, the job + builds the project from source, while the rest of the + workflow uses debug builds for line tracing and coverage + collection. + + *Related issues and pull requests on GitHub:* + `#1587 `__. + + +---- + + +1.22.0 +====== + +*(2025-10-05)* + + +Features +-------- + +- Added arm64 Windows wheel builds + -- by `@finnagin `__. + + *Related issues and pull requests on GitHub:* + `#1516 `__. + + +---- + + +1.21.0 +====== + +*(2025-10-05)* + + +Contributor-facing changes +-------------------------- + +- The ``reusable-cibuildwheel.yml`` workflow has been refactored to + be more generic and ``ci-cd.yml`` now holds all the configuration + toggles -- by `@webknjaz `__. + + *Related issues and pull requests on GitHub:* + `#1535 `__. + +- When building wheels, the source distribution is now passed directly + to the ``cibuildwheel`` invocation -- by `@webknjaz `__. + + *Related issues and pull requests on GitHub:* + `#1536 `__. + +- Added CI for Python 3.14 -- by `@kumaraditya303 `__. + + *Related issues and pull requests on GitHub:* + `#1560 `__. + + +---- + + +1.20.1 +====== + +*(2025-06-09)* + + +Bug fixes +--------- + +- Started raising a ``ValueError`` exception raised for corrupted + IPv6 URL values. + + These fixes the issue where exception ``IndexError`` was + leaking from the internal code because of not being handled and + transformed into a user-facing error. The problem was happening + under the following conditions: empty IPv6 URL, brackets in + reverse order. + + -- by `@MaelPic `__. + + *Related issues and pull requests on GitHub:* + `#1512 `__. + + +Packaging updates and notes for downstreams +------------------------------------------- + +- Updated to use Cython 3.1 universally across the build path -- by `@lysnikolaou `__. + + *Related issues and pull requests on GitHub:* + `#1514 `__. + +- Made Cython line tracing opt-in via the ``with-cython-tracing`` build config setting -- by `@bdraco `__. + + Previously, line tracing was enabled by default in ``pyproject.toml``, which caused build issues for some users and made wheels nearly twice as slow. + Now line tracing is only enabled when explicitly requested via ``pip install . --config-setting=with-cython-tracing=true`` or by setting the ``YARL_CYTHON_TRACING`` environment variable. + + *Related issues and pull requests on GitHub:* + `#1521 `__. + + +---- + + +1.20.0 +====== + +*(2025-04-16)* + + +Features +-------- + +- Implemented support for the free-threaded build of CPython 3.13 -- by `@lysnikolaou `__. + + *Related issues and pull requests on GitHub:* + `#1456 `__. + + +Packaging updates and notes for downstreams +------------------------------------------- + +- Started building wheels for the free-threaded build of CPython 3.13 -- by `@lysnikolaou `__. + + *Related issues and pull requests on GitHub:* + `#1456 `__. + + +---- + + +1.19.0 +====== + +*(2025-04-05)* + + +Bug fixes +--------- + +- Fixed entire name being re-encoded when using ``yarl.URL.with_suffix()`` -- by `@NTFSvolume `__. + + *Related issues and pull requests on GitHub:* + `#1468 `__. + + +Features +-------- + +- Started building armv7l wheels for manylinux -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1495 `__. + + +Contributor-facing changes +-------------------------- + +- GitHub Actions CI/CD is now configured to manage caching pip-ecosystem + dependencies using `re-actors/cache-python-deps`_ -- an action by + `@webknjaz `__ that takes into account ABI stability and the exact + version of Python runtime. + + .. _`re-actors/cache-python-deps`: + https://github.com/marketplace/actions/cache-python-deps + + *Related issues and pull requests on GitHub:* + `#1471 `__. + +- Increased minimum `propcache`_ version to 0.2.1 to fix failing tests -- by `@bdraco `__. + + .. _`propcache`: + https://github.com/aio-libs/propcache + + *Related issues and pull requests on GitHub:* + `#1479 `__. + +- Added all hidden folders to pytest's ``norecursedirs`` to prevent it + from trying to collect tests there -- by `@lysnikolaou `__. + + *Related issues and pull requests on GitHub:* + `#1480 `__. + + +Miscellaneous internal changes +------------------------------ + +- Improved accuracy of type annotations -- by `@Dreamsorcerer `__. + + *Related issues and pull requests on GitHub:* + `#1484 `__. + +- Improved performance of parsing query strings -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1493 `__, `#1497 `__. + +- Improved performance of the C unquoter -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1496 `__, `#1498 `__. + + +---- + + +1.18.3 +====== + +*(2024-12-01)* + + +Bug fixes +--------- + +- Fixed uppercase ASCII hosts being rejected by ``URL.build()()`` and ``yarl.URL.with_host()`` -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#954 `__, `#1442 `__. + + +Miscellaneous internal changes +------------------------------ + +- Improved performances of multiple path properties on cache miss -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1443 `__. + + +---- + + +1.18.2 +====== + +*(2024-11-29)* + + +No significant changes. + + +---- + + +1.18.1 +====== + +*(2024-11-29)* + + +Miscellaneous internal changes +------------------------------ + +- Improved cache performance when ``~yarl.URL`` objects are constructed from ``yarl.URL.build()`` with ``encoded=True`` -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1432 `__. + +- Improved cache performance for operations that produce a new ``~yarl.URL`` object -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1434 `__, `#1436 `__. + + +---- + + +1.18.0 +====== + +*(2024-11-21)* + + +Features +-------- + +- Added ``keep_query`` and ``keep_fragment`` flags in the ``yarl.URL.with_path()``, ``yarl.URL.with_name()`` and ``yarl.URL.with_suffix()`` methods, allowing users to optionally retain the query string and fragment in the resulting URL when replacing the path -- by `@paul-nameless `__. + + *Related issues and pull requests on GitHub:* + `#111 `__, `#1421 `__. + + +Contributor-facing changes +-------------------------- + +- Started running downstream ``aiohttp`` tests in CI -- by `@Cycloctane `__. + + *Related issues and pull requests on GitHub:* + `#1415 `__. + + +Miscellaneous internal changes +------------------------------ + +- Improved performance of converting ``~yarl.URL`` to a string -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1422 `__. + + +---- + + +1.17.2 +====== + +*(2024-11-17)* + + +Bug fixes +--------- + +- Stopped implicitly allowing the use of Cython pre-release versions when + building the distribution package -- by `@ajsanchezsanz `__ and + `@markgreene74 `__. + + *Related issues and pull requests on GitHub:* + `#1411 `__, `#1412 `__. + +- Fixed a bug causing ``~yarl.URL.port`` to return the default port when the given port was zero + -- by `@gmacon `__. + + *Related issues and pull requests on GitHub:* + `#1413 `__. + + +Features +-------- + +- Make error messages include details of incorrect type when ``port`` is not int in ``yarl.URL.build()``. + -- by `@Cycloctane `__. + + *Related issues and pull requests on GitHub:* + `#1414 `__. + + +Packaging updates and notes for downstreams +------------------------------------------- + +- Stopped implicitly allowing the use of Cython pre-release versions when + building the distribution package -- by `@ajsanchezsanz `__ and + `@markgreene74 `__. + + *Related issues and pull requests on GitHub:* + `#1411 `__, `#1412 `__. + + +Miscellaneous internal changes +------------------------------ + +- Improved performance of the ``yarl.URL.joinpath()`` method -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1418 `__. + + +---- + + +1.17.1 +====== + +*(2024-10-30)* + + +Miscellaneous internal changes +------------------------------ + +- Improved performance of many ``~yarl.URL`` methods -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1396 `__, `#1397 `__, `#1398 `__. + +- Improved performance of passing a `dict` or `str` to ``yarl.URL.extend_query()`` -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1401 `__. + + +---- + + +1.17.0 +====== + +*(2024-10-28)* + + +Features +-------- + +- Added ``~yarl.URL.host_port_subcomponent`` which returns the ``3986#section-3.2.2`` host and ``3986#section-3.2.3`` port subcomponent -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1375 `__. + + +---- + + +1.16.0 +====== + +*(2024-10-21)* + + +Bug fixes +--------- + +- Fixed blocking I/O to load Python code when creating a new ``~yarl.URL`` with non-ascii characters in the network location part -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1342 `__. + + +Removals and backward incompatible breaking changes +--------------------------------------------------- + +- Migrated to using a single cache for encoding hosts -- by `@bdraco `__. + + Passing ``ip_address_size`` and ``host_validate_size`` to ``yarl.cache_configure()`` is deprecated in favor of the new ``encode_host_size`` parameter and will be removed in a future release. For backwards compatibility, the old parameters affect the ``encode_host`` cache size. + + *Related issues and pull requests on GitHub:* + `#1348 `__, `#1357 `__, `#1363 `__. + + +Miscellaneous internal changes +------------------------------ + +- Improved performance of constructing ``~yarl.URL`` -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1336 `__. + +- Improved performance of calling ``yarl.URL.build()`` and constructing unencoded ``~yarl.URL`` -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1345 `__. + +- Reworked the internal encoding cache to improve performance on cache hit -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1369 `__. + + +---- + + +1.15.5 +====== + +*(2024-10-18)* + + +Miscellaneous internal changes +------------------------------ + +- Improved performance of the ``yarl.URL.joinpath()`` method -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1304 `__. + +- Improved performance of the ``yarl.URL.extend_query()`` method -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1305 `__. + +- Improved performance of the ``yarl.URL.origin()`` method -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1306 `__. + +- Improved performance of the ``yarl.URL.with_path()`` method -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1307 `__. + +- Improved performance of the ``yarl.URL.with_query()`` method -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1308 `__, `#1328 `__. + +- Improved performance of the ``yarl.URL.update_query()`` method -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1309 `__, `#1327 `__. + +- Improved performance of the ``yarl.URL.join()`` method -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1313 `__. + +- Improved performance of ``~yarl.URL`` equality checks -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1315 `__. + +- Improved performance of ``~yarl.URL`` methods that modify the network location -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1316 `__. + +- Improved performance of the ``yarl.URL.with_fragment()`` method -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1317 `__. + +- Improved performance of calculating the hash of ``~yarl.URL`` objects -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1318 `__. + +- Improved performance of the ``yarl.URL.relative()`` method -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1319 `__. + +- Improved performance of the ``yarl.URL.with_name()`` method -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1320 `__. + +- Improved performance of ``~yarl.URL.parent`` -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1321 `__. + +- Improved performance of the ``yarl.URL.with_scheme()`` method -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1322 `__. + + +---- + + +1.15.4 +====== + +*(2024-10-16)* + + +Miscellaneous internal changes +------------------------------ + +- Improved performance of the quoter when all characters are safe -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1288 `__. + +- Improved performance of unquoting strings -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1292 `__, `#1293 `__. + +- Improved performance of calling ``yarl.URL.build()`` -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1297 `__. + + +---- + + +1.15.3 +====== + +*(2024-10-15)* + + +Bug fixes +--------- + +- Fixed ``yarl.URL.build()`` failing to validate paths must start with a ``/`` when passing ``authority`` -- by `@bdraco `__. + + The validation only worked correctly when passing ``host``. + + *Related issues and pull requests on GitHub:* + `#1265 `__. + + +Removals and backward incompatible breaking changes +--------------------------------------------------- + +- Removed support for Python 3.8 as it has reached end of life -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1203 `__. + + +Miscellaneous internal changes +------------------------------ + +- Improved performance of constructing ``~yarl.URL`` when the net location is only the host -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1271 `__. + + +---- + + +1.15.2 +====== + +*(2024-10-13)* + + +Miscellaneous internal changes +------------------------------ + +- Improved performance of converting ``~yarl.URL`` to a string -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1234 `__. + +- Improved performance of ``yarl.URL.joinpath()`` -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1248 `__, `#1250 `__. + +- Improved performance of constructing query strings from ``~multidict.MultiDict`` -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1256 `__. + +- Improved performance of constructing query strings with ``int`` values -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1259 `__. + + +---- + + +1.15.1 +====== + +*(2024-10-12)* + + +Miscellaneous internal changes +------------------------------ + +- Improved performance of calling ``yarl.URL.build()`` -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1222 `__. + +- Improved performance of all ``~yarl.URL`` methods that create new ``~yarl.URL`` objects -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1226 `__. + +- Improved performance of ``~yarl.URL`` methods that modify the network location -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1229 `__. + + +---- + + +1.15.0 +====== + +*(2024-10-11)* + + +Bug fixes +--------- + +- Fixed validation with ``yarl.URL.with_scheme()`` when passed scheme is not lowercase -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1189 `__. + + +Features +-------- + +- Started building ``armv7l`` wheels -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1204 `__. + + +Miscellaneous internal changes +------------------------------ + +- Improved performance of constructing unencoded ``~yarl.URL`` objects -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1188 `__. + +- Added a cache for parsing hosts to reduce overhead of encoding ``~yarl.URL`` -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1190 `__. + +- Improved performance of constructing query strings from ``~collections.abc.Mapping`` -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1193 `__. + +- Improved performance of converting ``~yarl.URL`` objects to strings -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1198 `__. + + +---- + + +1.14.0 +====== + +*(2024-10-08)* + + +Packaging updates and notes for downstreams +------------------------------------------- + +- Switched to using the ``propcache`` package for property caching + -- by `@bdraco `__. + + The ``propcache`` package is derived from the property caching + code in ``yarl`` and has been broken out to avoid maintaining it for multiple + projects. + + *Related issues and pull requests on GitHub:* + `#1169 `__. + + +Contributor-facing changes +-------------------------- + +- Started testing with Hypothesis -- by `@webknjaz `__ and `@bdraco `__. + + Special thanks to `@Zac-HD `__ for helping us get started with this framework. + + *Related issues and pull requests on GitHub:* + `#860 `__. + + +Miscellaneous internal changes +------------------------------ + +- Improved performance of ``yarl.URL.is_default_port()`` when no explicit port is set -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1168 `__. + +- Improved performance of converting ``~yarl.URL`` to a string when no explicit port is set -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1170 `__. + +- Improved performance of the ``yarl.URL.origin()`` method -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1175 `__. + +- Improved performance of encoding hosts -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1176 `__. + + +---- + + +1.13.1 +====== + +*(2024-09-27)* + + +Miscellaneous internal changes +------------------------------ + +- Improved performance of calling ``yarl.URL.build()`` with ``authority`` -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1163 `__. + + +---- + + +1.13.0 +====== + +*(2024-09-26)* + + +Bug fixes +--------- + +- Started rejecting ASCII hostnames with invalid characters. For host strings that + look like authority strings, the exception message includes advice on what to do + instead -- by `@mjpieters `__. + + *Related issues and pull requests on GitHub:* + `#880 `__, `#954 `__. + +- Fixed IPv6 addresses missing brackets when the ``~yarl.URL`` was converted to a string -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1157 `__, `#1158 `__. + + +Features +-------- + +- Added ``~yarl.URL.host_subcomponent`` which returns the ``3986#section-3.2.2`` host subcomponent -- by `@bdraco `__. + + The only current practical difference between ``~yarl.URL.raw_host`` and ``~yarl.URL.host_subcomponent`` is that IPv6 addresses are returned bracketed. + + *Related issues and pull requests on GitHub:* + `#1159 `__. + + +---- + + +1.12.1 +====== + +*(2024-09-23)* + + +No significant changes. + + +---- + + +1.12.0 +====== + +*(2024-09-23)* + + +Features +-------- + +- Added ``~yarl.URL.path_safe`` to be able to fetch the path without ``%2F`` and ``%25`` decoded -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1150 `__. + + +Removals and backward incompatible breaking changes +--------------------------------------------------- + +- Restore decoding ``%2F`` (``/``) in ``URL.path`` -- by `@bdraco `__. + + This change restored the behavior before `#1057 `__. + + *Related issues and pull requests on GitHub:* + `#1151 `__. + + +Miscellaneous internal changes +------------------------------ + +- Improved performance of processing paths -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1143 `__. + + +---- + + +1.11.1 +====== + +*(2024-09-09)* + + +Bug fixes +--------- + +- Allowed scheme replacement for relative URLs if the scheme does not require a host -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#280 `__, `#1138 `__. + +- Allowed empty host for URL schemes other than the special schemes listed in the WHATWG URL spec -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1136 `__. + + +Features +-------- + +- Loosened restriction on integers as query string values to allow classes that implement ``__int__`` -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1139 `__. + + +Miscellaneous internal changes +------------------------------ + +- Improved performance of normalizing paths -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1137 `__. + + +---- + + +1.11.0 +====== + +*(2024-09-08)* + + +Features +-------- + +- Added ``URL.extend_query()()`` method, which can be used to extend parameters without replacing same named keys -- by `@bdraco `__. + + This method was primarily added to replace the inefficient hand rolled method currently used in ``aiohttp``. + + *Related issues and pull requests on GitHub:* + `#1128 `__. + + +Miscellaneous internal changes +------------------------------ + +- Improved performance of the Cython ``cached_property`` implementation -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1122 `__. + +- Simplified computing ports by removing unnecessary code -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1123 `__. + +- Improved performance of encoding non IPv6 hosts -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1125 `__. + +- Improved performance of ``URL.build()()`` when the path, query string, or fragment is an empty string -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1126 `__. + +- Improved performance of the ``URL.update_query()()`` method -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1130 `__. + +- Improved performance of processing query string changes when arguments are ``str`` -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1131 `__. + + +---- + + +1.10.0 +====== + +*(2024-09-06)* + + +Bug fixes +--------- + +- Fixed joining a path when the existing path was empty -- by `@bdraco `__. + + A regression in ``URL.join()()`` was introduced in `#1082 `__. + + *Related issues and pull requests on GitHub:* + `#1118 `__. + + +Features +-------- + +- Added ``URL.without_query_params()()`` method, to drop some parameters from query string -- by `@hongquan `__. + + *Related issues and pull requests on GitHub:* + `#774 `__, `#898 `__, `#1010 `__. + +- The previously protected types ``_SimpleQuery``, ``_QueryVariable``, and ``_Query`` are now available for use externally as ``SimpleQuery``, ``QueryVariable``, and ``Query`` -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1050 `__, `#1113 `__. + + +Contributor-facing changes +-------------------------- + +- Replaced all ``~typing.Optional`` with ``~typing.Union`` -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1095 `__. + + +Miscellaneous internal changes +------------------------------ + +- Significantly improved performance of parsing the network location -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1112 `__. + +- Added internal types to the cache to prevent future refactoring errors -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1117 `__. + + +---- + + +1.9.11 +====== + +*(2024-09-04)* + + +Bug fixes +--------- + +- Fixed a ``TypeError`` with ``MultiDictProxy`` and Python 3.8 -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1084 `__, `#1105 `__, `#1107 `__. + + +Miscellaneous internal changes +------------------------------ + +- Improved performance of encoding hosts -- by `@bdraco `__. + + Previously, the library would unconditionally try to parse a host as an IP Address. The library now avoids trying to parse a host as an IP Address if the string is not in one of the formats described in ``3986#section-3.2.2``. + + *Related issues and pull requests on GitHub:* + `#1104 `__. + + +---- + + +1.9.10 +====== + +*(2024-09-04)* + + +Bug fixes +--------- + +- ``URL.join()()`` has been changed to match + ``3986`` and align with + ``/ operation()`` and ``URL.joinpath()()`` + when joining URLs with empty segments. + Previously ``urllib.parse.urljoin`` was used, + which has known issues with empty segments + (`python/cpython#84774 `_). + + Due to the semantics of ``URL.join()()``, joining an + URL with scheme requires making it relative, prefixing with ``./``. + + .. code-block:: pycon + + >>> URL("https://web.archive.org/web/").join(URL("./https://github.com/aio-libs/yarl")) + URL('https://web.archive.org/web/https://github.com/aio-libs/yarl') + + + Empty segments are honored in the base as well as the joined part. + + .. code-block:: pycon + + >>> URL("https://web.archive.org/web/https://").join(URL("github.com/aio-libs/yarl")) + URL('https://web.archive.org/web/https://github.com/aio-libs/yarl') + + + + -- by `@commonism `__ + + This change initially appeared in 1.9.5 but was reverted in 1.9.6 to resolve a problem with query string handling. + + *Related issues and pull requests on GitHub:* + `#1039 `__, `#1082 `__. + + +Features +-------- + +- Added ``~yarl.URL.absolute`` which is now preferred over ``URL.is_absolute()`` -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1100 `__. + + +---- + + +1.9.9 +===== + +*(2024-09-04)* + + +Bug fixes +--------- + +- Added missing type on ``~yarl.URL.port`` -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1097 `__. + + +---- + + +1.9.8 +===== + +*(2024-09-03)* + + +Features +-------- + +- Covered the ``~yarl.URL`` object with types -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1084 `__. + +- Cache parsing of IP Addresses when encoding hosts -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1086 `__. + + +Contributor-facing changes +-------------------------- + +- Covered the ``~yarl.URL`` object with types -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1084 `__. + + +Miscellaneous internal changes +------------------------------ + +- Improved performance of handling ports -- by `@bdraco `__. + + *Related issues and pull requests on GitHub:* + `#1081 `__. + + +---- + + +1.9.7 +===== + +*(2024-09-01)* + + +Removals and backward incompatible breaking changes +--------------------------------------------------- + +- Removed support ``3986#section-3.2.3`` port normalization when the scheme is not one of ``http``, ``https``, ``wss``, or ``ws`` -- by `@bdraco `__. + + Support for port normalization was recently added in `#1033 `__ and contained code that would do blocking I/O if the scheme was not one of the four listed above. The code has been removed because this library is intended to be safe for usage with ``asyncio``. + + *Related issues and pull requests on GitHub:* + `#1076 `__. + + +Miscellaneous internal changes +------------------------------ + +- Improved performance of property caching -- by `@bdraco `__. + + The ``reify`` implementation from ``aiohttp`` was adapted to replace the internal ``cached_property`` implementation. + + *Related issues and pull requests on GitHub:* + `#1070 `__. + + +---- + + +1.9.6 +===== + +*(2024-08-30)* + + +Bug fixes +--------- + +- Reverted ``3986`` compatible ``URL.join()()`` honoring empty segments which was introduced in `#1039 `__. + + This change introduced a regression handling query string parameters with joined URLs. The change was reverted to maintain compatibility with the previous behavior. + + *Related issues and pull requests on GitHub:* + `#1067 `__. + + +---- + + +1.9.5 +===== + +*(2024-08-30)* + + +Bug fixes +--------- + +- Joining URLs with empty segments has been changed + to match ``3986``. + + Previously empty segments would be removed from path, + breaking use-cases such as + + .. code-block:: python + + URL("https://web.archive.org/web/") / "https://github.com/" + + Now ``/ operation()`` and ``URL.joinpath()()`` + keep empty segments, but do not introduce new empty segments. + e.g. + + .. code-block:: python + + URL("https://example.org/") / "" + + does not introduce an empty segment. + + -- by `@commonism `__ and `@youtux `__ + + *Related issues and pull requests on GitHub:* + `#1026 `__. + +- The default protocol ports of well-known URI schemes are now taken into account + during the normalization of the URL string representation in accordance with + ``3986#section-3.2.3``. + + Specified ports are removed from the ``str`` representation of a ``~yarl.URL`` + if the port matches the scheme's default port -- by `@commonism `__. + + *Related issues and pull requests on GitHub:* + `#1033 `__. + +- ``URL.join()()`` has been changed to match + ``3986`` and align with + ``/ operation()`` and ``URL.joinpath()()`` + when joining URLs with empty segments. + Previously ``urllib.parse.urljoin`` was used, + which has known issues with empty segments + (`python/cpython#84774 `_). + + Due to the semantics of ``URL.join()()``, joining an + URL with scheme requires making it relative, prefixing with ``./``. + + .. code-block:: pycon + + >>> URL("https://web.archive.org/web/").join(URL("./https://github.com/aio-libs/yarl")) + URL('https://web.archive.org/web/https://github.com/aio-libs/yarl') + + + Empty segments are honored in the base as well as the joined part. + + .. code-block:: pycon + + >>> URL("https://web.archive.org/web/https://").join(URL("github.com/aio-libs/yarl")) + URL('https://web.archive.org/web/https://github.com/aio-libs/yarl') + + + + -- by `@commonism `__ + + *Related issues and pull requests on GitHub:* + `#1039 `__. + + +Removals and backward incompatible breaking changes +--------------------------------------------------- + +- Stopped decoding ``%2F`` (``/``) in ``URL.path``, as this could lead to code incorrectly treating it as a path separator + -- by `@Dreamsorcerer `__. + + *Related issues and pull requests on GitHub:* + `#1057 `__. + +- Dropped support for Python 3.7 -- by `@Dreamsorcerer `__. + + *Related issues and pull requests on GitHub:* + `#1016 `__. + + +Improved documentation +---------------------- + +- On the ``Contributing docs`` page, + a link to the ``Towncrier philosophy`` has been fixed. + + *Related issues and pull requests on GitHub:* + `#981 `__. + +- The pre-existing ``/ magic method()`` + has been documented in the API reference -- by `@commonism `__. + + *Related issues and pull requests on GitHub:* + `#1026 `__. + + +Packaging updates and notes for downstreams +------------------------------------------- + +- A flaw in the logic for copying the project directory into a + temporary folder that led to infinite recursion when ``TMPDIR`` + was set to a project subdirectory path. This was happening in Fedora + and its downstream due to the use of `pyproject-rpm-macros + `__. It was + only reproducible with ``pip wheel`` and was not affecting the + ``pyproject-build`` users. + + -- by `@hroncok `__ and `@webknjaz `__ + + *Related issues and pull requests on GitHub:* + `#992 `__, `#1014 `__. + +- Support Python 3.13 and publish non-free-threaded wheels + + *Related issues and pull requests on GitHub:* + `#1054 `__. + + +Contributor-facing changes +-------------------------- + +- The CI/CD setup has been updated to test ``arm64`` wheels + under macOS 14, except for Python 3.7 that is unsupported + in that environment -- by `@webknjaz `__. + + *Related issues and pull requests on GitHub:* + `#1015 `__. + +- Removed unused type ignores and casts -- by `@hauntsaninja `__. + + *Related issues and pull requests on GitHub:* + `#1031 `__. + + +Miscellaneous internal changes +------------------------------ + +- ``port``, ``scheme``, and ``raw_host`` are now ``cached_property`` -- by `@bdraco `__. + + ``aiohttp`` accesses these properties quite often, which cause ``urllib`` to build the ``_hostinfo`` property every time. ``port``, ``scheme``, and ``raw_host`` are now cached properties, which will improve performance. + + *Related issues and pull requests on GitHub:* + `#1044 `__, `#1058 `__. + + +---- + + +1.9.4 (2023-12-06) +================== + +Bug fixes +--------- + +- Started raising ``TypeError`` when a string value is passed into + ``yarl.URL.build()`` as the ``port`` argument -- by `@commonism `__. + + Previously the empty string as port would create malformed URLs when rendered as string representations. (`#883 `__) + + +Packaging updates and notes for downstreams +------------------------------------------- + +- The leading ``--`` has been dropped from the `PEP 517 `__ in-tree build + backend config setting names. ``--pure-python`` is now just ``pure-python`` + -- by `@webknjaz `__. + + The usage now looks as follows: + + .. code-block:: console + + $ python -m build \ + --config-setting=pure-python=true \ + --config-setting=with-cython-tracing=true + + (`#963 `__) + + +Contributor-facing changes +-------------------------- + +- A step-by-step ``Release Guide`` guide has + been added, describing how to release *yarl* -- by `@webknjaz `__. + + This is primarily targeting maintainers. (`#960 `__) +- Coverage collection has been implemented for the Cython modules + -- by `@webknjaz `__. + + It will also be reported to Codecov from any non-release CI jobs. + + To measure coverage in a development environment, *yarl* can be + installed in editable mode: + + .. code-block:: console + + $ python -Im pip install -e . + + Editable install produces C-files required for the Cython coverage + plugin to map the measurements back to the PYX-files. + + `#961 `__ + +- It is now possible to request line tracing in Cython builds using the + ``with-cython-tracing`` `PEP 517 `__ config setting + -- `@webknjaz `__. + + This can be used in CI and development environment to measure coverage + on Cython modules, but is not normally useful to the end-users or + downstream packagers. + + Here's a usage example: + + .. code-block:: console + + $ python -Im pip install . --config-settings=with-cython-tracing=true + + For editable installs, this setting is on by default. Otherwise, it's + off unless requested explicitly. + + The following produces C-files required for the Cython coverage + plugin to map the measurements back to the PYX-files: + + .. code-block:: console + + $ python -Im pip install -e . + + Alternatively, the ``YARL_CYTHON_TRACING=1`` environment variable + can be set to do the same as the `PEP 517 `__ config setting. + + `#962 `__ + + +1.9.3 (2023-11-20) +================== + +Bug fixes +--------- + +- Stopped dropping trailing slashes in ``yarl.URL.joinpath()`` -- by `@gmacon `__. (`#862 `__, `#866 `__) +- Started accepting string subclasses in ``yarl.URL.__truediv__()`` operations (``URL / segment``) -- by `@mjpieters `__. (`#871 `__, `#884 `__) +- Fixed the human representation of URLs with square brackets in usernames and passwords -- by `@mjpieters `__. (`#876 `__, `#882 `__) +- Updated type hints to include ``URL.missing_port()``, ``URL.__bytes__()`` + and the ``encoding`` argument to ``yarl.URL.joinpath()`` + -- by `@mjpieters `__. (`#891 `__) + + +Packaging updates and notes for downstreams +------------------------------------------- + +- Integrated Cython 3 to enable building *yarl* under Python 3.12 -- by `@mjpieters `__. (`#829 `__, `#881 `__) +- Declared modern ``setuptools.build_meta`` as the `PEP 517 `__ build + backend in ``pyproject.toml`` explicitly -- by `@webknjaz `__. (`#886 `__) +- Converted most of the packaging setup into a declarative ``setup.cfg`` + config -- by `@webknjaz `__. (`#890 `__) +- The packaging is replaced from an old-fashioned ``setup.py`` to an + in-tree `PEP 517 `__ build backend -- by `@webknjaz `__. + + Whenever the end-users or downstream packagers need to build ``yarl`` from + source (a Git checkout or an sdist), they may pass a ``config_settings`` + flag ``--pure-python``. If this flag is not set, a C-extension will be built + and included into the distribution. + + Here is how this can be done with ``pip``: + + .. code-block:: console + + $ python -m pip install . --config-settings=--pure-python=false + + This will also work with ``-e | --editable``. + + The same can be achieved via ``pypa/build``: + + .. code-block:: console + + $ python -m build --config-setting=--pure-python=false + + Adding ``-w | --wheel`` can force ``pypa/build`` produce a wheel from source + directly, as opposed to building an ``sdist`` and then building from it. (`#893 `__) + + .. attention:: + + v1.9.3 was the only version using the ``--pure-python`` setting name. + Later versions dropped the ``--`` prefix, making it just ``pure-python``. + +- Declared Python 3.12 supported officially in the distribution package metadata + -- by `@edgarrmondragon `__. (`#942 `__) + + +Contributor-facing changes +-------------------------- + +- A regression test for no-host URLs was added per `#821 `__ + and ``3986`` -- by `@kenballus `__. (`#821 `__, `#822 `__) +- Started testing *yarl* against Python 3.12 in CI -- by `@mjpieters `__. (`#881 `__) +- All Python 3.12 jobs are now marked as required to pass in CI + -- by `@edgarrmondragon `__. (`#942 `__) +- MyST is now integrated in Sphinx -- by `@webknjaz `__. + + This allows the contributors to author new documents in Markdown + when they have difficulties with going straight RST. (`#953 `__) + + +1.9.2 (2023-04-25) +================== + +Bugfixes +-------- + +- Fix regression with ``yarl.URL.__truediv__()`` and absolute URLs with empty paths causing the raw path to lack the leading ``/``. + (`#854 `_) + + +1.9.1 (2023-04-21) +================== + +Bugfixes +-------- + +- Marked tests that fail on older Python patch releases (< 3.7.10, < 3.8.8 and < 3.9.2) as expected to fail due to missing a security fix for CVE-2021-23336. (`#850 `_) + + +1.9.0 (2023-04-19) +================== + +This release was never published to PyPI, due to issues with the build process. + +Features +-------- + +- Added ``URL.joinpath(*elements)``, to create a new URL appending multiple path elements. (`#704 `_) +- Made ``URL.__truediv__()()`` return ``NotImplemented`` if called with an + unsupported type — by `@michaeljpeters `__. + (`#832 `_) + + +Bugfixes +-------- + +- Path normalization for absolute URLs no longer raises a ValueError exception + when ``..`` segments would otherwise go beyond the URL path root. + (`#536 `_) +- Fixed an issue with update_query() not getting rid of the query when argument is None. (`#792 `_) +- Added some input restrictions on with_port() function to prevent invalid boolean inputs or out of valid port inputs; handled incorrect 0 port representation. (`#793 `_) +- Made ``yarl.URL.build()`` raise a ``TypeError`` if the ``host`` argument is ``None`` — by `@paulpapacz `__. (`#808 `_) +- Fixed an issue with ``update_query()`` getting rid of the query when the argument + is empty but not ``None``. (`#845 `_) + + +Misc +---- + +- `#220 `_ + + +1.8.2 (2022-12-03) +================== + +This is the first release that started shipping wheels for Python 3.11. + + +1.8.1 (2022-08-01) +================== + +Misc +---- + +- `#694 `_, `#699 `_, `#700 `_, `#701 `_, `#702 `_, `#703 `_, `#739 `_ + + +1.8.0 (2022-08-01) +================== + +Features +-------- + +- Added ``URL.raw_suffix``, ``URL.suffix``, ``URL.raw_suffixes``, ``URL.suffixes``, ``URL.with_suffix``. (`#613 `_) + + +Improved Documentation +---------------------- + +- Fixed broken internal references to ``yarl.URL.human_repr()``. + (`#665 `_) +- Fixed broken external references to ``multidict:index`` docs. (`#665 `_) + + +Deprecations and Removals +------------------------- + +- Dropped Python 3.6 support. (`#672 `_) + + +Misc +---- + +- `#646 `_, `#699 `_, `#701 `_ + + +1.7.2 (2021-11-01) +================== + +Bugfixes +-------- + +- Changed call in ``with_port()`` to stop reencoding parts of the URL that were already encoded. (`#623 `_) + + +1.7.1 (2021-10-07) +================== + +Bugfixes +-------- + +- Fix 1.7.0 build error + +1.7.0 (2021-10-06) +================== + +Features +-------- + +- Add ``__bytes__()`` magic method so that ``bytes(url)`` will work and use optimal ASCII encoding. + (`#582 `_) +- Started shipping platform-specific arm64 wheels for Apple Silicon. (`#622 `_) +- Started shipping platform-specific wheels with the ``musl`` tag targeting typical Alpine Linux runtimes. (`#622 `_) +- Added support for Python 3.10. (`#622 `_) + + +1.6.3 (2020-11-14) +================== + +Bugfixes +-------- + +- No longer loose characters when decoding incorrect percent-sequences (like ``%e2%82%f8``). All non-decodable percent-sequences are now preserved. + `#517 `_ +- Provide x86 Windows wheels. + `#535 `_ + + +---- + + +1.6.2 (2020-10-12) +================== + + +Bugfixes +-------- + +- Provide generated ``.c`` files in TarBall distribution. + `#530 `_ + +1.6.1 (2020-10-12) +================== + +Features +-------- + +- Provide wheels for ``aarch64``, ``i686``, ``ppc64le``, ``s390x`` architectures on + Linux as well as ``x86_64``. + `#507 `_ +- Provide wheels for Python 3.9. + `#526 `_ + +Bugfixes +-------- + +- ``human_repr()`` now always produces valid representation equivalent to the original URL (if the original URL is valid). + `#511 `_ +- Fixed requoting a single percent followed by a percent-encoded character in the Cython implementation. + `#514 `_ +- Fix ValueError when decoding ``%`` which is not followed by two hexadecimal digits. + `#516 `_ +- Fix decoding ``%`` followed by a space and hexadecimal digit. + `#520 `_ +- Fix annotation of ``with_query()``/``update_query()`` methods for ``key=[val1, val2]`` case. + `#528 `_ + +Removal +------- + +- Drop Python 3.5 support; Python 3.6 is the minimal supported Python version. + + +---- + + +1.6.0 (2020-09-23) +================== + +Features +-------- + +- Allow for int and float subclasses in query, while still denying bool. + `#492 `_ + + +Bugfixes +-------- + +- Do not requote arguments in ``URL.build()``, ``with_xxx()`` and in ``/`` operator. + `#502 `_ +- Keep IPv6 brackets in ``origin()``. + `#504 `_ + + +---- + + +1.5.1 (2020-08-01) +================== + +Bugfixes +-------- + +- Fix including relocated internal ``yarl._quoting_c`` C-extension into published PyPI dists. + `#485 `_ + + +Misc +---- + +- `#484 `_ + + +---- + + +1.5.0 (2020-07-26) +================== + +Features +-------- + +- Convert host to lowercase on URL building. + `#386 `_ +- Allow using ``mod`` operator (``%``) for updating query string (an alias for ``update_query()`` method). + `#435 `_ +- Allow use of sequences such as ``list`` and ``tuple`` in the values + of a mapping such as ``dict`` to represent that a key has many values:: + + url = URL("http://example.com") + assert url.with_query({"a": [1, 2]}) == URL("http://example.com/?a=1&a=2") + + `#443 `_ +- Support ``URL.build()`` with scheme and path (creates a relative URL). + `#464 `_ +- Cache slow IDNA encode/decode calls. + `#476 `_ +- Add ``@final`` / ``Final`` type hints + `#477 `_ +- Support URL authority/raw_authority properties and authority argument of ``URL.build()`` method. + `#478 `_ +- Hide the library implementation details, make the exposed public list very clean. + `#483 `_ + + +Bugfixes +-------- + +- Fix tests with newer Python (3.7.6, 3.8.1 and 3.9.0+). + `#409 `_ +- Fix a bug where query component, passed in a form of mapping or sequence, is unquoted in unexpected way. + `#426 `_ +- Hide ``Query`` and ``QueryVariable`` type aliases in ``__init__.pyi``, now they are prefixed with underscore. + `#431 `_ +- Keep IPv6 brackets after updating port/user/password. + `#451 `_ + + +---- + + +1.4.2 (2019-12-05) +================== + +Features +-------- + +- Workaround for missing ``str.isascii()`` in Python 3.6 + `#389 `_ + + +---- + + +1.4.1 (2019-11-29) +================== + +* Fix regression, make the library work on Python 3.5 and 3.6 again. + +1.4.0 (2019-11-29) +================== + +* Distinguish an empty password in URL from a password not provided at all (#262) + +* Fixed annotations for optional parameters of ``URL.build`` (#309) + +* Use None as default value of ``user`` parameter of ``URL.build`` (#309) + +* Enforce building C Accelerated modules when installing from source tarball, use + ``YARL_NO_EXTENSIONS`` environment variable for falling back to (slower) Pure Python + implementation (#329) + +* Drop Python 3.5 support + +* Fix quoting of plus in path by pure python version (#339) + +* Don't create a new URL if fragment is unchanged (#292) + +* Included in error message the path that produces starting slash forbidden error (#376) + +* Skip slow IDNA encoding for ASCII-only strings (#387) + + +1.3.0 (2018-12-11) +================== + +* Fix annotations for ``query`` parameter (#207) + +* An incoming query sequence can have int variables (the same as for + Mapping type) (#208) + +* Add ``URL.explicit_port`` property (#218) + +* Give a friendlier error when port can't be converted to int (#168) + +* ``bool(URL())`` now returns ``False`` (#272) + +1.2.6 (2018-06-14) +================== + +* Drop Python 3.4 trove classifier (#205) + +1.2.5 (2018-05-23) +================== + +* Fix annotations for ``build`` (#199) + +1.2.4 (2018-05-08) +================== + +* Fix annotations for ``cached_property`` (#195) + +1.2.3 (2018-05-03) +================== + +* Accept ``str`` subclasses in ``URL`` constructor (#190) + +1.2.2 (2018-05-01) +================== + +* Fix build + +1.2.1 (2018-04-30) +================== + +* Pin minimal required Python to 3.5.3 (#189) + +1.2.0 (2018-04-30) +================== + +* Forbid inheritance, replace ``__init__`` with ``__new__`` (#171) + +* Support PEP-561 (provide type hinting marker) (#182) + +1.1.1 (2018-02-17) +================== + +* Fix performance regression: don't encode empty ``netloc`` (#170) + +1.1.0 (2018-01-21) +================== + +* Make pure Python quoter consistent with Cython version (#162) + +1.0.0 (2018-01-15) +================== + +* Use fast path if quoted string does not need requoting (#154) + +* Speed up quoting/unquoting by ``_Quoter`` and ``_Unquoter`` classes (#155) + +* Drop ``yarl.quote`` and ``yarl.unquote`` public functions (#155) + +* Add custom string writer, reuse static buffer if available (#157) + Code is 50-80 times faster than Pure Python version (was 4-5 times faster) + +* Don't recode IP zone (#144) + +* Support ``encoded=True`` in ``yarl.URL.build()`` (#158) + +* Fix updating query with multiple keys (#160) + +0.18.0 (2018-01-10) +=================== + +* Fallback to IDNA 2003 if domain name is not IDNA 2008 compatible (#152) + +0.17.0 (2017-12-30) +=================== + +* Use IDNA 2008 for domain name processing (#149) + +0.16.0 (2017-12-07) +=================== + +* Fix raising ``TypeError`` by ``url.query_string()`` after + ``url.with_query({})`` (empty mapping) (#141) + +0.15.0 (2017-11-23) +=================== + +* Add ``raw_path_qs`` attribute (#137) + +0.14.2 (2017-11-14) +=================== + +* Restore ``strict`` parameter as no-op in ``quote`` / ``unquote`` + +0.14.1 (2017-11-13) +=================== + +* Restore ``strict`` parameter as no-op for sake of compatibility with + aiohttp 2.2 + +0.14.0 (2017-11-11) +=================== + +* Drop strict mode (#123) + +* Fix ``"ValueError: Unallowed PCT %"`` when there's a ``"%"`` in the URL (#124) + +0.13.0 (2017-10-01) +=================== + +* Document ``encoded`` parameter (#102) + +* Support relative URLs like ``'?key=value'`` (#100) + +* Unsafe encoding for QS fixed. Encode ``;`` character in value parameter (#104) + +* Process passwords without user names (#95) + +0.12.0 (2017-06-26) +=================== + +* Properly support paths without leading slash in ``URL.with_path()`` (#90) + +* Enable type annotation checks + +0.11.0 (2017-06-26) +=================== + +* Normalize path (#86) + +* Clear query and fragment parts in ``.with_path()`` (#85) + +0.10.3 (2017-06-13) +=================== + +* Prevent double URL arguments unquoting (#83) + +0.10.2 (2017-05-05) +=================== + +* Unexpected hash behavior (#75) + + +0.10.1 (2017-05-03) +=================== + +* Unexpected compare behavior (#73) + +* Do not quote or unquote + if not a query string. (#74) + + +0.10.0 (2017-03-14) +=================== + +* Added ``URL.build`` class method (#58) + +* Added ``path_qs`` attribute (#42) + + +0.9.8 (2017-02-16) +================== + +* Do not quote ``:`` in path + + +0.9.7 (2017-02-16) +================== + +* Load from pickle without _cache (#56) + +* Percent-encoded pluses in path variables become spaces (#59) + + +0.9.6 (2017-02-15) +================== + +* Revert backward incompatible change (BaseURL) + + +0.9.5 (2017-02-14) +================== + +* Fix BaseURL rich comparison support + + +0.9.4 (2017-02-14) +================== + +* Use BaseURL + + +0.9.3 (2017-02-14) +================== + +* Added BaseURL + + +0.9.2 (2017-02-08) +================== + +* Remove debug print + + +0.9.1 (2017-02-07) +================== + +* Do not lose tail chars (#45) + + +0.9.0 (2017-02-07) +================== + +* Allow to quote ``%`` in non strict mode (#21) + +* Incorrect parsing of query parameters with %3B (;) inside (#34) + +* Fix core dumps (#41) + +* ``tmpbuf`` - compiling error (#43) + +* Added ``URL.update_path()`` method + +* Added ``URL.update_query()`` method (#47) + + +0.8.1 (2016-12-03) +================== + +* Fix broken aiohttp: revert back ``quote`` / ``unquote``. + + +0.8.0 (2016-12-03) +================== + +* Support more verbose error messages in ``.with_query()`` (#24) + +* Don't percent-encode ``@`` and ``:`` in path (#32) + +* Don't expose ``yarl.quote`` and ``yarl.unquote``, these functions are + part of private API + +0.7.1 (2016-11-18) +================== + +* Accept not only ``str`` but all classes inherited from ``str`` also (#25) + +0.7.0 (2016-11-07) +================== + +* Accept ``int`` as value for ``.with_query()`` + +0.6.0 (2016-11-07) +================== + +* Explicitly use UTF8 encoding in ``setup.py`` (#20) +* Properly unquote non-UTF8 strings (#19) + +0.5.3 (2016-11-02) +================== + +* Don't use ``typing.NamedTuple`` fields but indexes on URL construction + +0.5.2 (2016-11-02) +================== + +* Inline ``_encode`` class method + +0.5.1 (2016-11-02) +================== + +* Make URL construction faster by removing extra classmethod calls + +0.5.0 (2016-11-02) +================== + +* Add Cython optimization for quoting/unquoting +* Provide binary wheels + +0.4.3 (2016-09-29) +================== + +* Fix typing stubs + +0.4.2 (2016-09-29) +================== + +* Expose ``quote()`` and ``unquote()`` as public API + +0.4.1 (2016-09-28) +================== + +* Support empty values in query (``'/path?arg'``) + +0.4.0 (2016-09-27) +================== + +* Introduce ``relative()`` (#16) + +0.3.2 (2016-09-27) +================== + +* Typo fixes #15 + +0.3.1 (2016-09-26) +================== + +* Support sequence of pairs as ``with_query()`` parameter + +0.3.0 (2016-09-26) +================== + +* Introduce ``is_default_port()`` + +0.2.1 (2016-09-26) +================== + +* Raise ValueError for URLs like 'http://:8080/' + +0.2.0 (2016-09-18) +================== + +* Avoid doubling slashes when joining paths (#13) + +* Appending path starting from slash is forbidden (#12) + +0.1.4 (2016-09-09) +================== + +* Add ``kwargs`` support for ``with_query()`` (#10) + +0.1.3 (2016-09-07) +================== + +* Document ``with_query()``, ``with_fragment()`` and ``origin()`` + +* Allow ``None`` for ``with_query()`` and ``with_fragment()`` + +0.1.2 (2016-09-07) +================== + +* Fix links, tune docs theme. + +0.1.1 (2016-09-06) +================== + +* Update README, old version used obsolete API + +0.1.0 (2016-09-06) +================== + +* The library was deeply refactored, bytes are gone away but all + accepted strings are encoded if needed. + +0.0.1 (2016-08-30) +================== + +* The first release. diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/yarl-1.23.0.dist-info/RECORD b/bioagent-bench/.venv/lib/python3.14/site-packages/yarl-1.23.0.dist-info/RECORD new file mode 100644 index 0000000000000000000000000000000000000000..253924b18ae85047a750315833d2401d13852960 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/yarl-1.23.0.dist-info/RECORD @@ -0,0 +1,19 @@ +yarl-1.23.0.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2 +yarl-1.23.0.dist-info/METADATA,sha256=2j-1Mh4t5bJ2YhQALVADES7LGL1NhfQVtBPv3RDvgKM,79589 +yarl-1.23.0.dist-info/RECORD,, +yarl-1.23.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +yarl-1.23.0.dist-info/WHEEL,sha256=SL-oA1LgveBNPW-W4-NXgp5RDas-imCTUEuBJZd8AkE,190 +yarl-1.23.0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358 +yarl-1.23.0.dist-info/licenses/NOTICE,sha256=VtasbIEFwKUTBMIdsGDjYa-ajqCvmnXCOcKLXRNpODg,609 +yarl-1.23.0.dist-info/top_level.txt,sha256=vf3SJuQh-k7YtvsUrV_OPOrT9Kqn0COlk7IPYyhtGkQ,5 +yarl/__init__.py,sha256=KmyqgCWeULtsz7vrl88s5DoGlB_TWhnidh9gjmpQUdg,281 +yarl/_parse.py,sha256=svtLt64KnlURnY4PBwxrudOc7JU5Tptu4hC7LwUTwBU,7100 +yarl/_path.py,sha256=A0FJUylZyzmlT0a3UDOBbK-EzZXCAYuQQBvG9eAC9hs,1291 +yarl/_query.py,sha256=BbAz7wC44pd6kR6UfPvTp4h3abidMKsmSkvdKwWZnus,4041 +yarl/_quoters.py,sha256=herzLBL6OgD37k7SIUnQ1qEsPKtcrUDt_4ai6xwevQk,1117 +yarl/_quoting.py,sha256=yKIqFTzFzWLVb08xy1DSxKNjFwo4f-oLlzxTuKwC57M,506 +yarl/_quoting_c.cpython-314-x86_64-linux-gnu.so,sha256=MMkY1y6o8wZaYzFZj4uHwiXaQa5XX8m-rTfdDvIiymM,129032 +yarl/_quoting_c.pyx,sha256=X40gvQSUB4l7nPKGeiS6pq2JreM36avLhVeBMxd5zmo,14297 +yarl/_quoting_py.py,sha256=_l-v8siONK5WihAvui1M5krNqooeV01s1t9nQGMjcVI,6752 +yarl/_url.py,sha256=NPloMycA8KSUZWe6MV3CryOZqWVpE70ROkr0mraS6rM,56976 +yarl/py.typed,sha256=ay5OMO475PlcZ_Fbun9maHW7Y6MBTk0UXL4ztHx3Iug,14 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/yarl-1.23.0.dist-info/REQUESTED b/bioagent-bench/.venv/lib/python3.14/site-packages/yarl-1.23.0.dist-info/REQUESTED new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/yarl-1.23.0.dist-info/WHEEL b/bioagent-bench/.venv/lib/python3.14/site-packages/yarl-1.23.0.dist-info/WHEEL new file mode 100644 index 0000000000000000000000000000000000000000..e95b86ebbeec679bd7b60aff998f6bd194db68bb --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/yarl-1.23.0.dist-info/WHEEL @@ -0,0 +1,7 @@ +Wheel-Version: 1.0 +Generator: setuptools (82.0.0) +Root-Is-Purelib: false +Tag: cp314-cp314-manylinux_2_17_x86_64 +Tag: cp314-cp314-manylinux2014_x86_64 +Tag: cp314-cp314-manylinux_2_28_x86_64 + diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/yarl-1.23.0.dist-info/licenses/LICENSE b/bioagent-bench/.venv/lib/python3.14/site-packages/yarl-1.23.0.dist-info/licenses/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..d645695673349e3947e8e5ae42332d0ac3164cd7 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/yarl-1.23.0.dist-info/licenses/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/yarl-1.23.0.dist-info/licenses/NOTICE b/bioagent-bench/.venv/lib/python3.14/site-packages/yarl-1.23.0.dist-info/licenses/NOTICE new file mode 100644 index 0000000000000000000000000000000000000000..fa53b2b138df881c4c95239d0e4bede831b36ab5 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/yarl-1.23.0.dist-info/licenses/NOTICE @@ -0,0 +1,13 @@ + Copyright 2016-2021, Andrew Svetlov and aio-libs team + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/yarl-1.23.0.dist-info/top_level.txt b/bioagent-bench/.venv/lib/python3.14/site-packages/yarl-1.23.0.dist-info/top_level.txt new file mode 100644 index 0000000000000000000000000000000000000000..e93e8bddefb14a8a753f7ecab6b934fd899cd9e5 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/yarl-1.23.0.dist-info/top_level.txt @@ -0,0 +1 @@ +yarl diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/yarl/__init__.py b/bioagent-bench/.venv/lib/python3.14/site-packages/yarl/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..6280e4c72a09244ac133dd04432cf3a4b6efd4d8 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/yarl/__init__.py @@ -0,0 +1,14 @@ +from ._query import Query, QueryVariable, SimpleQuery +from ._url import URL, cache_clear, cache_configure, cache_info + +__version__ = "1.23.0" + +__all__ = ( + "URL", + "SimpleQuery", + "QueryVariable", + "Query", + "cache_clear", + "cache_configure", + "cache_info", +) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/yarl/_parse.py b/bioagent-bench/.venv/lib/python3.14/site-packages/yarl/_parse.py new file mode 100644 index 0000000000000000000000000000000000000000..bb64165c7a7bcaf04d55aa8639db70ce24140cfd --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/yarl/_parse.py @@ -0,0 +1,202 @@ +"""URL parsing utilities.""" + +import re +import unicodedata +from functools import lru_cache +from urllib.parse import scheme_chars, uses_netloc + +from ._quoters import QUOTER, UNQUOTER_PLUS + +# Leading and trailing C0 control and space to be stripped per WHATWG spec. +# == "".join([chr(i) for i in range(0, 0x20 + 1)]) +WHATWG_C0_CONTROL_OR_SPACE = ( + "\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10" + "\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f " +) + +# Unsafe bytes to be removed per WHATWG spec +UNSAFE_URL_BYTES_TO_REMOVE = ["\t", "\r", "\n"] +USES_AUTHORITY = frozenset(uses_netloc) + +SplitURLType = tuple[str, str, str, str, str] + + +def split_url(url: str) -> SplitURLType: + """Split URL into parts.""" + # Adapted from urllib.parse.urlsplit + # Only lstrip url as some applications rely on preserving trailing space. + # (https://url.spec.whatwg.org/#concept-basic-url-parser would strip both) + url = url.lstrip(WHATWG_C0_CONTROL_OR_SPACE) + for b in UNSAFE_URL_BYTES_TO_REMOVE: + if b in url: + url = url.replace(b, "") + + scheme = netloc = query = fragment = "" + i = url.find(":") + if i > 0 and url[0] in scheme_chars: + for c in url[1:i]: + if c not in scheme_chars: + break + else: + scheme, url = url[:i].lower(), url[i + 1 :] + has_hash = "#" in url + has_question_mark = "?" in url + if url[:2] == "//": + delim = len(url) # position of end of domain part of url, default is end + if has_hash and has_question_mark: + delim_chars = "/?#" + elif has_question_mark: + delim_chars = "/?" + elif has_hash: + delim_chars = "/#" + else: + delim_chars = "/" + for c in delim_chars: # look for delimiters; the order is NOT important + wdelim = url.find(c, 2) # find first of this delim + if wdelim >= 0 and wdelim < delim: # if found + delim = wdelim # use earliest delim position + netloc = url[2:delim] + url = url[delim:] + has_left_bracket = "[" in netloc + has_right_bracket = "]" in netloc + if (has_left_bracket and not has_right_bracket) or ( + has_right_bracket and not has_left_bracket + ): + raise ValueError("Invalid IPv6 URL") + if has_left_bracket: + bracketed_host = netloc.partition("[")[2].partition("]")[0] + # Valid bracketed hosts are defined in + # https://www.rfc-editor.org/rfc/rfc3986#page-49 + # https://url.spec.whatwg.org/ + if bracketed_host and bracketed_host[0] == "v": + if not re.match(r"\Av[a-fA-F0-9]+\..+\Z", bracketed_host): + raise ValueError("IPvFuture address is invalid") + elif ":" not in bracketed_host: + raise ValueError("The IPv6 content between brackets is not valid") + if has_hash: + url, _, fragment = url.partition("#") + if has_question_mark: + url, _, query = url.partition("?") + if netloc and not netloc.isascii(): + _check_netloc(netloc) + return scheme, netloc, url, query, fragment + + +def _check_netloc(netloc: str) -> None: + # Adapted from urllib.parse._checknetloc + # looking for characters like \u2100 that expand to 'a/c' + # IDNA uses NFKC equivalence, so normalize for this check + + # ignore characters already included + # but not the surrounding text + n = netloc.replace("@", "").replace(":", "").replace("#", "").replace("?", "") + normalized_netloc = unicodedata.normalize("NFKC", n) + if n == normalized_netloc: + return + # Note that there are no unicode decompositions for the character '@' so + # its currently impossible to have test coverage for this branch, however if the + # one should be added in the future we want to make sure its still checked. + for c in "/?#@:": # pragma: no branch + if c in normalized_netloc: + raise ValueError( + f"netloc '{netloc}' contains invalid " + "characters under NFKC normalization" + ) + + +@lru_cache # match the same size as urlsplit +def split_netloc( + netloc: str, +) -> tuple[str | None, str | None, str | None, int | None]: + """Split netloc into username, password, host and port.""" + if "@" not in netloc: + username: str | None = None + password: str | None = None + hostinfo = netloc + else: + userinfo, _, hostinfo = netloc.rpartition("@") + username, have_password, password = userinfo.partition(":") + if not have_password: + password = None + + if "[" in hostinfo: + _, _, bracketed = hostinfo.partition("[") + hostname, _, port_str = bracketed.partition("]") + _, _, port_str = port_str.partition(":") + else: + hostname, _, port_str = hostinfo.partition(":") + + if not port_str: + return username or None, password, hostname or None, None + + try: + port = int(port_str) + except ValueError: + raise ValueError("Invalid URL: port can't be converted to integer") + if not (0 <= port <= 65535): + raise ValueError("Port out of range 0-65535") + return username or None, password, hostname or None, port + + +def unsplit_result( + scheme: str, netloc: str, url: str, query: str, fragment: str +) -> str: + """Unsplit a URL without any normalization.""" + if netloc or (scheme and scheme in USES_AUTHORITY) or url[:2] == "//": + if url and url[:1] != "/": + url = f"{scheme}://{netloc}/{url}" if scheme else f"{scheme}:{url}" + else: + url = f"{scheme}://{netloc}{url}" if scheme else f"//{netloc}{url}" + elif scheme: + url = f"{scheme}:{url}" + if query: + url = f"{url}?{query}" + return f"{url}#{fragment}" if fragment else url + + +@lru_cache # match the same size as urlsplit +def make_netloc( + user: str | None, + password: str | None, + host: str | None, + port: int | None, + encode: bool = False, +) -> str: + """Make netloc from parts. + + The user and password are encoded if encode is True. + + The host must already be encoded with _encode_host. + """ + if host is None: + return "" + ret = host + if port is not None: + ret = f"{ret}:{port}" + if user is None and password is None: + return ret + if password is not None: + if not user: + user = "" + elif encode: + user = QUOTER(user) + if encode: + password = QUOTER(password) + user = f"{user}:{password}" + elif user and encode: + user = QUOTER(user) + return f"{user}@{ret}" if user else ret + + +def query_to_pairs(query_string: str) -> list[tuple[str, str]]: + """Parse a query given as a string argument. + + Works like urllib.parse.parse_qsl with keep empty values. + """ + pairs: list[tuple[str, str]] = [] + if not query_string: + return pairs + for k_v in query_string.split("&"): + k, _, v = k_v.partition("=") + pairs.append((UNQUOTER_PLUS(k), UNQUOTER_PLUS(v))) + return pairs diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/yarl/_path.py b/bioagent-bench/.venv/lib/python3.14/site-packages/yarl/_path.py new file mode 100644 index 0000000000000000000000000000000000000000..c22f0b4b8cdd9280fd36789e2bc052b1c4938167 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/yarl/_path.py @@ -0,0 +1,41 @@ +"""Utilities for working with paths.""" + +from collections.abc import Sequence +from contextlib import suppress + + +def normalize_path_segments(segments: Sequence[str]) -> list[str]: + """Drop '.' and '..' from a sequence of str segments""" + + resolved_path: list[str] = [] + + for seg in segments: + if seg == "..": + # ignore any .. segments that would otherwise cause an + # IndexError when popped from resolved_path if + # resolving for rfc3986 + with suppress(IndexError): + resolved_path.pop() + elif seg != ".": + resolved_path.append(seg) + + if segments and segments[-1] in (".", ".."): + # do some post-processing here. + # if the last segment was a relative dir, + # then we need to append the trailing '/' + resolved_path.append("") + + return resolved_path + + +def normalize_path(path: str) -> str: + # Drop '.' and '..' from str path + prefix = "" + if path and path[0] == "/": + # preserve the "/" root element of absolute paths, copying it to the + # normalised output as per sections 5.2.4 and 6.2.2.3 of rfc3986. + prefix = "/" + path = path[1:] + + segments = path.split("/") + return prefix + "/".join(normalize_path_segments(segments)) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/yarl/_query.py b/bioagent-bench/.venv/lib/python3.14/site-packages/yarl/_query.py new file mode 100644 index 0000000000000000000000000000000000000000..809948d9a30c3e23207daf3573397511105ad6a4 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/yarl/_query.py @@ -0,0 +1,121 @@ +"""Query string handling.""" + +import math +from collections.abc import Iterable, Mapping, Sequence +from typing import TYPE_CHECKING, Any, SupportsInt, Union, cast + +from multidict import istr + +from ._quoters import QUERY_PART_QUOTER, QUERY_QUOTER + +SimpleQuery = Union[str, SupportsInt, float] +QueryVariable = Union[SimpleQuery, Sequence[SimpleQuery]] +Query = Union[ + None, str, Mapping[str, QueryVariable], Sequence[tuple[str, QueryVariable]] +] + + +def query_var(v: SimpleQuery) -> str: + """Convert a query variable to a string.""" + cls = type(v) + if cls is int: # Fast path for non-subclassed int + return str(v) + if isinstance(v, str): + return v + if isinstance(v, float): + if math.isinf(v): + raise ValueError("float('inf') is not supported") + if math.isnan(v): + raise ValueError("float('nan') is not supported") + return str(float(v)) + if cls is not bool and isinstance(v, SupportsInt): + return str(int(v)) + raise TypeError( + "Invalid variable type: value " + "should be str, int or float, got {!r} " + "of type {}".format(v, cls) + ) + + +def get_str_query_from_sequence_iterable( + items: Iterable[tuple[str | istr, QueryVariable]], +) -> str: + """Return a query string from a sequence of (key, value) pairs. + + value is a single value or a sequence of values for the key + + The sequence of values must be a list or tuple. + """ + quoter = QUERY_PART_QUOTER + pairs = [ + f"{quoter(k)}={quoter(v if type(v) is str else query_var(v))}" + for k, val in items + for v in ( + val if type(val) is not str and isinstance(val, (list, tuple)) else (val,) + ) + ] + return "&".join(pairs) + + +def get_str_query_from_iterable( + items: Iterable[tuple[str | istr, SimpleQuery]], +) -> str: + """Return a query string from an iterable. + + The iterable must contain (key, value) pairs. + + The values are not allowed to be sequences, only single values are + allowed. For sequences, use `_get_str_query_from_sequence_iterable`. + """ + quoter = QUERY_PART_QUOTER + # A listcomp is used since listcomps are inlined on CPython 3.12+ and + # they are a bit faster than a generator expression. + pairs = [ + f"{quoter(k)}={quoter(v if type(v) is str else query_var(v))}" for k, v in items + ] + return "&".join(pairs) + + +def get_str_query(*args: Any, **kwargs: Any) -> str | None: + """Return a query string from supported args.""" + query: ( + str + | Mapping[str, QueryVariable] + | Sequence[tuple[str | istr, SimpleQuery]] + | None + ) + if kwargs: + if args: + msg = "Either kwargs or single query parameter must be present" + raise ValueError(msg) + query = kwargs + elif len(args) == 1: + query = args[0] + else: + raise ValueError("Either kwargs or single query parameter must be present") + + if query is None: + return None + if not query: + return "" + if type(query) is dict: + return get_str_query_from_sequence_iterable(query.items()) + if type(query) is str or isinstance(query, str): + return QUERY_QUOTER(query) + if isinstance(query, Mapping): + return get_str_query_from_sequence_iterable(query.items()) + if isinstance(query, (bytes, bytearray, memoryview)): + msg = "Invalid query type: bytes, bytearray and memoryview are forbidden" + raise TypeError(msg) + if isinstance(query, Sequence): + # We don't expect sequence values if we're given a list of pairs + # already; only mappings like builtin `dict` which can't have the + # same key pointing to multiple values are allowed to use + # `_query_seq_pairs`. + if TYPE_CHECKING: + query = cast(Sequence[tuple[Union[str, istr], SimpleQuery]], query) + return get_str_query_from_iterable(query) + raise TypeError( + "Invalid query type: only str, mapping or " + "sequence of (key, value) pairs is allowed" + ) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/yarl/_quoters.py b/bioagent-bench/.venv/lib/python3.14/site-packages/yarl/_quoters.py new file mode 100644 index 0000000000000000000000000000000000000000..78f77ca1249f0c5b609d2605c7b01460c084350d --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/yarl/_quoters.py @@ -0,0 +1,32 @@ +"""Quoting and unquoting utilities for URL parts.""" + +from urllib.parse import quote + +from ._quoting import _Quoter, _Unquoter + +QUOTER = _Quoter(requote=False) +REQUOTER = _Quoter() +PATH_QUOTER = _Quoter(safe="@:", protected="/+", requote=False) +PATH_REQUOTER = _Quoter(safe="@:", protected="/+") +QUERY_QUOTER = _Quoter(safe="?/:@", protected="=+&;", qs=True, requote=False) +QUERY_REQUOTER = _Quoter(safe="?/:@", protected="=+&;", qs=True) +QUERY_PART_QUOTER = _Quoter(safe="?/:@", qs=True, requote=False) +FRAGMENT_QUOTER = _Quoter(safe="?/:@", requote=False) +FRAGMENT_REQUOTER = _Quoter(safe="?/:@") + +UNQUOTER = _Unquoter() +PATH_UNQUOTER = _Unquoter(unsafe="+") +PATH_SAFE_UNQUOTER = _Unquoter(ignore="/%", unsafe="+") +QS_UNQUOTER = _Unquoter(qs=True) +UNQUOTER_PLUS = _Unquoter(plus=True) # to match urllib.parse.unquote_plus + + +def human_quote(s: str | None, unsafe: str) -> str | None: + if not s: + return s + for c in "%" + unsafe: + if c in s: + s = s.replace(c, f"%{ord(c):02X}") + if s.isprintable(): + return s + return "".join(c if c.isprintable() else quote(c) for c in s) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/yarl/_quoting.py b/bioagent-bench/.venv/lib/python3.14/site-packages/yarl/_quoting.py new file mode 100644 index 0000000000000000000000000000000000000000..25d76c885cacaa815bb7e0149aedbe76c20f2228 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/yarl/_quoting.py @@ -0,0 +1,19 @@ +import os +import sys +from typing import TYPE_CHECKING + +__all__ = ("_Quoter", "_Unquoter") + + +NO_EXTENSIONS = bool(os.environ.get("YARL_NO_EXTENSIONS")) # type: bool +if sys.implementation.name != "cpython": + NO_EXTENSIONS = True + + +if TYPE_CHECKING or NO_EXTENSIONS: + from ._quoting_py import _Quoter, _Unquoter +else: + try: + from ._quoting_c import _Quoter, _Unquoter + except ImportError: # pragma: no cover + from ._quoting_py import _Quoter, _Unquoter # type: ignore[assignment] diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/yarl/_quoting_c.pyx b/bioagent-bench/.venv/lib/python3.14/site-packages/yarl/_quoting_c.pyx new file mode 100644 index 0000000000000000000000000000000000000000..dacf6b088c53e77a43f0435c06a2cbf51dcf4486 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/yarl/_quoting_c.pyx @@ -0,0 +1,451 @@ +from cpython.exc cimport PyErr_NoMemory +from cpython.mem cimport PyMem_Free, PyMem_Malloc, PyMem_Realloc +from cpython.unicode cimport ( + PyUnicode_DATA, + PyUnicode_DecodeASCII, + PyUnicode_DecodeUTF8Stateful, + PyUnicode_GET_LENGTH, + PyUnicode_KIND, + PyUnicode_READ, +) +from libc.stdint cimport uint8_t, uint64_t +from libc.string cimport memcpy, memset + +from string import ascii_letters, digits + + +cdef str GEN_DELIMS = ":/?#[]@" +cdef str SUB_DELIMS_WITHOUT_QS = "!$'()*," +cdef str SUB_DELIMS = SUB_DELIMS_WITHOUT_QS + '+?=;' +cdef str RESERVED = GEN_DELIMS + SUB_DELIMS +cdef str UNRESERVED = ascii_letters + digits + '-._~' +cdef str ALLOWED = UNRESERVED + SUB_DELIMS_WITHOUT_QS +cdef str QS = '+&=;' + +DEF BUF_SIZE = 8 * 1024 # 8KiB + +cdef inline Py_UCS4 _to_hex(uint8_t v) noexcept: + if v < 10: + return (v+0x30) # ord('0') == 0x30 + else: + return (v+0x41-10) # ord('A') == 0x41 + + +cdef inline int _from_hex(Py_UCS4 v) noexcept: + if '0' <= v <= '9': + return (v) - 0x30 # ord('0') == 0x30 + elif 'A' <= v <= 'F': + return (v) - 0x41 + 10 # ord('A') == 0x41 + elif 'a' <= v <= 'f': + return (v) - 0x61 + 10 # ord('a') == 0x61 + else: + return -1 + + +cdef inline int _is_lower_hex(Py_UCS4 v) noexcept: + return 'a' <= v <= 'f' + + +cdef inline long _restore_ch(Py_UCS4 d1, Py_UCS4 d2): + cdef int digit1 = _from_hex(d1) + if digit1 < 0: + return -1 + cdef int digit2 = _from_hex(d2) + if digit2 < 0: + return -1 + return digit1 << 4 | digit2 + + +cdef uint8_t ALLOWED_TABLE[16] +cdef uint8_t ALLOWED_NOTQS_TABLE[16] + + +cdef inline bint bit_at(uint8_t array[], uint64_t ch) noexcept: + return array[ch >> 3] & (1 << (ch & 7)) + + +cdef inline void set_bit(uint8_t array[], uint64_t ch) noexcept: + array[ch >> 3] |= (1 << (ch & 7)) + + +memset(ALLOWED_TABLE, 0, sizeof(ALLOWED_TABLE)) +memset(ALLOWED_NOTQS_TABLE, 0, sizeof(ALLOWED_NOTQS_TABLE)) + +for i in range(128): + if chr(i) in ALLOWED: + set_bit(ALLOWED_TABLE, i) + set_bit(ALLOWED_NOTQS_TABLE, i) + if chr(i) in QS: + set_bit(ALLOWED_NOTQS_TABLE, i) + +# ----------------- writer --------------------------- + +cdef struct Writer: + char *buf + bint heap_allocated_buf + Py_ssize_t size + Py_ssize_t pos + bint changed + + +cdef inline void _init_writer(Writer* writer, char* buf): + writer.buf = buf + writer.heap_allocated_buf = False + writer.size = BUF_SIZE + writer.pos = 0 + writer.changed = 0 + + +cdef inline void _release_writer(Writer* writer): + if writer.heap_allocated_buf: + PyMem_Free(writer.buf) + + +cdef inline int _write_char(Writer* writer, Py_UCS4 ch, bint changed): + cdef char * buf + cdef Py_ssize_t size + + if writer.pos == writer.size: + # reallocate + size = writer.size + BUF_SIZE + if not writer.heap_allocated_buf: + buf = PyMem_Malloc(size) + if buf == NULL: + PyErr_NoMemory() + return -1 + memcpy(buf, writer.buf, writer.size) + writer.heap_allocated_buf = True + else: + buf = PyMem_Realloc(writer.buf, size) + if buf == NULL: + PyErr_NoMemory() + return -1 + writer.buf = buf + writer.size = size + writer.buf[writer.pos] = ch + writer.pos += 1 + writer.changed |= changed + return 0 + + +cdef inline int _write_pct(Writer* writer, uint8_t ch, bint changed): + if _write_char(writer, '%', changed) < 0: + return -1 + if _write_char(writer, _to_hex(ch >> 4), changed) < 0: + return -1 + return _write_char(writer, _to_hex(ch & 0x0f), changed) + + +cdef inline int _write_utf8(Writer* writer, Py_UCS4 symbol): + cdef uint64_t utf = symbol + + if utf < 0x80: + return _write_pct(writer, utf, True) + elif utf < 0x800: + if _write_pct(writer, (0xc0 | (utf >> 6)), True) < 0: + return -1 + return _write_pct(writer, (0x80 | (utf & 0x3f)), True) + elif 0xD800 <= utf <= 0xDFFF: + # surogate pair, ignored + return 0 + elif utf < 0x10000: + if _write_pct(writer, (0xe0 | (utf >> 12)), True) < 0: + return -1 + if _write_pct(writer, (0x80 | ((utf >> 6) & 0x3f)), + True) < 0: + return -1 + return _write_pct(writer, (0x80 | (utf & 0x3f)), True) + elif utf > 0x10FFFF: + # symbol is too large + return 0 + else: + if _write_pct(writer, (0xf0 | (utf >> 18)), True) < 0: + return -1 + if _write_pct(writer, (0x80 | ((utf >> 12) & 0x3f)), + True) < 0: + return -1 + if _write_pct(writer, (0x80 | ((utf >> 6) & 0x3f)), + True) < 0: + return -1 + return _write_pct(writer, (0x80 | (utf & 0x3f)), True) + + +# --------------------- end writer -------------------------- + + +cdef class _Quoter: + cdef bint _qs + cdef bint _requote + + cdef uint8_t _safe_table[16] + cdef uint8_t _protected_table[16] + + def __init__( + self, *, str safe='', str protected='', bint qs=False, bint requote=True, + ): + cdef Py_UCS4 ch + + self._qs = qs + self._requote = requote + + if not self._qs: + memcpy(self._safe_table, + ALLOWED_NOTQS_TABLE, + sizeof(self._safe_table)) + else: + memcpy(self._safe_table, + ALLOWED_TABLE, + sizeof(self._safe_table)) + for ch in safe: + if ord(ch) > 127: + raise ValueError("Only safe symbols with ORD < 128 are allowed") + set_bit(self._safe_table, ch) + + memset(self._protected_table, 0, sizeof(self._protected_table)) + for ch in protected: + if ord(ch) > 127: + raise ValueError("Only safe symbols with ORD < 128 are allowed") + set_bit(self._safe_table, ch) + set_bit(self._protected_table, ch) + + def __call__(self, val): + if val is None: + return None + if type(val) is not str: + if isinstance(val, str): + # derived from str + val = str(val) + else: + raise TypeError("Argument should be str") + return self._do_quote_or_skip(val) + + cdef str _do_quote_or_skip(self, str val): + cdef char[BUF_SIZE] buffer + cdef Py_UCS4 ch + cdef Py_ssize_t length = PyUnicode_GET_LENGTH(val) + cdef Py_ssize_t idx = length + cdef bint must_quote = 0 + cdef Writer writer + cdef int kind = PyUnicode_KIND(val) + cdef const void *data = PyUnicode_DATA(val) + + # If everything in the string is in the safe + # table and all ASCII, we can skip quoting + while idx: + idx -= 1 + ch = PyUnicode_READ(kind, data, idx) + if ch >= 128 or not bit_at(self._safe_table, ch): + must_quote = 1 + break + + if not must_quote: + return val + + _init_writer(&writer, &buffer[0]) + try: + return self._do_quote(val, length, kind, data, &writer) + finally: + _release_writer(&writer) + + cdef str _do_quote( + self, + str val, + Py_ssize_t length, + int kind, + const void *data, + Writer *writer + ): + cdef Py_UCS4 ch + cdef long chl + cdef int changed + cdef Py_ssize_t idx = 0 + + while idx < length: + ch = PyUnicode_READ(kind, data, idx) + idx += 1 + if ch == '%' and self._requote and idx <= length - 2: + chl = _restore_ch( + PyUnicode_READ(kind, data, idx), + PyUnicode_READ(kind, data, idx + 1) + ) + if chl != -1: + ch = chl + idx += 2 + if ch < 128: + if bit_at(self._protected_table, ch): + if _write_pct(writer, ch, True) < 0: + raise + continue + + if bit_at(self._safe_table, ch): + if _write_char(writer, ch, True) < 0: + raise + continue + + changed = (_is_lower_hex(PyUnicode_READ(kind, data, idx - 2)) or + _is_lower_hex(PyUnicode_READ(kind, data, idx - 1))) + if _write_pct(writer, ch, changed) < 0: + raise + continue + else: + ch = '%' + + if self._write(writer, ch) < 0: + raise + + if not writer.changed: + return val + else: + return PyUnicode_DecodeASCII(writer.buf, writer.pos, "strict") + + cdef inline int _write(self, Writer *writer, Py_UCS4 ch): + if self._qs: + if ch == ' ': + return _write_char(writer, '+', True) + + if ch < 128 and bit_at(self._safe_table, ch): + return _write_char(writer, ch, False) + + return _write_utf8(writer, ch) + + +cdef class _Unquoter: + cdef str _ignore + cdef bint _has_ignore + cdef str _unsafe + cdef bytes _unsafe_bytes + cdef Py_ssize_t _unsafe_bytes_len + cdef const unsigned char * _unsafe_bytes_char + cdef bint _qs + cdef bint _plus # to match urllib.parse.unquote_plus + cdef _Quoter _quoter + cdef _Quoter _qs_quoter + + def __init__(self, *, ignore="", unsafe="", qs=False, plus=False): + self._ignore = ignore + self._has_ignore = bool(self._ignore) + self._unsafe = unsafe + # unsafe may only be extended ascii characters (0-255) + self._unsafe_bytes = self._unsafe.encode('ascii') + self._unsafe_bytes_len = len(self._unsafe_bytes) + self._unsafe_bytes_char = self._unsafe_bytes + self._qs = qs + self._plus = plus + self._quoter = _Quoter() + self._qs_quoter = _Quoter(qs=True) + + def __call__(self, val): + if val is None: + return None + if type(val) is not str: + if isinstance(val, str): + # derived from str + val = str(val) + else: + raise TypeError("Argument should be str") + return self._do_unquote(val) + + cdef str _do_unquote(self, str val): + cdef Py_ssize_t length = PyUnicode_GET_LENGTH(val) + if length == 0: + return val + + cdef list ret = [] + cdef char buffer[4] + cdef Py_ssize_t buflen = 0 + cdef Py_ssize_t consumed + cdef str unquoted + cdef Py_UCS4 ch = 0 + cdef long chl = 0 + cdef Py_ssize_t idx = 0 + cdef Py_ssize_t start_pct + cdef int kind = PyUnicode_KIND(val) + cdef const void *data = PyUnicode_DATA(val) + cdef bint changed = 0 + while idx < length: + ch = PyUnicode_READ(kind, data, idx) + idx += 1 + if ch == '%' and idx <= length - 2: + changed = 1 + chl = _restore_ch( + PyUnicode_READ(kind, data, idx), + PyUnicode_READ(kind, data, idx + 1) + ) + if chl != -1: + ch = chl + idx += 2 + assert buflen < 4 + buffer[buflen] = ch + buflen += 1 + try: + unquoted = PyUnicode_DecodeUTF8Stateful(buffer, buflen, + NULL, &consumed) + except UnicodeDecodeError: + start_pct = idx - buflen * 3 + buffer[0] = ch + buflen = 1 + ret.append(val[start_pct : idx - 3]) + try: + unquoted = PyUnicode_DecodeUTF8Stateful(buffer, buflen, + NULL, &consumed) + except UnicodeDecodeError: + buflen = 0 + ret.append(val[idx - 3 : idx]) + continue + if not unquoted: + assert consumed == 0 + continue + assert consumed == buflen + buflen = 0 + if self._qs and unquoted in '+=&;': + ret.append(self._qs_quoter(unquoted)) + elif ( + (self._unsafe_bytes_len and unquoted in self._unsafe) or + (self._has_ignore and unquoted in self._ignore) + ): + ret.append(self._quoter(unquoted)) + else: + ret.append(unquoted) + continue + else: + ch = '%' + + if buflen: + start_pct = idx - 1 - buflen * 3 + ret.append(val[start_pct : idx - 1]) + buflen = 0 + + if ch == '+': + if ( + (not self._qs and not self._plus) or + (self._unsafe_bytes_len and self._is_char_unsafe(ch)) + ): + ret.append('+') + else: + changed = 1 + ret.append(' ') + continue + + if self._unsafe_bytes_len and self._is_char_unsafe(ch): + changed = 1 + ret.append('%') + h = hex(ord(ch)).upper()[2:] + for ch in h: + ret.append(ch) + continue + + ret.append(ch) + + if not changed: + return val + + if buflen: + ret.append(val[length - buflen * 3 : length]) + + return ''.join(ret) + + cdef inline bint _is_char_unsafe(self, Py_UCS4 ch): + for i in range(self._unsafe_bytes_len): + if ch == self._unsafe_bytes_char[i]: + return True + return False diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/yarl/_quoting_py.py b/bioagent-bench/.venv/lib/python3.14/site-packages/yarl/_quoting_py.py new file mode 100644 index 0000000000000000000000000000000000000000..98197aa98db038ade28f206f8d71a1f68d0b9124 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/yarl/_quoting_py.py @@ -0,0 +1,213 @@ +import codecs +import re +from string import ascii_letters, ascii_lowercase, digits +from typing import overload + +BASCII_LOWERCASE = ascii_lowercase.encode("ascii") +BPCT_ALLOWED = {f"%{i:02X}".encode("ascii") for i in range(256)} +GEN_DELIMS = ":/?#[]@" +SUB_DELIMS_WITHOUT_QS = "!$'()*," +SUB_DELIMS = SUB_DELIMS_WITHOUT_QS + "+&=;" +RESERVED = GEN_DELIMS + SUB_DELIMS +UNRESERVED = ascii_letters + digits + "-._~" +ALLOWED = UNRESERVED + SUB_DELIMS_WITHOUT_QS + + +_IS_HEX = re.compile(b"[A-Z0-9][A-Z0-9]") +_IS_HEX_STR = re.compile("[A-Fa-f0-9][A-Fa-f0-9]") + +utf8_decoder = codecs.getincrementaldecoder("utf-8") + + +class _Quoter: + def __init__( + self, + *, + safe: str = "", + protected: str = "", + qs: bool = False, + requote: bool = True, + ) -> None: + self._safe = safe + self._protected = protected + self._qs = qs + self._requote = requote + + @overload + def __call__(self, val: str) -> str: ... + @overload + def __call__(self, val: None) -> None: ... + def __call__(self, val: str | None) -> str | None: + if val is None: + return None + if not isinstance(val, str): + raise TypeError("Argument should be str") + if not val: + return "" + bval = val.encode("utf8", errors="ignore") + ret = bytearray() + pct = bytearray() + safe = self._safe + safe += ALLOWED + if not self._qs: + safe += "+&=;" + safe += self._protected + bsafe = safe.encode("ascii") + idx = 0 + while idx < len(bval): + ch = bval[idx] + idx += 1 + + if pct: + if ch in BASCII_LOWERCASE: + ch = ch - 32 # convert to uppercase + pct.append(ch) + if len(pct) == 3: # pragma: no branch # peephole optimizer + buf = pct[1:] + if not _IS_HEX.match(buf): + ret.extend(b"%25") + pct.clear() + idx -= 2 + continue + try: + unquoted = chr(int(pct[1:].decode("ascii"), base=16)) + except ValueError: + ret.extend(b"%25") + pct.clear() + idx -= 2 + continue + + if unquoted in self._protected: + ret.extend(pct) + elif unquoted in safe: + ret.append(ord(unquoted)) + else: + ret.extend(pct) + pct.clear() + + # special case, if we have only one char after "%" + elif len(pct) == 2 and idx == len(bval): + ret.extend(b"%25") + pct.clear() + idx -= 1 + + continue + + elif ch == ord("%") and self._requote: + pct.clear() + pct.append(ch) + + # special case if "%" is last char + if idx == len(bval): + ret.extend(b"%25") + + continue + + if self._qs and ch == ord(" "): + ret.append(ord("+")) + continue + if ch in bsafe: + ret.append(ch) + continue + + ret.extend((f"%{ch:02X}").encode("ascii")) + + ret2 = ret.decode("ascii") + if ret2 == val: + return val + return ret2 + + +class _Unquoter: + def __init__( + self, + *, + ignore: str = "", + unsafe: str = "", + qs: bool = False, + plus: bool = False, + ) -> None: + self._ignore = ignore + self._unsafe = unsafe + self._qs = qs + self._plus = plus # to match urllib.parse.unquote_plus + self._quoter = _Quoter() + self._qs_quoter = _Quoter(qs=True) + + @overload + def __call__(self, val: str) -> str: ... + @overload + def __call__(self, val: None) -> None: ... + def __call__(self, val: str | None) -> str | None: + if val is None: + return None + if not isinstance(val, str): + raise TypeError("Argument should be str") + if not val: + return "" + decoder = utf8_decoder() + ret = [] + idx = 0 + while idx < len(val): + ch = val[idx] + idx += 1 + if ch == "%" and idx <= len(val) - 2: + pct = val[idx : idx + 2] + if _IS_HEX_STR.fullmatch(pct): + b = bytes([int(pct, base=16)]) + idx += 2 + try: + unquoted = decoder.decode(b) + except UnicodeDecodeError: + start_pct = idx - 3 - len(decoder.buffer) * 3 + ret.append(val[start_pct : idx - 3]) + decoder.reset() + try: + unquoted = decoder.decode(b) + except UnicodeDecodeError: + ret.append(val[idx - 3 : idx]) + continue + if not unquoted: + continue + if self._qs and unquoted in "+=&;": + to_add = self._qs_quoter(unquoted) + if to_add is None: # pragma: no cover + raise RuntimeError("Cannot quote None") + ret.append(to_add) + elif unquoted in self._unsafe or unquoted in self._ignore: + to_add = self._quoter(unquoted) + if to_add is None: # pragma: no cover + raise RuntimeError("Cannot quote None") + ret.append(to_add) + else: + ret.append(unquoted) + continue + + if decoder.buffer: + start_pct = idx - 1 - len(decoder.buffer) * 3 + ret.append(val[start_pct : idx - 1]) + decoder.reset() + + if ch == "+": + if (not self._qs and not self._plus) or ch in self._unsafe: + ret.append("+") + else: + ret.append(" ") + continue + + if ch in self._unsafe: + ret.append("%") + h = hex(ord(ch)).upper()[2:] + for ch in h: + ret.append(ch) + continue + + ret.append(ch) + + if decoder.buffer: + ret.append(val[-len(decoder.buffer) * 3 :]) + + ret2 = "".join(ret) + if ret2 == val: + return val + return ret2 diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/yarl/_url.py b/bioagent-bench/.venv/lib/python3.14/site-packages/yarl/_url.py new file mode 100644 index 0000000000000000000000000000000000000000..75043d52185a9b52191330d43ae9646cbbb06d42 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/yarl/_url.py @@ -0,0 +1,1665 @@ +import re +import sys +import warnings +from collections.abc import Mapping, Sequence +from enum import Enum +from functools import _CacheInfo, lru_cache +from ipaddress import ip_address +from typing import ( + TYPE_CHECKING, + Any, + NoReturn, + TypedDict, + TypeVar, + Union, + cast, + overload, +) +from urllib.parse import SplitResult, uses_relative + +import idna +from multidict import MultiDict, MultiDictProxy, istr +from propcache.api import under_cached_property as cached_property + +from ._parse import ( + USES_AUTHORITY, + SplitURLType, + make_netloc, + query_to_pairs, + split_netloc, + split_url, + unsplit_result, +) +from ._path import normalize_path, normalize_path_segments +from ._query import ( + Query, + QueryVariable, + SimpleQuery, + get_str_query, + get_str_query_from_iterable, + get_str_query_from_sequence_iterable, +) +from ._quoters import ( + FRAGMENT_QUOTER, + FRAGMENT_REQUOTER, + PATH_QUOTER, + PATH_REQUOTER, + PATH_SAFE_UNQUOTER, + PATH_UNQUOTER, + QS_UNQUOTER, + QUERY_QUOTER, + QUERY_REQUOTER, + QUOTER, + REQUOTER, + UNQUOTER, + human_quote, +) + +try: + from pydantic import GetCoreSchemaHandler, GetJsonSchemaHandler + from pydantic.json_schema import JsonSchemaValue + from pydantic_core import core_schema + + HAS_PYDANTIC = True +except ImportError: + HAS_PYDANTIC = False + + +DEFAULT_PORTS = {"http": 80, "https": 443, "ws": 80, "wss": 443, "ftp": 21} +USES_RELATIVE = frozenset(uses_relative) + +# Special schemes https://url.spec.whatwg.org/#special-scheme +# are not allowed to have an empty host https://url.spec.whatwg.org/#url-representation +SCHEME_REQUIRES_HOST = frozenset(("http", "https", "ws", "wss", "ftp")) + + +# reg-name: unreserved / pct-encoded / sub-delims +# this pattern matches anything that is *not* in those classes. and is only used +# on lower-cased ASCII values. +NOT_REG_NAME = re.compile( + r""" + # any character not in the unreserved or sub-delims sets, plus % + # (validated with the additional check for pct-encoded sequences below) + [^a-z0-9\-._~!$&'()*+,;=%] + | + # % only allowed if it is part of a pct-encoded + # sequence of 2 hex digits. + %(?![0-9a-f]{2}) + """, + re.VERBOSE, +) + +_T = TypeVar("_T") + +if sys.version_info >= (3, 11): + from typing import Self +else: + Self = Any + + +class UndefinedType(Enum): + """Singleton type for use with not set sentinel values.""" + + _singleton = 0 + + +UNDEFINED = UndefinedType._singleton + + +class CacheInfo(TypedDict): + """Host encoding cache.""" + + idna_encode: _CacheInfo + idna_decode: _CacheInfo + ip_address: _CacheInfo + host_validate: _CacheInfo + encode_host: _CacheInfo + + +class _InternalURLCache(TypedDict, total=False): + _val: SplitURLType + _origin: "URL" + absolute: bool + hash: int + scheme: str + raw_authority: str + authority: str + raw_user: str | None + user: str | None + raw_password: str | None + password: str | None + raw_host: str | None + host: str | None + host_subcomponent: str | None + host_port_subcomponent: str | None + port: int | None + explicit_port: int | None + raw_path: str + path: str + _parsed_query: list[tuple[str, str]] + query: "MultiDictProxy[str]" + raw_query_string: str + query_string: str + path_qs: str + raw_path_qs: str + raw_fragment: str + fragment: str + raw_parts: tuple[str, ...] + parts: tuple[str, ...] + parent: "URL" + raw_name: str + name: str + raw_suffix: str + suffix: str + raw_suffixes: tuple[str, ...] + suffixes: tuple[str, ...] + + +def rewrite_module(obj: _T) -> _T: + obj.__module__ = "yarl" + return obj + + +@lru_cache +def encode_url(url_str: str) -> "URL": + """Parse unencoded URL.""" + cache: _InternalURLCache = {} + host: str | None + scheme, netloc, path, query, fragment = split_url(url_str) + if not netloc: # netloc + host = "" + else: + if ":" in netloc or "@" in netloc or "[" in netloc: + # Complex netloc + username, password, host, port = split_netloc(netloc) + else: + username = password = port = None + host = netloc + if host is None: + if scheme in SCHEME_REQUIRES_HOST: + msg = ( + "Invalid URL: host is required for " + f"absolute urls with the {scheme} scheme" + ) + raise ValueError(msg) + else: + host = "" + host = _encode_host(host, validate_host=False) + # Remove brackets as host encoder adds back brackets for IPv6 addresses + cache["raw_host"] = host[1:-1] if "[" in host else host + cache["explicit_port"] = port + if password is None and username is None: + # Fast path for URLs without user, password + netloc = host if port is None else f"{host}:{port}" + cache["raw_user"] = None + cache["raw_password"] = None + else: + raw_user = REQUOTER(username) if username else username + raw_password = REQUOTER(password) if password else password + netloc = make_netloc(raw_user, raw_password, host, port) + cache["raw_user"] = raw_user + cache["raw_password"] = raw_password + + if path: + path = PATH_REQUOTER(path) + if netloc and "." in path: + path = normalize_path(path) + if query: + query = QUERY_REQUOTER(query) + if fragment: + fragment = FRAGMENT_REQUOTER(fragment) + + cache["scheme"] = scheme + cache["raw_path"] = "/" if not path and netloc else path + cache["raw_query_string"] = query + cache["raw_fragment"] = fragment + + self = object.__new__(URL) + self._scheme = scheme + self._netloc = netloc + self._path = path + self._query = query + self._fragment = fragment + self._cache = cache + return self + + +@lru_cache +def pre_encoded_url(url_str: str) -> "URL": + """Parse pre-encoded URL.""" + self = object.__new__(URL) + val = split_url(url_str) + self._scheme, self._netloc, self._path, self._query, self._fragment = val + self._cache = {} + return self + + +@lru_cache +def build_pre_encoded_url( + scheme: str, + authority: str, + user: str | None, + password: str | None, + host: str, + port: int | None, + path: str, + query_string: str, + fragment: str, +) -> "URL": + """Build a pre-encoded URL from parts.""" + self = object.__new__(URL) + self._scheme = scheme + if authority: + self._netloc = authority + elif host: + if port is not None: + port = None if port == DEFAULT_PORTS.get(scheme) else port + if user is None and password is None: + self._netloc = host if port is None else f"{host}:{port}" + else: + self._netloc = make_netloc(user, password, host, port) + else: + self._netloc = "" + self._path = path + self._query = query_string + self._fragment = fragment + self._cache = {} + return self + + +def from_parts_uncached( + scheme: str, netloc: str, path: str, query: str, fragment: str +) -> "URL": + """Create a new URL from parts.""" + self = object.__new__(URL) + self._scheme = scheme + self._netloc = netloc + self._path = path + self._query = query + self._fragment = fragment + self._cache = {} + return self + + +from_parts = lru_cache(from_parts_uncached) + + +@rewrite_module +class URL: + # Don't derive from str + # follow pathlib.Path design + # probably URL will not suffer from pathlib problems: + # it's intended for libraries like aiohttp, + # not to be passed into standard library functions like os.open etc. + + # URL grammar (RFC 3986) + # pct-encoded = "%" HEXDIG HEXDIG + # reserved = gen-delims / sub-delims + # gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@" + # sub-delims = "!" / "$" / "&" / "'" / "(" / ")" + # / "*" / "+" / "," / ";" / "=" + # unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + # URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] + # hier-part = "//" authority path-abempty + # / path-absolute + # / path-rootless + # / path-empty + # scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) + # authority = [ userinfo "@" ] host [ ":" port ] + # userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) + # host = IP-literal / IPv4address / reg-name + # IP-literal = "[" ( IPv6address / IPvFuture ) "]" + # IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" ) + # IPv6address = 6( h16 ":" ) ls32 + # / "::" 5( h16 ":" ) ls32 + # / [ h16 ] "::" 4( h16 ":" ) ls32 + # / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32 + # / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32 + # / [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32 + # / [ *4( h16 ":" ) h16 ] "::" ls32 + # / [ *5( h16 ":" ) h16 ] "::" h16 + # / [ *6( h16 ":" ) h16 ] "::" + # ls32 = ( h16 ":" h16 ) / IPv4address + # ; least-significant 32 bits of address + # h16 = 1*4HEXDIG + # ; 16 bits of address represented in hexadecimal + # IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet + # dec-octet = DIGIT ; 0-9 + # / %x31-39 DIGIT ; 10-99 + # / "1" 2DIGIT ; 100-199 + # / "2" %x30-34 DIGIT ; 200-249 + # / "25" %x30-35 ; 250-255 + # reg-name = *( unreserved / pct-encoded / sub-delims ) + # port = *DIGIT + # path = path-abempty ; begins with "/" or is empty + # / path-absolute ; begins with "/" but not "//" + # / path-noscheme ; begins with a non-colon segment + # / path-rootless ; begins with a segment + # / path-empty ; zero characters + # path-abempty = *( "/" segment ) + # path-absolute = "/" [ segment-nz *( "/" segment ) ] + # path-noscheme = segment-nz-nc *( "/" segment ) + # path-rootless = segment-nz *( "/" segment ) + # path-empty = 0 + # segment = *pchar + # segment-nz = 1*pchar + # segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" ) + # ; non-zero-length segment without any colon ":" + # pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + # query = *( pchar / "/" / "?" ) + # fragment = *( pchar / "/" / "?" ) + # URI-reference = URI / relative-ref + # relative-ref = relative-part [ "?" query ] [ "#" fragment ] + # relative-part = "//" authority path-abempty + # / path-absolute + # / path-noscheme + # / path-empty + # absolute-URI = scheme ":" hier-part [ "?" query ] + __slots__ = ("_cache", "_scheme", "_netloc", "_path", "_query", "_fragment") + + _cache: _InternalURLCache + _scheme: str + _netloc: str + _path: str + _query: str + _fragment: str + + def __new__( + cls, + val: Union[str, SplitResult, "URL", UndefinedType] = UNDEFINED, + *, + encoded: bool = False, + strict: bool | None = None, + ) -> "URL": + if strict is not None: # pragma: no cover + warnings.warn("strict parameter is ignored") + if type(val) is str: + return pre_encoded_url(val) if encoded else encode_url(val) + if type(val) is cls: + return val + if type(val) is SplitResult: + if not encoded: + raise ValueError("Cannot apply decoding to SplitResult") + return from_parts(*val) + if isinstance(val, str): + return pre_encoded_url(str(val)) if encoded else encode_url(str(val)) + if val is UNDEFINED: + # Special case for UNDEFINED since it might be unpickling and we do + # not want to cache as the `__set_state__` call would mutate the URL + # object in the `pre_encoded_url` or `encoded_url` caches. + self = object.__new__(URL) + self._scheme = self._netloc = self._path = self._query = self._fragment = "" + self._cache = {} + return self + raise TypeError("Constructor parameter should be str") + + @classmethod + def build( + cls, + *, + scheme: str = "", + authority: str = "", + user: str | None = None, + password: str | None = None, + host: str = "", + port: int | None = None, + path: str = "", + query: Query | None = None, + query_string: str = "", + fragment: str = "", + encoded: bool = False, + ) -> "URL": + """Creates and returns a new URL""" + + if authority and (user or password or host or port): + raise ValueError( + 'Can\'t mix "authority" with "user", "password", "host" or "port".' + ) + if port is not None and not isinstance(port, int): + raise TypeError(f"The port is required to be int, got {type(port)!r}.") + if port and not host: + raise ValueError('Can\'t build URL with "port" but without "host".') + if query and query_string: + raise ValueError('Only one of "query" or "query_string" should be passed') + if ( + scheme is None # type: ignore[redundant-expr] + or authority is None # type: ignore[redundant-expr] + or host is None # type: ignore[redundant-expr] + or path is None # type: ignore[redundant-expr] + or query_string is None # type: ignore[redundant-expr] + or fragment is None + ): + raise TypeError( + 'NoneType is illegal for "scheme", "authority", "host", "path", ' + '"query_string", and "fragment" args, use empty string instead.' + ) + + if query: + query_string = get_str_query(query) or "" + + if encoded: + return build_pre_encoded_url( + scheme, + authority, + user, + password, + host, + port, + path, + query_string, + fragment, + ) + + self = object.__new__(URL) + self._scheme = scheme + _host: str | None = None + if authority: + user, password, _host, port = split_netloc(authority) + _host = _encode_host(_host, validate_host=False) if _host else "" + elif host: + _host = _encode_host(host, validate_host=True) + else: + self._netloc = "" + + if _host is not None: + if port is not None: + port = None if port == DEFAULT_PORTS.get(scheme) else port + if user is None and password is None: + self._netloc = _host if port is None else f"{_host}:{port}" + else: + self._netloc = make_netloc(user, password, _host, port, True) + + path = PATH_QUOTER(path) if path else path + if path and self._netloc: + if "." in path: + path = normalize_path(path) + if path[0] != "/": + msg = ( + "Path in a URL with authority should " + "start with a slash ('/') if set" + ) + raise ValueError(msg) + + self._path = path + if not query and query_string: + query_string = QUERY_QUOTER(query_string) + self._query = query_string + self._fragment = FRAGMENT_QUOTER(fragment) if fragment else fragment + self._cache = {} + return self + + def __init_subclass__(cls) -> NoReturn: + raise TypeError(f"Inheriting a class {cls!r} from URL is forbidden") + + def __str__(self) -> str: + if not self._path and self._netloc and (self._query or self._fragment): + path = "/" + else: + path = self._path + if (port := self.explicit_port) is not None and port == DEFAULT_PORTS.get( + self._scheme + ): + # port normalization - using None for default ports to remove from rendering + # https://datatracker.ietf.org/doc/html/rfc3986.html#section-6.2.3 + host = self.host_subcomponent + netloc = make_netloc(self.raw_user, self.raw_password, host, None) + else: + netloc = self._netloc + return unsplit_result(self._scheme, netloc, path, self._query, self._fragment) + + def __repr__(self) -> str: + return f"{self.__class__.__name__}('{str(self)}')" + + def __bytes__(self) -> bytes: + return str(self).encode("ascii") + + def __eq__(self, other: object) -> bool: + if type(other) is not URL: + return NotImplemented + + path1 = "/" if not self._path and self._netloc else self._path + path2 = "/" if not other._path and other._netloc else other._path + return ( + self._scheme == other._scheme + and self._netloc == other._netloc + and path1 == path2 + and self._query == other._query + and self._fragment == other._fragment + ) + + def __hash__(self) -> int: + if (ret := self._cache.get("hash")) is None: + path = "/" if not self._path and self._netloc else self._path + ret = self._cache["hash"] = hash( + (self._scheme, self._netloc, path, self._query, self._fragment) + ) + return ret + + def __le__(self, other: object) -> bool: + if type(other) is not URL: + return NotImplemented + return self._val <= other._val + + def __lt__(self, other: object) -> bool: + if type(other) is not URL: + return NotImplemented + return self._val < other._val + + def __ge__(self, other: object) -> bool: + if type(other) is not URL: + return NotImplemented + return self._val >= other._val + + def __gt__(self, other: object) -> bool: + if type(other) is not URL: + return NotImplemented + return self._val > other._val + + def __truediv__(self, name: str) -> "URL": + if not isinstance(name, str): + return NotImplemented # type: ignore[unreachable] + return self._make_child((str(name),)) + + def __mod__(self, query: Query) -> "URL": + return self.update_query(query) + + def __bool__(self) -> bool: + return bool(self._netloc or self._path or self._query or self._fragment) + + def __getstate__(self) -> tuple[SplitResult]: + return (tuple.__new__(SplitResult, self._val),) + + def __setstate__( + self, state: tuple[SplitURLType] | tuple[None, _InternalURLCache] + ) -> None: + if state[0] is None and isinstance(state[1], dict): + # default style pickle + val = state[1]["_val"] + else: + unused: list[object] + val, *unused = state + self._scheme, self._netloc, self._path, self._query, self._fragment = val + self._cache = {} + + def _cache_netloc(self) -> None: + """Cache the netloc parts of the URL.""" + c = self._cache + split_loc = split_netloc(self._netloc) + c["raw_user"], c["raw_password"], c["raw_host"], c["explicit_port"] = split_loc + + def is_absolute(self) -> bool: + """A check for absolute URLs. + + Return True for absolute ones (having scheme or starting + with //), False otherwise. + + Is is preferred to call the .absolute property instead + as it is cached. + """ + return self.absolute + + def is_default_port(self) -> bool: + """A check for default port. + + Return True if port is default for specified scheme, + e.g. 'http://python.org' or 'http://python.org:80', False + otherwise. + + Return False for relative URLs. + + """ + if (explicit := self.explicit_port) is None: + # If the explicit port is None, then the URL must be + # using the default port unless its a relative URL + # which does not have an implicit port / default port + return self._netloc != "" + return explicit == DEFAULT_PORTS.get(self._scheme) + + def origin(self) -> "URL": + """Return an URL with scheme, host and port parts only. + + user, password, path, query and fragment are removed. + + """ + # TODO: add a keyword-only option for keeping user/pass maybe? + return self._origin + + @cached_property + def _val(self) -> SplitURLType: + return (self._scheme, self._netloc, self._path, self._query, self._fragment) + + @cached_property + def _origin(self) -> "URL": + """Return an URL with scheme, host and port parts only. + + user, password, path, query and fragment are removed. + """ + if not (netloc := self._netloc): + raise ValueError("URL should be absolute") + if not (scheme := self._scheme): + raise ValueError("URL should have scheme") + if "@" in netloc: + encoded_host = self.host_subcomponent + netloc = make_netloc(None, None, encoded_host, self.explicit_port) + elif not self._path and not self._query and not self._fragment: + return self + return from_parts(scheme, netloc, "", "", "") + + def relative(self) -> "URL": + """Return a relative part of the URL. + + scheme, user, password, host and port are removed. + + """ + if not self._netloc: + raise ValueError("URL should be absolute") + return from_parts("", "", self._path, self._query, self._fragment) + + @cached_property + def absolute(self) -> bool: + """A check for absolute URLs. + + Return True for absolute ones (having scheme or starting + with //), False otherwise. + + """ + # `netloc`` is an empty string for relative URLs + # Checking `netloc` is faster than checking `hostname` + # because `hostname` is a property that does some extra work + # to parse the host from the `netloc` + return self._netloc != "" + + @cached_property + def scheme(self) -> str: + """Scheme for absolute URLs. + + Empty string for relative URLs or URLs starting with // + + """ + return self._scheme + + @cached_property + def raw_authority(self) -> str: + """Encoded authority part of URL. + + Empty string for relative URLs. + + """ + return self._netloc + + @cached_property + def authority(self) -> str: + """Decoded authority part of URL. + + Empty string for relative URLs. + + """ + return make_netloc(self.user, self.password, self.host, self.port) + + @cached_property + def raw_user(self) -> str | None: + """Encoded user part of URL. + + None if user is missing. + + """ + # not .username + self._cache_netloc() + return self._cache["raw_user"] + + @cached_property + def user(self) -> str | None: + """Decoded user part of URL. + + None if user is missing. + + """ + if (raw_user := self.raw_user) is None: + return None + return UNQUOTER(raw_user) + + @cached_property + def raw_password(self) -> str | None: + """Encoded password part of URL. + + None if password is missing. + + """ + self._cache_netloc() + return self._cache["raw_password"] + + @cached_property + def password(self) -> str | None: + """Decoded password part of URL. + + None if password is missing. + + """ + if (raw_password := self.raw_password) is None: + return None + return UNQUOTER(raw_password) + + @cached_property + def raw_host(self) -> str | None: + """Encoded host part of URL. + + None for relative URLs. + + When working with IPv6 addresses, use the `host_subcomponent` property instead + as it will return the host subcomponent with brackets. + """ + # Use host instead of hostname for sake of shortness + # May add .hostname prop later + self._cache_netloc() + return self._cache["raw_host"] + + @cached_property + def host(self) -> str | None: + """Decoded host part of URL. + + None for relative URLs. + + """ + if (raw := self.raw_host) is None: + return None + if raw and raw[-1].isdigit() or ":" in raw: + # IP addresses are never IDNA encoded + return raw + return _idna_decode(raw) + + @cached_property + def host_subcomponent(self) -> str | None: + """Return the host subcomponent part of URL. + + None for relative URLs. + + https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2 + + `IP-literal = "[" ( IPv6address / IPvFuture ) "]"` + + Examples: + - `http://example.com:8080` -> `example.com` + - `http://example.com:80` -> `example.com` + - `https://127.0.0.1:8443` -> `127.0.0.1` + - `https://[::1]:8443` -> `[::1]` + - `http://[::1]` -> `[::1]` + + """ + if (raw := self.raw_host) is None: + return None + return f"[{raw}]" if ":" in raw else raw + + @cached_property + def host_port_subcomponent(self) -> str | None: + """Return the host and port subcomponent part of URL. + + Trailing dots are removed from the host part. + + This value is suitable for use in the Host header of an HTTP request. + + None for relative URLs. + + https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2 + `IP-literal = "[" ( IPv6address / IPvFuture ) "]"` + https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.3 + port = *DIGIT + + Examples: + - `http://example.com:8080` -> `example.com:8080` + - `http://example.com:80` -> `example.com` + - `http://example.com.:80` -> `example.com` + - `https://127.0.0.1:8443` -> `127.0.0.1:8443` + - `https://[::1]:8443` -> `[::1]:8443` + - `http://[::1]` -> `[::1]` + + """ + if (raw := self.raw_host) is None: + return None + if raw[-1] == ".": + # Remove all trailing dots from the netloc as while + # they are valid FQDNs in DNS, TLS validation fails. + # See https://github.com/aio-libs/aiohttp/issues/3636. + # To avoid string manipulation we only call rstrip if + # the last character is a dot. + raw = raw.rstrip(".") + port = self.explicit_port + if port is None or port == DEFAULT_PORTS.get(self._scheme): + return f"[{raw}]" if ":" in raw else raw + return f"[{raw}]:{port}" if ":" in raw else f"{raw}:{port}" + + @cached_property + def port(self) -> int | None: + """Port part of URL, with scheme-based fallback. + + None for relative URLs or URLs without explicit port and + scheme without default port substitution. + + """ + if (explicit_port := self.explicit_port) is not None: + return explicit_port + return DEFAULT_PORTS.get(self._scheme) + + @cached_property + def explicit_port(self) -> int | None: + """Port part of URL, without scheme-based fallback. + + None for relative URLs or URLs without explicit port. + + """ + self._cache_netloc() + return self._cache["explicit_port"] + + @cached_property + def raw_path(self) -> str: + """Encoded path of URL. + + / for absolute URLs without path part. + + """ + return self._path if self._path or not self._netloc else "/" + + @cached_property + def path(self) -> str: + """Decoded path of URL. + + / for absolute URLs without path part. + + """ + return PATH_UNQUOTER(self._path) if self._path else "/" if self._netloc else "" + + @cached_property + def path_safe(self) -> str: + """Decoded path of URL. + + / for absolute URLs without path part. + + / (%2F) and % (%25) are not decoded + + """ + if self._path: + return PATH_SAFE_UNQUOTER(self._path) + return "/" if self._netloc else "" + + @cached_property + def _parsed_query(self) -> list[tuple[str, str]]: + """Parse query part of URL.""" + return query_to_pairs(self._query) + + @cached_property + def query(self) -> "MultiDictProxy[str]": + """A MultiDictProxy representing parsed query parameters in decoded + representation. + + Empty value if URL has no query part. + + """ + return MultiDictProxy(MultiDict(self._parsed_query)) + + @cached_property + def raw_query_string(self) -> str: + """Encoded query part of URL. + + Empty string if query is missing. + + """ + return self._query + + @cached_property + def query_string(self) -> str: + """Decoded query part of URL. + + Empty string if query is missing. + + """ + return QS_UNQUOTER(self._query) if self._query else "" + + @cached_property + def path_qs(self) -> str: + """Decoded path of URL with query.""" + return self.path if not (q := self.query_string) else f"{self.path}?{q}" + + @cached_property + def raw_path_qs(self) -> str: + """Encoded path of URL with query.""" + if q := self._query: + return f"{self._path}?{q}" if self._path or not self._netloc else f"/?{q}" + return self._path if self._path or not self._netloc else "/" + + @cached_property + def raw_fragment(self) -> str: + """Encoded fragment part of URL. + + Empty string if fragment is missing. + + """ + return self._fragment + + @cached_property + def fragment(self) -> str: + """Decoded fragment part of URL. + + Empty string if fragment is missing. + + """ + return UNQUOTER(self._fragment) if self._fragment else "" + + @cached_property + def raw_parts(self) -> tuple[str, ...]: + """A tuple containing encoded *path* parts. + + ('/',) for absolute URLs if *path* is missing. + + """ + path = self._path + if self._netloc: + return ("/", *path[1:].split("/")) if path else ("/",) + if path and path[0] == "/": + return ("/", *path[1:].split("/")) + return tuple(path.split("/")) + + @cached_property + def parts(self) -> tuple[str, ...]: + """A tuple containing decoded *path* parts. + + ('/',) for absolute URLs if *path* is missing. + + """ + return tuple(UNQUOTER(part) for part in self.raw_parts) + + @cached_property + def parent(self) -> "URL": + """A new URL with last part of path removed and cleaned up query and + fragment. + + """ + path = self._path + if not path or path == "/": + if self._fragment or self._query: + return from_parts(self._scheme, self._netloc, path, "", "") + return self + parts = path.split("/") + return from_parts(self._scheme, self._netloc, "/".join(parts[:-1]), "", "") + + @cached_property + def raw_name(self) -> str: + """The last part of raw_parts.""" + parts = self.raw_parts + if not self._netloc: + return parts[-1] + parts = parts[1:] + return parts[-1] if parts else "" + + @cached_property + def name(self) -> str: + """The last part of parts.""" + return UNQUOTER(self.raw_name) + + @cached_property + def raw_suffix(self) -> str: + name = self.raw_name + i = name.rfind(".") + return name[i:] if 0 < i < len(name) - 1 else "" + + @cached_property + def suffix(self) -> str: + return UNQUOTER(self.raw_suffix) + + @cached_property + def raw_suffixes(self) -> tuple[str, ...]: + name = self.raw_name + if name.endswith("."): + return () + name = name.lstrip(".") + return tuple("." + suffix for suffix in name.split(".")[1:]) + + @cached_property + def suffixes(self) -> tuple[str, ...]: + return tuple(UNQUOTER(suffix) for suffix in self.raw_suffixes) + + def _make_child(self, paths: "Sequence[str]", encoded: bool = False) -> "URL": + """ + add paths to self._path, accounting for absolute vs relative paths, + keep existing, but do not create new, empty segments + """ + parsed: list[str] = [] + needs_normalize: bool = False + for idx, path in enumerate(reversed(paths)): + # empty segment of last is not removed + last = idx == 0 + if path and path[0] == "/": + raise ValueError( + f"Appending path {path!r} starting from slash is forbidden" + ) + # We need to quote the path if it is not already encoded + # This cannot be done at the end because the existing + # path is already quoted and we do not want to double quote + # the existing path. + path = path if encoded else PATH_QUOTER(path) + needs_normalize |= "." in path + segments = path.split("/") + segments.reverse() + # remove trailing empty segment for all but the last path + parsed += segments[1:] if not last and segments[0] == "" else segments + + if (path := self._path) and (old_segments := path.split("/")): + # If the old path ends with a slash, the last segment is an empty string + # and should be removed before adding the new path segments. + old = old_segments[:-1] if old_segments[-1] == "" else old_segments + old.reverse() + parsed += old + + # If the netloc is present, inject a leading slash when adding a + # path to an absolute URL where there was none before. + if (netloc := self._netloc) and parsed and parsed[-1] != "": + parsed.append("") + + parsed.reverse() + if not netloc or not needs_normalize: + return from_parts(self._scheme, netloc, "/".join(parsed), "", "") + + path = "/".join(normalize_path_segments(parsed)) + # If normalizing the path segments removed the leading slash, add it back. + if path and path[0] != "/": + path = f"/{path}" + return from_parts(self._scheme, netloc, path, "", "") + + def with_scheme(self, scheme: str) -> "URL": + """Return a new URL with scheme replaced.""" + # N.B. doesn't cleanup query/fragment + if not isinstance(scheme, str): + raise TypeError("Invalid scheme type") + lower_scheme = scheme.lower() + netloc = self._netloc + if not netloc and lower_scheme in SCHEME_REQUIRES_HOST: + msg = ( + "scheme replacement is not allowed for " + f"relative URLs for the {lower_scheme} scheme" + ) + raise ValueError(msg) + return from_parts(lower_scheme, netloc, self._path, self._query, self._fragment) + + def with_user(self, user: str | None) -> "URL": + """Return a new URL with user replaced. + + Autoencode user if needed. + + Clear user/password if user is None. + + """ + # N.B. doesn't cleanup query/fragment + if user is None: + password = None + elif isinstance(user, str): + user = QUOTER(user) + password = self.raw_password + else: + raise TypeError("Invalid user type") + if not (netloc := self._netloc): + raise ValueError("user replacement is not allowed for relative URLs") + encoded_host = self.host_subcomponent or "" + netloc = make_netloc(user, password, encoded_host, self.explicit_port) + return from_parts(self._scheme, netloc, self._path, self._query, self._fragment) + + def with_password(self, password: str | None) -> "URL": + """Return a new URL with password replaced. + + Autoencode password if needed. + + Clear password if argument is None. + + """ + # N.B. doesn't cleanup query/fragment + if password is None: + pass + elif isinstance(password, str): + password = QUOTER(password) + else: + raise TypeError("Invalid password type") + if not (netloc := self._netloc): + raise ValueError("password replacement is not allowed for relative URLs") + encoded_host = self.host_subcomponent or "" + port = self.explicit_port + netloc = make_netloc(self.raw_user, password, encoded_host, port) + return from_parts(self._scheme, netloc, self._path, self._query, self._fragment) + + def with_host(self, host: str) -> "URL": + """Return a new URL with host replaced. + + Autoencode host if needed. + + Changing host for relative URLs is not allowed, use .join() + instead. + + """ + # N.B. doesn't cleanup query/fragment + if not isinstance(host, str): + raise TypeError("Invalid host type") + if not (netloc := self._netloc): + raise ValueError("host replacement is not allowed for relative URLs") + if not host: + raise ValueError("host removing is not allowed") + encoded_host = _encode_host(host, validate_host=True) if host else "" + port = self.explicit_port + netloc = make_netloc(self.raw_user, self.raw_password, encoded_host, port) + return from_parts(self._scheme, netloc, self._path, self._query, self._fragment) + + def with_port(self, port: int | None) -> "URL": + """Return a new URL with port replaced. + + Clear port to default if None is passed. + + """ + # N.B. doesn't cleanup query/fragment + if port is not None: + if isinstance(port, bool) or not isinstance(port, int): + raise TypeError(f"port should be int or None, got {type(port)}") + if not (0 <= port <= 65535): + raise ValueError(f"port must be between 0 and 65535, got {port}") + if not (netloc := self._netloc): + raise ValueError("port replacement is not allowed for relative URLs") + encoded_host = self.host_subcomponent or "" + netloc = make_netloc(self.raw_user, self.raw_password, encoded_host, port) + return from_parts(self._scheme, netloc, self._path, self._query, self._fragment) + + def with_path( + self, + path: str, + *, + encoded: bool = False, + keep_query: bool = False, + keep_fragment: bool = False, + ) -> "URL": + """Return a new URL with path replaced.""" + netloc = self._netloc + if not encoded: + path = PATH_QUOTER(path) + if netloc: + path = normalize_path(path) if "." in path else path + if path and path[0] != "/": + path = f"/{path}" + query = self._query if keep_query else "" + fragment = self._fragment if keep_fragment else "" + return from_parts(self._scheme, netloc, path, query, fragment) + + @overload + def with_query(self, query: Query) -> "URL": ... + + @overload + def with_query(self, **kwargs: QueryVariable) -> "URL": ... + + def with_query(self, *args: Any, **kwargs: Any) -> "URL": + """Return a new URL with query part replaced. + + Accepts any Mapping (e.g. dict, multidict.MultiDict instances) + or str, autoencode the argument if needed. + + A sequence of (key, value) pairs is supported as well. + + It also can take an arbitrary number of keyword arguments. + + Clear query if None is passed. + + """ + # N.B. doesn't cleanup query/fragment + query = get_str_query(*args, **kwargs) or "" + return from_parts_uncached( + self._scheme, self._netloc, self._path, query, self._fragment + ) + + @overload + def extend_query(self, query: Query) -> "URL": ... + + @overload + def extend_query(self, **kwargs: QueryVariable) -> "URL": ... + + def extend_query(self, *args: Any, **kwargs: Any) -> "URL": + """Return a new URL with query part combined with the existing. + + This method will not remove existing query parameters. + + Example: + >>> url = URL('http://example.com/?a=1&b=2') + >>> url.extend_query(a=3, c=4) + URL('http://example.com/?a=1&b=2&a=3&c=4') + """ + if not (new_query := get_str_query(*args, **kwargs)): + return self + if query := self._query: + # both strings are already encoded so we can use a simple + # string join + query += new_query if query[-1] == "&" else f"&{new_query}" + else: + query = new_query + return from_parts_uncached( + self._scheme, self._netloc, self._path, query, self._fragment + ) + + @overload + def update_query(self, query: Query) -> "URL": ... + + @overload + def update_query(self, **kwargs: QueryVariable) -> "URL": ... + + def update_query(self, *args: Any, **kwargs: Any) -> "URL": + """Return a new URL with query part updated. + + This method will overwrite existing query parameters. + + Example: + >>> url = URL('http://example.com/?a=1&b=2') + >>> url.update_query(a=3, c=4) + URL('http://example.com/?a=3&b=2&c=4') + """ + in_query: ( + str + | Mapping[str, QueryVariable] + | Sequence[tuple[str | istr, SimpleQuery]] + | None + ) + if kwargs: + if args: + msg = "Either kwargs or single query parameter must be present" + raise ValueError(msg) + in_query = kwargs + elif len(args) == 1: + in_query = args[0] + else: + raise ValueError("Either kwargs or single query parameter must be present") + + if in_query is None: + query = "" + elif not in_query: + query = self._query + elif isinstance(in_query, Mapping): + qm: MultiDict[QueryVariable] = MultiDict(self._parsed_query) + qm.update(in_query) + query = get_str_query_from_sequence_iterable(qm.items()) + elif isinstance(in_query, str): + qstr: MultiDict[str] = MultiDict(self._parsed_query) + qstr.update(query_to_pairs(in_query)) + query = get_str_query_from_iterable(qstr.items()) + elif isinstance(in_query, (bytes, bytearray, memoryview)): + msg = "Invalid query type: bytes, bytearray and memoryview are forbidden" + raise TypeError(msg) + elif isinstance(in_query, Sequence): + # We don't expect sequence values if we're given a list of pairs + # already; only mappings like builtin `dict` which can't have the + # same key pointing to multiple values are allowed to use + # `_query_seq_pairs`. + if TYPE_CHECKING: + in_query = cast( + Sequence[tuple[Union[str, istr], SimpleQuery]], in_query + ) + qs: MultiDict[SimpleQuery] = MultiDict(self._parsed_query) + qs.update(in_query) + query = get_str_query_from_iterable(qs.items()) + else: + raise TypeError( + "Invalid query type: only str, mapping or " + "sequence of (key, value) pairs is allowed" + ) + return from_parts_uncached( + self._scheme, self._netloc, self._path, query, self._fragment + ) + + def without_query_params(self, *query_params: str) -> "URL": + """Remove some keys from query part and return new URL.""" + params_to_remove = set(query_params) & self.query.keys() + if not params_to_remove: + return self + return self.with_query( + tuple( + (name, value) + for name, value in self.query.items() + if name not in params_to_remove + ) + ) + + def with_fragment(self, fragment: str | None) -> "URL": + """Return a new URL with fragment replaced. + + Autoencode fragment if needed. + + Clear fragment to default if None is passed. + + """ + # N.B. doesn't cleanup query/fragment + if fragment is None: + raw_fragment = "" + elif not isinstance(fragment, str): + raise TypeError("Invalid fragment type") + else: + raw_fragment = FRAGMENT_QUOTER(fragment) + if self._fragment == raw_fragment: + return self + return from_parts( + self._scheme, self._netloc, self._path, self._query, raw_fragment + ) + + def with_name( + self, + name: str, + *, + keep_query: bool = False, + keep_fragment: bool = False, + ) -> "URL": + """Return a new URL with name (last part of path) replaced. + + Query and fragment parts are cleaned up. + + Name is encoded if needed. + + """ + # N.B. DOES cleanup query/fragment + if not isinstance(name, str): + raise TypeError("Invalid name type") + if "/" in name: + raise ValueError("Slash in name is not allowed") + name = PATH_QUOTER(name) + if name in (".", ".."): + raise ValueError(". and .. values are forbidden") + parts = list(self.raw_parts) + if netloc := self._netloc: + if len(parts) == 1: + parts.append(name) + else: + parts[-1] = name + parts[0] = "" # replace leading '/' + else: + parts[-1] = name + if parts[0] == "/": + parts[0] = "" # replace leading '/' + + query = self._query if keep_query else "" + fragment = self._fragment if keep_fragment else "" + return from_parts(self._scheme, netloc, "/".join(parts), query, fragment) + + def with_suffix( + self, + suffix: str, + *, + keep_query: bool = False, + keep_fragment: bool = False, + ) -> "URL": + """Return a new URL with suffix (file extension of name) replaced. + + Query and fragment parts are cleaned up. + + suffix is encoded if needed. + """ + if not isinstance(suffix, str): + raise TypeError("Invalid suffix type") + if suffix and not suffix[0] == "." or suffix == "." or "/" in suffix: + raise ValueError(f"Invalid suffix {suffix!r}") + name = self.raw_name + if not name: + raise ValueError(f"{self!r} has an empty name") + old_suffix = self.raw_suffix + suffix = PATH_QUOTER(suffix) + name = name + suffix if not old_suffix else name[: -len(old_suffix)] + suffix + if name in (".", ".."): + raise ValueError(". and .. values are forbidden") + parts = list(self.raw_parts) + if netloc := self._netloc: + if len(parts) == 1: + parts.append(name) + else: + parts[-1] = name + parts[0] = "" # replace leading '/' + else: + parts[-1] = name + if parts[0] == "/": + parts[0] = "" # replace leading '/' + + query = self._query if keep_query else "" + fragment = self._fragment if keep_fragment else "" + return from_parts(self._scheme, netloc, "/".join(parts), query, fragment) + + def join(self, url: "URL") -> "URL": + """Join URLs + + Construct a full (“absolute”) URL by combining a “base URL” + (self) with another URL (url). + + Informally, this uses components of the base URL, in + particular the addressing scheme, the network location and + (part of) the path, to provide missing components in the + relative URL. + + """ + if type(url) is not URL: + raise TypeError("url should be URL") + + scheme = url._scheme or self._scheme + if scheme != self._scheme or scheme not in USES_RELATIVE: + return url + + # scheme is in uses_authority as uses_authority is a superset of uses_relative + if (join_netloc := url._netloc) and scheme in USES_AUTHORITY: + return from_parts(scheme, join_netloc, url._path, url._query, url._fragment) + + orig_path = self._path + if join_path := url._path: + if join_path[0] == "/": + path = join_path + elif not orig_path: + path = f"/{join_path}" + elif orig_path[-1] == "/": + path = f"{orig_path}{join_path}" + else: + # … + # and relativizing ".." + # parts[0] is / for absolute urls, + # this join will add a double slash there + path = "/".join([*self.parts[:-1], ""]) + join_path + # which has to be removed + if orig_path[0] == "/": + path = path[1:] + path = normalize_path(path) if "." in path else path + else: + path = orig_path + + return from_parts( + scheme, + self._netloc, + path, + url._query if join_path or url._query else self._query, + url._fragment if join_path or url._fragment else self._fragment, + ) + + def joinpath(self, *other: str, encoded: bool = False) -> "URL": + """Return a new URL with the elements in other appended to the path.""" + return self._make_child(other, encoded=encoded) + + def human_repr(self) -> str: + """Return decoded human readable string for URL representation.""" + user = human_quote(self.user, "#/:?@[]") + password = human_quote(self.password, "#/:?@[]") + if (host := self.host) and ":" in host: + host = f"[{host}]" + path = human_quote(self.path, "#?") + if TYPE_CHECKING: + assert path is not None + query_string = "&".join( + "{}={}".format(human_quote(k, "#&+;="), human_quote(v, "#&+;=")) + for k, v in self.query.items() + ) + fragment = human_quote(self.fragment, "") + if TYPE_CHECKING: + assert fragment is not None + netloc = make_netloc(user, password, host, self.explicit_port) + return unsplit_result(self._scheme, netloc, path, query_string, fragment) + + if HAS_PYDANTIC: # pragma: no cover + # Borrowed from https://docs.pydantic.dev/latest/concepts/types/#handling-third-party-types + @classmethod + def __get_pydantic_json_schema__( + cls, core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler + ) -> JsonSchemaValue: + field_schema: dict[str, Any] = {} + field_schema.update(type="string", format="uri") + return field_schema + + @classmethod + def __get_pydantic_core_schema__( + cls, source_type: type[Self] | type[str], handler: GetCoreSchemaHandler + ) -> core_schema.CoreSchema: + from_str_schema = core_schema.chain_schema( + [ + core_schema.str_schema(), + core_schema.no_info_plain_validator_function(URL), + ] + ) + + return core_schema.json_or_python_schema( + json_schema=from_str_schema, + python_schema=core_schema.union_schema( + [ + # check if it's an instance first before doing any further work + core_schema.is_instance_schema(URL), + from_str_schema, + ] + ), + serialization=core_schema.plain_serializer_function_ser_schema(str), + ) + + +_DEFAULT_IDNA_SIZE = 256 +_DEFAULT_ENCODE_SIZE = 512 + + +@lru_cache(_DEFAULT_IDNA_SIZE) +def _idna_decode(raw: str) -> str: + try: + return idna.decode(raw.encode("ascii")) + except UnicodeError: # e.g. '::1' + return raw.encode("ascii").decode("idna") + + +@lru_cache(_DEFAULT_IDNA_SIZE) +def _idna_encode(host: str) -> str: + try: + return idna.encode(host, uts46=True).decode("ascii") + except UnicodeError: + return host.encode("idna").decode("ascii") + + +@lru_cache(_DEFAULT_ENCODE_SIZE) +def _encode_host(host: str, validate_host: bool) -> str: + """Encode host part of URL.""" + # If the host ends with a digit or contains a colon, its likely + # an IP address. + if host and (host[-1].isdigit() or ":" in host): + raw_ip, sep, zone = host.partition("%") + # If it looks like an IP, we check with _ip_compressed_version + # and fall-through if its not an IP address. This is a performance + # optimization to avoid parsing IP addresses as much as possible + # because it is orders of magnitude slower than almost any other + # operation this library does. + # Might be an IP address, check it + # + # IP Addresses can look like: + # https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2 + # - 127.0.0.1 (last character is a digit) + # - 2001:db8::ff00:42:8329 (contains a colon) + # - 2001:db8::ff00:42:8329%eth0 (contains a colon) + # - [2001:db8::ff00:42:8329] (contains a colon -- brackets should + # have been removed before it gets here) + # Rare IP Address formats are not supported per: + # https://datatracker.ietf.org/doc/html/rfc3986#section-7.4 + # + # IP parsing is slow, so its wrapped in an LRU + try: + ip = ip_address(raw_ip) + except ValueError: + pass + else: + # These checks should not happen in the + # LRU to keep the cache size small + host = ip.compressed + if ip.version == 6: + return f"[{host}%{zone}]" if sep else f"[{host}]" + return f"{host}%{zone}" if sep else host + + # IDNA encoding is slow, skip it for ASCII-only strings + if host.isascii(): + # Check for invalid characters explicitly; _idna_encode() does this + # for non-ascii host names. + host = host.lower() + if validate_host and (invalid := NOT_REG_NAME.search(host)): + value, pos, extra = invalid.group(), invalid.start(), "" + if value == "@" or (value == ":" and "@" in host[pos:]): + # this looks like an authority string + extra = ( + ", if the value includes a username or password, " + "use 'authority' instead of 'host'" + ) + raise ValueError( + f"Host {host!r} cannot contain {value!r} (at position {pos}){extra}" + ) from None + return host + + return _idna_encode(host) + + +@rewrite_module +def cache_clear() -> None: + """Clear all LRU caches.""" + _idna_encode.cache_clear() + _idna_decode.cache_clear() + _encode_host.cache_clear() + + +@rewrite_module +def cache_info() -> CacheInfo: + """Report cache statistics.""" + return { + "idna_encode": _idna_encode.cache_info(), + "idna_decode": _idna_decode.cache_info(), + "ip_address": _encode_host.cache_info(), + "host_validate": _encode_host.cache_info(), + "encode_host": _encode_host.cache_info(), + } + + +@rewrite_module +def cache_configure( + *, + idna_encode_size: int | None = _DEFAULT_IDNA_SIZE, + idna_decode_size: int | None = _DEFAULT_IDNA_SIZE, + ip_address_size: int | None | UndefinedType = UNDEFINED, + host_validate_size: int | None | UndefinedType = UNDEFINED, + encode_host_size: int | None | UndefinedType = UNDEFINED, +) -> None: + """Configure LRU cache sizes.""" + global _idna_decode, _idna_encode, _encode_host + # ip_address_size, host_validate_size are no longer + # used, but are kept for backwards compatibility. + if ip_address_size is not UNDEFINED or host_validate_size is not UNDEFINED: + warnings.warn( + "cache_configure() no longer accepts the " + "ip_address_size or host_validate_size arguments, " + "they are used to set the encode_host_size instead " + "and will be removed in the future", + DeprecationWarning, + stacklevel=2, + ) + + if encode_host_size is not None: + for size in (ip_address_size, host_validate_size): + if size is None: + encode_host_size = None + elif encode_host_size is UNDEFINED: + if size is not UNDEFINED: + encode_host_size = size + elif size is not UNDEFINED: + if TYPE_CHECKING: + assert isinstance(size, int) + assert isinstance(encode_host_size, int) + encode_host_size = max(size, encode_host_size) + if encode_host_size is UNDEFINED: + encode_host_size = _DEFAULT_ENCODE_SIZE + + _encode_host = lru_cache(encode_host_size)(_encode_host.__wrapped__) + _idna_decode = lru_cache(idna_decode_size)(_idna_decode.__wrapped__) + _idna_encode = lru_cache(idna_encode_size)(_idna_encode.__wrapped__) diff --git a/bioagent-bench/.venv/lib/python3.14/site-packages/yarl/py.typed b/bioagent-bench/.venv/lib/python3.14/site-packages/yarl/py.typed new file mode 100644 index 0000000000000000000000000000000000000000..dcf2c804da5e19d617a03a6c68aa128d1d1f89a0 --- /dev/null +++ b/bioagent-bench/.venv/lib/python3.14/site-packages/yarl/py.typed @@ -0,0 +1 @@ +# Placeholder diff --git a/bioagent-bench/__pycache__/download_dataset.cpython-311.pyc b/bioagent-bench/__pycache__/download_dataset.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2e4e8ee07b7a7dc144f1330360ba855f636050c8 Binary files /dev/null and b/bioagent-bench/__pycache__/download_dataset.cpython-311.pyc differ diff --git a/bioagent-bench/__pycache__/download_dataset.cpython-313.pyc b/bioagent-bench/__pycache__/download_dataset.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5f9d531877f12e7e6eced454a2a16e2b9d51a471 Binary files /dev/null and b/bioagent-bench/__pycache__/download_dataset.cpython-313.pyc differ diff --git a/bioagent-bench/src/dataset.py b/bioagent-bench/src/dataset.py new file mode 100644 index 0000000000000000000000000000000000000000..948c535c2e0cef684098ebaa3dea25f5ee5e786d --- /dev/null +++ b/bioagent-bench/src/dataset.py @@ -0,0 +1,328 @@ +import json +import tarfile +from pathlib import Path +from urllib.parse import urlparse +import requests +from typing import List, Union, Dict, Optional +import click + + +class BioAgentDataset: + """Download and manage bioinformatics benchmark datasets.""" + + def __init__(self, metadata_path: str = "src/task_metadata.json", base_dir: Union[str, Path] = "tasks"): + """Initialize with path to task metadata JSON file.""" + self.metadata_path = Path(metadata_path) + self.tasks = self._load_metadata() + self.base_dir = Path(base_dir) + + def _load_metadata(self) -> List[dict]: + """Load task metadata from JSON file.""" + with open(self.metadata_path, 'r') as f: + return json.load(f) + + def _get_task(self, task_id: str) -> dict: + """Get task metadata by task_id.""" + for task in self.tasks: + if task["task_id"] == task_id: + return task + raise ValueError(f"Task '{task_id}' not found") + + def _download_file(self, url: str, output_path: Path) -> bool: + """Download a single file from URL to output_path.""" + if not url or url.strip() == "": + return False + + if output_path.exists(): + print(f"File already exists: {output_path}") + return True + + print(f"Downloading {url} to {output_path}") + output_path.parent.mkdir(parents=True, exist_ok=True) + + try: + response = requests.get(url, stream=True) + response.raise_for_status() + + total_size_str = response.headers.get('Content-Length') + total_size = int(total_size_str) if total_size_str and total_size_str.isdigit() else 0 + + with open(output_path, 'wb') as f: + if total_size > 0: + label = f"Downloading {output_path.name}" + with click.progressbar(length=total_size, label=label, show_eta=True, show_percent=True) as bar: + for chunk in response.iter_content(chunk_size=8192): + if not chunk: + continue + f.write(chunk) + bar.update(len(chunk)) + else: + for chunk in response.iter_content(chunk_size=8192): + if not chunk: + continue + f.write(chunk) + + # Auto-extract tar archives if detected (handles .tar.gz, .tgz, .tar) + if tarfile.is_tarfile(output_path): + special_prefixes = ( + "kaiju_db_viruses_2024-08-15", + "k2_standard_16gb_20241228", + ) + subdir_name = next( + ( + prefix + for prefix in special_prefixes + if output_path.name.startswith(prefix) + ), + None, + ) + self._extract_tarfile(output_path, subdir_name=subdir_name) + + return True + + except Exception as e: + print(f"Error downloading {url}: {e}") + if output_path.exists(): + output_path.unlink() + return False + + def _extract_tarfile(self, tar_path: Path, subdir_name: Optional[str] = None) -> None: + """Extract tar archive contents directly and delete the archive.""" + output_dir = tar_path.parent + if subdir_name: + output_dir = output_dir / subdir_name + + output_dir.mkdir(parents=True, exist_ok=True) + + print(f"Extracting {tar_path} to {output_dir}") + + try: + with tarfile.open(tar_path, 'r:*') as tar: + for member in tar.getmembers(): + if member.isfile(): + member.name = Path(member.name).name + tar.extract(member, path=output_dir) + + tar_path.unlink() + print(f"Removed archive: {tar_path}") + print(f"Successfully extracted {tar_path} to {output_dir}") + + except Exception as e: + print(f"Error extracting {tar_path}: {e}") + + def _download_urls(self, urls: List[Union[str, Dict[str, str]]], target_dir: Path, category: str) -> bool: + """Download multiple URLs to target directory. + + Supports plain URL strings or objects with keys {"filename", "url"}. + """ + if not urls: + print(f"No {category} URLs to download") + return True + + success = True + for i, entry in enumerate(urls): + url: str = "" + filename: str = "" + + if isinstance(entry, str): + url = entry.strip() + if not url: + continue + parsed_url = urlparse(url) + filename = Path(parsed_url.path).name + elif isinstance(entry, dict): + url = (entry.get("url") or "").strip() + filename = (entry.get("filename") or "").strip() + if not filename and url: + parsed_url = urlparse(url) + filename = Path(parsed_url.path).name + else: + continue + + if not filename: + filename = f"{category}_{i}.tar.gz" + + if not url: + continue + + output_path = target_dir / filename + if not self._download_file(url, output_path): + success = False + + return success + + def download_data(self, task_id: str) -> bool: + """Download data files for a specific task.""" + task = self._get_task(task_id) + data_urls = task["download_urls"]["data"] + target_dir = self.base_dir / task_id / "data" + + print(f"Downloading data for task: {task_id}") + return self._download_urls(data_urls, target_dir, "data") + + def download_reference_data(self, task_id: str) -> bool: + """Download reference data files for a specific task.""" + task = self._get_task(task_id) + ref_urls = task["download_urls"]["reference_data"] + target_dir = self.base_dir / task_id / "reference" + + print(f"Downloading reference data for task: {task_id}") + return self._download_urls(ref_urls, target_dir, "reference") + + def download_results(self, task_id: str) -> bool: + """Download result files for a specific task (for evaluation).""" + task = self._get_task(task_id) + result_urls = task["download_urls"]["results"] + target_dir = self.base_dir / task_id / "results" + + print(f"Downloading results for task: {task_id}") + return self._download_urls(result_urls, target_dir, "results") + + def download_task(self, task_id: str, include_reference: bool = False) -> bool: + """Download all files for a task with optional reference data and results.""" + print(f"Downloading task: {task_id}") + + success = True + + if not self.download_data(task_id): + success = False + + if include_reference: + if not self.download_reference_data(task_id): + success = False + + return success + + def download_all_tasks(self, include_reference: bool = False) -> bool: + """Download all tasks with optional reference data and results.""" + success = True + + for task in self.tasks: + task_id = task["task_id"] + if not self.download_task(task_id, include_reference): + success = False + return success + + def list_tasks(self) -> List[str]: + """List all available task IDs.""" + return [task["task_id"] for task in self.tasks] + + def download_all_results(self) -> bool: + """Download results for all tasks.""" + success = True + for task in self.tasks: + task_id = task["task_id"] + if not self.download_results(task_id): + success = False + return success + + +# ----------------------------- +# CLI +# ----------------------------- + +@click.group() +@click.option( + "--metadata", + default="src/task_metadata.json", + show_default=True, + help="Path to task metadata JSON file.", +) +@click.option( + "--dest", + default="tasks", + show_default=True, + help="Base output directory where task folders will be created.", +) +@click.pass_context +def cli(ctx: click.Context, metadata: str, dest: str) -> None: + """BioAgent dataset manager.""" + ctx.obj = BioAgentDataset(metadata_path=metadata, base_dir=dest) + + +@cli.command("list-tasks") +@click.pass_obj +def cli_list_tasks(dataset: BioAgentDataset) -> None: + """List available task IDs.""" + for task_id in dataset.list_tasks(): + click.echo(task_id) + + +@cli.command("download") +@click.option("--task", "task_ids", multiple=True, help="Task ID(s) to download.") +@click.option("--all", "download_all", is_flag=True, help="Operate on all tasks.") +@click.option("--data/--no-data", default=True, show_default=True, help="Download data files.") +@click.option( + "--reference/--no-reference", + default=False, + show_default=True, + help="Include reference data.", +) +@click.option( + "--results/--no-results", + default=False, + show_default=True, + help="Include results files.", +) +@click.option( + "--dest", + default=None, + help="Base output directory where task folders will be created.", +) +@click.pass_obj +def cli_download( + dataset: BioAgentDataset, + task_ids: List[str], + download_all: bool, + data: bool, + reference: bool, + results: bool, + dest: Optional[str], +) -> None: + """Download datasets for tasks (data, reference, results).""" + if dest: + dataset.base_dir = Path(dest) + if not download_all and not task_ids: + click.echo("Specify at least one --task or use --all", err=True) + raise SystemExit(1) + + # If only results are requested, optimize the flow + if results and not data and not reference: + if download_all: + ok = dataset.download_all_results() + raise SystemExit(0 if ok else 1) + else: + ok = True + for tid in task_ids: + ok = dataset.download_results(tid) and ok + raise SystemExit(0 if ok else 1) + + target_ids = dataset.list_tasks() if download_all else list(task_ids) + ok = True + for tid in target_ids: + if data: + ok = dataset.download_task(tid, include_reference=reference) and ok + elif reference: + ok = dataset.download_reference_data(tid) and ok + if results: + ok = dataset.download_results(tid) and ok + raise SystemExit(0 if ok else 1) + + +@cli.command("download-all-results") +@click.option( + "--dest", + default=None, + help="Base output directory where task folders will be created.", +) +@click.pass_obj +def cli_download_all_results(dataset: BioAgentDataset, dest: Optional[str]) -> None: + """Download results for all tasks.""" + if dest: + dataset.base_dir = Path(dest) + ok = dataset.download_all_results() + raise SystemExit(0 if ok else 1) + + +if __name__ == "__main__": + cli() \ No newline at end of file diff --git a/bioagent-bench/src/publish_hf_datasets.py b/bioagent-bench/src/publish_hf_datasets.py new file mode 100644 index 0000000000000000000000000000000000000000..0b3e542d94ca424c570f3fd455f385ab3c19088e --- /dev/null +++ b/bioagent-bench/src/publish_hf_datasets.py @@ -0,0 +1,72 @@ +import os +from pathlib import Path +from typing import List + +import click +from datasets import load_from_disk, Dataset +from huggingface_hub import HfApi + + +def find_dataset_dirs(root: Path) -> List[Path]: + if not root.exists(): + return [] + candidates: List[Path] = [] + for info_file in root.rglob("dataset_info.json"): + ds_dir = info_file.parent + if (ds_dir / "state.json").exists(): + candidates.append(ds_dir) + return sorted({p.resolve() for p in candidates}) + + +def get_published_dataset_name(rel: Path) -> str: + rel_str = rel.as_posix() + rel_parts = rel_str.split("__") + task_name = rel_parts[0] + dataset_name = rel_parts[-1] + + return f"{task_name}.{dataset_name}" + + +@click.command(context_settings={"help_option_names": ["-h", "--help"]}) +@click.option( + "--out-dir", + type=click.Path(path_type=Path), + default=Path("data") / "hf", + show_default=True, + help="Directory containing saved Hugging Face datasets (produced by the preprocessing script).", +) +@click.option( + "--namespace", + type=str, + required=True, + help="Hugging Face user or organization to publish under (e.g., 'your-username').", +) +def main( + out_dir: Path, + namespace: str, +) -> None: + out_dir = out_dir.resolve() + api = HfApi() + + ds_dirs = find_dataset_dirs(out_dir) + if not ds_dirs: + click.echo(f"No datasets found under: {out_dir}") + raise SystemExit(0) + + for idx, ds_path in enumerate(ds_dirs, start=1): + rel = ds_path.relative_to(out_dir) + published_dataset_name = get_published_dataset_name(rel) + repo_id = f"{namespace}/{published_dataset_name}" + click.echo(f"[{idx}/{len(ds_dirs)}] Publishing {published_dataset_name}") + + api.create_repo( + repo_id=repo_id, repo_type="dataset", private=False, exist_ok=True + ) + ds = load_from_disk(str(ds_path)) + ds.push_to_hub( + repo_id=repo_id, commit_message=f"Add {published_dataset_name} dataset" + ) + + +if __name__ == "__main__": + main() diff --git a/bioagent-bench/src/run_huggingface_preprocessing.py b/bioagent-bench/src/run_huggingface_preprocessing.py new file mode 100644 index 0000000000000000000000000000000000000000..8da8d8c3edca4d8708cd1fdf38f07318953c0adb --- /dev/null +++ b/bioagent-bench/src/run_huggingface_preprocessing.py @@ -0,0 +1,532 @@ +import hashlib +import mimetypes +import os +import gzip +from dataclasses import dataclass +from pathlib import Path +from typing import Iterable, Iterator, List, Optional, Tuple + +import click +import pandas as pd +from datasets import Dataset + + +TABULAR_EXTENSIONS = {".csv", ".tsv", ".txt"} +DEFAULT_ROOT = Path("data").resolve() +DEFAULT_OUT = DEFAULT_ROOT / "hf" + + +@dataclass +class FileRecord: + task_id: str + relative_path: str + absolute_path: str + extension: str + size_bytes: int + is_gz: bool + mime_type: Optional[str] + sha256: Optional[str] + + +def iter_task_dirs(root_dir: Path) -> Iterable[Tuple[str, Path]]: + for child in sorted(root_dir.iterdir()): + if child.is_dir(): + yield child.name, child + + +def detect_delimiter(sample: str) -> Optional[str]: + if "\t" in sample and "," in sample: + return "\t" + if "\t" in sample: + return "\t" + if "," in sample: + return "," + if " " in sample: + return r"\s+" + return None + + +def is_tabular_file(path: Path) -> bool: + ext = path.suffix.lower() + if ext not in {'.csv', '.tsv'}: + return False + return True + +def is_fastq_file(path: Path) -> bool: + name = path.name.lower() + if name.endswith(".fastq") or name.endswith(".fq") or name.endswith(".fastq.gz") or name.endswith(".fq.gz"): + return True + return False + +def is_fasta_file(path: Path) -> bool: + name = path.name.lower() + if ( + name.endswith(".fa") + or name.endswith(".fasta") + or name.endswith(".fna") + or name.endswith(".fa.gz") + or name.endswith(".fasta.gz") + or name.endswith(".fna.gz") + ): + return True + return False + +def is_vcf_file(path: Path) -> bool: + name = path.name.lower() + if name.endswith(".vcf") or name.endswith(".vcf.gz") or name.endswith(".eff.vcf") or name.endswith(".eff.vcf.gz"): + return True + return False + +def compute_sha256(path: Path) -> str: + h = hashlib.sha256() + with open(path, "rb") as f: + while True: + chunk = f.read(1024 * 1024) + if not chunk: + break + h.update(chunk) + return h.hexdigest() + + +def infer_source_format(path: Path) -> str: + """Infer original file format for metadata.""" + name = path.name.lower() + if is_fastq_file(path): + return "fastq" + if is_fasta_file(path): + # Distinguish fna vs fasta vs fa for transparency + if name.endswith(".fna") or name.endswith(".fna.gz"): + return "fna" + if name.endswith(".fa") or name.endswith(".fa.gz"): + return "fa" + return "fasta" + if is_vcf_file(path): + if name.endswith(".eff.vcf") or name.endswith(".eff.vcf.gz"): + return "eff.vcf" + return "vcf" + if name.endswith(".tsv"): + return "tsv" + if name.endswith(".csv"): + return "csv" + return (path.suffix or "").lstrip(".") + + +def build_file_index( + task_id: str, task_dir: Path, compute_checksums: bool, checksum_bytes: Optional[int] +) -> Dataset: + records: List[FileRecord] = [] + for file_path in sorted(task_dir.rglob("*")): + if not file_path.is_file(): + continue + rel = str(file_path.relative_to(DEFAULT_ROOT)) + abs_path = str(file_path.resolve()) + ext = file_path.suffix.lower() + try: + size = file_path.stat().st_size + except FileNotFoundError: + size = 0 + is_gz = abs_path.endswith(".gz") + mime, _ = mimetypes.guess_type(abs_path) + sha = compute_sha256(file_path, max_bytes=checksum_bytes) if compute_checksums else None + records.append( + FileRecord( + task_id=task_id, + relative_path=rel, + absolute_path=abs_path, + extension=ext, + size_bytes=size, + is_gz=is_gz, + mime_type=mime, + sha256=sha, + ) + ) + df = pd.DataFrame([r.__dict__ for r in records]) + return Dataset.from_pandas(df, preserve_index=False) + + +def sanitize_for_dir(name: str) -> str: + name = name.replace(os.sep, "__").replace("/", "__") + return "".join(ch if (ch.isalnum() or ch in ("_", "-", ".")) else "_" for ch in name) + + +def fastq_reader(path: str, max_reads: Optional[int], source_sha256: str) -> Iterator[dict]: + is_gz = path.endswith(".gz") + opener = gzip.open if is_gz else open + emitted = 0 + with opener(path, "rt", encoding="utf-8", errors="ignore") as fh: + src_format = "fastq" + while True: + header = fh.readline() + if not header: + break + seq = fh.readline() + plus = fh.readline() + qual = fh.readline() + if not seq or not plus or not qual: + break + hdr = header.strip() + read_id = hdr[1:].split()[0] if hdr.startswith("@") and len(hdr) > 1 else hdr + yield { + "read_id": read_id, + "sequence": seq.strip(), + "quality": qual.strip(), + "source_file": os.path.basename(path), + "source_sha256": source_sha256, + "source_format": src_format, + } + emitted += 1 + if max_reads is not None and max_reads > 0 and emitted >= max_reads: + break + + +def fasta_reader(path: str, max_seqs: Optional[int], source_sha256: str) -> Iterator[dict]: + is_gz = path.endswith(".gz") + opener = gzip.open if is_gz else open + emitted = 0 + seq_id: Optional[str] = None + desc: str = "" + seq_chunks: List[str] = [] + def emit_current(): + if seq_id is None: + return None + sequence = "".join(seq_chunks) + return { + "seq_id": seq_id, + "description": desc, + "sequence": sequence, + "source_file": os.path.basename(path), + "source_sha256": source_sha256, + "source_format": infer_source_format(Path(path)), + } + with opener(path, "rt", encoding="utf-8", errors="ignore") as fh: + for line in fh: + if not line: + continue + if line.startswith(">"): + # Emit previous + record = emit_current() + if record: + yield record + emitted += 1 + if max_seqs is not None and max_seqs > 0 and emitted >= max_seqs: + return + # Start new + header = line[1:].rstrip("\n") + parts = header.split(None, 1) + seq_id = parts[0] if parts else "" + desc = parts[1] if len(parts) > 1 else "" + seq_chunks = [] + else: + seq_chunks.append(line.strip()) + # Emit last + record = emit_current() + if record: + yield record + + +def vcf_reader(path: str, source_sha256: str) -> Iterator[dict]: + is_gz = path.endswith(".gz") + opener = gzip.open if is_gz else open + with opener(path, "rt", encoding="utf-8", errors="ignore") as fh: + header_cols: Optional[List[str]] = None + src_format = "eff.vcf" if (path.endswith(".eff.vcf") or path.endswith(".eff.vcf.gz")) else "vcf" + for raw in fh: + if not raw: + continue + if raw.startswith("##"): + continue + if raw.startswith("#CHROM"): + header_cols = raw.lstrip("#").strip().split("\t") + continue + if raw.startswith("#"): + continue + if header_cols is None: + # Fallback to standard 8+ columns if header missing + header_cols = ["CHROM","POS","ID","REF","ALT","QUAL","FILTER","INFO","FORMAT"] + fields = raw.rstrip("\n").split("\t") + # Pad fields if fewer than header + if len(fields) < len(header_cols): + fields = fields + [""] * (len(header_cols) - len(fields)) + # Map standard columns + chrom = fields[0] if len(fields) > 0 else "" + pos = fields[1] if len(fields) > 1 else "" + vid = fields[2] if len(fields) > 2 else "" + ref = fields[3] if len(fields) > 3 else "" + alt = fields[4] if len(fields) > 4 else "" + qual = fields[5] if len(fields) > 5 else "" + flt = fields[6] if len(fields) > 6 else "" + info = fields[7] if len(fields) > 7 else "" + fmt = fields[8] if len(fields) > 8 else "" + samples = fields[9:] if len(fields) > 9 else [] + yield { + "chrom": chrom, + "pos": int(pos) if pos.isdigit() else None, + "id": vid, + "ref": ref, + "alt": alt, + "qual": qual, + "filter": flt, + "info": info, + "format": fmt, + "samples": samples, + "source_file": os.path.basename(path), + "source_sha256": source_sha256, + "source_format": src_format, + } + + +def read_tabular(path: Path, max_rows: Optional[int]) -> pd.DataFrame: + ext = path.suffix.lower() + sep: Optional[str] = None + try: + with open(path, "r", encoding="utf-8", errors="ignore") as fh: + head = "".join([fh.readline() for _ in range(3)]) + except Exception: + head = "" + if ext == ".tsv": + sep = "\t" + elif ext == ".csv": + sep = "," + else: + sep = detect_delimiter(head) or "," + nrows = max_rows if max_rows and max_rows > 0 else None + df = pd.read_csv( + path, + sep=sep, + engine="python", + on_bad_lines="skip", + nrows=nrows, + ) + return df + + +def save_dataset(ds: Dataset, out_dir: Path, overwrite: bool) -> None: + if out_dir.exists(): + if not overwrite: + return + for p in out_dir.iterdir(): + if p.is_file(): + p.unlink() + elif p.is_dir(): + for sub in p.rglob("*"): + if sub.is_file(): + sub.unlink() + try: + p.rmdir() + except OSError: + pass + out_dir.mkdir(parents=True, exist_ok=True) + ds.save_to_disk(str(out_dir)) + + +@click.command(context_settings={"help_option_names": ["-h", "--help"]}) +@click.option( + "--root-dir", + type=click.Path(path_type=Path), + default=str(DEFAULT_ROOT), + show_default=True, + help="Root directory containing dataset subfolders.", +) +@click.option( + "--out-dir", + type=click.Path(path_type=Path), + default=str(DEFAULT_OUT), + show_default=True, + help="Directory to write Hugging Face datasets.", +) + +@click.option( + "--max-rows", + type=int, + default=0, + show_default=True, + help="Limit rows when converting tabular files (0 means all rows).", +) +@click.option( + "--overwrite/--no-overwrite", + default=True, + show_default=True, + help="Overwrite existing converted datasets.", +) +@click.option( + "--flat-output/--nested-output", + default=True, + show_default=True, + help="Save each dataset as its own sibling directory directly under out-dir.", +) +@click.option( + "--max-fastq-reads", + type=int, + default=0, + show_default=True, + help="Limit number of reads per FASTQ file (0 means all reads).", +) +@click.option( + "--max-fasta-seqs", + type=int, + default=0, + show_default=True, + help="Limit number of sequences per FASTA file (0 means all sequences).", +) +def main( + root_dir: Path, + out_dir: Path, + max_rows: int, + overwrite: bool, + flat_output: bool, + max_fastq_reads: int, + max_fasta_seqs: int, +) -> None: + root_dir = root_dir.resolve() + out_dir = out_dir.resolve() + if not root_dir.exists(): + raise SystemExit(f"Root directory not found: {root_dir}") + + out_dir.mkdir(parents=True, exist_ok=True) + + for task_id, task_path in iter_task_dirs(root_dir): + try: + if task_path.resolve() == out_dir: + continue + except Exception: + pass + click.echo(f"[{task_id}] Processing directory: {task_path}") + + + tables_out_base = out_dir / task_id / "tables" + fastq_out_base = out_dir / task_id / "fastq" + fasta_out_base = out_dir / task_id / "fasta" + vcf_out_base = out_dir / task_id / "vcf" + converted_tables = 0 + converted_fastq = 0 + converted_fasta = 0 + converted_vcf = 0 + for file_path in sorted(task_path.rglob("*")): + if not file_path.is_file(): + continue + # Tabular conversion + if is_tabular_file(file_path): + try: + sha = compute_sha256(file_path) + src_format = infer_source_format(file_path) + df = read_tabular(file_path, max_rows=max_rows if max_rows > 0 else None) + if df.empty: + continue + # Add provenance columns + df.insert(0, "source_format", src_format) + df.insert(0, "source_file", file_path.name) + df.insert(0, "source_sha256", sha) + ds = Dataset.from_pandas(df, preserve_index=False) + rel_to_task = file_path.relative_to(task_path) + if flat_output: + rel_str = str(rel_to_task) + if rel_str.endswith(".gz"): + rel_str = rel_str[:-3] + rel_no_ext = str(Path(rel_str).with_suffix("")) + flat_name = f"{task_id}__tables__{sanitize_for_dir(rel_no_ext)}" + out_subdir = out_dir / flat_name + else: + stem = file_path.stem + subdir_parts = list(rel_to_task.parts[:-1]) # parent folders under task + safe_subdir = Path(*subdir_parts) if subdir_parts else Path() + out_subdir = tables_out_base / safe_subdir / stem + save_dataset(ds, out_subdir, overwrite=overwrite) + converted_tables += 1 + except Exception as e: + click.echo(f"[{task_id}] Skipping table {file_path.name}: {e}", err=True) + continue + + if is_fastq_file(file_path): + try: + sha = compute_sha256(file_path) + gen_kwargs = { + "path": str(file_path), + "max_reads": None if max_fastq_reads <= 0 else max_fastq_reads, + "source_sha256": sha, + } + ds = Dataset.from_generator(fastq_reader, gen_kwargs=gen_kwargs) + rel_to_task = file_path.relative_to(task_path) + if flat_output: + rel_str = str(rel_to_task) + if rel_str.endswith(".gz"): + rel_str = rel_str[:-3] + rel_no_ext = str(Path(rel_str).with_suffix("")) + flat_name = f"{task_id}__fastq__{sanitize_for_dir(rel_no_ext)}" + out_subdir = out_dir / flat_name + else: + name = file_path.name[:-3] if file_path.name.endswith(".gz") else file_path.name + stem = str(Path(name).with_suffix("")) + subdir_parts = list(rel_to_task.parts[:-1]) + safe_subdir = Path(*subdir_parts) if subdir_parts else Path() + out_subdir = fastq_out_base / safe_subdir / stem + save_dataset(ds, out_subdir, overwrite=overwrite) + converted_fastq += 1 + except Exception as e: + click.echo(f"[{task_id}] Skipping FASTQ {file_path.name}: {e}", err=True) + continue + + if is_fasta_file(file_path): + try: + sha = compute_sha256(file_path) + gen_kwargs = { + "path": str(file_path), + "max_seqs": None if max_fasta_seqs <= 0 else max_fasta_seqs, + "source_sha256": sha, + } + ds = Dataset.from_generator(fasta_reader, gen_kwargs=gen_kwargs) + rel_to_task = file_path.relative_to(task_path) + if flat_output: + rel_str = str(rel_to_task) + if rel_str.endswith(".gz"): + rel_str = rel_str[:-3] + rel_no_ext = str(Path(rel_str).with_suffix("")) + flat_name = f"{task_id}__fasta__{sanitize_for_dir(rel_no_ext)}" + out_subdir = out_dir / flat_name + else: + name = file_path.name[:-3] if file_path.name.endswith(".gz") else file_path.name + stem = str(Path(name).with_suffix("")) + subdir_parts = list(rel_to_task.parts[:-1]) + safe_subdir = Path(*subdir_parts) if subdir_parts else Path() + out_subdir = fasta_out_base / safe_subdir / stem + save_dataset(ds, out_subdir, overwrite=overwrite) + converted_fasta += 1 + except Exception as e: + click.echo(f"[{task_id}] Skipping FASTA {file_path.name}: {e}", err=True) + continue + + if is_vcf_file(file_path): + try: + sha = compute_sha256(file_path) + ds = Dataset.from_generator( + vcf_reader, + gen_kwargs={"path": str(file_path), "source_sha256": sha}, + ) + rel_to_task = file_path.relative_to(task_path) + if flat_output: + rel_str = str(rel_to_task) + if rel_str.endswith(".gz"): + rel_str = rel_str[:-3] + rel_no_ext = str(Path(rel_str).with_suffix("")) + flat_name = f"{task_id}__vcf__{sanitize_for_dir(rel_no_ext)}" + out_subdir = out_dir / flat_name + else: + name = file_path.name[:-3] if file_path.name.endswith(".gz") else file_path.name + stem = str(Path(name).with_suffix("")) + subdir_parts = list(rel_to_task.parts[:-1]) + safe_subdir = Path(*subdir_parts) if subdir_parts else Path() + out_subdir = vcf_out_base / safe_subdir / stem + save_dataset(ds, out_subdir, overwrite=overwrite) + converted_vcf += 1 + except Exception as e: + click.echo(f"[{task_id}] Skipping VCF {file_path.name}: {e}", err=True) + continue + click.echo(f"[{task_id}] Converted tabular files: {converted_tables}") + click.echo(f"[{task_id}] Converted FASTQ files: {converted_fastq}") + click.echo(f"[{task_id}] Converted FASTA files: {converted_fasta}") + click.echo(f"[{task_id}] Converted VCF files: {converted_vcf}") + click.echo(f"Done. Outputs in: {out_dir}") + +if __name__ == "__main__": + os.chdir(Path(__file__).resolve().parents[1]) + main() + diff --git a/bioagent-bench/src/task_metadata.json b/bioagent-bench/src/task_metadata.json new file mode 100644 index 0000000000000000000000000000000000000000..b6996ae9c5579ce7f123bd90818a0fc2dca8b9fb --- /dev/null +++ b/bioagent-bench/src/task_metadata.json @@ -0,0 +1,271 @@ +[ + { + "task_id": "alzheimer-mouse", + "name": "Alzheimer Mouse Models: Comparative Pathway Analysis", + "description": "Analyze 5xFAD, 3xTG-AD, and PS301S mouse models: normalize counts, perform differential expression, run KEGG pathway enrichment, and compare shared pathways across models.", + "task_prompt": "Perform a comparative differential expression analysis of three different Alzheimer's Disease mouse models (5xFAD, 3xTG-AD, and PS3O1S) to identify shared molecular KEGG pathways. The output should be a CSV file with the following columns: 'pathway','5xFAD_pvalue','3xTG_AD_pvalue','PS3O1S_pvalue'. Example csv Pathway,5xFAD_pvalue,3xTG_AD_pvalue,PS3O1S_pvalue\nPhagosome Homo sapiens hsa04145,1.5045916403148935e-09,0.3102788532065793,0.4443015705596512\n ", + "download_urls": { + "data": [ + { + "filename": "data.tar.gz", + "url": "https://osf.io/download/68b20d383e0a583200f9906f/" + } + ], + "reference_data": [], + "results": [ + { + "filename": "results.tar.gz", + "url": "https://osf.io/download/68336626b5864b789da4dc7f/" + } + ] + } + }, + { + "task_id": "comparative-genomics", + "name": "Comparative Genomics: Co-evolving Gene Clusters", + "description": "The datasets consists FASTA sequences and GFF annotations of a microbial genome for Micrococcus. The goal of is to do phylogenetic reconstruction of clusters of orthologous co-evolving genes; identify functionally conserved gene clusters across the genomes and group them into co-evolving functional modules.", + "task_prompt": "Reconstruct phylogeny and identify COGs across four Micrococcus genomes; filter clusters present in all genomes, coding-only, with high-confidence annotations. The output should be a CSV file with the following columns: 'cluster_number, 'consensus_annotation'.cluster_number,consensus_annotation\n1,K07222 K07222, putative flavoprotein involved in K+ transport\n2,K01069 gloB, gloC, HAGH, hydroxyacylglutathione hydrolase [EC:3.1.2.6]\n", + "download_urls": { + "data": [ + { + "filename": "data.tar.gz", + "url": "https://osf.io/download/68b20d89324caca584780a19/" + } + ], + "reference_data": [ + { + "filename": "actinobacteria.tar.gz", + "url": "https://osf.io/download/688634a1c0fb9e14f4f27c3d/" + } + ], + "results": [ + { + "filename": "results.tar.gz", + "url": "https://osf.io/download/6886330d335b56867ee7cc56/" + } + ] + } + }, + { + "task_id": "cystic-fibrosis", + "name": "Cystic Fibrosis Mendelian Variant Identification", + "description": "The sample dataset is a simulated dataset for finding the genetic cause of Cystic fibrosis. The dataset is real sequencing data from CEPH_1463 dataset provided by the Complete Genomics Diversity Panel. It consists of sequencing of a family: 4 grandparents, 2 parents and 11 siblings. A known Mandelian disease mutation has been added on three siblings, taking care to be consistent with the underlying heplotype structure. The goal is to find the mutation causing the mendalian recessive trait - Cystic Fibrosis.", + "task_prompt": "Find the genetic cause of Cystic fibrosis; identify the causal recessive variant consistent with affected siblings NA12885, NA12886, and NA12879. The output should be a CSV file with the following columns: chromosome,position,variant_id,reference,alternate,gene_name,gene_id,annotation,impact,transcript_id,hgvs_c,hgvs_p,clinical_significance,diseases,review_status,rs_id. chromosome,position,variant_id,reference,alternate,gene_name,gene_id,annotation,impact,transcript_id,hgvs_c,hgvs_p,clinical_significance,diseases,review_status,rs_id\nX,123456789,VAR123,A,G,GENE1,ENSG00000000001,missense_variant,MODERATE,ENST00000000001,c.123A>G,p.Lys41Arg,Likely_pathogenic,Disease_A; Disease_B; not_provided,reviewed_by_expert_panel,rs0000001", + "download_urls": { + "data": [ + { + "filename": "data.tar.gz", + "url": "https://osf.io/download/68b20df289a6df6718780c40/" + } + ], + "reference_data": [ + { + "filename": "clinvar_20250521.vcf.gz", + "url": "https://osf.io/download/68345adb08d1077918ab8378/" + } + ], + "results": [ + { + "filename": "results.tar.gz", + "url": "https://osf.io/download/v8egn/" + } + ] + } + }, + { + "task_id": "deseq", + "name": "RNA-Seq Differential Expression (DESeq2)", + "description": "The dataset consists of RNA-Seq samples from Candida parapsilosis wild-type (WT) strains grown in planktonic and biofilm conditions, generated as part of a study on gene expression and biofilm formation. The samples were sequenced on the Illumina HiSeq 2000 platform. The goal of this analysis is to perform differential expression analysis using DESeq2 to identify genes that are significantly up- or down-regulated between planktonic and biofilm conditions, providing insights into biofilm-associated transcriptional changes.", + "task_prompt": "Identify differentialy expressed genes between planktonic and biofilm conditions of Candida parapsilosis. The output should be a CSV file with the following columns: gene_id,log2FoldChange,pvalue,padj\nCPAR2_00000,1.0, 2.0, 1e-44", + "download_urls": { + "data": [ + { + "filename": "SRR1278968.tar.gz", + "url": "https://osf.io/download/68468fe0b8c35b8d7fd6a397/" + }, + { + "filename": "SRR1278969.tar.gz", + "url": "https://osf.io/download/68468dc10781da6500a4da4f/" + }, + { + "filename": "SRR1278970.tar.gz", + "url": "https://osf.io/download/68469352ccfa808c4bd6a429/" + }, + { + "filename": "SRR1278971.tar.gz", + "url": "https://osf.io/download/684694513564c1f16c85b2b5/" + }, + { + "filename": "SRR1278972.tar.gz", + "url": "https://osf.io/download/6846941a3564c1f16c85b2a7/" + }, + { + "filename": "SRR1278973.tar.gz", + "url": "https://osf.io/download/68b0afa67f881b4d577da9f7/" + } + ], + "reference_data": [ + { + "filename": "reference.tar.gz", + "url": "https://osf.io/download/6846a111dfcfcd74e2ab813b/" + } + ], + "results": [ + { + "filename": "results.tar.gz", + "url": "https://osf.io/download/68b0b0fba826cb07ed161e64/" + } + ] + } + }, + { + "task_id": "evolution", + "name": "Experimental Evolution Variant Calling (E. coli)", + "description": "The experiment follows a similar strategy as in what is called an experimental evolution experiment. The final aim is to identify the genome variations in evolved lines of E. coli. The data is composed of a single ancestor line and two evolved lines. The data is from a paired-end sequencing run data from an Illumina HiSeq. This data has been post-processed in two ways already. All sequences that were identified as belonging to the PhiX genome have been removed. Illumina adapters have been removed as well already.", + "task_prompt": "Identify and annotate genome variants in two evolved lines relative to an ancestor lines of E. coli; report only variants shared by both evolved lines of moderate or higher predicted severity. The output should be a CSV file with the following columns: 'chrom','pos','ref','alt','gene','impact','effect',status'.CHROM,POS,REF,ALT,GENE,IMPACT,EFFECT,STATUS\nNODE_4_length_102007_cov_6.351643,6352,T,C,ICCIAKIK_00327,MODERATE,missense_variant,shared", + "download_urls": { + "data": [ + { + "filename": "data.tar.gz", + "url": "https://osf.io/download/68b2105d035f781d2805e42f/" + } + ], + "reference_data": [], + "results": [ + { + "filename": "results.tar.gz", + "url": "https://osf.io/download/688a0db3222020edd0e7cc66/" + } + ] + } + }, + { + "task_id": "giab", + "name": "GIAB Variant Calling", + "description": "The GIAB dataset consists of Agilent SureSelect v7 exome sequencing (~75M reads) from the NA12878 reference sample, providing high-confidence benchmark variants on GRCh38. The task is to perform germline variant calling on NA12878.", + "task_prompt": "Use the sequencing data from NA12878 (Agilent SureSelect v7, ~75M reads) to perform germline variant calling. The output should be a .vcf.gz variant call file.", + "download_urls": { + "data": [ + { + "filename": "data.tar.gz", + "url": "https://osf.io/download/68b48a274bd046405df90c9c/" + } + ], + "reference_data": [ + { + "filename": "reference.tar.gz", + "url": "https://osf.io/download/689ca8f0f375f799101f201d/" + } + ], + "results": [ + { + "filename": "results.tar.gz", + "url": "https://osf.io/download/689caa007283d4d1371f1f5a/" + } + ] + } + }, + { + "task_id": "metagenomics", + "name": "Metagenomics: Community Comparison (Cuatro Ciénegas)", + "description": "The metagenomics dataset consists of sequencing reads from the Cuatro Ciénegas Basin, comparing microbial communities under control (JC1A) and nutrient-enriched (JP4D) conditions. The task is to profile taxonomic composition and report relative abundances of bacterial taxa.", + "task_prompt": "Perform metagenomic analysis of control (JC1A) and fertilized (JP4D) samples to classify microbial taxa and estimate relative abundances. The output should be a CSV file with the following columns: 'OTU','Kingdom','Phylum','JP4D','JC1A'.OTU,Kingdom,Phylum,JP4D,JC1A\n2771361,Bacteria,Pseudomonadota,84.8612882653061,66.4367816091954", + "download_urls": { + "data": [ + { + "filename": "data.tar.gz", + "url": "https://osf.io/download/68b216b7f757a5ccb105e32f/" + } + ], + "reference_data": [ + { + "filename": "k2_standard_16gb_20241228.tar.gz", + "url": "https://genome-idx.s3.amazonaws.com/kraken/k2_standard_16gb_20241228.tar.gz" + } + ], + "results": [ + { + "filename": "results.tar.gz", + "url": "https://osf.io/download/688c803bf191a05eb312ff76/" + } + ] + } + }, + { + "task_id": "single-cell", + "name": "Single-cell RNA-seq: Skeletal Muscle Exercise Response", + "description": "The single-cell RNA-seq dataset consists of human skeletal muscle samples collected before and after acute exercise, sequenced with 10X Genomics. The task is to identify cell types and determine their transcriptional responses to exercise.", + "task_prompt": "Analyze single-cell RNA-seq data from pre- and post-exercise skeletal muscle samples. Perform clustering, cell type identification, and differential expression analysis within each cell type between conditions. The output should be a CSV file with the following columns: 'cluster_id','predicted_cell_type','gene_name','logfoldchanges','pvals','pvals_adj','direction','abs_logfc'.cluster_id,predicted_cell_type,gene_name,logfoldchanges,pvals,pvals_adj,direction,abs_logfc\n0,Perivascular cell,RNASE1,-6.658449,3.443035537523368e-15,6.886071075046736e-12,down,6.658449", + "download_urls": { + "data": [ + { + "filename": "data.tar.gz", + "url": "https://osf.io/download/68b213c2e5d5269d1805e57b/" + } + ], + "reference_data": [ + { + "filename": "reference.tar.gz", + "url": "https://osf.io/download/688e6d05f99f6f9bdec3570d/" + } + ], + "results": [ + { + "filename": "results.tar.gz", + "url": "https://osf.io/download/68b20ccefd1e1bfb14ab157f/" + } + ] + } + }, + { + "task_id": "transcript-quant", + "name": "Transcript Quantification (Simulated RNA-Seq)", + "description": "The goal is to quantify transcript expression levels from paired-end RNA-Seq reads (reads_1.fq.gz, reads_2.fq.gz) using the provided reference transcriptome (transcriptome.fa). Because the data is simulated, the quantification should exactly reproduce the underlying counts. The results represent a mapping from transcript IDs → read counts.", + "task_prompt": "Perform transcript quantification on the provided paired-end RNA-Seq reads using the transcriptome reference. The output should be a .tsv file with the following columns:'transcript_id'\t'count'.transcript_id\tcount\nENST000000001\t523", + "download_urls": { + "data": [ + { + "filename": "data.tar.gz", + "url": "https://osf.io/download/68b2141b8f0cf7d351f99175/" + } + ], + "reference_data": [], + "results": [ + { + "filename": "results.tar.gz", + "url": "https://osf.io/download/684fefc073a328e5cbb689c8/" + } + ] + } + }, + { + "task_id": "viral-metagenomics", + "name": "Viral Metagenomics: Species Identification (Dolphin)", + "description": "The viral metagenomics dataset consists of paired-end sequencing reads from a dolphin with gastroenteritis of suspected viral origin. The task is to identify viral species present in the fecal sample by assembling and classifying contigs.", + "task_prompt": "Analyze paired-end metagenomic sequencing data from a dolphin fecal sample to identify potential viral agents. Assemble and classify contigs, then summarize results by taxonomic domain and species. The output should be a CSV file with the following columns: 'contig_count','domain','species'.contig_count,domain,species\n2,Viruses,H1N1 5\n68,Unclassified,", + "download_urls": { + "data": [ + { + "filename": "data.tar.gz", + "url": "https://osf.io/download/68b2149b6d0bf0c31e05e6fd/" + } + ], + "reference_data": [ + { + "filename": "reference-data.tar.gz", + "url": "https://osf.io/download/683423565d0944b2b685b1bd/" + }, + { + "filename": "kaiju_db_viruses_2024-08-15.tgz", + "url": "https://osf.io/download/6833926d0c80ee431a85b55e/" + } + ], + "results": [ + { + "filename": "results.tar.gz", + "url": "https://osf.io/download/6833945c0c26956233a4db0e/" + } + ] + } + } +] \ No newline at end of file